eslint.config.mjs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import stylistic from '@stylistic/eslint-plugin'
  2. import sonarjs from 'eslint-plugin-sonarjs'
  3. import tseslint from 'typescript-eslint'
  4. // Strict type-aware correctness rules plus repository formatting. Tests/examples relax deliberate
  5. // mock unsafety; vendored sources retain upstream style and receive only selected safety checks.
  6. export default tseslint.config(
  7. {
  8. ignores: [
  9. '**/lib/**',
  10. '**/node_modules/**',
  11. '**/.sessions/**',
  12. '.claude/**', // harness-local state (worktrees, skills) — other checkouts, not this one's sources
  13. '**/.doc-typecheck-*/**',
  14. '**/.node-next-types-*/**',
  15. 'website/.generated/**',
  16. 'vendor/**', // vendored source keeps upstream style and idioms
  17. 'native/**', // imported landlock-run subtree: self-contained workspace with its own gates (native/README.md)
  18. '**/*.js',
  19. '**/*.mjs',
  20. '*.config.ts', // root tool configs (vitest, tsdown) — no project service
  21. 'apps/*/*.config.ts', // app build configs — outside their project programs
  22. '**/tsdown.config.ts', // package build configs — in no tsconfig program, and TS syntax breaks the parserless fallback
  23. 'packages/client/tsdown.client.ts', // shared client build preset, same standing
  24. ],
  25. },
  26. // --- our packages: full strictness -------------------------------------
  27. {
  28. files: [
  29. 'packages/*/*/src/**/*.{ts,tsx}',
  30. 'apps/*/src/**/*.{ts,tsx}',
  31. 'examples/**/*.{ts,tsx}',
  32. 'scripts/**/*.{ts,tsx}',
  33. 'website/**/*.{ts,tsx}',
  34. ],
  35. extends: [
  36. ...tseslint.configs.strictTypeChecked,
  37. ],
  38. languageOptions: {
  39. parserOptions: {
  40. // One project service resolves each file to its owning tsconfig and shares dependency
  41. // graphs. Per-package programs duplicated path-mapped and Cordis closures, reaching ~5 GB.
  42. projectService: true,
  43. tsconfigRootDir: import.meta.dirname,
  44. },
  45. },
  46. rules: {
  47. // The bug class this repo cares most about: lost promises in the loop.
  48. '@typescript-eslint/no-floating-promises': 'error',
  49. '@typescript-eslint/no-misused-promises': 'error',
  50. '@typescript-eslint/require-await': 'error',
  51. '@typescript-eslint/switch-exhaustiveness-check': ['error', {
  52. considerDefaultExhaustiveForUnions: true,
  53. }],
  54. '@typescript-eslint/no-unnecessary-condition': ['error', {
  55. allowConstantLoopConditions: true,
  56. }],
  57. // `any` requires a justification comment — enforced as: no bare casts.
  58. '@typescript-eslint/no-explicit-any': 'error',
  59. // Style points where the codebase intentionally diverges from preset:
  60. '@typescript-eslint/no-namespace': 'off', // Cordis Config-namespace idiom
  61. '@typescript-eslint/no-empty-object-type': 'off', // merge-extensible maps
  62. '@typescript-eslint/no-invalid-void-type': 'off', // event signatures
  63. '@typescript-eslint/restrict-template-expressions': ['error', {
  64. allowNumber: true,
  65. allowBoolean: true,
  66. }],
  67. // `void foo()` in arrow listeners is our idiom for intentional fire-and-forget
  68. 'no-void': 'off',
  69. '@typescript-eslint/no-unused-vars': ['error', {
  70. argsIgnorePattern: '^_',
  71. varsIgnorePattern: '^_',
  72. caughtErrorsIgnorePattern: '^_',
  73. }],
  74. },
  75. },
  76. // --- examples: demo code conforms to async interfaces without awaiting ---
  77. {
  78. files: ['examples/**/*.ts'],
  79. rules: {
  80. '@typescript-eslint/require-await': 'off',
  81. },
  82. },
  83. // --- tests: same rules, minus the friction that fights test ergonomics --
  84. {
  85. files: [
  86. 'packages/*/*/tests/**/*.{ts,tsx}',
  87. 'apps/*/tests/**/*.{ts,tsx}',
  88. 'examples/*/tests/**/*.{ts,tsx}',
  89. 'scripts/**/*.spec.{ts,tsx}',
  90. ],
  91. extends: [
  92. ...tseslint.configs.strictTypeChecked,
  93. ],
  94. languageOptions: {
  95. parserOptions: {
  96. // Same shared project service as the src block: test files resolve
  97. // through the root solution to tsconfig.host.json (its include covers
  98. // every host tests/ tree).
  99. projectService: true,
  100. tsconfigRootDir: import.meta.dirname,
  101. },
  102. },
  103. rules: {
  104. '@typescript-eslint/no-floating-promises': 'error',
  105. '@typescript-eslint/no-misused-promises': 'error',
  106. '@typescript-eslint/no-explicit-any': 'error',
  107. '@typescript-eslint/no-non-null-assertion': 'off', // assertions follow expect()s
  108. '@typescript-eslint/no-unnecessary-condition': 'off',
  109. '@typescript-eslint/require-await': 'off', // mock execute() signatures
  110. '@typescript-eslint/no-empty-function': 'off', // stub agents
  111. '@typescript-eslint/only-throw-error': 'off', // testing non-Error throws
  112. '@typescript-eslint/no-namespace': 'off',
  113. '@typescript-eslint/no-empty-object-type': 'off',
  114. '@typescript-eslint/restrict-template-expressions': 'off',
  115. '@typescript-eslint/no-unused-vars': ['error', {
  116. argsIgnorePattern: '^_',
  117. varsIgnorePattern: '^_',
  118. caughtErrorsIgnorePattern: '^_',
  119. }],
  120. },
  121. },
  122. // --- client tests: the root program excludes packages/client (host/client
  123. // Context merges collide), so the shared project service cannot resolve
  124. // them — parse these through the client aggregate explicitly.
  125. {
  126. files: [
  127. 'packages/client/*/tests/**/*.{ts,tsx}',
  128. 'scripts/client-bundle-purity.spec.ts',
  129. ],
  130. languageOptions: {
  131. parserOptions: {
  132. projectService: false,
  133. project: ['./tsconfig.client.json'],
  134. tsconfigRootDir: import.meta.dirname,
  135. },
  136. },
  137. },
  138. // --- file-local duplication (all owned TypeScript) ---------------------
  139. {
  140. files: ['packages/**/*.{ts,tsx}', 'apps/**/*.{ts,tsx}', 'examples/**/*.{ts,tsx}', 'scripts/**/*.{ts,tsx}', 'website/**/*.{ts,tsx}'],
  141. plugins: { sonarjs },
  142. rules: {
  143. // Cross-file clones are covered separately by jscpd.
  144. 'sonarjs/duplicates-in-character-class': 'error',
  145. 'sonarjs/no-all-duplicated-branches': 'error',
  146. 'sonarjs/no-duplicate-in-composite': 'error',
  147. 'sonarjs/no-duplicate-test-title': 'error',
  148. 'sonarjs/no-identical-conditions': 'error',
  149. 'sonarjs/no-identical-expressions': 'error',
  150. 'sonarjs/no-identical-functions': 'error',
  151. 'sonarjs/no-duplicated-branches': 'error',
  152. },
  153. },
  154. // --- formatting (everything we own) -------------------------------------
  155. {
  156. files: [
  157. 'packages/**/*.{ts,tsx}',
  158. 'apps/**/*.{ts,tsx}',
  159. 'examples/**/*.{ts,tsx}',
  160. 'scripts/**/*.{ts,tsx}',
  161. 'website/**/*.{ts,tsx}',
  162. 'eslint.config.mjs',
  163. ],
  164. plugins: { '@stylistic': stylistic },
  165. rules: {
  166. '@stylistic/indent': ['error', 2],
  167. '@stylistic/semi': ['error', 'never'],
  168. '@stylistic/quotes': ['error', 'single', { avoidEscape: true }],
  169. '@stylistic/comma-dangle': ['error', 'always-multiline'],
  170. '@stylistic/eol-last': ['error', 'always'],
  171. '@stylistic/no-trailing-spaces': 'error',
  172. '@stylistic/object-curly-spacing': ['error', 'always'],
  173. '@stylistic/arrow-parens': ['error', 'as-needed', { requireForBlockBody: true }],
  174. '@stylistic/member-delimiter-style': ['error', {
  175. multiline: { delimiter: 'none' },
  176. singleline: { delimiter: 'semi', requireLast: false },
  177. }],
  178. '@stylistic/max-len': ['error', { code: 140, ignoreUrls: true, ignoreStrings: true, ignoreTemplateLiterals: true }],
  179. },
  180. },
  181. )