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: {