diff --git a/.golangci.yml b/.golangci.yml index 895990ee1a..2beb91b024 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -14,3 +14,10 @@ linters: - unused - misspell - revive + +issues: + exclude-rules: + - path: 'test/*' + text: 'dot-imports: should not use dot imports' + linters: + - revive diff --git a/pkg/crc/preflight/preflight.go b/pkg/crc/preflight/preflight.go index d8c97a0f25..26163893d1 100644 --- a/pkg/crc/preflight/preflight.go +++ b/pkg/crc/preflight/preflight.go @@ -52,9 +52,9 @@ func (check *Check) shouldSkip(config crcConfig.Storage) bool { func (check *Check) doCheck(config crcConfig.Storage) error { if check.checkDescription == "" { panic(fmt.Sprintf("Should not happen, empty description for check '%s'", check.configKeySuffix)) - } else { - logging.Infof("%s", check.checkDescription) } + + logging.Infof("%s", check.checkDescription) if check.shouldSkip(config) { logging.Warn("Skipping above check...") return nil diff --git a/tools/go.mod b/tools/go.mod index 8ba94bea63..d049d9b590 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/cfergeau/gomod2rpmdeps v0.0.0-20210223144124-2042c4850ca8 - github.com/golangci/golangci-lint v1.53.3 + github.com/golangci/golangci-lint v1.55.2 github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844 golang.org/x/tools v0.18.0 ) @@ -12,33 +12,37 @@ require ( require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.2.1 // indirect - github.com/4meepo/tagalign v1.2.2 // indirect - github.com/Abirdcfly/dupword v0.0.11 // indirect - github.com/Antonboom/errname v0.1.10 // indirect - github.com/Antonboom/nilnil v0.1.5 // indirect + github.com/4meepo/tagalign v1.3.3 // indirect + github.com/Abirdcfly/dupword v0.0.13 // indirect + github.com/Antonboom/errname v0.1.12 // indirect + github.com/Antonboom/nilnil v0.1.7 // indirect + github.com/Antonboom/testifylint v0.2.3 // indirect github.com/BurntSushi/toml v1.3.2 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect - github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 // indirect + github.com/GaijinEntertainment/go-exhaustruct/v3 v3.1.0 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.1.0 // indirect + github.com/alecthomas/go-check-sumtype v0.1.3 // indirect github.com/alexkohler/nakedret/v2 v2.0.2 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect - github.com/ashanbrown/forbidigo v1.5.3 // indirect + github.com/ashanbrown/forbidigo v1.6.0 // indirect github.com/ashanbrown/makezero v1.1.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bkielbasa/cyclop v1.2.1 // indirect github.com/blizzy78/varnamelen v0.8.0 // indirect github.com/bombsimon/wsl/v3 v3.4.0 // indirect - github.com/breml/bidichk v0.2.4 // indirect - github.com/breml/errchkjson v0.3.1 // indirect - github.com/butuzov/ireturn v0.2.0 // indirect + github.com/breml/bidichk v0.2.7 // indirect + github.com/breml/errchkjson v0.3.6 // indirect + github.com/butuzov/ireturn v0.2.2 // indirect github.com/butuzov/mirror v1.1.0 // indirect + github.com/catenacyber/perfsprint v0.2.0 // indirect + github.com/ccojocar/zxcvbn-go v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/charithe/durationcheck v0.0.10 // indirect - github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 // indirect + github.com/chavacava/garif v0.1.0 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect - github.com/daixiang0/gci v0.10.1 // indirect + github.com/daixiang0/gci v0.11.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/denis-tingaikin/go-header v0.4.3 // indirect github.com/esimonov/ifshort v1.0.4 // indirect @@ -48,7 +52,8 @@ require ( github.com/firefart/nonamedreturns v1.0.4 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/go-critic/go-critic v0.8.1 // indirect + github.com/ghostiam/protogetter v0.2.3 // indirect + github.com/go-critic/go-critic v0.9.0 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astequal v1.1.0 // indirect @@ -63,13 +68,13 @@ require ( github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 // indirect github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe // indirect - github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2 // indirect + github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e // indirect github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 // indirect github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca // indirect - github.com/golangci/misspell v0.4.0 // indirect - github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6 // indirect + github.com/golangci/misspell v0.4.1 // indirect + github.com/golangci/revgrep v0.5.2 // indirect github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/comment v1.4.2 // indirect @@ -81,7 +86,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jgautheron/goconst v1.5.1 // indirect + github.com/jgautheron/goconst v1.6.0 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect github.com/julz/importas v0.1.0 // indirect @@ -89,12 +94,13 @@ require ( github.com/kisielk/gotool v1.0.0 // indirect github.com/kkHAIKE/contextcheck v1.1.4 // indirect github.com/kulti/thelper v0.6.3 // indirect - github.com/kunwardeep/paralleltest v1.0.7 // indirect + github.com/kunwardeep/paralleltest v1.0.8 // indirect github.com/kyoh86/exportloopref v0.1.11 // indirect github.com/ldez/gomoddirectives v0.2.3 // indirect github.com/ldez/tagliatelle v0.5.0 // indirect github.com/leonklingele/grouper v1.1.1 // indirect github.com/lufeee/execinquery v1.2.1 // indirect + github.com/macabu/inamedparam v0.1.2 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/maratori/testableexamples v1.0.0 // indirect github.com/maratori/testpackage v1.1.1 // indirect @@ -104,34 +110,33 @@ require ( github.com/mattn/go-runewidth v0.0.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mbilski/exhaustivestruct v1.2.0 // indirect - github.com/mgechev/revive v1.3.2 // indirect + github.com/mgechev/revive v1.3.4 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moricho/tparallel v0.3.1 // indirect github.com/nakabonne/nestif v0.3.1 // indirect - github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect github.com/nishanths/exhaustive v0.11.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.12.1 // indirect + github.com/nunnatsa/ginkgolinter v0.14.1 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/polyfloyd/go-errorlint v1.4.2 // indirect + github.com/polyfloyd/go-errorlint v1.4.5 // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect - github.com/quasilyte/go-ruleguard v0.3.19 // indirect + github.com/quasilyte/go-ruleguard v0.4.0 // indirect github.com/quasilyte/gogrep v0.5.0 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect github.com/ryancurrah/gomodguard v1.3.0 // indirect - github.com/ryanrolds/sqlclosecheck v0.4.0 // indirect + github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect - github.com/sashamelentyev/usestdlibvars v1.23.0 // indirect - github.com/securego/gosec/v2 v2.16.0 // indirect + github.com/sashamelentyev/usestdlibvars v1.24.0 // indirect + github.com/securego/gosec/v2 v2.18.2 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect @@ -152,34 +157,35 @@ require ( github.com/subosito/gotenv v1.4.1 // indirect github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c // indirect github.com/tdakkota/asciicheck v0.2.0 // indirect - github.com/tetafro/godot v1.4.11 // indirect + github.com/tetafro/godot v1.4.15 // indirect github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect github.com/timonwong/loggercheck v0.9.4 // indirect github.com/tomarrell/wrapcheck/v2 v2.8.1 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect - github.com/ultraware/funlen v0.0.3 // indirect + github.com/ultraware/funlen v0.1.0 // indirect github.com/ultraware/whitespace v0.0.5 // indirect - github.com/uudashr/gocognit v1.0.6 // indirect - github.com/xen0n/gosmopolitan v1.2.1 // indirect + github.com/uudashr/gocognit v1.1.2 // indirect + github.com/xen0n/gosmopolitan v1.2.2 // indirect github.com/yagipy/maintidx v1.0.0 // indirect github.com/yeya24/promlinter v0.2.0 // indirect - github.com/ykadowak/zerologlint v0.1.2 // indirect - gitlab.com/bosi/decorder v0.2.3 // indirect - go.tmz.dev/musttag v0.7.0 // indirect + github.com/ykadowak/zerologlint v0.1.3 // indirect + gitlab.com/bosi/decorder v0.4.1 // indirect + go-simpler.org/sloglint v0.1.2 // indirect + go.tmz.dev/musttag v0.7.2 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect - golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2 // indirect + golang.org/x/exp/typeparams v0.0.0-20230307190834-24139beb5833 // indirect golang.org/x/mod v0.15.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.17.0 // indirect - golang.org/x/text v0.11.0 // indirect + golang.org/x/text v0.13.0 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - honnef.co/go/tools v0.4.3 // indirect + honnef.co/go/tools v0.4.6 // indirect mvdan.cc/gofumpt v0.5.0 // indirect mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect diff --git a/tools/go.sum b/tools/go.sum index e398e00cb2..98fefd6f1e 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -40,26 +40,32 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/4meepo/tagalign v1.2.2 h1:kQeUTkFTaBRtd/7jm8OKJl9iHk0gAO+TDFPHGSna0aw= -github.com/4meepo/tagalign v1.2.2/go.mod h1:Q9c1rYMZJc9dPRkbQPpcBNCLEmY2njbAsXhQOZFE2dE= -github.com/Abirdcfly/dupword v0.0.11 h1:z6v8rMETchZXUIuHxYNmlUAuKuB21PeaSymTed16wgU= -github.com/Abirdcfly/dupword v0.0.11/go.mod h1:wH8mVGuf3CP5fsBTkfWwwwKTjDnVVCxtU8d8rgeVYXA= -github.com/Antonboom/errname v0.1.10 h1:RZ7cYo/GuZqjr1nuJLNe8ZH+a+Jd9DaZzttWzak9Bls= -github.com/Antonboom/errname v0.1.10/go.mod h1:xLeiCIrvVNpUtsN0wxAh05bNIZpqE22/qDMnTBTttiA= -github.com/Antonboom/nilnil v0.1.5 h1:X2JAdEVcbPaOom2TUa1FxZ3uyuUlex0XMLGYMemu6l0= -github.com/Antonboom/nilnil v0.1.5/go.mod h1:I24toVuBKhfP5teihGWctrRiPbRKHwZIFOvc6v3HZXk= +github.com/4meepo/tagalign v1.3.3 h1:ZsOxcwGD/jP4U/aw7qeWu58i7dwYemfy5Y+IF1ACoNw= +github.com/4meepo/tagalign v1.3.3/go.mod h1:Q9c1rYMZJc9dPRkbQPpcBNCLEmY2njbAsXhQOZFE2dE= +github.com/Abirdcfly/dupword v0.0.13 h1:SMS17YXypwP000fA7Lr+kfyBQyW14tTT+nRv9ASwUUo= +github.com/Abirdcfly/dupword v0.0.13/go.mod h1:Ut6Ue2KgF/kCOawpW4LnExT+xZLQviJPE4klBPMK/5Y= +github.com/Antonboom/errname v0.1.12 h1:oh9ak2zUtsLp5oaEd/erjB4GPu9w19NyoIskZClDcQY= +github.com/Antonboom/errname v0.1.12/go.mod h1:bK7todrzvlaZoQagP1orKzWXv59X/x0W0Io2XT1Ssro= +github.com/Antonboom/nilnil v0.1.7 h1:ofgL+BA7vlA1K2wNQOsHzLJ2Pw5B5DpWRLdDAVvvTow= +github.com/Antonboom/nilnil v0.1.7/go.mod h1:TP+ScQWVEq0eSIxqU8CbdT5DFWoHp0MbP+KMUO1BKYQ= +github.com/Antonboom/testifylint v0.2.3 h1:MFq9zyL+rIVpsvLX4vDPLojgN7qODzWsrnftNX2Qh60= +github.com/Antonboom/testifylint v0.2.3/go.mod h1:IYaXaOX9NbfAyO+Y04nfjGI8wDemC1rUyM/cYolz018= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= -github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 h1:+r1rSv4gvYn0wmRjC8X7IAzX8QezqtFV9m0MUHFJgts= -github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0/go.mod h1:b3g59n2Y+T5xmcxJL+UEG2f8cQploZm1mR/v6BW0mU0= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.1.0 h1:3ZBs7LAezy8gh0uECsA6CGU43FF3zsx5f4eah5FxTMA= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.1.0/go.mod h1:rZLTje5A9kFBe0pzhpe2TdhRniBF++PRHQuRpR8esVc= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/OpenPeeDeeP/depguard/v2 v2.1.0 h1:aQl70G173h/GZYhWf36aE5H0KaujXfVMnn/f1kSDVYY= github.com/OpenPeeDeeP/depguard/v2 v2.1.0/go.mod h1:PUBgk35fX4i7JDmwzlJwJ+GMe6NfO1723wmJMgPThNQ= +github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= +github.com/alecthomas/go-check-sumtype v0.1.3 h1:M+tqMxB68hcgccRXBMVCPI4UJ+QUfdSx0xdbypKCqA8= +github.com/alecthomas/go-check-sumtype v0.1.3/go.mod h1:WyYPfhfkdhyrdaligV6svFopZV8Lqdzn5pyVBaV6jhQ= +github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -71,8 +77,8 @@ github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pO github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= -github.com/ashanbrown/forbidigo v1.5.3 h1:jfg+fkm/snMx+V9FBwsl1d340BV/99kZGv5jN9hBoXk= -github.com/ashanbrown/forbidigo v1.5.3/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= +github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY= +github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= @@ -86,14 +92,18 @@ github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= github.com/bombsimon/wsl/v3 v3.4.0 h1:RkSxjT3tmlptwfgEgTgU+KYKLI35p/tviNXNXiL2aNU= github.com/bombsimon/wsl/v3 v3.4.0/go.mod h1:KkIB+TXkqy6MvK9BDZVbZxKNYsE1/oLRJbIFtf14qqo= -github.com/breml/bidichk v0.2.4 h1:i3yedFWWQ7YzjdZJHnPo9d/xURinSq3OM+gyM43K4/8= -github.com/breml/bidichk v0.2.4/go.mod h1:7Zk0kRFt1LIZxtQdl9W9JwGAcLTTkOs+tN7wuEYGJ3s= -github.com/breml/errchkjson v0.3.1 h1:hlIeXuspTyt8Y/UmP5qy1JocGNR00KQHgfaNtRAjoxQ= -github.com/breml/errchkjson v0.3.1/go.mod h1:XroxrzKjdiutFyW3nWhw34VGg7kiMsDQox73yWCGI2U= -github.com/butuzov/ireturn v0.2.0 h1:kCHi+YzC150GE98WFuZQu9yrTn6GEydO2AuPLbTgnO4= -github.com/butuzov/ireturn v0.2.0/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc= +github.com/breml/bidichk v0.2.7 h1:dAkKQPLl/Qrk7hnP6P+E0xOodrq8Us7+U0o4UBOAlQY= +github.com/breml/bidichk v0.2.7/go.mod h1:YodjipAGI9fGcYM7II6wFvGhdMYsC5pHDlGzqvEW3tQ= +github.com/breml/errchkjson v0.3.6 h1:VLhVkqSBH96AvXEyclMR37rZslRrY2kcyq+31HCsVrA= +github.com/breml/errchkjson v0.3.6/go.mod h1:jhSDoFheAF2RSDOlCfhHO9KqhZgAYLyvHe7bRCX8f/U= +github.com/butuzov/ireturn v0.2.2 h1:jWI36dxXwVrI+RnXDwux2IZOewpmfv930OuIRfaBUJ0= +github.com/butuzov/ireturn v0.2.2/go.mod h1:RfGHUvvAuFFxoHKf4Z8Yxuh6OjlCw1KvR2zM1NFHeBk= github.com/butuzov/mirror v1.1.0 h1:ZqX54gBVMXu78QLoiqdwpl2mgmoOJTk7s4p4o+0avZI= github.com/butuzov/mirror v1.1.0/go.mod h1:8Q0BdQU6rC6WILDiBM60DBfvV78OLJmMmixe7GF45AE= +github.com/catenacyber/perfsprint v0.2.0 h1:azOocHLscPjqXVJ7Mf14Zjlkn4uNua0+Hcg1wTR6vUo= +github.com/catenacyber/perfsprint v0.2.0/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= +github.com/ccojocar/zxcvbn-go v1.0.1 h1:+sxrANSCj6CdadkcMnvde/GWU1vZiiXRbqYSCalV4/4= +github.com/ccojocar/zxcvbn-go v1.0.1/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= @@ -102,8 +112,8 @@ github.com/cfergeau/gomod2rpmdeps v0.0.0-20210223144124-2042c4850ca8 h1:wueFIqVE github.com/cfergeau/gomod2rpmdeps v0.0.0-20210223144124-2042c4850ca8/go.mod h1:0vvDpGoZYCjudlASJzqx+hOZjwhE+Q7jdJ8fIhkqsak= github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= -github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 h1:W9o46d2kbNL06lq7UNDPV0zYLzkrde/bjIqO02eoll0= -github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8/go.mod h1:gakxgyXaaPkxvLw1XQxNGK4I37ys9iBRzNUx/B7pUCo= +github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= +github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -114,8 +124,8 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= -github.com/daixiang0/gci v0.10.1 h1:eheNA3ljF6SxnPD/vE4lCBusVHmV3Rs3dkKvFrJ7MR0= -github.com/daixiang0/gci v0.10.1/go.mod h1:xtHP9N7AHdNvtRNfcx9gwTDfw7FRJx4bZUsiEfiNNAI= +github.com/daixiang0/gci v0.11.2 h1:Oji+oPsp3bQ6bNNgX30NBAVT18P4uBH4sRZnlOlTj7Y= +github.com/daixiang0/gci v0.11.2/go.mod h1:xtHP9N7AHdNvtRNfcx9gwTDfw7FRJx4bZUsiEfiNNAI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -142,8 +152,10 @@ github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwV github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/go-critic/go-critic v0.8.1 h1:16omCF1gN3gTzt4j4J6fKI/HnRojhEp+Eks6EuKw3vw= -github.com/go-critic/go-critic v0.8.1/go.mod h1:kpzXl09SIJX1cr9TB/g/sAG+eFEl7ZS9f9cqvZtyNl0= +github.com/ghostiam/protogetter v0.2.3 h1:qdv2pzo3BpLqezwqfGDLZ+nHEYmc5bUpIdsMbBVwMjw= +github.com/ghostiam/protogetter v0.2.3/go.mod h1:KmNLOsy1v04hKbvZs8EfGI1fk39AgTdRDxWNYPfXVc4= +github.com/go-critic/go-critic v0.9.0 h1:Pmys9qvU3pSML/3GEQ2Xd9RZ/ip+aXHKILuxczKGV/U= +github.com/go-critic/go-critic v0.9.0/go.mod h1:5P8tdXL7m/6qnyG6oRAlYLORvoXH0WDypYgAEmagT40= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -214,18 +226,18 @@ github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9 github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe h1:6RGUuS7EGotKx6J5HIP8ZtyMdiDscjMLfRBSPuzVVeo= github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe/go.mod h1:gjqyPShc/m8pEMpk0a3SeagVb0kaqvhscv+i9jI5ZhQ= -github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2 h1:amWTbTGqOZ71ruzrdA+Nx5WA3tV1N0goTspwmKCQvBY= -github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2/go.mod h1:9wOXstvyDRshQ9LggQuzBCGysxs3b6Uo/1MvYCR2NMs= -github.com/golangci/golangci-lint v1.53.3 h1:CUcRafczT4t1F+mvdkUm6KuOpxUZTl0yWN/rSU6sSMo= -github.com/golangci/golangci-lint v1.53.3/go.mod h1:W4Gg3ONq6p3Jl+0s/h9Gr0j7yEgHJWWZO2bHl2tBUXM= +github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e h1:ULcKCDV1LOZPFxGZaA6TlQbiM3J2GCPnkx/bGF6sX/g= +github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e/go.mod h1:Pm5KhLPA8gSnQwrQ6ukebRcapGb/BG9iUkdaiCcGHJM= +github.com/golangci/golangci-lint v1.55.2 h1:yllEIsSJ7MtlDBwDJ9IMBkyEUz2fYE0b5B8IUgO1oP8= +github.com/golangci/golangci-lint v1.55.2/go.mod h1:H60CZ0fuqoTwlTvnbyjhpZPWp7KmsjwV2yupIMiMXbM= github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA= github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= -github.com/golangci/misspell v0.4.0 h1:KtVB/hTK4bbL/S6bs64rYyk8adjmh1BygbBiaAiX+a0= -github.com/golangci/misspell v0.4.0/go.mod h1:W6O/bwV6lGDxUCChm2ykw9NQdd5bYd1Xkjo88UcWyJc= -github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6 h1:DIPQnGy2Gv2FSA4B/hh8Q7xx3B7AIDk3DAMeHclH1vQ= -github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6/go.mod h1:0AKcRCkMoKvUvlf89F6O7H2LYdhr1zBh736mBItOdRs= +github.com/golangci/misspell v0.4.1 h1:+y73iSicVy2PqyX7kmUefHusENlrP9YwuHZHPLGQj/g= +github.com/golangci/misspell v0.4.1/go.mod h1:9mAN1quEo3DlpbaIKKyEvRxK1pwqR9s/Sea1bJCtlNI= +github.com/golangci/revgrep v0.5.2 h1:EndcWoRhcnfj2NHQ+28hyuXpLMF+dQmCN+YaeeIl4FU= +github.com/golangci/revgrep v0.5.2/go.mod h1:bjAMA+Sh/QUfTDcHzxfyHxr4xKvllVr/0sCv2e7jJHA= github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys= github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -242,8 +254,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -294,8 +306,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.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -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/jgautheron/goconst v1.6.0 h1:gbMLWKRMkzAc6kYsQL6/TxaoBUg3Jm9LSF/Ih1ADWGA= +github.com/jgautheron/goconst v1.6.0/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48= @@ -328,8 +340,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= -github.com/kunwardeep/paralleltest v1.0.7 h1:2uCk94js0+nVNQoHZNLBkAR1DQJrVzw6T0RMzJn55dQ= -github.com/kunwardeep/paralleltest v1.0.7/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= +github.com/kunwardeep/paralleltest v1.0.8 h1:Ul2KsqtzFxTlSU7IP0JusWlLiNqQaloB9vguyjbE558= +github.com/kunwardeep/paralleltest v1.0.8/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ymEyhQ= github.com/kyoh86/exportloopref v0.1.11/go.mod h1:qkV4UF1zGl6EkF1ox8L5t9SwyeBAZ3qLMd6up458uqA= github.com/ldez/gomoddirectives v0.2.3 h1:y7MBaisZVDYmKvt9/l1mjNCiSA1BVn34U0ObUcJwlhA= @@ -340,6 +352,8 @@ github.com/leonklingele/grouper v1.1.1 h1:suWXRU57D4/Enn6pXR0QVqqWWrnJ9Osrz+5rjt github.com/leonklingele/grouper v1.1.1/go.mod h1:uk3I3uDfi9B6PeUjsCKi6ndcf63Uy7snXgR4yDYQVDY= github.com/lufeee/execinquery v1.2.1 h1:hf0Ems4SHcUGBxpGN7Jz78z1ppVkP/837ZlETPCEtOM= github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM= +github.com/macabu/inamedparam v0.1.2 h1:RR5cnayM6Q7cDhQol32DE2BGAPGMnffJ31LFE+UklaU= +github.com/macabu/inamedparam v0.1.2/go.mod h1:Xg25QvY7IBRl1KLPV9Rbml8JOMZtF/iAkNkmV7eQgjw= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= @@ -361,8 +375,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mbilski/exhaustivestruct v1.2.0 h1:wCBmUnSYufAHO6J4AVWY6ff+oxWxsVFrwgOdMUQePUo= github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc= -github.com/mgechev/revive v1.3.2 h1:Wb8NQKBaALBJ3xrrj4zpwJwqwNA6nDpyJSEQWcCka6U= -github.com/mgechev/revive v1.3.2/go.mod h1:UCLtc7o5vg5aXCwdUTU1kEBQ1v+YXPAkYDIDXbrs5I0= +github.com/mgechev/revive v1.3.4 h1:k/tO3XTaWY4DEHal9tWBkkUMJYO/dLDVyMmAQxmIMDc= +github.com/mgechev/revive v1.3.4/go.mod h1:W+pZCMu9qj8Uhfs1iJMQsEFLRozUfvwFwqVvRbSNLVw= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -378,21 +392,19 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= -github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA= -github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/nishanths/exhaustive v0.11.0 h1:T3I8nUGhl/Cwu5Z2hfc92l0e04D2GEW6e0l8pzda2l0= github.com/nishanths/exhaustive v0.11.0/go.mod h1:RqwDsZ1xY0dNdqHho2z6X+bgzizwbLYOWnZbbl2wLB4= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/nunnatsa/ginkgolinter v0.12.1 h1:vwOqb5Nu05OikTXqhvLdHCGcx5uthIYIl0t79UVrERQ= -github.com/nunnatsa/ginkgolinter v0.12.1/go.mod h1:AK8Ab1PypVrcGUusuKD8RDcl2KgsIwvNaaxAlyHSzso= +github.com/nunnatsa/ginkgolinter v0.14.1 h1:khx0CqR5U4ghsscjJ+lZVthp3zjIFytRXPTaQ/TMiyA= +github.com/nunnatsa/ginkgolinter v0.14.1/go.mod h1:nY0pafUSst7v7F637e7fymaMlQqI9c0Wka2fGsDkzWg= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= -github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k= +github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= +github.com/onsi/gomega v1.28.1 h1:MijcGUbfYuznzK/5R4CPNoUP/9Xvuo20sXfEm6XxoTA= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= +github.com/otiai10/copy v1.11.0 h1:OKBD80J/mLBrwnzXqGtFCzprFSGioo30JcmR4APsNwc= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= @@ -408,8 +420,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polyfloyd/go-errorlint v1.4.2 h1:CU+O4181IxFDdPH6t/HT7IiDj1I7zxNi1RIUxYwn8d0= -github.com/polyfloyd/go-errorlint v1.4.2/go.mod h1:k6fU/+fQe38ednoZS51T7gSIGQW1y94d6TkSr35OzH8= +github.com/polyfloyd/go-errorlint v1.4.5 h1:70YWmMy4FgRHehGNOUask3HtSFSOLKgmDn7ryNe7LqI= +github.com/polyfloyd/go-errorlint v1.4.5/go.mod h1:sIZEbFoDOCnTYYZoVkjc4hTnM459tuWA9H/EkdXwsKk= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= @@ -432,8 +444,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/quasilyte/go-ruleguard v0.3.19 h1:tfMnabXle/HzOb5Xe9CUZYWXKfkS1KwRmZyPmD9nVcc= -github.com/quasilyte/go-ruleguard v0.3.19/go.mod h1:lHSn69Scl48I7Gt9cX3VrbsZYvYiBYszZOZW4A+oTEw= +github.com/quasilyte/go-ruleguard v0.4.0 h1:DyM6r+TKL+xbKB4Nm7Afd1IQh9kEUKQs2pboWGKtvQo= +github.com/quasilyte/go-ruleguard v0.4.0/go.mod h1:Eu76Z/R8IXtViWUIHkE3p8gdH3/PKk1eh3YGfaEof10= github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= @@ -447,16 +459,16 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.3.0 h1:q15RT/pd6UggBXVBuLps8BXRvl5GPBcwVA7BJHMLuTw= github.com/ryancurrah/gomodguard v1.3.0/go.mod h1:ggBxb3luypPEzqVtq33ee7YSN35V28XeGnid8dnni50= -github.com/ryanrolds/sqlclosecheck v0.4.0 h1:i8SX60Rppc1wRuyQjMciLqIzV3xnoHB7/tXbr6RGYNI= -github.com/ryanrolds/sqlclosecheck v0.4.0/go.mod h1:TBRRjzL31JONc9i4XMinicuo+s+E8yKZ5FN8X3G6CKQ= +github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= +github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/NsJFwFK7Uxc= github.com/sanposhiho/wastedassign/v2 v2.0.7/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= -github.com/sashamelentyev/usestdlibvars v1.23.0 h1:01h+/2Kd+NblNItNeux0veSL5cBF1jbEOPrEhDzGYq0= -github.com/sashamelentyev/usestdlibvars v1.23.0/go.mod h1:YPwr/Y1LATzHI93CqoPUN/2BzGQ/6N/cl/KwgR0B/aU= -github.com/securego/gosec/v2 v2.16.0 h1:Pi0JKoasQQ3NnoRao/ww/N/XdynIB9NRYYZT5CyOs5U= -github.com/securego/gosec/v2 v2.16.0/go.mod h1:xvLcVZqUfo4aAQu56TNv7/Ltz6emAOQAEsrZrt7uGlI= +github.com/sashamelentyev/usestdlibvars v1.24.0 h1:MKNzmXtGh5N0y74Z/CIaJh4GlB364l0K1RUT08WSWAc= +github.com/sashamelentyev/usestdlibvars v1.24.0/go.mod h1:9cYkq+gYJ+a5W2RPdhfaSCnTVUC1OQP/bSiiBhq3OZE= +github.com/securego/gosec/v2 v2.18.2 h1:DkDt3wCiOtAHf1XkiXZBhQ6m6mK/b9T/wD257R3/c+I= +github.com/securego/gosec/v2 v2.18.2/go.mod h1:xUuqSF6i0So56Y2wwohWAmB07EdBkUN6crbLlHwbyJs= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= @@ -497,7 +509,6 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -505,7 +516,6 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= @@ -518,8 +528,8 @@ github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= -github.com/tetafro/godot v1.4.11 h1:BVoBIqAf/2QdbFmSwAWnaIqDivZdOV0ZRwEm6jivLKw= -github.com/tetafro/godot v1.4.11/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8= +github.com/tetafro/godot v1.4.15 h1:QzdIs+XB8q+U1WmQEWKHQbKmCw06QuQM7gLx/dky2RM= +github.com/tetafro/godot v1.4.15/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 h1:quvGphlmUVU+nhpFa4gg4yJyTRJ13reZMDHrKwYw53M= github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966/go.mod h1:27bSVNWSBOHm+qRp1T9qzaIpsWEP6TbUnei/43HK+PQ= github.com/timonwong/loggercheck v0.9.4 h1:HKKhqrjcVj8sxL7K77beXh0adEm6DLjV/QOGeMXEVi4= @@ -528,20 +538,20 @@ github.com/tomarrell/wrapcheck/v2 v2.8.1 h1:HxSqDSN0sAt0yJYsrcYVoEeyM4aI9yAm3KQp github.com/tomarrell/wrapcheck/v2 v2.8.1/go.mod h1:/n2Q3NZ4XFT50ho6Hbxg+RV1uyo2Uow/Vdm9NQcl5SE= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= -github.com/ultraware/funlen v0.0.3 h1:5ylVWm8wsNwH5aWo9438pwvsK0QiqVuUrt9bn7S/iLA= -github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= +github.com/ultraware/funlen v0.1.0 h1:BuqclbkY6pO+cvxoq7OsktIXZpgBSkYTQtmwhAK81vI= +github.com/ultraware/funlen v0.1.0/go.mod h1:XJqmOQja6DpxarLj6Jj1U7JuoS8PvL4nEqDaQhy22p4= github.com/ultraware/whitespace v0.0.5 h1:hh+/cpIcopyMYbZNVov9iSxvJU3OYQg78Sfaqzi/CzI= github.com/ultraware/whitespace v0.0.5/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= -github.com/uudashr/gocognit v1.0.6 h1:2Cgi6MweCsdB6kpcVQp7EW4U23iBFQWfTXiWlyp842Y= -github.com/uudashr/gocognit v1.0.6/go.mod h1:nAIUuVBnYU7pcninia3BHOvQkpQCeO76Uscky5BOwcY= -github.com/xen0n/gosmopolitan v1.2.1 h1:3pttnTuFumELBRSh+KQs1zcz4fN6Zy7aB0xlnQSn1Iw= -github.com/xen0n/gosmopolitan v1.2.1/go.mod h1:JsHq/Brs1o050OOdmzHeOr0N7OtlnKRAGAsElF8xBQA= +github.com/uudashr/gocognit v1.1.2 h1:l6BAEKJqQH2UpKAPKdMfZf5kE4W/2xk8pfU1OVLvniI= +github.com/uudashr/gocognit v1.1.2/go.mod h1:aAVdLURqcanke8h3vg35BC++eseDm66Z7KmchI5et4k= +github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= +github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= github.com/yeya24/promlinter v0.2.0 h1:xFKDQ82orCU5jQujdaD8stOHiv8UN68BSdn2a8u8Y3o= github.com/yeya24/promlinter v0.2.0/go.mod h1:u54lkmBOZrpEbQQ6gox2zWKKLKu2SGe+2KOiextY+IA= -github.com/ykadowak/zerologlint v0.1.2 h1:Um4P5RMmelfjQqQJKtE8ZW+dLZrXrENeIzWWKw800U4= -github.com/ykadowak/zerologlint v0.1.2/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= +github.com/ykadowak/zerologlint v0.1.3 h1:TLy1dTW3Nuc+YE3bYRPToG1Q9Ej78b5UUN6bjbGdxPE= +github.com/ykadowak/zerologlint v0.1.3/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -549,17 +559,19 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -gitlab.com/bosi/decorder v0.2.3 h1:gX4/RgK16ijY8V+BRQHAySfQAb354T7/xQpDB2n10P0= -gitlab.com/bosi/decorder v0.2.3/go.mod h1:9K1RB5+VPNQYtXtTDAzd2OEftsZb1oV0IrJrzChSdGE= -go-simpler.org/assert v0.5.0 h1:+5L/lajuQtzmbtEfh69sr5cRf2/xZzyJhFjoOz/PPqs= +gitlab.com/bosi/decorder v0.4.1 h1:VdsdfxhstabyhZovHafFw+9eJ6eU0d2CkFNJcZz/NU4= +gitlab.com/bosi/decorder v0.4.1/go.mod h1:jecSqWUew6Yle1pCr2eLWTensJMmsxHsBwt+PVbkAqA= +go-simpler.org/assert v0.6.0 h1:QxSrXa4oRuo/1eHMXSBFHKvJIpWABayzKldqZyugG7E= +go-simpler.org/sloglint v0.1.2 h1:IjdhF8NPxyn0Ckn2+fuIof7ntSnVUAqBFcQRrnG9AiM= +go-simpler.org/sloglint v0.1.2/go.mod h1:2LL+QImPfTslD5muNPydAEYmpXIj6o/WYcqnJjLi4o4= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.tmz.dev/musttag v0.7.0 h1:QfytzjTWGXZmChoX0L++7uQN+yRCPfyFm+whsM+lfGc= -go.tmz.dev/musttag v0.7.0/go.mod h1:oTFPvgOkJmp5kYL02S8+jrH0eLrBIl57rzWeA26zDEM= +go.tmz.dev/musttag v0.7.2 h1:1J6S9ipDbalBSODNT5jCep8dhZyMr4ttnjQagmGYR5s= +go.tmz.dev/musttag v0.7.2/go.mod h1:m6q5NiiSKMnQYokefa2xGoyoXnrswCbJ0AWYzf4Zs28= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= @@ -591,8 +603,8 @@ golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQ golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2 h1:J74nGeMgeFnYQJN59eFwh06jX/V8g0lB7LWpjSLxtgU= -golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20230307190834-24139beb5833 h1:jWGQJV4niP+CCmFW9ekjA9Zx8vYORzOUH2/Nl5WPuLQ= +golang.org/x/exp/typeparams v0.0.0-20230307190834-24139beb5833/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -769,8 +781,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -965,8 +977,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.4.3 h1:o/n5/K5gXqk8Gozvs2cnL0F2S1/g1vcGCAx2vETjITw= -honnef.co/go/tools v0.4.3/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA= +honnef.co/go/tools v0.4.6 h1:oFEHCKeID7to/3autwsWfnuv69j3NsfcXbvJKuIcep8= +honnef.co/go/tools v0.4.6/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E= mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= diff --git a/tools/vendor/github.com/4meepo/tagalign/.golangci.yml b/tools/vendor/github.com/4meepo/tagalign/.golangci.yml index 99baa8c213..e65f604fe1 100644 --- a/tools/vendor/github.com/4meepo/tagalign/.golangci.yml +++ b/tools/vendor/github.com/4meepo/tagalign/.golangci.yml @@ -42,7 +42,6 @@ linters-settings: - name: receiver-naming - name: redefines-builtin-id - name: string-of-int - - name: struct-tag - name: superfluous-else - name: time-naming - name: unconditional-recursion @@ -81,7 +80,7 @@ linters: - goprintffuncname - gosec - gosimple - - govet + # - govet - importas - ineffassign - makezero diff --git a/tools/vendor/github.com/4meepo/tagalign/README.md b/tools/vendor/github.com/4meepo/tagalign/README.md index 9181e7a190..9d04dccbf2 100644 --- a/tools/vendor/github.com/4meepo/tagalign/README.md +++ b/tools/vendor/github.com/4meepo/tagalign/README.md @@ -41,6 +41,40 @@ type FooBar struct { } ``` +## Usage + +By default tagalign will only align tags, but not sort them. But alignment and [sort feature](https://github.com/4meepo/tagalign#sort-tag) can work together or separately. + +* As a Golangci Linter (Recommended) + + Tagalign is a built-in linter in [Golangci Lint](https://golangci-lint.run/usage/linters/#tagalign) since `v1.53`. + > Note: In order to have the best experience, add the `--fix` flag to `golangci-lint` to enable the autofix feature. + +* Standalone Mode + + Install it using `GO` or download it [here](https://github.com/4meepo/tagalign/releases). + + ```bash + go install github.com/4meepo/tagalign/cmd/tagalign@latest + ``` + + Run it in your terminal. + + ```bash + # Only align tags. + tagalign -fix {package path} + # Only sort tags with fixed order. + tagalign -fix -noalign -sort -order "json,xml" {package path} + # Align and sort together. + tagalign -fix -sort -order "json,xml" {package path} + # Align and sort together in strict style. + tagalign -fix -sort -order "json,xml" -strict {package path} + ``` + +## Advanced Features + +### Sort Tag + In addition to alignment, it can also sort tags with fixed order. If we enable sort with fixed order `json,xml`, the following code ```go @@ -63,32 +97,29 @@ type SortExample struct { The fixed order is `json,xml`, so the tags `json` and `xml` will be sorted and aligned first, and the rest tags will be sorted and aligned in the dictionary order. -## Install - -```bash -go install github.com/4meepo/tagalign/cmd/tagalign -``` - -## Usage +### Strict Style -By default tagalign will only align tags, but not sort them. But alignment and sort can work together or separately. +Sometimes, you may want to align your tags in strict style. In this style, the tags will be sorted and aligned in the dictionary order, and the tags with the same name will be aligned together. For example, the following code -If you don't want to align tags, you can use `-noalign` to disable alignment. +```go +type StrictStyleExample struct { + Foo int ` xml:"baz" yaml:"bar" zip:"foo" binding:"required" gorm:"column:foo" validate:"required"` + Bar int `validate:"required" gorm:"column:bar" yaml:"foo" xml:"bar" binding:"required" json:"bar,omitempty" ` +} +``` -You can use `-sort` to enable sort and `-order` to set the fixed order of tags. +will be aligned to -```bash -# Only align tags. -tagalign -fix {package path} -# Only sort tags with fixed order. -tagalign -fix -noalign -sort -order "json,xml" {package path} -# Align and sort together. -tagalign -fix -sort -order "json,xml" {package path} +```go +type StrictStyleExample struct { + Foo int `binding:"required" gorm:"column:foo" validate:"required" xml:"baz" yaml:"bar" zip:"foo"` + Bar int `binding:"required" gorm:"column:bar" json:"bar,omitempty" validate:"required" xml:"bar" yaml:"foo"` +} ``` -TODO: integrate with golangci-lint +> ⚠️Note: The strict style can't run without the align or sort feature enabled. -## Reference +## References [Golang AST Visualizer](http://goast.yuroyoro.net/) diff --git a/tools/vendor/github.com/4meepo/tagalign/options.go b/tools/vendor/github.com/4meepo/tagalign/options.go index 4deaf8cbc4..ddec98da73 100644 --- a/tools/vendor/github.com/4meepo/tagalign/options.go +++ b/tools/vendor/github.com/4meepo/tagalign/options.go @@ -26,3 +26,12 @@ func WithAlign(enabled bool) Option { h.align = enabled } } + +// WithStrictStyle configure whether enable strict style. +// StrictStyle is disabled by default. +// Note: StrictStyle must be used with WithAlign(true) and WithSort(...) together, or it will be ignored. +func WithStrictStyle() Option { + return func(h *Helper) { + h.style = StrictStyle + } +} diff --git a/tools/vendor/github.com/4meepo/tagalign/tagalign.go b/tools/vendor/github.com/4meepo/tagalign/tagalign.go index 3dae96bc3e..4734b56661 100644 --- a/tools/vendor/github.com/4meepo/tagalign/tagalign.go +++ b/tools/vendor/github.com/4meepo/tagalign/tagalign.go @@ -22,6 +22,17 @@ const ( GolangciLintMode ) +type Style int + +const ( + DefaultStyle Style = iota + StrictStyle +) + +const ( + errTagValueSyntax = "bad syntax for struct tag value" +) + func NewAnalyzer(options ...Option) *analysis.Analyzer { return &analysis.Analyzer{ Name: "tagalign", @@ -38,12 +49,18 @@ func Run(pass *analysis.Pass, options ...Option) []Issue { for _, f := range pass.Files { h := &Helper{ mode: StandaloneMode, + style: DefaultStyle, align: true, } for _, opt := range options { opt(h) } + // StrictStyle must be used with WithAlign(true) and WithSort(...) together, or it will be ignored. + if h.style == StrictStyle && (!h.align || !h.sort) { + h.style = DefaultStyle + } + if !h.align && !h.sort { // do nothing return nil @@ -62,6 +79,8 @@ func Run(pass *analysis.Pass, options ...Option) []Issue { type Helper struct { mode Mode + style Style + align bool // whether enable tags align. sort bool // whether enable tags sort. fixedTagOrder []string // the order of tags, the other tags will be sorted by name. @@ -182,16 +201,36 @@ func (w *Helper) Process(pass *analysis.Pass) { //nolint:gocognit var maxTagNum int var tagsGroup, notSortedTagsGroup [][]*structtag.Tag - for i, field := range fields { - offsets[i] = pass.Fset.Position(field.Tag.Pos()).Column + + var uniqueKeys []string + addKey := func(k string) { + for _, key := range uniqueKeys { + if key == k { + return + } + } + uniqueKeys = append(uniqueKeys, k) + } + + for i := 0; i < len(fields); { + field := fields[i] + column := pass.Fset.Position(field.Tag.Pos()).Column - 1 + offsets[i] = column + tag, err := strconv.Unquote(field.Tag.Value) if err != nil { - break + // if tag value is not a valid string, report it directly + w.report(pass, field, column, errTagValueSyntax, field.Tag.Value) + fields = removeField(fields, i) + continue } tags, err := structtag.Parse(tag) if err != nil { - break + // if tag value is not a valid struct tag, report it directly + w.report(pass, field, column, err.Error(), field.Tag.Value) + fields = removeField(fields, i) + continue } maxTagNum = max(maxTagNum, tags.Len()) @@ -204,24 +243,47 @@ func (w *Helper) Process(pass *analysis.Pass) { //nolint:gocognit notSortedTagsGroup = append(notSortedTagsGroup, cp) sortBy(w.fixedTagOrder, tags) } - + for _, t := range tags.Tags() { + addKey(t.Key) + } tagsGroup = append(tagsGroup, tags.Tags()) + + i++ } - // if w.align{ - // record the max length of each column tag - tagMaxLens := make([]int, maxTagNum) + if w.sort && StrictStyle == w.style { + sortAllKeys(w.fixedTagOrder, uniqueKeys) + maxTagNum = len(uniqueKeys) + } + // record the max length of each column tag + type tagLen struct { + Key string // present only when sort enabled + Len int + } + tagMaxLens := make([]tagLen, maxTagNum) for j := 0; j < maxTagNum; j++ { var maxLength int + var key string for i := 0; i < len(tagsGroup); i++ { - if len(tagsGroup[i]) <= j { - // in case of index out of range - continue + if w.style == StrictStyle { + key = uniqueKeys[j] + // search by key + for _, tag := range tagsGroup[i] { + if tag.Key == key { + maxLength = max(maxLength, len(tag.String())) + break + } + } + } else { + if len(tagsGroup[i]) <= j { + // in case of index out of range + continue + } + maxLength = max(maxLength, len(tagsGroup[i][j].String())) } - maxLength = max(maxLength, len(tagsGroup[i][j].String())) } - tagMaxLens[j] = maxLength + tagMaxLens[j] = tagLen{key, maxLength} } for i, field := range fields { @@ -231,9 +293,28 @@ func (w *Helper) Process(pass *analysis.Pass) { //nolint:gocognit if w.align { // if align enabled, align tags. newTagBuilder := strings.Builder{} - for i, tag := range tags { - format := alignFormat(tagMaxLens[i] + 1) // with an extra space - newTagBuilder.WriteString(fmt.Sprintf(format, tag.String())) + for i, n := 0, 0; i < len(tags) && n < len(tagMaxLens); { + tag := tags[i] + var format string + if w.style == StrictStyle { + if tagMaxLens[n].Key == tag.Key { + // match + format = alignFormat(tagMaxLens[n].Len + 1) // with an extra space + newTagBuilder.WriteString(fmt.Sprintf(format, tag.String())) + i++ + n++ + } else { + // tag missing + format = alignFormat(tagMaxLens[n].Len + 1) + newTagBuilder.WriteString(fmt.Sprintf(format, "")) + n++ + } + } else { + format = alignFormat(tagMaxLens[n].Len + 1) // with an extra space + newTagBuilder.WriteString(fmt.Sprintf(format, tag.String())) + i++ + n++ + } } newTagStr = newTagBuilder.String() } else { @@ -249,7 +330,8 @@ func (w *Helper) Process(pass *analysis.Pass) { //nolint:gocognit newTagStr = strings.Join(tagsStr, " ") } - unquoteTag := strings.TrimSpace(newTagStr) + unquoteTag := strings.TrimRight(newTagStr, " ") + // unquoteTag := newTagStr newTagValue := fmt.Sprintf("`%s`", unquoteTag) if field.Tag.Value == newTagValue { // nothing changed @@ -258,19 +340,22 @@ func (w *Helper) Process(pass *analysis.Pass) { //nolint:gocognit msg := "tag is not aligned, should be: " + unquoteTag - w.report(pass, field, offsets[i]-1, msg, newTagValue) + w.report(pass, field, offsets[i], msg, newTagValue) } } // process single fields for _, field := range w.singleFields { + column := pass.Fset.Position(field.Tag.Pos()).Column - 1 tag, err := strconv.Unquote(field.Tag.Value) if err != nil { + w.report(pass, field, column, errTagValueSyntax, field.Tag.Value) continue } tags, err := structtag.Parse(tag) if err != nil { + w.report(pass, field, column, err.Error(), field.Tag.Value) continue } originalTags := append([]*structtag.Tag(nil), tags.Tags()...) @@ -278,20 +363,15 @@ func (w *Helper) Process(pass *analysis.Pass) { //nolint:gocognit sortBy(w.fixedTagOrder, tags) } - if reflect.DeepEqual(originalTags, tags.Tags()) { - // if tags order not changed, do nothing - continue - } - newTagValue := fmt.Sprintf("`%s`", tags.String()) - if field.Tag.Value == newTagValue { - // nothing changed + if reflect.DeepEqual(originalTags, tags.Tags()) && field.Tag.Value == newTagValue { + // if tags order not changed, do nothing continue } msg := "tag is not aligned , should be: " + tags.String() - w.report(pass, field, pass.Fset.Position(field.Tag.Pos()).Column-1, msg, newTagValue) + w.report(pass, field, column, msg, newTagValue) } } @@ -329,6 +409,27 @@ func sortBy(fixedOrder []string, tags *structtag.Tags) { }) } +func sortAllKeys(fixedOrder []string, keys []string) { + sort.Slice(keys, func(i, j int) bool { + oi := findIndex(fixedOrder, keys[i]) + oj := findIndex(fixedOrder, keys[j]) + + if oi == -1 && oj == -1 { + return keys[i] < keys[j] + } + + if oi == -1 { + return false + } + + if oj == -1 { + return true + } + + return oi < oj + }) +} + func findIndex(s []string, e string) int { for i, a := range s { if a == e { @@ -348,3 +449,11 @@ func max(a, b int) int { } return b } + +func removeField(fields []*ast.Field, index int) []*ast.Field { + if index < 0 || index >= len(fields) { + return fields + } + + return append(fields[:index], fields[index+1:]...) +} diff --git a/tools/vendor/github.com/Abirdcfly/dupword/README.md b/tools/vendor/github.com/Abirdcfly/dupword/README.md index 6917acae25..e6c5b919fa 100644 --- a/tools/vendor/github.com/Abirdcfly/dupword/README.md +++ b/tools/vendor/github.com/Abirdcfly/dupword/README.md @@ -109,10 +109,12 @@ Flags: apply all suggested fixes -flags print analyzer flags in JSON + -ignore value + ignore words -json emit JSON output -keyword value - key words for detecting duplicate words + keywords for detecting duplicate words -memprofile string write memory profile to this file -source @@ -128,7 +130,7 @@ Flags: ### 5. my advice -use `--keyword=the,and,a` and `-fix` together. I personally think that specifying only common repeated prepositions can effectively avoid false positives. +use `--keyword=the,and,a` and `-fix` together. I think that specifying only commonly repeated prepositions can effectively avoid false positives. see [dupword#4](https://github.com/Abirdcfly/dupword/issues/4) for real code example. diff --git a/tools/vendor/github.com/Abirdcfly/dupword/dupword.go b/tools/vendor/github.com/Abirdcfly/dupword/dupword.go index 508caca52f..c291eab527 100644 --- a/tools/vendor/github.com/Abirdcfly/dupword/dupword.go +++ b/tools/vendor/github.com/Abirdcfly/dupword/dupword.go @@ -52,6 +52,7 @@ This analyzer checks miswritten duplicate words in comments or package doc or st var ( defaultWord = []string{} // defaultWord = []string{"the", "and", "a"} + ignoreWord = map[string]bool{} ) type analyzer struct { @@ -70,7 +71,31 @@ func (a *analyzer) Set(w string) error { return nil } +type ignore struct { +} + +func (a *ignore) String() string { + t := make([]string,0, len(ignoreWord)) + for k := range ignoreWord { + t = append(t, k) + } + return strings.Join(t, ",") +} + +func (a *ignore) Set(w string) error { + for _, k := range strings.Split(w, ","){ + ignoreWord[k] = true + } + return nil +} + +// for test only +func ClearIgnoreWord() { + ignoreWord = map[string]bool{} +} + func NewAnalyzer() *analysis.Analyzer { + ignore := &ignore{} analyzer := &analyzer{KeyWord: defaultWord} a := &analysis.Analyzer{ Name: Name, @@ -80,7 +105,8 @@ func NewAnalyzer() *analysis.Analyzer { RunDespiteErrors: true, } a.Flags.Init(Name, flag.ExitOnError) - a.Flags.Var(analyzer, "keyword", "key words for detecting duplicate words") + a.Flags.Var(analyzer, "keyword", "keywords for detecting duplicate words") + a.Flags.Var(ignore, "ignore", "ignore words") a.Flags.Var(version{}, "V", "print version and exit") return a } @@ -176,7 +202,7 @@ func (a *analyzer) fixDuplicateWordInString(pass *analysis.Pass, lit *ast.BasicL } } -// CheckOneKey use to check there is defined duplicate word in a string. +// CheckOneKey use to check there is a defined duplicate word in a string. // raw is checked line. key is the keyword to check. empty means just check duplicate word. func CheckOneKey(raw, key string) (new string, findWord string, find bool) { if key == "" { @@ -298,5 +324,8 @@ func ExcludeWords(word string) (exclude bool) { if unicode.IsSymbol(firstRune) { return true } + if _, exist := ignoreWord[word]; exist { + return true + } return false } diff --git a/tools/vendor/github.com/Antonboom/errname/pkg/analyzer/analyzer.go b/tools/vendor/github.com/Antonboom/errname/pkg/analyzer/analyzer.go index 6425db137a..aa85225108 100644 --- a/tools/vendor/github.com/Antonboom/errname/pkg/analyzer/analyzer.go +++ b/tools/vendor/github.com/Antonboom/errname/pkg/analyzer/analyzer.go @@ -1,6 +1,7 @@ package analyzer import ( + "fmt" "go/ast" "go/token" "strconv" @@ -25,16 +26,16 @@ func New() *analysis.Analyzer { type stringSet = map[string]struct{} var ( - imports = []ast.Node{(*ast.ImportSpec)(nil)} - types = []ast.Node{(*ast.TypeSpec)(nil)} - funcs = []ast.Node{(*ast.FuncDecl)(nil)} + importNodes = []ast.Node{(*ast.ImportSpec)(nil)} + typeNodes = []ast.Node{(*ast.TypeSpec)(nil)} + funcNodes = []ast.Node{(*ast.FuncDecl)(nil)} ) func run(pass *analysis.Pass) (interface{}, error) { insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) pkgAliases := map[string]string{} - insp.Preorder(imports, func(node ast.Node) { + insp.Preorder(importNodes, func(node ast.Node) { i := node.(*ast.ImportSpec) if n := i.Name; n != nil && i.Path != nil { if path, err := strconv.Unquote(i.Path.Value); err == nil { @@ -45,14 +46,14 @@ func run(pass *analysis.Pass) (interface{}, error) { allTypes := stringSet{} typesSpecs := map[string]*ast.TypeSpec{} - insp.Preorder(types, func(node ast.Node) { + insp.Preorder(typeNodes, func(node ast.Node) { t := node.(*ast.TypeSpec) allTypes[t.Name.Name] = struct{}{} typesSpecs[t.Name.Name] = t }) errorTypes := stringSet{} - insp.Preorder(funcs, func(node ast.Node) { + insp.Preorder(funcNodes, func(node ast.Node) { f := node.(*ast.FuncDecl) t, ok := isMethodError(f) if !ok { @@ -62,7 +63,7 @@ func run(pass *analysis.Pass) (interface{}, error) { tSpec, ok := typesSpecs[t] if !ok { - panic("no specification for type " + t) + panic(fmt.Sprintf("no specification for type %q", t)) } if _, ok := tSpec.Type.(*ast.ArrayType); ok { @@ -75,7 +76,7 @@ func run(pass *analysis.Pass) (interface{}, error) { }) errorFuncs := stringSet{} - insp.Preorder(funcs, func(node ast.Node) { + insp.Preorder(funcNodes, func(node ast.Node) { f := node.(*ast.FuncDecl) if isFuncReturningErr(f.Type, allTypes, errorTypes) { errorFuncs[f.Name.Name] = struct{}{} diff --git a/tools/vendor/github.com/Antonboom/errname/pkg/analyzer/facts.go b/tools/vendor/github.com/Antonboom/errname/pkg/analyzer/facts.go index 8711f9cf5c..06f8d61d8e 100644 --- a/tools/vendor/github.com/Antonboom/errname/pkg/analyzer/facts.go +++ b/tools/vendor/github.com/Antonboom/errname/pkg/analyzer/facts.go @@ -1,8 +1,10 @@ package analyzer import ( + "fmt" "go/ast" "go/token" + "go/types" "strings" "unicode" ) @@ -34,15 +36,19 @@ func isMethodError(f *ast.FuncDecl) (typeName string, ok bool) { if i, ok := v.X.(*ast.Ident); ok { return i.Name } + case *ast.IndexListExpr: + if i, ok := v.X.(*ast.Ident); ok { + return i.Name + } } - return "" + panic(fmt.Errorf("unsupported Error() receiver type %q", types.ExprString(e))) } switch rt := f.Recv.List[0].Type; v := rt.(type) { - case *ast.Ident, *ast.IndexExpr: // SomeError, SomeError[T] + case *ast.Ident, *ast.IndexExpr, *ast.IndexListExpr: // SomeError, SomeError[T], SomeError[T1, T2, ...] receiverType = unwrapIdentName(rt) - case *ast.StarExpr: // *SomeError, *SomeError[T] + case *ast.StarExpr: // *SomeError, *SomeError[T], *SomeError[T1, T2, ...] receiverType = unwrapIdentName(v.X) } diff --git a/tools/vendor/github.com/Antonboom/nilnil/pkg/analyzer/analyzer.go b/tools/vendor/github.com/Antonboom/nilnil/pkg/analyzer/analyzer.go index 6bed7696a9..e980db5462 100644 --- a/tools/vendor/github.com/Antonboom/nilnil/pkg/analyzer/analyzer.go +++ b/tools/vendor/github.com/Antonboom/nilnil/pkg/analyzer/analyzer.go @@ -89,7 +89,7 @@ func (n *nilNil) run(pass *analysis.Pass) (interface{}, error) { fRes1, fRes2 := ft.Results.List[0], ft.Results.List[1] if !(n.isDangerNilField(fRes1, typeSpecs) && n.isErrorField(fRes2)) { - return + return false } rRes1, rRes2 := v.Results[0], v.Results[1] diff --git a/tools/vendor/github.com/Antonboom/testifylint/LICENSE b/tools/vendor/github.com/Antonboom/testifylint/LICENSE new file mode 100644 index 0000000000..9b1cf3a393 --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Anton Telyshev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/tools/vendor/github.com/Antonboom/testifylint/analyzer/analyzer.go b/tools/vendor/github.com/Antonboom/testifylint/analyzer/analyzer.go new file mode 100644 index 0000000000..c0b98f83c4 --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/analyzer/analyzer.go @@ -0,0 +1,175 @@ +package analyzer + +import ( + "fmt" + "go/ast" + "go/types" + "strings" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/ast/inspector" + + "github.com/Antonboom/testifylint/internal/analysisutil" + "github.com/Antonboom/testifylint/internal/checkers" + "github.com/Antonboom/testifylint/internal/config" + "github.com/Antonboom/testifylint/internal/testify" +) + +const ( + name = "testifylint" + doc = "Checks usage of " + testify.ModulePath + "." + url = "https://github.com/antonboom/" + name +) + +// New returns new instance of testifylint analyzer. +func New() *analysis.Analyzer { + cfg := config.NewDefault() + + analyzer := &analysis.Analyzer{ + Name: name, + Doc: doc, + URL: url, + Run: func(pass *analysis.Pass) (any, error) { + regularCheckers, advancedCheckers, err := newCheckers(cfg) + if err != nil { + return nil, fmt.Errorf("build checkers: %v", err) + } + + tl := &testifyLint{ + regularCheckers: regularCheckers, + advancedCheckers: advancedCheckers, + } + return tl.run(pass) + }, + } + config.BindToFlags(&cfg, &analyzer.Flags) + + return analyzer +} + +type testifyLint struct { + regularCheckers []checkers.RegularChecker + advancedCheckers []checkers.AdvancedChecker +} + +func (tl *testifyLint) run(pass *analysis.Pass) (any, error) { + filesToAnalysis := make([]*ast.File, 0, len(pass.Files)) + for _, f := range pass.Files { + if !analysisutil.Imports(f, testify.AssertPkgPath, testify.RequirePkgPath, testify.SuitePkgPath) { + continue + } + filesToAnalysis = append(filesToAnalysis, f) + } + + insp := inspector.New(filesToAnalysis) + + // Regular checkers. + insp.Preorder([]ast.Node{(*ast.CallExpr)(nil)}, func(node ast.Node) { + tl.regularCheck(pass, node.(*ast.CallExpr)) + }) + + // Advanced checkers. + for _, ch := range tl.advancedCheckers { + for _, d := range ch.Check(pass, insp) { + pass.Report(d) + } + } + + return nil, nil +} + +func (tl *testifyLint) regularCheck(pass *analysis.Pass, ce *ast.CallExpr) { + se, ok := ce.Fun.(*ast.SelectorExpr) + if !ok || se.Sel == nil { + return + } + fnName := se.Sel.Name + + initiatorPkg, isPkgCall := func() (*types.Package, bool) { + // Examples: + // s.Assert -> method of *suite.Suite -> package suite ("vendor/github.com/stretchr/testify/suite") + // s.Assert().Equal -> method of *assert.Assertions -> package assert ("vendor/github.com/stretchr/testify/assert") + // s.Equal -> method of *assert.Assertions -> package assert ("vendor/github.com/stretchr/testify/assert") + // reqObj.Falsef -> method of *require.Assertions -> package require ("vendor/github.com/stretchr/testify/require") + if sel, ok := pass.TypesInfo.Selections[se]; ok { + return sel.Obj().Pkg(), false + } + + // Examples: + // assert.False -> assert -> package assert ("vendor/github.com/stretchr/testify/assert") + // require.NotEqualf -> require -> package require ("vendor/github.com/stretchr/testify/require") + if id, ok := se.X.(*ast.Ident); ok { + if selObj := pass.TypesInfo.ObjectOf(id); selObj != nil { + if pkg, ok := selObj.(*types.PkgName); ok { + return pkg.Imported(), true + } + } + } + return nil, false + }() + if initiatorPkg == nil { + return + } + + isAssert := analysisutil.IsPkg(initiatorPkg, testify.AssertPkgName, testify.AssertPkgPath) + isRequire := analysisutil.IsPkg(initiatorPkg, testify.RequirePkgName, testify.RequirePkgPath) + if !(isAssert || isRequire) { + return + } + + call := &checkers.CallMeta{ + Range: ce, + IsPkg: isPkgCall, + IsAssert: isAssert, + Selector: se, + SelectorXStr: analysisutil.NodeString(pass.Fset, se.X), + Fn: checkers.FnMeta{ + Range: se.Sel, + Name: fnName, + IsFmt: strings.HasSuffix(fnName, "f"), + }, + Args: trimTArg(pass, isAssert, ce.Args), + ArgsRaw: ce.Args, + } + for _, ch := range tl.regularCheckers { + if d := ch.Check(pass, call); d != nil { + pass.Report(*d) + // NOTE(a.telyshev): I'm not interested in multiple diagnostics per assertion. + // This simplifies the code and also makes the linter more efficient. + return + } + } +} + +func trimTArg(pass *analysis.Pass, isAssert bool, args []ast.Expr) []ast.Expr { + if len(args) == 0 { + return args + } + + if isTestingTPtr(pass, isAssert, args[0]) { + return args[1:] + } + return args +} + +func isTestingTPtr(pass *analysis.Pass, isAssert bool, arg ast.Expr) bool { + pkgPath := testify.RequirePkgPath + if isAssert { + pkgPath = testify.AssertPkgPath + } + + testingInterfaceObj := analysisutil.ObjectOf(pass.Pkg, pkgPath, "TestingT") + if testingInterfaceObj == nil { + return false + } + + argType := pass.TypesInfo.TypeOf(arg) + if argType == nil { + return false + } + + return types.Implements( + argType, + testingInterfaceObj.Type().Underlying().(*types.Interface), + ) +} diff --git a/tools/vendor/github.com/Antonboom/testifylint/analyzer/checkers_factory.go b/tools/vendor/github.com/Antonboom/testifylint/analyzer/checkers_factory.go new file mode 100644 index 0000000000..e87e35e50d --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/analyzer/checkers_factory.go @@ -0,0 +1,48 @@ +package analyzer + +import ( + "fmt" + + "github.com/Antonboom/testifylint/internal/checkers" + "github.com/Antonboom/testifylint/internal/config" +) + +// newCheckers accepts linter config and returns slices of enabled checkers sorted by priority. +func newCheckers(cfg config.Config) ([]checkers.RegularChecker, []checkers.AdvancedChecker, error) { + enabledCheckers := cfg.EnabledCheckers + if len(enabledCheckers) == 0 { + enabledCheckers = checkers.EnabledByDefault() + } + if cfg.EnableAll { + enabledCheckers = checkers.All() + } + + checkers.SortByPriority(enabledCheckers) + + regularCheckers := make([]checkers.RegularChecker, 0, len(enabledCheckers)) + advancedCheckers := make([]checkers.AdvancedChecker, 0, len(enabledCheckers)/2) + + for _, name := range enabledCheckers { + ch, ok := checkers.Get(name) + if !ok { + return nil, nil, fmt.Errorf("unknown checker %q", name) + } + + switch c := ch.(type) { + case *checkers.ExpectedActual: + c.SetExpVarPattern(cfg.ExpectedActual.ExpVarPattern.Regexp) + + case *checkers.SuiteExtraAssertCall: + c.SetMode(cfg.SuiteExtraAssertCall.Mode) + } + + switch casted := ch.(type) { + case checkers.RegularChecker: + regularCheckers = append(regularCheckers, casted) + case checkers.AdvancedChecker: + advancedCheckers = append(advancedCheckers, casted) + } + } + + return regularCheckers, advancedCheckers, nil +} diff --git a/tools/vendor/github.com/Antonboom/testifylint/internal/analysisutil/doc.go b/tools/vendor/github.com/Antonboom/testifylint/internal/analysisutil/doc.go new file mode 100644 index 0000000000..b57cbd9384 --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/internal/analysisutil/doc.go @@ -0,0 +1,9 @@ +// Package analysisutil contains functions common for `analyzer` and `internal/checkers` packages. +// In addition, it is intended to "lighten" these packages. +// +// If the function is common to several packages, or it makes sense to test it separately without +// "polluting" the target package with tests of private functionality, then you can put function in this package. +// +// It's important to avoid dependency on `golang.org/x/tools/go/analysis` in the helpers API. +// This makes the API "narrower" and also allows you to test functions without some "abstraction leaks". +package analysisutil diff --git a/tools/vendor/github.com/Antonboom/testifylint/internal/analysisutil/file.go b/tools/vendor/github.com/Antonboom/testifylint/internal/analysisutil/file.go new file mode 100644 index 0000000000..3fc1f42b86 --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/internal/analysisutil/file.go @@ -0,0 +1,28 @@ +package analysisutil + +import ( + "go/ast" + "strconv" +) + +// Imports tells if the file imports at least one of the packages. +// If no packages provided then function returns false. +func Imports(file *ast.File, pkgs ...string) bool { + for _, i := range file.Imports { + if i.Path == nil { + continue + } + + path, err := strconv.Unquote(i.Path.Value) + if err != nil { + continue + } + // NOTE(a.telyshev): Don't use `slices.Contains` to keep the minimum module version 1.20. + for _, pkg := range pkgs { // Small O(n). + if pkg == path { + return true + } + } + } + return false +} diff --git a/tools/vendor/github.com/Antonboom/testifylint/internal/analysisutil/format.go b/tools/vendor/github.com/Antonboom/testifylint/internal/analysisutil/format.go new file mode 100644 index 0000000000..fcb4b847f6 --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/internal/analysisutil/format.go @@ -0,0 +1,34 @@ +package analysisutil + +import ( + "bytes" + "go/ast" + "go/format" + "go/token" +) + +// NodeString is a more powerful analogue of types.ExprString. +// Return empty string if node AST is invalid. +func NodeString(fset *token.FileSet, node ast.Node) string { + if v := formatNode(fset, node); v != nil { + return v.String() + } + return "" +} + +// NodeBytes works as NodeString but returns a byte slice. +// Return nil if node AST is invalid. +func NodeBytes(fset *token.FileSet, node ast.Node) []byte { + if v := formatNode(fset, node); v != nil { + return v.Bytes() + } + return nil +} + +func formatNode(fset *token.FileSet, node ast.Node) *bytes.Buffer { + buf := new(bytes.Buffer) + if err := format.Node(buf, fset, node); err != nil { + return nil + } + return buf +} diff --git a/tools/vendor/github.com/Antonboom/testifylint/internal/analysisutil/object.go b/tools/vendor/github.com/Antonboom/testifylint/internal/analysisutil/object.go new file mode 100644 index 0000000000..e01fba5c13 --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/internal/analysisutil/object.go @@ -0,0 +1,34 @@ +package analysisutil + +import ( + "go/ast" + "go/types" +) + +// ObjectOf works in context of Golang package and returns types.Object for the given object's package and name. +// The search is based on the provided package and its dependencies (imports). +// Returns nil if the object is not found. +func ObjectOf(pkg *types.Package, objPkg, objName string) types.Object { + if pkg.Path() == objPkg { + return pkg.Scope().Lookup(objName) + } + + for _, i := range pkg.Imports() { + if trimVendor(i.Path()) == objPkg { + return i.Scope().Lookup(objName) + } + } + return nil +} + +// IsObj returns true if expression is identifier which notes to given types.Object. +// Useful in combination with types.Universe objects. +func IsObj(typesInfo *types.Info, expr ast.Expr, expected types.Object) bool { + id, ok := expr.(*ast.Ident) + if !ok { + return false + } + + obj := typesInfo.ObjectOf(id) + return obj.Id() == expected.Id() +} diff --git a/tools/vendor/github.com/Antonboom/testifylint/internal/analysisutil/pkg.go b/tools/vendor/github.com/Antonboom/testifylint/internal/analysisutil/pkg.go new file mode 100644 index 0000000000..d34be5d341 --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/internal/analysisutil/pkg.go @@ -0,0 +1,19 @@ +package analysisutil + +import ( + "go/types" + "strings" +) + +// IsPkg checks that package has corresponding objName and path. +// Supports vendored packages. +func IsPkg(pkg *types.Package, name, path string) bool { + return pkg.Name() == name && trimVendor(pkg.Path()) == path +} + +func trimVendor(path string) string { + if strings.HasPrefix(path, "vendor/") { + return path[len("vendor/"):] + } + return path +} diff --git a/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/bool_compare.go b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/bool_compare.go new file mode 100644 index 0000000000..8245ab58e6 --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/bool_compare.go @@ -0,0 +1,223 @@ +package checkers + +import ( + "go/ast" + "go/token" + "go/types" + + "golang.org/x/tools/go/analysis" + + "github.com/Antonboom/testifylint/internal/analysisutil" +) + +// BoolCompare detects situations like +// +// assert.Equal(t, false, result) +// assert.NotEqual(t, result, true) +// assert.False(t, !result) +// assert.True(t, result == true) +// ... +// +// and requires +// +// assert.False(t, result) +// assert.True(t, result) +type BoolCompare struct{} // + +// NewBoolCompare constructs BoolCompare checker. +func NewBoolCompare() BoolCompare { return BoolCompare{} } +func (BoolCompare) Name() string { return "bool-compare" } + +func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { + newUseFnDiagnostic := func(proposed string, survivingArg ast.Node, replaceStart, replaceEnd token.Pos) *analysis.Diagnostic { + return newUseFunctionDiagnostic(checker.Name(), call, proposed, + newSuggestedFuncReplacement(call, proposed, analysis.TextEdit{ + Pos: replaceStart, + End: replaceEnd, + NewText: analysisutil.NodeBytes(pass.Fset, survivingArg), + }), + ) + } + + newUseTrueDiagnostic := func(survivingArg ast.Node, replaceStart, replaceEnd token.Pos) *analysis.Diagnostic { + return newUseFnDiagnostic("True", survivingArg, replaceStart, replaceEnd) + } + + newUseFalseDiagnostic := func(survivingArg ast.Node, replaceStart, replaceEnd token.Pos) *analysis.Diagnostic { + return newUseFnDiagnostic("False", survivingArg, replaceStart, replaceEnd) + } + + newNeedSimplifyDiagnostic := func(survivingArg ast.Node, replaceStart, replaceEnd token.Pos) *analysis.Diagnostic { + return newDiagnostic(checker.Name(), call, "need to simplify the assertion", + &analysis.SuggestedFix{ + Message: "Simplify the assertion", + TextEdits: []analysis.TextEdit{{ + Pos: replaceStart, + End: replaceEnd, + NewText: analysisutil.NodeBytes(pass.Fset, survivingArg), + }}, + }, + ) + } + + switch call.Fn.Name { + case "Equal", "Equalf": + if len(call.Args) < 2 { + return nil + } + + arg1, arg2 := call.Args[0], call.Args[1] + t1, t2 := isUntypedTrue(pass, arg1), isUntypedTrue(pass, arg2) + f1, f2 := isUntypedFalse(pass, arg1), isUntypedFalse(pass, arg2) + + switch { + case xor(t1, t2): + survivingArg, _ := anyVal([]bool{t1, t2}, arg2, arg1) + return newUseTrueDiagnostic(survivingArg, arg1.Pos(), arg2.End()) + + case xor(f1, f2): + survivingArg, _ := anyVal([]bool{f1, f2}, arg2, arg1) + return newUseFalseDiagnostic(survivingArg, arg1.Pos(), arg2.End()) + } + + case "NotEqual", "NotEqualf": + if len(call.Args) < 2 { + return nil + } + + arg1, arg2 := call.Args[0], call.Args[1] + t1, t2 := isUntypedTrue(pass, arg1), isUntypedTrue(pass, arg2) + f1, f2 := isUntypedFalse(pass, arg1), isUntypedFalse(pass, arg2) + + switch { + case xor(t1, t2): + survivingArg, _ := anyVal([]bool{t1, t2}, arg2, arg1) + return newUseFalseDiagnostic(survivingArg, arg1.Pos(), arg2.End()) + + case xor(f1, f2): + survivingArg, _ := anyVal([]bool{f1, f2}, arg2, arg1) + return newUseTrueDiagnostic(survivingArg, arg1.Pos(), arg2.End()) + } + + case "True", "Truef": + if len(call.Args) < 1 { + return nil + } + expr := call.Args[0] + + { + arg1, ok1 := isComparisonWithTrue(pass, expr, token.EQL) + arg2, ok2 := isComparisonWithFalse(pass, expr, token.NEQ) + + if survivingArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2); ok { + return newNeedSimplifyDiagnostic(survivingArg, expr.Pos(), expr.End()) + } + } + + { + arg1, ok1 := isComparisonWithTrue(pass, expr, token.NEQ) + arg2, ok2 := isComparisonWithFalse(pass, expr, token.EQL) + arg3, ok3 := isNegation(expr) + + if survivingArg, ok := anyVal([]bool{ok1, ok2, ok3}, arg1, arg2, arg3); ok { + return newUseFalseDiagnostic(survivingArg, expr.Pos(), expr.End()) + } + } + + case "False", "Falsef": + if len(call.Args) < 1 { + return nil + } + expr := call.Args[0] + + { + arg1, ok1 := isComparisonWithTrue(pass, expr, token.EQL) + arg2, ok2 := isComparisonWithFalse(pass, expr, token.NEQ) + + if survivingArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2); ok { + return newNeedSimplifyDiagnostic(survivingArg, expr.Pos(), expr.End()) + } + } + + { + arg1, ok1 := isComparisonWithTrue(pass, expr, token.NEQ) + arg2, ok2 := isComparisonWithFalse(pass, expr, token.EQL) + arg3, ok3 := isNegation(expr) + + if survivingArg, ok := anyVal([]bool{ok1, ok2, ok3}, arg1, arg2, arg3); ok { + return newUseTrueDiagnostic(survivingArg, expr.Pos(), expr.End()) + } + } + } + return nil +} + +var ( + falseObj = types.Universe.Lookup("false") + trueObj = types.Universe.Lookup("true") +) + +func isUntypedTrue(pass *analysis.Pass, e ast.Expr) bool { + return analysisutil.IsObj(pass.TypesInfo, e, trueObj) +} + +func isUntypedFalse(pass *analysis.Pass, e ast.Expr) bool { + return analysisutil.IsObj(pass.TypesInfo, e, falseObj) +} + +func isComparisonWithTrue(pass *analysis.Pass, e ast.Expr, op token.Token) (ast.Expr, bool) { + return isComparisonWith(pass, e, isUntypedTrue, op) +} + +func isComparisonWithFalse(pass *analysis.Pass, e ast.Expr, op token.Token) (ast.Expr, bool) { + return isComparisonWith(pass, e, isUntypedFalse, op) +} + +type predicate func(pass *analysis.Pass, e ast.Expr) bool + +func isComparisonWith(pass *analysis.Pass, e ast.Expr, predicate predicate, op token.Token) (ast.Expr, bool) { + be, ok := e.(*ast.BinaryExpr) + if !ok { + return nil, false + } + if be.Op != op { + return nil, false + } + + t1, t2 := predicate(pass, be.X), predicate(pass, be.Y) + if xor(t1, t2) { + if t1 { + return be.Y, true + } + return be.X, true + } + return nil, false +} + +func isNegation(e ast.Expr) (ast.Expr, bool) { + ue, ok := e.(*ast.UnaryExpr) + if !ok { + return nil, false + } + return ue.X, ue.Op == token.NOT +} + +func xor(a, b bool) bool { + return a != b +} + +// anyVal returns the first value[i] for which bools[i] is true. +func anyVal[T any](bools []bool, vals ...T) (T, bool) { + if len(bools) != len(vals) { + panic("inconsistent usage of valOr") //nolint:forbidigo // Does not depend on the code being analyzed. + } + + for i, b := range bools { + if b { + return vals[i], true + } + } + + var _default T + return _default, false +} diff --git a/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/checker.go b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/checker.go new file mode 100644 index 0000000000..f3249dc3ce --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/checker.go @@ -0,0 +1,59 @@ +package checkers + +import ( + "go/ast" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/ast/inspector" +) + +// CallMeta stores meta info about assertion function/method call, for example +// +// assert.Equal(t, 42, result, "helpful comment") +type CallMeta struct { + // Range contains start and end position of assertion call. + analysis.Range + // IsPkg true if this is package (not object) call. + IsPkg bool + // IsAssert true if this is "testify/assert" package (or object) call. + IsAssert bool + // Selector is the AST expression of "assert.Equal". + Selector *ast.SelectorExpr + // SelectorXStr is a string representation of Selector's left part – value before point, e.g. "assert". + SelectorXStr string + // Fn stores meta info about assertion function itself. + Fn FnMeta + // Args stores assertion call arguments but without `t *testing.T` argument. + // E.g [42, result, "helpful comment"]. + Args []ast.Expr + // ArgsRaw stores assertion call initial arguments. + // E.g [t, 42, result, "helpful comment"]. + ArgsRaw []ast.Expr +} + +// FnMeta stores meta info about assertion function itself, for example "Equal". +type FnMeta struct { + // Range contains start and end position of function Name. + analysis.Range + // Name is a function name. + Name string + // IsFmt is true if function is formatted, e.g. "Equalf". + IsFmt bool +} + +// Checker describes named checker. +type Checker interface { + Name() string +} + +// RegularChecker check assertion call presented in CallMeta form. +type RegularChecker interface { + Checker + Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic +} + +// AdvancedChecker implements complex Check logic different from trivial CallMeta check. +type AdvancedChecker interface { + Checker + Check(pass *analysis.Pass, inspector *inspector.Inspector) []analysis.Diagnostic +} diff --git a/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/checkers_registry.go b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/checkers_registry.go new file mode 100644 index 0000000000..47eaafb760 --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/checkers_registry.go @@ -0,0 +1,101 @@ +package checkers + +import ( + "sort" +) + +// registry stores checkers meta information in checkers' priority order. +var registry = checkersRegistry{ + // Regular checkers. + {factory: asCheckerFactory(NewFloatCompare), enabledByDefault: true}, + {factory: asCheckerFactory(NewBoolCompare), enabledByDefault: true}, + {factory: asCheckerFactory(NewEmpty), enabledByDefault: true}, + {factory: asCheckerFactory(NewLen), enabledByDefault: true}, + {factory: asCheckerFactory(NewCompares), enabledByDefault: true}, + {factory: asCheckerFactory(NewErrorNil), enabledByDefault: true}, + {factory: asCheckerFactory(NewErrorIsAs), enabledByDefault: true}, + {factory: asCheckerFactory(NewRequireError), enabledByDefault: true}, + {factory: asCheckerFactory(NewExpectedActual), enabledByDefault: true}, + {factory: asCheckerFactory(NewSuiteExtraAssertCall), enabledByDefault: true}, + {factory: asCheckerFactory(NewSuiteDontUsePkg), enabledByDefault: true}, + // Advanced checkers. + {factory: asCheckerFactory(NewSuiteTHelper), enabledByDefault: false}, +} + +type checkersRegistry []checkerMeta + +type checkerMeta struct { + factory checkerFactory + enabledByDefault bool +} + +type checkerFactory func() Checker + +func asCheckerFactory[T Checker](fn func() T) checkerFactory { + return func() Checker { + return fn() + } +} + +func (r checkersRegistry) get(name string) (m checkerMeta, priority int, found bool) { + for i, meta := range r { + if meta.factory().Name() == name { + return meta, i, true + } + } + return checkerMeta{}, 0, false +} + +// All returns all checkers names sorted by checker's priority. +func All() []string { + result := make([]string, 0, len(registry)) + for _, meta := range registry { + result = append(result, meta.factory().Name()) + } + return result +} + +// EnabledByDefault returns checkers enabled by default sorted by checker's priority. +func EnabledByDefault() []string { + result := make([]string, 0, len(registry)) + for _, meta := range registry { + if meta.enabledByDefault { + result = append(result, meta.factory().Name()) + } + } + return result +} + +// Get returns new checker instance by checker's name. +func Get(name string) (Checker, bool) { + meta, _, ok := registry.get(name) + if ok { + return meta.factory(), true + } + return nil, false +} + +// IsKnown checks if there is a checker with that name. +func IsKnown(name string) bool { + _, _, ok := registry.get(name) + return ok +} + +// IsEnabledByDefault returns true if a checker is enabled by default. +// Returns false if there is no such checker in the registry. +// For pre-validation use Get or IsKnown. +func IsEnabledByDefault(name string) bool { + meta, _, ok := registry.get(name) + return ok && meta.enabledByDefault +} + +// SortByPriority mutates the input checkers names by sorting them in checker priority order. +// Ignores unknown checkers. For pre-validation use Get or IsKnown. +func SortByPriority(checkers []string) { + sort.Slice(checkers, func(i, j int) bool { + lhs, rhs := checkers[i], checkers[j] + _, lhsPriority, _ := registry.get(lhs) + _, rhsPriority, _ := registry.get(rhs) + return lhsPriority < rhsPriority + }) +} diff --git a/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/compares.go b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/compares.go new file mode 100644 index 0000000000..afc829f971 --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/compares.go @@ -0,0 +1,95 @@ +package checkers + +import ( + "bytes" + "go/ast" + "go/token" + + "golang.org/x/tools/go/analysis" + + "github.com/Antonboom/testifylint/internal/analysisutil" +) + +// Compares detects situations like +// +// assert.True(t, a == b) +// assert.True(t, a != b) +// assert.True(t, a > b) +// assert.True(t, a >= b) +// assert.True(t, a < b) +// assert.True(t, a <= b) +// ... +// +// and requires +// +// assert.Equal(t, a, b) +// assert.NotEqual(t, a, b) +// assert.Greater(t, a, b) +// assert.GreaterOrEqual(t, a, b) +// assert.Less(t, a, b) +// assert.LessOrEqual(t, a, b) +type Compares struct{} + +// NewCompares constructs Compares checker. +func NewCompares() Compares { return Compares{} } +func (Compares) Name() string { return "compares" } + +func (checker Compares) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { + if len(call.Args) < 1 { + return nil + } + + be, ok := call.Args[0].(*ast.BinaryExpr) + if !ok { + return nil + } + + var tokenToProposedFn map[token.Token]string + + switch call.Fn.Name { + case "True", "Truef": + tokenToProposedFn = tokenToProposedFnInsteadOfTrue + case "False", "Falsef": + tokenToProposedFn = tokenToProposedFnInsteadOfFalse + default: + return nil + } + + if proposedFn, ok := tokenToProposedFn[be.Op]; ok { + a, b := be.X, be.Y + return newUseFunctionDiagnostic(checker.Name(), call, proposedFn, + newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{ + Pos: be.X.Pos(), + End: be.Y.End(), + NewText: formatAsCallArgs(pass, a, b), + }), + ) + } + return nil +} + +var tokenToProposedFnInsteadOfTrue = map[token.Token]string{ + token.EQL: "Equal", + token.NEQ: "NotEqual", + token.GTR: "Greater", + token.GEQ: "GreaterOrEqual", + token.LSS: "Less", + token.LEQ: "LessOrEqual", +} + +var tokenToProposedFnInsteadOfFalse = map[token.Token]string{ + token.EQL: "NotEqual", + token.NEQ: "Equal", + token.GTR: "LessOrEqual", + token.GEQ: "Less", + token.LSS: "GreaterOrEqual", + token.LEQ: "Greater", +} + +// formatAsCallArgs joins a and b and return bytes like `a, b`. +func formatAsCallArgs(pass *analysis.Pass, a, b ast.Node) []byte { + return bytes.Join([][]byte{ + analysisutil.NodeBytes(pass.Fset, a), + analysisutil.NodeBytes(pass.Fset, b), + }, []byte(", ")) +} diff --git a/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/diagnostic.go b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/diagnostic.go new file mode 100644 index 0000000000..4ab69c69bb --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/diagnostic.go @@ -0,0 +1,60 @@ +package checkers + +import ( + "fmt" + + "golang.org/x/tools/go/analysis" +) + +func newUseFunctionDiagnostic( + checker string, + call *CallMeta, + proposedFn string, + fix *analysis.SuggestedFix, +) *analysis.Diagnostic { + f := proposedFn + if call.Fn.IsFmt { + f += "f" + } + msg := fmt.Sprintf("use %s.%s", call.SelectorXStr, f) + + return newDiagnostic(checker, call, msg, fix) +} + +func newDiagnostic( + checker string, + rng analysis.Range, + msg string, + fix *analysis.SuggestedFix, +) *analysis.Diagnostic { + d := analysis.Diagnostic{ + Pos: rng.Pos(), + End: rng.End(), + Category: checker, + Message: checker + ": " + msg, + } + if fix != nil { + d.SuggestedFixes = []analysis.SuggestedFix{*fix} + } + return &d +} + +func newSuggestedFuncReplacement( + call *CallMeta, + proposedFn string, + additionalEdits ...analysis.TextEdit, +) *analysis.SuggestedFix { + if call.Fn.IsFmt { + proposedFn += "f" + } + return &analysis.SuggestedFix{ + Message: fmt.Sprintf("Replace `%s` with `%s`", call.Fn.Name, proposedFn), + TextEdits: append([]analysis.TextEdit{ + { + Pos: call.Fn.Pos(), + End: call.Fn.End(), + NewText: []byte(proposedFn), + }, + }, additionalEdits...), + } +} diff --git a/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/empty.go b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/empty.go new file mode 100644 index 0000000000..79c64205f4 --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/empty.go @@ -0,0 +1,172 @@ +package checkers + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + + "golang.org/x/tools/go/analysis" + + "github.com/Antonboom/testifylint/internal/analysisutil" +) + +// Empty detects situations like +// +// assert.Len(t, arr, 0) +// assert.Equal(t, 0, len(arr)) +// assert.NotEqual(t, 0, len(arr)) +// assert.GreaterOrEqual(t, len(arr), 1) +// ... +// +// and requires +// +// assert.Empty(t, arr) +// assert.NotEmpty(t, arr) +type Empty struct{} + +// NewEmpty constructs Empty checker. +func NewEmpty() Empty { return Empty{} } +func (Empty) Name() string { return "empty" } + +func (checker Empty) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { + if d := checker.checkEmpty(pass, call); d != nil { + return d + } + return checker.checkNotEmpty(pass, call) +} + +func (checker Empty) checkEmpty(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { //nolint:gocognit + newUseEmptyDiagnostic := func(replaceStart, replaceEnd token.Pos, replaceWith ast.Expr) *analysis.Diagnostic { + const proposed = "Empty" + return newUseFunctionDiagnostic(checker.Name(), call, proposed, + newSuggestedFuncReplacement(call, proposed, analysis.TextEdit{ + Pos: replaceStart, + End: replaceEnd, + NewText: analysisutil.NodeBytes(pass.Fset, replaceWith), + }), + ) + } + + if len(call.Args) < 2 { + return nil + } + a, b := call.Args[0], call.Args[1] + + switch call.Fn.Name { + case "Len", "Lenf": + if isZero(b) { + return newUseEmptyDiagnostic(a.Pos(), b.End(), a) + } + + case "Equal", "Equalf": + arg1, ok1 := isLenCallAndZero(pass, a, b) + arg2, ok2 := isLenCallAndZero(pass, b, a) + + if lenArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2); ok { + return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg) + } + + case "LessOrEqual", "LessOrEqualf": + if lenArg, ok := isBuiltinLenCall(pass, a); ok && isZero(b) { + return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg) + } + + case "GreaterOrEqual", "GreaterOrEqualf": + if lenArg, ok := isBuiltinLenCall(pass, b); ok && isZero(a) { + return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg) + } + + case "Less", "Lessf": + if lenArg, ok := isBuiltinLenCall(pass, a); ok && isOne(b) { + return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg) + } + + case "Greater", "Greaterf": + if lenArg, ok := isBuiltinLenCall(pass, b); ok && isOne(a) { + return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg) + } + } + return nil +} + +func (checker Empty) checkNotEmpty(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { //nolint:gocognit + newUseNotEmptyDiagnostic := func(replaceStart, replaceEnd token.Pos, replaceWith ast.Expr) *analysis.Diagnostic { + const proposed = "NotEmpty" + return newUseFunctionDiagnostic(checker.Name(), call, proposed, + newSuggestedFuncReplacement(call, proposed, analysis.TextEdit{ + Pos: replaceStart, + End: replaceEnd, + NewText: analysisutil.NodeBytes(pass.Fset, replaceWith), + }), + ) + } + + if len(call.Args) < 2 { + return nil + } + a, b := call.Args[0], call.Args[1] + + switch call.Fn.Name { + case "NotEqual", "NotEqualf": + arg1, ok1 := isLenCallAndZero(pass, a, b) + arg2, ok2 := isLenCallAndZero(pass, b, a) + + if lenArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2); ok { + return newUseNotEmptyDiagnostic(a.Pos(), b.End(), lenArg) + } + + case "Greater", "Greaterf": + if lenArg, ok := isBuiltinLenCall(pass, a); ok && isZero(b) || isOne(b) { + return newUseNotEmptyDiagnostic(a.Pos(), b.End(), lenArg) + } + + case "Less", "Lessf": + if lenArg, ok := isBuiltinLenCall(pass, b); ok && isZero(a) || isOne(a) { + return newUseNotEmptyDiagnostic(a.Pos(), b.End(), lenArg) + } + + case "GreaterOrEqual", "GreaterOrEqualf": + if lenArg, ok := isBuiltinLenCall(pass, a); ok && isOne(b) { + return newUseNotEmptyDiagnostic(a.Pos(), b.End(), lenArg) + } + + case "LessOrEqual", "LessOrEqualf": + if lenArg, ok := isBuiltinLenCall(pass, b); ok && isOne(a) { + return newUseNotEmptyDiagnostic(a.Pos(), b.End(), lenArg) + } + } + return nil +} + +var lenObj = types.Universe.Lookup("len") + +func isLenCallAndZero(pass *analysis.Pass, a, b ast.Expr) (ast.Expr, bool) { + lenArg, ok := isBuiltinLenCall(pass, a) + return lenArg, ok && isZero(b) +} + +func isBuiltinLenCall(pass *analysis.Pass, e ast.Expr) (ast.Expr, bool) { + ce, ok := e.(*ast.CallExpr) + if !ok { + return nil, false + } + + if analysisutil.IsObj(pass.TypesInfo, ce.Fun, lenObj) && len(ce.Args) == 1 { + return ce.Args[0], true + } + return nil, false +} + +func isZero(e ast.Expr) bool { + return isIntNumber(e, 0) +} + +func isOne(e ast.Expr) bool { + return isIntNumber(e, 1) +} + +func isIntNumber(e ast.Expr, v int) bool { + bl, ok := e.(*ast.BasicLit) + return ok && bl.Kind == token.INT && bl.Value == fmt.Sprintf("%d", v) +} diff --git a/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/error_is_as.go b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/error_is_as.go new file mode 100644 index 0000000000..e6abd0ba4f --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/error_is_as.go @@ -0,0 +1,124 @@ +package checkers + +import ( + "fmt" + "go/ast" + + "golang.org/x/tools/go/analysis" + + "github.com/Antonboom/testifylint/internal/analysisutil" +) + +// ErrorIsAs detects situations like +// +// assert.Error(t, err, errSentinel) +// assert.NoError(t, err, errSentinel) +// assert.True(t, errors.Is(err, errSentinel)) +// assert.False(t, errors.Is(err, errSentinel)) +// assert.True(t, errors.As(err, &target)) +// +// and requires +// +// assert.ErrorIs(t, err, errSentinel) +// assert.NotErrorIs(t, err, errSentinel) +// assert.ErrorAs(t, err, &target) +type ErrorIsAs struct{} + +// NewErrorIsAs constructs ErrorIsAs checker. +func NewErrorIsAs() ErrorIsAs { return ErrorIsAs{} } +func (ErrorIsAs) Name() string { return "error-is-as" } + +func (checker ErrorIsAs) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { + switch call.Fn.Name { + case "Error", "Errorf": + if len(call.Args) >= 2 && isError(pass, call.Args[1]) { + const proposed = "ErrorIs" + msg := fmt.Sprintf("invalid usage of %[1]s.Error, use %[1]s.%[2]s instead", call.SelectorXStr, proposed) + return newDiagnostic(checker.Name(), call, msg, newSuggestedFuncReplacement(call, proposed)) + } + + case "NoError", "NoErrorf": + if len(call.Args) >= 2 && isError(pass, call.Args[1]) { + const proposed = "NotErrorIs" + msg := fmt.Sprintf("invalid usage of %[1]s.NoError, use %[1]s.%[2]s instead", call.SelectorXStr, proposed) + return newDiagnostic(checker.Name(), call, msg, newSuggestedFuncReplacement(call, proposed)) + } + + case "True", "Truef": + if len(call.Args) < 1 { + return nil + } + + ce, ok := call.Args[0].(*ast.CallExpr) + if !ok { + return nil + } + if len(ce.Args) != 2 { + return nil + } + + var proposed string + switch { + case isErrorsIsCall(pass, ce): + proposed = "ErrorIs" + case isErrorsAsCall(pass, ce): + proposed = "ErrorAs" + } + if proposed != "" { + return newUseFunctionDiagnostic(checker.Name(), call, proposed, + newSuggestedFuncReplacement(call, proposed, analysis.TextEdit{ + Pos: ce.Pos(), + End: ce.End(), + NewText: formatAsCallArgs(pass, ce.Args[0], ce.Args[1]), + }), + ) + } + + case "False", "Falsef": + if len(call.Args) < 1 { + return nil + } + + ce, ok := call.Args[0].(*ast.CallExpr) + if !ok { + return nil + } + if len(ce.Args) != 2 { + return nil + } + + if isErrorsIsCall(pass, ce) { + const proposed = "NotErrorIs" + return newUseFunctionDiagnostic(checker.Name(), call, proposed, + newSuggestedFuncReplacement(call, proposed, analysis.TextEdit{ + Pos: ce.Pos(), + End: ce.End(), + NewText: formatAsCallArgs(pass, ce.Args[0], ce.Args[1]), + }), + ) + } + } + return nil +} + +func isErrorsIsCall(pass *analysis.Pass, ce *ast.CallExpr) bool { + return isErrorsPkgFnCall(pass, ce, "Is") +} + +func isErrorsAsCall(pass *analysis.Pass, ce *ast.CallExpr) bool { + return isErrorsPkgFnCall(pass, ce, "As") +} + +func isErrorsPkgFnCall(pass *analysis.Pass, ce *ast.CallExpr, fn string) bool { + se, ok := ce.Fun.(*ast.SelectorExpr) + if !ok { + return false + } + + errorsIsObj := analysisutil.ObjectOf(pass.Pkg, "errors", fn) + if errorsIsObj == nil { + return false + } + + return analysisutil.IsObj(pass.TypesInfo, se.Sel, errorsIsObj) +} diff --git a/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/error_nil.go b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/error_nil.go new file mode 100644 index 0000000000..b45629a48f --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/error_nil.go @@ -0,0 +1,109 @@ +package checkers + +import ( + "go/ast" + "go/token" + "go/types" + + "golang.org/x/tools/go/analysis" + + "github.com/Antonboom/testifylint/internal/analysisutil" +) + +// ErrorNil detects situations like +// +// assert.Nil(t, err) +// assert.NotNil(t, err) +// assert.Equal(t, err, nil) +// assert.NotEqual(t, err, nil) +// +// and requires +// +// assert.NoError(t, err) +// assert.Error(t, err) +type ErrorNil struct{} + +// NewErrorNil constructs ErrorNil checker. +func NewErrorNil() ErrorNil { return ErrorNil{} } +func (ErrorNil) Name() string { return "error-nil" } + +func (checker ErrorNil) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { + const ( + errorFn = "Error" + noErrorFn = "NoError" + ) + + proposedFn, survivingArg, replacementEndPos := func() (string, ast.Expr, token.Pos) { + switch call.Fn.Name { + case "NotNil", "NotNilf": + if len(call.Args) >= 1 && isError(pass, call.Args[0]) { + return errorFn, call.Args[0], call.Args[0].End() + } + + case "Nil", "Nilf": + if len(call.Args) >= 1 && isError(pass, call.Args[0]) { + return noErrorFn, call.Args[0], call.Args[0].End() + } + + case "Equal", "Equalf": + if len(call.Args) < 2 { + return "", nil, token.NoPos + } + a, b := call.Args[0], call.Args[1] + + switch { + case isError(pass, a) && isNil(pass, b): + return noErrorFn, a, b.End() + case isNil(pass, a) && isError(pass, b): + return noErrorFn, b, b.End() + } + + case "NotEqual", "NotEqualf": + if len(call.Args) < 2 { + return "", nil, token.NoPos + } + a, b := call.Args[0], call.Args[1] + + switch { + case isError(pass, a) && isNil(pass, b): + return errorFn, a, b.End() + case isNil(pass, a) && isError(pass, b): + return errorFn, b, b.End() + } + } + return "", nil, token.NoPos + }() + + if proposedFn != "" { + return newUseFunctionDiagnostic(checker.Name(), call, proposedFn, + newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{ + Pos: call.Args[0].Pos(), + End: replacementEndPos, + NewText: analysisutil.NodeBytes(pass.Fset, survivingArg), + }), + ) + } + return nil +} + +var errIface = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) + +func isError(pass *analysis.Pass, expr ast.Expr) bool { + t := pass.TypesInfo.TypeOf(expr) + if t == nil { + return false + } + + _, ok := t.Underlying().(*types.Interface) + return ok && types.Implements(t, errIface) +} + +func isNil(pass *analysis.Pass, expr ast.Expr) bool { + t := pass.TypesInfo.TypeOf(expr) + if t == nil { + return false + } + + b, ok := t.(*types.Basic) + return ok && b.Kind()&types.UntypedNil > 0 +} diff --git a/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/expected_actual.go b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/expected_actual.go new file mode 100644 index 0000000000..ff8243980b --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/expected_actual.go @@ -0,0 +1,156 @@ +package checkers + +import ( + "go/ast" + "go/types" + "regexp" + + "golang.org/x/tools/go/analysis" +) + +// DefaultExpectedVarPattern matches variables with "expected" or "wanted" prefix or suffix in the name. +var DefaultExpectedVarPattern = regexp.MustCompile( + `(^(exp(ected)?|want(ed)?)([A-Z]\w*)?$)|(^(\w*[a-z])?(Exp(ected)?|Want(ed)?)$)`) + +// ExpectedActual detects situation like +// +// assert.NotEqual(t, result, "expected value") +// +// and requires +// +// assert.NotEqual(t, "expected value", result) +type ExpectedActual struct { + expVarPattern *regexp.Regexp +} + +// NewExpectedActual constructs ExpectedActual checker using DefaultExpectedVarPattern. +func NewExpectedActual() *ExpectedActual { + return &ExpectedActual{expVarPattern: DefaultExpectedVarPattern} +} + +func (ExpectedActual) Name() string { return "expected-actual" } + +func (checker *ExpectedActual) SetExpVarPattern(p *regexp.Regexp) *ExpectedActual { + if p != nil { + checker.expVarPattern = p + } + return checker +} + +func (checker ExpectedActual) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { + switch call.Fn.Name { + case "Equal", "Equalf", "NotEqual", "NotEqualf", + "JSONEq", "JSONEqf", "YAMLEq", "YAMLEqf": + default: + return nil + } + + if len(call.Args) < 2 { + return nil + } + first, second := call.Args[0], call.Args[1] + + if checker.isWrongExpectedActualOrder(pass, first, second) { + return newDiagnostic(checker.Name(), call, "need to reverse actual and expected values", &analysis.SuggestedFix{ + Message: "Reverse actual and expected values", + TextEdits: []analysis.TextEdit{ + { + Pos: first.Pos(), + End: second.End(), + NewText: formatAsCallArgs(pass, second, first), + }, + }, + }) + } + return nil +} + +func (checker ExpectedActual) isWrongExpectedActualOrder(pass *analysis.Pass, first, second ast.Expr) bool { + leftIsCandidate := checker.isExpectedValueCandidate(pass, first) + rightIsCandidate := checker.isExpectedValueCandidate(pass, second) + return rightIsCandidate && !leftIsCandidate +} + +func (checker ExpectedActual) isExpectedValueCandidate(pass *analysis.Pass, expr ast.Expr) bool { + switch v := expr.(type) { + case *ast.CompositeLit: + return true + + case *ast.CallExpr: + return isCastedBasicLitOrExpectedValue(v, checker.expVarPattern) || + isExpectedValueFactory(v, checker.expVarPattern) + } + + return isBasicLit(expr) || + isUntypedConst(pass, expr) || + isTypedConst(pass, expr) || + isIdentNamedAsExpected(checker.expVarPattern, expr) || + isStructFieldNamedAsExpected(checker.expVarPattern, expr) +} + +func isCastedBasicLitOrExpectedValue(ce *ast.CallExpr, pattern *regexp.Regexp) bool { + if len(ce.Args) != 1 { + return false + } + + fn, ok := ce.Fun.(*ast.Ident) + if !ok { + return false + } + + switch fn.Name { + case "complex64", "complex128": + return true + + case "uint", "uint8", "uint16", "uint32", "uint64", + "int", "int8", "int16", "int32", "int64", + "float32", "float64", + "rune", "string": + return isBasicLit(ce.Args[0]) || isIdentNamedAsExpected(pattern, ce.Args[0]) + } + return false +} + +func isExpectedValueFactory(ce *ast.CallExpr, pattern *regexp.Regexp) bool { + if len(ce.Args) != 0 { + return false + } + + switch fn := ce.Fun.(type) { + case *ast.Ident: + return pattern.MatchString(fn.Name) + case *ast.SelectorExpr: + return pattern.MatchString(fn.Sel.Name) + } + return false +} + +func isBasicLit(e ast.Expr) bool { + _, ok := e.(*ast.BasicLit) + return ok +} + +func isUntypedConst(p *analysis.Pass, e ast.Expr) bool { + t := p.TypesInfo.TypeOf(e) + if t == nil { + return false + } + + b, ok := t.(*types.Basic) + return ok && b.Info()&types.IsUntyped > 0 +} + +func isTypedConst(p *analysis.Pass, e ast.Expr) bool { + tt, ok := p.TypesInfo.Types[e] + return ok && tt.IsValue() && tt.Value != nil +} + +func isIdentNamedAsExpected(pattern *regexp.Regexp, e ast.Expr) bool { + id, ok := e.(*ast.Ident) + return ok && pattern.MatchString(id.Name) +} + +func isStructFieldNamedAsExpected(pattern *regexp.Regexp, e ast.Expr) bool { + s, ok := e.(*ast.SelectorExpr) + return ok && isIdentNamedAsExpected(pattern, s.Sel) +} diff --git a/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/float_compare.go b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/float_compare.go new file mode 100644 index 0000000000..7d5b358b35 --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/float_compare.go @@ -0,0 +1,63 @@ +package checkers + +import ( + "go/ast" + "go/token" + "go/types" + + "golang.org/x/tools/go/analysis" +) + +// FloatCompare detects situation like +// +// assert.Equal(t, 42.42, a) +// assert.True(t, a == 42.42) +// assert.False(t, a != 42.42) +// +// and requires +// +// assert.InEpsilon(t, 42.42, a, 0.0001) // Or assert.InDelta +type FloatCompare struct{} + +// NewFloatCompare constructs FloatCompare checker. +func NewFloatCompare() FloatCompare { return FloatCompare{} } +func (FloatCompare) Name() string { return "float-compare" } + +func (checker FloatCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { + invalid := func() bool { + switch call.Fn.Name { + case "Equal", "Equalf": + return len(call.Args) > 1 && isFloat(pass, call.Args[0]) && isFloat(pass, call.Args[1]) + + case "True", "Truef": + return len(call.Args) > 0 && isFloatCompare(pass, call.Args[0], token.EQL) + + case "False", "Falsef": + return len(call.Args) > 0 && isFloatCompare(pass, call.Args[0], token.NEQ) + } + return false + }() + + if invalid { + return newUseFunctionDiagnostic(checker.Name(), call, "InEpsilon (or InDelta)", nil) + } + return nil +} + +func isFloat(pass *analysis.Pass, expr ast.Expr) bool { + t := pass.TypesInfo.TypeOf(expr) + if t == nil { + return false + } + + bt, ok := t.Underlying().(*types.Basic) + return ok && (bt.Info()&types.IsFloat > 0) +} + +func isFloatCompare(p *analysis.Pass, e ast.Expr, op token.Token) bool { + be, ok := e.(*ast.BinaryExpr) + if !ok { + return false + } + return be.Op == op && (isFloat(p, be.X) || isFloat(p, be.Y)) +} diff --git a/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/len.go b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/len.go new file mode 100644 index 0000000000..f10412f636 --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/len.go @@ -0,0 +1,86 @@ +package checkers + +import ( + "go/ast" + "go/token" + + "golang.org/x/tools/go/analysis" +) + +// Len detects situations like +// +// assert.Equal(t, 3, len(arr)) +// assert.True(t, len(arr) == 3) +// +// and requires +// +// assert.Len(t, arr, 3) +type Len struct{} + +// NewLen constructs Len checker. +func NewLen() Len { return Len{} } +func (Len) Name() string { return "len" } + +func (checker Len) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { + const proposedFn = "Len" + + switch call.Fn.Name { + case "Equal", "Equalf": + if len(call.Args) < 2 { + return nil + } + a, b := call.Args[0], call.Args[1] + + if lenArg, expectedLen, ok := xorLenCall(pass, a, b); ok { + return newUseFunctionDiagnostic(checker.Name(), call, proposedFn, + newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{ + Pos: a.Pos(), + End: b.End(), + NewText: formatAsCallArgs(pass, lenArg, expectedLen), + }), + ) + } + + case "True", "Truef": + if len(call.Args) < 1 { + return nil + } + expr := call.Args[0] + + if lenArg, expectedLen, ok := isLenEquality(pass, expr); ok { + return newUseFunctionDiagnostic(checker.Name(), call, proposedFn, + newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{ + Pos: expr.Pos(), + End: expr.End(), + NewText: formatAsCallArgs(pass, lenArg, expectedLen), + }), + ) + } + } + return nil +} + +func xorLenCall(pass *analysis.Pass, a, b ast.Expr) (lenArg ast.Expr, expectedLen ast.Expr, ok bool) { + arg1, ok1 := isBuiltinLenCall(pass, a) + arg2, ok2 := isBuiltinLenCall(pass, b) + + if xor(ok1, ok2) { + if ok1 { + return arg1, b, true + } + return arg2, a, true + } + return nil, nil, false +} + +func isLenEquality(pass *analysis.Pass, e ast.Expr) (ast.Expr, ast.Expr, bool) { + be, ok := e.(*ast.BinaryExpr) + if !ok { + return nil, nil, false + } + + if be.Op != token.EQL { + return nil, nil, false + } + return xorLenCall(pass, be.X, be.Y) +} diff --git a/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/require_error.go b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/require_error.go new file mode 100644 index 0000000000..2da71ed817 --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/require_error.go @@ -0,0 +1,35 @@ +package checkers + +import "golang.org/x/tools/go/analysis" + +// RequireError detects situations like +// +// assert.NoError(t, err) +// s.ErrorIs(err, io.EOF) +// s.Assert().Error(err) +// +// and requires +// +// require.NoError(t, err) +// s.Require().ErrorIs(err, io.EOF) +// s.Require().Error(err) +type RequireError struct{} + +// NewRequireError constructs RequireError checker. +func NewRequireError() RequireError { return RequireError{} } +func (RequireError) Name() string { return "require-error" } + +func (checker RequireError) Check(_ *analysis.Pass, call *CallMeta) *analysis.Diagnostic { + if !call.IsAssert { + return nil + } + + const msg = "for error assertions use require" + + switch call.Fn.Name { + case "Error", "ErrorIs", "ErrorAs", "EqualError", "ErrorContains", "NoError", "NotErrorIs", + "Errorf", "ErrorIsf", "ErrorAsf", "EqualErrorf", "ErrorContainsf", "NoErrorf", "NotErrorIsf": + return newDiagnostic(checker.Name(), call, msg, nil) + } + return nil +} diff --git a/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/suite_dont_use_pkg.go b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/suite_dont_use_pkg.go new file mode 100644 index 0000000000..bf84f6378e --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/suite_dont_use_pkg.go @@ -0,0 +1,96 @@ +package checkers + +import ( + "fmt" + "go/ast" + "go/types" + + "golang.org/x/tools/go/analysis" + + "github.com/Antonboom/testifylint/internal/analysisutil" + "github.com/Antonboom/testifylint/internal/testify" +) + +// SuiteDontUsePkg detects situation like +// +// func (s *MySuite) TestSomething() { +// assert.Equal(s.T(), 42, value) +// } +// +// and requires +// +// func (s *MySuite) TestSomething() { +// s.Equal(42, value) +// } +type SuiteDontUsePkg struct{} + +// NewSuiteDontUsePkg constructs SuiteDontUsePkg checker. +func NewSuiteDontUsePkg() SuiteDontUsePkg { return SuiteDontUsePkg{} } +func (SuiteDontUsePkg) Name() string { return "suite-dont-use-pkg" } + +func (checker SuiteDontUsePkg) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { + if !call.IsPkg { + return nil + } + + args := call.ArgsRaw + if len(args) < 2 { + return nil + } + t := args[0] + + ce, ok := t.(*ast.CallExpr) + if !ok { + return nil + } + se, ok := ce.Fun.(*ast.SelectorExpr) + if !ok { + return nil + } + if se.X == nil || !implementsTestifySuiteIface(pass, se.X) { + return nil + } + if se.Sel == nil || se.Sel.Name != "T" { + return nil + } + rcv, ok := se.X.(*ast.Ident) // At this point we ensure that `s.T()` is used as the first argument of assertion. + if !ok { + return nil + } + + newSelector := rcv.Name + if !call.IsAssert { + newSelector += "." + "Require()" + } + + msg := fmt.Sprintf("use %s.%s", newSelector, call.Fn.Name) + return newDiagnostic(checker.Name(), call, msg, &analysis.SuggestedFix{ + Message: fmt.Sprintf("Replace `%s` with `%s`", call.SelectorXStr, newSelector), + TextEdits: []analysis.TextEdit{ + // Replace package function with suite method. + { + Pos: call.Selector.X.Pos(), + End: call.Selector.X.End(), + NewText: []byte(newSelector), + }, + // Remove `s.T()`. + { + Pos: t.Pos(), + End: args[1].Pos(), + NewText: []byte(""), + }, + }, + }) +} + +func implementsTestifySuiteIface(pass *analysis.Pass, rcv ast.Expr) bool { + suiteIface := analysisutil.ObjectOf(pass.Pkg, testify.SuitePkgPath, "TestingSuite") + if suiteIface == nil { + return false + } + + return types.Implements( + pass.TypesInfo.TypeOf(rcv), + suiteIface.Type().Underlying().(*types.Interface), + ) +} diff --git a/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/suite_extra_assert_call.go b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/suite_extra_assert_call.go new file mode 100644 index 0000000000..791488b651 --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/suite_extra_assert_call.go @@ -0,0 +1,99 @@ +package checkers + +import ( + "fmt" + "go/ast" + + "golang.org/x/tools/go/analysis" + + "github.com/Antonboom/testifylint/internal/analysisutil" +) + +// SuiteExtraAssertCallMode reflects different modes of work of SuiteExtraAssertCall checker. +type SuiteExtraAssertCallMode int + +const ( + SuiteExtraAssertCallModeRemove SuiteExtraAssertCallMode = iota + SuiteExtraAssertCallModeRequire +) + +const DefaultSuiteExtraAssertCallMode = SuiteExtraAssertCallModeRemove + +// SuiteExtraAssertCall detects situation like +// +// func (s *MySuite) TestSomething() { +// s.Assert().Equal(42, value) +// } +// +// and requires +// +// func (s *MySuite) TestSomething() { +// s.Equal(42, value) +// } +// +// or vice versa (depending on the configurable mode). +type SuiteExtraAssertCall struct { + mode SuiteExtraAssertCallMode +} + +// NewSuiteExtraAssertCall constructs SuiteExtraAssertCall checker. +func NewSuiteExtraAssertCall() *SuiteExtraAssertCall { + return &SuiteExtraAssertCall{mode: DefaultSuiteExtraAssertCallMode} +} + +func (SuiteExtraAssertCall) Name() string { return "suite-extra-assert-call" } + +func (checker *SuiteExtraAssertCall) SetMode(m SuiteExtraAssertCallMode) *SuiteExtraAssertCall { + checker.mode = m + return checker +} + +func (checker SuiteExtraAssertCall) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { + if call.IsPkg { + return nil + } + + switch checker.mode { + case SuiteExtraAssertCallModeRequire: + x, ok := call.Selector.X.(*ast.Ident) // s.True + if !ok || x == nil || !implementsTestifySuiteIface(pass, x) { + return nil + } + + msg := fmt.Sprintf("use an explicit %s.Assert().%s", analysisutil.NodeString(pass.Fset, x), call.Fn.Name) + return newDiagnostic(checker.Name(), call, msg, &analysis.SuggestedFix{ + Message: "Add `Assert()` call", + TextEdits: []analysis.TextEdit{{ + Pos: x.End(), + End: x.End(), // Pure insertion. + NewText: []byte(".Assert()"), + }}, + }) + + case SuiteExtraAssertCallModeRemove: + x, ok := call.Selector.X.(*ast.CallExpr) // s.Assert().True + if !ok { + return nil + } + + se, ok := x.Fun.(*ast.SelectorExpr) + if !ok || se == nil || !implementsTestifySuiteIface(pass, se.X) { + return nil + } + if se.Sel == nil || se.Sel.Name != "Assert" { + return nil + } + + msg := fmt.Sprintf("need to simplify the assertion to %s.%s", analysisutil.NodeString(pass.Fset, se.X), call.Fn.Name) + return newDiagnostic(checker.Name(), call, msg, &analysis.SuggestedFix{ + Message: "Remove `Assert()` call", + TextEdits: []analysis.TextEdit{{ + Pos: se.Sel.Pos(), + End: x.End() + 1, // +1 for dot. + NewText: []byte(""), + }}, + }) + } + + return nil +} diff --git a/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/suite_thelper.go b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/suite_thelper.go new file mode 100644 index 0000000000..5cadc93ada --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/internal/checkers/suite_thelper.go @@ -0,0 +1,130 @@ +package checkers + +import ( + "fmt" + "go/ast" + "strings" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/ast/inspector" + + "github.com/Antonboom/testifylint/internal/analysisutil" + "github.com/Antonboom/testifylint/internal/testify" +) + +// SuiteTHelper requires t.Helper() call in suite helpers: +// +// func (s *RoomSuite) assertRoomRound(roundID RoundID) { +// s.T().Helper() +// s.Equal(roundID, s.getRoom().CurrentRound.ID) +// } +type SuiteTHelper struct{} + +// NewSuiteTHelper constructs SuiteTHelper checker. +func NewSuiteTHelper() SuiteTHelper { return SuiteTHelper{} } +func (SuiteTHelper) Name() string { return "suite-thelper" } + +func (checker SuiteTHelper) Check(pass *analysis.Pass, inspector *inspector.Inspector) (diagnostics []analysis.Diagnostic) { + inspector.Preorder([]ast.Node{(*ast.FuncDecl)(nil)}, func(node ast.Node) { + fd := node.(*ast.FuncDecl) + if !isTestifySuiteMethod(pass, fd) { + return + } + + if ident := fd.Name; ident == nil || isTestMethod(ident.Name) || isServiceMethod(ident.Name) { + return + } + + if !containsSuiteAssertions(pass, fd) { + return + } + + rcv := fd.Recv.List[0] + if len(rcv.Names) != 1 || rcv.Names[0] == nil { + return + } + rcvName := rcv.Names[0].Name + + helperCallStr := fmt.Sprintf("%s.T().Helper()", rcvName) + + firstStmt := fd.Body.List[0] + if analysisutil.NodeString(pass.Fset, firstStmt) == helperCallStr { + return + } + + msg := fmt.Sprintf("suite helper method must start with " + helperCallStr) + d := newDiagnostic(checker.Name(), fd, msg, &analysis.SuggestedFix{ + Message: fmt.Sprintf("Insert `%s`", helperCallStr), + TextEdits: []analysis.TextEdit{ + { + Pos: firstStmt.Pos(), + End: firstStmt.Pos(), // Pure insertion. + NewText: []byte(helperCallStr + "\n\n"), + }, + }, + }) + diagnostics = append(diagnostics, *d) + }) + return diagnostics +} + +func isTestifySuiteMethod(pass *analysis.Pass, fDecl *ast.FuncDecl) bool { + if fDecl.Recv == nil || len(fDecl.Recv.List) != 1 { + return false + } + + rcv := fDecl.Recv.List[0] + return implementsTestifySuiteIface(pass, rcv.Type) +} + +func isTestMethod(name string) bool { + return strings.HasPrefix(name, "Test") +} + +func isServiceMethod(name string) bool { + // https://github.com/stretchr/testify/blob/master/suite/interfaces.go + switch name { + case "T", "SetT", "SetS", "SetupSuite", "SetupTest", "TearDownSuite", "TearDownTest", + "BeforeTest", "AfterTest", "HandleStats", "SetupSubTest", "TearDownSubTest": + return true + } + return false +} + +func containsSuiteAssertions(pass *analysis.Pass, fn *ast.FuncDecl) bool { + if fn.Body == nil { + return false + } + + for _, s := range fn.Body.List { + if isSuiteAssertion(pass, s) { + return true + } + } + return false +} + +func isSuiteAssertion(pass *analysis.Pass, stmt ast.Stmt) bool { + expr, ok := stmt.(*ast.ExprStmt) + if !ok { + return false + } + + ce, ok := expr.X.(*ast.CallExpr) + if !ok { + return false + } + + se, ok := ce.Fun.(*ast.SelectorExpr) + if !ok || se.Sel == nil { + return false + } + + if sel, ok := pass.TypesInfo.Selections[se]; ok { + pkg := sel.Obj().Pkg() + isAssert := analysisutil.IsPkg(pkg, testify.AssertPkgName, testify.AssertPkgPath) + isRequire := analysisutil.IsPkg(pkg, testify.RequirePkgName, testify.RequirePkgPath) + return isAssert || isRequire + } + return false +} diff --git a/tools/vendor/github.com/Antonboom/testifylint/internal/config/config.go b/tools/vendor/github.com/Antonboom/testifylint/internal/config/config.go new file mode 100644 index 0000000000..51f6270088 --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/internal/config/config.go @@ -0,0 +1,53 @@ +package config + +import ( + "flag" + + "github.com/Antonboom/testifylint/internal/checkers" +) + +// NewDefault builds default testifylint config. +func NewDefault() Config { + return Config{ + EnableAll: false, + EnabledCheckers: checkers.EnabledByDefault(), + ExpectedActual: ExpectedActualConfig{ + ExpVarPattern: RegexpValue{checkers.DefaultExpectedVarPattern}, + }, + SuiteExtraAssertCall: SuiteExtraAssertCallConfig{ + Mode: checkers.DefaultSuiteExtraAssertCallMode, + }, + } +} + +// Config implements testifylint configuration. +type Config struct { + EnableAll bool + EnabledCheckers KnownCheckersValue + ExpectedActual ExpectedActualConfig + SuiteExtraAssertCall SuiteExtraAssertCallConfig +} + +// ExpectedActualConfig implements configuration of checkers.ExpectedActual. +type ExpectedActualConfig struct { + ExpVarPattern RegexpValue +} + +// SuiteExtraAssertCallConfig implements configuration of checkers.SuiteExtraAssertCall. +type SuiteExtraAssertCallConfig struct { + Mode checkers.SuiteExtraAssertCallMode +} + +// BindToFlags binds Config fields to according flags. +func BindToFlags(cfg *Config, fs *flag.FlagSet) { + fs.BoolVar(&cfg.EnableAll, "enable-all", false, "enable all checkers") + fs.Var(&cfg.EnabledCheckers, "enable", "comma separated list of enabled checkers") + fs.Var(&cfg.ExpectedActual.ExpVarPattern, "expected-actual.pattern", "regexp for expected variable name") + fs.Var(NewEnumValue(suiteExtraAssertCallModeAsString, &cfg.SuiteExtraAssertCall.Mode), + "suite-extra-assert-call.mode", "to require or remove extra Assert() call") +} + +var suiteExtraAssertCallModeAsString = map[string]checkers.SuiteExtraAssertCallMode{ + "remove": checkers.SuiteExtraAssertCallModeRemove, + "require": checkers.SuiteExtraAssertCallModeRequire, +} diff --git a/tools/vendor/github.com/Antonboom/testifylint/internal/config/flag_value_types.go b/tools/vendor/github.com/Antonboom/testifylint/internal/config/flag_value_types.go new file mode 100644 index 0000000000..2f0ee978f3 --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/internal/config/flag_value_types.go @@ -0,0 +1,105 @@ +package config + +import ( + "flag" + "fmt" + "regexp" + "sort" + "strings" + + "github.com/Antonboom/testifylint/internal/checkers" +) + +var ( + _ flag.Value = (*KnownCheckersValue)(nil) + _ flag.Value = (*RegexpValue)(nil) + _ flag.Value = (*EnumValue[checkers.SuiteExtraAssertCallMode])(nil) +) + +// KnownCheckersValue implements comma separated list of testify checkers. +type KnownCheckersValue []string + +func (kcv KnownCheckersValue) String() string { + return strings.Join(kcv, ",") +} + +func (kcv *KnownCheckersValue) Set(v string) error { + chckrs := strings.Split(v, ",") + for _, checkerName := range chckrs { + if ok := checkers.IsKnown(checkerName); !ok { + return fmt.Errorf("unknown checker %q", checkerName) + } + } + + *kcv = chckrs + return nil +} + +// RegexpValue is a special wrapper for support of flag.FlagSet over regexp.Regexp. +// Original regexp is available through RegexpValue.Regexp. +type RegexpValue struct { + *regexp.Regexp +} + +func (rv RegexpValue) String() string { + if rv.Regexp == nil { + return "" + } + return rv.Regexp.String() +} + +func (rv *RegexpValue) Set(v string) error { + compiled, err := regexp.Compile(v) + if err != nil { + return err + } + + rv.Regexp = compiled + return nil +} + +// EnumValue is a special type for support of flag.FlagSet over user-defined constants. +type EnumValue[EnumT comparable] struct { + mapping map[string]EnumT + keys []string + dst *EnumT +} + +// NewEnumValue takes the "enum-value-name to enum-value" mapping and a destination for the value passed through the CLI. +// Returns an EnumValue instance suitable for flag.FlagSet.Var. +func NewEnumValue[EnumT comparable](mapping map[string]EnumT, dst *EnumT) *EnumValue[EnumT] { + keys := make([]string, 0, len(mapping)) + for k := range mapping { + keys = append(keys, k) + } + sort.Strings(keys) + + return &EnumValue[EnumT]{ + mapping: mapping, + keys: keys, + dst: dst, + } +} + +func (e EnumValue[EnumT]) String() string { + if e.dst == nil { + return "" + } + + for k, v := range e.mapping { + if v == *e.dst { + return k + } + } + return "" +} + +func (e *EnumValue[EnumT]) Set(s string) error { + v, ok := e.mapping[s] + if !ok { + return fmt.Errorf("use one of (%v)", strings.Join(e.keys, " | ")) + } + + *e.dst = v + return nil +} diff --git a/tools/vendor/github.com/Antonboom/testifylint/internal/testify/const.go b/tools/vendor/github.com/Antonboom/testifylint/internal/testify/const.go new file mode 100644 index 0000000000..45731aa97a --- /dev/null +++ b/tools/vendor/github.com/Antonboom/testifylint/internal/testify/const.go @@ -0,0 +1,13 @@ +package testify + +const ( + ModulePath = "github.com/stretchr/testify" + + AssertPkgName = "assert" + RequirePkgName = "require" + SuitePkgName = "suite" + + AssertPkgPath = ModulePath + "/" + AssertPkgName + RequirePkgPath = ModulePath + "/" + RequirePkgName + SuitePkgPath = ModulePath + "/" + SuitePkgName +) diff --git a/tools/vendor/github.com/GaijinEntertainment/go-exhaustruct/v2/pkg/analyzer/analyzer.go b/tools/vendor/github.com/GaijinEntertainment/go-exhaustruct/v2/pkg/analyzer/analyzer.go deleted file mode 100644 index 279f2189df..0000000000 --- a/tools/vendor/github.com/GaijinEntertainment/go-exhaustruct/v2/pkg/analyzer/analyzer.go +++ /dev/null @@ -1,289 +0,0 @@ -package analyzer - -import ( - "errors" - "flag" - "go/ast" - "go/types" - "strings" - "sync" - - "golang.org/x/tools/go/analysis" - "golang.org/x/tools/go/analysis/passes/inspect" - "golang.org/x/tools/go/ast/inspector" -) - -var ( - ErrEmptyPattern = errors.New("pattern can't be empty") -) - -type analyzer struct { - include PatternsList - exclude PatternsList - - typesProcessCache map[types.Type]bool - typesProcessCacheMu sync.RWMutex - - structFieldsCache map[types.Type]*StructFields - structFieldsCacheMu sync.RWMutex -} - -// NewAnalyzer returns a go/analysis-compatible analyzer. -// -i arguments adds include patterns -// -e arguments adds exclude patterns -func NewAnalyzer(include []string, exclude []string) (*analysis.Analyzer, error) { - a := analyzer{ //nolint:exhaustruct - typesProcessCache: map[types.Type]bool{}, - - structFieldsCache: map[types.Type]*StructFields{}, - } - - var err error - - a.include, err = newPatternsList(include) - if err != nil { - return nil, err - } - - a.exclude, err = newPatternsList(exclude) - if err != nil { - return nil, err - } - - return &analysis.Analyzer{ //nolint:exhaustruct - Name: "exhaustruct", - Doc: "Checks if all structure fields are initialized", - Run: a.run, - Requires: []*analysis.Analyzer{inspect.Analyzer}, - Flags: a.newFlagSet(), - }, nil -} - -func (a *analyzer) newFlagSet() flag.FlagSet { - fs := flag.NewFlagSet("exhaustruct flags", flag.PanicOnError) - - fs.Var( - &reListVar{values: &a.include}, - "i", - "Regular expression to match struct packages and names, can receive multiple flags", - ) - fs.Var( - &reListVar{values: &a.exclude}, - "e", - "Regular expression to exclude struct packages and names, can receive multiple flags", - ) - - return *fs -} - -func (a *analyzer) run(pass *analysis.Pass) (interface{}, error) { - insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) //nolint:forcetypeassert - - nodeTypes := []ast.Node{ - (*ast.CompositeLit)(nil), - (*ast.ReturnStmt)(nil), - } - - insp.Preorder(nodeTypes, a.newVisitor(pass)) - - return nil, nil //nolint:nilnil -} - -//nolint:cyclop -func (a *analyzer) newVisitor(pass *analysis.Pass) func(node ast.Node) { - var ret *ast.ReturnStmt - - return func(node ast.Node) { - if retLit, ok := node.(*ast.ReturnStmt); ok { - // save return statement for future (to detect error-containing returns) - ret = retLit - - return - } - - lit, _ := node.(*ast.CompositeLit) - if lit.Type == nil { - // we're not interested in non-typed literals - return - } - - typ := pass.TypesInfo.TypeOf(lit.Type) - if typ == nil { - return - } - - strct, ok := typ.Underlying().(*types.Struct) - if !ok { - // we also not interested in non-structure literals - return - } - - strctName := exprName(lit.Type) - if strctName == "" { - return - } - - if !a.shouldProcessType(typ) { - return - } - - if len(lit.Elts) == 0 && ret != nil { - if ret.End() < lit.Pos() { - // we're outside last return statement - ret = nil - } else if returnContainsLiteral(ret, lit) && returnContainsError(ret, pass) { - // we're okay with empty literals in return statements with non-nil errors, like - // `return my.Struct{}, fmt.Errorf("non-nil error!")` - return - } - } - - missingFields := a.structMissingFields(lit, strct, strings.HasPrefix(typ.String(), pass.Pkg.Path()+".")) - - if len(missingFields) == 1 { - pass.Reportf(node.Pos(), "%s is missing in %s", missingFields[0], strctName) - } else if len(missingFields) > 1 { - pass.Reportf(node.Pos(), "%s are missing in %s", strings.Join(missingFields, ", "), strctName) - } - } -} - -func (a *analyzer) shouldProcessType(typ types.Type) bool { - if len(a.include) == 0 && len(a.exclude) == 0 { - // skip whole part with cache, since we have no restrictions and have to check everything - return true - } - - a.typesProcessCacheMu.RLock() - v, ok := a.typesProcessCache[typ] - a.typesProcessCacheMu.RUnlock() - - if !ok { - a.typesProcessCacheMu.Lock() - defer a.typesProcessCacheMu.Unlock() - - v = true - typStr := typ.String() - - if len(a.include) > 0 && !a.include.MatchesAny(typStr) { - v = false - } - - if v && a.exclude.MatchesAny(typStr) { - v = false - } - - a.typesProcessCache[typ] = v - } - - return v -} - -func (a *analyzer) structMissingFields(lit *ast.CompositeLit, strct *types.Struct, private bool) []string { - keys, unnamed := literalKeys(lit) - fields := a.structFields(strct) - - if unnamed { - if private { - return fields.All[len(keys):] - } - - return fields.Public[len(keys):] - } - - if private { - return difference(fields.AllRequired, keys) - } - - return difference(fields.PublicRequired, keys) -} - -func (a *analyzer) structFields(strct *types.Struct) *StructFields { - typ := strct.Underlying() - - a.structFieldsCacheMu.RLock() - fields, ok := a.structFieldsCache[typ] - a.structFieldsCacheMu.RUnlock() - - if !ok { - a.structFieldsCacheMu.Lock() - defer a.structFieldsCacheMu.Unlock() - - fields = NewStructFields(strct) - a.structFieldsCache[typ] = fields - } - - return fields -} - -func returnContainsLiteral(ret *ast.ReturnStmt, lit *ast.CompositeLit) bool { - for _, result := range ret.Results { - if l, ok := result.(*ast.CompositeLit); ok { - if lit == l { - return true - } - } - } - - return false -} - -func returnContainsError(ret *ast.ReturnStmt, pass *analysis.Pass) bool { - for _, result := range ret.Results { - if pass.TypesInfo.TypeOf(result).String() == "error" { - return true - } - } - - return false -} - -func literalKeys(lit *ast.CompositeLit) (keys []string, unnamed bool) { - for _, elt := range lit.Elts { - if k, ok := elt.(*ast.KeyValueExpr); ok { - if ident, ok := k.Key.(*ast.Ident); ok { - keys = append(keys, ident.Name) - } - - continue - } - - // in case we deal with unnamed initialization - no need to iterate over all - // elements - simply create slice with proper size - unnamed = true - keys = make([]string, len(lit.Elts)) - - return - } - - return -} - -// difference returns elements that are in `a` and not in `b`. -func difference(a, b []string) (diff []string) { - mb := make(map[string]struct{}, len(b)) - for _, x := range b { - mb[x] = struct{}{} - } - - for _, x := range a { - if _, found := mb[x]; !found { - diff = append(diff, x) - } - } - - return diff -} - -func exprName(expr ast.Expr) string { - if i, ok := expr.(*ast.Ident); ok { - return i.Name - } - - s, ok := expr.(*ast.SelectorExpr) - if !ok { - return "" - } - - return s.Sel.Name -} diff --git a/tools/vendor/github.com/GaijinEntertainment/go-exhaustruct/v2/pkg/analyzer/patterns-list.go b/tools/vendor/github.com/GaijinEntertainment/go-exhaustruct/v2/pkg/analyzer/patterns-list.go deleted file mode 100644 index 2884cab624..0000000000 --- a/tools/vendor/github.com/GaijinEntertainment/go-exhaustruct/v2/pkg/analyzer/patterns-list.go +++ /dev/null @@ -1,68 +0,0 @@ -package analyzer - -import ( - "fmt" - "regexp" -) - -type PatternsList []*regexp.Regexp - -// MatchesAny matches provided string against all regexps in a slice. -func (l PatternsList) MatchesAny(str string) bool { - for _, r := range l { - if r.MatchString(str) { - return true - } - } - - return false -} - -// newPatternsList parses slice of strings to a slice of compiled regular -// expressions. -func newPatternsList(in []string) (PatternsList, error) { - list := PatternsList{} - - for _, str := range in { - re, err := strToRegexp(str) - if err != nil { - return nil, err - } - - list = append(list, re) - } - - return list, nil -} - -type reListVar struct { - values *PatternsList -} - -func (v *reListVar) Set(value string) error { - re, err := strToRegexp(value) - if err != nil { - return err - } - - *v.values = append(*v.values, re) - - return nil -} - -func (v *reListVar) String() string { - return "" -} - -func strToRegexp(str string) (*regexp.Regexp, error) { - if str == "" { - return nil, ErrEmptyPattern - } - - re, err := regexp.Compile(str) - if err != nil { - return nil, fmt.Errorf("unable to compile %s as regular expression: %w", str, err) - } - - return re, nil -} diff --git a/tools/vendor/github.com/GaijinEntertainment/go-exhaustruct/v2/pkg/analyzer/struct-fields.go b/tools/vendor/github.com/GaijinEntertainment/go-exhaustruct/v2/pkg/analyzer/struct-fields.go deleted file mode 100644 index 56eda05f76..0000000000 --- a/tools/vendor/github.com/GaijinEntertainment/go-exhaustruct/v2/pkg/analyzer/struct-fields.go +++ /dev/null @@ -1,61 +0,0 @@ -package analyzer - -import ( - "go/types" - "reflect" - - "golang.org/x/exp/slices" -) - -type StructFields struct { - All []string - AllRequired []string - - Public []string - PublicRequired []string -} - -func NewStructFields(strct *types.Struct) *StructFields { - sf := StructFields{ - All: make([]string, strct.NumFields()), - AllRequired: make([]string, 0, strct.NumFields()), - Public: make([]string, 0, strct.NumFields()), - PublicRequired: make([]string, 0, strct.NumFields()), - } - - for i := 0; i < strct.NumFields(); i++ { - f := strct.Field(i) - isOptional := isFieldOptional(strct.Tag(i)) - - sf.All[i] = f.Name() - if !isOptional { - sf.AllRequired = append(sf.AllRequired, f.Name()) - } - - if f.Exported() { - sf.Public = append(sf.Public, f.Name()) - - if !isOptional { - sf.PublicRequired = append(sf.PublicRequired, f.Name()) - } - } - } - - sf.All = slices.Clip(sf.All) - sf.AllRequired = slices.Clip(sf.AllRequired) - sf.Public = slices.Clip(sf.Public) - sf.PublicRequired = slices.Clip(sf.PublicRequired) - - return &sf -} - -const ( - TagName = "exhaustruct" - OptionalTagValue = "optional" -) - -// isFieldOptional checks if field tags has an optional tag, and therefore can -// be omitted during structure initialization. -func isFieldOptional(tags string) bool { - return reflect.StructTag(tags).Get(TagName) == OptionalTagValue -} diff --git a/tools/vendor/github.com/GaijinEntertainment/go-exhaustruct/v2/LICENSE b/tools/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/LICENSE similarity index 100% rename from tools/vendor/github.com/GaijinEntertainment/go-exhaustruct/v2/LICENSE rename to tools/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/LICENSE diff --git a/tools/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/analyzer/analyzer.go b/tools/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/analyzer/analyzer.go new file mode 100644 index 0000000000..d0cd2d5bb4 --- /dev/null +++ b/tools/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/analyzer/analyzer.go @@ -0,0 +1,268 @@ +package analyzer + +import ( + "flag" + "fmt" + "go/ast" + "go/token" + "go/types" + "sync" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" + + "github.com/GaijinEntertainment/go-exhaustruct/v3/internal/fields" + "github.com/GaijinEntertainment/go-exhaustruct/v3/internal/pattern" +) + +type analyzer struct { + include pattern.List `exhaustruct:"optional"` + exclude pattern.List `exhaustruct:"optional"` + + fieldsCache map[types.Type]fields.StructFields + fieldsCacheMu sync.RWMutex `exhaustruct:"optional"` + + typeProcessingNeed map[string]bool + typeProcessingNeedMu sync.RWMutex `exhaustruct:"optional"` +} + +func NewAnalyzer(include, exclude []string) (*analysis.Analyzer, error) { + a := analyzer{ + fieldsCache: make(map[types.Type]fields.StructFields), + typeProcessingNeed: make(map[string]bool), + } + + var err error + + a.include, err = pattern.NewList(include...) + if err != nil { + return nil, err //nolint:wrapcheck + } + + a.exclude, err = pattern.NewList(exclude...) + if err != nil { + return nil, err //nolint:wrapcheck + } + + return &analysis.Analyzer{ //nolint:exhaustruct + Name: "exhaustruct", + Doc: "Checks if all structure fields are initialized", + Run: a.run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + Flags: a.newFlagSet(), + }, nil +} + +func (a *analyzer) newFlagSet() flag.FlagSet { + fs := flag.NewFlagSet("", flag.PanicOnError) + + fs.Var(&a.include, "i", `Regular expression to match type names, can receive multiple flags. +Anonymous structs can be matched by '' alias. +4ex: + github.com/GaijinEntertainment/go-exhaustruct/v3/analyzer\. + github.com/GaijinEntertainment/go-exhaustruct/v3/analyzer\.TypeInfo`) + fs.Var(&a.exclude, "e", `Regular expression to exclude type names, can receive multiple flags. +Anonymous structs can be matched by '' alias. +4ex: + github.com/GaijinEntertainment/go-exhaustruct/v3/analyzer\. + github.com/GaijinEntertainment/go-exhaustruct/v3/analyzer\.TypeInfo`) + + return *fs +} + +func (a *analyzer) run(pass *analysis.Pass) (any, error) { + insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) //nolint:forcetypeassert + + insp.WithStack( + []ast.Node{ + (*ast.CompositeLit)(nil), + }, + a.newVisitor(pass), + ) + + return nil, nil //nolint:nilnil +} + +// newVisitor returns visitor that only expects [ast.CompositeLit] nodes. +func (a *analyzer) newVisitor(pass *analysis.Pass) func(n ast.Node, push bool, stack []ast.Node) bool { + return func(n ast.Node, push bool, stack []ast.Node) bool { + if !push { + return true + } + + lit, ok := n.(*ast.CompositeLit) + if !ok { + // this should never happen, but better be prepared + return true + } + + structTyp, typeInfo, ok := getStructType(pass, lit) + if !ok { + return true + } + + if len(lit.Elts) == 0 { + if ret, ok := stackParentIsReturn(stack); ok { + if returnContainsNonNilError(pass, ret) { + // it is okay to return uninitialized structure in case struct's direct parent is + // a return statement containing non-nil error + // + // we're unable to check if returned error is custom, but at least we're able to + // cover str [error] type. + return true + } + } + } + + pos, msg := a.processStruct(pass, lit, structTyp, typeInfo) + if pos != nil { + pass.Reportf(*pos, msg) + } + + return true + } +} + +func getStructType(pass *analysis.Pass, lit *ast.CompositeLit) (*types.Struct, *TypeInfo, bool) { + switch typ := pass.TypesInfo.TypeOf(lit).(type) { + case *types.Named: // named type + if structTyp, ok := typ.Underlying().(*types.Struct); ok { + pkg := typ.Obj().Pkg() + ti := TypeInfo{ + Name: typ.Obj().Name(), + PackageName: pkg.Name(), + PackagePath: pkg.Path(), + } + + return structTyp, &ti, true + } + + return nil, nil, false + + case *types.Struct: // anonymous struct + ti := TypeInfo{ + Name: "", + PackageName: pass.Pkg.Name(), + PackagePath: pass.Pkg.Path(), + } + + return typ, &ti, true + + default: + return nil, nil, false + } +} + +func stackParentIsReturn(stack []ast.Node) (*ast.ReturnStmt, bool) { + // it is safe to skip boundary check, since stack always has at least one element + // - whole file. + ret, ok := stack[len(stack)-2].(*ast.ReturnStmt) + + return ret, ok +} + +func returnContainsNonNilError(pass *analysis.Pass, ret *ast.ReturnStmt) bool { + // errors are mostly located at the end of return statement, so we're starting + // from the end. + for i := len(ret.Results) - 1; i >= 0; i-- { + if pass.TypesInfo.TypeOf(ret.Results[i]).String() == "error" { + return true + } + } + + return false +} + +func (a *analyzer) processStruct( + pass *analysis.Pass, + lit *ast.CompositeLit, + structTyp *types.Struct, + info *TypeInfo, +) (*token.Pos, string) { + if !a.shouldProcessType(info) { + return nil, "" + } + + // unnamed structures are only defined in same package, along with types that has + // prefix identical to current package name. + isSamePackage := info.PackagePath == pass.Pkg.Path() + + if f := a.litSkippedFields(lit, structTyp, !isSamePackage); len(f) > 0 { + pos := lit.Pos() + + if len(f) == 1 { + return &pos, fmt.Sprintf("%s is missing field %s", info.ShortString(), f.String()) + } + + return &pos, fmt.Sprintf("%s is missing fields %s", info.ShortString(), f.String()) + } + + return nil, "" +} + +// shouldProcessType returns true if type should be processed basing off include +// and exclude patterns, defined though constructor and\or flags. +func (a *analyzer) shouldProcessType(info *TypeInfo) bool { + if len(a.include) == 0 && len(a.exclude) == 0 { + return true + } + + name := info.String() + + a.typeProcessingNeedMu.RLock() + res, ok := a.typeProcessingNeed[name] + a.typeProcessingNeedMu.RUnlock() + + if !ok { + a.typeProcessingNeedMu.Lock() + res = true + + if a.include != nil && !a.include.MatchFullString(name) { + res = false + } + + if res && a.exclude != nil && a.exclude.MatchFullString(name) { + res = false + } + + a.typeProcessingNeed[name] = res + a.typeProcessingNeedMu.Unlock() + } + + return res +} + +//revive:disable-next-line:unused-receiver +func (a *analyzer) litSkippedFields( + lit *ast.CompositeLit, + typ *types.Struct, + onlyExported bool, +) fields.StructFields { + a.fieldsCacheMu.RLock() + f, ok := a.fieldsCache[typ] + a.fieldsCacheMu.RUnlock() + + if !ok { + a.fieldsCacheMu.Lock() + f = fields.NewStructFields(typ) + a.fieldsCache[typ] = f + a.fieldsCacheMu.Unlock() + } + + return f.SkippedFields(lit, onlyExported) +} + +type TypeInfo struct { + Name string + PackageName string + PackagePath string +} + +func (t TypeInfo) String() string { + return t.PackagePath + "." + t.Name +} + +func (t TypeInfo) ShortString() string { + return t.PackageName + "." + t.Name +} diff --git a/tools/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/fields/struct.go b/tools/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/fields/struct.go new file mode 100644 index 0000000000..af2390e874 --- /dev/null +++ b/tools/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/fields/struct.go @@ -0,0 +1,124 @@ +package fields + +import ( + "go/ast" + "go/types" + "reflect" +) + +const ( + TagName = "exhaustruct" + OptionalTagValue = "optional" +) + +type StructField struct { + Name string + Exported bool + Optional bool +} + +type StructFields []*StructField + +// NewStructFields creates a new [StructFields] from a given struct type. +// StructFields items are listed in order they appear in the struct. +func NewStructFields(strct *types.Struct) StructFields { + sf := make(StructFields, 0, strct.NumFields()) + + for i := 0; i < strct.NumFields(); i++ { + f := strct.Field(i) + + sf = append(sf, &StructField{ + Name: f.Name(), + Exported: f.Exported(), + Optional: HasOptionalTag(strct.Tag(i)), + }) + } + + return sf +} + +func HasOptionalTag(tags string) bool { + return reflect.StructTag(tags).Get(TagName) == OptionalTagValue +} + +// String returns a comma-separated list of field names. +func (sf StructFields) String() (res string) { + for i := 0; i < len(sf); i++ { + if res != "" { + res += ", " + } + + res += sf[i].Name + } + + return res +} + +// SkippedFields returns a list of fields that are not present in the given +// literal, but expected to. +// +//revive:disable-next-line:cyclomatic +func (sf StructFields) SkippedFields(lit *ast.CompositeLit, onlyExported bool) StructFields { + if len(lit.Elts) != 0 && !isNamedLiteral(lit) { + if len(lit.Elts) == len(sf) { + return nil + } + + return sf[len(lit.Elts):] + } + + em := sf.existenceMap() + res := make(StructFields, 0, len(sf)) + + for i := 0; i < len(lit.Elts); i++ { + kv, ok := lit.Elts[i].(*ast.KeyValueExpr) + if !ok { + continue + } + + k, ok := kv.Key.(*ast.Ident) + if !ok { + continue + } + + em[k.Name] = true + } + + for i := 0; i < len(sf); i++ { + if em[sf[i].Name] || (!sf[i].Exported && onlyExported) || sf[i].Optional { + continue + } + + res = append(res, sf[i]) + } + + if len(res) == 0 { + return nil + } + + return res +} + +func (sf StructFields) existenceMap() map[string]bool { + m := make(map[string]bool, len(sf)) + + for i := 0; i < len(sf); i++ { + m[sf[i].Name] = false + } + + return m +} + +// isNamedLiteral returns true if the given literal is unnamed. +// +// The logic is basing on the principle that literal is named or unnamed, +// therefore is literal's first element is a [ast.KeyValueExpr], it is named. +// +// Method will panic if the given literal is empty. +func isNamedLiteral(lit *ast.CompositeLit) bool { + if _, ok := lit.Elts[0].(*ast.KeyValueExpr); !ok { + return false + } + + return true +} diff --git a/tools/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/pattern/list.go b/tools/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/pattern/list.go new file mode 100644 index 0000000000..a16e5058d2 --- /dev/null +++ b/tools/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/pattern/list.go @@ -0,0 +1,82 @@ +package pattern + +import ( + "fmt" + "regexp" + "strings" +) + +var ( + ErrEmptyPattern = fmt.Errorf("pattern can't be empty") + ErrCompilationFailed = fmt.Errorf("pattern compilation failed") +) + +// List is a list of regular expressions. +type List []*regexp.Regexp + +// NewList parses slice of strings to a slice of compiled regular expressions. +func NewList(strs ...string) (List, error) { + if len(strs) == 0 { + return nil, nil + } + + l := make(List, 0, len(strs)) + + for _, str := range strs { + re, err := strToRe(str) + if err != nil { + return nil, err + } + + l = append(l, re) + } + + return l, nil +} + +// MatchFullString matches provided string against all regexps in a slice and returns +// true if any of them matches whole string. +func (l List) MatchFullString(str string) bool { + for i := 0; i < len(l); i++ { + if m := l[i].FindStringSubmatch(str); len(m) > 0 && m[0] == str { + return true + } + } + + return false +} + +func (l *List) Set(value string) error { + re, err := strToRe(value) + if err != nil { + return err + } + + *l = append(*l, re) + + return nil +} + +func (l *List) String() string { + res := make([]string, 0, len(*l)) + + for _, re := range *l { + res = append(res, `"`+re.String()+`"`) + } + + return strings.Join(res, ", ") +} + +// strToRe parses string to a compiled regular expression that matches full string. +func strToRe(str string) (*regexp.Regexp, error) { + if str == "" { + return nil, ErrEmptyPattern + } + + re, err := regexp.Compile(str) + if err != nil { + return nil, fmt.Errorf("%w: %s: %w", ErrCompilationFailed, str, err) + } + + return re, nil +} diff --git a/tools/vendor/github.com/alecthomas/go-check-sumtype/.goreleaser.yml b/tools/vendor/github.com/alecthomas/go-check-sumtype/.goreleaser.yml new file mode 100644 index 0000000000..33bd03d060 --- /dev/null +++ b/tools/vendor/github.com/alecthomas/go-check-sumtype/.goreleaser.yml @@ -0,0 +1,32 @@ +project_name: go-check-sumtype +release: + github: + owner: alecthomas + name: go-check-sumtype +env: + - CGO_ENABLED=0 +builds: +- goos: + - linux + - darwin + - windows + goarch: + - arm64 + - amd64 + - "386" + goarm: + - "6" + main: ./cmd/go-check-sumtype + binary: go-check-sumtype +archives: + - + format: tar.gz + name_template: '{{ .Binary }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ + .Arm }}{{ end }}' + files: + - COPYING + - README* +snapshot: + name_template: SNAPSHOT-{{ .Commit }} +checksum: + name_template: '{{ .ProjectName }}-{{ .Version }}-checksums.txt' diff --git a/tools/vendor/github.com/alecthomas/go-check-sumtype/COPYING b/tools/vendor/github.com/alecthomas/go-check-sumtype/COPYING new file mode 100644 index 0000000000..bb9c20a094 --- /dev/null +++ b/tools/vendor/github.com/alecthomas/go-check-sumtype/COPYING @@ -0,0 +1,3 @@ +This project is dual-licensed under the Unlicense and MIT licenses. + +You may use this code under the terms of either license. diff --git a/tools/vendor/github.com/alecthomas/go-check-sumtype/LICENSE-MIT b/tools/vendor/github.com/alecthomas/go-check-sumtype/LICENSE-MIT new file mode 100644 index 0000000000..3b0a5dc09c --- /dev/null +++ b/tools/vendor/github.com/alecthomas/go-check-sumtype/LICENSE-MIT @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Andrew Gallant + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/tools/vendor/github.com/alecthomas/go-check-sumtype/README.md b/tools/vendor/github.com/alecthomas/go-check-sumtype/README.md new file mode 100644 index 0000000000..36614ef400 --- /dev/null +++ b/tools/vendor/github.com/alecthomas/go-check-sumtype/README.md @@ -0,0 +1,120 @@ +**Note: This is a fork of the great project [go-sumtype](https://github.com/BurntSushi/go-sumtype) by BurntSushi.** +**The original seems largely unmaintained, and the changes in this fork are backwards incompatible.** + +# go-check-sumtype [![CI](https://github.com/alecthomas/go-check-sumtype/actions/workflows/ci.yml/badge.svg)](https://github.com/alecthomas/go-check-sumtype/actions/workflows/ci.yml) +A simple utility for running exhaustiveness checks on type switch statements. +Exhaustiveness checks are only run on interfaces that are declared to be +"sum types." + +Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org). + +This work was inspired by our code at +[Diffeo](https://diffeo.com). + +## Installation + +```go +$ go get github.com/alecthomas/go-check-sumtype +``` + +For usage info, just run the command: + +``` +$ go-check-sumtype +``` + +Typical usage might look like this: + +``` +$ go-check-sumtype $(go list ./... | grep -v vendor) +``` + +## Usage + +`go-check-sumtype` takes a list of Go package paths or files and looks for sum type +declarations in each package/file provided. Exhaustiveness checks are then +performed for each use of a declared sum type in a type switch statement. +Namely, `go-check-sumtype` will report an error for any type switch statement that +either lacks a `default` clause or does not account for all possible variants. + +Declarations are provided in comments like so: + +``` +//sumtype:decl +type MySumType interface { ... } +``` + +`MySumType` must be *sealed*. That is, part of its interface definition +contains an unexported method. + +`go-check-sumtype` will produce an error if any of the above is not true. + +For valid declarations, `go-check-sumtype` will look for all occurrences in which a +value of type `MySumType` participates in a type switch statement. In those +occurrences, it will attempt to detect whether the type switch is exhaustive +or not. If it's not, `go-check-sumtype` will report an error. For example, running +`go-check-sumtype` on this source file: + +```go +package main + +//sumtype:decl +type MySumType interface { + sealed() +} + +type VariantA struct{} + +func (*VariantA) sealed() {} + +type VariantB struct{} + +func (*VariantB) sealed() {} + +func main() { + switch MySumType(nil).(type) { + case *VariantA: + } +} +``` + +produces the following: + +``` +$ sumtype mysumtype.go +mysumtype.go:18:2: exhaustiveness check failed for sum type 'MySumType': missing cases for VariantB +``` + +Adding either a `default` clause or a clause to handle `*VariantB` will cause +exhaustive checks to pass. + +As a special case, if the type switch statement contains a `default` clause +that always panics, then exhaustiveness checks are still performed. + +## Details and motivation + +Sum types are otherwise known as discriminated unions. That is, a sum type is +a finite set of disjoint values. In type systems that support sum types, the +language will guarantee that if one has a sum type `T`, then its value must +be one of its variants. + +Go's type system does not support sum types. A typical proxy for representing +sum types in Go is to use an interface with an unexported method and define +each variant of the sum type in the same package to satisfy said interface. +This guarantees that the set of types that satisfy the interface is closed +at compile time. Performing case analysis on these types is then done with +a type switch statement, e.g., `switch x.(type) { ... }`. Each clause of the +type switch corresponds to a *variant* of the sum type. The downside of this +approach is that Go's type system is not aware of the set of variants, so it +cannot tell you whether case analysis over a sum type is complete or not. + +The `go-check-sumtype` command recognizes this pattern, but it needs a small amount +of help to recognize which interfaces should be treated as sum types, which +is why the `//sumtype:decl` annotation is required. `go-check-sumtype` will +figure out all of the variants of a sum type by finding the set of types +defined in the same package that satisfy the interface specified by the +declaration. + +The `go-check-sumtype` command will prove its worth when you need to add a variant +to an existing sum type. Running `go-check-sumtype` will tell you immediately which +case analyses need to be updated to account for the new variant. diff --git a/tools/vendor/github.com/alecthomas/go-check-sumtype/UNLICENSE b/tools/vendor/github.com/alecthomas/go-check-sumtype/UNLICENSE new file mode 100644 index 0000000000..68a49daad8 --- /dev/null +++ b/tools/vendor/github.com/alecthomas/go-check-sumtype/UNLICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/tools/vendor/github.com/alecthomas/go-check-sumtype/check.go b/tools/vendor/github.com/alecthomas/go-check-sumtype/check.go new file mode 100644 index 0000000000..21d751af42 --- /dev/null +++ b/tools/vendor/github.com/alecthomas/go-check-sumtype/check.go @@ -0,0 +1,184 @@ +package gochecksumtype + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "sort" + "strings" + + "golang.org/x/tools/go/packages" +) + +// inexhaustiveError is returned from check for each occurrence of inexhaustive +// case analysis in a Go type switch statement. +type inexhaustiveError struct { + Position token.Position + Def sumTypeDef + Missing []types.Object +} + +func (e inexhaustiveError) Pos() token.Position { return e.Position } +func (e inexhaustiveError) Error() string { + return fmt.Sprintf( + "%s: exhaustiveness check failed for sum type %q (from %s): missing cases for %s", + e.Pos(), e.Def.Decl.TypeName, e.Def.Decl.Pos, strings.Join(e.Names(), ", ")) +} + +// Names returns a sorted list of names corresponding to the missing variant +// cases. +func (e inexhaustiveError) Names() []string { + var list []string + for _, o := range e.Missing { + list = append(list, o.Name()) + } + sort.Strings(list) + return list +} + +// check does exhaustiveness checking for the given sum type definitions in the +// given package. Every instance of inexhaustive case analysis is returned. +func check(pkg *packages.Package, defs []sumTypeDef) []error { + var errs []error + for _, astfile := range pkg.Syntax { + ast.Inspect(astfile, func(n ast.Node) bool { + swtch, ok := n.(*ast.TypeSwitchStmt) + if !ok { + return true + } + if err := checkSwitch(pkg, defs, swtch); err != nil { + errs = append(errs, err) + } + return true + }) + } + return errs +} + +// checkSwitch performs an exhaustiveness check on the given type switch +// statement. If the type switch is used on a sum type and does not cover +// all variants of that sum type, then an error is returned indicating which +// variants were missed. +// +// Note that if the type switch contains a non-panicing default case, then +// exhaustiveness checks are disabled. +func checkSwitch( + pkg *packages.Package, + defs []sumTypeDef, + swtch *ast.TypeSwitchStmt, +) error { + def, missing := missingVariantsInSwitch(pkg, defs, swtch) + if len(missing) > 0 { + return inexhaustiveError{ + Position: pkg.Fset.Position(swtch.Pos()), + Def: *def, + Missing: missing, + } + } + return nil +} + +// missingVariantsInSwitch returns a list of missing variants corresponding to +// the given switch statement. The corresponding sum type definition is also +// returned. (If no sum type definition could be found, then no exhaustiveness +// checks are performed, and therefore, no missing variants are returned.) +func missingVariantsInSwitch( + pkg *packages.Package, + defs []sumTypeDef, + swtch *ast.TypeSwitchStmt, +) (*sumTypeDef, []types.Object) { + asserted := findTypeAssertExpr(swtch) + ty := pkg.TypesInfo.TypeOf(asserted) + def := findDef(defs, ty) + if def == nil { + // We couldn't find a corresponding sum type, so there's + // nothing we can do to check it. + return nil, nil + } + variantExprs, hasDefault := switchVariants(swtch) + if hasDefault && !defaultClauseAlwaysPanics(swtch) { + // A catch-all case defeats all exhaustiveness checks. + return def, nil + } + var variantTypes []types.Type + for _, expr := range variantExprs { + variantTypes = append(variantTypes, pkg.TypesInfo.TypeOf(expr)) + } + return def, def.missing(variantTypes) +} + +// switchVariants returns all case expressions found in a type switch. This +// includes expressions from cases that have a list of expressions. +func switchVariants(swtch *ast.TypeSwitchStmt) (exprs []ast.Expr, hasDefault bool) { + for _, stmt := range swtch.Body.List { + clause := stmt.(*ast.CaseClause) + if clause.List == nil { + hasDefault = true + } else { + exprs = append(exprs, clause.List...) + } + } + return +} + +// defaultClauseAlwaysPanics returns true if the given switch statement has a +// default clause that always panics. Note that this is done on a best-effort +// basis. While there will never be any false positives, there may be false +// negatives. +// +// If the given switch statement has no default clause, then this function +// panics. +func defaultClauseAlwaysPanics(swtch *ast.TypeSwitchStmt) bool { + var clause *ast.CaseClause + for _, stmt := range swtch.Body.List { + c := stmt.(*ast.CaseClause) + if c.List == nil { + clause = c + break + } + } + if clause == nil { + panic("switch statement has no default clause") + } + if len(clause.Body) != 1 { + return false + } + exprStmt, ok := clause.Body[0].(*ast.ExprStmt) + if !ok { + return false + } + callExpr, ok := exprStmt.X.(*ast.CallExpr) + if !ok { + return false + } + fun, ok := callExpr.Fun.(*ast.Ident) + if !ok { + return false + } + return fun.Name == "panic" +} + +// findTypeAssertExpr extracts the expression that is being type asserted from a +// type swtich statement. +func findTypeAssertExpr(swtch *ast.TypeSwitchStmt) ast.Expr { + var expr ast.Expr + if assign, ok := swtch.Assign.(*ast.AssignStmt); ok { + expr = assign.Rhs[0] + } else { + expr = swtch.Assign.(*ast.ExprStmt).X + } + return expr.(*ast.TypeAssertExpr).X +} + +// findDef returns the sum type definition corresponding to the given type. If +// no such sum type definition exists, then nil is returned. +func findDef(defs []sumTypeDef, needle types.Type) *sumTypeDef { + for i := range defs { + def := &defs[i] + if types.Identical(needle.Underlying(), def.Ty) { + return def + } + } + return nil +} diff --git a/tools/vendor/github.com/alecthomas/go-check-sumtype/decl.go b/tools/vendor/github.com/alecthomas/go-check-sumtype/decl.go new file mode 100644 index 0000000000..ea2cd06dfa --- /dev/null +++ b/tools/vendor/github.com/alecthomas/go-check-sumtype/decl.go @@ -0,0 +1,68 @@ +package gochecksumtype + +import ( + "go/ast" + "go/token" + "strings" + + "golang.org/x/tools/go/packages" +) + +// sumTypeDecl is a declaration of a sum type in a Go source file. +type sumTypeDecl struct { + // The package path that contains this decl. + Package *packages.Package + // The type named by this decl. + TypeName string + // Position where the declaration was found. + Pos token.Position +} + +// Location returns a short string describing where this declaration was found. +func (d sumTypeDecl) Location() string { + return d.Pos.String() +} + +// findSumTypeDecls searches every package given for sum type declarations of +// the form `sumtype:decl`. +func findSumTypeDecls(pkgs []*packages.Package) ([]sumTypeDecl, error) { + var decls []sumTypeDecl + var retErr error + for _, pkg := range pkgs { + for _, file := range pkg.Syntax { + ast.Inspect(file, func(node ast.Node) bool { + if node == nil { + return true + } + decl, ok := node.(*ast.GenDecl) + if !ok || decl.Doc == nil { + return true + } + var tspec *ast.TypeSpec + for _, spec := range decl.Specs { + ts, ok := spec.(*ast.TypeSpec) + if !ok { + continue + } + tspec = ts + } + for _, line := range decl.Doc.List { + if !strings.HasPrefix(line.Text, "//sumtype:decl") { + continue + } + pos := pkg.Fset.Position(decl.Pos()) + if tspec == nil { + retErr = notFoundError{Decl: sumTypeDecl{Package: pkg, Pos: pos}} + return false + } + pos = pkg.Fset.Position(tspec.Pos()) + decl := sumTypeDecl{Package: pkg, TypeName: tspec.Name.Name, Pos: pos} + decls = append(decls, decl) + break + } + return true + }) + } + } + return decls, retErr +} diff --git a/tools/vendor/github.com/alecthomas/go-check-sumtype/def.go b/tools/vendor/github.com/alecthomas/go-check-sumtype/def.go new file mode 100644 index 0000000000..811b98f98c --- /dev/null +++ b/tools/vendor/github.com/alecthomas/go-check-sumtype/def.go @@ -0,0 +1,157 @@ +package gochecksumtype + +import ( + "fmt" + "go/token" + "go/types" +) + +// Error as returned by Run() +type Error interface { + error + Pos() token.Position +} + +// unsealedError corresponds to a declared sum type whose interface is not +// sealed. A sealed interface requires at least one unexported method. +type unsealedError struct { + Decl sumTypeDecl +} + +func (e unsealedError) Pos() token.Position { return e.Decl.Pos } +func (e unsealedError) Error() string { + return fmt.Sprintf( + "%s: interface '%s' is not sealed "+ + "(sealing requires at least one unexported method)", + e.Decl.Location(), e.Decl.TypeName) +} + +// notFoundError corresponds to a declared sum type whose type definition +// could not be found in the same Go package. +type notFoundError struct { + Decl sumTypeDecl +} + +func (e notFoundError) Pos() token.Position { return e.Decl.Pos } +func (e notFoundError) Error() string { + return fmt.Sprintf("%s: type '%s' is not defined", e.Decl.Location(), e.Decl.TypeName) +} + +// notInterfaceError corresponds to a declared sum type that does not +// correspond to an interface. +type notInterfaceError struct { + Decl sumTypeDecl +} + +func (e notInterfaceError) Pos() token.Position { return e.Decl.Pos } +func (e notInterfaceError) Error() string { + return fmt.Sprintf("%s: type '%s' is not an interface", e.Decl.Location(), e.Decl.TypeName) +} + +// sumTypeDef corresponds to the definition of a Go interface that is +// interpreted as a sum type. Its variants are determined by finding all types +// that implement said interface in the same package. +type sumTypeDef struct { + Decl sumTypeDecl + Ty *types.Interface + Variants []types.Object +} + +// findSumTypeDefs attempts to find a Go type definition for each of the given +// sum type declarations. If no such sum type definition could be found for +// any of the given declarations, then an error is returned. +func findSumTypeDefs(decls []sumTypeDecl) ([]sumTypeDef, []error) { + var defs []sumTypeDef + var errs []error + for _, decl := range decls { + def, err := newSumTypeDef(decl.Package.Types, decl) + if err != nil { + errs = append(errs, err) + continue + } + if def == nil { + errs = append(errs, notFoundError{decl}) + continue + } + defs = append(defs, *def) + } + return defs, errs +} + +// newSumTypeDef attempts to extract a sum type definition from a single +// package. If no such type corresponds to the given decl, then this function +// returns a nil def and a nil error. +// +// If the decl corresponds to a type that isn't an interface containing at +// least one unexported method, then this returns an error. +func newSumTypeDef(pkg *types.Package, decl sumTypeDecl) (*sumTypeDef, error) { + obj := pkg.Scope().Lookup(decl.TypeName) + if obj == nil { + return nil, nil + } + iface, ok := obj.Type().Underlying().(*types.Interface) + if !ok { + return nil, notInterfaceError{decl} + } + hasUnexported := false + for i := 0; i < iface.NumMethods(); i++ { + if !iface.Method(i).Exported() { + hasUnexported = true + break + } + } + if !hasUnexported { + return nil, unsealedError{decl} + } + def := &sumTypeDef{ + Decl: decl, + Ty: iface, + } + for _, name := range pkg.Scope().Names() { + obj, ok := pkg.Scope().Lookup(name).(*types.TypeName) + if !ok { + continue + } + ty := obj.Type() + if types.Identical(ty.Underlying(), iface) { + continue + } + if types.Implements(ty, iface) || types.Implements(types.NewPointer(ty), iface) { + def.Variants = append(def.Variants, obj) + } + } + return def, nil +} + +func (def *sumTypeDef) String() string { + return def.Decl.TypeName +} + +// missing returns a list of variants in this sum type that are not in the +// given list of types. +func (def *sumTypeDef) missing(tys []types.Type) []types.Object { + // TODO(ag): This is O(n^2). Fix that. /shrug + var missing []types.Object + for _, v := range def.Variants { + found := false + varty := indirect(v.Type()) + for _, ty := range tys { + ty = indirect(ty) + if types.Identical(varty, ty) { + found = true + } + } + if !found { + missing = append(missing, v) + } + } + return missing +} + +// indirect dereferences through an arbitrary number of pointer types. +func indirect(ty types.Type) types.Type { + if ty, ok := ty.(*types.Pointer); ok { + return indirect(ty.Elem()) + } + return ty +} diff --git a/tools/vendor/github.com/alecthomas/go-check-sumtype/doc.go b/tools/vendor/github.com/alecthomas/go-check-sumtype/doc.go new file mode 100644 index 0000000000..2b6e86764e --- /dev/null +++ b/tools/vendor/github.com/alecthomas/go-check-sumtype/doc.go @@ -0,0 +1,53 @@ +/* +sumtype takes a list of Go package paths or files and looks for sum type +declarations in each package/file provided. Exhaustiveness checks are then +performed for each use of a declared sum type in a type switch statement. +Namely, sumtype will report an error for any type switch statement that +either lacks a default clause or does not account for all possible variants. + +Declarations are provided in comments like so: + + //sumtype:decl + type MySumType interface { ... } + +MySumType must be *sealed*. That is, part of its interface definition contains +an unexported method. + +sumtype will produce an error if any of the above is not true. + +For valid declarations, sumtype will look for all occurrences in which a +value of type MySumType participates in a type switch statement. In those +occurrences, it will attempt to detect whether the type switch is exhaustive +or not. If it's not, sumtype will report an error. For example: + + $ cat mysumtype.go + package gochecksumtype + + //sumtype:decl + type MySumType interface { + sealed() + } + + type VariantA struct{} + + func (a *VariantA) sealed() {} + + type VariantB struct{} + + func (b *VariantB) sealed() {} + + func main() { + switch MySumType(nil).(type) { + case *VariantA: + } + } + $ sumtype mysumtype.go + mysumtype.go:18:2: exhaustiveness check failed for sum type 'MySumType': missing cases for VariantB + +Adding either a default clause or a clause to handle *VariantB will cause +exhaustive checks to pass. + +As a special case, if the type switch statement contains a default clause +that always panics, then exhaustiveness checks are still performed. +*/ +package gochecksumtype diff --git a/tools/vendor/github.com/alecthomas/go-check-sumtype/run.go b/tools/vendor/github.com/alecthomas/go-check-sumtype/run.go new file mode 100644 index 0000000000..fdcb643c5d --- /dev/null +++ b/tools/vendor/github.com/alecthomas/go-check-sumtype/run.go @@ -0,0 +1,26 @@ +package gochecksumtype + +import "golang.org/x/tools/go/packages" + +// Run sumtype checking on the given packages. +func Run(pkgs []*packages.Package) []error { + var errs []error + + decls, err := findSumTypeDecls(pkgs) + if err != nil { + return []error{err} + } + + defs, defErrs := findSumTypeDefs(decls) + errs = append(errs, defErrs...) + if len(defs) == 0 { + return errs + } + + for _, pkg := range pkgs { + if pkgErrs := check(pkg, defs); pkgErrs != nil { + errs = append(errs, pkgErrs...) + } + } + return errs +} diff --git a/tools/vendor/github.com/ashanbrown/forbidigo/forbidigo/forbidigo.go b/tools/vendor/github.com/ashanbrown/forbidigo/forbidigo/forbidigo.go index 943a69975d..a7a3ab591e 100644 --- a/tools/vendor/github.com/ashanbrown/forbidigo/forbidigo/forbidigo.go +++ b/tools/vendor/github.com/ashanbrown/forbidigo/forbidigo/forbidigo.go @@ -186,7 +186,40 @@ func (v *visitor) Visit(node ast.Node) ast.Visitor { if isGodocExample && v.cfg.ExcludeGodocExamples { return nil } - return v + ast.Walk(v, node.Type) + if node.Body != nil { + ast.Walk(v, node.Body) + } + return nil + // Ignore constant and type names + case *ast.ValueSpec: + // Look at only type and values for const and variable specs, and not names + if node.Type != nil { + ast.Walk(v, node.Type) + } + if node.Values != nil { + for _, x := range node.Values { + ast.Walk(v, x) + } + } + return nil + // Ignore import alias names + case *ast.ImportSpec: + return nil + // Ignore type names + case *ast.TypeSpec: + // Look at only type parameters for type spec + if node.TypeParams != nil { + ast.Walk(v, node.TypeParams) + } + ast.Walk(v, node.Type) + return nil + // Ignore field names + case *ast.Field: + if node.Type != nil { + ast.Walk(v, node.Type) + } + return nil // The following two are handled below. case *ast.SelectorExpr: case *ast.Ident: diff --git a/tools/vendor/github.com/breml/bidichk/pkg/bidichk/bidichk.go b/tools/vendor/github.com/breml/bidichk/pkg/bidichk/bidichk.go index 2e1e899349..f1bf20faba 100644 --- a/tools/vendor/github.com/breml/bidichk/pkg/bidichk/bidichk.go +++ b/tools/vendor/github.com/breml/bidichk/pkg/bidichk/bidichk.go @@ -15,7 +15,7 @@ import ( const ( doc = "bidichk detects dangerous unicode character sequences" - disallowedDoc = `coma separated list of disallowed runes (full name or short name) + disallowedDoc = `comma separated list of disallowed runes (full name or short name) Supported runes diff --git a/tools/vendor/github.com/breml/errchkjson/.goreleaser.yml b/tools/vendor/github.com/breml/errchkjson/.goreleaser.yml index 5f23690f15..a05c172cb6 100644 --- a/tools/vendor/github.com/breml/errchkjson/.goreleaser.yml +++ b/tools/vendor/github.com/breml/errchkjson/.goreleaser.yml @@ -14,13 +14,14 @@ builds: - windows - darwin archives: - - name_template: "{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" - replacements: - darwin: Darwin - linux: Linux - windows: Windows - 386: i386 - amd64: x86_64 + - name_template: >- + {{- .Binary }}_ + {{- .Version }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end -}} snapshot: name_template: "{{ .Tag }}-next" changelog: diff --git a/tools/vendor/github.com/breml/errchkjson/errchkjson.go b/tools/vendor/github.com/breml/errchkjson/errchkjson.go index 746709c763..4a23929cf2 100644 --- a/tools/vendor/github.com/breml/errchkjson/errchkjson.go +++ b/tools/vendor/github.com/breml/errchkjson/errchkjson.go @@ -308,14 +308,14 @@ func (e *errchkjson) inspectArgs(pass *analysis.Pass, args []ast.Expr) { } // Construct *types.Interface for interface encoding.TextMarshaler -// type TextMarshaler interface { -// MarshalText() (text []byte, err error) -// } // +// type TextMarshaler interface { +// MarshalText() (text []byte, err error) +// } func textMarshalerInterface() *types.Interface { textMarshalerInterface := types.NewInterfaceType([]*types.Func{ - types.NewFunc(token.NoPos, nil, "MarshalText", types.NewSignature( - nil, nil, types.NewTuple( + types.NewFunc(token.NoPos, nil, "MarshalText", types.NewSignatureType( + nil, nil, nil, nil, types.NewTuple( types.NewVar(token.NoPos, nil, "text", types.NewSlice( types.Universe.Lookup("byte").Type())), @@ -328,14 +328,14 @@ func textMarshalerInterface() *types.Interface { } // Construct *types.Interface for interface json.Marshaler -// type Marshaler interface { -// MarshalJSON() ([]byte, error) -// } // +// type Marshaler interface { +// MarshalJSON() ([]byte, error) +// } func jsonMarshalerInterface() *types.Interface { textMarshalerInterface := types.NewInterfaceType([]*types.Func{ - types.NewFunc(token.NoPos, nil, "MarshalJSON", types.NewSignature( - nil, nil, types.NewTuple( + types.NewFunc(token.NoPos, nil, "MarshalJSON", types.NewSignatureType( + nil, nil, nil, nil, types.NewTuple( types.NewVar(token.NoPos, nil, "", types.NewSlice( types.Universe.Lookup("byte").Type())), diff --git a/tools/vendor/github.com/butuzov/ireturn/analyzer/analyzer.go b/tools/vendor/github.com/butuzov/ireturn/analyzer/analyzer.go index 3a0bf7402d..21e5897b26 100644 --- a/tools/vendor/github.com/butuzov/ireturn/analyzer/analyzer.go +++ b/tools/vendor/github.com/butuzov/ireturn/analyzer/analyzer.go @@ -23,6 +23,7 @@ type validator interface { type analyzer struct { once sync.Once + mu sync.RWMutex handler validator err error @@ -83,11 +84,13 @@ func (a *analyzer) run(pass *analysis.Pass) (interface{}, error) { } seen[key] = true - a.found = append(a.found, issue.ExportDiagnostic()) + a.addDiagnostic(issue.ExportDiagnostic()) } }) // 02. Printing reports. + a.mu.RLock() + defer a.mu.RUnlock() for i := range a.found { pass.Report(a.found[i]) } @@ -95,6 +98,13 @@ func (a *analyzer) run(pass *analysis.Pass) (interface{}, error) { return nil, nil } +func (a *analyzer) addDiagnostic(d analysis.Diagnostic) { + a.mu.Lock() + defer a.mu.Unlock() + + a.found = append(a.found, d) +} + func (a *analyzer) readConfiguration(fs *flag.FlagSet) { cnf, err := config.New(fs) if err != nil { diff --git a/tools/vendor/github.com/butuzov/ireturn/analyzer/internal/config/config.go b/tools/vendor/github.com/butuzov/ireturn/analyzer/internal/config/config.go index e2f1aef6e9..46c73170ae 100644 --- a/tools/vendor/github.com/butuzov/ireturn/analyzer/internal/config/config.go +++ b/tools/vendor/github.com/butuzov/ireturn/analyzer/internal/config/config.go @@ -2,6 +2,7 @@ package config import ( "regexp" + "sync" "github.com/butuzov/ireturn/analyzer/internal/types" ) @@ -13,16 +14,13 @@ type defaultConfig struct { List []string // private fields (for search optimization look ups) - init bool + once sync.Once quick uint8 list []*regexp.Regexp } func (config *defaultConfig) Has(i types.IFace) bool { - if !config.init { - config.compileList() - config.init = true - } + config.once.Do(config.compileList) if config.quick&uint8(i.Type) > 0 { return true diff --git a/tools/vendor/github.com/butuzov/ireturn/analyzer/std.go b/tools/vendor/github.com/butuzov/ireturn/analyzer/std.go index ec361cd442..4c6c4e4204 100644 --- a/tools/vendor/github.com/butuzov/ireturn/analyzer/std.go +++ b/tools/vendor/github.com/butuzov/ireturn/analyzer/std.go @@ -191,4 +191,10 @@ var std = map[string]struct{}{ // added in Go v1.20 in compare to v1.19 (docker image) "crypto/ecdh": {}, "runtime/coverage": {}, + // added in Go v1.21 in compare to v1.20 (docker image) + "cmp": {}, + "log/slog": {}, + "maps": {}, + "slices": {}, + "testing/slogtest": {}, } diff --git a/tools/vendor/github.com/catenacyber/perfsprint/LICENSE b/tools/vendor/github.com/catenacyber/perfsprint/LICENSE new file mode 100644 index 0000000000..14c2b9e737 --- /dev/null +++ b/tools/vendor/github.com/catenacyber/perfsprint/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Catena cyber + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/tools/vendor/github.com/catenacyber/perfsprint/analyzer/analyzer.go b/tools/vendor/github.com/catenacyber/perfsprint/analyzer/analyzer.go new file mode 100644 index 0000000000..69087802a7 --- /dev/null +++ b/tools/vendor/github.com/catenacyber/perfsprint/analyzer/analyzer.go @@ -0,0 +1,333 @@ +package analyzer + +import ( + "bytes" + "go/ast" + "go/format" + "go/token" + "go/types" + "strconv" + + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" + + "golang.org/x/tools/go/analysis" +) + +var Analyzer = &analysis.Analyzer{ + Name: "perfsprint", + Doc: "Checks that fmt.Sprintf can be replaced with a faster alternative.", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, +} + +func run(pass *analysis.Pass) (interface{}, error) { + var fmtSprintObj, fmtSprintfObj types.Object + for _, pkg := range pass.Pkg.Imports() { + if pkg.Path() == "fmt" { + fmtSprintObj = pkg.Scope().Lookup("Sprint") + fmtSprintfObj = pkg.Scope().Lookup("Sprintf") + } + } + if fmtSprintfObj == nil { + return nil, nil + } + + insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter := []ast.Node{ + (*ast.CallExpr)(nil), + } + insp.Preorder(nodeFilter, func(node ast.Node) { + call := node.(*ast.CallExpr) + called, ok := call.Fun.(*ast.SelectorExpr) + if !ok { + return + } + calledObj := pass.TypesInfo.ObjectOf(called.Sel) + + var ( + fn string + verb string + value ast.Expr + err error + ) + switch { + case calledObj == fmtSprintObj && len(call.Args) == 1: + fn = "fmt.Sprint" + verb = "%v" + value = call.Args[0] + + case calledObj == fmtSprintfObj && len(call.Args) == 2: + verbLit, ok := call.Args[0].(*ast.BasicLit) + if !ok { + return + } + verb, err = strconv.Unquote(verbLit.Value) + if err != nil { + // Probably unreachable. + return + } + + fn = "fmt.Sprintf" + value = call.Args[1] + + default: + return + } + + switch verb { + default: + return + case "%d", "%v", "%x", "%t", "%s": + } + + valueType := pass.TypesInfo.TypeOf(value) + a, isArray := valueType.(*types.Array) + s, isSlice := valueType.(*types.Slice) + + var d *analysis.Diagnostic + switch { + case isBasicType(valueType, types.String) && oneOf(verb, "%v", "%s"): + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: fn + " can be replaced with just using the string", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Just use string value", + TextEdits: []analysis.TextEdit{{ + Pos: call.Pos(), + End: call.End(), + NewText: []byte(formatNode(pass.Fset, value)), + }}, + }, + }, + } + + case types.Implements(valueType, errIface) && oneOf(verb, "%v", "%s"): + errMethodCall := formatNode(pass.Fset, value) + ".Error()" + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: fn + " can be replaced with " + errMethodCall, + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Use " + errMethodCall, + TextEdits: []analysis.TextEdit{{ + Pos: call.Pos(), + End: call.End(), + NewText: []byte(errMethodCall), + }}, + }, + }, + } + + case isBasicType(valueType, types.Bool) && oneOf(verb, "%v", "%t"): + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: fn + " can be replaced with faster strconv.FormatBool", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Use strconv.FormatBool", + TextEdits: []analysis.TextEdit{{ + Pos: call.Pos(), + End: value.Pos(), + NewText: []byte("strconv.FormatBool("), + }}, + }, + }, + } + + case isArray && isBasicType(a.Elem(), types.Uint8) && oneOf(verb, "%x"): + if _, ok := value.(*ast.Ident); !ok { + // Doesn't support array literals. + return + } + + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: fn + " can be replaced with faster hex.EncodeToString", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Use hex.EncodeToString", + TextEdits: []analysis.TextEdit{ + { + Pos: call.Pos(), + End: value.Pos(), + NewText: []byte("hex.EncodeToString("), + }, + { + Pos: value.End(), + End: value.End(), + NewText: []byte("[:]"), + }, + }, + }, + }, + } + case isSlice && isBasicType(s.Elem(), types.Uint8) && oneOf(verb, "%x"): + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: fn + " can be replaced with faster hex.EncodeToString", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Use hex.EncodeToString", + TextEdits: []analysis.TextEdit{{ + Pos: call.Pos(), + End: value.Pos(), + NewText: []byte("hex.EncodeToString("), + }}, + }, + }, + } + + case isBasicType(valueType, types.Int8, types.Int16, types.Int32) && oneOf(verb, "%v", "%d"): + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: fn + " can be replaced with faster strconv.Itoa", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Use strconv.Itoa", + TextEdits: []analysis.TextEdit{ + { + Pos: call.Pos(), + End: value.Pos(), + NewText: []byte("strconv.Itoa(int("), + }, + { + Pos: value.End(), + End: value.End(), + NewText: []byte(")"), + }, + }, + }, + }, + } + case isBasicType(valueType, types.Int) && oneOf(verb, "%v", "%d"): + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: fn + " can be replaced with faster strconv.Itoa", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Use strconv.Itoa", + TextEdits: []analysis.TextEdit{{ + Pos: call.Pos(), + End: value.Pos(), + NewText: []byte("strconv.Itoa("), + }}, + }, + }, + } + case isBasicType(valueType, types.Int64) && oneOf(verb, "%v", "%d"): + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: fn + " can be replaced with faster strconv.FormatInt", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Use strconv.FormatInt", + TextEdits: []analysis.TextEdit{ + { + Pos: call.Pos(), + End: value.Pos(), + NewText: []byte("strconv.FormatInt("), + }, + { + Pos: value.End(), + End: value.End(), + NewText: []byte(", 10"), + }, + }, + }, + }, + } + + case isBasicType(valueType, types.Uint8, types.Uint16, types.Uint32, types.Uint) && oneOf(verb, "%v", "%d"): + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: fn + " can be replaced with faster strconv.FormatUint", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Use strconv.FormatUint", + TextEdits: []analysis.TextEdit{ + { + Pos: call.Pos(), + End: value.Pos(), + NewText: []byte("strconv.FormatUint(uint64("), + }, + { + Pos: value.End(), + End: value.End(), + NewText: []byte("), 10"), + }, + }, + }, + }, + } + case isBasicType(valueType, types.Uint64) && oneOf(verb, "%v", "%d"): + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: fn + " can be replaced with faster strconv.FormatUint", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Use strconv.FormatUint", + TextEdits: []analysis.TextEdit{ + { + Pos: call.Pos(), + End: value.Pos(), + NewText: []byte("strconv.FormatUint("), + }, + { + Pos: value.End(), + End: value.End(), + NewText: []byte(", 10"), + }, + }, + }, + }, + } + } + + if d != nil { + // Need to run goimports to fix using of fmt, strconv or encoding/hex afterwards. + pass.Report(*d) + } + }) + + return nil, nil +} + +var errIface = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) + +func isBasicType(lhs types.Type, expected ...types.BasicKind) bool { + for _, rhs := range expected { + if types.Identical(lhs, types.Typ[rhs]) { + return true + } + } + return false +} + +func formatNode(fset *token.FileSet, node ast.Node) string { + buf := new(bytes.Buffer) + if err := format.Node(buf, fset, node); err != nil { + return "" + } + return buf.String() +} + +func oneOf[T comparable](v T, expected ...T) bool { + for _, rhs := range expected { + if v == rhs { + return true + } + } + return false +} diff --git a/tools/vendor/github.com/ccojocar/zxcvbn-go/.gitignore b/tools/vendor/github.com/ccojocar/zxcvbn-go/.gitignore new file mode 100644 index 0000000000..e032cc2fcb --- /dev/null +++ b/tools/vendor/github.com/ccojocar/zxcvbn-go/.gitignore @@ -0,0 +1,5 @@ +zxcvbn +debug.test + +# SBOMs generated during CI +/bom.json diff --git a/tools/vendor/github.com/ccojocar/zxcvbn-go/.golangci.yml b/tools/vendor/github.com/ccojocar/zxcvbn-go/.golangci.yml new file mode 100644 index 0000000000..b54f70092e --- /dev/null +++ b/tools/vendor/github.com/ccojocar/zxcvbn-go/.golangci.yml @@ -0,0 +1,39 @@ +linters: + enable: + - asciicheck + - bodyclose + - dogsled + - durationcheck + - errcheck + - errorlint + - exportloopref + - gci + - ginkgolinter + - gofmt + - gofumpt + - goimports + - gosimple + - govet + - importas + - ineffassign + - megacheck + - misspell + - nakedret + - nolintlint + - revive + - staticcheck + - typecheck + - unconvert + - unparam + - unused + - wastedassign + +linters-settings: + gci: + sections: + - standard + - default + - prefix(github.com/ccojocar) + +run: + timeout: 5m diff --git a/tools/vendor/github.com/ccojocar/zxcvbn-go/.goreleaser.yml b/tools/vendor/github.com/ccojocar/zxcvbn-go/.goreleaser.yml new file mode 100644 index 0000000000..2386aeee52 --- /dev/null +++ b/tools/vendor/github.com/ccojocar/zxcvbn-go/.goreleaser.yml @@ -0,0 +1,27 @@ +--- +project_name: zxcvbn-go + +release: + extra_files: + - glob: ./bom.json + github: + owner: ccojocar + name: zxcvbn-go + +builds: + - main: ./testapp/ + binary: zxcvbn-go + goos: + - darwin + - linux + - windows + goarch: + - amd64 + - arm64 + - s390x + ldflags: -X main.Version={{.Version}} -X main.GitTag={{.Tag}} -X main.BuildDate={{.Date}} + env: + - CGO_ENABLED=0 + +gomod: + proxy: true diff --git a/tools/vendor/github.com/nbutton23/zxcvbn-go/LICENSE.txt b/tools/vendor/github.com/ccojocar/zxcvbn-go/LICENSE.txt similarity index 100% rename from tools/vendor/github.com/nbutton23/zxcvbn-go/LICENSE.txt rename to tools/vendor/github.com/ccojocar/zxcvbn-go/LICENSE.txt diff --git a/tools/vendor/github.com/ccojocar/zxcvbn-go/Makefile b/tools/vendor/github.com/ccojocar/zxcvbn-go/Makefile new file mode 100644 index 0000000000..0690f37538 --- /dev/null +++ b/tools/vendor/github.com/ccojocar/zxcvbn-go/Makefile @@ -0,0 +1,61 @@ +GIT_TAG?= $(shell git describe --always --tags) +BIN = zxcvbn-go +FMT_CMD = $(gofmt -s -l -w $(find . -type f -name '*.go' -not -path './vendor/*') | tee /dev/stderr) +IMAGE_REPO = ccojocar +DATE_FMT=+%Y-%m-%d +ifdef SOURCE_DATE_EPOCH + BUILD_DATE ?= $(shell date -u -d "@$(SOURCE_DATE_EPOCH)" "$(DATE_FMT)" 2>/dev/null || date -u -r "$(SOURCE_DATE_EPOCH)" "$(DATE_FMT)" 2>/dev/null || date -u "$(DATE_FMT)") +else + BUILD_DATE ?= $(shell date "$(DATE_FMT)") +endif +BUILDFLAGS := "-w -s -X 'main.Version=$(GIT_TAG)' -X 'main.GitTag=$(GIT_TAG)' -X 'main.BuildDate=$(BUILD_DATE)'" +CGO_ENABLED = 0 +GO := GO111MODULE=on go +GO_NOMOD :=GO111MODULE=off go +GOPATH ?= $(shell $(GO) env GOPATH) +GOBIN ?= $(GOPATH)/bin +GO_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2) +GOVULN_MIN_VERSION = 17 +GO_VERSION = 1.20 + +default: + $(MAKE) test + +install-govulncheck: + @if [ $(GO_MINOR_VERSION) -gt $(GOVULN_MIN_VERSION) ]; then \ + go install golang.org/x/vuln/cmd/govulncheck@latest; \ + fi + +test-all: fmt vet lint sec govulncheck test + +test: + go test -v ./... + +fmt: + @echo "FORMATTING" + @FORMATTED=`$(GO) fmt ./...` + @([ ! -z "$(FORMATTED)" ] && printf "Fixed unformatted files:\n$(FORMATTED)") || true + +vet: + @echo "VETTING" + $(GO) vet ./... + +lint: + @echo "LINTING: golangci-lint" + golangci-lint run + +sec: + @echo "SECURITY SCANNING" + gosec ./... + +govulncheck: install-govulncheck + @echo "CHECKING VULNERABILITIES" + @if [ $(GO_MINOR_VERSION) -gt $(GOVULN_MIN_VERSION) ]; then \ + govulncheck ./...; \ + fi + +clean: + rm -rf build vendor dist coverage.txt + rm -f release image $(BIN) + +.PHONY: test test-all fmt vet govulncheck clean diff --git a/tools/vendor/github.com/nbutton23/zxcvbn-go/README.md b/tools/vendor/github.com/ccojocar/zxcvbn-go/README.md similarity index 100% rename from tools/vendor/github.com/nbutton23/zxcvbn-go/README.md rename to tools/vendor/github.com/ccojocar/zxcvbn-go/README.md diff --git a/tools/vendor/github.com/nbutton23/zxcvbn-go/adjacency/adjcmartix.go b/tools/vendor/github.com/ccojocar/zxcvbn-go/adjacency/adjcmartix.go similarity index 82% rename from tools/vendor/github.com/nbutton23/zxcvbn-go/adjacency/adjcmartix.go rename to tools/vendor/github.com/ccojocar/zxcvbn-go/adjacency/adjcmartix.go index 66ad30b822..34526685cc 100644 --- a/tools/vendor/github.com/nbutton23/zxcvbn-go/adjacency/adjcmartix.go +++ b/tools/vendor/github.com/ccojocar/zxcvbn-go/adjacency/adjcmartix.go @@ -4,7 +4,7 @@ import ( "encoding/json" "log" - "github.com/nbutton23/zxcvbn-go/data" + "github.com/ccojocar/zxcvbn-go/data" ) // Graph holds information about different graphs @@ -25,7 +25,7 @@ func init() { GraphMap["l33t"] = BuildLeet() } -//BuildQwerty builds the Qwerty Graph +// BuildQwerty builds the Qwerty Graph func BuildQwerty() Graph { data, err := data.Asset("data/Qwerty.json") if err != nil { @@ -34,7 +34,7 @@ func BuildQwerty() Graph { return getAdjancencyGraphFromFile(data, "qwerty") } -//BuildDvorak builds the Dvorak Graph +// BuildDvorak builds the Dvorak Graph func BuildDvorak() Graph { data, err := data.Asset("data/Dvorak.json") if err != nil { @@ -43,7 +43,7 @@ func BuildDvorak() Graph { return getAdjancencyGraphFromFile(data, "dvorak") } -//BuildKeypad builds the Keypad Graph +// BuildKeypad builds the Keypad Graph func BuildKeypad() Graph { data, err := data.Asset("data/Keypad.json") if err != nil { @@ -52,7 +52,7 @@ func BuildKeypad() Graph { return getAdjancencyGraphFromFile(data, "keypad") } -//BuildMacKeypad builds the Mac Keypad Graph +// BuildMacKeypad builds the Mac Keypad Graph func BuildMacKeypad() Graph { data, err := data.Asset("data/MacKeypad.json") if err != nil { @@ -61,7 +61,7 @@ func BuildMacKeypad() Graph { return getAdjancencyGraphFromFile(data, "mac_keypad") } -//BuildLeet builds the L33T Graph +// BuildLeet builds the L33T Graph func BuildLeet() Graph { data, err := data.Asset("data/L33t.json") if err != nil { @@ -71,7 +71,6 @@ func BuildLeet() Graph { } func getAdjancencyGraphFromFile(data []byte, name string) Graph { - var graph Graph err := json.Unmarshal(data, &graph) if err != nil { @@ -82,9 +81,9 @@ func getAdjancencyGraphFromFile(data []byte, name string) Graph { } // CalculateAvgDegree calclates the average degree between nodes in the graph -//on qwerty, 'g' has degree 6, being adjacent to 'ftyhbv'. '\' has degree 1. -//this calculates the average over all keys. -//TODO double check that i ported this correctly scoring.coffee ln 5 +// on qwerty, 'g' has degree 6, being adjacent to 'ftyhbv'. '\' has degree 1. +// this calculates the average over all keys. +// TODO double check that i ported this correctly scoring.coffee ln 5 func (adjGrp Graph) CalculateAvgDegree() float64 { if adjGrp.averageDegree != float64(0) { return adjGrp.averageDegree @@ -92,14 +91,12 @@ func (adjGrp Graph) CalculateAvgDegree() float64 { var avg float64 var count float64 for _, value := range adjGrp.Graph { - for _, char := range value { if len(char) != 0 || char != " " { avg += float64(len(char)) count++ } } - } adjGrp.averageDegree = avg / count diff --git a/tools/vendor/github.com/nbutton23/zxcvbn-go/data/bindata.go b/tools/vendor/github.com/ccojocar/zxcvbn-go/data/bindata.go similarity index 99% rename from tools/vendor/github.com/nbutton23/zxcvbn-go/data/bindata.go rename to tools/vendor/github.com/ccojocar/zxcvbn-go/data/bindata.go index f3a0c010ca..3db0f1b100 100644 --- a/tools/vendor/github.com/nbutton23/zxcvbn-go/data/bindata.go +++ b/tools/vendor/github.com/ccojocar/zxcvbn-go/data/bindata.go @@ -33,7 +33,7 @@ func bindataRead(data []byte, name string) ([]byte, error) { } var buf bytes.Buffer - _, err = io.Copy(&buf, gz) + _, err = io.Copy(&buf, gz) // #nosec clErr := gz.Close() if err != nil { @@ -345,11 +345,13 @@ var _bindata = map[string]func() (*asset, error){ // directory embedded in the file by go-bindata. // For example if you run go-bindata on data/... and data contains the // following hierarchy: -// data/ -// foo.txt -// img/ -// a.png -// b.png +// +// data/ +// foo.txt +// img/ +// a.png +// b.png +// // then AssetDir("data") would return []string{"foo.txt", "img"} // AssetDir("data/img") would return []string{"a.png", "b.png"} // AssetDir("foo.txt") and AssetDir("notexist") would return an error diff --git a/tools/vendor/github.com/nbutton23/zxcvbn-go/entropy/entropyCalculator.go b/tools/vendor/github.com/ccojocar/zxcvbn-go/entropy/entropyCalculator.go similarity index 77% rename from tools/vendor/github.com/nbutton23/zxcvbn-go/entropy/entropyCalculator.go rename to tools/vendor/github.com/ccojocar/zxcvbn-go/entropy/entropyCalculator.go index 8f57ea0a47..80432572bd 100644 --- a/tools/vendor/github.com/nbutton23/zxcvbn-go/entropy/entropyCalculator.go +++ b/tools/vendor/github.com/ccojocar/zxcvbn-go/entropy/entropyCalculator.go @@ -1,12 +1,13 @@ package entropy import ( - "github.com/nbutton23/zxcvbn-go/adjacency" - "github.com/nbutton23/zxcvbn-go/match" - "github.com/nbutton23/zxcvbn-go/utils/math" "math" "regexp" "unicode" + + "github.com/ccojocar/zxcvbn-go/adjacency" + "github.com/ccojocar/zxcvbn-go/match" + zxcvbnmath "github.com/ccojocar/zxcvbn-go/utils/math" ) const ( @@ -27,7 +28,7 @@ var ( func DictionaryEntropy(match match.Match, rank float64) float64 { baseEntropy := math.Log2(rank) upperCaseEntropy := extraUpperCaseEntropy(match) - //TODO: L33t + // TODO: L33t return baseEntropy + upperCaseEntropy } @@ -46,18 +47,18 @@ func extraUpperCaseEntropy(match match.Match) float64 { return float64(0) } - //a capitalized word is the most common capitalization scheme, - //so it only doubles the search space (uncapitalized + capitalized): 1 extra bit of entropy. - //allcaps and end-capitalized are common enough too, underestimate as 1 extra bit to be safe. + // a capitalized word is the most common capitalization scheme, + // so it only doubles the search space (uncapitalized + capitalized): 1 extra bit of entropy. + // allcaps and end-capitalized are common enough too, underestimate as 1 extra bit to be safe. for _, matcher := range []*regexp.Regexp{startUpperRx, endUpperRx, allUpperRx} { if matcher.MatchString(word) { return float64(1) } } - //Otherwise calculate the number of ways to capitalize U+L uppercase+lowercase letters with U uppercase letters or - //less. Or, if there's more uppercase than lower (for e.g. PASSwORD), the number of ways to lowercase U+L letters - //with L lowercase letters or less. + // Otherwise calculate the number of ways to capitalize U+L uppercase+lowercase letters with U uppercase letters or + // less. Or, if there's more uppercase than lower (for e.g. PASSwORD), the number of ways to lowercase U+L letters + // with L lowercase letters or less. countUpper, countLower := float64(0), float64(0) for _, char := range word { @@ -71,21 +72,21 @@ func extraUpperCaseEntropy(match match.Match) float64 { var possibililities float64 for i := float64(0); i <= math.Min(countUpper, countLower); i++ { - possibililities += float64(zxcvbnmath.NChoseK(totalLenght, i)) + possibililities += zxcvbnmath.NChoseK(totalLenght, i) } if possibililities < 1 { return float64(1) } - return float64(math.Log2(possibililities)) + return (math.Log2(possibililities)) } // SpatialEntropy calculates the entropy for spatial matches func SpatialEntropy(match match.Match, turns int, shiftCount int) float64 { var s, d float64 if match.DictionaryName == "qwerty" || match.DictionaryName == "dvorak" { - //todo: verify qwerty and dvorak have the same length and degree + // todo: verify qwerty and dvorak have the same length and degree s = float64(len(adjacency.BuildQwerty().Graph)) d = adjacency.BuildQwerty().CalculateAvgDegree() } else { @@ -97,8 +98,8 @@ func SpatialEntropy(match match.Match, turns int, shiftCount int) float64 { length := float64(len(match.Token)) - //TODO: Should this be <= or just < ? - //Estimate the number of possible patterns w/ length L or less with t turns or less + // TODO: Should this be <= or just < ? + // Estimate the number of possible patterns w/ length L or less with t turns or less for i := float64(2); i <= length+1; i++ { possibleTurns := math.Min(float64(turns), i-1) for j := float64(1); j <= possibleTurns+1; j++ { @@ -108,8 +109,8 @@ func SpatialEntropy(match match.Match, turns int, shiftCount int) float64 { } entropy := math.Log2(possibilities) - //add extra entropu for shifted keys. ( % instead of 5 A instead of a) - //Math is similar to extra entropy for uppercase letters in dictionary matches. + // add extra entropu for shifted keys. ( % instead of 5 A instead of a) + // Math is similar to extra entropy for uppercase letters in dictionary matches. if S := float64(shiftCount); S > float64(0) { possibilities = float64(0) @@ -134,7 +135,7 @@ func RepeatEntropy(match match.Match) float64 { } // CalcBruteForceCardinality calculates the brute force cardinality -//TODO: Validate against python +// TODO: Validate against python func CalcBruteForceCardinality(password string) float64 { lower, upper, digits, symbols := float64(0), float64(0), float64(0), float64(0) @@ -157,12 +158,12 @@ func CalcBruteForceCardinality(password string) float64 { // SequenceEntropy calculates the entropy for sequences such as 4567 or cdef func SequenceEntropy(match match.Match, dictionaryLength int, ascending bool) float64 { firstChar := match.Token[0] - baseEntropy := float64(0) + var baseEntropy float64 if string(firstChar) == "a" || string(firstChar) == "1" { baseEntropy = float64(0) } else { baseEntropy = math.Log2(float64(dictionaryLength)) - //TODO: should this be just the first or any char? + // TODO: should this be just the first or any char? if unicode.IsUpper(rune(firstChar)) { baseEntropy++ } @@ -183,7 +184,7 @@ func ExtraLeetEntropy(match match.Match, password string) float64 { if string(char) != string(match.Token[index]) { subsitutions++ } else { - //TODO: Make this only true for 1337 chars that are not subs? + // TODO: Make this only true for 1337 chars that are not subs? unsub++ } } @@ -210,7 +211,7 @@ func DateEntropy(dateMatch match.DateMatch) float64 { } if dateMatch.Separator != "" { - entropy += 2 //add two bits for separator selection [/,-,.,etc] + entropy += 2 // add two bits for separator selection [/,-,.,etc] } return entropy } diff --git a/tools/vendor/github.com/nbutton23/zxcvbn-go/frequency/frequency.go b/tools/vendor/github.com/ccojocar/zxcvbn-go/frequency/frequency.go similarity index 96% rename from tools/vendor/github.com/nbutton23/zxcvbn-go/frequency/frequency.go rename to tools/vendor/github.com/ccojocar/zxcvbn-go/frequency/frequency.go index d056e4d4e6..4f51369e1f 100644 --- a/tools/vendor/github.com/nbutton23/zxcvbn-go/frequency/frequency.go +++ b/tools/vendor/github.com/ccojocar/zxcvbn-go/frequency/frequency.go @@ -4,7 +4,7 @@ import ( "encoding/json" "log" - "github.com/nbutton23/zxcvbn-go/data" + "github.com/ccojocar/zxcvbn-go/data" ) // List holds a frequency list @@ -28,8 +28,8 @@ func init() { Lists["Surname"] = getStringListFromAsset(surnameFilePath, "Surname") Lists["English"] = getStringListFromAsset(englishFilePath, "English") Lists["Passwords"] = getStringListFromAsset(passwordsFilePath, "Passwords") - } + func getAsset(name string) []byte { data, err := data.Asset(name) if err != nil { @@ -38,8 +38,8 @@ func getAsset(name string) []byte { return data } -func getStringListFromAsset(data []byte, name string) List { +func getStringListFromAsset(data []byte, name string) List { var tempList List err := json.Unmarshal(data, &tempList) if err != nil { diff --git a/tools/vendor/github.com/nbutton23/zxcvbn-go/match/match.go b/tools/vendor/github.com/ccojocar/zxcvbn-go/match/match.go similarity index 80% rename from tools/vendor/github.com/nbutton23/zxcvbn-go/match/match.go rename to tools/vendor/github.com/ccojocar/zxcvbn-go/match/match.go index dd30bea042..998dde1112 100644 --- a/tools/vendor/github.com/nbutton23/zxcvbn-go/match/match.go +++ b/tools/vendor/github.com/ccojocar/zxcvbn-go/match/match.go @@ -1,14 +1,16 @@ package match -//Matches is an alies for []Match used for sorting +// Matches is an alies for []Match used for sorting type Matches []Match func (s Matches) Len() int { return len(s) } + func (s Matches) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + func (s Matches) Less(i, j int) bool { if s[i].I < s[j].I { return true @@ -28,7 +30,7 @@ type Match struct { Entropy float64 } -//DateMatch is specifilly a match for type date +// DateMatch is specifilly a match for type date type DateMatch struct { Pattern string I, J int @@ -37,7 +39,7 @@ type DateMatch struct { Day, Month, Year int64 } -//Matcher are a func and ID that can be used to match different passwords +// Matcher are a func and ID that can be used to match different passwords type Matcher struct { MatchingFunc func(password string) []Match ID string diff --git a/tools/vendor/github.com/nbutton23/zxcvbn-go/matching/dateMatchers.go b/tools/vendor/github.com/ccojocar/zxcvbn-go/matching/dateMatchers.go similarity index 93% rename from tools/vendor/github.com/nbutton23/zxcvbn-go/matching/dateMatchers.go rename to tools/vendor/github.com/ccojocar/zxcvbn-go/matching/dateMatchers.go index 8dfdf2410b..fd7f383320 100644 --- a/tools/vendor/github.com/nbutton23/zxcvbn-go/matching/dateMatchers.go +++ b/tools/vendor/github.com/ccojocar/zxcvbn-go/matching/dateMatchers.go @@ -5,8 +5,8 @@ import ( "strconv" "strings" - "github.com/nbutton23/zxcvbn-go/entropy" - "github.com/nbutton23/zxcvbn-go/match" + "github.com/ccojocar/zxcvbn-go/entropy" + "github.com/ccojocar/zxcvbn-go/match" ) const ( @@ -20,12 +20,12 @@ var ( dateWithOutSepMatch = regexp.MustCompile(`\d{4,8}`) ) -//FilterDateSepMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher +// FilterDateSepMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher func FilterDateSepMatcher(m match.Matcher) bool { return m.ID == dateSepMatcherName } -//FilterDateWithoutSepMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher +// FilterDateWithoutSepMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher func FilterDateWithoutSepMatcher(m match.Matcher) bool { return m.ID == dateWithOutSepMatcherName } @@ -64,8 +64,8 @@ func dateSepMatcher(password string) []match.Match { return matches } -func dateSepMatchHelper(password string) []match.DateMatch { +func dateSepMatchHelper(password string) []match.DateMatch { var matches []match.DateMatch for _, v := range dateRxYearSuffix.FindAllString(password, len(password)) { @@ -101,7 +101,6 @@ func dateSepMatchHelper(password string) []match.DateMatch { } } return out - } type dateMatchCandidate struct { @@ -136,7 +135,7 @@ func dateWithoutSepMatch(password string) []match.Match { return matches } -//TODO Has issues with 6 digit dates +// TODO Has issues with 6 digit dates func dateWithoutSepMatchHelper(password string) (matches []match.DateMatch) { for _, v := range dateWithOutSepMatch.FindAllString(password, len(password)) { i := strings.Index(password, v) @@ -146,17 +145,17 @@ func dateWithoutSepMatchHelper(password string) (matches []match.DateMatch) { var candidatesRoundOne []dateMatchCandidate if length <= 6 { - //2-digit year prefix + // 2-digit year prefix candidatesRoundOne = append(candidatesRoundOne, buildDateMatchCandidate(v[2:], v[0:2], i, j)) - //2-digityear suffix + // 2-digityear suffix candidatesRoundOne = append(candidatesRoundOne, buildDateMatchCandidate(v[0:lastIndex-2], v[lastIndex-2:], i, j)) } if length >= 6 { - //4-digit year prefix + // 4-digit year prefix candidatesRoundOne = append(candidatesRoundOne, buildDateMatchCandidate(v[4:], v[0:4], i, j)) - //4-digit year sufix + // 4-digit year sufix candidatesRoundOne = append(candidatesRoundOne, buildDateMatchCandidate(v[0:lastIndex-3], v[lastIndex-3:], i, j)) } @@ -179,7 +178,6 @@ func dateWithoutSepMatchHelper(password string) (matches []match.DateMatch) { } intMonth, err := strconv.ParseInt(candidate.Month, 10, 16) - if err != nil { continue } @@ -204,6 +202,5 @@ func buildDateMatchCandidate(dayMonth, year string, i, j int) dateMatchCandidate } func buildDateMatchCandidateTwo(day, month string, year string, i, j int) dateMatchCandidateTwo { - return dateMatchCandidateTwo{Day: day, Month: month, Year: year, I: i, J: j} } diff --git a/tools/vendor/github.com/nbutton23/zxcvbn-go/matching/dictionaryMatch.go b/tools/vendor/github.com/ccojocar/zxcvbn-go/matching/dictionaryMatch.go similarity index 89% rename from tools/vendor/github.com/nbutton23/zxcvbn-go/matching/dictionaryMatch.go rename to tools/vendor/github.com/ccojocar/zxcvbn-go/matching/dictionaryMatch.go index 4ddb2c3b01..d0d4501880 100644 --- a/tools/vendor/github.com/nbutton23/zxcvbn-go/matching/dictionaryMatch.go +++ b/tools/vendor/github.com/ccojocar/zxcvbn-go/matching/dictionaryMatch.go @@ -3,8 +3,8 @@ package matching import ( "strings" - "github.com/nbutton23/zxcvbn-go/entropy" - "github.com/nbutton23/zxcvbn-go/match" + "github.com/ccojocar/zxcvbn-go/entropy" + "github.com/ccojocar/zxcvbn-go/match" ) func buildDictMatcher(dictName string, rankedDict map[string]int) func(password string) []match.Match { @@ -15,7 +15,6 @@ func buildDictMatcher(dictName string, rankedDict map[string]int) func(password } return matches } - } func dictionaryMatch(password string, dictionaryName string, rankedDict map[string]int) []match.Match { @@ -29,7 +28,8 @@ func dictionaryMatch(password string, dictionaryName string, rankedDict map[stri for j := i; j < length; j++ { word := pwLowerRunes[i : j+1] if val, ok := rankedDict[string(word)]; ok { - matchDic := match.Match{Pattern: "dictionary", + matchDic := match.Match{ + Pattern: "dictionary", DictionaryName: dictionaryName, I: i, J: j, @@ -46,7 +46,6 @@ func dictionaryMatch(password string, dictionaryName string, rankedDict map[stri } func buildRankedDict(unrankedList []string) map[string]int { - result := make(map[string]int) for i, v := range unrankedList { diff --git a/tools/vendor/github.com/nbutton23/zxcvbn-go/matching/leet.go b/tools/vendor/github.com/ccojocar/zxcvbn-go/matching/leet.go similarity index 95% rename from tools/vendor/github.com/nbutton23/zxcvbn-go/matching/leet.go rename to tools/vendor/github.com/ccojocar/zxcvbn-go/matching/leet.go index 610f1973fc..1f303aa6ea 100644 --- a/tools/vendor/github.com/nbutton23/zxcvbn-go/matching/leet.go +++ b/tools/vendor/github.com/ccojocar/zxcvbn-go/matching/leet.go @@ -3,14 +3,14 @@ package matching import ( "strings" - "github.com/nbutton23/zxcvbn-go/entropy" - "github.com/nbutton23/zxcvbn-go/match" + "github.com/ccojocar/zxcvbn-go/entropy" + "github.com/ccojocar/zxcvbn-go/match" ) // L33TMatcherName id const L33TMatcherName = "l33t" -//FilterL33tMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher +// FilterL33tMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher func FilterL33tMatcher(m match.Matcher) bool { return m.ID == L33TMatcherName } @@ -105,7 +105,7 @@ func createListOfMapsWithoutConflicts(table map[string][]string) []map[string][] return result } -// This function retrieves the list of values that appear for one or more keys. This is usefull to +// This function retrieves the list of values that appear for one or more keys. This is useful to // know which l33t chars can represent more than one letter. func retrieveConflictsListFromTable(table map[string][]string) []string { result := []string{} @@ -128,7 +128,7 @@ func retrieveConflictsListFromTable(table map[string][]string) []string { } // This function aims to create different maps for a given char if this char represents a conflict. -// If the specified char is not a conflit one, the same map will be returned. In scenarios which +// If the specified char is not a conflict one, the same map will be returned. In scenarios which // the provided char can not be found on map, an empty list will be returned. This function was // designed to be used on conflicts situations. func createDifferentMapsForLeetChar(table map[string][]string, leetChar string) []map[string][]string { @@ -158,7 +158,7 @@ func retrieveListOfKeysWithSpecificValueFromTable(table map[string][]string, val return result } -// This function returns a lsit of substitution map from a given table. Each map in the result will +// This function returns a list of substitution map from a given table. Each map in the result will // provide only one representation for each value. As an example, if the provided map contains the // values "@" and "4" in the possibilities to represent "a", two maps will be created where one // will contain "a" mapping to "@" and the other one will provide "a" mapping to "4". diff --git a/tools/vendor/github.com/nbutton23/zxcvbn-go/matching/matching.go b/tools/vendor/github.com/ccojocar/zxcvbn-go/matching/matching.go similarity index 87% rename from tools/vendor/github.com/nbutton23/zxcvbn-go/matching/matching.go rename to tools/vendor/github.com/ccojocar/zxcvbn-go/matching/matching.go index 4577db8a4f..c6948067bc 100644 --- a/tools/vendor/github.com/nbutton23/zxcvbn-go/matching/matching.go +++ b/tools/vendor/github.com/ccojocar/zxcvbn-go/matching/matching.go @@ -3,9 +3,9 @@ package matching import ( "sort" - "github.com/nbutton23/zxcvbn-go/adjacency" - "github.com/nbutton23/zxcvbn-go/frequency" - "github.com/nbutton23/zxcvbn-go/match" + "github.com/ccojocar/zxcvbn-go/adjacency" + "github.com/ccojocar/zxcvbn-go/frequency" + "github.com/ccojocar/zxcvbn-go/match" ) var ( @@ -23,8 +23,7 @@ func init() { // Omnimatch runs all matchers against the password func Omnimatch(password string, userInputs []string, filters ...func(match.Matcher) bool) (matches []match.Match) { - - //Can I run into the issue where nil is not equal to nil? + // Can I run into the issue where nil is not equal to nil? if dictionaryMatchers == nil || adjacencyGraphs == nil { loadFrequencyList() } @@ -51,7 +50,6 @@ func Omnimatch(password string, userInputs []string, filters ...func(match.Match } func loadFrequencyList() { - for n, list := range frequency.Lists { dictionaryMatchers = append(dictionaryMatchers, match.Matcher{MatchingFunc: buildDictMatcher(n, buildRankedDict(list.List)), ID: n}) } @@ -63,8 +61,8 @@ func loadFrequencyList() { adjacencyGraphs = append(adjacencyGraphs, adjacency.GraphMap["keypad"]) adjacencyGraphs = append(adjacencyGraphs, adjacency.GraphMap["macKeypad"]) - //l33tFilePath, _ := filepath.Abs("adjacency/L33t.json") - //L33T_TABLE = adjacency.GetAdjancencyGraphFromFile(l33tFilePath, "l33t") + // l33tFilePath, _ := filepath.Abs("adjacency/L33t.json") + // L33T_TABLE = adjacency.GetAdjancencyGraphFromFile(l33tFilePath, "l33t") sequences = make(map[string]string) sequences["lower"] = "abcdefghijklmnopqrstuvwxyz" @@ -78,5 +76,4 @@ func loadFrequencyList() { matchers = append(matchers, match.Matcher{MatchingFunc: l33tMatch, ID: L33TMatcherName}) matchers = append(matchers, match.Matcher{MatchingFunc: dateSepMatcher, ID: dateSepMatcherName}) matchers = append(matchers, match.Matcher{MatchingFunc: dateWithoutSepMatch, ID: dateWithOutSepMatcherName}) - } diff --git a/tools/vendor/github.com/nbutton23/zxcvbn-go/matching/repeatMatch.go b/tools/vendor/github.com/ccojocar/zxcvbn-go/matching/repeatMatch.go similarity index 74% rename from tools/vendor/github.com/nbutton23/zxcvbn-go/matching/repeatMatch.go rename to tools/vendor/github.com/ccojocar/zxcvbn-go/matching/repeatMatch.go index a93e459356..d52ba4254b 100644 --- a/tools/vendor/github.com/nbutton23/zxcvbn-go/matching/repeatMatch.go +++ b/tools/vendor/github.com/ccojocar/zxcvbn-go/matching/repeatMatch.go @@ -3,13 +3,13 @@ package matching import ( "strings" - "github.com/nbutton23/zxcvbn-go/entropy" - "github.com/nbutton23/zxcvbn-go/match" + "github.com/ccojocar/zxcvbn-go/entropy" + "github.com/ccojocar/zxcvbn-go/match" ) const repeatMatcherName = "REPEAT" -//FilterRepeatMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher +// FilterRepeatMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher func FilterRepeatMatcher(m match.Matcher) bool { return m.ID == repeatMatcherName } @@ -17,7 +17,7 @@ func FilterRepeatMatcher(m match.Matcher) bool { func repeatMatch(password string) []match.Match { var matches []match.Match - //Loop through password. if current == prev currentStreak++ else if currentStreak > 2 {buildMatch; currentStreak = 1} prev = current + // Loop through password. if current == prev currentStreak++ else if currentStreak > 2 {buildMatch; currentStreak = 1} prev = current var current, prev string currentStreak := 1 var i int @@ -29,9 +29,8 @@ func repeatMatch(password string) []match.Match { continue } - if strings.ToLower(current) == strings.ToLower(prev) { + if strings.EqualFold(current, prev) { currentStreak++ - } else if currentStreak > 2 { iPos := i - currentStreak jPos := i - 1 @@ -40,7 +39,8 @@ func repeatMatch(password string) []match.Match { I: iPos, J: jPos, Token: password[iPos : jPos+1], - DictionaryName: prev} + DictionaryName: prev, + } matchRepeat.Entropy = entropy.RepeatEntropy(matchRepeat) matches = append(matches, matchRepeat) currentStreak = 1 @@ -59,7 +59,8 @@ func repeatMatch(password string) []match.Match { I: iPos, J: jPos, Token: password[iPos : jPos+1], - DictionaryName: prev} + DictionaryName: prev, + } matchRepeat.Entropy = entropy.RepeatEntropy(matchRepeat) matches = append(matches, matchRepeat) } diff --git a/tools/vendor/github.com/nbutton23/zxcvbn-go/matching/sequenceMatch.go b/tools/vendor/github.com/ccojocar/zxcvbn-go/matching/sequenceMatch.go similarity index 88% rename from tools/vendor/github.com/nbutton23/zxcvbn-go/matching/sequenceMatch.go rename to tools/vendor/github.com/ccojocar/zxcvbn-go/matching/sequenceMatch.go index e0ed052293..6971945838 100644 --- a/tools/vendor/github.com/nbutton23/zxcvbn-go/matching/sequenceMatch.go +++ b/tools/vendor/github.com/ccojocar/zxcvbn-go/matching/sequenceMatch.go @@ -3,13 +3,13 @@ package matching import ( "strings" - "github.com/nbutton23/zxcvbn-go/entropy" - "github.com/nbutton23/zxcvbn-go/match" + "github.com/ccojocar/zxcvbn-go/entropy" + "github.com/ccojocar/zxcvbn-go/match" ) const sequenceMatcherName = "SEQ" -//FilterSequenceMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher +// FilterSequenceMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher func FilterSequenceMatcher(m match.Matcher) bool { return m.ID == sequenceMatcherName } @@ -64,10 +64,8 @@ func sequenceMatch(password string) []match.Match { matches = append(matches, matchSequence) } break - } else { - j++ } - + j++ } } i = j diff --git a/tools/vendor/github.com/nbutton23/zxcvbn-go/matching/spatialMatch.go b/tools/vendor/github.com/ccojocar/zxcvbn-go/matching/spatialMatch.go similarity index 59% rename from tools/vendor/github.com/nbutton23/zxcvbn-go/matching/spatialMatch.go rename to tools/vendor/github.com/ccojocar/zxcvbn-go/matching/spatialMatch.go index fd858f5d17..101ccea5e5 100644 --- a/tools/vendor/github.com/nbutton23/zxcvbn-go/matching/spatialMatch.go +++ b/tools/vendor/github.com/ccojocar/zxcvbn-go/matching/spatialMatch.go @@ -3,14 +3,14 @@ package matching import ( "strings" - "github.com/nbutton23/zxcvbn-go/adjacency" - "github.com/nbutton23/zxcvbn-go/entropy" - "github.com/nbutton23/zxcvbn-go/match" + "github.com/ccojocar/zxcvbn-go/adjacency" + "github.com/ccojocar/zxcvbn-go/entropy" + "github.com/ccojocar/zxcvbn-go/match" ) const spatialMatcherName = "SPATIAL" -//FilterSpatialMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher +// FilterSpatialMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher func FilterSpatialMatcher(m match.Matcher) bool { return m.ID == spatialMatcherName } @@ -25,39 +25,38 @@ func spatialMatch(password string) (matches []match.Match) { } func spatialMatchHelper(password string, graph adjacency.Graph) (matches []match.Match) { - for i := 0; i < len(password)-1; { j := i + 1 - lastDirection := -99 //an int that it should never be! + lastDirection := -99 // an int that it should never be! turns := 0 shiftedCount := 0 for { prevChar := password[j-1] found := false - foundDirection := -1 + var foundDirection int curDirection := -1 - //My graphs seem to be wrong. . . and where the hell is qwerty + // My graphs seem to be wrong. . . and where the hell is qwerty adjacents := graph.Graph[string(prevChar)] - //Consider growing pattern by one character if j hasn't gone over the edge + // Consider growing pattern by one character if j hasn't gone over the edge if j < len(password) { curChar := password[j] for _, adj := range adjacents { curDirection++ - if strings.Index(adj, string(curChar)) != -1 { + if strings.Contains(adj, string(curChar)) { found = true foundDirection = curDirection if strings.Index(adj, string(curChar)) == 1 { - //index 1 in the adjacency means the key is shifted, 0 means unshifted: A vs a, % vs 5, etc. - //for example, 'q' is adjacent to the entry '2@'. @ is shifted w/ index 1, 2 is unshifted. + // index 1 in the adjacency means the key is shifted, 0 means unshifted: A vs a, % vs 5, etc. + // for example, 'q' is adjacent to the entry '2@'. @ is shifted w/ index 1, 2 is unshifted. shiftedCount++ } if lastDirection != foundDirection { - //adding a turn is correct even in the initial case when last_direction is null: - //every spatial pattern starts with a turn. + // adding a turn is correct even in the initial case when last_direction is null: + // every spatial pattern starts with a turn. turns++ lastDirection = foundDirection } @@ -66,12 +65,12 @@ func spatialMatchHelper(password string, graph adjacency.Graph) (matches []match } } - //if the current pattern continued, extend j and try to grow again + // if the current pattern continued, extend j and try to grow again if found { j++ } else { - //otherwise push the pattern discovered so far, if any... - //don't consider length 1 or 2 chains. + // otherwise push the pattern discovered so far, if any... + // don't consider length 1 or 2 chains. if j-i > 2 { matchSpc := match.Match{Pattern: "spatial", I: i, J: j - 1, Token: password[i:j], DictionaryName: graph.Name} matchSpc.Entropy = entropy.SpatialEntropy(matchSpc, turns, shiftedCount) diff --git a/tools/vendor/github.com/nbutton23/zxcvbn-go/scoring/scoring.go b/tools/vendor/github.com/ccojocar/zxcvbn-go/scoring/scoring.go similarity index 84% rename from tools/vendor/github.com/nbutton23/zxcvbn-go/scoring/scoring.go rename to tools/vendor/github.com/ccojocar/zxcvbn-go/scoring/scoring.go index 4f68a6dca6..dbe3318848 100644 --- a/tools/vendor/github.com/nbutton23/zxcvbn-go/scoring/scoring.go +++ b/tools/vendor/github.com/ccojocar/zxcvbn-go/scoring/scoring.go @@ -2,11 +2,12 @@ package scoring import ( "fmt" - "github.com/nbutton23/zxcvbn-go/entropy" - "github.com/nbutton23/zxcvbn-go/match" - "github.com/nbutton23/zxcvbn-go/utils/math" "math" "sort" + + "github.com/ccojocar/zxcvbn-go/entropy" + "github.com/ccojocar/zxcvbn-go/match" + zxcvbnmath "github.com/ccojocar/zxcvbn-go/utils/math" ) const ( @@ -15,7 +16,7 @@ const ( //adjust for your site accordingly if you use another hash function, possibly by //several orders of magnitude! singleGuess float64 = 0.010 - numAttackers float64 = 100 //Cores used to make guesses + numAttackers float64 = 100 // Cores used to make guesses secondsPerGuess float64 = singleGuess / numAttackers ) @@ -33,11 +34,11 @@ type MinEntropyMatch struct { /* MinimumEntropyMatchSequence returns the minimum entropy - Takes a list of overlapping matches, returns the non-overlapping sublist with - minimum entropy. O(nm) dp alg for length-n password with m candidate matches. + Takes a list of overlapping matches, returns the non-overlapping sublist with + minimum entropy. O(nm) dp alg for length-n password with m candidate matches. */ func MinimumEntropyMatchSequence(password string, matches []match.Match) MinEntropyMatch { - bruteforceCardinality := float64(entropy.CalcBruteForceCardinality(password)) + bruteforceCardinality := entropy.CalcBruteForceCardinality(password) upToK := make([]float64, len(password)) backPointers := make([]match.Match, len(password)) @@ -50,7 +51,7 @@ func MinimumEntropyMatchSequence(password string, matches []match.Match) MinEntr } i, j := match.I, match.J - //see if best entropy up to i-1 + entropy of match is less that current min at j + // see if best entropy up to i-1 + entropy of match is less that current min at j upTo := get(upToK, i-1) candidateEntropy := upTo + match.Entropy @@ -62,7 +63,7 @@ func MinimumEntropyMatchSequence(password string, matches []match.Match) MinEntr } } - //walk backwards and decode the best sequence + // walk backwards and decode the best sequence var matchSequence []match.Match passwordLen := len(password) passwordLen-- @@ -80,12 +81,13 @@ func MinimumEntropyMatchSequence(password string, matches []match.Match) MinEntr sort.Sort(match.Matches(matchSequence)) makeBruteForceMatch := func(i, j int) match.Match { - return match.Match{Pattern: "bruteforce", + return match.Match{ + Pattern: "bruteforce", I: i, J: j, Token: password[i : j+1], - Entropy: math.Log2(math.Pow(bruteforceCardinality, float64(j-i)))} - + Entropy: math.Log2(math.Pow(bruteforceCardinality, float64(j-i))), + } } k := 0 @@ -110,14 +112,16 @@ func MinimumEntropyMatchSequence(password string, matches []match.Match) MinEntr } crackTime := roundToXDigits(entropyToCrackTime(minEntropy), 3) - return MinEntropyMatch{Password: password, + return MinEntropyMatch{ + Password: password, Entropy: roundToXDigits(minEntropy, 3), MatchSequence: matchSequenceCopy, CrackTime: crackTime, CrackTimeDisplay: displayTime(crackTime), - Score: crackTimeToScore(crackTime)} - + Score: crackTimeToScore(crackTime), + } } + func get(a []float64, i int) float64 { if i < 0 || i >= len(a) { return float64(0) diff --git a/tools/vendor/github.com/nbutton23/zxcvbn-go/utils/math/mathutils.go b/tools/vendor/github.com/ccojocar/zxcvbn-go/utils/math/mathutils.go similarity index 100% rename from tools/vendor/github.com/nbutton23/zxcvbn-go/utils/math/mathutils.go rename to tools/vendor/github.com/ccojocar/zxcvbn-go/utils/math/mathutils.go diff --git a/tools/vendor/github.com/nbutton23/zxcvbn-go/zxcvbn.go b/tools/vendor/github.com/ccojocar/zxcvbn-go/zxcvbn.go similarity index 76% rename from tools/vendor/github.com/nbutton23/zxcvbn-go/zxcvbn.go rename to tools/vendor/github.com/ccojocar/zxcvbn-go/zxcvbn.go index 9c34b1c8c0..f3dc19e4c5 100644 --- a/tools/vendor/github.com/nbutton23/zxcvbn-go/zxcvbn.go +++ b/tools/vendor/github.com/ccojocar/zxcvbn-go/zxcvbn.go @@ -3,10 +3,10 @@ package zxcvbn import ( "time" - "github.com/nbutton23/zxcvbn-go/match" - "github.com/nbutton23/zxcvbn-go/matching" - "github.com/nbutton23/zxcvbn-go/scoring" - "github.com/nbutton23/zxcvbn-go/utils/math" + "github.com/ccojocar/zxcvbn-go/match" + "github.com/ccojocar/zxcvbn-go/matching" + "github.com/ccojocar/zxcvbn-go/scoring" + zxcvbnmath "github.com/ccojocar/zxcvbn-go/utils/math" ) // PasswordStrength takes a password, userInputs and optional filters and returns a MinEntropyMatch diff --git a/tools/vendor/github.com/chavacava/garif/enums.go b/tools/vendor/github.com/chavacava/garif/enums.go new file mode 100644 index 0000000000..dea2daf131 --- /dev/null +++ b/tools/vendor/github.com/chavacava/garif/enums.go @@ -0,0 +1,41 @@ +package garif + +type ResultKind string + +// declare JSON values +const ( + _pass ResultKind = "pass" + _open ResultKind = "open" + _informational ResultKind = "informational" + _notApplicable ResultKind = "notApplicable" + _review ResultKind = "review" + _fail ResultKind = "fail" +) + +// create public visible constants with a namespace as enums +const ( + ResultKind_Pass ResultKind = _pass + ResultKind_Open ResultKind = _open + ResultKind_Informational ResultKind = _informational + ResultKind_NotApplicable ResultKind = _notApplicable + ResultKind_Review ResultKind = _review + ResultKind_Fail ResultKind = _fail +) + +type ResultLevel string + +// declare JSON values +const ( + _warning ResultLevel = "warning" + _error ResultLevel = "error" + _note ResultLevel = "note" + _none ResultLevel = "none" +) + +// create public visible constants with a namespace as enums +const ( + ResultLevel_Warning ResultLevel = _warning + ResultLevel_Error ResultLevel = _error + ResultLevel_Note ResultLevel = _note + ResultLevel_None ResultLevel = _none +) diff --git a/tools/vendor/github.com/chavacava/garif/models.go b/tools/vendor/github.com/chavacava/garif/models.go index 3668436a3c..f16a86136e 100644 --- a/tools/vendor/github.com/chavacava/garif/models.go +++ b/tools/vendor/github.com/chavacava/garif/models.go @@ -935,10 +935,10 @@ type Result struct { HostedViewerUri string `json:"hostedViewerUri,omitempty"` // A value that categorizes results by evaluation state. - Kind interface{} `json:"kind,omitempty"` + Kind ResultKind `json:"kind,omitempty"` // A value specifying the severity level of the result. - Level interface{} `json:"level,omitempty"` + Level ResultLevel `json:"level,omitempty"` // The set of locations where the result was detected. Specify only one location unless the problem indicated by the result can only be corrected by making a change at every specified location. Locations []*Location `json:"locations,omitempty"` diff --git a/tools/vendor/github.com/daixiang0/gci/pkg/config/config.go b/tools/vendor/github.com/daixiang0/gci/pkg/config/config.go index b32148b189..98513c056a 100644 --- a/tools/vendor/github.com/daixiang0/gci/pkg/config/config.go +++ b/tools/vendor/github.com/daixiang0/gci/pkg/config/config.go @@ -1,7 +1,6 @@ package config import ( - "io/ioutil" "sort" "strings" @@ -23,6 +22,7 @@ type BoolConfig struct { NoPrefixComments bool `yaml:"no-prefixComments"` Debug bool `yaml:"-"` SkipGenerated bool `yaml:"skipGenerated"` + SkipVendor bool `yaml:"skipVendor"` CustomOrder bool `yaml:"customOrder"` } @@ -72,19 +72,18 @@ func (g YamlConfig) Parse() (*Config, error) { return &Config{g.Cfg, sections, sectionSeparators}, nil } -func InitializeGciConfigFromYAML(filePath string) (*Config, error) { +func ParseConfig(in string) (*Config, error) { config := YamlConfig{} - yamlData, err := ioutil.ReadFile(filePath) - if err != nil { - return nil, err - } - err = yaml.Unmarshal(yamlData, &config) + + err := yaml.Unmarshal([]byte(in), &config) if err != nil { return nil, err } + gciCfg, err := config.Parse() if err != nil { return nil, err } + return gciCfg, nil } diff --git a/tools/vendor/github.com/daixiang0/gci/pkg/gci/gci.go b/tools/vendor/github.com/daixiang0/gci/pkg/gci/gci.go index 0fd7a0ec66..163e95a861 100644 --- a/tools/vendor/github.com/daixiang0/gci/pkg/gci/gci.go +++ b/tools/vendor/github.com/daixiang0/gci/pkg/gci/gci.go @@ -49,6 +49,16 @@ func WriteFormattedFiles(paths []string, cfg config.Config) error { }) } +func ListUnFormattedFiles(paths []string, cfg config.Config) error { + return processGoFilesInPaths(paths, cfg, func(filePath string, unmodifiedFile, formattedFile []byte) error { + if bytes.Equal(unmodifiedFile, formattedFile) { + return nil + } + fmt.Println(filePath) + return nil + }) +} + func DiffFormattedFiles(paths []string, cfg config.Config) error { return processStdInAndGoFilesInPaths(paths, cfg, func(filePath string, unmodifiedFile, formattedFile []byte) error { fileURI := span.URIFromPath(filePath) @@ -76,11 +86,11 @@ func DiffFormattedFilesToArray(paths []string, cfg config.Config, diffs *[]strin type fileFormattingFunc func(filePath string, unmodifiedFile, formattedFile []byte) error func processStdInAndGoFilesInPaths(paths []string, cfg config.Config, fileFunc fileFormattingFunc) error { - return ProcessFiles(io.StdInGenerator.Combine(io.GoFilesInPathsGenerator(paths)), cfg, fileFunc) + return ProcessFiles(io.StdInGenerator.Combine(io.GoFilesInPathsGenerator(paths, cfg.SkipVendor)), cfg, fileFunc) } func processGoFilesInPaths(paths []string, cfg config.Config, fileFunc fileFormattingFunc) error { - return ProcessFiles(io.GoFilesInPathsGenerator(paths), cfg, fileFunc) + return ProcessFiles(io.GoFilesInPathsGenerator(paths, cfg.SkipVendor), cfg, fileFunc) } func ProcessFiles(fileGenerator io.FileGeneratorFunc, cfg config.Config, fileFunc fileFormattingFunc) error { @@ -117,11 +127,17 @@ func LoadFormatGoFile(file io.FileObj, cfg config.Config) (src, dist []byte, err return nil, nil, err } + return LoadFormat(src, file.Path(), cfg) +} + +func LoadFormat(in []byte, path string, cfg config.Config) (src, dist []byte, err error) { + src = in + if cfg.SkipGenerated && parse.IsGeneratedFileByComment(string(src)) { return src, src, nil } - imports, headEnd, tailStart, cStart, cEnd, err := parse.ParseFile(src, file.Path()) + imports, headEnd, tailStart, cStart, cEnd, err := parse.ParseFile(src, path) if err != nil { if errors.Is(err, parse.NoImportError{}) { return src, src, nil @@ -191,6 +207,10 @@ func LoadFormatGoFile(file io.FileObj, cfg config.Config) (src, dist []byte, err for _, s := range slices { i += copy(dist[i:], s) } + + // remove ^M(\r\n) from Win to Unix + dist = bytes.ReplaceAll(dist, []byte{utils.WinLinebreak}, []byte{utils.Linebreak}) + log.L().Debug(fmt.Sprintf("raw:\n%s", dist)) dist, err = goFormat.Source(dist) if err != nil { diff --git a/tools/vendor/github.com/daixiang0/gci/pkg/gci/testdata.go b/tools/vendor/github.com/daixiang0/gci/pkg/gci/testdata.go new file mode 100644 index 0000000000..a48ce356c8 --- /dev/null +++ b/tools/vendor/github.com/daixiang0/gci/pkg/gci/testdata.go @@ -0,0 +1,1245 @@ +package gci + +type Cases struct { + name, config, in, out string +} + +var commonConfig = `sections: + - Standard + - Default + - Prefix(github.com/daixiang0) +` + +var testCases = []Cases{ + { + "already-good", + + commonConfig, + + `package main + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/gci" +) +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/gci" +) +`, + }, + { + "blank-format", + + commonConfig, + + `package main +import ( + "fmt" + + // comment + g "github.com/golang" // comment + + "github.com/daixiang0/gci" +) +`, + `package main + +import ( + "fmt" + + // comment + g "github.com/golang" // comment + + "github.com/daixiang0/gci" +) +`, + }, + { + "cgo-block", + + commonConfig, + + `package main + +import ( + /* + #include "types.h" + */ + "C" +) +`, + `package main + +import ( + /* + #include "types.h" + */ + "C" +) +`, + }, + { + "cgo-block-after-import", + + commonConfig, + + `package main + +import ( + "fmt" + + "github.com/daixiang0/gci" + g "github.com/golang" +) + +// #cgo CFLAGS: -DPNG_DEBUG=1 +// #cgo amd64 386 CFLAGS: -DX86=1 +// #cgo LDFLAGS: -lpng +// #include +import "C" +`, + `package main + +// #cgo CFLAGS: -DPNG_DEBUG=1 +// #cgo amd64 386 CFLAGS: -DX86=1 +// #cgo LDFLAGS: -lpng +// #include +import "C" + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/gci" +) +`, + }, + { + "cgo-block-before-import", + + commonConfig, + + `package main + +// #cgo CFLAGS: -DPNG_DEBUG=1 +// #cgo amd64 386 CFLAGS: -DX86=1 +// #cgo LDFLAGS: -lpng +// #include +import "C" + +import ( + "fmt" + + "github.com/daixiang0/gci" + + g "github.com/golang" +) +`, + `package main + +// #cgo CFLAGS: -DPNG_DEBUG=1 +// #cgo amd64 386 CFLAGS: -DX86=1 +// #cgo LDFLAGS: -lpng +// #include +import "C" + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/gci" +) +`, + }, + { + "cgo-block-mixed", + + commonConfig, + + `package main + +import ( + /* #include "types.h" + */"C" +) +`, + `package main + +import ( + /* #include "types.h" + */"C" +) +`, + }, + { + "cgo-block-mixed-with-content", + + commonConfig, + + `package main + +import ( + /* #include "types.h" + #include "other.h" */"C" +) +`, + `package main + +import ( + /* #include "types.h" + #include "other.h" */"C" +) +`, + }, + { + "cgo-block-prefix", + + commonConfig, + + `package main + +import ( + /* #include "types.h" */ "C" +) +`, + `package main + +import ( + /* #include "types.h" */ "C" +) +`, + }, + { + "cgo-block-single-line", + + commonConfig, + + `package main + +import ( + /* #include "types.h" */ + "C" +) +`, + `package main + +import ( + /* #include "types.h" */ + "C" +) +`, + }, + { + "cgo-line", + + commonConfig, + + `package main + +import ( + // #include "types.h" + "C" +) +`, + `package main + +import ( + // #include "types.h" + "C" +) +`, + }, + { + "cgo-multiline", + + commonConfig, + + `package main + +import ( + // #include "types.h" + // #include "other.h" + "C" +) +`, + `package main + +import ( + // #include "types.h" + // #include "other.h" + "C" +) +`, + }, + { + "cgo-single", + + commonConfig, + + `package main + +import ( + "fmt" + + "github.com/daixiang0/gci" +) + +import "C" + +import "github.com/golang" + +import ( + "github.com/daixiang0/gci" +) +`, + `package main + +import "C" + +import ( + "fmt" + + "github.com/golang" + + "github.com/daixiang0/gci" +) +`, + }, + { + "comment", + + commonConfig, + + `package main +import ( + //Do not forget to run Gci + "fmt" +) +`, + `package main +import ( + //Do not forget to run Gci + "fmt" +) +`, + }, + { + "comment-before-import", + + commonConfig, + + `package main + +// comment +import ( + "fmt" + "os" + + "github.com/daixiang0/gci" +) +`, + `package main + +// comment +import ( + "fmt" + "os" + + "github.com/daixiang0/gci" +) +`, + }, + { + "comment-in-the-tail", + + `sections: + - Standard + - Default + - Prefix(github.com/daixiang0) +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/gci" +) + +type test int + +// test +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/gci" +) + +type test int + +// test +`, + }, + { + "comment-top", + + commonConfig, + + `package main + +import ( + "os" // https://pkg.go.dev/os + // https://pkg.go.dev/fmt + "fmt" +) +`, + `package main + +import ( + // https://pkg.go.dev/fmt + "fmt" + "os" // https://pkg.go.dev/os +) +`, + }, + { + "comment-whithout-whitespace", + + commonConfig, + + `package proc + +import ( + "context"// no separating whitespace here //nolint:confusion +) +`, + `package proc + +import ( + "context"// no separating whitespace here //nolint:confusion +) +`, + }, + { + "comment-with-slashslash", + + commonConfig, + + `package main + +import ( + "fmt" // https://pkg.go.dev/fmt +) +`, + `package main + +import ( + "fmt" // https://pkg.go.dev/fmt +) +`, + }, + { + "custom-order", + + `customOrder: true +sections: + - Prefix(github.com/daixiang0) + - Default + - Standard +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/a" +) +`, + `package main + +import ( + "github.com/daixiang0/a" + + g "github.com/golang" + + "fmt" +) +`, + }, + { + "default-order", + + `sections: + - Standard + - Prefix(github.com/daixiang0) + - Default +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/a" +) +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/a" +) +`, + }, + { + "dot-and-blank", + + `sections: + - Standard + - Default + - Prefix(github.com/daixiang0) + - Blank + - Dot +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + . "github.com/golang/dot" + _ "github.com/golang/blank" + + "github.com/daixiang0/a" + "github.com/daixiang0/gci" + "github.com/daixiang0/gci/subtest" + . "github.com/daixiang0/gci/dot" + _ "github.com/daixiang0/gci/blank" +) +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/a" + "github.com/daixiang0/gci" + "github.com/daixiang0/gci/subtest" + + _ "github.com/daixiang0/gci/blank" + _ "github.com/golang/blank" + + . "github.com/daixiang0/gci/dot" + . "github.com/golang/dot" +) +`, + }, + { + "duplicate-imports", + + `sections: + - Standard + - Default + - Prefix(github.com/daixiang0) +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + a "github.com/daixiang0/gci" + "github.com/daixiang0/gci" +) +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/gci" + a "github.com/daixiang0/gci" +) +`, + }, + { + "grouped-multiple-custom", + + `sections: + - Standard + - Default + - Prefix(github.com/daixiang0,gitlab.com/daixiang0,daixiang0) +`, + `package main + +import ( + "daixiang0/lib1" + "fmt" + "github.com/daixiang0/gci" + "gitlab.com/daixiang0/gci" + g "github.com/golang" + "github.com/daixiang0/gci/subtest" +) +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + "daixiang0/lib1" + "github.com/daixiang0/gci" + "github.com/daixiang0/gci/subtest" + "gitlab.com/daixiang0/gci" +) +`, + }, + { + "leading-comment", + + commonConfig, + + `package main + +import ( + // foo + "fmt" +) +`, + `package main + +import ( + // foo + "fmt" +) +`, + }, + { + "linebreak", + + `sections: + - Standard + - Default + - Prefix(github.com/daixiang0) +`, + `package main + +import ( + g "github.com/golang" + + "fmt" + + "github.com/daixiang0/gci" + +) +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/gci" +) +`, + }, + { + "linebreak-no-custom", + + `sections: + - Standard + - Default + - Prefix(github.com/daixiang0) +`, + `package main + +import ( + g "github.com/golang" + + "fmt" + +) +`, + `package main + +import ( + "fmt" + + g "github.com/golang" +) +`, + }, + { + "mismatch-section", + + `sections: + - Standard + - Default + - Prefix(github.com/daixiang0) + - Prefix(github.com/daixiang0/gci) +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/gci" +) +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/gci" +) +`, + }, + { + "multiple-custom", + + `sections: + - Standard + - Default + - Prefix(github.com/daixiang0) + - Prefix(github.com/daixiang0/gci) + - Prefix(github.com/daixiang0/gci/subtest) +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/a" + "github.com/daixiang0/gci" + "github.com/daixiang0/gci/subtest" +) +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/a" + + "github.com/daixiang0/gci" + + "github.com/daixiang0/gci/subtest" +) +`, + }, + { + "multiple-imports", + + commonConfig, + + `package main + +import "fmt" + +import "context" + +import ( + "os" + + "github.com/daixiang0/test" +) + +import "math" + + +// main +func main() { +} +`, + `package main + +import ( + "context" + "fmt" + "math" + "os" + + "github.com/daixiang0/test" +) + +// main +func main() { +} +`, + }, + { + "multiple-line-comment", + + commonConfig, + + `package proc + +import ( + "context" // in-line comment + "fmt" + "os" + + //nolint:depguard // A multi-line comment explaining why in + // this one case it's OK to use os/exec even though depguard + // is configured to force us to use dlib/exec instead. + "os/exec" + + "golang.org/x/sys/unix" + "github.com/local/dlib/dexec" +) +`, + `package proc + +import ( + "context" // in-line comment + "fmt" + "os" + //nolint:depguard // A multi-line comment explaining why in + // this one case it's OK to use os/exec even though depguard + // is configured to force us to use dlib/exec instead. + "os/exec" + + "github.com/local/dlib/dexec" + "golang.org/x/sys/unix" +) +`, + }, + { + "nochar-after-import", + + commonConfig, + + `package main + +import ( + "fmt" +) +`, + `package main + +import ( + "fmt" +) +`, + }, + { + "no-format", + + commonConfig, + + `package main + +import( +"fmt" + +g "github.com/golang" + +"github.com/daixiang0/gci" +) +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/gci" +) +`, + }, + { + "nolint", + + commonConfig, + + `package main + +import ( + "fmt" + + "github.com/forbidden/pkg" //nolint:depguard + + _ "github.com/daixiang0/gci" //nolint:depguard +) +`, + `package main + +import ( + "fmt" + + "github.com/forbidden/pkg" //nolint:depguard + + _ "github.com/daixiang0/gci" //nolint:depguard +) +`, + }, + { + "number-in-alias", + + commonConfig, + + `package main + +import ( + "fmt" + + go_V1 "github.com/golang" + + "github.com/daixiang0/gci" +) +`, + `package main + +import ( + "fmt" + + go_V1 "github.com/golang" + + "github.com/daixiang0/gci" +) +`, + }, + { + "one-import", + + commonConfig, + + `package main +import ( + "fmt" +) + +func main() { +} +`, + `package main +import ( + "fmt" +) + +func main() { +} +`, + }, + { + "one-import-one-line", + + commonConfig, + + `package main + +import "fmt" + +func main() { +} +`, + `package main + +import "fmt" + +func main() { +} +`, + }, + { + "one-line-import-after-import", + + `sections: + - Standard + - Default + - Prefix(github.com/daixiang0) +`, + `package main + +import ( + "fmt" + "os" + + "github.com/daixiang0/test" +) + +import "context" +`, + `package main + +import ( + "context" + "fmt" + "os" + + "github.com/daixiang0/test" +) +`, + }, + { + "same-prefix-custom", + + `sections: + - Standard + - Default + - Prefix(github.com/daixiang0/gci) + - Prefix(github.com/daixiang0/gci/subtest) +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/gci" + "github.com/daixiang0/gci/subtest" +) +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/gci" + + "github.com/daixiang0/gci/subtest" +) +`, + }, + { + "simple-case", + + commonConfig, + + `package main + +import ( + "golang.org/x/tools" + + "fmt" + + "github.com/daixiang0/gci" +) +`, + `package main + +import ( + "fmt" + + "golang.org/x/tools" + + "github.com/daixiang0/gci" +) +`, + }, + { + "whitespace-test", + + commonConfig, + + `package main + +import ( + "fmt" + "github.com/golang" // golang + alias "github.com/daixiang0/gci" +) +`, + `package main + +import ( + "fmt" + + "github.com/golang" // golang + + alias "github.com/daixiang0/gci" +) +`, + }, + { + "with-above-comment-and-alias", + + commonConfig, + + `package main + +import ( + "fmt" + // golang + _ "github.com/golang" + "github.com/daixiang0/gci" +) +`, + `package main + +import ( + "fmt" + + // golang + _ "github.com/golang" + + "github.com/daixiang0/gci" +) +`, + }, + { + "with-comment-and-alias", + + commonConfig, + + `package main + +import ( + "fmt" + _ "github.com/golang" // golang + "github.com/daixiang0/gci" +) +`, + `package main + +import ( + "fmt" + + _ "github.com/golang" // golang + + "github.com/daixiang0/gci" +) +`, + }, + { + "same-prefix-custom", + + `sections: + - Standard + - Default + - Prefix(github.com/daixiang0/gci) + - Prefix(github.com/daixiang0/gci/subtest) +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/gci" + "github.com/daixiang0/gci/subtest" +) +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/gci" + + "github.com/daixiang0/gci/subtest" +) +`, + }, + { + "same-prefix-custom", + + `sections: + - Standard + - Default + - Prefix(github.com/daixiang0/gci) + - Prefix(github.com/daixiang0/gci/subtest) +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/gci" + "github.com/daixiang0/gci/subtest" +) +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/gci" + + "github.com/daixiang0/gci/subtest" +) +`, + }, + { + "blank-in-config", + + `sections: + - Standard + - Default + - Prefix( github.com/daixiang0/gci, github.com/daixiang0/gci/subtest ) +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/gci" + "github.com/daixiang0/gci/subtest" +) +`, + `package main + +import ( + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/gci" + "github.com/daixiang0/gci/subtest" +) +`, + }, +} diff --git a/tools/vendor/github.com/daixiang0/gci/pkg/io/file.go b/tools/vendor/github.com/daixiang0/gci/pkg/io/file.go index f92d16e141..79950792ca 100644 --- a/tools/vendor/github.com/daixiang0/gci/pkg/io/file.go +++ b/tools/vendor/github.com/daixiang0/gci/pkg/io/file.go @@ -39,8 +39,13 @@ func (a FileGeneratorFunc) Combine(b FileGeneratorFunc) FileGeneratorFunc { } } -func GoFilesInPathsGenerator(paths []string) FileGeneratorFunc { - return FilesInPathsGenerator(paths, isGoFile) +func GoFilesInPathsGenerator(paths []string, skipVendor bool) FileGeneratorFunc { + checkFunc := isGoFile + if skipVendor { + checkFunc = checkChains(isGoFile, isOutsideVendorDir) + } + + return FilesInPathsGenerator(paths, checkFunc) } func FilesInPathsGenerator(paths []string, fileCheckFun fileCheckFunction) FileGeneratorFunc { diff --git a/tools/vendor/github.com/daixiang0/gci/pkg/io/search.go b/tools/vendor/github.com/daixiang0/gci/pkg/io/search.go index 04f0058761..cd821582e7 100644 --- a/tools/vendor/github.com/daixiang0/gci/pkg/io/search.go +++ b/tools/vendor/github.com/daixiang0/gci/pkg/io/search.go @@ -6,7 +6,7 @@ import ( "path/filepath" ) -type fileCheckFunction func(file os.FileInfo) bool +type fileCheckFunction func(path string, file os.FileInfo) bool func FindFilesForPath(path string, fileCheckFun fileCheckFunction) ([]string, error) { switch entry, err := os.Stat(path); { @@ -14,7 +14,7 @@ func FindFilesForPath(path string, fileCheckFun fileCheckFunction) ([]string, er return nil, err case entry.IsDir(): return findFilesForDirectory(path, fileCheckFun) - case fileCheckFun(entry): + case fileCheckFun(path, entry): return []string{filepath.Clean(path)}, nil default: return []string{}, nil @@ -31,7 +31,7 @@ func findFilesForDirectory(dirPath string, fileCheckFun fileCheckFunction) ([]st if err != nil { return err } - if !entry.IsDir() && fileCheckFun(file) { + if !entry.IsDir() && fileCheckFun(path, file) { filePaths = append(filePaths, filepath.Clean(path)) } return nil @@ -42,6 +42,36 @@ func findFilesForDirectory(dirPath string, fileCheckFun fileCheckFunction) ([]st return filePaths, nil } -func isGoFile(file os.FileInfo) bool { +func isGoFile(_ string, file os.FileInfo) bool { return !file.IsDir() && filepath.Ext(file.Name()) == ".go" } + +func isOutsideVendorDir(path string, _ os.FileInfo) bool { + for { + base := filepath.Base(path) + if base == "vendor" { + return false + } + + prevPath := path + path = filepath.Dir(path) + + if prevPath == path { + break + } + } + + return true +} + +func checkChains(funcs ...fileCheckFunction) fileCheckFunction { + return func(path string, file os.FileInfo) bool { + for _, checkFunc := range funcs { + if !checkFunc(path, file) { + return false + } + } + + return true + } +} diff --git a/tools/vendor/github.com/daixiang0/gci/pkg/section/prefix.go b/tools/vendor/github.com/daixiang0/gci/pkg/section/prefix.go index a274347cdd..30bdd8f4ea 100644 --- a/tools/vendor/github.com/daixiang0/gci/pkg/section/prefix.go +++ b/tools/vendor/github.com/daixiang0/gci/pkg/section/prefix.go @@ -20,6 +20,7 @@ const CustomType = "custom" func (c Custom) MatchSpecificity(spec *parse.GciImports) specificity.MatchSpecificity { for _, prefix := range strings.Split(c.Prefix, CustomSeparator) { + prefix = strings.TrimSpace(prefix) if strings.HasPrefix(spec.Path, prefix) { return specificity.Match{Length: len(prefix)} } diff --git a/tools/vendor/github.com/daixiang0/gci/pkg/section/standard_list.go b/tools/vendor/github.com/daixiang0/gci/pkg/section/standard_list.go index f0e904d4d1..05e7999396 100644 --- a/tools/vendor/github.com/daixiang0/gci/pkg/section/standard_list.go +++ b/tools/vendor/github.com/daixiang0/gci/pkg/section/standard_list.go @@ -1,12 +1,14 @@ package section -// Code generated based on go1.20.1. DO NOT EDIT. +// Code generated based on go1.21.0 X:arenas. DO NOT EDIT. var standardPackages = map[string]struct{}{ "archive/tar": {}, "archive/zip": {}, + "arena": {}, "bufio": {}, "bytes": {}, + "cmp": {}, "compress/bzip2": {}, "compress/flate": {}, "compress/gzip": {}, @@ -96,7 +98,9 @@ var standardPackages = map[string]struct{}{ "io/fs": {}, "io/ioutil": {}, "log": {}, + "log/slog": {}, "log/syslog": {}, + "maps": {}, "math": {}, "math/big": {}, "math/bits": {}, @@ -139,6 +143,7 @@ var standardPackages = map[string]struct{}{ "runtime/pprof": {}, "runtime/race": {}, "runtime/trace": {}, + "slices": {}, "sort": {}, "strconv": {}, "strings": {}, @@ -149,6 +154,7 @@ var standardPackages = map[string]struct{}{ "testing/fstest": {}, "testing/iotest": {}, "testing/quick": {}, + "testing/slogtest": {}, "text/scanner": {}, "text/tabwriter": {}, "text/template": {}, diff --git a/tools/vendor/github.com/daixiang0/gci/pkg/utils/constants.go b/tools/vendor/github.com/daixiang0/gci/pkg/utils/constants.go index 0e7cce7576..2fafbc32cc 100644 --- a/tools/vendor/github.com/daixiang0/gci/pkg/utils/constants.go +++ b/tools/vendor/github.com/daixiang0/gci/pkg/utils/constants.go @@ -1,8 +1,9 @@ package utils const ( - Indent = '\t' - Linebreak = '\n' + Indent = '\t' + Linebreak = '\n' + WinLinebreak = '\r' Colon = ":" diff --git a/tools/vendor/github.com/ghostiam/protogetter/.goreleaser.yaml b/tools/vendor/github.com/ghostiam/protogetter/.goreleaser.yaml new file mode 100644 index 0000000000..a70d0fb006 --- /dev/null +++ b/tools/vendor/github.com/ghostiam/protogetter/.goreleaser.yaml @@ -0,0 +1,24 @@ +before: + hooks: + - go mod tidy +builds: + - id: protogetter + main: ./cmd/protogetter + binary: protogetter + env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin +checksum: + name_template: 'checksums.txt' +snapshot: + name_template: "{{ incpatch .Version }}-next" +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' + - '^ci:' \ No newline at end of file diff --git a/tools/vendor/github.com/ghostiam/protogetter/LICENSE b/tools/vendor/github.com/ghostiam/protogetter/LICENSE new file mode 100644 index 0000000000..b4449661b7 --- /dev/null +++ b/tools/vendor/github.com/ghostiam/protogetter/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Vladislav Fursov (GhostIAm) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/tools/vendor/github.com/ghostiam/protogetter/Makefile b/tools/vendor/github.com/ghostiam/protogetter/Makefile new file mode 100644 index 0000000000..af4b62bdf4 --- /dev/null +++ b/tools/vendor/github.com/ghostiam/protogetter/Makefile @@ -0,0 +1,9 @@ +.PHONY: test +test: + cd testdata && make vendor + go test -v ./... + +.PHONY: install +install: + go install ./cmd/protogetter + @echo "Installed in $(shell which protogetter)" diff --git a/tools/vendor/github.com/ghostiam/protogetter/README.md b/tools/vendor/github.com/ghostiam/protogetter/README.md new file mode 100644 index 0000000000..c033e9597f --- /dev/null +++ b/tools/vendor/github.com/ghostiam/protogetter/README.md @@ -0,0 +1,73 @@ +# Protogetter +Welcome to the Protogetter project! + +## Overview +Protogetter is a linter developed specifically for Go programmers working with nested `protobuf` types.\ +It's designed to aid developers in preventing `invalid memory address or nil pointer dereference` errors arising from direct access of nested `protobuf` fields. + +When working with `protobuf`, it's quite common to have complex structures where a message field is contained within another message, which itself can be part of another message, and so on. +If these fields are accessed directly and some field in the call chain will not be initialized, it can result in application panic. + +Protogetter addresses this issue by suggesting use of getter methods for field access. + +## How does it work? +Protogetter analyzes your Go code and helps detect direct `protobuf` field accesses that could give rise to panic.\ +The linter suggests using getters: +```go +m.GetFoo().GetBar().GetBaz() +``` +instead of direct field access: +```go +m.Foo.Bar.Baz +``` + +And you will then only need to perform a nil check after the final call: +```go +if m.GetFoo().GetBar().GetBaz() != nil { + // Do something with m.GetFoo().GetBar().GetBaz() +} +``` +instead of: +```go +if m.Foo != nil { + if m.Foo.Bar != nil { + if m.Foo.Bar.Baz != nil { + // Do something with m.Foo.Bar.Baz + } + } +} +``` + +or use zero values: + +```go +// If one of the methods returns `nil` we will receive 0 instead of panic. +v := m.GetFoo().GetBar().GetBaz().GetInt() +``` + +instead of panic: + +```go +// If at least one structure in the chains is not initialized, we will get a panic. +v := m.Foo.Bar.Baz.Int +``` + +which simplifies the code and makes it more reliable. + +## Installation + +```bash +go install github.com/ghostiam/protogetter/cmd/protogetter@latest +``` + +## Usage + +To run the linter: +```bash +protogetter ./... +``` + +Or to apply suggested fixes directly: +```bash +protogetter --fix ./... +``` diff --git a/tools/vendor/github.com/ghostiam/protogetter/posfilter.go b/tools/vendor/github.com/ghostiam/protogetter/posfilter.go new file mode 100644 index 0000000000..82075ccb16 --- /dev/null +++ b/tools/vendor/github.com/ghostiam/protogetter/posfilter.go @@ -0,0 +1,65 @@ +package protogetter + +import ( + "go/token" +) + +type PosFilter struct { + positions map[token.Pos]struct{} + alreadyReplaced map[string]map[int][2]int // map[filename][line][start, end] +} + +func NewPosFilter() *PosFilter { + return &PosFilter{ + positions: make(map[token.Pos]struct{}), + alreadyReplaced: make(map[string]map[int][2]int), + } +} + +func (f *PosFilter) IsFiltered(pos token.Pos) bool { + _, ok := f.positions[pos] + return ok +} + +func (f *PosFilter) AddPos(pos token.Pos) { + f.positions[pos] = struct{}{} +} + +func (f *PosFilter) IsAlreadyReplaced(fset *token.FileSet, pos, end token.Pos) bool { + filePos := fset.Position(pos) + fileEnd := fset.Position(end) + + lines, ok := f.alreadyReplaced[filePos.Filename] + if !ok { + return false + } + + lineRange, ok := lines[filePos.Line] + if !ok { + return false + } + + if lineRange[0] <= filePos.Offset && fileEnd.Offset <= lineRange[1] { + return true + } + + return false +} + +func (f *PosFilter) AddAlreadyReplaced(fset *token.FileSet, pos, end token.Pos) { + filePos := fset.Position(pos) + fileEnd := fset.Position(end) + + lines, ok := f.alreadyReplaced[filePos.Filename] + if !ok { + lines = make(map[int][2]int) + f.alreadyReplaced[filePos.Filename] = lines + } + + lineRange, ok := lines[filePos.Line] + if ok && lineRange[0] <= filePos.Offset && fileEnd.Offset <= lineRange[1] { + return + } + + lines[filePos.Line] = [2]int{filePos.Offset, fileEnd.Offset} +} diff --git a/tools/vendor/github.com/ghostiam/protogetter/processor.go b/tools/vendor/github.com/ghostiam/protogetter/processor.go new file mode 100644 index 0000000000..445f136b85 --- /dev/null +++ b/tools/vendor/github.com/ghostiam/protogetter/processor.go @@ -0,0 +1,234 @@ +package protogetter + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "reflect" + "strings" +) + +type processor struct { + info *types.Info + filter *PosFilter + + to strings.Builder + from strings.Builder + err error +} + +func Process(info *types.Info, filter *PosFilter, n ast.Node) (*Result, error) { + p := &processor{ + info: info, + filter: filter, + } + + return p.process(n) +} + +func (c *processor) process(n ast.Node) (*Result, error) { + switch x := n.(type) { + case *ast.AssignStmt: + // Skip any assignment to the field. + for _, lhs := range x.Lhs { + c.filter.AddPos(lhs.Pos()) + } + + case *ast.IncDecStmt: + // Skip any increment/decrement to the field. + c.filter.AddPos(x.X.Pos()) + + case *ast.UnaryExpr: + if x.Op == token.AND { + // Skip all expressions when the field is used as a pointer. + // Because this is not direct reading, but most likely writing by pointer (for example like sql.Scan). + c.filter.AddPos(x.X.Pos()) + } + + case *ast.CallExpr: + f, ok := x.Fun.(*ast.SelectorExpr) + if !ok { + return &Result{}, nil + } + + if !isProtoMessage(c.info, f.X) { + return &Result{}, nil + } + + c.processInner(x) + + case *ast.SelectorExpr: + if !isProtoMessage(c.info, x.X) { + // If the selector is not on a proto message, skip it. + return &Result{}, nil + } + + c.processInner(x) + + default: + return nil, fmt.Errorf("not implemented for type: %s (%s)", reflect.TypeOf(x), formatNode(n)) + } + + if c.err != nil { + return nil, c.err + } + + return &Result{ + From: c.from.String(), + To: c.to.String(), + }, nil +} + +func (c *processor) processInner(expr ast.Expr) { + switch x := expr.(type) { + case *ast.Ident: + c.write(x.Name) + + case *ast.BasicLit: + c.write(x.Value) + + case *ast.UnaryExpr: + if x.Op == token.AND { + c.write(formatNode(x)) + return + } + + c.write(x.Op.String()) + c.processInner(x.X) + + case *ast.SelectorExpr: + c.processInner(x.X) + c.write(".") + + // If getter exists, use it. + if methodIsExists(c.info, x.X, "Get"+x.Sel.Name) { + c.writeFrom(x.Sel.Name) + c.writeTo("Get" + x.Sel.Name + "()") + return + } + + // If the selector is not a proto-message or the method has already been called, we leave it unchanged. + // This approach is significantly more efficient than verifying the presence of methods in all cases. + c.write(x.Sel.Name) + + case *ast.CallExpr: + c.processInner(x.Fun) + c.write("(") + for i, arg := range x.Args { + if i > 0 { + c.write(",") + } + c.processInner(arg) + } + c.write(")") + + case *ast.IndexExpr: + c.processInner(x.X) + c.write("[") + c.processInner(x.Index) + c.write("]") + + case *ast.BinaryExpr: + c.processInner(x.X) + c.write(x.Op.String()) + c.processInner(x.Y) + + case *ast.ParenExpr: + c.write("(") + c.processInner(x.X) + c.write(")") + + case *ast.StarExpr: + c.write("*") + c.processInner(x.X) + + case *ast.CompositeLit: + c.write(formatNode(x)) + + case *ast.TypeAssertExpr: + c.write(formatNode(x)) + + default: + c.err = fmt.Errorf("processInner: not implemented for type: %s", reflect.TypeOf(x)) + } +} + +func (c *processor) write(s string) { + c.writeTo(s) + c.writeFrom(s) +} + +func (c *processor) writeTo(s string) { + c.to.WriteString(s) +} + +func (c *processor) writeFrom(s string) { + c.from.WriteString(s) +} + +// Result contains source code (from) and suggested change (to) +type Result struct { + From string + To string +} + +func (r *Result) Skipped() bool { + // If from and to are the same, skip it. + return r.From == r.To +} + +func isProtoMessage(info *types.Info, expr ast.Expr) bool { + // First, we are checking for the presence of the ProtoReflect method which is currently being generated + // and corresponds to v2 version. + // https://pkg.go.dev/google.golang.org/protobuf@v1.31.0/proto#Message + const protoV2Method = "ProtoReflect" + ok := methodIsExists(info, expr, protoV2Method) + if ok { + return true + } + + // Afterwards, we are checking the ProtoMessage method. All the structures that implement the proto.Message interface + // have a ProtoMessage method and are proto-structures. This interface has been generated since version 1.0.0 and + // continues to exist for compatibility. + // https://pkg.go.dev/github.com/golang/protobuf/proto?utm_source=godoc#Message + const protoV1Method = "ProtoMessage" + ok = methodIsExists(info, expr, protoV1Method) + if ok { + // Since there is a protoc-gen-gogo generator that implements the proto.Message interface, but may not generate + // getters or generate from without checking for nil, so even if getters exist, we skip them. + const protocGenGoGoMethod = "MarshalToSizedBuffer" + return !methodIsExists(info, expr, protocGenGoGoMethod) + } + + return false +} + +func methodIsExists(info *types.Info, x ast.Expr, name string) bool { + if info == nil { + return false + } + + t := info.TypeOf(x) + if t == nil { + return false + } + + ptr, ok := t.Underlying().(*types.Pointer) + if ok { + t = ptr.Elem() + } + + named, ok := t.(*types.Named) + if !ok { + return false + } + + for i := 0; i < named.NumMethods(); i++ { + if named.Method(i).Name() == name { + return true + } + } + + return false +} diff --git a/tools/vendor/github.com/ghostiam/protogetter/protogetter.go b/tools/vendor/github.com/ghostiam/protogetter/protogetter.go new file mode 100644 index 0000000000..80a8296720 --- /dev/null +++ b/tools/vendor/github.com/ghostiam/protogetter/protogetter.go @@ -0,0 +1,183 @@ +package protogetter + +import ( + "bytes" + "fmt" + "go/ast" + "go/format" + "go/token" + "log" + "strings" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/ast/inspector" +) + +type Mode int + +const ( + StandaloneMode Mode = iota + GolangciLintMode +) + +const msgFormat = "avoid direct access to proto field %s, use %s instead" + +func NewAnalyzer() *analysis.Analyzer { + return &analysis.Analyzer{ + Name: "protogetter", + Doc: "Reports direct reads from proto message fields when getters should be used", + Run: func(pass *analysis.Pass) (any, error) { + Run(pass, StandaloneMode) + return nil, nil + }, + } +} + +func Run(pass *analysis.Pass, mode Mode) []Issue { + nodeTypes := []ast.Node{ + (*ast.AssignStmt)(nil), + (*ast.CallExpr)(nil), + (*ast.SelectorExpr)(nil), + (*ast.IncDecStmt)(nil), + (*ast.UnaryExpr)(nil), + } + + // Skip protoc-generated files. + var files []*ast.File + for _, f := range pass.Files { + if !isProtocGeneratedFile(f) { + files = append(files, f) + + // ast.Print(pass.Fset, f) + } + } + + ins := inspector.New(files) + + var issues []Issue + + filter := NewPosFilter() + ins.Preorder(nodeTypes, func(node ast.Node) { + report := analyse(pass, filter, node) + if report == nil { + return + } + + switch mode { + case StandaloneMode: + pass.Report(report.ToDiagReport()) + case GolangciLintMode: + issues = append(issues, report.ToIssue(pass.Fset)) + } + }) + + return issues +} + +func analyse(pass *analysis.Pass, filter *PosFilter, n ast.Node) *Report { + // fmt.Printf("\n>>> check: %s\n", formatNode(n)) + // ast.Print(pass.Fset, n) + if filter.IsFiltered(n.Pos()) { + // fmt.Printf(">>> filtered\n") + return nil + } + + result, err := Process(pass.TypesInfo, filter, n) + if err != nil { + pass.Report(analysis.Diagnostic{ + Pos: n.Pos(), + End: n.End(), + Message: fmt.Sprintf("error: %v", err), + }) + + return nil + } + + // If existing in filter, skip it. + if filter.IsFiltered(n.Pos()) { + return nil + } + + if result.Skipped() { + return nil + } + + // If the expression has already been replaced, skip it. + if filter.IsAlreadyReplaced(pass.Fset, n.Pos(), n.End()) { + return nil + } + // Add the expression to the filter. + filter.AddAlreadyReplaced(pass.Fset, n.Pos(), n.End()) + + return &Report{ + node: n, + result: result, + } +} + +// Issue is used to integrate with golangci-lint's inline auto fix. +type Issue struct { + Pos token.Position + Message string + InlineFix InlineFix +} + +type InlineFix struct { + StartCol int // zero-based + Length int + NewString string +} + +type Report struct { + node ast.Node + result *Result +} + +func (r *Report) ToDiagReport() analysis.Diagnostic { + msg := fmt.Sprintf(msgFormat, r.result.From, r.result.To) + + return analysis.Diagnostic{ + Pos: r.node.Pos(), + End: r.node.End(), + Message: msg, + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: msg, + TextEdits: []analysis.TextEdit{ + { + Pos: r.node.Pos(), + End: r.node.End(), + NewText: []byte(r.result.To), + }, + }, + }, + }, + } +} + +func (r *Report) ToIssue(fset *token.FileSet) Issue { + msg := fmt.Sprintf(msgFormat, r.result.From, r.result.To) + return Issue{ + Pos: fset.Position(r.node.Pos()), + Message: msg, + InlineFix: InlineFix{ + StartCol: fset.Position(r.node.Pos()).Column - 1, + Length: len(r.result.From), + NewString: r.result.To, + }, + } +} + +func isProtocGeneratedFile(f *ast.File) bool { + return len(f.Comments) > 0 && strings.HasPrefix(f.Comments[0].Text(), "Code generated by protoc-gen-go") +} + +func formatNode(node ast.Node) string { + buf := new(bytes.Buffer) + if err := format.Node(buf, token.NewFileSet(), node); err != nil { + log.Printf("Error formatting expression: %v", err) + return "" + } + + return buf.String() +} diff --git a/tools/vendor/github.com/go-critic/go-critic/linter/linter.go b/tools/vendor/github.com/go-critic/go-critic/linter/linter.go index 27e9b659fb..d4bc17536e 100644 --- a/tools/vendor/github.com/go-critic/go-critic/linter/linter.go +++ b/tools/vendor/github.com/go-critic/go-critic/linter/linter.go @@ -5,6 +5,7 @@ import ( "go/token" "go/types" "strconv" + "strings" "github.com/go-toolsmith/astfmt" ) @@ -350,7 +351,25 @@ func (ctx *CheckerContext) SizeOf(typ types.Type) (int64, bool) { if named, ok := typ.(*types.Named); ok && named.TypeParams() != nil { return 0, false } - return ctx.SizesInfo.Sizeof(typ), true + return ctx.safeSizesInfoSizeof(typ) +} + +// safeSizesInfoSizeof unlike SizesInfo.Sizeof will not panic on struct with generic fields. +// it will catch a panic and recover from it, see https://github.com/go-critic/go-critic/issues/1354 +func (ctx *CheckerContext) safeSizesInfoSizeof(typ types.Type) (size int64, ok bool) { + ok = true + defer func() { + if r := recover(); r != nil { + if strings.Contains(r.(string), "assertion failed") { + size, ok = 0, false + } else { + panic(r) + } + } + }() + + size = ctx.SizesInfo.Sizeof(typ) + return size, ok } func resolvePkgObjects(ctx *Context, f *ast.File) { diff --git a/tools/vendor/github.com/golangci/gofmt/gofmt/doc.go b/tools/vendor/github.com/golangci/gofmt/gofmt/doc.go index da0c8581dd..d0a4580219 100644 --- a/tools/vendor/github.com/golangci/gofmt/gofmt/doc.go +++ b/tools/vendor/github.com/golangci/gofmt/gofmt/doc.go @@ -13,9 +13,11 @@ that directory, recursively. (Files starting with a period are ignored.) By default, gofmt prints the reformatted sources to standard output. Usage: + gofmt [flags] [path ...] The flags are: + -d Do not print reformatted sources to standard output. If a file's formatting is different than gofmt's, print diffs @@ -37,10 +39,10 @@ The flags are: the original file is restored from an automatic backup. Debugging support: + -cpuprofile filename Write cpu profile to the specified file. - The rewrite rule specified with the -r flag must be a string of the form: pattern -> replacement @@ -57,7 +59,7 @@ such a fragment, gofmt preserves leading indentation as well as leading and trailing spaces, so that individual sections of a Go program can be formatted by piping them through gofmt. -Examples +# Examples To check files for unnecessary parentheses: @@ -71,7 +73,7 @@ To convert the package tree from explicit slice upper bounds to implicit ones: gofmt -r 'α[β:len(α)] -> α[β:]' -w $GOROOT/src -The simplify command +# The simplify command When invoked with -s gofmt will make the following source transformations where possible. diff --git a/tools/vendor/github.com/golangci/gofmt/gofmt/gofmt.go b/tools/vendor/github.com/golangci/gofmt/gofmt/gofmt.go index e7612afae0..be046f34cf 100644 --- a/tools/vendor/github.com/golangci/gofmt/gofmt/gofmt.go +++ b/tools/vendor/github.com/golangci/gofmt/gofmt/gofmt.go @@ -76,6 +76,11 @@ func initParserMode() { if *allErrors { parserMode |= parser.AllErrors } + // It's only -r that makes use of go/ast's object resolution, + // so avoid the unnecessary work if the flag isn't used. + if *rewriteRule == "" { + parserMode |= parser.SkipObjectResolution + } } func isGoFile(f fs.DirEntry) bool { @@ -286,12 +291,9 @@ func processFile(filename string, info fs.FileInfo, in io.Reader, r *reporter) e } } if *doDiff { - data, err := diffWithReplaceTempFile(src, res, filename) - if err != nil { - return fmt.Errorf("computing diff: %s", err) - } - fmt.Fprintf(r, "diff -u %s %s\n", filepath.ToSlash(filename+".orig"), filepath.ToSlash(filename)) - r.Write(data) + newName := filepath.ToSlash(filename) + oldName := newName + ".orig" + r.Write(diff.Diff(oldName, src, newName, res)) } } @@ -350,7 +352,12 @@ func readFile(filename string, info fs.FileInfo, in io.Reader) ([]byte, error) { // stop to avoid corrupting it.) src := make([]byte, size+1) n, err := io.ReadFull(in, src) - if err != nil && err != io.ErrUnexpectedEOF { + switch err { + case nil, io.EOF, io.ErrUnexpectedEOF: + // io.ReadFull returns io.EOF (for an empty file) or io.ErrUnexpectedEOF + // (for a non-empty file) if the file was changed unexpectedly. Continue + // with comparing file sizes in those cases. + default: return nil, err } if n < size { @@ -463,43 +470,6 @@ func fileWeight(path string, info fs.FileInfo) int64 { return info.Size() } -func diffWithReplaceTempFile(b1, b2 []byte, filename string) ([]byte, error) { - data, err := diff.Diff("gofmt", b1, b2) - if len(data) > 0 { - return replaceTempFilename(data, filename) - } - return data, err -} - -// replaceTempFilename replaces temporary filenames in diff with actual one. -// -// --- /tmp/gofmt316145376 2017-02-03 19:13:00.280468375 -0500 -// +++ /tmp/gofmt617882815 2017-02-03 19:13:00.280468375 -0500 -// ... -// -> -// --- path/to/file.go.orig 2017-02-03 19:13:00.280468375 -0500 -// +++ path/to/file.go 2017-02-03 19:13:00.280468375 -0500 -// ... -func replaceTempFilename(diff []byte, filename string) ([]byte, error) { - bs := bytes.SplitN(diff, []byte{'\n'}, 3) - if len(bs) < 3 { - return nil, fmt.Errorf("got unexpected diff for %s", filename) - } - // Preserve timestamps. - var t0, t1 []byte - if i := bytes.LastIndexByte(bs[0], '\t'); i != -1 { - t0 = bs[0][i:] - } - if i := bytes.LastIndexByte(bs[1], '\t'); i != -1 { - t1 = bs[1][i:] - } - // Always print filepath with slash separator. - f := filepath.ToSlash(filename) - bs[0] = []byte(fmt.Sprintf("--- %s%s", f+".orig", t0)) - bs[1] = []byte(fmt.Sprintf("+++ %s%s", f, t1)) - return bytes.Join(bs, []byte{'\n'}), nil -} - const chmodSupported = runtime.GOOS != "windows" // backupFile writes data to a new file named filename with permissions perm, diff --git a/tools/vendor/github.com/golangci/gofmt/gofmt/golangci.go b/tools/vendor/github.com/golangci/gofmt/gofmt/golangci.go index c9c3fe2ae5..a69611e1d3 100644 --- a/tools/vendor/github.com/golangci/gofmt/gofmt/golangci.go +++ b/tools/vendor/github.com/golangci/gofmt/gofmt/golangci.go @@ -8,8 +8,14 @@ import ( "go/printer" "go/token" "os" + "path/filepath" + "sync" + + "github.com/golangci/gofmt/gofmt/internal/diff" ) +var parserModeMu sync.RWMutex + type RewriteRule struct { Pattern string Replacement string @@ -31,7 +37,9 @@ func RunRewrite(filename string, needSimplify bool, rewriteRules []RewriteRule) fset := token.NewFileSet() + parserModeMu.Lock() initParserMode() + parserModeMu.Unlock() file, sourceAdj, indentAdj, err := parse(fset, filename, src, false) if err != nil { @@ -59,12 +67,10 @@ func RunRewrite(filename string, needSimplify bool, rewriteRules []RewriteRule) } // formatting has changed - data, err := diffWithReplaceTempFile(src, res, filename) - if err != nil { - return nil, fmt.Errorf("error computing diff: %s", err) - } + newName := filepath.ToSlash(filename) + oldName := newName + ".orig" - return data, nil + return diff.Diff(oldName, src, newName, res), nil } func rewriteFileContent(fset *token.FileSet, file *ast.File, rewriteRules []RewriteRule) (*ast.File, error) { diff --git a/tools/vendor/github.com/golangci/gofmt/gofmt/internal.go b/tools/vendor/github.com/golangci/gofmt/gofmt/internal.go index 1abbdd6989..31a825bf83 100644 --- a/tools/vendor/github.com/golangci/gofmt/gofmt/internal.go +++ b/tools/vendor/github.com/golangci/gofmt/gofmt/internal.go @@ -26,6 +26,13 @@ func parse(fset *token.FileSet, filename string, src []byte, fragmentOk bool) ( indentAdj int, err error, ) { + + // START - Change related to usgae inside golangci-lint + parserModeMu.Lock() + parserMode := parserMode + parserModeMu.Unlock() + // END - Change related to usgae inside golangci-lint + // Try as whole source file. file, err = parser.ParseFile(fset, filename, src, parserMode) // If there's no error, return. If the error is that the source file didn't begin with a diff --git a/tools/vendor/github.com/golangci/gofmt/gofmt/internal/diff/diff.go b/tools/vendor/github.com/golangci/gofmt/gofmt/internal/diff/diff.go index cbd0529ec6..47b2856714 100644 --- a/tools/vendor/github.com/golangci/gofmt/gofmt/internal/diff/diff.go +++ b/tools/vendor/github.com/golangci/gofmt/gofmt/internal/diff/diff.go @@ -1,79 +1,261 @@ -// Copyright 2019 The Go Authors. All rights reserved. +// Copyright 2022 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package diff implements a Diff function that compare two inputs -// using the 'diff' tool. package diff import ( "bytes" - "io/ioutil" - "os" - "runtime" - - exec "github.com/golangci/gofmt/gofmt/internal/execabs" + "fmt" + "sort" + "strings" ) -// Returns diff of two arrays of bytes in diff tool format. -func Diff(prefix string, b1, b2 []byte) ([]byte, error) { - f1, err := writeTempFile(prefix, b1) - if err != nil { - return nil, err +// A pair is a pair of values tracked for both the x and y side of a diff. +// It is typically a pair of line indexes. +type pair struct{ x, y int } + +// Diff returns an anchored diff of the two texts old and new +// in the “unified diff” format. If old and new are identical, +// Diff returns a nil slice (no output). +// +// Unix diff implementations typically look for a diff with +// the smallest number of lines inserted and removed, +// which can in the worst case take time quadratic in the +// number of lines in the texts. As a result, many implementations +// either can be made to run for a long time or cut off the search +// after a predetermined amount of work. +// +// In contrast, this implementation looks for a diff with the +// smallest number of “unique” lines inserted and removed, +// where unique means a line that appears just once in both old and new. +// We call this an “anchored diff” because the unique lines anchor +// the chosen matching regions. An anchored diff is usually clearer +// than a standard diff, because the algorithm does not try to +// reuse unrelated blank lines or closing braces. +// The algorithm also guarantees to run in O(n log n) time +// instead of the standard O(n²) time. +// +// Some systems call this approach a “patience diff,” named for +// the “patience sorting” algorithm, itself named for a solitaire card game. +// We avoid that name for two reasons. First, the name has been used +// for a few different variants of the algorithm, so it is imprecise. +// Second, the name is frequently interpreted as meaning that you have +// to wait longer (to be patient) for the diff, meaning that it is a slower algorithm, +// when in fact the algorithm is faster than the standard one. +func Diff(oldName string, old []byte, newName string, new []byte) []byte { + if bytes.Equal(old, new) { + return nil } - defer os.Remove(f1) + x := lines(old) + y := lines(new) + + // Print diff header. + var out bytes.Buffer + fmt.Fprintf(&out, "diff %s %s\n", oldName, newName) + fmt.Fprintf(&out, "--- %s\n", oldName) + fmt.Fprintf(&out, "+++ %s\n", newName) + + // Loop over matches to consider, + // expanding each match to include surrounding lines, + // and then printing diff chunks. + // To avoid setup/teardown cases outside the loop, + // tgs returns a leading {0,0} and trailing {len(x), len(y)} pair + // in the sequence of matches. + var ( + done pair // printed up to x[:done.x] and y[:done.y] + chunk pair // start lines of current chunk + count pair // number of lines from each side in current chunk + ctext []string // lines for current chunk + ) + for _, m := range tgs(x, y) { + if m.x < done.x { + // Already handled scanning forward from earlier match. + continue + } - f2, err := writeTempFile(prefix, b2) - if err != nil { - return nil, err + // Expand matching lines as far possible, + // establishing that x[start.x:end.x] == y[start.y:end.y]. + // Note that on the first (or last) iteration we may (or definitey do) + // have an empty match: start.x==end.x and start.y==end.y. + start := m + for start.x > done.x && start.y > done.y && x[start.x-1] == y[start.y-1] { + start.x-- + start.y-- + } + end := m + for end.x < len(x) && end.y < len(y) && x[end.x] == y[end.y] { + end.x++ + end.y++ + } + + // Emit the mismatched lines before start into this chunk. + // (No effect on first sentinel iteration, when start = {0,0}.) + for _, s := range x[done.x:start.x] { + ctext = append(ctext, "-"+s) + count.x++ + } + for _, s := range y[done.y:start.y] { + ctext = append(ctext, "+"+s) + count.y++ + } + + // If we're not at EOF and have too few common lines, + // the chunk includes all the common lines and continues. + const C = 3 // number of context lines + if (end.x < len(x) || end.y < len(y)) && + (end.x-start.x < C || (len(ctext) > 0 && end.x-start.x < 2*C)) { + for _, s := range x[start.x:end.x] { + ctext = append(ctext, " "+s) + count.x++ + count.y++ + } + done = end + continue + } + + // End chunk with common lines for context. + if len(ctext) > 0 { + n := end.x - start.x + if n > C { + n = C + } + for _, s := range x[start.x : start.x+n] { + ctext = append(ctext, " "+s) + count.x++ + count.y++ + } + done = pair{start.x + n, start.y + n} + + // Format and emit chunk. + // Convert line numbers to 1-indexed. + // Special case: empty file shows up as 0,0 not 1,0. + if count.x > 0 { + chunk.x++ + } + if count.y > 0 { + chunk.y++ + } + fmt.Fprintf(&out, "@@ -%d,%d +%d,%d @@\n", chunk.x, count.x, chunk.y, count.y) + for _, s := range ctext { + out.WriteString(s) + } + count.x = 0 + count.y = 0 + ctext = ctext[:0] + } + + // If we reached EOF, we're done. + if end.x >= len(x) && end.y >= len(y) { + break + } + + // Otherwise start a new chunk. + chunk = pair{end.x - C, end.y - C} + for _, s := range x[chunk.x:end.x] { + ctext = append(ctext, " "+s) + count.x++ + count.y++ + } + done = end } - defer os.Remove(f2) - cmd := "diff" - if runtime.GOOS == "plan9" { - cmd = "/bin/ape/diff" + return out.Bytes() +} + +// lines returns the lines in the file x, including newlines. +// If the file does not end in a newline, one is supplied +// along with a warning about the missing newline. +func lines(x []byte) []string { + l := strings.SplitAfter(string(x), "\n") + if l[len(l)-1] == "" { + l = l[:len(l)-1] + } else { + // Treat last line as having a message about the missing newline attached, + // using the same text as BSD/GNU diff (including the leading backslash). + l[len(l)-1] += "\n\\ No newline at end of file\n" } + return l +} - data, err := exec.Command(cmd, "-u", f1, f2).CombinedOutput() - if len(data) > 0 { - // diff exits with a non-zero status when the files don't match. - // Ignore that failure as long as we get output. - err = nil +// tgs returns the pairs of indexes of the longest common subsequence +// of unique lines in x and y, where a unique line is one that appears +// once in x and once in y. +// +// The longest common subsequence algorithm is as described in +// Thomas G. Szymanski, “A Special Case of the Maximal Common +// Subsequence Problem,” Princeton TR #170 (January 1975), +// available at https://research.swtch.com/tgs170.pdf. +func tgs(x, y []string) []pair { + // Count the number of times each string appears in a and b. + // We only care about 0, 1, many, counted as 0, -1, -2 + // for the x side and 0, -4, -8 for the y side. + // Using negative numbers now lets us distinguish positive line numbers later. + m := make(map[string]int) + for _, s := range x { + if c := m[s]; c > -2 { + m[s] = c - 1 + } + } + for _, s := range y { + if c := m[s]; c > -8 { + m[s] = c - 4 + } } - // If we are on Windows and the diff is Cygwin diff, - // machines can get into a state where every Cygwin - // command works fine but prints a useless message like: + // Now unique strings can be identified by m[s] = -1+-4. // - // Cygwin WARNING: - // Couldn't compute FAST_CWD pointer. This typically occurs if you're using - // an older Cygwin version on a newer Windows. Please update to the latest - // available Cygwin version from https://cygwin.com/. If the problem persists, - // please see https://cygwin.com/problems.html - // - // Skip over that message and just return the actual diff. - if len(data) > 0 && !bytes.HasPrefix(data, []byte("--- ")) { - i := bytes.Index(data, []byte("\n--- ")) - if i >= 0 && i < 80*10 && bytes.Contains(data[:i], []byte("://cygwin.com/")) { - data = data[i+1:] + // Gather the indexes of those strings in x and y, building: + // xi[i] = increasing indexes of unique strings in x. + // yi[i] = increasing indexes of unique strings in y. + // inv[i] = index j such that x[xi[i]] = y[yi[j]]. + var xi, yi, inv []int + for i, s := range y { + if m[s] == -1+-4 { + m[s] = len(yi) + yi = append(yi, i) + } + } + for i, s := range x { + if j, ok := m[s]; ok && j >= 0 { + xi = append(xi, i) + inv = append(inv, j) } } - return data, err -} - -func writeTempFile(prefix string, data []byte) (string, error) { - file, err := ioutil.TempFile("", prefix) - if err != nil { - return "", err + // Apply Algorithm A from Szymanski's paper. + // In those terms, A = J = inv and B = [0, n). + // We add sentinel pairs {0,0}, and {len(x),len(y)} + // to the returned sequence, to help the processing loop. + J := inv + n := len(xi) + T := make([]int, n) + L := make([]int, n) + for i := range T { + T[i] = n + 1 + } + for i := 0; i < n; i++ { + k := sort.Search(n, func(k int) bool { + return T[k] >= J[i] + }) + T[k] = J[i] + L[i] = k + 1 } - _, err = file.Write(data) - if err1 := file.Close(); err == nil { - err = err1 + k := 0 + for _, v := range L { + if k < v { + k = v + } } - if err != nil { - os.Remove(file.Name()) - return "", err + seq := make([]pair, 2+k) + seq[1+k] = pair{len(x), len(y)} // sentinel at end + lastj := n + for i := n - 1; i >= 0; i-- { + if L[i] == k && J[i] < lastj { + seq[k] = pair{xi[i], yi[J[i]]} + k-- + } } - return file.Name(), nil + seq[0] = pair{0, 0} // sentinel at start + return seq } diff --git a/tools/vendor/github.com/golangci/gofmt/gofmt/internal/execabs/execabs.go b/tools/vendor/github.com/golangci/gofmt/gofmt/internal/execabs/execabs.go deleted file mode 100644 index 9a05d971da..0000000000 --- a/tools/vendor/github.com/golangci/gofmt/gofmt/internal/execabs/execabs.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2021 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package execabs is a drop-in replacement for os/exec -// that requires PATH lookups to find absolute paths. -// That is, execabs.Command("cmd") runs the same PATH lookup -// as exec.Command("cmd"), but if the result is a path -// which is relative, the Run and Start methods will report -// an error instead of running the executable. -package execabs - -import ( - "context" - "fmt" - "os/exec" - "path/filepath" - "reflect" - "unsafe" -) - -var ErrNotFound = exec.ErrNotFound - -type ( - Cmd = exec.Cmd - Error = exec.Error - ExitError = exec.ExitError -) - -func relError(file, path string) error { - return fmt.Errorf("%s resolves to executable relative to current directory (.%c%s)", file, filepath.Separator, path) -} - -func LookPath(file string) (string, error) { - path, err := exec.LookPath(file) - if err != nil { - return "", err - } - if filepath.Base(file) == file && !filepath.IsAbs(path) { - return "", relError(file, path) - } - return path, nil -} - -func fixCmd(name string, cmd *exec.Cmd) { - if filepath.Base(name) == name && !filepath.IsAbs(cmd.Path) { - // exec.Command was called with a bare binary name and - // exec.LookPath returned a path which is not absolute. - // Set cmd.lookPathErr and clear cmd.Path so that it - // cannot be run. - lookPathErr := (*error)(unsafe.Pointer(reflect.ValueOf(cmd).Elem().FieldByName("lookPathErr").Addr().Pointer())) - if *lookPathErr == nil { - *lookPathErr = relError(name, cmd.Path) - } - cmd.Path = "" - } -} - -func CommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd { - cmd := exec.CommandContext(ctx, name, arg...) - fixCmd(name, cmd) - return cmd - -} - -func Command(name string, arg ...string) *exec.Cmd { - cmd := exec.Command(name, arg...) - fixCmd(name, cmd) - return cmd -} diff --git a/tools/vendor/github.com/golangci/gofmt/gofmt/readme.md b/tools/vendor/github.com/golangci/gofmt/gofmt/readme.md index 36a716d819..c2faaab82d 100644 --- a/tools/vendor/github.com/golangci/gofmt/gofmt/readme.md +++ b/tools/vendor/github.com/golangci/gofmt/gofmt/readme.md @@ -1,3 +1,5 @@ # Hard Fork of gofmt 2022-08-31: Sync with go1.18.5 +2023-10-04: Sync with go1.19.13 +2023-10-04: Sync with go1.20.8 diff --git a/tools/vendor/github.com/golangci/gofmt/gofmt/simplify.go b/tools/vendor/github.com/golangci/gofmt/gofmt/simplify.go index 2c75495a69..3b34d562ba 100644 --- a/tools/vendor/github.com/golangci/gofmt/gofmt/simplify.go +++ b/tools/vendor/github.com/golangci/gofmt/gofmt/simplify.go @@ -53,22 +53,26 @@ func (s simplifier) Visit(node ast.Node) ast.Visitor { // can be simplified to: s[a:] // if s is "simple enough" (for now we only accept identifiers) // - // Note: This may not be correct because len may have been redeclared in another - // file belonging to the same package. However, this is extremely unlikely - // and so far (April 2016, after years of supporting this rewrite feature) + // Note: This may not be correct because len may have been redeclared in + // the same package. However, this is extremely unlikely and so far + // (April 2022, after years of supporting this rewrite feature) // has never come up, so let's keep it working as is (see also #15153). + // + // Also note that this code used to use go/ast's object tracking, + // which was removed in exchange for go/parser.Mode.SkipObjectResolution. + // False positives are extremely unlikely as described above, + // and go/ast's object tracking is incomplete in any case. if n.Max != nil { // - 3-index slices always require the 2nd and 3rd index break } - if s, _ := n.X.(*ast.Ident); s != nil && s.Obj != nil { - // the array/slice object is a single, resolved identifier + if s, _ := n.X.(*ast.Ident); s != nil { + // the array/slice object is a single identifier if call, _ := n.High.(*ast.CallExpr); call != nil && len(call.Args) == 1 && !call.Ellipsis.IsValid() { // the high expression is a function call with a single argument - if fun, _ := call.Fun.(*ast.Ident); fun != nil && fun.Name == "len" && fun.Obj == nil { - // the function called is "len" and it is not locally defined; and - // because we don't have dot imports, it must be the predefined len() - if arg, _ := call.Args[0].(*ast.Ident); arg != nil && arg.Obj == s.Obj { + if fun, _ := call.Fun.(*ast.Ident); fun != nil && fun.Name == "len" { + // the function called is "len" + if arg, _ := call.Args[0].(*ast.Ident); arg != nil && arg.Name == s.Name { // the len argument is the array/slice object n.High = nil } diff --git a/tools/vendor/github.com/golangci/gofmt/goimports/goimports.go b/tools/vendor/github.com/golangci/gofmt/goimports/goimports.go index 1fa3328f8a..20d92e119c 100644 --- a/tools/vendor/github.com/golangci/gofmt/goimports/goimports.go +++ b/tools/vendor/github.com/golangci/gofmt/goimports/goimports.go @@ -14,7 +14,7 @@ import ( "runtime" ) -// Extracted from golang.org/x/tools@v0.1.12/cmd/goimports/goimports.go +// Extracted from golang.org/x/tools@v0.13.0/cmd/goimports/goimports.go func writeTempFile(dir, prefix string, data []byte) (string, error) { file, err := ioutil.TempFile(dir, prefix) diff --git a/tools/vendor/github.com/golangci/gofmt/goimports/golangci.go b/tools/vendor/github.com/golangci/gofmt/goimports/golangci.go index 7edc37937c..6ff286ae06 100644 --- a/tools/vendor/github.com/golangci/gofmt/goimports/golangci.go +++ b/tools/vendor/github.com/golangci/gofmt/goimports/golangci.go @@ -3,7 +3,7 @@ package goimports import ( "bytes" "fmt" - "io/ioutil" + "os" "golang.org/x/tools/imports" ) @@ -11,7 +11,7 @@ import ( // Run runs goimports. // The local prefixes (comma separated) must be defined through the global variable imports.LocalPrefix. func Run(filename string) ([]byte, error) { - src, err := ioutil.ReadFile(filename) + src, err := os.ReadFile(filename) if err != nil { return nil, err } diff --git a/tools/vendor/github.com/golangci/gofmt/goimports/readme.md b/tools/vendor/github.com/golangci/gofmt/goimports/readme.md index 6c793eb7d1..e57ed550b1 100644 --- a/tools/vendor/github.com/golangci/gofmt/goimports/readme.md +++ b/tools/vendor/github.com/golangci/gofmt/goimports/readme.md @@ -1,3 +1,4 @@ # Hard Fork of goimports 2022-08-31: Sync with golang.org/x/tools v0.1.12 +2023-10-04: Sync with golang.org/x/tools v0.13.0 diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/commands/executor.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/commands/executor.go index 109edcb90c..61e221cb8b 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/commands/executor.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/commands/executor.go @@ -120,7 +120,7 @@ func NewExecutor(buildInfo BuildInfo) *Executor { } // recreate after getting config - e.DBManager = lintersdb.NewManager(e.cfg, e.log).WithCustomLinters() + e.DBManager = lintersdb.NewManager(e.cfg, e.log) // Slice options must be explicitly set for proper merging of config and command-line options. fixSlicesFlags(e.runCmd.Flags()) diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/commands/help.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/commands/help.go index 61d362e7d2..a06d508f27 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/commands/help.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/commands/help.go @@ -63,6 +63,10 @@ func printLinterConfigs(lcs []*linter.Config) { func (e *Executor) executeLintersHelp(_ *cobra.Command, _ []string) { var enabledLCs, disabledLCs []*linter.Config for _, lc := range e.DBManager.GetAllSupportedLinterConfigs() { + if lc.Internal { + continue + } + if lc.EnabledByDefault { enabledLCs = append(enabledLCs, lc) } else { @@ -78,8 +82,12 @@ func (e *Executor) executeLintersHelp(_ *cobra.Command, _ []string) { color.Green("\nLinters presets:") for _, p := range e.DBManager.AllPresets() { linters := e.DBManager.GetAllLinterConfigsForPreset(p) - linterNames := make([]string, 0, len(linters)) + var linterNames []string for _, lc := range linters { + if lc.Internal { + continue + } + linterNames = append(linterNames, lc.Name()) } sort.Strings(linterNames) diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/commands/linters.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/commands/linters.go index 13bb944d8a..292713ec90 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/commands/linters.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/commands/linters.go @@ -29,14 +29,22 @@ func (e *Executor) executeLinters(_ *cobra.Command, _ []string) error { } color.Green("Enabled by your configuration linters:\n") - enabledLinters := make([]*linter.Config, 0, len(enabledLintersMap)) - for _, linter := range enabledLintersMap { - enabledLinters = append(enabledLinters, linter) + var enabledLinters []*linter.Config + for _, lc := range enabledLintersMap { + if lc.Internal { + continue + } + + enabledLinters = append(enabledLinters, lc) } printLinterConfigs(enabledLinters) var disabledLCs []*linter.Config for _, lc := range e.DBManager.GetAllSupportedLinterConfigs() { + if lc.Internal { + continue + } + if enabledLintersMap[lc.Name()] == nil { disabledLCs = append(disabledLCs, lc) } diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/commands/root.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/commands/root.go index 5fe4c784d6..efe62ced2a 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/commands/root.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/commands/root.go @@ -149,10 +149,10 @@ func (e *Executor) needVersionOption() bool { } func initRootFlagSet(fs *pflag.FlagSet, cfg *config.Config, needVersionOption bool) { - fs.BoolVarP(&cfg.Run.IsVerbose, "verbose", "v", false, wh("verbose output")) + fs.BoolVarP(&cfg.Run.IsVerbose, "verbose", "v", false, wh("Verbose output")) var silent bool - fs.BoolVarP(&silent, "silent", "s", false, wh("disables congrats outputs")) + fs.BoolVarP(&silent, "silent", "s", false, wh("Disables congrats outputs")) if err := fs.MarkHidden("silent"); err != nil { panic(err) } diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/config/config.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/config/config.go index af40c63bdb..7941f428f4 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/config/config.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/config/config.go @@ -41,13 +41,13 @@ type Version struct { Debug bool `mapstructure:"debug"` } -func IsGreaterThanOrEqualGo118(v string) bool { +func IsGreaterThanOrEqualGo121(v string) bool { v1, err := hcversion.NewVersion(strings.TrimPrefix(v, "go")) if err != nil { return false } - limit, err := hcversion.NewVersion("1.18") + limit, err := hcversion.NewVersion("1.21") if err != nil { return false } diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/config/issues.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/config/issues.go index 417b28bdbf..5968d83d56 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/config/issues.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/config/issues.go @@ -54,7 +54,7 @@ var DefaultExcludePatterns = []ExcludePattern{ }, { ID: "EXC0008", - Pattern: "(G104|G307)", + Pattern: "(G104)", Linter: "gosec", Why: "Duplicated errcheck checks", }, diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/config/linters_settings.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/config/linters_settings.go index b520ea4c6e..0fee9f81ef 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/config/linters_settings.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/config/linters_settings.go @@ -113,10 +113,17 @@ var defaultLintersSettings = LintersSettings{ Ignore: "", Qualified: false, }, + SlogLint: SlogLintSettings{ + KVOnly: false, + AttrOnly: false, + NoRawKeys: false, + ArgsOnSepLines: false, + }, TagAlign: TagAlignSettings{ - Align: true, - Sort: true, - Order: nil, + Align: true, + Sort: true, + Order: nil, + Strict: false, }, Testpackage: TestpackageSettings{ SkipRegexp: `(export|internal)_test\.go`, @@ -125,6 +132,15 @@ var defaultLintersSettings = LintersSettings{ Unparam: UnparamSettings{ Algo: "cha", }, + Unused: UnusedSettings{ + FieldWritesAreUses: true, + PostStatementsAreReads: false, + ExportedIsUsed: true, + ExportedFieldsAreUsed: true, + ParametersAreUsed: true, + LocalVariablesAreUsed: true, + GeneratedIsUsed: true, + }, UseStdlibVars: UseStdlibVarsSettings{ HTTPMethod: true, HTTPStatusCode: true, @@ -212,15 +228,18 @@ type LintersSettings struct { Reassign ReassignSettings Revive ReviveSettings RowsErrCheck RowsErrCheckSettings + SlogLint SlogLintSettings Staticcheck StaticCheckSettings Structcheck StructCheckSettings Stylecheck StaticCheckSettings TagAlign TagAlignSettings Tagliatelle TagliatelleSettings + Testifylint TestifylintSettings Tenv TenvSettings Testpackage TestpackageSettings Thelper ThelperSettings Unparam UnparamSettings + Unused UnusedSettings UseStdlibVars UseStdlibVarsSettings Varcheck VarCheckSettings Varnamelen VarnamelenSettings @@ -272,7 +291,11 @@ type DepGuardDeny struct { type DecorderSettings struct { DecOrder []string `mapstructure:"dec-order"` + IgnoreUnderscoreVars bool `mapstructure:"ignore-underscore-vars"` DisableDecNumCheck bool `mapstructure:"disable-dec-num-check"` + DisableTypeDecNumCheck bool `mapstructure:"disable-type-dec-num-check"` + DisableConstDecNumCheck bool `mapstructure:"disable-const-dec-num-check"` + DisableVarDecNumCheck bool `mapstructure:"disable-var-dec-num-check"` DisableDecOrderCheck bool `mapstructure:"disable-dec-order-check"` DisableInitFuncFirstCheck bool `mapstructure:"disable-init-func-first-check"` } @@ -287,6 +310,7 @@ type DuplSettings struct { type DupWordSettings struct { Keywords []string `mapstructure:"keywords"` + Ignore []string `mapstructure:"ignore"` } type ErrcheckSettings struct { @@ -375,8 +399,9 @@ func (p *ForbidigoPattern) MarshalString() ([]byte, error) { } type FunlenSettings struct { - Lines int - Statements int + Lines int + Statements int + IgnoreComments bool `mapstructure:"ignore-comments"` } type GciSettings struct { @@ -387,12 +412,14 @@ type GciSettings struct { } type GinkgoLinterSettings struct { - SuppressLenAssertion bool `mapstructure:"suppress-len-assertion"` - SuppressNilAssertion bool `mapstructure:"suppress-nil-assertion"` - SuppressErrAssertion bool `mapstructure:"suppress-err-assertion"` - SuppressCompareAssertion bool `mapstructure:"suppress-compare-assertion"` - SuppressAsyncAssertion bool `mapstructure:"suppress-async-assertion"` - AllowHaveLenZero bool `mapstructure:"allow-havelen-zero"` + SuppressLenAssertion bool `mapstructure:"suppress-len-assertion"` + SuppressNilAssertion bool `mapstructure:"suppress-nil-assertion"` + SuppressErrAssertion bool `mapstructure:"suppress-err-assertion"` + SuppressCompareAssertion bool `mapstructure:"suppress-compare-assertion"` + SuppressAsyncAssertion bool `mapstructure:"suppress-async-assertion"` + SuppressTypeCompareWarning bool `mapstructure:"suppress-type-compare-assertion"` + ForbidFocusContainer bool `mapstructure:"forbid-focus-container"` + AllowHaveLenZero bool `mapstructure:"allow-havelen-zero"` } type GocognitSettings struct { @@ -609,7 +636,8 @@ type MalignedSettings struct { } type MisspellSettings struct { - Locale string + Locale string + // TODO(ldez): v2 the options must be renamed to `IgnoredRules`. IgnoreWords []string `mapstructure:"ignore-words"` } @@ -648,7 +676,8 @@ type NoNamedReturnsSettings struct { ReportErrorInDefer bool `mapstructure:"report-error-in-defer"` } type ParallelTestSettings struct { - IgnoreMissing bool `mapstructure:"ignore-missing"` + IgnoreMissing bool `mapstructure:"ignore-missing"` + IgnoreMissingSubtests bool `mapstructure:"ignore-missing-subtests"` } type PreallocSettings struct { @@ -695,6 +724,13 @@ type RowsErrCheckSettings struct { Packages []string } +type SlogLintSettings struct { + KVOnly bool `mapstructure:"kv-only"` + AttrOnly bool `mapstructure:"attr-only"` + NoRawKeys bool `mapstructure:"no-raw-keys"` + ArgsOnSepLines bool `mapstructure:"args-on-sep-lines"` +} + type StaticCheckSettings struct { // Deprecated: use the global `run.go` instead. GoVersion string `mapstructure:"go"` @@ -714,9 +750,10 @@ type StructCheckSettings struct { } type TagAlignSettings struct { - Align bool `mapstructure:"align"` - Sort bool `mapstructure:"sort"` - Order []string `mapstructure:"order"` + Align bool `mapstructure:"align"` + Sort bool `mapstructure:"sort"` + Order []string `mapstructure:"order"` + Strict bool `mapstructure:"strict"` } type TagliatelleSettings struct { @@ -726,6 +763,19 @@ type TagliatelleSettings struct { } } +type TestifylintSettings struct { + EnableAll bool `mapstructure:"enable-all"` + EnabledCheckers []string `mapstructure:"enable"` + + ExpectedActual struct { + ExpVarPattern string `mapstructure:"pattern"` + } `mapstructure:"expected-actual"` + + SuiteExtraAssertCall struct { + Mode string `mapstructure:"mode"` + } `mapstructure:"suite-extra-assert-call"` +} + type TestpackageSettings struct { SkipRegexp string `mapstructure:"skip-regexp"` AllowPackages []string `mapstructure:"allow-packages"` @@ -768,6 +818,16 @@ type UnparamSettings struct { Algo string } +type UnusedSettings struct { + FieldWritesAreUses bool `mapstructure:"field-writes-are-uses"` + PostStatementsAreReads bool `mapstructure:"post-statements-are-reads"` + ExportedIsUsed bool `mapstructure:"exported-is-used"` + ExportedFieldsAreUsed bool `mapstructure:"exported-fields-are-used"` + ParametersAreUsed bool `mapstructure:"parameters-are-used"` + LocalVariablesAreUsed bool `mapstructure:"local-variables-are-used"` + GeneratedIsUsed bool `mapstructure:"generated-is-used"` +} + type VarCheckSettings struct { CheckExportedFields bool `mapstructure:"exported-fields"` } @@ -828,6 +888,9 @@ type CustomLinterSettings struct { Path string // Description describes the purpose of the private linter. Description string - // The URL containing the source code for the private linter. + // OriginalURL The URL containing the source code for the private linter. OriginalURL string `mapstructure:"original-url"` + + // Settings plugin settings only work with linterdb.PluginConstructor symbol. + Settings any } diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/decorder.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/decorder.go index 9d492c4e82..5202a03a4f 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/decorder.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/decorder.go @@ -15,14 +15,22 @@ func NewDecorder(settings *config.DecorderSettings) *goanalysis.Linter { // disable all rules/checks by default cfg := map[string]any{ + "ignore-underscore-vars": false, "disable-dec-num-check": true, + "disable-type-dec-num-check": false, + "disable-const-dec-num-check": false, + "disable-var-dec-num-check": false, "disable-dec-order-check": true, "disable-init-func-first-check": true, } if settings != nil { cfg["dec-order"] = strings.Join(settings.DecOrder, ",") + cfg["ignore-underscore-vars"] = settings.IgnoreUnderscoreVars cfg["disable-dec-num-check"] = settings.DisableDecNumCheck + cfg["disable-type-dec-num-check"] = settings.DisableTypeDecNumCheck + cfg["disable-const-dec-num-check"] = settings.DisableConstDecNumCheck + cfg["disable-var-dec-num-check"] = settings.DisableVarDecNumCheck cfg["disable-dec-order-check"] = settings.DisableDecOrderCheck cfg["disable-init-func-first-check"] = settings.DisableInitFuncFirstCheck } diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/dupword.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/dupword.go index f5a99bc0df..6f079ffc89 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/dupword.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/dupword.go @@ -17,6 +17,7 @@ func NewDupWord(setting *config.DupWordSettings) *goanalysis.Linter { if setting != nil { cfgMap[a.Name] = map[string]any{ "keyword": strings.Join(setting.Keywords, ","), + "ignore": strings.Join(setting.Ignore, ","), } } diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/exhaustruct.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/exhaustruct.go index 37ea055d3e..5272879e1d 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/exhaustruct.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/exhaustruct.go @@ -1,7 +1,7 @@ package golinters import ( - "github.com/GaijinEntertainment/go-exhaustruct/v2/pkg/analyzer" + "github.com/GaijinEntertainment/go-exhaustruct/v3/analyzer" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/funlen.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/funlen.go index aae1623c7f..8def9c1f67 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/funlen.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/funlen.go @@ -52,7 +52,7 @@ func NewFunlen(settings *config.FunlenSettings) *goanalysis.Linter { func runFunlen(pass *analysis.Pass, settings *config.FunlenSettings) []goanalysis.Issue { var lintIssues []funlen.Message for _, file := range pass.Files { - fileIssues := funlen.Run(file, pass.Fset, settings.Lines, settings.Statements) + fileIssues := funlen.Run(file, pass.Fset, settings.Lines, settings.Statements, settings.IgnoreComments) lintIssues = append(lintIssues, fileIssues...) } diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/gci.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/gci.go index 4eb26dbdfc..3862267692 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/gci.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/gci.go @@ -118,7 +118,7 @@ func diffFormattedFilesToArray(paths []string, cfg gcicfg.Config, diffs *[]strin log.InitLogger() defer func() { _ = log.L().Sync() }() - return gci.ProcessFiles(io.GoFilesInPathsGenerator(paths), cfg, func(filePath string, unmodifiedFile, formattedFile []byte) error { + return gci.ProcessFiles(io.GoFilesInPathsGenerator(paths, true), cfg, func(filePath string, unmodifiedFile, formattedFile []byte) error { fileURI := span.URIFromPath(filePath) edits := myers.ComputeEdits(fileURI, string(unmodifiedFile), string(formattedFile)) unifiedEdits := gotextdiff.ToUnified(filePath, filePath, string(unmodifiedFile), edits) diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/ginkgolinter.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/ginkgolinter.go index b9e69b265d..8919de15bf 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/ginkgolinter.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/ginkgolinter.go @@ -14,12 +14,14 @@ func NewGinkgoLinter(cfg *config.GinkgoLinterSettings) *goanalysis.Linter { cfgMap := make(map[string]map[string]any) if cfg != nil { cfgMap[a.Name] = map[string]any{ - "suppress-len-assertion": cfg.SuppressLenAssertion, - "suppress-nil-assertion": cfg.SuppressNilAssertion, - "suppress-err-assertion": cfg.SuppressErrAssertion, - "suppress-compare-assertion": cfg.SuppressCompareAssertion, - "suppress-async-assertion": cfg.SuppressAsyncAssertion, - "allow-havelen-0": cfg.AllowHaveLenZero, + "suppress-len-assertion": cfg.SuppressLenAssertion, + "suppress-nil-assertion": cfg.SuppressNilAssertion, + "suppress-err-assertion": cfg.SuppressErrAssertion, + "suppress-compare-assertion": cfg.SuppressCompareAssertion, + "suppress-async-assertion": cfg.SuppressAsyncAssertion, + "suppress-type-compare-assertion": cfg.SuppressTypeCompareWarning, + "forbid-focus-container": cfg.ForbidFocusContainer, + "allow-havelen-0": cfg.AllowHaveLenZero, } } diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/gochecksumtype.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/gochecksumtype.go new file mode 100644 index 0000000000..fcc0cad588 --- /dev/null +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/gochecksumtype.go @@ -0,0 +1,80 @@ +package golinters + +import ( + "strings" + "sync" + + gochecksumtype "github.com/alecthomas/go-check-sumtype" + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/packages" + + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" + "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/result" +) + +const goCheckSumTypeName = "gochecksumtype" + +func NewGoCheckSumType() *goanalysis.Linter { + var mu sync.Mutex + var resIssues []goanalysis.Issue + + analyzer := &analysis.Analyzer{ + Name: goCheckSumTypeName, + Doc: goanalysis.TheOnlyanalyzerDoc, + Run: func(pass *analysis.Pass) (any, error) { + issues, err := runGoCheckSumType(pass) + if err != nil { + return nil, err + } + + if len(issues) == 0 { + return nil, nil + } + + mu.Lock() + resIssues = append(resIssues, issues...) + mu.Unlock() + + return nil, nil + }, + } + + return goanalysis.NewLinter( + goCheckSumTypeName, + `Run exhaustiveness checks on Go "sum types"`, + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(ctx *linter.Context) []goanalysis.Issue { + return resIssues + }).WithLoadMode(goanalysis.LoadModeTypesInfo) +} + +func runGoCheckSumType(pass *analysis.Pass) ([]goanalysis.Issue, error) { + var resIssues []goanalysis.Issue + + pkg := &packages.Package{ + Fset: pass.Fset, + Syntax: pass.Files, + Types: pass.Pkg, + TypesInfo: pass.TypesInfo, + } + + var unknownError error + errors := gochecksumtype.Run([]*packages.Package{pkg}) + for _, err := range errors { + err, ok := err.(gochecksumtype.Error) + if !ok { + unknownError = err + continue + } + + resIssues = append(resIssues, goanalysis.NewIssue(&result.Issue{ + FromLinter: goCheckSumTypeName, + Text: strings.TrimPrefix(err.Error(), err.Pos().String()+": "), + Pos: err.Pos(), + }, pass)) + } + + return resIssues, unknownError +} diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/gofmt_common.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/gofmt_common.go index cbed4e0bc0..6b7184d65b 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/gofmt_common.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/gofmt_common.go @@ -80,6 +80,16 @@ func (p *hunkChangesParser) parseDiffLines(h *diffpkg.Hunk) { ret = append(ret, dl) } + // if > 0, then the original file had a 'No newline at end of file' mark + if h.OrigNoNewlineAt > 0 { + dl := diffLine{ + originalNumber: currentOriginalLineNumber + 1, + typ: diffLineAdded, + data: "", + } + ret = append(ret, dl) + } + p.lines = ret } diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/govet.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/govet.go index 704dd6c577..4e16fb1429 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/govet.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/govet.go @@ -2,6 +2,7 @@ package golinters import ( "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/appends" "golang.org/x/tools/go/analysis/passes/asmdecl" "golang.org/x/tools/go/analysis/passes/assign" "golang.org/x/tools/go/analysis/passes/atomic" @@ -14,6 +15,8 @@ import ( "golang.org/x/tools/go/analysis/passes/copylock" _ "golang.org/x/tools/go/analysis/passes/ctrlflow" // unused, internal analyzer "golang.org/x/tools/go/analysis/passes/deepequalerrors" + "golang.org/x/tools/go/analysis/passes/defers" + "golang.org/x/tools/go/analysis/passes/directive" "golang.org/x/tools/go/analysis/passes/errorsas" "golang.org/x/tools/go/analysis/passes/fieldalignment" "golang.org/x/tools/go/analysis/passes/findcall" @@ -31,6 +34,7 @@ import ( "golang.org/x/tools/go/analysis/passes/shadow" "golang.org/x/tools/go/analysis/passes/shift" "golang.org/x/tools/go/analysis/passes/sigchanyzer" + "golang.org/x/tools/go/analysis/passes/slog" "golang.org/x/tools/go/analysis/passes/sortslice" "golang.org/x/tools/go/analysis/passes/stdmethods" "golang.org/x/tools/go/analysis/passes/stringintconv" @@ -50,6 +54,7 @@ import ( var ( allAnalyzers = []*analysis.Analyzer{ + appends.Analyzer, asmdecl.Analyzer, assign.Analyzer, atomic.Analyzer, @@ -60,6 +65,8 @@ var ( composite.Analyzer, copylock.Analyzer, deepequalerrors.Analyzer, + defers.Analyzer, + directive.Analyzer, errorsas.Analyzer, fieldalignment.Analyzer, findcall.Analyzer, @@ -75,6 +82,7 @@ var ( shadow.Analyzer, shift.Analyzer, sigchanyzer.Analyzer, + slog.Analyzer, sortslice.Analyzer, stdmethods.Analyzer, stringintconv.Analyzer, @@ -89,8 +97,9 @@ var ( unusedwrite.Analyzer, } - // https://github.com/golang/go/blob/9f834a559c9ed6cdf883e29b36e21e5f956df74f/src/cmd/vet/main.go#L46-L76 + // https://github.com/golang/go/blob/b56645a87b28840a180d64077877cb46570b4176/src/cmd/vet/main.go#L49-L81 defaultAnalyzers = []*analysis.Analyzer{ + appends.Analyzer, asmdecl.Analyzer, assign.Analyzer, atomic.Analyzer, @@ -99,6 +108,8 @@ var ( cgocall.Analyzer, composite.Analyzer, copylock.Analyzer, + defers.Analyzer, + directive.Analyzer, errorsas.Analyzer, framepointer.Analyzer, httpresponse.Analyzer, @@ -109,6 +120,7 @@ var ( printf.Analyzer, shift.Analyzer, sigchanyzer.Analyzer, + slog.Analyzer, stdmethods.Analyzer, stringintconv.Analyzer, structtag.Analyzer, diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/inamedparam.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/inamedparam.go new file mode 100644 index 0000000000..2906307552 --- /dev/null +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/inamedparam.go @@ -0,0 +1,19 @@ +package golinters + +import ( + "github.com/macabu/inamedparam" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" +) + +func NewINamedParam() *goanalysis.Linter { + a := inamedparam.Analyzer + + return goanalysis.NewLinter( + a.Name, + a.Doc, + []*analysis.Analyzer{a}, + nil, + ).WithLoadMode(goanalysis.LoadModeSyntax) +} diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/paralleltest.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/paralleltest.go index 92201e4e2f..4c03952c10 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/paralleltest.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/paralleltest.go @@ -9,13 +9,14 @@ import ( ) func NewParallelTest(settings *config.ParallelTestSettings) *goanalysis.Linter { - a := paralleltest.Analyzer + a := paralleltest.NewAnalyzer() var cfg map[string]map[string]any if settings != nil { cfg = map[string]map[string]any{ a.Name: { - "i": settings.IgnoreMissing, + "i": settings.IgnoreMissing, + "ignoremissingsubtests": settings.IgnoreMissingSubtests, }, } } diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/perfsprint.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/perfsprint.go new file mode 100644 index 0000000000..fb248a85dc --- /dev/null +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/perfsprint.go @@ -0,0 +1,19 @@ +package golinters + +import ( + "github.com/catenacyber/perfsprint/analyzer" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" +) + +func NewPerfSprint() *goanalysis.Linter { + a := analyzer.Analyzer + + return goanalysis.NewLinter( + a.Name, + a.Doc, + []*analysis.Analyzer{a}, + nil, + ).WithLoadMode(goanalysis.LoadModeTypesInfo) +} diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/protogetter.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/protogetter.go new file mode 100644 index 0000000000..23325ad55e --- /dev/null +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/protogetter.go @@ -0,0 +1,59 @@ +package golinters + +import ( + "sync" + + "github.com/ghostiam/protogetter" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" + "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/result" +) + +func NewProtoGetter() *goanalysis.Linter { + var mu sync.Mutex + var resIssues []goanalysis.Issue + + a := protogetter.NewAnalyzer() + a.Run = func(pass *analysis.Pass) (any, error) { + pgIssues := protogetter.Run(pass, protogetter.GolangciLintMode) + + issues := make([]goanalysis.Issue, len(pgIssues)) + for i, issue := range pgIssues { + report := &result.Issue{ + FromLinter: a.Name, + Pos: issue.Pos, + Text: issue.Message, + Replacement: &result.Replacement{ + Inline: &result.InlineFix{ + StartCol: issue.InlineFix.StartCol, + Length: issue.InlineFix.Length, + NewString: issue.InlineFix.NewString, + }, + }, + } + + issues[i] = goanalysis.NewIssue(report, pass) + } + + if len(issues) == 0 { + return nil, nil + } + + mu.Lock() + resIssues = append(resIssues, issues...) + mu.Unlock() + + return nil, nil + } + + return goanalysis.NewLinter( + a.Name, + a.Doc, + []*analysis.Analyzer{a}, + nil, + ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + return resIssues + }).WithLoadMode(goanalysis.LoadModeTypesInfo) +} diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/revive.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/revive.go index b57566e7af..28231957c4 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/revive.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/revive.go @@ -247,7 +247,7 @@ func safeTomlSlice(r []any) []any { } // This element is not exported by revive, so we need copy the code. -// Extracted from https://github.com/mgechev/revive/blob/v1.3.0/config/config.go#L15 +// Extracted from https://github.com/mgechev/revive/blob/v1.3.4/config/config.go#L15 var defaultRules = []lint.Rule{ &rule.VarDeclarationsRule{}, &rule.PackageCommentsRule{}, @@ -267,7 +267,6 @@ var defaultRules = []lint.Rule{ &rule.TimeNamingRule{}, &rule.ContextKeysType{}, &rule.ContextAsArgumentRule{}, - &rule.IfReturnRule{}, &rule.EmptyBlockRule{}, &rule.SuperfluousElseRule{}, &rule.UnusedParamRule{}, @@ -317,12 +316,17 @@ var allRules = append([]lint.Rule{ &rule.FunctionLength{}, &rule.NestedStructs{}, &rule.UselessBreak{}, + &rule.UncheckedTypeAssertionRule{}, &rule.TimeEqualRule{}, &rule.BannedCharsRule{}, &rule.OptimizeOperandsOrderRule{}, &rule.UseAnyRule{}, &rule.DataRaceRule{}, &rule.CommentSpacingsRule{}, + &rule.IfReturnRule{}, + &rule.RedundantImportAlias{}, + &rule.ImportAliasNamingRule{}, + &rule.EnforceMapStyleRule{}, }, defaultRules...) const defaultConfidence = 0.8 diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/sloglint.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/sloglint.go new file mode 100644 index 0000000000..b506d187fd --- /dev/null +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/sloglint.go @@ -0,0 +1,27 @@ +package golinters + +import ( + "go-simpler.org/sloglint" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" +) + +func NewSlogLint(settings *config.SlogLintSettings) *goanalysis.Linter { + var opts *sloglint.Options + if settings != nil { + opts = &sloglint.Options{ + KVOnly: settings.KVOnly, + AttrOnly: settings.AttrOnly, + NoRawKeys: settings.NoRawKeys, + ArgsOnSepLines: settings.ArgsOnSepLines, + } + } + + a := sloglint.New(opts) + + return goanalysis. + NewLinter(a.Name, a.Doc, []*analysis.Analyzer{a}, nil). + WithLoadMode(goanalysis.LoadModeTypesInfo) +} diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/tagalign.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/tagalign.go index 07b7564649..c23838f702 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/tagalign.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/tagalign.go @@ -24,6 +24,11 @@ func NewTagAlign(settings *config.TagAlignSettings) *goanalysis.Linter { if settings.Sort || len(settings.Order) > 0 { options = append(options, tagalign.WithSort(settings.Order...)) } + + // Strict style will be applied only if Align and Sort are enabled together. + if settings.Strict && settings.Align && settings.Sort { + options = append(options, tagalign.WithStrictStyle()) + } } analyzer := tagalign.NewAnalyzer(options...) diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/testifylint.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/testifylint.go new file mode 100644 index 0000000000..83bae2868a --- /dev/null +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/testifylint.go @@ -0,0 +1,36 @@ +package golinters + +import ( + "github.com/Antonboom/testifylint/analyzer" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" +) + +func NewTestifylint(settings *config.TestifylintSettings) *goanalysis.Linter { + a := analyzer.New() + + cfg := make(map[string]map[string]any) + if settings != nil { + cfg[a.Name] = map[string]any{ + "enable-all": settings.EnableAll, + } + if len(settings.EnabledCheckers) > 0 { + cfg[a.Name]["enable"] = settings.EnabledCheckers + } + if p := settings.ExpectedActual.ExpVarPattern; p != "" { + cfg[a.Name]["expected-actual.pattern"] = p + } + if m := settings.SuiteExtraAssertCall.Mode; m != "" { + cfg[a.Name]["suite-extra-assert-call.mode"] = m + } + } + + return goanalysis.NewLinter( + a.Name, + a.Doc, + []*analysis.Analyzer{a}, + cfg, + ).WithLoadMode(goanalysis.LoadModeTypesInfo) +} diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/unused.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/unused.go index aa9374d343..89ae7e98a2 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/unused.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/golinters/unused.go @@ -5,6 +5,9 @@ import ( "sync" "golang.org/x/tools/go/analysis" + "honnef.co/go/tools/analysis/facts/directives" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" "honnef.co/go/tools/unused" "github.com/golangci/golangci-lint/pkg/config" @@ -15,11 +18,7 @@ import ( const unusedName = "unused" -type UnusedSettings struct { - GoVersion string -} - -func NewUnused(settings *config.StaticCheckSettings) *goanalysis.Linter { +func NewUnused(settings *config.UnusedSettings, scSettings *config.StaticCheckSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue @@ -28,11 +27,7 @@ func NewUnused(settings *config.StaticCheckSettings) *goanalysis.Linter { Doc: unused.Analyzer.Analyzer.Doc, Requires: unused.Analyzer.Analyzer.Requires, Run: func(pass *analysis.Pass) (any, error) { - issues, err := runUnused(pass) - if err != nil { - return nil, err - } - + issues := runUnused(pass, settings) if len(issues) == 0 { return nil, nil } @@ -45,7 +40,7 @@ func NewUnused(settings *config.StaticCheckSettings) *goanalysis.Linter { }, } - setAnalyzerGoVersion(analyzer, getGoVersion(settings)) + setAnalyzerGoVersion(analyzer, getGoVersion(scSettings)) return goanalysis.NewLinter( unusedName, @@ -57,21 +52,18 @@ func NewUnused(settings *config.StaticCheckSettings) *goanalysis.Linter { }).WithLoadMode(goanalysis.LoadModeTypesInfo) } -func runUnused(pass *analysis.Pass) ([]goanalysis.Issue, error) { - res, err := unused.Analyzer.Analyzer.Run(pass) - if err != nil { - return nil, err - } +func runUnused(pass *analysis.Pass, cfg *config.UnusedSettings) []goanalysis.Issue { + res := getUnusedResults(pass, cfg) used := make(map[string]bool) - for _, obj := range res.(unused.Result).Used { + for _, obj := range res.Used { used[fmt.Sprintf("%s %d %s", obj.Position.Filename, obj.Position.Line, obj.Name)] = true } var issues []goanalysis.Issue // Inspired by https://github.com/dominikh/go-tools/blob/d694aadcb1f50c2d8ac0a1dd06217ebb9f654764/lintcmd/lint.go#L177-L197 - for _, object := range res.(unused.Result).Unused { + for _, object := range res.Unused { if object.Kind == "type param" { continue } @@ -90,5 +82,31 @@ func runUnused(pass *analysis.Pass) ([]goanalysis.Issue, error) { issues = append(issues, issue) } - return issues, nil + return issues +} + +func getUnusedResults(pass *analysis.Pass, settings *config.UnusedSettings) unused.Result { + opts := unused.Options{ + FieldWritesAreUses: settings.FieldWritesAreUses, + PostStatementsAreReads: settings.PostStatementsAreReads, + ExportedIsUsed: settings.ExportedIsUsed, + ExportedFieldsAreUsed: settings.ExportedFieldsAreUsed, + ParametersAreUsed: settings.ParametersAreUsed, + LocalVariablesAreUsed: settings.LocalVariablesAreUsed, + GeneratedIsUsed: settings.GeneratedIsUsed, + } + + // ref: https://github.com/dominikh/go-tools/blob/4ec1f474ca6c0feb8e10a8fcca4ab95f5b5b9881/internal/cmd/unused/unused.go#L68 + nodes := unused.Graph(pass.Fset, + pass.Files, + pass.Pkg, + pass.TypesInfo, + pass.ResultOf[directives.Analyzer].([]lint.Directive), + pass.ResultOf[generated.Analyzer].(map[string]generated.Generator), + opts, + ) + + sg := unused.SerializedGraph{} + sg.Merge(nodes) + return sg.Results() } diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/lint/linter/config.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/lint/linter/config.go index 5891ec2770..c911b5613d 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/lint/linter/config.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/lint/linter/config.go @@ -42,6 +42,7 @@ type Config struct { AlternativeNames []string OriginalURL string // URL of original (not forked) repo, needed for autogenerated README + Internal bool // Internal linters cannot be disabled (ex: typecheck). CanAutoFix bool IsSlow bool DoesChangeTypes bool @@ -55,6 +56,11 @@ func (lc *Config) WithEnabledByDefault() *Config { return lc } +func (lc *Config) WithInternal() *Config { + lc.Internal = true + return lc +} + func (lc *Config) ConsiderSlow() *Config { lc.IsSlow = true return lc @@ -128,7 +134,7 @@ func (lc *Config) Name() string { } func (lc *Config) WithNoopFallback(cfg *config.Config) *Config { - if cfg != nil && config.IsGreaterThanOrEqualGo118(cfg.Run.Go) { + if cfg != nil && config.IsGreaterThanOrEqualGo121(cfg.Run.Go) { lc.Linter = &Noop{ name: lc.Linter.Name(), desc: lc.Linter.Desc(), diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/custom_linters.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/custom_linters.go new file mode 100644 index 0000000000..d0eaa7905f --- /dev/null +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/custom_linters.go @@ -0,0 +1,131 @@ +package lintersdb + +import ( + "fmt" + "os" + "path/filepath" + "plugin" + + "github.com/hashicorp/go-multierror" + "github.com/spf13/viper" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" + "github.com/golangci/golangci-lint/pkg/lint/linter" +) + +type AnalyzerPlugin interface { + GetAnalyzers() []*analysis.Analyzer +} + +// getCustomLinterConfigs loads private linters that are specified in the golangci config file. +func (m *Manager) getCustomLinterConfigs() []*linter.Config { + if m.cfg == nil || m.log == nil { + return nil + } + + var linters []*linter.Config + + for name, settings := range m.cfg.LintersSettings.Custom { + lc, err := m.loadCustomLinterConfig(name, settings) + if err != nil { + m.log.Errorf("Unable to load custom analyzer %s:%s, %v", name, settings.Path, err) + } else { + linters = append(linters, lc) + } + } + + return linters +} + +// loadCustomLinterConfig loads the configuration of private linters. +// Private linters are dynamically loaded from .so plugin files. +func (m *Manager) loadCustomLinterConfig(name string, settings config.CustomLinterSettings) (*linter.Config, error) { + analyzers, err := m.getAnalyzerPlugin(settings.Path, settings.Settings) + if err != nil { + return nil, err + } + + m.log.Infof("Loaded %s: %s", settings.Path, name) + + customLinter := goanalysis.NewLinter(name, settings.Description, analyzers, nil). + WithLoadMode(goanalysis.LoadModeTypesInfo) + + linterConfig := linter.NewConfig(customLinter). + WithEnabledByDefault(). + WithLoadForGoAnalysis(). + WithURL(settings.OriginalURL) + + return linterConfig, nil +} + +// getAnalyzerPlugin loads a private linter as specified in the config file, +// loads the plugin from a .so file, +// and returns the 'AnalyzerPlugin' interface implemented by the private plugin. +// An error is returned if the private linter cannot be loaded +// or the linter does not implement the AnalyzerPlugin interface. +func (m *Manager) getAnalyzerPlugin(path string, settings any) ([]*analysis.Analyzer, error) { + if !filepath.IsAbs(path) { + // resolve non-absolute paths relative to config file's directory + configFilePath := viper.ConfigFileUsed() + absConfigFilePath, err := filepath.Abs(configFilePath) + if err != nil { + return nil, fmt.Errorf("could not get absolute representation of config file path %q: %v", configFilePath, err) + } + path = filepath.Join(filepath.Dir(absConfigFilePath), path) + } + + plug, err := plugin.Open(path) + if err != nil { + return nil, err + } + + analyzers, err := m.lookupPlugin(plug, settings) + if err != nil { + return nil, fmt.Errorf("lookup plugin %s: %w", path, err) + } + + return analyzers, nil +} + +func (m *Manager) lookupPlugin(plug *plugin.Plugin, settings any) ([]*analysis.Analyzer, error) { + symbol, err := plug.Lookup("New") + if err != nil { + analyzers, errP := m.lookupAnalyzerPlugin(plug) + if errP != nil { + // TODO(ldez): use `errors.Join` when we will upgrade to go1.20. + return nil, multierror.Append(err, errP) + } + + return analyzers, nil + } + + // The type func cannot be used here, must be the explicit signature. + constructor, ok := symbol.(func(any) ([]*analysis.Analyzer, error)) + if !ok { + return nil, fmt.Errorf("plugin does not abide by 'New' function: %T", symbol) + } + + return constructor(settings) +} + +func (m *Manager) lookupAnalyzerPlugin(plug *plugin.Plugin) ([]*analysis.Analyzer, error) { + symbol, err := plug.Lookup("AnalyzerPlugin") + if err != nil { + return nil, err + } + + // TODO(ldez): remove this env var (but keep the log) in the next minor version (v1.55.0) + if _, ok := os.LookupEnv("GOLANGCI_LINT_HIDE_WARNING_ABOUT_PLUGIN_API_DEPRECATION"); !ok { + m.log.Warnf("plugin: 'AnalyzerPlugin' plugins are deprecated, please use the new plugin signature: " + + "https://golangci-lint.run/contributing/new-linters/#create-a-plugin") + } + + analyzerPlugin, ok := symbol.(AnalyzerPlugin) + if !ok { + return nil, fmt.Errorf("plugin does not abide by 'AnalyzerPlugin' interface: %T", symbol) + } + + return analyzerPlugin.GetAnalyzers(), nil +} diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/enabled_set.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/enabled_set.go index 92615b57aa..c5c7874e45 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/enabled_set.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/enabled_set.go @@ -31,8 +31,10 @@ func NewEnabledSet(m *Manager, v *Validator, log logutils.Log, cfg *config.Confi } } +//nolint:gocyclo // the complexity cannot be reduced. func (es EnabledSet) build(lcfg *config.Linters, enabledByDefaultLinters []*linter.Config) map[string]*linter.Config { es.debugf("Linters config: %#v", lcfg) + resultLintersSet := map[string]*linter.Config{} switch { case len(lcfg.Presets) != 0: @@ -78,6 +80,14 @@ func (es EnabledSet) build(lcfg *config.Linters, enabledByDefaultLinters []*lint } } + // typecheck is not a real linter and cannot be disabled. + if _, ok := resultLintersSet["typecheck"]; !ok && (es.cfg == nil || !es.cfg.InternalCmdTest) { + for _, lc := range es.m.GetLinterConfigs("typecheck") { + // it's important to use lc.Name() nor name because name can be alias + resultLintersSet[lc.Name()] = lc + } + } + return resultLintersSet } @@ -134,8 +144,8 @@ func (es EnabledSet) GetOptimizedLinters() ([]*linter.Config, error) { func (es EnabledSet) combineGoAnalysisLinters(linters map[string]*linter.Config) { var goanalysisLinters []*goanalysis.Linter goanalysisPresets := map[string]bool{} - for _, linter := range linters { - lnt, ok := linter.Linter.(*goanalysis.Linter) + for _, lc := range linters { + lnt, ok := lc.Linter.(*goanalysis.Linter) if !ok { continue } @@ -144,7 +154,7 @@ func (es EnabledSet) combineGoAnalysisLinters(linters map[string]*linter.Config) continue } goanalysisLinters = append(goanalysisLinters, lnt) - for _, p := range linter.InPresets { + for _, p := range lc.InPresets { goanalysisPresets[p] = true } } @@ -197,6 +207,10 @@ func (es EnabledSet) combineGoAnalysisLinters(linters map[string]*linter.Config) func (es EnabledSet) verbosePrintLintersStatus(lcs map[string]*linter.Config) { var linterNames []string for _, lc := range lcs { + if lc.Internal { + continue + } + linterNames = append(linterNames, lc.Name()) } sort.StringSlice(linterNames).Sort() diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/manager.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/manager.go index 5a04fe193b..fd329ce57f 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/manager.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/manager.go @@ -1,29 +1,25 @@ package lintersdb import ( - "fmt" - "path/filepath" - "plugin" - - "github.com/spf13/viper" - "golang.org/x/tools/go/analysis" + "regexp" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters" - "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/logutils" - "github.com/golangci/golangci-lint/pkg/report" ) type Manager struct { - nameToLCs map[string][]*linter.Config - cfg *config.Config - log logutils.Log + cfg *config.Config + log logutils.Log + + nameToLCs map[string][]*linter.Config + customLinters []*linter.Config } func NewManager(cfg *config.Config, log logutils.Log) *Manager { m := &Manager{cfg: cfg, log: log} + m.customLinters = m.getCustomLinterConfigs() nameToLCs := make(map[string][]*linter.Config) for _, lc := range m.GetAllSupportedLinterConfigs() { @@ -37,28 +33,6 @@ func NewManager(cfg *config.Config, log logutils.Log) *Manager { return m } -// WithCustomLinters loads private linters that are specified in the golangci config file. -func (m *Manager) WithCustomLinters() *Manager { - if m.log == nil { - m.log = report.NewLogWrapper(logutils.NewStderrLog(logutils.DebugKeyEmpty), &report.Data{}) - } - if m.cfg != nil { - for name, settings := range m.cfg.LintersSettings.Custom { - lc, err := m.loadCustomLinterConfig(name, settings) - - if err != nil { - m.log.Errorf("Unable to load custom analyzer %s:%s, %v", - name, - settings.Path, - err) - } else { - m.nameToLCs[name] = append(m.nameToLCs[name], lc) - } - } - } - return m -} - func (Manager) AllPresets() []string { return []string{ linter.PresetBugs, @@ -153,16 +127,18 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { reassignCfg *config.ReassignSettings reviveCfg *config.ReviveSettings rowserrcheckCfg *config.RowsErrCheckSettings + sloglintCfg *config.SlogLintSettings staticcheckCfg *config.StaticCheckSettings structcheckCfg *config.StructCheckSettings stylecheckCfg *config.StaticCheckSettings tagalignCfg *config.TagAlignSettings tagliatelleCfg *config.TagliatelleSettings tenvCfg *config.TenvSettings + testifylintCfg *config.TestifylintSettings testpackageCfg *config.TestpackageSettings thelperCfg *config.ThelperSettings unparamCfg *config.UnparamSettings - unusedCfg *config.StaticCheckSettings + unusedCfg *config.UnusedSettings usestdlibvars *config.UseStdlibVarsSettings varcheckCfg *config.VarCheckSettings varnamelenCfg *config.VarnamelenSettings @@ -233,16 +209,18 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { reassignCfg = &m.cfg.LintersSettings.Reassign reviveCfg = &m.cfg.LintersSettings.Revive rowserrcheckCfg = &m.cfg.LintersSettings.RowsErrCheck + sloglintCfg = &m.cfg.LintersSettings.SlogLint staticcheckCfg = &m.cfg.LintersSettings.Staticcheck structcheckCfg = &m.cfg.LintersSettings.Structcheck stylecheckCfg = &m.cfg.LintersSettings.Stylecheck tagalignCfg = &m.cfg.LintersSettings.TagAlign tagliatelleCfg = &m.cfg.LintersSettings.Tagliatelle tenvCfg = &m.cfg.LintersSettings.Tenv + testifylintCfg = &m.cfg.LintersSettings.Testifylint testpackageCfg = &m.cfg.LintersSettings.Testpackage thelperCfg = &m.cfg.LintersSettings.Thelper unparamCfg = &m.cfg.LintersSettings.Unparam - unusedCfg = new(config.StaticCheckSettings) + unusedCfg = &m.cfg.LintersSettings.Unused usestdlibvars = &m.cfg.LintersSettings.UseStdlibVars varcheckCfg = &m.cfg.LintersSettings.Varcheck varnamelenCfg = &m.cfg.LintersSettings.Varnamelen @@ -255,7 +233,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { } if gocriticCfg != nil { - gocriticCfg.Go = m.cfg.Run.Go + gocriticCfg.Go = trimGoVersion(m.cfg.Run.Go) } if gofumptCfg != nil && gofumptCfg.LangVersion == "" { @@ -263,24 +241,24 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { } if staticcheckCfg != nil && staticcheckCfg.GoVersion == "" { - staticcheckCfg.GoVersion = m.cfg.Run.Go + staticcheckCfg.GoVersion = trimGoVersion(m.cfg.Run.Go) } if gosimpleCfg != nil && gosimpleCfg.GoVersion == "" { - gosimpleCfg.GoVersion = m.cfg.Run.Go + gosimpleCfg.GoVersion = trimGoVersion(m.cfg.Run.Go) } if stylecheckCfg != nil && stylecheckCfg.GoVersion != "" { - stylecheckCfg.GoVersion = m.cfg.Run.Go - } - if unusedCfg != nil && unusedCfg.GoVersion == "" { - unusedCfg.GoVersion = m.cfg.Run.Go + stylecheckCfg.GoVersion = trimGoVersion(m.cfg.Run.Go) } } const megacheckName = "megacheck" + var linters []*linter.Config + linters = append(linters, m.customLinters...) + // The linters are sorted in the alphabetical order (case-insensitive). // When a new linter is added the version in `WithSince(...)` must be the next minor version of golangci-lint. - return []*linter.Config{ + linters = append(linters, linter.NewConfig(golinters.NewAsasalint(asasalintCfg)). WithSince("1.47.0"). WithPresets(linter.PresetBugs). @@ -462,6 +440,12 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithSince("v1.12.0"). WithPresets(linter.PresetStyle), + linter.NewConfig(golinters.NewGoCheckSumType()). + WithSince("v1.55.0"). + WithPresets(linter.PresetBugs). + WithLoadForGoAnalysis(). + WithURL("https://github.com/alecthomas/go-check-sumtype"), + linter.NewConfig(golinters.NewGocognit(gocognitCfg)). WithSince("v1.20.0"). WithPresets(linter.PresetComplexity). @@ -596,6 +580,11 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithLoadForGoAnalysis(). WithURL("https://github.com/julz/importas"), + linter.NewConfig(golinters.NewINamedParam()). + WithSince("v1.55.0"). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/macabu/inamedparam"), + linter.NewConfig(golinters.NewIneffassign()). WithEnabledByDefault(). WithSince("v1.0.0"). @@ -723,6 +712,12 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetStyle, linter.PresetTest). WithURL("https://github.com/kunwardeep/paralleltest"), + linter.NewConfig(golinters.NewPerfSprint()). + WithSince("v1.55.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetPerformance). + WithURL("https://github.com/catenacyber/perfsprint"), + linter.NewConfig(golinters.NewPreAlloc(preallocCfg)). WithSince("v1.19.0"). WithPresets(linter.PresetPerformance). @@ -738,6 +733,13 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetStyle). WithURL("https://github.com/yeya24/promlinter"), + linter.NewConfig(golinters.NewProtoGetter()). + WithSince("v1.55.0"). + WithPresets(linter.PresetBugs). + WithLoadForGoAnalysis(). + WithAutoFix(). + WithURL("https://github.com/ghostiam/protogetter"), + linter.NewConfig(golinters.NewReassign(reassignCfg)). WithSince("1.49.0"). WithPresets(linter.PresetBugs). @@ -756,6 +758,12 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetBugs, linter.PresetSQL). WithURL("https://github.com/jingyugao/rowserrcheck"), + linter.NewConfig(golinters.NewSlogLint(sloglintCfg)). + WithSince("v1.55.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetStyle, linter.PresetFormatting). + WithURL("https://github.com/go-simpler/sloglint"), + linter.NewConfig(golinters.NewScopelint()). WithSince("v1.12.0"). WithPresets(linter.PresetBugs). @@ -811,6 +819,12 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetTest). WithURL("https://github.com/maratori/testableexamples"), + linter.NewConfig(golinters.NewTestifylint(testifylintCfg)). + WithSince("v1.55.0"). + WithPresets(linter.PresetTest, linter.PresetBugs). + WithLoadForGoAnalysis(). + WithURL("https://github.com/Antonboom/testifylint"), + linter.NewConfig(golinters.NewTestpackage(testpackageCfg)). WithSince("v1.25.0"). WithPresets(linter.PresetStyle, linter.PresetTest). @@ -829,6 +843,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithURL("https://github.com/moricho/tparallel"), linter.NewConfig(golinters.NewTypecheck()). + WithInternal(). WithEnabledByDefault(). WithSince("v1.3.0"). WithLoadForGoAnalysis(). @@ -847,7 +862,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithLoadForGoAnalysis(). WithURL("https://github.com/mvdan/unparam"), - linter.NewConfig(golinters.NewUnused(unusedCfg)). + linter.NewConfig(golinters.NewUnused(unusedCfg, staticcheckCfg)). WithEnabledByDefault(). WithSince("v1.20.0"). WithLoadForGoAnalysis(). @@ -898,18 +913,20 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetStyle). WithURL("https://github.com/bombsimon/wsl"), + linter.NewConfig(golinters.NewZerologLint()). + WithSince("v1.53.0"). + WithPresets(linter.PresetBugs). + WithLoadForGoAnalysis(). + WithURL("https://github.com/ykadowak/zerologlint"), + // nolintlint must be last because it looks at the results of all the previous linters for unused nolint directives linter.NewConfig(golinters.NewNoLintLint(noLintLintCfg)). WithSince("v1.26.0"). WithPresets(linter.PresetStyle). WithURL("https://github.com/golangci/golangci-lint/blob/master/pkg/golinters/nolintlint/README.md"), + ) - linter.NewConfig(golinters.NewZerologLint()). - WithSince("v1.53.0"). - WithPresets(linter.PresetBugs). - WithLoadForGoAnalysis(). - WithURL("https://github.com/ykadowak/zerologlint"), - } + return linters } func (m Manager) GetAllEnabledByDefaultLinters() []*linter.Config { @@ -951,62 +968,20 @@ func (m Manager) GetAllLinterConfigsForPreset(p string) []*linter.Config { return ret } -// loadCustomLinterConfig loads the configuration of private linters. -// Private linters are dynamically loaded from .so plugin files. -func (m Manager) loadCustomLinterConfig(name string, settings config.CustomLinterSettings) (*linter.Config, error) { - analyzer, err := m.getAnalyzerPlugin(settings.Path) - if err != nil { - return nil, err - } - m.log.Infof("Loaded %s: %s", settings.Path, name) - customLinter := goanalysis.NewLinter( - name, - settings.Description, - analyzer.GetAnalyzers(), - nil).WithLoadMode(goanalysis.LoadModeTypesInfo) - - linterConfig := linter.NewConfig(customLinter). - WithEnabledByDefault(). - WithLoadForGoAnalysis(). - WithURL(settings.OriginalURL) - - return linterConfig, nil -} - -type AnalyzerPlugin interface { - GetAnalyzers() []*analysis.Analyzer -} - -// getAnalyzerPlugin loads a private linter as specified in the config file, -// loads the plugin from a .so file, and returns the 'AnalyzerPlugin' interface -// implemented by the private plugin. -// An error is returned if the private linter cannot be loaded or the linter -// does not implement the AnalyzerPlugin interface. -func (m Manager) getAnalyzerPlugin(path string) (AnalyzerPlugin, error) { - if !filepath.IsAbs(path) { - // resolve non-absolute paths relative to config file's directory - configFilePath := viper.ConfigFileUsed() - absConfigFilePath, err := filepath.Abs(configFilePath) - if err != nil { - return nil, fmt.Errorf("could not get absolute representation of config file path %q: %v", configFilePath, err) - } - path = filepath.Join(filepath.Dir(absConfigFilePath), path) +// Trims the Go version to keep only M.m. +// Since Go 1.21 the version inside the go.mod can be a patched version (ex: 1.21.0). +// https://go.dev/doc/toolchain#versions +// This a problem with staticcheck and gocritic. +func trimGoVersion(v string) string { + if v == "" { + return "" } - plug, err := plugin.Open(path) - if err != nil { - return nil, err - } - - symbol, err := plug.Lookup("AnalyzerPlugin") - if err != nil { - return nil, err - } + exp := regexp.MustCompile(`(\d\.\d+)\.\d+`) - analyzerPlugin, ok := symbol.(AnalyzerPlugin) - if !ok { - return nil, fmt.Errorf("plugin %s does not abide by 'AnalyzerPlugin' interface", path) + if exp.MatchString(v) { + return exp.FindStringSubmatch(v)[1] } - return analyzerPlugin, nil + return v } diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/logutils/logutils.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/logutils/logutils.go index 80c9fed7a9..94479bc7b9 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/logutils/logutils.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/logutils/logutils.go @@ -59,7 +59,7 @@ const ( DebugKeyGoCritic = "gocritic" // Debugs `go-critic` linter. DebugKeyMegacheck = "megacheck" // Debugs `staticcheck` related linters. DebugKeyNolint = "nolint" // Debugs a filter excluding issues by `//nolint` comments. - DebugKeyRevive = "revive" // Debugs `revice` linter. + DebugKeyRevive = "revive" // Debugs `revive` linter. ) func getEnabledDebugs() map[string]bool { diff --git a/tools/vendor/github.com/golangci/golangci-lint/pkg/printers/github.go b/tools/vendor/github.com/golangci/golangci-lint/pkg/printers/github.go index 7f148097ab..c1da9df9c6 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/pkg/printers/github.go +++ b/tools/vendor/github.com/golangci/golangci-lint/pkg/printers/github.go @@ -3,6 +3,7 @@ package printers import ( "fmt" "io" + "path/filepath" "github.com/golangci/golangci-lint/pkg/result" ) @@ -26,7 +27,12 @@ func formatIssueAsGithub(issue *result.Issue) string { severity = issue.Severity } - ret := fmt.Sprintf("::%s file=%s,line=%d", severity, issue.FilePath(), issue.Line()) + // Convert backslashes to forward slashes. + // This is needed when running on windows. + // Otherwise, GitHub won't be able to show the annotations pointing to the file path with backslashes. + file := filepath.ToSlash(issue.FilePath()) + + ret := fmt.Sprintf("::%s file=%s,line=%d", severity, file, issue.Line()) if issue.Pos.Column != 0 { ret += fmt.Sprintf(",col=%d", issue.Pos.Column) } diff --git a/tools/vendor/github.com/golangci/misspell/.golangci.yml b/tools/vendor/github.com/golangci/misspell/.golangci.yml index 1a53216ae9..31c566eab3 100644 --- a/tools/vendor/github.com/golangci/misspell/.golangci.yml +++ b/tools/vendor/github.com/golangci/misspell/.golangci.yml @@ -21,10 +21,13 @@ linters-settings: gofumpt: extra-rules: true depguard: - list-type: blacklist - include-go-root: false - packages: - - github.com/pkg/errors + rules: + main: + deny: + - pkg: "github.com/instana/testify" + desc: not allowed + - pkg: "github.com/pkg/errors" + desc: Should be replaced by standard lib errors package godox: keywords: - FIXME diff --git a/tools/vendor/github.com/golangci/misspell/Dockerfile b/tools/vendor/github.com/golangci/misspell/Dockerfile index ce55fe61be..788ce3a775 100644 --- a/tools/vendor/github.com/golangci/misspell/Dockerfile +++ b/tools/vendor/github.com/golangci/misspell/Dockerfile @@ -1,7 +1,7 @@ -FROM golang:1.10.0-alpine +FROM golang:1.19-alpine # cache buster -RUN echo 4 +RUN echo 4 # git is needed for "go get" below RUN apk add --no-cache git make @@ -32,3 +32,4 @@ RUN /bin/true \ && wget -O /scowl-wl/words-US-60.txt ${SOURCE_US} \ && wget -O /scowl-wl/words-GB-ise-60.txt ${SOURCE_GB_ISE} +RUN git config --global --add safe.directory "/go/src/github.com/golangci/misspell" diff --git a/tools/vendor/github.com/golangci/misspell/Makefile b/tools/vendor/github.com/golangci/misspell/Makefile index e64a84b557..783f977cb4 100644 --- a/tools/vendor/github.com/golangci/misspell/Makefile +++ b/tools/vendor/github.com/golangci/misspell/Makefile @@ -1,11 +1,11 @@ -CONTAINER=nickg/misspell +CONTAINER=golangci/misspell default: lint test build install: ## install misspell into GOPATH/bin go install ./cmd/misspell -build: ## build and lint misspell +build: ## build misspell go build ./cmd/misspell test: ## run all tests @@ -14,10 +14,6 @@ test: ## run all tests lint: ## run linter golangci-lint run -# real publishing is done only by travis -publish: ## test goreleaser - ./scripts/goreleaser-dryrun.sh - # the grep in line 2 is to remove misspellings in the spelling dictionary # that trigger false positives!! falsepositives: /scowl-wl @@ -42,19 +38,16 @@ clean: ## clean up time go clean ./... git gc --aggressive -ci: ## run test like travis-ci does, requires docker +ci: docker-build ## run test like travis-ci does, requires docker docker run --rm \ -v $(PWD):/go/src/github.com/golangci/misspell \ -w /go/src/github.com/golangci/misspell \ ${CONTAINER} \ - make build falsepositives + make install falsepositives docker-build: ## build a docker test image docker build -t ${CONTAINER} . -docker-pull: ## pull latest test image - docker pull ${CONTAINER} - docker-console: ## log into the test image docker run --rm -it \ -v $(PWD):/go/src/github.com/golangci/misspell \ diff --git a/tools/vendor/github.com/golangci/misspell/case.go b/tools/vendor/github.com/golangci/misspell/case.go index 88ad44fa8c..0b580bedbc 100644 --- a/tools/vendor/github.com/golangci/misspell/case.go +++ b/tools/vendor/github.com/golangci/misspell/case.go @@ -43,9 +43,9 @@ func CaseStyle(word string) WordCase { } // CaseVariations returns: -// If AllUpper or First-Letter-Only is upcased: add the all upper case version. -// If AllLower, add the original, the title and upcase forms. -// If Mixed, return the original, and the all upcase form. +// If AllUpper or First-Letter-Only is upper-cased: add the all upper case version. +// If AllLower, add the original, the title and upper-case forms. +// If Mixed, return the original, and the all upper-case form. func CaseVariations(word string, style WordCase) []string { switch style { case CaseLower: diff --git a/tools/vendor/github.com/golangci/misspell/goreleaser.yml b/tools/vendor/github.com/golangci/misspell/goreleaser.yml index b4c8c099c9..97aa83e5ac 100644 --- a/tools/vendor/github.com/golangci/misspell/goreleaser.yml +++ b/tools/vendor/github.com/golangci/misspell/goreleaser.yml @@ -1,8 +1,7 @@ project_name: misspell builds: - - - main: cmd/misspell/main.go + - main: cmd/misspell/main.go binary: misspell ldflags: -s -w -X main.version={{.Version}} goos: diff --git a/tools/vendor/github.com/golangci/misspell/install-misspell.sh b/tools/vendor/github.com/golangci/misspell/install-misspell.sh index e24a84a20b..51e9b3372a 100644 --- a/tools/vendor/github.com/golangci/misspell/install-misspell.sh +++ b/tools/vendor/github.com/golangci/misspell/install-misspell.sh @@ -6,12 +6,12 @@ set -e usage() { this=$1 cat <= major*1_000_000_000+minor*1_000_000+patch*1_000 +} diff --git a/tools/vendor/github.com/google/go-cmp/cmp/compare.go b/tools/vendor/github.com/google/go-cmp/cmp/compare.go index 087320da7f..0f5b8a48c6 100644 --- a/tools/vendor/github.com/google/go-cmp/cmp/compare.go +++ b/tools/vendor/github.com/google/go-cmp/cmp/compare.go @@ -5,7 +5,7 @@ // Package cmp determines equality of values. // // This package is intended to be a more powerful and safer alternative to -// reflect.DeepEqual for comparing whether two values are semantically equal. +// [reflect.DeepEqual] for comparing whether two values are semantically equal. // It is intended to only be used in tests, as performance is not a goal and // it may panic if it cannot compare the values. Its propensity towards // panicking means that its unsuitable for production environments where a @@ -18,16 +18,17 @@ // For example, an equality function may report floats as equal so long as // they are within some tolerance of each other. // -// - Types with an Equal method may use that method to determine equality. -// This allows package authors to determine the equality operation -// for the types that they define. +// - Types with an Equal method (e.g., [time.Time.Equal]) may use that method +// to determine equality. This allows package authors to determine +// the equality operation for the types that they define. // // - If no custom equality functions are used and no Equal method is defined, // equality is determined by recursively comparing the primitive kinds on -// both values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, +// both values, much like [reflect.DeepEqual]. Unlike [reflect.DeepEqual], // unexported fields are not compared by default; they result in panics -// unless suppressed by using an Ignore option (see cmpopts.IgnoreUnexported) -// or explicitly compared using the Exporter option. +// unless suppressed by using an [Ignore] option +// (see [github.com/google/go-cmp/cmp/cmpopts.IgnoreUnexported]) +// or explicitly compared using the [Exporter] option. package cmp import ( @@ -45,14 +46,14 @@ import ( // Equal reports whether x and y are equal by recursively applying the // following rules in the given order to x and y and all of their sub-values: // -// - Let S be the set of all Ignore, Transformer, and Comparer options that +// - Let S be the set of all [Ignore], [Transformer], and [Comparer] options that // remain after applying all path filters, value filters, and type filters. -// If at least one Ignore exists in S, then the comparison is ignored. -// If the number of Transformer and Comparer options in S is non-zero, +// If at least one [Ignore] exists in S, then the comparison is ignored. +// If the number of [Transformer] and [Comparer] options in S is non-zero, // then Equal panics because it is ambiguous which option to use. -// If S contains a single Transformer, then use that to transform +// If S contains a single [Transformer], then use that to transform // the current values and recursively call Equal on the output values. -// If S contains a single Comparer, then use that to compare the current values. +// If S contains a single [Comparer], then use that to compare the current values. // Otherwise, evaluation proceeds to the next rule. // // - If the values have an Equal method of the form "(T) Equal(T) bool" or @@ -66,21 +67,22 @@ import ( // Functions are only equal if they are both nil, otherwise they are unequal. // // Structs are equal if recursively calling Equal on all fields report equal. -// If a struct contains unexported fields, Equal panics unless an Ignore option -// (e.g., cmpopts.IgnoreUnexported) ignores that field or the Exporter option -// explicitly permits comparing the unexported field. +// If a struct contains unexported fields, Equal panics unless an [Ignore] option +// (e.g., [github.com/google/go-cmp/cmp/cmpopts.IgnoreUnexported]) ignores that field +// or the [Exporter] option explicitly permits comparing the unexported field. // // Slices are equal if they are both nil or both non-nil, where recursively // calling Equal on all non-ignored slice or array elements report equal. // Empty non-nil slices and nil slices are not equal; to equate empty slices, -// consider using cmpopts.EquateEmpty. +// consider using [github.com/google/go-cmp/cmp/cmpopts.EquateEmpty]. // // Maps are equal if they are both nil or both non-nil, where recursively // calling Equal on all non-ignored map entries report equal. // Map keys are equal according to the == operator. -// To use custom comparisons for map keys, consider using cmpopts.SortMaps. +// To use custom comparisons for map keys, consider using +// [github.com/google/go-cmp/cmp/cmpopts.SortMaps]. // Empty non-nil maps and nil maps are not equal; to equate empty maps, -// consider using cmpopts.EquateEmpty. +// consider using [github.com/google/go-cmp/cmp/cmpopts.EquateEmpty]. // // Pointers and interfaces are equal if they are both nil or both non-nil, // where they have the same underlying concrete type and recursively diff --git a/tools/vendor/github.com/google/go-cmp/cmp/export_unsafe.go b/tools/vendor/github.com/google/go-cmp/cmp/export.go similarity index 94% rename from tools/vendor/github.com/google/go-cmp/cmp/export_unsafe.go rename to tools/vendor/github.com/google/go-cmp/cmp/export.go index e2c0f74e83..29f82fe6b2 100644 --- a/tools/vendor/github.com/google/go-cmp/cmp/export_unsafe.go +++ b/tools/vendor/github.com/google/go-cmp/cmp/export.go @@ -2,9 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !purego -// +build !purego - package cmp import ( @@ -12,8 +9,6 @@ import ( "unsafe" ) -const supportExporters = true - // retrieveUnexportedField uses unsafe to forcibly retrieve any field from // a struct such that the value has read-write permissions. // diff --git a/tools/vendor/github.com/google/go-cmp/cmp/export_panic.go b/tools/vendor/github.com/google/go-cmp/cmp/export_panic.go deleted file mode 100644 index ae851fe53f..0000000000 --- a/tools/vendor/github.com/google/go-cmp/cmp/export_panic.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2017, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build purego -// +build purego - -package cmp - -import "reflect" - -const supportExporters = false - -func retrieveUnexportedField(reflect.Value, reflect.StructField, bool) reflect.Value { - panic("no support for forcibly accessing unexported fields") -} diff --git a/tools/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go b/tools/vendor/github.com/google/go-cmp/cmp/internal/value/pointer.go similarity index 95% rename from tools/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go rename to tools/vendor/github.com/google/go-cmp/cmp/internal/value/pointer.go index 16e6860af6..e5dfff69af 100644 --- a/tools/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go +++ b/tools/vendor/github.com/google/go-cmp/cmp/internal/value/pointer.go @@ -2,9 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !purego -// +build !purego - package value import ( diff --git a/tools/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go b/tools/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go deleted file mode 100644 index 1a71bfcbd3..0000000000 --- a/tools/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2018, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build purego -// +build purego - -package value - -import "reflect" - -// Pointer is an opaque typed pointer and is guaranteed to be comparable. -type Pointer struct { - p uintptr - t reflect.Type -} - -// PointerOf returns a Pointer from v, which must be a -// reflect.Ptr, reflect.Slice, or reflect.Map. -func PointerOf(v reflect.Value) Pointer { - // NOTE: Storing a pointer as an uintptr is technically incorrect as it - // assumes that the GC implementation does not use a moving collector. - return Pointer{v.Pointer(), v.Type()} -} - -// IsNil reports whether the pointer is nil. -func (p Pointer) IsNil() bool { - return p.p == 0 -} - -// Uintptr returns the pointer as a uintptr. -func (p Pointer) Uintptr() uintptr { - return p.p -} diff --git a/tools/vendor/github.com/google/go-cmp/cmp/options.go b/tools/vendor/github.com/google/go-cmp/cmp/options.go index 1f9ca9c489..754496f3b3 100644 --- a/tools/vendor/github.com/google/go-cmp/cmp/options.go +++ b/tools/vendor/github.com/google/go-cmp/cmp/options.go @@ -13,15 +13,15 @@ import ( "github.com/google/go-cmp/cmp/internal/function" ) -// Option configures for specific behavior of Equal and Diff. In particular, -// the fundamental Option functions (Ignore, Transformer, and Comparer), +// Option configures for specific behavior of [Equal] and [Diff]. In particular, +// the fundamental Option functions ([Ignore], [Transformer], and [Comparer]), // configure how equality is determined. // -// The fundamental options may be composed with filters (FilterPath and -// FilterValues) to control the scope over which they are applied. +// The fundamental options may be composed with filters ([FilterPath] and +// [FilterValues]) to control the scope over which they are applied. // -// The cmp/cmpopts package provides helper functions for creating options that -// may be used with Equal and Diff. +// The [github.com/google/go-cmp/cmp/cmpopts] package provides helper functions +// for creating options that may be used with [Equal] and [Diff]. type Option interface { // filter applies all filters and returns the option that remains. // Each option may only read s.curPath and call s.callTTBFunc. @@ -56,9 +56,9 @@ type core struct{} func (core) isCore() {} -// Options is a list of Option values that also satisfies the Option interface. +// Options is a list of [Option] values that also satisfies the [Option] interface. // Helper comparison packages may return an Options value when packing multiple -// Option values into a single Option. When this package processes an Options, +// [Option] values into a single [Option]. When this package processes an Options, // it will be implicitly expanded into a flat list. // // Applying a filter on an Options is equivalent to applying that same filter @@ -105,16 +105,16 @@ func (opts Options) String() string { return fmt.Sprintf("Options{%s}", strings.Join(ss, ", ")) } -// FilterPath returns a new Option where opt is only evaluated if filter f -// returns true for the current Path in the value tree. +// FilterPath returns a new [Option] where opt is only evaluated if filter f +// returns true for the current [Path] in the value tree. // // This filter is called even if a slice element or map entry is missing and // provides an opportunity to ignore such cases. The filter function must be // symmetric such that the filter result is identical regardless of whether the // missing value is from x or y. // -// The option passed in may be an Ignore, Transformer, Comparer, Options, or -// a previously filtered Option. +// The option passed in may be an [Ignore], [Transformer], [Comparer], [Options], or +// a previously filtered [Option]. func FilterPath(f func(Path) bool, opt Option) Option { if f == nil { panic("invalid path filter function") @@ -142,7 +142,7 @@ func (f pathFilter) String() string { return fmt.Sprintf("FilterPath(%s, %v)", function.NameOf(reflect.ValueOf(f.fnc)), f.opt) } -// FilterValues returns a new Option where opt is only evaluated if filter f, +// FilterValues returns a new [Option] where opt is only evaluated if filter f, // which is a function of the form "func(T, T) bool", returns true for the // current pair of values being compared. If either value is invalid or // the type of the values is not assignable to T, then this filter implicitly @@ -154,8 +154,8 @@ func (f pathFilter) String() string { // If T is an interface, it is possible that f is called with two values with // different concrete types that both implement T. // -// The option passed in may be an Ignore, Transformer, Comparer, Options, or -// a previously filtered Option. +// The option passed in may be an [Ignore], [Transformer], [Comparer], [Options], or +// a previously filtered [Option]. func FilterValues(f interface{}, opt Option) Option { v := reflect.ValueOf(f) if !function.IsType(v.Type(), function.ValueFilter) || v.IsNil() { @@ -192,9 +192,9 @@ func (f valuesFilter) String() string { return fmt.Sprintf("FilterValues(%s, %v)", function.NameOf(f.fnc), f.opt) } -// Ignore is an Option that causes all comparisons to be ignored. -// This value is intended to be combined with FilterPath or FilterValues. -// It is an error to pass an unfiltered Ignore option to Equal. +// Ignore is an [Option] that causes all comparisons to be ignored. +// This value is intended to be combined with [FilterPath] or [FilterValues]. +// It is an error to pass an unfiltered Ignore option to [Equal]. func Ignore() Option { return ignore{} } type ignore struct{ core } @@ -234,6 +234,8 @@ func (validator) apply(s *state, vx, vy reflect.Value) { name = fmt.Sprintf("%q.%v", t.PkgPath(), t.Name()) // e.g., "path/to/package".MyType if _, ok := reflect.New(t).Interface().(error); ok { help = "consider using cmpopts.EquateErrors to compare error values" + } else if t.Comparable() { + help = "consider using cmpopts.EquateComparable to compare comparable Go types" } } else { // Unnamed type with unexported fields. Derive PkgPath from field. @@ -254,7 +256,7 @@ const identRx = `[_\p{L}][_\p{L}\p{N}]*` var identsRx = regexp.MustCompile(`^` + identRx + `(\.` + identRx + `)*$`) -// Transformer returns an Option that applies a transformation function that +// Transformer returns an [Option] that applies a transformation function that // converts values of a certain type into that of another. // // The transformer f must be a function "func(T) R" that converts values of @@ -265,13 +267,14 @@ var identsRx = regexp.MustCompile(`^` + identRx + `(\.` + identRx + `)*$`) // same transform to the output of itself (e.g., in the case where the // input and output types are the same), an implicit filter is added such that // a transformer is applicable only if that exact transformer is not already -// in the tail of the Path since the last non-Transform step. +// in the tail of the [Path] since the last non-[Transform] step. // For situations where the implicit filter is still insufficient, -// consider using cmpopts.AcyclicTransformer, which adds a filter -// to prevent the transformer from being recursively applied upon itself. +// consider using [github.com/google/go-cmp/cmp/cmpopts.AcyclicTransformer], +// which adds a filter to prevent the transformer from +// being recursively applied upon itself. // -// The name is a user provided label that is used as the Transform.Name in the -// transformation PathStep (and eventually shown in the Diff output). +// The name is a user provided label that is used as the [Transform.Name] in the +// transformation [PathStep] (and eventually shown in the [Diff] output). // The name must be a valid identifier or qualified identifier in Go syntax. // If empty, an arbitrary name is used. func Transformer(name string, f interface{}) Option { @@ -329,7 +332,7 @@ func (tr transformer) String() string { return fmt.Sprintf("Transformer(%s, %s)", tr.name, function.NameOf(tr.fnc)) } -// Comparer returns an Option that determines whether two values are equal +// Comparer returns an [Option] that determines whether two values are equal // to each other. // // The comparer f must be a function "func(T, T) bool" and is implicitly @@ -377,35 +380,32 @@ func (cm comparer) String() string { return fmt.Sprintf("Comparer(%s)", function.NameOf(cm.fnc)) } -// Exporter returns an Option that specifies whether Equal is allowed to +// Exporter returns an [Option] that specifies whether [Equal] is allowed to // introspect into the unexported fields of certain struct types. // // Users of this option must understand that comparing on unexported fields // from external packages is not safe since changes in the internal -// implementation of some external package may cause the result of Equal +// implementation of some external package may cause the result of [Equal] // to unexpectedly change. However, it may be valid to use this option on types // defined in an internal package where the semantic meaning of an unexported // field is in the control of the user. // -// In many cases, a custom Comparer should be used instead that defines +// In many cases, a custom [Comparer] should be used instead that defines // equality as a function of the public API of a type rather than the underlying // unexported implementation. // -// For example, the reflect.Type documentation defines equality to be determined +// For example, the [reflect.Type] documentation defines equality to be determined // by the == operator on the interface (essentially performing a shallow pointer -// comparison) and most attempts to compare *regexp.Regexp types are interested +// comparison) and most attempts to compare *[regexp.Regexp] types are interested // in only checking that the regular expression strings are equal. -// Both of these are accomplished using Comparers: +// Both of these are accomplished using [Comparer] options: // // Comparer(func(x, y reflect.Type) bool { return x == y }) // Comparer(func(x, y *regexp.Regexp) bool { return x.String() == y.String() }) // -// In other cases, the cmpopts.IgnoreUnexported option can be used to ignore -// all unexported fields on specified struct types. +// In other cases, the [github.com/google/go-cmp/cmp/cmpopts.IgnoreUnexported] +// option can be used to ignore all unexported fields on specified struct types. func Exporter(f func(reflect.Type) bool) Option { - if !supportExporters { - panic("Exporter is not supported on purego builds") - } return exporter(f) } @@ -415,10 +415,10 @@ func (exporter) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableO panic("not implemented") } -// AllowUnexported returns an Options that allows Equal to forcibly introspect +// AllowUnexported returns an [Option] that allows [Equal] to forcibly introspect // unexported fields of the specified struct types. // -// See Exporter for the proper use of this option. +// See [Exporter] for the proper use of this option. func AllowUnexported(types ...interface{}) Option { m := make(map[reflect.Type]bool) for _, typ := range types { @@ -432,7 +432,7 @@ func AllowUnexported(types ...interface{}) Option { } // Result represents the comparison result for a single node and -// is provided by cmp when calling Report (see Reporter). +// is provided by cmp when calling Report (see [Reporter]). type Result struct { _ [0]func() // Make Result incomparable flags resultFlags @@ -445,7 +445,7 @@ func (r Result) Equal() bool { } // ByIgnore reports whether the node is equal because it was ignored. -// This never reports true if Equal reports false. +// This never reports true if [Result.Equal] reports false. func (r Result) ByIgnore() bool { return r.flags&reportByIgnore != 0 } @@ -455,7 +455,7 @@ func (r Result) ByMethod() bool { return r.flags&reportByMethod != 0 } -// ByFunc reports whether a Comparer function determined equality. +// ByFunc reports whether a [Comparer] function determined equality. func (r Result) ByFunc() bool { return r.flags&reportByFunc != 0 } @@ -478,7 +478,7 @@ const ( reportByCycle ) -// Reporter is an Option that can be passed to Equal. When Equal traverses +// Reporter is an [Option] that can be passed to [Equal]. When [Equal] traverses // the value trees, it calls PushStep as it descends into each node in the // tree and PopStep as it ascend out of the node. The leaves of the tree are // either compared (determined to be equal or not equal) or ignored and reported diff --git a/tools/vendor/github.com/google/go-cmp/cmp/path.go b/tools/vendor/github.com/google/go-cmp/cmp/path.go index a0a588502e..c3c1456423 100644 --- a/tools/vendor/github.com/google/go-cmp/cmp/path.go +++ b/tools/vendor/github.com/google/go-cmp/cmp/path.go @@ -14,9 +14,9 @@ import ( "github.com/google/go-cmp/cmp/internal/value" ) -// Path is a list of PathSteps describing the sequence of operations to get +// Path is a list of [PathStep] describing the sequence of operations to get // from some root type to the current position in the value tree. -// The first Path element is always an operation-less PathStep that exists +// The first Path element is always an operation-less [PathStep] that exists // simply to identify the initial type. // // When traversing structs with embedded structs, the embedded struct will @@ -29,8 +29,13 @@ type Path []PathStep // a value's tree structure. Users of this package never need to implement // these types as values of this type will be returned by this package. // -// Implementations of this interface are -// StructField, SliceIndex, MapIndex, Indirect, TypeAssertion, and Transform. +// Implementations of this interface: +// - [StructField] +// - [SliceIndex] +// - [MapIndex] +// - [Indirect] +// - [TypeAssertion] +// - [Transform] type PathStep interface { String() string @@ -70,8 +75,9 @@ func (pa *Path) pop() { *pa = (*pa)[:len(*pa)-1] } -// Last returns the last PathStep in the Path. -// If the path is empty, this returns a non-nil PathStep that reports a nil Type. +// Last returns the last [PathStep] in the Path. +// If the path is empty, this returns a non-nil [PathStep] +// that reports a nil [PathStep.Type]. func (pa Path) Last() PathStep { return pa.Index(-1) } @@ -79,7 +85,8 @@ func (pa Path) Last() PathStep { // Index returns the ith step in the Path and supports negative indexing. // A negative index starts counting from the tail of the Path such that -1 // refers to the last step, -2 refers to the second-to-last step, and so on. -// If index is invalid, this returns a non-nil PathStep that reports a nil Type. +// If index is invalid, this returns a non-nil [PathStep] +// that reports a nil [PathStep.Type]. func (pa Path) Index(i int) PathStep { if i < 0 { i = len(pa) + i @@ -168,7 +175,8 @@ func (ps pathStep) String() string { return fmt.Sprintf("{%s}", s) } -// StructField represents a struct field access on a field called Name. +// StructField is a [PathStep] that represents a struct field access +// on a field called [StructField.Name]. type StructField struct{ *structField } type structField struct { pathStep @@ -204,10 +212,11 @@ func (sf StructField) String() string { return fmt.Sprintf(".%s", sf.name) } func (sf StructField) Name() string { return sf.name } // Index is the index of the field in the parent struct type. -// See reflect.Type.Field. +// See [reflect.Type.Field]. func (sf StructField) Index() int { return sf.idx } -// SliceIndex is an index operation on a slice or array at some index Key. +// SliceIndex is a [PathStep] that represents an index operation on +// a slice or array at some index [SliceIndex.Key]. type SliceIndex struct{ *sliceIndex } type sliceIndex struct { pathStep @@ -247,12 +256,12 @@ func (si SliceIndex) Key() int { // all of the indexes to be shifted. If an index is -1, then that // indicates that the element does not exist in the associated slice. // -// Key is guaranteed to return -1 if and only if the indexes returned -// by SplitKeys are not the same. SplitKeys will never return -1 for +// [SliceIndex.Key] is guaranteed to return -1 if and only if the indexes +// returned by SplitKeys are not the same. SplitKeys will never return -1 for // both indexes. func (si SliceIndex) SplitKeys() (ix, iy int) { return si.xkey, si.ykey } -// MapIndex is an index operation on a map at some index Key. +// MapIndex is a [PathStep] that represents an index operation on a map at some index Key. type MapIndex struct{ *mapIndex } type mapIndex struct { pathStep @@ -266,7 +275,7 @@ func (mi MapIndex) String() string { return fmt.Sprintf("[%#v]", // Key is the value of the map key. func (mi MapIndex) Key() reflect.Value { return mi.key } -// Indirect represents pointer indirection on the parent type. +// Indirect is a [PathStep] that represents pointer indirection on the parent type. type Indirect struct{ *indirect } type indirect struct { pathStep @@ -276,7 +285,7 @@ func (in Indirect) Type() reflect.Type { return in.typ } func (in Indirect) Values() (vx, vy reflect.Value) { return in.vx, in.vy } func (in Indirect) String() string { return "*" } -// TypeAssertion represents a type assertion on an interface. +// TypeAssertion is a [PathStep] that represents a type assertion on an interface. type TypeAssertion struct{ *typeAssertion } type typeAssertion struct { pathStep @@ -286,7 +295,8 @@ func (ta TypeAssertion) Type() reflect.Type { return ta.typ } func (ta TypeAssertion) Values() (vx, vy reflect.Value) { return ta.vx, ta.vy } func (ta TypeAssertion) String() string { return fmt.Sprintf(".(%v)", value.TypeString(ta.typ, false)) } -// Transform is a transformation from the parent type to the current type. +// Transform is a [PathStep] that represents a transformation +// from the parent type to the current type. type Transform struct{ *transform } type transform struct { pathStep @@ -297,13 +307,13 @@ func (tf Transform) Type() reflect.Type { return tf.typ } func (tf Transform) Values() (vx, vy reflect.Value) { return tf.vx, tf.vy } func (tf Transform) String() string { return fmt.Sprintf("%s()", tf.trans.name) } -// Name is the name of the Transformer. +// Name is the name of the [Transformer]. func (tf Transform) Name() string { return tf.trans.name } // Func is the function pointer to the transformer function. func (tf Transform) Func() reflect.Value { return tf.trans.fnc } -// Option returns the originally constructed Transformer option. +// Option returns the originally constructed [Transformer] option. // The == operator can be used to detect the exact option used. func (tf Transform) Option() Option { return tf.trans } diff --git a/tools/vendor/github.com/google/go-cmp/cmp/report_reflect.go b/tools/vendor/github.com/google/go-cmp/cmp/report_reflect.go index 2ab41fad3f..e39f42284e 100644 --- a/tools/vendor/github.com/google/go-cmp/cmp/report_reflect.go +++ b/tools/vendor/github.com/google/go-cmp/cmp/report_reflect.go @@ -199,7 +199,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, break } sf := t.Field(i) - if supportExporters && !isExported(sf.Name) { + if !isExported(sf.Name) { vv = retrieveUnexportedField(v, sf, true) } s := opts.WithTypeMode(autoType).FormatValue(vv, t.Kind(), ptrs) diff --git a/tools/vendor/github.com/jgautheron/goconst/.gitignore b/tools/vendor/github.com/jgautheron/goconst/.gitignore new file mode 100644 index 0000000000..a9d34d9c41 --- /dev/null +++ b/tools/vendor/github.com/jgautheron/goconst/.gitignore @@ -0,0 +1,2 @@ +.idea +goconst \ No newline at end of file diff --git a/tools/vendor/github.com/jgautheron/goconst/parser.go b/tools/vendor/github.com/jgautheron/goconst/parser.go index 2ed7a9a909..c1edd4c780 100644 --- a/tools/vendor/github.com/jgautheron/goconst/parser.go +++ b/tools/vendor/github.com/jgautheron/goconst/parser.go @@ -99,11 +99,11 @@ func (p *Parser) ProcessResults() { } // If the value is a number - if i, err := strconv.Atoi(str); err == nil { - if p.numberMin != 0 && i < p.numberMin { + if i, err := strconv.ParseInt(str, 0, 0); err == nil { + if p.numberMin != 0 && i < int64(p.numberMin) { delete(p.strs, str) } - if p.numberMax != 0 && i > p.numberMax { + if p.numberMax != 0 && i > int64(p.numberMax) { delete(p.strs, str) } } diff --git a/tools/vendor/github.com/jgautheron/goconst/visitor.go b/tools/vendor/github.com/jgautheron/goconst/visitor.go index c0974da8fd..a553814f5c 100644 --- a/tools/vendor/github.com/jgautheron/goconst/visitor.go +++ b/tools/vendor/github.com/jgautheron/goconst/visitor.go @@ -62,10 +62,6 @@ func (v *treeVisitor) Visit(node ast.Node) ast.Visitor { // if foo == "moo" case *ast.BinaryExpr: - if t.Op != token.EQL && t.Op != token.NEQ { - return v - } - var lit *ast.BasicLit var ok bool diff --git a/tools/vendor/github.com/kunwardeep/paralleltest/pkg/paralleltest/paralleltest.go b/tools/vendor/github.com/kunwardeep/paralleltest/pkg/paralleltest/paralleltest.go index 9c2fbb9862..e21f278cf3 100644 --- a/tools/vendor/github.com/kunwardeep/paralleltest/pkg/paralleltest/paralleltest.go +++ b/tools/vendor/github.com/kunwardeep/paralleltest/pkg/paralleltest/paralleltest.go @@ -7,7 +7,6 @@ import ( "strings" "golang.org/x/tools/go/analysis" - "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/ast/inspector" ) @@ -16,28 +15,36 @@ It also checks that the t.Parallel is used if multiple tests cases are run as pa As part of ensuring parallel tests works as expected it checks for reinitialising of the range value over the test cases.(https://tinyurl.com/y6555cy6)` -var Analyzer = &analysis.Analyzer{ - Name: "paralleltest", - Doc: Doc, - Run: run, - Flags: flags(), - Requires: []*analysis.Analyzer{inspect.Analyzer}, +func NewAnalyzer() *analysis.Analyzer { + return newParallelAnalyzer().analyzer } -const ignoreMissingFlag = "i" - -func flags() flag.FlagSet { - options := flag.NewFlagSet("", flag.ExitOnError) - options.Bool(ignoreMissingFlag, false, "ignore missing calls to t.Parallel") - return *options +// parallelAnalyzer is an internal analyzer that makes options available to a +// run pass. It wraps an `analysis.Analyzer` that should be returned for +// linters. +type parallelAnalyzer struct { + analyzer *analysis.Analyzer + ignoreMissing bool + ignoreMissingSubtests bool } -type boolValue bool +func newParallelAnalyzer() *parallelAnalyzer { + a := ¶llelAnalyzer{} -func run(pass *analysis.Pass) (interface{}, error) { + var flags flag.FlagSet + flags.BoolVar(&a.ignoreMissing, "i", false, "ignore missing calls to t.Parallel") + flags.BoolVar(&a.ignoreMissingSubtests, "ignoremissingsubtests", false, "ignore missing calls to t.Parallel in subtests") - ignoreMissing := pass.Analyzer.Flags.Lookup(ignoreMissingFlag).Value.(flag.Getter).Get().(bool) + a.analyzer = &analysis.Analyzer{ + Name: "paralleltest", + Doc: Doc, + Run: a.run, + Flags: flags, + } + return a +} +func (a *parallelAnalyzer) run(pass *analysis.Pass) (interface{}, error) { inspector := inspector.New(pass.Files) nodeFilter := []ast.Node{ @@ -127,13 +134,13 @@ func run(pass *analysis.Pass) (interface{}, error) { } } - if !ignoreMissing && !funcHasParallelMethod { + if !a.ignoreMissing && !funcHasParallelMethod { pass.Reportf(node.Pos(), "Function %s missing the call to method parallel\n", funcDecl.Name.Name) } if rangeStatementOverTestCasesExists && rangeNode != nil { if !rangeStatementHasParallelMethod { - if !ignoreMissing { + if !a.ignoreMissing && !a.ignoreMissingSubtests { pass.Reportf(rangeNode.Pos(), "Range statement for test %s missing the call to method parallel in test Run\n", funcDecl.Name.Name) } } else if loopVariableUsedInRun != nil { @@ -142,10 +149,10 @@ func run(pass *analysis.Pass) (interface{}, error) { } // Check if the t.Run is more than one as there is no point making one test parallel - if !ignoreMissing { + if !a.ignoreMissing && !a.ignoreMissingSubtests { if numberOfTestRun > 1 && len(positionOfTestRunNode) > 0 { for _, n := range positionOfTestRunNode { - pass.Reportf(n.Pos(), "Function %s has missing the call to method parallel in the test run\n", funcDecl.Name.Name) + pass.Reportf(n.Pos(), "Function %s missing the call to method parallel in the test run\n", funcDecl.Name.Name) } } } diff --git a/tools/vendor/github.com/macabu/inamedparam/.gitignore b/tools/vendor/github.com/macabu/inamedparam/.gitignore new file mode 100644 index 0000000000..3b735ec4a8 --- /dev/null +++ b/tools/vendor/github.com/macabu/inamedparam/.gitignore @@ -0,0 +1,21 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work diff --git a/tools/vendor/github.com/macabu/inamedparam/LICENSE-MIT b/tools/vendor/github.com/macabu/inamedparam/LICENSE-MIT new file mode 100644 index 0000000000..b95f480ee5 --- /dev/null +++ b/tools/vendor/github.com/macabu/inamedparam/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Matheus Macabu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/tools/vendor/github.com/macabu/inamedparam/README.md b/tools/vendor/github.com/macabu/inamedparam/README.md new file mode 100644 index 0000000000..80911c9d7a --- /dev/null +++ b/tools/vendor/github.com/macabu/inamedparam/README.md @@ -0,0 +1,18 @@ +# inamedparam + +A linter that reports interfaces with unnamed method parameters. + +## Usage + +### Standalone +You can also run it standalone through `go vet`. + +You must install the binary to your `$GOBIN` folder like so: +```sh +$ go install github.com/macabu/inamedparam/cmd/inamedparam +``` + +And then navigate to your Go project's root folder, where can run `go vet` in the following way: +```sh +$ go vet -vettool=$(which inamedparam) ./... +``` diff --git a/tools/vendor/github.com/macabu/inamedparam/inamedparam.go b/tools/vendor/github.com/macabu/inamedparam/inamedparam.go new file mode 100644 index 0000000000..433e04e5bf --- /dev/null +++ b/tools/vendor/github.com/macabu/inamedparam/inamedparam.go @@ -0,0 +1,67 @@ +package inamedparam + +import ( + "go/ast" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" +) + +var Analyzer = &analysis.Analyzer{ + Name: "inamedparam", + Doc: "reports interfaces with unnamed method parameters", + Run: run, + Requires: []*analysis.Analyzer{ + inspect.Analyzer, + }, +} + +func run(pass *analysis.Pass) (interface{}, error) { + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + + types := []ast.Node{ + &ast.InterfaceType{}, + } + + inspect.Preorder(types, func(n ast.Node) { + interfaceType, ok := n.(*ast.InterfaceType) + if !ok || interfaceType == nil || interfaceType.Methods == nil { + return + } + + for _, method := range interfaceType.Methods.List { + interfaceFunc, ok := method.Type.(*ast.FuncType) + if !ok || interfaceFunc == nil || interfaceFunc.Params == nil { + continue + } + + methodName := method.Names[0].Name + + for _, param := range interfaceFunc.Params.List { + if param.Names == nil { + var builtParamType string + + switch paramType := param.Type.(type) { + case *ast.SelectorExpr: + if ident := paramType.X.(*ast.Ident); ident != nil { + builtParamType += ident.Name + "." + } + + builtParamType += paramType.Sel.Name + case *ast.Ident: + builtParamType = paramType.Name + } + + if builtParamType != "" { + pass.Reportf(param.Pos(), "interface method %v must have named param for type %v", methodName, builtParamType) + } else { + pass.Reportf(param.Pos(), "interface method %v must have all named params", methodName) + } + } + } + } + }) + + return nil, nil +} diff --git a/tools/vendor/github.com/mgechev/revive/config/config.go b/tools/vendor/github.com/mgechev/revive/config/config.go index 04cd214042..abd554a9f8 100644 --- a/tools/vendor/github.com/mgechev/revive/config/config.go +++ b/tools/vendor/github.com/mgechev/revive/config/config.go @@ -31,7 +31,6 @@ var defaultRules = []lint.Rule{ &rule.TimeNamingRule{}, &rule.ContextKeysType{}, &rule.ContextAsArgumentRule{}, - &rule.IfReturnRule{}, &rule.EmptyBlockRule{}, &rule.SuperfluousElseRule{}, &rule.UnusedParamRule{}, @@ -81,12 +80,17 @@ var allRules = append([]lint.Rule{ &rule.FunctionLength{}, &rule.NestedStructs{}, &rule.UselessBreak{}, + &rule.UncheckedTypeAssertionRule{}, &rule.TimeEqualRule{}, &rule.BannedCharsRule{}, &rule.OptimizeOperandsOrderRule{}, &rule.UseAnyRule{}, &rule.DataRaceRule{}, &rule.CommentSpacingsRule{}, + &rule.IfReturnRule{}, + &rule.RedundantImportAlias{}, + &rule.ImportAliasNamingRule{}, + &rule.EnforceMapStyleRule{}, }, defaultRules...) var allFormatters = []lint.Formatter{ @@ -148,6 +152,14 @@ func parseConfig(path string, config *lint.Config) error { if err != nil { return fmt.Errorf("cannot parse the config file: %v", err) } + for k, r := range config.Rules { + err := r.Initialize() + if err != nil { + return fmt.Errorf("error in config of rule [%s] : [%v]", k, err) + } + config.Rules[k] = r + } + return nil } diff --git a/tools/vendor/github.com/mgechev/revive/formatter/sarif.go b/tools/vendor/github.com/mgechev/revive/formatter/sarif.go index c6288db76f..c42da73eb0 100644 --- a/tools/vendor/github.com/mgechev/revive/formatter/sarif.go +++ b/tools/vendor/github.com/mgechev/revive/formatter/sarif.go @@ -88,7 +88,7 @@ func (l *reviveRunLog) AddResult(failure lint.Failure) { location := garif.NewLocation().WithURI(filename).WithLineColumn(line, column) result.Locations = append(result.Locations, location) result.RuleId = failure.RuleName - result.Level = l.rules[failure.RuleName].Severity + result.Level = garif.ResultLevel(l.rules[failure.RuleName].Severity) l.run.Results = append(l.run.Results, result) } diff --git a/tools/vendor/github.com/mgechev/revive/internal/ifelse/args.go b/tools/vendor/github.com/mgechev/revive/internal/ifelse/args.go new file mode 100644 index 0000000000..c6e647e697 --- /dev/null +++ b/tools/vendor/github.com/mgechev/revive/internal/ifelse/args.go @@ -0,0 +1,11 @@ +package ifelse + +// PreserveScope is a configuration argument that prevents suggestions +// that would enlarge variable scope +const PreserveScope = "preserveScope" + +// Args contains arguments common to the early-return, indent-error-flow +// and superfluous-else rules (currently just preserveScope) +type Args struct { + PreserveScope bool +} diff --git a/tools/vendor/github.com/mgechev/revive/internal/ifelse/branch.go b/tools/vendor/github.com/mgechev/revive/internal/ifelse/branch.go new file mode 100644 index 0000000000..6e6036b899 --- /dev/null +++ b/tools/vendor/github.com/mgechev/revive/internal/ifelse/branch.go @@ -0,0 +1,93 @@ +package ifelse + +import ( + "fmt" + "go/ast" + "go/token" +) + +// Branch contains information about a branch within an if-else chain. +type Branch struct { + BranchKind + Call // The function called at the end for kind Panic or Exit. + HasDecls bool // The branch has one or more declarations (at the top level block) +} + +// BlockBranch gets the Branch of an ast.BlockStmt. +func BlockBranch(block *ast.BlockStmt) Branch { + blockLen := len(block.List) + if blockLen == 0 { + return Empty.Branch() + } + + branch := StmtBranch(block.List[blockLen-1]) + branch.HasDecls = hasDecls(block) + return branch +} + +// StmtBranch gets the Branch of an ast.Stmt. +func StmtBranch(stmt ast.Stmt) Branch { + switch stmt := stmt.(type) { + case *ast.ReturnStmt: + return Return.Branch() + case *ast.BlockStmt: + return BlockBranch(stmt) + case *ast.BranchStmt: + switch stmt.Tok { + case token.BREAK: + return Break.Branch() + case token.CONTINUE: + return Continue.Branch() + case token.GOTO: + return Goto.Branch() + } + case *ast.ExprStmt: + fn, ok := ExprCall(stmt) + if !ok { + break + } + kind, ok := DeviatingFuncs[fn] + if ok { + return Branch{BranchKind: kind, Call: fn} + } + case *ast.EmptyStmt: + return Empty.Branch() + case *ast.LabeledStmt: + return StmtBranch(stmt.Stmt) + } + return Regular.Branch() +} + +// String returns a brief string representation +func (b Branch) String() string { + switch b.BranchKind { + case Panic, Exit: + return fmt.Sprintf("... %v()", b.Call) + default: + return b.BranchKind.String() + } +} + +// LongString returns a longer form string representation +func (b Branch) LongString() string { + switch b.BranchKind { + case Panic, Exit: + return fmt.Sprintf("call to %v function", b.Call) + default: + return b.BranchKind.LongString() + } +} + +func hasDecls(block *ast.BlockStmt) bool { + for _, stmt := range block.List { + switch stmt := stmt.(type) { + case *ast.DeclStmt: + return true + case *ast.AssignStmt: + if stmt.Tok == token.DEFINE { + return true + } + } + } + return false +} diff --git a/tools/vendor/github.com/mgechev/revive/internal/ifelse/branch_kind.go b/tools/vendor/github.com/mgechev/revive/internal/ifelse/branch_kind.go new file mode 100644 index 0000000000..41601d1e1d --- /dev/null +++ b/tools/vendor/github.com/mgechev/revive/internal/ifelse/branch_kind.go @@ -0,0 +1,101 @@ +package ifelse + +// BranchKind is a classifier for if-else branches. It says whether the branch is empty, +// and whether the branch ends with a statement that deviates control flow. +type BranchKind int + +const ( + // Empty branches do nothing + Empty BranchKind = iota + + // Return branches return from the current function + Return + + // Continue branches continue a surrounding "for" loop + Continue + + // Break branches break a surrounding "for" loop + Break + + // Goto branches conclude with a "goto" statement + Goto + + // Panic branches panic the current function + Panic + + // Exit branches end the program + Exit + + // Regular branches do not fit any category above + Regular +) + +// IsEmpty tests if the branch is empty +func (k BranchKind) IsEmpty() bool { return k == Empty } + +// Returns tests if the branch returns from the current function +func (k BranchKind) Returns() bool { return k == Return } + +// Deviates tests if the control does not flow to the first +// statement following the if-else chain. +func (k BranchKind) Deviates() bool { + switch k { + case Empty, Regular: + return false + case Return, Continue, Break, Goto, Panic, Exit: + return true + default: + panic("invalid kind") + } +} + +// Branch returns a Branch with the given kind +func (k BranchKind) Branch() Branch { return Branch{BranchKind: k} } + +// String returns a brief string representation +func (k BranchKind) String() string { + switch k { + case Empty: + return "" + case Regular: + return "..." + case Return: + return "... return" + case Continue: + return "... continue" + case Break: + return "... break" + case Goto: + return "... goto" + case Panic: + return "... panic()" + case Exit: + return "... os.Exit()" + default: + panic("invalid kind") + } +} + +// LongString returns a longer form string representation +func (k BranchKind) LongString() string { + switch k { + case Empty: + return "an empty block" + case Regular: + return "a regular statement" + case Return: + return "a return statement" + case Continue: + return "a continue statement" + case Break: + return "a break statement" + case Goto: + return "a goto statement" + case Panic: + return "a function call that panics" + case Exit: + return "a function call that exits the program" + default: + panic("invalid kind") + } +} diff --git a/tools/vendor/github.com/mgechev/revive/internal/ifelse/chain.go b/tools/vendor/github.com/mgechev/revive/internal/ifelse/chain.go new file mode 100644 index 0000000000..9891635ee1 --- /dev/null +++ b/tools/vendor/github.com/mgechev/revive/internal/ifelse/chain.go @@ -0,0 +1,10 @@ +package ifelse + +// Chain contains information about an if-else chain. +type Chain struct { + If Branch // what happens at the end of the "if" block + Else Branch // what happens at the end of the "else" block + HasInitializer bool // is there an "if"-initializer somewhere in the chain? + HasPriorNonDeviating bool // is there a prior "if" block that does NOT deviate control flow? + AtBlockEnd bool // whether the chain is placed at the end of the surrounding block +} diff --git a/tools/vendor/github.com/mgechev/revive/internal/ifelse/doc.go b/tools/vendor/github.com/mgechev/revive/internal/ifelse/doc.go new file mode 100644 index 0000000000..0aa2c98175 --- /dev/null +++ b/tools/vendor/github.com/mgechev/revive/internal/ifelse/doc.go @@ -0,0 +1,6 @@ +// Package ifelse provides helpers for analysing the control flow in if-else chains, +// presently used by the following rules: +// - early-return +// - indent-error-flow +// - superfluous-else +package ifelse diff --git a/tools/vendor/github.com/mgechev/revive/internal/ifelse/func.go b/tools/vendor/github.com/mgechev/revive/internal/ifelse/func.go new file mode 100644 index 0000000000..7ba3519184 --- /dev/null +++ b/tools/vendor/github.com/mgechev/revive/internal/ifelse/func.go @@ -0,0 +1,51 @@ +package ifelse + +import ( + "fmt" + "go/ast" +) + +// Call contains the name of a function that deviates control flow. +type Call struct { + Pkg string // The package qualifier of the function, if not built-in. + Name string // The function name. +} + +// DeviatingFuncs lists known control flow deviating function calls. +var DeviatingFuncs = map[Call]BranchKind{ + {"os", "Exit"}: Exit, + {"log", "Fatal"}: Exit, + {"log", "Fatalf"}: Exit, + {"log", "Fatalln"}: Exit, + {"", "panic"}: Panic, + {"log", "Panic"}: Panic, + {"log", "Panicf"}: Panic, + {"log", "Panicln"}: Panic, +} + +// ExprCall gets the Call of an ExprStmt, if any. +func ExprCall(expr *ast.ExprStmt) (Call, bool) { + call, ok := expr.X.(*ast.CallExpr) + if !ok { + return Call{}, false + } + switch v := call.Fun.(type) { + case *ast.Ident: + return Call{Name: v.Name}, true + case *ast.SelectorExpr: + if ident, ok := v.X.(*ast.Ident); ok { + return Call{Name: v.Sel.Name, Pkg: ident.Name}, true + } + } + return Call{}, false +} + +// String returns the function name with package qualifier (if any) +func (f Call) String() string { + switch { + case f.Pkg != "": + return fmt.Sprintf("%s.%s", f.Pkg, f.Name) + default: + return f.Name + } +} diff --git a/tools/vendor/github.com/mgechev/revive/internal/ifelse/rule.go b/tools/vendor/github.com/mgechev/revive/internal/ifelse/rule.go new file mode 100644 index 0000000000..07ad456b65 --- /dev/null +++ b/tools/vendor/github.com/mgechev/revive/internal/ifelse/rule.go @@ -0,0 +1,105 @@ +package ifelse + +import ( + "go/ast" + "go/token" + + "github.com/mgechev/revive/lint" +) + +// Rule is an interface for linters operating on if-else chains +type Rule interface { + CheckIfElse(chain Chain, args Args) (failMsg string) +} + +// Apply evaluates the given Rule on if-else chains found within the given AST, +// and returns the failures. +// +// Note that in if-else chain with multiple "if" blocks, only the *last* one is checked, +// that is to say, given: +// +// if foo { +// ... +// } else if bar { +// ... +// } else { +// ... +// } +// +// Only the block following "bar" is linted. This is because the rules that use this function +// do not presently have anything to say about earlier blocks in the chain. +func Apply(rule Rule, node ast.Node, target Target, args lint.Arguments) []lint.Failure { + v := &visitor{rule: rule, target: target} + for _, arg := range args { + if arg == PreserveScope { + v.args.PreserveScope = true + } + } + ast.Walk(v, node) + return v.failures +} + +type visitor struct { + failures []lint.Failure + target Target + rule Rule + args Args +} + +func (v *visitor) Visit(node ast.Node) ast.Visitor { + block, ok := node.(*ast.BlockStmt) + if !ok { + return v + } + + for i, stmt := range block.List { + if ifStmt, ok := stmt.(*ast.IfStmt); ok { + v.visitChain(ifStmt, Chain{AtBlockEnd: i == len(block.List)-1}) + continue + } + ast.Walk(v, stmt) + } + return nil +} + +func (v *visitor) visitChain(ifStmt *ast.IfStmt, chain Chain) { + // look for other if-else chains nested inside this if { } block + ast.Walk(v, ifStmt.Body) + + if ifStmt.Else == nil { + // no else branch + return + } + + if as, ok := ifStmt.Init.(*ast.AssignStmt); ok && as.Tok == token.DEFINE { + chain.HasInitializer = true + } + chain.If = BlockBranch(ifStmt.Body) + + switch elseBlock := ifStmt.Else.(type) { + case *ast.IfStmt: + if !chain.If.Deviates() { + chain.HasPriorNonDeviating = true + } + v.visitChain(elseBlock, chain) + case *ast.BlockStmt: + // look for other if-else chains nested inside this else { } block + ast.Walk(v, elseBlock) + + chain.Else = BlockBranch(elseBlock) + if failMsg := v.rule.CheckIfElse(chain, v.args); failMsg != "" { + if chain.HasInitializer { + // if statement has a := initializer, so we might need to move the assignment + // onto its own line in case the body references it + failMsg += " (move short variable declaration to its own line if necessary)" + } + v.failures = append(v.failures, lint.Failure{ + Confidence: 1, + Node: v.target.node(ifStmt), + Failure: failMsg, + }) + } + default: + panic("invalid node type for else") + } +} diff --git a/tools/vendor/github.com/mgechev/revive/internal/ifelse/target.go b/tools/vendor/github.com/mgechev/revive/internal/ifelse/target.go new file mode 100644 index 0000000000..81ff1c3037 --- /dev/null +++ b/tools/vendor/github.com/mgechev/revive/internal/ifelse/target.go @@ -0,0 +1,25 @@ +package ifelse + +import "go/ast" + +// Target decides what line/column should be indicated by the rule in question. +type Target int + +const ( + // TargetIf means the text refers to the "if" + TargetIf Target = iota + + // TargetElse means the text refers to the "else" + TargetElse +) + +func (t Target) node(ifStmt *ast.IfStmt) ast.Node { + switch t { + case TargetIf: + return ifStmt + case TargetElse: + return ifStmt.Else + default: + panic("bad target") + } +} diff --git a/tools/vendor/github.com/mgechev/revive/lint/config.go b/tools/vendor/github.com/mgechev/revive/lint/config.go index 2763058046..9b26d58417 100644 --- a/tools/vendor/github.com/mgechev/revive/lint/config.go +++ b/tools/vendor/github.com/mgechev/revive/lint/config.go @@ -3,16 +3,44 @@ package lint // Arguments is type used for the arguments of a rule. type Arguments = []interface{} +type FileFilters = []*FileFilter + // RuleConfig is type used for the rule configuration. type RuleConfig struct { Arguments Arguments Severity Severity Disabled bool + // Exclude - rule-level file excludes, TOML related (strings) + Exclude []string + // excludeFilters - regex-based file filters, initialized from Exclude + excludeFilters []*FileFilter +} + +// Initialize - should be called after reading from TOML file +func (rc *RuleConfig) Initialize() error { + for _, f := range rc.Exclude { + ff, err := ParseFileFilter(f) + if err != nil { + return err + } + rc.excludeFilters = append(rc.excludeFilters, ff) + } + return nil } // RulesConfig defines the config for all rules. type RulesConfig = map[string]RuleConfig +// MustExclude - checks if given filename `name` must be excluded +func (rcfg *RuleConfig) MustExclude(name string) bool { + for _, exclude := range rcfg.excludeFilters { + if exclude.MatchFileName(name) { + return true + } + } + return false +} + // DirectiveConfig is type used for the linter directive configuration. type DirectiveConfig struct { Severity Severity diff --git a/tools/vendor/github.com/mgechev/revive/lint/file.go b/tools/vendor/github.com/mgechev/revive/lint/file.go index dcf0e608f6..23255304c5 100644 --- a/tools/vendor/github.com/mgechev/revive/lint/file.go +++ b/tools/vendor/github.com/mgechev/revive/lint/file.go @@ -102,6 +102,9 @@ func (f *File) lint(rules []Rule, config Config, failures chan Failure) { disabledIntervals := f.disabledIntervals(rules, mustSpecifyDisableReason, failures) for _, currentRule := range rules { ruleConfig := rulesConfig[currentRule.Name()] + if ruleConfig.MustExclude(f.Name) { + continue + } currentFailures := currentRule.Apply(f, ruleConfig.Arguments) for idx, failure := range currentFailures { if failure.RuleName == "" { diff --git a/tools/vendor/github.com/mgechev/revive/lint/filefilter.go b/tools/vendor/github.com/mgechev/revive/lint/filefilter.go new file mode 100644 index 0000000000..8da090b9cc --- /dev/null +++ b/tools/vendor/github.com/mgechev/revive/lint/filefilter.go @@ -0,0 +1,128 @@ +package lint + +import ( + "fmt" + "regexp" + "strings" +) + +// FileFilter - file filter to exclude some files for rule +// supports whole +// 1. file/dir names : pkg/mypkg/my.go, +// 2. globs: **/*.pb.go, +// 3. regexes (~ prefix) ~-tmp\.\d+\.go +// 4. special test marker `TEST` - treats as `~_test\.go` +type FileFilter struct { + // raw definition of filter inside config + raw string + // don't care what was at start, will use regexes inside + rx *regexp.Regexp + // marks filter as matching everything + matchesAll bool + // marks filter as matching nothing + matchesNothing bool +} + +// ParseFileFilter - creates [FileFilter] for given raw filter +// if empty string, it matches nothing +// if `*`, or `~`, it matches everything +// while regexp could be invalid, it could return it's compilation error +func ParseFileFilter(rawFilter string) (*FileFilter, error) { + rawFilter = strings.TrimSpace(rawFilter) + result := new(FileFilter) + result.raw = rawFilter + result.matchesNothing = len(result.raw) == 0 + result.matchesAll = result.raw == "*" || result.raw == "~" + if !result.matchesAll && !result.matchesNothing { + if err := result.prepareRegexp(); err != nil { + return nil, err + } + } + return result, nil +} + +func (ff *FileFilter) String() string { return ff.raw } + +// MatchFileName - checks if file name matches filter +func (ff *FileFilter) MatchFileName(name string) bool { + if ff.matchesAll { + return true + } + if ff.matchesNothing { + return false + } + name = strings.ReplaceAll(name, "\\", "/") + return ff.rx.MatchString(name) +} + +var fileFilterInvalidGlobRegexp = regexp.MustCompile(`[^/]\*\*[^/]`) +var escapeRegexSymbols = ".+{}()[]^$" + +func (ff *FileFilter) prepareRegexp() error { + var err error + var src = ff.raw + if src == "TEST" { + src = "~_test\\.go" + } + if strings.HasPrefix(src, "~") { + ff.rx, err = regexp.Compile(src[1:]) + if err != nil { + return fmt.Errorf("invalid file filter [%s], regexp compile error: [%v]", ff.raw, err) + } + return nil + } + /* globs */ + if strings.Contains(src, "*") { + if fileFilterInvalidGlobRegexp.MatchString(src) { + return fmt.Errorf("invalid file filter [%s], invalid glob pattern", ff.raw) + } + var rxBuild strings.Builder + rxBuild.WriteByte('^') + wasStar := false + justDirGlob := false + for _, c := range src { + if c == '*' { + if wasStar { + rxBuild.WriteString(`[\s\S]*`) + wasStar = false + justDirGlob = true + continue + } + wasStar = true + continue + } + if wasStar { + rxBuild.WriteString("[^/]*") + wasStar = false + } + if strings.ContainsRune(escapeRegexSymbols, c) { + rxBuild.WriteByte('\\') + } + rxBuild.WriteRune(c) + if c == '/' && justDirGlob { + rxBuild.WriteRune('?') + } + justDirGlob = false + } + if wasStar { + rxBuild.WriteString("[^/]*") + } + rxBuild.WriteByte('$') + ff.rx, err = regexp.Compile(rxBuild.String()) + if err != nil { + return fmt.Errorf("invalid file filter [%s], regexp compile error after glob expand: [%v]", ff.raw, err) + } + return nil + } + + // it's whole file mask, just escape dots and normilze separators + fillRx := src + fillRx = strings.ReplaceAll(fillRx, "\\", "/") + fillRx = strings.ReplaceAll(fillRx, ".", `\.`) + fillRx = "^" + fillRx + "$" + ff.rx, err = regexp.Compile(fillRx) + if err != nil { + return fmt.Errorf("invalid file filter [%s], regexp compile full path: [%v]", ff.raw, err) + } + return nil +} diff --git a/tools/vendor/github.com/mgechev/revive/rule/argument-limit.go b/tools/vendor/github.com/mgechev/revive/rule/argument-limit.go index 8042da15e3..8120288fd5 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/argument-limit.go +++ b/tools/vendor/github.com/mgechev/revive/rule/argument-limit.go @@ -14,10 +14,16 @@ type ArgumentsLimitRule struct { sync.Mutex } +const defaultArgumentsLimit = 8 + func (r *ArgumentsLimitRule) configure(arguments lint.Arguments) { r.Lock() + defer r.Unlock() if r.total == 0 { - checkNumberOfArguments(1, arguments, r.Name()) + if len(arguments) < 1 { + r.total = defaultArgumentsLimit + return + } total, ok := arguments[0].(int64) // Alt. non panicking version if !ok { @@ -25,7 +31,6 @@ func (r *ArgumentsLimitRule) configure(arguments lint.Arguments) { } r.total = int(total) } - r.Unlock() } // Apply applies the rule to given file. diff --git a/tools/vendor/github.com/mgechev/revive/rule/banned-characters.go b/tools/vendor/github.com/mgechev/revive/rule/banned-characters.go index 76fa2235a9..12997bae11 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/banned-characters.go +++ b/tools/vendor/github.com/mgechev/revive/rule/banned-characters.go @@ -19,11 +19,11 @@ const bannedCharsRuleName = "banned-characters" func (r *BannedCharsRule) configure(arguments lint.Arguments) { r.Lock() - if r.bannedCharList == nil { + defer r.Unlock() + if r.bannedCharList == nil && len(arguments) > 0 { checkNumberOfArguments(1, arguments, bannedCharsRuleName) r.bannedCharList = r.getBannedCharsList(arguments) } - r.Unlock() } // Apply applied the rule to the given file. diff --git a/tools/vendor/github.com/mgechev/revive/rule/cognitive-complexity.go b/tools/vendor/github.com/mgechev/revive/rule/cognitive-complexity.go index a9c11a7d0b..1973faef87 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/cognitive-complexity.go +++ b/tools/vendor/github.com/mgechev/revive/rule/cognitive-complexity.go @@ -16,10 +16,17 @@ type CognitiveComplexityRule struct { sync.Mutex } +const defaultMaxCognitiveComplexity = 7 + func (r *CognitiveComplexityRule) configure(arguments lint.Arguments) { r.Lock() + defer r.Unlock() if r.maxComplexity == 0 { - checkNumberOfArguments(1, arguments, r.Name()) + + if len(arguments) < 1 { + r.maxComplexity = defaultMaxCognitiveComplexity + return + } complexity, ok := arguments[0].(int64) if !ok { @@ -27,7 +34,6 @@ func (r *CognitiveComplexityRule) configure(arguments lint.Arguments) { } r.maxComplexity = int(complexity) } - r.Unlock() } // Apply applies the rule to given file. diff --git a/tools/vendor/github.com/mgechev/revive/rule/confusing-naming.go b/tools/vendor/github.com/mgechev/revive/rule/confusing-naming.go index 34cdb907a8..8b1c3eac4b 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/confusing-naming.go +++ b/tools/vendor/github.com/mgechev/revive/rule/confusing-naming.go @@ -27,10 +27,10 @@ type packages struct { func (ps *packages) methodNames(lp *lint.Package) pkgMethods { ps.mu.Lock() + defer ps.mu.Unlock() for _, pkg := range ps.pkgs { if pkg.pkg == lp { - ps.mu.Unlock() return pkg } } @@ -38,7 +38,6 @@ func (ps *packages) methodNames(lp *lint.Package) pkgMethods { pkgm := pkgMethods{pkg: lp, methods: make(map[string]map[string]*referenceMethod), mu: &sync.Mutex{}} ps.pkgs = append(ps.pkgs, pkgm) - ps.mu.Unlock() return pkgm } @@ -72,6 +71,7 @@ func (*ConfusingNamingRule) Name() string { // checkMethodName checks if a given method/function name is similar (just case differences) to other method/function of the same struct/file. func checkMethodName(holder string, id *ast.Ident, w *lintConfusingNames) { + if id.Name == "init" && holder == defaultStructName { // ignore init functions return @@ -137,8 +137,11 @@ func getStructName(r *ast.FieldList) string { t := r.List[0].Type - if p, _ := t.(*ast.StarExpr); p != nil { // if a pointer receiver => dereference pointer receiver types - t = p.X + switch v := t.(type) { + case *ast.StarExpr: + t = v.X + case *ast.IndexExpr: + t = v.X } if p, _ := t.(*ast.Ident); p != nil { diff --git a/tools/vendor/github.com/mgechev/revive/rule/cyclomatic.go b/tools/vendor/github.com/mgechev/revive/rule/cyclomatic.go index afd41818b8..9f6d50043d 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/cyclomatic.go +++ b/tools/vendor/github.com/mgechev/revive/rule/cyclomatic.go @@ -17,10 +17,16 @@ type CyclomaticRule struct { sync.Mutex } +const defaultMaxCyclomaticComplexity = 10 + func (r *CyclomaticRule) configure(arguments lint.Arguments) { r.Lock() + defer r.Unlock() if r.maxComplexity == 0 { - checkNumberOfArguments(1, arguments, r.Name()) + if len(arguments) < 1 { + r.maxComplexity = defaultMaxCyclomaticComplexity + return + } complexity, ok := arguments[0].(int64) // Alt. non panicking version if !ok { @@ -28,7 +34,6 @@ func (r *CyclomaticRule) configure(arguments lint.Arguments) { } r.maxComplexity = int(complexity) } - r.Unlock() } // Apply applies the rule to given file. diff --git a/tools/vendor/github.com/mgechev/revive/rule/defer.go b/tools/vendor/github.com/mgechev/revive/rule/defer.go index f8224fd4d1..f3ea17920e 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/defer.go +++ b/tools/vendor/github.com/mgechev/revive/rule/defer.go @@ -97,18 +97,21 @@ func (w lintDeferRule) Visit(node ast.Node) ast.Visitor { w.newFailure("return in a defer function has no effect", n, 1.0, "logic", "return") } case *ast.CallExpr: - if !w.inADefer && isIdent(n.Fun, "recover") { + isCallToRecover := isIdent(n.Fun, "recover") + switch { + case !w.inADefer && isCallToRecover: // func fn() { recover() } // // confidence is not 1 because recover can be in a function that is deferred elsewhere w.newFailure("recover must be called inside a deferred function", n, 0.8, "logic", "recover") - } else if w.inADefer && !w.inAFuncLit && isIdent(n.Fun, "recover") { + case w.inADefer && !w.inAFuncLit && isCallToRecover: // defer helper(recover()) // // confidence is not truly 1 because this could be in a correctly-deferred func, // but it is very likely to be a misunderstanding of defer's behavior around arguments. w.newFailure("recover must be called inside a deferred function, this is executing recover immediately", n, 1, "logic", "immediate-recover") } + case *ast.DeferStmt: if isIdent(n.Call.Fun, "recover") { // defer recover() @@ -119,7 +122,12 @@ func (w lintDeferRule) Visit(node ast.Node) ast.Visitor { } w.visitSubtree(n.Call.Fun, true, false, false) for _, a := range n.Call.Args { - w.visitSubtree(a, true, false, false) // check arguments, they should not contain recover() + switch a.(type) { + case *ast.FuncLit: + continue // too hard to analyze deferred calls with func literals args + default: + w.visitSubtree(a, true, false, false) // check arguments, they should not contain recover() + } } if w.inALoop { @@ -136,6 +144,7 @@ func (w lintDeferRule) Visit(node ast.Node) ast.Visitor { w.newFailure("be careful when deferring calls to methods without pointer receiver", fn, 0.8, "bad practice", "method-call") } } + } return nil } diff --git a/tools/vendor/github.com/mgechev/revive/rule/dot-imports.go b/tools/vendor/github.com/mgechev/revive/rule/dot-imports.go index 25ff526cb5..db78f1957b 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/dot-imports.go +++ b/tools/vendor/github.com/mgechev/revive/rule/dot-imports.go @@ -39,9 +39,8 @@ type lintImports struct { } func (w lintImports) Visit(_ ast.Node) ast.Visitor { - for i, is := range w.fileAst.Imports { - _ = i - if is.Name != nil && is.Name.Name == "." && !w.file.IsTest() { + for _, is := range w.fileAst.Imports { + if is.Name != nil && is.Name.Name == "." { w.onFailure(lint.Failure{ Confidence: 1, Failure: "should not use dot imports", diff --git a/tools/vendor/github.com/mgechev/revive/rule/early-return.go b/tools/vendor/github.com/mgechev/revive/rule/early-return.go index ed0fcfae43..90a0acc558 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/early-return.go +++ b/tools/vendor/github.com/mgechev/revive/rule/early-return.go @@ -2,9 +2,8 @@ package rule import ( "fmt" - "go/ast" - "go/token" + "github.com/mgechev/revive/internal/ifelse" "github.com/mgechev/revive/lint" ) @@ -13,16 +12,8 @@ import ( type EarlyReturnRule struct{} // Apply applies the rule to given file. -func (*EarlyReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { - var failures []lint.Failure - - onFailure := func(failure lint.Failure) { - failures = append(failures, failure) - } - - w := lintEarlyReturnRule{onFailure: onFailure} - ast.Walk(w, file.AST) - return failures +func (e *EarlyReturnRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure { + return ifelse.Apply(e, file.AST, ifelse.TargetIf, args) } // Name returns the rule name. @@ -30,147 +21,31 @@ func (*EarlyReturnRule) Name() string { return "early-return" } -type lintEarlyReturnRule struct { - onFailure func(lint.Failure) -} - -func (w lintEarlyReturnRule) Visit(node ast.Node) ast.Visitor { - ifStmt, ok := node.(*ast.IfStmt) - if !ok { - return w - } - - w.visitIf(ifStmt, false, false) - return nil -} - -func (w lintEarlyReturnRule) visitIf(ifStmt *ast.IfStmt, hasNonReturnBranch, hasIfInitializer bool) { - // look for other if-else chains nested inside this if { } block - ast.Walk(w, ifStmt.Body) - - if ifStmt.Else == nil { - // no else branch +// CheckIfElse evaluates the rule against an ifelse.Chain. +func (e *EarlyReturnRule) CheckIfElse(chain ifelse.Chain, args ifelse.Args) (failMsg string) { + if !chain.Else.Deviates() { + // this rule only applies if the else-block deviates control flow return } - if as, ok := ifStmt.Init.(*ast.AssignStmt); ok && as.Tok == token.DEFINE { - hasIfInitializer = true - } - bodyFlow := w.branchFlow(ifStmt.Body) - - switch elseBlock := ifStmt.Else.(type) { - case *ast.IfStmt: - if bodyFlow.canFlowIntoNext() { - hasNonReturnBranch = true - } - w.visitIf(elseBlock, hasNonReturnBranch, hasIfInitializer) - - case *ast.BlockStmt: - // look for other if-else chains nested inside this else { } block - ast.Walk(w, elseBlock) - - if hasNonReturnBranch && bodyFlow != branchFlowEmpty { - // if we de-indent this block then a previous branch - // might flow into it, affecting program behaviour - return - } - - if !bodyFlow.canFlowIntoNext() { - // avoid overlapping with superfluous-else - return - } - - elseFlow := w.branchFlow(elseBlock) - if !elseFlow.canFlowIntoNext() { - failMsg := fmt.Sprintf("if c {%[1]s } else {%[2]s } can be simplified to if !c {%[2]s }%[1]s", - bodyFlow, elseFlow) - - if hasIfInitializer { - // if statement has a := initializer, so we might need to move the assignment - // onto its own line in case the body references it - failMsg += " (move short variable declaration to its own line if necessary)" - } - - w.onFailure(lint.Failure{ - Confidence: 1, - Node: ifStmt, - Failure: failMsg, - }) - } - - default: - panic("invalid node type for else") - } -} - -type branchFlowKind int - -const ( - branchFlowEmpty branchFlowKind = iota - branchFlowReturn - branchFlowPanic - branchFlowContinue - branchFlowBreak - branchFlowGoto - branchFlowRegular -) - -func (w lintEarlyReturnRule) branchFlow(block *ast.BlockStmt) branchFlowKind { - blockLen := len(block.List) - if blockLen == 0 { - return branchFlowEmpty + if chain.HasPriorNonDeviating && !chain.If.IsEmpty() { + // if we de-indent this block then a previous branch + // might flow into it, affecting program behaviour + return } - switch stmt := block.List[blockLen-1].(type) { - case *ast.ReturnStmt: - return branchFlowReturn - case *ast.BlockStmt: - return w.branchFlow(stmt) - case *ast.BranchStmt: - switch stmt.Tok { - case token.BREAK: - return branchFlowBreak - case token.CONTINUE: - return branchFlowContinue - case token.GOTO: - return branchFlowGoto - } - case *ast.ExprStmt: - if call, ok := stmt.X.(*ast.CallExpr); ok && isIdent(call.Fun, "panic") { - return branchFlowPanic - } + if chain.If.Deviates() { + // avoid overlapping with superfluous-else + return } - return branchFlowRegular -} - -// Whether this branch's control can flow into the next statement following the if-else chain -func (k branchFlowKind) canFlowIntoNext() bool { - switch k { - case branchFlowReturn, branchFlowPanic, branchFlowContinue, branchFlowBreak, branchFlowGoto: - return false - default: - return true + if args.PreserveScope && !chain.AtBlockEnd && (chain.HasInitializer || chain.If.HasDecls) { + // avoid increasing variable scope + return } -} -func (k branchFlowKind) String() string { - switch k { - case branchFlowEmpty: - return "" - case branchFlowReturn: - return " ... return" - case branchFlowPanic: - return " ... panic()" - case branchFlowContinue: - return " ... continue" - case branchFlowBreak: - return " ... break" - case branchFlowGoto: - return " ... goto" - case branchFlowRegular: - return " ..." - default: - panic("invalid kind") + if chain.If.IsEmpty() { + return fmt.Sprintf("if c { } else { %[1]v } can be simplified to if !c { %[1]v }", chain.Else) } + return fmt.Sprintf("if c { ... } else { %[1]v } can be simplified to if !c { %[1]v } ...", chain.Else) } diff --git a/tools/vendor/github.com/mgechev/revive/rule/enforce-map-style.go b/tools/vendor/github.com/mgechev/revive/rule/enforce-map-style.go new file mode 100644 index 0000000000..ae27b654f2 --- /dev/null +++ b/tools/vendor/github.com/mgechev/revive/rule/enforce-map-style.go @@ -0,0 +1,164 @@ +package rule + +import ( + "fmt" + "go/ast" + "sync" + + "github.com/mgechev/revive/lint" +) + +type enforceMapStyleType string + +const ( + enforceMapStyleTypeAny enforceMapStyleType = "any" + enforceMapStyleTypeMake enforceMapStyleType = "make" + enforceMapStyleTypeLiteral enforceMapStyleType = "literal" +) + +func mapStyleFromString(s string) (enforceMapStyleType, error) { + switch s { + case string(enforceMapStyleTypeAny), "": + return enforceMapStyleTypeAny, nil + case string(enforceMapStyleTypeMake): + return enforceMapStyleTypeMake, nil + case string(enforceMapStyleTypeLiteral): + return enforceMapStyleTypeLiteral, nil + default: + return enforceMapStyleTypeAny, fmt.Errorf( + "invalid map style: %s (expecting one of %v)", + s, + []enforceMapStyleType{ + enforceMapStyleTypeAny, + enforceMapStyleTypeMake, + enforceMapStyleTypeLiteral, + }, + ) + } +} + +// EnforceMapStyleRule implements a rule to enforce `make(map[type]type)` over `map[type]type{}`. +type EnforceMapStyleRule struct { + configured bool + enforceMapStyle enforceMapStyleType + sync.Mutex +} + +func (r *EnforceMapStyleRule) configure(arguments lint.Arguments) { + r.Lock() + defer r.Unlock() + + if r.configured { + return + } + r.configured = true + + if len(arguments) < 1 { + r.enforceMapStyle = enforceMapStyleTypeAny + return + } + + enforceMapStyle, ok := arguments[0].(string) + if !ok { + panic(fmt.Sprintf("Invalid argument '%v' for 'enforce-map-style' rule. Expecting string, got %T", arguments[0], arguments[0])) + } + + var err error + r.enforceMapStyle, err = mapStyleFromString(enforceMapStyle) + + if err != nil { + panic(fmt.Sprintf("Invalid argument to the enforce-map-style rule: %v", err)) + } +} + +// Apply applies the rule to given file. +func (r *EnforceMapStyleRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + r.configure(arguments) + + if r.enforceMapStyle == enforceMapStyleTypeAny { + // this linter is not configured + return nil + } + + var failures []lint.Failure + + astFile := file.AST + ast.Inspect(astFile, func(n ast.Node) bool { + switch v := n.(type) { + case *ast.CompositeLit: + if r.enforceMapStyle != enforceMapStyleTypeMake { + return true + } + + if !r.isMapType(v.Type) { + return true + } + + if len(v.Elts) > 0 { + // not an empty map + return true + } + + failures = append(failures, lint.Failure{ + Confidence: 1, + Node: v, + Category: "style", + Failure: "use make(map[type]type) instead of map[type]type{}", + }) + case *ast.CallExpr: + if r.enforceMapStyle != enforceMapStyleTypeLiteral { + // skip any function calls, even if it's make(map[type]type) + // we don't want to report it if literals are not enforced + return true + } + + ident, ok := v.Fun.(*ast.Ident) + if !ok || ident.Name != "make" { + return true + } + + if len(v.Args) != 1 { + // skip make(map[type]type, size) and invalid empty declarations + return true + } + + if !r.isMapType(v.Args[0]) { + // not a map type + return true + } + + failures = append(failures, lint.Failure{ + Confidence: 1, + Node: v.Args[0], + Category: "style", + Failure: "use map[type]type{} instead of make(map[type]type)", + }) + } + return true + }) + + return failures +} + +// Name returns the rule name. +func (r *EnforceMapStyleRule) Name() string { + return "enforce-map-style" +} + +func (r *EnforceMapStyleRule) isMapType(v ast.Expr) bool { + switch t := v.(type) { + case *ast.MapType: + return true + case *ast.Ident: + if t.Obj == nil { + return false + } + typeSpec, ok := t.Obj.Decl.(*ast.TypeSpec) + if !ok { + return false + } + return r.isMapType(typeSpec.Type) + default: + return false + } +} diff --git a/tools/vendor/github.com/mgechev/revive/rule/file-header.go b/tools/vendor/github.com/mgechev/revive/rule/file-header.go index 76f548f51f..a7d69ff2b1 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/file-header.go +++ b/tools/vendor/github.com/mgechev/revive/rule/file-header.go @@ -21,21 +21,28 @@ var ( func (r *FileHeaderRule) configure(arguments lint.Arguments) { r.Lock() + defer r.Unlock() if r.header == "" { - checkNumberOfArguments(1, arguments, r.Name()) + if len(arguments) < 1 { + return + } + var ok bool r.header, ok = arguments[0].(string) if !ok { - panic(fmt.Sprintf("invalid argument for \"file-header\" rule: first argument should be a string, got %T", arguments[0])) + panic(fmt.Sprintf("invalid argument for \"file-header\" rule: argument should be a string, got %T", arguments[0])) } } - r.Unlock() } // Apply applies the rule to given file. func (r *FileHeaderRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { r.configure(arguments) + if r.header == "" { + return nil + } + failure := []lint.Failure{ { Node: file.AST, diff --git a/tools/vendor/github.com/mgechev/revive/rule/function-length.go b/tools/vendor/github.com/mgechev/revive/rule/function-length.go index d600d7a2a1..f7ab8b98e6 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/function-length.go +++ b/tools/vendor/github.com/mgechev/revive/rule/function-length.go @@ -19,13 +19,13 @@ type FunctionLength struct { func (r *FunctionLength) configure(arguments lint.Arguments) { r.Lock() + defer r.Unlock() if !r.configured { maxStmt, maxLines := r.parseArguments(arguments) r.maxStmt = int(maxStmt) r.maxLines = int(maxLines) r.configured = true } - r.Unlock() } // Apply applies the rule to given file. @@ -53,7 +53,14 @@ func (*FunctionLength) Name() string { return "function-length" } +const defaultFuncStmtsLimit = 50 +const defaultFuncLinesLimit = 75 + func (*FunctionLength) parseArguments(arguments lint.Arguments) (maxStmt, maxLines int64) { + if len(arguments) == 0 { + return defaultFuncStmtsLimit, defaultFuncLinesLimit + } + if len(arguments) != 2 { panic(fmt.Sprintf(`invalid configuration for "function-length" rule, expected 2 arguments but got %d`, len(arguments))) } diff --git a/tools/vendor/github.com/mgechev/revive/rule/function-result-limit.go b/tools/vendor/github.com/mgechev/revive/rule/function-result-limit.go index 5d2b87316a..6a0748011d 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/function-result-limit.go +++ b/tools/vendor/github.com/mgechev/revive/rule/function-result-limit.go @@ -14,11 +14,16 @@ type FunctionResultsLimitRule struct { sync.Mutex } +const defaultResultsLimit = 3 + func (r *FunctionResultsLimitRule) configure(arguments lint.Arguments) { r.Lock() + defer r.Unlock() if r.max == 0 { - checkNumberOfArguments(1, arguments, r.Name()) - + if len(arguments) < 1 { + r.max = defaultResultsLimit + return + } max, ok := arguments[0].(int64) // Alt. non panicking version if !ok { panic(fmt.Sprintf(`invalid value passed as return results number to the "function-result-limit" rule; need int64 but got %T`, arguments[0])) @@ -28,7 +33,6 @@ func (r *FunctionResultsLimitRule) configure(arguments lint.Arguments) { } r.max = int(max) } - r.Unlock() } // Apply applies the rule to given file. diff --git a/tools/vendor/github.com/mgechev/revive/rule/import-alias-naming.go b/tools/vendor/github.com/mgechev/revive/rule/import-alias-naming.go new file mode 100644 index 0000000000..292392b5de --- /dev/null +++ b/tools/vendor/github.com/mgechev/revive/rule/import-alias-naming.go @@ -0,0 +1,79 @@ +package rule + +import ( + "fmt" + "regexp" + "sync" + + "github.com/mgechev/revive/lint" +) + +// ImportAliasNamingRule lints import alias naming. +type ImportAliasNamingRule struct { + configured bool + namingRuleRegexp *regexp.Regexp + sync.Mutex +} + +const defaultNamingRule = "^[a-z][a-z0-9]{0,}$" + +var defaultNamingRuleRegexp = regexp.MustCompile(defaultNamingRule) + +func (r *ImportAliasNamingRule) configure(arguments lint.Arguments) { + r.Lock() + defer r.Unlock() + if r.configured { + return + } + + if len(arguments) < 1 { + r.namingRuleRegexp = defaultNamingRuleRegexp + return + } + + namingRule, ok := arguments[0].(string) // Alt. non panicking version + if !ok { + panic(fmt.Sprintf("Invalid argument '%v' for 'import-alias-naming' rule. Expecting string, got %T", arguments[0], arguments[0])) + } + + var err error + r.namingRuleRegexp, err = regexp.Compile(namingRule) + if err != nil { + panic(fmt.Sprintf("Invalid argument to the import-alias-naming rule. Expecting %q to be a valid regular expression, got: %v", namingRule, err)) + } +} + +// Apply applies the rule to given file. +func (r *ImportAliasNamingRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + r.configure(arguments) + + var failures []lint.Failure + + for _, is := range file.AST.Imports { + path := is.Path + if path == nil { + continue + } + + alias := is.Name + if alias == nil || alias.Name == "_" { + continue + } + + if !r.namingRuleRegexp.MatchString(alias.Name) { + failures = append(failures, lint.Failure{ + Confidence: 1, + Failure: fmt.Sprintf("import name (%s) must match the regular expression: %s", alias.Name, r.namingRuleRegexp.String()), + Node: alias, + Category: "imports", + }) + } + } + + return failures +} + +// Name returns the rule name. +func (*ImportAliasNamingRule) Name() string { + return "import-alias-naming" +} diff --git a/tools/vendor/github.com/mgechev/revive/rule/import-shadowing.go b/tools/vendor/github.com/mgechev/revive/rule/import-shadowing.go index 2bab704d02..046aeb688e 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/import-shadowing.go +++ b/tools/vendor/github.com/mgechev/revive/rule/import-shadowing.go @@ -29,6 +29,7 @@ func (*ImportShadowingRule) Apply(file *lint.File, _ lint.Arguments) []lint.Fail failures = append(failures, failure) }, alreadySeen: map[*ast.Object]struct{}{}, + skipIdents: map[*ast.Ident]struct{}{}, } ast.Walk(walker, fileAst) @@ -62,6 +63,7 @@ type importShadowing struct { importNames map[string]struct{} onFailure func(lint.Failure) alreadySeen map[*ast.Object]struct{} + skipIdents map[*ast.Ident]struct{} } // Visit visits AST nodes and checks if id nodes (ast.Ident) shadow an import name @@ -80,6 +82,10 @@ func (w importShadowing) Visit(n ast.Node) ast.Visitor { *ast.SelectorExpr, // skip analysis of selector expressions (anId.otherId): because if anId shadows an import name, it was already detected, and otherId does not shadows the import name *ast.StructType: // skip analysis of struct type because struct fields can not shadow an import name return nil + case *ast.FuncDecl: + if n.Recv != nil { + w.skipIdents[n.Name] = struct{}{} + } case *ast.Ident: if n == w.packageNameIdent { return nil // skip the ident corresponding to the package name of this file @@ -92,11 +98,12 @@ func (w importShadowing) Visit(n ast.Node) ast.Visitor { _, isImportName := w.importNames[id] _, alreadySeen := w.alreadySeen[n.Obj] - if isImportName && !alreadySeen { + _, skipIdent := w.skipIdents[n] + if isImportName && !alreadySeen && !skipIdent { w.onFailure(lint.Failure{ Confidence: 1, Node: n, - Category: "namming", + Category: "naming", Failure: fmt.Sprintf("The name '%s' shadows an import name", id), }) diff --git a/tools/vendor/github.com/mgechev/revive/rule/imports-blacklist.go b/tools/vendor/github.com/mgechev/revive/rule/imports-blacklist.go index 7106628155..bb8262cae8 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/imports-blacklist.go +++ b/tools/vendor/github.com/mgechev/revive/rule/imports-blacklist.go @@ -52,10 +52,6 @@ func (r *ImportsBlacklistRule) Apply(file *lint.File, arguments lint.Arguments) var failures []lint.Failure - if file.IsTest() { - return failures // skip, test file - } - for _, is := range file.AST.Imports { path := is.Path if path != nil && r.isBlacklisted(path.Value) { diff --git a/tools/vendor/github.com/mgechev/revive/rule/indent-error-flow.go b/tools/vendor/github.com/mgechev/revive/rule/indent-error-flow.go index e455801c47..b80e6486c9 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/indent-error-flow.go +++ b/tools/vendor/github.com/mgechev/revive/rule/indent-error-flow.go @@ -1,9 +1,7 @@ package rule import ( - "go/ast" - "go/token" - + "github.com/mgechev/revive/internal/ifelse" "github.com/mgechev/revive/lint" ) @@ -11,16 +9,8 @@ import ( type IndentErrorFlowRule struct{} // Apply applies the rule to given file. -func (*IndentErrorFlowRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { - var failures []lint.Failure - - onFailure := func(failure lint.Failure) { - failures = append(failures, failure) - } - - w := lintElse{make(map[*ast.IfStmt]bool), onFailure} - ast.Walk(w, file.AST) - return failures +func (e *IndentErrorFlowRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure { + return ifelse.Apply(e, file.AST, ifelse.TargetElse, args) } // Name returns the rule name. @@ -28,51 +18,28 @@ func (*IndentErrorFlowRule) Name() string { return "indent-error-flow" } -type lintElse struct { - ignore map[*ast.IfStmt]bool - onFailure func(lint.Failure) -} - -func (w lintElse) Visit(node ast.Node) ast.Visitor { - ifStmt, ok := node.(*ast.IfStmt) - if !ok || ifStmt.Else == nil { - return w - } - if w.ignore[ifStmt] { - if elseif, ok := ifStmt.Else.(*ast.IfStmt); ok { - w.ignore[elseif] = true - } - return w +// CheckIfElse evaluates the rule against an ifelse.Chain. +func (e *IndentErrorFlowRule) CheckIfElse(chain ifelse.Chain, args ifelse.Args) (failMsg string) { + if !chain.If.Deviates() { + // this rule only applies if the if-block deviates control flow + return } - if elseif, ok := ifStmt.Else.(*ast.IfStmt); ok { - w.ignore[elseif] = true - return w - } - if _, ok := ifStmt.Else.(*ast.BlockStmt); !ok { - // only care about elses without conditions - return w - } - if len(ifStmt.Body.List) == 0 { - return w + + if chain.HasPriorNonDeviating { + // if we de-indent the "else" block then a previous branch + // might flow into it, affecting program behaviour + return } - shortDecl := false // does the if statement have a ":=" initialization statement? - if ifStmt.Init != nil { - if as, ok := ifStmt.Init.(*ast.AssignStmt); ok && as.Tok == token.DEFINE { - shortDecl = true - } + + if !chain.If.Returns() { + // avoid overlapping with superfluous-else + return } - lastStmt := ifStmt.Body.List[len(ifStmt.Body.List)-1] - if _, ok := lastStmt.(*ast.ReturnStmt); ok { - extra := "" - if shortDecl { - extra = " (move short variable declaration to its own line if necessary)" - } - w.onFailure(lint.Failure{ - Confidence: 1, - Node: ifStmt.Else, - Category: "indent", - Failure: "if block ends with a return statement, so drop this else and outdent its block" + extra, - }) + + if args.PreserveScope && !chain.AtBlockEnd && (chain.HasInitializer || chain.Else.HasDecls) { + // avoid increasing variable scope + return } - return w + + return "if block ends with a return statement, so drop this else and outdent its block" } diff --git a/tools/vendor/github.com/mgechev/revive/rule/line-length-limit.go b/tools/vendor/github.com/mgechev/revive/rule/line-length-limit.go index 9e512c1c2c..1a414f6914 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/line-length-limit.go +++ b/tools/vendor/github.com/mgechev/revive/rule/line-length-limit.go @@ -18,10 +18,16 @@ type LineLengthLimitRule struct { sync.Mutex } +const defaultLineLengthLimit = 80 + func (r *LineLengthLimitRule) configure(arguments lint.Arguments) { r.Lock() + defer r.Unlock() if r.max == 0 { - checkNumberOfArguments(1, arguments, r.Name()) + if len(arguments) < 1 { + r.max = defaultLineLengthLimit + return + } max, ok := arguments[0].(int64) // Alt. non panicking version if !ok || max < 0 { @@ -30,7 +36,6 @@ func (r *LineLengthLimitRule) configure(arguments lint.Arguments) { r.max = int(max) } - r.Unlock() } // Apply applies the rule to given file. diff --git a/tools/vendor/github.com/mgechev/revive/rule/max-public-structs.go b/tools/vendor/github.com/mgechev/revive/rule/max-public-structs.go index e39f49c698..25be3e676f 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/max-public-structs.go +++ b/tools/vendor/github.com/mgechev/revive/rule/max-public-structs.go @@ -14,9 +14,17 @@ type MaxPublicStructsRule struct { sync.Mutex } +const defaultMaxPublicStructs = 5 + func (r *MaxPublicStructsRule) configure(arguments lint.Arguments) { r.Lock() + defer r.Unlock() if r.max < 1 { + if len(arguments) < 1 { + r.max = defaultMaxPublicStructs + return + } + checkNumberOfArguments(1, arguments, r.Name()) max, ok := arguments[0].(int64) // Alt. non panicking version @@ -25,7 +33,6 @@ func (r *MaxPublicStructsRule) configure(arguments lint.Arguments) { } r.max = max } - r.Unlock() } // Apply applies the rule to given file. diff --git a/tools/vendor/github.com/mgechev/revive/rule/redundant-import-alias.go b/tools/vendor/github.com/mgechev/revive/rule/redundant-import-alias.go new file mode 100644 index 0000000000..fa5281f24b --- /dev/null +++ b/tools/vendor/github.com/mgechev/revive/rule/redundant-import-alias.go @@ -0,0 +1,52 @@ +package rule + +import ( + "fmt" + "go/ast" + "strings" + + "github.com/mgechev/revive/lint" +) + +// RedundantImportAlias lints given else constructs. +type RedundantImportAlias struct{} + +// Apply applies the rule to given file. +func (*RedundantImportAlias) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + for _, imp := range file.AST.Imports { + if imp.Name == nil { + continue + } + + if getImportPackageName(imp) == imp.Name.Name { + failures = append(failures, lint.Failure{ + Confidence: 1, + Failure: fmt.Sprintf("Import alias \"%s\" is redundant", imp.Name.Name), + Node: imp, + Category: "imports", + }) + } + } + + return failures +} + +// Name returns the rule name. +func (*RedundantImportAlias) Name() string { + return "redundant-import-alias" +} + +func getImportPackageName(imp *ast.ImportSpec) string { + const pathSep = "/" + const strDelim = `"` + + path := imp.Path.Value + i := strings.LastIndex(path, pathSep) + if i == -1 { + return strings.Trim(path, strDelim) + } + + return strings.Trim(path[i+1:], strDelim) +} diff --git a/tools/vendor/github.com/mgechev/revive/rule/string-format.go b/tools/vendor/github.com/mgechev/revive/rule/string-format.go index 0e30ebf8b3..3edd62477d 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/string-format.go +++ b/tools/vendor/github.com/mgechev/revive/rule/string-format.go @@ -211,10 +211,14 @@ func (lintStringFormatRule) getCallName(call *ast.CallExpr) (callName string, ok if selector, ok := call.Fun.(*ast.SelectorExpr); ok { // Scoped function call scope, ok := selector.X.(*ast.Ident) - if !ok { - return "", false + if ok { + return scope.Name + "." + selector.Sel.Name, true + } + // Scoped function call inside structure + recv, ok := selector.X.(*ast.SelectorExpr) + if ok { + return recv.Sel.Name + "." + selector.Sel.Name, true } - return scope.Name + "." + selector.Sel.Name, true } return "", false diff --git a/tools/vendor/github.com/mgechev/revive/rule/superfluous-else.go b/tools/vendor/github.com/mgechev/revive/rule/superfluous-else.go index a9e4380c90..1ef67bf1a4 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/superfluous-else.go +++ b/tools/vendor/github.com/mgechev/revive/rule/superfluous-else.go @@ -2,9 +2,7 @@ package rule import ( "fmt" - "go/ast" - "go/token" - + "github.com/mgechev/revive/internal/ifelse" "github.com/mgechev/revive/lint" ) @@ -12,27 +10,8 @@ import ( type SuperfluousElseRule struct{} // Apply applies the rule to given file. -func (*SuperfluousElseRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { - var failures []lint.Failure - onFailure := func(failure lint.Failure) { - failures = append(failures, failure) - } - - branchingFunctions := map[string]map[string]bool{ - "os": {"Exit": true}, - "log": { - "Fatal": true, - "Fatalf": true, - "Fatalln": true, - "Panic": true, - "Panicf": true, - "Panicln": true, - }, - } - - w := lintSuperfluousElse{make(map[*ast.IfStmt]bool), onFailure, branchingFunctions} - ast.Walk(w, file.AST) - return failures +func (e *SuperfluousElseRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure { + return ifelse.Apply(e, file.AST, ifelse.TargetElse, args) } // Name returns the rule name. @@ -40,75 +19,28 @@ func (*SuperfluousElseRule) Name() string { return "superfluous-else" } -type lintSuperfluousElse struct { - ignore map[*ast.IfStmt]bool - onFailure func(lint.Failure) - branchingFunctions map[string]map[string]bool -} - -func (w lintSuperfluousElse) Visit(node ast.Node) ast.Visitor { - ifStmt, ok := node.(*ast.IfStmt) - if !ok || ifStmt.Else == nil { - return w - } - if w.ignore[ifStmt] { - if elseif, ok := ifStmt.Else.(*ast.IfStmt); ok { - w.ignore[elseif] = true - } - return w - } - if elseif, ok := ifStmt.Else.(*ast.IfStmt); ok { - w.ignore[elseif] = true - return w - } - if _, ok := ifStmt.Else.(*ast.BlockStmt); !ok { - // only care about elses without conditions - return w - } - if len(ifStmt.Body.List) == 0 { - return w - } - shortDecl := false // does the if statement have a ":=" initialization statement? - if ifStmt.Init != nil { - if as, ok := ifStmt.Init.(*ast.AssignStmt); ok && as.Tok == token.DEFINE { - shortDecl = true - } - } - extra := "" - if shortDecl { - extra = " (move short variable declaration to its own line if necessary)" +// CheckIfElse evaluates the rule against an ifelse.Chain. +func (e *SuperfluousElseRule) CheckIfElse(chain ifelse.Chain, args ifelse.Args) (failMsg string) { + if !chain.If.Deviates() { + // this rule only applies if the if-block deviates control flow + return } - lastStmt := ifStmt.Body.List[len(ifStmt.Body.List)-1] - switch stmt := lastStmt.(type) { - case *ast.BranchStmt: - tok := stmt.Tok.String() - if tok != "fallthrough" { - w.onFailure(newFailure(ifStmt.Else, "if block ends with a "+tok+" statement, so drop this else and outdent its block"+extra)) - } - case *ast.ExprStmt: - if ce, ok := stmt.X.(*ast.CallExpr); ok { // it's a function call - if fc, ok := ce.Fun.(*ast.SelectorExpr); ok { - if id, ok := fc.X.(*ast.Ident); ok { - fn := fc.Sel.Name - pkg := id.Name - if w.branchingFunctions[pkg][fn] { // it's a call to a branching function - w.onFailure( - newFailure(ifStmt.Else, fmt.Sprintf("if block ends with call to %s.%s function, so drop this else and outdent its block%s", pkg, fn, extra))) - } - } - } - } + if chain.HasPriorNonDeviating { + // if we de-indent the "else" block then a previous branch + // might flow into it, affecting program behaviour + return } - return w -} + if chain.If.Returns() { + // avoid overlapping with indent-error-flow + return + } -func newFailure(node ast.Node, msg string) lint.Failure { - return lint.Failure{ - Confidence: 1, - Node: node, - Category: "indent", - Failure: msg, + if args.PreserveScope && !chain.AtBlockEnd && (chain.HasInitializer || chain.Else.HasDecls) { + // avoid increasing variable scope + return } + + return fmt.Sprintf("if block ends with %v, so drop this else and outdent its block", chain.If.LongString()) } diff --git a/tools/vendor/github.com/mgechev/revive/rule/time-equal.go b/tools/vendor/github.com/mgechev/revive/rule/time-equal.go index 72ecf26fe4..3b85e18a8e 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/time-equal.go +++ b/tools/vendor/github.com/mgechev/revive/rule/time-equal.go @@ -60,9 +60,9 @@ func (l *lintTimeEqual) Visit(node ast.Node) ast.Visitor { var failure string switch expr.Op { case token.EQL: - failure = fmt.Sprintf("use %s.Equal(%s) instead of %q operator", expr.X, expr.Y, expr.Op) + failure = fmt.Sprintf("use %s.Equal(%s) instead of %q operator", gofmt(expr.X), gofmt(expr.Y), expr.Op) case token.NEQ: - failure = fmt.Sprintf("use !%s.Equal(%s) instead of %q operator", expr.X, expr.Y, expr.Op) + failure = fmt.Sprintf("use !%s.Equal(%s) instead of %q operator", gofmt(expr.X), gofmt(expr.Y), expr.Op) } l.onFailure(lint.Failure{ diff --git a/tools/vendor/github.com/mgechev/revive/rule/unchecked-type-assertion.go b/tools/vendor/github.com/mgechev/revive/rule/unchecked-type-assertion.go new file mode 100644 index 0000000000..df27743cbd --- /dev/null +++ b/tools/vendor/github.com/mgechev/revive/rule/unchecked-type-assertion.go @@ -0,0 +1,194 @@ +package rule + +import ( + "fmt" + "go/ast" + "sync" + + "github.com/mgechev/revive/lint" +) + +const ( + ruleUTAMessagePanic = "type assertion will panic if not matched" + ruleUTAMessageIgnored = "type assertion result ignored" +) + +// UncheckedTypeAssertionRule lints missing or ignored `ok`-value in danymic type casts. +type UncheckedTypeAssertionRule struct { + sync.Mutex + acceptIgnoredAssertionResult bool + configured bool +} + +func (u *UncheckedTypeAssertionRule) configure(arguments lint.Arguments) { + u.Lock() + defer u.Unlock() + + if len(arguments) == 0 || u.configured { + return + } + + u.configured = true + + args, ok := arguments[0].(map[string]any) + if !ok { + panic("Unable to get arguments. Expected object of key-value-pairs.") + } + + for k, v := range args { + switch k { + case "acceptIgnoredAssertionResult": + u.acceptIgnoredAssertionResult, ok = v.(bool) + if !ok { + panic(fmt.Sprintf("Unable to parse argument '%s'. Expected boolean.", k)) + } + default: + panic(fmt.Sprintf("Unknown argument: %s", k)) + } + } +} + +// Apply applies the rule to given file. +func (u *UncheckedTypeAssertionRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure { + u.configure(args) + + var failures []lint.Failure + + walker := &lintUnchekedTypeAssertion{ + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + acceptIgnoredTypeAssertionResult: u.acceptIgnoredAssertionResult, + } + + ast.Walk(walker, file.AST) + + return failures +} + +// Name returns the rule name. +func (*UncheckedTypeAssertionRule) Name() string { + return "unchecked-type-assertion" +} + +type lintUnchekedTypeAssertion struct { + onFailure func(lint.Failure) + acceptIgnoredTypeAssertionResult bool +} + +func isIgnored(e ast.Expr) bool { + ident, ok := e.(*ast.Ident) + if !ok { + return false + } + + return ident.Name == "_" +} + +func isTypeSwitch(e *ast.TypeAssertExpr) bool { + return e.Type == nil +} + +func (w *lintUnchekedTypeAssertion) requireNoTypeAssert(expr ast.Expr) { + e, ok := expr.(*ast.TypeAssertExpr) + if ok && !isTypeSwitch(e) { + w.addFailure(e, ruleUTAMessagePanic) + } +} + +func (w *lintUnchekedTypeAssertion) handleIfStmt(n *ast.IfStmt) { + ifCondition, ok := n.Cond.(*ast.BinaryExpr) + if ok { + w.requireNoTypeAssert(ifCondition.X) + w.requireNoTypeAssert(ifCondition.Y) + } +} + +func (w *lintUnchekedTypeAssertion) requireBinaryExpressionWithoutTypeAssertion(expr ast.Expr) { + binaryExpr, ok := expr.(*ast.BinaryExpr) + if ok { + w.requireNoTypeAssert(binaryExpr.X) + w.requireNoTypeAssert(binaryExpr.Y) + } +} + +func (w *lintUnchekedTypeAssertion) handleCaseClause(n *ast.CaseClause) { + for _, expr := range n.List { + w.requireNoTypeAssert(expr) + w.requireBinaryExpressionWithoutTypeAssertion(expr) + } +} + +func (w *lintUnchekedTypeAssertion) handleSwitch(n *ast.SwitchStmt) { + w.requireNoTypeAssert(n.Tag) + w.requireBinaryExpressionWithoutTypeAssertion(n.Tag) +} + +func (w *lintUnchekedTypeAssertion) handleAssignment(n *ast.AssignStmt) { + if len(n.Rhs) == 0 { + return + } + + e, ok := n.Rhs[0].(*ast.TypeAssertExpr) + if !ok || e == nil { + return + } + + if isTypeSwitch(e) { + return + } + + if len(n.Lhs) == 1 { + w.addFailure(e, ruleUTAMessagePanic) + } + + if !w.acceptIgnoredTypeAssertionResult && len(n.Lhs) == 2 && isIgnored(n.Lhs[1]) { + w.addFailure(e, ruleUTAMessageIgnored) + } +} + +// handles "return foo(.*bar)" - one of them is enough to fail as golang does not forward the type cast tuples in return statements +func (w *lintUnchekedTypeAssertion) handleReturn(n *ast.ReturnStmt) { + for _, r := range n.Results { + w.requireNoTypeAssert(r) + } +} + +func (w *lintUnchekedTypeAssertion) handleRange(n *ast.RangeStmt) { + w.requireNoTypeAssert(n.X) +} + +func (w *lintUnchekedTypeAssertion) handleChannelSend(n *ast.SendStmt) { + w.requireNoTypeAssert(n.Value) +} + +func (w *lintUnchekedTypeAssertion) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.RangeStmt: + w.handleRange(n) + case *ast.SwitchStmt: + w.handleSwitch(n) + case *ast.ReturnStmt: + w.handleReturn(n) + case *ast.AssignStmt: + w.handleAssignment(n) + case *ast.IfStmt: + w.handleIfStmt(n) + case *ast.CaseClause: + w.handleCaseClause(n) + case *ast.SendStmt: + w.handleChannelSend(n) + } + + return w +} + +func (w *lintUnchekedTypeAssertion) addFailure(n *ast.TypeAssertExpr, why string) { + s := fmt.Sprintf("type cast result is unchecked in %v - %s", gofmt(n), why) + w.onFailure(lint.Failure{ + Category: "bad practice", + Confidence: 1, + Node: n, + Failure: s, + }) +} diff --git a/tools/vendor/github.com/mgechev/revive/rule/unused-param.go b/tools/vendor/github.com/mgechev/revive/rule/unused-param.go index ab3da453ee..df6cd9af0e 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/unused-param.go +++ b/tools/vendor/github.com/mgechev/revive/rule/unused-param.go @@ -3,22 +3,72 @@ package rule import ( "fmt" "go/ast" + "regexp" + "sync" "github.com/mgechev/revive/lint" ) // UnusedParamRule lints unused params in functions. -type UnusedParamRule struct{} +type UnusedParamRule struct { + configured bool + // regex to check if some name is valid for unused parameter, "^_$" by default + allowRegex *regexp.Regexp + failureMsg string + sync.Mutex +} + +func (r *UnusedParamRule) configure(args lint.Arguments) { + r.Lock() + defer r.Unlock() + + if r.configured { + return + } + r.configured = true + + // while by default args is an array, i think it's good to provide structures inside it by default, not arrays or primitives + // it's more compatible to JSON nature of configurations + var allowedRegexStr string + if len(args) == 0 { + allowedRegexStr = "^_$" + r.failureMsg = "parameter '%s' seems to be unused, consider removing or renaming it as _" + } else { + // Arguments = [{}] + options := args[0].(map[string]interface{}) + // Arguments = [{allowedRegex="^_"}] + + if allowedRegexParam, ok := options["allowRegex"]; ok { + allowedRegexStr, ok = allowedRegexParam.(string) + if !ok { + panic(fmt.Errorf("error configuring %s rule: allowedRegex is not string but [%T]", r.Name(), allowedRegexParam)) + } + } + } + var err error + r.allowRegex, err = regexp.Compile(allowedRegexStr) + if err != nil { + panic(fmt.Errorf("error configuring %s rule: allowedRegex is not valid regex [%s]: %v", r.Name(), allowedRegexStr, err)) + } + + if r.failureMsg == "" { + r.failureMsg = "parameter '%s' seems to be unused, consider removing or renaming it to match " + r.allowRegex.String() + } +} // Apply applies the rule to given file. -func (*UnusedParamRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { +func (r *UnusedParamRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure { + r.configure(args) var failures []lint.Failure onFailure := func(failure lint.Failure) { failures = append(failures, failure) } - - w := lintUnusedParamRule{onFailure: onFailure} + w := lintUnusedParamRule{ + onFailure: onFailure, + allowRegex: r.allowRegex, + failureMsg: r.failureMsg, + } ast.Walk(w, file.AST) @@ -31,7 +81,9 @@ func (*UnusedParamRule) Name() string { } type lintUnusedParamRule struct { - onFailure func(lint.Failure) + onFailure func(lint.Failure) + allowRegex *regexp.Regexp + failureMsg string } func (w lintUnusedParamRule) Visit(node ast.Node) ast.Visitor { @@ -65,12 +117,15 @@ func (w lintUnusedParamRule) Visit(node ast.Node) ast.Visitor { for _, p := range n.Type.Params.List { for _, n := range p.Names { + if w.allowRegex.FindStringIndex(n.Name) != nil { + continue + } if params[n.Obj] { w.onFailure(lint.Failure{ Confidence: 1, Node: n, Category: "bad practice", - Failure: fmt.Sprintf("parameter '%s' seems to be unused, consider removing or renaming it as _", n.Name), + Failure: fmt.Sprintf(w.failureMsg, n.Name), }) } } diff --git a/tools/vendor/github.com/mgechev/revive/rule/unused-receiver.go b/tools/vendor/github.com/mgechev/revive/rule/unused-receiver.go index 2289a517e5..488572b7bd 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/unused-receiver.go +++ b/tools/vendor/github.com/mgechev/revive/rule/unused-receiver.go @@ -3,22 +3,72 @@ package rule import ( "fmt" "go/ast" + "regexp" + "sync" "github.com/mgechev/revive/lint" ) // UnusedReceiverRule lints unused params in functions. -type UnusedReceiverRule struct{} +type UnusedReceiverRule struct { + configured bool + // regex to check if some name is valid for unused parameter, "^_$" by default + allowRegex *regexp.Regexp + failureMsg string + sync.Mutex +} + +func (r *UnusedReceiverRule) configure(args lint.Arguments) { + r.Lock() + defer r.Unlock() + + if r.configured { + return + } + r.configured = true + + // while by default args is an array, i think it's good to provide structures inside it by default, not arrays or primitives + // it's more compatible to JSON nature of configurations + var allowedRegexStr string + if len(args) == 0 { + allowedRegexStr = "^_$" + r.failureMsg = "method receiver '%s' is not referenced in method's body, consider removing or renaming it as _" + } else { + // Arguments = [{}] + options := args[0].(map[string]interface{}) + // Arguments = [{allowedRegex="^_"}] + + if allowedRegexParam, ok := options["allowRegex"]; ok { + allowedRegexStr, ok = allowedRegexParam.(string) + if !ok { + panic(fmt.Errorf("error configuring [unused-receiver] rule: allowedRegex is not string but [%T]", allowedRegexParam)) + } + } + } + var err error + r.allowRegex, err = regexp.Compile(allowedRegexStr) + if err != nil { + panic(fmt.Errorf("error configuring [unused-receiver] rule: allowedRegex is not valid regex [%s]: %v", allowedRegexStr, err)) + } + if r.failureMsg == "" { + r.failureMsg = "method receiver '%s' is not referenced in method's body, consider removing or renaming it to match " + r.allowRegex.String() + } +} // Apply applies the rule to given file. -func (*UnusedReceiverRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { +func (r *UnusedReceiverRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure { + r.configure(args) var failures []lint.Failure onFailure := func(failure lint.Failure) { failures = append(failures, failure) } - w := lintUnusedReceiverRule{onFailure: onFailure} + w := lintUnusedReceiverRule{ + onFailure: onFailure, + allowRegex: r.allowRegex, + failureMsg: r.failureMsg, + } ast.Walk(w, file.AST) @@ -31,7 +81,9 @@ func (*UnusedReceiverRule) Name() string { } type lintUnusedReceiverRule struct { - onFailure func(lint.Failure) + onFailure func(lint.Failure) + allowRegex *regexp.Regexp + failureMsg string } func (w lintUnusedReceiverRule) Visit(node ast.Node) ast.Visitor { @@ -51,6 +103,10 @@ func (w lintUnusedReceiverRule) Visit(node ast.Node) ast.Visitor { return nil // the receiver is already named _ } + if w.allowRegex != nil && w.allowRegex.FindStringIndex(recID.Name) != nil { + return nil + } + // inspect the func body looking for references to the receiver id fselect := func(n ast.Node) bool { ident, isAnID := n.(*ast.Ident) @@ -67,7 +123,7 @@ func (w lintUnusedReceiverRule) Visit(node ast.Node) ast.Visitor { Confidence: 1, Node: recID, Category: "bad practice", - Failure: fmt.Sprintf("method receiver '%s' is not referenced in method's body, consider removing or renaming it as _", recID.Name), + Failure: fmt.Sprintf(w.failureMsg, recID.Name), }) return nil // full method body already inspected diff --git a/tools/vendor/github.com/mgechev/revive/rule/var-naming.go b/tools/vendor/github.com/mgechev/revive/rule/var-naming.go index fa4a188642..286ff9d75d 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/var-naming.go +++ b/tools/vendor/github.com/mgechev/revive/rule/var-naming.go @@ -13,27 +13,50 @@ import ( var anyCapsRE = regexp.MustCompile(`[A-Z]`) +// regexp for constant names like `SOME_CONST`, `SOME_CONST_2`, `X123_3`, `_SOME_PRIVATE_CONST` (#851, #865) +var upperCaseConstRE = regexp.MustCompile(`^_?[A-Z][A-Z\d]*(_[A-Z\d]+)*$`) + // VarNamingRule lints given else constructs. type VarNamingRule struct { - configured bool - whitelist []string - blacklist []string + configured bool + whitelist []string + blacklist []string + upperCaseConst bool // if true - allows to use UPPER_SOME_NAMES for constants sync.Mutex } func (r *VarNamingRule) configure(arguments lint.Arguments) { r.Lock() - if !r.configured { - if len(arguments) >= 1 { - r.whitelist = getList(arguments[0], "whitelist") - } + defer r.Unlock() + if r.configured { + return + } + + r.configured = true + if len(arguments) >= 1 { + r.whitelist = getList(arguments[0], "whitelist") + } - if len(arguments) >= 2 { - r.blacklist = getList(arguments[1], "blacklist") + if len(arguments) >= 2 { + r.blacklist = getList(arguments[1], "blacklist") + } + + if len(arguments) >= 3 { + // not pretty code because should keep compatibility with TOML (no mixed array types) and new map parameters + thirdArgument := arguments[2] + asSlice, ok := thirdArgument.([]interface{}) + if !ok { + panic(fmt.Sprintf("Invalid third argument to the var-naming rule. Expecting a %s of type slice, got %T", "options", arguments[2])) + } + if len(asSlice) != 1 { + panic(fmt.Sprintf("Invalid third argument to the var-naming rule. Expecting a %s of type slice, of len==1, but %d", "options", len(asSlice))) } - r.configured = true + args, ok := asSlice[0].(map[string]interface{}) + if !ok { + panic(fmt.Sprintf("Invalid third argument to the var-naming rule. Expecting a %s of type slice, of len==1, with map, but %T", "options", asSlice[0])) + } + r.upperCaseConst = fmt.Sprint(args["upperCaseConst"]) == "true" } - r.Unlock() } // Apply applies the rule to given file. @@ -52,6 +75,7 @@ func (r *VarNamingRule) Apply(file *lint.File, arguments lint.Arguments) []lint. onFailure: func(failure lint.Failure) { failures = append(failures, failure) }, + upperCaseConst: r.upperCaseConst, } // Package names need slightly different handling than other names. @@ -82,18 +106,18 @@ func (*VarNamingRule) Name() string { return "var-naming" } -func checkList(fl *ast.FieldList, thing string, w *lintNames) { +func (w *lintNames) checkList(fl *ast.FieldList, thing string) { if fl == nil { return } for _, f := range fl.List { for _, id := range f.Names { - check(id, thing, w) + w.check(id, thing) } } } -func check(id *ast.Ident, thing string, w *lintNames) { +func (w *lintNames) check(id *ast.Ident, thing string) { if id.Name == "_" { return } @@ -101,6 +125,12 @@ func check(id *ast.Ident, thing string, w *lintNames) { return } + // #851 upperCaseConst support + // if it's const + if thing == token.CONST.String() && w.upperCaseConst && upperCaseConstRE.Match([]byte(id.Name)) { + return + } + // Handle two common styles from other languages that don't belong in Go. if len(id.Name) >= 5 && allCapsRE.MatchString(id.Name) && strings.Contains(id.Name, "_") { w.onFailure(lint.Failure{ @@ -111,15 +141,6 @@ func check(id *ast.Ident, thing string, w *lintNames) { }) return } - if len(id.Name) > 2 && id.Name[0] == 'k' && id.Name[1] >= 'A' && id.Name[1] <= 'Z' { - should := string(id.Name[1]+'a'-'A') + id.Name[2:] - w.onFailure(lint.Failure{ - Failure: fmt.Sprintf("don't use leading k in Go names; %s %s should be %s", thing, id.Name, should), - Confidence: 0.8, - Node: id, - Category: "naming", - }) - } should := lint.Name(id.Name, w.whitelist, w.blacklist) if id.Name == should { @@ -144,11 +165,12 @@ func check(id *ast.Ident, thing string, w *lintNames) { } type lintNames struct { - file *lint.File - fileAst *ast.File - onFailure func(lint.Failure) - whitelist []string - blacklist []string + file *lint.File + fileAst *ast.File + onFailure func(lint.Failure) + whitelist []string + blacklist []string + upperCaseConst bool } func (w *lintNames) Visit(n ast.Node) ast.Visitor { @@ -159,7 +181,7 @@ func (w *lintNames) Visit(n ast.Node) ast.Visitor { } for _, exp := range v.Lhs { if id, ok := exp.(*ast.Ident); ok { - check(id, "var", w) + w.check(id, "var") } } case *ast.FuncDecl: @@ -181,31 +203,24 @@ func (w *lintNames) Visit(n ast.Node) ast.Visitor { // not exported in the Go API. // See https://github.com/golang/lint/issues/144. if ast.IsExported(v.Name.Name) || !isCgoExported(v) { - check(v.Name, thing, w) + w.check(v.Name, thing) } - checkList(v.Type.Params, thing+" parameter", w) - checkList(v.Type.Results, thing+" result", w) + w.checkList(v.Type.Params, thing+" parameter") + w.checkList(v.Type.Results, thing+" result") case *ast.GenDecl: if v.Tok == token.IMPORT { return w } - var thing string - switch v.Tok { - case token.CONST: - thing = "const" - case token.TYPE: - thing = "type" - case token.VAR: - thing = "var" - } + + thing := v.Tok.String() for _, spec := range v.Specs { switch s := spec.(type) { case *ast.TypeSpec: - check(s.Name, thing, w) + w.check(s.Name, thing) case *ast.ValueSpec: for _, id := range s.Names { - check(id, thing, w) + w.check(id, thing) } } } @@ -217,23 +232,23 @@ func (w *lintNames) Visit(n ast.Node) ast.Visitor { if !ok { // might be an embedded interface name continue } - checkList(ft.Params, "interface method parameter", w) - checkList(ft.Results, "interface method result", w) + w.checkList(ft.Params, "interface method parameter") + w.checkList(ft.Results, "interface method result") } case *ast.RangeStmt: if v.Tok == token.ASSIGN { return w } if id, ok := v.Key.(*ast.Ident); ok { - check(id, "range var", w) + w.check(id, "range var") } if id, ok := v.Value.(*ast.Ident); ok { - check(id, "range var", w) + w.check(id, "range var") } case *ast.StructType: for _, f := range v.Fields.List { for _, id := range f.Names { - check(id, "struct field", w) + w.check(id, "struct field") } } } diff --git a/tools/vendor/github.com/nbutton23/zxcvbn-go/.gitignore b/tools/vendor/github.com/nbutton23/zxcvbn-go/.gitignore deleted file mode 100644 index 4bff1a28e4..0000000000 --- a/tools/vendor/github.com/nbutton23/zxcvbn-go/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -zxcvbn -debug.test diff --git a/tools/vendor/github.com/nbutton23/zxcvbn-go/Makefile b/tools/vendor/github.com/nbutton23/zxcvbn-go/Makefile deleted file mode 100644 index 6aa13e0067..0000000000 --- a/tools/vendor/github.com/nbutton23/zxcvbn-go/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -PKG_LIST = $$( go list ./... | grep -v /vendor/ | grep -v "zxcvbn-go/data" ) - -.DEFAULT_GOAL := help - -.PHONY: help -help: - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' - -.PHONY: test -test: ## Run `go test {Package list}` on the packages - go test $(PKG_LIST) - -.PHONY: lint -lint: ## Run `golint {Package list}` - golint $(PKG_LIST) \ No newline at end of file diff --git a/tools/vendor/github.com/nunnatsa/ginkgolinter/.golangci.yml b/tools/vendor/github.com/nunnatsa/ginkgolinter/.golangci.yml new file mode 100644 index 0000000000..71ff0d034c --- /dev/null +++ b/tools/vendor/github.com/nunnatsa/ginkgolinter/.golangci.yml @@ -0,0 +1,3 @@ +linters: + enable: + - revive diff --git a/tools/vendor/github.com/nunnatsa/ginkgolinter/README.md b/tools/vendor/github.com/nunnatsa/ginkgolinter/README.md index e0a4b0739f..6c95917d26 100644 --- a/tools/vendor/github.com/nunnatsa/ginkgolinter/README.md +++ b/tools/vendor/github.com/nunnatsa/ginkgolinter/README.md @@ -27,7 +27,7 @@ ginkgolinter [-fix] ./... Use the `-fix` flag to apply the fix suggestions to the source code. ### Use ginkgolinter with golangci-lint -The ginkgolinter is now part of the popular [golangci-lint](https://golangci-lint.run/), starting from version `v0.51.1`. +The ginkgolinter is now part of the popular [golangci-lint](https://golangci-lint.run/), starting from version `v1.51.1`. It is not enabled by default, though. There are two ways to run ginkgolinter with golangci-lint: @@ -45,7 +45,8 @@ It is not enabled by default, though. There are two ways to run ginkgolinter wit - ginkgolinter ``` ## Linter Checks -The linter checks the gomega assertions in golang test code. Gomega may be used together with ginkgo tests, For example: +The linter checks the ginkgo and gomega assertions in golang test code. Gomega may be used together with ginkgo tests, +For example: ```go It("should test something", func() { // It is ginkgo test case function Expect("abcd").To(HaveLen(4), "the string should have a length of 4") // Expect is the gomega assertion @@ -71,7 +72,140 @@ The linter checks the `Expect`, `ExpectWithOffset` and the `Ω` "actual" functio It also supports the embedded `Not()` matcher -### Wrong Length Assertion +Some checks find actual bugs, and some are more for style. + +### Using a function call in async assertion [BUG] +This rule finds an actual bug in tests, where asserting a function call in an async function; e.g. `Eventually`. For +example: +```go +func slowInt(int val) int { + time.Sleep(time.Second) + return val +} + +... + +It("should test that slowInt returns 42, eventually", func() { + Eventually(slowInt(42)).WithPolling(time.Millisecond * 100).WithTimeout(time.Second * 2).Equal(42) +}) +``` +The problem with the above code is that it **should** poll - call the function - until it returns 42, but what actually +happens is that first the function is called, and we pass `42` to `Eventually` - not the function. This is not what we +tried to do here. + +The linter will suggest replacing this code by: +```go +It("should test that slowInt returns 42, eventually", func() { + Eventually(slowInt).WithArguments(42).WithPolling(time.Millisecond * 100).WithTimeout(time.Second * 2).Equal(42) +}) +``` + +The linter suggested replacing the function call by the function name. + +If function arguments are used, the linter will add the `WithArguments()` method to pass them. + +Please notice that `WithArguments()` is only supported from gomenga v1.22.0. + +When using an older version of gomega, change the code manually. For example: + +```go +It("should test that slowInt returns 42, eventually", func() { + Eventually(func() int { + slowint(42) + }).WithPolling(time.Millisecond * 100).WithTimeout(time.Second * 2).Equal(42) +}) +``` + +### Comparing a pointer with a value [BUG] +The linter warns when comparing a pointer with a value. +These comparisons are always wrong and will always fail. + +In case of a positive assertion (`To()` or `Should()`), the test will just fail. + +But the main concern is for false positive tests, when using a negative assertion (`NotTo()`, `ToNot()`, `ShouldNot()`, +`Should(Not())` etc.); e.g. +```go +num := 5 +... +pNum := &num +... +Expect(pNum).ShouldNot(Equal(6)) +``` +This assertion will pass, but for the wrong reasons: pNum is not equal 6, not because num == 5, but because pNum is +a pointer, while `6` is an `int`. + +In the case above, the linter will suggest `Expect(pNum).ShouldNot(HaveValue(Equal(6)))` + +This is also right for additional matchers: `BeTrue()` and `BeFalse()`, `BeIdenticalTo()`, `BeEquivalentTo()` +and `BeNumerically`. + +### Missing Assertion Method [BUG] +The linter warns when calling an "actual" method (e.g. `Expect()`, `Eventually()` etc.), without an assertion method (e.g +`Should()`, `NotTo()` etc.) + +For example: +```go +// no assertion for the result +Eventually(doSomething).WithTimeout(time.Seconds * 5).WithPolling(time.Milliseconds * 100) +``` + +The linter will not suggest a fix for this warning. + +This rule cannot be suppressed. + +### Focus Container / Focus individual spec found [BUG] +This rule finds ginkgo focus containers, or the `Focus` individual spec in the code. + +ginkgo supports the `FDescribe`, `FContext`, `FWhen`, `FIt`, `FDescribeTable` and `FEntry` +containers to allow the developer to focus +on a specific test or set of tests during test development or debug. + +For example: +```go +var _ = Describe("checking something", func() { + FIt("this test is the only one that will run", func(){ + ... + }) +}) +``` +Alternatively, the `Focus` individual spec may be used for the same purpose, e.g. +```go +var _ = Describe("checking something", Focus, func() { + It("this test is the only one that will run", func(){ + ... + }) +}) +``` + +These container, or the `Focus` spec, must not be part of the final source code, and should only be used locally by the developer. + +***This rule is disabled by default***. Use the `--forbid-focus-container=true` command line flag to enable it. + +### Comparing values from different types [BUG] + +The `Equal` and the `BeIdentical` matchers also check the type, not only the value. + +The following code will fail in runtime: +```go +x := 5 // x is int +Expect(x).Should(Eqaul(uint(5)) // x and uint(5) are with different +``` +When using negative checks, it's even worse, because we get a false positive: +``` +x := 5 +Expect(x).ShouldNot(Equal(uint(5)) +``` + +The linter suggests two options to solve this warning: either compare with the same type, e.g. +using casting, or use the `BeEquivalentTo` matcher. + +The linter can't guess what is the best solution in each case, and so it won't auto-fix this warning. + +To suppress this warning entirely, use the `--suppress-type-compare-assertion=true` command line parameter. + +To suppress a specific file or line, use the `// ginkgo-linter:ignore-type-compare-warning` comment (see [below](#suppress-warning-from-the-code)) + +### Wrong Length Assertion [STYLE] The linter finds assertion of the golang built-in `len` function, with all kind of matchers, while there are already gomega matchers for these usecases; We want to assert the item, rather than its length. There are several wrong patterns: @@ -102,10 +236,10 @@ The output of the linter,when finding issues, looks like this: ./testdata/src/a/a.go:18:5: ginkgo-linter: wrong length assertion; consider using `Expect("").Should(BeEmpty())` instead ./testdata/src/a/a.go:22:5: ginkgo-linter: wrong length assertion; consider using `Expect("").Should(BeEmpty())` instead ``` -#### use the `HaveLen(0)` matcher. +#### use the `HaveLen(0)` matcher. [STYLE] The linter will also warn about the `HaveLen(0)` matcher, and will suggest to replace it with `BeEmpty()` -### Wrong `nil` Assertion +### Wrong `nil` Assertion [STYLE] The linter finds assertion of the comparison to nil, with all kind of matchers, instead of using the existing `BeNil()` matcher; We want to assert the item, rather than a comparison result. There are several wrong patterns: @@ -127,7 +261,7 @@ Or even (double negative): `Ω(x != nil).Should(Not(BeTrue()))` => `Ω(x).Should(BeNil())` -### Wrong boolean Assertion +### Wrong boolean Assertion [STYLE] The linter finds assertion using the `Equal` method, with the values of to `true` or `false`, instead of using the existing `BeTrue()` or `BeFalse()` matcher. @@ -141,7 +275,7 @@ It also supports the embedded `Not()` matcher; e.g. `Ω(x).Should(Not(Equal(True)))` => `Ω(x).ShouldNot(BeTrue())` -### Wrong Error Assertion +### Wrong Error Assertion [STYLE] The linter finds assertion of errors compared with nil, or to be equal nil, or to be nil. The linter suggests to use `Succeed` for functions or `HaveOccurred` for error values.. There are several wrong patterns: @@ -159,7 +293,7 @@ It also supports the embedded `Not()` matcher; e.g. `Ω(err == nil).Should(Not(BeTrue()))` => `Ω(x).Should(HaveOccurred())` -### Wrong Comparison Assertion +### Wrong Comparison Assertion [STYLE] The linter finds assertion of boolean comparisons, which are already supported by existing gomega matchers. The linter assumes that when compared something to literals or constants, these values should be used for the assertion, @@ -198,85 +332,6 @@ Expect(x1 == c1).Should(BeTrue()) // ==> Expect(x1).Should(Equal(c1)) Expect(c1 == x1).Should(BeTrue()) // ==> Expect(x1).Should(Equal(c1)) ``` -### Using a function call in async assertion -This rule finds an actual bug in tests, where asserting a function call in an async function; e.g. `Eventually`. For -example: -```go -func slowInt(int val) int { - time.Sleep(time.Second) - return val -} - -... - -It("should test that slowInt returns 42, eventually", func() { - Eventually(slowInt(42)).WithPolling(time.Millisecond * 100).WithTimeout(time.Second * 2).Equal(42) -}) -``` -The problem with the above code is that it **should** poll - call the function - until it returns 42, but what actually -happens is that first the function is called, and we pass `42` to `Eventually` - not the function. This is not what we -tried to do here. - -The linter will suggest replacing this code by: -```go -It("should test that slowInt returns 42, eventually", func() { - Eventually(slowInt).WithArguments(42).WithPolling(time.Millisecond * 100).WithTimeout(time.Second * 2).Equal(42) -}) -``` - -The linter suggested replacing the function call by the function name. - -If function arguments are used, the linter will add the `WithArguments()` method to pass them. - -Please notice that `WithArguments()` is only supported from gomenga v1.22.0. - -When using an older version of gomega, change the code manually. For example: - -```go -It("should test that slowInt returns 42, eventually", func() { - Eventually(func() int { - slowint(42) - }).WithPolling(time.Millisecond * 100).WithTimeout(time.Second * 2).Equal(42) -}) -``` - -### Comparing a pointer with a value -The linter warns when comparing a pointer with a value. -These comparisons are always wrong and will always fail. - -In case of a positive assertion (`To()` or `Should()`), the test will just fail. - -But the main concern is for false positive tests, when using a negative assertion (`NotTo()`, `ToNot()`, `ShouldNot()`, -`Should(Not())` etc.); e.g. -```go -num := 5 -... -pNum := &num -... -Expect(pNum).ShouldNot(Equal(6)) -``` -This assertion will pass, but for the wrong reasons: pNum is not equal 6, not because num == 5, but because pNum is -a pointer, while `6` is an `int`. - -In the case above, the linter will suggest `Expect(pNum).ShouldNot(HaveValue(Equal(6)))` - -This is also right for additional matchers: `BeTrue()` and `BeFalse()`, `BeIdenticalTo()`, `BeEquivalentTo()` -and `BeNumerically`. - -### Missing Assertion Method -The linter warns when calling an "actual" method (e.g. `Expect()`, `Eventually()` etc.), without an assertion method (e.g -`Should()`, `NotTo()` etc.) - -For example: -```go -// no assertion for the result -Eventually(doSomething).WithTimeout(time.Seconds * 5).WithPolling(time.Milliseconds * 100) -``` - -The linter will not suggest a fix for this warning. - -This rule cannot be suppressed. - ## Suppress the linter ### Suppress warning from command line * Use the `--suppress-len-assertion=true` flag to suppress the wrong length assertion warning @@ -284,6 +339,8 @@ This rule cannot be suppressed. * Use the `--suppress-err-assertion=true` flag to suppress the wrong error assertion warning * Use the `--suppress-compare-assertion=true` flag to suppress the wrong comparison assertion warning * Use the `--suppress-async-assertion=true` flag to suppress the function call in async assertion warning +* Use the `--forbid-focus-container=true` flag to activate the focused container assertion (deactivated by default) +* Use the `--suppress-type-compare-assertion=true` to suppress the type compare assertion warning * Use the `--allow-havelen-0=true` flag to avoid warnings about `HaveLen(0)`; Note: this parameter is only supported from command line, and not from a comment. @@ -308,6 +365,21 @@ To suppress the wrong async assertion warning, add a comment with (only) `ginkgo-linter:ignore-async-assert-warning`. +To supress the focus container warning, add a comment with (only) + +`ginkgo-linter:ignore-focus-container-warning` + +To suppress the different type comparison, add a comment with (only) + +`ginkgo-linter:ignore-type-compare-warning` + +Notice that this comment will not work for an anonymous variable container like +```go +// ginkgo-linter:ignore-focus-container-warning (not working!!) +var _ = FDescribe(...) +``` +In this case, use the file comment (see bellow). + There are two options to use these comments: 1. If the comment is at the top of the file, supress the warning for the whole file; e.g.: ```go diff --git a/tools/vendor/github.com/nunnatsa/ginkgolinter/ginkgo_linter.go b/tools/vendor/github.com/nunnatsa/ginkgolinter/ginkgo_linter.go index 2ae6b77511..d1e5164fac 100644 --- a/tools/vendor/github.com/nunnatsa/ginkgolinter/ginkgo_linter.go +++ b/tools/vendor/github.com/nunnatsa/ginkgolinter/ginkgo_linter.go @@ -4,19 +4,21 @@ import ( "bytes" "flag" "fmt" - "github.com/nunnatsa/ginkgolinter/version" "go/ast" "go/constant" "go/printer" "go/token" gotypes "go/types" + "reflect" "github.com/go-toolsmith/astcopy" "golang.org/x/tools/go/analysis" + "github.com/nunnatsa/ginkgolinter/ginkgohandler" "github.com/nunnatsa/ginkgolinter/gomegahandler" "github.com/nunnatsa/ginkgolinter/reverseassertion" "github.com/nunnatsa/ginkgolinter/types" + "github.com/nunnatsa/ginkgolinter/version" ) // The ginkgolinter enforces standards of using ginkgo and gomega. @@ -35,6 +37,10 @@ const ( comparePointerToValue = linterName + ": comparing a pointer to a value will always fail. consider using `%s` instead" missingAssertionMessage = linterName + `: %q: missing assertion method. Expected "Should()", "To()", "ShouldNot()", "ToNot()" or "NotTo()"` missingAsyncAssertionMessage = linterName + `: %q: missing assertion method. Expected "Should()" or "ShouldNot()"` + focusContainerFound = linterName + ": Focus container found. This is used only for local debug and should not be part of the actual source code, consider to replace with %q" + focusSpecFound = linterName + ": Focus spec found. This is used only for local debug and should not be part of the actual source code, consider to remove it" + compareDifferentTypes = linterName + ": use %[1]s with different types: Comparing %[2]s with %[3]s; either change the expected value type if possible, or use the BeEquivalentTo() matcher, instead of %[1]s()" + compareInterfaces = linterName + ": be careful comparing interfaces. This can fail in runtime, if the actual implementing types are different" ) const ( // gomega matchers beEmpty = "BeEmpty" @@ -52,6 +58,9 @@ const ( // gomega matchers not = "Not" omega = "Ω" succeed = "Succeed" + and = "And" + or = "Or" + withTransform = "WithTransform" ) const ( // gomega actuals @@ -78,6 +87,7 @@ func NewAnalyzer() *analysis.Analyzer { SuppressNil: false, SuppressErr: false, SuppressCompare: false, + ForbidFocus: false, AllowHaveLen0: false, }, } @@ -88,14 +98,19 @@ func NewAnalyzer() *analysis.Analyzer { Run: linter.run, } + var ignored bool a.Flags.Init("ginkgolinter", flag.ExitOnError) a.Flags.Var(&linter.config.SuppressLen, "suppress-len-assertion", "Suppress warning for wrong length assertions") a.Flags.Var(&linter.config.SuppressNil, "suppress-nil-assertion", "Suppress warning for wrong nil assertions") a.Flags.Var(&linter.config.SuppressErr, "suppress-err-assertion", "Suppress warning for wrong error assertions") a.Flags.Var(&linter.config.SuppressCompare, "suppress-compare-assertion", "Suppress warning for wrong comparison assertions") a.Flags.Var(&linter.config.SuppressAsync, "suppress-async-assertion", "Suppress warning for function call in async assertion, like Eventually") + a.Flags.Var(&linter.config.SuppressTypeCompare, "suppress-type-compare-assertion", "Suppress warning for comparing values from different types, like int32 and uint32") a.Flags.Var(&linter.config.AllowHaveLen0, "allow-havelen-0", "Do not warn for HaveLen(0); default = false") + a.Flags.BoolVar(&ignored, "suppress-focus-container", true, "Suppress warning for ginkgo focus containers like FDescribe, FContext, FWhen or FIt. Deprecated and ignored: use --forbid-focus-container instead") + a.Flags.Var(&linter.config.ForbidFocus, "forbid-focus-container", "trigger a warning for ginkgo focus containers like FDescribe, FContext, FWhen or FIt; default = false.") + return a } @@ -116,6 +131,11 @@ currently, the linter searches for following: * trigger a warning for missing assertion method: [Bug] Eventually(checkSomething) +* trigger a warning when a ginkgo focus container (FDescribe, FContext, FWhen or FIt) is found. [Bug] + +* trigger a warning when using the Equal or the BeIdentical matcher with two different types, as these matchers will + fail in runtime. + * wrong length assertions. We want to assert the item rather than its length. [Style] For example: Expect(len(x)).Should(Equal(1)) @@ -152,12 +172,27 @@ func (l *ginkgoLinter) run(pass *analysis.Pass) (interface{}, error) { fileConfig.UpdateFromFile(cm) - handler := gomegahandler.GetGomegaHandler(file) - if handler == nil { // no gomega import => no use in gomega in this file; nothing to do here + gomegaHndlr := gomegahandler.GetGomegaHandler(file) + ginkgoHndlr := ginkgohandler.GetGinkgoHandler(file) + + if gomegaHndlr == nil && ginkgoHndlr == nil { // no gomega or ginkgo imports => no use in gomega in this file; nothing to do here continue } ast.Inspect(file, func(n ast.Node) bool { + if ginkgoHndlr != nil && fileConfig.ForbidFocus { + spec, ok := n.(*ast.ValueSpec) + if ok { + for _, val := range spec.Values { + if exp, ok := val.(*ast.CallExpr); ok { + if checkFocusContainer(pass, ginkgoHndlr, exp) { + return true + } + } + } + } + } + stmt, ok := n.(*ast.ExprStmt) if !ok { return true @@ -175,28 +210,62 @@ func (l *ginkgoLinter) run(pass *analysis.Pass) (interface{}, error) { return true } + if ginkgoHndlr != nil && bool(config.ForbidFocus) && checkFocusContainer(pass, ginkgoHndlr, assertionExp) { + return true + } + + // no more ginkgo checks. From here it's only gomega. So if there is no gomega handler, exit here. This is + // mostly to prevent nil pointer error. + if gomegaHndlr == nil { + return true + } + assertionFunc, ok := assertionExp.Fun.(*ast.SelectorExpr) if !ok { - checkNoAssertion(pass, assertionExp, handler) + checkNoAssertion(pass, assertionExp, gomegaHndlr) return true } if !isAssertionFunc(assertionFunc.Sel.Name) { - checkNoAssertion(pass, assertionExp, handler) + checkNoAssertion(pass, assertionExp, gomegaHndlr) return true } - actualExpr := handler.GetActualExpr(assertionFunc) + actualExpr := gomegaHndlr.GetActualExpr(assertionFunc) if actualExpr == nil { return true } - return checkExpression(pass, config, assertionExp, actualExpr, handler) + return checkExpression(pass, config, assertionExp, actualExpr, gomegaHndlr) }) } return nil, nil } +func checkFocusContainer(pass *analysis.Pass, ginkgoHndlr ginkgohandler.Handler, exp *ast.CallExpr) bool { + foundFocus := false + isFocus, id := ginkgoHndlr.GetFocusContainerName(exp) + if isFocus { + reportNewName(pass, id, id.Name[1:], focusContainerFound, id.Name) + foundFocus = true + } + + if id != nil && ginkgohandler.IsContainer(id) { + for _, arg := range exp.Args { + if ginkgoHndlr.IsFocusSpec(arg) { + reportNoFix(pass, arg.Pos(), focusSpecFound) + foundFocus = true + } else if callExp, ok := arg.(*ast.CallExpr); ok { + if checkFocusContainer(pass, ginkgoHndlr, callExp) { // handle table entries + foundFocus = true + } + } + } + } + + return foundFocus +} + func checkExpression(pass *analysis.Pass, config types.Config, assertionExp *ast.CallExpr, actualExpr *ast.CallExpr, handler gomegahandler.Handler) bool { expr := astcopy.CallExpr(assertionExp) oldExpr := goFmt(pass.Fset, expr) @@ -240,9 +309,153 @@ func checkExpression(pass *analysis.Pass, config types.Config, assertionExp *ast } else if checkPointerComparison(pass, config, assertionExp, expr, actualArg, handler, oldExpr) { return false - } else { - return handleAssertionOnly(pass, config, expr, handler, actualArg, oldExpr, true) + } else if !handleAssertionOnly(pass, config, expr, handler, actualArg, oldExpr, true) { + return false + } else if !config.SuppressTypeCompare { + return !checkEqualWrongType(pass, assertionExp, actualArg, handler, oldExpr) + } + + return true +} + +func checkEqualWrongType(pass *analysis.Pass, origExp *ast.CallExpr, actualArg ast.Expr, handler gomegahandler.Handler, old string) bool { + matcher, ok := origExp.Args[0].(*ast.CallExpr) + if !ok { + return false + } + + return checkEqualDifferentTypes(pass, matcher, actualArg, handler, old, false) +} + +func checkEqualDifferentTypes(pass *analysis.Pass, matcher *ast.CallExpr, actualArg ast.Expr, handler gomegahandler.Handler, old string, parentPointer bool) bool { + matcherFuncName, ok := handler.GetActualFuncName(matcher) + if !ok { + return false + } + + actualType := pass.TypesInfo.TypeOf(actualArg) + + switch matcherFuncName { + case equal, beIdenticalTo: // continue + case and, or: + foundIssue := false + for _, nestedExp := range matcher.Args { + nested, ok := nestedExp.(*ast.CallExpr) + if !ok { + continue + } + if checkEqualDifferentTypes(pass, nested, actualArg, handler, old, parentPointer) { + foundIssue = true + } + } + + return foundIssue + case withTransform: + nested, ok := matcher.Args[1].(*ast.CallExpr) + if !ok { + return false + } + + matcherFuncName, ok = handler.GetActualFuncName(nested) + switch matcherFuncName { + case equal, beIdenticalTo: + case not: + return checkEqualDifferentTypes(pass, nested, actualArg, handler, old, parentPointer) + default: + return false + } + + if t := getFuncType(pass, matcher.Args[0]); t != nil { + actualType = t + matcher = nested + + if !ok { + return false + } + } else { + return checkEqualDifferentTypes(pass, nested, actualArg, handler, old, parentPointer) + } + + case not: + nested, ok := matcher.Args[0].(*ast.CallExpr) + if !ok { + return false + } + + return checkEqualDifferentTypes(pass, nested, actualArg, handler, old, parentPointer) + + case haveValue: + nested, ok := matcher.Args[0].(*ast.CallExpr) + if !ok { + return false + } + + return checkEqualDifferentTypes(pass, nested, actualArg, handler, old, true) + default: + return false + } + + matcherValue := matcher.Args[0] + + switch act := actualType.(type) { + case *gotypes.Tuple: + actualType = act.At(0).Type() + case *gotypes.Pointer: + if parentPointer { + actualType = act.Elem() + } + } + + matcherType := pass.TypesInfo.TypeOf(matcherValue) + + if !reflect.DeepEqual(matcherType, actualType) { + // Equal can handle comparison of interface and a value that implements it + if isImplementing(matcherType, actualType) || isImplementing(actualType, matcherType) { + return false + } + + reportNoFix(pass, matcher.Pos(), compareDifferentTypes, matcherFuncName, actualType, matcherType) + return true } + + return false +} + +func getFuncType(pass *analysis.Pass, expr ast.Expr) gotypes.Type { + switch f := expr.(type) { + case *ast.FuncLit: + if f.Type != nil && f.Type.Results != nil && len(f.Type.Results.List) > 0 { + return pass.TypesInfo.TypeOf(f.Type.Results.List[0].Type) + } + case *ast.Ident: + a := pass.TypesInfo.TypeOf(f) + if sig, ok := a.(*gotypes.Signature); ok && sig.Results().Len() > 0 { + return sig.Results().At(0).Type() + } + } + + return nil +} + +func isImplementing(ifs, impl gotypes.Type) bool { + if gotypes.IsInterface(ifs) { + + var ( + theIfs *gotypes.Interface + ok bool + ) + + for { + theIfs, ok = ifs.(*gotypes.Interface) + if ok { + break + } + ifs = ifs.Underlying() + } + + return gotypes.Implements(impl, theIfs) + } + return false } // be careful - never change origExp!!! only modify its clone, expr!!! @@ -333,10 +546,7 @@ func checkAsyncAssertion(pass *analysis.Pass, config types.Config, expr *ast.Cal if len(actualExpr.Args) > funcIndex { if fun, funcCall := actualExpr.Args[funcIndex].(*ast.CallExpr); funcCall { t = pass.TypesInfo.TypeOf(fun) - switch t.(type) { - // allow functions that return function or channel. - case *gotypes.Signature, *gotypes.Chan, *gotypes.Pointer: - default: + if !isValidAsyncValueType(t) { actualExpr = handler.GetActualExpr(expr.Fun.(*ast.SelectorExpr)) if len(fun.Args) > 0 { @@ -371,6 +581,18 @@ func checkAsyncAssertion(pass *analysis.Pass, config types.Config, expr *ast.Cal return true } +func isValidAsyncValueType(t gotypes.Type) bool { + switch t.(type) { + // allow functions that return function or channel. + case *gotypes.Signature, *gotypes.Chan, *gotypes.Pointer: + return true + case *gotypes.Named: + return isValidAsyncValueType(t.Underlying()) + } + + return false +} + func startCheckComparison(exp *ast.CallExpr, handler gomegahandler.Handler) (*ast.CallExpr, bool) { matcher, ok := exp.Args[0].(*ast.CallExpr) if !ok { @@ -959,7 +1181,7 @@ func reportNilAssertion(pass *analysis.Pass, expr *ast.CallExpr, handler gomegah report(pass, expr, template, oldExpr) } -func report(pass *analysis.Pass, expr *ast.CallExpr, messageTemplate, oldExpr string) { +func report(pass *analysis.Pass, expr ast.Expr, messageTemplate, oldExpr string) { newExp := goFmt(pass.Fset, expr) pass.Report(analysis.Diagnostic{ Pos: expr.Pos(), @@ -979,6 +1201,25 @@ func report(pass *analysis.Pass, expr *ast.CallExpr, messageTemplate, oldExpr st }) } +func reportNewName(pass *analysis.Pass, id *ast.Ident, newName string, messageTemplate, oldExpr string) { + pass.Report(analysis.Diagnostic{ + Pos: id.Pos(), + Message: fmt.Sprintf(messageTemplate, newName), + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: fmt.Sprintf("should replace %s with %s", oldExpr, newName), + TextEdits: []analysis.TextEdit{ + { + Pos: id.Pos(), + End: id.End(), + NewText: []byte(newName), + }, + }, + }, + }, + }) +} + func reportNoFix(pass *analysis.Pass, pos token.Pos, message string, args ...any) { if len(args) > 0 { message = fmt.Sprintf(message, args...) @@ -1094,8 +1335,7 @@ func isPointer(pass *analysis.Pass, expr ast.Expr) bool { func isInterface(pass *analysis.Pass, expr ast.Expr) bool { t := pass.TypesInfo.TypeOf(expr) - _, ok := t.(*gotypes.Named) - return ok + return gotypes.IsInterface(t) } func isNumeric(pass *analysis.Pass, node ast.Expr) bool { diff --git a/tools/vendor/github.com/nunnatsa/ginkgolinter/ginkgohandler/handler.go b/tools/vendor/github.com/nunnatsa/ginkgolinter/ginkgohandler/handler.go new file mode 100644 index 0000000000..c0829c4695 --- /dev/null +++ b/tools/vendor/github.com/nunnatsa/ginkgolinter/ginkgohandler/handler.go @@ -0,0 +1,97 @@ +package ginkgohandler + +import ( + "go/ast" +) + +const ( + importPath = `"github.com/onsi/ginkgo"` + importPathV2 = `"github.com/onsi/ginkgo/v2"` + + focusSpec = "Focus" +) + +// Handler provide different handling, depend on the way ginkgo was imported, whether +// in imported with "." name, custom name or without any name. +type Handler interface { + GetFocusContainerName(*ast.CallExpr) (bool, *ast.Ident) + IsFocusSpec(ident ast.Expr) bool +} + +// GetGinkgoHandler returns a ginkgor handler according to the way ginkgo was imported in the specific file +func GetGinkgoHandler(file *ast.File) Handler { + for _, imp := range file.Imports { + if imp.Path.Value != importPath && imp.Path.Value != importPathV2 { + continue + } + + switch name := imp.Name.String(); { + case name == ".": + return dotHandler{} + case name == "": // import with no local name + return nameHandler("ginkgo") + default: + return nameHandler(name) + } + } + + return nil // no ginkgo import; this file does not use ginkgo +} + +// dotHandler is used when importing ginkgo with dot; i.e. +// import . "github.com/onsi/ginkgo" +type dotHandler struct{} + +func (h dotHandler) GetFocusContainerName(exp *ast.CallExpr) (bool, *ast.Ident) { + if fun, ok := exp.Fun.(*ast.Ident); ok { + return isFocusContainer(fun.Name), fun + } + return false, nil +} + +func (h dotHandler) IsFocusSpec(exp ast.Expr) bool { + id, ok := exp.(*ast.Ident) + return ok && id.Name == focusSpec +} + +// nameHandler is used when importing ginkgo without name; i.e. +// import "github.com/onsi/ginkgo" +// +// or with a custom name; e.g. +// import customname "github.com/onsi/ginkgo" +type nameHandler string + +func (h nameHandler) GetFocusContainerName(exp *ast.CallExpr) (bool, *ast.Ident) { + if sel, ok := exp.Fun.(*ast.SelectorExpr); ok { + if id, ok := sel.X.(*ast.Ident); ok && id.Name == string(h) { + return isFocusContainer(sel.Sel.Name), sel.Sel + } + } + return false, nil +} + +func (h nameHandler) IsFocusSpec(exp ast.Expr) bool { + if selExp, ok := exp.(*ast.SelectorExpr); ok { + if x, ok := selExp.X.(*ast.Ident); ok && x.Name == string(h) { + return selExp.Sel.Name == focusSpec + } + } + + return false +} + +func isFocusContainer(name string) bool { + switch name { + case "FDescribe", "FContext", "FWhen", "FIt", "FDescribeTable", "FEntry": + return true + } + return false +} + +func IsContainer(id *ast.Ident) bool { + switch id.Name { + case "It", "When", "Context", "Describe", "DescribeTable", "Entry": + return true + } + return isFocusContainer(id.Name) +} diff --git a/tools/vendor/github.com/nunnatsa/ginkgolinter/gomegahandler/handler.go b/tools/vendor/github.com/nunnatsa/ginkgolinter/gomegahandler/handler.go index 1e8765148c..0c34cb7c1c 100644 --- a/tools/vendor/github.com/nunnatsa/ginkgolinter/gomegahandler/handler.go +++ b/tools/vendor/github.com/nunnatsa/ginkgolinter/gomegahandler/handler.go @@ -52,11 +52,12 @@ func (h dotHandler) GetActualFuncName(expr *ast.CallExpr) (string, bool) { case *ast.SelectorExpr: if isGomegaVar(actualFunc.X, h) { return actualFunc.Sel.Name, true - } else { - if x, ok := actualFunc.X.(*ast.CallExpr); ok { - return h.GetActualFuncName(x) - } } + + if x, ok := actualFunc.X.(*ast.CallExpr); ok { + return h.GetActualFuncName(x) + } + case *ast.CallExpr: return h.GetActualFuncName(actualFunc) } diff --git a/tools/vendor/github.com/nunnatsa/ginkgolinter/types/config.go b/tools/vendor/github.com/nunnatsa/ginkgolinter/types/config.go index 43145d8471..6de22738dd 100644 --- a/tools/vendor/github.com/nunnatsa/ginkgolinter/types/config.go +++ b/tools/vendor/github.com/nunnatsa/ginkgolinter/types/config.go @@ -13,29 +13,35 @@ const ( suppressErrAssertionWarning = suppressPrefix + "ignore-err-assert-warning" suppressCompareAssertionWarning = suppressPrefix + "ignore-compare-assert-warning" suppressAsyncAsertWarning = suppressPrefix + "ignore-async-assert-warning" + suppressFocusContainerWarning = suppressPrefix + "ignore-focus-container-warning" + suppressTypeCompareWarning = suppressPrefix + "ignore-type-compare-warning" ) type Config struct { - SuppressLen Boolean - SuppressNil Boolean - SuppressErr Boolean - SuppressCompare Boolean - SuppressAsync Boolean - AllowHaveLen0 Boolean + SuppressLen Boolean + SuppressNil Boolean + SuppressErr Boolean + SuppressCompare Boolean + SuppressAsync Boolean + ForbidFocus Boolean + SuppressTypeCompare Boolean + AllowHaveLen0 Boolean } func (s *Config) AllTrue() bool { - return bool(s.SuppressLen && s.SuppressNil && s.SuppressErr && s.SuppressCompare && s.SuppressAsync) + return bool(s.SuppressLen && s.SuppressNil && s.SuppressErr && s.SuppressCompare && s.SuppressAsync && !s.ForbidFocus) } func (s *Config) Clone() Config { return Config{ - SuppressLen: s.SuppressLen, - SuppressNil: s.SuppressNil, - SuppressErr: s.SuppressErr, - SuppressCompare: s.SuppressCompare, - SuppressAsync: s.SuppressAsync, - AllowHaveLen0: s.AllowHaveLen0, + SuppressLen: s.SuppressLen, + SuppressNil: s.SuppressNil, + SuppressErr: s.SuppressErr, + SuppressCompare: s.SuppressCompare, + SuppressAsync: s.SuppressAsync, + ForbidFocus: s.ForbidFocus, + SuppressTypeCompare: s.SuppressTypeCompare, + AllowHaveLen0: s.AllowHaveLen0, } } @@ -53,11 +59,22 @@ func (s *Config) UpdateFromComment(commentGroup []*ast.CommentGroup) { comment = strings.TrimSuffix(comment, "*/") comment = strings.TrimSpace(comment) - s.SuppressLen = s.SuppressLen || (comment == suppressLengthAssertionWarning) - s.SuppressNil = s.SuppressNil || (comment == suppressNilAssertionWarning) - s.SuppressErr = s.SuppressErr || (comment == suppressErrAssertionWarning) - s.SuppressCompare = s.SuppressCompare || (comment == suppressCompareAssertionWarning) - s.SuppressAsync = s.SuppressAsync || (comment == suppressAsyncAsertWarning) + switch comment { + case suppressLengthAssertionWarning: + s.SuppressLen = true + case suppressNilAssertionWarning: + s.SuppressNil = true + case suppressErrAssertionWarning: + s.SuppressErr = true + case suppressCompareAssertionWarning: + s.SuppressCompare = true + case suppressAsyncAsertWarning: + s.SuppressAsync = true + case suppressFocusContainerWarning: + s.ForbidFocus = false + case suppressTypeCompareWarning: + s.SuppressTypeCompare = true + } } } } diff --git a/tools/vendor/github.com/polyfloyd/go-errorlint/errorlint/allowed.go b/tools/vendor/github.com/polyfloyd/go-errorlint/errorlint/allowed.go index 366b5c6b0f..df8d5f7138 100644 --- a/tools/vendor/github.com/polyfloyd/go-errorlint/errorlint/allowed.go +++ b/tools/vendor/github.com/polyfloyd/go-errorlint/errorlint/allowed.go @@ -3,6 +3,8 @@ package errorlint import ( "fmt" "go/ast" + "go/types" + "strings" ) var allowedErrors = []struct { @@ -34,23 +36,30 @@ var allowedErrors = []struct { {err: "io.EOF", fun: "(*bytes.Reader).ReadRune"}, {err: "io.EOF", fun: "(*bytes.Reader).ReadString"}, // pkg/database/sql - {err: "sql.ErrNoRows", fun: "(*database/sql.Row).Scan"}, + {err: "database/sql.ErrNoRows", fun: "(*database/sql.Row).Scan"}, + // pkg/debug/elf + {err: "io.EOF", fun: "debug/elf.Open"}, + {err: "io.EOF", fun: "debug/elf.NewFile"}, // pkg/io {err: "io.EOF", fun: "(io.Reader).Read"}, + {err: "io.EOF", fun: "(io.ReaderAt).ReadAt"}, + {err: "io.EOF", fun: "(*io.LimitedReader).Read"}, + {err: "io.EOF", fun: "(*io.SectionReader).Read"}, + {err: "io.EOF", fun: "(*io.SectionReader).ReadAt"}, {err: "io.ErrClosedPipe", fun: "(*io.PipeWriter).Write"}, {err: "io.ErrShortBuffer", fun: "io.ReadAtLeast"}, {err: "io.ErrUnexpectedEOF", fun: "io.ReadAtLeast"}, {err: "io.EOF", fun: "io.ReadFull"}, {err: "io.ErrUnexpectedEOF", fun: "io.ReadFull"}, // pkg/net/http - {err: "http.ErrServerClosed", fun: "(*net/http.Server).ListenAndServe"}, - {err: "http.ErrServerClosed", fun: "(*net/http.Server).ListenAndServeTLS"}, - {err: "http.ErrServerClosed", fun: "(*net/http.Server).Serve"}, - {err: "http.ErrServerClosed", fun: "(*net/http.Server).ServeTLS"}, - {err: "http.ErrServerClosed", fun: "http.ListenAndServe"}, - {err: "http.ErrServerClosed", fun: "http.ListenAndServeTLS"}, - {err: "http.ErrServerClosed", fun: "http.Serve"}, - {err: "http.ErrServerClosed", fun: "http.ServeTLS"}, + {err: "net/http.ErrServerClosed", fun: "(*net/http.Server).ListenAndServe"}, + {err: "net/http.ErrServerClosed", fun: "(*net/http.Server).ListenAndServeTLS"}, + {err: "net/http.ErrServerClosed", fun: "(*net/http.Server).Serve"}, + {err: "net/http.ErrServerClosed", fun: "(*net/http.Server).ServeTLS"}, + {err: "net/http.ErrServerClosed", fun: "net/http.ListenAndServe"}, + {err: "net/http.ErrServerClosed", fun: "net/http.ListenAndServeTLS"}, + {err: "net/http.ErrServerClosed", fun: "net/http.Serve"}, + {err: "net/http.ErrServerClosed", fun: "net/http.ServeTLS"}, // pkg/os {err: "io.EOF", fun: "(*os.File).Read"}, {err: "io.EOF", fun: "(*os.File).ReadAt"}, @@ -64,7 +73,21 @@ var allowedErrors = []struct { {err: "io.EOF", fun: "(*strings.Reader).ReadRune"}, } +var allowedErrorWildcards = []struct { + err string + fun string +}{ + // golang.org/x/sys/unix + {err: "golang.org/x/sys/unix.E", fun: "golang.org/x/sys/unix."}, +} + func isAllowedErrAndFunc(err, fun string) bool { + for _, allow := range allowedErrorWildcards { + if strings.HasPrefix(fun, allow.fun) && strings.HasPrefix(err, allow.err) { + return true + } + } + for _, allow := range allowedErrors { if allow.fun == fun && allow.err == err { return true @@ -73,7 +96,7 @@ func isAllowedErrAndFunc(err, fun string) bool { return false } -func isAllowedErrorComparison(info *TypesInfoExt, binExpr *ast.BinaryExpr) bool { +func isAllowedErrorComparison(pass *TypesInfoExt, binExpr *ast.BinaryExpr) bool { var errName string // `.`, e.g. `io.EOF` var callExprs []*ast.CallExpr @@ -84,11 +107,11 @@ func isAllowedErrorComparison(info *TypesInfoExt, binExpr *ast.BinaryExpr) bool case *ast.SelectorExpr: // A selector which we assume refers to a staticaly declared error // in a package. - errName = selectorToString(t) + errName = selectorToString(pass, t) case *ast.Ident: // Identifier, most likely to be the `err` variable or whatever // produces it. - callExprs = assigningCallExprs(info, t) + callExprs = assigningCallExprs(pass, t, map[types.Object]bool{}) case *ast.CallExpr: callExprs = append(callExprs, t) } @@ -108,11 +131,11 @@ func isAllowedErrorComparison(info *TypesInfoExt, binExpr *ast.BinaryExpr) bool // allowed. return false } - if sel, ok := info.Selections[functionSelector]; ok { + if sel, ok := pass.TypesInfo.Selections[functionSelector]; ok { functionNames[i] = fmt.Sprintf("(%s).%s", sel.Recv(), sel.Obj().Name()) } else { // If there is no selection, assume it is a package. - functionNames[i] = selectorToString(callExpr.Fun.(*ast.SelectorExpr)) + functionNames[i] = selectorToString(pass, callExpr.Fun.(*ast.SelectorExpr)) } } @@ -127,17 +150,23 @@ func isAllowedErrorComparison(info *TypesInfoExt, binExpr *ast.BinaryExpr) bool // assigningCallExprs finds all *ast.CallExpr nodes that are part of an // *ast.AssignStmt that assign to the subject identifier. -func assigningCallExprs(info *TypesInfoExt, subject *ast.Ident) []*ast.CallExpr { +func assigningCallExprs(pass *TypesInfoExt, subject *ast.Ident, visitedObjects map[types.Object]bool) []*ast.CallExpr { if subject.Obj == nil { return nil } - // Find other identifiers that reference this same object. Make sure to - // exclude the subject identifier as it will cause an infinite recursion - // and is being used in a read operation anyway. - sobj := info.ObjectOf(subject) + // Find other identifiers that reference this same object. + sobj := pass.TypesInfo.ObjectOf(subject) + + if visitedObjects[sobj] { + return nil + } + visitedObjects[sobj] = true + + // Make sure to exclude the subject identifier as it will cause an infinite recursion and is + // being used in a read operation anyway. identifiers := []*ast.Ident{} - for _, ident := range info.IdentifiersForObject[sobj] { + for _, ident := range pass.IdentifiersForObject[sobj] { if subject.Pos() != ident.Pos() { identifiers = append(identifiers, ident) } @@ -146,7 +175,7 @@ func assigningCallExprs(info *TypesInfoExt, subject *ast.Ident) []*ast.CallExpr // Find out whether the identifiers are part of an assignment statement. var callExprs []*ast.CallExpr for _, ident := range identifiers { - parent := info.NodeParent[ident] + parent := pass.NodeParent[ident] switch declT := parent.(type) { case *ast.AssignStmt: // The identifier is LHS of an assignment. @@ -174,7 +203,7 @@ func assigningCallExprs(info *TypesInfoExt, subject *ast.Ident) []*ast.CallExpr continue } // The subject was the result of assigning from another identifier. - callExprs = append(callExprs, assigningCallExprs(info, assignT)...) + callExprs = append(callExprs, assigningCallExprs(pass, assignT, visitedObjects)...) default: // TODO: inconclusive? } @@ -183,9 +212,7 @@ func assigningCallExprs(info *TypesInfoExt, subject *ast.Ident) []*ast.CallExpr return callExprs } -func selectorToString(selExpr *ast.SelectorExpr) string { - if ident, ok := selExpr.X.(*ast.Ident); ok { - return ident.Name + "." + selExpr.Sel.Name - } - return "" +func selectorToString(pass *TypesInfoExt, selExpr *ast.SelectorExpr) string { + o := pass.TypesInfo.Uses[selExpr.Sel] + return fmt.Sprintf("%s.%s", o.Pkg().Path(), o.Name()) } diff --git a/tools/vendor/github.com/polyfloyd/go-errorlint/errorlint/analysis.go b/tools/vendor/github.com/polyfloyd/go-errorlint/errorlint/analysis.go index c65c4ee62b..f034913ea3 100644 --- a/tools/vendor/github.com/polyfloyd/go-errorlint/errorlint/analysis.go +++ b/tools/vendor/github.com/polyfloyd/go-errorlint/errorlint/analysis.go @@ -35,13 +35,13 @@ func init() { func run(pass *analysis.Pass) (interface{}, error) { lints := []analysis.Diagnostic{} - extInfo := newTypesInfoExt(pass.TypesInfo) + extInfo := newTypesInfoExt(pass) if checkComparison { - l := LintErrorComparisons(pass.Fset, extInfo) + l := LintErrorComparisons(extInfo) lints = append(lints, l...) } if checkAsserts { - l := LintErrorTypeAssertions(pass.Fset, *pass.TypesInfo) + l := LintErrorTypeAssertions(pass.Fset, extInfo) lints = append(lints, l...) } if checkErrorf { @@ -57,7 +57,7 @@ func run(pass *analysis.Pass) (interface{}, error) { } type TypesInfoExt struct { - types.Info + *analysis.Pass // Maps AST nodes back to the node they are contained within. NodeParent map[ast.Node]ast.Node @@ -66,9 +66,9 @@ type TypesInfoExt struct { IdentifiersForObject map[types.Object][]*ast.Ident } -func newTypesInfoExt(info *types.Info) *TypesInfoExt { +func newTypesInfoExt(pass *analysis.Pass) *TypesInfoExt { nodeParent := map[ast.Node]ast.Node{} - for node := range info.Scopes { + for node := range pass.TypesInfo.Scopes { file, ok := node.(*ast.File) if !ok { continue @@ -86,15 +86,15 @@ func newTypesInfoExt(info *types.Info) *TypesInfoExt { } identifiersForObject := map[types.Object][]*ast.Ident{} - for node, obj := range info.Defs { + for node, obj := range pass.TypesInfo.Defs { identifiersForObject[obj] = append(identifiersForObject[obj], node) } - for node, obj := range info.Uses { + for node, obj := range pass.TypesInfo.Uses { identifiersForObject[obj] = append(identifiersForObject[obj], node) } return &TypesInfoExt{ - Info: *info, + Pass: pass, NodeParent: nodeParent, IdentifiersForObject: identifiersForObject, } diff --git a/tools/vendor/github.com/polyfloyd/go-errorlint/errorlint/lint.go b/tools/vendor/github.com/polyfloyd/go-errorlint/errorlint/lint.go index 920dc56e79..817cd6904c 100644 --- a/tools/vendor/github.com/polyfloyd/go-errorlint/errorlint/lint.go +++ b/tools/vendor/github.com/polyfloyd/go-errorlint/errorlint/lint.go @@ -158,10 +158,10 @@ func isFmtErrorfCallExpr(info types.Info, expr ast.Expr) (*ast.CallExpr, bool) { return nil, false } -func LintErrorComparisons(fset *token.FileSet, info *TypesInfoExt) []analysis.Diagnostic { +func LintErrorComparisons(info *TypesInfoExt) []analysis.Diagnostic { lints := []analysis.Diagnostic{} - for expr := range info.Types { + for expr := range info.TypesInfo.Types { // Find == and != operations. binExpr, ok := expr.(*ast.BinaryExpr) if !ok { @@ -175,7 +175,7 @@ func LintErrorComparisons(fset *token.FileSet, info *TypesInfoExt) []analysis.Di continue } // Find comparisons of which one side is a of type error. - if !isErrorComparison(info.Info, binExpr) { + if !isErrorComparison(info.TypesInfo, binExpr) { continue } // Some errors that are returned from some functions are exempt. @@ -193,7 +193,7 @@ func LintErrorComparisons(fset *token.FileSet, info *TypesInfoExt) []analysis.Di }) } - for scope := range info.Scopes { + for scope := range info.TypesInfo.Scopes { // Find value switch blocks. switchStmt, ok := scope.(*ast.SwitchStmt) if !ok { @@ -203,7 +203,7 @@ func LintErrorComparisons(fset *token.FileSet, info *TypesInfoExt) []analysis.Di if switchStmt.Tag == nil { continue } - tagType := info.Types[switchStmt.Tag] + tagType := info.TypesInfo.Types[switchStmt.Tag] if tagType.Type.String() != "error" { continue } @@ -233,7 +233,7 @@ func isNilComparison(binExpr *ast.BinaryExpr) bool { return false } -func isErrorComparison(info types.Info, binExpr *ast.BinaryExpr) bool { +func isErrorComparison(info *types.Info, binExpr *ast.BinaryExpr) bool { tx := info.Types[binExpr.X] ty := info.Types[binExpr.Y] return tx.Type.String() == "error" || ty.Type.String() == "error" @@ -252,11 +252,11 @@ func isNodeInErrorIsFunc(info *TypesInfoExt, node ast.Node) bool { return false } // There should be 1 argument of type error. - if ii := funcDecl.Type.Params.List; len(ii) != 1 || info.Types[ii[0].Type].Type.String() != "error" { + if ii := funcDecl.Type.Params.List; len(ii) != 1 || info.TypesInfo.Types[ii[0].Type].Type.String() != "error" { return false } // The return type should be bool. - if ii := funcDecl.Type.Results.List; len(ii) != 1 || info.Types[ii[0].Type].Type.String() != "bool" { + if ii := funcDecl.Type.Results.List; len(ii) != 1 || info.TypesInfo.Types[ii[0].Type].Type.String() != "bool" { return false } @@ -288,10 +288,10 @@ func switchComparesNonNil(switchStmt *ast.SwitchStmt) bool { return false } -func LintErrorTypeAssertions(fset *token.FileSet, info types.Info) []analysis.Diagnostic { +func LintErrorTypeAssertions(fset *token.FileSet, info *TypesInfoExt) []analysis.Diagnostic { lints := []analysis.Diagnostic{} - for expr := range info.Types { + for expr := range info.TypesInfo.Types { // Find type assertions. typeAssert, ok := expr.(*ast.TypeAssertExpr) if !ok { @@ -299,7 +299,11 @@ func LintErrorTypeAssertions(fset *token.FileSet, info types.Info) []analysis.Di } // Find type assertions that operate on values of type error. - if !isErrorTypeAssertion(info, typeAssert) { + if !isErrorTypeAssertion(*info.TypesInfo, typeAssert) { + continue + } + + if isNodeInErrorIsFunc(info, typeAssert) { continue } @@ -309,7 +313,7 @@ func LintErrorTypeAssertions(fset *token.FileSet, info types.Info) []analysis.Di }) } - for scope := range info.Scopes { + for scope := range info.TypesInfo.Scopes { // Find type switches. typeSwitch, ok := scope.(*ast.TypeSwitchStmt) if !ok { @@ -326,7 +330,11 @@ func LintErrorTypeAssertions(fset *token.FileSet, info types.Info) []analysis.Di } // Check whether the type switch is on a value of type error. - if !isErrorTypeAssertion(info, typeAssert) { + if !isErrorTypeAssertion(*info.TypesInfo, typeAssert) { + continue + } + + if isNodeInErrorIsFunc(info, typeSwitch) { continue } diff --git a/tools/vendor/github.com/quasilyte/go-ruleguard/internal/goenv/goenv.go b/tools/vendor/github.com/quasilyte/go-ruleguard/internal/goenv/goenv.go index 2f207aa07a..52d0f5204f 100644 --- a/tools/vendor/github.com/quasilyte/go-ruleguard/internal/goenv/goenv.go +++ b/tools/vendor/github.com/quasilyte/go-ruleguard/internal/goenv/goenv.go @@ -3,46 +3,26 @@ package goenv import ( "errors" "os/exec" - "runtime" - "strconv" "strings" ) func Read() (map[string]string, error) { - out, err := exec.Command("go", "env").CombinedOutput() + // pass in a fixed set of var names to avoid needing to unescape output + // pass in literals here instead of a variable list to avoid security linter warnings about command injection + out, err := exec.Command("go", "env", "GOROOT", "GOPATH", "GOARCH", "GOOS", "CGO_ENABLED").CombinedOutput() if err != nil { return nil, err } - return parseGoEnv(out, runtime.GOOS) + return parseGoEnv([]string{"GOROOT", "GOPATH", "GOARCH", "GOOS", "CGO_ENABLED"}, out) } -func parseGoEnv(data []byte, goos string) (map[string]string, error) { +func parseGoEnv(varNames []string, data []byte) (map[string]string, error) { vars := make(map[string]string) lines := strings.Split(strings.ReplaceAll(string(data), "\r\n", "\n"), "\n") - - if goos == "windows" { - // Line format is: `set $name=$value` - for _, l := range lines { - l = strings.TrimPrefix(l, "set ") - parts := strings.Split(l, "=") - if len(parts) != 2 { - continue - } - vars[parts[0]] = parts[1] - } - } else { - // Line format is: `$name="$value"` - for _, l := range lines { - parts := strings.Split(strings.TrimSpace(l), "=") - if len(parts) != 2 { - continue - } - val, err := strconv.Unquote(parts[1]) - if err != nil { - continue - } - vars[parts[0]] = val + for i, varName := range varNames { + if i < len(lines) && len(lines[i]) > 0 { + vars[varName] = lines[i] } } diff --git a/tools/vendor/github.com/quasilyte/go-ruleguard/ruleguard/engine.go b/tools/vendor/github.com/quasilyte/go-ruleguard/ruleguard/engine.go index 88feef92d6..e4cf954ffd 100644 --- a/tools/vendor/github.com/quasilyte/go-ruleguard/ruleguard/engine.go +++ b/tools/vendor/github.com/quasilyte/go-ruleguard/ruleguard/engine.go @@ -8,7 +8,6 @@ import ( "go/token" "go/types" "io" - "io/ioutil" "os" "sort" "strings" @@ -48,7 +47,7 @@ func (e *engine) LoadedGroups() []GoRuleGroup { } func (e *engine) Load(ctx *LoadContext, buildContext *build.Context, filename string, r io.Reader) error { - data, err := ioutil.ReadAll(r) + data, err := io.ReadAll(r) if err != nil { return err } diff --git a/tools/vendor/github.com/quasilyte/go-ruleguard/ruleguard/ir/gen_filter_op.go b/tools/vendor/github.com/quasilyte/go-ruleguard/ruleguard/ir/gen_filter_op.go index aecb975d8a..b1c8194926 100644 --- a/tools/vendor/github.com/quasilyte/go-ruleguard/ruleguard/ir/gen_filter_op.go +++ b/tools/vendor/github.com/quasilyte/go-ruleguard/ruleguard/ir/gen_filter_op.go @@ -7,7 +7,7 @@ import ( "bytes" "fmt" "go/format" - "io/ioutil" + "os" "strings" ) @@ -142,7 +142,7 @@ func main() { panic(err) } - if err := ioutil.WriteFile("filter_op.gen.go", pretty, 0644); err != nil { + if err := os.WriteFile("filter_op.gen.go", pretty, 0644); err != nil { panic(err) } } diff --git a/tools/vendor/github.com/quasilyte/go-ruleguard/ruleguard/ir_loader.go b/tools/vendor/github.com/quasilyte/go-ruleguard/ruleguard/ir_loader.go index d71668914c..dcb2fd2afa 100644 --- a/tools/vendor/github.com/quasilyte/go-ruleguard/ruleguard/ir_loader.go +++ b/tools/vendor/github.com/quasilyte/go-ruleguard/ruleguard/ir_loader.go @@ -8,7 +8,7 @@ import ( "go/parser" "go/token" "go/types" - "io/ioutil" + "os" "regexp" "github.com/quasilyte/gogrep" @@ -144,7 +144,7 @@ func (l *irLoader) loadBundle(bundle ir.BundleImport) error { } func (l *irLoader) loadExternFile(prefix, pkgPath, filename string) (*goRuleSet, error) { - src, err := ioutil.ReadFile(filename) + src, err := os.ReadFile(filename) if err != nil { return nil, err } diff --git a/tools/vendor/github.com/quasilyte/go-ruleguard/ruleguard/quasigo/gen_opcodes.go b/tools/vendor/github.com/quasilyte/go-ruleguard/ruleguard/quasigo/gen_opcodes.go index c8d512038c..80386df28a 100644 --- a/tools/vendor/github.com/quasilyte/go-ruleguard/ruleguard/quasigo/gen_opcodes.go +++ b/tools/vendor/github.com/quasilyte/go-ruleguard/ruleguard/quasigo/gen_opcodes.go @@ -7,8 +7,8 @@ import ( "bytes" "fmt" "go/format" - "io/ioutil" "log" + "os" "strings" "text/template" ) @@ -186,7 +186,7 @@ func writeFile(filename string, data []byte) { if err != nil { log.Panicf("gofmt: %v", err) } - if err := ioutil.WriteFile(filename, pretty, 0666); err != nil { + if err := os.WriteFile(filename, pretty, 0666); err != nil { log.Panicf("write %s: %v", filename, err) } } diff --git a/tools/vendor/github.com/quasilyte/go-ruleguard/ruleguard/runner.go b/tools/vendor/github.com/quasilyte/go-ruleguard/ruleguard/runner.go index c76b6db331..fdc95ab5e7 100644 --- a/tools/vendor/github.com/quasilyte/go-ruleguard/ruleguard/runner.go +++ b/tools/vendor/github.com/quasilyte/go-ruleguard/ruleguard/runner.go @@ -8,7 +8,7 @@ import ( "go/build" "go/printer" "go/token" - "io/ioutil" + "os" "path/filepath" "reflect" "sort" @@ -166,7 +166,7 @@ func (rr *rulesRunner) fileBytes() []byte { } // TODO(quasilyte): re-use src slice? - src, err := ioutil.ReadFile(rr.filename) + src, err := os.ReadFile(rr.filename) if err != nil || src == nil { // Assign a zero-length slice so rr.src // is never nil during the second fileBytes call. diff --git a/tools/vendor/github.com/ryanrolds/sqlclosecheck/pkg/analyzer/analyzer.go b/tools/vendor/github.com/ryanrolds/sqlclosecheck/pkg/analyzer/analyzer.go index c22817cafc..55e931a898 100644 --- a/tools/vendor/github.com/ryanrolds/sqlclosecheck/pkg/analyzer/analyzer.go +++ b/tools/vendor/github.com/ryanrolds/sqlclosecheck/pkg/analyzer/analyzer.go @@ -9,9 +9,10 @@ import ( ) const ( - rowsName = "Rows" - stmtName = "Stmt" - closeMethod = "Close" + rowsName = "Rows" + stmtName = "Stmt" + namedStmtName = "NamedStmt" + closeMethod = "Close" ) type action uint8 @@ -31,13 +32,15 @@ var ( sqlPackages = []string{ "database/sql", "github.com/jmoiron/sqlx", + "github.com/jackc/pgx/v5", + "github.com/jackc/pgx/v5/pgxpool", } ) func NewAnalyzer() *analysis.Analyzer { return &analysis.Analyzer{ Name: "sqlclosecheck", - Doc: "Checks that sql.Rows and sql.Stmt are closed.", + Doc: "Checks that sql.Rows, sql.Stmt, sqlx.NamedStmt, pgx.Query are closed.", Run: run, Requires: []*analysis.Analyzer{ buildssa.Analyzer, @@ -63,20 +66,18 @@ func run(pass *analysis.Pass) (interface{}, error) { for _, f := range funcs { for _, b := range f.Blocks { for i := range b.Instrs { - // Check if instruction is call that returns a target type + // Check if instruction is call that returns a target pointer type targetValues := getTargetTypesValues(b, i, targetTypes) if len(targetValues) == 0 { continue } - // log.Printf("%s", f.Name()) - // For each found target check if they are closed and deferred for _, targetValue := range targetValues { refs := (*targetValue.value).Referrers() isClosed := checkClosed(refs, targetTypes) if !isClosed { - pass.Reportf((targetValue.instr).Pos(), "Rows/Stmt was not closed") + pass.Reportf((targetValue.instr).Pos(), "Rows/Stmt/NamedStmt was not closed") } checkDeferred(pass, refs, targetTypes, false) @@ -88,17 +89,22 @@ func run(pass *analysis.Pass) (interface{}, error) { return nil, nil } -func getTargetTypes(pssa *buildssa.SSA, targetPackages []string) []*types.Pointer { - targets := []*types.Pointer{} +func getTargetTypes(pssa *buildssa.SSA, targetPackages []string) []any { + targets := []any{} for _, sqlPkg := range targetPackages { pkg := pssa.Pkg.Prog.ImportedPackage(sqlPkg) if pkg == nil { // the SQL package being checked isn't imported - return targets + continue + } + + rowsPtrType := getTypePointerFromName(pkg, rowsName) + if rowsPtrType != nil { + targets = append(targets, rowsPtrType) } - rowsType := getTypePointerFromName(pkg, rowsName) + rowsType := getTypeFromName(pkg, rowsName) if rowsType != nil { targets = append(targets, rowsType) } @@ -107,6 +113,11 @@ func getTargetTypes(pssa *buildssa.SSA, targetPackages []string) []*types.Pointe if stmtType != nil { targets = append(targets, stmtType) } + + namedStmtType := getTypePointerFromName(pkg, namedStmtName) + if namedStmtType != nil { + targets = append(targets, namedStmtType) + } } return targets @@ -115,7 +126,7 @@ func getTargetTypes(pssa *buildssa.SSA, targetPackages []string) []*types.Pointe func getTypePointerFromName(pkg *ssa.Package, name string) *types.Pointer { pkgType := pkg.Type(name) if pkgType == nil { - // this package does not use Rows/Stmt + // this package does not use Rows/Stmt/NamedStmt return nil } @@ -128,12 +139,28 @@ func getTypePointerFromName(pkg *ssa.Package, name string) *types.Pointer { return types.NewPointer(named) } +func getTypeFromName(pkg *ssa.Package, name string) *types.Named { + pkgType := pkg.Type(name) + if pkgType == nil { + // this package does not use Rows/Stmt + return nil + } + + obj := pkgType.Object() + named, ok := obj.Type().(*types.Named) + if !ok { + return nil + } + + return named +} + type targetValue struct { value *ssa.Value instr ssa.Instruction } -func getTargetTypesValues(b *ssa.BasicBlock, i int, targetTypes []*types.Pointer) []targetValue { +func getTargetTypesValues(b *ssa.BasicBlock, i int, targetTypes []any) []targetValue { targetValues := []targetValue{} instr := b.Instrs[i] @@ -149,21 +176,32 @@ func getTargetTypesValues(b *ssa.BasicBlock, i int, targetTypes []*types.Pointer varType := v.Type() for _, targetType := range targetTypes { - if !types.Identical(varType, targetType) { + var tt types.Type + + switch t := targetType.(type) { + case *types.Pointer: + tt = t + case *types.Named: + tt = t + default: + continue + } + + if !types.Identical(varType, tt) { continue } for _, cRef := range *call.Referrers() { switch instr := cRef.(type) { case *ssa.Call: - if len(instr.Call.Args) >= 1 && types.Identical(instr.Call.Args[0].Type(), targetType) { + if len(instr.Call.Args) >= 1 && types.Identical(instr.Call.Args[0].Type(), tt) { targetValues = append(targetValues, targetValue{ value: &instr.Call.Args[0], instr: call, }) } case ssa.Value: - if types.Identical(instr.Type(), targetType) { + if types.Identical(instr.Type(), tt) { targetValues = append(targetValues, targetValue{ value: &instr, instr: call, @@ -177,43 +215,42 @@ func getTargetTypesValues(b *ssa.BasicBlock, i int, targetTypes []*types.Pointer return targetValues } -func checkClosed(refs *[]ssa.Instruction, targetTypes []*types.Pointer) bool { +func checkClosed(refs *[]ssa.Instruction, targetTypes []any) bool { numInstrs := len(*refs) for idx, ref := range *refs { - // log.Printf("%T - %s", ref, ref) - action := getAction(ref, targetTypes) switch action { - case actionClosed: + case actionClosed, actionReturned, actionHandled: return true case actionPassed: // Passed and not used after if numInstrs == idx+1 { return true } - case actionReturned: - return true - case actionHandled: - return true - default: - // log.Printf(action) } } return false } -func getAction(instr ssa.Instruction, targetTypes []*types.Pointer) action { +func getAction(instr ssa.Instruction, targetTypes []any) action { switch instr := instr.(type) { case *ssa.Defer: - if instr.Call.Value == nil { - return actionUnvaluedDefer + if instr.Call.Value != nil { + name := instr.Call.Value.Name() + if name == closeMethod { + return actionClosed + } } - name := instr.Call.Value.Name() - if name == closeMethod { - return actionClosed + if instr.Call.Method != nil { + name := instr.Call.Method.Name() + if name == closeMethod { + return actionClosed + } } + + return actionUnvaluedDefer case *ssa.Call: if instr.Call.Value == nil { return actionUnvaluedCall @@ -265,7 +302,18 @@ func getAction(instr ssa.Instruction, targetTypes []*types.Pointer) action { case *ssa.UnOp: instrType := instr.Type() for _, targetType := range targetTypes { - if types.Identical(instrType, targetType) { + var tt types.Type + + switch t := targetType.(type) { + case *types.Pointer: + tt = t + case *types.Named: + tt = t + default: + continue + } + + if types.Identical(instrType, tt) { if checkClosed(instr.Referrers(), targetTypes) { return actionHandled } @@ -277,20 +325,22 @@ func getAction(instr ssa.Instruction, targetTypes []*types.Pointer) action { } case *ssa.Return: return actionReturned - default: - // log.Printf("%s", instr) } return actionUnhandled } -func checkDeferred(pass *analysis.Pass, instrs *[]ssa.Instruction, targetTypes []*types.Pointer, inDefer bool) { +func checkDeferred(pass *analysis.Pass, instrs *[]ssa.Instruction, targetTypes []any, inDefer bool) { for _, instr := range *instrs { switch instr := instr.(type) { case *ssa.Defer: if instr.Call.Value != nil && instr.Call.Value.Name() == closeMethod { return } + + if instr.Call.Method != nil && instr.Call.Method.Name() == closeMethod { + return + } case *ssa.Call: if instr.Call.Value != nil && instr.Call.Value.Name() == closeMethod { if !inDefer { @@ -316,7 +366,18 @@ func checkDeferred(pass *analysis.Pass, instrs *[]ssa.Instruction, targetTypes [ case *ssa.UnOp: instrType := instr.Type() for _, targetType := range targetTypes { - if types.Identical(instrType, targetType) { + var tt types.Type + + switch t := targetType.(type) { + case *types.Pointer: + tt = t + case *types.Named: + tt = t + default: + continue + } + + if types.Identical(instrType, tt) { checkDeferred(pass, instr.Referrers(), targetTypes, inDefer) } } @@ -326,10 +387,17 @@ func checkDeferred(pass *analysis.Pass, instrs *[]ssa.Instruction, targetTypes [ } } -func isTargetType(t types.Type, targetTypes []*types.Pointer) bool { +func isTargetType(t types.Type, targetTypes []any) bool { for _, targetType := range targetTypes { - if types.Identical(t, targetType) { - return true + switch tt := targetType.(type) { + case *types.Pointer: + if types.Identical(t, tt) { + return true + } + case *types.Named: + if types.Identical(t, tt) { + return true + } } } diff --git a/tools/vendor/github.com/sashamelentyev/usestdlibvars/pkg/analyzer/internal/mapping/mapping.go b/tools/vendor/github.com/sashamelentyev/usestdlibvars/pkg/analyzer/internal/mapping/mapping.go index b081edea32..5bad23d288 100644 --- a/tools/vendor/github.com/sashamelentyev/usestdlibvars/pkg/analyzer/internal/mapping/mapping.go +++ b/tools/vendor/github.com/sashamelentyev/usestdlibvars/pkg/analyzer/internal/mapping/mapping.go @@ -161,6 +161,9 @@ var TimeLayout = map[string]string{ time.StampMilli: "time.StampMilli", time.StampMicro: "time.StampMicro", time.StampNano: "time.StampNano", + time.DateTime: "time.DateTime", + time.DateOnly: "time.DateOnly", + time.TimeOnly: "time.TimeOnly", } var SQLIsolationLevel = map[string]string{ diff --git a/tools/vendor/github.com/securego/gosec/v2/.golangci.yml b/tools/vendor/github.com/securego/gosec/v2/.golangci.yml index b12140a25f..d591dc2493 100644 --- a/tools/vendor/github.com/securego/gosec/v2/.golangci.yml +++ b/tools/vendor/github.com/securego/gosec/v2/.golangci.yml @@ -2,7 +2,6 @@ linters: enable: - asciicheck - bodyclose - - depguard - dogsled - durationcheck - errcheck @@ -10,6 +9,7 @@ linters: - exportloopref - gci - ginkgolinter + - gochecknoinits - gofmt - gofumpt - goimports @@ -36,6 +36,10 @@ linters-settings: - standard - default - prefix(github.com/securego) + revive: + rules: + - name: dot-imports + disabled: true run: timeout: 5m diff --git a/tools/vendor/github.com/securego/gosec/v2/Makefile b/tools/vendor/github.com/securego/gosec/v2/Makefile index 09303d11a0..dcfb4b2ed9 100644 --- a/tools/vendor/github.com/securego/gosec/v2/Makefile +++ b/tools/vendor/github.com/securego/gosec/v2/Makefile @@ -11,7 +11,6 @@ endif BUILDFLAGS := "-w -s -X 'main.Version=$(GIT_TAG)' -X 'main.GitTag=$(GIT_TAG)' -X 'main.BuildDate=$(BUILD_DATE)'" CGO_ENABLED = 0 GO := GO111MODULE=on go -GO_NOMOD :=GO111MODULE=off go GOPATH ?= $(shell $(GO) env GOPATH) GOBIN ?= $(GOPATH)/bin GOSEC ?= $(GOBIN)/gosec @@ -25,15 +24,15 @@ default: install-test-deps: go install github.com/onsi/ginkgo/v2/ginkgo@latest - $(GO_NOMOD) get -u golang.org/x/crypto/ssh - $(GO_NOMOD) get -u github.com/lib/pq + go install golang.org/x/crypto/...@latest + go install github.com/lib/pq/...@latest install-govulncheck: @if [ $(GO_MINOR_VERSION) -gt $(GOVULN_MIN_VERSION) ]; then \ go install golang.org/x/vuln/cmd/govulncheck@latest; \ fi -test: install-test-deps build fmt vet sec govulncheck +test: install-test-deps build-race fmt vet sec govulncheck $(GINKGO) -v --fail-fast fmt: @@ -65,6 +64,9 @@ test-coverage: install-test-deps build: go build -o $(BIN) ./cmd/gosec/ +build-race: + go build -race -o $(BIN) ./cmd/gosec/ + clean: rm -rf build vendor dist coverage.txt rm -f release image $(BIN) @@ -89,5 +91,5 @@ image-push: image tlsconfig: go generate ./... - + .PHONY: test build clean release image image-push tlsconfig diff --git a/tools/vendor/github.com/securego/gosec/v2/README.md b/tools/vendor/github.com/securego/gosec/v2/README.md index 71e032d808..d9a33f12a6 100644 --- a/tools/vendor/github.com/securego/gosec/v2/README.md +++ b/tools/vendor/github.com/securego/gosec/v2/README.md @@ -1,7 +1,7 @@ # gosec - Golang Security Checker -Inspects source code for security problems by scanning the Go AST. +Inspects source code for security problems by scanning the Go AST and SSA code representation. @@ -157,6 +157,7 @@ directory you can supply `./...` as the input argument. - G304: File path provided as taint input - G305: File traversal when extracting zip/tar archive - G306: Poor file permissions used when writing to a new file +- G307: Poor file permissions used when creating a file with os.Create - G401: Detect the usage of DES, RC4, MD5 or SHA1 - G402: Look for bad TLS connection settings - G403: Ensure minimum RSA key length of 2048 bits @@ -167,6 +168,7 @@ directory you can supply `./...` as the input argument. - G504: Import blocklist: net/http/cgi - G505: Import blocklist: crypto/sha1 - G601: Implicit memory aliasing of items from a range statement +- G602: Slice access out of bounds ### Retired rules @@ -272,31 +274,33 @@ gosec -exclude-generated ./... ### Annotating code -As with all automated detection tools, there will be cases of false positives. In cases where gosec reports a failure that has been manually verified as being safe, +As with all automated detection tools, there will be cases of false positives. +In cases where gosec reports a failure that has been manually verified as being safe, it is possible to annotate the code with a comment that starts with `#nosec`. + The `#nosec` comment should have the format `#nosec [RuleList] [-- Justification]`. -The annotation causes gosec to stop processing any further nodes within the -AST so can apply to a whole block or more granularly to a single expression. +The `#nosec` comment needs to be placed on the line where the warning is reported. ```go - -import "md5" //#nosec - - -func main(){ - - /* #nosec */ - if x > y { - h := md5.New() // this will also be ignored - } - +func main() { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, // #nosec G402 + }, + } + + client := &http.Client{Transport: tr} + _, err := client.Get("https://golang.org/") + if err != nil { + fmt.Println(err) + } } - ``` -When a specific false positive has been identified and verified as safe, you may wish to suppress only that single rule (or a specific set of rules) -within a section of code, while continuing to scan for other problems. To do this, you can list the rule(s) to be suppressed within +When a specific false positive has been identified and verified as safe, you may +wish to suppress only that single rule (or a specific set of rules) within a section of code, +while continuing to scan for other problems. To do this, you can list the rule(s) to be suppressed within the `#nosec` annotation, e.g: `/* #nosec G401 */` or `//#nosec G201 G202 G203` You could put the description or justification text for the annotation. The diff --git a/tools/vendor/github.com/securego/gosec/v2/USERS.md b/tools/vendor/github.com/securego/gosec/v2/USERS.md index ffc0560814..9b6e4eeee4 100644 --- a/tools/vendor/github.com/securego/gosec/v2/USERS.md +++ b/tools/vendor/github.com/securego/gosec/v2/USERS.md @@ -15,6 +15,7 @@ This is a list of gosec's users. Please send a pull request with your organisati 9. [PingCAP/tidb](https://github.com/pingcap/tidb) 10. [Checkmarx](https://www.checkmarx.com/) 11. [SeatGeek](https://www.seatgeek.com/) +12. [reMarkable](https://remarkable.com) ## Projects diff --git a/tools/vendor/github.com/securego/gosec/v2/action.yml b/tools/vendor/github.com/securego/gosec/v2/action.yml index 0320f0c21a..aba47b60ce 100644 --- a/tools/vendor/github.com/securego/gosec/v2/action.yml +++ b/tools/vendor/github.com/securego/gosec/v2/action.yml @@ -10,7 +10,7 @@ inputs: runs: using: 'docker' - image: 'docker://securego/gosec:2.15.0' + image: 'docker://securego/gosec:2.18.1' args: - ${{ inputs.args }} diff --git a/tools/vendor/github.com/securego/gosec/v2/analyzer.go b/tools/vendor/github.com/securego/gosec/v2/analyzer.go index 830d338e4f..1fd1f5649a 100644 --- a/tools/vendor/github.com/securego/gosec/v2/analyzer.go +++ b/tools/vendor/github.com/securego/gosec/v2/analyzer.go @@ -57,9 +57,83 @@ const aliasOfAllRules = "*" var generatedCodePattern = regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`) +type ignore struct { + start int + end int + suppressions map[string][]issue.SuppressionInfo +} + +type ignores map[string][]ignore + +func newIgnores() ignores { + return make(map[string][]ignore) +} + +func (i ignores) parseLine(line string) (int, int) { + parts := strings.Split(line, "-") + start, err := strconv.Atoi(parts[0]) + if err != nil { + start = 0 + } + end := start + if len(parts) > 1 { + if e, err := strconv.Atoi(parts[1]); err == nil { + end = e + } + } + return start, end +} + +func (i ignores) add(file string, line string, suppressions map[string]issue.SuppressionInfo) { + is := []ignore{} + if _, ok := i[file]; ok { + is = i[file] + } + found := false + start, end := i.parseLine(line) + for _, ig := range is { + if ig.start <= start && ig.end >= end { + found = true + for r, s := range suppressions { + ss, ok := ig.suppressions[r] + if !ok { + ss = []issue.SuppressionInfo{} + } + ss = append(ss, s) + ig.suppressions[r] = ss + } + break + } + } + if !found { + ig := ignore{ + start: start, + end: end, + suppressions: map[string][]issue.SuppressionInfo{}, + } + for r, s := range suppressions { + ig.suppressions[r] = []issue.SuppressionInfo{s} + } + is = append(is, ig) + } + i[file] = is +} + +func (i ignores) get(file string, line string) map[string][]issue.SuppressionInfo { + start, end := i.parseLine(line) + if is, ok := i[file]; ok { + for _, i := range is { + if i.start <= start && i.end >= end { + return i.suppressions + } + } + } + return map[string][]issue.SuppressionInfo{} +} + // The Context is populated with data parsed from the source code as it is scanned. // It is passed through to all rule functions as they are called. Rules may use -// this data in conjunction withe the encountered AST node. +// this data in conjunction with the encountered AST node. type Context struct { FileSet *token.FileSet Comments ast.CommentMap @@ -69,7 +143,7 @@ type Context struct { Root *ast.File Imports *ImportTracker Config Config - Ignores []map[string][]issue.SuppressionInfo + Ignores ignores PassedValues map[string]interface{} } @@ -110,6 +184,7 @@ type Analyzer struct { trackSuppressions bool concurrency int analyzerList []*analysis.Analyzer + mu sync.Mutex } // NewAnalyzer builds a new analyzer. @@ -231,9 +306,7 @@ func (gosec *Analyzer) Process(buildTags []string, packagePaths ...string) error return fmt.Errorf("parsing errors in pkg %q: %w", pkg.Name, err) } gosec.CheckRules(pkg) - if on, err := gosec.config.IsGlobalEnabled(SSA); err == nil && on { - gosec.CheckAnalyzers(pkg) - } + gosec.CheckAnalyzers(pkg) } } } @@ -252,7 +325,9 @@ func (gosec *Analyzer) load(pkgPath string, conf *packages.Config) ([]*packages. // step 1/3 create build context. buildD := build.Default // step 2/3: add build tags to get env dependent files into basePackage. + gosec.mu.Lock() buildD.BuildTags = conf.BuildFlags + gosec.mu.Unlock() basePackage, err := buildD.ImportDir(pkgPath, build.ImportComment) if err != nil { return []*packages.Package{}, fmt.Errorf("importing dir %q: %w", pkgPath, err) @@ -276,7 +351,9 @@ func (gosec *Analyzer) load(pkgPath string, conf *packages.Config) ([]*packages. } // step 3/3 remove build tags from conf to proceed build correctly. + gosec.mu.Lock() conf.BuildFlags = nil + defer gosec.mu.Unlock() pkgs, err := packages.Load(conf, packageFiles...) if err != nil { return []*packages.Package{}, fmt.Errorf("loading files from package %q: %w", pkgPath, err) @@ -284,7 +361,7 @@ func (gosec *Analyzer) load(pkgPath string, conf *packages.Config) ([]*packages. return pkgs, nil } -// CheckRules runs analysis on the given package +// CheckRules runs analysis on the given package. func (gosec *Analyzer) CheckRules(pkg *packages.Package) { gosec.logger.Println("Checking package:", pkg.Name) for _, file := range pkg.Syntax { @@ -314,37 +391,22 @@ func (gosec *Analyzer) CheckRules(pkg *packages.Package) { gosec.context.PkgFiles = pkg.Syntax gosec.context.Imports = NewImportTracker() gosec.context.PassedValues = make(map[string]interface{}) + gosec.context.Ignores = newIgnores() + gosec.updateIgnores() ast.Walk(gosec, file) gosec.stats.NumFiles++ gosec.stats.NumLines += pkg.Fset.File(file.Pos()).LineCount() } } -// CheckAnalyzers runs analyzers on a given package +// CheckAnalyzers runs analyzers on a given package. func (gosec *Analyzer) CheckAnalyzers(pkg *packages.Package) { - ssaPass := &analysis.Pass{ - Analyzer: buildssa.Analyzer, - Fset: pkg.Fset, - Files: pkg.Syntax, - OtherFiles: pkg.OtherFiles, - IgnoredFiles: pkg.IgnoredFiles, - Pkg: pkg.Types, - TypesInfo: pkg.TypesInfo, - TypesSizes: pkg.TypesSizes, - ResultOf: nil, - Report: nil, - ImportObjectFact: nil, - ExportObjectFact: nil, - ImportPackageFact: nil, - ExportPackageFact: nil, - AllObjectFacts: nil, - AllPackageFacts: nil, - } - ssaResult, err := ssaPass.Analyzer.Run(ssaPass) - if err != nil { - gosec.logger.Printf("Error running SSA analyser on package %q: %s", pkg.Name, err) + ssaResult, err := gosec.buildSSA(pkg) + if err != nil || ssaResult == nil { + gosec.logger.Printf("Error building the SSA representation of the package %q: %s", pkg.Name, err) return } + resultMap := map[*analysis.Analyzer]interface{}{ buildssa.Analyzer: &analyzers.SSAAnalyzerResult{ Config: gosec.Config(), @@ -377,13 +439,44 @@ func (gosec *Analyzer) CheckAnalyzers(pkg *packages.Package) { continue } if result != nil { - if aissue, ok := result.(*issue.Issue); ok { - gosec.updateIssues(aissue, false, []issue.SuppressionInfo{}) + if passIssues, ok := result.([]*issue.Issue); ok { + for _, iss := range passIssues { + gosec.updateIssues(iss) + } } } } } +// buildSSA runs the SSA pass which builds the SSA representation of the package. It handles gracefully any panic. +func (gosec *Analyzer) buildSSA(pkg *packages.Package) (interface{}, error) { + defer func() { + if r := recover(); r != nil { + gosec.logger.Printf("Panic when running SSA analyser on package: %s", pkg.Name) + } + }() + ssaPass := &analysis.Pass{ + Analyzer: buildssa.Analyzer, + Fset: pkg.Fset, + Files: pkg.Syntax, + OtherFiles: pkg.OtherFiles, + IgnoredFiles: pkg.IgnoredFiles, + Pkg: pkg.Types, + TypesInfo: pkg.TypesInfo, + TypesSizes: pkg.TypesSizes, + ResultOf: nil, + Report: nil, + ImportObjectFact: nil, + ExportObjectFact: nil, + ImportPackageFact: nil, + ExportPackageFact: nil, + AllObjectFacts: nil, + AllPackageFacts: nil, + } + + return ssaPass.Analyzer.Run(ssaPass) +} + func isGeneratedFile(file *ast.File) bool { for _, comment := range file.Comments { for _, row := range comment.List { @@ -449,10 +542,17 @@ func (gosec *Analyzer) ignore(n ast.Node) map[string]issue.SuppressionInfo { if groups, ok := gosec.context.Comments[n]; ok && !gosec.ignoreNosec { // Checks if an alternative for #nosec is set and, if not, uses the default. - noSecDefaultTag := "#nosec" + noSecDefaultTag, err := gosec.config.GetGlobal(Nosec) + if err != nil { + noSecDefaultTag = NoSecTag(string(Nosec)) + } else { + noSecDefaultTag = NoSecTag(noSecDefaultTag) + } noSecAlternativeTag, err := gosec.config.GetGlobal(NoSecAlternative) if err != nil { noSecAlternativeTag = noSecDefaultTag + } else { + noSecAlternativeTag = NoSecTag(noSecAlternativeTag) } for _, group := range groups { @@ -507,11 +607,6 @@ func (gosec *Analyzer) ignore(n ast.Node) map[string]issue.SuppressionInfo { // Visit runs the gosec visitor logic over an AST created by parsing go code. // Rule methods added with AddRule will be invoked as necessary. func (gosec *Analyzer) Visit(n ast.Node) ast.Visitor { - ignores, ok := gosec.updateIgnoredRules(n) - if !ok { - return gosec - } - // Using ast.File instead of ast.ImportSpec, so that we can track all imports at once. switch i := n.(type) { case *ast.File: @@ -519,56 +614,48 @@ func (gosec *Analyzer) Visit(n ast.Node) ast.Visitor { } for _, rule := range gosec.ruleset.RegisteredFor(n) { - suppressions, ignored := gosec.updateSuppressions(rule.ID(), ignores) issue, err := rule.Match(n, gosec.context) if err != nil { file, line := GetLocation(n, gosec.context) file = path.Base(file) gosec.logger.Printf("Rule error: %v => %s (%s:%d)\n", reflect.TypeOf(rule), err, file, line) } - gosec.updateIssues(issue, ignored, suppressions) + gosec.updateIssues(issue) } return gosec } -func (gosec *Analyzer) updateIgnoredRules(n ast.Node) (map[string][]issue.SuppressionInfo, bool) { - if n == nil { - if len(gosec.context.Ignores) > 0 { - gosec.context.Ignores = gosec.context.Ignores[1:] - } - return nil, false +func (gosec *Analyzer) updateIgnores() { + for n := range gosec.context.Comments { + gosec.updateIgnoredRulesForNode(n) } - // Get any new rule exclusions. - ignoredRules := gosec.ignore(n) +} - // Now create the union of exclusions. - ignores := map[string][]issue.SuppressionInfo{} - if len(gosec.context.Ignores) > 0 { - for k, v := range gosec.context.Ignores[0] { - ignores[k] = v +func (gosec *Analyzer) updateIgnoredRulesForNode(n ast.Node) { + ignoredRules := gosec.ignore(n) + if len(ignoredRules) > 0 { + if gosec.context.Ignores == nil { + gosec.context.Ignores = newIgnores() } + line := issue.GetLine(gosec.context.FileSet.File(n.Pos()), n) + gosec.context.Ignores.add( + gosec.context.FileSet.File(n.Pos()).Name(), + line, + ignoredRules, + ) } - - for ruleID, suppression := range ignoredRules { - ignores[ruleID] = append(ignores[ruleID], suppression) - } - - // Push the new set onto the stack. - gosec.context.Ignores = append([]map[string][]issue.SuppressionInfo{ignores}, gosec.context.Ignores...) - - return ignores, true } -func (gosec *Analyzer) updateSuppressions(id string, ignores map[string][]issue.SuppressionInfo) ([]issue.SuppressionInfo, bool) { - // Check if all rules are ignored. - generalSuppressions, generalIgnored := ignores[aliasOfAllRules] - // Check if the specific rule is ignored - ruleSuppressions, ruleIgnored := ignores[id] +func (gosec *Analyzer) getSuppressionsAtLineInFile(file string, line string, id string) ([]issue.SuppressionInfo, bool) { + ignoredRules := gosec.context.Ignores.get(file, line) + // Check if the rule was specifically suppressed at this location. + generalSuppressions, generalIgnored := ignoredRules[aliasOfAllRules] + ruleSuppressions, ruleIgnored := ignoredRules[id] ignored := generalIgnored || ruleIgnored suppressions := append(generalSuppressions, ruleSuppressions...) - // Track external suppressions. + // Track external suppressions of this rule. if gosec.ruleset.IsRuleSuppressed(id) { ignored = true suppressions = append(suppressions, issue.SuppressionInfo{ @@ -579,8 +666,9 @@ func (gosec *Analyzer) updateSuppressions(id string, ignores map[string][]issue. return suppressions, ignored } -func (gosec *Analyzer) updateIssues(issue *issue.Issue, ignored bool, suppressions []issue.SuppressionInfo) { +func (gosec *Analyzer) updateIssues(issue *issue.Issue) { if issue != nil { + suppressions, ignored := gosec.getSuppressionsAtLineInFile(issue.File, issue.Line, issue.RuleID) if gosec.showIgnored { issue.NoSec = ignored } diff --git a/tools/vendor/github.com/securego/gosec/v2/analyzers/slice_bounds.go b/tools/vendor/github.com/securego/gosec/v2/analyzers/slice_bounds.go new file mode 100644 index 0000000000..08a55eb429 --- /dev/null +++ b/tools/vendor/github.com/securego/gosec/v2/analyzers/slice_bounds.go @@ -0,0 +1,386 @@ +// (c) Copyright gosec's authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package analyzers + +import ( + "errors" + "fmt" + "go/token" + "regexp" + "strconv" + "strings" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/buildssa" + "golang.org/x/tools/go/ssa" + + "github.com/securego/gosec/v2/issue" +) + +type bound int + +const ( + lowerUnbounded bound = iota + upperUnbounded + unbounded + upperBounded +) + +const maxDepth = 20 + +func newSliceBoundsAnalyzer(id string, description string) *analysis.Analyzer { + return &analysis.Analyzer{ + Name: id, + Doc: description, + Run: runSliceBounds, + Requires: []*analysis.Analyzer{buildssa.Analyzer}, + } +} + +func runSliceBounds(pass *analysis.Pass) (interface{}, error) { + ssaResult, err := getSSAResult(pass) + if err != nil { + return nil, err + } + + issues := map[ssa.Instruction]*issue.Issue{} + ifs := map[ssa.If]*ssa.BinOp{} + for _, mcall := range ssaResult.SSA.SrcFuncs { + for _, block := range mcall.DomPreorder() { + for _, instr := range block.Instrs { + switch instr := instr.(type) { + case *ssa.Alloc: + sliceCap, err := extractSliceCapFromAlloc(instr.String()) + if err != nil { + break + } + allocRefs := instr.Referrers() + if allocRefs == nil { + break + } + for _, instr := range *allocRefs { + if slice, ok := instr.(*ssa.Slice); ok { + if _, ok := slice.X.(*ssa.Alloc); ok { + if slice.Parent() != nil { + l, h := extractSliceBounds(slice) + newCap := computeSliceNewCap(l, h, sliceCap) + violations := []ssa.Instruction{} + trackSliceBounds(0, newCap, slice, &violations, ifs) + for _, s := range violations { + switch s := s.(type) { + case *ssa.Slice: + issue := newIssue( + pass.Analyzer.Name, + "slice bounds out of range", + pass.Fset, + s.Pos(), + issue.Low, + issue.High) + issues[s] = issue + case *ssa.IndexAddr: + issue := newIssue( + pass.Analyzer.Name, + "slice index out of range", + pass.Fset, + s.Pos(), + issue.Low, + issue.High) + issues[s] = issue + } + } + } + } + } + } + } + } + } + } + + for ifref, binop := range ifs { + bound, value, err := extractBinOpBound(binop) + if err != nil { + continue + } + for i, block := range ifref.Block().Succs { + if i == 1 { + bound = invBound(bound) + } + for _, instr := range block.Instrs { + if _, ok := issues[instr]; ok { + switch bound { + case lowerUnbounded: + break + case upperUnbounded, unbounded: + delete(issues, instr) + case upperBounded: + switch tinstr := instr.(type) { + case *ssa.Slice: + lower, upper := extractSliceBounds(tinstr) + if isSliceInsideBounds(0, value, lower, upper) { + delete(issues, instr) + } + case *ssa.IndexAddr: + indexValue, err := extractIntValue(tinstr.Index.String()) + if err != nil { + break + } + if isSliceIndexInsideBounds(0, value, indexValue) { + delete(issues, instr) + } + } + } + } + } + } + } + + foundIssues := []*issue.Issue{} + for _, issue := range issues { + foundIssues = append(foundIssues, issue) + } + if len(foundIssues) > 0 { + return foundIssues, nil + } + return nil, nil +} + +func trackSliceBounds(depth int, sliceCap int, slice ssa.Node, violations *[]ssa.Instruction, ifs map[ssa.If]*ssa.BinOp) { + if depth == maxDepth { + return + } + depth++ + if violations == nil { + violations = &[]ssa.Instruction{} + } + referrers := slice.Referrers() + if referrers != nil { + for _, refinstr := range *referrers { + switch refinstr := refinstr.(type) { + case *ssa.Slice: + checkAllSlicesBounds(depth, sliceCap, refinstr, violations, ifs) + switch refinstr.X.(type) { + case *ssa.Alloc, *ssa.Parameter: + l, h := extractSliceBounds(refinstr) + newCap := computeSliceNewCap(l, h, sliceCap) + trackSliceBounds(depth, newCap, refinstr, violations, ifs) + } + case *ssa.IndexAddr: + indexValue, err := extractIntValue(refinstr.Index.String()) + if err == nil && !isSliceIndexInsideBounds(0, sliceCap, indexValue) { + *violations = append(*violations, refinstr) + } + case *ssa.Call: + if ifref, cond := extractSliceIfLenCondition(refinstr); ifref != nil && cond != nil { + ifs[*ifref] = cond + } else { + parPos := -1 + for pos, arg := range refinstr.Call.Args { + if a, ok := arg.(*ssa.Slice); ok && a == slice { + parPos = pos + } + } + if fn, ok := refinstr.Call.Value.(*ssa.Function); ok { + if len(fn.Params) > parPos && parPos > -1 { + param := fn.Params[parPos] + trackSliceBounds(depth, sliceCap, param, violations, ifs) + } + } + } + } + } + } +} + +func checkAllSlicesBounds(depth int, sliceCap int, slice *ssa.Slice, violations *[]ssa.Instruction, ifs map[ssa.If]*ssa.BinOp) { + if depth == maxDepth { + return + } + depth++ + if violations == nil { + violations = &[]ssa.Instruction{} + } + sliceLow, sliceHigh := extractSliceBounds(slice) + if !isSliceInsideBounds(0, sliceCap, sliceLow, sliceHigh) { + *violations = append(*violations, slice) + } + switch slice.X.(type) { + case *ssa.Alloc, *ssa.Parameter, *ssa.Slice: + l, h := extractSliceBounds(slice) + newCap := computeSliceNewCap(l, h, sliceCap) + trackSliceBounds(depth, newCap, slice, violations, ifs) + } + + references := slice.Referrers() + if references == nil { + return + } + for _, ref := range *references { + switch s := ref.(type) { + case *ssa.Slice: + checkAllSlicesBounds(depth, sliceCap, s, violations, ifs) + switch s.X.(type) { + case *ssa.Alloc, *ssa.Parameter: + l, h := extractSliceBounds(s) + newCap := computeSliceNewCap(l, h, sliceCap) + trackSliceBounds(depth, newCap, s, violations, ifs) + } + } + } +} + +func extractSliceIfLenCondition(call *ssa.Call) (*ssa.If, *ssa.BinOp) { + if builtInLen, ok := call.Call.Value.(*ssa.Builtin); ok { + if builtInLen.Name() == "len" { + refs := call.Referrers() + if refs != nil { + for _, ref := range *refs { + if binop, ok := ref.(*ssa.BinOp); ok { + binoprefs := binop.Referrers() + for _, ref := range *binoprefs { + if ifref, ok := ref.(*ssa.If); ok { + return ifref, binop + } + } + } + } + } + } + } + return nil, nil +} + +func computeSliceNewCap(l, h, oldCap int) int { + if l == 0 && h == 0 { + return oldCap + } + if l > 0 && h == 0 { + return oldCap - l + } + if l == 0 && h > 0 { + return h + } + return h - l +} + +func invBound(bound bound) bound { + switch bound { + case lowerUnbounded: + return upperUnbounded + case upperUnbounded: + return lowerUnbounded + case upperBounded: + return unbounded + case unbounded: + return upperBounded + default: + return unbounded + } +} + +func extractBinOpBound(binop *ssa.BinOp) (bound, int, error) { + if binop.X != nil { + if x, ok := binop.X.(*ssa.Const); ok { + value, err := strconv.Atoi(x.Value.String()) + if err != nil { + return lowerUnbounded, value, err + } + switch binop.Op { + case token.LSS, token.LEQ: + return upperUnbounded, value, nil + case token.GTR, token.GEQ: + return lowerUnbounded, value, nil + case token.EQL: + return upperBounded, value, nil + case token.NEQ: + return unbounded, value, nil + } + } + } + if binop.Y != nil { + if y, ok := binop.Y.(*ssa.Const); ok { + value, err := strconv.Atoi(y.Value.String()) + if err != nil { + return lowerUnbounded, value, err + } + switch binop.Op { + case token.LSS, token.LEQ: + return lowerUnbounded, value, nil + case token.GTR, token.GEQ: + return upperUnbounded, value, nil + case token.EQL: + return upperBounded, value, nil + case token.NEQ: + return unbounded, value, nil + } + } + } + return lowerUnbounded, 0, fmt.Errorf("unable to extract constant from binop") +} + +func isSliceIndexInsideBounds(l, h int, index int) bool { + return (l <= index && index < h) +} + +func isSliceInsideBounds(l, h int, cl, ch int) bool { + return (l <= cl && h >= ch) && (l <= ch && h >= cl) +} + +func extractSliceBounds(slice *ssa.Slice) (int, int) { + var low int + if slice.Low != nil { + l, err := extractIntValue(slice.Low.String()) + if err == nil { + low = l + } + } + var high int + if slice.High != nil { + h, err := extractIntValue(slice.High.String()) + if err == nil { + high = h + } + } + return low, high +} + +func extractIntValue(value string) (int, error) { + parts := strings.Split(value, ":") + if len(parts) != 2 { + return 0, fmt.Errorf("invalid value: %s", value) + } + if parts[1] != "int" { + return 0, fmt.Errorf("invalid value: %s", value) + } + return strconv.Atoi(parts[0]) +} + +func extractSliceCapFromAlloc(instr string) (int, error) { + re := regexp.MustCompile(`new \[(\d+)\]*`) + var sliceCap int + matches := re.FindAllStringSubmatch(instr, -1) + if matches == nil { + return sliceCap, errors.New("no slice cap found") + } + + if len(matches) > 0 { + m := matches[0] + if len(m) > 1 { + return strconv.Atoi(m[1]) + } + } + + return 0, errors.New("no slice cap found") +} diff --git a/tools/vendor/github.com/securego/gosec/v2/analyzers/ssrf.go b/tools/vendor/github.com/securego/gosec/v2/analyzers/ssrf.go deleted file mode 100644 index a9dbd95008..0000000000 --- a/tools/vendor/github.com/securego/gosec/v2/analyzers/ssrf.go +++ /dev/null @@ -1,57 +0,0 @@ -// (c) Copyright gosec's authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package analyzers - -import ( - "golang.org/x/tools/go/analysis" - "golang.org/x/tools/go/analysis/passes/buildssa" - "golang.org/x/tools/go/ssa" - - "github.com/securego/gosec/v2/issue" -) - -func newSSRFAnalyzer(id string, description string) *analysis.Analyzer { - return &analysis.Analyzer{ - Name: id, - Doc: description, - Run: runSSRF, - Requires: []*analysis.Analyzer{buildssa.Analyzer}, - } -} - -func runSSRF(pass *analysis.Pass) (interface{}, error) { - ssaResult, err := getSSAResult(pass) - if err != nil { - return nil, err - } - // TODO: implement the analysis - for _, fn := range ssaResult.SSA.SrcFuncs { - for _, block := range fn.DomPreorder() { - for _, instr := range block.Instrs { - switch instr := instr.(type) { - case *ssa.Call: - callee := instr.Call.StaticCallee() - if callee != nil { - ssaResult.Logger.Printf("callee: %s\n", callee) - return newIssue(pass.Analyzer.Name, - "not implemeted", - pass.Fset, instr.Call.Pos(), issue.Low, issue.High), nil - } - } - } - } - } - return nil, nil -} diff --git a/tools/vendor/github.com/securego/gosec/v2/analyzers/util.go b/tools/vendor/github.com/securego/gosec/v2/analyzers/util.go index b090a3e454..5941184aa2 100644 --- a/tools/vendor/github.com/securego/gosec/v2/analyzers/util.go +++ b/tools/vendor/github.com/securego/gosec/v2/analyzers/util.go @@ -28,7 +28,7 @@ import ( ) // SSAAnalyzerResult contains various information returned by the -// SSA analysis along with some configuraion +// SSA analysis along with some configuration type SSAAnalyzerResult struct { Config map[string]interface{} Logger *log.Logger @@ -38,11 +38,11 @@ type SSAAnalyzerResult struct { // BuildDefaultAnalyzers returns the default list of analyzers func BuildDefaultAnalyzers() []*analysis.Analyzer { return []*analysis.Analyzer{ - newSSRFAnalyzer("G107", "URL provided to HTTP request as taint input"), + newSliceBoundsAnalyzer("G602", "Possible slice bounds out of range"), } } -// getSSAResult retrives the SSA result from analysis pass +// getSSAResult retrieves the SSA result from analysis pass func getSSAResult(pass *analysis.Pass) (*SSAAnalyzerResult, error) { result, ok := pass.ResultOf[buildssa.Analyzer] if !ok { diff --git a/tools/vendor/github.com/securego/gosec/v2/config.go b/tools/vendor/github.com/securego/gosec/v2/config.go index ca4cf21757..9cbb7a7134 100644 --- a/tools/vendor/github.com/securego/gosec/v2/config.go +++ b/tools/vendor/github.com/securego/gosec/v2/config.go @@ -33,6 +33,11 @@ const ( SSA GlobalOption = "ssa" ) +// NoSecTag returns the tag used to disable gosec for a line of code. +func NoSecTag(tag string) string { + return fmt.Sprintf("%s%s", "#", tag) +} + // Config is used to provide configuration and customization to each of the rules. type Config map[string]interface{} diff --git a/tools/vendor/github.com/securego/gosec/v2/cwe/data.go b/tools/vendor/github.com/securego/gosec/v2/cwe/data.go index ff1ad3c7d8..79a6b9d231 100644 --- a/tools/vendor/github.com/securego/gosec/v2/cwe/data.go +++ b/tools/vendor/github.com/securego/gosec/v2/cwe/data.go @@ -1,7 +1,5 @@ package cwe -import "fmt" - const ( // Acronym is the acronym of CWE Acronym = "CWE" @@ -13,139 +11,128 @@ const ( Organization = "MITRE" // Description the description of CWE Description = "The MITRE Common Weakness Enumeration" -) - -var ( // InformationURI link to the published CWE PDF - InformationURI = fmt.Sprintf("https://cwe.mitre.org/data/published/cwe_v%s.pdf/", Version) + InformationURI = "https://cwe.mitre.org/data/published/cwe_v" + Version + ".pdf/" // DownloadURI link to the zipped XML of the CWE list - DownloadURI = fmt.Sprintf("https://cwe.mitre.org/data/xml/cwec_v%s.xml.zip", Version) - - data = map[string]*Weakness{} - - weaknesses = []*Weakness{ - { - ID: "118", - Description: "The software does not restrict or incorrectly restricts operations within the boundaries of a resource that is accessed using an index or pointer, such as memory or files.", - Name: "Incorrect Access of Indexable Resource ('Range Error')", - }, - { - ID: "190", - Description: "The software performs a calculation that can produce an integer overflow or wraparound, when the logic assumes that the resulting value will always be larger than the original value. This can introduce other weaknesses when the calculation is used for resource management or execution control.", - Name: "Integer Overflow or Wraparound", - }, - { - ID: "200", - Description: "The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.", - Name: "Exposure of Sensitive Information to an Unauthorized Actor", - }, - { - ID: "22", - Description: "The software uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the software does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.", - Name: "Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')", - }, - { - ID: "242", - Description: "The program calls a function that can never be guaranteed to work safely.", - Name: "Use of Inherently Dangerous Function", - }, - { - ID: "276", - Description: "During installation, installed file permissions are set to allow anyone to modify those files.", - Name: "Incorrect Default Permissions", - }, - { - ID: "295", - Description: "The software does not validate, or incorrectly validates, a certificate.", - Name: "Improper Certificate Validation", - }, - { - ID: "310", - Description: "Weaknesses in this category are related to the design and implementation of data confidentiality and integrity. Frequently these deal with the use of encoding techniques, encryption libraries, and hashing algorithms. The weaknesses in this category could lead to a degradation of the quality data if they are not addressed.", - Name: "Cryptographic Issues", - }, - { - ID: "322", - Description: "The software performs a key exchange with an actor without verifying the identity of that actor.", - Name: "Key Exchange without Entity Authentication", - }, - { - ID: "326", - Description: "The software stores or transmits sensitive data using an encryption scheme that is theoretically sound, but is not strong enough for the level of protection required.", - Name: "Inadequate Encryption Strength", - }, - { - ID: "327", - Description: "The use of a broken or risky cryptographic algorithm is an unnecessary risk that may result in the exposure of sensitive information.", - Name: "Use of a Broken or Risky Cryptographic Algorithm", - }, - { - ID: "338", - Description: "The product uses a Pseudo-Random Number Generator (PRNG) in a security context, but the PRNG's algorithm is not cryptographically strong.", - Name: "Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG)", - }, - { - ID: "377", - Description: "Creating and using insecure temporary files can leave application and system data vulnerable to attack.", - Name: "Insecure Temporary File", - }, - { - ID: "400", - Description: "The software does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.", - Name: "Uncontrolled Resource Consumption", - }, - { - ID: "409", - Description: "The software does not handle or incorrectly handles a compressed input with a very high compression ratio that produces a large output.", - Name: "Improper Handling of Highly Compressed Data (Data Amplification)", - }, - { - ID: "703", - Description: "The software does not properly anticipate or handle exceptional conditions that rarely occur during normal operation of the software.", - Name: "Improper Check or Handling of Exceptional Conditions", - }, - { - ID: "78", - Description: "The software constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.", - Name: "Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')", - }, - { - ID: "79", - Description: "The software does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.", - Name: "Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')", - }, - { - ID: "798", - Description: "The software contains hard-coded credentials, such as a password or cryptographic key, which it uses for its own inbound authentication, outbound communication to external components, or encryption of internal data.", - Name: "Use of Hard-coded Credentials", - }, - { - ID: "88", - Description: "The software constructs a string for a command to executed by a separate component\nin another control sphere, but it does not properly delimit the\nintended arguments, options, or switches within that command string.", - Name: "Improper Neutralization of Argument Delimiters in a Command ('Argument Injection')", - }, - { - ID: "89", - Description: "The software constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component.", - Name: "Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')", - }, - { - ID: "676", - Description: "The program invokes a potentially dangerous function that could introduce a vulnerability if it is used incorrectly, but the function can also be used safely.", - Name: "Use of Potentially Dangerous Function", - }, - } + DownloadURI = "https://cwe.mitre.org/data/xml/cwec_v" + Version + ".xml.zip" ) -func init() { - for _, weakness := range weaknesses { - data[weakness.ID] = weakness - } +var idWeaknesses = map[string]*Weakness{ + "118": { + ID: "118", + Description: "The software does not restrict or incorrectly restricts operations within the boundaries of a resource that is accessed using an index or pointer, such as memory or files.", + Name: "Incorrect Access of Indexable Resource ('Range Error')", + }, + "190": { + ID: "190", + Description: "The software performs a calculation that can produce an integer overflow or wraparound, when the logic assumes that the resulting value will always be larger than the original value. This can introduce other weaknesses when the calculation is used for resource management or execution control.", + Name: "Integer Overflow or Wraparound", + }, + "200": { + ID: "200", + Description: "The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.", + Name: "Exposure of Sensitive Information to an Unauthorized Actor", + }, + "22": { + ID: "22", + Description: "The software uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the software does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.", + Name: "Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')", + }, + "242": { + ID: "242", + Description: "The program calls a function that can never be guaranteed to work safely.", + Name: "Use of Inherently Dangerous Function", + }, + "276": { + ID: "276", + Description: "During installation, installed file permissions are set to allow anyone to modify those files.", + Name: "Incorrect Default Permissions", + }, + "295": { + ID: "295", + Description: "The software does not validate, or incorrectly validates, a certificate.", + Name: "Improper Certificate Validation", + }, + "310": { + ID: "310", + Description: "Weaknesses in this category are related to the design and implementation of data confidentiality and integrity. Frequently these deal with the use of encoding techniques, encryption libraries, and hashing algorithms. The weaknesses in this category could lead to a degradation of the quality data if they are not addressed.", + Name: "Cryptographic Issues", + }, + "322": { + ID: "322", + Description: "The software performs a key exchange with an actor without verifying the identity of that actor.", + Name: "Key Exchange without Entity Authentication", + }, + "326": { + ID: "326", + Description: "The software stores or transmits sensitive data using an encryption scheme that is theoretically sound, but is not strong enough for the level of protection required.", + Name: "Inadequate Encryption Strength", + }, + "327": { + ID: "327", + Description: "The use of a broken or risky cryptographic algorithm is an unnecessary risk that may result in the exposure of sensitive information.", + Name: "Use of a Broken or Risky Cryptographic Algorithm", + }, + "338": { + ID: "338", + Description: "The product uses a Pseudo-Random Number Generator (PRNG) in a security context, but the PRNG's algorithm is not cryptographically strong.", + Name: "Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG)", + }, + "377": { + ID: "377", + Description: "Creating and using insecure temporary files can leave application and system data vulnerable to attack.", + Name: "Insecure Temporary File", + }, + "400": { + ID: "400", + Description: "The software does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.", + Name: "Uncontrolled Resource Consumption", + }, + "409": { + ID: "409", + Description: "The software does not handle or incorrectly handles a compressed input with a very high compression ratio that produces a large output.", + Name: "Improper Handling of Highly Compressed Data (Data Amplification)", + }, + "703": { + ID: "703", + Description: "The software does not properly anticipate or handle exceptional conditions that rarely occur during normal operation of the software.", + Name: "Improper Check or Handling of Exceptional Conditions", + }, + "78": { + ID: "78", + Description: "The software constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.", + Name: "Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')", + }, + "79": { + ID: "79", + Description: "The software does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.", + Name: "Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')", + }, + "798": { + ID: "798", + Description: "The software contains hard-coded credentials, such as a password or cryptographic key, which it uses for its own inbound authentication, outbound communication to external components, or encryption of internal data.", + Name: "Use of Hard-coded Credentials", + }, + "88": { + ID: "88", + Description: "The software constructs a string for a command to executed by a separate component\nin another control sphere, but it does not properly delimit the\nintended arguments, options, or switches within that command string.", + Name: "Improper Neutralization of Argument Delimiters in a Command ('Argument Injection')", + }, + "89": { + ID: "89", + Description: "The software constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component.", + Name: "Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')", + }, + "676": { + ID: "676", + Description: "The program invokes a potentially dangerous function that could introduce a vulnerability if it is used incorrectly, but the function can also be used safely.", + Name: "Use of Potentially Dangerous Function", + }, } // Get Retrieves a CWE weakness by it's id func Get(id string) *Weakness { - weakness, ok := data[id] + weakness, ok := idWeaknesses[id] if ok && weakness != nil { return weakness } diff --git a/tools/vendor/github.com/securego/gosec/v2/helpers.go b/tools/vendor/github.com/securego/gosec/v2/helpers.go index 08b7893eb2..15b2b5f3a3 100644 --- a/tools/vendor/github.com/securego/gosec/v2/helpers.go +++ b/tools/vendor/github.com/securego/gosec/v2/helpers.go @@ -96,11 +96,46 @@ func GetChar(n ast.Node) (byte, error) { return 0, fmt.Errorf("Unexpected AST node type: %T", n) } +// GetStringRecursive will recursively walk down a tree of *ast.BinaryExpr. It will then concat the results, and return. +// Unlike the other getters, it does _not_ raise an error for unknown ast.Node types. At the base, the recursion will hit a non-BinaryExpr type, +// either BasicLit or other, so it's not an error case. It will only error if `strconv.Unquote` errors. This matters, because there's +// currently functionality that relies on error values being returned by GetString if and when it hits a non-basiclit string node type, +// hence for cases where recursion is needed, we use this separate function, so that we can still be backwards compatible. +// +// This was added to handle a SQL injection concatenation case where the injected value is infixed between two strings, not at the start or end. See example below +// +// Do note that this will omit non-string values. So for example, if you were to use this node: +// ```go +// q := "SELECT * FROM foo WHERE name = '" + os.Args[0] + "' AND 1=1" // will result in "SELECT * FROM foo WHERE ” AND 1=1" + +func GetStringRecursive(n ast.Node) (string, error) { + if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.STRING { + return strconv.Unquote(node.Value) + } + + if expr, ok := n.(*ast.BinaryExpr); ok { + x, err := GetStringRecursive(expr.X) + if err != nil { + return "", err + } + + y, err := GetStringRecursive(expr.Y) + if err != nil { + return "", err + } + + return x + y, nil + } + + return "", nil +} + // GetString will read and return a string value from an ast.BasicLit func GetString(n ast.Node) (string, error) { if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.STRING { return strconv.Unquote(node.Value) } + return "", fmt.Errorf("Unexpected AST node type: %T", n) } @@ -148,7 +183,7 @@ func GetCallInfo(n ast.Node, ctx *Context) (string, string, error) { case *ast.CallExpr: switch call := expr.Fun.(type) { case *ast.Ident: - if call.Name == "new" { + if call.Name == "new" && len(expr.Args) > 0 { t := ctx.Info.TypeOf(expr.Args[0]) if t != nil { return t.String(), fn.Sel.Name, nil @@ -201,22 +236,21 @@ func GetCallStringArgsValues(n ast.Node, _ *Context) []string { return values } -// GetIdentStringValues return the string values of an Ident if they can be resolved -func GetIdentStringValues(ident *ast.Ident) []string { +func getIdentStringValues(ident *ast.Ident, stringFinder func(ast.Node) (string, error)) []string { values := []string{} obj := ident.Obj if obj != nil { switch decl := obj.Decl.(type) { case *ast.ValueSpec: for _, v := range decl.Values { - value, err := GetString(v) + value, err := stringFinder(v) if err == nil { values = append(values, value) } } case *ast.AssignStmt: for _, v := range decl.Rhs { - value, err := GetString(v) + value, err := stringFinder(v) if err == nil { values = append(values, value) } @@ -226,6 +260,18 @@ func GetIdentStringValues(ident *ast.Ident) []string { return values } +// getIdentStringRecursive returns the string of values of an Ident if they can be resolved +// The difference between this and GetIdentStringValues is that it will attempt to resolve the strings recursively, +// if it is passed a *ast.BinaryExpr. See GetStringRecursive for details +func GetIdentStringValuesRecursive(ident *ast.Ident) []string { + return getIdentStringValues(ident, GetStringRecursive) +} + +// GetIdentStringValues return the string values of an Ident if they can be resolved +func GetIdentStringValues(ident *ast.Ident) []string { + return getIdentStringValues(ident, GetString) +} + // GetBinaryExprOperands returns all operands of a binary expression by traversing // the expression tree func GetBinaryExprOperands(be *ast.BinaryExpr) []ast.Node { @@ -301,7 +347,7 @@ func Getenv(key, userDefault string) string { return userDefault } -// GetPkgRelativePath returns the Go relative relative path derived +// GetPkgRelativePath returns the Go relative path derived // form the given path func GetPkgRelativePath(path string) (string, error) { abspath, err := filepath.Abs(path) diff --git a/tools/vendor/github.com/securego/gosec/v2/issue/issue.go b/tools/vendor/github.com/securego/gosec/v2/issue/issue.go index 5bf00dec2d..1000b20423 100644 --- a/tools/vendor/github.com/securego/gosec/v2/issue/issue.go +++ b/tools/vendor/github.com/securego/gosec/v2/issue/issue.go @@ -87,6 +87,7 @@ var ruleToCWE = map[string]string{ "G504": "327", "G505": "327", "G601": "118", + "G602": "118", } // Issue is returned by a gosec rule if it discovers an issue with the scanned code. @@ -177,11 +178,7 @@ func codeSnippetEndLine(node ast.Node, fobj *token.File) int64 { // New creates a new Issue func New(fobj *token.File, node ast.Node, ruleID, desc string, severity, confidence Score) *Issue { name := fobj.Name() - start, end := fobj.Line(node.Pos()), fobj.Line(node.End()) - line := strconv.Itoa(start) - if start != end { - line = fmt.Sprintf("%d-%d", start, end) - } + line := GetLine(fobj, node) col := strconv.Itoa(fobj.Position(node.Pos()).Column) var code string @@ -216,3 +213,13 @@ func (i *Issue) WithSuppressions(suppressions []SuppressionInfo) *Issue { i.Suppressions = suppressions return i } + +// GetLine returns the line number of a given ast.Node +func GetLine(fobj *token.File, node ast.Node) string { + start, end := fobj.Line(node.Pos()), fobj.Line(node.End()) + line := strconv.Itoa(start) + if start != end { + line = fmt.Sprintf("%d-%d", start, end) + } + return line +} diff --git a/tools/vendor/github.com/securego/gosec/v2/rule.go b/tools/vendor/github.com/securego/gosec/v2/rule.go index 5e973b6acf..490a25da02 100644 --- a/tools/vendor/github.com/securego/gosec/v2/rule.go +++ b/tools/vendor/github.com/securego/gosec/v2/rule.go @@ -43,7 +43,7 @@ func NewRuleSet() RuleSet { return RuleSet{make(map[reflect.Type][]Rule), make(map[string]bool)} } -// Register adds a trigger for the supplied rule for the the +// Register adds a trigger for the supplied rule for the // specified ast nodes. func (r RuleSet) Register(rule Rule, isSuppressed bool, nodes ...ast.Node) { for _, n := range nodes { diff --git a/tools/vendor/github.com/securego/gosec/v2/rules/fileperms.go b/tools/vendor/github.com/securego/gosec/v2/rules/fileperms.go index 0376b6a03c..5311f74c6c 100644 --- a/tools/vendor/github.com/securego/gosec/v2/rules/fileperms.go +++ b/tools/vendor/github.com/securego/gosec/v2/rules/fileperms.go @@ -30,6 +30,7 @@ type filePermissions struct { calls []string } +// ID returns the ID of the rule. func (r *filePermissions) ID() string { return r.MetaData.ID } @@ -55,6 +56,7 @@ func modeIsSubset(subset int64, superset int64) bool { return (subset | superset) == superset } +// Match checks if the rule is matched. func (r *filePermissions) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { for _, pkg := range r.pkgs { if callexpr, matched := gosec.MatchCallByPackage(n, c, pkg, r.calls...); matched { @@ -116,3 +118,47 @@ func NewMkdirPerms(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { }, }, []ast.Node{(*ast.CallExpr)(nil)} } + +type osCreatePermissions struct { + issue.MetaData + mode int64 + pkgs []string + calls []string +} + +const defaultOsCreateMode = 0o666 + +// ID returns the ID of the rule. +func (r *osCreatePermissions) ID() string { + return r.MetaData.ID +} + +// Match checks if the rule is matched. +func (r *osCreatePermissions) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { + for _, pkg := range r.pkgs { + if _, matched := gosec.MatchCallByPackage(n, c, pkg, r.calls...); matched { + if !modeIsSubset(defaultOsCreateMode, r.mode) { + return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil + } + } + } + return nil, nil +} + +// NewOsCreatePerms reates a rule to detect file creation with a more permissive than configured +// permission mask. +func NewOsCreatePerms(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { + mode := getConfiguredMode(conf, id, 0o666) + return &osCreatePermissions{ + mode: mode, + pkgs: []string{"os"}, + calls: []string{"Create"}, + MetaData: issue.MetaData{ + ID: id, + Severity: issue.Medium, + Confidence: issue.High, + What: fmt.Sprintf("Expect file permissions to be %#o or less but os.Create used with default permissions %#o", + mode, defaultOsCreateMode), + }, + }, []ast.Node{(*ast.CallExpr)(nil)} +} diff --git a/tools/vendor/github.com/securego/gosec/v2/rules/hardcoded_credentials.go b/tools/vendor/github.com/securego/gosec/v2/rules/hardcoded_credentials.go index eac50d7c96..ed1fb947d1 100644 --- a/tools/vendor/github.com/securego/gosec/v2/rules/hardcoded_credentials.go +++ b/tools/vendor/github.com/securego/gosec/v2/rules/hardcoded_credentials.go @@ -15,17 +15,178 @@ package rules import ( + "fmt" "go/ast" "go/token" "regexp" "strconv" - zxcvbn "github.com/nbutton23/zxcvbn-go" + zxcvbn "github.com/ccojocar/zxcvbn-go" "github.com/securego/gosec/v2" "github.com/securego/gosec/v2/issue" ) +type secretPattern struct { + name string + regexp *regexp.Regexp +} + +var secretsPatterns = [...]secretPattern{ + { + name: "RSA private key", + regexp: regexp.MustCompile(`-----BEGIN RSA PRIVATE KEY-----`), + }, + { + name: "SSH (DSA) private key", + regexp: regexp.MustCompile(`-----BEGIN DSA PRIVATE KEY-----`), + }, + { + name: "SSH (EC) private key", + regexp: regexp.MustCompile(`-----BEGIN EC PRIVATE KEY-----`), + }, + { + name: "PGP private key block", + regexp: regexp.MustCompile(`-----BEGIN PGP PRIVATE KEY BLOCK-----`), + }, + { + name: "Slack Token", + regexp: regexp.MustCompile(`xox[pborsa]-[0-9]{12}-[0-9]{12}-[0-9]{12}-[a-z0-9]{32}`), + }, + { + name: "AWS API Key", + regexp: regexp.MustCompile(`AKIA[0-9A-Z]{16}`), + }, + { + name: "Amazon MWS Auth Token", + regexp: regexp.MustCompile(`amzn\.mws\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`), + }, + { + name: "AWS AppSync GraphQL Key", + regexp: regexp.MustCompile(`da2-[a-z0-9]{26}`), + }, + { + name: "GitHub personal access token", + regexp: regexp.MustCompile(`ghp_[a-zA-Z0-9]{36}`), + }, + { + name: "GitHub fine-grained access token", + regexp: regexp.MustCompile(`github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}`), + }, + { + name: "GitHub action temporary token", + regexp: regexp.MustCompile(`ghs_[a-zA-Z0-9]{36}`), + }, + { + name: "Google API Key", + regexp: regexp.MustCompile(`AIza[0-9A-Za-z\-_]{35}`), + }, + { + name: "Google Cloud Platform API Key", + regexp: regexp.MustCompile(`AIza[0-9A-Za-z\-_]{35}`), + }, + { + name: "Google Cloud Platform OAuth", + regexp: regexp.MustCompile(`[0-9]+-[0-9A-Za-z_]{32}\.apps\.googleusercontent\.com`), + }, + { + name: "Google Drive API Key", + regexp: regexp.MustCompile(`AIza[0-9A-Za-z\-_]{35}`), + }, + { + name: "Google Drive OAuth", + regexp: regexp.MustCompile(`[0-9]+-[0-9A-Za-z_]{32}\.apps\.googleusercontent\.com`), + }, + { + name: "Google (GCP) Service-account", + regexp: regexp.MustCompile(`"type": "service_account"`), + }, + { + name: "Google Gmail API Key", + regexp: regexp.MustCompile(`AIza[0-9A-Za-z\-_]{35}`), + }, + { + name: "Google Gmail OAuth", + regexp: regexp.MustCompile(`[0-9]+-[0-9A-Za-z_]{32}\.apps\.googleusercontent\.com`), + }, + { + name: "Google OAuth Access Token", + regexp: regexp.MustCompile(`ya29\.[0-9A-Za-z\-_]+`), + }, + { + name: "Google YouTube API Key", + regexp: regexp.MustCompile(`AIza[0-9A-Za-z\-_]{35}`), + }, + { + name: "Google YouTube OAuth", + regexp: regexp.MustCompile(`[0-9]+-[0-9A-Za-z_]{32}\.apps\.googleusercontent\.com`), + }, + { + name: "Generic API Key", + regexp: regexp.MustCompile(`[aA][pP][iI]_?[kK][eE][yY].*[''|"][0-9a-zA-Z]{32,45}[''|"]`), + }, + { + name: "Generic Secret", + regexp: regexp.MustCompile(`[sS][eE][cC][rR][eE][tT].*[''|"][0-9a-zA-Z]{32,45}[''|"]`), + }, + { + name: "Heroku API Key", + regexp: regexp.MustCompile(`[hH][eE][rR][oO][kK][uU].*[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}`), + }, + { + name: "MailChimp API Key", + regexp: regexp.MustCompile(`[0-9a-f]{32}-us[0-9]{1,2}`), + }, + { + name: "Mailgun API Key", + regexp: regexp.MustCompile(`key-[0-9a-zA-Z]{32}`), + }, + { + name: "Password in URL", + regexp: regexp.MustCompile(`[a-zA-Z]{3,10}://[^/\\s:@]{3,20}:[^/\\s:@]{3,20}@.{1,100}["'\\s]`), + }, + { + name: "Slack Webhook", + regexp: regexp.MustCompile(`https://hooks\.slack\.com/services/T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8}/[a-zA-Z0-9_]{24}`), + }, + { + name: "Stripe API Key", + regexp: regexp.MustCompile(`sk_live_[0-9a-zA-Z]{24}`), + }, + { + name: "Stripe API Key", + regexp: regexp.MustCompile(`sk_live_[0-9a-zA-Z]{24}`), + }, + { + name: "Stripe Restricted API Key", + regexp: regexp.MustCompile(`rk_live_[0-9a-zA-Z]{24}`), + }, + { + name: "Square Access Token", + regexp: regexp.MustCompile(`sq0atp-[0-9A-Za-z\-_]{22}`), + }, + { + name: "Square OAuth Secret", + regexp: regexp.MustCompile(`sq0csp-[0-9A-Za-z\-_]{43}`), + }, + { + name: "Telegram Bot API Key", + regexp: regexp.MustCompile(`[0-9]+:AA[0-9A-Za-z\-_]{33}`), + }, + { + name: "Twilio API Key", + regexp: regexp.MustCompile(`SK[0-9a-fA-F]{32}`), + }, + { + name: "Twitter Access Token", + regexp: regexp.MustCompile(`[tT][wW][iI][tT][tT][eE][rR].*[1-9][0-9]+-[0-9a-zA-Z]{40}`), + }, + { + name: "Twitter OAuth", + regexp: regexp.MustCompile(`[tT][wW][iI][tT][tT][eE][rR].*[''|"][0-9a-zA-Z]{35,44}[''|"]`), + }, +} + type credentials struct { issue.MetaData pattern *regexp.Regexp @@ -55,6 +216,15 @@ func (r *credentials) isHighEntropyString(str string) bool { entropyPerChar >= r.perCharThreshold)) } +func (r *credentials) isSecretPattern(str string) (bool, string) { + for _, pattern := range secretsPatterns { + if pattern.regexp.MatchString(str) { + return true, pattern.name + } + } + return false, "" +} + func (r *credentials) Match(n ast.Node, ctx *gosec.Context) (*issue.Issue, error) { switch node := n.(type) { case *ast.AssignStmt: @@ -70,6 +240,7 @@ func (r *credentials) Match(n ast.Node, ctx *gosec.Context) (*issue.Issue, error func (r *credentials) matchAssign(assign *ast.AssignStmt, ctx *gosec.Context) (*issue.Issue, error) { for _, i := range assign.Lhs { if ident, ok := i.(*ast.Ident); ok { + // First check LHS to find anything being assigned to variables whose name appears to be a cred if r.pattern.MatchString(ident.Name) { for _, e := range assign.Rhs { if val, err := gosec.GetString(e); err == nil { @@ -79,12 +250,28 @@ func (r *credentials) matchAssign(assign *ast.AssignStmt, ctx *gosec.Context) (* } } } + + // Now that no names were matched, match the RHS to see if the actual values being assigned are creds + for _, e := range assign.Rhs { + val, err := gosec.GetString(e) + if err != nil { + continue + } + + if r.ignoreEntropy || r.isHighEntropyString(val) { + if ok, patternName := r.isSecretPattern(val); ok { + return ctx.NewIssue(assign, r.ID(), fmt.Sprintf("%s: %s", r.What, patternName), r.Severity, r.Confidence), nil + } + } + } } } return nil, nil } func (r *credentials) matchValueSpec(valueSpec *ast.ValueSpec, ctx *gosec.Context) (*issue.Issue, error) { + // Running match against the variable name(s) first. Will catch any creds whose var name matches the pattern, + // then will go back over to check the values themselves. for index, ident := range valueSpec.Names { if r.pattern.MatchString(ident.Name) && valueSpec.Values != nil { // const foo, bar = "same value" @@ -98,6 +285,18 @@ func (r *credentials) matchValueSpec(valueSpec *ast.ValueSpec, ctx *gosec.Contex } } } + + // Now that no variable names have been matched, match the actual values to find any creds + for _, ident := range valueSpec.Values { + if val, err := gosec.GetString(ident); err == nil { + if r.ignoreEntropy || r.isHighEntropyString(val) { + if ok, patternName := r.isSecretPattern(val); ok { + return ctx.NewIssue(valueSpec, r.ID(), fmt.Sprintf("%s: %s", r.What, patternName), r.Severity, r.Confidence), nil + } + } + } + } + return nil, nil } @@ -119,6 +318,22 @@ func (r *credentials) matchEqualityCheck(binaryExpr *ast.BinaryExpr, ctx *gosec. } } } + + // Now that the variable names have been checked, and no matches were found, make sure that + // either the left or right operands is a string literal so we can match the value. + identStrConst, ok := binaryExpr.X.(*ast.BasicLit) + if !ok { + identStrConst, ok = binaryExpr.Y.(*ast.BasicLit) + } + + if ok && identStrConst.Kind == token.STRING { + s, _ := gosec.GetString(identStrConst) + if r.ignoreEntropy || r.isHighEntropyString(s) { + if ok, patternName := r.isSecretPattern(s); ok { + return ctx.NewIssue(binaryExpr, r.ID(), fmt.Sprintf("%s: %s", r.What, patternName), r.Severity, r.Confidence), nil + } + } + } } return nil, nil } @@ -138,6 +353,7 @@ func NewHardcodedCredentials(id string, conf gosec.Config) (gosec.Rule, []ast.No pattern = cfgPattern } } + if configIgnoreEntropy, ok := conf["ignore_entropy"]; ok { if cfgIgnoreEntropy, ok := configIgnoreEntropy.(bool); ok { ignoreEntropy = cfgIgnoreEntropy diff --git a/tools/vendor/github.com/securego/gosec/v2/rules/implicit_aliasing.go b/tools/vendor/github.com/securego/gosec/v2/rules/implicit_aliasing.go index 70678e29a3..a7eabb20b4 100644 --- a/tools/vendor/github.com/securego/gosec/v2/rules/implicit_aliasing.go +++ b/tools/vendor/github.com/securego/gosec/v2/rules/implicit_aliasing.go @@ -3,6 +3,7 @@ package rules import ( "go/ast" "go/token" + "go/types" "github.com/securego/gosec/v2" "github.com/securego/gosec/v2/issue" @@ -28,6 +29,23 @@ func containsUnary(exprs []*ast.UnaryExpr, expr *ast.UnaryExpr) bool { return false } +func getIdentExpr(expr ast.Expr) (*ast.Ident, bool) { + return doGetIdentExpr(expr, false) +} + +func doGetIdentExpr(expr ast.Expr, hasSelector bool) (*ast.Ident, bool) { + switch node := expr.(type) { + case *ast.Ident: + return node, hasSelector + case *ast.SelectorExpr: + return doGetIdentExpr(node.X, true) + case *ast.UnaryExpr: + return doGetIdentExpr(node.X, hasSelector) + default: + return nil, false + } +} + func (r *implicitAliasing) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { switch node := n.(type) { case *ast.RangeStmt: @@ -72,9 +90,13 @@ func (r *implicitAliasing) Match(n ast.Node, c *gosec.Context) (*issue.Issue, er } // If we find a unary op of & (reference) of an object within r.aliases, complain. - if ident, ok := node.X.(*ast.Ident); ok && node.Op.String() == "&" { - if _, contains := r.aliases[ident.Obj]; contains { - return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil + if identExpr, hasSelector := getIdentExpr(node); identExpr != nil && node.Op.String() == "&" { + if _, contains := r.aliases[identExpr.Obj]; contains { + _, isPointer := c.Info.TypeOf(identExpr).(*types.Pointer) + + if !hasSelector || !isPointer { + return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil + } } } case *ast.ReturnStmt: diff --git a/tools/vendor/github.com/securego/gosec/v2/rules/rulelist.go b/tools/vendor/github.com/securego/gosec/v2/rules/rulelist.go index d856eccad2..f9ca4f52c4 100644 --- a/tools/vendor/github.com/securego/gosec/v2/rules/rulelist.go +++ b/tools/vendor/github.com/securego/gosec/v2/rules/rulelist.go @@ -91,6 +91,7 @@ func Generate(trackSuppressions bool, filters ...RuleFilter) RuleList { {"G304", "File path provided as taint input", NewReadFile}, {"G305", "File path traversal when extracting zip archive", NewArchive}, {"G306", "Poor file permissions used when writing to a file", NewWritePerms}, + {"G307", "Poor file permissions used when creating a file with os.Create", NewOsCreatePerms}, // crypto {"G401", "Detect the usage of DES, RC4, MD5 or SHA1", NewUsesWeakCryptography}, diff --git a/tools/vendor/github.com/securego/gosec/v2/rules/sql.go b/tools/vendor/github.com/securego/gosec/v2/rules/sql.go index 4085b5d26a..61222bfdb3 100644 --- a/tools/vendor/github.com/securego/gosec/v2/rules/sql.go +++ b/tools/vendor/github.com/securego/gosec/v2/rules/sql.go @@ -98,6 +98,32 @@ func (s *sqlStrConcat) ID() string { return s.MetaData.ID } +// findInjectionInBranch walks diwb a set if expressions, and will create new issues if it finds SQL injections +// This method assumes you've already verified that the branch contains SQL syntax +func (s *sqlStrConcat) findInjectionInBranch(ctx *gosec.Context, branch []ast.Expr) *ast.BinaryExpr { + for _, node := range branch { + be, ok := node.(*ast.BinaryExpr) + if !ok { + continue + } + + operands := gosec.GetBinaryExprOperands(be) + + for _, op := range operands { + if _, ok := op.(*ast.BasicLit); ok { + continue + } + + if ident, ok := op.(*ast.Ident); ok && s.checkObject(ident, ctx) { + continue + } + + return be + } + } + return nil +} + // see if we can figure out what it is func (s *sqlStrConcat) checkObject(n *ast.Ident, c *gosec.Context) bool { if n.Obj != nil { @@ -140,6 +166,28 @@ func (s *sqlStrConcat) checkQuery(call *ast.CallExpr, ctx *gosec.Context) (*issu } } + // Handle the case where an injection occurs as an infixed string concatenation, ie "SELECT * FROM foo WHERE name = '" + os.Args[0] + "' AND 1=1" + if id, ok := query.(*ast.Ident); ok { + var match bool + for _, str := range gosec.GetIdentStringValuesRecursive(id) { + if s.MatchPatterns(str) { + match = true + break + } + } + + if !match { + return nil, nil + } + + switch decl := id.Obj.Decl.(type) { + case *ast.AssignStmt: + if injection := s.findInjectionInBranch(ctx, decl.Rhs); injection != nil { + return ctx.NewIssue(injection, s.ID(), s.What, s.Severity, s.Confidence), nil + } + } + } + return nil, nil } @@ -157,6 +205,7 @@ func (s *sqlStrConcat) Match(n ast.Node, ctx *gosec.Context) (*issue.Issue, erro return s.checkQuery(sqlQueryCall, ctx) } } + return nil, nil } @@ -165,7 +214,7 @@ func NewSQLStrConcat(id string, _ gosec.Config) (gosec.Rule, []ast.Node) { rule := &sqlStrConcat{ sqlStatement: sqlStatement{ patterns: []*regexp.Regexp{ - regexp.MustCompile(`(?i)(SELECT|DELETE|INSERT|UPDATE|INTO|FROM|WHERE) `), + regexp.MustCompile("(?i)(SELECT|DELETE|INSERT|UPDATE|INTO|FROM|WHERE)( |\n|\r|\t)"), }, MetaData: issue.MetaData{ ID: id, diff --git a/tools/vendor/github.com/securego/gosec/v2/rules/subproc.go b/tools/vendor/github.com/securego/gosec/v2/rules/subproc.go index ea50d692d5..1e2cedaa58 100644 --- a/tools/vendor/github.com/securego/gosec/v2/rules/subproc.go +++ b/tools/vendor/github.com/securego/gosec/v2/rules/subproc.go @@ -97,7 +97,7 @@ func (r *subprocess) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { } // isContext checks whether or not the node is a CommandContext call or not -// Thi is required in order to skip the first argument from the check. +// This is required in order to skip the first argument from the check. func (r *subprocess) isContext(n ast.Node, ctx *gosec.Context) bool { selector, indent, err := gosec.GetCallInfo(n, ctx) if err != nil { diff --git a/tools/vendor/github.com/securego/gosec/v2/rules/unsafe.go b/tools/vendor/github.com/securego/gosec/v2/rules/unsafe.go index e1e8d02310..2e2adca7c7 100644 --- a/tools/vendor/github.com/securego/gosec/v2/rules/unsafe.go +++ b/tools/vendor/github.com/securego/gosec/v2/rules/unsafe.go @@ -43,7 +43,7 @@ func (r *usingUnsafe) Match(n ast.Node, c *gosec.Context) (gi *issue.Issue, err func NewUsingUnsafe(id string, _ gosec.Config) (gosec.Rule, []ast.Node) { return &usingUnsafe{ pkg: "unsafe", - calls: []string{"Alignof", "Offsetof", "Sizeof", "Pointer"}, + calls: []string{"Pointer", "String", "StringData", "Slice", "SliceData"}, MetaData: issue.MetaData{ ID: id, What: "Use of unsafe calls should be audited", diff --git a/tools/vendor/github.com/tetafro/godot/.golangci.yml b/tools/vendor/github.com/tetafro/godot/.golangci.yml index 2b799b2653..920135d40a 100644 --- a/tools/vendor/github.com/tetafro/godot/.golangci.yml +++ b/tools/vendor/github.com/tetafro/godot/.golangci.yml @@ -19,7 +19,6 @@ linters: - unused - varcheck - bodyclose - - depguard - dogsled - dupl - funlen @@ -51,7 +50,7 @@ linters: linters-settings: godot: - check-all: true + scope: toplevel issues: exclude-use-default: false diff --git a/tools/vendor/github.com/tetafro/godot/README.md b/tools/vendor/github.com/tetafro/godot/README.md index 3f97b0e395..6b2e530b93 100644 --- a/tools/vendor/github.com/tetafro/godot/README.md +++ b/tools/vendor/github.com/tetafro/godot/README.md @@ -1,7 +1,7 @@ # godot [![License](http://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://raw.githubusercontent.com/tetafro/godot/master/LICENSE) -[![Github CI](https://img.shields.io/github/workflow/status/tetafro/godot/Test)](https://github.com/tetafro/godot/actions?query=workflow%3ATest) +[![Github CI](https://img.shields.io/github/actions/workflow/status/tetafro/godot/push.yml)](https://github.com/tetafro/godot/actions) [![Go Report](https://goreportcard.com/badge/github.com/tetafro/godot)](https://goreportcard.com/report/github.com/tetafro/godot) [![Codecov](https://codecov.io/gh/tetafro/godot/branch/master/graph/badge.svg)](https://codecov.io/gh/tetafro/godot) @@ -21,7 +21,7 @@ end of the last sentence if needed. Build from source ```sh -go get -u github.com/tetafro/godot/cmd/godot +go install github.com/tetafro/godot/cmd/godot@latest ``` or download binary from [releases page](https://github.com/tetafro/godot/releases). diff --git a/tools/vendor/github.com/tetafro/godot/checks.go b/tools/vendor/github.com/tetafro/godot/checks.go index cba54f310c..f5471cdf79 100644 --- a/tools/vendor/github.com/tetafro/godot/checks.go +++ b/tools/vendor/github.com/tetafro/godot/checks.go @@ -240,6 +240,9 @@ func isSpecialBlock(comment string) bool { strings.Contains(comment, "#define")) { return true } + if strings.HasPrefix(comment, "// Output: ") { + return true + } return false } diff --git a/tools/vendor/github.com/tetafro/godot/getters.go b/tools/vendor/github.com/tetafro/godot/getters.go index 6153772bdf..1a47c824ff 100644 --- a/tools/vendor/github.com/tetafro/godot/getters.go +++ b/tools/vendor/github.com/tetafro/godot/getters.go @@ -5,7 +5,7 @@ import ( "fmt" "go/ast" "go/token" - "io/ioutil" + "os" "regexp" "strings" ) @@ -200,10 +200,10 @@ func (pf *parsedFile) getAllComments(exclude []*regexp.Regexp) []comment { return comments } -// getText extracts text from comment. If comment is a special block +// getText extracts text from comment. If the comment is a special block // (e.g., CGO code), a block of empty lines is returned. If comment contains // special lines (e.g., tags or indented code examples), they are replaced -// with `specialReplacer` to skip checks for it. +// with `specialReplacer` to skip checks for them. // The result can be multiline. func getText(comment *ast.CommentGroup, exclude []*regexp.Regexp) (s string) { if len(comment.List) == 1 && @@ -241,10 +241,10 @@ func getText(comment *ast.CommentGroup, exclude []*regexp.Regexp) (s string) { return s[:len(s)-1] // trim last "\n" } -// readFile reads file and returns it's lines as strings. +// readFile reads file and returns its lines as strings. func readFile(file *ast.File, fset *token.FileSet) ([]string, error) { fname := fset.File(file.Package) - f, err := ioutil.ReadFile(fname.Name()) + f, err := os.ReadFile(fname.Name()) if err != nil { return nil, err } diff --git a/tools/vendor/github.com/tetafro/godot/godot.go b/tools/vendor/github.com/tetafro/godot/godot.go index 3a360a214b..df9271296a 100644 --- a/tools/vendor/github.com/tetafro/godot/godot.go +++ b/tools/vendor/github.com/tetafro/godot/godot.go @@ -6,7 +6,6 @@ import ( "fmt" "go/ast" "go/token" - "io/ioutil" "os" "regexp" "sort" @@ -69,7 +68,7 @@ func Run(file *ast.File, fset *token.FileSet, settings Settings) ([]Issue, error // Fix fixes all issues and returns new version of file content. func Fix(path string, file *ast.File, fset *token.FileSet, settings Settings) ([]byte, error) { // Read file - content, err := ioutil.ReadFile(path) // nolint: gosec + content, err := os.ReadFile(path) // nolint: gosec if err != nil { return nil, fmt.Errorf("read file: %v", err) } @@ -102,7 +101,7 @@ func Fix(path string, file *ast.File, fset *token.FileSet, settings Settings) ([ return fixed, nil } -// Replace rewrites original file with it's fixed version. +// Replace rewrites original file with its fixed version. func Replace(path string, file *ast.File, fset *token.FileSet, settings Settings) error { info, err := os.Stat(path) if err != nil { @@ -115,7 +114,7 @@ func Replace(path string, file *ast.File, fset *token.FileSet, settings Settings return fmt.Errorf("fix issues: %v", err) } - if err := ioutil.WriteFile(path, fixed, mode); err != nil { + if err := os.WriteFile(path, fixed, mode); err != nil { return fmt.Errorf("write file: %v", err) } return nil diff --git a/tools/vendor/github.com/ultraware/funlen/README.md b/tools/vendor/github.com/ultraware/funlen/README.md index aaf348521c..af2187694e 100644 --- a/tools/vendor/github.com/ultraware/funlen/README.md +++ b/tools/vendor/github.com/ultraware/funlen/README.md @@ -1,9 +1,47 @@ # Funlen linter -Funlen is a linter that checks for long functions. It can checks both on the number of lines and the number of statements. +Funlen is a linter that checks for long functions. It can check both on the number of lines and the number of statements. The default limits are 60 lines and 40 statements. You can configure these. -## Installation guide +## Description + +The intent for the funlen linter is to fit a function within one screen. If you need to scroll through a long function, tracing variables back to their definition or even just finding matching brackets can become difficult. + +Besides checking lines there's also a separate check for the number of statements, which gives a clearer idea of how much is actually being done in a function. + +The default values are used internally, but might to be adjusted for your specific environment. + +## Installation Funlen is included in [golangci-lint](https://github.com/golangci/golangci-lint/). Install it and enable funlen. + +# Exclude for tests + +golangci-lint offers a way to exclude linters in certain cases. More info can be found here: https://golangci-lint.run/usage/configuration/#issues-configuration. + +## Disable funlen for \_test.go files + +You can utilize the issues configuration in `.golangci.yml` to exclude the funlen linter for all test files: + +```yaml +issues: + exclude-rules: + # disable funlen for all _test.go files + - path: _test.go + linters: + - funlen +``` + +## Disable funlen only for Test funcs + +If you want to keep funlen enabled for example in helper functions in test files but disable it specifically for Test funcs, you can use the following configuration: + +```yaml +issues: + exclude-rules: + # disable funlen for test funcs + - source: "^func Test" + linters: + - funlen +``` diff --git a/tools/vendor/github.com/ultraware/funlen/main.go b/tools/vendor/github.com/ultraware/funlen/main.go index 2ba3530027..b68ddb926f 100644 --- a/tools/vendor/github.com/ultraware/funlen/main.go +++ b/tools/vendor/github.com/ultraware/funlen/main.go @@ -13,7 +13,7 @@ const ( ) // Run runs this linter on the provided code -func Run(file *ast.File, fset *token.FileSet, lineLimit, stmtLimit int) []Message { +func Run(file *ast.File, fset *token.FileSet, lineLimit int, stmtLimit int, ignoreComments bool) []Message { if lineLimit == 0 { lineLimit = defaultLineLimit } @@ -21,6 +21,8 @@ func Run(file *ast.File, fset *token.FileSet, lineLimit, stmtLimit int) []Messag stmtLimit = defaultStmtLimit } + cmap := ast.NewCommentMap(fset, file, file.Comments) + var msgs []Message for _, f := range file.Decls { decl, ok := f.(*ast.FuncDecl) @@ -36,7 +38,7 @@ func Run(file *ast.File, fset *token.FileSet, lineLimit, stmtLimit int) []Messag } if lineLimit > 0 { - if lines := getLines(fset, decl); lines > lineLimit { + if lines := getLines(fset, decl, cmap.Filter(decl), ignoreComments); lines > lineLimit { msgs = append(msgs, makeLineMessage(fset, decl.Name, lines, lineLimit)) } } @@ -65,8 +67,26 @@ func makeStmtMessage(fset *token.FileSet, funcInfo *ast.Ident, stmts, stmtLimit } } -func getLines(fset *token.FileSet, f *ast.FuncDecl) int { // nolint: interfacer - return fset.Position(f.End()).Line - fset.Position(f.Pos()).Line - 1 +func getLines(fset *token.FileSet, f *ast.FuncDecl, cmap ast.CommentMap, ignoreComments bool) int { // nolint: interfacer + var lineCount int + var commentCount int + + lineCount = fset.Position(f.End()).Line - fset.Position(f.Pos()).Line - 1 + + if !ignoreComments { + return lineCount + } + + for _, c := range cmap.Comments() { + // If the CommenGroup's lines are inside the function + // count how many comments are in the CommentGroup + if (fset.Position(c.Pos()).Line > fset.Position(f.Pos()).Line) && + (fset.Position(c.End()).Line < fset.Position(f.End()).Line) { + commentCount += len(c.List) + } + } + + return lineCount - commentCount } func parseStmts(s []ast.Stmt) (total int) { diff --git a/tools/vendor/github.com/uudashr/gocognit/README.md b/tools/vendor/github.com/uudashr/gocognit/README.md index 1e028c7897..57f31cf740 100644 --- a/tools/vendor/github.com/uudashr/gocognit/README.md +++ b/tools/vendor/github.com/uudashr/gocognit/README.md @@ -1,6 +1,6 @@ [![GoDoc](https://godoc.org/github.com/uudashr/gocognit?status.svg)](https://godoc.org/github.com/uudashr/gocognit) # Gocognit -Gocognit calculates cognitive complexities of functions in Go source code. A measurement of how hard does the code is intuitively to understand. +Gocognit calculates cognitive complexities of functions (and methods) in Go source code. A measurement of how hard does the code is intuitively to understand. ## Understanding the complexity @@ -37,10 +37,10 @@ func GetWords(number int) string { As you see above codes are the same, but the second code are easier to understand, that is why the cognitive complexity score are lower compare to the first one. -## Comparison with cyclometic complexity +## Comparison with cyclomatic complexity ### Example 1 -#### Cyclometic complexity +#### Cyclomatic complexity ```go func GetWords(number int) string { // +1 switch number { @@ -160,16 +160,40 @@ $ go get github.com/uudashr/gocognit/cmd/gocognit ``` $ gocognit Calculate cognitive complexities of Go functions. + Usage: - gocognit [flags] ... + + gocognit [ ...] ... + Flags: - -over N show functions with complexity > N only and - return exit code 1 if the set is non-empty - -top N show the top N most complex functions only - -avg show the average complexity over all functions, - not depending on whether -over or -top are set -The output fields for each line are: - + + -over N show functions with complexity > N only + and return exit code 1 if the output is non-empty + -top N show the top N most complex functions only + -avg show the average complexity over all functions, + not depending on whether -over or -top are set + -json encode the output as JSON + -f format string the format to use + (default "{{.PkgName}}.{{.FuncName}}:{{.Complexity}}:{{.Pos}}") + +The (default) output fields for each line are: + + + +The (default) output fields for each line are: + + {{.Complexity}} {{.PkgName}} {{.FuncName}} {{.Pos}} + +or equal to + +The struct being passed to the template is: + + type Stat struct { + PkgName string + FuncName string + Complexity int + Pos token.Position + } ``` Examples: @@ -180,6 +204,7 @@ $ gocognit main.go $ gocognit -top 10 src/ $ gocognit -over 25 docker $ gocognit -avg . +$ gocognit -ignore "_test|testdata" . ``` The output fields for each line are: @@ -187,6 +212,15 @@ The output fields for each line are: ``` +## Ignore individual functions +Ignore individual functions by specifying `gocognit:ignore` directive. +```go +//gocognit:ignore +func IgnoreMe() { + // ... +} +``` + ## Related project - [Gocyclo](https://github.com/fzipp/gocyclo) where the code are based on. - [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf) white paper by G. Ann Campbell. \ No newline at end of file diff --git a/tools/vendor/github.com/uudashr/gocognit/gocognit.go b/tools/vendor/github.com/uudashr/gocognit/gocognit.go index 1d539ee780..2bba2eb4f0 100644 --- a/tools/vendor/github.com/uudashr/gocognit/gocognit.go +++ b/tools/vendor/github.com/uudashr/gocognit/gocognit.go @@ -26,6 +26,11 @@ func (s Stat) String() string { func ComplexityStats(f *ast.File, fset *token.FileSet, stats []Stat) []Stat { for _, decl := range f.Decls { if fn, ok := decl.(*ast.FuncDecl); ok { + d := parseDirective(fn.Doc) + if d.Ignore { + continue + } + stats = append(stats, Stat{ PkgName: f.Name.Name, FuncName: funcName(fn), @@ -37,6 +42,24 @@ func ComplexityStats(f *ast.File, fset *token.FileSet, stats []Stat) []Stat { return stats } +type directive struct { + Ignore bool +} + +func parseDirective(doc *ast.CommentGroup) directive { + if doc == nil { + return directive{} + } + + for _, c := range doc.List { + if c.Text == "//gocognit:ignore" { + return directive{Ignore: true} + } + } + + return directive{} +} + // funcName returns the name representation of a function or method: // "(Type).Name" for methods or simply "Name" for functions. func funcName(fn *ast.FuncDecl) string { @@ -272,7 +295,7 @@ func (v *complexityVisitor) visitBranchStmt(n *ast.BranchStmt) ast.Visitor { } func (v *complexityVisitor) visitBinaryExpr(n *ast.BinaryExpr) ast.Visitor { - if (n.Op == token.LAND || n.Op == token.LOR) && !v.isCalculated(n) { + if isBinaryLogicalOp(n.Op) && !v.isCalculated(n) { ops := v.collectBinaryOps(n) var lastOp token.Token @@ -299,15 +322,10 @@ func (v *complexityVisitor) visitCallExpr(n *ast.CallExpr) ast.Visitor { func (v *complexityVisitor) collectBinaryOps(exp ast.Expr) []token.Token { v.markCalculated(exp) - switch exp := exp.(type) { - case *ast.BinaryExpr: + if exp, ok := exp.(*ast.BinaryExpr); ok { return mergeBinaryOps(v.collectBinaryOps(exp.X), exp.Op, v.collectBinaryOps(exp.Y)) - case *ast.ParenExpr: - // interest only on what inside paranthese - return v.collectBinaryOps(exp.X) - default: - return []token.Token{} } + return nil } func (v *complexityVisitor) incIfComplexity(n *ast.IfStmt) { @@ -320,16 +338,18 @@ func (v *complexityVisitor) incIfComplexity(n *ast.IfStmt) { func mergeBinaryOps(x []token.Token, op token.Token, y []token.Token) []token.Token { var out []token.Token - if len(x) != 0 { - out = append(out, x...) - } - out = append(out, op) - if len(y) != 0 { - out = append(out, y...) + out = append(out, x...) + if isBinaryLogicalOp(op) { + out = append(out, op) } + out = append(out, y...) return out } +func isBinaryLogicalOp(op token.Token) bool { + return op == token.LAND || op == token.LOR +} + const Doc = `Find complex function using cognitive complexity calculation. The gocognit analysis reports functions or methods which the complexity is over @@ -359,13 +379,19 @@ func run(pass *analysis.Pass) (interface{}, error) { (*ast.FuncDecl)(nil), } inspect.Preorder(nodeFilter, func(n ast.Node) { - fnDecl := n.(*ast.FuncDecl) + funcDecl := n.(*ast.FuncDecl) + + d := parseDirective(funcDecl.Doc) + if d.Ignore { + return + } + + fnName := funcName(funcDecl) - fnName := funcName(fnDecl) - fnComplexity := Complexity(fnDecl) + fnComplexity := Complexity(funcDecl) if fnComplexity > over { - pass.Reportf(fnDecl.Pos(), "cognitive complexity %d of func %s is high (> %d)", fnComplexity, fnName, over) + pass.Reportf(funcDecl.Pos(), "cognitive complexity %d of func %s is high (> %d)", fnComplexity, fnName, over) } }) diff --git a/tools/vendor/github.com/xen0n/gosmopolitan/README.md b/tools/vendor/github.com/xen0n/gosmopolitan/README.md index 93e3701e5d..86a5e64e00 100644 --- a/tools/vendor/github.com/xen0n/gosmopolitan/README.md +++ b/tools/vendor/github.com/xen0n/gosmopolitan/README.md @@ -54,7 +54,9 @@ following characteristics, and may not suit your particular project's needs: * Originally developed for an audience using non-Latin writing system(s), * Returns bare strings intended for humans containing such non-Latin characters, and -* May occasionally (or frequently) refer to the local timezone. +* May occasionally (or frequently) refer to the system timezone, but is + architecturally forbidden/discouraged to just treat the system timezone as + the reference timezone. For example, the lints may prove valuable if you're revamping a web service originally targetting the Chinese market (hence producing strings with Chinese @@ -64,71 +66,18 @@ will output nothing. ## golangci-lint integration -`gosmopolitan` is not integrated into [`golangci-lint`][gcl-home] yet, but -you can nevertheless run it [as a custom plugin][gcl-plugin]. +`gosmopolitan` support [has been merged][gcl-pr] into [`golangci-lint`][gcl-home], +and will be usable out-of-the-box in golangci-lint v1.53.0 or later. +Due to the opinionated coding style this linter advocates and checks for, if +you have `enable-all: true` in your `golangci.yml` and your project deals a +lot with Chinese text and/or `time.Local`, then you'll get flooded with lints +when you upgrade to golangci-lint v1.53.0. Just disable this linter (and +better yet, move away from `enable-all: true`) if the style does not suit your +specific use case. + +[gcl-pr]: https://github.com/golangci/golangci-lint/pull/3458 [gcl-home]: https://golangci-lint.run -[gcl-plugin]: https://golangci-lint.run/contributing/new-linters/#how-to-add-a-private-linter-to-golangci-lint - -First make yourself a plugin `.so` file like this: - -```go -// compile this with something like `go build -buildmode=plugin` - -package main - -import ( - "github.com/xen0n/gosmopolitan" - "golang.org/x/tools/go/analysis" -) - -type analyzerPlugin struct{} - -func (analyzerPlugin) GetAnalyzers() []*analysis.Analyzer { - // You can customize the options via gosmopolitan.NewAnalyzerWithConfig - // instead. - return []*analysis.Analyzer{ - gosmopolitan.DefaultAnalyzer, - } -} - -var AnalyzerPlugin analyzerPlugin -``` - -You just need to make sure the `golang.org/x/tools` version used to build the -plugin is consistent with that of your `golangci-lint` binary. (Of course the -`golangci-lint` binary should be built with plugin support enabled too; -notably, [the Homebrew `golangci-lint` is built without plugin support][hb-issue], -so beware of this.) - -[hb-issue]: https://github.com/golangci/golangci-lint/issues/1182 - -|`golangci-lint` version|`gosmopolitan` tag to use| -|-----------------------|-------------------------| -|1.50.x|v1.0.0| - -Then reference it in your `.golangci.yml`, and enable it in the `linters` -section: - -```yaml -linters: - # ... - enable: - # ... - - gosmopolitan - # ... - -linters-settings: - custom: - gosmopolitan: - path: 'path/to/your/plugin.so' - description: 'Report certain i18n/l10n anti-patterns in your Go codebase' - original-url: 'https://github.com/xen0n/gosmopolitan' - # ... -``` - -Then you can `golangci-lint run` and `//nolint:gosmopolitan` as you would -with any other supported linter. ## License diff --git a/tools/vendor/github.com/xen0n/gosmopolitan/README.zh-Hans.md b/tools/vendor/github.com/xen0n/gosmopolitan/README.zh-Hans.md index 7f1b7b7adf..682d10880e 100644 --- a/tools/vendor/github.com/xen0n/gosmopolitan/README.zh-Hans.md +++ b/tools/vendor/github.com/xen0n/gosmopolitan/README.zh-Hans.md @@ -46,7 +46,7 @@ * 项目原先是为使用非拉丁字母书写系统的受众群体开发的, * 项目会返回包含这些非拉丁字母字符的裸的字符串(即,未经处理或变换的), -* 项目可能偶尔(或者经常)引用程序当前运行环境的本地时区。 +* 项目可能偶尔(或者经常)引用程序当前运行环境的系统时区,但项目架构上禁止或不建议把系统时区直接作为业务参考时区使用。 举个例子:如果您在翻新一个本来面向中国用户群体(因此到处都在产生含有汉字的字符串)的 web 服务,以使其更加国际化,这里的 lints 可能会很有价值。 @@ -55,66 +55,14 @@ linter 则什么都不会输出。 ## 与 golangci-lint 集成 -`gosmopolitan` 目前没有集成进上游 [`golangci-lint`][gcl-home],但您仍然可以[以自定义插件的方式][gcl-plugin]使用本项目。 +`gosmopolitan` 支持[已经被合并][gcl-pr]入 [`golangci-lint`][gcl-home] 上游,在 golangci-lint v1.53.0 及以后的版本可以开箱即用。 +[gcl-pr]: https://github.com/golangci/golangci-lint/pull/3458 [gcl-home]: https://golangci-lint.run -[gcl-plugin]: https://golangci-lint.run/contributing/new-linters/#how-to-add-a-private-linter-to-golangci-lint -首先像这样做一个插件 `.so` 文件: - -```go -// 用类似 `go build -buildmode=plugin` 的方式编译 - -package main - -import ( - "github.com/xen0n/gosmopolitan" - "golang.org/x/tools/go/analysis" -) - -type analyzerPlugin struct{} - -func (analyzerPlugin) GetAnalyzers() []*analysis.Analyzer { - // 你可以用 gosmopolitan.NewAnalyzer 来自定义配置。 - return []*analysis.Analyzer{ - gosmopolitan.DefaultAnalyzer, - } -} - -var AnalyzerPlugin analyzerPlugin -``` - -您只需要保证构建时使用的 `golang.org/x/tools` 模块版本和您的 `golangci-lint` -二进制的相应模块版本一致。(当然,`golangci-lint` 二进制也应该包含插件支持; -[Homebrew 的 `golangci-lint` 没有插件支持][hb-issue],尤其需要注意。) - -[hb-issue]: https://github.com/golangci/golangci-lint/issues/1182 - -|`golangci-lint` 版本|对应可用的 `gosmopolitan` tag| -|--------------------|-----------------------------| -|1.50.x|v1.0.0| - -然后在您的 `.golangci.yml` 中引用它,在 `linters` 一节中启用它: - -```yaml -linters: - # ... - enable: - # ... - - gosmopolitan - # ... - -linters-settings: - custom: - gosmopolitan: - path: 'path/to/your/plugin.so' - description: 'Report certain i18n/l10n anti-patterns in your Go codebase' - original-url: 'https://github.com/xen0n/gosmopolitan' - # ... -``` - -这样您就可以像使用其他 linters 一样 `golangci-lint run` 和 -`//nolint:gosmopolitan` 了。 +由于本 linter 倡导和检查的代码风格带有鲜明立场,如果您在 `golangci.yml` 开了 +`enable-all: true` 并且您的项目处理很多中文文本或者 `time.Local`,那么您一旦升级到 +golangci-lint v1.53.0 就将被 lints 淹没。如果这种代码风格不适合您的具体使用场景,直接禁用本 linter(或者更彻底一些,不要 `enable-all: true` 了)就好。 ## 许可证 diff --git a/tools/vendor/github.com/ykadowak/zerologlint/README.md b/tools/vendor/github.com/ykadowak/zerologlint/README.md index 14443e2e3c..b0a5fc0f9f 100644 --- a/tools/vendor/github.com/ykadowak/zerologlint/README.md +++ b/tools/vendor/github.com/ykadowak/zerologlint/README.md @@ -47,5 +47,17 @@ func main() { // 4. Deferred case defer log.Info() // "must be dispatched by Msg or Send method" + + // 5. zerolog.Logger case + logger2 := zerolog.New(os.Stdout) + logger2.Info().Send() + + // 6. Dispatch in other function case + event := log.Info() + dispatcher(event) +} + +func dispatcher(e *zerolog.Event) { + e.Send() } ``` diff --git a/tools/vendor/github.com/ykadowak/zerologlint/zerologlint.go b/tools/vendor/github.com/ykadowak/zerologlint/zerologlint.go index bd588f996a..bec50e52bc 100644 --- a/tools/vendor/github.com/ykadowak/zerologlint/zerologlint.go +++ b/tools/vendor/github.com/ykadowak/zerologlint/zerologlint.go @@ -2,6 +2,7 @@ package zerologlint import ( "go/token" + "go/types" "strings" "golang.org/x/tools/go/analysis" @@ -65,14 +66,11 @@ func inspect(cd callDefer, set *map[posser]struct{}) { // check if it's in github.com/rs/zerolog/log since there's some // functions in github.com/rs/zerolog that returns zerolog.Event - // which should not be included - if isInLogPkg(*c) { + // which should not be included. However, zerolog.Logger receiver is an exception. + if isInLogPkg(*c) || isLoggerRecv(*c) { if isZerologEvent(c.Value) { - // check if this is a new instance of zerolog.Event like logger := log.Error() - // which should be dispatched afterwards at some point - if len(c.Args) == 0 { - (*set)[cd] = struct{}{} - } + // this ssa block should be dispatched afterwards at some point + (*set)[cd] = struct{}{} return } } @@ -81,23 +79,63 @@ func inspect(cd callDefer, set *map[posser]struct{}) { // check if the base is zerolog.Event. // if so, check if the StaticCallee is Send() or Msg(). // if so, remove the arg[0] from the set. + f := c.StaticCallee() + if f == nil { + return + } + if !isDispatchMethod(f) { + shouldReturn := true + for _, p := range f.Params { + if isZerologEvent(p) { + // check if this zerolog.Event as a parameter is dispatched in the function + // TODO: specifically, it can be dispatched in another function that is called in this function, and + // this algorithm cannot track that. But I'm tired of thinking about that for now. + for _, b := range f.Blocks { + for _, instr := range b.Instrs { + switch v := instr.(type) { + case *ssa.Call: + if inspectDispatchInFunction(v.Common()) { + shouldReturn = false + } + case *ssa.Defer: + if inspectDispatchInFunction(v.Common()) { + shouldReturn = false + } + } + } + } + } + } + if shouldReturn { + return + } + } for _, arg := range c.Args { if isZerologEvent(arg) { - if isDispatchMethod(*c) { - val := getRootSsaValue(arg) - // if there's branch, remove both ways from the set - if phi, ok := val.(*ssa.Phi); ok { - for _, edge := range phi.Edges { - delete(*set, edge) - } - } else { - delete(*set, val) + val := getRootSsaValue(arg) + // if there's branch, remove both ways from the set + if phi, ok := val.(*ssa.Phi); ok { + for _, edge := range phi.Edges { + delete(*set, edge) } + } else { + delete(*set, val) } } } } +func inspectDispatchInFunction(cc *ssa.CallCommon) bool { + if isDispatchMethod(cc.StaticCallee()) { + for _, arg := range cc.Args { + if isZerologEvent(arg) { + return true + } + } + } + return false +} + func isInLogPkg(c ssa.CallCommon) bool { switch v := c.Value.(type) { case ssa.Member: @@ -106,9 +144,18 @@ func isInLogPkg(c ssa.CallCommon) bool { return false } return strings.HasSuffix(p.Pkg.Path(), "github.com/rs/zerolog/log") - default: - return false } + return false +} + +func isLoggerRecv(c ssa.CallCommon) bool { + switch f := c.Value.(type) { + case *ssa.Function: + if recv := f.Signature.Recv(); recv != nil { + return strings.HasSuffix(types.TypeString(recv.Type(), nil), "zerolog.Logger") + } + } + return false } func isZerologEvent(v ssa.Value) bool { @@ -116,8 +163,11 @@ func isZerologEvent(v ssa.Value) bool { return strings.HasSuffix(ts, "github.com/rs/zerolog.Event") } -func isDispatchMethod(c ssa.CallCommon) bool { - m := c.StaticCallee().Name() +func isDispatchMethod(f *ssa.Function) bool { + if f == nil { + return false + } + m := f.Name() if m == "Send" || m == "Msg" || m == "Msgf" || m == "MsgFunc" { return true } @@ -127,15 +177,23 @@ func isDispatchMethod(c ssa.CallCommon) bool { func getRootSsaValue(v ssa.Value) ssa.Value { if c, ok := v.(*ssa.Call); ok { v := c.Value() + // When there is no receiver, that's the block of zerolog.Event - // eg. Error() method in log.Error().Str("foo", "bar"). Send() + // eg. Error() method in log.Error().Str("foo", "bar").Send() if len(v.Call.Args) == 0 { return v } + // Even when there is a receiver, if it's a zerolog.Logger instance, return this block + // eg. Info() method in zerolog.New(os.Stdout).Info() + root := v.Call.Args[0] + if !isZerologEvent(root) { + return v + } + // Ok to just return the receiver because all the method in this // chain is zerolog.Event at this point. - return getRootSsaValue(v.Call.Args[0]) + return getRootSsaValue(root) } return v } diff --git a/tools/vendor/gitlab.com/bosi/decorder/.gitignore b/tools/vendor/gitlab.com/bosi/decorder/.gitignore index 7b533f8194..48baa654b0 100644 --- a/tools/vendor/gitlab.com/bosi/decorder/.gitignore +++ b/tools/vendor/gitlab.com/bosi/decorder/.gitignore @@ -1,7 +1,7 @@ /.idea /.env +/.env.example /decorder -/deforder /LICENSES-3RD-PARTY /ytt /yq \ No newline at end of file diff --git a/tools/vendor/gitlab.com/bosi/decorder/.gitlab-ci.yml b/tools/vendor/gitlab.com/bosi/decorder/.gitlab-ci.yml index 1ea3b03f29..73e1273b34 100644 --- a/tools/vendor/gitlab.com/bosi/decorder/.gitlab-ci.yml +++ b/tools/vendor/gitlab.com/bosi/decorder/.gitlab-ci.yml @@ -12,7 +12,7 @@ stages: test: stage: test - image: golang:1.18.4@sha256:9349ed889adb906efa5ebc06485fe1b6a12fb265a01c9266a137bb1352565560 + image: golang:1.21.0@sha256:b490ae1f0ece153648dd3c5d25be59a63f966b5f9e1311245c947de4506981aa before_script: - set -eu - if [[ -f .env.pipeline ]];then cp .env.pipeline .env;fi @@ -27,14 +27,16 @@ test: lint:source-code: stage: test - image: golangci/golangci-lint:v1.47.2-alpine@sha256:10ed4891fdd1f7249f5e39d7c17ea746ce26adada3c05686c6aa31290abcd180 + image: golangci/golangci-lint:v1.54.2-alpine@sha256:e950721f6ae622dcc041f57cc0b61c3a78d4bbfc588facfc8b0166901a9f4848 script: + - apk add make bash + - make settings - '### run linter ###' - golangci-lint run ./... license-check: stage: test - image: golang:1.18.4@sha256:9349ed889adb906efa5ebc06485fe1b6a12fb265a01c9266a137bb1352565560 + image: golang:1.21.0@sha256:b490ae1f0ece153648dd3c5d25be59a63f966b5f9e1311245c947de4506981aa before_script: - set -eu - if [[ -f .env.pipeline ]];then cp .env.pipeline .env;fi @@ -51,7 +53,7 @@ license-check: pages: stage: release - image: golang:1.18.4@sha256:9349ed889adb906efa5ebc06485fe1b6a12fb265a01c9266a137bb1352565560 + image: golang:1.21.0@sha256:b490ae1f0ece153648dd3c5d25be59a63f966b5f9e1311245c947de4506981aa only: - tags script: diff --git a/tools/vendor/gitlab.com/bosi/decorder/README.md b/tools/vendor/gitlab.com/bosi/decorder/README.md index e729549377..5947e5ca2c 100644 --- a/tools/vendor/gitlab.com/bosi/decorder/README.md +++ b/tools/vendor/gitlab.com/bosi/decorder/README.md @@ -1,25 +1,29 @@ # Decorder -A declaration order linter for golang. In case of this tool declarations are `type`, `const`, `var` and `func`. +A declaration order linter for Go. In case of this tool declarations are `type`, `const`, `var` and `func`. ## Rules This linter applies multiple rules where each can be disabled via cli parameter. -| rule | description | cli-options | -|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------| -| declaration order | Enforces the order of global declarations (e.g. all global constants are always defined before variables). You can also define a subset of declarations if you don't want to enforce the order of all of them. | * disable check: `-disable-dec-order-check`
* custom order: `-dec-order var,const,func,type` | -| declaration number | Enforces that the statements const, var and type are only used once per file. You have to use parenthesis to declare e.g multiple global types inside a file. | disable check: `-disable-dec-num-check` | -| init func first | Enforces the init func to be the first function in file. | disable check: `-disable-init-func-first-check` | +| rule | description | cli-options | +|--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| declaration order | Enforces the order of global declarations (e.g. all global constants are always defined before variables).
You can also define a subset of declarations if you don't want to enforce the order of all of them. | * disable all checks: `-disable-dec-order-check`
* disable type checks: `-disable-type-dec-order-check`
* disable const checks: `-disable-const-dec-order-check`
* disable var checks: `-disable-var-dec-order-check`
* custom order: `-dec-order var,const,func,type` | +| declaration number | Enforces that the statements const, var and type are only used once per file. You have to use parenthesis
to declare e.g multiple global types inside a file. | disable check: `-disable-dec-num-check` | +| init func first | Enforces the init func to be the first function in file. | disable check: `-disable-init-func-first-check` | You may find the implementation of the rules inside `analyzer.go`. +Underscore var declarations can be ignored via `-ignore-underscore-vars`. + ## Installation ```shell go install gitlab.com/bosi/decorder/cmd/decorder ``` +You can use the linter via golangci-lint as well: https://golangci-lint.run/usage/linters/#decorder. + ## Usage ```shell @@ -35,6 +39,12 @@ decorder -disable-dec-order-check ./... # disable check for multiple declarations statements decorder -disable-dec-num-check ./... +# disable check for multiple declarations (var only) statements +decorder -disable-var-dec-num-check ./... + # disable check that init func is always first function decorder -disable-init-func-first-check ./... + +# ignore underscore variables for all checks +decorder -ignore-underscore-vars ./... ``` \ No newline at end of file diff --git a/tools/vendor/gitlab.com/bosi/decorder/analyzer.go b/tools/vendor/gitlab.com/bosi/decorder/analyzer.go index 308c96b967..08f82ccc11 100644 --- a/tools/vendor/gitlab.com/bosi/decorder/analyzer.go +++ b/tools/vendor/gitlab.com/bosi/decorder/analyzer.go @@ -1,9 +1,11 @@ package decorder import ( + "fmt" "go/ast" "go/token" "strings" + "sync" "golang.org/x/tools/go/analysis" ) @@ -16,6 +18,17 @@ type ( funcPoss []funcPos } + options struct { + decOrder string + ignoreUnderscoreVars bool + disableDecNumCheck bool + disableTypeDecNumCheck bool + disableConstDecNumCheck bool + disableVarDecNumCheck bool + disableDecOrderCheck bool + disableInitFuncFirstCheck bool + } + funcPos struct { start token.Pos end token.Pos @@ -26,9 +39,15 @@ const ( Name = "decorder" FlagDo = "dec-order" + FlagIuv = "ignore-underscore-vars" FlagDdnc = "disable-dec-num-check" + FlagDtdnc = "disable-type-dec-num-check" + FlagDcdnc = "disable-const-dec-num-check" + FlagDvdnc = "disable-var-dec-num-check" FlagDdoc = "disable-dec-order-check" FlagDiffc = "disable-init-func-first-check" + + defaultDecOrder = "type,const,var,func" ) var ( @@ -38,27 +57,45 @@ var ( Run: run, } - decOrder string - disableDecNumCheck bool - disableDecOrderCheck bool - disableInitFuncFirstCheck bool + opts = options{} tokens = []token.Token{token.TYPE, token.CONST, token.VAR, token.FUNC} + + decNumConf = map[token.Token]bool{ + token.TYPE: false, + token.CONST: false, + token.VAR: false, + } + decLock sync.Mutex ) //nolint:lll func init() { - Analyzer.Flags.StringVar(&decOrder, FlagDo, "type,const,var,func", "define the required order of types, constants, variables and functions declarations inside a file") - Analyzer.Flags.BoolVar(&disableDecNumCheck, FlagDdnc, false, "option to disable check for number of e.g. var declarations inside file") - Analyzer.Flags.BoolVar(&disableDecOrderCheck, FlagDdoc, false, "option to disable check for order of declarations inside file") - Analyzer.Flags.BoolVar(&disableInitFuncFirstCheck, FlagDiffc, false, "option to disable check that init function is always first function in file") + Analyzer.Flags.StringVar(&opts.decOrder, FlagDo, defaultDecOrder, "define the required order of types, constants, variables and functions declarations inside a file") + Analyzer.Flags.BoolVar(&opts.ignoreUnderscoreVars, FlagIuv, false, "option to ignore underscore vars for dec order and dec num check") + Analyzer.Flags.BoolVar(&opts.disableDecNumCheck, FlagDdnc, false, "option to disable (all) checks for number of declarations inside file") + Analyzer.Flags.BoolVar(&opts.disableTypeDecNumCheck, FlagDtdnc, false, "option to disable check for number of type declarations inside file") + Analyzer.Flags.BoolVar(&opts.disableConstDecNumCheck, FlagDcdnc, false, "option to disable check for number of const declarations inside file") + Analyzer.Flags.BoolVar(&opts.disableVarDecNumCheck, FlagDvdnc, false, "option to disable check for number of var declarations inside file") + Analyzer.Flags.BoolVar(&opts.disableDecOrderCheck, FlagDdoc, false, "option to disable check for order of declarations inside file") + Analyzer.Flags.BoolVar(&opts.disableInitFuncFirstCheck, FlagDiffc, false, "option to disable check that init function is always first function in file") +} + +func initDec() { + decLock.Lock() + decNumConf[token.TYPE] = opts.disableTypeDecNumCheck + decNumConf[token.CONST] = opts.disableConstDecNumCheck + decNumConf[token.VAR] = opts.disableVarDecNumCheck + decLock.Unlock() } func run(pass *analysis.Pass) (interface{}, error) { + initDec() + for _, f := range pass.Files { ast.Inspect(f, runDeclNumAndDecOrderCheck(pass)) - if !disableInitFuncFirstCheck { + if !opts.disableInitFuncFirstCheck { ast.Inspect(f, runInitFuncFirstCheck(pass)) } } @@ -90,7 +127,7 @@ func runInitFuncFirstCheck(pass *analysis.Pass) func(ast.Node) bool { func runDeclNumAndDecOrderCheck(pass *analysis.Pass) func(ast.Node) bool { dnc := newDecNumChecker() - if disableDecNumCheck && disableDecOrderCheck { + if opts.disableDecNumCheck && opts.disableDecOrderCheck { return func(n ast.Node) bool { return true } @@ -113,7 +150,7 @@ func runDeclNumAndDecOrderCheck(pass *analysis.Pass) func(ast.Node) bool { dnc.handleGenDecl(gd, pass) - if !disableDecOrderCheck { + if !opts.disableDecOrderCheck { dnc.handleDecOrderCheck(gd, pass) } @@ -134,7 +171,7 @@ func newDecNumChecker() decNumChecker { dnc.tokenMap[t.String()] = t } - for _, do := range strings.Split(decOrder, ",") { + for _, do := range strings.Split(opts.decOrder, ",") { dnc.decOrder = append(dnc.decOrder, strings.TrimSpace(do)) } @@ -159,19 +196,33 @@ func (dnc decNumChecker) isToLate(t token.Token) (string, bool) { func (dnc *decNumChecker) handleGenDecl(gd *ast.GenDecl, pass *analysis.Pass) { for _, t := range tokens { if gd.Tok == t { + if opts.ignoreUnderscoreVars && declName(gd) == "_" { + continue + } + dnc.tokenCounts[t]++ - if !disableDecNumCheck && dnc.tokenCounts[t] > 1 { + if !opts.disableDecNumCheck && !decNumConf[t] && dnc.tokenCounts[t] > 1 { pass.Reportf(gd.Pos(), "multiple \"%s\" declarations are not allowed; use parentheses instead", t.String()) } } } } +func declName(gd *ast.GenDecl) string { + for _, spec := range gd.Specs { + s, ok := spec.(*ast.ValueSpec) + if ok && len(s.Names) > 0 && s.Names[0] != nil { + return s.Names[0].Name + } + } + return "" +} + func (dnc decNumChecker) handleDecOrderCheck(gd *ast.GenDecl, pass *analysis.Pass) { l, c := dnc.isToLate(gd.Tok) if !c { - pass.Reportf(gd.Pos(), "%s must not be placed after %s", gd.Tok.String(), l) + pass.Reportf(gd.Pos(), fmtWrongOrderMsg(gd.Tok.String(), l)) } } @@ -189,12 +240,16 @@ func (dnc *decNumChecker) handleFuncDec(fd *ast.FuncDecl, pass *analysis.Pass) b dnc.tokenCounts[token.FUNC]++ - if !disableDecOrderCheck { + if !opts.disableDecOrderCheck { l, c := dnc.isToLate(token.FUNC) if !c { - pass.Reportf(fd.Pos(), "%s must not be placed after %s", token.FUNC.String(), l) + pass.Reportf(fd.Pos(), fmtWrongOrderMsg(token.FUNC.String(), l)) } } return true } + +func fmtWrongOrderMsg(target string, notAfter string) string { + return fmt.Sprintf("%s must not be placed after %s (desired order: %s)", target, notAfter, opts.decOrder) +} diff --git a/tools/vendor/gitlab.com/bosi/decorder/renovate.json b/tools/vendor/gitlab.com/bosi/decorder/renovate.json index 5c0388c596..60041578ce 100644 --- a/tools/vendor/gitlab.com/bosi/decorder/renovate.json +++ b/tools/vendor/gitlab.com/bosi/decorder/renovate.json @@ -1,29 +1,7 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ - "config:base", - "group:allNonMajor", - ":automergePatch", - ":automergeMinor", - ":automergeLinters", - ":automergeTesters", - ":automergeTypes" - ], - "enabled": true, - "dependencyDashboard": false, - "separateMajorMinor": true, - "separateMultipleMajor": false, - "prHourlyLimit": 2, - "prConcurrentLimit": 10, - "labels": [ - "depUpdate" - ], - "updateLockFiles": true, - "docker": { - "pinDigests": true - }, - "postUpdateOptions": [ - "gomodTidy" + "gitlab>bosi/renovate-configs//configs/golang", + "gitlab>bosi/renovate-configs//configs/automerge" ] } - diff --git a/tools/vendor/go-simpler.org/sloglint/.golangci.yml b/tools/vendor/go-simpler.org/sloglint/.golangci.yml new file mode 100644 index 0000000000..ef926a0562 --- /dev/null +++ b/tools/vendor/go-simpler.org/sloglint/.golangci.yml @@ -0,0 +1,22 @@ +linters: + disable-all: true + enable: + # enabled by default: + - errcheck + - gosimple + - govet + - ineffassign + - staticcheck + - unused + # disabled by default: + - gocritic + - gofumpt + +linters-settings: + gocritic: + enabled-tags: + - diagnostic + - style + - performance + - experimental + - opinionated diff --git a/tools/vendor/go-simpler.org/sloglint/.goreleaser.yml b/tools/vendor/go-simpler.org/sloglint/.goreleaser.yml new file mode 100644 index 0000000000..d31ea11d39 --- /dev/null +++ b/tools/vendor/go-simpler.org/sloglint/.goreleaser.yml @@ -0,0 +1,18 @@ +builds: + - main: ./cmd/sloglint + env: + - CGO_ENABLED=0 + flags: + - -trimpath + ldflags: + - -s -w -X main.version={{.Version}} + targets: + - darwin_amd64 + - darwin_arm64 + - linux_amd64 + - windows_amd64 + +archives: + - format_overrides: + - goos: windows + format: zip diff --git a/tools/vendor/go-simpler.org/sloglint/LICENSE b/tools/vendor/go-simpler.org/sloglint/LICENSE new file mode 100644 index 0000000000..a612ad9813 --- /dev/null +++ b/tools/vendor/go-simpler.org/sloglint/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/tools/vendor/go-simpler.org/sloglint/README.md b/tools/vendor/go-simpler.org/sloglint/README.md new file mode 100644 index 0000000000..8f26f3aa74 --- /dev/null +++ b/tools/vendor/go-simpler.org/sloglint/README.md @@ -0,0 +1,102 @@ +# sloglint + +[![checks](https://github.com/go-simpler/sloglint/actions/workflows/checks.yml/badge.svg)](https://github.com/go-simpler/sloglint/actions/workflows/checks.yml) +[![pkg.go.dev](https://pkg.go.dev/badge/go-simpler.org/sloglint.svg)](https://pkg.go.dev/go-simpler.org/sloglint) +[![goreportcard](https://goreportcard.com/badge/go-simpler.org/sloglint)](https://goreportcard.com/report/go-simpler.org/sloglint) +[![codecov](https://codecov.io/gh/go-simpler/sloglint/branch/main/graph/badge.svg)](https://codecov.io/gh/go-simpler/sloglint) + +A Go linter that ensures consistent code style when using `log/slog`. + +## 📌 About + +The `log/slog` API allows two different types of arguments: key-value pairs and attributes. +People may have different opinions about which one is better, +but nobody probably wants to mix them up because it makes the code harder to read. + +```go +slog.Info("a user has logged in", "user_id", 42, slog.String("ip_address", "192.0.2.0")) // ugh +``` + +`sloglint` finds such function calls and checks that all the arguments are either key-value pairs or attributes. +The linter has several options, so you can adjust it to your own code style. + +## 🚀 Features + +* Forbid mixing key-value pairs and attributes within a single function call (default) +* Enforce using either key-value pairs or attributes for the entire project (optional) +* Enforce using constants instead of raw keys (optional) +* Enforce putting arguments on separate lines (optional) + +## 📦 Install + +Download a prebuilt binary from the [Releases][1] page. + +## 📋 Usage + +```shell +sloglint [flags] ./... +``` + +### Key-value pairs only + +The `-kv-only` flag causes `sloglint` to report any use of attributes. + +```go +slog.Info("a user has logged in", slog.Int("user_id", 42)) // sloglint: attributes should not be used +``` + +### Attributes only + +In contrast, the `-attr-only` flag causes `sloglint` to report any use of key-value pairs. + +```go +slog.Info("a user has logged in", "user_id", 42) // sloglint: key-value pairs should not be used +``` + +### No raw keys + +To prevent typos, you may want to forbid the use of raw keys altogether. +The `-no-raw-keys` flag causes `sloglint` to report the use of strings as keys (including `slog.Attr` calls, e.g. `slog.Int("user_id", 42)`). + +```go +slog.Info("a user has logged in", "user_id", 42) // sloglint: raw keys should not be used +``` + +This report can be fixed by using either constants... + +```go +const UserId = "user_id" + +slog.Info("a user has logged in", UserId, 42) +``` + +...or custom `slog.Attr` constructors. + +```go +func UserId(value int) slog.Attr { return slog.Int("user_id", value) } + +slog.Info("a user has logged in", UserId(42)) +``` + +> 💡 Such helpers can be automatically generated for you by the [`sloggen`][2] tool. Give it a try too! + +### Arguments on separate lines + +To improve code readability, you may want to put arguments on separate lines, especially when using key-value pairs. +The `-args-on-sep-lines` flag causes `sloglint` to report 2+ arguments on the same line. + +```go +slog.Info("a user has logged in", "user_id", 42, "ip_address", "192.0.2.0") // sloglint: arguments should be put on separate lines +``` + +This report can be fixed by reformatting the code. + +```go +slog.Info("a user has logged in", + "user_id", 42, + "ip_address", "192.0.2.0", +) +``` + +[1]: https://github.com/go-simpler/sloglint/releases +[2]: https://github.com/go-simpler/sloggen diff --git a/tools/vendor/go-simpler.org/sloglint/sloglint.go b/tools/vendor/go-simpler.org/sloglint/sloglint.go new file mode 100644 index 0000000000..4e45206985 --- /dev/null +++ b/tools/vendor/go-simpler.org/sloglint/sloglint.go @@ -0,0 +1,226 @@ +// Package sloglint implements the sloglint analyzer. +package sloglint + +import ( + "errors" + "flag" + "go/ast" + "go/token" + "go/types" + "strconv" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" + "golang.org/x/tools/go/types/typeutil" +) + +// Options are options for the sloglint analyzer. +type Options struct { + KVOnly bool // Enforce using key-value pairs only (incompatible with AttrOnly). + AttrOnly bool // Enforce using attributes only (incompatible with KVOnly). + NoRawKeys bool // Enforce using constants instead of raw keys. + ArgsOnSepLines bool // Enforce putting arguments on separate lines. +} + +// New creates a new sloglint analyzer. +func New(opts *Options) *analysis.Analyzer { + if opts == nil { + opts = new(Options) + } + return &analysis.Analyzer{ + Name: "sloglint", + Doc: "ensure consistent code style when using log/slog", + Flags: flags(opts), + Requires: []*analysis.Analyzer{inspect.Analyzer}, + Run: func(pass *analysis.Pass) (any, error) { + if opts.KVOnly && opts.AttrOnly { + return nil, errors.New("sloglint: incompatible options provided") + } + run(pass, opts) + return nil, nil + }, + } +} + +func flags(opts *Options) flag.FlagSet { + fs := flag.NewFlagSet("sloglint", flag.ContinueOnError) + + boolVar := func(value *bool, name, usage string) { + fs.Func(name, usage, func(s string) error { + v, err := strconv.ParseBool(s) + *value = v + return err + }) + } + + boolVar(&opts.KVOnly, "kv-only", "enforce using key-value pairs only (incompatible with -attr-only)") + boolVar(&opts.AttrOnly, "attr-only", "enforce using attributes only (incompatible with -kv-only)") + boolVar(&opts.NoRawKeys, "no-raw-keys", "enforce using constants instead of raw keys") + boolVar(&opts.ArgsOnSepLines, "args-on-sep-lines", "enforce putting arguments on separate lines") + + return *fs +} + +var slogFuncs = map[string]int{ // funcName:argsPos + "log/slog.Log": 3, + "log/slog.Debug": 1, + "log/slog.Info": 1, + "log/slog.Warn": 1, + "log/slog.Error": 1, + "log/slog.DebugContext": 2, + "log/slog.InfoContext": 2, + "log/slog.WarnContext": 2, + "log/slog.ErrorContext": 2, + "(*log/slog.Logger).Log": 3, + "(*log/slog.Logger).Debug": 1, + "(*log/slog.Logger).Info": 1, + "(*log/slog.Logger).Warn": 1, + "(*log/slog.Logger).Error": 1, + "(*log/slog.Logger).DebugContext": 2, + "(*log/slog.Logger).InfoContext": 2, + "(*log/slog.Logger).WarnContext": 2, + "(*log/slog.Logger).ErrorContext": 2, +} + +var attrFuncs = map[string]struct{}{ + "log/slog.String": {}, + "log/slog.Int64": {}, + "log/slog.Int": {}, + "log/slog.Uint64": {}, + "log/slog.Float64": {}, + "log/slog.Bool": {}, + "log/slog.Time": {}, + "log/slog.Duration": {}, + "log/slog.Group": {}, + "log/slog.Any": {}, +} + +func run(pass *analysis.Pass, opts *Options) { + visit := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + filter := []ast.Node{(*ast.CallExpr)(nil)} + + visit.Preorder(filter, func(node ast.Node) { + call := node.(*ast.CallExpr) + + fn := typeutil.StaticCallee(pass.TypesInfo, call) + if fn == nil { + return + } + + argsPos, ok := slogFuncs[fn.FullName()] + if !ok { + return + } + + // NOTE: we assume that the arguments have already been validated by govet. + args := call.Args[argsPos:] + if len(args) == 0 { + return + } + + var keys []ast.Expr + var attrs []ast.Expr + + for i := 0; i < len(args); i++ { + typ := pass.TypesInfo.TypeOf(args[i]) + if typ == nil { + continue + } + switch typ.String() { + case "string": + keys = append(keys, args[i]) + i++ // skip the value. + case "log/slog.Attr": + attrs = append(attrs, args[i]) + } + } + + switch { + case opts.KVOnly && len(attrs) > 0: + pass.Reportf(call.Pos(), "attributes should not be used") + case opts.AttrOnly && len(attrs) < len(args): + pass.Reportf(call.Pos(), "key-value pairs should not be used") + case 0 < len(attrs) && len(attrs) < len(args): + pass.Reportf(call.Pos(), "key-value pairs and attributes should not be mixed") + } + + if opts.NoRawKeys && rawKeysUsed(pass.TypesInfo, keys, attrs) { + pass.Reportf(call.Pos(), "raw keys should not be used") + } + if opts.ArgsOnSepLines && argsOnSameLine(pass.Fset, call, keys, attrs) { + pass.Reportf(call.Pos(), "arguments should be put on separate lines") + } + }) +} + +func rawKeysUsed(info *types.Info, keys, attrs []ast.Expr) bool { + isConst := func(expr ast.Expr) bool { + ident, ok := expr.(*ast.Ident) + return ok && ident.Obj != nil && ident.Obj.Kind == ast.Con + } + + for _, key := range keys { + if !isConst(key) { + return true + } + } + + for _, attr := range attrs { + switch attr := attr.(type) { + case *ast.CallExpr: // e.g. slog.Int() + fn := typeutil.StaticCallee(info, attr) + if _, ok := attrFuncs[fn.FullName()]; ok && !isConst(attr.Args[0]) { + return true + } + + case *ast.CompositeLit: // slog.Attr{} + isRawKey := func(kv *ast.KeyValueExpr) bool { + return kv.Key.(*ast.Ident).Name == "Key" && !isConst(kv.Value) + } + + switch len(attr.Elts) { + case 1: // slog.Attr{Key: ...} | slog.Attr{Value: ...} + kv := attr.Elts[0].(*ast.KeyValueExpr) + if isRawKey(kv) { + return true + } + case 2: // slog.Attr{..., ...} | slog.Attr{Key: ..., Value: ...} + kv1, ok := attr.Elts[0].(*ast.KeyValueExpr) + if ok { + kv2 := attr.Elts[1].(*ast.KeyValueExpr) + if isRawKey(kv1) || isRawKey(kv2) { + return true + } + } else if !isConst(attr.Elts[0]) { + return true + } + } + } + } + + return false +} + +func argsOnSameLine(fset *token.FileSet, call ast.Expr, keys, attrs []ast.Expr) bool { + if len(keys)+len(attrs) <= 1 { + return false // special case: slog.Info("msg", "key", "value") is ok. + } + + l := len(keys) + len(attrs) + 1 + args := make([]ast.Expr, 0, l) + args = append(args, call) + args = append(args, keys...) + args = append(args, attrs...) + + lines := make(map[int]struct{}, l) + for _, arg := range args { + line := fset.Position(arg.Pos()).Line + if _, ok := lines[line]; ok { + return true + } + lines[line] = struct{}{} + } + + return false +} diff --git a/tools/vendor/go.tmz.dev/musttag/musttag.go b/tools/vendor/go.tmz.dev/musttag/musttag.go index 200163f264..7f4e05e755 100644 --- a/tools/vendor/go.tmz.dev/musttag/musttag.go +++ b/tools/vendor/go.tmz.dev/musttag/musttag.go @@ -8,8 +8,8 @@ import ( "go/token" "go/types" "path" - "path/filepath" "reflect" + "regexp" "strconv" "strings" @@ -43,16 +43,23 @@ func New(funcs ...Func) *analysis.Analyzer { Requires: []*analysis.Analyzer{inspect.Analyzer}, Run: func(pass *analysis.Pass) (any, error) { l := len(builtins) + len(funcs) + len(flagFuncs) - m := make(map[string]Func, l) + f := make(map[string]Func, l) + toMap := func(slice []Func) { for _, fn := range slice { - m[fn.Name] = fn + f[fn.Name] = fn } } toMap(builtins) toMap(funcs) toMap(flagFuncs) - return run(pass, m) + + mainModule, err := getMainModule() + if err != nil { + return nil, err + } + + return run(pass, mainModule, f) }, } } @@ -81,27 +88,16 @@ func flags(funcs *[]Func) flag.FlagSet { } // for tests only. -var ( - report = func(pass *analysis.Pass, st *structType, fn Func, fnPos token.Position) { - const format = "`%s` should be annotated with the `%s` tag as it is passed to `%s` at %s" - pass.Reportf(st.Pos, format, st.Name, fn.Tag, fn.shortName(), fnPos) - } +var report = func(pass *analysis.Pass, st *structType, fn Func, fnPos token.Position) { + const format = "`%s` should be annotated with the `%s` tag as it is passed to `%s` at %s" + pass.Reportf(st.Pos, format, st.Name, fn.Tag, fn.shortName(), fnPos) +} - // HACK: mainModulePackages() does not return packages from `testdata`, - // because it is ignored by the go tool, and thus, by the `go list` command. - // For tests to pass we need to add the packages with tests to the main module manually. - testPackages []string -) +var cleanFullName = regexp.MustCompile(`([^*/(]+/vendor/)`) // run starts the analysis. -func run(pass *analysis.Pass, funcs map[string]Func) (any, error) { - moduleDir, modulePackages, err := mainModule() - if err != nil { - return nil, err - } - for _, pkg := range testPackages { - modulePackages[pkg] = struct{}{} - } +func run(pass *analysis.Pass, mainModule string, funcs map[string]Func) (any, error) { + var err error walk := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) filter := []ast.Node{(*ast.CallExpr)(nil)} @@ -116,12 +112,13 @@ func run(pass *analysis.Pass, funcs map[string]Func) (any, error) { return // not a function call. } - caller := typeutil.StaticCallee(pass.TypesInfo, call) - if caller == nil { + callee := typeutil.StaticCallee(pass.TypesInfo, call) + if callee == nil { return // not a static call. } - fn, ok := funcs[caller.FullName()] + name := cleanFullName.ReplaceAllString(callee.FullName(), "") + fn, ok := funcs[name] if !ok { return // the function is not supported. } @@ -148,7 +145,7 @@ func run(pass *analysis.Pass, funcs map[string]Func) (any, error) { } checker := checker{ - mainModule: modulePackages, + mainModule: mainModule, seenTypes: make(map[string]struct{}), } @@ -164,7 +161,6 @@ func run(pass *analysis.Pass, funcs map[string]Func) (any, error) { } p := pass.Fset.Position(call.Pos()) - p.Filename, _ = filepath.Rel(moduleDir, p.Filename) report(pass, result, fn, p) }) @@ -181,7 +177,7 @@ type structType struct { // checker parses and checks struct types. type checker struct { - mainModule map[string]struct{} // do not check types outside of the main module; see issue #17. + mainModule string seenTypes map[string]struct{} // prevent panic on recursive types; see issue #16. } @@ -202,13 +198,16 @@ func (c *checker) parseStructType(t types.Type, pos token.Pos) (*structType, boo if pkg == nil { return nil, false } - if _, ok := c.mainModule[pkg.Path()]; !ok { + + if !strings.HasPrefix(pkg.Path(), c.mainModule) { return nil, false } + s, ok := t.Underlying().(*types.Struct) if !ok { return nil, false } + return &structType{ Struct: s, Pos: t.Obj().Pos(), diff --git a/tools/vendor/go.tmz.dev/musttag/utils.go b/tools/vendor/go.tmz.dev/musttag/utils.go index 73e21310a4..673747f15f 100644 --- a/tools/vendor/go.tmz.dev/musttag/utils.go +++ b/tools/vendor/go.tmz.dev/musttag/utils.go @@ -1,41 +1,57 @@ package musttag import ( + "encoding/json" + "errors" "fmt" + "io" + "os" "os/exec" "strings" ) -// mainModule returns the directory and the set of packages of the main module. -func mainModule() (dir string, packages map[string]struct{}, _ error) { - // https://pkg.go.dev/cmd/go#hdr-Package_lists_and_patterns - // > When using modules, "all" expands to all packages in the main module - // > and their dependencies, including dependencies needed by tests of any of those. +var ( + getwd = os.Getwd - // NOTE: the command may run out of file descriptors if go version <= 1.18, - // especially on macOS, which has the default soft limit set to 256 (ulimit -nS). - // Since go1.19 the limit is automatically increased to the maximum allowed value; - // see https://github.com/golang/go/issues/46279 for details. - cmd := [...]string{"go", "list", "-f={{if and (not .Standard) .Module.Main}}{{.ImportPath}}{{end}}", "all"} - - out, err := exec.Command(cmd[0], cmd[1:]...).Output() - if err != nil { - return "", nil, fmt.Errorf("running `go list all`: %w", err) + commandOutput = func(name string, args ...string) (string, error) { + output, err := exec.Command(name, args...).Output() + return string(output), err } +) - list := strings.Split(strings.TrimSpace(string(out)), "\n") - packages = make(map[string]struct{}, len(list)*2) +func getMainModule() (string, error) { + args := []string{"go", "list", "-m", "-json"} - for _, pkg := range list { - packages[pkg] = struct{}{} - packages[pkg+"_test"] = struct{}{} // `*_test` packages belong to the main module, see issue #24. + output, err := commandOutput(args[0], args[1:]...) + if err != nil { + return "", fmt.Errorf("running `%s`: %w", strings.Join(args, " "), err) } - out, err = exec.Command("go", "list", "-m", "-f={{.Dir}}").Output() + cwd, err := getwd() if err != nil { - return "", nil, fmt.Errorf("running `go list -m`: %w", err) + return "", fmt.Errorf("getting wd: %w", err) } - dir = strings.TrimSpace(string(out)) - return dir, packages, nil + decoder := json.NewDecoder(strings.NewReader(output)) + + for { + // multiple JSON objects will be returned when using Go workspaces; see #63 for details. + var module struct { + Path string `json:"Path"` + Main bool `json:"Main"` + Dir string `json:"Dir"` + GoMod string `json:"GoMod"` + GoVersion string `json:"GoVersion"` + } + if err := decoder.Decode(&module); err != nil { + if errors.Is(err, io.EOF) { + return "", fmt.Errorf("main module not found\n%s", output) + } + return "", fmt.Errorf("decoding json: %w\n%s", err, output) + } + + if module.Main && strings.HasPrefix(cwd, module.Dir) { + return module.Path, nil + } + } } diff --git a/tools/vendor/golang.org/x/text/unicode/norm/trie.go b/tools/vendor/golang.org/x/text/unicode/norm/trie.go index 423386bf43..e4250ae22c 100644 --- a/tools/vendor/golang.org/x/text/unicode/norm/trie.go +++ b/tools/vendor/golang.org/x/text/unicode/norm/trie.go @@ -29,7 +29,7 @@ var ( nfkcData = newNfkcTrie(0) ) -// lookupValue determines the type of block n and looks up the value for b. +// lookup determines the type of block n and looks up the value for b. // For n < t.cutoff, the block is a simple lookup table. Otherwise, the block // is a list of ranges with an accompanying value. Given a matching range r, // the value for b is by r.value + (b - r.lo) * stride. diff --git a/tools/vendor/golang.org/x/tools/go/analysis/passes/appends/appends.go b/tools/vendor/golang.org/x/tools/go/analysis/passes/appends/appends.go new file mode 100644 index 0000000000..6976f0d909 --- /dev/null +++ b/tools/vendor/golang.org/x/tools/go/analysis/passes/appends/appends.go @@ -0,0 +1,47 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package appends defines an Analyzer that detects +// if there is only one variable in append. +package appends + +import ( + _ "embed" + "go/ast" + "go/types" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/analysis/passes/internal/analysisutil" + "golang.org/x/tools/go/ast/inspector" + "golang.org/x/tools/go/types/typeutil" +) + +//go:embed doc.go +var doc string + +var Analyzer = &analysis.Analyzer{ + Name: "appends", + Doc: analysisutil.MustExtractDoc(doc, "appends"), + URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/appends", + Requires: []*analysis.Analyzer{inspect.Analyzer}, + Run: run, +} + +func run(pass *analysis.Pass) (interface{}, error) { + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + + nodeFilter := []ast.Node{ + (*ast.CallExpr)(nil), + } + inspect.Preorder(nodeFilter, func(n ast.Node) { + call := n.(*ast.CallExpr) + b, ok := typeutil.Callee(pass.TypesInfo, call).(*types.Builtin) + if ok && b.Name() == "append" && len(call.Args) == 1 { + pass.ReportRangef(call, "append with no values") + } + }) + + return nil, nil +} diff --git a/tools/vendor/golang.org/x/tools/go/analysis/passes/appends/doc.go b/tools/vendor/golang.org/x/tools/go/analysis/passes/appends/doc.go new file mode 100644 index 0000000000..2e6a2e010b --- /dev/null +++ b/tools/vendor/golang.org/x/tools/go/analysis/passes/appends/doc.go @@ -0,0 +1,20 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package appends defines an Analyzer that detects +// if there is only one variable in append. +// +// # Analyzer appends +// +// appends: check for missing values after append +// +// This checker reports calls to append that pass +// no values to be appended to the slice. +// +// s := []string{"a", "b", "c"} +// _ = append(s) +// +// Such calls are always no-ops and often indicate an +// underlying mistake. +package appends diff --git a/tools/vendor/golang.org/x/tools/go/analysis/passes/defers/defers.go b/tools/vendor/golang.org/x/tools/go/analysis/passes/defers/defers.go new file mode 100644 index 0000000000..5e8e80a6a7 --- /dev/null +++ b/tools/vendor/golang.org/x/tools/go/analysis/passes/defers/defers.go @@ -0,0 +1,59 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package defers + +import ( + _ "embed" + "go/ast" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/analysis/passes/internal/analysisutil" + "golang.org/x/tools/go/ast/inspector" + "golang.org/x/tools/go/types/typeutil" +) + +//go:embed doc.go +var doc string + +// Analyzer is the defers analyzer. +var Analyzer = &analysis.Analyzer{ + Name: "defers", + Requires: []*analysis.Analyzer{inspect.Analyzer}, + URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/defers", + Doc: analysisutil.MustExtractDoc(doc, "defers"), + Run: run, +} + +func run(pass *analysis.Pass) (interface{}, error) { + if !analysisutil.Imports(pass.Pkg, "time") { + return nil, nil + } + + checkDeferCall := func(node ast.Node) bool { + switch v := node.(type) { + case *ast.CallExpr: + if analysisutil.IsFunctionNamed(typeutil.StaticCallee(pass.TypesInfo, v), "time", "Since") { + pass.Reportf(v.Pos(), "call to time.Since is not deferred") + } + case *ast.FuncLit: + return false // prune + } + return true + } + + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + + nodeFilter := []ast.Node{ + (*ast.DeferStmt)(nil), + } + + inspect.Preorder(nodeFilter, func(n ast.Node) { + d := n.(*ast.DeferStmt) + ast.Inspect(d.Call, checkDeferCall) + }) + + return nil, nil +} diff --git a/tools/vendor/golang.org/x/tools/go/analysis/passes/defers/doc.go b/tools/vendor/golang.org/x/tools/go/analysis/passes/defers/doc.go new file mode 100644 index 0000000000..bdb1351628 --- /dev/null +++ b/tools/vendor/golang.org/x/tools/go/analysis/passes/defers/doc.go @@ -0,0 +1,25 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package defers defines an Analyzer that checks for common mistakes in defer +// statements. +// +// # Analyzer defers +// +// defers: report common mistakes in defer statements +// +// The defers analyzer reports a diagnostic when a defer statement would +// result in a non-deferred call to time.Since, as experience has shown +// that this is nearly always a mistake. +// +// For example: +// +// start := time.Now() +// ... +// defer recordLatency(time.Since(start)) // error: call to time.Since is not deferred +// +// The correct code is: +// +// defer func() { recordLatency(time.Since(start)) }() +package defers diff --git a/tools/vendor/golang.org/x/tools/go/analysis/passes/directive/directive.go b/tools/vendor/golang.org/x/tools/go/analysis/passes/directive/directive.go new file mode 100644 index 0000000000..2691f189aa --- /dev/null +++ b/tools/vendor/golang.org/x/tools/go/analysis/passes/directive/directive.go @@ -0,0 +1,209 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package directive defines an Analyzer that checks known Go toolchain directives. +package directive + +import ( + "go/ast" + "go/parser" + "go/token" + "strings" + "unicode" + "unicode/utf8" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/internal/analysisutil" +) + +const Doc = `check Go toolchain directives such as //go:debug + +This analyzer checks for problems with known Go toolchain directives +in all Go source files in a package directory, even those excluded by +//go:build constraints, and all non-Go source files too. + +For //go:debug (see https://go.dev/doc/godebug), the analyzer checks +that the directives are placed only in Go source files, only above the +package comment, and only in package main or *_test.go files. + +Support for other known directives may be added in the future. + +This analyzer does not check //go:build, which is handled by the +buildtag analyzer. +` + +var Analyzer = &analysis.Analyzer{ + Name: "directive", + Doc: Doc, + URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/directive", + Run: runDirective, +} + +func runDirective(pass *analysis.Pass) (interface{}, error) { + for _, f := range pass.Files { + checkGoFile(pass, f) + } + for _, name := range pass.OtherFiles { + if err := checkOtherFile(pass, name); err != nil { + return nil, err + } + } + for _, name := range pass.IgnoredFiles { + if strings.HasSuffix(name, ".go") { + f, err := parser.ParseFile(pass.Fset, name, nil, parser.ParseComments) + if err != nil { + // Not valid Go source code - not our job to diagnose, so ignore. + continue + } + checkGoFile(pass, f) + } else { + if err := checkOtherFile(pass, name); err != nil { + return nil, err + } + } + } + return nil, nil +} + +func checkGoFile(pass *analysis.Pass, f *ast.File) { + check := newChecker(pass, pass.Fset.File(f.Package).Name(), f) + + for _, group := range f.Comments { + // A +build comment is ignored after or adjoining the package declaration. + if group.End()+1 >= f.Package { + check.inHeader = false + } + // A //go:build comment is ignored after the package declaration + // (but adjoining it is OK, in contrast to +build comments). + if group.Pos() >= f.Package { + check.inHeader = false + } + + // Check each line of a //-comment. + for _, c := range group.List { + check.comment(c.Slash, c.Text) + } + } +} + +func checkOtherFile(pass *analysis.Pass, filename string) error { + // We cannot use the Go parser, since is not a Go source file. + // Read the raw bytes instead. + content, tf, err := analysisutil.ReadFile(pass.Fset, filename) + if err != nil { + return err + } + + check := newChecker(pass, filename, nil) + check.nonGoFile(token.Pos(tf.Base()), string(content)) + return nil +} + +type checker struct { + pass *analysis.Pass + filename string + file *ast.File // nil for non-Go file + inHeader bool // in file header (before package declaration) + inStar bool // currently in a /* */ comment +} + +func newChecker(pass *analysis.Pass, filename string, file *ast.File) *checker { + return &checker{ + pass: pass, + filename: filename, + file: file, + inHeader: true, + } +} + +func (check *checker) nonGoFile(pos token.Pos, fullText string) { + // Process each line. + text := fullText + inStar := false + for text != "" { + offset := len(fullText) - len(text) + var line string + line, text, _ = strings.Cut(text, "\n") + + if !inStar && strings.HasPrefix(line, "//") { + check.comment(pos+token.Pos(offset), line) + continue + } + + // Skip over, cut out any /* */ comments, + // to avoid being confused by a commented-out // comment. + for { + line = strings.TrimSpace(line) + if inStar { + var ok bool + _, line, ok = strings.Cut(line, "*/") + if !ok { + break + } + inStar = false + continue + } + line, inStar = stringsCutPrefix(line, "/*") + if !inStar { + break + } + } + if line != "" { + // Found non-comment non-blank line. + // Ends space for valid //go:build comments, + // but also ends the fraction of the file we can + // reliably parse. From this point on we might + // incorrectly flag "comments" inside multiline + // string constants or anything else (this might + // not even be a Go program). So stop. + break + } + } +} + +func (check *checker) comment(pos token.Pos, line string) { + if !strings.HasPrefix(line, "//go:") { + return + } + // testing hack: stop at // ERROR + if i := strings.Index(line, " // ERROR "); i >= 0 { + line = line[:i] + } + + verb := line + if i := strings.IndexFunc(verb, unicode.IsSpace); i >= 0 { + verb = verb[:i] + if line[i] != ' ' && line[i] != '\t' && line[i] != '\n' { + r, _ := utf8.DecodeRuneInString(line[i:]) + check.pass.Reportf(pos, "invalid space %#q in %s directive", r, verb) + } + } + + switch verb { + default: + // TODO: Use the go language version for the file. + // If that version is not newer than us, then we can + // report unknown directives. + + case "//go:build": + // Ignore. The buildtag analyzer reports misplaced comments. + + case "//go:debug": + if check.file == nil { + check.pass.Reportf(pos, "//go:debug directive only valid in Go source files") + } else if check.file.Name.Name != "main" && !strings.HasSuffix(check.filename, "_test.go") { + check.pass.Reportf(pos, "//go:debug directive only valid in package main or test") + } else if !check.inHeader { + check.pass.Reportf(pos, "//go:debug directive only valid before package declaration") + } + } +} + +// Go 1.20 strings.CutPrefix. +func stringsCutPrefix(s, prefix string) (after string, found bool) { + if !strings.HasPrefix(s, prefix) { + return s, false + } + return s[len(prefix):], true +} diff --git a/tools/vendor/golang.org/x/tools/go/analysis/passes/slog/doc.go b/tools/vendor/golang.org/x/tools/go/analysis/passes/slog/doc.go new file mode 100644 index 0000000000..ecb10e0948 --- /dev/null +++ b/tools/vendor/golang.org/x/tools/go/analysis/passes/slog/doc.go @@ -0,0 +1,23 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package slog defines an Analyzer that checks for +// mismatched key-value pairs in log/slog calls. +// +// # Analyzer slog +// +// slog: check for invalid structured logging calls +// +// The slog checker looks for calls to functions from the log/slog +// package that take alternating key-value pairs. It reports calls +// where an argument in a key position is neither a string nor a +// slog.Attr, and where a final key is missing its value. +// For example,it would report +// +// slog.Warn("message", 11, "k") // slog.Warn arg "11" should be a string or a slog.Attr +// +// and +// +// slog.Info("message", "k1", v1, "k2") // call to slog.Info missing a final value +package slog diff --git a/tools/vendor/golang.org/x/tools/go/analysis/passes/slog/slog.go b/tools/vendor/golang.org/x/tools/go/analysis/passes/slog/slog.go new file mode 100644 index 0000000000..a1323c3e66 --- /dev/null +++ b/tools/vendor/golang.org/x/tools/go/analysis/passes/slog/slog.go @@ -0,0 +1,234 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// TODO(jba) deduce which functions wrap the log/slog functions, and use the +// fact mechanism to propagate this information, so we can provide diagnostics +// for user-supplied wrappers. + +package slog + +import ( + _ "embed" + "fmt" + "go/ast" + "go/token" + "go/types" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/analysis/passes/internal/analysisutil" + "golang.org/x/tools/go/ast/inspector" + "golang.org/x/tools/go/types/typeutil" +) + +//go:embed doc.go +var doc string + +var Analyzer = &analysis.Analyzer{ + Name: "slog", + Doc: analysisutil.MustExtractDoc(doc, "slog"), + URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/slog", + Requires: []*analysis.Analyzer{inspect.Analyzer}, + Run: run, +} + +var stringType = types.Universe.Lookup("string").Type() + +// A position describes what is expected to appear in an argument position. +type position int + +const ( + // key is an argument position that should hold a string key or an Attr. + key position = iota + // value is an argument position that should hold a value. + value + // unknown represents that we do not know if position should hold a key or a value. + unknown +) + +func run(pass *analysis.Pass) (any, error) { + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter := []ast.Node{ + (*ast.CallExpr)(nil), + } + inspect.Preorder(nodeFilter, func(node ast.Node) { + call := node.(*ast.CallExpr) + fn := typeutil.StaticCallee(pass.TypesInfo, call) + if fn == nil { + return // not a static call + } + if call.Ellipsis != token.NoPos { + return // skip calls with "..." args + } + skipArgs, ok := kvFuncSkipArgs(fn) + if !ok { + // Not a slog function that takes key-value pairs. + return + } + if isMethodExpr(pass.TypesInfo, call) { + // Call is to a method value. Skip the first argument. + skipArgs++ + } + if len(call.Args) <= skipArgs { + // Too few args; perhaps there are no k-v pairs. + return + } + + // Check this call. + // The first position should hold a key or Attr. + pos := key + var unknownArg ast.Expr // nil or the last unknown argument + for _, arg := range call.Args[skipArgs:] { + t := pass.TypesInfo.Types[arg].Type + switch pos { + case key: + // Expect a string or Attr. + switch { + case t == stringType: + pos = value + case isAttr(t): + pos = key + case types.IsInterface(t): + // As we do not do dataflow, we do not know what the dynamic type is. + // It could be a string or an Attr so we don't know what to expect next. + pos = unknown + default: + if unknownArg == nil { + pass.ReportRangef(arg, "%s arg %q should be a string or a slog.Attr (possible missing key or value)", + shortName(fn), analysisutil.Format(pass.Fset, arg)) + } else { + pass.ReportRangef(arg, "%s arg %q should probably be a string or a slog.Attr (previous arg %q cannot be a key)", + shortName(fn), analysisutil.Format(pass.Fset, arg), analysisutil.Format(pass.Fset, unknownArg)) + } + // Stop here so we report at most one missing key per call. + return + } + + case value: + // Anything can appear in this position. + // The next position should be a key. + pos = key + + case unknown: + // Once we encounter an unknown position, we can never be + // sure if a problem later or at the end of the call is due to a + // missing final value, or a non-key in key position. + // In both cases, unknownArg != nil. + unknownArg = arg + + // We don't know what is expected about this position, but all hope is not lost. + if t != stringType && !isAttr(t) && !types.IsInterface(t) { + // This argument is definitely not a key. + // + // unknownArg cannot have been a key, in which case this is the + // corresponding value, and the next position should hold another key. + pos = key + } + } + } + if pos == value { + if unknownArg == nil { + pass.ReportRangef(call, "call to %s missing a final value", shortName(fn)) + } else { + pass.ReportRangef(call, "call to %s has a missing or misplaced value", shortName(fn)) + } + } + }) + return nil, nil +} + +func isAttr(t types.Type) bool { + return analysisutil.IsNamedType(t, "log/slog", "Attr") +} + +// shortName returns a name for the function that is shorter than FullName. +// Examples: +// +// "slog.Info" (instead of "log/slog.Info") +// "slog.Logger.With" (instead of "(*log/slog.Logger).With") +func shortName(fn *types.Func) string { + var r string + if recv := fn.Type().(*types.Signature).Recv(); recv != nil { + t := recv.Type() + if pt, ok := t.(*types.Pointer); ok { + t = pt.Elem() + } + if nt, ok := t.(*types.Named); ok { + r = nt.Obj().Name() + } else { + r = recv.Type().String() + } + r += "." + } + return fmt.Sprintf("%s.%s%s", fn.Pkg().Name(), r, fn.Name()) +} + +// If fn is a slog function that has a ...any parameter for key-value pairs, +// kvFuncSkipArgs returns the number of arguments to skip over to reach the +// corresponding arguments, and true. +// Otherwise it returns (0, false). +func kvFuncSkipArgs(fn *types.Func) (int, bool) { + if pkg := fn.Pkg(); pkg == nil || pkg.Path() != "log/slog" { + return 0, false + } + var recvName string // by default a slog package function + recv := fn.Type().(*types.Signature).Recv() + if recv != nil { + t := recv.Type() + if pt, ok := t.(*types.Pointer); ok { + t = pt.Elem() + } + if nt, ok := t.(*types.Named); !ok { + return 0, false + } else { + recvName = nt.Obj().Name() + } + } + skip, ok := kvFuncs[recvName][fn.Name()] + return skip, ok +} + +// The names of functions and methods in log/slog that take +// ...any for key-value pairs, mapped to the number of initial args to skip in +// order to get to the ones that match the ...any parameter. +// The first key is the dereferenced receiver type name, or "" for a function. +var kvFuncs = map[string]map[string]int{ + "": map[string]int{ + "Debug": 1, + "Info": 1, + "Warn": 1, + "Error": 1, + "DebugContext": 2, + "InfoContext": 2, + "WarnContext": 2, + "ErrorContext": 2, + "Log": 3, + "Group": 1, + }, + "Logger": map[string]int{ + "Debug": 1, + "Info": 1, + "Warn": 1, + "Error": 1, + "DebugContext": 2, + "InfoContext": 2, + "WarnContext": 2, + "ErrorContext": 2, + "Log": 3, + "With": 0, + }, + "Record": map[string]int{ + "Add": 0, + }, +} + +// isMethodExpr reports whether a call is to a MethodExpr. +func isMethodExpr(info *types.Info, c *ast.CallExpr) bool { + s, ok := c.Fun.(*ast.SelectorExpr) + if !ok { + return false + } + sel := info.Selections[s] + return sel != nil && sel.Kind() == types.MethodExpr +} diff --git a/tools/vendor/honnef.co/go/tools/analysis/lint/lint.go b/tools/vendor/honnef.co/go/tools/analysis/lint/lint.go index b4e37d6f40..7d116a23d0 100644 --- a/tools/vendor/honnef.co/go/tools/analysis/lint/lint.go +++ b/tools/vendor/honnef.co/go/tools/analysis/lint/lint.go @@ -8,6 +8,7 @@ import ( "go/ast" "go/build" "go/token" + "regexp" "strconv" "strings" @@ -206,21 +207,28 @@ func (v *VersionFlag) String() string { return fmt.Sprintf("1.%d", *v) } -func (v *VersionFlag) Set(s string) error { - if len(s) < 3 { - return fmt.Errorf("invalid Go version: %q", s) - } - if s[0] != '1' { - return fmt.Errorf("invalid Go version: %q", s) - } - if s[1] != '.' { - return fmt.Errorf("invalid Go version: %q", s) +var goVersionRE = regexp.MustCompile(`^(?:go)?1.(\d+).*$`) + +// ParseGoVersion parses Go versions of the form 1.M, 1.M.N, or 1.M.NrcR, with an optional "go" prefix. It assumes that +// versions have already been verified and are valid. +func ParseGoVersion(s string) (int, bool) { + m := goVersionRE.FindStringSubmatch(s) + if m == nil { + return 0, false } - i, err := strconv.Atoi(s[2:]) + n, err := strconv.Atoi(m[1]) if err != nil { + return 0, false + } + return n, true +} + +func (v *VersionFlag) Set(s string) error { + n, ok := ParseGoVersion(s) + if !ok { return fmt.Errorf("invalid Go version: %q", s) } - *v = VersionFlag(i) + *v = VersionFlag(n) return nil } diff --git a/tools/vendor/honnef.co/go/tools/go/ir/builder.go b/tools/vendor/honnef.co/go/tools/go/ir/builder.go index d56975b729..1a77ed0429 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/builder.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/builder.go @@ -1145,16 +1145,13 @@ func (b *builder) arrayLen(fn *Function, elts []ast.Expr) int64 { // x = T{a: x.a} // // all the reads must occur before all the writes. This is implicitly handled by the write buffering effected by -// compositeElement. +// compositeElement and explicitly by the storebuf for when we don't use CompositeValue. // // A CompositeLit may have pointer type only in the recursive (nested) // case when the type name is implicit. e.g. in []*T{{}}, the inner // literal has type *T behaves like &T{}. // In that case, addr must hold a T, not a *T. func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero bool, sb *storebuf) { - // Even though we no longer need storebuf for nested composite literals (because compositeElements act as buffers - // themselves), we still need storebuf for things like multiple assignment, e.g. 't.F1, t.F2 = T2{1}, T2{t.F1.X}' - typ := deref(fn.Pkg.typeOf(e)) switch t := typeutil.CoreType(typ).(type) { case *types.Struct: @@ -1220,41 +1217,77 @@ func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero final = zc } } else { - v := &CompositeValue{ - Values: make([]Value, at.Len()), - } - zc := emitConst(fn, zeroConst(at.Elem())) - for i := range v.Values { - v.Values[i] = zc - } - v.setType(at) + if at.Len() == int64(len(e.Elts)) { + // The literal specifies all elements, so we can use a composite value + v := &CompositeValue{ + Values: make([]Value, at.Len()), + } + zc := emitConst(fn, zeroConst(at.Elem())) + for i := range v.Values { + v.Values[i] = zc + } + v.setType(at) - var idx *Const - for _, e := range e.Elts { - if kv, ok := e.(*ast.KeyValueExpr); ok { - idx = b.expr(fn, kv.Key).(*Const) - e = kv.Value - } else { - var idxval int64 - if idx != nil { - idxval = idx.Int64() + 1 + var idx *Const + for _, e := range e.Elts { + if kv, ok := e.(*ast.KeyValueExpr); ok { + idx = b.expr(fn, kv.Key).(*Const) + e = kv.Value + } else { + var idxval int64 + if idx != nil { + idxval = idx.Int64() + 1 + } + idx = emitConst(fn, intConst(idxval)).(*Const) } - idx = emitConst(fn, intConst(idxval)).(*Const) - } - iaddr := &compositeElement{ - cv: v, - idx: int(idx.Int64()), - t: at.Elem(), - expr: e, + iaddr := &compositeElement{ + cv: v, + idx: int(idx.Int64()), + t: at.Elem(), + expr: e, + } + + b.assign(fn, iaddr, e, true, sb, e) + v.Bitmap.SetBit(&v.Bitmap, int(idx.Int64()), 1) + v.NumSet++ + } + final = v + fn.emit(v, e) + } else { + // Not all elements are specified. Populate the array with a series of stores, to guard against literals + // like []int{1<<62: 1}. + if !isZero { + // memclear + sb.store(&address{array, nil}, zeroValue(fn, deref(array.Type()), e), e) } - b.assign(fn, iaddr, e, true, sb, e) - v.Bitmap.SetBit(&v.Bitmap, int(idx.Int64()), 1) - v.NumSet++ + var idx *Const + for _, e := range e.Elts { + if kv, ok := e.(*ast.KeyValueExpr); ok { + idx = b.expr(fn, kv.Key).(*Const) + e = kv.Value + } else { + var idxval int64 + if idx != nil { + idxval = idx.Int64() + 1 + } + idx = emitConst(fn, intConst(idxval)).(*Const) + } + iaddr := &IndexAddr{ + X: array, + Index: idx, + } + iaddr.setType(types.NewPointer(at.Elem())) + fn.emit(iaddr, e) + if t != at { // slice + // backing array is unaliased => storebuf not needed. + b.assign(fn, &address{addr: iaddr, expr: e}, e, true, nil, e) + } else { + b.assign(fn, &address{addr: iaddr, expr: e}, e, true, sb, e) + } + } } - final = v - fn.emit(v, e) } if t != at { // slice if final != nil { diff --git a/tools/vendor/honnef.co/go/tools/go/ir/methods.go b/tools/vendor/honnef.co/go/tools/go/ir/methods.go index eb247a936a..b7903c8472 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/methods.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/methods.go @@ -11,8 +11,6 @@ import ( "go/types" "honnef.co/go/tools/analysis/lint" - - "golang.org/x/exp/typeparams" ) // MethodValue returns the Function implementing method sel, building @@ -118,7 +116,7 @@ func (prog *Program) RuntimeTypes() []types.Type { // declaredFunc returns the concrete function/method denoted by obj. // Panic ensues if there is none. func (prog *Program) declaredFunc(obj *types.Func) *Function { - if origin := typeparams.OriginMethod(obj); origin != obj { + if origin := obj.Origin(); origin != obj { // Calling method on instantiated type, create a wrapper that calls the generic type's method base := prog.packageLevelValue(origin) return makeInstance(prog, base.(*Function), obj.Type().(*types.Signature), nil) diff --git a/tools/vendor/honnef.co/go/tools/go/ir/source.go b/tools/vendor/honnef.co/go/tools/go/ir/source.go index 0aa8ef3036..155c5f7336 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/source.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/source.go @@ -14,8 +14,6 @@ import ( "go/ast" "go/token" "go/types" - - "golang.org/x/exp/typeparams" ) // EnclosingFunction returns the function that contains the syntax @@ -170,7 +168,7 @@ func (prog *Program) packageLevelValue(obj types.Object) Value { // TODO(adonovan): check the invariant that obj.Type() matches the // result's Signature, both in the params/results and in the receiver. func (prog *Program) FuncValue(obj *types.Func) *Function { - obj = typeparams.OriginMethod(obj) + obj = obj.Origin() fn, _ := prog.packageLevelValue(obj).(*Function) return fn } diff --git a/tools/vendor/honnef.co/go/tools/go/types/typeutil/util.go b/tools/vendor/honnef.co/go/tools/go/types/typeutil/util.go index b0aca16bdb..3a2ad973bb 100644 --- a/tools/vendor/honnef.co/go/tools/go/types/typeutil/util.go +++ b/tools/vendor/honnef.co/go/tools/go/types/typeutil/util.go @@ -122,6 +122,8 @@ func flattenFields(T *types.Struct, path []int, seen map[types.Type]bool) []Fiel if field.Anonymous() { if s, ok := Dereference(field.Type()).Underlying().(*types.Struct); ok { out = append(out, flattenFields(s, np, seen)...) + } else { + out = append(out, Field{field, tag, np}) } } else { out = append(out, Field{field, tag, np}) diff --git a/tools/vendor/honnef.co/go/tools/knowledge/deprecated.go b/tools/vendor/honnef.co/go/tools/knowledge/deprecated.go index 343d5deb75..caeb49975c 100644 --- a/tools/vendor/honnef.co/go/tools/knowledge/deprecated.go +++ b/tools/vendor/honnef.co/go/tools/knowledge/deprecated.go @@ -187,54 +187,44 @@ var StdlibDeprecations = map[string]Deprecation{ "syscall.Syscall18": {18, 18}, "syscall.Syscall6": {18, 18}, "syscall.Syscall9": {18, 18}, + + "reflect.SliceHeader": {21, 17}, + "reflect.StringHeader": {21, 20}, + "crypto/elliptic.GenerateKey": {21, 21}, + "crypto/elliptic.Marshal": {21, 21}, + "crypto/elliptic.Unmarshal": {21, 21}, + "(*crypto/elliptic.CurveParams).Add": {21, 21}, + "(*crypto/elliptic.CurveParams).Double": {21, 21}, + "(*crypto/elliptic.CurveParams).IsOnCurve": {21, 21}, + "(*crypto/elliptic.CurveParams).ScalarBaseMult": {21, 21}, + "(*crypto/elliptic.CurveParams).ScalarMult": {21, 21}, + "crypto/rsa.GenerateMultiPrimeKey": {21, DeprecatedNeverUse}, + "(crypto/rsa.PrecomputedValues).CRTValues": {21, DeprecatedNeverUse}, + "(crypto/x509.RevocationList).RevokedCertificates": {21, 21}, } -// Last imported from Go at 9f0234214473dfb785a5ad84a8fc62a6a395cbc3 with the following numbers of deprecations: +// Last imported from Go at c19c4c566c63818dfd059b352e52c4710eecf14d with the following numbers of deprecations: // // archive/tar/common.go:2 // archive/zip/struct.go:6 // bytes/bytes.go:1 -// cmd/api/testdata/src/pkg/p1/p1.go:8 -// cmd/api/testdata/src/pkg/p2/p2.go:2 -// cmd/api/testdata/src/pkg/p4/p4.go:1 -// cmd/compile/internal/noder/quirks.go:1 -// cmd/compile/internal/reflectdata/reflect.go:2 -// cmd/compile/internal/syntax/walk.go:1 -// cmd/compile/internal/types/sym.go:3 -// cmd/go/internal/modcmd/edit.go:1 -// cmd/go/testdata/mod/example.com_deprecated_a_v1.9.0.txt:2 -// cmd/go/testdata/mod/example.com_deprecated_b_v1.9.0.txt:2 -// cmd/go/testdata/mod/example.com_undeprecated_v1.0.0.txt:2 -// cmd/go/testdata/script/mod_deprecate_message.txt:4 -// cmd/go/testdata/script/mod_edit.txt:1 -// cmd/go/testdata/script/mod_list_deprecated.txt:2 -// cmd/go/testdata/script/mod_list_deprecated_replace.txt:1 -// cmd/internal/obj/link.go:5 -// cmd/internal/obj/textflag.go:1 -// cmd/vendor/golang.org/x/mod/modfile/rule.go:2 -// cmd/vendor/golang.org/x/mod/semver/semver.go:1 -// cmd/vendor/golang.org/x/sys/unix/zsysnum_darwin_amd64.go:1 -// cmd/vendor/golang.org/x/sys/unix/zsysnum_darwin_arm64.go:1 -// cmd/vendor/golang.org/x/sys/unix/zsysnum_openbsd_386.go:1 -// cmd/vendor/golang.org/x/sys/unix/zsysnum_openbsd_amd64.go:1 -// cmd/vendor/golang.org/x/sys/unix/zsysnum_openbsd_arm.go:1 -// cmd/vendor/golang.org/x/sys/unix/zsysnum_openbsd_arm64.go:1 -// cmd/vendor/golang.org/x/sys/unix/zsysnum_openbsd_riscv64.go:1 -// cmd/vendor/golang.org/x/sys/windows/security_windows.go:1 -// cmd/vendor/golang.org/x/sys/windows/syscall_windows.go:2 // compress/flate/inflate.go:2 // crypto/dsa/dsa.go:1 +// crypto/elliptic/elliptic.go:8 +// crypto/elliptic/params.go:5 // crypto/rc4/rc4.go:1 -// crypto/tls/common.go:7 +// crypto/rsa/rsa.go:2 +// crypto/tls/common.go:6 // crypto/x509/cert_pool.go:1 // crypto/x509/pem_decrypt.go:3 // crypto/x509/pkix/pkix.go:2 -// crypto/x509/x509.go:5 +// crypto/x509/x509.go:6 // database/sql/driver/driver.go:6 // debug/gosym/pclntab.go:2 // encoding/csv/reader.go:2 // encoding/json/decode.go:1 // encoding/json/encode.go:1 +// go/build/build.go:1 // go/doc/comment.go:2 // go/doc/doc.go:1 // go/doc/synopsis.go:1 @@ -260,7 +250,7 @@ var StdlibDeprecations = map[string]Deprecation{ // path/filepath/path_plan9.go:1 // path/filepath/path_unix.go:1 // path/filepath/path_windows.go:1 -// reflect/value.go:1 +// reflect/value.go:3 // regexp/regexp.go:1 // runtime/cpuprof.go:1 // strings/strings.go:1 diff --git a/tools/vendor/honnef.co/go/tools/pattern/match.go b/tools/vendor/honnef.co/go/tools/pattern/match.go index 32eb9d696f..3eae3bd631 100644 --- a/tools/vendor/honnef.co/go/tools/pattern/match.go +++ b/tools/vendor/honnef.co/go/tools/pattern/match.go @@ -6,6 +6,8 @@ import ( "go/token" "go/types" "reflect" + + "golang.org/x/tools/go/ast/astutil" ) var tokensByString = map[string]Token{ @@ -560,13 +562,14 @@ func (fn Symbol) Match(m *Matcher, node interface{}) (interface{}, bool) { return nil, false } - fun := r + fun := r.(ast.Expr) switch idx := fun.(type) { case *ast.IndexExpr: fun = idx.X case *ast.IndexListExpr: fun = idx.X } + fun = astutil.Unparen(fun) switch fun := fun.(type) { case *ast.Ident: diff --git a/tools/vendor/honnef.co/go/tools/simple/lint.go b/tools/vendor/honnef.co/go/tools/simple/lint.go index 084264367c..8458f7be69 100644 --- a/tools/vendor/honnef.co/go/tools/simple/lint.go +++ b/tools/vendor/honnef.co/go/tools/simple/lint.go @@ -778,13 +778,13 @@ var checkLoopAppendQ = pattern.MustParse(` x [(AssignStmt [lhs] "=" [(CallExpr (Builtin "append") [lhs val])])]) (RangeStmt - idx@(Ident _) + idx@(Object _) nil _ x [(AssignStmt [lhs] "=" [(CallExpr (Builtin "append") [lhs (IndexExpr x idx)])])]) (RangeStmt - idx@(Ident _) + idx@(Object _) nil _ x @@ -800,8 +800,7 @@ func CheckLoopAppend(pass *analysis.Pass) (interface{}, error) { return } - val, ok := m.State["val"].(types.Object) - if ok && refersTo(pass, m.State["lhs"].(ast.Expr), val) { + if val, ok := m.State["val"].(types.Object); ok && refersTo(pass, m.State["lhs"].(ast.Expr), val) { return } @@ -811,6 +810,26 @@ func CheckLoopAppend(pass *analysis.Pass) (interface{}, error) { return } + if idx, ok := m.State["idx"].(types.Object); ok && refersTo(pass, m.State["lhs"].(ast.Expr), idx) { + // The lhs mustn't refer to the index loop variable. + return + } + + if code.MayHaveSideEffects(pass, m.State["lhs"].(ast.Expr), pure) { + // The lhs may be dynamic and return different values on each iteration. For example: + // + // func bar() map[int][]int { /* return one of several maps */ } + // + // func foo(x []int, y [][]int) { + // for i := range x { + // bar()[0] = append(bar()[0], x[i]) + // } + // } + // + // The dynamic nature of the lhs might also affect the value of the index. + return + } + src := pass.TypesInfo.TypeOf(m.State["x"].(ast.Expr)) dst := pass.TypesInfo.TypeOf(m.State["lhs"].(ast.Expr)) if !types.Identical(src, dst) { diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/lint.go b/tools/vendor/honnef.co/go/tools/staticcheck/lint.go index fb911f3377..b7c0f91375 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/lint.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/lint.go @@ -1144,17 +1144,31 @@ func CheckDubiousDeferInChannelRangeLoop(pass *analysis.Pass) (interface{}, erro if !ok { return } + + stmts := []*ast.DeferStmt{} + exits := false fn2 := func(node ast.Node) bool { switch stmt := node.(type) { case *ast.DeferStmt: - report.Report(pass, stmt, "defers in this range loop won't run unless the channel gets closed") + stmts = append(stmts, stmt) case *ast.FuncLit: // Don't look into function bodies return false + case *ast.ReturnStmt: + exits = true + case *ast.BranchStmt: + exits = node.(*ast.BranchStmt).Tok == token.BREAK } return true } ast.Inspect(loop.Body, fn2) + + if exits { + return + } + for _, stmt := range stmts { + report.Report(pass, stmt, "defers in this range loop won't run unless the channel gets closed") + } } code.Preorder(pass, fn, (*ast.RangeStmt)(nil)) return nil, nil @@ -1598,7 +1612,7 @@ func CheckEmptyCriticalSection(pass *analysis.Pass) (interface{}, error) { if !ok { return nil, "", false } - call, ok := expr.X.(*ast.CallExpr) + call, ok := astutil.Unparen(expr.X).(*ast.CallExpr) if !ok { return nil, "", false } @@ -3168,7 +3182,7 @@ func CheckDeprecated(pass *analysis.Pass) (interface{}, error) { obj := pass.TypesInfo.ObjectOf(sel.Sel) if obj_, ok := obj.(*types.Func); ok { - obj = typeparams.OriginMethod(obj_) + obj = obj_.Origin() } if obj.Pkg() == nil { return true diff --git a/tools/vendor/honnef.co/go/tools/stylecheck/names.go b/tools/vendor/honnef.co/go/tools/stylecheck/names.go index f038d0632f..d37bf6baf6 100644 --- a/tools/vendor/honnef.co/go/tools/stylecheck/names.go +++ b/tools/vendor/honnef.co/go/tools/stylecheck/names.go @@ -114,7 +114,11 @@ func CheckNames(pass *analysis.Pass) (interface{}, error) { return } - if code.IsInTest(pass, v) && (strings.HasPrefix(v.Name.Name, "Example") || strings.HasPrefix(v.Name.Name, "Test") || strings.HasPrefix(v.Name.Name, "Benchmark")) { + if code.IsInTest(pass, v) && + (strings.HasPrefix(v.Name.Name, "Example") || + strings.HasPrefix(v.Name.Name, "Test") || + strings.HasPrefix(v.Name.Name, "Benchmark") || + strings.HasPrefix(v.Name.Name, "Fuzz")) { return } diff --git a/tools/vendor/honnef.co/go/tools/unused/unused.go b/tools/vendor/honnef.co/go/tools/unused/unused.go index fdf6d32cb2..edd2630759 100644 --- a/tools/vendor/honnef.co/go/tools/unused/unused.go +++ b/tools/vendor/honnef.co/go/tools/unused/unused.go @@ -17,7 +17,6 @@ import ( "honnef.co/go/tools/go/ast/astutil" "honnef.co/go/tools/go/types/typeutil" - "golang.org/x/exp/typeparams" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/types/objectpath" ) @@ -1151,8 +1150,7 @@ func (g *graph) decl(decl ast.Decl, by types.Object) { } case *ast.FuncDecl: - // XXX calling OriginMethod is unnecessary if we use types.Func.Origin - obj := typeparams.OriginMethod(g.info.ObjectOf(decl.Name).(*types.Func)) + obj := g.info.ObjectOf(decl.Name).(*types.Func).Origin() g.see(obj, nil) if token.IsExported(decl.Name.Name) && g.opts.ExportedIsUsed { @@ -1332,7 +1330,7 @@ func (g *graph) stmt(stmt ast.Stmt, by types.Object) { g.read(comm.Chan, by) g.read(comm.Value, by) case *ast.ExprStmt: - g.read(comm.X.(*ast.UnaryExpr).X, by) + g.read(astutil.Unparen(comm.X).(*ast.UnaryExpr).X, by) case *ast.AssignStmt: for _, lhs := range comm.Lhs { g.write(lhs, by) diff --git a/tools/vendor/modules.txt b/tools/vendor/modules.txt index f5eb7d396c..4d6e2480df 100644 --- a/tools/vendor/modules.txt +++ b/tools/vendor/modules.txt @@ -4,18 +4,25 @@ # 4d63.com/gochecknoglobals v0.2.1 ## explicit; go 1.15 4d63.com/gochecknoglobals/checknoglobals -# github.com/4meepo/tagalign v1.2.2 +# github.com/4meepo/tagalign v1.3.3 ## explicit; go 1.19 github.com/4meepo/tagalign -# github.com/Abirdcfly/dupword v0.0.11 +# github.com/Abirdcfly/dupword v0.0.13 ## explicit; go 1.20 github.com/Abirdcfly/dupword -# github.com/Antonboom/errname v0.1.10 +# github.com/Antonboom/errname v0.1.12 ## explicit; go 1.20 github.com/Antonboom/errname/pkg/analyzer -# github.com/Antonboom/nilnil v0.1.5 +# github.com/Antonboom/nilnil v0.1.7 ## explicit; go 1.20 github.com/Antonboom/nilnil/pkg/analyzer +# github.com/Antonboom/testifylint v0.2.3 +## explicit; go 1.20 +github.com/Antonboom/testifylint/analyzer +github.com/Antonboom/testifylint/internal/analysisutil +github.com/Antonboom/testifylint/internal/checkers +github.com/Antonboom/testifylint/internal/config +github.com/Antonboom/testifylint/internal/testify # github.com/BurntSushi/toml v1.3.2 ## explicit; go 1.16 github.com/BurntSushi/toml @@ -23,9 +30,11 @@ github.com/BurntSushi/toml/internal # github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 ## explicit; go 1.13 github.com/Djarvur/go-err113 -# github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 -## explicit; go 1.19 -github.com/GaijinEntertainment/go-exhaustruct/v2/pkg/analyzer +# github.com/GaijinEntertainment/go-exhaustruct/v3 v3.1.0 +## explicit; go 1.20 +github.com/GaijinEntertainment/go-exhaustruct/v3/analyzer +github.com/GaijinEntertainment/go-exhaustruct/v3/internal/fields +github.com/GaijinEntertainment/go-exhaustruct/v3/internal/pattern # github.com/Masterminds/semver v1.5.0 ## explicit github.com/Masterminds/semver @@ -33,6 +42,9 @@ github.com/Masterminds/semver ## explicit; go 1.20 github.com/OpenPeeDeeP/depguard/v2 github.com/OpenPeeDeeP/depguard/v2/internal/utils +# github.com/alecthomas/go-check-sumtype v0.1.3 +## explicit; go 1.18 +github.com/alecthomas/go-check-sumtype # github.com/alexkohler/nakedret/v2 v2.0.2 ## explicit; go 1.18 github.com/alexkohler/nakedret/v2 @@ -42,7 +54,7 @@ github.com/alexkohler/prealloc/pkg # github.com/alingse/asasalint v0.0.11 ## explicit; go 1.18 github.com/alingse/asasalint -# github.com/ashanbrown/forbidigo v1.5.3 +# github.com/ashanbrown/forbidigo v1.6.0 ## explicit; go 1.13 github.com/ashanbrown/forbidigo/forbidigo # github.com/ashanbrown/makezero v1.1.1 @@ -60,14 +72,14 @@ github.com/blizzy78/varnamelen # github.com/bombsimon/wsl/v3 v3.4.0 ## explicit; go 1.19 github.com/bombsimon/wsl/v3 -# github.com/breml/bidichk v0.2.4 -## explicit; go 1.19 +# github.com/breml/bidichk v0.2.7 +## explicit; go 1.20 github.com/breml/bidichk/pkg/bidichk -# github.com/breml/errchkjson v0.3.1 -## explicit; go 1.17 +# github.com/breml/errchkjson v0.3.6 +## explicit; go 1.20 github.com/breml/errchkjson -# github.com/butuzov/ireturn v0.2.0 -## explicit; go 1.15 +# github.com/butuzov/ireturn v0.2.2 +## explicit; go 1.18 github.com/butuzov/ireturn/analyzer github.com/butuzov/ireturn/analyzer/internal/config github.com/butuzov/ireturn/analyzer/internal/types @@ -75,6 +87,20 @@ github.com/butuzov/ireturn/analyzer/internal/types ## explicit; go 1.19 github.com/butuzov/mirror github.com/butuzov/mirror/internal/checker +# github.com/catenacyber/perfsprint v0.2.0 +## explicit; go 1.20 +github.com/catenacyber/perfsprint/analyzer +# github.com/ccojocar/zxcvbn-go v1.0.1 +## explicit; go 1.20 +github.com/ccojocar/zxcvbn-go +github.com/ccojocar/zxcvbn-go/adjacency +github.com/ccojocar/zxcvbn-go/data +github.com/ccojocar/zxcvbn-go/entropy +github.com/ccojocar/zxcvbn-go/frequency +github.com/ccojocar/zxcvbn-go/match +github.com/ccojocar/zxcvbn-go/matching +github.com/ccojocar/zxcvbn-go/scoring +github.com/ccojocar/zxcvbn-go/utils/math # github.com/cespare/xxhash/v2 v2.1.2 ## explicit; go 1.11 github.com/cespare/xxhash/v2 @@ -84,14 +110,14 @@ github.com/cfergeau/gomod2rpmdeps/cmd/gomod2rpmdeps # github.com/charithe/durationcheck v0.0.10 ## explicit; go 1.14 github.com/charithe/durationcheck -# github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 +# github.com/chavacava/garif v0.1.0 ## explicit; go 1.16 github.com/chavacava/garif # github.com/curioswitch/go-reassign v0.2.0 ## explicit; go 1.18 github.com/curioswitch/go-reassign github.com/curioswitch/go-reassign/internal/analyzer -# github.com/daixiang0/gci v0.10.1 +# github.com/daixiang0/gci v0.11.2 ## explicit; go 1.18 github.com/daixiang0/gci/pkg/config github.com/daixiang0/gci/pkg/format @@ -129,7 +155,10 @@ github.com/fsnotify/fsnotify # github.com/fzipp/gocyclo v0.6.0 ## explicit; go 1.18 github.com/fzipp/gocyclo -# github.com/go-critic/go-critic v0.8.1 +# github.com/ghostiam/protogetter v0.2.3 +## explicit; go 1.19 +github.com/ghostiam/protogetter +# github.com/go-critic/go-critic v0.9.0 ## explicit; go 1.18 github.com/go-critic/go-critic/checkers github.com/go-critic/go-critic/checkers/internal/astwalk @@ -195,14 +224,13 @@ github.com/golangci/dupl/syntax/golang # github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe ## explicit; go 1.17 github.com/golangci/go-misc/deadcode -# github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2 -## explicit; go 1.18 +# github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e +## explicit; go 1.20 github.com/golangci/gofmt/gofmt github.com/golangci/gofmt/gofmt/internal/diff -github.com/golangci/gofmt/gofmt/internal/execabs github.com/golangci/gofmt/goimports -# github.com/golangci/golangci-lint v1.53.3 -## explicit; go 1.19 +# github.com/golangci/golangci-lint v1.55.2 +## explicit; go 1.20 github.com/golangci/golangci-lint/cmd/golangci-lint github.com/golangci/golangci-lint/internal/cache github.com/golangci/golangci-lint/internal/errorutil @@ -234,16 +262,16 @@ github.com/golangci/lint-1 # github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca ## explicit github.com/golangci/maligned -# github.com/golangci/misspell v0.4.0 -## explicit; go 1.18 +# github.com/golangci/misspell v0.4.1 +## explicit; go 1.19 github.com/golangci/misspell -# github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6 -## explicit; go 1.17 +# github.com/golangci/revgrep v0.5.2 +## explicit; go 1.19 github.com/golangci/revgrep # github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 ## explicit github.com/golangci/unconvert -# github.com/google/go-cmp v0.5.9 +# github.com/google/go-cmp v0.6.0 ## explicit; go 1.13 github.com/google/go-cmp/cmp github.com/google/go-cmp/cmp/internal/diff @@ -295,7 +323,7 @@ github.com/hexops/gotextdiff/span # github.com/inconshreveable/mousetrap v1.1.0 ## explicit; go 1.18 github.com/inconshreveable/mousetrap -# github.com/jgautheron/goconst v1.5.1 +# github.com/jgautheron/goconst v1.6.0 ## explicit; go 1.13 github.com/jgautheron/goconst # github.com/jingyugao/rowserrcheck v1.1.1 @@ -320,7 +348,7 @@ github.com/kkHAIKE/contextcheck # github.com/kulti/thelper v0.6.3 ## explicit; go 1.18 github.com/kulti/thelper/pkg/analyzer -# github.com/kunwardeep/paralleltest v1.0.7 +# github.com/kunwardeep/paralleltest v1.0.8 ## explicit; go 1.17 github.com/kunwardeep/paralleltest/pkg/paralleltest # github.com/kyoh86/exportloopref v0.1.11 @@ -343,6 +371,9 @@ github.com/leonklingele/grouper/pkg/analyzer/vars # github.com/lufeee/execinquery v1.2.1 ## explicit; go 1.17 github.com/lufeee/execinquery +# github.com/macabu/inamedparam v0.1.2 +## explicit; go 1.19 +github.com/macabu/inamedparam # github.com/magiconair/properties v1.8.6 ## explicit; go 1.13 github.com/magiconair/properties @@ -370,10 +401,11 @@ github.com/matttproud/golang_protobuf_extensions/pbutil # github.com/mbilski/exhaustivestruct v1.2.0 ## explicit; go 1.15 github.com/mbilski/exhaustivestruct/pkg/analyzer -# github.com/mgechev/revive v1.3.2 -## explicit; go 1.19 +# github.com/mgechev/revive v1.3.4 +## explicit; go 1.20 github.com/mgechev/revive/config github.com/mgechev/revive/formatter +github.com/mgechev/revive/internal/ifelse github.com/mgechev/revive/internal/typeparams github.com/mgechev/revive/lint github.com/mgechev/revive/rule @@ -391,26 +423,16 @@ github.com/moricho/tparallel/pkg/ssainstr # github.com/nakabonne/nestif v0.3.1 ## explicit; go 1.15 github.com/nakabonne/nestif -# github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 -## explicit; go 1.14 -github.com/nbutton23/zxcvbn-go -github.com/nbutton23/zxcvbn-go/adjacency -github.com/nbutton23/zxcvbn-go/data -github.com/nbutton23/zxcvbn-go/entropy -github.com/nbutton23/zxcvbn-go/frequency -github.com/nbutton23/zxcvbn-go/match -github.com/nbutton23/zxcvbn-go/matching -github.com/nbutton23/zxcvbn-go/scoring -github.com/nbutton23/zxcvbn-go/utils/math # github.com/nishanths/exhaustive v0.11.0 ## explicit; go 1.18 github.com/nishanths/exhaustive # github.com/nishanths/predeclared v0.2.2 ## explicit; go 1.14 github.com/nishanths/predeclared/passes/predeclared -# github.com/nunnatsa/ginkgolinter v0.12.1 -## explicit; go 1.19 +# github.com/nunnatsa/ginkgolinter v0.14.1 +## explicit; go 1.20 github.com/nunnatsa/ginkgolinter +github.com/nunnatsa/ginkgolinter/ginkgohandler github.com/nunnatsa/ginkgolinter/gomegahandler github.com/nunnatsa/ginkgolinter/reverseassertion github.com/nunnatsa/ginkgolinter/types @@ -430,7 +452,7 @@ github.com/pelletier/go-toml/v2/internal/tracker # github.com/pmezard/go-difflib v1.0.0 ## explicit github.com/pmezard/go-difflib/difflib -# github.com/polyfloyd/go-errorlint v1.4.2 +# github.com/polyfloyd/go-errorlint v1.4.5 ## explicit; go 1.20 github.com/polyfloyd/go-errorlint/errorlint # github.com/prometheus/client_golang v1.12.1 @@ -451,8 +473,8 @@ github.com/prometheus/common/model github.com/prometheus/procfs github.com/prometheus/procfs/internal/fs github.com/prometheus/procfs/internal/util -# github.com/quasilyte/go-ruleguard v0.3.19 -## explicit; go 1.17 +# github.com/quasilyte/go-ruleguard v0.4.0 +## explicit; go 1.19 github.com/quasilyte/go-ruleguard/internal/goenv github.com/quasilyte/go-ruleguard/internal/golist github.com/quasilyte/go-ruleguard/internal/xsrcimporter @@ -485,8 +507,8 @@ github.com/randall77/makefat # github.com/ryancurrah/gomodguard v1.3.0 ## explicit; go 1.19 github.com/ryancurrah/gomodguard -# github.com/ryanrolds/sqlclosecheck v0.4.0 -## explicit; go 1.19 +# github.com/ryanrolds/sqlclosecheck v0.5.1 +## explicit; go 1.20 github.com/ryanrolds/sqlclosecheck/pkg/analyzer # github.com/sanposhiho/wastedassign/v2 v2.0.7 ## explicit; go 1.14 @@ -494,12 +516,12 @@ github.com/sanposhiho/wastedassign/v2 # github.com/sashamelentyev/interfacebloat v1.1.0 ## explicit; go 1.18 github.com/sashamelentyev/interfacebloat/pkg/analyzer -# github.com/sashamelentyev/usestdlibvars v1.23.0 -## explicit; go 1.19 +# github.com/sashamelentyev/usestdlibvars v1.24.0 +## explicit; go 1.20 github.com/sashamelentyev/usestdlibvars/pkg/analyzer github.com/sashamelentyev/usestdlibvars/pkg/analyzer/internal/mapping -# github.com/securego/gosec/v2 v2.16.0 -## explicit; go 1.19 +# github.com/securego/gosec/v2 v2.18.2 +## explicit; go 1.20 github.com/securego/gosec/v2 github.com/securego/gosec/v2/analyzers github.com/securego/gosec/v2/cwe @@ -577,8 +599,8 @@ github.com/t-yuki/gocover-cobertura # github.com/tdakkota/asciicheck v0.2.0 ## explicit; go 1.18 github.com/tdakkota/asciicheck -# github.com/tetafro/godot v1.4.11 -## explicit; go 1.16 +# github.com/tetafro/godot v1.4.15 +## explicit; go 1.20 github.com/tetafro/godot # github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 ## explicit; go 1.12 @@ -600,16 +622,16 @@ github.com/tomarrell/wrapcheck/v2/wrapcheck github.com/tommy-muehle/go-mnd/v2 github.com/tommy-muehle/go-mnd/v2/checks github.com/tommy-muehle/go-mnd/v2/config -# github.com/ultraware/funlen v0.0.3 -## explicit +# github.com/ultraware/funlen v0.1.0 +## explicit; go 1.20 github.com/ultraware/funlen # github.com/ultraware/whitespace v0.0.5 ## explicit github.com/ultraware/whitespace -# github.com/uudashr/gocognit v1.0.6 +# github.com/uudashr/gocognit v1.1.2 ## explicit; go 1.16 github.com/uudashr/gocognit -# github.com/xen0n/gosmopolitan v1.2.1 +# github.com/xen0n/gosmopolitan v1.2.2 ## explicit; go 1.19 github.com/xen0n/gosmopolitan # github.com/yagipy/maintidx v1.0.0 @@ -620,13 +642,16 @@ github.com/yagipy/maintidx/pkg/halstvol # github.com/yeya24/promlinter v0.2.0 ## explicit; go 1.16 github.com/yeya24/promlinter -# github.com/ykadowak/zerologlint v0.1.2 +# github.com/ykadowak/zerologlint v0.1.3 ## explicit; go 1.19 github.com/ykadowak/zerologlint -# gitlab.com/bosi/decorder v0.2.3 -## explicit; go 1.17 +# gitlab.com/bosi/decorder v0.4.1 +## explicit; go 1.20 gitlab.com/bosi/decorder -# go.tmz.dev/musttag v0.7.0 +# go-simpler.org/sloglint v0.1.2 +## explicit; go 1.20 +go-simpler.org/sloglint +# go.tmz.dev/musttag v0.7.2 ## explicit; go 1.19 go.tmz.dev/musttag # go.uber.org/atomic v1.7.0 @@ -648,7 +673,7 @@ go.uber.org/zap/zapcore ## explicit; go 1.20 golang.org/x/exp/constraints golang.org/x/exp/slices -# golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2 +# golang.org/x/exp/typeparams v0.0.0-20230307190834-24139beb5833 ## explicit; go 1.18 golang.org/x/exp/typeparams # golang.org/x/mod v0.15.0 @@ -665,7 +690,7 @@ golang.org/x/sync/semaphore ## explicit; go 1.18 golang.org/x/sys/unix golang.org/x/sys/windows -# golang.org/x/text v0.11.0 +# golang.org/x/text v0.13.0 ## explicit; go 1.17 golang.org/x/text/runes golang.org/x/text/transform @@ -675,6 +700,7 @@ golang.org/x/text/width ## explicit; go 1.18 golang.org/x/tools/cmd/goimports golang.org/x/tools/go/analysis +golang.org/x/tools/go/analysis/passes/appends golang.org/x/tools/go/analysis/passes/asmdecl golang.org/x/tools/go/analysis/passes/assign golang.org/x/tools/go/analysis/passes/atomic @@ -687,6 +713,8 @@ golang.org/x/tools/go/analysis/passes/composite golang.org/x/tools/go/analysis/passes/copylock golang.org/x/tools/go/analysis/passes/ctrlflow golang.org/x/tools/go/analysis/passes/deepequalerrors +golang.org/x/tools/go/analysis/passes/defers +golang.org/x/tools/go/analysis/passes/directive golang.org/x/tools/go/analysis/passes/errorsas golang.org/x/tools/go/analysis/passes/fieldalignment golang.org/x/tools/go/analysis/passes/findcall @@ -705,6 +733,7 @@ golang.org/x/tools/go/analysis/passes/reflectvaluecompare golang.org/x/tools/go/analysis/passes/shadow golang.org/x/tools/go/analysis/passes/shift golang.org/x/tools/go/analysis/passes/sigchanyzer +golang.org/x/tools/go/analysis/passes/slog golang.org/x/tools/go/analysis/passes/sortslice golang.org/x/tools/go/analysis/passes/stdmethods golang.org/x/tools/go/analysis/passes/stringintconv @@ -789,7 +818,7 @@ gopkg.in/yaml.v2 # gopkg.in/yaml.v3 v3.0.1 ## explicit gopkg.in/yaml.v3 -# honnef.co/go/tools v0.4.3 +# honnef.co/go/tools v0.4.6 ## explicit; go 1.19 honnef.co/go/tools/analysis/code honnef.co/go/tools/analysis/edit