Skip to content

Commit c11ca06

Browse files
authored
chore(eslint-plugin): make utility for static member access (typescript-eslint#9836)
* WIP * WIP * prefer-includes * prefer-regexp-exec * string-startswith-endswith * support template literal * more places * more methods * class literal property style * use-unknown-in-catch-callback * cleanup * fix type assertion * remove unnecesssary checks
1 parent 2ee528c commit c11ca06

13 files changed

+130
-171
lines changed

packages/eslint-plugin/src/rules/class-literal-property-style.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
33

44
import {
55
createRule,
6+
getStaticMemberAccessValue,
67
getStaticStringValue,
78
isAssignee,
89
isFunction,
10+
isStaticMemberAccessOfValue,
911
nullThrows,
1012
} from '../util';
1113

@@ -79,10 +81,6 @@ export default createRule<Options, MessageIds>({
7981
create(context, [style]) {
8082
const propertiesInfoStack: PropertiesInfo[] = [];
8183

82-
function getStringValue(node: TSESTree.Node): string {
83-
return getStaticStringValue(node) ?? context.sourceCode.getText(node);
84-
}
85-
8684
function enterClassBody(): void {
8785
propertiesInfoStack.push({
8886
properties: [],
@@ -102,8 +100,8 @@ export default createRule<Options, MessageIds>({
102100
return;
103101
}
104102

105-
const name = getStringValue(node.key);
106-
if (excludeSet.has(name)) {
103+
const name = getStaticMemberAccessValue(node, context);
104+
if (name && excludeSet.has(name)) {
107105
return;
108106
}
109107

@@ -167,15 +165,17 @@ export default createRule<Options, MessageIds>({
167165
return;
168166
}
169167

170-
const name = getStringValue(node.key);
171-
172-
const hasDuplicateKeySetter = node.parent.body.some(element => {
173-
return (
174-
element.type === AST_NODE_TYPES.MethodDefinition &&
175-
element.kind === 'set' &&
176-
getStringValue(element.key) === name
177-
);
178-
});
168+
const name = getStaticMemberAccessValue(node, context);
169+
170+
const hasDuplicateKeySetter =
171+
name &&
172+
node.parent.body.some(element => {
173+
return (
174+
element.type === AST_NODE_TYPES.MethodDefinition &&
175+
element.kind === 'set' &&
176+
isStaticMemberAccessOfValue(element, context, name)
177+
);
178+
});
179179
if (hasDuplicateKeySetter) {
180180
return;
181181
}

packages/eslint-plugin/src/rules/class-methods-use-this.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
createRule,
66
getFunctionHeadLoc,
77
getFunctionNameWithKind,
8-
getStaticStringValue,
8+
getStaticMemberAccessValue,
99
} from '../util';
1010

1111
type Options = [
@@ -182,10 +182,7 @@ export default createRule<Options, MessageIds>({
182182

183183
const hashIfNeeded =
184184
node.key.type === AST_NODE_TYPES.PrivateIdentifier ? '#' : '';
185-
const name =
186-
node.key.type === AST_NODE_TYPES.Literal
187-
? getStaticStringValue(node.key)
188-
: node.key.name || '';
185+
const name = getStaticMemberAccessValue(node, context);
189186

190187
return !exceptMethods.has(hashIfNeeded + (name ?? ''));
191188
}

packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { DefinitionType } from '@typescript-eslint/scope-manager';
22
import type { TSESTree } from '@typescript-eslint/utils';
33
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
44

5-
import { createRule, isFunction } from '../util';
5+
import { createRule, isFunction, isStaticMemberAccessOfValue } from '../util';
66
import type {
77
FunctionExpression,
88
FunctionInfo,
@@ -268,21 +268,11 @@ export default createRule<Options, MessageIds>({
268268
(node.type === AST_NODE_TYPES.Property && node.method) ||
269269
node.type === AST_NODE_TYPES.PropertyDefinition
270270
) {
271-
if (
272-
node.key.type === AST_NODE_TYPES.Literal &&
273-
typeof node.key.value === 'string'
274-
) {
275-
return options.allowedNames.includes(node.key.value);
276-
}
277-
if (
278-
node.key.type === AST_NODE_TYPES.TemplateLiteral &&
279-
node.key.expressions.length === 0
280-
) {
281-
return options.allowedNames.includes(node.key.quasis[0].value.raw);
282-
}
283-
if (!node.computed && node.key.type === AST_NODE_TYPES.Identifier) {
284-
return options.allowedNames.includes(node.key.name);
285-
}
271+
return isStaticMemberAccessOfValue(
272+
node,
273+
context,
274+
...options.allowedNames,
275+
);
286276
}
287277

288278
return false;

packages/eslint-plugin/src/rules/no-floating-promises.ts

Lines changed: 29 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
createRule,
99
getOperatorPrecedence,
1010
getParserServices,
11+
getStaticMemberAccessValue,
1112
isBuiltinSymbolLike,
1213
OperatorPrecedence,
1314
readonlynessOptionsDefaults,
@@ -334,27 +335,38 @@ export default createRule<Options, MessageId>({
334335
// If the outer expression is a call, a `.catch()` or `.then()` with
335336
// rejection handler handles the promise.
336337

337-
const catchRejectionHandler = getRejectionHandlerFromCatchCall(node);
338-
if (catchRejectionHandler) {
339-
if (isValidRejectionHandler(catchRejectionHandler)) {
340-
return { isUnhandled: false };
338+
const { callee } = node;
339+
if (callee.type === AST_NODE_TYPES.MemberExpression) {
340+
const methodName = getStaticMemberAccessValue(callee, context);
341+
const catchRejectionHandler =
342+
methodName === 'catch' && node.arguments.length >= 1
343+
? node.arguments[0]
344+
: undefined;
345+
if (catchRejectionHandler) {
346+
if (isValidRejectionHandler(catchRejectionHandler)) {
347+
return { isUnhandled: false };
348+
}
349+
return { isUnhandled: true, nonFunctionHandler: true };
341350
}
342-
return { isUnhandled: true, nonFunctionHandler: true };
343-
}
344351

345-
const thenRejectionHandler = getRejectionHandlerFromThenCall(node);
346-
if (thenRejectionHandler) {
347-
if (isValidRejectionHandler(thenRejectionHandler)) {
348-
return { isUnhandled: false };
352+
const thenRejectionHandler =
353+
methodName === 'then' && node.arguments.length >= 2
354+
? node.arguments[1]
355+
: undefined;
356+
if (thenRejectionHandler) {
357+
if (isValidRejectionHandler(thenRejectionHandler)) {
358+
return { isUnhandled: false };
359+
}
360+
return { isUnhandled: true, nonFunctionHandler: true };
349361
}
350-
return { isUnhandled: true, nonFunctionHandler: true };
351-
}
352362

353-
// `x.finally()` is transparent to resolution of the promise, so check `x`.
354-
// ("object" in this context is the `x` in `x.finally()`)
355-
const promiseFinallyObject = getObjectFromFinallyCall(node);
356-
if (promiseFinallyObject) {
357-
return isUnhandledPromise(checker, promiseFinallyObject);
363+
// `x.finally()` is transparent to resolution of the promise, so check `x`.
364+
// ("object" in this context is the `x` in `x.finally()`)
365+
const promiseFinallyObject =
366+
methodName === 'finally' ? callee.object : undefined;
367+
if (promiseFinallyObject) {
368+
return isUnhandledPromise(checker, promiseFinallyObject);
369+
}
358370
}
359371

360372
// All other cases are unhandled.
@@ -485,41 +497,3 @@ function isFunctionParam(
485497
}
486498
return false;
487499
}
488-
489-
function getRejectionHandlerFromCatchCall(
490-
expression: TSESTree.CallExpression,
491-
): TSESTree.CallExpressionArgument | undefined {
492-
if (
493-
expression.callee.type === AST_NODE_TYPES.MemberExpression &&
494-
expression.callee.property.type === AST_NODE_TYPES.Identifier &&
495-
expression.callee.property.name === 'catch' &&
496-
expression.arguments.length >= 1
497-
) {
498-
return expression.arguments[0];
499-
}
500-
return undefined;
501-
}
502-
503-
function getRejectionHandlerFromThenCall(
504-
expression: TSESTree.CallExpression,
505-
): TSESTree.CallExpressionArgument | undefined {
506-
if (
507-
expression.callee.type === AST_NODE_TYPES.MemberExpression &&
508-
expression.callee.property.type === AST_NODE_TYPES.Identifier &&
509-
expression.callee.property.name === 'then' &&
510-
expression.arguments.length >= 2
511-
) {
512-
return expression.arguments[1];
513-
}
514-
return undefined;
515-
}
516-
517-
function getObjectFromFinallyCall(
518-
expression: TSESTree.CallExpression,
519-
): TSESTree.Expression | undefined {
520-
return expression.callee.type === AST_NODE_TYPES.MemberExpression &&
521-
expression.callee.property.type === AST_NODE_TYPES.Identifier &&
522-
expression.callee.property.name === 'finally'
523-
? expression.callee.object
524-
: undefined;
525-
}

packages/eslint-plugin/src/rules/prefer-find.ts

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
22
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
3-
import type { RuleFix, Scope } from '@typescript-eslint/utils/ts-eslint';
3+
import type { RuleFix } from '@typescript-eslint/utils/ts-eslint';
44
import * as tsutils from 'ts-api-utils';
55
import type { Type } from 'typescript';
66

@@ -9,6 +9,7 @@ import {
99
getConstrainedTypeAtLocation,
1010
getParserServices,
1111
getStaticValue,
12+
isStaticMemberAccessOfValue,
1213
nullThrows,
1314
} from '../util';
1415

@@ -89,7 +90,7 @@ export default createRule({
8990
// or the optional chaining variants.
9091
if (callee.type === AST_NODE_TYPES.MemberExpression) {
9192
const isBracketSyntaxForFilter = callee.computed;
92-
if (isStaticMemberAccessOfValue(callee, 'filter', globalScope)) {
93+
if (isStaticMemberAccessOfValue(callee, context, 'filter')) {
9394
const filterNode = callee.property;
9495

9596
const filteredObjectType = getConstrainedTypeAtLocation(
@@ -162,7 +163,7 @@ export default createRule({
162163
if (
163164
callee.type === AST_NODE_TYPES.MemberExpression &&
164165
!callee.optional &&
165-
isStaticMemberAccessOfValue(callee, 'at', globalScope)
166+
isStaticMemberAccessOfValue(callee, context, 'at')
166167
) {
167168
const atArgument = getStaticValue(node.arguments[0], globalScope);
168169
if (atArgument != null && isTreatedAsZeroByArrayAt(atArgument.value)) {
@@ -321,25 +322,3 @@ export default createRule({
321322
};
322323
},
323324
});
324-
325-
/**
326-
* Answers whether the member expression looks like
327-
* `x.memberName`, `x['memberName']`,
328-
* or even `const mn = 'memberName'; x[mn]` (or optional variants thereof).
329-
*/
330-
function isStaticMemberAccessOfValue(
331-
memberExpression:
332-
| TSESTree.MemberExpressionComputedName
333-
| TSESTree.MemberExpressionNonComputedName,
334-
value: string,
335-
scope?: Scope.Scope,
336-
): boolean {
337-
if (!memberExpression.computed) {
338-
// x.memberName case.
339-
return memberExpression.property.name === value;
340-
}
341-
342-
// x['memberName'] cases.
343-
const staticValueResult = getStaticValue(memberExpression.property, scope);
344-
return staticValueResult != null && value === staticValueResult.value;
345-
}

packages/eslint-plugin/src/rules/prefer-includes.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
getConstrainedTypeAtLocation,
99
getParserServices,
1010
getStaticValue,
11+
isStaticMemberAccessOfValue,
1112
} from '../util';
1213

1314
export default createRule({
@@ -146,6 +147,9 @@ export default createRule({
146147
node: TSESTree.MemberExpression,
147148
allowFixing: boolean,
148149
): void {
150+
if (!isStaticMemberAccessOfValue(node, context, 'indexOf')) {
151+
return;
152+
}
149153
// Check if the comparison is equivalent to `includes()`.
150154
const callNode = node.parent as TSESTree.CallExpression;
151155
const compareNode = (
@@ -204,14 +208,14 @@ export default createRule({
204208

205209
return {
206210
// a.indexOf(b) !== 1
207-
"BinaryExpression > CallExpression.left > MemberExpression.callee[property.name='indexOf'][computed=false]"(
211+
'BinaryExpression > CallExpression.left > MemberExpression'(
208212
node: TSESTree.MemberExpression,
209213
): void {
210214
checkArrayIndexOf(node, /* allowFixing */ true);
211215
},
212216

213217
// a?.indexOf(b) !== 1
214-
"BinaryExpression > ChainExpression.left > CallExpression > MemberExpression.callee[property.name='indexOf'][computed=false]"(
218+
'BinaryExpression > ChainExpression.left > CallExpression > MemberExpression'(
215219
node: TSESTree.MemberExpression,
216220
): void {
217221
checkArrayIndexOf(node, /* allowFixing */ false);

packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
isPromiseConstructorLike,
1111
isPromiseLike,
1212
isReadonlyErrorLike,
13+
isStaticMemberAccessOfValue,
1314
} from '../util';
1415

1516
export type MessageIds = 'rejectAnError';
@@ -99,13 +100,8 @@ export default createRule<Options, MessageIds>({
99100
return;
100101
}
101102

102-
const rejectMethodCalled = callee.computed
103-
? callee.property.type === AST_NODE_TYPES.Literal &&
104-
callee.property.value === 'reject'
105-
: callee.property.name === 'reject';
106-
107103
if (
108-
!rejectMethodCalled ||
104+
!isStaticMemberAccessOfValue(callee, context, 'reject') ||
109105
!typeAtLocationIsLikePromise(callee.object)
110106
) {
111107
return;

packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,19 @@
11
import type { TSESTree } from '@typescript-eslint/utils';
2-
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
32
import * as tsutils from 'ts-api-utils';
43
import type * as ts from 'typescript';
54

65
import {
76
createRule,
87
getConstrainedTypeAtLocation,
98
getParserServices,
9+
isStaticMemberAccessOfValue,
1010
isTypeAssertion,
1111
} from '../util';
1212

1313
type MemberExpressionWithCallExpressionParent = TSESTree.MemberExpression & {
1414
parent: TSESTree.CallExpression;
1515
};
1616

17-
const getMemberExpressionName = (
18-
member: TSESTree.MemberExpression,
19-
): string | null => {
20-
if (!member.computed) {
21-
return member.property.name;
22-
}
23-
24-
if (
25-
member.property.type === AST_NODE_TYPES.Literal &&
26-
typeof member.property.value === 'string'
27-
) {
28-
return member.property.value;
29-
}
30-
31-
return null;
32-
};
33-
3417
export default createRule({
3518
name: 'prefer-reduce-type-parameter',
3619
meta: {
@@ -67,7 +50,7 @@ export default createRule({
6750
'CallExpression > MemberExpression.callee'(
6851
callee: MemberExpressionWithCallExpressionParent,
6952
): void {
70-
if (getMemberExpressionName(callee) !== 'reduce') {
53+
if (!isStaticMemberAccessOfValue(callee, context, 'reduce')) {
7154
return;
7255
}
7356

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy