Skip to content

support overload signature for deprecated. #39221

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 13 commits into from
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,
}
])