eslint-config-agent 3.0.4 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +158 -0
  2. package/README.md +592 -26
  3. package/configs/base-plugins.js +32 -0
  4. package/configs/config-files.js +19 -3
  5. package/configs/examples.js +12 -1
  6. package/configs/javascript.js +12 -4
  7. package/configs/length-rule-scope.js +27 -0
  8. package/configs/overrides.js +3 -14
  9. package/configs/test-files.js +40 -1
  10. package/configs/tsx.js +10 -0
  11. package/configs/typescript.js +44 -2
  12. package/exports/ddd.d.ts +10 -0
  13. package/exports/ddd.js +1 -3
  14. package/exports/incremental.d.ts +11 -0
  15. package/exports/incremental.js +39 -0
  16. package/exports/recommended-incremental.d.ts +11 -0
  17. package/exports/recommended-incremental.js +46 -0
  18. package/exports/recommended.d.ts +14 -0
  19. package/exports/recommended.js +110 -0
  20. package/exports/to-warnings.d.ts +20 -0
  21. package/exports/to-warnings.js +41 -0
  22. package/index.d.ts +18 -0
  23. package/index.js +883 -7
  24. package/package.json +46 -17
  25. package/plugins/index.js +6 -0
  26. package/rules/index.js +20 -22
  27. package/rules/no-inline-union-types/index.js +8 -6
  28. package/rules/no-process-env-properties/index.js +4 -4
  29. package/rules/no-record-literal-types/index.js +3 -3
  30. package/rules/plugin/import/index.js +41 -0
  31. package/rules/plugin/index.js +0 -2
  32. package/rules/plugin/n/index.js +16 -2
  33. package/rules/plugin/n/no-process-env/index.js +4 -4
  34. package/rules/plugin/react/index.js +150 -2
  35. package/rules/plugin/typescript-eslint/index.js +406 -0
  36. package/rules/require-spec-file-tsx/README.md +57 -0
  37. package/rules/require-spec-file-tsx/helpers.js +93 -0
  38. package/rules/require-spec-file-tsx/index.js +109 -0
  39. package/rules/plugin/jsx-a11y/index.js +0 -3
@@ -46,6 +46,37 @@ export const basePluginsConfig = [
46
46
  ],
47
47
  },
48
48
  },
49
+ // Require spec files for .tsx/.jsx source files too.
50
+ //
51
+ // `ddd/require-spec-file` only inspects `.js`/`.ts` files, so React/Preact
52
+ // components — the code this config primarily targets — were silently exempt
53
+ // from the spec requirement. This custom rule covers the JSX extensions with
54
+ // the same heuristic, completing the "spec file for every source file"
55
+ // promise. Its excludePatterns mirror the JSX-relevant entries of the `ddd`
56
+ // list above (index/stories files are exempt; the error/exception file
57
+ // patterns are exempt; `.spec`/`.test` files are skipped by the rule
58
+ // itself).
59
+ {
60
+ rules: {
61
+ 'custom/require-spec-file-tsx': [
62
+ 'error',
63
+ {
64
+ excludePatterns: [
65
+ '**/index.tsx',
66
+ '**/index.jsx',
67
+ '**/*.stories.{tsx,jsx}',
68
+ // Mirror the ddd error/exception exemptions for JSX extensions so
69
+ // an error component (e.g. an error boundary) is not singled out
70
+ // for a spec file when its `.ts` equivalent is exempt.
71
+ '**/*-error.{tsx,jsx}',
72
+ '**/*.error.{tsx,jsx}',
73
+ '**/errors/**',
74
+ '**/exceptions/**',
75
+ ],
76
+ },
77
+ ],
78
+ },
79
+ },
49
80
  // Disable DDD for config infrastructure files
50
81
  {
51
82
  files: [
@@ -60,6 +91,7 @@ export const basePluginsConfig = [
60
91
  ],
61
92
  rules: {
62
93
  'ddd/require-spec-file': 'off',
94
+ 'custom/require-spec-file-tsx': 'off',
63
95
  },
64
96
  },
65
97
  ]
