cue/ast/astutil: allow recursive application
This is enabled by explicitly marking a node as ApplyRecursively.
This is necessary for cue import to be able to use astutil.
Change-Id: Iecebdfa299e39a624f00515c037e84cf4a712d1f
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3760
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/ast/astutil/apply.go b/cue/ast/astutil/apply.go
index 924387e..76d7907 100644
--- a/cue/ast/astutil/apply.go
+++ b/cue/ast/astutil/apply.go
@@ -64,17 +64,27 @@
// InsertAfter inserts n after the current Node in its containing struct.
// If the current Node is not part of a struct, InsertAfter panics.
- // Apply does not walk n.
+ // Unless n is wrapped by ApplyRecursively, Apply does not walk n.
InsertAfter(n ast.Node)
// InsertBefore inserts n before the current Node in its containing struct.
// If the current Node is not part of a struct, InsertBefore panics.
- // Apply will not walk n.
+ // Unless n is wrapped by ApplyRecursively, Apply does not walk n.
InsertBefore(n ast.Node)
self() *cursor
}
+// ApplyRecursively indicates that a node inserted with InsertBefore,
+// or InsertAfter should be processed recursively.
+func ApplyRecursively(n ast.Node) ast.Node {
+ return recursive{n}
+}
+
+type recursive struct {
+ ast.Node
+}
+
type info struct {
f *ast.File
current *declsCursor
@@ -191,8 +201,12 @@
if ast.Comments(n) != nil {
CopyComments(n, c.node)
}
+ if r, ok := n.(recursive); ok {
+ n = r.Node
+ } else {
+ c.replaced = true
+ }
c.node = n
- c.replaced = true
}
func (c *cursor) InsertAfter(n ast.Node) { panic("unsupported") }
@@ -249,15 +263,23 @@
type declsCursor struct {
*cursor
- decls, after []ast.Decl
- delete bool
+ decls, after, process []ast.Decl
+ delete bool
}
func (c *declsCursor) InsertAfter(n ast.Node) {
+ if r, ok := n.(recursive); ok {
+ n = r.Node
+ c.process = append(c.process, n.(ast.Decl))
+ }
c.after = append(c.after, n.(ast.Decl))
}
func (c *declsCursor) InsertBefore(n ast.Node) {
+ if r, ok := n.(recursive); ok {
+ n = r.Node
+ c.process = append(c.process, n.(ast.Decl))
+ }
c.decls = append(c.decls, n.(ast.Decl))
}
@@ -279,8 +301,18 @@
c.decls = append(c.decls, c.node.(ast.Decl))
}
c.delete = false
+ for i := 0; i < len(c.process); i++ {
+ x := c.process[i]
+ c.node = x
+ c.typ = &c.process[i]
+ applyCursor(v, c)
+ if c.delete {
+ panic("cannot delete a node that was added with InsertBefore or InsertAfter")
+ }
+ }
c.decls = append(c.decls, c.after...)
c.after = c.after[:0]
+ c.process = c.process[:0]
}
// TODO: ultimately, programmatically linked nodes have to be resolved
diff --git a/cue/ast/astutil/apply_test.go b/cue/ast/astutil/apply_test.go
index 79838ad..7c981bf 100644
--- a/cue/ast/astutil/apply_test.go
+++ b/cue/ast/astutil/apply_test.go
@@ -87,6 +87,56 @@
return true
},
}, {
+ name: "insert after recursive",
+ in: `
+ foo: {
+ a: 3 @test()
+ }
+ `,
+ out: `
+foo: {
+ a: 3 @test()
+ iam: {
+ here: new
+ there: new
+ }
+ everywhere: new
+}
+iam: {
+ here: new
+ there: new
+}
+everywhere: new
+ `,
+ before: func(c astutil.Cursor) bool {
+ switch x := c.Node().(type) {
+ case *ast.Field:
+ switch x.Label.(*ast.Ident).Name {
+ default:
+ c.InsertAfter(astutil.ApplyRecursively(&ast.Field{
+ Label: ast.NewIdent("iam"),
+ Value: &ast.StructLit{Elts: []ast.Decl{
+ &ast.Field{
+ Label: ast.NewIdent("here"),
+ Value: ast.NewIdent("new"),
+ },
+ }},
+ }))
+ case "iam":
+ c.InsertAfter(&ast.Field{
+ Label: ast.NewIdent("everywhere"),
+ Value: ast.NewIdent("new"),
+ })
+ case "here":
+ c.InsertAfter(&ast.Field{
+ Label: ast.NewIdent("there"),
+ Value: ast.NewIdent("new"),
+ })
+ case "everywhere":
+ }
+ }
+ return true
+ }}, {
name: "templates",
in: `
foo: {