From ab2187089a8dea0d7f1ea611fe8f62db874498b7 Mon Sep 17 00:00:00 2001 From: Kunal Behbudzade <121988281+Kunalbehbud@users.noreply.github.com> Date: Tue, 28 Jul 2026 19:47:31 +0300 Subject: [PATCH] format: Keep rule body inline when the head spans multiple lines (#8904) ### Why the changes in this PR are needed? Fixes #8894. `opa fmt` expands a one-line `if` condition into a block whenever the rule head's *value* expression spans multiple lines, even though the condition itself is a single simple term. For example: ```rego foo := sprintf( "%d", [1], ) if allow ``` was reformatted to: ```rego foo := sprintf( "%d", [1], ) if { allow } ``` ### What are the changes in this PR? The inline-`if` path in `writeRule` decides whether to keep `if ` on one line by comparing the body term's row to the rule head's row: ```go if rule.Body[0].Location.Row == rule.Head.Location.Row { ``` `rule.Head.Location.Row` is the head's **start** row. Once the head value wraps onto later lines, the single body term sits on a later row than the head start, the equality fails, and formatting falls through to the block form. The fix compares against the head's **end** row instead (start row plus the number of newlines in the head's location text), so a single body term on the same line as `if` stays inline regardless of how many lines the head value occupies. Single-line heads are unaffected (end row == start row), and genuinely multi-statement bodies still expand as before (they don't hit the `len(rule.Body) == 1` branch). ### Notes to assist PR review: Added `v1/format/testfiles/v1/test_issue_8894.rego` (+`.formatted`) with the exact repro from the issue; it fails on `master` (expands to a block) and passes with this change. The rest of the format golden suite is unchanged. Signed-off-by: Kunalbehbud --- v1/format/format.go | 7 ++++++- v1/format/testfiles/v1/test_issue_8894.rego | 6 ++++++ v1/format/testfiles/v1/test_issue_8894.rego.formatted | 6 ++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 v1/format/testfiles/v1/test_issue_8894.rego create mode 100644 v1/format/testfiles/v1/test_issue_8894.rego.formatted diff --git a/v1/format/format.go b/v1/format/format.go index dfb4746334..1f25938bae 100644 --- a/v1/format/format.go +++ b/v1/format/format.go @@ -640,7 +640,12 @@ func (w *writer) writeRule(rule *ast.Rule, isElse bool, comments []*ast.Comment) if (w.fmtOpts.regoV1 || w.fmtOpts.ifs) && partialSetException { w.write(" if") if len(rule.Body) == 1 { - if rule.Body[0].Location.Row == rule.Head.Location.Row { + // Keep `if ` on one line when the single body term sits on the + // 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). + headEndRow := rule.Head.Location.Row + strings.Count(string(rule.Head.Location.Text), "\n") + if rule.Body[0].Location.Row == headEndRow { w.write(" ") var err error comments, err = w.writeExpr(rule.Body[0], comments) diff --git a/v1/format/testfiles/v1/test_issue_8894.rego b/v1/format/testfiles/v1/test_issue_8894.rego new file mode 100644 index 0000000000..b524d9ade4 --- /dev/null +++ b/v1/format/testfiles/v1/test_issue_8894.rego @@ -0,0 +1,6 @@ +package test + +foo := sprintf( + "%d", + [1], +) if allow diff --git a/v1/format/testfiles/v1/test_issue_8894.rego.formatted b/v1/format/testfiles/v1/test_issue_8894.rego.formatted new file mode 100644 index 0000000000..b524d9ade4 --- /dev/null +++ b/v1/format/testfiles/v1/test_issue_8894.rego.formatted @@ -0,0 +1,6 @@ +package test + +foo := sprintf( + "%d", + [1], +) if allow