modfile: fix Cleanup clobbering Line reference

Fixes golang/go#45130

Change-Id: I2dccba5e958911177f10a5104a182f86ff8378d9
Reviewed-on: https://go-review.googlesource.com/c/mod/+/303234
Reviewed-by: Michael Matloob <matloob@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: David Chase <drchase@google.com>
diff --git a/modfile/read.go b/modfile/read.go
index 2205682..de1b982 100644
--- a/modfile/read.go
+++ b/modfile/read.go
@@ -226,8 +226,9 @@
 				continue
 			}
 			if ww == 1 && len(stmt.RParen.Comments.Before) == 0 {
-				// Collapse block into single line.
-				line := &Line{
+				// Collapse block into single line but keep the Line reference used by the
+				// parsed File structure.
+				*stmt.Line[0] = Line{
 					Comments: Comments{
 						Before: commentsAdd(stmt.Before, stmt.Line[0].Before),
 						Suffix: commentsAdd(stmt.Line[0].Suffix, stmt.Suffix),
@@ -235,7 +236,7 @@
 					},
 					Token: stringsAdd(stmt.Token, stmt.Line[0].Token),
 				}
-				x.Stmt[w] = line
+				x.Stmt[w] = stmt.Line[0]
 				w++
 				continue
 			}
diff --git a/modfile/read_test.go b/modfile/read_test.go
index efc75e1..8f17eea 100644
--- a/modfile/read_test.go
+++ b/modfile/read_test.go
@@ -603,3 +603,134 @@
 		})
 	}
 }
+
+func TestCleanup(t *testing.T) {
+	for _, test := range []struct {
+		desc  string
+		want  string
+		input []Expr
+	}{
+		{
+			desc: "simple_lines",
+			want: `line: module a
+line: require b v1.0.0
+`,
+			input: []Expr{
+				&Line{
+					Token: []string{"module", "a"},
+				},
+				&Line{
+					Token: []string{"require", "b", "v1.0.0"},
+				},
+				&Line{
+					Token: nil,
+				},
+			},
+		}, {
+			desc: "line_block",
+			want: `line: module a
+block: require
+blockline: b v1.0.0
+blockline: c v1.0.0
+`,
+			input: []Expr{
+				&Line{
+					Token: []string{"module", "a"},
+				},
+				&LineBlock{
+					Token: []string{"require"},
+					Line: []*Line{
+						{
+							Token:   []string{"b", "v1.0.0"},
+							InBlock: true,
+						},
+						{
+							Token:   nil,
+							InBlock: true,
+						},
+						{
+							Token:   []string{"c", "v1.0.0"},
+							InBlock: true,
+						},
+					},
+				},
+			},
+		}, {
+			desc: "collapse",
+			want: `line: module a
+line: require b v1.0.0
+`,
+			input: []Expr{
+				&Line{
+					Token: []string{"module", "a"},
+				},
+				&LineBlock{
+					Token: []string{"require"},
+					Line: []*Line{
+						{
+							Token:   []string{"b", "v1.0.0"},
+							InBlock: true,
+						},
+						{
+							Token:   nil,
+							InBlock: true,
+						},
+					},
+				},
+			},
+		},
+	} {
+		t.Run(test.desc, func(t *testing.T) {
+			syntax := &FileSyntax{
+				Stmt: test.input,
+			}
+			syntax.Cleanup()
+
+			buf := &bytes.Buffer{}
+			for _, stmt := range syntax.Stmt {
+				switch stmt := stmt.(type) {
+				case *Line:
+					fmt.Fprintf(buf, "line: %v\n", strings.Join(stmt.Token, " "))
+				case *LineBlock:
+					fmt.Fprintf(buf, "block: %v\n", strings.Join(stmt.Token, " "))
+					for _, line := range stmt.Line {
+						fmt.Fprintf(buf, "blockline: %v\n", strings.Join(line.Token, " "))
+					}
+				}
+			}
+
+			got := strings.TrimSpace(buf.String())
+			want := strings.TrimSpace(test.want)
+			if got != want {
+				t.Errorf("got:\n%s\nwant:\n%s", got, want)
+			}
+		})
+	}
+}
+
+// Issue 45130: File.Cleanup breaks references so future edits do nothing
+func TestCleanupMaintainsRefs(t *testing.T) {
+	lineB := &Line{
+		Token:   []string{"b", "v1.0.0"},
+		InBlock: true,
+	}
+	syntax := &FileSyntax{
+		Stmt: []Expr{
+			&LineBlock{
+				Token: []string{"require"},
+				Line: []*Line{
+					lineB,
+					{
+						Token:   nil,
+						InBlock: true,
+					},
+				},
+			},
+		},
+	}
+	syntax.Cleanup()
+
+	if syntax.Stmt[0] != lineB {
+		t.Errorf("got:\n%v\nwant:\n%v", syntax.Stmt[0], lineB)
+	}
+}