Skip to content
63 changes: 56 additions & 7 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13274,15 +13274,56 @@ 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);
if (propName !== undefined) {
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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4594,6 +4594,16 @@ namespace ts {
/* @internal */ assignmentDeclarationMembers?: Map<number, Declaration>; // 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.
Expand Down Expand Up @@ -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 */
Expand Down
14 changes: 14 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
130 changes: 130 additions & 0 deletions tests/cases/fourslash/jsdocDeprecated_suggestion2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
///<reference path="fourslash.ts" />

//// 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,
},
])
67 changes: 67 additions & 0 deletions tests/cases/fourslash/jsdocDeprecated_suggestion3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
///<reference path="fourslash.ts" />

//// /** @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,
}
])