modfile: in SetRequireSeparateIndirect, convert lines to blocks

When reading go.mod, SetRequireSeparateIndirect will insert new
requirements into the last uncommented direct-only or indirect-only
block OR line. If the last such statement is a line,
SetRequireSeparateIndirect converts it to a block before inserting new
requirements. Cleanup will convert it back to a line later if no
requirements are inserted.

For golang/go#47733

Change-Id: Id8ee3b0ce2d005488ddb3d9a5349115bd93938e7
Reviewed-on: https://go-review.googlesource.com/c/mod/+/348576
Trust: Jay Conrod <jayconrod@google.com>
Run-TryBot: Jay Conrod <jayconrod@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
Reviewed-by: Michael Matloob <matloob@golang.org>
diff --git a/modfile/rule.go b/modfile/rule.go
index 9f7f115..98211a4 100644
--- a/modfile/rule.go
+++ b/modfile/rule.go
@@ -1087,9 +1087,8 @@
 		// We may insert new requirements into the last uncommented
 		// direct-only and indirect-only blocks. We may also move requirements
 		// to the opposite block if their indirect markings change.
-		lastDirectBlock, lastIndirectBlock *LineBlock
-		lastDirectBlockIndex               = -1
-		lastIndirectBlockIndex             = -1
+		lastDirectIndex   = -1
+		lastIndirectIndex = -1
 
 		// If there are no direct-only or indirect-only blocks, a new block may
 		// be inserted after the last require line or block.
@@ -1111,6 +1110,13 @@
 			}
 			lastRequireIndex = i
 			requireLineOrBlockCount++
+			if !hasComments(stmt.Comments) {
+				if isIndirect(stmt) {
+					lastIndirectIndex = i
+				} else {
+					lastDirectIndex = i
+				}
+			}
 
 		case *LineBlock:
 			if len(stmt.Token) == 0 || stmt.Token[0] != "require" {
@@ -1132,12 +1138,10 @@
 				}
 			}
 			if allDirect {
-				lastDirectBlock = stmt
-				lastDirectBlockIndex = i
+				lastDirectIndex = i
 			}
 			if allIndirect {
-				lastIndirectBlock = stmt
-				lastIndirectBlockIndex = i
+				lastIndirectIndex = i
 			}
 		}
 	}
@@ -1145,33 +1149,56 @@
 	oneFlatUncommentedBlock := requireLineOrBlockCount == 1 &&
 		!hasComments(*f.Syntax.Stmt[lastRequireIndex].Comment())
 
-	// Create direct and indirect blocks if needed. It's okay if they're empty.
-	// Cleanup will remove them and convert one-line blocks to lines.
-	if lastDirectBlock == nil {
-		lastDirectBlock = &LineBlock{Token: []string{"require"}}
-		i := len(f.Syntax.Stmt)
-		if lastIndirectBlockIndex >= 0 {
-			i = lastIndirectBlockIndex
-			lastIndirectBlockIndex++
-		} else if lastRequireIndex >= 0 {
-			i = lastRequireIndex + 1
-		}
+	// Create direct and indirect blocks if needed. Convert lines into blocks
+	// if needed. If we end up with an empty block or a one-line block,
+	// Cleanup will delete it or convert it to a line later.
+	insertBlock := func(i int) *LineBlock {
+		block := &LineBlock{Token: []string{"require"}}
 		f.Syntax.Stmt = append(f.Syntax.Stmt, nil)
 		copy(f.Syntax.Stmt[i+1:], f.Syntax.Stmt[i:])
-		f.Syntax.Stmt[i] = lastDirectBlock
-		lastDirectBlockIndex = i
+		f.Syntax.Stmt[i] = block
+		return block
 	}
 
-	if lastIndirectBlock == nil {
-		lastIndirectBlock = &LineBlock{Token: []string{"require"}}
-		i := len(f.Syntax.Stmt)
-		if lastDirectBlockIndex >= 0 {
-			i = lastDirectBlockIndex + 1
+	ensureBlock := func(i int) *LineBlock {
+		switch stmt := f.Syntax.Stmt[i].(type) {
+		case *LineBlock:
+			return stmt
+		case *Line:
+			block := &LineBlock{
+				Token: []string{"require"},
+				Line:  []*Line{stmt},
+			}
+			stmt.Token = stmt.Token[1:] // remove "require"
+			stmt.InBlock = true
+			f.Syntax.Stmt[i] = block
+			return block
+		default:
+			panic(fmt.Sprintf("unexpected statement: %v", stmt))
 		}
-		f.Syntax.Stmt = append(f.Syntax.Stmt, nil)
-		copy(f.Syntax.Stmt[i+1:], f.Syntax.Stmt[i:])
-		f.Syntax.Stmt[i] = lastIndirectBlock
-		lastIndirectBlockIndex = i
+	}
+
+	var lastDirectBlock *LineBlock
+	if lastDirectIndex < 0 {
+		if lastIndirectIndex >= 0 {
+			lastDirectIndex = lastIndirectIndex
+			lastIndirectIndex++
+		} else if lastRequireIndex >= 0 {
+			lastDirectIndex = lastRequireIndex + 1
+		} else {
+			lastDirectIndex = len(f.Syntax.Stmt)
+		}
+		lastDirectBlock = insertBlock(lastDirectIndex)
+	} else {
+		lastDirectBlock = ensureBlock(lastDirectIndex)
+	}
+
+	var lastIndirectBlock *LineBlock
+	if lastIndirectIndex < 0 {
+		lastIndirectIndex = lastDirectIndex + 1
+		lastIndirectBlock = insertBlock(lastIndirectIndex)
+	} else {
+		lastIndirectBlock = ensureBlock(lastIndirectIndex)
 	}
 
 	// Delete requirements we don't want anymore.
diff --git a/modfile/rule_test.go b/modfile/rule_test.go
index 93dfae0..21cea13 100644
--- a/modfile/rule_test.go
+++ b/modfile/rule_test.go
@@ -340,6 +340,28 @@
 		`,
 	},
 	{
+		`existing_line`,
+		`module m
+		require x.y/a v1.0.0
+		require x.y/c v1.0.0 // indirect
+		`,
+		[]require{
+			{"x.y/a", "v1.2.3", false},
+			{"x.y/b", "v1.2.3", false},
+			{"x.y/c", "v1.2.3", true},
+			{"x.y/d", "v1.2.3", true},
+		},
+		`module m
+		require (
+			x.y/a v1.2.3
+			x.y/b v1.2.3
+		)
+		require (
+			x.y/c v1.2.3 // indirect
+			x.y/d v1.2.3 // indirect
+		)`,
+	},
+	{
 		`existing_multi`,
 		`module m
 		require x.y/a v1.2.3
@@ -366,7 +388,10 @@
 			{"x.y/g", "v1.2.3", false},
 		},
 		`module m
-		require x.y/a v1.2.3
+		require (
+			x.y/a v1.2.3
+			x.y/h v1.2.3
+		)
 		require x.y/b v1.2.3 // indirect; demoted to indirect
 		require x.y/c v1.2.3 // not v1.2.3!
 		require x.y/d v1.2.3 // comment kept
@@ -376,7 +401,6 @@
 		require x.y/g v1.2.3
 		require x.y/i v1.2.3 // indirect
 		require x.y/j v1.2.3 // indirect
-		require x.y/h v1.2.3
 		`,
 	},
 	{