diff --git a/README.md b/README.md index b8c7a9d..0cc9568 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ By default, blank and dot sections are not used and the corresponding lines end All import blocks use one TAB(`\t`) as Indent. +Since v0.9.0, GCI always puts C import block as the first. + **Note**: `nolint` is hard to handle at section level, GCI will consider it as a single comment. diff --git a/main.go b/main.go index a9523bd..999ddf7 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,7 @@ import ( "github.com/daixiang0/gci/cmd/gci" ) -var version = "0.8.5" +var version = "0.9.0" func main() { e := gci.NewExecutor(version) diff --git a/pkg/gci/gci.go b/pkg/gci/gci.go index a8f5bd0..dc95329 100644 --- a/pkg/gci/gci.go +++ b/pkg/gci/gci.go @@ -121,7 +121,7 @@ func LoadFormatGoFile(file io.FileObj, cfg config.Config) (src, dist []byte, err return src, src, nil } - imports, headEnd, tailStart, err := parse.ParseFile(src, file.Path()) + imports, headEnd, tailStart, cStart, cEnd, err := parse.ParseFile(src, file.Path()) if err != nil { if errors.Is(err, parse.NoImportError{}) { return src, src, nil @@ -162,6 +162,12 @@ func LoadFormatGoFile(file io.FileObj, cfg config.Config) (src, dist []byte, err copy(tail, src[tailStart:]) head = append(head, utils.Linebreak) + // ensure C + if cStart != 0 { + head = append(head, src[cStart:cEnd]...) + head = append(head, utils.Linebreak) + } + // add beginning of import block head = append(head, `import (`...) // add end of import block diff --git a/pkg/gci/internal/testdata/cgo-block-after-import.cfg.yaml b/pkg/gci/internal/testdata/cgo-block-after-import.cfg.yaml new file mode 120000 index 0000000..b0db9e7 --- /dev/null +++ b/pkg/gci/internal/testdata/cgo-block-after-import.cfg.yaml @@ -0,0 +1 @@ +common.cfg.yaml \ No newline at end of file diff --git a/pkg/gci/internal/testdata/cgo-block-after-import.in.go b/pkg/gci/internal/testdata/cgo-block-after-import.in.go new file mode 100644 index 0000000..2f75b1f --- /dev/null +++ b/pkg/gci/internal/testdata/cgo-block-after-import.in.go @@ -0,0 +1,14 @@ +package main + +import ( + "fmt" + + "github.com/daixiang0/gci" + g "github.com/golang" +) + +// #cgo CFLAGS: -DPNG_DEBUG=1 +// #cgo amd64 386 CFLAGS: -DX86=1 +// #cgo LDFLAGS: -lpng +// #include +import "C" diff --git a/pkg/gci/internal/testdata/cgo-block-after-import.out.go b/pkg/gci/internal/testdata/cgo-block-after-import.out.go new file mode 100644 index 0000000..e75feda --- /dev/null +++ b/pkg/gci/internal/testdata/cgo-block-after-import.out.go @@ -0,0 +1,15 @@ +package main + +// #cgo CFLAGS: -DPNG_DEBUG=1 +// #cgo amd64 386 CFLAGS: -DX86=1 +// #cgo LDFLAGS: -lpng +// #include +import "C" + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/gci" +) diff --git a/pkg/gci/internal/testdata/cgo-block-before-import.cfg.yaml b/pkg/gci/internal/testdata/cgo-block-before-import.cfg.yaml deleted file mode 100644 index e666ab9..0000000 --- a/pkg/gci/internal/testdata/cgo-block-before-import.cfg.yaml +++ /dev/null @@ -1,4 +0,0 @@ -sections: - - Standard - - Default - - Prefix(github.com/daixiang0) diff --git a/pkg/gci/internal/testdata/cgo-block-before-import.cfg.yaml b/pkg/gci/internal/testdata/cgo-block-before-import.cfg.yaml new file mode 120000 index 0000000..b0db9e7 --- /dev/null +++ b/pkg/gci/internal/testdata/cgo-block-before-import.cfg.yaml @@ -0,0 +1 @@ +common.cfg.yaml \ No newline at end of file diff --git a/pkg/gci/internal/testdata/cgo-single.cfg.yaml b/pkg/gci/internal/testdata/cgo-single.cfg.yaml new file mode 120000 index 0000000..b0db9e7 --- /dev/null +++ b/pkg/gci/internal/testdata/cgo-single.cfg.yaml @@ -0,0 +1 @@ +common.cfg.yaml \ No newline at end of file diff --git a/pkg/gci/internal/testdata/cgo-single.in.go b/pkg/gci/internal/testdata/cgo-single.in.go new file mode 100644 index 0000000..144d8d2 --- /dev/null +++ b/pkg/gci/internal/testdata/cgo-single.in.go @@ -0,0 +1,15 @@ +package main + +import ( + "fmt" + + "github.com/daixiang0/gci" +) + +import "C" + +import "github.com/golang" + +import ( + "github.com/daixiang0/gci" +) diff --git a/pkg/gci/internal/testdata/cgo-single.out.go b/pkg/gci/internal/testdata/cgo-single.out.go new file mode 100644 index 0000000..467c205 --- /dev/null +++ b/pkg/gci/internal/testdata/cgo-single.out.go @@ -0,0 +1,11 @@ +package main + +import "C" + +import ( + "fmt" + + "github.com/golang" + + "github.com/daixiang0/gci" +) diff --git a/pkg/parse/parse.go b/pkg/parse/parse.go index ffb9580..1d50648 100644 --- a/pkg/parse/parse.go +++ b/pkg/parse/parse.go @@ -70,15 +70,15 @@ func getImports(imp *ast.ImportSpec) (start, end int, name string) { return } -func ParseFile(src []byte, filename string) (ImportList, int, int, error) { +func ParseFile(src []byte, filename string) (ImportList, int, int, int, int, error) { fileSet := token.NewFileSet() f, err := parser.ParseFile(fileSet, filename, src, parser.ParseComments) if err != nil { - return nil, 0, 0, err + return nil, 0, 0, 0, 0, err } if len(f.Imports) == 0 { - return nil, 0, 0, NoImportError{} + return nil, 0, 0, 0, 0, NoImportError{} } var ( @@ -86,15 +86,20 @@ func ParseFile(src []byte, filename string) (ImportList, int, int, error) { headEnd int // tailStart means the end + 1 of import block tailStart int - data ImportList + // cStart means the start of C import block + cStart int + // cEnd means the end of C import block + cEnd int + data ImportList ) - for _, d := range f.Decls { - switch d.(type) { + for index, decl := range f.Decls { + switch decl.(type) { + // skip BadDecl and FuncDecl case *ast.GenDecl: - dd := d.(*ast.GenDecl) + genDecl := decl.(*ast.GenDecl) - if dd.Tok == token.IMPORT { + if genDecl.Tok == token.IMPORT { // there are two cases, both end with linebreak: // 1. // import ( @@ -103,35 +108,58 @@ func ParseFile(src []byte, filename string) (ImportList, int, int, error) { // 2. // import "xxx" if headEnd == 0 { - headEnd = int(d.Pos()) - 1 + headEnd = int(decl.Pos()) - 1 + } + tailStart = int(decl.End()) + + for _, spec := range genDecl.Specs { + imp := spec.(*ast.ImportSpec) + // there are only one C import block + // ensure C import block is the first import block + if imp.Path.Value == C { + /* + common case: + + // #include + import "C" + + notice that decl.Pos() == genDecl.Pos() > genDecl.Doc.Pos() + */ + if genDecl.Doc != nil { + cStart = int(genDecl.Doc.Pos()) - 1 + // if C import block is the first, update headEnd + if index == 0 { + headEnd = cStart + } + } else { + /* + special case: + + import "C" + */ + cStart = int(decl.Pos()) - 1 + } + + cEnd = int(decl.End()) + + continue + } + + start, end, name := getImports(imp) + + data = append(data, &GciImports{ + Start: start, + End: end, + Name: name, + Path: strings.Trim(imp.Path.Value, `"`), + }) } - tailStart = int(d.End()) - } - } - } - - for _, imp := range f.Imports { - if imp.Path.Value == C { - if imp.Comment != nil { - headEnd = int(imp.Comment.End()) - } else { - headEnd = int(imp.Path.End()) } - continue } - - start, end, name := getImports(imp) - - data = append(data, &GciImports{ - Start: start, - End: end, - Name: name, - Path: strings.Trim(imp.Path.Value, `"`), - }) } sort.Sort(data) - return data, headEnd, tailStart, nil + return data, headEnd, tailStart, cStart, cEnd, nil } // IsGeneratedFileByComment reports whether the source file is generated code.