Skip to content

Commit 2158863

Browse files
authored
Breaking: new RoAST representation of module.comments (#1987)
A comment is just text at a given location, so perfectly representable as just a location. Doing this also allows us to avoid base64-decoding each one, as Regal can instead get the original text from the source file. While this is a breaking change for anyone referencing `input.comments` directly, users are advised to instead reference `data.regal.ast.comments_decoded`, which'll provide them a list of comment loctations in the usual location form. Also: - Played some with using the new `not` form in a few places, mostly so that we're invested in that from the start. Signed-off-by: Anders Eknert <anders.eknert@apple.com>
1 parent 7362807 commit 2158863

46 files changed

Lines changed: 282 additions & 415 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.regal/config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ rules:
142142
files:
143143
# This package is used to build the LSP initialize
144144
# response, and names must conform to the specification
145-
- "bundle/regal/lsp/initialize/initialize.rego"
145+
- "**/bundle/regal/lsp/initialize/initialize.rego"
146146
imports:
147147
unresolved-reference:
148148
level: error

build/capabilities.json

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5712,7 +5712,7 @@
57125712
"decl": {
57135713
"args": [
57145714
{
5715-
"description": "a duration like \"3m\"; see the [Go `time` package documentation](https://golang.org/pkg/time/#ParseDuration) for more details",
5715+
"description": "a duration like \"3m\"; see the [OPA `Duration Parsing` documentation](https://www.openpolicyagent.org/docs/latest/policy-reference/builtins/time#duration-parsing) for more details",
57165716
"name": "duration",
57175717
"type": "string"
57185718
}
@@ -6131,6 +6131,52 @@
61316131
"type": "function"
61326132
}
61336133
},
6134+
{
6135+
"name": "uri.is_valid",
6136+
"description": "Returns true if the input can be parsed as a URI.",
6137+
"decl": {
6138+
"args": [
6139+
{
6140+
"description": "the URI string to validate",
6141+
"name": "uri",
6142+
"type": "string"
6143+
}
6144+
],
6145+
"result": {
6146+
"description": "true if `uri` is a valid URI, false otherwise",
6147+
"name": "result",
6148+
"type": "boolean"
6149+
},
6150+
"type": "function"
6151+
}
6152+
},
6153+
{
6154+
"name": "uri.parse",
6155+
"description": "Parses a URI and returns an object containing its components according to RFC 3986. Empty components are omitted. In addition to the standard components, `raw_query` is returned for use with `urlquery` builtins, and `raw_path` is returned to allow detection of path-based exploits using percent-encoded characters.",
6156+
"decl": {
6157+
"args": [
6158+
{
6159+
"description": "the URI string to parse",
6160+
"name": "uri",
6161+
"type": "string"
6162+
}
6163+
],
6164+
"result": {
6165+
"description": "object containing URI components",
6166+
"dynamic": {
6167+
"key": {
6168+
"type": "string"
6169+
},
6170+
"value": {
6171+
"type": "string"
6172+
}
6173+
},
6174+
"name": "output",
6175+
"type": "object"
6176+
},
6177+
"type": "function"
6178+
}
6179+
},
61346180
{
61356181
"name": "urlquery.decode",
61366182
"description": "Decodes a URL-encoded input string.",
@@ -6403,6 +6449,9 @@
64036449
}
64046450
}
64056451
],
6452+
"future_keywords": [
6453+
"not"
6454+
],
64066455
"features": [
64076456
"keywords_in_refs",
64086457
"rego_v1",

bundle/regal/ast/ast_test.rego

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,14 @@ allow := true
3838

3939
blocks == [
4040
[
41-
{"location": {"col": 1, "end": {"col": 15, "row": 5}, "row": 3, "text": "# METADATA"}, "text": " METADATA"},
42-
{"location": {"col": 1, "end": {"col": 13, "row": 4}, "row": 4, "text": "# title: foo"}, "text": " title: foo"},
43-
{"location": {"col": 1, "end": {"col": 15, "row": 5}, "row": 5, "text": "# bar: invalid"}, "text": " bar: invalid"},
41+
{"col": 1, "end": {"col": 15, "row": 5}, "row": 3, "text": "# METADATA"},
42+
{"col": 1, "end": {"col": 13, "row": 4}, "row": 4, "text": "# title: foo"},
43+
{"col": 1, "end": {"col": 15, "row": 5}, "row": 5, "text": "# bar: invalid"},
4444
],
45-
[{"location": {"col": 1, "end": {"col": 15, "row": 8}, "row": 8, "text": "# not metadata"}, "text": " not metadata"}],
45+
[{"col": 1, "end": {"col": 15, "row": 8}, "row": 8, "text": "# not metadata"}],
4646
[
47-
{"location": {"col": 1, "end": {"col": 10, "row": 10}, "row": 10, "text": "# another"}, "text": " another"},
48-
{"location": {"col": 1, "end": {"col": 8, "row": 11}, "row": 11, "text": "# block"}, "text": " block"},
47+
{"col": 1, "end": {"col": 10, "row": 10}, "row": 10, "text": "# another"},
48+
{"col": 1, "end": {"col": 8, "row": 11}, "row": 11, "text": "# block"},
4949
],
5050
]
5151
}

