eslint-config-agent 3.0.3 → 3.0.5
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 +119 -0
- package/README.md +577 -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/overrides.js +1 -1
- package/configs/test-files.js +40 -1
- package/configs/tsx.js +10 -0
- package/configs/typescript.js +21 -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 +11 -0
- package/exports/recommended.js +81 -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 +594 -7
- package/package.json +62 -29
- 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 +2 -2
- package/rules/plugin/n/no-process-env/index.js +4 -4
- package/rules/plugin/react/index.js +69 -0
- package/rules/plugin/typescript-eslint/index.js +204 -0
- package/rules/require-spec-file-tsx/README.md +57 -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,15 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-config-agent",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.5",
|
|
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/",
|
|
@@ -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,11 @@
|
|
|
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:incremental": "node scripts/test-incremental.js",
|
|
72
|
+
"test:types": "tsc -p test/types/tsconfig.json",
|
|
35
73
|
"test:edge": "eslint test/edge-cases.tsx",
|
|
36
74
|
"test:performance": "eslint test/performance-test.tsx",
|
|
37
75
|
"test:comprehensive": "node scripts/test-runner.js",
|
|
@@ -72,39 +110,32 @@
|
|
|
72
110
|
"access": "public"
|
|
73
111
|
},
|
|
74
112
|
"packageManager": "pnpm@10.18.2",
|
|
113
|
+
"pnpm": {
|
|
114
|
+
"overrides": {
|
|
115
|
+
"semver": ">=7.5.2"
|
|
116
|
+
}
|
|
117
|
+
},
|
|
75
118
|
"devDependencies": {
|
|
76
|
-
"@commitlint/cli": "^20.5.
|
|
77
|
-
"@commitlint/config-conventional": "^20.5.
|
|
78
|
-
"@
|
|
79
|
-
"@eslint/
|
|
80
|
-
"@release-it/conventional-changelog": "^11.0.0",
|
|
81
|
-
"@typescript-eslint/eslint-plugin": "^8.46.1",
|
|
82
|
-
"@typescript-eslint/parser": "^8.46.1",
|
|
83
|
-
"@typescript-eslint/rule-tester": "^8.59.0",
|
|
119
|
+
"@commitlint/cli": "^20.5.3",
|
|
120
|
+
"@commitlint/config-conventional": "^20.5.3",
|
|
121
|
+
"@release-it/conventional-changelog": "^11.0.1",
|
|
122
|
+
"@typescript-eslint/rule-tester": "^8.60.1",
|
|
84
123
|
"cspell": "^9.8.0",
|
|
85
|
-
"eslint": "^9.37.0",
|
|
86
|
-
"eslint-import-resolver-typescript": "^4.4.4",
|
|
87
|
-
"eslint-plugin-class-export": "^1.0.1",
|
|
88
|
-
"eslint-plugin-import": "^2.32.0",
|
|
89
|
-
"eslint-plugin-n": "^17.23.1",
|
|
90
|
-
"eslint-plugin-preact": "^0.1.0",
|
|
91
|
-
"eslint-plugin-react": "^7.37.5",
|
|
92
|
-
"eslint-plugin-react-hooks": "^5.2.0",
|
|
93
|
-
"eslint-plugin-security": "^3.0.1",
|
|
94
|
-
"eslint-plugin-storybook": "^9.1.10",
|
|
95
124
|
"husky": "^9.1.7",
|
|
96
|
-
"knip": "^6.
|
|
125
|
+
"knip": "^6.15.0",
|
|
97
126
|
"lint-staged": "^16.4.0",
|
|
98
127
|
"prettier": "^3.8.3",
|
|
99
|
-
"release-it": "^20.0
|
|
100
|
-
"
|
|
128
|
+
"release-it": "^20.2.0",
|
|
129
|
+
"storybook": "^10.4.2",
|
|
130
|
+
"typescript": "^6.0.3"
|
|
101
131
|
},
|
|
102
132
|
"dependencies": {
|
|
103
133
|
"@eslint/eslintrc": "^3.3.5",
|
|
104
134
|
"@eslint/js": "^9.39.4",
|
|
105
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
106
|
-
"@typescript-eslint/parser": "^8.
|
|
135
|
+
"@typescript-eslint/eslint-plugin": "^8.60.1",
|
|
136
|
+
"@typescript-eslint/parser": "^8.60.1",
|
|
107
137
|
"eslint": "^9.39.4",
|
|
138
|
+
"eslint-import-resolver-typescript": "^4.4.5",
|
|
108
139
|
"eslint-plugin-class-export": "^1.0.1",
|
|
109
140
|
"eslint-plugin-ddd": "^0.5.2",
|
|
110
141
|
"eslint-plugin-default": "^1.1.0",
|
|
@@ -120,9 +151,11 @@
|
|
|
120
151
|
"eslint-plugin-required-exports": "^0.2.0",
|
|
121
152
|
"eslint-plugin-security": "^4.0.0",
|
|
122
153
|
"eslint-plugin-single-export": "^1.1.2",
|
|
123
|
-
"eslint-plugin-storybook": "^10.
|
|
154
|
+
"eslint-plugin-storybook": "^10.4.2",
|
|
124
155
|
"eslint-plugin-switch-case": "^4.0.0",
|
|
125
|
-
"
|
|
126
|
-
"
|
|
156
|
+
"eslint-plugin-unicorn": "^65.0.1",
|
|
157
|
+
"eslint-plugin-unused-imports": "^4.4.1",
|
|
158
|
+
"globals": "^17.6.0",
|
|
159
|
+
"typescript-eslint": "^8.60.1"
|
|
127
160
|
}
|
|
128
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
|
@@ -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
|
|
@@ -13,4 +13,73 @@ export const reactRules = {
|
|
|
13
13
|
'react/jsx-wrap-multilines': 'off',
|
|
14
14
|
'react/jsx-closing-bracket-location': 'off',
|
|
15
15
|
'react/jsx-no-useless-fragment': 'off',
|
|
16
|
+
// Guard against React's "leaked render" bug: a short-circuit like
|
|
17
|
+
// `{count && <List />}` renders the literal `0` (or `NaN`) when the left
|
|
18
|
+
// operand is a falsy *non-boolean*, and `{name && <h1>{name}</h1>}` renders
|
|
19
|
+
// an empty string the same way. React only skips rendering for `false`,
|
|
20
|
+
// `null` and `undefined`, so any other falsy value leaks straight into the
|
|
21
|
+
// DOM as visible text. That is a real rendering bug the type checker cannot
|
|
22
|
+
// catch (the JSX is perfectly typed) and exactly the shortcut AI assistants
|
|
23
|
+
// emit when conditionally rendering a list or label. Unlike the stylistic
|
|
24
|
+
// react rules turned off above, this is a correctness check, so it is turned
|
|
25
|
+
// on. `validStrategies: ['ternary', 'coerce']` forces the unambiguous form —
|
|
26
|
+
// an explicit `cond ? <X /> : null` or a boolean coercion `!!cond && <X />`
|
|
27
|
+
// — instead of the bare `&&` that leaks. The rule is auto-fixable, so
|
|
28
|
+
// consumers can adopt it with `eslint --fix`.
|
|
29
|
+
'react/jsx-no-leaked-render': [
|
|
30
|
+
'error',
|
|
31
|
+
{ validStrategies: ['ternary', 'coerce'] },
|
|
32
|
+
],
|
|
33
|
+
// Forbid using an array index as a React `key` (`items.map((item, i) => <Row
|
|
34
|
+
// key={i} />)`). A key is React's identity for a list child across renders;
|
|
35
|
+
// when it is the position rather than something stable about the item, React
|
|
36
|
+
// maps the wrong previous element onto the wrong new one as soon as the list
|
|
37
|
+
// is reordered, filtered, or has an item inserted/removed at the front or
|
|
38
|
+
// middle. The visible result is a class of silent UI bugs that no type check
|
|
39
|
+
// can catch: text typed into one input jumps to another row, the wrong item
|
|
40
|
+
// animates, a checkbox's checked state sticks to the wrong record, and
|
|
41
|
+
// uncontrolled component state generally "smears" across siblings. The fix is
|
|
42
|
+
// to key by a stable, item-specific value (an id, a slug, a content hash),
|
|
43
|
+
// which states the intended identity explicitly. `key={index}` is exactly the
|
|
44
|
+
// shortcut an AI assistant reaches for when the item has no obvious id, so it
|
|
45
|
+
// is squarely in this config's bug-prevention, explicit-over-clever stance —
|
|
46
|
+
// the same correctness-not-style bar that turns on `jsx-no-leaked-render`
|
|
47
|
+
// above while leaving the cosmetic react rules off. Like the other react
|
|
48
|
+
// rules it only applies to `.jsx`/`.tsx` files, so non-React TypeScript
|
|
49
|
+
// packages are unaffected. This is why a downstream repo (`oss-il`) already
|
|
50
|
+
// re-adds it by hand on top of the base config.
|
|
51
|
+
'react/no-array-index-key': 'error',
|
|
52
|
+
// Forbid the `dangerouslySetInnerHTML` prop. Setting inner HTML directly from
|
|
53
|
+
// a string bypasses React's XSS defenses: any server-sourced or user-supplied
|
|
54
|
+
// content rendered this way can execute arbitrary scripts in the browser.
|
|
55
|
+
// `dangerouslySetInnerHTML` is intentionally verbose — its name is a warning —
|
|
56
|
+
// but AI assistants still reach for it when they need to inject a server HTML
|
|
57
|
+
// fragment or render a rich-text field, without thinking through whether the
|
|
58
|
+
// content is safe. The only legitimate uses (known-safe static markup, a
|
|
59
|
+
// sanitized string from a DOMPurify-style pass) should be reviewed explicitly;
|
|
60
|
+
// the rule forces that review by making every usage a lint error, which the
|
|
61
|
+
// engineer must suppress with an `eslint-disable` comment that makes the
|
|
62
|
+
// intentional decision visible in code review. The rule is not auto-fixable,
|
|
63
|
+
// so each suppression is a deliberate act.
|
|
64
|
+
'react/no-danger': 'error',
|
|
65
|
+
// Forbid a freshly-constructed value as the `value` of a Context Provider:
|
|
66
|
+
// `<Ctx.Provider value={{ user, setUser }}>` or `value={[state, dispatch]}`
|
|
67
|
+
// or `value={() => ...}`. The object/array/function literal is rebuilt on
|
|
68
|
+
// every render of the providing component, so its identity changes every time
|
|
69
|
+
// even when the data inside is unchanged. React compares context values by
|
|
70
|
+
// reference, so a new reference forces *every* consumer of that context to
|
|
71
|
+
// re-render — including ones that `React.memo`/`useMemo` were added precisely
|
|
72
|
+
// to protect. The result is a silent, whole-subtree performance bug: the app
|
|
73
|
+
// is correct but does far more work than it should, and the cause (an inline
|
|
74
|
+
// literal three components up) is invisible at the consumer. The fix states
|
|
75
|
+
// the intent explicitly — hoist the value into a `useMemo`/`useCallback` (or a
|
|
76
|
+
// stable ref) so the reference only changes when the data does. An inline
|
|
77
|
+
// `value={{ ... }}` is exactly the shortcut an AI assistant emits when wiring
|
|
78
|
+
// up a provider, so it sits on the same correctness/perf-not-style bar that
|
|
79
|
+
// turns on `jsx-no-leaked-render` and `no-array-index-key` above while leaving
|
|
80
|
+
// the cosmetic react rules off. Like the other react rules it only applies to
|
|
81
|
+
// `.jsx`/`.tsx` files, so non-React TypeScript packages are unaffected. This
|
|
82
|
+
// is why a downstream repo (`oss-il`) already re-adds it by hand on top of the
|
|
83
|
+
// base config.
|
|
84
|
+
'react/jsx-no-constructed-context-values': 'error',
|
|
16
85
|
}
|