diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index aa0091f99523a..7b8c4fdc5ba11 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13274,6 +13274,45 @@ namespace ts { undefined; } + function getDeprecatedFlags(symbol: Symbol) { + if (!(symbol.flags & SymbolFlags.Deprecated)) { + return DeprecatedFlags.None; + } + + const symbolLinks = getSymbolLinks(symbol); + if (symbolLinks.deprecatedFlags === undefined) { + let deprecatedFlags = DeprecatedFlags.None; + let allSignatureLikeDeprecated = true; + forEach(symbol.declarations, decl => { + const isTypeDecl = isTypeDeclaration(decl); + const hasDeprecated = decl.flags & NodeFlags.Deprecated; + if (hasDeprecated && symbol.flags & SymbolFlags.Type && isTypeDecl) { + deprecatedFlags |= DeprecatedFlags.Type; + } + + if ((symbol.flags & (SymbolFlags.Constructor | SymbolFlags.Signature | SymbolFlags.Function | SymbolFlags.Method)) && isFunctionLike(decl)) { + if (hasDeprecated) { + deprecatedFlags |= DeprecatedFlags.Signature; + } + else { + allSignatureLikeDeprecated = false; + } + } + else if (hasDeprecated && symbol.flags & SymbolFlags.Value && !isTypeDecl) { + deprecatedFlags |= DeprecatedFlags.Value; + } + }); + + if (deprecatedFlags & DeprecatedFlags.Signature && allSignatureLikeDeprecated) { + deprecatedFlags &= DeprecatedFlags.SignatureExcludes; + deprecatedFlags |= DeprecatedFlags.Value; + } + + symbolLinks.deprecatedFlags = deprecatedFlags; + } + return symbolLinks.deprecatedFlags; + } + 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); @@ -13281,8 +13320,10 @@ namespace ts { const prop = getPropertyOfType(objectType, propName); if (prop) { if (accessNode && prop.flags & SymbolFlags.Deprecated) { - const deprecatedNode = accessExpression?.argumentExpression ?? (isIndexedAccessTypeNode(accessNode) ? accessNode.indexType : accessNode); - errorOrSuggestion(/* isError */ false, deprecatedNode, Diagnostics._0_is_deprecated, propName as string); + if (getDeprecatedFlags(prop) & (DeprecatedFlags.Type | DeprecatedFlags.Value)) { + const deprecatedNode = accessExpression?.argumentExpression ?? (isIndexedAccessTypeNode(accessNode) ? accessNode.indexType : accessNode); + errorOrSuggestion(/* isError */ false, deprecatedNode, Diagnostics._0_is_deprecated, propName as string); + } } if (accessExpression) { markPropertyAsReferenced(prop, accessExpression, /*isThisAccess*/ accessExpression.expression.kind === SyntaxKind.ThisKeyword); @@ -22043,10 +22084,11 @@ 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) { + const target = (localOrExportSymbol.flags & SymbolFlags.Alias ? resolveAlias(localOrExportSymbol) : localOrExportSymbol); + if (getDeprecatedFlags(target) & DeprecatedFlags.Value) { 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 // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind @@ -25013,7 +25055,7 @@ namespace ts { propType = indexInfo.type; } else { - if (prop.flags & SymbolFlags.Deprecated) { + if (getDeprecatedFlags(prop) & DeprecatedFlags.Value) { errorOrSuggestion(/* isError */ false, right, Diagnostics._0_is_deprecated, right.escapedText as string); } @@ -27404,6 +27446,13 @@ namespace ts { return nonInferrableType; } + if (signature.declaration && signature.declaration.flags & NodeFlags.Deprecated && ( + getDeprecatedFlags(signature.declaration.symbol) & DeprecatedFlags.Signature || + isCallSignatureDeclaration(signature.declaration) || isConstructSignatureDeclaration(signature.declaration) + )) { + errorOrSuggestion(/* isError */ false, node, Diagnostics._0_is_deprecated, signatureToString(signature)); + } + if (node.expression.kind === SyntaxKind.SuperKeyword) { return voidType; } @@ -30847,7 +30896,7 @@ namespace ts { } const symbol = getNodeLinks(node).resolvedSymbol; if (symbol) { - if (symbol.flags & SymbolFlags.Deprecated) { + if (getDeprecatedFlags(symbol) & DeprecatedFlags.Type) { const diagLocation = isTypeReferenceNode(node) && isQualifiedName(node.typeName) ? node.typeName.right : node; errorOrSuggestion(/* isError */ false, diagLocation, Diagnostics._0_is_deprecated, symbol.escapedName as string); } @@ -35192,7 +35241,7 @@ namespace ts { } } - if (isImportSpecifier(node) && target.flags & SymbolFlags.Deprecated) { + if (isImportSpecifier(node) && getDeprecatedFlags(target) & (DeprecatedFlags.Type | DeprecatedFlags.Value)) { 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 05ad94c7599f1..169e69fe15c8b 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4594,6 +4594,16 @@ namespace ts { /* @internal */ assignmentDeclarationMembers?: Map; // detected late-bound assignment declarations associated with the symbol } + /* @internal */ + export enum DeprecatedFlags { + None = 0, + Value = 1 << 0, + Type = 1 << 1, + Signature = 1 << 2, + + SignatureExcludes = Value | Type + } + /* @internal */ export interface SymbolLinks { immediateTarget?: Symbol; // Immediate target of an alias. May be another alias. Do not access directly, use `checker.getImmediateAliasedSymbol` instead. @@ -4634,6 +4644,7 @@ namespace ts { typeOnlyDeclaration?: TypeOnlyCompatibleAliasDeclaration | false; // First resolved alias declaration that makes the symbol only usable in type constructs isConstructorDeclaredProperty?: boolean; // Property declared through 'this.x = ...' assignment in constructor tupleLabelDeclaration?: NamedTupleMember | ParameterDeclaration; // Declaration associated with the tuple's label + deprecatedFlags?: DeprecatedFlags; } /* @internal */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index a6978df31b5df..294fbb2998e53 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -912,6 +912,20 @@ namespace ts { } } + export function isTypeDeclaration(decl: Declaration): boolean { + switch (decl.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.EnumMember: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.TypeParameter: + return true; + default: + return false; + } + } + export function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): DiagnosticWithLocation { const sourceFile = getSourceFileOfNode(node); return createDiagnosticForNodeInSourceFile(sourceFile, node, message, arg0, arg1, arg2, arg3); diff --git a/tests/cases/fourslash/jsdocDeprecated_suggestion2.ts b/tests/cases/fourslash/jsdocDeprecated_suggestion2.ts new file mode 100644 index 0000000000000..ffcc933dd15a7 --- /dev/null +++ b/tests/cases/fourslash/jsdocDeprecated_suggestion2.ts @@ -0,0 +1,130 @@ +/// + +//// 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: "'bar' is deprecated", + code: 6385, + range: ranges[1], + reportsDeprecated: true, + }, + { + message: "'bar' is deprecated", + code: 6385, + range: ranges[2], + reportsDeprecated: true, + }, + { + message: "'baz' 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, + }, +]) diff --git a/tests/cases/fourslash/jsdocDeprecated_suggestion3.ts b/tests/cases/fourslash/jsdocDeprecated_suggestion3.ts new file mode 100644 index 0000000000000..ccaa1cb341566 --- /dev/null +++ b/tests/cases/fourslash/jsdocDeprecated_suggestion3.ts @@ -0,0 +1,67 @@ +/// + +//// /** @deprecated */ +//// interface f { a: number } +//// declare function f(): void +//// declare const tf: [|f|] +//// f; +//// f(); + +//// 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); + +const ranges = test.ranges(); +verify.getSuggestionDiagnostics([ + { + message: "'f' is deprecated", + code: 6385, + range: ranges[0], + reportsDeprecated: true, + }, + { + message: "'b' is deprecated", + code: 6385, + range: ranges[1], + reportsDeprecated: true, + }, + { + message: "'b' 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, + } +])