From ff6d2eb3127c26565583b10c4490e0baeaebf7ac Mon Sep 17 00:00:00 2001
From: Mateusz Poliwczak <mpoliwczak34@gmail.com>
Date: Tue, 24 Dec 2024 14:18:59 +0100
Subject: [PATCH] go/printer: predict the position of the '.' in SelectorExpr

Workarorund for issue #70978, see the same issue for more details.

Fixes #70978

Change-Id: I7934d51af0679ac6fc10128d77001b0092bd7392
---
 src/go/printer/nodes.go        |  8 ++++
 src/go/printer/printer_test.go | 75 ++++++++++++++++++++++++++++++++++
 2 files changed, 83 insertions(+)

diff --git a/src/go/printer/nodes.go b/src/go/printer/nodes.go
index a295a68d6f8417..28e008d4398b2a 100644
--- a/src/go/printer/nodes.go
+++ b/src/go/printer/nodes.go
@@ -1156,6 +1156,14 @@ func (p *printer) possibleSelectorExpr(expr ast.Expr, prec1, depth int) bool {
 // multiple lines.
 func (p *printer) selectorExpr(x *ast.SelectorExpr, depth int, isMethod bool) bool {
 	p.expr1(x.X, token.HighestPrec, depth)
+
+	// We don't have the position of the dot, so we have to predict it,
+	// to avoid issues with comment handling.
+	// See https://go.dev/issue/70978 and TestIssue70978 for more details.
+	if x.Sel.Pos().IsValid() && p.pos.Offset > p.posFor(x.Sel.Pos()).Offset {
+		p.setPos(x.Sel.Pos())
+	}
+
 	p.print(token.PERIOD)
 	if line := p.lineFor(x.Sel.Pos()); p.pos.IsValid() && p.pos.Line < line {
 		p.print(indent, newline)
diff --git a/src/go/printer/printer_test.go b/src/go/printer/printer_test.go
index 2a9c8be3003249..bde0089a245d43 100644
--- a/src/go/printer/printer_test.go
+++ b/src/go/printer/printer_test.go
@@ -16,6 +16,7 @@ import (
 	"io"
 	"os"
 	"path/filepath"
+	"strings"
 	"testing"
 	"time"
 )
@@ -863,3 +864,77 @@ func TestEmptyDecl(t *testing.T) { // issue 63566
 		}
 	}
 }
+
+func TestIssue70978(t *testing.T) {
+	cases := []struct {
+		src     string
+		want    string
+		newName string
+	}{
+		{
+			newName: "someotherfmtpackage",
+			src: `package main
+
+func main() {
+	// comment
+
+	fmt.Println() // Comment
+}
+`,
+			want: `package main
+
+func main() {
+	// comment
+
+	someotherfmtpackage.Println() // Comment
+}
+`,
+		},
+		{
+			newName: "someotherfmtpkg",
+			src: `package main
+
+func main() {
+	// comment
+
+	fmt.Println() // Comment
+}
+`,
+			want: `package main
+
+func main() {
+	// comment
+
+	someotherfmtpkg.Println() // Comment
+}
+`,
+		},
+	}
+
+	for _, tt := range cases {
+		fset := token.NewFileSet()
+		f, err := parser.ParseFile(fset, "test.go", tt.src, parser.SkipObjectResolution|parser.ParseComments)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		ast.Inspect(f, func(n ast.Node) bool {
+			switch n := n.(type) {
+			case *ast.SelectorExpr:
+				switch x := n.X.(type) {
+				case *ast.Ident:
+					x.Name = tt.newName
+				}
+			}
+			return true
+		})
+
+		var b strings.Builder
+		config := Config{Mode: UseSpaces | TabIndent, Tabwidth: 8}
+		config.Fprint(&b, fset, f)
+		got := b.String()
+		if got != tt.want {
+			t.Errorf("unexpected Fprint output:\n%s\nwant:\n%s", got, tt.want)
+		}
+	}
+}