From 8c94062960a43d1d70fb26413dc13320208d2b42 Mon Sep 17 00:00:00 2001 From: auvred Date: Sat, 14 Sep 2024 12:21:30 +0300 Subject: [PATCH 1/6] fix(eslint-plugin): [no-deprecated] report on imported deprecated variables --- .../eslint-plugin/src/rules/no-deprecated.ts | 17 ++- .../eslint-plugin/tests/fixtures/class.ts | 11 ++ .../tests/rules/no-deprecated.test.ts | 100 +++++++++++++++++- 3 files changed, 120 insertions(+), 8 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-deprecated.ts b/packages/eslint-plugin/src/rules/no-deprecated.ts index ddbe08eb092..3ebfe6efb90 100644 --- a/packages/eslint-plugin/src/rules/no-deprecated.ts +++ b/packages/eslint-plugin/src/rules/no-deprecated.ts @@ -34,6 +34,15 @@ export default createRule({ const services = getParserServices(context); const checker = services.program.getTypeChecker(); + function tryToGetAliasedSymbol( + symbol: ts.Symbol | undefined, + ): ts.Symbol | undefined { + return symbol !== undefined && + tsutils.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias) + ? checker.getAliasedSymbol(symbol) + : symbol; + } + function isDeclaration(node: IdentifierLike): boolean { const { parent } = node; @@ -172,7 +181,7 @@ export default createRule({ const signature = checker.getResolvedSignature( tsNode as ts.CallLikeExpression, ); - const symbol = services.getSymbolAtLocation(node); + const symbol = tryToGetAliasedSymbol(services.getSymbolAtLocation(node)); if (signature) { const signatureDeprecation = getJsDocDeprecation(signature); if (signatureDeprecation !== undefined) { @@ -207,16 +216,14 @@ export default createRule({ : undefined; } - function getSymbol( - node: IdentifierLike, - ): ts.Signature | ts.Symbol | undefined { + function getSymbol(node: IdentifierLike): ts.Symbol | undefined { if (node.parent.type === AST_NODE_TYPES.Property) { return services .getTypeAtLocation(node.parent.parent) .getProperty(node.name); } - return services.getSymbolAtLocation(node); + return tryToGetAliasedSymbol(services.getSymbolAtLocation(node)); } function getDeprecationReason(node: IdentifierLike): string | undefined { diff --git a/packages/eslint-plugin/tests/fixtures/class.ts b/packages/eslint-plugin/tests/fixtures/class.ts index 89f06af9e8e..b4db3e9de77 100644 --- a/packages/eslint-plugin/tests/fixtures/class.ts +++ b/packages/eslint-plugin/tests/fixtures/class.ts @@ -11,3 +11,14 @@ export class Reducable { // used by no-implied-eval test function imports export class Function {} + +// used by no-deprecated to test importing deprecated things +/** @deprecated */ +export class DeprecatedClass { + /** @deprecated */ + foo: string = ''; +} +/** @deprecated */ +export const deprecatedVariable = 1; +/** @deprecated */ +export function deprecatedFunction() {} diff --git a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts index bdfe1013534..d136d0a78c7 100644 --- a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts +++ b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts @@ -816,7 +816,6 @@ ruleTester.run('no-deprecated', rule, { a.b(); `, - only: false, errors: [ { column: 11, @@ -839,7 +838,6 @@ ruleTester.run('no-deprecated', rule, { a.b(); `, - only: false, errors: [ { column: 11, @@ -864,7 +862,6 @@ ruleTester.run('no-deprecated', rule, { a.b(); `, - only: false, errors: [ { column: 11, @@ -1584,5 +1581,102 @@ ruleTester.run('no-deprecated', rule, { }, ], }, + { + code: ` + import { DeprecatedClass } from './class'; + + const foo = new DeprecatedClass(); + `, + errors: [ + { + column: 25, + endColumn: 40, + line: 4, + endLine: 4, + data: { name: 'DeprecatedClass' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { DeprecatedClass } from './class'; + + declare function inject(something: new () => unknown): void; + + inject(DeprecatedClass); + `, + errors: [ + { + column: 16, + endColumn: 31, + line: 6, + endLine: 6, + data: { name: 'DeprecatedClass' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { deprecatedVariable } from './class'; + + const foo = deprecatedVariable; + `, + errors: [ + { + column: 21, + endColumn: 39, + line: 4, + endLine: 4, + data: { name: 'deprecatedVariable' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { DeprecatedClass } from './class'; + + declare const x: DeprecatedClass; + + const { foo } = x; + `, + errors: [ + { + column: 26, + endColumn: 41, + line: 4, + endLine: 4, + data: { name: 'DeprecatedClass' }, + messageId: 'deprecated', + }, + { + column: 17, + endColumn: 20, + line: 6, + endLine: 6, + data: { name: 'foo' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { deprecatedFunction } from './class'; + + deprecatedFunction(); + `, + errors: [ + { + column: 9, + endColumn: 27, + line: 4, + endLine: 4, + data: { name: 'deprecatedFunction' }, + messageId: 'deprecated', + }, + ], + }, ], }); From 27a47dfa2885220052d6ed991625f1a81e9feecc Mon Sep 17 00:00:00 2001 From: auvred Date: Sat, 14 Sep 2024 18:31:18 +0300 Subject: [PATCH 2/6] i thought it would be a lot easier... --- .../eslint-plugin/src/rules/no-deprecated.ts | 158 ++-- .../tests/fixtures/deprecated.ts | 42 ++ .../tests/fixtures/tsconfig.json | 1 + .../tsconfig.moduleResolution-node16.json | 7 + .../tests/rules/no-deprecated.test.ts | 690 +++++++++++++++++- 5 files changed, 830 insertions(+), 68 deletions(-) create mode 100644 packages/eslint-plugin/tests/fixtures/deprecated.ts create mode 100644 packages/eslint-plugin/tests/fixtures/tsconfig.moduleResolution-node16.json diff --git a/packages/eslint-plugin/src/rules/no-deprecated.ts b/packages/eslint-plugin/src/rules/no-deprecated.ts index 3ebfe6efb90..3a33d0e488f 100644 --- a/packages/eslint-plugin/src/rules/no-deprecated.ts +++ b/packages/eslint-plugin/src/rules/no-deprecated.ts @@ -3,7 +3,12 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; import * as ts from 'typescript'; -import { createRule, getParserServices } from '../util'; +import { + createRule, + getParserServices, + nullThrows, + NullThrowsReasons, +} from '../util'; type IdentifierLike = TSESTree.Identifier | TSESTree.JSXIdentifier; @@ -34,13 +39,40 @@ export default createRule({ const services = getParserServices(context); const checker = services.program.getTypeChecker(); - function tryToGetAliasedSymbol( + // Deprecated jsdoc tags can be added on some symbol alias, e.g. + // + // export { /** @deprecated */ foo } + // + // When we import foo, its symbol is an alias of the exported foo (the one + // with the deprecated tag), which is itself an alias of the original foo. + // Therefore, we carefully go through the chain of aliases and check each + // immediate alias for deprecated tags + function searchForDeprecationInAliasesChain( symbol: ts.Symbol | undefined, - ): ts.Symbol | undefined { - return symbol !== undefined && - tsutils.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias) - ? checker.getAliasedSymbol(symbol) - : symbol; + checkDeprecationsOfAliasedSymbol: boolean, + ): string | undefined { + if (!symbol || !tsutils.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias)) { + return checkDeprecationsOfAliasedSymbol + ? getJsDocDeprecation(symbol) + : undefined; + } + const targetSymbol = checker.getAliasedSymbol(symbol); + while (tsutils.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias)) { + const reason = getJsDocDeprecation(symbol); + if (reason !== undefined) { + return reason; + } + const immediateAliasedSymbol: ts.Symbol | undefined = + symbol.getDeclarations() && checker.getImmediateAliasedSymbol(symbol); + if (!immediateAliasedSymbol) { + break; + } + symbol = immediateAliasedSymbol; + if (checkDeprecationsOfAliasedSymbol && symbol === targetSymbol) { + return getJsDocDeprecation(symbol); + } + } + return undefined; } function isDeclaration(node: IdentifierLike): boolean { @@ -178,59 +210,79 @@ export default createRule({ const tsNode = services.esTreeNodeToTSNodeMap.get(node.parent); // If the node is a direct function call, we look for its signature. - const signature = checker.getResolvedSignature( - tsNode as ts.CallLikeExpression, + const signature = nullThrows( + checker.getResolvedSignature(tsNode as ts.CallLikeExpression), + 'Expected call like node to have signature', ); - const symbol = tryToGetAliasedSymbol(services.getSymbolAtLocation(node)); - if (signature) { - const signatureDeprecation = getJsDocDeprecation(signature); - if (signatureDeprecation !== undefined) { - return signatureDeprecation; - } - - // Properties with function-like types have "deprecated" jsdoc - // on their symbols, not on their signatures: - // - // interface Props { - // /** @deprecated */ - // property: () => 'foo' - // ^symbol^ ^signature^ - // } - const symbolDeclarationKind = symbol?.declarations?.[0].kind; - if ( - symbolDeclarationKind !== ts.SyntaxKind.MethodDeclaration && - symbolDeclarationKind !== ts.SyntaxKind.FunctionDeclaration && - symbolDeclarationKind !== ts.SyntaxKind.MethodSignature - ) { - return getJsDocDeprecation(symbol); - } - } - // Or it could be a ClassDeclaration or a variable set to a ClassExpression. - const symbolAtLocation = - symbol && checker.getTypeOfSymbolAtLocation(symbol, tsNode).getSymbol(); - - return symbolAtLocation && - tsutils.isSymbolFlagSet(symbolAtLocation, ts.SymbolFlags.Class) - ? getJsDocDeprecation(symbolAtLocation) - : undefined; - } - - function getSymbol(node: IdentifierLike): ts.Symbol | undefined { - if (node.parent.type === AST_NODE_TYPES.Property) { - return services - .getTypeAtLocation(node.parent.parent) - .getProperty(node.name); + const symbol = services.getSymbolAtLocation(node); + const aliasedSymbol = + symbol !== undefined && + tsutils.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias) + ? checker.getAliasedSymbol(symbol) + : symbol; + const symbolDeclarationKind = aliasedSymbol?.declarations?.[0].kind; + // Properties with function-like types have "deprecated" jsdoc + // on their symbols, not on their signatures: + // + // interface Props { + // /** @deprecated */ + // property: () => 'foo' + // ^symbol^ ^signature^ + // } + if ( + symbolDeclarationKind !== ts.SyntaxKind.MethodDeclaration && + symbolDeclarationKind !== ts.SyntaxKind.FunctionDeclaration && + symbolDeclarationKind !== ts.SyntaxKind.MethodSignature + ) { + return ( + searchForDeprecationInAliasesChain(symbol, true) ?? + getJsDocDeprecation(signature) ?? + getJsDocDeprecation(aliasedSymbol) + ); } - - return tryToGetAliasedSymbol(services.getSymbolAtLocation(node)); + return ( + searchForDeprecationInAliasesChain( + symbol, + // Here we're working with a function declaration or method. + // Both can have 1 or more overloads, each overload creates one + // ts.Declaration which is placed in symbol.declarations. + // + // Imagine the following code: + // + // function foo(): void + // /** @deprecated Some Reason */ + // function foo(arg: string): void + // function foo(arg?: string): void {} + // + // foo() // <- foo is our symbol + // + // If we call getJsDocDeprecation(checker.getAliasedSymbol(symbol)), + // we get 'Some Reason', but after all, we are calling foo with + // a signature that is not deprecated! + // It works this way because symbol.getJsDocTags returns tags from + // all symbol declarations combined into one array. And AFAIK there is + // no publicly exported TS function that can tell us if a particular + // declaration is deprecated or not. + false, + ) ?? getJsDocDeprecation(signature) + ); } function getDeprecationReason(node: IdentifierLike): string | undefined { const callLikeNode = getCallLikeNode(node); - return callLikeNode - ? getCallLikeDeprecation(callLikeNode) - : getJsDocDeprecation(getSymbol(node)); + if (callLikeNode) { + return getCallLikeDeprecation(callLikeNode); + } + if (node.parent.type === AST_NODE_TYPES.Property) { + return getJsDocDeprecation( + services.getTypeAtLocation(node.parent.parent).getProperty(node.name), + ); + } + return searchForDeprecationInAliasesChain( + services.getSymbolAtLocation(node), + true, + ); } function checkIdentifier(node: IdentifierLike): void { diff --git a/packages/eslint-plugin/tests/fixtures/deprecated.ts b/packages/eslint-plugin/tests/fixtures/deprecated.ts new file mode 100644 index 00000000000..2302eabd3f5 --- /dev/null +++ b/packages/eslint-plugin/tests/fixtures/deprecated.ts @@ -0,0 +1,42 @@ +/** @deprecated */ +export class DeprecatedClass { + /** @deprecated */ + foo: string = ''; +} +/** @deprecated */ +export const deprecatedVariable = 1; +/** @deprecated */ +export function deprecatedFunction(): void {} +class NormalClass {} +const normalVariable = 1; +function normalFunction(): void; +function normalFunction(arg: string): void; +function normalFunction(arg?: string): void {} +function deprecatedFunctionWithOverloads(): void; +/** @deprecated */ +function deprecatedFunctionWithOverloads(arg: string): void; +function deprecatedFunctionWithOverloads(arg?: string): void {} +export class ClassWithDeprecatedConstructor { + constructor(); + /** @deprecated */ + constructor(arg: string); + constructor(arg?: string) {} +} +export { + /** @deprecated */ + NormalClass, + /** @deprecated */ + normalVariable, + /** @deprecated */ + normalFunction, + deprecatedFunctionWithOverloads, + /** @deprecated Reason */ + deprecatedFunctionWithOverloads as reexportedDeprecatedFunctionWithOverloads, + /** @deprecated Reason */ + ClassWithDeprecatedConstructor as ReexportedClassWithDeprecatedConstructor, +}; + +/** @deprecated */ +export default { + foo: 1, +}; diff --git a/packages/eslint-plugin/tests/fixtures/tsconfig.json b/packages/eslint-plugin/tests/fixtures/tsconfig.json index c16815aaf1a..a0fc993b1f4 100644 --- a/packages/eslint-plugin/tests/fixtures/tsconfig.json +++ b/packages/eslint-plugin/tests/fixtures/tsconfig.json @@ -11,6 +11,7 @@ "include": [ "file.ts", "consistent-type-exports.ts", + "deprecated.ts", "mixed-enums-decl.ts", "react.tsx", "var-declaration.ts" diff --git a/packages/eslint-plugin/tests/fixtures/tsconfig.moduleResolution-node16.json b/packages/eslint-plugin/tests/fixtures/tsconfig.moduleResolution-node16.json new file mode 100644 index 00000000000..b0fcd9a65b9 --- /dev/null +++ b/packages/eslint-plugin/tests/fixtures/tsconfig.moduleResolution-node16.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "node16", + "moduleResolution": "node16" + } +} diff --git a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts index d136d0a78c7..0571852a1a2 100644 --- a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts +++ b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts @@ -100,6 +100,26 @@ ruleTester.run('no-deprecated', rule, { a('b'); `, + ` + import { deprecatedFunctionWithOverloads } from './deprecated'; + + const foo = deprecatedFunctionWithOverloads(); + `, + ` + import * as imported from './deprecated'; + + const foo = imported.deprecatedFunctionWithOverloads(); + `, + ` + import { ClassWithDeprecatedConstructor } from './deprecated'; + + const foo = new ClassWithDeprecatedConstructor(); + `, + ` + import * as imported from './deprecated'; + + const foo = new imported.ClassWithDeprecatedConstructor(); + `, ` class A { a(value: 'b'): void; @@ -109,6 +129,16 @@ ruleTester.run('no-deprecated', rule, { declare const foo: A; foo.a('b'); `, + ` + const A = class { + /** @deprecated */ + constructor(); + constructor(arg: string); + constructor(arg?: string) {} + }; + + new A('a'); + `, ` type A = { (value: 'b'): void; @@ -207,6 +237,21 @@ ruleTester.run('no-deprecated', rule, { const [{ anchor = 'bar' }] = x; `, 'function fn(/** @deprecated */ foo = 4) {}', + { + code: ` + async function fn() { + const d = await import('./deprecated.js'); + d.default; + } + `, + languageOptions: { + parserOptions: { + tsconfigRootDir: rootDir, + project: 'tsconfig.moduleResolution-node16.json', + }, + }, + }, + 'call()', ], invalid: [ { @@ -677,13 +722,35 @@ ruleTester.run('no-deprecated', rule, { }, ], }, + { + code: ` + const A = class { + /** @deprecated */ + constructor(); + constructor(arg: string); + constructor(arg?: string) {} + }; + + new A(); + `, + errors: [ + { + column: 13, + endColumn: 14, + line: 9, + endLine: 9, + data: { name: 'A' }, + messageId: 'deprecated', + }, + ], + }, { code: ` declare const A: { /** @deprecated */ new (): string; }; - + new A(); `, errors: [ @@ -857,9 +924,9 @@ ruleTester.run('no-deprecated', rule, { return ''; } } - + declare const a: A; - + a.b(); `, errors: [ @@ -1583,8 +1650,8 @@ ruleTester.run('no-deprecated', rule, { }, { code: ` - import { DeprecatedClass } from './class'; - + import { DeprecatedClass } from './deprecated'; + const foo = new DeprecatedClass(); `, errors: [ @@ -1600,10 +1667,10 @@ ruleTester.run('no-deprecated', rule, { }, { code: ` - import { DeprecatedClass } from './class'; - + import { DeprecatedClass } from './deprecated'; + declare function inject(something: new () => unknown): void; - + inject(DeprecatedClass); `, errors: [ @@ -1619,8 +1686,8 @@ ruleTester.run('no-deprecated', rule, { }, { code: ` - import { deprecatedVariable } from './class'; - + import { deprecatedVariable } from './deprecated'; + const foo = deprecatedVariable; `, errors: [ @@ -1636,10 +1703,10 @@ ruleTester.run('no-deprecated', rule, { }, { code: ` - import { DeprecatedClass } from './class'; - + import { DeprecatedClass } from './deprecated'; + declare const x: DeprecatedClass; - + const { foo } = x; `, errors: [ @@ -1663,8 +1730,8 @@ ruleTester.run('no-deprecated', rule, { }, { code: ` - import { deprecatedFunction } from './class'; - + import { deprecatedFunction } from './deprecated'; + deprecatedFunction(); `, errors: [ @@ -1678,5 +1745,598 @@ ruleTester.run('no-deprecated', rule, { }, ], }, + { + code: ` + import * as imported from './deprecated'; + + const foo = new imported.NormalClass(); + `, + errors: [ + { + column: 34, + endColumn: 45, + line: 4, + endLine: 4, + data: { name: 'NormalClass' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { NormalClass } from './deprecated'; + + const foo = new NormalClass(); + `, + errors: [ + { + column: 25, + endColumn: 36, + line: 4, + endLine: 4, + data: { name: 'NormalClass' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = imported.NormalClass; + `, + errors: [ + { + column: 30, + endColumn: 41, + line: 4, + endLine: 4, + data: { name: 'NormalClass' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { NormalClass } from './deprecated'; + + const foo = NormalClass; + `, + errors: [ + { + column: 21, + endColumn: 32, + line: 4, + endLine: 4, + data: { name: 'NormalClass' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { normalVariable } from './deprecated'; + + const foo = normalVariable; + `, + errors: [ + { + column: 21, + endColumn: 35, + line: 4, + endLine: 4, + data: { name: 'normalVariable' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = imported.normalVariable; + `, + errors: [ + { + column: 30, + endColumn: 44, + line: 4, + endLine: 4, + data: { name: 'normalVariable' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const { normalVariable } = imported; + `, + errors: [ + { + column: 17, + endColumn: 31, + line: 4, + endLine: 4, + data: { name: 'normalVariable' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { normalFunction } from './deprecated'; + + const foo = normalFunction; + `, + errors: [ + { + column: 21, + endColumn: 35, + line: 4, + endLine: 4, + data: { name: 'normalFunction' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = imported.normalFunction; + `, + errors: [ + { + column: 30, + endColumn: 44, + line: 4, + endLine: 4, + data: { name: 'normalFunction' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const { normalFunction } = imported; + `, + errors: [ + { + column: 17, + endColumn: 31, + line: 4, + endLine: 4, + data: { name: 'normalFunction' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { normalFunction } from './deprecated'; + + const foo = normalFunction(); + `, + errors: [ + { + column: 21, + endColumn: 35, + line: 4, + endLine: 4, + data: { name: 'normalFunction' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = imported.normalFunction(); + `, + errors: [ + { + column: 30, + endColumn: 44, + line: 4, + endLine: 4, + data: { name: 'normalFunction' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { deprecatedFunctionWithOverloads } from './deprecated'; + + const foo = deprecatedFunctionWithOverloads('a'); + `, + errors: [ + { + column: 21, + endColumn: 52, + line: 4, + endLine: 4, + data: { name: 'deprecatedFunctionWithOverloads' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = imported.deprecatedFunctionWithOverloads('a'); + `, + errors: [ + { + column: 30, + endColumn: 61, + line: 4, + endLine: 4, + data: { name: 'deprecatedFunctionWithOverloads' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { reexportedDeprecatedFunctionWithOverloads } from './deprecated'; + + const foo = reexportedDeprecatedFunctionWithOverloads; + `, + errors: [ + { + column: 21, + endColumn: 62, + line: 4, + endLine: 4, + data: { + name: 'reexportedDeprecatedFunctionWithOverloads', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = imported.reexportedDeprecatedFunctionWithOverloads; + `, + errors: [ + { + column: 30, + endColumn: 71, + line: 4, + endLine: 4, + data: { + name: 'reexportedDeprecatedFunctionWithOverloads', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const { reexportedDeprecatedFunctionWithOverloads } = imported; + `, + errors: [ + { + column: 17, + endColumn: 58, + line: 4, + endLine: 4, + data: { + name: 'reexportedDeprecatedFunctionWithOverloads', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import { reexportedDeprecatedFunctionWithOverloads } from './deprecated'; + + const foo = reexportedDeprecatedFunctionWithOverloads(); + `, + errors: [ + { + column: 21, + endColumn: 62, + line: 4, + endLine: 4, + data: { + name: 'reexportedDeprecatedFunctionWithOverloads', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = imported.reexportedDeprecatedFunctionWithOverloads(); + `, + errors: [ + { + column: 30, + endColumn: 71, + line: 4, + endLine: 4, + data: { + name: 'reexportedDeprecatedFunctionWithOverloads', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import { reexportedDeprecatedFunctionWithOverloads } from './deprecated'; + + const foo = reexportedDeprecatedFunctionWithOverloads('a'); + `, + errors: [ + { + column: 21, + endColumn: 62, + line: 4, + endLine: 4, + data: { + name: 'reexportedDeprecatedFunctionWithOverloads', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = imported.reexportedDeprecatedFunctionWithOverloads('a'); + `, + errors: [ + { + column: 30, + endColumn: 71, + line: 4, + endLine: 4, + data: { + name: 'reexportedDeprecatedFunctionWithOverloads', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import { ClassWithDeprecatedConstructor } from './deprecated'; + + const foo = new ClassWithDeprecatedConstructor('a'); + `, + errors: [ + { + column: 25, + endColumn: 55, + line: 4, + endLine: 4, + data: { name: 'ClassWithDeprecatedConstructor' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = new imported.ClassWithDeprecatedConstructor('a'); + `, + errors: [ + { + column: 34, + endColumn: 64, + line: 4, + endLine: 4, + data: { name: 'ClassWithDeprecatedConstructor' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { ReexportedClassWithDeprecatedConstructor } from './deprecated'; + + const foo = ReexportedClassWithDeprecatedConstructor; + `, + errors: [ + { + column: 21, + endColumn: 61, + line: 4, + endLine: 4, + data: { + name: 'ReexportedClassWithDeprecatedConstructor', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = imported.ReexportedClassWithDeprecatedConstructor; + `, + errors: [ + { + column: 30, + endColumn: 70, + line: 4, + endLine: 4, + data: { + name: 'ReexportedClassWithDeprecatedConstructor', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const { ReexportedClassWithDeprecatedConstructor } = imported; + `, + errors: [ + { + column: 17, + endColumn: 57, + line: 4, + endLine: 4, + data: { + name: 'ReexportedClassWithDeprecatedConstructor', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import { ReexportedClassWithDeprecatedConstructor } from './deprecated'; + + const foo = ReexportedClassWithDeprecatedConstructor(); + `, + errors: [ + { + column: 21, + endColumn: 61, + line: 4, + endLine: 4, + data: { + name: 'ReexportedClassWithDeprecatedConstructor', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = imported.ReexportedClassWithDeprecatedConstructor(); + `, + errors: [ + { + column: 30, + endColumn: 70, + line: 4, + endLine: 4, + data: { + name: 'ReexportedClassWithDeprecatedConstructor', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import { ReexportedClassWithDeprecatedConstructor } from './deprecated'; + + const foo = ReexportedClassWithDeprecatedConstructor('a'); + `, + errors: [ + { + column: 21, + endColumn: 61, + line: 4, + endLine: 4, + data: { + name: 'ReexportedClassWithDeprecatedConstructor', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = imported.ReexportedClassWithDeprecatedConstructor('a'); + `, + errors: [ + { + column: 30, + endColumn: 70, + line: 4, + endLine: 4, + data: { + name: 'ReexportedClassWithDeprecatedConstructor', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import imported from './deprecated'; + + imported; + `, + errors: [ + { + column: 9, + endColumn: 17, + line: 4, + endLine: 4, + data: { name: 'imported' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + async function fn() { + const d = await import('./deprecated.js'); + d.default.default; + } + `, + languageOptions: { + parserOptions: { + tsconfigRootDir: rootDir, + project: 'tsconfig.moduleResolution-node16.json', + }, + }, + errors: [ + { + column: 21, + endColumn: 28, + line: 4, + endLine: 4, + data: { name: 'default' }, + messageId: 'deprecated', + }, + ], + }, ], }); From 1e613984c9f6baee9f41e2033cc270345ba02ba6 Mon Sep 17 00:00:00 2001 From: auvred Date: Sat, 14 Sep 2024 18:33:13 +0300 Subject: [PATCH 3/6] revert changes in class.ts fixture --- packages/eslint-plugin/tests/fixtures/class.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/eslint-plugin/tests/fixtures/class.ts b/packages/eslint-plugin/tests/fixtures/class.ts index b4db3e9de77..89f06af9e8e 100644 --- a/packages/eslint-plugin/tests/fixtures/class.ts +++ b/packages/eslint-plugin/tests/fixtures/class.ts @@ -11,14 +11,3 @@ export class Reducable { // used by no-implied-eval test function imports export class Function {} - -// used by no-deprecated to test importing deprecated things -/** @deprecated */ -export class DeprecatedClass { - /** @deprecated */ - foo: string = ''; -} -/** @deprecated */ -export const deprecatedVariable = 1; -/** @deprecated */ -export function deprecatedFunction() {} From 692b5f999232ef1f237291140d4c140f7ff22560 Mon Sep 17 00:00:00 2001 From: auvred Date: Sat, 14 Sep 2024 18:37:48 +0300 Subject: [PATCH 4/6] explain why we don't check aliased symbol when working with signatures --- packages/eslint-plugin/src/rules/no-deprecated.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/eslint-plugin/src/rules/no-deprecated.ts b/packages/eslint-plugin/src/rules/no-deprecated.ts index 3a33d0e488f..51b4124bd15 100644 --- a/packages/eslint-plugin/src/rules/no-deprecated.ts +++ b/packages/eslint-plugin/src/rules/no-deprecated.ts @@ -264,6 +264,9 @@ export default createRule({ // all symbol declarations combined into one array. And AFAIK there is // no publicly exported TS function that can tell us if a particular // declaration is deprecated or not. + // + // So, in case of function and method declarations, we don't check original + // aliased symbol, but rely on the getJsDocDeprecation(signature) call below. false, ) ?? getJsDocDeprecation(signature) ); From ac604ebcdda4a1cc8e1aed6c7b043bc31e70726e Mon Sep 17 00:00:00 2001 From: auvred Date: Sat, 14 Sep 2024 18:46:10 +0300 Subject: [PATCH 5/6] ci failures --- packages/eslint-plugin/src/rules/no-deprecated.ts | 7 +------ packages/eslint-plugin/tests/rules/no-deprecated.test.ts | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-deprecated.ts b/packages/eslint-plugin/src/rules/no-deprecated.ts index 51b4124bd15..42eb522d3ca 100644 --- a/packages/eslint-plugin/src/rules/no-deprecated.ts +++ b/packages/eslint-plugin/src/rules/no-deprecated.ts @@ -3,12 +3,7 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; import * as ts from 'typescript'; -import { - createRule, - getParserServices, - nullThrows, - NullThrowsReasons, -} from '../util'; +import { createRule, getParserServices, nullThrows } from '../util'; type IdentifierLike = TSESTree.Identifier | TSESTree.JSXIdentifier; diff --git a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts index 0571852a1a2..4a167bf73ca 100644 --- a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts +++ b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts @@ -251,7 +251,7 @@ ruleTester.run('no-deprecated', rule, { }, }, }, - 'call()', + 'call();', ], invalid: [ { From 3a63482eb74218d5337c2dbd54769539d7ee7591 Mon Sep 17 00:00:00 2001 From: auvred Date: Sat, 14 Sep 2024 21:59:09 +0300 Subject: [PATCH 6/6] test: make tests pass with projectservice enabled --- packages/eslint-plugin/tests/rules/no-deprecated.test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts index 4a167bf73ca..e8451d82b2c 100644 --- a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts +++ b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts @@ -247,7 +247,8 @@ ruleTester.run('no-deprecated', rule, { languageOptions: { parserOptions: { tsconfigRootDir: rootDir, - project: 'tsconfig.moduleResolution-node16.json', + projectService: false, + project: './tsconfig.moduleResolution-node16.json', }, }, }, @@ -2300,7 +2301,7 @@ ruleTester.run('no-deprecated', rule, { { code: ` import imported from './deprecated'; - + imported; `, errors: [ @@ -2324,7 +2325,8 @@ ruleTester.run('no-deprecated', rule, { languageOptions: { parserOptions: { tsconfigRootDir: rootDir, - project: 'tsconfig.moduleResolution-node16.json', + projectService: false, + project: './tsconfig.moduleResolution-node16.json', }, }, errors: [