eslint-cdk-plugin 3.4.4 → 3.4.5
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/dist/index.cjs +29324 -1576
- package/dist/index.d.cts +72 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +72 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +29324 -1552
- package/dist/index.mjs.map +1 -0
- package/package.json +19 -22
- package/src/configs/flat-config.ts +69 -0
- package/src/configs/index.ts +6 -0
- package/src/{utils/get-child-nodes.ts → core/ast-node/finder/child-nodes.ts} +2 -2
- package/src/{utils/getConstructor.ts → core/ast-node/finder/constructor.ts} +1 -1
- package/src/core/ast-node/finder/property-name.ts +20 -0
- package/src/core/ast-node/finder/public-property.ts +76 -0
- package/src/core/cdk-construct/type-checker/is-construct-or-stack.ts +21 -0
- package/src/core/cdk-construct/type-checker/is-construct.ts +22 -0
- package/src/{utils → core/cdk-construct/type-checker}/is-resource-with-readonly-interface.ts +5 -4
- package/src/core/cdk-construct/type-checker/is-resource.ts +17 -0
- package/src/core/cdk-construct/type-finder/index.ts +73 -0
- package/src/{utils/typecheck/ts-type.ts → core/ts-type/checker/is-array.ts} +2 -8
- package/src/core/ts-type/checker/is-class.ts +7 -0
- package/src/core/ts-type/checker/is-extends-target-super-class.ts +24 -0
- package/src/core/ts-type/checker/private/get-symbol.ts +5 -0
- package/src/core/ts-type/finder/array-element.ts +20 -0
- package/src/core/ts-type/finder/constructor-property-name.ts +21 -0
- package/src/{utils/getGenericTypeArgument.ts → core/ts-type/finder/generics-type-argument.ts} +5 -5
- package/src/index.ts +6 -108
- package/src/rules/construct-constructor-property.ts +48 -26
- package/src/rules/index.ts +34 -0
- package/src/rules/no-construct-in-interface.ts +5 -51
- package/src/rules/no-construct-in-public-property-of-construct.ts +20 -143
- package/src/rules/no-construct-stack-suffix.ts +5 -5
- package/src/rules/no-mutable-property-of-props-interface.ts +1 -1
- package/src/rules/no-mutable-public-property-of-construct.ts +47 -39
- package/src/rules/no-parent-name-construct-id-match.ts +4 -6
- package/src/rules/no-unused-props/index.ts +19 -10
- package/src/rules/no-unused-props/props-usage-analyzer.ts +207 -190
- package/src/rules/no-unused-props/props-usage-tracker.ts +4 -3
- package/src/rules/no-unused-props/visitor/direct-props-usage-visitor.ts +145 -0
- package/src/rules/no-unused-props/visitor/index.ts +5 -0
- package/src/rules/no-unused-props/visitor/instance-variable-usage-visitor.ts +117 -0
- package/src/rules/no-unused-props/visitor/interface/node-visitor.ts +9 -0
- package/src/rules/no-unused-props/visitor/method-call-collector-visitor.ts +60 -0
- package/src/rules/no-unused-props/visitor/props-alias-visitor.ts +81 -0
- package/src/rules/no-unused-props/visitor/traverse-nodes.ts +38 -0
- package/src/rules/no-variable-construct-id.ts +4 -4
- package/src/rules/pascal-case-construct-id.ts +5 -5
- package/src/rules/props-name-convention.ts +4 -4
- package/src/rules/require-jsdoc.ts +2 -2
- package/src/rules/require-passing-this.ts +4 -4
- package/src/rules/require-props-default-doc.ts +1 -1
- package/bin/migration.mjs +0 -100
- package/dist/index.d.ts +0 -47
- package/dist/index.d.ts.map +0 -1
- package/src/utils/getArrayElementType.ts +0 -14
- package/src/utils/getPropertyNames.ts +0 -41
- package/src/utils/typecheck/cdk.ts +0 -71
- package/src/utils/typecheck/ts-node.ts +0 -7
- /package/src/{utils/convertString.ts → shared/converter/to-pascal-case.ts} +0 -0
- /package/src/{utils/createRule.ts → shared/create-rule.ts} +0 -0
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ESLintUtils,
|
|
3
|
+
TSESLint
|
|
4
|
+
} from "@typescript-eslint/utils";
|
|
2
5
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
6
|
+
import {
|
|
7
|
+
findPublicPropertiesInClass,
|
|
8
|
+
PublicProperty,
|
|
9
|
+
} from "../core/ast-node/finder/public-property";
|
|
10
|
+
import { isConstructOrStackType } from "../core/cdk-construct/type-checker/is-construct-or-stack";
|
|
11
|
+
import { createRule } from "../shared/create-rule";
|
|
12
|
+
|
|
13
|
+
type Context = TSESLint.RuleContext<"invalidPublicPropertyOfConstruct", []>;
|
|
5
14
|
|
|
6
15
|
/**
|
|
7
16
|
* Disallow mutable public properties of Construct
|
|
@@ -32,45 +41,44 @@ export const noMutablePublicPropertyOfConstruct = createRule({
|
|
|
32
41
|
const type = parserServices.getTypeAtLocation(node);
|
|
33
42
|
if (!isConstructOrStackType(type)) return;
|
|
34
43
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
},
|
|
44
|
+
const publicProperties = findPublicPropertiesInClass(node);
|
|
45
|
+
for (const property of publicProperties) {
|
|
46
|
+
validatePublicProperty({
|
|
47
|
+
publicProperty: property,
|
|
48
|
+
context,
|
|
49
|
+
sourceCode,
|
|
71
50
|
});
|
|
72
51
|
}
|
|
73
52
|
},
|
|
74
53
|
};
|
|
75
54
|
},
|
|
76
55
|
});
|
|
56
|
+
|
|
57
|
+
const validatePublicProperty = (args: {
|
|
58
|
+
publicProperty: PublicProperty;
|
|
59
|
+
context: Context;
|
|
60
|
+
sourceCode: Readonly<TSESLint.SourceCode>;
|
|
61
|
+
}) => {
|
|
62
|
+
const { publicProperty, context, sourceCode } = args;
|
|
63
|
+
if (publicProperty.node.readonly) return;
|
|
64
|
+
|
|
65
|
+
context.report({
|
|
66
|
+
node: publicProperty.node,
|
|
67
|
+
messageId: "invalidPublicPropertyOfConstruct",
|
|
68
|
+
data: {
|
|
69
|
+
propertyName: publicProperty.name,
|
|
70
|
+
},
|
|
71
|
+
fix: (fixer) => {
|
|
72
|
+
const accessibility = publicProperty.node.accessibility ? "public " : "";
|
|
73
|
+
const paramText = sourceCode.getText(publicProperty.node);
|
|
74
|
+
const [key, value] = paramText.split(":");
|
|
75
|
+
const replacedKey = key.startsWith("public ")
|
|
76
|
+
? key.replace("public ", "")
|
|
77
|
+
: key;
|
|
78
|
+
return fixer.replaceText(
|
|
79
|
+
publicProperty.node,
|
|
80
|
+
`${accessibility}readonly ${replacedKey}:${value}`
|
|
81
|
+
);
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
};
|
|
@@ -6,12 +6,10 @@ import {
|
|
|
6
6
|
TSESTree,
|
|
7
7
|
} from "@typescript-eslint/utils";
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
isConstructType,
|
|
14
|
-
} from "../utils/typecheck/cdk";
|
|
9
|
+
import { isConstructType } from "../core/cdk-construct/type-checker/is-construct";
|
|
10
|
+
import { isConstructOrStackType } from "../core/cdk-construct/type-checker/is-construct-or-stack";
|
|
11
|
+
import { toPascalCase } from "../shared/converter/to-pascal-case";
|
|
12
|
+
import { createRule } from "../shared/create-rule";
|
|
15
13
|
|
|
16
14
|
type Option = {
|
|
17
15
|
disallowContainingParentName?: boolean;
|
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
} from "@typescript-eslint/utils";
|
|
8
8
|
import { Type } from "typescript";
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
10
|
+
import { findConstructor } from "../../core/ast-node/finder/constructor";
|
|
11
|
+
import { isConstructType } from "../../core/cdk-construct/type-checker/is-construct";
|
|
12
|
+
import { createRule } from "../../shared/create-rule";
|
|
13
13
|
|
|
14
14
|
import { PropsUsageAnalyzer } from "./props-usage-analyzer";
|
|
15
15
|
import { IPropsUsageTracker, PropsUsageTracker } from "./props-usage-tracker";
|
|
@@ -45,20 +45,18 @@ export const noUnusedProps = createRule({
|
|
|
45
45
|
const type = parserServices.getTypeAtLocation(node);
|
|
46
46
|
if (!isConstructType(type)) return;
|
|
47
47
|
|
|
48
|
-
const constructor =
|
|
48
|
+
const constructor = findConstructor(node);
|
|
49
49
|
if (!constructor) return;
|
|
50
50
|
|
|
51
51
|
const propsParam = getPropsParam(constructor, parserServices);
|
|
52
52
|
if (!propsParam) return;
|
|
53
|
-
|
|
53
|
+
if (isPropsUsedInSuperCall(constructor, propsParam.node.name)) return;
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
if (isPropsUsedInSuperCall(constructor, propsNode.name)) return;
|
|
57
|
-
const tracker = new PropsUsageTracker(propsType);
|
|
55
|
+
const tracker = new PropsUsageTracker(propsParam.type);
|
|
58
56
|
const analyzer = new PropsUsageAnalyzer(tracker);
|
|
59
57
|
|
|
60
|
-
analyzer.analyze(constructor,
|
|
61
|
-
reportUnusedProperties(tracker,
|
|
58
|
+
analyzer.analyze(constructor, propsParam.node);
|
|
59
|
+
reportUnusedProperties(tracker, propsParam.node, context);
|
|
62
60
|
},
|
|
63
61
|
};
|
|
64
62
|
},
|
|
@@ -86,6 +84,17 @@ const getPropsParam = (
|
|
|
86
84
|
|
|
87
85
|
/**
|
|
88
86
|
* Checks if props are used in a super call
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* constructor(scope: Construct, id: string, props: MyConstructProps) {
|
|
91
|
+
* super(scope, id, props); // props used here
|
|
92
|
+
* }
|
|
93
|
+
* ```
|
|
94
|
+
*
|
|
95
|
+
* @param constructor - The constructor method definition node
|
|
96
|
+
* @param propsPropertyName - The name of the props parameter
|
|
97
|
+
* @returns True if props are used in super call, false otherwise
|
|
89
98
|
*/
|
|
90
99
|
const isPropsUsedInSuperCall = (
|
|
91
100
|
constructor: TSESTree.MethodDefinition,
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { AST_NODE_TYPES, TSESTree } from "@typescript-eslint/utils";
|
|
2
2
|
|
|
3
|
-
import { getChildNodes } from "../../utils/get-child-nodes";
|
|
4
|
-
|
|
5
3
|
import { IPropsUsageTracker } from "./props-usage-tracker";
|
|
4
|
+
import {
|
|
5
|
+
DirectPropsUsageVisitor,
|
|
6
|
+
InstanceVariableUsageVisitor,
|
|
7
|
+
MethodCallCollectorVisitor,
|
|
8
|
+
PropsAliasVisitor,
|
|
9
|
+
traverseNodes,
|
|
10
|
+
} from "./visitor";
|
|
6
11
|
|
|
7
12
|
export interface IPropsUsageAnalyzer {
|
|
8
13
|
analyze(
|
|
@@ -12,41 +17,105 @@ export interface IPropsUsageAnalyzer {
|
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
export class PropsUsageAnalyzer implements IPropsUsageAnalyzer {
|
|
15
|
-
private readonly
|
|
16
|
-
private visitedNodes: Set<TSESTree.Node>;
|
|
17
|
-
private propsAliases: Set<string>;
|
|
18
|
-
|
|
19
|
-
constructor(propsUsageTracker: IPropsUsageTracker) {
|
|
20
|
-
this.propsUsageTracker = propsUsageTracker;
|
|
21
|
-
this.visitedNodes = new Set<TSESTree.Node>();
|
|
22
|
-
this.propsAliases = new Set<string>();
|
|
23
|
-
}
|
|
20
|
+
constructor(private readonly tracker: IPropsUsageTracker) {}
|
|
24
21
|
|
|
25
|
-
|
|
22
|
+
analyze(
|
|
26
23
|
constructor: TSESTree.MethodDefinition,
|
|
27
24
|
propsParam: TSESTree.Identifier
|
|
28
25
|
): void {
|
|
29
|
-
this.initialize();
|
|
30
26
|
const constructorBody = constructor.value.body;
|
|
31
27
|
const classNode = constructor.parent;
|
|
32
28
|
const propsParamName = propsParam.name;
|
|
33
29
|
if (!constructorBody) return;
|
|
34
30
|
|
|
35
|
-
this.
|
|
36
|
-
this.
|
|
37
|
-
this.
|
|
31
|
+
this.checkUsageForDirectAccess(constructorBody, propsParamName);
|
|
32
|
+
this.checkUsageForAliasAccess(constructorBody, propsParamName);
|
|
33
|
+
this.checkUsageForInstanceVariable(classNode, constructor, propsParamName);
|
|
34
|
+
this.checkUsageForPrivateMethodsCalledFromConstructor(
|
|
38
35
|
constructorBody,
|
|
39
36
|
classNode,
|
|
40
37
|
propsParamName
|
|
41
38
|
);
|
|
42
39
|
}
|
|
43
40
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Analyzes direct access to props within the constructor body.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* constructor(scope: Construct, id: string, props: MyConstructProps) {
|
|
47
|
+
* super(scope, id);
|
|
48
|
+
* console.log(props.bucketName); // <- Direct access tracked here
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @param constructorBody - The constructor's BlockStatement to analyze
|
|
53
|
+
* @param propsParamName - The name of the props parameter (e.g., "props")
|
|
54
|
+
*/
|
|
55
|
+
private checkUsageForDirectAccess(
|
|
56
|
+
constructorBody: TSESTree.BlockStatement,
|
|
57
|
+
propsParamName: string
|
|
58
|
+
): void {
|
|
59
|
+
const directVisitor = new DirectPropsUsageVisitor(
|
|
60
|
+
this.tracker,
|
|
61
|
+
propsParamName
|
|
62
|
+
);
|
|
63
|
+
traverseNodes(constructorBody, directVisitor);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Analyzes props usage via aliases within the constructor body.
|
|
68
|
+
*
|
|
69
|
+
* When props is assigned to another variable (alias), this method tracks
|
|
70
|
+
* usage of that alias throughout the constructor.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* constructor(scope: Construct, id: string, props: MyConstructProps) {
|
|
75
|
+
* super(scope, id);
|
|
76
|
+
* const p = props; // <- Alias assignment detected
|
|
77
|
+
* console.log(p.bucketName); // <- Usage tracked here
|
|
78
|
+
* }
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* @param constructorBody - The constructor's BlockStatement to analyze
|
|
82
|
+
* @param propsParamName - The name of the props parameter (e.g., "props")
|
|
83
|
+
*/
|
|
84
|
+
private checkUsageForAliasAccess(
|
|
85
|
+
constructorBody: TSESTree.BlockStatement,
|
|
86
|
+
propsParamName: string
|
|
87
|
+
): void {
|
|
88
|
+
const aliasVisitor = new PropsAliasVisitor(this.tracker, propsParamName);
|
|
89
|
+
traverseNodes(constructorBody, aliasVisitor);
|
|
47
90
|
}
|
|
48
91
|
|
|
49
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Analyzes the class body for props usage via instance variables.
|
|
94
|
+
*
|
|
95
|
+
* When props is assigned to an instance variable (e.g., `this.myProps = props`),
|
|
96
|
+
* this method tracks usage of that instance variable throughout the entire class.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* class MyConstruct extends Construct {
|
|
101
|
+
* private myProps: MyConstructProps;
|
|
102
|
+
*
|
|
103
|
+
* constructor(scope: Construct, id: string, props: MyConstructProps) {
|
|
104
|
+
* super(scope, id);
|
|
105
|
+
* this.myProps = props; // <- Instance variable assignment detected
|
|
106
|
+
* }
|
|
107
|
+
*
|
|
108
|
+
* someMethod() {
|
|
109
|
+
* console.log(this.myProps.bucketName); // <- Usage tracked here
|
|
110
|
+
* }
|
|
111
|
+
* }
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* @param classBody - The ClassBody node to analyze
|
|
115
|
+
* @param constructor - The constructor MethodDefinition node
|
|
116
|
+
* @param propsParamName - The name of the props parameter (e.g., "props")
|
|
117
|
+
*/
|
|
118
|
+
private checkUsageForInstanceVariable(
|
|
50
119
|
classBody: TSESTree.ClassBody,
|
|
51
120
|
constructor: TSESTree.MethodDefinition,
|
|
52
121
|
propsParamName: string
|
|
@@ -58,23 +127,45 @@ export class PropsUsageAnalyzer implements IPropsUsageAnalyzer {
|
|
|
58
127
|
);
|
|
59
128
|
if (!instanceVarName) return;
|
|
60
129
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
this.visitNodes(member.value.body, instanceVarName);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
130
|
+
const instanceVisitor = new InstanceVariableUsageVisitor(
|
|
131
|
+
this.tracker,
|
|
132
|
+
instanceVarName
|
|
133
|
+
);
|
|
134
|
+
traverseNodes(classBody, instanceVisitor);
|
|
69
135
|
}
|
|
70
136
|
|
|
71
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Analyzes private methods that are called from the constructor with props as an argument.
|
|
139
|
+
*
|
|
140
|
+
* When a constructor calls a private method and passes props to it, this method
|
|
141
|
+
* finds the method definition and analyzes the props usage within that method.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```typescript
|
|
145
|
+
* class MyConstruct extends Construct {
|
|
146
|
+
* constructor(scope: Construct, id: string, props: MyConstructProps) {
|
|
147
|
+
* super(scope, id);
|
|
148
|
+
* this.setupBucket(props); // <- Method call with props detected
|
|
149
|
+
* }
|
|
150
|
+
*
|
|
151
|
+
* private setupBucket(p: MyConstructProps) {
|
|
152
|
+
* // Props usage in this method body is analyzed
|
|
153
|
+
* new Bucket(this, 'Bucket', { bucketName: p.bucketName });
|
|
154
|
+
* }
|
|
155
|
+
* }
|
|
156
|
+
* ```
|
|
157
|
+
*
|
|
158
|
+
* @param constructorBody - The constructor's BlockStatement to search for method calls
|
|
159
|
+
* @param classBody - The ClassBody containing method definitions
|
|
160
|
+
* @param propsParamName - The name of the props parameter (e.g., "props")
|
|
161
|
+
*/
|
|
162
|
+
private checkUsageForPrivateMethodsCalledFromConstructor(
|
|
72
163
|
constructorBody: TSESTree.BlockStatement,
|
|
73
164
|
classBody: TSESTree.ClassBody,
|
|
74
165
|
propsParamName: string
|
|
75
166
|
): void {
|
|
76
|
-
// NOTE:
|
|
77
|
-
const methodCallsWithProps = this.
|
|
167
|
+
// NOTE: Collect method calls in constructor
|
|
168
|
+
const methodCallsWithProps = this.collectMethodCallsWithProps(
|
|
78
169
|
constructorBody,
|
|
79
170
|
propsParamName
|
|
80
171
|
);
|
|
@@ -88,131 +179,76 @@ export class PropsUsageAnalyzer implements IPropsUsageAnalyzer {
|
|
|
88
179
|
for (const argIndex of propsArgIndices) {
|
|
89
180
|
const param = methodDef.value.params[argIndex];
|
|
90
181
|
if (param && param.type === AST_NODE_TYPES.Identifier) {
|
|
91
|
-
this.
|
|
182
|
+
const visitor = new DirectPropsUsageVisitor(this.tracker, param.name);
|
|
183
|
+
traverseNodes(methodDef.value.body, visitor);
|
|
92
184
|
}
|
|
93
185
|
}
|
|
94
186
|
}
|
|
95
187
|
}
|
|
96
188
|
|
|
97
|
-
private visitNodes(node: TSESTree.Node, propsParamName: string): void {
|
|
98
|
-
if (this.visitedNodes.has(node)) return;
|
|
99
|
-
this.visitedNodes.add(node);
|
|
100
|
-
|
|
101
|
-
switch (node.type) {
|
|
102
|
-
case AST_NODE_TYPES.MemberExpression:
|
|
103
|
-
this.propsUsageTracker.markAsUsedForMemberExpression(
|
|
104
|
-
node,
|
|
105
|
-
propsParamName
|
|
106
|
-
);
|
|
107
|
-
// NOTE: Check if the object is an alias of props (e.g., a.bucketname where a = props)
|
|
108
|
-
if (
|
|
109
|
-
node.object.type === AST_NODE_TYPES.Identifier &&
|
|
110
|
-
this.propsAliases.has(node.object.name) &&
|
|
111
|
-
node.property.type === AST_NODE_TYPES.Identifier
|
|
112
|
-
) {
|
|
113
|
-
this.propsUsageTracker.markAsUsed(node.property.name);
|
|
114
|
-
}
|
|
115
|
-
break;
|
|
116
|
-
case AST_NODE_TYPES.VariableDeclarator:
|
|
117
|
-
this.propsUsageTracker.markAsUsedForVariableDeclarator(
|
|
118
|
-
node,
|
|
119
|
-
propsParamName
|
|
120
|
-
);
|
|
121
|
-
break;
|
|
122
|
-
case AST_NODE_TYPES.AssignmentExpression:
|
|
123
|
-
this.propsUsageTracker.markAsUsedForAssignmentExpression(
|
|
124
|
-
node,
|
|
125
|
-
propsParamName
|
|
126
|
-
);
|
|
127
|
-
break;
|
|
128
|
-
case AST_NODE_TYPES.Identifier:
|
|
129
|
-
// NOTE: Check if props object is used as a whole (e.g., console.log(props))
|
|
130
|
-
if (node.name === propsParamName) this.handlePropsIdentifier(node);
|
|
131
|
-
break;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// NOTE: Recursively visit child nodes
|
|
135
|
-
const children = getChildNodes(node);
|
|
136
|
-
for (const child of children) {
|
|
137
|
-
this.visitNodes(child, propsParamName);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
189
|
/**
|
|
142
|
-
*
|
|
190
|
+
* Collects method calls in the constructor body where props is passed as an argument.
|
|
191
|
+
*
|
|
192
|
+
* Uses `MethodCallCollectorVisitor` to traverse the constructor body and find
|
|
193
|
+
* all `this.methodName(props)` patterns.
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```typescript
|
|
197
|
+
* constructor(scope: Construct, id: string, props: MyConstructProps) {
|
|
198
|
+
* super(scope, id);
|
|
199
|
+
* this.setupBucket(props); // <- Collected: { methodName: "setupBucket", propsArgIndices: [0] }
|
|
200
|
+
* this.configure(config, props); // <- Collected: { methodName: "configure", propsArgIndices: [1] }
|
|
201
|
+
* }
|
|
202
|
+
* ```
|
|
203
|
+
*
|
|
204
|
+
* @param body - The constructor's BlockStatement to traverse
|
|
205
|
+
* @param propsParamName - The name of the props parameter (e.g., "props")
|
|
206
|
+
* @returns Array of method call info with method names and argument indices where props appears
|
|
143
207
|
*/
|
|
144
|
-
private
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
// - VariableDeclarator init with ObjectPattern (const { xxx } = props)
|
|
152
|
-
// - AssignmentExpression right (this.props = props)
|
|
153
|
-
|
|
154
|
-
switch (parent.type) {
|
|
155
|
-
case AST_NODE_TYPES.AssignmentExpression:
|
|
156
|
-
case AST_NODE_TYPES.MemberExpression: {
|
|
157
|
-
break;
|
|
158
|
-
}
|
|
159
|
-
case AST_NODE_TYPES.VariableDeclarator: {
|
|
160
|
-
// NOTE: const a = props - track 'a' as an alias of props
|
|
161
|
-
if (
|
|
162
|
-
parent.init === node &&
|
|
163
|
-
parent.id.type === AST_NODE_TYPES.Identifier
|
|
164
|
-
) {
|
|
165
|
-
this.propsAliases.add(parent.id.name);
|
|
166
|
-
}
|
|
167
|
-
break;
|
|
168
|
-
}
|
|
169
|
-
case AST_NODE_TYPES.CallExpression: {
|
|
170
|
-
if (!parent.arguments.includes(node)) break;
|
|
171
|
-
// NOTE: Check if props is passed as an argument
|
|
172
|
-
// NOTE: Distinguish between method calls and external function calls
|
|
173
|
-
// this.methodName(props) - will be analyzed by analyzePrivateMethodsCalledFromConstructor
|
|
174
|
-
// console.log(props) or someExternalFunction(props) - mark all as used
|
|
175
|
-
if (
|
|
176
|
-
parent.callee.type === AST_NODE_TYPES.MemberExpression &&
|
|
177
|
-
parent.callee.object.type === AST_NODE_TYPES.ThisExpression
|
|
178
|
-
) {
|
|
179
|
-
// NOTE: this.methodName(props) - will be analyzed later
|
|
180
|
-
break;
|
|
181
|
-
}
|
|
182
|
-
// NOTE: External function call - mark all as used
|
|
183
|
-
this.propsUsageTracker.markAllAsUsed();
|
|
184
|
-
break;
|
|
185
|
-
}
|
|
186
|
-
case AST_NODE_TYPES.ReturnStatement: {
|
|
187
|
-
// NOTE: return props - props as a whole
|
|
188
|
-
if (parent.argument === node) {
|
|
189
|
-
this.propsUsageTracker.markAllAsUsed();
|
|
190
|
-
}
|
|
191
|
-
break;
|
|
192
|
-
}
|
|
193
|
-
case AST_NODE_TYPES.ArrayExpression: {
|
|
194
|
-
// NOTE: [props] - props as a whole
|
|
195
|
-
if (parent.elements.includes(node)) {
|
|
196
|
-
this.propsUsageTracker.markAllAsUsed();
|
|
197
|
-
}
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
case AST_NODE_TYPES.Property: {
|
|
201
|
-
// NOTE: { key: props } - props as a whole
|
|
202
|
-
if (parent.value === node) {
|
|
203
|
-
this.propsUsageTracker.markAllAsUsed();
|
|
204
|
-
}
|
|
205
|
-
break;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
+
private collectMethodCallsWithProps(
|
|
209
|
+
body: TSESTree.BlockStatement,
|
|
210
|
+
propsParamName: string
|
|
211
|
+
): { methodName: string; propsArgIndices: number[] }[] {
|
|
212
|
+
const visitor = new MethodCallCollectorVisitor(propsParamName);
|
|
213
|
+
traverseNodes(body, visitor);
|
|
214
|
+
return visitor.result;
|
|
208
215
|
}
|
|
209
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Finds the instance variable name where props is assigned in the constructor.
|
|
219
|
+
*
|
|
220
|
+
* This method detects the pattern where props is stored in an instance variable
|
|
221
|
+
* for later access within the class.
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```typescript
|
|
225
|
+
* class MyConstruct extends Construct {
|
|
226
|
+
* private myProps: MyConstructProps;
|
|
227
|
+
*
|
|
228
|
+
* constructor(scope: Construct, id: string, props: MyConstructProps) {
|
|
229
|
+
* super(scope, id);
|
|
230
|
+
* this.myProps = props; // <- This pattern is detected
|
|
231
|
+
* }
|
|
232
|
+
* }
|
|
233
|
+
* ```
|
|
234
|
+
*
|
|
235
|
+
* AST structure for `this.myProps = props`:
|
|
236
|
+
* ExpressionStatement
|
|
237
|
+
* └── expression: AssignmentExpression
|
|
238
|
+
* ├── left: MemberExpression
|
|
239
|
+
* │ ├── object: ThisExpression
|
|
240
|
+
* │ └── property: Identifier (name: "myProps" - returned value)
|
|
241
|
+
* └── right: Identifier (name: "props" === propsParamName)
|
|
242
|
+
*
|
|
243
|
+
* @param body - The constructor's BlockStatement to analyze
|
|
244
|
+
* @param propsParamName - The name of the props parameter (e.g., "props")
|
|
245
|
+
* @returns The instance variable name (e.g., "myProps") or null if not found
|
|
246
|
+
*/
|
|
210
247
|
private findPropsInstanceVariable(
|
|
211
248
|
body: TSESTree.BlockStatement,
|
|
212
249
|
propsParamName: string
|
|
213
250
|
): string | null {
|
|
214
251
|
for (const statement of body.body) {
|
|
215
|
-
// NOTE: Handle expression statements (e.g., this.props = props;)
|
|
216
252
|
if (
|
|
217
253
|
statement.type === AST_NODE_TYPES.ExpressionStatement &&
|
|
218
254
|
statement.expression.type === AST_NODE_TYPES.AssignmentExpression &&
|
|
@@ -230,7 +266,35 @@ export class PropsUsageAnalyzer implements IPropsUsageAnalyzer {
|
|
|
230
266
|
}
|
|
231
267
|
|
|
232
268
|
/**
|
|
233
|
-
* Finds method definition in class body
|
|
269
|
+
* Finds a method definition in the class body by its name.
|
|
270
|
+
*
|
|
271
|
+
* This method is used to locate the actual method implementation when
|
|
272
|
+
* a method call like `this.someMethod(props)` is found in the constructor.
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* ```typescript
|
|
276
|
+
* class MyConstruct extends Construct {
|
|
277
|
+
* constructor(scope: Construct, id: string, props: MyConstructProps) {
|
|
278
|
+
* super(scope, id);
|
|
279
|
+
* this.setupBucket(props); // <- Method call detected
|
|
280
|
+
* }
|
|
281
|
+
*
|
|
282
|
+
* private setupBucket(p: MyConstructProps) { // <- This definition is found
|
|
283
|
+
* new Bucket(this, 'Bucket', { bucketName: p.bucketName });
|
|
284
|
+
* }
|
|
285
|
+
* }
|
|
286
|
+
* ```
|
|
287
|
+
*
|
|
288
|
+
* AST structure for method definition:
|
|
289
|
+
* MethodDefinition
|
|
290
|
+
* ├── key: Identifier (name: "setupBucket" === methodName)
|
|
291
|
+
* └── value: FunctionExpression
|
|
292
|
+
* ├── params: [Identifier, ...]
|
|
293
|
+
* └── body: BlockStatement
|
|
294
|
+
*
|
|
295
|
+
* @param classBody - The ClassBody node containing all class members
|
|
296
|
+
* @param methodName - The name of the method to find (e.g., "setupBucket")
|
|
297
|
+
* @returns The MethodDefinition node or null if not found
|
|
234
298
|
*/
|
|
235
299
|
private findMethodDefinition(
|
|
236
300
|
classBody: TSESTree.ClassBody,
|
|
@@ -247,51 +311,4 @@ export class PropsUsageAnalyzer implements IPropsUsageAnalyzer {
|
|
|
247
311
|
}
|
|
248
312
|
return null;
|
|
249
313
|
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Finds method calls in constructor body that receive props as argument
|
|
253
|
-
*/
|
|
254
|
-
private findMethodCallsWithProps(
|
|
255
|
-
body: TSESTree.BlockStatement,
|
|
256
|
-
propsParamName: string
|
|
257
|
-
): { methodName: string; propsArgIndices: number[] }[] {
|
|
258
|
-
const result: { methodName: string; propsArgIndices: number[] }[] = [];
|
|
259
|
-
const visited = new Set<TSESTree.Node>();
|
|
260
|
-
|
|
261
|
-
const visitNode = (node: TSESTree.Node): void => {
|
|
262
|
-
if (visited.has(node)) return;
|
|
263
|
-
visited.add(node);
|
|
264
|
-
|
|
265
|
-
// NOTE: Check for this.methodName(...) pattern
|
|
266
|
-
if (
|
|
267
|
-
node.type === AST_NODE_TYPES.CallExpression &&
|
|
268
|
-
node.callee.type === AST_NODE_TYPES.MemberExpression &&
|
|
269
|
-
node.callee.object.type === AST_NODE_TYPES.ThisExpression &&
|
|
270
|
-
node.callee.property.type === AST_NODE_TYPES.Identifier
|
|
271
|
-
) {
|
|
272
|
-
const methodName = node.callee.property.name;
|
|
273
|
-
const propsArgIndices: number[] = node.arguments.reduce<number[]>(
|
|
274
|
-
(acc, arg, index) =>
|
|
275
|
-
arg.type === AST_NODE_TYPES.Identifier &&
|
|
276
|
-
arg.name === propsParamName
|
|
277
|
-
? // NOTE: props is passed directly (e.g., this.method(props))
|
|
278
|
-
[...acc, index]
|
|
279
|
-
: acc,
|
|
280
|
-
[]
|
|
281
|
-
);
|
|
282
|
-
if (propsArgIndices.length) {
|
|
283
|
-
result.push({ methodName, propsArgIndices });
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// NOTE: Recursively visit child nodes
|
|
288
|
-
const children = getChildNodes(node);
|
|
289
|
-
for (const child of children) {
|
|
290
|
-
visitNode(child);
|
|
291
|
-
}
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
visitNode(body);
|
|
295
|
-
return result;
|
|
296
|
-
}
|
|
297
314
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AST_NODE_TYPES, TSESTree } from "@typescript-eslint/utils";
|
|
2
2
|
import { Type } from "typescript";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { findPropertyNames } from "../../core/ast-node/finder/property-name";
|
|
5
5
|
|
|
6
6
|
export interface IPropsUsageTracker {
|
|
7
7
|
/**
|
|
@@ -14,6 +14,7 @@ export interface IPropsUsageTracker {
|
|
|
14
14
|
node: TSESTree.MemberExpression,
|
|
15
15
|
propsParamName: string
|
|
16
16
|
): void;
|
|
17
|
+
|
|
17
18
|
/**
|
|
18
19
|
* Marks a property as used when it is accessed in a member expression.
|
|
19
20
|
*
|
|
@@ -125,8 +126,8 @@ export class PropsUsageTracker implements IPropsUsageTracker {
|
|
|
125
126
|
return;
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
const
|
|
129
|
-
for (const name of
|
|
129
|
+
const names = findPropertyNames(node.id.properties);
|
|
130
|
+
for (const name of names) {
|
|
130
131
|
this.markAsUsed(name);
|
|
131
132
|
}
|
|
132
133
|
}
|