From c46e4af5d5b64daa5e88a93bb8ba4bb5e45e3b0d Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Mon, 15 Apr 2024 18:00:12 -0500 Subject: [PATCH 01/13] WIP --- .../src/rules/class-methods-use-this.ts | 7 +--- .../rules/explicit-module-boundary-types.ts | 19 ++------- .../src/rules/prefer-promise-reject-errors.ts | 3 ++ packages/eslint-plugin/src/util/misc.ts | 39 ++++++++++++++++++- 4 files changed, 47 insertions(+), 21 deletions(-) diff --git a/packages/eslint-plugin/src/rules/class-methods-use-this.ts b/packages/eslint-plugin/src/rules/class-methods-use-this.ts index 6236a46ddb7b..781743bae541 100644 --- a/packages/eslint-plugin/src/rules/class-methods-use-this.ts +++ b/packages/eslint-plugin/src/rules/class-methods-use-this.ts @@ -5,7 +5,7 @@ import { createRule, getFunctionHeadLoc, getFunctionNameWithKind, - getStaticStringValue, + getStaticKeyValue, } from '../util'; type Options = [ @@ -184,10 +184,7 @@ export default createRule({ const hashIfNeeded = node.key.type === AST_NODE_TYPES.PrivateIdentifier ? '#' : ''; - const name = - node.key.type === AST_NODE_TYPES.Literal - ? getStaticStringValue(node.key) - : node.key.name || ''; + const name = getStaticKeyValue(node); return !exceptMethods.has(hashIfNeeded + (name ?? '')); } diff --git a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts index c8981e403941..5fffcc76751f 100644 --- a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts +++ b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts @@ -2,7 +2,7 @@ import { DefinitionType } from '@typescript-eslint/scope-manager'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import { createRule, isFunction } from '../util'; +import { createRule, getStaticKeyValue, isFunction } from '../util'; import type { FunctionExpression, FunctionInfo, @@ -268,20 +268,9 @@ export default createRule({ (node.type === AST_NODE_TYPES.Property && node.method) || node.type === AST_NODE_TYPES.PropertyDefinition ) { - if ( - node.key.type === AST_NODE_TYPES.Literal && - typeof node.key.value === 'string' - ) { - return options.allowedNames.includes(node.key.value); - } - if ( - node.key.type === AST_NODE_TYPES.TemplateLiteral && - node.key.expressions.length === 0 - ) { - return options.allowedNames.includes(node.key.quasis[0].value.raw); - } - if (!node.computed && node.key.type === AST_NODE_TYPES.Identifier) { - return options.allowedNames.includes(node.key.name); + const value = getStaticKeyValue(node, scope); + if (typeof value === 'string') { + return options.allowedNames.includes(value); } } diff --git a/packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts b/packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts index 3803b1298b4a..20a59ad6ed98 100644 --- a/packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts +++ b/packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts @@ -10,6 +10,7 @@ import { isPromiseConstructorLike, isPromiseLike, isReadonlyErrorLike, + isStaticKeyOfValue, } from '../util'; export type MessageIds = 'rejectAnError'; @@ -102,6 +103,8 @@ export default createRule({ callee.property.value === 'reject' : callee.property.name === 'reject'; + isStaticKeyOfValue(); + if ( !rejectMethodCalled || !typeAtLocationIsLikePromise(callee.object) diff --git a/packages/eslint-plugin/src/util/misc.ts b/packages/eslint-plugin/src/util/misc.ts index 9ba55c9d87f7..db14678d5622 100644 --- a/packages/eslint-plugin/src/util/misc.ts +++ b/packages/eslint-plugin/src/util/misc.ts @@ -7,7 +7,9 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as ts from 'typescript'; -import { isParenthesized } from './astUtils'; +import { getStaticValue, isParenthesized } from './astUtils'; +import type { Scope } from '@typescript-eslint/utils/ts-eslint'; +import { getStaticStringValue } from './getStaticStringValue'; const DEFINITION_EXTENSIONS = [ ts.Extension.Dts, @@ -230,6 +232,39 @@ function isParenlessArrowFunction( ); } +type NodeWithKey = + | TSESTree.MemberExpression + | TSESTree.MethodDefinition + | TSESTree.Property + | TSESTree.PropertyDefinition + | TSESTree.TSAbstractMethodDefinition + | TSESTree.TSAbstractPropertyDefinition; +function getStaticKeyValue( + node: NodeWithKey, + scope?: Scope.Scope | undefined, +): string | null { + const key = + node.type === AST_NODE_TYPES.MemberExpression ? node.property : node.key; + return node.computed + ? (getStaticValue(key, scope)?.value as string | null) + : key.type === AST_NODE_TYPES.Literal + ? getStaticStringValue(key) + : 'name' in key + ? key.name + : ''; +} + +/** + * Answers whether the member expression looks like + * `x.memberName`, `x['memberName']`, + * or even `const mn = 'memberName'; x[mn]` (or optional variants thereof). + */ +const isStaticKeyOfValue = ( + memberExpression: NodeWithKey, + value: string, + scope?: Scope.Scope | undefined, +): boolean => value === getStaticKeyValue(memberExpression, scope); + export { arrayGroupByToMap, arraysAreEqual, @@ -238,11 +273,13 @@ export { findFirstResult, formatWordList, getEnumNames, + getStaticKeyValue, getNameFromIndexSignature, getNameFromMember, isDefinitionFile, isRestParameterDeclaration, isParenlessArrowFunction, + isStaticKeyOfValue, MemberNameType, RequireKeys, typeNodeRequiresParentheses, From 32167e1d0954a3c34005934b31010e6b14258884 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Mon, 19 Aug 2024 07:45:29 -0500 Subject: [PATCH 02/13] WIP --- .../eslint-plugin/src/rules/class-methods-use-this.ts | 2 +- .../src/rules/explicit-module-boundary-types.ts | 2 +- .../src/rules/prefer-promise-reject-errors.ts | 9 +-------- packages/eslint-plugin/src/util/misc.ts | 10 +++++----- 4 files changed, 8 insertions(+), 15 deletions(-) diff --git a/packages/eslint-plugin/src/rules/class-methods-use-this.ts b/packages/eslint-plugin/src/rules/class-methods-use-this.ts index 1a22558843d3..e54e57f0cc76 100644 --- a/packages/eslint-plugin/src/rules/class-methods-use-this.ts +++ b/packages/eslint-plugin/src/rules/class-methods-use-this.ts @@ -182,7 +182,7 @@ export default createRule({ const hashIfNeeded = node.key.type === AST_NODE_TYPES.PrivateIdentifier ? '#' : ''; - const name = getStaticKeyValue(node); + const name = getStaticKeyValue(node, context); return !exceptMethods.has(hashIfNeeded + (name ?? '')); } diff --git a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts index 5fffcc76751f..bc7110830b38 100644 --- a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts +++ b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts @@ -268,7 +268,7 @@ export default createRule({ (node.type === AST_NODE_TYPES.Property && node.method) || node.type === AST_NODE_TYPES.PropertyDefinition ) { - const value = getStaticKeyValue(node, scope); + const value = getStaticKeyValue(node, context); if (typeof value === 'string') { return options.allowedNames.includes(value); } diff --git a/packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts b/packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts index 9a5574666dd9..8bb5d8f819bd 100644 --- a/packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts +++ b/packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts @@ -98,15 +98,8 @@ export default createRule({ return; } - const rejectMethodCalled = callee.computed - ? callee.property.type === AST_NODE_TYPES.Literal && - callee.property.value === 'reject' - : callee.property.name === 'reject'; - - isStaticKeyOfValue(); - if ( - !rejectMethodCalled || + !isStaticKeyOfValue(callee, 'reject', context) || !typeAtLocationIsLikePromise(callee.object) ) { return; diff --git a/packages/eslint-plugin/src/util/misc.ts b/packages/eslint-plugin/src/util/misc.ts index 5e8d880691d4..5af0e05c5b0c 100644 --- a/packages/eslint-plugin/src/util/misc.ts +++ b/packages/eslint-plugin/src/util/misc.ts @@ -5,10 +5,10 @@ import { requiresQuoting } from '@typescript-eslint/type-utils'; import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; +import type { RuleContext } from '@typescript-eslint/utils/ts-eslint'; import * as ts from 'typescript'; import { getStaticValue, isParenthesized } from './astUtils'; -import type { Scope } from '@typescript-eslint/utils/ts-eslint'; import { getStaticStringValue } from './getStaticStringValue'; const DEFINITION_EXTENSIONS = [ @@ -243,12 +243,12 @@ type NodeWithKey = | TSESTree.TSAbstractPropertyDefinition; function getStaticKeyValue( node: NodeWithKey, - scope?: Scope.Scope | undefined, + { sourceCode }: RuleContext, ): string | null { const key = node.type === AST_NODE_TYPES.MemberExpression ? node.property : node.key; return node.computed - ? (getStaticValue(key, scope)?.value as string | null) + ? (getStaticValue(key, sourceCode.getScope(node))?.value as string | null) : key.type === AST_NODE_TYPES.Literal ? getStaticStringValue(key) : 'name' in key @@ -264,8 +264,8 @@ function getStaticKeyValue( const isStaticKeyOfValue = ( memberExpression: NodeWithKey, value: string, - scope?: Scope.Scope | undefined, -): boolean => value === getStaticKeyValue(memberExpression, scope); + context: RuleContext, +): boolean => value === getStaticKeyValue(memberExpression, context); export { arrayGroupByToMap, From 7f7d42aa1f394681f978a2044ce786f25b786814 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Tue, 20 Aug 2024 07:01:02 -0500 Subject: [PATCH 03/13] prefer-includes --- packages/eslint-plugin/src/rules/prefer-includes.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-includes.ts b/packages/eslint-plugin/src/rules/prefer-includes.ts index 1a8f705b312f..d970631181d9 100644 --- a/packages/eslint-plugin/src/rules/prefer-includes.ts +++ b/packages/eslint-plugin/src/rules/prefer-includes.ts @@ -8,6 +8,7 @@ import { getConstrainedTypeAtLocation, getParserServices, getStaticValue, + isStaticKeyOfValue, } from '../util'; export default createRule({ @@ -146,6 +147,9 @@ export default createRule({ node: TSESTree.MemberExpression, allowFixing: boolean, ): void { + if (!isStaticKeyOfValue(node, 'indexOf', context)) { + return; + } // Check if the comparison is equivalent to `includes()`. const callNode = node.parent as TSESTree.CallExpression; const compareNode = ( @@ -204,14 +208,14 @@ export default createRule({ return { // a.indexOf(b) !== 1 - "BinaryExpression > CallExpression.left > MemberExpression.callee[property.name='indexOf'][computed=false]"( + 'BinaryExpression > CallExpression.left > MemberExpression'( node: TSESTree.MemberExpression, ): void { checkArrayIndexOf(node, /* allowFixing */ true); }, // a?.indexOf(b) !== 1 - "BinaryExpression > ChainExpression.left > CallExpression > MemberExpression.callee[property.name='indexOf'][computed=false]"( + 'BinaryExpression > ChainExpression.left > CallExpression > MemberExpression'( node: TSESTree.MemberExpression, ): void { checkArrayIndexOf(node, /* allowFixing */ false); From 55c63ba02d60fc390ea19c9e0b3bf1ac3db56d71 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Tue, 20 Aug 2024 07:02:31 -0500 Subject: [PATCH 04/13] prefer-regexp-exec --- packages/eslint-plugin/src/rules/prefer-regexp-exec.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts index b19e0305c7bb..6bbfcb016b73 100644 --- a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts +++ b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts @@ -9,6 +9,7 @@ import { getStaticValue, getTypeName, getWrappingFixer, + isStaticKeyOfValue, } from '../util'; enum ArgumentType { @@ -98,9 +99,12 @@ export default createRule({ } return { - "CallExpression[arguments.length=1] > MemberExpression.callee[property.name='match'][computed=false]"( + 'CallExpression[arguments.length=1] > MemberExpression'( memberNode: TSESTree.MemberExpression, ): void { + if (!isStaticKeyOfValue(memberNode, 'match', context)) { + return; + } const objectNode = memberNode.object; const callNode = memberNode.parent as TSESTree.CallExpression; const [argumentNode] = callNode.arguments; From 748569e423243cc031d01807bb55478d6ae194c1 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Tue, 20 Aug 2024 21:12:47 -0500 Subject: [PATCH 05/13] string-startswith-endswith --- .../src/rules/class-methods-use-this.ts | 4 ++-- .../src/rules/explicit-module-boundary-types.ts | 11 ++++++----- .../eslint-plugin/src/rules/prefer-includes.ts | 4 ++-- .../src/rules/prefer-promise-reject-errors.ts | 4 ++-- .../eslint-plugin/src/rules/prefer-regexp-exec.ts | 4 ++-- .../src/rules/prefer-string-starts-ends-with.ts | 10 ++++++---- packages/eslint-plugin/src/util/misc.ts | 15 +++++++++------ 7 files changed, 29 insertions(+), 23 deletions(-) diff --git a/packages/eslint-plugin/src/rules/class-methods-use-this.ts b/packages/eslint-plugin/src/rules/class-methods-use-this.ts index e54e57f0cc76..6e27a6229f12 100644 --- a/packages/eslint-plugin/src/rules/class-methods-use-this.ts +++ b/packages/eslint-plugin/src/rules/class-methods-use-this.ts @@ -5,7 +5,7 @@ import { createRule, getFunctionHeadLoc, getFunctionNameWithKind, - getStaticKeyValue, + getStaticMemberAccessValue, } from '../util'; type Options = [ @@ -182,7 +182,7 @@ export default createRule({ const hashIfNeeded = node.key.type === AST_NODE_TYPES.PrivateIdentifier ? '#' : ''; - const name = getStaticKeyValue(node, context); + const name = getStaticMemberAccessValue(node, context); return !exceptMethods.has(hashIfNeeded + (name ?? '')); } diff --git a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts index bc7110830b38..31249d20f34f 100644 --- a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts +++ b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts @@ -2,7 +2,7 @@ import { DefinitionType } from '@typescript-eslint/scope-manager'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import { createRule, getStaticKeyValue, isFunction } from '../util'; +import { createRule, isFunction, isStaticMemberAccessOfValue } from '../util'; import type { FunctionExpression, FunctionInfo, @@ -268,10 +268,11 @@ export default createRule({ (node.type === AST_NODE_TYPES.Property && node.method) || node.type === AST_NODE_TYPES.PropertyDefinition ) { - const value = getStaticKeyValue(node, context); - if (typeof value === 'string') { - return options.allowedNames.includes(value); - } + return isStaticMemberAccessOfValue( + node, + context, + ...options.allowedNames, + ); } return false; diff --git a/packages/eslint-plugin/src/rules/prefer-includes.ts b/packages/eslint-plugin/src/rules/prefer-includes.ts index d970631181d9..98a24fcc8917 100644 --- a/packages/eslint-plugin/src/rules/prefer-includes.ts +++ b/packages/eslint-plugin/src/rules/prefer-includes.ts @@ -8,7 +8,7 @@ import { getConstrainedTypeAtLocation, getParserServices, getStaticValue, - isStaticKeyOfValue, + isStaticMemberAccessOfValue, } from '../util'; export default createRule({ @@ -147,7 +147,7 @@ export default createRule({ node: TSESTree.MemberExpression, allowFixing: boolean, ): void { - if (!isStaticKeyOfValue(node, 'indexOf', context)) { + if (!isStaticMemberAccessOfValue(node, context, 'indexOf')) { return; } // Check if the comparison is equivalent to `includes()`. diff --git a/packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts b/packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts index 8bb5d8f819bd..a8fdb39ff526 100644 --- a/packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts +++ b/packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts @@ -10,7 +10,7 @@ import { isPromiseConstructorLike, isPromiseLike, isReadonlyErrorLike, - isStaticKeyOfValue, + isStaticMemberAccessOfValue, } from '../util'; export type MessageIds = 'rejectAnError'; @@ -99,7 +99,7 @@ export default createRule({ } if ( - !isStaticKeyOfValue(callee, 'reject', context) || + !isStaticMemberAccessOfValue(callee, context, 'reject') || !typeAtLocationIsLikePromise(callee.object) ) { return; diff --git a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts index 6bbfcb016b73..9254589a624e 100644 --- a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts +++ b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts @@ -9,7 +9,7 @@ import { getStaticValue, getTypeName, getWrappingFixer, - isStaticKeyOfValue, + isStaticMemberAccessOfValue, } from '../util'; enum ArgumentType { @@ -102,7 +102,7 @@ export default createRule({ 'CallExpression[arguments.length=1] > MemberExpression'( memberNode: TSESTree.MemberExpression, ): void { - if (!isStaticKeyOfValue(memberNode, 'match', context)) { + if (!isStaticMemberAccessOfValue(memberNode, context, 'match')) { return; } const objectNode = memberNode.object; diff --git a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts index f6a22152043c..d65fd9736ad5 100644 --- a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts +++ b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts @@ -9,6 +9,7 @@ import { getStaticValue, getTypeName, isNotClosingParenToken, + isStaticMemberAccessOfValue, nullThrows, NullThrowsReasons, } from '../util'; @@ -580,11 +581,12 @@ export default createRule({ // foo.substring(foo.length - 3) === 'bar' // foo.substring(foo.length - 3, foo.length) === 'bar' [[ - 'BinaryExpression > CallExpression.left > MemberExpression.callee[property.name="slice"][computed=false]', - 'BinaryExpression > CallExpression.left > MemberExpression.callee[property.name="substring"][computed=false]', - 'BinaryExpression > ChainExpression.left > CallExpression > MemberExpression.callee[property.name="slice"][computed=false]', - 'BinaryExpression > ChainExpression.left > CallExpression > MemberExpression.callee[property.name="substring"][computed=false]', + 'BinaryExpression > CallExpression.left > MemberExpression', + 'BinaryExpression > ChainExpression.left > CallExpression > MemberExpression', ].join(', ')](node: TSESTree.MemberExpression): void { + if (!isStaticMemberAccessOfValue(node, context, 'slice', 'substring')) { + return; + } const callNode = getParent(node) as TSESTree.CallExpression; const parentNode = getParent(callNode); diff --git a/packages/eslint-plugin/src/util/misc.ts b/packages/eslint-plugin/src/util/misc.ts index 5af0e05c5b0c..97dcf1e12012 100644 --- a/packages/eslint-plugin/src/util/misc.ts +++ b/packages/eslint-plugin/src/util/misc.ts @@ -241,7 +241,7 @@ type NodeWithKey = | TSESTree.PropertyDefinition | TSESTree.TSAbstractMethodDefinition | TSESTree.TSAbstractPropertyDefinition; -function getStaticKeyValue( +function getStaticMemberAccessValue( node: NodeWithKey, { sourceCode }: RuleContext, ): string | null { @@ -261,11 +261,14 @@ function getStaticKeyValue( * `x.memberName`, `x['memberName']`, * or even `const mn = 'memberName'; x[mn]` (or optional variants thereof). */ -const isStaticKeyOfValue = ( +const isStaticMemberAccessOfValue = ( memberExpression: NodeWithKey, - value: string, context: RuleContext, -): boolean => value === getStaticKeyValue(memberExpression, context); + ...values: string[] +): boolean => + (values as (string | null)[]).includes( + getStaticMemberAccessValue(memberExpression, context), + ); export { arrayGroupByToMap, @@ -275,13 +278,13 @@ export { findFirstResult, formatWordList, getEnumNames, - getStaticKeyValue, + getStaticMemberAccessValue, getNameFromIndexSignature, getNameFromMember, isDefinitionFile, isRestParameterDeclaration, isParenlessArrowFunction, - isStaticKeyOfValue, + isStaticMemberAccessOfValue, MemberNameType, RequireKeys, typeNodeRequiresParentheses, From af02dbe16975232518e9bc7126c228f3c2b48fc5 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Tue, 20 Aug 2024 21:44:43 -0500 Subject: [PATCH 06/13] support template literal --- packages/eslint-plugin/src/util/misc.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin/src/util/misc.ts b/packages/eslint-plugin/src/util/misc.ts index 97dcf1e12012..2c9a2ff7c02e 100644 --- a/packages/eslint-plugin/src/util/misc.ts +++ b/packages/eslint-plugin/src/util/misc.ts @@ -247,13 +247,18 @@ function getStaticMemberAccessValue( ): string | null { const key = node.type === AST_NODE_TYPES.MemberExpression ? node.property : node.key; - return node.computed - ? (getStaticValue(key, sourceCode.getScope(node))?.value as string | null) - : key.type === AST_NODE_TYPES.Literal - ? getStaticStringValue(key) - : 'name' in key - ? key.name - : ''; + if (node.computed) { + return getStaticValue(key, sourceCode.getScope(node))?.value as + | string + | null; + } + const { type } = key; + return type === AST_NODE_TYPES.Literal || + type === AST_NODE_TYPES.TemplateLiteral + ? getStaticStringValue(key) + : 'name' in key + ? key.name + : ''; } /** From cb8c3df55e200c7cd2525a25795ea3c573d09ddf Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Wed, 21 Aug 2024 06:45:05 -0500 Subject: [PATCH 07/13] more places --- .../src/rules/prefer-reduce-type-parameter.ts | 20 ++----------------- .../src/rules/require-array-sort-compare.ts | 8 +++++--- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts b/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts index 7eb781ed0d18..a777e06b5441 100644 --- a/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts +++ b/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts @@ -7,6 +7,7 @@ import { createRule, getConstrainedTypeAtLocation, getParserServices, + isStaticMemberAccessOfValue, isTypeAssertion, } from '../util'; @@ -14,23 +15,6 @@ type MemberExpressionWithCallExpressionParent = TSESTree.MemberExpression & { parent: TSESTree.CallExpression; }; -const getMemberExpressionName = ( - member: TSESTree.MemberExpression, -): string | null => { - if (!member.computed) { - return member.property.name; - } - - if ( - member.property.type === AST_NODE_TYPES.Literal && - typeof member.property.value === 'string' - ) { - return member.property.value; - } - - return null; -}; - export default createRule({ name: 'prefer-reduce-type-parameter', meta: { @@ -67,7 +51,7 @@ export default createRule({ 'CallExpression > MemberExpression.callee'( callee: MemberExpressionWithCallExpressionParent, ): void { - if (getMemberExpressionName(callee) !== 'reduce') { + if (isStaticMemberAccessOfValue(callee, context, 'reduce')) { return; } diff --git a/packages/eslint-plugin/src/rules/require-array-sort-compare.ts b/packages/eslint-plugin/src/rules/require-array-sort-compare.ts index e6f83e800bbb..ec1f1e1fdb15 100644 --- a/packages/eslint-plugin/src/rules/require-array-sort-compare.ts +++ b/packages/eslint-plugin/src/rules/require-array-sort-compare.ts @@ -5,6 +5,7 @@ import { getConstrainedTypeAtLocation, getParserServices, getTypeName, + isStaticMemberAccessOfValue, isTypeArrayTypeOrUnionOfArrayTypes, } from '../util'; @@ -66,6 +67,9 @@ export default createRule({ } function checkSortArgument(callee: TSESTree.MemberExpression): void { + if (!isStaticMemberAccessOfValue(callee, context, 'sort', 'toSorted')) { + return; + } const calleeObjType = getConstrainedTypeAtLocation( services, callee.object, @@ -81,9 +85,7 @@ export default createRule({ } return { - "CallExpression[arguments.length=0] > MemberExpression[property.name='sort'][computed=false]": - checkSortArgument, - "CallExpression[arguments.length=0] > MemberExpression[property.name='toSorted'][computed=false]": + 'CallExpression[arguments.length=0] > MemberExpression': checkSortArgument, }; }, From f19ee06753d0410f5493f51b9db6c59b561d7e6d Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Wed, 21 Aug 2024 07:08:46 -0500 Subject: [PATCH 08/13] more methods --- .../src/rules/no-floating-promises.ts | 87 +++++++------------ .../eslint-plugin/src/rules/prefer-find.ts | 29 +------ 2 files changed, 35 insertions(+), 81 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index e59c32665427..71ae37335238 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -1,14 +1,16 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; +import type { RuleContext } from '@typescript-eslint/utils/ts-eslint'; import * as tsutils from 'ts-api-utils'; import * as ts from 'typescript'; -import type { TypeOrValueSpecifier } from '../util'; +import { getStaticMemberAccessValue, TypeOrValueSpecifier } from '../util'; import { createRule, getOperatorPrecedence, getParserServices, isBuiltinSymbolLike, + isStaticMemberAccessOfValue, OperatorPrecedence, readonlynessOptionsDefaults, readonlynessOptionsSchema, @@ -327,27 +329,38 @@ export default createRule({ // If the outer expression is a call, a `.catch()` or `.then()` with // rejection handler handles the promise. - const catchRejectionHandler = getRejectionHandlerFromCatchCall(node); - if (catchRejectionHandler) { - if (isValidRejectionHandler(catchRejectionHandler)) { - return { isUnhandled: false }; + const { callee } = node; + if (callee.type === AST_NODE_TYPES.MemberExpression) { + const methodName = getStaticMemberAccessValue(callee, context); + const catchRejectionHandler = + methodName === 'catch' && node.arguments.length >= 1 + ? node.arguments[0] + : undefined; + if (catchRejectionHandler) { + if (isValidRejectionHandler(catchRejectionHandler)) { + return { isUnhandled: false }; + } + return { isUnhandled: true, nonFunctionHandler: true }; } - return { isUnhandled: true, nonFunctionHandler: true }; - } - const thenRejectionHandler = getRejectionHandlerFromThenCall(node); - if (thenRejectionHandler) { - if (isValidRejectionHandler(thenRejectionHandler)) { - return { isUnhandled: false }; + const thenRejectionHandler = + methodName === 'then' && node.arguments.length >= 2 + ? node.arguments[1] + : undefined; + if (thenRejectionHandler) { + if (isValidRejectionHandler(thenRejectionHandler)) { + return { isUnhandled: false }; + } + return { isUnhandled: true, nonFunctionHandler: true }; } - return { isUnhandled: true, nonFunctionHandler: true }; - } - // `x.finally()` is transparent to resolution of the promise, so check `x`. - // ("object" in this context is the `x` in `x.finally()`) - const promiseFinallyObject = getObjectFromFinallyCall(node); - if (promiseFinallyObject) { - return isUnhandledPromise(checker, promiseFinallyObject); + // `x.finally()` is transparent to resolution of the promise, so check `x`. + // ("object" in this context is the `x` in `x.finally()`) + const promiseFinallyObject = + methodName === 'finally' ? callee.object : undefined; + if (promiseFinallyObject) { + return isUnhandledPromise(checker, promiseFinallyObject); + } } // All other cases are unhandled. @@ -478,41 +491,3 @@ function isFunctionParam( } return false; } - -function getRejectionHandlerFromCatchCall( - expression: TSESTree.CallExpression, -): TSESTree.CallExpressionArgument | undefined { - if ( - expression.callee.type === AST_NODE_TYPES.MemberExpression && - expression.callee.property.type === AST_NODE_TYPES.Identifier && - expression.callee.property.name === 'catch' && - expression.arguments.length >= 1 - ) { - return expression.arguments[0]; - } - return undefined; -} - -function getRejectionHandlerFromThenCall( - expression: TSESTree.CallExpression, -): TSESTree.CallExpressionArgument | undefined { - if ( - expression.callee.type === AST_NODE_TYPES.MemberExpression && - expression.callee.property.type === AST_NODE_TYPES.Identifier && - expression.callee.property.name === 'then' && - expression.arguments.length >= 2 - ) { - return expression.arguments[1]; - } - return undefined; -} - -function getObjectFromFinallyCall( - expression: TSESTree.CallExpression, -): TSESTree.Expression | undefined { - return expression.callee.type === AST_NODE_TYPES.MemberExpression && - expression.callee.property.type === AST_NODE_TYPES.Identifier && - expression.callee.property.name === 'finally' - ? expression.callee.object - : undefined; -} diff --git a/packages/eslint-plugin/src/rules/prefer-find.ts b/packages/eslint-plugin/src/rules/prefer-find.ts index 587cd5d0f03d..389d4f581378 100644 --- a/packages/eslint-plugin/src/rules/prefer-find.ts +++ b/packages/eslint-plugin/src/rules/prefer-find.ts @@ -1,6 +1,6 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import type { RuleFix, Scope } from '@typescript-eslint/utils/ts-eslint'; +import type { RuleFix } from '@typescript-eslint/utils/ts-eslint'; import * as tsutils from 'ts-api-utils'; import type { Type } from 'typescript'; @@ -9,6 +9,7 @@ import { getConstrainedTypeAtLocation, getParserServices, getStaticValue, + isStaticMemberAccessOfValue, nullThrows, } from '../util'; @@ -89,7 +90,7 @@ export default createRule({ // or the optional chaining variants. if (callee.type === AST_NODE_TYPES.MemberExpression) { const isBracketSyntaxForFilter = callee.computed; - if (isStaticMemberAccessOfValue(callee, 'filter', globalScope)) { + if (isStaticMemberAccessOfValue(callee, context, 'filter')) { const filterNode = callee.property; const filteredObjectType = getConstrainedTypeAtLocation( @@ -162,7 +163,7 @@ export default createRule({ if ( callee.type === AST_NODE_TYPES.MemberExpression && !callee.optional && - isStaticMemberAccessOfValue(callee, 'at', globalScope) + isStaticMemberAccessOfValue(callee, context, 'at') ) { const atArgument = getStaticValue(node.arguments[0], globalScope); if (atArgument != null && isTreatedAsZeroByArrayAt(atArgument.value)) { @@ -321,25 +322,3 @@ export default createRule({ }; }, }); - -/** - * Answers whether the member expression looks like - * `x.memberName`, `x['memberName']`, - * or even `const mn = 'memberName'; x[mn]` (or optional variants thereof). - */ -function isStaticMemberAccessOfValue( - memberExpression: - | TSESTree.MemberExpressionComputedName - | TSESTree.MemberExpressionNonComputedName, - value: string, - scope?: Scope.Scope | undefined, -): boolean { - if (!memberExpression.computed) { - // x.memberName case. - return memberExpression.property.name === value; - } - - // x['memberName'] cases. - const staticValueResult = getStaticValue(memberExpression.property, scope); - return staticValueResult != null && value === staticValueResult.value; -} From 8af0b0443a7bd93b871d172bbde6cb9e57493642 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Wed, 21 Aug 2024 07:14:37 -0500 Subject: [PATCH 09/13] class literal property style --- .../src/rules/class-literal-property-style.ts | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/eslint-plugin/src/rules/class-literal-property-style.ts b/packages/eslint-plugin/src/rules/class-literal-property-style.ts index 816c7803604a..9ff318eccd13 100644 --- a/packages/eslint-plugin/src/rules/class-literal-property-style.ts +++ b/packages/eslint-plugin/src/rules/class-literal-property-style.ts @@ -3,9 +3,11 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, + getStaticMemberAccessValue, getStaticStringValue, isAssignee, isFunction, + isStaticMemberAccessOfValue, nullThrows, } from '../util'; @@ -102,8 +104,8 @@ export default createRule({ return; } - const name = getStringValue(node.key); - if (excludeSet.has(name)) { + const name = getStaticMemberAccessValue(node, context); + if (name && excludeSet.has(name)) { return; } @@ -167,15 +169,17 @@ export default createRule({ return; } - const name = getStringValue(node.key); - - const hasDuplicateKeySetter = node.parent.body.some(element => { - return ( - element.type === AST_NODE_TYPES.MethodDefinition && - element.kind === 'set' && - getStringValue(element.key) === name - ); - }); + const name = getStaticMemberAccessValue(node, context); + + const hasDuplicateKeySetter = + name && + node.parent.body.some(element => { + return ( + element.type === AST_NODE_TYPES.MethodDefinition && + element.kind === 'set' && + isStaticMemberAccessOfValue(element, context, name) + ); + }); if (hasDuplicateKeySetter) { return; } From b5db342a4b4271f97964e072c41c34ed39dea037 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Wed, 21 Aug 2024 07:16:38 -0500 Subject: [PATCH 10/13] use-unknown-in-catch-callback --- .../use-unknown-in-catch-callback-variable.ts | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts b/packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts index 0c9cf63adf2b..394155c865cc 100644 --- a/packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts +++ b/packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts @@ -8,6 +8,7 @@ import type * as ts from 'typescript'; import { createRule, getParserServices, + getStaticMemberAccessValue, getStaticValue, isParenlessArrowFunction, isRestParameterDeclaration, @@ -26,19 +27,6 @@ type MessageIds = const useUnknownMessageBase = 'Prefer the safe `: unknown` for a `{{method}}`{{append}} callback variable.'; -/** - * `x.memberName` => 'memberKey' - * - * `const mk = 'memberKey'; x[mk]` => 'memberKey' - * - * `const mk = 1234; x[mk]` => 1234 - */ -const getStaticMemberAccessKey = ( - { computed, property }: TSESTree.MemberExpression, - scope: Scope, -): { value: unknown } | null => - computed ? getStaticValue(property, scope) : { value: property.name }; - export default createRule<[], MessageIds>({ name: 'use-unknown-in-catch-callback-variable', meta: { @@ -242,9 +230,9 @@ export default createRule<[], MessageIds>({ return; } - const staticMemberAccessKey = getStaticMemberAccessKey( + const staticMemberAccessKey = getStaticMemberAccessValue( callee, - context.sourceCode.getScope(callee), + context, ); if (!staticMemberAccessKey) { return; @@ -259,7 +247,7 @@ export default createRule<[], MessageIds>({ append: string; argIndexToCheck: number; }[] - ).find(({ method }) => staticMemberAccessKey.value === method); + ).find(({ method }) => staticMemberAccessKey === method); if (!promiseMethodInfo) { return; } From 7ad1e451d50d1ae569889a17eca4fa7207c039bf Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Tue, 27 Aug 2024 07:33:33 -0500 Subject: [PATCH 11/13] cleanup --- .../eslint-plugin/src/rules/class-literal-property-style.ts | 4 ---- packages/eslint-plugin/src/rules/no-floating-promises.ts | 5 ++--- .../eslint-plugin/src/rules/prefer-reduce-type-parameter.ts | 3 +-- .../src/rules/use-unknown-in-catch-callback-variable.ts | 2 -- 4 files changed, 3 insertions(+), 11 deletions(-) diff --git a/packages/eslint-plugin/src/rules/class-literal-property-style.ts b/packages/eslint-plugin/src/rules/class-literal-property-style.ts index 9ff318eccd13..28d4a5cc78ba 100644 --- a/packages/eslint-plugin/src/rules/class-literal-property-style.ts +++ b/packages/eslint-plugin/src/rules/class-literal-property-style.ts @@ -81,10 +81,6 @@ export default createRule({ create(context, [style]) { const propertiesInfoStack: PropertiesInfo[] = []; - function getStringValue(node: TSESTree.Node): string { - return getStaticStringValue(node) ?? context.sourceCode.getText(node); - } - function enterClassBody(): void { propertiesInfoStack.push({ properties: [], diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index 71ae37335238..d21a2348e263 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -1,16 +1,15 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import type { RuleContext } from '@typescript-eslint/utils/ts-eslint'; import * as tsutils from 'ts-api-utils'; import * as ts from 'typescript'; -import { getStaticMemberAccessValue, TypeOrValueSpecifier } from '../util'; +import type { TypeOrValueSpecifier } from '../util'; import { createRule, getOperatorPrecedence, getParserServices, + getStaticMemberAccessValue, isBuiltinSymbolLike, - isStaticMemberAccessOfValue, OperatorPrecedence, readonlynessOptionsDefaults, readonlynessOptionsSchema, diff --git a/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts b/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts index a777e06b5441..a8c820a7b370 100644 --- a/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts +++ b/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts @@ -1,5 +1,4 @@ import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; import type * as ts from 'typescript'; @@ -51,7 +50,7 @@ export default createRule({ 'CallExpression > MemberExpression.callee'( callee: MemberExpressionWithCallExpressionParent, ): void { - if (isStaticMemberAccessOfValue(callee, context, 'reduce')) { + if (!isStaticMemberAccessOfValue(callee, context, 'reduce')) { return; } diff --git a/packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts b/packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts index 394155c865cc..a6060cd3f00e 100644 --- a/packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts +++ b/packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts @@ -1,4 +1,3 @@ -import type { Scope } from '@typescript-eslint/scope-manager'; import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import type { ReportDescriptor } from '@typescript-eslint/utils/ts-eslint'; @@ -9,7 +8,6 @@ import { createRule, getParserServices, getStaticMemberAccessValue, - getStaticValue, isParenlessArrowFunction, isRestParameterDeclaration, nullThrows, From d78bec42f73212c01bc8d4bca4bf6599fa455f9b Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Mon, 2 Sep 2024 10:21:56 -0500 Subject: [PATCH 12/13] fix type assertion --- packages/eslint-plugin/src/util/misc.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/util/misc.ts b/packages/eslint-plugin/src/util/misc.ts index 7c44055d7037..00a42eeb0a87 100644 --- a/packages/eslint-plugin/src/util/misc.ts +++ b/packages/eslint-plugin/src/util/misc.ts @@ -244,13 +244,14 @@ type NodeWithKey = function getStaticMemberAccessValue( node: NodeWithKey, { sourceCode }: RuleContext, -): string | null { +): string | null | undefined { const key = node.type === AST_NODE_TYPES.MemberExpression ? node.property : node.key; if (node.computed) { return getStaticValue(key, sourceCode.getScope(node))?.value as | string - | null; + | null + | undefined; } const { type } = key; return type === AST_NODE_TYPES.Literal || @@ -271,7 +272,7 @@ const isStaticMemberAccessOfValue = ( context: RuleContext, ...values: string[] ): boolean => - (values as (string | null)[]).includes( + (values as (string | null | undefined)[]).includes( getStaticMemberAccessValue(memberExpression, context), ); From b628ce5aa6622485770e70d8c4e9e242d605b344 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Mon, 2 Sep 2024 11:37:55 -0500 Subject: [PATCH 13/13] remove unnecesssary checks --- packages/eslint-plugin/src/util/misc.ts | 28 +++++++++++-------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/packages/eslint-plugin/src/util/misc.ts b/packages/eslint-plugin/src/util/misc.ts index 00a42eeb0a87..aeed9f96d039 100644 --- a/packages/eslint-plugin/src/util/misc.ts +++ b/packages/eslint-plugin/src/util/misc.ts @@ -1,7 +1,6 @@ /** * @fileoverview Really small utility functions that didn't deserve their own files */ - import { requiresQuoting } from '@typescript-eslint/type-utils'; import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; @@ -9,7 +8,6 @@ import type { RuleContext } from '@typescript-eslint/utils/ts-eslint'; import * as ts from 'typescript'; import { getStaticValue, isParenthesized } from './astUtils'; -import { getStaticStringValue } from './getStaticStringValue'; const DEFINITION_EXTENSIONS = [ ts.Extension.Dts, @@ -244,22 +242,20 @@ type NodeWithKey = function getStaticMemberAccessValue( node: NodeWithKey, { sourceCode }: RuleContext, -): string | null | undefined { +): string | undefined { const key = node.type === AST_NODE_TYPES.MemberExpression ? node.property : node.key; - if (node.computed) { - return getStaticValue(key, sourceCode.getScope(node))?.value as - | string - | null - | undefined; + if (!node.computed) { + return key.type === AST_NODE_TYPES.Literal + ? `${key.value}` + : (key as TSESTree.Identifier | TSESTree.PrivateIdentifier).name; } - const { type } = key; - return type === AST_NODE_TYPES.Literal || - type === AST_NODE_TYPES.TemplateLiteral - ? getStaticStringValue(key) - : 'name' in key - ? key.name - : ''; + const value = getStaticValue(key, sourceCode.getScope(node))?.value as + | string + | number + | null + | undefined; + return value == null ? undefined : `${value}`; } /** @@ -272,7 +268,7 @@ const isStaticMemberAccessOfValue = ( context: RuleContext, ...values: string[] ): boolean => - (values as (string | null | undefined)[]).includes( + (values as (string | undefined)[]).includes( getStaticMemberAccessValue(memberExpression, context), );