Skip to content

Commit 1e04bd4

Browse files
author
Andy
authored
Support completions for members with symbol names (#23091)
1 parent 751eab9 commit 1e04bd4

File tree

2 files changed

+41
-5
lines changed

2 files changed

+41
-5
lines changed

src/services/completions.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
namespace ts.Completions {
55
export type Log = (message: string) => void;
66

7-
type SymbolOriginInfo = { type: "this-type" } | SymbolOriginInfoExport;
7+
type SymbolOriginInfo = { type: "this-type" } | { type: "symbol-member" } | SymbolOriginInfoExport;
88
interface SymbolOriginInfoExport {
99
type: "export";
1010
moduleSymbol: Symbol;
@@ -208,9 +208,9 @@ namespace ts.Completions {
208208
}
209209
// We should only have needsConvertPropertyAccess if there's a property access to convert. But see #21790.
210210
// Somehow there was a global with a non-identifier name. Hopefully someone will complain about getting a "foo bar" global completion and provide a repro.
211-
else if (needsConvertPropertyAccess && propertyAccessToConvert) {
212-
insertText = `[${quote(name, preferences)}]`;
213-
const dot = findChildOfKind(propertyAccessToConvert, SyntaxKind.DotToken, sourceFile)!;
211+
else if ((origin && origin.type === "symbol-member" || needsConvertPropertyAccess) && propertyAccessToConvert) {
212+
insertText = needsConvertPropertyAccess ? `[${quote(name, preferences)}]` : `[${name}]`;
213+
const dot = findChildOfKind(propertyAccessToConvert!, SyntaxKind.DotToken, sourceFile)!;
214214
// If the text after the '.' starts with this name, write over it. Else, add new text.
215215
const end = startsWith(name, propertyAccessToConvert.name.text) ? propertyAccessToConvert.name.end : dot.end;
216216
replacementSpan = createTextSpanFromBounds(dot.getStart(sourceFile), end);
@@ -1064,12 +1064,33 @@ namespace ts.Completions {
10641064
else {
10651065
for (const symbol of type.getApparentProperties()) {
10661066
if (typeChecker.isValidPropertyAccessForCompletions(<PropertyAccessExpression>(node.parent), type, symbol)) {
1067-
symbols.push(symbol);
1067+
addPropertySymbol(symbol);
10681068
}
10691069
}
10701070
}
10711071
}
10721072

1073+
function addPropertySymbol(symbol: Symbol) {
1074+
// If this is e.g. [Symbol.iterator], add a completion for `Symbol`.
1075+
const symbolSymbol = firstDefined(symbol.declarations, decl => {
1076+
const name = getNameOfDeclaration(decl);
1077+
const leftName = name.kind === SyntaxKind.ComputedPropertyName ? getLeftMostName(name.expression) : undefined;
1078+
return leftName && typeChecker.getSymbolAtLocation(leftName);
1079+
});
1080+
if (symbolSymbol) {
1081+
symbols.push(symbolSymbol);
1082+
symbolToOriginInfoMap[getSymbolId(symbolSymbol)] = { type: "symbol-member" };
1083+
}
1084+
else {
1085+
symbols.push(symbol);
1086+
}
1087+
}
1088+
1089+
/** Given 'a.b.c', returns 'a'. */
1090+
function getLeftMostName(e: Expression): Identifier | undefined {
1091+
return isIdentifier(e) ? e : isPropertyAccessExpression(e) ? getLeftMostName(e.expression) : undefined;
1092+
}
1093+
10731094
function tryGetGlobalSymbols(): boolean {
10741095
const result: GlobalsSearch = tryGetObjectLikeCompletionSymbols()
10751096
|| tryGetImportOrExportClauseCompletionSymbols()
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////declare const Symbol: (s: string) => symbol;
4+
////const s = Symbol("s");
5+
////interface I { [s]: number };
6+
////declare const i: I;
7+
////i[|./*i*/|];
8+
////
9+
////namespace N { export const s2 = Symbol("s2"); }
10+
////interface J { [N.s2]: number; }
11+
////declare const j: J;
12+
////j[|./*j*/|];
13+
14+
verify.completionsAt("i", [{ name: "s", insertText: "[s]", replacementSpan: test.ranges()[0] }], { includeInsertTextCompletions: true });
15+
verify.completionsAt("j", [{ name: "N", insertText: "[N]", replacementSpan: test.ranges()[1] }], { includeInsertTextCompletions: true })

0 commit comments

Comments
 (0)