117 lines
2.8 KiB
Go
117 lines
2.8 KiB
Go
|
package importas
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"go/ast"
|
||
|
"go/types"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
|
||
|
"golang.org/x/tools/go/analysis"
|
||
|
"golang.org/x/tools/go/analysis/passes/inspect"
|
||
|
"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(config),
|
||
|
|
||
|
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||
|
}
|
||
|
|
||
|
func run(pass *analysis.Pass) (interface{}, error) {
|
||
|
return runWithConfig(config, pass)
|
||
|
}
|
||
|
|
||
|
func runWithConfig(config *Config, pass *analysis.Pass) (interface{}, error) {
|
||
|
if err := config.CompileRegexp(); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||
|
inspect.Preorder([]ast.Node{(*ast.ImportSpec)(nil)}, func(n ast.Node) {
|
||
|
visitImportSpecNode(config, n.(*ast.ImportSpec), pass)
|
||
|
})
|
||
|
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
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()
|
||
|
}
|
||
|
|
||
|
if alias == "." {
|
||
|
return // Dot aliases are generally used in tests, so ignore.
|
||
|
}
|
||
|
|
||
|
if strings.HasPrefix(alias, "_") {
|
||
|
return // Used by go test and for auto-includes, not a conflict.
|
||
|
}
|
||
|
|
||
|
path, err := strconv.Unquote(node.Path.Value)
|
||
|
if err != nil {
|
||
|
pass.Reportf(node.Pos(), "import not quoted")
|
||
|
}
|
||
|
|
||
|
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: message,
|
||
|
SuggestedFixes: []analysis.SuggestedFix{{
|
||
|
Message: "Use correct alias",
|
||
|
TextEdits: findEdits(node, pass.TypesInfo.Uses, path, alias, required),
|
||
|
}},
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func findEdits(node ast.Node, uses map[*ast.Ident]types.Object, importPath, original, required string) []analysis.TextEdit {
|
||
|
// Edit the actual import line.
|
||
|
result := []analysis.TextEdit{{
|
||
|
Pos: node.Pos(),
|
||
|
End: node.End(),
|
||
|
NewText: []byte(required + " " + strconv.Quote(importPath)),
|
||
|
}}
|
||
|
|
||
|
// Edit all the uses of the alias in the code.
|
||
|
for use, pkg := range uses {
|
||
|
pkgName, ok := pkg.(*types.PkgName)
|
||
|
if !ok {
|
||
|
// skip identifiers that aren't pointing at a PkgName.
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if pkgName.Pos() != node.Pos() {
|
||
|
// skip identifiers pointing to a different import statement.
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
result = append(result, analysis.TextEdit{
|
||
|
Pos: use.Pos(),
|
||
|
End: use.End(),
|
||
|
NewText: []byte(required),
|
||
|
})
|
||
|
}
|
||
|
|
||
|
return result
|
||
|
}
|