diff --git a/ast/annotations_test.go b/ast/annotations_test.go index 57b317de0e..444d32d651 100644 --- a/ast/annotations_test.go +++ b/ast/annotations_test.go @@ -415,19 +415,20 @@ package root2`, note: "overlapping rule paths (same module)", modules: map[string]string{ "mod": `package test +import rego.v1 # METADATA # title: P1 -p[v] {v = 1} +p contains v if {v = 1} # METADATA # title: P2 -p[v] {v = 2}`, +p contains v if {v = 2}`, }, expected: []AnnotationsRef{ { Path: MustParseRef("data.test.p"), - Location: &Location{File: "mod", Row: 5}, + Location: &Location{File: "mod", Row: 6}, Annotations: &Annotations{ Scope: "rule", Title: "P1", @@ -435,7 +436,7 @@ p[v] {v = 2}`, }, { Path: MustParseRef("data.test.p"), - Location: &Location{File: "mod", Row: 9}, + Location: &Location{File: "mod", Row: 10}, Annotations: &Annotations{ Scope: "rule", Title: "P2", @@ -447,18 +448,22 @@ p[v] {v = 2}`, note: "overlapping rule paths (different modules)", modules: map[string]string{ "mod1": `package test +import rego.v1 + # METADATA # title: P1 -p[v] {v = 1}`, +p contains v if {v = 1}`, "mod2": `package test +import rego.v1 + # METADATA # title: P2 -p[v] {v = 2}`, +p contains v if {v = 2}`, }, expected: []AnnotationsRef{ { Path: MustParseRef("data.test.p"), - Location: &Location{File: "mod1", Row: 4}, + Location: &Location{File: "mod1", Row: 6}, Annotations: &Annotations{ Scope: "rule", Title: "P1", @@ -466,7 +471,7 @@ p[v] {v = 2}`, }, { Path: MustParseRef("data.test.p"), - Location: &Location{File: "mod2", Row: 4}, + Location: &Location{File: "mod2", Row: 6}, Annotations: &Annotations{ Scope: "rule", Title: "P2", @@ -478,18 +483,22 @@ p[v] {v = 2}`, note: "overlapping rule paths (different modules, rule head refs)", modules: map[string]string{ "mod1": `package test.a +import rego.v1 + # METADATA # title: P1 -b.c.p[v] {v = 1}`, +b.c.p[v] if {v = 1}`, "mod2": `package test +import rego.v1 + # METADATA # title: P2 -a.b.c.p[v] {v = 2}`, +a.b.c.p[v] if {v = 2}`, }, expected: []AnnotationsRef{ { Path: MustParseRef("data.test.a.b.c.p"), - Location: &Location{File: "mod1", Row: 4}, + Location: &Location{File: "mod1", Row: 6}, Annotations: &Annotations{ Scope: "rule", Title: "P1", @@ -497,7 +506,7 @@ a.b.c.p[v] {v = 2}`, }, { Path: MustParseRef("data.test.a.b.c.p"), - Location: &Location{File: "mod2", Row: 4}, + Location: &Location{File: "mod2", Row: 6}, Annotations: &Annotations{ Scope: "rule", Title: "P2", diff --git a/ast/capabilities_test.go b/ast/capabilities_test.go index 67e913ff63..b956c1051e 100644 --- a/ast/capabilities_test.go +++ b/ast/capabilities_test.go @@ -263,7 +263,11 @@ func TestCapabilitiesMinimumCompatibleVersion(t *testing.T) { for _, tc := range tests { t.Run(tc.note, func(t *testing.T) { - c := MustCompileModules(map[string]string{"test.rego": tc.module}) + c := MustCompileModulesWithOpts(map[string]string{"test.rego": tc.module}, CompileOpts{ + ParserOptions: ParserOptions{ + RegoVersion: RegoV0, + }, + }) minVersion, found := c.Required.MinimumCompatibleVersion() if !found || minVersion != tc.version { t.Fatal("expected", tc.version, "but got", minVersion) diff --git a/ast/check_test.go b/ast/check_test.go index bc84952d37..7cae88f773 100644 --- a/ast/check_test.go +++ b/ast/check_test.go @@ -1225,17 +1225,19 @@ func TestCheckRefErrInvalid(t *testing.T) { func TestFunctionsTypeInference(t *testing.T) { functions := []string{ - `foo([a, b]) = y { split(a, b, y) }`, - `bar(x) = y { count(x, y) }`, - `baz([x, y]) = z { sprintf("%s%s", [x, y], z) }`, - `qux({"bar": x, "foo": y}) = {a: b} { upper(y, a); json.unmarshal(x, b) }`, - `corge(x) = y { qux({"bar": x, "foo": x}, a); baz([a["{5: true}"], "BUZ"], y) }`, + `foo([a, b]) = y if { split(a, b, y) }`, + `bar(x) = y if { count(x, y) }`, + `baz([x, y]) = z if { sprintf("%s%s", [x, y], z) }`, + `qux({"bar": x, "foo": y}) = {a: b} if { upper(y, a); json.unmarshal(x, b) }`, + `corge(x) = y if { qux({"bar": x, "foo": x}, a); baz([a["{5: true}"], "BUZ"], y) }`, } body := strings.Join(functions, "\n") base := fmt.Sprintf("package base\n%s", body) + popts := ParserOptions{AllFutureKeywords: true} + c := NewCompiler() - if c.Compile(map[string]*Module{"base": MustParseModule(base)}); c.Failed() { + if c.Compile(map[string]*Module{"base": MustParseModuleWithOpts(base, popts)}); c.Failed() { t.Fatalf("Failed to compile base module: %v", c.Errors) } @@ -1244,68 +1246,68 @@ func TestFunctionsTypeInference(t *testing.T) { wantErr bool }{ { - `fn(_) = y { data.base.foo(["hello", 5], y) }`, + `fn(_) = y if { data.base.foo(["hello", 5], y) }`, true, }, { - `fn(_) = y { data.base.foo(["hello", "ll"], y) }`, + `fn(_) = y if { data.base.foo(["hello", "ll"], y) }`, false, }, { - `fn(_) = y { data.base.baz(["hello", "ll"], y) }`, + `fn(_) = y if { data.base.baz(["hello", "ll"], y) }`, false, }, { - `fn(_) = y { data.base.baz([5, ["foo", "bar", true]], y) }`, + `fn(_) = y if { data.base.baz([5, ["foo", "bar", true]], y) }`, false, }, { - `fn(_) = y { data.base.baz(["hello", {"a": "b", "c": 3}], y) }`, + `fn(_) = y if { data.base.baz(["hello", {"a": "b", "c": 3}], y) }`, false, }, { - `fn(_) = y { data.base.corge("this is not json", y) }`, + `fn(_) = y if { data.base.corge("this is not json", y) }`, false, }, { - `fn(x) = y { data.non_existent(x, a); y = a[0] }`, + `fn(x) = y if { data.non_existent(x, a); y = a[0] }`, true, }, { - `fn(x) = y { y = [x] }`, + `fn(x) = y if { y = [x] }`, false, }, { - `f(x) = y { [x] = y }`, + `f(x) = y if { [x] = y }`, false, }, { - `fn(x) = y { y = {"k": x} }`, + `fn(x) = y if { y = {"k": x} }`, false, }, { - `f(x) = y { {"k": x} = y }`, + `f(x) = y if { {"k": x} = y }`, false, }, { - `p { [data.base.foo] }`, + `p if { [data.base.foo] }`, true, }, { - `p { x = data.base.foo }`, + `p if { x = data.base.foo }`, true, }, { - `p { data.base.foo(data.base.bar) }`, + `p if { data.base.foo(data.base.bar) }`, true, }, } for n, test := range tests { t.Run(fmt.Sprintf("Test Case %d", n), func(t *testing.T) { - mod := MustParseModule(fmt.Sprintf("package test\n%s", test.body)) + mod := MustParseModuleWithOpts(fmt.Sprintf("package test\n%s", test.body), popts) c := NewCompiler() - c.Compile(map[string]*Module{"base": MustParseModule(base), "mod": mod}) + c.Compile(map[string]*Module{"base": MustParseModuleWithOpts(base, popts), "mod": mod}) if test.wantErr && !c.Failed() { t.Errorf("Expected error but got success") } else if !test.wantErr && c.Failed() { @@ -1321,8 +1323,9 @@ func TestFunctionTypeInferenceUnappliedWithObjectVarKey(t *testing.T) { // from args in the head. module := MustParseModule(` package test + import rego.v1 - f(x) = y { y = {x: 1} } + f(x) := y if { y = {x: 1} } `) elems := []util.T{ @@ -1348,12 +1351,13 @@ func TestCheckValidErrors(t *testing.T) { module := MustParseModule(` package test + import rego.v1 - p { + p if { concat("", 1) # type error } - q { + q if { r(1) } @@ -1361,33 +1365,35 @@ func TestCheckValidErrors(t *testing.T) { module2 := MustParseModule(` package test + import rego.v1 - b { + b if { a(1) # call erroneous function } - a(x) { + a(x) if { max("foo") # max requires an array } - m { + m if { 1 / "foo" # type error } - n { + n if { m # call erroneous rule }`) module3 := MustParseModule(` package test + import rego.v1 x := {"a" : 1} - y { + y if { z } - z { + z if { x[1] == 1 # undefined reference error }`) @@ -1506,11 +1512,12 @@ func TestCheckErrorOrdering(t *testing.T) { mod := MustParseModule(` package test + import rego.v1 q = true - p { data.test.q = 1 } # type error: bool = number - p { data.test.q = 2 } # type error: bool = number + p if { data.test.q = 1 } # type error: bool = number + p if { data.test.q = 2 } # type error: bool = number `) input := make([]util.T, len(mod.Rules)) @@ -2310,6 +2317,7 @@ p { input = "foo" }`}}, for i, module := range tc.modules { mod, err := ParseModuleWithOpts(fmt.Sprintf("test%d.rego", i+1), module, ParserOptions{ ProcessAnnotation: true, + RegoVersion: RegoV0, }) if err != nil { t.Fatal(err) @@ -2360,12 +2368,13 @@ func TestCheckAnnotationInference(t *testing.T) { modules: map[string]string{ "test.rego": ` package test +import rego.v1 # METADATA # scope: rule # schemas: # - input: schema.foo -p = x { input = x } +p = x if { input = x } q = p`, }, @@ -2438,11 +2447,12 @@ func TestRemoteSchema(t *testing.T) { policy := fmt.Sprintf(` package test +import rego.v1 # METADATA # schemas: # - input: {$ref: "%s"} -p { +p if { input == 42 }`, server.URL) @@ -2486,11 +2496,12 @@ func TestRemoteSchemaHostNotAllowed(t *testing.T) { policy := fmt.Sprintf(` package test +import rego.v1 # METADATA # schemas: # - input: {$ref: "%s"} -p { +p if { input == 42 }`, server.URL) diff --git a/ast/compile_test.go b/ast/compile_test.go index f586ceb331..dade9ed506 100644 --- a/ast/compile_test.go +++ b/ast/compile_test.go @@ -321,15 +321,13 @@ func TestCompilerGetExports(t *testing.T) { { note: "var key single-value ref rule", modules: modules(`package p - q.r[s] = 1 { s := "foo" }`), + q.r[s] = 1 if { s := "foo" }`), exports: map[string][]string{"data.p": {"q.r"}}, }, { note: "simple multi-value ref rule", modules: modules(`package p - import future.keywords - - q.r.s contains 1 { true }`), + q.r.s contains 1 if { true }`), exports: map[string][]string{"data.p": {"q.r.s"}}, }, { @@ -389,7 +387,7 @@ func TestCompilerGetExports(t *testing.T) { { note: "single-value (ref) rule with var key", modules: modules(`package p - a.b.q[x] = y { x := 1; y := true } + a.b.q[x] = y if { x := 1; y := true } a.b.q[2] = 2`), exports: map[string][]string{ "data.p": {"a.b.q", "a.b.q[2]"}, // TODO(sr): GroundPrefix? right thing here? @@ -477,70 +475,70 @@ func TestCompilerCheckRuleHeadRefs(t *testing.T) { note: "ref contains var", modules: modules( `package x - p.q[i].r = 1 { i := 10 }`, + p.q[i].r = 1 if { i := 10 }`, ), }, { note: "valid: ref is single-value rule with var key", modules: modules( `package x - p.q.r[i] { i := 10 }`, + p.q.r[i] if { i := 10 }`, ), }, { note: "valid: ref is single-value rule with var key and value", modules: modules( `package x - p.q.r[i] = j { i := 10; j := 11 }`, + p.q.r[i] = j if { i := 10; j := 11 }`, ), }, { note: "valid: ref is single-value rule with var key and static value", modules: modules( `package x - p.q.r[i] = "ten" { i := 10 }`, + p.q.r[i] = "ten" if { i := 10 }`, ), }, { note: "valid: ref is single-value rule with number key", modules: modules( `package x - p.q.r[1] { true }`, + p.q.r[1] if { true }`, ), }, { note: "valid: ref is single-value rule with boolean key", modules: modules( `package x - p.q.r[true] { true }`, + p.q.r[true] if { true }`, ), }, { note: "valid: ref is single-value rule with null key", modules: modules( `package x - p.q.r[null] { true }`, + p.q.r[null] if { true }`, ), }, { note: "valid: ref is single-value rule with set literal key", modules: modules( `package x - p.q.r[set()] { true }`, + p.q.r[set()] if { true }`, ), }, { note: "valid: ref is single-value rule with array literal key", modules: modules( `package x - p.q.r[[]] { true }`, + p.q.r[[]] if { true }`, ), }, { note: "valid: ref is single-value rule with object literal key", modules: modules( `package x - p.q.r[{}] { true }`, + p.q.r[{}] if { true }`, ), }, { @@ -548,21 +546,21 @@ func TestCompilerCheckRuleHeadRefs(t *testing.T) { modules: modules( `package x x := [1,2,3] - p.q.r[x[i]] { i := 0}`, + p.q.r[x[i]] if { i := 0}`, ), }, { note: "invalid: ref in ref", modules: modules( `package x - p.q[arr[0]].r { i := 10 }`, + p.q[arr[0]].r if { i := 10 }`, ), }, { note: "invalid: non-string in ref (not last position)", modules: modules( `package x - p.q[10].r { true }`, + p.q[10].r if { true }`, ), }, { @@ -695,9 +693,9 @@ func TestRuleTreeWithDotsInHeads(t *testing.T) { note: "simple: two modules, one using ref head, one package path", modules: modules( `package x - p.q.r = 1 { input == 1 }`, + p.q.r = 1 if { input == 1 }`, `package x.p.q - r = 2 { input == 2 }`, + r = 2 if { input == 2 }`, ), size: 2, }, @@ -705,9 +703,9 @@ func TestRuleTreeWithDotsInHeads(t *testing.T) { note: "conflict: two modules, both using ref head, different package paths", modules: modules( `package x - p.q.r = 1 { input == 1 }`, // x.p.q.r = 1 + p.q.r = 1 if { input == 1 }`, // x.p.q.r = 1 `package x.p - q.r.s = 2 { input == 2 }`, // x.p.q.r.s = 2 + q.r.s = 2 if { input == 2 }`, // x.p.q.r.s = 2 ), size: 2, }, @@ -728,7 +726,7 @@ func TestRuleTreeWithDotsInHeads(t *testing.T) { p.q.w[1] = 2 p.q.w[{"foo": "baz"}] = 20 p.q.x[true] = false - p.q.x[y] = y { y := "y" }`, + p.q.x[y] = y if { y := "y" }`, ), size: 4, depth: 6, @@ -781,7 +779,7 @@ func TestRuleIndices(t *testing.T) { p.q contains "foo" - p[q] := r { + p[q] := r if { q := "bar" r := "baz" }`, @@ -848,7 +846,11 @@ func TestRuleIndices(t *testing.T) { } func TestRuleTreeWithVars(t *testing.T) { - opts := ParserOptions{AllFutureKeywords: true, unreleasedKeywords: true} + opts := ParserOptions{ + RegoVersion: RegoV0, + AllFutureKeywords: true, + unreleasedKeywords: true, + } t.Run("simple single-value rule", func(t *testing.T) { mod0 := `package a.b @@ -1118,8 +1120,12 @@ func TestModuleTreeFilenameOrder(t *testing.T) { // becomes very apparent: before this change, the rule that was reported as // "conflicting" was that of either one of the input files, randomly. mods := map[string]*Module{ - "0.rego": MustParseModule("package p\nr = 1 { true }"), - "1.rego": MustParseModule("package p\nr = 2 { true }"), + "0.rego": MustParseModule(`package p +import rego.v1 +r = 1 if { true }`), + "1.rego": MustParseModule(`package p +import rego.v1 +r = 2 if { true }`), } tree := NewModuleTree(mods) vals := tree.Children[Var("data")].Children[String("p")].Modules @@ -1149,9 +1155,10 @@ func TestRuleTree(t *testing.T) { p = 1`) mods["mod-incr"] = MustParseModule(` package a.b.c + import rego.v1 - s[1] { true } - s[2] { true }`, + s contains 1 if { true } + s contains 2 if { true }`, ) mods["dots-in-heads"] = MustParseModule(` @@ -1219,7 +1226,7 @@ func TestCompilerEmpty(t *testing.T) { func TestCompilerExample(t *testing.T) { c := NewCompiler() - m := MustParseModule(testModule) + m := MustParseModuleWithOpts(testModule, ParserOptions{AllFutureKeywords: true}) c.Compile(map[string]*Module{"testMod": m}) assertNotFailed(t, c) } @@ -1231,7 +1238,7 @@ func TestCompilerWithStageAfter(t *testing.T) { CompilerStageDefinition{"MockStage", "mock_stage", func(*Compiler) *Error { return NewError(CompileErr, &Location{}, "mock stage error") }}, ) - m := MustParseModule(testModule) + m := MustParseModuleWithOpts(testModule, ParserOptions{AllFutureKeywords: true}) c.Compile(map[string]*Module{"testMod": m}) if !c.Failed() { @@ -1267,7 +1274,9 @@ q := true`) CompilerStageDefinition{"MockStage", "mock_stage", func(*Compiler) *Error { return NewError(CompileErr, &Location{}, "mock stage error") }}) m := MustParseModule(`package p -q { +import rego.v1 + +q if { 1 == "a" # would fail "CheckTypes", the next stage } `) @@ -1467,7 +1476,7 @@ func TestCompilerFunctions(t *testing.T) { modules := map[string]*Module{} for i, module := range tc.modules { name := fmt.Sprintf("mod%d", i) - modules[name], err = ParseModule(name, module) + modules[name], err = ParseModuleWithOpts(name, module, ParserOptions{RegoVersion: RegoV0}) if err != nil { panic(err) } @@ -1486,13 +1495,15 @@ func TestCompilerFunctions(t *testing.T) { func TestCompilerErrorLimit(t *testing.T) { modules := map[string]*Module{ "test": MustParseModule(`package test - r = y { y = true; x = z } + import rego.v1 - s[x] = y { + r = y if { y = true; x = z } + + s[x] = y if { z = y + x } - t[x] { split(x, y, z) } + t contains x if { split(x, y, z) } `), } @@ -1501,8 +1512,8 @@ func TestCompilerErrorLimit(t *testing.T) { errs := c.Errors exp := []string{ - "2:20: rego_unsafe_var_error: var x is unsafe", - "2:20: rego_unsafe_var_error: var z is unsafe", + "4:23: rego_unsafe_var_error: var x is unsafe", + "4:23: rego_unsafe_var_error: var z is unsafe", "rego_compile_error: error limit reached", } @@ -1524,12 +1535,12 @@ func TestCompilerCheckSafetyHead(t *testing.T) { popts := ParserOptions{AllFutureKeywords: true, unreleasedKeywords: true} c.Modules["newMod"] = MustParseModuleWithOpts(`package a.b -unboundKey[x1] = y { q[y] = {"foo": [1, 2, [{"bar": y}]]} } -unboundVal[y] = x2 { q[y] = {"foo": [1, 2, [{"bar": y}]]} } -unboundCompositeVal[y] = [{"foo": x3, "bar": y}] { q[y] = {"foo": [1, 2, [{"bar": y}]]} } -unboundCompositeKey[[{"x": x4}]] { q[y] } -unboundBuiltinOperator = eq { 4 = 1 } -unboundElse { false } else = else_var { true } +unboundKey[x1] = y if { q[y] = {"foo": [1, 2, [{"bar": y}]]} } +unboundVal[y] = x2 if { q[y] = {"foo": [1, 2, [{"bar": y}]]} } +unboundCompositeVal[y] = [{"foo": x3, "bar": y}] if { q[y] = {"foo": [1, 2, [{"bar": y}]]} } +unboundCompositeKey contains [{"x": x4}] if { q[y] } +unboundBuiltinOperator = eq if { 4 = 1 } +unboundElse if { false } else = else_var if { true } c.d.e[x5] if true f.g.h[y] = x6 if y := "y" i.j.k contains x7 if true @@ -1601,7 +1612,11 @@ func TestCompilerCheckSafetyBodyReordering(t *testing.T) { for i, tc := range tests { t.Run(tc.note, func(t *testing.T) { - opts := ParserOptions{AllFutureKeywords: true, unreleasedKeywords: true} + opts := ParserOptions{ + RegoVersion: RegoV0, + AllFutureKeywords: true, + unreleasedKeywords: true, + } c := NewCompiler() c.Modules = getCompilerTestModules() c.Modules["reordering"] = MustParseModuleWithOpts(fmt.Sprintf( @@ -1636,20 +1651,20 @@ func TestCompilerCheckSafetyBodyReorderingClosures(t *testing.T) { { note: "comprehensions-1", mod: MustParseModule(`package compr - +import rego.v1 import data.b import data.c -p = true { v = [null | true]; xs = [x | a[i] = x; a = [y | y != 1; y = c[j]]]; xs[j] > 0; z = [true | data.a.b.d.t with input as i2; i2 = i]; b[i] = j } +p = true if { v = [null | true]; xs = [x | a[i] = x; a = [y | y != 1; y = c[j]]]; xs[j] > 0; z = [true | data.a.b.d.t with input as i2; i2 = i]; b[i] = j } `), exp: MustParseBody(`v = [null | true]; data.b[i] = j; xs = [x | a = [y | y = data.c[j]; y != 1]; a[i] = x]; xs[j] > 0; z = [true | i2 = i; data.a.b.d.t with input as i2]`), }, { note: "comprehensions-2", mod: MustParseModule(`package compr - +import rego.v1 import data.b import data.c -q = true { _ = [x | x = b[i]]; _ = b[j]; _ = [x | x = true; x != false]; true != false; _ = [x | data.foo[_] = x]; data.foo[_] = _ } +q = true if { _ = [x | x = b[i]]; _ = b[j]; _ = [x | x = true; x != false]; true != false; _ = [x | data.foo[_] = x]; data.foo[_] = _ } `), exp: MustParseBody(`_ = [x | x = data.b[i]]; _ = data.b[j]; _ = [x | x = true; x != false]; true != false; _ = [x | data.foo[_] = x]; data.foo[_] = _`), }, @@ -1657,22 +1672,22 @@ q = true { _ = [x | x = b[i]]; _ = b[j]; _ = [x | x = true; x != false]; true != { note: "comprehensions-3", mod: MustParseModule(`package compr - +import rego.v1 import data.b import data.c -fn(x) = y { +fn(x) = y if { trim(x, ".", y) } -r = true { a = [x | split(y, ".", z); x = z[i]; fn("...foo.bar..", y)] } +r = true if { a = [x | split(y, ".", z); x = z[i]; fn("...foo.bar..", y)] } `), exp: MustParseBody(`a = [x | data.compr.fn("...foo.bar..", y); split(y, ".", z); x = z[i]]`), }, { note: "closure over function output", mod: MustParseModule(`package test -import future.keywords +import rego.v1 -p { +p if { object.get(input.subject.roles[_], comp, [""], output) comp = [ 1 | true ] every y in [2] { @@ -1716,39 +1731,39 @@ func TestCompilerCheckSafetyBodyErrors(t *testing.T) { moduleContent string expected string }{ - {"ref-head", `p { a.b.c = "foo" }`, `{a,}`}, - {"ref-head-2", `p { {"foo": [{"bar": a.b.c}]} = {"foo": [{"bar": "baz"}]} }`, `{a,}`}, - {"negation", `p { a = [1, 2, 3, 4]; not a[i] = x }`, `{i, x}`}, - {"negation-head", `p[x] { a = [1, 2, 3, 4]; not a[i] = x }`, `{i,x}`}, - {"negation-multiple", `p { a = [1, 2, 3, 4]; b = [1, 2, 3, 4]; not a[i] = x; not b[j] = x }`, `{i, x, j}`}, - {"negation-nested", `p { a = [{"foo": ["bar", "baz"]}]; not a[0].foo = [a[0].foo[i], a[0].foo[j]] } `, `{i, j}`}, - {"builtin-input", `p { count([1, 2, x], x) }`, `{x,}`}, - {"builtin-input-name", `p { count(eq, 1) }`, `{eq,}`}, - {"builtin-multiple", `p { x > 0; x <= 3; x != 2 }`, `{x,}`}, - {"unordered-object-keys", `p { x = "a"; [{x: y, z: a}] = [{"a": 1, "b": 2}]}`, `{a,y,z}`}, - {"unordered-sets", `p { x = "a"; [{x, y}] = [{1, 2}]}`, `{y,}`}, - {"array-compr", `p { _ = [x | x = data.a[_]; y > 1] }`, `{y,}`}, - {"array-compr-nested", `p { _ = [x | x = a[_]; a = [y | y = data.a[_]; z > 1]] }`, `{z,}`}, - {"array-compr-closure", `p { _ = [v | v = [x | x = data.a[_]]; x > 1] }`, `{x,}`}, - {"array-compr-term", `p { _ = [u | true] }`, `{u,}`}, - {"array-compr-term-nested", `p { _ = [v | v = [w | w != 0]] }`, `{w,}`}, - {"array-compr-mixed", `p { _ = [x | y = [a | a = z[i]]] }`, `{a, x, z, i}`}, - {"array-compr-builtin", `p { [true | eq != 2] }`, `{eq,}`}, - {"closure-self", `p { x = [x | x = 1] }`, `{x,}`}, - {"closure-transitive", `p { x = y; x = [y | y = 1] }`, `{x,y}`}, - {"nested", `p { count(baz[i].attr[bar[dead.beef]], n) }`, `{dead,}`}, - {"negated-import", `p { not foo; not bar; not baz }`, `set()`}, - {"rewritten", `p[{"foo": dead[i]}] { true }`, `{dead, i}`}, - {"with-value", `p { data.a.b.d.t with input as x }`, `{x,}`}, - {"with-value-2", `p { x = data.a.b.d.t with input as x }`, `{x,}`}, - {"else-kw", "p { false } else { count(x, 1) }", `{x,}`}, - {"function", "foo(x) = [y, z] { split(x, y, z) }", `{y,z}`}, - {"call-vars-input", "p { f(x, x) } f(x) = x { true }", `{x,}`}, - {"call-no-output", "p { f(x) } f(x) = x { true }", `{x,}`}, - {"call-too-few", "p { f(1,x) } f(x,y) { true }", "{x,}"}, - {"object-key-comprehension", "p { { {p|x}: 0 } }", "{x,}"}, - {"set-value-comprehension", "p { {1, {p|x}} }", "{x,}"}, - {"every", "p { every y in [10] { x > y } }", "{x,}"}, + {"ref-head", `p if { a.b.c = "foo" }`, `{a,}`}, + {"ref-head-2", `p if { {"foo": [{"bar": a.b.c}]} = {"foo": [{"bar": "baz"}]} }`, `{a,}`}, + {"negation", `p if { a = [1, 2, 3, 4]; not a[i] = x }`, `{i, x}`}, + {"negation-head", `p contains x if { a = [1, 2, 3, 4]; not a[i] = x }`, `{i,x}`}, + {"negation-multiple", `p if { a = [1, 2, 3, 4]; b = [1, 2, 3, 4]; not a[i] = x; not b[j] = x }`, `{i, x, j}`}, + {"negation-nested", `p if { a = [{"foo": ["bar", "baz"]}]; not a[0].foo = [a[0].foo[i], a[0].foo[j]] } `, `{i, j}`}, + {"builtin-input", `p if { count([1, 2, x], x) }`, `{x,}`}, + {"builtin-input-name", `p if { count(eq, 1) }`, `{eq,}`}, + {"builtin-multiple", `p if { x > 0; x <= 3; x != 2 }`, `{x,}`}, + {"unordered-object-keys", `p if { x = "a"; [{x: y, z: a}] = [{"a": 1, "b": 2}]}`, `{a,y,z}`}, + {"unordered-sets", `p if { x = "a"; [{x, y}] = [{1, 2}]}`, `{y,}`}, + {"array-compr", `p if { _ = [x | x = data.a[_]; y > 1] }`, `{y,}`}, + {"array-compr-nested", `p if { _ = [x | x = a[_]; a = [y | y = data.a[_]; z > 1]] }`, `{z,}`}, + {"array-compr-closure", `p if { _ = [v | v = [x | x = data.a[_]]; x > 1] }`, `{x,}`}, + {"array-compr-term", `p if { _ = [u | true] }`, `{u,}`}, + {"array-compr-term-nested", `p if { _ = [v | v = [w | w != 0]] }`, `{w,}`}, + {"array-compr-mixed", `p if { _ = [x | y = [a | a = z[i]]] }`, `{a, x, z, i}`}, + {"array-compr-builtin", `p if { [true | eq != 2] }`, `{eq,}`}, + {"closure-self", `p if { x = [x | x = 1] }`, `{x,}`}, + {"closure-transitive", `p if { x = y; x = [y | y = 1] }`, `{x,y}`}, + {"nested", `p if { count(baz[i].attr[bar[dead.beef]], n) }`, `{dead,}`}, + {"negated-import", `p if { not foo; not bar; not baz }`, `set()`}, + {"rewritten", `p contains {"foo": dead[i]} if { true }`, `{dead, i}`}, + {"with-value", `p if { data.a.b.d.t with input as x }`, `{x,}`}, + {"with-value-2", `p if { x = data.a.b.d.t with input as x }`, `{x,}`}, + {"else-kw", "p if { false } else if { count(x, 1) }", `{x,}`}, + {"function", "foo(x) = [y, z] if { split(x, y, z) }", `{y,z}`}, + {"call-vars-input", "p if { f(x, x) } f(x) = x if { true }", `{x,}`}, + {"call-no-output", "p if { f(x) } f(x) = x if { true }", `{x,}`}, + {"call-too-few", "p if { f(1,x) } f(x,y) if { true }", "{x,}"}, + {"object-key-comprehension", "p if { { {p|x}: 0 } }", "{x,}"}, + {"set-value-comprehension", "p if { {1, {p|x}} }", "{x,}"}, + {"every", "p if { every y in [10] { x > y } }", "{x,}"}, } makeErrMsg := func(varName string) string { @@ -1769,7 +1784,10 @@ func TestCompilerCheckSafetyBodyErrors(t *testing.T) { sort.Strings(expected) // Compile test module. - popts := ParserOptions{AllFutureKeywords: true, unreleasedKeywords: true} + popts := ParserOptions{ + AllFutureKeywords: true, + unreleasedKeywords: true, + } c := NewCompiler() c.Modules = map[string]*Module{ "newMod": MustParseModuleWithOpts(fmt.Sprintf(` @@ -1804,8 +1822,9 @@ func TestCompilerCheckSafetyBodyErrors(t *testing.T) { func TestCompilerCheckSafetyVarLoc(t *testing.T) { _, err := CompileModules(map[string]string{"test.rego": `package test +import rego.v1 -p { +p if { not x x > y }`}) @@ -1816,12 +1835,12 @@ p { errs := err.(Errors) - if !strings.Contains(errs[0].Message, "var x is unsafe") || errs[0].Location.Row != 4 { - t.Fatal("expected error on row 4 but got:", err) + if !strings.Contains(errs[0].Message, "var x is unsafe") || errs[0].Location.Row != 5 { + t.Fatal("expected error on row 5 but got:", err) } - if !strings.Contains(errs[1].Message, "var y is unsafe") || errs[1].Location.Row != 5 { - t.Fatal("expected y is unsafe on row 5 but got:", err) + if !strings.Contains(errs[1].Message, "var y is unsafe") || errs[1].Location.Row != 6 { + t.Fatal("expected y is unsafe on row 6 but got:", err) } } @@ -1874,68 +1893,67 @@ func TestCompilerCheckRuleConflicts(t *testing.T) { c := getCompilerWithParsedModules(map[string]string{ "mod1.rego": `package badrules -p[x] { x = 1 } -p[x] = y { x = y; x = "a" } -q[1] { true } -q = {1, 2, 3} { true } -r[x] = y { x = y; x = "a" } -r[x] = y { x = y; x = "a" } -s[x] { x = "a" } -s[x] { x = "b" } -t := x { x = "a"}`, +p contains x if { x = 1 } +p[x] = y if { x = y; x = "a" } +q contains 1 if { true } +q = {1, 2, 3} if { true } +r[x] = y if { x = y; x = "a" } +r[x] = y if { x = y; x = "a" } +s contains x if { x = "a" } +s contains x if { x = "b" } +t := x if { x = "a"}`, // valid extension of r in mod1.rego "mod2a.rego": `package badrules.r -q[1] { true }`, +q contains 1 if { true }`, // invalid override of s in mod1.rego "mod2b.rego": `package badrules.s -q[1] { true }`, +q contains 1 if { true }`, // invalid override of t in mod1.rego "mod2c.rego": `package badrules.t -q[1] { true }`, +q contains 1 if { true }`, "mod3.rego": `package badrules.defkw default foo = 1 default foo = 2 -foo = 3 { true } +foo = 3 if { true } default p.q.bar = 1 default p.q.bar = 2 -p.q.bar = 3 { true } +p.q.bar = 3 if { true } `, "mod4.rego": `package badrules.arity -f(1) { true } -f { true } +f(1) if { true } +f if { true } -g(1) { true } -g(1,2) { true } +g(1) if { true } +g(1,2) if { true } -p.q.h(1) { true } -p.q.h { true } +p.q.h(1) if { true } +p.q.h if { true } -p.q.i(1) { true } -p.q.i(1,2) { true }`, +p.q.i(1) if { true } +p.q.i(1,2) if { true }`, "mod5.rego": `package badrules.dataoverlap -p { true }`, +p if { true }`, "mod6.rego": `package badrules.existserr -p { true }`, +p if { true }`, "mod7.rego": `package badrules.foo -import future.keywords bar.baz contains "quz" if true`, "mod8.rego": `package badrules.complete_partial p := 1 -p[r] := 2 { r := "foo" }`, +p[r] := 2 if { r := "foo" }`, }) c.WithPathConflictsCheck(func(path []string) (bool, error) { @@ -1982,7 +2000,7 @@ func TestCompilerCheckRuleConflictsDefaultFunction(t *testing.T) { modules: modules( `package pkg default f(_) = 100 - f(x, y) = x { + f(x, y) = x if { x == y }`), err: "rego_type_error: conflicting rules data.pkg.f found", @@ -2016,7 +2034,7 @@ func TestCompilerCheckRuleConflictsDotsInRuleHeads(t *testing.T) { note: "arity mismatch, ref and non-ref rule", modules: modules( `package pkg - p.q.r { true }`, + p.q.r if { true }`, `package pkg.p.q r(_) = 2`), err: "rego_type_error: conflicting rules data.pkg.p.q.r found", @@ -2026,7 +2044,7 @@ func TestCompilerCheckRuleConflictsDotsInRuleHeads(t *testing.T) { modules: modules( `package pkg default p.q.r = 3 - p.q.r { true }`, + p.q.r if { true }`, `package pkg.p.q default r = 4 r = 2`), @@ -2036,7 +2054,7 @@ func TestCompilerCheckRuleConflictsDotsInRuleHeads(t *testing.T) { note: "arity mismatch, ref and ref rule", modules: modules( `package pkg.a.b - p.q.r { true }`, + p.q.r if { true }`, `package pkg.a b.p.q.r(_) = 2`), err: "rego_type_error: conflicting rules data.pkg.a.b.p.q.r found", @@ -2046,7 +2064,7 @@ func TestCompilerCheckRuleConflictsDotsInRuleHeads(t *testing.T) { modules: modules( `package pkg default p.q.w.r = 3 - p.q.w.r { true }`, + p.q.w.r if { true }`, `package pkg.p default q.w.r = 4 q.w.r = 2`), @@ -2073,7 +2091,7 @@ func TestCompilerCheckRuleConflictsDotsInRuleHeads(t *testing.T) { note: "module conflict: non-ref rule", modules: modules( `package pkg.q - r { true }`, + r if { true }`, `package pkg.q.r`), err: "rego_type_error: package pkg.q.r conflicts with rule r defined at mod0.rego:2", }, @@ -2081,7 +2099,7 @@ func TestCompilerCheckRuleConflictsDotsInRuleHeads(t *testing.T) { note: "module conflict: ref rule", modules: modules( `package pkg - p.q.r { true }`, + p.q.r if { true }`, `package pkg.p.q.r`), err: "rego_type_error: package pkg.p.q.r conflicts with rule p.q.r defined at mod0.rego:2", }, @@ -2089,18 +2107,18 @@ func TestCompilerCheckRuleConflictsDotsInRuleHeads(t *testing.T) { note: "single-value with other rule overlap", modules: modules( `package pkg - p.q.r { true }`, + p.q.r if { true }`, `package pkg - p.q.r.s { true }`), + p.q.r.s if { true }`), err: "rego_type_error: rule data.pkg.p.q.r conflicts with [data.pkg.p.q.r.s]", }, { note: "single-value with other rule overlap", modules: modules( `package pkg - p.q.r { true } - p.q.r.s { true } - p.q.r.t { true }`), + p.q.r if { true } + p.q.r.s if { true } + p.q.r.t if { true }`), err: "rego_type_error: rule data.pkg.p.q.r conflicts with [data.pkg.p.q.r.s data.pkg.p.q.r.t]", }, { @@ -2108,39 +2126,39 @@ func TestCompilerCheckRuleConflictsDotsInRuleHeads(t *testing.T) { modules: modules( `package pkg p.q := 1 - p.q[r] := 2 { r := "foo" }`), + p.q[r] := 2 if { r := "foo" }`), err: "rego_type_error: conflicting rules data.pkg.p.q[r] foun", }, { note: "single-value with other rule overlap, unknown key", modules: modules( `package pkg - p.q[r] = x { r = input.key; x = input.foo } - p.q.r.s = x { true } + p.q[r] = x if { r = input.key; x = input.foo } + p.q.r.s = x if { true } `), }, { note: "single-value with other rule overlap, unknown ref var and key", modules: modules( `package pkg - p.q[r][s] = x { r = input.key1; s = input.key2; x = input.foo } - p.q.r.s.t = x { true } + p.q[r][s] = x if { r = input.key1; s = input.key2; x = input.foo } + p.q.r.s.t = x if { true } `), }, { note: "single-value partial object with other partial object rule overlap, unknown keys (regression test for #5855; invalidated by multi-var refs)", modules: modules( `package pkg - p[r] := x { r = input.key; x = input.bar } - p.q[r] := x { r = input.key; x = input.bar } + p[r] := x if { r = input.key; x = input.bar } + p.q[r] := x if { r = input.key; x = input.bar } `), }, { note: "single-value partial object with other partial object (implicit 'true' value) rule overlap, unknown keys", modules: modules( `package pkg - p[r] := x { r = input.key; x = input.bar } - p.q[r] { r = input.key } + p[r] := x if { r = input.key; x = input.bar } + p.q[r] if { r = input.key } `), }, { @@ -2148,16 +2166,16 @@ func TestCompilerCheckRuleConflictsDotsInRuleHeads(t *testing.T) { modules: modules( `package pkg import future.keywords - p[r] := x { r = input.key; x = input.bar } - p.q contains r { r = input.key } + p[r] := x if { r = input.key; x = input.bar } + p.q contains r if { r = input.key } `), }, { note: "single-value partial object with multi-value rule overlap, unknown key", modules: modules( `package pkg - p[r] := x { r = input.key; x = input.bar } - p.q { true } + p[r] := x if { r = input.key; x = input.bar } + p contains q if { true } `), err: "rego_type_error: conflicting rules data.pkg.p found", }, @@ -2165,15 +2183,15 @@ func TestCompilerCheckRuleConflictsDotsInRuleHeads(t *testing.T) { note: "single-value rule with known and unknown key", modules: modules( `package pkg - p.q[r] = x { r = input.key; x = input.foo } - p.q.s = "x" { true } + p.q[r] = x if { r = input.key; x = input.foo } + p.q.s = "x" if { true } `), }, { note: "multi-value rule with other rule overlap", modules: modules( `package pkg - p[v] { v := ["a", "b"][_] } + p contains v if { v := ["a", "b"][_] } p.q := 42 `), err: "rego_type_error: rule data.pkg.p conflicts with [data.pkg.p.q]", @@ -2182,8 +2200,8 @@ func TestCompilerCheckRuleConflictsDotsInRuleHeads(t *testing.T) { note: "multi-value rule with other rule (ref) overlap", modules: modules( `package pkg - p[v] { v := ["a", "b"][_] } - p.q.r { true } + p contains v if { v := ["a", "b"][_] } + p.q.r if { true } `), err: "rego_type_error: rule data.pkg.p conflicts with [data.pkg.p.q.r]", }, @@ -2192,8 +2210,8 @@ func TestCompilerCheckRuleConflictsDotsInRuleHeads(t *testing.T) { modules: modules( `package pkg import future.keywords - p.q contains v { v := ["a", "b"][_] } - p.q.r { true } + p.q contains v if { v := ["a", "b"][_] } + p.q.r if { true } `), err: "rule data.pkg.p.q conflicts with [data.pkg.p.q.r]", }, @@ -2202,8 +2220,8 @@ func TestCompilerCheckRuleConflictsDotsInRuleHeads(t *testing.T) { modules: modules( `package pkg import future.keywords - p[q] contains v { v := ["a", "b"][_] } - p.q.r { true } + p[q] contains v if { v := ["a", "b"][_] } + p.q.r if { true } `), }, { @@ -2211,7 +2229,7 @@ func TestCompilerCheckRuleConflictsDotsInRuleHeads(t *testing.T) { modules: modules( `package pkg p(x) := x - p.q.r { true } + p.q.r if { true } `), err: "rego_type_error: rule data.pkg.p conflicts with [data.pkg.p.q.r]", }, @@ -2220,7 +2238,7 @@ func TestCompilerCheckRuleConflictsDotsInRuleHeads(t *testing.T) { modules: modules( `package pkg p(x) := x - p.q.r { true } + p.q.r if { true } `), err: "rego_type_error: rule data.pkg.p conflicts with [data.pkg.p.q.r]", }, @@ -2229,7 +2247,7 @@ func TestCompilerCheckRuleConflictsDotsInRuleHeads(t *testing.T) { modules: modules( `package pkg p.q(x) := x - p.q.r { true } + p.q.r if { true } `), err: "rego_type_error: rule data.pkg.p.q conflicts with [data.pkg.p.q.r]", }, @@ -2262,7 +2280,7 @@ func TestCompilerCheckRulePkgConflicts(t *testing.T) { note: "Package can be declared within dynamic extent of rule (#6387 regression test)", modules: modules( `package test - p[x] := y { x := "a"; y := "b" }`, + p[x] := y if { x := "a"; y := "b" }`, `package test.p q := 1`), }, @@ -2270,7 +2288,7 @@ func TestCompilerCheckRulePkgConflicts(t *testing.T) { note: "Package can be declared deep within dynamic extent of rule (#6387 regression test)", modules: modules( `package test - p[x] := y { x := "a"; y := "b" }`, + p[x] := y if { x := "a"; y := "b" }`, `package test.p.q.r.s t := 1`), }, @@ -2278,7 +2296,7 @@ func TestCompilerCheckRulePkgConflicts(t *testing.T) { note: "Package cannot be declared within extent of single-value rule (ground ref)", modules: modules( `package test - p := x { x := "a" }`, + p := x if { x := "a" }`, `package test.p q := 1`), err: []string{ @@ -2290,7 +2308,7 @@ func TestCompilerCheckRulePkgConflicts(t *testing.T) { note: "Package cannot be declared within extent of multi-value rule", modules: modules( `package test - p[x] { x := "a" }`, + p contains x if { x := "a" }`, `package test.p q := 1`), err: []string{ @@ -2322,38 +2340,39 @@ func TestCompilerCheckUndefinedFuncs(t *testing.T) { module := ` package test + import rego.v1 - undefined_function { + undefined_function if { data.deadbeef(x) } - undefined_global { + undefined_global if { deadbeef(x) } # NOTE: all the dynamic dispatch examples here are not supported, # we're checking assertions about the error returned. - undefined_dynamic_dispatch { + undefined_dynamic_dispatch if { x = "f"; data.test2[x](1) } - undefined_dynamic_dispatch_declared_var { + undefined_dynamic_dispatch_declared_var if { y := "f"; data.test2[y](1) } - undefined_dynamic_dispatch_declared_var_in_array { + undefined_dynamic_dispatch_declared_var_in_array if { z := "f"; data.test2[[z]](1) } - arity_mismatch_1 { + arity_mismatch_1 if { data.test2.f(1,2,3) } - arity_mismatch_2 { + arity_mismatch_2 if { data.test2.f() } - arity_mismatch_3 { + arity_mismatch_3 if { x:= data.test2.f() } ` @@ -2380,8 +2399,8 @@ func TestCompilerCheckUndefinedFuncs(t *testing.T) { "rego_type_error: undefined function data.test2[y]", "rego_type_error: undefined function data.test2[[z]]", "rego_type_error: function data.test2.f has arity 1, got 3 arguments", - "test.rego:31: rego_type_error: function data.test2.f has arity 1, got 0 arguments", - "test.rego:35: rego_type_error: function data.test2.f has arity 1, got 0 arguments", + "test.rego:32: rego_type_error: function data.test2.f has arity 1, got 0 arguments", + "test.rego:36: rego_type_error: function data.test2.f has arity 1, got 0 arguments", } for _, w := range want { if !strings.Contains(result, w) { @@ -2749,7 +2768,11 @@ func TestCompilerRewriteExprTerms(t *testing.T) { for _, tc := range cases { t.Run(tc.note, func(t *testing.T) { compiler := NewCompiler() - opts := ParserOptions{AllFutureKeywords: true, unreleasedKeywords: true} + opts := ParserOptions{ + RegoVersion: RegoV0, + AllFutureKeywords: true, + unreleasedKeywords: true, + } compiler.Modules = map[string]*Module{ "test": MustParseModuleWithOpts(tc.module, opts), @@ -2856,7 +2879,11 @@ p := [data() | data := 1]`, for _, tc := range cases { t.Run(tc.note, func(t *testing.T) { compiler := NewCompiler() - opts := ParserOptions{AllFutureKeywords: true, unreleasedKeywords: true} + opts := ParserOptions{ + RegoVersion: RegoV0, + AllFutureKeywords: true, + unreleasedKeywords: true, + } compiler.Modules = map[string]*Module{ "test": MustParseModuleWithOpts(tc.module, opts), @@ -3422,7 +3449,7 @@ func runStrictnessTestCase(t *testing.T, cases []strictnessTestCase, assertLocat return func(t *testing.T) { compiler := NewCompiler().WithStrict(strict) compiler.Modules = map[string]*Module{ - "test": MustParseModule(tc.module), + "test": MustParseModuleWithOpts(tc.module, ParserOptions{RegoVersion: RegoV0}), } compileStages(compiler, nil) @@ -3998,7 +4025,7 @@ func TestCompileRegoV1Import(t *testing.T) { compiler := NewCompiler() compiler.Modules = map[string]*Module{} for name, mod := range tc.modules { - if parsed, err := ParseModuleWithOpts(name, mod, ParserOptions{}); err != nil { + if parsed, err := ParseModuleWithOpts(name, mod, ParserOptions{RegoVersion: RegoV0}); err != nil { t.Fatal(err) } else { compiler.Modules[name] = parsed @@ -4025,7 +4052,7 @@ a.b.c = 1 q if a.b.c == 1 `, exp: `package test -a.b.c = 1 { true } +a.b.c = 1 if { true } q if data.test.a.b.c = 1 `, }, @@ -4051,7 +4078,7 @@ p := count([x | q[x]]) q[1] = 1 `, exp: `package test -p := __local0__ { true; __local1__ = [x | data.test.q[x]]; count(__local1__, __local0__) } +p := __local0__ if { true; __local1__ = [x | data.test.q[x]]; count(__local1__, __local0__) } q[1] = 1 `, }, @@ -4367,8 +4394,9 @@ func TestCompilerResolveErrors(t *testing.T) { c.Modules = map[string]*Module{ "shadow-globals": MustParseModule(` package shadow_globals + import rego.v1 - f([input]) { true } + f([input]) if { true } `), } @@ -4391,29 +4419,29 @@ func TestCompilerRewriteTermsInHead(t *testing.T) { }{ { note: "imports", - mod: MustParseModule(`package head + mod: module(`package head import data.doc1 as bar import data.doc2 as corge import input.x.y.foo import input.qux as baz -p[foo[bar[i]]] = {"baz": baz, "corge": corge} { true } +p[foo[bar[i]]] = {"baz": baz, "corge": corge} if { true } `), exp: MustParseRule(`p[__local0__] = __local1__ { true; __local0__ = input.x.y.foo[data.doc1[i]]; __local1__ = {"baz": input.qux, "corge": data.doc2} }`), }, { note: "array comprehension value", - mod: MustParseModule(`package head -q = [true | true] { true } + mod: module(`package head +q = [true | true] if { true } `), exp: MustParseRule(`q = __local0__ { true; __local0__ = [true | true] }`), }, { note: "array comprehension value in else head", - mod: MustParseModule(`package head -q { + mod: module(`package head +q if { false -} else = [true | true] { +} else = [true | true] if { true } `), @@ -4421,10 +4449,10 @@ q { }, { note: "array comprehension value in head (comprehension-local var)", - mod: MustParseModule(`package head -q = [a | a := true] { + mod: module(`package head +q = [a | a := true] if { false -} else = [a | a := true] { +} else = [a | a := true] if { true } `), @@ -4432,10 +4460,10 @@ q = [a | a := true] { }, { note: "array comprehension value in function head (comprehension-local var)", - mod: MustParseModule(`package head -f(x) = [a | a := true] { + mod: module(`package head +f(x) = [a | a := true] if { false -} else = [a | a := true] { +} else = [a | a := true] if { true } `), @@ -4443,10 +4471,10 @@ f(x) = [a | a := true] { }, { note: "array comprehension value in else-func head (reused arg rewrite)", - mod: MustParseModule(`package head -f(x, y) = [x | y] { + mod: module(`package head +f(x, y) = [x | y] if { false -} else = [x | y] { +} else = [x | y] if { true } `), @@ -4454,17 +4482,17 @@ f(x, y) = [x | y] { }, { note: "object comprehension value", - mod: MustParseModule(`package head -r = {"true": true | true} { true } + mod: module(`package head +r = {"true": true | true} if { true } `), exp: MustParseRule(`r = __local0__ { true; __local0__ = {"true": true | true} }`), }, { note: "object comprehension value in else head", - mod: MustParseModule(`package head -q { + mod: module(`package head +q if { false -} else = {"true": true | true} { +} else = {"true": true | true} if { true } `), @@ -4472,10 +4500,10 @@ q { }, { note: "object comprehension value in head (comprehension-local var)", - mod: MustParseModule(`package head -q = {"a": a | a := true} { + mod: module(`package head +q = {"a": a | a := true} if { false -} else = {"a": a | a := true} { +} else = {"a": a | a := true} if { true } `), @@ -4483,10 +4511,10 @@ q = {"a": a | a := true} { }, { note: "object comprehension value in function head (comprehension-local var)", - mod: MustParseModule(`package head -f(x) = {"a": a | a := true} { + mod: module(`package head +f(x) = {"a": a | a := true} if { false -} else = {"a": a | a := true} { +} else = {"a": a | a := true} if { true } `), @@ -4494,10 +4522,10 @@ f(x) = {"a": a | a := true} { }, { note: "object comprehension value in else-func head (reused arg rewrite)", - mod: MustParseModule(`package head -f(x, y) = {x: y | true} { + mod: module(`package head +f(x, y) = {x: y | true} if { false -} else = {x: y | true} { +} else = {x: y | true} if { true } `), @@ -4505,17 +4533,17 @@ f(x, y) = {x: y | true} { }, { note: "set comprehension value", - mod: MustParseModule(`package head -s = {true | true} { true } + mod: module(`package head +s = {true | true} if { true } `), exp: MustParseRule(`s = __local0__ { true; __local0__ = {true | true} }`), }, { note: "set comprehension value in else head", - mod: MustParseModule(`package head -q = {false | false} { + mod: module(`package head +q = {false | false} if { false -} else = {true | true} { +} else = {true | true} if { true } `), @@ -4523,10 +4551,10 @@ q = {false | false} { }, { note: "set comprehension value in head (comprehension-local var)", - mod: MustParseModule(`package head -q = {a | a := true} { + mod: module(`package head +q = {a | a := true} if { false -} else = {a | a := true} { +} else = {a | a := true} if { true } `), @@ -4534,10 +4562,10 @@ q = {a | a := true} { }, { note: "set comprehension value in function head (comprehension-local var)", - mod: MustParseModule(`package head -f(x) = {a | a := true} { + mod: module(`package head +f(x) = {a | a := true} if { false -} else = {a | a := true} { +} else = {a | a := true} if { true } `), @@ -4545,10 +4573,10 @@ f(x) = {a | a := true} { }, { note: "set comprehension value in else-func head (reused arg rewrite)", - mod: MustParseModule(`package head -f(x, y) = {x | y} { + mod: module(`package head +f(x, y) = {x | y} if { false -} else = {x | y} { +} else = {x | y} if { true } `), @@ -4556,11 +4584,11 @@ f(x, y) = {x | y} { }, { note: "import in else value", - mod: MustParseModule(`package head + mod: module(`package head import input.qux as baz -elsekw { +elsekw if { false -} else = baz { +} else = baz if { true } `), @@ -4568,7 +4596,7 @@ elsekw { }, { note: "import ref in last ref head term", - mod: MustParseModule(`package head + mod: module(`package head import data.doc1 as bar x.y.z[bar[i]] = true `), @@ -4576,9 +4604,7 @@ x.y.z[bar[i]] = true }, { note: "import ref in multi-value ref rule", - mod: MustParseModule(`package head -import future.keywords.if -import future.keywords.contains + mod: module(`package head import data.doc1 as bar x.y.w contains bar[i] if true `), @@ -4695,13 +4721,13 @@ func TestCompilerRewriteRegoMetadataCalls(t *testing.T) { note: "rego.metadata called, no metadata", module: `package test -p { +p if { rego.metadata.chain()[0].path == ["test", "p"] rego.metadata.rule() == {} }`, exp: `package test -p = true { +p = true if { __local2__ = [{"path": ["test", "p"]}] __local3__ = {} __local0__ = __local2__ @@ -4714,13 +4740,13 @@ p = true { note: "rego.metadata called, no output var, no metadata", module: `package test -p { +p if { rego.metadata.chain() rego.metadata.rule() }`, exp: `package test -p = true { +p = true if { __local0__ = [{"path": ["test", "p"]}] __local1__ = {} __local0__ @@ -4735,14 +4761,14 @@ package test # METADATA # title: My P Rule -p { +p if { rego.metadata.chain()[0].title == "My P Rule" rego.metadata.chain()[1].description == "A test package" } # METADATA # title: My Other P Rule -p { +p if { rego.metadata.rule().title == "My Other P Rule" }`, exp: `# METADATA @@ -4751,7 +4777,7 @@ package test # METADATA # {"scope":"rule","title":"My P Rule"} -p = true { +p = true if { __local3__ = [ {"annotations": {"scope": "rule", "title": "My P Rule"}, "path": ["test", "p"]}, {"annotations": {"description": "A test package", "scope": "package"}, "path": ["test"]} @@ -4764,7 +4790,7 @@ p = true { # METADATA # {"scope":"rule","title":"My Other P Rule"} -p = true { +p = true if { __local4__ = {"scope": "rule", "title": "My Other P Rule"} __local2__ = __local4__ equal(__local2__.title, "My Other P Rule") @@ -4776,7 +4802,7 @@ p = true { # description: TEST package test -p { +p if { rego.metadata.chain()[0].path == ["test", "p"] rego.metadata.chain()[1].path == ["test"] }`, @@ -4784,7 +4810,7 @@ p { # {"scope":"package","description":"TEST"} package test -p = true { +p = true if { __local2__ = [ {"path": ["test", "p"]}, {"annotations": {"description": "TEST", "scope": "package"}, "path": ["test"]} @@ -4801,7 +4827,7 @@ p = true { p := rego.metadata.chain()`, exp: `package test -p := __local0__ { +p := __local0__ if { __local1__ = [{"path": ["test", "p"]}] true __local0__ = __local1__ @@ -4811,22 +4837,22 @@ p := __local0__ { note: "rego.metadata argument in function call", module: `package test -p { +p if { q(rego.metadata.chain()) } -q(s) { +q(s) if { s == ["test", "p"] }`, exp: `package test -p = true { +p = true if { __local2__ = [{"path": ["test", "p"]}] __local1__ = __local2__ data.test.q(__local1__) } -q(__local0__) = true { +q(__local0__) = true if { equal(__local0__, ["test", "p"]) }`, }, @@ -4837,7 +4863,7 @@ q(__local0__) = true { p = [x | x := rego.metadata.chain()]`, exp: `package test -p = [__local0__ | __local1__ = __local2__; __local0__ = __local1__] { +p = [__local0__ | __local1__ = __local2__; __local0__ = __local1__] if { __local2__ = [{"path": ["test", "p"]}] true }`, @@ -4846,13 +4872,13 @@ p = [__local0__ | __local1__ = __local2__; __local0__ = __local1__] { note: "rego.metadata used in nested array comprehension", module: `package test -p { +p if { y := [x | x := rego.metadata.chain()] y[0].path == ["test", "p"] }`, exp: `package test -p = true { +p = true if { __local3__ = [{"path": ["test", "p"]}]; __local1__ = [__local0__ | __local2__ = __local3__; __local0__ = __local2__]; equal(__local1__[0].path, ["test", "p"]) @@ -4865,7 +4891,7 @@ p = true { p = {x | x := rego.metadata.chain()}`, exp: `package test -p = {__local0__ | __local1__ = __local2__; __local0__ = __local1__} { +p = {__local0__ | __local1__ = __local2__; __local0__ = __local1__} if { __local2__ = [{"path": ["test", "p"]}] true }`, @@ -4874,13 +4900,13 @@ p = {__local0__ | __local1__ = __local2__; __local0__ = __local1__} { note: "rego.metadata used in nested set comprehension", module: `package test -p { +p if { y := {x | x := rego.metadata.chain()} y[0].path == ["test", "p"] }`, exp: `package test -p = true { +p = true if { __local3__ = [{"path": ["test", "p"]}] __local1__ = {__local0__ | __local2__ = __local3__; __local0__ = __local2__} equal(__local1__[0].path, ["test", "p"]) @@ -4893,7 +4919,7 @@ p = true { p = {i: x | x := rego.metadata.chain()[i]}`, exp: `package test -p = {i: __local0__ | __local1__ = __local2__; __local0__ = __local1__[i]} { +p = {i: __local0__ | __local1__ = __local2__; __local0__ = __local1__[i]} if { __local2__ = [{"path": ["test", "p"]}] true }`, @@ -4902,13 +4928,13 @@ p = {i: __local0__ | __local1__ = __local2__; __local0__ = __local1__[i]} { note: "rego.metadata used in nested object comprehension", module: `package test -p { +p if { y := {i: x | x := rego.metadata.chain()[i]} y[0].path == ["test", "p"] }`, exp: `package test -p = true { +p = true if { __local3__ = [{"path": ["test", "p"]}] __local1__ = {i: __local0__ | __local2__ = __local3__; __local0__ = __local2__[i]} equal(__local1__[0].path, ["test", "p"]) @@ -4920,13 +4946,17 @@ p = true { t.Run(tc.note, func(t *testing.T) { c := NewCompiler() c.Modules = map[string]*Module{ - "test.rego": MustParseModule(tc.module), + "test.rego": module(tc.module), } compileStages(c, c.rewriteRegoMetadataCalls) assertNotFailed(t, c) result := c.Modules["test.rego"] - exp := MustParseModuleWithOpts(tc.exp, ParserOptions{ProcessAnnotation: true}) + exp := MustParseModuleWithOpts(tc.exp, ParserOptions{ + AllFutureKeywords: true, + unreleasedKeywords: true, + ProcessAnnotation: true, + }) if result.Compare(exp) != 0 { t.Fatalf("\nExpected:\n\n%v\n\nGot:\n\n%v", exp, result) @@ -4959,15 +4989,16 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { module string exp interface{} expRewrittenMap map[Var]Var + regoVersion RegoVersion }{ { module: ` package test - body { a := 1; a > 0 } + body if { a := 1; a > 0 } `, exp: ` package test - body = true { __local0__ = 1; gt(__local0__, 0) } + body = true if { __local0__ = 1; gt(__local0__, 0) } `, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("a"), @@ -4976,11 +5007,11 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { { module: ` package test - head_vars(a) = b { b := a } + head_vars(a) = b if { b := a } `, exp: ` package test - head_vars(__local0__) = __local1__ { __local1__ = __local0__ } + head_vars(__local0__) = __local1__ if { __local1__ = __local0__ } `, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("a"), @@ -4990,11 +5021,11 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { { module: ` package test - head_key[a] { a := 1 } + head_key contains a if { a := 1 } `, exp: ` package test - head_key[__local0__] { __local0__ = 1 } + head_key contains __local0__ if { __local0__ = 1 } `, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("a"), @@ -5003,11 +5034,11 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { { module: ` package test - head_unsafe_var[a] { some a } + head_unsafe_var contains a if { some a } `, exp: ` package test - head_unsafe_var[__local0__] { true } + head_unsafe_var contains __local0__ if { true } `, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("a"), @@ -5018,14 +5049,14 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { package test p = {1,2,3} x = 4 - head_nested[p[x]] { + head_nested contains p[x] if { some x }`, exp: ` package test p = {1,2,3} x = 4 - head_nested[data.test.p[__local0__]] + head_nested contains data.test.p[__local0__] `, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("x"), @@ -5035,14 +5066,14 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { module: ` package test p = {1,2} - head_closure_nested[p[x]] { + head_closure_nested contains p[x] if { y = [true | some x; x = 1] } `, exp: ` package test p = {1,2} - head_closure_nested[data.test.p[x]] { + head_closure_nested contains data.test.p[x] if { y = [true | __local0__ = 1] } `, @@ -5053,14 +5084,14 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { { module: ` package test - nested { + nested if { a := [1,2,3] x := [true | a[i] > 1] } `, exp: ` package test - nested = true { __local0__ = [1, 2, 3]; __local1__ = [true | gt(__local0__[i], 1)] } + nested = true if { __local0__ = [1, 2, 3]; __local1__ = [true | gt(__local0__[i], 1)] } `, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("a"), @@ -5071,12 +5102,12 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { module: ` package test x = 2 - shadow_globals[x] { x := 1 } + shadow_globals contains x if { x := 1 } `, exp: ` package test - x = 2 { true } - shadow_globals[__local0__] { __local0__ = 1 } + x = 2 if { true } + shadow_globals contains __local0__ if { __local0__ = 1 } `, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("x"), @@ -5085,11 +5116,11 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { { module: ` package test - shadow_rule[shadow_rule] { shadow_rule := 1 } + shadow_rule contains shadow_rule if { shadow_rule := 1 } `, exp: ` package test - shadow_rule[__local0__] { __local0__ = 1 } + shadow_rule contains __local0__ if { __local0__ = 1 } `, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("shadow_rule"), @@ -5108,6 +5139,7 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { Var("__local0__"): Var("data"), Var("__local1__"): Var("input"), }, + regoVersion: RegoV0, // shadowing only allowed in v0 }, { module: ` @@ -5121,6 +5153,7 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("input"), }, + regoVersion: RegoV0, // shadowing only allowed in v0 }, { module: ` @@ -5140,11 +5173,12 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { Var("__local0__"): Var("a"), Var("__local1__"): Var("input"), }, + regoVersion: RegoV0, // shadowing only allowed in v0 }, { module: ` package test - shadow_comprehensions { + shadow_comprehensions if { a := 1 [true | a := 2; b := 1] b := 2 @@ -5152,7 +5186,7 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { `, exp: ` package test - shadow_comprehensions = true { __local0__ = 1; [true | __local1__ = 2; __local2__ = 1]; __local3__ = 2 } + shadow_comprehensions = true if { __local0__ = 1; [true | __local1__ = 2; __local2__ = 1]; __local3__ = 2 } `, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("a"), @@ -5164,14 +5198,14 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { { module: ` package test - scoping { + scoping if { [true | a := 1] [true | a := 2] } `, exp: ` package test - scoping = true { [true | __local0__ = 1]; [true | __local1__ = 2] } + scoping = true if { [true | __local0__ = 1]; [true | __local1__ = 2] } `, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("a"), @@ -5181,13 +5215,13 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { { module: ` package test - object_keys { + object_keys if { {k: v1, "k2": v2} := {"foo": 1, "k2": 2} } `, exp: ` package test - object_keys = true { {"k2": __local0__, k: __local1__} = {"foo": 1, "k2": 2} } + object_keys = true if { {"k2": __local0__, k: __local1__} = {"foo": 1, "k2": 2} } `, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("v2"), @@ -5198,14 +5232,14 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { module: ` package test head_array_comprehensions = [[x] | x := 1] - head_set_comprehensions = {[x] | x := 1} - head_object_comprehensions = {k: [x] | k := "foo"; x := 1} + head_set_comprehensions = {[x] | x := 1} + head_object_comprehensions = {k: [x] | k := "foo"; x := 1} `, exp: ` package test - head_array_comprehensions = [[__local0__] | __local0__ = 1] { true } - head_set_comprehensions = {[__local1__] | __local1__ = 1} { true } - head_object_comprehensions = {__local2__: [__local3__] | __local2__ = "foo"; __local3__ = 1} { true } + head_array_comprehensions = [[__local0__] | __local0__ = 1] if { true } + head_set_comprehensions = {[__local1__] | __local1__ = 1} if { true } + head_object_comprehensions = {__local2__: [__local3__] | __local2__ = "foo"; __local3__ = 1} if { true } `, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("x"), @@ -5217,14 +5251,14 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { { module: ` package test - rewritten_object_key { + rewritten_object_key if { k := "foo" {k: 1} } `, exp: ` package test - rewritten_object_key = true { __local0__ = "foo"; {__local0__: 1} } + rewritten_object_key = true if { __local0__ = "foo"; {__local0__: 1} } `, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("k"), @@ -5233,13 +5267,13 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { { module: ` package test - rewritten_object_key_head[[{k: 1}]] { + rewritten_object_key_head contains [{k: 1}] if { k := "foo" } `, exp: ` package test - rewritten_object_key_head[[{__local0__: 1}]] { __local0__ = "foo" } + rewritten_object_key_head contains [{__local0__: 1}] if { __local0__ = "foo" } `, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("k"), @@ -5248,13 +5282,13 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { { module: ` package test - rewritten_object_key_head_value = [{k: 1}] { + rewritten_object_key_head_value = [{k: 1}] if { k := "foo" } `, exp: ` package test - rewritten_object_key_head_value = [{__local0__: 1}] { __local0__ = "foo" } + rewritten_object_key_head_value = [{__local0__: 1}] if { __local0__ = "foo" } `, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("k"), @@ -5276,18 +5310,19 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { Var("__local0__"): Var("input"), Var("__local1__"): Var("a"), }, + regoVersion: RegoV0, // shadowing only allowed in v0 }, { module: ` package test - rewrite_with_value_in_assignment { + rewrite_with_value_in_assignment if { a := 1 b := 1 with input as [a] } `, exp: ` package test - rewrite_with_value_in_assignment = true { __local0__ = 1; __local1__ = 1 with input as [__local0__] } + rewrite_with_value_in_assignment = true if { __local0__ = 1; __local1__ = 1 with input as [__local0__] } `, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("a"), @@ -5297,14 +5332,14 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { { module: ` package test - rewrite_with_value_in_expr { + rewrite_with_value_in_expr if { a := 1 a > 0 with input as [a] } `, exp: ` package test - rewrite_with_value_in_expr = true { __local0__ = 1; gt(__local0__, 0) with input as [__local0__] } + rewrite_with_value_in_expr = true if { __local0__ = 1; gt(__local0__, 0) with input as [__local0__] } `, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("a"), @@ -5313,14 +5348,14 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { { module: ` package test - rewrite_nested_with_value_in_expr { + rewrite_nested_with_value_in_expr if { a := 1 a > 0 with input as object.union({"a": a}, {"max_a": max([a])}) } `, exp: ` package test - rewrite_nested_with_value_in_expr = true { __local0__ = 1; gt(__local0__, 0) with input as object.union({"a": __local0__}, {"max_a": max([__local0__])}) } + rewrite_nested_with_value_in_expr = true if { __local0__ = 1; gt(__local0__, 0) with input as object.union({"a": __local0__}, {"max_a": max([__local0__])}) } `, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("a"), @@ -5330,15 +5365,15 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { module: ` package test global = {} - ref_shadowed { + ref_shadowed if { global := {"a": 1} global.a > 0 } `, exp: ` package test - global = {} { true } - ref_shadowed = true { __local0__ = {"a": 1}; gt(__local0__.a, 0) } + global = {} if { true } + ref_shadowed = true if { __local0__ = {"a": 1}; gt(__local0__.a, 0) } `, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("global"), @@ -5347,10 +5382,10 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { { module: ` package test - f(x) = y { + f(x) = y if { x == 1 y := 2 - } else = y { + } else = y if { x == 3 y := 4 } @@ -5359,10 +5394,10 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { // args will be rewritten. Since we cannot currently redefine the // args, we must parse the module and then manually update the args. exp: func() *Module { - module := MustParseModule(` + module := module(` package test - f(__local0__) = __local1__ { __local0__ == 1; __local1__ = 2 } else = __local2__ { __local0__ == 3; __local2__ = 4 } + f(__local0__) = __local1__ if { __local0__ == 1; __local1__ = 2 } else = __local2__ if { __local0__ == 3; __local2__ = 4 } `) module.Rules[0].Else.Head.Args[0].Value = Var("__local0__") return module @@ -5376,11 +5411,11 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { { module: ` package test - f({"x": [x]}) = y { x == 1; y := 2 }`, + f({"x": [x]}) = y if { x == 1; y := 2 }`, exp: ` package test - f({"x": [__local0__]}) = __local1__ { __local0__ == 1; __local1__ = 2 }`, + f({"x": [__local0__]}) = __local1__ if { __local0__ == 1; __local1__ = 2 }`, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("x"), Var("__local1__"): Var("y"), @@ -5390,12 +5425,12 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { module: ` package test - f(x, [x]) = x { x == 1 } + f(x, [x]) = x if { x == 1 } `, exp: ` package test - f(__local0__, [__local0__]) = __local0__ { __local0__ == 1 } + f(__local0__, [__local0__]) = __local0__ if { __local0__ == 1 } `, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("x"), @@ -5405,12 +5440,12 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { module: ` package test - f(x) = {x[0]: 1} { true } + f(x) = {x[0]: 1} if { true } `, exp: ` package test - f(__local0__) = {__local0__[0]: 1} { true } + f(__local0__) = {__local0__[0]: 1} if { true } `, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("x"), @@ -5420,14 +5455,14 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { module: ` package test - f({{t | t := 0}: 1}) { + f({{t | t := 0}: 1}) if { true } `, exp: ` package test - f({{__local0__ | __local0__ = 0}: 1}) { true } + f({{__local0__ | __local0__ = 0}: 1}) if { true } `, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("t"), @@ -5437,14 +5472,14 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { module: ` package test - f({{t | t := 0}}) { + f({{t | t := 0}}) if { true } `, exp: ` package test - f({{__local0__ | __local0__ = 0}}) { true } + f({{__local0__ | __local0__ = 0}}) if { true } `, expRewrittenMap: map[Var]Var{ Var("__local0__"): Var("t"), @@ -5454,9 +5489,14 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { for i, tc := range tests { t.Run(fmt.Sprint(i), func(t *testing.T) { + setRegoVersion := func(po ParserOptions) ParserOptions { + po.RegoVersion = tc.regoVersion + return po + } + c := NewCompiler() c.Modules = map[string]*Module{ - "test.rego": MustParseModule(tc.module), + "test.rego": module(tc.module, setRegoVersion), } compileStages(c, c.rewriteLocalVars) assertNotFailed(t, c) @@ -5464,7 +5504,7 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { var exp *Module switch e := tc.exp.(type) { case string: - exp = MustParseModule(e) + exp = module(e, setRegoVersion) case func() *Module: exp = e() default: @@ -5480,27 +5520,28 @@ func TestCompilerRewriteLocalAssignments(t *testing.T) { } } + func TestRewriteLocalVarDeclarationErrors(t *testing.T) { c := NewCompiler() - c.Modules["test"] = MustParseModule(`package test + c.Modules["test"] = module(`package test - redeclaration { + redeclaration if { r1 = 1 r1 := 2 r2 := 1 [b, r2] := [1, 2] - input.path == 1 - input := "foo" + foo.path == 1 + foo := "foo" _ := [1 | nested := 1; nested := 2] } - negation { + negation if { not a := 1 } - bad_assign { + bad_assign if { null := x true := x 4.5 := x @@ -5513,11 +5554,11 @@ func TestRewriteLocalVarDeclarationErrors(t *testing.T) { [z, 1] := [1, 2] } - arg_redeclared(arg1) { + arg_redeclared(arg1) if { arg1 := 1 } - arg_nested_redeclared({{arg_nested| arg_nested := 1; arg_nested := 2}}) { true } + arg_nested_redeclared({{arg_nested| arg_nested := 1; arg_nested := 2}}) if { true } `) compileStages(c, c.rewriteLocalVars) @@ -5525,7 +5566,7 @@ func TestRewriteLocalVarDeclarationErrors(t *testing.T) { expectedErrors := []string{ "var r1 referenced above", "var r2 assigned above", - "var input referenced above", + "var foo referenced above", "var nested assigned above", "arg arg1 redeclared", "var arg_nested assigned above", @@ -5579,7 +5620,7 @@ func TestRewriteDeclaredVarsStage(t *testing.T) { module: ` package test - p { + p if { a := {"a": "a"} {a.a: a.a} } @@ -5587,7 +5628,7 @@ func TestRewriteDeclaredVarsStage(t *testing.T) { exp: ` package test - p { + p if { __local0__ = {"a": "a"} {__local0__.a: __local0__.a} } @@ -5598,7 +5639,7 @@ func TestRewriteDeclaredVarsStage(t *testing.T) { module: ` package test - p { + p if { a := {"a": "a"} {a.a} } @@ -5606,7 +5647,7 @@ func TestRewriteDeclaredVarsStage(t *testing.T) { exp: ` package test - p { + p if { __local0__ = {"a": "a"} {__local0__.a} } @@ -5620,12 +5661,12 @@ func TestRewriteDeclaredVarsStage(t *testing.T) { c := NewCompiler() c.Modules = map[string]*Module{ - "test.rego": MustParseModule(tc.module), + "test.rego": module(tc.module), } compileStages(c, c.rewriteLocalVars) - exp := MustParseModule(tc.exp) + exp := module(tc.exp) result := c.Modules["test.rego"] if !exp.Equal(result) { @@ -5648,13 +5689,13 @@ func TestRewriteDeclaredVars(t *testing.T) { package test x = 1 y = 2 - p { some x; input = [x, y] } + p if { some x; input = [x, y] } `, exp: ` package test x = 1 y = 2 - p { __local1__ = data.test.y; input = [__local0__, __local1__] } + p if { __local1__ = data.test.y; input = [__local0__, __local1__] } `, }, { @@ -5663,13 +5704,13 @@ func TestRewriteDeclaredVars(t *testing.T) { package test x = [] y = {} - p { some x; walk(y, [x, y]) } + p if { some x; walk(y, [x, y]) } `, exp: ` package test x = [] y = {} - p { __local1__ = data.test.y; __local2__ = data.test.y; walk(__local1__, [__local0__, __local2__]) } + p if { __local1__ = data.test.y; __local2__ = data.test.y; walk(__local1__, [__local0__, __local2__]) } `, }, { @@ -5678,29 +5719,29 @@ func TestRewriteDeclaredVars(t *testing.T) { package test x = "a" y = 1 - q[[2, "b"]] - p { some x; q[[y,x]] } + q contains [2, "b"] + p if { some x; q[[y,x]] } `, exp: ` package test x = "a" y = 1 - q[[2, "b"]] - p { __local1__ = data.test.y; data.test.q[[__local1__, __local0__]] } + q contains [2, "b"] + p if { __local1__ = data.test.y; data.test.q[[__local1__, __local0__]] } `, }, { note: "with: rewrite target", module: ` package test - p { + p if { x := "foo" true with input[x] as 1 } `, exp: ` package test - p { + p if { __local0__ = "foo"; true with input[__local0__] as 1 } @@ -5710,14 +5751,14 @@ func TestRewriteDeclaredVars(t *testing.T) { note: "with: rewrite target in comprehension term", module: ` package test - p { - input := "bar" - { { 2 | true with input[input] as 1} | true } + p if { + foo := "bar" + { { 2 | true with input[foo] as 1} | true } } `, exp: ` package test - p { + p if { __local0__ = "bar" {__local1__ | true; __local1__ = { 2 | true with input[__local0__] as 1 }} } @@ -5728,7 +5769,7 @@ func TestRewriteDeclaredVars(t *testing.T) { module: ` package test - p.r.q[s] = t { + p.r.q[s] = t if { t := 1 s := input.foo } @@ -5736,7 +5777,7 @@ func TestRewriteDeclaredVars(t *testing.T) { exp: ` package test - p.r.q[__local1__] = __local0__ { + p.r.q[__local1__] = __local0__ if { __local0__ = 1 __local1__ = input.foo } @@ -5748,12 +5789,12 @@ func TestRewriteDeclaredVars(t *testing.T) { package test import future.keywords.in xs = ["a", "b", "c"] - p { some x in xs; x == "a" } + p if { some x in xs; x == "a" } `, exp: ` package test xs = ["a", "b", "c"] - p { __local2__ = data.test.xs[__local1__]; __local2__ = "a" } + p if { __local2__ = data.test.xs[__local1__]; __local2__ = "a" } `, }, { @@ -5762,12 +5803,12 @@ func TestRewriteDeclaredVars(t *testing.T) { package test import future.keywords.in xs = ["a", "b", "c"] - p { some k, x in xs; x == "a"; k == 2 } + p if { some k, x in xs; x == "a"; k == 2 } `, exp: ` package test xs = ["a", "b", "c"] - p { __local1__ = data.test.xs[__local0__]; __local1__ = "a"; __local0__ = 2 } + p if { __local1__ = data.test.xs[__local0__]; __local1__ = "a"; __local0__ = 2 } `, }, { @@ -5776,7 +5817,7 @@ func TestRewriteDeclaredVars(t *testing.T) { package test import future.keywords.in xs = [["a", "b", "c"], []] - p { + p if { some i some k, x in xs[i] x == "a" @@ -5786,7 +5827,7 @@ func TestRewriteDeclaredVars(t *testing.T) { exp: ` package test xs = [["a", "b", "c"], []] - p = true { __local2__ = data.test.xs[__local0__][__local1__]; __local2__ = "a"; __local1__ = 2 } + p = true if { __local2__ = data.test.xs[__local0__][__local1__]; __local2__ = "a"; __local1__ = 2 } `, }, { @@ -5796,7 +5837,7 @@ func TestRewriteDeclaredVars(t *testing.T) { import future.keywords.in i = 0 xs = [["a", "b", "c"], []] - p { + p if { some k, x in xs[i] x == "a" k == 2 @@ -5806,14 +5847,14 @@ func TestRewriteDeclaredVars(t *testing.T) { package test i = 0 xs = [["a", "b", "c"], []] - p = true { __local2__ = data.test.i; __local1__ = data.test.xs[__local2__][__local0__]; __local1__ = "a"; __local0__ = 2 } + p = true if { __local2__ = data.test.i; __local1__ = data.test.xs[__local2__][__local0__]; __local1__ = "a"; __local0__ = 2 } `, }, { note: "rewrite some: with modifier on domain", module: ` package test - p { + p if { some k, x in input with input as [1, 1, 1] k == 0 x == 1 @@ -5821,7 +5862,7 @@ func TestRewriteDeclaredVars(t *testing.T) { `, exp: ` package test - p { + p if { __local1__ = input[__local0__] with input as [1, 1, 1] __local0__ = 0 __local1__ = 1 @@ -5838,7 +5879,7 @@ func TestRewriteDeclaredVars(t *testing.T) { xs = [1, 2] k = "foo" v = "bar" - p { + p if { every k, v in xs { k + v > i } } `, @@ -5848,7 +5889,7 @@ func TestRewriteDeclaredVars(t *testing.T) { xs = [1, 2] k = "foo" v = "bar" - p = true { + p = true if { __local2__ = data.test.xs every __local0__, __local1__ in __local2__ { plus(__local0__, __local1__, __local3__) @@ -5863,7 +5904,7 @@ func TestRewriteDeclaredVars(t *testing.T) { package test # import future.keywords.in # import future.keywords.every - p { + p if { every k, v in [1] { v >= 1 } } `, @@ -5879,13 +5920,13 @@ func TestRewriteDeclaredVars(t *testing.T) { module: ` package test - p { + p if { every __local0__, v in [1] { v >= 1 } } `, exp: ` package test - p = true { + p = true if { __local3__ = [1] every __local1__, __local2__ in __local3__ { __local2__ >= 1 } } @@ -5897,7 +5938,7 @@ func TestRewriteDeclaredVars(t *testing.T) { package test # import future.keywords.in # import future.keywords.every - p { + p if { every v in [1] { true } } `, @@ -5909,13 +5950,13 @@ func TestRewriteDeclaredVars(t *testing.T) { package test # import future.keywords.in # import future.keywords.every - p { + p if { every k, _ in [1] { k >= 0 } } `, exp: ` package test - p = true { + p = true if { __local1__ = [1] every __local0__, _ in __local1__ { gte(__local0__, 0) } } @@ -5927,13 +5968,13 @@ func TestRewriteDeclaredVars(t *testing.T) { package test # import future.keywords.in # import future.keywords.every - p { + p if { every _, _ in [1] { true } } `, exp: ` package test - p = true { __local0__ = [1]; every _, _ in __local0__ { true } } + p = true if { __local0__ = [1]; every _, _ in __local0__ { true } } `, }, { @@ -5942,7 +5983,7 @@ func TestRewriteDeclaredVars(t *testing.T) { package test # import future.keywords.in # import future.keywords.every - p { + p if { some x x = 10 every x in [1] { x == 1 } @@ -5950,7 +5991,7 @@ func TestRewriteDeclaredVars(t *testing.T) { `, exp: ` package test - p = true { + p = true if { __local0__ = 10 __local3__ = [1] every __local1__, __local2__ in __local3__ { __local2__ = 1 } @@ -5963,7 +6004,7 @@ func TestRewriteDeclaredVars(t *testing.T) { package test # import future.keywords.in # import future.keywords.every - p { + p if { some y y = 10 every x in [1] { x == y } @@ -5971,7 +6012,7 @@ func TestRewriteDeclaredVars(t *testing.T) { `, exp: ` package test - p = true { + p = true if { __local0__ = 10 __local3__ = [1] every __local1__, __local2__ in __local3__ { @@ -5986,7 +6027,7 @@ func TestRewriteDeclaredVars(t *testing.T) { package test # import future.keywords.in # import future.keywords.every - p[x] { + p contains x if { some x x = 10 every _ in [1] { true } @@ -5994,7 +6035,7 @@ func TestRewriteDeclaredVars(t *testing.T) { `, exp: ` package test - p[__local0__] { __local0__ = 10; __local2__ = [1]; every __local1__, _ in __local2__ { true } } + p contains __local0__ if { __local0__ = 10; __local2__ = [1]; every __local1__, _ in __local2__ { true } } `, }, { @@ -6003,7 +6044,7 @@ func TestRewriteDeclaredVars(t *testing.T) { package test # import future.keywords.in # import future.keywords.every - p { + p if { xs := [[1], [2]] every v in [1] { every w in xs[v] { @@ -6014,7 +6055,7 @@ func TestRewriteDeclaredVars(t *testing.T) { `, exp: ` package test - p = true { + p = true if { __local0__ = [[1], [2]] __local5__ = [1] every __local1__, __local2__ in __local5__ { @@ -6032,13 +6073,13 @@ func TestRewriteDeclaredVars(t *testing.T) { package test # import future.keywords.in # import future.keywords.every - p { + p if { every x in input { x == 1 } with input as [1, 1, 1] } `, exp: ` package test - p { + p if { __local2__ = input with input as [1, 1, 1] every __local0__, __local1__ in __local2__ { __local1__ = 1 @@ -6052,14 +6093,14 @@ func TestRewriteDeclaredVars(t *testing.T) { package test # import future.keywords.in # import future.keywords.every - p { + p if { xs := [1, 2] every x in input { x == 1 } with input as xs } `, exp: ` package test - p { + p if { __local0__ = [1, 2] __local3__ = input with input as __local0__ every __local1__, __local2__ in __local3__ { @@ -6074,13 +6115,13 @@ func TestRewriteDeclaredVars(t *testing.T) { package test # import future.keywords.in # import future.keywords.every - p { + p if { every x in [2] { x == input } with input as 2 } `, exp: ` package test - p { + p if { __local2__ = [2] with input as 2 every __local0__, __local1__ in __local2__ { __local1__ = input @@ -6094,13 +6135,13 @@ func TestRewriteDeclaredVars(t *testing.T) { package test # import future.keywords.in # import future.keywords.every - p { + p if { every x, y in input { true with data.test.q[x][y] as 100 } } `, exp: ` package test - p { + p if { __local2__ = input every __local0__, __local1__ in __local2__ { true with data.test.q[__local0__][__local1__] as 100 @@ -6114,7 +6155,7 @@ func TestRewriteDeclaredVars(t *testing.T) { package test x = 1 y = 2 - p { + p if { some x, z z = 3 [x | x = 2; y = 2; some z; z = 4] @@ -6124,7 +6165,7 @@ func TestRewriteDeclaredVars(t *testing.T) { package test x = 1 y = 2 - p { + p if { __local1__ = 3 [__local0__ | __local0__ = 2; data.test.y = 2; __local2__ = 4] } @@ -6137,7 +6178,7 @@ func TestRewriteDeclaredVars(t *testing.T) { x = "a" y = 1 z = 2 - p[x] = [y, z] { + p[x] = [y, z] if { some x, z x = "b" z = 4 @@ -6147,7 +6188,7 @@ func TestRewriteDeclaredVars(t *testing.T) { x = "a" y = 1 z = 2 - p[__local0__] = __local2__ { + p[__local0__] = __local2__ if { __local0__ = "b" __local1__ = 4; __local3__ = data.test.y @@ -6160,23 +6201,23 @@ func TestRewriteDeclaredVars(t *testing.T) { module: ` package test - p { + p if { f(input, "bar") } - f(x, y) { + f(x, y) if { x[y] } `, exp: ` package test - p = true { + p = true if { __local2__ = input; data.test.f(__local2__, "bar") } - f(__local0__, __local1__) = true { + f(__local0__, __local1__) = true if { __local0__[__local1__] } `, @@ -6185,7 +6226,7 @@ func TestRewriteDeclaredVars(t *testing.T) { note: "redeclare err", module: ` package test - p { + p if { some x some x } @@ -6196,7 +6237,7 @@ func TestRewriteDeclaredVars(t *testing.T) { note: "redeclare assigned err", module: ` package test - p { + p if { x := 1 some x } @@ -6207,7 +6248,7 @@ func TestRewriteDeclaredVars(t *testing.T) { note: "redeclare reference err", module: ` package test - p { + p if { data.q[x] some x } @@ -6218,7 +6259,7 @@ func TestRewriteDeclaredVars(t *testing.T) { note: "declare unused err", module: ` package test - p { + p if { some x } `, @@ -6228,7 +6269,7 @@ func TestRewriteDeclaredVars(t *testing.T) { note: "declare unsafe err", module: ` package test - p[x] { + p contains x if { some x x == 1 } @@ -6240,7 +6281,7 @@ func TestRewriteDeclaredVars(t *testing.T) { module: ` package test - f([a]) { + f([a]) if { some a a = 1 } @@ -6278,7 +6319,7 @@ func TestCheckUnusedFunctionArgVars(t *testing.T) { { note: "one of the two function args is not used - issue 5602 regression test", module: `package test - func(x, y) { + func(x, y) if { x = 1 }`, expectedErrors: Errors{ @@ -6292,7 +6333,7 @@ func TestCheckUnusedFunctionArgVars(t *testing.T) { { note: "one of the two ref-head function args is not used", module: `package test - a.b.c.func(x, y) { + a.b.c.func(x, y) if { x = 1 }`, expectedErrors: Errors{ @@ -6306,7 +6347,7 @@ func TestCheckUnusedFunctionArgVars(t *testing.T) { { note: "multiple unused argvar in scope - issue 5602 regression test", module: `package test - func(x, y) { + func(x, y) if { input.baz = 1 input.test == "foo" }`, @@ -6326,7 +6367,7 @@ func TestCheckUnusedFunctionArgVars(t *testing.T) { { note: "some unused argvar in scope - issue 5602 regression test", module: `package test - func(x, y) { + func(x, y) if { input.test == "foo" x = 1 }`, @@ -6341,7 +6382,7 @@ func TestCheckUnusedFunctionArgVars(t *testing.T) { { note: "wildcard argvar that's ignored - issue 5602 regression test", module: `package test - func(x, _) { + func(x, _) if { input.test == "foo" x = 1 }`, @@ -6350,7 +6391,7 @@ func TestCheckUnusedFunctionArgVars(t *testing.T) { { note: "wildcard argvar that's ignored - issue 5602 regression test", module: `package test - func(x, _) { + func(x, _) if { input.test == "foo" }`, expectedErrors: Errors{ @@ -6364,7 +6405,7 @@ func TestCheckUnusedFunctionArgVars(t *testing.T) { { note: "argvar not used in body but in head - issue 5602 regression test", module: `package test - func(x) := x { + func(x) := x if { input.test == "foo" }`, expectedErrors: Errors{}, @@ -6373,7 +6414,7 @@ func TestCheckUnusedFunctionArgVars(t *testing.T) { note: "argvar not used in body but in head value comprehension", module: `package test a := {"foo": 1} - func(x) := { x: v | v := a[x] } { + func(x) := { x: v | v := a[x] } if { input.test == "foo" }`, expectedErrors: Errors{}, @@ -6382,9 +6423,9 @@ func TestCheckUnusedFunctionArgVars(t *testing.T) { note: "argvar not used in body but in else-head value comprehension", module: `package test a := {"foo": 1} - func(x) { + func(x) if { input.test == "foo" - } else := { x: v | v := a[x] } { + } else := { x: v | v := a[x] } if { input.test == "bar" }`, expectedErrors: Errors{}, @@ -6393,7 +6434,7 @@ func TestCheckUnusedFunctionArgVars(t *testing.T) { note: "argvar not used in body and shadowed in head value comprehension", module: `package test a := {"foo": 1} - func(x) := { x: v | x := "foo"; v := a[x] } { + func(x) := { x: v | x := "foo"; v := a[x] } if { input.test == "foo" }`, expectedErrors: Errors{ @@ -6407,9 +6448,9 @@ func TestCheckUnusedFunctionArgVars(t *testing.T) { { note: "argvar used in primary body but not in else body", module: `package test - func(x) { + func(x) if { input.test == x - } else := false { + } else := false if { input.test == "foo" }`, expectedErrors: Errors{}, @@ -6417,9 +6458,9 @@ func TestCheckUnusedFunctionArgVars(t *testing.T) { { note: "argvar used in primary body but not in else body (with wildcard)", module: `package test - func(x, _) { + func(x, _) if { input.test == x - } else := false { + } else := false if { input.test == "foo" }`, expectedErrors: Errors{}, @@ -6427,9 +6468,9 @@ func TestCheckUnusedFunctionArgVars(t *testing.T) { { note: "argvar not used in primary body but in else body", module: `package test - func(x) { + func(x) if { input.test == "foo" - } else := false { + } else := false if { input.test == x }`, expectedErrors: Errors{}, @@ -6437,9 +6478,9 @@ func TestCheckUnusedFunctionArgVars(t *testing.T) { { note: "argvar not used in primary body but in else body (with wildcard)", module: `package test - func(x, _) { + func(x, _) if { input.test == "foo" - } else := false { + } else := false if { input.test == x }`, expectedErrors: Errors{}, @@ -6447,7 +6488,7 @@ func TestCheckUnusedFunctionArgVars(t *testing.T) { { note: "argvar used in primary body but not in implicit else body", module: `package test - func(x) { + func(x) if { input.test == x } else := false`, expectedErrors: Errors{}, @@ -6455,11 +6496,11 @@ func TestCheckUnusedFunctionArgVars(t *testing.T) { { note: "argvars usage spread over multiple bodies", module: `package test - func(x, y, z) { + func(x, y, z) if { input.test == x - } else { + } else if { input.test == y - } else { + } else if { input.test == z }`, expectedErrors: Errors{}, @@ -6467,11 +6508,11 @@ func TestCheckUnusedFunctionArgVars(t *testing.T) { { note: "argvars usage spread over multiple bodies, missing in first", module: `package test - func(x, y, z) { + func(x, y, z) if { input.test == "foo" - } else { + } else if { input.test == y - } else { + } else if { input.test == z }`, expectedErrors: Errors{ @@ -6485,11 +6526,11 @@ func TestCheckUnusedFunctionArgVars(t *testing.T) { { note: "argvars usage spread over multiple bodies, missing in second", module: `package test - func(x, y, z) { + func(x, y, z) if { input.test == x - } else { + } else if { input.test == "bar" - } else { + } else if { input.test == z }`, expectedErrors: Errors{ @@ -6503,11 +6544,11 @@ func TestCheckUnusedFunctionArgVars(t *testing.T) { { note: "argvars usage spread over multiple bodies, missing in third", module: `package test - func(x, y, z) { + func(x, y, z) if { input.test == x - } else { + } else if { input.test == y - } else { + } else if { input.test == "baz" }`, expectedErrors: Errors{ @@ -6537,7 +6578,7 @@ func TestCheckUnusedFunctionArgVars(t *testing.T) { t.Run(tc.note, func(t *testing.T) { compiler := NewCompiler().WithStrict(true) compiler.Modules = map[string]*Module{ - "test": MustParseModule(tc.module), + "test": module(tc.module), } compileStages(compiler, nil) @@ -6551,7 +6592,7 @@ func TestCompileUnusedAssignedVarsErrorLocations(t *testing.T) { { note: "one of the two function args is not used - issue 5662 regression test", module: `package test - func(x, y) { + func(x, y) if { x = 1 }`, expectedErrors: Errors{ @@ -6565,7 +6606,7 @@ func TestCompileUnusedAssignedVarsErrorLocations(t *testing.T) { { note: "multiple unused assigned var in scope - issue 5662 regression test", module: `package test - allow { + allow if { input.message == "world" input.test == "foo" input.x == "foo" @@ -6604,7 +6645,7 @@ func TestCompileUnusedAssignedVarsErrorLocations(t *testing.T) { t.Run(tc.note, func(t *testing.T) { compiler := NewCompiler().WithStrict(true) compiler.Modules = map[string]*Module{ - "test": MustParseModule(tc.module), + "test": module(tc.module), } compileStages(compiler, nil) assertErrors(t, compiler.Errors, tc.expectedErrors, true) @@ -6619,7 +6660,7 @@ func TestCompileUnusedDeclaredVarsErrorLocations(t *testing.T) { note: "simple unused some var - issue 4238 regression test", module: `package test - foo { + foo if { print("Hello world") some i }`, @@ -6635,12 +6676,12 @@ func TestCompileUnusedDeclaredVarsErrorLocations(t *testing.T) { note: "simple unused some vars, 2x rules", module: `package test - foo { + foo if { print("Hello world") some i } - bar { + bar if { print("Hello world") some j }`, @@ -6662,7 +6703,7 @@ func TestCompileUnusedDeclaredVarsErrorLocations(t *testing.T) { module: `package test x := [1, 1, 1] - foo2 { + foo2 if { print("A") some a, b, c some i, j @@ -6707,7 +6748,7 @@ func TestCompileUnusedDeclaredVarsErrorLocations(t *testing.T) { t.Run(tc.note, func(t *testing.T) { compiler := NewCompiler().WithStrict(true) compiler.Modules = map[string]*Module{ - "test": MustParseModule(tc.module), + "test": module(tc.module), } compileStages(compiler, nil) @@ -6737,14 +6778,14 @@ func TestCompileInvalidEqAssignExpr(t *testing.T) { c.Modules["error"] = MustParseModuleWithOpts(`package errors - p if { - # Arity mismatches are caught in the checkUndefinedFuncs check, - # and invalid eq/assign calls are passed along until then. - assign() - assign(1) - eq() - eq(1) - }`, ParserOptions{RegoVersion: tc.regoVersion, AllFutureKeywords: true}) + p if { + # Arity mismatches are caught in the checkUndefinedFuncs check, + # and invalid eq/assign calls are passed along until then. + assign() + assign(1) + eq() + eq(1) + }`, ParserOptions{RegoVersion: tc.regoVersion, AllFutureKeywords: true}) var prev func() checkUndefinedFuncs := reflect.ValueOf(c.checkUndefinedFuncs) @@ -6797,17 +6838,17 @@ func TestCompilerRewriteDoubleEq(t *testing.T) { }{ { note: "vars and constants", - input: "p { x = 1; x == 1; y = [1,2,3]; y == [1,2,3] }", + input: "p if { x = 1; x == 1; y = [1,2,3]; y == [1,2,3] }", exp: `x = 1; x = 1; y = [1,2,3]; y = [1,2,3]`, }, { note: "refs", - input: "p { input.x == data.y }", + input: "p if { input.x == data.y }", exp: `input.x = data.y`, }, { note: "comprehensions", - input: "p { [1|true] == [2|true] }", + input: "p if { [1|true] == [2|true] }", exp: `[1|true] = [2|true]`, }, // TODO(tsandall): improve support for calls so that extra unification step is @@ -6818,29 +6859,29 @@ func TestCompilerRewriteDoubleEq(t *testing.T) { // have been converted into = and then the safety check would need to be updated. { note: "calls", - input: "p { count([1,2]) == 2 }", + input: "p if { count([1,2]) == 2 }", exp: `count([1,2], __local0__); __local0__ = 2`, }, { note: "embedded", - input: "p { x = 1; y = [x == 0] }", + input: "p if { x = 1; y = [x == 0] }", exp: `x = 1; equal(x, 0, __local0__); y = [__local0__]`, }, { note: "embedded in call", - input: `p { x = 0; neq(true, x == 1) }`, + input: `p if { x = 0; neq(true, x == 1) }`, exp: `x = 0; equal(x, 1, __local0__); neq(true, __local0__)`, }, { note: "comprehension in object key", - input: `p { {{1 | 0 == 0}: 2} }`, + input: `p if { {{1 | 0 == 0}: 2} }`, exp: `{{1 | 0 = 0}: 2}`, }, } for _, tc := range tests { t.Run(tc.note, func(t *testing.T) { c := NewCompiler() - c.Modules["test"] = MustParseModule("package test\n" + tc.input) + c.Modules["test"] = module("package test\n" + tc.input) compileStages(c, c.rewriteEquals) assertNotFailed(t, c) exp := MustParseBody(tc.exp) @@ -6863,34 +6904,34 @@ func TestCompilerRewriteDynamicTerms(t *testing.T) { input string expected string }{ - {`arr { [str] }`, `__local0__ = data.test.str; [__local0__]`}, - {`arr2 { [[str]] }`, `__local0__ = data.test.str; [[__local0__]]`}, - {`obj { {"x": str} }`, `__local0__ = data.test.str; {"x": __local0__}`}, - {`obj2 { {"x": {"y": str}} }`, `__local0__ = data.test.str; {"x": {"y": __local0__}}`}, - {`set { {str} }`, `__local0__ = data.test.str; {__local0__}`}, - {`set2 { {{str}} }`, `__local0__ = data.test.str; {{__local0__}}`}, - {`ref { str[str] }`, `__local0__ = data.test.str; data.test.str[__local0__]`}, - {`ref2 { str[str[str]] }`, `__local0__ = data.test.str; __local1__ = data.test.str[__local0__]; data.test.str[__local1__]`}, - {`arr_compr { [1 | [str]] }`, `[1 | __local0__ = data.test.str; [__local0__]]`}, - {`arr_compr2 { [1 | [1 | [str]]] }`, `[1 | [1 | __local0__ = data.test.str; [__local0__]]]`}, - {`set_compr { {1 | [str]} }`, `{1 | __local0__ = data.test.str; [__local0__]}`}, - {`set_compr2 { {1 | {1 | [str]}} }`, `{1 | {1 | __local0__ = data.test.str; [__local0__]}}`}, - {`obj_compr { {"a": "b" | [str]} }`, `{"a": "b" | __local0__ = data.test.str; [__local0__]}`}, - {`obj_compr2 { {"a": "b" | {"a": "b" | [str]}} }`, `{"a": "b" | {"a": "b" | __local0__ = data.test.str; [__local0__]}}`}, - {`equality { str = str }`, `data.test.str = data.test.str`}, - {`equality2 { [str] = [str] }`, `__local0__ = data.test.str; __local1__ = data.test.str; [__local0__] = [__local1__]`}, - {`call { startswith(str, "") }`, `__local0__ = data.test.str; startswith(__local0__, "")`}, - {`call2 { count([str], n) }`, `__local0__ = data.test.str; count([__local0__], n)`}, - {`eq_with { [str] = [1] with input as 1 }`, `__local0__ = data.test.str with input as 1; [__local0__] = [1] with input as 1`}, - {`term_with { [[str]] with input as 1 }`, `__local0__ = data.test.str with input as 1; [[__local0__]] with input as 1`}, - {`call_with { count(str) with input as 1 }`, `__local0__ = data.test.str with input as 1; count(__local0__) with input as 1`}, - {`call_func { f(input, "foo") } f(x,y) { x[y] }`, `__local2__ = input; data.test.f(__local2__, "foo")`}, - {`call_func2 { f(input.foo, "foo") } f(x,y) { x[y] }`, `__local2__ = input.foo; data.test.f(__local2__, "foo")`}, - {`every_domain { every _ in str { true } }`, `__local1__ = data.test.str; every __local0__, _ in __local1__ { true }`}, - {`every_domain_array { every _ in [1, 2, 3] { true } }`, `__local1__ = [1, 2, 3]; every __local0__, _ in __local1__ { true }`}, - {`every_domain_call { every _ in numbers.range(1, 10) { true } }`, `numbers.range(1, 10, __local2__); __local1__ = __local2__; every __local0__, _ in __local1__ { true }`}, - {`every_domain_array_w_calls { every _ in [1 / 2, "foo", abs(-1)] { true } }`, `div(1, 2, __local2__); abs(-1, __local3__); __local1__ = [__local2__, "foo", __local3__]; every __local0__, _ in __local1__ { true }`}, - {`every_body { every _ in [] { [str] } }`, + {`arr if { [str] }`, `__local0__ = data.test.str; [__local0__]`}, + {`arr2 if { [[str]] }`, `__local0__ = data.test.str; [[__local0__]]`}, + {`obj if { {"x": str} }`, `__local0__ = data.test.str; {"x": __local0__}`}, + {`obj2 if { {"x": {"y": str}} }`, `__local0__ = data.test.str; {"x": {"y": __local0__}}`}, + {`set if { {str} }`, `__local0__ = data.test.str; {__local0__}`}, + {`set2 if { {{str}} }`, `__local0__ = data.test.str; {{__local0__}}`}, + {`ref if { str[str] }`, `__local0__ = data.test.str; data.test.str[__local0__]`}, + {`ref2 if { str[str[str]] }`, `__local0__ = data.test.str; __local1__ = data.test.str[__local0__]; data.test.str[__local1__]`}, + {`arr_compr if { [1 | [str]] }`, `[1 | __local0__ = data.test.str; [__local0__]]`}, + {`arr_compr2 if { [1 | [1 | [str]]] }`, `[1 | [1 | __local0__ = data.test.str; [__local0__]]]`}, + {`set_compr if { {1 | [str]} }`, `{1 | __local0__ = data.test.str; [__local0__]}`}, + {`set_compr2 if { {1 | {1 | [str]}} }`, `{1 | {1 | __local0__ = data.test.str; [__local0__]}}`}, + {`obj_compr if { {"a": "b" | [str]} }`, `{"a": "b" | __local0__ = data.test.str; [__local0__]}`}, + {`obj_compr2 if { {"a": "b" | {"a": "b" | [str]}} }`, `{"a": "b" | {"a": "b" | __local0__ = data.test.str; [__local0__]}}`}, + {`equality if { str = str }`, `data.test.str = data.test.str`}, + {`equality2 if { [str] = [str] }`, `__local0__ = data.test.str; __local1__ = data.test.str; [__local0__] = [__local1__]`}, + {`call if { startswith(str, "") }`, `__local0__ = data.test.str; startswith(__local0__, "")`}, + {`call2 if { count([str], n) }`, `__local0__ = data.test.str; count([__local0__], n)`}, + {`eq_with if { [str] = [1] with input as 1 }`, `__local0__ = data.test.str with input as 1; [__local0__] = [1] with input as 1`}, + {`term_with if { [[str]] with input as 1 }`, `__local0__ = data.test.str with input as 1; [[__local0__]] with input as 1`}, + {`call_with if { count(str) with input as 1 }`, `__local0__ = data.test.str with input as 1; count(__local0__) with input as 1`}, + {`call_func if { f(input, "foo") } f(x,y) if { x[y] }`, `__local2__ = input; data.test.f(__local2__, "foo")`}, + {`call_func2 if { f(input.foo, "foo") } f(x,y) if { x[y] }`, `__local2__ = input.foo; data.test.f(__local2__, "foo")`}, + {`every_domain if { every _ in str { true } }`, `__local1__ = data.test.str; every __local0__, _ in __local1__ { true }`}, + {`every_domain_array if { every _ in [1, 2, 3] { true } }`, `__local1__ = [1, 2, 3]; every __local0__, _ in __local1__ { true }`}, + {`every_domain_call if { every _ in numbers.range(1, 10) { true } }`, `numbers.range(1, 10, __local2__); __local1__ = __local2__; every __local0__, _ in __local1__ { true }`}, + {`every_domain_array_w_calls if { every _ in [1 / 2, "foo", abs(-1)] { true } }`, `div(1, 2, __local2__); abs(-1, __local3__); __local1__ = [__local2__, "foo", __local3__]; every __local0__, _ in __local1__ { true }`}, + {`every_body if { every _ in [] { [str] } }`, `__local1__ = []; every __local0__, _ in __local1__ { __local2__ = data.test.str; [__local2__] }`}, } @@ -6898,8 +6939,7 @@ func TestCompilerRewriteDynamicTerms(t *testing.T) { t.Run(tc.input, func(t *testing.T) { c := NewCompiler() opts := ParserOptions{AllFutureKeywords: true, unreleasedKeywords: true} - module := fixture + tc.input - c.Modules["test"] = MustParseModuleWithOpts(module, opts) + c.Modules["test"] = module(fixture + tc.input) compileStages(c, c.rewriteDynamicTerms) assertNotFailed(t, c) expected := MustParseBodyWithOpts(tc.expected, opts) @@ -6928,119 +6968,119 @@ func TestCompilerRewriteWithValue(t *testing.T) { }{ { note: "nop", - input: `p { true with input as 1 }`, - expected: `p { true with input as 1 }`, + input: `p if { true with input as 1 }`, + expected: `p if { true with input as 1 }`, }, { note: "refs", - input: `p { true with input as arr }`, - expected: `p { __local0__ = data.test.arr; true with input as __local0__ }`, + input: `p if { true with input as arr }`, + expected: `p if { __local0__ = data.test.arr; true with input as __local0__ }`, }, { note: "array comprehension", - input: `p { true with input as [true | true] }`, - expected: `p { __local0__ = [true | true]; true with input as __local0__ }`, + input: `p if { true with input as [true | true] }`, + expected: `p if { __local0__ = [true | true]; true with input as __local0__ }`, }, { note: "set comprehension", - input: `p { true with input as {true | true} }`, - expected: `p { __local0__ = {true | true}; true with input as __local0__ }`, + input: `p if { true with input as {true | true} }`, + expected: `p if { __local0__ = {true | true}; true with input as __local0__ }`, }, { note: "object comprehension", - input: `p { true with input as {"k": true | true} }`, - expected: `p { __local0__ = {"k": true | true}; true with input as __local0__ }`, + input: `p if { true with input as {"k": true | true} }`, + expected: `p if { __local0__ = {"k": true | true}; true with input as __local0__ }`, }, { note: "comprehension nested", - input: `p { true with input as [true | true with input as arr] }`, - expected: `p { __local0__ = [true | __local1__ = data.test.arr; true with input as __local1__]; true with input as __local0__ }`, + input: `p if { true with input as [true | true with input as arr] }`, + expected: `p if { __local0__ = [true | __local1__ = data.test.arr; true with input as __local1__]; true with input as __local0__ }`, }, { note: "multiple", - input: `p { true with input.a as arr[0] with input.b as arr[1] }`, - expected: `p { __local0__ = data.test.arr[0]; __local1__ = data.test.arr[1]; true with input.a as __local0__ with input.b as __local1__ }`, + input: `p if { true with input.a as arr[0] with input.b as arr[1] }`, + expected: `p if { __local0__ = data.test.arr[0]; __local1__ = data.test.arr[1]; true with input.a as __local0__ with input.b as __local1__ }`, }, { note: "invalid target", - input: `p { true with foo.q as 1 }`, + input: `p if { true with foo.q as 1 }`, wantErr: fmt.Errorf("rego_type_error: with keyword target must reference existing input, data, or a function"), }, { note: "built-in function: replaced by (unknown) var", - input: `p { true with time.now_ns as foo }`, - expected: `p { true with time.now_ns as foo }`, // `foo` still a Var here + input: `p if { true with time.now_ns as foo }`, + expected: `p if { true with time.now_ns as foo }`, // `foo` still a Var here }, { note: "built-in function: valid, arity 0", input: ` - p { true with time.now_ns as now } + p if { true with time.now_ns as now } now() = 1 `, - expected: `p { true with time.now_ns as data.test.now }`, + expected: `p if { true with time.now_ns as data.test.now }`, }, { note: "built-in function: valid func ref, arity 1", input: ` - p { true with http.send as mock_http_send } + p if { true with http.send as mock_http_send } mock_http_send(_) = { "body": "yay" } `, - expected: `p { true with http.send as data.test.mock_http_send }`, + expected: `p if { true with http.send as data.test.mock_http_send }`, }, { note: "built-in function: replaced by value", input: ` - p { true with http.send as { "body": "yay" } } + p if { true with http.send as { "body": "yay" } } `, - expected: `p { true with http.send as {"body": "yay"} }`, + expected: `p if { true with http.send as {"body": "yay"} }`, }, { note: "built-in function: replaced by var", input: ` - p { + p if { resp := { "body": "yay" } true with http.send as resp } `, - expected: `p { __local0__ = {"body": "yay"}; true with http.send as __local0__ }`, + expected: `p if { __local0__ = {"body": "yay"}; true with http.send as __local0__ }`, }, { note: "non-built-in function: replaced by var", input: ` - p { + p if { resp := true f(true) with f as resp } - f(false) { true } + f(false) if { true } `, - expected: `p { __local0__ = true; data.test.f(true) with data.test.f as __local0__ }`, + expected: `p if { __local0__ = true; data.test.f(true) with data.test.f as __local0__ }`, }, { note: "built-in function: replaced by comprehension", input: ` - p { true with http.send as { x: true | x := ["a", "b"][_] } } + p if { true with http.send as { x: true | x := ["a", "b"][_] } } `, - expected: `p { __local2__ = {__local0__: true | __local1__ = ["a", "b"]; __local0__ = __local1__[_]}; true with http.send as __local2__ }`, + expected: `p if { __local2__ = {__local0__: true | __local1__ = ["a", "b"]; __local0__ = __local1__[_]}; true with http.send as __local2__ }`, }, { note: "built-in function: replaced by ref", input: ` - p { true with http.send as resp } + p if { true with http.send as resp } resp := { "body": "yay" } `, - expected: `p { true with http.send as data.test.resp }`, + expected: `p if { true with http.send as data.test.resp }`, }, { note: "built-in function: replaced by another built-in (ref)", input: ` - p { true with http.send as object.union_n } + p if { true with http.send as object.union_n } `, - expected: `p { true with http.send as object.union_n }`, + expected: `p if { true with http.send as object.union_n }`, }, { note: "built-in function: replaced by another built-in (simple)", input: ` - p { true with http.send as count } + p if { true with http.send as count } `, expectedRule: func() *Rule { r := MustParseRule(`p { true with http.send as count }`) @@ -7052,7 +7092,7 @@ func TestCompilerRewriteWithValue(t *testing.T) { note: "built-in function: replaced by another built-in that's marked unsafe", input: ` q := is_object({"url": "https://httpbin.org", "method": "GET"}) - p { q with is_object as http.send } + p if { q with is_object as http.send } `, opts: func(c *Compiler) *Compiler { return c.WithUnsafeBuiltins(map[string]struct{}{"http.send": {}}) }, wantErr: fmt.Errorf("rego_compile_error: with keyword replacing built-in function: target must not be unsafe: \"http.send\""), @@ -7062,7 +7102,7 @@ func TestCompilerRewriteWithValue(t *testing.T) { input: ` r(_) = {} q := r({"url": "https://httpbin.org", "method": "GET"}) - p { + p if { q with r as http.send }`, opts: func(c *Compiler) *Compiler { return c.WithUnsafeBuiltins(map[string]struct{}{"http.send": {}}) }, @@ -7071,11 +7111,12 @@ func TestCompilerRewriteWithValue(t *testing.T) { { note: "built-in function: valid, arity 1, non-compound name", input: ` - p { concat("/", input) with concat as mock_concat } + p if { concat("/", input) with concat as mock_concat } mock_concat(_, _) = "foo/bar" `, expectedRule: func() *Rule { - r := MustParseRule(`p { concat("/", input) with concat as data.test.mock_concat }`) + r := MustParseRuleWithOpts(`p if { concat("/", input) with concat as data.test.mock_concat }`, + ParserOptions{RegoVersion: RegoV1}) r.Body[0].With[0].Target.Value = Ref([]*Term{VarTerm("concat")}) return r }(), @@ -7088,14 +7129,13 @@ func TestCompilerRewriteWithValue(t *testing.T) { if tc.opts != nil { c = tc.opts(c) } - module := fixture + tc.input - c.Modules["test"] = MustParseModule(module) + c.Modules["test"] = module(fixture + tc.input) compileStages(c, c.rewriteWithModifiers) if tc.wantErr == nil { assertNotFailed(t, c) expected := tc.expectedRule if expected == nil { - expected = MustParseRule(tc.expected) + expected = MustParseRuleWithOpts(tc.expected, ParserOptions{RegoVersion: RegoV1}) } result := c.Modules["test"].Rules[1] if result.Compare(expected) != 0 { @@ -7118,69 +7158,69 @@ func TestCompilerRewritePrintCallsErasure(t *testing.T) { { note: "no-op", module: `package test - p { true }`, + p if { true }`, exp: `package test - p { true }`, + p if { true }`, }, { note: "replace empty body with true", module: `package test - p { print(1) } + p if { print(1) } `, exp: `package test - p { true } `, + p if { true } `, }, { note: "rule body", module: `package test - p { false; print(1) } + p if { false; print(1) } `, exp: `package test - p { false } `, + p if { false } `, }, { note: "set comprehension body", module: `package test - p { {1 | false; print(1)} } + p if { {1 | false; print(1)} } `, exp: `package test - p { {1 | false} } `, + p if { {1 | false} } `, }, { note: "array comprehension body", module: `package test - p { [1 | false; print(1)] } + p if { [1 | false; print(1)] } `, exp: `package test - p { [1 | false] } `, + p if { [1 | false] } `, }, { note: "object comprehension body", module: `package test - p { {"x": 1 | false; print(1)} } + p if { {"x": 1 | false; print(1)} } `, exp: `package test - p { {"x": 1 | false} } `, + p if { {"x": 1 | false} } `, }, { note: "every body", module: `package test - p { every _ in [] { false; print(1) } } + p if { every _ in [] { false; print(1) } } `, exp: `package test - p = true { __local1__ = []; every __local0__, _ in __local1__ { false } }`, + p = true if { __local1__ = []; every __local0__, _ in __local1__ { false } }`, }, { note: "in head", @@ -7189,21 +7229,20 @@ func TestCompilerRewritePrintCallsErasure(t *testing.T) { p = {1 | print("x")}`, exp: `package test - p = __local0__ { true; __local0__ = {1 | true} }`, + p = __local0__ if { true; __local0__ = {1 | true} }`, }, } for _, tc := range cases { t.Run(tc.note, func(t *testing.T) { c := NewCompiler().WithEnablePrintStatements(false) - opts := ParserOptions{AllFutureKeywords: true, unreleasedKeywords: true} c.Compile(map[string]*Module{ - "test.rego": MustParseModuleWithOpts(tc.module, opts), + "test.rego": module(tc.module), }) if c.Failed() { t.Fatal(c.Errors) } - exp := MustParseModuleWithOpts(tc.exp, opts) + exp := module(tc.exp) if !exp.Equal(c.Modules["test.rego"]) { t.Fatalf("Expected:\n\n%v\n\nGot:\n\n%v", exp, c.Modules["test.rego"]) } @@ -7221,20 +7260,20 @@ func TestCompilerRewritePrintCallsErrors(t *testing.T) { note: "non-existent var", module: `package test - p { print(x) }`, + p if { print(x) }`, exp: errors.New("var x is undeclared"), }, { note: "declared after print", module: `package test - p { print(x); x = 7 }`, + p if { print(x); x = 7 }`, exp: errors.New("var x is undeclared"), }, { note: "inside comprehension", module: `package test - p { {1 | print(x)} = {1 | print(7)} } + p if { {1 | print(x)} = {1 | print(7)} } `, exp: errors.New("var x is undeclared"), }, @@ -7244,7 +7283,7 @@ func TestCompilerRewritePrintCallsErrors(t *testing.T) { t.Run(tc.note, func(t *testing.T) { c := NewCompiler().WithEnablePrintStatements(true) c.Compile(map[string]*Module{ - "test.rego": MustParseModule(tc.module), + "test.rego": module(tc.module), }) if !c.Failed() { t.Fatal("expected error") @@ -7266,55 +7305,55 @@ func TestCompilerRewritePrintCalls(t *testing.T) { note: "print one", module: `package test - p { print(1) }`, + p if { print(1) }`, exp: `package test - p = true { __local1__ = {__local0__ | __local0__ = 1}; internal.print([__local1__]) }`, + p = true if { __local1__ = {__local0__ | __local0__ = 1}; internal.print([__local1__]) }`, }, { note: "print multiple", module: `package test - p { print(1, 2) }`, + p if { print(1, 2) }`, exp: `package test - p = true { __local2__ = {__local0__ | __local0__ = 1}; __local3__ = {__local1__ | __local1__ = 2}; internal.print([__local2__, __local3__]) }`, + p = true if { __local2__ = {__local0__ | __local0__ = 1}; __local3__ = {__local1__ | __local1__ = 2}; internal.print([__local2__, __local3__]) }`, }, { note: "print inside set comprehension", module: `package test - p { x = 1; {2 | print(x)} }`, + p if { x = 1; {2 | print(x)} }`, exp: `package test - p = true { x = 1; {2 | __local1__ = {__local0__ | __local0__ = x}; internal.print([__local1__])} }`, + p = true if { x = 1; {2 | __local1__ = {__local0__ | __local0__ = x}; internal.print([__local1__])} }`, }, { note: "print inside array comprehension", module: `package test - p { x = 1; [2 | print(x)] }`, + p if { x = 1; [2 | print(x)] }`, exp: `package test - p = true { x = 1; [2 | __local1__ = {__local0__ | __local0__ = x}; internal.print([__local1__])] }`, + p = true if { x = 1; [2 | __local1__ = {__local0__ | __local0__ = x}; internal.print([__local1__])] }`, }, { note: "print inside object comprehension", module: `package test - p { x = 1; {"x": 2 | print(x)} }`, + p if { x = 1; {"x": 2 | print(x)} }`, exp: `package test - p = true { x = 1; {"x": 2 | __local1__ = {__local0__ | __local0__ = x}; internal.print([__local1__])} }`, + p = true if { x = 1; {"x": 2 | __local1__ = {__local0__ | __local0__ = x}; internal.print([__local1__])} }`, }, { note: "print inside every", module: `package test - p { every x in [1,2] { print(x) } }`, + p if { every x in [1,2] { print(x) } }`, exp: `package test - p = true { + p = true if { __local3__ = [1, 2] every __local0__, __local1__ in __local3__ { __local4__ = {__local2__ | __local2__ = __local1__} @@ -7326,13 +7365,13 @@ func TestCompilerRewritePrintCalls(t *testing.T) { note: "print output of nested call", module: `package test - p { + p if { x := split("abc", "")[y] print(x, y) }`, exp: `package test - p = true { split("abc", "", __local3__); __local0__ = __local3__[y]; __local4__ = {__local1__ | __local1__ = __local0__}; __local5__ = {__local2__ | __local2__ = y}; internal.print([__local4__, __local5__]) }`, + p = true if { split("abc", "", __local3__); __local0__ = __local3__[y]; __local4__ = {__local1__ | __local1__ = __local0__}; __local5__ = {__local2__ | __local2__ = y}; internal.print([__local4__, __local5__]) }`, }, { note: "print call in head", @@ -7341,7 +7380,7 @@ func TestCompilerRewritePrintCalls(t *testing.T) { p = {1 | print("x") }`, exp: `package test - p = __local1__ { + p = __local1__ if { true __local1__ = {1 | __local2__ = { __local0__ | __local0__ = "x"}; internal.print([__local2__])} }`, @@ -7353,50 +7392,50 @@ func TestCompilerRewritePrintCalls(t *testing.T) { f(a) = {1 | a[x]; print(x)}`, exp: `package test - f(__local0__) = __local2__ { true; __local2__ = {1 | __local0__[x]; __local3__ = {__local1__ | __local1__ = x}; internal.print([__local3__])} } + f(__local0__) = __local2__ if { true; __local2__ = {1 | __local0__[x]; __local3__ = {__local1__ | __local1__ = x}; internal.print([__local3__])} } `, }, { note: "print call of var in head key", module: `package test f(_) = [1, 2, 3] - p[x] { [_, x, _] := f(true); print(x) }`, + p contains x if { [_, x, _] := f(true); print(x) }`, exp: `package test - f(__local0__) = [1, 2, 3] { true } - p[__local2__] { data.test.f(true, __local5__); [__local1__, __local2__, __local3__] = __local5__; __local6__ = {__local4__ | __local4__ = __local2__}; internal.print([__local6__]) } + f(__local0__) = [1, 2, 3] if { true } + p contains __local2__ if { data.test.f(true, __local5__); [__local1__, __local2__, __local3__] = __local5__; __local6__ = {__local4__ | __local4__ = __local2__}; internal.print([__local6__]) } `, }, { note: "print call of var in head value", module: `package test f(_) = [1, 2, 3] - p = x { [_, x, _] := f(true); print(x) }`, + p = x if { [_, x, _] := f(true); print(x) }`, exp: `package test - f(__local0__) = [1, 2, 3] { true } - p = __local2__ { data.test.f(true, __local5__); [__local1__, __local2__, __local3__] = __local5__; __local6__ = {__local4__ | __local4__ = __local2__}; internal.print([__local6__]) } + f(__local0__) = [1, 2, 3] if { true } + p = __local2__ if { data.test.f(true, __local5__); [__local1__, __local2__, __local3__] = __local5__; __local6__ = {__local4__ | __local4__ = __local2__}; internal.print([__local6__]) } `, }, { note: "print call of vars in head key and value", module: `package test f(_) = [1, 2, 3] - p[x] = y { [_, x, y] := f(true); print(x) }`, + p[x] = y if { [_, x, y] := f(true); print(x) }`, exp: `package test - f(__local0__) = [1, 2, 3] { true } - p[__local2__] = __local3__ { data.test.f(true, __local5__); [__local1__, __local2__, __local3__] = __local5__; __local6__ = {__local4__ | __local4__ = __local2__}; internal.print([__local6__]) } + f(__local0__) = [1, 2, 3] if { true } + p[__local2__] = __local3__ if { data.test.f(true, __local5__); [__local1__, __local2__, __local3__] = __local5__; __local6__ = {__local4__ | __local4__ = __local2__}; internal.print([__local6__]) } `, }, { note: "print call of vars altered with 'with' and call", module: `package test q = input - p { + p if { x := q with input as json.unmarshal("{}") print(x) }`, exp: `package test - q = __local3__ { true; __local3__ = input } - p = true { + q = __local3__ if { true; __local3__ = input } + p = true if { json.unmarshal("{}", __local2__) __local0__ = data.test.q with input as __local2__ __local4__ = {__local1__ | __local1__ = __local0__} @@ -7408,14 +7447,13 @@ func TestCompilerRewritePrintCalls(t *testing.T) { for _, tc := range cases { t.Run(tc.note, func(t *testing.T) { c := NewCompiler().WithEnablePrintStatements(true) - opts := ParserOptions{AllFutureKeywords: true, unreleasedKeywords: true} c.Compile(map[string]*Module{ - "test.rego": MustParseModuleWithOpts(tc.module, opts), + "test.rego": module(tc.module), }) if c.Failed() { t.Fatal(c.Errors) } - exp := MustParseModuleWithOpts(tc.exp, opts) + exp := module(tc.exp) if !exp.Equal(c.Modules["test.rego"]) { t.Fatalf("Expected:\n\n%v\n\nGot:\n\n%v", exp, c.Modules["test.rego"]) } @@ -7425,31 +7463,30 @@ func TestCompilerRewritePrintCalls(t *testing.T) { func TestRewritePrintCallsWithElseImplicitArgs(t *testing.T) { - module := `package test + mod := `package test - f(x, y) { + f(x, y) if { x = y } - else = false { + else = false if { print(x, y) }` c := NewCompiler().WithEnablePrintStatements(true) - opts := ParserOptions{AllFutureKeywords: true, unreleasedKeywords: true} c.Compile(map[string]*Module{ - "test.rego": MustParseModuleWithOpts(module, opts), + "test.rego": module(mod), }) if c.Failed() { t.Fatal(c.Errors) } - exp := MustParseModuleWithOpts(`package test + exp := module(`package test - f(__local0__, __local1__) = true { __local0__ = __local1__ } - else = false { __local4__ = {__local2__ | __local2__ = __local0__}; __local5__ = {__local3__ | __local3__ = __local1__}; internal.print([__local4__, __local5__]) } - `, opts) + f(__local0__, __local1__) = true if { __local0__ = __local1__ } + else = false if { __local4__ = {__local2__ | __local2__ = __local0__}; __local5__ = {__local3__ | __local3__ = __local1__}; internal.print([__local4__, __local5__]) } + `) // NOTE(tsandall): we have to patch the implicit args on the else rule // because of how the parser copies the arg names across from the first @@ -7472,20 +7509,20 @@ func TestCompilerMockFunction(t *testing.T) { note: "simple valid", module: `package test now() = 123 - p { true with time.now_ns as now } + p if { true with time.now_ns as now } `, }, { note: "simple valid, simple name", module: `package test mock_concat(_, _) = "foo/bar" - p { concat("/", input) with concat as mock_concat } + p if { concat("/", input) with concat as mock_concat } `, }, { note: "invalid ref: nonexistant", module: `package test - p { true with time.now_ns as now } + p if { true with time.now_ns as now } `, err: "rego_unsafe_var_error: var now is unsafe", // we're running all compiler stages here }, @@ -7493,21 +7530,21 @@ func TestCompilerMockFunction(t *testing.T) { note: "valid ref: not a function, but arity = 0", module: `package test now = 1 - p { true with time.now_ns as now } + p if { true with time.now_ns as now } `, }, { note: "ref: not a function, arity > 0", module: `package test http_send = { "body": "nope" } - p { true with http.send as http_send } + p if { true with http.send as http_send } `, }, { note: "invalid ref: arity mismatch", module: `package test http_send(_, _) = { "body": "nope" } - p { true with http.send as http_send } + p if { true with http.send as http_send } `, err: "rego_type_error: http.send: arity mismatch\n\thave: (any, any)\n\twant: (request: object[string: any])", }, @@ -7515,21 +7552,21 @@ func TestCompilerMockFunction(t *testing.T) { note: "invalid ref: arity mismatch (in call)", module: `package test http_send(_, _) = { "body": "nope" } - p { http.send({}) with http.send as http_send } + p if { http.send({}) with http.send as http_send } `, err: "rego_type_error: http.send: arity mismatch\n\thave: (any, any)\n\twant: (request: object[string: any])", }, { note: "invalid ref: value another built-in with different type", module: `package test - p { true with http.send as net.lookup_ip_addr } + p if { true with http.send as net.lookup_ip_addr } `, err: "rego_type_error: http.send: arity mismatch\n\thave: (string)\n\twant: (request: object[string: any])", }, { note: "ref: value another built-in with compatible type", module: `package test - p { true with count as object.union_n } + p if { true with count as object.union_n } `, }, { @@ -7539,7 +7576,7 @@ func TestCompilerMockFunction(t *testing.T) { `, module: `package test import data.mocks - p { true with http.send as mocks.http_send } + p if { true with http.send as mocks.http_send } `, }, { @@ -7549,14 +7586,14 @@ func TestCompilerMockFunction(t *testing.T) { `, module: `package test import data.mocks.http_send - p { true with http.send as http_send } + p if { true with http.send as http_send } `, }, { note: "invalid target: relation", module: `package test my_walk(_, _) - p { true with walk as my_walk } + p if { true with walk as my_walk } `, err: "rego_compile_error: with keyword replacing built-in function: target must not be a relation", }, @@ -7564,21 +7601,21 @@ func TestCompilerMockFunction(t *testing.T) { note: "invalid target: eq", module: `package test my_eq(_, _) - p { true with eq as my_eq } + p if { true with eq as my_eq } `, err: `rego_compile_error: with keyword replacing built-in function: replacement of "eq" invalid`, }, { note: "invalid target: rego.metadata.chain", module: `package test - p { true with rego.metadata.chain as [] } + p if { true with rego.metadata.chain as [] } `, err: `rego_compile_error: with keyword replacing built-in function: replacement of "rego.metadata.chain" invalid`, }, { note: "invalid target: rego.metadata.rule", module: `package test - p { true with rego.metadata.rule as {} } + p if { true with rego.metadata.rule as {} } `, err: `rego_compile_error: with keyword replacing built-in function: replacement of "rego.metadata.rule" invalid`, }, @@ -7586,7 +7623,7 @@ func TestCompilerMockFunction(t *testing.T) { note: "invalid target: internal.print", module: `package test my_print(_, _) - p { true with internal.print as my_print } + p if { true with internal.print as my_print } `, err: `rego_compile_error: with keyword replacing built-in function: replacement of internal function "internal.print" invalid`, }, @@ -7595,14 +7632,14 @@ func TestCompilerMockFunction(t *testing.T) { module: `package test mock(_) mock_mock(_) - p { bar(foo.bar("one")) with bar as mock with foo.bar as mock_mock } + p if { bar(foo.bar("one")) with bar as mock with foo.bar as mock_mock } `, }, { note: "non-built-in function replaced value", module: `package test original(_) - p { original(true) with original as 123 } + p if { original(true) with original as 123 } `, }, { @@ -7610,7 +7647,7 @@ func TestCompilerMockFunction(t *testing.T) { module: `package test original() = 1 mock() = 2 - p { original() with original as mock } + p if { original() with original as mock } `, err: "rego_type_error: undefined function data.test.original", // TODO(sr): file bug -- this doesn't depend on "with" used or not }, @@ -7619,14 +7656,14 @@ func TestCompilerMockFunction(t *testing.T) { module: `package test original(_) mock(_) - p { original(true) with original as mock } + p if { original(true) with original as mock } `, }, { note: "non-built-in function replaced by built-in", module: `package test original(_) - p { original([1]) with original as count } + p if { original([1]) with original as count } `, }, { @@ -7634,7 +7671,7 @@ func TestCompilerMockFunction(t *testing.T) { module: `package test original(_) mock(_, _) - p { original([1]) with original as mock } + p if { original([1]) with original as mock } `, err: "rego_type_error: data.test.original: arity mismatch\n\thave: (any, any)\n\twant: (any)", }, @@ -7642,7 +7679,7 @@ func TestCompilerMockFunction(t *testing.T) { note: "non-built-in function replaced by built-in, arity mismatch", module: `package test original(_) - p { original([1]) with original as concat } + p if { original([1]) with original as concat } `, err: "rego_type_error: data.test.original: arity mismatch\n\thave: (string, any)\n\twant: (any)", }, @@ -7661,9 +7698,9 @@ func TestCompilerMockFunction(t *testing.T) { }, }) if tc.extra != "" { - c.Modules["extra"] = MustParseModule(tc.extra) + c.Modules["extra"] = module(tc.extra) } - c.Modules["test"] = MustParseModule(tc.module) + c.Modules["test"] = module(tc.module) // NOTE(sr): We're running all compiler stages here, since the type checking of // built-in function replacements happens at the type check stage. @@ -7684,10 +7721,10 @@ func TestCompilerMockFunction(t *testing.T) { func TestCompilerMockVirtualDocumentPartially(t *testing.T) { c := NewCompiler() - c.Modules["test"] = MustParseModule(` + c.Modules["test"] = module(` package test p = {"a": 1} - q = x { p = x with p.a as 2 } + q = x if { p = x with p.a as 2 } `) compileStages(c, c.rewriteWithModifiers) @@ -7711,7 +7748,7 @@ func TestCompilerCheckUnusedAssignedVar(t *testing.T) { { note: "simple rule with wildcard", module: `package test - p { + p if { _ := 1 } `, @@ -7719,7 +7756,7 @@ func TestCompilerCheckUnusedAssignedVar(t *testing.T) { { note: "simple rule", module: `package test - p { + p if { x := 1 y := 2 z := x + 3 @@ -7733,7 +7770,7 @@ func TestCompilerCheckUnusedAssignedVar(t *testing.T) { { note: "rule with return", module: `package test - p = x { + p = x if { x := 2 y := 3 } @@ -7745,7 +7782,7 @@ func TestCompilerCheckUnusedAssignedVar(t *testing.T) { { note: "rule with function call", module: `package test - p { + p if { x := 2 y := f(x) } @@ -7757,7 +7794,7 @@ func TestCompilerCheckUnusedAssignedVar(t *testing.T) { { note: "rule with nested array comprehension", module: `package test - p { + p if { x := 2 y := [z | z := 2 * x] } @@ -7769,7 +7806,7 @@ func TestCompilerCheckUnusedAssignedVar(t *testing.T) { { note: "rule with nested array comprehension and shadowing", module: `package test - p { + p if { x := 2 y := [x | x := 2 * x] } @@ -7781,7 +7818,7 @@ func TestCompilerCheckUnusedAssignedVar(t *testing.T) { { note: "rule with nested array comprehension and shadowing (unused shadowed var)", module: `package test - p { + p if { x := 2 y := [x | x := 2] } @@ -7794,7 +7831,7 @@ func TestCompilerCheckUnusedAssignedVar(t *testing.T) { { note: "rule with nested array comprehension and shadowing (unused shadowing var)", module: `package test - p { + p if { x := 2 x > 1 [1 | x := 2] @@ -7807,7 +7844,7 @@ func TestCompilerCheckUnusedAssignedVar(t *testing.T) { { note: "rule with nested array comprehension and some declaration", module: `package test - p { + p if { some i _ := [z | z := [1, 2][i]] } @@ -7816,7 +7853,7 @@ func TestCompilerCheckUnusedAssignedVar(t *testing.T) { { note: "rule with nested set comprehension", module: `package test - p { + p if { x := 2 y := {z | z := 2 * x} } @@ -7828,7 +7865,7 @@ func TestCompilerCheckUnusedAssignedVar(t *testing.T) { { note: "rule with nested set comprehension and unused inner var", module: `package test - p { + p if { x := 2 y := {z | z := 2 * x; a := 2} } @@ -7840,7 +7877,7 @@ func TestCompilerCheckUnusedAssignedVar(t *testing.T) { { note: "rule with nested object comprehension", module: `package test - p { + p if { x := 2 y := {z: x | z := 2 * x} } @@ -7852,7 +7889,7 @@ func TestCompilerCheckUnusedAssignedVar(t *testing.T) { { note: "rule with nested closure", module: `package test - p { + p if { x := 1 a := 1 { y | y := [ z | z:=[1,2,3][a]; z > 1 ][_] } @@ -7865,7 +7902,7 @@ func TestCompilerCheckUnusedAssignedVar(t *testing.T) { { note: "rule with nested closure and unused inner var", module: `package test - p { + p if { x := 1 { y | y := [ z | z:=[1,2,3][x]; z > 1; a := 2 ][_] } } @@ -7877,7 +7914,7 @@ func TestCompilerCheckUnusedAssignedVar(t *testing.T) { { note: "simple function", module: `package test - f() { + f() if { x := 1 y := 2 } @@ -7890,7 +7927,7 @@ func TestCompilerCheckUnusedAssignedVar(t *testing.T) { { note: "simple function with wildcard", module: `package test - f() { + f() if { x := 1 _ := 2 } @@ -7902,7 +7939,7 @@ func TestCompilerCheckUnusedAssignedVar(t *testing.T) { { note: "function with return", module: `package test - f() = x { + f() = x if { x := 1 y := 2 } @@ -8106,7 +8143,7 @@ func TestCompilerCheckUnusedAssignedVar(t *testing.T) { { note: "every: unused assigned var in body", module: `package test - p { every i in [1] { y := 10; i == 1 } } + p if { every i in [1] { y := 10; i == 1 } } `, expectedErrors: Errors{ &Error{Message: "assigned var y unused"}, @@ -8115,7 +8152,7 @@ func TestCompilerCheckUnusedAssignedVar(t *testing.T) { { note: "general ref in rule head", module: `package test - p[q].r[s] := 1 { + p[q].r[s] := 1 if { q := "foo" s := "bar" t := "baz" @@ -8128,7 +8165,7 @@ func TestCompilerCheckUnusedAssignedVar(t *testing.T) { { note: "general ref in rule head (no errors)", module: `package test - p[q].r[s] := 1 { + p[q].r[s] := 1 if { q := "foo" s := "bar" } @@ -8140,9 +8177,8 @@ func TestCompilerCheckUnusedAssignedVar(t *testing.T) { makeTestRunner := func(tc testCase, strict bool) func(t *testing.T) { return func(t *testing.T) { compiler := NewCompiler().WithStrict(strict) - opts := ParserOptions{AllFutureKeywords: true, unreleasedKeywords: true} compiler.Modules = map[string]*Module{ - "test": MustParseModuleWithOpts(tc.module, opts), + "test": module(tc.module), } compileStages(compiler, compiler.rewriteLocalVars) @@ -8163,22 +8199,22 @@ func TestCompilerCheckUnusedAssignedVar(t *testing.T) { func TestCompilerSetGraph(t *testing.T) { c := NewCompiler() c.Modules = getCompilerTestModules() - c.Modules["elsekw"] = MustParseModule(` + c.Modules["elsekw"] = module(` package elsekw - p { + p if { false - } else = q { + } else = q if { false - } else { + } else if { r } q = true r = true - s { t } - t { false } else { true } + s if { t } + t if { false } else if { true } `) compileStages(c, c.setGraph) @@ -8283,14 +8319,14 @@ func TestCompilerSetGraph(t *testing.T) { func TestGraphCycle(t *testing.T) { mod1 := `package a.b.c - p { q } - q { r } - r { s } - s { q }` + p if { q } + q if { r } + r if { s } + s if { q }` c := NewCompiler() c.Modules = map[string]*Module{ - "mod1": MustParseModule(mod1), + "mod1": module(mod1), } compileStages(c, c.setGraph) @@ -8303,26 +8339,26 @@ func TestGraphCycle(t *testing.T) { elsekw := `package elsekw - p { + p if { false - } else = q { + } else = q if { true } - q { + q if { false - } else { + } else if { r } - r { s } + r if { s } - s { p } + s if { p } ` c = NewCompiler() c.Modules = map[string]*Module{ - "elsekw": MustParseModule(elsekw), + "elsekw": module(elsekw), } compileStages(c, c.setGraph) @@ -8338,105 +8374,105 @@ func TestGraphCycle(t *testing.T) { func TestCompilerCheckRecursion(t *testing.T) { c := NewCompiler() c.Modules = map[string]*Module{ - "newMod1": MustParseModule(`package rec - -s = true { t } -t = true { s } -a = true { b } -b = true { c } -c = true { d; e } -d = true { true } -e = true { a }`), - "newMod2": MustParseModule(`package rec - -x = true { s }`, + "newMod1": module(`package rec + +s = true if { t } +t = true if { s } +a = true if { b } +b = true if { c } +c = true if { d; e } +d = true if { true } +e = true if { a }`), + "newMod2": module(`package rec + +x = true if { s }`, ), - "newMod3": MustParseModule(`package rec2 + "newMod3": module(`package rec2 import data.rec.x -y = true { x }`), - "newMod4": MustParseModule(`package rec3 +y = true if { x }`), + "newMod4": module(`package rec3 -p[x] = y { data.rec4[x][y] = z }`, +p[x] = y if { data.rec4[x][y] = z }`, ), - "newMod5": MustParseModule(`package rec4 + "newMod5": module(`package rec4 import data.rec3.p -q[x] = y { p[x] = y }`), - "newMod6": MustParseModule(`package rec5 +q[x] = y if { p[x] = y }`), + "newMod6": module(`package rec5 -acp[x] { acq[x] } -acq[x] { a = [true | acp[_]]; a[_] = x } +acp contains x if { acq[x] } +acq contains x if { a = [true | acp[_]]; a[_] = x } `, ), - "newMod7": MustParseModule(`package rec6 + "newMod7": module(`package rec6 -np[x] = y { data.a[data.b.c[nq[x]]] = y } -nq[x] = y { data.d[data.e[x].f[np[y]]] }`, +np[x] = y if { data.a[data.b.c[nq[x]]] = y } +nq[x] = y if { data.d[data.e[x].f[np[y]]] }`, ), - "newMod8": MustParseModule(`package rec7 + "newMod8": module(`package rec7 -prefix = true { data.rec7 }`, +prefix = true if { data.rec7 }`, ), - "newMod9": MustParseModule(`package rec8 + "newMod9": module(`package rec8 -dataref = true { data }`, +dataref = true if { data }`, ), - "newMod10": MustParseModule(`package rec9 + "newMod10": module(`package rec9 - else_self { false } else { else_self } + else_self if { false } else if { else_self } - elsetop { + elsetop if { false - } else = elsemid { + } else = elsemid if { true } - elsemid { + elsemid if { false - } else { + } else if { elsebottom } - elsebottom { elsetop } + elsebottom if { elsetop } `), - "fnMod1": MustParseModule(`package f0 + "fnMod1": module(`package f0 - fn(x) = y { + fn(x) = y if { fn(x, y) }`), - "fnMod2": MustParseModule(`package f1 + "fnMod2": module(`package f1 - foo(x) = y { + foo(x) = y if { bar("buz", x, y) } - bar(x, y) = z { + bar(x, y) = z if { foo([x, y], z) }`), - "fnMod3": MustParseModule(`package f2 + "fnMod3": module(`package f2 - foo(x) = y { + foo(x) = y if { bar("buz", x, y) } - bar(x, y) = z { + bar(x, y) = z if { x = p[y] z = x } - p[x] = y { + p[x] = y if { x = "foo.bar" foo(x, y) }`), - "everyMod": MustParseModule(`package everymod + "everyMod": module(`package everymod import future.keywords.every - everyp { + everyp if { every x in [true, false] { x; everyp } } - everyq[1] { + everyq contains 1 if { every x in everyq { x == 1 } }`), } @@ -8505,19 +8541,19 @@ func TestCompilerCheckDynamicRecursion(t *testing.T) { }{ { note: "recursion", - mod: MustParseModule(` + mod: module(` package recursion pkg = "recursion" -foo[x] { +foo contains x if { data[pkg]["foo"][x] } `), err: "rego_recursion_error: rule data.recursion.foo is recursive: data.recursion.foo -> data.recursion.foo", }, {note: "system.main", - mod: MustParseModule(` + mod: module(` package system.main -foo { +foo if { data[input] } `), @@ -8546,21 +8582,21 @@ func TestCompilerCheckPartialRuleRecursion(t *testing.T) { policy := `package test # R1 -results[id] := 1 { +results[id] := 1 if { id := "bar" } # R2 -results.foo := 2 { +results.foo := 2 if { final_allow } # R3 -final_allow { +final_allow if { results.foo == 3 }` c := NewCompiler() - c.Modules = map[string]*Module{"test": MustParseModule(policy)} + c.Modules = map[string]*Module{"test": module(policy)} compileStages(c, c.checkRecursion) expected := Errors{ @@ -8579,9 +8615,9 @@ func TestCompilerCheckVoidCalls(t *testing.T) { }, }}) c.Compile(map[string]*Module{ - "test.rego": MustParseModule(`package test + "test.rego": module(`package test - p { + p if { x = test(true) }`), }) @@ -8596,10 +8632,10 @@ func TestCompilerGetRulesExact(t *testing.T) { mods := getCompilerTestModules() // Add incrementally defined rules. - mods["mod-incr"] = MustParseModule(`package a.b.c + mods["mod-incr"] = module(`package a.b.c -p[1] { true } -p[2] { true }`, +p contains 1 if { true } +p contains 2 if { true }`, ) c := NewCompiler() @@ -8655,10 +8691,10 @@ func TestCompilerGetRulesForVirtualDocument(t *testing.T) { mods := getCompilerTestModules() // Add incrementally defined rules. - mods["mod-incr"] = MustParseModule(`package a.b.c + mods["mod-incr"] = module(`package a.b.c -p[1] { true } -p[2] { true }`, +p contains 1 if { true } +p contains 2 if { true }`, ) c := NewCompiler() @@ -8718,11 +8754,11 @@ func TestCompilerGetRulesWithPrefix(t *testing.T) { mods := getCompilerTestModules() // Add incrementally defined rules. - mods["mod-incr"] = MustParseModule(`package a.b.c + mods["mod-incr"] = module(`package a.b.c -p[1] { true } -p[2] { true } -q[3] { true }`, +p contains 1 if { true } +p contains 2 if { true } +q contains 3 if { true }`, ) c := NewCompiler() @@ -8786,9 +8822,9 @@ func TestCompilerGetRules(t *testing.T) { compiler := getCompilerWithParsedModules(map[string]string{ "mod1": `package a.b.c -p[x] = y { q[x] = y } -q["a"] = 1 { true } -q["b"] = 2 { true }`, +p[x] = y if { q[x] = y } +q["a"] = 1 if { true } +q["b"] = 2 if { true }`, }) compileStages(compiler, nil) @@ -8845,9 +8881,9 @@ r3 = 3`, "hidden": `package system.hidden r4 = 4`, "mod4": `package b.c -r5[x] = 5 { x := "foo" } -r5.bar = 6 { input.x } -r5.baz = 7 { input.y } +r5[x] = 5 if { x := "foo" } +r5.bar = 6 if { input.x } +r5.baz = 7 if { input.y } `, }) @@ -8926,11 +8962,11 @@ func TestCompileCustomBuiltins(t *testing.T) { }) compiler.Compile(map[string]*Module{ - "test.rego": MustParseModule(` + "test.rego": module(` package test - p { baz("x") = x } - q { foo.bar("x") = x } + p if { baz("x") = x } + q if { foo.bar("x") = x } `), }) @@ -8958,11 +8994,11 @@ func TestCompileCustomBuiltins(t *testing.T) { } compiler.Compile(map[string]*Module{ - "test.rego": MustParseModule(` + "test.rego": module(` package test - p { baz(1) = x } # type error - q { foo.bar(1) = x } # type error + p if { baz(1) = x } # type error + q if { foo.bar(1) = x } # type error `), }) @@ -8990,38 +9026,38 @@ func TestCompilerLazyLoadingError(t *testing.T) { func TestCompilerLazyLoading(t *testing.T) { - mod1 := MustParseModule(`package a.b.c + mod1 := module(`package a.b.c import data.x.z1 as z2 -p = true { q; r } -q = true { z2 }`) +p = true if { q; r } +q = true if { z2 }`) orig1 := mod1.Copy() - mod2 := MustParseModule(`package a.b.c + mod2 := module(`package a.b.c -r = true { true }`) +r = true if { true }`) orig2 := mod2.Copy() - mod3 := MustParseModule(`package x + mod3 := module(`package x import data.foo.bar import input.input -z1 = true { [localvar | count(bar.baz.qux, localvar)] }`) +z1 = true if { [localvar | count(bar.baz.qux, localvar)] }`) orig3 := mod3.Copy() - mod4 := MustParseModule(`package foo.bar.baz + mod4 := module(`package foo.bar.baz -qux = grault { true }`) +qux = grault if { true }`) orig4 := mod4.Copy() - mod5 := MustParseModule(`package foo.bar.baz + mod5 := module(`package foo.bar.baz import data.d.e.f -deadbeef = f { true } -grault = deadbeef { true }`) +deadbeef = f if { true } +grault = deadbeef if { true }`) orig5 := mod5.Copy() // testLoader will return 4 rounds of parsed modules. @@ -9032,6 +9068,8 @@ grault = deadbeef { true }`) {"mod5": mod5}, } + popts := ParserOptions{AllFutureKeywords: true, unreleasedKeywords: true} + // For each round, run checks. tests := []func(map[string]*Module){ func(map[string]*Module) { @@ -9039,30 +9077,30 @@ grault = deadbeef { true }`) // collection. }, func(partial map[string]*Module) { - p := MustParseRule(`p = true { data.a.b.c.q; data.a.b.c.r }`) + p := MustParseRuleWithOpts(`p = true { data.a.b.c.q; data.a.b.c.r }`, popts) if !partial["mod1"].Rules[0].Equal(p) { t.Errorf("Expected %v but got %v", p, partial["mod1"].Rules[0]) } - q := MustParseRule(`q = true { data.x.z1 }`) + q := MustParseRuleWithOpts(`q = true { data.x.z1 }`, popts) if !partial["mod1"].Rules[1].Equal(q) { t.Errorf("Expected %v but got %v", q, partial["mod1"].Rules[0]) } }, func(partial map[string]*Module) { - z1 := MustParseRule(`z1 = true { [localvar | count(data.foo.bar.baz.qux, localvar)] }`) + z1 := MustParseRuleWithOpts(`z1 = true { [localvar | count(data.foo.bar.baz.qux, localvar)] }`, popts) if !partial["mod3"].Rules[0].Equal(z1) { t.Errorf("Expected %v but got %v", z1, partial["mod3"].Rules[0]) } }, func(partial map[string]*Module) { - qux := MustParseRule(`qux = grault { true }`) + qux := MustParseRuleWithOpts(`qux = grault { true }`, popts) if !partial["mod4"].Rules[0].Equal(qux) { t.Errorf("Expected %v but got %v", qux, partial["mod4"].Rules[0]) } }, func(partial map[string]*Module) { - grault := MustParseRule(`qux = data.foo.bar.baz.grault { true }`) // rewrite has not happened yet - f := MustParseRule(`deadbeef = data.d.e.f { true }`) + grault := MustParseRuleWithOpts(`qux = data.foo.bar.baz.grault { true }`, popts) // rewrite has not happened yet + f := MustParseRuleWithOpts(`deadbeef = data.d.e.f { true }`, popts) if !partial["mod4"].Rules[0].Equal(grault) { t.Errorf("Expected %v but got %v", grault, partial["mod4"].Rules[0]) } @@ -9099,7 +9137,7 @@ grault = deadbeef { true }`) func TestCompilerWithMetrics(t *testing.T) { m := metrics.New() c := NewCompiler().WithMetrics(m) - mod := MustParseModule(testModule) + mod := MustParseModuleWithOpts(testModule, ParserOptions{AllFutureKeywords: true}) c.Compile(map[string]*Module{"testMod": mod}) assertNotFailed(t, c) @@ -9118,7 +9156,7 @@ func TestCompilerWithStageAfterWithMetrics(t *testing.T) { c.WithMetrics(m) - mod := MustParseModule(testModule) + mod := MustParseModuleWithOpts(testModule, ParserOptions{AllFutureKeywords: true}) c.Compile(map[string]*Module{"testMod": mod}) assertNotFailed(t, c) @@ -9145,7 +9183,7 @@ func TestCompilerBuildComprehensionIndexKeySet(t *testing.T) { module: ` package test - p { + p if { value = input[i] keys = [j | value = input[j]] } @@ -9161,7 +9199,7 @@ func TestCompilerBuildComprehensionIndexKeySet(t *testing.T) { module: ` package test - p { + p if { v1 = input[i].v1 v2 = input[i].v2 keys = [j | v1 = input[j].v1; v2 = input[j].v2] @@ -9195,7 +9233,7 @@ func TestCompilerBuildComprehensionIndexKeySet(t *testing.T) { module: ` package test - p { + p if { [v | input[i] = v] # skip because no assignment }`, wantDebug: 0, @@ -9205,7 +9243,7 @@ func TestCompilerBuildComprehensionIndexKeySet(t *testing.T) { module: ` package test - p { + p if { v = input[i] ks = [j | input[j] = v] with data.x as 1 # skip because of with modifier }`, @@ -9216,7 +9254,7 @@ func TestCompilerBuildComprehensionIndexKeySet(t *testing.T) { module: ` package test - p { + p if { v = input[i] a = [] not a = [j | input[j] = v] # skip due to negation @@ -9228,7 +9266,7 @@ func TestCompilerBuildComprehensionIndexKeySet(t *testing.T) { module: ` package test - p { + p if { v = input[i] }`, wantDebug: 0, // nothing interesting to report here @@ -9238,7 +9276,7 @@ func TestCompilerBuildComprehensionIndexKeySet(t *testing.T) { module: ` package test - f(x) { + f(x) if { v = input[i] ys = [y | y = x[j]] # x is not safe }`, @@ -9249,7 +9287,7 @@ func TestCompilerBuildComprehensionIndexKeySet(t *testing.T) { module: ` package test - p { + p if { ys = [y | y = input[j]] }`, wantDebug: 1, @@ -9259,7 +9297,7 @@ func TestCompilerBuildComprehensionIndexKeySet(t *testing.T) { module: ` package test - p { + p if { x = input[i] # 'x' is a candidate for z (line 7) y = 2 # 'y' is a candidate for z z = [1 | @@ -9280,7 +9318,7 @@ func TestCompilerBuildComprehensionIndexKeySet(t *testing.T) { module: ` package test - f(x) { + f(x) if { y = input[x] ys = [y | y = input[x]] }`, @@ -9291,7 +9329,7 @@ func TestCompilerBuildComprehensionIndexKeySet(t *testing.T) { module: ` package test - p[x] { + p contains x if { y = input[x] ys = [y | y = input[x]] }`, @@ -9302,7 +9340,7 @@ func TestCompilerBuildComprehensionIndexKeySet(t *testing.T) { module: ` package test - p[x] { + p contains x if { y = input.bar[x] ys = [y | a = input.foo; walk(a, [x, y])] }`, @@ -9313,7 +9351,7 @@ func TestCompilerBuildComprehensionIndexKeySet(t *testing.T) { module: ` package test - p[x] { + p contains x if { y = input[x] ys = [y | y = input[z]; z = x] }`, @@ -9330,7 +9368,7 @@ func TestCompilerBuildComprehensionIndexKeySet(t *testing.T) { dbg := bytes.Buffer{} m := metrics.New() compiler := NewCompiler().WithMetrics(m).WithDebug(&dbg) - mod, err := ParseModule("test.rego", tc.module) + mod, err := ParseModuleWithOpts("test.rego", tc.module, ParserOptions{AllFutureKeywords: true, unreleasedKeywords: true}) if err != nil { t.Fatal(err) } @@ -9407,14 +9445,36 @@ func TestCompilerBuildRequiredCapabilities(t *testing.T) { keywords []string }{ { - note: "trivial", + note: "trivial v0", module: ` package x p { input > 7 } `, + opts: CompileOpts{ParserOptions: ParserOptions{RegoVersion: RegoV0}}, builtins: []string{"eq", "gt"}, }, + { + note: "trivial v1", + module: ` + package x + + p if { input > 7 } + `, + opts: CompileOpts{ParserOptions: ParserOptions{RegoVersion: RegoV1}}, + builtins: []string{"eq", "gt"}, + }, + { + note: "rego.v1 import", + module: ` + package x + + import rego.v1 + + p if { true } + `, + features: []string{"rego_v1_import"}, + }, { note: "future.keywords wildcard", module: ` @@ -9441,8 +9501,9 @@ func TestCompilerBuildRequiredCapabilities(t *testing.T) { module: ` package x - p { a := 7 } + p if { a := 7 } `, + opts: CompileOpts{ParserOptions: ParserOptions{RegoVersion: RegoV1}}, builtins: []string{"assign", "eq"}, }, { @@ -9450,8 +9511,9 @@ func TestCompilerBuildRequiredCapabilities(t *testing.T) { module: ` package x - p { input == 7 } + p if { input == 7 } `, + opts: CompileOpts{ParserOptions: ParserOptions{RegoVersion: RegoV1}}, builtins: []string{"eq", "equal"}, }, { @@ -9459,9 +9521,9 @@ func TestCompilerBuildRequiredCapabilities(t *testing.T) { module: ` package x - p { print(7) } + p if { print(7) } `, - opts: CompileOpts{EnablePrintStatements: true}, + opts: CompileOpts{EnablePrintStatements: true, ParserOptions: ParserOptions{RegoVersion: RegoV1}}, builtins: []string{"eq", "internal.print", "print"}, }, @@ -9470,9 +9532,9 @@ func TestCompilerBuildRequiredCapabilities(t *testing.T) { module: ` package x - p { print(7) } + p if { print(7) } `, - opts: CompileOpts{EnablePrintStatements: false}, + opts: CompileOpts{EnablePrintStatements: false, ParserOptions: ParserOptions{RegoVersion: RegoV1}}, builtins: []string{"print"}, // only print required because compiler will replace with true }, { @@ -9527,12 +9589,13 @@ func TestCompilerAllowMultipleAssignments(t *testing.T) { func TestQueryCompiler(t *testing.T) { tests := []struct { - note string - q string - pkg string - imports []string - input string - expected interface{} + note string + q string + pkg string + imports []string + input string + regoVersion RegoVersion + expected interface{} }{ { note: "empty query", @@ -9585,9 +9648,10 @@ func TestQueryCompiler(t *testing.T) { expected: fmt.Errorf("1 error occurred: 1:1: rego_unsafe_var_error: var z is unsafe"), }, { - note: "unsafe var that is a future keyword", - q: "1 in 2", - expected: fmt.Errorf("1 error occurred: 1:3: rego_unsafe_var_error: var in is unsafe (hint: `import future.keywords.in` to import a future keyword)"), + note: "unsafe var that is a future keyword", + q: "1 in 2", + expected: fmt.Errorf("1 error occurred: 1:3: rego_unsafe_var_error: var in is unsafe (hint: `import future.keywords.in` to import a future keyword)"), + regoVersion: RegoV0, }, { note: "unsafe declared var", @@ -9676,7 +9740,8 @@ func TestQueryCompiler(t *testing.T) { }, } for _, tc := range tests { - t.Run(tc.note, runQueryCompilerTest(tc.q, tc.pkg, tc.imports, tc.expected)) + popts := ParserOptions{RegoVersion: tc.regoVersion} + t.Run(tc.note, runQueryCompilerTest(tc.q, popts, tc.pkg, tc.imports, tc.expected)) } } @@ -9977,9 +10042,10 @@ func assertNotFailed(t *testing.T, c *Compiler) { func getCompilerWithParsedModules(mods map[string]string) *Compiler { parsed := map[string]*Module{} + popts := ParserOptions{AllFutureKeywords: true, unreleasedKeywords: true} for id, input := range mods { - mod, err := ParseModule(id, input) + mod, err := ParseModuleWithOpts(id, input, popts) if err != nil { panic(err) } @@ -10027,71 +10093,71 @@ func compileStages(c *Compiler, upto func()) { func getCompilerTestModules() map[string]*Module { mod1 := MustParseModule(`package a.b.c - +import rego.v1 import data.x.y.z as foo import data.g.h.k -p[x] { q[x]; not r[x] } -q[x] { foo[i] = x } -z = 400 { true }`, +p contains x if { q[x]; not r[x] } +q contains x if { foo[i] = x } +z = 400 if { true }`, ) mod2 := MustParseModule(`package a.b.c - +import rego.v1 import data.bar import data.x.y.p -r[x] { bar[x] = 100; p = 101 }`) +r contains x if { bar[x] = 100; p = 101 }`) mod3 := MustParseModule(`package a.b.d - +import rego.v1 import input.x as y -t = true { input = {y.secret: [{y.keyid}]} } -x = false { true }`) +t = true if { input = {y.secret: [{y.keyid}]} } +x = false if { true }`) mod4 := MustParseModule(`package a.b.empty`) mod5 := MustParseModule(`package a.b.compr - +import rego.v1 import input.x as y import data.a.b.c.q -p = true { [y.a | true] } -r = true { [q.a | true] } -s = true { [true | y.a = 0] } -t = true { [true | q[i] = 1] } -u = true { [true | _ = [y.a | true]] } -v = true { [true | _ = [true | q[i] = 1]] } +p = true if { [y.a | true] } +r = true if { [q.a | true] } +s = true if { [true | y.a = 0] } +t = true if { [true | q[i] = 1] } +u = true if { [true | _ = [y.a | true]] } +v = true if { [true | _ = [true | q[i] = 1]] } `, ) mod6 := MustParseModule(`package a.b.nested - +import rego.v1 import data.x import data.z import input.x as y -p = true { x[y[i].a[z.b[j]]] } -q = true { x = v; v[y[i]] } -r = 1 { true } -s = true { x[r] }`, +p = true if { x[y[i].a[z.b[j]]] } +q = true if { x = v; v[y[i]] } +r = 1 if { true } +s = true if { x[r] }`, ) mod7 := MustParseModule(`package a.b.funcs - -fn(x) = y { +import rego.v1 +fn(x) = y if { trim(x, ".", y) } -bar([x, y]) = [a, [b, c]] { +bar([x, y]) = [a, [b, c]] if { fn(x, a) y[1].b = b y[i].a = "hi" c = y[i].b } -foorule = true { +foorule = true if { bar(["hi.there", [{"a": "hi", "b": 1}, {"a": "bye", "b": 0}]], [a, [b, c]]) }`) @@ -10116,14 +10182,14 @@ func compilerErrsToStringSlice(errors []*Error) []string { return result } -func runQueryCompilerTest(q, pkg string, imports []string, expected interface{}) func(*testing.T) { +func runQueryCompilerTest(q string, popts ParserOptions, pkg string, imports []string, expected interface{}) func(*testing.T) { return func(t *testing.T) { t.Helper() c := NewCompiler().WithEnablePrintStatements(false) c.Compile(getCompilerTestModules()) assertNotFailed(t, c) qc := c.QueryCompiler() - query := MustParseBody(q) + query := MustParseBodyWithOpts(q, popts) var qctx *QueryContext if pkg != "" { @@ -10180,7 +10246,7 @@ func TestCompilerCapabilitiesFeatures(t *testing.T) { { note: "no features, general-ref-head rule", module: `package test - p[q].r[s] := 42 { q := "foo"; s := "bar" }`, + p[q].r[s] := 42 if { q := "foo"; s := "bar" }`, expectedErr: "rego_compile_error: rule heads with refs are not supported: p[q].r[s]", }, { @@ -10213,7 +10279,7 @@ func TestCompilerCapabilitiesFeatures(t *testing.T) { FeatureRefHeadStringPrefixes, }, module: `package test - p[q].r[s] := 42 { q := "foo"; s := "bar" }`, + p[q].r[s] := 42 if { q := "foo"; s := "bar" }`, expectedErr: "rego_type_error: rule heads with general refs (containing variables) are not supported: p[q].r[s]", }, { @@ -10222,7 +10288,7 @@ func TestCompilerCapabilitiesFeatures(t *testing.T) { FeatureRefHeads, }, module: `package test - p[q].r[s] := 42 { q := "foo"; s := "bar" }`, + p[q].r[s] := 42 if { q := "foo"; s := "bar" }`, }, { note: "string-prefix-ref-head & ref-head features, general-ref-head rule", @@ -10231,7 +10297,7 @@ func TestCompilerCapabilitiesFeatures(t *testing.T) { FeatureRefHeads, }, module: `package test - p[q].r[s] := 42 { q := "foo"; s := "bar" }`, + p[q].r[s] := 42 if { q := "foo"; s := "bar" }`, }, { note: "string-prefix-ref-head & ref-head features, ref-head rule", @@ -10304,7 +10370,7 @@ func TestCompilerCapabilitiesFeatures(t *testing.T) { capabilities.Features = tc.features compiler := NewCompiler().WithCapabilities(capabilities) - compiler.Compile(map[string]*Module{"test": MustParseModule(tc.module)}) + compiler.Compile(map[string]*Module{"test": module(tc.module)}) if tc.expectedErr != "" { if !compiler.Failed() { t.Fatal("expected error but got success") @@ -10335,12 +10401,12 @@ func TestCompilerCapabilitiesExtendedWithCustomBuiltins(t *testing.T) { }, }) - module1 := MustParseModule(`package test + module1 := module(`package test - p { foo(1); bar(2) }`) - module2 := MustParseModule(`package test + p if { foo(1); bar(2) }`) + module2 := module(`package test - p { plus(1,2,x) }`) + p if { plus(1,2,x) }`) compiler.Compile(map[string]*Module{"x": module1}) if compiler.Failed() { @@ -10370,8 +10436,8 @@ func TestCompilerWithUnsafeBuiltins(t *testing.T) { } // These modules should not compile for the same reason. - modules := map[string]*Module{"mod1": MustParseModule(`package a.b.c -deny { + modules := map[string]*Module{"mod1": module(`package a.b.c +deny if { re_match(input.user, ".*bob.*") }`)} compiler.Compile(modules) @@ -10423,15 +10489,15 @@ package policy default allow := false -allow { +allow if { input.identity = "foo" } -allow { +allow if { input.path = ["foo", "bar"] } -allow { +allow if { input.params = {"foo": "bar"} }` @@ -10440,7 +10506,7 @@ package policy default allow := false -allow { +allow if { input.identty = "foo" }` @@ -10449,7 +10515,7 @@ package policy default allow := false -allow { +allow if { input.path = "foo" }` @@ -10458,11 +10524,11 @@ package policy default allow := false -allow { +allow if { input.identty = "foo" } -allow { +allow if { input.path = "foo" }` @@ -10486,7 +10552,9 @@ allow { for i, module := range tc.modules { mod, err := ParseModuleWithOpts(fmt.Sprintf("test%d.rego", i+1), module, ParserOptions{ - ProcessAnnotation: true, + ProcessAnnotation: true, + AllFutureKeywords: true, + unreleasedKeywords: true, }) if err != nil { t.Fatal(err) @@ -10725,7 +10793,7 @@ func TestCompilerWithRecursiveSchema(t *testing.T) { # - input: schema.input package opa.recursion -deny { +deny if { input.Something.Y.X.Name == "Something" } ` @@ -10739,7 +10807,11 @@ deny { schemaSet.Put(MustParseRef("schema.input"), schema) c.WithSchemas(schemaSet) - m := MustParseModuleWithOpts(exampleModule, ParserOptions{ProcessAnnotation: true}) + m := MustParseModuleWithOpts(exampleModule, ParserOptions{ + ProcessAnnotation: true, + AllFutureKeywords: true, + unreleasedKeywords: true, + }) c.Compile(map[string]*Module{"testMod": m}) if c.Failed() { t.Errorf("Expected compilation to succeed, but got errors: %v", c.Errors) @@ -10784,7 +10856,7 @@ func TestCompilerWithRecursiveSchemaAndInvalidSource(t *testing.T) { # - input: schema.input package opa.recursion -deny { +deny if { input.Something.Y.X.ThisDoesNotExist == "Something" } ` @@ -10799,7 +10871,11 @@ deny { schemaSet.Put(MustParseRef("schema.input"), schema) c.WithSchemas(schemaSet) - m := MustParseModuleWithOpts(exampleModule, ParserOptions{ProcessAnnotation: true}) + m := MustParseModuleWithOpts(exampleModule, ParserOptions{ + ProcessAnnotation: true, + AllFutureKeywords: true, + unreleasedKeywords: true, + }) c.Compile(map[string]*Module{"testMod": m}) if !c.Failed() { t.Errorf("Expected compilation to fail, but it succeeded") @@ -10821,6 +10897,20 @@ func modules(ms ...string) []*Module { return mods } +// FIXME(v1-test-refactor): In OPA 1.0, a call to here can be replaced with a call to MustParseModule. +func module(raw string, opts ...func(ParserOptions) ParserOptions) *Module { + popts := ParserOptions{ + AllFutureKeywords: true, + unreleasedKeywords: true, + } + + for _, opt := range opts { + popts = opt(popts) + } + + return MustParseModuleWithOpts(raw, popts) +} + func TestCompilerWithRecursiveSchemaAvoidRace(t *testing.T) { jsonSchema := `{ @@ -10909,7 +10999,7 @@ func TestCompilerWithRecursiveSchemaAvoidRace(t *testing.T) { # - input: schema.input package race.condition -deny { +deny if { queue := input.aws.sqs.queues[_] policy := queue.policies[_] doc := json.unmarshal(policy.document.value) @@ -10928,7 +11018,11 @@ deny { schemaSet.Put(MustParseRef("schema.input"), schema) c.WithSchemas(schemaSet) - m := MustParseModuleWithOpts(exampleModule, ParserOptions{ProcessAnnotation: true}) + m := MustParseModuleWithOpts(exampleModule, ParserOptions{ + ProcessAnnotation: true, + AllFutureKeywords: true, + unreleasedKeywords: true, + }) c.Compile(map[string]*Module{"testMod": m}) if c.Failed() { t.Fatal(c.Errors) @@ -10945,7 +11039,6 @@ func TestCompilerRewriteTestRulesForTracing(t *testing.T) { { note: "ref comparison, no rewrite", module: `package test -import rego.v1 a := 1 b := 2 @@ -10955,10 +11048,10 @@ test_something if { }`, exp: `package test -a := 1 { true } -b := 2 { true } +a := 1 if { true } +b := 2 if { true } -test_something = true { +test_something = true if { data.test.a = data.test.b }`, }, @@ -10966,7 +11059,6 @@ test_something = true { note: "ref comparison, rewrite", rewrite: true, module: `package test -import rego.v1 a := 1 b := 2 @@ -10978,10 +11070,10 @@ test_something if { // accessible by the tracer. exp: `package test -a := 1 { true } -b := 2 { true } +a := 1 if { true } +b := 2 if { true } -test_something = true { +test_something = true if { __local0__ = data.test.a __local1__ = data.test.b __local0__ = __local1__ @@ -10991,7 +11083,6 @@ test_something = true { note: "ref comparison, not-stmt, rewrite", rewrite: true, module: `package test -import rego.v1 a := 1 b := 2 @@ -11002,17 +11093,16 @@ test_something if { // We don't break out local vars from a not-stmt, as that would change the semantics of the rule. exp: `package test -a := 1 { true } -b := 2 { true } +a := 1 if { true } +b := 2 if { true } -test_something = true { +test_something = true if { not data.test.a = data.test.b }`, }, { note: "ref comparison, inside every-stmt, no rewrite", module: `package test -import rego.v1 a := 1 b := 2 @@ -11024,13 +11114,12 @@ test_something if { } }`, exp: `package test -import future.keywords -a := 1 { true } -b := 2 { true } -l := [1, 2, 3] { true } +a := 1 if { true } +b := 2 if { true } +l := [1, 2, 3] if { true } -test_something = true { +test_something = true if { __local2__ = data.test.l every __local0__, __local1__ in __local2__ { __local4__ = data.test.b @@ -11044,7 +11133,6 @@ test_something = true { note: "ref comparison, inside every-stmt, rewrite", rewrite: true, module: `package test -import rego.v1 a := 1 b := 2 @@ -11058,13 +11146,12 @@ test_something if { // When tests contain an 'every' statement, we're interested in the circumstances that made the every fail, // so it's body is rewritten. exp: `package test -import future.keywords -a := 1 { true } -b := 2 { true } -l := [1, 2, 3] { true } +a := 1 if { true } +b := 2 if { true } +l := [1, 2, 3] if { true } -test_something = true { +test_something = true if { __local2__ = data.test.l; every __local0__, __local1__ in __local2__ { __local4__ = data.test.b @@ -11088,7 +11175,7 @@ test_something = true { assertNotFailed(t, c) result := c.Modules["test.rego"] - exp := MustParseModule(tc.exp) + exp := module(tc.exp) exp.Imports = nil // We strip the imports since the compiler will too if result.Compare(exp) != 0 { t.Fatalf("\nExpected:\n\n%v\n\nGot:\n\n%v", exp, result) diff --git a/ast/example_test.go b/ast/example_test.go index b50062dc3e..c529a49308 100644 --- a/ast/example_test.go +++ b/ast/example_test.go @@ -15,11 +15,12 @@ func ExampleCompiler_Compile() { // Define an input module that will be compiled. exampleModule := `package opa.example +import rego.v1 import data.foo import input.bar -p[x] { foo[x]; not bar[x]; x >= min_x } -min_x = 100 { true }` +p[x] if { foo[x]; not bar[x]; x >= min_x } +min_x = 100 if { true }` // Parse the input module to obtain the AST representation. mod, err := ast.ParseModule("my_module", exampleModule) @@ -56,11 +57,12 @@ func ExampleQueryCompiler_Compile() { // Define an input module that will be compiled. exampleModule := `package opa.example +import rego.v1 import data.foo import input.bar -p[x] { foo[x]; not bar[x]; x >= min_x } -min_x = 100 { true }` +p[x] if { foo[x]; not bar[x]; x >= min_x } +min_x = 100 if { true }` // Parse the input module to obtain the AST representation. mod, err := ast.ParseModule("my_module", exampleModule) diff --git a/ast/index_test.go b/ast/index_test.go index 5026c2c16f..f5b6a3549c 100644 --- a/ast/index_test.go +++ b/ast/index_test.go @@ -53,7 +53,7 @@ func TestBaseDocEqIndexing(t *testing.T) { } everyMod := MustParseModuleWithOpts(`package test - p { every _ in [] { input.a = 1 } }`, opts) + p if { every _ in [] { input.a = 1 } }`, opts) // NOTE(sr): This looks a bit silly; but it's what // @@ -61,9 +61,9 @@ func TestBaseDocEqIndexing(t *testing.T) { // // will get rewritten to -- so to assert that the domain of 'every' expressions // get respected in the rule indexing, we'll need to provide this "pseudo-compiled" - // module source here. + // mod source here. everyModWithDomain := MustParseModuleWithOpts(`package test - p { + p if { __local0__ = input.a every x in __local0__ { input.x = x } } { @@ -84,10 +84,10 @@ func TestBaseDocEqIndexing(t *testing.T) { # ref.multi.value.key[k] contains v if { k := input.k; v := input.v } # not supported yet `, opts) - module := MustParseModule(` + mod := module(` package test - exact { + exact if { input.x = 1 input.y = 2 } { @@ -96,7 +96,7 @@ func TestBaseDocEqIndexing(t *testing.T) { } - scalars { + scalars if { input.x = 0 input.y = 1 } { @@ -109,7 +109,7 @@ func TestBaseDocEqIndexing(t *testing.T) { input.x = 2 } - vars { + vars if { input.x = 1 input.y = 2 } { @@ -120,7 +120,7 @@ func TestBaseDocEqIndexing(t *testing.T) { input.z = 5 } - composite_arr { + composite_arr if { input.x = 1 input.y = [1,2,3] input.z = 1 @@ -137,11 +137,11 @@ func TestBaseDocEqIndexing(t *testing.T) { input.y = [1,[2,3],4] } - composite_obj { + composite_obj if { input.y = {"foo": "bar", "bar": x} } - equal { + equal if { input.x == 1 } { input.x == 2 @@ -150,7 +150,7 @@ func TestBaseDocEqIndexing(t *testing.T) { } # filtering ruleset contains rules that cannot be indexed (for different reasons). - filtering { + filtering if { count([], x) } { not input.x = 0 @@ -171,13 +171,13 @@ func TestBaseDocEqIndexing(t *testing.T) { # exercise default keyword default allow = false - allow { + allow if { input.x = 1 } { input.x = 0 } - glob_match { + glob_match if { x = input.x glob.match("foo:*:bar", [":"], x) } { @@ -191,40 +191,40 @@ func TestBaseDocEqIndexing(t *testing.T) { glob.match("dead:*:beef", [":"], x) } - glob_match_mappers { + glob_match_mappers if { input.x = x glob.match("foo:*", [":"], x) } - glob_match_mappers { + glob_match_mappers if { input.x = x } - glob_match_mappers_non_mapped_match { + glob_match_mappers_non_mapped_match if { input.x = "/bar" } - glob_match_mappers_non_mapped_match { + glob_match_mappers_non_mapped_match if { input.x = x glob.match("bar", ["/"], x) } - glob_match_overlapped_mappers { + glob_match_overlapped_mappers if { input.x = x glob.match("foo:*", [":"], x) } - glob_match_overlapped_mappers { + glob_match_overlapped_mappers if { input.x = x glob.match("foo/*", ["/"], x) } - glob_match_disjoint_mappers { + glob_match_disjoint_mappers if { input.x = x glob.match("foo:*", [":"], x) } - glob_match_disjoint_mappers { + glob_match_disjoint_mappers if { input.x = x glob.match("bar/*", ["/"], x) } @@ -247,7 +247,7 @@ func TestBaseDocEqIndexing(t *testing.T) { ruleset: "exact", input: `{"x": 3, "y": 4}`, expectedRS: []string{ - `exact { input.x = 3; input.y = 4 }`, + `exact if { input.x = 3; input.y = 4 }`, }, checkResult: expectOnlyGroundRefs(true), // covering base case }, @@ -256,30 +256,30 @@ func TestBaseDocEqIndexing(t *testing.T) { ruleset: "scalars", input: `{"x": 2, "y": 2}`, expectedRS: []string{ - `scalars { input.x = 2 }`}, + `scalars if { input.x = 2 }`}, }, { note: "disjoint match", ruleset: "scalars", input: `{"x": 2, "y": 2, "z": 2}`, expectedRS: []string{ - `scalars { input.x = 2 }`, - `scalars { input.y = 2; input.z = 2}`}, + `scalars if { input.x = 2 }`, + `scalars if { input.y = 2; input.z = 2}`}, }, { note: "ordering match", ruleset: "scalars", input: `{"x": 0, "y": 1}`, expectedRS: []string{ - `scalars { input.x = 0; input.y = 1 }`, - `scalars { 1 = input.y; input.x = 0 }`}, + `scalars if { input.x = 0; input.y = 1 }`, + `scalars if { 1 = input.y; input.x = 0 }`}, }, { note: "type no match", ruleset: "vars", input: `{"y": 3, "x": {1,2,3}}`, expectedRS: []string{ - `vars { input.x = x; input.y = 3 }`, + `vars if { input.x = x; input.y = 3 }`, }, }, { @@ -287,7 +287,7 @@ func TestBaseDocEqIndexing(t *testing.T) { ruleset: "vars", input: `{"x": 1, "y": 3}`, expectedRS: []string{ - `vars { input.x = x; input.y = 3 }`, + `vars if { input.x = x; input.y = 3 }`, }, }, { @@ -295,8 +295,8 @@ func TestBaseDocEqIndexing(t *testing.T) { ruleset: "vars", input: `{"x": 4, "z": 5, "y": 3}`, expectedRS: []string{ - `vars { input.x = x; input.y = 3 }`, - `vars { input.x = 4; input.z = 5 }`, + `vars if { input.x = x; input.y = 3 }`, + `vars if { input.x = 4; input.z = 5 }`, }, }, { @@ -308,8 +308,8 @@ func TestBaseDocEqIndexing(t *testing.T) { "z": 1, }`, expectedRS: []string{ - `composite_arr { input.x = 1; input.y = [1,2,3]; input.z = 1 }`, - `composite_arr { input.y = [1,[2,3],4] }`, + `composite_arr if { input.x = 1; input.y = [1,2,3]; input.z = 1 }`, + `composite_arr if { input.y = [1,[2,3],4] }`, }, }, { @@ -320,8 +320,8 @@ func TestBaseDocEqIndexing(t *testing.T) { "y": [1,2,4,5], }`, expectedRS: []string{ - `composite_arr { input.x = 1; input.y = [1,2,4,x] }`, - `composite_arr { input.y = [1,[2,3],4] }`, + `composite_arr if { input.x = 1; input.y = [1,2,4,x] }`, + `composite_arr if { input.y = [1,[2,3],4] }`, }, }, { @@ -333,9 +333,9 @@ func TestBaseDocEqIndexing(t *testing.T) { "z": 3, }`, expectedRS: []string{ - `composite_arr { input.x = 1; input.y = [1,2,4,x] }`, - `composite_arr { input.y = [1,2,y,5]; input.z = 3 }`, - `composite_arr { input.y = [1,[2,3],4] }`, + `composite_arr if { input.x = 1; input.y = [1,2,4,x] }`, + `composite_arr if { input.y = [1,2,y,5]; input.z = 3 }`, + `composite_arr if { input.y = [1,[2,3],4] }`, }, }, { @@ -346,7 +346,7 @@ func TestBaseDocEqIndexing(t *testing.T) { "y": [1,[2,3],4], }`, expectedRS: []string{ - `composite_arr { input.y = [1,[2,3],4] }`, + `composite_arr if { input.y = [1,[2,3],4] }`, }, }, { @@ -354,8 +354,8 @@ func TestBaseDocEqIndexing(t *testing.T) { ruleset: "composite_arr", input: `{"y": []}`, expectedRS: []string{ - `composite_arr { input.y = [] }`, - `composite_arr { input.y = [1,[2,3],4] }`, + `composite_arr if { input.y = [] }`, + `composite_arr if { input.y = [1,[2,3],4] }`, }, }, { @@ -363,7 +363,7 @@ func TestBaseDocEqIndexing(t *testing.T) { ruleset: "composite_obj", input: `{"y": {"foo": "bar", "bar": "baz"}}`, expectedRS: []string{ - `composite_obj { input.y = {"foo": "bar", "bar": x} }`, + `composite_obj if { input.y = {"foo": "bar", "bar": x} }`, }, }, { @@ -371,8 +371,8 @@ func TestBaseDocEqIndexing(t *testing.T) { ruleset: "equal", input: `{"x": 2, "y": 3}`, expectedRS: []string{ - "equal { input.y == 3 }", - "equal { input.x == 2 }", + "equal if { input.y == 3 }", + "equal if { input.x == 2 }", }, }, { @@ -399,41 +399,41 @@ func TestBaseDocEqIndexing(t *testing.T) { note: "match and non-indexable rules", ruleset: "filtering", input: `{"x": 1}`, - expectedRS: module.RuleSet(Var("filtering")), + expectedRS: mod.RuleSet(Var("filtering")), }, { note: "non-indexable rules", ruleset: "filtering", input: `{}`, - expectedRS: module.RuleSet(Var("filtering")).Diff(NewRuleSet(MustParseRule(`filtering { input.x = 1 }`))), + expectedRS: mod.RuleSet(Var("filtering")).Diff(NewRuleSet(MustParseRuleWithOpts(`filtering if { input.x = 1 }`, opts))), }, { note: "unknown: all", ruleset: "composite_arr", unknowns: []string{`input.x`, `input.y`, `input.z`}, - expectedRS: module.RuleSet(Var("composite_arr")), + expectedRS: mod.RuleSet(Var("composite_arr")), }, { note: "unknown: partial", ruleset: "composite_arr", unknowns: []string{`input.x`, `input.y`}, input: `{"z": 3}`, - expectedRS: module.RuleSet(Var("composite_arr")).Diff(NewRuleSet(MustParseRule(`composite_arr { + expectedRS: mod.RuleSet(Var("composite_arr")).Diff(NewRuleSet(MustParseRuleWithOpts(`composite_arr if { input.x = 1 input.y = [1,2,3] input.z = 1 - }`))), + }`, opts))), }, { note: "glob.match", ruleset: "glob_match", input: `{"x": "foo:1234:bar"}`, expectedRS: []string{` - glob_match { + glob_match if { x = input.x glob.match("foo:*:bar", [":"], x) }`, ` - glob_match { + glob_match if { x = input.x glob.match("foo:*:*", [":"], x) }`}, @@ -444,13 +444,13 @@ func TestBaseDocEqIndexing(t *testing.T) { input: `{"x": "foo:bar"}`, expectedRS: []string{ ` - glob_match_mappers { + glob_match_mappers if { input.x = x glob.match("foo:*", [":"], x) } `, ` - glob_match_mappers { + glob_match_mappers if { input.x = x } `}, @@ -460,7 +460,7 @@ func TestBaseDocEqIndexing(t *testing.T) { ruleset: "glob_match_mappers_non_mapped_match", input: `{"x": "/bar"}`, expectedRS: []string{ - `glob_match_mappers_non_mapped_match { + `glob_match_mappers_non_mapped_match if { input.x = "/bar" }`}, }, @@ -481,12 +481,12 @@ func TestBaseDocEqIndexing(t *testing.T) { input: `{"x": "foo:bar"}`, expectedRS: []string{ ` - glob_match_overlapped_mappers { + glob_match_overlapped_mappers if { input.x = x glob.match("foo:*", [":"], x) } `, ` - glob_match_overlapped_mappers { + glob_match_overlapped_mappers if { input.x = x glob.match("foo/*", ["/"], x) } @@ -498,7 +498,7 @@ func TestBaseDocEqIndexing(t *testing.T) { ruleset: "glob_match_disjoint_mappers", input: `{"x": "foo:bar"}`, expectedRS: []string{ - `glob_match_disjoint_mappers { input.x = x; glob.match("foo:*", [":"], x) }`, + `glob_match_disjoint_mappers if { input.x = x; glob.match("foo:*", [":"], x) }`, }, }, { @@ -509,25 +509,25 @@ func TestBaseDocEqIndexing(t *testing.T) { }, { note: "glob.match: do not index captured output", - module: MustParseModule(`package test - p { x = input.x; glob.match("/a/*/c", ["/"], x, false) } + module: module(`package test + p if { x = input.x; glob.match("/a/*/c", ["/"], x, false) } `), ruleset: "p", input: `{"x": "wrong"}`, expectedRS: []string{ - `p { x = input.x; glob.match("/a/*/c", ["/"], x, false) }`, + `p if { x = input.x; glob.match("/a/*/c", ["/"], x, false) }`, }, }, { note: "functions: args match", - module: MustParseModule(`package test - f(x) = y { + module: module(`package test + f(x) = y if { input.a = "foo" x = 10 y := 10 } - f(x) = 12 { x = 11 } - f(x) = x+1 { + f(x) = 12 if { x = 11 } + f(x) = x+1 if { input.a = x x != 10 x != 11 @@ -536,20 +536,20 @@ func TestBaseDocEqIndexing(t *testing.T) { input: `{"a": "foo"}`, args: []Value{Number("11")}, expectedRS: []string{ - `f(x) = 12 { x = 11 } `, - `f(x) = plus(x, 1) { input.a = x; neq(x, 10); neq(x, 11) }`, // neq not respected in index + `f(x) = 12 if { x = 11 } `, + `f(x) = plus(x, 1) if { input.a = x; neq(x, 10); neq(x, 11) }`, // neq not respected in index }, }, { note: "functions: input + args match", - module: MustParseModule(`package test - f(x) = y { + module: module(`package test + f(x) = y if { input.a = "foo" x = 10 y := 10 } - f(x) = 12 { x = 11 } - f(x) = x+1 { + f(x) = 12 if { x = 11 } + f(x) = x+1 if { input.a = x x != 10 x != 11 @@ -558,19 +558,19 @@ func TestBaseDocEqIndexing(t *testing.T) { input: `{"a": "foo"}`, args: []Value{Number("10")}, expectedRS: []string{ - `f(x) = y { input.a = "foo"; x = 10; assign(y, 10) }`, - `f(x) = plus(x, 1) { input.a = x; neq(x, 10); neq(x, 11) }`, // neq not respected in index + `f(x) = y if { input.a = "foo"; x = 10; assign(y, 10) }`, + `f(x) = plus(x, 1) if { input.a = x; neq(x, 10); neq(x, 11) }`, // neq not respected in index }, }, { note: "functions: multiple args, each matches", - module: MustParseModule(`package test - g(x, y) = z { + module: module(`package test + g(x, y) = z if { x = 12 y = "monkeys" z = 1 } - g(a, b) = c { + g(a, b) = c if { a = "a" b = "b" c = "c" @@ -578,83 +578,83 @@ func TestBaseDocEqIndexing(t *testing.T) { ruleset: "g", args: []Value{Number("12"), StringTerm("monkeys").Value}, expectedRS: []string{ - `g(x, y) = z { x = 12; y = "monkeys"; z = 1 }`, + `g(x, y) = z if { x = 12; y = "monkeys"; z = 1 }`, }, }, { note: "functions: glob.match in function, arg matching first glob", - module: MustParseModule(`package test - glob_f(a) = true { + module: module(`package test + glob_f(a) = true if { glob.match("foo:*", [":"], a) } - glob_f(a) = true { + glob_f(a) = true if { glob.match("baz:*", [":"], a) } - glob_f(a) = true { + glob_f(a) = true if { a = 12 }`), ruleset: "glob_f", args: []Value{StringTerm("foo:bar").Value}, expectedRS: []string{ - `glob_f(a) = true { glob.match("foo:*", [":"], a) }`, + `glob_f(a) = true if { glob.match("foo:*", [":"], a) }`, }, }, { note: "functions: glob.match in function, arg matching second glob", - module: MustParseModule(`package test - glob_f(a) = true { + module: module(`package test + glob_f(a) = true if { glob.match("foo:*", [":"], a) } - glob_f(a) = true { + glob_f(a) = true if { glob.match("baz:*", [":"], a) } - glob_f(a) = true { + glob_f(a) = true if { a = 12 }`), ruleset: "glob_f", args: []Value{StringTerm("baz:bar").Value}, expectedRS: []string{ - `glob_f(a) = true { glob.match("baz:*", [":"], a) }`, + `glob_f(a) = true if { glob.match("baz:*", [":"], a) }`, }, }, { note: "functions: glob.match in function, arg matching non-glob rule", - module: MustParseModule(`package test - glob_f(a) = true { + module: module(`package test + glob_f(a) = true if { glob.match("baz:*", [":"], a) } - glob_f(a) = true { + glob_f(a) = true if { a = 12 }`), ruleset: "glob_f", args: []Value{Number("12")}, expectedRS: []string{ - `glob_f(a) = true { a = 12 }`, + `glob_f(a) = true if { a = 12 }`, }, }, { note: "functions: multiple outputs for same inputs", - module: MustParseModule(`package test - f(x) = y { a = x; equal(a, 1, r); y = r } - f(x) = y { a = x; equal(a, 2, r); y = r }`), + module: module(`package test + f(x) = y if { a = x; equal(a, 1, r); y = r } + f(x) = y if { a = x; equal(a, 2, r); y = r }`), ruleset: "f", input: `{}`, args: []Value{Number("1")}, expectedRS: []string{ - `f(x) = y { a = x; equal(a, 1, r); y = r }`, - `f(x) = y { a = x; equal(a, 2, r); y = r }`, + `f(x) = y if { a = x; equal(a, 1, r); y = r }`, + `f(x) = y if { a = x; equal(a, 2, r); y = r }`, }, }, { note: "functions: do not index equal(x,y,z)", - module: MustParseModule(`package test - f(x) = y { equal(x, 1, z); y = z } + module: module(`package test + f(x) = y if { equal(x, 1, z); y = z } `), ruleset: "f", input: `{}`, args: []Value{Number("2")}, expectedRS: []string{ - `f(x) = y { equal(x, 1, z); y = z }`, + `f(x) = y if { equal(x, 1, z); y = z }`, }, }, { @@ -705,7 +705,7 @@ func TestBaseDocEqIndexing(t *testing.T) { }, // { // note: "ref: multi value, var in ref", - // module: refMod, + // mod: refMod, // ruleRef: MustParseRef("ref.multi.value.key[k]"), // input: `{"k": 1, "v": 2}`, // expectedRS: RuleSet([]*Rule{refMod.Rules[3]}), @@ -714,12 +714,12 @@ func TestBaseDocEqIndexing(t *testing.T) { for _, tc := range tests { t.Run(tc.note, func(t *testing.T) { - module := module + mod := mod if tc.module != nil { - module = tc.module + mod = tc.module } rules := []*Rule{} - for _, rule := range module.Rules { + for _, rule := range mod.Rules { if tc.ruleRef == nil { if rule.Head.Name == Var(tc.ruleset) { rules = append(rules, rule) @@ -744,7 +744,7 @@ func TestBaseDocEqIndexing(t *testing.T) { switch e := tc.expectedRS.(type) { case []string: for _, r := range e { - expectedRS.Add(MustParseRule(r)) + expectedRS.Add(MustParseRuleWithOpts(r, opts)) } case RuleSet: expectedRS = e @@ -796,25 +796,25 @@ func TestBaseDocEqIndexing(t *testing.T) { func TestBaseDocEqIndexingPriorities(t *testing.T) { - module := MustParseModule(` + module := module(` package test - p { # r1 + p if { # r1 false - } else { # r2 + } else if { # r2 input.x = "x1" input.y = "y1" - } else { # r3 + } else if { # r3 input.z = "z1" } - p { # r4 + p if { # r4 input.x = "x1" } - p { # r5 + p if { # r5 input.z = "z2" - } else { # r6 + } else if { # r6 input.z = "z1" } `) @@ -865,10 +865,10 @@ func TestBaseDocEqIndexingErrors(t *testing.T) { return false }) - module := MustParseModule(` + module := module(` package ex - p { input.raise_error = 1 }`) + p if { input.raise_error = 1 }`) if !index.Build(module.Rules) { t.Fatalf("Expected index to build") @@ -937,21 +937,21 @@ func TestSplitStringEscaped(t *testing.T) { } func TestGetAllRules(t *testing.T) { - module := MustParseModule(` + module := module(` package test default p = 42 - p { + p if { input.x = "x1" input.y = "y1" - } else { + } else if { true - } else { + } else if { input.z = "z1" } - p { + p if { input.z = "z1" } `) @@ -992,19 +992,19 @@ func TestGetAllRules(t *testing.T) { func TestSkipIndexing(t *testing.T) { - module := MustParseModule(`package test + module := module(`package test - p { + p if { internal.print("here") input.foo = 7 - } else = false { + } else = false if { input.bar = 8 - } else = true { + } else = true if { internal.print("here 2") input.bar = 9 } - p { + p if { input.foo = 9 }`) @@ -1050,11 +1050,11 @@ func TestBaseDocIndexResultEarlyExit(t *testing.T) { { note: "single rule", expectedEE: true, - module: MustParseModule(`package test -r { + module: module(`package test +r if { input.x = 1 } -r = 3 { +r = 3 if { input.y = 2 }`), input: `{"x": 1}`, @@ -1066,11 +1066,11 @@ r = 3 { note: "no early exit: two rules, indexing disabled", disableIndexing: true, expectedEE: false, - module: MustParseModule(`package test -r { + module: module(`package test +r if { input.x = 1 } -r = 3 { +r = 3 if { input.y = 2 }`), input: `{"x": 1}`, @@ -1083,11 +1083,11 @@ r = 3 { note: "two rules, indexing disabled", disableIndexing: true, expectedEE: true, - module: MustParseModule(`package test -r { + module: module(`package test +r if { input.x = 1 } -r { +r if { input.y = 2 }`), input: `{"x": 1}`, @@ -1099,11 +1099,11 @@ r { { note: "no early exit: different constant value", expectedEE: false, - module: MustParseModule(`package test -r { + module: module(`package test +r if { input.x = 1 } -r = 2 { +r = 2 if { input.x = 1 input.y = 2 }`), @@ -1116,11 +1116,11 @@ r = 2 { { note: "same constant value", expectedEE: true, - module: MustParseModule(`package test -r { + module: module(`package test +r if { input.x = 1 } -r { +r if { input.y = 1 }`), input: `{"x": 1, "y": 1}`, @@ -1128,11 +1128,11 @@ r { { note: "no early exit: one rule with with non-constant value", expectedEE: false, - module: MustParseModule(`package test -r { + module: module(`package test +r if { input.x = 1 } -r = x { +r = x if { input.y = 1 x = "foo" }`), @@ -1145,11 +1145,11 @@ r = x { { note: "same ref value (input)", expectedEE: true, - module: MustParseModule(`package test -r = input.a { + module: module(`package test +r = input.a if { input.x = 1 } -r = input.a { +r = input.a if { input.y = 1 }`), input: `{"x": 1, "y": 1}`, @@ -1157,11 +1157,11 @@ r = input.a { { note: "same ref value (data)", expectedEE: true, - module: MustParseModule(`package test -r = data.a { + module: module(`package test +r = data.a if { input.x = 1 } -r = data.a { +r = data.a if { input.y = 1 }`), input: `{"x": 1, "y": 1}`, @@ -1169,14 +1169,14 @@ r = data.a { { note: "else: same constant value", expectedEE: true, - module: MustParseModule(`package test -r { + module: module(`package test +r if { input.x = 1 } -else { +else if { true } -r { +r if { input.y = 1 }`), input: `{"x": 1, "y": 1}`, @@ -1184,14 +1184,14 @@ r { { note: "else: no early exit: different constant value", expectedEE: false, - module: MustParseModule(`package test -r { + module: module(`package test +r if { input.x = 1 } -else = false { +else = false if { true } -r { +r if { input.y = 1 }`), input: `{"x": 1, "y": 1}`, @@ -1203,11 +1203,11 @@ r { { note: "function: single rule", expectedEE: true, - module: MustParseModule(`package test -r(x) { + module: module(`package test +r(x) if { input.x = x } -r = 3 { +r = 3 if { input.y = 2 }`), input: `{"x": 1}`, @@ -1218,11 +1218,11 @@ r = 3 { { note: "function: no early exit: different constant value", expectedEE: false, - module: MustParseModule(`package test -r(x) { + module: module(`package test +r(x) if { input.x = x } -r(y) = 2 { +r(y) = 2 if { input.x = 1 input.y = y }`), @@ -1235,11 +1235,11 @@ r(y) = 2 { { note: "function: same constant value", expectedEE: true, - module: MustParseModule(`package test -r(x) { + module: module(`package test +r(x) if { input.x = x } -r(y) { +r(y) if { input.y = y }`), input: `{"x": 1, "y": 1}`, @@ -1247,11 +1247,11 @@ r(y) { { note: "function: no early exit: one with with non-constant value", expectedEE: false, - module: MustParseModule(`package test -r(x) { + module: module(`package test +r(x) if { input.x = x } -r(y) = x { +r(y) = x if { input.y = y x = "foo" }`), @@ -1260,11 +1260,11 @@ r(y) = x { { // NOTE(sr): impossible, the compiler rewrites this note: "function: same ref value (input)", expectedEE: true, - module: MustParseModule(`package test -r(x) = input.a { + module: module(`package test +r(x) = input.a if { input.x = x } -r(y) = input.a { +r(y) = input.a if { input.y = y }`), input: `{"x": 1, "y": 1}`, @@ -1272,11 +1272,11 @@ r(y) = input.a { { // NOTE(sr): impossible, the compiler rewrites this note: "function: same ref value (data)", expectedEE: true, - module: MustParseModule(`package test -r(x) = data.a { + module: module(`package test +r(x) = data.a if { input.x = x } -r(y) = data.a { +r(y) = data.a if { input.y = y }`), input: `{"x": 1, "y": 1}`, @@ -1289,12 +1289,12 @@ r(y) = data.a { note: "no early exit: same ref but bound to vars", expectedEE: false, - module: MustParseModule(`package test -r = v { + module: module(`package test +r = v if { input.x = 1 v = input.a } -r = v { +r = v if { input.y = 1 v = input.a }`), @@ -1307,12 +1307,12 @@ r = v { { note: "no early exit: same value but with non-ground", expectedEE: false, - module: MustParseModule(`package test -r = [1, {"a": v}] { + module: module(`package test +r = [1, {"a": v}] if { input.x = 1 v = "a" } -r = [1, {"a": v}] { +r = [1, {"a": v}] if { input.y = 1 v = "a" }`), @@ -1327,8 +1327,8 @@ r = [1, {"a": v}] { expectedEE: false, // NOTE(sr): this is what the indexer gets after rewriting // r = { i | i := data.arr[i] } { true } - module: MustParseModule(`package test -r = local0 { + module: module(`package test +r = local0 if { local0 = {i | i := data.arr[i]} }`), input: `{}`, diff --git a/ast/marshal_test.go b/ast/marshal_test.go index 63b7c9ae91..d7f0cee5c5 100644 --- a/ast/marshal_test.go +++ b/ast/marshal_test.go @@ -398,10 +398,10 @@ func TestRule_MarshalJSON(t *testing.T) { # comment - allow { true } + allow if { true } ` - module, err := ParseModuleWithOpts("example.rego", rawModule, ParserOptions{}) + module, err := ParseModuleWithOpts("example.rego", rawModule, ParserOptions{AllFutureKeywords: true}) if err != nil { t.Fatal(err) } @@ -481,10 +481,10 @@ func TestHead_MarshalJSON(t *testing.T) { # comment - allow { true } + allow if { true } ` - module, err := ParseModuleWithOpts("example.rego", rawModule, ParserOptions{}) + module, err := ParseModuleWithOpts("example.rego", rawModule, ParserOptions{AllFutureKeywords: true}) if err != nil { t.Fatal(err) } @@ -585,10 +585,10 @@ func TestExpr_MarshalJSON(t *testing.T) { # comment - allow { true } + allow if { true } ` - module, err := ParseModuleWithOpts("example.rego", rawModule, ParserOptions{}) + module, err := ParseModuleWithOpts("example.rego", rawModule, ParserOptions{AllFutureKeywords: true}) if err != nil { t.Fatal(err) } @@ -630,7 +630,7 @@ func TestExpr_MarshalJSON(t *testing.T) { } return e }(), - ExpectedJSON: `{"index":0,"location":{"file":"example.rego","row":6,"col":10},"terms":{"type":"boolean","value":true}}`, + ExpectedJSON: `{"index":0,"location":{"file":"example.rego","row":6,"col":13},"terms":{"type":"boolean","value":true}}`, }, } @@ -653,10 +653,10 @@ func TestExpr_UnmarshalJSON(t *testing.T) { # comment - allow { true } + allow if { true } ` - module, err := ParseModuleWithOpts("example.rego", rawModule, ParserOptions{}) + module, err := ParseModuleWithOpts("example.rego", rawModule, ParserOptions{AllFutureKeywords: true}) if err != nil { t.Fatal(err) } @@ -678,7 +678,7 @@ func TestExpr_UnmarshalJSON(t *testing.T) { }(), }, "location case": { - JSON: `{"index":0,"location":{"file":"example.rego","row":6,"col":10},"terms":{"type":"boolean","value":true}}`, + JSON: `{"index":0,"location":{"file":"example.rego","row":6,"col":13},"terms":{"type":"boolean","value":true}}`, ExpectedExpr: expr, }, } @@ -757,16 +757,14 @@ func TestEvery_MarshalJSON(t *testing.T) { rawModule := ` package foo -import future.keywords.every - -allow { +allow if { every e in [1,2,3] { e == 1 } } ` - module, err := ParseModuleWithOpts("example.rego", rawModule, ParserOptions{}) + module, err := ParseModuleWithOpts("example.rego", rawModule, ParserOptions{AllFutureKeywords: true}) if err != nil { t.Fatal(err) } @@ -798,7 +796,7 @@ allow { e.jsonOptions = astJSON.Options{MarshalOptions: astJSON.MarshalOptions{IncludeLocation: astJSON.NodeToggle{Every: true}}} return e }(), - ExpectedJSON: `{"body":[{"index":0,"terms":[{"type":"ref","value":[{"type":"var","value":"equal"}]},{"type":"var","value":"e"},{"type":"number","value":1}]}],"domain":{"type":"array","value":[{"type":"number","value":1},{"type":"number","value":2},{"type":"number","value":3}]},"key":null,"location":{"file":"example.rego","row":7,"col":2},"value":{"type":"var","value":"e"}}`, + ExpectedJSON: `{"body":[{"index":0,"terms":[{"type":"ref","value":[{"type":"var","value":"equal"}]},{"type":"var","value":"e"},{"type":"number","value":1}]}],"domain":{"type":"array","value":[{"type":"number","value":1},{"type":"number","value":2},{"type":"number","value":3}]},"key":null,"location":{"file":"example.rego","row":5,"col":2},"value":{"type":"var","value":"e"}}`, }, } @@ -820,14 +818,14 @@ func TestWith_MarshalJSON(t *testing.T) { rawModule := ` package foo -a {input} +a if {input} -b { +b if { a with input as 1 } ` - module, err := ParseModuleWithOpts("example.rego", rawModule, ParserOptions{}) + module, err := ParseModuleWithOpts("example.rego", rawModule, ParserOptions{AllFutureKeywords: true}) if err != nil { t.Fatal(err) } diff --git a/ast/parser_bench_test.go b/ast/parser_bench_test.go index d978f6d4f5..66db8647e8 100644 --- a/ast/parser_bench_test.go +++ b/ast/parser_bench_test.go @@ -91,52 +91,52 @@ func BenchmarkParseBasicABACModule(b *testing.B) { default allow = false - allow { + allow if { user_is_owner } - allow { + allow if { user_is_employee action_is_read } - allow { + allow if { user_is_employee user_is_senior action_is_update } - allow { + allow if { user_is_customer action_is_read not pet_is_adopted } - user_is_owner { + user_is_owner if { data.user_attributes[input.user].title == "owner" } - user_is_employee { + user_is_employee if { data.user_attributes[input.user].title == "employee" } - user_is_customer { + user_is_customer if { data.user_attributes[input.user].title == "customer" } - user_is_senior { + user_is_senior if { data.user_attributes[input.user].tenure > 8 } - action_is_read { + action_is_read if { input.action == "read" } - action_is_update { + action_is_update if { input.action == "update" } - pet_is_adopted { + pet_is_adopted if { data.pet_attributes[input.resource].adopted == true } ` @@ -146,7 +146,7 @@ func BenchmarkParseBasicABACModule(b *testing.B) { func runParseModuleBenchmark(b *testing.B, mod string) { b.ResetTimer() for i := 0; i < b.N; i++ { - _, err := ParseModule("", mod) + _, err := ParseModuleWithOpts("", mod, ParserOptions{AllFutureKeywords: true}) if err != nil { b.Fatalf("Unexpected error: %s", err) } @@ -176,7 +176,7 @@ func runParseStatementBenchmarkWithError(b *testing.B, stmt string) { func generateModule(numRules int) string { mod := "package bench\n" for i := 0; i < numRules; i++ { - mod += fmt.Sprintf("p%d { input.x%d = %d }\n", i, i, i) + mod += fmt.Sprintf("p%d if { input.x%d = %d }\n", i, i, i) } return mod } diff --git a/ast/parser_ext.go b/ast/parser_ext.go index afaa1d890c..62e4b4efcd 100644 --- a/ast/parser_ext.go +++ b/ast/parser_ext.go @@ -103,6 +103,14 @@ func MustParseStatement(input string) Statement { return parsed } +func MustParseStatementWithOpts(input string, popts ParserOptions) Statement { + parsed, err := ParseStatementWithOpts(input, popts) + if err != nil { + panic(err) + } + return parsed +} + // MustParseRef returns a parsed reference. // If an error occurs during parsing, panic. func MustParseRef(input string) Ref { @@ -123,6 +131,16 @@ func MustParseRule(input string) *Rule { return parsed } +// MustParseRuleWithOpts returns a parsed rule. +// If an error occurs during parsing, panic. +func MustParseRuleWithOpts(input string, opts ParserOptions) *Rule { + parsed, err := ParseRuleWithOpts(input, opts) + if err != nil { + panic(err) + } + return parsed +} + // MustParseTerm returns a parsed term. // If an error occurs during parsing, panic. func MustParseTerm(input string) *Term { @@ -608,6 +626,17 @@ func ParseStatement(input string) (Statement, error) { return stmts[0], nil } +func ParseStatementWithOpts(input string, popts ParserOptions) (Statement, error) { + stmts, _, err := ParseStatementsWithOpts("", input, popts) + if err != nil { + return nil, err + } + if len(stmts) != 1 { + return nil, fmt.Errorf("expected exactly one statement") + } + return stmts[0], nil +} + // ParseStatements is deprecated. Use ParseStatementWithOpts instead. func ParseStatements(filename, input string) ([]Statement, []*Comment, error) { return ParseStatementsWithOpts(filename, input, ParserOptions{}) diff --git a/ast/parser_test.go b/ast/parser_test.go index eff9d4585f..7801f1b9b0 100644 --- a/ast/parser_test.go +++ b/ast/parser_test.go @@ -30,7 +30,7 @@ import data.networks import data.ports # A server exists in the violations set if... -violations[server] { +violations contains server if { # ...the server exists server = servers[i] # ...and any of the server’s protocols is HTTP @@ -40,7 +40,7 @@ violations[server] { } # A server exists in the public_servers set if... -public_servers[server] { +public_servers contains server if { # Semicolons are optional. Can group expressions onto one line. server = servers[i]; server.ports[j] = ports[k].id # ...and the server is connected to a port ports[k].networks[l] = networks[m].id; # ...and the port is connected to a network @@ -735,6 +735,7 @@ func TestSomeDeclExpr(t *testing.T) { ), }) + // Only relevant for v0, as the 'in' keyword isn't a permitted var name in v1. assertParseRule(t, "whitespace separated, following `in` rule ref", ` p[x] { some x @@ -746,19 +747,23 @@ func TestSomeDeclExpr(t *testing.T) { NewExpr(&SomeDecl{Symbols: []*Term{VarTerm("x")}}), NewExpr(RefTerm(VarTerm("in"), VarTerm("x"))), ), - }) + }, ParserOptions{RegoVersion: RegoV0}) + // Only relevant for v0, as the 'in' keyword is included in v1. assertParseErrorContains(t, "some x in ... usage is hinted properly", ` - p[x] { + p contains x if { some x in {"foo": "bar"} }`, - "unexpected identifier token: expected \\n or ; or } (hint: `import future.keywords.in` for `some x in xs` expressions)") + "unexpected identifier token: expected \\n or ; or } (hint: `import future.keywords.in` for `some x in xs` expressions)", + ParserOptions{RegoVersion: RegoV0}) + // Only relevant for v0, as the 'in' keyword is included in v1. assertParseErrorContains(t, "some x, y in ... usage is hinted properly", ` - p[y] = x { + p[y] = x if { some x, y in {"foo": "bar"} }`, - "unexpected identifier token: expected \\n or ; or } (hint: `import future.keywords.in` for `some x in xs` expressions)") + "unexpected identifier token: expected \\n or ; or } (hint: `import future.keywords.in` for `some x in xs` expressions)", + ParserOptions{RegoVersion: RegoV0}) assertParseRule(t, "whitespace terminated", ` @@ -844,18 +849,21 @@ func TestEvery(t *testing.T) { With: []*With{{Value: ArrayTerm(), Target: NewTerm(MustParseRef("input"))}}, }, opts) + // Only relevant for v0, as the 'every' keyword is included in v1. assertParseErrorContains(t, "every x, y in ... usage is hinted properly", ` p { every x, y in {"foo": "bar"} { is_string(x); is_string(y) } }`, - "unexpected identifier token: expected \\n or ; or } (hint: `import future.keywords.every` for `every x in xs { ... }` expressions)") + "unexpected identifier token: expected \\n or ; or } (hint: `import future.keywords.every` for `every x in xs { ... }` expressions)", + ParserOptions{RegoVersion: RegoV0}) + // Only relevant for v0, as the 'in' keyword is included in v1. assertParseErrorContains(t, "not every 'every' gets a hint", ` p { every x }`, "unexpected identifier token: expected \\n or ; or }\n\tevery x\n", // this asserts that the tail of the error message doesn't contain a hint - ) + ParserOptions{RegoVersion: RegoV0}) assertParseErrorContains(t, "invalid domain (internal.member_2)", "every internal.member_2()", "illegal domain", opts) assertParseErrorContains(t, "invalid domain (internal.member_3)", "every internal.member_3()", "illegal domain", opts) @@ -1264,14 +1272,20 @@ func TestFutureImports(t *testing.T) { import rego.v1 import future.keywords.in ` - assertParseModuleErrorMatch(t, "rego.v1 and future.keywords.in imported", mod, "rego_parse_error: the `rego.v1` import implies `future.keywords`, these are therefore mutually exclusive") + // Only applies to v0, as the 'rego.v1' import is a no-op in v1 + assertParseModuleErrorMatch(t, "rego.v1 and future.keywords.in imported", mod, + "rego_parse_error: the `rego.v1` import implies `future.keywords`, these are therefore mutually exclusive", + ParserOptions{RegoVersion: RegoV0}) mod = ` package p import future.keywords import rego.v1 ` - assertParseModuleErrorMatch(t, "rego.v1 and future.keywords imported", mod, "rego_parse_error: the `rego.v1` import implies `future.keywords`, these are therefore mutually exclusive") + // Only applies to v0, as the 'rego.v1' import is a no-op in v1 + assertParseModuleErrorMatch(t, "rego.v1 and future.keywords imported", mod, + "rego_parse_error: the `rego.v1` import implies `future.keywords`, these are therefore mutually exclusive", + ParserOptions{RegoVersion: RegoV0}) } func TestFutureAndRegoV1ImportsExtraction(t *testing.T) { @@ -1342,11 +1356,14 @@ func TestHintsOnUnknownImport(t *testing.T) { } func TestRegoV1Import(t *testing.T) { - assertParseErrorContains(t, "rego", "import rego", "invalid import `rego`, must be `rego.v1`") - assertParseErrorContains(t, "rego.foo", "import rego.foo", "invalid import `rego.foo`, must be `rego.v1`") - assertParseErrorContains(t, "rego.foo.bar", "import rego.foo.bar", "invalid import `rego.foo.bar`, must be `rego.v1`") - assertParseErrorContains(t, "rego.v1.bar", "import rego.v1.bar", "invalid import `rego.v1.bar`, must be `rego.v1`") - assertParseErrorContains(t, "rego.v1 + alias", "import rego.v1 as xyz", "`rego` imports cannot be aliased") + // These tests assert that the 'rego.v1' import is correctly handled in v0. + popts := ParserOptions{RegoVersion: RegoV0} + + assertParseErrorContains(t, "rego", "import rego", "invalid import `rego`, must be `rego.v1`", popts) + assertParseErrorContains(t, "rego.foo", "import rego.foo", "invalid import `rego.foo`, must be `rego.v1`", popts) + assertParseErrorContains(t, "rego.foo.bar", "import rego.foo.bar", "invalid import `rego.foo.bar`, must be `rego.v1`", popts) + assertParseErrorContains(t, "rego.v1.bar", "import rego.v1.bar", "invalid import `rego.v1.bar`, must be `rego.v1`", popts) + assertParseErrorContains(t, "rego.v1 + alias", "import rego.v1 as xyz", "`rego` imports cannot be aliased", popts) assertParseImport(t, "import rego.v1", "import rego.v1", &Import{Path: RefTerm(VarTerm("rego"), StringTerm("v1"))}, @@ -1904,7 +1921,7 @@ f(x) if { for _, tc := range tests { t.Run(tc.note, func(t *testing.T) { - _, errs := ParseModuleWithOpts("", tc.module, ParserOptions{}) + _, errs := ParseModuleWithOpts("", tc.module, popts) if len(tc.expectedErrors) == 0 && errs != nil { t.Fatalf("expected no errors, got:\n\n%v", errs) @@ -2319,7 +2336,7 @@ func TestRule(t *testing.T) { tr := BooleanTerm(true) head := func(v string) *Head { return &Head{Name: name, Reference: ref, Value: tr, Args: []*Term{VarTerm(v)}} } assertParseModule(t, "wildcard in chained function heads", `package test - f(_) { true } { true } + f(_) if { true } { true } `, &Module{ Package: MustParsePackage(`package test`), Rules: []*Rule{ @@ -2332,7 +2349,8 @@ func TestRule(t *testing.T) { Body: MustParseBody("true"), }, }, - }) + }, + ParserOptions{AllFutureKeywords: true}) } func TestRuleContains(t *testing.T) { @@ -2892,50 +2910,50 @@ q(0).r(0) { true }`, func TestRuleElseKeyword(t *testing.T) { mod := `package test - p { + p if { "p0" } - p { + p if { "p1" - } else { + } else if { "p1_e1" - } else = [null] { + } else = [null] if { "p1_e2" - } else = x { + } else = x if { x = "p1_e3" } - p { + p if { "p2" } - f(x) { + f(x) if { x < 100 - } else = false { + } else = false if { x > 200 - } else { + } else if { x != 150 } - _ { + _ if { x > 0 - } else { + } else if { x == -1 - } else { + } else if { x > -100 } - nobody = 1 { + nobody = 1 if { false } else = 7 - nobody_f(x) = 1 { + nobody_f(x) = 1 if { false } else = 7 ` - parsed, err := ParseModule("", mod) + parsed, err := ParseModuleWithOpts("", mod, ParserOptions{AllFutureKeywords: true}) if err != nil { t.Fatalf("Unexpected parse error: %v", err) } @@ -3245,9 +3263,9 @@ else := 2 func TestMultipleEnclosedBodies(t *testing.T) { - result, err := ParseModule("", `package ex + result := module(`package ex -p[x] = y { +p[x] = y if { x = "a" y = 1 } { @@ -3257,31 +3275,25 @@ p[x] = y { q = 1 -f(x) { +f(x) if { x < 10 } { x > 1000 } -`, - ) +`) - if err != nil { - t.Fatalf("Unexpected parse error: %v", err) - } + expected := module(`package ex - expected := MustParseModule(`package ex - -p[x] = y { x = "a"; y = 1 } -p[x] = y { x = "b"; y = 2 } -q = 1 { true } -f(x) { x < 10 } -f(x) { x > 1000 }`, +p[x] = y if { x = "a"; y = 1 } +p[x] = y if { x = "b"; y = 2 } +q = 1 if { true } +f(x) if { x < 10 } +f(x) if { x > 1000 }`, ) if !expected.Equal(result) { t.Fatal("Expected modules to be equal but got:\n\n", result, "\n\nExpected:\n\n", expected) } - } func TestEmptyModule(t *testing.T) { @@ -3303,6 +3315,107 @@ func TestComments(t *testing.T) { # by itself + p[x] = y if { y = "foo"; + # inside a rule + x = "bar"; + x != y; + q[x] + } + + import input.xyz.abc + + q # interrupting + + contains a # the head of a rule + + if { m = [1,2, + 3, ]; + a = m[i] + + } + + r contains x if { x = [ a | # inside comprehension + a = z[i] + b[i].a = a ] + + y = { a | # inside set comprehension + a = z[i] + b[i].a = a} + + z = {a: i | # inside object comprehension + a = z[i] + b[i].a = a} + }` + + popts := ParserOptions{AllFutureKeywords: true} + + assertParseModule(t, "module comments", testModule, &Module{ + Package: MustParseStatement(`package a.b.c`).(*Package), + Imports: []*Import{ + MustParseStatement("import input.e.f as g").(*Import), + MustParseStatement("import input.h").(*Import), + MustParseStatement("import input.xyz.abc").(*Import), + }, + Rules: []*Rule{ + MustParseStatementWithOpts(`p[x] = y if { y = "foo"; x = "bar"; x != y; q[x] }`, popts).(*Rule), + MustParseStatementWithOpts(`q contains a if { m = [1, 2, 3]; a = m[i] }`, popts).(*Rule), + MustParseStatementWithOpts(`r contains x if { x = [a | a = z[i]; b[i].a = a]; y = {a | a = z[i]; b[i].a = a}; z = {a: i | a = z[i]; b[i].a = a} }`, popts).(*Rule), + }, + }, popts) + + module, err := ParseModuleWithOpts("test.rego", testModule, popts) + if err != nil { + t.Fatal("Unexpected error:", err) + } + + exp := []struct { + text string + row int + col int + }{ + {text: "end of line", row: 3, col: 28}, + {text: "by itself", row: 6, col: 5}, + {text: "inside a rule", row: 9, col: 9}, + {text: "interrupting", row: 17, col: 7}, + {text: "the head of a rule", row: 19, col: 14}, + {text: "inside comprehension", row: 27, col: 30}, + {text: "inside set comprehension", row: 31, col: 13}, + {text: "inside object comprehension", row: 35, col: 15}, + } + + if len(module.Comments) != len(exp) { + t.Fatalf("Expected %v comments but got %v", len(exp), len(module.Comments)) + } + + for i := range exp { + + expc := &Comment{ + Text: []byte(" " + exp[i].text), + Location: &Location{ + File: "test.rego", + Text: []byte("# " + exp[i].text), + Row: exp[i].row, + Col: exp[i].col, + }, + } + + if !expc.Equal(module.Comments[i]) { + comment := module.Comments[i] + fmt.Printf("comment: %v %v %v %v\n", comment.Location.File, comment.Location.Text, comment.Location.Col, comment.Location.Row) + fmt.Printf("expcomm: %v %v %v %v\n", expc.Location.File, expc.Location.Text, expc.Location.Col, expc.Location.Row) + t.Errorf("Expected %q but got: %q (want: %d:%d, got: %d:%d)", expc, comment, exp[i].row, exp[i].col, comment.Location.Row, comment.Location.Col) + } + } +} + +func TestCommentsV0(t *testing.T) { + testModule := `package a.b.c + + import input.e.f as g # end of line + import input.h + + # by itself + p[x] = y { y = "foo"; # inside a rule x = "bar"; @@ -3335,6 +3448,8 @@ func TestComments(t *testing.T) { b[i].a = a} }` + popts := ParserOptions{RegoVersion: RegoV0} + assertParseModule(t, "module comments", testModule, &Module{ Package: MustParseStatement(`package a.b.c`).(*Package), Imports: []*Import{ @@ -3343,13 +3458,13 @@ func TestComments(t *testing.T) { MustParseStatement("import input.xyz.abc").(*Import), }, Rules: []*Rule{ - MustParseStatement(`p[x] = y { y = "foo"; x = "bar"; x != y; q[x] }`).(*Rule), - MustParseStatement(`q[a] { m = [1, 2, 3]; a = m[i] }`).(*Rule), - MustParseStatement(`r[x] { x = [a | a = z[i]; b[i].a = a]; y = {a | a = z[i]; b[i].a = a}; z = {a: i | a = z[i]; b[i].a = a} }`).(*Rule), + MustParseStatementWithOpts(`p[x] = y { y = "foo"; x = "bar"; x != y; q[x] }`, popts).(*Rule), + MustParseStatementWithOpts(`q[a] { m = [1, 2, 3]; a = m[i] }`, popts).(*Rule), + MustParseStatementWithOpts(`r[x] { x = [a | a = z[i]; b[i].a = a]; y = {a | a = z[i]; b[i].a = a}; z = {a: i | a = z[i]; b[i].a = a} }`, popts).(*Rule), }, - }) + }, popts) - module, err := ParseModule("test.rego", testModule) + module, err := ParseModuleWithOpts("test.rego", testModule, popts) if err != nil { t.Fatal("Unexpected error:", err) } @@ -3455,6 +3570,8 @@ func TestCommentsWhitespace(t *testing.T) { } func TestExample(t *testing.T) { + popts := ParserOptions{AllFutureKeywords: true} + assertParseModule(t, "example module", testModule, &Module{ Package: MustParseStatement(`package opa.examples`).(*Package), Imports: []*Import{ @@ -3463,10 +3580,10 @@ func TestExample(t *testing.T) { MustParseStatement("import data.ports").(*Import), }, Rules: []*Rule{ - MustParseStatement(`violations[server] { server = servers[i]; server.protocols[j] = "http"; public_servers[server] }`).(*Rule), - MustParseStatement(`public_servers[server] { server = servers[i]; server.ports[j] = ports[k].id; ports[k].networks[l] = networks[m].id; networks[m].public = true }`).(*Rule), + MustParseStatementWithOpts(`violations contains server if { server = servers[i]; server.protocols[j] = "http"; public_servers[server] }`, popts).(*Rule), + MustParseStatementWithOpts(`public_servers contains server if { server = servers[i]; server.ports[j] = ports[k].id; ports[k].networks[l] = networks[m].id; networks[m].public = true }`, popts).(*Rule), }, - }) + }, popts) } func TestModuleParseErrors(t *testing.T) { @@ -3494,7 +3611,7 @@ func TestModuleParseErrors(t *testing.T) { } func TestLocation(t *testing.T) { - mod, err := ParseModule("test", testModule) + mod, err := ParseModuleWithOpts("test", testModule, ParserOptions{AllFutureKeywords: true}) if err != nil { t.Errorf("Unexpected error while parsing test module: %v", err) return @@ -3519,30 +3636,42 @@ func TestRuleFromBodyRefs(t *testing.T) { // the same as parsing the string as a Rule directly. Without also passing // TestRuleRefHeads, these tests are not to be trusted -- if changing something, // start with getting TestRuleRefHeads to PASS. + // + // NOTE: Some of these test cases are invalid v1 Rego, and are locked to v0. tests := []struct { - note string - rule string - exp string + note string + regoVersion RegoVersion + rule string + exp string }{ { - note: "no dots: single-value rule (complete doc)", - rule: `foo["bar"] = 12`, - exp: `foo["bar"] = 12 { true }`, + note: "no dots: single-value rule (complete doc)", + regoVersion: RegoV0, + rule: `foo["bar"] = 12`, + exp: `foo["bar"] = 12 { true }`, + }, + { + note: "no dots: partial set of numbers", + regoVersion: RegoV0, + rule: `foo[1]`, + exp: `foo[1] { true }`, }, { - note: "no dots: partial set of numbers", - rule: `foo[1]`, - exp: `foo[1] { true }`, + note: "no dots: shorthand set of strings", // back compat + regoVersion: RegoV0, + rule: `foo.one`, + exp: `foo["one"] { true }`, }, { - note: "no dots: shorthand set of strings", // back compat - rule: `foo.one`, - exp: `foo["one"] { true }`, + note: "no dots: partial set", + regoVersion: RegoV0, + rule: `foo[x] { x = 1 }`, + exp: `foo[x] { x = 1 }`, }, { - note: "no dots: partial set", - rule: `foo[x] { x = 1 }`, - exp: `foo[x] { x = 1 }`, + note: "no dots + contains + if: partial set", + rule: `foo contains x if { x = 1 }`, + exp: `foo contains x if { x = 1 }`, }, { note: "no dots + if: complete doc", @@ -3560,9 +3689,15 @@ func TestRuleFromBodyRefs(t *testing.T) { exp: `foo(x) = y { true }`, }, { - note: "no dots: partial set, ref element", - rule: `test[arr[0]]`, - exp: `test[arr[0]] { true }`, + note: "no dots: partial set, ref element", + regoVersion: RegoV0, + rule: `test[arr[0]]`, + exp: `test[arr[0]] { true }`, + }, + { + note: "no dots + contains: partial set, ref element", + rule: `test contains arr[0]`, + exp: `test contains arr[0] if { true }`, }, { note: "one dot: complete rule shorthand", @@ -3575,9 +3710,15 @@ func TestRuleFromBodyRefs(t *testing.T) { exp: `foo.bar[x] = "buz" { true }`, }, { - note: "one dot, bracket with var: partial set", - rule: `foo.bar[x] { x = 1 }`, - exp: `foo.bar[x] { x = 1 }`, + note: "one dot, bracket with var: partial set", + regoVersion: RegoV0, + rule: `foo.bar[x] { x = 1 }`, + exp: `foo.bar[x] { x = 1 }`, + }, + { + note: "one dot, contains with var: partial set", + rule: `foo.bar contains x if { x = 1 }`, + exp: `foo.bar contains x if { x = 1 }`, }, { note: "one dot, bracket with string: complete doc", @@ -3586,8 +3727,8 @@ func TestRuleFromBodyRefs(t *testing.T) { }, { note: "one dot, bracket with var, rule body: partial object", - rule: `foo.bar[x] = "buz" { x = 1 }`, - exp: `foo.bar[x] = "buz" { x = 1 }`, + rule: `foo.bar[x] = "buz" if { x = 1 }`, + exp: `foo.bar[x] = "buz" if { x = 1 }`, }, { note: "one dot: function", @@ -3601,18 +3742,30 @@ func TestRuleFromBodyRefs(t *testing.T) { }, { note: "two dots, bracket with var: partial object", - rule: `foo.bar.baz[x] = "buz" { x = 1 }`, - exp: `foo.bar.baz[x] = "buz" { x = 1 }`, + rule: `foo.bar.baz[x] = "buz" if { x = 1 }`, + exp: `foo.bar.baz[x] = "buz" if { x = 1 }`, + }, + { + note: "two dots, bracket with var: partial set", + regoVersion: RegoV0, + rule: `foo.bar.baz[x] { x = 1 }`, + exp: `foo.bar.baz[x] { x = 1 }`, }, { - note: "two dots, bracket with var: partial set", - rule: `foo.bar.baz[x] { x = 1 }`, - exp: `foo.bar.baz[x] { x = 1 }`, + note: "two dots, contains with var: partial set", + rule: `foo.bar.baz contains x if { x = 1 }`, + exp: `foo.bar.baz contains x if { x = 1 }`, }, { - note: "one dot, bracket with string, no key: complete doc", - rule: `foo.bar["baz"]`, - exp: `foo.bar.baz { true }`, + note: "one dot, bracket with string, no key: complete doc", + regoVersion: RegoV0, + rule: `foo.bar["baz"]`, + exp: `foo.bar.baz { true }`, + }, + { + note: "one dot, bracket with string, no key, value: complete doc", + rule: `foo.bar["baz"] := true`, + exp: `foo.bar.baz := true if { true }`, }, { note: "two dots: function", @@ -3626,32 +3779,46 @@ func TestRuleFromBodyRefs(t *testing.T) { }, { note: "non-ground ref: complete doc", - rule: `foo.bar[i].baz { i := 1 }`, - exp: `foo.bar[i].baz { i := 1 }`, + rule: `foo.bar[i].baz if { i := 1 }`, + exp: `foo.bar[i].baz if { i := 1 }`, }, { - note: "non-ground ref: partial set", - rule: `foo.bar[i].baz[x] { i := 1; x := 2 }`, - exp: `foo.bar[i].baz[x] { i := 1; x := 2 }`, + note: "non-ground ref, bracket-key: partial set", + regoVersion: RegoV0, + rule: `foo.bar[i].baz[x] { i := 1; x := 2 }`, + exp: `foo.bar[i].baz[x] { i := 1; x := 2 }`, + }, + { + note: "non-ground ref, contains-key: partial set", + rule: `foo.bar[i].baz contains x if { i := 1; x := 2 }`, + exp: `foo.bar[i].baz contains x if { i := 1; x := 2 }`, }, { note: "non-ground ref: partial object", - rule: `foo.bar[i].baz[x] = 3 { i := 1; x := 2 }`, - exp: `foo.bar[i].baz[x] = 3 { i := 1; x := 2 }`, + rule: `foo.bar[i].baz[x] = 3 if { i := 1; x := 2 }`, + exp: `foo.bar[i].baz[x] = 3 if { i := 1; x := 2 }`, }, { note: "non-ground ref: function", - rule: `foo.bar[i].baz(x) = 3 { i := 1 }`, - exp: `foo.bar[i].baz(x) = 3 { i := 1 }`, + rule: `foo.bar[i].baz(x) = 3 if { i := 1 }`, + exp: `foo.bar[i].baz(x) = 3 if { i := 1 }`, + }, + { + note: "last term is number: partial set", + regoVersion: RegoV0, + rule: `foo.bar.baz[3] { true }`, + exp: `foo.bar.baz[3] { true }`, }, { - note: "last term is number: partial set", - rule: `foo.bar.baz[3] { true }`, - exp: `foo.bar.baz[3] { true }`, + note: "contains with number: partial set", + rule: `foo.bar.baz contains 3 if { true }`, + exp: `foo.bar.baz contains 3 if { true }`, }, } for _, tc := range tests { t.Run(tc.note, func(t *testing.T) { + opts.RegoVersion = tc.regoVersion + r, err := ParseRuleWithOpts(tc.exp, opts) if err != nil { t.Fatal(err) @@ -3710,6 +3877,8 @@ func assertErrorWithMessage(t *testing.T, err error, msg string) { } func TestRuleFromBody(t *testing.T) { + popts := ParserOptions{RegoVersion: RegoV0} + tests := []struct { input string exp string @@ -3741,7 +3910,7 @@ func TestRuleFromBody(t *testing.T) { Rules: []*Rule{ MustParseRule(tc.exp), }, - }) + }, popts) }) } @@ -3750,7 +3919,7 @@ func TestRuleFromBody(t *testing.T) { for _, tc := range tests { testModule += tc.input + "\n" } - module, err := ParseModule("test.rego", testModule) + module, err := ParseModuleWithOpts("test.rego", testModule, popts) if err != nil { t.Fatal(err) } @@ -3946,7 +4115,7 @@ func TestWildcards(t *testing.T) { func TestRuleFromBodyJSONOptions(t *testing.T) { tests := []string{ `pi = 3.14159`, - `p[x] { x = 1 }`, + `p contains x if { x = 1 }`, `greeting = "hello"`, `cores = [{0: 1}, {1: 2}]`, `wrapper = cores[0][1]`, @@ -3954,10 +4123,10 @@ func TestRuleFromBodyJSONOptions(t *testing.T) { `foo["bar"] = "buz"`, `foo["9"] = "10"`, `foo.buz = "bar"`, - `foo.fizz.buzz`, - `bar[1]`, - `bar[[{"foo":"baz"}]]`, - `bar.qux`, + `foo.fizz contains "buzz"`, + `bar contains 1`, + `bar contains [{"foo":"baz"}]`, + `bar contains "qux"`, `input = 1`, `data = 2`, `f(1) = 2`, @@ -3965,7 +4134,7 @@ func TestRuleFromBodyJSONOptions(t *testing.T) { `d1 := 1234`, } - parserOpts := ParserOptions{ProcessAnnotation: true} + parserOpts := ParserOptions{ProcessAnnotation: true, AllFutureKeywords: true} parserOpts.JSONOptions = &astJSON.Options{ MarshalOptions: astJSON.MarshalOptions{ IncludeLocation: astJSON.NodeToggle{ @@ -4025,14 +4194,14 @@ func TestRuleFromBodyJSONOptionsLocationOptions(t *testing.T) { func TestRuleModulePtr(t *testing.T) { mod := `package test - p { true } - p { true } - q { true } + p if { true } + p if { true } + q if { true } r = 1 default s = 2 ` - parsed, err := ParseModule("", mod) + parsed, err := ParseModuleWithOpts("", mod, ParserOptions{AllFutureKeywords: true}) if err != nil { t.Fatalf("Unexpected parse error: %v", err) } @@ -4047,7 +4216,7 @@ func TestRuleModulePtr(t *testing.T) { func TestNoMatchError(t *testing.T) { mod := `package test - p { true; + p if { true; 1 != 0; # <-- parse error: no match }` @@ -4061,11 +4230,11 @@ func TestNoMatchError(t *testing.T) { mod = `package test - p { true // <-- parse error: no match` + p if { true // <-- parse error: no match` - _, err = ParseModule("foo.rego", mod) + _, err = ParseModuleWithOpts("foo.rego", mod, ParserOptions{AllFutureKeywords: true}) - loc := NewLocation([]byte{'/'}, "foo.rego", 3, 12) + loc := NewLocation([]byte{'/'}, "foo.rego", 3, 15) if !loc.Equal(err.(Errors)[0].Location) { t.Fatalf("Expected %v but got: %v", loc, err) @@ -4317,20 +4486,20 @@ func TestRuleHeadLocation(t *testing.T) { const input = `package pkg -p[x] { +p contains x if { x = "hi" } { x = "bye" } -f(x) { +f(x) if { false -} else = false { +} else = false if { true } ` - module := MustParseModule(input) + module := module(input) for _, tc := range []struct { note string @@ -4343,7 +4512,7 @@ f(x) { location: module.Rules[0].Location, expectedRow: 3, expectedText: ` -p[x] { +p contains x if { x = "hi" } `, @@ -4352,7 +4521,7 @@ p[x] { note: "partial rule head", location: module.Rules[0].Head.Location, expectedRow: 3, - expectedText: `p[x]`, + expectedText: `p contains x`, }, { note: "partial rule head key", @@ -4395,9 +4564,9 @@ p[x] { location: module.Rules[2].Location, expectedRow: 9, expectedText: ` -f(x) { +f(x) if { false -} else = false { +} else = false if { true } `, @@ -4419,7 +4588,7 @@ f(x) { location: module.Rules[2].Else.Location, expectedRow: 11, expectedText: ` -else = false { +else = false if { true } `, @@ -4549,7 +4718,7 @@ func TestParserText(t *testing.T) { func TestRuleText(t *testing.T) { input := ` package test -r[x] = y { +r[x] = y if { x = input.a x = "foo" } { @@ -4560,13 +4729,13 @@ r[x] = y { x = "baz" } -r[x] = y { +r[x] = y if { x = input.d x = "qux" } ` - mod := MustParseModule(input) + mod := module(input) rules := mod.Rules if len(rules) != 4 { @@ -4575,7 +4744,7 @@ r[x] = y { expectedRuleText := []string{ ` -r[x] = y { +r[x] = y if { x = input.a x = "foo" } @@ -4593,7 +4762,7 @@ r[x] = y { } `, ` -r[x] = y { +r[x] = y if { x = input.d x = "qux" } @@ -4748,7 +4917,7 @@ import data.ports # scope: rule # schemas: # - data.servers: schema.servers -public_servers[server] { +public_servers contains server if { server = servers[i]; server.ports[j] = ports[k].id ports[k].networks[l] = networks[m].id; networks[m].public = true @@ -4778,7 +4947,7 @@ import data.ports # - data.servers: schema.servers # - data.networks: schema.networks # - data.ports: schema.ports -public_servers[server] { +public_servers contains server if { server = servers[i]; server.ports[j] = ports[k].id ports[k].networks[l] = networks[m].id; networks[m].public = true @@ -4812,7 +4981,7 @@ import data.ports # - data.ports: schema.ports # This is a comment after the metadata YAML -public_servers[server] { +public_servers contains server if { server = servers[i]; server.ports[j] = ports[k].id ports[k].networks[l] = networks[m].id; networks[m].public = true @@ -4845,7 +5014,7 @@ import data.ports # - data.networks: schema.networks # - data.ports: schema.ports # -public_servers[server] { +public_servers contains server if { server = servers[i]; server.ports[j] = ports[k].id ports[k].networks[l] = networks[m].id; networks[m].public = true @@ -4994,13 +5163,13 @@ import data.ports # - data.servers: schema.servers # - data.networks: schema.networks # - data.ports: schema.ports -public_servers[server] { +public_servers contains server if { server = servers[i]; server.ports[j] = ports[k].id ports[k].networks[l] = networks[m].id; networks[m].public = true } -public_servers_1[server] { +public_servers_1 contains server if { server = servers[i]; server.ports[j] = ports[k].id ports[k].networks[l] = networks[m].id; networks[m].public = true @@ -5031,7 +5200,7 @@ import data.ports # scope: rule # schemas: # - data.servers: schema.servers -public_servers[server] { +public_servers contains server if { server = servers[i] } @@ -5040,7 +5209,7 @@ public_servers[server] { # schemas: # - data.networks: schema.networks # - data.ports: schema.ports -public_servers_1[server] { +public_servers_1 contains server if { ports[k].networks[l] = networks[m].id; networks[m].public = true }`, @@ -5073,7 +5242,7 @@ public_servers_1[server] { # METADATA # title: My rule 2 -p { input = "str" }`, +p if { input = "str" }`, expNumComments: 4, expAnnotations: []*Annotations{ { @@ -5093,7 +5262,7 @@ p { input = "str" }`, # METADATA # scope: rule -p { input.x > 7 }`, +p if { input.x > 7 }`, expError: "test.rego:3: rego_parse_error: expected METADATA block, found whitespace", }, { @@ -5115,7 +5284,7 @@ p := 7`, # title: My package package test -p { input = "str" }`, +p if { input = "str" }`, expNumComments: 2, expAnnotations: []*Annotations{ { @@ -5132,7 +5301,7 @@ p { input = "str" }`, # title: My import import input.foo -p { input = "str" }`, +p if { input = "str" }`, expNumComments: 2, expError: "1 error occurred: test.rego:3: rego_parse_error: invalid annotation scope 'import'", }, @@ -5202,7 +5371,7 @@ import data.foo`, # METADATA # schemas: # - input: {"type": "string"} -p { input = "str" }`, +p if { input = "str" }`, expNumComments: 3, expAnnotations: []*Annotations{ { @@ -5248,7 +5417,7 @@ p { input = "str" }`, # number: 42 # string: foo bar baz # flag: -p { input = "str" }`, +p if { input = "str" }`, expNumComments: 31, expAnnotations: []*Annotations{ { @@ -5300,6 +5469,7 @@ p { input = "str" }`, t.Run(tc.note, func(t *testing.T) { mod, err := ParseModuleWithOpts("test.rego", tc.module, ParserOptions{ ProcessAnnotation: true, + AllFutureKeywords: true, }) if err != nil { if tc.expError == "" || !strings.Contains(err.Error(), tc.expError) { @@ -5514,12 +5684,12 @@ package test # METADATA # title: p-1 # description: p-1 -p[1] +p contains 1 # METADATA # title: p-2 # description: p-2 -p[2] +p contains 2 # METADATA # title: q @@ -5571,7 +5741,7 @@ package test # METADATA # title: p-1 # description: p-1 -p[1] +p contains 1 # METADATA # title: q @@ -5581,7 +5751,7 @@ q := 1 # METADATA # title: p-2 # description: p-2 -p[2] +p contains 2 `, expAnnotations: map[int][]*Annotations{ 13: { @@ -5620,7 +5790,10 @@ p[2] for _, tc := range tests { t.Run(tc.note, func(t *testing.T) { - pm, err := ParseModuleWithOpts("test.rego", tc.module, ParserOptions{ProcessAnnotation: true}) + pm, err := ParseModuleWithOpts("test.rego", tc.module, ParserOptions{ + ProcessAnnotation: true, + AllFutureKeywords: true, + }) if err != nil { t.Fatal(err) } @@ -6307,6 +6480,8 @@ func assertParseImport(t *testing.T, msg string, input string, correct *Import, } func assertParseModule(t *testing.T, msg string, input string, correct *Module, opts ...ParserOptions) { + t.Helper() + opt := ParserOptions{} if len(opts) == 1 { opt = opts[0] @@ -6377,9 +6552,15 @@ func assertParseModuleError(t *testing.T, msg, input string) { } } -func assertParseModuleErrorMatch(t *testing.T, msg, input string, expected string) { +func assertParseModuleErrorMatch(t *testing.T, msg, input string, expected string, opts ...ParserOptions) { t.Helper() - m, err := ParseModule("", input) + + opt := ParserOptions{} + if len(opts) == 1 { + opt = opts[0] + } + + m, err := ParseModuleWithOpts("", input, opt) if err == nil { t.Errorf("Error on test \"%s\": expected parse error: %v (parsed)", msg, m) } diff --git a/ast/policy_test.go b/ast/policy_test.go index 0248780c18..b70f901414 100644 --- a/ast/policy_test.go +++ b/ast/policy_test.go @@ -20,28 +20,28 @@ func TestModuleJSONRoundTrip(t *testing.T) { mod, err := ParseModuleWithOpts("test.rego", `package a.b.c -import future.keywords +import rego.v1 import data.x.y as z import data.u.i -p = [1, 2, {"foo": 3.14}] { r[x] = 1; not q[x] } -r[y] = v { i[1] = y; v = i[2] } -q[x] { a = [true, false, null, {"x": [1, 2, 3]}]; a[i] = x } -t = true { xs = [{"x": a[i].a} | a[i].n = "bob"; b[x]] } -big = 1e+1000 { true } -odd = -0.1 { true } -s = {1, 2, 3} { true } -s = set() { false } -empty_obj = true { {} } -empty_arr = true { [] } -empty_set = true { set() } -using_with = true { x = data.foo + 1 with input.foo as bar } -x = 2 { input = null } +p = [1, 2, {"foo": 3.14}] if { r[x] = 1; not q[x] } +r[y] = v if { i[1] = y; v = i[2] } +q contains x if { a = [true, false, null, {"x": [1, 2, 3]}]; a[i] = x } +t = true if { xs = [{"x": a[i].a} | a[i].n = "bob"; b[x]] } +big = 1e+1000 if { true } +odd = -0.1 if { true } +s = {1, 2, 3} if { true } +s = set() if { false } +empty_obj = true if { {} } +empty_arr = true if { [] } +empty_set = true if { set() } +using_with = true if { x = data.foo + 1 with input.foo as bar } +x = 2 if { input = null } default allow = true -f(x) = y { y = x } -a = true { xs = {a: b | input.y[a] = "foo"; b = input.z["bar"]} } -b = true { xs = {{"x": a[i].a} | a[i].n = "bob"; b[x]} } -call_values { f(x) != g(x) } +f(x) = y if { y = x } +a = true if { xs = {a: b | input.y[a] = "foo"; b = input.z["bar"]} } +b = true if { xs = {{"x": a[i].a} | a[i].n = "bob"; b[x]} } +call_values if { f(x) != g(x) } assigned := 1 rule.having.ref.head[1] = x if x := 2 @@ -487,8 +487,9 @@ func TestRuleString(t *testing.T) { trueBody := NewBody(NewExpr(BooleanTerm(true))) tests := []struct { - rule *Rule - exp string + rule *Rule + expV0 string + expV1 string }{ { rule: &Rule{ @@ -497,28 +498,32 @@ func TestRuleString(t *testing.T) { Equality.Expr(StringTerm("foo"), StringTerm("bar")), ), }, - exp: `p = true { "foo" = "bar" }`, + expV0: `p = true { "foo" = "bar" }`, + expV1: `p = true if { "foo" = "bar" }`, }, { rule: &Rule{ Head: NewHead(Var("p"), VarTerm("x")), Body: trueBody, }, - exp: `p[x] { true }`, + expV0: `p[x] { true }`, + expV1: `p contains x if { true }`, }, { rule: &Rule{ Head: RefHead(MustParseRef("p[x]"), BooleanTerm(true)), Body: MustParseBody("x = 1"), }, - exp: `p[x] = true { x = 1 }`, + expV0: `p[x] = true { x = 1 }`, + expV1: `p[x] = true if { x = 1 }`, }, { rule: &Rule{ Head: RefHead(MustParseRef("p.q.r[x]"), BooleanTerm(true)), Body: MustParseBody("x = 1"), }, - exp: `p.q.r[x] = true { x = 1 }`, + expV0: `p.q.r[x] = true { x = 1 }`, + expV1: `p.q.r[x] = true if { x = 1 }`, }, { rule: &Rule{ @@ -528,7 +533,8 @@ func TestRuleString(t *testing.T) { }, Body: MustParseBody("x = 1"), }, - exp: `p.q.r contains 1 { x = 1 }`, + expV0: `p.q.r contains 1 { x = 1 }`, + expV1: `p.q.r contains 1 if { x = 1 }`, }, { rule: &Rule{ @@ -542,14 +548,16 @@ func TestRuleString(t *testing.T) { Equality.Expr(StringTerm("b"), VarTerm("y")), ), }, - exp: `p[x] = y { "foo" = x; not a.b[x]; "b" = y }`, + expV0: `p[x] = y { "foo" = x; not a.b[x]; "b" = y }`, + expV1: `p[x] = y if { "foo" = x; not a.b[x]; "b" = y }`, }, { rule: &Rule{ Default: true, Head: NewHead("p", nil, BooleanTerm(true)), }, - exp: `default p = true`, + expV0: `default p = true`, + expV1: `default p = true`, }, { rule: &Rule{ @@ -560,7 +568,8 @@ func TestRuleString(t *testing.T) { }, Body: NewBody(Plus.Expr(VarTerm("x"), VarTerm("y"), VarTerm("z"))), }, - exp: "f(x, y) = z { plus(x, y, z) }", + expV0: "f(x, y) = z { plus(x, y, z) }", + expV1: "f(x, y) = z if { plus(x, y, z) }", }, { rule: &Rule{ @@ -573,48 +582,60 @@ func TestRuleString(t *testing.T) { Equality.Expr(StringTerm("foo"), StringTerm("bar")), ), }, - exp: `p := true { "foo" = "bar" }`, + expV0: `p := true { "foo" = "bar" }`, + expV1: `p := true if { "foo" = "bar" }`, }, { rule: &Rule{ Head: RefHead(MustParseRef("p.q.r")), Body: trueBody, }, - exp: `p.q.r { true }`, + expV0: `p.q.r { true }`, + expV1: `p.q.r if { true }`, }, { rule: &Rule{ Head: RefHead(MustParseRef("p.q.r"), StringTerm("foo")), Body: trueBody, }, - exp: `p.q.r = "foo" { true }`, + expV0: `p.q.r = "foo" { true }`, + expV1: `p.q.r = "foo" if { true }`, }, { rule: &Rule{ Head: RefHead(MustParseRef("p.q.r[x]"), StringTerm("foo")), Body: MustParseBody(`x := 1`), }, - exp: `p.q.r[x] = "foo" { assign(x, 1) }`, + expV0: `p.q.r[x] = "foo" { assign(x, 1) }`, + expV1: `p.q.r[x] = "foo" if { assign(x, 1) }`, }, } for _, tc := range tests { - t.Run(tc.exp, func(t *testing.T) { - assertRuleString(t, tc.rule, tc.exp) + t.Run(tc.expV0, func(t *testing.T) { + assertRuleString(t, tc.rule, tc.expV0, toStringOpts{regoVersion: RegoV0}) + assertRuleString(t, tc.rule, tc.expV1, toStringOpts{regoVersion: RegoV1}) + + switch DefaultRegoVersion { + case RegoV0: + assertRuleString(t, tc.rule, tc.expV0, toStringOpts{}) + case RegoV1: + assertRuleString(t, tc.rule, tc.expV1, toStringOpts{}) + } }) } } func TestRulePath(t *testing.T) { ruleWithMod := func(r string) Ref { - mod := MustParseModule("package pkg\n" + r) + mod := module("package pkg\n" + r) return mod.Rules[0].Ref().GroundPrefix() } - if exp, act := MustParseRef("data.pkg.p.q.r"), ruleWithMod("p.q.r { true }"); !exp.Equal(act) { + if exp, act := MustParseRef("data.pkg.p.q.r"), ruleWithMod("p.q.r if { true }"); !exp.Equal(act) { t.Errorf("expected %v, got %v", exp, act) } - if exp, act := MustParseRef("data.pkg.p"), ruleWithMod("p { true }"); !exp.Equal(act) { + if exp, act := MustParseRef("data.pkg.p"), ruleWithMod("p if { true }"); !exp.Equal(act) { t.Errorf("expected %v, got %v", exp, act) } } @@ -623,12 +644,13 @@ func TestModuleString(t *testing.T) { input := `package a.b.c +import rego.v1 import data.foo.bar import input.xyz -p = true { not bar } -q = true { xyz.abc = 2 } -wildcard = true { bar[_] = 1 }` +p = true if { not bar } +q = true if { xyz.abc = 2 } +wildcard = true if { bar[_] = 1 }` mod := MustParseModule(input) @@ -822,6 +844,7 @@ func mustParseURL(str string) url.URL { func TestModuleStringAnnotations(t *testing.T) { module, err := ParseModuleWithOpts("test.rego", `package test +import rego.v1 # METADATA # scope: rule @@ -833,9 +856,11 @@ p := 7`, ParserOptions{ProcessAnnotation: true}) exp := `package test +import rego.v1 + # METADATA # {"scope":"rule"} -p := 7 { true }` +p := 7 if { true }` if module.String() != exp { t.Fatalf("expected %q but got %q", exp, module.String()) @@ -1052,10 +1077,10 @@ func assertHeadsNotEqual(t *testing.T, a, b *Head) { } } -func assertRuleString(t *testing.T, rule *Rule, expected string) { +func assertRuleString(t *testing.T, rule *Rule, expected string, opts toStringOpts) { t.Helper() - result := rule.String() + result := rule.stringWithOpts(opts) if result != expected { - t.Errorf("Expected %v but got %v", expected, result) + t.Errorf("Expected %v but got %v for rego-version %v", expected, result, opts.regoVersion) } } diff --git a/ast/pretty_test.go b/ast/pretty_test.go index 50fe97c142..e51a3b8f3e 100644 --- a/ast/pretty_test.go +++ b/ast/pretty_test.go @@ -12,12 +12,12 @@ import ( func TestPretty(t *testing.T) { - module := MustParseModule(` + module := module(` package foo.bar import data.baz as qux - p[x] = y { + p[x] = y if { x = a + b y = {"foo": [{1, null}, true]} } diff --git a/ast/term_test.go b/ast/term_test.go index 6cec4a2b5f..f8ee09916b 100644 --- a/ast/term_test.go +++ b/ast/term_test.go @@ -1187,18 +1187,21 @@ func TestJSONWithOptLazyObjOptOut(t *testing.T) { } func assertTermEqual(t *testing.T, x *Term, y *Term) { + t.Helper() if !x.Equal(y) { t.Errorf("Failure on equality: \n%s and \n%s\n", x, y) } } func assertTermNotEqual(t *testing.T, x *Term, y *Term) { + t.Helper() if x.Equal(y) { t.Errorf("Failure on non-equality: \n%s and \n%s\n", x, y) } } func assertToString(t *testing.T, val Value, expected string) { + t.Helper() result := val.String() if result != expected { t.Errorf("Expected %v but got %v", expected, result) diff --git a/ast/transform_test.go b/ast/transform_test.go index 8c5a051aae..189badbd32 100644 --- a/ast/transform_test.go +++ b/ast/transform_test.go @@ -9,22 +9,22 @@ import ( ) func TestTransform(t *testing.T) { - module := MustParseModule(`package ex.this + mod := module(`package ex.this import input.foo import data.bar.this as qux import future.keywords.every -p = true { "this" = "that" } -p = "this" { false } -p["this"] { false } -p[y] = {"this": ["this"]} { false } -p = true { ["this" | "this"] } -p = n { count({"this", "that"}, n) with input.foo.this as {"this": true} } -p { false } else = "this" { "this" } else = ["this"] { true } -foo(x) = y { split(x, "this", y) } -p { every x in ["this"] { x == "this" } } -a.b.c.this["this"] = d { d := "this" } +p = true if { "this" = "that" } +p = "this" if { false } +p contains "this" if { false } +p[y] = {"this": ["this"]} if { false } +p = true if { ["this" | "this"] } +p = n if { count({"this", "that"}, n) with input.foo.this as {"this": true} } +p if { false } else = "this" if { "this" } else = ["this"] if { true } +foo(x) = y if { split(x, "this", y) } +p if { every x in ["this"] { x == "this" } } +a.b.c.this["this"] = d if { d := "this" } `) result, err := Transform(&GenericTransformer{ @@ -34,7 +34,7 @@ a.b.c.this["this"] = d { d := "this" } } return x, nil }, - }, module) + }, mod) if err != nil { t.Fatalf("Unexpected error during transform: %v", err) @@ -45,22 +45,22 @@ a.b.c.this["this"] = d { d := "this" } t.Fatalf("Expected module from transform but got: %v", result) } - expected := MustParseModule(`package ex.that + expected := module(`package ex.that import input.foo import data.bar.that as qux import future.keywords.every -p = true { "that" = "that" } -p = "that" { false } -p["that"] { false } -p[y] = {"that": ["that"]} { false } -p = true { ["that" | "that"] } -p = n { count({"that"}, n) with input.foo.that as {"that": true} } -p { false } else = "that" { "that" } else = ["that"] { true } -foo(x) = y { split(x, "that", y) } -p { every x in ["that"] { x == "that" } } -a.b.c.that["that"] = d { d := "that" } +p = true if { "that" = "that" } +p = "that" if { false } +p contains "that" if { false } +p[y] = {"that": ["that"]} if { false } +p = true if { ["that" | "that"] } +p = n if { count({"that"}, n) with input.foo.that as {"that": true} } +p if { false } else = "that" if { "that" } else = ["that"] if { true } +foo(x) = y if { split(x, "that", y) } +p if { every x in ["that"] { x == "that" } } +a.b.c.that["that"] = d if { d := "that" } `) if !expected.Equal(resultMod) { @@ -117,8 +117,8 @@ p := 7`, ParserOptions{ProcessAnnotation: true}) } func TestTransformRefsAndRuleHeads(t *testing.T) { - module := MustParseModule(`package test -p.q.this.fo[x] = y { x := "x"; y := "y" }`) + module := module(`package test +p.q.this.fo[x] = y if { x := "x"; y := "y" }`) result, err := TransformRefs(module, func(r Ref) (Value, error) { if r[0].Value.Compare(Var("p")) == 0 { diff --git a/ast/visit_test.go b/ast/visit_test.go index 23d4f57e72..f8ae5b0768 100644 --- a/ast/visit_test.go +++ b/ast/visit_test.go @@ -19,11 +19,11 @@ func (vis *testVis) Visit(x interface{}) bool { func TestVisitor(t *testing.T) { - rule := MustParseModule(`package a.b + rule := module(`package a.b import input.x.y as z -t[x] = y { +t[x] = y if { p[x] = {"foo": [y, 2, {"bar": 3}]} not q[x] y = [[x, z] | x = "x"; z = "z"] @@ -33,9 +33,9 @@ t[x] = y { count({1, 2, 3}, n) with input.foo.bar as x } -p { false } else { false } else { true } +p if { false } else if { false } else if { true } -fn([x, y]) = z { json.unmarshal(x, z); z > y } +fn([x, y]) = z if { json.unmarshal(x, z); z > y } `) vis := &testVis{} NewGenericVisitor(vis.Visit).Walk(rule) @@ -81,11 +81,11 @@ func TestWalkVars(t *testing.T) { } func TestGenericVisitor(t *testing.T) { - rule := MustParseModule(`package a.b + rule := module(`package a.b import input.x.y as z -t[x] = y { +t[x] = y if { p[x] = {"foo": [y, 2, {"bar": 3}]} not q[x] y = [[x, z] | x = "x"; z = "z"] @@ -95,9 +95,9 @@ t[x] = y { count({1, 2, 3}, n) with input.foo.bar as x } -p { false } else { false } else { true } +p if { false } else if { false } else if { true } -fn([x, y]) = z { json.unmarshal(x, z); z > y } +fn([x, y]) = z if { json.unmarshal(x, z); z > y } `) var elems []interface{} @@ -113,11 +113,11 @@ fn([x, y]) = z { json.unmarshal(x, z); z > y } } func TestBeforeAfterVisitor(t *testing.T) { - rule := MustParseModule(`package a.b + rule := module(`package a.b import input.x.y as z -t[x] = y { +t[x] = y if { p[x] = {"foo": [y, 2, {"bar": 3}]} not q[x] y = [[x, z] | x = "x"; z = "z"] @@ -127,9 +127,9 @@ t[x] = y { count({1, 2, 3}, n) with input.foo.bar as x } -p { false } else { false } else { true } +p if { false } else if { false } else if { true } -fn([x, y]) = z { json.unmarshal(x, z); z > y } +fn([x, y]) = z if { json.unmarshal(x, z); z > y } `) var before, after []interface{} diff --git a/compile/compile_test.go b/compile/compile_test.go index 572e0063e1..ef683c7591 100644 --- a/compile/compile_test.go +++ b/compile/compile_test.go @@ -1246,8 +1246,8 @@ func TestCompilerOptimizationWithConfiguredNamespace(t *testing.T) { t.Fatalf("expected two modules but got: %v", len(compiler.bundle.Modules)) } - // The compiler will strip the rego.v1 import from the module, so we need to compare it - // to a pure v1 module that doesn't require the import. + // The compiler will drop the rego.v1 import, so we need to affix the rego-version of the expected module to + // v1, to have its string serialization include 'if'/'else' keywords but not the rego.v1 import. optimizedExp := ast.MustParseModuleWithOpts(`package custom __not1_0_2__ = true if { data.test.q = _; _ }`, ast.ParserOptions{RegoVersion: ast.RegoV1})