Skip to content

Commit

Permalink
Strict mode (#4)
Browse files Browse the repository at this point in the history
* Add new test case

* Fix wrong test description

* Implement strict mode

* Add strict flag

* Add testcase

* Add test case for strict mode

* Prepare config instances for each test case

* Add README about strict mode

* Apply suggestions from code review

Co-authored-by: Julian Friedman <julz.friedman@uk.ibm.com>

* Fix for review

* Rename -strict flag to -no-unaliased

Co-authored-by: Julian Friedman <julz.friedman@uk.ibm.com>
  • Loading branch information
sachaos and julz authored Apr 19, 2021
1 parent a22c8f7 commit 841f0c0
Show file tree
Hide file tree
Showing 10 changed files with 112 additions and 29 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ importas \
./...
~~~~

### `-no-unaliased` option

By default, importas allows non-aliased imports, even when the package is specified by `-alias` flag.
With `-no-unaliased` option, importas does not allow this.

~~~~
importas -no-unaliased \
-alias knative.dev/serving/pkg/apis/autoscaling/v1alpha1:autoscalingv1alpha1 \
-alias knative.dev/serving/pkg/apis/serving/v1:servingv1 \
./...
~~~~

### Use regular expression

You can specify the package path by regular expression, and alias by regular expression replacement syntax like following snippet.
Expand Down
43 changes: 25 additions & 18 deletions analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,20 @@ import (
"golang.org/x/tools/go/ast/inspector"
)

var config = &Config{
RequiredAlias: make(map[string]string),
}

var Analyzer = &analysis.Analyzer{
Name: "importas",
Doc: "Enforces consistent import aliases",
Run: run,

Flags: flags(),
Flags: flags(config),

Requires: []*analysis.Analyzer{inspect.Analyzer},
}

var config = &Config{
RequiredAlias: make(map[string]string),
}

func run(pass *analysis.Pass) (interface{}, error) {
return runWithConfig(config, pass)
}
Expand All @@ -37,18 +37,22 @@ func runWithConfig(config *Config, pass *analysis.Pass) (interface{}, error) {

inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
inspect.Preorder([]ast.Node{(*ast.ImportSpec)(nil)}, func(n ast.Node) {
visitImportSpecNode(n.(*ast.ImportSpec), pass)
visitImportSpecNode(config, n.(*ast.ImportSpec), pass)
})

return nil, nil
}

func visitImportSpecNode(node *ast.ImportSpec, pass *analysis.Pass) {
if node.Name == nil {
return // not aliased at all, ignore. (Maybe add strict mode for this?).
func visitImportSpecNode(config *Config, node *ast.ImportSpec, pass *analysis.Pass) {
if !config.DisallowUnaliased && node.Name == nil {
return
}

alias := ""
if node.Name != nil {
alias = node.Name.String()
}

alias := node.Name.String()
if alias == "." {
return // Dot aliases are generally used in tests, so ignore.
}
Expand All @@ -63,10 +67,15 @@ func visitImportSpecNode(node *ast.ImportSpec, pass *analysis.Pass) {
}

if required, exists := config.AliasFor(path); exists && required != alias {
message := fmt.Sprintf("import %q imported as %q but must be %q according to config", path, alias, required)
if alias == "" {
message = fmt.Sprintf("import %q imported without alias but must be with alias %q according to config", path, required)
}

pass.Report(analysis.Diagnostic{
Pos: node.Pos(),
End: node.End(),
Message: fmt.Sprintf("import %q imported as %q but must be %q according to config", path, alias, required),
Message: message,
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Use correct alias",
TextEdits: findEdits(node, pass.TypesInfo.Uses, path, alias, required),
Expand Down Expand Up @@ -96,13 +105,11 @@ func findEdits(node ast.Node, uses map[*ast.Ident]types.Object, importPath, orig
continue
}

if original == pkgName.Name() {
result = append(result, analysis.TextEdit{
Pos: use.Pos(),
End: use.End(),
NewText: []byte(required),
})
}
result = append(result, analysis.TextEdit{
Pos: use.Pos(),
End: use.End(),
NewText: []byte(required),
})
}

return result
Expand Down
44 changes: 37 additions & 7 deletions analyzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,34 @@ import (
"os"
"os/exec"
"path/filepath"
"strconv"
"testing"

"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/inspect"
)

func TestAnalyzer(t *testing.T) {
testdata := analysistest.TestData()

testCases := []struct {
desc string
pkg string
aliases stringMap
desc string
pkg string
aliases stringMap
disallowUnaliased bool
}{
{
desc: "Valid imports",
desc: "Invalid imports",
pkg: "a",
aliases: stringMap{
"fmt": "fff",
"os": "stdos",
"io": "iio",
},
},
{
desc: "Invalid imports",
desc: "Valid imports",
pkg: "b",
aliases: stringMap{
"fmt": "fff",
Expand All @@ -49,6 +54,16 @@ func TestAnalyzer(t *testing.T) {
"knative.dev/serving/pkg/apis/(\\w+)/(v[\\w\\d]+)": "$1$2",
},
},
{
desc: "disallow unaliased mode",
pkg: "e",
aliases: stringMap{
"fmt": "fff",
"os": "stdos",
"io": "iio",
},
disallowUnaliased: true,
},
}

for _, test := range testCases {
Expand All @@ -71,7 +86,17 @@ func TestAnalyzer(t *testing.T) {
}
}

a := Analyzer
cnf := Config{
RequiredAlias: make(map[string]string),
}
flgs := flags(&cnf)
a := &analysis.Analyzer{
Flags: flgs,
Run: func(pass *analysis.Pass) (interface{}, error) {
return runWithConfig(&cnf, pass)
},
Requires: []*analysis.Analyzer{inspect.Analyzer},
}

flg := a.Flags.Lookup("alias")
for k, v := range test.aliases {
Expand All @@ -81,7 +106,12 @@ func TestAnalyzer(t *testing.T) {
}
}

analysistest.RunWithSuggestedFixes(t, testdata, Analyzer, test.pkg)
noUnaliasedFlg := a.Flags.Lookup("no-unaliased")
if err := noUnaliasedFlg.Value.Set(strconv.FormatBool(test.disallowUnaliased)); err != nil {
t.Fatal(err)
}

analysistest.RunWithSuggestedFixes(t, testdata, a, test.pkg)
})
}
}
5 changes: 3 additions & 2 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import (
)

type Config struct {
RequiredAlias map[string]string
Rules []*Rule
RequiredAlias map[string]string
Rules []*Rule
DisallowUnaliased bool
}

func (c *Config) CompileRegexp() error {
Expand Down
3 changes: 2 additions & 1 deletion flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import (
"strings"
)

func flags() flag.FlagSet {
func flags(config *Config) flag.FlagSet {
fs := flag.FlagSet{}
fs.Var(stringMap(config.RequiredAlias), "alias", "required import alias in form path:alias")
fs.BoolVar(&config.DisallowUnaliased, "no-unaliased", false, "do not allow unaliased imports of aliased packages")
return fs
}

Expand Down
2 changes: 2 additions & 0 deletions testdata/src/a/a.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package a

import (
wrong_alias "fmt" // want `import "fmt" imported as "wrong_alias" but must be "fff" according to config`
"io"
wrong_alias_again "os" // want `import "os" imported as "wrong_alias_again" but must be "stdos" according to config`
)

func ImportAsWrongAlias() {
wrong_alias.Println("foo")
wrong_alias_again.Stdout.WriteString("bar")
io.Pipe()
}
4 changes: 3 additions & 1 deletion testdata/src/a/a.go.golden
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package a

import (
fff "fmt" // want `import "fmt" imported as "wrong_alias" but must be "fff" according to config`
fff "fmt" // want `import "fmt" imported as "wrong_alias" but must be "fff" according to config`
"io"
stdos "os" // want `import "os" imported as "wrong_alias_again" but must be "stdos" according to config`
)

func ImportAsWrongAlias() {
fff.Println("foo")
stdos.Stdout.WriteString("bar")
io.Pipe()
}
2 changes: 2 additions & 0 deletions testdata/src/b/b.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package b
import (
fff "fmt"
stdos "os"
"io"
)

func ImportAsWrongAlias() {
fff.Println("foo")
stdos.Stdout.WriteString("bar")
io.Pipe()
}
13 changes: 13 additions & 0 deletions testdata/src/e/e.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package e

import (
wrong_alias "fmt" // want `import "fmt" imported as "wrong_alias" but must be "fff" according to config`
"io" // want `import "io" imported without alias but must be with alias "iio" according to config`
wrong_alias_again "os" // want `import "os" imported as "wrong_alias_again" but must be "stdos" according to config`
)

func ImportAsWrongAlias() {
wrong_alias.Println("foo")
wrong_alias_again.Stdout.WriteString("bar")
io.Pipe()
}
13 changes: 13 additions & 0 deletions testdata/src/e/e.go.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package e

import (
fff "fmt" // want `import "fmt" imported as "wrong_alias" but must be "fff" according to config`
iio "io" // want `import "io" imported without alias but must be with alias "iio" according to config`
stdos "os" // want `import "os" imported as "wrong_alias_again" but must be "stdos" according to config`
)

func ImportAsWrongAlias() {
fff.Println("foo")
stdos.Stdout.WriteString("bar")
iio.Pipe()
}

0 comments on commit 841f0c0

Please sign in to comment.