Skip to content

Commit

Permalink
lsp/hover: Show example links (#906)
Browse files Browse the repository at this point in the history
* lsp/hover: Show example links

Where examples to https://docs.styra.com/opa/rego-by-example
are available, then show the links to these pages.

Signed-off-by: Charlie Egan <[email protected]>

* Improve keyword performance

Signed-off-by: Charlie Egan <[email protected]>

* Remove comment

Signed-off-by: Charlie Egan <[email protected]>

* Tidy _determine_keywords for every

Signed-off-by: Charlie Egan <[email protected]>

* inst

* Improve if keyword performance

Results of benchmarking:

old median	28.986063
old mean	99.36704471
new median	20.632625
new mean	84.75417271

new	old
170.351834	165.913458
15.080667	18.174667
722.364416	14.838167
144.308166	40.304292
24.4045	22.223417
61.691458	548.937375
171.228291	349.011583
14.812834	19.470125
14.869208	18.471875
46.716167	78.956167
16.760459	15.528125
14.35275	15.014083
14.8865	35.748709
20.632625	48.546583
14.898875
25.510542
15.363084
70.541459
168.860167
17.247625
14.956

These results were tested by disabling all other keyword
rule heads and measuring the total time to get the list of
keywords in go.

Signed-off-by: Charlie Egan <[email protected]>

* Remove time stmt

* Add exprs rule to ast package

This is to be used when a list of expressions is needed in rules and for
keyword operations.

Signed-off-by: Charlie Egan <[email protected]>

* Handle values in heads

---------

Signed-off-by: Charlie Egan <[email protected]>
  • Loading branch information
charlieegan3 committed Jul 11, 2024
1 parent a6e97af commit 1efd82c
Show file tree
Hide file tree
Showing 16 changed files with 670 additions and 10 deletions.
140 changes: 140 additions & 0 deletions bundle/regal/ast/keywords.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package regal.ast

import rego.v1

keywords[row] contains keyword if {
some idx, line in input.regal.file.lines

col := indexof(line, " if ")
col > 0

row := idx + 1

not row in _comment_row_index

keyword := {
"name": "if",
"location": {
"row": idx + 1,
"col": col + 2,
},
}
}

keywords[pkg.location.row] contains keyword if {
pkg := input["package"]

keyword := {
"name": "package",
"location": {
"row": pkg.location.row,
"col": pkg.location.col,
},
}
}

keywords[imp.location.row] contains keyword if {
some imp in input.imports

keyword := {
"name": "import",
"location": {
"row": imp.location.row,
"col": imp.location.col,
},
}
}

keywords[loc.row] contains keyword if {
some rule in input.rules

loc := rule.head.location

col := indexof(base64.decode(loc.text), " contains ")

col > 0

keyword := {
"name": "contains",
"location": {
"row": loc.row,
"col": col + 2,
},
}
}

keywords[value.row] contains keyword if {
some expr in exprs[_]

walk(expr.terms, [path, value])

value.col
value.row

name := _keyword_b64s[value.text]

parent_path := array.slice(path, 0, count(path) - 1)
context := object.get(expr.terms, parent_path, {})

some keyword in _determine_keywords(context, value, name)
}

keywords[value.row] contains keyword if {
some rule in input.rules
rule.head.assign

walk(rule.head.value, [path, value])

value.col
value.row

name := _keyword_b64s[value.text]

parent_path := array.slice(path, 0, count(path) - 1)
context := object.get(rule.head.value, parent_path, {})

some keyword in _determine_keywords(context, value, name)
}

_determine_keywords(_, value, name) := {keyword} if {
name in {"in", "some"}

keyword := {
"name": name,
"location": {
"row": value.row,
"col": value.col,
},
}
}

_determine_keywords(context, value, "every") := keywords if {
text := base64.decode(context.value.location.text)

keywords := {
{
"name": "every",
"location": {
"row": value.row,
"col": value.col,
},
},
{
"name": "in",
"location": {
"row": value.row,
"col": (context.value.location.col + count(text)) + 1,
},
},
}
}

_comment_row_index contains comment.Location.row if {
some comment in object.get(input, "comments", [])
}

_keyword_b64s := {
"aW4=": "in",
"c29tZQ==": "some",
"ZXZlcnk=": "every",
}
223 changes: 223 additions & 0 deletions bundle/regal/ast/keywords_test.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
package regal.ast_test

import rego.v1

import data.regal.ast

test_keywords_package if {
policy := `package policy`

kwds := ast.keywords with input as regal.parse_module("p.rego", policy)

count(kwds) == 1 # lines with keywords

_keyword_on_row(
kwds,
1,
{
"name": "package",
"location": {"row": 1, "col": 1},
},
)
}

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

kwds := ast.keywords with input as regal.parse_module("p.rego", policy)

count(kwds) == 2 # lines with keywords

_keyword_on_row(
kwds,
3,
{
"name": "import",
"location": {"row": 3, "col": 1},
},
)
}

