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.
- package/CHANGELOG.md +158 -0
- package/README.md +592 -26
- package/configs/base-plugins.js +32 -0
- package/configs/config-files.js +19 -3
- package/configs/examples.js +12 -1
- package/configs/javascript.js +12 -4
- package/configs/length-rule-scope.js +27 -0
- package/configs/overrides.js +3 -14
- package/configs/test-files.js +40 -1
- package/configs/tsx.js +10 -0
- package/configs/typescript.js +44 -2
- package/exports/ddd.d.ts +10 -0
- package/exports/ddd.js +1 -3
- package/exports/incremental.d.ts +11 -0
- package/exports/incremental.js +39 -0
- package/exports/recommended-incremental.d.ts +11 -0
- package/exports/recommended-incremental.js +46 -0
- package/exports/recommended.d.ts +14 -0
- package/exports/recommended.js +110 -0
- package/exports/to-warnings.d.ts +20 -0
- package/exports/to-warnings.js +41 -0
- package/index.d.ts +18 -0
- package/index.js +883 -7
- package/package.json +46 -17
- package/plugins/index.js +6 -0
- package/rules/index.js +20 -22
- package/rules/no-inline-union-types/index.js +8 -6
- package/rules/no-process-env-properties/index.js +4 -4
- package/rules/no-record-literal-types/index.js +3 -3
- package/rules/plugin/import/index.js +41 -0
- package/rules/plugin/index.js +0 -2
- package/rules/plugin/n/index.js +16 -2
- package/rules/plugin/n/no-process-env/index.js +4 -4
- package/rules/plugin/react/index.js +150 -2
- package/rules/plugin/typescript-eslint/index.js +406 -0
- package/rules/require-spec-file-tsx/README.md +57 -0
- package/rules/require-spec-file-tsx/helpers.js +93 -0
- package/rules/require-spec-file-tsx/index.js +109 -0
- package/rules/plugin/jsx-a11y/index.js +0 -3
package/package.json
CHANGED
|
@@ -1,21 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-config-agent",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "ESLint configuration package with TypeScript support",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"types": "./index.d.ts",
|
|
6
7
|
"type": "module",
|
|
7
8
|
"exports": {
|
|
8
|
-
".":
|
|
9
|
-
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./index.d.ts",
|
|
11
|
+
"default": "./index.js"
|
|
12
|
+
},
|
|
13
|
+
"./ddd": {
|
|
14
|
+
"types": "./exports/ddd.d.ts",
|
|
15
|
+
"default": "./exports/ddd.js"
|
|
16
|
+
},
|
|
17
|
+
"./recommended": {
|
|
18
|
+
"types": "./exports/recommended.d.ts",
|
|
19
|
+
"default": "./exports/recommended.js"
|
|
20
|
+
},
|
|
21
|
+
"./recommended-incremental": {
|
|
22
|
+
"types": "./exports/recommended-incremental.d.ts",
|
|
23
|
+
"default": "./exports/recommended-incremental.js"
|
|
24
|
+
},
|
|
25
|
+
"./incremental": {
|
|
26
|
+
"types": "./exports/incremental.d.ts",
|
|
27
|
+
"default": "./exports/incremental.js"
|
|
28
|
+
},
|
|
29
|
+
"./to-warnings": {
|
|
30
|
+
"types": "./exports/to-warnings.d.ts",
|
|
31
|
+
"default": "./exports/to-warnings.js"
|
|
32
|
+
},
|
|
33
|
+
"./package.json": "./package.json"
|
|
10
34
|
},
|
|
11
35
|
"files": [
|
|
12
36
|
"index.js",
|
|
37
|
+
"index.d.ts",
|
|
13
38
|
"README.md",
|
|
14
39
|
"CHANGELOG.md",
|
|
15
40
|
"configs/",
|
|
16
41
|
"plugins/",
|
|
17
42
|
"exports/",
|
|
18
|
-
"rules
|
|
43
|
+
"rules/**/*.js",
|
|
19
44
|
"!rules/**/examples/",
|
|
20
45
|
"!rules/**/*.spec.js",
|
|
21
46
|
"!rules/**/*.test.js"
|
|
@@ -23,6 +48,14 @@
|
|
|
23
48
|
"engines": {
|
|
24
49
|
"node": ">=20.0.0"
|
|
25
50
|
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"typescript": ">=4.8.4"
|
|
53
|
+
},
|
|
54
|
+
"peerDependenciesMeta": {
|
|
55
|
+
"typescript": {
|
|
56
|
+
"optional": true
|
|
57
|
+
}
|
|
58
|
+
},
|
|
26
59
|
"scripts": {
|
|
27
60
|
"lint": "eslint .",
|
|
28
61
|
"lint:test": "eslint test/",
|
|
@@ -32,6 +65,12 @@
|
|
|
32
65
|
"test:invalid": "eslint test/invalid.tsx test/long-function.tsx test/jsx-extension-test.js",
|
|
33
66
|
"test:hooks": "eslint test/react-hooks-rules.tsx",
|
|
34
67
|
"test:imports": "eslint test/import-export-rules.ts",
|
|
68
|
+
"test:recommended": "node scripts/test-recommended.js",
|
|
69
|
+
"test:recommended-incremental": "node scripts/test-recommended-incremental.js",
|
|
70
|
+
"test:exports": "node scripts/test-exports.js",
|
|
71
|
+
"test:pack": "node scripts/verify-pack.js",
|
|
72
|
+
"test:incremental": "node scripts/test-incremental.js",
|
|
73
|
+
"test:types": "tsc -p test/types/tsconfig.json",
|
|
35
74
|
"test:edge": "eslint test/edge-cases.tsx",
|
|
36
75
|
"test:performance": "eslint test/performance-test.tsx",
|
|
37
76
|
"test:comprehensive": "node scripts/test-runner.js",
|
|
@@ -80,28 +119,16 @@
|
|
|
80
119
|
"devDependencies": {
|
|
81
120
|
"@commitlint/cli": "^20.5.3",
|
|
82
121
|
"@commitlint/config-conventional": "^20.5.3",
|
|
83
|
-
"@eslint/eslintrc": "^3.3.1",
|
|
84
|
-
"@eslint/js": "^9.37.0",
|
|
85
122
|
"@release-it/conventional-changelog": "^11.0.1",
|
|
86
|
-
"@typescript-eslint/eslint-plugin": "^8.46.1",
|
|
87
|
-
"@typescript-eslint/parser": "^8.46.1",
|
|
88
123
|
"@typescript-eslint/rule-tester": "^8.60.1",
|
|
89
124
|
"cspell": "^9.8.0",
|
|
90
|
-
"eslint": "^9.37.0",
|
|
91
|
-
"eslint-plugin-class-export": "^1.0.1",
|
|
92
|
-
"eslint-plugin-import": "^2.32.0",
|
|
93
|
-
"eslint-plugin-n": "^17.23.1",
|
|
94
|
-
"eslint-plugin-preact": "^0.1.0",
|
|
95
|
-
"eslint-plugin-react": "^7.37.5",
|
|
96
|
-
"eslint-plugin-react-hooks": "^5.2.0",
|
|
97
|
-
"eslint-plugin-security": "^3.0.1",
|
|
98
125
|
"husky": "^9.1.7",
|
|
99
126
|
"knip": "^6.15.0",
|
|
100
127
|
"lint-staged": "^16.4.0",
|
|
101
128
|
"prettier": "^3.8.3",
|
|
102
129
|
"release-it": "^20.2.0",
|
|
103
130
|
"storybook": "^10.4.2",
|
|
104
|
-
"typescript
|
|
131
|
+
"typescript": "^6.0.3"
|
|
105
132
|
},
|
|
106
133
|
"dependencies": {
|
|
107
134
|
"@eslint/eslintrc": "^3.3.5",
|
|
@@ -127,6 +154,8 @@
|
|
|
127
154
|
"eslint-plugin-single-export": "^1.1.2",
|
|
128
155
|
"eslint-plugin-storybook": "^10.4.2",
|
|
129
156
|
"eslint-plugin-switch-case": "^4.0.0",
|
|
157
|
+
"eslint-plugin-unicorn": "^65.0.1",
|
|
158
|
+
"eslint-plugin-unused-imports": "^4.4.1",
|
|
130
159
|
"globals": "^17.6.0",
|
|
131
160
|
"typescript-eslint": "^8.60.1"
|
|
132
161
|
}
|
package/plugins/index.js
CHANGED
|
@@ -18,7 +18,10 @@ import errorPlugin from 'eslint-plugin-error'
|
|
|
18
18
|
import defaultPlugin from 'eslint-plugin-default'
|
|
19
19
|
import dddPlugin from 'eslint-plugin-ddd'
|
|
20
20
|
import preactPlugin from 'eslint-plugin-preact'
|
|
21
|
+
import unusedImportsPlugin from 'eslint-plugin-unused-imports'
|
|
22
|
+
import unicornPlugin from 'eslint-plugin-unicorn'
|
|
21
23
|
import { noDefaultClassExportRule } from '../rules/no-default-class-export/index.js'
|
|
24
|
+
import { requireSpecFileTsxRule } from '../rules/require-spec-file-tsx/index.js'
|
|
22
25
|
|
|
23
26
|
// Centralized plugin configuration
|
|
24
27
|
export const plugins = {
|
|
@@ -35,9 +38,12 @@ export const plugins = {
|
|
|
35
38
|
default: defaultPlugin,
|
|
36
39
|
ddd: dddPlugin,
|
|
37
40
|
preact: preactPlugin,
|
|
41
|
+
'unused-imports': unusedImportsPlugin,
|
|
42
|
+
unicorn: unicornPlugin,
|
|
38
43
|
custom: {
|
|
39
44
|
rules: {
|
|
40
45
|
'no-default-class-export': noDefaultClassExportRule,
|
|
46
|
+
'require-spec-file-tsx': requireSpecFileTsxRule,
|
|
41
47
|
},
|
|
42
48
|
},
|
|
43
49
|
}
|
package/rules/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
// Core rule configurations
|
|
9
|
-
import noTrailingSpacesConfig from './no-trailing-spaces/index.js'
|
|
9
|
+
import { noTrailingSpacesConfig } from './no-trailing-spaces/index.js'
|
|
10
10
|
import {
|
|
11
11
|
maxFunctionLinesWarning,
|
|
12
12
|
maxFunctionLinesError,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
} from './max-file-lines/index.js'
|
|
18
18
|
|
|
19
19
|
// Custom restricted syntax rules
|
|
20
|
-
import {
|
|
20
|
+
import { noProcessEnvironmentPropertiesConfig } from './no-process-env-properties/index.js'
|
|
21
21
|
import { noTypeAssertionsConfig } from './no-type-assertions/index.js'
|
|
22
22
|
import { noExportSpecifiersConfig } from './no-empty-exports/index.js'
|
|
23
23
|
import { noDefaultClassExportRules } from './no-default-class-export/index.js'
|
|
@@ -42,7 +42,7 @@ const allRules = {
|
|
|
42
42
|
maxFileLinesError,
|
|
43
43
|
|
|
44
44
|
// Custom restricted syntax rules
|
|
45
|
-
|
|
45
|
+
noProcessEnvironmentPropertiesConfig,
|
|
46
46
|
noTypeAssertionsConfig,
|
|
47
47
|
noExportSpecifiersConfig,
|
|
48
48
|
noDefaultClassExportRules,
|
|
@@ -61,27 +61,25 @@ const allRules = {
|
|
|
61
61
|
export default allRules
|
|
62
62
|
|
|
63
63
|
// Named exports for backward compatibility
|
|
64
|
+
|
|
65
|
+
export { noTrailingSpacesConfig } from './no-trailing-spaces/index.js'
|
|
64
66
|
export {
|
|
65
|
-
// Core rule configurations
|
|
66
|
-
noTrailingSpacesConfig,
|
|
67
67
|
maxFunctionLinesWarning,
|
|
68
68
|
maxFunctionLinesError,
|
|
69
|
+
} from './max-function-lines/index.js'
|
|
70
|
+
export {
|
|
69
71
|
maxFileLinesWarning,
|
|
70
72
|
maxFileLinesError,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// Plugin rule configurations
|
|
85
|
-
pluginRules,
|
|
86
|
-
typescriptEslintRules,
|
|
87
|
-
}
|
|
73
|
+
} from './max-file-lines/index.js'
|
|
74
|
+
export { noProcessEnvironmentPropertiesConfig } from './no-process-env-properties/index.js'
|
|
75
|
+
export { noTypeAssertionsConfig } from './no-type-assertions/index.js'
|
|
76
|
+
export { noExportSpecifiersConfig } from './no-empty-exports/index.js'
|
|
77
|
+
export { noDefaultClassExportRules } from './no-default-class-export/index.js'
|
|
78
|
+
export { noNullishCoalescingConfig } from './nullish-coalescing/index.js'
|
|
79
|
+
export { switchStatementsReturnTypeConfigs } from './switch-statements-return-type/index.js'
|
|
80
|
+
export { switchCaseFunctionsReturnTypeConfigs } from './switch-case-functions-return-type/index.js'
|
|
81
|
+
export { switchCaseExplicitReturnConfigs } from './switch-case-explicit-return/index.js'
|
|
82
|
+
export { noTrivialTypeAliasesConfigs } from './no-trivial-type-aliases/index.js'
|
|
83
|
+
export { noInlineUnionTypesConfigs } from './no-inline-union-types/index.js'
|
|
84
|
+
export { pluginRules } from './plugin/index.js'
|
|
85
|
+
export { typescriptEslintRules } from './plugin/typescript-eslint/index.js'
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* Examples:
|
|
8
8
|
* - ❌ function foo(status: 'active' | 'inactive') {}
|
|
9
9
|
* - ❌ interface User { role: 'admin' | 'user'; }
|
|
10
|
+
* - ❌ interface User { id: string | number; }
|
|
10
11
|
* - ❌ class Config { mode: 'dev' | 'prod'; }
|
|
12
|
+
* - ❌ class Config { value: string | number; }
|
|
11
13
|
* - ✅ type Status = 'active' | 'inactive'; function foo(status: Status) {}
|
|
12
14
|
* - ✅ type Role = 'admin' | 'user'; interface User { role: Role; }
|
|
13
15
|
* - ✅ type Mode = 'dev' | 'prod'; class Config { mode: Mode; }
|
|
@@ -20,20 +22,20 @@ const rule = 'error'
|
|
|
20
22
|
const selectorGeneral =
|
|
21
23
|
'TSTypeAnnotation > TSUnionType:not(PropertyDefinition > .typeAnnotation > .typeAnnotation):not(TSPropertySignature > .typeAnnotation > .typeAnnotation)'
|
|
22
24
|
|
|
23
|
-
// Interface properties with literal
|
|
25
|
+
// Interface properties with inline unions (literal or otherwise)
|
|
24
26
|
const selectorInterfaceProperty =
|
|
25
|
-
'TSPropertySignature > TSTypeAnnotation > TSUnionType
|
|
27
|
+
'TSPropertySignature > TSTypeAnnotation > TSUnionType'
|
|
26
28
|
|
|
27
|
-
// Class properties with literal
|
|
29
|
+
// Class properties with inline unions (literal or otherwise)
|
|
28
30
|
const selectorClassProperty =
|
|
29
|
-
'PropertyDefinition > TSTypeAnnotation > TSUnionType
|
|
31
|
+
'PropertyDefinition > TSTypeAnnotation > TSUnionType'
|
|
30
32
|
|
|
31
33
|
const messageGeneral =
|
|
32
34
|
'Use a named type declaration instead of inline union types.'
|
|
33
35
|
const messageProperty =
|
|
34
|
-
'Interface properties with
|
|
36
|
+
'Interface properties with inline unions should use a named type declaration.'
|
|
35
37
|
const messageClassProperty =
|
|
36
|
-
'Class properties with
|
|
38
|
+
'Class properties with inline unions should use a named type declaration.'
|
|
37
39
|
|
|
38
40
|
/**
|
|
39
41
|
* Export the complete rule configurations for no-restricted-syntax
|
|
@@ -23,14 +23,14 @@ const message =
|
|
|
23
23
|
/**
|
|
24
24
|
* Export the complete rule configuration for no-restricted-syntax
|
|
25
25
|
* Can be used in ESLint config as part of no-restricted-syntax rules:
|
|
26
|
-
* "no-restricted-syntax": ["error", ...otherRules,
|
|
26
|
+
* "no-restricted-syntax": ["error", ...otherRules, noProcessEnvironmentPropertiesConfig]
|
|
27
27
|
*/
|
|
28
|
-
const
|
|
28
|
+
const noProcessEnvironmentPropertiesConfig = {
|
|
29
29
|
selector,
|
|
30
30
|
message,
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// Consolidated exports
|
|
34
|
-
export { rule, selector, message,
|
|
34
|
+
export { rule, selector, message, noProcessEnvironmentPropertiesConfig }
|
|
35
35
|
|
|
36
|
-
export default
|
|
36
|
+
export default noProcessEnvironmentPropertiesConfig
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
const rule = 'error'
|
|
24
24
|
|
|
25
25
|
// First selector: matches TSLiteralType descendants of first param (catches literals inside unions)
|
|
26
|
-
const
|
|
26
|
+
const selectorNestedParameters =
|
|
27
27
|
'TSTypeReference[typeName.name="Record"] > TSTypeParameterInstantiation > .params:first-child TSLiteralType'
|
|
28
28
|
|
|
29
29
|
// Second selector: matches direct TSLiteralType as first param
|
|
@@ -38,7 +38,7 @@ const message =
|
|
|
38
38
|
* Can be used in ESLint config as part of no-restricted-syntax rules
|
|
39
39
|
*/
|
|
40
40
|
const noRecordLiteralTypesNestedConfig = {
|
|
41
|
-
selector:
|
|
41
|
+
selector: selectorNestedParameters,
|
|
42
42
|
message,
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -58,7 +58,7 @@ const noRecordLiteralTypesConfigs = [
|
|
|
58
58
|
// Consolidated exports
|
|
59
59
|
export {
|
|
60
60
|
rule,
|
|
61
|
-
|
|
61
|
+
selectorNestedParameters,
|
|
62
62
|
selectorFirstChild,
|
|
63
63
|
message,
|
|
64
64
|
noRecordLiteralTypesNestedConfig,
|
|
@@ -21,4 +21,45 @@ export const importRules = {
|
|
|
21
21
|
'import/newline-after-import': 'off',
|
|
22
22
|
'import/first': 'error',
|
|
23
23
|
'import/prefer-default-export': 'off',
|
|
24
|
+
// Import hygiene: collapse duplicate import statements from the same module
|
|
25
|
+
// and forbid exporting mutable bindings (export let/var), which create
|
|
26
|
+
// shared mutable state across modules and are a common AI-generated footgun.
|
|
27
|
+
'import/no-duplicates': 'error',
|
|
28
|
+
'import/no-mutable-exports': 'error',
|
|
29
|
+
// Forbid circular import dependencies (A imports B imports A). Cycles cause
|
|
30
|
+
// order-dependent runtime bugs where a module reads a not-yet-initialized
|
|
31
|
+
// binding from its partner and silently sees `undefined`, defeat tree
|
|
32
|
+
// shaking, and are a reliable signal of tangled, hard-to-follow module
|
|
33
|
+
// boundaries. They are also a frequent AI-generated footgun, since an
|
|
34
|
+
// assistant editing one file cannot see the import graph it closes. Detect
|
|
35
|
+
// them statically. `maxDepth: Infinity` follows the full import graph so deep
|
|
36
|
+
// cycles are caught too; `ignoreExternal` skips traversal into node_modules
|
|
37
|
+
// for performance, since cycles inside dependencies are not the consumer's
|
|
38
|
+
// to fix.
|
|
39
|
+
'import/no-cycle': ['error', { maxDepth: Infinity, ignoreExternal: true }],
|
|
40
|
+
// A module importing itself is always a mistake (usually a copy-paste or a
|
|
41
|
+
// bad auto-import) and produces a degenerate cycle; flag it explicitly.
|
|
42
|
+
'import/no-self-import': 'error',
|
|
43
|
+
// Forbid empty named import blocks (`import {} from 'mod'`). An empty block
|
|
44
|
+
// is the residue of deleting the last named binding from an import: the
|
|
45
|
+
// statement now pulls in nothing yet still reads as if it imports names,
|
|
46
|
+
// which misleads readers and quietly keeps a dead dependency edge alive. A
|
|
47
|
+
// bare side-effect import (`import 'mod'`) — or removing the line — states
|
|
48
|
+
// the real intent. This is a frequent leftover from AI-assisted edits that
|
|
49
|
+
// rewrite an import list one binding at a time, putting it squarely in scope
|
|
50
|
+
// for this config's explicit-over-clever stance. The rule is auto-fixable.
|
|
51
|
+
'import/no-empty-named-blocks': 'error',
|
|
52
|
+
// Forbid redundant segments in relative import paths, e.g. `./foo/../bar`
|
|
53
|
+
// (really `./bar`), `./../sibling` (really `../sibling`) or `./././mod`
|
|
54
|
+
// (really `./mod`). These resolve to the same module but read as if they
|
|
55
|
+
// point somewhere else, forcing a reviewer to mentally collapse the path to
|
|
56
|
+
// know what is actually imported, and they hide accidental directory
|
|
57
|
+
// traversals introduced by a bad auto-import or a copy-paste from another
|
|
58
|
+
// folder — a frequent AI-assisted-edit footgun and squarely the
|
|
59
|
+
// explicit-over-clever ground this config covers. The rule is auto-fixable,
|
|
60
|
+
// so adopting projects get a one-shot `--fix`. `commonjs: true` extends the
|
|
61
|
+
// check to `require()` calls; `noUselessIndex` is left at its default (off)
|
|
62
|
+
// so explicit `/index` references stay allowed and only genuinely redundant
|
|
63
|
+
// `../`/`./` segments are flagged.
|
|
64
|
+
'import/no-useless-path-segments': ['error', { commonjs: true }],
|
|
24
65
|
}
|
package/rules/plugin/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { securityRules } from './security/index.js'
|
|
2
2
|
import { importRules } from './import/index.js'
|
|
3
|
-
import { jsxA11yRules } from './jsx-a11y/index.js'
|
|
4
3
|
import { reactRules } from './react/index.js'
|
|
5
4
|
import { nRules } from './n/index.js'
|
|
6
5
|
import { classExportRules } from './class-export/index.js'
|
|
@@ -8,7 +7,6 @@ import { classExportRules } from './class-export/index.js'
|
|
|
8
7
|
export const pluginRules = {
|
|
9
8
|
...securityRules,
|
|
10
9
|
...importRules,
|
|
11
|
-
...jsxA11yRules,
|
|
12
10
|
...reactRules,
|
|
13
11
|
...nRules,
|
|
14
12
|
...classExportRules,
|
package/rules/plugin/n/index.js
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { noProcessEnvironmentConfig } from './no-process-env/index.js'
|
|
2
2
|
|
|
3
3
|
export const nRules = {
|
|
4
|
-
'n/no-process-env':
|
|
4
|
+
'n/no-process-env': noProcessEnvironmentConfig,
|
|
5
|
+
// Require the `node:` protocol prefix on every import of a Node.js built-in
|
|
6
|
+
// module (e.g. `import fs from 'node:fs'` instead of `import fs from 'fs'`).
|
|
7
|
+
// The bare form (`'fs'`, `'path'`, `'crypto'`) is ambiguous: without the
|
|
8
|
+
// protocol, a reader (and a bundler) must consult package.json to confirm
|
|
9
|
+
// that the name is not a userland npm package shadowing the built-in. The
|
|
10
|
+
// `node:` prefix is unambiguous at a glance, is supported since Node 14.18.0
|
|
11
|
+
// (the minimum already required by this package's `engines` field), and is
|
|
12
|
+
// the form the package itself uses in all its own scripts and custom rules
|
|
13
|
+
// (e.g. `import { existsSync } from 'node:fs'`). Shipping the rule in the
|
|
14
|
+
// shared set means consuming projects inherit the same convention
|
|
15
|
+
// automatically. The rule is auto-fixable (`eslint --fix`), so adoption is
|
|
16
|
+
// free. It fires only when a built-in module is actually imported — browser
|
|
17
|
+
// and React code that imports nothing from Node.js is unaffected.
|
|
18
|
+
'n/prefer-node-protocol': 'error',
|
|
5
19
|
}
|
|
@@ -13,11 +13,11 @@ const options = {}
|
|
|
13
13
|
/**
|
|
14
14
|
* Export the complete rule configuration
|
|
15
15
|
* Can be used in ESLint config as:
|
|
16
|
-
* "n/no-process-env":
|
|
16
|
+
* "n/no-process-env": noProcessEnvironmentConfig
|
|
17
17
|
*/
|
|
18
|
-
const
|
|
18
|
+
const noProcessEnvironmentConfig = [rule, options]
|
|
19
19
|
|
|
20
20
|
// Consolidated exports
|
|
21
|
-
export { rule, options,
|
|
21
|
+
export { rule, options, noProcessEnvironmentConfig }
|
|
22
22
|
|
|
23
|
-
export default
|
|
23
|
+
export default noProcessEnvironmentConfig
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
export const reactRules = {
|
|
2
|
-
|
|
2
|
+
// Require self-closing tags for JSX components and HTML elements that have
|
|
3
|
+
// no children. `<Button></Button>` and `<div></div>` become `<Button />` and
|
|
4
|
+
// `<div />` respectively. An explicit open/close tag pair is meaningful only
|
|
5
|
+
// when it contains children; without children it is pure noise — it adds an
|
|
6
|
+
// extra close tag the reader has to scan past to confirm nothing is inside,
|
|
7
|
+
// and it is exactly the verbose form AI assistants emit when scaffolding
|
|
8
|
+
// components from a template. The single-tag form states "this element has
|
|
9
|
+
// no children" directly, which is the explicit, low-noise style this config
|
|
10
|
+
// favors throughout. The rule is auto-fixable, so `eslint --fix` collapses
|
|
11
|
+
// every violating element in one pass. `component: true` covers React
|
|
12
|
+
// components (`<MyComp></MyComp>`) and `html: true` covers intrinsic
|
|
13
|
+
// elements (`<div></div>`, `<span></span>`), so both surfaces stay
|
|
14
|
+
// consistent. `oss-il` already enforces this manually on top of the base
|
|
15
|
+
// config; promoting it here removes that copy-paste.
|
|
16
|
+
'react/self-closing-comp': ['error', { component: true, html: true }],
|
|
3
17
|
'react/destructuring-assignment': 'off',
|
|
4
18
|
'react/jsx-props-no-spreading': 'off',
|
|
5
19
|
'react/button-has-type': 'off',
|
|
@@ -12,5 +26,139 @@ export const reactRules = {
|
|
|
12
26
|
'react/require-default-props': 'off',
|
|
13
27
|
'react/jsx-wrap-multilines': 'off',
|
|
14
28
|
'react/jsx-closing-bracket-location': 'off',
|
|
15
|
-
|
|
29
|
+
// Forbid fragments that wrap a single child: `<><MyComponent /></>` adds DOM
|
|
30
|
+
// overhead and a return-type mismatch (`React.ReactElement` vs `JSX.Element`)
|
|
31
|
+
// without buying anything. It is exactly the kind of dead-weight an AI
|
|
32
|
+
// assistant emits when scaffolding a component. `allowExpressions: false`
|
|
33
|
+
// also covers `<>{value}</>` — if you need a fragment solely for a JSX
|
|
34
|
+
// expression, wrap it in a parent instead. The rule is auto-fixable, so
|
|
35
|
+
// `eslint --fix` rewrites `<><X /></>` to `<X />` with zero manual work.
|
|
36
|
+
// oss-il already enables this by hand on top of the base config; promoting
|
|
37
|
+
// it here removes the per-repo copy-paste.
|
|
38
|
+
'react/jsx-no-useless-fragment': ['error', { allowExpressions: false }],
|
|
39
|
+
// Guard against React's "leaked render" bug: a short-circuit like
|
|
40
|
+
// `{count && <List />}` renders the literal `0` (or `NaN`) when the left
|
|
41
|
+
// operand is a falsy *non-boolean*, and `{name && <h1>{name}</h1>}` renders
|
|
42
|
+
// an empty string the same way. React only skips rendering for `false`,
|
|
43
|
+
// `null` and `undefined`, so any other falsy value leaks straight into the
|
|
44
|
+
// DOM as visible text. That is a real rendering bug the type checker cannot
|
|
45
|
+
// catch (the JSX is perfectly typed) and exactly the shortcut AI assistants
|
|
46
|
+
// emit when conditionally rendering a list or label. Unlike the stylistic
|
|
47
|
+
// react rules turned off above, this is a correctness check, so it is turned
|
|
48
|
+
// on. `validStrategies: ['ternary', 'coerce']` forces the unambiguous form —
|
|
49
|
+
// an explicit `cond ? <X /> : null` or a boolean coercion `!!cond && <X />`
|
|
50
|
+
// — instead of the bare `&&` that leaks. The rule is auto-fixable, so
|
|
51
|
+
// consumers can adopt it with `eslint --fix`.
|
|
52
|
+
'react/jsx-no-leaked-render': [
|
|
53
|
+
'error',
|
|
54
|
+
{ validStrategies: ['ternary', 'coerce'] },
|
|
55
|
+
],
|
|
56
|
+
// Forbid using an array index as a React `key` (`items.map((item, i) => <Row
|
|
57
|
+
// key={i} />)`). A key is React's identity for a list child across renders;
|
|
58
|
+
// when it is the position rather than something stable about the item, React
|
|
59
|
+
// maps the wrong previous element onto the wrong new one as soon as the list
|
|
60
|
+
// is reordered, filtered, or has an item inserted/removed at the front or
|
|
61
|
+
// middle. The visible result is a class of silent UI bugs that no type check
|
|
62
|
+
// can catch: text typed into one input jumps to another row, the wrong item
|
|
63
|
+
// animates, a checkbox's checked state sticks to the wrong record, and
|
|
64
|
+
// uncontrolled component state generally "smears" across siblings. The fix is
|
|
65
|
+
// to key by a stable, item-specific value (an id, a slug, a content hash),
|
|
66
|
+
// which states the intended identity explicitly. `key={index}` is exactly the
|
|
67
|
+
// shortcut an AI assistant reaches for when the item has no obvious id, so it
|
|
68
|
+
// is squarely in this config's bug-prevention, explicit-over-clever stance —
|
|
69
|
+
// the same correctness-not-style bar that turns on `jsx-no-leaked-render`
|
|
70
|
+
// above while leaving the cosmetic react rules off. Like the other react
|
|
71
|
+
// rules it only applies to `.jsx`/`.tsx` files, so non-React TypeScript
|
|
72
|
+
// packages are unaffected. This is why a downstream repo (`oss-il`) already
|
|
73
|
+
// re-adds it by hand on top of the base config.
|
|
74
|
+
'react/no-array-index-key': 'error',
|
|
75
|
+
// Forbid the `dangerouslySetInnerHTML` prop. Setting inner HTML directly from
|
|
76
|
+
// a string bypasses React's XSS defenses: any server-sourced or user-supplied
|
|
77
|
+
// content rendered this way can execute arbitrary scripts in the browser.
|
|
78
|
+
// `dangerouslySetInnerHTML` is intentionally verbose — its name is a warning —
|
|
79
|
+
// but AI assistants still reach for it when they need to inject a server HTML
|
|
80
|
+
// fragment or render a rich-text field, without thinking through whether the
|
|
81
|
+
// content is safe. The only legitimate uses (known-safe static markup, a
|
|
82
|
+
// sanitized string from a DOMPurify-style pass) should be reviewed explicitly;
|
|
83
|
+
// the rule forces that review by making every usage a lint error, which the
|
|
84
|
+
// engineer must suppress with an `eslint-disable` comment that makes the
|
|
85
|
+
// intentional decision visible in code review. The rule is not auto-fixable,
|
|
86
|
+
// so each suppression is a deliberate act.
|
|
87
|
+
'react/no-danger': 'error',
|
|
88
|
+
// Enforce the `[value, setValue]` naming convention for `useState` pairs.
|
|
89
|
+
// When `useState` is destructured with a setter that does not match
|
|
90
|
+
// `set<Capitalize(stateName)>` — e.g. `const [data, handleUpdate] = useState()`
|
|
91
|
+
// instead of `const [data, setData] = useState()` — the mismatch signals a
|
|
92
|
+
// naming mistake that often means the wrong setter was wired up: the right
|
|
93
|
+
// value is read but a misnamed variable is written on update, a class of
|
|
94
|
+
// silent state-management bug that type-checking cannot catch (both names are
|
|
95
|
+
// just `Dispatch<SetStateAction<T>>`). AI assistants introduce exactly this
|
|
96
|
+
// slip when stitching a component from multiple snippets — emitting
|
|
97
|
+
// `const [user, setCurrentUser] = useState()` or
|
|
98
|
+
// `const [loading, handleLoadingChange] = useState(false)`, each of which
|
|
99
|
+
// compiles fine but breaks the per-component naming contract that makes state
|
|
100
|
+
// variables identifiable at a glance. The rule is not auto-fixable because
|
|
101
|
+
// only the author knows whether the state name or the setter name is the
|
|
102
|
+
// intended canonical one. It fires only on `.tsx`/`.jsx` files, so pure
|
|
103
|
+
// TypeScript packages are unaffected.
|
|
104
|
+
'react/hook-use-state': 'error',
|
|
105
|
+
// Forbid a freshly-constructed value as the `value` of a Context Provider:
|
|
106
|
+
// `<Ctx.Provider value={{ user, setUser }}>` or `value={[state, dispatch]}`
|
|
107
|
+
// or `value={() => ...}`. The object/array/function literal is rebuilt on
|
|
108
|
+
// every render of the providing component, so its identity changes every time
|
|
109
|
+
// even when the data inside is unchanged. React compares context values by
|
|
110
|
+
// reference, so a new reference forces *every* consumer of that context to
|
|
111
|
+
// re-render — including ones that `React.memo`/`useMemo` were added precisely
|
|
112
|
+
// to protect. The result is a silent, whole-subtree performance bug: the app
|
|
113
|
+
// is correct but does far more work than it should, and the cause (an inline
|
|
114
|
+
// literal three components up) is invisible at the consumer. The fix states
|
|
115
|
+
// the intent explicitly — hoist the value into a `useMemo`/`useCallback` (or a
|
|
116
|
+
// stable ref) so the reference only changes when the data does. An inline
|
|
117
|
+
// `value={{ ... }}` is exactly the shortcut an AI assistant emits when wiring
|
|
118
|
+
// up a provider, so it sits on the same correctness/perf-not-style bar that
|
|
119
|
+
// turns on `jsx-no-leaked-render` and `no-array-index-key` above while leaving
|
|
120
|
+
// the cosmetic react rules off. Like the other react rules it only applies to
|
|
121
|
+
// `.jsx`/`.tsx` files, so non-React TypeScript packages are unaffected. This
|
|
122
|
+
// is why a downstream repo (`oss-il`) already re-adds it by hand on top of the
|
|
123
|
+
// base config.
|
|
124
|
+
'react/jsx-no-constructed-context-values': 'error',
|
|
125
|
+
// Forbid using an object type (`{}`, `[]`, or a function literal) as the
|
|
126
|
+
// default value for a prop in function-component destructuring:
|
|
127
|
+
//
|
|
128
|
+
// function List({ items = [], onSelect = () => {} }) { ... }
|
|
129
|
+
//
|
|
130
|
+
// Each call to the component creates a fresh object/array/function literal
|
|
131
|
+
// for the default, so the reference changes on every render even when the
|
|
132
|
+
// prop is not actually provided. Any child that is `React.memo`-ed on that
|
|
133
|
+
// prop, or any `useEffect`/`useCallback`/`useMemo` that lists it as a
|
|
134
|
+
// dependency, will re-run unnecessarily — a silent performance bug the type
|
|
135
|
+
// checker cannot detect. The fix is to hoist the constant outside the
|
|
136
|
+
// component or memoize it:
|
|
137
|
+
//
|
|
138
|
+
// const DEFAULT_ITEMS: string[] = []
|
|
139
|
+
// function List({ items = DEFAULT_ITEMS }) { ... }
|
|
140
|
+
//
|
|
141
|
+
// This is exactly the shortcut an AI assistant reaches for when a prop is
|
|
142
|
+
// optional and the model doesn't want to add a module-level constant. The
|
|
143
|
+
// rule sits on the same correctness/perf-not-style bar as `jsx-no-leaked-
|
|
144
|
+
// render`, `no-array-index-key`, and `jsx-no-constructed-context-values`
|
|
145
|
+
// above — all of which catch silent reference-equality bugs that are
|
|
146
|
+
// invisible to the type checker. Like the other react rules this only fires
|
|
147
|
+
// on `.jsx`/`.tsx` files, so non-React TypeScript packages are unaffected.
|
|
148
|
+
'react/no-object-type-as-default-prop': 'error',
|
|
149
|
+
// Require a `key` prop on every element created inside an array/iterator
|
|
150
|
+
// (`.map()`, a spread of JSX literals, etc.). `key` is React's per-child
|
|
151
|
+
// identity across renders; without it React falls back to matching children
|
|
152
|
+
// by position, so as soon as the list is reordered, filtered, or gains/loses
|
|
153
|
+
// an item, state and DOM nodes "smear" onto the wrong element — text typed
|
|
154
|
+
// into one row's input jumps to another, the wrong checkbox stays checked,
|
|
155
|
+
// an animation plays on the wrong item. The JSX is perfectly typed either
|
|
156
|
+
// way, so this is a real rendering bug invisible to the type checker, and
|
|
157
|
+
// exactly the shortcut an AI assistant reaches for when it renders a list
|
|
158
|
+
// without pausing to think about identity. It sits on the same
|
|
159
|
+
// correctness-not-style bar as `no-array-index-key`, `jsx-no-leaked-render`,
|
|
160
|
+
// and `jsx-no-constructed-context-values` above. Like the other react rules
|
|
161
|
+
// it only applies to `.jsx`/`.tsx` files, so non-React TypeScript packages
|
|
162
|
+
// are unaffected.
|
|
163
|
+
'react/jsx-key': 'error',
|
|
16
164
|
}
|