@@ -1,16 +1,32 @@
1
1
  /**
2
2
  * Configuration for configuration files
3
3
  * Allows hardcoded values and default exports in config files
4
+ *
5
+ * The `*.config.*` and `eslint.config.*` globs are recursive (they are
6
+ * prefixed with a globstar) so the relaxations also reach config files that
7
+ * live below the repo root — e.g. `apps/web/vite.config.ts` or
8
+ * `packages/ui/eslint.config.mjs` in a monorepo. Previously these used
9
+ * root-only globs (`*.config.{...}`), so a nested `eslint.config.mjs` /
10
+ * `vite.config.ts` silently escaped the config-file relaxations and got hit
11
+ * with `default/no-localhost`, `default/no-hardcoded-urls`, `max-lines`, etc.
12
+ * — the same asymmetry the `.mjs`/`.cjs` coverage fix addressed for the
13
+ * JavaScript rule set. This now mirrors the recursive-glob convention every
14
+ * other config block already uses.
15
+ *
16
+ * `index.js` is intentionally left root-scoped: broadening it to a recursive
17
+ * glob would relax these rules for every barrel/entry file in a consumer's
18
+ * source tree, masking real issues. Only files whose names mark them as
19
+ * configuration (`*.config.*`, `eslint.config.*`) are matched recursively.
4
20
  */
5
21
 
6
22
  export const configFilesConfig = [
7
23
  // Configuration files - allow hardcoded values for configuration purposes
8
24
  {
9
25
  files: [
10
- '*.config.{js,ts}',
11
- 'eslint.config.{js,ts}',
26
+ '**/*.config.{js,ts,mjs,cjs}',
27
+ '**/eslint.config.{js,ts,mjs,cjs}',
12
28
  'index.js',
13
- '**/configs/**/*.{js,ts}',
29
+ '**/configs/**/*.{js,ts,mjs,cjs}',
14
30
  ],
15
31
  rules: {
16
32
  'default/no-localhost': 'off',
@@ -45,7 +45,7 @@ const sharedRestrictedSyntax = [
45
45
  message:
46
46
  'Exporting from external libraries is not allowed. Only re-export from relative paths or scoped packages.',
47
47
  },
48
- allRules.noProcessEnvPropertiesConfig,
48
+ allRules.noProcessEnvironmentPropertiesConfig,
49
49
  allRules.noExportSpecifiersConfig,
50
50
  ...allRules.noDefaultClassExportRules,
51
51
  ]
@@ -83,10 +83,21 @@ export const examplesConfig = [
83
83
  // Relax file/function length limits for concise examples
84
84
  'max-lines-per-function': 'off',
85
85
  'max-lines': 'off',
86
+ // Example fixtures demonstrate rule behavior and use `console.log` as
87
+ // illustrative sample code, so the shared `no-console` ban does not apply.
88
+ 'no-console': 'off',
86
89
  // Examples don't need JSDoc
87
90
  'jsdoc/require-jsdoc': 'off',
88
91
  'jsdoc/require-param': 'off',
89
92
  'jsdoc/require-returns': 'off',
90
93
  },
91
94
  },
95
+ {
96
+ // JavaScript example fixtures (the `.ts` block above only matches `.ts`).
97
+ // They likewise use `console.log` as sample code.
98
+ files: ['**/rules/**/examples/**/*.js'],
99
+ rules: {
100
+ 'no-console': 'off',
101
+ },
102
+ },
92
103
  ]
@@ -1,8 +1,14 @@
1
1
  /**
2
2
  * JavaScript File Configurations
3
3
  *
4
- * Handles all JavaScript (.js) file-specific rules.
4
+ * Handles all JavaScript (.js, .mjs, .cjs) file-specific rules.
5
5
  * Plugin registration removed - all plugins registered globally in index.js.
6
+ *
7
+ * The ESM/CommonJS extensions (`.mjs`/`.cjs`) are linted with the same rule set
8
+ * as plain `.js`, mirroring how the TypeScript config covers `.mts`/`.cts`
9
+ * alongside `.ts`. Previously these were ignored here, so `.mjs`/`.cjs` source
10
+ * files silently escaped the package's signature `no-restricted-syntax`,
11
+ * `single-export`, and `required-exports` rules.
6
12
  */
7
13
 
8
14
  import globals from 'globals'