bundle/regal/ast/comments.rego

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
11
package regal.ast
22

33
# METADATA
4-
# description: all comments in the input AST with their `text` attribute base64 decoded
4+
# description: all comments in the input AST
55
comments_decoded := [decoded |
6-
some comment in _comments
6+
some location in _comments
77

8-
text_decoded := base64.decode(comment.text)
9-
[row_str, col_str, end_row_str, end_col_str] := split(comment.location, ":")
8+
[row_str, col_str, end_row_str, end_col_str] := split(location, ":")
9+
row := to_number(row_str)
10+
col := to_number(col_str)
11+
text := substring(input.regal.file.lines[row - 1], col - 1, -1)
1012

1113
decoded := {
12-
"text": text_decoded,
13-
"location": {
14-
"row": to_number(row_str),
15-
"col": to_number(col_str),
16-
"end": {
17-
"row": to_number(end_row_str),
18-
"col": to_number(end_col_str),
19-
},
20-
"text": $"#{text_decoded}",
14+
"row": row,
15+
"col": col,
16+
"end": {
17+
"row": to_number(end_row_str),
18+
"col": to_number(end_col_str),
2119
},
20+
"text": text,
2221
}
2322
]
2423

@@ -46,7 +45,7 @@ comments["metadata_attributes"] := {
4645
# METADATA
4746
# description: true if comment matches a metadata annotation attribute
4847
comments["annotation_match"](str) if regex.match(
49-
`^\s*(scope|title|description|labels|related_resources|authors|organizations|schemas|entrypoint|custom|compile)\s*:`,
48+
`^#?\s*(scope|title|description|labels|related_resources|authors|organizations|schemas|entrypoint|custom|compile)\s*:`,
5049
str,
5150
)
5251

@@ -55,13 +54,13 @@ comments["annotation_match"](str) if regex.match(
5554
# map of all ignore directive comments, like ("# regal ignore:line-length")
5655
# found in input AST, indexed by the row they're at
5756
ignore_directives[row] := rules if {
58-
some comment in comments_decoded
57+
some location in comments_decoded
5958

60-
contains(comment.text, "regal ignore:")
59+
contains(location.text, "regal ignore:")
6160

62-
row := comment.location.row + 1
61+
row := location.row + 1
6362

64-
rules := regex.split(`,\s*`, trim_space(regex.replace(comment.text, `^.*regal ignore:\s*(\S+)`, "$1")))
63+
rules := regex.split(`,\s*`, trim_space(regex.replace(location.text, `^.*regal ignore:\s*(\S+)`, "$1")))
6564
}
6665

6766
# METADATA
@@ -71,7 +70,7 @@ ignore_directives[row] := rules if {
7170
# one before is considered to be part of a block.
7271
comment_blocks(comments_decoded) := blocks if {
7372
row_partitions := [partition |
74-
rows := [comment.location.row | some comment in comments_decoded]
73+
rows := [location.row | some location in comments_decoded]
7574
breaks := _splits(rows)
7675

7776
some j, k in breaks
@@ -85,12 +84,12 @@ comment_blocks(comments_decoded) := blocks if {
8584
blocks := [block |
8685
some row_partition in row_partitions
8786
some block in {col: partition |
88-
some comment in row_partition
87+
some location in row_partition
8988

90-
col := comment.location.col # regal ignore:comprehension-term-assignment
91-
partition := [partition_comment |
92-
some partition_comment in row_partition
93-
partition_comment.location.col == col
89+
col := location.col # regal ignore:comprehension-term-assignment
90+
partition := [partition_location |
91+
some partition_location in row_partition
92+
partition_location.col == col
9493
]
9594
}
9695
]

bundle/regal/lsp/documentlink/documentlink.rego

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
# - input.params: schema.regal.lsp.textdocument
1515
package regal.lsp.documentlink
1616

17-
import data.regal.util
17+
import data.regal.ast
1818

1919
# METADATA
2020
# entrypoint: true
@@ -24,19 +24,15 @@ result["response"] := items
2424
# description: Set of links in document
2525
# entrypoint: true
2626
items contains item if {
27-
module := data.workspace.parsed[input.params.textDocument.uri]
27+
some location in ast.comments_decoded
2828

29-
some encoded in module.comments
29+
contains(location.text, "regal ignore:")
3030

31-
text := base64.decode(encoded.text)
32-
contains(text, "regal ignore:")
31+
row := location.row - 1
3332

34-
loc := util.to_location_no_text(encoded.location)
35-
row := loc.row - 1
33+
some rule in regex.split(`,\s*`, trim_space(regex.replace(location.text, `^.*regal ignore:\s*(\S+)`, "$1")))
3634

37-
some rule in regex.split(`,\s*`, trim_space(regex.replace(text, `^.*regal ignore:\s*(\S+)`, "$1")))
38-
39-
col := loc.col + indexof(text, rule)
35+
col := (location.col + indexof(location.text, rule)) - 1
4036

4137
item := {
4238
"target": $"https://www.openpolicyagent.org/projects/regal/rules/{_category_for[rule]}/{rule}",

bundle/regal/lsp/documentlink/documentlink_test.rego

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ package regal.lsp.documentlink_test
33
import data.regal.lsp.documentlink
44

55
test_documentlink_ranges_in_inline_ignores if {
6-
items := documentlink.items with input as {"params": {"textDocument": {"uri": "file://p.rego"}}}
7-
with data.workspace.parsed["file://p.rego"] as regal.parse_module("p.rego", concat("\n", [
8-
"package p",
9-
"",
10-
"# regal ignore:messy-rule,unresolved-reference",
11-
"ignored if directives",
12-
]))
6+
lines := [
7+
"package p",
8+
"",
9+
"# regal ignore:messy-rule,unresolved-reference",
10+
"ignored if directives",
11+
]
12+
13+
items := documentlink.items
14+
with input.params.textDocument.uri as "file://p.rego"
15+
with input.regal.file.lines as lines
16+
with data.workspace.parsed["file://p.rego"] as regal.parse_module("p.rego", concat("\n", lines))
1317
with data.workspace.config.rules as {
1418
"style": {"messy-rule": {}},
1519
"imports": {"unresolved-reference": {}},

bundle/regal/lsp/foldingrange/foldingrange.rego

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ ranges contains range if {
3838
count(block) > 1
3939

4040
# note: the comment locations have already been deserialized in ast.comments.blocks
41-
range := _block_range(block[0].location, regal.last(block).location, only_lines)
41+
range := _block_range(block[0], regal.last(block), only_lines)
4242
}
4343

4444
# METADATA

bundle/regal/lsp/foldingrange/foldingrange_test.rego

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ import data.h
8282
test_folding_comment_blocks if {
8383
result := foldingrange.result
8484
with input.params.textDocument.uri as "file:///p.rego"
85+
with input.regal.file.lines as comment_blocks_module.regal.file.lines
8586
with data.workspace.parsed["file:///p.rego"] as comment_blocks_module
8687

8788
result.response == {
@@ -105,6 +106,7 @@ test_folding_comment_blocks if {
105106
test_folding_comment_blocks_only_lines if {
106107
result := foldingrange.result
107108
with input.params.textDocument.uri as "file:///p.rego"
109+
with input.regal.file.lines as comment_blocks_module.regal.file.lines
108110
with data.client.capabilities.textDocument.foldingRange.lineFoldingOnly as true
109111
with data.workspace.parsed["file:///p.rego"] as comment_blocks_module
110112

@@ -236,6 +238,7 @@ test_folding_limit_honored[limit] if {
236238

237239
result := foldingrange.result
238240
with input.params.textDocument.uri as "file:///p.rego"
241+
with input.regal.file.lines as comment_blocks_module.regal.file.lines
239242
with data.client.capabilities.textDocument.foldingRange.rangeLimit as limit
240243
with data.workspace.parsed["file:///p.rego"] as comment_blocks_module
241244

bundle/regal/lsp/hover/hover_test.rego

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -226,14 +226,14 @@ test_builtin_url_override if {
226226
test_keyword_hover if {
227227
res := hover.result.response
228228
with input as {
229-
"params": {"position": {
230-
"line": 0,
231-
"character": 3,
232-
}},
233-
"regal": {"file": {
234-
"lines": ["package foo", ""],
235-
"uri": "file:///p.rego",
236-
}},
229+
"params": {
230+
"textDocument": {"uri": "file:///p.rego"},
231+
"position": {
232+
"line": 0,
233+
"character": 3,
234+
},
235+
},
236+
"regal": {"file": {"lines": ["package foo", ""]}},
237237
}
238238
with data.workspace.parsed["file:///p.rego"] as regal.parse_module("p.rego", "package foo\n")
239239

bundle/regal/lsp/hover/keywords/keywords.rego

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1+
# METADATA
2+
# schemas:
3+
# - input: schema.regal.lsp.common
4+
# - input.params: schema.regal.lsp.textdocument
15
package regal.lsp.hover.keywords
26

7+
import data.regal.ast
38
import data.regal.util
49

5-
_module := data.workspace.parsed[input.regal.file.uri]
10+
_module := data.workspace.parsed[input.params.textDocument.uri]
611

712
# METADATA
813
# description: collects keywords from input module by the line that they appear on
@@ -116,4 +121,4 @@ _in_on_row(row) := [keyword |
116121
}
117122
]
118123

119-
_comment_row_index contains util.to_location_object(comment.location).row if some comment in _module.comments
124+
_comment_row_index contains location.row if some location in ast.comments_decoded

0 commit comments

Comments
 (0)