eslint-cdk-plugin 2.2.0 → 3.0.2
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/README.md +1 -1
- package/dist/index.cjs +149 -169
- package/dist/index.d.ts +39 -39
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +149 -169
- package/package.json +12 -11
- package/src/index.ts +29 -28
- package/src/rules/construct-constructor-property.ts +4 -4
- package/src/rules/{no-class-in-interface.ts → no-construct-in-interface.ts} +8 -7
- package/src/rules/no-construct-in-public-property-of-construct.ts +155 -0
- package/src/rules/no-construct-stack-suffix.ts +6 -6
- package/src/rules/no-import-private.ts +2 -2
- package/src/rules/no-mutable-property-of-props-interface.ts +60 -0
- package/src/rules/no-mutable-public-property-of-construct.ts +76 -0
- package/src/rules/no-parent-name-construct-id-match.ts +6 -28
- package/src/rules/no-variable-construct-id.ts +4 -4
- package/src/rules/pascal-case-construct-id.ts +4 -4
- package/src/rules/require-passing-this.ts +6 -6
- package/src/rules/no-mutable-props-interface.ts +0 -58
- package/src/rules/no-mutable-public-fields.ts +0 -75
- package/src/rules/no-public-class-fields.ts +0 -154
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-cdk-plugin",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "eslint plugin for AWS CDK projects",
|
|
5
5
|
"main": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -30,23 +30,24 @@
|
|
|
30
30
|
"docs:preview": "cd ./docs && pnpm install && pnpm run preview"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@eslint/js": "^9.
|
|
34
|
-
"@types/node": "^22.
|
|
35
|
-
"@typescript-eslint/rule-tester": "^8.
|
|
33
|
+
"@eslint/js": "^9.26.0",
|
|
34
|
+
"@types/node": "^22.15.0",
|
|
35
|
+
"@typescript-eslint/rule-tester": "^8.32.1",
|
|
36
36
|
"eslint": "9.22.0",
|
|
37
37
|
"eslint-plugin-import": "^2.31.0",
|
|
38
|
-
"pkgroll": "^2.
|
|
38
|
+
"pkgroll": "^2.12.2",
|
|
39
39
|
"standard-version": "^9.5.0",
|
|
40
|
-
"typescript": "^5.8.
|
|
41
|
-
"typescript-eslint": "^8.
|
|
42
|
-
"
|
|
40
|
+
"typescript": "^5.8.3",
|
|
41
|
+
"typescript-eslint": "^8.32.1",
|
|
42
|
+
"vitepress-plugin-llms": "^1.1.4",
|
|
43
|
+
"vitest": "^3.1.3"
|
|
43
44
|
},
|
|
44
45
|
"dependencies": {
|
|
45
|
-
"@typescript-eslint/parser": "^8.
|
|
46
|
-
"@typescript-eslint/utils": "^8.
|
|
46
|
+
"@typescript-eslint/parser": "^8.32.1",
|
|
47
|
+
"@typescript-eslint/utils": "^8.32.1"
|
|
47
48
|
},
|
|
48
49
|
"volta": {
|
|
49
|
-
"node": "22.
|
|
50
|
+
"node": "22.15.0"
|
|
50
51
|
},
|
|
51
52
|
"files": [
|
|
52
53
|
"dist",
|
package/src/index.ts
CHANGED
|
@@ -3,13 +3,13 @@ import tsParser from "@typescript-eslint/parser";
|
|
|
3
3
|
import { name, version } from "../package.json";
|
|
4
4
|
|
|
5
5
|
import { constructConstructorProperty } from "./rules/construct-constructor-property";
|
|
6
|
-
import {
|
|
6
|
+
import { noConstructInInterface } from "./rules/no-construct-in-interface";
|
|
7
|
+
import { noConstructInPublicPropertyOfConstruct } from "./rules/no-construct-in-public-property-of-construct";
|
|
7
8
|
import { noConstructStackSuffix } from "./rules/no-construct-stack-suffix";
|
|
8
9
|
import { noImportPrivate } from "./rules/no-import-private";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
10
|
+
import { noMutablePropertyOfPropsInterface } from "./rules/no-mutable-property-of-props-interface";
|
|
11
|
+
import { noMutablePublicPropertyOfConstruct } from "./rules/no-mutable-public-property-of-construct";
|
|
11
12
|
import { noParentNameConstructIdMatch } from "./rules/no-parent-name-construct-id-match";
|
|
12
|
-
import { noPublicClassFields } from "./rules/no-public-class-fields";
|
|
13
13
|
import { noVariableConstructId } from "./rules/no-variable-construct-id";
|
|
14
14
|
import { pascalCaseConstructId } from "./rules/pascal-case-construct-id";
|
|
15
15
|
import { propsNameConvention } from "./rules/props-name-convention";
|
|
@@ -18,20 +18,21 @@ import { requirePassingThis } from "./rules/require-passing-this";
|
|
|
18
18
|
import { requirePropsDefaultDoc } from "./rules/require-props-default-doc";
|
|
19
19
|
|
|
20
20
|
const rules = {
|
|
21
|
-
"
|
|
21
|
+
"construct-constructor-property": constructConstructorProperty,
|
|
22
|
+
"no-construct-in-interface": noConstructInInterface,
|
|
23
|
+
"no-construct-in-public-property-of-construct":
|
|
24
|
+
noConstructInPublicPropertyOfConstruct,
|
|
22
25
|
"no-construct-stack-suffix": noConstructStackSuffix,
|
|
26
|
+
"no-import-private": noImportPrivate,
|
|
27
|
+
"no-mutable-property-of-props-interface": noMutablePropertyOfPropsInterface,
|
|
28
|
+
"no-mutable-public-property-of-construct": noMutablePublicPropertyOfConstruct,
|
|
23
29
|
"no-parent-name-construct-id-match": noParentNameConstructIdMatch,
|
|
24
|
-
"no-public-class-fields": noPublicClassFields,
|
|
25
|
-
"pascal-case-construct-id": pascalCaseConstructId,
|
|
26
|
-
"require-passing-this": requirePassingThis,
|
|
27
30
|
"no-variable-construct-id": noVariableConstructId,
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"construct-constructor-property": constructConstructorProperty,
|
|
31
|
+
"pascal-case-construct-id": pascalCaseConstructId,
|
|
32
|
+
"props-name-convention": propsNameConvention,
|
|
31
33
|
"require-jsdoc": requireJSDoc,
|
|
34
|
+
"require-passing-this": requirePassingThis,
|
|
32
35
|
"require-props-default-doc": requirePropsDefaultDoc,
|
|
33
|
-
"props-name-convention": propsNameConvention,
|
|
34
|
-
"no-import-private": noImportPrivate,
|
|
35
36
|
};
|
|
36
37
|
|
|
37
38
|
const cdkPlugin = {
|
|
@@ -55,39 +56,39 @@ const createFlatConfig = (rules: Record<string, unknown>) => {
|
|
|
55
56
|
};
|
|
56
57
|
|
|
57
58
|
const recommended = createFlatConfig({
|
|
58
|
-
"cdk/
|
|
59
|
+
"cdk/construct-constructor-property": "error",
|
|
60
|
+
"cdk/no-construct-in-interface": "error",
|
|
61
|
+
"cdk/no-construct-in-public-property-of-construct": "error",
|
|
59
62
|
"cdk/no-construct-stack-suffix": "error",
|
|
63
|
+
"cdk/no-mutable-property-of-props-interface": "warn",
|
|
64
|
+
"cdk/no-mutable-public-property-of-construct": "warn",
|
|
60
65
|
"cdk/no-parent-name-construct-id-match": [
|
|
61
66
|
"error",
|
|
62
67
|
{ disallowContainingParentName: false },
|
|
63
68
|
],
|
|
64
|
-
"cdk/no-
|
|
69
|
+
"cdk/no-variable-construct-id": "error",
|
|
65
70
|
"cdk/pascal-case-construct-id": "error",
|
|
66
71
|
"cdk/require-passing-this": ["error", { allowNonThisAndDisallowScope: true }],
|
|
67
|
-
"cdk/no-variable-construct-id": "error",
|
|
68
|
-
"cdk/no-mutable-public-fields": "warn",
|
|
69
|
-
"cdk/no-mutable-props-interface": "warn",
|
|
70
|
-
"cdk/construct-constructor-property": "error",
|
|
71
72
|
});
|
|
72
73
|
|
|
73
74
|
const strict = createFlatConfig({
|
|
74
|
-
"cdk/
|
|
75
|
+
"cdk/construct-constructor-property": "error",
|
|
76
|
+
"cdk/no-construct-in-interface": "error",
|
|
77
|
+
"cdk/no-construct-in-public-property-of-construct": "error",
|
|
75
78
|
"cdk/no-construct-stack-suffix": "error",
|
|
79
|
+
"cdk/no-import-private": "error",
|
|
80
|
+
"cdk/no-mutable-property-of-props-interface": "error",
|
|
81
|
+
"cdk/no-mutable-public-property-of-construct": "error",
|
|
76
82
|
"cdk/no-parent-name-construct-id-match": [
|
|
77
83
|
"error",
|
|
78
84
|
{ disallowContainingParentName: true },
|
|
79
85
|
],
|
|
80
|
-
"cdk/no-public-class-fields": "error",
|
|
81
|
-
"cdk/pascal-case-construct-id": "error",
|
|
82
|
-
"cdk/require-passing-this": "error",
|
|
83
86
|
"cdk/no-variable-construct-id": "error",
|
|
84
|
-
"cdk/
|
|
85
|
-
"cdk/
|
|
86
|
-
"cdk/construct-constructor-property": "error",
|
|
87
|
+
"cdk/pascal-case-construct-id": "error",
|
|
88
|
+
"cdk/props-name-convention": "error",
|
|
87
89
|
"cdk/require-jsdoc": "error",
|
|
90
|
+
"cdk/require-passing-this": "error",
|
|
88
91
|
"cdk/require-props-default-doc": "error",
|
|
89
|
-
"cdk/props-name-convention": "error",
|
|
90
|
-
"cdk/no-import-private": "error",
|
|
91
92
|
});
|
|
92
93
|
|
|
93
94
|
const configs = {
|
|
@@ -67,7 +67,7 @@ const validateConstructorProperty = (
|
|
|
67
67
|
// NOTE: Check if the constructor has at least 2 parameters
|
|
68
68
|
if (params.length < 2) {
|
|
69
69
|
context.report({
|
|
70
|
-
node: constructor,
|
|
70
|
+
node: constructor.value,
|
|
71
71
|
messageId: "invalidConstructorProperty",
|
|
72
72
|
});
|
|
73
73
|
return;
|
|
@@ -80,7 +80,7 @@ const validateConstructorProperty = (
|
|
|
80
80
|
firstParam.name !== "scope"
|
|
81
81
|
) {
|
|
82
82
|
context.report({
|
|
83
|
-
node:
|
|
83
|
+
node: firstParam,
|
|
84
84
|
messageId: "invalidConstructorProperty",
|
|
85
85
|
});
|
|
86
86
|
return;
|
|
@@ -93,7 +93,7 @@ const validateConstructorProperty = (
|
|
|
93
93
|
secondParam.name !== "id"
|
|
94
94
|
) {
|
|
95
95
|
context.report({
|
|
96
|
-
node:
|
|
96
|
+
node: secondParam,
|
|
97
97
|
messageId: "invalidConstructorProperty",
|
|
98
98
|
});
|
|
99
99
|
return;
|
|
@@ -109,7 +109,7 @@ const validateConstructorProperty = (
|
|
|
109
109
|
thirdParam.name !== "props"
|
|
110
110
|
) {
|
|
111
111
|
context.report({
|
|
112
|
-
node:
|
|
112
|
+
node: thirdParam,
|
|
113
113
|
messageId: "invalidConstructorProperty",
|
|
114
114
|
});
|
|
115
115
|
return;
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils";
|
|
2
2
|
|
|
3
3
|
import { SYMBOL_FLAGS } from "../constants/tsInternalFlags";
|
|
4
|
+
import { isConstructOrStackType } from "../utils/typeCheck";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
* Enforces the use of interface types instead of
|
|
7
|
+
* Enforces the use of interface types instead of CDK Construct types in interface properties
|
|
7
8
|
* @param context - The rule context provided by ESLint
|
|
8
9
|
* @returns An object containing the AST visitor functions
|
|
9
10
|
* @see {@link https://eslint-cdk-plugin.dev/rules/no-class-in-interface} - Documentation
|
|
10
11
|
*/
|
|
11
|
-
export const
|
|
12
|
+
export const noConstructInInterface = ESLintUtils.RuleCreator.withoutDocs({
|
|
12
13
|
meta: {
|
|
13
14
|
type: "problem",
|
|
14
15
|
docs: {
|
|
15
|
-
description: "Disallow
|
|
16
|
+
description: "Disallow CDK Construct types in interface properties",
|
|
16
17
|
},
|
|
17
18
|
messages: {
|
|
18
|
-
|
|
19
|
-
"Interface property '{{ propertyName }}' should not use
|
|
19
|
+
invalidInterfaceProperty:
|
|
20
|
+
"Interface property '{{ propertyName }}' should not use CDK Construct type '{{ typeName }}'. Consider using an interface or type alias instead.",
|
|
20
21
|
},
|
|
21
22
|
schema: [],
|
|
22
23
|
},
|
|
@@ -35,7 +36,7 @@ export const noClassInInterface = ESLintUtils.RuleCreator.withoutDocs({
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
const type = parserServices.getTypeAtLocation(property);
|
|
38
|
-
if (!type
|
|
39
|
+
if (!isConstructOrStackType(type)) continue;
|
|
39
40
|
|
|
40
41
|
// NOTE: In order not to make it dependent on the typescript library, it defines its own unions.
|
|
41
42
|
// Therefore, the type information structures do not match.
|
|
@@ -45,7 +46,7 @@ export const noClassInInterface = ESLintUtils.RuleCreator.withoutDocs({
|
|
|
45
46
|
|
|
46
47
|
context.report({
|
|
47
48
|
node: property,
|
|
48
|
-
messageId: "
|
|
49
|
+
messageId: "invalidInterfaceProperty",
|
|
49
50
|
data: {
|
|
50
51
|
propertyName: property.key.name,
|
|
51
52
|
typeName: type.symbol.name,
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AST_NODE_TYPES,
|
|
3
|
+
ESLintUtils,
|
|
4
|
+
ParserServicesWithTypeInformation,
|
|
5
|
+
TSESLint,
|
|
6
|
+
TSESTree,
|
|
7
|
+
} from "@typescript-eslint/utils";
|
|
8
|
+
|
|
9
|
+
import { SYMBOL_FLAGS } from "../constants/tsInternalFlags";
|
|
10
|
+
import { isConstructOrStackType } from "../utils/typeCheck";
|
|
11
|
+
|
|
12
|
+
type Context = TSESLint.RuleContext<"invalidPublicPropertyOfConstruct", []>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Disallow Construct types in public property of Construct
|
|
16
|
+
* @param context - The rule context provided by ESLint
|
|
17
|
+
* @returns An object containing the AST visitor functions
|
|
18
|
+
* @see {@link https://eslint-cdk-plugin.dev/rules/no-construct-in-public-property-of-construct} - Documentation
|
|
19
|
+
*/
|
|
20
|
+
export const noConstructInPublicPropertyOfConstruct =
|
|
21
|
+
ESLintUtils.RuleCreator.withoutDocs({
|
|
22
|
+
meta: {
|
|
23
|
+
type: "problem",
|
|
24
|
+
docs: {
|
|
25
|
+
description: "Disallow Construct types in public property of Construct",
|
|
26
|
+
},
|
|
27
|
+
messages: {
|
|
28
|
+
invalidPublicPropertyOfConstruct:
|
|
29
|
+
"Public property '{{ propertyName }}' of Construct should not use Construct type '{{ typeName }}'. Consider using an interface or type alias instead.",
|
|
30
|
+
},
|
|
31
|
+
schema: [],
|
|
32
|
+
},
|
|
33
|
+
defaultOptions: [],
|
|
34
|
+
create(context) {
|
|
35
|
+
const parserServices = ESLintUtils.getParserServices(context);
|
|
36
|
+
return {
|
|
37
|
+
ClassDeclaration(node) {
|
|
38
|
+
const type = parserServices.getTypeAtLocation(node);
|
|
39
|
+
if (!isConstructOrStackType(type)) return;
|
|
40
|
+
|
|
41
|
+
// NOTE: Check class members
|
|
42
|
+
validatePublicPropertyOfConstruct(node, context, parserServices);
|
|
43
|
+
|
|
44
|
+
// NOTE: Check constructor parameter properties
|
|
45
|
+
const constructor = node.body.body.find(
|
|
46
|
+
(member): member is TSESTree.MethodDefinition =>
|
|
47
|
+
member.type === AST_NODE_TYPES.MethodDefinition &&
|
|
48
|
+
member.kind === "constructor"
|
|
49
|
+
);
|
|
50
|
+
if (
|
|
51
|
+
!constructor ||
|
|
52
|
+
constructor.value.type !== AST_NODE_TYPES.FunctionExpression
|
|
53
|
+
) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
validateConstructorParameterProperty(
|
|
58
|
+
constructor,
|
|
59
|
+
context,
|
|
60
|
+
parserServices
|
|
61
|
+
);
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* check the public property of Construct
|
|
69
|
+
* - if it is a Construct type, report an error
|
|
70
|
+
*/
|
|
71
|
+
const validatePublicPropertyOfConstruct = (
|
|
72
|
+
node: TSESTree.ClassDeclaration,
|
|
73
|
+
context: Context,
|
|
74
|
+
parserServices: ParserServicesWithTypeInformation
|
|
75
|
+
) => {
|
|
76
|
+
for (const property of node.body.body) {
|
|
77
|
+
if (
|
|
78
|
+
property.type !== AST_NODE_TYPES.PropertyDefinition ||
|
|
79
|
+
property.key.type !== AST_NODE_TYPES.Identifier
|
|
80
|
+
) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// NOTE: Skip private and protected fields
|
|
85
|
+
if (["private", "protected"].includes(property.accessibility ?? "")) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// NOTE: Skip fields without type annotation
|
|
90
|
+
if (!property.typeAnnotation) continue;
|
|
91
|
+
|
|
92
|
+
const type = parserServices.getTypeAtLocation(property);
|
|
93
|
+
if (!isConstructOrStackType(type)) continue;
|
|
94
|
+
|
|
95
|
+
// NOTE: In order not to make it dependent on the typescript library, it defines its own unions.
|
|
96
|
+
// Therefore, the type information structures do not match.
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
|
|
98
|
+
const isClass = type.symbol.flags === SYMBOL_FLAGS.CLASS;
|
|
99
|
+
if (!isClass) continue;
|
|
100
|
+
|
|
101
|
+
context.report({
|
|
102
|
+
node: property,
|
|
103
|
+
messageId: "invalidPublicPropertyOfConstruct",
|
|
104
|
+
data: {
|
|
105
|
+
propertyName: property.key.name,
|
|
106
|
+
typeName: type.symbol.name,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* check the constructor parameter property
|
|
114
|
+
* - if it is a Construct type, report an error
|
|
115
|
+
*/
|
|
116
|
+
const validateConstructorParameterProperty = (
|
|
117
|
+
constructor: TSESTree.MethodDefinition,
|
|
118
|
+
context: Context,
|
|
119
|
+
parserServices: ParserServicesWithTypeInformation
|
|
120
|
+
) => {
|
|
121
|
+
for (const param of constructor.value.params) {
|
|
122
|
+
if (
|
|
123
|
+
param.type !== AST_NODE_TYPES.TSParameterProperty ||
|
|
124
|
+
param.parameter.type !== AST_NODE_TYPES.Identifier
|
|
125
|
+
) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// NOTE: Skip private and protected parameters
|
|
130
|
+
if (["private", "protected"].includes(param.accessibility ?? "")) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// NOTE: Skip parameters without type annotation
|
|
135
|
+
if (!param.parameter.typeAnnotation) continue;
|
|
136
|
+
|
|
137
|
+
const type = parserServices.getTypeAtLocation(param);
|
|
138
|
+
if (!isConstructOrStackType(type)) continue;
|
|
139
|
+
|
|
140
|
+
// NOTE: In order not to make it dependent on the typescript library, it defines its own unions.
|
|
141
|
+
// Therefore, the type information structures do not match.
|
|
142
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
|
|
143
|
+
const isClass = type.symbol.flags === SYMBOL_FLAGS.CLASS;
|
|
144
|
+
if (!isClass) continue;
|
|
145
|
+
|
|
146
|
+
context.report({
|
|
147
|
+
node: param,
|
|
148
|
+
messageId: "invalidPublicPropertyOfConstruct",
|
|
149
|
+
data: {
|
|
150
|
+
propertyName: param.parameter.name,
|
|
151
|
+
typeName: type.symbol.name,
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
};
|
|
@@ -22,7 +22,7 @@ type Options = [
|
|
|
22
22
|
}
|
|
23
23
|
];
|
|
24
24
|
|
|
25
|
-
type Context = TSESLint.RuleContext<"
|
|
25
|
+
type Context = TSESLint.RuleContext<"invalidConstructId", Options>;
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Enforces that Construct IDs do not end with 'Construct' or 'Stack' suffix
|
|
@@ -38,7 +38,7 @@ export const noConstructStackSuffix = ESLintUtils.RuleCreator.withoutDocs({
|
|
|
38
38
|
"Effort to avoid using 'Construct' and 'Stack' suffix in construct id.",
|
|
39
39
|
},
|
|
40
40
|
messages: {
|
|
41
|
-
|
|
41
|
+
invalidConstructId:
|
|
42
42
|
"{{ classType }} ID '{{ id }}' should not include {{ suffix }} suffix.",
|
|
43
43
|
},
|
|
44
44
|
schema: [
|
|
@@ -110,8 +110,8 @@ const validateConstructId = (
|
|
|
110
110
|
formattedConstructId.endsWith(SUFFIX_TYPE.CONSTRUCT)
|
|
111
111
|
) {
|
|
112
112
|
context.report({
|
|
113
|
-
node,
|
|
114
|
-
messageId: "
|
|
113
|
+
node: secondArg,
|
|
114
|
+
messageId: "invalidConstructId",
|
|
115
115
|
data: {
|
|
116
116
|
classType: "Construct",
|
|
117
117
|
id: secondArg.value,
|
|
@@ -123,8 +123,8 @@ const validateConstructId = (
|
|
|
123
123
|
formattedConstructId.endsWith(SUFFIX_TYPE.STACK)
|
|
124
124
|
) {
|
|
125
125
|
context.report({
|
|
126
|
-
node,
|
|
127
|
-
messageId: "
|
|
126
|
+
node: secondArg,
|
|
127
|
+
messageId: "invalidConstructId",
|
|
128
128
|
data: {
|
|
129
129
|
classType: "Stack",
|
|
130
130
|
id: secondArg.value,
|
|
@@ -16,7 +16,7 @@ export const noImportPrivate: Rule.RuleModule = {
|
|
|
16
16
|
"Cannot import modules from private dir at different levels of the hierarchy.",
|
|
17
17
|
},
|
|
18
18
|
messages: {
|
|
19
|
-
|
|
19
|
+
invalidImportPath:
|
|
20
20
|
"Cannot import modules from private dir at different levels of the hierarchy.",
|
|
21
21
|
},
|
|
22
22
|
schema: [],
|
|
@@ -43,7 +43,7 @@ export const noImportPrivate: Rule.RuleModule = {
|
|
|
43
43
|
(segment, index) => segment !== importDirSegments[index]
|
|
44
44
|
)
|
|
45
45
|
) {
|
|
46
|
-
context.report({ node, messageId: "
|
|
46
|
+
context.report({ node, messageId: "invalidImportPath" });
|
|
47
47
|
}
|
|
48
48
|
},
|
|
49
49
|
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Disallow mutable properties of Construct Props (interface)
|
|
5
|
+
* @param context - The rule context provided by ESLint
|
|
6
|
+
* @returns An object containing the AST visitor functions
|
|
7
|
+
* @see {@link https://eslint-cdk-plugin.dev/rules/no-mutable-property-of-props-interface} - Documentation
|
|
8
|
+
*/
|
|
9
|
+
export const noMutablePropertyOfPropsInterface =
|
|
10
|
+
ESLintUtils.RuleCreator.withoutDocs({
|
|
11
|
+
meta: {
|
|
12
|
+
type: "problem",
|
|
13
|
+
docs: {
|
|
14
|
+
description:
|
|
15
|
+
"Disallow mutable properties of Construct Props (interface)",
|
|
16
|
+
},
|
|
17
|
+
fixable: "code",
|
|
18
|
+
messages: {
|
|
19
|
+
invalidPropertyOfPropsInterface:
|
|
20
|
+
"Property '{{ propertyName }}' of Construct Props should be readonly.",
|
|
21
|
+
},
|
|
22
|
+
schema: [],
|
|
23
|
+
},
|
|
24
|
+
defaultOptions: [],
|
|
25
|
+
create(context) {
|
|
26
|
+
return {
|
|
27
|
+
TSInterfaceDeclaration(node) {
|
|
28
|
+
const sourceCode = context.sourceCode;
|
|
29
|
+
|
|
30
|
+
// NOTE: Interface name check for "Props"
|
|
31
|
+
if (!node.id.name.endsWith("Props")) return;
|
|
32
|
+
|
|
33
|
+
for (const property of node.body.body) {
|
|
34
|
+
// NOTE: check property signature
|
|
35
|
+
if (
|
|
36
|
+
property.type !== AST_NODE_TYPES.TSPropertySignature ||
|
|
37
|
+
property.key.type !== AST_NODE_TYPES.Identifier
|
|
38
|
+
) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// NOTE: Skip if already readonly
|
|
43
|
+
if (property.readonly) continue;
|
|
44
|
+
|
|
45
|
+
context.report({
|
|
46
|
+
node: property,
|
|
47
|
+
messageId: "invalidPropertyOfPropsInterface",
|
|
48
|
+
data: {
|
|
49
|
+
propertyName: property.key.name,
|
|
50
|
+
},
|
|
51
|
+
fix: (fixer) => {
|
|
52
|
+
const propertyText = sourceCode.getText(property);
|
|
53
|
+
return fixer.replaceText(property, `readonly ${propertyText}`);
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
|
|
3
|
+
import { isConstructOrStackType } from "../utils/typeCheck";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Disallow mutable public properties of Construct
|
|
7
|
+
* @param context - The rule context provided by ESLint
|
|
8
|
+
* @returns An object containing the AST visitor functions
|
|
9
|
+
* @see {@link https://eslint-cdk-plugin.dev/rules/no-mutable-public-property-of-construct} - Documentation
|
|
10
|
+
*/
|
|
11
|
+
export const noMutablePublicPropertyOfConstruct =
|
|
12
|
+
ESLintUtils.RuleCreator.withoutDocs({
|
|
13
|
+
meta: {
|
|
14
|
+
type: "problem",
|
|
15
|
+
docs: {
|
|
16
|
+
description: "Disallow mutable public properties of Construct",
|
|
17
|
+
},
|
|
18
|
+
fixable: "code",
|
|
19
|
+
messages: {
|
|
20
|
+
invalidPublicPropertyOfConstruct:
|
|
21
|
+
"Public property '{{ propertyName }}' should be readonly. Consider adding the 'readonly' modifier.",
|
|
22
|
+
},
|
|
23
|
+
schema: [],
|
|
24
|
+
},
|
|
25
|
+
defaultOptions: [],
|
|
26
|
+
create(context) {
|
|
27
|
+
const parserServices = ESLintUtils.getParserServices(context);
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
ClassDeclaration(node) {
|
|
31
|
+
const sourceCode = context.sourceCode;
|
|
32
|
+
const type = parserServices.getTypeAtLocation(node);
|
|
33
|
+
if (!isConstructOrStackType(type)) return;
|
|
34
|
+
|
|
35
|
+
for (const member of node.body.body) {
|
|
36
|
+
// NOTE: check property definition
|
|
37
|
+
if (
|
|
38
|
+
member.type !== AST_NODE_TYPES.PropertyDefinition ||
|
|
39
|
+
member.key.type !== AST_NODE_TYPES.Identifier
|
|
40
|
+
) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// NOTE: Skip private and protected fields
|
|
45
|
+
if (["private", "protected"].includes(member.accessibility ?? "")) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// NOTE: Skip if readonly is present
|
|
50
|
+
if (member.readonly) continue;
|
|
51
|
+
|
|
52
|
+
context.report({
|
|
53
|
+
node: member,
|
|
54
|
+
messageId: "invalidPublicPropertyOfConstruct",
|
|
55
|
+
data: {
|
|
56
|
+
propertyName: member.key.name,
|
|
57
|
+
},
|
|
58
|
+
fix: (fixer) => {
|
|
59
|
+
const accessibility = member.accessibility ? "public " : "";
|
|
60
|
+
const paramText = sourceCode.getText(member);
|
|
61
|
+
const [key, value] = paramText.split(":");
|
|
62
|
+
const replacedKey = key.startsWith("public ")
|
|
63
|
+
? key.replace("public ", "")
|
|
64
|
+
: key;
|
|
65
|
+
|
|
66
|
+
return fixer.replaceText(
|
|
67
|
+
member,
|
|
68
|
+
`${accessibility}readonly ${replacedKey}:${value}`
|
|
69
|
+
);
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
});
|