From 0b4b007603a00a806d3a69faa810e8307c85991d Mon Sep 17 00:00:00 2001 From: "R. Aidan Campbell" <aidan.campbell@gmail.com> Date: Sat, 17 Aug 2024 13:18:34 -0700 Subject: [PATCH] add scancheck linter --- .golangci.next.reference.yml | 2 + go.mod | 1 + go.sum | 2 + jsonschema/golangci.next.jsonschema.json | 1 + pkg/golinters/scancheck/scancheck.go | 18 ++ pkg/golinters/scancheck/testdata/scancheck.go | 179 ++++++++++++++++++ pkg/lint/lintersdb/builder_linter.go | 6 + 7 files changed, 209 insertions(+) create mode 100644 pkg/golinters/scancheck/scancheck.go create mode 100644 pkg/golinters/scancheck/testdata/scancheck.go diff --git a/.golangci.next.reference.yml b/.golangci.next.reference.yml index 2d4c9e10e009..0ff9ba331c2c 100644 --- a/.golangci.next.reference.yml +++ b/.golangci.next.reference.yml @@ -2674,6 +2674,7 @@ linters: - reassign - revive - rowserrcheck + - scancheck - sloglint - spancheck - sqlclosecheck @@ -2789,6 +2790,7 @@ linters: - reassign - revive - rowserrcheck + - scancheck - sloglint - spancheck - sqlclosecheck diff --git a/go.mod b/go.mod index ecb2b9612bc8..45824ff16f01 100644 --- a/go.mod +++ b/go.mod @@ -86,6 +86,7 @@ require ( github.com/pelletier/go-toml/v2 v2.2.2 github.com/polyfloyd/go-errorlint v1.6.0 github.com/quasilyte/go-ruleguard/dsl v0.3.22 + github.com/raidancampbell/scancheck v1.0.2 github.com/ryancurrah/gomodguard v1.3.3 github.com/ryanrolds/sqlclosecheck v0.5.1 github.com/sanposhiho/wastedassign/v2 v2.0.7 diff --git a/go.sum b/go.sum index 73a0cc77faab..7cce242b492e 100644 --- a/go.sum +++ b/go.sum @@ -458,6 +458,8 @@ github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= +github.com/raidancampbell/scancheck v1.0.2 h1:7ZZAB/ogNlmqLwNyV6UgJ/Hn+1BeuIWZ8m/WYGCI+hE= +github.com/raidancampbell/scancheck v1.0.2/go.mod h1:tDBwTPKt6IvDRCCPAjh2zNRzVeh1zQm2tRMzVf8RAt4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= diff --git a/jsonschema/golangci.next.jsonschema.json b/jsonschema/golangci.next.jsonschema.json index 510740d61660..f01ff07379a0 100644 --- a/jsonschema/golangci.next.jsonschema.json +++ b/jsonschema/golangci.next.jsonschema.json @@ -384,6 +384,7 @@ "reassign", "revive", "rowserrcheck", + "scancheck", "scopelint", "sloglint", "sqlclosecheck", diff --git a/pkg/golinters/scancheck/scancheck.go b/pkg/golinters/scancheck/scancheck.go new file mode 100644 index 000000000000..847ed6e41e22 --- /dev/null +++ b/pkg/golinters/scancheck/scancheck.go @@ -0,0 +1,18 @@ +package scancheck + +import ( + "github.com/golangci/golangci-lint/pkg/goanalysis" + "github.com/raidancampbell/scancheck/pkg/scancheck" + "golang.org/x/tools/go/analysis" +) + +func New() *goanalysis.Linter { + a := scancheck.Analyzer + + return goanalysis.NewLinter( + a.Name, + a.Doc, + []*analysis.Analyzer{a}, + nil, + ).WithLoadMode(goanalysis.LoadModeTypesInfo) +} diff --git a/pkg/golinters/scancheck/testdata/scancheck.go b/pkg/golinters/scancheck/testdata/scancheck.go new file mode 100644 index 000000000000..bdcfd24f84bf --- /dev/null +++ b/pkg/golinters/scancheck/testdata/scancheck.go @@ -0,0 +1,179 @@ +//golangcitest:args -Escancheck +package testdata + +import ( + "bufio" + "fmt" + "io" +) + +func incorrectErrorScanner(reader io.Reader) { + scanner := bufio.NewScanner(reader) + + for scanner.Scan() { + _ = scanner.Bytes() + + // this is incorrect behavior: if scanner.Scan() returns false, scanner.Err() should be checked. + // meaning that scanner.Err() should only be checked outside the loop. + if err := scanner.Err(); err != nil { // want "scanner.Err\\(\\) called inside a Scan\\(\\) loop" + fmt.Printf("oh no! %v", err) + } + } +} + +func bufioRawScanner(reader io.Reader) { + var scanner = bufio.Scanner{} + + for scanner.Scan() { + _ = scanner.Bytes() + if err := scanner.Err(); err != nil { // want "scanner.Err\\(\\) called inside a Scan\\(\\) loop" + fmt.Printf("oh no! %v", err) + } + } +} + +func bufioRawNewScanner(reader io.Reader) { + scanner := new(bufio.Scanner) + + for scanner.Scan() { + _ = scanner.Bytes() + if err := scanner.Err(); err != nil { // want "scanner.Err\\(\\) called inside a Scan\\(\\) loop" + fmt.Printf("oh no! %v", err) + } + } +} + +func multipleAssignment(reader io.Reader) { + _, scanner := bufio.NewReader(reader), bufio.NewScanner(reader) + + for scanner.Scan() { + _ = scanner.Bytes() + if err := scanner.Err(); err != nil { // want "scanner.Err\\(\\) called inside a Scan\\(\\) loop" + fmt.Printf("oh no! %v", err) + } + } +} + +func unrelatedBufioScanner(reader io.Reader) { + x := func(_ bufio.Scanner) *notABufioScanner { + return newNotBufioScanner() + } + scanner := x(bufio.Scanner{}) + + for scanner.Scan() { + if err := scanner.Err(); err != nil { + fmt.Printf("oh no! %v", err) + } + } + +} + +func correctErrorScanner(reader io.Reader) { + scanner := bufio.NewScanner(reader) + + for scanner.Scan() { + _ = scanner.Bytes() + + // this is incorrect behavior: if scanner.Scan() returns false, scanner.Err() should be checked. + // meaning that scanner.Err() should only be checked outside the loop. + } + + if err := scanner.Err(); err != nil { + fmt.Printf("oh no! %v", err) + } +} + +func hasNoScanner() {} + +func scannerIsNotScanned(reader io.Reader) { + scanner := bufio.NewScanner(reader) + _ = scanner.Bytes() +} + +func scannerScannedOutsideForLoop(reader io.Reader) { + scanner := bufio.NewScanner(reader) + _ = scanner.Scan() + if err := scanner.Err(); err != nil { + fmt.Printf("oh no! %v", err) + } + _ = scanner.Bytes() +} + +func bufioNotScanner(reader io.Reader) { + r := bufio.NewReader(reader) + + for _, err := r.ReadByte(); err != nil; { + } +} + +func scannerNotBufio(reader io.Reader) { + sg := scannerGenerator{} + scanner := sg.NewScanner() + + for scanner.Scan() { + if err := scanner.Err(); err != nil { + fmt.Printf("oh no! %v", err) + } + } +} + +func scannerShadowingBufio(reader io.Reader) { + bufio := scannerGenerator{} + scanner := bufio.NewScanner() + + for scanner.Scan() { + if err := scanner.Err(); err != nil { + fmt.Printf("oh no! %v", err) + } + } +} + +func scannerAlmostShadowingBufio(reader io.Reader) { + bufio := scannerGenerator{} + scanner := bufio.NewScannerWithDifferentName() + + for scanner.Scan() { + if err := scanner.Err(); err != nil { + fmt.Printf("oh no! %v", err) + } + } +} + +func scanNotScanner(reader io.Reader) { + b := new(boolScanner) + for b.Scan() { + if err := newNotBufioScanner().Err(); err != nil { + fmt.Printf("oh no! %v", err) + } + } +} + +func newNotBufioScanner() *notABufioScanner { + return new(notABufioScanner) +} + +type notABufioScanner struct{} + +func (n notABufioScanner) Scan() bool { + return true +} + +func (n notABufioScanner) Err() error { + return nil +} + +type scannerGenerator struct{} + +func (s scannerGenerator) NewScanner() *notABufioScanner { + return newNotBufioScanner() +} + +func (s scannerGenerator) NewScannerWithDifferentName() *notABufioScanner { + return newNotBufioScanner() +} + +type boolScanner struct{} + +func (b boolScanner) Scan() bool { + return false +} diff --git a/pkg/lint/lintersdb/builder_linter.go b/pkg/lint/lintersdb/builder_linter.go index 2e6c148e329c..1a257ec199d4 100644 --- a/pkg/lint/lintersdb/builder_linter.go +++ b/pkg/lint/lintersdb/builder_linter.go @@ -87,6 +87,7 @@ import ( "github.com/golangci/golangci-lint/pkg/golinters/reassign" "github.com/golangci/golangci-lint/pkg/golinters/revive" "github.com/golangci/golangci-lint/pkg/golinters/rowserrcheck" + "github.com/golangci/golangci-lint/pkg/golinters/scancheck" "github.com/golangci/golangci-lint/pkg/golinters/sloglint" "github.com/golangci/golangci-lint/pkg/golinters/spancheck" "github.com/golangci/golangci-lint/pkg/golinters/sqlclosecheck" @@ -668,6 +669,11 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithPresets(linter.PresetBugs, linter.PresetSQL). WithURL("https://github.com/jingyugao/rowserrcheck"), + linter.NewConfig(scancheck.New()). + WithSince("v1.61.0"). + WithPresets(linter.PresetBugs, linter.PresetError). + WithURL("https://github.com/raidancampbell/scancheck"), + linter.NewConfig(sloglint.New(&cfg.LintersSettings.SlogLint)). WithSince("v1.55.0"). WithLoadForGoAnalysis().