2019-03-27 20:46:20 +02:00
package rule
import (
"fmt"
"go/ast"
"go/token"
"strings"
"github.com/mgechev/revive/lint"
)
2024-12-01 17:44:41 +02:00
// ImportShadowingRule spots identifiers that shadow an import.
2019-03-27 20:46:20 +02:00
type ImportShadowingRule struct { }
// Apply applies the rule to given file.
2022-04-10 11:55:13 +02:00
func ( * ImportShadowingRule ) Apply ( file * lint . File , _ lint . Arguments ) [ ] lint . Failure {
2019-03-27 20:46:20 +02:00
var failures [ ] lint . Failure
importNames := map [ string ] struct { } { }
for _ , imp := range file . AST . Imports {
importNames [ getName ( imp ) ] = struct { } { }
}
fileAst := file . AST
walker := importShadowing {
2021-06-29 21:40:40 +02:00
packageNameIdent : fileAst . Name ,
importNames : importNames ,
2019-03-27 20:46:20 +02:00
onFailure : func ( failure lint . Failure ) {
failures = append ( failures , failure )
} ,
alreadySeen : map [ * ast . Object ] struct { } { } ,
2023-08-21 23:45:41 +02:00
skipIdents : map [ * ast . Ident ] struct { } { } ,
2019-03-27 20:46:20 +02:00
}
ast . Walk ( walker , fileAst )
return failures
}
// Name returns the rule name.
2022-04-10 11:55:13 +02:00
func ( * ImportShadowingRule ) Name ( ) string {
2019-03-27 20:46:20 +02:00
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 {
2021-06-29 21:40:40 +02:00
packageNameIdent * ast . Ident
importNames map [ string ] struct { }
onFailure func ( lint . Failure )
alreadySeen map [ * ast . Object ] struct { }
2023-08-21 23:45:41 +02:00
skipIdents map [ * ast . Ident ] struct { }
2019-03-27 20:46:20 +02:00
}
// 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
2023-08-21 23:45:41 +02:00
case * ast . FuncDecl :
if n . Recv != nil {
w . skipIdents [ n . Name ] = struct { } { }
}
2019-03-27 20:46:20 +02:00
case * ast . Ident :
2021-06-29 21:40:40 +02:00
if n == w . packageNameIdent {
return nil // skip the ident corresponding to the package name of this file
}
2019-03-27 20:46:20 +02:00
id := n . Name
if id == "_" {
return w // skip _ id
}
_ , isImportName := w . importNames [ id ]
_ , alreadySeen := w . alreadySeen [ n . Obj ]
2023-08-21 23:45:41 +02:00
_ , skipIdent := w . skipIdents [ n ]
if isImportName && ! alreadySeen && ! skipIdent {
2019-03-27 20:46:20 +02:00
w . onFailure ( lint . Failure {
Confidence : 1 ,
Node : n ,
2023-08-21 23:45:41 +02:00
Category : "naming" ,
2019-03-27 20:46:20 +02:00
Failure : fmt . Sprintf ( "The name '%s' shadows an import name" , id ) ,
} )
w . alreadySeen [ n . Obj ] = struct { } { }
}
}
return w
}