@@ -10,15 +16,13 @@ import globals from 'globals'
10
16
  export const javascriptConfig = (sharedRules, sharedRestrictedSyntax) => [
11
17
  // JavaScript files (not JSX)
12
18
  {
13
- files: ['**/*.js'],
19
+ files: ['**/*.js', '**/*.mjs', '**/*.cjs'],
14
20
  ignores: [
15
21
  '**/node_modules/**',
16
22
  '**/dist/**',
17
23
  '**/build/**',
18
24
  'pnpm-lock.yaml',
19
25
  '**/*.umd.js',
20
- '**/*.cjs',
21
- '**/*.mjs',
22
26
  '**/*.stories.{js,jsx,ts,tsx}',
23
27
  '**/rules/**/index.js',
24
28
  ],
@@ -70,6 +74,10 @@ export const javascriptConfig = (sharedRules, sharedRestrictedSyntax) => [
70
74
  },
71
75
  rules: {
72
76
  ...sharedRules,
77
+ // Build/CI tooling under `scripts/` is a console program: printing to
78
+ // stdout is its job, so the shared `no-console` ban (aimed at leftover
79
+ // debugging in shipped source) does not apply here.
80
+ 'no-console': 'off',
73
81
  'no-restricted-syntax': ['error', ...sharedRestrictedSyntax],
74
82
  },
75
83
  },
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Function/File Length Rule Scope
3
+ *
4
+ * `files`/`ignores` scope shared by the strict (error-level)
5
+ * `max-lines-per-function`/`max-lines` block in `configs/overrides.js` and the
6
+ * `recommended` preset's warn-level equivalent (`exports/recommended.js`).
7
+ * Extracted to its own module so the two can never drift apart — the preset
8
+ * only needs to relax the *severity*, not widen or narrow which files the
9
+ * rules apply to (tests, stories, config files, and this package's own
10
+ * `index.js` stay exempt either way).
11
+ */
12
+
13
+ export const lengthRuleFileMatch = {
14
+ files: ['**/*.{ts,tsx,js,jsx}'],
15
+ ignores: [
16
+ '**/*.stories.{js,jsx,ts,tsx}',
17
+ '**/*.test.{js,jsx,ts,tsx}',
18
+ '**/*.spec.{js,jsx,ts,tsx}',
19
+ '**/test/**/*.{js,jsx,ts,tsx}',
20
+ '**/tests/**/*.{js,jsx,ts,tsx}',
21
+ '**/__tests__/**/*.{js,jsx,ts,tsx}',
22
+ '**/configs/**/*.{js,ts}',
23
+ '*.config.{js,ts}',
24
+ 'eslint.config.{js,ts}',
25
+ 'index.js',
26
+ ],
27
+ }
@@ -7,6 +7,7 @@
7
7
 
8
8
  import globals from 'globals'
9
9
  import allRules from '../rules/index.js'
10
+ import { lengthRuleFileMatch } from './length-rule-scope.js'
10
11
 
11
12
  export const overridesConfig = (
12
13
  sharedRestrictedSyntax,
@@ -79,7 +80,7 @@ export const overridesConfig = (
79
80
  'Type assertions with indexed access types like "as (typeof X)[number]" are not allowed. Use a named type instead.',
80
81
  },
81
82
  allRules.noTypeAssertionsConfig,
82
- allRules.noProcessEnvPropertiesConfig,
83
+ allRules.noProcessEnvironmentPropertiesConfig,
83
84
  allRules.noExportSpecifiersConfig,
84
85
  ],
85
86
  },
