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.
Files changed (60) hide show
  1. package/dist/index.cjs +29324 -1576
  2. package/dist/index.d.cts +72 -0
  3. package/dist/index.d.cts.map +1 -0
  4. package/dist/index.d.mts +72 -0
  5. package/dist/index.d.mts.map +1 -0
  6. package/dist/index.mjs +29324 -1552
  7. package/dist/index.mjs.map +1 -0
  8. package/package.json +19 -22
  9. package/src/configs/flat-config.ts +69 -0
  10. package/src/configs/index.ts +6 -0
  11. package/src/{utils/get-child-nodes.ts → core/ast-node/finder/child-nodes.ts} +2 -2
  12. package/src/{utils/getConstructor.ts → core/ast-node/finder/constructor.ts} +1 -1
  13. package/src/core/ast-node/finder/property-name.ts +20 -0
  14. package/src/core/ast-node/finder/public-property.ts +76 -0
  15. package/src/core/cdk-construct/type-checker/is-construct-or-stack.ts +21 -0
  16. package/src/core/cdk-construct/type-checker/is-construct.ts +22 -0
  17. package/src/{utils → core/cdk-construct/type-checker}/is-resource-with-readonly-interface.ts +5 -4
  18. package/src/core/cdk-construct/type-checker/is-resource.ts +17 -0
  19. package/src/core/cdk-construct/type-finder/index.ts +73 -0
  20. package/src/{utils/typecheck/ts-type.ts → core/ts-type/checker/is-array.ts} +2 -8
  21. package/src/core/ts-type/checker/is-class.ts +7 -0
  22. package/src/core/ts-type/checker/is-extends-target-super-class.ts +24 -0
  23. package/src/core/ts-type/checker/private/get-symbol.ts +5 -0
  24. package/src/core/ts-type/finder/array-element.ts +20 -0
  25. package/src/core/ts-type/finder/constructor-property-name.ts +21 -0
  26. package/src/{utils/getGenericTypeArgument.ts → core/ts-type/finder/generics-type-argument.ts} +5 -5
  27. package/src/index.ts +6 -108
  28. package/src/rules/construct-constructor-property.ts +48 -26
  29. package/src/rules/index.ts +34 -0
  30. package/src/rules/no-construct-in-interface.ts +5 -51
  31. package/src/rules/no-construct-in-public-property-of-construct.ts +20 -143
  32. package/src/rules/no-construct-stack-suffix.ts +5 -5
  33. package/src/rules/no-mutable-property-of-props-interface.ts +1 -1
  34. package/src/rules/no-mutable-public-property-of-construct.ts +47 -39
  35. package/src/rules/no-parent-name-construct-id-match.ts +4 -6
  36. package/src/rules/no-unused-props/index.ts +19 -10
  37. package/src/rules/no-unused-props/props-usage-analyzer.ts +207 -190
  38. package/src/rules/no-unused-props/props-usage-tracker.ts +4 -3
  39. package/src/rules/no-unused-props/visitor/direct-props-usage-visitor.ts +145 -0
  40. package/src/rules/no-unused-props/visitor/index.ts +5 -0
  41. package/src/rules/no-unused-props/visitor/instance-variable-usage-visitor.ts +117 -0
  42. package/src/rules/no-unused-props/visitor/interface/node-visitor.ts +9 -0
  43. package/src/rules/no-unused-props/visitor/method-call-collector-visitor.ts +60 -0
  44. package/src/rules/no-unused-props/visitor/props-alias-visitor.ts +81 -0
  45. package/src/rules/no-unused-props/visitor/traverse-nodes.ts +38 -0
  46. package/src/rules/no-variable-construct-id.ts +4 -4
  47. package/src/rules/pascal-case-construct-id.ts +5 -5
  48. package/src/rules/props-name-convention.ts +4 -4
  49. package/src/rules/require-jsdoc.ts +2 -2
  50. package/src/rules/require-passing-this.ts +4 -4
  51. package/src/rules/require-props-default-doc.ts +1 -1
  52. package/bin/migration.mjs +0 -100
  53. package/dist/index.d.ts +0 -47
  54. package/dist/index.d.ts.map +0 -1
  55. package/src/utils/getArrayElementType.ts +0 -14
  56. package/src/utils/getPropertyNames.ts +0 -41
  57. package/src/utils/typecheck/cdk.ts +0 -71
  58. package/src/utils/typecheck/ts-node.ts +0 -7
  59. /package/src/{utils/convertString.ts → shared/converter/to-pascal-case.ts} +0 -0
  60. /package/src/{utils/createRule.ts → shared/create-rule.ts} +0 -0
@@ -1,7 +1,16 @@
1
- import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils";
1
+ import {
2
+ ESLintUtils,
3
+ TSESLint
4
+ } from "@typescript-eslint/utils";
2
5
 
3
- import { createRule } from "../utils/createRule";
4
- import { isConstructOrStackType } from "../utils/typecheck/cdk";
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
- 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
- },
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 { toPascalCase } from "../utils/convertString";
10
- import { createRule } from "../utils/createRule";
11
- import {
12
- isConstructOrStackType,
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 { createRule } from "../../utils/createRule";
11
- import { getConstructor } from "../../utils/getConstructor";
12
- import { isConstructType } from "../../utils/typecheck/cdk";
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 = getConstructor(node);
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
- const { node: propsNode, type: propsType } = propsParam;
53
+ if (isPropsUsedInSuperCall(constructor, propsParam.node.name)) return;
54
54
 
55
- // NOTE: Standard props parameter (e.g. props: MyConstructProps)
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, propsNode);
61
- reportUnusedProperties(tracker, propsNode, context);
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 propsUsageTracker: IPropsUsageTracker;
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
- public analyze(
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.visitNodes(constructorBody, propsParamName);
36
- this.analyzeClassBody(classNode, constructor, propsParamName);
37
- this.analyzePrivateMethodsCalledFromConstructor(
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
- private initialize(): void {
45
- this.visitedNodes.clear();
46
- this.propsAliases.clear();
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
- private analyzeClassBody(
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
- for (const member of classBody.body) {
62
- if (
63
- member.type === AST_NODE_TYPES.MethodDefinition &&
64
- member.value.body
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
- private analyzePrivateMethodsCalledFromConstructor(
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: Find method calls in constructor
77
- const methodCallsWithProps = this.findMethodCallsWithProps(
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.visitNodes(methodDef.value.body, param.name);
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
- * Handles cases where props is used as a whole identifier
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 handlePropsIdentifier(node: TSESTree.Identifier): void {
145
- const parent = node.parent;
146
- if (!parent) return;
147
-
148
- // NOTE: Check if this identifier is in a context where it's used as a whole
149
- // Exclude cases already handled by other methods:
150
- // - MemberExpression object (props.xxx)
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 { getPropertyNames } from "../../utils/getPropertyNames";
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 propertyNames = getPropertyNames(node.id.properties);
129
- for (const name of propertyNames) {
129
+ const names = findPropertyNames(node.id.properties);
130
+ for (const name of names) {
130
131
  this.markAsUsed(name);
131
132
  }
132
133
  }