eslint-config-agent 1.1.2 → 1.1.4
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 +16 -0
- package/index.js +78 -35
- package/package.json +1 -1
- package/rules/index.js +60 -0
- package/rules/no-empty-exports/examples/invalid/empty-export.js +2 -0
- package/rules/no-empty-exports/examples/invalid/multiple-exports.js +5 -0
- package/rules/no-empty-exports/examples/invalid/single-export.js +3 -0
- package/rules/no-empty-exports/examples/valid/default-export.js +3 -0
- package/rules/no-empty-exports/examples/valid/direct-class.js +6 -0
- package/rules/no-empty-exports/examples/valid/direct-const.js +2 -0
- package/rules/no-empty-exports/examples/valid/direct-function.js +4 -0
- package/rules/no-empty-exports/examples/valid/re-export-from.js +2 -0
- package/rules/no-empty-exports/examples/valid/re-export-star.js +2 -0
- package/rules/no-empty-exports/index.js +16 -0
- package/rules/no-empty-exports/no-empty-exports.spec.js +104 -0
- package/rules/no-type-assertions/index.js +38 -0
- package/rules/no-type-assertions/no-type-assertions.spec.js +246 -0
- package/rules/plugin/typescript-eslint/index.js +1 -7
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file. See [Conven
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
## [1.1.4](https://github.com/tupe12334/eslint-config/compare/v1.1.3...v1.1.4) (2025-09-10)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* add no-empty-exports rule to prevent empty export specifiers and include test cases ([7757007](https://github.com/tupe12334/eslint-config/commit/77570076e41bdab64dbed53ca7a40a935eda7e3a))
|
|
12
|
+
* enhance export specifier rules and update test configurations for improved validation ([fe64b9c](https://github.com/tupe12334/eslint-config/commit/fe64b9cb7f1bdf80b16a97db75a5fd04901c2e7f))
|
|
13
|
+
* remove package-specific ignores from ESLint config for cleaner configuration ([03cef75](https://github.com/tupe12334/eslint-config/commit/03cef75a6ad6d682ede5f4c5dc5d5e1a8d0a9aaa))
|
|
14
|
+
|
|
15
|
+
## [1.1.3](https://github.com/tupe12334/eslint-config/compare/v1.1.2...v1.1.3) (2025-09-10)
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* add no-type-assertions rule to disallow TypeScript type assertions using "as" keyword ([39b708f](https://github.com/tupe12334/eslint-config/commit/39b708f9da6eb0c491b0e53248af78821f4b3598))
|
|
20
|
+
* consolidate ESLint rule imports into a single index file for better organization ([37be460](https://github.com/tupe12334/eslint-config/commit/37be46083350d151a5803b9c230c538caa9eb857))
|
|
21
|
+
* refactor ESLint rule imports to use a consolidated allRules object for improved organization ([47c3eea](https://github.com/tupe12334/eslint-config/commit/47c3eea5f02dd204bf86ab6150d319eed07440da))
|
|
22
|
+
|
|
7
23
|
## [1.1.2](https://github.com/tupe12334/eslint-config/compare/v1.1.1...v1.1.2) (2025-09-10)
|
|
8
24
|
|
|
9
25
|
### Features
|
package/index.js
CHANGED
|
@@ -9,12 +9,7 @@ import nPlugin from "eslint-plugin-n";
|
|
|
9
9
|
import classExportPlugin from "eslint-plugin-class-export";
|
|
10
10
|
import storybookPlugin from "eslint-plugin-storybook";
|
|
11
11
|
import globals from "globals";
|
|
12
|
-
import
|
|
13
|
-
import { maxFunctionLinesWarning, maxFunctionLinesError } from "./rules/max-function-lines/index.js";
|
|
14
|
-
import { maxFileLinesWarning, maxFileLinesError } from "./rules/max-file-lines/index.js";
|
|
15
|
-
import { pluginRules } from "./rules/plugin/index.js";
|
|
16
|
-
import { typescriptEslintRules } from "./rules/plugin/typescript-eslint/index.js";
|
|
17
|
-
import { noProcessEnvPropertiesConfig } from "./rules/no-process-env-properties/index.js";
|
|
12
|
+
import allRules from "./rules/index.js";
|
|
18
13
|
|
|
19
14
|
// Conditionally import preact plugin if available
|
|
20
15
|
let preactPlugin = null;
|
|
@@ -24,21 +19,20 @@ try {
|
|
|
24
19
|
// eslint-plugin-preact is not available
|
|
25
20
|
}
|
|
26
21
|
|
|
27
|
-
|
|
28
22
|
// Shared rules for both JS and TS files
|
|
29
23
|
const sharedRules = {
|
|
30
|
-
...pluginRules,
|
|
24
|
+
...allRules.pluginRules,
|
|
31
25
|
"object-curly-newline": "off",
|
|
32
26
|
"no-shadow": "off",
|
|
33
27
|
"comma-dangle": "off",
|
|
34
28
|
"function-paren-newline": "off",
|
|
35
29
|
quotes: "off",
|
|
36
30
|
"no-unused-vars": "off",
|
|
37
|
-
"max-lines-per-function": maxFunctionLinesWarning,
|
|
38
|
-
"max-lines": maxFileLinesWarning,
|
|
31
|
+
"max-lines-per-function": allRules.maxFunctionLinesWarning,
|
|
32
|
+
"max-lines": allRules.maxFileLinesWarning,
|
|
39
33
|
semi: "off",
|
|
40
34
|
complexity: "off",
|
|
41
|
-
"no-trailing-spaces": noTrailingSpacesConfig,
|
|
35
|
+
"no-trailing-spaces": allRules.noTrailingSpacesConfig,
|
|
42
36
|
"operator-linebreak": "off",
|
|
43
37
|
"implicit-arrow-linebreak": "off",
|
|
44
38
|
"arrow-body-style": "off",
|
|
@@ -99,13 +93,15 @@ const sharedRestrictedSyntax = [
|
|
|
99
93
|
message:
|
|
100
94
|
"Exporting from external libraries is not allowed. Only re-export from relative paths or scoped packages.",
|
|
101
95
|
},
|
|
102
|
-
noProcessEnvPropertiesConfig,
|
|
96
|
+
allRules.noProcessEnvPropertiesConfig,
|
|
97
|
+
allRules.noExportSpecifiersConfig,
|
|
103
98
|
];
|
|
104
99
|
|
|
105
100
|
// Required export rules (always errors)
|
|
106
101
|
const requiredExportRules = [
|
|
107
102
|
{
|
|
108
|
-
selector:
|
|
103
|
+
selector:
|
|
104
|
+
"ClassDeclaration:not(ExportNamedDeclaration > ClassDeclaration):not(ExportDefaultDeclaration > ClassDeclaration)",
|
|
109
105
|
message:
|
|
110
106
|
"Classes must be exported. Use 'export class' or 'export default class'.",
|
|
111
107
|
},
|
|
@@ -148,11 +144,7 @@ const tsOnlyRestrictedSyntax = [
|
|
|
148
144
|
message:
|
|
149
145
|
"Class properties with literal unions should use a named type declaration.",
|
|
150
146
|
},
|
|
151
|
-
|
|
152
|
-
selector:
|
|
153
|
-
'TSAsExpression:not(:has(TSTypeReference[typeName.name="const"]))',
|
|
154
|
-
message: 'Type assertions with "as" are not allowed except for "as const".',
|
|
155
|
-
},
|
|
147
|
+
allRules.noTypeAssertionsConfig,
|
|
156
148
|
{
|
|
157
149
|
selector: "TSAsExpression:has(> TSIndexedAccessType > TSTypeQuery)",
|
|
158
150
|
message:
|
|
@@ -208,9 +200,6 @@ const config = [
|
|
|
208
200
|
},
|
|
209
201
|
},
|
|
210
202
|
reactHooks.configs["recommended-latest"],
|
|
211
|
-
{
|
|
212
|
-
ignores: ["packages/auth-service-sdk/**"],
|
|
213
|
-
},
|
|
214
203
|
js.configs.recommended,
|
|
215
204
|
|
|
216
205
|
// TypeScript and TSX files
|
|
@@ -221,10 +210,6 @@ const config = [
|
|
|
221
210
|
"**/dist/**",
|
|
222
211
|
"**/build/**",
|
|
223
212
|
"pnpm-lock.yaml",
|
|
224
|
-
"packages/auth-service-sdk/**",
|
|
225
|
-
"packages/auth-service-sdk/src/core/**",
|
|
226
|
-
"packages/auth-service-sdk/src/client/**",
|
|
227
|
-
"packages/auth-service-sdk/src/*.gen.ts",
|
|
228
213
|
"**/*.stories.{js,jsx,ts,tsx}",
|
|
229
214
|
],
|
|
230
215
|
languageOptions: {
|
|
@@ -257,7 +242,7 @@ const config = [
|
|
|
257
242
|
},
|
|
258
243
|
rules: {
|
|
259
244
|
...sharedRules,
|
|
260
|
-
...typescriptEslintRules,
|
|
245
|
+
...allRules.typescriptEslintRules,
|
|
261
246
|
"no-undef": "off", // TypeScript handles this
|
|
262
247
|
"no-restricted-syntax": [
|
|
263
248
|
"error",
|
|
@@ -434,11 +419,11 @@ const config = [
|
|
|
434
419
|
"**/dist/**",
|
|
435
420
|
"**/build/**",
|
|
436
421
|
"pnpm-lock.yaml",
|
|
437
|
-
"packages/auth-service-sdk/**",
|
|
438
422
|
"**/*.umd.js",
|
|
439
423
|
"**/*.cjs",
|
|
440
424
|
"**/*.mjs",
|
|
441
425
|
"**/*.stories.{js,jsx,ts,tsx}",
|
|
426
|
+
"**/rules/**",
|
|
442
427
|
],
|
|
443
428
|
languageOptions: {
|
|
444
429
|
parserOptions: {
|
|
@@ -504,7 +489,6 @@ const config = [
|
|
|
504
489
|
"**/dist/**",
|
|
505
490
|
"**/build/**",
|
|
506
491
|
"pnpm-lock.yaml",
|
|
507
|
-
"packages/auth-service-sdk/**",
|
|
508
492
|
"**/*.stories.{js,jsx,ts,tsx}",
|
|
509
493
|
],
|
|
510
494
|
languageOptions: {
|
|
@@ -624,7 +608,6 @@ const config = [
|
|
|
624
608
|
"**/dist/**",
|
|
625
609
|
"**/build/**",
|
|
626
610
|
"pnpm-lock.yaml",
|
|
627
|
-
"packages/auth-service-sdk/**",
|
|
628
611
|
"**/*.stories.{js,jsx,ts,tsx}",
|
|
629
612
|
],
|
|
630
613
|
languageOptions: {
|
|
@@ -725,7 +708,33 @@ const config = [
|
|
|
725
708
|
rule.selector !==
|
|
726
709
|
"ExportNamedDeclaration[specifiers.length>1]:not([source])" &&
|
|
727
710
|
rule.selector !==
|
|
728
|
-
"Program:has(ExportNamedDeclaration:not([source]) ~ ExportNamedDeclaration:not([source]))"
|
|
711
|
+
"Program:has(ExportNamedDeclaration:not([source]) ~ ExportNamedDeclaration:not([source]))" &&
|
|
712
|
+
rule.selector !==
|
|
713
|
+
"ExportNamedDeclaration:not([source]):not(:has(VariableDeclaration)):not(:has(FunctionDeclaration)):not(:has(ClassDeclaration)):not(:has(TSInterfaceDeclaration)):not(:has(TSTypeAliasDeclaration)):not(:has(TSEnumDeclaration))"
|
|
714
|
+
),
|
|
715
|
+
...tsOnlyRestrictedSyntax,
|
|
716
|
+
],
|
|
717
|
+
},
|
|
718
|
+
},
|
|
719
|
+
|
|
720
|
+
// Test files that should have ERROR level rules but exclude export specifier rule
|
|
721
|
+
{
|
|
722
|
+
files: [
|
|
723
|
+
"**/test/type-assertions/**",
|
|
724
|
+
"**/test/test-optional.ts",
|
|
725
|
+
"**/test/test-js-optional.js",
|
|
726
|
+
"**/test/test-record-literals.ts",
|
|
727
|
+
"**/test/no-env-access-test.ts",
|
|
728
|
+
"**/test/import-export-rules.ts",
|
|
729
|
+
],
|
|
730
|
+
rules: {
|
|
731
|
+
"max-lines-per-function": "off",
|
|
732
|
+
"no-restricted-syntax": [
|
|
733
|
+
"error", // Error level for these test files
|
|
734
|
+
...sharedRestrictedSyntax.filter(
|
|
735
|
+
(rule) =>
|
|
736
|
+
rule.selector !==
|
|
737
|
+
"ExportNamedDeclaration:not([source]):not(:has(VariableDeclaration)):not(:has(FunctionDeclaration)):not(:has(ClassDeclaration)):not(:has(TSInterfaceDeclaration)):not(:has(TSTypeAliasDeclaration)):not(:has(TSEnumDeclaration))"
|
|
729
738
|
),
|
|
730
739
|
...tsOnlyRestrictedSyntax,
|
|
731
740
|
],
|
|
@@ -736,6 +745,9 @@ const config = [
|
|
|
736
745
|
{
|
|
737
746
|
files: [
|
|
738
747
|
"**/test/invalid.tsx", // Special handling for the main test file
|
|
748
|
+
"**/test/single-export-valid.ts", // Allow export specifiers for import/group-exports testing
|
|
749
|
+
"**/test/typescript-rules.ts", // Allow export specifiers for typescript rules testing
|
|
750
|
+
"**/test/type-assertions/indexed-access-valid.ts", // Allow export specifiers for type assertions testing
|
|
739
751
|
],
|
|
740
752
|
rules: {
|
|
741
753
|
"max-lines-per-function": "off",
|
|
@@ -746,7 +758,9 @@ const config = [
|
|
|
746
758
|
rule.selector !==
|
|
747
759
|
"ExportNamedDeclaration[specifiers.length>1]:not([source])" &&
|
|
748
760
|
rule.selector !==
|
|
749
|
-
"Program:has(ExportNamedDeclaration:not([source]) ~ ExportNamedDeclaration:not([source]))"
|
|
761
|
+
"Program:has(ExportNamedDeclaration:not([source]) ~ ExportNamedDeclaration:not([source]))" &&
|
|
762
|
+
rule.selector !==
|
|
763
|
+
"ExportNamedDeclaration:not([source]):not(:has(VariableDeclaration)):not(:has(FunctionDeclaration)):not(:has(ClassDeclaration)):not(:has(TSInterfaceDeclaration)):not(:has(TSTypeAliasDeclaration)):not(:has(TSEnumDeclaration))"
|
|
750
764
|
),
|
|
751
765
|
...tsOnlyRestrictedSyntax,
|
|
752
766
|
// For TSX files, also include required export rules as warnings since we can't mix severities
|
|
@@ -786,7 +800,13 @@ const config = [
|
|
|
786
800
|
// Switch case rules as errors for all TypeScript/JSX files (must come last to override)
|
|
787
801
|
{
|
|
788
802
|
files: ["**/*.{ts,tsx,js,jsx}"],
|
|
789
|
-
ignores: [
|
|
803
|
+
ignores: [
|
|
804
|
+
"**/*.stories.{js,jsx,ts,tsx}",
|
|
805
|
+
"**/test/**",
|
|
806
|
+
"!**/test/export/**",
|
|
807
|
+
"!**/test/required-exports/**",
|
|
808
|
+
"**/rules/**",
|
|
809
|
+
],
|
|
790
810
|
rules: {
|
|
791
811
|
"no-restricted-syntax": [
|
|
792
812
|
"error",
|
|
@@ -867,7 +887,9 @@ const config = [
|
|
|
867
887
|
message:
|
|
868
888
|
'Type assertions with indexed access types like "as (typeof X)[number]" are not allowed. Use a named type instead.',
|
|
869
889
|
},
|
|
870
|
-
|
|
890
|
+
allRules.noTypeAssertionsConfig,
|
|
891
|
+
allRules.noProcessEnvPropertiesConfig,
|
|
892
|
+
allRules.noExportSpecifiersConfig,
|
|
871
893
|
// Export restriction rules
|
|
872
894
|
// Required export rules
|
|
873
895
|
...requiredExportRules,
|
|
@@ -897,9 +919,9 @@ const config = [
|
|
|
897
919
|
ignores: ["**/*.stories.{js,jsx,ts,tsx}"],
|
|
898
920
|
rules: {
|
|
899
921
|
// Function length: error at 70+ lines
|
|
900
|
-
"max-lines-per-function": maxFunctionLinesError,
|
|
922
|
+
"max-lines-per-function": allRules.maxFunctionLinesError,
|
|
901
923
|
// File length: error at 100+ lines
|
|
902
|
-
"max-lines": maxFileLinesError,
|
|
924
|
+
"max-lines": allRules.maxFileLinesError,
|
|
903
925
|
},
|
|
904
926
|
},
|
|
905
927
|
|
|
@@ -954,6 +976,27 @@ const config = [
|
|
|
954
976
|
...storybookPlugin.configs.recommended.rules,
|
|
955
977
|
},
|
|
956
978
|
},
|
|
979
|
+
|
|
980
|
+
// Rules directory configuration - allow export specifiers for API definitions
|
|
981
|
+
{
|
|
982
|
+
files: ["**/rules/**/*.{js,ts}"],
|
|
983
|
+
languageOptions: {
|
|
984
|
+
globals: {
|
|
985
|
+
...globals.node,
|
|
986
|
+
},
|
|
987
|
+
},
|
|
988
|
+
rules: {
|
|
989
|
+
"no-restricted-syntax": [
|
|
990
|
+
"error",
|
|
991
|
+
// Include all shared rules EXCEPT our export specifier rule
|
|
992
|
+
...sharedRestrictedSyntax.filter(
|
|
993
|
+
(rule) =>
|
|
994
|
+
rule.selector !==
|
|
995
|
+
"ExportNamedDeclaration:not([source]):not(:has(VariableDeclaration)):not(:has(FunctionDeclaration)):not(:has(ClassDeclaration)):not(:has(TSInterfaceDeclaration)):not(:has(TSTypeAliasDeclaration)):not(:has(TSEnumDeclaration))"
|
|
996
|
+
),
|
|
997
|
+
],
|
|
998
|
+
},
|
|
999
|
+
},
|
|
957
1000
|
];
|
|
958
1001
|
|
|
959
1002
|
export default config;
|
package/package.json
CHANGED
package/rules/index.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aggregated exports for all custom ESLint rules
|
|
3
|
+
*
|
|
4
|
+
* This file centralizes imports from all rule modules in the rules/ directory
|
|
5
|
+
* and re-exports them for easier consumption by the main configuration.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Core rule configurations
|
|
9
|
+
import noTrailingSpacesConfig from "./no-trailing-spaces/index.js";
|
|
10
|
+
import { maxFunctionLinesWarning, maxFunctionLinesError } from "./max-function-lines/index.js";
|
|
11
|
+
import { maxFileLinesWarning, maxFileLinesError } from "./max-file-lines/index.js";
|
|
12
|
+
|
|
13
|
+
// Custom restricted syntax rules
|
|
14
|
+
import { noProcessEnvPropertiesConfig } from "./no-process-env-properties/index.js";
|
|
15
|
+
import { noTypeAssertionsConfig } from "./no-type-assertions/index.js";
|
|
16
|
+
import { noExportSpecifiersConfig } from "./no-empty-exports/index.js";
|
|
17
|
+
|
|
18
|
+
// Plugin rule configurations
|
|
19
|
+
import { pluginRules } from "./plugin/index.js";
|
|
20
|
+
import { typescriptEslintRules } from "./plugin/typescript-eslint/index.js";
|
|
21
|
+
|
|
22
|
+
// Consolidated exports
|
|
23
|
+
const allRules = {
|
|
24
|
+
// Core rule configurations
|
|
25
|
+
noTrailingSpacesConfig,
|
|
26
|
+
maxFunctionLinesWarning,
|
|
27
|
+
maxFunctionLinesError,
|
|
28
|
+
maxFileLinesWarning,
|
|
29
|
+
maxFileLinesError,
|
|
30
|
+
|
|
31
|
+
// Custom restricted syntax rules
|
|
32
|
+
noProcessEnvPropertiesConfig,
|
|
33
|
+
noTypeAssertionsConfig,
|
|
34
|
+
noExportSpecifiersConfig,
|
|
35
|
+
|
|
36
|
+
// Plugin rule configurations
|
|
37
|
+
pluginRules,
|
|
38
|
+
typescriptEslintRules,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default allRules;
|
|
42
|
+
|
|
43
|
+
// Named exports for backward compatibility
|
|
44
|
+
export {
|
|
45
|
+
// Core rule configurations
|
|
46
|
+
noTrailingSpacesConfig,
|
|
47
|
+
maxFunctionLinesWarning,
|
|
48
|
+
maxFunctionLinesError,
|
|
49
|
+
maxFileLinesWarning,
|
|
50
|
+
maxFileLinesError,
|
|
51
|
+
|
|
52
|
+
// Custom restricted syntax rules
|
|
53
|
+
noProcessEnvPropertiesConfig,
|
|
54
|
+
noTypeAssertionsConfig,
|
|
55
|
+
noExportSpecifiersConfig,
|
|
56
|
+
|
|
57
|
+
// Plugin rule configurations
|
|
58
|
+
pluginRules,
|
|
59
|
+
typescriptEslintRules,
|
|
60
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Rule to prevent export { } statements of any length
|
|
2
|
+
|
|
3
|
+
const selector = 'ExportNamedDeclaration:not([source]):not(:has(VariableDeclaration)):not(:has(FunctionDeclaration)):not(:has(ClassDeclaration)):not(:has(TSInterfaceDeclaration)):not(:has(TSTypeAliasDeclaration)):not(:has(TSEnumDeclaration))';
|
|
4
|
+
const message = 'Export specifier syntax "export { ... }" is not allowed. Use direct exports instead.';
|
|
5
|
+
|
|
6
|
+
const rule = {
|
|
7
|
+
selector,
|
|
8
|
+
message,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const noExportSpecifiersConfig = {
|
|
12
|
+
selector,
|
|
13
|
+
message,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export { rule, selector, message, noExportSpecifiersConfig };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { RuleTester } from "eslint";
|
|
2
|
+
import { selector, message } from "./index.js";
|
|
3
|
+
|
|
4
|
+
const ruleTester = new RuleTester({
|
|
5
|
+
languageOptions: {
|
|
6
|
+
ecmaVersion: 2022,
|
|
7
|
+
sourceType: "module",
|
|
8
|
+
parserOptions: {
|
|
9
|
+
ecmaFeatures: {
|
|
10
|
+
jsx: true,
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// Create a mock rule for testing
|
|
17
|
+
const mockRule = {
|
|
18
|
+
meta: {
|
|
19
|
+
type: "problem",
|
|
20
|
+
docs: {
|
|
21
|
+
description: "Disallow empty export statements",
|
|
22
|
+
},
|
|
23
|
+
schema: [],
|
|
24
|
+
},
|
|
25
|
+
create(context) {
|
|
26
|
+
return {
|
|
27
|
+
[selector]: (node) => {
|
|
28
|
+
context.report({
|
|
29
|
+
node,
|
|
30
|
+
message,
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
ruleTester.run("no-empty-exports", mockRule, {
|
|
38
|
+
valid: [
|
|
39
|
+
// Named exports with declarations
|
|
40
|
+
'export const foo = 1;',
|
|
41
|
+
'export function bar() {}',
|
|
42
|
+
'export class Baz {}',
|
|
43
|
+
|
|
44
|
+
// Default exports
|
|
45
|
+
'const foo = 1; export default foo;',
|
|
46
|
+
'export default function() {}',
|
|
47
|
+
'export default class {}',
|
|
48
|
+
|
|
49
|
+
// Re-exports from other modules (allowed)
|
|
50
|
+
'export { } from "./other";',
|
|
51
|
+
'export { foo } from "./other";',
|
|
52
|
+
'export * from "./other";',
|
|
53
|
+
'export * as namespace from "./other";',
|
|
54
|
+
|
|
55
|
+
// Regular exports
|
|
56
|
+
'const Bar = {}; export { Bar };',
|
|
57
|
+
|
|
58
|
+
// No exports at all
|
|
59
|
+
'const foo = 1;',
|
|
60
|
+
'function bar() {}',
|
|
61
|
+
'class Baz {}',
|
|
62
|
+
|
|
63
|
+
// Named exports with local variables
|
|
64
|
+
'const foo = 1; const bar = 2; export { foo };',
|
|
65
|
+
'const foo = 1; const bar = 2; export { foo, bar };',
|
|
66
|
+
'const foo = 1; export { foo as bar };',
|
|
67
|
+
],
|
|
68
|
+
invalid: [
|
|
69
|
+
{
|
|
70
|
+
code: 'export { };',
|
|
71
|
+
errors: [{ message }],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
code: 'export {\n};',
|
|
75
|
+
errors: [{ message }],
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
code: 'export { };',
|
|
79
|
+
errors: [{ message }],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
code: `export {
|
|
83
|
+
};`,
|
|
84
|
+
errors: [{ message }],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
code: 'export { /* comment */ };',
|
|
88
|
+
errors: [{ message }],
|
|
89
|
+
},
|
|
90
|
+
// Multiple empty exports
|
|
91
|
+
{
|
|
92
|
+
code: `export { };
|
|
93
|
+
export { };`,
|
|
94
|
+
errors: [{ message }, { message }],
|
|
95
|
+
},
|
|
96
|
+
// Mixed with valid exports
|
|
97
|
+
{
|
|
98
|
+
code: `export const foo = 1;
|
|
99
|
+
export { };
|
|
100
|
+
export function bar() {}`,
|
|
101
|
+
errors: [{ message }],
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule configuration for no-type-assertions
|
|
3
|
+
*
|
|
4
|
+
* This rule disallows TypeScript type assertions using the "as" keyword,
|
|
5
|
+
* except for "as const" which is allowed for creating readonly literal types.
|
|
6
|
+
*
|
|
7
|
+
* Examples:
|
|
8
|
+
* - ❌ value as string
|
|
9
|
+
* - ❌ value as User
|
|
10
|
+
* - ❌ obj as { name: string }
|
|
11
|
+
* - ✅ { mode: 'prod' } as const
|
|
12
|
+
* - ✅ ['a', 'b'] as const
|
|
13
|
+
*
|
|
14
|
+
* @see https://eslint.org/docs/latest/rules/no-restricted-syntax
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const rule = "error";
|
|
18
|
+
|
|
19
|
+
const selector =
|
|
20
|
+
'TSAsExpression:not(:has(TSTypeReference[typeName.name="const"]))';
|
|
21
|
+
|
|
22
|
+
const message =
|
|
23
|
+
'Type assertions with "as" are not allowed except for "as const".';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Export the complete rule configuration for no-restricted-syntax
|
|
27
|
+
* Can be used in ESLint config as part of no-restricted-syntax rules:
|
|
28
|
+
* "no-restricted-syntax": ["error", ...otherRules, noTypeAssertionsConfig]
|
|
29
|
+
*/
|
|
30
|
+
const noTypeAssertionsConfig = {
|
|
31
|
+
selector,
|
|
32
|
+
message,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Consolidated exports
|
|
36
|
+
export { rule, selector, message, noTypeAssertionsConfig };
|
|
37
|
+
|
|
38
|
+
export default noTypeAssertionsConfig;
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { RuleTester } from "eslint";
|
|
2
|
+
import { noTypeAssertionsConfig } from "./index.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Test suite for no-type-assertions rule
|
|
6
|
+
*
|
|
7
|
+
* This tests the no-restricted-syntax configuration that prevents
|
|
8
|
+
* TypeScript type assertions using "as" keyword except for "as const".
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Create a custom rule for testing our selector
|
|
12
|
+
const noTypeAssertionsRule = {
|
|
13
|
+
meta: {
|
|
14
|
+
type: "problem",
|
|
15
|
+
docs: {
|
|
16
|
+
description: "Disallow type assertions except 'as const'",
|
|
17
|
+
},
|
|
18
|
+
messages: {
|
|
19
|
+
noTypeAssertions: noTypeAssertionsConfig.message,
|
|
20
|
+
},
|
|
21
|
+
schema: [],
|
|
22
|
+
},
|
|
23
|
+
create(context) {
|
|
24
|
+
return {
|
|
25
|
+
[noTypeAssertionsConfig.selector](node) {
|
|
26
|
+
context.report({
|
|
27
|
+
node,
|
|
28
|
+
messageId: "noTypeAssertions",
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const ruleTester = new RuleTester({
|
|
36
|
+
languageOptions: {
|
|
37
|
+
ecmaVersion: 2022,
|
|
38
|
+
sourceType: "module",
|
|
39
|
+
parser: (await import("@typescript-eslint/parser")).default,
|
|
40
|
+
parserOptions: {
|
|
41
|
+
ecmaFeatures: {
|
|
42
|
+
jsx: true,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
ruleTester.run("no-type-assertions", noTypeAssertionsRule, {
|
|
49
|
+
valid: [
|
|
50
|
+
// Valid: "as const" usage
|
|
51
|
+
{
|
|
52
|
+
code: "const config = { mode: 'production' } as const;",
|
|
53
|
+
name: "object as const",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
code: "const colors = ['red', 'green', 'blue'] as const;",
|
|
57
|
+
name: "array as const",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
code: "const status = 'loading' as const;",
|
|
61
|
+
name: "string literal as const",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
code: "const numbers = [1, 2, 3] as const;",
|
|
65
|
+
name: "number array as const",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
code: "const tuple = ['name', 42, true] as const;",
|
|
69
|
+
name: "mixed tuple as const",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
code: "const nested = { items: [{ id: 1 }] as const } as const;",
|
|
73
|
+
name: "nested as const",
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
// Valid: No type assertions at all
|
|
77
|
+
{
|
|
78
|
+
code: "const value: string = 'hello';",
|
|
79
|
+
name: "type annotation instead of assertion",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
code: "function getValue(): string { return 'test'; }",
|
|
83
|
+
name: "function with return type",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
code: "interface User { name: string; } const user: User = { name: 'John' };",
|
|
87
|
+
name: "interface with proper typing",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
code: "const element = document.getElementById('test');",
|
|
91
|
+
name: "no type assertion on DOM access",
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
|
|
95
|
+
invalid: [
|
|
96
|
+
// Invalid: Basic type assertions
|
|
97
|
+
{
|
|
98
|
+
code: "declare const value: unknown; const str = value as string;",
|
|
99
|
+
errors: [{ messageId: "noTypeAssertions" }],
|
|
100
|
+
name: "basic string assertion",
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
code: "declare const value: unknown; const num = value as number;",
|
|
104
|
+
errors: [{ messageId: "noTypeAssertions" }],
|
|
105
|
+
name: "basic number assertion",
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
code: "declare const value: unknown; const bool = value as boolean;",
|
|
109
|
+
errors: [{ messageId: "noTypeAssertions" }],
|
|
110
|
+
name: "basic boolean assertion",
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
// Invalid: Interface and type assertions
|
|
114
|
+
{
|
|
115
|
+
code: "interface User { name: string; } declare const value: unknown; const user = value as User;",
|
|
116
|
+
errors: [{ messageId: "noTypeAssertions" }],
|
|
117
|
+
name: "interface assertion",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
code: "type Status = 'loading' | 'success'; declare const value: unknown; const status = value as Status;",
|
|
121
|
+
errors: [{ messageId: "noTypeAssertions" }],
|
|
122
|
+
name: "type alias assertion",
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
// Invalid: DOM element assertions
|
|
126
|
+
{
|
|
127
|
+
code: "const element = document.getElementById('test') as HTMLInputElement;",
|
|
128
|
+
errors: [{ messageId: "noTypeAssertions" }],
|
|
129
|
+
name: "DOM element assertion",
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
code: "const button = document.querySelector('.btn') as HTMLButtonElement;",
|
|
133
|
+
errors: [{ messageId: "noTypeAssertions" }],
|
|
134
|
+
name: "DOM querySelector assertion",
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
// Invalid: Object and array assertions
|
|
138
|
+
{
|
|
139
|
+
code: "declare const data: unknown; const obj = data as { name: string };",
|
|
140
|
+
errors: [{ messageId: "noTypeAssertions" }],
|
|
141
|
+
name: "object literal type assertion",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
code: "declare const data: unknown; const arr = data as string[];",
|
|
145
|
+
errors: [{ messageId: "noTypeAssertions" }],
|
|
146
|
+
name: "array type assertion",
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
code: "declare const data: unknown; const tuple = data as [string, number];",
|
|
150
|
+
errors: [{ messageId: "noTypeAssertions" }],
|
|
151
|
+
name: "tuple type assertion",
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
// Invalid: Function parameter and return assertions
|
|
155
|
+
{
|
|
156
|
+
code: "function test(param: unknown) { return param as string; }",
|
|
157
|
+
errors: [{ messageId: "noTypeAssertions" }],
|
|
158
|
+
name: "return value assertion",
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
code: "const fn = (value: unknown) => value as number;",
|
|
162
|
+
errors: [{ messageId: "noTypeAssertions" }],
|
|
163
|
+
name: "arrow function assertion",
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
// Invalid: Complex expressions
|
|
167
|
+
{
|
|
168
|
+
code: "declare const api: any; const result = api.getData() as Promise<User>;",
|
|
169
|
+
errors: [{ messageId: "noTypeAssertions" }],
|
|
170
|
+
name: "method call assertion",
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
code: "declare const obj: any; const prop = obj.property as string;",
|
|
174
|
+
errors: [{ messageId: "noTypeAssertions" }],
|
|
175
|
+
name: "property access assertion",
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
code: "declare const arr: any[]; const first = arr[0] as string;",
|
|
179
|
+
errors: [{ messageId: "noTypeAssertions" }],
|
|
180
|
+
name: "array access assertion",
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
// Invalid: Generic type assertions
|
|
184
|
+
{
|
|
185
|
+
code: "declare const value: unknown; const generic = value as Array<string>;",
|
|
186
|
+
errors: [{ messageId: "noTypeAssertions" }],
|
|
187
|
+
name: "generic type assertion",
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
code: "declare const value: unknown; const promise = value as Promise<number>;",
|
|
191
|
+
errors: [{ messageId: "noTypeAssertions" }],
|
|
192
|
+
name: "Promise generic assertion",
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
// Invalid: Any type assertions
|
|
196
|
+
{
|
|
197
|
+
code: "declare const value: unknown; const anything = value as any;",
|
|
198
|
+
errors: [{ messageId: "noTypeAssertions" }],
|
|
199
|
+
name: "any type assertion",
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
// Invalid: Chained assertions
|
|
203
|
+
{
|
|
204
|
+
code: "declare const value: unknown; const result = (value as any).prop as string;",
|
|
205
|
+
errors: [
|
|
206
|
+
{ messageId: "noTypeAssertions" },
|
|
207
|
+
{ messageId: "noTypeAssertions" }
|
|
208
|
+
],
|
|
209
|
+
name: "chained assertions",
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
// Invalid: Template literal expressions
|
|
213
|
+
{
|
|
214
|
+
code: "declare const id: unknown; const message = `User ID: ${id as string}`;",
|
|
215
|
+
errors: [{ messageId: "noTypeAssertions" }],
|
|
216
|
+
name: "assertion in template literal",
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
// Invalid: Conditional expressions
|
|
220
|
+
{
|
|
221
|
+
code: "declare const value: unknown; const result = value ? value as string : 'default';",
|
|
222
|
+
errors: [{ messageId: "noTypeAssertions" }],
|
|
223
|
+
name: "assertion in conditional",
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
// Invalid: Object property values
|
|
227
|
+
{
|
|
228
|
+
code: "declare const name: unknown; const user = { name: name as string, age: 30 };",
|
|
229
|
+
errors: [{ messageId: "noTypeAssertions" }],
|
|
230
|
+
name: "assertion in object property",
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
// Invalid: Array elements
|
|
234
|
+
{
|
|
235
|
+
code: "declare const item: unknown; const items = [item as string, 'other'];",
|
|
236
|
+
errors: [{ messageId: "noTypeAssertions" }],
|
|
237
|
+
name: "assertion in array element",
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
console.log("✅ All no-type-assertions tests passed!");
|
|
243
|
+
console.log(` Selector: ${noTypeAssertionsConfig.selector}`);
|
|
244
|
+
console.log(` Message: ${noTypeAssertionsConfig.message}`);
|
|
245
|
+
|
|
246
|
+
export { noTypeAssertionsRule };
|
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
export const typescriptEslintRules = {
|
|
2
2
|
"@typescript-eslint/no-explicit-any": "error",
|
|
3
|
-
"@typescript-eslint/consistent-type-assertions":
|
|
4
|
-
"error",
|
|
5
|
-
{
|
|
6
|
-
assertionStyle: "as",
|
|
7
|
-
objectLiteralTypeAssertions: "allow-as-parameter",
|
|
8
|
-
},
|
|
9
|
-
],
|
|
3
|
+
"@typescript-eslint/consistent-type-assertions": "off",
|
|
10
4
|
};
|