Skip to content

Commit 392c738

Browse files
authored
refactor: simplify callee name checks and remove getPropertyName (#1905)
1 parent 7f6d874 commit 392c738

52 files changed

Lines changed: 274 additions & 595 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/rule-implementation-patterns-term-based.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
144144
const [, isStatic] = methodStack.at(-1) ?? [];
145145
const [setState] = setStateStack.at(-1) ?? [];
146146
if (!isComponent || isStatic || setState == null) return;
147-
if (Extract.getPropertyName(node.property) === "state") {
147+
if (node.property.type === AST.Identifier && node.property.name === "state") {
148148
context.report({ messageId: "default", node });
149149
}
150150
},

packages/ast/docs/@eslint-react/namespaces/Extract/README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ Helpers for extracting information from `TSESTree` nodes.
66

77
## Functions
88

9-
| Function | Description |
10-
| ----------------------------------------------------------- | ----------- |
11-
| [getFullyQualifiedName](functions/getFullyQualifiedName.md) | - |
12-
| [getPropertyName](functions/getPropertyName.md) | - |
13-
| [getRootIdentifier](functions/getRootIdentifier.md) | - |
14-
| [unwrap](functions/unwrap.md) | - |
9+
| Function | Description |
10+
| ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
11+
| [getCalleeName](functions/getCalleeName.md) | Get the name of a call expression's callee when it is an identifier or a member expression whose property is an identifier |
12+
| [getFullyQualifiedName](functions/getFullyQualifiedName.md) | - |
13+
| [getRootIdentifier](functions/getRootIdentifier.md) | - |
14+
| [unwrap](functions/unwrap.md) | - |
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[@eslint-react/ast](../../../../README.md) / [Extract](../README.md) / getCalleeName
2+
3+
# Function: getCalleeName()
4+
5+
```ts
6+
function getCalleeName(node: CallExpression): string | null;
7+
```
8+
9+
Get the name of a call expression's callee when it is an identifier
10+
or a member expression whose property is an identifier
11+
12+
## Parameters
13+
14+
| Parameter | Type | Description |
15+
| --------- | ---------------- | ------------------------ |
16+
| `node` | `CallExpression` | The call expression node |
17+
18+
## Returns
19+
20+
`string` \| `null`
21+
22+
The callee name, or `null` if it cannot be determined

packages/ast/docs/@eslint-react/namespaces/Extract/functions/getPropertyName.md

Lines changed: 0 additions & 18 deletions
This file was deleted.

packages/ast/src/extract.test.ts

Lines changed: 0 additions & 144 deletions
This file was deleted.

packages/ast/src/extract.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,19 @@ export function getRootIdentifier(node: TSESTree.Expression | TSESTree.PrivateId
1818
return null;
1919
}
2020

21-
export function getPropertyName(node: TSESTree.Node, resolve = (n: TSESTree.Identifier | TSESTree.PrivateIdentifier): string | null => n.name): string | null {
22-
node = unwrap(node);
23-
if (node.type === AST.Identifier || node.type === AST.PrivateIdentifier) {
24-
return resolve(node);
21+
/**
22+
* Get the name of a call expression's callee when it is an identifier
23+
* or a member expression whose property is an identifier
24+
* @param node The call expression node
25+
* @returns The callee name, or `null` if it cannot be determined
26+
*/
27+
export function getCalleeName(node: TSESTree.CallExpression): string | null {
28+
const callee = unwrap(node.callee);
29+
if (callee.type === AST.Identifier) {
30+
return callee.name;
2531
}
26-
if (node.type === AST.Literal) {
27-
return String(node.value);
28-
}
29-
if (node.type === AST.TemplateLiteral && node.expressions.length === 0) {
30-
return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw ?? null;
32+
if (callee.type === AST.MemberExpression && callee.property.type === AST.Identifier) {
33+
return callee.property.name;
3134
}
3235
return null;
3336
}

packages/core/src/class-component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ export function isThisSetStateCall(node: TSESTree.CallExpression) {
141141
return (
142142
callee.type === AST.MemberExpression
143143
&& callee.object.type === AST.ThisExpression
144-
&& Extract.getPropertyName(callee.property) === "setState"
144+
&& Extract.getCalleeName(node) === "setState"
145145
);
146146
}
147147

@@ -154,7 +154,7 @@ export function isAssignmentToThisState(node: TSESTree.AssignmentExpression) {
154154
let current: TSESTree.Node = Extract.unwrap(left);
155155
while (current.type === AST.MemberExpression) {
156156
const { object, property } = current;
157-
if (object.type === AST.ThisExpression && Extract.getPropertyName(property) === "state") {
157+
if (object.type === AST.ThisExpression && property.type === AST.Identifier && property.name === "state") {
158158
return true;
159159
}
160160
current = Extract.unwrap(object);

packages/core/src/function-component.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,9 @@ export function isFunctionComponentDefinition(context: RuleContext, node: TSESTr
296296
const parentCallee = parent.type === AST.CallExpression
297297
? Extract.unwrap(parent.callee)
298298
: null;
299+
const parentCalleeName = parent.type === AST.CallExpression
300+
? Extract.getCalleeName(parent)
301+
: null;
299302
switch (true) {
300303
case Check.isOneOf([AST.ArrowFunctionExpression, AST.FunctionExpression])(node)
301304
&& parent.type === AST.Property
@@ -318,12 +321,12 @@ export function isFunctionComponentDefinition(context: RuleContext, node: TSESTr
318321
break;
319322
case parentCallee != null
320323
&& parentCallee.type === AST.MemberExpression
321-
&& Extract.getPropertyName(parentCallee.property) === "map":
324+
&& parentCalleeName === "map":
322325
if (hint & FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback) return false;
323326
break;
324327
case parentCallee != null
325328
&& parentCallee.type === AST.MemberExpression
326-
&& Extract.getPropertyName(parentCallee.property) === "flatMap":
329+
&& parentCalleeName === "flatMap":
327330
if (hint & FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayFlatMapCallback) return false;
328331
break;
329332
case parent.type === AST.CallExpression

packages/core/src/hook.ts

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -113,17 +113,10 @@ export function isHookDefinition(node: TSESTreeFunction | null) {
113113
*/
114114
export function isHookCall(node: TSESTree.Node | null): node is TSESTree.CallExpression {
115115
if (node == null) return false;
116-
if (node.type !== AST.CallExpression) {
117-
return false;
118-
}
119-
const callee = Extract.unwrap(node.callee);
120-
if (callee.type === AST.Identifier) {
121-
return isHookName(callee.name);
122-
}
123-
if (callee.type === AST.MemberExpression) {
124-
return callee.property.type === AST.Identifier && isHookName(callee.property.name);
125-
}
126-
return false;
116+
if (node.type !== AST.CallExpression) return false;
117+
const name = Extract.getCalleeName(node);
118+
if (name == null) return false;
119+
return isHookName(name);
127120
}
128121

129122
/**
@@ -137,19 +130,10 @@ export function isUseEffectLikeCall(
137130
additionalEffectHooks: RegExpLike = { test: constFalse },
138131
): node is TSESTree.CallExpression {
139132
if (node == null) return false;
140-
if (node.type !== AST.CallExpression) {
141-
return false;
142-
}
143-
const callee = Extract.unwrap(node.callee);
144-
return [/^use\w*Effect$/u, additionalEffectHooks].some((regexp) => {
145-
if (callee.type === AST.Identifier) {
146-
return regexp.test(callee.name);
147-
}
148-
if (callee.type === AST.MemberExpression) {
149-
return callee.property.type === AST.Identifier && regexp.test(callee.property.name);
150-
}
151-
return false;
152-
});
133+
if (node.type !== AST.CallExpression) return false;
134+
const name = Extract.getCalleeName(node);
135+
if (name == null) return false;
136+
return /^use\w*Effect$/u.test(name) || additionalEffectHooks.test(name);
153137
}
154138

155139
/**
@@ -163,18 +147,10 @@ export function isUseStateLikeCall(
163147
additionalStateHooks: RegExpLike = { test: constFalse },
164148
): node is TSESTree.CallExpression {
165149
if (node == null) return false;
166-
if (node.type !== AST.CallExpression) {
167-
return false;
168-
}
169-
const callee = Extract.unwrap(node.callee);
170-
switch (true) {
171-
case callee.type === AST.Identifier:
172-
return callee.name === "useState" || additionalStateHooks.test(callee.name);
173-
case callee.type === AST.MemberExpression
174-
&& callee.property.type === AST.Identifier:
175-
return Extract.getPropertyName(callee.property) === "useState" || additionalStateHooks.test(callee.property.name);
176-
}
177-
return false;
150+
if (node.type !== AST.CallExpression) return false;
151+
const name = Extract.getCalleeName(node);
152+
if (name == null) return false;
153+
return name === "useState" || additionalStateHooks.test(name);
178154
}
179155

180156
// #endregion

packages/core/src/jsx.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,9 @@ describe("isJsxLike", () => {
214214
expect(run("React.createElement('div');", hint)).toBe(false);
215215
});
216216

217-
it("should match computed member access", () => {
218-
// `React['createElement']` has a Literal property.
219-
expect(run("React['createElement']('div');")).toBe(true);
217+
it("should not match computed member access", () => {
218+
// `React['createElement']` has a Literal property, which is not statically resolved.
219+
expect(run("React['createElement']('div');")).toBe(false);
220220
});
221221

222222
it("should detect createElement when callee is wrapped in TSAsExpression", () => {

0 commit comments

Comments
 (0)