diff --git a/internal/ast/ast.go b/internal/ast/ast.go index 225a85010e..df18a8f367 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -1749,6 +1749,7 @@ type ( ObjectTypeDeclaration = Node // ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode JsxOpeningLikeElement = Node // JsxOpeningElement | JsxSelfClosingElement NamedImportsOrExports = Node // NamedImports | NamedExports + BreakOrContinueStatement = Node // BreakStatement | ContinueStatement ) // Aliases for node singletons diff --git a/internal/ls/completions.go b/internal/ls/completions.go index 257b4b2d87..852328d56a 100644 --- a/internal/ls/completions.go +++ b/internal/ls/completions.go @@ -296,7 +296,12 @@ func (l *LanguageService) getCompletionsAtPosition( // !!! string literal completions - // !!! label completions + if previousToken != nil && ast.IsBreakOrContinueStatement(previousToken.Parent) && + (previousToken.Kind == ast.KindBreakKeyword || + previousToken.Kind == ast.KindContinueKeyword || + previousToken.Kind == ast.KindIdentifier) { + return l.getLabelCompletionsAtPosition(previousToken.Parent, clientOptions, file, position) + } checker, done := program.GetTypeCheckerForFile(ctx, file) defer done() @@ -4103,3 +4108,66 @@ func (l *LanguageService) createLSPCompletionItem( Data: nil, // !!! auto-imports } } + +func (l *LanguageService) getLabelCompletionsAtPosition( + node *ast.BreakOrContinueStatement, + clientOptions *lsproto.CompletionClientCapabilities, + file *ast.SourceFile, + position int, +) *lsproto.CompletionList { + items := l.getLabelStatementCompletions(node, clientOptions, file, position) + if len(items) == 0 { + return nil + } + defaultCommitCharacters := getDefaultCommitCharacters(false /*isNewIdentifierLocation*/) + itemDefaults := setCommitCharacters(clientOptions, items, &defaultCommitCharacters) + return &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: items, + } +} + +func (l *LanguageService) getLabelStatementCompletions( + node *ast.BreakOrContinueStatement, + clientOptions *lsproto.CompletionClientCapabilities, + file *ast.SourceFile, + position int, +) []*lsproto.CompletionItem { + var uniques core.Set[string] + var items []*lsproto.CompletionItem + current := node + for current != nil { + if ast.IsFunctionLike(current) { + break + } + if ast.IsLabeledStatement(current) { + name := current.Label().Text() + if !uniques.Has(name) { + uniques.Add(name) + items = append(items, l.createLSPCompletionItem( + name, + "", /*insertText*/ + "", /*filterText*/ + SortTextLocationPriority, + ScriptElementKindLabel, + core.Set[ScriptElementKindModifier]{}, /*kindModifiers*/ + nil, /*replacementSpan*/ + nil, /*optionalReplacementSpan*/ + nil, /*commitCharacters*/ + nil, /*labelDetails*/ + file, + position, + clientOptions, + false, /*isMemberCompletion*/ + false, /*isSnippet*/ + false, /*hasAction*/ + false, /*preselect*/ + "", /*source*/ + )) + } + } + current = current.Parent + } + return items +} diff --git a/internal/ls/completions_test.go b/internal/ls/completions_test.go index 3b9b1edc0d..801fc8e7c3 100644 --- a/internal/ls/completions_test.go +++ b/internal/ls/completions_test.go @@ -54,6 +54,7 @@ func TestCompletions(t *testing.T) { variableKind := ptrTo(lsproto.CompletionItemKindVariable) classKind := ptrTo(lsproto.CompletionItemKindClass) keywordKind := ptrTo(lsproto.CompletionItemKindKeyword) + propertyKind := ptrTo(lsproto.CompletionItemKindProperty) stringMembers := []*lsproto.CompletionItem{ {Label: "charAt", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, @@ -1528,6 +1529,156 @@ function fn3() { }, }, }, + { + name: "completionListWithLabel", + files: map[string]string{ + defaultMainFileName: `label: while (true) { + break /*1*/ + continue /*2*/ + testlabel: while (true) { + break /*3*/ + continue /*4*/ + break tes/*5*/ + continue tes/*6*/ + } + break /*7*/ + break; /*8*/ +}`, + }, + expectedResult: map[string]*testCaseResult{ + "1": { + list: &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: []*lsproto.CompletionItem{ + { + Label: "label", + Kind: propertyKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + }, + }, + }, + }, + "2": { + list: &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: []*lsproto.CompletionItem{ + { + Label: "label", + Kind: propertyKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + }, + }, + }, + }, + "7": { + list: &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: []*lsproto.CompletionItem{ + { + Label: "label", + Kind: propertyKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + }, + }, + }, + }, + "3": { + list: &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: []*lsproto.CompletionItem{ + { + Label: "testlabel", + Kind: propertyKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + }, + { + Label: "label", + Kind: propertyKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + }, + }, + }, + }, + "4": { + list: &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: []*lsproto.CompletionItem{ + { + Label: "testlabel", + Kind: propertyKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + }, + { + Label: "label", + Kind: propertyKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + }, + }, + }, + }, + "5": { + list: &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: []*lsproto.CompletionItem{ + { + Label: "testlabel", + Kind: propertyKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + }, + { + Label: "label", + Kind: propertyKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + }, + }, + }, + }, + "6": { + list: &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: []*lsproto.CompletionItem{ + { + Label: "testlabel", + Kind: propertyKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + }, + { + Label: "label", + Kind: propertyKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + }, + }, + }, + }, + "8": { + list: &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: []*lsproto.CompletionItem{}, + }, + isIncludes: true, + excludes: []string{"label"}, + }, + }, + }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) {