Skip to content

Commit

Permalink
internal/imports: merge import declarations
Browse files Browse the repository at this point in the history
When an import is added to the ast, the import declarations are merged
together into the first import declaration. Since this is a part of
the formatting functionality of goimports, do this during formatting
regardless.

The merging pass was added to astutil.AddNamedImport in order to address
issue golang/go#14075. This joined imports from other blocks into the first
import declaration, so that a single block of imports isn't split across
multiple blocks.

This functionality is more of a formatting change than a fix imports
change, in line with sorting the imports, which occurs even when
FormatOnly. The formatting was only applied when an import was added
(not renamed or deleted). This change makes formatting by goimports
more consistent across runs and is not dependent on the exact fixes
that need to be applied.

Change-Id: Icb90bf694ff35e2d6405a3d477cf82fcd3e697e0
Reviewed-on: https://go-review.googlesource.com/c/tools/+/189941
Run-TryBot: Suzy Mueller <[email protected]>
Reviewed-by: Heschi Kreinick <[email protected]>
  • Loading branch information
suzmue committed Aug 14, 2019
1 parent 578fe56 commit ea41424
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 0 deletions.
30 changes: 30 additions & 0 deletions internal/imports/fix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,37 @@ func foo() {
}
`,
},
// Merge import blocks, even when no additions are required.
{
name: "merge_import_blocks_no_fix",
in: `package foo
import (
"fmt"
)
import "os"
import (
"rsc.io/p"
)
var _, _ = os.Args, fmt.Println
var _, _ = snappy.ErrCorrupt, p.P
`,
out: `package foo
import (
"fmt"
"os"
"github.com/golang/snappy"
"rsc.io/p"
)
var _, _ = os.Args, fmt.Println
var _, _ = snappy.ErrCorrupt, p.P
`,
},
// Delete existing empty import block
{
name: "delete_empty_import_block",
Expand Down
1 change: 1 addition & 0 deletions internal/imports/imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ func initialize(filename string, src []byte, opt *Options) ([]byte, *Options, er
}

func formatFile(fileSet *token.FileSet, file *ast.File, src []byte, adjust func(orig []byte, src []byte) []byte, opt *Options) ([]byte, error) {
mergeImports(opt.Env, fileSet, file)
sortImports(opt.Env, fileSet, file)
imps := astutil.Imports(fileSet, file)
var spacesBefore []string // import paths we need spaces before
Expand Down
47 changes: 47 additions & 0 deletions internal/imports/sortimports.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,53 @@ func sortImports(env *ProcessEnv, fset *token.FileSet, f *ast.File) {
}
}

// mergeImports merges all the import declarations into the first one.
// Taken from golang.org/x/tools/ast/astutil.
func mergeImports(env *ProcessEnv, fset *token.FileSet, f *ast.File) {
if len(f.Decls) <= 1 {
return
}

// Merge all the import declarations into the first one.
var first *ast.GenDecl
for i := 0; i < len(f.Decls); i++ {
decl := f.Decls[i]
gen, ok := decl.(*ast.GenDecl)
if !ok || gen.Tok != token.IMPORT || declImports(gen, "C") {
continue
}
if first == nil {
first = gen
continue // Don't touch the first one.
}
// We now know there is more than one package in this import
// declaration. Ensure that it ends up parenthesized.
first.Lparen = first.Pos()
// Move the imports of the other import declaration to the first one.
for _, spec := range gen.Specs {
spec.(*ast.ImportSpec).Path.ValuePos = first.Pos()
first.Specs = append(first.Specs, spec)
}
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
i--
}
}

// declImports reports whether gen contains an import of path.
// Taken from golang.org/x/tools/ast/astutil.
func declImports(gen *ast.GenDecl, path string) bool {
if gen.Tok != token.IMPORT {
return false
}
for _, spec := range gen.Specs {
impspec := spec.(*ast.ImportSpec)
if importPath(impspec) == path {
return true
}
}
return false
}

func importPath(s ast.Spec) string {
t, err := strconv.Unquote(s.(*ast.ImportSpec).Path.Value)
if err == nil {
Expand Down

0 comments on commit ea41424

Please sign in to comment.