@@ -87,19 +88,7 @@ export const overridesConfig = (
87
88
 
88
89
  // Function and file length rules - strict error thresholds
89
90
  {
90
- files: ['**/*.{ts,tsx,js,jsx}'],
91
- ignores: [
92
- '**/*.stories.{js,jsx,ts,tsx}',
93
- '**/*.test.{js,jsx,ts,tsx}',
94
- '**/*.spec.{js,jsx,ts,tsx}',
95
- '**/test/**/*.{js,jsx,ts,tsx}',
96
- '**/tests/**/*.{js,jsx,ts,tsx}',
97
- '**/__tests__/**/*.{js,jsx,ts,tsx}',
98
- '**/configs/**/*.{js,ts}',
99
- '*.config.{js,ts}',
100
- 'eslint.config.{js,ts}',
101
- 'index.js',
102
- ],
91
+ ...lengthRuleFileMatch,
103
92
  rules: {
104
93
  'max-lines-per-function': allRules.maxFunctionLinesError,
105
94
  'max-lines': allRules.maxFileLinesError,
@@ -58,7 +58,7 @@ const sharedRestrictedSyntax = [
58
58
  message:
59
59
  'Exporting from external libraries is not allowed. Only re-export from relative paths or scoped packages.',
60
60
  },
61
- allRules.noProcessEnvPropertiesConfig,
61
+ allRules.noProcessEnvironmentPropertiesConfig,
62
62
  allRules.noExportSpecifiersConfig,
63
63
  ...allRules.noDefaultClassExportRules,
64
64
  ]
@@ -101,13 +101,49 @@ export const testFilesConfig = [
101
101
  rules: {
102
102
  'max-lines-per-function': 'off',
103
103
  'max-lines': 'off', // Ignore file length limits in test and spec files
104
+ // The shared config bans bare `console.log` in shipped source, but test
105
+ // and rule-tester spec files use `console.log` legitimately to report
106
+ // progress and assertions (this package's own `*.test.js` runners do
107
+ // exactly that), and fixtures embed it as sample code. Keep the rule on
108
+ // for real source and off here so test logging is not flagged.
109
+ 'no-console': 'off',
104
110
  'default/no-localhost': ['error', { allowInTests: true }],
105
111
  'default/no-hardcoded-urls': ['error', { allowInTests: true }],
106
112
  'default/no-default-params': 'off', // Allow default parameters in test files for demonstration purposes
113
+ // Test and rule-tester spec files routinely embed source snippets inside
114
+ // string literals as fixtures (e.g. `code: 'const x = `${y}`'`), where a
115
+ // literal `${...}` is the intended payload, not a mis-quoted template.
116
+ // Keep no-template-curly-in-string on for real source but off here so the
117
+ // fixtures are not flagged.
118
+ 'no-template-curly-in-string': 'off',
107
119
  // Disable error plugin rules in test files
108
120
  'error/no-generic-error': 'off',
109
121
  'error/require-custom-error': 'off',
110
122
  'error/no-throw-literal': 'off',
123
+ // Relax the noisy `eslint-plugin-security` heuristics for test code.
124
+ //
125
+ // These rules exist to harden *shipped* code against attacker-controlled
126
+ // input, but their detection is purely syntactic and fires constantly on
127
+ // perfectly safe test setup: a fixture path built from a temp dir
128
+ // (`detect-non-literal-fs-filename`), indexing a lookup table by a loop
129
+ // variable (`detect-object-injection`), compiling a pattern from a test
130
+ // input (`detect-non-literal-regexp`), spawning a helper binary in an
131
+ // integration test (`detect-child-process`), or `require`-ing a module
132
+ // resolved at runtime in a fixture loader (`detect-non-literal-require`).
133
+ // None of these are vulnerabilities in test files — the inputs are the
134
+ // test's own literals, not untrusted data, and the code never reaches
135
+ // production — so they are false positives that force adopters to litter
136
+ // specs with `eslint-disable` comments (this package's own
137
+ // `scripts/test-runner.js` already does exactly that). Turning them off
138
+ // for tests mirrors the `error/*` relaxations above; the genuinely
139
+ // value-adding security rules (`detect-unsafe-regex` for ReDoS,
140
+ // `detect-eval-with-expression`, `detect-buffer-noassert`, the timing and
141
+ // bidi-character checks) stay enabled everywhere.
142
+ 'security/detect-non-literal-fs-filename': 'off',
143
+ 'security/detect-object-injection': 'off',
144
+ 'security/detect-non-literal-regexp': 'off',
145
+ 'security/detect-child-process': 'off',
146
+ 'security/detect-non-literal-require': 'off',
111
147
  // Allow multiple exports in test files for testing import/export patterns
112
148
  'no-restricted-syntax': [
113
149
  'warn',
@@ -204,6 +240,9 @@ export const testFilesConfig = [
204
240
  'jsdoc/require-jsdoc': 'off',
205
241
  'jsdoc/require-param': 'off',
206
242
  'jsdoc/require-returns': 'off',
243
+ // Spec files act as CLI test runners and legitimately call process.exit()
244
+ // to report pass/fail status — the unicorn rule is for library code only.
245
+ 'unicorn/no-process-exit': 'off',
207
246
  },
208
247
  },
209
248
 
package/configs/tsx.js CHANGED
@@ -30,9 +30,19 @@ export const tsxConfig = (
30
30
  },
31
31
  },
32
32
  settings: {
33
+ react: {
34
+ version: 'detect',
35
+ },
33
36
  'import/resolver': {
34
37
  typescript: {},
35
38
  },
39
+ // Tell eslint-plugin-import to parse TypeScript dependency files with the
40
+ // TS parser. Without this, the resolver finds the file but the plugin
41
+ // cannot read its imports/exports, so cross-file rules like
42
+ // import/no-cycle silently never fire on .ts/.tsx code.
43
+ 'import/parsers': {
44
+ '@typescript-eslint/parser': ['.ts', '.tsx', '.mts', '.cts'],
45
+ },
36
46
  },
37
47
  rules: {
38
48
  ...sharedRules,
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * TypeScript File Configurations
3
3
  *
4
- * Handles all TypeScript (.ts) file-specific rules.
4
+ * Handles all TypeScript (.ts, .mts, .cts) file-specific rules.
5
5
  * Plugin registration removed - all plugins registered globally in index.js.
6
6
  */
7
7
 
@@ -15,7 +15,7 @@ export const typescriptConfig = (
15
15
  ) => [
16
16
  // TypeScript files - Base config
17
17
  {
18
- files: ['**/*.ts'],
18
+ files: ['**/*.ts', '**/*.mts', '**/*.cts'],
19
19
  ignores: [
20
20
  '**/node_modules/**',
21
21
  '**/dist/**',
@@ -30,15 +30,57 @@ export const typescriptConfig = (
30
30
  },
31
31
  },
32
32
  settings: {
33
+ react: {
34
+ version: 'detect',
35
+ },
33
36
  'import/resolver': {
34
37
  typescript: {},
35
38
  },
39
+ // Tell eslint-plugin-import to parse TypeScript dependency files with the
40
+ // TS parser. Without this, the resolver finds the file but the plugin
41
+ // cannot read its imports/exports, so cross-file rules like
42
+ // import/no-cycle silently never fire on .ts/.tsx code.
43
+ 'import/parsers': {
44
+ '@typescript-eslint/parser': ['.ts', '.tsx', '.mts', '.cts'],
45
+ },
36
46
  },
37
47
  rules: {
38
48
  ...sharedRules,
39
49
  ...allRules.typescriptEslintRules,
40
50
  'no-undef': 'off', // TypeScript handles this
51
+ 'jsdoc/require-jsdoc': [
52
+ 'error',
53
+ {
54
+ require: {
55
+ ClassDeclaration: true,
56
+ FunctionDeclaration: true,
57
+ },
58
+ },
59
+ ],
41
60
  'custom/no-default-class-export': 'error',
61
+ // Require an explicit return type on every function, not just the
62
+ // exported ones `explicit-module-boundary-types` would cover. Inferred
63
+ // return types are computed bottom-up from the body, so a refactor deep
64
+ // inside a helper can silently widen or change what it returns — a `null`
65
+ // branch added here, a `Promise` introduced there — and nothing flags it
66
+ // until that drift surfaces far away at a call site (or type-checks fine
67
+ // and ships as a runtime bug). Writing the type down inverts the check:
68
+ // the function body is now verified against the contract the author
69
+ // declared, so the mistake fails at the function itself instead of
70
+ // leaking outward. This is exactly the shortcut an AI assistant takes
71
+ // when it emits a helper and lets inference "figure out" the return type,
72
+ // which puts it squarely in this config's explicit-over-clever,
73
+ // fail-locally stance. It is type-aware but cheap (it reads the function
74
+ // signature, no whole-program analysis) and is in neither
75
+ // `eslint:recommended` nor typescript-eslint's `strictTypeChecked` /
76
+ // `stylisticTypeChecked` presets this config extends, so it must be
77
+ // enabled explicitly — which is why `tools-view` re-adds it by hand on
78
+ // top of the base config. It is scoped to `.ts`/`.mts`/`.cts` (this
79
+ // file's `files`) and deliberately NOT applied to `.tsx`: requiring an
80
+ // explicit return type on every JSX component is high-noise for little
81
+ // gain, so React function components stay exempt, matching how
82
+ // `tools-view` enables it on `src/**/*.ts` only.
83
+ '@typescript-eslint/explicit-function-return-type': 'error',
42
84
  'single-export/single-export': 'error',
43
85
  'required-exports/required-exports': [
44
86
  'error',
@@ -0,0 +1,10 @@
1
+ import type { Linter } from 'eslint'
2
+
3
+ /**
4
+ * DDD preset. The DDD rules (`require-spec-file`) now ship in the base config,
5
+ * so this entry point is equivalent to the default `eslint-config-agent`
6
+ * export and is kept for backward compatibility.
7
+ */
8
+ declare const config: Linter.Config[]
9
+
10
+ export default config
package/exports/ddd.js CHANGED
@@ -10,6 +10,4 @@
10
10
  * - import config from "eslint-config-agent/ddd" (same as above)
11
11
  */
12
12
 
13
- import config from '../index.js'
14
-
15
- export default config
13
+ export { default } from '../index.js'
@@ -0,0 +1,11 @@
1
+ import type { Linter } from 'eslint'
2
+
3
+ /**
4
+ * Incremental adoption preset: the full `eslint-config-agent` ruleset with every
5
+ * error-level rule downgraded to a warning. `eslint` exits `0` so CI stays
6
+ * green while the complete backlog is still reported, letting a team burn it
7
+ * down before flipping back to errors.
8
+ */
9
+ declare const config: Linter.Config[]
10
+
11
+ export default config
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Incremental adoption preset.
3
+ *
4
+ * Dropping the full strict ruleset onto an existing codebase surfaces a large
5
+ * backlog of pre-existing violations at once, so `eslint` exits non-zero and CI
6
+ * goes red on the very first run. Teams then either give up or copy-paste a
7
+ * helper that rewrites every `error` rule to `warn` — the exact same block,
8
+ * repeated in every repo that adopts this config.
9
+ *
10
+ * This entry point ships that pattern so it no longer has to be copy-pasted:
11
+ *
12
+ * ```js
13
+ * import incremental from 'eslint-config-agent/incremental'
14
+ *
15
+ * export default incremental
16
+ * ```
17
+ *
18
+ * `incremental` is the full `eslint-config-agent` ruleset with every
19
+ * error-level rule downgraded to a warning. `eslint` exits `0`, so `pnpm lint`
20
+ * (and CI) stays green, while the complete ruleset is still reported — letting a
21
+ * team burn the backlog down gradually and flip back to errors once it is clear.
22
+ *
23
+ * To enforce a rule as a hard error before the rest of the backlog is cleared,
24
+ * append your own override layer, which wins over the warned defaults:
25
+ *
26
+ * ```js
27
+ * import incremental from 'eslint-config-agent/incremental'
28
+ *
29
+ * export default [
30
+ * ...incremental,
31
+ * { rules: { eqeqeq: ['error', 'always'] } },
32
+ * ]
33
+ * ```
34
+ */
35
+
36
+ import config from '../index.js'
37
+ import { toWarnings } from './to-warnings.js'
38
+
39
+ export default config.map(toWarnings)
@@ -0,0 +1,11 @@
1
+ import type { Linter } from 'eslint'
2
+
3
+ /**
4
+ * Recommended + incremental preset: the `recommended` config with every
5
+ * surviving error-level rule downgraded to a warning. The gentlest on-ramp —
6
+ * the noisiest rules are off and everything else warns, so `eslint` exits `0`
7
+ * while the remaining backlog is still reported.
8
+ */
9
+ declare const config: Linter.Config[]
10
+
11
+ export default config
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Recommended + incremental (relaxed, warn-level) preset.
3
+ *
4
+ * The package already ships two adoption on-ramps, but each only solves half of
5
+ * what an existing codebase hits on the first lint run:
6
+ *
7
+ * - `recommended` turns the most divisive rules **off** (spec files, single
8
+ * export, custom `error/*`, optional chaining / nullish-coalescing bans,
9
+ * and so on) — but every remaining rule still reports as an **error**, so a
10
+ * real backlog still fails CI on the first run.
11
+ * - `incremental` keeps **every** rule reporting but downgrades them all to
12
+ * **warnings** — so CI stays green, yet the divisive rules keep firing as a
13
+ * wall of warnings on idiomatic TypeScript and React/Preact + Tailwind code.
14
+ *
15
+ * The gentlest possible on-ramp is the combination of the two: relax the
16
+ * divisive rules entirely *and* downgrade everything else to a warning. That is
17
+ * exactly this preset — the `recommended` config with every surviving
18
+ * error-level rule rewritten to `warn`:
19
+ *
20
+ * ```js
21
+ * import recommendedIncremental from 'eslint-config-agent/recommended-incremental'
22
+ *
23
+ * export default recommendedIncremental
24
+ * ```
25
+ *
26
+ * `eslint` exits `0`, so `pnpm lint` (and CI) stays green, the noisiest rules
27
+ * are silent, and the remaining quality rules show up as warnings you can burn
28
+ * down before tightening back up.
29
+ *
30
+ * To enforce a rule as a hard error before the rest of the backlog is cleared,
31
+ * append your own override layer, which wins over the warned defaults:
32
+ *
33
+ * ```js
34
+ * import recommendedIncremental from 'eslint-config-agent/recommended-incremental'
35
+ *
36
+ * export default [
37
+ * ...recommendedIncremental,
38
+ * { rules: { eqeqeq: ['error', 'always'] } },
39
+ * ]
40
+ * ```
41
+ */
42
+
43
+ import recommended from './recommended.js'
44
+ import { toWarnings } from './to-warnings.js'
45
+
46
+ export default recommended.map(toWarnings)
@@ -0,0 +1,14 @@
1
+ import type { Linter } from 'eslint'
2
+
3
+ /**
4
+ * Recommended (relaxed) preset: the default config with the most divisive rules
5
+ * turned off (spec files, single export, custom `error/*`, optional chaining /
6
+ * nullish-coalescing bans, and so on) for incremental adoption.
7
+ * `max-lines-per-function`/`max-lines` are downgraded to `warn` (same 70/100
8
+ * line thresholds as the strict default) so a real codebase's legacy long
9
+ * functions/files surface as backlog instead of failing CI. Every other
10
+ * surviving rule still reports as an error.
11
+ */
12
+ declare const config: Linter.Config[]
13
+
14
+ export default config
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Recommended (relaxed) preset.
3
+ *
4
+ * The default export is intentionally strict — it is tuned for greenfield
5
+ * projects that adopt every convention from day one (a spec file beside every
6
+ * source file, custom Error classes, no optional chaining / nullish
7
+ * coalescing, single export per module, and so on).
8
+ *
9
+ * Existing codebases usually cannot satisfy all of that at once, so they end
10
+ * up copying the same block of rule overrides into their own
11
+ * `eslint.config.js` just to get the config to load without a wall of errors.
12
+ *
13
+ * This preset bundles those common overrides so incremental adopters can opt
14
+ * into the core quality rules immediately while deferring the most opinionated
15
+ * ones:
16
+ *
17
+ * ```js
18
+ * import recommended from 'eslint-config-agent/recommended'
19
+ *
20
+ * export default recommended
21
+ * ```
22
+ *
23
+ * Re-enable any individual rule by appending your own override layer.
24
+ */
25
+
26
+ import config from '../index.js'
27
+ import { noProcessEnvironmentPropertiesConfig } from '../rules/no-process-env-properties/index.js'
28
+ import { errorOptions as maxFunctionLinesErrorOptions } from '../rules/max-function-lines/index.js'
29
+ import { errorOptions as maxFileLinesErrorOptions } from '../rules/max-file-lines/index.js'
30
+ import { lengthRuleFileMatch } from '../configs/length-rule-scope.js'
31
+
32
+ const relaxedOverrides = {
33
+ name: 'eslint-config-agent/recommended-overrides',
34
+ rules: {
35
+ // Don't require a `*.spec.*` file alongside every source file. The strict
36
+ // default splits this requirement across two rules: `ddd/require-spec-file`
37
+ // covers `.js`/`.ts` (the upstream `eslint-plugin-ddd` rule bails out on any
38
+ // other extension), and the bundled `custom/require-spec-file-tsx` covers
39
+ // `.tsx`/`.jsx` — i.e. the React/Preact components this config primarily
40
+ // targets. Relaxing only the first would still force a spec file beside
41
+ // every component, defeating the preset's purpose for its core audience, so
42
+ // both halves are disabled together here.
43
+ 'ddd/require-spec-file': 'off',
44
+ 'custom/require-spec-file-tsx': 'off',
45
+ // Allow multiple exports per module.
46
+ 'single-export/single-export': 'off',
47
+ 'required-exports/required-exports': 'off',
48
+ // Allow generic `Error` instead of mandating custom Error classes.
49
+ 'error/no-generic-error': 'off',
50
+ 'error/require-custom-error': 'off',
51
+ 'error/no-literal-error-message': 'off',
52
+ // Don't require a JSDoc block on every exported function and class. The
53
+ // strict default enables `jsdoc/require-jsdoc` at `error` for every
54
+ // `FunctionDeclaration` and `ClassDeclaration` (see `index.js` and
55
+ // `configs/typescript.js`), so adopting the config on an existing codebase
56
+ // produces a wall of `Missing JSDoc comment` errors on day one. This is the
57
+ // third of the three highest-volume on-ramp rules the README calls out —
58
+ // alongside `ddd/require-spec-file` and `error/no-literal-error-message`,
59
+ // both already relaxed above — so relax it here to match the preset's
60
+ // stated incremental-adoption purpose. Only the "document everything"
61
+ // requirement is lifted: the jsdoc *content* rules (`check-param-names`,
62
+ // `require-returns`, ...) stay enabled, so any JSDoc that is actually
63
+ // written is still validated.
64
+ 'jsdoc/require-jsdoc': 'off',
65
+ // Allow default parameter values.
66
+ 'default/no-default-params': 'off',
67
+ // Allow `type` aliases, not just `interface` declarations.
68
+ '@typescript-eslint/consistent-type-definitions': 'off',
69
+ // Don't require a semantic `className` on every HTML element, and don't
70
+ // reject Tailwind-only class lists. The strict default enables
71
+ // `jsx-classname/require-classname` with `{ ignoreTailwind: true }`, which
72
+ // errors both on elements with no `className` and on elements whose
73
+ // `className` contains only Tailwind utilities (e.g.
74
+ // `<div className="flex gap-2" />`). That makes the config impossible to
75
+ // adopt incrementally in any React/Preact + Tailwind codebase — by far the
76
+ // most common modern setup — so relax it for the recommended preset.
77
+ 'jsx-classname/require-classname': 'off',
78
+ // The most divisive layer: optional chaining (`?.`), nullish coalescing
79
+ // (`??`), type assertions, switch-default bans, and inline union/record
80
+ // type restrictions. Removing them lets idiomatic TypeScript through during
81
+ // adoption. The process.env guard (`no-process-env-properties`) is NOT in
82
+ // this group: scattered `process.env.FOO` access is a configuration smell
83
+ // regardless of adoption stage, so it is kept as the one surviving
84
+ // no-restricted-syntax check.
85
+ 'no-restricted-syntax': ['error', noProcessEnvironmentPropertiesConfig],
86
+ },
87
+ }
88
+
89
+ // The strict default promotes function/file length to hard errors in its
90
+ // final override layer (`configs/overrides.js`'s "Function and file length
91
+ // rules" block) — at >70 lines per function and >100 lines per file — which
92
+ // blocks incremental adoption: an existing codebase pointing `recommended` at
93
+ // its real source tree still gets fatal errors on every long legacy
94
+ // function/file, exactly the "wall of errors" this preset exists to avoid
95
+ // (see issue #85). Downgrade both to `warn` while keeping the same 70/100
96
+ // thresholds — and the same file scope (`lengthRuleFileMatch`, shared with
97
+ // the strict block) — so the signal still surfaces in `eslint` output and the
98
+ // backlog can be burned down, but it no longer fails a CI run that treats
99
+ // errors as fatal. Consumers can re-enable these as errors with their own
100
+ // override layer once they are ready.
101
+ const lengthWarningOverrides = {
102
+ name: 'eslint-config-agent/recommended-length-warnings',
103
+ ...lengthRuleFileMatch,
104
+ rules: {
105
+ 'max-lines-per-function': ['warn', maxFunctionLinesErrorOptions],
106
+ 'max-lines': ['warn', maxFileLinesErrorOptions],
107
+ },
108
+ }
109
+
110
+ export default [...config, relaxedOverrides, lengthWarningOverrides]