From d9f963ecd2b794e5e22079ebc90a44041c9c0c29 Mon Sep 17 00:00:00 2001 From: kingwl Date: Mon, 17 Aug 2020 23:13:39 +0800 Subject: [PATCH 1/2] Add support for enum completions --- src/services/completions.ts | 57 +++++++++++++++++++ .../fourslash/completionsForCaseWithEnum1.ts | 21 +++++++ .../fourslash/completionsForCaseWithEnum2.ts | 22 +++++++ .../fourslash/completionsForCaseWithEnum3.ts | 22 +++++++ .../fourslash/completionsForCaseWithEnum4.ts | 22 +++++++ .../fourslash/completionsForCaseWithEnum5.ts | 21 +++++++ 6 files changed, 165 insertions(+) create mode 100644 tests/cases/fourslash/completionsForCaseWithEnum1.ts create mode 100644 tests/cases/fourslash/completionsForCaseWithEnum2.ts create mode 100644 tests/cases/fourslash/completionsForCaseWithEnum3.ts create mode 100644 tests/cases/fourslash/completionsForCaseWithEnum4.ts create mode 100644 tests/cases/fourslash/completionsForCaseWithEnum5.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index 2bec3132df955..acb8a6e374bf8 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -33,6 +33,7 @@ namespace ts.Completions { Export = 1 << 2, Promise = 1 << 3, Nullable = 1 << 4, + Enum = 1 << 5, SymbolMemberNoExport = SymbolMember, SymbolMemberExport = SymbolMember | Export, @@ -49,6 +50,11 @@ namespace ts.Completions { isFromPackageJson?: boolean; } + interface SymbolOriginInfoEnum extends SymbolOriginInfo { + kind: SymbolOriginInfoKind; + needCase: boolean; + } + function originIsThisType(origin: SymbolOriginInfo): boolean { return !!(origin.kind & SymbolOriginInfoKind.ThisType); } @@ -69,6 +75,10 @@ namespace ts.Completions { return !!(origin.kind & SymbolOriginInfoKind.Promise); } + function originIsEnum(origin: SymbolOriginInfo): origin is SymbolOriginInfoEnum { + return !!(origin.kind & SymbolOriginInfoKind.Enum); + } + function originIsNullableMember(origin: SymbolOriginInfo): boolean { return !!(origin.kind & SymbolOriginInfoKind.Nullable); } @@ -419,6 +429,10 @@ namespace ts.Completions { insertText = needsConvertPropertyAccess ? `${awaitText}${insertText}` : `${awaitText}${insertQuestionDot ? "?." : "."}${insertText}`; replacementSpan = createTextSpanFromBounds(propertyAccessToConvert.getStart(sourceFile), propertyAccessToConvert.end); } + if (origin && originIsEnum(origin) && symbol.parent) { + name = `${symbol.parent.name}.${symbol.name}`; + insertText = origin.needCase ? `case ${name}:` : name; + } if (insertText !== undefined && !preferences.includeCompletionsWithInsertText) { return undefined; @@ -940,6 +954,8 @@ namespace ts.Completions { let node = currentToken; let propertyAccessToConvert: PropertyAccessExpression | undefined; let isRightOfDot = false; + let isRightOfCase = false; + let isInsideSwitchBody = false; let isRightOfQuestionDot = false; let isRightOfOpenTag = false; let isStartingCloseTag = false; @@ -988,6 +1004,12 @@ namespace ts.Completions { return undefined; } } + else if (contextToken.kind === SyntaxKind.CaseKeyword) { + isRightOfCase = true; + } + else if (location.kind === SyntaxKind.CaseBlock) { + isInsideSwitchBody = true; + } else if (sourceFile.languageVariant === LanguageVariant.JSX) { // // If the tagname is a property access expression, we will then walk up to the top most of property access expression. @@ -1085,6 +1107,9 @@ namespace ts.Completions { if (isRightOfDot || isRightOfQuestionDot) { getTypeScriptMemberSymbols(); } + else if (isRightOfCase || isInsideSwitchBody) { + getSwitchCaseEnumSymbols(); + } else if (isRightOfOpenTag) { const tagSymbols = typeChecker.getJsxIntrinsicTagNamesAt(location); Debug.assertEachIsDefined(tagSymbols, "getJsxIntrinsicTagNames() should all be defined"); @@ -1151,6 +1176,38 @@ namespace ts.Completions { } } + function getSwitchCaseEnumSymbols(): void { + const containerSwitch = findAncestor(contextToken, isSwitchStatement); + if (!containerSwitch) { + return; + } + + const type = typeChecker.getTypeAtLocation(containerSwitch.expression); + if (type && type.symbol && type.symbol.flags & SymbolFlags.Enum && type !== typeChecker.getAnyType()) { + const seens = new Set(); + containerSwitch.caseBlock.clauses.forEach(clause => { + if (isDefaultClause(clause)) { + return; + } + const existedSymbol = typeChecker.getSymbolAtLocation(clause.expression); + if (existedSymbol) { + seens.add(getSymbolId(existedSymbol)); + } + }); + + const symbol = skipAlias(type.symbol, typeChecker); + const exportedSymbols = typeChecker.getExportsOfModule(symbol); + for (const exportedSymbol of exportedSymbols) { + if (seens.has(getSymbolId(exportedSymbol))) { + continue; + } + + symbolToOriginInfoMap[getSymbolId(exportedSymbol)] = { kind: SymbolOriginInfoKind.Enum, needCase: isInsideSwitchBody } as SymbolOriginInfoEnum; + symbols.push(exportedSymbol); + } + } + } + function getTypeScriptMemberSymbols(): void { // Right of dot member completion list completionKind = CompletionKind.PropertyAccess; diff --git a/tests/cases/fourslash/completionsForCaseWithEnum1.ts b/tests/cases/fourslash/completionsForCaseWithEnum1.ts new file mode 100644 index 0000000000000..cd2ff8b602d78 --- /dev/null +++ b/tests/cases/fourslash/completionsForCaseWithEnum1.ts @@ -0,0 +1,21 @@ +/// +//// enum E { +//// A, +//// B, +//// } +//// declare const e: E +//// switch (e) { +//// case /*1*/ +//// } + +verify.completions({ + marker: "1", + includes: [{ + name: "E.A", insertText: "E.A" + }, { + name: "E.B", insertText: "E.B" + }], + preferences: { + includeInsertTextCompletions: true + } +}); diff --git a/tests/cases/fourslash/completionsForCaseWithEnum2.ts b/tests/cases/fourslash/completionsForCaseWithEnum2.ts new file mode 100644 index 0000000000000..bc5404d175f76 --- /dev/null +++ b/tests/cases/fourslash/completionsForCaseWithEnum2.ts @@ -0,0 +1,22 @@ +/// +//// enum E { +//// A, +//// B, +//// } +//// declare const e: E +//// switch (e) { +//// case E.B: break; +//// case /*1*/ +//// } + +verify.completions({ + marker: "1", + includes: [{ + name: "E.A", insertText: "E.A" + }], + excludes: ['E.B'], + preferences: { + includeInsertTextCompletions: true + } +}); + diff --git a/tests/cases/fourslash/completionsForCaseWithEnum3.ts b/tests/cases/fourslash/completionsForCaseWithEnum3.ts new file mode 100644 index 0000000000000..ff815c46cf1f2 --- /dev/null +++ b/tests/cases/fourslash/completionsForCaseWithEnum3.ts @@ -0,0 +1,22 @@ +/// +//// enum E { +//// A = "A", +//// B = "B", +//// } +//// declare const e: E +//// switch (e) { +//// case E.B: break; +//// case /*1*/ +//// } + +verify.completions({ + marker: "1", + includes: [{ + name: "E.A", insertText: "E.A" + }], + excludes: ['E.B'], + preferences: { + includeInsertTextCompletions: true + } +}); + diff --git a/tests/cases/fourslash/completionsForCaseWithEnum4.ts b/tests/cases/fourslash/completionsForCaseWithEnum4.ts new file mode 100644 index 0000000000000..ec9160e0538c5 --- /dev/null +++ b/tests/cases/fourslash/completionsForCaseWithEnum4.ts @@ -0,0 +1,22 @@ +/// +//// enum E { +//// A = "A", +//// B = "B", +//// } +//// declare const e: E +//// switch (e) { +//// /*1*/ +//// } + +verify.completions({ + marker: "1", + includes: [{ + name: "E.A", insertText: "case E.A:" + }, { + name: "E.B", insertText: "case E.B:" + }], + preferences: { + includeInsertTextCompletions: true + } +}); + diff --git a/tests/cases/fourslash/completionsForCaseWithEnum5.ts b/tests/cases/fourslash/completionsForCaseWithEnum5.ts new file mode 100644 index 0000000000000..8bc1e0226c7d8 --- /dev/null +++ b/tests/cases/fourslash/completionsForCaseWithEnum5.ts @@ -0,0 +1,21 @@ +/// +//// enum E { +//// A = "A", +//// B = "B", +//// } +//// declare const e: E +//// switch (e) { +//// case E.B: break; +//// /*1*/ +//// } + +verify.completions({ + marker: "1", + includes: [{ + name: "E.A", insertText: "case E.A:" + }], + excludes: ['E.B'], + preferences: { + includeInsertTextCompletions: true + } +}); From b8ba05a210dba24944d17ea83217734d9381bd45 Mon Sep 17 00:00:00 2001 From: kingwl Date: Wed, 19 Aug 2020 14:23:37 +0800 Subject: [PATCH 2/2] fix test case --- src/services/completions.ts | 9 +++++++-- .../fourslash/completionsForCaseWithEnum1.ts | 5 ++++- .../fourslash/completionsForCaseWithEnum6.ts | 20 +++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 tests/cases/fourslash/completionsForCaseWithEnum6.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index acb8a6e374bf8..f0f1b7d79f64e 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1184,6 +1184,13 @@ namespace ts.Completions { const type = typeChecker.getTypeAtLocation(containerSwitch.expression); if (type && type.symbol && type.symbol.flags & SymbolFlags.Enum && type !== typeChecker.getAnyType()) { + const symbol = skipAlias(type.symbol, typeChecker); + const exportedSymbols = typeChecker.getExportsOfModule(symbol); + if (!exportedSymbols.length) { + symbols.push(symbol); + return; + } + const seens = new Set(); containerSwitch.caseBlock.clauses.forEach(clause => { if (isDefaultClause(clause)) { @@ -1195,8 +1202,6 @@ namespace ts.Completions { } }); - const symbol = skipAlias(type.symbol, typeChecker); - const exportedSymbols = typeChecker.getExportsOfModule(symbol); for (const exportedSymbol of exportedSymbols) { if (seens.has(getSymbolId(exportedSymbol))) { continue; diff --git a/tests/cases/fourslash/completionsForCaseWithEnum1.ts b/tests/cases/fourslash/completionsForCaseWithEnum1.ts index cd2ff8b602d78..183f598e40591 100644 --- a/tests/cases/fourslash/completionsForCaseWithEnum1.ts +++ b/tests/cases/fourslash/completionsForCaseWithEnum1.ts @@ -7,9 +7,12 @@ //// switch (e) { //// case /*1*/ //// } +//// switch (e) { +//// case /*2*/: +//// } verify.completions({ - marker: "1", + marker: ["1", "2"], includes: [{ name: "E.A", insertText: "E.A" }, { diff --git a/tests/cases/fourslash/completionsForCaseWithEnum6.ts b/tests/cases/fourslash/completionsForCaseWithEnum6.ts new file mode 100644 index 0000000000000..795883cfacd6d --- /dev/null +++ b/tests/cases/fourslash/completionsForCaseWithEnum6.ts @@ -0,0 +1,20 @@ +/// +//// enum E { +//// A, +//// B, +//// } +//// declare const e: E +//// switch (e) { +//// case /*1*/ +//// } +//// switch (e) { +//// case /*2*/: +//// } + +verify.completions({ + marker: ["1", "2"], + excludes: ["E.A", "E.B"], + preferences: { + includeInsertTextCompletions: false + } +});