Skip to content

Commit

Permalink
parse: include rule head location when requested
Browse files Browse the repository at this point in the history
Prior to this commit, when the AST information
were serialized to JSON, rules expressed as

discussed in open-policy-agent#5790 included only the location
of their value and not the one of the rule
ref var itself. Now the AST serialized to
JSON includes this information.

Fixes: open-policy-agent#5790
Signed-off-by: Gianluca Oldani <[email protected]>
  • Loading branch information
Trolloldem committed Apr 12, 2023
1 parent 002c980 commit 70e0331
Show file tree
Hide file tree
Showing 3 changed files with 549 additions and 31 deletions.
100 changes: 70 additions & 30 deletions ast/parser_ext.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,14 +258,18 @@ func ParseCompleteDocRuleFromEqExpr(module *Module, lhs, rhs *Term) (*Rule, erro
}
head.Value = rhs
head.Location = lhs.Location
head.setJSONOptions(lhs.jsonOptions)

body := NewBody(NewExpr(BooleanTerm(true).SetLocation(rhs.Location)).SetLocation(rhs.Location))
body[0].setJSONOptions(rhs.jsonOptions)
setTermsJSONOptions(body[0].Terms, &rhs.jsonOptions)

return &Rule{
Location: lhs.Location,
Head: head,
Body: NewBody(
NewExpr(BooleanTerm(true).SetLocation(rhs.Location)).SetLocation(rhs.Location),
),
Module: module,
Location: lhs.Location,
Head: head,
Body: body,
Module: module,
jsonOptions: lhs.jsonOptions,
}, nil
}

Expand All @@ -280,14 +284,19 @@ func ParseCompleteDocRuleWithDotsFromTerm(module *Module, term *Term) (*Rule, er
}
head := RefHead(ref, BooleanTerm(true).SetLocation(term.Location))
head.Location = term.Location
head.jsonOptions = term.jsonOptions

body := NewBody(NewExpr(BooleanTerm(true).SetLocation(term.Location)).SetLocation(term.Location))
body[0].setJSONOptions(term.jsonOptions)
setTermsJSONOptions(body[0].Terms, &term.jsonOptions)

return &Rule{
Location: term.Location,
Head: head,
Body: NewBody(
NewExpr(BooleanTerm(true).SetLocation(term.Location)).SetLocation(term.Location),
),
Module: module,
Body: body,
Module: module,

jsonOptions: term.jsonOptions,
}, nil
}

Expand All @@ -309,14 +318,18 @@ func ParsePartialObjectDocRuleFromEqExpr(module *Module, lhs, rhs *Term) (*Rule,
head.Key = ref[1]
}
head.Location = rhs.Location
head.jsonOptions = rhs.jsonOptions

body := NewBody(NewExpr(BooleanTerm(true).SetLocation(rhs.Location)).SetLocation(rhs.Location))
body[0].setJSONOptions(rhs.jsonOptions)
setTermsJSONOptions(body[0].Terms, &rhs.jsonOptions)

rule := &Rule{
Location: rhs.Location,
Head: head,
Body: NewBody(
NewExpr(BooleanTerm(true).SetLocation(rhs.Location)).SetLocation(rhs.Location),
),
Module: module,
Location: rhs.Location,
Head: head,
Body: body,
Module: module,
jsonOptions: rhs.jsonOptions,
}

return rule, nil
Expand Down Expand Up @@ -344,14 +357,18 @@ func ParsePartialSetDocRuleFromTerm(module *Module, term *Term) (*Rule, error) {
head.Key = ref[1]
}
head.Location = term.Location
head.jsonOptions = term.jsonOptions

body := NewBody(NewExpr(BooleanTerm(true).SetLocation(term.Location)).SetLocation(term.Location))
body[0].setJSONOptions(term.jsonOptions)
setTermsJSONOptions(body[0].Terms, &term.jsonOptions)

rule := &Rule{
Location: term.Location,
Head: head,
Body: NewBody(
NewExpr(BooleanTerm(true).SetLocation(term.Location)).SetLocation(term.Location),
),
Module: module,
Location: term.Location,
Head: head,
Body: body,
Module: module,
jsonOptions: term.jsonOptions,
}

return rule, nil
Expand All @@ -377,12 +394,18 @@ func ParseRuleFromCallEqExpr(module *Module, lhs, rhs *Term) (*Rule, error) {
head := RefHead(ref, rhs)
head.Location = lhs.Location
head.Args = Args(call[1:])
head.jsonOptions = lhs.jsonOptions

body := NewBody(NewExpr(BooleanTerm(true).SetLocation(rhs.Location)).SetLocation(rhs.Location))
body[0].setJSONOptions(rhs.jsonOptions)
setTermsJSONOptions(body[0].Terms, &rhs.jsonOptions)

rule := &Rule{
Location: lhs.Location,
Head: head,
Body: NewBody(NewExpr(BooleanTerm(true).SetLocation(rhs.Location)).SetLocation(rhs.Location)),
Module: module,
Location: lhs.Location,
Head: head,
Body: body,
Module: module,
jsonOptions: lhs.jsonOptions,
}

return rule, nil
Expand All @@ -404,12 +427,18 @@ func ParseRuleFromCallExpr(module *Module, terms []*Term) (*Rule, error) {
head := RefHead(ref, BooleanTerm(true).SetLocation(loc))
head.Location = loc
head.Args = terms[1:]
head.jsonOptions = terms[0].jsonOptions

body := NewBody(NewExpr(BooleanTerm(true).SetLocation(loc)).SetLocation(loc))
body[0].setJSONOptions(terms[0].jsonOptions)
setTermsJSONOptions(body[0].Terms, &terms[0].jsonOptions)

rule := &Rule{
Location: loc,
Head: head,
Module: module,
Body: NewBody(NewExpr(BooleanTerm(true).SetLocation(loc)).SetLocation(loc)),
Location: loc,
Head: head,
Module: module,
Body: body,
jsonOptions: terms[0].jsonOptions,
}
return rule, nil
}
Expand Down Expand Up @@ -686,6 +715,17 @@ func setRuleModule(rule *Rule, module *Module) {
}
}

