github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/stylecheck/st1013/st1013.go (about) 1 package st1013 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/constant" 7 "strconv" 8 9 "github.com/amarpal/go-tools/analysis/code" 10 "github.com/amarpal/go-tools/analysis/edit" 11 "github.com/amarpal/go-tools/analysis/facts/generated" 12 "github.com/amarpal/go-tools/analysis/lint" 13 "github.com/amarpal/go-tools/analysis/report" 14 "github.com/amarpal/go-tools/config" 15 16 "golang.org/x/tools/go/analysis" 17 "golang.org/x/tools/go/analysis/passes/inspect" 18 ) 19 20 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ 21 Analyzer: &analysis.Analyzer{ 22 Name: "ST1013", 23 Run: run, 24 Requires: []*analysis.Analyzer{generated.Analyzer, config.Analyzer, inspect.Analyzer}, 25 }, 26 Doc: &lint.Documentation{ 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 func run(pass *analysis.Pass) (interface{}, error) { 43 whitelist := map[string]bool{} 44 for _, code := range config.For(pass).HTTPStatusCodeWhitelist { 45 whitelist[code] = true 46 } 47 fn := func(node ast.Node) { 48 call := node.(*ast.CallExpr) 49 50 var arg int 51 switch code.CallName(pass, call) { 52 case "net/http.Error": 53 arg = 2 54 case "net/http.Redirect": 55 arg = 3 56 case "net/http.StatusText": 57 arg = 0 58 case "net/http.RedirectHandler": 59 arg = 1 60 default: 61 return 62 } 63 if arg >= len(call.Args) { 64 return 65 } 66 tv, ok := code.IntegerLiteral(pass, call.Args[arg]) 67 if !ok { 68 return 69 } 70 n, ok := constant.Int64Val(tv.Value) 71 if !ok { 72 return 73 } 74 if whitelist[strconv.FormatInt(n, 10)] { 75 return 76 } 77 78 s, ok := httpStatusCodes[n] 79 if !ok { 80 return 81 } 82 lit := call.Args[arg] 83 report.Report(pass, lit, fmt.Sprintf("should use constant http.%s instead of numeric literal %d", s, n), 84 report.FilterGenerated(), 85 report.Fixes(edit.Fix(fmt.Sprintf("use http.%s instead of %d", s, n), edit.ReplaceWithString(lit, "http."+s)))) 86 } 87 code.Preorder(pass, fn, (*ast.CallExpr)(nil)) 88 return nil, nil 89 } 90 91 var httpStatusCodes = map[int64]string{ 92 100: "StatusContinue", 93 101: "StatusSwitchingProtocols", 94 102: "StatusProcessing", 95 200: "StatusOK", 96 201: "StatusCreated", 97 202: "StatusAccepted", 98 203: "StatusNonAuthoritativeInfo", 99 204: "StatusNoContent", 100 205: "StatusResetContent", 101 206: "StatusPartialContent", 102 207: "StatusMultiStatus", 103 208: "StatusAlreadyReported", 104 226: "StatusIMUsed", 105 300: "StatusMultipleChoices", 106 301: "StatusMovedPermanently", 107 302: "StatusFound", 108 303: "StatusSeeOther", 109 304: "StatusNotModified", 110 305: "StatusUseProxy", 111 307: "StatusTemporaryRedirect", 112 308: "StatusPermanentRedirect", 113 400: "StatusBadRequest", 114 401: "StatusUnauthorized", 115 402: "StatusPaymentRequired", 116 403: "StatusForbidden", 117 404: "StatusNotFound", 118 405: "StatusMethodNotAllowed", 119 406: "StatusNotAcceptable", 120 407: "StatusProxyAuthRequired", 121 408: "StatusRequestTimeout", 122 409: "StatusConflict", 123 410: "StatusGone", 124 411: "StatusLengthRequired", 125 412: "StatusPreconditionFailed", 126 413: "StatusRequestEntityTooLarge", 127 414: "StatusRequestURITooLong", 128 415: "StatusUnsupportedMediaType", 129 416: "StatusRequestedRangeNotSatisfiable", 130 417: "StatusExpectationFailed", 131 418: "StatusTeapot", 132 422: "StatusUnprocessableEntity", 133 423: "StatusLocked", 134 424: "StatusFailedDependency", 135 426: "StatusUpgradeRequired", 136 428: "StatusPreconditionRequired", 137 429: "StatusTooManyRequests", 138 431: "StatusRequestHeaderFieldsTooLarge", 139 451: "StatusUnavailableForLegalReasons", 140 500: "StatusInternalServerError", 141 501: "StatusNotImplemented", 142 502: "StatusBadGateway", 143 503: "StatusServiceUnavailable", 144 504: "StatusGatewayTimeout", 145 505: "StatusHTTPVersionNotSupported", 146 506: "StatusVariantAlsoNegotiates", 147 507: "StatusInsufficientStorage", 148 508: "StatusLoopDetected", 149 510: "StatusNotExtended", 150 511: "StatusNetworkAuthenticationRequired", 151 }