Skip to content

Commit

Permalink
More Rego completion providers (#858)
Browse files Browse the repository at this point in the history
Rules converted:
- `commonrule`
- `package`
- `rego.v1`

Also:
- move some common functions into utility package.
- add `Name()` to provider interface to be able to easily measure
  performance impact of a named provider

Signed-off-by: Anders Eknert <[email protected]>
  • Loading branch information
anderseknert committed Jun 20, 2024
1 parent 39e57db commit 82fd171
Show file tree
Hide file tree
Showing 26 changed files with 389 additions and 580 deletions.
19 changes: 19 additions & 0 deletions bundle/regal/lsp/completion/location/location.rego
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,25 @@ word_at(line, col) := word if {
}
}

# METADATA
# description: |
# find ref at column in line, and return its text, and the offset
# from the position (before and after)
# this is similar to word_at but captures `.` as well
ref_at(line, col) := word if {
text_before := substring(line, 0, col - 1)
word_before := _to_string(regex.find_n(`[a-zA-Z_\.]+$`, text_before, 1))

text_after := substring(line, col - 1, count(line))
word_after := _to_string(regex.find_n(`^[a-zA-Z_\.]+`, text_after, 1))

word := {
"offset_before": count(word_before),
"offset_after": count(word_after),
"text": sprintf("%s%s", [word_before, word_after]),
}
}

_to_string(arr) := "" if count(arr) == 0

_to_string(arr) := arr[0] if count(arr) > 0
46 changes: 46 additions & 0 deletions bundle/regal/lsp/completion/providers/commonrule/commonrule.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package regal.lsp.completion.providers.commonrule

import rego.v1

import data.regal.lsp.completion.kind
import data.regal.lsp.completion.location

suggested_names := {
"allow",
"authorized",
"deny",
}

items contains item if {
position := location.to_position(input.regal.context.location)
line := input.regal.file.lines[position.line]

some label in suggested_names

invoke_suggestion(line, label)

item := {
"label": label,
"kind": kind.snippet,
"detail": "common name",
"documentation": {
"kind": "markdown",
"value": sprintf("%q is a common rule name", [label]),
},
"textEdit": {
"range": {
"start": {
"line": position.line,
"character": 0,
},
"end": position,
},
"newText": sprintf("%s ", [label]),
},
}
}

invoke_suggestion("", _)

# regal ignore:external-reference
invoke_suggestion(line, label) if startswith(label, line)
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package regal.lsp.completion.providers.commonrule_test

import rego.v1

import data.regal.lsp.completion.providers.commonrule as provider
import data.regal.lsp.completion.providers.utils_test as util

test_common_name_completion_on_invoked if {
policy := `package policy
import rego.v1
`
module := regal.parse_module("p.rego", policy)
items := provider.items with input as util.input_module_with_location(module, policy, {"row": 5, "col": 2})

expected_item(items, "allow")
expected_item(items, "deny")
expected_item(items, "authorized")
}

test_common_name_completion_on_typed if {
policy := `package policy
import rego.v1
`
module := regal.parse_module("p.rego", policy)
new_policy := concat("", [policy, "d"])
items := provider.items with input as util.input_module_with_location(module, new_policy, {"row": 5, "col": 2})

expected_item(items, "deny")
}

expected_item(items, label) if {
item := {
"label": label,
"detail": "common name",
"documentation": {
"kind": "markdown",
"value": sprintf("%q is a common rule name", [label]),
},
"kind": 15,
"textEdit": {
"range": {
"start": {
"line": 4,
"character": 0,
},
"end": {
"line": 4,
"character": 1,
},
},
"newText": sprintf("%s ", [label]),
},
}

item in items
}
15 changes: 4 additions & 11 deletions bundle/regal/lsp/completion/providers/default/default_test.rego
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package regal.lsp.completion.providers.default_test
import rego.v1

import data.regal.lsp.completion.providers["default"] as provider
import data.regal.lsp.completion.providers.utils_test as util

