diff --git a/.golangci.reference.yml b/.golangci.reference.yml index 7de210b98ba4..d98c0cf52730 100644 --- a/.golangci.reference.yml +++ b/.golangci.reference.yml @@ -2072,6 +2072,7 @@ linters: - predeclared - promlinter - reassign + - responsewriterlint - revive - rowserrcheck - scopelint diff --git a/go.mod b/go.mod index 84e4478593de..513942a57bc5 100644 --- a/go.mod +++ b/go.mod @@ -48,6 +48,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-version v1.6.0 github.com/hexops/gotextdiff v1.0.3 + github.com/javorszky/go-responsewriter-lint v0.1.2 github.com/jgautheron/goconst v1.5.1 github.com/jingyugao/rowserrcheck v1.1.1 github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af diff --git a/go.sum b/go.sum index 8bde49ab6f8c..363096f3454a 100644 --- a/go.sum +++ b/go.sum @@ -292,6 +292,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/javorszky/go-responsewriter-lint v0.1.2 h1:tidNkspygGMNpulecVWku/rzt+KkRhu+LuPLgWVjUAo= +github.com/javorszky/go-responsewriter-lint v0.1.2/go.mod h1:n+m48J/7g3XTWeEE3lnG/hSPVwRPo9ZgT8xpzLu86ks= github.com/jgautheron/goconst v1.5.1 h1:HxVbL1MhydKs8R8n/HE5NPvzfaYmQJA3o879lE4+WcM= github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= diff --git a/pkg/golinters/responsewriterlint.go b/pkg/golinters/responsewriterlint.go new file mode 100644 index 000000000000..365967de56b4 --- /dev/null +++ b/pkg/golinters/responsewriterlint.go @@ -0,0 +1,19 @@ +package golinters + +import ( + "github.com/javorszky/go-responsewriter-lint/pkg/analyzer" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" +) + +func NewResponseWriterLint() *goanalysis.Linter { + a := analyzer.New() + + return goanalysis.NewLinter( + a.Name, + a.Doc, + []*analysis.Analyzer{a}, + nil, + ).WithLoadMode(goanalysis.LoadModeTypesInfo) +} diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index 6f406f7d26e3..97de628ee939 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -722,6 +722,12 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithLoadForGoAnalysis(). WithURL("https://github.com/curioswitch/go-reassign"), + linter.NewConfig(golinters.NewResponseWriterLint()). + WithSince("1.52.0"). + WithPresets(linter.PresetBugs). + WithLoadForGoAnalysis(). + WithURL("https://github.com/javorszky/go-responsewriter-lint"), + linter.NewConfig(golinters.NewRevive(reviveCfg)). WithSince("v1.37.0"). WithPresets(linter.PresetStyle, linter.PresetMetaLinter). diff --git a/test/testdata/responsewriterlint.go b/test/testdata/responsewriterlint.go new file mode 100644 index 000000000000..6e27ee927e63 --- /dev/null +++ b/test/testdata/responsewriterlint.go @@ -0,0 +1,66 @@ +//golangcitest:args -Eresponsewriterlint +package testdata + +import ( + "errors" + "fmt" + "net/http" +) + +type notAResponseWriter struct{} + +func (narw notAResponseWriter) Write(in []byte) (int, error) { + // actually do nothing + return 42, errors.New("no") +} + +func (narw notAResponseWriter) WriteHeader(code int) { + // also do nothing +} + +func (narw notAResponseWriter) Header() http.Header { + return http.Header{} +} + +type rwlRandom struct{} + +func rwlExampleOne(w http.ResponseWriter, r *http.Request) { + w.Header().Add("some header", "value") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`boys in the yard`)) +} + +func rwlExampleTwo(s string, r *http.Request) error { + fmt.Printf("this is a thing") + + return nil +} + +func rwlFakeWriter(w notAResponseWriter) { + w.Header().Add("something", "other") + w.WriteHeader(420) + _, _ = w.Write([]byte(`fooled ya`)) +} + +func (b rwlRandom) method(w http.ResponseWriter, r *http.Request) { + w.Header().Add("some header", "value") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`boys in the yard`)) +} + +func (b *rwlRandom) methodPointer(w http.ResponseWriter, r *http.Request) { + w.Header().Add("some header", "value") + _, _ = w.Write([]byte(`boys in the yard`)) + w.WriteHeader(http.StatusOK) // want "function methodPointer: http.ResponseWriter.Write is called before http.ResponseWriter.WriteHeader. Headers are already sent, this has no effect." +} + +func rwlExampleThree(bloe http.ResponseWriter, r *http.Request) { + _, _ = bloe.Write([]byte(`hellyea`)) // want "function rwlExampleThree: Multiple calls to http.ResponseWriter.Write in the same function body. This is most probably a bug." + + bloe.WriteHeader(http.StatusBadRequest) // want "function rwlExampleThree: Multiple calls to http.ResponseWriter.WriteHeader in the same function body. This is most probably a bug." + _, _ = bloe.Write([]byte(`hellyelamdflmda`)) // want "function rwlExampleThree: Multiple calls to http.ResponseWriter.Write in the same function body. This is most probably a bug." + bloe.WriteHeader(http.StatusInternalServerError) // want "function rwlExampleThree: Multiple calls to http.ResponseWriter.WriteHeader in the same function body. This is most probably a bug." + + bloe.Header().Set("help", "somebody") // want "function rwlExampleThree: http.ResponseWriter.Header called after calling http.ResponseWriter.Write. This has no effect." "function rwlExampleThree: http.ResponseWriter.Header called after calling http.ResponseWriter.Write. This has no effect." "function rwlExampleThree: http.ResponseWriter.Header called after calling http.ResponseWriter.WriteHeader. This has no effect." "function rwlExampleThree: http.ResponseWriter.Header called after calling http.ResponseWriter.WriteHeader. This has no effect." + bloe.Header().Set("dddd", "someboddaady") // want "function rwlExampleThree: http.ResponseWriter.Header called after calling http.ResponseWriter.Write. This has no effect." "function rwlExampleThree: http.ResponseWriter.Header called after calling http.ResponseWriter.Write. This has no effect." "function rwlExampleThree: http.ResponseWriter.Header called after calling http.ResponseWriter.WriteHeader. This has no effect." "function rwlExampleThree: http.ResponseWriter.Header called after calling http.ResponseWriter.WriteHeader. This has no effect." +}