Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add scancheck linter #4925

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .golangci.next.reference.yml
Original file line number Diff line number Diff line change
@@ -2674,6 +2674,7 @@ linters:
- reassign
- revive
- rowserrcheck
- scancheck
- sloglint
- spancheck
- sqlclosecheck
@@ -2789,6 +2790,7 @@ linters:
- reassign
- revive
- rowserrcheck
- scancheck
- sloglint
- spancheck
- sqlclosecheck
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions jsonschema/golangci.next.jsonschema.json
Original file line number Diff line number Diff line change
@@ -384,6 +384,7 @@
"reassign",
"revive",
"rowserrcheck",
"scancheck",
"scopelint",
"sloglint",
"sqlclosecheck",
18 changes: 18 additions & 0 deletions pkg/golinters/scancheck/scancheck.go
Original file line number Diff line number Diff line change
@@ -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)
}
179 changes: 179 additions & 0 deletions pkg/golinters/scancheck/testdata/scancheck.go
Original file line number Diff line number Diff line change
@@ -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
}
6 changes: 6 additions & 0 deletions pkg/lint/lintersdb/builder_linter.go
Original file line number Diff line number Diff line change
@@ -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().