test_default_completion_on_typing if {
policy := `package policy
Expand All @@ -13,7 +14,7 @@ import rego.v1
`
module := regal.parse_module("p.rego", policy)
new_policy := sprintf("%s%s", [policy, "d"])
items := provider.items with input as input_with_location(module, new_policy, {"row": 5, "col": 2})
items := provider.items with input as util.input_module_with_location(module, new_policy, {"row": 5, "col": 2})

items == {{
"detail": "default <rule-name> := <value>",
Expand Down Expand Up @@ -43,7 +44,7 @@ deny if false
`
module := regal.parse_module("p.rego", policy)
new_policy := sprintf("%s%s", [policy, "d"])
items := provider.items with input as input_with_location(module, new_policy, {"row": 9, "col": 2})
items := provider.items with input as util.input_module_with_location(module, new_policy, {"row": 9, "col": 2})

items == {
{
Expand Down Expand Up @@ -93,7 +94,7 @@ import rego.v1
`
module := regal.parse_module("p.rego", policy)
items := provider.items with input as input_with_location(module, policy, {"row": 5, "col": 2})
items := provider.items with input as util.input_module_with_location(module, policy, {"row": 5, "col": 2})

items == {{
"detail": "default <rule-name> := <value>",
Expand All @@ -108,11 +109,3 @@ import rego.v1
},
}}
}

input_with_location(module, policy, location) := object.union(module, {"regal": {
"file": {
"name": "p.rego",
"lines": split(policy, "\n"),
},
"context": {"location": location},
}})
21 changes: 3 additions & 18 deletions bundle/regal/lsp/completion/providers/import/import_test.rego
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,15 @@ package regal.lsp.completion.providers.import_test
import rego.v1

import data.regal.lsp.completion.providers["import"] as provider
import data.regal.lsp.completion.providers.utils_test as util

test_import_completion_empty_line if {
policy := `package policy
import rego.v1
`

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 := provider.items with input as util.input_with_location(policy, {"row": 5, "col": 1})
items == {{
"label": "import",
"detail": "import <path>",
Expand All @@ -41,14 +33,7 @@ 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 := provider.items with input as util.input_with_location(policy, {"row": 5, "col": 3})

items == {{
"label": "import",
Expand Down
39 changes: 39 additions & 0 deletions bundle/regal/lsp/completion/providers/package/package.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package regal.lsp.completion.providers["package"]

import rego.v1

import data.regal.lsp.completion.kind
import data.regal.lsp.completion.location

items contains item if {
not strings.any_prefix_match(input.regal.file.lines, "package ")

position := location.to_position(input.regal.context.location)
line := input.regal.file.lines[position.line]

invoke_suggestion(line)

item := {
"label": "package",
"kind": kind.keyword,
"detail": "package <package-name>",
"textEdit": {
"range": {
"start": {
"line": position.line,
"character": 0,
},
"end": {
"line": position.line,
"character": position.character,
},
},
"newText": "package ",
},
}
}

invoke_suggestion("")

# regal ignore:external-reference
invoke_suggestion(line) if startswith("package", line)
60 changes: 60 additions & 0 deletions bundle/regal/lsp/completion/providers/package/package_test.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package regal.lsp.completion.providers.package_test

import rego.v1

import data.regal.lsp.completion.providers["package"] as provider
import data.regal.lsp.completion.providers.utils_test as util

test_package_completion_on_typing if {
policy := `p`
items := provider.items with input as util.input_with_location(policy, {"row": 1, "col": 2})
items == {{
"detail": "package <package-name>",
"kind": 14,
"label": "package",
"textEdit": {
"newText": "package ",
"range": {
"end": {
"character": 1,
"line": 0,
},
"start": {
"character": 0,
"line": 0,
},
},
},
}}
}

test_package_completion_on_invoked if {
policy := ``
items := provider.items with input as util.input_with_location(policy, {"row": 1, "col": 1})
items == {{
"detail": "package <package-name>",
"kind": 14,
"label": "package",
"textEdit": {
"newText": "package ",
"range": {
"end": {
"character": 0,
"line": 0,
},
"start": {
"character": 0,
"line": 0,
},
},
},
}}
}

test_package_completion_not_suggested_if_already_present if {
policy := `packae policy
`
items := provider.items with input as util.input_with_location(policy, {"row": 3, "col": 1})
items == set()
}
33 changes: 33 additions & 0 deletions bundle/regal/lsp/completion/providers/regov1/regov1.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package regal.lsp.completion.providers.regov1

import rego.v1

import data.regal.lsp.completion.kind
import data.regal.lsp.completion.location

items contains item if {
not strings.any_prefix_match(input.regal.file.lines, "import rego.v1")

position := location.to_position(input.regal.context.location)
line := input.regal.file.lines[position.line]

startswith(line, "import ")

word := location.ref_at(line, input.regal.context.location.col)

invoke_suggestion(word)

item := {
"label": "rego.v1",
"kind": kind.module,
"detail": "use rego.v1",
"textEdit": {
"range": location.word_range(word, position),
"newText": "rego.v1\n\n",
},
}
}

invoke_suggestion(word) if {
startswith("rego.v1", word.text)
}
Loading

0 comments on commit 82fd171

Please sign in to comment.