Skip to content

Commit

Permalink
Add basic attempt at local module handling
Browse files Browse the repository at this point in the history
The idea here is to support a new formatting section, `module`, which is
the module we're currently running in as a replacement for
`prefix(module/we/are/running/in)`.

This attempt is very basic: if the `module` section is requested it:

* Looks for a `go.mod` in the current directory or any parent directory,
  or errors if we can't find or read it (this is to make it clear when
  something goes wrong, compared to seeing a confusing "No section found
  for Import" error later
* Stores the module path from `go.mod`
* Looks for imports that have that path as a prefix when specifying
  ordering

This attempt intentionally simple and does not try to handle
workspaces
  • Loading branch information
matthewhughes934 committed Oct 29, 2023
1 parent 19d84f7 commit 112d948
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 0 deletions.
13 changes: 13 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func (g YamlConfig) Parse() (*Config, error) {
if sections == nil {
sections = section.DefaultSections()
}
configureSections(sections)

// if default order sorted sections
if !g.Cfg.CustomOrder {
Expand Down Expand Up @@ -88,3 +89,15 @@ func ParseConfig(in string) (*Config, error) {

return gciCfg, nil
}

func configureSections(sections section.SectionList) error {
for _, sec := range sections {
switch s := sec.(type) {
case *section.Module:
if err := s.Populate(); err != nil {
return err
}
}
}
return nil
}
22 changes: 22 additions & 0 deletions pkg/gci/testdata.go
Original file line number Diff line number Diff line change
Expand Up @@ -1273,6 +1273,28 @@ import (
testing "github.com/daixiang0/test"
g "github.com/golang"
)
`,
},
{
"basic module",
`sections:
- Standard
- Module
`,
`package main
import (
"os"
"github.com/daixiang0/gci/cmd/gci"
)
`,
`package main
import (
"os"
"github.com/daixiang0/gci/cmd/gci"
)
`,
},
}
78 changes: 78 additions & 0 deletions pkg/section/module.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package section

import (
"fmt"
"os"
"path/filepath"
"strings"

"golang.org/x/mod/modfile"

"github.com/daixiang0/gci/pkg/parse"
"github.com/daixiang0/gci/pkg/specificity"
)

const moduleType = "module"

type Module struct {
ModulePaths []string
}

func (m *Module) MatchSpecificity(spec *parse.GciImports) specificity.MatchSpecificity {
for _, modPath := range m.ModulePaths {
if strings.HasPrefix(spec.Path, modPath) {
return specificity.Module{}
}
}

return specificity.MisMatch{}
}

func (m *Module) String() string {
return moduleType
}

func (m *Module) Type() string {
return moduleType
}

func (m *Module) Populate() error {
dir, err := os.Getwd()
if err != nil {
// TODO: warn
return nil
}
modFile := findModule(dir)
if modFile == "" {
return fmt.Errorf("local module lookup was requested, but could not find module file")
}

modContent, err := os.ReadFile(modFile)
if err != nil {
return fmt.Errorf("failed to read %s: %v", modFile, err)
}

modPath := modfile.ModulePath(modContent)
if modPath == "" {
return fmt.Errorf("could not read module path from %s", modFile)
}

m.ModulePaths = []string{modPath}
return nil
}

func findModule(dir string) string {
// Look for enclosing go.mod.
for {
target := filepath.Join(dir, "go.mod")
if fi, err := os.Stat(target); err == nil && !fi.IsDir() {
return target
}
d := filepath.Dir(dir)
if d == dir {
break
}
dir = d
}
return ""
}
58 changes: 58 additions & 0 deletions pkg/section/module_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package section

import (
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/daixiang0/gci/pkg/log"
)

func init() {
log.InitLogger()
defer log.L().Sync()
}

func runWithDir(t *testing.T, dir string) {
oldWd, err := os.Getwd()
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, os.Chdir(oldWd)) })

require.NoError(t, os.Chdir(dir))
}

func TestPopulateFailsIfNoGoMod(t *testing.T) {
dir := t.TempDir()
runWithDir(t, dir)

m := &Module{}
assert.ErrorContains(t, m.Populate(), "local module lookup was requested, but could not find module file")
}

func TestPopulateFailsIfUnreadableGoMod(t *testing.T) {
dir := t.TempDir()
runWithDir(t, dir)
modPath := filepath.Join(dir, "go.mod")

_, err := os.Create(modPath)
require.NoError(t, err)
require.NoError(t, os.Chmod(modPath, 0o002))

m := &Module{}
assert.ErrorContains(t, m.Populate(), "failed to read ")
}

func TestPopulateFailsIfInvalidGoMod(t *testing.T) {
dir := t.TempDir()
runWithDir(t, dir)
modPath := filepath.Join(dir, "go.mod")

modContents := "not a valid go.mod"
require.NoError(t, os.WriteFile(modPath, []byte(modContents), 0o644))

m := &Module{}
assert.ErrorContains(t, m.Populate(), "could not read module path from ")
}
2 changes: 2 additions & 0 deletions pkg/section/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ func Parse(data []string) (SectionList, error) {
list = append(list, Blank{})
} else if s == "alias" {
list = append(list, Alias{})
} else if s == "module" {
list = append(list, &Module{})
} else {
errString += fmt.Sprintf(" %s", s)
}
Expand Down
1 change: 1 addition & 0 deletions pkg/section/section.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func (list SectionList) String() []string {
}

func DefaultSections() SectionList {
// pointer for Module since we need to mutate it
return SectionList{Standard{}, Default{}}
}

Expand Down
15 changes: 15 additions & 0 deletions pkg/specificity/module.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package specificity

type Module struct{}

func (m Module) IsMoreSpecific(than MatchSpecificity) bool {
return isMoreSpecific(m, than)
}

func (m Module) Equal(to MatchSpecificity) bool {
return equalSpecificity(m, to)
}

func (Module) class() specificityClass {
return ModuleClass
}
1 change: 1 addition & 0 deletions pkg/specificity/specificity.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const (
StandardClass = 20
MatchClass = 30
NameClass = 40
ModuleClass = 50
)

// MatchSpecificity is used to determine which section matches an import best
Expand Down

0 comments on commit 112d948

Please sign in to comment.