Node-based @deprecated checks (#39323)

* Node-based @deprecated checks

Switch the checker to syntactic checks for `@deprecated` on
declarations. This requires a bit more checking of declarations in the
checker at times, but it

1. Gets rid of work, and a symbol flag, in the binder.
2. Skips work in the checker unless there is a `@deprecated` tag.
3. Makes it fairly simple to only issue errors on particular signatures
   of overloaded functions.

* remove in-progress comment

* remove unused isTypeDeclaration

* ❤️ lint

* support jsx and tagged template functions

* Support decorators too
This commit is contained in:
Nathan Shively-Sanders
2020-07-02 14:09:59 -07:00
committed by GitHub
parent 652a1c5950
commit 93ccc17658
8 changed files with 504 additions and 108 deletions
-4
View File
@@ -537,10 +537,6 @@ namespace ts {
symbol.parent = parent;
}
if (node.flags & NodeFlags.Deprecated) {
symbol.flags |= SymbolFlags.Deprecated;
}
return symbol;
}
+27 -8
View File
@@ -13284,13 +13284,19 @@ namespace ts {
undefined;
}
function isUncalledFunctionReference(node: Node, symbol: Symbol) {
return !(symbol.flags & (SymbolFlags.Function | SymbolFlags.Method))
|| !isCallLikeExpression(findAncestor(node, n => !isAccessExpression(n)) || node.parent)
&& every(symbol.declarations, d => !isFunctionLike(d) || !!(d.flags & NodeFlags.Deprecated));
}
function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, fullIndexType: Type, suppressNoImplicitAnyError: boolean, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags) {
const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined;
const propName = accessNode && isPrivateIdentifier(accessNode) ? undefined : getPropertyNameFromIndex(indexType, accessNode);
if (propName !== undefined) {
const prop = getPropertyOfType(objectType, propName);
if (prop) {
if (accessNode && prop.flags & SymbolFlags.Deprecated) {
if (accessNode && prop.valueDeclaration?.flags & NodeFlags.Deprecated && isUncalledFunctionReference(accessNode, prop)) {
const deprecatedNode = accessExpression?.argumentExpression ?? (isIndexedAccessTypeNode(accessNode) ? accessNode.indexType : accessNode);
errorOrSuggestion(/* isError */ false, deprecatedNode, Diagnostics._0_is_deprecated, propName as string);
}
@@ -22055,9 +22061,8 @@ namespace ts {
const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol);
let declaration: Declaration | undefined = localOrExportSymbol.valueDeclaration;
const target = (symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol);
if (target.flags & SymbolFlags.Deprecated) {
errorOrSuggestion(/* isError */ false, node, Diagnostics._0_is_deprecated, node.escapedText as string);
if (declaration?.flags & NodeFlags.Deprecated && isUncalledFunctionReference(node.parent, localOrExportSymbol)) {
errorOrSuggestion(/* isError */ false, node, Diagnostics._0_is_deprecated, node.escapedText as string);;
}
if (localOrExportSymbol.flags & SymbolFlags.Class) {
// Due to the emit for class decorators, any reference to the class from inside of the class body
@@ -24604,6 +24609,7 @@ namespace ts {
if (isNodeOpeningLikeElement) {
const jsxOpeningLikeNode = node as JsxOpeningLikeElement;
const sig = getResolvedSignature(jsxOpeningLikeNode);
checkDeprecatedSignature(sig, node);
checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(jsxOpeningLikeNode), getReturnTypeOfSignature(sig), jsxOpeningLikeNode);
}
}
@@ -25032,7 +25038,7 @@ namespace ts {
propType = indexInfo.type;
}
else {
if (prop.flags & SymbolFlags.Deprecated) {
if (prop.valueDeclaration?.flags & NodeFlags.Deprecated && isUncalledFunctionReference(node, prop)) {
errorOrSuggestion(/* isError */ false, right, Diagnostics._0_is_deprecated, right.escapedText as string);
}
@@ -27424,6 +27430,8 @@ namespace ts {
return nonInferrableType;
}
checkDeprecatedSignature(signature, node);
if (node.expression.kind === SyntaxKind.SuperKeyword) {
return voidType;
}
@@ -27483,6 +27491,12 @@ namespace ts {
return returnType;
}
function checkDeprecatedSignature(signature: Signature, node: Node) {
if (signature.declaration && signature.declaration.flags & NodeFlags.Deprecated) {
errorOrSuggestion(/*isError*/ false, node, Diagnostics._0_is_deprecated, signatureToString(signature));
}
}
function isSymbolOrSymbolForCall(node: Node) {
if (!isCallExpression(node)) return false;
let left = node.expression;
@@ -27591,7 +27605,9 @@ namespace ts {
if (languageVersion < ScriptTarget.ES2015) {
checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject);
}
return getReturnTypeOfSignature(getResolvedSignature(node));
const signature = getResolvedSignature(node);
checkDeprecatedSignature(signature, node);
return getReturnTypeOfSignature(signature);
}
function checkAssertion(node: AssertionExpression) {
@@ -30867,7 +30883,7 @@ namespace ts {
}
const symbol = getNodeLinks(node).resolvedSymbol;
if (symbol) {
if (symbol.flags & SymbolFlags.Deprecated) {
if (every(symbol.declarations, d => !isTypeDeclaration(d) || !!(d.flags & NodeFlags.Deprecated))) {
const diagLocation = isTypeReferenceNode(node) && isQualifiedName(node.typeName) ? node.typeName.right : node;
errorOrSuggestion(/* isError */ false, diagLocation, Diagnostics._0_is_deprecated, symbol.escapedName as string);
}
@@ -31719,6 +31735,7 @@ namespace ts {
/** Check a decorator */
function checkDecorator(node: Decorator): void {
const signature = getResolvedSignature(node);
checkDeprecatedSignature(signature, node);
const returnType = getReturnTypeOfSignature(signature);
if (returnType.flags & TypeFlags.Any) {
return;
@@ -35212,7 +35229,9 @@ namespace ts {
}
}
if (isImportSpecifier(node) && target.flags & SymbolFlags.Deprecated) {
if (isImportSpecifier(node) &&
(target.valueDeclaration && target.valueDeclaration.flags & NodeFlags.Deprecated
|| every(target.declarations, d => !!(d.flags & NodeFlags.Deprecated)))) {
errorOrSuggestion(/* isError */ false, node.name, Diagnostics._0_is_deprecated, symbol.escapedName as string);
}
}
+1 -2
View File
@@ -4512,10 +4512,9 @@ namespace ts {
Transient = 1 << 25, // Transient symbol (created during type check)
Assignment = 1 << 26, // Assignment treated as declaration (eg `this.prop = 1`)
ModuleExports = 1 << 27, // Symbol for CommonJS `module` of `module.exports`
Deprecated = 1 << 28, // Symbol has Deprecated declaration tag (eg `@deprecated`)
/* @internal */
All = FunctionScopedVariable | BlockScopedVariable | Property | EnumMember | Function | Class | Interface | ConstEnum | RegularEnum | ValueModule | NamespaceModule | TypeLiteral
| ObjectLiteral | Method | Constructor | GetAccessor | SetAccessor | Signature | TypeParameter | TypeAlias | ExportValue | Alias | Prototype | ExportStar | Optional | Transient | Deprecated,
| ObjectLiteral | Method | Constructor | GetAccessor | SetAccessor | Signature | TypeParameter | TypeAlias | ExportValue | Alias | Prototype | ExportStar | Optional | Transient,
Enum = RegularEnum | ConstEnum,
Variable = FunctionScopedVariable | BlockScopedVariable,
-1
View File
@@ -2343,7 +2343,6 @@ declare namespace ts {
Transient = 33554432,
Assignment = 67108864,
ModuleExports = 134217728,
Deprecated = 268435456,
Enum = 384,
Variable = 3,
Value = 111551,
-1
View File
@@ -2343,7 +2343,6 @@ declare namespace ts {
Transient = 33554432,
Assignment = 67108864,
ModuleExports = 134217728,
Deprecated = 268435456,
Enum = 384,
Variable = 3,
Value = 111551,
@@ -1,19 +1,22 @@
///<reference path="fourslash.ts" />
// @experimentalDecorators: true
// @Filename: a.ts
//// export namespace foo {
//// /** @deprecated */
//// export function faff () { }
//// [|faff|]()
//// [|faff()|]
//// }
//// const [|a|] = foo.[|faff|]()
//// const [|a|] = [|foo.faff()|]
//// foo[[|"faff"|]]
//// const { [|faff|] } = foo
//// faff()
//// [|faff()|]
//// /** @deprecated */
//// export function bar () {
//// foo?.[|faff|]()
//// [|foo?.faff()|]
//// }
//// foo?.[[|"faff"|]]?.()
//// [|bar|]();
//// [|foo?.["faff"]?.()|]
//// [|bar()|];
//// /** @deprecated */
//// export interface Foo {
//// /** @deprecated */
@@ -22,13 +25,52 @@
//// /** @deprecated */
//// export type QW = [|Foo|][[|"zzz"|]]
//// export type WQ = [|QW|]
//// class C {
//// /** @deprecated */
//// constructor() {
//// }
//// /** @deprecated */
//// m() { }
//// }
//// /** @deprecated */
//// class D {
//// constructor() {
//// }
//// }
//// var c = [|new C()|]
//// [|c.m()|]
//// c.[|m|]
//// new [|D|]()
//// C
//// [|D|]
// @Filename: j.tsx
//// type Props = { someProp?: any }
//// declare var props: Props
//// /** @deprecated */
//// function Compi(_props: Props) {
//// return <div></div>
//// }
//// [|Compi|];
//// [|<Compi />|];
//// [|<Compi {...props}>|]<div></div></[|Compi|]>;
//// /** @deprecated */
//// function ttf(_x: unknown) {
//// }
//// [|ttf``|]
//// [|ttf|]
//// /** @deprecated */
//// function dec(_c: unknown) { }
//// [|dec|]
//// [|@dec|]
//// class K { }
// @Filename: b.ts
//// // imports and aliases
//// import * as f from './a';
//// import { [|bar|], [|QW|] } from './a';
//// f.[|bar|]();
//// f.foo.[|faff|]();
//// [|bar|]();
//// [|f.bar()|];
//// [|f.foo.faff()|];
//// [|bar()|];
//// type Z = [|QW|];
//// type A = f.[|Foo|];
//// type B = f.[|QW|];
@@ -40,127 +82,214 @@ const ranges = test.ranges();
verify.getSuggestionDiagnostics([
{
message: "'faff' is deprecated",
code: 6385,
range: ranges[0],
reportsDeprecated: true,
"code": 6385,
"message": "'(): void' is deprecated",
"reportsDeprecated": true,
"range": ranges[0]
},
{
message: "'a' is declared but its value is never read.",
code: 6133,
range: ranges[1],
reportsUnnecessary: true
"code": 6133,
"message": "'a' is declared but its value is never read.",
"reportsUnnecessary": true,
"range": ranges[1]
},
{
message: "'faff' is deprecated",
code: 6385,
range: ranges[2],
reportsDeprecated: true,
"code": 6385,
"message": "'(): void' is deprecated",
"reportsDeprecated": true,
"range": ranges[2]
},
{
message: "'faff' is deprecated",
code: 6385,
range: ranges[3],
reportsDeprecated: true,
"code": 6385,
"message": "'faff' is deprecated",
"reportsDeprecated": true,
"range": ranges[3]
},
{
message: "'faff' is deprecated",
code: 6385,
range: ranges[4],
reportsDeprecated: true,
"code": 6385,
"message": "'faff' is deprecated",
"reportsDeprecated": true,
"range": ranges[4]
},
{
message: "'faff' is deprecated",
code: 6385,
range: ranges[5],
reportsDeprecated: true,
"code": 6385,
"message": "'(): void' is deprecated",
"reportsDeprecated": true,
"range": ranges[5]
},
{
message: "'faff' is deprecated",
code: 6385,
range: ranges[6],
reportsDeprecated: true,
"code": 6385,
"message": "'(): void' is deprecated",
"reportsDeprecated": true,
"range": ranges[6]
},
{
message: "'bar' is deprecated",
code: 6385,
range: ranges[7],
reportsDeprecated: true,
"code": 6385,
"message": "'(): void' is deprecated",
"reportsDeprecated": true,
"range": ranges[7]
},
{
message: "'Foo' is deprecated",
code: 6385,
range: ranges[8],
reportsDeprecated: true,
"code": 6385,
"message": "'(): void' is deprecated",
"reportsDeprecated": true,
"range": ranges[8]
},
{
message: "'zzz' is deprecated",
code: 6385,
range: ranges[9],
reportsDeprecated: true,
"code": 6385,
"message": "'Foo' is deprecated",
"reportsDeprecated": true,
"range": ranges[9]
},
{
message: "'QW' is deprecated",
code: 6385,
range: ranges[10],
reportsDeprecated: true,
}
])
"code": 6385,
"message": "'zzz' is deprecated",
"reportsDeprecated": true,
"range": ranges[10]
},
{
"code": 6385,
"message": "'QW' is deprecated",
"reportsDeprecated": true,
"range": ranges[11]
},
{
"code": 6385,
"message": "'(): C' is deprecated",
"reportsDeprecated": true,
"range": ranges[12]
},
{
"code": 6385,
"message": "'(): void' is deprecated",
"reportsDeprecated": true,
"range": ranges[13]
},
{
"code": 6385,
"message": "'m' is deprecated",
"reportsDeprecated": true,
"range": ranges[14]
},
{
"code": 6385,
"message": "'D' is deprecated",
"reportsDeprecated": true,
"range": ranges[15]
},
{
"code": 6385,
"message": "'D' is deprecated",
"reportsDeprecated": true,
"range": ranges[16]
},
]);
goTo.file('j.tsx')
verify.getSuggestionDiagnostics([
{
"code": 6385,
"message": "'Compi' is deprecated",
"reportsDeprecated": true,
"range": ranges[17]
},
{
"code": 6385,
"message": "'(_props: Props): any' is deprecated",
"reportsDeprecated": true,
"range": ranges[18]
},
{
"code": 6385,
"message": "'(_props: Props): any' is deprecated",
"reportsDeprecated": true,
"range": ranges[19]
},
{
"code": 6385,
"message": "'Compi' is deprecated",
"reportsDeprecated": true,
"range": ranges[20]
},
{
"code": 6385,
"message": "'(_x: unknown): void' is deprecated",
"reportsDeprecated": true,
"range": ranges[21]
},
{
"code": 6385,
"message": "'ttf' is deprecated",
"reportsDeprecated": true,
"range": ranges[22]
},
{
"code": 6385,
"message": "'dec' is deprecated",
"reportsDeprecated": true,
"range": ranges[23]
},
{
"code": 6385,
"message": "'(_c: unknown): void' is deprecated",
"reportsDeprecated": true,
"range": ranges[24]
},
]);
goTo.file('b.ts')
verify.getSuggestionDiagnostics([
{
message: "'bar' is deprecated",
code: 6385,
range: ranges[11],
reportsDeprecated: true,
"code": 6385,
"message": "'bar' is deprecated",
"reportsDeprecated": true,
"range": ranges[25]
},
{
message: "'QW' is deprecated",
code: 6385,
range: ranges[12],
reportsDeprecated: true,
"code": 6385,
"message": "'QW' is deprecated",
"reportsDeprecated": true,
"range": ranges[26]
},
{
message: "'bar' is deprecated",
code: 6385,
range: ranges[13],
reportsDeprecated: true,
"code": 6385,
"message": "'(): void' is deprecated",
"reportsDeprecated": true,
"range": ranges[27]
},
{
message: "'faff' is deprecated",
code: 6385,
range: ranges[14],
reportsDeprecated: true,
"code": 6385,
"message": "'(): void' is deprecated",
"reportsDeprecated": true,
"range": ranges[28]
},
{
message: "'bar' is deprecated",
code: 6385,
range: ranges[15],
reportsDeprecated: true,
"code": 6385,
"message": "'(): void' is deprecated",
"reportsDeprecated": true,
"range": ranges[29]
},
{
message: "'QW' is deprecated",
code: 6385,
range: ranges[16],
reportsDeprecated: true,
"code": 6385,
"message": "'QW' is deprecated",
"reportsDeprecated": true,
"range": ranges[30]
},
{
message: "'Foo' is deprecated",
code: 6385,
range: ranges[17],
reportsDeprecated: true,
"code": 6385,
"message": "'Foo' is deprecated",
"reportsDeprecated": true,
"range": ranges[31]
},
{
message: "'QW' is deprecated",
code: 6385,
range: ranges[18],
reportsDeprecated: true,
"code": 6385,
"message": "'QW' is deprecated",
"reportsDeprecated": true,
"range": ranges[32]
},
{
message: "'O' is declared but never used.",
code: 6196,
range: ranges[19],
reportsUnnecessary: true
"code": 6196,
"message": "'O' is declared but never used.",
"reportsUnnecessary": true,
"range": ranges[33]
}
])
])
@@ -0,0 +1,131 @@
///<reference path="fourslash.ts" />
//// // overloads
//// declare function foo(a: string): number;
//// /** @deprecated */
//// declare function foo(): undefined;
//// declare function foo (a?: string): number | undefined;
//// [|foo()|];
//// foo('');
//// foo;
//// /** @deprecated */
//// declare function bar(): number;
//// [|bar()|];
//// [|bar|];
//// /** @deprecated */
//// declare function baz(): number;
//// /** @deprecated */
//// declare function baz(): number | undefined;
//// [|baz()|];
//// [|baz|];
//// interface Foo {
//// /** @deprecated */
//// (): void
//// (a: number): void
//// }
//// declare const f: Foo;
//// [|f()|];
//// f(1);
//// interface T {
//// createElement(): void
//// /** @deprecated */
//// createElement(tag: 'xmp'): void;
//// }
//// declare const t: T;
//// t.createElement();
//// [|t.createElement('xmp')|];
//// declare class C {
//// /** @deprecated */
//// constructor ();
//// constructor(v: string)
//// }
//// C;
//// const c = [|new C()|];
//// interface Ca {
//// /** @deprecated */
//// (): void
//// new (): void
//// }
//// interface Cb {
//// (): void
//// /** @deprecated */
//// new (): string
//// }
//// declare const ca: Ca;
//// declare const cb: Cb;
//// ca;
//// cb;
//// [|ca()|];
//// cb();
//// new ca();
//// [|new cb()|];
const ranges = test.ranges();
verify.getSuggestionDiagnostics([
{
message: "'(): undefined' is deprecated",
code: 6385,
range: ranges[0],
reportsDeprecated: true,
},
{
message: "'(): number' is deprecated",
code: 6385,
range: ranges[1],
reportsDeprecated: true,
},
{
message: "'bar' is deprecated",
code: 6385,
range: ranges[2],
reportsDeprecated: true,
},
{
message: "'(): number' is deprecated",
code: 6385,
range: ranges[3],
reportsDeprecated: true,
},
{
message: "'baz' is deprecated",
code: 6385,
range: ranges[4],
reportsDeprecated: true,
},
{
message: "'(): void' is deprecated",
code: 6385,
range: ranges[5],
reportsDeprecated: true,
},
{
message: `'(tag: "xmp"): void' is deprecated`,
code: 6385,
range: ranges[6],
reportsDeprecated: true,
},
{
message: `'(): C' is deprecated`,
code: 6385,
range: ranges[7],
reportsDeprecated: true,
},
{
message: `'(): void' is deprecated`,
code: 6385,
range: ranges[8],
reportsDeprecated: true,
},
{
message: `'(): string' is deprecated`,
code: 6385,
range: ranges[9],
reportsDeprecated: true,
},
])
@@ -0,0 +1,124 @@
///<reference path="fourslash.ts" />
//// // merges
//// /** @deprecated */
//// interface a { a: number }
//// declare function a(): void
//// declare const ta: [|a|]
//// a;
//// a();
//// interface b { a: number; }
//// /** @deprecated */
//// declare function b(): void
//// declare const tb: b;
//// [|b|]
//// [|b()|];
//// interface c { }
//// /** @deprecated */
//// declare function c(): void
//// declare function c(a: number): void
//// declare const tc: c;
//// c;
//// [|c()|];
//// c(1);
//// /** @deprecated */
//// interface d { }
//// declare function d(): void
//// declare function d(a: number): void
//// declare const td: [|d|];
//// d;
//// d();
//// d(1);
//// /** @deprecated */
//// declare function e(): void
//// /** @deprecated */
//// declare function e(a: number): void
//// [|e|];
//// [|e()|];
//// [|e(1)|];
//// /** @deprecated */
//// interface f { a: number }
//// declare const tf: [|f|]
//// /** @deprecated */
//// type g = number
//// declare const tg: [|g|]
//// /** @deprecated */
//// class H { }
//// declare const th: [|H|]
const ranges = test.ranges();
verify.getSuggestionDiagnostics([
{
message: "'a' is deprecated",
code: 6385,
range: ranges[0],
reportsDeprecated: true,
},
{
message: "'b' is deprecated",
code: 6385,
range: ranges[1],
reportsDeprecated: true,
},
{
message: "'(): void' is deprecated",
code: 6385,
range: ranges[2],
reportsDeprecated: true,
},
{
message: "'(): void' is deprecated",
code: 6385,
range: ranges[3],
reportsDeprecated: true,
},
{
message: "'d' is deprecated",
code: 6385,
range: ranges[4],
reportsDeprecated: true,
},
{
message: "'e' is deprecated",
code: 6385,
range: ranges[5],
reportsDeprecated: true,
},
{
message: "'(): void' is deprecated",
code: 6385,
range: ranges[6],
reportsDeprecated: true,
},
{
message: "'(a: number): void' is deprecated",
code: 6385,
range: ranges[7],
reportsDeprecated: true,
},
{
message: "'f' is deprecated",
code: 6385,
range: ranges[8],
reportsDeprecated: true,
},
{
message: "'g' is deprecated",
code: 6385,
range: ranges[9],
reportsDeprecated: true,
},
{
message: "'H' is deprecated",
code: 6385,
range: ranges[10],
reportsDeprecated: true,
},
])