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

     1  package rule
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/songshiyun/revive/lint"
    10  )
    11  
    12  const (
    13  	defaultStrLitLimit = 2
    14  	kindFLOAT          = "FLOAT"
    15  	kindINT            = "INT"
    16  	kindSTRING         = "STRING"
    17  )
    18  
    19  type whiteList map[string]map[string]bool
    20  
    21  func newWhiteList() whiteList {
    22  	return map[string]map[string]bool{kindINT: {}, kindFLOAT: {}, kindSTRING: {}}
    23  }
    24  
    25  func (wl whiteList) add(kind, list string) {
    26  	elems := strings.Split(list, ",")
    27  	for _, e := range elems {
    28  		wl[kind][e] = true
    29  	}
    30  }
    31  
    32  // AddConstantRule lints unused params in functions.
    33  type AddConstantRule struct {
    34  	whiteList   whiteList
    35  	strLitLimit int
    36  }
    37  
    38  // Apply applies the rule to given file.
    39  func (r *AddConstantRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
    40  	if r.whiteList == nil {
    41  		r.strLitLimit = defaultStrLitLimit
    42  		r.whiteList = newWhiteList()
    43  		if len(arguments) > 0 {
    44  			args, ok := arguments[0].(map[string]interface{})
    45  			if !ok {
    46  				panic(fmt.Sprintf("Invalid argument to the add-constant rule. Expecting a k,v map, got %T", arguments[0]))
    47  			}
    48  			for k, v := range args {
    49  				kind := ""
    50  				switch k {
    51  				case "allowFloats":
    52  					kind = kindFLOAT
    53  					fallthrough
    54  				case "allowInts":
    55  					if kind == "" {
    56  						kind = kindINT
    57  					}
    58  					fallthrough
    59  				case "allowStrs":
    60  					if kind == "" {
    61  						kind = kindSTRING
    62  					}
    63  					list, ok := v.(string)
    64  					if !ok {
    65  						panic(fmt.Sprintf("Invalid argument to the add-constant rule, string expected. Got '%v' (%T)", v, v))
    66  					}
    67  					r.whiteList.add(kind, list)
    68  				case "maxLitCount":
    69  					sl, ok := v.(string)
    70  					if !ok {
    71  						panic(fmt.Sprintf("Invalid argument to the add-constant rule, expecting string representation of an integer. Got '%v' (%T)", v, v))
    72  					}
    73  
    74  					limit, err := strconv.Atoi(sl)
    75  					if err != nil {
    76  						panic(fmt.Sprintf("Invalid argument to the add-constant rule, expecting string representation of an integer. Got '%v'", v))
    77  					}
    78  					r.strLitLimit = limit
    79  				}
    80  			}
    81  		}
    82  	}
    83  
    84  	var failures []lint.Failure
    85  
    86  	onFailure := func(failure lint.Failure) {
    87  		failures = append(failures, failure)
    88  	}
    89  
    90  	w := lintAddConstantRule{onFailure: onFailure, strLits: make(map[string]int), strLitLimit: r.strLitLimit, whiteLst: r.whiteList}
    91  
    92  	ast.Walk(w, file.AST)
    93  
    94  	return failures
    95  }
    96  
    97  // Name returns the rule name.
    98  func (r *AddConstantRule) Name() string {
    99  	return "add-constant"
   100  }
   101  
   102  type lintAddConstantRule struct {
   103  	onFailure   func(lint.Failure)
   104  	strLits     map[string]int
   105  	strLitLimit int
   106  	whiteLst    whiteList
   107  }
   108  
   109  func (w lintAddConstantRule) Visit(node ast.Node) ast.Visitor {
   110  	switch n := node.(type) {
   111  	case *ast.GenDecl:
   112  		return nil // skip declarations
   113  	case *ast.BasicLit:
   114  		switch kind := n.Kind.String(); kind {
   115  		case kindFLOAT, kindINT:
   116  			w.checkNumLit(kind, n)
   117  		case kindSTRING:
   118  			w.checkStrLit(n)
   119  		}
   120  	}
   121  
   122  	return w
   123  }
   124  
   125  func (w lintAddConstantRule) checkStrLit(n *ast.BasicLit) {
   126  	if w.whiteLst[kindSTRING][n.Value] {
   127  		return
   128  	}
   129  
   130  	count := w.strLits[n.Value]
   131  	if count >= 0 {
   132  		w.strLits[n.Value] = count + 1
   133  		if w.strLits[n.Value] > w.strLitLimit {
   134  			w.onFailure(lint.Failure{
   135  				Confidence: 1,
   136  				Node:       n,
   137  				Category:   "style",
   138  				Failure:    fmt.Sprintf("string literal %s appears, at least, %d times, create a named constant for it", n.Value, w.strLits[n.Value]),
   139  			})
   140  			w.strLits[n.Value] = -1 // mark it to avoid failing again on the same literal
   141  		}
   142  	}
   143  }
   144  
   145  func (w lintAddConstantRule) checkNumLit(kind string, n *ast.BasicLit) {
   146  	if w.whiteLst[kind][n.Value] {
   147  		return
   148  	}
   149  
   150  	w.onFailure(lint.Failure{
   151  		Confidence: 1,
   152  		Node:       n,
   153  		Category:   "style",
   154  		Failure:    fmt.Sprintf("avoid magic numbers like '%s', create a named constant for it", n.Value),
   155  	})
   156  }