package rule import ( "fmt" "go/ast" "go/token" "strings" "github.com/mgechev/revive/lint" ) // ImportShadowingRule lints given else constructs. type ImportShadowingRule struct{} // Apply applies the rule to given file. func (r *ImportShadowingRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { var failures []lint.Failure importNames := map[string]struct{}{} for _, imp := range file.AST.Imports { importNames[getName(imp)] = struct{}{} } fileAst := file.AST walker := importShadowing{ importNames: importNames, onFailure: func(failure lint.Failure) { failures = append(failures, failure) }, alreadySeen: map[*ast.Object]struct{}{}, } ast.Walk(walker, fileAst) return failures } // Name returns the rule name. func (r *ImportShadowingRule) Name() string { return "import-shadowing" } func getName(imp *ast.ImportSpec) string { const pathSep = "/" const strDelim = `"` if imp.Name != nil { return imp.Name.Name } path := imp.Path.Value i := strings.LastIndex(path, pathSep) if i == -1 { return strings.Trim(path, strDelim) } return strings.Trim(path[i+1:], strDelim) } type importShadowing struct { importNames map[string]struct{} onFailure func(lint.Failure) alreadySeen map[*ast.Object]struct{} } // Visit visits AST nodes and checks if id nodes (ast.Ident) shadow an import name func (w importShadowing) Visit(n ast.Node) ast.Visitor { switch n := n.(type) { case *ast.AssignStmt: if n.Tok == token.DEFINE { return w // analyze variable declarations of the form id := expr } return nil // skip assigns of the form id = expr (not an id declaration) case *ast.CallExpr, // skip call expressions (not an id declaration) *ast.ImportSpec, // skip import section subtree because we already have the list of imports *ast.KeyValueExpr, // skip analysis of key-val expressions ({key:value}): ids of such expressions, even the same of an import name, do not shadow the import name *ast.ReturnStmt, // skip skipping analysis of returns, ids in expression were already analyzed *ast.SelectorExpr, // skip analysis of selector expressions (anId.otherId): because if anId shadows an import name, it was already detected, and otherId does not shadows the import name *ast.StructType: // skip analysis of struct type because struct fields can not shadow an import name return nil case *ast.Ident: id := n.Name if id == "_" { return w // skip _ id } _, isImportName := w.importNames[id] _, alreadySeen := w.alreadySeen[n.Obj] if isImportName && !alreadySeen { w.onFailure(lint.Failure{ Confidence: 1, Node: n, Category: "namming", Failure: fmt.Sprintf("The name '%s' shadows an import name", id), }) w.alreadySeen[n.Obj] = struct{}{} } } return w }