diff --git a/packages/eslint-plugin/src/rules/no-deprecated.ts b/packages/eslint-plugin/src/rules/no-deprecated.ts index ddbe08eb0926..42eb522d3ca9 100644 --- a/packages/eslint-plugin/src/rules/no-deprecated.ts +++ b/packages/eslint-plugin/src/rules/no-deprecated.ts @@ -3,7 +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 } from '../util'; +import { createRule, getParserServices, nullThrows } from '../util'; type IdentifierLike = TSESTree.Identifier | TSESTree.JSXIdentifier; @@ -34,6 +34,42 @@ export default createRule({ const services = getParserServices(context); const checker = services.program.getTypeChecker(); + // 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, + 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 { const { parent } = node; @@ -169,61 +205,82 @@ 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 = 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.Signature | 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 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. + // + // 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) + ); } 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 000000000000..2302eabd3f54 --- /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 c16815aaf1ac..a0fc993b1f48 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 000000000000..b0fcd9a65b92 --- /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 bdfe10135349..e8451d82b2cb 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,22 @@ 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, + projectService: false, + project: './tsconfig.moduleResolution-node16.json', + }, + }, + }, + 'call();', ], invalid: [ { @@ -677,13 +723,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: [ @@ -816,7 +884,6 @@ ruleTester.run('no-deprecated', rule, { a.b(); `, - only: false, errors: [ { column: 11, @@ -839,7 +906,6 @@ ruleTester.run('no-deprecated', rule, { a.b(); `, - only: false, errors: [ { column: 11, @@ -859,12 +925,11 @@ ruleTester.run('no-deprecated', rule, { return ''; } } - + declare const a: A; - + a.b(); `, - only: false, errors: [ { column: 11, @@ -1584,5 +1649,696 @@ ruleTester.run('no-deprecated', rule, { }, ], }, + { + code: ` + import { DeprecatedClass } from './deprecated'; + + const foo = new DeprecatedClass(); + `, + errors: [ + { + column: 25, + endColumn: 40, + line: 4, + endLine: 4, + data: { name: 'DeprecatedClass' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { DeprecatedClass } from './deprecated'; + + 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 './deprecated'; + + const foo = deprecatedVariable; + `, + errors: [ + { + column: 21, + endColumn: 39, + line: 4, + endLine: 4, + data: { name: 'deprecatedVariable' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { DeprecatedClass } from './deprecated'; + + 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 './deprecated'; + + deprecatedFunction(); + `, + errors: [ + { + column: 9, + endColumn: 27, + line: 4, + endLine: 4, + data: { name: 'deprecatedFunction' }, + messageId: 'deprecated', + }, + ], + }, + { + 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, + projectService: false, + project: './tsconfig.moduleResolution-node16.json', + }, + }, + errors: [ + { + column: 21, + endColumn: 28, + line: 4, + endLine: 4, + data: { name: 'default' }, + messageId: 'deprecated', + }, + ], + }, ], });
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: