st1013.go raw
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
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.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 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 }
152