Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f6c3566
fix services' type's isLiteral
gabritto Sep 23, 2022
4467330
update literal completions tests
gabritto Sep 23, 2022
a206fe1
initial prototype
gabritto Sep 7, 2022
73c1eea
use symbol to expression. TODO: filter existing, replace import nodes
gabritto Sep 16, 2022
5648cba
WIP
gabritto Sep 23, 2022
d46c0d2
WIP
gabritto Sep 28, 2022
297f892
remove booleans from literals
gabritto Sep 28, 2022
fd1d6ed
Merge branch 'gabritto/servicesIsLiteral' into gabritto/switchsnippet
gabritto Sep 28, 2022
4c528b3
trigger at case keyword positions
gabritto Sep 29, 2022
1a5cd05
clean up tests
gabritto Nov 8, 2022
ee42732
fix element access expression case
gabritto Nov 9, 2022
1819d0b
refactor dealing with existing values into a tracker
gabritto Nov 9, 2022
b19543e
Merge branch 'main' into gabritto/switchsnippet
gabritto Nov 10, 2022
bd5b817
fix merge errors
gabritto Nov 10, 2022
f02122b
cleanup and more tests
gabritto Nov 10, 2022
a35bc4a
fix lint errors
gabritto Nov 10, 2022
83b88f7
more merge conflict fixes and cleanup
gabritto Nov 11, 2022
599fb30
use appropriate quotes
gabritto Nov 11, 2022
97dcf69
small indentation fix
gabritto Nov 11, 2022
3b92638
refactor case clause tracker
gabritto Nov 14, 2022
89f6f6b
Merge branch 'main' into gabritto/switchsnippet
gabritto Nov 14, 2022
1894d2e
experiment: support tabstops after each case clause
gabritto Nov 22, 2022
90767fc
address small CR comments
gabritto Nov 23, 2022
d1c8968
fix completion entry details; add test case
gabritto Nov 30, 2022
fb15ba1
Merge branch 'main' into gabritto/switchsnippet
gabritto Nov 30, 2022
3980b93
fix lint errors
gabritto Dec 1, 2022
8823108
remove space before tab stops; refactor
gabritto Dec 1, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
initial prototype
  • Loading branch information
gabritto committed Sep 23, 2022
commit a206fe1407d4bb2c9dda2a7b7b5cdae7df3fa584
5 changes: 5 additions & 0 deletions src/compiler/factory/nodeTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@ namespace ts {
return node.kind === SyntaxKind.ImportKeyword;
}

/*@internal*/
export function isCaseKeyword(node: Node): node is CaseKeyword {
return node.kind === SyntaxKind.CaseKeyword;
}

// Names

