Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better default suggestions #848

Merged
merged 1 commit into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
225 changes: 0 additions & 225 deletions bundle/regal/ast/ast.rego
Original file line number Diff line number Diff line change
Expand Up @@ -90,236 +90,11 @@ identifiers := rule_and_function_names | imported_identifiers

rule_names contains ref_to_string(rule.head.ref) if some rule in rules

# METADATA
# description: parse provided snippet with a generic package declaration added
policy(snippet) := regal.parse_module("policy.rego", concat("", [
"package policy\n\n",
snippet,
]))

# METADATA
# description: parses provided policy with all future keywords imported. Primarily for testing.
with_rego_v1(policy) := regal.parse_module("policy.rego", concat("", [
`package policy

import rego.v1

`,
policy,
]))

_find_nested_vars(obj) := [value |
walk(obj, [_, value])
value.type == "var"
indexof(value.value, "$") == -1
]

# simple assignment, i.e. `x := 100` returns `x`
# always returns a single var, but wrapped in an
# array for consistency
_find_assign_vars(_, value) := var if {
value[1].type == "var"
var := [value[1]]
}

# 'destructuring' array assignment, i.e.
# [a, b, c] := [1, 2, 3]
# or
# {a: b} := {"foo": "bar"}
_find_assign_vars(_, value) := var if {
value[1].type in {"array", "object"}
var := _find_nested_vars(value[1])
}

# var declared via `some`, i.e. `some x` or `some x, y`
_find_some_decl_vars(_, value) := [v |
some v in value
v.type == "var"
]

# single var declared via `some in`, i.e. `some x in y`
_find_some_in_decl_vars(_, value) := var if {
arr := value[0].value
count(arr) == 3

var := _find_nested_vars(arr[1])
}

# two vars declared via `some in`, i.e. `some x, y in z`
_find_some_in_decl_vars(_, value) := var if {
arr := value[0].value
count(arr) == 4

var := [v |
some i in [1, 2]
some v in _find_nested_vars(arr[i])
]
}

# find vars like input[x].foo[y] where x and y are vars
# note: value.type == "ref" check must have been done before calling this function
find_ref_vars(value) := [var |
some i, var in value.value

# ignore first element, as it is the base ref (like input or data)
i > 0
var.type == "var"
]

# one or two vars declared via `every`, i.e. `every x in y {}`
# or `every`, i.e. `every x, y in y {}`
_find_every_vars(_, value) := var if {
key_var := [v | v := value.key; v.type == "var"; indexof(v.value, "$") == -1]
val_var := [v | v := value.value; v.type == "var"; indexof(v.value, "$") == -1]

var := array.concat(key_var, val_var)
}

# METADATA
# description: |
# traverses all nodes in provided term (using `walk`), and returns an array with
# all variables declared in term, i,e [x, y] or {x: y}, etc.
find_term_vars(term) := [value |
walk(term, [_, value])

value.type == "var"
]

_find_set_or_array_comprehension_vars(value) := [value.value.term] if {
value.value.term.type == "var"
} else := find_term_vars(value.value.term)

_find_object_comprehension_vars(value) := array.concat(key, val) if {
key := [value.value.key | value.value.key.type == "var"]
val := [value.value.value | value.value.value.type == "var"]
}

_find_vars(_, value, last) := find_term_vars(function_ret_args(fn_name, value)) if {
last == "terms"
value[0].type == "ref"
value[0].value[0].type == "var"
value[0].value[0].value != "assign"

fn_name := ref_to_string(value[0].value)

not contains(fn_name, "$")
fn_name in all_function_names # regal ignore:external-reference
function_ret_in_args(fn_name, value)
}

_find_vars(path, value, last) := _find_assign_vars(path, value) if {
last == "terms"
value[0].type == "ref"
value[0].value[0].type == "var"
value[0].value[0].value == "assign"
}

# `=` isn't necessarily assignment, and only considering the variable on the
# left-hand side is equally dubious, but we'll treat `x = 1` as `x := 1` for
# the purpose of this function until we have a more robust way of dealing with
# unification
_find_vars(path, value, last) := _find_assign_vars(path, value) if {
last == "terms"
value[0].type == "ref"
value[0].value[0].type == "var"
value[0].value[0].value == "eq"
}

_find_vars(_, value, _) := find_ref_vars(value) if value.type == "ref"

_find_vars(path, value, last) := _find_some_in_decl_vars(path, value) if {
last == "symbols"
value[0].type == "call"
}

_find_vars(path, value, last) := _find_some_decl_vars(path, value) if {
last == "symbols"
value[0].type != "call"
}

_find_vars(path, value, last) := _find_every_vars(path, value) if {
last == "terms"
value.domain
}

_find_vars(_, value, _) := _find_set_or_array_comprehension_vars(value) if {
value.type in {"setcomprehension", "arraycomprehension"}
}

_find_vars(_, value, _) := _find_object_comprehension_vars(value) if value.type == "objectcomprehension"

find_some_decl_vars(rule) := [var |
walk(rule, [path, value])

regal.last(path) == "symbols"
value[0].type != "call"

some var in _find_some_decl_vars(path, value)
]

# METADATA
# description: |
# traverses all nodes under provided node (using `walk`), and returns an array with
# all variables declared via assignment (:=), `some`, `every` and in comprehensions
find_vars(node) := [var |
walk(node, [path, value])

some var in _find_vars(path, value, regal.last(path))
]

_function_arg_names(rule) := {arg.value |
some arg in rule.head.args
arg.type == "var"
}

# METADATA
# description: |
# finds all vars declared in `rule` *before* the `location` provided
# note: this isn't 100% accurate, as it doesn't take into account `=`
# assignments / unification, but it's likely good enough since other rules
# recommend against those
find_vars_in_local_scope(rule, location) := [var |
some var in find_vars(rule)
not startswith(var.value, "$")
_before_location(var, location)
]

_before_location(var, location) if var.location.row < location.row

_before_location(var, location) if {
var.location.row == location.row
var.location.col < location.col
}

# METADATA
# description: find *only* names in the local scope, and not e.g. rule names
find_names_in_local_scope(rule, location) := names if {
fn_arg_names := _function_arg_names(rule)
var_names := {var.value | some var in find_vars_in_local_scope(rule, location)}

names := fn_arg_names | var_names
}

# METADATA
# description: |
# similar to `find_vars_in_local_scope`, but returns all variable names in scope
# of the given location *and* the rule names present in the scope (i.e. module)
find_names_in_scope(rule, location) := names if {
locals := find_names_in_local_scope(rule, location)

# parens below added by opa-fmt :)
names := (rule_names | imported_identifiers) | locals
}

# METADATA
# description: |
# find all variables declared via `some` declarations (and *not* `some .. in`)
# in the scope of the given location
find_some_decl_names_in_scope(rule, location) := {some_var.value |
some some_var in find_some_decl_vars(rule)
_before_location(some_var, location)
}

# METADATA
# description: |
# determine if var in ref (e.g. `x` in `input[x]`) is used as input or output
Expand Down
Loading