@@ -270,8 +270,22 @@ func playExample(file *ast.File, f *ast.FuncDecl) *ast.File {
270
270
// Use unresolved identifiers to determine the imports used by this
271
271
// example. The heuristic assumes package names match base import
272
272
// paths for imports w/o renames (should be good enough most of the time).
273
- namedImports := make (map [string ]string ) // [name]path
274
- var blankImports []ast.Spec // _ imports
273
+ var namedImports []ast.Spec
274
+ var blankImports []ast.Spec // _ imports
275
+
276
+ // To preserve the blank lines between groups of imports, find the
277
+ // start position of each group, and assign that position to all
278
+ // imports from that group.
279
+ groupStarts := findImportGroupStarts (file .Imports )
280
+ groupStart := func (s * ast.ImportSpec ) token.Pos {
281
+ for i , start := range groupStarts {
282
+ if s .Path .ValuePos < start {
283
+ return groupStarts [i - 1 ]
284
+ }
285
+ }
286
+ return groupStarts [len (groupStarts )- 1 ]
287
+ }
288
+
275
289
for _ , s := range file .Imports {
276
290
p , err := strconv .Unquote (s .Path .Value )
277
291
if err != nil {
@@ -295,7 +309,12 @@ func playExample(file *ast.File, f *ast.FuncDecl) *ast.File {
295
309
}
296
310
}
297
311
if unresolved [n ] {
298
- namedImports [n ] = p
312
+ // Copy the spec and its path to avoid modifying the original.
313
+ spec := * s
314
+ path := * s .Path
315
+ spec .Path = & path
316
+ spec .Path .ValuePos = groupStart (& spec )
317
+ namedImports = append (namedImports , & spec )
299
318
delete (unresolved , n )
300
319
}
301
320
}
@@ -345,14 +364,7 @@ func playExample(file *ast.File, f *ast.FuncDecl) *ast.File {
345
364
Lparen : 1 , // Need non-zero Lparen and Rparen so that printer
346
365
Rparen : 1 , // treats this as a factored import.
347
366
}
348
- for n , p := range namedImports {
349
- s := & ast.ImportSpec {Path : & ast.BasicLit {Value : strconv .Quote (p )}}
350
- if path .Base (p ) != n {
351
- s .Name = ast .NewIdent (n )
352
- }
353
- importDecl .Specs = append (importDecl .Specs , s )
354
- }
355
- importDecl .Specs = append (importDecl .Specs , blankImports ... )
367
+ importDecl .Specs = append (namedImports , blankImports ... )
356
368
357
369
// Synthesize main function.
358
370
funcDecl := & ast.FuncDecl {
@@ -369,7 +381,6 @@ func playExample(file *ast.File, f *ast.FuncDecl) *ast.File {
369
381
sort .Slice (decls , func (i , j int ) bool {
370
382
return decls [i ].Pos () < decls [j ].Pos ()
371
383
})
372
-
373
384
sort .Slice (comments , func (i , j int ) bool {
374
385
return comments [i ].Pos () < comments [j ].Pos ()
375
386
})
@@ -382,6 +393,41 @@ func playExample(file *ast.File, f *ast.FuncDecl) *ast.File {
382
393
}
383
394
}
384
395
396
+ // findImportGroupStarts finds the start positions of each sequence of import
397
+ // specs that are not separated by a blank line.
398
+ func findImportGroupStarts (imps []* ast.ImportSpec ) []token.Pos {
399
+ startImps := findImportGroupStarts1 (imps )
400
+ groupStarts := make ([]token.Pos , len (startImps ))
401
+ for i , imp := range startImps {
402
+ groupStarts [i ] = imp .Pos ()
403
+ }
404
+ return groupStarts
405
+ }
406
+
407
+ // Helper for findImportGroupStarts to ease testing.
408
+ func findImportGroupStarts1 (origImps []* ast.ImportSpec ) []* ast.ImportSpec {
409
+ // Copy to avoid mutation.
410
+ imps := make ([]* ast.ImportSpec , len (origImps ))
411
+ copy (imps , origImps )
412
+ // Assume the imports are sorted by position.
413
+ sort .Slice (imps , func (i , j int ) bool { return imps [i ].Pos () < imps [j ].Pos () })
414
+ // Assume gofmt has been applied, so there is a blank line between adjacent imps
415
+ // if and only if they are more than 2 positions apart (newline, tab).
416
+ var groupStarts []* ast.ImportSpec
417
+ prevEnd := token .Pos (- 2 )
418
+ for _ , imp := range imps {
419
+ if imp .Pos ()- prevEnd > 2 {
420
+ groupStarts = append (groupStarts , imp )
421
+ }
422
+ prevEnd = imp .End ()
423
+ // Account for end-of-line comments.
424
+ if imp .Comment != nil {
425
+ prevEnd = imp .Comment .End ()
426
+ }
427
+ }
428
+ return groupStarts
429
+ }
430
+
385
431
// playExampleFile takes a whole file example and synthesizes a new *ast.File
386
432
// such that the example is function main in package main.
387
433
func playExampleFile (file * ast.File ) * ast.File {
0 commit comments