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.
Files changed (79) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/configs/test-files.js +248 -0
  3. package/index.js +18 -117
  4. package/package.json +9 -4
  5. package/rules/index.js +3 -0
  6. package/rules/no-record-literal-types/README.md +63 -0
  7. package/rules/no-record-literal-types/index.js +67 -0
  8. package/rules/nullish-coalescing/index.js +45 -0
  9. package/rules/jsx-classname-required/jsx-classname-required.spec.js +0 -283
  10. package/rules/max-file-lines/examples/invalid/long-file.js +0 -113
  11. package/rules/max-file-lines/examples/invalid/medium-file.js +0 -127
  12. package/rules/max-file-lines/examples/invalid/very-long-file.ts +0 -144
  13. package/rules/max-file-lines/examples/valid/short-file.js +0 -66
  14. package/rules/max-file-lines/examples/valid/spec-file.spec.js +0 -44
  15. package/rules/max-file-lines/examples/valid/test-file.test.js +0 -25
  16. package/rules/max-file-lines/max-file-lines.spec.js +0 -74
  17. package/rules/max-function-lines/max-function-lines.spec.js +0 -74
  18. package/rules/no-class-property-defaults/examples/invalid/array-default.ts +0 -7
  19. package/rules/no-class-property-defaults/examples/invalid/boolean-default.ts +0 -7
  20. package/rules/no-class-property-defaults/examples/invalid/complex-expression-default.ts +0 -7
  21. package/rules/no-class-property-defaults/examples/invalid/function-default.ts +0 -7
  22. package/rules/no-class-property-defaults/examples/invalid/number-default.ts +0 -7
  23. package/rules/no-class-property-defaults/examples/invalid/object-default.ts +0 -7
  24. package/rules/no-class-property-defaults/examples/invalid/private-default.ts +0 -7
  25. package/rules/no-class-property-defaults/examples/invalid/readonly-default.ts +0 -7
  26. package/rules/no-class-property-defaults/examples/invalid/static-default.ts +0 -7
  27. package/rules/no-class-property-defaults/examples/invalid/string-default.ts +0 -7
  28. package/rules/no-class-property-defaults/examples/valid/abstract-property.ts +0 -7
  29. package/rules/no-class-property-defaults/examples/valid/conditional-initialization.ts +0 -11
  30. package/rules/no-class-property-defaults/examples/valid/constructor-initialization.ts +0 -11
  31. package/rules/no-class-property-defaults/examples/valid/method-initialization.ts +0 -15
  32. package/rules/no-class-property-defaults/examples/valid/no-default-property.ts +0 -11
  33. package/rules/no-class-property-defaults/examples/valid/readonly-no-default.ts +0 -11
  34. package/rules/no-class-property-defaults/no-class-property-defaults.spec.js +0 -327
  35. package/rules/no-default-class-export/examples/invalid/default-abstract-class.ts +0 -10
  36. package/rules/no-default-class-export/examples/invalid/default-class-declaration.ts +0 -12
  37. package/rules/no-default-class-export/examples/invalid/default-class-expression.ts +0 -12
  38. package/rules/no-default-class-export/examples/invalid/default-generic-class.ts +0 -12
  39. package/rules/no-default-class-export/examples/valid/abstract-class-export.ts +0 -17
  40. package/rules/no-default-class-export/examples/valid/generic-class-export.ts +0 -16
  41. package/rules/no-default-class-export/examples/valid/multiple-named-exports.ts +0 -22
  42. package/rules/no-default-class-export/examples/valid/named-class-export.ts +0 -12
  43. package/rules/no-default-class-export/no-default-class-export.spec.js +0 -301
  44. package/rules/no-empty-exports/examples/invalid/empty-export.js +0 -2
  45. package/rules/no-empty-exports/examples/invalid/multiple-exports.js +0 -5
  46. package/rules/no-empty-exports/examples/invalid/single-export.js +0 -3
  47. package/rules/no-empty-exports/examples/valid/default-export.js +0 -3
  48. package/rules/no-empty-exports/examples/valid/direct-class.js +0 -6
  49. package/rules/no-empty-exports/examples/valid/direct-const.js +0 -2
  50. package/rules/no-empty-exports/examples/valid/direct-function.js +0 -4
  51. package/rules/no-empty-exports/examples/valid/re-export-from.js +0 -2
  52. package/rules/no-empty-exports/examples/valid/re-export-star.js +0 -2
  53. package/rules/no-empty-exports/no-empty-exports.spec.js +0 -104
  54. package/rules/no-process-env-properties/no-process-env-properties.spec.js +0 -207
  55. package/rules/no-trailing-spaces/no-trailing-spaces.spec.js +0 -128
  56. package/rules/no-type-assertions/no-type-assertions.spec.js +0 -246
  57. package/rules/plugin/import/group-exports/examples/invalid/mixed-declaration-styles.js +0 -8
  58. package/rules/plugin/import/group-exports/examples/invalid/multiple-export-statements.js +0 -8
  59. package/rules/plugin/import/group-exports/examples/invalid/scattered-exports.js +0 -10
  60. package/rules/plugin/import/group-exports/examples/valid/direct-exports.js +0 -2
  61. package/rules/plugin/import/group-exports/examples/valid/mixed-with-default.js +0 -7
  62. package/rules/plugin/import/group-exports/examples/valid/single-export-statement.js +0 -6
  63. package/rules/plugin/import/no-unused-modules/examples/invalid/dead-code.js +0 -2
  64. package/rules/plugin/import/no-unused-modules/examples/invalid/unused-class.js +0 -6
  65. package/rules/plugin/import/no-unused-modules/examples/invalid/unused-export.js +0 -2
  66. package/rules/plugin/import/no-unused-modules/examples/invalid/unused-function.js +0 -4
  67. package/rules/plugin/import/no-unused-modules/examples/invalid/unused-module.js +0 -4
  68. package/rules/plugin/import/no-unused-modules/examples/valid/consumed-import.js +0 -8
  69. package/rules/plugin/import/no-unused-modules/examples/valid/entry-point.js +0 -5
  70. package/rules/plugin/import/no-unused-modules/examples/valid/used-class.js +0 -6
  71. package/rules/plugin/import/no-unused-modules/examples/valid/used-export.js +0 -2
  72. package/rules/plugin/import/no-unused-modules/examples/valid/used-function.js +0 -4
  73. package/rules/plugin/typescript-eslint/no-explicit-any/examples/invalid/any-array.ts +0 -2
  74. package/rules/plugin/typescript-eslint/no-explicit-any/examples/invalid/any-parameter.ts +0 -4
  75. package/rules/plugin/typescript-eslint/no-explicit-any/examples/invalid/any-return-type.ts +0 -4
  76. package/rules/plugin/typescript-eslint/no-explicit-any/examples/invalid/any-variable.ts +0 -5
  77. package/rules/plugin/typescript-eslint/no-explicit-any/examples/valid/generics.ts +0 -16
  78. package/rules/plugin/typescript-eslint/no-explicit-any/examples/valid/specific-types.ts +0 -9
  79. 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
- // Disable function and file size limits for test and spec files
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.7",
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
- "rules/"
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-error": "^1.1.1"
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;