diff --git a/src/services/completions.ts b/src/services/completions.ts index 98d5add6fe0a4..749a9b6f6d5ce 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1,5 +1,7 @@ /* @internal */ namespace ts.Completions { + export type Log = (message: string) => void; + export enum SortText { LocalDeclarationPriority = "0", LocationPriority = "1", @@ -8,9 +10,27 @@ namespace ts.Completions { SuggestedClassMembers = "4", GlobalsOrKeywords = "5", AutoImportSuggestions = "6", - JavascriptIdentifiers = "7" + JavascriptIdentifiers = "7", + DeprecatedLocalDeclarationPriority = "8", + DeprecatedLocationPriority = "9", + DeprecatedOptionalMember = "10", + DeprecatedMemberDeclaredBySpreadAssignment = "11", + DeprecatedSuggestedClassMembers = "12", + DeprecatedGlobalsOrKeywords = "13", + DeprecatedAutoImportSuggestions = "14" } - export type Log = (message: string) => void; + + enum SortTextId { + LocalDeclarationPriority, + LocationPriority, + OptionalMember, + MemberDeclaredBySpreadAssignment, + SuggestedClassMembers, + GlobalsOrKeywords, + AutoImportSuggestions + } + + const DeprecatedSortTextStart = SortTextId.AutoImportSuggestions + 2; // for Javascript identifiers since with this change they are preferred over deprecated symbols /** * Special values for `CompletionInfo['source']` used to disambiguate @@ -105,8 +125,8 @@ namespace ts.Completions { */ type SymbolOriginInfoMap = Record; - /** Map from symbol id -> SortText. */ - type SymbolSortTextMap = (SortText | undefined)[]; + /** Map from symbol id -> SortTextId. */ + type SymbolSortTextIdMap = (SortTextId | undefined)[]; const enum KeywordCompletionFilters { None, // No keywords @@ -205,7 +225,7 @@ namespace ts.Completions { isJsxIdentifierExpected, importCompletionNode, insideJsDocTagTypeExpression, - symbolToSortTextMap, + symbolToSortTextIdMap, } = completionData; // Verify if the file is JSX language variant @@ -238,7 +258,7 @@ namespace ts.Completions { importCompletionNode, recommendedCompletion, symbolToOriginInfoMap, - symbolToSortTextMap + symbolToSortTextIdMap ); getJSCompletionEntries(sourceFile, location.pos, uniqueNames, compilerOptions.target!, entries); // TODO: GH#18217 } @@ -266,7 +286,7 @@ namespace ts.Completions { importCompletionNode, recommendedCompletion, symbolToOriginInfoMap, - symbolToSortTextMap + symbolToSortTextIdMap ); } @@ -566,7 +586,7 @@ namespace ts.Completions { importCompletionNode?: Node, recommendedCompletion?: Symbol, symbolToOriginInfoMap?: SymbolOriginInfoMap, - symbolToSortTextMap?: SymbolSortTextMap, + symbolToSortTextIdMap?: SymbolSortTextIdMap, ): UniqueNameSet { const start = timestamp(); const variableDeclaration = getVariableDeclaration(location); @@ -580,14 +600,17 @@ namespace ts.Completions { const symbol = symbols[i]; const origin = symbolToOriginInfoMap?.[i]; const info = getCompletionEntryDisplayNameForSymbol(symbol, target, origin, kind, !!jsxIdentifierExpected); - if (!info || uniques.get(info.name) || kind === CompletionKind.Global && symbolToSortTextMap && !shouldIncludeSymbol(symbol, symbolToSortTextMap)) { + if (!info || uniques.get(info.name) || kind === CompletionKind.Global && symbolToSortTextIdMap && !shouldIncludeSymbol(symbol, symbolToSortTextIdMap)) { continue; } const { name, needsConvertPropertyAccess } = info; + const symbolId = getSymbolId(symbol); + const sortTextId = symbolToSortTextIdMap?.[symbolId] ?? SortTextId.LocationPriority; + const sortText = (isDeprecated(symbol, typeChecker) ? DeprecatedSortTextStart + sortTextId : sortTextId).toString() as SortText; const entry = createCompletionEntry( symbol, - symbolToSortTextMap && symbolToSortTextMap[getSymbolId(symbol)] || SortText.LocationPriority, + sortText, contextToken, location, sourceFile, @@ -623,7 +646,7 @@ namespace ts.Completions { add: name => uniques.set(name, true), }; - function shouldIncludeSymbol(symbol: Symbol, symbolToSortTextMap: SymbolSortTextMap): boolean { + function shouldIncludeSymbol(symbol: Symbol, symbolToSortTextIdMap: SymbolSortTextIdMap): boolean { if (!isSourceFile(location)) { // export = /**/ here we want to get all meanings, so any symbol is ok if (isExportAssignment(location.parent)) { @@ -645,9 +668,9 @@ namespace ts.Completions { // Auto Imports are not available for scripts so this conditional is always false if (!!sourceFile.externalModuleIndicator && !compilerOptions.allowUmdGlobalAccess - && symbolToSortTextMap[getSymbolId(symbol)] === SortText.GlobalsOrKeywords - && (symbolToSortTextMap[getSymbolId(symbolOrigin)] === SortText.AutoImportSuggestions - || symbolToSortTextMap[getSymbolId(symbolOrigin)] === SortText.LocationPriority)) { + && symbolToSortTextIdMap[getSymbolId(symbol)] === SortTextId.GlobalsOrKeywords + && (symbolToSortTextIdMap[getSymbolId(symbolOrigin)] === SortTextId.AutoImportSuggestions + || symbolToSortTextIdMap[getSymbolId(symbolOrigin)] === SortTextId.LocationPriority)) { return false; } // Continue with origin symbol @@ -669,8 +692,6 @@ namespace ts.Completions { } } - - function getLabelCompletionAtPosition(node: BreakOrContinueStatement): CompletionInfo | undefined { const entries = getLabelStatementCompletions(node); if (entries.length) { @@ -912,7 +933,7 @@ namespace ts.Completions { readonly previousToken: Node | undefined; readonly isJsxInitializer: IsJsxInitializer; readonly insideJsDocTagTypeExpression: boolean; - readonly symbolToSortTextMap: SymbolSortTextMap; + readonly symbolToSortTextIdMap: SymbolSortTextIdMap; readonly isTypeOnlyLocation: boolean; /** In JSX tag name and attribute names, identifiers like "my-tag" or "aria-name" is valid identifier. */ readonly isJsxIdentifierExpected: boolean; @@ -1239,7 +1260,7 @@ namespace ts.Completions { // This also gets mutated in nested-functions after the return let symbols: Symbol[] = []; const symbolToOriginInfoMap: SymbolOriginInfoMap = []; - const symbolToSortTextMap: SymbolSortTextMap = []; + const symbolToSortTextIdMap: SymbolSortTextIdMap = []; const seenPropertySymbols = new Map(); const isTypeOnly = isTypeOnlyCompletion(); const getModuleSpecifierResolutionHost = memoizeOne((isFromPackageJson: boolean) => { @@ -1295,7 +1316,7 @@ namespace ts.Completions { previousToken, isJsxInitializer, insideJsDocTagTypeExpression, - symbolToSortTextMap, + symbolToSortTextIdMap, isTypeOnlyLocation: isTypeOnly, isJsxIdentifierExpected, importCompletionNode, @@ -1484,7 +1505,7 @@ namespace ts.Completions { function addSymbolSortInfo(symbol: Symbol) { if (isStaticProperty(symbol)) { - symbolToSortTextMap[getSymbolId(symbol)] = SortText.LocalDeclarationPriority; + symbolToSortTextIdMap[getSymbolId(symbol)] = SortTextId.LocalDeclarationPriority; } } @@ -1601,7 +1622,7 @@ namespace ts.Completions { for (const symbol of symbols) { if (!typeChecker.isArgumentsSymbol(symbol) && !some(symbol.declarations, d => d.getSourceFile() === sourceFile)) { - symbolToSortTextMap[getSymbolId(symbol)] = SortText.GlobalsOrKeywords; + symbolToSortTextIdMap[getSymbolId(symbol)] = SortTextId.GlobalsOrKeywords; } } @@ -1612,7 +1633,7 @@ namespace ts.Completions { for (const symbol of getPropertiesForCompletion(thisType, typeChecker)) { symbolToOriginInfoMap[symbols.length] = { kind: SymbolOriginInfoKind.ThisType }; symbols.push(symbol); - symbolToSortTextMap[getSymbolId(symbol)] = SortText.SuggestedClassMembers; + symbolToSortTextIdMap[getSymbolId(symbol)] = SortTextId.SuggestedClassMembers; } } } @@ -1694,7 +1715,7 @@ namespace ts.Completions { return false; } - /** Mutates `symbols`, `symbolToOriginInfoMap`, and `symbolToSortTextMap` */ + /** Mutates `symbols`, `symbolToOriginInfoMap`, and `symbolToSortTextIdMap` */ function collectAutoImports(resolveModuleSpecifiers: boolean) { if (!shouldOfferImportCompletions()) return; Debug.assert(!detailsEntryId?.data); @@ -1765,12 +1786,12 @@ namespace ts.Completions { function pushAutoImportSymbol(symbol: Symbol, origin: SymbolOriginInfoResolvedExport | SymbolOriginInfoExport) { const symbolId = getSymbolId(symbol); - if (symbolToSortTextMap[symbolId] === SortText.GlobalsOrKeywords) { + if (symbolToSortTextIdMap[symbolId] === SortTextId.GlobalsOrKeywords) { // If an auto-importable symbol is available as a global, don't add the auto import return; } symbolToOriginInfoMap[symbols.length] = origin; - symbolToSortTextMap[symbolId] = importCompletionNode ? SortText.LocationPriority : SortText.AutoImportSuggestions; + symbolToSortTextIdMap[symbolId] = importCompletionNode ? SortTextId.LocationPriority : SortTextId.AutoImportSuggestions; symbols.push(symbol); } @@ -2085,7 +2106,7 @@ namespace ts.Completions { localsContainer.locals?.forEach((symbol, name) => { symbols.push(symbol); if (localsContainer.symbol?.exports?.has(name)) { - symbolToSortTextMap[getSymbolId(symbol)] = SortText.OptionalMember; + symbolToSortTextIdMap[getSymbolId(symbol)] = SortTextId.OptionalMember; } }); return GlobalsSearch.Success; @@ -2533,7 +2554,8 @@ namespace ts.Completions { function setSortTextToOptionalMember() { symbols.forEach(m => { if (m.flags & SymbolFlags.Optional) { - symbolToSortTextMap[getSymbolId(m)] = symbolToSortTextMap[getSymbolId(m)] || SortText.OptionalMember; + const symbolId = getSymbolId(m); + symbolToSortTextIdMap[symbolId] = symbolToSortTextIdMap[symbolId] ?? SortTextId.OptionalMember; } }); } @@ -2545,7 +2567,7 @@ namespace ts.Completions { } for (const contextualMemberSymbol of contextualMemberSymbols) { if (membersDeclaredBySpreadAssignment.has(contextualMemberSymbol.name)) { - symbolToSortTextMap[getSymbolId(contextualMemberSymbol)] = SortText.MemberDeclaredBySpreadAssignment; + symbolToSortTextIdMap[getSymbolId(contextualMemberSymbol)] = SortTextId.MemberDeclaredBySpreadAssignment; } } } @@ -3091,4 +3113,9 @@ namespace ts.Completions { addToSeen(seenModules, getSymbolId(sym)) && checker.getExportsOfModule(sym).some(e => symbolCanBeReferencedAtTypeLocation(e, checker, seenModules)); } + + function isDeprecated(symbol: Symbol, checker: TypeChecker) { + const declarations = skipAlias(symbol, checker).declarations; + return !!length(declarations) && every(declarations, isDeprecatedDeclaration); + } } diff --git a/src/services/symbolDisplay.ts b/src/services/symbolDisplay.ts index fc99fd745d215..1293665d7bc28 100644 --- a/src/services/symbolDisplay.ts +++ b/src/services/symbolDisplay.ts @@ -100,10 +100,6 @@ namespace ts.SymbolDisplay { return ScriptElementKind.unknown; } - function isDeprecatedDeclaration(decl: Declaration) { - return !!(getCombinedNodeFlagsAlwaysIncludeJSDoc(decl) & ModifierFlags.Deprecated); - } - function getNormalizedSymbolModifiers(symbol: Symbol) { if (symbol.declarations && symbol.declarations.length) { const [declaration, ...declarations] = symbol.declarations; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index e5d9b0da77564..88a8e3a1c19b9 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -3353,5 +3353,9 @@ namespace ts { || (!!globalCachePath && startsWith(getCanonicalFileName(globalCachePath), toNodeModulesParent)); } + export function isDeprecatedDeclaration(decl: Declaration) { + return !!(getCombinedNodeFlagsAlwaysIncludeJSDoc(decl) & ModifierFlags.Deprecated); + } + // #endregion } diff --git a/tests/cases/fourslash/completionsWithDeprecatedTag1.ts b/tests/cases/fourslash/completionsWithDeprecatedTag1.ts index f3bd7e93d6a5e..1ce322b6a1c4c 100644 --- a/tests/cases/fourslash/completionsWithDeprecatedTag1.ts +++ b/tests/cases/fourslash/completionsWithDeprecatedTag1.ts @@ -25,27 +25,26 @@ verify.completions({ marker: "1", includes: [ - { name: "Foo", kind: "interface", kindModifiers: "deprecated" } + { name: "Foo", kind: "interface", kindModifiers: "deprecated", sortText: completion.SortText.DeprecatedLocationPriority } ] }, { marker: "2", includes: [ - { name: "bar", kind: "method", kindModifiers: "deprecated" } + { name: "bar", kind: "method", kindModifiers: "deprecated", sortText: completion.SortText.DeprecatedLocationPriority } ] }, { marker: "3", includes: [ - { name: "prop", kind: "property", kindModifiers: "deprecated" } + { name: "prop", kind: "property", kindModifiers: "deprecated", sortText: completion.SortText.DeprecatedLocationPriority } ] }, { marker: "4", includes: [ - { name: "foobar", kind: "function", kindModifiers: "export,deprecated" } + { name: "foobar", kind: "function", kindModifiers: "export,deprecated", sortText: completion.SortText.DeprecatedLocationPriority } ] }, { marker: "5", includes: [ - { name: "foobar", kind: "alias", kindModifiers: "export,deprecated" } + { name: "foobar", kind: "alias", kindModifiers: "export,deprecated", sortText: completion.SortText.DeprecatedLocationPriority } ] -} -); +}); diff --git a/tests/cases/fourslash/completionsWithDeprecatedTag10.ts b/tests/cases/fourslash/completionsWithDeprecatedTag10.ts new file mode 100644 index 0000000000000..f12e33feef458 --- /dev/null +++ b/tests/cases/fourslash/completionsWithDeprecatedTag10.ts @@ -0,0 +1,24 @@ +/// + +// @Filename: /foo.ts +/////** @deprecated foo */ +////export const foo = 0; + +// @Filename: /index.ts +/////**/ + +verify.completions({ + marker: "", + includes: [{ + name: "foo", + source: "/foo", + sourceDisplay: "./foo", + hasAction: true, + kind: "const", + kindModifiers: "export,deprecated", + sortText: completion.SortText.DeprecatedAutoImportSuggestions + }], + preferences: { + includeCompletionsForModuleExports: true, + }, +}); diff --git a/tests/cases/fourslash/completionsWithDeprecatedTag2.ts b/tests/cases/fourslash/completionsWithDeprecatedTag2.ts index a902c6b30eb8a..9561a9a56c502 100644 --- a/tests/cases/fourslash/completionsWithDeprecatedTag2.ts +++ b/tests/cases/fourslash/completionsWithDeprecatedTag2.ts @@ -9,7 +9,10 @@ verify.completions({ marker: "", - includes: [ - { name: "foo", kind: "function", kindModifiers: "deprecated,declare" } - ] + includes: [{ + name: "foo", + kind: "function", + kindModifiers: "deprecated,declare", + sortText: completion.SortText.DeprecatedLocationPriority + }] }); diff --git a/tests/cases/fourslash/completionsWithDeprecatedTag3.ts b/tests/cases/fourslash/completionsWithDeprecatedTag3.ts index 19e9d5c561b90..0be6a6f06a81e 100644 --- a/tests/cases/fourslash/completionsWithDeprecatedTag3.ts +++ b/tests/cases/fourslash/completionsWithDeprecatedTag3.ts @@ -9,7 +9,10 @@ verify.completions({ marker: "", - includes: [ - { name: "foo", kind: "function", kindModifiers: "declare" } - ] + includes: [{ + name: "foo", + kind: "function", + kindModifiers: "declare", + sortText: completion.SortText.LocationPriority + }] }); diff --git a/tests/cases/fourslash/completionsWithDeprecatedTag4.ts b/tests/cases/fourslash/completionsWithDeprecatedTag4.ts new file mode 100644 index 0000000000000..9df8bc60e91ba --- /dev/null +++ b/tests/cases/fourslash/completionsWithDeprecatedTag4.ts @@ -0,0 +1,23 @@ +/// + +// @noLib: true + +////f({ +//// a/**/ +//// xyz: ``, +////}); +////declare function f(options: { +//// /** @deprecated abc */ +//// abc?: number, +//// xyz?: string +////}): void; + +verify.completions({ + marker: "", + exact: [{ + name: "abc", + kind: "property", + kindModifiers: "deprecated,declare,optional", + sortText: completion.SortText.DeprecatedOptionalMember + }], +}); diff --git a/tests/cases/fourslash/completionsWithDeprecatedTag5.ts b/tests/cases/fourslash/completionsWithDeprecatedTag5.ts new file mode 100644 index 0000000000000..bebad02f6bcc0 --- /dev/null +++ b/tests/cases/fourslash/completionsWithDeprecatedTag5.ts @@ -0,0 +1,24 @@ +/// + +////class Foo { +//// /** @deprecated m */ +//// static m() {} +////} +////Foo./**/ + +verify.completions({ + marker: "", + exact: [ + { + name: "prototype", + sortText: completion.SortText.LocationPriority + }, + { + name: "m", + kind: "method", + kindModifiers: "static,deprecated", + sortText: completion.SortText.DeprecatedLocalDeclarationPriority + }, + ...completion.functionMembers + ] +}); diff --git a/tests/cases/fourslash/completionsWithDeprecatedTag6.ts b/tests/cases/fourslash/completionsWithDeprecatedTag6.ts new file mode 100644 index 0000000000000..b174e97660378 --- /dev/null +++ b/tests/cases/fourslash/completionsWithDeprecatedTag6.ts @@ -0,0 +1,17 @@ +/// + +////module Foo { +//// /** @deprecated foo */ +//// export var foo: number; +////} +////Foo./**/ + +verify.completions({ + marker: "", + exact: [{ + name: "foo", + kind: "var", + kindModifiers: "export,deprecated", + sortText: completion.SortText.DeprecatedLocationPriority + }] +}); diff --git a/tests/cases/fourslash/completionsWithDeprecatedTag7.ts b/tests/cases/fourslash/completionsWithDeprecatedTag7.ts new file mode 100644 index 0000000000000..dc1bf9bdac521 --- /dev/null +++ b/tests/cases/fourslash/completionsWithDeprecatedTag7.ts @@ -0,0 +1,27 @@ +/// + +// @strict: true + +////interface I { +//// /** @deprecated a */ +//// a: number; +////} + +////const foo = { +//// a: 1 +////} + +////const i: I = { +//// ...foo, +//// /**/ +////} + +verify.completions({ + marker: "", + exact: [{ + name: "a", + sortText: completion.SortText.DeprecatedMemberDeclaredBySpreadAssignment, + kind: 'property', + kindModifiers: "deprecated" + }] +}); diff --git a/tests/cases/fourslash/completionsWithDeprecatedTag8.ts b/tests/cases/fourslash/completionsWithDeprecatedTag8.ts new file mode 100644 index 0000000000000..d326d0daf0931 --- /dev/null +++ b/tests/cases/fourslash/completionsWithDeprecatedTag8.ts @@ -0,0 +1,25 @@ +/// + +////class C { +//// /** @deprecated */ +//// p: number; +//// m() { +//// return (/**/) +//// } +////} + +verify.completions({ + marker: "", + includes: [{ + name: "p", + kind: "property", + kindModifiers: "deprecated", + insertText: "this.p", + sortText: completion.SortText.DeprecatedSuggestedClassMembers, + source: completion.CompletionSource.ThisProperty + }], + preferences: { + includeInsertTextCompletions: true + }, + isNewIdentifierLocation: true +}); diff --git a/tests/cases/fourslash/completionsWithDeprecatedTag9.ts b/tests/cases/fourslash/completionsWithDeprecatedTag9.ts new file mode 100644 index 0000000000000..819aeb1802587 --- /dev/null +++ b/tests/cases/fourslash/completionsWithDeprecatedTag9.ts @@ -0,0 +1,30 @@ +/// + +// @lib: dom +// @allowJs: true + +// @Filename: globals.d.ts +/////** @deprecated foo */ +////declare var foo: string; + +// @Filename: index.ts +////class Foo { +//// foo: number; +//// m() { +//// foo/**/ +//// } +////} + +verify.completions({ + marker: "", + includes: [{ + name: "foo", + kind: "var", + kindModifiers: "deprecated,declare", + sortText: completion.SortText.DeprecatedGlobalsOrKeywords + }] +}, { + preferences: { + includeInsertTextCompletions: true + } +}); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 295ba1fd14264..ebb6bce58aab7 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -796,7 +796,14 @@ declare namespace completion { SuggestedClassMembers = "4", GlobalsOrKeywords = "5", AutoImportSuggestions = "6", - JavascriptIdentifiers = "7" + JavascriptIdentifiers = "7", + DeprecatedLocalDeclarationPriority = "8", + DeprecatedLocationPriority = "9", + DeprecatedOptionalMember = "10", + DeprecatedMemberDeclaredBySpreadAssignment = "11", + DeprecatedSuggestedClassMembers = "12", + DeprecatedGlobalsOrKeywords = "13", + DeprecatedAutoImportSuggestions = "14" } export const enum CompletionSource { ThisProperty = "ThisProperty/"