Skip to content

Commit 41f04a0

Browse files
committed
internal/refactor/inline: fix comment movement due to added imports
Logically separate the rewriting of imports from the rest of the file, so that floating comments don't wander into the import block. Fixes golang/go#67336 Updates golang/go#67335 Change-Id: I14bcbe1d15bf9abaed7535bdcf871b25c47c9a11 Reviewed-on: https://go-review.googlesource.com/c/tools/+/629979 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
1 parent 0841661 commit 41f04a0

File tree

4 files changed

+276
-17
lines changed

4 files changed

+276
-17
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
This is the test case from golang/go#67335, where the inlining resulted in bad
2+
formatting.
3+
4+
-- go.mod --
5+
module example.com
6+
7+
go 1.20
8+
9+
-- define/my/typ/foo.go --
10+
package typ
11+
type T int
12+
13+
-- some/other/pkg/foo.go --
14+
package pkg
15+
import "context"
16+
import "example.com/define/my/typ"
17+
func Foo(typ.T) context.Context{ return nil }
18+
19+
-- one/more/pkg/foo.go --
20+
package pkg
21+
func Bar() {}
22+
23+
-- to/be/inlined/foo.go --
24+
package inlined
25+
26+
import "context"
27+
import "example.com/some/other/pkg"
28+
import "example.com/define/my/typ"
29+
30+
func Baz(ctx context.Context) context.Context {
31+
return pkg.Foo(typ.T(5))
32+
}
33+
34+
-- b/c/foo.go --
35+
package c
36+
import (
37+
"context"
38+
"example.com/to/be/inlined"
39+
"example.com/one/more/pkg"
40+
)
41+
42+
const (
43+
// This is a variable
44+
someConst = 5
45+
)
46+
47+
func foo() {
48+
inlined.Baz(context.TODO()) //@ codeaction("Baz", "refactor.inline.call", result=inline)
49+
pkg.Bar()
50+
}
51+
52+
-- @inline/b/c/foo.go --
53+
package c
54+
55+
import (
56+
"context"
57+
58+
"example.com/define/my/typ"
59+
"example.com/one/more/pkg"
60+
pkg0 "example.com/some/other/pkg"
61+
)
62+
63+
const (
64+
// This is a variable
65+
someConst = 5
66+
)
67+
68+
func foo() {
69+
var _ context.Context = context.TODO()
70+
pkg0.Foo(typ.T(5)) //@ codeaction("Baz", "refactor.inline.call", result=inline)
71+
pkg.Bar()
72+
}

internal/refactor/inline/inline.go

Lines changed: 89 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"go/constant"
1212
"go/format"
1313
"go/parser"
14+
"go/printer"
1415
"go/token"
1516
"go/types"
1617
pathpkg "path"
@@ -202,17 +203,37 @@ func (st *state) inline() (*Result, error) {
202203
}
203204
}
204205

