diff --git a/src/services/completions.ts b/src/services/completions.ts index 372819464bc..4160ac80369 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1117,15 +1117,19 @@ namespace ts.Completions { } function addPropertySymbol(symbol: Symbol) { + // For a computed property with an accessible name like `Symbol.iterator`, + // we'll add a completion for the *name* `Symbol` instead of for the property. // If this is e.g. [Symbol.iterator], add a completion for `Symbol`. - const symbolSymbol = firstDefined(symbol.declarations, decl => { - const name = getNameOfDeclaration(decl); - const leftName = name && name.kind === SyntaxKind.ComputedPropertyName ? getLeftMostName(name.expression) : undefined; - return leftName && typeChecker.getSymbolAtLocation(leftName); - }); - if (symbolSymbol) { - symbols.push(symbolSymbol); - symbolToOriginInfoMap[getSymbolId(symbolSymbol)] = { type: "symbol-member" }; + const computedPropertyName = firstDefined(symbol.declarations, decl => tryCast(getNameOfDeclaration(decl), isComputedPropertyName)); + if (computedPropertyName) { + const leftMostName = getLeftMostName(computedPropertyName.expression); // The completion is for `Symbol`, not `iterator`. + const nameSymbol = leftMostName && typeChecker.getSymbolAtLocation(leftMostName); + // If this is nested like for `namespace N { export const sym = Symbol(); }`, we'll add the completion for `N`. + const firstAccessibleSymbol = nameSymbol && getFirstSymbolInChain(nameSymbol, contextToken, typeChecker); + if (firstAccessibleSymbol && !symbolToOriginInfoMap[getSymbolId(firstAccessibleSymbol)]) { + symbols.push(firstAccessibleSymbol); + symbolToOriginInfoMap[getSymbolId(firstAccessibleSymbol)] = { type: "symbol-member" }; + } } else { symbols.push(symbol); diff --git a/tests/cases/fourslash/completionsUniqueSymbol.ts b/tests/cases/fourslash/completionsUniqueSymbol.ts new file mode 100644 index 00000000000..a00fe0d7167 --- /dev/null +++ b/tests/cases/fourslash/completionsUniqueSymbol.ts @@ -0,0 +1,22 @@ +/// + +////declare const Symbol: () => symbol; +////namespace M { +//// export const sym = Symbol(); +////} +////namespace N { +//// const sym = Symbol(); +//// export interface I { +//// [sym]: number; +//// [M.sym]: number; +//// } +////} +//// +////declare const i: N.I; +////i[|./**/|]; + +verify.completions({ + marker: "", + exact: { name: "M", insertText: "[M]", replacementSpan: test.ranges()[0] }, + preferences: { includeInsertTextCompletions: true }, +}); diff --git a/tests/cases/fourslash/completionsUniqueSymbol_import.ts b/tests/cases/fourslash/completionsUniqueSymbol_import.ts new file mode 100644 index 00000000000..f15bc59abff --- /dev/null +++ b/tests/cases/fourslash/completionsUniqueSymbol_import.ts @@ -0,0 +1,26 @@ +/// + +// @Filename: /a.ts +////declare const Symbol: () => symbol; +////const privateSym = Symbol(); +////export const publicSym = Symbol(); +////export interface I { +//// [privateSym]: number; +//// [publicSym]: number; +//// n: number; +////} +////export const i: I; + +// @Filename: /user.ts +////import { i } from "./a"; +////i[|./**/|]; + +verify.completions({ + marker: "", + // TODO: GH#25095 Should include `publicSym` + exact: "n", + preferences: { + includeInsertTextCompletions: true, + includeCompletionsForModuleExports: true, + }, +});