-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
lsp: implement used refs completions (#794)
This implements a completions provider that makes suggestions based on refs users have already typed into their files. Signed-off-by: Charlie Egan <[email protected]>
- Loading branch information
1 parent
b4eaad3
commit fdda4b5
Showing
15 changed files
with
407 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package providers | ||
|
||
import ( | ||
"strings" | ||
|
||
"github.com/styrainc/regal/internal/lsp/cache" | ||
"github.com/styrainc/regal/internal/lsp/types" | ||
"github.com/styrainc/regal/internal/lsp/types/completion" | ||
) | ||
|
||
// UsedRefs is a completion provider that provides completions for refs | ||
// that have already been typed into a module. | ||
type UsedRefs struct{} | ||
|
||
func (*UsedRefs) Run(c *cache.Cache, params types.CompletionParams, _ *Options) ([]types.CompletionItem, error) { | ||
fileURI := params.TextDocument.URI | ||
|
||
lines, currentLine := completionLineHelper(c, fileURI, params.Position.Line) | ||
if len(lines) < 1 || currentLine == "" { | ||
return []types.CompletionItem{}, nil | ||
} | ||
|
||
if !inRuleBody(currentLine) { | ||
return []types.CompletionItem{}, nil | ||
} | ||
|
||
words := patternWhiteSpace.Split(strings.TrimSpace(currentLine), -1) | ||
lastWord := words[len(words)-1] | ||
|
||
refNames, ok := c.GetUsedRefs(fileURI) | ||
if !ok { | ||
return []types.CompletionItem{}, nil | ||
} | ||
|
||
items := []types.CompletionItem{} | ||
|
||
for _, ref := range refNames { | ||
if !strings.HasPrefix(ref, lastWord) { | ||
continue | ||
} | ||
|
||
items = append(items, types.CompletionItem{ | ||
Label: ref, | ||
Kind: completion.Reference, | ||
Detail: "Existing ref used in module", | ||
Documentation: &types.MarkupContent{ | ||
Kind: "markdown", | ||
Value: `Existing ref used in module`, | ||
}, | ||
TextEdit: &types.TextEdit{ | ||
Range: types.Range{ | ||
Start: types.Position{ | ||
Line: params.Position.Line, | ||
Character: params.Position.Character - uint(len(lastWord)), | ||
}, | ||
End: types.Position{ | ||
Line: params.Position.Line, Character: params.Position.Character, | ||
}, | ||
}, | ||
NewText: ref, | ||
}, | ||
}) | ||
} | ||
|
||
return items, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package providers | ||
|
||
import ( | ||
"context" | ||
"slices" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/styrainc/regal/internal/lsp/cache" | ||
"github.com/styrainc/regal/internal/lsp/completions/refs" | ||
"github.com/styrainc/regal/internal/lsp/types" | ||
"github.com/styrainc/regal/internal/parse" | ||
) | ||
|
||
func TestUsedRefs(t *testing.T) { | ||
t.Parallel() | ||
|
||
c := cache.NewCache() | ||
|
||
currentlyEditingFileContents := `package example | ||
import rego.v1 | ||
import data.foo as wow | ||
import data.bar | ||
allow if input.user == "admin" | ||
allow if data.users.admin == input.user | ||
deny contains wow.password if { | ||
input.magic == true | ||
} | ||
deny contains input.parrot if { | ||
bar.parrot != "a bird" | ||
} | ||
` | ||
|
||
uri := "file:///example.rego" | ||
|
||
mod, err := parse.Module(uri, currentlyEditingFileContents) | ||
if err != nil { | ||
t.Fatalf("Unexpected error when parsing %s contents: %v", uri, err) | ||
} | ||
|
||
c.SetModule(uri, mod) | ||
c.SetFileRefs(uri, refs.DefinedInModule(mod)) | ||
|
||
refNames, err := refs.UsedInModule(context.Background(), mod) | ||
if err != nil { | ||
t.Fatalf("Unexpected error: %s", err) | ||
} | ||
|
||
c.SetUsedRefs(uri, refNames) | ||
|
||
c.SetFileContents(uri, currentlyEditingFileContents+` | ||
allow if { | ||
true == i | ||
}`) | ||
|
||
p := &UsedRefs{} | ||
|
||
completionParams := types.CompletionParams{ | ||
TextDocument: types.TextDocumentIdentifier{ | ||
URI: uri, | ||
}, | ||
Position: types.Position{ | ||
Line: 20, | ||
Character: 11, | ||
}, | ||
} | ||
|
||
completions, err := p.Run(c, completionParams, nil) | ||
if err != nil { | ||
t.Fatalf("Unexpected error: %v", err) | ||
} | ||
|
||
expectedRefs := []string{ | ||
"input.magic", | ||
"input.parrot", | ||
"input.user", | ||
} | ||
slices.Sort(expectedRefs) | ||
|
||
foundRefs := make([]string, len(completions)) | ||
|
||
for i, c := range completions { | ||
foundRefs[i] = c.Label | ||
} | ||
|
||
slices.Sort(foundRefs) | ||
|
||
if !slices.Equal(expectedRefs, foundRefs) { | ||
t.Fatalf( | ||
"Expected completions to be\n%s\ngot:\n%s", | ||
strings.Join(expectedRefs, "\n"), | ||
strings.Join(foundRefs, "\n"), | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package lsp.completions | ||
|
||
import rego.v1 | ||
|
||
import data.regal.ast | ||
|
||
# ref_names returns a list of ref names that are used in the module. | ||
# built-in functions are not included as they are provided by another completions provider. | ||
# imports are not included as we need to use the imported_identifier instead | ||
# (i.e. maybe an alias). | ||
ref_names contains name if { | ||
some ref in ast.all_refs | ||
|
||
name := ast.ref_to_string(ref.value) | ||
|
||
not name in ast.builtin_functions_called | ||
not name in imports | ||
} | ||
|
||
# if a user has imported data.foo, then foo should be suggested. | ||
# if they have imported data.foo as bar, then bar should be suggested. | ||
# this also has the benefit of skipping future.* and rego.v1 as | ||
# imported_identifiers will only match data.* and input.* | ||
ref_names contains name if { | ||
some name in ast.imported_identifiers | ||
} | ||
|
||
# imports are not shown as we need to show the imported alias instead | ||
imports contains ref if { | ||
some imp in ast.imports | ||
|
||
ref := ast.ref_to_string(imp.path.value) | ||
} |
Oops, something went wrong.