mirror of
https://github.com/google/go-jsonnet.git
synced 2025-08-07 23:07:14 +02:00
74 lines
2.0 KiB
Go
74 lines
2.0 KiB
Go
package traversal
|
|
|
|
// Package traversal provides relatively lightweight checks
|
|
// which can all fit within one traversal of the AST.
|
|
// Currently available checks:
|
|
// * Loop detection
|
|
// TODO(sbarzowski) add more
|
|
|
|
import (
|
|
"github.com/google/go-jsonnet/ast"
|
|
"github.com/google/go-jsonnet/linter/internal/utils"
|
|
|
|
"github.com/google/go-jsonnet/internal/parser"
|
|
)
|
|
|
|
func findLoopingInChildren(node ast.Node, vars map[ast.Identifier]ast.Node, runOf map[ast.Identifier]int, currentRun int, ec *utils.ErrCollector) bool {
|
|
for _, c := range parser.DirectChildren(node) {
|
|
found := findLooping(c, vars, runOf, currentRun, ec)
|
|
if found {
|
|
return found
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func findLooping(node ast.Node, vars map[ast.Identifier]ast.Node, runOf map[ast.Identifier]int, currentRun int, ec *utils.ErrCollector) bool {
|
|
switch node := node.(type) {
|
|
case *ast.Var:
|
|
_, varFromThisLocal := vars[node.Id]
|
|
if !varFromThisLocal {
|
|
return false
|
|
}
|
|
firstRun, reachedBefore := runOf[node.Id]
|
|
if !reachedBefore {
|
|
runOf[node.Id] = currentRun
|
|
return findLooping(vars[node.Id], vars, runOf, currentRun, ec)
|
|
} else if firstRun == currentRun {
|
|
// TODO(sbarzowski) Maybe report the whole path of the looping, rather than just the last element
|
|
ec.StaticErr("Endless loop in local definition", node.Loc())
|
|
return true
|
|
}
|
|
}
|
|
return findLoopingInChildren(node, vars, runOf, currentRun, ec)
|
|
}
|
|
|
|
func findLoopingInLocal(node *ast.Local, ec *utils.ErrCollector) {
|
|
vars := make(map[ast.Identifier]ast.Node)
|
|
runOf := make(map[ast.Identifier]int)
|
|
for _, b := range node.Binds {
|
|
if b.Body == nil {
|
|
panic("Body cannot be nil")
|
|
}
|
|
vars[b.Variable] = b.Body
|
|
}
|
|
for i, b := range node.Binds {
|
|
found := findLooping(b.Body, vars, runOf, i, ec)
|
|
if found {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Traverse visits all nodes in the AST and runs appropriate
|
|
// checks.
|
|
func Traverse(node ast.Node, ec *utils.ErrCollector) {
|
|
switch node := node.(type) {
|
|
case *ast.Local:
|
|
findLoopingInLocal(node, ec)
|
|
}
|
|
for _, c := range parser.Children(node) {
|
|
Traverse(c, ec)
|
|
}
|
|
}
|