diff --git a/eslint.config.mjs b/eslint.config.mjs
index b4d50a9435af..966d32aa8418 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -95,7 +95,7 @@ export default tseslint.config(
},
rules: {
// make sure we're not leveraging any deprecated APIs
- 'deprecation/deprecation': 'error',
+ '@typescript-eslint/deprecation': 'error',
// TODO: https://github.com/typescript-eslint/typescript-eslint/issues/8538
'@typescript-eslint/no-confusing-void-expression': 'off',
diff --git a/packages/eslint-plugin/docs/rules/deprecation.mdx b/packages/eslint-plugin/docs/rules/deprecation.mdx
new file mode 100644
index 000000000000..4e4633147800
--- /dev/null
+++ b/packages/eslint-plugin/docs/rules/deprecation.mdx
@@ -0,0 +1,33 @@
+---
+description: 'Prevent usage of deprecated members'
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+> 🛑 This file is source code, not the primary documentation location! 🛑
+>
+> See **https://typescript-eslint.io/rules/deprecation** for documentation.
+
+This rule supersedes the `deprecation/deprecation` rule from `eslint-plugin-deprecation`
+
+
+
+
+```ts
+escape('Hello'); // The signature '(string: string): string' of 'escape' is deprecated: A legacy feature for browser compatibility
+unescape('Hello'); // The signature '(string: string): string' of 'unescape' is deprecated: A legacy feature for browser compatibility
+RegExp.lastMatch; // 'lastMatch' is deprecated: A legacy feature for browser compatibility
+
+/**
+ * @deprecated for some reason
+ */
+declare const someValue: string;
+
+console.log(someValue); // 'someValue' is deprecated: for some reason
+
+new Buffer(38); // 'Buffer' is deprecated. since v10.0.0 - Use `Buffer.alloc()` instead (also see `Buffer.allocUnsafe()`).
+```
+
+
+
diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts
index 5c1e1d7725ac..bceea0ba26f5 100644
--- a/packages/eslint-plugin/src/configs/all.ts
+++ b/packages/eslint-plugin/src/configs/all.ts
@@ -29,6 +29,7 @@ export = {
'@typescript-eslint/consistent-type-imports': 'error',
'default-param-last': 'off',
'@typescript-eslint/default-param-last': 'error',
+ '@typescript-eslint/deprecation': 'error',
'dot-notation': 'off',
'@typescript-eslint/dot-notation': 'error',
'@typescript-eslint/explicit-function-return-type': 'error',
diff --git a/packages/eslint-plugin/src/configs/disable-type-checked.ts b/packages/eslint-plugin/src/configs/disable-type-checked.ts
index 9a45d83452cf..45a91be9a216 100644
--- a/packages/eslint-plugin/src/configs/disable-type-checked.ts
+++ b/packages/eslint-plugin/src/configs/disable-type-checked.ts
@@ -13,6 +13,7 @@ export = {
'@typescript-eslint/await-thenable': 'off',
'@typescript-eslint/consistent-return': 'off',
'@typescript-eslint/consistent-type-exports': 'off',
+ '@typescript-eslint/deprecation': 'off',
'@typescript-eslint/dot-notation': 'off',
'@typescript-eslint/naming-convention': 'off',
'@typescript-eslint/no-array-delete': 'off',
diff --git a/packages/eslint-plugin/src/configs/strict-type-checked-only.ts b/packages/eslint-plugin/src/configs/strict-type-checked-only.ts
index 53f13d96748f..eaa8296a0160 100644
--- a/packages/eslint-plugin/src/configs/strict-type-checked-only.ts
+++ b/packages/eslint-plugin/src/configs/strict-type-checked-only.ts
@@ -11,6 +11,7 @@ export = {
extends: ['./configs/base', './configs/eslint-recommended'],
rules: {
'@typescript-eslint/await-thenable': 'error',
+ '@typescript-eslint/deprecation': 'error',
'@typescript-eslint/no-array-delete': 'error',
'@typescript-eslint/no-base-to-string': 'error',
'@typescript-eslint/no-confusing-void-expression': 'error',
diff --git a/packages/eslint-plugin/src/configs/strict-type-checked.ts b/packages/eslint-plugin/src/configs/strict-type-checked.ts
index 7987868204db..5a3eba335729 100644
--- a/packages/eslint-plugin/src/configs/strict-type-checked.ts
+++ b/packages/eslint-plugin/src/configs/strict-type-checked.ts
@@ -16,6 +16,7 @@ export = {
{ minimumDescriptionLength: 10 },
],
'@typescript-eslint/ban-types': 'error',
+ '@typescript-eslint/deprecation': 'error',
'no-array-constructor': 'off',
'@typescript-eslint/no-array-constructor': 'error',
'@typescript-eslint/no-array-delete': 'error',
diff --git a/packages/eslint-plugin/src/rules/deprecation.ts b/packages/eslint-plugin/src/rules/deprecation.ts
new file mode 100644
index 000000000000..f08983b4ab55
--- /dev/null
+++ b/packages/eslint-plugin/src/rules/deprecation.ts
@@ -0,0 +1,298 @@
+import type {
+ ParserServicesWithTypeInformation,
+ TSESTree,
+} from '@typescript-eslint/utils';
+import { AST_NODE_TYPES } from '@typescript-eslint/utils';
+import type { RuleContext } from '@typescript-eslint/utils/ts-eslint';
+import type {
+ EntityName,
+ JSDocComment,
+ JSDocMemberName,
+ NodeArray,
+ Symbol as TSSymbol,
+ TypeChecker,
+} from 'typescript';
+import {
+ getAllJSDocTags,
+ isIdentifier,
+ isJSDocDeprecatedTag,
+ isJSDocLinkLike,
+ isJSDocMemberName,
+ isQualifiedName,
+ isShorthandPropertyAssignment,
+ TypeFormatFlags,
+} from 'typescript';
+
+import { createRule, getParserServices } from '../util';
+
+type Options = [];
+type MessageIds =
+ | 'deprecated'
+ | 'deprecatedWithReason'
+ | 'deprecatedSignature'
+ | 'deprecatedSignatureWithReason';
+
+function shouldIgnoreIdentifier(node: TSESTree.Identifier): boolean {
+ switch (node.parent.type) {
+ case AST_NODE_TYPES.FunctionDeclaration:
+ case AST_NODE_TYPES.TSDeclareFunction:
+ case AST_NODE_TYPES.ClassDeclaration:
+ case AST_NODE_TYPES.TSInterfaceDeclaration:
+ case AST_NODE_TYPES.TSTypeAliasDeclaration:
+ case AST_NODE_TYPES.Property:
+ return true;
+ case AST_NODE_TYPES.VariableDeclarator:
+ return node.parent.init !== node;
+ case AST_NODE_TYPES.TSPropertySignature:
+ case AST_NODE_TYPES.PropertyDefinition:
+ return node.parent.key === node;
+ }
+ return false;
+}
+
+function formatEntityName(name: EntityName | JSDocMemberName): string {
+ let current = '';
+ let currentName: EntityName | JSDocMemberName | undefined = name;
+
+ while (currentName) {
+ if (isQualifiedName(currentName) || isJSDocMemberName(currentName)) {
+ if (current === '') {
+ current = currentName.right.text;
+ } else {
+ current = `${currentName.right.text}#${current}`;
+ }
+ currentName = currentName.left;
+ continue;
+ }
+ if (isIdentifier(currentName)) {
+ if (current === '') {
+ return currentName.text;
+ }
+ current = `${currentName.text}#${current}`;
+ currentName = undefined;
+ continue;
+ }
+ break;
+ }
+ //
+ return current;
+}
+
+function formatComments(comment: string | NodeArray): string {
+ if (typeof comment === 'string') {
+ return comment;
+ }
+
+ // TODO: Implement a detection algorithm to detect "Use X instead", resolve types and give a different error message
+ /*
+ const links = comment.filter(
+ isJSDocLinkLike,
+ );
+ if (links.length === 1) {
+ const link = links[0];
+
+ if (link.name !== undefined) {
+ return `Use '${formatEntityName(link.name)}' instead.`;
+ }
+ }
+ */
+
+ return comment
+ .map(single => {
+ if (isJSDocLinkLike(single)) {
+ if (single.name) {
+ return formatEntityName(single.name);
+ }
+ return single.text;
+ }
+ return single.text;
+ })
+ .join('');
+}
+
+function handleMaybeDeprecatedSymbol(
+ ctx: Readonly>,
+ services: ParserServicesWithTypeInformation,
+ checker: TypeChecker,
+ node: TSESTree.Node,
+ sym: TSSymbol,
+ name: string,
+): void {
+ if (
+ node.type === AST_NODE_TYPES.Identifier &&
+ (node.parent.type === AST_NODE_TYPES.CallExpression ||
+ node.parent.type === AST_NODE_TYPES.NewExpression) &&
+ node.parent.callee === node
+ ) {
+ /*
+ Function call
+ We should in this case check the resolved signature instead
+ */
+
+ const tsParent = services.esTreeNodeToTSNodeMap.get(node.parent);
+ const sig = checker.getResolvedSignature(tsParent);
+ if (sig === undefined) {
+ return;
+ }
+ const decl = sig.getDeclaration();
+ if ((decl as undefined | typeof decl) === undefined) {
+ // May happen if we have an implicit constructor on a class
+ return;
+ }
+
+ for (const tag of getAllJSDocTags(decl, isJSDocDeprecatedTag)) {
+ if (tag.comment) {
+ ctx.report({
+ messageId: 'deprecatedSignatureWithReason',
+ node,
+ data: {
+ name,
+ signature: checker.signatureToString(
+ sig,
+ tsParent,
+ TypeFormatFlags.WriteTypeArgumentsOfSignature,
+ ),
+ reason: formatComments(tag.comment),
+ },
+ });
+ return;
+ }
+ ctx.report({
+ messageId: 'deprecatedSignature',
+ node,
+ data: {
+ name,
+ signature: checker.signatureToString(
+ sig,
+ tsParent,
+ TypeFormatFlags.WriteTypeArgumentsOfSignature,
+ ),
+ },
+ });
+ }
+ return;
+ }
+
+ for (const decl of sym.getDeclarations() ?? []) {
+ for (const tag of getAllJSDocTags(decl, isJSDocDeprecatedTag)) {
+ if (tag.comment) {
+ ctx.report({
+ messageId: 'deprecatedWithReason',
+ node,
+ data: {
+ name,
+ reason: formatComments(tag.comment),
+ },
+ });
+ return;
+ }
+ ctx.report({
+ messageId: 'deprecated',
+ node,
+ data: {
+ name,
+ },
+ });
+ }
+ }
+}
+
+export default createRule({
+ name: 'deprecation',
+ meta: {
+ docs: {
+ description: 'Disallow usage of deprecated APIs',
+ requiresTypeChecking: true,
+ recommended: 'strict',
+ },
+ messages: {
+ deprecated: `'{{name}}' is deprecated.`,
+ deprecatedWithReason: `'{{name}}' is deprecated: {{reason}}`,
+ deprecatedSignature: `The signature '{{signature}}' of '{{name}}' is deprecated.`,
+ deprecatedSignatureWithReason: `The signature '{{signature}}' of '{{name}}' is deprecated: {{reason}}`,
+ },
+ schema: [],
+ type: 'problem',
+ },
+ defaultOptions: [],
+ create(ctx) {
+ const services = getParserServices(ctx);
+ const checker = services.program.getTypeChecker();
+
+ return {
+ // TODO: Support a[b] syntax
+ Property(node): void {
+ const par = services.esTreeNodeToTSNodeMap.get(node);
+
+ if (node.key.type !== AST_NODE_TYPES.Identifier) {
+ return;
+ }
+
+ if (isShorthandPropertyAssignment(par)) {
+ const sym = checker.getTypeAtLocation(par.name).getSymbol();
+ if (sym === undefined) {
+ return;
+ }
+
+ handleMaybeDeprecatedSymbol(
+ ctx,
+ services,
+ checker,
+ node,
+ sym,
+ node.key.name,
+ );
+ }
+ return;
+ },
+ Identifier(node): void {
+ if (shouldIgnoreIdentifier(node)) {
+ return;
+ }
+
+ const sym = services.getSymbolAtLocation(node);
+ if (sym === undefined) {
+ // Types unavailable
+ return;
+ }
+
+ try {
+ handleMaybeDeprecatedSymbol(
+ ctx,
+ services,
+ checker,
+ node,
+ sym,
+ node.name,
+ );
+ } catch {
+ return;
+ }
+ },
+ MemberExpression(node): void {
+ if (node.property.type === AST_NODE_TYPES.PrivateIdentifier) {
+ const identifier = node.property;
+
+ const sym = services.getSymbolAtLocation(identifier);
+ if (sym === undefined) {
+ // Types unavailable
+ return;
+ }
+
+ try {
+ handleMaybeDeprecatedSymbol(
+ ctx,
+ services,
+ checker,
+ identifier,
+ sym,
+ `#${identifier.name}`,
+ );
+ } catch {
+ return;
+ }
+ }
+ },
+ };
+ },
+});
diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts
index c167013211c0..0faf30ce7b44 100644
--- a/packages/eslint-plugin/src/rules/index.ts
+++ b/packages/eslint-plugin/src/rules/index.ts
@@ -16,6 +16,7 @@ import consistentTypeDefinitions from './consistent-type-definitions';
import consistentTypeExports from './consistent-type-exports';
import consistentTypeImports from './consistent-type-imports';
import defaultParamLast from './default-param-last';
+import deprecation from './deprecation';
import dotNotation from './dot-notation';
import explicitFunctionReturnType from './explicit-function-return-type';
import explicitMemberAccessibility from './explicit-member-accessibility';
@@ -140,6 +141,7 @@ export default {
'consistent-type-exports': consistentTypeExports,
'consistent-type-imports': consistentTypeImports,
'default-param-last': defaultParamLast,
+ deprecation: deprecation,
'dot-notation': dotNotation,
'explicit-function-return-type': explicitFunctionReturnType,
'explicit-member-accessibility': explicitMemberAccessibility,
diff --git a/packages/eslint-plugin/tests/rules/deprecation.test.ts b/packages/eslint-plugin/tests/rules/deprecation.test.ts
new file mode 100644
index 000000000000..d6904f17b2d9
--- /dev/null
+++ b/packages/eslint-plugin/tests/rules/deprecation.test.ts
@@ -0,0 +1,318 @@
+import { RuleTester } from '@typescript-eslint/rule-tester';
+
+import rule from '../../src/rules/deprecation';
+import { getFixturesRootDir } from '../RuleTester';
+
+const rootPath = getFixturesRootDir();
+
+const ruleTester = new RuleTester({
+ parser: '@typescript-eslint/parser',
+ parserOptions: {
+ sourceType: 'module',
+ tsconfigRootDir: rootPath,
+ project: './tsconfig.json',
+ },
+});
+
+ruleTester.run('deprecation', rule, {
+ valid: [
+ `
+declare const b: string;
+if (false as boolean) {
+ /**
+ * @deprecated
+ */
+ const b = '';
+}
+
+const a = b;
+ `.trim(),
+ `
+const a = 'a';
+ `.trim(),
+ `
+/**
+ * @deprecated
+ */
+const a = 'a';
+ `.trim(),
+ `
+/**
+ * @deprecated
+ */
+class A {}
+ `.trim(),
+ `
+/**
+ * @deprecated
+ */
+interface A {}
+ `.trim(),
+ `
+/**
+ * @deprecated
+ */
+declare class A {}
+ `.trim(),
+ `
+class A {
+ /**
+ * @deprecated
+ */
+ b: string;
+}
+ `.trim(),
+ `
+declare class A {
+ /**
+ * @deprecated
+ */
+ b: string;
+}
+ `.trim(),
+ `
+interface A {
+ /**
+ * @deprecated
+ */
+ b: string;
+}
+ `.trim(),
+ `
+class A {
+ /**
+ * @deprecated
+ */
+ b: string;
+}
+
+class B extends A {
+ b: string;
+}
+ `.trim(),
+ `
+/** @deprecated */
+declare function a(val: string): string;
+declare function a(val: number): number;
+ `.trim(),
+ `
+/** @deprecated */
+declare function a(val: string): string;
+declare function a(val: number): number;
+
+a(2);
+ `.trim(),
+ `
+/** @deprecated */
+declare function a(val: K): K;
+declare function a(val: number): number;
+declare function a(val: boolean): boolean;
+
+a(2);
+ `.trim(),
+ ],
+ invalid: [
+ {
+ code: `
+/**
+ * @deprecated EXAMPLE
+ */
+const a = 'a';
+
+console.log(a);
+ `.trim(),
+ errors: [
+ {
+ messageId: 'deprecatedWithReason',
+ data: {
+ name: 'a',
+ reason: 'EXAMPLE',
+ },
+ line: 6,
+ },
+ ],
+ },
+ {
+ code: `
+const a = {
+ /**
+ * @deprecated
+ */
+ b: 'Hi!',
+};
+
+const c = a.b;
+ `.trim(),
+ errors: [
+ {
+ messageId: 'deprecated',
+ line: 8,
+ data: {
+ name: 'b',
+ },
+ },
+ ],
+ },
+ {
+ code: `
+/**
+ * @deprecated
+ */
+const a = {
+ b: 'Hi!',
+};
+
+const b = {
+ a,
+};
+ `.trim(),
+ errors: [
+ {
+ messageId: 'deprecated',
+ data: {
+ name: 'a',
+ line: 9,
+ },
+ },
+ ],
+ },
+ {
+ code: `
+const a = {
+ /**
+ * @deprecated
+ */
+ b: 'Hi!',
+};
+
+function c(d: string = a.b) {}
+ `.trim(),
+ errors: [
+ {
+ messageId: 'deprecated',
+ data: {
+ name: 'b',
+ },
+ line: 8,
+ },
+ ],
+ },
+ {
+ code: `
+/**
+ * @deprecated
+ */
+type C = string;
+
+class A {}
+ `.trim(),
+ errors: [
+ {
+ line: 6,
+ messageId: 'deprecated',
+ data: {
+ name: 'C',
+ },
+ },
+ ],
+ },
+ {
+ code: `
+class A {
+ /**
+ * @deprecated
+ */
+ #b: string;
+
+ constructor() {
+ this.#b = 'Hi!';
+ }
+}
+ `.trim(),
+ errors: [
+ {
+ line: 8,
+ messageId: 'deprecated',
+ data: {
+ name: '#b',
+ },
+ },
+ ],
+ },
+ {
+ code: `
+declare namespace a {
+ /**
+ * @deprecated
+ */
+ const a: string;
+}
+declare namespace a {
+ const a: string;
+}
+
+const b = a.a;
+ `,
+ errors: [
+ {
+ messageId: 'deprecated',
+ line: 12,
+ data: {
+ name: 'a',
+ },
+ },
+ ],
+ },
+ {
+ code: `
+/** @deprecated */
+declare function a(val: K): K;
+declare function a(val: number): number;
+declare function a(val: boolean): boolean;
+
+a('B');
+ `.trim(),
+ errors: [
+ {
+ messageId: 'deprecatedSignature',
+ line: 6,
+ data: {
+ signature: `<"B">(val: "B"): "B"`,
+ name: 'a',
+ },
+ },
+ ],
+ },
+ {
+ code: `
+class A {
+ /** @deprecated */
+ constructor(value: string) {}
+}
+
+new A('VALUE');
+ `,
+ errors: [
+ {
+ messageId: 'deprecatedSignature',
+ },
+ ],
+ },
+ {
+ code: `
+declare interface A {
+ /** @deprecated */
+ new (value: string): A;
+}
+declare const A: A;
+
+new A('VALUE');
+ `,
+ errors: [
+ {
+ messageId: 'deprecatedSignature',
+ },
+ ],
+ },
+ ],
+});
diff --git a/packages/typescript-eslint/src/configs/all.ts b/packages/typescript-eslint/src/configs/all.ts
index f01ac17c8ddb..19dad3f81543 100644
--- a/packages/typescript-eslint/src/configs/all.ts
+++ b/packages/typescript-eslint/src/configs/all.ts
@@ -38,6 +38,7 @@ export default (
'@typescript-eslint/consistent-type-imports': 'error',
'default-param-last': 'off',
'@typescript-eslint/default-param-last': 'error',
+ '@typescript-eslint/deprecation': 'error',
'dot-notation': 'off',
'@typescript-eslint/dot-notation': 'error',
'@typescript-eslint/explicit-function-return-type': 'error',
diff --git a/packages/typescript-eslint/src/configs/disable-type-checked.ts b/packages/typescript-eslint/src/configs/disable-type-checked.ts
index 9df504415e37..43f946a69afc 100644
--- a/packages/typescript-eslint/src/configs/disable-type-checked.ts
+++ b/packages/typescript-eslint/src/configs/disable-type-checked.ts
@@ -16,6 +16,7 @@ export default (
'@typescript-eslint/await-thenable': 'off',
'@typescript-eslint/consistent-return': 'off',
'@typescript-eslint/consistent-type-exports': 'off',
+ '@typescript-eslint/deprecation': 'off',
'@typescript-eslint/dot-notation': 'off',
'@typescript-eslint/naming-convention': 'off',
'@typescript-eslint/no-array-delete': 'off',
diff --git a/packages/typescript-eslint/src/configs/strict-type-checked-only.ts b/packages/typescript-eslint/src/configs/strict-type-checked-only.ts
index 415dd3eb342b..1bd10ccfa719 100644
--- a/packages/typescript-eslint/src/configs/strict-type-checked-only.ts
+++ b/packages/typescript-eslint/src/configs/strict-type-checked-only.ts
@@ -20,6 +20,7 @@ export default (
name: 'typescript-eslint/strict-type-checked-only',
rules: {
'@typescript-eslint/await-thenable': 'error',
+ '@typescript-eslint/deprecation': 'error',
'@typescript-eslint/no-array-delete': 'error',
'@typescript-eslint/no-base-to-string': 'error',
'@typescript-eslint/no-confusing-void-expression': 'error',
diff --git a/packages/typescript-eslint/src/configs/strict-type-checked.ts b/packages/typescript-eslint/src/configs/strict-type-checked.ts
index 61d0a4d579a2..acee87d8c307 100644
--- a/packages/typescript-eslint/src/configs/strict-type-checked.ts
+++ b/packages/typescript-eslint/src/configs/strict-type-checked.ts
@@ -25,6 +25,7 @@ export default (
{ minimumDescriptionLength: 10 },
],
'@typescript-eslint/ban-types': 'error',
+ '@typescript-eslint/deprecation': 'error',
'no-array-constructor': 'off',
'@typescript-eslint/no-array-constructor': 'error',
'@typescript-eslint/no-array-delete': 'error',