[gopls-release-branch.0.5] internal/lsp: fix handling of //-style comments in CRLF files

As part of the earlier changes, I didn't realize that multiple //-style
comments would be grouped as one *ast.Comment, even though they are
multiple comment tokens. Handle the possibility of multiple consecutive
comment tokens.

Fixes golang/go#42923

Change-Id: I6bc6cbdfb28a8e60c699288528566e406f27514c
Reviewed-on: https://go-review.googlesource.com/c/tools/+/275012
Trust: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
(cherry picked from commit 73cf035baebf5c665180adbbb8f49d51e3d647e6)
Reviewed-on: https://go-review.googlesource.com/c/tools/+/275437
diff --git a/gopls/internal/regtest/formatting_test.go b/gopls/internal/regtest/formatting_test.go
index 80344c1..e6da9dc 100644
--- a/gopls/internal/regtest/formatting_test.go
+++ b/gopls/internal/regtest/formatting_test.go
@@ -162,31 +162,26 @@
 	})
 }
 
-// Reproduce golang/go#41057.
-func TestCRLF(t *testing.T) {
-	runner.Run(t, "-- main.go --", func(t *testing.T, env *Env) {
-		want := `package main
+// Tests various possibilities for comments in files with CRLF line endings.
+// Import organization in these files has historically been a source of bugs.
+func TestCRLFLineEndings(t *testing.T) {
+	for _, tt := range []struct {
+		issue, want string
+	}{
+		{
+			issue: "41057",
+			want: `package main
 
 /*
 Hi description
 */
 func Hi() {
 }
-`
-		crlf := strings.ReplaceAll(want, "\n", "\r\n")
-		env.CreateBuffer("main.go", crlf)
-		env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 1))
-		env.SaveBuffer("main.go")
-		got := env.Editor.BufferText("main.go")
-		if want != got {
-			t.Errorf("unexpected content after save:\n%s", tests.Diff(want, got))
-		}
-	})
-}
-
-func TestCRLF_42646(t *testing.T) {
-	runner.Run(t, "-- main.go --", func(t *testing.T, env *Env) {
-		want := `package main
+`,
+		},
+		{
+			issue: "42646",
+			want: `package main
 
 import (
 	"fmt"
@@ -211,15 +206,32 @@
 	const server_port = 8080
 	fmt.Printf("port: %d\n", server_port)
 }
-`
-		crlf := strings.ReplaceAll(want, "\n", "\r\n")
-		env.CreateBuffer("main.go", crlf)
-		env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 1))
-		env.OrganizeImports("main.go")
-		got := env.Editor.BufferText("main.go")
-		got = strings.ReplaceAll(got, "\r\n", "\n") // convert everything to LF for simplicity
-		if want != got {
-			t.Errorf("unexpected content after save:\n%s", tests.Diff(want, got))
-		}
-	})
+`,
+		},
+		{
+			issue: "42923",
+			want: `package main
+
+// Line 1.
+// aa
+type Tree struct {
+	arr []string
+}
+`,
+		},
+	} {
+		t.Run(tt.issue, func(t *testing.T) {
+			run(t, "-- main.go --", func(t *testing.T, env *Env) {
+				crlf := strings.ReplaceAll(tt.want, "\n", "\r\n")
+				env.CreateBuffer("main.go", crlf)
+				env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 1))
+				env.OrganizeImports("main.go")
+				got := env.Editor.BufferText("main.go")
+				got = strings.ReplaceAll(got, "\r\n", "\n") // convert everything to LF for simplicity
+				if tt.want != got {
+					t.Errorf("unexpected content after save:\n%s", tests.Diff(tt.want, got))
+				}
+			})
+		})
+	}
 }
diff --git a/internal/lsp/source/format.go b/internal/lsp/source/format.go
index 522f522..77cb5c4 100644
--- a/internal/lsp/source/format.go
+++ b/internal/lsp/source/format.go
@@ -227,21 +227,23 @@
 		pkgEnd := f.Name.End()
 		importEnd = maybeAdjustToLineEnd(pkgEnd, false)
 	}
-	for _, c := range f.Comments {
-		if end := tok.Offset(c.End()); end > importEnd {
-			startLine := tok.Position(c.Pos()).Line
-			endLine := tok.Position(c.End()).Line
+	for _, cgroup := range f.Comments {
+		for _, c := range cgroup.List {
+			if end := tok.Offset(c.End()); end > importEnd {
+				startLine := tok.Position(c.Pos()).Line
+				endLine := tok.Position(c.End()).Line
 
-			// Work around golang/go#41197 by checking if the comment might
-			// contain "\r", and if so, find the actual end position of the
-			// comment by scanning the content of the file.
-			startOffset := tok.Offset(c.Pos())
-			if startLine != endLine && bytes.Contains(src[startOffset:], []byte("\r")) {
-				if commentEnd := scanForCommentEnd(tok, src[startOffset:]); commentEnd > 0 {
-					end = startOffset + commentEnd
+				// Work around golang/go#41197 by checking if the comment might
+				// contain "\r", and if so, find the actual end position of the
+				// comment by scanning the content of the file.
+				startOffset := tok.Offset(c.Pos())
+				if startLine != endLine && bytes.Contains(src[startOffset:], []byte("\r")) {
+					if commentEnd := scanForCommentEnd(tok, src[startOffset:]); commentEnd > 0 {
+						end = startOffset + commentEnd
+					}
 				}
+				importEnd = maybeAdjustToLineEnd(tok.Pos(end), true)
 			}
-			importEnd = maybeAdjustToLineEnd(tok.Pos(end), true)
 		}
 	}
 	if importEnd > len(src) {