diff --git a/internal/imports/fix_test.go b/internal/imports/fix_test.go index d15b6dd248..fca61568f7 100644 --- a/internal/imports/fix_test.go +++ b/internal/imports/fix_test.go @@ -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", diff --git a/internal/imports/imports.go b/internal/imports/imports.go index 82d02f0d3b..9a132cddbb 100644 --- a/internal/imports/imports.go +++ b/internal/imports/imports.go @@ -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 diff --git a/internal/imports/sortimports.go b/internal/imports/sortimports.go index 0a156fe2ef..226279471d 100644 --- a/internal/imports/sortimports.go +++ b/internal/imports/sortimports.go @@ -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 {