src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

st1013.go (4303B)


      1 package st1013
      2 
      3 import (
      4 	"fmt"
      5 	"go/ast"
      6 	"go/constant"
      7 	"strconv"
      8 
      9 	"honnef.co/go/tools/analysis/code"
     10 	"honnef.co/go/tools/analysis/edit"
     11 	"honnef.co/go/tools/analysis/facts/generated"
     12 	"honnef.co/go/tools/analysis/lint"
     13 	"honnef.co/go/tools/analysis/report"
     14 	"honnef.co/go/tools/config"
     15 	"honnef.co/go/tools/pattern"
     16 
     17 	"golang.org/x/tools/go/analysis"
     18 )
     19 
     20 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
     21 	Analyzer: &analysis.Analyzer{
     22 		Name:     "ST1013",
     23 		Run:      run,
     24 		Requires: append([]*analysis.Analyzer{generated.Analyzer, config.Analyzer}, code.RequiredAnalyzers...),
     25 	},
     26 	Doc: &lint.RawDocumentation{
     27 		Title: `Should use constants for HTTP error codes, not magic numbers`,
     28 		Text: `HTTP has a tremendous number of status codes. While some of those are
     29 well known (200, 400, 404, 500), most of them are not. The \'net/http\'
     30 package provides constants for all status codes that are part of the
     31 various specifications. It is recommended to use these constants
     32 instead of hard-coding magic numbers, to vastly improve the
     33 readability of your code.`,
     34 		Since:   "2019.1",
     35 		Options: []string{"http_status_code_whitelist"},
     36 		MergeIf: lint.MergeIfAny,
     37 	},
     38 })
     39 
     40 var Analyzer = SCAnalyzer.Analyzer
     41 
     42 var query = pattern.MustParse(`
     43 	(CallExpr
     44 		(Symbol
     45 			name@(Or
     46 			"net/http.Error"
     47 			"net/http.Redirect"
     48 			"net/http.StatusText"
     49 			"net/http.RedirectHandler"))
     50 		args)`)
     51 
     52 func run(pass *analysis.Pass) (any, error) {
     53 	whitelist := map[string]bool{}
     54 	for _, code := range config.For(pass).HTTPStatusCodeWhitelist {
     55 		whitelist[code] = true
     56 	}
     57 	for _, m := range code.Matches(pass, query) {
     58 		var arg int
     59 		switch m.State["name"].(string) {
     60 		case "net/http.Error":
     61 			arg = 2
     62 		case "net/http.Redirect":
     63 			arg = 3
     64 		case "net/http.StatusText":
     65 			arg = 0
     66 		case "net/http.RedirectHandler":
     67 			arg = 1
     68 		default:
     69 			continue
     70 		}
     71 		args := m.State["args"].([]ast.Expr)
     72 		if arg >= len(args) {
     73 			continue
     74 		}
     75 		tv, ok := code.IntegerLiteral(pass, args[arg])
     76 		if !ok {
     77 			continue
     78 		}
     79 		n, ok := constant.Int64Val(tv.Value)
     80 		if !ok {
     81 			continue
     82 		}
     83 		if whitelist[strconv.FormatInt(n, 10)] {
     84 			continue
     85 		}
     86 
     87 		s, ok := httpStatusCodes[n]
     88 		if !ok {
     89 			continue
     90 		}
     91 		lit := args[arg]
     92 		report.Report(pass, lit, fmt.Sprintf("should use constant http.%s instead of numeric literal %d", s, n),
     93 			report.FilterGenerated(),
     94 			report.Fixes(edit.Fix(fmt.Sprintf("Use http.%s instead of %d", s, n), edit.ReplaceWithString(lit, "http."+s))))
     95 	}
     96 	return nil, nil
     97 }
     98 
     99 var httpStatusCodes = map[int64]string{
    100 	100: "StatusContinue",
    101 	101: "StatusSwitchingProtocols",
    102 	102: "StatusProcessing",
    103 	200: "StatusOK",
    104 	201: "StatusCreated",
    105 	202: "StatusAccepted",
    106 	203: "StatusNonAuthoritativeInfo",
    107 	204: "StatusNoContent",
    108 	205: "StatusResetContent",
    109 	206: "StatusPartialContent",
    110 	207: "StatusMultiStatus",
    111 	208: "StatusAlreadyReported",
    112 	226: "StatusIMUsed",
    113 	300: "StatusMultipleChoices",
    114 	301: "StatusMovedPermanently",
    115 	302: "StatusFound",
    116 	303: "StatusSeeOther",
    117 	304: "StatusNotModified",
    118 	305: "StatusUseProxy",
    119 	307: "StatusTemporaryRedirect",
    120 	308: "StatusPermanentRedirect",
    121 	400: "StatusBadRequest",
    122 	401: "StatusUnauthorized",
    123 	402: "StatusPaymentRequired",
    124 	403: "StatusForbidden",
    125 	404: "StatusNotFound",
    126 	405: "StatusMethodNotAllowed",
    127 	406: "StatusNotAcceptable",
    128 	407: "StatusProxyAuthRequired",
    129 	408: "StatusRequestTimeout",
    130 	409: "StatusConflict",
    131 	410: "StatusGone",
    132 	411: "StatusLengthRequired",
    133 	412: "StatusPreconditionFailed",
    134 	413: "StatusRequestEntityTooLarge",
    135 	414: "StatusRequestURITooLong",
    136 	415: "StatusUnsupportedMediaType",
    137 	416: "StatusRequestedRangeNotSatisfiable",
    138 	417: "StatusExpectationFailed",
    139 	418: "StatusTeapot",
    140 	422: "StatusUnprocessableEntity",
    141 	423: "StatusLocked",
    142 	424: "StatusFailedDependency",
    143 	426: "StatusUpgradeRequired",
    144 	428: "StatusPreconditionRequired",
    145 	429: "StatusTooManyRequests",
    146 	431: "StatusRequestHeaderFieldsTooLarge",
    147 	451: "StatusUnavailableForLegalReasons",
    148 	500: "StatusInternalServerError",
    149 	501: "StatusNotImplemented",
    150 	502: "StatusBadGateway",
    151 	503: "StatusServiceUnavailable",
    152 	504: "StatusGatewayTimeout",
    153 	505: "StatusHTTPVersionNotSupported",
    154 	506: "StatusVariantAlsoNegotiates",
    155 	507: "StatusInsufficientStorage",
    156 	508: "StatusLoopDetected",
    157 	510: "StatusNotExtended",
    158 	511: "StatusNetworkAuthenticationRequired",
    159 }