diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8fa3c4fce2a4a..b0711c3467296 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2605,14 +2605,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return diagnostic; } - function isDeprecatedSymbol(symbol: Symbol) { - const parentSymbol = getParentOfSymbol(symbol); - if (parentSymbol && length(symbol.declarations) > 1) { - return parentSymbol.flags & SymbolFlags.Interface ? some(symbol.declarations, isDeprecatedDeclaration) : every(symbol.declarations, isDeprecatedDeclaration); - } - return !!symbol.valueDeclaration && isDeprecatedDeclaration(symbol.valueDeclaration) - || length(symbol.declarations) && every(symbol.declarations, isDeprecatedDeclaration); - } + function isDeprecatedSymbol(symbol: Symbol) { + const parentSymbol = getParentOfSymbol(symbol); + if (parentSymbol && length(symbol.declarations) > 1) { + if (parentSymbol.flags & SymbolFlags.Interface || symbol.flags & SymbolFlags.Accessor) { + return some(symbol.declarations, isDeprecatedDeclaration); + } + return every(symbol.declarations, isDeprecatedDeclaration); + } + return !!symbol.valueDeclaration && isDeprecatedDeclaration(symbol.valueDeclaration) + || length(symbol.declarations) && every(symbol.declarations, isDeprecatedDeclaration); + } function isDeprecatedDeclaration(declaration: Declaration) { return !!(getCombinedNodeFlagsCached(declaration) & NodeFlags.Deprecated); diff --git a/src/services/completions.ts b/src/services/completions.ts index 28d29136dab89..368829c4e6a8a 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -6084,10 +6084,14 @@ function symbolCanBeReferencedAtTypeLocation(symbol: Symbol, checker: TypeChecke } } -function isDeprecated(symbol: Symbol, checker: TypeChecker) { - const declarations = skipAlias(symbol, checker).declarations; - return !!length(declarations) && every(declarations, isDeprecatedDeclaration); -} +function isDeprecated(symbol: Symbol, checker: TypeChecker) { + const resolvedSymbol = skipAlias(symbol, checker); + const declarations = resolvedSymbol.declarations; + if (resolvedSymbol.flags & SymbolFlags.Accessor) { + return !!length(declarations) && some(declarations, isDeprecatedDeclaration); + } + return !!length(declarations) && every(declarations, isDeprecatedDeclaration); +} /** * True if the first character of `lowercaseCharacters` is the first character diff --git a/src/services/symbolDisplay.ts b/src/services/symbolDisplay.ts index 03b2b59193621..c7778ffc2becf 100644 --- a/src/services/symbolDisplay.ts +++ b/src/services/symbolDisplay.ts @@ -213,20 +213,25 @@ function getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeCheck return ScriptElementKind.unknown; } -function getNormalizedSymbolModifiers(symbol: Symbol) { - if (symbol.declarations && symbol.declarations.length) { - const [declaration, ...declarations] = symbol.declarations; - // omit deprecated flag if some declarations are not deprecated - const excludeFlags = length(declarations) && isDeprecatedDeclaration(declaration) && some(declarations, d => !isDeprecatedDeclaration(d)) - ? ModifierFlags.Deprecated - : ModifierFlags.None; - const modifiers = getNodeModifiers(declaration, excludeFlags); - if (modifiers) { - return modifiers.split(","); - } - } - return []; -} +function getNormalizedSymbolModifiers(symbol: Symbol) { + if (symbol.declarations && symbol.declarations.length) { + const [declaration, ...declarations] = symbol.declarations; + // omit deprecated flag if some declarations are not deprecated + const excludeFlags = length(declarations) && isDeprecatedDeclaration(declaration) && some(declarations, d => !isDeprecatedDeclaration(d)) + ? ModifierFlags.Deprecated + : ModifierFlags.None; + const modifiers = getNodeModifiers(declaration, excludeFlags); + if (modifiers) { + const result = modifiers.split(","); + // For getter/setter pairs, include deprecated if any accessor has @deprecated + if (symbol.flags & SymbolFlags.Accessor && !result.includes("deprecated") && some(symbol.declarations, isDeprecatedDeclaration)) { + result.push("deprecated"); + } + return result; + } + } + return []; +} /** @internal */ export function getSymbolModifiers(typeChecker: TypeChecker, symbol: Symbol): string { diff --git a/tests/cases/fourslash/completionsWithDeprecatedGetterSetter.ts b/tests/cases/fourslash/completionsWithDeprecatedGetterSetter.ts new file mode 100644 index 0000000000000..54053a447cc43 --- /dev/null +++ b/tests/cases/fourslash/completionsWithDeprecatedGetterSetter.ts @@ -0,0 +1,35 @@ +/// + +// Tests that @deprecated on getter/setter pairs correctly marks the property as deprecated in completions. +// Regression from #41941, reported in #62965. + +//// class Test { +//// /** @deprecated */ +//// public get test1() { return 0; } +//// public set test1(_value: number) {} +//// +//// public get test2() { return 0; } +//// /** @deprecated */ +//// public set test2(_value: number) {} +//// +//// /** @deprecated */ +//// public get test3() { return 0; } +//// /** @deprecated */ +//// public set test3(_value: number) {} +//// +//// public get test4() { return 0; } +//// public set test4(_value: number) {} +//// } +//// +//// const t = new Test(); +//// t./*1*/; + +verify.completions({ + marker: "1", + includes: [ + { name: "test1", kind: "getter", kindModifiers: "public,deprecated", sortText: completion.SortText.Deprecated(completion.SortText.LocationPriority) }, + { name: "test2", kind: "getter", kindModifiers: "public,deprecated", sortText: completion.SortText.Deprecated(completion.SortText.LocationPriority) }, + { name: "test3", kind: "getter", kindModifiers: "public,deprecated", sortText: completion.SortText.Deprecated(completion.SortText.LocationPriority) }, + { name: "test4", kind: "getter", kindModifiers: "public", sortText: completion.SortText.LocationPriority }, + ], +}); diff --git a/tests/cases/fourslash/jsdocDeprecated_suggestionGetterSetter.ts b/tests/cases/fourslash/jsdocDeprecated_suggestionGetterSetter.ts new file mode 100644 index 0000000000000..4e40b678f9f34 --- /dev/null +++ b/tests/cases/fourslash/jsdocDeprecated_suggestionGetterSetter.ts @@ -0,0 +1,50 @@ +/// + +// Tests that @deprecated on getter/setter pairs produces suggestion diagnostics. +// Regression from #41941, reported in #62965. + +//// class Test { +//// /** @deprecated */ +//// public get test1() { return 0; } +//// public set test1(_value: number) {} +//// +//// public get test2() { return 0; } +//// /** @deprecated */ +//// public set test2(_value: number) {} +//// +//// /** @deprecated */ +//// public get test3() { return 0; } +//// /** @deprecated */ +//// public set test3(_value: number) {} +//// +//// public get test4() { return 0; } +//// public set test4(_value: number) {} +//// } +//// +//// const t = new Test(); +//// const a = t.[|test1|]; +//// const b = t.[|test2|]; +//// const c = t.[|test3|]; +//// const d = t.test4; + +const ranges = test.ranges(); +verify.getSuggestionDiagnostics([ + { + "code": 6385, + "message": "'test1' is deprecated.", + "reportsDeprecated": true, + "range": ranges[0], + }, + { + "code": 6385, + "message": "'test2' is deprecated.", + "reportsDeprecated": true, + "range": ranges[1], + }, + { + "code": 6385, + "message": "'test3' is deprecated.", + "reportsDeprecated": true, + "range": ranges[2], + }, +]);