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  }