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 28d4a5cc78ba..9f02142f4d6e 100644 --- a/packages/eslint-plugin/src/rules/class-literal-property-style.ts +++ b/packages/eslint-plugin/src/rules/class-literal-property-style.ts @@ -4,7 +4,6 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, getStaticMemberAccessValue, - getStaticStringValue, isAssignee, isFunction, isStaticMemberAccessOfValue, @@ -25,7 +24,7 @@ interface NodeWithModifiers { interface PropertiesInfo { properties: TSESTree.PropertyDefinition[]; - excludeSet: Set; + excludeSet: Set; } const printNodeModifiers = ( @@ -132,9 +131,7 @@ export default createRule({ const { excludeSet } = propertiesInfoStack[propertiesInfoStack.length - 1]; - const name = - getStaticStringValue(node.property) ?? - context.sourceCode.getText(node.property); + const name = getStaticMemberAccessValue(node, context); if (name) { excludeSet.add(name); 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 6e27a6229f12..4b369d7a51be 100644 --- a/packages/eslint-plugin/src/rules/class-methods-use-this.ts +++ b/packages/eslint-plugin/src/rules/class-methods-use-this.ts @@ -184,7 +184,9 @@ export default createRule({ node.key.type === AST_NODE_TYPES.PrivateIdentifier ? '#' : ''; const name = getStaticMemberAccessValue(node, context); - return !exceptMethods.has(hashIfNeeded + (name ?? '')); + return ( + typeof name !== 'string' || !exceptMethods.has(hashIfNeeded + name) + ); } /** diff --git a/packages/eslint-plugin/src/util/isArrayMethodCallWithPredicate.ts b/packages/eslint-plugin/src/util/isArrayMethodCallWithPredicate.ts index 746e9003722c..39e46d9dda39 100644 --- a/packages/eslint-plugin/src/util/isArrayMethodCallWithPredicate.ts +++ b/packages/eslint-plugin/src/util/isArrayMethodCallWithPredicate.ts @@ -9,7 +9,7 @@ import * as tsutils from 'ts-api-utils'; import { getStaticMemberAccessValue } from './misc'; -const ARRAY_PREDICATE_FUNCTIONS = new Set([ +const ARRAY_PREDICATE_FUNCTIONS = new Set([ 'filter', 'find', 'findIndex', @@ -30,7 +30,7 @@ export function isArrayMethodCallWithPredicate( const staticAccessValue = getStaticMemberAccessValue(node.callee, context); - if (!staticAccessValue || !ARRAY_PREDICATE_FUNCTIONS.has(staticAccessValue)) { + if (!ARRAY_PREDICATE_FUNCTIONS.has(staticAccessValue)) { return false; } diff --git a/packages/eslint-plugin/src/util/misc.ts b/packages/eslint-plugin/src/util/misc.ts index aeed9f96d039..41933dc551c2 100644 --- a/packages/eslint-plugin/src/util/misc.ts +++ b/packages/eslint-plugin/src/util/misc.ts @@ -239,36 +239,85 @@ type NodeWithKey = | TSESTree.PropertyDefinition | TSESTree.TSAbstractMethodDefinition | TSESTree.TSAbstractPropertyDefinition; + +/** + * Gets a member being accessed or declared if its value can be determined statically, and + * resolves it to the string or symbol value that will be used as the actual member + * access key at runtime. Otherwise, returns `undefined`. + * + * ```ts + * x.member // returns 'member' + * ^^^^^^^^ + * + * x?.member // returns 'member' (optional chaining is treated the same) + * ^^^^^^^^^ + * + * x['value'] // returns 'value' + * ^^^^^^^^^^ + * + * x[Math.random()] // returns undefined (not a static value) + * ^^^^^^^^^^^^^^^^ + * + * arr[0] // returns '0' (NOT 0) + * ^^^^^^ + * + * arr[0n] // returns '0' (NOT 0n) + * ^^^^^^^ + * + * const s = Symbol.for('symbolName') + * x[s] // returns `Symbol.for('symbolName')` (since it's a static/global symbol) + * ^^^^ + * + * const us = Symbol('symbolName') + * x[us] // returns undefined (since it's a unique symbol, so not statically analyzable) + * ^^^^^ + * + * var object = { + * 1234: '4567', // returns '1234' (NOT 1234) + * ^^^^^^^^^^^^ + * method() { } // returns 'method' + * ^^^^^^^^^^^^ + * } + * + * class WithMembers { + * foo: string // returns 'foo' + * ^^^^^^^^^^^ + * } + * ``` + */ function getStaticMemberAccessValue( node: NodeWithKey, { sourceCode }: RuleContext, -): string | undefined { +): string | symbol | undefined { const key = node.type === AST_NODE_TYPES.MemberExpression ? node.property : node.key; - if (!node.computed) { - return key.type === AST_NODE_TYPES.Literal - ? `${key.value}` - : (key as TSESTree.Identifier | TSESTree.PrivateIdentifier).name; + const { type } = key; + if ( + !node.computed && + (type === AST_NODE_TYPES.Identifier || + type === AST_NODE_TYPES.PrivateIdentifier) + ) { + return key.name; + } + const result = getStaticValue(key, sourceCode.getScope(node)); + if (!result) { + return undefined; } - const value = getStaticValue(key, sourceCode.getScope(node))?.value as - | string - | number - | null - | undefined; - return value == null ? undefined : `${value}`; + const { value } = result; + return typeof value === 'symbol' ? value : String(value); } /** * Answers whether the member expression looks like - * `x.memberName`, `x['memberName']`, - * or even `const mn = 'memberName'; x[mn]` (or optional variants thereof). + * `x.value`, `x['value']`, + * or even `const v = 'value'; x[v]` (or optional variants thereof). */ const isStaticMemberAccessOfValue = ( memberExpression: NodeWithKey, context: RuleContext, - ...values: string[] + ...values: (string | symbol)[] ): boolean => - (values as (string | undefined)[]).includes( + (values as (string | symbol | undefined)[]).includes( getStaticMemberAccessValue(memberExpression, context), ); diff --git a/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts b/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts index eda213576ff8..3bff869ae8b1 100644 --- a/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts +++ b/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts @@ -407,6 +407,7 @@ export class Test { 'method'() {} ['prop']() {} [\`prop\`]() {} + [null]() {} [\`\${v}\`](): void {} foo = () => { @@ -416,7 +417,7 @@ export class Test { `, options: [ { - allowedNames: ['prop', 'method', 'foo'], + allowedNames: ['prop', 'method', 'null', 'foo'], }, ], }, diff --git a/packages/eslint-plugin/tests/rules/prefer-promise-reject-errors.test.ts b/packages/eslint-plugin/tests/rules/prefer-promise-reject-errors.test.ts index d890886eff15..e78575881255 100644 --- a/packages/eslint-plugin/tests/rules/prefer-promise-reject-errors.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-promise-reject-errors.test.ts @@ -266,6 +266,15 @@ ruleTester.run('prefer-promise-reject-errors', rule, { declare const foo: PromiseConstructor; foo.reject(new Error()); `, + 'console[Symbol.iterator]();', + ` + class A { + a = []; + [Symbol.iterator]() { + return this.a[Symbol.iterator](); + } + } + `, ], invalid: [ {