From 93ccc176588e7f5ede6ff220a680f33b11269e12 Mon Sep 17 00:00:00 2001
From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>
Date: Thu, 2 Jul 2020 14:09:59 -0700
Subject: [PATCH] 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
* :heart: lint
* support jsx and tagged template functions
* Support decorators too
---
src/compiler/binder.ts | 4 -
src/compiler/checker.ts | 35 +-
src/compiler/types.ts | 3 +-
.../reference/api/tsserverlibrary.d.ts | 1 -
tests/baselines/reference/api/typescript.d.ts | 1 -
.../fourslash/jsdocDeprecated_suggestion1.ts | 313 +++++++++++++-----
.../fourslash/jsdocDeprecated_suggestion2.ts | 131 ++++++++
.../fourslash/jsdocDeprecated_suggestion3.ts | 124 +++++++
8 files changed, 504 insertions(+), 108 deletions(-)
create mode 100644 tests/cases/fourslash/jsdocDeprecated_suggestion2.ts
create mode 100644 tests/cases/fourslash/jsdocDeprecated_suggestion3.ts
diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts
index a8ff97aab2e..ff617602d43 100644
--- a/src/compiler/binder.ts
+++ b/src/compiler/binder.ts
@@ -537,10 +537,6 @@ namespace ts {
symbol.parent = parent;
}
- if (node.flags & NodeFlags.Deprecated) {
- symbol.flags |= SymbolFlags.Deprecated;
- }
-
return symbol;
}
diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index a5c26b61541..7c8a38f6212 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -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);
}
}
diff --git a/src/compiler/types.ts b/src/compiler/types.ts
index 518114d08b8..2472b302545 100644
--- a/src/compiler/types.ts
+++ b/src/compiler/types.ts
@@ -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,
diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts
index dd44ee78b01..360aa065005 100644
--- a/tests/baselines/reference/api/tsserverlibrary.d.ts
+++ b/tests/baselines/reference/api/tsserverlibrary.d.ts
@@ -2343,7 +2343,6 @@ declare namespace ts {
Transient = 33554432,
Assignment = 67108864,
ModuleExports = 134217728,
- Deprecated = 268435456,
Enum = 384,
Variable = 3,
Value = 111551,
diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts
index 2ec3b7371aa..d30b0b8f7ac 100644
--- a/tests/baselines/reference/api/typescript.d.ts
+++ b/tests/baselines/reference/api/typescript.d.ts
@@ -2343,7 +2343,6 @@ declare namespace ts {
Transient = 33554432,
Assignment = 67108864,
ModuleExports = 134217728,
- Deprecated = 268435456,
Enum = 384,
Variable = 3,
Value = 111551,
diff --git a/tests/cases/fourslash/jsdocDeprecated_suggestion1.ts b/tests/cases/fourslash/jsdocDeprecated_suggestion1.ts
index a8e13edbe04..5725870f0fb 100644
--- a/tests/cases/fourslash/jsdocDeprecated_suggestion1.ts
+++ b/tests/cases/fourslash/jsdocDeprecated_suggestion1.ts
@@ -1,19 +1,22 @@
+///