Skip to content

Commit

Permalink
Add option to separate named imports
Browse files Browse the repository at this point in the history
This addresses #116

Add a `-separate-named` boolean option.  When activated, named/aliased
imports are split off into a separate block below the relevant group,
e.g. named std below std, named project below project, etc.
  • Loading branch information
n-oden committed Jul 3, 2024
1 parent 5f29621 commit 85c88e0
Show file tree
Hide file tree
Showing 7 changed files with 297 additions and 23 deletions.
12 changes: 12 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const (
useCacheArg = "use-cache"
applyToGeneratedFiles = "apply-to-generated-files"
excludesArg = "excludes"
separateNamedArg = "separate-named"

// Deprecated options
localArg = "local"
Expand All @@ -49,6 +50,7 @@ var (
shouldSetAlias *bool
shouldFormat *bool
shouldApplyToGeneratedFiles *bool
shouldSeparateNamedImports *bool
listFileName *bool
setExitStatus *bool
isRecursive *bool
Expand Down Expand Up @@ -150,6 +152,12 @@ Optional parameter.`,
"Option will perform additional formatting. Optional parameter.",
)

shouldSeparateNamedImports = flag.Bool(
separateNamedArg,
false,
"Option will separate named imports from the rest of the imports, per group. Optional parameter.",
)

isRecursive = flag.Bool(
recursiveArg,
false,
Expand Down Expand Up @@ -238,6 +246,10 @@ func main() {
options = append(options, reviser.WithSkipGeneratedFile)
}

if shouldSeparateNamedImports != nil && *shouldSeparateNamedImports {
options = append(options, reviser.WithSeparatedNamedImports)
}

if localPkgPrefixes != "" {
if companyPkgPrefixes != "" {
companyPkgPrefixes = localPkgPrefixes
Expand Down
68 changes: 58 additions & 10 deletions reviser/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type SourceFile struct {
shouldUseAliasForVersionSuffix bool
shouldFormatCode bool
shouldSkipAutoGenerated bool
shouldSeparateNamedImports bool
hasSeparateSideEffectGroup bool
companyPackagePrefixes []string
importsOrders ImportsOrders
Expand Down Expand Up @@ -165,12 +166,16 @@ func (f *SourceFile) groupImports(
importsWithMetadata map[string]*commentsMetadata,
) *groupsImports {
var (
stdImports []string
projectImports []string
projectLocalPkgs []string
generalImports []string
blankedImports []string
dottedImports []string
stdImports []string
projectImports []string
projectLocalPkgs []string
generalImports []string
namedStdImports []string
namedProjectImports []string
namedProjectLocalPkgs []string
namedGeneralImports []string
blankedImports []string
dottedImports []string
)

for imprt := range importsWithMetadata {
Expand All @@ -185,14 +190,33 @@ func (f *SourceFile) groupImports(
}

pkgWithoutAlias := skipPackageAlias(imprt)
values := strings.Split(imprt, " ")

if _, ok := std.StdPackages[pkgWithoutAlias]; ok {
if f.shouldSeparateNamedImports {
if len(values) > 1 {
namedStdImports = append(namedStdImports, imprt)
} else {
stdImports = append(stdImports, imprt)
}
continue
}
stdImports = append(stdImports, imprt)
continue
}

var isLocalPackageFound bool
for _, localPackagePrefix := range localPkgPrefixes {
if strings.HasPrefix(pkgWithoutAlias, localPackagePrefix) && !strings.HasPrefix(pkgWithoutAlias, projectName) {
if f.shouldSeparateNamedImports {
if len(values) > 1 {
namedProjectLocalPkgs = append(namedProjectLocalPkgs, imprt)
} else {
projectLocalPkgs = append(projectLocalPkgs, imprt)
}
isLocalPackageFound = true
break
}
projectLocalPkgs = append(projectLocalPkgs, imprt)
isLocalPackageFound = true
break
Expand All @@ -204,10 +228,26 @@ func (f *SourceFile) groupImports(
}

if strings.Contains(pkgWithoutAlias, projectName) {
if f.shouldSeparateNamedImports {
if len(values) > 1 {
namedProjectImports = append(namedProjectImports, imprt)
} else {
projectImports = append(projectImports, imprt)
}
continue
}
projectImports = append(projectImports, imprt)
continue
}

if f.shouldSeparateNamedImports {
if len(values) > 1 {
namedGeneralImports = append(namedGeneralImports, imprt)
} else {
generalImports = append(generalImports, imprt)
}
continue
}
generalImports = append(generalImports, imprt)
}

Expand All @@ -217,13 +257,21 @@ func (f *SourceFile) groupImports(
sort.Strings(projectImports)
sort.Strings(blankedImports)
sort.Strings(dottedImports)
sort.Strings(namedStdImports)
sort.Strings(namedGeneralImports)
sort.Strings(namedProjectLocalPkgs)
sort.Strings(namedProjectImports)

result := &groupsImports{
common: &common{
std: stdImports,
general: generalImports,
company: projectLocalPkgs,
project: projectImports,
std: stdImports,
namedStd: namedStdImports,
general: generalImports,
namedGeneral: namedGeneralImports,
company: projectLocalPkgs,
namedCompany: namedProjectLocalPkgs,
project: projectImports,
namedProject: namedProjectImports,
},
blanked: blankedImports,
dotted: dottedImports,
Expand Down
5 changes: 5 additions & 0 deletions reviser/file_option.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,8 @@ func WithSkipGeneratedFile(f *SourceFile) error {
f.shouldSkipAutoGenerated = true
return nil
}

func WithSeparatedNamedImports(f *SourceFile) error {
f.shouldSeparateNamedImports = true
return nil
}
136 changes: 136 additions & 0 deletions reviser/file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1999,3 +1999,139 @@ import (
})
}
}

func TestSourceFile_Fix_WithSeparatedNamedImports(t *testing.T) {
type args struct {
projectName string
filePath string
fileContent string
}
tests := []struct {
name string
args args
want string
wantChange bool
wantErr bool
}{
{
name: "simple",
args: args{
projectName: "github.com/incu6us/goimports-reviser",
filePath: "./testdata/example.go",
fileContent: `package testdata
import (
"fmt"
"github.com/incu6us/goimports-reviser/testdata/innderpkg"
"bytes"
"golang.org/x/exp/slices"
)
`,
},
want: `package testdata
import (
"bytes"
"fmt"
"golang.org/x/exp/slices"
"github.com/incu6us/goimports-reviser/testdata/innderpkg"
)
`,
wantChange: true,
wantErr: false,
},
{
name: "named",
args: args{
projectName: "github.com/incu6us/goimports-reviser",
filePath: "./testdata/example.go",
fileContent: `package testdata
import (
"fmt"
second "github.com/incu6us/goimports-reviser/testdata/secondpkg"
by "bytes"
js "encoding/json"
"golang.org/x/exp/slices"
er "golang.org/x/errors"
"github.com/incu6us/goimports-reviser/testdata/innderpkg"
)
`,
},
want: `package testdata
import (
"fmt"
by "bytes"
js "encoding/json"
"golang.org/x/exp/slices"
er "golang.org/x/errors"
"github.com/incu6us/goimports-reviser/testdata/innderpkg"
second "github.com/incu6us/goimports-reviser/testdata/secondpkg"
)
`,
wantChange: true,
wantErr: false,
},
{
name: "named with comments",
args: args{
projectName: "github.com/incu6us/goimports-reviser",
filePath: "./testdata/example.go",
fileContent: `package testdata
import (
"fmt" //fmt package
second "github.com/incu6us/goimports-reviser/testdata/secondpkg" //secondpkg package
by "bytes"
js "encoding/json"
"golang.org/x/exp/slices" //slices package
er "golang.org/x/errors"
"github.com/incu6us/goimports-reviser/testdata/innderpkg"
)
`,
},
want: `package testdata
import (
"fmt" //fmt package
by "bytes"
js "encoding/json"
"golang.org/x/exp/slices" //slices package
er "golang.org/x/errors"
"github.com/incu6us/goimports-reviser/testdata/innderpkg"
second "github.com/incu6us/goimports-reviser/testdata/secondpkg" //secondpkg package
)
`,
wantChange: true,
wantErr: false,
},
}

for _, tt := range tests {
if tt.args.filePath != StandardInput && !strings.Contains(tt.args.filePath, "does-not-exist") {
require.NoError(t, os.WriteFile(tt.args.filePath, []byte(tt.args.fileContent), 0o644))
}

t.Run(tt.name, func(t *testing.T) {
got, _, hasChange, err := NewSourceFile(tt.args.projectName, tt.args.filePath).Fix(WithSeparatedNamedImports)
if tt.wantErr {
assert.Error(t, err)
return
}

assert.NoError(t, err)
assert.Equal(t, tt.wantChange, hasChange)
assert.Equal(t, tt.want, string(got))
})
}
}
46 changes: 38 additions & 8 deletions reviser/import_order.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ type ImportsOrder string

const (
// StdImportsOrder is std libs, e.g. fmt, errors, strings...
StdImportsOrder ImportsOrder = "std"
StdImportsOrder ImportsOrder = "std"
NamedStdImportsOrder ImportsOrder = "namedStd"
// CompanyImportsOrder is packages that belong to the same organization
CompanyImportsOrder ImportsOrder = "company"
CompanyImportsOrder ImportsOrder = "company"
NamedCompanyImportsOrder ImportsOrder = "namedCompany"
// ProjectImportsOrder is packages that are inside the current project
ProjectImportsOrder ImportsOrder = "project"
ProjectImportsOrder ImportsOrder = "project"
NamedProjectImportsOrder ImportsOrder = "namedProject"
// GeneralImportsOrder is packages that are outside. In other words it is general purpose libraries
GeneralImportsOrder ImportsOrder = "general"
GeneralImportsOrder ImportsOrder = "general"
NamedGeneralImportsOrder ImportsOrder = "namedGeneral"
// BlankedImportsOrder is separate group for "_" imports
BlankedImportsOrder ImportsOrder = "blanked"
// DottedImportsOrder is separate group for "." imports
Expand All @@ -40,13 +44,13 @@ func (o ImportsOrders) sortImportsByOrder(importGroups *groupsImports) [][]strin
var imports []string
switch group {
case StdImportsOrder:
imports = importGroups.std
imports = appendGroups(importGroups.std, importGroups.namedStd)
case GeneralImportsOrder:
imports = importGroups.general
imports = appendGroups(importGroups.general, importGroups.namedGeneral)
case CompanyImportsOrder:
imports = importGroups.company
imports = appendGroups(importGroups.company, importGroups.namedCompany)
case ProjectImportsOrder:
imports = importGroups.project
imports = appendGroups(importGroups.project, importGroups.namedProject)
case BlankedImportsOrder:
imports = importGroups.blanked
case DottedImportsOrder:
Expand Down Expand Up @@ -138,3 +142,29 @@ func unique(s []string) []string {
}
return list
}

func appendGroups(input ...[]string) []string {
switch len(input) {
case 0:
return []string{}
case 1:
return input[0]
default:
break
}
separator := []string{"\n", "\n"}

var output []string

for idx, block := range input {
if idx == 0 {
output = append(output, block...)
continue
}
if len(block) > 0 {
output = append(output, append(separator, block...)...)
}
}

return output
}
Loading

0 comments on commit 85c88e0

Please sign in to comment.