diff --git a/src/services/completions.ts b/src/services/completions.ts index 28837bcce4..6ea177cb70 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -39,6 +39,11 @@ namespace ts.Completions { return getStringLiteralCompletionEntries(sourceFile, position, typeChecker, compilerOptions, host, log); } + const contextToken = findPrecedingToken(position, sourceFile); + if (isInBreakOrContinue(contextToken)) { + return getLabelCompletionAtPosition(contextToken); + } + const completionData = getCompletionData(typeChecker, log, sourceFile, position, allSourceFiles, options, compilerOptions.target); if (!completionData) { return undefined; @@ -223,6 +228,18 @@ namespace ts.Completions { return uniques; } + function isInBreakOrContinue(contextToken: Node): boolean { + return contextToken && isBreakOrContinueStatement(contextToken.parent) && + (contextToken.kind === SyntaxKind.BreakKeyword || contextToken.kind === SyntaxKind.ContinueKeyword || contextToken.kind === SyntaxKind.Identifier); + } + + function getLabelCompletionAtPosition(contextToken: Node): CompletionInfo | undefined { + const entries = getLabelStatementCompletions(contextToken.parent); + if (entries.length) { + return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries }; + } + } + function getStringLiteralCompletionEntries(sourceFile: SourceFile, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost, log: Log): CompletionInfo | undefined { const node = findPrecedingToken(position, sourceFile); if (!node || node.kind !== SyntaxKind.StringLiteral) { @@ -358,6 +375,32 @@ namespace ts.Completions { return undefined; } + function getLabelStatementCompletions(node: Node): CompletionEntry[] { + const entries: CompletionEntry[] = []; + const uniques = createMap(); + let current = node; + + while (current) { + if (isFunctionLike(current)) { + break; + } + if (isLabeledStatement(current)) { + const name = current.label.text; + if (!uniques.has(name)) { + uniques.set(name, true); + entries.push({ + name, + kindModifiers: ScriptElementKindModifier.none, + kind: ScriptElementKind.label, + sortText: "0" + }); + } + } + current = current.parent; + } + return entries; + } + function addStringLiteralCompletionsFromType(type: Type, result: Push, typeChecker: TypeChecker, uniques = createMap()): void { if (type && type.flags & TypeFlags.TypeParameter) { type = typeChecker.getBaseConstraintOfType(type); diff --git a/tests/cases/fourslash/completionListWithLabel.ts b/tests/cases/fourslash/completionListWithLabel.ts new file mode 100644 index 0000000000..1efc0ebb5f --- /dev/null +++ b/tests/cases/fourslash/completionListWithLabel.ts @@ -0,0 +1,44 @@ +/// + +//// label: while (true) { +//// break /*1*/ +//// continue /*2*/ +//// testlabel: while (true) { +//// break /*3*/ +//// continue /*4*/ +//// break tes/*5*/ +//// continue tes/*6*/ +//// } +//// break /*7*/ +//// break; /*8*/ +////} + +goTo.marker("1"); +verify.completionListContains("label"); + +goTo.marker("2"); +verify.completionListContains("label"); +verify.not.completionListContains("testlabel"); + +goTo.marker("3"); +verify.completionListContains("label"); +verify.completionListContains("testlabel"); + +goTo.marker("4"); +verify.completionListContains("label"); +verify.completionListContains("testlabel"); + +goTo.marker("5"); +verify.completionListContains("testlabel"); +verify.completionListContains("label"); + +goTo.marker("6"); +verify.completionListContains("testlabel"); +verify.completionListContains("label"); + +goTo.marker("7"); +verify.completionListContains("label"); +verify.not.completionListContains("testlabel"); + +goTo.marker("8"); +verify.not.completionListContains("label");