export function isQualifiedName(node: Node): node is QualifiedName {
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1326,6 +1326,7 @@ namespace ts {
export type AssertsKeyword = KeywordToken<SyntaxKind.AssertsKeyword>;
export type AssertKeyword = KeywordToken<SyntaxKind.AssertKeyword>;
export type AwaitKeyword = KeywordToken<SyntaxKind.AwaitKeyword>;
export type CaseKeyword = KeywordToken<SyntaxKind.CaseKeyword>;

/** @deprecated Use `AwaitKeyword` instead. */
export type AwaitKeywordToken = AwaitKeyword;
Expand Down
107 changes: 101 additions & 6 deletions src/services/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,12 +264,13 @@ namespace ts.Completions {
}

if (triggerCharacter === " ") {
// `isValidTrigger` ensures we are at `import |`
if (preferences.includeCompletionsForImportStatements && preferences.includeCompletionsWithInsertText) {
return { isGlobalCompletion: true, isMemberCompletion: false, isNewIdentifierLocation: true, isIncomplete: true, entries: [] };
// `isValidTrigger` ensures we are at `import |` or `case |`.
if (previousToken && isImportKeyword(previousToken)) {
if (preferences.includeCompletionsForImportStatements && preferences.includeCompletionsWithInsertText) {
return { isGlobalCompletion: true, isMemberCompletion: false, isNewIdentifierLocation: true, isIncomplete: true, entries: [] };
}
return undefined;
}
return undefined;

}

// If the request is a continuation of an earlier `isIncomplete` response,
Expand Down Expand Up @@ -564,6 +565,14 @@ namespace ts.Completions {
getJSCompletionEntries(sourceFile, location.pos, uniqueNames, getEmitScriptTarget(compilerOptions), entries);
}

// >> TODO: prototype remaining cases here
if (contextToken && isCaseKeyword(contextToken) && preferences.includeCompletionsWithInsertText) {
const casesEntry = getExhaustiveCaseSnippets(contextToken, sourceFile, preferences, compilerOptions, host, program.getTypeChecker(), formatContext);
if (casesEntry) {
entries.push(casesEntry);
}
}

return {
flags: completionData.flags,
isGlobalCompletion: isInSnippetScope,
Expand All @@ -579,6 +588,90 @@ namespace ts.Completions {
return !isSourceFileJS(sourceFile) || !!isCheckJsEnabledForFile(sourceFile, compilerOptions);
}

function getExhaustiveCaseSnippets(
contextToken: Token<SyntaxKind.CaseKeyword>,
sourceFile: SourceFile,
preferences: UserPreferences,
options: CompilerOptions,
host: LanguageServiceHost,
checker: TypeChecker,
formatContext: formatting.FormatContext | undefined): CompletionEntry | undefined {
const caseClause = tryCast(contextToken.parent, isCaseClause);
if (caseClause) {
const switchType = getSwitchedType(caseClause, checker);
if (switchType && switchType.isUnion() && every(switchType.types, type => type.isLiteral())) { // >> TODO: does this work for enum members?
const elements: [string, Expression][] = mapDefined(switchType.types as LiteralType[], type => {
if (type.flags & TypeFlags.EnumLiteral) {
Debug.assert(type.symbol, "TODO: should this hold always?");
Debug.assert(type.symbol.parent, "TODO: should this hold always too?");
const target = getEmitScriptTarget(options);
// >> TODO: figure out if need an import action
const memberInfo = getCompletionEntryDisplayNameForSymbol(
type.symbol,
target,
/*origin*/ undefined,
CompletionKind.None, /*jsxIdentifierExpected*/ false);
const enumInfo = getCompletionEntryDisplayNameForSymbol(
type.symbol.parent,
target,
/*origin*/ undefined,
CompletionKind.None, /*jsxIdentifierExpected*/ false);
if (memberInfo && enumInfo) {
const enumExp = factory.createIdentifier(enumInfo.name); // >> TODO: have to properly get the exp
const access: Expression = factory.createPropertyAccessExpression(enumExp, memberInfo.name); // >> TODO: we might need to use [] to access this member
// >> TODO: convert access to text properly
return [`${enumInfo.name}.${memberInfo.name}`, access]; // >> TODO: this looks hacky, do it some other way
}
}
else {
const text = completionNameForLiteral(sourceFile, preferences, type.value);
const literal: Expression = typeof type.value === "object"
? factory.createBigIntLiteral(type.value)
: typeof type.value === "number"
? factory.createNumericLiteral(type.value)
: factory.createStringLiteral(type.value);
return [text, literal];
}
return undefined; // >> TODO: actually only do this if every return is defined,
// >> otherwise won't really be exhaustive
});

const clauses = map(elements, element => {
return factory.createCaseClause(element[1], []);
});
const printer = createSnippetPrinter({
removeComments: true,
module: options.module,
target: options.target,
newLine: getNewLineKind(getNewLineCharacter(options, maybeBind(host, host.getNewLine))),
});
// >> TODO: get format context and format before printing, if possible
const insertText = formatContext
? printer.printAndFormatSnippetList(
ListFormat.MultiLine | ListFormat.NoTrailingNewLine,
factory.createNodeArray(clauses),
sourceFile,
formatContext)
: printer.printSnippetList(
ListFormat.MultiLine | ListFormat.NoTrailingNewLine,
factory.createNodeArray(clauses),
sourceFile);

return {
name: elements[0][0],
isRecommended: true, // >> I assume that is ok because if there's another recommended, it will be sorted after this one
kind: ScriptElementKind.unknown, // >> TODO: what should this be?
sortText: SortText.LocalDeclarationPriority,
insertText,
replacementSpan: getReplacementSpanForContextToken(contextToken),
}
// >> TODO: filter cases that are already there
}
}

return undefined;
}

function isMemberCompletionKind(kind: CompletionKind): boolean {
switch (kind) {
case CompletionKind.ObjectPropertyDeclaration:
Expand Down Expand Up @@ -4191,7 +4284,9 @@ namespace ts.Completions {
? !!tryGetImportFromModuleSpecifier(contextToken)
: contextToken.kind === SyntaxKind.SlashToken && isJsxClosingElement(contextToken.parent));
case " ":
return !!contextToken && isImportKeyword(contextToken) && contextToken.parent.kind === SyntaxKind.SourceFile;
return !!contextToken && (
isImportKeyword(contextToken) && contextToken.parent.kind === SyntaxKind.SourceFile ||
contextToken.kind === SyntaxKind.CaseKeyword);
default:
return Debug.assertNever(triggerCharacter);
}
Expand Down
80 changes: 80 additions & 0 deletions tests/cases/fourslash/contextualTypeInSwitch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/// <reference path="fourslash.ts" />

//// enum E {
//// A = 0,
//// B = "B",
//// C = "C",
//// }
//// declare const u: E.A | E.B | 1;
//// switch (u) {
//// case /*1*/
//// }
//// declare const e: E;
//// switch (e) {
//// case /*2*/
//// }
//// enum F {
//// D = 1 << 0,
//// E = 1 << 1,
//// F = 1 << 2,
//// }
//// declare const f: F;
//// switch (f) {
//// case /*3*/
//// }

verify.completions(
{
marker: "1",
isNewIdentifierLocation: false,
includes: [
// {
// name: "A",
// sortText: completion.SortText.LocationPriority,
// },
// {
// name: "B",
// sortText: completion.SortText.LocationPriority,
// }
],
excludes: [
// >> TODO: exclude C
],
},
{
marker: "2",
isNewIdentifierLocation: false,
includes: [
// {
// name: "A",
// sortText: completion.SortText.LocationPriority,
// },
// {
// name: "B",
// sortText: completion.SortText.LocationPriority,
// },
// {
// name: "C",
// sortText: completion.SortText.LocationPriority,
// }
],
},
{
marker: "3",
isNewIdentifierLocation: false,
includes: [
// {
// name: "D",
// sortText: completion.SortText.LocationPriority,
// },
// {
// name: "E",
// sortText: completion.SortText.LocationPriority,
// },
// {
// name: "F",
// sortText: completion.SortText.LocationPriority,
// }
],
},
);