From 6fb08783b4bec7e95011a0a3cade13268d9eec68 Mon Sep 17 00:00:00 2001 From: Anders Eknert Date: Tue, 18 Jun 2024 18:18:08 +0200 Subject: [PATCH 1/3] Port Import provider to Rego Signed-off-by: Anders Eknert --- .../completion/providers/import/import.rego | 28 ++++++ .../providers/import/import_test.rego | 37 ++++++++ internal/lsp/completions/manager.go | 1 - internal/lsp/completions/manager_test.go | 6 +- internal/lsp/completions/providers/import.go | 50 ----------- .../lsp/completions/providers/import_test.go | 85 ------------------- 6 files changed, 68 insertions(+), 139 deletions(-) create mode 100644 bundle/regal/lsp/completion/providers/import/import.rego create mode 100644 bundle/regal/lsp/completion/providers/import/import_test.rego delete mode 100644 internal/lsp/completions/providers/import.go delete mode 100644 internal/lsp/completions/providers/import_test.go diff --git a/bundle/regal/lsp/completion/providers/import/import.rego b/bundle/regal/lsp/completion/providers/import/import.rego new file mode 100644 index 00000000..15a39a56 --- /dev/null +++ b/bundle/regal/lsp/completion/providers/import/import.rego @@ -0,0 +1,28 @@ +package regal.lsp.completion.providers["import"] + +import rego.v1 + +import data.regal.lsp.completion.kind +import data.regal.lsp.completion.location + +items contains item if { + position := location.to_position(input.regal.context.location) + line := input.regal.file.lines[position.line] + word := location.word_at(line, input.regal.context.location.col) + + invoke_suggestion(line) + + item := { + "label": "import", + "kind": kind.keyword, + "detail": "import ", + "textEdit": { + "range": location.word_range(word, position), + "newText": "import ", + }, + } +} + +invoke_suggestion("") + +invoke_suggestion(line) if startswith("import", line) diff --git a/bundle/regal/lsp/completion/providers/import/import_test.rego b/bundle/regal/lsp/completion/providers/import/import_test.rego new file mode 100644 index 00000000..cd28785e --- /dev/null +++ b/bundle/regal/lsp/completion/providers/import/import_test.rego @@ -0,0 +1,37 @@ +package regal.lsp.completion.providers.import_test + +import rego.v1 + +import data.regal.lsp.completion.providers["import"] as provider + +test_import_completion_on_typing if { + policy := `package policy + +import rego.v1 + +` + module := regal.parse_module("p.rego", policy) + new_policy := concat("", [policy, "i"]) + items := provider.items with input as input_with_location(module, new_policy, {"row": 5, "col": 2}) + + items == {{ + "label": "import", + "detail": "import ", + "kind": 14, + "textEdit": { + "newText": "import ", + "range": { + "start": {"character": 0, "line": 4}, + "end": {"character": 1, "line": 4}, + }, + }, + }} +} + +input_with_location(module, policy, location) := object.union(module, {"regal": { + "file": { + "name": "p.rego", + "lines": split(policy, "\n"), + }, + "context": {"location": location}, +}}) diff --git a/internal/lsp/completions/manager.go b/internal/lsp/completions/manager.go index 9cb5205e..2ba9330a 100644 --- a/internal/lsp/completions/manager.go +++ b/internal/lsp/completions/manager.go @@ -32,7 +32,6 @@ func NewDefaultManager(c *cache.Cache, store storage.Store) *Manager { m.RegisterProvider(&providers.Package{}) m.RegisterProvider(&providers.PackageName{}) - m.RegisterProvider(&providers.Import{}) m.RegisterProvider(&providers.BuiltIns{}) m.RegisterProvider(&providers.RegoV1{}) m.RegisterProvider(&providers.PackageRefs{}) diff --git a/internal/lsp/completions/manager_test.go b/internal/lsp/completions/manager_test.go index 68e77b2e..821df96b 100644 --- a/internal/lsp/completions/manager_test.go +++ b/internal/lsp/completions/manager_test.go @@ -55,7 +55,7 @@ func TestManagerEarlyExitInsideComment(t *testing.T) { fileContents := `package p -import rego.v1 # modern rego i +# foo := http ` module := ast.MustParseModule(fileContents) @@ -64,7 +64,7 @@ import rego.v1 # modern rego i c.SetModule(fileURI, module) mgr := NewManager(c, &ManagerOptions{}) - mgr.RegisterProvider(&providers.Import{}) + mgr.RegisterProvider(&providers.BuiltIns{}) completionParams := types.CompletionParams{ TextDocument: types.TextDocumentIdentifier{ @@ -72,7 +72,7 @@ import rego.v1 # modern rego i }, Position: types.Position{ Line: 2, - Character: 30, + Character: 13, }, } diff --git a/internal/lsp/completions/providers/import.go b/internal/lsp/completions/providers/import.go deleted file mode 100644 index a1925ca1..00000000 --- a/internal/lsp/completions/providers/import.go +++ /dev/null @@ -1,50 +0,0 @@ -//nolint:dupl -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" -) - -// Import will return completions for the import keyword when at the start of a line. -type Import struct{} - -func (*Import) Run(c *cache.Cache, params types.CompletionParams, _ *Options) ([]types.CompletionItem, error) { - fileURI := params.TextDocument.URI - - _, currentLine := completionLineHelper(c, fileURI, params.Position.Line) - - // the user manually invoked completions at the beginning of an empty line - if params.Position.Character == 0 && strings.TrimSpace(currentLine) == "" { - return importCompletionItem(params), nil - } - - // the user must type i before we provide completions - if params.Position.Character != 0 && - strings.HasPrefix(currentLine, "i") && - !strings.HasPrefix(currentLine, "import ") { - return importCompletionItem(params), nil - } - - return []types.CompletionItem{}, nil -} - -func importCompletionItem(params types.CompletionParams) []types.CompletionItem { - return []types.CompletionItem{ - { - Label: "import", - Kind: completion.Keyword, - Detail: "import ", - TextEdit: &types.TextEdit{ - Range: types.Range{ - Start: types.Position{Line: params.Position.Line, Character: 0}, - End: types.Position{Line: params.Position.Line, Character: params.Position.Character}, - }, - NewText: "import ", - }, - }, - } -} diff --git a/internal/lsp/completions/providers/import_test.go b/internal/lsp/completions/providers/import_test.go deleted file mode 100644 index 72340fe9..00000000 --- a/internal/lsp/completions/providers/import_test.go +++ /dev/null @@ -1,85 +0,0 @@ -//nolint:dupl -package providers - -import ( - "testing" - - "github.com/styrainc/regal/internal/lsp/cache" - "github.com/styrainc/regal/internal/lsp/types" - "github.com/styrainc/regal/internal/lsp/types/completion" -) - -func TestImportInvoked(t *testing.T) { - t.Parallel() - - c := cache.NewCache() - - fileContents := "package policy\n\n" - - c.SetFileContents(testCaseFileURI, fileContents) - - p := &Import{} - - completionParams := types.CompletionParams{ - TextDocument: types.TextDocumentIdentifier{ - URI: testCaseFileURI, - }, - Position: types.Position{ - Line: 3, - Character: 0, - }, - Context: types.CompletionContext{ - TriggerKind: completion.Invoked, - }, - } - - completions, err := p.Run(c, completionParams, nil) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - if len(completions) != 1 { - t.Fatalf("Expected exactly one completion, got: %v", completions) - } - - comp := completions[0] - if comp.Label != "import" { - t.Fatalf("Expected label to be 'import', got: %v", comp.Label) - } -} - -func TestImportTypedI(t *testing.T) { - t.Parallel() - - c := cache.NewCache() - - fileContents := "package policy\n\ni" - - c.SetFileContents(testCaseFileURI, fileContents) - - p := &Import{} - - completionParams := types.CompletionParams{ - TextDocument: types.TextDocumentIdentifier{ - URI: testCaseFileURI, - }, - Position: types.Position{ - Line: 2, - Character: 1, - }, - } - - completions, err := p.Run(c, completionParams, nil) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - if len(completions) != 1 { - t.Fatalf("Expected exactly one completion, got: %v", completions) - } - - comp := completions[0] - if comp.Label != "import" { - t.Fatalf("Expected label to be 'import', got: %v", comp.Label) - } -} From 66d4259a072073dfb386dd2411e8ac6ba1073373 Mon Sep 17 00:00:00 2001 From: Charlie Egan Date: Wed, 19 Jun 2024 10:29:07 +0100 Subject: [PATCH 2/3] Update imports test case Signed-off-by: Charlie Egan --- .../providers/import/import_test.rego | 52 ++++++++++++++----- .../providers/locals/locals_test.rego | 16 +++--- .../lsp/completion/providers/utils_test.rego | 8 +++ .../lsp/completions/providers/policy_test.go | 4 +- 4 files changed, 56 insertions(+), 24 deletions(-) create mode 100644 bundle/regal/lsp/completion/providers/utils_test.rego diff --git a/bundle/regal/lsp/completion/providers/import/import_test.rego b/bundle/regal/lsp/completion/providers/import/import_test.rego index cd28785e..938bf14c 100644 --- a/bundle/regal/lsp/completion/providers/import/import_test.rego +++ b/bundle/regal/lsp/completion/providers/import/import_test.rego @@ -4,15 +4,21 @@ import rego.v1 import data.regal.lsp.completion.providers["import"] as provider -test_import_completion_on_typing if { +test_import_completion_empty_line if { policy := `package policy import rego.v1 ` - module := regal.parse_module("p.rego", policy) - new_policy := concat("", [policy, "i"]) - items := provider.items with input as input_with_location(module, new_policy, {"row": 5, "col": 2}) + + regal_module := {"regal": { + "file": { + "name": "p.rego", + "lines": split(policy, "\n"), + }, + "context": {"location": {"row": 5, "col": 1}}, + }} + items := provider.items with input as regal_module items == {{ "label": "import", @@ -22,16 +28,38 @@ import rego.v1 "newText": "import ", "range": { "start": {"character": 0, "line": 4}, - "end": {"character": 1, "line": 4}, + "end": {"character": 0, "line": 4}, }, }, }} } -input_with_location(module, policy, location) := object.union(module, {"regal": { - "file": { - "name": "p.rego", - "lines": split(policy, "\n"), - }, - "context": {"location": location}, -}}) +test_import_completion_on_typing if { + policy := `package policy + +import rego.v1 + +imp` + + regal_module := {"regal": { + "file": { + "name": "p.rego", + "lines": split(policy, "\n"), + }, + "context": {"location": {"row": 5, "col": 3}}, + }} + items := provider.items with input as regal_module + + items == {{ + "label": "import", + "detail": "import ", + "kind": 14, + "textEdit": { + "newText": "import ", + "range": { + "start": {"character": 0, "line": 4}, + "end": {"character": 3, "line": 4}, + }, + }, + }} +} diff --git a/bundle/regal/lsp/completion/providers/locals/locals_test.rego b/bundle/regal/lsp/completion/providers/locals/locals_test.rego index 7f6e779a..bf3cb1b3 100644 --- a/bundle/regal/lsp/completion/providers/locals/locals_test.rego +++ b/bundle/regal/lsp/completion/providers/locals/locals_test.rego @@ -2,7 +2,8 @@ package regal.lsp.completion.providers.locals_test import rego.v1 -import data.regal.lsp.completion.providers.locals +import data.regal.lsp.completion.providers.locals as provider +import data.regal.lsp.completion.providers.utils_test as utils test_no_locals_in_completion_items if { workspace := {"file:///p.rego": `package policy @@ -26,7 +27,7 @@ bar if { "col": 9, }}, }} - items := locals.items with input as regal_module with data.workspace.parsed as parsed_modules(workspace) + items := provider.items with input as regal_module with data.workspace.parsed as utils.parsed_modules(workspace) count(items) == 0 } @@ -56,7 +57,7 @@ function(bar) if { }}, }} - items := locals.items with input as regal_module with data.workspace.parsed as parsed_modules(workspace) + items := provider.items with input as regal_module with data.workspace.parsed as utils.parsed_modules(workspace) count(items) == 2 expect_item(items, "bar", {"end": {"character": 9, "line": 8}, "start": {"character": 8, "line": 8}}) @@ -88,7 +89,7 @@ function(bar) if { }}, }} - items := locals.items with input as regal_module with data.workspace.parsed as parsed_modules(workspace) + items := provider.items with input as regal_module with data.workspace.parsed as utils.parsed_modules(workspace) count(items) == 2 expect_item(items, "bar", {"end": {"character": 24, "line": 8}, "start": {"character": 23, "line": 8}}) @@ -116,17 +117,12 @@ function(bar) := f if { "col": 19, }}, }} - items := locals.items with input as regal_module with data.workspace.parsed as parsed_modules(workspace) + items := provider.items with input as regal_module with data.workspace.parsed as utils.parsed_modules(workspace) count(items) == 1 expect_item(items, "foo", {"end": {"character": 18, "line": 4}, "start": {"character": 17, "line": 4}}) } -parsed_modules(workspace) := {file_uri: parsed_module | - some file_uri, contents in workspace - parsed_module := regal.parse_module(file_uri, contents) -} - expect_item(items, label, range) if { expected := {"detail": "local variable", "kind": 6} diff --git a/bundle/regal/lsp/completion/providers/utils_test.rego b/bundle/regal/lsp/completion/providers/utils_test.rego new file mode 100644 index 00000000..08c6644b --- /dev/null +++ b/bundle/regal/lsp/completion/providers/utils_test.rego @@ -0,0 +1,8 @@ +package regal.lsp.completion.providers.utils_test + +import rego.v1 + +parsed_modules(workspace) := {file_uri: parsed_module | + some file_uri, contents in workspace + parsed_module := regal.parse_module(file_uri, contents) +} diff --git a/internal/lsp/completions/providers/policy_test.go b/internal/lsp/completions/providers/policy_test.go index 38b3aa03..5e9ad8a6 100644 --- a/internal/lsp/completions/providers/policy_test.go +++ b/internal/lsp/completions/providers/policy_test.go @@ -13,7 +13,7 @@ import ( "github.com/styrainc/regal/internal/parse" ) -func TestPolicyProvider(t *testing.T) { +func TestPolicyProvider_Example1(t *testing.T) { t.Parallel() policy := `package p @@ -71,7 +71,7 @@ allow if { } } -func TestPolicyProvider_B(t *testing.T) { +func TestPolicyProvider_Example2(t *testing.T) { t.Parallel() file1 := ast.MustParseModule(`package example From d87c3f6193a333a2bd82488cb508acff6b3a853d Mon Sep 17 00:00:00 2001 From: Charlie Egan Date: Wed, 19 Jun 2024 11:05:42 +0100 Subject: [PATCH 3/3] Move expect item to utils Signed-off-by: Charlie Egan --- .../providers/locals/locals_test.rego | 24 ++++--------------- .../lsp/completion/providers/utils_test.rego | 14 +++++++++++ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/bundle/regal/lsp/completion/providers/locals/locals_test.rego b/bundle/regal/lsp/completion/providers/locals/locals_test.rego index bf3cb1b3..0ae4d1d0 100644 --- a/bundle/regal/lsp/completion/providers/locals/locals_test.rego +++ b/bundle/regal/lsp/completion/providers/locals/locals_test.rego @@ -60,8 +60,8 @@ function(bar) if { items := provider.items with input as regal_module with data.workspace.parsed as utils.parsed_modules(workspace) count(items) == 2 - expect_item(items, "bar", {"end": {"character": 9, "line": 8}, "start": {"character": 8, "line": 8}}) - expect_item(items, "baz", {"end": {"character": 9, "line": 8}, "start": {"character": 8, "line": 8}}) + utils.expect_item(items, "bar", {"end": {"character": 9, "line": 8}, "start": {"character": 8, "line": 8}}) + utils.expect_item(items, "baz", {"end": {"character": 9, "line": 8}, "start": {"character": 8, "line": 8}}) } test_locals_in_completion_items_function_call if { @@ -92,8 +92,8 @@ function(bar) if { items := provider.items with input as regal_module with data.workspace.parsed as utils.parsed_modules(workspace) count(items) == 2 - expect_item(items, "bar", {"end": {"character": 24, "line": 8}, "start": {"character": 23, "line": 8}}) - expect_item(items, "baz", {"end": {"character": 24, "line": 8}, "start": {"character": 23, "line": 8}}) + utils.expect_item(items, "bar", {"end": {"character": 24, "line": 8}, "start": {"character": 23, "line": 8}}) + utils.expect_item(items, "baz", {"end": {"character": 24, "line": 8}, "start": {"character": 23, "line": 8}}) } test_locals_in_completion_items_rule_head_assignment if { @@ -120,19 +120,5 @@ function(bar) := f if { items := provider.items with input as regal_module with data.workspace.parsed as utils.parsed_modules(workspace) count(items) == 1 - expect_item(items, "foo", {"end": {"character": 18, "line": 4}, "start": {"character": 17, "line": 4}}) -} - -expect_item(items, label, range) if { - expected := {"detail": "local variable", "kind": 6} - - item := object.union(expected, { - "label": label, - "textEdit": { - "newText": label, - "range": range, - }, - }) - - item in items + utils.expect_item(items, "foo", {"end": {"character": 18, "line": 4}, "start": {"character": 17, "line": 4}}) } diff --git a/bundle/regal/lsp/completion/providers/utils_test.rego b/bundle/regal/lsp/completion/providers/utils_test.rego index 08c6644b..ef09b0c7 100644 --- a/bundle/regal/lsp/completion/providers/utils_test.rego +++ b/bundle/regal/lsp/completion/providers/utils_test.rego @@ -6,3 +6,17 @@ parsed_modules(workspace) := {file_uri: parsed_module | some file_uri, contents in workspace parsed_module := regal.parse_module(file_uri, contents) } + +expect_item(items, label, range) if { + expected := {"detail": "local variable", "kind": 6} + + item := object.union(expected, { + "label": label, + "textEdit": { + "newText": label, + "range": range, + }, + }) + + item in items +}