internal/lsp: fix rename with Windows line endings

Replacing the text in the comment line-by-line prevents issues to do
with CRLF/LF line endings.

No test, because txtar expects LF line endings, and I didn't think it
was worth more invasive changes to handle this.

Fixes golang/go#39364.

Change-Id: Ia26b311a851396e4dde1954ebfc1b40c0a3c04fb
Reviewed-on: https://go-review.googlesource.com/c/tools/+/240757
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index d168076..02252bf 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -702,13 +702,11 @@
 		val := res[uri]
 		got += val
 	}
-
-	renamed := string(r.data.Golden(tag, filename, func() ([]byte, error) {
+	want := string(r.data.Golden(tag, filename, func() ([]byte, error) {
 		return []byte(got), nil
 	}))
-
-	if renamed != got {
-		t.Errorf("rename failed for %s, expected:\n%v\ngot:\n%v", newText, renamed, got)
+	if want != got {
+		t.Errorf("rename failed for %s:\n%s", newText, tests.Diff(want, got))
 	}
 }
 
diff --git a/internal/lsp/source/rename.go b/internal/lsp/source/rename.go
index beba1ad..2c1cd57 100644
--- a/internal/lsp/source/rename.go
+++ b/internal/lsp/source/rename.go
@@ -12,6 +12,7 @@
 	"go/token"
 	"go/types"
 	"regexp"
+	"strings"
 
 	"golang.org/x/tools/go/types/typeutil"
 	"golang.org/x/tools/internal/event"
@@ -199,17 +200,28 @@
 		}
 
 		// Perform the rename in doc comments declared in the original package.
+		// go/parser strips out \r\n returns from the comment text, so go
+		// line-by-line through the comment text to get the correct positions.
 		for _, comment := range doc.List {
-			for _, locs := range docRegexp.FindAllStringIndex(comment.Text, -1) {
-				rng := span.NewRange(r.fset, comment.Pos()+token.Pos(locs[0]), comment.Pos()+token.Pos(locs[1]))
-				spn, err := rng.Span()
-				if err != nil {
-					return nil, err
+			lines := strings.Split(comment.Text, "\n")
+			tok := r.fset.File(comment.Pos())
+			commentLine := tok.Position(comment.Pos()).Line
+			for i, line := range lines {
+				lineStart := comment.Pos()
+				if i > 0 {
+					lineStart = tok.LineStart(commentLine + i)
 				}
-				result[spn.URI()] = append(result[spn.URI()], diff.TextEdit{
-					Span:    spn,
-					NewText: r.to,
-				})
+				for _, locs := range docRegexp.FindAllIndex([]byte(line), -1) {
+					rng := span.NewRange(r.fset, lineStart+token.Pos(locs[0]), lineStart+token.Pos(locs[1]))
+					spn, err := rng.Span()
+					if err != nil {
+						return nil, err
+					}
+					result[spn.URI()] = append(result[spn.URI()], diff.TextEdit{
+						Span:    spn,
+						NewText: r.to,
+					})
+				}
 			}
 		}
 	}
diff --git a/internal/lsp/testdata/lsp/primarymod/rename/b/b.go b/internal/lsp/testdata/lsp/primarymod/rename/b/b.go
index 2fd4c7d..8455f03 100644
--- a/internal/lsp/testdata/lsp/primarymod/rename/b/b.go
+++ b/internal/lsp/testdata/lsp/primarymod/rename/b/b.go
@@ -7,3 +7,14 @@
 	a = 2
 	_ = a
 }
+
+var (
+	// Hello there.
+	// Foo does the thing.
+	Foo int //@rename("Foo", "Bob")
+)
+
+/*
+Hello description
+*/
+func Hello() {} //@rename("Hello", "Goodbye")
diff --git a/internal/lsp/testdata/lsp/primarymod/rename/b/b.go.golden b/internal/lsp/testdata/lsp/primarymod/rename/b/b.go.golden
index c39509e..c8691ef 100644
--- a/internal/lsp/testdata/lsp/primarymod/rename/b/b.go.golden
+++ b/internal/lsp/testdata/lsp/primarymod/rename/b/b.go.golden
@@ -9,5 +9,60 @@
 	_ = error
 }
 
+var (
+	// Hello there.
+	// Foo does the thing.
+	Foo int //@rename("Foo", "Bob")
+)
+
+/*
+Hello description
+*/
+func Hello() {} //@rename("Hello", "Goodbye")
+
 -- uint-rename --
 builtin object "int"
+-- Bob-rename --
+package b
+
+var c int //@rename("int", "uint")
+
+func _() {
+	a := 1 //@rename("a", "error")
+	a = 2
+	_ = a
+}
+
+var (
+	// Hello there.
+	// Bob does the thing.
+	Bob int //@rename("Foo", "Bob")
+)
+
+/*
+Hello description
+*/
+func Hello() {} //@rename("Hello", "Goodbye")
+
+-- Goodbye-rename --
+package b
+
+var c int //@rename("int", "uint")
+
+func _() {
+	a := 1 //@rename("a", "error")
+	a = 2
+	_ = a
+}
+
+var (
+	// Hello there.
+	// Foo does the thing.
+	Foo int //@rename("Foo", "Bob")
+)
+
+/*
+Goodbye description
+*/
+func Goodbye() {} //@rename("Hello", "Goodbye")
+
diff --git a/internal/lsp/testdata/lsp/summary.txt.golden b/internal/lsp/testdata/lsp/summary.txt.golden
index 1563cb0..bf5762f 100644
--- a/internal/lsp/testdata/lsp/summary.txt.golden
+++ b/internal/lsp/testdata/lsp/summary.txt.golden
@@ -16,7 +16,7 @@
 TypeDefinitionsCount = 2
 HighlightsCount = 69
 ReferencesCount = 11
-RenamesCount = 25
+RenamesCount = 27
 PrepareRenamesCount = 7
 SymbolsCount = 3
 WorkspaceSymbolsCount = 2
diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go
index 4bbdda6..88d07a3 100644
--- a/internal/lsp/tests/tests.go
+++ b/internal/lsp/tests/tests.go
@@ -820,7 +820,7 @@
 	}))
 	got := buf.String()
 	if want != got {
-		t.Errorf("test summary does not match: %v", Diff(want, got))
+		t.Errorf("test summary does not match:\n%s", Diff(want, got))
 	}
 }