@@ -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.
0 commit comments