func setTermsJSONOptions(bodyTerms interface{}, jsonOptions *JSONOptions) {
switch terms := bodyTerms.(type) {
case []*Term:
for _, term := range terms {
term.setJSONOptions(*jsonOptions)
}
case customJSON:
terms.setJSONOptions(*jsonOptions)
}
}

// ParserErrorDetail holds additional details for parser errors.
type ParserErrorDetail struct {
Line string `json:"line"`
Expand Down
97 changes: 97 additions & 0 deletions ast/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3276,6 +3276,56 @@ func TestWildcards(t *testing.T) {
})
}

func TestRuleFromBodyJSONOptions(t *testing.T) {
tests := []string{
`pi = 3.14159`,
`p[x] { x = 1 }`,
`greeting = "hello"`,
`cores = [{0: 1}, {1: 2}]`,
`wrapper = cores[0][1]`,
`pi = [3, 1, 4, x, y, z]`,
`foo["bar"] = "buz"`,
`foo["9"] = "10"`,
`foo.buz = "bar"`,
`foo.fizz.buzz`,
`bar[1]`,
`bar[[{"foo":"baz"}]]`,
`bar.qux`,
`input = 1`,
`data = 2`,
`f(1) = 2`,
`f(1)`,
`d1 := 1234`,
}

parserOpts := ParserOptions{ProcessAnnotation: true}
parserOpts.JSONOptions = &JSONOptions{
MarshalOptions: JSONMarshalOptions{
IncludeLocation: NodeToggle{
Term: true,
Package: true,
Comment: true,
Import: true,
Rule: true,
Head: true,
Expr: true,
SomeDecl: true,
Every: true,
With: true,
Annotations: true,
AnnotationsRef: true,
},
},
}

for _, tc := range tests {
t.Run(tc, func(t *testing.T) {
testModule := "package a.b.c\n" + tc
assertParseModuleJSONOptions(t, tc, testModule, parserOpts)
})
}
}

func TestRuleModulePtr(t *testing.T) {
mod := `package test
Expand Down Expand Up @@ -5053,6 +5103,53 @@ func assertParseModule(t *testing.T, msg string, input string, correct *Module,

}

func assertParseModuleJSONOptions(t *testing.T, msg string, input string, opts ...ParserOptions) {
opt := ParserOptions{}
if len(opts) == 1 {
opt = opts[0]
}
m, err := ParseModuleWithOpts("", input, opt)
if err != nil {
t.Errorf("Error on test \"%s\": parse error on %s: %s", msg, input, err)
return
}

if len(m.Rules) != 1 {
t.Fatalf("Error on test \"%s\": expected 1 rule but got %d", msg, len(m.Rules))
}

rule := m.Rules[0]
if rule.Head.jsonOptions != *opt.JSONOptions {
t.Fatalf("Error on test \"%s\": expected rule Head JSONOptions\n%v\n, got\n%v", msg, *opt.JSONOptions, rule.Head.jsonOptions)
}
if rule.Body[0].jsonOptions != *opt.JSONOptions {
t.Fatalf("Error on test \"%s\": expected rule Body JSONOptions\n%v\n, got\n%v", msg, *opt.JSONOptions, rule.Body[0].jsonOptions)
}
switch terms := rule.Body[0].Terms.(type) {
case []*Term:
for _, term := range terms {
if term.jsonOptions != *opt.JSONOptions {
t.Fatalf("Error on test \"%s\": expected body Term JSONOptions\n%v\n, got\n%v", msg, *opt.JSONOptions, term.jsonOptions)
}
}
case *SomeDecl:
if terms.jsonOptions != *opt.JSONOptions {
t.Fatalf("Error on test \"%s\": expected body Term JSONOptions\n%v\n, got\n%v", msg, *opt.JSONOptions, terms.jsonOptions)
}
case *Every:
if terms.jsonOptions != *opt.JSONOptions {
t.Fatalf("Error on test \"%s\": expected body Term JSONOptions\n%v\n, got\n%v", msg, *opt.JSONOptions, terms.jsonOptions)
}
case *Term:
if terms.jsonOptions != *opt.JSONOptions {
t.Fatalf("Error on test \"%s\": expected body Term JSONOptions\n%v\n, got\n%v", msg, *opt.JSONOptions, terms.jsonOptions)
}
}
if rule.jsonOptions != *opt.JSONOptions {
t.Fatalf("Error on test \"%s\": expected rule JSONOptions\n%v\n, got\n%v", msg, *opt.JSONOptions, rule.jsonOptions)
}
}

func assertParseModuleError(t *testing.T, msg, input string) {
m, err := ParseModule("", input)
if err == nil {
Expand Down
Loading

0 comments on commit 70e0331

Please sign in to comment.