eslint-plugin-awscdk 4.2.1 → 4.3.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 +0 -27
- package/dist/index.cjs +3621 -3243
- package/dist/index.d.cts +89 -135
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +89 -136
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3614 -3238
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -23
- package/src/configs/classic-config.ts +7 -6
- package/src/configs/flat-config.ts +3 -2
- package/src/configs/index.ts +6 -1
- package/src/core/cdk-construct/type-checker/is-resource-with-readonly-interface.ts +0 -1
- package/src/rules/index.ts +8 -1
- package/src/rules/no-import-private.ts +1 -2
- package/src/rules/no-unused-props/index.ts +0 -1
- package/src/rules/no-unused-props/props-usage-analyzer.ts +5 -1
- package/src/rules/no-unused-props/visitor/direct-props-usage-visitor.ts +0 -1
- package/src/rules/no-unused-props/visitor/instance-variable-usage-visitor.ts +0 -1
- package/src/rules/no-unused-props/visitor/method-call-collector-visitor.ts +4 -1
- package/src/rules/no-unused-props/visitor/props-alias-visitor.ts +0 -1
- package/src/rules/no-unused-props/visitor/traverse-nodes.ts +0 -1
- package/src/rules/prevent-construct-id-collision.ts +144 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-awscdk",
|
|
3
|
-
"version": "4.2
|
|
3
|
+
"version": "4.3.2",
|
|
4
4
|
"description": "eslint plugin for AWS CDK projects",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"aws",
|
|
@@ -44,39 +44,28 @@
|
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"test
|
|
50
|
-
"
|
|
51
|
-
"fmt": "oxfmt",
|
|
52
|
-
"fmt:check": "oxfmt --check",
|
|
53
|
-
"check": "tsgo --noEmit",
|
|
54
|
-
"pack": "pnpm run build && npm pack",
|
|
55
|
-
"docs": "pnpm run --filter=@eslint-plugin-awscdk/docs",
|
|
56
|
-
"example:flat-config": "pnpm run --filter=@eslint-plugin-awscdk/example-flat-config",
|
|
57
|
-
"example:classic-config": "pnpm run --filter=@eslint-plugin-awscdk/example-classic-config",
|
|
58
|
-
"example:oxlint": "pnpm run --filter=@eslint-plugin-awscdk/example-oxlint"
|
|
47
|
+
"pack": "vp pack",
|
|
48
|
+
"check": "vp check",
|
|
49
|
+
"test": "vp test --run",
|
|
50
|
+
"docs": "vp run -F @eslint-plugin-awscdk/docs"
|
|
59
51
|
},
|
|
60
52
|
"devDependencies": {
|
|
61
53
|
"@eslint/js": "^10.0.1",
|
|
62
54
|
"@secretlint/secretlint-rule-preset-recommend": "^11.2.5",
|
|
63
55
|
"@types/node": "^24.10.13",
|
|
64
|
-
"@typescript-eslint/parser": "^8.
|
|
65
|
-
"@typescript-eslint/rule-tester": "^8.
|
|
66
|
-
"@typescript-eslint/utils": "^8.
|
|
67
|
-
"@
|
|
56
|
+
"@typescript-eslint/parser": "^8.58.0",
|
|
57
|
+
"@typescript-eslint/rule-tester": "^8.58.0",
|
|
58
|
+
"@typescript-eslint/utils": "^8.58.0",
|
|
59
|
+
"@voidzero-dev/vite-plus-core": "^0.1.11",
|
|
68
60
|
"eslint": "^10.0.0",
|
|
69
|
-
"eslint-plugin-import": "^2.32.0",
|
|
70
|
-
"oxfmt": "^0.32.0",
|
|
71
61
|
"secretlint": "^11.2.5",
|
|
72
|
-
"tsdown": "^0.20.3",
|
|
73
62
|
"typescript": "^5.9.3",
|
|
74
|
-
"typescript-eslint": "^8.
|
|
75
|
-
"
|
|
63
|
+
"typescript-eslint": "^8.58.0",
|
|
64
|
+
"vite-plus": "^0.1.11"
|
|
76
65
|
},
|
|
77
66
|
"peerDependencies": {
|
|
78
67
|
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
|
79
|
-
"typescript": ">=4.8.4 <6.
|
|
68
|
+
"typescript": ">=4.8.4 <6.1.0"
|
|
80
69
|
},
|
|
81
70
|
"engines": {
|
|
82
71
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { ClassicConfig } from "@typescript-eslint/utils/ts-eslint";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
rules: ClassicConfig.RulesRecord,
|
|
5
|
-
): {
|
|
3
|
+
type ClassicRulesConfig = {
|
|
6
4
|
plugins: ["awscdk"];
|
|
7
5
|
rules: ClassicConfig.RulesRecord;
|
|
8
|
-
}
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const createClassicConfig = (rules: ClassicConfig.RulesRecord): ClassicRulesConfig => {
|
|
9
9
|
return {
|
|
10
10
|
plugins: ["awscdk"],
|
|
11
11
|
rules,
|
|
12
12
|
};
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
export const recommended = createClassicConfig({
|
|
15
|
+
export const recommended: ClassicRulesConfig = createClassicConfig({
|
|
16
16
|
"awscdk/construct-constructor-property": "error",
|
|
17
17
|
"awscdk/migrate-disable-comments": "error",
|
|
18
18
|
"awscdk/no-construct-in-interface": "error",
|
|
@@ -28,7 +28,7 @@ export const recommended = createClassicConfig({
|
|
|
28
28
|
"awscdk/require-passing-this": ["error", { allowNonThisAndDisallowScope: true }],
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
export const strict = createClassicConfig({
|
|
31
|
+
export const strict: ClassicRulesConfig = createClassicConfig({
|
|
32
32
|
"awscdk/construct-constructor-property": "error",
|
|
33
33
|
"awscdk/migrate-disable-comments": "error",
|
|
34
34
|
"awscdk/no-construct-in-interface": "error",
|
|
@@ -42,6 +42,7 @@ export const strict = createClassicConfig({
|
|
|
42
42
|
"awscdk/no-variable-construct-id": "error",
|
|
43
43
|
"awscdk/pascal-case-construct-id": "error",
|
|
44
44
|
"awscdk/prefer-grants-property": "error",
|
|
45
|
+
"awscdk/prevent-construct-id-collision": "error",
|
|
45
46
|
"awscdk/props-name-convention": "error",
|
|
46
47
|
"awscdk/require-jsdoc": "error",
|
|
47
48
|
"awscdk/require-passing-this": "error",
|
|
@@ -30,7 +30,7 @@ const createFlatConfig = (rules: _FlatConfig.Rules): FlatConfig => {
|
|
|
30
30
|
};
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
export const recommended = createFlatConfig({
|
|
33
|
+
export const recommended: FlatConfig = createFlatConfig({
|
|
34
34
|
"awscdk/construct-constructor-property": "error",
|
|
35
35
|
"awscdk/migrate-disable-comments": "error",
|
|
36
36
|
"awscdk/no-construct-in-interface": "error",
|
|
@@ -46,7 +46,7 @@ export const recommended = createFlatConfig({
|
|
|
46
46
|
"awscdk/require-passing-this": ["error", { allowNonThisAndDisallowScope: true }],
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
-
export const strict = createFlatConfig({
|
|
49
|
+
export const strict: FlatConfig = createFlatConfig({
|
|
50
50
|
"awscdk/construct-constructor-property": "error",
|
|
51
51
|
"awscdk/migrate-disable-comments": "error",
|
|
52
52
|
"awscdk/no-construct-in-interface": "error",
|
|
@@ -60,6 +60,7 @@ export const strict = createFlatConfig({
|
|
|
60
60
|
"awscdk/no-variable-construct-id": "error",
|
|
61
61
|
"awscdk/pascal-case-construct-id": "error",
|
|
62
62
|
"awscdk/prefer-grants-property": "error",
|
|
63
|
+
"awscdk/prevent-construct-id-collision": "error",
|
|
63
64
|
"awscdk/props-name-convention": "error",
|
|
64
65
|
"awscdk/require-jsdoc": "error",
|
|
65
66
|
"awscdk/require-passing-this": "error",
|
package/src/configs/index.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { recommended as classicRecommended, strict as classicStrict } from "./classic-config";
|
|
2
2
|
import { recommended, strict } from "./flat-config";
|
|
3
3
|
|
|
4
|
-
export const configs
|
|
4
|
+
export const configs: Readonly<{
|
|
5
|
+
recommended: typeof recommended;
|
|
6
|
+
strict: typeof strict;
|
|
7
|
+
classicRecommended: typeof classicRecommended;
|
|
8
|
+
classicStrict: typeof classicStrict;
|
|
9
|
+
}> = {
|
|
5
10
|
recommended,
|
|
6
11
|
strict,
|
|
7
12
|
classicRecommended,
|
package/src/rules/index.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { TSESLint } from "@typescript-eslint/utils";
|
|
2
|
+
import type { Rule } from "eslint";
|
|
3
|
+
|
|
1
4
|
import { constructConstructorProperty } from "./construct-constructor-property";
|
|
2
5
|
import { migrateDisableComments } from "./migrate-disable-comments";
|
|
3
6
|
import { noConstructInInterface } from "./no-construct-in-interface";
|
|
@@ -11,12 +14,15 @@ import { noUnusedProps } from "./no-unused-props";
|
|
|
11
14
|
import { noVariableConstructId } from "./no-variable-construct-id";
|
|
12
15
|
import { pascalCaseConstructId } from "./pascal-case-construct-id";
|
|
13
16
|
import { preferGrantsProperty } from "./prefer-grants-property";
|
|
17
|
+
import { preventConstructIdCollision } from "./prevent-construct-id-collision";
|
|
14
18
|
import { propsNameConvention } from "./props-name-convention";
|
|
15
19
|
import { requireJSDoc } from "./require-jsdoc";
|
|
16
20
|
import { requirePassingThis } from "./require-passing-this";
|
|
17
21
|
import { requirePropsDefaultDoc } from "./require-props-default-doc";
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
type RuleModule = TSESLint.RuleModule<string, readonly unknown[]> | Rule.RuleModule;
|
|
24
|
+
|
|
25
|
+
export const rules: Record<string, RuleModule> = {
|
|
20
26
|
"construct-constructor-property": constructConstructorProperty,
|
|
21
27
|
"migrate-disable-comments": migrateDisableComments,
|
|
22
28
|
"no-construct-in-interface": noConstructInInterface,
|
|
@@ -30,6 +36,7 @@ export const rules = {
|
|
|
30
36
|
"no-variable-construct-id": noVariableConstructId,
|
|
31
37
|
"pascal-case-construct-id": pascalCaseConstructId,
|
|
32
38
|
"prefer-grants-property": preferGrantsProperty,
|
|
39
|
+
"prevent-construct-id-collision": preventConstructIdCollision,
|
|
33
40
|
"props-name-convention": propsNameConvention,
|
|
34
41
|
"require-jsdoc": requireJSDoc,
|
|
35
42
|
"require-passing-this": requirePassingThis,
|
|
@@ -10,7 +10,6 @@ import { Type } from "typescript";
|
|
|
10
10
|
import { findConstructor } from "../../core/ast-node/finder/constructor";
|
|
11
11
|
import { isConstructType } from "../../core/cdk-construct/type-checker/is-construct";
|
|
12
12
|
import { createRule } from "../../shared/create-rule";
|
|
13
|
-
|
|
14
13
|
import { PropsUsageAnalyzer } from "./props-usage-analyzer";
|
|
15
14
|
import { IPropsUsageTracker, PropsUsageTracker } from "./props-usage-tracker";
|
|
16
15
|
|
|
@@ -14,7 +14,11 @@ export interface IPropsUsageAnalyzer {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export class PropsUsageAnalyzer implements IPropsUsageAnalyzer {
|
|
17
|
-
|
|
17
|
+
private readonly tracker: IPropsUsageTracker;
|
|
18
|
+
|
|
19
|
+
constructor(tracker: IPropsUsageTracker) {
|
|
20
|
+
this.tracker = tracker;
|
|
21
|
+
}
|
|
18
22
|
|
|
19
23
|
analyze(constructor: TSESTree.MethodDefinition, propsParam: TSESTree.Identifier): void {
|
|
20
24
|
const constructorBody = constructor.value.body;
|
|
@@ -26,8 +26,11 @@ type MethodCallInfo = {
|
|
|
26
26
|
*/
|
|
27
27
|
export class MethodCallCollectorVisitor implements INodeVisitor {
|
|
28
28
|
private readonly _result: MethodCallInfo[] = [];
|
|
29
|
+
private readonly propsParamName: string;
|
|
29
30
|
|
|
30
|
-
constructor(
|
|
31
|
+
constructor(propsParamName: string) {
|
|
32
|
+
this.propsParamName = propsParamName;
|
|
33
|
+
}
|
|
31
34
|
|
|
32
35
|
visitCallExpression(node: TSESTree.CallExpression): void {
|
|
33
36
|
// NOTE: Check for this.methodName(...) pattern
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { AST_NODE_TYPES, TSESTree } from "@typescript-eslint/utils";
|
|
2
2
|
|
|
3
3
|
import { findChildNodes } from "../../../core/ast-node/finder/child-nodes";
|
|
4
|
-
|
|
5
4
|
import { INodeVisitor } from "./interface/node-visitor";
|
|
6
5
|
|
|
7
6
|
export const traverseNodes = (node: TSESTree.Node, visitor: INodeVisitor): void => {
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { AST_NODE_TYPES, ESLintUtils, TSESTree } from "@typescript-eslint/utils";
|
|
2
|
+
|
|
3
|
+
import { isConstructType } from "../core/cdk-construct/type-checker/is-construct";
|
|
4
|
+
import { findConstructorPropertyNames } from "../core/ts-type/finder/constructor-property-name";
|
|
5
|
+
import { createRule } from "../shared/create-rule";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Prevent Construct ID collisions inside loops.
|
|
9
|
+
* Reports when a literal ID is used for a Construct instantiated inside a loop.
|
|
10
|
+
*/
|
|
11
|
+
export const preventConstructIdCollision = createRule({
|
|
12
|
+
name: "prevent-construct-id-collision",
|
|
13
|
+
meta: {
|
|
14
|
+
type: "problem",
|
|
15
|
+
docs: {
|
|
16
|
+
description:
|
|
17
|
+
"Disallow using literal Construct IDs inside loops, which may cause ID collisions.",
|
|
18
|
+
},
|
|
19
|
+
messages: {
|
|
20
|
+
preventConstructIdCollision:
|
|
21
|
+
"Construct ID '{{ constructId }}' is a literal value inside a loop. This may cause ID collisions. Use a variable that changes per iteration instead.",
|
|
22
|
+
},
|
|
23
|
+
schema: [],
|
|
24
|
+
},
|
|
25
|
+
defaultOptions: [],
|
|
26
|
+
create(context) {
|
|
27
|
+
const parserServices = ESLintUtils.getParserServices(context);
|
|
28
|
+
return {
|
|
29
|
+
NewExpression(node) {
|
|
30
|
+
const type = parserServices.getTypeAtLocation(node);
|
|
31
|
+
|
|
32
|
+
if (!isConstructType(type) || node.arguments.length < 2) return;
|
|
33
|
+
|
|
34
|
+
const constructorPropertyNames = findConstructorPropertyNames(type);
|
|
35
|
+
if (constructorPropertyNames[1] !== "id") return;
|
|
36
|
+
|
|
37
|
+
validateConstructIdInLoop(node, context);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Validate whether a Construct ID is a literal inside a loop
|
|
45
|
+
*/
|
|
46
|
+
const validateConstructIdInLoop = (
|
|
47
|
+
node: TSESTree.NewExpression,
|
|
48
|
+
context: Parameters<typeof preventConstructIdCollision.create>[0],
|
|
49
|
+
) => {
|
|
50
|
+
if (!isInsideLoop(node)) return;
|
|
51
|
+
|
|
52
|
+
const secondArg = node.arguments[1];
|
|
53
|
+
|
|
54
|
+
// NOTE: String literals may cause ID collisions
|
|
55
|
+
if (secondArg.type === AST_NODE_TYPES.Literal && typeof secondArg.value === "string") {
|
|
56
|
+
context.report({
|
|
57
|
+
node: secondArg,
|
|
58
|
+
messageId: "preventConstructIdCollision",
|
|
59
|
+
data: { constructId: secondArg.value },
|
|
60
|
+
});
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// NOTE: Template literals without expressions are also static values
|
|
65
|
+
if (secondArg.type === AST_NODE_TYPES.TemplateLiteral && !secondArg.expressions.length) {
|
|
66
|
+
const constructId = secondArg.quasis.map((q) => q.value.raw).join("");
|
|
67
|
+
context.report({
|
|
68
|
+
node: secondArg,
|
|
69
|
+
messageId: "preventConstructIdCollision",
|
|
70
|
+
data: { constructId },
|
|
71
|
+
});
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check whether a node is inside a loop.
|
|
78
|
+
* Detects for, for...in, for...of, while, do...while statements,
|
|
79
|
+
* and callbacks of iteration methods (forEach, map, etc.)
|
|
80
|
+
*/
|
|
81
|
+
const isInsideLoop = (node: TSESTree.Node): boolean => {
|
|
82
|
+
let current = node.parent;
|
|
83
|
+
while (current) {
|
|
84
|
+
// NOTE: Detect loop statements
|
|
85
|
+
if (
|
|
86
|
+
current.type === AST_NODE_TYPES.ForStatement ||
|
|
87
|
+
current.type === AST_NODE_TYPES.ForInStatement ||
|
|
88
|
+
current.type === AST_NODE_TYPES.ForOfStatement ||
|
|
89
|
+
current.type === AST_NODE_TYPES.WhileStatement ||
|
|
90
|
+
current.type === AST_NODE_TYPES.DoWhileStatement
|
|
91
|
+
) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// NOTE: Detect iteration method callbacks (ArrowFunction/FunctionExpression)
|
|
96
|
+
if (
|
|
97
|
+
(current.type === AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
98
|
+
current.type === AST_NODE_TYPES.FunctionExpression) &&
|
|
99
|
+
isIterationMethodCallback(current)
|
|
100
|
+
) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// NOTE: Stop at non-constructor method definitions
|
|
105
|
+
if (current.type === AST_NODE_TYPES.MethodDefinition && current.kind !== "constructor") {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
current = current.parent;
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const ITERATION_METHODS = new Set([
|
|
115
|
+
"forEach",
|
|
116
|
+
"map",
|
|
117
|
+
"flatMap",
|
|
118
|
+
"filter",
|
|
119
|
+
"reduce",
|
|
120
|
+
"reduceRight",
|
|
121
|
+
"every",
|
|
122
|
+
"some",
|
|
123
|
+
"find",
|
|
124
|
+
"findIndex",
|
|
125
|
+
"findLast",
|
|
126
|
+
"findLastIndex",
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Check whether an arrow function or function expression is a callback of an iteration method
|
|
131
|
+
*/
|
|
132
|
+
const isIterationMethodCallback = (
|
|
133
|
+
node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression,
|
|
134
|
+
): boolean => {
|
|
135
|
+
const parent = node.parent;
|
|
136
|
+
if (parent?.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
137
|
+
|
|
138
|
+
const callee = parent.callee;
|
|
139
|
+
if (callee.type !== AST_NODE_TYPES.MemberExpression) return false;
|
|
140
|
+
|
|
141
|
+
if (callee.property.type !== AST_NODE_TYPES.Identifier) return false;
|
|
142
|
+
|
|
143
|
+
return ITERATION_METHODS.has(callee.property.name);
|
|
144
|
+
};
|