From 845839aa0cb2152494319553f48f37a4960a9b5a Mon Sep 17 00:00:00 2001 From: undsoft Date: Fri, 28 Mar 2025 21:17:32 +0100 Subject: [PATCH 1/5] fix(eslint-plugin): [no-deprecated] adds support for string literal member access (#10958) --- .../eslint-plugin/src/rules/no-deprecated.ts | 31 +- .../tests/rules/no-deprecated.test.ts | 378 ++++++++++++++++++ 2 files changed, 397 insertions(+), 12 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-deprecated.ts b/packages/eslint-plugin/src/rules/no-deprecated.ts index 94d7a5ed7262..7c2c0bbd1546 100644 --- a/packages/eslint-plugin/src/rules/no-deprecated.ts +++ b/packages/eslint-plugin/src/rules/no-deprecated.ts @@ -10,13 +10,14 @@ import { createRule, getParserServices, nullThrows, - typeOrValueSpecifiersSchema, typeMatchesSomeSpecifier, + typeOrValueSpecifiersSchema, } from '../util'; type IdentifierLike = | TSESTree.Identifier | TSESTree.JSXIdentifier + | TSESTree.Literal | TSESTree.PrivateIdentifier | TSESTree.Super; @@ -209,13 +210,9 @@ export default createRule({ return displayParts ? ts.displayPartsToString(displayParts) : ''; } - type CallLikeNode = - | TSESTree.CallExpression - | TSESTree.JSXOpeningElement - | TSESTree.NewExpression - | TSESTree.TaggedTemplateExpression; + type CalleeNode = TSESTree.Expression | TSESTree.JSXTagNameExpression; - function isNodeCalleeOfParent(node: TSESTree.Node): node is CallLikeNode { + function isNodeCalleeOfParent(node: TSESTree.Node): node is CalleeNode { switch (node.parent?.type) { case AST_NODE_TYPES.NewExpression: case AST_NODE_TYPES.CallExpression: @@ -232,7 +229,7 @@ export default createRule({ } } - function getCallLikeNode(node: TSESTree.Node): CallLikeNode | undefined { + function getCallLikeNode(node: TSESTree.Node): CalleeNode | undefined { let callee = node; while ( @@ -245,7 +242,7 @@ export default createRule({ return isNodeCalleeOfParent(callee) ? callee : undefined; } - function getCallLikeDeprecation(node: CallLikeNode): string | undefined { + function getCallLikeDeprecation(node: CalleeNode): string | undefined { const tsNode = services.esTreeNodeToTSNodeMap.get(node.parent); // If the node is a direct function call, we look for its signature. @@ -254,7 +251,10 @@ export default createRule({ 'Expected call like node to have signature', ); - const symbol = services.getSymbolAtLocation(node); + const nodeToFind = + node.type === AST_NODE_TYPES.MemberExpression ? node.property : node; + + const symbol = services.getSymbolAtLocation(nodeToFind); const aliasedSymbol = symbol != null && tsutils.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias) ? checker.getAliasedSymbol(symbol) @@ -334,14 +334,16 @@ export default createRule({ if ( node.parent.type === AST_NODE_TYPES.JSXAttribute && - node.type !== AST_NODE_TYPES.Super + node.type !== AST_NODE_TYPES.Super && + node.type !== AST_NODE_TYPES.Literal ) { return getJSXAttributeDeprecation(node.parent.parent, node.name); } if ( node.parent.type === AST_NODE_TYPES.Property && - node.type !== AST_NODE_TYPES.Super + node.type !== AST_NODE_TYPES.Super && + node.type !== AST_NODE_TYPES.Literal ) { const property = services .getTypeAtLocation(node.parent.parent) @@ -401,6 +403,7 @@ export default createRule({ checkIdentifier(node); } }, + 'MemberExpression > Literal': checkIdentifier, PrivateIdentifier: checkIdentifier, Super: checkIdentifier, }; @@ -416,5 +419,9 @@ function getReportedNodeName(node: IdentifierLike): string { return `#${node.name}`; } + if (node.type === AST_NODE_TYPES.Literal) { + return String(node.value); + } + return node.name; } diff --git a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts index 6469c0fe5c23..8c31d2f46fb8 100644 --- a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts +++ b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts @@ -45,8 +45,32 @@ ruleTester.run('no-deprecated', rule, { /** @deprecated */ c: 2, }; + a['b']; + `, + ` + const a = { + b: 1, + /** @deprecated */ c: 2, + }; + + a['b' + 'c']; + `, + ` + const a = { + b: 1, + /** @deprecated */ c: 2, + }; + a?.b; `, + ` + const a = { + b: 1, + /** @deprecated */ c: 2, + }; + + a?.['b']; + `, ` declare const a: { b: 1; @@ -55,6 +79,14 @@ ruleTester.run('no-deprecated', rule, { a.b; `, + ` + declare const a: { + b: 1; + /** @deprecated */ c: 2; + }; + + a['b']; + `, ` class A { b: 1; @@ -63,6 +95,32 @@ ruleTester.run('no-deprecated', rule, { new A().b; `, + ` + class A { + b: 1; + /** @deprecated */ c: 2; + } + + new A()['b']; + `, + ` + class A { + c: 1; + } + class B { + /** @deprecated */ c: 2; + } + + new A()['c']; + `, + ` + class A { + b: () => {}; + /** @deprecated */ c: () => {}; + } + + new A()['b'](); + `, ` class A { accessor b: 1; @@ -71,6 +129,14 @@ ruleTester.run('no-deprecated', rule, { new A().b; `, + ` + class A { + accessor b: 1; + /** @deprecated */ accessor c: 2; + } + + new A()['b']; + `, ` declare class A { /** @deprecated */ @@ -80,6 +146,15 @@ ruleTester.run('no-deprecated', rule, { A.c; `, + ` + declare class A { + /** @deprecated */ + static b: string; + static c: string; + } + + A['c']; + `, ` declare class A { /** @deprecated */ @@ -89,6 +164,15 @@ ruleTester.run('no-deprecated', rule, { A.c; `, + ` + declare class A { + /** @deprecated */ + static accessor b: string; + static accessor c: string; + } + + A['c']; + `, ` namespace A { /** @deprecated */ @@ -98,6 +182,15 @@ ruleTester.run('no-deprecated', rule, { A.c; `, + ` + namespace A { + /** @deprecated */ + export const b = ''; + export const c = ''; + } + + A['c']; + `, ` enum A { /** @deprecated */ @@ -107,6 +200,15 @@ ruleTester.run('no-deprecated', rule, { A.c; `, + ` + enum A { + /** @deprecated */ + b = 'b', + c = 'c', + } + + A['c']; + `, ` function a(value: 'b' | undefined): void; /** @deprecated */ @@ -609,6 +711,22 @@ exists('/foo'); }, ], }, + { + code: ` + /** @deprecated */ const a = { b: 1 }; + console.log(a['b']); + `, + errors: [ + { + column: 21, + data: { name: 'a' }, + endColumn: 22, + endLine: 3, + line: 3, + messageId: 'deprecated', + }, + ], + }, { code: ` /** @deprecated */ const a = { b: 1 }; @@ -625,6 +743,22 @@ exists('/foo'); }, ], }, + { + code: ` + /** @deprecated */ const a = { b: 1 }; + console.log(a?.['b']); + `, + errors: [ + { + column: 21, + data: { name: 'a' }, + endColumn: 22, + endLine: 3, + line: 3, + messageId: 'deprecated', + }, + ], + }, { code: ` /** @deprecated */ const a = { b: { c: 1 } }; @@ -673,6 +807,22 @@ exists('/foo'); }, ], }, + { + code: ` + /** @deprecated */ const a = { b: { c: 1 } }; + a?.['b']?.['c']; + `, + errors: [ + { + column: 9, + data: { name: 'a' }, + endColumn: 10, + endLine: 3, + line: 3, + messageId: 'deprecated', + }, + ], + }, { code: ` const a = { @@ -691,6 +841,24 @@ exists('/foo'); }, ], }, + { + code: ` + const a = { + /** @deprecated */ b: { c: 1 }, + }; + a['b']['c']; + `, + errors: [ + { + column: 11, + data: { name: 'b' }, + endColumn: 14, + endLine: 5, + line: 5, + messageId: 'deprecated', + }, + ], + }, { code: ` declare const a: { @@ -1090,6 +1258,28 @@ exists('/foo'); declare const a: A; + a['b']; + `, + errors: [ + { + column: 11, + data: { name: 'b' }, + endColumn: 14, + endLine: 9, + line: 9, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + declare class A { + /** @deprecated */ + b: () => string; + } + + declare const a: A; + a.b(); `, errors: [ @@ -1103,6 +1293,28 @@ exists('/foo'); }, ], }, + { + code: ` + declare class A { + /** @deprecated */ + b: () => string; + } + + declare const a: A; + + a['b'](); + `, + errors: [ + { + column: 11, + data: { name: 'b' }, + endColumn: 14, + endLine: 9, + line: 9, + messageId: 'deprecated', + }, + ], + }, { code: ` interface A { @@ -1125,6 +1337,28 @@ exists('/foo'); }, ], }, + { + code: ` + interface A { + /** @deprecated */ + b: () => string; + } + + declare const a: A; + + a['b'](); + `, + errors: [ + { + column: 11, + data: { name: 'b' }, + endColumn: 14, + endLine: 9, + line: 9, + messageId: 'deprecated', + }, + ], + }, { code: ` class A { @@ -1192,6 +1426,26 @@ exists('/foo'); }, ], }, + { + code: ` + declare class A { + /** @deprecated */ + static b: string; + } + + A['b']; + `, + errors: [ + { + column: 11, + data: { name: 'b' }, + endColumn: 14, + endLine: 7, + line: 7, + messageId: 'deprecated', + }, + ], + }, { code: ` declare const a: { @@ -1212,6 +1466,26 @@ exists('/foo'); }, ], }, + { + code: ` + declare const a: { + /** @deprecated */ + b: string; + }; + + a['b']; + `, + errors: [ + { + column: 11, + data: { name: 'b' }, + endColumn: 14, + endLine: 7, + line: 7, + messageId: 'deprecated', + }, + ], + }, { code: ` interface A { @@ -1384,6 +1658,26 @@ exists('/foo'); }, ], }, + { + code: ` + namespace A { + /** @deprecated */ + export const b = ''; + } + + A['b']; + `, + errors: [ + { + column: 11, + data: { name: 'b' }, + endColumn: 14, + endLine: 7, + line: 7, + messageId: 'deprecated', + }, + ], + }, { code: ` export namespace A { @@ -1424,6 +1718,26 @@ exists('/foo'); }, ], }, + { + code: ` + namespace A { + /** @deprecated */ + export function b() {} + } + + A['b'](); + `, + errors: [ + { + column: 11, + data: { name: 'b' }, + endColumn: 14, + endLine: 7, + line: 7, + messageId: 'deprecated', + }, + ], + }, { code: ` namespace assert { @@ -1510,6 +1824,26 @@ exists('/foo'); }, ], }, + { + code: ` + enum A { + /** @deprecated */ + a, + } + + A['a']; + `, + errors: [ + { + column: 11, + data: { name: 'a' }, + endColumn: 14, + endLine: 7, + line: 7, + messageId: 'deprecated', + }, + ], + }, { code: ` /** @deprecated */ @@ -2814,6 +3148,28 @@ class B extends A { declare const a: A; + a['b']; + `, + errors: [ + { + column: 11, + data: { name: 'b' }, + endColumn: 14, + endLine: 9, + line: 9, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + declare class A { + /** @deprecated */ + accessor b: () => string; + } + + declare const a: A; + a.b(); `, errors: [ @@ -2827,6 +3183,28 @@ class B extends A { }, ], }, + { + code: ` + declare class A { + /** @deprecated */ + accessor b: () => string; + } + + declare const a: A; + + a['b'](); + `, + errors: [ + { + column: 11, + data: { name: 'b' }, + endColumn: 14, + endLine: 9, + line: 9, + messageId: 'deprecated', + }, + ], + }, { code: ` class A { From 82f748f78a7ca1d2b10e4f113bd450a5f740e5da Mon Sep 17 00:00:00 2001 From: undsoft Date: Fri, 4 Apr 2025 16:00:46 +0200 Subject: [PATCH 2/5] fix(eslint-plugin): [no-deprecated] adds support for computed member access (#10958) --- .../eslint-plugin/src/rules/no-deprecated.ts | 82 +++++- .../tests/rules/no-deprecated.test.ts | 258 ++++++++++++++++++ 2 files changed, 325 insertions(+), 15 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-deprecated.ts b/packages/eslint-plugin/src/rules/no-deprecated.ts index 7c2c0bbd1546..3eea60e2a707 100644 --- a/packages/eslint-plugin/src/rules/no-deprecated.ts +++ b/packages/eslint-plugin/src/rules/no-deprecated.ts @@ -1,25 +1,30 @@ -import type { TSESTree } from '@typescript-eslint/utils'; - -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; import * as ts from 'typescript'; -import type { TypeOrValueSpecifier } from '../util'; - import { createRule, getParserServices, nullThrows, typeMatchesSomeSpecifier, + TypeOrValueSpecifier, typeOrValueSpecifiersSchema, } from '../util'; +import { getPropertyName } from '@typescript-eslint/utils/ast-utils'; +import { RuleContext } from '@typescript-eslint/utils/ts-eslint'; type IdentifierLike = | TSESTree.Identifier | TSESTree.JSXIdentifier | TSESTree.Literal | TSESTree.PrivateIdentifier - | TSESTree.Super; + | TSESTree.Super + | TSESTree.TemplateLiteral; + +type IdentifierInComputedProperty = + | TSESTree.Identifier + | TSESTree.Literal + | TSESTree.TemplateLiteral; type MessageIds = 'deprecated' | 'deprecatedWithReason'; @@ -189,6 +194,19 @@ export default createRule({ } } + function isInComputedProperty( + node: IdentifierLike, + ): node is IdentifierInComputedProperty { + return ( + node.parent.type === AST_NODE_TYPES.MemberExpression && + node.parent.computed && + node === node.parent.property && + (node.type === AST_NODE_TYPES.Literal || + node.type === AST_NODE_TYPES.TemplateLiteral || + node.type === AST_NODE_TYPES.Identifier) + ); + } + function getJsDocDeprecation( symbol: ts.Signature | ts.Symbol | undefined, ): string | undefined { @@ -210,6 +228,22 @@ export default createRule({ return displayParts ? ts.displayPartsToString(displayParts) : ''; } + function getComputedPropertyDeprecation( + node: TSESTree.MemberExpression, + ): string | undefined { + const propertyName = getPropertyName( + node, + context.sourceCode.getScope(node), + ); + if (!propertyName) { + return undefined; + } + + const objectType = services.getTypeAtLocation(node.object); + const property = objectType.getProperty(propertyName); + return getJsDocDeprecation(property); + } + type CalleeNode = TSESTree.Expression | TSESTree.JSXTagNameExpression; function isNodeCalleeOfParent(node: TSESTree.Node): node is CalleeNode { @@ -251,10 +285,11 @@ export default createRule({ 'Expected call like node to have signature', ); - const nodeToFind = - node.type === AST_NODE_TYPES.MemberExpression ? node.property : node; + if (node.type === AST_NODE_TYPES.MemberExpression && node.computed) { + return getComputedPropertyDeprecation(node); + } - const symbol = services.getSymbolAtLocation(nodeToFind); + const symbol = services.getSymbolAtLocation(node); const aliasedSymbol = symbol != null && tsutils.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias) ? checker.getAliasedSymbol(symbol) @@ -332,18 +367,22 @@ export default createRule({ return getCallLikeDeprecation(callLikeNode); } + if (isInComputedProperty(node)) { + return getComputedPropertyDeprecation( + node.parent as TSESTree.MemberExpression, + ); + } + if ( node.parent.type === AST_NODE_TYPES.JSXAttribute && - node.type !== AST_NODE_TYPES.Super && - node.type !== AST_NODE_TYPES.Literal + node.type !== AST_NODE_TYPES.Super ) { return getJSXAttributeDeprecation(node.parent.parent, node.name); } if ( node.parent.type === AST_NODE_TYPES.Property && - node.type !== AST_NODE_TYPES.Super && - node.type !== AST_NODE_TYPES.Literal + node.type !== AST_NODE_TYPES.Super ) { const property = services .getTypeAtLocation(node.parent.parent) @@ -380,7 +419,7 @@ export default createRule({ return; } - const name = getReportedNodeName(node); + const name = getReportedNodeName(node, context); context.report({ ...(reason @@ -404,13 +443,17 @@ export default createRule({ } }, 'MemberExpression > Literal': checkIdentifier, + 'MemberExpression > TemplateLiteral': checkIdentifier, PrivateIdentifier: checkIdentifier, Super: checkIdentifier, }; }, }); -function getReportedNodeName(node: IdentifierLike): string { +function getReportedNodeName( + node: IdentifierLike, + context: Readonly>, +): string { if (node.type === AST_NODE_TYPES.Super) { return 'super'; } @@ -423,5 +466,14 @@ function getReportedNodeName(node: IdentifierLike): string { return String(node.value); } + if (node.type === AST_NODE_TYPES.TemplateLiteral) { + return ( + getPropertyName( + node.parent as TSESTree.MemberExpression, + context.sourceCode.getScope(node), + ) || '' + ); + } + return node.name; } diff --git a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts index 8c31d2f46fb8..5144a1afa9c4 100644 --- a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts +++ b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts @@ -61,6 +61,16 @@ ruleTester.run('no-deprecated', rule, { /** @deprecated */ c: 2, }; + const key = 'b'; + + a[key]; + `, + ` + const a = { + b: 1, + /** @deprecated */ c: 2, + }; + a?.b; `, ` @@ -87,6 +97,44 @@ ruleTester.run('no-deprecated', rule, { a['b']; `, + ` + declare const a: { + b: 1; + /** @deprecated */ c: 2; + }; + + a[\`\${'b'}\`]; + `, + ` + declare const a: { + b: 1; + /** @deprecated */ c: 2; + }; + + const key = 'b'; + + a[\`\${key}\`]; + `, + ` + declare const a: { + /** @deprecated */ c: 1; + cc: 2; + }; + + const key = 'c'; + + a[\`\${key + key}\`]; + `, + ` + declare const a: { + /** @deprecated */ c: 1; + cc: 2; + }; + + const key = 'c'; + + a[\`\${key}\${key}\`]; + `, ` class A { b: 1; @@ -103,6 +151,15 @@ ruleTester.run('no-deprecated', rule, { new A()['b']; `, + ` + class A { + b: 1; + /** @deprecated */ c: 2; + } + const key = 'b'; + + new A()[b]; + `, ` class A { c: 1; @@ -841,6 +898,25 @@ exists('/foo'); }, ], }, + { + code: ` + const a = { + /** @deprecated */ b: 1, + }; + const key = 'b'; + a[key]; + `, + errors: [ + { + column: 11, + data: { name: 'key' }, + endColumn: 14, + endLine: 6, + line: 6, + messageId: 'deprecated', + }, + ], + }, { code: ` const a = { @@ -1212,6 +1288,52 @@ exists('/foo'); b(): string; } + declare const a: A; + const key = 'b'; + + a[key]; + `, + errors: [ + { + column: 11, + data: { name: 'key' }, + endColumn: 14, + endLine: 10, + line: 10, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + declare class A { + /** @deprecated */ + b(): string; + } + + declare const a: A; + const key = 'b'; + + a[key](); + `, + errors: [ + { + column: 11, + data: { name: 'key' }, + endColumn: 14, + endLine: 10, + line: 10, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + declare class A { + /** @deprecated */ + b(): string; + } + declare const a: A; a.b(); @@ -1271,6 +1393,74 @@ exists('/foo'); }, ], }, + { + code: ` + declare class A { + /** @deprecated */ + b(): string; + } + + declare const a: A; + const key = 'b'; + + a[\`\${key}\`]; + `, + errors: [ + { + column: 11, + data: { name: 'b' }, + endColumn: 19, + endLine: 10, + line: 10, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + declare class A { + /** @deprecated */ + computed(): string; + } + + declare const a: A; + const k1 = 'comp'; + const k2 = 'uted'; + + a[\`\${k1}\${k2}\`]; + `, + errors: [ + { + column: 11, + data: { name: 'computed' }, + endColumn: 23, + endLine: 11, + line: 11, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + declare class A { + /** @deprecated */ + b(): string; + } + + declare const a: A; + const c = \`\${a.b}\`; + `, + errors: [ + { + column: 24, + data: { name: 'b' }, + endColumn: 25, + endLine: 8, + line: 8, + messageId: 'deprecated', + }, + ], + }, { code: ` declare class A { @@ -1344,6 +1534,52 @@ exists('/foo'); b: () => string; } + declare const a: A; + const key = 'b'; + + a[key]; + `, + errors: [ + { + column: 11, + data: { name: 'key' }, + endColumn: 14, + endLine: 10, + line: 10, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + interface A { + /** @deprecated */ + b: () => string; + } + + declare const a: A; + const key = 'b'; + + a[key](); + `, + errors: [ + { + column: 11, + data: { name: 'key' }, + endColumn: 14, + endLine: 10, + line: 10, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + interface A { + /** @deprecated */ + b: () => string; + } + declare const a: A; a['b'](); @@ -1831,6 +2067,28 @@ exists('/foo'); a, } + const key = 'a'; + + A[key]; + `, + errors: [ + { + column: 11, + data: { name: 'key' }, + endColumn: 14, + endLine: 9, + line: 9, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + enum A { + /** @deprecated */ + a, + } + A['a']; `, errors: [ From 74c79d9a546fd7390115d73943c73e494b39a5b6 Mon Sep 17 00:00:00 2001 From: undsoft Date: Fri, 4 Apr 2025 16:23:44 +0200 Subject: [PATCH 3/5] fix(eslint-plugin): [no-deprecated] adds support for computed member access (#10958) --- packages/eslint-plugin/src/rules/no-deprecated.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-deprecated.ts b/packages/eslint-plugin/src/rules/no-deprecated.ts index 3eea60e2a707..b1aba1fe6b6a 100644 --- a/packages/eslint-plugin/src/rules/no-deprecated.ts +++ b/packages/eslint-plugin/src/rules/no-deprecated.ts @@ -1,17 +1,20 @@ -import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils'; +import type { TSESTree } from '@typescript-eslint/utils'; +import type { RuleContext } from '@typescript-eslint/utils/ts-eslint'; + +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; +import { getPropertyName } from '@typescript-eslint/utils/ast-utils'; import * as tsutils from 'ts-api-utils'; import * as ts from 'typescript'; +import type { TypeOrValueSpecifier } from '../util'; + import { createRule, getParserServices, nullThrows, typeMatchesSomeSpecifier, - TypeOrValueSpecifier, typeOrValueSpecifiersSchema, } from '../util'; -import { getPropertyName } from '@typescript-eslint/utils/ast-utils'; -import { RuleContext } from '@typescript-eslint/utils/ts-eslint'; type IdentifierLike = | TSESTree.Identifier From cb50efed3d43e873f59835e981a7fe575317de0a Mon Sep 17 00:00:00 2001 From: undsoft Date: Sun, 20 Apr 2025 16:55:37 +0200 Subject: [PATCH 4/5] fix(eslint-plugin): [no-deprecated] adds support for computed member access (#10958) --- .../eslint-plugin/src/rules/no-deprecated.ts | 79 ++++++++++--------- .../tests/rules/no-deprecated.test.ts | 12 +-- 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-deprecated.ts b/packages/eslint-plugin/src/rules/no-deprecated.ts index b1aba1fe6b6a..4c59104262ed 100644 --- a/packages/eslint-plugin/src/rules/no-deprecated.ts +++ b/packages/eslint-plugin/src/rules/no-deprecated.ts @@ -266,7 +266,7 @@ export default createRule({ } } - function getCallLikeNode(node: TSESTree.Node): CalleeNode | undefined { + function getCalleeNode(node: TSESTree.Node): CalleeNode | undefined { let callee = node; while ( @@ -288,10 +288,6 @@ export default createRule({ 'Expected call like node to have signature', ); - if (node.type === AST_NODE_TYPES.MemberExpression && node.computed) { - return getComputedPropertyDeprecation(node); - } - const symbol = services.getSymbolAtLocation(node); const aliasedSymbol = symbol != null && tsutils.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias) @@ -364,18 +360,55 @@ export default createRule({ return getJsDocDeprecation(symbol); } - function getDeprecationReason(node: IdentifierLike): string | undefined { - const callLikeNode = getCallLikeNode(node); - if (callLikeNode) { - return getCallLikeDeprecation(callLikeNode); + function getReportedNodeName( + node: IdentifierLike, + context: Readonly>, + ): string { + if (node.type === AST_NODE_TYPES.Super) { + return 'super'; + } + + if (node.type === AST_NODE_TYPES.PrivateIdentifier) { + return `#${node.name}`; + } + + if (node.type === AST_NODE_TYPES.Literal) { + return String(node.value); + } + + if (node.type === AST_NODE_TYPES.TemplateLiteral) { + return ( + getPropertyName( + node.parent as TSESTree.MemberExpression, + context.sourceCode.getScope(node), + ) || '' + ); } + if (isInComputedProperty(node)) { + return ( + getPropertyName( + node.parent as TSESTree.MemberExpression, + context.sourceCode.getScope(node), + ) || '' + ); + } + + return node.name; + } + + function getDeprecationReason(node: IdentifierLike): string | undefined { if (isInComputedProperty(node)) { return getComputedPropertyDeprecation( node.parent as TSESTree.MemberExpression, ); } + const callLikeNode = getCalleeNode(node); + if (callLikeNode) { + return getCallLikeDeprecation(callLikeNode); + } + if ( node.parent.type === AST_NODE_TYPES.JSXAttribute && node.type !== AST_NODE_TYPES.Super @@ -452,31 +485,3 @@ export default createRule({ }; }, }); - -function getReportedNodeName( - node: IdentifierLike, - context: Readonly>, -): string { - if (node.type === AST_NODE_TYPES.Super) { - return 'super'; - } - - if (node.type === AST_NODE_TYPES.PrivateIdentifier) { - return `#${node.name}`; - } - - if (node.type === AST_NODE_TYPES.Literal) { - return String(node.value); - } - - if (node.type === AST_NODE_TYPES.TemplateLiteral) { - return ( - getPropertyName( - node.parent as TSESTree.MemberExpression, - context.sourceCode.getScope(node), - ) || '' - ); - } - - return node.name; -} diff --git a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts index 5144a1afa9c4..d4553816dfe7 100644 --- a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts +++ b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts @@ -909,7 +909,7 @@ exists('/foo'); errors: [ { column: 11, - data: { name: 'key' }, + data: { name: 'b' }, endColumn: 14, endLine: 6, line: 6, @@ -1296,7 +1296,7 @@ exists('/foo'); errors: [ { column: 11, - data: { name: 'key' }, + data: { name: 'b' }, endColumn: 14, endLine: 10, line: 10, @@ -1319,7 +1319,7 @@ exists('/foo'); errors: [ { column: 11, - data: { name: 'key' }, + data: { name: 'b' }, endColumn: 14, endLine: 10, line: 10, @@ -1542,7 +1542,7 @@ exists('/foo'); errors: [ { column: 11, - data: { name: 'key' }, + data: { name: 'b' }, endColumn: 14, endLine: 10, line: 10, @@ -1565,7 +1565,7 @@ exists('/foo'); errors: [ { column: 11, - data: { name: 'key' }, + data: { name: 'b' }, endColumn: 14, endLine: 10, line: 10, @@ -2074,7 +2074,7 @@ exists('/foo'); errors: [ { column: 11, - data: { name: 'key' }, + data: { name: 'a' }, endColumn: 14, endLine: 9, line: 9, From 72234962e99e7b2822fcd98869a3e61a018eb81d Mon Sep 17 00:00:00 2001 From: undsoft Date: Sun, 20 Apr 2025 17:16:17 +0200 Subject: [PATCH 5/5] fix(eslint-plugin): [no-deprecated] adds support for computed member access (#10958) --- packages/eslint-plugin/src/rules/no-deprecated.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-deprecated.ts b/packages/eslint-plugin/src/rules/no-deprecated.ts index 7349011f1987..8a4481cafd0e 100644 --- a/packages/eslint-plugin/src/rules/no-deprecated.ts +++ b/packages/eslint-plugin/src/rules/no-deprecated.ts @@ -376,16 +376,10 @@ export default createRule({ return String(node.value); } - if (node.type === AST_NODE_TYPES.TemplateLiteral) { - return ( - getPropertyName( - node.parent as TSESTree.MemberExpression, - context.sourceCode.getScope(node), - ) || '' - ); - } - - if (isInComputedProperty(node)) { + if ( + node.type === AST_NODE_TYPES.TemplateLiteral || + isInComputedProperty(node) + ) { return ( getPropertyName( node.parent as TSESTree.MemberExpression,