eslint-config-agent 1.2.3 → 1.3.1
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 +18 -0
- package/index.js +19 -0
- package/package.json +2 -1
- package/rules/index.js +3 -0
- package/rules/no-default-class-export/examples/invalid/default-abstract-class.ts +10 -0
- package/rules/no-default-class-export/examples/invalid/default-class-declaration.ts +12 -0
- package/rules/no-default-class-export/examples/invalid/default-class-expression.ts +12 -0
- package/rules/no-default-class-export/examples/invalid/default-generic-class.ts +12 -0
- package/rules/no-default-class-export/examples/valid/abstract-class-export.ts +17 -0
- package/rules/no-default-class-export/examples/valid/generic-class-export.ts +16 -0
- package/rules/no-default-class-export/examples/valid/multiple-named-exports.ts +22 -0
- package/rules/no-default-class-export/examples/valid/named-class-export.ts +12 -0
- package/rules/no-default-class-export/index.js +120 -0
- package/rules/no-default-class-export/no-default-class-export.spec.js +301 -0
- package/rules/plugin/import/group-exports/index.js +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@ All notable changes to this project will be documented in this file. See [Conven
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
## [1.3.1](https://github.com/tupe12334/eslint-config/compare/v1.3.0...v1.3.1) (2025-09-20)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* update eslint-plugin-single-export version to 1.1.1 and adjust dependencies ([d2a82b5](https://github.com/tupe12334/eslint-config/commit/d2a82b51fc302ffb0589fb58a6a26452833d6639))
|
|
12
|
+
|
|
13
|
+
## [1.3.0](https://github.com/tupe12334/eslint-config/compare/v1.2.3...v1.3.0) (2025-09-20)
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* add eslint-plugin-single-export to enforce single export rules ([9cfb800](https://github.com/tupe12334/eslint-config/commit/9cfb80073cf9cdcefd31af5cc2c9a1c83dcd6926))
|
|
18
|
+
* add no-default-class-export rule to enforce named exports for classes ([03d5273](https://github.com/tupe12334/eslint-config/commit/03d52731ca190ebf3d76d4ded7c6ace7fe5fc75f))
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* disable group-exports rule to prevent enforcing single statement exports ([0cfd699](https://github.com/tupe12334/eslint-config/commit/0cfd6994a0978d8a15e7ecae83b0765e5cfcf747))
|
|
23
|
+
* improve null check for class names in no-default-class-export rule ([bf090e3](https://github.com/tupe12334/eslint-config/commit/bf090e3490f08f6e9b07a16227ab22aba702f31c))
|
|
24
|
+
|
|
7
25
|
## [1.2.3](https://github.com/tupe12334/eslint-config/compare/v1.2.2...v1.2.3) (2025-09-14)
|
|
8
26
|
|
|
9
27
|
### Features
|
package/index.js
CHANGED
|
@@ -7,9 +7,11 @@ import importPlugin from "eslint-plugin-import";
|
|
|
7
7
|
import securityPlugin from "eslint-plugin-security";
|
|
8
8
|
import nPlugin from "eslint-plugin-n";
|
|
9
9
|
import classExportPlugin from "eslint-plugin-class-export";
|
|
10
|
+
import singleExportPlugin from "eslint-plugin-single-export";
|
|
10
11
|
import storybookPlugin from "eslint-plugin-storybook";
|
|
11
12
|
import globals from "globals";
|
|
12
13
|
import allRules from "./rules/index.js";
|
|
14
|
+
import { noDefaultClassExportRule } from "./rules/no-default-class-export/index.js";
|
|
13
15
|
|
|
14
16
|
// Conditionally import preact plugin if available
|
|
15
17
|
let preactPlugin = null;
|
|
@@ -95,6 +97,7 @@ const sharedRestrictedSyntax = [
|
|
|
95
97
|
},
|
|
96
98
|
allRules.noProcessEnvPropertiesConfig,
|
|
97
99
|
allRules.noExportSpecifiersConfig,
|
|
100
|
+
...allRules.noDefaultClassExportRules,
|
|
98
101
|
];
|
|
99
102
|
|
|
100
103
|
// Required export rules (always errors)
|
|
@@ -198,6 +201,12 @@ const config = [
|
|
|
198
201
|
plugins: {
|
|
199
202
|
n: nPlugin,
|
|
200
203
|
"class-export": classExportPlugin,
|
|
204
|
+
"single-export": singleExportPlugin,
|
|
205
|
+
"custom": {
|
|
206
|
+
rules: {
|
|
207
|
+
"no-default-class-export": noDefaultClassExportRule,
|
|
208
|
+
},
|
|
209
|
+
},
|
|
201
210
|
},
|
|
202
211
|
},
|
|
203
212
|
reactHooks.configs["recommended-latest"],
|
|
@@ -245,6 +254,8 @@ const config = [
|
|
|
245
254
|
...sharedRules,
|
|
246
255
|
...allRules.typescriptEslintRules,
|
|
247
256
|
"no-undef": "off", // TypeScript handles this
|
|
257
|
+
"custom/no-default-class-export": "error",
|
|
258
|
+
"single-export/single-export": "error",
|
|
248
259
|
"no-restricted-syntax": [
|
|
249
260
|
"error",
|
|
250
261
|
...sharedRestrictedSyntax,
|
|
@@ -261,6 +272,7 @@ const config = [
|
|
|
261
272
|
rules: {
|
|
262
273
|
// Include all shared rules (like max-lines-per-function)
|
|
263
274
|
...sharedRules,
|
|
275
|
+
"single-export/single-export": "off", // TSX files have their own specific export rules
|
|
264
276
|
"no-restricted-syntax": [
|
|
265
277
|
"error",
|
|
266
278
|
// Switch case rules as errors
|
|
@@ -337,6 +349,7 @@ const config = [
|
|
|
337
349
|
files: ["**/*.tsx"],
|
|
338
350
|
ignores: ["**/*.stories.{js,jsx,ts,tsx}"],
|
|
339
351
|
rules: {
|
|
352
|
+
"single-export/single-export": "off", // TSX files have their own specific export rules
|
|
340
353
|
"no-restricted-syntax": [
|
|
341
354
|
"warn",
|
|
342
355
|
// Include shared rules but remove the multiple exports restriction and switch case rules for TSX
|
|
@@ -446,6 +459,7 @@ const config = [
|
|
|
446
459
|
security: securityPlugin,
|
|
447
460
|
n: nPlugin,
|
|
448
461
|
"class-export": classExportPlugin,
|
|
462
|
+
"single-export": singleExportPlugin,
|
|
449
463
|
},
|
|
450
464
|
settings: {
|
|
451
465
|
react: {
|
|
@@ -454,6 +468,7 @@ const config = [
|
|
|
454
468
|
},
|
|
455
469
|
rules: {
|
|
456
470
|
...sharedRules,
|
|
471
|
+
"single-export/single-export": "error",
|
|
457
472
|
"no-restricted-syntax": [
|
|
458
473
|
"error",
|
|
459
474
|
...sharedRestrictedSyntax,
|
|
@@ -513,6 +528,7 @@ const config = [
|
|
|
513
528
|
security: securityPlugin,
|
|
514
529
|
n: nPlugin,
|
|
515
530
|
"class-export": classExportPlugin,
|
|
531
|
+
"single-export": singleExportPlugin,
|
|
516
532
|
...(preactPlugin && { preact: preactPlugin }),
|
|
517
533
|
},
|
|
518
534
|
settings: {
|
|
@@ -523,6 +539,7 @@ const config = [
|
|
|
523
539
|
rules: {
|
|
524
540
|
...sharedRules,
|
|
525
541
|
"no-undef": "off", // TypeScript handles this
|
|
542
|
+
"single-export/single-export": "off", // JSX files have their own specific export rules
|
|
526
543
|
"no-restricted-syntax": [
|
|
527
544
|
"error",
|
|
528
545
|
// Switch case rules as errors
|
|
@@ -632,6 +649,7 @@ const config = [
|
|
|
632
649
|
security: securityPlugin,
|
|
633
650
|
n: nPlugin,
|
|
634
651
|
"class-export": classExportPlugin,
|
|
652
|
+
"single-export": singleExportPlugin,
|
|
635
653
|
...(preactPlugin && { preact: preactPlugin }),
|
|
636
654
|
},
|
|
637
655
|
settings: {
|
|
@@ -641,6 +659,7 @@ const config = [
|
|
|
641
659
|
},
|
|
642
660
|
rules: {
|
|
643
661
|
"no-undef": "off", // TypeScript handles this
|
|
662
|
+
"single-export/single-export": "off", // JSX files have their own specific export rules
|
|
644
663
|
"no-restricted-syntax": [
|
|
645
664
|
"warn",
|
|
646
665
|
// Include shared rules but remove the multiple exports restriction and switch case rules for JSX
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-config-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "ESLint configuration package with TypeScript support",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -100,6 +100,7 @@
|
|
|
100
100
|
"eslint-plugin-react": "^7.37.5",
|
|
101
101
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
102
102
|
"eslint-plugin-security": "^3.0.1",
|
|
103
|
+
"eslint-plugin-single-export": "^1.1.1",
|
|
103
104
|
"eslint-plugin-storybook": "^9.1.5",
|
|
104
105
|
"globals": "^16.3.0",
|
|
105
106
|
"release-it": "^19.0.4",
|
package/rules/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import { noProcessEnvPropertiesConfig } from "./no-process-env-properties/index.
|
|
|
15
15
|
import { noTypeAssertionsConfig } from "./no-type-assertions/index.js";
|
|
16
16
|
import { noExportSpecifiersConfig } from "./no-empty-exports/index.js";
|
|
17
17
|
import { noClassPropertyDefaultsConfig } from "./no-class-property-defaults/index.js";
|
|
18
|
+
import { noDefaultClassExportRules } from "./no-default-class-export/index.js";
|
|
18
19
|
|
|
19
20
|
// Plugin rule configurations
|
|
20
21
|
import { pluginRules } from "./plugin/index.js";
|
|
@@ -34,6 +35,7 @@ const allRules = {
|
|
|
34
35
|
noTypeAssertionsConfig,
|
|
35
36
|
noExportSpecifiersConfig,
|
|
36
37
|
noClassPropertyDefaultsConfig,
|
|
38
|
+
noDefaultClassExportRules,
|
|
37
39
|
|
|
38
40
|
// Plugin rule configurations
|
|
39
41
|
pluginRules,
|
|
@@ -56,6 +58,7 @@ export {
|
|
|
56
58
|
noTypeAssertionsConfig,
|
|
57
59
|
noExportSpecifiersConfig,
|
|
58
60
|
noClassPropertyDefaultsConfig,
|
|
61
|
+
noDefaultClassExportRules,
|
|
59
62
|
|
|
60
63
|
// Plugin rule configurations
|
|
61
64
|
pluginRules,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Valid: Named abstract class export
|
|
2
|
+
export abstract class BaseService {
|
|
3
|
+
protected initialized = false;
|
|
4
|
+
|
|
5
|
+
abstract initialize(): Promise<void>;
|
|
6
|
+
|
|
7
|
+
isInitialized(): boolean {
|
|
8
|
+
return this.initialized;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class ConcreteService extends BaseService {
|
|
13
|
+
async initialize(): Promise<void> {
|
|
14
|
+
// Implementation
|
|
15
|
+
this.initialized = true;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Valid: Named generic class export
|
|
2
|
+
export class Container<T> {
|
|
3
|
+
private items: T[] = [];
|
|
4
|
+
|
|
5
|
+
add(item: T): void {
|
|
6
|
+
this.items.push(item);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get(index: number): T | undefined {
|
|
10
|
+
return this.items[index];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
getAll(): T[] {
|
|
14
|
+
return [...this.items];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Valid: Multiple named exports including classes
|
|
2
|
+
export interface UserData {
|
|
3
|
+
id: number;
|
|
4
|
+
name: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class UserRepository {
|
|
8
|
+
private data: UserData[] = [];
|
|
9
|
+
|
|
10
|
+
save(user: UserData): void {
|
|
11
|
+
this.data.push(user);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
findById(id: number): UserData | undefined {
|
|
15
|
+
return this.data.find(user => user.id === id);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const DEFAULT_USER: UserData = {
|
|
20
|
+
id: 0,
|
|
21
|
+
name: 'Anonymous'
|
|
22
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule configuration for preventing default class exports
|
|
3
|
+
*
|
|
4
|
+
* This rule enforces named exports for classes instead of default exports
|
|
5
|
+
* to improve code organization, import consistency, and tree-shaking.
|
|
6
|
+
*
|
|
7
|
+
* Default class exports can make it harder to:
|
|
8
|
+
* - Track class usage across the codebase
|
|
9
|
+
* - Perform refactoring and renaming operations
|
|
10
|
+
* - Enable proper tree-shaking optimizations
|
|
11
|
+
* - Maintain consistent import naming conventions
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Custom ESLint rule that prevents default class exports and provides auto-fix
|
|
16
|
+
*/
|
|
17
|
+
export const noDefaultClassExportRule = {
|
|
18
|
+
meta: {
|
|
19
|
+
type: "problem",
|
|
20
|
+
docs: {
|
|
21
|
+
description: "Disallow default class exports in favor of named exports",
|
|
22
|
+
category: "Best Practices",
|
|
23
|
+
},
|
|
24
|
+
fixable: "code",
|
|
25
|
+
messages: {
|
|
26
|
+
noDefaultClassDeclaration: "Default class exports are not allowed. Use named exports instead: 'export class {{className}} {}'.",
|
|
27
|
+
noDefaultClassExpression: "Default class expressions are not allowed. Use named class declarations instead: 'export class ClassName {}'.",
|
|
28
|
+
},
|
|
29
|
+
schema: [],
|
|
30
|
+
},
|
|
31
|
+
create(context) {
|
|
32
|
+
const sourceCode = context.getSourceCode();
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
ExportDefaultDeclaration(node) {
|
|
36
|
+
// Check if the default export is a class declaration
|
|
37
|
+
if (node.declaration && node.declaration.type === "ClassDeclaration") {
|
|
38
|
+
const classDeclaration = node.declaration;
|
|
39
|
+
const className = (classDeclaration.id && classDeclaration.id.name) || "ClassName";
|
|
40
|
+
|
|
41
|
+
context.report({
|
|
42
|
+
node: node,
|
|
43
|
+
messageId: "noDefaultClassDeclaration",
|
|
44
|
+
data: {
|
|
45
|
+
className: className,
|
|
46
|
+
},
|
|
47
|
+
fix(fixer) {
|
|
48
|
+
// Handle anonymous classes
|
|
49
|
+
if (!classDeclaration.id) {
|
|
50
|
+
// Get class body and other parts
|
|
51
|
+
const body = sourceCode.getText(classDeclaration.body);
|
|
52
|
+
const superClass = classDeclaration.superClass
|
|
53
|
+
? ` extends ${sourceCode.getText(classDeclaration.superClass)}`
|
|
54
|
+
: "";
|
|
55
|
+
|
|
56
|
+
return fixer.replaceText(node, `export class ${className}${superClass} ${body}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// For named classes, just replace default with named export
|
|
60
|
+
const classText = sourceCode.getText(classDeclaration);
|
|
61
|
+
return fixer.replaceText(node, `export ${classText}`);
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
// Check if the default export is a class expression
|
|
66
|
+
else if (node.declaration && node.declaration.type === "ClassExpression") {
|
|
67
|
+
const classExpression = node.declaration;
|
|
68
|
+
const className = (classExpression.id && classExpression.id.name) || "ClassName";
|
|
69
|
+
|
|
70
|
+
context.report({
|
|
71
|
+
node: node,
|
|
72
|
+
messageId: "noDefaultClassExpression",
|
|
73
|
+
fix(fixer) {
|
|
74
|
+
// Build the fixed class declaration
|
|
75
|
+
const body = sourceCode.getText(classExpression.body);
|
|
76
|
+
const superClass = classExpression.superClass
|
|
77
|
+
? ` extends ${sourceCode.getText(classExpression.superClass)}`
|
|
78
|
+
: "";
|
|
79
|
+
|
|
80
|
+
const newCode = `export class ${className}${superClass} ${body}`;
|
|
81
|
+
return fixer.replaceText(node, newCode);
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Backward compatibility: no-restricted-syntax configurations
|
|
92
|
+
* (for use in existing rule arrays that don't support custom rules)
|
|
93
|
+
*/
|
|
94
|
+
export const noDefaultClassExportConfig = {
|
|
95
|
+
selector: "ExportDefaultDeclaration > ClassDeclaration",
|
|
96
|
+
message: "Default class exports are not allowed. Use named exports instead: 'export class ClassName {}'."
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const noDefaultClassExpressionConfig = {
|
|
100
|
+
selector: "ExportDefaultDeclaration > ClassExpression",
|
|
101
|
+
message: "Default class expressions are not allowed. Use named class declarations instead: 'export class ClassName {}'."
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Combined rule configurations for comprehensive default class export prevention
|
|
106
|
+
*/
|
|
107
|
+
export const noDefaultClassExportRules = [
|
|
108
|
+
noDefaultClassExportConfig,
|
|
109
|
+
noDefaultClassExpressionConfig
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
// Default export for backward compatibility
|
|
113
|
+
export default noDefaultClassExportConfig;
|
|
114
|
+
|
|
115
|
+
// Named exports for specific use cases
|
|
116
|
+
export {
|
|
117
|
+
noDefaultClassExportConfig as noDefaultClassExport,
|
|
118
|
+
noDefaultClassExpressionConfig as noDefaultClassExpression,
|
|
119
|
+
noDefaultClassExportRules as allNoDefaultClassExportRules,
|
|
120
|
+
};
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { RuleTester } from "eslint";
|
|
2
|
+
import { noDefaultClassExportRule } from "./index.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Test suite for no-default-class-export rule
|
|
6
|
+
*
|
|
7
|
+
* This tests the custom ESLint rule that prevents default class exports
|
|
8
|
+
* and provides auto-fix functionality to convert them to named exports.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const ruleTester = new RuleTester({
|
|
12
|
+
languageOptions: {
|
|
13
|
+
ecmaVersion: 2022,
|
|
14
|
+
sourceType: "module",
|
|
15
|
+
parser: (await import("@typescript-eslint/parser")).default,
|
|
16
|
+
parserOptions: {
|
|
17
|
+
ecmaFeatures: {
|
|
18
|
+
jsx: true,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
ruleTester.run("no-default-class-export", noDefaultClassExportRule, {
|
|
25
|
+
valid: [
|
|
26
|
+
// Valid: Named class export
|
|
27
|
+
{
|
|
28
|
+
code: `
|
|
29
|
+
export class UserService {
|
|
30
|
+
constructor() {}
|
|
31
|
+
}
|
|
32
|
+
`,
|
|
33
|
+
},
|
|
34
|
+
// Valid: Multiple named exports including class
|
|
35
|
+
{
|
|
36
|
+
code: `
|
|
37
|
+
export interface UserData {
|
|
38
|
+
id: number;
|
|
39
|
+
name: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class UserRepository {
|
|
43
|
+
save(user: UserData): void {}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const DEFAULT_USER: UserData = {
|
|
47
|
+
id: 0,
|
|
48
|
+
name: 'Anonymous'
|
|
49
|
+
};
|
|
50
|
+
`,
|
|
51
|
+
},
|
|
52
|
+
// Valid: Abstract class with named export
|
|
53
|
+
{
|
|
54
|
+
code: `
|
|
55
|
+
export abstract class BaseService {
|
|
56
|
+
abstract initialize(): void;
|
|
57
|
+
}
|
|
58
|
+
`,
|
|
59
|
+
},
|
|
60
|
+
// Valid: Generic class with named export
|
|
61
|
+
{
|
|
62
|
+
code: `
|
|
63
|
+
export class Container<T> {
|
|
64
|
+
private items: T[] = [];
|
|
65
|
+
add(item: T): void {
|
|
66
|
+
this.items.push(item);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
`,
|
|
70
|
+
},
|
|
71
|
+
// Valid: Class with decorators (named export)
|
|
72
|
+
{
|
|
73
|
+
code: `
|
|
74
|
+
@Injectable()
|
|
75
|
+
export class ApiService {
|
|
76
|
+
constructor() {}
|
|
77
|
+
}
|
|
78
|
+
`,
|
|
79
|
+
},
|
|
80
|
+
// Valid: Default export of non-class
|
|
81
|
+
{
|
|
82
|
+
code: `
|
|
83
|
+
export default function createUser() {
|
|
84
|
+
return { id: 1, name: 'Test' };
|
|
85
|
+
}
|
|
86
|
+
`,
|
|
87
|
+
},
|
|
88
|
+
// Valid: Default export of constant
|
|
89
|
+
{
|
|
90
|
+
code: `
|
|
91
|
+
const config = { apiUrl: 'http://localhost' };
|
|
92
|
+
export default config;
|
|
93
|
+
`,
|
|
94
|
+
},
|
|
95
|
+
// Valid: Default export of object
|
|
96
|
+
{
|
|
97
|
+
code: `
|
|
98
|
+
export default {
|
|
99
|
+
users: [],
|
|
100
|
+
addUser(name: string) {
|
|
101
|
+
this.users.push(name);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
`,
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
|
|
108
|
+
invalid: [
|
|
109
|
+
// Invalid: Default class declaration export
|
|
110
|
+
{
|
|
111
|
+
code: `
|
|
112
|
+
export default class UserService {
|
|
113
|
+
constructor() {}
|
|
114
|
+
}
|
|
115
|
+
`,
|
|
116
|
+
output: `
|
|
117
|
+
export class UserService {
|
|
118
|
+
constructor() {}
|
|
119
|
+
}
|
|
120
|
+
`,
|
|
121
|
+
errors: [
|
|
122
|
+
{
|
|
123
|
+
messageId: "noDefaultClassDeclaration",
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
// Invalid: Default anonymous class (treated as declaration)
|
|
128
|
+
{
|
|
129
|
+
code: `
|
|
130
|
+
export default class {
|
|
131
|
+
getValue() {
|
|
132
|
+
return 42;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
`,
|
|
136
|
+
output: `
|
|
137
|
+
export class ClassName {
|
|
138
|
+
getValue() {
|
|
139
|
+
return 42;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
`,
|
|
143
|
+
errors: [
|
|
144
|
+
{
|
|
145
|
+
messageId: "noDefaultClassDeclaration",
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
// Invalid: Default abstract class
|
|
150
|
+
{
|
|
151
|
+
code: `
|
|
152
|
+
export default abstract class BaseHandler {
|
|
153
|
+
abstract handle(): void;
|
|
154
|
+
}
|
|
155
|
+
`,
|
|
156
|
+
output: `
|
|
157
|
+
export abstract class BaseHandler {
|
|
158
|
+
abstract handle(): void;
|
|
159
|
+
}
|
|
160
|
+
`,
|
|
161
|
+
errors: [
|
|
162
|
+
{
|
|
163
|
+
messageId: "noDefaultClassDeclaration",
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
// Invalid: Default generic class
|
|
168
|
+
{
|
|
169
|
+
code: `
|
|
170
|
+
export default class Repository<T> {
|
|
171
|
+
private items: T[] = [];
|
|
172
|
+
save(item: T): void {
|
|
173
|
+
this.items.push(item);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
`,
|
|
177
|
+
output: `
|
|
178
|
+
export class Repository<T> {
|
|
179
|
+
private items: T[] = [];
|
|
180
|
+
save(item: T): void {
|
|
181
|
+
this.items.push(item);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
`,
|
|
185
|
+
errors: [
|
|
186
|
+
{
|
|
187
|
+
messageId: "noDefaultClassDeclaration",
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
// Invalid: Default class with decorators
|
|
192
|
+
{
|
|
193
|
+
code: `
|
|
194
|
+
@Injectable()
|
|
195
|
+
export default class ApiService {
|
|
196
|
+
constructor() {}
|
|
197
|
+
}
|
|
198
|
+
`,
|
|
199
|
+
output: `
|
|
200
|
+
@Injectable()
|
|
201
|
+
export class ApiService {
|
|
202
|
+
constructor() {}
|
|
203
|
+
}
|
|
204
|
+
`,
|
|
205
|
+
errors: [
|
|
206
|
+
{
|
|
207
|
+
messageId: "noDefaultClassDeclaration",
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
},
|
|
211
|
+
// Invalid: Default class extending another class
|
|
212
|
+
{
|
|
213
|
+
code: `
|
|
214
|
+
export default class ConcreteService extends BaseService {
|
|
215
|
+
initialize(): void {}
|
|
216
|
+
}
|
|
217
|
+
`,
|
|
218
|
+
output: `
|
|
219
|
+
export class ConcreteService extends BaseService {
|
|
220
|
+
initialize(): void {}
|
|
221
|
+
}
|
|
222
|
+
`,
|
|
223
|
+
errors: [
|
|
224
|
+
{
|
|
225
|
+
messageId: "noDefaultClassDeclaration",
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
},
|
|
229
|
+
// Invalid: Default class implementing interface
|
|
230
|
+
{
|
|
231
|
+
code: `
|
|
232
|
+
export default class UserService implements IUserService {
|
|
233
|
+
getUsers(): User[] {
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
`,
|
|
238
|
+
output: `
|
|
239
|
+
export class UserService implements IUserService {
|
|
240
|
+
getUsers(): User[] {
|
|
241
|
+
return [];
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
`,
|
|
245
|
+
errors: [
|
|
246
|
+
{
|
|
247
|
+
messageId: "noDefaultClassDeclaration",
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
},
|
|
251
|
+
// Invalid: Default class with static methods
|
|
252
|
+
{
|
|
253
|
+
code: `
|
|
254
|
+
export default class Utils {
|
|
255
|
+
static format(text: string): string {
|
|
256
|
+
return text.trim();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
`,
|
|
260
|
+
output: `
|
|
261
|
+
export class Utils {
|
|
262
|
+
static format(text: string): string {
|
|
263
|
+
return text.trim();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
`,
|
|
267
|
+
errors: [
|
|
268
|
+
{
|
|
269
|
+
messageId: "noDefaultClassDeclaration",
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
},
|
|
273
|
+
// Invalid: Named class expression (edge case)
|
|
274
|
+
{
|
|
275
|
+
code: `
|
|
276
|
+
export default class NamedClass {
|
|
277
|
+
test() {
|
|
278
|
+
return 'test';
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
`,
|
|
282
|
+
output: `
|
|
283
|
+
export class NamedClass {
|
|
284
|
+
test() {
|
|
285
|
+
return 'test';
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
`,
|
|
289
|
+
errors: [
|
|
290
|
+
{
|
|
291
|
+
messageId: "noDefaultClassDeclaration",
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
console.log("✅ All no-default-class-export rule tests passed!");
|
|
299
|
+
|
|
300
|
+
// Export for potential use in other test files
|
|
301
|
+
export { noDefaultClassExportRule };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export const groupExportsConfig = {
|
|
2
|
-
"import/group-exports": "
|
|
3
|
-
};
|
|
2
|
+
"import/group-exports": "off",
|
|
3
|
+
};
|