206+
// File rewriting. This proceeds in multiple passes, in order to maximally
207+
// preserve comment positioning. (This could be greatly simplified once
208+
// comments are stored in the tree.)
209+
//
205210
// Don't call replaceNode(caller.File, res.old, res.new)
206211
// as it mutates the caller's syntax tree.
207212
// Instead, splice the file, replacing the extent of the "old"
208213
// node by a formatting of the "new" node, and re-parse.
209214
// We'll fix up the imports on this new tree, and format again.
210-
var f *ast.File
215+
//
216+
// Inv: f is the result of parsing content, using fset.
217+
var (
218+
content = caller.Content
219+
fset = caller.Fset
220+
f *ast.File // parsed below
221+
)
222+
reparse := func() error {
223+
const mode = parser.ParseComments | parser.SkipObjectResolution | parser.AllErrors
224+
f, err = parser.ParseFile(fset, "callee.go", content, mode)
225+
if err != nil {
226+
// Something has gone very wrong.
227+
logf("failed to reparse <<%s>>", string(content)) // debugging
228+
return err
229+
}
230+
return nil
231+
}
211232
{
212-
start := offsetOf(caller.Fset, res.old.Pos())
213-
end := offsetOf(caller.Fset, res.old.End())
233+
start := offsetOf(fset, res.old.Pos())
234+
end := offsetOf(fset, res.old.End())
214235
var out bytes.Buffer
215-
out.Write(caller.Content[:start])
236+
out.Write(content[:start])
216237
// TODO(adonovan): might it make more sense to use
217238
// callee.Fset when formatting res.new?
218239
// The new tree is a mix of (cloned) caller nodes for
@@ -232,21 +253,18 @@ func (st *state) inline() (*Result, error) {
232253
if i > 0 {
233254
out.WriteByte('\n')
234255
}
235-
if err := format.Node(&out, caller.Fset, stmt); err != nil {
256+
if err := format.Node(&out, fset, stmt); err != nil {
236257
return nil, err
237258
}
238259
}
239260
} else {
240-
if err := format.Node(&out, caller.Fset, res.new); err != nil {
261+
if err := format.Node(&out, fset, res.new); err != nil {
241262
return nil, err
242263
}
243264
}
244-
out.Write(caller.Content[end:])
245-
const mode = parser.ParseComments | parser.SkipObjectResolution | parser.AllErrors
246-
f, err = parser.ParseFile(caller.Fset, "callee.go", &out, mode)
247-
if err != nil {
248-
// Something has gone very wrong.
249-
logf("failed to parse <<%s>>", &out) // debugging
265+
out.Write(content[end:])
266+
content = out.Bytes()
267+
if err := reparse(); err != nil {
250268
return nil, err
251269
}
252270
}
@@ -257,15 +275,58 @@ func (st *state) inline() (*Result, error) {
257275
// to avoid migration of pre-import comments.
258276
// The imports will be organized below.
259277
if len(res.newImports) > 0 {
260-
var importDecl *ast.GenDecl
278+
// If we have imports to add, do so independent of the rest of the file.
279+
// Otherwise, the length of the new imports may consume floating comments,
280+
// causing them to be printed inside the imports block.
281+
var (
282+
importDecl *ast.GenDecl
283+
comments []*ast.CommentGroup // relevant comments.
284+
before, after []byte // pre- and post-amble for the imports block.
285+
)
261286
if len(f.Imports) > 0 {
262287
// Append specs to existing import decl
263288
importDecl = f.Decls[0].(*ast.GenDecl)
289+
for _, comment := range f.Comments {
290+
// Filter comments. Don't use CommentMap.Filter here, because we don't
291+
// want to include comments that document the import decl itself, for
292+
// example:
293+
//
294+
// // We don't want this comment to be duplicated.
295+
// import (
296+
// "something"
297+
// )
298+
if importDecl.Pos() <= comment.Pos() && comment.Pos() < importDecl.End() {
299+
comments = append(comments, comment)
300+
}
301+
}
302+
before = content[:offsetOf(fset, importDecl.Pos())]
303+
importDecl.Doc = nil // present in before
304+
after = content[offsetOf(fset, importDecl.End()):]
264305
} else {
265306
// Insert new import decl.
266307
importDecl = &ast.GenDecl{Tok: token.IMPORT}
267308
f.Decls = prepend[ast.Decl](importDecl, f.Decls...)
309+
310+
// Make room for the new declaration after the package declaration.
311+
pkgEnd := f.Name.End()
312+
file := fset.File(pkgEnd)
313+
if file == nil {
314+
logf("internal error: missing pkg file")
315+
return nil, fmt.Errorf("missing pkg file for %s", f.Name.Name)
316+
}
317+
// Preserve any comments after the package declaration, by splicing in
318+
// the new import block after the end of the package declaration line.
319+
line := file.Line(pkgEnd)
320+
if line < len(file.Lines()) { // line numbers are 1-based
321+
nextLinePos := file.LineStart(line + 1)
322+
nextLine := offsetOf(fset, nextLinePos)
323+
before = slices.Concat(content[:nextLine], []byte("\n"))
324+
after = slices.Concat([]byte("\n\n"), content[nextLine:])
325+
} else {
326+
before = slices.Concat(content, []byte("\n\n"))
327+
}
268328
}
329+
// Add new imports.
269330
for _, imp := range res.newImports {
270331
// Check that the new imports are accessible.
271332
path, _ := strconv.Unquote(imp.spec.Path.Value)
@@ -274,6 +335,21 @@ func (st *state) inline() (*Result, error) {
274335
}
275336
importDecl.Specs = append(importDecl.Specs, imp.spec)
276337
}
338+
var out bytes.Buffer
339+
out.Write(before)
340+
commented := &printer.CommentedNode{
341+
Node: importDecl,
342+
Comments: comments,
343+
}
344+
if err := format.Node(&out, fset, commented); err != nil {
345+
logf("failed to format new importDecl: %v", err) // debugging
346+
return nil, err
347+
}
348+
out.Write(after)
349+
content = out.Bytes()
350+
if err := reparse(); err != nil {
351+
return nil, err
352+
}
277353
}
278354

279355
// Delete imports referenced only by caller.Call.Fun.
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
This file checks various handling of comments when adding imports.
2+
3+
-- go.mod --
4+
module testdata
5+
go 1.12
6+
7+
-- a/empty.go --
8+
package a // This is package a.
9+
10+
func _() {
11+
a() //@ inline(re"a", empty)
12+
}
13+
14+
-- empty --
15+
package a // This is package a.
16+
17+
import "testdata/b"
18+
19+
func _() {
20+
b.B() //@ inline(re"a", empty)
21+
}
22+
-- a/existing.go --
23+
package a // This is package a.
24+
25+
// This is an import block.
26+
import (
27+
// This is an import of io.
28+
"io"
29+
30+
// This is an import of c.
31+
"testdata/c"
32+
)
33+
34+
var (
35+
// This is an io.Writer.
36+
_ io.Writer
37+
// This is c.C
38+
_ c.C
39+
)
40+
41+
func _() {
42+
a() //@ inline(re"a", existing)
43+
}
44+
45+
-- existing --
46+
package a // This is package a.
47+
48+
// This is an import block.
49+
import (
50+
// This is an import of io.
51+
"io"
52+
53+
// This is an import of c.
54+
"testdata/b"
55+
"testdata/c"
56+
)
57+
58+
var (
59+
// This is an io.Writer.
60+
_ io.Writer
61+
// This is c.C
62+
_ c.C
63+
)
64+
65+
func _() {
66+
b.B() //@ inline(re"a", existing)
67+
}
68+
69+
-- a/noparens.go --
70+
package a // This is package a.
71+
72+
// This is an import of c.
73+
import "testdata/c"
74+
75+
func _() {
76+
var _ c.C
77+
a() //@ inline(re"a", noparens)
78+
}
79+
80+
-- noparens --
81+
package a // This is package a.
82+
83+
// This is an import of c.
84+
import (
85+
"testdata/b"
86+
"testdata/c"
87+
)
88+
89+
func _() {
90+
var _ c.C
91+
b.B() //@ inline(re"a", noparens)
92+
}
93+
94+
-- a/a.go --
95+
package a
96+
97+
// This is an import of b.
98+
import "testdata/b"
99+
100+
func a() {
101+
// This is a call to B.
102+
b.B()
103+
}
104+
105+
-- b/b.go --
106+
package b
107+
108+
func B() {}
109+
110+
-- c/c.go --
111+
package c
112+
113+
type C int

internal/refactor/inline/testdata/issue63298.txtar

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,11 @@ func B() {}
3838
package a
3939

4040
import (
41-
"testdata/b"
4241
b0 "testdata/another/b"
43-
44-
//@ inline(re"a2", result)
42+
"testdata/b"
4543
)
4644

4745
func _() {
4846
b.B()
49-
b0.B()
47+
b0.B() //@ inline(re"a2", result)
5048
}

0 commit comments

Comments
 (0)