1
0

eslint.config.mjs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  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. 'vendor/**', // vendored source keeps upstream style and idioms
  15. '**/*.js',
  16. '**/*.mjs',
  17. '*.config.ts', // root tool configs (vitest, tsdown) — no project service
  18. ],
  19. },
  20. // --- our packages: full strictness -------------------------------------
  21. {
  22. files: ['packages/*/*/src/**/*.ts', 'examples/**/*.ts', 'scripts/**/*.ts'],
  23. extends: [
  24. ...tseslint.configs.strictTypeChecked,
  25. ],
  26. languageOptions: {
  27. parserOptions: {
  28. // One project service resolves each file to its owning tsconfig and shares dependency
  29. // graphs. Per-package programs duplicated path-mapped and Cordis closures, reaching ~5 GB.
  30. projectService: true,
  31. tsconfigRootDir: import.meta.dirname,
  32. },
  33. },
  34. rules: {
  35. // The bug class this repo cares most about: lost promises in the loop.
  36. '@typescript-eslint/no-floating-promises': 'error',
  37. '@typescript-eslint/no-misused-promises': 'error',
  38. '@typescript-eslint/require-await': 'error',
  39. '@typescript-eslint/switch-exhaustiveness-check': ['error', {
  40. considerDefaultExhaustiveForUnions: true,
  41. }],
  42. '@typescript-eslint/no-unnecessary-condition': ['error', {
  43. allowConstantLoopConditions: true,
  44. }],
  45. // `any` requires a justification comment — enforced as: no bare casts.
  46. '@typescript-eslint/no-explicit-any': 'error',
  47. // Style points where the codebase intentionally diverges from preset:
  48. '@typescript-eslint/no-namespace': 'off', // Cordis Config-namespace idiom
  49. '@typescript-eslint/no-empty-object-type': 'off', // merge-extensible maps
  50. '@typescript-eslint/no-invalid-void-type': 'off', // event signatures
  51. '@typescript-eslint/restrict-template-expressions': ['error', {
  52. allowNumber: true,
  53. allowBoolean: true,
  54. }],
  55. // `void foo()` in arrow listeners is our idiom for intentional fire-and-forget
  56. 'no-void': 'off',
  57. '@typescript-eslint/no-unused-vars': ['error', {
  58. argsIgnorePattern: '^_',
  59. varsIgnorePattern: '^_',
  60. caughtErrorsIgnorePattern: '^_',
  61. }],
  62. },
  63. },
  64. // --- examples: demo code conforms to async interfaces without awaiting ---
  65. {
  66. files: ['examples/**/*.ts'],
  67. rules: {
  68. '@typescript-eslint/require-await': 'off',
  69. },
  70. },
  71. // --- tests: same rules, minus the friction that fights test ergonomics --
  72. {
  73. files: ['packages/*/*/tests/**/*.ts', 'examples/*/tests/**/*.ts', 'scripts/**/*.spec.ts'],
  74. extends: [
  75. ...tseslint.configs.strictTypeChecked,
  76. ],
  77. languageOptions: {
  78. parserOptions: {
  79. // Same shared project service as the src block: test files resolve
  80. // through the root tsconfig (its include covers every tests/ tree).
  81. projectService: true,
  82. tsconfigRootDir: import.meta.dirname,
  83. },
  84. },
  85. rules: {
  86. '@typescript-eslint/no-floating-promises': 'error',
  87. '@typescript-eslint/no-misused-promises': 'error',
  88. '@typescript-eslint/no-explicit-any': 'error',
  89. '@typescript-eslint/no-non-null-assertion': 'off', // assertions follow expect()s
  90. '@typescript-eslint/no-unnecessary-condition': 'off',
  91. '@typescript-eslint/require-await': 'off', // mock execute() signatures
  92. '@typescript-eslint/no-empty-function': 'off', // stub agents
  93. '@typescript-eslint/only-throw-error': 'off', // testing non-Error throws
  94. '@typescript-eslint/no-namespace': 'off',
  95. '@typescript-eslint/no-empty-object-type': 'off',
  96. '@typescript-eslint/restrict-template-expressions': 'off',
  97. '@typescript-eslint/no-unused-vars': ['error', {
  98. argsIgnorePattern: '^_',
  99. varsIgnorePattern: '^_',
  100. caughtErrorsIgnorePattern: '^_',
  101. }],
  102. },
  103. },
  104. // --- file-local duplication (all owned TypeScript) ---------------------
  105. {
  106. files: ['packages/**/*.ts', 'examples/**/*.ts', 'scripts/**/*.ts'],
  107. plugins: { sonarjs },
  108. rules: {
  109. // Cross-file clones are covered separately by jscpd.
  110. 'sonarjs/duplicates-in-character-class': 'error',
  111. 'sonarjs/no-all-duplicated-branches': 'error',
  112. 'sonarjs/no-duplicate-in-composite': 'error',
  113. 'sonarjs/no-duplicate-test-title': 'error',
  114. 'sonarjs/no-identical-conditions': 'error',
  115. 'sonarjs/no-identical-expressions': 'error',
  116. 'sonarjs/no-identical-functions': 'error',
  117. 'sonarjs/no-duplicated-branches': 'error',
  118. },
  119. },
  120. // --- formatting (everything we own) -------------------------------------
  121. {
  122. files: ['packages/**/*.ts', 'examples/**/*.ts', 'scripts/**/*.ts', 'eslint.config.mjs'],
  123. plugins: { '@stylistic': stylistic },
  124. rules: {
  125. '@stylistic/indent': ['error', 2],
  126. '@stylistic/semi': ['error', 'never'],
  127. '@stylistic/quotes': ['error', 'single', { avoidEscape: true }],
  128. '@stylistic/comma-dangle': ['error', 'always-multiline'],
  129. '@stylistic/eol-last': ['error', 'always'],
  130. '@stylistic/no-trailing-spaces': 'error',
  131. '@stylistic/object-curly-spacing': ['error', 'always'],
  132. '@stylistic/arrow-parens': ['error', 'as-needed', { requireForBlockBody: true }],
  133. '@stylistic/member-delimiter-style': ['error', {
  134. multiline: { delimiter: 'none' },
  135. singleline: { delimiter: 'semi', requireLast: false },
  136. }],
  137. '@stylistic/max-len': ['error', { code: 140, ignoreUrls: true, ignoreStrings: true, ignoreTemplateLiterals: true }],
  138. },
  139. },
  140. )