Skip to content

Commit be607bd

Browse files
author
Andy
authored
getStringLiteralCompletionEntries: switch on parent.type (#21169)
* getStringLiteralCompletionEntries: switch on parent.type * Use a 'default' case and reduce findPrecedingToken calls * fromType -> fromContextualType
1 parent 964565e commit be607bd

File tree

2 files changed

+85
-66
lines changed

2 files changed

+85
-66
lines changed

src/services/completions.ts

Lines changed: 84 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,14 @@ namespace ts.Completions {
3535
return entries && pathCompletionsInfo(entries);
3636
}
3737

38-
if (isInString(sourceFile, position)) {
39-
return getStringLiteralCompletionEntries(sourceFile, position, typeChecker, compilerOptions, host, log);
38+
const contextToken = findPrecedingToken(position, sourceFile);
39+
40+
if (isInString(sourceFile, position, contextToken)) {
41+
return !contextToken || !isStringLiteral(contextToken) && !isNoSubstitutionTemplateLiteral(contextToken)
42+
? undefined
43+
: getStringLiteralCompletionEntries(sourceFile, contextToken, position, typeChecker, compilerOptions, host, log);
4044
}
4145

42-
const contextToken = findPrecedingToken(position, sourceFile);
4346
if (contextToken && isBreakOrContinueStatement(contextToken.parent)
4447
&& (contextToken.kind === SyntaxKind.BreakKeyword || contextToken.kind === SyntaxKind.ContinueKeyword || contextToken.kind === SyntaxKind.Identifier)) {
4548
return getLabelCompletionAtPosition(contextToken.parent);
@@ -261,70 +264,87 @@ namespace ts.Completions {
261264
}
262265
}
263266

264-
function getStringLiteralCompletionEntries(sourceFile: SourceFile, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost, log: Log): CompletionInfo | undefined {
265-
const node = findPrecedingToken(position, sourceFile);
266-
if (!node || !isStringLiteral(node) && !isNoSubstitutionTemplateLiteral(node)) {
267-
return undefined;
268-
}
267+
function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringLiteralLike, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost, log: Log): CompletionInfo | undefined {
268+
switch (node.parent.kind) {
269+
case SyntaxKind.LiteralType:
270+
switch (node.parent.parent.kind) {
271+
case SyntaxKind.TypeReference:
272+
// TODO: GH#21168
273+
return undefined;
274+
case SyntaxKind.IndexedAccessType:
275+
// Get all apparent property names
276+
// i.e. interface Foo {
277+
// foo: string;
278+
// bar: string;
279+
// }
280+
// let x: Foo["/*completion position*/"]
281+
const type = typeChecker.getTypeFromTypeNode((node.parent.parent as IndexedAccessTypeNode).objectType);
282+
return getStringLiteralCompletionEntriesFromElementAccessOrIndexedAccess(node, sourceFile, type, typeChecker, compilerOptions.target, log);
283+
default:
284+
return undefined;
285+
}
269286

270-
if (node.parent.kind === SyntaxKind.PropertyAssignment &&
271-
node.parent.parent.kind === SyntaxKind.ObjectLiteralExpression &&
272-
(<PropertyAssignment>node.parent).name === node) {
273-
// Get quoted name of properties of the object literal expression
274-
// i.e. interface ConfigFiles {
275-
// 'jspm:dev': string
276-
// }
277-
// let files: ConfigFiles = {
278-
// '/*completion position*/'
279-
// }
280-
//
281-
// function foo(c: ConfigFiles) {}
282-
// foo({
283-
// '/*completion position*/'
284-
// });
285-
return getStringLiteralCompletionEntriesFromPropertyAssignment(<ObjectLiteralElement>node.parent, sourceFile, typeChecker, compilerOptions.target, log);
286-
}
287-
else if (isElementAccessExpression(node.parent) && node.parent.argumentExpression === node) {
288-
// Get all names of properties on the expression
289-
// i.e. interface A {
290-
// 'prop1': string
291-
// }
292-
// let a: A;
293-
// a['/*completion position*/']
294-
const type = typeChecker.getTypeAtLocation(node.parent.expression);
295-
return getStringLiteralCompletionEntriesFromElementAccessOrIndexedAccess(node, sourceFile, type, typeChecker, compilerOptions.target, log);
296-
}
297-
else if (node.parent.kind === SyntaxKind.ImportDeclaration || node.parent.kind === SyntaxKind.ExportDeclaration
298-
|| isRequireCall(node.parent, /*checkArgumentIsStringLiteral*/ false) || isImportCall(node.parent)
299-
|| isExpressionOfExternalModuleImportEqualsDeclaration(node)) {
300-
// Get all known external module names or complete a path to a module
301-
// i.e. import * as ns from "/*completion position*/";
302-
// var y = import("/*completion position*/");
303-
// import x = require("/*completion position*/");
304-
// var y = require("/*completion position*/");
305-
// export * from "/*completion position*/";
306-
const entries = PathCompletions.getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker);
307-
return pathCompletionsInfo(entries);
308-
}
309-
else if (isIndexedAccessTypeNode(node.parent.parent)) {
310-
// Get all apparent property names
311-
// i.e. interface Foo {
312-
// foo: string;
313-
// bar: string;
314-
// }
315-
// let x: Foo["/*completion position*/"]
316-
const type = typeChecker.getTypeFromTypeNode(node.parent.parent.objectType);
317-
return getStringLiteralCompletionEntriesFromElementAccessOrIndexedAccess(node, sourceFile, type, typeChecker, compilerOptions.target, log);
318-
}
319-
else {
320-
const argumentInfo = SignatureHelp.getImmediatelyContainingArgumentInfo(node, position, sourceFile);
321-
if (argumentInfo) {
322-
// Get string literal completions from specialized signatures of the target
323-
// i.e. declare function f(a: 'A');
324-
// f("/*completion position*/")
325-
return getStringLiteralCompletionEntriesFromCallExpression(argumentInfo, typeChecker);
287+
case SyntaxKind.PropertyAssignment:
288+
if (node.parent.parent.kind === SyntaxKind.ObjectLiteralExpression &&
289+
(<PropertyAssignment>node.parent).name === node) {
290+
// Get quoted name of properties of the object literal expression
291+
// i.e. interface ConfigFiles {
292+
// 'jspm:dev': string
293+
// }
294+
// let files: ConfigFiles = {
295+
// '/*completion position*/'
296+
// }
297+
//
298+
// function foo(c: ConfigFiles) {}
299+
// foo({
300+
// '/*completion position*/'
301+
// });
302+
return getStringLiteralCompletionEntriesFromPropertyAssignment(<PropertyAssignment>node.parent, sourceFile, typeChecker, compilerOptions.target, log);
303+
}
304+
return fromContextualType();
305+
306+
case SyntaxKind.ElementAccessExpression: {
307+
const { expression, argumentExpression } = node.parent as ElementAccessExpression;
308+
if (node === argumentExpression) {
309+
// Get all names of properties on the expression
310+
// i.e. interface A {
311+
// 'prop1': string
312+
// }
313+
// let a: A;
314+
// a['/*completion position*/']
315+
const type = typeChecker.getTypeAtLocation(expression);
316+
return getStringLiteralCompletionEntriesFromElementAccessOrIndexedAccess(node, sourceFile, type, typeChecker, compilerOptions.target, log);
317+
}
318+
break;
326319
}
327320

321+
case SyntaxKind.CallExpression:
322+
case SyntaxKind.NewExpression:
323+
if (!isRequireCall(node.parent, /*checkArgumentIsStringLiteral*/ false) && !isImportCall(node.parent)) {
324+
const argumentInfo = SignatureHelp.getImmediatelyContainingArgumentInfo(node, position, sourceFile);
325+
// Get string literal completions from specialized signatures of the target
326+
// i.e. declare function f(a: 'A');
327+
// f("/*completion position*/")
328+
return argumentInfo ? getStringLiteralCompletionEntriesFromCallExpression(argumentInfo, typeChecker) : fromContextualType();
329+
}
330+
// falls through
331+
332+
case SyntaxKind.ImportDeclaration:
333+
case SyntaxKind.ExportDeclaration:
334+
case SyntaxKind.ExternalModuleReference:
335+
// Get all known external module names or complete a path to a module
336+
// i.e. import * as ns from "/*completion position*/";
337+
// var y = import("/*completion position*/");
338+
// import x = require("/*completion position*/");
339+
// var y = require("/*completion position*/");
340+
// export * from "/*completion position*/";
341+
return pathCompletionsInfo(PathCompletions.getStringLiteralCompletionsFromModuleNames(sourceFile, node as StringLiteral, compilerOptions, host, typeChecker));
342+
343+
default:
344+
return fromContextualType();
345+
}
346+
347+
function fromContextualType(): CompletionInfo {
328348
// Get completion for string literal from string literal type
329349
// i.e. var x: "hi" | "hello" = "/*completion position*/"
330350
return getStringLiteralCompletionEntriesFromType(getContextualTypeFromParent(node, typeChecker), typeChecker);

src/services/utilities.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -833,8 +833,7 @@ namespace ts {
833833
}
834834
}
835835

836-
export function isInString(sourceFile: SourceFile, position: number): boolean {
837-
const previousToken = findPrecedingToken(position, sourceFile);
836+
export function isInString(sourceFile: SourceFile, position: number, previousToken = findPrecedingToken(position, sourceFile)): boolean {
838837
if (previousToken && isStringTextContainingNode(previousToken)) {
839838
const start = previousToken.getStart();
840839
const end = previousToken.getEnd();

0 commit comments

Comments
 (0)