From 25c5e570914ed6f83ed692c825506c1c4f02fff4 Mon Sep 17 00:00:00 2001 From: Marius Schulz Date: Sat, 30 Dec 2017 09:40:53 +0100 Subject: [PATCH] Return string completions for indexed access types --- src/compiler/utilities.ts | 2 +- src/services/completions.ts | 20 ++++++++++++++----- .../reference/api/tsserverlibrary.d.ts | 2 +- tests/baselines/reference/api/typescript.d.ts | 2 +- ...letionForStringLiteralInIndexedAccess01.ts | 13 ++++++++++++ 5 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 tests/cases/fourslash/completionForStringLiteralInIndexedAccess01.ts diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 8a3e313231..fce141490a 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4403,7 +4403,7 @@ namespace ts { return node.kind === SyntaxKind.RegularExpressionLiteral; } - export function isNoSubstitutionTemplateLiteral(node: Node): node is LiteralExpression { + export function isNoSubstitutionTemplateLiteral(node: Node): node is NoSubstitutionTemplateLiteral { return node.kind === SyntaxKind.NoSubstitutionTemplateLiteral; } diff --git a/src/services/completions.ts b/src/services/completions.ts index ed1933c361..bc1cb4d677 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -264,7 +264,8 @@ namespace ts.Completions { // } // let a: A; // a['/*completion position*/'] - return getStringLiteralCompletionEntriesFromElementAccess(node.parent, typeChecker, compilerOptions.target, log); + const type = typeChecker.getTypeAtLocation(node.parent.expression); + return getStringLiteralCompletionEntriesFromElementAccessOrIndexedAccess(node, type, typeChecker, compilerOptions.target, log); } else if (node.parent.kind === SyntaxKind.ImportDeclaration || node.parent.kind === SyntaxKind.ExportDeclaration || isRequireCall(node.parent, /*checkArgumentIsStringLiteral*/ false) || isImportCall(node.parent) @@ -278,6 +279,16 @@ namespace ts.Completions { const entries = PathCompletions.getStringLiteralCompletionsFromModuleNames(node, compilerOptions, host, typeChecker); return pathCompletionsInfo(entries); } + else if (isIndexedAccessTypeNode(node.parent.parent)) { + // Get all apparent property names + // i.e. interface Foo { + // foo: string; + // bar: string; + // } + // let x: Foo["/*completion position*/"] + const type = typeChecker.getTypeFromTypeNode(node.parent.parent.objectType); + return getStringLiteralCompletionEntriesFromElementAccessOrIndexedAccess(node, type, typeChecker, compilerOptions.target, log); + } else { const argumentInfo = SignatureHelp.getImmediatelyContainingArgumentInfo(node, position, sourceFile); if (argumentInfo) { @@ -334,11 +345,10 @@ namespace ts.Completions { return undefined; } - function getStringLiteralCompletionEntriesFromElementAccess(node: ElementAccessExpression, typeChecker: TypeChecker, target: ScriptTarget, log: Log): CompletionInfo | undefined { - const type = typeChecker.getTypeAtLocation(node.expression); + function getStringLiteralCompletionEntriesFromElementAccessOrIndexedAccess(stringLiteralNode: StringLiteral | NoSubstitutionTemplateLiteral, type: Type, typeChecker: TypeChecker, target: ScriptTarget, log: Log): CompletionInfo | undefined { const entries: CompletionEntry[] = []; if (type) { - getCompletionEntriesFromSymbols(type.getApparentProperties(), entries, node, /*performCharacterChecks*/ false, typeChecker, target, log, /*allowStringLiteral*/ true); + getCompletionEntriesFromSymbols(type.getApparentProperties(), entries, stringLiteralNode, /*performCharacterChecks*/ false, typeChecker, target, log, /*allowStringLiteral*/ true); if (entries.length) { return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: true, entries }; } @@ -1583,7 +1593,7 @@ namespace ts.Completions { switch (contextToken.kind) { case SyntaxKind.OpenParenToken: case SyntaxKind.CommaToken: - return isConstructorDeclaration(contextToken.parent) && contextToken.parent; + return isConstructorDeclaration(contextToken.parent) && contextToken.parent; default: if (isConstructorParameterCompletion(contextToken)) { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 6d7a69d350..84ae424e07 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2911,7 +2911,7 @@ declare namespace ts { function isStringLiteral(node: Node): node is StringLiteral; function isJsxText(node: Node): node is JsxText; function isRegularExpressionLiteral(node: Node): node is RegularExpressionLiteral; - function isNoSubstitutionTemplateLiteral(node: Node): node is LiteralExpression; + function isNoSubstitutionTemplateLiteral(node: Node): node is NoSubstitutionTemplateLiteral; function isTemplateHead(node: Node): node is TemplateHead; function isTemplateMiddle(node: Node): node is TemplateMiddle; function isTemplateTail(node: Node): node is TemplateTail; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 4e9f13be2c..c48392e9c1 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2964,7 +2964,7 @@ declare namespace ts { function isStringLiteral(node: Node): node is StringLiteral; function isJsxText(node: Node): node is JsxText; function isRegularExpressionLiteral(node: Node): node is RegularExpressionLiteral; - function isNoSubstitutionTemplateLiteral(node: Node): node is LiteralExpression; + function isNoSubstitutionTemplateLiteral(node: Node): node is NoSubstitutionTemplateLiteral; function isTemplateHead(node: Node): node is TemplateHead; function isTemplateMiddle(node: Node): node is TemplateMiddle; function isTemplateTail(node: Node): node is TemplateTail; diff --git a/tests/cases/fourslash/completionForStringLiteralInIndexedAccess01.ts b/tests/cases/fourslash/completionForStringLiteralInIndexedAccess01.ts new file mode 100644 index 0000000000..0a86f5396c --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralInIndexedAccess01.ts @@ -0,0 +1,13 @@ +/// + +////interface Foo { +//// foo: string; +//// bar: string; +////} +//// +////let x: Foo["/*1*/"] + +goTo.marker("1"); +verify.completionListContains("foo"); +verify.completionListContains("bar"); +verify.completionListCount(2);