Skip to content

Commit d107343

Browse files
author
win5do
committed
cmd/goimports: add flags
-r: remove blank line in imports -exit-code: exit with a non-zero exit code if files were not already formatted
1 parent 1b796a9 commit d107343

File tree

5 files changed

+332
-5
lines changed

5 files changed

+332
-5
lines changed

cmd/goimports/goimports.go

+11-4
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ import (
2626

2727
var (
2828
// main operation modes
29-
list = flag.Bool("l", false, "list files whose formatting differs from goimport's")
30-
write = flag.Bool("w", false, "write result to (source) file instead of stdout")
31-
doDiff = flag.Bool("d", false, "display diffs instead of rewriting files")
32-
srcdir = flag.String("srcdir", "", "choose imports as if source code is from `dir`. When operating on a single file, dir may instead be the complete file name.")
29+
list = flag.Bool("l", false, "list files whose formatting differs from goimport's")
30+
write = flag.Bool("w", false, "write result to (source) file instead of stdout")
31+
doDiff = flag.Bool("d", false, "display diffs instead of rewriting files")
32+
srcdir = flag.String("srcdir", "", "choose imports as if source code is from `dir`. When operating on a single file, dir may instead be the complete file name.")
33+
diffExitCode = flag.Bool("exit-code", false, "exit with a non-zero exit code if files were not already formatted") // use in ci lint
3334

3435
verbose bool // verbose logging
3536

@@ -53,6 +54,7 @@ func init() {
5354
flag.BoolVar(&options.AllErrors, "e", false, "report all errors (not just the first 10 on different lines)")
5455
flag.StringVar(&options.LocalPrefix, "local", "", "put imports beginning with this string after 3rd-party packages; comma-separated list")
5556
flag.BoolVar(&options.FormatOnly, "format-only", false, "if true, don't fix imports and only format. In this mode, goimports is effectively gofmt, with the addition that imports are grouped into sections.")
57+
flag.BoolVar(&options.RegroupImports, "r", false, "remove blank line in imports")
5658
}
5759

5860
func report(err error) {
@@ -144,6 +146,11 @@ func processFile(filename string, in io.Reader, out io.Writer, argType argumentT
144146
}
145147

146148
if !bytes.Equal(src, res) {
149+
if *diffExitCode {
150+
// change exitCode when not formatted
151+
exitCode = 1
152+
}
153+
147154
// formatting has changed
148155
if *list {
149156
fmt.Fprintln(out, filename)

go/ast/astutil/imports.go

+48
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
package astutil // import "golang.org/x/tools/go/ast/astutil"
77

88
import (
9+
"bytes"
910
"fmt"
1011
"go/ast"
12+
"go/parser"
1113
"go/token"
1214
"strconv"
1315
"strings"
@@ -488,3 +490,49 @@ func Imports(fset *token.FileSet, f *ast.File) [][]*ast.ImportSpec {
488490

489491
return groups
490492
}
493+
494+
// RemoveBlankLineInImport remove blank line in import block.
495+
func RemoveBlankLineInImport(filename string, src []byte) (out []byte, rerr error) {
496+
tokenSet := token.NewFileSet()
497+
astFile, err := parser.ParseFile(tokenSet, filename, src, parser.ParseComments)
498+
if err != nil {
499+
return src, err
500+
}
501+
502+
if len(astFile.Decls) <= 1 {
503+
return src, nil
504+
}
505+
tokenFile := tokenSet.File(astFile.Pos())
506+
507+
for i := 0; i < len(astFile.Decls); i++ {
508+
decl := astFile.Decls[i]
509+
gen, ok := decl.(*ast.GenDecl)
510+
if !ok || gen.Tok != token.IMPORT || declImports(gen, "C") {
511+
continue
512+
}
513+
514+
if !gen.Lparen.IsValid() {
515+
// Not a block: sorted by default.
516+
continue
517+
}
518+
519+
lpLine := tokenFile.Line(gen.Lparen)
520+
rpLine := tokenFile.Line(gen.Rparen)
521+
src = removeEmptyLine(src, lpLine, rpLine)
522+
}
523+
524+
return src, nil
525+
}
526+
527+
func removeEmptyLine(src []byte, l, r int) []byte {
528+
lines := bytes.Split(src, []byte("\n"))
529+
for i := l; i < r; i++ {
530+
if i < len(lines) && len(bytes.TrimSpace(lines[i])) == 0 {
531+
lines = append(lines[:i], lines[i+1:]...)
532+
i--
533+
r--
534+
}
535+
}
536+
537+
return bytes.Join(lines, []byte("\n"))
538+
}

go/ast/astutil/imports_test.go

+197
Original file line numberDiff line numberDiff line change
@@ -2126,3 +2126,200 @@ func TestUsesImport(t *testing.T) {
21262126
}
21272127
}
21282128
}
2129+
2130+
var removeBlankLineInImportTests = []struct {
2131+
name string
2132+
src string
2133+
wantOut string
2134+
equalSrc bool
2135+
}{
2136+
{
2137+
name: "blank line",
2138+
src: `package foo
2139+
2140+
import (
2141+
"context"
2142+
"fmt"
2143+
2144+
"github.com/foo/a"
2145+
"github.com/foo/b"
2146+
2147+
"go.pkg.com/bar/x"
2148+
"go.pkg.com/bar/y"
2149+
2150+
"github.com/foo/c"
2151+
"go.pkg.com/bar/z"
2152+
)
2153+
2154+
var (
2155+
ctx context.Context
2156+
fmt1 fmt.Formatter
2157+
a1 a.A
2158+
b1 b.A
2159+
c1 c.A
2160+
x1 x.A
2161+
y1 y.A
2162+
z1 z.A
2163+
)
2164+
`,
2165+
wantOut: `package foo
2166+
2167+
import (
2168+
"context"
2169+
"fmt"
2170+
"github.com/foo/a"
2171+
"github.com/foo/b"
2172+
"go.pkg.com/bar/x"
2173+
"go.pkg.com/bar/y"
2174+
"github.com/foo/c"
2175+
"go.pkg.com/bar/z"
2176+
)
2177+
2178+
var (
2179+
ctx context.Context
2180+
fmt1 fmt.Formatter
2181+
a1 a.A
2182+
b1 b.A
2183+
c1 c.A
2184+
x1 x.A
2185+
y1 y.A
2186+
z1 z.A
2187+
)
2188+
`,
2189+
},
2190+
{
2191+
name: "no blank line",
2192+
src: `package foo
2193+
2194+
import (
2195+
"context"
2196+
"fmt"
2197+
"github.com/foo/a"
2198+
"github.com/foo/b"
2199+
"github.com/foo/c"
2200+
"go.pkg.com/bar/x"
2201+
"go.pkg.com/bar/y"
2202+
"go.pkg.com/bar/z"
2203+
)
2204+
2205+
var (
2206+
ctx context.Context
2207+
fmt1 fmt.Formatter
2208+
a1 a.A
2209+
b1 b.A
2210+
c1 c.A
2211+
x1 x.A
2212+
y1 y.A
2213+
z1 z.A
2214+
)
2215+
`,
2216+
equalSrc: true,
2217+
},
2218+
{
2219+
name: "import comment",
2220+
src: `package foo
2221+
2222+
import (
2223+
"context"
2224+
"fmt"
2225+
"github.com/foo/a" // comment1
2226+
"github.com/foo/b"
2227+
"github.com/foo/c"
2228+
// comment2
2229+
"go.pkg.com/bar/x"
2230+
"go.pkg.com/bar/y"
2231+
"go.pkg.com/bar/z"
2232+
)
2233+
2234+
var (
2235+
ctx context.Context
2236+
fmt1 fmt.Formatter
2237+
a1 a.A
2238+
b1 b.A
2239+
c1 c.A
2240+
x1 x.A
2241+
y1 y.A
2242+
z1 z.A
2243+
)
2244+
`,
2245+
equalSrc: true,
2246+
},
2247+
{
2248+
name: "import comment blank line",
2249+
src: `package foo
2250+
2251+
import (
2252+
"context"
2253+
"fmt"
2254+
2255+
"github.com/foo/a" // comment1
2256+
"github.com/foo/b"
2257+
"github.com/foo/c"
2258+
2259+
// comment2
2260+
2261+
"go.pkg.com/bar/x"
2262+
"go.pkg.com/bar/y"
2263+
"go.pkg.com/bar/z"
2264+
)
2265+
2266+
var (
2267+
ctx context.Context
2268+
fmt1 fmt.Formatter
2269+
a1 a.A
2270+
b1 b.A
2271+
c1 c.A
2272+
x1 x.A
2273+
y1 y.A
2274+
z1 z.A
2275+
)
2276+
`,
2277+
wantOut: `package foo
2278+
2279+
import (
2280+
"context"
2281+
"fmt"
2282+
"github.com/foo/a" // comment1
2283+
"github.com/foo/b"
2284+
"github.com/foo/c"
2285+
// comment2
2286+
"go.pkg.com/bar/x"
2287+
"go.pkg.com/bar/y"
2288+
"go.pkg.com/bar/z"
2289+
)
2290+
2291+
var (
2292+
ctx context.Context
2293+
fmt1 fmt.Formatter
2294+
a1 a.A
2295+
b1 b.A
2296+
c1 c.A
2297+
x1 x.A
2298+
y1 y.A
2299+
z1 z.A
2300+
)
2301+
`,
2302+
},
2303+
}
2304+
2305+
func TestRemoveBlankLineInImport(t *testing.T) {
2306+
for _, test := range removeBlankLineInImportTests {
2307+
gotOut, err := RemoveBlankLineInImport("test.go", []byte(test.src))
2308+
if err != nil {
2309+
t.Errorf("%s: %v", test.name, err)
2310+
continue
2311+
}
2312+
2313+
var wantOut string
2314+
if test.equalSrc {
2315+
wantOut = test.src
2316+
2317+
} else {
2318+
wantOut = test.wantOut
2319+
}
2320+
2321+
if string(gotOut) != wantOut {
2322+
t.Errorf("RemoveBlankLineInImport() gotOut = %s, want %s", gotOut, test.wantOut)
2323+
}
2324+
}
2325+
}

internal/imports/fix_test.go

+67
Original file line numberDiff line numberDiff line change
@@ -3085,3 +3085,70 @@ func BenchmarkMatchesPath(b *testing.B) {
30853085
})
30863086
}
30873087
}
3088+
3089+
func TestRegroupImports(t *testing.T) {
3090+
const input = `package foo
3091+
3092+
import (
3093+
"fmt"
3094+
3095+
"github.com/foo/a"
3096+
"github.com/foo/b"
3097+
3098+
"context"
3099+
"go.pkg.com/bar/x"
3100+
"go.pkg.com/bar/y"
3101+
3102+
"github.com/foo/c"
3103+
"go.pkg.com/bar/z"
3104+
)
3105+
3106+
var (
3107+
ctx context.Context
3108+
fmt1 fmt.Formatter
3109+
a1 a.A
3110+
b1 b.A
3111+
c1 c.A
3112+
x1 x.A
3113+
y1 y.A
3114+
z1 z.A
3115+
)
3116+
`
3117+
3118+
const want = `package foo
3119+
3120+
import (
3121+
"context"
3122+
"fmt"
3123+
3124+
"github.com/foo/a"
3125+
"github.com/foo/b"
3126+
"github.com/foo/c"
3127+
"go.pkg.com/bar/x"
3128+
"go.pkg.com/bar/y"
3129+
"go.pkg.com/bar/z"
3130+
)
3131+
3132+
var (
3133+
ctx context.Context
3134+
fmt1 fmt.Formatter
3135+
a1 a.A
3136+
b1 b.A
3137+
c1 c.A
3138+
x1 x.A
3139+
y1 y.A
3140+
z1 z.A
3141+
)
3142+
`
3143+
3144+
testConfig{
3145+
module: packagestest.Module{
3146+
Name: "foo.com",
3147+
Files: fm{
3148+
"p/test.go": input,
3149+
},
3150+
},
3151+
}.processTest(t, "foo.com", "p/test.go", nil, &Options{
3152+
RegroupImports: true,
3153+
}, want)
3154+
}

internal/imports/imports.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,19 @@ type Options struct {
4141
TabIndent bool // Use tabs for indent (true if nil *Options provided)
4242
TabWidth int // Tab width (8 if nil *Options provided)
4343

44-
FormatOnly bool // Disable the insertion and deletion of imports
44+
FormatOnly bool // Disable the insertion and deletion of imports
45+
RegroupImports bool // Remove blank line in imports.
4546
}
4647

4748
// Process implements golang.org/x/tools/imports.Process with explicit context in opt.Env.
4849
func Process(filename string, src []byte, opt *Options) (formatted []byte, err error) {
50+
if opt.RegroupImports {
51+
src, err = astutil.RemoveBlankLineInImport(filename, src)
52+
if err != nil {
53+
return nil, err
54+
}
55+
}
56+
4957
fileSet := token.NewFileSet()
5058
var parserMode parser.Mode
5159
if opt.Comments {

0 commit comments

Comments
 (0)