test_keywords_if if {
policy := `package policy
import rego.v1
allow if {
# if things
true
}
`

kwds := ast.keywords with input as object.union(
regal.parse_module("p.rego", policy),
{"regal": {"file": {"lines": split(policy, "\n")}}},
)

count(kwds) == 3 # lines with keywords

_keyword_on_row(
kwds,
5,
{
"name": "if",
"location": {"row": 5, "col": 7},
},
)
}

test_keywords_if_on_another_line if {
policy := `package policy
import rego.v1
allow contains {
"foo": true,
} if {
# if things
true
}
`

kwds := ast.keywords with input as object.union(
regal.parse_module("p.rego", policy),
{"regal": {"file": {"lines": split(policy, "\n")}}},
)

count(kwds) == 4 # lines with keywords

_keyword_on_row(
kwds,
7,
{
"name": "if",
"location": {"row": 7, "col": 3},
},
)
}

test_keywords_some_in if {
policy := ast.with_rego_v1(`
allow if {
some e in [1,2,3]
}`)

kwds := ast.keywords with input as policy

count(kwds) == 4 # lines with keywords

_keyword_on_row(
kwds,
7,
{"name": "some", "location": {"row": 7, "col": 2}},
)

_keyword_on_row(
kwds,
7,
{"name": "in", "location": {"row": 7, "col": 9}},
)
}

test_keywords_some_no_body if {
policy := ast.with_rego_v1(`list := [e|
some e in [1,2,3]
]`)

kwds := ast.keywords with input as policy

count(kwds) == 3 # lines with keywords

_keyword_on_row(
kwds,
6,
{"name": "some", "location": {"row": 6, "col": 2}},
)

_keyword_on_row(
kwds,
6,
{"name": "in", "location": {"row": 6, "col": 9}},
)
}

test_keywords_some_in_func_arg if {
policy := ast.with_rego_v1(`foo := concat(".", [part |
some part in ["a","b","c"]
])`)

kwds := ast.keywords with input as policy

count(kwds) == 3 # lines with keywords

_keyword_on_row(
kwds,
6,
{"name": "some", "location": {"row": 6, "col": 2}},
)

_keyword_on_row(
kwds,
6,
{"name": "in", "location": {"row": 6, "col": 12}},
)
}

test_keywords_contains if {
policy := ast.with_rego_v1(`
messages contains "hello" if {
1 == 1
}`)

kwds := ast.keywords with input as policy

count(kwds) == 3 # lines with keywords

_keyword_on_row(
kwds,
6,
{"name": "contains", "location": {"row": 6, "col": 10}},
)

_keyword_on_row(
kwds,
6,
{"name": "if", "location": {"row": 6, "col": 27}},
)
}

test_keywords_every if {
policy := ast.with_rego_v1(`
allow if {
every k in [1,2,3] {
k == "foo"
v == 1
}
}`)

kwds := ast.keywords with input as policy

count(kwds) == 4 # lines with keywords

_keyword_on_row(
kwds,
7,
{"name": "every", "location": {"row": 7, "col": 2}},
)

_keyword_on_row(
kwds,
7,
{"name": "in", "location": {"row": 7, "col": 10}},
)
}

_keyword_on_row(kwds, row, keyword) if {
some kwd in object.get(kwds, row, {})

kwd.name == keyword.name
kwd.location.row == keyword.location.row
kwd.location.col == keyword.location.col
}
10 changes: 10 additions & 0 deletions bundle/regal/ast/search.rego
Original file line number Diff line number Diff line change
Expand Up @@ -242,3 +242,13 @@ find_some_decl_names_in_scope(rule, location) := {some_var.value |
some some_var in find_some_decl_vars(rule)
_before_location(rule, some_var, location)
}

# _rules_with_bodies[rule_index] := rule if {
# some rule_index, rule in input.rules
# not generated_body(rule)
# }

exprs[rule_index][expr_index] := expr if {
some rule_index, rule in input.rules
some expr_index, expr in rule.body
}
Loading

0 comments on commit 1efd82c

Please sign in to comment.