github.com/songshiyun/revive@v1.1.5-0.20220323112655-f8433a19b3c5/rule/import-shadowing.go (about)

     1  package rule
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/token"
     7  	"strings"
     8  
     9  	"github.com/songshiyun/revive/lint"
    10  )
    11  
    12  // ImportShadowingRule lints given else constructs.
    13  type ImportShadowingRule struct{}
    14  
    15  // Apply applies the rule to given file.
    16  func (r *ImportShadowingRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
    17  	var failures []lint.Failure
    18  
    19  	importNames := map[string]struct{}{}
    20  	for _, imp := range file.AST.Imports {
    21  		importNames[getName(imp)] = struct{}{}
    22  	}
    23  
    24  	fileAst := file.AST
    25  	walker := importShadowing{
    26  		packageNameIdent: fileAst.Name,
    27  		importNames:      importNames,
    28  		onFailure: func(failure lint.Failure) {
    29  			failures = append(failures, failure)
    30  		},
    31  		alreadySeen: map[*ast.Object]struct{}{},
    32  	}
    33  
    34  	ast.Walk(walker, fileAst)
    35  
    36  	return failures
    37  }
    38  
    39  // Name returns the rule name.
    40  func (r *ImportShadowingRule) Name() string {
    41  	return "import-shadowing"
    42  }
    43  
    44  func getName(imp *ast.ImportSpec) string {
    45  	const pathSep = "/"
    46  	const strDelim = `"`
    47  	if imp.Name != nil {
    48  		return imp.Name.Name
    49  	}
    50  
    51  	path := imp.Path.Value
    52  	i := strings.LastIndex(path, pathSep)
    53  	if i == -1 {
    54  		return strings.Trim(path, strDelim)
    55  	}
    56  
    57  	return strings.Trim(path[i+1:], strDelim)
    58  }
    59  
    60  type importShadowing struct {
    61  	packageNameIdent *ast.Ident
    62  	importNames      map[string]struct{}
    63  	onFailure        func(lint.Failure)
    64  	alreadySeen      map[*ast.Object]struct{}
    65  }
    66  
    67  // Visit visits AST nodes and checks if id nodes (ast.Ident) shadow an import name
    68  func (w importShadowing) Visit(n ast.Node) ast.Visitor {
    69  	switch n := n.(type) {
    70  	case *ast.AssignStmt:
    71  		if n.Tok == token.DEFINE {
    72  			return w // analyze variable declarations of the form id := expr
    73  		}
    74  
    75  		return nil // skip assigns of the form id = expr (not an id declaration)
    76  	case *ast.CallExpr, // skip call expressions (not an id declaration)
    77  		*ast.ImportSpec,   // skip import section subtree because we already have the list of imports
    78  		*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
    79  		*ast.ReturnStmt,   // skip skipping analysis of returns, ids in expression were already analyzed
    80  		*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
    81  		*ast.StructType:   // skip analysis of struct type because struct fields can not shadow an import name
    82  		return nil
    83  	case *ast.Ident:
    84  		if n == w.packageNameIdent {
    85  			return nil // skip the ident corresponding to the package name of this file
    86  		}
    87  
    88  		id := n.Name
    89  		if id == "_" {
    90  			return w // skip _ id
    91  		}
    92  
    93  		_, isImportName := w.importNames[id]
    94  		_, alreadySeen := w.alreadySeen[n.Obj]
    95  		if isImportName && !alreadySeen {
    96  			w.onFailure(lint.Failure{
    97  				Confidence: 1,
    98  				Node:       n,
    99  				Category:   "namming",
   100  				Failure:    fmt.Sprintf("The name '%s' shadows an import name", id),
   101  			})
   102  
   103  			w.alreadySeen[n.Obj] = struct{}{}
   104  		}
   105  	}
   106  
   107  	return w
   108  }