format: Don't unwrap one-line rule body braces from single set term (#8972)

Before this fix, e.g., the rule:

```rego
p if { {false} }
```

would be formated to:

```rego
p if {false}
```

which changes the semantics from a rule body containing a single set
(`{false}`) to a rule body containing a single scalar value (`false`).

Signed-off-by: Johan Fylling <johan.dev@fylling.se>
This commit is contained in:
Johan Fylling
2026-08-04 10:22:25 +02:00
committed by GitHub
parent aeb937c8c7
commit 6682a18b12
5 changed files with 119 additions and 2 deletions
+20 -1
View File
@@ -644,8 +644,12 @@ func (w *writer) writeRule(rule *ast.Rule, isElse bool, comments []*ast.Comment)
// same line as the end of the head. Comparing against the head's
// start row would wrongly expand the condition into a block whenever
// the head value spans multiple lines (e.g. a multi-line call).
//
// Additionally, a single set term must not be stripped of the outer body
// braces, as that would semantically change the inner set to a body:
// `p if { { x } }` -> p if { x }
headEndRow := rule.Head.Location.Row + strings.Count(string(rule.Head.Location.Text), "\n")
if rule.Body[0].Location.Row == headEndRow {
if rule.Body[0].Location.Row == headEndRow && !isSetTerm(rule.Body[0]) {
w.write(" ")
var err error
comments, err = w.writeExpr(rule.Body[0], comments)
@@ -1029,6 +1033,21 @@ func exprTermsEndRow(expr *ast.Expr) int {
return loc.Row + bytes.Count(text, []byte{'\n'})
}
// isSetTerm reports whether expr is a non-negated set term.
func isSetTerm(expr *ast.Expr) bool {
if expr.IsNegated() {
return false
}
term, ok := expr.Terms.(*ast.Term)
if !ok {
return false
}
_, ok = term.Value.(ast.Set)
return ok
}
func (w *writer) writeSomeDecl(decl *ast.SomeDecl, comments []*ast.Comment) ([]*ast.Comment, error) {
var err error
comments, err = w.insertComments(comments, decl.Location)
@@ -0,0 +1,35 @@
package test
multiple_elements if { {1, 2} }
ref_element if { {input.x} }
scalar_element if { {false} }
set_element if { {{1}} }
array_element if { {[1]} }
parenthesized_scalar_element if { ({false}) }
parenthesized_parenthesized if { ({1, 2}) }
fn(x) if { {x, 2} }
collection contains 1 if { {1, 2} }
a.b.c if { {1, 2} }
with_else := 1 if { {1, 2} } else := 2 if { true }
already_multiline if {
{1, 2}
}
ref_term if { input.x }
object_term if { {"a": 1} }
set_comprehension if { {y | y := input.x} }
set_comparison if { {1, 2} == {1, 2} }
@@ -0,0 +1,59 @@
package test
multiple_elements if {
{1, 2}
}
ref_element if {
{input.x}
}
scalar_element if {
{false}
}
set_element if {
{{1}}
}
array_element if {
{[1]}
}
parenthesized_scalar_element if {
{false}
}
parenthesized_parenthesized if {
{1, 2}
}
fn(x) if {
{x, 2}
}
collection contains 1 if {
{1, 2}
}
a.b.c if {
{1, 2}
}
with_else := 1 if {
{1, 2}
}
else := 2
already_multiline if {
{1, 2}
}
ref_term if input.x
object_term if {"a": 1}
set_comprehension if {y | y := input.x}
set_comparison if {1, 2} == {1, 2}
@@ -36,4 +36,6 @@ b if {
} with input.y as 5 with input.y2 as 6
with input.y3 as 7
with input.y4 as 8
}
}
c if { not {input.x} }
@@ -37,3 +37,5 @@ b if {
with input.y3 as 7
with input.y4 as 8
}
c if not { input.x }