eslint-config-agent 1.3.7 → 1.3.9
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 +20 -0
- package/configs/test-files.js +248 -0
- package/index.js +18 -117
- package/package.json +9 -4
- package/rules/index.js +3 -0
- package/rules/no-record-literal-types/README.md +63 -0
- package/rules/no-record-literal-types/index.js +67 -0
- package/rules/nullish-coalescing/index.js +45 -0
- package/rules/jsx-classname-required/jsx-classname-required.spec.js +0 -283
- package/rules/max-file-lines/examples/invalid/long-file.js +0 -113
- package/rules/max-file-lines/examples/invalid/medium-file.js +0 -127
- package/rules/max-file-lines/examples/invalid/very-long-file.ts +0 -144
- package/rules/max-file-lines/examples/valid/short-file.js +0 -66
- package/rules/max-file-lines/examples/valid/spec-file.spec.js +0 -44
- package/rules/max-file-lines/examples/valid/test-file.test.js +0 -25
- package/rules/max-file-lines/max-file-lines.spec.js +0 -74
- package/rules/max-function-lines/max-function-lines.spec.js +0 -74
- package/rules/no-class-property-defaults/examples/invalid/array-default.ts +0 -7
- package/rules/no-class-property-defaults/examples/invalid/boolean-default.ts +0 -7
- package/rules/no-class-property-defaults/examples/invalid/complex-expression-default.ts +0 -7
- package/rules/no-class-property-defaults/examples/invalid/function-default.ts +0 -7
- package/rules/no-class-property-defaults/examples/invalid/number-default.ts +0 -7
- package/rules/no-class-property-defaults/examples/invalid/object-default.ts +0 -7
- package/rules/no-class-property-defaults/examples/invalid/private-default.ts +0 -7
- package/rules/no-class-property-defaults/examples/invalid/readonly-default.ts +0 -7
- package/rules/no-class-property-defaults/examples/invalid/static-default.ts +0 -7
- package/rules/no-class-property-defaults/examples/invalid/string-default.ts +0 -7
- package/rules/no-class-property-defaults/examples/valid/abstract-property.ts +0 -7
- package/rules/no-class-property-defaults/examples/valid/conditional-initialization.ts +0 -11
- package/rules/no-class-property-defaults/examples/valid/constructor-initialization.ts +0 -11
- package/rules/no-class-property-defaults/examples/valid/method-initialization.ts +0 -15
- package/rules/no-class-property-defaults/examples/valid/no-default-property.ts +0 -11
- package/rules/no-class-property-defaults/examples/valid/readonly-no-default.ts +0 -11
- package/rules/no-class-property-defaults/no-class-property-defaults.spec.js +0 -327
- package/rules/no-default-class-export/examples/invalid/default-abstract-class.ts +0 -10
- package/rules/no-default-class-export/examples/invalid/default-class-declaration.ts +0 -12
- package/rules/no-default-class-export/examples/invalid/default-class-expression.ts +0 -12
- package/rules/no-default-class-export/examples/invalid/default-generic-class.ts +0 -12
- package/rules/no-default-class-export/examples/valid/abstract-class-export.ts +0 -17
- package/rules/no-default-class-export/examples/valid/generic-class-export.ts +0 -16
- package/rules/no-default-class-export/examples/valid/multiple-named-exports.ts +0 -22
- package/rules/no-default-class-export/examples/valid/named-class-export.ts +0 -12
- package/rules/no-default-class-export/no-default-class-export.spec.js +0 -301
- package/rules/no-empty-exports/examples/invalid/empty-export.js +0 -2
- package/rules/no-empty-exports/examples/invalid/multiple-exports.js +0 -5
- package/rules/no-empty-exports/examples/invalid/single-export.js +0 -3
- package/rules/no-empty-exports/examples/valid/default-export.js +0 -3
- package/rules/no-empty-exports/examples/valid/direct-class.js +0 -6
- package/rules/no-empty-exports/examples/valid/direct-const.js +0 -2
- package/rules/no-empty-exports/examples/valid/direct-function.js +0 -4
- package/rules/no-empty-exports/examples/valid/re-export-from.js +0 -2
- package/rules/no-empty-exports/examples/valid/re-export-star.js +0 -2
- package/rules/no-empty-exports/no-empty-exports.spec.js +0 -104
- package/rules/no-process-env-properties/no-process-env-properties.spec.js +0 -207
- package/rules/no-trailing-spaces/no-trailing-spaces.spec.js +0 -128
- package/rules/no-type-assertions/no-type-assertions.spec.js +0 -246
- package/rules/plugin/import/group-exports/examples/invalid/mixed-declaration-styles.js +0 -8
- package/rules/plugin/import/group-exports/examples/invalid/multiple-export-statements.js +0 -8
- package/rules/plugin/import/group-exports/examples/invalid/scattered-exports.js +0 -10
- package/rules/plugin/import/group-exports/examples/valid/direct-exports.js +0 -2
- package/rules/plugin/import/group-exports/examples/valid/mixed-with-default.js +0 -7
- package/rules/plugin/import/group-exports/examples/valid/single-export-statement.js +0 -6
- package/rules/plugin/import/no-unused-modules/examples/invalid/dead-code.js +0 -2
- package/rules/plugin/import/no-unused-modules/examples/invalid/unused-class.js +0 -6
- package/rules/plugin/import/no-unused-modules/examples/invalid/unused-export.js +0 -2
- package/rules/plugin/import/no-unused-modules/examples/invalid/unused-function.js +0 -4
- package/rules/plugin/import/no-unused-modules/examples/invalid/unused-module.js +0 -4
- package/rules/plugin/import/no-unused-modules/examples/valid/consumed-import.js +0 -8
- package/rules/plugin/import/no-unused-modules/examples/valid/entry-point.js +0 -5
- package/rules/plugin/import/no-unused-modules/examples/valid/used-class.js +0 -6
- package/rules/plugin/import/no-unused-modules/examples/valid/used-export.js +0 -2
- package/rules/plugin/import/no-unused-modules/examples/valid/used-function.js +0 -4
- package/rules/plugin/typescript-eslint/no-explicit-any/examples/invalid/any-array.ts +0 -2
- package/rules/plugin/typescript-eslint/no-explicit-any/examples/invalid/any-parameter.ts +0 -4
- package/rules/plugin/typescript-eslint/no-explicit-any/examples/invalid/any-return-type.ts +0 -4
- package/rules/plugin/typescript-eslint/no-explicit-any/examples/invalid/any-variable.ts +0 -5
- package/rules/plugin/typescript-eslint/no-explicit-any/examples/valid/generics.ts +0 -16
- package/rules/plugin/typescript-eslint/no-explicit-any/examples/valid/specific-types.ts +0 -9
- package/rules/plugin/typescript-eslint/no-explicit-any/examples/valid/unknown-type.ts +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file. See [Conven
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
## [1.3.9](https://github.com/tupe12334/eslint-config/compare/v1.3.8...v1.3.9) (2025-09-27)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* update eslint-plugin-error to version 1.1.3 for improved stability ([4fc6a2f](https://github.com/tupe12334/eslint-config/commit/4fc6a2f688ea195d5e2038222449993fce4a7dc7))
|
|
12
|
+
|
|
13
|
+
## [1.3.8](https://github.com/tupe12334/eslint-config/compare/v1.3.7...v1.3.8) (2025-09-27)
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* add eslint-plugin-default for enhanced linting rules; configure default plugin strict settings ([70d3ac6](https://github.com/tupe12334/eslint-config/commit/70d3ac6f7ab6666eb7652840cf8eb6b68de74df7))
|
|
18
|
+
* add test files configuration and refactor index.js to import shared rules ([28d3799](https://github.com/tupe12334/eslint-config/commit/28d3799edbc7d4726027adbbdc597746579e516e))
|
|
19
|
+
* add TODO.md with function export and translation function guidelines ([94bfd21](https://github.com/tupe12334/eslint-config/commit/94bfd2167b29480cf7f3d2bb11120756239b0ee7))
|
|
20
|
+
* implement no-nullish-coalescing rule with examples and tests ([838196f](https://github.com/tupe12334/eslint-config/commit/838196f500125d09e92f9fa0d7f6bb65280a2f3b))
|
|
21
|
+
* implement no-record-literal-types rule to enforce type safety; add examples and tests ([3308ccb](https://github.com/tupe12334/eslint-config/commit/3308ccb59ba4e9d05725e2f810453df454c63ba5))
|
|
22
|
+
* include configs directory in package.json files list ([3bce2b4](https://github.com/tupe12334/eslint-config/commit/3bce2b41440a7330816603918bf74dfea3900b2b))
|
|
23
|
+
* refine .npmignore and package.json to better manage test and example files in rules directory ([9ed652b](https://github.com/tupe12334/eslint-config/commit/9ed652bae2410faa4428dab583e3494abf9cc5bc))
|
|
24
|
+
* update .npmignore to include examples and test files in rules directory ([d1bb23d](https://github.com/tupe12334/eslint-config/commit/d1bb23df50c623ada2bc2805f7638f9398f2ba64))
|
|
25
|
+
* update no-empty-exports tests for consistency and clarity; enhance test-runner to scan for spec files ([25a7d26](https://github.com/tupe12334/eslint-config/commit/25a7d2660cc992e9031e0577197bfb57faf2c570))
|
|
26
|
+
|
|
7
27
|
## [1.3.7](https://github.com/tupe12334/eslint-config/compare/v1.3.6...v1.3.7) (2025-09-24)
|
|
8
28
|
|
|
9
29
|
### Features
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import allRules from "../rules/index.js";
|
|
2
|
+
import { noRecordLiteralTypesConfigs } from "../rules/no-record-literal-types/index.js";
|
|
3
|
+
|
|
4
|
+
// Shared rules for both JS and TS files
|
|
5
|
+
const sharedRules = {
|
|
6
|
+
...allRules.pluginRules,
|
|
7
|
+
"object-curly-newline": "off",
|
|
8
|
+
"no-shadow": "off",
|
|
9
|
+
"comma-dangle": "off",
|
|
10
|
+
"function-paren-newline": "off",
|
|
11
|
+
quotes: "off",
|
|
12
|
+
"no-unused-vars": "off",
|
|
13
|
+
"@typescript-eslint/no-unused-vars": "off",
|
|
14
|
+
"max-lines-per-function": allRules.maxFunctionLinesWarning,
|
|
15
|
+
"max-lines": allRules.maxFileLinesWarning,
|
|
16
|
+
semi: "off",
|
|
17
|
+
complexity: "off",
|
|
18
|
+
"no-trailing-spaces": allRules.noTrailingSpacesConfig,
|
|
19
|
+
"operator-linebreak": "off",
|
|
20
|
+
"implicit-arrow-linebreak": "off",
|
|
21
|
+
"arrow-body-style": "off",
|
|
22
|
+
"no-continue": "off",
|
|
23
|
+
// Additional built-in error handling rules
|
|
24
|
+
"prefer-promise-reject-errors": "error",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Shared no-restricted-syntax rules for both JS and TS
|
|
28
|
+
const sharedRestrictedSyntax = [
|
|
29
|
+
{
|
|
30
|
+
selector: "MemberExpression[optional=true]",
|
|
31
|
+
message: "Optional chaining is not allowed.",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
selector: "CallExpression[optional=true]",
|
|
35
|
+
message: "Optional chaining is not allowed.",
|
|
36
|
+
},
|
|
37
|
+
allRules.noNullishCoalescingConfig,
|
|
38
|
+
{
|
|
39
|
+
selector:
|
|
40
|
+
"ExportNamedDeclaration[exportKind=type]:not([source]):has(ExportSpecifier)",
|
|
41
|
+
message:
|
|
42
|
+
"Type-only exports are not allowed. Use regular export or re-export with 'from' clause.",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
selector: "ExportSpecifier[local.name=default][exported.name!=default]",
|
|
46
|
+
message:
|
|
47
|
+
"Re-exporting default as named export is not allowed. Use explicit export declaration instead.",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
selector:
|
|
51
|
+
"Program:has(ImportDeclaration) ExportNamedDeclaration:has(VariableDeclaration > VariableDeclarator[init.type=Identifier]):not(:has(ClassDeclaration))",
|
|
52
|
+
message:
|
|
53
|
+
"Exporting imported variables is not allowed. Use direct re-export with 'from' clause or define new values.",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
selector: "SwitchStatement > SwitchCase > ReturnStatement[argument=null]",
|
|
57
|
+
message:
|
|
58
|
+
"Switch case functions must provide an explicit return value. Default return values are not allowed.",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
selector:
|
|
62
|
+
"SwitchStatement > SwitchCase > BlockStatement > ReturnStatement[argument=null]",
|
|
63
|
+
message:
|
|
64
|
+
"Switch case functions must provide an explicit return value. Default return values are not allowed.",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
selector: "SwitchStatement > SwitchCase[test=null]",
|
|
68
|
+
message:
|
|
69
|
+
"Default cases are not allowed in switch statements. Handle all possible cases explicitly.",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
selector:
|
|
73
|
+
"ExportNamedDeclaration[source.value=/^[a-z]/]:not([source.value=/^@/])",
|
|
74
|
+
message:
|
|
75
|
+
"Exporting from external libraries is not allowed. Only re-export from relative paths or scoped packages.",
|
|
76
|
+
},
|
|
77
|
+
allRules.noProcessEnvPropertiesConfig,
|
|
78
|
+
allRules.noExportSpecifiersConfig,
|
|
79
|
+
...allRules.noDefaultClassExportRules,
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
// TypeScript-specific no-restricted-syntax rules
|
|
83
|
+
const tsOnlyRestrictedSyntax = [
|
|
84
|
+
...noRecordLiteralTypesConfigs,
|
|
85
|
+
{
|
|
86
|
+
selector:
|
|
87
|
+
"TSTypeAnnotation > TSUnionType:not(PropertyDefinition > .typeAnnotation > .typeAnnotation):not(TSPropertySignature > .typeAnnotation > .typeAnnotation)",
|
|
88
|
+
message: "Use a named type declaration instead of inline union types.",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
selector:
|
|
92
|
+
"TSPropertySignature > TSTypeAnnotation > TSUnionType:has(TSLiteralType)",
|
|
93
|
+
message:
|
|
94
|
+
"Interface properties with literal unions should use a named type declaration.",
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
selector:
|
|
98
|
+
"PropertyDefinition > TSTypeAnnotation > TSUnionType:has(TSLiteralType)",
|
|
99
|
+
message:
|
|
100
|
+
"Class properties with literal unions should use a named type declaration.",
|
|
101
|
+
},
|
|
102
|
+
allRules.noTypeAssertionsConfig,
|
|
103
|
+
allRules.noClassPropertyDefaultsConfig,
|
|
104
|
+
{
|
|
105
|
+
selector: "TSAsExpression:has(> TSIndexedAccessType > TSTypeQuery)",
|
|
106
|
+
message:
|
|
107
|
+
'Type assertions with indexed access types like "as (typeof X)[number]" are not allowed. Use a named type instead.',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
selector:
|
|
111
|
+
"SwitchStatement > SwitchCase ArrowFunctionExpression:not([returnType])",
|
|
112
|
+
message:
|
|
113
|
+
"Switch case arrow functions must have explicit return type annotations.",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
selector:
|
|
117
|
+
"SwitchStatement > SwitchCase FunctionExpression:not([returnType])",
|
|
118
|
+
message:
|
|
119
|
+
"Switch case function expressions must have explicit return type annotations.",
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
selector:
|
|
123
|
+
"SwitchStatement > SwitchCase > BlockStatement ArrowFunctionExpression:not([returnType])",
|
|
124
|
+
message:
|
|
125
|
+
"Switch case arrow functions must have explicit return type annotations.",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
selector:
|
|
129
|
+
"SwitchStatement > SwitchCase > BlockStatement FunctionExpression:not([returnType])",
|
|
130
|
+
message:
|
|
131
|
+
"Switch case function expressions must have explicit return type annotations.",
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
selector: "FunctionDeclaration:has(SwitchStatement):not([returnType])",
|
|
135
|
+
message:
|
|
136
|
+
"Functions containing switch statements must have explicit return type annotations.",
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
selector: "ArrowFunctionExpression:has(SwitchStatement):not([returnType])",
|
|
140
|
+
message:
|
|
141
|
+
"Arrow functions containing switch statements must have explicit return type annotations.",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
selector: "FunctionExpression:has(SwitchStatement):not([returnType])",
|
|
145
|
+
message:
|
|
146
|
+
"Function expressions containing switch statements must have explicit return type annotations.",
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
// Test and spec files configuration
|
|
151
|
+
export const testFilesConfig = [
|
|
152
|
+
// Disable function and file size limits for test and spec files
|
|
153
|
+
{
|
|
154
|
+
files: [
|
|
155
|
+
"**/*.test.{js,jsx,ts,tsx}",
|
|
156
|
+
"**/*.spec.{js,jsx,ts,tsx}",
|
|
157
|
+
"**/test/**/*.{js,jsx,ts,tsx}",
|
|
158
|
+
"**/tests/**/*.{js,jsx,ts,tsx}",
|
|
159
|
+
"**/__tests__/**/*.{js,jsx,ts,tsx}",
|
|
160
|
+
],
|
|
161
|
+
ignores: [
|
|
162
|
+
"**/long-function-test.tsx", // Exception: this file tests the max-lines rule itself
|
|
163
|
+
"**/test/export/**", // Export tests should follow strict export rules
|
|
164
|
+
"**/test/required-exports/**", // Required export tests should follow strict export rules
|
|
165
|
+
],
|
|
166
|
+
rules: {
|
|
167
|
+
"max-lines-per-function": "off",
|
|
168
|
+
"max-lines": "off", // Ignore file length limits in test and spec files
|
|
169
|
+
// Allow multiple exports in test files for testing import/export patterns
|
|
170
|
+
"no-restricted-syntax": [
|
|
171
|
+
"warn",
|
|
172
|
+
...sharedRestrictedSyntax.filter(
|
|
173
|
+
(rule) =>
|
|
174
|
+
rule.selector !==
|
|
175
|
+
"ExportNamedDeclaration[specifiers.length>1]:not([source])" &&
|
|
176
|
+
rule.selector !==
|
|
177
|
+
"Program:has(ExportNamedDeclaration:not([source]) ~ ExportNamedDeclaration:not([source]))" &&
|
|
178
|
+
rule.selector !==
|
|
179
|
+
"ExportNamedDeclaration:not([source]):not(:has(VariableDeclaration)):not(:has(FunctionDeclaration)):not(:has(ClassDeclaration)):not(:has(TSInterfaceDeclaration)):not(:has(TSTypeAliasDeclaration)):not(:has(TSEnumDeclaration))"
|
|
180
|
+
),
|
|
181
|
+
...tsOnlyRestrictedSyntax,
|
|
182
|
+
],
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
// Test files that should have ERROR level rules but exclude export specifier rule
|
|
187
|
+
{
|
|
188
|
+
files: [
|
|
189
|
+
"**/test/type-assertions/**",
|
|
190
|
+
"**/test/test-optional.ts",
|
|
191
|
+
"**/test/test-js-optional.js",
|
|
192
|
+
"**/test/test-record-literals.ts",
|
|
193
|
+
"**/test/no-env-access-test.ts",
|
|
194
|
+
"**/test/import-export-rules.ts",
|
|
195
|
+
],
|
|
196
|
+
rules: {
|
|
197
|
+
"max-lines-per-function": "off",
|
|
198
|
+
"no-restricted-syntax": [
|
|
199
|
+
"error", // Error level for these test files
|
|
200
|
+
...sharedRestrictedSyntax.filter(
|
|
201
|
+
(rule) =>
|
|
202
|
+
rule.selector !==
|
|
203
|
+
"ExportNamedDeclaration:not([source]):not(:has(VariableDeclaration)):not(:has(FunctionDeclaration)):not(:has(ClassDeclaration)):not(:has(TSInterfaceDeclaration)):not(:has(TSTypeAliasDeclaration)):not(:has(TSEnumDeclaration))"
|
|
204
|
+
),
|
|
205
|
+
...tsOnlyRestrictedSyntax,
|
|
206
|
+
],
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
// Test files configuration with mixed severity levels
|
|
211
|
+
{
|
|
212
|
+
files: [
|
|
213
|
+
"**/test/invalid.tsx", // Special handling for the main test file
|
|
214
|
+
"**/test/single-export-valid.ts", // Allow export specifiers for import/group-exports testing
|
|
215
|
+
"**/test/typescript-rules.ts", // Allow export specifiers for typescript rules testing
|
|
216
|
+
"**/test/type-assertions/indexed-access-valid.ts", // Allow export specifiers for type assertions testing
|
|
217
|
+
],
|
|
218
|
+
rules: {
|
|
219
|
+
"max-lines-per-function": "off",
|
|
220
|
+
"no-restricted-syntax": [
|
|
221
|
+
"warn", // Base level for most rules
|
|
222
|
+
...sharedRestrictedSyntax.filter(
|
|
223
|
+
(rule) =>
|
|
224
|
+
rule.selector !==
|
|
225
|
+
"ExportNamedDeclaration[specifiers.length>1]:not([source])" &&
|
|
226
|
+
rule.selector !==
|
|
227
|
+
"Program:has(ExportNamedDeclaration:not([source]) ~ ExportNamedDeclaration:not([source]))" &&
|
|
228
|
+
rule.selector !==
|
|
229
|
+
"ExportNamedDeclaration:not([source]):not(:has(VariableDeclaration)):not(:has(FunctionDeclaration)):not(:has(ClassDeclaration)):not(:has(TSInterfaceDeclaration)):not(:has(TSTypeAliasDeclaration)):not(:has(TSEnumDeclaration))"
|
|
230
|
+
),
|
|
231
|
+
...tsOnlyRestrictedSyntax,
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
// Disable file length rules for configuration and spec files
|
|
237
|
+
{
|
|
238
|
+
files: [
|
|
239
|
+
"index.js", // Main configuration file
|
|
240
|
+
"**/rules/**/*.spec.js", // Spec files in rules directory
|
|
241
|
+
"**/scripts/**/*.js", // Script files
|
|
242
|
+
],
|
|
243
|
+
rules: {
|
|
244
|
+
"max-lines": "off",
|
|
245
|
+
"max-lines-per-function": "off",
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
];
|
package/index.js
CHANGED
|
@@ -12,7 +12,10 @@ import storybookPlugin from "eslint-plugin-storybook";
|
|
|
12
12
|
import globals from "globals";
|
|
13
13
|
import allRules from "./rules/index.js";
|
|
14
14
|
import { noDefaultClassExportRule } from "./rules/no-default-class-export/index.js";
|
|
15
|
+
import { noRecordLiteralTypesConfigs } from "./rules/no-record-literal-types/index.js";
|
|
15
16
|
import errorPlugin from "eslint-plugin-error";
|
|
17
|
+
import defaultPlugin from "eslint-plugin-default";
|
|
18
|
+
import { testFilesConfig } from "./configs/test-files.js";
|
|
16
19
|
|
|
17
20
|
// Conditionally import preact plugin if available
|
|
18
21
|
let preactPlugin = null;
|
|
@@ -55,11 +58,7 @@ const sharedRestrictedSyntax = [
|
|
|
55
58
|
selector: "CallExpression[optional=true]",
|
|
56
59
|
message: "Optional chaining is not allowed.",
|
|
57
60
|
},
|
|
58
|
-
|
|
59
|
-
selector: 'LogicalExpression[operator="??"]',
|
|
60
|
-
message:
|
|
61
|
-
"Nullish coalescing operator (??) is not allowed. Use explicit null/undefined checks instead.",
|
|
62
|
-
},
|
|
61
|
+
allRules.noNullishCoalescingConfig,
|
|
63
62
|
{
|
|
64
63
|
selector:
|
|
65
64
|
"ExportNamedDeclaration[exportKind=type]:not([source]):has(ExportSpecifier)",
|
|
@@ -106,18 +105,7 @@ const sharedRestrictedSyntax = [
|
|
|
106
105
|
|
|
107
106
|
// TypeScript-specific no-restricted-syntax rules
|
|
108
107
|
const tsOnlyRestrictedSyntax = [
|
|
109
|
-
|
|
110
|
-
selector:
|
|
111
|
-
'TSTypeReference[typeName.name="Record"] > TSTypeParameterInstantiation > .params:first-child TSLiteralType',
|
|
112
|
-
message:
|
|
113
|
-
"Avoid using Record with string literal keys. Use a more specific interface or type instead.",
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
selector:
|
|
117
|
-
'TSTypeReference[typeName.name="Record"] > TSTypeParameterInstantiation > TSLiteralType:first-child',
|
|
118
|
-
message:
|
|
119
|
-
"Avoid using Record with string literal keys. Use a more specific interface or type instead.",
|
|
120
|
-
},
|
|
108
|
+
...noRecordLiteralTypesConfigs,
|
|
121
109
|
{
|
|
122
110
|
selector:
|
|
123
111
|
"TSTypeAnnotation > TSUnionType:not(PropertyDefinition > .typeAnnotation > .typeAnnotation):not(TSPropertySignature > .typeAnnotation > .typeAnnotation)",
|
|
@@ -226,6 +214,16 @@ const config = [
|
|
|
226
214
|
...errorPlugin.configs.strict.rules,
|
|
227
215
|
},
|
|
228
216
|
},
|
|
217
|
+
// Default plugin strict config
|
|
218
|
+
{
|
|
219
|
+
plugins: {
|
|
220
|
+
default: defaultPlugin,
|
|
221
|
+
},
|
|
222
|
+
rules: {
|
|
223
|
+
"default/no-localhost": ["error", { allowInTests: true }],
|
|
224
|
+
"default/no-hardcoded-urls": ["error", { allowInTests: true }],
|
|
225
|
+
},
|
|
226
|
+
},
|
|
229
227
|
|
|
230
228
|
// TypeScript and TSX files
|
|
231
229
|
{
|
|
@@ -723,89 +721,8 @@ const config = [
|
|
|
723
721
|
},
|
|
724
722
|
},
|
|
725
723
|
|
|
726
|
-
//
|
|
727
|
-
|
|
728
|
-
files: [
|
|
729
|
-
"**/*.test.{js,jsx,ts,tsx}",
|
|
730
|
-
"**/*.spec.{js,jsx,ts,tsx}",
|
|
731
|
-
"**/test/**/*.{js,jsx,ts,tsx}",
|
|
732
|
-
"**/tests/**/*.{js,jsx,ts,tsx}",
|
|
733
|
-
"**/__tests__/**/*.{js,jsx,ts,tsx}",
|
|
734
|
-
],
|
|
735
|
-
ignores: [
|
|
736
|
-
"**/long-function-test.tsx", // Exception: this file tests the max-lines rule itself
|
|
737
|
-
"**/test/export/**", // Export tests should follow strict export rules
|
|
738
|
-
"**/test/required-exports/**", // Required export tests should follow strict export rules
|
|
739
|
-
],
|
|
740
|
-
rules: {
|
|
741
|
-
"max-lines-per-function": "off",
|
|
742
|
-
"max-lines": "off", // Ignore file length limits in test and spec files
|
|
743
|
-
// Allow multiple exports in test files for testing import/export patterns
|
|
744
|
-
"no-restricted-syntax": [
|
|
745
|
-
"warn",
|
|
746
|
-
...sharedRestrictedSyntax.filter(
|
|
747
|
-
(rule) =>
|
|
748
|
-
rule.selector !==
|
|
749
|
-
"ExportNamedDeclaration[specifiers.length>1]:not([source])" &&
|
|
750
|
-
rule.selector !==
|
|
751
|
-
"Program:has(ExportNamedDeclaration:not([source]) ~ ExportNamedDeclaration:not([source]))" &&
|
|
752
|
-
rule.selector !==
|
|
753
|
-
"ExportNamedDeclaration:not([source]):not(:has(VariableDeclaration)):not(:has(FunctionDeclaration)):not(:has(ClassDeclaration)):not(:has(TSInterfaceDeclaration)):not(:has(TSTypeAliasDeclaration)):not(:has(TSEnumDeclaration))"
|
|
754
|
-
),
|
|
755
|
-
...tsOnlyRestrictedSyntax,
|
|
756
|
-
],
|
|
757
|
-
},
|
|
758
|
-
},
|
|
759
|
-
|
|
760
|
-
// Test files that should have ERROR level rules but exclude export specifier rule
|
|
761
|
-
{
|
|
762
|
-
files: [
|
|
763
|
-
"**/test/type-assertions/**",
|
|
764
|
-
"**/test/test-optional.ts",
|
|
765
|
-
"**/test/test-js-optional.js",
|
|
766
|
-
"**/test/test-record-literals.ts",
|
|
767
|
-
"**/test/no-env-access-test.ts",
|
|
768
|
-
"**/test/import-export-rules.ts",
|
|
769
|
-
],
|
|
770
|
-
rules: {
|
|
771
|
-
"max-lines-per-function": "off",
|
|
772
|
-
"no-restricted-syntax": [
|
|
773
|
-
"error", // Error level for these test files
|
|
774
|
-
...sharedRestrictedSyntax.filter(
|
|
775
|
-
(rule) =>
|
|
776
|
-
rule.selector !==
|
|
777
|
-
"ExportNamedDeclaration:not([source]):not(:has(VariableDeclaration)):not(:has(FunctionDeclaration)):not(:has(ClassDeclaration)):not(:has(TSInterfaceDeclaration)):not(:has(TSTypeAliasDeclaration)):not(:has(TSEnumDeclaration))"
|
|
778
|
-
),
|
|
779
|
-
...tsOnlyRestrictedSyntax,
|
|
780
|
-
],
|
|
781
|
-
},
|
|
782
|
-
},
|
|
783
|
-
|
|
784
|
-
// Test files configuration with mixed severity levels
|
|
785
|
-
{
|
|
786
|
-
files: [
|
|
787
|
-
"**/test/invalid.tsx", // Special handling for the main test file
|
|
788
|
-
"**/test/single-export-valid.ts", // Allow export specifiers for import/group-exports testing
|
|
789
|
-
"**/test/typescript-rules.ts", // Allow export specifiers for typescript rules testing
|
|
790
|
-
"**/test/type-assertions/indexed-access-valid.ts", // Allow export specifiers for type assertions testing
|
|
791
|
-
],
|
|
792
|
-
rules: {
|
|
793
|
-
"max-lines-per-function": "off",
|
|
794
|
-
"no-restricted-syntax": [
|
|
795
|
-
"warn", // Base level for most rules
|
|
796
|
-
...sharedRestrictedSyntax.filter(
|
|
797
|
-
(rule) =>
|
|
798
|
-
rule.selector !==
|
|
799
|
-
"ExportNamedDeclaration[specifiers.length>1]:not([source])" &&
|
|
800
|
-
rule.selector !==
|
|
801
|
-
"Program:has(ExportNamedDeclaration:not([source]) ~ ExportNamedDeclaration:not([source]))" &&
|
|
802
|
-
rule.selector !==
|
|
803
|
-
"ExportNamedDeclaration:not([source]):not(:has(VariableDeclaration)):not(:has(FunctionDeclaration)):not(:has(ClassDeclaration)):not(:has(TSInterfaceDeclaration)):not(:has(TSTypeAliasDeclaration)):not(:has(TSEnumDeclaration))"
|
|
804
|
-
),
|
|
805
|
-
...tsOnlyRestrictedSyntax,
|
|
806
|
-
],
|
|
807
|
-
},
|
|
808
|
-
},
|
|
724
|
+
// Test and spec files configuration (imported from separate config)
|
|
725
|
+
...testFilesConfig,
|
|
809
726
|
|
|
810
727
|
// Index files configuration - allow specific export patterns
|
|
811
728
|
{
|
|
@@ -928,11 +845,7 @@ const config = [
|
|
|
928
845
|
selector: "CallExpression[optional=true]",
|
|
929
846
|
message: "Optional chaining is not allowed.",
|
|
930
847
|
},
|
|
931
|
-
|
|
932
|
-
selector: 'LogicalExpression[operator="??"]',
|
|
933
|
-
message:
|
|
934
|
-
"Nullish coalescing operator (??) is not allowed. Use explicit null/undefined checks instead.",
|
|
935
|
-
},
|
|
848
|
+
allRules.noNullishCoalescingConfig,
|
|
936
849
|
{
|
|
937
850
|
selector: 'TSAsExpression[typeAnnotation.type="TSIndexedAccessType"]',
|
|
938
851
|
message:
|
|
@@ -974,18 +887,6 @@ const config = [
|
|
|
974
887
|
},
|
|
975
888
|
},
|
|
976
889
|
|
|
977
|
-
// Disable file length rules for configuration and spec files
|
|
978
|
-
{
|
|
979
|
-
files: [
|
|
980
|
-
"index.js", // Main configuration file
|
|
981
|
-
"**/rules/**/*.spec.js", // Spec files in rules directory
|
|
982
|
-
"**/scripts/**/*.js", // Script files
|
|
983
|
-
],
|
|
984
|
-
rules: {
|
|
985
|
-
"max-lines": "off",
|
|
986
|
-
"max-lines-per-function": "off",
|
|
987
|
-
},
|
|
988
|
-
},
|
|
989
890
|
|
|
990
891
|
// Allow default exports in configuration files (must be last to override)
|
|
991
892
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-config-agent",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.9",
|
|
4
4
|
"description": "ESLint configuration package with TypeScript support",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
"index.js",
|
|
12
12
|
"README.md",
|
|
13
13
|
"CHANGELOG.md",
|
|
14
|
-
"
|
|
14
|
+
"configs/",
|
|
15
|
+
"rules/**/index.js",
|
|
16
|
+
"!rules/**/examples/",
|
|
17
|
+
"!rules/**/*.spec.js",
|
|
18
|
+
"!rules/**/*.test.js"
|
|
15
19
|
],
|
|
16
20
|
"engines": {
|
|
17
21
|
"node": ">=18.0.0"
|
|
@@ -28,7 +32,7 @@
|
|
|
28
32
|
"test:edge": "eslint test/edge-cases.tsx",
|
|
29
33
|
"test:performance": "eslint test/performance-test.tsx",
|
|
30
34
|
"test:comprehensive": "node scripts/test-runner.js",
|
|
31
|
-
"test:ci": "eslint . --ignore-pattern 'test/**' --ignore-pattern 'scripts/**' --ignore-pattern 'rules/**/examples/**' --max-warnings 0",
|
|
35
|
+
"test:ci": "eslint . --ignore-pattern 'test/**' --ignore-pattern 'scripts/**' --ignore-pattern 'rules/**/examples/**' --ignore-pattern 'configs/**' --ignore-pattern 'index.js' --max-warnings 0",
|
|
32
36
|
"validate": "node scripts/validate-config.js",
|
|
33
37
|
"release": "dotenv -e .env -- release-it",
|
|
34
38
|
"release:patch": "dotenv -e .env -- release-it patch",
|
|
@@ -108,6 +112,7 @@
|
|
|
108
112
|
"typescript-eslint": "^8.40.0"
|
|
109
113
|
},
|
|
110
114
|
"dependencies": {
|
|
111
|
-
"eslint-plugin-
|
|
115
|
+
"eslint-plugin-default": "^1.0.1",
|
|
116
|
+
"eslint-plugin-error": "^1.1.3"
|
|
112
117
|
}
|
|
113
118
|
}
|
package/rules/index.js
CHANGED
|
@@ -17,6 +17,7 @@ import { noExportSpecifiersConfig } from "./no-empty-exports/index.js";
|
|
|
17
17
|
import { noClassPropertyDefaultsConfig } from "./no-class-property-defaults/index.js";
|
|
18
18
|
import { noDefaultClassExportRules } from "./no-default-class-export/index.js";
|
|
19
19
|
import { jsxClassNameRequiredRule } from "./jsx-classname-required/index.js";
|
|
20
|
+
import { noNullishCoalescingConfig } from "./nullish-coalescing/index.js";
|
|
20
21
|
|
|
21
22
|
// Plugin rule configurations
|
|
22
23
|
import { pluginRules } from "./plugin/index.js";
|
|
@@ -38,6 +39,7 @@ const allRules = {
|
|
|
38
39
|
noClassPropertyDefaultsConfig,
|
|
39
40
|
noDefaultClassExportRules,
|
|
40
41
|
jsxClassNameRequiredRule,
|
|
42
|
+
noNullishCoalescingConfig,
|
|
41
43
|
|
|
42
44
|
// Plugin rule configurations
|
|
43
45
|
pluginRules,
|
|
@@ -62,6 +64,7 @@ export {
|
|
|
62
64
|
noClassPropertyDefaultsConfig,
|
|
63
65
|
noDefaultClassExportRules,
|
|
64
66
|
jsxClassNameRequiredRule,
|
|
67
|
+
noNullishCoalescingConfig,
|
|
65
68
|
|
|
66
69
|
// Plugin rule configurations
|
|
67
70
|
pluginRules,
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# no-record-literal-types
|
|
2
|
+
|
|
3
|
+
Disallows using Record type with string literal keys in favor of more specific interface or type definitions.
|
|
4
|
+
|
|
5
|
+
## Rule Details
|
|
6
|
+
|
|
7
|
+
This rule helps enforce better type safety and readability by discouraging the use of `Record<LiteralKeys, ValueType>` patterns and encouraging more explicit type definitions.
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
❌ **Incorrect** - Record with literal keys:
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
type UserInfo = Record<"name" | "age", string>;
|
|
15
|
+
type Status = Record<"active", boolean>;
|
|
16
|
+
type Config = Record<"host" | "port" | "ssl", string>;
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
✅ **Correct** - Explicit interfaces and types:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
interface UserInfo {
|
|
23
|
+
name: string;
|
|
24
|
+
age: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type Status = {
|
|
28
|
+
active: boolean;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type Config = {
|
|
32
|
+
host: string;
|
|
33
|
+
port: number;
|
|
34
|
+
ssl: boolean;
|
|
35
|
+
};
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
✅ **Correct** - Generic Record types:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
type UserData = Record<string, unknown>;
|
|
42
|
+
type IndexMap = Record<number, string>;
|
|
43
|
+
type DynamicKeys = Record<`prefix-${string}`, boolean>;
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Why This Rule Exists
|
|
47
|
+
|
|
48
|
+
1. **Better IntelliSense**: Explicit interfaces provide better autocomplete and documentation
|
|
49
|
+
2. **Type Safety**: More specific types catch errors earlier
|
|
50
|
+
3. **Maintainability**: Clear structure makes code easier to understand and modify
|
|
51
|
+
4. **Consistency**: Encourages consistent type definition patterns
|
|
52
|
+
|
|
53
|
+
## Testing
|
|
54
|
+
|
|
55
|
+
This rule is tested through integration tests in `/test/test-record-literals.ts` which validates that the rule correctly identifies and reports violations in real TypeScript code.
|
|
56
|
+
|
|
57
|
+
The rule uses two AST selectors to comprehensively catch Record literal patterns:
|
|
58
|
+
1. `.params:first-child TSLiteralType` - Catches literals inside unions
|
|
59
|
+
2. `TSLiteralType:first-child` - Catches direct literal types
|
|
60
|
+
|
|
61
|
+
## Configuration
|
|
62
|
+
|
|
63
|
+
This rule is automatically included when using the eslint-config-agent package and applies to all TypeScript files.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule configuration for no-record-literal-types
|
|
3
|
+
*
|
|
4
|
+
* This rule disallows using Record type with string literal keys in favor of
|
|
5
|
+
* more specific interface or type definitions for better type safety and readability.
|
|
6
|
+
*
|
|
7
|
+
* The Record utility type with literal string keys can make code harder to understand
|
|
8
|
+
* and maintain because it doesn't explicitly define the structure of the object.
|
|
9
|
+
* Using specific interfaces or types provides better IntelliSense, type checking,
|
|
10
|
+
* and documentation.
|
|
11
|
+
*
|
|
12
|
+
* Examples:
|
|
13
|
+
* - ❌ Record<'name' | 'age', string>
|
|
14
|
+
* - ❌ Record<'active', boolean>
|
|
15
|
+
* - ❌ Record<'foo' | 'bar' | 'baz', number>
|
|
16
|
+
* - ✅ interface UserInfo { name: string; age: string; }
|
|
17
|
+
* - ✅ type Status = { active: boolean; }
|
|
18
|
+
* - ✅ Record<string, unknown> (generic keys are allowed)
|
|
19
|
+
*
|
|
20
|
+
* @see https://eslint.org/docs/latest/rules/no-restricted-syntax
|
|
21
|
+
* @see https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const rule = "error";
|
|
25
|
+
|
|
26
|
+
// First selector: matches TSLiteralType descendants of first param (catches literals inside unions)
|
|
27
|
+
const selectorNestedParams = 'TSTypeReference[typeName.name="Record"] > TSTypeParameterInstantiation > .params:first-child TSLiteralType';
|
|
28
|
+
|
|
29
|
+
// Second selector: matches direct TSLiteralType as first param
|
|
30
|
+
const selectorFirstChild = 'TSTypeReference[typeName.name="Record"] > TSTypeParameterInstantiation > TSLiteralType:first-child';
|
|
31
|
+
|
|
32
|
+
const message = "Avoid using Record with string literal keys. Use a more specific interface or type instead.";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Export the complete rule configurations for no-restricted-syntax
|
|
36
|
+
* Can be used in ESLint config as part of no-restricted-syntax rules
|
|
37
|
+
*/
|
|
38
|
+
const noRecordLiteralTypesNestedConfig = {
|
|
39
|
+
selector: selectorNestedParams,
|
|
40
|
+
message,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const noRecordLiteralTypesFirstChildConfig = {
|
|
44
|
+
selector: selectorFirstChild,
|
|
45
|
+
message,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Combined rule configurations for comprehensive Record literal type prevention
|
|
50
|
+
*/
|
|
51
|
+
const noRecordLiteralTypesConfigs = [
|
|
52
|
+
noRecordLiteralTypesNestedConfig,
|
|
53
|
+
noRecordLiteralTypesFirstChildConfig,
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
// Consolidated exports
|
|
57
|
+
export {
|
|
58
|
+
rule,
|
|
59
|
+
selectorNestedParams,
|
|
60
|
+
selectorFirstChild,
|
|
61
|
+
message,
|
|
62
|
+
noRecordLiteralTypesNestedConfig,
|
|
63
|
+
noRecordLiteralTypesFirstChildConfig,
|
|
64
|
+
noRecordLiteralTypesConfigs,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export default noRecordLiteralTypesConfigs;
|