diff --git a/src/services/completions.ts b/src/services/completions.ts index 1fcd947e5bb..744152022bb 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -83,7 +83,7 @@ namespace ts.Completions { } case StringLiteralCompletionKind.Types: { const entries = completion.types.map(type => ({ name: type.value, kindModifiers: ScriptElementKindModifier.none, kind: ScriptElementKind.typeElement, sortText: "0" })); - return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries }; + return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: completion.isNewIdentifier, entries }; } default: return Debug.assertNever(completion); @@ -358,16 +358,18 @@ namespace ts.Completions { readonly symbols: ReadonlyArray; readonly hasIndexSignature: boolean; } - type StringLiteralCompletion = - | { readonly kind: StringLiteralCompletionKind.Paths, readonly paths: ReadonlyArray } - | StringLiteralCompletionsFromProperties - | { readonly kind: StringLiteralCompletionKind.Types, readonly types: ReadonlyArray }; + interface StringLiteralCompletionsFromTypes { + readonly kind: StringLiteralCompletionKind.Types; + readonly types: ReadonlyArray; + readonly isNewIdentifier: boolean; + } + type StringLiteralCompletion = { readonly kind: StringLiteralCompletionKind.Paths, readonly paths: ReadonlyArray } | StringLiteralCompletionsFromProperties | StringLiteralCompletionsFromTypes; function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringLiteralLike, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost): StringLiteralCompletion | undefined { switch (node.parent.kind) { case SyntaxKind.LiteralType: switch (node.parent.parent.kind) { case SyntaxKind.TypeReference: - return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(node.parent as LiteralTypeNode), typeChecker) }; + return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(node.parent as LiteralTypeNode), typeChecker), isNewIdentifier: false }; case SyntaxKind.IndexedAccessType: // Get all apparent property names // i.e. interface Foo { @@ -419,13 +421,7 @@ namespace ts.Completions { // Get string literal completions from specialized signatures of the target // i.e. declare function f(a: 'A'); // f("/*completion position*/") - if (argumentInfo) { - const candidates: Signature[] = []; - typeChecker.getResolvedSignature(argumentInfo.invocation, candidates, argumentInfo.argumentCount); - const uniques = createMap(); - return { kind: StringLiteralCompletionKind.Types, types: flatMap(candidates, candidate => getStringLiteralTypes(typeChecker.getParameterType(candidate, argumentInfo.argumentIndex), typeChecker, uniques)) }; - } - return fromContextualType(); + return argumentInfo ? getStringLiteralCompletionsFromSignature(argumentInfo, typeChecker) : fromContextualType(); } // falls through (is `require("")` or `import("")`) @@ -447,10 +443,26 @@ namespace ts.Completions { function fromContextualType(): StringLiteralCompletion { // Get completion for string literal from string literal type // i.e. var x: "hi" | "hello" = "/*completion position*/" - return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker), typeChecker) }; + return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker), typeChecker), isNewIdentifier: false }; } } + function getStringLiteralCompletionsFromSignature(argumentInfo: SignatureHelp.ArgumentListInfo, checker: TypeChecker): StringLiteralCompletionsFromTypes { + let isNewIdentifier = false; + + const uniques = createMap(); + const candidates: Signature[] = []; + checker.getResolvedSignature(argumentInfo.invocation, candidates, argumentInfo.argumentCount); + const types = flatMap(candidates, candidate => { + if (!candidate.hasRestParameter && argumentInfo.argumentCount > candidate.parameters.length) return; + const type = checker.getParameterType(candidate, argumentInfo.argumentIndex); + isNewIdentifier = isNewIdentifier || !!(type.flags & TypeFlags.String); + return getStringLiteralTypes(type, checker, uniques); + }); + + return { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier }; + } + function stringLiteralCompletionsFromProperties(type: Type | undefined): StringLiteralCompletionsFromProperties | undefined { return type && { kind: StringLiteralCompletionKind.Properties, symbols: type.getApparentProperties(), hasIndexSignature: hasIndexSignature(type) }; } diff --git a/tests/cases/fourslash/completionForStringLiteral3.ts b/tests/cases/fourslash/completionForStringLiteral3.ts index 7b4713a81a7..4e0a2133a0a 100644 --- a/tests/cases/fourslash/completionForStringLiteral3.ts +++ b/tests/cases/fourslash/completionForStringLiteral3.ts @@ -9,4 +9,4 @@ //// ////f("/*2*/ -verify.completionsAt(["1", "2"], ["A", "B", "C"]); +verify.completionsAt(["1", "2"], ["A", "B", "C"], { isNewIdentifierLocation: true }); diff --git a/tests/cases/fourslash/completionForStringLiteralFromSignature.ts b/tests/cases/fourslash/completionForStringLiteralFromSignature.ts new file mode 100644 index 00000000000..ec7d046ef9b --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralFromSignature.ts @@ -0,0 +1,7 @@ +/// + +////declare function f(a: "x"): void; +////declare function f(a: string): void; +////f("/**/"); + +verify.completionsAt("", ["x"], { isNewIdentifierLocation: true }); diff --git a/tests/cases/fourslash/completionForStringLiteralFromSignature2.ts b/tests/cases/fourslash/completionForStringLiteralFromSignature2.ts new file mode 100644 index 00000000000..176efac4e55 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralFromSignature2.ts @@ -0,0 +1,7 @@ +/// + +////declare function f(a: "x"): void; +////declare function f(a: string, b: number): void; +////f("/**/", 0); + +verify.completionsAt("", [], { isNewIdentifierLocation: true });