diff options
| author | dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> | 2024-03-04 17:40:11 +0000 |
|---|---|---|
| committer | Taras Madan <tarasmadan@google.com> | 2024-03-04 18:34:55 +0000 |
| commit | 5fc5366972c874b919f93165bb4ed4e2bcb7c350 (patch) | |
| tree | 287c3361a0dee0c72af80d9a1a66714a06e98a62 | |
| parent | 1be5ce38a9059c356eb193a8c34d60d61c9fc31f (diff) | |
mod: bump github.com/golangci/golangci-lint from 1.55.2 to 1.56.2
Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.55.2 to 1.56.2.
- [Release notes](https://github.com/golangci/golangci-lint/releases)
- [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/golangci/golangci-lint/compare/v1.55.2...v1.56.2)
---
updated-dependencies:
- dependency-name: github.com/golangci/golangci-lint
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot] <support@github.com>
436 files changed, 15302 insertions, 10694 deletions
@@ -9,7 +9,7 @@ require ( cloud.google.com/go/secretmanager v1.11.5 cloud.google.com/go/storage v1.39.0 github.com/dvyukov/go-fuzz v0.0.0-20220726122315-1d375ef9f9f6 - github.com/golangci/golangci-lint v1.55.2 + github.com/golangci/golangci-lint v1.56.2 github.com/google/go-cmp v0.6.0 github.com/gorilla/handlers v1.5.2 github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab @@ -21,7 +21,7 @@ require ( golang.org/x/perf v0.0.0-20230221235046-aebcfb61e84c golang.org/x/sync v0.6.0 golang.org/x/sys v0.17.0 - golang.org/x/tools v0.14.0 + golang.org/x/tools v0.18.0 google.golang.org/api v0.167.0 google.golang.org/appengine/v2 v2.0.5 google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 @@ -40,13 +40,13 @@ require ( 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/Antonboom/testifylint v1.1.2 // indirect github.com/BurntSushi/toml v1.3.2 // indirect github.com/Djarvur/go-err113 v0.1.0 // indirect - github.com/GaijinEntertainment/go-exhaustruct/v3 v3.1.0 // indirect + github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.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/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect + github.com/alecthomas/go-check-sumtype v0.1.4 // 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 @@ -55,39 +55,40 @@ require ( 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/bombsimon/wsl/v4 v4.2.1 // 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/ireturn v0.3.0 // 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/catenacyber/perfsprint v0.6.0 // indirect + github.com/ccojocar/zxcvbn-go v1.0.2 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/charithe/durationcheck v0.0.10 // indirect github.com/chavacava/garif v0.1.0 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect - github.com/daixiang0/gci v0.11.2 // indirect + github.com/daixiang0/gci v0.12.1 // 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 - github.com/ettle/strcase v0.1.1 // indirect - github.com/fatih/color v1.15.0 // indirect + github.com/ettle/strcase v0.2.0 // indirect + github.com/fatih/color v1.16.0 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/firefart/nonamedreturns v1.0.4 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/ghostiam/protogetter v0.2.3 // indirect - github.com/go-critic/go-critic v0.9.0 // indirect + github.com/ghostiam/protogetter v0.3.4 // indirect + github.com/go-critic/go-critic v0.11.1 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // 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 + github.com/go-toolsmith/astequal v1.2.0 // indirect github.com/go-toolsmith/astfmt v1.1.0 // indirect github.com/go-toolsmith/astp v1.1.0 // indirect github.com/go-toolsmith/strparse v1.1.0 // indirect github.com/go-toolsmith/typep v1.1.0 // indirect + github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gofrs/flock v0.8.1 // indirect @@ -108,52 +109,51 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.1 // indirect - github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 // indirect + github.com/gordonklaus/ineffassign v0.1.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/comment v1.4.2 // indirect github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect github.com/gostaticanalysis/nilerr v0.1.1 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-version v1.6.0 // indirect 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.6.0 // indirect + github.com/jgautheron/goconst v1.7.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/jjti/go-spancheck v0.5.2 // indirect github.com/julz/importas v0.1.0 // indirect - github.com/kisielk/errcheck v1.6.3 // indirect + github.com/kisielk/errcheck v1.7.0 // indirect 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.8 // indirect + github.com/kunwardeep/paralleltest v1.0.9 // 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/macabu/inamedparam v0.1.3 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/maratori/testableexamples v1.0.0 // indirect github.com/maratori/testpackage v1.1.1 // indirect github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mbilski/exhaustivestruct v1.2.0 // indirect - github.com/mgechev/revive v1.3.4 // indirect + github.com/mgechev/revive v1.3.7 // 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/nishanths/exhaustive v0.11.0 // indirect + github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.14.1 // indirect + github.com/nunnatsa/ginkgolinter v0.15.2 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/polyfloyd/go-errorlint v1.4.5 // indirect + github.com/polyfloyd/go-errorlint v1.4.8 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect @@ -166,8 +166,8 @@ require ( 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.24.0 // indirect - github.com/securego/gosec/v2 v2.18.2 // indirect + github.com/sashamelentyev/usestdlibvars v1.25.0 // indirect + github.com/securego/gosec/v2 v2.19.0 // 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 @@ -175,7 +175,7 @@ require ( github.com/sivchari/tenv v1.7.1 // indirect github.com/sonatard/noctx v0.0.2 // indirect github.com/sourcegraph/go-diff v0.7.0 // indirect - github.com/spf13/afero v1.9.3 // indirect + github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cobra v1.7.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect @@ -187,34 +187,34 @@ require ( github.com/subosito/gotenv v1.4.2 // 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.15 // indirect + github.com/tetafro/godot v1.4.16 // 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.1.0 // indirect - github.com/ultraware/whitespace v0.0.5 // indirect + github.com/ultraware/whitespace v0.1.0 // 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.3 // indirect + github.com/ykadowak/zerologlint v0.1.5 // indirect gitlab.com/bosi/decorder v0.4.1 // indirect - go-simpler.org/sloglint v0.1.2 // indirect + go-simpler.org/musttag v0.8.0 // indirect + go-simpler.org/sloglint v0.4.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 // indirect go.opentelemetry.io/otel v1.23.0 // indirect go.opentelemetry.io/otel/metric v1.23.0 // indirect go.opentelemetry.io/otel/trace v1.23.0 // indirect - go.tmz.dev/musttag v0.7.2 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/crypto v0.19.0 // indirect - golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect - golang.org/x/exp/typeparams v0.0.0-20230307190834-24139beb5833 // indirect - golang.org/x/mod v0.13.0 // indirect + golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect + golang.org/x/exp/typeparams v0.0.0-20231219180239-dc181d75b848 // indirect + golang.org/x/mod v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/appengine v1.6.8 // indirect @@ -225,8 +225,8 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect honnef.co/go/tools v0.4.6 // indirect - mvdan.cc/gofumpt v0.5.0 // indirect + mvdan.cc/gofumpt v0.6.0 // indirect mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect - mvdan.cc/unparam v0.0.0-20230125043941-70a0ce6e7b95 // indirect + mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 // indirect ) @@ -8,7 +8,6 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= @@ -19,9 +18,6 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -58,7 +54,6 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 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= cloud.google.com/go/storage v1.39.0 h1:brbjUa4hbDHhpQf48tjqMaXEV+f1OGoaTmQau9tmCsA= cloud.google.com/go/storage v1.39.0/go.mod h1:OAEj/WZwUYjA3YHQ10/YcN9ttGuEpLwvaoyBXIPikEk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -71,28 +66,28 @@ github.com/Antonboom/errname v0.1.12 h1:oh9ak2zUtsLp5oaEd/erjB4GPu9w19NyoIskZClD 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/Antonboom/testifylint v1.1.2 h1:IdLRermiLRogxY5AumBL4sP0A+qKHQM/AP1Xd7XOTKc= +github.com/Antonboom/testifylint v1.1.2/go.mod h1:9PFi+vWa8zzl4/B/kqmFJcw85ZUv8ReyBzuQCd30+WI= 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.1.0 h1:uCRZZOdMQ0TZPHYTdYpoC0bLYJKPEHPUJ8MeAa51lNU= github.com/Djarvur/go-err113 v0.1.0/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= -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/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 h1:sATXp1x6/axKxz2Gjxv8MALP0bXaNRfQinEwyfMcx8c= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0/go.mod h1:Nl76DrGNJTA1KJ0LePKBw/vznBX1EHbAZX8mwjR82nI= github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20190129172621-c8b1d7a94ddf/go.mod h1:aJ4qN3TfrelA6NZ6AXsXRfmEVaYin3EDbSPJrKS8OXo= 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/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= +github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= github.com/aclements/go-gg v0.0.0-20170118225347-6dbb4e4fefb0/go.mod h1:55qNq4vcpkIuHowELi5C8e+1yUHtoLoOUR9QU5j7Tes= github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794/go.mod h1:7e+I0LQFUI9AXWxOfsQROs9xPhoJtbsyWcjJqDd4KPY= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/ajstarks/svgo v0.0.0-20210923152817-c3b6e2f0c527/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= 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/go-check-sumtype v0.1.4 h1:WCvlB3l5Vq5dZQTFmodqL2g68uHiSwwlWcT5a2FGK0c= +github.com/alecthomas/go-check-sumtype v0.1.4/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= @@ -118,22 +113,22 @@ github.com/bkielbasa/cyclop v1.2.1 h1:AeF71HZDob1P2/pRm1so9cd1alZnrpyc4q2uP2l0gJ github.com/bkielbasa/cyclop v1.2.1/go.mod h1:K/dT/M0FPAiYjBgQGau7tz+3TMh4FWAEqlMhzFWCrgM= github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= 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/bombsimon/wsl/v4 v4.2.1 h1:Cxg6u+XDWff75SIFFmNsqnIOgob+Q9hG6y/ioKbRFiM= +github.com/bombsimon/wsl/v4 v4.2.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= 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/ireturn v0.3.0 h1:hTjMqWw3y5JC3kpnC5vXmFJAWI/m31jaCYQqzkS6PL0= +github.com/butuzov/ireturn v0.3.0/go.mod h1:A09nIiwiqzN/IoVo9ogpa0Hzi9fex1kd9PSD6edP5ZA= 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/catenacyber/perfsprint v0.6.0 h1:VSv95RRkk5+BxrU/YTPcnxuMEWar1iMK5Vyh3fWcBfs= +github.com/catenacyber/perfsprint v0.6.0/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= +github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= +github.com/ccojocar/zxcvbn-go v1.0.2/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/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -148,14 +143,12 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbinPNFs5gPSBOsJtx3wTT94VBY= 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.11.2 h1:Oji+oPsp3bQ6bNNgX30NBAVT18P4uBH4sRZnlOlTj7Y= -github.com/daixiang0/gci v0.11.2/go.mod h1:xtHP9N7AHdNvtRNfcx9gwTDfw7FRJx4bZUsiEfiNNAI= +github.com/daixiang0/gci v0.12.1 h1:ugsG+KRYny1VK4oqrX4Vtj70bo4akYKa0tgT1DXMYiY= +github.com/daixiang0/gci v0.12.1/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= @@ -166,16 +159,14 @@ github.com/dvyukov/go-fuzz v0.0.0-20220726122315-1d375ef9f9f6/go.mod h1:11Gm+ccJ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= github.com/esimonov/ifshort v1.0.4 h1:6SID4yGWfRae/M7hkVDVVyppy8q/v9OuxNdmjLQStBA= github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0= -github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= -github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= +github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -184,15 +175,15 @@ github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phm github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -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/ghostiam/protogetter v0.3.4 h1:5SZ+lZSNmNkSbGVSF9hUHhv/b7ELF9Rwchoq7btYo6c= +github.com/ghostiam/protogetter v0.3.4/go.mod h1:A0JgIhs0fgVnotGinjQiKaFVG3waItLJNwPmcMzDnvk= +github.com/go-critic/go-critic v0.11.1 h1:/zBseUSUMytnRqxjlsYNbDDxpu3R2yH8oLXo/FOE8b8= +github.com/go-critic/go-critic v0.11.1/go.mod h1:aZVQR7+gazH6aDEQx4356SD7d8ez8MipYjXbEl5JAKA= github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= @@ -223,8 +214,9 @@ github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4 github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw= github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= -github.com/go-toolsmith/astequal v1.1.0 h1:kHKm1AWqClYn15R0K1KKE4RG614D46n+nqUQ06E1dTw= github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ= +github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw= +github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY= github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco= github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4= github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= @@ -235,6 +227,8 @@ github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQi github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= +github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c= +github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U= github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= @@ -283,8 +277,8 @@ github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe h1:6RGUuS7EGotKx6 github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe/go.mod h1:gjqyPShc/m8pEMpk0a3SeagVb0kaqvhscv+i9jI5ZhQ= 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/golangci-lint v1.56.2 h1:dgQzlWHgNbCqJjuxRJhFEnHDVrrjuTGQHJ3RIZMpp/o= +github.com/golangci/golangci-lint v1.56.2/go.mod h1:7CfNO675+EY7j84jihO4iAqDQ80s3HCjcc5M6B7SlZQ= 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= @@ -321,7 +315,6 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= 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= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -330,9 +323,6 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs= github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -351,9 +341,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.12.1 h1:9F8GV9r9ztXyAi00gsMQHNoF51xPZm8uj1dpYt2ZETM= github.com/googleapis/gax-go/v2 v2.12.1/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 h1:mrEEilTAUmaAORhssPPkxj84TsHrPMLBGW2Z4SoTxm8= -github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= +github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= +github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= @@ -367,11 +356,6 @@ github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3 github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= @@ -382,17 +366,18 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab h1:BA4a7pe6ZTd9F8kXETBoijjFJ/ntaa//1wiH9BZu4zU= github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= 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.6.0 h1:gbMLWKRMkzAc6kYsQL6/TxaoBUg3Jm9LSF/Ih1ADWGA= -github.com/jgautheron/goconst v1.6.0/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= +github.com/jgautheron/goconst v1.7.0 h1:cEqH+YBKLsECnRSd4F4TK5ri8t/aXtt/qoL0Ft252B0= +github.com/jgautheron/goconst v1.7.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= github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= +github.com/jjti/go-spancheck v0.5.2 h1:WXTZG3efY/ji1Vi8mkH+23O3bLeKR6hp3tI3YB7XwKk= +github.com/jjti/go-spancheck v0.5.2/go.mod h1:ARPNI1JRG1V2Rjnd6/2f2NEfghjSVDZGVmruNKlnXU0= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -406,15 +391,14 @@ github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/kisielk/errcheck v1.6.3 h1:dEKh+GLHcWm2oN34nMvDzn1sqI0i0WxPvrgiJA5JuM8= -github.com/kisielk/errcheck v1.6.3/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw= +github.com/kisielk/errcheck v1.7.0 h1:+SbscKmWJ5mOK/bO1zS60F5I9WwZDWOfRsC4RwfwRV0= +github.com/kisielk/errcheck v1.7.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkHAIKE/contextcheck v1.1.4 h1:B6zAaLhOEEcjvUgIYEqystmnFk1Oemn8bvJhbt0GMb8= github.com/kkHAIKE/contextcheck v1.1.4/go.mod h1:1+i/gWqokIa+dm31mqGLZhZJ7Uh44DJGZVmr6QRBNJg= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -423,8 +407,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.8 h1:Ul2KsqtzFxTlSU7IP0JusWlLiNqQaloB9vguyjbE558= -github.com/kunwardeep/paralleltest v1.0.8/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= +github.com/kunwardeep/paralleltest v1.0.9 h1:3Sr2IfFNcsMmlqPk1cjTUbJ4zofKPGyHxenwPebgTug= +github.com/kunwardeep/paralleltest v1.0.9/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= @@ -435,8 +419,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/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= +github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= @@ -450,8 +434,8 @@ github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwM github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -459,8 +443,8 @@ github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGw 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.4 h1:k/tO3XTaWY4DEHal9tWBkkUMJYO/dLDVyMmAQxmIMDc= -github.com/mgechev/revive v1.3.4/go.mod h1:W+pZCMu9qj8Uhfs1iJMQsEFLRozUfvwFwqVvRbSNLVw= +github.com/mgechev/revive v1.3.7 h1:502QY0vQGe9KtYJ9FpxMz9rL+Fc/P13CI5POL4uHCcE= +github.com/mgechev/revive v1.3.7/go.mod h1:RJ16jUbF0OWC3co/+XTxmFNgEpUPwnnA0BRllX2aDNA= 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= @@ -476,18 +460,18 @@ 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/nishanths/exhaustive v0.11.0 h1:T3I8nUGhl/Cwu5Z2hfc92l0e04D2GEW6e0l8pzda2l0= -github.com/nishanths/exhaustive v0.11.0/go.mod h1:RqwDsZ1xY0dNdqHho2z6X+bgzizwbLYOWnZbbl2wLB4= +github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= +github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= 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.14.1 h1:khx0CqR5U4ghsscjJ+lZVthp3zjIFytRXPTaQ/TMiyA= -github.com/nunnatsa/ginkgolinter v0.14.1/go.mod h1:nY0pafUSst7v7F637e7fymaMlQqI9c0Wka2fGsDkzWg= +github.com/nunnatsa/ginkgolinter v0.15.2 h1:N2ORxUxPU56R9gsfLIlVVvCv/V/VVou5qVI1oBKBNHg= +github.com/nunnatsa/ginkgolinter v0.15.2/go.mod h1:oYxE7dt1vZI8cK2rZOs3RgTaBN2vggkqnENmoJ8kVvc= 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.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= -github.com/onsi/gomega v1.28.1 h1:MijcGUbfYuznzK/5R4CPNoUP/9Xvuo20sXfEm6XxoTA= +github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= +github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo= 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/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= 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= @@ -501,11 +485,10 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -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.5 h1:70YWmMy4FgRHehGNOUask3HtSFSOLKgmDn7ryNe7LqI= -github.com/polyfloyd/go-errorlint v1.4.5/go.mod h1:sIZEbFoDOCnTYYZoVkjc4hTnM459tuWA9H/EkdXwsKk= +github.com/polyfloyd/go-errorlint v1.4.8 h1:jiEjKDH33ouFktyez7sckv6pHWif9B7SuS8cutDXFHw= +github.com/polyfloyd/go-errorlint v1.4.8/go.mod h1:NNCxFcFjZcw3xNjVdCchERkEM6Oz7wta2XJVxRftwO4= 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= @@ -544,7 +527,7 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= @@ -556,10 +539,10 @@ github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/ 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.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/sashamelentyev/usestdlibvars v1.25.0 h1:IK8SI2QyFzy/2OD2PYnhy84dpfNo9qADrRt6LH8vSzU= +github.com/sashamelentyev/usestdlibvars v1.25.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= +github.com/securego/gosec/v2 v2.19.0 h1:gl5xMkOI0/E6Hxx0XCY2XujA3V7SNSefA8sC+3f1gnk= +github.com/securego/gosec/v2 v2.19.0/go.mod h1:hOkDcHz9J/XIgIlPDXalxjeVYsHxoWUc5zJSHxcB8YM= 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= @@ -579,8 +562,8 @@ github.com/sonatard/noctx v0.0.2 h1:L7Dz4De2zDQhW8S0t+KUjY0MAQJd6SgVwhzNIc4ok00= github.com/sonatard/noctx v0.0.2/go.mod h1:kzFz+CzWSjQ2OzIm46uJZoXuBpa2+0y3T36U18dWqIo= github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= -github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= -github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= @@ -620,8 +603,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.15 h1:QzdIs+XB8q+U1WmQEWKHQbKmCw06QuQM7gLx/dky2RM= -github.com/tetafro/godot v1.4.15/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= +github.com/tetafro/godot v1.4.16 h1:4ChfhveiNLk4NveAZ9Pu2AN8QZ2nkUGFuadM9lrr5D0= +github.com/tetafro/godot v1.4.16/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= @@ -634,8 +617,8 @@ github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 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/ultraware/whitespace v0.1.0 h1:O1HKYoh0kIeqE8sFqZf1o0qbORXUCOQFrlaQyZsczZw= +github.com/ultraware/whitespace v0.1.0/go.mod h1:/se4r3beMFNmewJ4Xmz0nMQ941GJt+qmSHGP9emHYe0= 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= @@ -644,8 +627,8 @@ 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.3 h1:TLy1dTW3Nuc+YE3bYRPToG1Q9Ej78b5UUN6bjbGdxPE= -github.com/ykadowak/zerologlint v0.1.3/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= +github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= +github.com/ykadowak/zerologlint v0.1.5/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= @@ -655,16 +638,17 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 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-simpler.org/assert v0.7.0 h1:OzWWZqfNxt8cLS+MlUp6Tgk1HjPkmgdKBq9qvy8lZsA= +go-simpler.org/musttag v0.8.0 h1:DR4UTgetNNhPRNo02rkK1hwDTRzAPotN+ZqYpdtEwWc= +go-simpler.org/musttag v0.8.0/go.mod h1:fiNdCkXt2S6je9Eblma3okjnlva9NT1Eg/WUt19rWu8= +go-simpler.org/sloglint v0.4.0 h1:UVJuUJo63iNQNFEOtZ6o1xAgagVg/giVLLvG9nNLobI= +go-simpler.org/sloglint v0.4.0/go.mod h1:v6zJ++j/thFPhefs2wEXoCKwT10yo5nkBDYRCXyqgNQ= go.einride.tech/aip v0.66.0 h1:XfV+NQX6L7EOYK11yoHHFtndeaWh3KbD9/cN/6iWEt8= 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.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0 h1:P+/g8GpuJGYbOp2tAdKrIPUX9JO02q8Q0YNlHolpibA= @@ -678,8 +662,6 @@ go.opentelemetry.io/otel/metric v1.23.0/go.mod h1:MqUW2X2a6Q8RN96E2/nqNoT+z9BSms go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= go.opentelemetry.io/otel/trace v1.23.0 h1:37Ik5Ib7xfYVb4V1UtnT97T1jI+AoIYkJyPkuL4iJgI= go.opentelemetry.io/otel/trace v1.23.0/go.mod h1:GSGTbIClEsuZrGIzoEHqsVfxgn5UkggkflQwDScNUsk= -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.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= @@ -693,9 +675,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= @@ -713,12 +693,12 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4= -golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= +golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= 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-20230307190834-24139beb5833 h1:jWGQJV4niP+CCmFW9ekjA9Zx8vYORzOUH2/Nl5WPuLQ= -golang.org/x/exp/typeparams v0.0.0-20230307190834-24139beb5833/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20231219180239-dc181d75b848 h1:UhRVJ0i7bF9n/Hd8YjW3eKjlPVBHzbQdxrBgjbSKl64= +golang.org/x/exp/typeparams v0.0.0-20231219180239-dc181d75b848/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 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= @@ -740,7 +720,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -749,7 +728,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= @@ -758,8 +736,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -789,10 +767,7 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -810,10 +785,6 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= @@ -865,18 +836,13 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -893,6 +859,7 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -906,7 +873,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -971,14 +937,8 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201001104356-43ebab892c4c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= @@ -992,8 +952,8 @@ golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1023,9 +983,6 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.167.0 h1:CKHrQD1BLRii6xdkatBDXyKzM0mkawt2QP+H3LtPmSE= google.golang.org/api v0.167.0/go.mod h1:4FcBc686KFi7QI/U51/2GKKevfZMpM17sCdibqe/bSA= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -1034,7 +991,6 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/appengine/v2 v2.0.5 h1:4C+F3Cd3L2nWEfSmFEZDPjQvDwL8T0YCeZBysZifP3k= @@ -1068,13 +1024,6 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c h1:9g7erC9qu44ks7UK4gDNlnk4kOxZG707xKm4jVniy6o= @@ -1094,10 +1043,7 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -1142,14 +1088,14 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 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/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo= +mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= -mvdan.cc/unparam v0.0.0-20230125043941-70a0ce6e7b95 h1:n/xhncJPSt0YzfOhnyn41XxUdrWQNgmLBG72FE27Fqw= -mvdan.cc/unparam v0.0.0-20230125043941-70a0ce6e7b95/go.mod h1:2vU506e8nGWodqcci641NLi4im2twWSq4Lod756epHQ= +mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 h1:zCr3iRRgdk5eIikZNDphGcM6KGVTx3Yu+/Uu9Es254w= +mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14/go.mod h1:ZzZjEpJDOmx8TdVU6umamY3Xy0UAQUI2DHbf05USVbI= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= diff --git a/vendor/github.com/Antonboom/testifylint/analyzer/analyzer.go b/vendor/github.com/Antonboom/testifylint/analyzer/analyzer.go index c0b98f83c..84d7e815d 100644 --- a/vendor/github.com/Antonboom/testifylint/analyzer/analyzer.go +++ b/vendor/github.com/Antonboom/testifylint/analyzer/analyzer.go @@ -3,8 +3,6 @@ package analyzer import ( "fmt" "go/ast" - "go/types" - "strings" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/ast/inspector" @@ -79,58 +77,11 @@ func (tl *testifyLint) run(pass *analysis.Pass) (any, error) { } 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 { + call := checkers.NewCallMeta(pass, ce) + if nil == call { 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) @@ -140,36 +91,3 @@ func (tl *testifyLint) regularCheck(pass *analysis.Pass, ce *ast.CallExpr) { } } } - -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/vendor/github.com/Antonboom/testifylint/analyzer/checkers_factory.go b/vendor/github.com/Antonboom/testifylint/analyzer/checkers_factory.go index e87e35e50..de1d6017f 100644 --- a/vendor/github.com/Antonboom/testifylint/analyzer/checkers_factory.go +++ b/vendor/github.com/Antonboom/testifylint/analyzer/checkers_factory.go @@ -9,14 +9,34 @@ import ( // 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 err := cfg.Validate(); err != nil { + return nil, nil, err } + + enabledCheckersSet := make(map[string]struct{}) + if cfg.EnableAll { - enabledCheckers = checkers.All() + for _, checker := range checkers.All() { + enabledCheckersSet[checker] = struct{}{} + } + } else if !cfg.DisableAll { + for _, checker := range checkers.EnabledByDefault() { + enabledCheckersSet[checker] = struct{}{} + } + } + + for _, checker := range cfg.EnabledCheckers { + enabledCheckersSet[checker] = struct{}{} + } + + for _, checker := range cfg.DisabledCheckers { + delete(enabledCheckersSet, checker) } + enabledCheckers := make([]string, 0, len(enabledCheckersSet)) + for v := range enabledCheckersSet { + enabledCheckers = append(enabledCheckers, v) + } checkers.SortByPriority(enabledCheckers) regularCheckers := make([]checkers.RegularChecker, 0, len(enabledCheckers)) @@ -32,6 +52,9 @@ func newCheckers(cfg config.Config) ([]checkers.RegularChecker, []checkers.Advan case *checkers.ExpectedActual: c.SetExpVarPattern(cfg.ExpectedActual.ExpVarPattern.Regexp) + case *checkers.RequireError: + c.SetFnPattern(cfg.RequireError.FnPattern.Regexp) + case *checkers.SuiteExtraAssertCall: c.SetMode(cfg.SuiteExtraAssertCall.Mode) } diff --git a/vendor/github.com/Antonboom/testifylint/internal/analysisutil/object.go b/vendor/github.com/Antonboom/testifylint/internal/analysisutil/object.go index e01fba5c1..4e0346d2b 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/analysisutil/object.go +++ b/vendor/github.com/Antonboom/testifylint/internal/analysisutil/object.go @@ -30,5 +30,5 @@ func IsObj(typesInfo *types.Info, expr ast.Expr, expected types.Object) bool { } obj := typesInfo.ObjectOf(id) - return obj.Id() == expected.Id() + return obj == expected } diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/blank_import.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/blank_import.go new file mode 100644 index 000000000..403691e27 --- /dev/null +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/blank_import.go @@ -0,0 +1,69 @@ +package checkers + +import ( + "fmt" + "strconv" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/ast/inspector" + + "github.com/Antonboom/testifylint/internal/testify" +) + +// BlankImport detects useless blank imports of testify's packages. +// These imports are useless since testify doesn't do any magic with init() function. +// +// The checker detects situations like +// +// import ( +// "testing" +// +// _ "github.com/stretchr/testify" +// _ "github.com/stretchr/testify/assert" +// _ "github.com/stretchr/testify/http" +// _ "github.com/stretchr/testify/mock" +// _ "github.com/stretchr/testify/require" +// _ "github.com/stretchr/testify/suite" +// ) +// +// and requires +// +// import ( +// "testing" +// ) +type BlankImport struct{} + +// NewBlankImport constructs BlankImport checker. +func NewBlankImport() BlankImport { return BlankImport{} } +func (BlankImport) Name() string { return "blank-import" } + +func (checker BlankImport) Check(pass *analysis.Pass, _ *inspector.Inspector) (diagnostics []analysis.Diagnostic) { + for _, file := range pass.Files { + for _, imp := range file.Imports { + if imp.Name == nil || imp.Name.Name != "_" { + continue + } + + pkg, err := strconv.Unquote(imp.Path.Value) + if err != nil { + continue + } + if _, ok := packagesNotIntendedForBlankImport[pkg]; !ok { + continue + } + + msg := fmt.Sprintf("avoid blank import of %s as it does nothing", pkg) + diagnostics = append(diagnostics, *newDiagnostic(checker.Name(), imp, msg, nil)) + } + } + return diagnostics +} + +var packagesNotIntendedForBlankImport = map[string]struct{}{ + testify.ModulePath: {}, + testify.AssertPkgPath: {}, + testify.HTTPPkgPath: {}, + testify.MockPkgPath: {}, + testify.RequirePkgPath: {}, + testify.SuitePkgPath: {}, +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/bool_compare.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/bool_compare.go index 8245ab58e..c8db9420e 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/bool_compare.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/bool_compare.go @@ -13,7 +13,10 @@ import ( // BoolCompare detects situations like // // assert.Equal(t, false, result) -// assert.NotEqual(t, result, true) +// assert.EqualValues(t, false, result) +// assert.Exactly(t, false, result) +// assert.NotEqual(t, true, result) +// assert.NotEqualValues(t, true, result) // assert.False(t, !result) // assert.True(t, result == true) // ... @@ -60,13 +63,17 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. ) } - switch call.Fn.Name { - case "Equal", "Equalf": + switch call.Fn.NameFTrimmed { + case "Equal", "EqualValues", "Exactly": if len(call.Args) < 2 { return nil } arg1, arg2 := call.Args[0], call.Args[1] + if isEmptyInterface(pass, arg1) || isEmptyInterface(pass, arg2) { + return nil + } + t1, t2 := isUntypedTrue(pass, arg1), isUntypedTrue(pass, arg2) f1, f2 := isUntypedFalse(pass, arg1), isUntypedFalse(pass, arg2) @@ -80,12 +87,16 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. return newUseFalseDiagnostic(survivingArg, arg1.Pos(), arg2.End()) } - case "NotEqual", "NotEqualf": + case "NotEqual", "NotEqualValues": if len(call.Args) < 2 { return nil } arg1, arg2 := call.Args[0], call.Args[1] + if isEmptyInterface(pass, arg1) || isEmptyInterface(pass, arg2) { + return nil + } + t1, t2 := isUntypedTrue(pass, arg1), isUntypedTrue(pass, arg2) f1, f2 := isUntypedFalse(pass, arg1), isUntypedFalse(pass, arg2) @@ -99,7 +110,7 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. return newUseTrueDiagnostic(survivingArg, arg1.Pos(), arg2.End()) } - case "True", "Truef": + case "True": if len(call.Args) < 1 { return nil } @@ -109,7 +120,8 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. arg1, ok1 := isComparisonWithTrue(pass, expr, token.EQL) arg2, ok2 := isComparisonWithFalse(pass, expr, token.NEQ) - if survivingArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2); ok { + survivingArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2) + if ok && !isEmptyInterface(pass, survivingArg) { return newNeedSimplifyDiagnostic(survivingArg, expr.Pos(), expr.End()) } } @@ -119,12 +131,13 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. arg2, ok2 := isComparisonWithFalse(pass, expr, token.EQL) arg3, ok3 := isNegation(expr) - if survivingArg, ok := anyVal([]bool{ok1, ok2, ok3}, arg1, arg2, arg3); ok { + survivingArg, ok := anyVal([]bool{ok1, ok2, ok3}, arg1, arg2, arg3) + if ok && !isEmptyInterface(pass, survivingArg) { return newUseFalseDiagnostic(survivingArg, expr.Pos(), expr.End()) } } - case "False", "Falsef": + case "False": if len(call.Args) < 1 { return nil } @@ -134,7 +147,8 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. arg1, ok1 := isComparisonWithTrue(pass, expr, token.EQL) arg2, ok2 := isComparisonWithFalse(pass, expr, token.NEQ) - if survivingArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2); ok { + survivingArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2) + if ok && !isEmptyInterface(pass, survivingArg) { return newNeedSimplifyDiagnostic(survivingArg, expr.Pos(), expr.End()) } } @@ -144,7 +158,8 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. arg2, ok2 := isComparisonWithFalse(pass, expr, token.EQL) arg3, ok3 := isNegation(expr) - if survivingArg, ok := anyVal([]bool{ok1, ok2, ok3}, arg1, arg2, arg3); ok { + survivingArg, ok := anyVal([]bool{ok1, ok2, ok3}, arg1, arg2, arg3) + if ok && !isEmptyInterface(pass, survivingArg) { return newUseTrueDiagnostic(survivingArg, expr.Pos(), expr.End()) } } @@ -221,3 +236,13 @@ func anyVal[T any](bools []bool, vals ...T) (T, bool) { var _default T return _default, false } + +func isEmptyInterface(pass *analysis.Pass, expr ast.Expr) bool { + t, ok := pass.TypesInfo.Types[expr] + if !ok { + return false + } + + iface, ok := t.Type.Underlying().(*types.Interface) + return ok && iface.NumMethods() == 0 +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/call_meta.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/call_meta.go new file mode 100644 index 000000000..44eed49a6 --- /dev/null +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/call_meta.go @@ -0,0 +1,136 @@ +package checkers + +import ( + "go/ast" + "go/types" + "strings" + + "golang.org/x/tools/go/analysis" + + "github.com/Antonboom/testifylint/internal/analysisutil" + "github.com/Antonboom/testifylint/internal/testify" +) + +// 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 +} + +func (c CallMeta) String() string { + return c.SelectorXStr + "." + c.Fn.Name +} + +// 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 + // NameFTrimmed is a function name without "f" suffix. + NameFTrimmed string + // IsFmt is true if function is formatted, e.g. "Equalf". + IsFmt bool +} + +// NewCallMeta returns meta information about testify assertion call. +// Returns nil if ast.CallExpr is not testify call. +func NewCallMeta(pass *analysis.Pass, ce *ast.CallExpr) *CallMeta { + se, ok := ce.Fun.(*ast.SelectorExpr) + if !ok || se.Sel == nil { + return nil + } + 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 nil + } + + isAssert := analysisutil.IsPkg(initiatorPkg, testify.AssertPkgName, testify.AssertPkgPath) + isRequire := analysisutil.IsPkg(initiatorPkg, testify.RequirePkgName, testify.RequirePkgPath) + if !(isAssert || isRequire) { + return nil + } + + return &CallMeta{ + Range: ce, + IsPkg: isPkgCall, + IsAssert: isAssert, + Selector: se, + SelectorXStr: analysisutil.NodeString(pass.Fset, se.X), + Fn: FnMeta{ + Range: se.Sel, + Name: fnName, + NameFTrimmed: strings.TrimSuffix(fnName, "f"), + IsFmt: strings.HasSuffix(fnName, "f"), + }, + Args: trimTArg(pass, ce.Args), + ArgsRaw: ce.Args, + } +} + +func trimTArg(pass *analysis.Pass, args []ast.Expr) []ast.Expr { + if len(args) == 0 { + return args + } + + if isTestingTPtr(pass, args[0]) { + return args[1:] + } + return args +} + +func isTestingTPtr(pass *analysis.Pass, arg ast.Expr) bool { + assertTestingTObj := analysisutil.ObjectOf(pass.Pkg, testify.AssertPkgPath, "TestingT") + requireTestingTObj := analysisutil.ObjectOf(pass.Pkg, testify.RequirePkgPath, "TestingT") + + argType := pass.TypesInfo.TypeOf(arg) + if argType == nil { + return false + } + + return ((assertTestingTObj != nil) && + types.Implements(argType, assertTestingTObj.Type().Underlying().(*types.Interface))) || + ((requireTestingTObj != nil) && + types.Implements(argType, requireTestingTObj.Type().Underlying().(*types.Interface))) +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/checker.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/checker.go index f3249dc3c..ac23af6f6 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/checker.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/checker.go @@ -1,46 +1,10 @@ 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 diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/checkers_registry.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/checkers_registry.go index 47eaafb76..e34a21bf9 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/checkers_registry.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/checkers_registry.go @@ -13,12 +13,16 @@ var registry = checkersRegistry{ {factory: asCheckerFactory(NewLen), enabledByDefault: true}, {factory: asCheckerFactory(NewCompares), enabledByDefault: true}, {factory: asCheckerFactory(NewErrorNil), enabledByDefault: true}, + {factory: asCheckerFactory(NewNilCompare), 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}, + {factory: asCheckerFactory(NewUselessAssert), enabledByDefault: true}, // Advanced checkers. + {factory: asCheckerFactory(NewBlankImport), enabledByDefault: true}, + {factory: asCheckerFactory(NewGoRequire), enabledByDefault: true}, + {factory: asCheckerFactory(NewRequireError), enabledByDefault: true}, {factory: asCheckerFactory(NewSuiteTHelper), enabledByDefault: false}, } diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/compares.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/compares.go index afc829f97..336a34512 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/compares.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/compares.go @@ -18,6 +18,7 @@ import ( // assert.True(t, a >= b) // assert.True(t, a < b) // assert.True(t, a <= b) +// assert.False(t, a == b) // ... // // and requires @@ -46,10 +47,10 @@ func (checker Compares) Check(pass *analysis.Pass, call *CallMeta) *analysis.Dia var tokenToProposedFn map[token.Token]string - switch call.Fn.Name { - case "True", "Truef": + switch call.Fn.NameFTrimmed { + case "True": tokenToProposedFn = tokenToProposedFnInsteadOfTrue - case "False", "Falsef": + case "False": tokenToProposedFn = tokenToProposedFnInsteadOfFalse default: return nil diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/empty.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/empty.go index 79c64205f..5ad371bb4 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/empty.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/empty.go @@ -15,9 +15,19 @@ import ( // // assert.Len(t, arr, 0) // assert.Equal(t, 0, len(arr)) +// assert.EqualValues(t, 0, len(arr)) +// assert.Exactly(t, 0, len(arr)) +// assert.LessOrEqual(t, len(arr), 0) +// assert.GreaterOrEqual(t, 0, len(arr)) +// assert.Less(t, len(arr), 0) +// assert.Greater(t, 0, len(arr)) +// assert.Less(t, len(arr), 1) +// assert.Greater(t, 1, len(arr)) +// // assert.NotEqual(t, 0, len(arr)) -// assert.GreaterOrEqual(t, len(arr), 1) -// ... +// assert.NotEqualValues(t, 0, len(arr)) +// assert.Less(t, 0, len(arr)) +// assert.Greater(t, len(arr), 0) // // and requires // @@ -53,13 +63,13 @@ func (checker Empty) checkEmpty(pass *analysis.Pass, call *CallMeta) *analysis.D } a, b := call.Args[0], call.Args[1] - switch call.Fn.Name { - case "Len", "Lenf": + switch call.Fn.NameFTrimmed { + case "Len": if isZero(b) { return newUseEmptyDiagnostic(a.Pos(), b.End(), a) } - case "Equal", "Equalf": + case "Equal", "EqualValues", "Exactly": arg1, ok1 := isLenCallAndZero(pass, a, b) arg2, ok2 := isLenCallAndZero(pass, b, a) @@ -67,23 +77,23 @@ func (checker Empty) checkEmpty(pass *analysis.Pass, call *CallMeta) *analysis.D return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg) } - case "LessOrEqual", "LessOrEqualf": + case "LessOrEqual": if lenArg, ok := isBuiltinLenCall(pass, a); ok && isZero(b) { return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg) } - case "GreaterOrEqual", "GreaterOrEqualf": + case "GreaterOrEqual": 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) { + case "Less": + if lenArg, ok := isBuiltinLenCall(pass, a); ok && (isOne(b) || isZero(b)) { return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg) } - case "Greater", "Greaterf": - if lenArg, ok := isBuiltinLenCall(pass, b); ok && isOne(a) { + case "Greater": + if lenArg, ok := isBuiltinLenCall(pass, b); ok && (isOne(a) || isZero(a)) { return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg) } } @@ -107,8 +117,8 @@ func (checker Empty) checkNotEmpty(pass *analysis.Pass, call *CallMeta) *analysi } a, b := call.Args[0], call.Args[1] - switch call.Fn.Name { - case "NotEqual", "NotEqualf": + switch call.Fn.NameFTrimmed { + case "NotEqual", "NotEqualValues": arg1, ok1 := isLenCallAndZero(pass, a, b) arg2, ok2 := isLenCallAndZero(pass, b, a) @@ -116,23 +126,13 @@ func (checker Empty) checkNotEmpty(pass *analysis.Pass, call *CallMeta) *analysi 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) { + case "Less": + if lenArg, ok := isBuiltinLenCall(pass, b); ok && isZero(a) { return newUseNotEmptyDiagnostic(a.Pos(), b.End(), lenArg) } - case "LessOrEqual", "LessOrEqualf": - if lenArg, ok := isBuiltinLenCall(pass, b); ok && isOne(a) { + case "Greater": + if lenArg, ok := isBuiltinLenCall(pass, a); ok && isZero(b) { return newUseNotEmptyDiagnostic(a.Pos(), b.End(), lenArg) } } diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/error_is_as.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/error_is_as.go index e6abd0ba4..0363873a6 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/error_is_as.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/error_is_as.go @@ -3,6 +3,7 @@ package checkers import ( "fmt" "go/ast" + "go/types" "golang.org/x/tools/go/analysis" @@ -22,6 +23,8 @@ import ( // assert.ErrorIs(t, err, errSentinel) // assert.NotErrorIs(t, err, errSentinel) // assert.ErrorAs(t, err, &target) +// +// Also ErrorIsAs repeats go vet's "errorsas" check logic. type ErrorIsAs struct{} // NewErrorIsAs constructs ErrorIsAs checker. @@ -29,22 +32,22 @@ 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": + switch call.Fn.NameFTrimmed { + case "Error": 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": + case "NoError": 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": + case "True": if len(call.Args) < 1 { return nil } @@ -74,7 +77,7 @@ func (checker ErrorIsAs) Check(pass *analysis.Pass, call *CallMeta) *analysis.Di ) } - case "False", "Falsef": + case "False": if len(call.Args) < 1 { return nil } @@ -97,6 +100,45 @@ func (checker ErrorIsAs) Check(pass *analysis.Pass, call *CallMeta) *analysis.Di }), ) } + + case "ErrorAs": + if len(call.Args) < 2 { + return nil + } + + // NOTE(a.telyshev): Logic below must be consistent with + // https://cs.opensource.google/go/x/tools/+/master:go/analysis/passes/errorsas/errorsas.go + + var ( + defaultReport = fmt.Sprintf("second argument to %s must be a non-nil pointer to either a type that implements error, or to any interface type", call) //nolint:lll + errorPtrReport = fmt.Sprintf("second argument to %s should not be *error", call) + ) + + target := call.Args[1] + + if isEmptyInterface(pass, target) { + // `any` interface case. It is always allowed, since it often indicates + // a value forwarded from another source. + return nil + } + + tv, ok := pass.TypesInfo.Types[target] + if !ok { + return nil + } + + pt, ok := tv.Type.Underlying().(*types.Pointer) + if !ok { + return newDiagnostic(checker.Name(), call, defaultReport, nil) + } + if pt.Elem() == errorType { + return newDiagnostic(checker.Name(), call, errorPtrReport, nil) + } + + _, isInterface := pt.Elem().Underlying().(*types.Interface) + if !isInterface && !types.Implements(pt.Elem(), errorIface) { + return newDiagnostic(checker.Name(), call, defaultReport, nil) + } } return nil } diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/error_nil.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/error_nil.go index b45629a48..5b0af7458 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/error_nil.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/error_nil.go @@ -14,8 +14,14 @@ import ( // // assert.Nil(t, err) // assert.NotNil(t, err) -// assert.Equal(t, err, nil) -// assert.NotEqual(t, err, nil) +// assert.Equal(t, nil, err) +// assert.EqualValues(t, nil, err) +// assert.Exactly(t, nil, err) +// assert.ErrorIs(t, err, nil) +// +// assert.NotEqual(t, nil, err) +// assert.NotEqualValues(t, nil, err) +// assert.NotErrorIs(t, err, nil) // // and requires // @@ -34,40 +40,40 @@ func (checker ErrorNil) Check(pass *analysis.Pass, call *CallMeta) *analysis.Dia ) proposedFn, survivingArg, replacementEndPos := func() (string, ast.Expr, token.Pos) { - switch call.Fn.Name { - case "NotNil", "NotNilf": + switch call.Fn.NameFTrimmed { + case "Nil": if len(call.Args) >= 1 && isError(pass, call.Args[0]) { - return errorFn, call.Args[0], call.Args[0].End() + return noErrorFn, call.Args[0], call.Args[0].End() } - case "Nil", "Nilf": + case "NotNil": if len(call.Args) >= 1 && isError(pass, call.Args[0]) { - return noErrorFn, call.Args[0], call.Args[0].End() + return errorFn, call.Args[0], call.Args[0].End() } - case "Equal", "Equalf": + case "Equal", "EqualValues", "Exactly", "ErrorIs": 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): + case isError(pass, a) && isNil(b): return noErrorFn, a, b.End() - case isNil(pass, a) && isError(pass, b): + case isNil(a) && isError(pass, b): return noErrorFn, b, b.End() } - case "NotEqual", "NotEqualf": + case "NotEqual", "NotEqualValues", "NotErrorIs": 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): + case isError(pass, a) && isNil(b): return errorFn, a, b.End() - case isNil(pass, a) && isError(pass, b): + case isNil(a) && isError(pass, b): return errorFn, b, b.End() } } @@ -86,7 +92,10 @@ func (checker ErrorNil) Check(pass *analysis.Pass, call *CallMeta) *analysis.Dia return nil } -var errIface = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) +var ( + errorType = types.Universe.Lookup("error").Type() + errorIface = errorType.Underlying().(*types.Interface) +) func isError(pass *analysis.Pass, expr ast.Expr) bool { t := pass.TypesInfo.TypeOf(expr) @@ -95,15 +104,10 @@ func isError(pass *analysis.Pass, expr ast.Expr) bool { } _, ok := t.Underlying().(*types.Interface) - return ok && types.Implements(t, errIface) + return ok && types.Implements(t, errorIface) } -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 +func isNil(expr ast.Expr) bool { + ident, ok := expr.(*ast.Ident) + return ok && ident.Name == "nil" } diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/expected_actual.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/expected_actual.go index ff8243980..e6825eaa6 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/expected_actual.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/expected_actual.go @@ -2,10 +2,13 @@ package checkers import ( "go/ast" + "go/token" "go/types" "regexp" "golang.org/x/tools/go/analysis" + + "github.com/Antonboom/testifylint/internal/analysisutil" ) // DefaultExpectedVarPattern matches variables with "expected" or "wanted" prefix or suffix in the name. @@ -14,11 +17,30 @@ var DefaultExpectedVarPattern = regexp.MustCompile( // ExpectedActual detects situation like // -// assert.NotEqual(t, result, "expected value") +// assert.Equal(t, result, expected) +// assert.EqualExportedValues(t, resultObj, User{Name: "Anton"}) +// assert.EqualValues(t, result, 42) +// assert.Exactly(t, result, int64(42)) +// assert.JSONEq(t, result, `{"version": 3}`) +// assert.InDelta(t, result, 42.42, 1.0) +// assert.InDeltaMapValues(t, result, map[string]float64{"score": 0.99}, 1.0) +// assert.InDeltaSlice(t, result, []float64{0.98, 0.99}, 1.0) +// assert.InEpsilon(t, result, 42.42, 0.0001) +// assert.InEpsilonSlice(t, result, []float64{0.9801, 0.9902}, 0.0001) +// assert.IsType(t, result, (*User)(nil)) +// assert.NotEqual(t, result, "expected") +// assert.NotEqualValues(t, result, "expected") +// assert.NotSame(t, resultPtr, &value) +// assert.Same(t, resultPtr, &value) +// assert.WithinDuration(t, resultTime, time.Date(2023, 01, 12, 11, 46, 33, 0, nil), time.Second) +// assert.YAMLEq(t, result, "version: '3'") // // and requires // -// assert.NotEqual(t, "expected value", result) +// assert.Equal(t, expected, result) +// assert.EqualExportedValues(t, User{Name: "Anton"}, resultObj) +// assert.EqualValues(t, 42, result) +// ... type ExpectedActual struct { expVarPattern *regexp.Regexp } @@ -38,9 +60,24 @@ func (checker *ExpectedActual) SetExpVarPattern(p *regexp.Regexp) *ExpectedActua } func (checker ExpectedActual) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { - switch call.Fn.Name { - case "Equal", "Equalf", "NotEqual", "NotEqualf", - "JSONEq", "JSONEqf", "YAMLEq", "YAMLEqf": + switch call.Fn.NameFTrimmed { + case "Equal", + "EqualExportedValues", + "EqualValues", + "Exactly", + "InDelta", + "InDeltaMapValues", + "InDeltaSlice", + "InEpsilon", + "InEpsilonSlice", + "IsType", + "JSONEq", + "NotEqual", + "NotEqualValues", + "NotSame", + "Same", + "WithinDuration", + "YAMLEq": default: return nil } @@ -73,21 +110,37 @@ func (checker ExpectedActual) isWrongExpectedActualOrder(pass *analysis.Pass, fi func (checker ExpectedActual) isExpectedValueCandidate(pass *analysis.Pass, expr ast.Expr) bool { switch v := expr.(type) { + case *ast.ParenExpr: + return checker.isExpectedValueCandidate(pass, v.X) + + case *ast.StarExpr: // *value + return checker.isExpectedValueCandidate(pass, v.X) + + case *ast.UnaryExpr: + return (v.Op == token.AND) && checker.isExpectedValueCandidate(pass, v.X) // &value + case *ast.CompositeLit: return true case *ast.CallExpr: - return isCastedBasicLitOrExpectedValue(v, checker.expVarPattern) || - isExpectedValueFactory(v, checker.expVarPattern) + return isParenExpr(v) || + isCastedBasicLitOrExpectedValue(v, checker.expVarPattern) || + isExpectedValueFactory(pass, v, checker.expVarPattern) } return isBasicLit(expr) || isUntypedConst(pass, expr) || isTypedConst(pass, expr) || isIdentNamedAsExpected(checker.expVarPattern, expr) || + isStructVarNamedAsExpected(checker.expVarPattern, expr) || isStructFieldNamedAsExpected(checker.expVarPattern, expr) } +func isParenExpr(ce *ast.CallExpr) bool { + _, ok := ce.Fun.(*ast.ParenExpr) + return ok +} + func isCastedBasicLitOrExpectedValue(ce *ast.CallExpr, pattern *regexp.Regexp) bool { if len(ce.Args) != 1 { return false @@ -111,15 +164,16 @@ func isCastedBasicLitOrExpectedValue(ce *ast.CallExpr, pattern *regexp.Regexp) b return false } -func isExpectedValueFactory(ce *ast.CallExpr, pattern *regexp.Regexp) bool { - if len(ce.Args) != 0 { - return false - } - +func isExpectedValueFactory(pass *analysis.Pass, ce *ast.CallExpr, pattern *regexp.Regexp) bool { switch fn := ce.Fun.(type) { case *ast.Ident: return pattern.MatchString(fn.Name) + case *ast.SelectorExpr: + timeDateFn := analysisutil.ObjectOf(pass.Pkg, "time", "Date") + if timeDateFn != nil && analysisutil.IsObj(pass.TypesInfo, fn.Sel, timeDateFn) { + return true + } return pattern.MatchString(fn.Sel.Name) } return false @@ -150,6 +204,11 @@ func isIdentNamedAsExpected(pattern *regexp.Regexp, e ast.Expr) bool { return ok && pattern.MatchString(id.Name) } +func isStructVarNamedAsExpected(pattern *regexp.Regexp, e ast.Expr) bool { + s, ok := e.(*ast.SelectorExpr) + return ok && isIdentNamedAsExpected(pattern, s.X) +} + func isStructFieldNamedAsExpected(pattern *regexp.Regexp, e ast.Expr) bool { s, ok := e.(*ast.SelectorExpr) return ok && isIdentNamedAsExpected(pattern, s.Sel) diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/float_compare.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/float_compare.go index 7d5b358b3..10b1330de 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/float_compare.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/float_compare.go @@ -1,6 +1,7 @@ package checkers import ( + "fmt" "go/ast" "go/token" "go/types" @@ -10,13 +11,15 @@ import ( // FloatCompare detects situation like // -// assert.Equal(t, 42.42, a) -// assert.True(t, a == 42.42) -// assert.False(t, a != 42.42) +// assert.Equal(t, 42.42, result) +// assert.EqualValues(t, 42.42, result) +// assert.Exactly(t, 42.42, result) +// assert.True(t, result == 42.42) +// assert.False(t, result != 42.42) // // and requires // -// assert.InEpsilon(t, 42.42, a, 0.0001) // Or assert.InDelta +// assert.InEpsilon(t, 42.42, result, 0.0001) // Or assert.InDelta type FloatCompare struct{} // NewFloatCompare constructs FloatCompare checker. @@ -25,21 +28,25 @@ 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]) + switch call.Fn.NameFTrimmed { + case "Equal", "EqualValues", "Exactly": + return len(call.Args) > 1 && (isFloat(pass, call.Args[0]) || isFloat(pass, call.Args[1])) - case "True", "Truef": + case "True": return len(call.Args) > 0 && isFloatCompare(pass, call.Args[0], token.EQL) - case "False", "Falsef": + case "False": 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) + format := "use %s.InEpsilon (or InDelta)" + if call.Fn.IsFmt { + format = "use %s.InEpsilonf (or InDeltaf)" + } + return newDiagnostic(checker.Name(), call, fmt.Sprintf(format, call.SelectorXStr), nil) } return nil } diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/go_require.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/go_require.go new file mode 100644 index 000000000..4c0c9d1fd --- /dev/null +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/go_require.go @@ -0,0 +1,301 @@ +package checkers + +import ( + "fmt" + "go/ast" + "go/types" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/ast/inspector" + + "github.com/Antonboom/testifylint/internal/analysisutil" +) + +const ( + goRequireFnReportFormat = "%s contains assertions that must only be used in the goroutine running the test function" + goRequireCallReportFormat = "%s must only be used in the goroutine running the test function" +) + +// GoRequire takes idea from go vet's "testinggoroutine" check +// and detects usage of require package's functions or assert.FailNow in the non-test goroutines +// +// go func() { +// conn, err = lis.Accept() +// require.NoError(t, err) +// +// if assert.Error(err) { +// assert.FailNow(t, msg) +// } +// }() +type GoRequire struct{} + +// NewGoRequire constructs GoRequire checker. +func NewGoRequire() GoRequire { return GoRequire{} } +func (GoRequire) Name() string { return "go-require" } + +// Check should be consistent with +// https://cs.opensource.google/go/x/tools/+/master:go/analysis/passes/testinggoroutine/testinggoroutine.go +// +// But due to the fact that the Check covers cases missed by go vet, +// the implementation turned out to be terribly complicated. +// +// In simple words, the algorithm is as follows: +// - we walk along the call tree and store the status, whether we are in the test goroutine or not; +// - if we are in a test goroutine, then require is allowed, otherwise not; +// - when we encounter the launch of a subtest or `go` statement, the status changes; +// - in order to correctly handle the return to the correct status when exiting the current function, +// we have to store a stack of statuses (inGoroutineRunningTestFunc). +// +// Other test functions called in the test function are also analyzed to make a verdict about the current function. +// This leads to recursion, which the cache of processed functions (processedFuncs) helps reduce the impact of. +// Also, because of this, we have to pre-collect a list of test function declarations (testsDecls). +func (checker GoRequire) Check(pass *analysis.Pass, inspector *inspector.Inspector) (diagnostics []analysis.Diagnostic) { + testsDecls := make(funcDeclarations) + inspector.Preorder([]ast.Node{(*ast.FuncDecl)(nil)}, func(node ast.Node) { + fd := node.(*ast.FuncDecl) + + if isTestingFuncOrMethod(pass, fd) { + if tf, ok := pass.TypesInfo.ObjectOf(fd.Name).(*types.Func); ok { + testsDecls[tf] = fd + } + } + }) + + var inGoroutineRunningTestFunc boolStack + processedFuncs := make(map[*ast.FuncDecl]goRequireVerdict) + + nodesFilter := []ast.Node{ + (*ast.FuncDecl)(nil), + (*ast.GoStmt)(nil), + (*ast.CallExpr)(nil), + } + inspector.Nodes(nodesFilter, func(node ast.Node, push bool) bool { + if fd, ok := node.(*ast.FuncDecl); ok { + if !isTestingFuncOrMethod(pass, fd) { + return false + } + + if push { + inGoroutineRunningTestFunc.Push(true) + } else { + inGoroutineRunningTestFunc.Pop() + } + return true + } + + if _, ok := node.(*ast.GoStmt); ok { + if push { + inGoroutineRunningTestFunc.Push(false) + } else { + inGoroutineRunningTestFunc.Pop() + } + return true + } + + ce := node.(*ast.CallExpr) + if isSubTestRun(pass, ce) { + if push { + // t.Run spawns the new testing goroutine and declines + // possible warnings from previous "simple" goroutine. + inGoroutineRunningTestFunc.Push(true) + } else { + inGoroutineRunningTestFunc.Pop() + } + return true + } + + if !push { + return false + } + if inGoroutineRunningTestFunc.Last() { + // We are in testing goroutine and can skip any assertion checks. + return true + } + + testifyCall := NewCallMeta(pass, ce) + if testifyCall != nil { + switch checker.checkCall(testifyCall) { + case goRequireVerdictRequire: + d := newDiagnostic(checker.Name(), testifyCall, fmt.Sprintf(goRequireCallReportFormat, "require"), nil) + diagnostics = append(diagnostics, *d) + + case goRequireVerdictAssertFailNow: + d := newDiagnostic(checker.Name(), testifyCall, fmt.Sprintf(goRequireCallReportFormat, testifyCall), nil) + diagnostics = append(diagnostics, *d) + + case goRequireVerdictNoExit: + } + return false + } + + // Case of nested function call. + { + calledFd := testsDecls.Get(pass, ce) + if calledFd == nil { + return true + } + + if v := checker.checkFunc(pass, calledFd, testsDecls, processedFuncs); v != goRequireVerdictNoExit { + caller := analysisutil.NodeString(pass.Fset, ce.Fun) + d := newDiagnostic(checker.Name(), ce, fmt.Sprintf(goRequireFnReportFormat, caller), nil) + diagnostics = append(diagnostics, *d) + } + } + return true + }) + + return diagnostics +} + +func (checker GoRequire) checkFunc( + pass *analysis.Pass, + fd *ast.FuncDecl, + testsDecls funcDeclarations, + processedFuncs map[*ast.FuncDecl]goRequireVerdict, +) (result goRequireVerdict) { + if v, ok := processedFuncs[fd]; ok { + return v + } + + ast.Inspect(fd, func(node ast.Node) bool { + if result != goRequireVerdictNoExit { + return false + } + + if _, ok := node.(*ast.GoStmt); ok { + return false + } + + ce, ok := node.(*ast.CallExpr) + if !ok { + return true + } + + testifyCall := NewCallMeta(pass, ce) + if testifyCall != nil { + if v := checker.checkCall(testifyCall); v != goRequireVerdictNoExit { + result, processedFuncs[fd] = v, v + } + return false + } + + // Case of nested function call. + { + calledFd := testsDecls.Get(pass, ce) + if calledFd == nil { + return true + } + if calledFd == fd { + // Recursion. + return true + } + + if v := checker.checkFunc(pass, calledFd, testsDecls, processedFuncs); v != goRequireVerdictNoExit { + result = v + return false + } + return true + } + }) + + return result +} + +type goRequireVerdict int + +const ( + goRequireVerdictNoExit goRequireVerdict = iota + goRequireVerdictRequire + goRequireVerdictAssertFailNow +) + +func (checker GoRequire) checkCall(call *CallMeta) goRequireVerdict { + if !call.IsAssert { + return goRequireVerdictRequire + } + if call.Fn.NameFTrimmed == "FailNow" { + return goRequireVerdictAssertFailNow + } + return goRequireVerdictNoExit +} + +type funcDeclarations map[*types.Func]*ast.FuncDecl + +// Get returns the declaration of a called function or method. +// Currently, only static calls within the same package are supported, otherwise returns nil. +func (fd funcDeclarations) Get(pass *analysis.Pass, ce *ast.CallExpr) *ast.FuncDecl { + var obj types.Object + + switch fun := ce.Fun.(type) { + case *ast.SelectorExpr: + obj = pass.TypesInfo.ObjectOf(fun.Sel) + + case *ast.Ident: + obj = pass.TypesInfo.ObjectOf(fun) + + case *ast.IndexExpr: + if id, ok := fun.X.(*ast.Ident); ok { + obj = pass.TypesInfo.ObjectOf(id) + } + + case *ast.IndexListExpr: + if id, ok := fun.X.(*ast.Ident); ok { + obj = pass.TypesInfo.ObjectOf(id) + } + } + + if tf, ok := obj.(*types.Func); ok { + return fd[tf] + } + return nil +} + +type boolStack []bool + +func (s *boolStack) Push(v bool) { + *s = append(*s, v) +} + +func (s *boolStack) Pop() bool { + n := len(*s) + if n == 0 { + return false + } + + last := (*s)[n-1] + *s = (*s)[:n-1] + return last +} + +func (s boolStack) Last() bool { + n := len(s) + if n == 0 { + return false + } + return s[n-1] +} + +func isSubTestRun(pass *analysis.Pass, ce *ast.CallExpr) bool { + se, ok := ce.Fun.(*ast.SelectorExpr) + if !ok || se.Sel == nil { + return false + } + return (isTestingTPtr(pass, se.X) || implementsTestifySuiteIface(pass, se.X)) && se.Sel.Name == "Run" +} + +func isTestingFuncOrMethod(pass *analysis.Pass, fd *ast.FuncDecl) bool { + return hasTestingTParam(pass, fd) || isTestifySuiteMethod(pass, fd) +} + +func hasTestingTParam(pass *analysis.Pass, fd *ast.FuncDecl) bool { + if fd.Type == nil || fd.Type.Params == nil { + return false + } + + for _, param := range fd.Type.Params.List { + if isTestingTPtr(pass, param.Type) { + return true + } + } + return false +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/len.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/len.go index f10412f63..d4e6a48b5 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/len.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/len.go @@ -10,6 +10,8 @@ import ( // Len detects situations like // // assert.Equal(t, 3, len(arr)) +// assert.EqualValues(t, 3, len(arr)) +// assert.Exactly(t, 3, len(arr)) // assert.True(t, len(arr) == 3) // // and requires @@ -24,14 +26,18 @@ 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": + switch call.Fn.NameFTrimmed { + case "Equal", "EqualValues", "Exactly": if len(call.Args) < 2 { return nil } a, b := call.Args[0], call.Args[1] if lenArg, expectedLen, ok := xorLenCall(pass, a, b); ok { + if expectedLen == b && !isIntBasicLit(expectedLen) { + // https://github.com/Antonboom/testifylint/issues/9 + return nil + } return newUseFunctionDiagnostic(checker.Name(), call, proposedFn, newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{ Pos: a.Pos(), @@ -41,13 +47,13 @@ func (checker Len) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnost ) } - case "True", "Truef": + case "True": if len(call.Args) < 1 { return nil } expr := call.Args[0] - if lenArg, expectedLen, ok := isLenEquality(pass, expr); ok { + if lenArg, expectedLen, ok := isLenEquality(pass, expr); ok && isIntBasicLit(expectedLen) { return newUseFunctionDiagnostic(checker.Name(), call, proposedFn, newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{ Pos: expr.Pos(), @@ -84,3 +90,8 @@ func isLenEquality(pass *analysis.Pass, e ast.Expr) (ast.Expr, ast.Expr, bool) { } return xorLenCall(pass, be.X, be.Y) } + +func isIntBasicLit(e ast.Expr) bool { + bl, ok := e.(*ast.BasicLit) + return ok && bl.Kind == token.INT +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/nil_compare.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/nil_compare.go new file mode 100644 index 000000000..89680a069 --- /dev/null +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/nil_compare.go @@ -0,0 +1,69 @@ +package checkers + +import ( + "go/ast" + + "golang.org/x/tools/go/analysis" + + "github.com/Antonboom/testifylint/internal/analysisutil" +) + +// NilCompare detects situations like +// +// assert.Equal(t, nil, value) +// assert.EqualValues(t, nil, value) +// assert.Exactly(t, nil, value) +// +// assert.NotEqual(t, nil, value) +// assert.NotEqualValues(t, nil, value) +// +// and requires +// +// assert.Nil(t, value) +// assert.NotNil(t, value) +type NilCompare struct{} + +// NewNilCompare constructs NilCompare checker. +func NewNilCompare() NilCompare { return NilCompare{} } +func (NilCompare) Name() string { return "nil-compare" } + +func (checker NilCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { + if len(call.Args) < 2 { + return nil + } + + survivingArg, ok := xorNil(call.Args[0], call.Args[1]) + if !ok { + return nil + } + + var proposedFn string + + switch call.Fn.NameFTrimmed { + case "Equal", "EqualValues", "Exactly": + proposedFn = "Nil" + case "NotEqual", "NotEqualValues": + proposedFn = "NotNil" + default: + return nil + } + + return newUseFunctionDiagnostic(checker.Name(), call, proposedFn, + newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{ + Pos: call.Args[0].Pos(), + End: call.Args[1].End(), + NewText: analysisutil.NodeBytes(pass.Fset, survivingArg), + }), + ) +} + +func xorNil(first, second ast.Expr) (ast.Expr, bool) { + a, b := isNil(first), isNil(second) + if xor(a, b) { + if a { + return second, true + } + return first, true + } + return nil, false +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/require_error.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/require_error.go index 2da71ed81..ab09dd447 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/require_error.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/require_error.go @@ -1,35 +1,338 @@ package checkers -import "golang.org/x/tools/go/analysis" +import ( + "fmt" + "go/ast" + "go/token" + "regexp" -// RequireError detects situations like + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/ast/inspector" +) + +const requireErrorReport = "for error assertions use require" + +// RequireError detects error assertions like // +// assert.Error(t, err) // s.Error(err), s.Assert().Error(err) +// assert.ErrorIs(t, err, io.EOF) +// assert.ErrorAs(t, err, &target) +// assert.EqualError(t, err, "end of file") +// assert.ErrorContains(t, err, "end of file") // assert.NoError(t, err) -// s.ErrorIs(err, io.EOF) -// s.Assert().Error(err) +// assert.NotErrorIs(t, err, io.EOF) // // and requires // -// require.NoError(t, err) -// s.Require().ErrorIs(err, io.EOF) -// s.Require().Error(err) -type RequireError struct{} +// require.Error(t, err) // s.Require().Error(err), s.Require().Error(err) +// require.ErrorIs(t, err, io.EOF) +// require.ErrorAs(t, err, &target) +// ... +// +// RequireError ignores: +// - assertion in the `if` condition; +// - the entire `if-else[-if]` block, if there is an assertion in any `if` condition; +// - the last assertion in the block, if there are no methods/functions calls after it; +// - assertions in an explicit goroutine; +// - assertions in an explicit testing cleanup function or suite teardown methods; +// - sequence of NoError assertions. +type RequireError struct { + fnPattern *regexp.Regexp +} // NewRequireError constructs RequireError checker. -func NewRequireError() RequireError { return RequireError{} } -func (RequireError) Name() string { return "require-error" } +func NewRequireError() *RequireError { return new(RequireError) } +func (RequireError) Name() string { return "require-error" } + +func (checker *RequireError) SetFnPattern(p *regexp.Regexp) *RequireError { + if p != nil { + checker.fnPattern = p + } + return checker +} + +func (checker RequireError) Check(pass *analysis.Pass, inspector *inspector.Inspector) []analysis.Diagnostic { + callsByFunc := make(map[funcID][]*callMeta) + + // Stage 1. Collect meta information about any calls inside functions. + + inspector.WithStack([]ast.Node{(*ast.CallExpr)(nil)}, func(node ast.Node, push bool, stack []ast.Node) bool { + if !push { + return false + } + if len(stack) < 3 { + return true + } + + fID := findSurroundingFunc(pass, stack) + if fID == nil { + return true + } + + _, prevIsIfStmt := stack[len(stack)-2].(*ast.IfStmt) + _, prevIsAssignStmt := stack[len(stack)-2].(*ast.AssignStmt) + _, prevPrevIsIfStmt := stack[len(stack)-3].(*ast.IfStmt) + inIfCond := prevIsIfStmt || (prevPrevIsIfStmt && prevIsAssignStmt) + + callExpr := node.(*ast.CallExpr) + testifyCall := NewCallMeta(pass, callExpr) + + call := &callMeta{ + call: callExpr, + testifyCall: testifyCall, + rootIf: findRootIf(stack), + parentIf: findNearestNode[*ast.IfStmt](stack), + parentBlock: findNearestNode[*ast.BlockStmt](stack), + inIfCond: inIfCond, + inNoErrorSeq: false, // Will be filled in below. + } + + callsByFunc[*fID] = append(callsByFunc[*fID], call) + return testifyCall == nil // Do not support asserts in asserts. + }) + + // Stage 2. Analyze calls and block context. + + var diagnostics []analysis.Diagnostic + + callsByBlock := map[*ast.BlockStmt][]*callMeta{} + for _, calls := range callsByFunc { + for _, c := range calls { + if b := c.parentBlock; b != nil { + callsByBlock[b] = append(callsByBlock[b], c) + } + } + } + + markCallsInNoErrorSequence(callsByBlock) + + for funcInfo, calls := range callsByFunc { + for i, c := range calls { + if funcInfo.isTestCleanup { + continue + } + if funcInfo.isGoroutine { + continue + } + + if c.testifyCall == nil { + continue + } + if !c.testifyCall.IsAssert { + continue + } + switch c.testifyCall.Fn.NameFTrimmed { + default: + continue + case "Error", "ErrorIs", "ErrorAs", "EqualError", "ErrorContains", "NoError", "NotErrorIs": + } + + if needToSkipBasedOnContext(c, i, calls, callsByBlock) { + continue + } + if p := checker.fnPattern; p != nil && !p.MatchString(c.testifyCall.Fn.Name) { + continue + } + + diagnostics = append(diagnostics, + *newDiagnostic(checker.Name(), c.testifyCall, requireErrorReport, nil)) + } + } + + return diagnostics +} + +func needToSkipBasedOnContext( + currCall *callMeta, + currCallIndex int, + otherCalls []*callMeta, + callsByBlock map[*ast.BlockStmt][]*callMeta, +) bool { + if currCall.inNoErrorSeq { + // Skip `assert.NoError` sequence. + return true + } + + if currCall.inIfCond { + // Skip assertions in the "if condition". + return true + } + + if currCall.rootIf != nil { + for _, rootCall := range otherCalls { + if (rootCall.rootIf == currCall.rootIf) && rootCall.inIfCond { + // Skip assertions in the entire if-else[-if] block, if some of "if condition" contains assertion. + return true + } + } + } + + block := currCall.parentBlock + blockCalls := callsByBlock[block] + isLastCallInBlock := blockCalls[len(blockCalls)-1] == currCall + + noCallsAfter := true -func (checker RequireError) Check(_ *analysis.Pass, call *CallMeta) *analysis.Diagnostic { - if !call.IsAssert { - return nil + _, blockEndWithReturn := block.List[len(block.List)-1].(*ast.ReturnStmt) + if !blockEndWithReturn { + for i := currCallIndex + 1; i < len(otherCalls); i++ { + nextCall := otherCalls[i] + nextCallInElseBlock := false + + if pIf := currCall.parentIf; pIf != nil && pIf.Else != nil { + ast.Inspect(pIf.Else, func(n ast.Node) bool { + if n == nextCall.call { + nextCallInElseBlock = true + return false + } + return true + }) + } + + if !nextCallInElseBlock { + noCallsAfter = false + break + } + } } - const msg = "for error assertions use require" + // Skip assertion if this is the last operation in the test. + return isLastCallInBlock && noCallsAfter +} + +func findSurroundingFunc(pass *analysis.Pass, stack []ast.Node) *funcID { + for i := len(stack) - 2; i >= 0; i-- { + var fType *ast.FuncType + var fName string + var isTestCleanup bool + var isGoroutine bool + + switch fd := stack[i].(type) { + case *ast.FuncDecl: + fType, fName = fd.Type, fd.Name.Name + + if isTestifySuiteMethod(pass, fd) { + if ident := fd.Name; ident != nil && isAfterTestMethod(ident.Name) { + isTestCleanup = true + } + } + + case *ast.FuncLit: + fType, fName = fd.Type, "anonymous" + + if i >= 2 { //nolint:nestif + if ce, ok := stack[i-1].(*ast.CallExpr); ok { + if se, ok := ce.Fun.(*ast.SelectorExpr); ok { + isTestCleanup = isTestingTPtr(pass, se.X) && se.Sel != nil && (se.Sel.Name == "Cleanup") + } + + if _, ok := stack[i-2].(*ast.GoStmt); ok { + isGoroutine = true + } + } + } - 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) + default: + continue + } + + return &funcID{ + pos: fType.Pos(), + posStr: pass.Fset.Position(fType.Pos()).String(), + name: fName, + isTestCleanup: isTestCleanup, + isGoroutine: isGoroutine, + } } return nil } + +func findRootIf(stack []ast.Node) *ast.IfStmt { + nearestIf, i := findNearestNodeWithIdx[*ast.IfStmt](stack) + for ; i > 0; i-- { + parent, ok := stack[i-1].(*ast.IfStmt) + if ok { + nearestIf = parent + } else { + break + } + } + return nearestIf +} + +func findNearestNode[T ast.Node](stack []ast.Node) (v T) { + v, _ = findNearestNodeWithIdx[T](stack) + return +} + +func findNearestNodeWithIdx[T ast.Node](stack []ast.Node) (v T, index int) { + for i := len(stack) - 2; i >= 0; i-- { + if n, ok := stack[i].(T); ok { + return n, i + } + } + return +} + +func markCallsInNoErrorSequence(callsByBlock map[*ast.BlockStmt][]*callMeta) { + for _, calls := range callsByBlock { + for i, c := range calls { + if c.testifyCall == nil { + continue + } + + var prevIsNoError bool + if i > 0 { + if prev := calls[i-1].testifyCall; prev != nil { + prevIsNoError = isNoErrorAssertion(prev.Fn.Name) + } + } + + var nextIsNoError bool + if i < len(calls)-1 { + if next := calls[i+1].testifyCall; next != nil { + nextIsNoError = isNoErrorAssertion(next.Fn.Name) + } + } + + if isNoErrorAssertion(c.testifyCall.Fn.Name) && (prevIsNoError || nextIsNoError) { + calls[i].inNoErrorSeq = true + } + } + } +} + +type callMeta struct { + call *ast.CallExpr + testifyCall *CallMeta + rootIf *ast.IfStmt // The root `if` in if-else[-if] chain. + parentIf *ast.IfStmt // The nearest `if`, can be equal with rootIf. + parentBlock *ast.BlockStmt + inIfCond bool // True for code like `if assert.ErrorAs(t, err, &target) {`. + inNoErrorSeq bool // True for sequence of `assert.NoError` assertions. +} + +type funcID struct { + pos token.Pos + posStr string + name string + isTestCleanup bool + isGoroutine bool +} + +func (id funcID) String() string { + return fmt.Sprintf("%s at %s", id.name, id.posStr) +} + +func isAfterTestMethod(name string) bool { + // https://github.com/stretchr/testify/blob/master/suite/interfaces.go + switch name { + case "TearDownSuite", "TearDownTest", "AfterTest", "HandleStats", "TearDownSubTest": + return true + } + return false +} + +func isNoErrorAssertion(fnName string) bool { + return (fnName == "NoError") || (fnName == "NoErrorf") +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/useless_assert.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/useless_assert.go new file mode 100644 index 000000000..669f9d187 --- /dev/null +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/useless_assert.go @@ -0,0 +1,71 @@ +package checkers + +import ( + "golang.org/x/tools/go/analysis" + + "github.com/Antonboom/testifylint/internal/analysisutil" +) + +// UselessAssert detects useless asserts like +// +// 1) Asserting of the same variable +// +// assert.Equal(t, tt.value, tt.value) +// assert.ElementsMatch(t, users, users) +// ... +// +// 2) Open for contribution... +type UselessAssert struct{} + +// NewUselessAssert constructs UselessAssert checker. +func NewUselessAssert() UselessAssert { return UselessAssert{} } +func (UselessAssert) Name() string { return "useless-assert" } + +func (checker UselessAssert) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { + switch call.Fn.NameFTrimmed { + case + "Contains", + "ElementsMatch", + "Equal", + "EqualExportedValues", + "EqualValues", + "ErrorAs", + "ErrorIs", + "Exactly", + "Greater", + "GreaterOrEqual", + "Implements", + "InDelta", + "InDeltaMapValues", + "InDeltaSlice", + "InEpsilon", + "InEpsilonSlice", + "IsType", + "JSONEq", + "Less", + "LessOrEqual", + "NotEqual", + "NotEqualValues", + "NotErrorIs", + "NotRegexp", + "NotSame", + "NotSubset", + "Regexp", + "Same", + "Subset", + "WithinDuration", + "YAMLEq": + default: + return nil + } + + if len(call.Args) < 2 { + return nil + } + first, second := call.Args[0], call.Args[1] + + if analysisutil.NodeString(pass.Fset, first) == analysisutil.NodeString(pass.Fset, second) { + return newDiagnostic(checker.Name(), call, "asserting of the same variable", nil) + } + return nil +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/config/config.go b/vendor/github.com/Antonboom/testifylint/internal/config/config.go index 51f627008..6dcfbdb52 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/config/config.go +++ b/vendor/github.com/Antonboom/testifylint/internal/config/config.go @@ -1,7 +1,9 @@ package config import ( + "errors" "flag" + "fmt" "github.com/Antonboom/testifylint/internal/checkers" ) @@ -9,11 +11,16 @@ import ( // NewDefault builds default testifylint config. func NewDefault() Config { return Config{ - EnableAll: false, - EnabledCheckers: checkers.EnabledByDefault(), + EnableAll: false, + DisabledCheckers: nil, + DisableAll: false, + EnabledCheckers: nil, ExpectedActual: ExpectedActualConfig{ ExpVarPattern: RegexpValue{checkers.DefaultExpectedVarPattern}, }, + RequireError: RequireErrorConfig{ + FnPattern: RegexpValue{nil}, + }, SuiteExtraAssertCall: SuiteExtraAssertCallConfig{ Mode: checkers.DefaultSuiteExtraAssertCallMode, }, @@ -22,9 +29,13 @@ func NewDefault() Config { // Config implements testifylint configuration. type Config struct { - EnableAll bool - EnabledCheckers KnownCheckersValue + EnableAll bool + DisabledCheckers KnownCheckersValue + DisableAll bool + EnabledCheckers KnownCheckersValue + ExpectedActual ExpectedActualConfig + RequireError RequireErrorConfig SuiteExtraAssertCall SuiteExtraAssertCallConfig } @@ -33,16 +44,55 @@ type ExpectedActualConfig struct { ExpVarPattern RegexpValue } +// RequireErrorConfig implements configuration of checkers.RequireError. +type RequireErrorConfig struct { + FnPattern RegexpValue +} + // SuiteExtraAssertCallConfig implements configuration of checkers.SuiteExtraAssertCall. type SuiteExtraAssertCallConfig struct { Mode checkers.SuiteExtraAssertCallMode } +func (cfg Config) Validate() error { + if cfg.EnableAll { + if cfg.DisableAll { + return errors.New("enable-all and disable-all options must not be combined") + } + + if len(cfg.EnabledCheckers) != 0 { + return errors.New("enable-all and enable options must not be combined") + } + } + + if cfg.DisableAll { + if len(cfg.DisabledCheckers) != 0 { + return errors.New("disable-all and disable options must not be combined") + } + + if len(cfg.EnabledCheckers) == 0 { + return errors.New("all checkers were disabled, but no one checker was enabled: at least one must be enabled") + } + } + + for _, checker := range cfg.DisabledCheckers { + if cfg.EnabledCheckers.Contains(checker) { + return fmt.Errorf("checker %q disabled and enabled at one moment", checker) + } + } + + return nil +} + // 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.DisabledCheckers, "disable", "comma separated list of disabled checkers (to exclude from enabled by default)") + fs.BoolVar(&cfg.DisableAll, "disable-all", false, "disable all checkers") + fs.Var(&cfg.EnabledCheckers, "enable", "comma separated list of enabled checkers (in addition to enabled by default)") + fs.Var(&cfg.ExpectedActual.ExpVarPattern, "expected-actual.pattern", "regexp for expected variable name") + fs.Var(&cfg.RequireError.FnPattern, "require-error.fn-pattern", "regexp for error assertions that should only be analyzed") fs.Var(NewEnumValue(suiteExtraAssertCallModeAsString, &cfg.SuiteExtraAssertCall.Mode), "suite-extra-assert-call.mode", "to require or remove extra Assert() call") } diff --git a/vendor/github.com/Antonboom/testifylint/internal/config/flag_value_types.go b/vendor/github.com/Antonboom/testifylint/internal/config/flag_value_types.go index 2f0ee978f..5b08ec47b 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/config/flag_value_types.go +++ b/vendor/github.com/Antonboom/testifylint/internal/config/flag_value_types.go @@ -35,6 +35,15 @@ func (kcv *KnownCheckersValue) Set(v string) error { return nil } +func (kcv KnownCheckersValue) Contains(v string) bool { + for _, checker := range kcv { + if checker == v { + return true + } + } + return false +} + // RegexpValue is a special wrapper for support of flag.FlagSet over regexp.Regexp. // Original regexp is available through RegexpValue.Regexp. type RegexpValue struct { diff --git a/vendor/github.com/Antonboom/testifylint/internal/testify/const.go b/vendor/github.com/Antonboom/testifylint/internal/testify/const.go index 45731aa97..3476e4040 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/testify/const.go +++ b/vendor/github.com/Antonboom/testifylint/internal/testify/const.go @@ -4,10 +4,14 @@ const ( ModulePath = "github.com/stretchr/testify" AssertPkgName = "assert" + HTTPPkgName = "http" + MockPkgName = "mock" RequirePkgName = "require" SuitePkgName = "suite" AssertPkgPath = ModulePath + "/" + AssertPkgName + HTTPPkgPath = ModulePath + "/" + HTTPPkgName + MockPkgPath = ModulePath + "/" + MockPkgName RequirePkgPath = ModulePath + "/" + RequirePkgName SuitePkgPath = ModulePath + "/" + SuitePkgName ) diff --git a/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/analyzer/analyzer.go b/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/analyzer/analyzer.go index d0cd2d5bb..b490f1c64 100644 --- a/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/analyzer/analyzer.go +++ b/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/analyzer/analyzer.go @@ -12,16 +12,17 @@ import ( "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/comment" "github.com/GaijinEntertainment/go-exhaustruct/v3/internal/pattern" + "github.com/GaijinEntertainment/go-exhaustruct/v3/internal/structure" ) type analyzer struct { include pattern.List `exhaustruct:"optional"` exclude pattern.List `exhaustruct:"optional"` - fieldsCache map[types.Type]fields.StructFields - fieldsCacheMu sync.RWMutex `exhaustruct:"optional"` + structFields structure.FieldsCache `exhaustruct:"optional"` + comments comment.Cache `exhaustruct:"optional"` typeProcessingNeed map[string]bool typeProcessingNeedMu sync.RWMutex `exhaustruct:"optional"` @@ -29,8 +30,8 @@ type analyzer struct { func NewAnalyzer(include, exclude []string) (*analysis.Analyzer, error) { a := analyzer{ - fieldsCache: make(map[types.Type]fields.StructFields), typeProcessingNeed: make(map[string]bool), + comments: comment.Cache{}, } var err error @@ -74,12 +75,7 @@ Anonymous structs can be matched by '<anonymous>' alias. 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), - ) + insp.WithStack([]ast.Node{(*ast.CompositeLit)(nil)}, a.newVisitor(pass)) return nil, nil //nolint:nilnil } @@ -115,7 +111,10 @@ func (a *analyzer) newVisitor(pass *analysis.Pass) func(n ast.Node, push bool, s } } - pos, msg := a.processStruct(pass, lit, structTyp, typeInfo) + file := a.comments.Get(pass.Fset, stack[0].(*ast.File)) //nolint:forcetypeassert + rc := getCompositeLitRelatedComments(stack, file) + pos, msg := a.processStruct(pass, lit, structTyp, typeInfo, rc) + if pos != nil { pass.Reportf(*pos, msg) } @@ -124,6 +123,35 @@ func (a *analyzer) newVisitor(pass *analysis.Pass) func(n ast.Node, push bool, s } } +// getCompositeLitRelatedComments returns all comments that are related to checked node. We +// have to traverse the stack manually as ast do not associate comments with +// [ast.CompositeLit]. +func getCompositeLitRelatedComments(stack []ast.Node, cm ast.CommentMap) []*ast.CommentGroup { + comments := make([]*ast.CommentGroup, 0) + + for i := len(stack) - 1; i >= 0; i-- { + node := stack[i] + + switch node.(type) { + case *ast.CompositeLit, // stack[len(stack)-1] + *ast.ReturnStmt, // return ... + *ast.IndexExpr, // map[enum]...{...}[key] + *ast.CallExpr, // myfunc(map...) + *ast.UnaryExpr, // &map... + *ast.AssignStmt, // variable assignment (without var keyword) + *ast.DeclStmt, // var declaration, parent of *ast.GenDecl + *ast.GenDecl, // var declaration, parent of *ast.ValueSpec + *ast.ValueSpec: // var declaration + comments = append(comments, cm[node]...) + + default: + return comments + } + } + + return comments +} + func getStructType(pass *analysis.Pass, lit *ast.CompositeLit) (*types.Struct, *TypeInfo, bool) { switch typ := pass.TypesInfo.TypeOf(lit).(type) { case *types.Named: // named type @@ -179,8 +207,15 @@ func (a *analyzer) processStruct( lit *ast.CompositeLit, structTyp *types.Struct, info *TypeInfo, + comments []*ast.CommentGroup, ) (*token.Pos, string) { - if !a.shouldProcessType(info) { + shouldProcess := a.shouldProcessType(info) + + if shouldProcess && comment.HasDirective(comments, comment.DirectiveIgnore) { + return nil, "" + } + + if !shouldProcess && !comment.HasDirective(comments, comment.DirectiveEnforce) { return nil, "" } @@ -233,24 +268,12 @@ func (a *analyzer) shouldProcessType(info *TypeInfo) bool { 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) +) structure.Fields { + return a.structFields.Get(typ).Skipped(lit, onlyExported) } type TypeInfo struct { diff --git a/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/comment/cache.go b/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/comment/cache.go new file mode 100644 index 000000000..88edef638 --- /dev/null +++ b/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/comment/cache.go @@ -0,0 +1,35 @@ +package comment + +import ( + "go/ast" + "go/token" + "sync" +) + +type Cache struct { + comments map[*ast.File]ast.CommentMap + mu sync.RWMutex +} + +// Get returns a comment map for a given file. In case if a comment map is not +// found, it creates a new one. +func (c *Cache) Get(fset *token.FileSet, f *ast.File) ast.CommentMap { + c.mu.RLock() + if cm, ok := c.comments[f]; ok { + c.mu.RUnlock() + return cm + } + c.mu.RUnlock() + + c.mu.Lock() + defer c.mu.Unlock() + + if c.comments == nil { + c.comments = make(map[*ast.File]ast.CommentMap) + } + + cm := ast.NewCommentMap(fset, f, f.Comments) + c.comments[f] = cm + + return cm +} diff --git a/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/comment/directive.go b/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/comment/directive.go new file mode 100644 index 000000000..a39a8076f --- /dev/null +++ b/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/comment/directive.go @@ -0,0 +1,28 @@ +package comment + +import ( + "go/ast" + "strings" +) + +type Directive string + +const ( + prefix = `//exhaustruct:` + DirectiveIgnore Directive = prefix + `ignore` + DirectiveEnforce Directive = prefix + `enforce` +) + +// HasDirective parses a directive from a given list of comments. +// If no directive is found, the second return value is `false`. +func HasDirective(comments []*ast.CommentGroup, expected Directive) bool { + for _, cg := range comments { + for _, commentLine := range cg.List { + if strings.HasPrefix(commentLine.Text, string(expected)) { + return true + } + } + } + + return false +} diff --git a/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/structure/fields-cache.go b/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/structure/fields-cache.go new file mode 100644 index 000000000..12a379692 --- /dev/null +++ b/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/structure/fields-cache.go @@ -0,0 +1,35 @@ +package structure + +import ( + "go/types" + "sync" +) + +type FieldsCache struct { + fields map[*types.Struct]Fields + mu sync.RWMutex +} + +// Get returns a struct fields for a given type. In case if a struct fields is +// not found, it creates a new one from type definition. +func (c *FieldsCache) Get(typ *types.Struct) Fields { + c.mu.RLock() + fields, ok := c.fields[typ] + c.mu.RUnlock() + + if ok { + return fields + } + + c.mu.Lock() + defer c.mu.Unlock() + + if c.fields == nil { + c.fields = make(map[*types.Struct]Fields) + } + + fields = NewFields(typ) + c.fields[typ] = fields + + return fields +} diff --git a/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/fields/struct.go b/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/structure/fields.go index af2390e87..b6b1a48c8 100644 --- a/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/fields/struct.go +++ b/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/structure/fields.go @@ -1,33 +1,34 @@ -package fields +package structure import ( "go/ast" "go/types" "reflect" + "strings" ) const ( - TagName = "exhaustruct" - OptionalTagValue = "optional" + tagName = "exhaustruct" + optionalTagValue = "optional" ) -type StructField struct { +type Field struct { Name string Exported bool Optional bool } -type StructFields []*StructField +type Fields []*Field -// 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()) +// NewFields creates a new [Fields] from a given struct type. +// Fields items are listed in order they appear in the struct. +func NewFields(strct *types.Struct) Fields { + sf := make(Fields, 0, strct.NumFields()) for i := 0; i < strct.NumFields(); i++ { f := strct.Field(i) - sf = append(sf, &StructField{ + sf = append(sf, &Field{ Name: f.Name(), Exported: f.Exported(), Optional: HasOptionalTag(strct.Tag(i)), @@ -38,27 +39,29 @@ func NewStructFields(strct *types.Struct) StructFields { } func HasOptionalTag(tags string) bool { - return reflect.StructTag(tags).Get(TagName) == OptionalTagValue + return reflect.StructTag(tags).Get(tagName) == optionalTagValue } // String returns a comma-separated list of field names. -func (sf StructFields) String() (res string) { +func (sf Fields) String() string { + b := strings.Builder{} + for i := 0; i < len(sf); i++ { - if res != "" { - res += ", " + if b.Len() != 0 { + b.WriteString(", ") } - res += sf[i].Name + b.WriteString(sf[i].Name) } - return res + return b.String() } -// SkippedFields returns a list of fields that are not present in the given +// Skipped 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 { +func (sf Fields) Skipped(lit *ast.CompositeLit, onlyExported bool) Fields { if len(lit.Elts) != 0 && !isNamedLiteral(lit) { if len(lit.Elts) == len(sf) { return nil @@ -68,7 +71,7 @@ func (sf StructFields) SkippedFields(lit *ast.CompositeLit, onlyExported bool) S } em := sf.existenceMap() - res := make(StructFields, 0, len(sf)) + res := make(Fields, 0, len(sf)) for i := 0; i < len(lit.Elts); i++ { kv, ok := lit.Elts[i].(*ast.KeyValueExpr) @@ -99,7 +102,7 @@ func (sf StructFields) SkippedFields(lit *ast.CompositeLit, onlyExported bool) S return res } -func (sf StructFields) existenceMap() map[string]bool { +func (sf Fields) existenceMap() map[string]bool { m := make(map[string]bool, len(sf)) for i := 0; i < len(sf); i++ { diff --git a/vendor/github.com/OpenPeeDeeP/depguard/v2/.gitignore b/vendor/github.com/OpenPeeDeeP/depguard/v2/.gitignore index 97cca67c6..e189bdb22 100644 --- a/vendor/github.com/OpenPeeDeeP/depguard/v2/.gitignore +++ b/vendor/github.com/OpenPeeDeeP/depguard/v2/.gitignore @@ -12,3 +12,4 @@ *.out .idea +.null-ls*.go diff --git a/vendor/github.com/OpenPeeDeeP/depguard/v2/README.md b/vendor/github.com/OpenPeeDeeP/depguard/v2/README.md index 3de3f6317..2ccfa22c5 100644 --- a/vendor/github.com/OpenPeeDeeP/depguard/v2/README.md +++ b/vendor/github.com/OpenPeeDeeP/depguard/v2/README.md @@ -7,13 +7,12 @@ allow specific packages within a repository. ## Install ```bash -go get github.com/OpenPeeDeeP/depguard/v2 +go install github.com/OpenPeeDeeP/depguard@latest ``` ## Config -The Depguard binary looks for a file named `^\.?depguard\.(yaml|yml|json|toml)$` in the current -current working directory. Examples include (`.depguard.yml` or `depguard.toml`). +The Depguard binary looks for a file named `^\.?depguard\.(yaml|yml|json|toml)$` in the current working directory. Examples include (`.depguard.yml` or `depguard.toml`). The following is an example configuration file. @@ -24,6 +23,7 @@ The following is an example configuration file. "$all", "!$test" ], + "listMode": "Strict", "allow": [ "$gostd", "github.com/OpenPeeDeeP" @@ -36,6 +36,7 @@ The following is an example configuration file. "files": [ "$test" ], + "listMode": "Lax", "deny": { "github.com/stretchr/testify": "Please use standard library for tests" } @@ -48,6 +49,7 @@ the linter's output. - `files` - list of file globs that will match this list of settings to compare against - `allow` - list of allowed packages - `deny` - map of packages that are not allowed where the value is a suggestion += `listMode` - the mode to use for package matching Files are matched using [Globs](https://github.com/gobwas/glob). If the files list is empty, then all files will match that list. Prefixing a file @@ -67,6 +69,21 @@ A Prefix List just means that a package will match a value, if the value is a prefix of the package. Example `github.com/OpenPeeDeeP/depguard` package will match a value of `github.com/OpenPeeDeeP` but won't match `github.com/OpenPeeDeeP/depguard/v2`. +ListMode is used to determine the package matching priority. There are three +different modes; Original, Strict, and Lax. + +Original is the original way that the package was written to use. It is not recommended +to stay with this and is only here for backwards compatibility. + +Strict, at its roots, is everything is denied unless in allowed. + +Lax, at its roots, is everything is allowed unless it is denied. + +There are cases where a package can be matched in both the allow and denied lists. +You may allow a subpackage but deny the root or vice versa. The `settings_tests.go` file +has many scenarios listed out under `TestListImportAllowed`. These tests will stay +up to date as features are added. + ### Variables There are variable replacements for each type of list (file or package). This is @@ -74,7 +91,7 @@ to reduce repetition and tedious behaviors. #### File Variables -> you can still use and exclamation mark `!` in front of a variable to say not to +> you can still use an exclamation mark `!` in front of a variable to say not to use it. Example `!$test` will match any file that is not a go test file. - `$all` - matches all go files diff --git a/vendor/github.com/OpenPeeDeeP/depguard/v2/settings.go b/vendor/github.com/OpenPeeDeeP/depguard/v2/settings.go index 440f32985..311cacc88 100644 --- a/vendor/github.com/OpenPeeDeeP/depguard/v2/settings.go +++ b/vendor/github.com/OpenPeeDeeP/depguard/v2/settings.go @@ -11,12 +11,22 @@ import ( ) type List struct { - Files []string `json:"files" yaml:"files" toml:"files" mapstructure:"files"` - Allow []string `json:"allow" yaml:"allow" toml:"allow" mapstructure:"allow"` - Deny map[string]string `json:"deny" yaml:"deny" toml:"deny" mapstructure:"deny"` + ListMode string `json:"listMode" yaml:"listMode" toml:"listMode" mapstructure:"listMode"` + Files []string `json:"files" yaml:"files" toml:"files" mapstructure:"files"` + Allow []string `json:"allow" yaml:"allow" toml:"allow" mapstructure:"allow"` + Deny map[string]string `json:"deny" yaml:"deny" toml:"deny" mapstructure:"deny"` } +type listMode int + +const ( + listModeOriginal listMode = iota + listModeStrict + listModeLax +) + type list struct { + listMode listMode name string files []glob.Glob negFiles []glob.Glob @@ -33,6 +43,20 @@ func (l *List) compile() (*list, error) { var errs utils.MultiError var err error + // Determine List Mode + switch strings.ToLower(l.ListMode) { + case "": + li.listMode = listModeOriginal + case "original": + li.listMode = listModeOriginal + case "strict": + li.listMode = listModeStrict + case "lax": + li.listMode = listModeLax + default: + errs = append(errs, fmt.Errorf("%s is not a known list mode", l.ListMode)) + } + // Compile Files for _, f := range l.Files { var negate bool @@ -113,16 +137,25 @@ func (l *list) fileMatch(fileName string) bool { } func (l *list) importAllowed(imp string) (bool, string) { - inAllowed := len(l.allow) == 0 - if !inAllowed { - inAllowed, _ = strInPrefixList(imp, l.allow) + inAllowed, aIdx := strInPrefixList(imp, l.allow) + inDenied, dIdx := strInPrefixList(imp, l.deny) + var allowed bool + switch l.listMode { + case listModeOriginal: + inAllowed = len(l.allow) == 0 || inAllowed + allowed = inAllowed && !inDenied + case listModeStrict: + allowed = inAllowed && (!inDenied || len(l.allow[aIdx]) > len(l.deny[dIdx])) + case listModeLax: + allowed = !inDenied || (inAllowed && len(l.allow[aIdx]) > len(l.deny[dIdx])) + default: + allowed = false } - inDenied, suggIdx := strInPrefixList(imp, l.deny) sugg := "" - if inDenied && suggIdx != -1 { - sugg = l.suggestions[suggIdx] + if !allowed && inDenied && dIdx != -1 { + sugg = l.suggestions[dIdx] } - return inAllowed && !inDenied, sugg + return allowed, sugg } type LinterSettings map[string]*List diff --git a/vendor/github.com/alecthomas/go-check-sumtype/decl.go b/vendor/github.com/alecthomas/go-check-sumtype/decl.go index ea2cd06df..9dec9eefd 100644 --- a/vendor/github.com/alecthomas/go-check-sumtype/decl.go +++ b/vendor/github.com/alecthomas/go-check-sumtype/decl.go @@ -57,6 +57,7 @@ func findSumTypeDecls(pkgs []*packages.Package) ([]sumTypeDecl, error) { } pos = pkg.Fset.Position(tspec.Pos()) decl := sumTypeDecl{Package: pkg, TypeName: tspec.Name.Name, Pos: pos} + debugf("found sum type decl: %s.%s", decl.Package.PkgPath, decl.TypeName) decls = append(decls, decl) break } diff --git a/vendor/github.com/alecthomas/go-check-sumtype/def.go b/vendor/github.com/alecthomas/go-check-sumtype/def.go index 811b98f98..24729ac01 100644 --- a/vendor/github.com/alecthomas/go-check-sumtype/def.go +++ b/vendor/github.com/alecthomas/go-check-sumtype/def.go @@ -1,11 +1,21 @@ package gochecksumtype import ( + "flag" "fmt" "go/token" "go/types" + "log" ) +var debug = flag.Bool("debug", false, "enable debug logging") + +func debugf(format string, args ...interface{}) { + if *debug { + log.Printf(format, args...) + } +} + // Error as returned by Run() type Error interface { error @@ -107,6 +117,7 @@ func newSumTypeDef(pkg *types.Package, decl sumTypeDecl) (*sumTypeDef, error) { Decl: decl, Ty: iface, } + debugf("searching for variants of %s.%s\n", pkg.Path(), decl.TypeName) for _, name := range pkg.Scope().Names() { obj, ok := pkg.Scope().Lookup(name).(*types.TypeName) if !ok { @@ -116,7 +127,12 @@ func newSumTypeDef(pkg *types.Package, decl sumTypeDecl) (*sumTypeDef, error) { if types.Identical(ty.Underlying(), iface) { continue } + // Skip generic types. + if named, ok := ty.(*types.Named); ok && named.TypeParams() != nil { + continue + } if types.Implements(ty, iface) || types.Implements(types.NewPointer(ty), iface) { + debugf(" found variant: %s.%s\n", pkg.Path(), obj.Name()) def.Variants = append(def.Variants, obj) } } diff --git a/vendor/github.com/bombsimon/wsl/v3/.gitignore b/vendor/github.com/bombsimon/wsl/v4/.gitignore index 1c8eba613..1c8eba613 100644 --- a/vendor/github.com/bombsimon/wsl/v3/.gitignore +++ b/vendor/github.com/bombsimon/wsl/v4/.gitignore diff --git a/vendor/github.com/bombsimon/wsl/v3/.golangci.yml b/vendor/github.com/bombsimon/wsl/v4/.golangci.yml index 336ad4bc8..543012008 100644 --- a/vendor/github.com/bombsimon/wsl/v3/.golangci.yml +++ b/vendor/github.com/bombsimon/wsl/v4/.golangci.yml @@ -39,6 +39,7 @@ linters: disable: - cyclop - deadcode + - depguard - dupl - dupword - exhaustivestruct @@ -62,11 +63,13 @@ linters: - nosnakecase - paralleltest - prealloc + - rowserrcheck - scopelint - structcheck - testpackage - varcheck - varnamelen + - wastedassign fast: false diff --git a/vendor/github.com/bombsimon/wsl/v3/LICENSE b/vendor/github.com/bombsimon/wsl/v4/LICENSE index 4dade6d1c..4dade6d1c 100644 --- a/vendor/github.com/bombsimon/wsl/v3/LICENSE +++ b/vendor/github.com/bombsimon/wsl/v4/LICENSE diff --git a/vendor/github.com/bombsimon/wsl/v3/README.md b/vendor/github.com/bombsimon/wsl/v4/README.md index 8ff74392b..0bcf01d96 100644 --- a/vendor/github.com/bombsimon/wsl/v3/README.md +++ b/vendor/github.com/bombsimon/wsl/v4/README.md @@ -1,4 +1,4 @@ -# WSL - Whitespace Linter +# wsl - Whitespace Linter [](https://forthebadge.com) [](https://forthebadge.com) @@ -6,83 +6,58 @@ [](https://github.com/bombsimon/wsl/actions/workflows/go.yml) [](https://coveralls.io/github/bombsimon/wsl?branch=master) -WSL is a linter that enforces a very **non scientific** vision of how to make +`wsl` is a linter that enforces a very **non scientific** vision of how to make code more readable by enforcing empty lines at the right places. -I think too much code out there is to cuddly and a bit too warm for it's own -good, making it harder for other people to read and understand. The linter will -warn about newlines in and around blocks, in the beginning of files and other -places in the code. - -**I know this linter is aggressive** and a lot of projects I've tested it on -have failed miserably. For this linter to be useful at all I want to be open to -new ideas, configurations and discussions! Also note that some of the warnings -might be bugs or unintentional false positives so I would love an +**This linter is aggressive** and a lot of projects I've tested it on have +failed miserably. For this linter to be useful at all I want to be open to new +ideas, configurations and discussions! Also note that some of the warnings might +be bugs or unintentional false positives so I would love an [issue](https://github.com/bombsimon/wsl/issues/new) to fix, discuss, change or make something configurable! ## Installation -### By `go get` (local installation) - -You can do that by using: - ```sh -go get -u github.com/bombsimon/wsl/v3/cmd/... -``` - -### By golangci-lint (CI automation) +# Latest release +go install github.com/bombsimon/wsl/v4/cmd/wsl -`wsl` is already integrated with -[golangci-lint](https://github.com/golangci/golangci-lint). Please refer to the -instructions there. +# Main branch +go install github.com/bombsimon/wsl/v4/cmd/wsl@master +``` ## Usage -How to use depends on how you install `wsl`. +> **Note**: This linter provides a fixer that can fix most issues with the +> `--fix` flag. However, currently `golangci-lint` [does not support suggested +> fixes](https://github.com/golangci/golangci-lint/issues/1779) so the `--fix` +> flag in `golangci-lint` will **not** work. -### With local binary - -The general command format for `wsl` is: +`wsl` uses the [analysis](https://pkg.go.dev/golang.org/x/tools/go/analysis) +package meaning it will operate on package level with the default analysis flags +and way of working. ```sh -$ wsl [flags] <file1> [files...] -$ wsl [flags] </path/to/package/...> - -# Examples +wsl --help +wsl [flags] </path/to/package/...> -$ wsl ./main.go -$ wsl --no-test ./main.go -$ wsl --allow-cuddle-declarations ./main.go -$ wsl --no-test --allow-cuddle-declaration ./main.go -$ wsl --no-test --allow-trailing-comment ./myProject/... +wsl --allow-cuddle-declarations --fix ./... ``` -The "..." wildcard is not used like other `go` commands but instead can only -be to a relative or absolute path. - -By default, the linter will run on `./...` which means all go files in the -current path and all subsequent paths, including test files. To disable linting -test files, use `-n` or `--no-test`. - -### By `golangci-lint` (CI automation) - -The recommended command is: +`wsl` is also integrated in [`golangci-lint`](https://golangci-lint.run) ```sh -golangci-lint run --disable-all --enable wsl +golangci-lint run --no-config --disable-all --enable wsl ``` -For more information, please refer to -[golangci-lint](https://github.com/golangci/golangci-lint)'s documentation. - ## Issues and configuration The linter suppers a few ways to configure it to satisfy more than one kind of code style. These settings could be set either with flags or with YAML configuration if used via `golangci-lint`. -The supported configuration can be found [in the documentation](doc/configuration.md). +The supported configuration can be found [in the +documentation](doc/configuration.md). Below are the available checklist for any hit from `wsl`. If you do not see any, feel free to raise an [issue](https://github.com/bombsimon/wsl/issues/new). @@ -106,14 +81,11 @@ feel free to raise an [issue](https://github.com/bombsimon/wsl/issues/new). * [For statements should only be cuddled with assignments used in the iteration](doc/rules.md#for-statements-should-only-be-cuddled-with-assignments-used-in-the-iteration) * [Go statements can only invoke functions assigned on line above](doc/rules.md#go-statements-can-only-invoke-functions-assigned-on-line-above) * [If statements should only be cuddled with assignments](doc/rules.md#if-statements-should-only-be-cuddled-with-assignments) -* [If statements should only be cuddled with assignments used in the if - statement - itself](doc/rules.md#if-statements-should-only-be-cuddled-with-assignments-used-in-the-if-statement-itself) +* [If statements should only be cuddled with assignments used in the if statement itself](doc/rules.md#if-statements-should-only-be-cuddled-with-assignments-used-in-the-if-statement-itself) * [If statements that check an error must be cuddled with the statement that assigned the error](doc/rules.md#if-statements-that-check-an-error-must-be-cuddled-with-the-statement-that-assigned-the-error) -* [Only cuddled expressions if assigning variable or using from line - above](doc/rules.md#only-cuddled-expressions-if-assigning-variable-or-using-from-line-above) +* [Only cuddled expressions if assigning variable or using from line above](doc/rules.md#only-cuddled-expressions-if-assigning-variable-or-using-from-line-above) * [Only one cuddle assignment allowed before defer statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-defer-statement) -* [Only one cuddle assginment allowed before for statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-for-statement) +* [Only one cuddle assignment allowed before for statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-for-statement) * [Only one cuddle assignment allowed before go statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-go-statement) * [Only one cuddle assignment allowed before if statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-if-statement) * [Only one cuddle assignment allowed before range statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-range-statement) diff --git a/vendor/github.com/bombsimon/wsl/v4/analyzer.go b/vendor/github.com/bombsimon/wsl/v4/analyzer.go new file mode 100644 index 000000000..b8eac1587 --- /dev/null +++ b/vendor/github.com/bombsimon/wsl/v4/analyzer.go @@ -0,0 +1,141 @@ +package wsl + +import ( + "flag" + "strings" + + "golang.org/x/tools/go/analysis" +) + +func NewAnalyzer(config *Configuration) *analysis.Analyzer { + wa := &wslAnalyzer{config: config} + + return &analysis.Analyzer{ + Name: "wsl", + Doc: "add or remove empty lines", + Flags: wa.flags(), + Run: wa.run, + RunDespiteErrors: true, + } +} + +func defaultConfig() *Configuration { + return &Configuration{ + AllowAssignAndAnythingCuddle: false, + AllowAssignAndCallCuddle: true, + AllowCuddleDeclaration: false, + AllowMultiLineAssignCuddle: true, + AllowSeparatedLeadingComment: false, + AllowTrailingComment: false, + ForceCuddleErrCheckAndAssign: false, + ForceExclusiveShortDeclarations: false, + StrictAppend: true, + AllowCuddleWithCalls: []string{"Lock", "RLock"}, + AllowCuddleWithRHS: []string{"Unlock", "RUnlock"}, + ErrorVariableNames: []string{"err"}, + ForceCaseTrailingWhitespaceLimit: 0, + } +} + +// wslAnalyzer is a wrapper around the configuration which is used to be able to +// set the configuration when creating the analyzer and later be able to update +// flags and running method. +type wslAnalyzer struct { + config *Configuration +} + +func (wa *wslAnalyzer) flags() flag.FlagSet { + flags := flag.NewFlagSet("", flag.ExitOnError) + + // If we have a configuration set we're not running from the command line so + // we don't use any flags. + if wa.config != nil { + return *flags + } + + wa.config = defaultConfig() + + flags.BoolVar(&wa.config.AllowAssignAndAnythingCuddle, "allow-assign-and-anything", false, "Allow assignments and anything to be cuddled") + flags.BoolVar(&wa.config.AllowAssignAndCallCuddle, "allow-assign-and-call", true, "Allow assignments and calls to be cuddled (if using same variable/type)") + flags.BoolVar(&wa.config.AllowCuddleDeclaration, "allow-cuddle-declarations", false, "Allow declarations to be cuddled") + flags.BoolVar(&wa.config.AllowMultiLineAssignCuddle, "allow-multi-line-assign", true, "Allow cuddling with multi line assignments") + flags.BoolVar(&wa.config.AllowSeparatedLeadingComment, "allow-separated-leading-comment", false, "Allow empty newlines in leading comments") + flags.BoolVar(&wa.config.AllowTrailingComment, "allow-trailing-comment", false, "Allow blocks to end with a comment") + flags.BoolVar(&wa.config.ForceCuddleErrCheckAndAssign, "force-err-cuddling", false, "Force cuddling of error checks with error var assignment") + flags.BoolVar(&wa.config.ForceExclusiveShortDeclarations, "force-short-decl-cuddling", false, "Force short declarations to cuddle by themselves") + flags.BoolVar(&wa.config.StrictAppend, "strict-append", true, "Strict rules for append") + flags.IntVar(&wa.config.ForceCaseTrailingWhitespaceLimit, "force-case-trailing-whitespace", 0, "Force newlines for case blocks > this number.") + + flags.Var(&multiStringValue{slicePtr: &wa.config.AllowCuddleWithCalls}, "allow-cuddle-with-calls", "Comma separated list of idents that can have cuddles after") + flags.Var(&multiStringValue{slicePtr: &wa.config.AllowCuddleWithRHS}, "allow-cuddle-with-rhs", "Comma separated list of idents that can have cuddles before") + flags.Var(&multiStringValue{slicePtr: &wa.config.ErrorVariableNames}, "error-variable-names", "Comma separated list of error variable names") + + return *flags +} + +func (wa *wslAnalyzer) run(pass *analysis.Pass) (interface{}, error) { + for _, file := range pass.Files { + filename := pass.Fset.PositionFor(file.Pos(), false).Filename + if !strings.HasSuffix(filename, ".go") { + continue + } + + processor := newProcessorWithConfig(file, pass.Fset, wa.config) + processor.parseAST() + + for pos, fix := range processor.result { + textEdits := []analysis.TextEdit{} + for _, f := range fix.fixRanges { + textEdits = append(textEdits, analysis.TextEdit{ + Pos: f.fixRangeStart, + End: f.fixRangeEnd, + NewText: []byte("\n"), + }) + } + + pass.Report(analysis.Diagnostic{ + Pos: pos, + Category: "whitespace", + Message: fix.reason, + SuggestedFixes: []analysis.SuggestedFix{ + { + TextEdits: textEdits, + }, + }, + }) + } + } + + //nolint:nilnil // A pass don't need to return anything. + return nil, nil +} + +// multiStringValue is a flag that supports multiple values. It's implemented to +// contain a pointer to a string slice that will be overwritten when the flag's +// `Set` method is called. +type multiStringValue struct { + slicePtr *[]string +} + +// Set implements the flag.Value interface and will overwrite the pointer to the +// slice with a new pointer after splitting the flag by comma. +func (m *multiStringValue) Set(value string) error { + s := []string{} + + for _, v := range strings.Split(value, ",") { + s = append(s, strings.TrimSpace(v)) + } + + *m.slicePtr = s + + return nil +} + +// Set implements the flag.Value interface. +func (m *multiStringValue) String() string { + if m.slicePtr == nil { + return "" + } + + return strings.Join(*m.slicePtr, ", ") +} diff --git a/vendor/github.com/bombsimon/wsl/v3/wsl.go b/vendor/github.com/bombsimon/wsl/v4/wsl.go index 1b139c047..6fd33335a 100644 --- a/vendor/github.com/bombsimon/wsl/v3/wsl.go +++ b/vendor/github.com/bombsimon/wsl/v4/wsl.go @@ -3,45 +3,44 @@ package wsl import ( "fmt" "go/ast" - "go/parser" "go/token" - "os" "reflect" + "sort" "strings" ) // Error reason strings. const ( - reasonMustCuddleErrCheck = "if statements that check an error must be cuddled with the statement that assigned the error" - reasonOnlyCuddleIfWithAssign = "if statements should only be cuddled with assignments" - reasonOnlyOneCuddle = "only one cuddle assignment allowed before if statement" - reasonOnlyCuddleWithUsedAssign = "if statements should only be cuddled with assignments used in the if statement itself" - reasonOnlyCuddle2LineReturn = "return statements should not be cuddled if block has more than two lines" - reasonMultiLineBranchCuddle = "branch statements should not be cuddled if block has more than two lines" + reasonAnonSwitchCuddled = "anonymous switch statements should never be cuddled" reasonAppendCuddledWithoutUse = "append only allowed to cuddle with appended value" reasonAssignsCuddleAssign = "assignments should only be cuddled with other assignments" - reasonNeverCuddleDeclare = "declarations should never be cuddled" - reasonExpressionCuddledWithDeclOrRet = "expressions should not be cuddled with declarations or returns" - reasonExpressionCuddledWithBlock = "expressions should not be cuddled with blocks" - reasonExprCuddlingNonAssignedVar = "only cuddled expressions if assigning variable or using from line above" - reasonOneCuddleBeforeRange = "only one cuddle assignment allowed before range statement" - reasonRangeCuddledWithoutUse = "ranges should only be cuddled with assignments used in the iteration" - reasonOneCuddleBeforeDefer = "only one cuddle assignment allowed before defer statement" + reasonBlockEndsWithWS = "block should not end with a whitespace (or comment)" + reasonBlockStartsWithWS = "block should not start with a whitespace" + reasonCaseBlockTooCuddly = "case block should end with newline at this size" reasonDeferCuddledWithOtherVar = "defer statements should only be cuddled with expressions on same variable" - reasonForWithoutCondition = "for statement without condition should never be cuddled" - reasonForWithMoreThanOneCuddle = "only one cuddle assignment allowed before for statement" + reasonExprCuddlingNonAssignedVar = "only cuddled expressions if assigning variable or using from line above" + reasonExpressionCuddledWithBlock = "expressions should not be cuddled with blocks" + reasonExpressionCuddledWithDeclOrRet = "expressions should not be cuddled with declarations or returns" reasonForCuddledAssignWithoutUse = "for statements should only be cuddled with assignments used in the iteration" - reasonOneCuddleBeforeGo = "only one cuddle assignment allowed before go statement" + reasonForWithoutCondition = "for statement without condition should never be cuddled" reasonGoFuncWithoutAssign = "go statements can only invoke functions assigned on line above" - reasonSwitchManyCuddles = "only one cuddle assignment allowed before switch statement" - reasonAnonSwitchCuddled = "anonymous switch statements should never be cuddled" + reasonMultiLineBranchCuddle = "branch statements should not be cuddled if block has more than two lines" + reasonMustCuddleErrCheck = "if statements that check an error must be cuddled with the statement that assigned the error" + reasonNeverCuddleDeclare = "declarations should never be cuddled" + reasonOnlyCuddle2LineReturn = "return statements should not be cuddled if block has more than two lines" + reasonOnlyCuddleIfWithAssign = "if statements should only be cuddled with assignments" + reasonOnlyCuddleWithUsedAssign = "if statements should only be cuddled with assignments used in the if statement itself" + reasonOnlyOneCuddleBeforeDefer = "only one cuddle assignment allowed before defer statement" + reasonOnlyOneCuddleBeforeFor = "only one cuddle assignment allowed before for statement" + reasonOnlyOneCuddleBeforeGo = "only one cuddle assignment allowed before go statement" + reasonOnlyOneCuddleBeforeIf = "only one cuddle assignment allowed before if statement" + reasonOnlyOneCuddleBeforeRange = "only one cuddle assignment allowed before range statement" + reasonOnlyOneCuddleBeforeSwitch = "only one cuddle assignment allowed before switch statement" + reasonOnlyOneCuddleBeforeTypeSwitch = "only one cuddle assignment allowed before type switch statement" + reasonRangeCuddledWithoutUse = "ranges should only be cuddled with assignments used in the iteration" + reasonShortDeclNotExclusive = "short declaration should cuddle only with other short declarations" reasonSwitchCuddledWithoutUse = "switch statements should only be cuddled with variables switched" - reasonTypeSwitchTooCuddled = "only one cuddle assignment allowed before type switch statement" reasonTypeSwitchCuddledWithoutUse = "type switch statements should only be cuddled with variables switched" - reasonBlockStartsWithWS = "block should not start with a whitespace" - reasonBlockEndsWithWS = "block should not end with a whitespace (or comment)" - reasonCaseBlockTooCuddly = "case block should end with newline at this size" - reasonShortDeclNotExclusive = "short declaration should cuddle only with other short declarations" ) // Warning strings. @@ -54,6 +53,7 @@ const ( warnUnknownRHS = "UNKNOWN RHS" ) +// Configuration represents configurable settings for the linter. type Configuration struct { // StrictAppend will do strict checking when assigning from append (x = // append(x, y)). If this is set to true the append call must append either @@ -82,7 +82,7 @@ type Configuration struct { // x = AnotherAssign() AllowAssignAndCallCuddle bool - // AllowAssignAndCallCuddle allows assignments to be cuddled with anything. + // AllowAssignAndAnythingCuddle allows assignments to be cuddled with anything. // Example supported with this set to true: // if x == 1 { // x = 0 @@ -176,94 +176,40 @@ type Configuration struct { ForceExclusiveShortDeclarations bool } -// DefaultConfig returns default configuration. -func DefaultConfig() Configuration { - return Configuration{ - StrictAppend: true, - AllowAssignAndCallCuddle: true, - AllowAssignAndAnythingCuddle: false, - AllowMultiLineAssignCuddle: true, - AllowTrailingComment: false, - AllowSeparatedLeadingComment: false, - ForceCuddleErrCheckAndAssign: false, - ForceExclusiveShortDeclarations: false, - ForceCaseTrailingWhitespaceLimit: 0, - AllowCuddleWithCalls: []string{"Lock", "RLock"}, - AllowCuddleWithRHS: []string{"Unlock", "RUnlock"}, - ErrorVariableNames: []string{"err"}, - } +// fix is a range to fixup. +type fix struct { + fixRangeStart token.Pos + fixRangeEnd token.Pos } -// Result represents the result of one error. -type Result struct { - FileName string - LineNumber int - Position token.Position - Reason string +// result represents the result of one error. +type result struct { + fixRanges []fix + reason string } -// String returns the filename, line number and reason of a Result. -func (r *Result) String() string { - return fmt.Sprintf("%s:%d: %s", r.FileName, r.LineNumber, r.Reason) -} - -type Processor struct { - config Configuration - result []Result - warnings []string - fileSet *token.FileSet +// processor is the type that keeps track of the file and fileset and holds the +// results from parsing the AST. +type processor struct { + config *Configuration file *ast.File + fileSet *token.FileSet + result map[token.Pos]result + warnings []string } -// NewProcessor will create a Processor. -// -//nolint:gocritic // It's fine to copy config struct -func NewProcessorWithConfig(cfg Configuration) *Processor { - return &Processor{ - result: []Result{}, - config: cfg, - } -} - -// NewProcessor will create a Processor. -func NewProcessor() *Processor { - return NewProcessorWithConfig(DefaultConfig()) -} - -// ProcessFiles takes a string slice with file names (full paths) and lints -// them. -// -//nolint:gocritic // Don't want named returns -func (p *Processor) ProcessFiles(filenames []string) ([]Result, []string) { - for _, filename := range filenames { - data, err := os.ReadFile(filename) - if err != nil { - panic(err) - } - - p.process(filename, data) +// newProcessorWithConfig will create a Processor with the passed configuration. +func newProcessorWithConfig(file *ast.File, fileSet *token.FileSet, cfg *Configuration) *processor { + return &processor{ + config: cfg, + file: file, + fileSet: fileSet, + result: make(map[token.Pos]result), } - - return p.result, p.warnings } -func (p *Processor) process(filename string, data []byte) { - fileSet := token.NewFileSet() - file, err := parser.ParseFile(fileSet, filename, data, parser.ParseComments) - // If the file is not parsable let's add a syntax error and move on. - if err != nil { - p.result = append(p.result, Result{ - FileName: filename, - LineNumber: 0, - Reason: fmt.Sprintf("invalid syntax, file cannot be linted (%s)", err.Error()), - }) - - return - } - - p.fileSet = fileSet - p.file = file - +// parseAST will parse the AST attached to the Processor instance. +func (p *processor) parseAST() { for _, d := range p.file.Decls { switch v := d.(type) { case *ast.FuncDecl: @@ -279,7 +225,7 @@ func (p *Processor) process(filename string, data []byte) { // parseBlockBody will parse any kind of block statements such as switch cases // and if statements. A list of Result is returned. -func (p *Processor) parseBlockBody(ident *ast.Ident, block *ast.BlockStmt) { +func (p *processor) parseBlockBody(ident *ast.Ident, block *ast.BlockStmt) { // Nothing to do if there's no value. if reflect.ValueOf(block).IsNil() { return @@ -294,7 +240,7 @@ func (p *Processor) parseBlockBody(ident *ast.Ident, block *ast.BlockStmt) { // parseBlockStatements will parse all the statements found in the body of a // node. A list of Result is returned. -func (p *Processor) parseBlockStatements(statements []ast.Stmt) { +func (p *processor) parseBlockStatements(statements []ast.Stmt) { for i, stmt := range statements { // Start by checking if this statement is another block (other than if, // for and range). This could be assignment to a function, defer or go @@ -380,14 +326,38 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { continue } - moreThanOneStatementAbove := func() bool { - if i < 2 { + nStatementsBefore := func(n int) bool { + if i < n { return false } - statementBeforePreviousStatement := statements[i-2] + for j := 1; j < n; j++ { + s1 := statements[i-j] + s2 := statements[i-(j+1)] + + if p.nodeStart(s1)-1 != p.nodeEnd(s2) { + return false + } + } - return p.nodeStart(previousStatement)-1 == p.nodeEnd(statementBeforePreviousStatement) + return true + } + + nStatementsAfter := func(n int) bool { + if len(statements)-1 < i+n { + return false + } + + for j := 0; j < n; j++ { + s1 := statements[i+j] + s2 := statements[i+j+1] + + if p.nodeEnd(s1)+1 != p.nodeStart(s2) { + return false + } + } + + return true } isLastStatementInBlockOfOnlyTwoLines := func() bool { @@ -413,9 +383,30 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { // it was and use *that* statement's position if p.config.ForceExclusiveShortDeclarations && cuddledWithLastStmt { if p.isShortDecl(stmt) && !p.isShortDecl(previousStatement) { - p.addError(stmt.Pos(), reasonShortDeclNotExclusive) + var reportNode ast.Node = previousStatement + + cm := ast.NewCommentMap(p.fileSet, stmt, p.file.Comments) + if cg, ok := cm[stmt]; ok && len(cg) > 0 { + for _, c := range cg { + if c.Pos() > previousStatement.End() && c.End() < stmt.Pos() { + reportNode = c + } + } + } + + p.addErrorRange( + stmt.Pos(), + reportNode.End(), + reportNode.End(), + reasonShortDeclNotExclusive, + ) } else if p.isShortDecl(previousStatement) && !p.isShortDecl(stmt) { - p.addError(previousStatement.Pos(), reasonShortDeclNotExclusive) + p.addErrorRange( + previousStatement.Pos(), + stmt.Pos(), + stmt.Pos(), + reasonShortDeclNotExclusive, + ) } } @@ -428,6 +419,31 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { } } + reportNewlineTwoLinesAbove := func(n1, n2 ast.Node, reason string) { + if atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) || + atLeastOneInListsMatch(assignedOnLineAbove, calledOrAssignedFirstInBlock) { + // If both the assignment on the line above _and_ the assignment + // two lines above is part of line or first in block, add the + // newline as if non were. + _, isAssignmentTwoLinesAbove := statements[i-2].(*ast.AssignStmt) + assignedTwoLinesAbove := p.findLHS(statements[i-2]) + + if isAssignmentTwoLinesAbove && + (atLeastOneInListsMatch(rightAndLeftHandSide, assignedTwoLinesAbove) || + atLeastOneInListsMatch(assignedTwoLinesAbove, calledOrAssignedFirstInBlock)) { + p.addWhitespaceBeforeError(n1, reason) + } else { + // If the variable on the line above is allowed to be + // cuddled, break two lines above so we keep the proper + // cuddling. + p.addErrorRange(n1.Pos(), n2.Pos(), n2.Pos(), reason) + } + } else { + // If not, break here so we separate the cuddled variable. + p.addWhitespaceBeforeError(n1, reason) + } + } + switch t := stmt.(type) { case *ast.IfStmt: checkingErrInitializedInline := func() bool { @@ -460,7 +476,12 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { } if atLeastOneInListsMatch(assignedOnLineAbove, p.config.ErrorVariableNames) { - p.addError(t.Pos(), reasonMustCuddleErrCheck) + p.addErrorRange( + stmt.Pos(), + previousStatement.End(), + stmt.Pos(), + reasonMustCuddleErrCheck, + ) } } @@ -468,12 +489,12 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { } if len(assignedOnLineAbove) == 0 { - p.addError(t.Pos(), reasonOnlyCuddleIfWithAssign) + p.addWhitespaceBeforeError(t, reasonOnlyCuddleIfWithAssign) continue } - if moreThanOneStatementAbove() { - p.addError(t.Pos(), reasonOnlyOneCuddle) + if nStatementsBefore(2) { + reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeIf) continue } @@ -485,33 +506,34 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { continue } - p.addError(t.Pos(), reasonOnlyCuddleWithUsedAssign) + p.addWhitespaceBeforeError(t, reasonOnlyCuddleWithUsedAssign) case *ast.ReturnStmt: if isLastStatementInBlockOfOnlyTwoLines() { continue } - p.addError(t.Pos(), reasonOnlyCuddle2LineReturn) + p.addWhitespaceBeforeError(t, reasonOnlyCuddle2LineReturn) case *ast.BranchStmt: if isLastStatementInBlockOfOnlyTwoLines() { continue } - p.addError(t.Pos(), reasonMultiLineBranchCuddle) + p.addWhitespaceBeforeError(t, reasonMultiLineBranchCuddle) case *ast.AssignStmt: // append is usually an assignment but should not be allowed to be // cuddled with anything not appended. if len(rightHandSide) > 0 && rightHandSide[len(rightHandSide)-1] == "append" { if p.config.StrictAppend { if !atLeastOneInListsMatch(calledOrAssignedOnLineAbove, rightHandSide) { - p.addError(t.Pos(), reasonAppendCuddledWithoutUse) + p.addWhitespaceBeforeError(t, reasonAppendCuddledWithoutUse) } } continue } - if _, ok := previousStatement.(*ast.AssignStmt); ok { + switch previousStatement.(type) { + case *ast.AssignStmt, *ast.IncDecStmt: continue } @@ -535,10 +557,18 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { } } - p.addError(t.Pos(), reasonAssignsCuddleAssign) + p.addWhitespaceBeforeError(t, reasonAssignsCuddleAssign) + case *ast.IncDecStmt: + switch previousStatement.(type) { + case *ast.AssignStmt, *ast.IncDecStmt: + continue + } + + p.addWhitespaceBeforeError(t, reasonAssignsCuddleAssign) + case *ast.DeclStmt: if !p.config.AllowCuddleDeclaration { - p.addError(t.Pos(), reasonNeverCuddleDeclare) + p.addWhitespaceBeforeError(t, reasonNeverCuddleDeclare) } case *ast.ExprStmt: switch previousStatement.(type) { @@ -547,9 +577,9 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { continue } - p.addError(t.Pos(), reasonExpressionCuddledWithDeclOrRet) + p.addWhitespaceBeforeError(t, reasonExpressionCuddledWithDeclOrRet) case *ast.IfStmt, *ast.RangeStmt, *ast.SwitchStmt: - p.addError(t.Pos(), reasonExpressionCuddledWithBlock) + p.addWhitespaceBeforeError(t, reasonExpressionCuddledWithBlock) } // If the expression is called on a type or variable used or @@ -567,17 +597,17 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { // If we assigned variables on the line above but didn't use them in // this expression there should probably be a newline between them. if len(assignedOnLineAbove) > 0 && !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) { - p.addError(t.Pos(), reasonExprCuddlingNonAssignedVar) + p.addWhitespaceBeforeError(t, reasonExprCuddlingNonAssignedVar) } case *ast.RangeStmt: - if moreThanOneStatementAbove() { - p.addError(t.Pos(), reasonOneCuddleBeforeRange) + if nStatementsBefore(2) { + reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeRange) continue } if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) { if !atLeastOneInListsMatch(assignedOnLineAbove, calledOrAssignedFirstInBlock) { - p.addError(t.Pos(), reasonRangeCuddledWithoutUse) + p.addWhitespaceBeforeError(t, reasonRangeCuddledWithoutUse) } } case *ast.DeferStmt: @@ -586,27 +616,38 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { continue } - // Special treatment of deferring body closes after error checking - // according to best practices. See - // https://github.com/bombsimon/wsl/issues/31 which links to - // discussion about error handling after HTTP requests. This is hard - // coded and very specific but for now this is to be seen as a - // special case. What this does is that it *only* allows a defer - // statement with `Close` on the right hand side to be cuddled with - // an if-statement to support this: - // resp, err := client.Do(req) - // if err != nil { - // return err - // } - // defer resp.Body.Close() - if _, ok := previousStatement.(*ast.IfStmt); ok { - if atLeastOneInListsMatch(rightHandSide, []string{"Close"}) { - continue + if nStatementsBefore(2) { + // We allow cuddling defer if the defer references something + // used two lines above. + // There are several reasons to why we do this. + // Originally there was a special use case only for "Close" + // + // https://github.com/bombsimon/wsl/issues/31 which links to + // resp, err := client.Do(req) + // if err != nil { + // return err + // } + // defer resp.Body.Close() + // + // After a discussion in a followup issue it makes sense to not + // only hard code `Close` but for anything that's referenced two + // statements above. + // + // https://github.com/bombsimon/wsl/issues/85 + // db, err := OpenDB() + // require.NoError(t, err) + // defer db.Close() + // + // All of this is only allowed if there's exactly three cuddled + // statements, otherwise the regular rules apply. + if !nStatementsBefore(3) && !nStatementsAfter(1) { + variablesTwoLinesAbove := append(p.findLHS(statements[i-2]), p.findRHS(statements[i-2])...) + if atLeastOneInListsMatch(rightHandSide, variablesTwoLinesAbove) { + continue + } } - } - if moreThanOneStatementAbove() { - p.addError(t.Pos(), reasonOneCuddleBeforeDefer) + reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeDefer) continue } @@ -620,7 +661,7 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { } // Allow use to cuddled defer func literals with usages on line - // abouve. Example: + // above. Example: // b := getB() // defer func() { // makesSenseToUse(b) @@ -638,18 +679,16 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { } if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) { - p.addError(t.Pos(), reasonDeferCuddledWithOtherVar) + p.addWhitespaceBeforeError(t, reasonDeferCuddledWithOtherVar) } case *ast.ForStmt: if len(rightAndLeftHandSide) == 0 { - p.addError(t.Pos(), reasonForWithoutCondition) - + p.addWhitespaceBeforeError(t, reasonForWithoutCondition) continue } - if moreThanOneStatementAbove() { - p.addError(t.Pos(), reasonForWithMoreThanOneCuddle) - + if nStatementsBefore(2) { + reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeFor) continue } @@ -658,7 +697,7 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { // first line in the block for details. if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) { if !atLeastOneInListsMatch(assignedOnLineAbove, calledOrAssignedFirstInBlock) { - p.addError(t.Pos(), reasonForCuddledAssignWithoutUse) + p.addWhitespaceBeforeError(t, reasonForCuddledAssignWithoutUse) } } case *ast.GoStmt: @@ -666,9 +705,8 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { continue } - if moreThanOneStatementAbove() { - p.addError(t.Pos(), reasonOneCuddleBeforeGo) - + if nStatementsBefore(2) { + reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeGo) continue } @@ -689,26 +727,24 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { } if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) { - p.addError(t.Pos(), reasonGoFuncWithoutAssign) + p.addWhitespaceBeforeError(t, reasonGoFuncWithoutAssign) } case *ast.SwitchStmt: - if moreThanOneStatementAbove() { - p.addError(t.Pos(), reasonSwitchManyCuddles) - + if nStatementsBefore(2) { + reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeSwitch) continue } if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) { if len(rightAndLeftHandSide) == 0 { - p.addError(t.Pos(), reasonAnonSwitchCuddled) + p.addWhitespaceBeforeError(t, reasonAnonSwitchCuddled) } else { - p.addError(t.Pos(), reasonSwitchCuddledWithoutUse) + p.addWhitespaceBeforeError(t, reasonSwitchCuddledWithoutUse) } } case *ast.TypeSwitchStmt: - if moreThanOneStatementAbove() { - p.addError(t.Pos(), reasonTypeSwitchTooCuddled) - + if nStatementsBefore(2) { + reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeTypeSwitch) continue } @@ -717,11 +753,11 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { // Allow type assertion on variables used in the first case // immediately. if !atLeastOneInListsMatch(assignedOnLineAbove, calledOrAssignedFirstInBlock) { - p.addError(t.Pos(), reasonTypeSwitchCuddledWithoutUse) + p.addWhitespaceBeforeError(t, reasonTypeSwitchCuddledWithoutUse) } } case *ast.CaseClause, *ast.CommClause: - // Case clauses will be checked by not allowing leading ot trailing + // Case clauses will be checked by not allowing leading of trailing // whitespaces within the block. There's nothing in the case itself // that may be cuddled. default: @@ -735,7 +771,7 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { // directly as the first argument inside a body. // The body will then be parsed as a *ast.BlockStmt (regular block) or as a list // of []ast.Stmt (case block). -func (p *Processor) firstBodyStatement(i int, allStmt []ast.Stmt) ast.Node { +func (p *processor) firstBodyStatement(i int, allStmt []ast.Stmt) ast.Node { stmt := allStmt[i] // Start by checking if the statement has a body (probably if-statement, @@ -766,7 +802,14 @@ func (p *Processor) firstBodyStatement(i int, allStmt []ast.Stmt) ast.Node { } } - p.parseBlockBody(nil, statementBodyContent) + // If statement bodies will be parsed already when finding block bodies. + // The reason is because if/else-if/else chains is nested in the AST + // where the else bit is a part of the if statement. Since if statements + // is the only statement that can be chained like this we exclude it + // from parsing it again here. + if _, ok := stmt.(*ast.IfStmt); !ok { + p.parseBlockBody(nil, statementBodyContent) + } case []ast.Stmt: // The Body field for an *ast.CaseClause or *ast.CommClause is of type // []ast.Stmt. We must check leading and trailing whitespaces and then @@ -791,7 +834,7 @@ func (p *Processor) firstBodyStatement(i int, allStmt []ast.Stmt) ast.Node { return firstBodyStatement } -func (p *Processor) findLHS(node ast.Node) []string { +func (p *processor) findLHS(node ast.Node) []string { var lhs []string if node == nil { @@ -850,7 +893,7 @@ func (p *Processor) findLHS(node ast.Node) []string { return lhs } -func (p *Processor) findRHS(node ast.Node) []string { +func (p *processor) findRHS(node ast.Node) []string { var rhs []string if node == nil { @@ -934,7 +977,7 @@ func (p *Processor) findRHS(node ast.Node) []string { return rhs } -func (p *Processor) isShortDecl(node ast.Node) bool { +func (p *processor) isShortDecl(node ast.Node) bool { if t, ok := node.(*ast.AssignStmt); ok { return t.Tok == token.DEFINE } @@ -942,16 +985,22 @@ func (p *Processor) isShortDecl(node ast.Node) bool { return false } -func (p *Processor) findBlockStmt(node ast.Node) []*ast.BlockStmt { +func (p *processor) findBlockStmt(node ast.Node) []*ast.BlockStmt { var blocks []*ast.BlockStmt switch t := node.(type) { + case *ast.BlockStmt: + return []*ast.BlockStmt{t} case *ast.AssignStmt: for _, x := range t.Rhs { blocks = append(blocks, p.findBlockStmt(x)...) } case *ast.CallExpr: blocks = append(blocks, p.findBlockStmt(t.Fun)...) + + for _, x := range t.Args { + blocks = append(blocks, p.findBlockStmt(x)...) + } case *ast.FuncLit: blocks = append(blocks, t.Body) case *ast.ExprStmt: @@ -964,6 +1013,8 @@ func (p *Processor) findBlockStmt(node ast.Node) []*ast.BlockStmt { blocks = append(blocks, p.findBlockStmt(t.Call)...) case *ast.GoStmt: blocks = append(blocks, p.findBlockStmt(t.Call)...) + case *ast.IfStmt: + blocks = append([]*ast.BlockStmt{t.Body}, p.findBlockStmt(t.Else)...) } return blocks @@ -1020,15 +1071,15 @@ func atLeastOneInListsMatch(listOne, listTwo []string) bool { // findLeadingAndTrailingWhitespaces will find leading and trailing whitespaces // in a node. The method takes comments in consideration which will make the // parser more gentle. -func (p *Processor) findLeadingAndTrailingWhitespaces(ident *ast.Ident, stmt, nextStatement ast.Node) { +func (p *processor) findLeadingAndTrailingWhitespaces(ident *ast.Ident, stmt, nextStatement ast.Node) { var ( - allowedLinesBeforeFirstStatement = 1 - commentMap = ast.NewCommentMap(p.fileSet, stmt, p.file.Comments) - blockStatements []ast.Stmt - blockStartLine int - blockEndLine int - blockStartPos token.Pos - blockEndPos token.Pos + commentMap = ast.NewCommentMap(p.fileSet, stmt, p.file.Comments) + blockStatements []ast.Stmt + blockStartLine int + blockEndLine int + blockStartPos token.Pos + blockEndPos token.Pos + isCase bool ) // Depending on the block type, get the statements in the block and where @@ -1041,9 +1092,11 @@ func (p *Processor) findLeadingAndTrailingWhitespaces(ident *ast.Ident, stmt, ne case *ast.CaseClause: blockStatements = t.Body blockStartPos = t.Colon + isCase = true case *ast.CommClause: blockStatements = t.Body blockStartPos = t.Colon + isCase = true default: p.addWarning(warnWSNodeTypeNotImplemented, stmt.Pos(), stmt) @@ -1055,8 +1108,8 @@ func (p *Processor) findLeadingAndTrailingWhitespaces(ident *ast.Ident, stmt, ne return } - blockStartLine = p.fileSet.Position(blockStartPos).Line - blockEndLine = p.fileSet.Position(blockEndPos).Line + blockStartLine = p.fileSet.PositionFor(blockStartPos, false).Line + blockEndLine = p.fileSet.PositionFor(blockEndPos, false).Line // No whitespace possible if LBrace and RBrace is on the same line. if blockStartLine == blockEndLine { @@ -1064,152 +1117,238 @@ func (p *Processor) findLeadingAndTrailingWhitespaces(ident *ast.Ident, stmt, ne } var ( - firstStatement = blockStatements[0] - lastStatement = blockStatements[len(blockStatements)-1] - seenCommentGroups = 0 + firstStatement = blockStatements[0] + lastStatement = blockStatements[len(blockStatements)-1] ) - // Get the comment related to the first statement, we do allow commends in + // Get the comment related to the first statement, we do allow comments in // the beginning of a block before the first statement. - if c, ok := commentMap[firstStatement]; ok { - for _, commentGroup := range c { - // If the comment group is on the same line as the block start - // (LBrace) we should not consider it. - if p.nodeStart(commentGroup) == blockStartLine { + var ( + openingNodePos = blockStartPos + 1 + lastLeadingComment ast.Node + ) + + var ( + firstStatementCommentGroups []*ast.CommentGroup + lastStatementCommentGroups []*ast.CommentGroup + ) + + if cg, ok := commentMap[firstStatement]; ok && !isCase { + firstStatementCommentGroups = cg + } else { + // TODO: Just like with trailing whitespaces comments in a case block is + // tied to the last token of the first statement. For now we iterate over + // all comments in the stmt and grab those that's after colon and before + // first statement. + for _, cg := range commentMap { + if len(cg) < 1 { continue } - // We only care about comments before our statement from the comment - // map. As soon as we hit comments after our statement let's break - // out! - if commentGroup.Pos() > firstStatement.Pos() { - break + // If we have comments and the last comment ends before the first + // statement and the node is after the colon, this must be the node + // mapped to comments. + for _, c := range cg { + if c.End() < firstStatement.Pos() && c.Pos() > blockStartPos { + firstStatementCommentGroups = append(firstStatementCommentGroups, c) + } } - // We store number of seen comment groups because we allow multiple - // groups with a newline between them; but if the first one has WS - // before it, we're not going to count it to force an error. - if p.config.AllowSeparatedLeadingComment { - cg := p.fileSet.Position(commentGroup.Pos()).Line + // And same if we have comments where the first comment is after the + // last statement but before the next statement (next case). As with + // the other things, if there is not next statement it's no next + // case and the logic will be handled when parsing the block. + if nextStatement == nil { + continue + } - if seenCommentGroups > 0 || cg == blockStartLine+1 { - seenCommentGroups++ + for _, c := range cg { + if c.Pos() > lastStatement.End() && c.End() < nextStatement.Pos() { + lastStatementCommentGroups = append(lastStatementCommentGroups, c) } - } else { - seenCommentGroups++ } + } + + // Since the comments come from a map they might not be ordered meaning + // that the last and first comment groups can be in the wrong order. We + // fix this by sorting all comments by pos after adding them all to the + // slice. + sort.Slice(firstStatementCommentGroups, func(i, j int) bool { + return firstStatementCommentGroups[i].Pos() < firstStatementCommentGroups[j].Pos() + }) + + sort.Slice(lastStatementCommentGroups, func(i, j int) bool { + return lastStatementCommentGroups[i].Pos() < lastStatementCommentGroups[j].Pos() + }) + } - // Support both /* multiline */ and //single line comments - for _, c := range commentGroup.List { - allowedLinesBeforeFirstStatement += len(strings.Split(c.Text, "\n")) + for _, commentGroup := range firstStatementCommentGroups { + // If the comment group is on the same line as the block start + // (LBrace) we should not consider it. + if p.nodeEnd(commentGroup) == blockStartLine { + openingNodePos = commentGroup.End() + continue + } + + // We only care about comments before our statement from the comment + // map. As soon as we hit comments after our statement let's break + // out! + if commentGroup.Pos() > firstStatement.Pos() { + break + } + + // We never allow leading whitespace for the first comment. + if lastLeadingComment == nil && p.nodeStart(commentGroup)-1 != blockStartLine { + p.addErrorRange( + openingNodePos, + openingNodePos, + commentGroup.Pos(), + reasonBlockStartsWithWS, + ) + } + + // If lastLeadingComment is set this is not the first comment so we + // should remove whitespace between them if we don't explicitly + // allow it. + if lastLeadingComment != nil && !p.config.AllowSeparatedLeadingComment { + if p.nodeStart(commentGroup)+1 != p.nodeEnd(lastLeadingComment) { + p.addErrorRange( + openingNodePos, + lastLeadingComment.End(), + commentGroup.Pos(), + reasonBlockStartsWithWS, + ) } } + + lastLeadingComment = commentGroup } - // If we allow separated comments, allow for a space after each group - if p.config.AllowSeparatedLeadingComment { - if seenCommentGroups > 1 { - allowedLinesBeforeFirstStatement += seenCommentGroups - 1 - } else if seenCommentGroups == 1 { - allowedLinesBeforeFirstStatement++ - } + lastNodePos := openingNodePos + if lastLeadingComment != nil { + lastNodePos = lastLeadingComment.End() + blockStartLine = p.nodeEnd(lastLeadingComment) } - // And now if the first statement is passed the number of allowed lines, - // then we had extra WS, possibly before the first comment group. - if p.nodeStart(firstStatement) > blockStartLine+allowedLinesBeforeFirstStatement { - p.addError( - blockStartPos, + // Check if we have a whitespace between the last node which can be the + // Lbrace, a comment on the same line or the last comment if we have + // comments inside the actual block and the first statement. This is never + // allowed. + if p.nodeStart(firstStatement)-1 != blockStartLine { + p.addErrorRange( + openingNodePos, + lastNodePos, + firstStatement.Pos(), reasonBlockStartsWithWS, ) } // If the blockEndLine is not 0 we're a regular block (not case). if blockEndLine != 0 { - if p.config.AllowTrailingComment { - if lastComment, ok := commentMap[lastStatement]; ok { - var ( - lastCommentGroup = lastComment[len(lastComment)-1] - lastCommentLine = lastCommentGroup.List[len(lastCommentGroup.List)-1] - countNewlines = 0 - ) + // We don't want to reject example functions since they have to end with + // a comment. + if isExampleFunc(ident) { + return + } - countNewlines += len(strings.Split(lastCommentLine.Text, "\n")) + var ( + lastNode ast.Node = lastStatement + trailingComments []ast.Node + ) - // No newlines between trailing comments and end of block. - if p.nodeStart(lastCommentLine)+countNewlines != blockEndLine-1 { - return + // Check if we have an comments _after_ the last statement and update + // the last node if so. + if c, ok := commentMap[lastStatement]; ok { + lastComment := c[len(c)-1] + if lastComment.Pos() > lastStatement.End() && lastComment.Pos() < stmt.End() { + lastNode = lastComment + } + } + + // TODO: This should be improved. + // The trailing comments are mapped to the last statement item which can + // be anything depending on what the last statement is. + // In `fmt.Println("hello")`, trailing comments will be mapped to + // `*ast.BasicLit` for the "hello" string. + // A short term improvement can be to cache this but for now we naively + // iterate over all items when we check a block. + for _, commentGroups := range commentMap { + for _, commentGroup := range commentGroups { + if commentGroup.Pos() < lastNode.End() || commentGroup.End() > stmt.End() { + continue } + + trailingComments = append(trailingComments, commentGroup) } } - if p.nodeEnd(lastStatement) != blockEndLine-1 && !isExampleFunc(ident) { - p.addError(blockEndPos, reasonBlockEndsWithWS) + // TODO: Should this be relaxed? + // Given the old code we only allowed trailing newline if it was + // directly tied to the last statement so for backwards compatibility + // we'll do the same. This means we fail all but the last whitespace + // even when allowing trailing comments. + for _, comment := range trailingComments { + if p.nodeStart(comment)-p.nodeEnd(lastNode) > 1 { + p.addErrorRange( + blockEndPos, + lastNode.End(), + comment.Pos(), + reasonBlockEndsWithWS, + ) + } + + lastNode = comment + } + + if !p.config.AllowTrailingComment && p.nodeEnd(stmt)-1 != p.nodeEnd(lastStatement) { + p.addErrorRange( + blockEndPos, + lastNode.End(), + stmt.End()-1, + reasonBlockEndsWithWS, + ) } return } + // Nothing to do if we're not looking for enforced newline. + if p.config.ForceCaseTrailingWhitespaceLimit == 0 { + return + } + // If we don't have any nextStatement the trailing whitespace will be // handled when parsing the switch. If we do have a next statement we can // see where it starts by getting it's colon position. We set the end of the // current case to the position of the next case. - switch n := nextStatement.(type) { - case *ast.CaseClause: - blockEndPos = n.Case - case *ast.CommClause: - blockEndPos = n.Case + switch nextStatement.(type) { + case *ast.CaseClause, *ast.CommClause: default: // No more cases return } - blockEndLine = p.fileSet.Position(blockEndPos).Line - 1 - - var ( - blockSize = blockEndLine - blockStartLine - caseTrailingCommentLines int - ) - - // TODO: I don't know what comments are bound to in cases. For regular - // blocks the last comment is bound to the last statement but for cases - // they are bound to the case clause expression. This will however get us all - // comments and depending on the case expression this gets tricky. - // - // To handle this I get the comment map from the current statement (the case - // itself) and iterate through all groups and all comment within all groups. - // I then get the comments after the last statement but before the next case - // clause and just map each line of comment that way. - for _, commentGroups := range commentMap { - for _, commentGroup := range commentGroups { - for _, comment := range commentGroup.List { - commentLine := p.fileSet.Position(comment.Pos()).Line - - // Ignore comments before the last statement. - if commentLine <= p.nodeStart(lastStatement) { - continue - } - - // Ignore comments after the end of this case. - if commentLine > blockEndLine { - continue - } - - // This allows /* multiline */ comments with newlines as well - // as regular (//) ones - caseTrailingCommentLines += len(strings.Split(comment.Text, "\n")) - } - } + var closingNode ast.Node = lastStatement + for _, commentGroup := range lastStatementCommentGroups { + // TODO: In future versions we might want to close the gaps between + // comments. However this is not currently reported in v3 so we + // won't add this for now. + // if p.nodeStart(commentGroup)-1 != p.nodeEnd(closingNode) {} + closingNode = commentGroup } - hasTrailingWhitespace := p.nodeEnd(lastStatement)+caseTrailingCommentLines != blockEndLine + totalRowsInCase := p.nodeEnd(closingNode) - blockStartLine + if totalRowsInCase < p.config.ForceCaseTrailingWhitespaceLimit { + return + } - // If the force trailing limit is configured and we don't end with a newline. - if p.config.ForceCaseTrailingWhitespaceLimit > 0 && !hasTrailingWhitespace { - // Check if the block size is too big to miss the newline. - if blockSize >= p.config.ForceCaseTrailingWhitespaceLimit { - p.addError(lastStatement.Pos(), reasonCaseBlockTooCuddly) - } + if p.nodeEnd(closingNode)+1 == p.nodeStart(nextStatement) { + p.addErrorRange( + closingNode.Pos(), + closingNode.End(), + closingNode.End(), + reasonCaseBlockTooCuddly, + ) } } @@ -1217,15 +1356,15 @@ func isExampleFunc(ident *ast.Ident) bool { return ident != nil && strings.HasPrefix(ident.Name, "Example") } -func (p *Processor) nodeStart(node ast.Node) int { - return p.fileSet.Position(node.Pos()).Line +func (p *processor) nodeStart(node ast.Node) int { + return p.fileSet.PositionFor(node.Pos(), false).Line } -func (p *Processor) nodeEnd(node ast.Node) int { - line := p.fileSet.Position(node.End()).Line +func (p *processor) nodeEnd(node ast.Node) int { + line := p.fileSet.PositionFor(node.End(), false).Line if isEmptyLabeledStmt(node) { - return p.fileSet.Position(node.Pos()).Line + return p.fileSet.PositionFor(node.Pos(), false).Line } return line @@ -1242,21 +1381,29 @@ func isEmptyLabeledStmt(node ast.Node) bool { return empty } -// Add an error for the file and line number for the current token.Pos with the -// given reason. -func (p *Processor) addError(pos token.Pos, reason string) { - position := p.fileSet.Position(pos) +func (p *processor) addWhitespaceBeforeError(node ast.Node, reason string) { + p.addErrorRange(node.Pos(), node.Pos(), node.Pos(), reason) +} + +func (p *processor) addErrorRange(reportAt, start, end token.Pos, reason string) { + report, ok := p.result[reportAt] + if !ok { + report = result{ + reason: reason, + fixRanges: []fix{}, + } + } - p.result = append(p.result, Result{ - FileName: position.Filename, - LineNumber: position.Line, - Position: position, - Reason: reason, + report.fixRanges = append(report.fixRanges, fix{ + fixRangeStart: start, + fixRangeEnd: end, }) + + p.result[reportAt] = report } -func (p *Processor) addWarning(w string, pos token.Pos, t interface{}) { - position := p.fileSet.Position(pos) +func (p *processor) addWarning(w string, pos token.Pos, t interface{}) { + position := p.fileSet.PositionFor(pos, false) p.warnings = append(p.warnings, fmt.Sprintf("%s:%d: %s (%T)", position.Filename, position.Line, w, t), diff --git a/vendor/github.com/butuzov/ireturn/analyzer/analyzer.go b/vendor/github.com/butuzov/ireturn/analyzer/analyzer.go index 21e5897b2..f68170fb3 100644 --- a/vendor/github.com/butuzov/ireturn/analyzer/analyzer.go +++ b/vendor/github.com/butuzov/ireturn/analyzer/analyzer.go @@ -4,6 +4,7 @@ import ( "flag" "go/ast" gotypes "go/types" + "runtime" "strings" "sync" @@ -22,10 +23,11 @@ type validator interface { } type analyzer struct { - once sync.Once - mu sync.RWMutex - handler validator - err error + once sync.Once + mu sync.RWMutex + handler validator + err error + diabledNolint bool found []analysis.Diagnostic } @@ -61,8 +63,7 @@ func (a *analyzer) run(pass *analysis.Pass) (interface{}, error) { } // 003. Is it allowed to be checked? - // TODO(butuzov): add inline comment - if hasDisallowDirective(f.Doc) { + if !a.diabledNolint && hasDisallowDirective(f.Doc) { return } @@ -70,7 +71,6 @@ func (a *analyzer) run(pass *analysis.Pass) (interface{}, error) { // 004. Filtering Results. for _, issue := range filterInterfaces(pass, f.Type, dotImportedStd) { - if a.handler.IsValid(issue) { continue } @@ -112,6 +112,13 @@ func (a *analyzer) readConfiguration(fs *flag.FlagSet) { return } + // First: checking nonolint directive + val := fs.Lookup("nonolint") + if val != nil { + a.diabledNolint = fs.Lookup("nonolint").Value.String() == "true" + } + + // Second: validators implementation next if validatorImpl, ok := cnf.(validator); ok { a.handler = validatorImpl return @@ -136,6 +143,7 @@ func flags() flag.FlagSet { set := flag.NewFlagSet("", flag.PanicOnError) set.String("allow", "", "accept-list of the comma-separated interfaces") set.String("reject", "", "reject-list of the comma-separated interfaces") + set.Bool("nonolint", false, "disable nolint checks") return *set } @@ -146,13 +154,10 @@ func filterInterfaces(p *analysis.Pass, ft *ast.FuncType, di map[string]struct{} return results } - tp := newTypeParams(ft.TypeParams) - for _, el := range ft.Results.List { switch v := el.Type.(type) { // ----- empty or anonymous interfaces case *ast.InterfaceType: - if len(v.Methods.List) == 0 { results = append(results, types.NewIssue("interface{}", types.EmptyInterface)) continue @@ -164,35 +169,65 @@ func filterInterfaces(p *analysis.Pass, ft *ast.FuncType, di map[string]struct{} case *ast.Ident: t1 := p.TypesInfo.TypeOf(el.Type) - if !gotypes.IsInterface(t1.Underlying()) { + val, ok := t1.Underlying().(*gotypes.Interface) + if !ok { continue } - word := t1.String() - // only build in interface is error - if obj := gotypes.Universe.Lookup(word); obj != nil { - results = append(results, types.NewIssue(obj.Name(), types.ErrorInterface)) + var ( + name = t1.String() + isNamed = strings.Contains(name, ".") + isEmpty = val.Empty() + ) + + // catching any + if isEmpty && name == "any" { + results = append(results, types.NewIssue(name, types.EmptyInterface)) + continue + } + + // NOTE: FIXED! + if name == "error" { + results = append(results, types.NewIssue(name, types.ErrorInterface)) continue } - // found in type params - if tp.In(word) { - results = append(results, types.NewIssue(word, types.Generic)) + if !isNamed { + + typeParams := val.String() + prefix, suffix := "interface{", "}" + if strings.HasPrefix(typeParams, prefix) { // nolint: gosimple + typeParams = typeParams[len(prefix):] + } + if strings.HasSuffix(typeParams, suffix) { + typeParams = typeParams[:len(typeParams)-1] + } + + goVersion := runtime.Version() + if strings.HasPrefix(goVersion, "go1.18") || strings.HasPrefix(goVersion, "go1.19") { + typeParams = strings.ReplaceAll(typeParams, "|", " | ") + } + + results = append(results, types.IFace{ + Name: name, + Type: types.Generic, + OfType: typeParams, + }) continue } // is it dot-imported package? // handling cases when stdlib package imported via "." dot-import if len(di) > 0 { - name := stdPkgInterface(word) - if _, ok := di[name]; ok { - results = append(results, types.NewIssue(word, types.NamedStdInterface)) + pkgName := stdPkgInterface(name) + if _, ok := di[pkgName]; ok { + results = append(results, types.NewIssue(name, types.NamedStdInterface)) continue } } - results = append(results, types.NewIssue(word, types.NamedInterface)) + results = append(results, types.NewIssue(name, types.NamedInterface)) // ------- standard library and 3rd party interfaces case *ast.SelectorExpr: diff --git a/vendor/github.com/butuzov/ireturn/analyzer/internal/types/iface.go b/vendor/github.com/butuzov/ireturn/analyzer/internal/types/iface.go index 13f19a3e2..5e576374d 100644 --- a/vendor/github.com/butuzov/ireturn/analyzer/internal/types/iface.go +++ b/vendor/github.com/butuzov/ireturn/analyzer/internal/types/iface.go @@ -14,6 +14,7 @@ type IFace struct { Pos token.Pos // Token Position FuncName string // + OfType string } func NewIssue(name string, interfaceType IType) IFace { @@ -30,11 +31,15 @@ func (i *IFace) Enrich(f *ast.FuncDecl) { } func (i IFace) String() string { - if i.Type == Generic { - return fmt.Sprintf("%s returns generic interface (%s)", i.FuncName, i.Name) + if i.Type != Generic { + return fmt.Sprintf("%s returns interface (%s)", i.FuncName, i.Name) } - return fmt.Sprintf("%s returns interface (%s)", i.FuncName, i.Name) + if i.OfType != "" { + return fmt.Sprintf("%s returns generic interface (%s) of type param %s", i.FuncName, i.Name, i.OfType) + } + + return fmt.Sprintf("%s returns generic interface (%s)", i.FuncName, i.Name) } func (i IFace) HashString() string { diff --git a/vendor/github.com/butuzov/ireturn/analyzer/std.go b/vendor/github.com/butuzov/ireturn/analyzer/std.go index 4c6c4e420..cac464612 100644 --- a/vendor/github.com/butuzov/ireturn/analyzer/std.go +++ b/vendor/github.com/butuzov/ireturn/analyzer/std.go @@ -197,4 +197,7 @@ var std = map[string]struct{}{ "maps": {}, "slices": {}, "testing/slogtest": {}, + // added in Go v1.22 in compare to v1.21 (docker image) + "go/version": {}, + "math/rand/v2": {}, } diff --git a/vendor/github.com/butuzov/ireturn/analyzer/typeparams.go b/vendor/github.com/butuzov/ireturn/analyzer/typeparams.go deleted file mode 100644 index 14193c355..000000000 --- a/vendor/github.com/butuzov/ireturn/analyzer/typeparams.go +++ /dev/null @@ -1,38 +0,0 @@ -package analyzer - -import ( - "go/ast" -) - -type typeParams struct { - found []string -} - -func newTypeParams(fl *ast.FieldList) typeParams { - tp := typeParams{} - - if fl == nil { - return tp - } - - for _, el := range fl.List { - if el == nil { - continue - } - - for _, name := range el.Names { - tp.found = append(tp.found, name.Name) - } - } - - return tp -} - -func (tp typeParams) In(t string) bool { - for _, i := range tp.found { - if i == t { - return true - } - } - return false -} diff --git a/vendor/github.com/catenacyber/perfsprint/analyzer/analyzer.go b/vendor/github.com/catenacyber/perfsprint/analyzer/analyzer.go index 69087802a..ad312ca69 100644 --- a/vendor/github.com/catenacyber/perfsprint/analyzer/analyzer.go +++ b/vendor/github.com/catenacyber/perfsprint/analyzer/analyzer.go @@ -6,7 +6,9 @@ import ( "go/format" "go/token" "go/types" + "sort" "strconv" + "strings" "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/ast/inspector" @@ -14,24 +16,66 @@ import ( "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}, +type perfSprint struct { + intConv bool + errError bool + errorf bool + sprintf1 bool + fiximports bool } -func run(pass *analysis.Pass) (interface{}, error) { - var fmtSprintObj, fmtSprintfObj types.Object +func newPerfSprint() *perfSprint { + return &perfSprint{ + intConv: true, + errError: false, + errorf: true, + sprintf1: true, + fiximports: true, + } +} + +func New() *analysis.Analyzer { + n := newPerfSprint() + r := &analysis.Analyzer{ + Name: "perfsprint", + Doc: "Checks that fmt.Sprintf can be replaced with a faster alternative.", + Run: n.run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + } + r.Flags.BoolVar(&n.intConv, "int-conversion", true, "optimizes even if it requires an int or uint type cast") + r.Flags.BoolVar(&n.errError, "err-error", false, "optimizes into err.Error() even if it is only equivalent for non-nil errors") + r.Flags.BoolVar(&n.errorf, "errorf", true, "optimizes fmt.Errorf") + r.Flags.BoolVar(&n.sprintf1, "sprintf1", true, "optimizes fmt.Sprintf with only one argument") + r.Flags.BoolVar(&n.fiximports, "fiximports", true, "fix needed imports from other fixes") + return r +} + +// true if verb is a format string that could be replaced with concatenation. +func isConcatable(verb string) bool { + hasPrefix := + (strings.HasPrefix(verb, "%s") && !strings.Contains(verb, "%[1]s")) || + (strings.HasPrefix(verb, "%[1]s") && !strings.Contains(verb, "%s")) + hasSuffix := + (strings.HasSuffix(verb, "%s") && !strings.Contains(verb, "%[1]s")) || + (strings.HasSuffix(verb, "%[1]s") && !strings.Contains(verb, "%s")) + + return (hasPrefix || hasSuffix) && !(hasPrefix && hasSuffix) +} + +func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { + var fmtSprintObj, fmtSprintfObj, fmtErrorfObj types.Object for _, pkg := range pass.Pkg.Imports() { if pkg.Path() == "fmt" { fmtSprintObj = pkg.Scope().Lookup("Sprint") fmtSprintfObj = pkg.Scope().Lookup("Sprintf") + fmtErrorfObj = pkg.Scope().Lookup("Errorf") } } - if fmtSprintfObj == nil { + if fmtSprintfObj == nil && fmtSprintObj == nil && fmtErrorfObj == nil { return nil, nil } + removedFmtUsages := make(map[string]int) + neededPackages := make(map[string]map[string]bool) insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) nodeFilter := []ast.Node{ @@ -52,11 +96,29 @@ func run(pass *analysis.Pass) (interface{}, error) { err error ) switch { + case calledObj == fmtErrorfObj && len(call.Args) == 1: + if n.errorf { + fn = "fmt.Errorf" + verb = "%s" + value = call.Args[0] + } else { + return + } + case calledObj == fmtSprintObj && len(call.Args) == 1: fn = "fmt.Sprint" verb = "%v" value = call.Args[0] + case calledObj == fmtSprintfObj && len(call.Args) == 1: + if n.sprintf1 { + fn = "fmt.Sprintf" + verb = "%s" + value = call.Args[0] + } else { + return + } + case calledObj == fmtSprintfObj && len(call.Args) == 2: verbLit, ok := call.Args[0].(*ast.BasicLit) if !ok { @@ -67,6 +129,10 @@ func run(pass *analysis.Pass) (interface{}, error) { // Probably unreachable. return } + // one single explicit arg is simplified + if strings.HasPrefix(verb, "%[1]") { + verb = "%" + verb[4:] + } fn = "fmt.Sprintf" value = call.Args[1] @@ -77,6 +143,9 @@ func run(pass *analysis.Pass) (interface{}, error) { switch verb { default: + if fn == "fmt.Sprintf" && isConcatable(verb) { + break + } return case "%d", "%v", "%x", "%t", "%s": } @@ -88,24 +157,52 @@ func run(pass *analysis.Pass) (interface{}, error) { 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)), - }}, + fname := pass.Fset.File(call.Pos()).Name() + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + removedFmtUsages[fname]++ + if fn == "fmt.Errorf" { + neededPackages[fname]["errors"] = true + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: fn + " can be replaced with errors.New", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Use errors.New", + TextEdits: []analysis.TextEdit{{ + Pos: call.Pos(), + End: value.Pos(), + NewText: []byte("errors.New("), + }}, + }, }, - }, + } + } else { + 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"): + case types.Implements(valueType, errIface) && oneOf(verb, "%v", "%s") && n.errError: + // known false positive if this error is nil + // fmt.Sprint(nil) does not panic like nil.Error() does errMethodCall := formatNode(pass.Fset, value) + ".Error()" + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -123,6 +220,13 @@ func run(pass *analysis.Pass) (interface{}, error) { } case isBasicType(valueType, types.Bool) && oneOf(verb, "%v", "%t"): + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -145,6 +249,13 @@ func run(pass *analysis.Pass) (interface{}, error) { return } + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["encoding/hex"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -168,6 +279,13 @@ func run(pass *analysis.Pass) (interface{}, error) { }, } case isSlice && isBasicType(s.Elem(), types.Uint8) && oneOf(verb, "%x"): + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["encoding/hex"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -184,7 +302,14 @@ func run(pass *analysis.Pass) (interface{}, error) { }, } - case isBasicType(valueType, types.Int8, types.Int16, types.Int32) && oneOf(verb, "%v", "%d"): + case isBasicType(valueType, types.Int8, types.Int16, types.Int32) && oneOf(verb, "%v", "%d") && n.intConv: + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -208,6 +333,13 @@ func run(pass *analysis.Pass) (interface{}, error) { }, } case isBasicType(valueType, types.Int) && oneOf(verb, "%v", "%d"): + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -224,6 +356,13 @@ func run(pass *analysis.Pass) (interface{}, error) { }, } case isBasicType(valueType, types.Int64) && oneOf(verb, "%v", "%d"): + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -247,7 +386,18 @@ func run(pass *analysis.Pass) (interface{}, error) { }, } - case isBasicType(valueType, types.Uint8, types.Uint16, types.Uint32, types.Uint) && oneOf(verb, "%v", "%d"): + case isBasicType(valueType, types.Uint8, types.Uint16, types.Uint32, types.Uint) && oneOf(verb, "%v", "%d", "%x") && n.intConv: + base := []byte("), 10") + if verb == "%x" { + base = []byte("), 16") + } + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -264,13 +414,24 @@ func run(pass *analysis.Pass) (interface{}, error) { { Pos: value.End(), End: value.End(), - NewText: []byte("), 10"), + NewText: base, }, }, }, }, } - case isBasicType(valueType, types.Uint64) && oneOf(verb, "%v", "%d"): + case isBasicType(valueType, types.Uint64) && oneOf(verb, "%v", "%d", "%x"): + base := []byte(", 10") + if verb == "%x" { + base = []byte(", 16") + } + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -287,20 +448,123 @@ func run(pass *analysis.Pass) (interface{}, error) { { Pos: value.End(), End: value.End(), - NewText: []byte(", 10"), + NewText: base, }, }, }, }, } + case isBasicType(valueType, types.String) && fn == "fmt.Sprintf" && isConcatable(verb): + var fix string + if strings.HasSuffix(verb, "%s") { + fix = strconv.Quote(verb[:len(verb)-2]) + "+" + formatNode(pass.Fset, value) + } else if strings.HasSuffix(verb, "%[1]s") { + fix = strconv.Quote(verb[:len(verb)-5]) + "+" + formatNode(pass.Fset, value) + } else if strings.HasPrefix(verb, "%s") { + fix = formatNode(pass.Fset, value) + "+" + strconv.Quote(verb[2:]) + } else { + fix = formatNode(pass.Fset, value) + "+" + strconv.Quote(verb[5:]) + } + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: fn + " can be replaced with string addition", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Use string addition", + TextEdits: []analysis.TextEdit{{ + Pos: call.Pos(), + End: call.End(), + NewText: []byte(fix), + }}, + }, + }, + } } if d != nil { - // Need to run goimports to fix using of fmt, strconv or encoding/hex afterwards. pass.Report(*d) } }) + if len(removedFmtUsages) > 0 && n.fiximports { + for _, pkg := range pass.Pkg.Imports() { + if pkg.Path() == "fmt" { + insp = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter = []ast.Node{ + (*ast.SelectorExpr)(nil), + } + insp.Preorder(nodeFilter, func(node ast.Node) { + selec := node.(*ast.SelectorExpr) + selecok, ok := selec.X.(*ast.Ident) + if ok { + pkgname, ok := pass.TypesInfo.ObjectOf(selecok).(*types.PkgName) + if ok && pkgname.Name() == pkg.Name() { + fname := pass.Fset.File(pkgname.Pos()).Name() + removedFmtUsages[fname]-- + } + } + }) + } else if pkg.Path() == "errors" || pkg.Path() == "strconv" || pkg.Path() == "encoding/hex" { + insp = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter = []ast.Node{ + (*ast.ImportSpec)(nil), + } + insp.Preorder(nodeFilter, func(node ast.Node) { + gd := node.(*ast.ImportSpec) + if gd.Path.Value == strconv.Quote(pkg.Path()) { + fname := pass.Fset.File(gd.Pos()).Name() + _, ok := neededPackages[fname] + if ok { + delete(neededPackages[fname], pkg.Path()) + } + } + }) + } + } + insp = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter = []ast.Node{ + (*ast.ImportSpec)(nil), + } + insp.Preorder(nodeFilter, func(node ast.Node) { + gd := node.(*ast.ImportSpec) + if gd.Path.Value == `"fmt"` { + fix := "" + fname := pass.Fset.File(gd.Pos()).Name() + if removedFmtUsages[fname] < 0 { + fix += `"fmt"` + if len(neededPackages[fname]) == 0 { + return + } + } + keys := make([]string, 0, len(neededPackages[fname])) + for k := range neededPackages[fname] { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + fix = fix + "\n\t\"" + k + `"` + } + pass.Report(analysis.Diagnostic{ + Pos: gd.Pos(), + End: gd.End(), + Message: "Fix imports", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Fix imports", + TextEdits: []analysis.TextEdit{{ + Pos: gd.Pos(), + End: gd.End(), + NewText: []byte(fix), + }}, + }, + }}) + } + }) + } + return nil, nil } diff --git a/vendor/github.com/ccojocar/zxcvbn-go/match/match.go b/vendor/github.com/ccojocar/zxcvbn-go/match/match.go index 998dde111..da3e894ec 100644 --- a/vendor/github.com/ccojocar/zxcvbn-go/match/match.go +++ b/vendor/github.com/ccojocar/zxcvbn-go/match/match.go @@ -16,9 +16,8 @@ func (s Matches) Less(i, j int) bool { return true } else if s[i].I == s[j].I { return s[i].J < s[j].J - } else { - return false } + return false } // Match represents different matches diff --git a/vendor/github.com/ccojocar/zxcvbn-go/renovate.json b/vendor/github.com/ccojocar/zxcvbn-go/renovate.json new file mode 100644 index 000000000..58ee1e0ea --- /dev/null +++ b/vendor/github.com/ccojocar/zxcvbn-go/renovate.json @@ -0,0 +1,25 @@ +{ + "dependencyDashboard": true, + "dependencyDashboardTitle" : "Renovate(bot) : dependency dashboard", + "vulnerabilityAlerts": { + "enabled": true + }, + "extends": [ + ":preserveSemverRanges", + "group:all", + "schedule:weekly" + ], + "lockFileMaintenance": { + "commitMessageAction": "Update", + "enabled": true, + "extends": [ + "group:all", + "schedule:weekly" + ] + }, + "postUpdateOptions": [ + "gomodTidy", + "gomodUpdateImportPaths" + ], + "separateMajorMinor": false +} diff --git a/vendor/github.com/ccojocar/zxcvbn-go/scoring/scoring.go b/vendor/github.com/ccojocar/zxcvbn-go/scoring/scoring.go index dbe331884..f25606a8d 100644 --- a/vendor/github.com/ccojocar/zxcvbn-go/scoring/scoring.go +++ b/vendor/github.com/ccojocar/zxcvbn-go/scoring/scoring.go @@ -161,9 +161,8 @@ func displayTime(seconds float64) string { return fmt.Sprintf(formater, (1 + math.Ceil(seconds/month)), "months") } else if seconds < century { return fmt.Sprintf(formater, (1 + math.Ceil(seconds/century)), "years") - } else { - return "centuries" } + return "centuries" } func crackTimeToScore(seconds float64) int { diff --git a/vendor/github.com/daixiang0/gci/pkg/config/config.go b/vendor/github.com/daixiang0/gci/pkg/config/config.go index 98513c056..51f6ccf3b 100644 --- a/vendor/github.com/daixiang0/gci/pkg/config/config.go +++ b/vendor/github.com/daixiang0/gci/pkg/config/config.go @@ -15,6 +15,7 @@ var defaultOrder = map[string]int{ section.CustomType: 2, section.BlankType: 3, section.DotType: 4, + section.AliasType: 5, } type BoolConfig struct { diff --git a/vendor/github.com/daixiang0/gci/pkg/gci/testdata.go b/vendor/github.com/daixiang0/gci/pkg/gci/testdata.go index a48ce356c..77c06dc63 100644 --- a/vendor/github.com/daixiang0/gci/pkg/gci/testdata.go +++ b/vendor/github.com/daixiang0/gci/pkg/gci/testdata.go @@ -1242,4 +1242,37 @@ import ( ) `, }, + { + "alias", + + `sections: + - Standard + - Default + - Alias +`, + `package main + +import ( + testing "github.com/daixiang0/test" + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/gci" + "github.com/daixiang0/gci/subtest" +) +`, + `package main + +import ( + "fmt" + + "github.com/daixiang0/gci" + "github.com/daixiang0/gci/subtest" + + testing "github.com/daixiang0/test" + g "github.com/golang" +) +`, + }, } diff --git a/vendor/github.com/daixiang0/gci/pkg/section/alias.go b/vendor/github.com/daixiang0/gci/pkg/section/alias.go new file mode 100644 index 000000000..423e96acf --- /dev/null +++ b/vendor/github.com/daixiang0/gci/pkg/section/alias.go @@ -0,0 +1,25 @@ +package section + +import ( + "github.com/daixiang0/gci/pkg/parse" + "github.com/daixiang0/gci/pkg/specificity" +) + +type Alias struct{} + +const AliasType = "alias" + +func (b Alias) MatchSpecificity(spec *parse.GciImports) specificity.MatchSpecificity { + if spec.Name != "." && spec.Name != "_" && spec.Name != "" { + return specificity.NameMatch{} + } + return specificity.MisMatch{} +} + +func (b Alias) String() string { + return AliasType +} + +func (b Alias) Type() string { + return AliasType +} diff --git a/vendor/github.com/daixiang0/gci/pkg/section/parser.go b/vendor/github.com/daixiang0/gci/pkg/section/parser.go index 9834dcd13..38435f540 100644 --- a/vendor/github.com/daixiang0/gci/pkg/section/parser.go +++ b/vendor/github.com/daixiang0/gci/pkg/section/parser.go @@ -33,6 +33,8 @@ func Parse(data []string) (SectionList, error) { list = append(list, Dot{}) } else if s == "blank" { list = append(list, Blank{}) + } else if s == "alias" { + list = append(list, Alias{}) } else { errString += fmt.Sprintf(" %s", s) } diff --git a/vendor/github.com/ettle/strcase/.golangci.yml b/vendor/github.com/ettle/strcase/.golangci.yml index 4d31fcc5b..b7ce85d42 100644 --- a/vendor/github.com/ettle/strcase/.golangci.yml +++ b/vendor/github.com/ettle/strcase/.golangci.yml @@ -14,8 +14,6 @@ linters-settings: - ifElseChain - whyNoLint - wrapperFunc - golint: - min-confidence: 0.5 govet: check-shadowing: true lll: @@ -37,7 +35,6 @@ linters: disable-all: true enable: - bodyclose - - deadcode - depguard - dogsled - dupl @@ -47,26 +44,23 @@ linters: - gocyclo - gofmt - goimports - - golint - goprintffuncname - gosec - gosimple - govet - ineffassign - - interfacer - lll - misspell - nakedret - nolintlint + - revive - rowserrcheck - staticcheck - - structcheck - stylecheck - typecheck - unconvert - unparam - unused - - varcheck - whitespace # don't enable: diff --git a/vendor/github.com/ettle/strcase/.readme.tmpl b/vendor/github.com/ettle/strcase/.readme.tmpl index 135765c40..4d7a894f0 100644 --- a/vendor/github.com/ettle/strcase/.readme.tmpl +++ b/vendor/github.com/ettle/strcase/.readme.tmpl @@ -16,10 +16,10 @@ Convert strings to `snake_case`, `camelCase`, `PascalCase`, `kebab-case` and mor ## <a name="pkg-index">Index</a>{{if .Consts}} * [Constants](#pkg-constants){{end}}{{if .Vars}} * [Variables](#pkg-variables){{end}}{{- range .Funcs -}}{{$name_html := html .Name}} -* [{{node_html $ .Decl false | sanitize}}](#{{$name_html}}){{- end}}{{- range .Types}}{{$tname_html := html .Name}} -* [type {{$tname_html}}](#{{$tname_html}}){{- range .Funcs}}{{$name_html := html .Name}} - * [{{node_html $ .Decl false | sanitize}}](#{{$name_html}}){{- end}}{{- range .Methods}}{{$name_html := html .Name}} - * [{{node_html $ .Decl false | sanitize}}](#{{$tname_html}}.{{$name_html}}){{- end}}{{- end}}{{- if $.Notes}}{{- range $marker, $item := $.Notes}} +* [{{node_html $ .Decl false | sanitize}}](#func-{{$name_html}}){{- end}}{{- range .Types}}{{$tname_html := html .Name}} +* [type {{$tname_html}}](#type-{{$tname_html}}){{- range .Funcs}}{{$name_html := html .Name}} + * [{{node_html $ .Decl false | sanitize}}](#func-{{$name_html}}){{- end}}{{- range .Methods}}{{$name_html := html .Name}} + * [{{node_html $ .Decl false | sanitize}}](#type-{{$tname_html}}.{{$name_html}}){{- end}}{{- end}}{{- if $.Notes}}{{- range $marker, $item := $.Notes}} * [{{noteTitle $marker | html}}s](#pkg-note-{{$marker}}){{end}}{{end}} {{if $.Examples}} #### <a name="pkg-examples">Examples</a>{{- range $.Examples}} diff --git a/vendor/github.com/ettle/strcase/Makefile b/vendor/github.com/ettle/strcase/Makefile index 462f8b473..ac98b4aa5 100644 --- a/vendor/github.com/ettle/strcase/Makefile +++ b/vendor/github.com/ettle/strcase/Makefile @@ -1,16 +1,19 @@ .PHONY: benchmark docs lint test docs: - which godoc2ghmd || ( go get github.com/DevotedHealth/godoc2ghmd && go mod tidy ) + which godoc2ghmd || go get github.com/DevotedHealth/godoc2ghmd godoc2ghmd -template .readme.tmpl github.com/ettle/strcase > README.md + go mod tidy test: go test -cover ./... lint: - which golangci-lint || ( go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.27.0 && go mod tidy ) + which golangci-lint || go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.50.1 golangci-lint run golangci-lint run benchmark/*.go + go mod tidy benchmark: - cd benchmark && go test -bench=. -test.benchmem && go mod tidy + cd benchmark && go test -bench=. -test.benchmem + go mod tidy diff --git a/vendor/github.com/ettle/strcase/README.md b/vendor/github.com/ettle/strcase/README.md index ee165e3e5..a984da80d 100644 --- a/vendor/github.com/ettle/strcase/README.md +++ b/vendor/github.com/ettle/strcase/README.md @@ -32,21 +32,24 @@ Example usage strcase.ToSnake("FOOBar") // foo_bar // Support Go initialisms - strcase.ToGoCamel("http_response") // HTTPResponse + strcase.ToGoPascal("http_response") // HTTPResponse // Specify case and delimiter strcase.ToCase("HelloWorld", strcase.UpperCase, '.') // HELLO.WORLD -### Why this package +## Why this package + String strcase is pretty straight forward and there are a number of methods to do it. This package is fully featured, more customizable, better tested, and -faster* than other packages and what you would probably whip up yourself. +faster than other packages and what you would probably whip up yourself. ### Unicode support + We work for with unicode strings and pay very little performance penalty for it as we optimized for the common use case of ASCII only strings. ### Customization + You can create a custom caser that changes the behavior to what you want. This customization also reduces the pressure for us to change the default behavior which means that things are more stable for everyone involved. The goal is to @@ -71,19 +74,22 @@ make the common path easy and fast, while making the uncommon path possible. assert.Equal(t, "http_200", c.ToSnake("http200")) ### Initialism support + By default, we use the golint intialisms list. You can customize and override the initialisms if you wish to add additional ones, such as "SSL" or "CMS" or domain specific ones to your industry. - ToGoCamel("http_response") // HTTPResponse + ToGoPascal("http_response") // HTTPResponse ToGoSnake("http_response") // HTTP_response ### Test coverage + We have a wide ranging test suite to make sure that we understand our behavior. Test coverage isn't everything, but we aim for 100% coverage. ### Fast + Optimized to reduce memory allocations with Builder. Benchmarked and optimized around common cases. @@ -96,56 +102,57 @@ Hopefully I was fair to each library and happy to rerun benchmarks differently or reword my commentary based on suggestions or updates. - // This package - // Go intialisms and custom casers are slower - BenchmarkToTitle-4 992491 1559 ns/op 32 B/op 1 allocs/op - BenchmarkToSnake-4 1000000 1475 ns/op 32 B/op 1 allocs/op - BenchmarkToSNAKE-4 1000000 1609 ns/op 32 B/op 1 allocs/op - BenchmarkToGoSnake-4 275010 3697 ns/op 44 B/op 4 allocs/op - BenchmarkToCustomCaser-4 342704 4191 ns/op 56 B/op 4 allocs/op + // This package - faster then almost all libraries + // Initialisms are more complicated and slightly slower, but still fast + BenchmarkToTitle-96 9617142 125.7 ns/op 16 B/op 1 allocs/op + BenchmarkToSnake-96 10659919 120.7 ns/op 16 B/op 1 allocs/op + BenchmarkToSNAKE-96 9018282 126.4 ns/op 16 B/op 1 allocs/op + BenchmarkToGoSnake-96 4903687 254.5 ns/op 26 B/op 4 allocs/op + BenchmarkToCustomCaser-96 4434489 265.0 ns/op 28 B/op 4 allocs/op // Segment has very fast snake case and camel case libraries // No features or customization, but very very fast - BenchmarkSegment-4 1303809 938 ns/op 16 B/op 1 allocs/op + BenchmarkSegment-96 33625734 35.54 ns/op 16 B/op 1 allocs/op - // Stdlib strings.Title for comparison, even though it only splits on spaces - BenchmarkToTitleStrings-4 1213467 1164 ns/op 16 B/op 1 allocs/op + // Iancoleman has gotten some performance improvements, but remains + // without unicode support and lacks fine-grained customization + BenchmarkToSnakeIan-96 13141522 92.99 ns/op 16 B/op 1 allocs/op + + // Stdlib strings.Title is deprecated; using golang.org/x.text + BenchmarkGolangOrgXTextCases-96 4665676 262.5 ns/op 272 B/op 2 allocs/op // Other libraries or code snippets // - Most are slower, by up to an order of magnitude - // - None support initialisms or customization + // - No support for initialisms or customization // - Some generate only camelCase or snake_case // - Many lack unicode support - BenchmarkToSnakeStoewer-4 973200 2075 ns/op 64 B/op 2 allocs/op + BenchmarkToSnakeStoewer-96 8095468 148.9 ns/op 64 B/op 2 allocs/op // Copying small rune arrays is slow - BenchmarkToSnakeSiongui-4 264315 4229 ns/op 48 B/op 10 allocs/op - BenchmarkGoValidator-4 206811 5152 ns/op 184 B/op 9 allocs/op + BenchmarkToSnakeSiongui-96 2912593 401.7 ns/op 112 B/op 19 allocs/op + BenchmarkGoValidator-96 3493800 342.6 ns/op 184 B/op 9 allocs/op // String alloction is slow - BenchmarkToSnakeFatih-4 82675 12280 ns/op 392 B/op 26 allocs/op - BenchmarkToSnakeIanColeman-4 83276 13903 ns/op 145 B/op 13 allocs/op + BenchmarkToSnakeFatih-96 1282648 945.1 ns/op 616 B/op 26 allocs/op // Regexp is slow - BenchmarkToSnakeGolangPrograms-4 74448 18586 ns/op 176 B/op 11 allocs/op + BenchmarkToSnakeGolangPrograms-96 778674 1495 ns/op 227 B/op 11 allocs/op // These results aren't a surprise - my initial version of this library was // painfully slow. I think most of us, without spending some time with // profilers and benchmarks, would write also something on the slower side. -### Why not this package +### Zero dependencies + +That's right - zero. We only import the Go standard library. No hassles with +dependencies, licensing, security alerts. + +## Why not this package + If every nanosecond matters and this is used in a tight loop, use segment.io's libraries (<a href="https://github.com/segmentio/go-snakecase">https://github.com/segmentio/go-snakecase</a> and <a href="https://github.com/segmentio/go-camelcase">https://github.com/segmentio/go-camelcase</a>). They lack features, but make up for -it by being blazing fast. Alternatively, if you need your code to work slightly -differently, fork them and tailor it for your use case. - -If you don't like having external imports, I get it. This package only imports -packages for testing, otherwise it only uses the standard library. If that's -not enough, you can use this repo as the foundation for your own. MIT Licensed. +it by being blazing fast. -This package is still relatively new and while I've used it for a while -personally, it doesn't have the miles that other packages do. I've tested this -code agains't their test cases to make sure that there aren't any surprises. +## Migrating from other packages -### Migrating from other packages If you are migrating from from another package, you may find slight differences in output. To reduce the delta, you may find it helpful to use the following custom casers to mimic the behavior of the other package. @@ -161,32 +168,32 @@ custom casers to mimic the behavior of the other package. ## <a name="pkg-index">Index</a> -* [func ToCamel(s string) string](#ToCamel) -* [func ToCase(s string, wordCase WordCase, delimiter rune) string](#ToCase) -* [func ToGoCamel(s string) string](#ToGoCamel) -* [func ToGoCase(s string, wordCase WordCase, delimiter rune) string](#ToGoCase) -* [func ToGoKebab(s string) string](#ToGoKebab) -* [func ToGoPascal(s string) string](#ToGoPascal) -* [func ToGoSnake(s string) string](#ToGoSnake) -* [func ToKEBAB(s string) string](#ToKEBAB) -* [func ToKebab(s string) string](#ToKebab) -* [func ToPascal(s string) string](#ToPascal) -* [func ToSNAKE(s string) string](#ToSNAKE) -* [func ToSnake(s string) string](#ToSnake) -* [type Caser](#Caser) - * [func NewCaser(goInitialisms bool, initialismOverrides map[string]bool, splitFn SplitFn) *Caser](#NewCaser) - * [func (c *Caser) ToCamel(s string) string](#Caser.ToCamel) - * [func (c *Caser) ToCase(s string, wordCase WordCase, delimiter rune) string](#Caser.ToCase) - * [func (c *Caser) ToKEBAB(s string) string](#Caser.ToKEBAB) - * [func (c *Caser) ToKebab(s string) string](#Caser.ToKebab) - * [func (c *Caser) ToPascal(s string) string](#Caser.ToPascal) - * [func (c *Caser) ToSNAKE(s string) string](#Caser.ToSNAKE) - * [func (c *Caser) ToSnake(s string) string](#Caser.ToSnake) -* [type SplitAction](#SplitAction) -* [type SplitFn](#SplitFn) - * [func NewSplitFn(delimiters []rune, splitOptions ...SplitOption) SplitFn](#NewSplitFn) -* [type SplitOption](#SplitOption) -* [type WordCase](#WordCase) +* [func ToCamel(s string) string](#func-ToCamel) +* [func ToCase(s string, wordCase WordCase, delimiter rune) string](#func-ToCase) +* [func ToGoCamel(s string) string](#func-ToGoCamel) +* [func ToGoCase(s string, wordCase WordCase, delimiter rune) string](#func-ToGoCase) +* [func ToGoKebab(s string) string](#func-ToGoKebab) +* [func ToGoPascal(s string) string](#func-ToGoPascal) +* [func ToGoSnake(s string) string](#func-ToGoSnake) +* [func ToKEBAB(s string) string](#func-ToKEBAB) +* [func ToKebab(s string) string](#func-ToKebab) +* [func ToPascal(s string) string](#func-ToPascal) +* [func ToSNAKE(s string) string](#func-ToSNAKE) +* [func ToSnake(s string) string](#func-ToSnake) +* [type Caser](#type-Caser) + * [func NewCaser(goInitialisms bool, initialismOverrides map[string]bool, splitFn SplitFn) *Caser](#func-NewCaser) + * [func (c *Caser) ToCamel(s string) string](#type-Caser.ToCamel) + * [func (c *Caser) ToCase(s string, wordCase WordCase, delimiter rune) string](#type-Caser.ToCase) + * [func (c *Caser) ToKEBAB(s string) string](#type-Caser.ToKEBAB) + * [func (c *Caser) ToKebab(s string) string](#type-Caser.ToKebab) + * [func (c *Caser) ToPascal(s string) string](#type-Caser.ToPascal) + * [func (c *Caser) ToSNAKE(s string) string](#type-Caser.ToSNAKE) + * [func (c *Caser) ToSnake(s string) string](#type-Caser.ToSnake) +* [type SplitAction](#type-SplitAction) +* [type SplitFn](#type-SplitFn) + * [func NewSplitFn(delimiters []rune, splitOptions ...SplitOption) SplitFn](#func-NewSplitFn) +* [type SplitOption](#type-SplitOption) +* [type WordCase](#type-WordCase) @@ -201,7 +208,7 @@ Also known as lowerCamelCase or mixedCase. -## <a name="ToCase">func</a> [ToCase](./strcase.go#L70) +## <a name="ToCase">func</a> [ToCase](./strcase.go#L72) ``` go func ToCase(s string, wordCase WordCase, delimiter rune) string ``` @@ -209,18 +216,20 @@ ToCase returns words in given case and delimiter. -## <a name="ToGoCamel">func</a> [ToGoCamel](./strcase.go#L65) +## <a name="ToGoCamel">func</a> [ToGoCamel](./strcase.go#L67) ``` go func ToGoCamel(s string) string ``` ToGoCamel returns words in camelCase (capitalized words concatenated together, with first word lower case). Also known as lowerCamelCase or mixedCase. -Respects Go's common initialisms (e.g. httpResponse -> HTTPResponse). +Respects Go's common initialisms, but first word remains lowercased which is +important for code generator use cases (e.g. toJson -> toJSON, httpResponse +-> httpResponse). -## <a name="ToGoCase">func</a> [ToGoCase](./strcase.go#L77) +## <a name="ToGoCase">func</a> [ToGoCase](./strcase.go#L79) ``` go func ToGoCase(s string, wordCase WordCase, delimiter rune) string ``` @@ -415,7 +424,7 @@ ToSnake returns words in snake_case (lower case words with underscores). -## <a name="SplitAction">type</a> [SplitAction](./split.go#L110) +## <a name="SplitAction">type</a> [SplitAction](./split.go#L111) ``` go type SplitAction int ``` @@ -457,7 +466,7 @@ SplitFn defines how to split a string into words -### <a name="NewSplitFn">func</a> [NewSplitFn](./split.go#L14-L17) +### <a name="NewSplitFn">func</a> [NewSplitFn](./split.go#L15-L18) ``` go func NewSplitFn( delimiters []rune, @@ -469,13 +478,12 @@ NewSplitFn returns a SplitFn based on the options provided. NewSplitFn covers the majority of common options that other strcase libraries provide and should allow you to simply create a custom caser. For more complicated use cases, feel free to write your own SplitFn -nolint:gocyclo -## <a name="SplitOption">type</a> [SplitOption](./split.go#L93) +## <a name="SplitOption">type</a> [SplitOption](./split.go#L94) ``` go type SplitOption int ``` @@ -524,6 +532,9 @@ const ( // TitleCase - Only first letter upper cased (Example) TitleCase // CamelCase - TitleCase except lower case first word (exampleText) + // Notably, even if the first word is an initialism, it will be lower + // cased. This is important for code generators where capital letters + // mean exported functions. i.e. jsonString(), not JSONString() CamelCase ) ``` diff --git a/vendor/github.com/ettle/strcase/assert.go b/vendor/github.com/ettle/strcase/assert.go new file mode 100644 index 000000000..09344e40f --- /dev/null +++ b/vendor/github.com/ettle/strcase/assert.go @@ -0,0 +1,24 @@ +package strcase + +// We use a lightweight replacement for testify/assert to reduce dependencies + +// testingT interface allows us to test our assert functions +type testingT interface { + Logf(format string, args ...interface{}) + Fail() +} + +// assertTrue will fail if the value is not true +func assertTrue(t testingT, value bool) { + if !value { + t.Fail() + } +} + +// assertEqual will fail if the two strings are not equal +func assertEqual(t testingT, expected, actual string) { + if expected != actual { + t.Logf("Expected: %s Actual: %s", expected, actual) + t.Fail() + } +} diff --git a/vendor/github.com/ettle/strcase/caser.go b/vendor/github.com/ettle/strcase/caser.go index 891a67189..2e7eb955b 100644 --- a/vendor/github.com/ettle/strcase/caser.go +++ b/vendor/github.com/ettle/strcase/caser.go @@ -10,17 +10,17 @@ type Caser struct { // // A Caser should be created when you want fine grained control over how the words are split. // -// Notes on function arguments +// Notes on function arguments // -// goInitialisms: Whether to use Golint's intialisms +// goInitialisms: Whether to use Golint's intialisms // -// initialismOverrides: A mapping of extra initialisms -// Keys must be in ALL CAPS. Merged with Golint's if goInitialisms is set. -// Setting a key to false will override Golint's. +// initialismOverrides: A mapping of extra initialisms +// Keys must be in ALL CAPS. Merged with Golint's if goInitialisms is set. +// Setting a key to false will override Golint's. // -// splitFn: How to separate words -// Override the default split function. Consider using NewSplitFn to -// configure one instead of writing your own. +// splitFn: How to separate words +// Override the default split function. Consider using NewSplitFn to +// configure one instead of writing your own. func NewCaser(goInitialisms bool, initialismOverrides map[string]bool, splitFn SplitFn) *Caser { c := &Caser{ initialisms: golintInitialisms, diff --git a/vendor/github.com/ettle/strcase/convert.go b/vendor/github.com/ettle/strcase/convert.go index 70fedb144..cb901d079 100644 --- a/vendor/github.com/ettle/strcase/convert.go +++ b/vendor/github.com/ettle/strcase/convert.go @@ -29,6 +29,7 @@ const ( // Case 2: UpperCase words, which don't need to support initialisms since everything is in upper case // convertWithoutInitialims only works for to UpperCase and LowerCase +// //nolint:gocyclo func convertWithoutInitialisms(input string, delimiter rune, wordCase WordCase) string { input = strings.TrimSpace(input) @@ -38,7 +39,7 @@ func convertWithoutInitialisms(input string, delimiter rune, wordCase WordCase) } var b strings.Builder - b.Grow(len(input) * 2) // In case we need to write delimiters where they weren't before + b.Grow(len(input) + 4) // In case we need to write delimiters where they weren't before var prev, curr rune next := runes[0] // 0 length will have already returned so safe to index @@ -90,13 +91,14 @@ func convertWithoutInitialisms(input string, delimiter rune, wordCase WordCase) // Must be original case b.WriteRune(curr) } - inWord = inWord || true + inWord = true } return b.String() } // convertWithGoInitialisms changes a input string to a certain case with a // delimiter, respecting go initialisms but not skip runes +// //nolint:gocyclo func convertWithGoInitialisms(input string, delimiter rune, wordCase WordCase) string { input = strings.TrimSpace(input) @@ -106,7 +108,7 @@ func convertWithGoInitialisms(input string, delimiter rune, wordCase WordCase) s } var b strings.Builder - b.Grow(len(input) * 2) // In case we need to write delimiters where they weren't before + b.Grow(len(input) + 4) // In case we need to write delimiters where they weren't before firstWord := true @@ -122,10 +124,15 @@ func convertWithGoInitialisms(input string, delimiter rune, wordCase WordCase) s // Don't bother with initialisms if the word is longer than 5 // A quick proxy to avoid the extra memory allocations if end-start <= 5 { - key := strings.ToUpper(string(runes[start:end])) - if golintInitialisms[key] { + var word strings.Builder + word.Grow(end - start) + for i := start; i < end; i++ { + word.WriteRune(toUpper(runes[i])) + } + w := word.String() + if golintInitialisms[w] { if !firstWord || wordCase != CamelCase { - b.WriteString(key) + b.WriteString(w) firstWord = false return } @@ -188,6 +195,7 @@ func convertWithGoInitialisms(input string, delimiter rune, wordCase WordCase) s // convert changes a input string to a certain case with a delimiter, // respecting arbitrary initialisms and skip characters +// //nolint:gocyclo func convert(input string, fn SplitFn, delimiter rune, wordCase WordCase, initialisms map[string]bool) string { @@ -198,7 +206,7 @@ func convert(input string, fn SplitFn, delimiter rune, wordCase WordCase, } var b strings.Builder - b.Grow(len(input) * 2) // In case we need to write delimiters where they weren't before + b.Grow(len(input) + 4) // In case we need to write delimiters where they weren't before firstWord := true var skipIndexes []int @@ -221,13 +229,14 @@ func convert(input string, fn SplitFn, delimiter rune, wordCase WordCase, // I'm open to it if there is a use case if initialisms != nil { var word strings.Builder + word.Grow(end - start) for i := start; i < end; i++ { word.WriteRune(toUpper(runes[i])) } - key := word.String() - if initialisms[key] { + w := word.String() + if initialisms[w] { if !firstWord || wordCase != CamelCase { - b.WriteString(key) + b.WriteString(w) firstWord = false return } diff --git a/vendor/github.com/ettle/strcase/doc.go b/vendor/github.com/ettle/strcase/doc.go index b898a4e45..c3bf14a8f 100644 --- a/vendor/github.com/ettle/strcase/doc.go +++ b/vendor/github.com/ettle/strcase/doc.go @@ -2,78 +2,78 @@ Package strcase is a package for converting strings into various word cases (e.g. snake_case, camelCase) - go get -u github.com/ettle/strcase + go get -u github.com/ettle/strcase Example usage - strcase.ToSnake("Hello World") // hello_world - strcase.ToSNAKE("Hello World") // HELLO_WORLD + strcase.ToSnake("Hello World") // hello_world + strcase.ToSNAKE("Hello World") // HELLO_WORLD - strcase.ToKebab("helloWorld") // hello-world - strcase.ToKEBAB("helloWorld") // HELLO-WORLD + strcase.ToKebab("helloWorld") // hello-world + strcase.ToKEBAB("helloWorld") // HELLO-WORLD - strcase.ToPascal("hello-world") // HelloWorld - strcase.ToCamel("hello-world") // helloWorld + strcase.ToPascal("hello-world") // HelloWorld + strcase.ToCamel("hello-world") // helloWorld - // Handle odd cases - strcase.ToSnake("FOOBar") // foo_bar + // Handle odd cases + strcase.ToSnake("FOOBar") // foo_bar - // Support Go initialisms - strcase.ToGoPascal("http_response") // HTTPResponse + // Support Go initialisms + strcase.ToGoPascal("http_response") // HTTPResponse - // Specify case and delimiter - strcase.ToCase("HelloWorld", strcase.UpperCase, '.') // HELLO.WORLD + // Specify case and delimiter + strcase.ToCase("HelloWorld", strcase.UpperCase, '.') // HELLO.WORLD -Why this package +## Why this package String strcase is pretty straight forward and there are a number of methods to do it. This package is fully featured, more customizable, better tested, and -faster* than other packages and what you would probably whip up yourself. +faster than other packages and what you would probably whip up yourself. -Unicode support +### Unicode support We work for with unicode strings and pay very little performance penalty for it as we optimized for the common use case of ASCII only strings. -Customization +### Customization You can create a custom caser that changes the behavior to what you want. This customization also reduces the pressure for us to change the default behavior which means that things are more stable for everyone involved. The goal is to make the common path easy and fast, while making the uncommon path possible. - c := NewCaser( - // Use Go's default initialisms e.g. ID, HTML - true, - // Override initialisms (e.g. don't initialize HTML but initialize SSL - map[string]bool{"SSL": true, "HTML": false}, - // Write your own custom SplitFn - // - NewSplitFn( - []rune{'*', '.', ','}, - SplitCase, - SplitAcronym, - PreserveNumberFormatting, - SplitBeforeNumber, - SplitAfterNumber, - )) - assert.Equal(t, "http_200", c.ToSnake("http200")) - -Initialism support + c := NewCaser( + // Use Go's default initialisms e.g. ID, HTML + true, + // Override initialisms (e.g. don't initialize HTML but initialize SSL + map[string]bool{"SSL": true, "HTML": false}, + // Write your own custom SplitFn + // + NewSplitFn( + []rune{'*', '.', ','}, + SplitCase, + SplitAcronym, + PreserveNumberFormatting, + SplitBeforeNumber, + SplitAfterNumber, + )) + assert.Equal(t, "http_200", c.ToSnake("http200")) + +### Initialism support By default, we use the golint intialisms list. You can customize and override the initialisms if you wish to add additional ones, such as "SSL" or "CMS" or domain specific ones to your industry. - ToGoPascal("http_response") // HTTPResponse - ToGoSnake("http_response") // HTTP_response + ToGoPascal("http_response") // HTTPResponse + ToGoSnake("http_response") // HTTP_response -Test coverage +### Test coverage We have a wide ranging test suite to make sure that we understand our behavior. Test coverage isn't everything, but we aim for 100% coverage. -Fast +### Fast Optimized to reduce memory allocations with Builder. Benchmarked and optimized around common cases. @@ -86,70 +86,65 @@ common cases have a large performance impact. Hopefully I was fair to each library and happy to rerun benchmarks differently or reword my commentary based on suggestions or updates. - // This package - faster then almost all libraries - // Initialisms are more complicated and slightly slower, but still faster then other libraries that do less - BenchmarkToTitle-4 7821166 221 ns/op 32 B/op 1 allocs/op - BenchmarkToSnake-4 9378589 202 ns/op 32 B/op 1 allocs/op - BenchmarkToSNAKE-4 6174453 223 ns/op 32 B/op 1 allocs/op - BenchmarkToGoSnake-4 3114266 434 ns/op 44 B/op 4 allocs/op - BenchmarkToCustomCaser-4 2973855 448 ns/op 56 B/op 4 allocs/op - - // Segment has very fast snake case and camel case libraries - // No features or customization, but very very fast - BenchmarkSegment-4 24003495 64.9 ns/op 16 B/op 1 allocs/op - - // Stdlib strings.Title for comparison, even though it only splits on spaces - BenchmarkToTitleStrings-4 11259376 161 ns/op 16 B/op 1 allocs/op - - // Other libraries or code snippets - // - Most are slower, by up to an order of magnitude - // - None support initialisms or customization - // - Some generate only camelCase or snake_case - // - Many lack unicode support - BenchmarkToSnakeStoewer-4 7103268 297 ns/op 64 B/op 2 allocs/op - // Copying small rune arrays is slow - BenchmarkToSnakeSiongui-4 3710768 413 ns/op 48 B/op 10 allocs/op - BenchmarkGoValidator-4 2416479 1049 ns/op 184 B/op 9 allocs/op - // String alloction is slow - BenchmarkToSnakeFatih-4 1000000 2407 ns/op 624 B/op 26 allocs/op - BenchmarkToSnakeIanColeman-4 1005766 1426 ns/op 160 B/op 13 allocs/op - // Regexp is slow - BenchmarkToSnakeGolangPrograms-4 614689 2237 ns/op 225 B/op 11 allocs/op - - - - // These results aren't a surprise - my initial version of this library was - // painfully slow. I think most of us, without spending some time with - // profilers and benchmarks, would write also something on the slower side. - - -Why not this package + // This package - faster then almost all libraries + // Initialisms are more complicated and slightly slower, but still fast + BenchmarkToTitle-96 9617142 125.7 ns/op 16 B/op 1 allocs/op + BenchmarkToSnake-96 10659919 120.7 ns/op 16 B/op 1 allocs/op + BenchmarkToSNAKE-96 9018282 126.4 ns/op 16 B/op 1 allocs/op + BenchmarkToGoSnake-96 4903687 254.5 ns/op 26 B/op 4 allocs/op + BenchmarkToCustomCaser-96 4434489 265.0 ns/op 28 B/op 4 allocs/op + + // Segment has very fast snake case and camel case libraries + // No features or customization, but very very fast + BenchmarkSegment-96 33625734 35.54 ns/op 16 B/op 1 allocs/op + + // Iancoleman has gotten some performance improvements, but remains + // without unicode support and lacks fine-grained customization + BenchmarkToSnakeIan-96 13141522 92.99 ns/op 16 B/op 1 allocs/op + + // Stdlib strings.Title is deprecated; using golang.org/x.text + BenchmarkGolangOrgXTextCases-96 4665676 262.5 ns/op 272 B/op 2 allocs/op + + // Other libraries or code snippets + // - Most are slower, by up to an order of magnitude + // - No support for initialisms or customization + // - Some generate only camelCase or snake_case + // - Many lack unicode support + BenchmarkToSnakeStoewer-96 8095468 148.9 ns/op 64 B/op 2 allocs/op + // Copying small rune arrays is slow + BenchmarkToSnakeSiongui-96 2912593 401.7 ns/op 112 B/op 19 allocs/op + BenchmarkGoValidator-96 3493800 342.6 ns/op 184 B/op 9 allocs/op + // String alloction is slow + BenchmarkToSnakeFatih-96 1282648 945.1 ns/op 616 B/op 26 allocs/op + // Regexp is slow + BenchmarkToSnakeGolangPrograms-96 778674 1495 ns/op 227 B/op 11 allocs/op + + // These results aren't a surprise - my initial version of this library was + // painfully slow. I think most of us, without spending some time with + // profilers and benchmarks, would write also something on the slower side. + +### Zero dependencies + +That's right - zero. We only import the Go standard library. No hassles with +dependencies, licensing, security alerts. + +## Why not this package If every nanosecond matters and this is used in a tight loop, use segment.io's libraries (https://github.com/segmentio/go-snakecase and https://github.com/segmentio/go-camelcase). They lack features, but make up for -it by being blazing fast. Alternatively, if you need your code to work slightly -differently, fork them and tailor it for your use case. - -If you don't like having external imports, I get it. This package only imports -packages for testing, otherwise it only uses the standard library. If that's -not enough, you can use this repo as the foundation for your own. MIT Licensed. +it by being blazing fast. -This package is still relatively new and while I've used it for a while -personally, it doesn't have the miles that other packages do. I've tested this -code agains't their test cases to make sure that there aren't any surprises. - -Migrating from other packages +## Migrating from other packages If you are migrating from from another package, you may find slight differences in output. To reduce the delta, you may find it helpful to use the following custom casers to mimic the behavior of the other package. - // From https://github.com/iancoleman/strcase - var c = NewCaser(false, nil, NewSplitFn([]rune{'_', '-', '.'}, SplitCase, SplitAcronym, SplitBeforeNumber)) - - // From https://github.com/stoewer/go-strcase - var c = NewCaser(false, nil, NewSplitFn([]rune{'_', '-'}, SplitCase), SplitAcronym) + // From https://github.com/iancoleman/strcase + var c = NewCaser(false, nil, NewSplitFn([]rune{'_', '-', '.'}, SplitCase, SplitAcronym, SplitBeforeNumber)) + // From https://github.com/stoewer/go-strcase + var c = NewCaser(false, nil, NewSplitFn([]rune{'_', '-'}, SplitCase), SplitAcronym) */ package strcase diff --git a/vendor/github.com/ettle/strcase/split.go b/vendor/github.com/ettle/strcase/split.go index 84381106b..32bc29759 100644 --- a/vendor/github.com/ettle/strcase/split.go +++ b/vendor/github.com/ettle/strcase/split.go @@ -10,6 +10,7 @@ type SplitFn func(prev, curr, next rune) SplitAction // NewSplitFn covers the majority of common options that other strcase // libraries provide and should allow you to simply create a custom caser. // For more complicated use cases, feel free to write your own SplitFn +// //nolint:gocyclo func NewSplitFn( delimiters []rune, diff --git a/vendor/github.com/fatih/color/color.go b/vendor/github.com/fatih/color/color.go index 889f9e77b..c4234287d 100644 --- a/vendor/github.com/fatih/color/color.go +++ b/vendor/github.com/fatih/color/color.go @@ -65,6 +65,29 @@ const ( CrossedOut ) +const ( + ResetBold Attribute = iota + 22 + ResetItalic + ResetUnderline + ResetBlinking + _ + ResetReversed + ResetConcealed + ResetCrossedOut +) + +var mapResetAttributes map[Attribute]Attribute = map[Attribute]Attribute{ + Bold: ResetBold, + Faint: ResetBold, + Italic: ResetItalic, + Underline: ResetUnderline, + BlinkSlow: ResetBlinking, + BlinkRapid: ResetBlinking, + ReverseVideo: ResetReversed, + Concealed: ResetConcealed, + CrossedOut: ResetCrossedOut, +} + // Foreground text colors const ( FgBlack Attribute = iota + 30 @@ -246,10 +269,7 @@ func (c *Color) Printf(format string, a ...interface{}) (n int, err error) { // On Windows, users should wrap w with colorable.NewColorable() if w is of // type *os.File. func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) { - c.SetWriter(w) - defer c.UnsetWriter(w) - - return fmt.Fprintln(w, a...) + return fmt.Fprintln(w, c.wrap(fmt.Sprint(a...))) } // Println formats using the default formats for its operands and writes to @@ -258,10 +278,7 @@ func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) { // encountered. This is the standard fmt.Print() method wrapped with the given // color. func (c *Color) Println(a ...interface{}) (n int, err error) { - c.Set() - defer c.unset() - - return fmt.Fprintln(Output, a...) + return fmt.Fprintln(Output, c.wrap(fmt.Sprint(a...))) } // Sprint is just like Print, but returns a string instead of printing it. @@ -271,7 +288,7 @@ func (c *Color) Sprint(a ...interface{}) string { // Sprintln is just like Println, but returns a string instead of printing it. func (c *Color) Sprintln(a ...interface{}) string { - return c.wrap(fmt.Sprintln(a...)) + return fmt.Sprintln(c.Sprint(a...)) } // Sprintf is just like Printf, but returns a string instead of printing it. @@ -353,7 +370,7 @@ func (c *Color) SprintfFunc() func(format string, a ...interface{}) string { // string. Windows users should use this in conjunction with color.Output. func (c *Color) SprintlnFunc() func(a ...interface{}) string { return func(a ...interface{}) string { - return c.wrap(fmt.Sprintln(a...)) + return fmt.Sprintln(c.Sprint(a...)) } } @@ -383,7 +400,18 @@ func (c *Color) format() string { } func (c *Color) unformat() string { - return fmt.Sprintf("%s[%dm", escape, Reset) + //return fmt.Sprintf("%s[%dm", escape, Reset) + //for each element in sequence let's use the speficic reset escape, ou the generic one if not found + format := make([]string, len(c.params)) + for i, v := range c.params { + format[i] = strconv.Itoa(int(Reset)) + ra, ok := mapResetAttributes[v] + if ok { + format[i] = strconv.Itoa(int(ra)) + } + } + + return fmt.Sprintf("%s[%sm", escape, strings.Join(format, ";")) } // DisableColor disables the color output. Useful to not change any existing @@ -411,6 +439,12 @@ func (c *Color) isNoColorSet() bool { // Equals returns a boolean value indicating whether two colors are equal. func (c *Color) Equals(c2 *Color) bool { + if c == nil && c2 == nil { + return true + } + if c == nil || c2 == nil { + return false + } if len(c.params) != len(c2.params) { return false } diff --git a/vendor/github.com/ghostiam/protogetter/Makefile b/vendor/github.com/ghostiam/protogetter/Makefile index af4b62bdf..4c2a62af1 100644 --- a/vendor/github.com/ghostiam/protogetter/Makefile +++ b/vendor/github.com/ghostiam/protogetter/Makefile @@ -1,6 +1,6 @@ .PHONY: test test: - cd testdata && make vendor + $(MAKE) -C testdata vendor go test -v ./... .PHONY: install diff --git a/vendor/github.com/ghostiam/protogetter/processor.go b/vendor/github.com/ghostiam/protogetter/processor.go index 445f136b8..d65199dd2 100644 --- a/vendor/github.com/ghostiam/protogetter/processor.go +++ b/vendor/github.com/ghostiam/protogetter/processor.go @@ -12,16 +12,18 @@ import ( type processor struct { info *types.Info filter *PosFilter + cfg *Config to strings.Builder from strings.Builder err error } -func Process(info *types.Info, filter *PosFilter, n ast.Node) (*Result, error) { +func Process(info *types.Info, filter *PosFilter, n ast.Node, cfg *Config) (*Result, error) { p := &processor{ info: info, filter: filter, + cfg: cfg, } return p.process(n) @@ -31,8 +33,12 @@ 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()) + for _, s := range x.Lhs { + c.filter.AddPos(s.Pos()) + + if se, ok := s.(*ast.StarExpr); ok { + c.filter.AddPos(se.X.Pos()) + } } case *ast.IncDecStmt: @@ -47,6 +53,14 @@ func (c *processor) process(n ast.Node) (*Result, error) { } case *ast.CallExpr: + if !c.cfg.ReplaceFirstArgInAppend && len(x.Args) > 0 { + if v, ok := x.Fun.(*ast.Ident); ok && v.Name == "append" { + // Skip first argument of append function. + c.filter.AddPos(x.Args[0].Pos()) + break + } + } + f, ok := x.Fun.(*ast.SelectorExpr) if !ok { return &Result{}, nil @@ -66,6 +80,67 @@ func (c *processor) process(n ast.Node) (*Result, error) { c.processInner(x) + case *ast.StarExpr: + f, ok := x.X.(*ast.SelectorExpr) + if !ok { + return &Result{}, nil + } + + if !isProtoMessage(c.info, f.X) { + return &Result{}, nil + } + + // proto2 generates fields as pointers. Hence, the indirection + // must be removed when generating the fix for the case. + // The `*` is retained in `c.from`, but excluded from the fix + // present in the `c.to`. + c.writeFrom("*") + c.processInner(x.X) + + case *ast.BinaryExpr: + // Check if the expression is a comparison. + if x.Op != token.EQL && x.Op != token.NEQ { + return &Result{}, nil + } + + // Check if one of the operands is nil. + + xIdent, xOk := x.X.(*ast.Ident) + yIdent, yOk := x.Y.(*ast.Ident) + + xIsNil := xOk && xIdent.Name == "nil" + yIsNil := yOk && yIdent.Name == "nil" + + if !xIsNil && !yIsNil { + return &Result{}, nil + } + + // Extract the non-nil operand for further checks + + var expr ast.Expr + if xIsNil { + expr = x.Y + } else { + expr = x.X + } + + se, ok := expr.(*ast.SelectorExpr) + if !ok { + return &Result{}, nil + } + + if !isProtoMessage(c.info, se.X) { + return &Result{}, nil + } + + // Check if the Getter function of the protobuf message returns a pointer. + hasPointer, ok := getterResultHasPointer(c.info, se.X, se.Sel.Name) + if !ok || hasPointer { + return &Result{}, nil + } + + c.filter.AddPos(x.X.Pos()) + default: return nil, fmt.Errorf("not implemented for type: %s (%s)", reflect.TypeOf(x), formatNode(n)) } @@ -204,14 +279,14 @@ func isProtoMessage(info *types.Info, expr ast.Expr) bool { return false } -func methodIsExists(info *types.Info, x ast.Expr, name string) bool { +func typesNamed(info *types.Info, x ast.Expr) (*types.Named, bool) { if info == nil { - return false + return nil, false } t := info.TypeOf(x) if t == nil { - return false + return nil, false } ptr, ok := t.Underlying().(*types.Pointer) @@ -221,6 +296,15 @@ func methodIsExists(info *types.Info, x ast.Expr, name string) bool { named, ok := t.(*types.Named) if !ok { + return nil, false + } + + return named, true +} + +func methodIsExists(info *types.Info, x ast.Expr, name string) bool { + named, ok := typesNamed(info, x) + if !ok { return false } @@ -232,3 +316,38 @@ func methodIsExists(info *types.Info, x ast.Expr, name string) bool { return false } + +func getterResultHasPointer(info *types.Info, x ast.Expr, name string) (hasPointer, ok bool) { + named, ok := typesNamed(info, x) + if !ok { + return false, false + } + + for i := 0; i < named.NumMethods(); i++ { + method := named.Method(i) + if method.Name() != "Get"+name { + continue + } + + var sig *types.Signature + sig, ok = method.Type().(*types.Signature) + if !ok { + return false, false + } + + results := sig.Results() + if results.Len() == 0 { + return false, false + } + + firstType := results.At(0) + _, ok = firstType.Type().(*types.Pointer) + if !ok { + return false, true + } + + return true, true + } + + return false, false +} diff --git a/vendor/github.com/ghostiam/protogetter/protogetter.go b/vendor/github.com/ghostiam/protogetter/protogetter.go index 80a829672..31eee8572 100644 --- a/vendor/github.com/ghostiam/protogetter/protogetter.go +++ b/vendor/github.com/ghostiam/protogetter/protogetter.go @@ -2,13 +2,16 @@ package protogetter import ( "bytes" + "flag" "fmt" "go/ast" "go/format" "go/token" "log" + "path/filepath" "strings" + "github.com/gobwas/glob" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/ast/inspector" ) @@ -22,34 +25,101 @@ const ( const msgFormat = "avoid direct access to proto field %s, use %s instead" -func NewAnalyzer() *analysis.Analyzer { +func NewAnalyzer(cfg *Config) *analysis.Analyzer { + if cfg == nil { + cfg = &Config{} + } + return &analysis.Analyzer{ - Name: "protogetter", - Doc: "Reports direct reads from proto message fields when getters should be used", + Name: "protogetter", + Doc: "Reports direct reads from proto message fields when getters should be used", + Flags: flags(cfg), Run: func(pass *analysis.Pass) (any, error) { - Run(pass, StandaloneMode) - return nil, nil + _, err := Run(pass, cfg) + return nil, err }, } } -func Run(pass *analysis.Pass, mode Mode) []Issue { +func flags(opts *Config) flag.FlagSet { + fs := flag.NewFlagSet("protogetter", flag.ContinueOnError) + + fs.Func("skip-generated-by", "skip files generated with the given prefixes", func(s string) error { + for _, prefix := range strings.Split(s, ",") { + opts.SkipGeneratedBy = append(opts.SkipGeneratedBy, prefix) + } + return nil + }) + fs.Func("skip-files", "skip files with the given glob patterns", func(s string) error { + for _, pattern := range strings.Split(s, ",") { + opts.SkipFiles = append(opts.SkipFiles, pattern) + } + return nil + }) + fs.BoolVar(&opts.SkipAnyGenerated, "skip-any-generated", false, "skip any generated files") + + return *fs +} + +type Config struct { + Mode Mode // Zero value is StandaloneMode. + SkipGeneratedBy []string + SkipFiles []string + SkipAnyGenerated bool + ReplaceFirstArgInAppend bool +} + +func Run(pass *analysis.Pass, cfg *Config) ([]Issue, error) { + skipGeneratedBy := make([]string, 0, len(cfg.SkipGeneratedBy)+3) + // Always skip files generated by protoc-gen-go, protoc-gen-go-grpc and protoc-gen-grpc-gateway. + skipGeneratedBy = append(skipGeneratedBy, "protoc-gen-go", "protoc-gen-go-grpc", "protoc-gen-grpc-gateway") + for _, s := range cfg.SkipGeneratedBy { + s = strings.TrimSpace(s) + if s == "" { + continue + } + skipGeneratedBy = append(skipGeneratedBy, s) + } + + skipFilesGlobPatterns := make([]glob.Glob, 0, len(cfg.SkipFiles)) + for _, s := range cfg.SkipFiles { + s = strings.TrimSpace(s) + if s == "" { + continue + } + + compile, err := glob.Compile(s) + if err != nil { + return nil, fmt.Errorf("invalid glob pattern: %w", err) + } + + skipFilesGlobPatterns = append(skipFilesGlobPatterns, compile) + } + nodeTypes := []ast.Node{ (*ast.AssignStmt)(nil), + (*ast.BinaryExpr)(nil), (*ast.CallExpr)(nil), (*ast.SelectorExpr)(nil), + (*ast.StarExpr)(nil), (*ast.IncDecStmt)(nil), (*ast.UnaryExpr)(nil), } - // Skip protoc-generated files. + // Skip filtered files. var files []*ast.File for _, f := range pass.Files { - if !isProtocGeneratedFile(f) { - files = append(files, f) + if skipGeneratedFile(f, skipGeneratedBy, cfg.SkipAnyGenerated) { + continue + } - // ast.Print(pass.Fset, f) + if skipFilesByGlob(pass.Fset.File(f.Pos()).Name(), skipFilesGlobPatterns) { + continue } + + files = append(files, f) + + // ast.Print(pass.Fset, f) } ins := inspector.New(files) @@ -58,12 +128,12 @@ func Run(pass *analysis.Pass, mode Mode) []Issue { filter := NewPosFilter() ins.Preorder(nodeTypes, func(node ast.Node) { - report := analyse(pass, filter, node) + report := analyse(pass, filter, node, cfg) if report == nil { return } - switch mode { + switch cfg.Mode { case StandaloneMode: pass.Report(report.ToDiagReport()) case GolangciLintMode: @@ -71,10 +141,10 @@ func Run(pass *analysis.Pass, mode Mode) []Issue { } }) - return issues + return issues, nil } -func analyse(pass *analysis.Pass, filter *PosFilter, n ast.Node) *Report { +func analyse(pass *analysis.Pass, filter *PosFilter, n ast.Node, cfg *Config) *Report { // fmt.Printf("\n>>> check: %s\n", formatNode(n)) // ast.Print(pass.Fset, n) if filter.IsFiltered(n.Pos()) { @@ -82,7 +152,7 @@ func analyse(pass *analysis.Pass, filter *PosFilter, n ast.Node) *Report { return nil } - result, err := Process(pass.TypesInfo, filter, n) + result, err := Process(pass.TypesInfo, filter, n, cfg) if err != nil { pass.Report(analysis.Diagnostic{ Pos: n.Pos(), @@ -168,8 +238,34 @@ func (r *Report) ToIssue(fset *token.FileSet) Issue { } } -func isProtocGeneratedFile(f *ast.File) bool { - return len(f.Comments) > 0 && strings.HasPrefix(f.Comments[0].Text(), "Code generated by protoc-gen-go") +func skipGeneratedFile(f *ast.File, prefixes []string, skipAny bool) bool { + if len(f.Comments) == 0 { + return false + } + firstComment := f.Comments[0].Text() + + // https://golang.org/s/generatedcode + if skipAny && strings.HasPrefix(firstComment, "Code generated") { + return true + } + + for _, prefix := range prefixes { + if strings.HasPrefix(firstComment, "Code generated by "+prefix) { + return true + } + } + + return false +} + +func skipFilesByGlob(filename string, patterns []glob.Glob) bool { + for _, pattern := range patterns { + if pattern.Match(filename) || pattern.Match(filepath.Base(filename)) { + return true + } + } + + return false } func formatNode(node ast.Node) string { diff --git a/vendor/github.com/go-critic/go-critic/checkers/commentedOutCode_checker.go b/vendor/github.com/go-critic/go-critic/checkers/commentedOutCode_checker.go index 402ba3306..8595b7951 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/commentedOutCode_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/commentedOutCode_checker.go @@ -6,6 +6,7 @@ import ( "go/token" "regexp" "strings" + "unicode/utf8" "github.com/go-critic/go-critic/checkers/internal/astwalk" "github.com/go-critic/go-critic/linter" @@ -18,6 +19,12 @@ func init() { info.Name = "commentedOutCode" info.Tags = []string{linter.DiagnosticTag, linter.ExperimentalTag} info.Summary = "Detects commented-out code inside function bodies" + info.Params = linter.CheckerParams{ + "minLength": { + Value: 15, + Usage: "min length of the comment that triggers a warning", + }, + } info.Before = ` // fmt.Println("Debugging hard") foo(1, 2)` @@ -27,6 +34,7 @@ foo(1, 2)` return astwalk.WalkerForLocalComment(&commentedOutCodeChecker{ ctx: ctx, notQuiteFuncCall: regexp.MustCompile(`\w+\s+\([^)]*\)\s*$`), + minLength: info.Params.Int("minLength"), }), nil }) } @@ -37,6 +45,7 @@ type commentedOutCodeChecker struct { fn *ast.FuncDecl notQuiteFuncCall *regexp.Regexp + minLength int } func (c *commentedOutCodeChecker) EnterFunc(fn *ast.FuncDecl) bool { @@ -69,7 +78,7 @@ func (c *commentedOutCodeChecker) VisitLocalComment(cg *ast.CommentGroup) { // Some very short comment that can be skipped. // Usually triggering on these results in false positive. // Unless there is a very popular call like print/println. - cond := len(s) < len("quite too short") && + cond := utf8.RuneCountInString(s) < c.minLength && !strings.Contains(s, "print") && !strings.Contains(s, "fmt.") && !strings.Contains(s, "log.") diff --git a/vendor/github.com/go-critic/go-critic/checkers/dupImports_checker.go b/vendor/github.com/go-critic/go-critic/checkers/dupImports_checker.go index 19079871f..ed674eb85 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/dupImports_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/dupImports_checker.go @@ -15,7 +15,7 @@ func init() { info.Before = ` import ( "fmt" - priting "fmt" // Imported the second time + printing "fmt" // Imported the second time )` info.After = ` import( diff --git a/vendor/github.com/go-critic/go-critic/checkers/flagName_checker.go b/vendor/github.com/go-critic/go-critic/checkers/flagName_checker.go index 98b76e261..701066860 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/flagName_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/flagName_checker.go @@ -64,7 +64,7 @@ func (c *flagNameChecker) checkFlagName(call *ast.CallExpr, arg ast.Expr) { case name == "": c.warnEmpty(call) case strings.HasPrefix(name, "-"): - c.warnHypenPrefix(call, name) + c.warnHyphenPrefix(call, name) case strings.Contains(name, "="): c.warnEq(call, name) case strings.Contains(name, " "): @@ -76,8 +76,8 @@ func (c *flagNameChecker) warnEmpty(cause ast.Node) { c.ctx.Warn(cause, "empty flag name") } -func (c *flagNameChecker) warnHypenPrefix(cause ast.Node, name string) { - c.ctx.Warn(cause, "flag name %q should not start with a hypen", name) +func (c *flagNameChecker) warnHyphenPrefix(cause ast.Node, name string) { + c.ctx.Warn(cause, "flag name %q should not start with a hyphen", name) } func (c *flagNameChecker) warnEq(cause ast.Node, name string) { diff --git a/vendor/github.com/go-critic/go-critic/checkers/hugeParam_checker.go b/vendor/github.com/go-critic/go-critic/checkers/hugeParam_checker.go index 3b7f1d12b..7b7a3c538 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/hugeParam_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/hugeParam_checker.go @@ -5,6 +5,8 @@ import ( "github.com/go-critic/go-critic/checkers/internal/astwalk" "github.com/go-critic/go-critic/linter" + + "github.com/go-toolsmith/astcast" ) func init() { @@ -39,12 +41,30 @@ type hugeParamChecker struct { func (c *hugeParamChecker) VisitFuncDecl(decl *ast.FuncDecl) { // TODO(quasilyte): maybe it's worthwhile to permit skipping // test files for this checker? + if c.isImplementStringer(decl) { + return + } + if decl.Recv != nil { c.checkParams(decl.Recv.List) } c.checkParams(decl.Type.Params.List) } +// isImplementStringer check method signature is: String() string. +func (*hugeParamChecker) isImplementStringer(decl *ast.FuncDecl) bool { + if decl.Recv != nil && + decl.Name.Name == "String" && + decl.Type != nil && + len(decl.Type.Params.List) == 0 && + len(decl.Type.Results.List) == 1 && + astcast.ToIdent(decl.Type.Results.List[0].Type).Name == "string" { + return true + } + + return false +} + func (c *hugeParamChecker) checkParams(params []*ast.Field) { for _, p := range params { for _, id := range p.Names { diff --git a/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astflow.go b/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astflow.go index 63d181e5e..f64907d69 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astflow.go +++ b/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astflow.go @@ -18,7 +18,7 @@ import ( // // If proven really useful, can be moved to go-toolsmith library. -// IsImmutable reports whether n can be midified through any operation. +// IsImmutable reports whether n can be modified through any operation. func IsImmutable(info *types.Info, n ast.Expr) bool { if astp.IsBasicLit(n) { return true diff --git a/vendor/github.com/go-critic/go-critic/checkers/mapKey_checker.go b/vendor/github.com/go-critic/go-critic/checkers/mapKey_checker.go index ebc61c12a..2885dc725 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/mapKey_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/mapKey_checker.go @@ -117,7 +117,7 @@ func (c *mapKeyChecker) checkWhitespace(lit *ast.CompositeLit) { } func (c *mapKeyChecker) warnWhitespace(key ast.Node) { - c.ctx.Warn(key, "suspucious whitespace in %s key", key) + c.ctx.Warn(key, "suspicious whitespace in %s key", key) } func (c *mapKeyChecker) warnDupKey(key ast.Node) { diff --git a/vendor/github.com/go-critic/go-critic/checkers/rulesdata/rulesdata.go b/vendor/github.com/go-critic/go-critic/checkers/rulesdata/rulesdata.go index 503118c7e..4ab31076f 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/rulesdata/rulesdata.go +++ b/vendor/github.com/go-critic/go-critic/checkers/rulesdata/rulesdata.go @@ -363,7 +363,7 @@ var PrecompiledRules = &ir.File{ Line: 114, Name: "sloppyLen", MatcherName: "m", - DocTags: []string{"style"}, + DocTags: []string{"diagnostic"}, DocSummary: "Detects usage of `len` when result is obvious or doesn't make sense", DocBefore: "len(arr) <= 0", DocAfter: "len(arr) == 0", @@ -493,21 +493,45 @@ var PrecompiledRules = &ir.File{ }, }, { - Line: 164, - SyntaxPatterns: []ir.PatternString{{Line: 164, Value: "len($s) == 0"}}, + Line: 163, + SyntaxPatterns: []ir.PatternString{{Line: 163, Value: "len($s) > 0"}}, + ReportTemplate: "replace `$$` with `$s != \"\"`", + WhereExpr: ir.FilterExpr{ + Line: 164, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"s\"].Type.Is(`string`)", + Value: "s", + Args: []ir.FilterExpr{{Line: 164, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + }, + }, + { + Line: 167, + SyntaxPatterns: []ir.PatternString{{Line: 167, Value: "len($s) == 0"}}, + ReportTemplate: "replace `$$` with `$s == \"\"`", + WhereExpr: ir.FilterExpr{ + Line: 168, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"s\"].Type.Is(`string`)", + Value: "s", + Args: []ir.FilterExpr{{Line: 168, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + }, + }, + { + Line: 170, + SyntaxPatterns: []ir.PatternString{{Line: 170, Value: "len($s) <= 0"}}, ReportTemplate: "replace `$$` with `$s == \"\"`", WhereExpr: ir.FilterExpr{ - Line: 165, + Line: 171, Op: ir.FilterVarTypeIsOp, Src: "m[\"s\"].Type.Is(`string`)", Value: "s", - Args: []ir.FilterExpr{{Line: 165, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + Args: []ir.FilterExpr{{Line: 171, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, }, }, }, }, { - Line: 173, + Line: 179, Name: "stringXbytes", MatcherName: "m", DocTags: []string{"performance"}, @@ -516,180 +540,180 @@ var PrecompiledRules = &ir.File{ DocAfter: "copy(b, s)", Rules: []ir.Rule{ { - Line: 174, - SyntaxPatterns: []ir.PatternString{{Line: 174, Value: "copy($_, []byte($s))"}}, + Line: 180, + SyntaxPatterns: []ir.PatternString{{Line: 180, Value: "copy($_, []byte($s))"}}, ReportTemplate: "can simplify `[]byte($s)` to `$s`", }, { - Line: 176, - SyntaxPatterns: []ir.PatternString{{Line: 176, Value: "string($b) == \"\""}}, + Line: 182, + SyntaxPatterns: []ir.PatternString{{Line: 182, Value: "string($b) == \"\""}}, ReportTemplate: "suggestion: len($b) == 0", SuggestTemplate: "len($b) == 0", WhereExpr: ir.FilterExpr{ - Line: 176, + Line: 182, Op: ir.FilterVarTypeIsOp, Src: "m[\"b\"].Type.Is(`[]byte`)", Value: "b", - Args: []ir.FilterExpr{{Line: 176, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, + Args: []ir.FilterExpr{{Line: 182, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, }, }, { - Line: 177, - SyntaxPatterns: []ir.PatternString{{Line: 177, Value: "string($b) != \"\""}}, + Line: 183, + SyntaxPatterns: []ir.PatternString{{Line: 183, Value: "string($b) != \"\""}}, ReportTemplate: "suggestion: len($b) != 0", SuggestTemplate: "len($b) != 0", WhereExpr: ir.FilterExpr{ - Line: 177, + Line: 183, Op: ir.FilterVarTypeIsOp, Src: "m[\"b\"].Type.Is(`[]byte`)", Value: "b", - Args: []ir.FilterExpr{{Line: 177, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, + Args: []ir.FilterExpr{{Line: 183, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, }, }, { - Line: 179, - SyntaxPatterns: []ir.PatternString{{Line: 179, Value: "len(string($b))"}}, + Line: 185, + SyntaxPatterns: []ir.PatternString{{Line: 185, Value: "len(string($b))"}}, ReportTemplate: "suggestion: len($b)", SuggestTemplate: "len($b)", WhereExpr: ir.FilterExpr{ - Line: 179, + Line: 185, Op: ir.FilterVarTypeIsOp, Src: "m[\"b\"].Type.Is(`[]byte`)", Value: "b", - Args: []ir.FilterExpr{{Line: 179, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, + Args: []ir.FilterExpr{{Line: 185, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, }, }, { - Line: 181, - SyntaxPatterns: []ir.PatternString{{Line: 181, Value: "string($x) == string($y)"}}, + Line: 187, + SyntaxPatterns: []ir.PatternString{{Line: 187, Value: "string($x) == string($y)"}}, ReportTemplate: "suggestion: bytes.Equal($x, $y)", SuggestTemplate: "bytes.Equal($x, $y)", WhereExpr: ir.FilterExpr{ - Line: 182, + Line: 188, Op: ir.FilterAndOp, Src: "m[\"x\"].Type.Is(`[]byte`) && m[\"y\"].Type.Is(`[]byte`)", Args: []ir.FilterExpr{ { - Line: 182, + Line: 188, Op: ir.FilterVarTypeIsOp, Src: "m[\"x\"].Type.Is(`[]byte`)", Value: "x", - Args: []ir.FilterExpr{{Line: 182, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, + Args: []ir.FilterExpr{{Line: 188, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, }, { - Line: 182, + Line: 188, Op: ir.FilterVarTypeIsOp, Src: "m[\"y\"].Type.Is(`[]byte`)", Value: "y", - Args: []ir.FilterExpr{{Line: 182, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, + Args: []ir.FilterExpr{{Line: 188, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, }, }, }, }, { - Line: 185, - SyntaxPatterns: []ir.PatternString{{Line: 185, Value: "string($x) != string($y)"}}, + Line: 191, + SyntaxPatterns: []ir.PatternString{{Line: 191, Value: "string($x) != string($y)"}}, ReportTemplate: "suggestion: !bytes.Equal($x, $y)", SuggestTemplate: "!bytes.Equal($x, $y)", WhereExpr: ir.FilterExpr{ - Line: 186, + Line: 192, Op: ir.FilterAndOp, Src: "m[\"x\"].Type.Is(`[]byte`) && m[\"y\"].Type.Is(`[]byte`)", Args: []ir.FilterExpr{ { - Line: 186, + Line: 192, Op: ir.FilterVarTypeIsOp, Src: "m[\"x\"].Type.Is(`[]byte`)", Value: "x", - Args: []ir.FilterExpr{{Line: 186, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, + Args: []ir.FilterExpr{{Line: 192, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, }, { - Line: 186, + Line: 192, Op: ir.FilterVarTypeIsOp, Src: "m[\"y\"].Type.Is(`[]byte`)", Value: "y", - Args: []ir.FilterExpr{{Line: 186, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, + Args: []ir.FilterExpr{{Line: 192, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, }, }, }, }, { - Line: 189, - SyntaxPatterns: []ir.PatternString{{Line: 189, Value: "$re.Match([]byte($s))"}}, + Line: 195, + SyntaxPatterns: []ir.PatternString{{Line: 195, Value: "$re.Match([]byte($s))"}}, ReportTemplate: "suggestion: $re.MatchString($s)", SuggestTemplate: "$re.MatchString($s)", WhereExpr: ir.FilterExpr{ - Line: 190, + Line: 196, Op: ir.FilterAndOp, Src: "m[\"re\"].Type.Is(`*regexp.Regexp`) && m[\"s\"].Type.Is(`string`)", Args: []ir.FilterExpr{ { - Line: 190, + Line: 196, Op: ir.FilterVarTypeIsOp, Src: "m[\"re\"].Type.Is(`*regexp.Regexp`)", Value: "re", - Args: []ir.FilterExpr{{Line: 190, Op: ir.FilterStringOp, Src: "`*regexp.Regexp`", Value: "*regexp.Regexp"}}, + Args: []ir.FilterExpr{{Line: 196, Op: ir.FilterStringOp, Src: "`*regexp.Regexp`", Value: "*regexp.Regexp"}}, }, { - Line: 190, + Line: 196, Op: ir.FilterVarTypeIsOp, Src: "m[\"s\"].Type.Is(`string`)", Value: "s", - Args: []ir.FilterExpr{{Line: 190, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + Args: []ir.FilterExpr{{Line: 196, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, }, }, }, }, { - Line: 193, - SyntaxPatterns: []ir.PatternString{{Line: 193, Value: "$re.FindIndex([]byte($s))"}}, + Line: 199, + SyntaxPatterns: []ir.PatternString{{Line: 199, Value: "$re.FindIndex([]byte($s))"}}, ReportTemplate: "suggestion: $re.FindStringIndex($s)", SuggestTemplate: "$re.FindStringIndex($s)", WhereExpr: ir.FilterExpr{ - Line: 194, + Line: 200, Op: ir.FilterAndOp, Src: "m[\"re\"].Type.Is(`*regexp.Regexp`) && m[\"s\"].Type.Is(`string`)", Args: []ir.FilterExpr{ { - Line: 194, + Line: 200, Op: ir.FilterVarTypeIsOp, Src: "m[\"re\"].Type.Is(`*regexp.Regexp`)", Value: "re", - Args: []ir.FilterExpr{{Line: 194, Op: ir.FilterStringOp, Src: "`*regexp.Regexp`", Value: "*regexp.Regexp"}}, + Args: []ir.FilterExpr{{Line: 200, Op: ir.FilterStringOp, Src: "`*regexp.Regexp`", Value: "*regexp.Regexp"}}, }, { - Line: 194, + Line: 200, Op: ir.FilterVarTypeIsOp, Src: "m[\"s\"].Type.Is(`string`)", Value: "s", - Args: []ir.FilterExpr{{Line: 194, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + Args: []ir.FilterExpr{{Line: 200, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, }, }, }, }, { - Line: 197, - SyntaxPatterns: []ir.PatternString{{Line: 197, Value: "$re.FindAllIndex([]byte($s), $n)"}}, + Line: 203, + SyntaxPatterns: []ir.PatternString{{Line: 203, Value: "$re.FindAllIndex([]byte($s), $n)"}}, ReportTemplate: "suggestion: $re.FindAllStringIndex($s, $n)", SuggestTemplate: "$re.FindAllStringIndex($s, $n)", WhereExpr: ir.FilterExpr{ - Line: 198, + Line: 204, Op: ir.FilterAndOp, Src: "m[\"re\"].Type.Is(`*regexp.Regexp`) && m[\"s\"].Type.Is(`string`)", Args: []ir.FilterExpr{ { - Line: 198, + Line: 204, Op: ir.FilterVarTypeIsOp, Src: "m[\"re\"].Type.Is(`*regexp.Regexp`)", Value: "re", - Args: []ir.FilterExpr{{Line: 198, Op: ir.FilterStringOp, Src: "`*regexp.Regexp`", Value: "*regexp.Regexp"}}, + Args: []ir.FilterExpr{{Line: 204, Op: ir.FilterStringOp, Src: "`*regexp.Regexp`", Value: "*regexp.Regexp"}}, }, { - Line: 198, + Line: 204, Op: ir.FilterVarTypeIsOp, Src: "m[\"s\"].Type.Is(`string`)", Value: "s", - Args: []ir.FilterExpr{{Line: 198, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + Args: []ir.FilterExpr{{Line: 204, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, }, }, }, @@ -697,7 +721,7 @@ var PrecompiledRules = &ir.File{ }, }, { - Line: 207, + Line: 213, Name: "indexAlloc", MatcherName: "m", DocTags: []string{"performance"}, @@ -706,22 +730,22 @@ var PrecompiledRules = &ir.File{ DocAfter: "bytes.Index(x, []byte(y))", DocNote: "See Go issue for details: https://github.com/golang/go/issues/25864", Rules: []ir.Rule{{ - Line: 208, - SyntaxPatterns: []ir.PatternString{{Line: 208, Value: "strings.Index(string($x), $y)"}}, + Line: 214, + SyntaxPatterns: []ir.PatternString{{Line: 214, Value: "strings.Index(string($x), $y)"}}, ReportTemplate: "consider replacing $$ with bytes.Index($x, []byte($y))", WhereExpr: ir.FilterExpr{ - Line: 209, + Line: 215, Op: ir.FilterAndOp, Src: "m[\"x\"].Pure && m[\"y\"].Pure", Args: []ir.FilterExpr{ - {Line: 209, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, - {Line: 209, Op: ir.FilterVarPureOp, Src: "m[\"y\"].Pure", Value: "y"}, + {Line: 215, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + {Line: 215, Op: ir.FilterVarPureOp, Src: "m[\"y\"].Pure", Value: "y"}, }, }, }}, }, { - Line: 217, + Line: 223, Name: "wrapperFunc", MatcherName: "m", DocTags: []string{"style"}, @@ -730,169 +754,169 @@ var PrecompiledRules = &ir.File{ DocAfter: "wg.Done()", Rules: []ir.Rule{ { - Line: 218, - SyntaxPatterns: []ir.PatternString{{Line: 218, Value: "$wg.Add(-1)"}}, + Line: 224, + SyntaxPatterns: []ir.PatternString{{Line: 224, Value: "$wg.Add(-1)"}}, ReportTemplate: "use WaitGroup.Done method in `$$`", WhereExpr: ir.FilterExpr{ - Line: 219, + Line: 225, Op: ir.FilterVarTypeIsOp, Src: "m[\"wg\"].Type.Is(`sync.WaitGroup`)", Value: "wg", - Args: []ir.FilterExpr{{Line: 219, Op: ir.FilterStringOp, Src: "`sync.WaitGroup`", Value: "sync.WaitGroup"}}, + Args: []ir.FilterExpr{{Line: 225, Op: ir.FilterStringOp, Src: "`sync.WaitGroup`", Value: "sync.WaitGroup"}}, }, }, { - Line: 222, - SyntaxPatterns: []ir.PatternString{{Line: 222, Value: "$buf.Truncate(0)"}}, + Line: 228, + SyntaxPatterns: []ir.PatternString{{Line: 228, Value: "$buf.Truncate(0)"}}, ReportTemplate: "use Buffer.Reset method in `$$`", WhereExpr: ir.FilterExpr{ - Line: 223, + Line: 229, Op: ir.FilterVarTypeIsOp, Src: "m[\"buf\"].Type.Is(`bytes.Buffer`)", Value: "buf", - Args: []ir.FilterExpr{{Line: 223, Op: ir.FilterStringOp, Src: "`bytes.Buffer`", Value: "bytes.Buffer"}}, + Args: []ir.FilterExpr{{Line: 229, Op: ir.FilterStringOp, Src: "`bytes.Buffer`", Value: "bytes.Buffer"}}, }, }, { - Line: 226, - SyntaxPatterns: []ir.PatternString{{Line: 226, Value: "http.HandlerFunc(http.NotFound)"}}, + Line: 232, + SyntaxPatterns: []ir.PatternString{{Line: 232, Value: "http.HandlerFunc(http.NotFound)"}}, ReportTemplate: "use http.NotFoundHandler method in `$$`", }, { - Line: 228, - SyntaxPatterns: []ir.PatternString{{Line: 228, Value: "strings.SplitN($_, $_, -1)"}}, + Line: 234, + SyntaxPatterns: []ir.PatternString{{Line: 234, Value: "strings.SplitN($_, $_, -1)"}}, ReportTemplate: "use strings.Split method in `$$`", }, { - Line: 229, - SyntaxPatterns: []ir.PatternString{{Line: 229, Value: "strings.Replace($_, $_, $_, -1)"}}, + Line: 235, + SyntaxPatterns: []ir.PatternString{{Line: 235, Value: "strings.Replace($_, $_, $_, -1)"}}, ReportTemplate: "use strings.ReplaceAll method in `$$`", }, { - Line: 230, - SyntaxPatterns: []ir.PatternString{{Line: 230, Value: "strings.Map(unicode.ToTitle, $_)"}}, + Line: 236, + SyntaxPatterns: []ir.PatternString{{Line: 236, Value: "strings.Map(unicode.ToTitle, $_)"}}, ReportTemplate: "use strings.ToTitle method in `$$`", }, { - Line: 231, + Line: 237, SyntaxPatterns: []ir.PatternString{ - {Line: 231, Value: "strings.Index($s1, $s2) >= 0"}, - {Line: 231, Value: "strings.Index($s1, $s2) != -1"}, + {Line: 237, Value: "strings.Index($s1, $s2) >= 0"}, + {Line: 237, Value: "strings.Index($s1, $s2) != -1"}, }, ReportTemplate: "suggestion: strings.Contains($s1, $s2)", SuggestTemplate: "strings.Contains($s1, $s2)", }, { - Line: 232, + Line: 238, SyntaxPatterns: []ir.PatternString{ - {Line: 232, Value: "strings.IndexAny($s1, $s2) >= 0"}, - {Line: 232, Value: "strings.IndexAny($s1, $s2) != -1"}, + {Line: 238, Value: "strings.IndexAny($s1, $s2) >= 0"}, + {Line: 238, Value: "strings.IndexAny($s1, $s2) != -1"}, }, ReportTemplate: "suggestion: strings.ContainsAny($s1, $s2)", SuggestTemplate: "strings.ContainsAny($s1, $s2)", }, { - Line: 233, + Line: 239, SyntaxPatterns: []ir.PatternString{ - {Line: 233, Value: "strings.IndexRune($s1, $s2) >= 0"}, - {Line: 233, Value: "strings.IndexRune($s1, $s2) != -1"}, + {Line: 239, Value: "strings.IndexRune($s1, $s2) >= 0"}, + {Line: 239, Value: "strings.IndexRune($s1, $s2) != -1"}, }, ReportTemplate: "suggestion: strings.ContainsRune($s1, $s2)", SuggestTemplate: "strings.ContainsRune($s1, $s2)", }, { - Line: 235, + Line: 241, SyntaxPatterns: []ir.PatternString{ - {Line: 235, Value: "$i := strings.Index($s, $sep); $*_; $x, $y = $s[:$i], $s[$i+1:]"}, - {Line: 236, Value: "$i := strings.Index($s, $sep); $*_; $x = $s[:$i]; $*_; $y = $s[$i+1:]"}, + {Line: 241, Value: "$i := strings.Index($s, $sep); $*_; $x, $y = $s[:$i], $s[$i+1:]"}, + {Line: 242, Value: "$i := strings.Index($s, $sep); $*_; $x = $s[:$i]; $*_; $y = $s[$i+1:]"}, }, ReportTemplate: "suggestion: $x, $y, _ = strings.Cut($s, $sep)", SuggestTemplate: "$x, $y, _ = strings.Cut($s, $sep)", WhereExpr: ir.FilterExpr{ - Line: 237, + Line: 243, Op: ir.FilterGoVersionGreaterEqThanOp, Src: "m.GoVersion().GreaterEqThan(\"1.18\")", Value: "1.18", }, }, { - Line: 240, + Line: 246, SyntaxPatterns: []ir.PatternString{ - {Line: 241, Value: "if $i := strings.Index($s, $sep); $i != -1 { $*_; $x, $y = $s[:$i], $s[$i+1:]; $*_ }"}, - {Line: 242, Value: "if $i := strings.Index($s, $sep); $i != -1 { $*_; $x = $s[:$i]; $*_; $y = $s[$i+1:]; $*_ }"}, - {Line: 243, Value: "if $i := strings.Index($s, $sep); $i >= 0 { $*_; $x, $y = $s[:$i], $s[$i+1:]; $*_ }"}, - {Line: 244, Value: "if $i := strings.Index($s, $sep); $i >= 0 { $*_; $x = $s[:$i]; $*_; $y = $s[$i+1:]; $*_ }"}, + {Line: 247, Value: "if $i := strings.Index($s, $sep); $i != -1 { $*_; $x, $y = $s[:$i], $s[$i+1:]; $*_ }"}, + {Line: 248, Value: "if $i := strings.Index($s, $sep); $i != -1 { $*_; $x = $s[:$i]; $*_; $y = $s[$i+1:]; $*_ }"}, + {Line: 249, Value: "if $i := strings.Index($s, $sep); $i >= 0 { $*_; $x, $y = $s[:$i], $s[$i+1:]; $*_ }"}, + {Line: 250, Value: "if $i := strings.Index($s, $sep); $i >= 0 { $*_; $x = $s[:$i]; $*_; $y = $s[$i+1:]; $*_ }"}, }, ReportTemplate: "suggestion: if $x, $y, ok = strings.Cut($s, $sep); ok { ... }", SuggestTemplate: "if $x, $y, ok = strings.Cut($s, $sep); ok { ... }", WhereExpr: ir.FilterExpr{ - Line: 245, + Line: 251, Op: ir.FilterGoVersionGreaterEqThanOp, Src: "m.GoVersion().GreaterEqThan(\"1.18\")", Value: "1.18", }, }, { - Line: 248, - SyntaxPatterns: []ir.PatternString{{Line: 248, Value: "bytes.SplitN(b, []byte(\".\"), -1)"}}, + Line: 254, + SyntaxPatterns: []ir.PatternString{{Line: 254, Value: "bytes.SplitN(b, []byte(\".\"), -1)"}}, ReportTemplate: "use bytes.Split method in `$$`", }, { - Line: 249, - SyntaxPatterns: []ir.PatternString{{Line: 249, Value: "bytes.Replace($_, $_, $_, -1)"}}, + Line: 255, + SyntaxPatterns: []ir.PatternString{{Line: 255, Value: "bytes.Replace($_, $_, $_, -1)"}}, ReportTemplate: "use bytes.ReplaceAll method in `$$`", }, { - Line: 250, - SyntaxPatterns: []ir.PatternString{{Line: 250, Value: "bytes.Map(unicode.ToUpper, $_)"}}, + Line: 256, + SyntaxPatterns: []ir.PatternString{{Line: 256, Value: "bytes.Map(unicode.ToUpper, $_)"}}, ReportTemplate: "use bytes.ToUpper method in `$$`", }, { - Line: 251, - SyntaxPatterns: []ir.PatternString{{Line: 251, Value: "bytes.Map(unicode.ToLower, $_)"}}, + Line: 257, + SyntaxPatterns: []ir.PatternString{{Line: 257, Value: "bytes.Map(unicode.ToLower, $_)"}}, ReportTemplate: "use bytes.ToLower method in `$$`", }, { - Line: 252, - SyntaxPatterns: []ir.PatternString{{Line: 252, Value: "bytes.Map(unicode.ToTitle, $_)"}}, + Line: 258, + SyntaxPatterns: []ir.PatternString{{Line: 258, Value: "bytes.Map(unicode.ToTitle, $_)"}}, ReportTemplate: "use bytes.ToTitle method in `$$`", }, { - Line: 253, + Line: 259, SyntaxPatterns: []ir.PatternString{ - {Line: 253, Value: "bytes.Index($b1, $b2) >= 0"}, - {Line: 253, Value: "bytes.Index($b1, $b2) != -1"}, + {Line: 259, Value: "bytes.Index($b1, $b2) >= 0"}, + {Line: 259, Value: "bytes.Index($b1, $b2) != -1"}, }, ReportTemplate: "suggestion: bytes.Contains($b1, $b2)", SuggestTemplate: "bytes.Contains($b1, $b2)", }, { - Line: 254, + Line: 260, SyntaxPatterns: []ir.PatternString{ - {Line: 254, Value: "bytes.IndexAny($b1, $b2) >= 0"}, - {Line: 254, Value: "bytes.IndexAny($b1, $b2) != -1"}, + {Line: 260, Value: "bytes.IndexAny($b1, $b2) >= 0"}, + {Line: 260, Value: "bytes.IndexAny($b1, $b2) != -1"}, }, ReportTemplate: "suggestion: bytes.ContainsAny($b1, $b2)", SuggestTemplate: "bytes.ContainsAny($b1, $b2)", }, { - Line: 255, + Line: 261, SyntaxPatterns: []ir.PatternString{ - {Line: 255, Value: "bytes.IndexRune($b1, $b2) >= 0"}, - {Line: 255, Value: "bytes.IndexRune($b1, $b2) != -1"}, + {Line: 261, Value: "bytes.IndexRune($b1, $b2) >= 0"}, + {Line: 261, Value: "bytes.IndexRune($b1, $b2) != -1"}, }, ReportTemplate: "suggestion: bytes.ContainsRune($b1, $b2)", SuggestTemplate: "bytes.ContainsRune($b1, $b2)", }, { - Line: 257, - SyntaxPatterns: []ir.PatternString{{Line: 257, Value: "draw.DrawMask($_, $_, $_, $_, nil, image.Point{}, $_)"}}, + Line: 263, + SyntaxPatterns: []ir.PatternString{{Line: 263, Value: "draw.DrawMask($_, $_, $_, $_, nil, image.Point{}, $_)"}}, ReportTemplate: "use draw.Draw method in `$$`", }, }, }, { - Line: 265, + Line: 271, Name: "regexpMust", MatcherName: "m", DocTags: []string{"style"}, @@ -901,22 +925,22 @@ var PrecompiledRules = &ir.File{ DocAfter: "re := regexp.MustCompile(\"const pattern\")", Rules: []ir.Rule{ { - Line: 266, - SyntaxPatterns: []ir.PatternString{{Line: 266, Value: "regexp.Compile($pat)"}}, + Line: 272, + SyntaxPatterns: []ir.PatternString{{Line: 272, Value: "regexp.Compile($pat)"}}, ReportTemplate: "for const patterns like $pat, use regexp.MustCompile", WhereExpr: ir.FilterExpr{ - Line: 267, + Line: 273, Op: ir.FilterVarConstOp, Src: "m[\"pat\"].Const", Value: "pat", }, }, { - Line: 270, - SyntaxPatterns: []ir.PatternString{{Line: 270, Value: "regexp.CompilePOSIX($pat)"}}, + Line: 276, + SyntaxPatterns: []ir.PatternString{{Line: 276, Value: "regexp.CompilePOSIX($pat)"}}, ReportTemplate: "for const patterns like $pat, use regexp.MustCompilePOSIX", WhereExpr: ir.FilterExpr{ - Line: 271, + Line: 277, Op: ir.FilterVarConstOp, Src: "m[\"pat\"].Const", Value: "pat", @@ -925,7 +949,7 @@ var PrecompiledRules = &ir.File{ }, }, { - Line: 279, + Line: 285, Name: "badCall", MatcherName: "m", DocTags: []string{"diagnostic"}, @@ -934,22 +958,22 @@ var PrecompiledRules = &ir.File{ DocAfter: "strings.Replace(s, from, to, -1)", Rules: []ir.Rule{ { - Line: 280, - SyntaxPatterns: []ir.PatternString{{Line: 280, Value: "strings.Replace($_, $_, $_, $zero)"}}, + Line: 286, + SyntaxPatterns: []ir.PatternString{{Line: 286, Value: "strings.Replace($_, $_, $_, $zero)"}}, ReportTemplate: "suspicious arg 0, probably meant -1", WhereExpr: ir.FilterExpr{ - Line: 281, + Line: 287, Op: ir.FilterEqOp, Src: "m[\"zero\"].Value.Int() == 0", Args: []ir.FilterExpr{ { - Line: 281, + Line: 287, Op: ir.FilterVarValueIntOp, Src: "m[\"zero\"].Value.Int()", Value: "zero", }, { - Line: 281, + Line: 287, Op: ir.FilterIntOp, Src: "0", Value: int64(0), @@ -959,22 +983,22 @@ var PrecompiledRules = &ir.File{ LocationVar: "zero", }, { - Line: 283, - SyntaxPatterns: []ir.PatternString{{Line: 283, Value: "bytes.Replace($_, $_, $_, $zero)"}}, + Line: 289, + SyntaxPatterns: []ir.PatternString{{Line: 289, Value: "bytes.Replace($_, $_, $_, $zero)"}}, ReportTemplate: "suspicious arg 0, probably meant -1", WhereExpr: ir.FilterExpr{ - Line: 284, + Line: 290, Op: ir.FilterEqOp, Src: "m[\"zero\"].Value.Int() == 0", Args: []ir.FilterExpr{ { - Line: 284, + Line: 290, Op: ir.FilterVarValueIntOp, Src: "m[\"zero\"].Value.Int()", Value: "zero", }, { - Line: 284, + Line: 290, Op: ir.FilterIntOp, Src: "0", Value: int64(0), @@ -984,22 +1008,22 @@ var PrecompiledRules = &ir.File{ LocationVar: "zero", }, { - Line: 287, - SyntaxPatterns: []ir.PatternString{{Line: 287, Value: "strings.SplitN($_, $_, $zero)"}}, + Line: 293, + SyntaxPatterns: []ir.PatternString{{Line: 293, Value: "strings.SplitN($_, $_, $zero)"}}, ReportTemplate: "suspicious arg 0, probably meant -1", WhereExpr: ir.FilterExpr{ - Line: 288, + Line: 294, Op: ir.FilterEqOp, Src: "m[\"zero\"].Value.Int() == 0", Args: []ir.FilterExpr{ { - Line: 288, + Line: 294, Op: ir.FilterVarValueIntOp, Src: "m[\"zero\"].Value.Int()", Value: "zero", }, { - Line: 288, + Line: 294, Op: ir.FilterIntOp, Src: "0", Value: int64(0), @@ -1009,22 +1033,22 @@ var PrecompiledRules = &ir.File{ LocationVar: "zero", }, { - Line: 290, - SyntaxPatterns: []ir.PatternString{{Line: 290, Value: "bytes.SplitN($_, $_, $zero)"}}, + Line: 296, + SyntaxPatterns: []ir.PatternString{{Line: 296, Value: "bytes.SplitN($_, $_, $zero)"}}, ReportTemplate: "suspicious arg 0, probably meant -1", WhereExpr: ir.FilterExpr{ - Line: 291, + Line: 297, Op: ir.FilterEqOp, Src: "m[\"zero\"].Value.Int() == 0", Args: []ir.FilterExpr{ { - Line: 291, + Line: 297, Op: ir.FilterVarValueIntOp, Src: "m[\"zero\"].Value.Int()", Value: "zero", }, { - Line: 291, + Line: 297, Op: ir.FilterIntOp, Src: "0", Value: int64(0), @@ -1034,19 +1058,19 @@ var PrecompiledRules = &ir.File{ LocationVar: "zero", }, { - Line: 294, - SyntaxPatterns: []ir.PatternString{{Line: 294, Value: "append($_)"}}, + Line: 300, + SyntaxPatterns: []ir.PatternString{{Line: 300, Value: "append($_)"}}, ReportTemplate: "no-op append call, probably missing arguments", }, { - Line: 296, - SyntaxPatterns: []ir.PatternString{{Line: 296, Value: "filepath.Join($_)"}}, + Line: 302, + SyntaxPatterns: []ir.PatternString{{Line: 302, Value: "filepath.Join($_)"}}, ReportTemplate: "suspicious Join on 1 argument", }, }, }, { - Line: 303, + Line: 309, Name: "assignOp", MatcherName: "m", DocTags: []string{"style"}, @@ -1055,87 +1079,87 @@ var PrecompiledRules = &ir.File{ DocAfter: "x *= 2", Rules: []ir.Rule{ { - Line: 304, - SyntaxPatterns: []ir.PatternString{{Line: 304, Value: "$x = $x + 1"}}, + Line: 310, + SyntaxPatterns: []ir.PatternString{{Line: 310, Value: "$x = $x + 1"}}, ReportTemplate: "replace `$$` with `$x++`", - WhereExpr: ir.FilterExpr{Line: 304, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 310, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 305, - SyntaxPatterns: []ir.PatternString{{Line: 305, Value: "$x = $x - 1"}}, + Line: 311, + SyntaxPatterns: []ir.PatternString{{Line: 311, Value: "$x = $x - 1"}}, ReportTemplate: "replace `$$` with `$x--`", - WhereExpr: ir.FilterExpr{Line: 305, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 311, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 307, - SyntaxPatterns: []ir.PatternString{{Line: 307, Value: "$x = $x + $y"}}, + Line: 313, + SyntaxPatterns: []ir.PatternString{{Line: 313, Value: "$x = $x + $y"}}, ReportTemplate: "replace `$$` with `$x += $y`", - WhereExpr: ir.FilterExpr{Line: 307, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 313, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 308, - SyntaxPatterns: []ir.PatternString{{Line: 308, Value: "$x = $x - $y"}}, + Line: 314, + SyntaxPatterns: []ir.PatternString{{Line: 314, Value: "$x = $x - $y"}}, ReportTemplate: "replace `$$` with `$x -= $y`", - WhereExpr: ir.FilterExpr{Line: 308, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 314, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 310, - SyntaxPatterns: []ir.PatternString{{Line: 310, Value: "$x = $x * $y"}}, + Line: 316, + SyntaxPatterns: []ir.PatternString{{Line: 316, Value: "$x = $x * $y"}}, ReportTemplate: "replace `$$` with `$x *= $y`", - WhereExpr: ir.FilterExpr{Line: 310, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 316, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 311, - SyntaxPatterns: []ir.PatternString{{Line: 311, Value: "$x = $x / $y"}}, + Line: 317, + SyntaxPatterns: []ir.PatternString{{Line: 317, Value: "$x = $x / $y"}}, ReportTemplate: "replace `$$` with `$x /= $y`", - WhereExpr: ir.FilterExpr{Line: 311, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 317, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 312, - SyntaxPatterns: []ir.PatternString{{Line: 312, Value: "$x = $x % $y"}}, + Line: 318, + SyntaxPatterns: []ir.PatternString{{Line: 318, Value: "$x = $x % $y"}}, ReportTemplate: "replace `$$` with `$x %= $y`", - WhereExpr: ir.FilterExpr{Line: 312, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 318, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 313, - SyntaxPatterns: []ir.PatternString{{Line: 313, Value: "$x = $x & $y"}}, + Line: 319, + SyntaxPatterns: []ir.PatternString{{Line: 319, Value: "$x = $x & $y"}}, ReportTemplate: "replace `$$` with `$x &= $y`", - WhereExpr: ir.FilterExpr{Line: 313, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 319, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 314, - SyntaxPatterns: []ir.PatternString{{Line: 314, Value: "$x = $x | $y"}}, + Line: 320, + SyntaxPatterns: []ir.PatternString{{Line: 320, Value: "$x = $x | $y"}}, ReportTemplate: "replace `$$` with `$x |= $y`", - WhereExpr: ir.FilterExpr{Line: 314, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 320, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 315, - SyntaxPatterns: []ir.PatternString{{Line: 315, Value: "$x = $x ^ $y"}}, + Line: 321, + SyntaxPatterns: []ir.PatternString{{Line: 321, Value: "$x = $x ^ $y"}}, ReportTemplate: "replace `$$` with `$x ^= $y`", - WhereExpr: ir.FilterExpr{Line: 315, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 321, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 316, - SyntaxPatterns: []ir.PatternString{{Line: 316, Value: "$x = $x << $y"}}, + Line: 322, + SyntaxPatterns: []ir.PatternString{{Line: 322, Value: "$x = $x << $y"}}, ReportTemplate: "replace `$$` with `$x <<= $y`", - WhereExpr: ir.FilterExpr{Line: 316, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 322, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 317, - SyntaxPatterns: []ir.PatternString{{Line: 317, Value: "$x = $x >> $y"}}, + Line: 323, + SyntaxPatterns: []ir.PatternString{{Line: 323, Value: "$x = $x >> $y"}}, ReportTemplate: "replace `$$` with `$x >>= $y`", - WhereExpr: ir.FilterExpr{Line: 317, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 323, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 318, - SyntaxPatterns: []ir.PatternString{{Line: 318, Value: "$x = $x &^ $y"}}, + Line: 324, + SyntaxPatterns: []ir.PatternString{{Line: 324, Value: "$x = $x &^ $y"}}, ReportTemplate: "replace `$$` with `$x &^= $y`", - WhereExpr: ir.FilterExpr{Line: 318, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 324, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, }, }, { - Line: 325, + Line: 331, Name: "preferWriteByte", MatcherName: "m", DocTags: []string{"performance", "experimental", "opinionated"}, @@ -1143,45 +1167,45 @@ var PrecompiledRules = &ir.File{ DocBefore: "w.WriteRune('\\n')", DocAfter: "w.WriteByte('\\n')", Rules: []ir.Rule{{ - Line: 329, - SyntaxPatterns: []ir.PatternString{{Line: 329, Value: "$w.WriteRune($c)"}}, + Line: 335, + SyntaxPatterns: []ir.PatternString{{Line: 335, Value: "$w.WriteRune($c)"}}, ReportTemplate: "consider writing single byte rune $c with $w.WriteByte($c)", WhereExpr: ir.FilterExpr{ - Line: 330, + Line: 336, Op: ir.FilterAndOp, Src: "m[\"w\"].Type.Implements(\"io.ByteWriter\") && (m[\"c\"].Const && m[\"c\"].Value.Int() < runeSelf)", Args: []ir.FilterExpr{ { - Line: 330, + Line: 336, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.ByteWriter\")", Value: "w", - Args: []ir.FilterExpr{{Line: 330, Op: ir.FilterStringOp, Src: "\"io.ByteWriter\"", Value: "io.ByteWriter"}}, + Args: []ir.FilterExpr{{Line: 336, Op: ir.FilterStringOp, Src: "\"io.ByteWriter\"", Value: "io.ByteWriter"}}, }, { - Line: 330, + Line: 336, Op: ir.FilterAndOp, Src: "(m[\"c\"].Const && m[\"c\"].Value.Int() < runeSelf)", Args: []ir.FilterExpr{ { - Line: 330, + Line: 336, Op: ir.FilterVarConstOp, Src: "m[\"c\"].Const", Value: "c", }, { - Line: 330, + Line: 336, Op: ir.FilterLtOp, Src: "m[\"c\"].Value.Int() < runeSelf", Args: []ir.FilterExpr{ { - Line: 330, + Line: 336, Op: ir.FilterVarValueIntOp, Src: "m[\"c\"].Value.Int()", Value: "c", }, { - Line: 330, + Line: 336, Op: ir.FilterIntOp, Src: "runeSelf", Value: int64(128), @@ -1195,7 +1219,7 @@ var PrecompiledRules = &ir.File{ }}, }, { - Line: 338, + Line: 344, Name: "preferFprint", MatcherName: "m", DocTags: []string{"performance", "experimental"}, @@ -1204,139 +1228,139 @@ var PrecompiledRules = &ir.File{ DocAfter: "fmt.Fprintf(w, \"%x\", 10)", Rules: []ir.Rule{ { - Line: 339, - SyntaxPatterns: []ir.PatternString{{Line: 339, Value: "$w.Write([]byte(fmt.Sprint($*args)))"}}, + Line: 345, + SyntaxPatterns: []ir.PatternString{{Line: 345, Value: "$w.Write([]byte(fmt.Sprint($*args)))"}}, ReportTemplate: "fmt.Fprint($w, $args) should be preferred to the $$", SuggestTemplate: "fmt.Fprint($w, $args)", WhereExpr: ir.FilterExpr{ - Line: 340, + Line: 346, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.Writer\")", Value: "w", - Args: []ir.FilterExpr{{Line: 340, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, + Args: []ir.FilterExpr{{Line: 346, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, }, }, { - Line: 344, - SyntaxPatterns: []ir.PatternString{{Line: 344, Value: "$w.Write([]byte(fmt.Sprintf($*args)))"}}, + Line: 350, + SyntaxPatterns: []ir.PatternString{{Line: 350, Value: "$w.Write([]byte(fmt.Sprintf($*args)))"}}, ReportTemplate: "fmt.Fprintf($w, $args) should be preferred to the $$", SuggestTemplate: "fmt.Fprintf($w, $args)", WhereExpr: ir.FilterExpr{ - Line: 345, + Line: 351, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.Writer\")", Value: "w", - Args: []ir.FilterExpr{{Line: 345, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, + Args: []ir.FilterExpr{{Line: 351, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, }, }, { - Line: 349, - SyntaxPatterns: []ir.PatternString{{Line: 349, Value: "$w.Write([]byte(fmt.Sprintln($*args)))"}}, + Line: 355, + SyntaxPatterns: []ir.PatternString{{Line: 355, Value: "$w.Write([]byte(fmt.Sprintln($*args)))"}}, ReportTemplate: "fmt.Fprintln($w, $args) should be preferred to the $$", SuggestTemplate: "fmt.Fprintln($w, $args)", WhereExpr: ir.FilterExpr{ - Line: 350, + Line: 356, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.Writer\")", Value: "w", - Args: []ir.FilterExpr{{Line: 350, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, + Args: []ir.FilterExpr{{Line: 356, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, }, }, { - Line: 354, - SyntaxPatterns: []ir.PatternString{{Line: 354, Value: "io.WriteString($w, fmt.Sprint($*args))"}}, + Line: 360, + SyntaxPatterns: []ir.PatternString{{Line: 360, Value: "io.WriteString($w, fmt.Sprint($*args))"}}, ReportTemplate: "suggestion: fmt.Fprint($w, $args)", SuggestTemplate: "fmt.Fprint($w, $args)", }, { - Line: 355, - SyntaxPatterns: []ir.PatternString{{Line: 355, Value: "io.WriteString($w, fmt.Sprintf($*args))"}}, + Line: 361, + SyntaxPatterns: []ir.PatternString{{Line: 361, Value: "io.WriteString($w, fmt.Sprintf($*args))"}}, ReportTemplate: "suggestion: fmt.Fprintf($w, $args)", SuggestTemplate: "fmt.Fprintf($w, $args)", }, { - Line: 356, - SyntaxPatterns: []ir.PatternString{{Line: 356, Value: "io.WriteString($w, fmt.Sprintln($*args))"}}, + Line: 362, + SyntaxPatterns: []ir.PatternString{{Line: 362, Value: "io.WriteString($w, fmt.Sprintln($*args))"}}, ReportTemplate: "suggestion: fmt.Fprintln($w, $args)", SuggestTemplate: "fmt.Fprintln($w, $args)", }, { - Line: 358, - SyntaxPatterns: []ir.PatternString{{Line: 358, Value: "$w.WriteString(fmt.Sprint($*args))"}}, + Line: 364, + SyntaxPatterns: []ir.PatternString{{Line: 364, Value: "$w.WriteString(fmt.Sprint($*args))"}}, ReportTemplate: "suggestion: fmt.Fprint($w, $args)", SuggestTemplate: "fmt.Fprint($w, $args)", WhereExpr: ir.FilterExpr{ - Line: 359, + Line: 365, Op: ir.FilterAndOp, Src: "m[\"w\"].Type.Implements(\"io.Writer\") && m[\"w\"].Type.Implements(\"io.StringWriter\")", Args: []ir.FilterExpr{ { - Line: 359, + Line: 365, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.Writer\")", Value: "w", - Args: []ir.FilterExpr{{Line: 359, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, + Args: []ir.FilterExpr{{Line: 365, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, }, { - Line: 359, + Line: 365, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.StringWriter\")", Value: "w", - Args: []ir.FilterExpr{{Line: 359, Op: ir.FilterStringOp, Src: "\"io.StringWriter\"", Value: "io.StringWriter"}}, + Args: []ir.FilterExpr{{Line: 365, Op: ir.FilterStringOp, Src: "\"io.StringWriter\"", Value: "io.StringWriter"}}, }, }, }, }, { - Line: 361, - SyntaxPatterns: []ir.PatternString{{Line: 361, Value: "$w.WriteString(fmt.Sprintf($*args))"}}, + Line: 367, + SyntaxPatterns: []ir.PatternString{{Line: 367, Value: "$w.WriteString(fmt.Sprintf($*args))"}}, ReportTemplate: "suggestion: fmt.Fprintf($w, $args)", SuggestTemplate: "fmt.Fprintf($w, $args)", WhereExpr: ir.FilterExpr{ - Line: 362, + Line: 368, Op: ir.FilterAndOp, Src: "m[\"w\"].Type.Implements(\"io.Writer\") && m[\"w\"].Type.Implements(\"io.StringWriter\")", Args: []ir.FilterExpr{ { - Line: 362, + Line: 368, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.Writer\")", Value: "w", - Args: []ir.FilterExpr{{Line: 362, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, + Args: []ir.FilterExpr{{Line: 368, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, }, { - Line: 362, + Line: 368, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.StringWriter\")", Value: "w", - Args: []ir.FilterExpr{{Line: 362, Op: ir.FilterStringOp, Src: "\"io.StringWriter\"", Value: "io.StringWriter"}}, + Args: []ir.FilterExpr{{Line: 368, Op: ir.FilterStringOp, Src: "\"io.StringWriter\"", Value: "io.StringWriter"}}, }, }, }, }, { - Line: 364, - SyntaxPatterns: []ir.PatternString{{Line: 364, Value: "$w.WriteString(fmt.Sprintln($*args))"}}, + Line: 370, + SyntaxPatterns: []ir.PatternString{{Line: 370, Value: "$w.WriteString(fmt.Sprintln($*args))"}}, ReportTemplate: "suggestion: fmt.Fprintln($w, $args)", SuggestTemplate: "fmt.Fprintln($w, $args)", WhereExpr: ir.FilterExpr{ - Line: 365, + Line: 371, Op: ir.FilterAndOp, Src: "m[\"w\"].Type.Implements(\"io.Writer\") && m[\"w\"].Type.Implements(\"io.StringWriter\")", Args: []ir.FilterExpr{ { - Line: 365, + Line: 371, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.Writer\")", Value: "w", - Args: []ir.FilterExpr{{Line: 365, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, + Args: []ir.FilterExpr{{Line: 371, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, }, { - Line: 365, + Line: 371, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.StringWriter\")", Value: "w", - Args: []ir.FilterExpr{{Line: 365, Op: ir.FilterStringOp, Src: "\"io.StringWriter\"", Value: "io.StringWriter"}}, + Args: []ir.FilterExpr{{Line: 371, Op: ir.FilterStringOp, Src: "\"io.StringWriter\"", Value: "io.StringWriter"}}, }, }, }, @@ -1344,7 +1368,7 @@ var PrecompiledRules = &ir.File{ }, }, { - Line: 373, + Line: 379, Name: "dupArg", MatcherName: "m", DocTags: []string{"diagnostic"}, @@ -1353,62 +1377,62 @@ var PrecompiledRules = &ir.File{ DocAfter: "copy(dst, src)", Rules: []ir.Rule{ { - Line: 374, + Line: 380, SyntaxPatterns: []ir.PatternString{ - {Line: 374, Value: "$x.Equal($x)"}, - {Line: 374, Value: "$x.Equals($x)"}, - {Line: 374, Value: "$x.Compare($x)"}, - {Line: 374, Value: "$x.Cmp($x)"}, + {Line: 380, Value: "$x.Equal($x)"}, + {Line: 380, Value: "$x.Equals($x)"}, + {Line: 380, Value: "$x.Compare($x)"}, + {Line: 380, Value: "$x.Cmp($x)"}, }, ReportTemplate: "suspicious method call with the same argument and receiver", - WhereExpr: ir.FilterExpr{Line: 375, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 381, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 378, + Line: 384, SyntaxPatterns: []ir.PatternString{ - {Line: 378, Value: "copy($x, $x)"}, - {Line: 379, Value: "math.Max($x, $x)"}, - {Line: 380, Value: "math.Min($x, $x)"}, - {Line: 381, Value: "reflect.Copy($x, $x)"}, - {Line: 382, Value: "reflect.DeepEqual($x, $x)"}, - {Line: 383, Value: "strings.Contains($x, $x)"}, - {Line: 384, Value: "strings.Compare($x, $x)"}, - {Line: 385, Value: "strings.EqualFold($x, $x)"}, - {Line: 386, Value: "strings.HasPrefix($x, $x)"}, - {Line: 387, Value: "strings.HasSuffix($x, $x)"}, - {Line: 388, Value: "strings.Index($x, $x)"}, - {Line: 389, Value: "strings.LastIndex($x, $x)"}, - {Line: 390, Value: "strings.Split($x, $x)"}, - {Line: 391, Value: "strings.SplitAfter($x, $x)"}, - {Line: 392, Value: "strings.SplitAfterN($x, $x, $_)"}, - {Line: 393, Value: "strings.SplitN($x, $x, $_)"}, - {Line: 394, Value: "strings.Replace($_, $x, $x, $_)"}, - {Line: 395, Value: "strings.ReplaceAll($_, $x, $x)"}, - {Line: 396, Value: "bytes.Contains($x, $x)"}, - {Line: 397, Value: "bytes.Compare($x, $x)"}, - {Line: 398, Value: "bytes.Equal($x, $x)"}, - {Line: 399, Value: "bytes.EqualFold($x, $x)"}, - {Line: 400, Value: "bytes.HasPrefix($x, $x)"}, - {Line: 401, Value: "bytes.HasSuffix($x, $x)"}, - {Line: 402, Value: "bytes.Index($x, $x)"}, - {Line: 403, Value: "bytes.LastIndex($x, $x)"}, - {Line: 404, Value: "bytes.Split($x, $x)"}, - {Line: 405, Value: "bytes.SplitAfter($x, $x)"}, - {Line: 406, Value: "bytes.SplitAfterN($x, $x, $_)"}, - {Line: 407, Value: "bytes.SplitN($x, $x, $_)"}, - {Line: 408, Value: "bytes.Replace($_, $x, $x, $_)"}, - {Line: 409, Value: "bytes.ReplaceAll($_, $x, $x)"}, - {Line: 410, Value: "types.Identical($x, $x)"}, - {Line: 411, Value: "types.IdenticalIgnoreTags($x, $x)"}, - {Line: 412, Value: "draw.Draw($x, $_, $x, $_, $_)"}, + {Line: 384, Value: "copy($x, $x)"}, + {Line: 385, Value: "math.Max($x, $x)"}, + {Line: 386, Value: "math.Min($x, $x)"}, + {Line: 387, Value: "reflect.Copy($x, $x)"}, + {Line: 388, Value: "reflect.DeepEqual($x, $x)"}, + {Line: 389, Value: "strings.Contains($x, $x)"}, + {Line: 390, Value: "strings.Compare($x, $x)"}, + {Line: 391, Value: "strings.EqualFold($x, $x)"}, + {Line: 392, Value: "strings.HasPrefix($x, $x)"}, + {Line: 393, Value: "strings.HasSuffix($x, $x)"}, + {Line: 394, Value: "strings.Index($x, $x)"}, + {Line: 395, Value: "strings.LastIndex($x, $x)"}, + {Line: 396, Value: "strings.Split($x, $x)"}, + {Line: 397, Value: "strings.SplitAfter($x, $x)"}, + {Line: 398, Value: "strings.SplitAfterN($x, $x, $_)"}, + {Line: 399, Value: "strings.SplitN($x, $x, $_)"}, + {Line: 400, Value: "strings.Replace($_, $x, $x, $_)"}, + {Line: 401, Value: "strings.ReplaceAll($_, $x, $x)"}, + {Line: 402, Value: "bytes.Contains($x, $x)"}, + {Line: 403, Value: "bytes.Compare($x, $x)"}, + {Line: 404, Value: "bytes.Equal($x, $x)"}, + {Line: 405, Value: "bytes.EqualFold($x, $x)"}, + {Line: 406, Value: "bytes.HasPrefix($x, $x)"}, + {Line: 407, Value: "bytes.HasSuffix($x, $x)"}, + {Line: 408, Value: "bytes.Index($x, $x)"}, + {Line: 409, Value: "bytes.LastIndex($x, $x)"}, + {Line: 410, Value: "bytes.Split($x, $x)"}, + {Line: 411, Value: "bytes.SplitAfter($x, $x)"}, + {Line: 412, Value: "bytes.SplitAfterN($x, $x, $_)"}, + {Line: 413, Value: "bytes.SplitN($x, $x, $_)"}, + {Line: 414, Value: "bytes.Replace($_, $x, $x, $_)"}, + {Line: 415, Value: "bytes.ReplaceAll($_, $x, $x)"}, + {Line: 416, Value: "types.Identical($x, $x)"}, + {Line: 417, Value: "types.IdenticalIgnoreTags($x, $x)"}, + {Line: 418, Value: "draw.Draw($x, $_, $x, $_, $_)"}, }, ReportTemplate: "suspicious duplicated args in $$", - WhereExpr: ir.FilterExpr{Line: 413, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 419, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, }, }, { - Line: 421, + Line: 427, Name: "returnAfterHttpError", MatcherName: "m", DocTags: []string{"diagnostic", "experimental"}, @@ -1416,14 +1440,14 @@ var PrecompiledRules = &ir.File{ DocBefore: "if err != nil { http.Error(...); }", DocAfter: "if err != nil { http.Error(...); return; }", Rules: []ir.Rule{{ - Line: 422, - SyntaxPatterns: []ir.PatternString{{Line: 422, Value: "if $_ { $*_; http.Error($w, $err, $code) }"}}, + Line: 428, + SyntaxPatterns: []ir.PatternString{{Line: 428, Value: "if $_ { $*_; http.Error($w, $err, $code) }"}}, ReportTemplate: "Possibly return is missed after the http.Error call", LocationVar: "w", }}, }, { - Line: 431, + Line: 437, Name: "preferFilepathJoin", MatcherName: "m", DocTags: []string{"style", "experimental"}, @@ -1431,35 +1455,35 @@ var PrecompiledRules = &ir.File{ DocBefore: "x + string(os.PathSeparator) + y", DocAfter: "filepath.Join(x, y)", Rules: []ir.Rule{{ - Line: 432, - SyntaxPatterns: []ir.PatternString{{Line: 432, Value: "$x + string(os.PathSeparator) + $y"}}, + Line: 438, + SyntaxPatterns: []ir.PatternString{{Line: 438, Value: "$x + string(os.PathSeparator) + $y"}}, ReportTemplate: "filepath.Join($x, $y) should be preferred to the $$", SuggestTemplate: "filepath.Join($x, $y)", WhereExpr: ir.FilterExpr{ - Line: 433, + Line: 439, Op: ir.FilterAndOp, Src: "m[\"x\"].Type.Is(`string`) && m[\"y\"].Type.Is(`string`)", Args: []ir.FilterExpr{ { - Line: 433, + Line: 439, Op: ir.FilterVarTypeIsOp, Src: "m[\"x\"].Type.Is(`string`)", Value: "x", - Args: []ir.FilterExpr{{Line: 433, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + Args: []ir.FilterExpr{{Line: 439, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, }, { - Line: 433, + Line: 439, Op: ir.FilterVarTypeIsOp, Src: "m[\"y\"].Type.Is(`string`)", Value: "y", - Args: []ir.FilterExpr{{Line: 433, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + Args: []ir.FilterExpr{{Line: 439, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, }, }, }, }}, }, { - Line: 442, + Line: 448, Name: "preferStringWriter", MatcherName: "m", DocTags: []string{"performance", "experimental"}, @@ -1468,35 +1492,35 @@ var PrecompiledRules = &ir.File{ DocAfter: "w.WriteString(\"foo\")", Rules: []ir.Rule{ { - Line: 443, - SyntaxPatterns: []ir.PatternString{{Line: 443, Value: "$w.Write([]byte($s))"}}, + Line: 449, + SyntaxPatterns: []ir.PatternString{{Line: 449, Value: "$w.Write([]byte($s))"}}, ReportTemplate: "$w.WriteString($s) should be preferred to the $$", SuggestTemplate: "$w.WriteString($s)", WhereExpr: ir.FilterExpr{ - Line: 444, + Line: 450, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.StringWriter\")", Value: "w", - Args: []ir.FilterExpr{{Line: 444, Op: ir.FilterStringOp, Src: "\"io.StringWriter\"", Value: "io.StringWriter"}}, + Args: []ir.FilterExpr{{Line: 450, Op: ir.FilterStringOp, Src: "\"io.StringWriter\"", Value: "io.StringWriter"}}, }, }, { - Line: 448, - SyntaxPatterns: []ir.PatternString{{Line: 448, Value: "io.WriteString($w, $s)"}}, + Line: 454, + SyntaxPatterns: []ir.PatternString{{Line: 454, Value: "io.WriteString($w, $s)"}}, ReportTemplate: "$w.WriteString($s) should be preferred to the $$", SuggestTemplate: "$w.WriteString($s)", WhereExpr: ir.FilterExpr{ - Line: 449, + Line: 455, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.StringWriter\")", Value: "w", - Args: []ir.FilterExpr{{Line: 449, Op: ir.FilterStringOp, Src: "\"io.StringWriter\"", Value: "io.StringWriter"}}, + Args: []ir.FilterExpr{{Line: 455, Op: ir.FilterStringOp, Src: "\"io.StringWriter\"", Value: "io.StringWriter"}}, }, }, }, }, { - Line: 458, + Line: 464, Name: "sliceClear", MatcherName: "m", DocTags: []string{"performance", "experimental"}, @@ -1504,22 +1528,22 @@ var PrecompiledRules = &ir.File{ DocBefore: "for i := 0; i < len(buf); i++ { buf[i] = 0 }", DocAfter: "for i := range buf { buf[i] = 0 }", Rules: []ir.Rule{{ - Line: 459, - SyntaxPatterns: []ir.PatternString{{Line: 459, Value: "for $i := 0; $i < len($xs); $i++ { $xs[$i] = $zero }"}}, + Line: 465, + SyntaxPatterns: []ir.PatternString{{Line: 465, Value: "for $i := 0; $i < len($xs); $i++ { $xs[$i] = $zero }"}}, ReportTemplate: "rewrite as for-range so compiler can recognize this pattern", WhereExpr: ir.FilterExpr{ - Line: 460, + Line: 466, Op: ir.FilterEqOp, Src: "m[\"zero\"].Value.Int() == 0", Args: []ir.FilterExpr{ { - Line: 460, + Line: 466, Op: ir.FilterVarValueIntOp, Src: "m[\"zero\"].Value.Int()", Value: "zero", }, { - Line: 460, + Line: 466, Op: ir.FilterIntOp, Src: "0", Value: int64(0), @@ -1529,7 +1553,7 @@ var PrecompiledRules = &ir.File{ }}, }, { - Line: 468, + Line: 474, Name: "syncMapLoadAndDelete", MatcherName: "m", DocTags: []string{"diagnostic", "experimental"}, @@ -1537,33 +1561,33 @@ var PrecompiledRules = &ir.File{ DocBefore: "v, ok := m.Load(k); if ok { m.Delete($k); f(v); }", DocAfter: "v, deleted := m.LoadAndDelete(k); if deleted { f(v) }", Rules: []ir.Rule{{ - Line: 469, - SyntaxPatterns: []ir.PatternString{{Line: 469, Value: "$_, $ok := $m.Load($k); if $ok { $m.Delete($k); $*_ }"}}, + Line: 475, + SyntaxPatterns: []ir.PatternString{{Line: 475, Value: "$_, $ok := $m.Load($k); if $ok { $m.Delete($k); $*_ }"}}, ReportTemplate: "use $m.LoadAndDelete to perform load+delete operations atomically", WhereExpr: ir.FilterExpr{ - Line: 470, + Line: 476, Op: ir.FilterAndOp, Src: "m.GoVersion().GreaterEqThan(\"1.15\") &&\n\tm[\"m\"].Type.Is(`*sync.Map`)", Args: []ir.FilterExpr{ { - Line: 470, + Line: 476, Op: ir.FilterGoVersionGreaterEqThanOp, Src: "m.GoVersion().GreaterEqThan(\"1.15\")", Value: "1.15", }, { - Line: 471, + Line: 477, Op: ir.FilterVarTypeIsOp, Src: "m[\"m\"].Type.Is(`*sync.Map`)", Value: "m", - Args: []ir.FilterExpr{{Line: 471, Op: ir.FilterStringOp, Src: "`*sync.Map`", Value: "*sync.Map"}}, + Args: []ir.FilterExpr{{Line: 477, Op: ir.FilterStringOp, Src: "`*sync.Map`", Value: "*sync.Map"}}, }, }, }, }}, }, { - Line: 479, + Line: 485, Name: "sprintfQuotedString", MatcherName: "m", DocTags: []string{"diagnostic", "experimental"}, @@ -1571,34 +1595,34 @@ var PrecompiledRules = &ir.File{ DocBefore: "fmt.Sprintf(`\"%s\"`, s)", DocAfter: "fmt.Sprintf(`%q`, s)", Rules: []ir.Rule{{ - Line: 480, - SyntaxPatterns: []ir.PatternString{{Line: 480, Value: "fmt.Sprintf($s, $*_)"}}, + Line: 486, + SyntaxPatterns: []ir.PatternString{{Line: 486, Value: "fmt.Sprintf($s, $*_)"}}, ReportTemplate: "use %q instead of \"%s\" for quoted strings", WhereExpr: ir.FilterExpr{ - Line: 481, + Line: 487, Op: ir.FilterOrOp, Src: "m[\"s\"].Text.Matches(\"^`.*\\\"%s\\\".*`$\") ||\n\tm[\"s\"].Text.Matches(`^\".*\\\\\"%s\\\\\".*\"$`)", Args: []ir.FilterExpr{ { - Line: 481, + Line: 487, Op: ir.FilterVarTextMatchesOp, Src: "m[\"s\"].Text.Matches(\"^`.*\\\"%s\\\".*`$\")", Value: "s", - Args: []ir.FilterExpr{{Line: 481, Op: ir.FilterStringOp, Src: "\"^`.*\\\"%s\\\".*`$\"", Value: "^`.*\"%s\".*`$"}}, + Args: []ir.FilterExpr{{Line: 487, Op: ir.FilterStringOp, Src: "\"^`.*\\\"%s\\\".*`$\"", Value: "^`.*\"%s\".*`$"}}, }, { - Line: 482, + Line: 488, Op: ir.FilterVarTextMatchesOp, Src: "m[\"s\"].Text.Matches(`^\".*\\\\\"%s\\\\\".*\"$`)", Value: "s", - Args: []ir.FilterExpr{{Line: 482, Op: ir.FilterStringOp, Src: "`^\".*\\\\\"%s\\\\\".*\"$`", Value: "^\".*\\\\\"%s\\\\\".*\"$"}}, + Args: []ir.FilterExpr{{Line: 488, Op: ir.FilterStringOp, Src: "`^\".*\\\\\"%s\\\\\".*\"$`", Value: "^\".*\\\\\"%s\\\\\".*\"$"}}, }, }, }, }}, }, { - Line: 490, + Line: 496, Name: "offBy1", MatcherName: "m", DocTags: []string{"diagnostic"}, @@ -1607,80 +1631,80 @@ var PrecompiledRules = &ir.File{ DocAfter: "xs[len(xs)-1]", Rules: []ir.Rule{ { - Line: 491, - SyntaxPatterns: []ir.PatternString{{Line: 491, Value: "$x[len($x)]"}}, + Line: 497, + SyntaxPatterns: []ir.PatternString{{Line: 497, Value: "$x[len($x)]"}}, ReportTemplate: "index expr always panics; maybe you wanted $x[len($x)-1]?", SuggestTemplate: "$x[len($x)-1]", WhereExpr: ir.FilterExpr{ - Line: 492, + Line: 498, Op: ir.FilterAndOp, Src: "m[\"x\"].Pure && m[\"x\"].Type.Is(`[]$_`)", Args: []ir.FilterExpr{ - {Line: 492, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + {Line: 498, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, { - Line: 492, + Line: 498, Op: ir.FilterVarTypeIsOp, Src: "m[\"x\"].Type.Is(`[]$_`)", Value: "x", - Args: []ir.FilterExpr{{Line: 492, Op: ir.FilterStringOp, Src: "`[]$_`", Value: "[]$_"}}, + Args: []ir.FilterExpr{{Line: 498, Op: ir.FilterStringOp, Src: "`[]$_`", Value: "[]$_"}}, }, }, }, }, { - Line: 499, + Line: 505, SyntaxPatterns: []ir.PatternString{ - {Line: 500, Value: "$i := strings.Index($s, $_); $_ := $slicing[$i:]"}, - {Line: 501, Value: "$i := strings.Index($s, $_); $_ = $slicing[$i:]"}, - {Line: 502, Value: "$i := bytes.Index($s, $_); $_ := $slicing[$i:]"}, - {Line: 503, Value: "$i := bytes.Index($s, $_); $_ = $slicing[$i:]"}, + {Line: 506, Value: "$i := strings.Index($s, $_); $_ := $slicing[$i:]"}, + {Line: 507, Value: "$i := strings.Index($s, $_); $_ = $slicing[$i:]"}, + {Line: 508, Value: "$i := bytes.Index($s, $_); $_ := $slicing[$i:]"}, + {Line: 509, Value: "$i := bytes.Index($s, $_); $_ = $slicing[$i:]"}, }, ReportTemplate: "Index() can return -1; maybe you wanted to do $s[$i+1:]", WhereExpr: ir.FilterExpr{ - Line: 504, + Line: 510, Op: ir.FilterEqOp, Src: "m[\"s\"].Text == m[\"slicing\"].Text", Args: []ir.FilterExpr{ - {Line: 504, Op: ir.FilterVarTextOp, Src: "m[\"s\"].Text", Value: "s"}, - {Line: 504, Op: ir.FilterVarTextOp, Src: "m[\"slicing\"].Text", Value: "slicing"}, + {Line: 510, Op: ir.FilterVarTextOp, Src: "m[\"s\"].Text", Value: "s"}, + {Line: 510, Op: ir.FilterVarTextOp, Src: "m[\"slicing\"].Text", Value: "slicing"}, }, }, LocationVar: "slicing", }, { - Line: 508, + Line: 514, SyntaxPatterns: []ir.PatternString{ - {Line: 509, Value: "$i := strings.Index($s, $_); $_ := $slicing[:$i]"}, - {Line: 510, Value: "$i := strings.Index($s, $_); $_ = $slicing[:$i]"}, - {Line: 511, Value: "$i := bytes.Index($s, $_); $_ := $slicing[:$i]"}, - {Line: 512, Value: "$i := bytes.Index($s, $_); $_ = $slicing[:$i]"}, + {Line: 515, Value: "$i := strings.Index($s, $_); $_ := $slicing[:$i]"}, + {Line: 516, Value: "$i := strings.Index($s, $_); $_ = $slicing[:$i]"}, + {Line: 517, Value: "$i := bytes.Index($s, $_); $_ := $slicing[:$i]"}, + {Line: 518, Value: "$i := bytes.Index($s, $_); $_ = $slicing[:$i]"}, }, ReportTemplate: "Index() can return -1; maybe you wanted to do $s[:$i+1]", WhereExpr: ir.FilterExpr{ - Line: 513, + Line: 519, Op: ir.FilterEqOp, Src: "m[\"s\"].Text == m[\"slicing\"].Text", Args: []ir.FilterExpr{ - {Line: 513, Op: ir.FilterVarTextOp, Src: "m[\"s\"].Text", Value: "s"}, - {Line: 513, Op: ir.FilterVarTextOp, Src: "m[\"slicing\"].Text", Value: "slicing"}, + {Line: 519, Op: ir.FilterVarTextOp, Src: "m[\"s\"].Text", Value: "s"}, + {Line: 519, Op: ir.FilterVarTextOp, Src: "m[\"slicing\"].Text", Value: "slicing"}, }, }, LocationVar: "slicing", }, { - Line: 517, + Line: 523, SyntaxPatterns: []ir.PatternString{ - {Line: 518, Value: "$s[strings.Index($s, $_):]"}, - {Line: 519, Value: "$s[:strings.Index($s, $_)]"}, - {Line: 520, Value: "$s[bytes.Index($s, $_):]"}, - {Line: 521, Value: "$s[:bytes.Index($s, $_)]"}, + {Line: 524, Value: "$s[strings.Index($s, $_):]"}, + {Line: 525, Value: "$s[:strings.Index($s, $_)]"}, + {Line: 526, Value: "$s[bytes.Index($s, $_):]"}, + {Line: 527, Value: "$s[:bytes.Index($s, $_)]"}, }, ReportTemplate: "Index() can return -1; maybe you wanted to do Index()+1", }, }, }, { - Line: 529, + Line: 535, Name: "unslice", MatcherName: "m", DocTags: []string{"style"}, @@ -1688,35 +1712,35 @@ var PrecompiledRules = &ir.File{ DocBefore: "copy(b[:], values...)", DocAfter: "copy(b, values...)", Rules: []ir.Rule{{ - Line: 530, - SyntaxPatterns: []ir.PatternString{{Line: 530, Value: "$s[:]"}}, + Line: 536, + SyntaxPatterns: []ir.PatternString{{Line: 536, Value: "$s[:]"}}, ReportTemplate: "could simplify $$ to $s", SuggestTemplate: "$s", WhereExpr: ir.FilterExpr{ - Line: 531, + Line: 537, Op: ir.FilterOrOp, Src: "m[\"s\"].Type.Is(`string`) || m[\"s\"].Type.Is(`[]$_`)", Args: []ir.FilterExpr{ { - Line: 531, + Line: 537, Op: ir.FilterVarTypeIsOp, Src: "m[\"s\"].Type.Is(`string`)", Value: "s", - Args: []ir.FilterExpr{{Line: 531, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + Args: []ir.FilterExpr{{Line: 537, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, }, { - Line: 531, + Line: 537, Op: ir.FilterVarTypeIsOp, Src: "m[\"s\"].Type.Is(`[]$_`)", Value: "s", - Args: []ir.FilterExpr{{Line: 531, Op: ir.FilterStringOp, Src: "`[]$_`", Value: "[]$_"}}, + Args: []ir.FilterExpr{{Line: 537, Op: ir.FilterStringOp, Src: "`[]$_`", Value: "[]$_"}}, }, }, }, }}, }, { - Line: 540, + Line: 546, Name: "yodaStyleExpr", MatcherName: "m", DocTags: []string{"style", "experimental"}, @@ -1725,105 +1749,105 @@ var PrecompiledRules = &ir.File{ DocAfter: "return ptr != nil", Rules: []ir.Rule{ { - Line: 541, - SyntaxPatterns: []ir.PatternString{{Line: 541, Value: "$constval != $x"}}, + Line: 547, + SyntaxPatterns: []ir.PatternString{{Line: 547, Value: "$constval != $x"}}, ReportTemplate: "consider to change order in expression to $x != $constval", WhereExpr: ir.FilterExpr{ - Line: 541, + Line: 547, Op: ir.FilterAndOp, Src: "m[\"constval\"].Node.Is(`BasicLit`) && !m[\"x\"].Node.Is(`BasicLit`)", Args: []ir.FilterExpr{ { - Line: 541, + Line: 547, Op: ir.FilterVarNodeIsOp, Src: "m[\"constval\"].Node.Is(`BasicLit`)", Value: "constval", - Args: []ir.FilterExpr{{Line: 541, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, + Args: []ir.FilterExpr{{Line: 547, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, }, { - Line: 541, + Line: 547, Op: ir.FilterNotOp, Src: "!m[\"x\"].Node.Is(`BasicLit`)", Args: []ir.FilterExpr{{ - Line: 541, + Line: 547, Op: ir.FilterVarNodeIsOp, Src: "m[\"x\"].Node.Is(`BasicLit`)", Value: "x", - Args: []ir.FilterExpr{{Line: 541, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, + Args: []ir.FilterExpr{{Line: 547, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, }}, }, }, }, }, { - Line: 543, - SyntaxPatterns: []ir.PatternString{{Line: 543, Value: "$constval == $x"}}, + Line: 549, + SyntaxPatterns: []ir.PatternString{{Line: 549, Value: "$constval == $x"}}, ReportTemplate: "consider to change order in expression to $x == $constval", WhereExpr: ir.FilterExpr{ - Line: 543, + Line: 549, Op: ir.FilterAndOp, Src: "m[\"constval\"].Node.Is(`BasicLit`) && !m[\"x\"].Node.Is(`BasicLit`)", Args: []ir.FilterExpr{ { - Line: 543, + Line: 549, Op: ir.FilterVarNodeIsOp, Src: "m[\"constval\"].Node.Is(`BasicLit`)", Value: "constval", - Args: []ir.FilterExpr{{Line: 543, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, + Args: []ir.FilterExpr{{Line: 549, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, }, { - Line: 543, + Line: 549, Op: ir.FilterNotOp, Src: "!m[\"x\"].Node.Is(`BasicLit`)", Args: []ir.FilterExpr{{ - Line: 543, + Line: 549, Op: ir.FilterVarNodeIsOp, Src: "m[\"x\"].Node.Is(`BasicLit`)", Value: "x", - Args: []ir.FilterExpr{{Line: 543, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, + Args: []ir.FilterExpr{{Line: 549, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, }}, }, }, }, }, { - Line: 546, - SyntaxPatterns: []ir.PatternString{{Line: 546, Value: "nil != $x"}}, + Line: 552, + SyntaxPatterns: []ir.PatternString{{Line: 552, Value: "nil != $x"}}, ReportTemplate: "consider to change order in expression to $x != nil", WhereExpr: ir.FilterExpr{ - Line: 546, + Line: 552, Op: ir.FilterNotOp, Src: "!m[\"x\"].Node.Is(`BasicLit`)", Args: []ir.FilterExpr{{ - Line: 546, + Line: 552, Op: ir.FilterVarNodeIsOp, Src: "m[\"x\"].Node.Is(`BasicLit`)", Value: "x", - Args: []ir.FilterExpr{{Line: 546, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, + Args: []ir.FilterExpr{{Line: 552, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, }}, }, }, { - Line: 548, - SyntaxPatterns: []ir.PatternString{{Line: 548, Value: "nil == $x"}}, + Line: 554, + SyntaxPatterns: []ir.PatternString{{Line: 554, Value: "nil == $x"}}, ReportTemplate: "consider to change order in expression to $x == nil", WhereExpr: ir.FilterExpr{ - Line: 548, + Line: 554, Op: ir.FilterNotOp, Src: "!m[\"x\"].Node.Is(`BasicLit`)", Args: []ir.FilterExpr{{ - Line: 548, + Line: 554, Op: ir.FilterVarNodeIsOp, Src: "m[\"x\"].Node.Is(`BasicLit`)", Value: "x", - Args: []ir.FilterExpr{{Line: 548, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, + Args: []ir.FilterExpr{{Line: 554, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, }}, }, }, }, }, { - Line: 556, + Line: 562, Name: "equalFold", MatcherName: "m", DocTags: []string{"performance", "experimental"}, @@ -1832,114 +1856,114 @@ var PrecompiledRules = &ir.File{ DocAfter: "strings.EqualFold(x, y)", Rules: []ir.Rule{ { - Line: 565, + Line: 571, SyntaxPatterns: []ir.PatternString{ - {Line: 566, Value: "strings.ToLower($x) == $y"}, - {Line: 567, Value: "strings.ToLower($x) == strings.ToLower($y)"}, - {Line: 568, Value: "$x == strings.ToLower($y)"}, - {Line: 569, Value: "strings.ToUpper($x) == $y"}, - {Line: 570, Value: "strings.ToUpper($x) == strings.ToUpper($y)"}, - {Line: 571, Value: "$x == strings.ToUpper($y)"}, + {Line: 572, Value: "strings.ToLower($x) == $y"}, + {Line: 573, Value: "strings.ToLower($x) == strings.ToLower($y)"}, + {Line: 574, Value: "$x == strings.ToLower($y)"}, + {Line: 575, Value: "strings.ToUpper($x) == $y"}, + {Line: 576, Value: "strings.ToUpper($x) == strings.ToUpper($y)"}, + {Line: 577, Value: "$x == strings.ToUpper($y)"}, }, ReportTemplate: "consider replacing with strings.EqualFold($x, $y)", SuggestTemplate: "strings.EqualFold($x, $y)", WhereExpr: ir.FilterExpr{ - Line: 572, + Line: 578, Op: ir.FilterAndOp, Src: "m[\"x\"].Pure && m[\"y\"].Pure && m[\"x\"].Text != m[\"y\"].Text", Args: []ir.FilterExpr{ { - Line: 572, + Line: 578, Op: ir.FilterAndOp, Src: "m[\"x\"].Pure && m[\"y\"].Pure", Args: []ir.FilterExpr{ - {Line: 572, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, - {Line: 572, Op: ir.FilterVarPureOp, Src: "m[\"y\"].Pure", Value: "y"}, + {Line: 578, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + {Line: 578, Op: ir.FilterVarPureOp, Src: "m[\"y\"].Pure", Value: "y"}, }, }, { - Line: 572, + Line: 578, Op: ir.FilterNeqOp, Src: "m[\"x\"].Text != m[\"y\"].Text", Args: []ir.FilterExpr{ - {Line: 572, Op: ir.FilterVarTextOp, Src: "m[\"x\"].Text", Value: "x"}, - {Line: 572, Op: ir.FilterVarTextOp, Src: "m[\"y\"].Text", Value: "y"}, + {Line: 578, Op: ir.FilterVarTextOp, Src: "m[\"x\"].Text", Value: "x"}, + {Line: 578, Op: ir.FilterVarTextOp, Src: "m[\"y\"].Text", Value: "y"}, }, }, }, }, }, { - Line: 577, + Line: 583, SyntaxPatterns: []ir.PatternString{ - {Line: 578, Value: "strings.ToLower($x) != $y"}, - {Line: 579, Value: "strings.ToLower($x) != strings.ToLower($y)"}, - {Line: 580, Value: "$x != strings.ToLower($y)"}, - {Line: 581, Value: "strings.ToUpper($x) != $y"}, - {Line: 582, Value: "strings.ToUpper($x) != strings.ToUpper($y)"}, - {Line: 583, Value: "$x != strings.ToUpper($y)"}, + {Line: 584, Value: "strings.ToLower($x) != $y"}, + {Line: 585, Value: "strings.ToLower($x) != strings.ToLower($y)"}, + {Line: 586, Value: "$x != strings.ToLower($y)"}, + {Line: 587, Value: "strings.ToUpper($x) != $y"}, + {Line: 588, Value: "strings.ToUpper($x) != strings.ToUpper($y)"}, + {Line: 589, Value: "$x != strings.ToUpper($y)"}, }, ReportTemplate: "consider replacing with !strings.EqualFold($x, $y)", SuggestTemplate: "!strings.EqualFold($x, $y)", WhereExpr: ir.FilterExpr{ - Line: 584, + Line: 590, Op: ir.FilterAndOp, Src: "m[\"x\"].Pure && m[\"y\"].Pure && m[\"x\"].Text != m[\"y\"].Text", Args: []ir.FilterExpr{ { - Line: 584, + Line: 590, Op: ir.FilterAndOp, Src: "m[\"x\"].Pure && m[\"y\"].Pure", Args: []ir.FilterExpr{ - {Line: 584, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, - {Line: 584, Op: ir.FilterVarPureOp, Src: "m[\"y\"].Pure", Value: "y"}, + {Line: 590, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + {Line: 590, Op: ir.FilterVarPureOp, Src: "m[\"y\"].Pure", Value: "y"}, }, }, { - Line: 584, + Line: 590, Op: ir.FilterNeqOp, Src: "m[\"x\"].Text != m[\"y\"].Text", Args: []ir.FilterExpr{ - {Line: 584, Op: ir.FilterVarTextOp, Src: "m[\"x\"].Text", Value: "x"}, - {Line: 584, Op: ir.FilterVarTextOp, Src: "m[\"y\"].Text", Value: "y"}, + {Line: 590, Op: ir.FilterVarTextOp, Src: "m[\"x\"].Text", Value: "x"}, + {Line: 590, Op: ir.FilterVarTextOp, Src: "m[\"y\"].Text", Value: "y"}, }, }, }, }, }, { - Line: 589, + Line: 595, SyntaxPatterns: []ir.PatternString{ - {Line: 590, Value: "bytes.Equal(bytes.ToLower($x), $y)"}, - {Line: 591, Value: "bytes.Equal(bytes.ToLower($x), bytes.ToLower($y))"}, - {Line: 592, Value: "bytes.Equal($x, bytes.ToLower($y))"}, - {Line: 593, Value: "bytes.Equal(bytes.ToUpper($x), $y)"}, - {Line: 594, Value: "bytes.Equal(bytes.ToUpper($x), bytes.ToUpper($y))"}, - {Line: 595, Value: "bytes.Equal($x, bytes.ToUpper($y))"}, + {Line: 596, Value: "bytes.Equal(bytes.ToLower($x), $y)"}, + {Line: 597, Value: "bytes.Equal(bytes.ToLower($x), bytes.ToLower($y))"}, + {Line: 598, Value: "bytes.Equal($x, bytes.ToLower($y))"}, + {Line: 599, Value: "bytes.Equal(bytes.ToUpper($x), $y)"}, + {Line: 600, Value: "bytes.Equal(bytes.ToUpper($x), bytes.ToUpper($y))"}, + {Line: 601, Value: "bytes.Equal($x, bytes.ToUpper($y))"}, }, ReportTemplate: "consider replacing with bytes.EqualFold($x, $y)", SuggestTemplate: "bytes.EqualFold($x, $y)", WhereExpr: ir.FilterExpr{ - Line: 596, + Line: 602, Op: ir.FilterAndOp, Src: "m[\"x\"].Pure && m[\"y\"].Pure && m[\"x\"].Text != m[\"y\"].Text", Args: []ir.FilterExpr{ { - Line: 596, + Line: 602, Op: ir.FilterAndOp, Src: "m[\"x\"].Pure && m[\"y\"].Pure", Args: []ir.FilterExpr{ - {Line: 596, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, - {Line: 596, Op: ir.FilterVarPureOp, Src: "m[\"y\"].Pure", Value: "y"}, + {Line: 602, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + {Line: 602, Op: ir.FilterVarPureOp, Src: "m[\"y\"].Pure", Value: "y"}, }, }, { - Line: 596, + Line: 602, Op: ir.FilterNeqOp, Src: "m[\"x\"].Text != m[\"y\"].Text", Args: []ir.FilterExpr{ - {Line: 596, Op: ir.FilterVarTextOp, Src: "m[\"x\"].Text", Value: "x"}, - {Line: 596, Op: ir.FilterVarTextOp, Src: "m[\"y\"].Text", Value: "y"}, + {Line: 602, Op: ir.FilterVarTextOp, Src: "m[\"x\"].Text", Value: "x"}, + {Line: 602, Op: ir.FilterVarTextOp, Src: "m[\"y\"].Text", Value: "y"}, }, }, }, @@ -1948,7 +1972,7 @@ var PrecompiledRules = &ir.File{ }, }, { - Line: 605, + Line: 611, Name: "argOrder", MatcherName: "m", DocTags: []string{"diagnostic"}, @@ -1956,45 +1980,45 @@ var PrecompiledRules = &ir.File{ DocBefore: "strings.HasPrefix(\"#\", userpass)", DocAfter: "strings.HasPrefix(userpass, \"#\")", Rules: []ir.Rule{{ - Line: 606, + Line: 612, SyntaxPatterns: []ir.PatternString{ - {Line: 607, Value: "strings.HasPrefix($lit, $s)"}, - {Line: 608, Value: "bytes.HasPrefix($lit, $s)"}, - {Line: 609, Value: "strings.HasSuffix($lit, $s)"}, - {Line: 610, Value: "bytes.HasSuffix($lit, $s)"}, - {Line: 611, Value: "strings.Contains($lit, $s)"}, - {Line: 612, Value: "bytes.Contains($lit, $s)"}, - {Line: 613, Value: "strings.TrimPrefix($lit, $s)"}, - {Line: 614, Value: "bytes.TrimPrefix($lit, $s)"}, - {Line: 615, Value: "strings.TrimSuffix($lit, $s)"}, - {Line: 616, Value: "bytes.TrimSuffix($lit, $s)"}, - {Line: 617, Value: "strings.Split($lit, $s)"}, - {Line: 618, Value: "bytes.Split($lit, $s)"}, + {Line: 613, Value: "strings.HasPrefix($lit, $s)"}, + {Line: 614, Value: "bytes.HasPrefix($lit, $s)"}, + {Line: 615, Value: "strings.HasSuffix($lit, $s)"}, + {Line: 616, Value: "bytes.HasSuffix($lit, $s)"}, + {Line: 617, Value: "strings.Contains($lit, $s)"}, + {Line: 618, Value: "bytes.Contains($lit, $s)"}, + {Line: 619, Value: "strings.TrimPrefix($lit, $s)"}, + {Line: 620, Value: "bytes.TrimPrefix($lit, $s)"}, + {Line: 621, Value: "strings.TrimSuffix($lit, $s)"}, + {Line: 622, Value: "bytes.TrimSuffix($lit, $s)"}, + {Line: 623, Value: "strings.Split($lit, $s)"}, + {Line: 624, Value: "bytes.Split($lit, $s)"}, }, ReportTemplate: "$lit and $s arguments order looks reversed", WhereExpr: ir.FilterExpr{ - Line: 619, + Line: 625, Op: ir.FilterAndOp, Src: "(m[\"lit\"].Const || m[\"lit\"].ConstSlice) &&\n\t!(m[\"s\"].Const || m[\"s\"].ConstSlice) &&\n\t!m[\"lit\"].Node.Is(`Ident`)", Args: []ir.FilterExpr{ { - Line: 619, + Line: 625, Op: ir.FilterAndOp, Src: "(m[\"lit\"].Const || m[\"lit\"].ConstSlice) &&\n\t!(m[\"s\"].Const || m[\"s\"].ConstSlice)", Args: []ir.FilterExpr{ { - Line: 619, + Line: 625, Op: ir.FilterOrOp, Src: "(m[\"lit\"].Const || m[\"lit\"].ConstSlice)", Args: []ir.FilterExpr{ { - Line: 619, + Line: 625, Op: ir.FilterVarConstOp, Src: "m[\"lit\"].Const", Value: "lit", }, { - Line: 619, + Line: 625, Op: ir.FilterVarConstSliceOp, Src: "m[\"lit\"].ConstSlice", Value: "lit", @@ -2002,22 +2026,22 @@ var PrecompiledRules = &ir.File{ }, }, { - Line: 620, + Line: 626, Op: ir.FilterNotOp, Src: "!(m[\"s\"].Const || m[\"s\"].ConstSlice)", Args: []ir.FilterExpr{{ - Line: 620, + Line: 626, Op: ir.FilterOrOp, Src: "(m[\"s\"].Const || m[\"s\"].ConstSlice)", Args: []ir.FilterExpr{ { - Line: 620, + Line: 626, Op: ir.FilterVarConstOp, Src: "m[\"s\"].Const", Value: "s", }, { - Line: 620, + Line: 626, Op: ir.FilterVarConstSliceOp, Src: "m[\"s\"].ConstSlice", Value: "s", @@ -2028,15 +2052,15 @@ var PrecompiledRules = &ir.File{ }, }, { - Line: 621, + Line: 627, Op: ir.FilterNotOp, Src: "!m[\"lit\"].Node.Is(`Ident`)", Args: []ir.FilterExpr{{ - Line: 621, + Line: 627, Op: ir.FilterVarNodeIsOp, Src: "m[\"lit\"].Node.Is(`Ident`)", Value: "lit", - Args: []ir.FilterExpr{{Line: 621, Op: ir.FilterStringOp, Src: "`Ident`", Value: "Ident"}}, + Args: []ir.FilterExpr{{Line: 627, Op: ir.FilterStringOp, Src: "`Ident`", Value: "Ident"}}, }}, }, }, @@ -2044,7 +2068,7 @@ var PrecompiledRules = &ir.File{ }}, }, { - Line: 629, + Line: 635, Name: "stringConcatSimplify", MatcherName: "m", DocTags: []string{"style", "experimental"}, @@ -2053,27 +2077,27 @@ var PrecompiledRules = &ir.File{ DocAfter: "x + \"_\" + y", Rules: []ir.Rule{ { - Line: 630, - SyntaxPatterns: []ir.PatternString{{Line: 630, Value: "strings.Join([]string{$x, $y}, \"\")"}}, + Line: 636, + SyntaxPatterns: []ir.PatternString{{Line: 636, Value: "strings.Join([]string{$x, $y}, \"\")"}}, ReportTemplate: "suggestion: $x + $y", SuggestTemplate: "$x + $y", }, { - Line: 631, - SyntaxPatterns: []ir.PatternString{{Line: 631, Value: "strings.Join([]string{$x, $y, $z}, \"\")"}}, + Line: 637, + SyntaxPatterns: []ir.PatternString{{Line: 637, Value: "strings.Join([]string{$x, $y, $z}, \"\")"}}, ReportTemplate: "suggestion: $x + $y + $z", SuggestTemplate: "$x + $y + $z", }, { - Line: 632, - SyntaxPatterns: []ir.PatternString{{Line: 632, Value: "strings.Join([]string{$x, $y}, $glue)"}}, + Line: 638, + SyntaxPatterns: []ir.PatternString{{Line: 638, Value: "strings.Join([]string{$x, $y}, $glue)"}}, ReportTemplate: "suggestion: $x + $glue + $y", SuggestTemplate: "$x + $glue + $y", }, }, }, { - Line: 639, + Line: 645, Name: "timeExprSimplify", MatcherName: "m", DocTags: []string{"style", "experimental"}, @@ -2082,39 +2106,39 @@ var PrecompiledRules = &ir.File{ DocAfter: "t.UnixMilli()", Rules: []ir.Rule{ { - Line: 644, - SyntaxPatterns: []ir.PatternString{{Line: 644, Value: "$t.Unix() / 1000"}}, + Line: 650, + SyntaxPatterns: []ir.PatternString{{Line: 650, Value: "$t.Unix() / 1000"}}, ReportTemplate: "use $t.UnixMilli() instead of $$", SuggestTemplate: "$t.UnixMilli()", WhereExpr: ir.FilterExpr{ - Line: 645, + Line: 651, Op: ir.FilterAndOp, Src: "m.GoVersion().GreaterEqThan(\"1.17\") && isTime(m[\"t\"])", Args: []ir.FilterExpr{ { - Line: 645, + Line: 651, Op: ir.FilterGoVersionGreaterEqThanOp, Src: "m.GoVersion().GreaterEqThan(\"1.17\")", Value: "1.17", }, { - Line: 645, + Line: 651, Op: ir.FilterOrOp, Src: "isTime(m[\"t\"])", Args: []ir.FilterExpr{ { - Line: 645, + Line: 651, Op: ir.FilterVarTypeIsOp, Src: "m[\"t\"].Type.Is(`time.Time`)", Value: "t", - Args: []ir.FilterExpr{{Line: 641, Op: ir.FilterStringOp, Src: "`time.Time`", Value: "time.Time"}}, + Args: []ir.FilterExpr{{Line: 647, Op: ir.FilterStringOp, Src: "`time.Time`", Value: "time.Time"}}, }, { - Line: 645, + Line: 651, Op: ir.FilterVarTypeIsOp, Src: "m[\"t\"].Type.Is(`*time.Time`)", Value: "t", - Args: []ir.FilterExpr{{Line: 641, Op: ir.FilterStringOp, Src: "`*time.Time`", Value: "*time.Time"}}, + Args: []ir.FilterExpr{{Line: 647, Op: ir.FilterStringOp, Src: "`*time.Time`", Value: "*time.Time"}}, }, }, }, @@ -2122,39 +2146,39 @@ var PrecompiledRules = &ir.File{ }, }, { - Line: 649, - SyntaxPatterns: []ir.PatternString{{Line: 649, Value: "$t.UnixNano() * 1000"}}, + Line: 655, + SyntaxPatterns: []ir.PatternString{{Line: 655, Value: "$t.UnixNano() * 1000"}}, ReportTemplate: "use $t.UnixMicro() instead of $$", SuggestTemplate: "$t.UnixMicro()", WhereExpr: ir.FilterExpr{ - Line: 650, + Line: 656, Op: ir.FilterAndOp, Src: "m.GoVersion().GreaterEqThan(\"1.17\") && isTime(m[\"t\"])", Args: []ir.FilterExpr{ { - Line: 650, + Line: 656, Op: ir.FilterGoVersionGreaterEqThanOp, Src: "m.GoVersion().GreaterEqThan(\"1.17\")", Value: "1.17", }, { - Line: 650, + Line: 656, Op: ir.FilterOrOp, Src: "isTime(m[\"t\"])", Args: []ir.FilterExpr{ { - Line: 650, + Line: 656, Op: ir.FilterVarTypeIsOp, Src: "m[\"t\"].Type.Is(`time.Time`)", Value: "t", - Args: []ir.FilterExpr{{Line: 641, Op: ir.FilterStringOp, Src: "`time.Time`", Value: "time.Time"}}, + Args: []ir.FilterExpr{{Line: 647, Op: ir.FilterStringOp, Src: "`time.Time`", Value: "time.Time"}}, }, { - Line: 650, + Line: 656, Op: ir.FilterVarTypeIsOp, Src: "m[\"t\"].Type.Is(`*time.Time`)", Value: "t", - Args: []ir.FilterExpr{{Line: 641, Op: ir.FilterStringOp, Src: "`*time.Time`", Value: "*time.Time"}}, + Args: []ir.FilterExpr{{Line: 647, Op: ir.FilterStringOp, Src: "`*time.Time`", Value: "*time.Time"}}, }, }, }, @@ -2164,72 +2188,7 @@ var PrecompiledRules = &ir.File{ }, }, { - Line: 659, - Name: "timeCmpSimplify", - MatcherName: "m", - DocTags: []string{"style", "experimental"}, - DocSummary: "Detects Before/After call of time.Time that can be simplified", - DocBefore: "!t.Before(tt)", - DocAfter: "t.After(tt)", - Rules: []ir.Rule{ - { - Line: 664, - SyntaxPatterns: []ir.PatternString{{Line: 664, Value: "!$t.Before($tt)"}}, - ReportTemplate: "suggestion: $t.After($tt)", - SuggestTemplate: "$t.After($tt)", - WhereExpr: ir.FilterExpr{ - Line: 665, - Op: ir.FilterOrOp, - Src: "isTime(m[\"t\"])", - Args: []ir.FilterExpr{ - { - Line: 665, - Op: ir.FilterVarTypeIsOp, - Src: "m[\"t\"].Type.Is(`time.Time`)", - Value: "t", - Args: []ir.FilterExpr{{Line: 661, Op: ir.FilterStringOp, Src: "`time.Time`", Value: "time.Time"}}, - }, - { - Line: 665, - Op: ir.FilterVarTypeIsOp, - Src: "m[\"t\"].Type.Is(`*time.Time`)", - Value: "t", - Args: []ir.FilterExpr{{Line: 661, Op: ir.FilterStringOp, Src: "`*time.Time`", Value: "*time.Time"}}, - }, - }, - }, - }, - { - Line: 668, - SyntaxPatterns: []ir.PatternString{{Line: 668, Value: "!$t.After($tt)"}}, - ReportTemplate: "suggestion: $t.Before($tt)", - SuggestTemplate: "$t.Before($tt)", - WhereExpr: ir.FilterExpr{ - Line: 669, - Op: ir.FilterOrOp, - Src: "isTime(m[\"t\"])", - Args: []ir.FilterExpr{ - { - Line: 669, - Op: ir.FilterVarTypeIsOp, - Src: "m[\"t\"].Type.Is(`time.Time`)", - Value: "t", - Args: []ir.FilterExpr{{Line: 661, Op: ir.FilterStringOp, Src: "`time.Time`", Value: "time.Time"}}, - }, - { - Line: 669, - Op: ir.FilterVarTypeIsOp, - Src: "m[\"t\"].Type.Is(`*time.Time`)", - Value: "t", - Args: []ir.FilterExpr{{Line: 661, Op: ir.FilterStringOp, Src: "`*time.Time`", Value: "*time.Time"}}, - }, - }, - }, - }, - }, - }, - { - Line: 677, + Line: 665, Name: "exposedSyncMutex", MatcherName: "m", DocTags: []string{"style", "experimental"}, @@ -2238,57 +2197,57 @@ var PrecompiledRules = &ir.File{ DocAfter: "type Foo struct{ ...; mu sync.Mutex; ... }", Rules: []ir.Rule{ { - Line: 682, - SyntaxPatterns: []ir.PatternString{{Line: 682, Value: "type $x struct { $*_; sync.Mutex; $*_ }"}}, + Line: 670, + SyntaxPatterns: []ir.PatternString{{Line: 670, Value: "type $x struct { $*_; sync.Mutex; $*_ }"}}, ReportTemplate: "don't embed sync.Mutex", WhereExpr: ir.FilterExpr{ - Line: 683, + Line: 671, Op: ir.FilterVarTextMatchesOp, Src: "isExported(m[\"x\"])", Value: "x", - Args: []ir.FilterExpr{{Line: 679, Op: ir.FilterStringOp, Src: "`^\\p{Lu}`", Value: "^\\p{Lu}"}}, + Args: []ir.FilterExpr{{Line: 667, Op: ir.FilterStringOp, Src: "`^\\p{Lu}`", Value: "^\\p{Lu}"}}, }, }, { - Line: 686, - SyntaxPatterns: []ir.PatternString{{Line: 686, Value: "type $x struct { $*_; *sync.Mutex; $*_ }"}}, + Line: 674, + SyntaxPatterns: []ir.PatternString{{Line: 674, Value: "type $x struct { $*_; *sync.Mutex; $*_ }"}}, ReportTemplate: "don't embed *sync.Mutex", WhereExpr: ir.FilterExpr{ - Line: 687, + Line: 675, Op: ir.FilterVarTextMatchesOp, Src: "isExported(m[\"x\"])", Value: "x", - Args: []ir.FilterExpr{{Line: 679, Op: ir.FilterStringOp, Src: "`^\\p{Lu}`", Value: "^\\p{Lu}"}}, + Args: []ir.FilterExpr{{Line: 667, Op: ir.FilterStringOp, Src: "`^\\p{Lu}`", Value: "^\\p{Lu}"}}, }, }, { - Line: 690, - SyntaxPatterns: []ir.PatternString{{Line: 690, Value: "type $x struct { $*_; sync.RWMutex; $*_ }"}}, + Line: 678, + SyntaxPatterns: []ir.PatternString{{Line: 678, Value: "type $x struct { $*_; sync.RWMutex; $*_ }"}}, ReportTemplate: "don't embed sync.RWMutex", WhereExpr: ir.FilterExpr{ - Line: 691, + Line: 679, Op: ir.FilterVarTextMatchesOp, Src: "isExported(m[\"x\"])", Value: "x", - Args: []ir.FilterExpr{{Line: 679, Op: ir.FilterStringOp, Src: "`^\\p{Lu}`", Value: "^\\p{Lu}"}}, + Args: []ir.FilterExpr{{Line: 667, Op: ir.FilterStringOp, Src: "`^\\p{Lu}`", Value: "^\\p{Lu}"}}, }, }, { - Line: 694, - SyntaxPatterns: []ir.PatternString{{Line: 694, Value: "type $x struct { $*_; *sync.RWMutex; $*_ }"}}, + Line: 682, + SyntaxPatterns: []ir.PatternString{{Line: 682, Value: "type $x struct { $*_; *sync.RWMutex; $*_ }"}}, ReportTemplate: "don't embed *sync.RWMutex", WhereExpr: ir.FilterExpr{ - Line: 695, + Line: 683, Op: ir.FilterVarTextMatchesOp, Src: "isExported(m[\"x\"])", Value: "x", - Args: []ir.FilterExpr{{Line: 679, Op: ir.FilterStringOp, Src: "`^\\p{Lu}`", Value: "^\\p{Lu}"}}, + Args: []ir.FilterExpr{{Line: 667, Op: ir.FilterStringOp, Src: "`^\\p{Lu}`", Value: "^\\p{Lu}"}}, }, }, }, }, { - Line: 703, + Line: 691, Name: "badSorting", MatcherName: "m", DocTags: []string{"diagnostic", "experimental"}, @@ -2297,83 +2256,83 @@ var PrecompiledRules = &ir.File{ DocAfter: "sort.Strings(xs)", Rules: []ir.Rule{ { - Line: 704, - SyntaxPatterns: []ir.PatternString{{Line: 704, Value: "$x = sort.IntSlice($x)"}}, + Line: 692, + SyntaxPatterns: []ir.PatternString{{Line: 692, Value: "$x = sort.IntSlice($x)"}}, ReportTemplate: "suspicious sort.IntSlice usage, maybe sort.Ints was intended?", SuggestTemplate: "sort.Ints($x)", WhereExpr: ir.FilterExpr{ - Line: 705, + Line: 693, Op: ir.FilterVarTypeIsOp, Src: "m[\"x\"].Type.Is(`[]int`)", Value: "x", - Args: []ir.FilterExpr{{Line: 705, Op: ir.FilterStringOp, Src: "`[]int`", Value: "[]int"}}, + Args: []ir.FilterExpr{{Line: 693, Op: ir.FilterStringOp, Src: "`[]int`", Value: "[]int"}}, }, }, { - Line: 709, - SyntaxPatterns: []ir.PatternString{{Line: 709, Value: "$x = sort.Float64Slice($x)"}}, + Line: 697, + SyntaxPatterns: []ir.PatternString{{Line: 697, Value: "$x = sort.Float64Slice($x)"}}, ReportTemplate: "suspicious sort.Float64s usage, maybe sort.Float64s was intended?", SuggestTemplate: "sort.Float64s($x)", WhereExpr: ir.FilterExpr{ - Line: 710, + Line: 698, Op: ir.FilterVarTypeIsOp, Src: "m[\"x\"].Type.Is(`[]float64`)", Value: "x", - Args: []ir.FilterExpr{{Line: 710, Op: ir.FilterStringOp, Src: "`[]float64`", Value: "[]float64"}}, + Args: []ir.FilterExpr{{Line: 698, Op: ir.FilterStringOp, Src: "`[]float64`", Value: "[]float64"}}, }, }, { - Line: 714, - SyntaxPatterns: []ir.PatternString{{Line: 714, Value: "$x = sort.StringSlice($x)"}}, + Line: 702, + SyntaxPatterns: []ir.PatternString{{Line: 702, Value: "$x = sort.StringSlice($x)"}}, ReportTemplate: "suspicious sort.StringSlice usage, maybe sort.Strings was intended?", SuggestTemplate: "sort.Strings($x)", WhereExpr: ir.FilterExpr{ - Line: 715, + Line: 703, Op: ir.FilterVarTypeIsOp, Src: "m[\"x\"].Type.Is(`[]string`)", Value: "x", - Args: []ir.FilterExpr{{Line: 715, Op: ir.FilterStringOp, Src: "`[]string`", Value: "[]string"}}, + Args: []ir.FilterExpr{{Line: 703, Op: ir.FilterStringOp, Src: "`[]string`", Value: "[]string"}}, }, }, }, }, { - Line: 724, + Line: 712, Name: "externalErrorReassign", MatcherName: "m", DocTags: []string{"diagnostic", "experimental"}, - DocSummary: "Detects suspicious reassigment of error from another package", + DocSummary: "Detects suspicious reassignment of error from another package", DocBefore: "io.EOF = nil", DocAfter: "/* don't do it */", Rules: []ir.Rule{{ - Line: 725, - SyntaxPatterns: []ir.PatternString{{Line: 725, Value: "$pkg.$err = $x"}}, - ReportTemplate: "suspicious reassigment of error from another package", + Line: 713, + SyntaxPatterns: []ir.PatternString{{Line: 713, Value: "$pkg.$err = $x"}}, + ReportTemplate: "suspicious reassignment of error from another package", WhereExpr: ir.FilterExpr{ - Line: 726, + Line: 714, Op: ir.FilterAndOp, Src: "m[\"err\"].Type.Is(`error`) && m[\"pkg\"].Object.Is(`PkgName`)", Args: []ir.FilterExpr{ { - Line: 726, + Line: 714, Op: ir.FilterVarTypeIsOp, Src: "m[\"err\"].Type.Is(`error`)", Value: "err", - Args: []ir.FilterExpr{{Line: 726, Op: ir.FilterStringOp, Src: "`error`", Value: "error"}}, + Args: []ir.FilterExpr{{Line: 714, Op: ir.FilterStringOp, Src: "`error`", Value: "error"}}, }, { - Line: 726, + Line: 714, Op: ir.FilterVarObjectIsOp, Src: "m[\"pkg\"].Object.Is(`PkgName`)", Value: "pkg", - Args: []ir.FilterExpr{{Line: 726, Op: ir.FilterStringOp, Src: "`PkgName`", Value: "PkgName"}}, + Args: []ir.FilterExpr{{Line: 714, Op: ir.FilterStringOp, Src: "`PkgName`", Value: "PkgName"}}, }, }, }, }}, }, { - Line: 734, + Line: 722, Name: "emptyDecl", MatcherName: "m", DocTags: []string{"diagnostic", "experimental"}, @@ -2382,24 +2341,24 @@ var PrecompiledRules = &ir.File{ DocAfter: "/* nothing */", Rules: []ir.Rule{ { - Line: 735, - SyntaxPatterns: []ir.PatternString{{Line: 735, Value: "var()"}}, + Line: 723, + SyntaxPatterns: []ir.PatternString{{Line: 723, Value: "var()"}}, ReportTemplate: "empty var() block", }, { - Line: 736, - SyntaxPatterns: []ir.PatternString{{Line: 736, Value: "const()"}}, + Line: 724, + SyntaxPatterns: []ir.PatternString{{Line: 724, Value: "const()"}}, ReportTemplate: "empty const() block", }, { - Line: 737, - SyntaxPatterns: []ir.PatternString{{Line: 737, Value: "type()"}}, + Line: 725, + SyntaxPatterns: []ir.PatternString{{Line: 725, Value: "type()"}}, ReportTemplate: "empty type() block", }, }, }, { - Line: 744, + Line: 732, Name: "dynamicFmtString", MatcherName: "m", DocTags: []string{"diagnostic", "experimental"}, @@ -2408,16 +2367,16 @@ var PrecompiledRules = &ir.File{ DocAfter: "fmt.Errorf(\"%s\", msg)", Rules: []ir.Rule{ { - Line: 745, - SyntaxPatterns: []ir.PatternString{{Line: 745, Value: "fmt.Errorf($f)"}}, + Line: 733, + SyntaxPatterns: []ir.PatternString{{Line: 733, Value: "fmt.Errorf($f)"}}, ReportTemplate: "use errors.New($f) or fmt.Errorf(\"%s\", $f) instead", SuggestTemplate: "errors.New($f)", WhereExpr: ir.FilterExpr{ - Line: 746, + Line: 734, Op: ir.FilterNotOp, Src: "!m[\"f\"].Const", Args: []ir.FilterExpr{{ - Line: 746, + Line: 734, Op: ir.FilterVarConstOp, Src: "m[\"f\"].Const", Value: "f", @@ -2425,15 +2384,15 @@ var PrecompiledRules = &ir.File{ }, }, { - Line: 750, - SyntaxPatterns: []ir.PatternString{{Line: 750, Value: "fmt.Errorf($f($*args))"}}, + Line: 738, + SyntaxPatterns: []ir.PatternString{{Line: 738, Value: "fmt.Errorf($f($*args))"}}, ReportTemplate: "use errors.New($f($*args)) or fmt.Errorf(\"%s\", $f($*args)) instead", SuggestTemplate: "errors.New($f($*args))", }, }, }, { - Line: 759, + Line: 747, Name: "stringsCompare", MatcherName: "m", DocTags: []string{"style", "experimental"}, @@ -2442,25 +2401,25 @@ var PrecompiledRules = &ir.File{ DocAfter: "x < y", Rules: []ir.Rule{ { - Line: 760, - SyntaxPatterns: []ir.PatternString{{Line: 760, Value: "strings.Compare($s1, $s2) == 0"}}, + Line: 748, + SyntaxPatterns: []ir.PatternString{{Line: 748, Value: "strings.Compare($s1, $s2) == 0"}}, ReportTemplate: "suggestion: $s1 == $s2", SuggestTemplate: "$s1 == $s2", }, { - Line: 763, + Line: 751, SyntaxPatterns: []ir.PatternString{ - {Line: 763, Value: "strings.Compare($s1, $s2) == -1"}, - {Line: 764, Value: "strings.Compare($s1, $s2) < 0"}, + {Line: 751, Value: "strings.Compare($s1, $s2) == -1"}, + {Line: 752, Value: "strings.Compare($s1, $s2) < 0"}, }, ReportTemplate: "suggestion: $s1 < $s2", SuggestTemplate: "$s1 < $s2", }, { - Line: 767, + Line: 755, SyntaxPatterns: []ir.PatternString{ - {Line: 767, Value: "strings.Compare($s1, $s2) == 1"}, - {Line: 768, Value: "strings.Compare($s1, $s2) > 0"}, + {Line: 755, Value: "strings.Compare($s1, $s2) == 1"}, + {Line: 756, Value: "strings.Compare($s1, $s2) > 0"}, }, ReportTemplate: "suggestion: $s1 > $s2", SuggestTemplate: "$s1 > $s2", @@ -2468,7 +2427,7 @@ var PrecompiledRules = &ir.File{ }, }, { - Line: 776, + Line: 764, Name: "uncheckedInlineErr", MatcherName: "m", DocTags: []string{"diagnostic", "experimental"}, @@ -2476,47 +2435,47 @@ var PrecompiledRules = &ir.File{ DocBefore: "if err := expr(); err2 != nil { /*...*/ }", DocAfter: "if err := expr(); err != nil { /*...*/ }", Rules: []ir.Rule{{ - Line: 777, + Line: 765, SyntaxPatterns: []ir.PatternString{ - {Line: 778, Value: "if $err := $_($*_); $err2 != nil { $*_ }"}, - {Line: 779, Value: "if $err = $_($*_); $err2 != nil { $*_ }"}, - {Line: 780, Value: "if $*_, $err := $_($*_); $err2 != nil { $*_ }"}, - {Line: 781, Value: "if $*_, $err = $_($*_); $err2 != nil { $*_ }"}, + {Line: 766, Value: "if $err := $_($*_); $err2 != nil { $*_ }"}, + {Line: 767, Value: "if $err = $_($*_); $err2 != nil { $*_ }"}, + {Line: 768, Value: "if $*_, $err := $_($*_); $err2 != nil { $*_ }"}, + {Line: 769, Value: "if $*_, $err = $_($*_); $err2 != nil { $*_ }"}, }, ReportTemplate: "$err error is unchecked, maybe intended to check it instead of $err2", WhereExpr: ir.FilterExpr{ - Line: 782, + Line: 770, Op: ir.FilterAndOp, Src: "m[\"err\"].Type.Implements(\"error\") && m[\"err2\"].Type.Implements(\"error\") &&\n\tm[\"err\"].Text != m[\"err2\"].Text", Args: []ir.FilterExpr{ { - Line: 782, + Line: 770, Op: ir.FilterAndOp, Src: "m[\"err\"].Type.Implements(\"error\") && m[\"err2\"].Type.Implements(\"error\")", Args: []ir.FilterExpr{ { - Line: 782, + Line: 770, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"err\"].Type.Implements(\"error\")", Value: "err", - Args: []ir.FilterExpr{{Line: 782, Op: ir.FilterStringOp, Src: "\"error\"", Value: "error"}}, + Args: []ir.FilterExpr{{Line: 770, Op: ir.FilterStringOp, Src: "\"error\"", Value: "error"}}, }, { - Line: 782, + Line: 770, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"err2\"].Type.Implements(\"error\")", Value: "err2", - Args: []ir.FilterExpr{{Line: 782, Op: ir.FilterStringOp, Src: "\"error\"", Value: "error"}}, + Args: []ir.FilterExpr{{Line: 770, Op: ir.FilterStringOp, Src: "\"error\"", Value: "error"}}, }, }, }, { - Line: 783, + Line: 771, Op: ir.FilterNeqOp, Src: "m[\"err\"].Text != m[\"err2\"].Text", Args: []ir.FilterExpr{ - {Line: 783, Op: ir.FilterVarTextOp, Src: "m[\"err\"].Text", Value: "err"}, - {Line: 783, Op: ir.FilterVarTextOp, Src: "m[\"err2\"].Text", Value: "err2"}, + {Line: 771, Op: ir.FilterVarTextOp, Src: "m[\"err\"].Text", Value: "err"}, + {Line: 771, Op: ir.FilterVarTextOp, Src: "m[\"err2\"].Text", Value: "err2"}, }, }, }, @@ -2525,108 +2484,34 @@ var PrecompiledRules = &ir.File{ }}, }, { - Line: 792, - Name: "sloppyTestFuncName", + Line: 780, + Name: "badSyncOnceFunc", MatcherName: "m", DocTags: []string{"diagnostic", "experimental"}, - DocSummary: "Detects unsupported test and benchmark funcs", - DocBefore: "func TessstUnit(t *testing.T)", - DocAfter: "func TestUnit(t *testing.T)", + DocSummary: "Detects bad usage of sync.OnceFunc", + DocBefore: "sync.OnceFunc(foo)()", + DocAfter: "fooOnce := sync.OnceFunc(foo); ...; fooOnce()", Rules: []ir.Rule{ { - Line: 793, - SyntaxPatterns: []ir.PatternString{{Line: 793, Value: "func $test($_ *testing.T) { $*_ }"}}, - ReportTemplate: "function $test should be of form TestXXX(t *testing.T)", + Line: 781, + SyntaxPatterns: []ir.PatternString{{Line: 781, Value: "$*_; sync.OnceFunc($x); $*_;"}}, + ReportTemplate: "possible sync.OnceFunc misuse, sync.OnceFunc($x) result is not used", WhereExpr: ir.FilterExpr{ - Line: 794, - Op: ir.FilterAndOp, - Src: "!m[\"test\"].Text.Matches(\"Test.*\") &&\n\t!m[\"test\"].Text.Matches(\"test.*\")", - Args: []ir.FilterExpr{ - { - Line: 794, - Op: ir.FilterNotOp, - Src: "!m[\"test\"].Text.Matches(\"Test.*\")", - Args: []ir.FilterExpr{{ - Line: 794, - Op: ir.FilterVarTextMatchesOp, - Src: "m[\"test\"].Text.Matches(\"Test.*\")", - Value: "test", - Args: []ir.FilterExpr{{Line: 794, Op: ir.FilterStringOp, Src: "\"Test.*\"", Value: "Test.*"}}, - }}, - }, - { - Line: 795, - Op: ir.FilterNotOp, - Src: "!m[\"test\"].Text.Matches(\"test.*\")", - Args: []ir.FilterExpr{{ - Line: 795, - Op: ir.FilterVarTextMatchesOp, - Src: "m[\"test\"].Text.Matches(\"test.*\")", - Value: "test", - Args: []ir.FilterExpr{{Line: 795, Op: ir.FilterStringOp, Src: "\"test.*\"", Value: "test.*"}}, - }}, - }, - }, - }, - }, - { - Line: 798, - SyntaxPatterns: []ir.PatternString{{Line: 798, Value: "func $bench($_ *testing.B) { $*_ }"}}, - ReportTemplate: "function $bench should be of form BenchmarkXXX(b *testing.B)", - WhereExpr: ir.FilterExpr{ - Line: 799, - Op: ir.FilterAndOp, - Src: "!m[\"bench\"].Text.Matches(\"Benchmark.*\") &&\n\t!m[\"bench\"].Text.Matches(\"bench.*\")", - Args: []ir.FilterExpr{ - { - Line: 799, - Op: ir.FilterNotOp, - Src: "!m[\"bench\"].Text.Matches(\"Benchmark.*\")", - Args: []ir.FilterExpr{{ - Line: 799, - Op: ir.FilterVarTextMatchesOp, - Src: "m[\"bench\"].Text.Matches(\"Benchmark.*\")", - Value: "bench", - Args: []ir.FilterExpr{{Line: 799, Op: ir.FilterStringOp, Src: "\"Benchmark.*\"", Value: "Benchmark.*"}}, - }}, - }, - { - Line: 800, - Op: ir.FilterNotOp, - Src: "!m[\"bench\"].Text.Matches(\"bench.*\")", - Args: []ir.FilterExpr{{ - Line: 800, - Op: ir.FilterVarTextMatchesOp, - Src: "m[\"bench\"].Text.Matches(\"bench.*\")", - Value: "bench", - Args: []ir.FilterExpr{{Line: 800, Op: ir.FilterStringOp, Src: "\"bench.*\"", Value: "bench.*"}}, - }}, - }, - }, - }, - }, - { - Line: 803, - SyntaxPatterns: []ir.PatternString{{Line: 803, Value: "func $test($_ *testing.T) { $*_ }"}}, - ReportTemplate: "function $test looks like a test helper, consider to change 1st param to 'tb testing.TB'", - WhereExpr: ir.FilterExpr{ - Line: 804, - Op: ir.FilterVarTextMatchesOp, - Src: "m[\"test\"].Text.Matches(\"^test.*\")", - Value: "test", - Args: []ir.FilterExpr{{Line: 804, Op: ir.FilterStringOp, Src: "\"^test.*\"", Value: "^test.*"}}, + Line: 783, + Op: ir.FilterGoVersionGreaterEqThanOp, + Src: "m.GoVersion().GreaterEqThan(\"1.21\")", + Value: "1.21", }, }, { - Line: 807, - SyntaxPatterns: []ir.PatternString{{Line: 807, Value: "func $bench($_ *testing.B) { $*_ }"}}, - ReportTemplate: "function $bench looks like a benchmark helper, consider to change 1st param to 'tb testing.TB'", + Line: 785, + SyntaxPatterns: []ir.PatternString{{Line: 785, Value: "sync.OnceFunc($x)()"}}, + ReportTemplate: "possible sync.OnceFunc misuse, consider to assign sync.OnceFunc($x) to a variable", WhereExpr: ir.FilterExpr{ - Line: 808, - Op: ir.FilterVarTextMatchesOp, - Src: "m[\"bench\"].Text.Matches(\"^bench(mark)?.*\")", - Value: "bench", - Args: []ir.FilterExpr{{Line: 808, Op: ir.FilterStringOp, Src: "\"^bench(mark)?.*\"", Value: "^bench(mark)?.*"}}, + Line: 787, + Op: ir.FilterGoVersionGreaterEqThanOp, + Src: "m.GoVersion().GreaterEqThan(\"1.21\")", + Value: "1.21", }, }, }, diff --git a/vendor/github.com/go-critic/go-critic/checkers/typeUnparen_checker.go b/vendor/github.com/go-critic/go-critic/checkers/typeUnparen_checker.go index d270268bd..e2e225ebf 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/typeUnparen_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/typeUnparen_checker.go @@ -14,7 +14,7 @@ func init() { var info linter.CheckerInfo info.Name = "typeUnparen" info.Tags = []string{linter.StyleTag, linter.OpinionatedTag} - info.Summary = "Detects unneded parenthesis inside type expressions and suggests to remove them" + info.Summary = "Detects unneeded parenthesis inside type expressions and suggests to remove them" info.Before = `type foo [](func([](func())))` info.After = `type foo []func([]func())` diff --git a/vendor/github.com/go-critic/go-critic/checkers/unlambda_checker.go b/vendor/github.com/go-critic/go-critic/checkers/unlambda_checker.go index bcfe5a0c4..0401bf5d3 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/unlambda_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/unlambda_checker.go @@ -91,7 +91,7 @@ func (c *unlambdaChecker) VisitExpr(x ast.Expr) { } } - if len(result.Args) == n { + if c.lenArgs(result.Args) == n { c.warn(fn, callable) } } @@ -99,3 +99,20 @@ func (c *unlambdaChecker) VisitExpr(x ast.Expr) { func (c *unlambdaChecker) warn(cause ast.Node, suggestion string) { c.ctx.Warn(cause, "replace `%s` with `%s`", cause, suggestion) } + +func (c *unlambdaChecker) lenArgs(args []ast.Expr) int { + lenArgs := len(args) + + for _, arg := range args { + callExp, ok := arg.(*ast.CallExpr) + if !ok { + continue + } + + // Don't count function call. only args. + lenArgs-- + lenArgs += c.lenArgs(callExp.Args) + } + + return lenArgs +} diff --git a/vendor/github.com/go-critic/go-critic/checkers/unnecessaryDefer_checker.go b/vendor/github.com/go-critic/go-critic/checkers/unnecessaryDefer_checker.go index 4358ab171..4c1ed41f6 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/unnecessaryDefer_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/unnecessaryDefer_checker.go @@ -37,7 +37,7 @@ type unnecessaryDeferChecker struct { // Visit implements the ast.Visitor. This visitor keeps track of the block // statement belongs to a function or any other block. If the block is not a // function and ends with a defer statement that should be OK since it's -// defering the outer function. +// deferring the outer function. func (c *unnecessaryDeferChecker) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.FuncDecl, *ast.FuncLit: diff --git a/vendor/github.com/go-critic/go-critic/checkers/utils.go b/vendor/github.com/go-critic/go-critic/checkers/utils.go index e9123352d..4757bbf5f 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/utils.go +++ b/vendor/github.com/go-critic/go-critic/checkers/utils.go @@ -260,7 +260,7 @@ func isUnitTestFunc(ctx *linter.CheckerContext, fn *ast.FuncDecl) bool { return false } -// qualifiedName returns called expr fully-quallified name. +// qualifiedName returns called expr fully-qualified name. // // It works for simple identifiers like f => "f" and identifiers // from other package like pkg.f => "pkg.f". diff --git a/vendor/github.com/go-toolsmith/astequal/astequal.go b/vendor/github.com/go-toolsmith/astequal/astequal.go index 3d8db4af9..d1a04e942 100644 --- a/vendor/github.com/go-toolsmith/astequal/astequal.go +++ b/vendor/github.com/go-toolsmith/astequal/astequal.go @@ -4,8 +4,6 @@ package astequal import ( "go/ast" "go/token" - - "golang.org/x/exp/typeparams" ) // Node reports whether two AST nodes are structurally (deep) equal. @@ -109,8 +107,8 @@ func astExprEq(x, y ast.Expr) bool { y, ok := y.(*ast.IndexExpr) return ok && astIndexExprEq(x, y) - case *typeparams.IndexListExpr: - y, ok := y.(*typeparams.IndexListExpr) + case *ast.IndexListExpr: + y, ok := y.(*ast.IndexListExpr) return ok && astIndexListExprEq(x, y) case *ast.SliceExpr: @@ -323,7 +321,7 @@ func astFuncTypeEq(x, y *ast.FuncType) bool { } return astFieldListEq(x.Params, y.Params) && astFieldListEq(x.Results, y.Results) && - astFieldListEq(typeparams.ForFuncType(x), typeparams.ForFuncType(y)) + astFieldListEq(forFuncType(x), forFuncType(y)) } func astBasicLitEq(x, y *ast.BasicLit) bool { @@ -378,7 +376,7 @@ func astIndexExprEq(x, y *ast.IndexExpr) bool { return astExprEq(x.X, y.X) && astExprEq(x.Index, y.Index) } -func astIndexListExprEq(x, y *typeparams.IndexListExpr) bool { +func astIndexListExprEq(x, y *ast.IndexListExpr) bool { if x == nil || y == nil { return x == y } @@ -690,7 +688,7 @@ func astTypeSpecEq(x, y *ast.TypeSpec) bool { return x == y } return astIdentEq(x.Name, y.Name) && astExprEq(x.Type, y.Type) && - astFieldListEq(typeparams.ForTypeSpec(x), typeparams.ForTypeSpec(y)) + astFieldListEq(forTypeSpec(x), forTypeSpec(y)) } func astValueSpecEq(x, y *ast.ValueSpec) bool { @@ -755,3 +753,19 @@ func astExprSliceEq(xs, ys []ast.Expr) bool { } return true } + +// forTypeSpec returns n.TypeParams. +func forTypeSpec(n *ast.TypeSpec) *ast.FieldList { + if n == nil { + return nil + } + return n.TypeParams +} + +// forFuncType returns n.TypeParams. +func forFuncType(n *ast.FuncType) *ast.FieldList { + if n == nil { + return nil + } + return n.TypeParams +} diff --git a/vendor/github.com/go-toolsmith/astequal/diff.go b/vendor/github.com/go-toolsmith/astequal/diff.go new file mode 100644 index 000000000..cd69b4525 --- /dev/null +++ b/vendor/github.com/go-toolsmith/astequal/diff.go @@ -0,0 +1,23 @@ +package astequal + +import ( + "bytes" + "go/ast" + "go/format" + "go/token" + + "github.com/google/go-cmp/cmp" +) + +func Diff(x, y ast.Node) string { + var buf bytes.Buffer + format.Node(&buf, token.NewFileSet(), x) + s1 := buf.String() + + buf.Reset() + format.Node(&buf, token.NewFileSet(), y) + s2 := buf.String() + + // TODO(cristaloleg): replace with a more lightweight diff impl. + return cmp.Diff(s1, s2) +} diff --git a/vendor/github.com/go-viper/mapstructure/v2/.editorconfig b/vendor/github.com/go-viper/mapstructure/v2/.editorconfig new file mode 100644 index 000000000..1f664d13a --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.go] +indent_style = tab + +[{Makefile,*.mk}] +indent_style = tab + +[*.nix] +indent_size = 2 diff --git a/vendor/github.com/go-viper/mapstructure/v2/.envrc b/vendor/github.com/go-viper/mapstructure/v2/.envrc new file mode 100644 index 000000000..c66fc0d35 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/.envrc @@ -0,0 +1,4 @@ +if ! has nix_direnv_version || ! nix_direnv_version 3.0.1; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.1/direnvrc" "sha256-17G+Mvt/JsyJrwsf7bqMr7ho7liHP+0Lo4RMIHgp0F8=" +fi +use flake . --impure diff --git a/vendor/github.com/go-viper/mapstructure/v2/.gitignore b/vendor/github.com/go-viper/mapstructure/v2/.gitignore new file mode 100644 index 000000000..470e7ca2b --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/.gitignore @@ -0,0 +1,6 @@ +/.devenv/ +/.direnv/ +/.pre-commit-config.yaml +/bin/ +/build/ +/var/ diff --git a/vendor/github.com/go-viper/mapstructure/v2/.golangci.yaml b/vendor/github.com/go-viper/mapstructure/v2/.golangci.yaml new file mode 100644 index 000000000..763143aa7 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/.golangci.yaml @@ -0,0 +1,23 @@ +run: + timeout: 5m + +linters-settings: + gci: + sections: + - standard + - default + - prefix(github.com/go-viper/mapstructure) + golint: + min-confidence: 0 + goimports: + local-prefixes: github.com/go-viper/maptstructure + +linters: + disable-all: true + enable: + - gci + - gofmt + - gofumpt + - goimports + - staticcheck + # - stylecheck diff --git a/vendor/github.com/go-viper/mapstructure/v2/CHANGELOG.md b/vendor/github.com/go-viper/mapstructure/v2/CHANGELOG.md new file mode 100644 index 000000000..ae634d1cc --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/CHANGELOG.md @@ -0,0 +1,101 @@ +## 1.5.1 + +* Wrap errors so they're compatible with `errors.Is` and `errors.As` [GH-282] +* Fix map of slices not decoding properly in certain cases. [GH-266] + +## 1.5.0 + +* New option `IgnoreUntaggedFields` to ignore decoding to any fields + without `mapstructure` (or the configured tag name) set [GH-277] +* New option `ErrorUnset` which makes it an error if any fields + in a target struct are not set by the decoding process. [GH-225] +* New function `OrComposeDecodeHookFunc` to help compose decode hooks. [GH-240] +* Decoding to slice from array no longer crashes [GH-265] +* Decode nested struct pointers to map [GH-271] +* Fix issue where `,squash` was ignored if `Squash` option was set. [GH-280] +* Fix issue where fields with `,omitempty` would sometimes decode + into a map with an empty string key [GH-281] + +## 1.4.3 + +* Fix cases where `json.Number` didn't decode properly [GH-261] + +## 1.4.2 + +* Custom name matchers to support any sort of casing, formatting, etc. for + field names. [GH-250] +* Fix possible panic in ComposeDecodeHookFunc [GH-251] + +## 1.4.1 + +* Fix regression where `*time.Time` value would be set to empty and not be sent + to decode hooks properly [GH-232] + +## 1.4.0 + +* A new decode hook type `DecodeHookFuncValue` has been added that has + access to the full values. [GH-183] +* Squash is now supported with embedded fields that are struct pointers [GH-205] +* Empty strings will convert to 0 for all numeric types when weakly decoding [GH-206] + +## 1.3.3 + +* Decoding maps from maps creates a settable value for decode hooks [GH-203] + +## 1.3.2 + +* Decode into interface type with a struct value is supported [GH-187] + +## 1.3.1 + +* Squash should only squash embedded structs. [GH-194] + +## 1.3.0 + +* Added `",omitempty"` support. This will ignore zero values in the source + structure when encoding. [GH-145] + +## 1.2.3 + +* Fix duplicate entries in Keys list with pointer values. [GH-185] + +## 1.2.2 + +* Do not add unsettable (unexported) values to the unused metadata key + or "remain" value. [GH-150] + +## 1.2.1 + +* Go modules checksum mismatch fix + +## 1.2.0 + +* Added support to capture unused values in a field using the `",remain"` value + in the mapstructure tag. There is an example to showcase usage. +* Added `DecoderConfig` option to always squash embedded structs +* `json.Number` can decode into `uint` types +* Empty slices are preserved and not replaced with nil slices +* Fix panic that can occur in when decoding a map into a nil slice of structs +* Improved package documentation for godoc + +## 1.1.2 + +* Fix error when decode hook decodes interface implementation into interface + type. [GH-140] + +## 1.1.1 + +* Fix panic that can happen in `decodePtr` + +## 1.1.0 + +* Added `StringToIPHookFunc` to convert `string` to `net.IP` and `net.IPNet` [GH-133] +* Support struct to struct decoding [GH-137] +* If source map value is nil, then destination map value is nil (instead of empty) +* If source slice value is nil, then destination slice value is nil (instead of empty) +* If source pointer is nil, then destination pointer is set to nil (instead of + allocated zero value of type) + +## 1.0.0 + +* Initial tagged stable release. diff --git a/vendor/github.com/go-viper/mapstructure/v2/LICENSE b/vendor/github.com/go-viper/mapstructure/v2/LICENSE new file mode 100644 index 000000000..f9c841a51 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 Mitchell Hashimoto + +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/vendor/github.com/go-viper/mapstructure/v2/README.md b/vendor/github.com/go-viper/mapstructure/v2/README.md new file mode 100644 index 000000000..2b28db894 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/README.md @@ -0,0 +1,59 @@ +# mapstructure + +[](https://github.com/go-viper/mapstructure/actions?query=workflow%3ACI) +[](https://pkg.go.dev/mod/github.com/go-viper/mapstructure/v2) + + +mapstructure is a Go library for decoding generic map values to structures +and vice versa, while providing helpful error handling. + +This library is most useful when decoding values from some data stream (JSON, +Gob, etc.) where you don't _quite_ know the structure of the underlying data +until you read a part of it. You can therefore read a `map[string]interface{}` +and use this library to decode it into the proper underlying native Go +structure. + +## Installation + +```shell +go get github.com/go-viper/mapstructure/v2 +``` + +## Usage & Example + +For usage and examples see the [documentation](https://pkg.go.dev/mod/github.com/go-viper/mapstructure/v2). + +The `Decode` function has examples associated with it there. + +## But Why?! + +Go offers fantastic standard libraries for decoding formats such as JSON. +The standard method is to have a struct pre-created, and populate that struct +from the bytes of the encoded format. This is great, but the problem is if +you have configuration or an encoding that changes slightly depending on +specific fields. For example, consider this JSON: + +```json +{ + "type": "person", + "name": "Mitchell" +} +``` + +Perhaps we can't populate a specific structure without first reading +the "type" field from the JSON. We could always do two passes over the +decoding of the JSON (reading the "type" first, and the rest later). +However, it is much simpler to just decode this into a `map[string]interface{}` +structure, read the "type" key, then use something like this library +to decode it into the proper structure. + +## Credits + +Mapstructure was originally created by [@mitchellh](https://github.com/mitchellh). +This is a maintained fork of the original library. + +Read more about the reasons for the fork [here](https://github.com/mitchellh/mapstructure/issues/349). + +## License + +The project is licensed under the [MIT License](LICENSE). diff --git a/vendor/github.com/go-viper/mapstructure/v2/decode_hooks.go b/vendor/github.com/go-viper/mapstructure/v2/decode_hooks.go new file mode 100644 index 000000000..840d6adce --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/decode_hooks.go @@ -0,0 +1,334 @@ +package mapstructure + +import ( + "encoding" + "errors" + "fmt" + "net" + "net/netip" + "reflect" + "strconv" + "strings" + "time" +) + +// typedDecodeHook takes a raw DecodeHookFunc (an interface{}) and turns +// it into the proper DecodeHookFunc type, such as DecodeHookFuncType. +func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc { + // Create variables here so we can reference them with the reflect pkg + var f1 DecodeHookFuncType + var f2 DecodeHookFuncKind + var f3 DecodeHookFuncValue + + // Fill in the variables into this interface and the rest is done + // automatically using the reflect package. + potential := []interface{}{f1, f2, f3} + + v := reflect.ValueOf(h) + vt := v.Type() + for _, raw := range potential { + pt := reflect.ValueOf(raw).Type() + if vt.ConvertibleTo(pt) { + return v.Convert(pt).Interface() + } + } + + return nil +} + +// DecodeHookExec executes the given decode hook. This should be used +// since it'll naturally degrade to the older backwards compatible DecodeHookFunc +// that took reflect.Kind instead of reflect.Type. +func DecodeHookExec( + raw DecodeHookFunc, + from reflect.Value, to reflect.Value, +) (interface{}, error) { + switch f := typedDecodeHook(raw).(type) { + case DecodeHookFuncType: + return f(from.Type(), to.Type(), from.Interface()) + case DecodeHookFuncKind: + return f(from.Kind(), to.Kind(), from.Interface()) + case DecodeHookFuncValue: + return f(from, to) + default: + return nil, errors.New("invalid decode hook signature") + } +} + +// ComposeDecodeHookFunc creates a single DecodeHookFunc that +// automatically composes multiple DecodeHookFuncs. +// +// The composed funcs are called in order, with the result of the +// previous transformation. +func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc { + return func(f reflect.Value, t reflect.Value) (interface{}, error) { + var err error + data := f.Interface() + + newFrom := f + for _, f1 := range fs { + data, err = DecodeHookExec(f1, newFrom, t) + if err != nil { + return nil, err + } + newFrom = reflect.ValueOf(data) + } + + return data, nil + } +} + +// OrComposeDecodeHookFunc executes all input hook functions until one of them returns no error. In that case its value is returned. +// If all hooks return an error, OrComposeDecodeHookFunc returns an error concatenating all error messages. +func OrComposeDecodeHookFunc(ff ...DecodeHookFunc) DecodeHookFunc { + return func(a, b reflect.Value) (interface{}, error) { + var allErrs string + var out interface{} + var err error + + for _, f := range ff { + out, err = DecodeHookExec(f, a, b) + if err != nil { + allErrs += err.Error() + "\n" + continue + } + + return out, nil + } + + return nil, errors.New(allErrs) + } +} + +// StringToSliceHookFunc returns a DecodeHookFunc that converts +// string to []string by splitting on the given sep. +func StringToSliceHookFunc(sep string) DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.SliceOf(f) { + return data, nil + } + + raw := data.(string) + if raw == "" { + return []string{}, nil + } + + return strings.Split(raw, sep), nil + } +} + +// StringToTimeDurationHookFunc returns a DecodeHookFunc that converts +// strings to time.Duration. +func StringToTimeDurationHookFunc() DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(time.Duration(5)) { + return data, nil + } + + // Convert it by parsing + return time.ParseDuration(data.(string)) + } +} + +// StringToIPHookFunc returns a DecodeHookFunc that converts +// strings to net.IP +func StringToIPHookFunc() DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(net.IP{}) { + return data, nil + } + + // Convert it by parsing + ip := net.ParseIP(data.(string)) + if ip == nil { + return net.IP{}, fmt.Errorf("failed parsing ip %v", data) + } + + return ip, nil + } +} + +// StringToIPNetHookFunc returns a DecodeHookFunc that converts +// strings to net.IPNet +func StringToIPNetHookFunc() DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(net.IPNet{}) { + return data, nil + } + + // Convert it by parsing + _, net, err := net.ParseCIDR(data.(string)) + return net, err + } +} + +// StringToTimeHookFunc returns a DecodeHookFunc that converts +// strings to time.Time. +func StringToTimeHookFunc(layout string) DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(time.Time{}) { + return data, nil + } + + // Convert it by parsing + return time.Parse(layout, data.(string)) + } +} + +// WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to +// the decoder. +// +// Note that this is significantly different from the WeaklyTypedInput option +// of the DecoderConfig. +func WeaklyTypedHook( + f reflect.Kind, + t reflect.Kind, + data interface{}, +) (interface{}, error) { + dataVal := reflect.ValueOf(data) + switch t { + case reflect.String: + switch f { + case reflect.Bool: + if dataVal.Bool() { + return "1", nil + } + return "0", nil + case reflect.Float32: + return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil + case reflect.Int: + return strconv.FormatInt(dataVal.Int(), 10), nil + case reflect.Slice: + dataType := dataVal.Type() + elemKind := dataType.Elem().Kind() + if elemKind == reflect.Uint8 { + return string(dataVal.Interface().([]uint8)), nil + } + case reflect.Uint: + return strconv.FormatUint(dataVal.Uint(), 10), nil + } + } + + return data, nil +} + +func RecursiveStructToMapHookFunc() DecodeHookFunc { + return func(f reflect.Value, t reflect.Value) (interface{}, error) { + if f.Kind() != reflect.Struct { + return f.Interface(), nil + } + + var i interface{} = struct{}{} + if t.Type() != reflect.TypeOf(&i).Elem() { + return f.Interface(), nil + } + + m := make(map[string]interface{}) + t.Set(reflect.ValueOf(m)) + + return f.Interface(), nil + } +} + +// TextUnmarshallerHookFunc returns a DecodeHookFunc that applies +// strings to the UnmarshalText function, when the target type +// implements the encoding.TextUnmarshaler interface +func TextUnmarshallerHookFunc() DecodeHookFuncType { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + result := reflect.New(t).Interface() + unmarshaller, ok := result.(encoding.TextUnmarshaler) + if !ok { + return data, nil + } + str, ok := data.(string) + if !ok { + str = reflect.Indirect(reflect.ValueOf(&data)).Elem().String() + } + if err := unmarshaller.UnmarshalText([]byte(str)); err != nil { + return nil, err + } + return result, nil + } +} + +// StringToNetIPAddrHookFunc returns a DecodeHookFunc that converts +// strings to netip.Addr. +func StringToNetIPAddrHookFunc() DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(netip.Addr{}) { + return data, nil + } + + // Convert it by parsing + return netip.ParseAddr(data.(string)) + } +} + +// StringToNetIPAddrPortHookFunc returns a DecodeHookFunc that converts +// strings to netip.AddrPort. +func StringToNetIPAddrPortHookFunc() DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(netip.AddrPort{}) { + return data, nil + } + + // Convert it by parsing + return netip.ParseAddrPort(data.(string)) + } +} diff --git a/vendor/github.com/go-viper/mapstructure/v2/error.go b/vendor/github.com/go-viper/mapstructure/v2/error.go new file mode 100644 index 000000000..47a99e5af --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/error.go @@ -0,0 +1,50 @@ +package mapstructure + +import ( + "errors" + "fmt" + "sort" + "strings" +) + +// Error implements the error interface and can represents multiple +// errors that occur in the course of a single decode. +type Error struct { + Errors []string +} + +func (e *Error) Error() string { + points := make([]string, len(e.Errors)) + for i, err := range e.Errors { + points[i] = fmt.Sprintf("* %s", err) + } + + sort.Strings(points) + return fmt.Sprintf( + "%d error(s) decoding:\n\n%s", + len(e.Errors), strings.Join(points, "\n")) +} + +// WrappedErrors implements the errwrap.Wrapper interface to make this +// return value more useful with the errwrap and go-multierror libraries. +func (e *Error) WrappedErrors() []error { + if e == nil { + return nil + } + + result := make([]error, len(e.Errors)) + for i, e := range e.Errors { + result[i] = errors.New(e) + } + + return result +} + +func appendErrors(errors []string, err error) []string { + switch e := err.(type) { + case *Error: + return append(errors, e.Errors...) + default: + return append(errors, e.Error()) + } +} diff --git a/vendor/github.com/go-viper/mapstructure/v2/flake.lock b/vendor/github.com/go-viper/mapstructure/v2/flake.lock new file mode 100644 index 000000000..5a387d329 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/flake.lock @@ -0,0 +1,273 @@ +{ + "nodes": { + "devenv": { + "inputs": { + "flake-compat": "flake-compat", + "nix": "nix", + "nixpkgs": "nixpkgs", + "pre-commit-hooks": "pre-commit-hooks" + }, + "locked": { + "lastModified": 1702549996, + "narHash": "sha256-mEN+8gjWUXRxBCcixeth+jlDNuzxbpFwZNOEc4K22vw=", + "owner": "cachix", + "repo": "devenv", + "rev": "e681a99ffe2d2882f413a5d771129223c838ddce", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1701473968, + "narHash": "sha256-YcVE5emp1qQ8ieHUnxt1wCZCC3ZfAS+SRRWZ2TMda7E=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "34fed993f1674c8d06d58b37ce1e0fe5eebcb9f5", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1685518550, + "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "devenv", + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1660459072, + "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "lowdown-src": { + "flake": false, + "locked": { + "lastModified": 1633514407, + "narHash": "sha256-Dw32tiMjdK9t3ETl5fzGrutQTzh2rufgZV4A/BbxuD4=", + "owner": "kristapsdz", + "repo": "lowdown", + "rev": "d2c2b44ff6c27b936ec27358a2653caaef8f73b8", + "type": "github" + }, + "original": { + "owner": "kristapsdz", + "repo": "lowdown", + "type": "github" + } + }, + "nix": { + "inputs": { + "lowdown-src": "lowdown-src", + "nixpkgs": [ + "devenv", + "nixpkgs" + ], + "nixpkgs-regression": "nixpkgs-regression" + }, + "locked": { + "lastModified": 1676545802, + "narHash": "sha256-EK4rZ+Hd5hsvXnzSzk2ikhStJnD63odF7SzsQ8CuSPU=", + "owner": "domenkozar", + "repo": "nix", + "rev": "7c91803598ffbcfe4a55c44ac6d49b2cf07a527f", + "type": "github" + }, + "original": { + "owner": "domenkozar", + "ref": "relaxed-flakes", + "repo": "nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1678875422, + "narHash": "sha256-T3o6NcQPwXjxJMn2shz86Chch4ljXgZn746c2caGxd8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "126f49a01de5b7e35a43fd43f891ecf6d3a51459", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "dir": "lib", + "lastModified": 1701253981, + "narHash": "sha256-ztaDIyZ7HrTAfEEUt9AtTDNoCYxUdSd6NrRHaYOIxtk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e92039b55bcd58469325ded85d4f58dd5a4eaf58", + "type": "github" + }, + "original": { + "dir": "lib", + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-regression": { + "locked": { + "lastModified": 1643052045, + "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1685801374, + "narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "c37ca420157f4abc31e26f436c1145f8951ff373", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1702539185, + "narHash": "sha256-KnIRG5NMdLIpEkZTnN5zovNYc0hhXjAgv6pfd5Z4c7U=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "aa9d4729cbc99dabacb50e3994dcefb3ea0f7447", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": [ + "devenv", + "flake-compat" + ], + "flake-utils": "flake-utils", + "gitignore": "gitignore", + "nixpkgs": [ + "devenv", + "nixpkgs" + ], + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1688056373, + "narHash": "sha256-2+SDlNRTKsgo3LBRiMUcoEUb6sDViRNQhzJquZ4koOI=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "5843cf069272d92b60c3ed9e55b7a8989c01d4c7", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs_2" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/vendor/github.com/go-viper/mapstructure/v2/flake.nix b/vendor/github.com/go-viper/mapstructure/v2/flake.nix new file mode 100644 index 000000000..4ed0f5331 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/flake.nix @@ -0,0 +1,39 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + flake-parts.url = "github:hercules-ci/flake-parts"; + devenv.url = "github:cachix/devenv"; + }; + + outputs = inputs@{ flake-parts, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + imports = [ + inputs.devenv.flakeModule + ]; + + systems = [ "x86_64-linux" "x86_64-darwin" "aarch64-darwin" ]; + + perSystem = { config, self', inputs', pkgs, system, ... }: rec { + devenv.shells = { + default = { + languages = { + go.enable = true; + }; + + pre-commit.hooks = { + nixpkgs-fmt.enable = true; + }; + + packages = with pkgs; [ + golangci-lint + ]; + + # https://github.com/cachix/devenv/issues/528#issuecomment-1556108767 + containers = pkgs.lib.mkForce { }; + }; + + ci = devenv.shells.default; + }; + }; + }; +} diff --git a/vendor/github.com/go-viper/mapstructure/v2/mapstructure.go b/vendor/github.com/go-viper/mapstructure/v2/mapstructure.go new file mode 100644 index 000000000..27f21bc72 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/mapstructure.go @@ -0,0 +1,1562 @@ +// Package mapstructure exposes functionality to convert one arbitrary +// Go type into another, typically to convert a map[string]interface{} +// into a native Go structure. +// +// The Go structure can be arbitrarily complex, containing slices, +// other structs, etc. and the decoder will properly decode nested +// maps and so on into the proper structures in the native Go struct. +// See the examples to see what the decoder is capable of. +// +// The simplest function to start with is Decode. +// +// # Field Tags +// +// When decoding to a struct, mapstructure will use the field name by +// default to perform the mapping. For example, if a struct has a field +// "Username" then mapstructure will look for a key in the source value +// of "username" (case insensitive). +// +// type User struct { +// Username string +// } +// +// You can change the behavior of mapstructure by using struct tags. +// The default struct tag that mapstructure looks for is "mapstructure" +// but you can customize it using DecoderConfig. +// +// # Renaming Fields +// +// To rename the key that mapstructure looks for, use the "mapstructure" +// tag and set a value directly. For example, to change the "username" example +// above to "user": +// +// type User struct { +// Username string `mapstructure:"user"` +// } +// +// # Embedded Structs and Squashing +// +// Embedded structs are treated as if they're another field with that name. +// By default, the two structs below are equivalent when decoding with +// mapstructure: +// +// type Person struct { +// Name string +// } +// +// type Friend struct { +// Person +// } +// +// type Friend struct { +// Person Person +// } +// +// This would require an input that looks like below: +// +// map[string]interface{}{ +// "person": map[string]interface{}{"name": "alice"}, +// } +// +// If your "person" value is NOT nested, then you can append ",squash" to +// your tag value and mapstructure will treat it as if the embedded struct +// were part of the struct directly. Example: +// +// type Friend struct { +// Person `mapstructure:",squash"` +// } +// +// Now the following input would be accepted: +// +// map[string]interface{}{ +// "name": "alice", +// } +// +// When decoding from a struct to a map, the squash tag squashes the struct +// fields into a single map. Using the example structs from above: +// +// Friend{Person: Person{Name: "alice"}} +// +// Will be decoded into a map: +// +// map[string]interface{}{ +// "name": "alice", +// } +// +// DecoderConfig has a field that changes the behavior of mapstructure +// to always squash embedded structs. +// +// # Remainder Values +// +// If there are any unmapped keys in the source value, mapstructure by +// default will silently ignore them. You can error by setting ErrorUnused +// in DecoderConfig. If you're using Metadata you can also maintain a slice +// of the unused keys. +// +// You can also use the ",remain" suffix on your tag to collect all unused +// values in a map. The field with this tag MUST be a map type and should +// probably be a "map[string]interface{}" or "map[interface{}]interface{}". +// See example below: +// +// type Friend struct { +// Name string +// Other map[string]interface{} `mapstructure:",remain"` +// } +// +// Given the input below, Other would be populated with the other +// values that weren't used (everything but "name"): +// +// map[string]interface{}{ +// "name": "bob", +// "address": "123 Maple St.", +// } +// +// # Omit Empty Values +// +// When decoding from a struct to any other value, you may use the +// ",omitempty" suffix on your tag to omit that value if it equates to +// the zero value. The zero value of all types is specified in the Go +// specification. +// +// For example, the zero type of a numeric type is zero ("0"). If the struct +// field value is zero and a numeric type, the field is empty, and it won't +// be encoded into the destination type. +// +// type Source struct { +// Age int `mapstructure:",omitempty"` +// } +// +// # Unexported fields +// +// Since unexported (private) struct fields cannot be set outside the package +// where they are defined, the decoder will simply skip them. +// +// For this output type definition: +// +// type Exported struct { +// private string // this unexported field will be skipped +// Public string +// } +// +// Using this map as input: +// +// map[string]interface{}{ +// "private": "I will be ignored", +// "Public": "I made it through!", +// } +// +// The following struct will be decoded: +// +// type Exported struct { +// private: "" // field is left with an empty string (zero value) +// Public: "I made it through!" +// } +// +// # Other Configuration +// +// mapstructure is highly configurable. See the DecoderConfig struct +// for other features and options that are supported. +package mapstructure + +import ( + "encoding/json" + "errors" + "fmt" + "reflect" + "sort" + "strconv" + "strings" +) + +// DecodeHookFunc is the callback function that can be used for +// data transformations. See "DecodeHook" in the DecoderConfig +// struct. +// +// The type must be one of DecodeHookFuncType, DecodeHookFuncKind, or +// DecodeHookFuncValue. +// Values are a superset of Types (Values can return types), and Types are a +// superset of Kinds (Types can return Kinds) and are generally a richer thing +// to use, but Kinds are simpler if you only need those. +// +// The reason DecodeHookFunc is multi-typed is for backwards compatibility: +// we started with Kinds and then realized Types were the better solution, +// but have a promise to not break backwards compat so we now support +// both. +type DecodeHookFunc interface{} + +// DecodeHookFuncType is a DecodeHookFunc which has complete information about +// the source and target types. +type DecodeHookFuncType func(reflect.Type, reflect.Type, interface{}) (interface{}, error) + +// DecodeHookFuncKind is a DecodeHookFunc which knows only the Kinds of the +// source and target types. +type DecodeHookFuncKind func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error) + +// DecodeHookFuncValue is a DecodeHookFunc which has complete access to both the source and target +// values. +type DecodeHookFuncValue func(from reflect.Value, to reflect.Value) (interface{}, error) + +// DecoderConfig is the configuration that is used to create a new decoder +// and allows customization of various aspects of decoding. +type DecoderConfig struct { + // DecodeHook, if set, will be called before any decoding and any + // type conversion (if WeaklyTypedInput is on). This lets you modify + // the values before they're set down onto the resulting struct. The + // DecodeHook is called for every map and value in the input. This means + // that if a struct has embedded fields with squash tags the decode hook + // is called only once with all of the input data, not once for each + // embedded struct. + // + // If an error is returned, the entire decode will fail with that error. + DecodeHook DecodeHookFunc + + // If ErrorUnused is true, then it is an error for there to exist + // keys in the original map that were unused in the decoding process + // (extra keys). + ErrorUnused bool + + // If ErrorUnset is true, then it is an error for there to exist + // fields in the result that were not set in the decoding process + // (extra fields). This only applies to decoding to a struct. This + // will affect all nested structs as well. + ErrorUnset bool + + // ZeroFields, if set to true, will zero fields before writing them. + // For example, a map will be emptied before decoded values are put in + // it. If this is false, a map will be merged. + ZeroFields bool + + // If WeaklyTypedInput is true, the decoder will make the following + // "weak" conversions: + // + // - bools to string (true = "1", false = "0") + // - numbers to string (base 10) + // - bools to int/uint (true = 1, false = 0) + // - strings to int/uint (base implied by prefix) + // - int to bool (true if value != 0) + // - string to bool (accepts: 1, t, T, TRUE, true, True, 0, f, F, + // FALSE, false, False. Anything else is an error) + // - empty array = empty map and vice versa + // - negative numbers to overflowed uint values (base 10) + // - slice of maps to a merged map + // - single values are converted to slices if required. Each + // element is weakly decoded. For example: "4" can become []int{4} + // if the target type is an int slice. + // + WeaklyTypedInput bool + + // Squash will squash embedded structs. A squash tag may also be + // added to an individual struct field using a tag. For example: + // + // type Parent struct { + // Child `mapstructure:",squash"` + // } + Squash bool + + // Metadata is the struct that will contain extra metadata about + // the decoding. If this is nil, then no metadata will be tracked. + Metadata *Metadata + + // Result is a pointer to the struct that will contain the decoded + // value. + Result interface{} + + // The tag name that mapstructure reads for field names. This + // defaults to "mapstructure" + TagName string + + // IgnoreUntaggedFields ignores all struct fields without explicit + // TagName, comparable to `mapstructure:"-"` as default behaviour. + IgnoreUntaggedFields bool + + // MatchName is the function used to match the map key to the struct + // field name or tag. Defaults to `strings.EqualFold`. This can be used + // to implement case-sensitive tag values, support snake casing, etc. + MatchName func(mapKey, fieldName string) bool +} + +// A Decoder takes a raw interface value and turns it into structured +// data, keeping track of rich error information along the way in case +// anything goes wrong. Unlike the basic top-level Decode method, you can +// more finely control how the Decoder behaves using the DecoderConfig +// structure. The top-level Decode method is just a convenience that sets +// up the most basic Decoder. +type Decoder struct { + config *DecoderConfig +} + +// Metadata contains information about decoding a structure that +// is tedious or difficult to get otherwise. +type Metadata struct { + // Keys are the keys of the structure which were successfully decoded + Keys []string + + // Unused is a slice of keys that were found in the raw value but + // weren't decoded since there was no matching field in the result interface + Unused []string + + // Unset is a slice of field names that were found in the result interface + // but weren't set in the decoding process since there was no matching value + // in the input + Unset []string +} + +// Decode takes an input structure and uses reflection to translate it to +// the output structure. output must be a pointer to a map or struct. +func Decode(input interface{}, output interface{}) error { + config := &DecoderConfig{ + Metadata: nil, + Result: output, + } + + decoder, err := NewDecoder(config) + if err != nil { + return err + } + + return decoder.Decode(input) +} + +// WeakDecode is the same as Decode but is shorthand to enable +// WeaklyTypedInput. See DecoderConfig for more info. +func WeakDecode(input, output interface{}) error { + config := &DecoderConfig{ + Metadata: nil, + Result: output, + WeaklyTypedInput: true, + } + + decoder, err := NewDecoder(config) + if err != nil { + return err + } + + return decoder.Decode(input) +} + +// DecodeMetadata is the same as Decode, but is shorthand to +// enable metadata collection. See DecoderConfig for more info. +func DecodeMetadata(input interface{}, output interface{}, metadata *Metadata) error { + config := &DecoderConfig{ + Metadata: metadata, + Result: output, + } + + decoder, err := NewDecoder(config) + if err != nil { + return err + } + + return decoder.Decode(input) +} + +// WeakDecodeMetadata is the same as Decode, but is shorthand to +// enable both WeaklyTypedInput and metadata collection. See +// DecoderConfig for more info. +func WeakDecodeMetadata(input interface{}, output interface{}, metadata *Metadata) error { + config := &DecoderConfig{ + Metadata: metadata, + Result: output, + WeaklyTypedInput: true, + } + + decoder, err := NewDecoder(config) + if err != nil { + return err + } + + return decoder.Decode(input) +} + +// NewDecoder returns a new decoder for the given configuration. Once +// a decoder has been returned, the same configuration must not be used +// again. +func NewDecoder(config *DecoderConfig) (*Decoder, error) { + val := reflect.ValueOf(config.Result) + if val.Kind() != reflect.Ptr { + return nil, errors.New("result must be a pointer") + } + + val = val.Elem() + if !val.CanAddr() { + return nil, errors.New("result must be addressable (a pointer)") + } + + if config.Metadata != nil { + if config.Metadata.Keys == nil { + config.Metadata.Keys = make([]string, 0) + } + + if config.Metadata.Unused == nil { + config.Metadata.Unused = make([]string, 0) + } + + if config.Metadata.Unset == nil { + config.Metadata.Unset = make([]string, 0) + } + } + + if config.TagName == "" { + config.TagName = "mapstructure" + } + + if config.MatchName == nil { + config.MatchName = strings.EqualFold + } + + result := &Decoder{ + config: config, + } + + return result, nil +} + +// Decode decodes the given raw interface to the target pointer specified +// by the configuration. +func (d *Decoder) Decode(input interface{}) error { + return d.decode("", input, reflect.ValueOf(d.config.Result).Elem()) +} + +// Decodes an unknown data type into a specific reflection value. +func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) error { + var inputVal reflect.Value + if input != nil { + inputVal = reflect.ValueOf(input) + + // We need to check here if input is a typed nil. Typed nils won't + // match the "input == nil" below so we check that here. + if inputVal.Kind() == reflect.Ptr && inputVal.IsNil() { + input = nil + } + } + + if input == nil { + // If the data is nil, then we don't set anything, unless ZeroFields is set + // to true. + if d.config.ZeroFields { + outVal.Set(reflect.Zero(outVal.Type())) + + if d.config.Metadata != nil && name != "" { + d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) + } + } + return nil + } + + if !inputVal.IsValid() { + // If the input value is invalid, then we just set the value + // to be the zero value. + outVal.Set(reflect.Zero(outVal.Type())) + if d.config.Metadata != nil && name != "" { + d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) + } + return nil + } + + if d.config.DecodeHook != nil { + // We have a DecodeHook, so let's pre-process the input. + var err error + input, err = DecodeHookExec(d.config.DecodeHook, inputVal, outVal) + if err != nil { + return fmt.Errorf("error decoding '%s': %w", name, err) + } + } + + var err error + outputKind := getKind(outVal) + addMetaKey := true + switch outputKind { + case reflect.Bool: + err = d.decodeBool(name, input, outVal) + case reflect.Interface: + err = d.decodeBasic(name, input, outVal) + case reflect.String: + err = d.decodeString(name, input, outVal) + case reflect.Int: + err = d.decodeInt(name, input, outVal) + case reflect.Uint: + err = d.decodeUint(name, input, outVal) + case reflect.Float32: + err = d.decodeFloat(name, input, outVal) + case reflect.Struct: + err = d.decodeStruct(name, input, outVal) + case reflect.Map: + err = d.decodeMap(name, input, outVal) + case reflect.Ptr: + addMetaKey, err = d.decodePtr(name, input, outVal) + case reflect.Slice: + err = d.decodeSlice(name, input, outVal) + case reflect.Array: + err = d.decodeArray(name, input, outVal) + case reflect.Func: + err = d.decodeFunc(name, input, outVal) + default: + // If we reached this point then we weren't able to decode it + return fmt.Errorf("%s: unsupported type: %s", name, outputKind) + } + + // If we reached here, then we successfully decoded SOMETHING, so + // mark the key as used if we're tracking metainput. + if addMetaKey && d.config.Metadata != nil && name != "" { + d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) + } + + return err +} + +// This decodes a basic type (bool, int, string, etc.) and sets the +// value to "data" of that type. +func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) error { + if val.IsValid() && val.Elem().IsValid() { + elem := val.Elem() + + // If we can't address this element, then its not writable. Instead, + // we make a copy of the value (which is a pointer and therefore + // writable), decode into that, and replace the whole value. + copied := false + if !elem.CanAddr() { + copied = true + + // Make *T + copy := reflect.New(elem.Type()) + + // *T = elem + copy.Elem().Set(elem) + + // Set elem so we decode into it + elem = copy + } + + // Decode. If we have an error then return. We also return right + // away if we're not a copy because that means we decoded directly. + if err := d.decode(name, data, elem); err != nil || !copied { + return err + } + + // If we're a copy, we need to set te final result + val.Set(elem.Elem()) + return nil + } + + dataVal := reflect.ValueOf(data) + + // If the input data is a pointer, and the assigned type is the dereference + // of that exact pointer, then indirect it so that we can assign it. + // Example: *string to string + if dataVal.Kind() == reflect.Ptr && dataVal.Type().Elem() == val.Type() { + dataVal = reflect.Indirect(dataVal) + } + + if !dataVal.IsValid() { + dataVal = reflect.Zero(val.Type()) + } + + dataValType := dataVal.Type() + if !dataValType.AssignableTo(val.Type()) { + return fmt.Errorf( + "'%s' expected type '%s', got '%s'", + name, val.Type(), dataValType) + } + + val.Set(dataVal) + return nil +} + +func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + + converted := true + switch { + case dataKind == reflect.String: + val.SetString(dataVal.String()) + case dataKind == reflect.Bool && d.config.WeaklyTypedInput: + if dataVal.Bool() { + val.SetString("1") + } else { + val.SetString("0") + } + case dataKind == reflect.Int && d.config.WeaklyTypedInput: + val.SetString(strconv.FormatInt(dataVal.Int(), 10)) + case dataKind == reflect.Uint && d.config.WeaklyTypedInput: + val.SetString(strconv.FormatUint(dataVal.Uint(), 10)) + case dataKind == reflect.Float32 && d.config.WeaklyTypedInput: + val.SetString(strconv.FormatFloat(dataVal.Float(), 'f', -1, 64)) + case dataKind == reflect.Slice && d.config.WeaklyTypedInput, + dataKind == reflect.Array && d.config.WeaklyTypedInput: + dataType := dataVal.Type() + elemKind := dataType.Elem().Kind() + switch elemKind { + case reflect.Uint8: + var uints []uint8 + if dataKind == reflect.Array { + uints = make([]uint8, dataVal.Len(), dataVal.Len()) + for i := range uints { + uints[i] = dataVal.Index(i).Interface().(uint8) + } + } else { + uints = dataVal.Interface().([]uint8) + } + val.SetString(string(uints)) + default: + converted = false + } + default: + converted = false + } + + if !converted { + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", + name, val.Type(), dataVal.Type(), data) + } + + return nil +} + +func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + dataType := dataVal.Type() + + switch { + case dataKind == reflect.Int: + val.SetInt(dataVal.Int()) + case dataKind == reflect.Uint: + val.SetInt(int64(dataVal.Uint())) + case dataKind == reflect.Float32: + val.SetInt(int64(dataVal.Float())) + case dataKind == reflect.Bool && d.config.WeaklyTypedInput: + if dataVal.Bool() { + val.SetInt(1) + } else { + val.SetInt(0) + } + case dataKind == reflect.String && d.config.WeaklyTypedInput: + str := dataVal.String() + if str == "" { + str = "0" + } + + i, err := strconv.ParseInt(str, 0, val.Type().Bits()) + if err == nil { + val.SetInt(i) + } else { + return fmt.Errorf("cannot parse '%s' as int: %s", name, err) + } + case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": + jn := data.(json.Number) + i, err := jn.Int64() + if err != nil { + return fmt.Errorf( + "error decoding json.Number into %s: %s", name, err) + } + val.SetInt(i) + default: + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", + name, val.Type(), dataVal.Type(), data) + } + + return nil +} + +func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + dataType := dataVal.Type() + + switch { + case dataKind == reflect.Int: + i := dataVal.Int() + if i < 0 && !d.config.WeaklyTypedInput { + return fmt.Errorf("cannot parse '%s', %d overflows uint", + name, i) + } + val.SetUint(uint64(i)) + case dataKind == reflect.Uint: + val.SetUint(dataVal.Uint()) + case dataKind == reflect.Float32: + f := dataVal.Float() + if f < 0 && !d.config.WeaklyTypedInput { + return fmt.Errorf("cannot parse '%s', %f overflows uint", + name, f) + } + val.SetUint(uint64(f)) + case dataKind == reflect.Bool && d.config.WeaklyTypedInput: + if dataVal.Bool() { + val.SetUint(1) + } else { + val.SetUint(0) + } + case dataKind == reflect.String && d.config.WeaklyTypedInput: + str := dataVal.String() + if str == "" { + str = "0" + } + + i, err := strconv.ParseUint(str, 0, val.Type().Bits()) + if err == nil { + val.SetUint(i) + } else { + return fmt.Errorf("cannot parse '%s' as uint: %s", name, err) + } + case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": + jn := data.(json.Number) + i, err := strconv.ParseUint(string(jn), 0, 64) + if err != nil { + return fmt.Errorf( + "error decoding json.Number into %s: %s", name, err) + } + val.SetUint(i) + default: + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", + name, val.Type(), dataVal.Type(), data) + } + + return nil +} + +func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + + switch { + case dataKind == reflect.Bool: + val.SetBool(dataVal.Bool()) + case dataKind == reflect.Int && d.config.WeaklyTypedInput: + val.SetBool(dataVal.Int() != 0) + case dataKind == reflect.Uint && d.config.WeaklyTypedInput: + val.SetBool(dataVal.Uint() != 0) + case dataKind == reflect.Float32 && d.config.WeaklyTypedInput: + val.SetBool(dataVal.Float() != 0) + case dataKind == reflect.String && d.config.WeaklyTypedInput: + b, err := strconv.ParseBool(dataVal.String()) + if err == nil { + val.SetBool(b) + } else if dataVal.String() == "" { + val.SetBool(false) + } else { + return fmt.Errorf("cannot parse '%s' as bool: %s", name, err) + } + default: + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", + name, val.Type(), dataVal.Type(), data) + } + + return nil +} + +func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + dataType := dataVal.Type() + + switch { + case dataKind == reflect.Int: + val.SetFloat(float64(dataVal.Int())) + case dataKind == reflect.Uint: + val.SetFloat(float64(dataVal.Uint())) + case dataKind == reflect.Float32: + val.SetFloat(dataVal.Float()) + case dataKind == reflect.Bool && d.config.WeaklyTypedInput: + if dataVal.Bool() { + val.SetFloat(1) + } else { + val.SetFloat(0) + } + case dataKind == reflect.String && d.config.WeaklyTypedInput: + str := dataVal.String() + if str == "" { + str = "0" + } + + f, err := strconv.ParseFloat(str, val.Type().Bits()) + if err == nil { + val.SetFloat(f) + } else { + return fmt.Errorf("cannot parse '%s' as float: %s", name, err) + } + case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": + jn := data.(json.Number) + i, err := jn.Float64() + if err != nil { + return fmt.Errorf( + "error decoding json.Number into %s: %s", name, err) + } + val.SetFloat(i) + default: + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", + name, val.Type(), dataVal.Type(), data) + } + + return nil +} + +func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) error { + valType := val.Type() + valKeyType := valType.Key() + valElemType := valType.Elem() + + // By default we overwrite keys in the current map + valMap := val + + // If the map is nil or we're purposely zeroing fields, make a new map + if valMap.IsNil() || d.config.ZeroFields { + // Make a new map to hold our result + mapType := reflect.MapOf(valKeyType, valElemType) + valMap = reflect.MakeMap(mapType) + } + + dataVal := reflect.ValueOf(data) + + // Resolve any levels of indirection + for dataVal.Kind() == reflect.Pointer { + dataVal = reflect.Indirect(dataVal) + } + + // Check input type and based on the input type jump to the proper func + switch dataVal.Kind() { + case reflect.Map: + return d.decodeMapFromMap(name, dataVal, val, valMap) + + case reflect.Struct: + return d.decodeMapFromStruct(name, dataVal, val, valMap) + + case reflect.Array, reflect.Slice: + if d.config.WeaklyTypedInput { + return d.decodeMapFromSlice(name, dataVal, val, valMap) + } + + fallthrough + + default: + return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind()) + } +} + +func (d *Decoder) decodeMapFromSlice(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { + // Special case for BC reasons (covered by tests) + if dataVal.Len() == 0 { + val.Set(valMap) + return nil + } + + for i := 0; i < dataVal.Len(); i++ { + err := d.decode( + name+"["+strconv.Itoa(i)+"]", + dataVal.Index(i).Interface(), val) + if err != nil { + return err + } + } + + return nil +} + +func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { + valType := val.Type() + valKeyType := valType.Key() + valElemType := valType.Elem() + + // Accumulate errors + errors := make([]string, 0) + + // If the input data is empty, then we just match what the input data is. + if dataVal.Len() == 0 { + if dataVal.IsNil() { + if !val.IsNil() { + val.Set(dataVal) + } + } else { + // Set to empty allocated value + val.Set(valMap) + } + + return nil + } + + for _, k := range dataVal.MapKeys() { + fieldName := name + "[" + k.String() + "]" + + // First decode the key into the proper type + currentKey := reflect.Indirect(reflect.New(valKeyType)) + if err := d.decode(fieldName, k.Interface(), currentKey); err != nil { + errors = appendErrors(errors, err) + continue + } + + // Next decode the data into the proper type + v := dataVal.MapIndex(k).Interface() + currentVal := reflect.Indirect(reflect.New(valElemType)) + if err := d.decode(fieldName, v, currentVal); err != nil { + errors = appendErrors(errors, err) + continue + } + + valMap.SetMapIndex(currentKey, currentVal) + } + + // Set the built up map to the value + val.Set(valMap) + + // If we had errors, return those + if len(errors) > 0 { + return &Error{errors} + } + + return nil +} + +func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { + typ := dataVal.Type() + for i := 0; i < typ.NumField(); i++ { + // Get the StructField first since this is a cheap operation. If the + // field is unexported, then ignore it. + f := typ.Field(i) + if f.PkgPath != "" { + continue + } + + // Next get the actual value of this field and verify it is assignable + // to the map value. + v := dataVal.Field(i) + if !v.Type().AssignableTo(valMap.Type().Elem()) { + return fmt.Errorf("cannot assign type '%s' to map value field of type '%s'", v.Type(), valMap.Type().Elem()) + } + + tagValue := f.Tag.Get(d.config.TagName) + keyName := f.Name + + if tagValue == "" && d.config.IgnoreUntaggedFields { + continue + } + + // If Squash is set in the config, we squash the field down. + squash := d.config.Squash && v.Kind() == reflect.Struct && f.Anonymous + + v = dereferencePtrToStructIfNeeded(v, d.config.TagName) + + // Determine the name of the key in the map + if index := strings.Index(tagValue, ","); index != -1 { + if tagValue[:index] == "-" { + continue + } + // If "omitempty" is specified in the tag, it ignores empty values. + if strings.Index(tagValue[index+1:], "omitempty") != -1 && isEmptyValue(v) { + continue + } + + // If "squash" is specified in the tag, we squash the field down. + squash = squash || strings.Index(tagValue[index+1:], "squash") != -1 + if squash { + // When squashing, the embedded type can be a pointer to a struct. + if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct { + v = v.Elem() + } + + // The final type must be a struct + if v.Kind() != reflect.Struct { + return fmt.Errorf("cannot squash non-struct type '%s'", v.Type()) + } + } else { + if strings.Index(tagValue[index+1:], "remain") != -1 { + if v.Kind() != reflect.Map { + return fmt.Errorf("error remain-tag field with invalid type: '%s'", v.Type()) + } + + ptr := v.MapRange() + for ptr.Next() { + valMap.SetMapIndex(ptr.Key(), ptr.Value()) + } + continue + } + } + if keyNameTagValue := tagValue[:index]; keyNameTagValue != "" { + keyName = keyNameTagValue + } + } else if len(tagValue) > 0 { + if tagValue == "-" { + continue + } + keyName = tagValue + } + + switch v.Kind() { + // this is an embedded struct, so handle it differently + case reflect.Struct: + x := reflect.New(v.Type()) + x.Elem().Set(v) + + vType := valMap.Type() + vKeyType := vType.Key() + vElemType := vType.Elem() + mType := reflect.MapOf(vKeyType, vElemType) + vMap := reflect.MakeMap(mType) + + // Creating a pointer to a map so that other methods can completely + // overwrite the map if need be (looking at you decodeMapFromMap). The + // indirection allows the underlying map to be settable (CanSet() == true) + // where as reflect.MakeMap returns an unsettable map. + addrVal := reflect.New(vMap.Type()) + reflect.Indirect(addrVal).Set(vMap) + + err := d.decode(keyName, x.Interface(), reflect.Indirect(addrVal)) + if err != nil { + return err + } + + // the underlying map may have been completely overwritten so pull + // it indirectly out of the enclosing value. + vMap = reflect.Indirect(addrVal) + + if squash { + for _, k := range vMap.MapKeys() { + valMap.SetMapIndex(k, vMap.MapIndex(k)) + } + } else { + valMap.SetMapIndex(reflect.ValueOf(keyName), vMap) + } + + default: + valMap.SetMapIndex(reflect.ValueOf(keyName), v) + } + } + + if val.CanAddr() { + val.Set(valMap) + } + + return nil +} + +func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) (bool, error) { + // If the input data is nil, then we want to just set the output + // pointer to be nil as well. + isNil := data == nil + if !isNil { + switch v := reflect.Indirect(reflect.ValueOf(data)); v.Kind() { + case reflect.Chan, + reflect.Func, + reflect.Interface, + reflect.Map, + reflect.Ptr, + reflect.Slice: + isNil = v.IsNil() + } + } + if isNil { + if !val.IsNil() && val.CanSet() { + nilValue := reflect.New(val.Type()).Elem() + val.Set(nilValue) + } + + return true, nil + } + + // Create an element of the concrete (non pointer) type and decode + // into that. Then set the value of the pointer to this type. + valType := val.Type() + valElemType := valType.Elem() + if val.CanSet() { + realVal := val + if realVal.IsNil() || d.config.ZeroFields { + realVal = reflect.New(valElemType) + } + + if err := d.decode(name, data, reflect.Indirect(realVal)); err != nil { + return false, err + } + + val.Set(realVal) + } else { + if err := d.decode(name, data, reflect.Indirect(val)); err != nil { + return false, err + } + } + return false, nil +} + +func (d *Decoder) decodeFunc(name string, data interface{}, val reflect.Value) error { + // Create an element of the concrete (non pointer) type and decode + // into that. Then set the value of the pointer to this type. + dataVal := reflect.Indirect(reflect.ValueOf(data)) + if val.Type() != dataVal.Type() { + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", + name, val.Type(), dataVal.Type(), data) + } + val.Set(dataVal) + return nil +} + +func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataValKind := dataVal.Kind() + valType := val.Type() + valElemType := valType.Elem() + sliceType := reflect.SliceOf(valElemType) + + // If we have a non array/slice type then we first attempt to convert. + if dataValKind != reflect.Array && dataValKind != reflect.Slice { + if d.config.WeaklyTypedInput { + switch { + // Slice and array we use the normal logic + case dataValKind == reflect.Slice, dataValKind == reflect.Array: + break + + // Empty maps turn into empty slices + case dataValKind == reflect.Map: + if dataVal.Len() == 0 { + val.Set(reflect.MakeSlice(sliceType, 0, 0)) + return nil + } + // Create slice of maps of other sizes + return d.decodeSlice(name, []interface{}{data}, val) + + case dataValKind == reflect.String && valElemType.Kind() == reflect.Uint8: + return d.decodeSlice(name, []byte(dataVal.String()), val) + + // All other types we try to convert to the slice type + // and "lift" it into it. i.e. a string becomes a string slice. + default: + // Just re-try this function with data as a slice. + return d.decodeSlice(name, []interface{}{data}, val) + } + } + + return fmt.Errorf( + "'%s': source data must be an array or slice, got %s", name, dataValKind) + } + + // If the input value is nil, then don't allocate since empty != nil + if dataValKind != reflect.Array && dataVal.IsNil() { + return nil + } + + valSlice := val + if valSlice.IsNil() || d.config.ZeroFields { + // Make a new slice to hold our result, same size as the original data. + valSlice = reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len()) + } else if valSlice.Len() > dataVal.Len() { + valSlice = valSlice.Slice(0, dataVal.Len()) + } + + // Accumulate any errors + errors := make([]string, 0) + + for i := 0; i < dataVal.Len(); i++ { + currentData := dataVal.Index(i).Interface() + for valSlice.Len() <= i { + valSlice = reflect.Append(valSlice, reflect.Zero(valElemType)) + } + currentField := valSlice.Index(i) + + fieldName := name + "[" + strconv.Itoa(i) + "]" + if err := d.decode(fieldName, currentData, currentField); err != nil { + errors = appendErrors(errors, err) + } + } + + // Finally, set the value to the slice we built up + val.Set(valSlice) + + // If there were errors, we return those + if len(errors) > 0 { + return &Error{errors} + } + + return nil +} + +func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataValKind := dataVal.Kind() + valType := val.Type() + valElemType := valType.Elem() + arrayType := reflect.ArrayOf(valType.Len(), valElemType) + + valArray := val + + if isComparable(valArray) && valArray.Interface() == reflect.Zero(valArray.Type()).Interface() || d.config.ZeroFields { + // Check input type + if dataValKind != reflect.Array && dataValKind != reflect.Slice { + if d.config.WeaklyTypedInput { + switch { + // Empty maps turn into empty arrays + case dataValKind == reflect.Map: + if dataVal.Len() == 0 { + val.Set(reflect.Zero(arrayType)) + return nil + } + + // All other types we try to convert to the array type + // and "lift" it into it. i.e. a string becomes a string array. + default: + // Just re-try this function with data as a slice. + return d.decodeArray(name, []interface{}{data}, val) + } + } + + return fmt.Errorf( + "'%s': source data must be an array or slice, got %s", name, dataValKind) + + } + if dataVal.Len() > arrayType.Len() { + return fmt.Errorf( + "'%s': expected source data to have length less or equal to %d, got %d", name, arrayType.Len(), dataVal.Len()) + } + + // Make a new array to hold our result, same size as the original data. + valArray = reflect.New(arrayType).Elem() + } + + // Accumulate any errors + errors := make([]string, 0) + + for i := 0; i < dataVal.Len(); i++ { + currentData := dataVal.Index(i).Interface() + currentField := valArray.Index(i) + + fieldName := name + "[" + strconv.Itoa(i) + "]" + if err := d.decode(fieldName, currentData, currentField); err != nil { + errors = appendErrors(errors, err) + } + } + + // Finally, set the value to the array we built up + val.Set(valArray) + + // If there were errors, we return those + if len(errors) > 0 { + return &Error{errors} + } + + return nil +} + +func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + + // If the type of the value to write to and the data match directly, + // then we just set it directly instead of recursing into the structure. + if dataVal.Type() == val.Type() { + val.Set(dataVal) + return nil + } + + dataValKind := dataVal.Kind() + switch dataValKind { + case reflect.Map: + return d.decodeStructFromMap(name, dataVal, val) + + case reflect.Struct: + // Not the most efficient way to do this but we can optimize later if + // we want to. To convert from struct to struct we go to map first + // as an intermediary. + + // Make a new map to hold our result + mapType := reflect.TypeOf((map[string]interface{})(nil)) + mval := reflect.MakeMap(mapType) + + // Creating a pointer to a map so that other methods can completely + // overwrite the map if need be (looking at you decodeMapFromMap). The + // indirection allows the underlying map to be settable (CanSet() == true) + // where as reflect.MakeMap returns an unsettable map. + addrVal := reflect.New(mval.Type()) + + reflect.Indirect(addrVal).Set(mval) + if err := d.decodeMapFromStruct(name, dataVal, reflect.Indirect(addrVal), mval); err != nil { + return err + } + + result := d.decodeStructFromMap(name, reflect.Indirect(addrVal), val) + return result + + default: + return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind()) + } +} + +func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) error { + dataValType := dataVal.Type() + if kind := dataValType.Key().Kind(); kind != reflect.String && kind != reflect.Interface { + return fmt.Errorf( + "'%s' needs a map with string keys, has '%s' keys", + name, dataValType.Key().Kind()) + } + + dataValKeys := make(map[reflect.Value]struct{}) + dataValKeysUnused := make(map[interface{}]struct{}) + for _, dataValKey := range dataVal.MapKeys() { + dataValKeys[dataValKey] = struct{}{} + dataValKeysUnused[dataValKey.Interface()] = struct{}{} + } + + targetValKeysUnused := make(map[interface{}]struct{}) + errors := make([]string, 0) + + // This slice will keep track of all the structs we'll be decoding. + // There can be more than one struct if there are embedded structs + // that are squashed. + structs := make([]reflect.Value, 1, 5) + structs[0] = val + + // Compile the list of all the fields that we're going to be decoding + // from all the structs. + type field struct { + field reflect.StructField + val reflect.Value + } + + // remainField is set to a valid field set with the "remain" tag if + // we are keeping track of remaining values. + var remainField *field + + fields := []field{} + for len(structs) > 0 { + structVal := structs[0] + structs = structs[1:] + + structType := structVal.Type() + + for i := 0; i < structType.NumField(); i++ { + fieldType := structType.Field(i) + fieldVal := structVal.Field(i) + if fieldVal.Kind() == reflect.Ptr && fieldVal.Elem().Kind() == reflect.Struct { + // Handle embedded struct pointers as embedded structs. + fieldVal = fieldVal.Elem() + } + + // If "squash" is specified in the tag, we squash the field down. + squash := d.config.Squash && fieldVal.Kind() == reflect.Struct && fieldType.Anonymous + remain := false + + // We always parse the tags cause we're looking for other tags too + tagParts := strings.Split(fieldType.Tag.Get(d.config.TagName), ",") + for _, tag := range tagParts[1:] { + if tag == "squash" { + squash = true + break + } + + if tag == "remain" { + remain = true + break + } + } + + if squash { + if fieldVal.Kind() != reflect.Struct { + errors = appendErrors(errors, + fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldVal.Kind())) + } else { + structs = append(structs, fieldVal) + } + continue + } + + // Build our field + if remain { + remainField = &field{fieldType, fieldVal} + } else { + // Normal struct field, store it away + fields = append(fields, field{fieldType, fieldVal}) + } + } + } + + // for fieldType, field := range fields { + for _, f := range fields { + field, fieldValue := f.field, f.val + fieldName := field.Name + + tagValue := field.Tag.Get(d.config.TagName) + if tagValue == "" && d.config.IgnoreUntaggedFields { + continue + } + tagValue = strings.SplitN(tagValue, ",", 2)[0] + if tagValue != "" { + fieldName = tagValue + } + + rawMapKey := reflect.ValueOf(fieldName) + rawMapVal := dataVal.MapIndex(rawMapKey) + if !rawMapVal.IsValid() { + // Do a slower search by iterating over each key and + // doing case-insensitive search. + for dataValKey := range dataValKeys { + mK, ok := dataValKey.Interface().(string) + if !ok { + // Not a string key + continue + } + + if d.config.MatchName(mK, fieldName) { + rawMapKey = dataValKey + rawMapVal = dataVal.MapIndex(dataValKey) + break + } + } + + if !rawMapVal.IsValid() { + // There was no matching key in the map for the value in + // the struct. Remember it for potential errors and metadata. + targetValKeysUnused[fieldName] = struct{}{} + continue + } + } + + if !fieldValue.IsValid() { + // This should never happen + panic("field is not valid") + } + + // If we can't set the field, then it is unexported or something, + // and we just continue onwards. + if !fieldValue.CanSet() { + continue + } + + // Delete the key we're using from the unused map so we stop tracking + delete(dataValKeysUnused, rawMapKey.Interface()) + + // If the name is empty string, then we're at the root, and we + // don't dot-join the fields. + if name != "" { + fieldName = name + "." + fieldName + } + + if err := d.decode(fieldName, rawMapVal.Interface(), fieldValue); err != nil { + errors = appendErrors(errors, err) + } + } + + // If we have a "remain"-tagged field and we have unused keys then + // we put the unused keys directly into the remain field. + if remainField != nil && len(dataValKeysUnused) > 0 { + // Build a map of only the unused values + remain := map[interface{}]interface{}{} + for key := range dataValKeysUnused { + remain[key] = dataVal.MapIndex(reflect.ValueOf(key)).Interface() + } + + // Decode it as-if we were just decoding this map onto our map. + if err := d.decodeMap(name, remain, remainField.val); err != nil { + errors = appendErrors(errors, err) + } + + // Set the map to nil so we have none so that the next check will + // not error (ErrorUnused) + dataValKeysUnused = nil + } + + if d.config.ErrorUnused && len(dataValKeysUnused) > 0 { + keys := make([]string, 0, len(dataValKeysUnused)) + for rawKey := range dataValKeysUnused { + keys = append(keys, rawKey.(string)) + } + sort.Strings(keys) + + err := fmt.Errorf("'%s' has invalid keys: %s", name, strings.Join(keys, ", ")) + errors = appendErrors(errors, err) + } + + if d.config.ErrorUnset && len(targetValKeysUnused) > 0 { + keys := make([]string, 0, len(targetValKeysUnused)) + for rawKey := range targetValKeysUnused { + keys = append(keys, rawKey.(string)) + } + sort.Strings(keys) + + err := fmt.Errorf("'%s' has unset fields: %s", name, strings.Join(keys, ", ")) + errors = appendErrors(errors, err) + } + + if len(errors) > 0 { + return &Error{errors} + } + + // Add the unused keys to the list of unused keys if we're tracking metadata + if d.config.Metadata != nil { + for rawKey := range dataValKeysUnused { + key := rawKey.(string) + if name != "" { + key = name + "." + key + } + + d.config.Metadata.Unused = append(d.config.Metadata.Unused, key) + } + for rawKey := range targetValKeysUnused { + key := rawKey.(string) + if name != "" { + key = name + "." + key + } + + d.config.Metadata.Unset = append(d.config.Metadata.Unset, key) + } + } + + return nil +} + +func isEmptyValue(v reflect.Value) bool { + switch getKind(v) { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + return false +} + +func getKind(val reflect.Value) reflect.Kind { + kind := val.Kind() + + switch { + case kind >= reflect.Int && kind <= reflect.Int64: + return reflect.Int + case kind >= reflect.Uint && kind <= reflect.Uint64: + return reflect.Uint + case kind >= reflect.Float32 && kind <= reflect.Float64: + return reflect.Float32 + default: + return kind + } +} + +func isStructTypeConvertibleToMap(typ reflect.Type, checkMapstructureTags bool, tagName string) bool { + for i := 0; i < typ.NumField(); i++ { + f := typ.Field(i) + if f.PkgPath == "" && !checkMapstructureTags { // check for unexported fields + return true + } + if checkMapstructureTags && f.Tag.Get(tagName) != "" { // check for mapstructure tags inside + return true + } + } + return false +} + +func dereferencePtrToStructIfNeeded(v reflect.Value, tagName string) reflect.Value { + if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct { + return v + } + deref := v.Elem() + derefT := deref.Type() + if isStructTypeConvertibleToMap(derefT, true, tagName) { + return deref + } + return v +} diff --git a/vendor/github.com/go-viper/mapstructure/v2/reflect_go1_19.go b/vendor/github.com/go-viper/mapstructure/v2/reflect_go1_19.go new file mode 100644 index 000000000..d0913fff6 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/reflect_go1_19.go @@ -0,0 +1,44 @@ +//go:build !go1.20 + +package mapstructure + +import "reflect" + +func isComparable(v reflect.Value) bool { + k := v.Kind() + switch k { + case reflect.Invalid: + return false + + case reflect.Array: + switch v.Type().Elem().Kind() { + case reflect.Interface, reflect.Array, reflect.Struct: + for i := 0; i < v.Type().Len(); i++ { + // if !v.Index(i).Comparable() { + if !isComparable(v.Index(i)) { + return false + } + } + return true + } + return v.Type().Comparable() + + case reflect.Interface: + // return v.Elem().Comparable() + return isComparable(v.Elem()) + + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + return false + + // if !v.Field(i).Comparable() { + if !isComparable(v.Field(i)) { + return false + } + } + return true + + default: + return v.Type().Comparable() + } +} diff --git a/vendor/github.com/go-viper/mapstructure/v2/reflect_go1_20.go b/vendor/github.com/go-viper/mapstructure/v2/reflect_go1_20.go new file mode 100644 index 000000000..f8255a1b1 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/reflect_go1_20.go @@ -0,0 +1,10 @@ +//go:build go1.20 + +package mapstructure + +import "reflect" + +// TODO: remove once we drop support for Go <1.20 +func isComparable(v reflect.Value) bool { + return v.Comparable() +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/commands/config.go b/vendor/github.com/golangci/golangci-lint/pkg/commands/config.go index a16ef6310..45c4fcd77 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/commands/config.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/config.go @@ -5,8 +5,10 @@ import ( "os" "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/spf13/viper" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/exitcodes" "github.com/golangci/golangci-lint/pkg/fsutils" ) @@ -14,7 +16,7 @@ import ( func (e *Executor) initConfig() { cmd := &cobra.Command{ Use: "config", - Short: "Config", + Short: "Config file information", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, _ []string) error { return cmd.Help() @@ -29,7 +31,12 @@ func (e *Executor) initConfig() { ValidArgsFunction: cobra.NoFileCompletions, Run: e.executePathCmd, } - e.initRunConfiguration(pathCmd) // allow --config + + fs := pathCmd.Flags() + fs.SortFlags = false // sort them as they are defined here + + initConfigFileFlagSet(fs, &e.cfg.Run) + cmd.AddCommand(pathCmd) } @@ -59,3 +66,8 @@ func (e *Executor) executePathCmd(_ *cobra.Command, _ []string) { fmt.Println(usedConfigFile) } + +func initConfigFileFlagSet(fs *pflag.FlagSet, cfg *config.Run) { + fs.StringVarP(&cfg.Config, "config", "c", "", wh("Read config from file path `PATH`")) + fs.BoolVar(&cfg.NoConfig, "no-config", false, wh("Don't read config file")) +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/commands/executor.go b/vendor/github.com/golangci/golangci-lint/pkg/commands/executor.go index 61e221cb8..d241f5656 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/commands/executor.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/executor.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "crypto/sha256" + "errors" "fmt" "io" "os" @@ -78,7 +79,7 @@ func NewExecutor(buildInfo BuildInfo) *Executor { // to setup log level early we need to parse config from command line extra time to // find `-v` option commandLineCfg, err := e.getConfigForCommandLine() - if err != nil && err != pflag.ErrHelp { + if err != nil && !errors.Is(err, pflag.ErrHelp) { e.log.Fatalf("Can't get config for command line: %s", err) } if commandLineCfg != nil { diff --git a/vendor/github.com/golangci/golangci-lint/pkg/commands/linters.go b/vendor/github.com/golangci/golangci-lint/pkg/commands/linters.go index 292713ec9..69df21154 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/commands/linters.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/linters.go @@ -2,10 +2,13 @@ package commands import ( "fmt" + "strings" "github.com/fatih/color" "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/lint/linter" ) @@ -17,8 +20,25 @@ func (e *Executor) initLinters() { ValidArgsFunction: cobra.NoFileCompletions, RunE: e.executeLinters, } + + fs := e.lintersCmd.Flags() + fs.SortFlags = false // sort them as they are defined here + + initConfigFileFlagSet(fs, &e.cfg.Run) + e.initLintersFlagSet(fs, &e.cfg.Linters) + e.rootCmd.AddCommand(e.lintersCmd) - e.initRunConfiguration(e.lintersCmd) +} + +func (e *Executor) initLintersFlagSet(fs *pflag.FlagSet, cfg *config.Linters) { + fs.StringSliceVarP(&cfg.Disable, "disable", "D", nil, wh("Disable specific linter")) + fs.BoolVar(&cfg.DisableAll, "disable-all", false, wh("Disable all linters")) + fs.StringSliceVarP(&cfg.Enable, "enable", "E", nil, wh("Enable specific linter")) + fs.BoolVar(&cfg.EnableAll, "enable-all", false, wh("Enable all linters")) + fs.BoolVar(&cfg.Fast, "fast", false, wh("Enable only fast linters from enabled linters set (first run won't be fast)")) + fs.StringSliceVarP(&cfg.Presets, "presets", "p", nil, + wh(fmt.Sprintf("Enable presets (%s) of linters. Run 'golangci-lint help linters' to see "+ + "them. This option implies option --disable-all", strings.Join(e.DBManager.AllPresets(), "|")))) } // executeLinters runs the 'linters' CLI command, which displays the supported linters. @@ -28,18 +48,9 @@ func (e *Executor) executeLinters(_ *cobra.Command, _ []string) error { return fmt.Errorf("can't get enabled linters: %w", err) } - color.Green("Enabled by your configuration linters:\n") 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 @@ -47,9 +58,13 @@ func (e *Executor) executeLinters(_ *cobra.Command, _ []string) error { if enabledLintersMap[lc.Name()] == nil { disabledLCs = append(disabledLCs, lc) + } else { + enabledLinters = append(enabledLinters, lc) } } + color.Green("Enabled by your configuration linters:\n") + printLinterConfigs(enabledLinters) color.Red("\nDisabled by your configuration linters:\n") printLinterConfigs(disabledLCs) diff --git a/vendor/github.com/golangci/golangci-lint/pkg/commands/root.go b/vendor/github.com/golangci/golangci-lint/pkg/commands/root.go index efe62ced2..4425be10f 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/commands/root.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/root.go @@ -119,7 +119,7 @@ func formatMemory(memBytes uint64) string { func getDefaultConcurrency() int { if os.Getenv(envHelpRun) == "1" { - // Make stable concurrency for README help generating builds. + // Make stable concurrency for generating help documentation. const prettyConcurrency = 8 return prettyConcurrency } @@ -165,7 +165,8 @@ func initRootFlagSet(fs *pflag.FlagSet, cfg *config.Config, needVersionOption bo fs.StringVar(&cfg.Run.CPUProfilePath, "cpu-profile-path", "", wh("Path to CPU profile output file")) fs.StringVar(&cfg.Run.MemProfilePath, "mem-profile-path", "", wh("Path to memory profile output file")) fs.StringVar(&cfg.Run.TracePath, "trace-path", "", wh("Path to trace output file")) - fs.IntVarP(&cfg.Run.Concurrency, "concurrency", "j", getDefaultConcurrency(), wh("Concurrency (default NumCPU)")) + fs.IntVarP(&cfg.Run.Concurrency, "concurrency", "j", getDefaultConcurrency(), + wh("Number of CPUs to use (Default: number of logical CPUs)")) if needVersionOption { fs.BoolVar(&cfg.Run.PrintVersion, "version", false, wh("Print version")) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/commands/run.go b/vendor/github.com/golangci/golangci-lint/pkg/commands/run.go index 9149b177b..5c7083c30 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/commands/run.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/run.go @@ -8,12 +8,14 @@ import ( "log" "os" "runtime" + "sort" "strings" "time" "github.com/fatih/color" "github.com/spf13/cobra" "github.com/spf13/pflag" + "golang.org/x/exp/maps" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/exitcodes" @@ -27,6 +29,8 @@ import ( const defaultFileMode = 0644 +const defaultTimeout = time.Minute + const ( // envFailOnWarnings value: "1" envFailOnWarnings = "FAIL_ON_WARNINGS" @@ -34,35 +38,8 @@ const ( envMemLogEvery = "GL_MEM_LOG_EVERY" ) -func getDefaultIssueExcludeHelp() string { - parts := []string{"Use or not use default excludes:"} - for _, ep := range config.DefaultExcludePatterns { - parts = append(parts, - fmt.Sprintf(" # %s %s: %s", ep.ID, ep.Linter, ep.Why), - fmt.Sprintf(" - %s", color.YellowString(ep.Pattern)), - "", - ) - } - return strings.Join(parts, "\n") -} - -func getDefaultDirectoryExcludeHelp() string { - parts := []string{"Use or not use default excluded directories:"} - for _, dir := range packages.StdExcludeDirRegexps { - parts = append(parts, fmt.Sprintf(" - %s", color.YellowString(dir))) - } - parts = append(parts, "") - return strings.Join(parts, "\n") -} - -func wh(text string) string { - return color.GreenString(text) -} - -const defaultTimeout = time.Minute - //nolint:funlen,gomnd -func initFlagSet(fs *pflag.FlagSet, cfg *config.Config, m *lintersdb.Manager, isFinalInit bool) { +func (e *Executor) initFlagSet(fs *pflag.FlagSet, cfg *config.Config, isFinalInit bool) { hideFlag := func(name string) { if err := fs.MarkHidden(name); err != nil { panic(err) @@ -77,6 +54,10 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config, m *lintersdb.Manager, is } } + // Config file config + rc := &cfg.Run + initConfigFileFlagSet(fs, rc) + // Output config oc := &cfg.Output fs.StringVar(&oc.Format, "out-format", @@ -96,9 +77,8 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config, m *lintersdb.Manager, is } // Run config - rc := &cfg.Run fs.StringVar(&rc.ModulesDownloadMode, "modules-download-mode", "", - "Modules download mode. If not empty, passed as -mod=<mode> to go tools") + wh("Modules download mode. If not empty, passed as -mod=<mode> to go tools")) fs.IntVar(&rc.ExitCodeIfIssuesFound, "issues-exit-code", exitcodes.IssuesFound, wh("Exit code when issues were found")) fs.StringVar(&rc.Go, "go", "", wh("Targeted Go version")) @@ -113,8 +93,6 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config, m *lintersdb.Manager, is fs.BoolVar(&rc.AnalyzeTests, "tests", true, wh("Analyze tests (*_test.go)")) fs.BoolVar(&rc.PrintResourcesUsage, "print-resources-usage", false, wh("Print avg and max memory usage of golangci-lint and total time")) - fs.StringVarP(&rc.Config, "config", "c", "", wh("Read config from file path `PATH`")) - fs.BoolVar(&rc.NoConfig, "no-config", false, wh("Don't read config")) fs.StringSliceVar(&rc.SkipDirs, "skip-dirs", nil, wh("Regexps of directories to skip")) fs.BoolVar(&rc.UseDefaultSkipDirs, "skip-dirs-use-default", true, getDefaultDirectoryExcludeHelp()) fs.StringSliceVar(&rc.SkipFiles, "skip-files", nil, wh("Regexps of files to skip")) @@ -122,9 +100,10 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config, m *lintersdb.Manager, is const allowParallelDesc = "Allow multiple parallel golangci-lint instances running. " + "If false (default) - golangci-lint acquires file lock on start." fs.BoolVar(&rc.AllowParallelRunners, "allow-parallel-runners", false, wh(allowParallelDesc)) - const allowSerialDesc = "Allow multiple golangci-lint instances running, but serialize them around a lock. " + + const allowSerialDesc = "Allow multiple golangci-lint instances running, but serialize them around a lock. " + "If false (default) - golangci-lint exits with an error if it fails to acquire file lock on start." fs.BoolVar(&rc.AllowSerialRunners, "allow-serial-runners", false, wh(allowSerialDesc)) + fs.BoolVar(&rc.ShowStats, "show-stats", false, wh("Show statistics per linter")) // Linters settings config lsc := &cfg.LintersSettings @@ -197,15 +176,7 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config, m *lintersdb.Manager, is // Linters config lc := &cfg.Linters - fs.StringSliceVarP(&lc.Enable, "enable", "E", nil, wh("Enable specific linter")) - fs.StringSliceVarP(&lc.Disable, "disable", "D", nil, wh("Disable specific linter")) - fs.BoolVar(&lc.EnableAll, "enable-all", false, wh("Enable all linters")) - - fs.BoolVar(&lc.DisableAll, "disable-all", false, wh("Disable all linters")) - fs.StringSliceVarP(&lc.Presets, "presets", "p", nil, - wh(fmt.Sprintf("Enable presets (%s) of linters. Run 'golangci-lint linters' to see "+ - "them. This option implies option --disable-all", strings.Join(m.AllPresets(), "|")))) - fs.BoolVar(&lc.Fast, "fast", false, wh("Run only fast linters from enabled linters set (first run won't be fast)")) + e.initLintersFlagSet(fs, lc) // Issues config ic := &cfg.Issues @@ -232,13 +203,13 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config, m *lintersdb.Manager, is wh("Show only new issues created in git patch with file path `PATH`")) fs.BoolVar(&ic.WholeFiles, "whole-files", false, wh("Show issues in any part of update files (requires new-from-rev or new-from-patch)")) - fs.BoolVar(&ic.NeedFix, "fix", false, "Fix found issues (if it's supported by the linter)") + fs.BoolVar(&ic.NeedFix, "fix", false, wh("Fix found issues (if it's supported by the linter)")) } func (e *Executor) initRunConfiguration(cmd *cobra.Command) { fs := cmd.Flags() fs.SortFlags = false // sort them as they are defined here - initFlagSet(fs, e.cfg, e.DBManager, true) + e.initFlagSet(fs, e.cfg, true) } func (e *Executor) getConfigForCommandLine() (*config.Config, error) { @@ -251,7 +222,7 @@ func (e *Executor) getConfigForCommandLine() (*config.Config, error) { // `changed` variable inside string slice vars will be shared. // Use another config variable here, not e.cfg, to not // affect main parsing by this parsing of only config option. - initFlagSet(fs, &cfg, e.DBManager, false) + e.initFlagSet(fs, &cfg, false) initVersionFlagSet(fs, &cfg) // Parse max options, even force version option: don't want @@ -261,11 +232,11 @@ func (e *Executor) getConfigForCommandLine() (*config.Config, error) { fs.Usage = func() {} // otherwise, help text will be printed twice if err := fs.Parse(os.Args); err != nil { - if err == pflag.ErrHelp { + if errors.Is(err, pflag.ErrHelp) { return nil, err } - return nil, fmt.Errorf("can't parse args: %s", err) + return nil, fmt.Errorf("can't parse args: %w", err) } return &cfg, nil @@ -408,6 +379,8 @@ func (e *Executor) runAndPrint(ctx context.Context, args []string) error { } } + e.printStats(issues) + e.setExitCodeIfIssuesFound(issues) e.fileCache.PrintStats(e.log) @@ -433,7 +406,7 @@ func (e *Executor) printReports(issues []result.Issue, path, format string) erro if file, ok := w.(io.Closer); shouldClose && ok { _ = file.Close() } - return fmt.Errorf("can't print %d issues: %s", len(issues), err) + return fmt.Errorf("can't print %d issues: %w", len(issues), err) } if file, ok := w.(io.Closer); shouldClose && ok { @@ -489,6 +462,31 @@ func (e *Executor) createPrinter(format string, w io.Writer) (printers.Printer, return p, nil } +func (e *Executor) printStats(issues []result.Issue) { + if !e.cfg.Run.ShowStats { + return + } + + if len(issues) == 0 { + e.runCmd.Println("0 issues.") + return + } + + stats := map[string]int{} + for idx := range issues { + stats[issues[idx].FromLinter]++ + } + + e.runCmd.Printf("%d issues:\n", len(issues)) + + keys := maps.Keys(stats) + sort.Strings(keys) + + for _, key := range keys { + e.runCmd.Printf("* %s: %d\n", key, stats[key]) + } +} + // executeRun executes the 'run' CLI command, which runs the linters. func (e *Executor) executeRun(_ *cobra.Command, args []string) { needTrackResources := e.cfg.Run.IsVerbose || e.cfg.Run.PrintResourcesUsage @@ -609,3 +607,28 @@ func watchResources(ctx context.Context, done chan struct{}, logger logutils.Log logger.Infof("Execution took %s", time.Since(startedAt)) close(done) } + +func getDefaultIssueExcludeHelp() string { + parts := []string{color.GreenString("Use or not use default excludes:")} + for _, ep := range config.DefaultExcludePatterns { + parts = append(parts, + fmt.Sprintf(" # %s %s: %s", ep.ID, ep.Linter, ep.Why), + fmt.Sprintf(" - %s", color.YellowString(ep.Pattern)), + "", + ) + } + return strings.Join(parts, "\n") +} + +func getDefaultDirectoryExcludeHelp() string { + parts := []string{color.GreenString("Use or not use default excluded directories:")} + for _, dir := range packages.StdExcludeDirRegexps { + parts = append(parts, fmt.Sprintf(" - %s", color.YellowString(dir))) + } + parts = append(parts, "") + return strings.Join(parts, "\n") +} + +func wh(text string) string { + return color.GreenString(text) +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/commands/version.go b/vendor/github.com/golangci/golangci-lint/pkg/commands/version.go index bb7732250..10ab7c1bb 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/commands/version.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/version.go @@ -38,7 +38,7 @@ func (e *Executor) initVersion() { Short: "Version", Args: cobra.NoArgs, ValidArgsFunction: cobra.NoFileCompletions, - RunE: func(cmd *cobra.Command, _ []string) error { + RunE: func(_ *cobra.Command, _ []string) error { if e.cfg.Version.Debug { info, ok := debug.ReadBuildInfo() if !ok { diff --git a/vendor/github.com/golangci/golangci-lint/pkg/config/config.go b/vendor/github.com/golangci/golangci-lint/pkg/config/config.go index 7941f428f..a5483a20e 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/config/config.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/config/config.go @@ -41,13 +41,13 @@ type Version struct { Debug bool `mapstructure:"debug"` } -func IsGreaterThanOrEqualGo121(v string) bool { +func IsGreaterThanOrEqualGo122(v string) bool { v1, err := hcversion.NewVersion(strings.TrimPrefix(v, "go")) if err != nil { return false } - limit, err := hcversion.NewVersion("1.21") + limit, err := hcversion.NewVersion("1.22") if err != nil { return false } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/config/issues.go b/vendor/github.com/golangci/golangci-lint/pkg/config/issues.go index 5968d83d5..27dcf8105 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/config/issues.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/config/issues.go @@ -139,16 +139,16 @@ type BaseRule struct { func (b *BaseRule) Validate(minConditionsCount int) error { if err := validateOptionalRegex(b.Path); err != nil { - return fmt.Errorf("invalid path regex: %v", err) + return fmt.Errorf("invalid path regex: %w", err) } if err := validateOptionalRegex(b.PathExcept); err != nil { - return fmt.Errorf("invalid path-except regex: %v", err) + return fmt.Errorf("invalid path-except regex: %w", err) } if err := validateOptionalRegex(b.Text); err != nil { - return fmt.Errorf("invalid text regex: %v", err) + return fmt.Errorf("invalid text regex: %w", err) } if err := validateOptionalRegex(b.Source); err != nil { - return fmt.Errorf("invalid source regex: %v", err) + return fmt.Errorf("invalid source regex: %w", err) } nonBlank := 0 if len(b.Linters) > 0 { diff --git a/vendor/github.com/golangci/golangci-lint/pkg/config/linters_settings.go b/vendor/github.com/golangci/golangci-lint/pkg/config/linters_settings.go index 0fee9f81e..a3206f597 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/config/linters_settings.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/config/linters_settings.go @@ -74,6 +74,9 @@ var defaultLintersSettings = LintersSettings{ MaxDeclLines: 1, MaxDeclChars: 30, }, + Inamedparam: INamedParamSettings{ + SkipSingleParam: false, + }, InterfaceBloat: InterfaceBloatSettings{ Max: 10, }, @@ -104,6 +107,12 @@ var defaultLintersSettings = LintersSettings{ RequireSpecific: false, AllowUnused: false, }, + PerfSprint: PerfSprintSettings{ + IntConversion: true, + ErrError: false, + ErrorF: true, + SprintF1: true, + }, Prealloc: PreallocSettings{ Simple: true, RangeLoops: true, @@ -114,9 +123,13 @@ var defaultLintersSettings = LintersSettings{ Qualified: false, }, SlogLint: SlogLintSettings{ + NoMixedArgs: true, KVOnly: false, AttrOnly: false, + ContextOnly: false, + StaticMsg: false, NoRawKeys: false, + KeyNamingCase: "", ArgsOnSepLines: false, }, TagAlign: TagAlignSettings{ @@ -206,6 +219,7 @@ type LintersSettings struct { Grouper GrouperSettings Ifshort IfshortSettings ImportAs ImportAsSettings + Inamedparam INamedParamSettings InterfaceBloat InterfaceBloatSettings Ireturn IreturnSettings Lll LllSettings @@ -222,20 +236,23 @@ type LintersSettings struct { NoLintLint NoLintLintSettings NoNamedReturns NoNamedReturnsSettings ParallelTest ParallelTestSettings + PerfSprint PerfSprintSettings Prealloc PreallocSettings Predeclared PredeclaredSettings Promlinter PromlinterSettings + ProtoGetter ProtoGetterSettings Reassign ReassignSettings Revive ReviveSettings RowsErrCheck RowsErrCheckSettings SlogLint SlogLintSettings + Spancheck SpancheckSettings Staticcheck StaticCheckSettings Structcheck StructCheckSettings Stylecheck StaticCheckSettings TagAlign TagAlignSettings Tagliatelle TagliatelleSettings - Testifylint TestifylintSettings Tenv TenvSettings + Testifylint TestifylintSettings Testpackage TestpackageSettings Thelper ThelperSettings Unparam UnparamSettings @@ -279,9 +296,10 @@ type DepGuardSettings struct { } type DepGuardList struct { - Files []string `mapstructure:"files"` - Allow []string `mapstructure:"allow"` - Deny []DepGuardDeny `mapstructure:"deny"` + ListMode string `mapstructure:"list-mode"` + Files []string `mapstructure:"files"` + Allow []string `mapstructure:"allow"` + Deny []DepGuardDeny `mapstructure:"deny"` } type DepGuardDeny struct { @@ -345,6 +363,7 @@ type ExhaustiveSettings struct { PackageScopeOnly bool `mapstructure:"package-scope-only"` ExplicitExhaustiveMap bool `mapstructure:"explicit-exhaustive-map"` ExplicitExhaustiveSwitch bool `mapstructure:"explicit-exhaustive-switch"` + DefaultCaseRequired bool `mapstructure:"default-case-required"` } type ExhaustiveStructSettings struct { @@ -427,14 +446,15 @@ type GocognitSettings struct { } type GoConstSettings struct { - IgnoreTests bool `mapstructure:"ignore-tests"` - MatchWithConstants bool `mapstructure:"match-constant"` - MinStringLen int `mapstructure:"min-len"` - MinOccurrencesCount int `mapstructure:"min-occurrences"` - ParseNumbers bool `mapstructure:"numbers"` - NumberMin int `mapstructure:"min"` - NumberMax int `mapstructure:"max"` - IgnoreCalls bool `mapstructure:"ignore-calls"` + IgnoreStrings string `mapstructure:"ignore-strings"` + IgnoreTests bool `mapstructure:"ignore-tests"` + MatchWithConstants bool `mapstructure:"match-constant"` + MinStringLen int `mapstructure:"min-len"` + MinOccurrencesCount int `mapstructure:"min-occurrences"` + ParseNumbers bool `mapstructure:"numbers"` + NumberMin int `mapstructure:"min"` + NumberMax int `mapstructure:"max"` + IgnoreCalls bool `mapstructure:"ignore-calls"` } type GoCriticSettings struct { @@ -599,6 +619,10 @@ type ImportAsAlias struct { Alias string } +type INamedParamSettings struct { + SkipSingleParam bool `mapstructure:"skip-single-param"` +} + type InterfaceBloatSettings struct { Max int `mapstructure:"max"` } @@ -636,8 +660,9 @@ type MalignedSettings struct { } type MisspellSettings struct { + Mode string `mapstructure:"mode"` Locale string - // TODO(ldez): v2 the options must be renamed to `IgnoredRules`. + // TODO(ldez): v2 the option must be renamed to `IgnoredRules`. IgnoreWords []string `mapstructure:"ignore-words"` } @@ -675,11 +700,19 @@ type NoLintLintSettings struct { type NoNamedReturnsSettings struct { ReportErrorInDefer bool `mapstructure:"report-error-in-defer"` } + type ParallelTestSettings struct { IgnoreMissing bool `mapstructure:"ignore-missing"` IgnoreMissingSubtests bool `mapstructure:"ignore-missing-subtests"` } +type PerfSprintSettings struct { + IntConversion bool `mapstructure:"int-conversion"` + ErrError bool `mapstructure:"err-error"` + ErrorF bool `mapstructure:"errorf"` + SprintF1 bool `mapstructure:"sprintf1"` +} + type PreallocSettings struct { Simple bool RangeLoops bool `mapstructure:"range-loops"` @@ -696,6 +729,13 @@ type PromlinterSettings struct { DisabledLinters []string `mapstructure:"disabled-linters"` } +type ProtoGetterSettings struct { + SkipGeneratedBy []string `mapstructure:"skip-generated-by"` + SkipFiles []string `mapstructure:"skip-files"` + SkipAnyGenerated bool `mapstructure:"skip-any-generated"` + ReplaceFirstArgInAppend bool `mapstructure:"replace-first-arg-in-append"` +} + type ReassignSettings struct { Patterns []string `mapstructure:"patterns"` } @@ -725,10 +765,19 @@ type RowsErrCheckSettings struct { } 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"` + NoMixedArgs bool `mapstructure:"no-mixed-args"` + KVOnly bool `mapstructure:"kv-only"` + AttrOnly bool `mapstructure:"attr-only"` + ContextOnly bool `mapstructure:"context-only"` + StaticMsg bool `mapstructure:"static-msg"` + NoRawKeys bool `mapstructure:"no-raw-keys"` + KeyNamingCase string `mapstructure:"key-naming-case"` + ArgsOnSepLines bool `mapstructure:"args-on-sep-lines"` +} + +type SpancheckSettings struct { + Checks []string `mapstructure:"checks"` + IgnoreCheckSignatures []string `mapstructure:"ignore-check-signatures"` } type StaticCheckSettings struct { @@ -764,13 +813,19 @@ type TagliatelleSettings struct { } type TestifylintSettings struct { - EnableAll bool `mapstructure:"enable-all"` - EnabledCheckers []string `mapstructure:"enable"` + EnableAll bool `mapstructure:"enable-all"` + DisableAll bool `mapstructure:"disable-all"` + EnabledCheckers []string `mapstructure:"enable"` + DisabledCheckers []string `mapstructure:"disable"` ExpectedActual struct { ExpVarPattern string `mapstructure:"pattern"` } `mapstructure:"expected-actual"` + RequireError struct { + FnPattern string `mapstructure:"fn-pattern"` + } `mapstructure:"require-error"` + SuiteExtraAssertCall struct { Mode string `mapstructure:"mode"` } `mapstructure:"suite-extra-assert-call"` diff --git a/vendor/github.com/golangci/golangci-lint/pkg/config/reader.go b/vendor/github.com/golangci/golangci-lint/pkg/config/reader.go index de203876e..0c4fa13b0 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/config/reader.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/config/reader.go @@ -5,12 +5,12 @@ import ( "fmt" "os" "path/filepath" + "slices" "strings" + "github.com/go-viper/mapstructure/v2" "github.com/mitchellh/go-homedir" - "github.com/mitchellh/mapstructure" "github.com/spf13/viper" - "golang.org/x/exp/slices" "github.com/golangci/golangci-lint/pkg/exitcodes" "github.com/golangci/golangci-lint/pkg/fsutils" @@ -38,11 +38,11 @@ func (r *FileReader) Read() error { configFile, err := r.parseConfigOption() if err != nil { - if err == errConfigDisabled { + if errors.Is(err, errConfigDisabled) { return nil } - return fmt.Errorf("can't parse --config option: %s", err) + return fmt.Errorf("can't parse --config option: %w", err) } if configFile != "" { @@ -65,7 +65,7 @@ func (r *FileReader) parseConfig() error { return nil } - return fmt.Errorf("can't read viper config: %s", err) + return fmt.Errorf("can't read viper config: %w", err) } usedConfigFile := viper.ConfigFileUsed() @@ -100,11 +100,11 @@ func (r *FileReader) parseConfig() error { // Needed for forbidigo. mapstructure.TextUnmarshallerHookFunc(), ))); err != nil { - return fmt.Errorf("can't unmarshal config by viper: %s", err) + return fmt.Errorf("can't unmarshal config by viper: %w", err) } if err := r.validateConfig(); err != nil { - return fmt.Errorf("can't validate config: %s", err) + return fmt.Errorf("can't validate config: %w", err) } if r.cfg.InternalTest { // just for testing purposes: to detect config file usage @@ -138,7 +138,7 @@ func (r *FileReader) validateConfig() error { } for i, rule := range c.Issues.ExcludeRules { if err := rule.Validate(); err != nil { - return fmt.Errorf("error in exclude rule #%d: %v", i, err) + return fmt.Errorf("error in exclude rule #%d: %w", i, err) } } if len(c.Severity.Rules) > 0 && c.Severity.Default == "" { @@ -146,11 +146,11 @@ func (r *FileReader) validateConfig() error { } for i, rule := range c.Severity.Rules { if err := rule.Validate(); err != nil { - return fmt.Errorf("error in severity rule #%d: %v", i, err) + return fmt.Errorf("error in severity rule #%d: %w", i, err) } } if err := c.LintersSettings.Govet.Validate(); err != nil { - return fmt.Errorf("error in govet config: %v", err) + return fmt.Errorf("error in govet config: %w", err) } return nil } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/config/run.go b/vendor/github.com/golangci/golangci-lint/pkg/config/run.go index ff812d0a2..2bb21d9fa 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/config/run.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/config/run.go @@ -37,4 +37,6 @@ type Run struct { AllowParallelRunners bool `mapstructure:"allow-parallel-runners"` AllowSerialRunners bool `mapstructure:"allow-serial-runners"` + + ShowStats bool `mapstructure:"show-stats"` } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/fsutils/fsutils.go b/vendor/github.com/golangci/golangci-lint/pkg/fsutils/fsutils.go index a39c105e4..715a3a4af 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/fsutils/fsutils.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/fsutils/fsutils.go @@ -34,7 +34,7 @@ func Getwd() (string, error) { evaledWd, err := EvalSymlinks(cachedWd) if err != nil { - cachedWd, cachedWdError = "", fmt.Errorf("can't eval symlinks on wd %s: %s", cachedWd, err) + cachedWd, cachedWdError = "", fmt.Errorf("can't eval symlinks on wd %s: %w", cachedWd, err) return } @@ -70,13 +70,13 @@ func ShortestRelPath(path, wd string) (string, error) { var err error wd, err = Getwd() if err != nil { - return "", fmt.Errorf("can't get working directory: %s", err) + return "", fmt.Errorf("can't get working directory: %w", err) } } evaledPath, err := EvalSymlinks(path) if err != nil { - return "", fmt.Errorf("can't eval symlinks for path %s: %s", path, err) + return "", fmt.Errorf("can't eval symlinks for path %s: %w", path, err) } path = evaledPath @@ -92,7 +92,7 @@ func ShortestRelPath(path, wd string) (string, error) { relPath, err := filepath.Rel(wd, absPath) if err != nil { - return "", fmt.Errorf("can't get relative path for path %s and root %s: %s", + return "", fmt.Errorf("can't get relative path for path %s and root %s: %w", absPath, wd, err) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/asciicheck.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/asciicheck.go index df301b417..7e7ee05e5 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/asciicheck.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/asciicheck.go @@ -8,10 +8,12 @@ import ( ) func NewAsciicheck() *goanalysis.Linter { + a := asciicheck.NewAnalyzer() + return goanalysis.NewLinter( - "asciicheck", - "Simple linter to check that your code does not contain non-ASCII identifiers", - []*analysis.Analyzer{asciicheck.NewAnalyzer()}, + a.Name, + a.Doc, + []*analysis.Analyzer{a}, nil, ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/bidichk.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/bidichk.go index e1b347176..ef266f55c 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/bidichk.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/bidichk.go @@ -51,7 +51,7 @@ func NewBiDiChkFuncName(cfg *config.BiDiChkSettings) *goanalysis.Linter { } return goanalysis.NewLinter( - "bidichk", + a.Name, "Checks for dangerous unicode character sequences", []*analysis.Analyzer{a}, cfgMap, diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/cyclop.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/cyclop.go index 5ad65f122..f76c55552 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/cyclop.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/cyclop.go @@ -8,8 +8,6 @@ import ( "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" ) -const cyclopName = "cyclop" - func NewCyclop(settings *config.Cyclop) *goanalysis.Linter { a := analyzer.NewAnalyzer() @@ -31,7 +29,7 @@ func NewCyclop(settings *config.Cyclop) *goanalysis.Linter { } return goanalysis.NewLinter( - cyclopName, + a.Name, "checks function and package cyclomatic complexity", []*analysis.Analyzer{a}, cfg, diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/depguard.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/depguard.go index 23986708c..49e471df8 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/depguard.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/depguard.go @@ -15,8 +15,9 @@ func NewDepguard(settings *config.DepGuardSettings) *goanalysis.Linter { if settings != nil { for s, rule := range settings.Rules { list := &depguard.List{ - Files: rule.Files, - Allow: rule.Allow, + ListMode: rule.ListMode, + Files: rule.Files, + Allow: rule.Allow, } // because of bug with Viper parsing (split on dot) we use a list of struct instead of a map. diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/errchkjson.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/errchkjson.go index 171de00a4..1af4450b4 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/errchkjson.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/errchkjson.go @@ -23,10 +23,8 @@ func NewErrChkJSONFuncName(cfg *config.ErrChkJSONSettings) *goanalysis.Linter { } return goanalysis.NewLinter( - "errchkjson", - "Checks types passed to the json encoding functions. "+ - "Reports unsupported types and optionally reports occasions, "+ - "where the check for the returned error can be omitted.", + a.Name, + a.Doc, []*analysis.Analyzer{a}, cfgMap, ).WithLoadMode(goanalysis.LoadModeTypesInfo) diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/errname.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/errname.go index 96564cfa8..193a7aba7 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/errname.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/errname.go @@ -8,10 +8,12 @@ import ( ) func NewErrName() *goanalysis.Linter { + a := analyzer.New() + return goanalysis.NewLinter( - "errname", - "Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`.", - []*analysis.Analyzer{analyzer.New()}, + a.Name, + a.Doc, + []*analysis.Analyzer{a}, nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/exhaustive.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/exhaustive.go index 3824afa0b..fe58e10f0 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/exhaustive.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/exhaustive.go @@ -23,6 +23,7 @@ func NewExhaustive(settings *config.ExhaustiveSettings) *goanalysis.Linter { exhaustive.PackageScopeOnlyFlag: settings.PackageScopeOnly, exhaustive.ExplicitExhaustiveMapFlag: settings.ExplicitExhaustiveMap, exhaustive.ExplicitExhaustiveSwitchFlag: settings.ExplicitExhaustiveSwitch, + exhaustive.DefaultCaseRequiredFlag: settings.DefaultCaseRequired, }, } } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/runner.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/runner.go index 46871bc5b..a8660b612 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/runner.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/runner.go @@ -17,6 +17,7 @@ import ( "sort" "sync" + "golang.org/x/exp/maps" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/packages" @@ -159,10 +160,7 @@ func (r *runner) buildActionFactDeps(act *action, a *analysis.Analyzer, pkg *pac act.objectFacts = make(map[objectFactKey]analysis.Fact) act.packageFacts = make(map[packageFactKey]analysis.Fact) - paths := make([]string, 0, len(pkg.Imports)) - for path := range pkg.Imports { - paths = append(paths, path) - } + paths := maps.Keys(pkg.Imports) sort.Strings(paths) // for determinism for _, path := range paths { dep := r.makeAction(a, pkg.Imports[path], initialPkgs, actions, actAlloc) @@ -212,10 +210,7 @@ func (r *runner) prepareAnalysis(pkgs []*packages.Package, } } - allActions := make([]*action, 0, len(actions)) - for _, act := range actions { - allActions = append(allActions, act) - } + allActions := maps.Values(actions) debugf("Built %d actions", len(actions)) diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/runner_action.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/runner_action.go index 5ded9fac9..6b57cb0c9 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/runner_action.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/runner_action.go @@ -9,7 +9,6 @@ import ( "runtime/debug" "time" - "github.com/hashicorp/go-multierror" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/packages" "golang.org/x/tools/go/types/objectpath" @@ -126,20 +125,16 @@ func (act *action) analyze() { }(time.Now()) // Report an error if any dependency failures. - var depErrors *multierror.Error + var depErrors error for _, dep := range act.deps { if dep.err == nil { continue } - depErrors = multierror.Append(depErrors, errors.Unwrap(dep.err)) + depErrors = errors.Join(depErrors, errors.Unwrap(dep.err)) } if depErrors != nil { - depErrors.ErrorFormat = func(e []error) string { - return fmt.Sprintf("failed prerequisites: %v", e) - } - - act.err = depErrors + act.err = fmt.Errorf("failed prerequisites: %w", depErrors) return } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/gochecksumtype.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/gochecksumtype.go index fcc0cad58..5516179df 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/gochecksumtype.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/gochecksumtype.go @@ -45,7 +45,7 @@ func NewGoCheckSumType() *goanalysis.Linter { `Run exhaustiveness checks on Go "sum types"`, []*analysis.Analyzer{analyzer}, nil, - ).WithIssuesReporter(func(ctx *linter.Context) []goanalysis.Issue { + ).WithIssuesReporter(func(_ *linter.Context) []goanalysis.Issue { return resIssues }).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goconst.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goconst.go index e277509d2..b51885275 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goconst.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goconst.go @@ -53,6 +53,7 @@ func NewGoconst(settings *config.GoConstSettings) *goanalysis.Linter { func runGoconst(pass *analysis.Pass, settings *config.GoConstSettings) ([]goanalysis.Issue, error) { cfg := goconstAPI.Config{ + IgnoreStrings: settings.IgnoreStrings, IgnoreTests: settings.IgnoreTests, MatchWithConstants: settings.MatchWithConstants, MinStringLength: settings.MinStringLen, diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/gocritic.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/gocritic.go index 1319c72d9..3cf43afc6 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/gocritic.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/gocritic.go @@ -14,6 +14,7 @@ import ( "github.com/go-critic/go-critic/checkers" gocriticlinter "github.com/go-critic/go-critic/linter" + "golang.org/x/exp/maps" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" @@ -219,10 +220,7 @@ func (w *goCriticWrapper) configureCheckerInfo(info *gocriticlinter.CheckerInfo, info.Name, k) } - var supportedKeys []string - for sk := range info.Params { - supportedKeys = append(supportedKeys, sk) - } + supportedKeys := maps.Keys(info.Params) sort.Strings(supportedKeys) return fmt.Errorf("checker %s config param %s doesn't exist, all existing: %s", @@ -311,11 +309,7 @@ func (s *goCriticSettingsWrapper) checkerTagsDebugf() { tagToCheckers := s.buildTagToCheckersMap() - allTags := make([]string, 0, len(tagToCheckers)) - for tag := range tagToCheckers { - allTags = append(allTags, tag) - } - + allTags := maps.Keys(tagToCheckers) sort.Strings(allTags) goCriticDebugf("All gocritic existing tags and checks:") @@ -609,12 +603,7 @@ func intersectStringSlice(s1, s2 []string) []string { } func sprintAllowedCheckerNames(allowedNames map[string]bool) string { - namesSlice := make([]string, 0, len(allowedNames)) - - for name := range allowedNames { - namesSlice = append(namesSlice, name) - } - + namesSlice := maps.Keys(allowedNames) return sprintStrings(namesSlice) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/golint.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/golint.go index a6fc73c9e..22ca59048 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/golint.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/golint.go @@ -56,7 +56,7 @@ func runGoLint(pass *analysis.Pass, settings *config.GoLintSettings) ([]goanalys ps, err := l.LintPkg(pass.Files, pass.Fset, pass.Pkg, pass.TypesInfo) if err != nil { - return nil, fmt.Errorf("can't lint %d files: %s", len(pass.Files), err) + return nil, fmt.Errorf("can't lint %d files: %w", len(pass.Files), err) } if len(ps) == 0 { diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goprintffuncname.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goprintffuncname.go index c5516dc7f..e513718ba 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goprintffuncname.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goprintffuncname.go @@ -8,10 +8,12 @@ import ( ) func NewGoPrintfFuncName() *goanalysis.Linter { + a := analyzer.Analyzer + return goanalysis.NewLinter( - "goprintffuncname", - "Checks that printf-like functions are named with `f` at the end", - []*analysis.Analyzer{analyzer.Analyzer}, + a.Name, + a.Doc, + []*analysis.Analyzer{a}, nil, ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/govet.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/govet.go index 4e16fb142..a0d33835d 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/govet.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/govet.go @@ -1,6 +1,8 @@ package golinters import ( + "slices" + "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/appends" "golang.org/x/tools/go/analysis/passes/asmdecl" @@ -170,36 +172,25 @@ func analyzersFromConfig(settings *config.GovetSettings) []*analysis.Analyzer { } func isAnalyzerEnabled(name string, cfg *config.GovetSettings, defaultAnalyzers []*analysis.Analyzer) bool { - if cfg.EnableAll { - for _, n := range cfg.Disable { - if n == name { - return false - } - } - return true + // TODO(ldez) remove loopclosure when go1.23 + if name == loopclosure.Analyzer.Name && config.IsGreaterThanOrEqualGo122(cfg.Go) { + return false } - // Raw for loops should be OK on small slice lengths. - for _, n := range cfg.Enable { - if n == name { - return true - } - } + switch { + case cfg.EnableAll: + return !slices.Contains(cfg.Disable, name) - for _, n := range cfg.Disable { - if n == name { - return false - } - } + case slices.Contains(cfg.Enable, name): + return true - if cfg.DisableAll { + case slices.Contains(cfg.Disable, name): return false - } - for _, a := range defaultAnalyzers { - if a.Name == name { - return true - } + case cfg.DisableAll: + return false + + default: + return slices.ContainsFunc(defaultAnalyzers, func(a *analysis.Analyzer) bool { return a.Name == name }) } - return false } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/grouper.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/grouper.go index 9feecf3ba..41761f2ae 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/grouper.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/grouper.go @@ -25,7 +25,7 @@ func NewGrouper(settings *config.GrouperSettings) *goanalysis.Linter { return goanalysis.NewLinter( "grouper", - "An analyzer to analyze expression groups.", + "Analyze expression groups.", []*analysis.Analyzer{grouper.New()}, linterCfg, ).WithLoadMode(goanalysis.LoadModeSyntax) diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/ifshort.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/ifshort.go index 1574eaf70..50e2c172e 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/ifshort.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/ifshort.go @@ -19,10 +19,12 @@ func NewIfshort(settings *config.IfshortSettings) *goanalysis.Linter { } } + a := analyzer.Analyzer + return goanalysis.NewLinter( - "ifshort", - "Checks that your code uses short syntax for if-statements whenever possible", - []*analysis.Analyzer{analyzer.Analyzer}, + a.Name, + a.Doc, + []*analysis.Analyzer{a}, cfg, ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/inamedparam.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/inamedparam.go index 290630755..887f3db2a 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/inamedparam.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/inamedparam.go @@ -4,16 +4,27 @@ import ( "github.com/macabu/inamedparam" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" ) -func NewINamedParam() *goanalysis.Linter { +func NewINamedParam(settings *config.INamedParamSettings) *goanalysis.Linter { a := inamedparam.Analyzer + var cfg map[string]map[string]any + + if settings != nil { + cfg = map[string]map[string]any{ + a.Name: { + "skip-single-param": settings.SkipSingleParam, + }, + } + } + return goanalysis.NewLinter( a.Name, a.Doc, []*analysis.Analyzer{a}, - nil, + cfg, ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/ineffassign.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/ineffassign.go index c87bb2fa5..ac5eb20ad 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/ineffassign.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/ineffassign.go @@ -8,10 +8,12 @@ import ( ) func NewIneffassign() *goanalysis.Linter { + a := ineffassign.Analyzer + return goanalysis.NewLinter( - "ineffassign", + a.Name, "Detects when assignments to existing variables are not used", - []*analysis.Analyzer{ineffassign.Analyzer}, + []*analysis.Analyzer{a}, nil, ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/ireturn.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/ireturn.go index 34dc09d26..dc09dad0e 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/ireturn.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/ireturn.go @@ -16,8 +16,9 @@ func NewIreturn(settings *config.IreturnSettings) *goanalysis.Linter { cfg := map[string]map[string]any{} if settings != nil { cfg[a.Name] = map[string]any{ - "allow": strings.Join(settings.Allow, ","), - "reject": strings.Join(settings.Reject, ","), + "allow": strings.Join(settings.Allow, ","), + "reject": strings.Join(settings.Reject, ","), + "nonolint": true, } } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/lll.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/lll.go index 9ed320120..61498087a 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/lll.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/lll.go @@ -2,6 +2,7 @@ package golinters import ( "bufio" + "errors" "fmt" "go/token" "os" @@ -82,7 +83,7 @@ func getLLLIssuesForFile(filename string, maxLineLen int, tabSpaces string) ([]r f, err := os.Open(filename) if err != nil { - return nil, fmt.Errorf("can't open file %s: %s", filename, err) + return nil, fmt.Errorf("can't open file %s: %w", filename, err) } defer f.Close() @@ -127,7 +128,7 @@ func getLLLIssuesForFile(filename string, maxLineLen int, tabSpaces string) ([]r } if err := scanner.Err(); err != nil { - if err == bufio.ErrTooLong && maxLineLen < bufio.MaxScanTokenSize { + if errors.Is(err, bufio.ErrTooLong) && maxLineLen < bufio.MaxScanTokenSize { // scanner.Scan() might fail if the line is longer than bufio.MaxScanTokenSize // In the case where the specified maxLineLen is smaller than bufio.MaxScanTokenSize // we can return this line as a long line instead of returning an error. @@ -148,7 +149,7 @@ func getLLLIssuesForFile(filename string, maxLineLen int, tabSpaces string) ([]r FromLinter: lllName, }) } else { - return nil, fmt.Errorf("can't scan file %s: %s", filename, err) + return nil, fmt.Errorf("can't scan file %s: %w", filename, err) } } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/mirror.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/mirror.go index 4adc001a1..d6e2bb06a 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/mirror.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/mirror.go @@ -39,7 +39,7 @@ func NewMirror() *goanalysis.Linter { Pos: i.Start, } - if len(i.InlineFix) > 0 { + if i.InlineFix != "" { issue.Replacement = &result.Replacement{ Inline: &result.InlineFix{ StartCol: i.Start.Column - 1, diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/misspell.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/misspell.go index ce2b79a7c..0f69cdb87 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/misspell.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/misspell.go @@ -29,7 +29,7 @@ func NewMisspell(settings *config.MisspellSettings) *goanalysis.Linter { return goanalysis.NewLinter( misspellName, - "Finds commonly misspelled English words in comments", + "Finds commonly misspelled English words", []*analysis.Analyzer{analyzer}, nil, ).WithContextSetter(func(lintCtx *linter.Context) { @@ -40,7 +40,7 @@ func NewMisspell(settings *config.MisspellSettings) *goanalysis.Linter { return nil, ruleErr } - issues, err := runMisspell(lintCtx, pass, replacer) + issues, err := runMisspell(lintCtx, pass, replacer, settings.Mode) if err != nil { return nil, err } @@ -60,15 +60,16 @@ func NewMisspell(settings *config.MisspellSettings) *goanalysis.Linter { }).WithLoadMode(goanalysis.LoadModeSyntax) } -func runMisspell(lintCtx *linter.Context, pass *analysis.Pass, replacer *misspell.Replacer) ([]goanalysis.Issue, error) { +func runMisspell(lintCtx *linter.Context, pass *analysis.Pass, replacer *misspell.Replacer, mode string) ([]goanalysis.Issue, error) { fileNames := getFileNames(pass) var issues []goanalysis.Issue for _, filename := range fileNames { - lintIssues, err := runMisspellOnFile(lintCtx, filename, replacer) + lintIssues, err := runMisspellOnFile(lintCtx, filename, replacer, mode) if err != nil { return nil, err } + for i := range lintIssues { issues = append(issues, goanalysis.NewIssue(&lintIssues[i], pass)) } @@ -104,25 +105,36 @@ func createMisspellReplacer(settings *config.MisspellSettings) (*misspell.Replac return replacer, nil } -func runMisspellOnFile(lintCtx *linter.Context, filename string, replacer *misspell.Replacer) ([]result.Issue, error) { - var res []result.Issue +func runMisspellOnFile(lintCtx *linter.Context, filename string, replacer *misspell.Replacer, mode string) ([]result.Issue, error) { fileContent, err := lintCtx.FileCache.GetFileBytes(filename) if err != nil { - return nil, fmt.Errorf("can't get file %s contents: %s", filename, err) + return nil, fmt.Errorf("can't get file %s contents: %w", filename, err) } - // use r.Replace, not r.ReplaceGo because r.ReplaceGo doesn't find - // issues inside strings: it searches only inside comments. r.Replace - // searches all words: it treats input as a plain text. A standalone misspell - // tool uses r.Replace by default. - _, diffs := replacer.Replace(string(fileContent)) + // `r.ReplaceGo` doesn't find issues inside strings: it searches only inside comments. + // `r.Replace` searches all words: it treats input as a plain text. + // The standalone misspell tool uses `r.Replace` by default. + var replace func(input string) (string, []misspell.Diff) + switch strings.ToLower(mode) { + case "restricted": + replace = replacer.ReplaceGo + default: + replace = replacer.Replace + } + + _, diffs := replace(string(fileContent)) + + var res []result.Issue + for _, diff := range diffs { text := fmt.Sprintf("`%s` is a misspelling of `%s`", diff.Original, diff.Corrected) + pos := token.Position{ Filename: filename, Line: diff.Line, Column: diff.Column + 1, } + replacement := &result.Replacement{ Inline: &result.InlineFix{ StartCol: diff.Column, diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/musttag.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/musttag.go index d9ea7efc7..72d919582 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/musttag.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/musttag.go @@ -1,7 +1,7 @@ package golinters import ( - "go.tmz.dev/musttag" + "go-simpler.org/musttag" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/nakedret.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/nakedret.go index d276ac6a9..6153860fb 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/nakedret.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/nakedret.go @@ -8,20 +8,18 @@ import ( "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" ) -const nakedretName = "nakedret" - func NewNakedret(settings *config.NakedretSettings) *goanalysis.Linter { var maxLines int if settings != nil { maxLines = settings.MaxFuncLines } - analyzer := nakedret.NakedReturnAnalyzer(uint(maxLines)) + a := nakedret.NakedReturnAnalyzer(uint(maxLines)) return goanalysis.NewLinter( - nakedretName, - "Finds naked returns in functions greater than a specified function length", - []*analysis.Analyzer{analyzer}, + a.Name, + a.Doc, + []*analysis.Analyzer{a}, nil, ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/noctx.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/noctx.go index b7fcd2a73..cff9c97dc 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/noctx.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/noctx.go @@ -8,10 +8,12 @@ import ( ) func NewNoctx() *goanalysis.Linter { + a := noctx.Analyzer + return goanalysis.NewLinter( - "noctx", - "noctx finds sending http request without context.Context", - []*analysis.Analyzer{noctx.Analyzer}, + a.Name, + "Finds sending http request without context.Context", + []*analysis.Analyzer{a}, nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/nolintlint.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/nolintlint.go index 00ef1f833..ae372ab79 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/nolintlint.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/nolintlint.go @@ -76,7 +76,7 @@ func runNoLintLint(pass *analysis.Pass, settings *config.NoLintLintSettings) ([] lintIssues, err := lnt.Run(pass.Fset, nodes...) if err != nil { - return nil, fmt.Errorf("linter failed to run: %s", err) + return nil, fmt.Errorf("linter failed to run: %w", err) } var issues []goanalysis.Issue diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/nolintlint/nolintlint.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/nolintlint/nolintlint.go index 9c6b10f38..a245561a0 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/nolintlint/nolintlint.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/nolintlint/nolintlint.go @@ -19,12 +19,12 @@ type BaseIssue struct { replacement *result.Replacement } -//nolint:gocritic // TODO must be change in the future. +//nolint:gocritic // TODO(ldez) must be change in the future. func (b BaseIssue) Position() token.Position { return b.position } -//nolint:gocritic // TODO must be change in the future. +//nolint:gocritic // TODO(ldez) must be change in the future. func (b BaseIssue) Replacement() *result.Replacement { return b.replacement } @@ -33,53 +33,49 @@ type ExtraLeadingSpace struct { BaseIssue } -//nolint:gocritic // TODO must be change in the future. +//nolint:gocritic // TODO(ldez) must be change in the future. func (i ExtraLeadingSpace) Details() string { return fmt.Sprintf("directive `%s` should not have more than one leading space", i.fullDirective) } -//nolint:gocritic // TODO must be change in the future. func (i ExtraLeadingSpace) String() string { return toString(i) } type NotMachine struct { BaseIssue } -//nolint:gocritic // TODO must be change in the future. +//nolint:gocritic // TODO(ldez) must be change in the future. func (i NotMachine) Details() string { expected := i.fullDirective[:2] + strings.TrimLeftFunc(i.fullDirective[2:], unicode.IsSpace) return fmt.Sprintf("directive `%s` should be written without leading space as `%s`", i.fullDirective, expected) } -//nolint:gocritic // TODO must be change in the future. func (i NotMachine) String() string { return toString(i) } type NotSpecific struct { BaseIssue } -//nolint:gocritic // TODO must be change in the future. +//nolint:gocritic // TODO(ldez) must be change in the future. func (i NotSpecific) Details() string { return fmt.Sprintf("directive `%s` should mention specific linter such as `%s:my-linter`", i.fullDirective, i.directiveWithOptionalLeadingSpace) } -//nolint:gocritic // TODO must be change in the future. func (i NotSpecific) String() string { return toString(i) } type ParseError struct { BaseIssue } -//nolint:gocritic // TODO must be change in the future. +//nolint:gocritic // TODO(ldez) must be change in the future. func (i ParseError) Details() string { return fmt.Sprintf("directive `%s` should match `%s[:<comma-separated-linters>] [// <explanation>]`", i.fullDirective, i.directiveWithOptionalLeadingSpace) } -//nolint:gocritic // TODO must be change in the future. func (i ParseError) String() string { return toString(i) } type NoExplanation struct { @@ -87,13 +83,12 @@ type NoExplanation struct { fullDirectiveWithoutExplanation string } -//nolint:gocritic // TODO must be change in the future. +//nolint:gocritic // TODO(ldez) must be change in the future. func (i NoExplanation) Details() string { return fmt.Sprintf("directive `%s` should provide explanation such as `%s // this is why`", i.fullDirective, i.fullDirectiveWithoutExplanation) } -//nolint:gocritic // TODO must be change in the future. func (i NoExplanation) String() string { return toString(i) } type UnusedCandidate struct { @@ -101,7 +96,7 @@ type UnusedCandidate struct { ExpectedLinter string } -//nolint:gocritic // TODO must be change in the future. +//nolint:gocritic // TODO(ldez) must be change in the future. func (i UnusedCandidate) Details() string { details := fmt.Sprintf("directive `%s` is unused", i.fullDirective) if i.ExpectedLinter != "" { @@ -110,7 +105,6 @@ func (i UnusedCandidate) Details() string { return details } -//nolint:gocritic // TODO must be change in the future. func (i UnusedCandidate) String() string { return toString(i) } func toString(i Issue) string { @@ -185,7 +179,7 @@ func (l Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) { } directiveWithOptionalLeadingSpace := "//" - if len(leadingSpace) > 0 { + if leadingSpace != "" { directiveWithOptionalLeadingSpace += " " } @@ -202,7 +196,7 @@ func (l Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) { } // check for, report and eliminate leading spaces, so we can check for other issues - if len(leadingSpace) > 0 { + if leadingSpace != "" { removeWhitespace := &result.Replacement{ Inline: &result.InlineFix{ StartCol: pos.Column + 1, @@ -231,7 +225,7 @@ func (l Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) { lintersText, explanation := fullMatches[1], fullMatches[2] var linters []string - if len(lintersText) > 0 && !strings.HasPrefix(lintersText, "all") { + if lintersText != "" && !strings.HasPrefix(lintersText, "all") { lls := strings.Split(lintersText, ",") linters = make([]string, 0, len(lls)) rangeStart := (pos.Column - 1) + len("//") + len(leadingSpace) + len("nolint:") diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/paralleltest.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/paralleltest.go index 4c03952c1..c49f74aa2 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/paralleltest.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/paralleltest.go @@ -22,8 +22,8 @@ func NewParallelTest(settings *config.ParallelTestSettings) *goanalysis.Linter { } return goanalysis.NewLinter( - "paralleltest", - "paralleltest detects missing usage of t.Parallel() method in your Go test", + a.Name, + "Detects missing usage of t.Parallel() method in your Go test", []*analysis.Analyzer{a}, cfg, ).WithLoadMode(goanalysis.LoadModeTypesInfo) diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/perfsprint.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/perfsprint.go index fb248a85d..acaa3a522 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/perfsprint.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/perfsprint.go @@ -4,16 +4,28 @@ import ( "github.com/catenacyber/perfsprint/analyzer" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" ) -func NewPerfSprint() *goanalysis.Linter { - a := analyzer.Analyzer +func NewPerfSprint(settings *config.PerfSprintSettings) *goanalysis.Linter { + a := analyzer.New() + + cfg := map[string]map[string]any{ + a.Name: {"fiximports": false}, + } + + if settings != nil { + cfg[a.Name]["int-conversion"] = settings.IntConversion + cfg[a.Name]["err-error"] = settings.ErrError + cfg[a.Name]["errorf"] = settings.ErrorF + cfg[a.Name]["sprintf1"] = settings.SprintF1 + } return goanalysis.NewLinter( a.Name, a.Doc, []*analysis.Analyzer{a}, - nil, + cfg, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/protogetter.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/protogetter.go index 23325ad55..9a5e7b4db 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/protogetter.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/protogetter.go @@ -6,18 +6,33 @@ import ( "github.com/ghostiam/protogetter" "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" "github.com/golangci/golangci-lint/pkg/result" ) -func NewProtoGetter() *goanalysis.Linter { +func NewProtoGetter(settings *config.ProtoGetterSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue - a := protogetter.NewAnalyzer() + var cfg protogetter.Config + if settings != nil { + cfg = protogetter.Config{ + SkipGeneratedBy: settings.SkipGeneratedBy, + SkipFiles: settings.SkipFiles, + SkipAnyGenerated: settings.SkipAnyGenerated, + ReplaceFirstArgInAppend: settings.ReplaceFirstArgInAppend, + } + } + cfg.Mode = protogetter.GolangciLintMode + + a := protogetter.NewAnalyzer(&cfg) a.Run = func(pass *analysis.Pass) (any, error) { - pgIssues := protogetter.Run(pass, protogetter.GolangciLintMode) + pgIssues, err := protogetter.Run(pass, &cfg) + if err != nil { + return nil, err + } issues := make([]goanalysis.Issue, len(pgIssues)) for i, issue := range pgIssues { diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/revive.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/revive.go index 28231957c..46d8b1c9c 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/revive.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/revive.go @@ -34,7 +34,7 @@ type jsonObject struct { // NewRevive returns a new Revive linter. // -//nolint:dupl + func NewRevive(settings *config.ReviveSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue @@ -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.4/config/config.go#L15 +// Extracted from https://github.com/mgechev/revive/blob/v1.3.7/config/config.go#L15 var defaultRules = []lint.Rule{ &rule.VarDeclarationsRule{}, &rule.PackageCommentsRule{}, @@ -290,7 +290,7 @@ var allRules = append([]lint.Rule{ &rule.ModifiesValRecRule{}, &rule.ConstantLogicalExprRule{}, &rule.BoolLiteralRule{}, - &rule.ImportsBlacklistRule{}, + &rule.ImportsBlocklistRule{}, &rule.FunctionResultsLimitRule{}, &rule.MaxPublicStructsRule{}, &rule.RangeValInClosureRule{}, @@ -327,6 +327,9 @@ var allRules = append([]lint.Rule{ &rule.RedundantImportAlias{}, &rule.ImportAliasNamingRule{}, &rule.EnforceMapStyleRule{}, + &rule.EnforceRepeatedArgTypeStyleRule{}, + &rule.EnforceSliceStyleRule{}, + &rule.MaxControlNestingRule{}, }, defaultRules...) const defaultConfidence = 0.8 @@ -349,8 +352,8 @@ func normalizeConfig(cfg *lint.Config) { } if cfg.EnableAllRules { // Add to the configuration all rules not yet present in it - for _, rule := range allRules { - ruleName := rule.Name() + for _, r := range allRules { + ruleName := r.Name() _, alreadyInConf := cfg.Rules[ruleName] if alreadyInConf { continue diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/rowserrcheck.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/rowserrcheck.go index 5a66d62e7..d67efd069 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/rowserrcheck.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/rowserrcheck.go @@ -14,12 +14,12 @@ func NewRowsErrCheck(settings *config.RowsErrCheckSettings) *goanalysis.Linter { pkgs = settings.Packages } - analyzer := rowserr.NewAnalyzer(pkgs...) + a := rowserr.NewAnalyzer(pkgs...) return goanalysis.NewLinter( - "rowserrcheck", - "checks whether Err of rows is checked successfully", - []*analysis.Analyzer{analyzer}, + a.Name, + "checks whether Rows.Err of rows is checked successfully", + []*analysis.Analyzer{a}, nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/sloglint.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/sloglint.go index b506d187f..acea90d53 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/sloglint.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/sloglint.go @@ -12,9 +12,13 @@ func NewSlogLint(settings *config.SlogLintSettings) *goanalysis.Linter { var opts *sloglint.Options if settings != nil { opts = &sloglint.Options{ + NoMixedArgs: settings.NoMixedArgs, KVOnly: settings.KVOnly, AttrOnly: settings.AttrOnly, + ContextOnly: settings.ContextOnly, + StaticMsg: settings.StaticMsg, NoRawKeys: settings.NoRawKeys, + KeyNamingCase: settings.KeyNamingCase, ArgsOnSepLines: settings.ArgsOnSepLines, } } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/spancheck.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/spancheck.go new file mode 100644 index 000000000..934124477 --- /dev/null +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/spancheck.go @@ -0,0 +1,29 @@ +package golinters + +import ( + "github.com/jjti/go-spancheck" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" +) + +func NewSpancheck(settings *config.SpancheckSettings) *goanalysis.Linter { + cfg := spancheck.NewDefaultConfig() + + if settings != nil { + if settings.Checks != nil { + cfg.EnabledChecks = settings.Checks + } + + if settings.IgnoreCheckSignatures != nil { + cfg.IgnoreChecksSignaturesSlice = settings.IgnoreCheckSignatures + } + } + + a := spancheck.NewAnalyzerWithConfig(cfg) + + return goanalysis. + NewLinter(a.Name, a.Doc, []*analysis.Analyzer{a}, nil). + WithLoadMode(goanalysis.LoadModeTypesInfo) +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/sqlclosecheck.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/sqlclosecheck.go index ff2c0c08f..e63b292a2 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/sqlclosecheck.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/sqlclosecheck.go @@ -8,12 +8,12 @@ import ( ) func NewSQLCloseCheck() *goanalysis.Linter { + a := analyzer.NewAnalyzer() + return goanalysis.NewLinter( - "sqlclosecheck", - "Checks that sql.Rows and sql.Stmt are closed.", - []*analysis.Analyzer{ - analyzer.NewAnalyzer(), - }, + a.Name, + a.Doc, + []*analysis.Analyzer{a}, nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/stylecheck.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/stylecheck.go index 2e1e21c5b..d9b0f87c8 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/stylecheck.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/stylecheck.go @@ -15,7 +15,7 @@ func NewStylecheck(settings *config.StaticCheckSettings) *goanalysis.Linter { // `scconfig.Analyzer` is a singleton, then it's not possible to have more than one instance for all staticcheck "sub-linters". // When we will merge the 4 "sub-linters", the problem will disappear: https://github.com/golangci/golangci-lint/issues/357 // Currently only stylecheck analyzer has a configuration in staticcheck. - scconfig.Analyzer.Run = func(pass *analysis.Pass) (any, error) { + scconfig.Analyzer.Run = func(_ *analysis.Pass) (any, error) { return cfg, nil } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/testifylint.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/testifylint.go index 83bae2868..d9cfc04f6 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/testifylint.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/testifylint.go @@ -14,14 +14,22 @@ func NewTestifylint(settings *config.TestifylintSettings) *goanalysis.Linter { cfg := make(map[string]map[string]any) if settings != nil { cfg[a.Name] = map[string]any{ - "enable-all": settings.EnableAll, + "enable-all": settings.EnableAll, + "disable-all": settings.DisableAll, } if len(settings.EnabledCheckers) > 0 { cfg[a.Name]["enable"] = settings.EnabledCheckers } + if len(settings.DisabledCheckers) > 0 { + cfg[a.Name]["disable"] = settings.DisabledCheckers + } + if p := settings.ExpectedActual.ExpVarPattern; p != "" { cfg[a.Name]["expected-actual.pattern"] = p } + if p := settings.RequireError.FnPattern; p != "" { + cfg[a.Name]["require-error.fn-pattern"] = p + } if m := settings.SuiteExtraAssertCall.Mode; m != "" { cfg[a.Name]["suite-extra-assert-call.mode"] = m } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/thelper.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/thelper.go index 84a8e9e8b..1ae85ef42 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/thelper.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/thelper.go @@ -4,6 +4,7 @@ import ( "strings" "github.com/kulti/thelper/pkg/analyzer" + "golang.org/x/exp/maps" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" @@ -42,10 +43,7 @@ func NewThelper(cfg *config.ThelperSettings) *goanalysis.Linter { linterLogger.Fatalf("thelper: at least one option must be enabled") } - var args []string - for k := range opts { - args = append(args, k) - } + args := maps.Keys(opts) cfgMap := map[string]map[string]any{ a.Name: { @@ -54,8 +52,8 @@ func NewThelper(cfg *config.ThelperSettings) *goanalysis.Linter { } return goanalysis.NewLinter( - "thelper", - "thelper detects Go test helpers without t.Helper() call and checks the consistency of test helpers", + a.Name, + a.Doc, []*analysis.Analyzer{a}, cfgMap, ).WithLoadMode(goanalysis.LoadModeTypesInfo) diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/tparallel.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/tparallel.go index cbe97516c..643f2c271 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/tparallel.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/tparallel.go @@ -8,10 +8,11 @@ import ( ) func NewTparallel() *goanalysis.Linter { + a := tparallel.Analyzer return goanalysis.NewLinter( - "tparallel", - "tparallel detects inappropriate usage of t.Parallel() method in your Go test codes", - []*analysis.Analyzer{tparallel.Analyzer}, + a.Name, + a.Doc, + []*analysis.Analyzer{a}, nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/unused.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/unused.go index 89ae7e98a..a93061c96 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/unused.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/unused.go @@ -47,7 +47,7 @@ func NewUnused(settings *config.UnusedSettings, scSettings *config.StaticCheckSe "Checks Go code for unused constants, variables, functions and types", []*analysis.Analyzer{analyzer}, nil, - ).WithIssuesReporter(func(lintCtx *linter.Context) []goanalysis.Issue { + ).WithIssuesReporter(func(_ *linter.Context) []goanalysis.Issue { return resIssues }).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/varcheck.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/varcheck.go index 495c5b59f..ea735672f 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/varcheck.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/varcheck.go @@ -30,7 +30,7 @@ func NewVarcheck(settings *config.VarCheckSettings) *goanalysis.Linter { "Finds unused global variables and constants", []*analysis.Analyzer{analyzer}, nil, - ).WithContextSetter(func(lintCtx *linter.Context) { + ).WithContextSetter(func(_ *linter.Context) { analyzer.Run = func(pass *analysis.Pass) (any, error) { issues := runVarCheck(pass, settings) diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/wastedassign.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/wastedassign.go index 92798d4f7..9038c827d 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/wastedassign.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/wastedassign.go @@ -8,10 +8,12 @@ import ( ) func NewWastedAssign() *goanalysis.Linter { + a := wastedassign.Analyzer + return goanalysis.NewLinter( - "wastedassign", - "wastedassign finds wasted assignment statements.", - []*analysis.Analyzer{wastedassign.Analyzer}, + a.Name, + "Finds wasted assignment statements", + []*analysis.Analyzer{a}, nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/whitespace.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/whitespace.go index e5941fa5d..5487b1016 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/whitespace.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/whitespace.go @@ -2,7 +2,6 @@ package golinters import ( "fmt" - "go/token" "sync" "github.com/ultraware/whitespace" @@ -16,7 +15,6 @@ import ( const whitespaceName = "whitespace" -//nolint:dupl func NewWhitespace(settings *config.WhitespaceSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue @@ -24,25 +22,22 @@ func NewWhitespace(settings *config.WhitespaceSettings) *goanalysis.Linter { var wsSettings whitespace.Settings if settings != nil { wsSettings = whitespace.Settings{ + Mode: whitespace.RunningModeGolangCI, MultiIf: settings.MultiIf, MultiFunc: settings.MultiFunc, } } - analyzer := &analysis.Analyzer{ - Name: whitespaceName, - Doc: goanalysis.TheOnlyanalyzerDoc, - Run: goanalysis.DummyRun, - } + a := whitespace.NewAnalyzer(&wsSettings) return goanalysis.NewLinter( - whitespaceName, - "Tool for detection of leading and trailing whitespace", - []*analysis.Analyzer{analyzer}, + a.Name, + a.Doc, + []*analysis.Analyzer{a}, nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (any, error) { - issues, err := runWhitespace(lintCtx, pass, wsSettings) + ).WithContextSetter(func(_ *linter.Context) { + a.Run = func(pass *analysis.Pass) (any, error) { + issues, err := runWhitespace(pass, wsSettings) if err != nil { return nil, err } @@ -62,46 +57,45 @@ func NewWhitespace(settings *config.WhitespaceSettings) *goanalysis.Linter { }).WithLoadMode(goanalysis.LoadModeSyntax) } -func runWhitespace(lintCtx *linter.Context, pass *analysis.Pass, wsSettings whitespace.Settings) ([]goanalysis.Issue, error) { - var messages []whitespace.Message - for _, file := range pass.Files { - messages = append(messages, whitespace.Run(file, pass.Fset, wsSettings)...) - } - - if len(messages) == 0 { - return nil, nil - } +func runWhitespace(pass *analysis.Pass, wsSettings whitespace.Settings) ([]goanalysis.Issue, error) { + lintIssues := whitespace.Run(pass, &wsSettings) - issues := make([]goanalysis.Issue, len(messages)) - for k, i := range messages { - issue := result.Issue{ - Pos: token.Position{ - Filename: i.Pos.Filename, - Line: i.Pos.Line, - }, - LineRange: &result.Range{From: i.Pos.Line, To: i.Pos.Line}, - Text: i.Message, - FromLinter: whitespaceName, - Replacement: &result.Replacement{}, + issues := make([]goanalysis.Issue, len(lintIssues)) + for i, issue := range lintIssues { + report := &result.Issue{ + FromLinter: whitespaceName, + Pos: pass.Fset.PositionFor(issue.Diagnostic, false), + Text: issue.Message, } - bracketLine, err := lintCtx.LineCache.GetLine(issue.Pos.Filename, issue.Pos.Line) - if err != nil { - return nil, fmt.Errorf("failed to get line %s:%d: %w", issue.Pos.Filename, issue.Pos.Line, err) - } + switch issue.MessageType { + case whitespace.MessageTypeRemove: + if len(issue.LineNumbers) == 0 { + continue + } + + report.LineRange = &result.Range{ + From: issue.LineNumbers[0], + To: issue.LineNumbers[len(issue.LineNumbers)-1], + } + + report.Replacement = &result.Replacement{NeedOnlyDelete: true} + + case whitespace.MessageTypeAdd: + report.Pos = pass.Fset.PositionFor(issue.FixStart, false) + report.Replacement = &result.Replacement{ + Inline: &result.InlineFix{ + StartCol: 0, + Length: 1, + NewString: "\n\t", + }, + } - switch i.Type { - case whitespace.MessageTypeLeading: - issue.LineRange.To++ // cover two lines by the issue: opening bracket "{" (issue.Pos.Line) and following empty line - case whitespace.MessageTypeTrailing: - issue.LineRange.From-- // cover two lines by the issue: closing bracket "}" (issue.Pos.Line) and preceding empty line - issue.Pos.Line-- // set in sync with LineRange.From to not break fixer and other code features - case whitespace.MessageTypeAddAfter: - bracketLine += "\n" + default: + return nil, fmt.Errorf("unknown message type: %v", issue.MessageType) } - issue.Replacement.NewLines = []string{bracketLine} - issues[k] = goanalysis.NewIssue(&issue, pass) + issues[i] = goanalysis.NewIssue(report, pass) } return issues, nil diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/wrapcheck.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/wrapcheck.go index 098eb87ba..6d25db427 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/wrapcheck.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/wrapcheck.go @@ -8,8 +8,6 @@ import ( "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" ) -const wrapcheckName = "wrapcheck" - func NewWrapcheck(settings *config.WrapcheckSettings) *goanalysis.Linter { cfg := wrapcheck.NewDefaultConfig() if settings != nil { @@ -30,7 +28,7 @@ func NewWrapcheck(settings *config.WrapcheckSettings) *goanalysis.Linter { a := wrapcheck.NewAnalyzer(cfg) return goanalysis.NewLinter( - wrapcheckName, + a.Name, a.Doc, []*analysis.Analyzer{a}, nil, diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/wsl.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/wsl.go index 05697a629..3b090a686 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/wsl.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/wsl.go @@ -1,89 +1,39 @@ package golinters import ( - "sync" - - "github.com/bombsimon/wsl/v3" + "github.com/bombsimon/wsl/v4" "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" - "github.com/golangci/golangci-lint/pkg/result" ) -const wslName = "wsl" - -// NewWSL returns a new WSL linter. func NewWSL(settings *config.WSLSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - - conf := wsl.DefaultConfig() - + var conf *wsl.Configuration if settings != nil { - conf.StrictAppend = settings.StrictAppend - conf.AllowAssignAndCallCuddle = settings.AllowAssignAndCallCuddle - conf.AllowAssignAndAnythingCuddle = settings.AllowAssignAndAnythingCuddle - conf.AllowMultiLineAssignCuddle = settings.AllowMultiLineAssignCuddle - conf.ForceCaseTrailingWhitespaceLimit = settings.ForceCaseTrailingWhitespaceLimit - conf.AllowTrailingComment = settings.AllowTrailingComment - conf.AllowSeparatedLeadingComment = settings.AllowSeparatedLeadingComment - conf.AllowCuddleDeclaration = settings.AllowCuddleDeclaration - conf.AllowCuddleWithCalls = settings.AllowCuddleWithCalls - conf.AllowCuddleWithRHS = settings.AllowCuddleWithRHS - conf.ForceCuddleErrCheckAndAssign = settings.ForceCuddleErrCheckAndAssign - conf.ErrorVariableNames = settings.ErrorVariableNames - conf.ForceExclusiveShortDeclarations = settings.ForceExclusiveShortDeclarations + conf = &wsl.Configuration{ + StrictAppend: settings.StrictAppend, + AllowAssignAndCallCuddle: settings.AllowAssignAndCallCuddle, + AllowAssignAndAnythingCuddle: settings.AllowAssignAndAnythingCuddle, + AllowMultiLineAssignCuddle: settings.AllowMultiLineAssignCuddle, + ForceCaseTrailingWhitespaceLimit: settings.ForceCaseTrailingWhitespaceLimit, + AllowTrailingComment: settings.AllowTrailingComment, + AllowSeparatedLeadingComment: settings.AllowSeparatedLeadingComment, + AllowCuddleDeclaration: settings.AllowCuddleDeclaration, + AllowCuddleWithCalls: settings.AllowCuddleWithCalls, + AllowCuddleWithRHS: settings.AllowCuddleWithRHS, + ForceCuddleErrCheckAndAssign: settings.ForceCuddleErrCheckAndAssign, + ErrorVariableNames: settings.ErrorVariableNames, + ForceExclusiveShortDeclarations: settings.ForceExclusiveShortDeclarations, + } } - analyzer := &analysis.Analyzer{ - Name: goanalysis.TheOnlyAnalyzerName, - Doc: goanalysis.TheOnlyanalyzerDoc, - Run: func(pass *analysis.Pass) (any, error) { - issues := runWSL(pass, &conf) - - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() - - return nil, nil - }, - } + a := wsl.NewAnalyzer(conf) return goanalysis.NewLinter( - wslName, - "Whitespace Linter - Forces you to use empty lines!", - []*analysis.Analyzer{analyzer}, + a.Name, + a.Doc, + []*analysis.Analyzer{a}, nil, - ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeSyntax) -} - -func runWSL(pass *analysis.Pass, conf *wsl.Configuration) []goanalysis.Issue { - if conf == nil { - return nil - } - - files := getFileNames(pass) - wslErrors, _ := wsl.NewProcessorWithConfig(*conf).ProcessFiles(files) - if len(wslErrors) == 0 { - return nil - } - - var issues []goanalysis.Issue - for _, err := range wslErrors { - issues = append(issues, goanalysis.NewIssue(&result.Issue{ - FromLinter: wslName, - Pos: err.Position, - Text: err.Reason, - }, pass)) - } - - return issues + ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/zerologlint.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/zerologlint.go index a37bca12e..edde72665 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/zerologlint.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/zerologlint.go @@ -8,10 +8,12 @@ import ( ) func NewZerologLint() *goanalysis.Linter { + a := zerologlint.Analyzer + return goanalysis.NewLinter( - "zerologlint", - "Detects the wrong usage of `zerolog` that a user forgets to dispatch with `Send` or `Msg`.", - []*analysis.Analyzer{zerologlint.Analyzer}, + a.Name, + a.Doc, + []*analysis.Analyzer{a}, nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/lint/linter/config.go b/vendor/github.com/golangci/golangci-lint/pkg/lint/linter/config.go index c911b5613..ed5e5508c 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/lint/linter/config.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/lint/linter/config.go @@ -134,11 +134,11 @@ func (lc *Config) Name() string { } func (lc *Config) WithNoopFallback(cfg *config.Config) *Config { - if cfg != nil && config.IsGreaterThanOrEqualGo121(cfg.Run.Go) { + if cfg != nil && config.IsGreaterThanOrEqualGo122(cfg.Run.Go) { lc.Linter = &Noop{ name: lc.Linter.Name(), desc: lc.Linter.Desc(), - run: func(pass *analysis.Pass) (any, error) { + run: func(_ *analysis.Pass) (any, error) { return nil, nil }, } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/custom_linters.go b/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/custom_linters.go index d0eaa7905..188c14d91 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/custom_linters.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/custom_linters.go @@ -1,12 +1,11 @@ package lintersdb import ( + "errors" "fmt" - "os" "path/filepath" "plugin" - "github.com/hashicorp/go-multierror" "github.com/spf13/viper" "golang.org/x/tools/go/analysis" @@ -71,7 +70,7 @@ func (m *Manager) getAnalyzerPlugin(path string, settings any) ([]*analysis.Anal 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) + return nil, fmt.Errorf("could not get absolute representation of config file path %q: %w", configFilePath, err) } path = filepath.Join(filepath.Dir(absConfigFilePath), path) } @@ -94,8 +93,7 @@ func (m *Manager) lookupPlugin(plug *plugin.Plugin, settings any) ([]*analysis.A 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 nil, errors.Join(err, errP) } return analyzers, nil @@ -116,11 +114,8 @@ func (m *Manager) lookupAnalyzerPlugin(plug *plugin.Plugin) ([]*analysis.Analyze 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") - } + 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 { diff --git a/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/enabled_set.go b/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/enabled_set.go index c5c7874e4..6f7b91b4d 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/enabled_set.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/enabled_set.go @@ -4,6 +4,8 @@ import ( "os" "sort" + "golang.org/x/exp/maps" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" @@ -115,10 +117,7 @@ func (es EnabledSet) GetOptimizedLinters() ([]*linter.Config, error) { es.verbosePrintLintersStatus(resultLintersSet) es.combineGoAnalysisLinters(resultLintersSet) - var resultLinters []*linter.Config - for _, lc := range resultLintersSet { - resultLinters = append(resultLinters, lc) - } + resultLinters := maps.Values(resultLintersSet) // Make order of execution of linters (go/analysis metalinter and unused) stable. sort.Slice(resultLinters, func(i, j int) bool { @@ -185,10 +184,7 @@ func (es EnabledSet) combineGoAnalysisLinters(linters map[string]*linter.Config) ml := goanalysis.NewMetaLinter(goanalysisLinters) - var presets []string - for p := range goanalysisPresets { - presets = append(presets, p) - } + presets := maps.Keys(goanalysisPresets) mlConfig := &linter.Config{ Linter: ml, diff --git a/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/manager.go b/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/manager.go index fd329ce57..fdc73ec73 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/manager.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/manager.go @@ -105,6 +105,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { grouperCfg *config.GrouperSettings ifshortCfg *config.IfshortSettings importAsCfg *config.ImportAsSettings + inamedparamCfg *config.INamedParamSettings interfaceBloatCfg *config.InterfaceBloatSettings ireturnCfg *config.IreturnSettings lllCfg *config.LllSettings @@ -121,13 +122,16 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { noLintLintCfg *config.NoLintLintSettings noNamedReturnsCfg *config.NoNamedReturnsSettings parallelTestCfg *config.ParallelTestSettings + perfSprintCfg *config.PerfSprintSettings preallocCfg *config.PreallocSettings predeclaredCfg *config.PredeclaredSettings promlinterCfg *config.PromlinterSettings + protogetterCfg *config.ProtoGetterSettings reassignCfg *config.ReassignSettings reviveCfg *config.ReviveSettings rowserrcheckCfg *config.RowsErrCheckSettings sloglintCfg *config.SlogLintSettings + spancheckCfg *config.SpancheckSettings staticcheckCfg *config.StaticCheckSettings structcheckCfg *config.StructCheckSettings stylecheckCfg *config.StaticCheckSettings @@ -187,6 +191,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { grouperCfg = &m.cfg.LintersSettings.Grouper ifshortCfg = &m.cfg.LintersSettings.Ifshort importAsCfg = &m.cfg.LintersSettings.ImportAs + inamedparamCfg = &m.cfg.LintersSettings.Inamedparam interfaceBloatCfg = &m.cfg.LintersSettings.InterfaceBloat ireturnCfg = &m.cfg.LintersSettings.Ireturn lllCfg = &m.cfg.LintersSettings.Lll @@ -202,14 +207,17 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { nlreturnCfg = &m.cfg.LintersSettings.Nlreturn noLintLintCfg = &m.cfg.LintersSettings.NoLintLint noNamedReturnsCfg = &m.cfg.LintersSettings.NoNamedReturns - preallocCfg = &m.cfg.LintersSettings.Prealloc parallelTestCfg = &m.cfg.LintersSettings.ParallelTest + perfSprintCfg = &m.cfg.LintersSettings.PerfSprint + preallocCfg = &m.cfg.LintersSettings.Prealloc predeclaredCfg = &m.cfg.LintersSettings.Predeclared promlinterCfg = &m.cfg.LintersSettings.Promlinter + protogetterCfg = &m.cfg.LintersSettings.ProtoGetter reassignCfg = &m.cfg.LintersSettings.Reassign reviveCfg = &m.cfg.LintersSettings.Revive rowserrcheckCfg = &m.cfg.LintersSettings.RowsErrCheck sloglintCfg = &m.cfg.LintersSettings.SlogLint + spancheckCfg = &m.cfg.LintersSettings.Spancheck staticcheckCfg = &m.cfg.LintersSettings.Staticcheck structcheckCfg = &m.cfg.LintersSettings.Structcheck stylecheckCfg = &m.cfg.LintersSettings.Stylecheck @@ -228,25 +236,22 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { wrapcheckCfg = &m.cfg.LintersSettings.Wrapcheck wslCfg = &m.cfg.LintersSettings.WSL - if govetCfg != nil { - govetCfg.Go = m.cfg.Run.Go - } + govetCfg.Go = m.cfg.Run.Go - if gocriticCfg != nil { - gocriticCfg.Go = trimGoVersion(m.cfg.Run.Go) - } + gocriticCfg.Go = trimGoVersion(m.cfg.Run.Go) - if gofumptCfg != nil && gofumptCfg.LangVersion == "" { + if gofumptCfg.LangVersion == "" { gofumptCfg.LangVersion = m.cfg.Run.Go } - if staticcheckCfg != nil && staticcheckCfg.GoVersion == "" { + // staticcheck related linters. + if staticcheckCfg.GoVersion == "" { staticcheckCfg.GoVersion = trimGoVersion(m.cfg.Run.Go) } - if gosimpleCfg != nil && gosimpleCfg.GoVersion == "" { + if gosimpleCfg.GoVersion == "" { gosimpleCfg.GoVersion = trimGoVersion(m.cfg.Run.Go) } - if stylecheckCfg != nil && stylecheckCfg.GoVersion != "" { + if stylecheckCfg.GoVersion != "" { stylecheckCfg.GoVersion = trimGoVersion(m.cfg.Run.Go) } } @@ -580,7 +585,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithLoadForGoAnalysis(). WithURL("https://github.com/julz/importas"), - linter.NewConfig(golinters.NewINamedParam()). + linter.NewConfig(golinters.NewINamedParam(inamedparamCfg)). WithSince("v1.55.0"). WithPresets(linter.PresetStyle). WithURL("https://github.com/macabu/inamedparam"), @@ -654,7 +659,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithSince("v1.51.0"). WithLoadForGoAnalysis(). WithPresets(linter.PresetStyle, linter.PresetBugs). - WithURL("https://github.com/tmzane/musttag"), + WithURL("https://github.com/go-simpler/musttag"), linter.NewConfig(golinters.NewNakedret(nakedretCfg)). WithSince("v1.19.0"). @@ -712,7 +717,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetStyle, linter.PresetTest). WithURL("https://github.com/kunwardeep/paralleltest"), - linter.NewConfig(golinters.NewPerfSprint()). + linter.NewConfig(golinters.NewPerfSprint(perfSprintCfg)). WithSince("v1.55.0"). WithLoadForGoAnalysis(). WithPresets(linter.PresetPerformance). @@ -733,7 +738,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetStyle). WithURL("https://github.com/yeya24/promlinter"), - linter.NewConfig(golinters.NewProtoGetter()). + linter.NewConfig(golinters.NewProtoGetter(protogetterCfg)). WithSince("v1.55.0"). WithPresets(linter.PresetBugs). WithLoadForGoAnalysis(). @@ -776,6 +781,12 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithLoadForGoAnalysis(). WithURL("https://github.com/ryanrolds/sqlclosecheck"), + linter.NewConfig(golinters.NewSpancheck(spancheckCfg)). + WithSince("v1.56.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetBugs). + WithURL("https://github.com/jjti/go-spancheck"), + linter.NewConfig(golinters.NewStaticcheck(staticcheckCfg)). WithEnabledByDefault(). WithSince("v1.0.0"). @@ -977,7 +988,7 @@ func trimGoVersion(v string) string { return "" } - exp := regexp.MustCompile(`(\d\.\d+)\.\d+`) + exp := regexp.MustCompile(`(\d\.\d+)(?:\.\d+|[a-z]+\d)`) if exp.MatchString(v) { return exp.FindStringSubmatch(v)[1] diff --git a/vendor/github.com/golangci/golangci-lint/pkg/lint/runner.go b/vendor/github.com/golangci/golangci-lint/pkg/lint/runner.go index d270892d5..e7cb17555 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/lint/runner.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/lint/runner.go @@ -2,11 +2,11 @@ package lint import ( "context" + "errors" "fmt" "runtime/debug" "strings" - "github.com/hashicorp/go-multierror" gopackages "golang.org/x/tools/go/packages" "github.com/golangci/golangci-lint/internal/errorutil" @@ -205,7 +205,7 @@ func (r Runner) Run(ctx context.Context, linters []*linter.Config, lintCtx *lint defer sw.Print() var ( - lintErrors *multierror.Error + lintErrors error issues []result.Issue ) @@ -214,7 +214,7 @@ func (r Runner) Run(ctx context.Context, linters []*linter.Config, lintCtx *lint sw.TrackStage(lc.Name(), func() { linterIssues, err := r.runLinterSafe(ctx, lintCtx, lc) if err != nil { - lintErrors = multierror.Append(lintErrors, fmt.Errorf("can't run linter %s: %w", lc.Linter.Name(), err)) + lintErrors = errors.Join(lintErrors, fmt.Errorf("can't run linter %s", lc.Linter.Name()), err) r.Log.Warnf("Can't run linter %s: %v", lc.Linter.Name(), err) return @@ -224,7 +224,7 @@ func (r Runner) Run(ctx context.Context, linters []*linter.Config, lintCtx *lint }) } - return r.processLintResults(issues), lintErrors.ErrorOrNil() + return r.processLintResults(issues), lintErrors } func (r *Runner) processIssues(issues []result.Issue, sw *timeutils.Stopwatch, statPerProcessor map[string]processorStat) []result.Issue { diff --git a/vendor/github.com/golangci/golangci-lint/pkg/packages/errors.go b/vendor/github.com/golangci/golangci-lint/pkg/packages/errors.go index 489836712..ff37651af 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/packages/errors.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/packages/errors.go @@ -18,7 +18,7 @@ func ParseErrorPosition(pos string) (*token.Position, error) { file := parts[0] line, err := strconv.Atoi(parts[1]) if err != nil { - return nil, fmt.Errorf("can't parse line number %q: %s", parts[1], err) + return nil, fmt.Errorf("can't parse line number %q: %w", parts[1], err) } var column int diff --git a/vendor/github.com/golangci/golangci-lint/pkg/printers/checkstyle.go b/vendor/github.com/golangci/golangci-lint/pkg/printers/checkstyle.go index 3762ca056..e32eef7f5 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/printers/checkstyle.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/printers/checkstyle.go @@ -7,6 +7,7 @@ import ( "sort" "github.com/go-xmlfmt/xmlfmt" + "golang.org/x/exp/maps" "github.com/golangci/golangci-lint/pkg/result" ) @@ -74,10 +75,7 @@ func (p Checkstyle) Print(issues []result.Issue) error { file.Errors = append(file.Errors, newError) } - out.Files = make([]*checkstyleFile, 0, len(files)) - for _, file := range files { - out.Files = append(out.Files, file) - } + out.Files = maps.Values(files) sort.Slice(out.Files, func(i, j int) bool { return out.Files[i].Name < out.Files[j].Name diff --git a/vendor/github.com/golangci/golangci-lint/pkg/printers/github.go b/vendor/github.com/golangci/golangci-lint/pkg/printers/github.go index c1da9df9c..f471d5a86 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/printers/github.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/printers/github.go @@ -15,7 +15,7 @@ type github struct { const defaultGithubSeverity = "error" // NewGithub output format outputs issues according to GitHub actions format: -// https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message +// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message func NewGithub(w io.Writer) Printer { return &github{w: w} } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/printers/junitxml.go b/vendor/github.com/golangci/golangci-lint/pkg/printers/junitxml.go index 86a3811e4..3e3f82f58 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/printers/junitxml.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/printers/junitxml.go @@ -7,6 +7,8 @@ import ( "sort" "strings" + "golang.org/x/exp/maps" + "github.com/golangci/golangci-lint/pkg/result" ) @@ -71,9 +73,7 @@ func (p JunitXML) Print(issues []result.Issue) error { } var res testSuitesXML - for _, val := range suites { - res.TestSuites = append(res.TestSuites, val) - } + res.TestSuites = maps.Values(suites) sort.Slice(res.TestSuites, func(i, j int) bool { return res.TestSuites[i].Suite < res.TestSuites[j].Suite diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/diff.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/diff.go index 67104bab0..496d9c865 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/diff.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/diff.go @@ -47,7 +47,7 @@ func (p Diff) Process(issues []result.Issue) ([]result.Issue, error) { if p.patchFilePath != "" { patch, err := os.ReadFile(p.patchFilePath) if err != nil { - return nil, fmt.Errorf("can't read from patch file %s: %s", p.patchFilePath, err) + return nil, fmt.Errorf("can't read from patch file %s: %w", p.patchFilePath, err) } patchReader = bytes.NewReader(patch) } else if p.patch != "" { @@ -60,7 +60,7 @@ func (p Diff) Process(issues []result.Issue) ([]result.Issue, error) { WholeFiles: p.wholeFiles, } if err := c.Prepare(); err != nil { - return nil, fmt.Errorf("can't prepare diff by revgrep: %s", err) + return nil, fmt.Errorf("can't prepare diff by revgrep: %w", err) } return transformIssues(issues, func(i *result.Issue) *result.Issue { diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/nolint.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/nolint.go index 181d3bf1f..a72dd1ef2 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/nolint.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/nolint.go @@ -9,6 +9,8 @@ import ( "sort" "strings" + "golang.org/x/exp/maps" + "github.com/golangci/golangci-lint/pkg/golinters" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/lint/lintersdb" @@ -289,10 +291,7 @@ func (p *Nolint) Finish() { return } - unknownLinters := make([]string, 0, len(p.unknownLintersSet)) - for name := range p.unknownLintersSet { - unknownLinters = append(unknownLinters, name) - } + unknownLinters := maps.Keys(p.unknownLintersSet) sort.Strings(unknownLinters) p.log.Warnf("Found unknown linters in //nolint directives: %s", strings.Join(unknownLinters, ", ")) diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/skip_files.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/skip_files.go index 9579bee84..6c1c58695 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/skip_files.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/skip_files.go @@ -21,7 +21,7 @@ func NewSkipFiles(patterns []string, pathPrefix string) (*SkipFiles, error) { p = fsutils.NormalizePathInRegex(p) patternRe, err := regexp.Compile(p) if err != nil { - return nil, fmt.Errorf("can't compile regexp %q: %s", p, err) + return nil, fmt.Errorf("can't compile regexp %q: %w", p, err) } patternsRe = append(patternsRe, patternRe) } diff --git a/vendor/github.com/hashicorp/errwrap/LICENSE b/vendor/github.com/hashicorp/errwrap/LICENSE deleted file mode 100644 index c33dcc7c9..000000000 --- a/vendor/github.com/hashicorp/errwrap/LICENSE +++ /dev/null @@ -1,354 +0,0 @@ -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/vendor/github.com/hashicorp/errwrap/README.md b/vendor/github.com/hashicorp/errwrap/README.md deleted file mode 100644 index 444df08f8..000000000 --- a/vendor/github.com/hashicorp/errwrap/README.md +++ /dev/null @@ -1,89 +0,0 @@ -# errwrap - -`errwrap` is a package for Go that formalizes the pattern of wrapping errors -and checking if an error contains another error. - -There is a common pattern in Go of taking a returned `error` value and -then wrapping it (such as with `fmt.Errorf`) before returning it. The problem -with this pattern is that you completely lose the original `error` structure. - -Arguably the _correct_ approach is that you should make a custom structure -implementing the `error` interface, and have the original error as a field -on that structure, such [as this example](http://golang.org/pkg/os/#PathError). -This is a good approach, but you have to know the entire chain of possible -rewrapping that happens, when you might just care about one. - -`errwrap` formalizes this pattern (it doesn't matter what approach you use -above) by giving a single interface for wrapping errors, checking if a specific -error is wrapped, and extracting that error. - -## Installation and Docs - -Install using `go get github.com/hashicorp/errwrap`. - -Full documentation is available at -http://godoc.org/github.com/hashicorp/errwrap - -## Usage - -#### Basic Usage - -Below is a very basic example of its usage: - -```go -// A function that always returns an error, but wraps it, like a real -// function might. -func tryOpen() error { - _, err := os.Open("/i/dont/exist") - if err != nil { - return errwrap.Wrapf("Doesn't exist: {{err}}", err) - } - - return nil -} - -func main() { - err := tryOpen() - - // We can use the Contains helpers to check if an error contains - // another error. It is safe to do this with a nil error, or with - // an error that doesn't even use the errwrap package. - if errwrap.Contains(err, "does not exist") { - // Do something - } - if errwrap.ContainsType(err, new(os.PathError)) { - // Do something - } - - // Or we can use the associated `Get` functions to just extract - // a specific error. This would return nil if that specific error doesn't - // exist. - perr := errwrap.GetType(err, new(os.PathError)) -} -``` - -#### Custom Types - -If you're already making custom types that properly wrap errors, then -you can get all the functionality of `errwraps.Contains` and such by -implementing the `Wrapper` interface with just one function. Example: - -```go -type AppError { - Code ErrorCode - Err error -} - -func (e *AppError) WrappedErrors() []error { - return []error{e.Err} -} -``` - -Now this works: - -```go -err := &AppError{Err: fmt.Errorf("an error")} -if errwrap.ContainsType(err, fmt.Errorf("")) { - // This will work! -} -``` diff --git a/vendor/github.com/hashicorp/errwrap/errwrap.go b/vendor/github.com/hashicorp/errwrap/errwrap.go deleted file mode 100644 index 44e368e56..000000000 --- a/vendor/github.com/hashicorp/errwrap/errwrap.go +++ /dev/null @@ -1,178 +0,0 @@ -// Package errwrap implements methods to formalize error wrapping in Go. -// -// All of the top-level functions that take an `error` are built to be able -// to take any error, not just wrapped errors. This allows you to use errwrap -// without having to type-check and type-cast everywhere. -package errwrap - -import ( - "errors" - "reflect" - "strings" -) - -// WalkFunc is the callback called for Walk. -type WalkFunc func(error) - -// Wrapper is an interface that can be implemented by custom types to -// have all the Contains, Get, etc. functions in errwrap work. -// -// When Walk reaches a Wrapper, it will call the callback for every -// wrapped error in addition to the wrapper itself. Since all the top-level -// functions in errwrap use Walk, this means that all those functions work -// with your custom type. -type Wrapper interface { - WrappedErrors() []error -} - -// Wrap defines that outer wraps inner, returning an error type that -// can be cleanly used with the other methods in this package, such as -// Contains, GetAll, etc. -// -// This function won't modify the error message at all (the outer message -// will be used). -func Wrap(outer, inner error) error { - return &wrappedError{ - Outer: outer, - Inner: inner, - } -} - -// Wrapf wraps an error with a formatting message. This is similar to using -// `fmt.Errorf` to wrap an error. If you're using `fmt.Errorf` to wrap -// errors, you should replace it with this. -// -// format is the format of the error message. The string '{{err}}' will -// be replaced with the original error message. -// -// Deprecated: Use fmt.Errorf() -func Wrapf(format string, err error) error { - outerMsg := "<nil>" - if err != nil { - outerMsg = err.Error() - } - - outer := errors.New(strings.Replace( - format, "{{err}}", outerMsg, -1)) - - return Wrap(outer, err) -} - -// Contains checks if the given error contains an error with the -// message msg. If err is not a wrapped error, this will always return -// false unless the error itself happens to match this msg. -func Contains(err error, msg string) bool { - return len(GetAll(err, msg)) > 0 -} - -// ContainsType checks if the given error contains an error with -// the same concrete type as v. If err is not a wrapped error, this will -// check the err itself. -func ContainsType(err error, v interface{}) bool { - return len(GetAllType(err, v)) > 0 -} - -// Get is the same as GetAll but returns the deepest matching error. -func Get(err error, msg string) error { - es := GetAll(err, msg) - if len(es) > 0 { - return es[len(es)-1] - } - - return nil -} - -// GetType is the same as GetAllType but returns the deepest matching error. -func GetType(err error, v interface{}) error { - es := GetAllType(err, v) - if len(es) > 0 { - return es[len(es)-1] - } - - return nil -} - -// GetAll gets all the errors that might be wrapped in err with the -// given message. The order of the errors is such that the outermost -// matching error (the most recent wrap) is index zero, and so on. -func GetAll(err error, msg string) []error { - var result []error - - Walk(err, func(err error) { - if err.Error() == msg { - result = append(result, err) - } - }) - - return result -} - -// GetAllType gets all the errors that are the same type as v. -// -// The order of the return value is the same as described in GetAll. -func GetAllType(err error, v interface{}) []error { - var result []error - - var search string - if v != nil { - search = reflect.TypeOf(v).String() - } - Walk(err, func(err error) { - var needle string - if err != nil { - needle = reflect.TypeOf(err).String() - } - - if needle == search { - result = append(result, err) - } - }) - - return result -} - -// Walk walks all the wrapped errors in err and calls the callback. If -// err isn't a wrapped error, this will be called once for err. If err -// is a wrapped error, the callback will be called for both the wrapper -// that implements error as well as the wrapped error itself. -func Walk(err error, cb WalkFunc) { - if err == nil { - return - } - - switch e := err.(type) { - case *wrappedError: - cb(e.Outer) - Walk(e.Inner, cb) - case Wrapper: - cb(err) - - for _, err := range e.WrappedErrors() { - Walk(err, cb) - } - case interface{ Unwrap() error }: - cb(err) - Walk(e.Unwrap(), cb) - default: - cb(err) - } -} - -// wrappedError is an implementation of error that has both the -// outer and inner errors. -type wrappedError struct { - Outer error - Inner error -} - -func (w *wrappedError) Error() string { - return w.Outer.Error() -} - -func (w *wrappedError) WrappedErrors() []error { - return []error{w.Outer, w.Inner} -} - -func (w *wrappedError) Unwrap() error { - return w.Inner -} diff --git a/vendor/github.com/hashicorp/go-multierror/LICENSE b/vendor/github.com/hashicorp/go-multierror/LICENSE deleted file mode 100644 index 82b4de97c..000000000 --- a/vendor/github.com/hashicorp/go-multierror/LICENSE +++ /dev/null @@ -1,353 +0,0 @@ -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/vendor/github.com/hashicorp/go-multierror/Makefile b/vendor/github.com/hashicorp/go-multierror/Makefile deleted file mode 100644 index b97cd6ed0..000000000 --- a/vendor/github.com/hashicorp/go-multierror/Makefile +++ /dev/null @@ -1,31 +0,0 @@ -TEST?=./... - -default: test - -# test runs the test suite and vets the code. -test: generate - @echo "==> Running tests..." - @go list $(TEST) \ - | grep -v "/vendor/" \ - | xargs -n1 go test -timeout=60s -parallel=10 ${TESTARGS} - -# testrace runs the race checker -testrace: generate - @echo "==> Running tests (race)..." - @go list $(TEST) \ - | grep -v "/vendor/" \ - | xargs -n1 go test -timeout=60s -race ${TESTARGS} - -# updatedeps installs all the dependencies needed to run and build. -updatedeps: - @sh -c "'${CURDIR}/scripts/deps.sh' '${NAME}'" - -# generate runs `go generate` to build the dynamically generated source files. -generate: - @echo "==> Generating..." - @find . -type f -name '.DS_Store' -delete - @go list ./... \ - | grep -v "/vendor/" \ - | xargs -n1 go generate - -.PHONY: default test testrace updatedeps generate diff --git a/vendor/github.com/hashicorp/go-multierror/README.md b/vendor/github.com/hashicorp/go-multierror/README.md deleted file mode 100644 index 71dd308ed..000000000 --- a/vendor/github.com/hashicorp/go-multierror/README.md +++ /dev/null @@ -1,150 +0,0 @@ -# go-multierror - -[](https://circleci.com/gh/hashicorp/go-multierror) -[](https://pkg.go.dev/github.com/hashicorp/go-multierror) - - -[circleci]: https://app.circleci.com/pipelines/github/hashicorp/go-multierror -[godocs]: https://pkg.go.dev/github.com/hashicorp/go-multierror - -`go-multierror` is a package for Go that provides a mechanism for -representing a list of `error` values as a single `error`. - -This allows a function in Go to return an `error` that might actually -be a list of errors. If the caller knows this, they can unwrap the -list and access the errors. If the caller doesn't know, the error -formats to a nice human-readable format. - -`go-multierror` is fully compatible with the Go standard library -[errors](https://golang.org/pkg/errors/) package, including the -functions `As`, `Is`, and `Unwrap`. This provides a standardized approach -for introspecting on error values. - -## Installation and Docs - -Install using `go get github.com/hashicorp/go-multierror`. - -Full documentation is available at -https://pkg.go.dev/github.com/hashicorp/go-multierror - -### Requires go version 1.13 or newer - -`go-multierror` requires go version 1.13 or newer. Go 1.13 introduced -[error wrapping](https://golang.org/doc/go1.13#error_wrapping), which -this library takes advantage of. - -If you need to use an earlier version of go, you can use the -[v1.0.0](https://github.com/hashicorp/go-multierror/tree/v1.0.0) -tag, which doesn't rely on features in go 1.13. - -If you see compile errors that look like the below, it's likely that -you're on an older version of go: - -``` -/go/src/github.com/hashicorp/go-multierror/multierror.go:112:9: undefined: errors.As -/go/src/github.com/hashicorp/go-multierror/multierror.go:117:9: undefined: errors.Is -``` - -## Usage - -go-multierror is easy to use and purposely built to be unobtrusive in -existing Go applications/libraries that may not be aware of it. - -**Building a list of errors** - -The `Append` function is used to create a list of errors. This function -behaves a lot like the Go built-in `append` function: it doesn't matter -if the first argument is nil, a `multierror.Error`, or any other `error`, -the function behaves as you would expect. - -```go -var result error - -if err := step1(); err != nil { - result = multierror.Append(result, err) -} -if err := step2(); err != nil { - result = multierror.Append(result, err) -} - -return result -``` - -**Customizing the formatting of the errors** - -By specifying a custom `ErrorFormat`, you can customize the format -of the `Error() string` function: - -```go -var result *multierror.Error - -// ... accumulate errors here, maybe using Append - -if result != nil { - result.ErrorFormat = func([]error) string { - return "errors!" - } -} -``` - -**Accessing the list of errors** - -`multierror.Error` implements `error` so if the caller doesn't know about -multierror, it will work just fine. But if you're aware a multierror might -be returned, you can use type switches to access the list of errors: - -```go -if err := something(); err != nil { - if merr, ok := err.(*multierror.Error); ok { - // Use merr.Errors - } -} -``` - -You can also use the standard [`errors.Unwrap`](https://golang.org/pkg/errors/#Unwrap) -function. This will continue to unwrap into subsequent errors until none exist. - -**Extracting an error** - -The standard library [`errors.As`](https://golang.org/pkg/errors/#As) -function can be used directly with a multierror to extract a specific error: - -```go -// Assume err is a multierror value -err := somefunc() - -// We want to know if "err" has a "RichErrorType" in it and extract it. -var errRich RichErrorType -if errors.As(err, &errRich) { - // It has it, and now errRich is populated. -} -``` - -**Checking for an exact error value** - -Some errors are returned as exact errors such as the [`ErrNotExist`](https://golang.org/pkg/os/#pkg-variables) -error in the `os` package. You can check if this error is present by using -the standard [`errors.Is`](https://golang.org/pkg/errors/#Is) function. - -```go -// Assume err is a multierror value -err := somefunc() -if errors.Is(err, os.ErrNotExist) { - // err contains os.ErrNotExist -} -``` - -**Returning a multierror only if there are errors** - -If you build a `multierror.Error`, you can use the `ErrorOrNil` function -to return an `error` implementation only if there are errors to return: - -```go -var result *multierror.Error - -// ... accumulate errors here - -// Return the `error` only if errors were added to the multierror, otherwise -// return nil since there are no errors. -return result.ErrorOrNil() -``` diff --git a/vendor/github.com/hashicorp/go-multierror/append.go b/vendor/github.com/hashicorp/go-multierror/append.go deleted file mode 100644 index 3e2589bfd..000000000 --- a/vendor/github.com/hashicorp/go-multierror/append.go +++ /dev/null @@ -1,43 +0,0 @@ -package multierror - -// Append is a helper function that will append more errors -// onto an Error in order to create a larger multi-error. -// -// If err is not a multierror.Error, then it will be turned into -// one. If any of the errs are multierr.Error, they will be flattened -// one level into err. -// Any nil errors within errs will be ignored. If err is nil, a new -// *Error will be returned. -func Append(err error, errs ...error) *Error { - switch err := err.(type) { - case *Error: - // Typed nils can reach here, so initialize if we are nil - if err == nil { - err = new(Error) - } - - // Go through each error and flatten - for _, e := range errs { - switch e := e.(type) { - case *Error: - if e != nil { - err.Errors = append(err.Errors, e.Errors...) - } - default: - if e != nil { - err.Errors = append(err.Errors, e) - } - } - } - - return err - default: - newErrs := make([]error, 0, len(errs)+1) - if err != nil { - newErrs = append(newErrs, err) - } - newErrs = append(newErrs, errs...) - - return Append(&Error{}, newErrs...) - } -} diff --git a/vendor/github.com/hashicorp/go-multierror/flatten.go b/vendor/github.com/hashicorp/go-multierror/flatten.go deleted file mode 100644 index aab8e9abe..000000000 --- a/vendor/github.com/hashicorp/go-multierror/flatten.go +++ /dev/null @@ -1,26 +0,0 @@ -package multierror - -// Flatten flattens the given error, merging any *Errors together into -// a single *Error. -func Flatten(err error) error { - // If it isn't an *Error, just return the error as-is - if _, ok := err.(*Error); !ok { - return err - } - - // Otherwise, make the result and flatten away! - flatErr := new(Error) - flatten(err, flatErr) - return flatErr -} - -func flatten(err error, flatErr *Error) { - switch err := err.(type) { - case *Error: - for _, e := range err.Errors { - flatten(e, flatErr) - } - default: - flatErr.Errors = append(flatErr.Errors, err) - } -} diff --git a/vendor/github.com/hashicorp/go-multierror/format.go b/vendor/github.com/hashicorp/go-multierror/format.go deleted file mode 100644 index 47f13c49a..000000000 --- a/vendor/github.com/hashicorp/go-multierror/format.go +++ /dev/null @@ -1,27 +0,0 @@ -package multierror - -import ( - "fmt" - "strings" -) - -// ErrorFormatFunc is a function callback that is called by Error to -// turn the list of errors into a string. -type ErrorFormatFunc func([]error) string - -// ListFormatFunc is a basic formatter that outputs the number of errors -// that occurred along with a bullet point list of the errors. -func ListFormatFunc(es []error) string { - if len(es) == 1 { - return fmt.Sprintf("1 error occurred:\n\t* %s\n\n", es[0]) - } - - points := make([]string, len(es)) - for i, err := range es { - points[i] = fmt.Sprintf("* %s", err) - } - - return fmt.Sprintf( - "%d errors occurred:\n\t%s\n\n", - len(es), strings.Join(points, "\n\t")) -} diff --git a/vendor/github.com/hashicorp/go-multierror/group.go b/vendor/github.com/hashicorp/go-multierror/group.go deleted file mode 100644 index 9c29efb7f..000000000 --- a/vendor/github.com/hashicorp/go-multierror/group.go +++ /dev/null @@ -1,38 +0,0 @@ -package multierror - -import "sync" - -// Group is a collection of goroutines which return errors that need to be -// coalesced. -type Group struct { - mutex sync.Mutex - err *Error - wg sync.WaitGroup -} - -// Go calls the given function in a new goroutine. -// -// If the function returns an error it is added to the group multierror which -// is returned by Wait. -func (g *Group) Go(f func() error) { - g.wg.Add(1) - - go func() { - defer g.wg.Done() - - if err := f(); err != nil { - g.mutex.Lock() - g.err = Append(g.err, err) - g.mutex.Unlock() - } - }() -} - -// Wait blocks until all function calls from the Go method have returned, then -// returns the multierror. -func (g *Group) Wait() *Error { - g.wg.Wait() - g.mutex.Lock() - defer g.mutex.Unlock() - return g.err -} diff --git a/vendor/github.com/hashicorp/go-multierror/multierror.go b/vendor/github.com/hashicorp/go-multierror/multierror.go deleted file mode 100644 index f54574326..000000000 --- a/vendor/github.com/hashicorp/go-multierror/multierror.go +++ /dev/null @@ -1,121 +0,0 @@ -package multierror - -import ( - "errors" - "fmt" -) - -// Error is an error type to track multiple errors. This is used to -// accumulate errors in cases and return them as a single "error". -type Error struct { - Errors []error - ErrorFormat ErrorFormatFunc -} - -func (e *Error) Error() string { - fn := e.ErrorFormat - if fn == nil { - fn = ListFormatFunc - } - - return fn(e.Errors) -} - -// ErrorOrNil returns an error interface if this Error represents -// a list of errors, or returns nil if the list of errors is empty. This -// function is useful at the end of accumulation to make sure that the value -// returned represents the existence of errors. -func (e *Error) ErrorOrNil() error { - if e == nil { - return nil - } - if len(e.Errors) == 0 { - return nil - } - - return e -} - -func (e *Error) GoString() string { - return fmt.Sprintf("*%#v", *e) -} - -// WrappedErrors returns the list of errors that this Error is wrapping. It is -// an implementation of the errwrap.Wrapper interface so that multierror.Error -// can be used with that library. -// -// This method is not safe to be called concurrently. Unlike accessing the -// Errors field directly, this function also checks if the multierror is nil to -// prevent a null-pointer panic. It satisfies the errwrap.Wrapper interface. -func (e *Error) WrappedErrors() []error { - if e == nil { - return nil - } - return e.Errors -} - -// Unwrap returns an error from Error (or nil if there are no errors). -// This error returned will further support Unwrap to get the next error, -// etc. The order will match the order of Errors in the multierror.Error -// at the time of calling. -// -// The resulting error supports errors.As/Is/Unwrap so you can continue -// to use the stdlib errors package to introspect further. -// -// This will perform a shallow copy of the errors slice. Any errors appended -// to this error after calling Unwrap will not be available until a new -// Unwrap is called on the multierror.Error. -func (e *Error) Unwrap() error { - // If we have no errors then we do nothing - if e == nil || len(e.Errors) == 0 { - return nil - } - - // If we have exactly one error, we can just return that directly. - if len(e.Errors) == 1 { - return e.Errors[0] - } - - // Shallow copy the slice - errs := make([]error, len(e.Errors)) - copy(errs, e.Errors) - return chain(errs) -} - -// chain implements the interfaces necessary for errors.Is/As/Unwrap to -// work in a deterministic way with multierror. A chain tracks a list of -// errors while accounting for the current represented error. This lets -// Is/As be meaningful. -// -// Unwrap returns the next error. In the cleanest form, Unwrap would return -// the wrapped error here but we can't do that if we want to properly -// get access to all the errors. Instead, users are recommended to use -// Is/As to get the correct error type out. -// -// Precondition: []error is non-empty (len > 0) -type chain []error - -// Error implements the error interface -func (e chain) Error() string { - return e[0].Error() -} - -// Unwrap implements errors.Unwrap by returning the next error in the -// chain or nil if there are no more errors. -func (e chain) Unwrap() error { - if len(e) == 1 { - return nil - } - - return e[1:] -} - -// As implements errors.As by attempting to map to the current value. -func (e chain) As(target interface{}) bool { - return errors.As(e[0], target) -} - -// Is implements errors.Is by comparing the current value directly. -func (e chain) Is(target error) bool { - return errors.Is(e[0], target) -} diff --git a/vendor/github.com/hashicorp/go-multierror/prefix.go b/vendor/github.com/hashicorp/go-multierror/prefix.go deleted file mode 100644 index 5c477abe4..000000000 --- a/vendor/github.com/hashicorp/go-multierror/prefix.go +++ /dev/null @@ -1,37 +0,0 @@ -package multierror - -import ( - "fmt" - - "github.com/hashicorp/errwrap" -) - -// Prefix is a helper function that will prefix some text -// to the given error. If the error is a multierror.Error, then -// it will be prefixed to each wrapped error. -// -// This is useful to use when appending multiple multierrors -// together in order to give better scoping. -func Prefix(err error, prefix string) error { - if err == nil { - return nil - } - - format := fmt.Sprintf("%s {{err}}", prefix) - switch err := err.(type) { - case *Error: - // Typed nils can reach here, so initialize if we are nil - if err == nil { - err = new(Error) - } - - // Wrap each of the errors - for i, e := range err.Errors { - err.Errors[i] = errwrap.Wrapf(format, e) - } - - return err - default: - return errwrap.Wrapf(format, err) - } -} diff --git a/vendor/github.com/hashicorp/go-multierror/sort.go b/vendor/github.com/hashicorp/go-multierror/sort.go deleted file mode 100644 index fecb14e81..000000000 --- a/vendor/github.com/hashicorp/go-multierror/sort.go +++ /dev/null @@ -1,16 +0,0 @@ -package multierror - -// Len implements sort.Interface function for length -func (err Error) Len() int { - return len(err.Errors) -} - -// Swap implements sort.Interface function for swapping elements -func (err Error) Swap(i, j int) { - err.Errors[i], err.Errors[j] = err.Errors[j], err.Errors[i] -} - -// Less implements sort.Interface function for determining order -func (err Error) Less(i, j int) bool { - return err.Errors[i].Error() < err.Errors[j].Error() -} diff --git a/vendor/github.com/jgautheron/goconst/README.md b/vendor/github.com/jgautheron/goconst/README.md index 8dd093baf..c671eb541 100644 --- a/vendor/github.com/jgautheron/goconst/README.md +++ b/vendor/github.com/jgautheron/goconst/README.md @@ -23,6 +23,7 @@ Usage: Flags: -ignore exclude files matching the given regular expression + -ignore-strings exclude strings matching the given regular expression -ignore-tests exclude tests from the search (default: true) -min-occurrences report from how many occurrences (default: 2) -min-length only report strings with the minimum given length (default: 3) diff --git a/vendor/github.com/jgautheron/goconst/api.go b/vendor/github.com/jgautheron/goconst/api.go index d56fcd6c2..b838e035f 100644 --- a/vendor/github.com/jgautheron/goconst/api.go +++ b/vendor/github.com/jgautheron/goconst/api.go @@ -14,6 +14,7 @@ type Issue struct { } type Config struct { + IgnoreStrings string IgnoreTests bool MatchWithConstants bool MinStringLength int @@ -28,6 +29,7 @@ func Run(files []*ast.File, fset *token.FileSet, cfg *Config) ([]Issue, error) { p := New( "", "", + cfg.IgnoreStrings, cfg.IgnoreTests, cfg.MatchWithConstants, cfg.ParseNumbers, diff --git a/vendor/github.com/jgautheron/goconst/parser.go b/vendor/github.com/jgautheron/goconst/parser.go index c1edd4c78..2f32740b9 100644 --- a/vendor/github.com/jgautheron/goconst/parser.go +++ b/vendor/github.com/jgautheron/goconst/parser.go @@ -24,11 +24,11 @@ const ( type Parser struct { // Meant to be passed via New() - path, ignore string - ignoreTests, matchConstant bool - minLength, minOccurrences int - numberMin, numberMax int - excludeTypes map[Type]bool + path, ignore, ignoreStrings string + ignoreTests, matchConstant bool + minLength, minOccurrences int + numberMin, numberMax int + excludeTypes map[Type]bool supportedTokens []token.Token @@ -39,7 +39,7 @@ type Parser struct { // New creates a new instance of the parser. // This is your entry point if you'd like to use goconst as an API. -func New(path, ignore string, ignoreTests, matchConstant, numbers bool, numberMin, numberMax, minLength, minOccurrences int, excludeTypes map[Type]bool) *Parser { +func New(path, ignore, ignoreStrings string, ignoreTests, matchConstant, numbers bool, numberMin, numberMax, minLength, minOccurrences int, excludeTypes map[Type]bool) *Parser { supportedTokens := []token.Token{token.STRING} if numbers { supportedTokens = append(supportedTokens, token.INT, token.FLOAT) @@ -48,6 +48,7 @@ func New(path, ignore string, ignoreTests, matchConstant, numbers bool, numberMi return &Parser{ path: path, ignore: ignore, + ignoreStrings: ignoreStrings, ignoreTests: ignoreTests, matchConstant: matchConstant, minLength: minLength, @@ -98,6 +99,16 @@ func (p *Parser) ProcessResults() { delete(p.strs, str) } + if p.ignoreStrings != "" { + match, err := regexp.MatchString(p.ignoreStrings, str) + if err != nil { + log.Println(err) + } + if match { + delete(p.strs, str) + } + } + // If the value is a number if i, err := strconv.ParseInt(str, 0, 0); err == nil { if p.numberMin != 0 && i < int64(p.numberMin) { diff --git a/vendor/github.com/jjti/go-spancheck/.gitignore b/vendor/github.com/jjti/go-spancheck/.gitignore new file mode 100644 index 000000000..1f83be414 --- /dev/null +++ b/vendor/github.com/jjti/go-spancheck/.gitignore @@ -0,0 +1,19 @@ +# 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/ +src/ diff --git a/vendor/github.com/jjti/go-spancheck/.golangci.yml b/vendor/github.com/jjti/go-spancheck/.golangci.yml new file mode 100644 index 000000000..15d8513d6 --- /dev/null +++ b/vendor/github.com/jjti/go-spancheck/.golangci.yml @@ -0,0 +1,103 @@ +## A good ref for this: https://gist.github.com/maratori/47a4d00457a92aa426dbd48a18776322 + +run: + timeout: 5m + tests: true +linters: + enable: + - asasalint # checks for pass []any as any in variadic func(...any) + - asciicheck # checks that your code does not contain non-ASCII identifiers + - bidichk # checks for dangerous unicode character sequences + - bodyclose + - containedctx + - decorder # checks declaration order and count of types, constants, variables and functions + - dogsled + - dupword # checks for duplicate words in the source code + - durationcheck # checks for two durations multiplied together + - errcheck + - errname + - errorlint + - exhaustive # checks exhaustiveness of enum switch statements + - exportloopref # checks for pointers to enclosing loop variables + - gci + - gochecknoinits # checks that no init functions are present in Go code + - gocritic + - gomnd + - gosimple + - govet + - importas # enforces consistent import aliases + - ineffassign + - loggercheck + - makezero # finds slice declarations with non-zero initial length + - mirror + - misspell + - musttag # enforces field tags in (un)marshaled structs + - nakedret + - nestif # reports deeply nested if statements + - nilerr # finds the code that returns nil even if it checks that the error is not nil + - noctx # finds sending http request without context.Context + - nolintlint # reports ill-formed or insufficient nolint directives + - predeclared # finds code that shadows one of Go's predeclared identifiers + - promlinter + - reassign # checks that package variables are not reassigned + - revive # fast, configurable, extensible, flexible, and beautiful linter for Go, drop-in replacement of golint + - staticcheck + - stylecheck + - tenv + - thelper # detects golang test helpers without t.Helper() call and checks the consistency of test helpers + - unconvert # removes unnecessary type conversions + - unparam # reports unused function parameters + - unused + - usestdlibvars # detects the possibility to use variables/constants from the Go standard library + - wastedassign # finds wasted assignment statements + - whitespace # detects leading and trailing whitespace +linters-settings: + gci: + skip-generated: true + custom-order: true + sections: + - standard # Standard section: captures all standard packages. + - default # Default section: contains all imports that could not be matched to another section type. + - prefix(github.com/jjti) + exhaustive: + # Program elements to check for exhaustiveness. + # Default: [ switch ] + check: + - switch + - map + gocritic: + settings: + captLocal: + # Whether to restrict checker to params only. + # Default: true + paramsOnly: false + underef: + # Whether to skip (*x).method() calls where x is a pointer receiver. + # Default: true + skipRecvDeref: false + govet: + enable-all: true + disable: + - fieldalignment # too strict + - shadow # bunch of false positive, doesn't realize when we return from a func + misspell: + locale: US + nakedret: + max-func-lines: 0 + nestif: + # Minimal complexity of if statements to report. + # Default: 5 + min-complexity: 4 + nolintlint: + # Enable to require an explanation of nonzero length after each nolint directive. + # Default: false + require-explanation: true + stylecheck: + checks: ["all"] +issues: + include: + - EXC0001 # Error return value of x is not checked + - EXC0013 # package comment should be of the form "(.+)... + - EXC0014 # comment on exported (.+) should be of the form "(.+)..." + exclude: + - ifElseChain diff --git a/vendor/github.com/jjti/go-spancheck/CONTRIBUTING.md b/vendor/github.com/jjti/go-spancheck/CONTRIBUTING.md new file mode 100644 index 000000000..32932fae1 --- /dev/null +++ b/vendor/github.com/jjti/go-spancheck/CONTRIBUTING.md @@ -0,0 +1,51 @@ +# Contributing guideline + +Contributions are welcome + appreciated. + +## Open Requests + +These are a couple contributions I would especially appreciate: + +1. Add check for SetAttributes: https://github.com/jjti/go-spancheck/issues/1 +1. Add SuggestedFix(es): https://github.com/jjti/go-spancheck/issues/2 + +## Steps + +### 1. Create an Issue + +If one does not exist already, open a bug report or feature request in [https://github.com/jjti/go-spancheck/issues](https://github.com/jjti/go-spancheck/issues). + +### 2. Add a test case + +Test cases are in `/testdata`. + +If fixing a bug, you can add it to `testdata/enableall/enable_all.go` (for example): + +```go +func _() { + ctx, span := otel.Tracer("foo").Start(context.Background(), "bar") // want "span.End is not called on all paths, possible memory leak" + print(ctx.Done(), span.IsRecording()) +} // want "return can be reached without calling span.End" +``` + +If adding a new feature with a new combination of flags, create a new module within `testdata`: + +1. Create a new module, eg `testdata/setattributes` +1. Copy/paste go.mod/sum into the new module directory and update the module definition, eg `module github.com/jjti/go-spancheck/testdata/setattributes` +1. Add the module to the workspace in [go.work](./go.work) +1. Add the module's directory to the `testvendor` Make target in [Makefile](./Makefile) + +### 3. Run tests + +```bash +make test +``` + +### 4. Open a PR + +Eg of a GitHub snippet for PRs: + +```bash +alias gpr='gh pr view --web 2>/dev/null || gh pr create --web --fill' +gpr +``` diff --git a/vendor/github.com/jjti/go-spancheck/LICENSE b/vendor/github.com/jjti/go-spancheck/LICENSE new file mode 100644 index 000000000..552ddf2dc --- /dev/null +++ b/vendor/github.com/jjti/go-spancheck/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Joshua Timmons + +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/vendor/github.com/jjti/go-spancheck/Makefile b/vendor/github.com/jjti/go-spancheck/Makefile new file mode 100644 index 000000000..39d80f7c6 --- /dev/null +++ b/vendor/github.com/jjti/go-spancheck/Makefile @@ -0,0 +1,27 @@ +.PHONY: fmt +fmt: + golangci-lint run --fix --config ./.golangci.yml + +.PHONY: test +test: testvendor + go test -v ./... + +# note: I'm copying https://github.com/ghostiam/protogetter/blob/main/testdata/Makefile +# +# x/tools/go/analysis/analysistest does not support go modules. To work around this issue +# we need to vendor any external modules to `./src`. +# +# Follow https://github.com/golang/go/issues/37054 for more details. +.PHONY: testvendor +testvendor: + @rm -rf base/src + @cd testdata/base && go mod vendor + @cp -r testdata/base/vendor testdata/base/src + @cp -r testdata/base/vendor testdata/disableerrorchecks/src + @cp -r testdata/base/vendor testdata/enableall/src + @rm -rf testdata/base/vendor + +.PHONY: install +install: + go install ./cmd/spancheck + @echo "Installed in $(shell which spancheck)"
\ No newline at end of file diff --git a/vendor/github.com/jjti/go-spancheck/README.md b/vendor/github.com/jjti/go-spancheck/README.md new file mode 100644 index 000000000..98aedec28 --- /dev/null +++ b/vendor/github.com/jjti/go-spancheck/README.md @@ -0,0 +1,210 @@ +# go-spancheck + + +[](https://github.com/jjti/go-spancheck/actions/workflows/ci.yaml) +[](https://goreportcard.com/report/github.com/jjti/go-spancheck) +[](LICENSE) + +Checks usage of: + +- [OpenTelemetry spans](https://opentelemetry.io/docs/instrumentation/go/manual/) from [go.opentelemetry.io/otel/trace](go.opentelemetry.io/otel/trace) +- [OpenCensus spans](https://opencensus.io/quickstart/go/tracing/) from [go.opencensus.io/trace](https://pkg.go.dev/go.opencensus.io/trace#Span) + +## Installation & Usage + +```bash +go install github.com/jjti/go-spancheck/cmd/spancheck@latest +spancheck ./... +``` + +## Example + +```bash +spancheck -checks 'end,set-status,record-error' ./... +``` + +```go +func _() error { + // span.End is not called on all paths, possible memory leak + // span.SetStatus is not called on all paths + // span.RecordError is not called on all paths + _, span := otel.Tracer("foo").Start(context.Background(), "bar") + + if true { + // return can be reached without calling span.End + // return can be reached without calling span.SetStatus + // return can be reached without calling span.RecordError + return errors.New("err") + } + + return nil // return can be reached without calling span.End +} +``` + +## Configuration + +Only the `span.End()` check is enabled by default. The others can be enabled with `-checks 'end,set-status,record-error'`. + +```txt +$ spancheck -h +... +Flags: + -checks string + comma-separated list of checks to enable (options: end, set-status, record-error) (default "end") + -ignore-check-signatures string + comma-separated list of regex for function signatures that disable checks on errors +``` + +### Ignore check signatures + +The `span.SetStatus()` and `span.RecordError()` checks warn when there is: + +1. a path to return statement +1. that returns an error +1. without a call (to `SetStatus` or `RecordError`, respectively) + +But it's convenient to call `SetStatus` and `RecordError` from utility methods [[1](https://andydote.co.uk/2023/09/19/tracing-is-better/#step-2-wrap-the-errors)]. To support that, the `ignore-*-check-signatures` settings will suppress warnings if the configured function is present in the path. + +For example, by default, the code below would have warnings as shown: + +```go +func task(ctx context.Context) error { + ctx, span := otel.Tracer("foo").Start(ctx, "bar") // span.SetStatus is not called on all paths + defer span.End() + + if err := subTask(ctx); err != nil { + return recordErr(span, err) // return can be reached without calling span.SetStatus + } + + return nil +} + +func recordErr(span trace.Span, err error) error { + span.SetStatus(codes.Error, err.Error()) + span.RecordError(err) + return err +} +``` + +The warnings are can be ignored by setting `-ignore-check-signatures` flag to `recordErr`: + +```bash +spancheck -checks 'end,set-status,record-error' -ignore-check-signatures 'recordErr' ./... +``` + +## Problem Statement + +Tracing is a celebrated [[1](https://andydote.co.uk/2023/09/19/tracing-is-better/),[2](https://charity.wtf/2022/08/15/live-your-best-life-with-structured-events/)] and well marketed [[3](https://docs.datadoghq.com/tracing/),[4](https://www.honeycomb.io/distributed-tracing)] pillar of observability. But self-instrumented tracing requires a lot of easy-to-forget boilerplate: + +```go +import ( + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/codes" +) + +func task(ctx context.Context) error { + ctx, span := otel.Tracer("foo").Start(ctx, "bar") + defer span.End() // call `.End()` + + if err := subTask(ctx); err != nil { + span.SetStatus(codes.Error, err.Error()) // call SetStatus(codes.Error, msg) to set status:error + span.RecordError(err) // call RecordError(err) to record an error event + return err + } + + return nil +} +``` + +For spans to be _really_ useful, developers need to: + +1. call `span.End()` always +1. call `span.SetStatus(codes.Error, msg)` on error +1. call `span.RecordError(err)` on error +1. call `span.SetAttributes()` liberally + +- OpenTelemetry: [Creating spans](https://opentelemetry.io/docs/instrumentation/go/manual/#creating-spans) +- Uptrace: [OpenTelemetry Go Tracing API](https://uptrace.dev/opentelemetry/go-tracing.html#quickstart) + +This linter helps developers with steps 1-3. + +## Checks + +This linter supports three checks, each documented below. Only the check for `span.End()` is enabled by default. See [Configuration](#configuration) for instructions on enabling the others. + +### `span.End()` + +Enabled by default. + +Not calling `End` can cause memory leaks and prevents spans from being closed. + +> Any Span that is created MUST also be ended. This is the responsibility of the user. Implementations of this API may leak memory or other resources if Spans are not ended. + +[source: trace.go](https://github.com/open-telemetry/opentelemetry-go/blob/98b32a6c3a87fbee5d34c063b9096f416b250897/trace/trace.go#L523) + +```go +func task(ctx context.Context) error { + otel.Tracer("app").Start(ctx, "foo") // span is unassigned, probable memory leak + _, span := otel.Tracer().Start(ctx, "foo") // span.End is not called on all paths, possible memory leak + return nil // return can be reached without calling span.End +} +``` + +### `span.SetStatus(codes.Error, "msg")` + +Disabled by default. Enable with `-checks 'set-status'`. + +Developers should call `SetStatus` on spans. The status attribute is an important, first-class attribute: + +1. observability platforms and APMs differentiate "success" vs "failure" using [span's status codes](https://docs.datadoghq.com/tracing/metrics/). +1. telemetry collector agents, like the [Open Telemetry Collector's Tail Sampling Processor](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/processor/tailsamplingprocessor/README.md#:~:text=Sampling%20Processor.-,status_code,-%3A%20Sample%20based%20upon), are configurable to sample `Error` spans at a higher rate than `OK` spans. +1. observability platforms, like [DataDog, have trace retention filters that use spans' status](https://docs.datadoghq.com/tracing/trace_pipeline/trace_retention/). In other words, `status:error` spans often receive special treatment with the assumption they are more useful for debugging. And forgetting to set the status can lead to spans, with useful debugging information, being dropped. + +```go +func _() error { + _, span := otel.Tracer("foo").Start(context.Background(), "bar") // span.SetStatus is not called on all paths + defer span.End() + + if err := subTask(); err != nil { + span.RecordError(err) + return errors.New(err) // return can be reached without calling span.SetStatus + } + + return nil +} +``` + +OpenTelemetry docs: [Set span status](https://opentelemetry.io/docs/instrumentation/go/manual/#set-span-status). + +### `span.RecordError(err)` + +Disabled by default. Enable with `-checks 'record-error'`. + +Calling `RecordError` creates a new exception-type [event (structured log message)](https://opentelemetry.io/docs/concepts/signals/traces/#span-events) on the span. This is recommended to capture the error's stack trace. + +```go +func _() error { + _, span := otel.Tracer("foo").Start(context.Background(), "bar") // span.RecordError is not called on all paths + defer span.End() + + if err := subTask(); err != nil { + span.SetStatus(codes.Error, err.Error()) + return errors.New(err) // return can be reached without calling span.RecordError + } + + return nil +} +``` + +OpenTelemetry docs: [Record errors](https://opentelemetry.io/docs/instrumentation/go/manual/#record-errors). + +Note: this check is not applied to [OpenCensus spans](https://pkg.go.dev/go.opencensus.io/trace#SpanInterface) because they have no `RecordError` method. + +## Attribution + +This linter is the product of liberal copying of: + +- [github.com/golang/tools/go/analysis/passes/lostcancel](https://github.com/golang/tools/tree/master/go/analysis/passes/lostcancel) (half the linter) +- [github.com/tomarrell/wrapcheck](https://github.com/tomarrell/wrapcheck) (error type checking and config) +- [github.com/Antonboom/testifylint](https://github.com/Antonboom/testifylint) (README) +- [github.com/ghostiam/protogetter](https://github.com/ghostiam/protogetter/blob/main/testdata/Makefile) (test setup) diff --git a/vendor/github.com/jjti/go-spancheck/config.go b/vendor/github.com/jjti/go-spancheck/config.go new file mode 100644 index 000000000..4005f49e0 --- /dev/null +++ b/vendor/github.com/jjti/go-spancheck/config.go @@ -0,0 +1,141 @@ +package spancheck + +import ( + "flag" + "fmt" + "log" + "regexp" + "strings" +) + +// Check is a type of check that can be enabled or disabled. +type Check int + +const ( + // EndCheck if enabled, checks that span.End() is called after span creation and before the function returns. + EndCheck Check = iota + + // SetStatusCheck if enabled, checks that `span.SetStatus(codes.Error, msg)` is called when returning an error. + SetStatusCheck + + // RecordErrorCheck if enabled, checks that span.RecordError(err) is called when returning an error. + RecordErrorCheck +) + +func (c Check) String() string { + switch c { + case EndCheck: + return "end" + case SetStatusCheck: + return "set-status" + case RecordErrorCheck: + return "record-error" + default: + return "" + } +} + +var ( + // Checks is a list of all checks by name. + Checks = map[string]Check{ + EndCheck.String(): EndCheck, + SetStatusCheck.String(): SetStatusCheck, + RecordErrorCheck.String(): RecordErrorCheck, + } +) + +// Config is a configuration for the spancheck analyzer. +type Config struct { + fs flag.FlagSet + + // EnabledChecks is a list of checks to enable by name. + EnabledChecks []string + + // IgnoreChecksSignaturesSlice is a slice of strings that are turned into + // the IgnoreSetStatusCheckSignatures regex. + IgnoreChecksSignaturesSlice []string + + endCheckEnabled bool + setStatusEnabled bool + recordErrorEnabled bool + + // ignoreChecksSignatures is a regex that, if matched, disables the + // SetStatus and RecordError checks on error. + ignoreChecksSignatures *regexp.Regexp +} + +// NewDefaultConfig returns a new Config with default values. +func NewDefaultConfig() *Config { + return &Config{ + EnabledChecks: []string{EndCheck.String()}, + } +} + +// finalize parses checks and signatures from the public string slices of Config. +func (c *Config) finalize() { + c.parseSignatures() + + checks := parseChecks(c.EnabledChecks) + c.endCheckEnabled = contains(checks, EndCheck) + c.setStatusEnabled = contains(checks, SetStatusCheck) + c.recordErrorEnabled = contains(checks, RecordErrorCheck) +} + +// parseSignatures sets the Ignore*CheckSignatures regex from the string slices. +func (c *Config) parseSignatures() { + if c.ignoreChecksSignatures == nil && len(c.IgnoreChecksSignaturesSlice) > 0 { + if len(c.IgnoreChecksSignaturesSlice) == 1 && c.IgnoreChecksSignaturesSlice[0] == "" { + return + } + + c.ignoreChecksSignatures = createRegex(c.IgnoreChecksSignaturesSlice) + } +} + +func parseChecks(checksSlice []string) []Check { + if len(checksSlice) == 0 { + return nil + } + + checks := []Check{} + for _, check := range checksSlice { + checkName := strings.TrimSpace(check) + if checkName == "" { + continue + } + + check, ok := Checks[checkName] + if !ok { + continue + } + + checks = append(checks, check) + } + + return checks +} + +func createRegex(sigs []string) *regexp.Regexp { + if len(sigs) == 0 { + return nil + } + + regex := fmt.Sprintf("(%s)", strings.Join(sigs, "|")) + regexCompiled, err := regexp.Compile(regex) + if err != nil { + log.Default().Print("[WARN] failed to compile regex from signature flag", "regex", regex, "err", err) + return nil + } + + return regexCompiled +} + +func contains(s []Check, e Check) bool { + for _, a := range s { + if a == e { + return true + } + } + + return false +} diff --git a/vendor/github.com/jjti/go-spancheck/doc.go b/vendor/github.com/jjti/go-spancheck/doc.go new file mode 100644 index 000000000..f9dec043f --- /dev/null +++ b/vendor/github.com/jjti/go-spancheck/doc.go @@ -0,0 +1,37 @@ +// Package spancheck defines a linter that checks for mistakes with OTEL trace spans. +// +// # Analyzer spancheck +// +// spancheck: check for mistakes with OpenTelemetry trace spans. +// +// Common mistakes with OTEL trace spans include forgetting to call End: +// +// func(ctx context.Context) { +// ctx, span := otel.Tracer("app").Start(ctx, "span") +// // defer span.End() should be here +// +// // do stuff +// } +// +// Forgetting to set an Error status: +// +// ctx, span := otel.Tracer("app").Start(ctx, "span") +// defer span.End() +// +// if err := task(); err != nil { +// // span.SetStatus(codes.Error, err.Error()) should be here +// span.RecordError(err) +// return fmt.Errorf("failed to run task: %w", err) +// } +// +// Forgetting to record the Error: +// +// ctx, span := otel.Tracer("app").Start(ctx, "span") +// defer span.End() +// +// if err := task(); err != nil { +// span.SetStatus(codes.Error, err.Error()) +// // span.RecordError(err) should be here +// return fmt.Errorf("failed to run task: %w", err) +// } +package spancheck diff --git a/vendor/github.com/jjti/go-spancheck/go.work b/vendor/github.com/jjti/go-spancheck/go.work new file mode 100644 index 000000000..7d0a87b9e --- /dev/null +++ b/vendor/github.com/jjti/go-spancheck/go.work @@ -0,0 +1,8 @@ +go 1.20 + +use ( + . + ./testdata/base + ./testdata/disableerrorchecks + ./testdata/enableall +) diff --git a/vendor/github.com/jjti/go-spancheck/go.work.sum b/vendor/github.com/jjti/go-spancheck/go.work.sum new file mode 100644 index 000000000..f3cdef790 --- /dev/null +++ b/vendor/github.com/jjti/go-spancheck/go.work.sum @@ -0,0 +1,3 @@ +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= diff --git a/vendor/github.com/jjti/go-spancheck/spancheck.go b/vendor/github.com/jjti/go-spancheck/spancheck.go new file mode 100644 index 000000000..ebfc1ac68 --- /dev/null +++ b/vendor/github.com/jjti/go-spancheck/spancheck.go @@ -0,0 +1,431 @@ +package spancheck + +import ( + "go/ast" + "go/types" + "log" + "regexp" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/ctrlflow" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" + "golang.org/x/tools/go/cfg" +) + +const stackLen = 32 + +// spanType differentiates span types. +type spanType int + +const ( + spanUnset spanType = iota // not a span + spanOpenTelemetry // from go.opentelemetry.io/otel + spanOpenCensus // from go.opencensus.io/trace +) + +var ( + // this approach stolen from errcheck + // https://github.com/kisielk/errcheck/blob/7f94c385d0116ccc421fbb4709e4a484d98325ee/errcheck/errcheck.go#L22 + errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) +) + +// NewAnalyzerWithConfig returns a new analyzer configured with the Config passed in. +// Its config can be set for testing. +func NewAnalyzerWithConfig(config *Config) *analysis.Analyzer { + return newAnalyzer(config) +} + +func newAnalyzer(config *Config) *analysis.Analyzer { + config.finalize() + + return &analysis.Analyzer{ + Name: "spancheck", + Doc: "Checks for mistakes with OpenTelemetry/Census spans.", + Flags: config.fs, + Run: run(config), + Requires: []*analysis.Analyzer{ + ctrlflow.Analyzer, + inspect.Analyzer, + }, + } +} + +func run(config *Config) func(*analysis.Pass) (interface{}, error) { + return func(pass *analysis.Pass) (interface{}, error) { + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + + nodeFilter := []ast.Node{ + (*ast.FuncLit)(nil), // f := func() {} + (*ast.FuncDecl)(nil), // func foo() {} + } + inspect.Preorder(nodeFilter, func(n ast.Node) { + runFunc(pass, n, config) + }) + + return nil, nil + } +} + +type spanVar struct { + stmt ast.Node + id *ast.Ident + vr *types.Var + spanType spanType +} + +// runFunc checks if the node is a function, has a span, and the span never has SetStatus set. +func runFunc(pass *analysis.Pass, node ast.Node, config *Config) { + // copying https://cs.opensource.google/go/x/tools/+/master:go/analysis/passes/lostcancel/lostcancel.go + + // Find scope of function node + var funcScope *types.Scope + switch v := node.(type) { + case *ast.FuncLit: + funcScope = pass.TypesInfo.Scopes[v.Type] + case *ast.FuncDecl: + funcScope = pass.TypesInfo.Scopes[v.Type] + } + + // Maps each span variable to its defining ValueSpec/AssignStmt. + spanVars := make(map[*ast.Ident]spanVar) + + // Find the set of span vars to analyze. + stack := make([]ast.Node, 0, stackLen) + ast.Inspect(node, func(n ast.Node) bool { + switch n.(type) { + case *ast.FuncLit: + if len(stack) > 0 { + return false // don't stray into nested functions + } + case nil: + stack = stack[:len(stack)-1] // pop + return true + } + stack = append(stack, n) // push + + // Look for [{AssignStmt,ValueSpec} CallExpr SelectorExpr]: + // + // ctx, span := otel.Tracer("app").Start(...) + // ctx, span = otel.Tracer("app").Start(...) + // var ctx, span = otel.Tracer("app").Start(...) + sType, sStart := isSpanStart(pass.TypesInfo, n) + if !sStart || !isCall(stack[len(stack)-2]) { + return true + } + + stmt := stack[len(stack)-3] + id := getID(stmt) + if id == nil { + pass.ReportRangef(n, "span is unassigned, probable memory leak") + return true + } + + if id.Name == "_" { + pass.ReportRangef(id, "span is unassigned, probable memory leak") + } else if v, ok := pass.TypesInfo.Uses[id].(*types.Var); ok { + // If the span variable is defined outside function scope, + // do not analyze it. + if funcScope.Contains(v.Pos()) { + spanVars[id] = spanVar{ + vr: v, + stmt: stmt, + id: id, + spanType: sType, + } + } + } else if v, ok := pass.TypesInfo.Defs[id].(*types.Var); ok { + spanVars[id] = spanVar{ + vr: v, + stmt: stmt, + id: id, + spanType: sType, + } + } + + return true + }) + + if len(spanVars) == 0 { + return // no need to inspect CFG + } + + // Obtain the CFG. + cfgs := pass.ResultOf[ctrlflow.Analyzer].(*ctrlflow.CFGs) + var g *cfg.CFG + var sig *types.Signature + switch node := node.(type) { + case *ast.FuncDecl: + sig, _ = pass.TypesInfo.Defs[node.Name].Type().(*types.Signature) + g = cfgs.FuncDecl(node) + case *ast.FuncLit: + sig, _ = pass.TypesInfo.Types[node.Type].Type.(*types.Signature) + g = cfgs.FuncLit(node) + } + if sig == nil { + return // missing type information + } + + // Check for missing calls. + for _, sv := range spanVars { + if config.endCheckEnabled { + // Check if there's no End to the span. + if ret := missingSpanCalls(pass, g, sv, "End", func(pass *analysis.Pass, ret *ast.ReturnStmt) *ast.ReturnStmt { return ret }, nil); ret != nil { + pass.ReportRangef(sv.stmt, "%s.End is not called on all paths, possible memory leak", sv.vr.Name()) + pass.ReportRangef(ret, "return can be reached without calling %s.End", sv.vr.Name()) + } + } + + if config.setStatusEnabled { + // Check if there's no SetStatus to the span setting an error. + if ret := missingSpanCalls(pass, g, sv, "SetStatus", returnsErr, config.ignoreChecksSignatures); ret != nil { + pass.ReportRangef(sv.stmt, "%s.SetStatus is not called on all paths", sv.vr.Name()) + pass.ReportRangef(ret, "return can be reached without calling %s.SetStatus", sv.vr.Name()) + } + } + + if config.recordErrorEnabled && sv.spanType == spanOpenTelemetry { // RecordError only exists in OpenTelemetry + // Check if there's no RecordError to the span setting an error. + if ret := missingSpanCalls(pass, g, sv, "RecordError", returnsErr, config.ignoreChecksSignatures); ret != nil { + pass.ReportRangef(sv.stmt, "%s.RecordError is not called on all paths", sv.vr.Name()) + pass.ReportRangef(ret, "return can be reached without calling %s.RecordError", sv.vr.Name()) + } + } + } +} + +// isSpanStart reports whether n is tracer.Start() +func isSpanStart(info *types.Info, n ast.Node) (spanType, bool) { + sel, ok := n.(*ast.SelectorExpr) + if !ok { + return spanUnset, false + } + + switch sel.Sel.Name { + case "Start": // https://github.com/open-telemetry/opentelemetry-go/blob/98b32a6c3a87fbee5d34c063b9096f416b250897/trace/trace.go#L523 + obj, ok := info.Uses[sel.Sel] + return spanOpenTelemetry, ok && obj.Pkg().Path() == "go.opentelemetry.io/otel/trace" + case "StartSpan": // https://pkg.go.dev/go.opencensus.io/trace#StartSpan + obj, ok := info.Uses[sel.Sel] + return spanOpenCensus, ok && obj.Pkg().Path() == "go.opencensus.io/trace" + case "StartSpanWithRemoteParent": // https://github.com/census-instrumentation/opencensus-go/blob/v0.24.0/trace/trace_api.go#L66 + obj, ok := info.Uses[sel.Sel] + return spanOpenCensus, ok && obj.Pkg().Path() == "go.opencensus.io/trace" + default: + return spanUnset, false + } +} + +func isCall(n ast.Node) bool { + _, ok := n.(*ast.CallExpr) + return ok +} + +func getID(node ast.Node) *ast.Ident { + switch stmt := node.(type) { + case *ast.ValueSpec: + if len(stmt.Names) > 1 { + return stmt.Names[1] + } + case *ast.AssignStmt: + if len(stmt.Lhs) > 1 { + id, _ := stmt.Lhs[1].(*ast.Ident) + return id + } + } + return nil +} + +// missingSpanCalls finds a path through the CFG, from stmt (which defines +// the 'span' variable v) to a return statement, that doesn't call the passed selector on the span. +func missingSpanCalls( + pass *analysis.Pass, + g *cfg.CFG, + sv spanVar, + selName string, + checkErr func(pass *analysis.Pass, ret *ast.ReturnStmt) *ast.ReturnStmt, + ignoreCheckSig *regexp.Regexp, +) *ast.ReturnStmt { + // usesCall reports whether stmts contain a use of the selName call on variable v. + usesCall := func(pass *analysis.Pass, stmts []ast.Node) bool { + found, reAssigned := false, false + for _, subStmt := range stmts { + stack := []ast.Node{} + ast.Inspect(subStmt, func(n ast.Node) bool { + switch n := n.(type) { + case *ast.FuncLit: + if len(stack) > 0 { + return false // don't stray into nested functions + } + case *ast.CallExpr: + if ident, ok := n.Fun.(*ast.Ident); ok { + fnSig := pass.TypesInfo.ObjectOf(ident).String() + if ignoreCheckSig != nil && ignoreCheckSig.MatchString(fnSig) { + found = true + return false + } + } + case nil: + stack = stack[:len(stack)-1] // pop + return true + } + stack = append(stack, n) // push + + // Check whether the span was assigned over top of its old value. + _, spanStart := isSpanStart(pass.TypesInfo, n) + if spanStart { + if id := getID(stack[len(stack)-3]); id != nil && id.Obj.Decl == sv.id.Obj.Decl { + reAssigned = true + return false + } + } + + if n, ok := n.(*ast.SelectorExpr); ok { + // Selector (End, SetStatus, RecordError) hit. + if n.Sel.Name == selName { + id, ok := n.X.(*ast.Ident) + found = ok && id.Obj.Decl == sv.id.Obj.Decl + } + + // Check if an ignore signature matches. + fnSig := pass.TypesInfo.ObjectOf(n.Sel).String() + if ignoreCheckSig != nil && ignoreCheckSig.MatchString(fnSig) { + found = true + } + } + + return !found + }) + } + return found && !reAssigned + } + + // blockUses computes "uses" for each block, caching the result. + memo := make(map[*cfg.Block]bool) + blockUses := func(pass *analysis.Pass, b *cfg.Block) bool { + res, ok := memo[b] + if !ok { + res = usesCall(pass, b.Nodes) + memo[b] = res + } + return res + } + + // Find the var's defining block in the CFG, + // plus the rest of the statements of that block. + var defBlock *cfg.Block + var rest []ast.Node +outer: + for _, b := range g.Blocks { + for i, n := range b.Nodes { + if n == sv.stmt { + defBlock = b + rest = b.Nodes[i+1:] + break outer + } + } + } + if defBlock == nil { + log.Default().Print("[ERROR] internal error: can't find defining block for span var") + } + + // Is the call "used" in the remainder of its defining block? + if usesCall(pass, rest) { + return nil + } + + // Does the defining block return without making the call? + if ret := defBlock.Return(); ret != nil { + return checkErr(pass, ret) + } + + // Search the CFG depth-first for a path, from defblock to a + // return block, in which v is never "used". + seen := make(map[*cfg.Block]bool) + var search func(blocks []*cfg.Block) *ast.ReturnStmt + search = func(blocks []*cfg.Block) *ast.ReturnStmt { + for _, b := range blocks { + if seen[b] { + continue + } + seen[b] = true + + // Prune the search if the block uses v. + if blockUses(pass, b) { + continue + } + + // Found path to return statement? + if ret := returnsErr(pass, b.Return()); ret != nil { + return ret // found + } + + // Recur + if ret := returnsErr(pass, search(b.Succs)); ret != nil { + return ret + } + } + return nil + } + + return search(defBlock.Succs) +} + +func returnsErr(pass *analysis.Pass, ret *ast.ReturnStmt) *ast.ReturnStmt { + if ret == nil { + return nil + } + + for _, r := range ret.Results { + if isErrorType(pass.TypesInfo.TypeOf(r)) { + return ret + } + + if r, ok := r.(*ast.CallExpr); ok { + for _, err := range errorsByArg(pass, r) { + if err { + return ret + } + } + } + } + + return nil +} + +// errorsByArg returns a slice s such that +// len(s) == number of return types of call +// s[i] == true iff return type at position i from left is an error type +// +// copied from https://github.com/kisielk/errcheck/blob/master/errcheck/errcheck.go +func errorsByArg(pass *analysis.Pass, call *ast.CallExpr) []bool { + switch t := pass.TypesInfo.Types[call].Type.(type) { + case *types.Named: + // Single return + return []bool{isErrorType(t)} + case *types.Pointer: + // Single return via pointer + return []bool{isErrorType(t)} + case *types.Tuple: + // Multiple returns + s := make([]bool, t.Len()) + for i := 0; i < t.Len(); i++ { + switch et := t.At(i).Type().(type) { + case *types.Named: + // Single return + s[i] = isErrorType(et) + case *types.Pointer: + // Single return via pointer + s[i] = isErrorType(et) + default: + s[i] = false + } + } + return s + } + return []bool{false} +} + +func isErrorType(t types.Type) bool { + return types.Implements(t, errorType) +} diff --git a/vendor/github.com/kisielk/errcheck/errcheck/analyzer.go b/vendor/github.com/kisielk/errcheck/errcheck/analyzer.go index 68593cc9a..82ab6298a 100644 --- a/vendor/github.com/kisielk/errcheck/errcheck/analyzer.go +++ b/vendor/github.com/kisielk/errcheck/errcheck/analyzer.go @@ -31,7 +31,6 @@ func init() { } func runAnalyzer(pass *analysis.Pass) (interface{}, error) { - exclude := map[string]bool{} if !argExcludeOnly { for _, name := range DefaultExcludedSymbols { @@ -65,8 +64,9 @@ func runAnalyzer(pass *analysis.Pass) (interface{}, error) { for _, err := range v.errors { pass.Report(analysis.Diagnostic{ - Pos: pass.Fset.File(f.Pos()).Pos(err.Pos.Offset), - Message: "unchecked error", + Pos: pass.Fset.File(f.Pos()).Pos(err.Pos.Offset), + Message: "unchecked error", + Category: "errcheck", }) } diff --git a/vendor/github.com/kisielk/errcheck/errcheck/errcheck.go b/vendor/github.com/kisielk/errcheck/errcheck/errcheck.go index a5ee3711c..d61d348f7 100644 --- a/vendor/github.com/kisielk/errcheck/errcheck/errcheck.go +++ b/vendor/github.com/kisielk/errcheck/errcheck/errcheck.go @@ -167,7 +167,7 @@ func (c *Checker) LoadPackages(paths ...string) ([]*packages.Package, error) { buildFlags = append(buildFlags, fmt.Sprintf("-mod=%s", c.Mod)) } cfg := &packages.Config{ - Mode: packages.LoadAllSyntax, + Mode: packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo, Tests: !c.Exclusions.TestFiles, BuildFlags: buildFlags, } @@ -205,7 +205,7 @@ func (c *Checker) CheckPackage(pkg *packages.Package) Result { ignore := map[string]*regexp.Regexp{} // Apply SymbolRegexpsByPackage first so that if the same path appears in - // Packages, a more narrow regexp will be superceded by dotStar below. + // Packages, a more narrow regexp will be superseded by dotStar below. if regexps := c.Exclusions.SymbolRegexpsByPackage; regexps != nil { for pkg, re := range regexps { // TODO warn if previous entry overwritten? @@ -337,7 +337,7 @@ func (v *visitor) selectorName(call *ast.CallExpr) string { // names are returned. If the function is package-qualified (like "fmt.Printf()") // then just that function's fullName is returned. // -// Otherwise, we walk through all the potentially embeddded interfaces of the receiver +// Otherwise, we walk through all the potentially embedded interfaces of the receiver // the collect a list of type-qualified function names that we will check. func (v *visitor) namesForExcludeCheck(call *ast.CallExpr) []string { sel, fn, ok := v.selectorAndFunc(call) diff --git a/vendor/github.com/kisielk/errcheck/errcheck/excludes.go b/vendor/github.com/kisielk/errcheck/errcheck/excludes.go index 22db9fe11..a783b5a76 100644 --- a/vendor/github.com/kisielk/errcheck/errcheck/excludes.go +++ b/vendor/github.com/kisielk/errcheck/errcheck/excludes.go @@ -3,64 +3,60 @@ package errcheck import ( "bufio" "bytes" - "io/ioutil" + "os" "strings" ) -var ( - // DefaultExcludedSymbols is a list of symbol names that are usually excluded from checks by default. - // - // Note, that they still need to be explicitly copied to Checker.Exclusions.Symbols - DefaultExcludedSymbols = []string{ - // bytes - "(*bytes.Buffer).Write", - "(*bytes.Buffer).WriteByte", - "(*bytes.Buffer).WriteRune", - "(*bytes.Buffer).WriteString", +// DefaultExcludedSymbols is a list of symbol names that are usually excluded from checks by default. +// +// Note, that they still need to be explicitly copied to Checker.Exclusions.Symbols +var DefaultExcludedSymbols = []string{ + // bytes + "(*bytes.Buffer).Write", + "(*bytes.Buffer).WriteByte", + "(*bytes.Buffer).WriteRune", + "(*bytes.Buffer).WriteString", - // fmt - "fmt.Errorf", - "fmt.Print", - "fmt.Printf", - "fmt.Println", - "fmt.Fprint(*bytes.Buffer)", - "fmt.Fprintf(*bytes.Buffer)", - "fmt.Fprintln(*bytes.Buffer)", - "fmt.Fprint(*strings.Builder)", - "fmt.Fprintf(*strings.Builder)", - "fmt.Fprintln(*strings.Builder)", - "fmt.Fprint(os.Stderr)", - "fmt.Fprintf(os.Stderr)", - "fmt.Fprintln(os.Stderr)", + // fmt + "fmt.Print", + "fmt.Printf", + "fmt.Println", + "fmt.Fprint(*bytes.Buffer)", + "fmt.Fprintf(*bytes.Buffer)", + "fmt.Fprintln(*bytes.Buffer)", + "fmt.Fprint(*strings.Builder)", + "fmt.Fprintf(*strings.Builder)", + "fmt.Fprintln(*strings.Builder)", + "fmt.Fprint(os.Stderr)", + "fmt.Fprintf(os.Stderr)", + "fmt.Fprintln(os.Stderr)", - // io - "(*io.PipeReader).CloseWithError", - "(*io.PipeWriter).CloseWithError", + // io + "(*io.PipeReader).CloseWithError", + "(*io.PipeWriter).CloseWithError", - // math/rand - "math/rand.Read", - "(*math/rand.Rand).Read", + // math/rand + "math/rand.Read", + "(*math/rand.Rand).Read", - // strings - "(*strings.Builder).Write", - "(*strings.Builder).WriteByte", - "(*strings.Builder).WriteRune", - "(*strings.Builder).WriteString", + // strings + "(*strings.Builder).Write", + "(*strings.Builder).WriteByte", + "(*strings.Builder).WriteRune", + "(*strings.Builder).WriteString", - // hash - "(hash.Hash).Write", - } -) + // hash + "(hash.Hash).Write", +} // ReadExcludes reads an excludes file, a newline delimited file that lists // patterns for which to allow unchecked errors. // // Lines that start with two forward slashes are considered comments and are ignored. -// func ReadExcludes(path string) ([]string, error) { var excludes []string - buf, err := ioutil.ReadFile(path) + buf, err := os.ReadFile(path) if err != nil { return nil, err } diff --git a/vendor/github.com/kunwardeep/paralleltest/pkg/paralleltest/paralleltest.go b/vendor/github.com/kunwardeep/paralleltest/pkg/paralleltest/paralleltest.go index e21f278cf..1aecc1a51 100644 --- a/vendor/github.com/kunwardeep/paralleltest/pkg/paralleltest/paralleltest.go +++ b/vendor/github.com/kunwardeep/paralleltest/pkg/paralleltest/paralleltest.go @@ -54,8 +54,10 @@ func (a *parallelAnalyzer) run(pass *analysis.Pass) (interface{}, error) { inspector.Preorder(nodeFilter, func(node ast.Node) { funcDecl := node.(*ast.FuncDecl) var funcHasParallelMethod, + funcCantParallelMethod, rangeStatementOverTestCasesExists, - rangeStatementHasParallelMethod bool + rangeStatementHasParallelMethod, + rangeStatementCantParallelMethod bool var loopVariableUsedInRun *string var numberOfTestRun int var positionOfTestRunNode []ast.Node @@ -77,20 +79,29 @@ func (a *parallelAnalyzer) run(pass *analysis.Pass) (interface{}, error) { funcHasParallelMethod = methodParallelIsCalledInTestFunction(n, testVar) } + // Check if the test calls t.Setenv, cannot be used in parallel tests or tests with parallel ancestors + if !funcCantParallelMethod { + funcCantParallelMethod = methodSetenvIsCalledInTestFunction(n, testVar) + } + // Check if the t.Run within the test function is calling t.Parallel if methodRunIsCalledInTestFunction(n, testVar) { // n is a call to t.Run; find out the name of the subtest's *testing.T parameter. innerTestVar := getRunCallbackParameterName(n) hasParallel := false + cantParallel := false numberOfTestRun++ ast.Inspect(v, func(p ast.Node) bool { if !hasParallel { hasParallel = methodParallelIsCalledInTestFunction(p, innerTestVar) } + if !cantParallel { + cantParallel = methodSetenvIsCalledInTestFunction(p, innerTestVar) + } return true }) - if !hasParallel { + if !hasParallel && !cantParallel { positionOfTestRunNode = append(positionOfTestRunNode, n) } } @@ -122,6 +133,10 @@ func (a *parallelAnalyzer) run(pass *analysis.Pass) (interface{}, error) { rangeStatementHasParallelMethod = methodParallelIsCalledInMethodRun(r.X, innerTestVar) } + if !rangeStatementCantParallelMethod { + rangeStatementCantParallelMethod = methodSetenvIsCalledInMethodRun(r.X, innerTestVar) + } + if loopVariableUsedInRun == nil { if run, ok := r.X.(*ast.CallExpr); ok { loopVariableUsedInRun = loopVarReferencedInRun(run, loopVars, pass.TypesInfo) @@ -134,12 +149,17 @@ func (a *parallelAnalyzer) run(pass *analysis.Pass) (interface{}, error) { } } - if !a.ignoreMissing && !funcHasParallelMethod { + // Descendents which call Setenv, also prevent tests from calling Parallel + if rangeStatementCantParallelMethod { + funcCantParallelMethod = true + } + + if !a.ignoreMissing && !funcHasParallelMethod && !funcCantParallelMethod { pass.Reportf(node.Pos(), "Function %s missing the call to method parallel\n", funcDecl.Name.Name) } if rangeStatementOverTestCasesExists && rangeNode != nil { - if !rangeStatementHasParallelMethod { + if !rangeStatementHasParallelMethod && !rangeStatementCantParallelMethod { 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) } @@ -162,15 +182,23 @@ func (a *parallelAnalyzer) run(pass *analysis.Pass) (interface{}, error) { } func methodParallelIsCalledInMethodRun(node ast.Node, testVar string) bool { - var methodParallelCalled bool + return targetMethodIsCalledInMethodRun(node, testVar, "Parallel") +} + +func methodSetenvIsCalledInMethodRun(node ast.Node, testVar string) bool { + return targetMethodIsCalledInMethodRun(node, testVar, "Setenv") +} + +func targetMethodIsCalledInMethodRun(node ast.Node, testVar, targetMethod string) bool { + var called bool // nolint: gocritic switch callExp := node.(type) { case *ast.CallExpr: for _, arg := range callExp.Args { - if !methodParallelCalled { + if !called { ast.Inspect(arg, func(n ast.Node) bool { - if !methodParallelCalled { - methodParallelCalled = methodParallelIsCalledInRunMethod(n, testVar) + if !called { + called = exprCallHasMethod(n, testVar, targetMethod) return true } return false @@ -178,11 +206,7 @@ func methodParallelIsCalledInMethodRun(node ast.Node, testVar string) bool { } } } - return methodParallelCalled -} - -func methodParallelIsCalledInRunMethod(node ast.Node, testVar string) bool { - return exprCallHasMethod(node, testVar, "Parallel") + return called } func methodParallelIsCalledInTestFunction(node ast.Node, testVar string) bool { @@ -196,6 +220,11 @@ func methodRunIsCalledInRangeStatement(node ast.Node, testVar string) bool { func methodRunIsCalledInTestFunction(node ast.Node, testVar string) bool { return exprCallHasMethod(node, testVar, "Run") } + +func methodSetenvIsCalledInTestFunction(node ast.Node, testVar string) bool { + return exprCallHasMethod(node, testVar, "Setenv") +} + func exprCallHasMethod(node ast.Node, receiverName, methodName string) bool { // nolint: gocritic switch n := node.(type) { diff --git a/vendor/github.com/macabu/inamedparam/.gitignore b/vendor/github.com/macabu/inamedparam/.gitignore index 3b735ec4a..f8d51e94c 100644 --- a/vendor/github.com/macabu/inamedparam/.gitignore +++ b/vendor/github.com/macabu/inamedparam/.gitignore @@ -7,6 +7,7 @@ *.dll *.so *.dylib +inamedparam # Test binary, built with `go test -c` *.test diff --git a/vendor/github.com/macabu/inamedparam/.golangci.yml b/vendor/github.com/macabu/inamedparam/.golangci.yml new file mode 100644 index 000000000..f0efa1cb6 --- /dev/null +++ b/vendor/github.com/macabu/inamedparam/.golangci.yml @@ -0,0 +1,33 @@ +run: + deadline: 30s + +linters: + enable-all: true + disable: + - cyclop + - deadcode + - depguard + - exhaustivestruct + - exhaustruct + - forcetypeassert + - gochecknoglobals + - gocognit + - golint + - ifshort + - interfacer + - maligned + - nilnil + - nosnakecase + - paralleltest + - scopelint + - structcheck + - varcheck + +linters-settings: + gci: + sections: + - standard + - default + - prefix(github.com/macabu/inamedparam) + section-separators: + - newLine diff --git a/vendor/github.com/macabu/inamedparam/README.md b/vendor/github.com/macabu/inamedparam/README.md index 80911c9d7..3336cb950 100644 --- a/vendor/github.com/macabu/inamedparam/README.md +++ b/vendor/github.com/macabu/inamedparam/README.md @@ -2,10 +2,16 @@ A linter that reports interfaces with unnamed method parameters. +## Flags/Config +```sh +-skip-single-param + skip interfaces with a single unnamed parameter +``` + ## Usage ### Standalone -You can also run it standalone through `go vet`. +You can run it standalone through `go vet`. You must install the binary to your `$GOBIN` folder like so: ```sh @@ -16,3 +22,17 @@ And then navigate to your Go project's root folder, where can run `go vet` in th ```sh $ go vet -vettool=$(which inamedparam) ./... ``` + +### golangci-lint +`inamedparam` was added as a linter to `golangci-lint` on version `v1.55.0`. It is disabled by default. + +To enable it, you can add it to your `.golangci.yml` file, as such: +```yaml +run: + deadline: 30s + +linters: + disable-all: true + enable: + - inamedparam +``` diff --git a/vendor/github.com/macabu/inamedparam/inamedparam.go b/vendor/github.com/macabu/inamedparam/inamedparam.go index 433e04e5b..8ba7fe188 100644 --- a/vendor/github.com/macabu/inamedparam/inamedparam.go +++ b/vendor/github.com/macabu/inamedparam/inamedparam.go @@ -1,6 +1,7 @@ package inamedparam import ( + "flag" "go/ast" "golang.org/x/tools/go/analysis" @@ -8,15 +9,30 @@ import ( "golang.org/x/tools/go/ast/inspector" ) +const ( + analyzerName = "inamedparam" + + flagSkipSingleParam = "skip-single-param" +) + var Analyzer = &analysis.Analyzer{ - Name: "inamedparam", - Doc: "reports interfaces with unnamed method parameters", - Run: run, + Name: analyzerName, + Doc: "reports interfaces with unnamed method parameters", + Run: run, + Flags: flags(), Requires: []*analysis.Analyzer{ inspect.Analyzer, }, } +func flags() flag.FlagSet { + flags := flag.NewFlagSet(analyzerName, flag.ExitOnError) + + flags.Bool(flagSkipSingleParam, false, "skip interface methods with a single unnamed parameter") + + return *flags +} + func run(pass *analysis.Pass) (interface{}, error) { inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) @@ -24,6 +40,8 @@ func run(pass *analysis.Pass) (interface{}, error) { &ast.InterfaceType{}, } + skipSingleParam := pass.Analyzer.Flags.Lookup(flagSkipSingleParam).Value.(flag.Getter).Get().(bool) + inspect.Preorder(types, func(n ast.Node) { interfaceType, ok := n.(*ast.InterfaceType) if !ok || interfaceType == nil || interfaceType.Methods == nil { @@ -36,8 +54,17 @@ func run(pass *analysis.Pass) (interface{}, error) { continue } + // Improvement: add test case to reproduce this. Help wanted. + if len(method.Names) == 0 { + continue + } + methodName := method.Names[0].Name + if skipSingleParam && len(interfaceFunc.Params.List) == 1 { + continue + } + for _, param := range interfaceFunc.Params.List { if param.Names == nil { var builtParamType string diff --git a/vendor/github.com/mattn/go-isatty/isatty_bsd.go b/vendor/github.com/mattn/go-isatty/isatty_bsd.go index d569c0c94..d0ea68f40 100644 --- a/vendor/github.com/mattn/go-isatty/isatty_bsd.go +++ b/vendor/github.com/mattn/go-isatty/isatty_bsd.go @@ -1,6 +1,7 @@ -//go:build (darwin || freebsd || openbsd || netbsd || dragonfly || hurd) && !appengine +//go:build (darwin || freebsd || openbsd || netbsd || dragonfly || hurd) && !appengine && !tinygo // +build darwin freebsd openbsd netbsd dragonfly hurd // +build !appengine +// +build !tinygo package isatty diff --git a/vendor/github.com/mattn/go-isatty/isatty_others.go b/vendor/github.com/mattn/go-isatty/isatty_others.go index 31503226f..7402e0618 100644 --- a/vendor/github.com/mattn/go-isatty/isatty_others.go +++ b/vendor/github.com/mattn/go-isatty/isatty_others.go @@ -1,5 +1,6 @@ -//go:build appengine || js || nacl || wasm -// +build appengine js nacl wasm +//go:build (appengine || js || nacl || tinygo || wasm) && !windows +// +build appengine js nacl tinygo wasm +// +build !windows package isatty diff --git a/vendor/github.com/mattn/go-isatty/isatty_tcgets.go b/vendor/github.com/mattn/go-isatty/isatty_tcgets.go index 67787657f..0337d8cf6 100644 --- a/vendor/github.com/mattn/go-isatty/isatty_tcgets.go +++ b/vendor/github.com/mattn/go-isatty/isatty_tcgets.go @@ -1,6 +1,7 @@ -//go:build (linux || aix || zos) && !appengine +//go:build (linux || aix || zos) && !appengine && !tinygo // +build linux aix zos // +build !appengine +// +build !tinygo package isatty diff --git a/vendor/github.com/mgechev/revive/config/config.go b/vendor/github.com/mgechev/revive/config/config.go index abd554a9f..50a2b8966 100644 --- a/vendor/github.com/mgechev/revive/config/config.go +++ b/vendor/github.com/mgechev/revive/config/config.go @@ -1,3 +1,4 @@ +// Package config implements revive's configuration data structures and related methods package config import ( @@ -5,9 +6,9 @@ import ( "fmt" "os" - "github.com/mgechev/revive/formatter" - "github.com/BurntSushi/toml" + + "github.com/mgechev/revive/formatter" "github.com/mgechev/revive/lint" "github.com/mgechev/revive/rule" ) @@ -54,7 +55,7 @@ var allRules = append([]lint.Rule{ &rule.ModifiesValRecRule{}, &rule.ConstantLogicalExprRule{}, &rule.BoolLiteralRule{}, - &rule.ImportsBlacklistRule{}, + &rule.ImportsBlocklistRule{}, &rule.FunctionResultsLimitRule{}, &rule.MaxPublicStructsRule{}, &rule.RangeValInClosureRule{}, @@ -91,6 +92,9 @@ var allRules = append([]lint.Rule{ &rule.RedundantImportAlias{}, &rule.ImportAliasNamingRule{}, &rule.EnforceMapStyleRule{}, + &rule.EnforceRepeatedArgTypeStyleRule{}, + &rule.EnforceSliceStyleRule{}, + &rule.MaxControlNestingRule{}, }, defaultRules...) var allFormatters = []lint.Formatter{ @@ -128,7 +132,8 @@ func GetLintingRules(config *lint.Config, extraRules []lint.Rule) ([]lint.Rule, var lintingRules []lint.Rule for name, ruleConfig := range config.Rules { - r, ok := rulesMap[name] + actualName := actualRuleName(name) + r, ok := rulesMap[actualName] if !ok { return nil, fmt.Errorf("cannot find rule: %s", name) } @@ -143,6 +148,15 @@ func GetLintingRules(config *lint.Config, extraRules []lint.Rule) ([]lint.Rule, return lintingRules, nil } +func actualRuleName(name string) string { + switch name { + case "imports-blacklist": + return "imports-blocklist" + default: + return name + } +} + func parseConfig(path string, config *lint.Config) error { file, err := os.ReadFile(path) if err != nil { diff --git a/vendor/github.com/mgechev/revive/formatter/checkstyle.go b/vendor/github.com/mgechev/revive/formatter/checkstyle.go index 33a3b2ca1..f45b63c92 100644 --- a/vendor/github.com/mgechev/revive/formatter/checkstyle.go +++ b/vendor/github.com/mgechev/revive/formatter/checkstyle.go @@ -3,7 +3,7 @@ package formatter import ( "bytes" "encoding/xml" - plainTemplate "text/template" + plain "text/template" "github.com/mgechev/revive/lint" ) @@ -50,7 +50,7 @@ func (*Checkstyle) Format(failures <-chan lint.Failure, config lint.Config) (str issues[fn] = append(issues[fn], iss) } - t, err := plainTemplate.New("revive").Parse(checkstyleTemplate) + t, err := plain.New("revive").Parse(checkstyleTemplate) if err != nil { return "", err } diff --git a/vendor/github.com/mgechev/revive/formatter/default.go b/vendor/github.com/mgechev/revive/formatter/default.go index f76a7b29a..2d5a04434 100644 --- a/vendor/github.com/mgechev/revive/formatter/default.go +++ b/vendor/github.com/mgechev/revive/formatter/default.go @@ -1,6 +1,7 @@ package formatter import ( + "bytes" "fmt" "github.com/mgechev/revive/lint" @@ -19,8 +20,9 @@ func (*Default) Name() string { // Format formats the failures gotten from the lint. func (*Default) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) { + var buf bytes.Buffer for failure := range failures { - fmt.Printf("%v: %s\n", failure.Position.Start, failure.Failure) + fmt.Fprintf(&buf, "%v: %s\n", failure.Position.Start, failure.Failure) } - return "", nil + return buf.String(), nil } diff --git a/vendor/github.com/mgechev/revive/formatter/doc.go b/vendor/github.com/mgechev/revive/formatter/doc.go new file mode 100644 index 000000000..bb89f20ea --- /dev/null +++ b/vendor/github.com/mgechev/revive/formatter/doc.go @@ -0,0 +1,2 @@ +// Package formatter implements the linter output formatters. +package formatter diff --git a/vendor/github.com/mgechev/revive/formatter/friendly.go b/vendor/github.com/mgechev/revive/formatter/friendly.go index ced8fa46c..5ff329a23 100644 --- a/vendor/github.com/mgechev/revive/formatter/friendly.go +++ b/vendor/github.com/mgechev/revive/formatter/friendly.go @@ -3,6 +3,7 @@ package formatter import ( "bytes" "fmt" + "io" "sort" "github.com/fatih/color" @@ -31,13 +32,14 @@ func (*Friendly) Name() string { // Format formats the failures gotten from the lint. func (f *Friendly) Format(failures <-chan lint.Failure, config lint.Config) (string, error) { + var buf bytes.Buffer errorMap := map[string]int{} warningMap := map[string]int{} totalErrors := 0 totalWarnings := 0 for failure := range failures { sev := severity(config, failure) - f.printFriendlyFailure(failure, sev) + f.printFriendlyFailure(&buf, failure, sev) if sev == lint.SeverityWarning { warningMap[failure.RuleName]++ totalWarnings++ @@ -47,29 +49,29 @@ func (f *Friendly) Format(failures <-chan lint.Failure, config lint.Config) (str totalErrors++ } } - f.printSummary(totalErrors, totalWarnings) - f.printStatistics(color.RedString("Errors:"), errorMap) - f.printStatistics(color.YellowString("Warnings:"), warningMap) - return "", nil + f.printSummary(&buf, totalErrors, totalWarnings) + f.printStatistics(&buf, color.RedString("Errors:"), errorMap) + f.printStatistics(&buf, color.YellowString("Warnings:"), warningMap) + return buf.String(), nil } -func (f *Friendly) printFriendlyFailure(failure lint.Failure, severity lint.Severity) { - f.printHeaderRow(failure, severity) - f.printFilePosition(failure) - fmt.Println() - fmt.Println() +func (f *Friendly) printFriendlyFailure(w io.Writer, failure lint.Failure, severity lint.Severity) { + f.printHeaderRow(w, failure, severity) + f.printFilePosition(w, failure) + fmt.Fprintln(w) + fmt.Fprintln(w) } -func (f *Friendly) printHeaderRow(failure lint.Failure, severity lint.Severity) { +func (f *Friendly) printHeaderRow(w io.Writer, failure lint.Failure, severity lint.Severity) { emoji := getWarningEmoji() if severity == lint.SeverityError { emoji = getErrorEmoji() } - fmt.Print(f.table([][]string{{emoji, "https://revive.run/r#" + failure.RuleName, color.GreenString(failure.Failure)}})) + fmt.Fprint(w, f.table([][]string{{emoji, "https://revive.run/r#" + failure.RuleName, color.GreenString(failure.Failure)}})) } -func (*Friendly) printFilePosition(failure lint.Failure) { - fmt.Printf(" %s:%d:%d", failure.GetFilename(), failure.Position.Start.Line, failure.Position.Start.Column) +func (*Friendly) printFilePosition(w io.Writer, failure lint.Failure) { + fmt.Fprintf(w, " %s:%d:%d", failure.GetFilename(), failure.Position.Start.Line, failure.Position.Start.Column) } type statEntry struct { @@ -77,7 +79,7 @@ type statEntry struct { failures int } -func (*Friendly) printSummary(errors, warnings int) { +func (*Friendly) printSummary(w io.Writer, errors, warnings int) { emoji := getWarningEmoji() if errors > 0 { emoji = getErrorEmoji() @@ -96,18 +98,18 @@ func (*Friendly) printSummary(errors, warnings int) { } str := fmt.Sprintf("%d %s (%d %s, %d %s)", errors+warnings, problemsLabel, errors, errorsLabel, warnings, warningsLabel) if errors > 0 { - fmt.Printf("%s %s\n", emoji, color.RedString(str)) - fmt.Println() + fmt.Fprintf(w, "%s %s\n", emoji, color.RedString(str)) + fmt.Fprintln(w) return } if warnings > 0 { - fmt.Printf("%s %s\n", emoji, color.YellowString(str)) - fmt.Println() + fmt.Fprintf(w, "%s %s\n", emoji, color.YellowString(str)) + fmt.Fprintln(w) return } } -func (f *Friendly) printStatistics(header string, stats map[string]int) { +func (f *Friendly) printStatistics(w io.Writer, header string, stats map[string]int) { if len(stats) == 0 { return } @@ -122,8 +124,8 @@ func (f *Friendly) printStatistics(header string, stats map[string]int) { for _, entry := range data { formatted = append(formatted, []string{color.GreenString(fmt.Sprintf("%d", entry.failures)), entry.name}) } - fmt.Println(header) - fmt.Println(f.table(formatted)) + fmt.Fprintln(w, header) + fmt.Fprintln(w, f.table(formatted)) } func (*Friendly) table(rows [][]string) string { diff --git a/vendor/github.com/mgechev/revive/formatter/ndjson.go b/vendor/github.com/mgechev/revive/formatter/ndjson.go index a02d9c80f..58b35dc44 100644 --- a/vendor/github.com/mgechev/revive/formatter/ndjson.go +++ b/vendor/github.com/mgechev/revive/formatter/ndjson.go @@ -1,8 +1,8 @@ package formatter import ( + "bytes" "encoding/json" - "os" "github.com/mgechev/revive/lint" ) @@ -20,7 +20,8 @@ func (*NDJSON) Name() string { // Format formats the failures gotten from the lint. func (*NDJSON) Format(failures <-chan lint.Failure, config lint.Config) (string, error) { - enc := json.NewEncoder(os.Stdout) + var buf bytes.Buffer + enc := json.NewEncoder(&buf) for failure := range failures { obj := jsonObject{} obj.Severity = severity(config, failure) @@ -30,5 +31,5 @@ func (*NDJSON) Format(failures <-chan lint.Failure, config lint.Config) (string, return "", err } } - return "", nil + return buf.String(), nil } diff --git a/vendor/github.com/mgechev/revive/formatter/plain.go b/vendor/github.com/mgechev/revive/formatter/plain.go index 6e083bcfd..09ebf6cdc 100644 --- a/vendor/github.com/mgechev/revive/formatter/plain.go +++ b/vendor/github.com/mgechev/revive/formatter/plain.go @@ -1,6 +1,7 @@ package formatter import ( + "bytes" "fmt" "github.com/mgechev/revive/lint" @@ -19,8 +20,9 @@ func (*Plain) Name() string { // Format formats the failures gotten from the lint. func (*Plain) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) { + var buf bytes.Buffer for failure := range failures { - fmt.Printf("%v: %s %s\n", failure.Position.Start, failure.Failure, "https://revive.run/r#"+failure.RuleName) + fmt.Fprintf(&buf, "%v: %s %s\n", failure.Position.Start, failure.Failure, "https://revive.run/r#"+failure.RuleName) } - return "", nil + return buf.String(), nil } diff --git a/vendor/github.com/mgechev/revive/formatter/unix.go b/vendor/github.com/mgechev/revive/formatter/unix.go index ef2f1613a..e46f3c275 100644 --- a/vendor/github.com/mgechev/revive/formatter/unix.go +++ b/vendor/github.com/mgechev/revive/formatter/unix.go @@ -1,6 +1,7 @@ package formatter import ( + "bytes" "fmt" "github.com/mgechev/revive/lint" @@ -8,7 +9,8 @@ import ( // Unix is an implementation of the Formatter interface // which formats the errors to a simple line based error format -// main.go:24:9: [errorf] should replace errors.New(fmt.Sprintf(...)) with fmt.Errorf(...) +// +// main.go:24:9: [errorf] should replace errors.New(fmt.Sprintf(...)) with fmt.Errorf(...) type Unix struct { Metadata lint.FormatterMetadata } @@ -20,8 +22,9 @@ func (*Unix) Name() string { // Format formats the failures gotten from the lint. func (*Unix) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) { + var buf bytes.Buffer for failure := range failures { - fmt.Printf("%v: [%s] %s\n", failure.Position.Start, failure.RuleName, failure.Failure) + fmt.Fprintf(&buf, "%v: [%s] %s\n", failure.Position.Start, failure.RuleName, failure.Failure) } - return "", nil + return buf.String(), nil } diff --git a/vendor/github.com/mgechev/revive/lint/config.go b/vendor/github.com/mgechev/revive/lint/config.go index 9b26d5841..7e51a93c2 100644 --- a/vendor/github.com/mgechev/revive/lint/config.go +++ b/vendor/github.com/mgechev/revive/lint/config.go @@ -3,6 +3,7 @@ package lint // Arguments is type used for the arguments of a rule. type Arguments = []interface{} +// FileFilters is type used for modeling file filters to apply to rules. type FileFilters = []*FileFilter // RuleConfig is type used for the rule configuration. @@ -32,8 +33,8 @@ func (rc *RuleConfig) Initialize() error { 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 { +func (rc *RuleConfig) MustExclude(name string) bool { + for _, exclude := range rc.excludeFilters { if exclude.MatchFileName(name) { return true } diff --git a/vendor/github.com/mgechev/revive/lint/doc.go b/vendor/github.com/mgechev/revive/lint/doc.go new file mode 100644 index 000000000..7048adf4b --- /dev/null +++ b/vendor/github.com/mgechev/revive/lint/doc.go @@ -0,0 +1,2 @@ +// Package lint implements the linting machinery. +package lint diff --git a/vendor/github.com/mgechev/revive/lint/utils.go b/vendor/github.com/mgechev/revive/lint/utils.go index 28657c6df..6ccfb0ef2 100644 --- a/vendor/github.com/mgechev/revive/lint/utils.go +++ b/vendor/github.com/mgechev/revive/lint/utils.go @@ -6,7 +6,7 @@ import ( ) // Name returns a different name if it should be different. -func Name(name string, whitelist, blacklist []string) (should string) { +func Name(name string, allowlist, blocklist []string) (should string) { // Fast path for simple cases: "_" and all lowercase. if name == "_" { return name @@ -57,12 +57,12 @@ func Name(name string, whitelist, blacklist []string) (should string) { // [w,i) is a word. word := string(runes[w:i]) ignoreInitWarnings := map[string]bool{} - for _, i := range whitelist { + for _, i := range allowlist { ignoreInitWarnings[i] = true } extraInits := map[string]bool{} - for _, i := range blacklist { + for _, i := range blocklist { extraInits[i] = true } @@ -71,6 +71,10 @@ func Name(name string, whitelist, blacklist []string) (should string) { if w == 0 && unicode.IsLower(runes[w]) { u = strings.ToLower(u) } + // Keep lowercase s for IDs + if u == "IDS" { + u = "IDs" + } // All the common initialisms are ASCII, // so we can replace the bytes exactly. copy(runes[w:], []rune(u)) @@ -99,6 +103,7 @@ var commonInitialisms = map[string]bool{ "HTTP": true, "HTTPS": true, "ID": true, + "IDS": true, "IP": true, "JSON": true, "LHS": true, diff --git a/vendor/github.com/mgechev/revive/rule/add-constant.go b/vendor/github.com/mgechev/revive/rule/add-constant.go index 36a7003da..86182623a 100644 --- a/vendor/github.com/mgechev/revive/rule/add-constant.go +++ b/vendor/github.com/mgechev/revive/rule/add-constant.go @@ -18,13 +18,13 @@ const ( kindSTRING = "STRING" ) -type whiteList map[string]map[string]bool +type allowList map[string]map[string]bool -func newWhiteList() whiteList { +func newAllowList() allowList { return map[string]map[string]bool{kindINT: {}, kindFLOAT: {}, kindSTRING: {}} } -func (wl whiteList) add(kind, list string) { +func (wl allowList) add(kind, list string) { elems := strings.Split(list, ",") for _, e := range elems { wl[kind][e] = true @@ -33,7 +33,7 @@ func (wl whiteList) add(kind, list string) { // AddConstantRule lints unused params in functions. type AddConstantRule struct { - whiteList whiteList + allowList allowList ignoreFunctions []*regexp.Regexp strLitLimit int sync.Mutex @@ -49,12 +49,13 @@ func (r *AddConstantRule) Apply(file *lint.File, arguments lint.Arguments) []lin failures = append(failures, failure) } - w := lintAddConstantRule{ + w := &lintAddConstantRule{ onFailure: onFailure, strLits: make(map[string]int), strLitLimit: r.strLitLimit, - whiteLst: r.whiteList, + allowList: r.allowList, ignoreFunctions: r.ignoreFunctions, + structTags: make(map[*ast.BasicLit]struct{}), } ast.Walk(w, file.AST) @@ -71,11 +72,16 @@ type lintAddConstantRule struct { onFailure func(lint.Failure) strLits map[string]int strLitLimit int - whiteLst whiteList + allowList allowList ignoreFunctions []*regexp.Regexp + structTags map[*ast.BasicLit]struct{} } -func (w lintAddConstantRule) Visit(node ast.Node) ast.Visitor { +func (w *lintAddConstantRule) Visit(node ast.Node) ast.Visitor { + if node == nil { + return nil + } + switch n := node.(type) { case *ast.CallExpr: w.checkFunc(n) @@ -83,13 +89,23 @@ func (w lintAddConstantRule) Visit(node ast.Node) ast.Visitor { case *ast.GenDecl: return nil // skip declarations case *ast.BasicLit: - w.checkLit(n) + if !w.isStructTag(n) { + w.checkLit(n) + } + case *ast.StructType: + if n.Fields != nil { + for _, field := range n.Fields.List { + if field.Tag != nil { + w.structTags[field.Tag] = struct{}{} + } + } + } } return w } -func (w lintAddConstantRule) checkFunc(expr *ast.CallExpr) { +func (w *lintAddConstantRule) checkFunc(expr *ast.CallExpr) { fName := w.getFuncName(expr) for _, arg := range expr.Args { @@ -105,7 +121,7 @@ func (w lintAddConstantRule) checkFunc(expr *ast.CallExpr) { } } -func (w lintAddConstantRule) getFuncName(expr *ast.CallExpr) string { +func (*lintAddConstantRule) getFuncName(expr *ast.CallExpr) string { switch f := expr.Fun.(type) { case *ast.SelectorExpr: switch prefix := f.X.(type) { @@ -119,7 +135,7 @@ func (w lintAddConstantRule) getFuncName(expr *ast.CallExpr) string { return "" } -func (w lintAddConstantRule) checkLit(n *ast.BasicLit) { +func (w *lintAddConstantRule) checkLit(n *ast.BasicLit) { switch kind := n.Kind.String(); kind { case kindFLOAT, kindINT: w.checkNumLit(kind, n) @@ -128,7 +144,7 @@ func (w lintAddConstantRule) checkLit(n *ast.BasicLit) { } } -func (w lintAddConstantRule) isIgnoredFunc(fName string) bool { +func (w *lintAddConstantRule) isIgnoredFunc(fName string) bool { for _, pattern := range w.ignoreFunctions { if pattern.MatchString(fName) { return true @@ -138,8 +154,8 @@ func (w lintAddConstantRule) isIgnoredFunc(fName string) bool { return false } -func (w lintAddConstantRule) checkStrLit(n *ast.BasicLit) { - if w.whiteLst[kindSTRING][n.Value] { +func (w *lintAddConstantRule) checkStrLit(n *ast.BasicLit) { + if w.allowList[kindSTRING][n.Value] { return } @@ -158,8 +174,8 @@ func (w lintAddConstantRule) checkStrLit(n *ast.BasicLit) { } } -func (w lintAddConstantRule) checkNumLit(kind string, n *ast.BasicLit) { - if w.whiteLst[kind][n.Value] { +func (w *lintAddConstantRule) checkNumLit(kind string, n *ast.BasicLit) { + if w.allowList[kind][n.Value] { return } @@ -171,15 +187,20 @@ func (w lintAddConstantRule) checkNumLit(kind string, n *ast.BasicLit) { }) } +func (w *lintAddConstantRule) isStructTag(n *ast.BasicLit) bool { + _, ok := w.structTags[n] + return ok +} + func (r *AddConstantRule) configure(arguments lint.Arguments) { r.Lock() defer r.Unlock() - if r.whiteList == nil { + if r.allowList == nil { r.strLitLimit = defaultStrLitLimit - r.whiteList = newWhiteList() + r.allowList = newAllowList() if len(arguments) > 0 { - args, ok := arguments[0].(map[string]interface{}) + args, ok := arguments[0].(map[string]any) if !ok { panic(fmt.Sprintf("Invalid argument to the add-constant rule. Expecting a k,v map, got %T", arguments[0])) } @@ -202,7 +223,7 @@ func (r *AddConstantRule) configure(arguments lint.Arguments) { if !ok { panic(fmt.Sprintf("Invalid argument to the add-constant rule, string expected. Got '%v' (%T)", v, v)) } - r.whiteList.add(kind, list) + r.allowList.add(kind, list) case "maxLitCount": sl, ok := v.(string) if !ok { diff --git a/vendor/github.com/mgechev/revive/rule/comment-spacings.go b/vendor/github.com/mgechev/revive/rule/comment-spacings.go index 0d75c55f3..2b8240ca5 100644 --- a/vendor/github.com/mgechev/revive/rule/comment-spacings.go +++ b/vendor/github.com/mgechev/revive/rule/comment-spacings.go @@ -8,7 +8,7 @@ import ( "github.com/mgechev/revive/lint" ) -// CommentSpacings Rule check the whether there is a space between +// CommentSpacingsRule check the whether there is a space between // the comment symbol( // ) and the start of the comment text type CommentSpacingsRule struct { allowList []string @@ -36,6 +36,7 @@ func (r *CommentSpacingsRule) configure(arguments lint.Arguments) { } } +// Apply the rule. func (r *CommentSpacingsRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure { r.configure(args) @@ -74,6 +75,7 @@ func (r *CommentSpacingsRule) Apply(file *lint.File, args lint.Arguments) []lint return failures } +// Name yields this rule name. func (*CommentSpacingsRule) Name() string { return "comment-spacings" } diff --git a/vendor/github.com/mgechev/revive/rule/confusing-naming.go b/vendor/github.com/mgechev/revive/rule/confusing-naming.go index 8b1c3eac4..febfd8824 100644 --- a/vendor/github.com/mgechev/revive/rule/confusing-naming.go +++ b/vendor/github.com/mgechev/revive/rule/confusing-naming.go @@ -71,7 +71,6 @@ 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 @@ -112,7 +111,7 @@ func checkMethodName(holder string, id *ast.Ident, w *lintConfusingNames) { pkgm.methods[holder] = make(map[string]*referenceMethod, 1) } - // update the black list + // update the block list if pkgm.methods[holder] == nil { println("no entry for '", holder, "'") } diff --git a/vendor/github.com/mgechev/revive/rule/constant-logical-expr.go b/vendor/github.com/mgechev/revive/rule/constant-logical-expr.go index 9abc95d67..36cd641f7 100644 --- a/vendor/github.com/mgechev/revive/rule/constant-logical-expr.go +++ b/vendor/github.com/mgechev/revive/rule/constant-logical-expr.go @@ -11,7 +11,7 @@ import ( type ConstantLogicalExprRule struct{} // Apply applies the rule to given file. -func (r *ConstantLogicalExprRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { +func (*ConstantLogicalExprRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { var failures []lint.Failure onFailure := func(failure lint.Failure) { @@ -63,7 +63,7 @@ func (w *lintConstantLogicalExpr) Visit(node ast.Node) ast.Visitor { return w } -func (w *lintConstantLogicalExpr) isOperatorWithLogicalResult(t token.Token) bool { +func (*lintConstantLogicalExpr) isOperatorWithLogicalResult(t token.Token) bool { switch t { case token.LAND, token.LOR, token.EQL, token.LSS, token.GTR, token.NEQ, token.LEQ, token.GEQ: return true @@ -72,7 +72,7 @@ func (w *lintConstantLogicalExpr) isOperatorWithLogicalResult(t token.Token) boo return false } -func (w *lintConstantLogicalExpr) isEqualityOperator(t token.Token) bool { +func (*lintConstantLogicalExpr) isEqualityOperator(t token.Token) bool { switch t { case token.EQL, token.LEQ, token.GEQ: return true @@ -81,7 +81,7 @@ func (w *lintConstantLogicalExpr) isEqualityOperator(t token.Token) bool { return false } -func (w *lintConstantLogicalExpr) isInequalityOperator(t token.Token) bool { +func (*lintConstantLogicalExpr) isInequalityOperator(t token.Token) bool { switch t { case token.LSS, token.GTR, token.NEQ: return true diff --git a/vendor/github.com/mgechev/revive/rule/context-as-argument.go b/vendor/github.com/mgechev/revive/rule/context-as-argument.go index 3c400065e..e0c8cfa5e 100644 --- a/vendor/github.com/mgechev/revive/rule/context-as-argument.go +++ b/vendor/github.com/mgechev/revive/rule/context-as-argument.go @@ -82,7 +82,7 @@ func (w lintContextArguments) Visit(n ast.Node) ast.Visitor { func getAllowTypesFromArguments(args lint.Arguments) map[string]struct{} { allowTypesBefore := []string{} if len(args) >= 1 { - argKV, ok := args[0].(map[string]interface{}) + argKV, ok := args[0].(map[string]any) if !ok { panic(fmt.Sprintf("Invalid argument to the context-as-argument rule. Expecting a k,v map, got %T", args[0])) } diff --git a/vendor/github.com/mgechev/revive/rule/datarace.go b/vendor/github.com/mgechev/revive/rule/datarace.go index 26fcadcdc..39e96696a 100644 --- a/vendor/github.com/mgechev/revive/rule/datarace.go +++ b/vendor/github.com/mgechev/revive/rule/datarace.go @@ -53,7 +53,7 @@ func (w lintDataRaces) Visit(n ast.Node) ast.Visitor { return nil } -func (w lintDataRaces) ExtractReturnIDs(fields []*ast.Field) map[*ast.Object]struct{} { +func (lintDataRaces) ExtractReturnIDs(fields []*ast.Field) map[*ast.Object]struct{} { r := map[*ast.Object]struct{}{} for _, f := range fields { for _, id := range f.Names { @@ -111,7 +111,7 @@ func (w lintFunctionForDataRaces) Visit(node ast.Node) ast.Visitor { return ok } - ids := pick(funcLit.Body, selectIDs, nil) + ids := pick(funcLit.Body, selectIDs) for _, id := range ids { id := id.(*ast.Ident) _, isRangeID := w.rangeIDs[id.Obj] diff --git a/vendor/github.com/mgechev/revive/rule/defer.go b/vendor/github.com/mgechev/revive/rule/defer.go index f3ea17920..adc6478ae 100644 --- a/vendor/github.com/mgechev/revive/rule/defer.go +++ b/vendor/github.com/mgechev/revive/rule/defer.go @@ -56,7 +56,7 @@ func (*DeferRule) allowFromArgs(args lint.Arguments) map[string]bool { return allow } - aa, ok := args[0].([]interface{}) + aa, ok := args[0].([]any) if !ok { panic(fmt.Sprintf("Invalid argument '%v' for 'defer' rule. Expecting []string, got %T", args[0], args[0])) } @@ -144,8 +144,8 @@ 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/vendor/github.com/mgechev/revive/rule/doc.go b/vendor/github.com/mgechev/revive/rule/doc.go new file mode 100644 index 000000000..55bf6caa6 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/doc.go @@ -0,0 +1,2 @@ +// Package rule implements revive's linting rules. +package rule diff --git a/vendor/github.com/mgechev/revive/rule/dot-imports.go b/vendor/github.com/mgechev/revive/rule/dot-imports.go index db78f1957..6b877677d 100644 --- a/vendor/github.com/mgechev/revive/rule/dot-imports.go +++ b/vendor/github.com/mgechev/revive/rule/dot-imports.go @@ -1,16 +1,23 @@ package rule import ( + "fmt" "go/ast" + "sync" "github.com/mgechev/revive/lint" ) // DotImportsRule lints given else constructs. -type DotImportsRule struct{} +type DotImportsRule struct { + sync.Mutex + allowedPackages allowPackages +} // Apply applies the rule to given file. -func (*DotImportsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { +func (r *DotImportsRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + r.configure(arguments) + var failures []lint.Failure fileAst := file.AST @@ -20,6 +27,7 @@ func (*DotImportsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { onFailure: func(failure lint.Failure) { failures = append(failures, failure) }, + allowPackages: r.allowedPackages, } ast.Walk(walker, fileAst) @@ -32,15 +40,49 @@ func (*DotImportsRule) Name() string { return "dot-imports" } +func (r *DotImportsRule) configure(arguments lint.Arguments) { + r.Lock() + defer r.Unlock() + + if r.allowedPackages != nil { + return + } + + r.allowedPackages = make(allowPackages) + if len(arguments) == 0 { + return + } + + args, ok := arguments[0].(map[string]any) + if !ok { + panic(fmt.Sprintf("Invalid argument to the dot-imports rule. Expecting a k,v map, got %T", arguments[0])) + } + + if allowedPkgArg, ok := args["allowedPackages"]; ok { + if pkgs, ok := allowedPkgArg.([]any); ok { + for _, p := range pkgs { + if pkg, ok := p.(string); ok { + r.allowedPackages.add(pkg) + } else { + panic(fmt.Sprintf("Invalid argument to the dot-imports rule, string expected. Got '%v' (%T)", p, p)) + } + } + } else { + panic(fmt.Sprintf("Invalid argument to the dot-imports rule, []string expected. Got '%v' (%T)", allowedPkgArg, allowedPkgArg)) + } + } +} + type lintImports struct { - file *lint.File - fileAst *ast.File - onFailure func(lint.Failure) + file *lint.File + fileAst *ast.File + onFailure func(lint.Failure) + allowPackages allowPackages } func (w lintImports) Visit(_ ast.Node) ast.Visitor { for _, is := range w.fileAst.Imports { - if is.Name != nil && is.Name.Name == "." { + if is.Name != nil && is.Name.Name == "." && !w.allowPackages.isAllowedPackage(is.Path.Value) { w.onFailure(lint.Failure{ Confidence: 1, Failure: "should not use dot imports", @@ -51,3 +93,14 @@ func (w lintImports) Visit(_ ast.Node) ast.Visitor { } return nil } + +type allowPackages map[string]struct{} + +func (ap allowPackages) add(pkg string) { + ap[fmt.Sprintf(`"%s"`, pkg)] = struct{}{} // import path strings are with double quotes +} + +func (ap allowPackages) isAllowedPackage(pkg string) bool { + _, allowed := ap[pkg] + return allowed +} diff --git a/vendor/github.com/mgechev/revive/rule/early-return.go b/vendor/github.com/mgechev/revive/rule/early-return.go index 90a0acc55..9c04a1dbe 100644 --- a/vendor/github.com/mgechev/revive/rule/early-return.go +++ b/vendor/github.com/mgechev/revive/rule/early-return.go @@ -22,7 +22,7 @@ func (*EarlyReturnRule) Name() string { } // CheckIfElse evaluates the rule against an ifelse.Chain. -func (e *EarlyReturnRule) CheckIfElse(chain ifelse.Chain, args ifelse.Args) (failMsg string) { +func (*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 diff --git a/vendor/github.com/mgechev/revive/rule/enforce-map-style.go b/vendor/github.com/mgechev/revive/rule/enforce-map-style.go index ae27b654f..36ac2374c 100644 --- a/vendor/github.com/mgechev/revive/rule/enforce-map-style.go +++ b/vendor/github.com/mgechev/revive/rule/enforce-map-style.go @@ -47,7 +47,7 @@ type EnforceMapStyleRule struct { func (r *EnforceMapStyleRule) configure(arguments lint.Arguments) { r.Lock() defer r.Unlock() - + if r.configured { return } @@ -58,7 +58,7 @@ func (r *EnforceMapStyleRule) configure(arguments lint.Arguments) { return } - enforceMapStyle, ok := arguments[0].(string) + 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])) } @@ -141,7 +141,7 @@ func (r *EnforceMapStyleRule) Apply(file *lint.File, arguments lint.Arguments) [ } // Name returns the rule name. -func (r *EnforceMapStyleRule) Name() string { +func (*EnforceMapStyleRule) Name() string { return "enforce-map-style" } diff --git a/vendor/github.com/mgechev/revive/rule/enforce-repeated-arg-type-style.go b/vendor/github.com/mgechev/revive/rule/enforce-repeated-arg-type-style.go new file mode 100644 index 000000000..067082b1b --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/enforce-repeated-arg-type-style.go @@ -0,0 +1,191 @@ +package rule + +import ( + "fmt" + "go/ast" + "go/types" + "sync" + + "github.com/mgechev/revive/lint" +) + +type enforceRepeatedArgTypeStyleType string + +const ( + enforceRepeatedArgTypeStyleTypeAny enforceRepeatedArgTypeStyleType = "any" + enforceRepeatedArgTypeStyleTypeShort enforceRepeatedArgTypeStyleType = "short" + enforceRepeatedArgTypeStyleTypeFull enforceRepeatedArgTypeStyleType = "full" +) + +func repeatedArgTypeStyleFromString(s string) enforceRepeatedArgTypeStyleType { + switch s { + case string(enforceRepeatedArgTypeStyleTypeAny), "": + return enforceRepeatedArgTypeStyleTypeAny + case string(enforceRepeatedArgTypeStyleTypeShort): + return enforceRepeatedArgTypeStyleTypeShort + case string(enforceRepeatedArgTypeStyleTypeFull): + return enforceRepeatedArgTypeStyleTypeFull + default: + err := fmt.Errorf( + "invalid repeated arg type style: %s (expecting one of %v)", + s, + []enforceRepeatedArgTypeStyleType{ + enforceRepeatedArgTypeStyleTypeAny, + enforceRepeatedArgTypeStyleTypeShort, + enforceRepeatedArgTypeStyleTypeFull, + }, + ) + + panic(fmt.Sprintf("Invalid argument to the enforce-repeated-arg-type-style rule: %v", err)) + } +} + +// EnforceRepeatedArgTypeStyleRule implements a rule to enforce repeated argument type style. +type EnforceRepeatedArgTypeStyleRule struct { + configured bool + funcArgStyle enforceRepeatedArgTypeStyleType + funcRetValStyle enforceRepeatedArgTypeStyleType + + sync.Mutex +} + +func (r *EnforceRepeatedArgTypeStyleRule) configure(arguments lint.Arguments) { + r.Lock() + defer r.Unlock() + + if r.configured { + return + } + r.configured = true + + r.funcArgStyle = enforceRepeatedArgTypeStyleTypeAny + r.funcRetValStyle = enforceRepeatedArgTypeStyleTypeAny + + if len(arguments) == 0 { + return + } + + switch funcArgStyle := arguments[0].(type) { + case string: + r.funcArgStyle = repeatedArgTypeStyleFromString(funcArgStyle) + r.funcRetValStyle = repeatedArgTypeStyleFromString(funcArgStyle) + case map[string]any: // expecting map[string]string + for k, v := range funcArgStyle { + switch k { + case "funcArgStyle": + val, ok := v.(string) + if !ok { + panic(fmt.Sprintf("Invalid map value type for 'enforce-repeated-arg-type-style' rule. Expecting string, got %T", v)) + } + r.funcArgStyle = repeatedArgTypeStyleFromString(val) + case "funcRetValStyle": + val, ok := v.(string) + if !ok { + panic(fmt.Sprintf("Invalid map value '%v' for 'enforce-repeated-arg-type-style' rule. Expecting string, got %T", v, v)) + } + r.funcRetValStyle = repeatedArgTypeStyleFromString(val) + default: + panic(fmt.Sprintf("Invalid map key for 'enforce-repeated-arg-type-style' rule. Expecting 'funcArgStyle' or 'funcRetValStyle', got %v", k)) + } + } + default: + panic(fmt.Sprintf("Invalid argument '%v' for 'import-alias-naming' rule. Expecting string or map[string]string, got %T", arguments[0], arguments[0])) + } +} + +// Apply applies the rule to a given file. +func (r *EnforceRepeatedArgTypeStyleRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + r.configure(arguments) + + if r.funcArgStyle == enforceRepeatedArgTypeStyleTypeAny && r.funcRetValStyle == enforceRepeatedArgTypeStyleTypeAny { + // This linter is not configured, return no failures. + return nil + } + + var failures []lint.Failure + + err := file.Pkg.TypeCheck() + if err != nil { + // the file has other issues + return nil + } + typesInfo := file.Pkg.TypesInfo() + + astFile := file.AST + ast.Inspect(astFile, func(n ast.Node) bool { + switch fn := n.(type) { + case *ast.FuncDecl: + if r.funcArgStyle == enforceRepeatedArgTypeStyleTypeFull { + if fn.Type.Params != nil { + for _, field := range fn.Type.Params.List { + if len(field.Names) > 1 { + failures = append(failures, lint.Failure{ + Confidence: 1, + Node: field, + Category: "style", + Failure: "argument types should not be omitted", + }) + } + } + } + } + + if r.funcArgStyle == enforceRepeatedArgTypeStyleTypeShort { + var prevType ast.Expr + if fn.Type.Params != nil { + for _, field := range fn.Type.Params.List { + if types.Identical(typesInfo.Types[field.Type].Type, typesInfo.Types[prevType].Type) { + failures = append(failures, lint.Failure{ + Confidence: 1, + Node: field, + Category: "style", + Failure: "repeated argument type can be omitted", + }) + } + prevType = field.Type + } + } + } + + if r.funcRetValStyle == enforceRepeatedArgTypeStyleTypeFull { + if fn.Type.Results != nil { + for _, field := range fn.Type.Results.List { + if len(field.Names) > 1 { + failures = append(failures, lint.Failure{ + Confidence: 1, + Node: field, + Category: "style", + Failure: "return types should not be omitted", + }) + } + } + } + } + + if r.funcRetValStyle == enforceRepeatedArgTypeStyleTypeShort { + var prevType ast.Expr + if fn.Type.Results != nil { + for _, field := range fn.Type.Results.List { + if field.Names != nil && types.Identical(typesInfo.Types[field.Type].Type, typesInfo.Types[prevType].Type) { + failures = append(failures, lint.Failure{ + Confidence: 1, + Node: field, + Category: "style", + Failure: "repeated return type can be omitted", + }) + } + prevType = field.Type + } + } + } + } + return true + }) + + return failures +} + +// Name returns the name of the linter rule. +func (*EnforceRepeatedArgTypeStyleRule) Name() string { + return "enforce-repeated-arg-type-style" +} diff --git a/vendor/github.com/mgechev/revive/rule/enforce-slice-style.go b/vendor/github.com/mgechev/revive/rule/enforce-slice-style.go new file mode 100644 index 000000000..abaf20be0 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/enforce-slice-style.go @@ -0,0 +1,193 @@ +package rule + +import ( + "fmt" + "go/ast" + "sync" + + "github.com/mgechev/revive/lint" +) + +type enforceSliceStyleType string + +const ( + enforceSliceStyleTypeAny enforceSliceStyleType = "any" + enforceSliceStyleTypeMake enforceSliceStyleType = "make" + enforceSliceStyleTypeLiteral enforceSliceStyleType = "literal" +) + +func sliceStyleFromString(s string) (enforceSliceStyleType, error) { + switch s { + case string(enforceSliceStyleTypeAny), "": + return enforceSliceStyleTypeAny, nil + case string(enforceSliceStyleTypeMake): + return enforceSliceStyleTypeMake, nil + case string(enforceSliceStyleTypeLiteral): + return enforceSliceStyleTypeLiteral, nil + default: + return enforceSliceStyleTypeAny, fmt.Errorf( + "invalid slice style: %s (expecting one of %v)", + s, + []enforceSliceStyleType{ + enforceSliceStyleTypeAny, + enforceSliceStyleTypeMake, + enforceSliceStyleTypeLiteral, + }, + ) + } +} + +// EnforceSliceStyleRule implements a rule to enforce `make([]type)` over `[]type{}`. +type EnforceSliceStyleRule struct { + configured bool + enforceSliceStyle enforceSliceStyleType + sync.Mutex +} + +func (r *EnforceSliceStyleRule) configure(arguments lint.Arguments) { + r.Lock() + defer r.Unlock() + + if r.configured { + return + } + r.configured = true + + if len(arguments) < 1 { + r.enforceSliceStyle = enforceSliceStyleTypeAny + return + } + + enforceSliceStyle, ok := arguments[0].(string) + if !ok { + panic(fmt.Sprintf("Invalid argument '%v' for 'enforce-slice-style' rule. Expecting string, got %T", arguments[0], arguments[0])) + } + + var err error + r.enforceSliceStyle, err = sliceStyleFromString(enforceSliceStyle) + + if err != nil { + panic(fmt.Sprintf("Invalid argument to the enforce-slice-style rule: %v", err)) + } +} + +// Apply applies the rule to given file. +func (r *EnforceSliceStyleRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + r.configure(arguments) + + if r.enforceSliceStyle == enforceSliceStyleTypeAny { + // 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.enforceSliceStyle != enforceSliceStyleTypeMake { + return true + } + + if !r.isSliceType(v.Type) { + return true + } + + if len(v.Elts) > 0 { + // not an empty slice + return true + } + + failures = append(failures, lint.Failure{ + Confidence: 1, + Node: v, + Category: "style", + Failure: "use make([]type) instead of []type{} (or declare nil slice)", + }) + case *ast.CallExpr: + if r.enforceSliceStyle != enforceSliceStyleTypeLiteral { + // skip any function calls, even if it's make([]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) < 2 { + // skip invalid make declarations + return true + } + + if !r.isSliceType(v.Args[0]) { + // not a slice type + return true + } + + arg, ok := v.Args[1].(*ast.BasicLit) + if !ok { + // skip invalid make declarations + return true + } + + if arg.Value != "0" { + // skip slice with non-zero size + return true + } + + if len(v.Args) > 2 { + arg, ok := v.Args[2].(*ast.BasicLit) + if !ok { + // skip invalid make declarations + return true + } + + if arg.Value != "0" { + // skip non-zero capacity slice + return true + } + } + + failures = append(failures, lint.Failure{ + Confidence: 1, + Node: v.Args[0], + Category: "style", + Failure: "use []type{} instead of make([]type, 0) (or declare nil slice)", + }) + } + return true + }) + + return failures +} + +// Name returns the rule name. +func (*EnforceSliceStyleRule) Name() string { + return "enforce-slice-style" +} + +func (r *EnforceSliceStyleRule) isSliceType(v ast.Expr) bool { + switch t := v.(type) { + case *ast.ArrayType: + if t.Len != nil { + // array + return false + } + // slice + return true + case *ast.Ident: + if t.Obj == nil { + return false + } + typeSpec, ok := t.Obj.Decl.(*ast.TypeSpec) + if !ok { + return false + } + return r.isSliceType(typeSpec.Type) + default: + return false + } +} diff --git a/vendor/github.com/mgechev/revive/rule/flag-param.go b/vendor/github.com/mgechev/revive/rule/flag-param.go index 19a05f9fe..f9bfb712c 100644 --- a/vendor/github.com/mgechev/revive/rule/flag-param.go +++ b/vendor/github.com/mgechev/revive/rule/flag-param.go @@ -88,7 +88,7 @@ func (w conditionVisitor) Visit(node ast.Node) ast.Visitor { return false } - uses := pick(ifStmt.Cond, fselect, nil) + uses := pick(ifStmt.Cond, fselect) if len(uses) < 1 { return w diff --git a/vendor/github.com/mgechev/revive/rule/function-length.go b/vendor/github.com/mgechev/revive/rule/function-length.go index f7ab8b98e..fd65884e9 100644 --- a/vendor/github.com/mgechev/revive/rule/function-length.go +++ b/vendor/github.com/mgechev/revive/rule/function-length.go @@ -171,7 +171,7 @@ func (w lintFuncLength) countFuncLitStmts(stmt ast.Expr) int { return 0 } -func (w lintFuncLength) countBodyListStmts(t interface{}) int { +func (w lintFuncLength) countBodyListStmts(t any) int { i := reflect.ValueOf(t).Elem().FieldByName(`Body`).Elem().FieldByName(`List`).Interface() return w.countStmts(i.([]ast.Stmt)) } diff --git a/vendor/github.com/mgechev/revive/rule/import-alias-naming.go b/vendor/github.com/mgechev/revive/rule/import-alias-naming.go index 292392b5d..a6d096c8b 100644 --- a/vendor/github.com/mgechev/revive/rule/import-alias-naming.go +++ b/vendor/github.com/mgechev/revive/rule/import-alias-naming.go @@ -10,14 +10,15 @@ import ( // ImportAliasNamingRule lints import alias naming. type ImportAliasNamingRule struct { - configured bool - namingRuleRegexp *regexp.Regexp + configured bool + allowRegexp *regexp.Regexp + denyRegexp *regexp.Regexp sync.Mutex } -const defaultNamingRule = "^[a-z][a-z0-9]{0,}$" +const defaultImportAliasNamingAllowRule = "^[a-z][a-z0-9]{0,}$" -var defaultNamingRuleRegexp = regexp.MustCompile(defaultNamingRule) +var defaultImportAliasNamingAllowRegexp = regexp.MustCompile(defaultImportAliasNamingAllowRule) func (r *ImportAliasNamingRule) configure(arguments lint.Arguments) { r.Lock() @@ -26,20 +27,31 @@ func (r *ImportAliasNamingRule) configure(arguments lint.Arguments) { return } - if len(arguments) < 1 { - r.namingRuleRegexp = defaultNamingRuleRegexp + if len(arguments) == 0 { + r.allowRegexp = defaultImportAliasNamingAllowRegexp 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])) + switch namingRule := arguments[0].(type) { + case string: + r.setAllowRule(namingRule) + case map[string]any: // expecting map[string]string + for k, v := range namingRule { + switch k { + case "allowRegex": + r.setAllowRule(v) + case "denyRegex": + r.setDenyRule(v) + default: + panic(fmt.Sprintf("Invalid map key for 'import-alias-naming' rule. Expecting 'allowRegex' or 'denyRegex', got %v", k)) + } + } + default: + panic(fmt.Sprintf("Invalid argument '%v' for 'import-alias-naming' rule. Expecting string or map[string]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)) + if r.allowRegexp == nil && r.denyRegexp == nil { + r.allowRegexp = defaultImportAliasNamingAllowRegexp } } @@ -56,14 +68,23 @@ func (r *ImportAliasNamingRule) Apply(file *lint.File, arguments lint.Arguments) } alias := is.Name - if alias == nil || alias.Name == "_" { + if alias == nil || alias.Name == "_" || alias.Name == "." { // "_" and "." are special types of import aiases and should be processed by another linter rule continue } - if !r.namingRuleRegexp.MatchString(alias.Name) { + if r.allowRegexp != nil && !r.allowRegexp.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.allowRegexp.String()), + Node: alias, + Category: "imports", + }) + } + + if r.denyRegexp != nil && r.denyRegexp.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()), + Failure: fmt.Sprintf("import name (%s) must NOT match the regular expression: %s", alias.Name, r.denyRegexp.String()), Node: alias, Category: "imports", }) @@ -77,3 +98,29 @@ func (r *ImportAliasNamingRule) Apply(file *lint.File, arguments lint.Arguments) func (*ImportAliasNamingRule) Name() string { return "import-alias-naming" } + +func (r *ImportAliasNamingRule) setAllowRule(value any) { + namingRule, ok := value.(string) + if !ok { + panic(fmt.Sprintf("Invalid argument '%v' for import-alias-naming allowRegexp rule. Expecting string, got %T", value, value)) + } + + namingRuleRegexp, err := regexp.Compile(namingRule) + if err != nil { + panic(fmt.Sprintf("Invalid argument to the import-alias-naming allowRegexp rule. Expecting %q to be a valid regular expression, got: %v", namingRule, err)) + } + r.allowRegexp = namingRuleRegexp +} + +func (r *ImportAliasNamingRule) setDenyRule(value any) { + namingRule, ok := value.(string) + if !ok { + panic(fmt.Sprintf("Invalid argument '%v' for import-alias-naming denyRegexp rule. Expecting string, got %T", value, value)) + } + + namingRuleRegexp, err := regexp.Compile(namingRule) + if err != nil { + panic(fmt.Sprintf("Invalid argument to the import-alias-naming denyRegexp rule. Expecting %q to be a valid regular expression, got: %v", namingRule, err)) + } + r.denyRegexp = namingRuleRegexp +} diff --git a/vendor/github.com/mgechev/revive/rule/imports-blacklist.go b/vendor/github.com/mgechev/revive/rule/imports-blocklist.go index bb8262cae..431066403 100644 --- a/vendor/github.com/mgechev/revive/rule/imports-blacklist.go +++ b/vendor/github.com/mgechev/revive/rule/imports-blocklist.go @@ -8,37 +8,37 @@ import ( "github.com/mgechev/revive/lint" ) -// ImportsBlacklistRule lints given else constructs. -type ImportsBlacklistRule struct { - blacklist []*regexp.Regexp +// ImportsBlocklistRule lints given else constructs. +type ImportsBlocklistRule struct { + blocklist []*regexp.Regexp sync.Mutex } -var replaceRegexp = regexp.MustCompile(`/?\*\*/?`) +var replaceImportRegexp = regexp.MustCompile(`/?\*\*/?`) -func (r *ImportsBlacklistRule) configure(arguments lint.Arguments) { +func (r *ImportsBlocklistRule) configure(arguments lint.Arguments) { r.Lock() defer r.Unlock() - if r.blacklist == nil { - r.blacklist = make([]*regexp.Regexp, 0) + if r.blocklist == nil { + r.blocklist = make([]*regexp.Regexp, 0) for _, arg := range arguments { argStr, ok := arg.(string) if !ok { - panic(fmt.Sprintf("Invalid argument to the imports-blacklist rule. Expecting a string, got %T", arg)) + panic(fmt.Sprintf("Invalid argument to the imports-blocklist rule. Expecting a string, got %T", arg)) } - regStr, err := regexp.Compile(fmt.Sprintf(`(?m)"%s"$`, replaceRegexp.ReplaceAllString(argStr, `(\W|\w)*`))) + regStr, err := regexp.Compile(fmt.Sprintf(`(?m)"%s"$`, replaceImportRegexp.ReplaceAllString(argStr, `(\W|\w)*`))) if err != nil { - panic(fmt.Sprintf("Invalid argument to the imports-blacklist rule. Expecting %q to be a valid regular expression, got: %v", argStr, err)) + panic(fmt.Sprintf("Invalid argument to the imports-blocklist rule. Expecting %q to be a valid regular expression, got: %v", argStr, err)) } - r.blacklist = append(r.blacklist, regStr) + r.blocklist = append(r.blocklist, regStr) } } } -func (r *ImportsBlacklistRule) isBlacklisted(path string) bool { - for _, regex := range r.blacklist { +func (r *ImportsBlocklistRule) isBlocklisted(path string) bool { + for _, regex := range r.blocklist { if regex.MatchString(path) { return true } @@ -47,17 +47,17 @@ func (r *ImportsBlacklistRule) isBlacklisted(path string) bool { } // Apply applies the rule to given file. -func (r *ImportsBlacklistRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { +func (r *ImportsBlocklistRule) 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 && r.isBlacklisted(path.Value) { + if path != nil && r.isBlocklisted(path.Value) { failures = append(failures, lint.Failure{ Confidence: 1, - Failure: "should not use the following blacklisted import: " + path.Value, + Failure: "should not use the following blocklisted import: " + path.Value, Node: is, Category: "imports", }) @@ -68,6 +68,6 @@ func (r *ImportsBlacklistRule) Apply(file *lint.File, arguments lint.Arguments) } // Name returns the rule name. -func (*ImportsBlacklistRule) Name() string { - return "imports-blacklist" +func (*ImportsBlocklistRule) Name() string { + return "imports-blocklist" } diff --git a/vendor/github.com/mgechev/revive/rule/indent-error-flow.go b/vendor/github.com/mgechev/revive/rule/indent-error-flow.go index b80e6486c..294ceef84 100644 --- a/vendor/github.com/mgechev/revive/rule/indent-error-flow.go +++ b/vendor/github.com/mgechev/revive/rule/indent-error-flow.go @@ -19,7 +19,7 @@ func (*IndentErrorFlowRule) Name() string { } // CheckIfElse evaluates the rule against an ifelse.Chain. -func (e *IndentErrorFlowRule) CheckIfElse(chain ifelse.Chain, args ifelse.Args) (failMsg string) { +func (*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 diff --git a/vendor/github.com/mgechev/revive/rule/max-control-nesting.go b/vendor/github.com/mgechev/revive/rule/max-control-nesting.go new file mode 100644 index 000000000..c4eb36193 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/max-control-nesting.go @@ -0,0 +1,128 @@ +package rule + +import ( + "fmt" + "go/ast" + "sync" + + "github.com/mgechev/revive/lint" +) + +// MaxControlNestingRule lints given else constructs. +type MaxControlNestingRule struct { + max int64 + sync.Mutex +} + +const defaultMaxControlNesting = 5 + +// Apply applies the rule to given file. +func (r *MaxControlNestingRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + r.configure(arguments) + + var failures []lint.Failure + + fileAst := file.AST + + walker := &lintMaxControlNesting{ + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + max: int(r.max), + } + + ast.Walk(walker, fileAst) + + return failures +} + +// Name returns the rule name. +func (*MaxControlNestingRule) Name() string { + return "max-control-nesting" +} + +type lintMaxControlNesting struct { + max int + onFailure func(lint.Failure) + nestingLevelAcc int + lastCtrlStmt ast.Node +} + +func (w *lintMaxControlNesting) Visit(n ast.Node) ast.Visitor { + if w.nestingLevelAcc > w.max { // we are visiting a node beyond the max nesting level + w.onFailure(lint.Failure{ + Failure: fmt.Sprintf("control flow nesting exceeds %d", w.max), + Confidence: 1, + Node: w.lastCtrlStmt, + Category: "complexity", + }) + return nil // stop visiting deeper + } + + switch v := n.(type) { + case *ast.IfStmt: + w.lastCtrlStmt = v + w.walkControlledBlock(v.Body) // "then" branch block + if v.Else != nil { + w.walkControlledBlock(v.Else) // "else" branch block + } + return nil // stop re-visiting nesting blocks (already visited by w.walkControlledBlock) + + case *ast.ForStmt: + w.lastCtrlStmt = v + w.walkControlledBlock(v.Body) + return nil // stop re-visiting nesting blocks (already visited by w.walkControlledBlock) + + case *ast.CaseClause: // switch case + w.lastCtrlStmt = v + for _, s := range v.Body { // visit each statement in the case clause + w.walkControlledBlock(s) + } + return nil // stop re-visiting nesting blocks (already visited by w.walkControlledBlock) + + case *ast.CommClause: // select case + w.lastCtrlStmt = v + for _, s := range v.Body { // visit each statement in the select case clause + w.walkControlledBlock(s) + } + return nil // stop re-visiting nesting blocks (already visited by w.walkControlledBlock) + + case *ast.FuncLit: + walker := &lintMaxControlNesting{ + onFailure: w.onFailure, + max: w.max, + } + ast.Walk(walker, v.Body) + return nil + } + + return w +} + +func (w *lintMaxControlNesting) walkControlledBlock(b ast.Node) { + oldNestingLevel := w.nestingLevelAcc + w.nestingLevelAcc++ + ast.Walk(w, b) + w.nestingLevelAcc = oldNestingLevel +} + +func (r *MaxControlNestingRule) configure(arguments lint.Arguments) { + r.Lock() + defer r.Unlock() + if !(r.max < 1) { + return // max already set + } + + if len(arguments) < 1 { + r.max = defaultMaxControlNesting + return + } + + checkNumberOfArguments(1, arguments, r.Name()) + + max, ok := arguments[0].(int64) // Alt. non panicking version + if !ok { + panic(`invalid value passed as argument number to the "max-control-nesting" rule`) + } + r.max = max +} diff --git a/vendor/github.com/mgechev/revive/rule/modifies-value-receiver.go b/vendor/github.com/mgechev/revive/rule/modifies-value-receiver.go index 34e651557..e9e64b9a6 100644 --- a/vendor/github.com/mgechev/revive/rule/modifies-value-receiver.go +++ b/vendor/github.com/mgechev/revive/rule/modifies-value-receiver.go @@ -78,11 +78,6 @@ func (w lintModifiesValRecRule) Visit(node ast.Node) ast.Visitor { if name == "" || name != receiverName { continue } - - if w.skipType(ast.Expr(e.Sel)) { - continue - } - case *ast.Ident: // receiver := ... if e.Name != receiverName { continue @@ -97,7 +92,7 @@ func (w lintModifiesValRecRule) Visit(node ast.Node) ast.Visitor { return false } - assignmentsToReceiver := pick(n.Body, fselect, nil) + assignmentsToReceiver := pick(n.Body, fselect) for _, assignment := range assignmentsToReceiver { w.onFailure(lint.Failure{ diff --git a/vendor/github.com/mgechev/revive/rule/optimize-operands-order.go b/vendor/github.com/mgechev/revive/rule/optimize-operands-order.go index 88928bb98..841bde56c 100644 --- a/vendor/github.com/mgechev/revive/rule/optimize-operands-order.go +++ b/vendor/github.com/mgechev/revive/rule/optimize-operands-order.go @@ -54,13 +54,13 @@ func (w lintOptimizeOperandsOrderlExpr) Visit(node ast.Node) ast.Visitor { } // check if the left sub-expression contains a function call - nodes := pick(binExpr.X, isCaller, nil) + nodes := pick(binExpr.X, isCaller) if len(nodes) < 1 { return w } // check if the right sub-expression does not contain a function call - nodes = pick(binExpr.Y, isCaller, nil) + nodes = pick(binExpr.Y, isCaller) if len(nodes) > 0 { return w } diff --git a/vendor/github.com/mgechev/revive/rule/string-format.go b/vendor/github.com/mgechev/revive/rule/string-format.go index 3edd62477..70edf7387 100644 --- a/vendor/github.com/mgechev/revive/rule/string-format.go +++ b/vendor/github.com/mgechev/revive/rule/string-format.go @@ -38,7 +38,7 @@ func (*StringFormatRule) Name() string { // ParseArgumentsTest is a public wrapper around w.parseArguments used for testing. Returns the error message provided to panic, or nil if no error was encountered func (StringFormatRule) ParseArgumentsTest(arguments lint.Arguments) *string { w := lintStringFormatRule{} - c := make(chan interface{}) + c := make(chan any) // Parse the arguments in a goroutine, defer a recover() call, return the error encountered (or nil if there was no error) go func() { defer func() { @@ -101,8 +101,8 @@ func (w *lintStringFormatRule) parseArguments(arguments lint.Arguments) { } } -func (w lintStringFormatRule) parseArgument(argument interface{}, ruleNum int) (scope stringFormatSubruleScope, regex *regexp.Regexp, negated bool, errorMessage string) { - g, ok := argument.([]interface{}) // Cast to generic slice first +func (w lintStringFormatRule) parseArgument(argument any, ruleNum int) (scope stringFormatSubruleScope, regex *regexp.Regexp, negated bool, errorMessage string) { + g, ok := argument.([]any) // Cast to generic slice first if !ok { w.configError("argument is not a slice", ruleNum, 0) } diff --git a/vendor/github.com/mgechev/revive/rule/struct-tag.go b/vendor/github.com/mgechev/revive/rule/struct-tag.go index d1c8056aa..f6ee47a73 100644 --- a/vendor/github.com/mgechev/revive/rule/struct-tag.go +++ b/vendor/github.com/mgechev/revive/rule/struct-tag.go @@ -140,7 +140,7 @@ func (lintStructTagRule) getTagName(tag *structtag.Tag) string { return strings.TrimPrefix(option, "name=") } } - return "" //protobuf tag lacks 'name' option + return "" // protobuf tag lacks 'name' option default: return tag.Name } diff --git a/vendor/github.com/mgechev/revive/rule/superfluous-else.go b/vendor/github.com/mgechev/revive/rule/superfluous-else.go index 1ef67bf1a..2aa1b6b2c 100644 --- a/vendor/github.com/mgechev/revive/rule/superfluous-else.go +++ b/vendor/github.com/mgechev/revive/rule/superfluous-else.go @@ -20,7 +20,7 @@ func (*SuperfluousElseRule) Name() string { } // CheckIfElse evaluates the rule against an ifelse.Chain. -func (e *SuperfluousElseRule) CheckIfElse(chain ifelse.Chain, args ifelse.Args) (failMsg string) { +func (*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 diff --git a/vendor/github.com/mgechev/revive/rule/unconditional-recursion.go b/vendor/github.com/mgechev/revive/rule/unconditional-recursion.go index bad907533..9ac2648cd 100644 --- a/vendor/github.com/mgechev/revive/rule/unconditional-recursion.go +++ b/vendor/github.com/mgechev/revive/rule/unconditional-recursion.go @@ -45,8 +45,9 @@ type funcStatus struct { } type lintUnconditionalRecursionRule struct { - onFailure func(lint.Failure) - currentFunc *funcStatus + onFailure func(lint.Failure) + currentFunc *funcStatus + inGoStatement bool } // Visit will traverse the file AST. @@ -68,9 +69,13 @@ func (w lintUnconditionalRecursionRule) Visit(node ast.Node) ast.Visitor { default: rec = n.Recv.List[0].Names[0] } - w.currentFunc = &funcStatus{&funcDesc{rec, n.Name}, false} case *ast.CallExpr: + // check if call arguments has a recursive call + for _, arg := range n.Args { + ast.Walk(w, arg) + } + var funcID *ast.Ident var selector *ast.Ident switch c := n.Fun.(type) { @@ -84,6 +89,9 @@ func (w lintUnconditionalRecursionRule) Visit(node ast.Node) ast.Visitor { return nil } funcID = c.Sel + case *ast.FuncLit: + ast.Walk(w, c.Body) // analyze the body of the function literal + return nil default: return w } @@ -93,11 +101,12 @@ func (w lintUnconditionalRecursionRule) Visit(node ast.Node) ast.Visitor { w.currentFunc.funcDesc.equal(&funcDesc{selector, funcID}) { w.onFailure(lint.Failure{ Category: "logic", - Confidence: 1, + Confidence: 0.8, Node: n, Failure: "unconditional recursive call", }) } + return nil case *ast.IfStmt: w.updateFuncStatus(n.Body) w.updateFuncStatus(n.Else) @@ -115,16 +124,21 @@ func (w lintUnconditionalRecursionRule) Visit(node ast.Node) ast.Visitor { w.updateFuncStatus(n.Body) return nil case *ast.GoStmt: - for _, a := range n.Call.Args { - ast.Walk(w, a) // check if arguments have a recursive call - } - return nil // recursive async call is not an issue + w.inGoStatement = true + ast.Walk(w, n.Call) + w.inGoStatement = false + return nil case *ast.ForStmt: if n.Cond != nil { return nil } // unconditional loop return w + case *ast.FuncLit: + if w.inGoStatement { + return w + } + return nil // literal call (closure) is not necessarily an issue } return w @@ -181,5 +195,5 @@ func (lintUnconditionalRecursionRule) hasControlExit(node ast.Node) bool { return false } - return len(pick(node, isExit, nil)) != 0 + return len(pick(node, isExit)) != 0 } diff --git a/vendor/github.com/mgechev/revive/rule/unhandled-error.go b/vendor/github.com/mgechev/revive/rule/unhandled-error.go index 32a5fe48b..ce6fa3864 100644 --- a/vendor/github.com/mgechev/revive/rule/unhandled-error.go +++ b/vendor/github.com/mgechev/revive/rule/unhandled-error.go @@ -119,7 +119,7 @@ func (w *lintUnhandledErrors) addFailure(n *ast.CallExpr) { Category: "bad practice", Confidence: 1, Node: n, - Failure: fmt.Sprintf("Unhandled error in call to function %v", gofmt(n.Fun)), + Failure: fmt.Sprintf("Unhandled error in call to function %v", name), }) } diff --git a/vendor/github.com/mgechev/revive/rule/unused-param.go b/vendor/github.com/mgechev/revive/rule/unused-param.go index df6cd9af0..4b04ee916 100644 --- a/vendor/github.com/mgechev/revive/rule/unused-param.go +++ b/vendor/github.com/mgechev/revive/rule/unused-param.go @@ -35,7 +35,7 @@ func (r *UnusedParamRule) configure(args lint.Arguments) { r.failureMsg = "parameter '%s' seems to be unused, consider removing or renaming it as _" } else { // Arguments = [{}] - options := args[0].(map[string]interface{}) + options := args[0].(map[string]any) // Arguments = [{allowedRegex="^_"}] if allowedRegexParam, ok := options["allowRegex"]; ok { @@ -87,54 +87,64 @@ type lintUnusedParamRule struct { } func (w lintUnusedParamRule) Visit(node ast.Node) ast.Visitor { + var ( + funcType *ast.FuncType + funcBody *ast.BlockStmt + ) switch n := node.(type) { + case *ast.FuncLit: + funcType = n.Type + funcBody = n.Body case *ast.FuncDecl: - params := retrieveNamedParams(n.Type.Params) - if len(params) < 1 { - return nil // skip, func without parameters - } - if n.Body == nil { return nil // skip, is a function prototype } - // inspect the func body looking for references to parameters - fselect := func(n ast.Node) bool { - ident, isAnID := n.(*ast.Ident) + funcType = n.Type + funcBody = n.Body + default: + return w // skip, not a function + } - if !isAnID { - return false - } + params := retrieveNamedParams(funcType.Params) + if len(params) < 1 { + return w // skip, func without parameters + } - _, isAParam := params[ident.Obj] - if isAParam { - params[ident.Obj] = false // mark as used - } + // inspect the func body looking for references to parameters + fselect := func(n ast.Node) bool { + ident, isAnID := n.(*ast.Ident) + if !isAnID { return false } - _ = pick(n.Body, fselect, nil) - - 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(w.failureMsg, n.Name), - }) - } - } + + _, isAParam := params[ident.Obj] + if isAParam { + params[ident.Obj] = false // mark as used } - return nil // full method body already inspected + return false + } + _ = pick(funcBody, fselect) + + for _, p := range funcType.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(w.failureMsg, n.Name), + }) + } + } } - return w + return w // full method body was inspected } func retrieveNamedParams(params *ast.FieldList) map[*ast.Object]bool { diff --git a/vendor/github.com/mgechev/revive/rule/unused-receiver.go b/vendor/github.com/mgechev/revive/rule/unused-receiver.go index 488572b7b..715dba338 100644 --- a/vendor/github.com/mgechev/revive/rule/unused-receiver.go +++ b/vendor/github.com/mgechev/revive/rule/unused-receiver.go @@ -35,7 +35,7 @@ func (r *UnusedReceiverRule) configure(args lint.Arguments) { 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{}) + options := args[0].(map[string]any) // Arguments = [{allowedRegex="^_"}] if allowedRegexParam, ok := options["allowRegex"]; ok { @@ -113,7 +113,7 @@ func (w lintUnusedReceiverRule) Visit(node ast.Node) ast.Visitor { return isAnID && ident.Obj == recID.Obj } - refs2recID := pick(n.Body, fselect, nil) + refs2recID := pick(n.Body, fselect) if len(refs2recID) > 0 { return nil // the receiver is referenced in the func body diff --git a/vendor/github.com/mgechev/revive/rule/utils.go b/vendor/github.com/mgechev/revive/rule/utils.go index dca1674ca..5778e7696 100644 --- a/vendor/github.com/mgechev/revive/rule/utils.go +++ b/vendor/github.com/mgechev/revive/rule/utils.go @@ -93,21 +93,15 @@ func srcLine(src []byte, p token.Position) string { // pick yields a list of nodes by picking them from a sub-ast with root node n. // Nodes are selected by applying the fselect function -// f function is applied to each selected node before inserting it in the final result. -// If f==nil then it defaults to the identity function (ie it returns the node itself) -func pick(n ast.Node, fselect func(n ast.Node) bool, f func(n ast.Node) []ast.Node) []ast.Node { +func pick(n ast.Node, fselect func(n ast.Node) bool) []ast.Node { var result []ast.Node if n == nil { return result } - if f == nil { - f = func(n ast.Node) []ast.Node { return []ast.Node{n} } - } - onSelect := func(n ast.Node) { - result = append(result, f(n)...) + result = append(result, n) } p := picker{fselect: fselect, onSelect: onSelect} ast.Walk(p, n) @@ -158,7 +152,7 @@ func isExprABooleanLit(n ast.Node) (lexeme string, ok bool) { } // gofmt returns a string representation of an AST subtree. -func gofmt(x interface{}) string { +func gofmt(x any) string { buf := bytes.Buffer{} fs := token.NewFileSet() printer.Fprint(&buf, fs, x) diff --git a/vendor/github.com/mgechev/revive/rule/var-naming.go b/vendor/github.com/mgechev/revive/rule/var-naming.go index 286ff9d75..e91c22dc2 100644 --- a/vendor/github.com/mgechev/revive/rule/var-naming.go +++ b/vendor/github.com/mgechev/revive/rule/var-naming.go @@ -18,10 +18,11 @@ 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 - upperCaseConst bool // if true - allows to use UPPER_SOME_NAMES for constants + configured bool + allowlist []string + blocklist []string + upperCaseConst bool // if true - allows to use UPPER_SOME_NAMES for constants + skipPackageNameChecks bool sync.Mutex } @@ -34,31 +35,53 @@ func (r *VarNamingRule) configure(arguments lint.Arguments) { r.configured = true if len(arguments) >= 1 { - r.whitelist = getList(arguments[0], "whitelist") + r.allowlist = getList(arguments[0], "allowlist") } if len(arguments) >= 2 { - r.blacklist = getList(arguments[1], "blacklist") + r.blocklist = getList(arguments[1], "blocklist") } 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{}) + asSlice, ok := thirdArgument.([]any) 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))) } - args, ok := asSlice[0].(map[string]interface{}) + args, ok := asSlice[0].(map[string]any) 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.skipPackageNameChecks = fmt.Sprint(args["skipPackageNameChecks"]) == "true" } } +func (r *VarNamingRule) applyPackageCheckRules(walker *lintNames) { + // Package names need slightly different handling than other names. + if strings.Contains(walker.fileAst.Name.Name, "_") && !strings.HasSuffix(walker.fileAst.Name.Name, "_test") { + walker.onFailure(lint.Failure{ + Failure: "don't use an underscore in package name", + Confidence: 1, + Node: walker.fileAst.Name, + Category: "naming", + }) + } + if anyCapsRE.MatchString(walker.fileAst.Name.Name) { + walker.onFailure(lint.Failure{ + Failure: fmt.Sprintf("don't use MixedCaps in package name; %s should be %s", walker.fileAst.Name.Name, strings.ToLower(walker.fileAst.Name.Name)), + Confidence: 1, + Node: walker.fileAst.Name, + Category: "naming", + }) + } + +} + // Apply applies the rule to given file. func (r *VarNamingRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { r.configure(arguments) @@ -70,30 +93,16 @@ func (r *VarNamingRule) Apply(file *lint.File, arguments lint.Arguments) []lint. walker := lintNames{ file: file, fileAst: fileAst, - whitelist: r.whitelist, - blacklist: r.blacklist, + allowlist: r.allowlist, + blocklist: r.blocklist, onFailure: func(failure lint.Failure) { failures = append(failures, failure) }, upperCaseConst: r.upperCaseConst, } - // Package names need slightly different handling than other names. - if strings.Contains(walker.fileAst.Name.Name, "_") && !strings.HasSuffix(walker.fileAst.Name.Name, "_test") { - walker.onFailure(lint.Failure{ - Failure: "don't use an underscore in package name", - Confidence: 1, - Node: walker.fileAst.Name, - Category: "naming", - }) - } - if anyCapsRE.MatchString(walker.fileAst.Name.Name) { - walker.onFailure(lint.Failure{ - Failure: fmt.Sprintf("don't use MixedCaps in package name; %s should be %s", walker.fileAst.Name.Name, strings.ToLower(walker.fileAst.Name.Name)), - Confidence: 1, - Node: walker.fileAst.Name, - Category: "naming", - }) + if !r.skipPackageNameChecks { + r.applyPackageCheckRules(&walker) } ast.Walk(&walker, fileAst) @@ -127,7 +136,7 @@ func (w *lintNames) check(id *ast.Ident, thing string) { // #851 upperCaseConst support // if it's const - if thing == token.CONST.String() && w.upperCaseConst && upperCaseConstRE.Match([]byte(id.Name)) { + if thing == token.CONST.String() && w.upperCaseConst && upperCaseConstRE.MatchString(id.Name) { return } @@ -142,7 +151,7 @@ func (w *lintNames) check(id *ast.Ident, thing string) { return } - should := lint.Name(id.Name, w.whitelist, w.blacklist) + should := lint.Name(id.Name, w.allowlist, w.blocklist) if id.Name == should { return } @@ -168,8 +177,8 @@ type lintNames struct { file *lint.File fileAst *ast.File onFailure func(lint.Failure) - whitelist []string - blacklist []string + allowlist []string + blocklist []string upperCaseConst bool } @@ -255,8 +264,8 @@ func (w *lintNames) Visit(n ast.Node) ast.Visitor { return w } -func getList(arg interface{}, argName string) []string { - temp, ok := arg.([]interface{}) +func getList(arg any, argName string) []string { + temp, ok := arg.([]any) if !ok { panic(fmt.Sprintf("Invalid argument to the var-naming rule. Expecting a %s of type slice with initialisms, got %T", argName, arg)) } diff --git a/vendor/github.com/nishanths/exhaustive/comment.go b/vendor/github.com/nishanths/exhaustive/comment.go index cc84beaf7..123e0181b 100644 --- a/vendor/github.com/nishanths/exhaustive/comment.go +++ b/vendor/github.com/nishanths/exhaustive/comment.go @@ -3,45 +3,14 @@ package exhaustive import ( "go/ast" "go/token" - "regexp" "strings" ) -// For definition of generated file see: -// http://golang.org/s/generatedcode - -var generatedCodeRe = regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`) - -func isGeneratedFile(file *ast.File) bool { - // NOTE: file.Comments includes file.Doc as well, so no need - // to separately check file.Doc. - for _, c := range file.Comments { - for _, cc := range c.List { - // This check handles the "must appear before the first - // non-comment, non-blank text in the file" requirement. - // - // According to https://golang.org/ref/spec#Source_file_organization - // the package clause is the first element in a file, which - // should make it the first non-comment, non-blank text. - if c.Pos() >= file.Package { - return false - } - // According to the docs: - // '\r' has been removed. - // '\n' has been removed for //-style comments - // This has also been manually verified. - if generatedCodeRe.MatchString(cc.Text) { - return true - } - } - } - - return false -} - const ( - ignoreComment = "//exhaustive:ignore" - enforceComment = "//exhaustive:enforce" + ignoreComment = "//exhaustive:ignore" + enforceComment = "//exhaustive:enforce" + ignoreDefaultCaseRequiredComment = "//exhaustive:ignore-default-case-required" + enforceDefaultCaseRequiredComment = "//exhaustive:enforce-default-case-required" ) func hasCommentPrefix(comments []*ast.CommentGroup, comment string) bool { diff --git a/vendor/github.com/nishanths/exhaustive/comment_go121.go b/vendor/github.com/nishanths/exhaustive/comment_go121.go new file mode 100644 index 000000000..a7bbc8881 --- /dev/null +++ b/vendor/github.com/nishanths/exhaustive/comment_go121.go @@ -0,0 +1,11 @@ +//go:build go1.21 + +package exhaustive + +import ( + "go/ast" +) + +func isGeneratedFile(file *ast.File) bool { + return ast.IsGenerated(file) +} diff --git a/vendor/github.com/nishanths/exhaustive/comment_pre_go121.go b/vendor/github.com/nishanths/exhaustive/comment_pre_go121.go new file mode 100644 index 000000000..28d2ed493 --- /dev/null +++ b/vendor/github.com/nishanths/exhaustive/comment_pre_go121.go @@ -0,0 +1,27 @@ +//go:build !go1.21 + +package exhaustive + +import ( + "go/ast" + "regexp" +) + +// For definition of generated file see: +// http://golang.org/s/generatedcode + +var generatedCodeRe = regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`) + +func isGeneratedFile(file *ast.File) bool { + for _, c := range file.Comments { + for _, cc := range c.List { + if cc.Pos() > file.Package { + break + } + if generatedCodeRe.MatchString(cc.Text) { + return true + } + } + } + return false +} diff --git a/vendor/github.com/nishanths/exhaustive/doc.go b/vendor/github.com/nishanths/exhaustive/doc.go index 8435e5d24..a745247db 100644 --- a/vendor/github.com/nishanths/exhaustive/doc.go +++ b/vendor/github.com/nishanths/exhaustive/doc.go @@ -10,8 +10,8 @@ The Go [language spec] does not have an explicit definition for enums. For the purpose of this analyzer, and by convention, an enum type is any named type that: - - has underlying type float, string, or integer (includes byte and rune); - and + - has an [underlying type] of float, string, or integer (includes byte + and rune); and - has at least one constant of its type defined in the same [block]. In the example below, Biome is an enum type. The three constants are its @@ -209,6 +209,7 @@ To ignore specific types, specify the -ignore-enum-types flag: exhaustive -ignore-enum-types '^time\.Duration$|^example\.org/measure\.Unit$' [language spec]: https://golang.org/ref/spec +[underlying type]: https://golang.org/ref/spec#Underlying_types [block]: https://golang.org/ref/spec#Blocks [BasicKind]: https://pkg.go.dev/go/types#BasicKind */ diff --git a/vendor/github.com/nishanths/exhaustive/exhaustive.go b/vendor/github.com/nishanths/exhaustive/exhaustive.go index d67a60c32..013ac47bb 100644 --- a/vendor/github.com/nishanths/exhaustive/exhaustive.go +++ b/vendor/github.com/nishanths/exhaustive/exhaustive.go @@ -19,6 +19,7 @@ func registerFlags() { Analyzer.Flags.BoolVar(&fExplicitExhaustiveMap, ExplicitExhaustiveMapFlag, false, `check map literal only if associated with "//exhaustive:enforce" comment`) Analyzer.Flags.BoolVar(&fCheckGenerated, CheckGeneratedFlag, false, "check generated files") Analyzer.Flags.BoolVar(&fDefaultSignifiesExhaustive, DefaultSignifiesExhaustiveFlag, false, "switch statement is unconditionally exhaustive if it has a default case") + Analyzer.Flags.BoolVar(&fDefaultCaseRequired, DefaultCaseRequiredFlag, false, "switch statement requires default case even if exhaustive") Analyzer.Flags.Var(&fIgnoreEnumMembers, IgnoreEnumMembersFlag, "ignore constants matching `regexp`") Analyzer.Flags.Var(&fIgnoreEnumTypes, IgnoreEnumTypesFlag, "ignore types matching `regexp`") Analyzer.Flags.BoolVar(&fPackageScopeOnly, PackageScopeOnlyFlag, false, "only discover enums declared in file-level blocks") @@ -36,6 +37,7 @@ const ( ExplicitExhaustiveMapFlag = "explicit-exhaustive-map" CheckGeneratedFlag = "check-generated" DefaultSignifiesExhaustiveFlag = "default-signifies-exhaustive" + DefaultCaseRequiredFlag = "default-case-required" IgnoreEnumMembersFlag = "ignore-enum-members" IgnoreEnumTypesFlag = "ignore-enum-types" PackageScopeOnlyFlag = "package-scope-only" @@ -52,6 +54,7 @@ var ( fExplicitExhaustiveMap bool fCheckGenerated bool fDefaultSignifiesExhaustive bool + fDefaultCaseRequired bool fIgnoreEnumMembers regexpFlag fIgnoreEnumTypes regexpFlag fPackageScopeOnly bool @@ -65,6 +68,7 @@ func resetFlags() { fExplicitExhaustiveMap = false fCheckGenerated = false fDefaultSignifiesExhaustive = false + fDefaultCaseRequired = false fIgnoreEnumMembers = regexpFlag{} fIgnoreEnumTypes = regexpFlag{} fPackageScopeOnly = false @@ -121,6 +125,7 @@ func run(pass *analysis.Pass) (interface{}, error) { conf := switchConfig{ explicit: fExplicitExhaustiveSwitch, defaultSignifiesExhaustive: fDefaultSignifiesExhaustive, + defaultCaseRequired: fDefaultCaseRequired, checkGenerated: fCheckGenerated, ignoreConstant: fIgnoreEnumMembers.re, ignoreType: fIgnoreEnumTypes.re, diff --git a/vendor/github.com/nishanths/exhaustive/switch.go b/vendor/github.com/nishanths/exhaustive/switch.go index 000ef9886..d235fbec4 100644 --- a/vendor/github.com/nishanths/exhaustive/switch.go +++ b/vendor/github.com/nishanths/exhaustive/switch.go @@ -5,6 +5,7 @@ import ( "go/ast" "go/types" "regexp" + "strings" "golang.org/x/tools/go/analysis" ) @@ -44,6 +45,7 @@ const ( resultNoEnforceComment = "has no enforce comment" resultEnumMembersAccounted = "required enum members accounted for" resultDefaultCaseSuffices = "default case satisfies exhaustiveness" + resultMissingDefaultCase = "missing required default case" resultReportedDiagnostic = "reported diagnostic" resultEnumTypes = "invalid or empty composing enum types" ) @@ -52,11 +54,47 @@ const ( type switchConfig struct { explicit bool defaultSignifiesExhaustive bool + defaultCaseRequired bool checkGenerated bool ignoreConstant *regexp.Regexp // can be nil ignoreType *regexp.Regexp // can be nil } +// There are few possibilities, and often none, so we use a possibly-nil slice +func userDirectives(comments []*ast.CommentGroup) []string { + var directives []string + for _, c := range comments { + for _, cc := range c.List { + // The order matters here: we always want to check the longest first. + for _, d := range []string{ + enforceDefaultCaseRequiredComment, + ignoreDefaultCaseRequiredComment, + enforceComment, + ignoreComment, + } { + if strings.HasPrefix(cc.Text, d) { + directives = append(directives, d) + // The break here is important: once we associate a comment + // with a particular (longest-possible) directive, we don't want + // to map to another! + break + } + } + } + } + return directives +} + +// Can be replaced with slices.Contains with go1.21 +func directivesIncludes(directives []string, d string) bool { + for _, ud := range directives { + if ud == d { + return true + } + } + return false +} + // switchChecker returns a node visitor that checks exhaustiveness of // enum switch statements for the supplied pass, and reports // diagnostics. The node visitor expects only *ast.SwitchStmt nodes. @@ -80,17 +118,27 @@ func switchChecker(pass *analysis.Pass, cfg switchConfig, generated boolCache, c sw := n.(*ast.SwitchStmt) switchComments := comments.get(pass.Fset, file)[sw] - if !cfg.explicit && hasCommentPrefix(switchComments, ignoreComment) { + uDirectives := userDirectives(switchComments) + if !cfg.explicit && directivesIncludes(uDirectives, ignoreComment) { // Skip checking of this switch statement due to ignore // comment. Still return true because there may be nested // switch statements that are not to be ignored. return true, resultIgnoreComment } - if cfg.explicit && !hasCommentPrefix(switchComments, enforceComment) { + if cfg.explicit && !directivesIncludes(uDirectives, enforceComment) { // Skip checking of this switch statement due to missing // enforce comment. return true, resultNoEnforceComment } + requireDefaultCase := cfg.defaultCaseRequired + if directivesIncludes(uDirectives, ignoreDefaultCaseRequiredComment) { + requireDefaultCase = false + } + if directivesIncludes(uDirectives, enforceDefaultCaseRequiredComment) { + // We have "if" instead of "else if" here in case of conflicting ignore/enforce directives. + // In that case, because this is second, we will default to enforcing. + requireDefaultCase = true + } if sw.Tag == nil { return true, resultNoSwitchTag // never possible for valid Go program? @@ -114,13 +162,21 @@ func switchChecker(pass *analysis.Pass, cfg switchConfig, generated boolCache, c checkl.add(e.typ, e.members, pass.Pkg == e.typ.Pkg()) } - def := analyzeSwitchClauses(sw, pass.TypesInfo, checkl.found) + defaultCaseExists := analyzeSwitchClauses(sw, pass.TypesInfo, checkl.found) + if !defaultCaseExists && requireDefaultCase { + // Even if the switch explicitly enumerates all the + // enum values, the user has still required all switches + // to have a default case. We check this first to avoid + // early-outs + pass.Report(makeMissingDefaultDiagnostic(sw, dedupEnumTypes(toEnumTypes(es)))) + return true, resultMissingDefaultCase + } if len(checkl.remaining()) == 0 { // All enum members accounted for. // Nothing to report. return true, resultEnumMembersAccounted } - if def && cfg.defaultSignifiesExhaustive { + if defaultCaseExists && cfg.defaultSignifiesExhaustive { // Though enum members are not accounted for, the // existence of the default case signifies // exhaustiveness. So don't report. @@ -167,3 +223,14 @@ func makeSwitchDiagnostic(sw *ast.SwitchStmt, enumTypes []enumType, missing map[ ), } } + +func makeMissingDefaultDiagnostic(sw *ast.SwitchStmt, enumTypes []enumType) analysis.Diagnostic { + return analysis.Diagnostic{ + Pos: sw.Pos(), + End: sw.End(), + Message: fmt.Sprintf( + "missing default case in switch of type %s", + diagnosticEnumTypes(enumTypes), + ), + } +} diff --git a/vendor/github.com/nunnatsa/ginkgolinter/Makefile b/vendor/github.com/nunnatsa/ginkgolinter/Makefile index e8efae583..586633006 100644 --- a/vendor/github.com/nunnatsa/ginkgolinter/Makefile +++ b/vendor/github.com/nunnatsa/ginkgolinter/Makefile @@ -5,9 +5,12 @@ HASH_FLAG := -X github.com/nunnatsa/ginkgolinter/version.gitHash=$(COMMIT_HASH) BUILD_ARGS := -ldflags "$(VERSION_FLAG) $(HASH_FLAG)" -build: +build: unit-test go build $(BUILD_ARGS) -o ginkgolinter ./cmd/ginkgolinter +unit-test: + go test ./... + build-for-windows: GOOS=windows GOARCH=amd64 go build $(BUILD_ARGS) -o bin/ginkgolinter-amd64.exe ./cmd/ginkgolinter diff --git a/vendor/github.com/nunnatsa/ginkgolinter/README.md b/vendor/github.com/nunnatsa/ginkgolinter/README.md index 6c95917d2..3832f610d 100644 --- a/vendor/github.com/nunnatsa/ginkgolinter/README.md +++ b/vendor/github.com/nunnatsa/ginkgolinter/README.md @@ -44,7 +44,7 @@ It is not enabled by default, though. There are two ways to run ginkgolinter wit enable: - ginkgolinter ``` -## Linter Checks +## Linter Rules The linter checks the ginkgo and gomega assertions in golang test code. Gomega may be used together with ginkgo tests, For example: ```go @@ -177,7 +177,8 @@ var _ = Describe("checking something", Focus, 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. +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. @@ -205,8 +206,30 @@ To suppress this warning entirely, use the `--suppress-type-compare-assertion=tr To suppress a specific file or line, use the `// ginkgo-linter:ignore-type-compare-warning` comment (see [below](#suppress-warning-from-the-code)) +### Wrong Usage of the `MatchError` gomega Matcher [BUG] +The `MatchError` gomega matcher asserts an error value (and if it's not nil). +There are four valid formats for using this Matcher: +* error value; e.g. `Expect(err).To(MatchError(anotherErr))` +* string, to be equal to the output of the `Error()` method; e.g. `Expect(err).To(MatchError("Not Found"))` +* A gomega matcher that asserts strings; e.g. `Expect(err).To(MatchError(ContainSubstring("Found")))` +* [from v0.29.0] a function that receive a single error parameter and returns a single boolean value. + In this format, an additional single string parameter, with the function description, is also required; e.g. + `Expect(err).To(MatchError(isNotFound, "is the error is a not found error"))` + +These four format are checked on runtime, but sometimes it's too late. ginkgolinter performs a static analysis and so it +will find these issues on build time. + +ginkgolinter checks the following: +* Is the first parameter is one of the four options above. +* That there are no additional parameters passed to the matcher; e.g. + `MatchError(isNotFoundFunc, "a valid description" , "not used string")`. In this case, the matcher won't fail on run + time, but the additional parameters are not in use and ignored. +* If the first parameter is a function with the format of `func(error)bool`, ginkgolinter makes sure that the second + parameter exists and its type is string. + ### 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. +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: ```go @@ -240,7 +263,8 @@ The output of the linter,when finding issues, looks like this: The linter will also warn about the `HaveLen(0)` matcher, and will suggest to replace it with `BeEmpty()` ### 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. +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: diff --git a/vendor/github.com/nunnatsa/ginkgolinter/ginkgo_linter.go b/vendor/github.com/nunnatsa/ginkgolinter/ginkgo_linter.go index d1e5164fa..a0aff1612 100644 --- a/vendor/github.com/nunnatsa/ginkgolinter/ginkgo_linter.go +++ b/vendor/github.com/nunnatsa/ginkgolinter/ginkgo_linter.go @@ -16,6 +16,7 @@ import ( "github.com/nunnatsa/ginkgolinter/ginkgohandler" "github.com/nunnatsa/ginkgolinter/gomegahandler" + "github.com/nunnatsa/ginkgolinter/interfaces" "github.com/nunnatsa/ginkgolinter/reverseassertion" "github.com/nunnatsa/ginkgolinter/types" "github.com/nunnatsa/ginkgolinter/version" @@ -40,8 +41,13 @@ const ( 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" + matchErrorArgWrongType = linterName + ": the MatchError matcher used to assert a non error type (%s)" + matchErrorWrongTypeAssertion = linterName + ": MatchError first parameter (%s) must be error, string, GomegaMatcher or func(error)bool are allowed" + matchErrorMissingDescription = linterName + ": missing function description as second parameter of MatchError" + matchErrorRedundantArg = linterName + ": redundant MatchError arguments; consider removing them; consider using `%s` instead" + matchErrorNoFuncDescription = linterName + ": The second parameter of MatchError must be the function description (string)" ) + const ( // gomega matchers beEmpty = "BeEmpty" beEquivalentTo = "BeEquivalentTo" @@ -61,6 +67,7 @@ const ( // gomega matchers and = "And" or = "Or" withTransform = "WithTransform" + matchError = "MatchError" ) const ( // gomega actuals @@ -133,6 +140,8 @@ currently, the linter searches for following: * trigger a warning when a ginkgo focus container (FDescribe, FContext, FWhen or FIt) is found. [Bug] +* validate the MatchError gomega matcher [Bug] + * trigger a warning when using the Equal or the BeIdentical matcher with two different types, as these matchers will fail in runtime. @@ -179,6 +188,8 @@ func (l *ginkgoLinter) run(pass *analysis.Pass) (interface{}, error) { continue } + //gomegaMatcher = getMatcherInterface(pass, file) + ast.Inspect(file, func(n ast.Node) bool { if ginkgoHndlr != nil && fileConfig.ForbidFocus { spec, ok := n.(*ast.ValueSpec) @@ -304,6 +315,8 @@ func checkExpression(pass *analysis.Pass, config types.Config, assertionExp *ast } return bool(config.SuppressCompare) || checkComparison(expr, pass, matcher, handler, first, second, op, oldExpr) + } else if checkMatchError(pass, assertionExp, actualArg, handler, oldExpr) { + return false } else if isExprError(pass, actualArg) { return bool(config.SuppressErr) || checkNilError(pass, expr, handler, actualArg, oldExpr) @@ -318,6 +331,130 @@ func checkExpression(pass *analysis.Pass, config types.Config, assertionExp *ast return true } +func checkMatchError(pass *analysis.Pass, origExp *ast.CallExpr, actualArg ast.Expr, handler gomegahandler.Handler, oldExpr string) bool { + matcher, ok := origExp.Args[0].(*ast.CallExpr) + if !ok { + return false + } + + return doCheckMatchError(pass, origExp, matcher, actualArg, handler, oldExpr) +} + +func doCheckMatchError(pass *analysis.Pass, origExp *ast.CallExpr, matcher *ast.CallExpr, actualArg ast.Expr, handler gomegahandler.Handler, oldExpr string) bool { + name, ok := handler.GetActualFuncName(matcher) + if !ok { + return false + } + switch name { + case matchError: + case not: + nested, ok := matcher.Args[0].(*ast.CallExpr) + if !ok { + return false + } + + return doCheckMatchError(pass, origExp, nested, actualArg, handler, oldExpr) + case and, or: + res := true + for _, arg := range matcher.Args { + if nested, ok := arg.(*ast.CallExpr); ok { + if !doCheckMatchError(pass, origExp, nested, actualArg, handler, oldExpr) { + res = false + } + } + } + return res + default: + return false + } + + if !isExprError(pass, actualArg) { + reportNoFix(pass, origExp.Pos(), matchErrorArgWrongType, goFmt(pass.Fset, actualArg)) + } + + expr := astcopy.CallExpr(matcher) + + validAssertion, requiredParams := checkMatchErrorAssertion(pass, matcher) + if !validAssertion { + reportNoFix(pass, expr.Pos(), matchErrorWrongTypeAssertion, goFmt(pass.Fset, matcher.Args[0])) + return false + } + + numParams := len(matcher.Args) + if numParams == requiredParams { + if numParams == 2 { + t := pass.TypesInfo.TypeOf(matcher.Args[1]) + if !gotypes.Identical(t, gotypes.Typ[gotypes.String]) { + pass.Reportf(expr.Pos(), matchErrorNoFuncDescription) + return false + } + } + return true + } + + if requiredParams == 2 && numParams == 1 { + reportNoFix(pass, expr.Pos(), matchErrorMissingDescription) + return false + } + + var newArgsSuggestion = []ast.Expr{expr.Args[0]} + if requiredParams == 2 { + newArgsSuggestion = append(newArgsSuggestion, expr.Args[1]) + } + expr.Args = newArgsSuggestion + report(pass, expr, matchErrorRedundantArg, oldExpr) + return false +} + +func checkMatchErrorAssertion(pass *analysis.Pass, matcher *ast.CallExpr) (bool, int) { + if isErrorMatcherValidArg(pass, matcher.Args[0]) { + return true, 1 + } + + t1 := pass.TypesInfo.TypeOf(matcher.Args[0]) + if isFuncErrBool(t1) { + return true, 2 + } + + return false, 0 +} + +// isFuncErrBool checks if a function is with the signature `func(error) bool` +func isFuncErrBool(t gotypes.Type) bool { + sig, ok := t.(*gotypes.Signature) + if !ok { + return false + } + if sig.Params().Len() != 1 || sig.Results().Len() != 1 { + return false + } + + if !interfaces.ImplementsError(sig.Params().At(0).Type()) { + return false + } + + b, ok := sig.Results().At(0).Type().(*gotypes.Basic) + if ok && b.Name() == "bool" && b.Info() == gotypes.IsBoolean && b.Kind() == gotypes.Bool { + return true + } + + return false +} + +func isErrorMatcherValidArg(pass *analysis.Pass, arg ast.Expr) bool { + if isExprError(pass, arg) { + return true + } + + if t, ok := pass.TypesInfo.TypeOf(arg).(*gotypes.Basic); ok && t.Kind() == gotypes.String { + return true + } + + t := pass.TypesInfo.TypeOf(arg) + + return interfaces.ImplementsGomegaMatcher(t) +} + 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 { @@ -1297,28 +1434,18 @@ func goFmt(fset *token.FileSet, x ast.Expr) string { return b.String() } -var errorType *gotypes.Interface - -func init() { - errorType = gotypes.Universe.Lookup("error").Type().Underlying().(*gotypes.Interface) -} - -func isError(t gotypes.Type) bool { - return gotypes.Implements(t, errorType) -} - func isExprError(pass *analysis.Pass, expr ast.Expr) bool { actualArgType := pass.TypesInfo.TypeOf(expr) switch t := actualArgType.(type) { case *gotypes.Named: - if isError(actualArgType) { + if interfaces.ImplementsError(actualArgType) { return true } case *gotypes.Tuple: if t.Len() > 0 { switch t0 := t.At(0).Type().(type) { case *gotypes.Named, *gotypes.Pointer: - if isError(t0) { + if interfaces.ImplementsError(t0) { return true } } diff --git a/vendor/github.com/nunnatsa/ginkgolinter/gomegahandler/handler.go b/vendor/github.com/nunnatsa/ginkgolinter/gomegahandler/handler.go index 0c34cb7c1..419145b75 100644 --- a/vendor/github.com/nunnatsa/ginkgolinter/gomegahandler/handler.go +++ b/vendor/github.com/nunnatsa/ginkgolinter/gomegahandler/handler.go @@ -5,6 +5,10 @@ import ( "go/token" ) +const ( + importPath = `"github.com/onsi/gomega"` +) + // Handler provide different handling, depend on the way gomega was imported, whether // in imported with "." name, custom name or without any name. type Handler interface { @@ -23,7 +27,7 @@ type Handler interface { // GetGomegaHandler returns a gomegar handler according to the way gomega was imported in the specific file func GetGomegaHandler(file *ast.File) Handler { for _, imp := range file.Imports { - if imp.Path.Value != `"github.com/onsi/gomega"` { + if imp.Path.Value != importPath { continue } diff --git a/vendor/github.com/nunnatsa/ginkgolinter/interfaces/interfaces.go b/vendor/github.com/nunnatsa/ginkgolinter/interfaces/interfaces.go new file mode 100644 index 000000000..dafeacd4f --- /dev/null +++ b/vendor/github.com/nunnatsa/ginkgolinter/interfaces/interfaces.go @@ -0,0 +1,76 @@ +package interfaces + +import ( + "go/token" + gotypes "go/types" +) + +var ( + errorType *gotypes.Interface + gomegaMatcherType *gotypes.Interface +) + +func init() { + errorType = gotypes.Universe.Lookup("error").Type().Underlying().(*gotypes.Interface) + gomegaMatcherType = generateTheGomegaMatcherInfType() +} + +// generateTheGomegaMatcherInfType generates a types.Interface instance that represents the +// GomegaMatcher interface. +// The original code is (copied from https://github.com/nunnatsa/ginkgolinter/blob/8fdd05eee922578d4699f49d267001c01e0b9f1e/testdata/src/a/vendor/github.com/onsi/gomega/types/types.go) +// +// type GomegaMatcher interface { +// Match(actual interface{}) (success bool, err error) +// FailureMessage(actual interface{}) (message string) +// NegatedFailureMessage(actual interface{}) (message string) +// } +func generateTheGomegaMatcherInfType() *gotypes.Interface { + err := gotypes.Universe.Lookup("error").Type() + bl := gotypes.Typ[gotypes.Bool] + str := gotypes.Typ[gotypes.String] + anyType := gotypes.Universe.Lookup("any").Type() + + return gotypes.NewInterfaceType([]*gotypes.Func{ + // Match(actual interface{}) (success bool, err error) + gotypes.NewFunc(token.NoPos, nil, "Match", gotypes.NewSignatureType( + nil, nil, nil, + gotypes.NewTuple( + gotypes.NewVar(token.NoPos, nil, "actual", anyType), + ), + gotypes.NewTuple( + gotypes.NewVar(token.NoPos, nil, "", bl), + gotypes.NewVar(token.NoPos, nil, "", err), + ), false), + ), + // FailureMessage(actual interface{}) (message string) + gotypes.NewFunc(token.NoPos, nil, "FailureMessage", gotypes.NewSignatureType( + nil, nil, nil, + gotypes.NewTuple( + gotypes.NewVar(token.NoPos, nil, "", anyType), + ), + gotypes.NewTuple( + gotypes.NewVar(token.NoPos, nil, "", str), + ), + false), + ), + //NegatedFailureMessage(actual interface{}) (message string) + gotypes.NewFunc(token.NoPos, nil, "NegatedFailureMessage", gotypes.NewSignatureType( + nil, nil, nil, + gotypes.NewTuple( + gotypes.NewVar(token.NoPos, nil, "", anyType), + ), + gotypes.NewTuple( + gotypes.NewVar(token.NoPos, nil, "", str), + ), + false), + ), + }, nil) +} + +func ImplementsError(t gotypes.Type) bool { + return gotypes.Implements(t, errorType) +} + +func ImplementsGomegaMatcher(t gotypes.Type) bool { + return gotypes.Implements(t, gomegaMatcherType) +} diff --git a/vendor/github.com/polyfloyd/go-errorlint/errorlint/allowed.go b/vendor/github.com/polyfloyd/go-errorlint/errorlint/allowed.go index df8d5f713..8bfb4c9b2 100644 --- a/vendor/github.com/polyfloyd/go-errorlint/errorlint/allowed.go +++ b/vendor/github.com/polyfloyd/go-errorlint/errorlint/allowed.go @@ -41,6 +41,7 @@ var allowedErrors = []struct { {err: "io.EOF", fun: "debug/elf.Open"}, {err: "io.EOF", fun: "debug/elf.NewFile"}, // pkg/io + {err: "io.EOF", fun: "(io.ReadCloser).Read"}, {err: "io.EOF", fun: "(io.Reader).Read"}, {err: "io.EOF", fun: "(io.ReaderAt).ReadAt"}, {err: "io.EOF", fun: "(*io.LimitedReader).Read"}, @@ -71,6 +72,9 @@ var allowedErrors = []struct { {err: "io.EOF", fun: "(*strings.Reader).ReadAt"}, {err: "io.EOF", fun: "(*strings.Reader).ReadByte"}, {err: "io.EOF", fun: "(*strings.Reader).ReadRune"}, + // pkg/context + {err: "context.DeadlineExceeded", fun: "(context.Context).Err"}, + {err: "context.Canceled", fun: "(context.Context).Err"}, } var allowedErrorWildcards = []struct { diff --git a/vendor/github.com/polyfloyd/go-errorlint/errorlint/lint.go b/vendor/github.com/polyfloyd/go-errorlint/errorlint/lint.go index 817cd6904..572a3816d 100644 --- a/vendor/github.com/polyfloyd/go-errorlint/errorlint/lint.go +++ b/vendor/github.com/polyfloyd/go-errorlint/errorlint/lint.go @@ -307,6 +307,11 @@ func LintErrorTypeAssertions(fset *token.FileSet, info *TypesInfoExt) []analysis continue } + // If the asserted type is not an error, allow the expression. + if !implementsError(info.TypesInfo.Types[typeAssert.Type].Type) { + continue + } + lints = append(lints, analysis.Diagnostic{ Message: "type assertion on error will fail on wrapped errors. Use errors.As to check for specific errors", Pos: typeAssert.Pos(), diff --git a/vendor/github.com/polyfloyd/go-errorlint/errorlint/printf.go b/vendor/github.com/polyfloyd/go-errorlint/errorlint/printf.go index 973752592..4c0e12525 100644 --- a/vendor/github.com/polyfloyd/go-errorlint/errorlint/printf.go +++ b/vendor/github.com/polyfloyd/go-errorlint/errorlint/printf.go @@ -7,23 +7,6 @@ import ( "strings" ) -func verbOrder(verbs []verb, numArgs int) [][]verb { - orderedVerbs := make([][]verb, numArgs) - i := 0 - for _, v := range verbs { - if v.index != -1 { - i = v.index - 1 - } - if i >= len(orderedVerbs) { - continue - } - orderedVerbs[i] = append(orderedVerbs[i], v) - verbs = verbs[1:] - i++ - } - return orderedVerbs -} - type verb struct { format string formatOffset int diff --git a/vendor/github.com/securego/gosec/v2/.goreleaser.yml b/vendor/github.com/securego/gosec/v2/.goreleaser.yml index e3c903e7a..bd85bab3a 100644 --- a/vendor/github.com/securego/gosec/v2/.goreleaser.yml +++ b/vendor/github.com/securego/gosec/v2/.goreleaser.yml @@ -19,6 +19,7 @@ builds: - amd64 - arm64 - s390x + - ppc64le ldflags: -X main.Version={{.Version}} -X main.GitTag={{.Tag}} -X main.BuildDate={{.Date}} env: - CGO_ENABLED=0 diff --git a/vendor/github.com/securego/gosec/v2/Makefile b/vendor/github.com/securego/gosec/v2/Makefile index dcfb4b2ed..4f6cce765 100644 --- a/vendor/github.com/securego/gosec/v2/Makefile +++ b/vendor/github.com/securego/gosec/v2/Makefile @@ -17,7 +17,7 @@ GOSEC ?= $(GOBIN)/gosec GINKGO ?= $(GOBIN)/ginkgo GO_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2) GOVULN_MIN_VERSION = 17 -GO_VERSION = 1.20 +GO_VERSION = 1.22 default: $(MAKE) build @@ -76,7 +76,7 @@ release: goreleaser release build-linux: - CGO_ENABLED=$(CGO_ENABLED) GOOS=linux GOARCH=amd64 go build -ldflags=$(BUILDFLAGS) -o $(BIN) ./cmd/gosec/ + CGO_ENABLED=$(CGO_ENABLED) GOOS=linux go build -ldflags=$(BUILDFLAGS) -o $(BIN) ./cmd/gosec/ image: @echo "Building the Docker image..." diff --git a/vendor/github.com/securego/gosec/v2/README.md b/vendor/github.com/securego/gosec/v2/README.md index d9a33f12a..f7b41df2e 100644 --- a/vendor/github.com/securego/gosec/v2/README.md +++ b/vendor/github.com/securego/gosec/v2/README.md @@ -1,5 +1,5 @@ -# gosec - Golang Security Checker +# gosec - Go Security Checker Inspects source code for security problems by scanning the Go AST and SSA code representation. @@ -105,7 +105,7 @@ jobs: # we let the report trigger content trigger a failure using the GitHub Security features. args: '-no-fail -fmt sarif -out results.sarif ./...' - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@v1 + uses: github/codeql-action/upload-sarif@v2 with: # Path to SARIF file relative to the root of the repository sarif_file: results.sarif @@ -113,18 +113,10 @@ jobs: ### Local Installation -#### Go 1.16+ - ```bash go install github.com/securego/gosec/v2/cmd/gosec@latest ``` -#### Go version < 1.16 - -```bash -go get -u github.com/securego/gosec/v2/cmd/gosec -``` - ## Usage Gosec can be configured to only run a subset of rules, to exclude certain file @@ -393,7 +385,7 @@ schema-generate -i sarif-schema-2.1.0.json -o mypath/types.go ``` Most of the MarshallJSON/UnmarshalJSON are removed except the one for PropertyBag which is handy to inline the additional properties. The rest can be removed. -The URI,ID, UUID, GUID were renamed so it fits the Golang convention defined [here](https://github.com/golang/lint/blob/master/lint.go#L700) +The URI,ID, UUID, GUID were renamed so it fits the Go convention defined [here](https://github.com/golang/lint/blob/master/lint.go#L700) ### Tests diff --git a/vendor/github.com/securego/gosec/v2/action.yml b/vendor/github.com/securego/gosec/v2/action.yml index aba47b60c..3097075ce 100644 --- a/vendor/github.com/securego/gosec/v2/action.yml +++ b/vendor/github.com/securego/gosec/v2/action.yml @@ -10,7 +10,7 @@ inputs: runs: using: 'docker' - image: 'docker://securego/gosec:2.18.1' + image: 'docker://securego/gosec:2.18.2' args: - ${{ inputs.args }} diff --git a/vendor/github.com/securego/gosec/v2/analyzer.go b/vendor/github.com/securego/gosec/v2/analyzer.go index 1fd1f5649..0b1225b9b 100644 --- a/vendor/github.com/securego/gosec/v2/analyzer.go +++ b/vendor/github.com/securego/gosec/v2/analyzer.go @@ -123,7 +123,7 @@ func (i ignores) get(file string, line string) map[string][]issue.SuppressionInf start, end := i.parseLine(line) if is, ok := i[file]; ok { for _, i := range is { - if i.start <= start && i.end >= end { + if start <= i.start && end >= i.end { return i.suppressions } } @@ -414,6 +414,9 @@ func (gosec *Analyzer) CheckAnalyzers(pkg *packages.Package) { SSA: ssaResult.(*buildssa.SSA), }, } + + generatedFiles := gosec.generatedFiles(pkg) + for _, analyzer := range gosec.analyzerList { pass := &analysis.Pass{ Analyzer: analyzer, @@ -441,6 +444,11 @@ func (gosec *Analyzer) CheckAnalyzers(pkg *packages.Package) { if result != nil { if passIssues, ok := result.([]*issue.Issue); ok { for _, iss := range passIssues { + if gosec.excludeGenerated { + if _, ok := generatedFiles[iss.File]; ok { + continue + } + } gosec.updateIssues(iss) } } @@ -448,6 +456,21 @@ func (gosec *Analyzer) CheckAnalyzers(pkg *packages.Package) { } } +func (gosec *Analyzer) generatedFiles(pkg *packages.Package) map[string]bool { + generatedFiles := map[string]bool{} + for _, file := range pkg.Syntax { + if isGeneratedFile(file) { + fp := pkg.Fset.File(file.Pos()) + if fp == nil { + // skip files which cannot be located + continue + } + generatedFiles[fp.Name()] = true + } + } + return generatedFiles +} + // 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() { @@ -557,8 +580,8 @@ func (gosec *Analyzer) ignore(n ast.Node) map[string]issue.SuppressionInfo { for _, group := range groups { comment := strings.TrimSpace(group.Text()) - foundDefaultTag := strings.HasPrefix(comment, noSecDefaultTag) || regexp.MustCompile("\n *"+noSecDefaultTag).Match([]byte(comment)) - foundAlternativeTag := strings.HasPrefix(comment, noSecAlternativeTag) || regexp.MustCompile("\n *"+noSecAlternativeTag).Match([]byte(comment)) + foundDefaultTag := strings.HasPrefix(comment, noSecDefaultTag) || regexp.MustCompile("\n *"+noSecDefaultTag).MatchString(comment) + foundAlternativeTag := strings.HasPrefix(comment, noSecAlternativeTag) || regexp.MustCompile("\n *"+noSecAlternativeTag).MatchString(comment) if foundDefaultTag || foundAlternativeTag { gosec.stats.NumNosec++ diff --git a/vendor/github.com/spf13/afero/afero.go b/vendor/github.com/spf13/afero/afero.go index 199480cd0..39f658520 100644 --- a/vendor/github.com/spf13/afero/afero.go +++ b/vendor/github.com/spf13/afero/afero.go @@ -97,7 +97,7 @@ type Fs interface { // Chown changes the uid and gid of the named file. Chown(name string, uid, gid int) error - //Chtimes changes the access and modification times of the named file + // Chtimes changes the access and modification times of the named file Chtimes(name string, atime time.Time, mtime time.Time) error } diff --git a/vendor/github.com/spf13/afero/basepath.go b/vendor/github.com/spf13/afero/basepath.go index 70a1d9168..2e72793a3 100644 --- a/vendor/github.com/spf13/afero/basepath.go +++ b/vendor/github.com/spf13/afero/basepath.go @@ -40,7 +40,6 @@ func (f *BasePathFile) Name() string { func (f *BasePathFile) ReadDir(n int) ([]fs.DirEntry, error) { if rdf, ok := f.File.(fs.ReadDirFile); ok { return rdf.ReadDir(n) - } return readDirFile{f.File}.ReadDir(n) } diff --git a/vendor/github.com/spf13/afero/const_bsds.go b/vendor/github.com/spf13/afero/const_bsds.go index eed0f225f..30855de57 100644 --- a/vendor/github.com/spf13/afero/const_bsds.go +++ b/vendor/github.com/spf13/afero/const_bsds.go @@ -11,8 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build aix || darwin || openbsd || freebsd || netbsd || dragonfly -// +build aix darwin openbsd freebsd netbsd dragonfly +//go:build aix || darwin || openbsd || freebsd || netbsd || dragonfly || zos +// +build aix darwin openbsd freebsd netbsd dragonfly zos package afero diff --git a/vendor/github.com/spf13/afero/const_win_unix.go b/vendor/github.com/spf13/afero/const_win_unix.go index 004d57e2f..12792d21e 100644 --- a/vendor/github.com/spf13/afero/const_win_unix.go +++ b/vendor/github.com/spf13/afero/const_win_unix.go @@ -10,8 +10,8 @@ // 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. -//go:build !darwin && !openbsd && !freebsd && !dragonfly && !netbsd && !aix -// +build !darwin,!openbsd,!freebsd,!dragonfly,!netbsd,!aix +//go:build !darwin && !openbsd && !freebsd && !dragonfly && !netbsd && !aix && !zos +// +build !darwin,!openbsd,!freebsd,!dragonfly,!netbsd,!aix,!zos package afero diff --git a/vendor/github.com/spf13/afero/copyOnWriteFs.go b/vendor/github.com/spf13/afero/copyOnWriteFs.go index 6ff8f3099..184d6dd70 100644 --- a/vendor/github.com/spf13/afero/copyOnWriteFs.go +++ b/vendor/github.com/spf13/afero/copyOnWriteFs.go @@ -223,7 +223,7 @@ func (u *CopyOnWriteFs) OpenFile(name string, flag int, perm os.FileMode) (File, return nil, err } if isaDir { - if err = u.layer.MkdirAll(dir, 0777); err != nil { + if err = u.layer.MkdirAll(dir, 0o777); err != nil { return nil, err } return u.layer.OpenFile(name, flag, perm) @@ -247,8 +247,9 @@ func (u *CopyOnWriteFs) OpenFile(name string, flag int, perm os.FileMode) (File, // This function handles the 9 different possibilities caused // by the union which are the intersection of the following... -// layer: doesn't exist, exists as a file, and exists as a directory -// base: doesn't exist, exists as a file, and exists as a directory +// +// layer: doesn't exist, exists as a file, and exists as a directory +// base: doesn't exist, exists as a file, and exists as a directory func (u *CopyOnWriteFs) Open(name string) (File, error) { // Since the overlay overrides the base we check that first b, err := u.isBaseFile(name) @@ -322,5 +323,5 @@ func (u *CopyOnWriteFs) MkdirAll(name string, perm os.FileMode) error { } func (u *CopyOnWriteFs) Create(name string) (File, error) { - return u.OpenFile(name, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0666) + return u.OpenFile(name, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0o666) } diff --git a/vendor/github.com/spf13/afero/ioutil.go b/vendor/github.com/spf13/afero/ioutil.go index 386c9cdc2..fa6abe1ee 100644 --- a/vendor/github.com/spf13/afero/ioutil.go +++ b/vendor/github.com/spf13/afero/ioutil.go @@ -141,8 +141,10 @@ func WriteFile(fs Fs, filename string, data []byte, perm os.FileMode) error { // We generate random temporary file names so that there's a good // chance the file doesn't exist yet - keeps the number of tries in // TempFile to a minimum. -var randNum uint32 -var randmu sync.Mutex +var ( + randNum uint32 + randmu sync.Mutex +) func reseed() uint32 { return uint32(time.Now().UnixNano() + int64(os.Getpid())) @@ -190,7 +192,7 @@ func TempFile(fs Fs, dir, pattern string) (f File, err error) { nconflict := 0 for i := 0; i < 10000; i++ { name := filepath.Join(dir, prefix+nextRandom()+suffix) - f, err = fs.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) + f, err = fs.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o600) if os.IsExist(err) { if nconflict++; nconflict > 10 { randmu.Lock() @@ -214,6 +216,7 @@ func TempFile(fs Fs, dir, pattern string) (f File, err error) { func (a Afero) TempDir(dir, prefix string) (name string, err error) { return TempDir(a.Fs, dir, prefix) } + func TempDir(fs Fs, dir, prefix string) (name string, err error) { if dir == "" { dir = os.TempDir() @@ -222,7 +225,7 @@ func TempDir(fs Fs, dir, prefix string) (name string, err error) { nconflict := 0 for i := 0; i < 10000; i++ { try := filepath.Join(dir, prefix+nextRandom()) - err = fs.Mkdir(try, 0700) + err = fs.Mkdir(try, 0o700) if os.IsExist(err) { if nconflict++; nconflict > 10 { randmu.Lock() diff --git a/vendor/github.com/spf13/afero/mem/file.go b/vendor/github.com/spf13/afero/mem/file.go index 3cf4693b5..62fe4498e 100644 --- a/vendor/github.com/spf13/afero/mem/file.go +++ b/vendor/github.com/spf13/afero/mem/file.go @@ -245,7 +245,7 @@ func (f *File) Truncate(size int64) error { defer f.fileData.Unlock() if size > int64(len(f.fileData.data)) { diff := size - int64(len(f.fileData.data)) - f.fileData.data = append(f.fileData.data, bytes.Repeat([]byte{00}, int(diff))...) + f.fileData.data = append(f.fileData.data, bytes.Repeat([]byte{0o0}, int(diff))...) } else { f.fileData.data = f.fileData.data[0:size] } @@ -285,7 +285,7 @@ func (f *File) Write(b []byte) (n int, err error) { tail = f.fileData.data[n+int(cur):] } if diff > 0 { - f.fileData.data = append(f.fileData.data, append(bytes.Repeat([]byte{00}, int(diff)), b...)...) + f.fileData.data = append(f.fileData.data, append(bytes.Repeat([]byte{0o0}, int(diff)), b...)...) f.fileData.data = append(f.fileData.data, tail...) } else { f.fileData.data = append(f.fileData.data[:cur], b...) @@ -321,16 +321,19 @@ func (s *FileInfo) Name() string { s.Unlock() return name } + func (s *FileInfo) Mode() os.FileMode { s.Lock() defer s.Unlock() return s.mode } + func (s *FileInfo) ModTime() time.Time { s.Lock() defer s.Unlock() return s.modtime } + func (s *FileInfo) IsDir() bool { s.Lock() defer s.Unlock() diff --git a/vendor/github.com/spf13/afero/memmap.go b/vendor/github.com/spf13/afero/memmap.go index d06975e71..d6c744e8d 100644 --- a/vendor/github.com/spf13/afero/memmap.go +++ b/vendor/github.com/spf13/afero/memmap.go @@ -15,9 +15,13 @@ package afero import ( "fmt" + "io" + "log" "os" "path/filepath" + + "sort" "strings" "sync" "time" @@ -43,7 +47,7 @@ func (m *MemMapFs) getData() map[string]*mem.FileData { // Root should always exist, right? // TODO: what about windows? root := mem.CreateDir(FilePathSeparator) - mem.SetMode(root, os.ModeDir|0755) + mem.SetMode(root, os.ModeDir|0o755) m.data[FilePathSeparator] = root }) return m.data @@ -87,6 +91,24 @@ func (m *MemMapFs) findParent(f *mem.FileData) *mem.FileData { return pfile } +func (m *MemMapFs) findDescendants(name string) []*mem.FileData { + fData := m.getData() + descendants := make([]*mem.FileData, 0, len(fData)) + for p, dFile := range fData { + if strings.HasPrefix(p, name+FilePathSeparator) { + descendants = append(descendants, dFile) + } + } + + sort.Slice(descendants, func(i, j int) bool { + cur := len(strings.Split(descendants[i].Name(), FilePathSeparator)) + next := len(strings.Split(descendants[j].Name(), FilePathSeparator)) + return cur < next + }) + + return descendants +} + func (m *MemMapFs) registerWithParent(f *mem.FileData, perm os.FileMode) { if f == nil { return @@ -96,12 +118,12 @@ func (m *MemMapFs) registerWithParent(f *mem.FileData, perm os.FileMode) { pdir := filepath.Dir(filepath.Clean(f.Name())) err := m.lockfreeMkdir(pdir, perm) if err != nil { - //log.Println("Mkdir error:", err) + // log.Println("Mkdir error:", err) return } parent, err = m.lockfreeOpen(pdir) if err != nil { - //log.Println("Open after Mkdir error:", err) + // log.Println("Open after Mkdir error:", err) return } } @@ -237,7 +259,7 @@ func (m *MemMapFs) OpenFile(name string, flag int, perm os.FileMode) (File, erro file = mem.NewReadOnlyFileHandle(file.(*mem.File).Data()) } if flag&os.O_APPEND > 0 { - _, err = file.Seek(0, os.SEEK_END) + _, err = file.Seek(0, io.SeekEnd) if err != nil { file.Close() return nil, err @@ -308,11 +330,22 @@ func (m *MemMapFs) Rename(oldname, newname string) error { if _, ok := m.getData()[oldname]; ok { m.mu.RUnlock() m.mu.Lock() - m.unRegisterWithParent(oldname) + err := m.unRegisterWithParent(oldname) + if err != nil { + return err + } + fileData := m.getData()[oldname] - delete(m.getData(), oldname) mem.ChangeFileName(fileData, newname) m.getData()[newname] = fileData + + err = m.renameDescendants(oldname, newname) + if err != nil { + return err + } + + delete(m.getData(), oldname) + m.registerWithParent(fileData, 0) m.mu.Unlock() m.mu.RLock() @@ -322,6 +355,29 @@ func (m *MemMapFs) Rename(oldname, newname string) error { return nil } +func (m *MemMapFs) renameDescendants(oldname, newname string) error { + descendants := m.findDescendants(oldname) + removes := make([]string, 0, len(descendants)) + for _, desc := range descendants { + descNewName := strings.Replace(desc.Name(), oldname, newname, 1) + err := m.unRegisterWithParent(desc.Name()) + if err != nil { + return err + } + + removes = append(removes, desc.Name()) + mem.ChangeFileName(desc, descNewName) + m.getData()[descNewName] = desc + + m.registerWithParent(desc, 0) + } + for _, r := range removes { + delete(m.getData(), r) + } + + return nil +} + func (m *MemMapFs) LstatIfPossible(name string) (os.FileInfo, bool, error) { fileInfo, err := m.Stat(name) return fileInfo, false, err diff --git a/vendor/github.com/spf13/afero/regexpfs.go b/vendor/github.com/spf13/afero/regexpfs.go index ac359c62a..218f3b235 100644 --- a/vendor/github.com/spf13/afero/regexpfs.go +++ b/vendor/github.com/spf13/afero/regexpfs.go @@ -10,7 +10,6 @@ import ( // The RegexpFs filters files (not directories) by regular expression. Only // files matching the given regexp will be allowed, all others get a ENOENT error ( // "No such file or directory"). -// type RegexpFs struct { re *regexp.Regexp source Fs diff --git a/vendor/github.com/spf13/afero/symlink.go b/vendor/github.com/spf13/afero/symlink.go index d1c6ea53d..aa6ae125b 100644 --- a/vendor/github.com/spf13/afero/symlink.go +++ b/vendor/github.com/spf13/afero/symlink.go @@ -21,9 +21,9 @@ import ( // filesystems saying so. // It indicates support for 3 symlink related interfaces that implement the // behaviors of the os methods: -// - Lstat -// - Symlink, and -// - Readlink +// - Lstat +// - Symlink, and +// - Readlink type Symlinker interface { Lstater Linker diff --git a/vendor/github.com/spf13/afero/unionFile.go b/vendor/github.com/spf13/afero/unionFile.go index 333d367f4..62dd6c93c 100644 --- a/vendor/github.com/spf13/afero/unionFile.go +++ b/vendor/github.com/spf13/afero/unionFile.go @@ -47,7 +47,7 @@ func (f *UnionFile) Read(s []byte) (int, error) { if (err == nil || err == io.EOF) && f.Base != nil { // advance the file position also in the base file, the next // call may be a write at this position (or a seek with SEEK_CUR) - if _, seekErr := f.Base.Seek(int64(n), os.SEEK_CUR); seekErr != nil { + if _, seekErr := f.Base.Seek(int64(n), io.SeekCurrent); seekErr != nil { // only overwrite err in case the seek fails: we need to // report an eventual io.EOF to the caller err = seekErr @@ -130,7 +130,7 @@ func (f *UnionFile) Name() string { type DirsMerger func(lofi, bofi []os.FileInfo) ([]os.FileInfo, error) var defaultUnionMergeDirsFn = func(lofi, bofi []os.FileInfo) ([]os.FileInfo, error) { - var files = make(map[string]os.FileInfo) + files := make(map[string]os.FileInfo) for _, fi := range lofi { files[fi.Name()] = fi @@ -151,7 +151,6 @@ var defaultUnionMergeDirsFn = func(lofi, bofi []os.FileInfo) ([]os.FileInfo, err } return rfi, nil - } // Readdir will weave the two directories together and @@ -275,7 +274,7 @@ func copyFile(base Fs, layer Fs, name string, bfh File) error { return err } if !exists { - err = layer.MkdirAll(filepath.Dir(name), 0777) // FIXME? + err = layer.MkdirAll(filepath.Dir(name), 0o777) // FIXME? if err != nil { return err } diff --git a/vendor/github.com/spf13/afero/util.go b/vendor/github.com/spf13/afero/util.go index cb7de23f2..9e4cba274 100644 --- a/vendor/github.com/spf13/afero/util.go +++ b/vendor/github.com/spf13/afero/util.go @@ -43,7 +43,7 @@ func WriteReader(fs Fs, path string, r io.Reader) (err error) { ospath := filepath.FromSlash(dir) if ospath != "" { - err = fs.MkdirAll(ospath, 0777) // rwx, rw, r + err = fs.MkdirAll(ospath, 0o777) // rwx, rw, r if err != nil { if err != os.ErrExist { return err @@ -71,7 +71,7 @@ func SafeWriteReader(fs Fs, path string, r io.Reader) (err error) { ospath := filepath.FromSlash(dir) if ospath != "" { - err = fs.MkdirAll(ospath, 0777) // rwx, rw, r + err = fs.MkdirAll(ospath, 0o777) // rwx, rw, r if err != nil { return } @@ -124,7 +124,7 @@ func GetTempDir(fs Fs, subPath string) string { return addSlash(dir) } - err := fs.MkdirAll(dir, 0777) + err := fs.MkdirAll(dir, 0o777) if err != nil { panic(err) } @@ -197,7 +197,6 @@ func FileContainsAnyBytes(fs Fs, filename string, subslices [][]byte) (bool, err // readerContains reports whether any of the subslices is within r. func readerContainsAny(r io.Reader, subslices ...[]byte) bool { - if r == nil || len(subslices) == 0 { return false } diff --git a/vendor/github.com/tetafro/godot/.golangci.yml b/vendor/github.com/tetafro/godot/.golangci.yml index 920135d40..ea380eb83 100644 --- a/vendor/github.com/tetafro/godot/.golangci.yml +++ b/vendor/github.com/tetafro/godot/.golangci.yml @@ -8,45 +8,53 @@ skip-dirs: linters: disable-all: true enable: - - deadcode - - errcheck - - gosimple - - govet - - ineffassign - - staticcheck - - structcheck - - typecheck - - unused - - varcheck + - asciicheck - bodyclose + - cyclop - dogsled - - dupl - - funlen + - durationcheck + - errcheck + - errname + - errorlint + - exhaustive + - exportloopref + - exportloopref - gochecknoinits + - gocognit - goconst - gocritic - gocyclo - godot + - goerr113 - gofmt - gofumpt - goimports - - golint - - gomnd - - gomodguard - goprintffuncname - gosec + - gosimple + - govet + - importas + - ineffassign - lll - - maligned - misspell - nakedret - nestif + - noctx + - nolintlint - prealloc + - revive - rowserrcheck - - scopelint + - sqlclosecheck + - sqlclosecheck + - staticcheck - stylecheck + - typecheck - unconvert - unparam + - unused + - wastedassign - whitespace + - wrapcheck linters-settings: godot: @@ -54,13 +62,19 @@ linters-settings: issues: exclude-use-default: false + exclude: + - "do not define dynamic errors, use wrapped static errors instead" exclude-rules: - path: _test\.go linters: - dupl - errcheck - funlen + - gocognit + - cyclop - gosec - - path: cmd/godot/main\.go + - noctx + - path: main\.go linters: + - cyclop - gomnd diff --git a/vendor/github.com/tetafro/godot/checks.go b/vendor/github.com/tetafro/godot/checks.go index f5471cdf7..0e53c220a 100644 --- a/vendor/github.com/tetafro/godot/checks.go +++ b/vendor/github.com/tetafro/godot/checks.go @@ -21,7 +21,7 @@ var ( // Abbreviations to exclude from capital letters check. abbreviations = []string{"i.e.", "i. e.", "e.g.", "e. g.", "etc."} - // Special tags in comments like "// nolint:", or "// +k8s:". + // Special tags in comments like "//nolint:", or "//+k8s:". tags = regexp.MustCompile(`^\+?[a-z0-9]+:`) // Special hashtags in comments like "// #nosec". @@ -31,18 +31,24 @@ var ( endURL = regexp.MustCompile(`[a-z]+://[^\s]+$`) ) +// position is a position inside a comment (might be multiline comment). +type position struct { + line int // starts at 1 + column int // starts at 1, byte count +} + // checkComments checks every comment accordings to the rules from // `settings` argument. func checkComments(comments []comment, settings Settings) []Issue { - var issues []Issue // nolint: prealloc + var issues []Issue for _, c := range comments { if settings.Period { - if iss := checkCommentForPeriod(c); iss != nil { + if iss := checkPeriod(c); iss != nil { issues = append(issues, *iss) } } if settings.Capital { - if iss := checkCommentForCapital(c); len(iss) > 0 { + if iss := checkCapital(c); len(iss) > 0 { issues = append(issues, iss...) } } @@ -50,14 +56,34 @@ func checkComments(comments []comment, settings Settings) []Issue { return issues } -// checkCommentForPeriod checks that the last sentense of the comment ends +// checkPeriod checks that the last sentense of the comment ends // in a period. -func checkCommentForPeriod(c comment) *Issue { - pos, ok := checkPeriod(c.text) - if ok { +func checkPeriod(c comment) *Issue { + // Check last non-empty line + var found bool + var line string + var pos position + lines := strings.Split(c.text, "\n") + for i := len(lines) - 1; i >= 0; i-- { + line = strings.TrimRightFunc(lines[i], unicode.IsSpace) + if line == "" { + continue + } + found = true + pos.line = i + 1 + break + } + // All lines are empty + if !found { + return nil + } + // Correct line + if hasSuffix(line, lastChars) { return nil } + pos.column = len(line) + 1 + // Shift position to its real value. `c.text` doesn't contain comment's // special symbols: /* or //, and line indentations inside. It also // contains */ in the end in case of block comment. @@ -94,95 +120,15 @@ func checkCommentForPeriod(c comment) *Issue { return &iss } -// checkCommentForCapital checks that each sentense of the comment starts with -// a capital letter. -// nolint: unparam -func checkCommentForCapital(c comment) []Issue { - pp := checkCapital(c.text, c.decl) - if len(pp) == 0 { - return nil - } - - issues := make([]Issue, len(pp)) - for i, pos := range pp { - // Shift position by the length of comment's special symbols: /* or // - isBlock := strings.HasPrefix(c.lines[0], "/*") - if (isBlock && pos.line == 1) || !isBlock { - pos.column += 2 - } - - iss := Issue{ - Pos: token.Position{ - Filename: c.start.Filename, - Offset: c.start.Offset, - Line: pos.line + c.start.Line - 1, - Column: pos.column + c.start.Column - 1, - }, - Message: noCapitalMessage, - } - - // Make a replacement. Use `pos.original` to get an original original from - // attached lines. Use `iss.Pos.Column` because it's a position in - // the original original. - original := c.lines[pos.line-1] - col := byteToRuneColumn(original, iss.Pos.Column) - 1 - rep := string(unicode.ToTitle([]rune(original)[col])) // capital letter - if len(original) < iss.Pos.Column-1+len(rep) { - // This should never happen. Avoid panics, skip this check. - continue - } - iss.Replacement = original[:iss.Pos.Column-1] + rep + - original[iss.Pos.Column-1+len(rep):] - - // Save replacement to raw lines to be able to combine it with - // further replacements - c.lines[pos.line-1] = iss.Replacement - - issues[i] = iss - } - - return issues -} - -// checkPeriod checks that the last sentense of the text ends in a period. -// NOTE: Returned position is a position inside given text, not in the -// original file. -func checkPeriod(comment string) (pos position, ok bool) { - // Check last non-empty line - var found bool - var line string - lines := strings.Split(comment, "\n") - for i := len(lines) - 1; i >= 0; i-- { - line = strings.TrimRightFunc(lines[i], unicode.IsSpace) - if line == "" { - continue - } - found = true - pos.line = i + 1 - break - } - // All lines are empty - if !found { - return position{}, true - } - // Correct line - if hasSuffix(line, lastChars) { - return position{}, true - } - - pos.column = len(line) + 1 - return pos, false -} - -// checkCapital checks that each sentense of the text starts with +// checkCapital checks that each sentense of the comment starts with // a capital letter. -// NOTE: First letter is not checked in declaration comments, because they -// can describe unexported functions, which start with small letter. -func checkCapital(comment string, skipFirst bool) (pp []position) { +// +//nolint:cyclop,funlen +func checkCapital(c comment) []Issue { // Remove common abbreviations from the comment for _, abbr := range abbreviations { repl := strings.ReplaceAll(abbr, ".", "_") - comment = strings.ReplaceAll(comment, abbr, repl) + c.text = strings.ReplaceAll(c.text, abbr, repl) } // List of states during the scan: `empty` - nothing special, @@ -190,12 +136,14 @@ func checkCapital(comment string, skipFirst bool) (pp []position) { // `endOfSentence` - found `endChar`, and then space or newline. const empty, endChar, endOfSentence = 1, 2, 3 + var pp []position pos := position{line: 1} state := endOfSentence - if skipFirst { + if c.decl { + // Skip first state = empty } - for _, r := range comment { + for _, r := range c.text { s := string(r) pos.column++ @@ -223,12 +171,54 @@ func checkCapital(comment string, skipFirst bool) (pp []position) { if state == endOfSentence && unicode.IsLower(r) { pp = append(pp, position{ line: pos.line, - column: runeToByteColumn(comment, pos.column), + column: runeToByteColumn(c.text, pos.column), }) } state = empty } - return pp + if len(pp) == 0 { + return nil + } + + issues := make([]Issue, len(pp)) + for i, pos := range pp { + // Shift position by the length of comment's special symbols: /* or // + isBlock := strings.HasPrefix(c.lines[0], "/*") + if (isBlock && pos.line == 1) || !isBlock { + pos.column += 2 + } + + iss := Issue{ + Pos: token.Position{ + Filename: c.start.Filename, + Offset: c.start.Offset, + Line: pos.line + c.start.Line - 1, + Column: pos.column + c.start.Column - 1, + }, + Message: noCapitalMessage, + } + + // Make a replacement. Use `pos.original` to get an original original from + // attached lines. Use `iss.Pos.Column` because it's a position in + // the original original. + original := c.lines[pos.line-1] + col := byteToRuneColumn(original, iss.Pos.Column) - 1 + rep := string(unicode.ToTitle([]rune(original)[col])) // capital letter + if len(original) < iss.Pos.Column-1+len(rep) { + // This should never happen. Avoid panics, skip this check. + continue + } + iss.Replacement = original[:iss.Pos.Column-1] + rep + + original[iss.Pos.Column-1+len(rep):] + + // Save replacement to raw lines to be able to combine it with + // further replacements + c.lines[pos.line-1] = iss.Replacement + + issues[i] = iss + } + + return issues } // isSpecialBlock checks that given block of comment lines is special and @@ -246,7 +236,7 @@ func isSpecialBlock(comment string) bool { return false } -// isSpecialBlock checks that given comment line is special and +// isSpecialLine checks that given comment line is special and // shouldn't be checked as a regular sentence. func isSpecialLine(comment string) bool { // Skip cgo export tags: https://golang.org/cmd/cgo/#hdr-C_references_to_Go diff --git a/vendor/github.com/tetafro/godot/getters.go b/vendor/github.com/tetafro/godot/getters.go index 1a47c824f..7d3d22fb1 100644 --- a/vendor/github.com/tetafro/godot/getters.go +++ b/vendor/github.com/tetafro/godot/getters.go @@ -44,7 +44,7 @@ func newParsedFile(file *ast.File, fset *token.FileSet) (*parsedFile, error) { // from "go/format" won't help here if the original file is not gofmt-ed. pf.lines, err = readFile(file, fset) if err != nil { - return nil, fmt.Errorf("read file: %v", err) + return nil, fmt.Errorf("read file: %w", err) } // Dirty hack. For some cases Go generates temporary files during @@ -58,9 +58,13 @@ func newParsedFile(file *ast.File, fset *token.FileSet) (*parsedFile, error) { return nil, errUnsuitableInput } - // Check consistency to avoid checking slice indexes in each function + // Check consistency to avoid checking slice indexes in each function. + // Note that `PositionFor` is used with `adjusted=false` to skip `//line` + // directives that can set references to other files (e.g. templates) + // instead of the real ones, and break consistency here. + // Issue: https://github.com/tetafro/godot/issues/32 lastComment := pf.file.Comments[len(pf.file.Comments)-1] - if p := pf.fset.Position(lastComment.End()); len(pf.lines) < p.Line { + if p := pf.fset.PositionFor(lastComment.End(), false); len(pf.lines) < p.Line { return nil, fmt.Errorf("inconsistency between file and AST: %s", p.Filename) } @@ -82,7 +86,7 @@ func (pf *parsedFile) getComments(scope Scope, exclude []*regexp.Regexp) []comme pf.getBlockComments(exclude), pf.getTopLevelComments(exclude)..., ) - default: + case DeclScope: // Top level declaration comments and comments from the inside // of top level blocks comments = append(pf.getBlockComments(exclude), decl...) @@ -118,7 +122,7 @@ func (pf *parsedFile) getBlockComments(exclude []*regexp.Regexp) []comment { // Skip comments that are not top-level for this block // (the block itself is top level, so comments inside this block // would be on column 2) - // nolint: gomnd + //nolint:gomnd if pf.fset.Position(c.Pos()).Column != 2 { continue } @@ -136,7 +140,7 @@ func (pf *parsedFile) getBlockComments(exclude []*regexp.Regexp) []comment { // getTopLevelComments gets all top level comments. func (pf *parsedFile) getTopLevelComments(exclude []*regexp.Regexp) []comment { - var comments []comment // nolint: prealloc + var comments []comment //nolint:prealloc for _, c := range pf.file.Comments { if c == nil || len(c.List) == 0 { continue @@ -157,7 +161,7 @@ func (pf *parsedFile) getTopLevelComments(exclude []*regexp.Regexp) []comment { // getDeclarationComments gets top level declaration comments. func (pf *parsedFile) getDeclarationComments(exclude []*regexp.Regexp) []comment { - var comments []comment // nolint: prealloc + var comments []comment //nolint:prealloc for _, decl := range pf.file.Decls { var cg *ast.CommentGroup switch d := decl.(type) { @@ -184,7 +188,7 @@ func (pf *parsedFile) getDeclarationComments(exclude []*regexp.Regexp) []comment // getAllComments gets every single comment from the file. func (pf *parsedFile) getAllComments(exclude []*regexp.Regexp) []comment { - var comments []comment //nolint: prealloc + var comments []comment //nolint:prealloc for _, c := range pf.file.Comments { if c == nil || len(c.List) == 0 { continue @@ -205,6 +209,8 @@ func (pf *parsedFile) getAllComments(exclude []*regexp.Regexp) []comment { // special lines (e.g., tags or indented code examples), they are replaced // with `specialReplacer` to skip checks for them. // The result can be multiline. +// +//nolint:cyclop func getText(comment *ast.CommentGroup, exclude []*regexp.Regexp) (s string) { if len(comment.List) == 1 && strings.HasPrefix(comment.List[0].Text, "/*") && @@ -246,7 +252,7 @@ func readFile(file *ast.File, fset *token.FileSet) ([]string, error) { fname := fset.File(file.Package) f, err := os.ReadFile(fname.Name()) if err != nil { - return nil, err + return nil, err //nolint:wrapcheck } return strings.Split(string(f), "\n"), nil } diff --git a/vendor/github.com/tetafro/godot/godot.go b/vendor/github.com/tetafro/godot/godot.go index df9271296..e825e9a6d 100644 --- a/vendor/github.com/tetafro/godot/godot.go +++ b/vendor/github.com/tetafro/godot/godot.go @@ -3,6 +3,7 @@ package godot import ( + "errors" "fmt" "go/ast" "go/token" @@ -24,12 +25,6 @@ type Issue struct { Replacement string } -// position is a position inside a comment (might be multiline comment). -type position struct { - line int // starts at 1 - column int // starts at 1, byte count -} - // comment is an internal representation of AST comment entity with additional // data attached. The latter is used for creating a full replacement for // the line with issues. @@ -43,18 +38,18 @@ type comment struct { // Run runs this linter on the provided code. func Run(file *ast.File, fset *token.FileSet, settings Settings) ([]Issue, error) { pf, err := newParsedFile(file, fset) - if err == errEmptyInput || err == errUnsuitableInput { + if errors.Is(err, errEmptyInput) || errors.Is(err, errUnsuitableInput) { return nil, nil } if err != nil { - return nil, fmt.Errorf("parse input file: %v", err) + return nil, fmt.Errorf("parse input file: %w", err) } exclude := make([]*regexp.Regexp, len(settings.Exclude)) for i := 0; i < len(settings.Exclude); i++ { exclude[i], err = regexp.Compile(settings.Exclude[i]) if err != nil { - return nil, fmt.Errorf("invalid regexp: %v", err) + return nil, fmt.Errorf("invalid regexp: %w", err) } } @@ -68,9 +63,9 @@ 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 := os.ReadFile(path) // nolint: gosec + content, err := os.ReadFile(path) //nolint:gosec if err != nil { - return nil, fmt.Errorf("read file: %v", err) + return nil, fmt.Errorf("read file: %w", err) } if len(content) == 0 { return nil, nil @@ -78,7 +73,7 @@ func Fix(path string, file *ast.File, fset *token.FileSet, settings Settings) ([ issues, err := Run(file, fset, settings) if err != nil { - return nil, fmt.Errorf("run linter: %v", err) + return nil, fmt.Errorf("run linter: %w", err) } // slice -> map @@ -105,17 +100,17 @@ func Fix(path string, file *ast.File, fset *token.FileSet, settings Settings) ([ func Replace(path string, file *ast.File, fset *token.FileSet, settings Settings) error { info, err := os.Stat(path) if err != nil { - return fmt.Errorf("check file: %v", err) + return fmt.Errorf("check file: %w", err) } mode := info.Mode() fixed, err := Fix(path, file, fset, settings) if err != nil { - return fmt.Errorf("fix issues: %v", err) + return fmt.Errorf("fix issues: %w", err) } if err := os.WriteFile(path, fixed, mode); err != nil { - return fmt.Errorf("write file: %v", err) + return fmt.Errorf("write file: %w", err) } return nil } diff --git a/vendor/github.com/ultraware/whitespace/README.md b/vendor/github.com/ultraware/whitespace/README.md index 2a88f1338..f99ecce36 100644 --- a/vendor/github.com/ultraware/whitespace/README.md +++ b/vendor/github.com/ultraware/whitespace/README.md @@ -4,4 +4,6 @@ Whitespace is a linter that checks for unnecessary newlines at the start and end ## Installation guide -Whitespace is included in [golangci-lint](https://github.com/golangci/golangci-lint/). Install it and enable whitespace. +To install as a standalone linter, run `go install github.com/ultraware/whitespace`. + +Whitespace is also included in [golangci-lint](https://github.com/golangci/golangci-lint/). Install it and enable whitespace. diff --git a/vendor/github.com/ultraware/whitespace/main.go b/vendor/github.com/ultraware/whitespace/main.go deleted file mode 100644 index d178ea293..000000000 --- a/vendor/github.com/ultraware/whitespace/main.go +++ /dev/null @@ -1,162 +0,0 @@ -package whitespace - -import ( - "go/ast" - "go/token" -) - -// Message contains a message -type Message struct { - Pos token.Position - Type MessageType - Message string -} - -// MessageType describes what should happen to fix the warning -type MessageType uint8 - -// List of MessageTypes -const ( - MessageTypeLeading MessageType = iota + 1 - MessageTypeTrailing - MessageTypeAddAfter -) - -// Settings contains settings for edge-cases -type Settings struct { - MultiIf bool - MultiFunc bool -} - -// Run runs this linter on the provided code -func Run(file *ast.File, fset *token.FileSet, settings Settings) []Message { - var messages []Message - - for _, f := range file.Decls { - decl, ok := f.(*ast.FuncDecl) - if !ok || decl.Body == nil { // decl.Body can be nil for e.g. cgo - continue - } - - vis := visitor{file.Comments, fset, nil, make(map[*ast.BlockStmt]bool), settings} - ast.Walk(&vis, decl) - - messages = append(messages, vis.messages...) - } - - return messages -} - -type visitor struct { - comments []*ast.CommentGroup - fset *token.FileSet - messages []Message - wantNewline map[*ast.BlockStmt]bool - settings Settings -} - -func (v *visitor) Visit(node ast.Node) ast.Visitor { - if node == nil { - return v - } - - if stmt, ok := node.(*ast.IfStmt); ok && v.settings.MultiIf { - checkMultiLine(v, stmt.Body, stmt.Cond) - } - - if stmt, ok := node.(*ast.FuncLit); ok && v.settings.MultiFunc { - checkMultiLine(v, stmt.Body, stmt.Type) - } - - if stmt, ok := node.(*ast.FuncDecl); ok && v.settings.MultiFunc { - checkMultiLine(v, stmt.Body, stmt.Type) - } - - if stmt, ok := node.(*ast.BlockStmt); ok { - wantNewline := v.wantNewline[stmt] - - comments := v.comments - if wantNewline { - comments = nil // Comments also count as a newline if we want a newline - } - first, last := firstAndLast(comments, v.fset, stmt.Pos(), stmt.End(), stmt.List) - - startMsg := checkStart(v.fset, stmt.Lbrace, first) - - if wantNewline && startMsg == nil { - v.messages = append(v.messages, Message{v.fset.Position(stmt.Pos()), MessageTypeAddAfter, `multi-line statement should be followed by a newline`}) - } else if !wantNewline && startMsg != nil { - v.messages = append(v.messages, *startMsg) - } - - if msg := checkEnd(v.fset, stmt.Rbrace, last); msg != nil { - v.messages = append(v.messages, *msg) - } - } - - return v -} - -func checkMultiLine(v *visitor, body *ast.BlockStmt, stmtStart ast.Node) { - start, end := posLine(v.fset, stmtStart.Pos()), posLine(v.fset, stmtStart.End()) - - if end > start { // Check only multi line conditions - v.wantNewline[body] = true - } -} - -func posLine(fset *token.FileSet, pos token.Pos) int { - return fset.Position(pos).Line -} - -func firstAndLast(comments []*ast.CommentGroup, fset *token.FileSet, start, end token.Pos, stmts []ast.Stmt) (ast.Node, ast.Node) { - if len(stmts) == 0 { - return nil, nil - } - - first, last := ast.Node(stmts[0]), ast.Node(stmts[len(stmts)-1]) - - for _, c := range comments { - if posLine(fset, c.Pos()) == posLine(fset, start) || posLine(fset, c.End()) == posLine(fset, end) { - continue - } - - if c.Pos() < start || c.End() > end { - continue - } - if c.Pos() < first.Pos() { - first = c - } - if c.End() > last.End() { - last = c - } - } - - return first, last -} - -func checkStart(fset *token.FileSet, start token.Pos, first ast.Node) *Message { - if first == nil { - return nil - } - - if posLine(fset, start)+1 < posLine(fset, first.Pos()) { - pos := fset.Position(start) - return &Message{pos, MessageTypeLeading, `unnecessary leading newline`} - } - - return nil -} - -func checkEnd(fset *token.FileSet, end token.Pos, last ast.Node) *Message { - if last == nil { - return nil - } - - if posLine(fset, end)-1 > posLine(fset, last.End()) { - pos := fset.Position(end) - return &Message{pos, MessageTypeTrailing, `unnecessary trailing newline`} - } - - return nil -} diff --git a/vendor/github.com/ultraware/whitespace/whitespace.go b/vendor/github.com/ultraware/whitespace/whitespace.go new file mode 100644 index 000000000..350e9b7e4 --- /dev/null +++ b/vendor/github.com/ultraware/whitespace/whitespace.go @@ -0,0 +1,307 @@ +package whitespace + +import ( + "flag" + "go/ast" + "go/token" + "strings" + + "golang.org/x/tools/go/analysis" +) + +// MessageType describes what should happen to fix the warning. +type MessageType uint8 + +// List of MessageTypes. +const ( + MessageTypeRemove MessageType = iota + 1 + MessageTypeAdd +) + +// RunningMode describes the mode the linter is run in. This can be either +// native or golangci-lint. +type RunningMode uint8 + +const ( + RunningModeNative RunningMode = iota + RunningModeGolangCI +) + +// Message contains a message and diagnostic information. +type Message struct { + // Diagnostic is what position the diagnostic should be put at. This isn't + // always the same as the fix start, f.ex. when we fix trailing newlines we + // put the diagnostic at the right bracket but we fix between the end of the + // last statement and the bracket. + Diagnostic token.Pos + + // FixStart is the span start of the fix. + FixStart token.Pos + + // FixEnd is the span end of the fix. + FixEnd token.Pos + + // LineNumbers represent the actual line numbers in the file. This is set + // when finding the diagnostic to make it easier to suggest fixes in + // golangci-lint. + LineNumbers []int + + // MessageType represents the type of message it is. + MessageType MessageType + + // Message is the diagnostic to show. + Message string +} + +// Settings contains settings for edge-cases. +type Settings struct { + Mode RunningMode + MultiIf bool + MultiFunc bool +} + +// NewAnalyzer creates a new whitespace analyzer. +func NewAnalyzer(settings *Settings) *analysis.Analyzer { + if settings == nil { + settings = &Settings{} + } + + return &analysis.Analyzer{ + Name: "whitespace", + Doc: "Whitespace is a linter that checks for unnecessary newlines at the start and end of functions, if, for, etc.", + Flags: flags(settings), + Run: func(p *analysis.Pass) (any, error) { + Run(p, settings) + return nil, nil + }, + RunDespiteErrors: true, + } +} + +func flags(settings *Settings) flag.FlagSet { + flags := flag.NewFlagSet("", flag.ExitOnError) + flags.BoolVar(&settings.MultiIf, "multi-if", settings.MultiIf, "Check that multi line if-statements have a leading newline") + flags.BoolVar(&settings.MultiFunc, "multi-func", settings.MultiFunc, "Check that multi line functions have a leading newline") + + return *flags +} + +func Run(pass *analysis.Pass, settings *Settings) []Message { + messages := []Message{} + + for _, file := range pass.Files { + filename := pass.Fset.Position(file.Pos()).Filename + if !strings.HasSuffix(filename, ".go") { + continue + } + + fileMessages := runFile(file, pass.Fset, *settings) + + if settings.Mode == RunningModeGolangCI { + messages = append(messages, fileMessages...) + continue + } + + for _, message := range fileMessages { + pass.Report(analysis.Diagnostic{ + Pos: message.Diagnostic, + Category: "whitespace", + Message: message.Message, + SuggestedFixes: []analysis.SuggestedFix{ + { + TextEdits: []analysis.TextEdit{ + { + Pos: message.FixStart, + End: message.FixEnd, + NewText: []byte("\n"), + }, + }, + }, + }, + }) + } + } + + return messages +} + +func runFile(file *ast.File, fset *token.FileSet, settings Settings) []Message { + var messages []Message + + for _, f := range file.Decls { + decl, ok := f.(*ast.FuncDecl) + if !ok || decl.Body == nil { // decl.Body can be nil for e.g. cgo + continue + } + + vis := visitor{file.Comments, fset, nil, make(map[*ast.BlockStmt]bool), settings} + ast.Walk(&vis, decl) + + messages = append(messages, vis.messages...) + } + + return messages +} + +type visitor struct { + comments []*ast.CommentGroup + fset *token.FileSet + messages []Message + wantNewline map[*ast.BlockStmt]bool + settings Settings +} + +func (v *visitor) Visit(node ast.Node) ast.Visitor { + if node == nil { + return v + } + + if stmt, ok := node.(*ast.IfStmt); ok && v.settings.MultiIf { + checkMultiLine(v, stmt.Body, stmt.Cond) + } + + if stmt, ok := node.(*ast.FuncLit); ok && v.settings.MultiFunc { + checkMultiLine(v, stmt.Body, stmt.Type) + } + + if stmt, ok := node.(*ast.FuncDecl); ok && v.settings.MultiFunc { + checkMultiLine(v, stmt.Body, stmt.Type) + } + + if stmt, ok := node.(*ast.BlockStmt); ok { + wantNewline := v.wantNewline[stmt] + + comments := v.comments + if wantNewline { + comments = nil // Comments also count as a newline if we want a newline + } + + opening, first, last := firstAndLast(comments, v.fset, stmt) + startMsg := checkStart(v.fset, opening, first) + + if wantNewline && startMsg == nil && len(stmt.List) >= 1 { + v.messages = append(v.messages, Message{ + Diagnostic: opening, + FixStart: stmt.List[0].Pos(), + FixEnd: stmt.List[0].Pos(), + LineNumbers: []int{v.fset.PositionFor(stmt.List[0].Pos(), false).Line}, + MessageType: MessageTypeAdd, + Message: "multi-line statement should be followed by a newline", + }) + } else if !wantNewline && startMsg != nil { + v.messages = append(v.messages, *startMsg) + } + + if msg := checkEnd(v.fset, stmt.Rbrace, last); msg != nil { + v.messages = append(v.messages, *msg) + } + } + + return v +} + +func checkMultiLine(v *visitor, body *ast.BlockStmt, stmtStart ast.Node) { + start, end := posLine(v.fset, stmtStart.Pos()), posLine(v.fset, stmtStart.End()) + + if end > start { // Check only multi line conditions + v.wantNewline[body] = true + } +} + +func posLine(fset *token.FileSet, pos token.Pos) int { + return fset.PositionFor(pos, false).Line +} + +func firstAndLast(comments []*ast.CommentGroup, fset *token.FileSet, stmt *ast.BlockStmt) (token.Pos, ast.Node, ast.Node) { + openingPos := stmt.Lbrace + 1 + + if len(stmt.List) == 0 { + return openingPos, nil, nil + } + + first, last := ast.Node(stmt.List[0]), ast.Node(stmt.List[len(stmt.List)-1]) + + for _, c := range comments { + // If the comment is on the same line as the opening pos (initially the + // left bracket) but it starts after the pos the comment must be after + // the bracket and where that comment ends should be considered where + // the fix should start. + if posLine(fset, c.Pos()) == posLine(fset, openingPos) && c.Pos() > openingPos { + if posLine(fset, c.End()) != posLine(fset, openingPos) { + // This is a multiline comment that spans from the `LBrace` line + // to a line further down. This should always be seen as ok! + first = c + } else { + openingPos = c.End() + } + } + + if posLine(fset, c.Pos()) == posLine(fset, stmt.Pos()) || posLine(fset, c.End()) == posLine(fset, stmt.End()) { + continue + } + + if c.Pos() < stmt.Pos() || c.End() > stmt.End() { + continue + } + + if c.Pos() < first.Pos() { + first = c + } + + if c.End() > last.End() { + last = c + } + } + + return openingPos, first, last +} + +func checkStart(fset *token.FileSet, start token.Pos, first ast.Node) *Message { + if first == nil { + return nil + } + + if posLine(fset, start)+1 < posLine(fset, first.Pos()) { + return &Message{ + Diagnostic: start, + FixStart: start, + FixEnd: first.Pos(), + LineNumbers: linesBetween(fset, start, first.Pos()), + MessageType: MessageTypeRemove, + Message: "unnecessary leading newline", + } + } + + return nil +} + +func checkEnd(fset *token.FileSet, end token.Pos, last ast.Node) *Message { + if last == nil { + return nil + } + + if posLine(fset, end)-1 > posLine(fset, last.End()) { + return &Message{ + Diagnostic: end, + FixStart: last.End(), + FixEnd: end, + LineNumbers: linesBetween(fset, last.End(), end), + MessageType: MessageTypeRemove, + Message: "unnecessary trailing newline", + } + } + + return nil +} + +func linesBetween(fset *token.FileSet, a, b token.Pos) []int { + lines := []int{} + aPosition := fset.PositionFor(a, false) + bPosition := fset.PositionFor(b, false) + + for i := aPosition.Line + 1; i < bPosition.Line; i++ { + lines = append(lines, i) + } + + return lines +} diff --git a/vendor/github.com/ykadowak/zerologlint/.goreleaser.yaml b/vendor/github.com/ykadowak/zerologlint/.goreleaser.yaml index c1b23f00e..f3af3f212 100644 --- a/vendor/github.com/ykadowak/zerologlint/.goreleaser.yaml +++ b/vendor/github.com/ykadowak/zerologlint/.goreleaser.yaml @@ -11,13 +11,6 @@ builds: - linux - windows - darwin -archives: - - replacements: - darwin: Darwin - linux: Linux - windows: Windows - 386: i386 - amd64: x86_64 checksum: name_template: 'checksums.txt' snapshot: diff --git a/vendor/github.com/ykadowak/zerologlint/zerologlint.go b/vendor/github.com/ykadowak/zerologlint/zerologlint.go index bec50e52b..8c8fb74fc 100644 --- a/vendor/github.com/ykadowak/zerologlint/zerologlint.go +++ b/vendor/github.com/ykadowak/zerologlint/zerologlint.go @@ -13,8 +13,8 @@ import ( ) var Analyzer = &analysis.Analyzer{ - Name: "zerologlinter", - Doc: "finds cases where zerolog methods are not followed by Msg or Send", + Name: "zerologlint", + Doc: "Detects the wrong usage of `zerolog` that a user forgets to dispatch with `Send` or `Msg`", Run: run, Requires: []*analysis.Analyzer{ buildssa.Analyzer, @@ -26,42 +26,65 @@ type posser interface { Pos() token.Pos } -// posser is an interface just to hold both ssa.Call and ssa.Defer in our set +// callDefer is an interface just to hold both ssa.Call and ssa.Defer in our set type callDefer interface { Common() *ssa.CallCommon Pos() token.Pos } -func run(pass *analysis.Pass) (interface{}, error) { - srcFuncs := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs - - // This set holds all the ssa block that is a zerolog.Event type instance +type linter struct { + // eventSet holds all the ssa block that is a zerolog.Event type instance // that should be dispatched. // Everytime the zerolog.Event is dispatched with Msg() or Send(), // deletes that block from this set. // At the end, check if the set is empty, or report the not dispatched block. - set := make(map[posser]struct{}) + eventSet map[posser]struct{} + // deleteLater holds the ssa block that should be deleted from eventSet after + // all the inspection is done. + // this is required because `else` ssa block comes after the dispatch of `if`` block. + // e.g., if err != nil { log.Error() } else { log.Info() } log.Send() + // deleteLater takes care of the log.Info() block. + deleteLater map[posser]struct{} + recLimit uint +} + +func run(pass *analysis.Pass) (interface{}, error) { + srcFuncs := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs + + l := &linter{ + eventSet: make(map[posser]struct{}), + deleteLater: make(map[posser]struct{}), + recLimit: 100, + } for _, sf := range srcFuncs { for _, b := range sf.Blocks { for _, instr := range b.Instrs { if c, ok := instr.(*ssa.Call); ok { - inspect(c, &set) + l.inspect(c) } else if c, ok := instr.(*ssa.Defer); ok { - inspect(c, &set) + l.inspect(c) } } } } + + // apply deleteLater to envetSet for else branches of if-else cases + + for k := range l.deleteLater { + delete(l.eventSet, k) + } + // At the end, if the set is clear -> ok. - // Otherwise, there must be a left zerolog.Event var that weren't dispached. So report it. - for k := range set { + // Otherwise, there must be a left zerolog.Event var that weren't dispatched. So report it. + for k := range l.eventSet { pass.Reportf(k.Pos(), "must be dispatched by Msg or Send method") } + return nil, nil } -func inspect(cd callDefer, set *map[posser]struct{}) { +func (l *linter) inspect(cd callDefer) { c := cd.Common() // check if it's in github.com/rs/zerolog/log since there's some @@ -70,7 +93,7 @@ func inspect(cd callDefer, set *map[posser]struct{}) { if isInLogPkg(*c) || isLoggerRecv(*c) { if isZerologEvent(c.Value) { // this ssa block should be dispatched afterwards at some point - (*set)[cd] = struct{}{} + l.eventSet[cd] = struct{}{} return } } @@ -88,7 +111,7 @@ func inspect(cd callDefer, set *map[posser]struct{}) { 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 + // TODO: technically, 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 { @@ -96,10 +119,12 @@ func inspect(cd callDefer, set *map[posser]struct{}) { case *ssa.Call: if inspectDispatchInFunction(v.Common()) { shouldReturn = false + break } case *ssa.Defer: if inspectDispatchInFunction(v.Common()) { shouldReturn = false + break } } } @@ -112,19 +137,56 @@ func inspect(cd callDefer, set *map[posser]struct{}) { } for _, arg := range c.Args { if isZerologEvent(arg) { - val := getRootSsaValue(arg) - // if there's branch, remove both ways from the set - if phi, ok := val.(*ssa.Phi); ok { + // if there's branch, track both ways + // this is for the case like: + // logger := log.Info() + // if err != nil { + // logger = log.Error() + // } + // logger.Send() + // + // Similar case like below goes to the same root but that doesn't + // have any side effect. + // logger := log.Info() + // if err != nil { + // logger = logger.Str("a", "b") + // } + // logger.Send() + if phi, ok := arg.(*ssa.Phi); ok { for _, edge := range phi.Edges { - delete(*set, edge) + l.dfsEdge(edge, make(map[ssa.Value]struct{}), 0) } } else { - delete(*set, val) + val := getRootSsaValue(arg) + delete(l.eventSet, val) } } } } +func (l *linter) dfsEdge(v ssa.Value, visit map[ssa.Value]struct{}, cnt uint) { + // only for safety + if cnt > l.recLimit { + return + } + cnt++ + + if _, ok := visit[v]; ok { + return + } + visit[v] = struct{}{} + + val := getRootSsaValue(v) + phi, ok := val.(*ssa.Phi) + if !ok { + l.deleteLater[val] = struct{}{} + return + } + for _, edge := range phi.Edges { + l.dfsEdge(edge, visit, cnt) + } +} + func inspectDispatchInFunction(cc *ssa.CallCommon) bool { if isDispatchMethod(cc.StaticCallee()) { for _, arg := range cc.Args { diff --git a/vendor/go.tmz.dev/musttag/.golangci.yml b/vendor/go-simpler.org/musttag/.golangci.yml index 641471cc4..641471cc4 100644 --- a/vendor/go.tmz.dev/musttag/.golangci.yml +++ b/vendor/go-simpler.org/musttag/.golangci.yml diff --git a/vendor/go.tmz.dev/musttag/.goreleaser.yml b/vendor/go-simpler.org/musttag/.goreleaser.yml index 259080886..dd75ac945 100644 --- a/vendor/go.tmz.dev/musttag/.goreleaser.yml +++ b/vendor/go-simpler.org/musttag/.goreleaser.yml @@ -16,12 +16,3 @@ archives: - format_overrides: - goos: windows format: zip - -brews: - - tap: - owner: tmzane - name: homebrew-tap - token: '{{ .Env.HOMEBREW_TAP_TOKEN }}' - homepage: https://github.com/tmzane/musttag - description: A Go linter that enforces field tags in (un)marshaled structs - license: MPL-2.0 diff --git a/vendor/go.tmz.dev/musttag/LICENSE b/vendor/go-simpler.org/musttag/LICENSE index a612ad981..a612ad981 100644 --- a/vendor/go.tmz.dev/musttag/LICENSE +++ b/vendor/go-simpler.org/musttag/LICENSE diff --git a/vendor/go-simpler.org/musttag/README.md b/vendor/go-simpler.org/musttag/README.md new file mode 100644 index 000000000..54c2744cc --- /dev/null +++ b/vendor/go-simpler.org/musttag/README.md @@ -0,0 +1,102 @@ +# musttag + +[](https://github.com/go-simpler/musttag/actions/workflows/checks.yml) +[](https://pkg.go.dev/go-simpler.org/musttag) +[](https://goreportcard.com/report/go-simpler.org/musttag) +[](https://codecov.io/gh/go-simpler/musttag) + +A Go linter that enforces field tags in (un)marshaled structs. + +## 📌 About + +`musttag` checks that exported fields of a struct passed to a `Marshal`-like function are annotated with the relevant tag: + +```go +// BAD: +var user struct { + Name string +} +data, err := json.Marshal(user) + +// GOOD: +var user struct { + Name string `json:"name"` +} +data, err := json.Marshal(user) +``` + +The rational from [Uber Style Guide][1]: + +> The serialized form of the structure is a contract between different systems. +> Changes to the structure of the serialized form, including field names, break this contract. +> Specifying field names inside tags makes the contract explicit, +> and it guards against accidentally breaking the contract by refactoring or renaming fields. + +## 🚀 Features + +The following packages are supported out of the box: + +* [encoding/json][2] +* [encoding/xml][3] +* [gopkg.in/yaml.v3][4] +* [github.com/BurntSushi/toml][5] +* [github.com/mitchellh/mapstructure][6] +* [github.com/jmoiron/sqlx][7] + +In addition, any [custom package](#custom-packages) can be added to the list. + +## 📦 Install + +`musttag` is integrated into [`golangci-lint`][8], and this is the recommended way to use it. + +To enable the linter, add the following lines to `.golangci.yml`: + +```yaml +linters: + enable: + - musttag +``` + +Alternatively, you can download a prebuilt binary from the [Releases][9] page to use `musttag` standalone. + +## 📋 Usage + +Run `golangci-lint` with `musttag` enabled. +See the list of [available options][10] to configure the linter. + +When using `sloglint` standalone, pass the options as flags. + +### Custom packages + +To report a custom function, you need to add its description to `.golangci.yml`. +The following is an example of adding support for [`hclsimple.Decode`][11]: + +```yaml +linters-settings: + musttag: + functions: + # The full name of the function, including the package. + - name: github.com/hashicorp/hcl/v2/hclsimple.Decode + # The struct tag whose presence should be ensured. + tag: hcl + # The position of the argument to check. + arg-pos: 2 +``` + +The same can be done via the `-fn=<name:tag:arg-pos>` flag when using `musttag` standalone: + +```shell +musttag -fn="github.com/hashicorp/hcl/v2/hclsimple.DecodeFile:hcl:2" ./... +``` + +[1]: https://github.com/uber-go/guide/blob/master/style.md#use-field-tags-in-marshaled-structs +[2]: https://pkg.go.dev/encoding/json +[3]: https://pkg.go.dev/encoding/xml +[4]: https://pkg.go.dev/gopkg.in/yaml.v3 +[5]: https://pkg.go.dev/github.com/BurntSushi/toml +[6]: https://pkg.go.dev/github.com/mitchellh/mapstructure +[7]: https://pkg.go.dev/github.com/jmoiron/sqlx +[8]: https://golangci-lint.run +[9]: https://github.com/go-simpler/musttag/releases +[10]: https://golangci-lint.run/usage/linters/#musttag +[11]: https://pkg.go.dev/github.com/hashicorp/hcl/v2/hclsimple#Decode diff --git a/vendor/go-simpler.org/musttag/builtins.go b/vendor/go-simpler.org/musttag/builtins.go new file mode 100644 index 000000000..3305513f8 --- /dev/null +++ b/vendor/go-simpler.org/musttag/builtins.go @@ -0,0 +1,133 @@ +package musttag + +// builtins is a set of functions supported out of the box. +var builtins = []Func{ + // https://pkg.go.dev/encoding/json + { + Name: "encoding/json.Marshal", Tag: "json", ArgPos: 0, + ifaceWhitelist: []string{"encoding/json.Marshaler", "encoding.TextMarshaler"}, + }, + { + Name: "encoding/json.MarshalIndent", Tag: "json", ArgPos: 0, + ifaceWhitelist: []string{"encoding/json.Marshaler", "encoding.TextMarshaler"}, + }, + { + Name: "encoding/json.Unmarshal", Tag: "json", ArgPos: 1, + ifaceWhitelist: []string{"encoding/json.Unmarshaler", "encoding.TextUnmarshaler"}, + }, + { + Name: "(*encoding/json.Encoder).Encode", Tag: "json", ArgPos: 0, + ifaceWhitelist: []string{"encoding/json.Marshaler", "encoding.TextMarshaler"}, + }, + { + Name: "(*encoding/json.Decoder).Decode", Tag: "json", ArgPos: 0, + ifaceWhitelist: []string{"encoding/json.Unmarshaler", "encoding.TextUnmarshaler"}, + }, + + // https://pkg.go.dev/encoding/xml + { + Name: "encoding/xml.Marshal", Tag: "xml", ArgPos: 0, + ifaceWhitelist: []string{"encoding/xml.Marshaler", "encoding.TextMarshaler"}, + }, + { + Name: "encoding/xml.MarshalIndent", Tag: "xml", ArgPos: 0, + ifaceWhitelist: []string{"encoding/xml.Marshaler", "encoding.TextMarshaler"}, + }, + { + Name: "encoding/xml.Unmarshal", Tag: "xml", ArgPos: 1, + ifaceWhitelist: []string{"encoding/xml.Unmarshaler", "encoding.TextUnmarshaler"}, + }, + { + Name: "(*encoding/xml.Encoder).Encode", Tag: "xml", ArgPos: 0, + ifaceWhitelist: []string{"encoding/xml.Marshaler", "encoding.TextMarshaler"}, + }, + { + Name: "(*encoding/xml.Decoder).Decode", Tag: "xml", ArgPos: 0, + ifaceWhitelist: []string{"encoding/xml.Unmarshaler", "encoding.TextUnmarshaler"}, + }, + { + Name: "(*encoding/xml.Encoder).EncodeElement", Tag: "xml", ArgPos: 0, + ifaceWhitelist: []string{"encoding/xml.Marshaler", "encoding.TextMarshaler"}, + }, + { + Name: "(*encoding/xml.Decoder).DecodeElement", Tag: "xml", ArgPos: 0, + ifaceWhitelist: []string{"encoding/xml.Unmarshaler", "encoding.TextUnmarshaler"}, + }, + + // https://pkg.go.dev/gopkg.in/yaml.v3 + { + Name: "gopkg.in/yaml.v3.Marshal", Tag: "yaml", ArgPos: 0, + ifaceWhitelist: []string{"gopkg.in/yaml.v3.Marshaler"}, + }, + { + Name: "gopkg.in/yaml.v3.Unmarshal", Tag: "yaml", ArgPos: 1, + ifaceWhitelist: []string{"gopkg.in/yaml.v3.Unmarshaler"}, + }, + { + Name: "(*gopkg.in/yaml.v3.Encoder).Encode", Tag: "yaml", ArgPos: 0, + ifaceWhitelist: []string{"gopkg.in/yaml.v3.Marshaler"}, + }, + { + Name: "(*gopkg.in/yaml.v3.Decoder).Decode", Tag: "yaml", ArgPos: 0, + ifaceWhitelist: []string{"gopkg.in/yaml.v3.Unmarshaler"}, + }, + + // https://pkg.go.dev/github.com/BurntSushi/toml + { + Name: "github.com/BurntSushi/toml.Unmarshal", Tag: "toml", ArgPos: 1, + ifaceWhitelist: []string{"github.com/BurntSushi/toml.Unmarshaler", "encoding.TextUnmarshaler"}, + }, + { + Name: "github.com/BurntSushi/toml.Decode", Tag: "toml", ArgPos: 1, + ifaceWhitelist: []string{"github.com/BurntSushi/toml.Unmarshaler", "encoding.TextUnmarshaler"}, + }, + { + Name: "github.com/BurntSushi/toml.DecodeFS", Tag: "toml", ArgPos: 2, + ifaceWhitelist: []string{"github.com/BurntSushi/toml.Unmarshaler", "encoding.TextUnmarshaler"}, + }, + { + Name: "github.com/BurntSushi/toml.DecodeFile", Tag: "toml", ArgPos: 1, + ifaceWhitelist: []string{"github.com/BurntSushi/toml.Unmarshaler", "encoding.TextUnmarshaler"}, + }, + { + Name: "(*github.com/BurntSushi/toml.Encoder).Encode", Tag: "toml", ArgPos: 0, + ifaceWhitelist: []string{"encoding.TextMarshaler"}, + }, + { + Name: "(*github.com/BurntSushi/toml.Decoder).Decode", Tag: "toml", ArgPos: 0, + ifaceWhitelist: []string{"github.com/BurntSushi/toml.Unmarshaler", "encoding.TextUnmarshaler"}, + }, + + // https://pkg.go.dev/github.com/mitchellh/mapstructure + {Name: "github.com/mitchellh/mapstructure.Decode", Tag: "mapstructure", ArgPos: 1}, + {Name: "github.com/mitchellh/mapstructure.DecodeMetadata", Tag: "mapstructure", ArgPos: 1}, + {Name: "github.com/mitchellh/mapstructure.WeakDecode", Tag: "mapstructure", ArgPos: 1}, + {Name: "github.com/mitchellh/mapstructure.WeakDecodeMetadata", Tag: "mapstructure", ArgPos: 1}, + + // https://pkg.go.dev/github.com/jmoiron/sqlx + {Name: "github.com/jmoiron/sqlx.Get", Tag: "db", ArgPos: 1}, + {Name: "github.com/jmoiron/sqlx.GetContext", Tag: "db", ArgPos: 2}, + {Name: "github.com/jmoiron/sqlx.Select", Tag: "db", ArgPos: 1}, + {Name: "github.com/jmoiron/sqlx.SelectContext", Tag: "db", ArgPos: 2}, + {Name: "github.com/jmoiron/sqlx.StructScan", Tag: "db", ArgPos: 1}, + {Name: "(*github.com/jmoiron/sqlx.Conn).GetContext", Tag: "db", ArgPos: 1}, + {Name: "(*github.com/jmoiron/sqlx.Conn).SelectContext", Tag: "db", ArgPos: 1}, + {Name: "(*github.com/jmoiron/sqlx.DB).Get", Tag: "db", ArgPos: 0}, + {Name: "(*github.com/jmoiron/sqlx.DB).GetContext", Tag: "db", ArgPos: 1}, + {Name: "(*github.com/jmoiron/sqlx.DB).Select", Tag: "db", ArgPos: 0}, + {Name: "(*github.com/jmoiron/sqlx.DB).SelectContext", Tag: "db", ArgPos: 1}, + {Name: "(*github.com/jmoiron/sqlx.NamedStmt).Get", Tag: "db", ArgPos: 0}, + {Name: "(*github.com/jmoiron/sqlx.NamedStmt).GetContext", Tag: "db", ArgPos: 1}, + {Name: "(*github.com/jmoiron/sqlx.NamedStmt).Select", Tag: "db", ArgPos: 0}, + {Name: "(*github.com/jmoiron/sqlx.NamedStmt).SelectContext", Tag: "db", ArgPos: 1}, + {Name: "(*github.com/jmoiron/sqlx.Row).StructScan", Tag: "db", ArgPos: 0}, + {Name: "(*github.com/jmoiron/sqlx.Rows).StructScan", Tag: "db", ArgPos: 0}, + {Name: "(*github.com/jmoiron/sqlx.Stmt).Get", Tag: "db", ArgPos: 0}, + {Name: "(*github.com/jmoiron/sqlx.Stmt).GetContext", Tag: "db", ArgPos: 1}, + {Name: "(*github.com/jmoiron/sqlx.Stmt).Select", Tag: "db", ArgPos: 0}, + {Name: "(*github.com/jmoiron/sqlx.Stmt).SelectContext", Tag: "db", ArgPos: 1}, + {Name: "(*github.com/jmoiron/sqlx.Tx).Get", Tag: "db", ArgPos: 0}, + {Name: "(*github.com/jmoiron/sqlx.Tx).GetContext", Tag: "db", ArgPos: 1}, + {Name: "(*github.com/jmoiron/sqlx.Tx).Select", Tag: "db", ArgPos: 0}, + {Name: "(*github.com/jmoiron/sqlx.Tx).SelectContext", Tag: "db", ArgPos: 1}, +} diff --git a/vendor/go-simpler.org/musttag/musttag.go b/vendor/go-simpler.org/musttag/musttag.go new file mode 100644 index 000000000..b7e41f3d3 --- /dev/null +++ b/vendor/go-simpler.org/musttag/musttag.go @@ -0,0 +1,262 @@ +// Package musttag implements the musttag analyzer. +package musttag + +import ( + "flag" + "fmt" + "go/ast" + "go/types" + "reflect" + "strconv" + "strings" + + "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" +) + +// Func describes a function call to look for, e.g. [json.Marshal]. +type Func struct { + Name string // The full name of the function, including the package. + Tag string // The struct tag whose presence should be ensured. + ArgPos int // The position of the argument to check. + + // a list of interface names (including the package); + // if at least one is implemented by the argument, no check is performed. + ifaceWhitelist []string +} + +// New creates a new musttag analyzer. +// To report a custom function, provide its description as [Func]. +func New(funcs ...Func) *analysis.Analyzer { + var flagFuncs []Func + return &analysis.Analyzer{ + Name: "musttag", + Doc: "enforce field tags in (un)marshaled structs", + Flags: flags(&flagFuncs), + Requires: []*analysis.Analyzer{inspect.Analyzer}, + Run: func(pass *analysis.Pass) (any, error) { + l := len(builtins) + len(funcs) + len(flagFuncs) + allFuncs := make(map[string]Func, l) + + merge := func(slice []Func) { + for _, fn := range slice { + allFuncs[fn.Name] = fn + } + } + merge(builtins) + merge(funcs) + merge(flagFuncs) + + mainModule, err := getMainModule() + if err != nil { + return nil, err + } + + return run(pass, mainModule, allFuncs) + }, + } +} + +func flags(funcs *[]Func) flag.FlagSet { + fs := flag.NewFlagSet("musttag", flag.ContinueOnError) + fs.Func("fn", "report a custom function (name:tag:arg-pos)", func(s string) error { + parts := strings.Split(s, ":") + if len(parts) != 3 || parts[0] == "" || parts[1] == "" { + return strconv.ErrSyntax + } + pos, err := strconv.Atoi(parts[2]) + if err != nil { + return err + } + *funcs = append(*funcs, Func{ + Name: parts[0], + Tag: parts[1], + ArgPos: pos, + }) + return nil + }) + return *fs +} + +func run(pass *analysis.Pass, mainModule string, funcs map[string]Func) (_ any, err error) { + visit := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + filter := []ast.Node{(*ast.CallExpr)(nil)} + + visit.Preorder(filter, func(node ast.Node) { + if err != nil { + return // there is already an error. + } + + call, ok := node.(*ast.CallExpr) + if !ok { + return // not a function call. + } + + callee := typeutil.StaticCallee(pass.TypesInfo, call) + if callee == nil { + return // not a static call. + } + + fn, ok := funcs[cutVendor(callee.FullName())] + if !ok { + return // unsupported function. + } + + if len(call.Args) <= fn.ArgPos { + err = fmt.Errorf("musttag: Func.ArgPos cannot be %d: %s accepts only %d argument(s)", fn.ArgPos, fn.Name, len(call.Args)) + return + } + + arg := call.Args[fn.ArgPos] + if ident, ok := arg.(*ast.Ident); ok && ident.Obj == nil { + return // e.g. json.Marshal(nil) + } + + typ := pass.TypesInfo.TypeOf(arg) + if typ == nil { + return // no type info found. + } + + // TODO: check nested structs too. + if implementsInterface(typ, fn.ifaceWhitelist, pass.Pkg.Imports()) { + return // the type implements a Marshaler interface; see issue #64. + } + + checker := checker{ + mainModule: mainModule, + seenTypes: make(map[string]struct{}), + } + + styp, ok := checker.parseStruct(typ) + if !ok { + return // not a struct. + } + + if valid := checker.checkStruct(styp, fn.Tag); valid { + return // nothing to report. + } + + pass.Reportf(arg.Pos(), "the given struct should be annotated with the `%s` tag", fn.Tag) + }) + + return nil, err +} + +type checker struct { + mainModule string + seenTypes map[string]struct{} +} + +func (c *checker) parseStruct(typ types.Type) (*types.Struct, bool) { + for { + // unwrap pointers (if any) first. + ptr, ok := typ.(*types.Pointer) + if !ok { + break + } + typ = ptr.Elem() + } + + switch typ := typ.(type) { + case *types.Named: // a struct of the named type. + pkg := typ.Obj().Pkg() + if pkg == nil { + return nil, false + } + if !strings.HasPrefix(pkg.Path(), c.mainModule) { + return nil, false + } + styp, ok := typ.Underlying().(*types.Struct) + if !ok { + return nil, false + } + return styp, true + + case *types.Struct: // an anonymous struct. + return typ, true + + default: + return nil, false + } +} + +func (c *checker) checkStruct(styp *types.Struct, tag string) (valid bool) { + c.seenTypes[styp.String()] = struct{}{} + + for i := 0; i < styp.NumFields(); i++ { + field := styp.Field(i) + if !field.Exported() { + continue + } + + if _, ok := reflect.StructTag(styp.Tag(i)).Lookup(tag); !ok { + // tag is not required for embedded types; see issue #12. + if !field.Embedded() { + return false + } + } + + nested, ok := c.parseStruct(field.Type()) + if !ok { + continue + } + if _, ok := c.seenTypes[nested.String()]; ok { + continue + } + if valid := c.checkStruct(nested, tag); !valid { + return false + } + } + + return true +} + +func implementsInterface(typ types.Type, ifaces []string, imports []*types.Package) bool { + findScope := func(pkgName string) (*types.Scope, bool) { + // fast path: check direct imports (e.g. looking for "encoding/json.Marshaler"). + for _, direct := range imports { + if pkgName == cutVendor(direct.Path()) { + return direct.Scope(), true + } + } + // slow path: check indirect imports (e.g. looking for "encoding.TextMarshaler"). + // TODO: only check indirect imports from the package (e.g. "encoding/json") of the analyzed function (e.g. "encoding/json.Marshal"). + for _, direct := range imports { + for _, indirect := range direct.Imports() { + if pkgName == cutVendor(indirect.Path()) { + return indirect.Scope(), true + } + } + } + return nil, false + } + + for _, ifacePath := range ifaces { + // "encoding/json.Marshaler" -> "encoding/json" + "Marshaler" + idx := strings.LastIndex(ifacePath, ".") + if idx == -1 { + continue + } + pkgName, ifaceName := ifacePath[:idx], ifacePath[idx+1:] + + scope, ok := findScope(pkgName) + if !ok { + continue + } + obj := scope.Lookup(ifaceName) + if obj == nil { + continue + } + iface, ok := obj.Type().Underlying().(*types.Interface) + if !ok { + continue + } + if types.Implements(typ, iface) { + return true + } + } + + return false +} diff --git a/vendor/go-simpler.org/musttag/utils.go b/vendor/go-simpler.org/musttag/utils.go new file mode 100644 index 000000000..1a13f96c2 --- /dev/null +++ b/vendor/go-simpler.org/musttag/utils.go @@ -0,0 +1,49 @@ +package musttag + +import ( + "encoding/json" + "fmt" + "os/exec" + "strings" +) + +func getMainModule() (string, error) { + args := [...]string{"go", "mod", "edit", "-json"} + + out, err := exec.Command(args[0], args[1:]...).Output() + if err != nil { + return "", fmt.Errorf("running %q: %w", strings.Join(args[:], " "), err) + } + + var info struct { + Module struct { + Path string `json:"Path"` + } `json:"Module"` + } + if err := json.Unmarshal(out, &info); err != nil { + return "", fmt.Errorf("decoding module info: %w\n%s", err, out) + } + + return info.Module.Path, nil +} + +// based on golang.org/x/tools/imports.VendorlessPath +func cutVendor(path string) string { + var prefix string + + switch { + case strings.HasPrefix(path, "(*"): + prefix, path = "(*", path[len("(*"):] + case strings.HasPrefix(path, "("): + prefix, path = "(", path[len("("):] + } + + if i := strings.LastIndex(path, "/vendor/"); i >= 0 { + return prefix + path[i+len("/vendor/"):] + } + if strings.HasPrefix(path, "vendor/") { + return prefix + path[len("vendor/"):] + } + + return prefix + path +} diff --git a/vendor/go-simpler.org/sloglint/README.md b/vendor/go-simpler.org/sloglint/README.md index 8f26f3aa7..fc4d9debb 100644 --- a/vendor/go-simpler.org/sloglint/README.md +++ b/vendor/go-simpler.org/sloglint/README.md @@ -10,36 +10,53 @@ 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. +While people may have different opinions about which one is better, most seem to agree on one thing: it should be consistent. +With `sloglint` you can enforce various rules for `log/slog` based on your preferred 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 not mixing key-value pairs and attributes (default) +* Enforce using either key-value pairs only or attributes only (optional) +* Enforce using methods that accept a context (optional) +* Enforce using static log messages (optional) * Enforce using constants instead of raw keys (optional) +* Enforce a single key naming convention (optional) * Enforce putting arguments on separate lines (optional) ## 📦 Install -Download a prebuilt binary from the [Releases][1] page. +`sloglint` is integrated into [`golangci-lint`][1], and this is the recommended way to use it. + +To enable the linter, add the following lines to `.golangci.yml`: + +```yaml +linters: + enable: + - sloglint +``` + +Alternatively, you can download a prebuilt binary from the [Releases][2] page to use `sloglint` standalone. ## 📋 Usage -```shell -sloglint [flags] ./... +Run `golangci-lint` with `sloglint` enabled. +See the list of [available options][3] to configure the linter. + +When using `sloglint` standalone, pass the options as flags of the same name. + +### No mixed arguments + +The `no-mixed-args` option causes `sloglint` to report mixing key-values pairs and attributes within a single function call: + +```go +slog.Info("a user has logged in", "user_id", 42, slog.String("ip_address", "192.0.2.0")) // sloglint: key-value pairs and attributes should not be mixed ``` +It is enabled by default. + ### Key-value pairs only -The `-kv-only` flag causes `sloglint` to report any use of attributes. +The `kv-only` option 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 @@ -47,16 +64,48 @@ slog.Info("a user has logged in", slog.Int("user_id", 42)) // sloglint: attribut ### Attributes only -In contrast, the `-attr-only` flag causes `sloglint` to report any use of key-value pairs. +In contrast, the `attr-only` option 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 ``` +### Context only + +Some `slog.Handler` implementations make use of the given `context.Context` (e.g. to access context values). +For them to work properly, you need to pass a context to all logger calls. +The `context-only` option causes `sloglint` to report the use of methods without a context: + +```go +slog.Info("a user has logged in") // sloglint: methods without a context should not be used +``` + +This report can be fixed by using the equivalent method with the `Context` suffix: + +```go +slog.InfoContext(ctx, "a user has logged in") +``` + +### Static messages + +To get the most out of structured logging, you may want to require log messages to be static. +The `static-msg` option causes `sloglint` to report non-static messages: + +```go +slog.Info(fmt.Sprintf("a user with id %d has logged in", 42)) // sloglint: message should be a string literal or a constant +``` + +The report can be fixed by moving dynamic values to arguments: + +```go +slog.Info("a user has logged in", "user_id", 42) +``` + ### 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)`). +The `no-raw-keys` option 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 @@ -70,7 +119,7 @@ const UserId = "user_id" slog.Info("a user has logged in", UserId, 42) ``` -...or custom `slog.Attr` constructors. +...or custom `slog.Attr` constructors: ```go func UserId(value int) slog.Attr { return slog.Int("user_id", value) } @@ -78,18 +127,30 @@ 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! +> [!TIP] +> Such helpers can be automatically generated for you by the [`sloggen`][4] tool. Give it a try too! + +### Key naming convention + +To ensure consistency in logs, you may want to enforce a single key naming convention. +The `key-naming-case` option causes `sloglint` to report keys written in a case other than the given one: + +```go +slog.Info("a user has logged in", "user-id", 42) // sloglint: keys should be written in snake_case +``` + +Possible values are `snake`, `kebab`, `camel`, or `pascal`. ### 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. +The `args-on-sep-lines` option 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. +This report can be fixed by reformatting the code: ```go slog.Info("a user has logged in", @@ -98,5 +159,7 @@ slog.Info("a user has logged in", ) ``` -[1]: https://github.com/go-simpler/sloglint/releases -[2]: https://github.com/go-simpler/sloggen +[1]: https://golangci-lint.run +[2]: https://github.com/go-simpler/sloglint/releases +[3]: https://golangci-lint.run/usage/linters/#sloglint +[4]: https://github.com/go-simpler/sloggen diff --git a/vendor/go-simpler.org/sloglint/sloglint.go b/vendor/go-simpler.org/sloglint/sloglint.go index 4e4520698..b7f72ce48 100644 --- a/vendor/go-simpler.org/sloglint/sloglint.go +++ b/vendor/go-simpler.org/sloglint/sloglint.go @@ -4,11 +4,13 @@ package sloglint import ( "errors" "flag" + "fmt" "go/ast" "go/token" "go/types" "strconv" + "github.com/ettle/strcase" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/ast/inspector" @@ -17,16 +19,20 @@ import ( // 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. + NoMixedArgs bool // Enforce not mixing key-value pairs and attributes (default true). + KVOnly bool // Enforce using key-value pairs only (overrides NoMixedArgs, incompatible with AttrOnly). + AttrOnly bool // Enforce using attributes only (overrides NoMixedArgs, incompatible with KVOnly). + ContextOnly bool // Enforce using methods that accept a context. + StaticMsg bool // Enforce using static log messages. + NoRawKeys bool // Enforce using constants instead of raw keys. + KeyNamingCase string // Enforce a single key naming convention ("snake", "kebab", "camel", or "pascal"). + 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) + opts = &Options{NoMixedArgs: true} } return &analysis.Analyzer{ Name: "sloglint", @@ -35,7 +41,12 @@ func New(opts *Options) *analysis.Analyzer { 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") + return nil, fmt.Errorf("sloglint: Options.KVOnly and Options.AttrOnly: %w", errIncompatible) + } + switch opts.KeyNamingCase { + case "", snakeCase, kebabCase, camelCase, pascalCase: + default: + return nil, fmt.Errorf("sloglint: Options.KeyNamingCase=%s: %w", opts.KeyNamingCase, errInvalidValue) } run(pass, opts) return nil, nil @@ -43,6 +54,11 @@ func New(opts *Options) *analysis.Analyzer { } } +var ( + errIncompatible = errors.New("incompatible options") + errInvalidValue = errors.New("invalid value") +) + func flags(opts *Options) flag.FlagSet { fs := flag.NewFlagSet("sloglint", flag.ContinueOnError) @@ -54,11 +70,19 @@ func flags(opts *Options) flag.FlagSet { }) } - 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.NoMixedArgs, "no-mixed-args", "enforce not mixing key-value pairs and attributes (default true)") + boolVar(&opts.KVOnly, "kv-only", "enforce using key-value pairs only (overrides -no-mixed-args, incompatible with -attr-only)") + boolVar(&opts.AttrOnly, "attr-only", "enforce using attributes only (overrides -no-mixed-args, incompatible with -kv-only)") + boolVar(&opts.ContextOnly, "context-only", "enforce using methods that accept a context") + boolVar(&opts.StaticMsg, "static-msg", "enforce using static log messages") 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") + fs.Func("key-naming-case", "enforce a single key naming convention (snake|kebab|camel|pascal)", func(s string) error { + opts.KeyNamingCase = s + return nil + }) + return *fs } @@ -96,6 +120,13 @@ var attrFuncs = map[string]struct{}{ "log/slog.Any": {}, } +const ( + snakeCase = "snake" + kebabCase = "kebab" + camelCase = "camel" + pascalCase = "pascal" +) + func run(pass *analysis.Pass, opts *Options) { visit := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) filter := []ast.Node{(*ast.CallExpr)(nil)} @@ -113,6 +144,16 @@ func run(pass *analysis.Pass, opts *Options) { return } + if opts.ContextOnly { + typ := pass.TypesInfo.TypeOf(call.Args[0]) + if typ != nil && typ.String() != "context.Context" { + pass.Reportf(call.Pos(), "methods without a context should not be used") + } + } + if opts.StaticMsg && !staticMsg(call.Args[argsPos-1]) { + pass.Reportf(call.Pos(), "message should be a string literal or a constant") + } + // NOTE: we assume that the arguments have already been validated by govet. args := call.Args[argsPos:] if len(args) == 0 { @@ -141,7 +182,7 @@ func run(pass *analysis.Pass, opts *Options) { 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): + case opts.NoMixedArgs && 0 < len(attrs) && len(attrs) < len(args): pass.Reportf(call.Pos(), "key-value pairs and attributes should not be mixed") } @@ -151,9 +192,31 @@ func run(pass *analysis.Pass, opts *Options) { if opts.ArgsOnSepLines && argsOnSameLine(pass.Fset, call, keys, attrs) { pass.Reportf(call.Pos(), "arguments should be put on separate lines") } + + switch { + case opts.KeyNamingCase == snakeCase && badKeyNames(pass.TypesInfo, strcase.ToSnake, keys, attrs): + pass.Reportf(call.Pos(), "keys should be written in snake_case") + case opts.KeyNamingCase == kebabCase && badKeyNames(pass.TypesInfo, strcase.ToKebab, keys, attrs): + pass.Reportf(call.Pos(), "keys should be written in kebab-case") + case opts.KeyNamingCase == camelCase && badKeyNames(pass.TypesInfo, strcase.ToCamel, keys, attrs): + pass.Reportf(call.Pos(), "keys should be written in camelCase") + case opts.KeyNamingCase == pascalCase && badKeyNames(pass.TypesInfo, strcase.ToPascal, keys, attrs): + pass.Reportf(call.Pos(), "keys should be written in PascalCase") + } }) } +func staticMsg(expr ast.Expr) bool { + switch msg := expr.(type) { + case *ast.BasicLit: // e.g. slog.Info("msg") + return msg.Kind == token.STRING + case *ast.Ident: // e.g. const msg = "msg"; slog.Info(msg) + return msg.Obj != nil && msg.Obj.Kind == ast.Con + default: + return false + } +} + func rawKeysUsed(info *types.Info, keys, attrs []ast.Expr) bool { isConst := func(expr ast.Expr) bool { ident, ok := expr.(*ast.Ident) @@ -202,6 +265,64 @@ func rawKeysUsed(info *types.Info, keys, attrs []ast.Expr) bool { return false } +func badKeyNames(info *types.Info, caseFn func(string) string, keys, attrs []ast.Expr) bool { + for _, key := range keys { + if name, ok := getKeyName(key); ok && name != caseFn(name) { + return true + } + } + + for _, attr := range attrs { + var expr ast.Expr + switch attr := attr.(type) { + case *ast.CallExpr: // e.g. slog.Int() + fn := typeutil.StaticCallee(info, attr) + if _, ok := attrFuncs[fn.FullName()]; ok { + expr = attr.Args[0] + } + case *ast.CompositeLit: // slog.Attr{} + switch len(attr.Elts) { + case 1: // slog.Attr{Key: ...} | slog.Attr{Value: ...} + if kv := attr.Elts[0].(*ast.KeyValueExpr); kv.Key.(*ast.Ident).Name == "Key" { + expr = kv.Value + } + case 2: // slog.Attr{..., ...} | slog.Attr{Key: ..., Value: ...} + expr = attr.Elts[0] + if kv1, ok := attr.Elts[0].(*ast.KeyValueExpr); ok && kv1.Key.(*ast.Ident).Name == "Key" { + expr = kv1.Value + } + if kv2, ok := attr.Elts[1].(*ast.KeyValueExpr); ok && kv2.Key.(*ast.Ident).Name == "Key" { + expr = kv2.Value + } + } + } + if name, ok := getKeyName(expr); ok && name != caseFn(name) { + return true + } + } + + return false +} + +func getKeyName(expr ast.Expr) (string, bool) { + if expr == nil { + return "", false + } + if ident, ok := expr.(*ast.Ident); ok { + if ident.Obj == nil || ident.Obj.Decl == nil || ident.Obj.Kind != ast.Con { + return "", false + } + if spec, ok := ident.Obj.Decl.(*ast.ValueSpec); ok && len(spec.Values) > 0 { + // TODO: support len(spec.Values) > 1; e.g. "const foo, bar = 1, 2" + expr = spec.Values[0] + } + } + if lit, ok := expr.(*ast.BasicLit); ok && lit.Kind == token.STRING { + return lit.Value, 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. diff --git a/vendor/go.tmz.dev/musttag/README.md b/vendor/go.tmz.dev/musttag/README.md deleted file mode 100644 index 621c7a9a3..000000000 --- a/vendor/go.tmz.dev/musttag/README.md +++ /dev/null @@ -1,106 +0,0 @@ -# musttag - -[](https://github.com/tmzane/musttag/actions/workflows/checks.yml) -[](https://pkg.go.dev/go.tmz.dev/musttag) -[](https://goreportcard.com/report/go.tmz.dev/musttag) -[](https://codecov.io/gh/tmzane/musttag) - -A Go linter that enforces field tags in (un)marshaled structs - -## 📌 About - -`musttag` checks that exported fields of a struct passed to a `Marshal`-like function are annotated with the relevant tag: - -```go -// BAD: -var user struct { - Name string -} -data, err := json.Marshal(user) - -// GOOD: -var user struct { - Name string `json:"name"` -} -data, err := json.Marshal(user) -``` - -The rational from [Uber Style Guide][1]: - -> The serialized form of the structure is a contract between different systems. -> Changes to the structure of the serialized form, including field names, break this contract. -> Specifying field names inside tags makes the contract explicit, -> and it guards against accidentally breaking the contract by refactoring or renaming fields. - -## 🚀 Features - -The following packages are supported out of the box: - -* [`encoding/json`][2] -* [`encoding/xml`][3] -* [`gopkg.in/yaml.v3`][4] -* [`github.com/BurntSushi/toml`][5] -* [`github.com/mitchellh/mapstructure`][6] -* [`github.com/jmoiron/sqlx`][7] - -In addition, any [custom package](#custom-packages) can be added to the list. - -## 📋 Usage - -`musttag` is already integrated into `golangci-lint`, and this is the recommended way to use it. - -To enable the linter, add the following lines to `.golangci.yml`: - -```yaml -linters: - enable: - - musttag -``` - -If you'd rather prefer to use `musttag` standalone, you can install it via `brew`... - -```shell -brew install tmzane/tap/musttag -``` - -...or download a prebuilt binary from the [Releases][9] page. - -Then run it either directly or as a `go vet` tool: - -```shell -go vet -vettool=$(which musttag) ./... -``` - -### Custom packages - -To enable reporting a custom function, you need to add its description to `.golangci.yml`. - -The following is an example of adding support for the `hclsimple.DecodeFile` function from [`github.com/hashicorp/hcl`][8]: - -```yaml -linters-settings: - musttag: - functions: - # The full name of the function, including the package. - - name: github.com/hashicorp/hcl/v2/hclsimple.DecodeFile - # The struct tag whose presence should be ensured. - tag: hcl - # The position of the argument to check. - arg-pos: 2 -``` - -The same can be done via the `-fn=name:tag:arg-pos` flag when using `musttag` standalone: - -```shell -musttag -fn="github.com/hashicorp/hcl/v2/hclsimple.DecodeFile:hcl:2" ./... -``` - -[1]: https://github.com/uber-go/guide/blob/master/style.md#use-field-tags-in-marshaled-structs -[2]: https://pkg.go.dev/encoding/json -[3]: https://pkg.go.dev/encoding/xml -[4]: https://github.com/go-yaml/yaml -[5]: https://github.com/BurntSushi/toml -[6]: https://github.com/mitchellh/mapstructure -[7]: https://github.com/jmoiron/sqlx -[8]: https://github.com/hashicorp/hcl -[9]: https://github.com/tmzane/musttag/releases diff --git a/vendor/go.tmz.dev/musttag/builtins.go b/vendor/go.tmz.dev/musttag/builtins.go deleted file mode 100644 index 66914fa9e..000000000 --- a/vendor/go.tmz.dev/musttag/builtins.go +++ /dev/null @@ -1,67 +0,0 @@ -package musttag - -// builtins is a set of functions supported out of the box. -var builtins = []Func{ - // https://pkg.go.dev/encoding/json - {Name: "encoding/json.Marshal", Tag: "json", ArgPos: 0}, - {Name: "encoding/json.MarshalIndent", Tag: "json", ArgPos: 0}, - {Name: "encoding/json.Unmarshal", Tag: "json", ArgPos: 1}, - {Name: "(*encoding/json.Encoder).Encode", Tag: "json", ArgPos: 0}, - {Name: "(*encoding/json.Decoder).Decode", Tag: "json", ArgPos: 0}, - - // https://pkg.go.dev/encoding/xml - {Name: "encoding/xml.Marshal", Tag: "xml", ArgPos: 0}, - {Name: "encoding/xml.MarshalIndent", Tag: "xml", ArgPos: 0}, - {Name: "encoding/xml.Unmarshal", Tag: "xml", ArgPos: 1}, - {Name: "(*encoding/xml.Encoder).Encode", Tag: "xml", ArgPos: 0}, - {Name: "(*encoding/xml.Decoder).Decode", Tag: "xml", ArgPos: 0}, - {Name: "(*encoding/xml.Encoder).EncodeElement", Tag: "xml", ArgPos: 0}, - {Name: "(*encoding/xml.Decoder).DecodeElement", Tag: "xml", ArgPos: 0}, - - // https://github.com/go-yaml/yaml - {Name: "gopkg.in/yaml.v3.Marshal", Tag: "yaml", ArgPos: 0}, - {Name: "gopkg.in/yaml.v3.Unmarshal", Tag: "yaml", ArgPos: 1}, - {Name: "(*gopkg.in/yaml.v3.Encoder).Encode", Tag: "yaml", ArgPos: 0}, - {Name: "(*gopkg.in/yaml.v3.Decoder).Decode", Tag: "yaml", ArgPos: 0}, - - // https://github.com/BurntSushi/toml - {Name: "github.com/BurntSushi/toml.Unmarshal", Tag: "toml", ArgPos: 1}, - {Name: "github.com/BurntSushi/toml.Decode", Tag: "toml", ArgPos: 1}, - {Name: "github.com/BurntSushi/toml.DecodeFS", Tag: "toml", ArgPos: 2}, - {Name: "github.com/BurntSushi/toml.DecodeFile", Tag: "toml", ArgPos: 1}, - {Name: "(*github.com/BurntSushi/toml.Encoder).Encode", Tag: "toml", ArgPos: 0}, - {Name: "(*github.com/BurntSushi/toml.Decoder).Decode", Tag: "toml", ArgPos: 0}, - - // https://github.com/mitchellh/mapstructure - {Name: "github.com/mitchellh/mapstructure.Decode", Tag: "mapstructure", ArgPos: 1}, - {Name: "github.com/mitchellh/mapstructure.DecodeMetadata", Tag: "mapstructure", ArgPos: 1}, - {Name: "github.com/mitchellh/mapstructure.WeakDecode", Tag: "mapstructure", ArgPos: 1}, - {Name: "github.com/mitchellh/mapstructure.WeakDecodeMetadata", Tag: "mapstructure", ArgPos: 1}, - - // https://github.com/jmoiron/sqlx - {Name: "github.com/jmoiron/sqlx.Get", Tag: "db", ArgPos: 1}, - {Name: "github.com/jmoiron/sqlx.GetContext", Tag: "db", ArgPos: 2}, - {Name: "github.com/jmoiron/sqlx.Select", Tag: "db", ArgPos: 1}, - {Name: "github.com/jmoiron/sqlx.SelectContext", Tag: "db", ArgPos: 2}, - {Name: "github.com/jmoiron/sqlx.StructScan", Tag: "db", ArgPos: 1}, - {Name: "(*github.com/jmoiron/sqlx.Conn).GetContext", Tag: "db", ArgPos: 1}, - {Name: "(*github.com/jmoiron/sqlx.Conn).SelectContext", Tag: "db", ArgPos: 1}, - {Name: "(*github.com/jmoiron/sqlx.DB).Get", Tag: "db", ArgPos: 0}, - {Name: "(*github.com/jmoiron/sqlx.DB).GetContext", Tag: "db", ArgPos: 1}, - {Name: "(*github.com/jmoiron/sqlx.DB).Select", Tag: "db", ArgPos: 0}, - {Name: "(*github.com/jmoiron/sqlx.DB).SelectContext", Tag: "db", ArgPos: 1}, - {Name: "(*github.com/jmoiron/sqlx.NamedStmt).Get", Tag: "db", ArgPos: 0}, - {Name: "(*github.com/jmoiron/sqlx.NamedStmt).GetContext", Tag: "db", ArgPos: 1}, - {Name: "(*github.com/jmoiron/sqlx.NamedStmt).Select", Tag: "db", ArgPos: 0}, - {Name: "(*github.com/jmoiron/sqlx.NamedStmt).SelectContext", Tag: "db", ArgPos: 1}, - {Name: "(*github.com/jmoiron/sqlx.Row).StructScan", Tag: "db", ArgPos: 0}, - {Name: "(*github.com/jmoiron/sqlx.Rows).StructScan", Tag: "db", ArgPos: 0}, - {Name: "(*github.com/jmoiron/sqlx.Stmt).Get", Tag: "db", ArgPos: 0}, - {Name: "(*github.com/jmoiron/sqlx.Stmt).GetContext", Tag: "db", ArgPos: 1}, - {Name: "(*github.com/jmoiron/sqlx.Stmt).Select", Tag: "db", ArgPos: 0}, - {Name: "(*github.com/jmoiron/sqlx.Stmt).SelectContext", Tag: "db", ArgPos: 1}, - {Name: "(*github.com/jmoiron/sqlx.Tx).Get", Tag: "db", ArgPos: 0}, - {Name: "(*github.com/jmoiron/sqlx.Tx).GetContext", Tag: "db", ArgPos: 1}, - {Name: "(*github.com/jmoiron/sqlx.Tx).Select", Tag: "db", ArgPos: 0}, - {Name: "(*github.com/jmoiron/sqlx.Tx).SelectContext", Tag: "db", ArgPos: 1}, -} diff --git a/vendor/go.tmz.dev/musttag/musttag.go b/vendor/go.tmz.dev/musttag/musttag.go deleted file mode 100644 index 7f4e05e75..000000000 --- a/vendor/go.tmz.dev/musttag/musttag.go +++ /dev/null @@ -1,259 +0,0 @@ -// Package musttag implements the musttag analyzer. -package musttag - -import ( - "flag" - "fmt" - "go/ast" - "go/token" - "go/types" - "path" - "reflect" - "regexp" - "strconv" - "strings" - - "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" -) - -// Func describes a function call to look for, e.g. json.Marshal. -type Func struct { - Name string // Name is the full name of the function, including the package. - Tag string // Tag is the struct tag whose presence should be ensured. - ArgPos int // ArgPos is the position of the argument to check. -} - -func (fn Func) shortName() string { - name := strings.NewReplacer("*", "", "(", "", ")", "").Replace(fn.Name) - return path.Base(name) -} - -// New creates a new musttag analyzer. -// To report a custom function provide its description via Func, -// it will be added to the builtin ones. -func New(funcs ...Func) *analysis.Analyzer { - var flagFuncs []Func - return &analysis.Analyzer{ - Name: "musttag", - Doc: "enforce field tags in (un)marshaled structs", - Flags: flags(&flagFuncs), - Requires: []*analysis.Analyzer{inspect.Analyzer}, - Run: func(pass *analysis.Pass) (any, error) { - l := len(builtins) + len(funcs) + len(flagFuncs) - f := make(map[string]Func, l) - - toMap := func(slice []Func) { - for _, fn := range slice { - f[fn.Name] = fn - } - } - toMap(builtins) - toMap(funcs) - toMap(flagFuncs) - - mainModule, err := getMainModule() - if err != nil { - return nil, err - } - - return run(pass, mainModule, f) - }, - } -} - -// flags creates a flag set for the analyzer. -// The funcs slice will be filled with custom functions passed via CLI flags. -func flags(funcs *[]Func) flag.FlagSet { - fs := flag.NewFlagSet("musttag", flag.ContinueOnError) - fs.Func("fn", "report custom function (name:tag:argpos)", func(s string) error { - parts := strings.Split(s, ":") - if len(parts) != 3 || parts[0] == "" || parts[1] == "" { - return strconv.ErrSyntax - } - pos, err := strconv.Atoi(parts[2]) - if err != nil { - return err - } - *funcs = append(*funcs, Func{ - Name: parts[0], - Tag: parts[1], - ArgPos: pos, - }) - return nil - }) - return *fs -} - -// 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 cleanFullName = regexp.MustCompile(`([^*/(]+/vendor/)`) - -// run starts the analysis. -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)} - - walk.Preorder(filter, func(n ast.Node) { - if err != nil { - return // there is already an error. - } - - call, ok := n.(*ast.CallExpr) - if !ok { - return // not a function call. - } - - callee := typeutil.StaticCallee(pass.TypesInfo, call) - if callee == nil { - return // not a static call. - } - - name := cleanFullName.ReplaceAllString(callee.FullName(), "") - fn, ok := funcs[name] - if !ok { - return // the function is not supported. - } - - if len(call.Args) <= fn.ArgPos { - err = fmt.Errorf("Func.ArgPos cannot be %d: %s accepts only %d argument(s)", fn.ArgPos, fn.Name, len(call.Args)) - return - } - - arg := call.Args[fn.ArgPos] - if unary, ok := arg.(*ast.UnaryExpr); ok { - arg = unary.X // e.g. json.Marshal(&foo) - } - - initialPos := token.NoPos - switch arg := arg.(type) { - case *ast.Ident: // e.g. json.Marshal(foo) - if arg.Obj == nil { - return // e.g. json.Marshal(nil) - } - initialPos = arg.Obj.Pos() - case *ast.CompositeLit: // e.g. json.Marshal(struct{}{}) - initialPos = arg.Pos() - } - - checker := checker{ - mainModule: mainModule, - seenTypes: make(map[string]struct{}), - } - - t := pass.TypesInfo.TypeOf(arg) - st, ok := checker.parseStructType(t, initialPos) - if !ok { - return // not a struct argument. - } - - result, ok := checker.checkStructType(st, fn.Tag) - if ok { - return // nothing to report. - } - - p := pass.Fset.Position(call.Pos()) - report(pass, result, fn, p) - }) - - return nil, err -} - -// structType is an extension for types.Struct. -// The content of the fields depends on whether the type is named or not. -type structType struct { - *types.Struct - Name string // for types.Named: the type's name; for anonymous: a placeholder string. - Pos token.Pos // for types.Named: the type's position; for anonymous: the corresponding identifier's position. -} - -// checker parses and checks struct types. -type checker struct { - mainModule string - seenTypes map[string]struct{} // prevent panic on recursive types; see issue #16. -} - -// parseStructType parses the given types.Type, returning the underlying struct type. -func (c *checker) parseStructType(t types.Type, pos token.Pos) (*structType, bool) { - for { - // unwrap pointers (if any) first. - ptr, ok := t.(*types.Pointer) - if !ok { - break - } - t = ptr.Elem() - } - - switch t := t.(type) { - case *types.Named: // a struct of the named type. - pkg := t.Obj().Pkg() // may be nil; see issue #38. - if pkg == nil { - return nil, false - } - - 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(), - Name: t.Obj().Name(), - }, true - - case *types.Struct: // an anonymous struct. - return &structType{ - Struct: t, - Pos: pos, - Name: "anonymous struct", - }, true - } - - return nil, false -} - -// checkStructType recursively checks whether the given struct type is annotated with the tag. -// The result is the type of the first nested struct which fields are not properly annotated. -func (c *checker) checkStructType(st *structType, tag string) (*structType, bool) { - c.seenTypes[st.String()] = struct{}{} - - for i := 0; i < st.NumFields(); i++ { - field := st.Field(i) - if !field.Exported() { - continue - } - - if _, ok := reflect.StructTag(st.Tag(i)).Lookup(tag); !ok { - // tag is not required for embedded types; see issue #12. - if !field.Embedded() { - return st, false - } - } - - nested, ok := c.parseStructType(field.Type(), st.Pos) // TODO: or field.Pos()? - if !ok { - continue - } - if _, ok := c.seenTypes[nested.String()]; ok { - continue - } - if result, ok := c.checkStructType(nested, tag); !ok { - return result, false - } - } - - return nil, true -} diff --git a/vendor/go.tmz.dev/musttag/utils.go b/vendor/go.tmz.dev/musttag/utils.go deleted file mode 100644 index 673747f15..000000000 --- a/vendor/go.tmz.dev/musttag/utils.go +++ /dev/null @@ -1,57 +0,0 @@ -package musttag - -import ( - "encoding/json" - "errors" - "fmt" - "io" - "os" - "os/exec" - "strings" -) - -var ( - getwd = os.Getwd - - commandOutput = func(name string, args ...string) (string, error) { - output, err := exec.Command(name, args...).Output() - return string(output), err - } -) - -func getMainModule() (string, error) { - args := []string{"go", "list", "-m", "-json"} - - output, err := commandOutput(args[0], args[1:]...) - if err != nil { - return "", fmt.Errorf("running `%s`: %w", strings.Join(args, " "), err) - } - - cwd, err := getwd() - if err != nil { - return "", fmt.Errorf("getting wd: %w", err) - } - - 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/vendor/golang.org/x/exp/constraints/constraints.go b/vendor/golang.org/x/exp/constraints/constraints.go deleted file mode 100644 index 2c033dff4..000000000 --- a/vendor/golang.org/x/exp/constraints/constraints.go +++ /dev/null @@ -1,50 +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 constraints defines a set of useful constraints to be used -// with type parameters. -package constraints - -// Signed is a constraint that permits any signed integer type. -// If future releases of Go add new predeclared signed integer types, -// this constraint will be modified to include them. -type Signed interface { - ~int | ~int8 | ~int16 | ~int32 | ~int64 -} - -// Unsigned is a constraint that permits any unsigned integer type. -// If future releases of Go add new predeclared unsigned integer types, -// this constraint will be modified to include them. -type Unsigned interface { - ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr -} - -// Integer is a constraint that permits any integer type. -// If future releases of Go add new predeclared integer types, -// this constraint will be modified to include them. -type Integer interface { - Signed | Unsigned -} - -// Float is a constraint that permits any floating-point type. -// If future releases of Go add new predeclared floating-point types, -// this constraint will be modified to include them. -type Float interface { - ~float32 | ~float64 -} - -// Complex is a constraint that permits any complex numeric type. -// If future releases of Go add new predeclared complex numeric types, -// this constraint will be modified to include them. -type Complex interface { - ~complex64 | ~complex128 -} - -// Ordered is a constraint that permits any ordered type: any type -// that supports the operators < <= >= >. -// If future releases of Go add new ordered types, -// this constraint will be modified to include them. -type Ordered interface { - Integer | Float | ~string -} diff --git a/vendor/golang.org/x/exp/maps/maps.go b/vendor/golang.org/x/exp/maps/maps.go new file mode 100644 index 000000000..ecc0dabb7 --- /dev/null +++ b/vendor/golang.org/x/exp/maps/maps.go @@ -0,0 +1,94 @@ +// 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 maps defines various functions useful with maps of any type. +package maps + +// Keys returns the keys of the map m. +// The keys will be in an indeterminate order. +func Keys[M ~map[K]V, K comparable, V any](m M) []K { + r := make([]K, 0, len(m)) + for k := range m { + r = append(r, k) + } + return r +} + +// Values returns the values of the map m. +// The values will be in an indeterminate order. +func Values[M ~map[K]V, K comparable, V any](m M) []V { + r := make([]V, 0, len(m)) + for _, v := range m { + r = append(r, v) + } + return r +} + +// Equal reports whether two maps contain the same key/value pairs. +// Values are compared using ==. +func Equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool { + if len(m1) != len(m2) { + return false + } + for k, v1 := range m1 { + if v2, ok := m2[k]; !ok || v1 != v2 { + return false + } + } + return true +} + +// EqualFunc is like Equal, but compares values using eq. +// Keys are still compared with ==. +func EqualFunc[M1 ~map[K]V1, M2 ~map[K]V2, K comparable, V1, V2 any](m1 M1, m2 M2, eq func(V1, V2) bool) bool { + if len(m1) != len(m2) { + return false + } + for k, v1 := range m1 { + if v2, ok := m2[k]; !ok || !eq(v1, v2) { + return false + } + } + return true +} + +// Clear removes all entries from m, leaving it empty. +func Clear[M ~map[K]V, K comparable, V any](m M) { + for k := range m { + delete(m, k) + } +} + +// Clone returns a copy of m. This is a shallow clone: +// the new keys and values are set using ordinary assignment. +func Clone[M ~map[K]V, K comparable, V any](m M) M { + // Preserve nil in case it matters. + if m == nil { + return nil + } + r := make(M, len(m)) + for k, v := range m { + r[k] = v + } + return r +} + +// Copy copies all key/value pairs in src adding them to dst. +// When a key in src is already present in dst, +// the value in dst will be overwritten by the value associated +// with the key in src. +func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2) { + for k, v := range src { + dst[k] = v + } +} + +// DeleteFunc deletes any key/value pairs from m for which del returns true. +func DeleteFunc[M ~map[K]V, K comparable, V any](m M, del func(K, V) bool) { + for k, v := range m { + if del(k, v) { + delete(m, k) + } + } +} diff --git a/vendor/golang.org/x/exp/slices/slices.go b/vendor/golang.org/x/exp/slices/slices.go deleted file mode 100644 index 2540bd682..000000000 --- a/vendor/golang.org/x/exp/slices/slices.go +++ /dev/null @@ -1,258 +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 slices defines various functions useful with slices of any type. -// Unless otherwise specified, these functions all apply to the elements -// of a slice at index 0 <= i < len(s). -// -// Note that the less function in IsSortedFunc, SortFunc, SortStableFunc requires a -// strict weak ordering (https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings), -// or the sorting may fail to sort correctly. A common case is when sorting slices of -// floating-point numbers containing NaN values. -package slices - -import "golang.org/x/exp/constraints" - -// Equal reports whether two slices are equal: the same length and all -// elements equal. If the lengths are different, Equal returns false. -// Otherwise, the elements are compared in increasing index order, and the -// comparison stops at the first unequal pair. -// Floating point NaNs are not considered equal. -func Equal[E comparable](s1, s2 []E) bool { - if len(s1) != len(s2) { - return false - } - for i := range s1 { - if s1[i] != s2[i] { - return false - } - } - return true -} - -// EqualFunc reports whether two slices are equal using a comparison -// function on each pair of elements. If the lengths are different, -// EqualFunc returns false. Otherwise, the elements are compared in -// increasing index order, and the comparison stops at the first index -// for which eq returns false. -func EqualFunc[E1, E2 any](s1 []E1, s2 []E2, eq func(E1, E2) bool) bool { - if len(s1) != len(s2) { - return false - } - for i, v1 := range s1 { - v2 := s2[i] - if !eq(v1, v2) { - return false - } - } - return true -} - -// Compare compares the elements of s1 and s2. -// The elements are compared sequentially, starting at index 0, -// until one element is not equal to the other. -// The result of comparing the first non-matching elements is returned. -// If both slices are equal until one of them ends, the shorter slice is -// considered less than the longer one. -// The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2. -// Comparisons involving floating point NaNs are ignored. -func Compare[E constraints.Ordered](s1, s2 []E) int { - s2len := len(s2) - for i, v1 := range s1 { - if i >= s2len { - return +1 - } - v2 := s2[i] - switch { - case v1 < v2: - return -1 - case v1 > v2: - return +1 - } - } - if len(s1) < s2len { - return -1 - } - return 0 -} - -// CompareFunc is like Compare but uses a comparison function -// on each pair of elements. The elements are compared in increasing -// index order, and the comparisons stop after the first time cmp -// returns non-zero. -// The result is the first non-zero result of cmp; if cmp always -// returns 0 the result is 0 if len(s1) == len(s2), -1 if len(s1) < len(s2), -// and +1 if len(s1) > len(s2). -func CompareFunc[E1, E2 any](s1 []E1, s2 []E2, cmp func(E1, E2) int) int { - s2len := len(s2) - for i, v1 := range s1 { - if i >= s2len { - return +1 - } - v2 := s2[i] - if c := cmp(v1, v2); c != 0 { - return c - } - } - if len(s1) < s2len { - return -1 - } - return 0 -} - -// Index returns the index of the first occurrence of v in s, -// or -1 if not present. -func Index[E comparable](s []E, v E) int { - for i := range s { - if v == s[i] { - return i - } - } - return -1 -} - -// IndexFunc returns the first index i satisfying f(s[i]), -// or -1 if none do. -func IndexFunc[E any](s []E, f func(E) bool) int { - for i := range s { - if f(s[i]) { - return i - } - } - return -1 -} - -// Contains reports whether v is present in s. -func Contains[E comparable](s []E, v E) bool { - return Index(s, v) >= 0 -} - -// ContainsFunc reports whether at least one -// element e of s satisfies f(e). -func ContainsFunc[E any](s []E, f func(E) bool) bool { - return IndexFunc(s, f) >= 0 -} - -// Insert inserts the values v... into s at index i, -// returning the modified slice. -// In the returned slice r, r[i] == v[0]. -// Insert panics if i is out of range. -// This function is O(len(s) + len(v)). -func Insert[S ~[]E, E any](s S, i int, v ...E) S { - tot := len(s) + len(v) - if tot <= cap(s) { - s2 := s[:tot] - copy(s2[i+len(v):], s[i:]) - copy(s2[i:], v) - return s2 - } - s2 := make(S, tot) - copy(s2, s[:i]) - copy(s2[i:], v) - copy(s2[i+len(v):], s[i:]) - return s2 -} - -// Delete removes the elements s[i:j] from s, returning the modified slice. -// Delete panics if s[i:j] is not a valid slice of s. -// Delete modifies the contents of the slice s; it does not create a new slice. -// Delete is O(len(s)-j), so if many items must be deleted, it is better to -// make a single call deleting them all together than to delete one at a time. -// Delete might not modify the elements s[len(s)-(j-i):len(s)]. If those -// elements contain pointers you might consider zeroing those elements so that -// objects they reference can be garbage collected. -func Delete[S ~[]E, E any](s S, i, j int) S { - _ = s[i:j] // bounds check - - return append(s[:i], s[j:]...) -} - -// Replace replaces the elements s[i:j] by the given v, and returns the -// modified slice. Replace panics if s[i:j] is not a valid slice of s. -func Replace[S ~[]E, E any](s S, i, j int, v ...E) S { - _ = s[i:j] // verify that i:j is a valid subslice - tot := len(s[:i]) + len(v) + len(s[j:]) - if tot <= cap(s) { - s2 := s[:tot] - copy(s2[i+len(v):], s[j:]) - copy(s2[i:], v) - return s2 - } - s2 := make(S, tot) - copy(s2, s[:i]) - copy(s2[i:], v) - copy(s2[i+len(v):], s[j:]) - return s2 -} - -// Clone returns a copy of the slice. -// The elements are copied using assignment, so this is a shallow clone. -func Clone[S ~[]E, E any](s S) S { - // Preserve nil in case it matters. - if s == nil { - return nil - } - return append(S([]E{}), s...) -} - -// Compact replaces consecutive runs of equal elements with a single copy. -// This is like the uniq command found on Unix. -// Compact modifies the contents of the slice s; it does not create a new slice. -// When Compact discards m elements in total, it might not modify the elements -// s[len(s)-m:len(s)]. If those elements contain pointers you might consider -// zeroing those elements so that objects they reference can be garbage collected. -func Compact[S ~[]E, E comparable](s S) S { - if len(s) < 2 { - return s - } - i := 1 - for k := 1; k < len(s); k++ { - if s[k] != s[k-1] { - if i != k { - s[i] = s[k] - } - i++ - } - } - return s[:i] -} - -// CompactFunc is like Compact but uses a comparison function. -func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S { - if len(s) < 2 { - return s - } - i := 1 - for k := 1; k < len(s); k++ { - if !eq(s[k], s[k-1]) { - if i != k { - s[i] = s[k] - } - i++ - } - } - return s[:i] -} - -// Grow increases the slice's capacity, if necessary, to guarantee space for -// another n elements. After Grow(n), at least n elements can be appended -// to the slice without another allocation. If n is negative or too large to -// allocate the memory, Grow panics. -func Grow[S ~[]E, E any](s S, n int) S { - if n < 0 { - panic("cannot be negative") - } - if n -= cap(s) - len(s); n > 0 { - // TODO(https://go.dev/issue/53888): Make using []E instead of S - // to workaround a compiler bug where the runtime.growslice optimization - // does not take effect. Revert when the compiler is fixed. - s = append([]E(s)[:cap(s)], make([]E, n)...)[:len(s)] - } - return s -} - -// Clip removes unused capacity from the slice, returning s[:len(s):len(s)]. -func Clip[S ~[]E, E any](s S) S { - return s[:len(s):len(s)] -} diff --git a/vendor/golang.org/x/exp/slices/sort.go b/vendor/golang.org/x/exp/slices/sort.go deleted file mode 100644 index 231b6448a..000000000 --- a/vendor/golang.org/x/exp/slices/sort.go +++ /dev/null @@ -1,128 +0,0 @@ -// 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 slices - -import ( - "math/bits" - - "golang.org/x/exp/constraints" -) - -// Sort sorts a slice of any ordered type in ascending order. -// Sort may fail to sort correctly when sorting slices of floating-point -// numbers containing Not-a-number (NaN) values. -// Use slices.SortFunc(x, func(a, b float64) bool {return a < b || (math.IsNaN(a) && !math.IsNaN(b))}) -// instead if the input may contain NaNs. -func Sort[E constraints.Ordered](x []E) { - n := len(x) - pdqsortOrdered(x, 0, n, bits.Len(uint(n))) -} - -// SortFunc sorts the slice x in ascending order as determined by the less function. -// This sort is not guaranteed to be stable. -// -// SortFunc requires that less is a strict weak ordering. -// See https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings. -func SortFunc[E any](x []E, less func(a, b E) bool) { - n := len(x) - pdqsortLessFunc(x, 0, n, bits.Len(uint(n)), less) -} - -// SortStableFunc sorts the slice x while keeping the original order of equal -// elements, using less to compare elements. -func SortStableFunc[E any](x []E, less func(a, b E) bool) { - stableLessFunc(x, len(x), less) -} - -// IsSorted reports whether x is sorted in ascending order. -func IsSorted[E constraints.Ordered](x []E) bool { - for i := len(x) - 1; i > 0; i-- { - if x[i] < x[i-1] { - return false - } - } - return true -} - -// IsSortedFunc reports whether x is sorted in ascending order, with less as the -// comparison function. -func IsSortedFunc[E any](x []E, less func(a, b E) bool) bool { - for i := len(x) - 1; i > 0; i-- { - if less(x[i], x[i-1]) { - return false - } - } - return true -} - -// BinarySearch searches for target in a sorted slice and returns the position -// where target is found, or the position where target would appear in the -// sort order; it also returns a bool saying whether the target is really found -// in the slice. The slice must be sorted in increasing order. -func BinarySearch[E constraints.Ordered](x []E, target E) (int, bool) { - // Inlining is faster than calling BinarySearchFunc with a lambda. - n := len(x) - // Define x[-1] < target and x[n] >= target. - // Invariant: x[i-1] < target, x[j] >= target. - i, j := 0, n - for i < j { - h := int(uint(i+j) >> 1) // avoid overflow when computing h - // i ≤ h < j - if x[h] < target { - i = h + 1 // preserves x[i-1] < target - } else { - j = h // preserves x[j] >= target - } - } - // i == j, x[i-1] < target, and x[j] (= x[i]) >= target => answer is i. - return i, i < n && x[i] == target -} - -// BinarySearchFunc works like BinarySearch, but uses a custom comparison -// function. The slice must be sorted in increasing order, where "increasing" -// is defined by cmp. cmp should return 0 if the slice element matches -// the target, a negative number if the slice element precedes the target, -// or a positive number if the slice element follows the target. -// cmp must implement the same ordering as the slice, such that if -// cmp(a, t) < 0 and cmp(b, t) >= 0, then a must precede b in the slice. -func BinarySearchFunc[E, T any](x []E, target T, cmp func(E, T) int) (int, bool) { - n := len(x) - // Define cmp(x[-1], target) < 0 and cmp(x[n], target) >= 0 . - // Invariant: cmp(x[i - 1], target) < 0, cmp(x[j], target) >= 0. - i, j := 0, n - for i < j { - h := int(uint(i+j) >> 1) // avoid overflow when computing h - // i ≤ h < j - if cmp(x[h], target) < 0 { - i = h + 1 // preserves cmp(x[i - 1], target) < 0 - } else { - j = h // preserves cmp(x[j], target) >= 0 - } - } - // i == j, cmp(x[i-1], target) < 0, and cmp(x[j], target) (= cmp(x[i], target)) >= 0 => answer is i. - return i, i < n && cmp(x[i], target) == 0 -} - -type sortedHint int // hint for pdqsort when choosing the pivot - -const ( - unknownHint sortedHint = iota - increasingHint - decreasingHint -) - -// xorshift paper: https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf -type xorshift uint64 - -func (r *xorshift) Next() uint64 { - *r ^= *r << 13 - *r ^= *r >> 17 - *r ^= *r << 5 - return uint64(*r) -} - -func nextPowerOfTwo(length int) uint { - return 1 << bits.Len(uint(length)) -} diff --git a/vendor/golang.org/x/exp/slices/zsortfunc.go b/vendor/golang.org/x/exp/slices/zsortfunc.go deleted file mode 100644 index 2a632476c..000000000 --- a/vendor/golang.org/x/exp/slices/zsortfunc.go +++ /dev/null @@ -1,479 +0,0 @@ -// Code generated by gen_sort_variants.go; DO NOT EDIT. - -// 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 slices - -// insertionSortLessFunc sorts data[a:b] using insertion sort. -func insertionSortLessFunc[E any](data []E, a, b int, less func(a, b E) bool) { - for i := a + 1; i < b; i++ { - for j := i; j > a && less(data[j], data[j-1]); j-- { - data[j], data[j-1] = data[j-1], data[j] - } - } -} - -// siftDownLessFunc implements the heap property on data[lo:hi]. -// first is an offset into the array where the root of the heap lies. -func siftDownLessFunc[E any](data []E, lo, hi, first int, less func(a, b E) bool) { - root := lo - for { - child := 2*root + 1 - if child >= hi { - break - } - if child+1 < hi && less(data[first+child], data[first+child+1]) { - child++ - } - if !less(data[first+root], data[first+child]) { - return - } - data[first+root], data[first+child] = data[first+child], data[first+root] - root = child - } -} - -func heapSortLessFunc[E any](data []E, a, b int, less func(a, b E) bool) { - first := a - lo := 0 - hi := b - a - - // Build heap with greatest element at top. - for i := (hi - 1) / 2; i >= 0; i-- { - siftDownLessFunc(data, i, hi, first, less) - } - - // Pop elements, largest first, into end of data. - for i := hi - 1; i >= 0; i-- { - data[first], data[first+i] = data[first+i], data[first] - siftDownLessFunc(data, lo, i, first, less) - } -} - -// pdqsortLessFunc sorts data[a:b]. -// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort. -// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf -// C++ implementation: https://github.com/orlp/pdqsort -// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/ -// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort. -func pdqsortLessFunc[E any](data []E, a, b, limit int, less func(a, b E) bool) { - const maxInsertion = 12 - - var ( - wasBalanced = true // whether the last partitioning was reasonably balanced - wasPartitioned = true // whether the slice was already partitioned - ) - - for { - length := b - a - - if length <= maxInsertion { - insertionSortLessFunc(data, a, b, less) - return - } - - // Fall back to heapsort if too many bad choices were made. - if limit == 0 { - heapSortLessFunc(data, a, b, less) - return - } - - // If the last partitioning was imbalanced, we need to breaking patterns. - if !wasBalanced { - breakPatternsLessFunc(data, a, b, less) - limit-- - } - - pivot, hint := choosePivotLessFunc(data, a, b, less) - if hint == decreasingHint { - reverseRangeLessFunc(data, a, b, less) - // The chosen pivot was pivot-a elements after the start of the array. - // After reversing it is pivot-a elements before the end of the array. - // The idea came from Rust's implementation. - pivot = (b - 1) - (pivot - a) - hint = increasingHint - } - - // The slice is likely already sorted. - if wasBalanced && wasPartitioned && hint == increasingHint { - if partialInsertionSortLessFunc(data, a, b, less) { - return - } - } - - // Probably the slice contains many duplicate elements, partition the slice into - // elements equal to and elements greater than the pivot. - if a > 0 && !less(data[a-1], data[pivot]) { - mid := partitionEqualLessFunc(data, a, b, pivot, less) - a = mid - continue - } - - mid, alreadyPartitioned := partitionLessFunc(data, a, b, pivot, less) - wasPartitioned = alreadyPartitioned - - leftLen, rightLen := mid-a, b-mid - balanceThreshold := length / 8 - if leftLen < rightLen { - wasBalanced = leftLen >= balanceThreshold - pdqsortLessFunc(data, a, mid, limit, less) - a = mid + 1 - } else { - wasBalanced = rightLen >= balanceThreshold - pdqsortLessFunc(data, mid+1, b, limit, less) - b = mid - } - } -} - -// partitionLessFunc does one quicksort partition. -// Let p = data[pivot] -// Moves elements in data[a:b] around, so that data[i]<p and data[j]>=p for i<newpivot and j>newpivot. -// On return, data[newpivot] = p -func partitionLessFunc[E any](data []E, a, b, pivot int, less func(a, b E) bool) (newpivot int, alreadyPartitioned bool) { - data[a], data[pivot] = data[pivot], data[a] - i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned - - for i <= j && less(data[i], data[a]) { - i++ - } - for i <= j && !less(data[j], data[a]) { - j-- - } - if i > j { - data[j], data[a] = data[a], data[j] - return j, true - } - data[i], data[j] = data[j], data[i] - i++ - j-- - - for { - for i <= j && less(data[i], data[a]) { - i++ - } - for i <= j && !less(data[j], data[a]) { - j-- - } - if i > j { - break - } - data[i], data[j] = data[j], data[i] - i++ - j-- - } - data[j], data[a] = data[a], data[j] - return j, false -} - -// partitionEqualLessFunc partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot]. -// It assumed that data[a:b] does not contain elements smaller than the data[pivot]. -func partitionEqualLessFunc[E any](data []E, a, b, pivot int, less func(a, b E) bool) (newpivot int) { - data[a], data[pivot] = data[pivot], data[a] - i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned - - for { - for i <= j && !less(data[a], data[i]) { - i++ - } - for i <= j && less(data[a], data[j]) { - j-- - } - if i > j { - break - } - data[i], data[j] = data[j], data[i] - i++ - j-- - } - return i -} - -// partialInsertionSortLessFunc partially sorts a slice, returns true if the slice is sorted at the end. -func partialInsertionSortLessFunc[E any](data []E, a, b int, less func(a, b E) bool) bool { - const ( - maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted - shortestShifting = 50 // don't shift any elements on short arrays - ) - i := a + 1 - for j := 0; j < maxSteps; j++ { - for i < b && !less(data[i], data[i-1]) { - i++ - } - - if i == b { - return true - } - - if b-a < shortestShifting { - return false - } - - data[i], data[i-1] = data[i-1], data[i] - - // Shift the smaller one to the left. - if i-a >= 2 { - for j := i - 1; j >= 1; j-- { - if !less(data[j], data[j-1]) { - break - } - data[j], data[j-1] = data[j-1], data[j] - } - } - // Shift the greater one to the right. - if b-i >= 2 { - for j := i + 1; j < b; j++ { - if !less(data[j], data[j-1]) { - break - } - data[j], data[j-1] = data[j-1], data[j] - } - } - } - return false -} - -// breakPatternsLessFunc scatters some elements around in an attempt to break some patterns -// that might cause imbalanced partitions in quicksort. -func breakPatternsLessFunc[E any](data []E, a, b int, less func(a, b E) bool) { - length := b - a - if length >= 8 { - random := xorshift(length) - modulus := nextPowerOfTwo(length) - - for idx := a + (length/4)*2 - 1; idx <= a+(length/4)*2+1; idx++ { - other := int(uint(random.Next()) & (modulus - 1)) - if other >= length { - other -= length - } - data[idx], data[a+other] = data[a+other], data[idx] - } - } -} - -// choosePivotLessFunc chooses a pivot in data[a:b]. -// -// [0,8): chooses a static pivot. -// [8,shortestNinther): uses the simple median-of-three method. -// [shortestNinther,∞): uses the Tukey ninther method. -func choosePivotLessFunc[E any](data []E, a, b int, less func(a, b E) bool) (pivot int, hint sortedHint) { - const ( - shortestNinther = 50 - maxSwaps = 4 * 3 - ) - - l := b - a - - var ( - swaps int - i = a + l/4*1 - j = a + l/4*2 - k = a + l/4*3 - ) - - if l >= 8 { - if l >= shortestNinther { - // Tukey ninther method, the idea came from Rust's implementation. - i = medianAdjacentLessFunc(data, i, &swaps, less) - j = medianAdjacentLessFunc(data, j, &swaps, less) - k = medianAdjacentLessFunc(data, k, &swaps, less) - } - // Find the median among i, j, k and stores it into j. - j = medianLessFunc(data, i, j, k, &swaps, less) - } - - switch swaps { - case 0: - return j, increasingHint - case maxSwaps: - return j, decreasingHint - default: - return j, unknownHint - } -} - -// order2LessFunc returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a. -func order2LessFunc[E any](data []E, a, b int, swaps *int, less func(a, b E) bool) (int, int) { - if less(data[b], data[a]) { - *swaps++ - return b, a - } - return a, b -} - -// medianLessFunc returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c. -func medianLessFunc[E any](data []E, a, b, c int, swaps *int, less func(a, b E) bool) int { - a, b = order2LessFunc(data, a, b, swaps, less) - b, c = order2LessFunc(data, b, c, swaps, less) - a, b = order2LessFunc(data, a, b, swaps, less) - return b -} - -// medianAdjacentLessFunc finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a. -func medianAdjacentLessFunc[E any](data []E, a int, swaps *int, less func(a, b E) bool) int { - return medianLessFunc(data, a-1, a, a+1, swaps, less) -} - -func reverseRangeLessFunc[E any](data []E, a, b int, less func(a, b E) bool) { - i := a - j := b - 1 - for i < j { - data[i], data[j] = data[j], data[i] - i++ - j-- - } -} - -func swapRangeLessFunc[E any](data []E, a, b, n int, less func(a, b E) bool) { - for i := 0; i < n; i++ { - data[a+i], data[b+i] = data[b+i], data[a+i] - } -} - -func stableLessFunc[E any](data []E, n int, less func(a, b E) bool) { - blockSize := 20 // must be > 0 - a, b := 0, blockSize - for b <= n { - insertionSortLessFunc(data, a, b, less) - a = b - b += blockSize - } - insertionSortLessFunc(data, a, n, less) - - for blockSize < n { - a, b = 0, 2*blockSize - for b <= n { - symMergeLessFunc(data, a, a+blockSize, b, less) - a = b - b += 2 * blockSize - } - if m := a + blockSize; m < n { - symMergeLessFunc(data, a, m, n, less) - } - blockSize *= 2 - } -} - -// symMergeLessFunc merges the two sorted subsequences data[a:m] and data[m:b] using -// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum -// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz -// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in -// Computer Science, pages 714-723. Springer, 2004. -// -// Let M = m-a and N = b-n. Wolog M < N. -// The recursion depth is bound by ceil(log(N+M)). -// The algorithm needs O(M*log(N/M + 1)) calls to data.Less. -// The algorithm needs O((M+N)*log(M)) calls to data.Swap. -// -// The paper gives O((M+N)*log(M)) as the number of assignments assuming a -// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation -// in the paper carries through for Swap operations, especially as the block -// swapping rotate uses only O(M+N) Swaps. -// -// symMerge assumes non-degenerate arguments: a < m && m < b. -// Having the caller check this condition eliminates many leaf recursion calls, -// which improves performance. -func symMergeLessFunc[E any](data []E, a, m, b int, less func(a, b E) bool) { - // Avoid unnecessary recursions of symMerge - // by direct insertion of data[a] into data[m:b] - // if data[a:m] only contains one element. - if m-a == 1 { - // Use binary search to find the lowest index i - // such that data[i] >= data[a] for m <= i < b. - // Exit the search loop with i == b in case no such index exists. - i := m - j := b - for i < j { - h := int(uint(i+j) >> 1) - if less(data[h], data[a]) { - i = h + 1 - } else { - j = h - } - } - // Swap values until data[a] reaches the position before i. - for k := a; k < i-1; k++ { - data[k], data[k+1] = data[k+1], data[k] - } - return - } - - // Avoid unnecessary recursions of symMerge - // by direct insertion of data[m] into data[a:m] - // if data[m:b] only contains one element. - if b-m == 1 { - // Use binary search to find the lowest index i - // such that data[i] > data[m] for a <= i < m. - // Exit the search loop with i == m in case no such index exists. - i := a - j := m - for i < j { - h := int(uint(i+j) >> 1) - if !less(data[m], data[h]) { - i = h + 1 - } else { - j = h - } - } - // Swap values until data[m] reaches the position i. - for k := m; k > i; k-- { - data[k], data[k-1] = data[k-1], data[k] - } - return - } - - mid := int(uint(a+b) >> 1) - n := mid + m - var start, r int - if m > mid { - start = n - b - r = mid - } else { - start = a - r = m - } - p := n - 1 - - for start < r { - c := int(uint(start+r) >> 1) - if !less(data[p-c], data[c]) { - start = c + 1 - } else { - r = c - } - } - - end := n - start - if start < m && m < end { - rotateLessFunc(data, start, m, end, less) - } - if a < start && start < mid { - symMergeLessFunc(data, a, start, mid, less) - } - if mid < end && end < b { - symMergeLessFunc(data, mid, end, b, less) - } -} - -// rotateLessFunc rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data: -// Data of the form 'x u v y' is changed to 'x v u y'. -// rotate performs at most b-a many calls to data.Swap, -// and it assumes non-degenerate arguments: a < m && m < b. -func rotateLessFunc[E any](data []E, a, m, b int, less func(a, b E) bool) { - i := m - a - j := b - m - - for i != j { - if i > j { - swapRangeLessFunc(data, m-i, m, j, less) - i -= j - } else { - swapRangeLessFunc(data, m-i, m+j-i, i, less) - j -= i - } - } - // i == j - swapRangeLessFunc(data, m-i, m, i, less) -} diff --git a/vendor/golang.org/x/exp/slices/zsortordered.go b/vendor/golang.org/x/exp/slices/zsortordered.go deleted file mode 100644 index efaa1c8b7..000000000 --- a/vendor/golang.org/x/exp/slices/zsortordered.go +++ /dev/null @@ -1,481 +0,0 @@ -// Code generated by gen_sort_variants.go; DO NOT EDIT. - -// 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 slices - -import "golang.org/x/exp/constraints" - -// insertionSortOrdered sorts data[a:b] using insertion sort. -func insertionSortOrdered[E constraints.Ordered](data []E, a, b int) { - for i := a + 1; i < b; i++ { - for j := i; j > a && (data[j] < data[j-1]); j-- { - data[j], data[j-1] = data[j-1], data[j] - } - } -} - -// siftDownOrdered implements the heap property on data[lo:hi]. -// first is an offset into the array where the root of the heap lies. -func siftDownOrdered[E constraints.Ordered](data []E, lo, hi, first int) { - root := lo - for { - child := 2*root + 1 - if child >= hi { - break - } - if child+1 < hi && (data[first+child] < data[first+child+1]) { - child++ - } - if !(data[first+root] < data[first+child]) { - return - } - data[first+root], data[first+child] = data[first+child], data[first+root] - root = child - } -} - -func heapSortOrdered[E constraints.Ordered](data []E, a, b int) { - first := a - lo := 0 - hi := b - a - - // Build heap with greatest element at top. - for i := (hi - 1) / 2; i >= 0; i-- { - siftDownOrdered(data, i, hi, first) - } - - // Pop elements, largest first, into end of data. - for i := hi - 1; i >= 0; i-- { - data[first], data[first+i] = data[first+i], data[first] - siftDownOrdered(data, lo, i, first) - } -} - -// pdqsortOrdered sorts data[a:b]. -// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort. -// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf -// C++ implementation: https://github.com/orlp/pdqsort -// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/ -// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort. -func pdqsortOrdered[E constraints.Ordered](data []E, a, b, limit int) { - const maxInsertion = 12 - - var ( - wasBalanced = true // whether the last partitioning was reasonably balanced - wasPartitioned = true // whether the slice was already partitioned - ) - - for { - length := b - a - - if length <= maxInsertion { - insertionSortOrdered(data, a, b) - return - } - - // Fall back to heapsort if too many bad choices were made. - if limit == 0 { - heapSortOrdered(data, a, b) - return - } - - // If the last partitioning was imbalanced, we need to breaking patterns. - if !wasBalanced { - breakPatternsOrdered(data, a, b) - limit-- - } - - pivot, hint := choosePivotOrdered(data, a, b) - if hint == decreasingHint { - reverseRangeOrdered(data, a, b) - // The chosen pivot was pivot-a elements after the start of the array. - // After reversing it is pivot-a elements before the end of the array. - // The idea came from Rust's implementation. - pivot = (b - 1) - (pivot - a) - hint = increasingHint - } - - // The slice is likely already sorted. - if wasBalanced && wasPartitioned && hint == increasingHint { - if partialInsertionSortOrdered(data, a, b) { - return - } - } - - // Probably the slice contains many duplicate elements, partition the slice into - // elements equal to and elements greater than the pivot. - if a > 0 && !(data[a-1] < data[pivot]) { - mid := partitionEqualOrdered(data, a, b, pivot) - a = mid - continue - } - - mid, alreadyPartitioned := partitionOrdered(data, a, b, pivot) - wasPartitioned = alreadyPartitioned - - leftLen, rightLen := mid-a, b-mid - balanceThreshold := length / 8 - if leftLen < rightLen { - wasBalanced = leftLen >= balanceThreshold - pdqsortOrdered(data, a, mid, limit) - a = mid + 1 - } else { - wasBalanced = rightLen >= balanceThreshold - pdqsortOrdered(data, mid+1, b, limit) - b = mid - } - } -} - -// partitionOrdered does one quicksort partition. -// Let p = data[pivot] -// Moves elements in data[a:b] around, so that data[i]<p and data[j]>=p for i<newpivot and j>newpivot. -// On return, data[newpivot] = p -func partitionOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivot int, alreadyPartitioned bool) { - data[a], data[pivot] = data[pivot], data[a] - i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned - - for i <= j && (data[i] < data[a]) { - i++ - } - for i <= j && !(data[j] < data[a]) { - j-- - } - if i > j { - data[j], data[a] = data[a], data[j] - return j, true - } - data[i], data[j] = data[j], data[i] - i++ - j-- - - for { - for i <= j && (data[i] < data[a]) { - i++ - } - for i <= j && !(data[j] < data[a]) { - j-- - } - if i > j { - break - } - data[i], data[j] = data[j], data[i] - i++ - j-- - } - data[j], data[a] = data[a], data[j] - return j, false -} - -// partitionEqualOrdered partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot]. -// It assumed that data[a:b] does not contain elements smaller than the data[pivot]. -func partitionEqualOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivot int) { - data[a], data[pivot] = data[pivot], data[a] - i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned - - for { - for i <= j && !(data[a] < data[i]) { - i++ - } - for i <= j && (data[a] < data[j]) { - j-- - } - if i > j { - break - } - data[i], data[j] = data[j], data[i] - i++ - j-- - } - return i -} - -// partialInsertionSortOrdered partially sorts a slice, returns true if the slice is sorted at the end. -func partialInsertionSortOrdered[E constraints.Ordered](data []E, a, b int) bool { - const ( - maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted - shortestShifting = 50 // don't shift any elements on short arrays - ) - i := a + 1 - for j := 0; j < maxSteps; j++ { - for i < b && !(data[i] < data[i-1]) { - i++ - } - - if i == b { - return true - } - - if b-a < shortestShifting { - return false - } - - data[i], data[i-1] = data[i-1], data[i] - - // Shift the smaller one to the left. - if i-a >= 2 { - for j := i - 1; j >= 1; j-- { - if !(data[j] < data[j-1]) { - break - } - data[j], data[j-1] = data[j-1], data[j] - } - } - // Shift the greater one to the right. - if b-i >= 2 { - for j := i + 1; j < b; j++ { - if !(data[j] < data[j-1]) { - break - } - data[j], data[j-1] = data[j-1], data[j] - } - } - } - return false -} - -// breakPatternsOrdered scatters some elements around in an attempt to break some patterns -// that might cause imbalanced partitions in quicksort. -func breakPatternsOrdered[E constraints.Ordered](data []E, a, b int) { - length := b - a - if length >= 8 { - random := xorshift(length) - modulus := nextPowerOfTwo(length) - - for idx := a + (length/4)*2 - 1; idx <= a+(length/4)*2+1; idx++ { - other := int(uint(random.Next()) & (modulus - 1)) - if other >= length { - other -= length - } - data[idx], data[a+other] = data[a+other], data[idx] - } - } -} - -// choosePivotOrdered chooses a pivot in data[a:b]. -// -// [0,8): chooses a static pivot. -// [8,shortestNinther): uses the simple median-of-three method. -// [shortestNinther,∞): uses the Tukey ninther method. -func choosePivotOrdered[E constraints.Ordered](data []E, a, b int) (pivot int, hint sortedHint) { - const ( - shortestNinther = 50 - maxSwaps = 4 * 3 - ) - - l := b - a - - var ( - swaps int - i = a + l/4*1 - j = a + l/4*2 - k = a + l/4*3 - ) - - if l >= 8 { - if l >= shortestNinther { - // Tukey ninther method, the idea came from Rust's implementation. - i = medianAdjacentOrdered(data, i, &swaps) - j = medianAdjacentOrdered(data, j, &swaps) - k = medianAdjacentOrdered(data, k, &swaps) - } - // Find the median among i, j, k and stores it into j. - j = medianOrdered(data, i, j, k, &swaps) - } - - switch swaps { - case 0: - return j, increasingHint - case maxSwaps: - return j, decreasingHint - default: - return j, unknownHint - } -} - -// order2Ordered returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a. -func order2Ordered[E constraints.Ordered](data []E, a, b int, swaps *int) (int, int) { - if data[b] < data[a] { - *swaps++ - return b, a - } - return a, b -} - -// medianOrdered returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c. -func medianOrdered[E constraints.Ordered](data []E, a, b, c int, swaps *int) int { - a, b = order2Ordered(data, a, b, swaps) - b, c = order2Ordered(data, b, c, swaps) - a, b = order2Ordered(data, a, b, swaps) - return b -} - -// medianAdjacentOrdered finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a. -func medianAdjacentOrdered[E constraints.Ordered](data []E, a int, swaps *int) int { - return medianOrdered(data, a-1, a, a+1, swaps) -} - -func reverseRangeOrdered[E constraints.Ordered](data []E, a, b int) { - i := a - j := b - 1 - for i < j { - data[i], data[j] = data[j], data[i] - i++ - j-- - } -} - -func swapRangeOrdered[E constraints.Ordered](data []E, a, b, n int) { - for i := 0; i < n; i++ { - data[a+i], data[b+i] = data[b+i], data[a+i] - } -} - -func stableOrdered[E constraints.Ordered](data []E, n int) { - blockSize := 20 // must be > 0 - a, b := 0, blockSize - for b <= n { - insertionSortOrdered(data, a, b) - a = b - b += blockSize - } - insertionSortOrdered(data, a, n) - - for blockSize < n { - a, b = 0, 2*blockSize - for b <= n { - symMergeOrdered(data, a, a+blockSize, b) - a = b - b += 2 * blockSize - } - if m := a + blockSize; m < n { - symMergeOrdered(data, a, m, n) - } - blockSize *= 2 - } -} - -// symMergeOrdered merges the two sorted subsequences data[a:m] and data[m:b] using -// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum -// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz -// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in -// Computer Science, pages 714-723. Springer, 2004. -// -// Let M = m-a and N = b-n. Wolog M < N. -// The recursion depth is bound by ceil(log(N+M)). -// The algorithm needs O(M*log(N/M + 1)) calls to data.Less. -// The algorithm needs O((M+N)*log(M)) calls to data.Swap. -// -// The paper gives O((M+N)*log(M)) as the number of assignments assuming a -// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation -// in the paper carries through for Swap operations, especially as the block -// swapping rotate uses only O(M+N) Swaps. -// -// symMerge assumes non-degenerate arguments: a < m && m < b. -// Having the caller check this condition eliminates many leaf recursion calls, -// which improves performance. -func symMergeOrdered[E constraints.Ordered](data []E, a, m, b int) { - // Avoid unnecessary recursions of symMerge - // by direct insertion of data[a] into data[m:b] - // if data[a:m] only contains one element. - if m-a == 1 { - // Use binary search to find the lowest index i - // such that data[i] >= data[a] for m <= i < b. - // Exit the search loop with i == b in case no such index exists. - i := m - j := b - for i < j { - h := int(uint(i+j) >> 1) - if data[h] < data[a] { - i = h + 1 - } else { - j = h - } - } - // Swap values until data[a] reaches the position before i. - for k := a; k < i-1; k++ { - data[k], data[k+1] = data[k+1], data[k] - } - return - } - - // Avoid unnecessary recursions of symMerge - // by direct insertion of data[m] into data[a:m] - // if data[m:b] only contains one element. - if b-m == 1 { - // Use binary search to find the lowest index i - // such that data[i] > data[m] for a <= i < m. - // Exit the search loop with i == m in case no such index exists. - i := a - j := m - for i < j { - h := int(uint(i+j) >> 1) - if !(data[m] < data[h]) { - i = h + 1 - } else { - j = h - } - } - // Swap values until data[m] reaches the position i. - for k := m; k > i; k-- { - data[k], data[k-1] = data[k-1], data[k] - } - return - } - - mid := int(uint(a+b) >> 1) - n := mid + m - var start, r int - if m > mid { - start = n - b - r = mid - } else { - start = a - r = m - } - p := n - 1 - - for start < r { - c := int(uint(start+r) >> 1) - if !(data[p-c] < data[c]) { - start = c + 1 - } else { - r = c - } - } - - end := n - start - if start < m && m < end { - rotateOrdered(data, start, m, end) - } - if a < start && start < mid { - symMergeOrdered(data, a, start, mid) - } - if mid < end && end < b { - symMergeOrdered(data, mid, end, b) - } -} - -// rotateOrdered rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data: -// Data of the form 'x u v y' is changed to 'x v u y'. -// rotate performs at most b-a many calls to data.Swap, -// and it assumes non-degenerate arguments: a < m && m < b. -func rotateOrdered[E constraints.Ordered](data []E, a, m, b int) { - i := m - a - j := b - m - - for i != j { - if i > j { - swapRangeOrdered(data, m-i, m, j) - i -= j - } else { - swapRangeOrdered(data, m-i, m+j-i, i) - j -= i - } - } - // i == j - swapRangeOrdered(data, m-i, m, i) -} diff --git a/vendor/golang.org/x/exp/typeparams/typeparams_go117.go b/vendor/golang.org/x/exp/typeparams/typeparams_go117.go index efc33f10f..c1da79316 100644 --- a/vendor/golang.org/x/exp/typeparams/typeparams_go117.go +++ b/vendor/golang.org/x/exp/typeparams/typeparams_go117.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !go1.18 -// +build !go1.18 package typeparams diff --git a/vendor/golang.org/x/exp/typeparams/typeparams_go118.go b/vendor/golang.org/x/exp/typeparams/typeparams_go118.go index 1176104b2..0b35449d1 100644 --- a/vendor/golang.org/x/exp/typeparams/typeparams_go118.go +++ b/vendor/golang.org/x/exp/typeparams/typeparams_go118.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build go1.18 -// +build go1.18 package typeparams diff --git a/vendor/golang.org/x/mod/modfile/rule.go b/vendor/golang.org/x/mod/modfile/rule.go index e0869fa38..35fd1f534 100644 --- a/vendor/golang.org/x/mod/modfile/rule.go +++ b/vendor/golang.org/x/mod/modfile/rule.go @@ -542,7 +542,7 @@ func parseReplace(filename string, line *Line, verb string, args []string, fix V if strings.Contains(ns, "@") { return nil, errorf("replacement module must match format 'path version', not 'path@version'") } - return nil, errorf("replacement module without version must be directory path (rooted or starting with ./ or ../)") + return nil, errorf("replacement module without version must be directory path (rooted or starting with . or ..)") } if filepath.Separator == '/' && strings.Contains(ns, `\`) { return nil, errorf("replacement directory appears to be Windows path (on a non-windows system)") @@ -555,7 +555,6 @@ func parseReplace(filename string, line *Line, verb string, args []string, fix V } if IsDirectoryPath(ns) { return nil, errorf("replacement module directory path %q cannot have version", ns) - } } return &Replace{ @@ -679,14 +678,15 @@ func (f *WorkFile) add(errs *ErrorList, line *Line, verb string, args []string, } } -// IsDirectoryPath reports whether the given path should be interpreted -// as a directory path. Just like on the go command line, relative paths +// IsDirectoryPath reports whether the given path should be interpreted as a directory path. +// Just like on the go command line, relative paths starting with a '.' or '..' path component // and rooted paths are directory paths; the rest are module paths. func IsDirectoryPath(ns string) bool { // Because go.mod files can move from one system to another, // we check all known path syntaxes, both Unix and Windows. - return strings.HasPrefix(ns, "./") || strings.HasPrefix(ns, "../") || strings.HasPrefix(ns, "/") || - strings.HasPrefix(ns, `.\`) || strings.HasPrefix(ns, `..\`) || strings.HasPrefix(ns, `\`) || + return ns == "." || strings.HasPrefix(ns, "./") || strings.HasPrefix(ns, `.\`) || + ns == ".." || strings.HasPrefix(ns, "../") || strings.HasPrefix(ns, `..\`) || + strings.HasPrefix(ns, "/") || strings.HasPrefix(ns, `\`) || len(ns) >= 2 && ('A' <= ns[0] && ns[0] <= 'Z' || 'a' <= ns[0] && ns[0] <= 'z') && ns[1] == ':' } diff --git a/vendor/golang.org/x/sys/execabs/execabs.go b/vendor/golang.org/x/sys/execabs/execabs.go deleted file mode 100644 index 3bf40fdfe..000000000 --- a/vendor/golang.org/x/sys/execabs/execabs.go +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2020 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. -// -// See https://blog.golang.org/path-security for more information -// about when it may be necessary or appropriate to use this package. -package execabs - -import ( - "context" - "fmt" - "os/exec" - "path/filepath" - "reflect" - "unsafe" -) - -// ErrNotFound is the error resulting if a path search failed to find an executable file. -// It is an alias for exec.ErrNotFound. -var ErrNotFound = exec.ErrNotFound - -// Cmd represents an external command being prepared or run. -// It is an alias for exec.Cmd. -type Cmd = exec.Cmd - -// Error is returned by LookPath when it fails to classify a file as an executable. -// It is an alias for exec.Error. -type Error = exec.Error - -// An ExitError reports an unsuccessful exit by a command. -// It is an alias for exec.ExitError. -type ExitError = exec.ExitError - -func relError(file, path string) error { - return fmt.Errorf("%s resolves to executable in current directory (.%c%s)", file, filepath.Separator, path) -} - -// LookPath searches for an executable named file in the directories -// named by the PATH environment variable. If file contains a slash, -// it is tried directly and the PATH is not consulted. The result will be -// an absolute path. -// -// LookPath differs from exec.LookPath in its handling of PATH lookups, -// which are used for file names without slashes. If exec.LookPath's -// PATH lookup would have returned an executable from the current directory, -// LookPath instead returns an error. -func LookPath(file string) (string, error) { - path, err := exec.LookPath(file) - if err != nil && !isGo119ErrDot(err) { - 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) && !isGo119ErrFieldSet(cmd) { - // 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 = "" - } -} - -// CommandContext is like Command but includes a context. -// -// The provided context is used to kill the process (by calling os.Process.Kill) -// if the context becomes done before the command completes on its own. -func CommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd { - cmd := exec.CommandContext(ctx, name, arg...) - fixCmd(name, cmd) - return cmd - -} - -// Command returns the Cmd struct to execute the named program with the given arguments. -// See exec.Command for most details. -// -// Command differs from exec.Command in its handling of PATH lookups, -// which are used when the program name contains no slashes. -// If exec.Command would have returned an exec.Cmd configured to run an -// executable from the current directory, Command instead -// returns an exec.Cmd that will return an error from Start or Run. -func Command(name string, arg ...string) *exec.Cmd { - cmd := exec.Command(name, arg...) - fixCmd(name, cmd) - return cmd -} diff --git a/vendor/golang.org/x/sys/execabs/execabs_go118.go b/vendor/golang.org/x/sys/execabs/execabs_go118.go deleted file mode 100644 index 5627d70e3..000000000 --- a/vendor/golang.org/x/sys/execabs/execabs_go118.go +++ /dev/null @@ -1,17 +0,0 @@ -// 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. - -//go:build !go1.19 - -package execabs - -import "os/exec" - -func isGo119ErrDot(err error) bool { - return false -} - -func isGo119ErrFieldSet(cmd *exec.Cmd) bool { - return false -} diff --git a/vendor/golang.org/x/sys/execabs/execabs_go119.go b/vendor/golang.org/x/sys/execabs/execabs_go119.go deleted file mode 100644 index d60ab1b41..000000000 --- a/vendor/golang.org/x/sys/execabs/execabs_go119.go +++ /dev/null @@ -1,20 +0,0 @@ -// 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. - -//go:build go1.19 - -package execabs - -import ( - "errors" - "os/exec" -) - -func isGo119ErrDot(err error) bool { - return errors.Is(err, exec.ErrDot) -} - -func isGo119ErrFieldSet(cmd *exec.Cmd) bool { - return cmd.Err != nil -} diff --git a/vendor/golang.org/x/tools/go/analysis/analysistest/analysistest.go b/vendor/golang.org/x/tools/go/analysis/analysistest/analysistest.go index 0c066baa3..95db20f4b 100644 --- a/vendor/golang.org/x/tools/go/analysis/analysistest/analysistest.go +++ b/vendor/golang.org/x/tools/go/analysis/analysistest/analysistest.go @@ -15,6 +15,7 @@ import ( "os" "path/filepath" "regexp" + "runtime" "sort" "strconv" "strings" @@ -128,6 +129,19 @@ type Testing interface { func RunWithSuggestedFixes(t Testing, dir string, a *analysis.Analyzer, patterns ...string) []*Result { r := Run(t, dir, a, patterns...) + // If the immediate caller of RunWithSuggestedFixes is in + // x/tools, we apply stricter checks as required by gopls. + inTools := false + { + var pcs [1]uintptr + n := runtime.Callers(1, pcs[:]) + frames := runtime.CallersFrames(pcs[:n]) + fr, _ := frames.Next() + if fr.Func != nil && strings.HasPrefix(fr.Func.Name(), "golang.org/x/tools/") { + inTools = true + } + } + // Process each result (package) separately, matching up the suggested // fixes into a diff, which we will compare to the .golden file. We have // to do this per-result in case a file appears in two packages, such as in @@ -145,16 +159,26 @@ func RunWithSuggestedFixes(t Testing, dir string, a *analysis.Analyzer, patterns // Validate edits, prepare the fileEdits map and read the file contents. for _, diag := range act.Diagnostics { - for _, sf := range diag.SuggestedFixes { - for _, edit := range sf.TextEdits { + for _, fix := range diag.SuggestedFixes { + + // Assert that lazy fixes have a Category (#65578, #65087). + if inTools && len(fix.TextEdits) == 0 && diag.Category == "" { + t.Errorf("missing Diagnostic.Category for SuggestedFix without TextEdits (gopls requires the category for the name of the fix command") + } + + for _, edit := range fix.TextEdits { + start, end := edit.Pos, edit.End + if !end.IsValid() { + end = start + } // Validate the edit. - if edit.Pos > edit.End { + if start > end { t.Errorf( "diagnostic for analysis %v contains Suggested Fix with malformed edit: pos (%v) > end (%v)", - act.Pass.Analyzer.Name, edit.Pos, edit.End) + act.Pass.Analyzer.Name, start, end) continue } - file, endfile := act.Pass.Fset.File(edit.Pos), act.Pass.Fset.File(edit.End) + file, endfile := act.Pass.Fset.File(start), act.Pass.Fset.File(end) if file == nil || endfile == nil || file != endfile { t.Errorf( "diagnostic for analysis %v contains Suggested Fix with malformed spanning files %v and %v", @@ -171,9 +195,9 @@ func RunWithSuggestedFixes(t Testing, dir string, a *analysis.Analyzer, patterns if _, ok := fileEdits[file]; !ok { fileEdits[file] = make(map[string][]diff.Edit) } - fileEdits[file][sf.Message] = append(fileEdits[file][sf.Message], diff.Edit{ - Start: file.Offset(edit.Pos), - End: file.Offset(edit.End), + fileEdits[file][fix.Message] = append(fileEdits[file][fix.Message], diff.Edit{ + Start: file.Offset(start), + End: file.Offset(end), New: string(edit.NewText), }) } diff --git a/vendor/golang.org/x/tools/go/analysis/diagnostic.go b/vendor/golang.org/x/tools/go/analysis/diagnostic.go index f67c97294..c638f2758 100644 --- a/vendor/golang.org/x/tools/go/analysis/diagnostic.go +++ b/vendor/golang.org/x/tools/go/analysis/diagnostic.go @@ -31,14 +31,14 @@ type Diagnostic struct { // see https://pkg.go.dev/net/url#URL.ResolveReference. URL string - // SuggestedFixes contains suggested fixes for a diagnostic - // which can be used to perform edits to a file that address - // the diagnostic. - // - // Diagnostics should not contain SuggestedFixes that overlap. - SuggestedFixes []SuggestedFix // optional + // SuggestedFixes is an optional list of fixes to address the + // problem described by the diagnostic, each one representing + // an alternative strategy; at most one may be applied. + SuggestedFixes []SuggestedFix - Related []RelatedInformation // optional + // Related contains optional secondary positions and messages + // related to the primary diagnostic. + Related []RelatedInformation } // RelatedInformation contains information related to a diagnostic. @@ -55,8 +55,7 @@ type RelatedInformation struct { // user can choose to apply to their code. Usually the SuggestedFix is // meant to fix the issue flagged by the diagnostic. // -// TextEdits for a SuggestedFix should not overlap, -// nor contain edits for other packages. +// The TextEdits must not overlap, nor contain edits for other packages. type SuggestedFix struct { // A description for this suggested fix to be shown to a user deciding // whether to accept it. diff --git a/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/flags.go b/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/flags.go index 9e3fde72b..ff14ff58f 100644 --- a/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/flags.go +++ b/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/flags.go @@ -362,15 +362,24 @@ type JSONSuggestedFix struct { Edits []JSONTextEdit `json:"edits"` } -// A JSONDiagnostic can be used to encode and decode analysis.Diagnostics to and -// from JSON. -// TODO(matloob): Should the JSON diagnostics contain ranges? -// If so, how should they be formatted? +// A JSONDiagnostic describes the JSON schema of an analysis.Diagnostic. +// +// TODO(matloob): include End position if present. type JSONDiagnostic struct { - Category string `json:"category,omitempty"` - Posn string `json:"posn"` - Message string `json:"message"` - SuggestedFixes []JSONSuggestedFix `json:"suggested_fixes,omitempty"` + Category string `json:"category,omitempty"` + Posn string `json:"posn"` // e.g. "file.go:line:column" + Message string `json:"message"` + SuggestedFixes []JSONSuggestedFix `json:"suggested_fixes,omitempty"` + Related []JSONRelatedInformation `json:"related,omitempty"` +} + +// A JSONRelated describes a secondary position and message related to +// a primary diagnostic. +// +// TODO(adonovan): include End position if present. +type JSONRelatedInformation struct { + Posn string `json:"posn"` // e.g. "file.go:line:column" + Message string `json:"message"` } // Add adds the result of analysis 'name' on package 'id'. @@ -401,11 +410,19 @@ func (tree JSONTree) Add(fset *token.FileSet, id, name string, diags []analysis. Edits: edits, }) } + var related []JSONRelatedInformation + for _, r := range f.Related { + related = append(related, JSONRelatedInformation{ + Posn: fset.Position(r.Pos).String(), + Message: r.Message, + }) + } jdiag := JSONDiagnostic{ Category: f.Category, Posn: fset.Position(f.Pos).String(), Message: f.Message, SuggestedFixes: fixes, + Related: related, } diagnostics = append(diagnostics, jdiag) } diff --git a/vendor/golang.org/x/tools/go/analysis/internal/checker/checker.go b/vendor/golang.org/x/tools/go/analysis/internal/checker/checker.go index 33ca77a06..3c8935008 100644 --- a/vendor/golang.org/x/tools/go/analysis/internal/checker/checker.go +++ b/vendor/golang.org/x/tools/go/analysis/internal/checker/checker.go @@ -342,20 +342,28 @@ func applyFixes(roots []*action) error { for _, edit := range sf.TextEdits { // Validate the edit. // Any error here indicates a bug in the analyzer. - file := act.pkg.Fset.File(edit.Pos) + start, end := edit.Pos, edit.End + file := act.pkg.Fset.File(start) if file == nil { return fmt.Errorf("analysis %q suggests invalid fix: missing file info for pos (%v)", - act.a.Name, edit.Pos) + act.a.Name, start) } - if edit.Pos > edit.End { + if !end.IsValid() { + end = start + } + if start > end { return fmt.Errorf("analysis %q suggests invalid fix: pos (%v) > end (%v)", - act.a.Name, edit.Pos, edit.End) + act.a.Name, start, end) } - if eof := token.Pos(file.Base() + file.Size()); edit.End > eof { + if eof := token.Pos(file.Base() + file.Size()); end > eof { return fmt.Errorf("analysis %q suggests invalid fix: end (%v) past end of file (%v)", - act.a.Name, edit.End, eof) + act.a.Name, end, eof) + } + edit := diff.Edit{ + Start: file.Offset(start), + End: file.Offset(end), + New: string(edit.NewText), } - edit := diff.Edit{Start: file.Offset(edit.Pos), End: file.Offset(edit.End), New: string(edit.NewText)} editsForTokenFile[file] = append(editsForTokenFile[file], edit) } } @@ -485,11 +493,11 @@ func diff3Conflict(path string, xlabel, ylabel string, xedits, yedits []diff.Edi } oldlabel, old := "base", string(contents) - xdiff, err := diff.ToUnified(oldlabel, xlabel, old, xedits) + xdiff, err := diff.ToUnified(oldlabel, xlabel, old, xedits, diff.DefaultContextLines) if err != nil { return err } - ydiff, err := diff.ToUnified(oldlabel, ylabel, old, yedits) + ydiff, err := diff.ToUnified(oldlabel, ylabel, old, yedits, diff.DefaultContextLines) if err != nil { return err } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/appends/appends.go b/vendor/golang.org/x/tools/go/analysis/passes/appends/appends.go index f0b90a492..6976f0d90 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/appends/appends.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/appends/appends.go @@ -15,6 +15,7 @@ import ( "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 @@ -36,12 +37,9 @@ func run(pass *analysis.Pass) (interface{}, error) { } inspect.Preorder(nodeFilter, func(n ast.Node) { call := n.(*ast.CallExpr) - if ident, ok := call.Fun.(*ast.Ident); ok && ident.Name == "append" { - if _, ok := pass.TypesInfo.Uses[ident].(*types.Builtin); ok { - if len(call.Args) == 1 { - pass.ReportRangef(call, "append with no values") - } - } + 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") } }) diff --git a/vendor/golang.org/x/tools/go/analysis/passes/assign/assign.go b/vendor/golang.org/x/tools/go/analysis/passes/assign/assign.go index 10489bea1..3bfd50122 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/assign/assign.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/assign/assign.go @@ -18,6 +18,7 @@ import ( "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/astutil" "golang.org/x/tools/go/ast/inspector" ) @@ -77,7 +78,7 @@ func run(pass *analysis.Pass) (interface{}, error) { // isMapIndex returns true if e is a map index expression. func isMapIndex(info *types.Info, e ast.Expr) bool { - if idx, ok := analysisutil.Unparen(e).(*ast.IndexExpr); ok { + if idx, ok := astutil.Unparen(e).(*ast.IndexExpr); ok { if typ := info.Types[idx.X].Type; typ != nil { _, ok := typ.Underlying().(*types.Map) return ok diff --git a/vendor/golang.org/x/tools/go/analysis/passes/atomic/atomic.go b/vendor/golang.org/x/tools/go/analysis/passes/atomic/atomic.go index b40e081ec..931f9ca75 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/atomic/atomic.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/atomic/atomic.go @@ -8,12 +8,12 @@ import ( _ "embed" "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 @@ -52,18 +52,8 @@ func run(pass *analysis.Pass) (interface{}, error) { if !ok { continue } - sel, ok := call.Fun.(*ast.SelectorExpr) - if !ok { - continue - } - pkgIdent, _ := sel.X.(*ast.Ident) - pkgName, ok := pass.TypesInfo.Uses[pkgIdent].(*types.PkgName) - if !ok || pkgName.Imported().Path() != "sync/atomic" { - continue - } - - switch sel.Sel.Name { - case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr": + fn := typeutil.StaticCallee(pass.TypesInfo, call) + if analysisutil.IsFunctionNamed(fn, "sync/atomic", "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr") { checkAtomicAddAssignment(pass, n.Lhs[i], call) } } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/atomicalign/atomicalign.go b/vendor/golang.org/x/tools/go/analysis/passes/atomicalign/atomicalign.go index 01683e45a..aff6d25b3 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/atomicalign/atomicalign.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/atomicalign/atomicalign.go @@ -18,6 +18,7 @@ import ( "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" ) const Doc = "check for non-64-bits-aligned arguments to sync/atomic functions" @@ -42,31 +43,20 @@ func run(pass *analysis.Pass) (interface{}, error) { nodeFilter := []ast.Node{ (*ast.CallExpr)(nil), } + funcNames := []string{ + "AddInt64", "AddUint64", + "LoadInt64", "LoadUint64", + "StoreInt64", "StoreUint64", + "SwapInt64", "SwapUint64", + "CompareAndSwapInt64", "CompareAndSwapUint64", + } inspect.Preorder(nodeFilter, func(node ast.Node) { call := node.(*ast.CallExpr) - sel, ok := call.Fun.(*ast.SelectorExpr) - if !ok { - return - } - pkgIdent, ok := sel.X.(*ast.Ident) - if !ok { - return - } - pkgName, ok := pass.TypesInfo.Uses[pkgIdent].(*types.PkgName) - if !ok || pkgName.Imported().Path() != "sync/atomic" { - return - } - - switch sel.Sel.Name { - case "AddInt64", "AddUint64", - "LoadInt64", "LoadUint64", - "StoreInt64", "StoreUint64", - "SwapInt64", "SwapUint64", - "CompareAndSwapInt64", "CompareAndSwapUint64": - + fn := typeutil.StaticCallee(pass.TypesInfo, call) + if analysisutil.IsFunctionNamed(fn, "sync/atomic", funcNames...) { // For all the listed functions, the expression to check is always the first function argument. - check64BitAlignment(pass, sel.Sel.Name, call.Args[0]) + check64BitAlignment(pass, fn.Name(), call.Args[0]) } }) diff --git a/vendor/golang.org/x/tools/go/analysis/passes/bools/bools.go b/vendor/golang.org/x/tools/go/analysis/passes/bools/bools.go index 4219f087b..564329774 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/bools/bools.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/bools/bools.go @@ -14,6 +14,7 @@ import ( "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/astutil" "golang.org/x/tools/go/ast/inspector" ) @@ -83,7 +84,7 @@ func (op boolOp) commutativeSets(info *types.Info, e *ast.BinaryExpr, seen map[* i := 0 var sets [][]ast.Expr for j := 0; j <= len(exprs); j++ { - if j == len(exprs) || hasSideEffects(info, exprs[j]) { + if j == len(exprs) || analysisutil.HasSideEffects(info, exprs[j]) { if i < j { sets = append(sets, exprs[i:j]) } @@ -162,46 +163,13 @@ func (op boolOp) checkSuspect(pass *analysis.Pass, exprs []ast.Expr) { } } -// hasSideEffects reports whether evaluation of e has side effects. -func hasSideEffects(info *types.Info, e ast.Expr) bool { - safe := true - ast.Inspect(e, func(node ast.Node) bool { - switch n := node.(type) { - case *ast.CallExpr: - typVal := info.Types[n.Fun] - switch { - case typVal.IsType(): - // Type conversion, which is safe. - case typVal.IsBuiltin(): - // Builtin func, conservatively assumed to not - // be safe for now. - safe = false - return false - default: - // A non-builtin func or method call. - // Conservatively assume that all of them have - // side effects for now. - safe = false - return false - } - case *ast.UnaryExpr: - if n.Op == token.ARROW { - safe = false - return false - } - } - return true - }) - return !safe -} - // split returns a slice of all subexpressions in e that are connected by op. // For example, given 'a || (b || c) || d' with the or op, // split returns []{d, c, b, a}. // seen[e] is already true; any newly processed exprs are added to seen. func (op boolOp) split(e ast.Expr, seen map[*ast.BinaryExpr]bool) (exprs []ast.Expr) { for { - e = unparen(e) + e = astutil.Unparen(e) if b, ok := e.(*ast.BinaryExpr); ok && b.Op == op.tok { seen[b] = true exprs = append(exprs, op.split(b.Y, seen)...) @@ -213,14 +181,3 @@ func (op boolOp) split(e ast.Expr, seen map[*ast.BinaryExpr]bool) (exprs []ast.E } return } - -// unparen returns e with any enclosing parentheses stripped. -func unparen(e ast.Expr) ast.Expr { - for { - p, ok := e.(*ast.ParenExpr) - if !ok { - return e - } - e = p.X - } -} diff --git a/vendor/golang.org/x/tools/go/analysis/passes/buildssa/buildssa.go b/vendor/golang.org/x/tools/go/analysis/passes/buildssa/buildssa.go index 881b8fd67..f077ea282 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/buildssa/buildssa.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/buildssa/buildssa.go @@ -26,15 +26,13 @@ var Analyzer = &analysis.Analyzer{ } // SSA provides SSA-form intermediate representation for all the -// non-blank source functions in the current package. +// source functions in the current package. type SSA struct { Pkg *ssa.Package SrcFuncs []*ssa.Function } func run(pass *analysis.Pass) (interface{}, error) { - // Plundered from ssautil.BuildPackage. - // We must create a new Program for each Package because the // analysis API provides no place to hang a Program shared by // all Packages. Consequently, SSA Packages and Functions do not @@ -51,20 +49,10 @@ func run(pass *analysis.Pass) (interface{}, error) { prog := ssa.NewProgram(pass.Fset, mode) - // Create SSA packages for all imports. - // Order is not significant. - created := make(map[*types.Package]bool) - var createAll func(pkgs []*types.Package) - createAll = func(pkgs []*types.Package) { - for _, p := range pkgs { - if !created[p] { - created[p] = true - prog.CreatePackage(p, nil, nil, true) - createAll(p.Imports()) - } - } + // Create SSA packages for direct imports. + for _, p := range pass.Pkg.Imports() { + prog.CreatePackage(p, nil, nil, true) } - createAll(pass.Pkg.Imports()) // Create and build the primary package. ssapkg := prog.CreatePackage(pass.Pkg, pass.Files, pass.TypesInfo, false) @@ -76,16 +64,6 @@ func run(pass *analysis.Pass) (interface{}, error) { for _, f := range pass.Files { for _, decl := range f.Decls { if fdecl, ok := decl.(*ast.FuncDecl); ok { - - // SSA will not build a Function - // for a FuncDecl named blank. - // That's arguably too strict but - // relaxing it would break uniqueness of - // names of package members. - if fdecl.Name.Name == "_" { - continue - } - // (init functions have distinct Func // objects named "init" and distinct // ssa.Functions named "init#1", ...) diff --git a/vendor/golang.org/x/tools/go/analysis/passes/cgocall/cgocall.go b/vendor/golang.org/x/tools/go/analysis/passes/cgocall/cgocall.go index 98d9a777a..4e8643975 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/cgocall/cgocall.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/cgocall/cgocall.go @@ -19,6 +19,7 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/internal/analysisutil" + "golang.org/x/tools/go/ast/astutil" ) const debug = false @@ -64,7 +65,7 @@ func checkCgo(fset *token.FileSet, f *ast.File, info *types.Info, reportf func(t // Is this a C.f() call? var name string - if sel, ok := analysisutil.Unparen(call.Fun).(*ast.SelectorExpr); ok { + if sel, ok := astutil.Unparen(call.Fun).(*ast.SelectorExpr); ok { if id, ok := sel.X.(*ast.Ident); ok && id.Name == "C" { name = sel.Sel.Name } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/composite/composite.go b/vendor/golang.org/x/tools/go/analysis/passes/composite/composite.go index c7a49776f..847063bb3 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/composite/composite.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/composite/composite.go @@ -72,7 +72,7 @@ func run(pass *analysis.Pass) (interface{}, error) { } var structuralTypes []types.Type switch typ := typ.(type) { - case *typeparams.TypeParam: + case *types.TypeParam: terms, err := typeparams.StructuralTerms(typ) if err != nil { return // invalid type @@ -163,7 +163,7 @@ func isLocalType(pass *analysis.Pass, typ types.Type) bool { case *types.Named: // names in package foo are local to foo_test too return strings.TrimSuffix(x.Obj().Pkg().Path(), "_test") == strings.TrimSuffix(pass.Pkg.Path(), "_test") - case *typeparams.TypeParam: + case *types.TypeParam: return strings.TrimSuffix(x.Obj().Pkg().Path(), "_test") == strings.TrimSuffix(pass.Pkg.Path(), "_test") } return false diff --git a/vendor/golang.org/x/tools/go/analysis/passes/copylock/copylock.go b/vendor/golang.org/x/tools/go/analysis/passes/copylock/copylock.go index ec7727de7..6cbbc7e81 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/copylock/copylock.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/copylock/copylock.go @@ -16,6 +16,7 @@ import ( "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/astutil" "golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/internal/typeparams" ) @@ -223,7 +224,7 @@ func (path typePath) String() string { } func lockPathRhs(pass *analysis.Pass, x ast.Expr) typePath { - x = analysisutil.Unparen(x) // ignore parens on rhs + x = astutil.Unparen(x) // ignore parens on rhs if _, ok := x.(*ast.CompositeLit); ok { return nil @@ -233,7 +234,7 @@ func lockPathRhs(pass *analysis.Pass, x ast.Expr) typePath { return nil } if star, ok := x.(*ast.StarExpr); ok { - if _, ok := analysisutil.Unparen(star.X).(*ast.CallExpr); ok { + if _, ok := astutil.Unparen(star.X).(*ast.CallExpr); ok { // A call may return a pointer to a zero value. return nil } @@ -254,7 +255,7 @@ func lockPath(tpkg *types.Package, typ types.Type, seen map[types.Type]bool) typ } seen[typ] = true - if tpar, ok := typ.(*typeparams.TypeParam); ok { + if tpar, ok := typ.(*types.TypeParam); ok { terms, err := typeparams.StructuralTerms(tpar) if err != nil { return nil // invalid type @@ -319,9 +320,7 @@ func lockPath(tpkg *types.Package, typ types.Type, seen map[types.Type]bool) typ // In go1.10, sync.noCopy did not implement Locker. // (The Unlock method was added only in CL 121876.) // TODO(adonovan): remove workaround when we drop go1.10. - if named, ok := typ.(*types.Named); ok && - named.Obj().Name() == "noCopy" && - named.Obj().Pkg().Path() == "sync" { + if analysisutil.IsNamedType(typ, "sync", "noCopy") { return []string{typ.String()} } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/deepequalerrors/deepequalerrors.go b/vendor/golang.org/x/tools/go/analysis/passes/deepequalerrors/deepequalerrors.go index 3a1818764..5e17bd1ab 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/deepequalerrors/deepequalerrors.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/deepequalerrors/deepequalerrors.go @@ -15,6 +15,7 @@ import ( "golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/types/typeutil" + "golang.org/x/tools/internal/aliases" ) const Doc = `check for calls of reflect.DeepEqual on error values @@ -46,11 +47,8 @@ func run(pass *analysis.Pass) (interface{}, error) { } inspect.Preorder(nodeFilter, func(n ast.Node) { call := n.(*ast.CallExpr) - fn, ok := typeutil.Callee(pass.TypesInfo, call).(*types.Func) - if !ok { - return - } - if fn.FullName() == "reflect.DeepEqual" && hasError(pass, call.Args[0]) && hasError(pass, call.Args[1]) { + fn, _ := typeutil.Callee(pass.TypesInfo, call).(*types.Func) + if analysisutil.IsFunctionNamed(fn, "reflect", "DeepEqual") && hasError(pass, call.Args[0]) && hasError(pass, call.Args[1]) { pass.ReportRangef(call, "avoid using reflect.DeepEqual with errors") } }) @@ -104,7 +102,8 @@ func containsError(typ types.Type) bool { return true } } - case *types.Named: + case *types.Named, + *aliases.Alias: return check(t.Underlying()) // We list the remaining valid type kinds for completeness. diff --git a/vendor/golang.org/x/tools/go/analysis/passes/defers/defers.go b/vendor/golang.org/x/tools/go/analysis/passes/defers/defers.go index ed2a122f2..5e8e80a6a 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/defers/defers.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/defers/defers.go @@ -7,7 +7,6 @@ package defers import ( _ "embed" "go/ast" - "go/types" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" @@ -36,8 +35,7 @@ func run(pass *analysis.Pass) (interface{}, error) { checkDeferCall := func(node ast.Node) bool { switch v := node.(type) { case *ast.CallExpr: - fn, ok := typeutil.Callee(pass.TypesInfo, v).(*types.Func) - if ok && fn.Name() == "Since" && fn.Pkg().Path() == "time" { + if analysisutil.IsFunctionNamed(typeutil.StaticCallee(pass.TypesInfo, v), "time", "Since") { pass.Reportf(v.Pos(), "call to time.Since is not deferred") } case *ast.FuncLit: diff --git a/vendor/golang.org/x/tools/go/analysis/passes/errorsas/errorsas.go b/vendor/golang.org/x/tools/go/analysis/passes/errorsas/errorsas.go index 2fcbdfafb..7f62ad4c8 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/errorsas/errorsas.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/errorsas/errorsas.go @@ -51,15 +51,12 @@ func run(pass *analysis.Pass) (interface{}, error) { inspect.Preorder(nodeFilter, func(n ast.Node) { call := n.(*ast.CallExpr) fn := typeutil.StaticCallee(pass.TypesInfo, call) - if fn == nil { - return // not a static call + if !analysisutil.IsFunctionNamed(fn, "errors", "As") { + return } if len(call.Args) < 2 { return // not enough arguments, e.g. called with return values of another function } - if fn.FullName() != "errors.As" { - return - } if err := checkAsTarget(pass, call.Args[1]); err != nil { pass.ReportRangef(call, "%v", err) } @@ -69,9 +66,6 @@ func run(pass *analysis.Pass) (interface{}, error) { var errorType = types.Universe.Lookup("error").Type() -// pointerToInterfaceOrError reports whether the type of e is a pointer to an interface or a type implementing error, -// or is the empty interface. - // checkAsTarget reports an error if the second argument to errors.As is invalid. func checkAsTarget(pass *analysis.Pass, e ast.Expr) error { t := pass.TypesInfo.Types[e].Type diff --git a/vendor/golang.org/x/tools/go/analysis/passes/httpresponse/httpresponse.go b/vendor/golang.org/x/tools/go/analysis/passes/httpresponse/httpresponse.go index 61c3b764f..c6b6c81b4 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/httpresponse/httpresponse.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/httpresponse/httpresponse.go @@ -116,7 +116,7 @@ func isHTTPFuncOrMethodOnClient(info *types.Info, expr *ast.CallExpr) bool { if res.Len() != 2 { return false // the function called does not return two values. } - if ptr, ok := res.At(0).Type().(*types.Pointer); !ok || !isNamedType(ptr.Elem(), "net/http", "Response") { + if ptr, ok := res.At(0).Type().(*types.Pointer); !ok || !analysisutil.IsNamedType(ptr.Elem(), "net/http", "Response") { return false // the first return type is not *http.Response. } @@ -131,11 +131,11 @@ func isHTTPFuncOrMethodOnClient(info *types.Info, expr *ast.CallExpr) bool { return ok && id.Name == "http" // function in net/http package. } - if isNamedType(typ, "net/http", "Client") { + if analysisutil.IsNamedType(typ, "net/http", "Client") { return true // method on http.Client. } ptr, ok := typ.(*types.Pointer) - return ok && isNamedType(ptr.Elem(), "net/http", "Client") // method on *http.Client. + return ok && analysisutil.IsNamedType(ptr.Elem(), "net/http", "Client") // method on *http.Client. } // restOfBlock, given a traversal stack, finds the innermost containing @@ -171,13 +171,3 @@ func rootIdent(n ast.Node) *ast.Ident { return nil } } - -// isNamedType reports whether t is the named type path.name. -func isNamedType(t types.Type, path, name string) bool { - n, ok := t.(*types.Named) - if !ok { - return false - } - obj := n.Obj() - return obj.Name() == name && obj.Pkg() != nil && obj.Pkg().Path() == path -} diff --git a/vendor/golang.org/x/tools/go/analysis/passes/ifaceassert/parameterized.go b/vendor/golang.org/x/tools/go/analysis/passes/ifaceassert/parameterized.go index b84577fcf..12507f996 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/ifaceassert/parameterized.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/ifaceassert/parameterized.go @@ -95,14 +95,14 @@ func (w *tpWalker) isParameterized(typ types.Type) (res bool) { return w.isParameterized(t.Elem()) case *types.Named: - list := typeparams.NamedTypeArgs(t) + list := t.TypeArgs() for i, n := 0, list.Len(); i < n; i++ { if w.isParameterized(list.At(i)) { return true } } - case *typeparams.TypeParam: + case *types.TypeParam: return true default: diff --git a/vendor/golang.org/x/tools/go/analysis/passes/internal/analysisutil/util.go b/vendor/golang.org/x/tools/go/analysis/passes/internal/analysisutil/util.go index a8d84034d..3f01b3b55 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/internal/analysisutil/util.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/internal/analysisutil/util.go @@ -13,6 +13,8 @@ import ( "go/token" "go/types" "os" + + "golang.org/x/tools/internal/analysisinternal" ) // Format returns a string representation of the expression. @@ -55,17 +57,6 @@ func HasSideEffects(info *types.Info, e ast.Expr) bool { return !safe } -// Unparen returns e with any enclosing parentheses stripped. -func Unparen(e ast.Expr) ast.Expr { - for { - p, ok := e.(*ast.ParenExpr) - if !ok { - return e - } - e = p.X - } -} - // ReadFile reads a file and adds it to the FileSet // so that we can report errors against it using lineStart. func ReadFile(fset *token.FileSet, filename string) ([]byte, *token.File, error) { @@ -118,3 +109,48 @@ func Imports(pkg *types.Package, path string) bool { } return false } + +// IsNamedType reports whether t is the named type with the given package path +// and one of the given names. +// This function avoids allocating the concatenation of "pkg.Name", +// which is important for the performance of syntax matching. +func IsNamedType(t types.Type, pkgPath string, names ...string) bool { + n, ok := t.(*types.Named) + if !ok { + return false + } + obj := n.Obj() + if obj == nil || obj.Pkg() == nil || obj.Pkg().Path() != pkgPath { + return false + } + name := obj.Name() + for _, n := range names { + if name == n { + return true + } + } + return false +} + +// IsFunctionNamed reports whether f is a top-level function defined in the +// given package and has one of the given names. +// It returns false if f is nil or a method. +func IsFunctionNamed(f *types.Func, pkgPath string, names ...string) bool { + if f == nil { + return false + } + if f.Pkg() == nil || f.Pkg().Path() != pkgPath { + return false + } + if f.Type().(*types.Signature).Recv() != nil { + return false + } + for _, n := range names { + if f.Name() == n { + return true + } + } + return false +} + +var MustExtractDoc = analysisinternal.MustExtractDoc diff --git a/vendor/golang.org/x/tools/go/analysis/passes/loopclosure/doc.go b/vendor/golang.org/x/tools/go/analysis/passes/loopclosure/doc.go index dc544df1b..c95b1c1c9 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/loopclosure/doc.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/loopclosure/doc.go @@ -14,8 +14,12 @@ // in such a way (e.g. with go or defer) that it may outlive the loop // iteration and possibly observe the wrong value of the variable. // +// Note: An iteration variable can only outlive a loop iteration in Go versions <=1.21. +// In Go 1.22 and later, the loop variable lifetimes changed to create a new +// iteration variable per loop iteration. (See go.dev/issue/60078.) +// // In this example, all the deferred functions run after the loop has -// completed, so all observe the final value of v. +// completed, so all observe the final value of v [<go1.22]. // // for _, v := range list { // defer func() { @@ -32,7 +36,10 @@ // }() // } // -// The next example uses a go statement and has a similar problem. +// After Go version 1.22, the previous two for loops are equivalent +// and both are correct. +// +// The next example uses a go statement and has a similar problem [<go1.22]. // In addition, it has a data race because the loop updates v // concurrent with the goroutines accessing it. // @@ -56,7 +63,7 @@ // } // // The t.Parallel() call causes the rest of the function to execute -// concurrent with the loop. +// concurrent with the loop [<go1.22]. // // The analyzer reports references only in the last statement, // as it is not deep enough to understand the effects of subsequent diff --git a/vendor/golang.org/x/tools/go/analysis/passes/loopclosure/loopclosure.go b/vendor/golang.org/x/tools/go/analysis/passes/loopclosure/loopclosure.go index 5620c35fa..4724c9f3b 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/loopclosure/loopclosure.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/loopclosure/loopclosure.go @@ -14,6 +14,7 @@ import ( "golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/types/typeutil" + "golang.org/x/tools/internal/versions" ) //go:embed doc.go @@ -31,10 +32,15 @@ func run(pass *analysis.Pass) (interface{}, error) { inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) nodeFilter := []ast.Node{ + (*ast.File)(nil), (*ast.RangeStmt)(nil), (*ast.ForStmt)(nil), } - inspect.Preorder(nodeFilter, func(n ast.Node) { + inspect.Nodes(nodeFilter, func(n ast.Node, push bool) bool { + if !push { + // inspect.Nodes is slightly suboptimal as we only use push=true. + return true + } // Find the variables updated by the loop statement. var vars []types.Object addVar := func(expr ast.Expr) { @@ -46,6 +52,11 @@ func run(pass *analysis.Pass) (interface{}, error) { } var body *ast.BlockStmt switch n := n.(type) { + case *ast.File: + // Only traverse the file if its goversion is strictly before go1.22. + goversion := versions.Lang(versions.FileVersions(pass.TypesInfo, n)) + // goversion is empty for older go versions (or the version is invalid). + return goversion == "" || versions.Compare(goversion, "go1.22") < 0 case *ast.RangeStmt: body = n.Body addVar(n.Key) @@ -64,7 +75,7 @@ func run(pass *analysis.Pass) (interface{}, error) { } } if vars == nil { - return + return true } // Inspect statements to find function literals that may be run outside of @@ -113,6 +124,7 @@ func run(pass *analysis.Pass) (interface{}, error) { } } } + return true }) return nil, nil } @@ -359,20 +371,5 @@ func isMethodCall(info *types.Info, expr ast.Expr, pkgPath, typeName, method str if ptr, ok := recv.Type().(*types.Pointer); ok { rtype = ptr.Elem() } - named, ok := rtype.(*types.Named) - if !ok { - return false - } - if named.Obj().Name() != typeName { - return false - } - pkg := f.Pkg() - if pkg == nil { - return false - } - if pkg.Path() != pkgPath { - return false - } - - return true + return analysisutil.IsNamedType(rtype, pkgPath, typeName) } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/lostcancel/lostcancel.go b/vendor/golang.org/x/tools/go/analysis/passes/lostcancel/lostcancel.go index 2bccb6750..bf56a5c06 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/lostcancel/lostcancel.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/lostcancel/lostcancel.go @@ -172,7 +172,18 @@ func runFunc(pass *analysis.Pass, node ast.Node) { if ret := lostCancelPath(pass, g, v, stmt, sig); ret != nil { lineno := pass.Fset.Position(stmt.Pos()).Line pass.ReportRangef(stmt, "the %s function is not used on all paths (possible context leak)", v.Name()) - pass.ReportRangef(ret, "this return statement may be reached without using the %s var defined on line %d", v.Name(), lineno) + + pos, end := ret.Pos(), ret.End() + // golang/go#64547: cfg.Block.Return may return a synthetic + // ReturnStmt that overflows the file. + if pass.Fset.File(pos) != pass.Fset.File(end) { + end = pos + } + pass.Report(analysis.Diagnostic{ + Pos: pos, + End: end, + Message: fmt.Sprintf("this return statement may be reached without using the %s var defined on line %d", v.Name(), lineno), + }) } } } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/nilfunc/nilfunc.go b/vendor/golang.org/x/tools/go/analysis/passes/nilfunc/nilfunc.go index 6df134399..778f7f1f8 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/nilfunc/nilfunc.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/nilfunc/nilfunc.go @@ -62,7 +62,7 @@ func run(pass *analysis.Pass) (interface{}, error) { obj = pass.TypesInfo.Uses[v] case *ast.SelectorExpr: obj = pass.TypesInfo.Uses[v.Sel] - case *ast.IndexExpr, *typeparams.IndexListExpr: + case *ast.IndexExpr, *ast.IndexListExpr: // Check generic functions such as "f[T1,T2]". x, _, _, _ := typeparams.UnpackIndexExpr(v) if id, ok := x.(*ast.Ident); ok { diff --git a/vendor/golang.org/x/tools/go/analysis/passes/nilness/nilness.go b/vendor/golang.org/x/tools/go/analysis/passes/nilness/nilness.go index d1ca0748e..5e14c096a 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/nilness/nilness.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/nilness/nilness.go @@ -38,11 +38,15 @@ func run(pass *analysis.Pass) (interface{}, error) { func runFunc(pass *analysis.Pass, fn *ssa.Function) { reportf := func(category string, pos token.Pos, format string, args ...interface{}) { - pass.Report(analysis.Diagnostic{ - Pos: pos, - Category: category, - Message: fmt.Sprintf(format, args...), - }) + // We ignore nil-checking ssa.Instructions + // that don't correspond to syntax. + if pos.IsValid() { + pass.Report(analysis.Diagnostic{ + Pos: pos, + Category: category, + Message: fmt.Sprintf(format, args...), + }) + } } // notNil reports an error if v is provably nil. @@ -185,6 +189,42 @@ func runFunc(pass *analysis.Pass, fn *ssa.Function) { } } + // In code of the form: + // + // if ptr, ok := x.(*T); ok { ... } else { fsucc } + // + // the fsucc block learns that ptr == nil, + // since that's its zero value. + if If, ok := b.Instrs[len(b.Instrs)-1].(*ssa.If); ok { + // Handle "if ok" and "if !ok" variants. + cond, fsucc := If.Cond, b.Succs[1] + if unop, ok := cond.(*ssa.UnOp); ok && unop.Op == token.NOT { + cond, fsucc = unop.X, b.Succs[0] + } + + // Match pattern: + // t0 = typeassert (pointerlike) + // t1 = extract t0 #0 // ptr + // t2 = extract t0 #1 // ok + // if t2 goto tsucc, fsucc + if extract1, ok := cond.(*ssa.Extract); ok && extract1.Index == 1 { + if assert, ok := extract1.Tuple.(*ssa.TypeAssert); ok && + isNillable(assert.AssertedType) { + for _, pinstr := range *assert.Referrers() { + if extract0, ok := pinstr.(*ssa.Extract); ok && + extract0.Index == 0 && + extract0.Tuple == extract1.Tuple { + for _, d := range b.Dominees() { + if len(d.Preds) == 1 && d == fsucc { + visit(d, append(stack, fact{extract0, isnil})) + } + } + } + } + } + } + } + for _, d := range b.Dominees() { visit(d, stack) } @@ -356,3 +396,23 @@ func (ff facts) negate() facts { } return nn } + +func is[T any](x any) bool { + _, ok := x.(T) + return ok +} + +func isNillable(t types.Type) bool { + switch t := typeparams.CoreType(t).(type) { + case *types.Pointer, + *types.Map, + *types.Signature, + *types.Chan, + *types.Interface, + *types.Slice: + return true + case *types.Basic: + return t == types.Typ[types.UnsafePointer] + } + return false +} diff --git a/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go b/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go index b2b8c67c7..070654f01 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go @@ -511,15 +511,10 @@ func isFormatter(typ types.Type) bool { sig := fn.Type().(*types.Signature) return sig.Params().Len() == 2 && sig.Results().Len() == 0 && - isNamed(sig.Params().At(0).Type(), "fmt", "State") && + analysisutil.IsNamedType(sig.Params().At(0).Type(), "fmt", "State") && types.Identical(sig.Params().At(1).Type(), types.Typ[types.Rune]) } -func isNamed(T types.Type, pkgpath, name string) bool { - named, ok := T.(*types.Named) - return ok && named.Obj().Pkg().Path() == pkgpath && named.Obj().Name() == name -} - // formatState holds the parsed representation of a printf directive such as "%3.*[4]d". // It is constructed by parsePrintfVerb. type formatState struct { diff --git a/vendor/golang.org/x/tools/go/analysis/passes/printf/types.go b/vendor/golang.org/x/tools/go/analysis/passes/printf/types.go index 7cbb0bdbf..ab98e5699 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/printf/types.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/printf/types.go @@ -72,7 +72,7 @@ func (m *argMatcher) match(typ types.Type, topLevel bool) bool { return true } - if typ, _ := typ.(*typeparams.TypeParam); typ != nil { + if typ, _ := typ.(*types.TypeParam); typ != nil { // Avoid infinite recursion through type parameters. if m.seen[typ] { return true diff --git a/vendor/golang.org/x/tools/go/analysis/passes/reflectvaluecompare/reflectvaluecompare.go b/vendor/golang.org/x/tools/go/analysis/passes/reflectvaluecompare/reflectvaluecompare.go index 27677139e..6789d7357 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/reflectvaluecompare/reflectvaluecompare.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/reflectvaluecompare/reflectvaluecompare.go @@ -49,11 +49,8 @@ func run(pass *analysis.Pass) (interface{}, error) { } } case *ast.CallExpr: - fn, ok := typeutil.Callee(pass.TypesInfo, n).(*types.Func) - if !ok { - return - } - if fn.FullName() == "reflect.DeepEqual" && (isReflectValue(pass, n.Args[0]) || isReflectValue(pass, n.Args[1])) { + fn, _ := typeutil.Callee(pass.TypesInfo, n).(*types.Func) + if analysisutil.IsFunctionNamed(fn, "reflect", "DeepEqual") && (isReflectValue(pass, n.Args[0]) || isReflectValue(pass, n.Args[1])) { pass.ReportRangef(n, "avoid using reflect.DeepEqual with reflect.Value") } } @@ -68,11 +65,7 @@ func isReflectValue(pass *analysis.Pass, e ast.Expr) bool { return false } // See if the type is reflect.Value - named, ok := tv.Type.(*types.Named) - if !ok { - return false - } - if obj := named.Obj(); obj == nil || obj.Pkg() == nil || obj.Pkg().Path() != "reflect" || obj.Name() != "Value" { + if !analysisutil.IsNamedType(tv.Type, "reflect", "Value") { return false } if _, ok := e.(*ast.CompositeLit); ok { diff --git a/vendor/golang.org/x/tools/go/analysis/passes/shift/shift.go b/vendor/golang.org/x/tools/go/analysis/passes/shift/shift.go index bafb9112e..e272df709 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/shift/shift.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/shift/shift.go @@ -99,7 +99,7 @@ func checkLongShift(pass *analysis.Pass, node ast.Node, x, y ast.Expr) { } var structuralTypes []types.Type switch t := t.(type) { - case *typeparams.TypeParam: + case *types.TypeParam: terms, err := typeparams.StructuralTerms(t) if err != nil { return // invalid type diff --git a/vendor/golang.org/x/tools/go/analysis/passes/slog/slog.go b/vendor/golang.org/x/tools/go/analysis/passes/slog/slog.go index 92c1da8ef..a1323c3e6 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/slog/slog.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/slog/slog.go @@ -139,7 +139,7 @@ func run(pass *analysis.Pass) (any, error) { } func isAttr(t types.Type) bool { - return isNamed(t, "log/slog", "Attr") + return analysisutil.IsNamedType(t, "log/slog", "Attr") } // shortName returns a name for the function that is shorter than FullName. @@ -232,12 +232,3 @@ func isMethodExpr(info *types.Info, c *ast.CallExpr) bool { sel := info.Selections[s] return sel != nil && sel.Kind() == types.MethodExpr } - -// isNamed reports whether t is exactly a named type in a package with a given path. -func isNamed(t types.Type, path, name string) bool { - if n, ok := t.(*types.Named); ok { - obj := n.Obj() - return obj.Pkg() != nil && obj.Pkg().Path() == path && obj.Name() == name - } - return false -} diff --git a/vendor/golang.org/x/tools/go/analysis/passes/sortslice/analyzer.go b/vendor/golang.org/x/tools/go/analysis/passes/sortslice/analyzer.go index 1fe206b0f..6c151a02c 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/sortslice/analyzer.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/sortslice/analyzer.go @@ -47,12 +47,7 @@ func run(pass *analysis.Pass) (interface{}, error) { inspect.Preorder(nodeFilter, func(n ast.Node) { call := n.(*ast.CallExpr) fn, _ := typeutil.Callee(pass.TypesInfo, call).(*types.Func) - if fn == nil { - return - } - - fnName := fn.FullName() - if fnName != "sort.Slice" && fnName != "sort.SliceStable" && fnName != "sort.SliceIsSorted" { + if !analysisutil.IsFunctionNamed(fn, "sort", "Slice", "SliceStable", "SliceIsSorted") { return } @@ -131,7 +126,7 @@ func run(pass *analysis.Pass) (interface{}, error) { pass.Report(analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), - Message: fmt.Sprintf("%s's argument must be a slice; is called with %s", fnName, typ.String()), + Message: fmt.Sprintf("%s's argument must be a slice; is called with %s", fn.FullName(), typ.String()), SuggestedFixes: fixes, }) }) diff --git a/vendor/golang.org/x/tools/go/analysis/passes/stringintconv/string.go b/vendor/golang.org/x/tools/go/analysis/passes/stringintconv/string.go index bb04dae62..005e2e54b 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/stringintconv/string.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/stringintconv/string.go @@ -15,6 +15,7 @@ import ( "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/internal/aliases" "golang.org/x/tools/internal/typeparams" ) @@ -194,16 +195,15 @@ func run(pass *analysis.Pass) (interface{}, error) { func structuralTypes(t types.Type) ([]types.Type, error) { var structuralTypes []types.Type - switch t := t.(type) { - case *typeparams.TypeParam: - terms, err := typeparams.StructuralTerms(t) + if tp, ok := aliases.Unalias(t).(*types.TypeParam); ok { + terms, err := typeparams.StructuralTerms(tp) if err != nil { return nil, err } for _, term := range terms { structuralTypes = append(structuralTypes, term.Type()) } - default: + } else { structuralTypes = append(structuralTypes, t) } return structuralTypes, nil diff --git a/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/doc.go b/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/doc.go index a68adb12b..4cd5b71e9 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/doc.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/doc.go @@ -7,7 +7,7 @@ // // # Analyzer testinggoroutine // -// testinggoroutine: report calls to (*testing.T).Fatal from goroutines started by a test. +// testinggoroutine: report calls to (*testing.T).Fatal from goroutines started by a test // // Functions that abruptly terminate a test, such as the Fatal, Fatalf, FailNow, and // Skip{,f,Now} methods of *testing.T, must be called from the test goroutine itself. diff --git a/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/testinggoroutine.go b/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/testinggoroutine.go index 907b71503..dc5307a15 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/testinggoroutine.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/testinggoroutine.go @@ -6,18 +6,28 @@ package testinggoroutine 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/astutil" "golang.org/x/tools/go/ast/inspector" - "golang.org/x/tools/internal/typeparams" + "golang.org/x/tools/go/types/typeutil" ) //go:embed doc.go var doc string +var reportSubtest bool + +func init() { + Analyzer.Flags.BoolVar(&reportSubtest, "subtest", false, "whether to check if t.Run subtest is terminated correctly; experimental") +} + var Analyzer = &analysis.Analyzer{ Name: "testinggoroutine", Doc: analysisutil.MustExtractDoc(doc, "testinggoroutine"), @@ -26,15 +36,6 @@ var Analyzer = &analysis.Analyzer{ Run: run, } -var forbidden = map[string]bool{ - "FailNow": true, - "Fatal": true, - "Fatalf": true, - "Skip": true, - "Skipf": true, - "SkipNow": true, -} - func run(pass *analysis.Pass) (interface{}, error) { inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) @@ -42,38 +43,90 @@ func run(pass *analysis.Pass) (interface{}, error) { return nil, nil } - // Filter out anything that isn't a function declaration. - onlyFuncs := []ast.Node{ - (*ast.FuncDecl)(nil), + toDecl := localFunctionDecls(pass.TypesInfo, pass.Files) + + // asyncs maps nodes whose statements will be executed concurrently + // with respect to some test function, to the call sites where they + // are invoked asynchronously. There may be multiple such call sites + // for e.g. test helpers. + asyncs := make(map[ast.Node][]*asyncCall) + var regions []ast.Node + addCall := func(c *asyncCall) { + if c != nil { + r := c.region + if asyncs[r] == nil { + regions = append(regions, r) + } + asyncs[r] = append(asyncs[r], c) + } } - inspect.Nodes(onlyFuncs, func(node ast.Node, push bool) bool { - fnDecl, ok := node.(*ast.FuncDecl) - if !ok { + // Collect all of the go callee() and t.Run(name, callee) extents. + inspect.Nodes([]ast.Node{ + (*ast.FuncDecl)(nil), + (*ast.GoStmt)(nil), + (*ast.CallExpr)(nil), + }, func(node ast.Node, push bool) bool { + if !push { return false } + switch node := node.(type) { + case *ast.FuncDecl: + return hasBenchmarkOrTestParams(node) - if !hasBenchmarkOrTestParams(fnDecl) { - return false + case *ast.GoStmt: + c := goAsyncCall(pass.TypesInfo, node, toDecl) + addCall(c) + + case *ast.CallExpr: + c := tRunAsyncCall(pass.TypesInfo, node) + addCall(c) } + return true + }) - // Now traverse the benchmark/test's body and check that none of the - // forbidden methods are invoked in the goroutines within the body. - ast.Inspect(fnDecl, func(n ast.Node) bool { - goStmt, ok := n.(*ast.GoStmt) + // Check for t.Forbidden() calls within each region r that is a + // callee in some go r() or a t.Run("name", r). + // + // Also considers a special case when r is a go t.Forbidden() call. + for _, region := range regions { + ast.Inspect(region, func(n ast.Node) bool { + if n == region { + return true // always descend into the region itself. + } else if asyncs[n] != nil { + return false // will be visited by another region. + } + + call, ok := n.(*ast.CallExpr) if !ok { return true } + x, sel, fn := forbiddenMethod(pass.TypesInfo, call) + if x == nil { + return true + } - checkGoStmt(pass, goStmt) + for _, e := range asyncs[region] { + if !withinScope(e.scope, x) { + forbidden := formatMethod(sel, fn) // e.g. "(*testing.T).Forbidden - // No need to further traverse the GoStmt since right - // above we manually traversed it in the ast.Inspect(goStmt, ...) - return false + var context string + var where analysis.Range = e.async // Put the report at the go fun() or t.Run(name, fun). + if _, local := e.fun.(*ast.FuncLit); local { + where = call // Put the report at the t.Forbidden() call. + } else if id, ok := e.fun.(*ast.Ident); ok { + context = fmt.Sprintf(" (%s calls %s)", id.Name, forbidden) + } + if _, ok := e.async.(*ast.GoStmt); ok { + pass.ReportRangef(where, "call to %s from a non-test goroutine%s", forbidden, context) + } else if reportSubtest { + pass.ReportRangef(where, "call to %s on %s defined outside of the subtest%s", forbidden, x.Name(), context) + } + } + } + return true }) - - return false - }) + } return nil, nil } @@ -100,7 +153,6 @@ func typeIsTestingDotTOrB(expr ast.Expr) (string, bool) { if !ok { return "", false } - varPkg := selExpr.X.(*ast.Ident) if varPkg.Name != "testing" { return "", false @@ -111,73 +163,116 @@ func typeIsTestingDotTOrB(expr ast.Expr) (string, bool) { return varTypeName, ok } -// goStmtFunc returns the ast.Node of a call expression -// that was invoked as a go statement. Currently, only -// function literals declared in the same function, and -// static calls within the same package are supported. -func goStmtFun(goStmt *ast.GoStmt) ast.Node { - switch fun := goStmt.Call.Fun.(type) { - case *ast.IndexExpr, *typeparams.IndexListExpr: - x, _, _, _ := typeparams.UnpackIndexExpr(fun) - id, _ := x.(*ast.Ident) - if id == nil { - break - } - if id.Obj == nil { - break - } - if funDecl, ok := id.Obj.Decl.(ast.Node); ok { - return funDecl - } - case *ast.Ident: - // TODO(cuonglm): improve this once golang/go#48141 resolved. - if fun.Obj == nil { - break - } - if funDecl, ok := fun.Obj.Decl.(ast.Node); ok { - return funDecl - } - case *ast.FuncLit: - return goStmt.Call.Fun +// asyncCall describes a region of code that needs to be checked for +// t.Forbidden() calls as it is started asynchronously from an async +// node go fun() or t.Run(name, fun). +type asyncCall struct { + region ast.Node // region of code to check for t.Forbidden() calls. + async ast.Node // *ast.GoStmt or *ast.CallExpr (for t.Run) + scope ast.Node // Report t.Forbidden() if t is not declared within scope. + fun ast.Expr // fun in go fun() or t.Run(name, fun) +} + +// withinScope returns true if x.Pos() is in [scope.Pos(), scope.End()]. +func withinScope(scope ast.Node, x *types.Var) bool { + if scope != nil { + return x.Pos() != token.NoPos && scope.Pos() <= x.Pos() && x.Pos() <= scope.End() } - return goStmt.Call + return false } -// checkGoStmt traverses the goroutine and checks for the -// use of the forbidden *testing.(B, T) methods. -func checkGoStmt(pass *analysis.Pass, goStmt *ast.GoStmt) { - fn := goStmtFun(goStmt) - // Otherwise examine the goroutine to check for the forbidden methods. - ast.Inspect(fn, func(n ast.Node) bool { - selExpr, ok := n.(*ast.SelectorExpr) - if !ok { - return true - } +// goAsyncCall returns the extent of a call from a go fun() statement. +func goAsyncCall(info *types.Info, goStmt *ast.GoStmt, toDecl func(*types.Func) *ast.FuncDecl) *asyncCall { + call := goStmt.Call - _, bad := forbidden[selExpr.Sel.Name] - if !bad { - return true + fun := astutil.Unparen(call.Fun) + if id := funcIdent(fun); id != nil { + if lit := funcLitInScope(id); lit != nil { + return &asyncCall{region: lit, async: goStmt, scope: nil, fun: fun} } + } - // Now filter out false positives by the import-path/type. - ident, ok := selExpr.X.(*ast.Ident) - if !ok { - return true + if fn := typeutil.StaticCallee(info, call); fn != nil { // static call or method in the package? + if decl := toDecl(fn); decl != nil { + return &asyncCall{region: decl, async: goStmt, scope: nil, fun: fun} } - if ident.Obj == nil || ident.Obj.Decl == nil { - return true - } - field, ok := ident.Obj.Decl.(*ast.Field) - if !ok { - return true - } - if typeName, ok := typeIsTestingDotTOrB(field.Type); ok { - var fnRange analysis.Range = goStmt - if _, ok := fn.(*ast.FuncLit); ok { - fnRange = selExpr - } - pass.ReportRangef(fnRange, "call to (*%s).%s from a non-test goroutine", typeName, selExpr.Sel) + } + + // Check go statement for go t.Forbidden() or go func(){t.Forbidden()}(). + return &asyncCall{region: goStmt, async: goStmt, scope: nil, fun: fun} +} + +// tRunAsyncCall returns the extent of a call from a t.Run("name", fun) expression. +func tRunAsyncCall(info *types.Info, call *ast.CallExpr) *asyncCall { + if len(call.Args) != 2 { + return nil + } + run := typeutil.Callee(info, call) + if run, ok := run.(*types.Func); !ok || !isMethodNamed(run, "testing", "Run") { + return nil + } + + fun := astutil.Unparen(call.Args[1]) + if lit, ok := fun.(*ast.FuncLit); ok { // function lit? + return &asyncCall{region: lit, async: call, scope: lit, fun: fun} + } + + if id := funcIdent(fun); id != nil { + if lit := funcLitInScope(id); lit != nil { // function lit in variable? + return &asyncCall{region: lit, async: call, scope: lit, fun: fun} } - return true - }) + } + + // Check within t.Run(name, fun) for calls to t.Forbidden, + // e.g. t.Run(name, func(t *testing.T){ t.Forbidden() }) + return &asyncCall{region: call, async: call, scope: fun, fun: fun} +} + +var forbidden = []string{ + "FailNow", + "Fatal", + "Fatalf", + "Skip", + "Skipf", + "SkipNow", +} + +// forbiddenMethod decomposes a call x.m() into (x, x.m, m) where +// x is a variable, x.m is a selection, and m is the static callee m. +// Returns (nil, nil, nil) if call is not of this form. +func forbiddenMethod(info *types.Info, call *ast.CallExpr) (*types.Var, *types.Selection, *types.Func) { + // Compare to typeutil.StaticCallee. + fun := astutil.Unparen(call.Fun) + selExpr, ok := fun.(*ast.SelectorExpr) + if !ok { + return nil, nil, nil + } + sel := info.Selections[selExpr] + if sel == nil { + return nil, nil, nil + } + + var x *types.Var + if id, ok := astutil.Unparen(selExpr.X).(*ast.Ident); ok { + x, _ = info.Uses[id].(*types.Var) + } + if x == nil { + return nil, nil, nil + } + + fn, _ := sel.Obj().(*types.Func) + if fn == nil || !isMethodNamed(fn, "testing", forbidden...) { + return nil, nil, nil + } + return x, sel, fn +} + +func formatMethod(sel *types.Selection, fn *types.Func) string { + var ptr string + rtype := sel.Recv() + if p, ok := rtype.(*types.Pointer); ok { + ptr = "*" + rtype = p.Elem() + } + return fmt.Sprintf("(%s%s).%s", ptr, rtype.String(), fn.Name()) } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/util.go b/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/util.go new file mode 100644 index 000000000..d156851db --- /dev/null +++ b/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/util.go @@ -0,0 +1,96 @@ +// 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 testinggoroutine + +import ( + "go/ast" + "go/types" + + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/internal/typeparams" +) + +// AST and types utilities that not specific to testinggoroutines. + +// localFunctionDecls returns a mapping from *types.Func to *ast.FuncDecl in files. +func localFunctionDecls(info *types.Info, files []*ast.File) func(*types.Func) *ast.FuncDecl { + var fnDecls map[*types.Func]*ast.FuncDecl // computed lazily + return func(f *types.Func) *ast.FuncDecl { + if f != nil && fnDecls == nil { + fnDecls = make(map[*types.Func]*ast.FuncDecl) + for _, file := range files { + for _, decl := range file.Decls { + if fnDecl, ok := decl.(*ast.FuncDecl); ok { + if fn, ok := info.Defs[fnDecl.Name].(*types.Func); ok { + fnDecls[fn] = fnDecl + } + } + } + } + } + // TODO: once we only support go1.19+, set f = f.Origin() here. + return fnDecls[f] + } +} + +// isMethodNamed returns true if f is a method defined +// in package with the path pkgPath with a name in names. +func isMethodNamed(f *types.Func, pkgPath string, names ...string) bool { + if f == nil { + return false + } + if f.Pkg() == nil || f.Pkg().Path() != pkgPath { + return false + } + if f.Type().(*types.Signature).Recv() == nil { + return false + } + for _, n := range names { + if f.Name() == n { + return true + } + } + return false +} + +func funcIdent(fun ast.Expr) *ast.Ident { + switch fun := astutil.Unparen(fun).(type) { + case *ast.IndexExpr, *ast.IndexListExpr: + x, _, _, _ := typeparams.UnpackIndexExpr(fun) // necessary? + id, _ := x.(*ast.Ident) + return id + case *ast.Ident: + return fun + default: + return nil + } +} + +// funcLitInScope returns a FuncLit that id is at least initially assigned to. +// +// TODO: This is closely tied to id.Obj which is deprecated. +func funcLitInScope(id *ast.Ident) *ast.FuncLit { + // Compare to (*ast.Object).Pos(). + if id.Obj == nil { + return nil + } + var rhs ast.Expr + switch d := id.Obj.Decl.(type) { + case *ast.AssignStmt: + for i, x := range d.Lhs { + if ident, isIdent := x.(*ast.Ident); isIdent && ident.Name == id.Name && i < len(d.Rhs) { + rhs = d.Rhs[i] + } + } + case *ast.ValueSpec: + for i, n := range d.Names { + if n.Name == id.Name && i < len(d.Values) { + rhs = d.Values[i] + } + } + } + lit, _ := rhs.(*ast.FuncLit) + return lit +} diff --git a/vendor/golang.org/x/tools/go/analysis/passes/tests/tests.go b/vendor/golang.org/x/tools/go/analysis/passes/tests/tests.go index 9589a46a5..6db12f3cb 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/tests/tests.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/tests/tests.go @@ -17,7 +17,6 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/internal/analysisutil" - "golang.org/x/tools/internal/typeparams" ) //go:embed doc.go @@ -257,13 +256,7 @@ func isTestingType(typ types.Type, testingType string) bool { if !ok { return false } - named, ok := ptr.Elem().(*types.Named) - if !ok { - return false - } - obj := named.Obj() - // obj.Pkg is nil for the error type. - return obj != nil && obj.Pkg() != nil && obj.Pkg().Path() == "testing" && obj.Name() == testingType + return analysisutil.IsNamedType(ptr.Elem(), "testing", testingType) } // Validate that fuzz target function's arguments are of accepted types. @@ -397,7 +390,7 @@ func checkExampleName(pass *analysis.Pass, fn *ast.FuncDecl) { if results := fn.Type.Results; results != nil && len(results.List) != 0 { pass.Reportf(fn.Pos(), "%s should return nothing", fnName) } - if tparams := typeparams.ForFuncType(fn.Type); tparams != nil && len(tparams.List) > 0 { + if tparams := fn.Type.TypeParams; tparams != nil && len(tparams.List) > 0 { pass.Reportf(fn.Pos(), "%s should not have type params", fnName) } @@ -466,7 +459,7 @@ func checkTest(pass *analysis.Pass, fn *ast.FuncDecl, prefix string) { return } - if tparams := typeparams.ForFuncType(fn.Type); tparams != nil && len(tparams.List) > 0 { + if tparams := fn.Type.TypeParams; tparams != nil && len(tparams.List) > 0 { // Note: cmd/go/internal/load also errors about TestXXX and BenchmarkXXX functions with type parameters. // We have currently decided to also warn before compilation/package loading. This can help users in IDEs. // TODO(adonovan): use ReportRangef(tparams). diff --git a/vendor/golang.org/x/tools/go/analysis/passes/timeformat/timeformat.go b/vendor/golang.org/x/tools/go/analysis/passes/timeformat/timeformat.go index c45b9fa54..eb84502bd 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/timeformat/timeformat.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/timeformat/timeformat.go @@ -88,29 +88,16 @@ func run(pass *analysis.Pass) (interface{}, error) { } func isTimeDotFormat(f *types.Func) bool { - if f.Name() != "Format" || f.Pkg().Path() != "time" { - return false - } - sig, ok := f.Type().(*types.Signature) - if !ok { + if f.Name() != "Format" || f.Pkg() == nil || f.Pkg().Path() != "time" { return false } // Verify that the receiver is time.Time. - recv := sig.Recv() - if recv == nil { - return false - } - named, ok := recv.Type().(*types.Named) - return ok && named.Obj().Name() == "Time" + recv := f.Type().(*types.Signature).Recv() + return recv != nil && analysisutil.IsNamedType(recv.Type(), "time", "Time") } func isTimeDotParse(f *types.Func) bool { - if f.Name() != "Parse" || f.Pkg().Path() != "time" { - return false - } - // Verify that there is no receiver. - sig, ok := f.Type().(*types.Signature) - return ok && sig.Recv() == nil + return analysisutil.IsFunctionNamed(f, "time", "Parse") } // badFormatAt return the start of a bad format in e or -1 if no bad format is found. diff --git a/vendor/golang.org/x/tools/go/analysis/passes/unmarshal/unmarshal.go b/vendor/golang.org/x/tools/go/analysis/passes/unmarshal/unmarshal.go index 7043baa89..f4e73528b 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/unmarshal/unmarshal.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/unmarshal/unmarshal.go @@ -14,7 +14,6 @@ import ( "golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/types/typeutil" - "golang.org/x/tools/internal/typeparams" ) //go:embed doc.go @@ -92,7 +91,7 @@ func run(pass *analysis.Pass) (interface{}, error) { t := pass.TypesInfo.Types[call.Args[argidx]].Type switch t.Underlying().(type) { - case *types.Pointer, *types.Interface, *typeparams.TypeParam: + case *types.Pointer, *types.Interface, *types.TypeParam: return } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/unsafeptr/unsafeptr.go b/vendor/golang.org/x/tools/go/analysis/passes/unsafeptr/unsafeptr.go index e43ac2078..32e71ef97 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/unsafeptr/unsafeptr.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/unsafeptr/unsafeptr.go @@ -15,6 +15,7 @@ import ( "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/astutil" "golang.org/x/tools/go/ast/inspector" ) @@ -68,7 +69,7 @@ func isSafeUintptr(info *types.Info, x ast.Expr) bool { // Check unsafe.Pointer safety rules according to // https://golang.org/pkg/unsafe/#Pointer. - switch x := analysisutil.Unparen(x).(type) { + switch x := astutil.Unparen(x).(type) { case *ast.SelectorExpr: // "(6) Conversion of a reflect.SliceHeader or // reflect.StringHeader Data field to or from Pointer." @@ -104,8 +105,7 @@ func isSafeUintptr(info *types.Info, x ast.Expr) bool { } switch sel.Sel.Name { case "Pointer", "UnsafeAddr": - t, ok := info.Types[sel.X].Type.(*types.Named) - if ok && t.Obj().Pkg().Path() == "reflect" && t.Obj().Name() == "Value" { + if analysisutil.IsNamedType(info.Types[sel.X].Type, "reflect", "Value") { return true } } @@ -118,7 +118,7 @@ func isSafeUintptr(info *types.Info, x ast.Expr) bool { // isSafeArith reports whether x is a pointer arithmetic expression that is safe // to convert to unsafe.Pointer. func isSafeArith(info *types.Info, x ast.Expr) bool { - switch x := analysisutil.Unparen(x).(type) { + switch x := astutil.Unparen(x).(type) { case *ast.CallExpr: // Base case: initial conversion from unsafe.Pointer to uintptr. return len(x.Args) == 1 && @@ -153,13 +153,5 @@ func hasBasicType(info *types.Info, x ast.Expr, kind types.BasicKind) bool { // isReflectHeader reports whether t is reflect.SliceHeader or reflect.StringHeader. func isReflectHeader(t types.Type) bool { - if named, ok := t.(*types.Named); ok { - if obj := named.Obj(); obj.Pkg() != nil && obj.Pkg().Path() == "reflect" { - switch obj.Name() { - case "SliceHeader", "StringHeader": - return true - } - } - } - return false + return analysisutil.IsNamedType(t, "reflect", "SliceHeader", "StringHeader") } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go b/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go index cb487a217..76f42b052 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go @@ -24,6 +24,7 @@ import ( "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/astutil" "golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/types/typeutil" ) @@ -58,7 +59,25 @@ func init() { // List standard library functions here. // The context.With{Cancel,Deadline,Timeout} entries are // effectively redundant wrt the lostcancel analyzer. - funcs.Set("errors.New,fmt.Errorf,fmt.Sprintf,fmt.Sprint,sort.Reverse,context.WithValue,context.WithCancel,context.WithDeadline,context.WithTimeout") + funcs = stringSetFlag{ + "context.WithCancel": true, + "context.WithDeadline": true, + "context.WithTimeout": true, + "context.WithValue": true, + "errors.New": true, + "fmt.Errorf": true, + "fmt.Sprint": true, + "fmt.Sprintf": true, + "slices.Clip": true, + "slices.Compact": true, + "slices.CompactFunc": true, + "slices.Delete": true, + "slices.DeleteFunc": true, + "slices.Grow": true, + "slices.Insert": true, + "slices.Replace": true, + "sort.Reverse": true, + } Analyzer.Flags.Var(&funcs, "funcs", "comma-separated list of functions whose results must be used") @@ -82,7 +101,7 @@ func run(pass *analysis.Pass) (interface{}, error) { (*ast.ExprStmt)(nil), } inspect.Preorder(nodeFilter, func(n ast.Node) { - call, ok := analysisutil.Unparen(n.(*ast.ExprStmt).X).(*ast.CallExpr) + call, ok := astutil.Unparen(n.(*ast.ExprStmt).X).(*ast.CallExpr) if !ok { return // not a call statement } @@ -92,7 +111,6 @@ func run(pass *analysis.Pass) (interface{}, error) { if !ok { return // e.g. var or builtin } - if sig := fn.Type().(*types.Signature); sig.Recv() != nil { // method (e.g. foo.String()) if types.Identical(sig, sigNoArgsStringResult) { diff --git a/vendor/golang.org/x/tools/go/analysis/validate.go b/vendor/golang.org/x/tools/go/analysis/validate.go index 9da5692af..4f2c40456 100644 --- a/vendor/golang.org/x/tools/go/analysis/validate.go +++ b/vendor/golang.org/x/tools/go/analysis/validate.go @@ -19,6 +19,8 @@ import ( // that the Requires graph is acyclic; // that analyzer fact types are unique; // that each fact type is a pointer. +// +// Analyzer names need not be unique, though this may be confusing. func Validate(analyzers []*Analyzer) error { // Map each fact type to its sole generating analyzer. factTypes := make(map[reflect.Type]*Analyzer) diff --git a/vendor/golang.org/x/tools/go/ast/astutil/enclosing.go b/vendor/golang.org/x/tools/go/ast/astutil/enclosing.go index 9fa5aa192..2c4c4e232 100644 --- a/vendor/golang.org/x/tools/go/ast/astutil/enclosing.go +++ b/vendor/golang.org/x/tools/go/ast/astutil/enclosing.go @@ -11,8 +11,6 @@ import ( "go/ast" "go/token" "sort" - - "golang.org/x/tools/internal/typeparams" ) // PathEnclosingInterval returns the node that encloses the source @@ -322,7 +320,7 @@ func childrenOf(n ast.Node) []ast.Node { children = append(children, n.Recv) } children = append(children, n.Name) - if tparams := typeparams.ForFuncType(n.Type); tparams != nil { + if tparams := n.Type.TypeParams; tparams != nil { children = append(children, tparams) } if n.Type.Params != nil { @@ -377,7 +375,7 @@ func childrenOf(n ast.Node) []ast.Node { tok(n.Lbrack, len("[")), tok(n.Rbrack, len("]"))) - case *typeparams.IndexListExpr: + case *ast.IndexListExpr: children = append(children, tok(n.Lbrack, len("[")), tok(n.Rbrack, len("]"))) @@ -588,7 +586,7 @@ func NodeDescription(n ast.Node) string { return "decrement statement" case *ast.IndexExpr: return "index expression" - case *typeparams.IndexListExpr: + case *ast.IndexListExpr: return "index list expression" case *ast.InterfaceType: return "interface type" diff --git a/vendor/golang.org/x/tools/go/ast/astutil/rewrite.go b/vendor/golang.org/x/tools/go/ast/astutil/rewrite.go index f430b21b9..58934f766 100644 --- a/vendor/golang.org/x/tools/go/ast/astutil/rewrite.go +++ b/vendor/golang.org/x/tools/go/ast/astutil/rewrite.go @@ -9,8 +9,6 @@ import ( "go/ast" "reflect" "sort" - - "golang.org/x/tools/internal/typeparams" ) // An ApplyFunc is invoked by Apply for each node n, even if n is nil, @@ -252,7 +250,7 @@ func (a *application) apply(parent ast.Node, name string, iter *iterator, n ast. a.apply(n, "X", nil, n.X) a.apply(n, "Index", nil, n.Index) - case *typeparams.IndexListExpr: + case *ast.IndexListExpr: a.apply(n, "X", nil, n.X) a.applyList(n, "Indices") @@ -293,7 +291,7 @@ func (a *application) apply(parent ast.Node, name string, iter *iterator, n ast. a.apply(n, "Fields", nil, n.Fields) case *ast.FuncType: - if tparams := typeparams.ForFuncType(n); tparams != nil { + if tparams := n.TypeParams; tparams != nil { a.apply(n, "TypeParams", nil, tparams) } a.apply(n, "Params", nil, n.Params) @@ -408,7 +406,7 @@ func (a *application) apply(parent ast.Node, name string, iter *iterator, n ast. case *ast.TypeSpec: a.apply(n, "Doc", nil, n.Doc) a.apply(n, "Name", nil, n.Name) - if tparams := typeparams.ForTypeSpec(n); tparams != nil { + if tparams := n.TypeParams; tparams != nil { a.apply(n, "TypeParams", nil, tparams) } a.apply(n, "Type", nil, n.Type) diff --git a/vendor/golang.org/x/tools/go/ast/inspector/typeof.go b/vendor/golang.org/x/tools/go/ast/inspector/typeof.go index 703c81395..2a872f89d 100644 --- a/vendor/golang.org/x/tools/go/ast/inspector/typeof.go +++ b/vendor/golang.org/x/tools/go/ast/inspector/typeof.go @@ -12,8 +12,6 @@ package inspector import ( "go/ast" "math" - - "golang.org/x/tools/internal/typeparams" ) const ( @@ -171,7 +169,7 @@ func typeOf(n ast.Node) uint64 { return 1 << nIncDecStmt case *ast.IndexExpr: return 1 << nIndexExpr - case *typeparams.IndexListExpr: + case *ast.IndexListExpr: return 1 << nIndexListExpr case *ast.InterfaceType: return 1 << nInterfaceType diff --git a/vendor/golang.org/x/tools/go/buildutil/tags.go b/vendor/golang.org/x/tools/go/buildutil/tags.go index 7cf523bca..32c8d1424 100644 --- a/vendor/golang.org/x/tools/go/buildutil/tags.go +++ b/vendor/golang.org/x/tools/go/buildutil/tags.go @@ -4,17 +4,22 @@ package buildutil -// This logic was copied from stringsFlag from $GOROOT/src/cmd/go/build.go. +// This duplicated logic must be kept in sync with that from go build: +// $GOROOT/src/cmd/go/internal/work/build.go (tagsFlag.Set) +// $GOROOT/src/cmd/go/internal/base/flag.go (StringsFlag.Set) +// $GOROOT/src/cmd/internal/quoted/quoted.go (isSpaceByte, Split) -import "fmt" +import ( + "fmt" + "strings" +) const TagsFlagDoc = "a list of `build tags` to consider satisfied during the build. " + "For more information about build tags, see the description of " + "build constraints in the documentation for the go/build package" // TagsFlag is an implementation of the flag.Value and flag.Getter interfaces that parses -// a flag value in the same manner as go build's -tags flag and -// populates a []string slice. +// a flag value the same as go build's -tags flag and populates a []string slice. // // See $GOROOT/src/go/build/doc.go for description of build tags. // See $GOROOT/src/cmd/go/doc.go for description of 'go build -tags' flag. @@ -25,19 +30,32 @@ const TagsFlagDoc = "a list of `build tags` to consider satisfied during the bui type TagsFlag []string func (v *TagsFlag) Set(s string) error { - var err error - *v, err = splitQuotedFields(s) - if *v == nil { - *v = []string{} + // See $GOROOT/src/cmd/go/internal/work/build.go (tagsFlag.Set) + // For compatibility with Go 1.12 and earlier, allow "-tags='a b c'" or even just "-tags='a'". + if strings.Contains(s, " ") || strings.Contains(s, "'") { + var err error + *v, err = splitQuotedFields(s) + if *v == nil { + *v = []string{} + } + return err + } + + // Starting in Go 1.13, the -tags flag is a comma-separated list of build tags. + *v = []string{} + for _, s := range strings.Split(s, ",") { + if s != "" { + *v = append(*v, s) + } } - return err + return nil } func (v *TagsFlag) Get() interface{} { return *v } func splitQuotedFields(s string) ([]string, error) { - // Split fields allowing '' or "" around elements. - // Quotes further inside the string do not count. + // See $GOROOT/src/cmd/internal/quoted/quoted.go (Split) + // This must remain in sync with that logic. var f []string for len(s) > 0 { for len(s) > 0 && isSpaceByte(s[0]) { @@ -76,5 +94,7 @@ func (v *TagsFlag) String() string { } func isSpaceByte(c byte) bool { + // See $GOROOT/src/cmd/internal/quoted/quoted.go (isSpaceByte, Split) + // This list must remain in sync with that. return c == ' ' || c == '\t' || c == '\n' || c == '\r' } diff --git a/vendor/golang.org/x/tools/go/cfg/cfg.go b/vendor/golang.org/x/tools/go/cfg/cfg.go index 37d799f4b..e9c48d51d 100644 --- a/vendor/golang.org/x/tools/go/cfg/cfg.go +++ b/vendor/golang.org/x/tools/go/cfg/cfg.go @@ -113,7 +113,11 @@ func (b *Block) String() string { return fmt.Sprintf("block %d (%s)", b.Index, b.comment) } -// Return returns the return statement at the end of this block if present, nil otherwise. +// Return returns the return statement at the end of this block if present, nil +// otherwise. +// +// When control falls off the end of the function, the ReturnStmt is synthetic +// and its [ast.Node.End] position may be beyond the end of the file. func (b *Block) Return() (ret *ast.ReturnStmt) { if len(b.Nodes) > 0 { ret, _ = b.Nodes[len(b.Nodes)-1].(*ast.ReturnStmt) diff --git a/vendor/golang.org/x/tools/go/internal/cgo/cgo.go b/vendor/golang.org/x/tools/go/internal/cgo/cgo.go index 38d5c6c7c..697974bb9 100644 --- a/vendor/golang.org/x/tools/go/internal/cgo/cgo.go +++ b/vendor/golang.org/x/tools/go/internal/cgo/cgo.go @@ -59,11 +59,10 @@ import ( "go/token" "log" "os" + "os/exec" "path/filepath" "regexp" "strings" - - exec "golang.org/x/sys/execabs" ) // ProcessFiles invokes the cgo preprocessor on bp.CgoFiles, parses diff --git a/vendor/golang.org/x/tools/go/internal/cgo/cgo_pkgconfig.go b/vendor/golang.org/x/tools/go/internal/cgo/cgo_pkgconfig.go index 7d94bbc1e..b5bb95a63 100644 --- a/vendor/golang.org/x/tools/go/internal/cgo/cgo_pkgconfig.go +++ b/vendor/golang.org/x/tools/go/internal/cgo/cgo_pkgconfig.go @@ -8,7 +8,7 @@ import ( "errors" "fmt" "go/build" - exec "golang.org/x/sys/execabs" + "os/exec" "strings" ) diff --git a/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go b/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go index 0454cdd78..333676b7c 100644 --- a/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go +++ b/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go @@ -13,16 +13,17 @@ import ( "golang.org/x/tools/internal/gocommand" ) -var debug = false - func GetSizesForArgsGolist(ctx context.Context, inv gocommand.Invocation, gocmdRunner *gocommand.Runner) (string, string, error) { inv.Verb = "list" inv.Args = []string{"-f", "{{context.GOARCH}} {{context.Compiler}}", "--", "unsafe"} stdout, stderr, friendlyErr, rawErr := gocmdRunner.RunRaw(ctx, inv) var goarch, compiler string if rawErr != nil { - if rawErrMsg := rawErr.Error(); strings.Contains(rawErrMsg, "cannot find main module") || strings.Contains(rawErrMsg, "go.mod file not found") { - // User's running outside of a module. All bets are off. Get GOARCH and guess compiler is gc. + rawErrMsg := rawErr.Error() + if strings.Contains(rawErrMsg, "cannot find main module") || + strings.Contains(rawErrMsg, "go.mod file not found") { + // User's running outside of a module. + // All bets are off. Get GOARCH and guess compiler is gc. // TODO(matloob): Is this a problem in practice? inv.Verb = "env" inv.Args = []string{"GOARCH"} @@ -32,8 +33,12 @@ func GetSizesForArgsGolist(ctx context.Context, inv gocommand.Invocation, gocmdR } goarch = strings.TrimSpace(envout.String()) compiler = "gc" - } else { + } else if friendlyErr != nil { return "", "", friendlyErr + } else { + // This should be unreachable, but be defensive + // in case RunRaw's error results are inconsistent. + return "", "", rawErr } } else { fields := strings.Fields(stdout.String()) diff --git a/vendor/golang.org/x/tools/go/loader/loader.go b/vendor/golang.org/x/tools/go/loader/loader.go index edf62c2cc..013c0f505 100644 --- a/vendor/golang.org/x/tools/go/loader/loader.go +++ b/vendor/golang.org/x/tools/go/loader/loader.go @@ -23,7 +23,7 @@ import ( "golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/internal/cgo" - "golang.org/x/tools/internal/typeparams" + "golang.org/x/tools/internal/versions" ) var ignoreVendor build.ImportMode @@ -1033,13 +1033,14 @@ func (imp *importer) newPackageInfo(path, dir string) *PackageInfo { Defs: make(map[*ast.Ident]types.Object), Uses: make(map[*ast.Ident]types.Object), Implicits: make(map[ast.Node]types.Object), + Instances: make(map[*ast.Ident]types.Instance), Scopes: make(map[ast.Node]*types.Scope), Selections: make(map[*ast.SelectorExpr]*types.Selection), }, errorFunc: imp.conf.TypeChecker.Error, dir: dir, } - typeparams.InitInstanceInfo(&info.Info) + versions.InitFileVersions(&info.Info) // Copy the types.Config so we can vary it across PackageInfos. tc := imp.conf.TypeChecker diff --git a/vendor/golang.org/x/tools/go/packages/doc.go b/vendor/golang.org/x/tools/go/packages/doc.go index a7a8f73e3..a8d7b06ac 100644 --- a/vendor/golang.org/x/tools/go/packages/doc.go +++ b/vendor/golang.org/x/tools/go/packages/doc.go @@ -5,12 +5,20 @@ /* Package packages loads Go packages for inspection and analysis. -The Load function takes as input a list of patterns and return a list of Package -structs describing individual packages matched by those patterns. -The LoadMode controls the amount of detail in the loaded packages. - -Load passes most patterns directly to the underlying build tool, -but all patterns with the prefix "query=", where query is a +The [Load] function takes as input a list of patterns and returns a +list of [Package] values describing individual packages matched by those +patterns. +A [Config] specifies configuration options, the most important of which is +the [LoadMode], which controls the amount of detail in the loaded packages. + +Load passes most patterns directly to the underlying build tool. +The default build tool is the go command. +Its supported patterns are described at +https://pkg.go.dev/cmd/go#hdr-Package_lists_and_patterns. +Other build systems may be supported by providing a "driver"; +see [The driver protocol]. + +All patterns with the prefix "query=", where query is a non-empty string of letters from [a-z], are reserved and may be interpreted as query operators. @@ -64,9 +72,31 @@ reported about the loaded packages. See the documentation for type LoadMode for details. Most tools should pass their command-line arguments (after any flags) -uninterpreted to the loader, so that the loader can interpret them +uninterpreted to [Load], so that it can interpret them according to the conventions of the underlying build system. + See the Example function for typical usage. + +# The driver protocol + +[Load] may be used to load Go packages even in Go projects that use +alternative build systems, by installing an appropriate "driver" +program for the build system and specifying its location in the +GOPACKAGESDRIVER environment variable. +For example, +https://github.com/bazelbuild/rules_go/wiki/Editor-and-tool-integration +explains how to use the driver for Bazel. + +The driver program is responsible for interpreting patterns in its +preferred notation and reporting information about the packages that +those patterns identify. Drivers must also support the special "file=" +and "pattern=" patterns described above. + +The patterns are provided as positional command-line arguments. A +JSON-encoded [DriverRequest] message providing additional information +is written to the driver's standard input. The driver must write a +JSON-encoded [DriverResponse] message to its standard output. (This +message differs from the JSON schema produced by 'go list'.) */ package packages // import "golang.org/x/tools/go/packages" diff --git a/vendor/golang.org/x/tools/go/packages/external.go b/vendor/golang.org/x/tools/go/packages/external.go index 7242a0a7d..4335c1eb1 100644 --- a/vendor/golang.org/x/tools/go/packages/external.go +++ b/vendor/golang.org/x/tools/go/packages/external.go @@ -2,46 +2,85 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// This file enables an external tool to intercept package requests. -// If the tool is present then its results are used in preference to -// the go list command. - package packages +// This file defines the protocol that enables an external "driver" +// tool to supply package metadata in place of 'go list'. + import ( "bytes" "encoding/json" "fmt" - exec "golang.org/x/sys/execabs" "os" + "os/exec" "strings" ) -// The Driver Protocol +// DriverRequest defines the schema of a request for package metadata +// from an external driver program. The JSON-encoded DriverRequest +// message is provided to the driver program's standard input. The +// query patterns are provided as command-line arguments. // -// The driver, given the inputs to a call to Load, returns metadata about the packages specified. -// This allows for different build systems to support go/packages by telling go/packages how the -// packages' source is organized. -// The driver is a binary, either specified by the GOPACKAGESDRIVER environment variable or in -// the path as gopackagesdriver. It's given the inputs to load in its argv. See the package -// documentation in doc.go for the full description of the patterns that need to be supported. -// A driver receives as a JSON-serialized driverRequest struct in standard input and will -// produce a JSON-serialized driverResponse (see definition in packages.go) in its standard output. - -// driverRequest is used to provide the portion of Load's Config that is needed by a driver. -type driverRequest struct { +// See the package documentation for an overview. +type DriverRequest struct { Mode LoadMode `json:"mode"` + // Env specifies the environment the underlying build system should be run in. Env []string `json:"env"` + // BuildFlags are flags that should be passed to the underlying build system. BuildFlags []string `json:"build_flags"` + // Tests specifies whether the patterns should also return test packages. Tests bool `json:"tests"` + // Overlay maps file paths (relative to the driver's working directory) to the byte contents // of overlay files. Overlay map[string][]byte `json:"overlay"` } +// DriverResponse defines the schema of a response from an external +// driver program, providing the results of a query for package +// metadata. The driver program must write a JSON-encoded +// DriverResponse message to its standard output. +// +// See the package documentation for an overview. +type DriverResponse struct { + // NotHandled is returned if the request can't be handled by the current + // driver. If an external driver returns a response with NotHandled, the + // rest of the DriverResponse is ignored, and go/packages will fallback + // to the next driver. If go/packages is extended in the future to support + // lists of multiple drivers, go/packages will fall back to the next driver. + NotHandled bool + + // Compiler and Arch are the arguments pass of types.SizesFor + // to get a types.Sizes to use when type checking. + Compiler string + Arch string + + // Roots is the set of package IDs that make up the root packages. + // We have to encode this separately because when we encode a single package + // we cannot know if it is one of the roots as that requires knowledge of the + // graph it is part of. + Roots []string `json:",omitempty"` + + // Packages is the full set of packages in the graph. + // The packages are not connected into a graph. + // The Imports if populated will be stubs that only have their ID set. + // Imports will be connected and then type and syntax information added in a + // later pass (see refine). + Packages []*Package + + // GoVersion is the minor version number used by the driver + // (e.g. the go command on the PATH) when selecting .go files. + // Zero means unknown. + GoVersion int +} + +// driver is the type for functions that query the build system for the +// packages named by the patterns. +type driver func(cfg *Config, patterns ...string) (*DriverResponse, error) + // findExternalDriver returns the file path of a tool that supplies // the build system package structure, or "" if not found." // If GOPACKAGESDRIVER is set in the environment findExternalTool returns its @@ -64,8 +103,8 @@ func findExternalDriver(cfg *Config) driver { return nil } } - return func(cfg *Config, words ...string) (*driverResponse, error) { - req, err := json.Marshal(driverRequest{ + return func(cfg *Config, words ...string) (*DriverResponse, error) { + req, err := json.Marshal(DriverRequest{ Mode: cfg.Mode, Env: cfg.Env, BuildFlags: cfg.BuildFlags, @@ -92,7 +131,7 @@ func findExternalDriver(cfg *Config) driver { fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd), stderr) } - var response driverResponse + var response DriverResponse if err := json.Unmarshal(buf.Bytes(), &response); err != nil { return nil, err } diff --git a/vendor/golang.org/x/tools/go/packages/golist.go b/vendor/golang.org/x/tools/go/packages/golist.go index 1f1eade0a..22305d9c9 100644 --- a/vendor/golang.org/x/tools/go/packages/golist.go +++ b/vendor/golang.org/x/tools/go/packages/golist.go @@ -11,6 +11,7 @@ import ( "fmt" "log" "os" + "os/exec" "path" "path/filepath" "reflect" @@ -20,7 +21,6 @@ import ( "sync" "unicode" - exec "golang.org/x/sys/execabs" "golang.org/x/tools/go/internal/packagesdriver" "golang.org/x/tools/internal/gocommand" "golang.org/x/tools/internal/packagesinternal" @@ -35,23 +35,23 @@ type goTooOldError struct { error } -// responseDeduper wraps a driverResponse, deduplicating its contents. +// responseDeduper wraps a DriverResponse, deduplicating its contents. type responseDeduper struct { seenRoots map[string]bool seenPackages map[string]*Package - dr *driverResponse + dr *DriverResponse } func newDeduper() *responseDeduper { return &responseDeduper{ - dr: &driverResponse{}, + dr: &DriverResponse{}, seenRoots: map[string]bool{}, seenPackages: map[string]*Package{}, } } -// addAll fills in r with a driverResponse. -func (r *responseDeduper) addAll(dr *driverResponse) { +// addAll fills in r with a DriverResponse. +func (r *responseDeduper) addAll(dr *DriverResponse) { for _, pkg := range dr.Packages { r.addPackage(pkg) } @@ -128,7 +128,7 @@ func (state *golistState) mustGetEnv() map[string]string { // goListDriver uses the go list command to interpret the patterns and produce // the build system package structure. // See driver for more details. -func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) { +func goListDriver(cfg *Config, patterns ...string) (_ *DriverResponse, err error) { // Make sure that any asynchronous go commands are killed when we return. parentCtx := cfg.Context if parentCtx == nil { @@ -146,16 +146,18 @@ func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) { } // Fill in response.Sizes asynchronously if necessary. - var sizeserr error - var sizeswg sync.WaitGroup if cfg.Mode&NeedTypesSizes != 0 || cfg.Mode&NeedTypes != 0 { - sizeswg.Add(1) + errCh := make(chan error) go func() { compiler, arch, err := packagesdriver.GetSizesForArgsGolist(ctx, state.cfgInvocation(), cfg.gocmdRunner) - sizeserr = err response.dr.Compiler = compiler response.dr.Arch = arch - sizeswg.Done() + errCh <- err + }() + defer func() { + if sizesErr := <-errCh; sizesErr != nil { + err = sizesErr + } }() } @@ -208,87 +210,10 @@ extractQueries: } } - // Only use go/packages' overlay processing if we're using a Go version - // below 1.16. Otherwise, go list handles it. - if goVersion, err := state.getGoVersion(); err == nil && goVersion < 16 { - modifiedPkgs, needPkgs, err := state.processGolistOverlay(response) - if err != nil { - return nil, err - } - - var containsCandidates []string - if len(containFiles) > 0 { - containsCandidates = append(containsCandidates, modifiedPkgs...) - containsCandidates = append(containsCandidates, needPkgs...) - } - if err := state.addNeededOverlayPackages(response, needPkgs); err != nil { - return nil, err - } - // Check candidate packages for containFiles. - if len(containFiles) > 0 { - for _, id := range containsCandidates { - pkg, ok := response.seenPackages[id] - if !ok { - response.addPackage(&Package{ - ID: id, - Errors: []Error{{ - Kind: ListError, - Msg: fmt.Sprintf("package %s expected but not seen", id), - }}, - }) - continue - } - for _, f := range containFiles { - for _, g := range pkg.GoFiles { - if sameFile(f, g) { - response.addRoot(id) - } - } - } - } - } - // Add root for any package that matches a pattern. This applies only to - // packages that are modified by overlays, since they are not added as - // roots automatically. - for _, pattern := range restPatterns { - match := matchPattern(pattern) - for _, pkgID := range modifiedPkgs { - pkg, ok := response.seenPackages[pkgID] - if !ok { - continue - } - if match(pkg.PkgPath) { - response.addRoot(pkg.ID) - } - } - } - } - - sizeswg.Wait() - if sizeserr != nil { - return nil, sizeserr - } + // (We may yet return an error due to defer.) return response.dr, nil } -func (state *golistState) addNeededOverlayPackages(response *responseDeduper, pkgs []string) error { - if len(pkgs) == 0 { - return nil - } - dr, err := state.createDriverResponse(pkgs...) - if err != nil { - return err - } - for _, pkg := range dr.Packages { - response.addPackage(pkg) - } - _, needPkgs, err := state.processGolistOverlay(response) - if err != nil { - return err - } - return state.addNeededOverlayPackages(response, needPkgs) -} - func (state *golistState) runContainsQueries(response *responseDeduper, queries []string) error { for _, query := range queries { // TODO(matloob): Do only one query per directory. @@ -340,7 +265,7 @@ func (state *golistState) runContainsQueries(response *responseDeduper, queries // adhocPackage attempts to load or construct an ad-hoc package for a given // query, if the original call to the driver produced inadequate results. -func (state *golistState) adhocPackage(pattern, query string) (*driverResponse, error) { +func (state *golistState) adhocPackage(pattern, query string) (*DriverResponse, error) { response, err := state.createDriverResponse(query) if err != nil { return nil, err @@ -431,7 +356,7 @@ func otherFiles(p *jsonPackage) [][]string { // createDriverResponse uses the "go list" command to expand the pattern // words and return a response for the specified packages. -func (state *golistState) createDriverResponse(words ...string) (*driverResponse, error) { +func (state *golistState) createDriverResponse(words ...string) (*DriverResponse, error) { // go list uses the following identifiers in ImportPath and Imports: // // "p" -- importable package or main (command) @@ -458,7 +383,7 @@ func (state *golistState) createDriverResponse(words ...string) (*driverResponse pkgs := make(map[string]*Package) additionalErrors := make(map[string][]Error) // Decode the JSON and convert it to Package form. - response := &driverResponse{ + response := &DriverResponse{ GoVersion: goVersion, } for dec := json.NewDecoder(buf); dec.More(); { diff --git a/vendor/golang.org/x/tools/go/packages/golist_overlay.go b/vendor/golang.org/x/tools/go/packages/golist_overlay.go index 9576b472f..d823c474a 100644 --- a/vendor/golang.org/x/tools/go/packages/golist_overlay.go +++ b/vendor/golang.org/x/tools/go/packages/golist_overlay.go @@ -6,314 +6,11 @@ package packages import ( "encoding/json" - "fmt" - "go/parser" - "go/token" - "os" "path/filepath" - "regexp" - "sort" - "strconv" - "strings" "golang.org/x/tools/internal/gocommand" ) -// processGolistOverlay provides rudimentary support for adding -// files that don't exist on disk to an overlay. The results can be -// sometimes incorrect. -// TODO(matloob): Handle unsupported cases, including the following: -// - determining the correct package to add given a new import path -func (state *golistState) processGolistOverlay(response *responseDeduper) (modifiedPkgs, needPkgs []string, err error) { - havePkgs := make(map[string]string) // importPath -> non-test package ID - needPkgsSet := make(map[string]bool) - modifiedPkgsSet := make(map[string]bool) - - pkgOfDir := make(map[string][]*Package) - for _, pkg := range response.dr.Packages { - // This is an approximation of import path to id. This can be - // wrong for tests, vendored packages, and a number of other cases. - havePkgs[pkg.PkgPath] = pkg.ID - dir, err := commonDir(pkg.GoFiles) - if err != nil { - return nil, nil, err - } - if dir != "" { - pkgOfDir[dir] = append(pkgOfDir[dir], pkg) - } - } - - // If no new imports are added, it is safe to avoid loading any needPkgs. - // Otherwise, it's hard to tell which package is actually being loaded - // (due to vendoring) and whether any modified package will show up - // in the transitive set of dependencies (because new imports are added, - // potentially modifying the transitive set of dependencies). - var overlayAddsImports bool - - // If both a package and its test package are created by the overlay, we - // need the real package first. Process all non-test files before test - // files, and make the whole process deterministic while we're at it. - var overlayFiles []string - for opath := range state.cfg.Overlay { - overlayFiles = append(overlayFiles, opath) - } - sort.Slice(overlayFiles, func(i, j int) bool { - iTest := strings.HasSuffix(overlayFiles[i], "_test.go") - jTest := strings.HasSuffix(overlayFiles[j], "_test.go") - if iTest != jTest { - return !iTest // non-tests are before tests. - } - return overlayFiles[i] < overlayFiles[j] - }) - for _, opath := range overlayFiles { - contents := state.cfg.Overlay[opath] - base := filepath.Base(opath) - dir := filepath.Dir(opath) - var pkg *Package // if opath belongs to both a package and its test variant, this will be the test variant - var testVariantOf *Package // if opath is a test file, this is the package it is testing - var fileExists bool - isTestFile := strings.HasSuffix(opath, "_test.go") - pkgName, ok := extractPackageName(opath, contents) - if !ok { - // Don't bother adding a file that doesn't even have a parsable package statement - // to the overlay. - continue - } - // If all the overlay files belong to a different package, change the - // package name to that package. - maybeFixPackageName(pkgName, isTestFile, pkgOfDir[dir]) - nextPackage: - for _, p := range response.dr.Packages { - if pkgName != p.Name && p.ID != "command-line-arguments" { - continue - } - for _, f := range p.GoFiles { - if !sameFile(filepath.Dir(f), dir) { - continue - } - // Make sure to capture information on the package's test variant, if needed. - if isTestFile && !hasTestFiles(p) { - // TODO(matloob): Are there packages other than the 'production' variant - // of a package that this can match? This shouldn't match the test main package - // because the file is generated in another directory. - testVariantOf = p - continue nextPackage - } else if !isTestFile && hasTestFiles(p) { - // We're examining a test variant, but the overlaid file is - // a non-test file. Because the overlay implementation - // (currently) only adds a file to one package, skip this - // package, so that we can add the file to the production - // variant of the package. (https://golang.org/issue/36857 - // tracks handling overlays on both the production and test - // variant of a package). - continue nextPackage - } - if pkg != nil && p != pkg && pkg.PkgPath == p.PkgPath { - // We have already seen the production version of the - // for which p is a test variant. - if hasTestFiles(p) { - testVariantOf = pkg - } - } - pkg = p - if filepath.Base(f) == base { - fileExists = true - } - } - } - // The overlay could have included an entirely new package or an - // ad-hoc package. An ad-hoc package is one that we have manually - // constructed from inadequate `go list` results for a file= query. - // It will have the ID command-line-arguments. - if pkg == nil || pkg.ID == "command-line-arguments" { - // Try to find the module or gopath dir the file is contained in. - // Then for modules, add the module opath to the beginning. - pkgPath, ok, err := state.getPkgPath(dir) - if err != nil { - return nil, nil, err - } - if !ok { - break - } - var forTest string // only set for x tests - isXTest := strings.HasSuffix(pkgName, "_test") - if isXTest { - forTest = pkgPath - pkgPath += "_test" - } - id := pkgPath - if isTestFile { - if isXTest { - id = fmt.Sprintf("%s [%s.test]", pkgPath, forTest) - } else { - id = fmt.Sprintf("%s [%s.test]", pkgPath, pkgPath) - } - } - if pkg != nil { - // TODO(rstambler): We should change the package's path and ID - // here. The only issue is that this messes with the roots. - } else { - // Try to reclaim a package with the same ID, if it exists in the response. - for _, p := range response.dr.Packages { - if reclaimPackage(p, id, opath, contents) { - pkg = p - break - } - } - // Otherwise, create a new package. - if pkg == nil { - pkg = &Package{ - PkgPath: pkgPath, - ID: id, - Name: pkgName, - Imports: make(map[string]*Package), - } - response.addPackage(pkg) - havePkgs[pkg.PkgPath] = id - // Add the production package's sources for a test variant. - if isTestFile && !isXTest && testVariantOf != nil { - pkg.GoFiles = append(pkg.GoFiles, testVariantOf.GoFiles...) - pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, testVariantOf.CompiledGoFiles...) - // Add the package under test and its imports to the test variant. - pkg.forTest = testVariantOf.PkgPath - for k, v := range testVariantOf.Imports { - pkg.Imports[k] = &Package{ID: v.ID} - } - } - if isXTest { - pkg.forTest = forTest - } - } - } - } - if !fileExists { - pkg.GoFiles = append(pkg.GoFiles, opath) - // TODO(matloob): Adding the file to CompiledGoFiles can exhibit the wrong behavior - // if the file will be ignored due to its build tags. - pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, opath) - modifiedPkgsSet[pkg.ID] = true - } - imports, err := extractImports(opath, contents) - if err != nil { - // Let the parser or type checker report errors later. - continue - } - for _, imp := range imports { - // TODO(rstambler): If the package is an x test and the import has - // a test variant, make sure to replace it. - if _, found := pkg.Imports[imp]; found { - continue - } - overlayAddsImports = true - id, ok := havePkgs[imp] - if !ok { - var err error - id, err = state.resolveImport(dir, imp) - if err != nil { - return nil, nil, err - } - } - pkg.Imports[imp] = &Package{ID: id} - // Add dependencies to the non-test variant version of this package as well. - if testVariantOf != nil { - testVariantOf.Imports[imp] = &Package{ID: id} - } - } - } - - // toPkgPath guesses the package path given the id. - toPkgPath := func(sourceDir, id string) (string, error) { - if i := strings.IndexByte(id, ' '); i >= 0 { - return state.resolveImport(sourceDir, id[:i]) - } - return state.resolveImport(sourceDir, id) - } - - // Now that new packages have been created, do another pass to determine - // the new set of missing packages. - for _, pkg := range response.dr.Packages { - for _, imp := range pkg.Imports { - if len(pkg.GoFiles) == 0 { - return nil, nil, fmt.Errorf("cannot resolve imports for package %q with no Go files", pkg.PkgPath) - } - pkgPath, err := toPkgPath(filepath.Dir(pkg.GoFiles[0]), imp.ID) - if err != nil { - return nil, nil, err - } - if _, ok := havePkgs[pkgPath]; !ok { - needPkgsSet[pkgPath] = true - } - } - } - - if overlayAddsImports { - needPkgs = make([]string, 0, len(needPkgsSet)) - for pkg := range needPkgsSet { - needPkgs = append(needPkgs, pkg) - } - } - modifiedPkgs = make([]string, 0, len(modifiedPkgsSet)) - for pkg := range modifiedPkgsSet { - modifiedPkgs = append(modifiedPkgs, pkg) - } - return modifiedPkgs, needPkgs, err -} - -// resolveImport finds the ID of a package given its import path. -// In particular, it will find the right vendored copy when in GOPATH mode. -func (state *golistState) resolveImport(sourceDir, importPath string) (string, error) { - env, err := state.getEnv() - if err != nil { - return "", err - } - if env["GOMOD"] != "" { - return importPath, nil - } - - searchDir := sourceDir - for { - vendorDir := filepath.Join(searchDir, "vendor") - exists, ok := state.vendorDirs[vendorDir] - if !ok { - info, err := os.Stat(vendorDir) - exists = err == nil && info.IsDir() - state.vendorDirs[vendorDir] = exists - } - - if exists { - vendoredPath := filepath.Join(vendorDir, importPath) - if info, err := os.Stat(vendoredPath); err == nil && info.IsDir() { - // We should probably check for .go files here, but shame on anyone who fools us. - path, ok, err := state.getPkgPath(vendoredPath) - if err != nil { - return "", err - } - if ok { - return path, nil - } - } - } - - // We know we've hit the top of the filesystem when we Dir / and get /, - // or C:\ and get C:\, etc. - next := filepath.Dir(searchDir) - if next == searchDir { - break - } - searchDir = next - } - return importPath, nil -} - -func hasTestFiles(p *Package) bool { - for _, f := range p.GoFiles { - if strings.HasSuffix(f, "_test.go") { - return true - } - } - return false -} - // determineRootDirs returns a mapping from absolute directories that could // contain code to their corresponding import path prefixes. func (state *golistState) determineRootDirs() (map[string]string, error) { @@ -384,192 +81,3 @@ func (state *golistState) determineRootDirsGOPATH() (map[string]string, error) { } return m, nil } - -func extractImports(filename string, contents []byte) ([]string, error) { - f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.ImportsOnly) // TODO(matloob): reuse fileset? - if err != nil { - return nil, err - } - var res []string - for _, imp := range f.Imports { - quotedPath := imp.Path.Value - path, err := strconv.Unquote(quotedPath) - if err != nil { - return nil, err - } - res = append(res, path) - } - return res, nil -} - -// reclaimPackage attempts to reuse a package that failed to load in an overlay. -// -// If the package has errors and has no Name, GoFiles, or Imports, -// then it's possible that it doesn't yet exist on disk. -func reclaimPackage(pkg *Package, id string, filename string, contents []byte) bool { - // TODO(rstambler): Check the message of the actual error? - // It differs between $GOPATH and module mode. - if pkg.ID != id { - return false - } - if len(pkg.Errors) != 1 { - return false - } - if pkg.Name != "" || pkg.ExportFile != "" { - return false - } - if len(pkg.GoFiles) > 0 || len(pkg.CompiledGoFiles) > 0 || len(pkg.OtherFiles) > 0 { - return false - } - if len(pkg.Imports) > 0 { - return false - } - pkgName, ok := extractPackageName(filename, contents) - if !ok { - return false - } - pkg.Name = pkgName - pkg.Errors = nil - return true -} - -func extractPackageName(filename string, contents []byte) (string, bool) { - // TODO(rstambler): Check the message of the actual error? - // It differs between $GOPATH and module mode. - f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.PackageClauseOnly) // TODO(matloob): reuse fileset? - if err != nil { - return "", false - } - return f.Name.Name, true -} - -// commonDir returns the directory that all files are in, "" if files is empty, -// or an error if they aren't in the same directory. -func commonDir(files []string) (string, error) { - seen := make(map[string]bool) - for _, f := range files { - seen[filepath.Dir(f)] = true - } - if len(seen) > 1 { - return "", fmt.Errorf("files (%v) are in more than one directory: %v", files, seen) - } - for k := range seen { - // seen has only one element; return it. - return k, nil - } - return "", nil // no files -} - -// It is possible that the files in the disk directory dir have a different package -// name from newName, which is deduced from the overlays. If they all have a different -// package name, and they all have the same package name, then that name becomes -// the package name. -// It returns true if it changes the package name, false otherwise. -func maybeFixPackageName(newName string, isTestFile bool, pkgsOfDir []*Package) { - names := make(map[string]int) - for _, p := range pkgsOfDir { - names[p.Name]++ - } - if len(names) != 1 { - // some files are in different packages - return - } - var oldName string - for k := range names { - oldName = k - } - if newName == oldName { - return - } - // We might have a case where all of the package names in the directory are - // the same, but the overlay file is for an x test, which belongs to its - // own package. If the x test does not yet exist on disk, we may not yet - // have its package name on disk, but we should not rename the packages. - // - // We use a heuristic to determine if this file belongs to an x test: - // The test file should have a package name whose package name has a _test - // suffix or looks like "newName_test". - maybeXTest := strings.HasPrefix(oldName+"_test", newName) || strings.HasSuffix(newName, "_test") - if isTestFile && maybeXTest { - return - } - for _, p := range pkgsOfDir { - p.Name = newName - } -} - -// This function is copy-pasted from -// https://github.com/golang/go/blob/9706f510a5e2754595d716bd64be8375997311fb/src/cmd/go/internal/search/search.go#L360. -// It should be deleted when we remove support for overlays from go/packages. -// -// NOTE: This does not handle any ./... or ./ style queries, as this function -// doesn't know the working directory. -// -// matchPattern(pattern)(name) reports whether -// name matches pattern. Pattern is a limited glob -// pattern in which '...' means 'any string' and there -// is no other special syntax. -// Unfortunately, there are two special cases. Quoting "go help packages": -// -// First, /... at the end of the pattern can match an empty string, -// so that net/... matches both net and packages in its subdirectories, like net/http. -// Second, any slash-separated pattern element containing a wildcard never -// participates in a match of the "vendor" element in the path of a vendored -// package, so that ./... does not match packages in subdirectories of -// ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do. -// Note, however, that a directory named vendor that itself contains code -// is not a vendored package: cmd/vendor would be a command named vendor, -// and the pattern cmd/... matches it. -func matchPattern(pattern string) func(name string) bool { - // Convert pattern to regular expression. - // The strategy for the trailing /... is to nest it in an explicit ? expression. - // The strategy for the vendor exclusion is to change the unmatchable - // vendor strings to a disallowed code point (vendorChar) and to use - // "(anything but that codepoint)*" as the implementation of the ... wildcard. - // This is a bit complicated but the obvious alternative, - // namely a hand-written search like in most shell glob matchers, - // is too easy to make accidentally exponential. - // Using package regexp guarantees linear-time matching. - - const vendorChar = "\x00" - - if strings.Contains(pattern, vendorChar) { - return func(name string) bool { return false } - } - - re := regexp.QuoteMeta(pattern) - re = replaceVendor(re, vendorChar) - switch { - case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`): - re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)` - case re == vendorChar+`/\.\.\.`: - re = `(/vendor|/` + vendorChar + `/\.\.\.)` - case strings.HasSuffix(re, `/\.\.\.`): - re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?` - } - re = strings.ReplaceAll(re, `\.\.\.`, `[^`+vendorChar+`]*`) - - reg := regexp.MustCompile(`^` + re + `$`) - - return func(name string) bool { - if strings.Contains(name, vendorChar) { - return false - } - return reg.MatchString(replaceVendor(name, vendorChar)) - } -} - -// replaceVendor returns the result of replacing -// non-trailing vendor path elements in x with repl. -func replaceVendor(x, repl string) string { - if !strings.Contains(x, "vendor") { - return x - } - elem := strings.Split(x, "/") - for i := 0; i < len(elem)-1; i++ { - if elem[i] == "vendor" { - elem[i] = repl - } - } - return strings.Join(elem, "/") -} diff --git a/vendor/golang.org/x/tools/go/packages/packages.go b/vendor/golang.org/x/tools/go/packages/packages.go index ece0e7c60..f33b0afc2 100644 --- a/vendor/golang.org/x/tools/go/packages/packages.go +++ b/vendor/golang.org/x/tools/go/packages/packages.go @@ -27,8 +27,8 @@ import ( "golang.org/x/tools/go/gcexportdata" "golang.org/x/tools/internal/gocommand" "golang.org/x/tools/internal/packagesinternal" - "golang.org/x/tools/internal/typeparams" "golang.org/x/tools/internal/typesinternal" + "golang.org/x/tools/internal/versions" ) // A LoadMode controls the amount of detail to return when loading. @@ -206,43 +206,6 @@ type Config struct { Overlay map[string][]byte } -// driver is the type for functions that query the build system for the -// packages named by the patterns. -type driver func(cfg *Config, patterns ...string) (*driverResponse, error) - -// driverResponse contains the results for a driver query. -type driverResponse struct { - // NotHandled is returned if the request can't be handled by the current - // driver. If an external driver returns a response with NotHandled, the - // rest of the driverResponse is ignored, and go/packages will fallback - // to the next driver. If go/packages is extended in the future to support - // lists of multiple drivers, go/packages will fall back to the next driver. - NotHandled bool - - // Compiler and Arch are the arguments pass of types.SizesFor - // to get a types.Sizes to use when type checking. - Compiler string - Arch string - - // Roots is the set of package IDs that make up the root packages. - // We have to encode this separately because when we encode a single package - // we cannot know if it is one of the roots as that requires knowledge of the - // graph it is part of. - Roots []string `json:",omitempty"` - - // Packages is the full set of packages in the graph. - // The packages are not connected into a graph. - // The Imports if populated will be stubs that only have their ID set. - // Imports will be connected and then type and syntax information added in a - // later pass (see refine). - Packages []*Package - - // GoVersion is the minor version number used by the driver - // (e.g. the go command on the PATH) when selecting .go files. - // Zero means unknown. - GoVersion int -} - // Load loads and returns the Go packages named by the given patterns. // // Config specifies loading options; @@ -258,31 +221,55 @@ type driverResponse struct { // proceeding with further analysis. The PrintErrors function is // provided for convenient display of all errors. func Load(cfg *Config, patterns ...string) ([]*Package, error) { - l := newLoader(cfg) - response, err := defaultDriver(&l.Config, patterns...) + ld := newLoader(cfg) + response, external, err := defaultDriver(&ld.Config, patterns...) if err != nil { return nil, err } - l.sizes = types.SizesFor(response.Compiler, response.Arch) - return l.refine(response) + + ld.sizes = types.SizesFor(response.Compiler, response.Arch) + if ld.sizes == nil && ld.Config.Mode&(NeedTypes|NeedTypesSizes|NeedTypesInfo) != 0 { + // Type size information is needed but unavailable. + if external { + // An external driver may fail to populate the Compiler/GOARCH fields, + // especially since they are relatively new (see #63700). + // Provide a sensible fallback in this case. + ld.sizes = types.SizesFor("gc", runtime.GOARCH) + if ld.sizes == nil { // gccgo-only arch + ld.sizes = types.SizesFor("gc", "amd64") + } + } else { + // Go list should never fail to deliver accurate size information. + // Reject the whole Load since the error is the same for every package. + return nil, fmt.Errorf("can't determine type sizes for compiler %q on GOARCH %q", + response.Compiler, response.Arch) + } + } + + return ld.refine(response) } // defaultDriver is a driver that implements go/packages' fallback behavior. // It will try to request to an external driver, if one exists. If there's // no external driver, or the driver returns a response with NotHandled set, // defaultDriver will fall back to the go list driver. -func defaultDriver(cfg *Config, patterns ...string) (*driverResponse, error) { - driver := findExternalDriver(cfg) - if driver == nil { - driver = goListDriver +// The boolean result indicates that an external driver handled the request. +func defaultDriver(cfg *Config, patterns ...string) (*DriverResponse, bool, error) { + if driver := findExternalDriver(cfg); driver != nil { + response, err := driver(cfg, patterns...) + if err != nil { + return nil, false, err + } else if !response.NotHandled { + return response, true, nil + } + // (fall through) } - response, err := driver(cfg, patterns...) + + response, err := goListDriver(cfg, patterns...) if err != nil { - return response, err - } else if response.NotHandled { - return goListDriver(cfg, patterns...) + return nil, false, err } - return response, nil + return response, false, nil } // A Package describes a loaded Go package. @@ -411,12 +398,6 @@ func init() { packagesinternal.GetDepsErrors = func(p interface{}) []*packagesinternal.PackageError { return p.(*Package).depsErrors } - packagesinternal.GetGoCmdRunner = func(config interface{}) *gocommand.Runner { - return config.(*Config).gocmdRunner - } - packagesinternal.SetGoCmdRunner = func(config interface{}, runner *gocommand.Runner) { - config.(*Config).gocmdRunner = runner - } packagesinternal.SetModFile = func(config interface{}, value string) { config.(*Config).modFile = value } @@ -553,7 +534,7 @@ type loaderPackage struct { type loader struct { pkgs map[string]*loaderPackage Config - sizes types.Sizes + sizes types.Sizes // non-nil if needed by mode parseCache map[string]*parseValue parseCacheMu sync.Mutex exportMu sync.Mutex // enforces mutual exclusion of exportdata operations @@ -633,7 +614,7 @@ func newLoader(cfg *Config) *loader { // refine connects the supplied packages into a graph and then adds type // and syntax information as requested by the LoadMode. -func (ld *loader) refine(response *driverResponse) ([]*Package, error) { +func (ld *loader) refine(response *DriverResponse) ([]*Package, error) { roots := response.Roots rootMap := make(map[string]int, len(roots)) for i, root := range roots { @@ -678,39 +659,38 @@ func (ld *loader) refine(response *driverResponse) ([]*Package, error) { } } - // Materialize the import graph. - - const ( - white = 0 // new - grey = 1 // in progress - black = 2 // complete - ) - - // visit traverses the import graph, depth-first, - // and materializes the graph as Packages.Imports. - // - // Valid imports are saved in the Packages.Import map. - // Invalid imports (cycles and missing nodes) are saved in the importErrors map. - // Thus, even in the presence of both kinds of errors, the Import graph remains a DAG. - // - // visit returns whether the package needs src or has a transitive - // dependency on a package that does. These are the only packages - // for which we load source code. - var stack []*loaderPackage - var visit func(lpkg *loaderPackage) bool - var srcPkgs []*loaderPackage - visit = func(lpkg *loaderPackage) bool { - switch lpkg.color { - case black: - return lpkg.needsrc - case grey: - panic("internal error: grey node") - } - lpkg.color = grey - stack = append(stack, lpkg) // push - stubs := lpkg.Imports // the structure form has only stubs with the ID in the Imports - // If NeedImports isn't set, the imports fields will all be zeroed out. - if ld.Mode&NeedImports != 0 { + if ld.Mode&NeedImports != 0 { + // Materialize the import graph. + + const ( + white = 0 // new + grey = 1 // in progress + black = 2 // complete + ) + + // visit traverses the import graph, depth-first, + // and materializes the graph as Packages.Imports. + // + // Valid imports are saved in the Packages.Import map. + // Invalid imports (cycles and missing nodes) are saved in the importErrors map. + // Thus, even in the presence of both kinds of errors, + // the Import graph remains a DAG. + // + // visit returns whether the package needs src or has a transitive + // dependency on a package that does. These are the only packages + // for which we load source code. + var stack []*loaderPackage + var visit func(lpkg *loaderPackage) bool + visit = func(lpkg *loaderPackage) bool { + switch lpkg.color { + case black: + return lpkg.needsrc + case grey: + panic("internal error: grey node") + } + lpkg.color = grey + stack = append(stack, lpkg) // push + stubs := lpkg.Imports // the structure form has only stubs with the ID in the Imports lpkg.Imports = make(map[string]*Package, len(stubs)) for importPath, ipkg := range stubs { var importErr error @@ -734,40 +714,39 @@ func (ld *loader) refine(response *driverResponse) ([]*Package, error) { } lpkg.Imports[importPath] = imp.Package } - } - if lpkg.needsrc { - srcPkgs = append(srcPkgs, lpkg) - } - if ld.Mode&NeedTypesSizes != 0 { - lpkg.TypesSizes = ld.sizes - } - stack = stack[:len(stack)-1] // pop - lpkg.color = black - return lpkg.needsrc - } + // Complete type information is required for the + // immediate dependencies of each source package. + if lpkg.needsrc && ld.Mode&NeedTypes != 0 { + for _, ipkg := range lpkg.Imports { + ld.pkgs[ipkg.ID].needtypes = true + } + } - if ld.Mode&NeedImports == 0 { - // We do this to drop the stub import packages that we are not even going to try to resolve. - for _, lpkg := range initial { - lpkg.Imports = nil + // NeedTypeSizes causes TypeSizes to be set even + // on packages for which types aren't needed. + if ld.Mode&NeedTypesSizes != 0 { + lpkg.TypesSizes = ld.sizes + } + stack = stack[:len(stack)-1] // pop + lpkg.color = black + + return lpkg.needsrc } - } else { + // For each initial package, create its import DAG. for _, lpkg := range initial { visit(lpkg) } - } - if ld.Mode&NeedImports != 0 && ld.Mode&NeedTypes != 0 { - for _, lpkg := range srcPkgs { - // Complete type information is required for the - // immediate dependencies of each source package. - for _, ipkg := range lpkg.Imports { - imp := ld.pkgs[ipkg.ID] - imp.needtypes = true - } + + } else { + // !NeedImports: drop the stub (ID-only) import packages + // that we are not even going to try to resolve. + for _, lpkg := range initial { + lpkg.Imports = nil } } + // Load type data and syntax if needed, starting at // the initial packages (roots of the import DAG). if ld.Mode&NeedTypes != 0 || ld.Mode&NeedSyntax != 0 { @@ -1001,10 +980,11 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { Defs: make(map[*ast.Ident]types.Object), Uses: make(map[*ast.Ident]types.Object), Implicits: make(map[ast.Node]types.Object), + Instances: make(map[*ast.Ident]types.Instance), Scopes: make(map[ast.Node]*types.Scope), Selections: make(map[*ast.SelectorExpr]*types.Selection), } - typeparams.InitInstanceInfo(lpkg.TypesInfo) + versions.InitFileVersions(lpkg.TypesInfo) lpkg.TypesSizes = ld.sizes importer := importerFunc(func(path string) (*types.Package, error) { @@ -1042,7 +1022,7 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { IgnoreFuncBodies: ld.Mode&NeedDeps == 0 && !lpkg.initial, Error: appendError, - Sizes: ld.sizes, + Sizes: ld.sizes, // may be nil } if lpkg.Module != nil && lpkg.Module.GoVersion != "" { typesinternal.SetGoVersion(tc, "go"+lpkg.Module.GoVersion) diff --git a/vendor/golang.org/x/tools/go/ssa/builder.go b/vendor/golang.org/x/tools/go/ssa/builder.go index 0e49537d0..8622dfc53 100644 --- a/vendor/golang.org/x/tools/go/ssa/builder.go +++ b/vendor/golang.org/x/tools/go/ssa/builder.go @@ -4,106 +4,73 @@ package ssa -// This file implements the BUILD phase of SSA construction. +// This file defines the builder, which builds SSA-form IR for function bodies. // -// SSA construction has two phases, CREATE and BUILD. In the CREATE phase -// (create.go), all packages are constructed and type-checked and -// definitions of all package members are created, method-sets are -// computed, and wrapper methods are synthesized. -// ssa.Packages are created in arbitrary order. +// SSA construction has two phases, "create" and "build". First, one +// or more packages are created in any order by a sequence of calls to +// CreatePackage, either from syntax or from mere type information. +// Each created package has a complete set of Members (const, var, +// type, func) that can be accessed through methods like +// Program.FuncValue. // -// In the BUILD phase (builder.go), the builder traverses the AST of -// each Go source function and generates SSA instructions for the -// function body. Initializer expressions for package-level variables -// are emitted to the package's init() function in the order specified -// by go/types.Info.InitOrder, then code for each function in the -// package is generated in lexical order. -// The BUILD phases for distinct packages are independent and are -// executed in parallel. +// It is not necessary to call CreatePackage for all dependencies of +// each syntax package, only for its direct imports. (In future +// perhaps even this restriction may be lifted.) // -// TODO(adonovan): indeed, building functions is now embarrassingly parallel. -// Audit for concurrency then benchmark using more goroutines. +// Second, packages created from syntax are built, by one or more +// calls to Package.Build, which may be concurrent; or by a call to +// Program.Build, which builds all packages in parallel. Building +// traverses the type-annotated syntax tree of each function body and +// creates SSA-form IR, a control-flow graph of instructions, +// populating fields such as Function.Body, .Params, and others. // -// State: +// Building may create additional methods, including: +// - wrapper methods (e.g. for embeddding, or implicit &recv) +// - bound method closures (e.g. for use(recv.f)) +// - thunks (e.g. for use(I.f) or use(T.f)) +// - generic instances (e.g. to produce f[int] from f[any]). +// As these methods are created, they are added to the build queue, +// and then processed in turn, until a fixed point is reached, +// Since these methods might belong to packages that were not +// created (by a call to CreatePackage), their Pkg field is unset. // -// The Package's and Program's indices (maps) are populated and -// mutated during the CREATE phase, but during the BUILD phase they -// remain constant. The sole exception is Prog.methodSets and its -// related maps, which are protected by a dedicated mutex. +// Instances of generic functions may be either instantiated (f[int] +// is a copy of f[T] with substitutions) or wrapped (f[int] delegates +// to f[T]), depending on the availability of generic syntax and the +// InstantiateGenerics mode flag. // -// Generic functions declared in a package P can be instantiated from functions -// outside of P. This happens independently of the CREATE and BUILD phase of P. +// Each package has an initializer function named "init" that calls +// the initializer functions of each direct import, computes and +// assigns the initial value of each global variable, and calls each +// source-level function named "init". (These generate SSA functions +// named "init#1", "init#2", etc.) // -// Locks: +// Runtime types // -// Mutexes are currently acquired according to the following order: -// Prog.methodsMu ⊃ canonizer.mu ⊃ printMu -// where x ⊃ y denotes that y can be acquired while x is held -// and x cannot be acquired while y is held. +// Each MakeInterface operation is a conversion from a non-interface +// type to an interface type. The semantics of this operation requires +// a runtime type descriptor, which is the type portion of an +// interface, and the value abstracted by reflect.Type. // -// Synthetics: +// The program accumulates all non-parameterized types that are +// encountered as MakeInterface operands, along with all types that +// may be derived from them using reflection. This set is available as +// Program.RuntimeTypes, and the methods of these types may be +// reachable via interface calls or reflection even if they are never +// referenced from the SSA IR. (In practice, algorithms such as RTA +// that compute reachability from package main perform their own +// tracking of runtime types at a finer grain, so this feature is not +// very useful.) // -// During the BUILD phase new functions can be created and built. These include: -// - wrappers (wrappers, bounds, thunks) -// - generic function instantiations -// These functions do not belong to a specific Pkg (Pkg==nil). Instead the -// Package that led to them being CREATED is obligated to ensure these -// are BUILT during the BUILD phase of the Package. +// Function literals // -// Runtime types: +// Anonymous functions must be built as soon as they are encountered, +// as it may affect locals of the enclosing function, but they are not +// marked 'built' until the end of the outermost enclosing function. +// (Among other things, this causes them to be logged in top-down order.) // -// A concrete type is a type that is fully monomorphized with concrete types, -// i.e. it cannot reach a TypeParam type. -// Some concrete types require full runtime type information. Cases -// include checking whether a type implements an interface or -// interpretation by the reflect package. All such types that may require -// this information will have all of their method sets built and will be added to Prog.methodSets. -// A type T is considered to require runtime type information if it is -// a runtime type and has a non-empty method set and either: -// - T flows into a MakeInterface instructions, -// - T appears in a concrete exported member, or -// - T is a type reachable from a type S that has non-empty method set. -// For any such type T, method sets must be created before the BUILD -// phase of the package is done. -// -// Function literals: -// -// The BUILD phase of a function literal (anonymous function) is tied to the -// BUILD phase of the enclosing parent function. The FreeVars of an anonymous -// function are discovered by building the anonymous function. This in turn -// changes which variables must be bound in a MakeClosure instruction in the -// parent. Anonymous functions also track where they are referred to in their -// parent function. -// -// Happens-before: -// -// The above discussion leads to the following happens-before relation for -// the BUILD and CREATE phases. -// The happens-before relation (with X<Y denoting X happens-before Y) are: -// - CREATE fn < fn.startBody() < fn.finishBody() < fn.built -// for any function fn. -// - anon.parent.startBody() < CREATE anon, and -// anon.finishBody() < anon.parent().finishBody() < anon.built < fn.built -// for an anonymous function anon (i.e. anon.parent() != nil). -// - CREATE fn.Pkg < CREATE fn -// for a declared function fn (i.e. fn.Pkg != nil) -// - fn.built < BUILD pkg done -// for any function fn created during the CREATE or BUILD phase of a package -// pkg. This includes declared and synthetic functions. -// -// Program.MethodValue: -// -// Program.MethodValue may trigger new wrapper and instantiation functions to -// be created. It has the same obligation to BUILD created functions as a -// Package. -// -// Program.NewFunction: -// -// This is a low level operation for creating functions that do not exist in -// the source. Use with caution. -// -// TODO(taking): Use consistent terminology for "concrete". -// TODO(taking): Use consistent terminology for "monomorphization"/"instantiate"/"expand". +// The Function.build fields determines the algorithm for building the +// function body. It is cleared to mark that building is complete. import ( "fmt" @@ -115,6 +82,7 @@ import ( "sync" "golang.org/x/tools/internal/typeparams" + "golang.org/x/tools/internal/versions" ) type opaqueType struct { @@ -150,7 +118,6 @@ type builder struct { // Invariant: 0 <= rtypes <= finished <= created.Len() created *creator // functions created during building finished int // Invariant: create[i].built holds for i in [0,finished) - rtypes int // Invariant: all of the runtime types for create[i] have been added for i in [0,rtypes) } // cond emits to fn code to evaluate boolean condition e and jump @@ -323,10 +290,8 @@ func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ // treat make([]T, n, m) as new([m]T)[:n] cap := m.Int64() at := types.NewArray(ct.Elem(), cap) - alloc := emitNew(fn, at, pos) - alloc.Comment = "makeslice" v := &Slice{ - X: alloc, + X: emitNew(fn, at, pos, "makeslice"), High: n, } v.setPos(pos) @@ -363,9 +328,7 @@ func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ } case "new": - alloc := emitNew(fn, mustDeref(typ), pos) - alloc.Comment = "new" - return alloc + return emitNew(fn, mustDeref(typ), pos, "new") case "len", "cap": // Special case: len or cap of an array or *array is @@ -419,7 +382,7 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue { if isBlankIdent(e) { return blank{} } - obj := fn.objectOf(e) + obj := fn.objectOf(e).(*types.Var) var v Value if g := fn.Prog.packageLevelMember(obj); g != nil { v = g.(*Global) // var (address) @@ -432,11 +395,10 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue { typ, _ := deref(fn.typeOf(e)) var v *Alloc if escaping { - v = emitNew(fn, typ, e.Lbrace) + v = emitNew(fn, typ, e.Lbrace, "complit") } else { - v = fn.addLocal(typ, e.Lbrace) + v = emitLocal(fn, typ, e.Lbrace, "complit") } - v.Comment = "complit" var sb storebuf b.compLit(fn, v, e, true, &sb) sb.emit(fn) @@ -634,7 +596,8 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { panic("non-constant BasicLit") // unreachable case *ast.FuncLit: - fn2 := &Function{ + /* function literal */ + anon := &Function{ name: fmt.Sprintf("%s$%d", fn.Name(), 1+len(fn.AnonFuncs)), Signature: fn.typeOf(e.Type).(*types.Signature), pos: e.Type.Func, @@ -643,23 +606,24 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { Pkg: fn.Pkg, Prog: fn.Prog, syntax: e, + info: fn.info, + goversion: fn.goversion, + build: (*builder).buildFromSyntax, topLevelOrigin: nil, // use anonIdx to lookup an anon instance's origin. typeparams: fn.typeparams, // share the parent's type parameters. typeargs: fn.typeargs, // share the parent's type arguments. - info: fn.info, - subst: fn.subst, // share the parent's type substitutions. + subst: fn.subst, // share the parent's type substitutions. } - fn.AnonFuncs = append(fn.AnonFuncs, fn2) - b.created.Add(fn2) - b.buildFunctionBody(fn2) - // fn2 is not done BUILDing. fn2.referrers can still be updated. - // fn2 is done BUILDing after fn.finishBody(). - if fn2.FreeVars == nil { - return fn2 + fn.AnonFuncs = append(fn.AnonFuncs, anon) + // Build anon immediately, as it may cause fn's locals to escape. + // (It is not marked 'built' until the end of the enclosing FuncDecl.) + anon.build(b, anon) + if anon.FreeVars == nil { + return anon } - v := &MakeClosure{Fn: fn2} + v := &MakeClosure{Fn: anon} v.setType(fn.typ(tv.Type)) - for _, fv := range fn2.FreeVars { + for _, fv := range anon.FreeVars { v.Bindings = append(v.Bindings, fv.outer) fv.outer = nil } @@ -792,7 +756,9 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { case *types.Nil: return zeroConst(fn.instanceType(e)) } + // Package-level func or var? + // (obj must belong to same package or a direct import.) if v := fn.Prog.packageLevelMember(obj); v != nil { if g, ok := v.(*Global); ok { return emitLoad(fn, g) // var (address) @@ -800,12 +766,12 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { callee := v.(*Function) // (func) if callee.typeparams.Len() > 0 { targs := fn.subst.types(instanceArgs(fn.info, e)) - callee = fn.Prog.needsInstance(callee, targs, b.created) + callee = callee.instance(targs, b.created) } return callee } // Local var. - return emitLoad(fn, fn.lookup(obj, false)) // var (address) + return emitLoad(fn, fn.lookup(obj.(*types.Var), false)) // var (address) case *ast.SelectorExpr: sel := fn.selection(e) @@ -821,7 +787,7 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { case types.MethodExpr: // (*T).f or T.f, the method f from the method-set of type T. // The result is a "thunk". - thunk := makeThunk(fn.Prog, sel, b.created) + thunk := createThunk(fn.Prog, sel, b.created) return emitConv(fn, thunk, fn.typ(tv.Type)) case types.MethodVal: @@ -836,7 +802,7 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { if types.IsInterface(rt) { // If v may be an interface type I (after instantiating), // we must emit a check that v is non-nil. - if recv, ok := sel.recv.(*typeparams.TypeParam); ok { + if recv, ok := sel.recv.(*types.TypeParam); ok { // Emit a nil check if any possible instantiation of the // type parameter is an interface type. if typeSetOf(recv).Len() > 0 { @@ -856,7 +822,7 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { } else { // non-type param interface // Emit nil check: typeassert v.(I). - emitTypeAssert(fn, v, rt, token.NoPos) + emitTypeAssert(fn, v, rt, e.Sel.Pos()) } } if targs := receiverTypeArgs(obj); len(targs) > 0 { @@ -864,7 +830,7 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { obj = fn.Prog.canon.instantiateMethod(obj, fn.subst.types(targs), fn.Prog.ctxt) } c := &MakeClosure{ - Fn: makeBound(fn.Prog, obj, b.created), + Fn: createBound(fn.Prog, obj, b.created), Bindings: []Value{v}, } c.setPos(e.Sel.Pos()) @@ -882,7 +848,7 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { panic("unexpected expression-relative selector") - case *typeparams.IndexListExpr: + case *ast.IndexListExpr: // f[X, Y] must be a generic function if !instance(fn.info, e.X) { panic("unexpected expression-could not match index list to instantiation") @@ -966,7 +932,10 @@ func (b *builder) receiver(fn *Function, e ast.Expr, wantAddr, escaping bool, se last := len(sel.index) - 1 // The position of implicit selection is the position of the inducing receiver expression. v = emitImplicitSelections(fn, v, sel.index[:last], e.Pos()) - if _, vptr := deref(v.Type()); !wantAddr && vptr { + if types.IsInterface(v.Type()) { + // When v is an interface, sel.Kind()==MethodValue and v.f is invoked. + // So v is not loaded, even if v has a pointer core type. + } else if _, vptr := deref(v.Type()); !wantAddr && vptr { v = emitLoad(fn, v) } return v @@ -994,11 +963,7 @@ func (b *builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) { c.Method = obj } else { // "Call"-mode call. - callee := fn.Prog.originFunc(obj) - if callee.typeparams.Len() > 0 { - callee = fn.Prog.needsInstance(callee, receiverTypeArgs(obj), b.created) - } - c.Value = callee + c.Value = fn.Prog.objectMethod(obj, b.created) c.Args = append(c.Args, v) } return @@ -1090,9 +1055,8 @@ func (b *builder) emitCallArgs(fn *Function, sig *types.Signature, e *ast.CallEx } else { // Replace a suffix of args with a slice containing it. at := types.NewArray(vt, int64(len(varargs))) - a := emitNew(fn, at, token.NoPos) + a := emitNew(fn, at, token.NoPos, "varargs") a.setPos(e.Rparen) - a.Comment = "varargs" for i, arg := range varargs { iaddr := &IndexAddr{ X: a, @@ -1139,7 +1103,7 @@ func (b *builder) localValueSpec(fn *Function, spec *ast.ValueSpec) { // 1:1 assignment for i, id := range spec.Names { if !isBlankIdent(id) { - fn.addLocalForIdent(id) + emitLocalVar(fn, identVar(fn, id)) } lval := b.addr(fn, id, false) // non-escaping b.assign(fn, lval, spec.Values[i], true, nil) @@ -1150,7 +1114,7 @@ func (b *builder) localValueSpec(fn *Function, spec *ast.ValueSpec) { // Locals are implicitly zero-initialized. for _, id := range spec.Names { if !isBlankIdent(id) { - lhs := fn.addLocalForIdent(id) + lhs := emitLocalVar(fn, identVar(fn, id)) if fn.debugInfo() { emitDebugRef(fn, id, lhs, true) } @@ -1162,7 +1126,7 @@ func (b *builder) localValueSpec(fn *Function, spec *ast.ValueSpec) { tuple := b.exprN(fn, spec.Values[0]) for i, id := range spec.Names { if !isBlankIdent(id) { - fn.addLocalForIdent(id) + emitLocalVar(fn, identVar(fn, id)) lhs := b.addr(fn, id, false) // non-escaping lhs.store(fn, emitExtract(fn, tuple, i)) } @@ -1182,8 +1146,8 @@ func (b *builder) assignStmt(fn *Function, lhss, rhss []ast.Expr, isDef bool) { var lval lvalue = blank{} if !isBlankIdent(lhs) { if isDef { - if obj := fn.info.Defs[lhs.(*ast.Ident)]; obj != nil { - fn.addNamedLocal(obj) + if obj, ok := fn.info.Defs[lhs.(*ast.Ident)].(*types.Var); ok { + emitLocalVar(fn, obj) isZero[i] = true } } @@ -1292,9 +1256,7 @@ func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero switch t := t.(type) { case *types.Slice: at = types.NewArray(t.Elem(), b.arrayLen(fn, e.Elts)) - alloc := emitNew(fn, at, e.Lbrace) - alloc.Comment = "slicelit" - array = alloc + array = emitNew(fn, at, e.Lbrace, "slicelit") case *types.Array: at = t array = addr @@ -1582,13 +1544,13 @@ func (b *builder) typeSwitchStmt(fn *Function, s *ast.TypeSwitchStmt, label *lbl } func (b *builder) typeCaseBody(fn *Function, cc *ast.CaseClause, x Value, done *BasicBlock) { - if obj := fn.info.Implicits[cc]; obj != nil { + if obj, ok := fn.info.Implicits[cc].(*types.Var); ok { // In a switch y := x.(type), each case clause // implicitly declares a distinct object y. // In a single-type case, y has that type. // In multi-type cases, 'case nil' and default, // y has the same type as the interface operand. - emitStore(fn, fn.addNamedLocal(obj), x, obj.Pos()) + emitStore(fn, emitLocalVar(fn, obj), x, obj.Pos()) } fn.targets = &targets{ tail: fn.targets, @@ -1737,7 +1699,7 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) { case *ast.AssignStmt: // x := <-states[state].Chan if comm.Tok == token.DEFINE { - fn.addLocalForIdent(comm.Lhs[0].(*ast.Ident)) + emitLocalVar(fn, identVar(fn, comm.Lhs[0].(*ast.Ident))) } x := b.addr(fn, comm.Lhs[0], false) // non-escaping v := emitExtract(fn, sel, r) @@ -1748,7 +1710,7 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) { if len(comm.Lhs) == 2 { // x, ok := ... if comm.Tok == token.DEFINE { - fn.addLocalForIdent(comm.Lhs[1].(*ast.Ident)) + emitLocalVar(fn, identVar(fn, comm.Lhs[1].(*ast.Ident))) } ok := b.addr(fn, comm.Lhs[1], false) // non-escaping ok.store(fn, emitExtract(fn, sel, 1)) @@ -1783,20 +1745,32 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) { // forStmt emits to fn code for the for statement s, optionally // labelled by label. func (b *builder) forStmt(fn *Function, s *ast.ForStmt, label *lblock) { - // ...init... - // jump loop + // Use forStmtGo122 instead if it applies. + if s.Init != nil { + if assign, ok := s.Init.(*ast.AssignStmt); ok && assign.Tok == token.DEFINE { + afterGo122 := versions.Compare(fn.goversion, "go1.21") > 0 + if afterGo122 { + b.forStmtGo122(fn, s, label) + return + } + } + } + + // ...init... + // jump loop // loop: - // if cond goto body else done + // if cond goto body else done // body: - // ...body... - // jump post - // post: (target of continue) - // ...post... - // jump loop + // ...body... + // jump post + // post: (target of continue) + // ...post... + // jump loop // done: (target of break) if s.Init != nil { b.stmt(fn, s.Init) } + body := fn.newBasicBlock("for.body") done := fn.newBasicBlock("for.done") // target of 'break' loop := body // target of back-edge @@ -1834,23 +1808,188 @@ func (b *builder) forStmt(fn *Function, s *ast.ForStmt, label *lblock) { fn.currentBlock = done } +// forStmtGo122 emits to fn code for the for statement s, optionally +// labelled by label. s must define its variables. +// +// This allocates once per loop iteration. This is only correct in +// GoVersions >= go1.22. +func (b *builder) forStmtGo122(fn *Function, s *ast.ForStmt, label *lblock) { + // i_outer = alloc[T] + // *i_outer = ...init... // under objects[i] = i_outer + // jump loop + // loop: + // i = phi [head: i_outer, loop: i_next] + // ...cond... // under objects[i] = i + // if cond goto body else done + // body: + // ...body... // under objects[i] = i (same as loop) + // jump post + // post: + // tmp = *i + // i_next = alloc[T] + // *i_next = tmp + // ...post... // under objects[i] = i_next + // goto loop + // done: + + init := s.Init.(*ast.AssignStmt) + startingBlocks := len(fn.Blocks) + + pre := fn.currentBlock // current block before starting + loop := fn.newBasicBlock("for.loop") // target of back-edge + body := fn.newBasicBlock("for.body") + post := fn.newBasicBlock("for.post") // target of 'continue' + done := fn.newBasicBlock("for.done") // target of 'break' + + // For each of the n loop variables, we create five SSA values, + // outer, phi, next, load, and store in pre, loop, and post. + // There is no limit on n. + type loopVar struct { + obj *types.Var + outer *Alloc + phi *Phi + load *UnOp + next *Alloc + store *Store + } + vars := make([]loopVar, len(init.Lhs)) + for i, lhs := range init.Lhs { + v := identVar(fn, lhs.(*ast.Ident)) + typ := fn.typ(v.Type()) + + fn.currentBlock = pre + outer := emitLocal(fn, typ, v.Pos(), v.Name()) + + fn.currentBlock = loop + phi := &Phi{Comment: v.Name()} + phi.pos = v.Pos() + phi.typ = outer.Type() + fn.emit(phi) + + fn.currentBlock = post + // If next is is local, it reuses the address and zeroes the old value so + // load before allocating next. + load := emitLoad(fn, phi) + next := emitLocal(fn, typ, v.Pos(), v.Name()) + store := emitStore(fn, next, load, token.NoPos) + + phi.Edges = []Value{outer, next} // pre edge is emitted before post edge. + + vars[i] = loopVar{v, outer, phi, load, next, store} + } + + // ...init... under fn.objects[v] = i_outer + fn.currentBlock = pre + for _, v := range vars { + fn.vars[v.obj] = v.outer + } + const isDef = false // assign to already-allocated outers + b.assignStmt(fn, init.Lhs, init.Rhs, isDef) + if label != nil { + label._break = done + label._continue = post + } + emitJump(fn, loop) + + // ...cond... under fn.objects[v] = i + fn.currentBlock = loop + for _, v := range vars { + fn.vars[v.obj] = v.phi + } + if s.Cond != nil { + b.cond(fn, s.Cond, body, done) + } else { + emitJump(fn, body) + } + + // ...body... under fn.objects[v] = i + fn.currentBlock = body + fn.targets = &targets{ + tail: fn.targets, + _break: done, + _continue: post, + } + b.stmt(fn, s.Body) + fn.targets = fn.targets.tail + emitJump(fn, post) + + // ...post... under fn.objects[v] = i_next + for _, v := range vars { + fn.vars[v.obj] = v.next + } + fn.currentBlock = post + if s.Post != nil { + b.stmt(fn, s.Post) + } + emitJump(fn, loop) // back-edge + fn.currentBlock = done + + // For each loop variable that does not escape, + // (the common case), fuse its next cells into its + // (local) outer cell as they have disjoint live ranges. + // + // It is sufficient to test whether i_next escapes, + // because its Heap flag will be marked true if either + // the cond or post expression causes i to escape + // (because escape distributes over phi). + var nlocals int + for _, v := range vars { + if !v.next.Heap { + nlocals++ + } + } + if nlocals > 0 { + replace := make(map[Value]Value, 2*nlocals) + dead := make(map[Instruction]bool, 4*nlocals) + for _, v := range vars { + if !v.next.Heap { + replace[v.next] = v.outer + replace[v.phi] = v.outer + dead[v.phi], dead[v.next], dead[v.load], dead[v.store] = true, true, true, true + } + } + + // Replace all uses of i_next and phi with i_outer. + // Referrers have not been built for fn yet so only update Instruction operands. + // We need only look within the blocks added by the loop. + var operands []*Value // recycle storage + for _, b := range fn.Blocks[startingBlocks:] { + for _, instr := range b.Instrs { + operands = instr.Operands(operands[:0]) + for _, ptr := range operands { + k := *ptr + if v := replace[k]; v != nil { + *ptr = v + } + } + } + } + + // Remove instructions for phi, load, and store. + // lift() will remove the unused i_next *Alloc. + isDead := func(i Instruction) bool { return dead[i] } + loop.Instrs = removeInstrsIf(loop.Instrs, isDead) + post.Instrs = removeInstrsIf(post.Instrs, isDead) + } +} + // rangeIndexed emits to fn the header for an integer-indexed loop // over array, *array or slice value x. // The v result is defined only if tv is non-nil. // forPos is the position of the "for" token. func (b *builder) rangeIndexed(fn *Function, x Value, tv types.Type, pos token.Pos) (k, v Value, loop, done *BasicBlock) { // - // length = len(x) - // index = -1 - // loop: (target of continue) - // index++ - // if index < length goto body else done + // length = len(x) + // index = -1 + // loop: (target of continue) + // index++ + // if index < length goto body else done // body: - // k = index - // v = x[index] - // ...body... - // jump loop - // done: (target of break) + // k = index + // v = x[index] + // ...body... + // jump loop + // done: (target of break) // Determine number of iterations. var length Value @@ -1872,7 +2011,7 @@ func (b *builder) rangeIndexed(fn *Function, x Value, tv types.Type, pos token.P length = fn.emit(&c) } - index := fn.addLocal(tInt, token.NoPos) + index := emitLocal(fn, tInt, token.NoPos, "rangeindex") emitStore(fn, index, intConst(-1), pos) loop = fn.newBasicBlock("rangeindex.loop") @@ -1935,16 +2074,16 @@ func (b *builder) rangeIndexed(fn *Function, x Value, tv types.Type, pos token.P // if the respective component is not wanted. func (b *builder) rangeIter(fn *Function, x Value, tk, tv types.Type, pos token.Pos) (k, v Value, loop, done *BasicBlock) { // - // it = range x + // it = range x // loop: (target of continue) - // okv = next it (ok, key, value) - // ok = extract okv #0 - // if ok goto body else done + // okv = next it (ok, key, value) + // ok = extract okv #0 + // if ok goto body else done // body: - // k = extract okv #1 - // v = extract okv #2 - // ...body... - // jump loop + // k = extract okv #1 + // v = extract okv #2 + // ...body... + // jump loop // done: (target of break) // @@ -1997,13 +2136,13 @@ func (b *builder) rangeIter(fn *Function, x Value, tk, tv types.Type, pos token. func (b *builder) rangeChan(fn *Function, x Value, tk types.Type, pos token.Pos) (k Value, loop, done *BasicBlock) { // // loop: (target of continue) - // ko = <-x (key, ok) - // ok = extract ko #1 - // if ok goto body else done + // ko = <-x (key, ok) + // ok = extract ko #1 + // if ok goto body else done // body: - // k = extract ko #0 - // ... - // goto loop + // k = extract ko #0 + // ...body... + // goto loop // done: (target of break) loop = fn.newBasicBlock("rangechan.loop") @@ -2030,6 +2169,57 @@ func (b *builder) rangeChan(fn *Function, x Value, tk types.Type, pos token.Pos) return } +// rangeInt emits to fn the header for a range loop with an integer operand. +// tk is the key value's type, or nil if the k result is not wanted. +// pos is the position of the "for" token. +func (b *builder) rangeInt(fn *Function, x Value, tk types.Type, pos token.Pos) (k Value, loop, done *BasicBlock) { + // + // iter = 0 + // if 0 < x goto body else done + // loop: (target of continue) + // iter++ + // if iter < x goto body else done + // body: + // k = x + // ...body... + // jump loop + // done: (target of break) + + if isUntyped(x.Type()) { + x = emitConv(fn, x, tInt) + } + + T := x.Type() + iter := emitLocal(fn, T, token.NoPos, "rangeint.iter") + // x may be unsigned. Avoid initializing x to -1. + + body := fn.newBasicBlock("rangeint.body") + done = fn.newBasicBlock("rangeint.done") + emitIf(fn, emitCompare(fn, token.LSS, zeroConst(T), x, token.NoPos), body, done) + + loop = fn.newBasicBlock("rangeint.loop") + fn.currentBlock = loop + + incr := &BinOp{ + Op: token.ADD, + X: emitLoad(fn, iter), + Y: emitConv(fn, vOne, T), + } + incr.setType(T) + emitStore(fn, iter, fn.emit(incr), pos) + emitIf(fn, emitCompare(fn, token.LSS, incr, x, token.NoPos), body, done) + fn.currentBlock = body + + if tk != nil { + // Integer types (int, uint8, etc.) are named and + // we know that k is assignable to x when tk != nil. + // This implies tk and T are identical so no conversion is needed. + k = emitLoad(fn, iter) + } + + return +} + // rangeStmt emits to fn code for the range statement s, optionally // labelled by label. func (b *builder) rangeStmt(fn *Function, s *ast.RangeStmt, label *lblock) { @@ -2041,21 +2231,26 @@ func (b *builder) rangeStmt(fn *Function, s *ast.RangeStmt, label *lblock) { tv = fn.typeOf(s.Value) } - // If iteration variables are defined (:=), this - // occurs once outside the loop. - // - // Unlike a short variable declaration, a RangeStmt - // using := never redeclares an existing variable; it - // always creates a new one. - if s.Tok == token.DEFINE { + // create locals for s.Key and s.Value. + createVars := func() { + // Unlike a short variable declaration, a RangeStmt + // using := never redeclares an existing variable; it + // always creates a new one. if tk != nil { - fn.addLocalForIdent(s.Key.(*ast.Ident)) + emitLocalVar(fn, identVar(fn, s.Key.(*ast.Ident))) } if tv != nil { - fn.addLocalForIdent(s.Value.(*ast.Ident)) + emitLocalVar(fn, identVar(fn, s.Value.(*ast.Ident))) } } + afterGo122 := versions.Compare(fn.goversion, "go1.21") > 0 + if s.Tok == token.DEFINE && !afterGo122 { + // pre-go1.22: If iteration variables are defined (:=), this + // occurs once outside the loop. + createVars() + } + x := b.expr(fn, s.X) var k, v Value @@ -2067,13 +2262,30 @@ func (b *builder) rangeStmt(fn *Function, s *ast.RangeStmt, label *lblock) { case *types.Chan: k, loop, done = b.rangeChan(fn, x, tk, s.For) - case *types.Map, *types.Basic: // string + case *types.Map: k, v, loop, done = b.rangeIter(fn, x, tk, tv, s.For) + case *types.Basic: + switch { + case rt.Info()&types.IsString != 0: + k, v, loop, done = b.rangeIter(fn, x, tk, tv, s.For) + + case rt.Info()&types.IsInteger != 0: + k, loop, done = b.rangeInt(fn, x, tk, s.For) + + default: + panic("Cannot range over basic type: " + rt.String()) + } + default: panic("Cannot range over: " + rt.String()) } + if s.Tok == token.DEFINE && afterGo122 { + // go1.22: If iteration variables are defined (:=), this occurs inside the loop. + createVars() + } + // Evaluate both LHS expressions before we update either. var kl, vl lvalue if tk != nil { @@ -2297,73 +2509,71 @@ start: } } +// A buildFunc is a strategy for building the SSA body for a function. +type buildFunc = func(*builder, *Function) + +// iterate causes all created but unbuilt functions to be built. As +// this may create new methods, the process is iterated until it +// converges. +func (b *builder) iterate() { + for ; b.finished < b.created.Len(); b.finished++ { + fn := b.created.At(b.finished) + b.buildFunction(fn) + } +} + // buildFunction builds SSA code for the body of function fn. Idempotent. func (b *builder) buildFunction(fn *Function) { - if !fn.built { + if fn.build != nil { assert(fn.parent == nil, "anonymous functions should not be built by buildFunction()") - b.buildFunctionBody(fn) + + if fn.Prog.mode&LogSource != 0 { + defer logStack("build %s @ %s", fn, fn.Prog.Fset.Position(fn.pos))() + } + fn.build(b, fn) fn.done() } } -// buildFunctionBody builds SSA code for the body of function fn. -// -// fn is not done building until fn.done() is called. -func (b *builder) buildFunctionBody(fn *Function) { - // TODO(taking): see if this check is reachable. - if fn.Blocks != nil { - return // building already started +// buildParamsOnly builds fn.Params from fn.Signature, but does not build fn.Body. +func (b *builder) buildParamsOnly(fn *Function) { + // For external (C, asm) functions or functions loaded from + // export data, we must set fn.Params even though there is no + // body code to reference them. + if recv := fn.Signature.Recv(); recv != nil { + fn.addParamVar(recv) } + params := fn.Signature.Params() + for i, n := 0, params.Len(); i < n; i++ { + fn.addParamVar(params.At(i)) + } +} - var recvField *ast.FieldList - var body *ast.BlockStmt - var functype *ast.FuncType - switch n := fn.syntax.(type) { - case nil: - if fn.Params != nil { - return // not a Go source function. (Synthetic, or from object file.) - } +// buildFromSyntax builds fn.Body from fn.syntax, which must be non-nil. +func (b *builder) buildFromSyntax(fn *Function) { + var ( + recvField *ast.FieldList + body *ast.BlockStmt + functype *ast.FuncType + ) + switch syntax := fn.syntax.(type) { case *ast.FuncDecl: - functype = n.Type - recvField = n.Recv - body = n.Body + functype = syntax.Type + recvField = syntax.Recv + body = syntax.Body + if body == nil { + b.buildParamsOnly(fn) // no body (non-Go function) + return + } case *ast.FuncLit: - functype = n.Type - body = n.Body + functype = syntax.Type + body = syntax.Body + case nil: + panic("no syntax") default: - panic(n) + panic(syntax) // unexpected syntax } - if body == nil { - // External function. - if fn.Params == nil { - // This condition ensures we add a non-empty - // params list once only, but we may attempt - // the degenerate empty case repeatedly. - // TODO(adonovan): opt: don't do that. - - // We set Function.Params even though there is no body - // code to reference them. This simplifies clients. - if recv := fn.Signature.Recv(); recv != nil { - fn.addParamObj(recv) - } - params := fn.Signature.Params() - for i, n := 0, params.Len(); i < n; i++ { - fn.addParamObj(params.At(i)) - } - } - return - } - - // Build instantiation wrapper around generic body? - if fn.topLevelOrigin != nil && fn.subst == nil { - buildInstantiationWrapper(fn) - return - } - - if fn.Prog.mode&LogSource != 0 { - defer logStack("build function %s @ %s", fn, fn.Prog.Fset.Position(fn.pos))() - } fn.startBody() fn.createSyntacticParams(recvField, functype) b.stmt(fn, body) @@ -2381,45 +2591,17 @@ func (b *builder) buildFunctionBody(fn *Function) { fn.finishBody() } -// buildCreated does the BUILD phase for each function created by builder that is not yet BUILT. -// Functions are built using buildFunction. -// -// May add types that require runtime type information to builder. -func (b *builder) buildCreated() { - for ; b.finished < b.created.Len(); b.finished++ { - fn := b.created.At(b.finished) - b.buildFunction(fn) - } -} - -// Adds any needed runtime type information for the created functions. +// addRuntimeType records t as a runtime type, +// along with all types derivable from it using reflection. // -// May add newly CREATEd functions that may need to be built or runtime type information. -// -// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu) -func (b *builder) needsRuntimeTypes() { - if b.created.Len() == 0 { - return - } - prog := b.created.At(0).Prog - - var rtypes []types.Type - for ; b.rtypes < b.finished; b.rtypes++ { - fn := b.created.At(b.rtypes) - rtypes = append(rtypes, mayNeedRuntimeTypes(fn)...) - } - - // Calling prog.needMethodsOf(T) on a basic type T is a no-op. - // Filter out the basic types to reduce acquiring prog.methodsMu. - rtypes = nonbasicTypes(rtypes) - - for _, T := range rtypes { - prog.needMethodsOf(T, b.created) - } -} - -func (b *builder) done() bool { - return b.rtypes >= b.created.Len() +// Acquires prog.runtimeTypesMu. +func addRuntimeType(prog *Program, t types.Type) { + prog.runtimeTypesMu.Lock() + defer prog.runtimeTypesMu.Unlock() + forEachReachable(&prog.MethodSets, t, func(t types.Type) bool { + prev, _ := prog.runtimeTypes.Set(t, true).(bool) + return !prev // already seen? + }) } // Build calls Package.Build for each package in prog. @@ -2447,9 +2629,11 @@ func (prog *Program) Build() { // Build builds SSA code for all functions and vars in package p. // -// Precondition: CreatePackage must have been called for all of p's -// direct imports (and hence its direct imports must have been -// error-free). +// CreatePackage must have been called for all of p's direct imports +// (and hence its direct imports must have been error-free). It is not +// necessary to call CreatePackage for indirect dependencies. +// Functions will be created for all necessary methods in those +// packages on demand. // // Build is idempotent and thread-safe. func (p *Package) Build() { p.buildOnce.Do(p.build) } @@ -2458,45 +2642,39 @@ func (p *Package) build() { if p.info == nil { return // synthetic package, e.g. "testmain" } - - // Ensure we have runtime type info for all exported members. - // Additionally filter for just concrete types that can be runtime types. - // - // TODO(adonovan): ideally belongs in memberFromObject, but - // that would require package creation in topological order. - for name, mem := range p.Members { - isGround := func(m Member) bool { - switch m := m.(type) { - case *Type: - named, _ := m.Type().(*types.Named) - return named == nil || typeparams.ForNamed(named) == nil - case *Function: - return m.typeparams.Len() == 0 - } - return true // *NamedConst, *Global - } - if ast.IsExported(name) && isGround(mem) { - p.Prog.needMethodsOf(mem.Type(), &p.created) - } - } if p.Prog.mode&LogSource != 0 { defer logStack("build %s", p)() } b := builder{created: &p.created} - init := p.init - init.startBody() + b.iterate() + + // We no longer need transient information: ASTs or go/types deductions. + p.info = nil + p.created = nil + p.files = nil + p.initVersion = nil + + if p.Prog.mode&SanityCheckFunctions != 0 { + sanityCheckPackage(p) + } +} + +// buildPackageInit builds fn.Body for the synthetic package initializer. +func (b *builder) buildPackageInit(fn *Function) { + p := fn.Pkg + fn.startBody() var done *BasicBlock if p.Prog.mode&BareInits == 0 { // Make init() skip if package is already initialized. initguard := p.Var("init$guard") - doinit := init.newBasicBlock("init.start") - done = init.newBasicBlock("init.done") - emitIf(init, emitLoad(init, initguard), done, doinit) - init.currentBlock = doinit - emitStore(init, initguard, vTrue, token.NoPos) + doinit := fn.newBasicBlock("init.start") + done = fn.newBasicBlock("init.done") + emitIf(fn, emitLoad(fn, initguard), done, doinit) + fn.currentBlock = doinit + emitStore(fn, initguard, vTrue, token.NoPos) // Call the init() function of each package we import. for _, pkg := range p.Pkg.Imports() { @@ -2506,9 +2684,9 @@ func (p *Package) build() { } var v Call v.Call.Value = prereq.init - v.Call.pos = init.pos + v.Call.pos = fn.pos v.setType(types.NewTuple()) - init.emit(&v) + fn.emit(&v) } } @@ -2516,11 +2694,18 @@ func (p *Package) build() { if len(p.info.InitOrder) > 0 && len(p.files) == 0 { panic("no source files provided for package. cannot initialize globals") } + for _, varinit := range p.info.InitOrder { - if init.Prog.mode&LogSource != 0 { + if fn.Prog.mode&LogSource != 0 { fmt.Fprintf(os.Stderr, "build global initializer %v @ %s\n", varinit.Lhs, p.Prog.Fset.Position(varinit.Rhs.Pos())) } + // Initializers for global vars are evaluated in dependency + // order, but may come from arbitrary files of the package + // with different versions, so we transiently update + // fn.goversion for each one. (Since init is a synthetic + // function it has no syntax of its own that needs a version.) + fn.goversion = p.initVersion[varinit.Rhs] if len(varinit.Lhs) == 1 { // 1:1 initialization: var x, y = a(), b() var lval lvalue @@ -2529,28 +2714,33 @@ func (p *Package) build() { } else { lval = blank{} } - b.assign(init, lval, varinit.Rhs, true, nil) + b.assign(fn, lval, varinit.Rhs, true, nil) } else { // n:1 initialization: var x, y := f() - tuple := b.exprN(init, varinit.Rhs) + tuple := b.exprN(fn, varinit.Rhs) for i, v := range varinit.Lhs { if v.Name() == "_" { continue } - emitStore(init, p.objects[v].(*Global), emitExtract(init, tuple, i), v.Pos()) + emitStore(fn, p.objects[v].(*Global), emitExtract(fn, tuple, i), v.Pos()) } } } + // The rest of the init function is synthetic: + // no syntax, info, goversion. + fn.info = nil + fn.goversion = "" + // Call all of the declared init() functions in source order. for _, file := range p.files { for _, decl := range file.Decls { if decl, ok := decl.(*ast.FuncDecl); ok { id := decl.Name if !isBlankIdent(id) && id.Name == "init" && decl.Recv == nil { - fn := p.objects[p.info.Defs[id]].(*Function) + declaredInit := p.objects[p.info.Defs[id]].(*Function) var v Call - v.Call.Value = fn + v.Call.Value = declaredInit v.setType(types.NewTuple()) p.init.emit(&v) } @@ -2560,35 +2750,9 @@ func (p *Package) build() { // Finish up init(). if p.Prog.mode&BareInits == 0 { - emitJump(init, done) - init.currentBlock = done - } - init.emit(new(Return)) - init.finishBody() - init.done() - - // Build all CREATEd functions and add runtime types. - // These Functions include package-level functions, init functions, methods, and synthetic (including unreachable/blank ones). - // Builds any functions CREATEd while building this package. - // - // Initially the created functions for the package are: - // [init, decl0, ... , declN] - // Where decl0, ..., declN are declared functions in source order, but it's not significant. - // - // As these are built, more functions (function literals, wrappers, etc.) can be CREATEd. - // Iterate until we reach a fixed point. - // - // Wait for init() to be BUILT as that cannot be built by buildFunction(). - // - for !b.done() { - b.buildCreated() // build any CREATEd and not BUILT function. May add runtime types. - b.needsRuntimeTypes() // Add all of the runtime type information. May CREATE Functions. - } - - p.info = nil // We no longer need ASTs or go/types deductions. - p.created = nil // We no longer need created functions. - - if p.Prog.mode&SanityCheckFunctions != 0 { - sanityCheckPackage(p) + emitJump(fn, done) + fn.currentBlock = done } + fn.emit(new(Return)) + fn.finishBody() } diff --git a/vendor/golang.org/x/tools/go/ssa/const.go b/vendor/golang.org/x/tools/go/ssa/const.go index 4a51a2cb4..2a6ac5882 100644 --- a/vendor/golang.org/x/tools/go/ssa/const.go +++ b/vendor/golang.org/x/tools/go/ssa/const.go @@ -125,7 +125,7 @@ func zeroString(t types.Type, from *types.Package) string { components[i] = zeroString(t.At(i).Type(), from) } return "(" + strings.Join(components, ", ") + ")" - case *typeparams.TypeParam: + case *types.TypeParam: return "*new(" + relType(t, from) + ")" } panic(fmt.Sprint("zeroString: unexpected ", t)) diff --git a/vendor/golang.org/x/tools/go/ssa/coretype.go b/vendor/golang.org/x/tools/go/ssa/coretype.go index 128d61e42..88136b438 100644 --- a/vendor/golang.org/x/tools/go/ssa/coretype.go +++ b/vendor/golang.org/x/tools/go/ssa/coretype.go @@ -40,19 +40,19 @@ func isBytestring(T types.Type) bool { } // termList is a list of types. -type termList []*typeparams.Term // type terms of the type set +type termList []*types.Term // type terms of the type set func (s termList) Len() int { return len(s) } func (s termList) At(i int) types.Type { return s[i].Type() } // typeSetOf returns the type set of typ. Returns an empty typeset on an error. func typeSetOf(typ types.Type) termList { // This is a adaptation of x/exp/typeparams.NormalTerms which x/tools cannot depend on. - var terms []*typeparams.Term + var terms []*types.Term var err error switch typ := typ.(type) { - case *typeparams.TypeParam: + case *types.TypeParam: terms, err = typeparams.StructuralTerms(typ) - case *typeparams.Union: + case *types.Union: terms, err = typeparams.UnionTermSet(typ) case *types.Interface: terms, err = typeparams.InterfaceTermSet(typ) @@ -60,7 +60,7 @@ func typeSetOf(typ types.Type) termList { // Common case. // Specializing the len=1 case to avoid a slice // had no measurable space/time benefit. - terms = []*typeparams.Term{typeparams.NewTerm(false, typ)} + terms = []*types.Term{types.NewTerm(false, typ)} } if err != nil { diff --git a/vendor/golang.org/x/tools/go/ssa/create.go b/vendor/golang.org/x/tools/go/ssa/create.go index 1bf88c83e..c4da35d0b 100644 --- a/vendor/golang.org/x/tools/go/ssa/create.go +++ b/vendor/golang.org/x/tools/go/ssa/create.go @@ -15,41 +15,43 @@ import ( "os" "sync" - "golang.org/x/tools/go/types/typeutil" - "golang.org/x/tools/internal/typeparams" + "golang.org/x/tools/internal/versions" ) // NewProgram returns a new SSA Program. // // mode controls diagnostics and checking during SSA construction. +// +// To construct an SSA program: +// +// - Call NewProgram to create an empty Program. +// - Call CreatePackage providing typed syntax for each package +// you want to build, and call it with types but not +// syntax for each of those package's direct dependencies. +// - Call [Package.Build] on each syntax package you wish to build, +// or [Program.Build] to build all of them. +// +// See the Example tests for simple examples. func NewProgram(fset *token.FileSet, mode BuilderMode) *Program { - prog := &Program{ + return &Program{ Fset: fset, imported: make(map[string]*Package), packages: make(map[*types.Package]*Package), - thunks: make(map[selectionKey]*Function), - bounds: make(map[boundsKey]*Function), mode: mode, canon: newCanonizer(), - ctxt: typeparams.NewContext(), - instances: make(map[*Function]*instanceSet), + ctxt: types.NewContext(), parameterized: tpWalker{seen: make(map[types.Type]bool)}, } - - h := typeutil.MakeHasher() // protected by methodsMu, in effect - prog.methodSets.SetHasher(h) - prog.runtimeTypes.SetHasher(h) - - return prog } // memberFromObject populates package pkg with a member for the // typechecker object obj. // // For objects from Go source code, syntax is the associated syntax -// tree (for funcs and vars only); it will be used during the build +// tree (for funcs and vars only) and goversion defines the +// appropriate interpretation; they will be used during the build // phase. -func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) { +func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node, goversion string) { name := obj.Name() switch obj := obj.(type) { case *types.Builtin: @@ -58,9 +60,11 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) { } case *types.TypeName: - pkg.Members[name] = &Type{ - object: obj, - pkg: pkg, + if name != "_" { + pkg.Members[name] = &Type{ + object: obj, + pkg: pkg, + } } case *types.Const: @@ -70,7 +74,9 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) { pkg: pkg, } pkg.objects[obj] = c - pkg.Members[name] = c + if name != "_" { + pkg.Members[name] = c + } case *types.Var: g := &Global{ @@ -81,7 +87,9 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) { pos: obj.Pos(), } pkg.objects[obj] = g - pkg.Members[name] = g + if name != "_" { + pkg.Members[name] = g + } case *types.Func: sig := obj.Type().(*types.Signature) @@ -89,36 +97,10 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) { pkg.ninit++ name = fmt.Sprintf("init#%d", pkg.ninit) } - - // Collect type parameters if this is a generic function/method. - var tparams *typeparams.TypeParamList - if rtparams := typeparams.RecvTypeParams(sig); rtparams.Len() > 0 { - tparams = rtparams - } else if sigparams := typeparams.ForSignature(sig); sigparams.Len() > 0 { - tparams = sigparams - } - - fn := &Function{ - name: name, - object: obj, - Signature: sig, - syntax: syntax, - pos: obj.Pos(), - Pkg: pkg, - Prog: pkg.Prog, - typeparams: tparams, - info: pkg.info, - } - pkg.created.Add(fn) - if syntax == nil { - fn.Synthetic = "loaded from gc object file" - } - if tparams.Len() > 0 { - fn.Prog.createInstanceSet(fn) - } - + fn := createFunction(pkg.Prog, obj, name, syntax, pkg.info, goversion, &pkg.created) + fn.Pkg = pkg pkg.objects[obj] = fn - if sig.Recv() == nil { + if name != "_" && sig.Recv() == nil { pkg.Members[name] = fn // package-level function } @@ -127,45 +109,79 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) { } } +// createFunction creates a function or method. It supports both +// CreatePackage (with or without syntax) and the on-demand creation +// of methods in non-created packages based on their types.Func. +func createFunction(prog *Program, obj *types.Func, name string, syntax ast.Node, info *types.Info, goversion string, cr *creator) *Function { + sig := obj.Type().(*types.Signature) + + // Collect type parameters. + var tparams *types.TypeParamList + if rtparams := sig.RecvTypeParams(); rtparams.Len() > 0 { + tparams = rtparams // method of generic type + } else if sigparams := sig.TypeParams(); sigparams.Len() > 0 { + tparams = sigparams // generic function + } + + /* declared function/method (from syntax or export data) */ + fn := &Function{ + name: name, + object: obj, + Signature: sig, + build: (*builder).buildFromSyntax, + syntax: syntax, + info: info, + goversion: goversion, + pos: obj.Pos(), + Pkg: nil, // may be set by caller + Prog: prog, + typeparams: tparams, + } + if fn.syntax == nil { + fn.Synthetic = "from type information" + fn.build = (*builder).buildParamsOnly + } + if tparams.Len() > 0 { + fn.generic = new(generic) + } + cr.Add(fn) + return fn +} + // membersFromDecl populates package pkg with members for each // typechecker object (var, func, const or type) associated with the // specified decl. -func membersFromDecl(pkg *Package, decl ast.Decl) { +func membersFromDecl(pkg *Package, decl ast.Decl, goversion string) { switch decl := decl.(type) { case *ast.GenDecl: // import, const, type or var switch decl.Tok { case token.CONST: for _, spec := range decl.Specs { for _, id := range spec.(*ast.ValueSpec).Names { - if !isBlankIdent(id) { - memberFromObject(pkg, pkg.info.Defs[id], nil) - } + memberFromObject(pkg, pkg.info.Defs[id], nil, "") } } case token.VAR: for _, spec := range decl.Specs { + for _, rhs := range spec.(*ast.ValueSpec).Values { + pkg.initVersion[rhs] = goversion + } for _, id := range spec.(*ast.ValueSpec).Names { - if !isBlankIdent(id) { - memberFromObject(pkg, pkg.info.Defs[id], spec) - } + memberFromObject(pkg, pkg.info.Defs[id], spec, goversion) } } case token.TYPE: for _, spec := range decl.Specs { id := spec.(*ast.TypeSpec).Name - if !isBlankIdent(id) { - memberFromObject(pkg, pkg.info.Defs[id], nil) - } + memberFromObject(pkg, pkg.info.Defs[id], nil, "") } } case *ast.FuncDecl: id := decl.Name - if !isBlankIdent(id) { - memberFromObject(pkg, pkg.info.Defs[id], decl) - } + memberFromObject(pkg, pkg.info.Defs[id], decl, goversion) } } @@ -182,7 +198,7 @@ func (c *creator) Add(fn *Function) { func (c *creator) At(i int) *Function { return (*c)[i] } func (c *creator) Len() int { return len(*c) } -// CreatePackage constructs and returns an SSA Package from the +// CreatePackage creates and returns an SSA Package from the // specified type-checked, error-free file ASTs, and populates its // Members mapping. // @@ -190,36 +206,48 @@ func (c *creator) Len() int { return len(*c) } // subsequent call to ImportedPackage(pkg.Path()). // // The real work of building SSA form for each function is not done -// until a subsequent call to Package.Build(). +// until a subsequent call to Package.Build. +// +// CreatePackage should not be called after building any package in +// the program. func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info *types.Info, importable bool) *Package { + // TODO(adonovan): assert that no package has yet been built. + if pkg == nil { + panic("nil pkg") // otherwise pkg.Scope below returns types.Universe! + } p := &Package{ Prog: prog, Members: make(map[string]Member), objects: make(map[types.Object]Member), Pkg: pkg, - info: info, // transient (CREATE and BUILD phases) - files: files, // transient (CREATE and BUILD phases) + syntax: info != nil, + // transient values (cleared after Package.Build) + info: info, + files: files, + initVersion: make(map[ast.Expr]string), } - // Add init() function. + /* synthesized package initializer */ p.init = &Function{ name: "init", Signature: new(types.Signature), Synthetic: "package initializer", Pkg: p, Prog: prog, + build: (*builder).buildPackageInit, info: p.info, + goversion: "", // See Package.build for details. } p.Members[p.init.name] = p.init p.created.Add(p.init) - // CREATE phase. // Allocate all package members: vars, funcs, consts and types. if len(files) > 0 { // Go source package. for _, file := range files { + goversion := versions.Lang(versions.FileVersions(p.info, file)) for _, decl := range file.Decls { - membersFromDecl(p, decl) + membersFromDecl(p, decl, goversion) } } } else { @@ -229,11 +257,11 @@ func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info * scope := p.Pkg.Scope() for _, name := range scope.Names() { obj := scope.Lookup(name) - memberFromObject(p, obj, nil) + memberFromObject(p, obj, nil, "") if obj, ok := obj.(*types.TypeName); ok { if named, ok := obj.Type().(*types.Named); ok { for i, n := 0, named.NumMethods(); i < n; i++ { - memberFromObject(p, named.Method(i), nil) + memberFromObject(p, named.Method(i), nil, "") } } } @@ -271,8 +299,8 @@ func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info * // printMu serializes printing of Packages/Functions to stdout. var printMu sync.Mutex -// AllPackages returns a new slice containing all packages in the -// program prog in unspecified order. +// AllPackages returns a new slice containing all packages created by +// prog.CreatePackage in in unspecified order. func (prog *Program) AllPackages() []*Package { pkgs := make([]*Package, 0, len(prog.packages)) for _, pkg := range prog.packages { diff --git a/vendor/golang.org/x/tools/go/ssa/doc.go b/vendor/golang.org/x/tools/go/ssa/doc.go index a687de45e..3310b5509 100644 --- a/vendor/golang.org/x/tools/go/ssa/doc.go +++ b/vendor/golang.org/x/tools/go/ssa/doc.go @@ -7,8 +7,6 @@ // static single-assignment (SSA) form intermediate representation // (IR) for the bodies of functions. // -// THIS INTERFACE IS EXPERIMENTAL AND IS LIKELY TO CHANGE. -// // For an introduction to SSA form, see // http://en.wikipedia.org/wiki/Static_single_assignment_form. // This page provides a broader reading list: @@ -21,15 +19,15 @@ // All looping, branching and switching constructs are replaced with // unstructured control flow. Higher-level control flow constructs // such as multi-way branch can be reconstructed as needed; see -// ssautil.Switches() for an example. +// [golang.org/x/tools/go/ssa/ssautil.Switches] for an example. // // The simplest way to create the SSA representation of a package is -// to load typed syntax trees using golang.org/x/tools/go/packages, then -// invoke the ssautil.Packages helper function. See Example_loadPackages -// and Example_loadWholeProgram for examples. -// The resulting ssa.Program contains all the packages and their +// to load typed syntax trees using [golang.org/x/tools/go/packages], then +// invoke the [golang.org/x/tools/go/ssa/ssautil.Packages] helper function. +// (See the package-level Examples named LoadPackages and LoadWholeProgram.) +// The resulting [ssa.Program] contains all the packages and their // members, but SSA code is not created for function bodies until a -// subsequent call to (*Package).Build or (*Program).Build. +// subsequent call to [Package.Build] or [Program.Build]. // // The builder initially builds a naive SSA form in which all local // variables are addresses of stack locations with explicit loads and @@ -41,13 +39,13 @@ // // The primary interfaces of this package are: // -// - Member: a named member of a Go package. -// - Value: an expression that yields a value. -// - Instruction: a statement that consumes values and performs computation. -// - Node: a Value or Instruction (emphasizing its membership in the SSA value graph) +// - [Member]: a named member of a Go package. +// - [Value]: an expression that yields a value. +// - [Instruction]: a statement that consumes values and performs computation. +// - [Node]: a [Value] or [Instruction] (emphasizing its membership in the SSA value graph) // -// A computation that yields a result implements both the Value and -// Instruction interfaces. The following table shows for each +// A computation that yields a result implements both the [Value] and +// [Instruction] interfaces. The following table shows for each // concrete type which of these interfaces it implements. // // Value? Instruction? Member? @@ -97,15 +95,15 @@ // *TypeAssert ✔ ✔ // *UnOp ✔ ✔ // -// Other key types in this package include: Program, Package, Function -// and BasicBlock. +// Other key types in this package include: [Program], [Package], [Function] +// and [BasicBlock]. // // The program representation constructed by this package is fully // resolved internally, i.e. it does not rely on the names of Values, // Packages, Functions, Types or BasicBlocks for the correct // interpretation of the program. Only the identities of objects and // the topology of the SSA and type graphs are semantically -// significant. (There is one exception: Ids, used to identify field +// significant. (There is one exception: [types.Id] values, which identify field // and method names, contain strings.) Avoidance of name-based // operations simplifies the implementation of subsequent passes and // can make them very efficient. Many objects are nonetheless named @@ -113,11 +111,9 @@ // either accurate or unambiguous. The public API exposes a number of // name-based maps for client convenience. // -// The ssa/ssautil package provides various utilities that depend only -// on the public API of this package. -// -// TODO(adonovan): Consider the exceptional control-flow implications -// of defer and recover(). +// The [golang.org/x/tools/go/ssa/ssautil] package provides various +// helper functions, for example to simplify loading a Go program into +// SSA form. // // TODO(adonovan): write a how-to document for all the various cases // of trying to determine corresponding elements across the four diff --git a/vendor/golang.org/x/tools/go/ssa/dom.go b/vendor/golang.org/x/tools/go/ssa/dom.go index 66a2f5e6e..02c1ae83a 100644 --- a/vendor/golang.org/x/tools/go/ssa/dom.go +++ b/vendor/golang.org/x/tools/go/ssa/dom.go @@ -40,20 +40,25 @@ func (b *BasicBlock) Dominates(c *BasicBlock) bool { return b.dom.pre <= c.dom.pre && c.dom.post <= b.dom.post } -type byDomPreorder []*BasicBlock - -func (a byDomPreorder) Len() int { return len(a) } -func (a byDomPreorder) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a byDomPreorder) Less(i, j int) bool { return a[i].dom.pre < a[j].dom.pre } - -// DomPreorder returns a new slice containing the blocks of f in -// dominator tree preorder. +// DomPreorder returns a new slice containing the blocks of f +// in a preorder traversal of the dominator tree. func (f *Function) DomPreorder() []*BasicBlock { - n := len(f.Blocks) - order := make(byDomPreorder, n) - copy(order, f.Blocks) - sort.Sort(order) - return order + slice := append([]*BasicBlock(nil), f.Blocks...) + sort.Slice(slice, func(i, j int) bool { + return slice[i].dom.pre < slice[j].dom.pre + }) + return slice +} + +// DomPostorder returns a new slice containing the blocks of f +// in a postorder traversal of the dominator tree. +// (This is not the same as a postdominance order.) +func (f *Function) DomPostorder() []*BasicBlock { + slice := append([]*BasicBlock(nil), f.Blocks...) + sort.Slice(slice, func(i, j int) bool { + return slice[i].dom.post < slice[j].dom.post + }) + return slice } // domInfo contains a BasicBlock's dominance information. diff --git a/vendor/golang.org/x/tools/go/ssa/emit.go b/vendor/golang.org/x/tools/go/ssa/emit.go index abb617e6d..d77b4407a 100644 --- a/vendor/golang.org/x/tools/go/ssa/emit.go +++ b/vendor/golang.org/x/tools/go/ssa/emit.go @@ -13,16 +13,53 @@ import ( "go/types" ) -// emitNew emits to f a new (heap Alloc) instruction allocating an -// object of type typ. pos is the optional source location. -func emitNew(f *Function, typ types.Type, pos token.Pos) *Alloc { - v := &Alloc{Heap: true} +// emitAlloc emits to f a new Alloc instruction allocating a variable +// of type typ. +// +// The caller must set Alloc.Heap=true (for an heap-allocated variable) +// or add the Alloc to f.Locals (for a frame-allocated variable). +// +// During building, a variable in f.Locals may have its Heap flag +// set when it is discovered that its address is taken. +// These Allocs are removed from f.Locals at the end. +// +// The builder should generally call one of the emit{New,Local,LocalVar} wrappers instead. +func emitAlloc(f *Function, typ types.Type, pos token.Pos, comment string) *Alloc { + v := &Alloc{Comment: comment} v.setType(types.NewPointer(typ)) v.setPos(pos) f.emit(v) return v } +// emitNew emits to f a new Alloc instruction heap-allocating a +// variable of type typ. pos is the optional source location. +func emitNew(f *Function, typ types.Type, pos token.Pos, comment string) *Alloc { + alloc := emitAlloc(f, typ, pos, comment) + alloc.Heap = true + return alloc +} + +// emitLocal creates a local var for (t, pos, comment) and +// emits an Alloc instruction for it. +// +// (Use this function or emitNew for synthetic variables; +// for source-level variables, use emitLocalVar.) +func emitLocal(f *Function, t types.Type, pos token.Pos, comment string) *Alloc { + local := emitAlloc(f, t, pos, comment) + f.Locals = append(f.Locals, local) + return local +} + +// emitLocalVar creates a local var for v and emits an Alloc instruction for it. +// Subsequent calls to f.lookup(v) return it. +// It applies the appropriate generic instantiation to the type. +func emitLocalVar(f *Function, v *types.Var) *Alloc { + alloc := emitLocal(f, f.typ(v.Type()), v.Pos(), v.Name()) + f.vars[v] = alloc + return alloc +} + // emitLoad emits to f an instruction to load the address addr into a // new temporary, and returns the value so defined. func emitLoad(f *Function, addr Value) *UnOp { @@ -148,7 +185,7 @@ func emitCompare(f *Function, op token.Token, x, y Value, pos token.Pos) Value { // Precondition: neither argument is a named type. func isValuePreserving(ut_src, ut_dst types.Type) bool { // Identical underlying types? - if structTypesIdentical(ut_dst, ut_src) { + if types.IdenticalIgnoreTags(ut_dst, ut_src) { return true } @@ -206,6 +243,13 @@ func emitConv(f *Function, val Value, typ types.Type) Value { val = emitConv(f, val, types.Default(ut_src)) } + // Record the types of operands to MakeInterface, if + // non-parameterized, as they are the set of runtime types. + t := val.Type() + if f.typeparams.Len() == 0 || !f.Prog.parameterized.isParameterized(t) { + addRuntimeType(f.Prog, t) + } + mi := &MakeInterface{X: val} mi.setType(typ) return f.emit(mi) @@ -537,48 +581,6 @@ func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, id *ast. return v } -// emitSliceToArray emits to f code to convert a slice value to an array value. -// -// Precondition: all types in type set of typ are arrays and convertible to all -// types in the type set of val.Type(). -func emitSliceToArray(f *Function, val Value, typ types.Type) Value { - // Emit the following: - // if val == nil && len(typ) == 0 { - // ptr = &[0]T{} - // } else { - // ptr = SliceToArrayPointer(val) - // } - // v = *ptr - - ptype := types.NewPointer(typ) - p := &SliceToArrayPointer{X: val} - p.setType(ptype) - ptr := f.emit(p) - - nilb := f.newBasicBlock("slicetoarray.nil") - nonnilb := f.newBasicBlock("slicetoarray.nonnil") - done := f.newBasicBlock("slicetoarray.done") - - cond := emitCompare(f, token.EQL, ptr, zeroConst(ptype), token.NoPos) - emitIf(f, cond, nilb, nonnilb) - f.currentBlock = nilb - - zero := f.addLocal(typ, token.NoPos) - emitJump(f, done) - f.currentBlock = nonnilb - - emitJump(f, done) - f.currentBlock = done - - phi := &Phi{Edges: []Value{zero, ptr}, Comment: "slicetoarray"} - phi.pos = val.Pos() - phi.setType(typ) - x := f.emit(phi) - unOp := &UnOp{Op: token.MUL, X: x} - unOp.setType(typ) - return f.emit(unOp) -} - // createRecoverBlock emits to f a block of code to return after a // recovered panic, and sets f.Recover to it. // diff --git a/vendor/golang.org/x/tools/go/ssa/func.go b/vendor/golang.org/x/tools/go/ssa/func.go index 38c3e31ba..22f878d4e 100644 --- a/vendor/golang.org/x/tools/go/ssa/func.go +++ b/vendor/golang.org/x/tools/go/ssa/func.go @@ -10,13 +10,10 @@ import ( "bytes" "fmt" "go/ast" - "go/token" "go/types" "io" "os" "strings" - - "golang.org/x/tools/internal/typeparams" ) // Like ObjectOf, but panics instead of returning nil. @@ -46,7 +43,7 @@ func (f *Function) typ(T types.Type) types.Type { // If id is an Instance, returns info.Instances[id].Type. // Otherwise returns f.typeOf(id). func (f *Function) instanceType(id *ast.Ident) types.Type { - if t, ok := typeparams.GetInstances(f.info)[id]; ok { + if t, ok := f.info.Instances[id]; ok { return t.Type } return f.typeOf(id) @@ -108,52 +105,40 @@ type lblock struct { // labelledBlock returns the branch target associated with the // specified label, creating it if needed. func (f *Function) labelledBlock(label *ast.Ident) *lblock { - obj := f.objectOf(label) + obj := f.objectOf(label).(*types.Label) lb := f.lblocks[obj] if lb == nil { lb = &lblock{_goto: f.newBasicBlock(label.Name)} if f.lblocks == nil { - f.lblocks = make(map[types.Object]*lblock) + f.lblocks = make(map[*types.Label]*lblock) } f.lblocks[obj] = lb } return lb } -// addParam adds a (non-escaping) parameter to f.Params of the -// specified name, type and source position. -func (f *Function) addParam(name string, typ types.Type, pos token.Pos) *Parameter { - v := &Parameter{ - name: name, - typ: typ, - pos: pos, - parent: f, - } - f.Params = append(f.Params, v) - return v -} - -func (f *Function) addParamObj(obj types.Object) *Parameter { - name := obj.Name() +// addParamVar adds a parameter to f.Params. +func (f *Function) addParamVar(v *types.Var) *Parameter { + name := v.Name() if name == "" { name = fmt.Sprintf("arg%d", len(f.Params)) } - param := f.addParam(name, f.typ(obj.Type()), obj.Pos()) - param.object = obj + param := &Parameter{ + name: name, + object: v, + typ: f.typ(v.Type()), + parent: f, + } + f.Params = append(f.Params, param) return param } // addSpilledParam declares a parameter that is pre-spilled to the // stack; the function body will load/store the spilled location. // Subsequent lifting will eliminate spills where possible. -func (f *Function) addSpilledParam(obj types.Object) { - param := f.addParamObj(obj) - spill := &Alloc{Comment: obj.Name()} - spill.setType(types.NewPointer(param.Type())) - spill.setPos(obj.Pos()) - f.objects[obj] = spill - f.Locals = append(f.Locals, spill) - f.emit(spill) +func (f *Function) addSpilledParam(obj *types.Var) { + param := f.addParamVar(obj) + spill := emitLocalVar(f, obj) f.emit(&Store{Addr: spill, Val: param}) } @@ -161,7 +146,7 @@ func (f *Function) addSpilledParam(obj types.Object) { // Precondition: f.Type() already set. func (f *Function) startBody() { f.currentBlock = f.newBasicBlock("entry") - f.objects = make(map[types.Object]Value) // needed for some synthetics, e.g. init + f.vars = make(map[*types.Var]Value) // needed for some synthetics, e.g. init } // createSyntacticParams populates f.Params and generates code (spills @@ -177,11 +162,11 @@ func (f *Function) createSyntacticParams(recv *ast.FieldList, functype *ast.Func if recv != nil { for _, field := range recv.List { for _, n := range field.Names { - f.addSpilledParam(f.info.Defs[n]) + f.addSpilledParam(identVar(f, n)) } // Anonymous receiver? No need to spill. if field.Names == nil { - f.addParamObj(f.Signature.Recv()) + f.addParamVar(f.Signature.Recv()) } } } @@ -191,11 +176,11 @@ func (f *Function) createSyntacticParams(recv *ast.FieldList, functype *ast.Func n := len(f.Params) // 1 if has recv, 0 otherwise for _, field := range functype.Params.List { for _, n := range field.Names { - f.addSpilledParam(f.info.Defs[n]) + f.addSpilledParam(identVar(f, n)) } // Anonymous parameter? No need to spill. if field.Names == nil { - f.addParamObj(f.Signature.Params().At(len(f.Params) - n)) + f.addParamVar(f.Signature.Params().At(len(f.Params) - n)) } } } @@ -205,7 +190,8 @@ func (f *Function) createSyntacticParams(recv *ast.FieldList, functype *ast.Func for _, field := range functype.Results.List { // Implicit "var" decl of locals for named results. for _, n := range field.Names { - f.namedResults = append(f.namedResults, f.addLocalForIdent(n)) + namedResult := emitLocalVar(f, identVar(f, n)) + f.namedResults = append(f.namedResults, namedResult) } } } @@ -250,49 +236,14 @@ func buildReferrers(f *Function) { } } -// mayNeedRuntimeTypes returns all of the types in the body of fn that might need runtime types. -// -// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu) -func mayNeedRuntimeTypes(fn *Function) []types.Type { - // Collect all types that may need rtypes, i.e. those that flow into an interface. - var ts []types.Type - for _, bb := range fn.Blocks { - for _, instr := range bb.Instrs { - if mi, ok := instr.(*MakeInterface); ok { - ts = append(ts, mi.X.Type()) - } - } - } - - // Types that contain a parameterized type are considered to not be runtime types. - if fn.typeparams.Len() == 0 { - return ts // No potentially parameterized types. - } - // Filter parameterized types, in place. - fn.Prog.methodsMu.Lock() - defer fn.Prog.methodsMu.Unlock() - filtered := ts[:0] - for _, t := range ts { - if !fn.Prog.parameterized.isParameterized(t) { - filtered = append(filtered, t) - } - } - return filtered -} - // finishBody() finalizes the contents of the function after SSA code generation of its body. // // The function is not done being built until done() is called. func (f *Function) finishBody() { - f.objects = nil + f.vars = nil f.currentBlock = nil f.lblocks = nil - // Don't pin the AST in memory (except in debug mode). - if n := f.syntax; n != nil && !f.debugInfo() { - f.syntax = extentNode{n.Pos(), n.End()} - } - // Remove from f.Locals any Allocs that escape to the heap. j := 0 for _, l := range f.Locals { @@ -320,15 +271,15 @@ func (f *Function) finishBody() { lift(f) } - // clear remaining stateful variables + // clear remaining builder state f.namedResults = nil // (used by lifting) - f.info = nil f.subst = nil numberRegisters(f) // uses f.namedRegisters } -// After this, function is done with BUILD phase. +// done marks the building of f's SSA body complete, +// along with any nested functions, and optionally prints them. func (f *Function) done() { assert(f.parent == nil, "done called on an anonymous function") @@ -338,7 +289,7 @@ func (f *Function) done() { visit(anon) // anon is done building before f. } - f.built = true // function is done with BUILD phase + f.build = nil // function is built if f.Prog.mode&PrintFunctions != 0 { printMu.Lock() @@ -376,7 +327,6 @@ func (f *Function) removeNilBlocks() { // size of the instruction stream, and causes Functions to depend upon // the ASTs, potentially keeping them live in memory for longer. func (pkg *Package) SetDebugMode(debug bool) { - // TODO(adonovan): do we want ast.File granularity? pkg.debug = debug } @@ -387,40 +337,25 @@ func (f *Function) debugInfo() bool { return p != nil && p.debug } -// addNamedLocal creates a local variable, adds it to function f and -// returns it. Its name and type are taken from obj. Subsequent -// calls to f.lookup(obj) will return the same local. -func (f *Function) addNamedLocal(obj types.Object) *Alloc { - l := f.addLocal(obj.Type(), obj.Pos()) - l.Comment = obj.Name() - f.objects[obj] = l - return l -} - -func (f *Function) addLocalForIdent(id *ast.Ident) *Alloc { - return f.addNamedLocal(f.info.Defs[id]) -} - -// addLocal creates an anonymous local variable of type typ, adds it -// to function f and returns it. pos is the optional source location. -func (f *Function) addLocal(typ types.Type, pos token.Pos) *Alloc { - typ = f.typ(typ) - v := &Alloc{} - v.setType(types.NewPointer(typ)) - v.setPos(pos) - f.Locals = append(f.Locals, v) - f.emit(v) - return v -} - // lookup returns the address of the named variable identified by obj // that is local to function f or one of its enclosing functions. // If escaping, the reference comes from a potentially escaping pointer // expression and the referent must be heap-allocated. -func (f *Function) lookup(obj types.Object, escaping bool) Value { - if v, ok := f.objects[obj]; ok { - if alloc, ok := v.(*Alloc); ok && escaping { - alloc.Heap = true +// We assume the referent is a *Alloc or *Phi. +// (The only Phis at this stage are those created directly by go1.22 "for" loops.) +func (f *Function) lookup(obj *types.Var, escaping bool) Value { + if v, ok := f.vars[obj]; ok { + if escaping { + switch v := v.(type) { + case *Alloc: + v.Heap = true + case *Phi: + for _, edge := range v.Edges { + if alloc, ok := edge.(*Alloc); ok { + alloc.Heap = true + } + } + } } return v // function-local var (address) } @@ -438,7 +373,7 @@ func (f *Function) lookup(obj types.Object, escaping bool) Value { outer: outer, parent: f, } - f.objects[obj] = v + f.vars[obj] = v f.FreeVars = append(f.FreeVars, v) return v } @@ -536,7 +471,7 @@ func writeSignature(buf *bytes.Buffer, from *types.Package, name string, sig *ty func (fn *Function) declaredPackage() *Package { switch { case fn.Pkg != nil: - return fn.Pkg // non-generic function + return fn.Pkg // non-generic function (does that follow??) case fn.topLevelOrigin != nil: return fn.topLevelOrigin.Pkg // instance of a named generic function case fn.parent != nil: @@ -689,17 +624,11 @@ func (prog *Program) NewFunction(name string, sig *types.Signature, provenance s return &Function{Prog: prog, name: name, Signature: sig, Synthetic: provenance} } -type extentNode [2]token.Pos - -func (n extentNode) Pos() token.Pos { return n[0] } -func (n extentNode) End() token.Pos { return n[1] } - -// Syntax returns an ast.Node whose Pos/End methods provide the -// lexical extent of the function if it was defined by Go source code -// (f.Synthetic==""), or nil otherwise. -// -// If f was built with debug information (see Package.SetDebugRef), -// the result is the *ast.FuncDecl or *ast.FuncLit that declared the -// function. Otherwise, it is an opaque Node providing only position -// information; this avoids pinning the AST in memory. +// Syntax returns the function's syntax (*ast.Func{Decl,Lit) +// if it was produced from syntax. func (f *Function) Syntax() ast.Node { return f.syntax } + +// identVar returns the variable defined by id. +func identVar(fn *Function, id *ast.Ident) *types.Var { + return fn.info.Defs[id].(*types.Var) +} diff --git a/vendor/golang.org/x/tools/go/ssa/identical.go b/vendor/golang.org/x/tools/go/ssa/identical.go deleted file mode 100644 index e8026967b..000000000 --- a/vendor/golang.org/x/tools/go/ssa/identical.go +++ /dev/null @@ -1,12 +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 go1.8 -// +build go1.8 - -package ssa - -import "go/types" - -var structTypesIdentical = types.IdenticalIgnoreTags diff --git a/vendor/golang.org/x/tools/go/ssa/identical_17.go b/vendor/golang.org/x/tools/go/ssa/identical_17.go deleted file mode 100644 index 575aa5dfc..000000000 --- a/vendor/golang.org/x/tools/go/ssa/identical_17.go +++ /dev/null @@ -1,12 +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 !go1.8 -// +build !go1.8 - -package ssa - -import "go/types" - -var structTypesIdentical = types.Identical diff --git a/vendor/golang.org/x/tools/go/ssa/instantiate.go b/vendor/golang.org/x/tools/go/ssa/instantiate.go index 38249dea2..c155f6736 100644 --- a/vendor/golang.org/x/tools/go/ssa/instantiate.go +++ b/vendor/golang.org/x/tools/go/ssa/instantiate.go @@ -6,130 +6,60 @@ package ssa import ( "fmt" - "go/ast" "go/types" + "sync" "golang.org/x/tools/internal/typeparams" ) -// _Instances returns all of the instances generated by runtime types for this function in an unspecified order. -// -// Thread-safe. -// -// This is an experimental interface! It may change without warning. -func (prog *Program) _Instances(fn *Function) []*Function { - if fn.typeparams.Len() == 0 || len(fn.typeargs) > 0 { - return nil - } - - prog.methodsMu.Lock() - defer prog.methodsMu.Unlock() - return prog.instances[fn].list() -} - -// A set of instantiations of a generic function fn. -type instanceSet struct { - fn *Function // fn.typeparams.Len() > 0 and len(fn.typeargs) == 0. - instances map[*typeList]*Function // canonical type arguments to an instance. - syntax *ast.FuncDecl // fn.syntax copy for instantiating after fn is done. nil on synthetic packages. - info *types.Info // fn.pkg.info copy for building after fn is done.. nil on synthetic packages. - - // TODO(taking): Consider ways to allow for clearing syntax and info when done building. - // May require a public API change as MethodValue can request these be built after prog.Build() is done. -} - -func (insts *instanceSet) list() []*Function { - if insts == nil { - return nil - } - - fns := make([]*Function, 0, len(insts.instances)) - for _, fn := range insts.instances { - fns = append(fns, fn) - } - return fns +// A generic records information about a generic origin function, +// including a cache of existing instantiations. +type generic struct { + instancesMu sync.Mutex + instances map[*typeList]*Function // canonical type arguments to an instance. } -// createInstanceSet adds a new instanceSet for a generic function fn if one does not exist. +// instance returns a Function that is the instantiation of generic +// origin function fn with the type arguments targs. // -// Precondition: fn is a package level declaration (function or method). +// Any created instance is added to cr. // -// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodMu) -func (prog *Program) createInstanceSet(fn *Function) { - assert(fn.typeparams.Len() > 0 && len(fn.typeargs) == 0, "Can only create instance sets for generic functions") - - prog.methodsMu.Lock() - defer prog.methodsMu.Unlock() - - syntax, _ := fn.syntax.(*ast.FuncDecl) - assert((syntax == nil) == (fn.syntax == nil), "fn.syntax is either nil or a *ast.FuncDecl") - - if _, ok := prog.instances[fn]; !ok { - prog.instances[fn] = &instanceSet{ - fn: fn, - syntax: syntax, - info: fn.info, +// Acquires fn.generic.instancesMu. +func (fn *Function) instance(targs []types.Type, cr *creator) *Function { + key := fn.Prog.canon.List(targs) + + gen := fn.generic + + gen.instancesMu.Lock() + defer gen.instancesMu.Unlock() + inst, ok := gen.instances[key] + if !ok { + inst = createInstance(fn, targs, cr) + if gen.instances == nil { + gen.instances = make(map[*typeList]*Function) } + gen.instances[key] = inst } + return inst } -// needsInstance returns a Function that is the instantiation of fn with the type arguments targs. -// -// Any CREATEd instance is added to cr. -// -// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodMu) -func (prog *Program) needsInstance(fn *Function, targs []types.Type, cr *creator) *Function { - prog.methodsMu.Lock() - defer prog.methodsMu.Unlock() - - return prog.lookupOrCreateInstance(fn, targs, cr) -} - -// lookupOrCreateInstance returns a Function that is the instantiation of fn with the type arguments targs. -// -// Any CREATEd instance is added to cr. -// -// EXCLUSIVE_LOCKS_REQUIRED(prog.methodMu) -func (prog *Program) lookupOrCreateInstance(fn *Function, targs []types.Type, cr *creator) *Function { - return prog.instances[fn].lookupOrCreate(targs, &prog.parameterized, cr) -} - -// lookupOrCreate returns the instantiation of insts.fn using targs. +// createInstance returns the instantiation of generic function fn using targs. // If the instantiation is created, this is added to cr. -func (insts *instanceSet) lookupOrCreate(targs []types.Type, parameterized *tpWalker, cr *creator) *Function { - if insts.instances == nil { - insts.instances = make(map[*typeList]*Function) - } - - fn := insts.fn +// +// Requires fn.generic.instancesMu. +func createInstance(fn *Function, targs []types.Type, cr *creator) *Function { prog := fn.Prog - // canonicalize on a tuple of targs. Sig is not unique. - // - // func A[T any]() { - // var x T - // fmt.Println("%T", x) - // } - key := prog.canon.List(targs) - if inst, ok := insts.instances[key]; ok { - return inst - } - - // CREATE instance/instantiation wrapper - var syntax ast.Node - if insts.syntax != nil { - syntax = insts.syntax - } - + // Compute signature. var sig *types.Signature var obj *types.Func if recv := fn.Signature.Recv(); recv != nil { // method - m := fn.object.(*types.Func) - obj = prog.canon.instantiateMethod(m, targs, prog.ctxt) + obj = prog.canon.instantiateMethod(fn.object, targs, prog.ctxt) sig = obj.Type().(*types.Signature) } else { - instSig, err := typeparams.Instantiate(prog.ctxt, fn.Signature, targs, false) + // function + instSig, err := types.Instantiate(prog.ctxt, fn.Signature, targs, false) if err != nil { panic(err) } @@ -137,41 +67,48 @@ func (insts *instanceSet) lookupOrCreate(targs []types.Type, parameterized *tpWa if !ok { panic("Instantiate of a Signature returned a non-signature") } - obj = fn.object.(*types.Func) // instantiation does not exist yet + obj = fn.object // instantiation does not exist yet sig = prog.canon.Type(instance).(*types.Signature) } - var synthetic string - var subst *subster - - concrete := !parameterized.anyParameterized(targs) - - if prog.mode&InstantiateGenerics != 0 && concrete { + // Choose strategy (instance or wrapper). + var ( + synthetic string + subst *subster + build buildFunc + ) + if prog.mode&InstantiateGenerics != 0 && !prog.parameterized.anyParameterized(targs) { synthetic = fmt.Sprintf("instance of %s", fn.Name()) - scope := typeparams.OriginMethod(obj).Scope() - subst = makeSubster(prog.ctxt, scope, fn.typeparams, targs, false) + if fn.syntax != nil { + scope := typeparams.OriginMethod(obj).Scope() + subst = makeSubster(prog.ctxt, scope, fn.typeparams, targs, false) + build = (*builder).buildFromSyntax + } else { + build = (*builder).buildParamsOnly + } } else { synthetic = fmt.Sprintf("instantiation wrapper of %s", fn.Name()) + build = (*builder).buildInstantiationWrapper } - name := fmt.Sprintf("%s%s", fn.Name(), targs) // may not be unique + /* generic instance or instantiation wrapper */ instance := &Function{ - name: name, + name: fmt.Sprintf("%s%s", fn.Name(), targs), // may not be unique object: obj, Signature: sig, Synthetic: synthetic, - syntax: syntax, + syntax: fn.syntax, // \ + info: fn.info, // } empty for non-created packages + goversion: fn.goversion, // / + build: build, topLevelOrigin: fn, pos: obj.Pos(), Pkg: nil, Prog: fn.Prog, typeparams: fn.typeparams, // share with origin typeargs: targs, - info: insts.info, // on synthetic packages info is nil. subst: subst, } - cr.Add(instance) - insts.instances[key] = instance return instance } diff --git a/vendor/golang.org/x/tools/go/ssa/lift.go b/vendor/golang.org/x/tools/go/ssa/lift.go index dbd8790c6..da49fe9f1 100644 --- a/vendor/golang.org/x/tools/go/ssa/lift.go +++ b/vendor/golang.org/x/tools/go/ssa/lift.go @@ -103,9 +103,14 @@ func buildDomFrontier(fn *Function) domFrontier { } func removeInstr(refs []Instruction, instr Instruction) []Instruction { + return removeInstrsIf(refs, func(i Instruction) bool { return i == instr }) +} + +func removeInstrsIf(refs []Instruction, p func(Instruction) bool) []Instruction { + // TODO(taking): replace with go1.22 slices.DeleteFunc. i := 0 for _, ref := range refs { - if ref == instr { + if p(ref) { continue } refs[i] = ref diff --git a/vendor/golang.org/x/tools/go/ssa/methods.go b/vendor/golang.org/x/tools/go/ssa/methods.go index 294498371..4797b3928 100644 --- a/vendor/golang.org/x/tools/go/ssa/methods.go +++ b/vendor/golang.org/x/tools/go/ssa/methods.go @@ -10,54 +10,124 @@ import ( "fmt" "go/types" + "golang.org/x/tools/go/types/typeutil" "golang.org/x/tools/internal/typeparams" ) // MethodValue returns the Function implementing method sel, building -// wrapper methods on demand. It returns nil if sel denotes an -// abstract (interface or parameterized) method. +// wrapper methods on demand. It returns nil if sel denotes an +// interface or generic method. // // Precondition: sel.Kind() == MethodVal. // // Thread-safe. // -// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu) +// Acquires prog.methodsMu. func (prog *Program) MethodValue(sel *types.Selection) *Function { if sel.Kind() != types.MethodVal { panic(fmt.Sprintf("MethodValue(%s) kind != MethodVal", sel)) } T := sel.Recv() if types.IsInterface(T) { - return nil // abstract method (interface, possibly type param) + return nil // interface method or type parameter } + + if prog.parameterized.isParameterized(T) { + return nil // generic method + } + if prog.mode&LogSource != 0 { defer logStack("MethodValue %s %v", T, sel)() } - var m *Function - b := builder{created: &creator{}} + var cr creator - prog.methodsMu.Lock() - // Checks whether a type param is reachable from T. - // This is an expensive check. May need to be optimized later. - if !prog.parameterized.isParameterized(T) { - m = prog.addMethod(prog.createMethodSet(T), sel, b.created) + m := func() *Function { + prog.methodsMu.Lock() + defer prog.methodsMu.Unlock() + + // Get or create SSA method set. + mset, ok := prog.methodSets.At(T).(*methodSet) + if !ok { + mset = &methodSet{mapping: make(map[string]*Function)} + prog.methodSets.Set(T, mset) + } + + // Get or create SSA method. + id := sel.Obj().Id() + fn, ok := mset.mapping[id] + if !ok { + obj := sel.Obj().(*types.Func) + _, ptrObj := deptr(recvType(obj)) + _, ptrRecv := deptr(T) + needsPromotion := len(sel.Index()) > 1 + needsIndirection := !ptrObj && ptrRecv + if needsPromotion || needsIndirection { + fn = createWrapper(prog, toSelection(sel), &cr) + } else { + fn = prog.objectMethod(obj, &cr) + } + if fn.Signature.Recv() == nil { + panic(fn) + } + mset.mapping[id] = fn + } + + return fn + }() + + b := builder{created: &cr} + b.iterate() + + return m +} + +// objectMethod returns the Function for a given method symbol. +// The symbol may be an instance of a generic function. It need not +// belong to an existing SSA package created by a call to +// prog.CreatePackage. +// +// objectMethod panics if the function is not a method. +// +// Acquires prog.objectMethodsMu. +func (prog *Program) objectMethod(obj *types.Func, cr *creator) *Function { + sig := obj.Type().(*types.Signature) + if sig.Recv() == nil { + panic("not a method: " + obj.String()) } - prog.methodsMu.Unlock() - if m == nil { - return nil // abstract method (generic) + // Belongs to a created package? + if fn := prog.FuncValue(obj); fn != nil { + return fn } - for !b.done() { - b.buildCreated() - b.needsRuntimeTypes() + + // Instantiation of generic? + if originObj := typeparams.OriginMethod(obj); originObj != obj { + origin := prog.objectMethod(originObj, cr) + assert(origin.typeparams.Len() > 0, "origin is not generic") + targs := receiverTypeArgs(obj) + return origin.instance(targs, cr) } - return m + + // Consult/update cache of methods created from types.Func. + prog.objectMethodsMu.Lock() + defer prog.objectMethodsMu.Unlock() + fn, ok := prog.objectMethods[obj] + if !ok { + fn = createFunction(prog, obj, obj.Name(), nil, nil, "", cr) + fn.Synthetic = "from type information (on demand)" + + if prog.objectMethods == nil { + prog.objectMethods = make(map[*types.Func]*Function) + } + prog.objectMethods[obj] = fn + } + return fn } // LookupMethod returns the implementation of the method of type T // identified by (pkg, name). It returns nil if the method exists but -// is abstract, and panics if T has no such method. +// is an interface method or generic method, and panics if T has no such method. func (prog *Program) LookupMethod(T types.Type, pkg *types.Package, name string) *Function { sel := prog.MethodSets.MethodSet(T).Lookup(pkg, name) if sel == nil { @@ -68,208 +138,136 @@ func (prog *Program) LookupMethod(T types.Type, pkg *types.Package, name string) // methodSet contains the (concrete) methods of a concrete type (non-interface, non-parameterized). type methodSet struct { - mapping map[string]*Function // populated lazily - complete bool // mapping contains all methods -} - -// Precondition: T is a concrete type, e.g. !isInterface(T) and not parameterized. -// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu) -func (prog *Program) createMethodSet(T types.Type) *methodSet { - if prog.mode&SanityCheckFunctions != 0 { - if types.IsInterface(T) || prog.parameterized.isParameterized(T) { - panic("type is interface or parameterized") - } - } - mset, ok := prog.methodSets.At(T).(*methodSet) - if !ok { - mset = &methodSet{mapping: make(map[string]*Function)} - prog.methodSets.Set(T, mset) - } - return mset -} - -// Adds any created functions to cr. -// Precondition: T is a concrete type, e.g. !isInterface(T) and not parameterized. -// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu) -func (prog *Program) addMethod(mset *methodSet, sel *types.Selection, cr *creator) *Function { - if sel.Kind() == types.MethodExpr { - panic(sel) - } - id := sel.Obj().Id() - fn := mset.mapping[id] - if fn == nil { - sel := toSelection(sel) - obj := sel.obj.(*types.Func) - - _, ptrObj := deptr(recvType(obj)) - _, ptrRecv := deptr(sel.recv) - - needsPromotion := len(sel.index) > 1 - needsIndirection := !ptrObj && ptrRecv - if needsPromotion || needsIndirection { - fn = makeWrapper(prog, sel, cr) - } else { - fn = prog.originFunc(obj) - if fn.typeparams.Len() > 0 { // instantiate - targs := receiverTypeArgs(obj) - fn = prog.lookupOrCreateInstance(fn, targs, cr) - } - } - if fn.Signature.Recv() == nil { - panic(fn) // missing receiver - } - mset.mapping[id] = fn - } - return fn + mapping map[string]*Function // populated lazily } -// RuntimeTypes returns a new unordered slice containing all -// concrete types in the program for which a complete (non-empty) -// method set is required at run-time. +// RuntimeTypes returns a new unordered slice containing all types in +// the program for which a runtime type is required. +// +// A runtime type is required for any non-parameterized, non-interface +// type that is converted to an interface, or for any type (including +// interface types) derivable from one through reflection. +// +// The methods of such types may be reachable through reflection or +// interface calls even if they are never called directly. // // Thread-safe. // -// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu) +// Acquires prog.runtimeTypesMu. func (prog *Program) RuntimeTypes() []types.Type { - prog.methodsMu.Lock() - defer prog.methodsMu.Unlock() - - var res []types.Type - prog.methodSets.Iterate(func(T types.Type, v interface{}) { - if v.(*methodSet).complete { - res = append(res, T) - } - }) - return res -} - -// declaredFunc returns the concrete function/method denoted by obj. -// Panic ensues if there is none. -func (prog *Program) declaredFunc(obj *types.Func) *Function { - if v := prog.packageLevelMember(obj); v != nil { - return v.(*Function) - } - panic("no concrete method: " + obj.String()) + prog.runtimeTypesMu.Lock() + defer prog.runtimeTypesMu.Unlock() + return prog.runtimeTypes.Keys() } -// needMethodsOf ensures that runtime type information (including the -// complete method set) is available for the specified type T and all -// its subcomponents. -// -// needMethodsOf must be called for at least every type that is an -// operand of some MakeInterface instruction, and for the type of -// every exported package member. -// -// Adds any created functions to cr. -// -// Precondition: T is not a method signature (*Signature with Recv()!=nil). -// Precondition: T is not parameterized. -// -// Thread-safe. (Called via Package.build from multiple builder goroutines.) +// forEachReachable calls f for type T and each type reachable from +// its type through reflection. // -// TODO(adonovan): make this faster. It accounts for 20% of SSA build time. +// The function f must use memoization to break cycles and +// return false when the type has already been visited. // -// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu) -func (prog *Program) needMethodsOf(T types.Type, cr *creator) { - prog.methodsMu.Lock() - prog.needMethods(T, false, cr) - prog.methodsMu.Unlock() -} - -// Precondition: T is not a method signature (*Signature with Recv()!=nil). -// Precondition: T is not parameterized. -// Recursive case: skip => don't create methods for T. -// -// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu) -func (prog *Program) needMethods(T types.Type, skip bool, cr *creator) { - // Each package maintains its own set of types it has visited. - if prevSkip, ok := prog.runtimeTypes.At(T).(bool); ok { - // needMethods(T) was previously called - if !prevSkip || skip { - return // already seen, with same or false 'skip' value - } - } - prog.runtimeTypes.Set(T, skip) - - tmset := prog.MethodSets.MethodSet(T) - - if !skip && !types.IsInterface(T) && tmset.Len() > 0 { - // Create methods of T. - mset := prog.createMethodSet(T) - if !mset.complete { - mset.complete = true - n := tmset.Len() - for i := 0; i < n; i++ { - prog.addMethod(mset, tmset.At(i), cr) +// TODO(adonovan): publish in typeutil and share with go/callgraph/rta. +func forEachReachable(msets *typeutil.MethodSetCache, T types.Type, f func(types.Type) bool) { + var visit func(T types.Type, skip bool) + visit = func(T types.Type, skip bool) { + if !skip { + if !f(T) { + return } } - } - - // Recursion over signatures of each method. - for i := 0; i < tmset.Len(); i++ { - sig := tmset.At(i).Type().(*types.Signature) - prog.needMethods(sig.Params(), false, cr) - prog.needMethods(sig.Results(), false, cr) - } - switch t := T.(type) { - case *types.Basic: - // nop + // Recursion over signatures of each method. + tmset := msets.MethodSet(T) + for i := 0; i < tmset.Len(); i++ { + sig := tmset.At(i).Type().(*types.Signature) + // It is tempting to call visit(sig, false) + // but, as noted in golang.org/cl/65450043, + // the Signature.Recv field is ignored by + // types.Identical and typeutil.Map, which + // is confusing at best. + // + // More importantly, the true signature rtype + // reachable from a method using reflection + // has no receiver but an extra ordinary parameter. + // For the Read method of io.Reader we want: + // func(Reader, []byte) (int, error) + // but here sig is: + // func([]byte) (int, error) + // with .Recv = Reader (though it is hard to + // notice because it doesn't affect Signature.String + // or types.Identical). + // + // TODO(adonovan): construct and visit the correct + // non-method signature with an extra parameter + // (though since unnamed func types have no methods + // there is essentially no actual demand for this). + // + // TODO(adonovan): document whether or not it is + // safe to skip non-exported methods (as RTA does). + visit(sig.Params(), true) // skip the Tuple + visit(sig.Results(), true) // skip the Tuple + } - case *types.Interface: - // nop---handled by recursion over method set. + switch T := T.(type) { + case *types.Basic: + // nop - case *types.Pointer: - prog.needMethods(t.Elem(), false, cr) + case *types.Interface: + // nop---handled by recursion over method set. - case *types.Slice: - prog.needMethods(t.Elem(), false, cr) + case *types.Pointer: + visit(T.Elem(), false) - case *types.Chan: - prog.needMethods(t.Elem(), false, cr) + case *types.Slice: + visit(T.Elem(), false) - case *types.Map: - prog.needMethods(t.Key(), false, cr) - prog.needMethods(t.Elem(), false, cr) + case *types.Chan: + visit(T.Elem(), false) - case *types.Signature: - if t.Recv() != nil { - panic(fmt.Sprintf("Signature %s has Recv %s", t, t.Recv())) - } - prog.needMethods(t.Params(), false, cr) - prog.needMethods(t.Results(), false, cr) - - case *types.Named: - // A pointer-to-named type can be derived from a named - // type via reflection. It may have methods too. - prog.needMethods(types.NewPointer(T), false, cr) - - // Consider 'type T struct{S}' where S has methods. - // Reflection provides no way to get from T to struct{S}, - // only to S, so the method set of struct{S} is unwanted, - // so set 'skip' flag during recursion. - prog.needMethods(t.Underlying(), true, cr) - - case *types.Array: - prog.needMethods(t.Elem(), false, cr) - - case *types.Struct: - for i, n := 0, t.NumFields(); i < n; i++ { - prog.needMethods(t.Field(i).Type(), false, cr) - } + case *types.Map: + visit(T.Key(), false) + visit(T.Elem(), false) - case *types.Tuple: - for i, n := 0, t.Len(); i < n; i++ { - prog.needMethods(t.At(i).Type(), false, cr) - } + case *types.Signature: + if T.Recv() != nil { + panic(fmt.Sprintf("Signature %s has Recv %s", T, T.Recv())) + } + visit(T.Params(), true) // skip the Tuple + visit(T.Results(), true) // skip the Tuple + + case *types.Named: + // A pointer-to-named type can be derived from a named + // type via reflection. It may have methods too. + visit(types.NewPointer(T), false) + + // Consider 'type T struct{S}' where S has methods. + // Reflection provides no way to get from T to struct{S}, + // only to S, so the method set of struct{S} is unwanted, + // so set 'skip' flag during recursion. + visit(T.Underlying(), true) // skip the unnamed type + + case *types.Array: + visit(T.Elem(), false) + + case *types.Struct: + for i, n := 0, T.NumFields(); i < n; i++ { + // TODO(adonovan): document whether or not + // it is safe to skip non-exported fields. + visit(T.Field(i).Type(), false) + } - case *typeparams.TypeParam: - panic(T) // type parameters are always abstract. + case *types.Tuple: + for i, n := 0, T.Len(); i < n; i++ { + visit(T.At(i).Type(), false) + } - case *typeparams.Union: - // nop + case *types.TypeParam, *types.Union: + // forEachReachable must not be called on parameterized types. + panic(T) - default: - panic(T) + default: + panic(T) + } } + visit(T, false) } diff --git a/vendor/golang.org/x/tools/go/ssa/parameterized.go b/vendor/golang.org/x/tools/go/ssa/parameterized.go index b90ee0e86..84db49d39 100644 --- a/vendor/golang.org/x/tools/go/ssa/parameterized.go +++ b/vendor/golang.org/x/tools/go/ssa/parameterized.go @@ -6,6 +6,7 @@ package ssa import ( "go/types" + "sync" "golang.org/x/tools/internal/typeparams" ) @@ -14,11 +15,24 @@ import ( // // NOTE: Adapted from go/types/infer.go. If that is exported in a future release remove this copy. type tpWalker struct { + mu sync.Mutex seen map[types.Type]bool } -// isParameterized returns true when typ reaches any type parameter. -func (w *tpWalker) isParameterized(typ types.Type) (res bool) { +// isParameterized reports whether t recursively contains a type parameter. +// Thread-safe. +func (w *tpWalker) isParameterized(t types.Type) bool { + // TODO(adonovan): profile. If this operation is expensive, + // handle the most common but shallow cases such as T, pkg.T, + // *T without consulting the cache under the lock. + + w.mu.Lock() + defer w.mu.Unlock() + return w.isParameterizedLocked(t) +} + +// Requires w.mu. +func (w *tpWalker) isParameterizedLocked(typ types.Type) (res bool) { // NOTE: Adapted from go/types/infer.go. Try to keep in sync. // detect cycles @@ -35,25 +49,25 @@ func (w *tpWalker) isParameterized(typ types.Type) (res bool) { break case *types.Array: - return w.isParameterized(t.Elem()) + return w.isParameterizedLocked(t.Elem()) case *types.Slice: - return w.isParameterized(t.Elem()) + return w.isParameterizedLocked(t.Elem()) case *types.Struct: for i, n := 0, t.NumFields(); i < n; i++ { - if w.isParameterized(t.Field(i).Type()) { + if w.isParameterizedLocked(t.Field(i).Type()) { return true } } case *types.Pointer: - return w.isParameterized(t.Elem()) + return w.isParameterizedLocked(t.Elem()) case *types.Tuple: n := t.Len() for i := 0; i < n; i++ { - if w.isParameterized(t.At(i).Type()) { + if w.isParameterizedLocked(t.At(i).Type()) { return true } } @@ -66,11 +80,11 @@ func (w *tpWalker) isParameterized(typ types.Type) (res bool) { // Similarly, the receiver of a method may declare (rather than // use) type parameters, we don't care about those either. // Thus, we only need to look at the input and result parameters. - return w.isParameterized(t.Params()) || w.isParameterized(t.Results()) + return w.isParameterizedLocked(t.Params()) || w.isParameterizedLocked(t.Results()) case *types.Interface: for i, n := 0, t.NumMethods(); i < n; i++ { - if w.isParameterized(t.Method(i).Type()) { + if w.isParameterizedLocked(t.Method(i).Type()) { return true } } @@ -79,31 +93,31 @@ func (w *tpWalker) isParameterized(typ types.Type) (res bool) { panic(err) } for _, term := range terms { - if w.isParameterized(term.Type()) { + if w.isParameterizedLocked(term.Type()) { return true } } case *types.Map: - return w.isParameterized(t.Key()) || w.isParameterized(t.Elem()) + return w.isParameterizedLocked(t.Key()) || w.isParameterizedLocked(t.Elem()) case *types.Chan: - return w.isParameterized(t.Elem()) + return w.isParameterizedLocked(t.Elem()) case *types.Named: - args := typeparams.NamedTypeArgs(t) + args := t.TypeArgs() // TODO(taking): this does not match go/types/infer.go. Check with rfindley. - if params := typeparams.ForNamed(t); params.Len() > args.Len() { + if params := t.TypeParams(); params.Len() > args.Len() { return true } for i, n := 0, args.Len(); i < n; i++ { - if w.isParameterized(args.At(i)) { + if w.isParameterizedLocked(args.At(i)) { return true } } - return w.isParameterized(t.Underlying()) // recurse for types local to parameterized functions + return w.isParameterizedLocked(t.Underlying()) // recurse for types local to parameterized functions - case *typeparams.TypeParam: + case *types.TypeParam: return true default: @@ -113,9 +127,13 @@ func (w *tpWalker) isParameterized(typ types.Type) (res bool) { return false } +// anyParameterized reports whether any element of ts is parameterized. +// Thread-safe. func (w *tpWalker) anyParameterized(ts []types.Type) bool { + w.mu.Lock() + defer w.mu.Unlock() for _, t := range ts { - if w.isParameterized(t) { + if w.isParameterizedLocked(t) { return true } } diff --git a/vendor/golang.org/x/tools/go/ssa/print.go b/vendor/golang.org/x/tools/go/ssa/print.go index 7f34a7b58..727a73502 100644 --- a/vendor/golang.org/x/tools/go/ssa/print.go +++ b/vendor/golang.org/x/tools/go/ssa/print.go @@ -17,7 +17,6 @@ import ( "strings" "golang.org/x/tools/go/types/typeutil" - "golang.org/x/tools/internal/typeparams" ) // relName returns the name of v relative to i. @@ -51,7 +50,7 @@ func relType(t types.Type, from *types.Package) string { return s } -func relTerm(term *typeparams.Term, from *types.Package) string { +func relTerm(term *types.Term, from *types.Package) string { s := relType(term.Type(), from) if term.Tilde() { return "~" + s diff --git a/vendor/golang.org/x/tools/go/ssa/sanity.go b/vendor/golang.org/x/tools/go/ssa/sanity.go index 886be0532..22a3c6bc3 100644 --- a/vendor/golang.org/x/tools/go/ssa/sanity.go +++ b/vendor/golang.org/x/tools/go/ssa/sanity.go @@ -132,6 +132,11 @@ func (s *sanity) checkInstr(idx int, instr Instruction) { case *BinOp: case *Call: + if common := instr.Call; common.IsInvoke() { + if !types.IsInterface(common.Value.Type()) { + s.errorf("invoke on %s (%s) which is not an interface type (or type param)", common.Value, common.Value.Type()) + } + } case *ChangeInterface: case *ChangeType: case *SliceToArrayPointer: @@ -422,7 +427,8 @@ func (s *sanity) checkFunction(fn *Function) bool { // shared across packages, or duplicated as weak symbols in a // separate-compilation model), and error.Error. if fn.Pkg == nil { - if strings.HasPrefix(fn.Synthetic, "wrapper ") || + if strings.HasPrefix(fn.Synthetic, "from type information (on demand)") || + strings.HasPrefix(fn.Synthetic, "wrapper ") || strings.HasPrefix(fn.Synthetic, "bound ") || strings.HasPrefix(fn.Synthetic, "thunk ") || strings.HasSuffix(fn.name, "Error") || diff --git a/vendor/golang.org/x/tools/go/ssa/source.go b/vendor/golang.org/x/tools/go/ssa/source.go index 9c900e3aa..6700305bd 100644 --- a/vendor/golang.org/x/tools/go/ssa/source.go +++ b/vendor/golang.org/x/tools/go/ssa/source.go @@ -172,16 +172,19 @@ func (f *Function) ValueForExpr(e ast.Expr) (value Value, isAddr bool) { // --- Lookup functions for source-level named entities (types.Objects) --- // Package returns the SSA Package corresponding to the specified -// type-checker package object. -// It returns nil if no such SSA package has been created. -func (prog *Program) Package(obj *types.Package) *Package { - return prog.packages[obj] +// type-checker package. It returns nil if no such Package was +// created by a prior call to prog.CreatePackage. +func (prog *Program) Package(pkg *types.Package) *Package { + return prog.packages[pkg] } -// packageLevelMember returns the package-level member corresponding to -// the specified named object, which may be a package-level const -// (*NamedConst), var (*Global) or func (*Function) of some package in -// prog. It returns nil if the object is not found. +// packageLevelMember returns the package-level member corresponding +// to the specified symbol, which may be a package-level const +// (*NamedConst), var (*Global) or func/method (*Function) of some +// package in prog. +// +// It returns nil if the object belongs to a package that has not been +// created by prog.CreatePackage. func (prog *Program) packageLevelMember(obj types.Object) Member { if pkg, ok := prog.packages[obj.Pkg()]; ok { return pkg.objects[obj] @@ -189,24 +192,16 @@ func (prog *Program) packageLevelMember(obj types.Object) Member { return nil } -// originFunc returns the package-level generic function that is the -// origin of obj. If returns nil if the generic function is not found. -func (prog *Program) originFunc(obj *types.Func) *Function { - return prog.declaredFunc(typeparams.OriginMethod(obj)) -} - -// FuncValue returns the concrete Function denoted by the source-level -// named function obj, or nil if obj denotes an interface method. -// -// TODO(adonovan): check the invariant that obj.Type() matches the -// result's Signature, both in the params/results and in the receiver. +// FuncValue returns the SSA function or (non-interface) method +// denoted by the specified func symbol. It returns nil id the symbol +// denotes an interface method, or belongs to a package that was not +// created by prog.CreatePackage. func (prog *Program) FuncValue(obj *types.Func) *Function { fn, _ := prog.packageLevelMember(obj).(*Function) return fn } -// ConstValue returns the SSA Value denoted by the source-level named -// constant obj. +// ConstValue returns the SSA constant denoted by the specified const symbol. func (prog *Program) ConstValue(obj *types.Const) *Const { // TODO(adonovan): opt: share (don't reallocate) // Consts for const objects and constant ast.Exprs. @@ -223,7 +218,7 @@ func (prog *Program) ConstValue(obj *types.Const) *Const { } // VarValue returns the SSA Value that corresponds to a specific -// identifier denoting the source-level named variable obj. +// identifier denoting the specified var symbol. // // VarValue returns nil if a local variable was not found, perhaps // because its package was not built, the debug information was not diff --git a/vendor/golang.org/x/tools/go/ssa/ssa.go b/vendor/golang.org/x/tools/go/ssa/ssa.go index bd42f2e0a..30bf4bc67 100644 --- a/vendor/golang.org/x/tools/go/ssa/ssa.go +++ b/vendor/golang.org/x/tools/go/ssa/ssa.go @@ -23,20 +23,25 @@ import ( type Program struct { Fset *token.FileSet // position information for the files of this Program imported map[string]*Package // all importable Packages, keyed by import path - packages map[*types.Package]*Package // all loaded Packages, keyed by object + packages map[*types.Package]*Package // all created Packages mode BuilderMode // set of mode bits for SSA construction MethodSets typeutil.MethodSetCache // cache of type-checker's method-sets - canon *canonizer // type canonicalization map - ctxt *typeparams.Context // cache for type checking instantiations + canon *canonizer // type canonicalization map + ctxt *types.Context // cache for type checking instantiations - methodsMu sync.Mutex // guards the following maps: - methodSets typeutil.Map // maps type to its concrete methodSet - runtimeTypes typeutil.Map // types for which rtypes are needed - bounds map[boundsKey]*Function // bounds for curried x.Method closures - thunks map[selectionKey]*Function // thunks for T.Method expressions - instances map[*Function]*instanceSet // instances of generic functions - parameterized tpWalker // determines whether a type reaches a type parameter. + methodsMu sync.Mutex + methodSets typeutil.Map // maps type to its concrete *methodSet + + parameterized tpWalker // memoization of whether a type refers to type parameters + + runtimeTypesMu sync.Mutex + runtimeTypes typeutil.Map // set of runtime types (from MakeInterface) + + // objectMethods is a memoization of objectMethod + // to avoid creation of duplicate methods from type information. + objectMethodsMu sync.Mutex + objectMethods map[*types.Func]*Function } // A Package is a single analyzed Go package containing Members for @@ -51,17 +56,19 @@ type Package struct { Prog *Program // the owning program Pkg *types.Package // the corresponding go/types.Package Members map[string]Member // all package members keyed by name (incl. init and init#%d) - objects map[types.Object]Member // mapping of package objects to members (incl. methods). Contains *NamedConst, *Global, *Function. + objects map[types.Object]Member // mapping of package objects to members (incl. methods). Contains *NamedConst, *Global, *Function (values but not types) init *Function // Func("init"); the package's init function debug bool // include full debug info in this package + syntax bool // package was loaded from syntax // The following fields are set transiently, then cleared // after building. - buildOnce sync.Once // ensures package building occurs once - ninit int32 // number of init functions - info *types.Info // package type information - files []*ast.File // package ASTs - created creator // members created as a result of building this package (includes declared functions, wrappers) + buildOnce sync.Once // ensures package building occurs once + ninit int32 // number of init functions + info *types.Info // package type information + files []*ast.File // package ASTs + created creator // members created as a result of building this package (includes declared functions, wrappers) + initVersion map[ast.Expr]string // goversion to use for each global var init expr } // A Member is a member of a Go package, implemented by *NamedConst, @@ -296,8 +303,8 @@ type Node interface { // // A generic function is a function or method that has uninstantiated type // parameters (TypeParams() != nil). Consider a hypothetical generic -// method, (*Map[K,V]).Get. It may be instantiated with all ground -// (non-parameterized) types as (*Map[string,int]).Get or with +// method, (*Map[K,V]).Get. It may be instantiated with all +// non-parameterized types as (*Map[string,int]).Get or with // parameterized types as (*Map[string,U]).Get, where U is a type parameter. // In both instantiations, Origin() refers to the instantiated generic // method, (*Map[K,V]).Get, TypeParams() refers to the parameters [K,V] of @@ -305,39 +312,45 @@ type Node interface { // respectively, and is nil in the generic method. type Function struct { name string - object types.Object // a declared *types.Func or one of its wrappers - method *selection // info about provenance of synthetic methods; thunk => non-nil + object *types.Func // symbol for declared function (nil for FuncLit or synthetic init) + method *selection // info about provenance of synthetic methods; thunk => non-nil Signature *types.Signature pos token.Pos - Synthetic string // provenance of synthetic function; "" for true source functions - syntax ast.Node // *ast.Func{Decl,Lit}; replaced with simple ast.Node after build, unless debug mode - parent *Function // enclosing function if anon; nil if global - Pkg *Package // enclosing package; nil for shared funcs (wrappers and error.Error) - Prog *Program // enclosing program + // source information + Synthetic string // provenance of synthetic function; "" for true source functions + syntax ast.Node // *ast.Func{Decl,Lit}, if from syntax (incl. generic instances) + info *types.Info // type annotations (iff syntax != nil) + goversion string // Go version of syntax (NB: init is special) + + build buildFunc // algorithm to build function body (nil => built) + parent *Function // enclosing function if anon; nil if global + Pkg *Package // enclosing package; nil for shared funcs (wrappers and error.Error) + Prog *Program // enclosing program + + // These fields are populated only when the function body is built: + Params []*Parameter // function parameters; for methods, includes receiver FreeVars []*FreeVar // free variables whose values must be supplied by closure - Locals []*Alloc // local variables of this function + Locals []*Alloc // frame-allocated variables of this function Blocks []*BasicBlock // basic blocks of the function; nil => external Recover *BasicBlock // optional; control transfers here after recovered panic AnonFuncs []*Function // anonymous functions directly beneath this one referrers []Instruction // referring instructions (iff Parent() != nil) - built bool // function has completed both CREATE and BUILD phase. anonIdx int32 // position of a nested function in parent's AnonFuncs. fn.Parent()!=nil => fn.Parent().AnonFunc[fn.anonIdx] == fn. - typeparams *typeparams.TypeParamList // type parameters of this function. typeparams.Len() > 0 => generic or instance of generic function - typeargs []types.Type // type arguments that instantiated typeparams. len(typeargs) > 0 => instance of generic function - topLevelOrigin *Function // the origin function if this is an instance of a source function. nil if Parent()!=nil. + typeparams *types.TypeParamList // type parameters of this function. typeparams.Len() > 0 => generic or instance of generic function + typeargs []types.Type // type arguments that instantiated typeparams. len(typeargs) > 0 => instance of generic function + topLevelOrigin *Function // the origin function if this is an instance of a source function. nil if Parent()!=nil. + generic *generic // instances of this function, if generic - // The following fields are set transiently during building, - // then cleared. + // The following fields are cleared after building. currentBlock *BasicBlock // where to emit code - objects map[types.Object]Value // addresses of local variables + vars map[*types.Var]Value // addresses of local variables namedResults []*Alloc // tuple of named results targets *targets // linked stack of branch targets - lblocks map[types.Object]*lblock // labelled blocks - info *types.Info // *types.Info to build from. nil for wrappers. - subst *subster // non-nil => expand generic body using this type substitution of ground types + lblocks map[*types.Label]*lblock // labelled blocks + subst *subster // type parameter substitutions (if non-nil) } // BasicBlock represents an SSA basic block. @@ -402,9 +415,8 @@ type FreeVar struct { // A Parameter represents an input parameter of a function. type Parameter struct { name string - object types.Object // a *types.Var; nil for non-source locals + object *types.Var // non-nil typ types.Type - pos token.Pos parent *Function referrers []Instruction } @@ -482,15 +494,12 @@ type Builtin struct { // type of the allocated variable is actually // Type().Underlying().(*types.Pointer).Elem(). // -// If Heap is false, Alloc allocates space in the function's -// activation record (frame); we refer to an Alloc(Heap=false) as a -// "local" alloc. Each local Alloc returns the same address each time -// it is executed within the same activation; the space is -// re-initialized to zero. +// If Heap is false, Alloc zero-initializes the same local variable in +// the call frame and returns its address; in this case the Alloc must +// be present in Function.Locals. We call this a "local" alloc. // -// If Heap is true, Alloc allocates space in the heap; we -// refer to an Alloc(Heap=true) as a "new" alloc. Each new Alloc -// returns a different address each time it is executed. +// If Heap is true, Alloc allocates a new zero-initialized variable +// each time the instruction is executed. We call this a "new" alloc. // // When Alloc is applied to a channel, map or slice type, it returns // the address of an uninitialized (nil) reference of that kind; store @@ -681,8 +690,8 @@ type Convert struct { type MultiConvert struct { register X Value - from []*typeparams.Term - to []*typeparams.Term + from []*types.Term + to []*types.Term } // ChangeInterface constructs a value of one interface type from a @@ -1068,11 +1077,12 @@ type Next struct { // Type() reflects the actual type of the result, possibly a // 2-types.Tuple; AssertedType is the asserted type. // -// Pos() returns the ast.CallExpr.Lparen if the instruction arose from -// an explicit T(e) conversion; the ast.TypeAssertExpr.Lparen if the -// instruction arose from an explicit e.(T) operation; or the -// ast.CaseClause.Case if the instruction arose from a case of a -// type-switch statement. +// Depending on the TypeAssert's purpose, Pos may return: +// - the ast.CallExpr.Lparen of an explicit T(e) conversion; +// - the ast.TypeAssertExpr.Lparen of an explicit e.(T) operation; +// - the ast.CaseClause.Case of a case of a type-switch statement; +// - the Ident(m).NamePos of an interface method value i.m +// (for which TypeAssert may be used to effect the nil check). // // Example printed form: // @@ -1390,7 +1400,7 @@ type anInstruction struct { // represents a dynamically dispatched call to an interface method. // In this mode, Value is the interface value and Method is the // interface's abstract method. The interface value may be a type -// parameter. Note: an abstract method may be shared by multiple +// parameter. Note: an interface method may be shared by multiple // interfaces due to embedding; Value.Type() provides the specific // interface used for this call. // @@ -1408,7 +1418,7 @@ type anInstruction struct { // the last element of Args is a slice. type CallCommon struct { Value Value // receiver (invoke mode) or func value (call mode) - Method *types.Func // abstract method (invoke mode) + Method *types.Func // interface method (invoke mode) Args []Value // actual parameters (in static method call, includes receiver) pos token.Pos // position of CallExpr.Lparen, iff explicit in source } @@ -1507,14 +1517,19 @@ func (v *Global) String() string { return v.RelString(nil) func (v *Global) Package() *Package { return v.Pkg } func (v *Global) RelString(from *types.Package) string { return relString(v, from) } -func (v *Function) Name() string { return v.name } -func (v *Function) Type() types.Type { return v.Signature } -func (v *Function) Pos() token.Pos { return v.pos } -func (v *Function) Token() token.Token { return token.FUNC } -func (v *Function) Object() types.Object { return v.object } -func (v *Function) String() string { return v.RelString(nil) } -func (v *Function) Package() *Package { return v.Pkg } -func (v *Function) Parent() *Function { return v.parent } +func (v *Function) Name() string { return v.name } +func (v *Function) Type() types.Type { return v.Signature } +func (v *Function) Pos() token.Pos { return v.pos } +func (v *Function) Token() token.Token { return token.FUNC } +func (v *Function) Object() types.Object { + if v.object != nil { + return types.Object(v.object) + } + return nil +} +func (v *Function) String() string { return v.RelString(nil) } +func (v *Function) Package() *Package { return v.Pkg } +func (v *Function) Parent() *Function { return v.parent } func (v *Function) Referrers() *[]Instruction { if v.parent != nil { return &v.referrers @@ -1524,10 +1539,7 @@ func (v *Function) Referrers() *[]Instruction { // TypeParams are the function's type parameters if generic or the // type parameters that were instantiated if fn is an instantiation. -// -// TODO(taking): declare result type as *types.TypeParamList -// after we drop support for go1.17. -func (fn *Function) TypeParams() *typeparams.TypeParamList { +func (fn *Function) TypeParams() *types.TypeParamList { return fn.typeparams } @@ -1562,7 +1574,7 @@ func (v *Parameter) Type() types.Type { return v.typ } func (v *Parameter) Name() string { return v.name } func (v *Parameter) Object() types.Object { return v.object } func (v *Parameter) Referrers() *[]Instruction { return &v.referrers } -func (v *Parameter) Pos() token.Pos { return v.pos } +func (v *Parameter) Pos() token.Pos { return v.object.Pos() } func (v *Parameter) Parent() *Function { return v.parent } func (v *Alloc) Type() types.Type { return v.typ } diff --git a/vendor/golang.org/x/tools/go/ssa/ssautil/load.go b/vendor/golang.org/x/tools/go/ssa/ssautil/load.go index 96d69a20a..3daa67a07 100644 --- a/vendor/golang.org/x/tools/go/ssa/ssautil/load.go +++ b/vendor/golang.org/x/tools/go/ssa/ssautil/load.go @@ -14,14 +14,14 @@ import ( "golang.org/x/tools/go/loader" "golang.org/x/tools/go/packages" "golang.org/x/tools/go/ssa" - "golang.org/x/tools/internal/typeparams" + "golang.org/x/tools/internal/versions" ) // Packages creates an SSA program for a set of packages. // // The packages must have been loaded from source syntax using the -// golang.org/x/tools/go/packages.Load function in LoadSyntax or -// LoadAllSyntax mode. +// [packages.Load] function in [packages.LoadSyntax] or +// [packages.LoadAllSyntax] mode. // // Packages creates an SSA package for each well-typed package in the // initial list, plus all their dependencies. The resulting list of @@ -29,12 +29,30 @@ import ( // a nil if SSA code could not be constructed for the corresponding initial // package due to type errors. // -// Code for bodies of functions is not built until Build is called on -// the resulting Program. SSA code is constructed only for the initial -// packages with well-typed syntax trees. +// Code for bodies of functions is not built until [Program.Build] is +// called on the resulting Program. SSA code is constructed only for +// the initial packages with well-typed syntax trees. // // The mode parameter controls diagnostics and checking during SSA construction. func Packages(initial []*packages.Package, mode ssa.BuilderMode) (*ssa.Program, []*ssa.Package) { + // TODO(adonovan): opt: this calls CreatePackage far more than + // necessary: for all dependencies, not just the (non-initial) + // direct dependencies of the initial packages. + // + // But can it reasonably be changed without breaking the + // spirit and/or letter of the law above? Clients may notice + // if we call CreatePackage less, as methods like + // Program.FuncValue will return nil. Or must we provide a new + // function (and perhaps deprecate this one)? Is it worth it? + // + // Tim King makes the interesting point that it would be + // possible to entirely alleviate the client from the burden + // of calling CreatePackage for non-syntax packages, if we + // were to treat vars and funcs lazily in the same way we now + // treat methods. (In essence, try to move away from the + // notion of ssa.Packages, and make the Program answer + // all reasonable questions about any types.Object.) + return doPackages(initial, mode, false) } @@ -42,7 +60,7 @@ func Packages(initial []*packages.Package, mode ssa.BuilderMode) (*ssa.Program, // their dependencies. // // The packages must have been loaded from source syntax using the -// golang.org/x/tools/go/packages.Load function in LoadAllSyntax mode. +// [packages.Load] function in [packages.LoadAllSyntax] mode. // // AllPackages creates an SSA package for each well-typed package in the // initial list, plus all their dependencies. The resulting list of @@ -102,7 +120,7 @@ func doPackages(initial []*packages.Package, mode ssa.BuilderMode, deps bool) (* // // The mode parameter controls diagnostics and checking during SSA construction. // -// Deprecated: Use golang.org/x/tools/go/packages and the Packages +// Deprecated: Use [golang.org/x/tools/go/packages] and the [Packages] // function instead; see ssa.Example_loadPackages. func CreateProgram(lprog *loader.Program, mode ssa.BuilderMode) *ssa.Program { prog := ssa.NewProgram(lprog.Fset, mode) @@ -116,16 +134,17 @@ func CreateProgram(lprog *loader.Program, mode ssa.BuilderMode) *ssa.Program { return prog } -// BuildPackage builds an SSA program with IR for a single package. +// BuildPackage builds an SSA program with SSA intermediate +// representation (IR) for all functions of a single package. // -// It populates pkg by type-checking the specified file ASTs. All +// It populates pkg by type-checking the specified file syntax trees. All // dependencies are loaded using the importer specified by tc, which // typically loads compiler export data; SSA code cannot be built for -// those packages. BuildPackage then constructs an ssa.Program with all +// those packages. BuildPackage then constructs an [ssa.Program] with all // dependency packages created, and builds and returns the SSA package // corresponding to pkg. // -// The caller must have set pkg.Path() to the import path. +// The caller must have set pkg.Path to the import path. // // The operation fails if there were any type-checking or import errors. // @@ -143,10 +162,11 @@ func BuildPackage(tc *types.Config, fset *token.FileSet, pkg *types.Package, fil Defs: make(map[*ast.Ident]types.Object), Uses: make(map[*ast.Ident]types.Object), Implicits: make(map[ast.Node]types.Object), + Instances: make(map[*ast.Ident]types.Instance), Scopes: make(map[ast.Node]*types.Scope), Selections: make(map[*ast.SelectorExpr]*types.Selection), } - typeparams.InitInstanceInfo(info) + versions.InitFileVersions(info) if err := types.NewChecker(tc, fset, pkg, info).Files(files); err != nil { return nil, nil, err } @@ -168,6 +188,25 @@ func BuildPackage(tc *types.Config, fset *token.FileSet, pkg *types.Package, fil } createAll(pkg.Imports()) + // TODO(adonovan): we could replace createAll with just: + // + // // Create SSA packages for all imports. + // for _, p := range pkg.Imports() { + // prog.CreatePackage(p, nil, nil, true) + // } + // + // (with minor changes to changes to ../builder_test.go as + // shown in CL 511715 PS 10.) But this would strictly violate + // the letter of the doc comment above, which says "all + // dependencies created". + // + // Tim makes the good point with some extra work we could + // remove the need for any CreatePackage calls except the + // ones with syntax (i.e. primary packages). Of course + // You wouldn't have ssa.Packages and Members for as + // many things but no-one really uses that anyway. + // I wish I had done this from the outset. + // Create and build the primary package. ssapkg := prog.CreatePackage(pkg, files, info, false) ssapkg.Build() diff --git a/vendor/golang.org/x/tools/go/ssa/ssautil/visit.go b/vendor/golang.org/x/tools/go/ssa/ssautil/visit.go index 5f27050b0..b4feb42cb 100644 --- a/vendor/golang.org/x/tools/go/ssa/ssautil/visit.go +++ b/vendor/golang.org/x/tools/go/ssa/ssautil/visit.go @@ -4,7 +4,14 @@ package ssautil // import "golang.org/x/tools/go/ssa/ssautil" -import "golang.org/x/tools/go/ssa" +import ( + "go/ast" + "go/types" + + "golang.org/x/tools/go/ssa" + + _ "unsafe" // for linkname hack +) // This file defines utilities for visiting the SSA representation of // a Program. @@ -18,50 +25,113 @@ import "golang.org/x/tools/go/ssa" // synthetic wrappers. // // Precondition: all packages are built. +// +// TODO(adonovan): this function is underspecified. It doesn't +// actually work like a linker, which computes reachability from main +// using something like go/callgraph/cha (without materializing the +// call graph). In fact, it treats all public functions and all +// methods of public non-parameterized types as roots, even though +// they may be unreachable--but only in packages created from syntax. +// +// I think we should deprecate AllFunctions function in favor of two +// clearly defined ones: +// +// 1. The first would efficiently compute CHA reachability from a set +// of main packages, making it suitable for a whole-program +// analysis context with InstantiateGenerics, in conjunction with +// Program.Build. +// +// 2. The second would return only the set of functions corresponding +// to source Func{Decl,Lit} syntax, like SrcFunctions in +// go/analysis/passes/buildssa; this is suitable for +// package-at-a-time (or handful of packages) context. +// ssa.Package could easily expose it as a field. +// +// We could add them unexported for now and use them via the linkname hack. func AllFunctions(prog *ssa.Program) map[*ssa.Function]bool { - visit := visitor{ - prog: prog, - seen: make(map[*ssa.Function]bool), + seen := make(map[*ssa.Function]bool) + + var function func(fn *ssa.Function) + function = func(fn *ssa.Function) { + if !seen[fn] { + seen[fn] = true + var buf [10]*ssa.Value // avoid alloc in common case + for _, b := range fn.Blocks { + for _, instr := range b.Instrs { + for _, op := range instr.Operands(buf[:0]) { + if fn, ok := (*op).(*ssa.Function); ok { + function(fn) + } + } + } + } + } } - visit.program() - return visit.seen -} -type visitor struct { - prog *ssa.Program - seen map[*ssa.Function]bool -} + // TODO(adonovan): opt: provide a way to share a builder + // across a sequence of MethodValue calls. -func (visit *visitor) program() { - for _, pkg := range visit.prog.AllPackages() { - for _, mem := range pkg.Members { - if fn, ok := mem.(*ssa.Function); ok { - visit.function(fn) + methodsOf := func(T types.Type) { + if !types.IsInterface(T) { + mset := prog.MethodSets.MethodSet(T) + for i := 0; i < mset.Len(); i++ { + function(prog.MethodValue(mset.At(i))) } } } - for _, T := range visit.prog.RuntimeTypes() { - mset := visit.prog.MethodSets.MethodSet(T) - for i, n := 0, mset.Len(); i < n; i++ { - visit.function(visit.prog.MethodValue(mset.At(i))) + + // Historically, Program.RuntimeTypes used to include the type + // of any exported member of a package loaded from syntax that + // has a non-parameterized type, plus all types + // reachable from that type using reflection, even though + // these runtime types may not be required for them. + // + // Rather than break existing programs that rely on + // AllFunctions visiting extra methods that are unreferenced + // by IR and unreachable via reflection, we moved the logic + // here, unprincipled though it is. + // (See doc comment for better ideas.) + // + // Nonetheless, after the move, we no longer visit every + // method of any type recursively reachable from T, only the + // methods of T and *T themselves, and we only apply this to + // named types T, and not to the type of every exported + // package member. + exportedTypeHack := func(t *ssa.Type) { + if isSyntactic(t.Package()) && + ast.IsExported(t.Name()) && + !types.IsInterface(t.Type()) { + // Consider only named types. + // (Ignore aliases and unsafe.Pointer.) + if named, ok := t.Type().(*types.Named); ok { + if named.TypeParams() == nil { + methodsOf(named) // T + methodsOf(types.NewPointer(named)) // *T + } + } } } -} -func (visit *visitor) function(fn *ssa.Function) { - if !visit.seen[fn] { - visit.seen[fn] = true - var buf [10]*ssa.Value // avoid alloc in common case - for _, b := range fn.Blocks { - for _, instr := range b.Instrs { - for _, op := range instr.Operands(buf[:0]) { - if fn, ok := (*op).(*ssa.Function); ok { - visit.function(fn) - } - } + for _, pkg := range prog.AllPackages() { + for _, mem := range pkg.Members { + switch mem := mem.(type) { + case *ssa.Function: + // Visit all package-level declared functions. + function(mem) + + case *ssa.Type: + exportedTypeHack(mem) } } } + + // Visit all methods of types for which runtime types were + // materialized, as they are reachable through reflection. + for _, T := range prog.RuntimeTypes() { + methodsOf(T) + } + + return seen } // MainPackages returns the subset of the specified packages @@ -76,3 +146,12 @@ func MainPackages(pkgs []*ssa.Package) []*ssa.Package { } return mains } + +// TODO(adonovan): propose a principled API for this. One possibility +// is a new field, Package.SrcFunctions []*Function, which would +// contain the list of SrcFunctions described in point 2 of the +// AllFunctions doc comment, or nil if the package is not from syntax. +// But perhaps overloading nil vs empty slice is too subtle. +// +//go:linkname isSyntactic golang.org/x/tools/go/ssa.isSyntactic +func isSyntactic(pkg *ssa.Package) bool diff --git a/vendor/golang.org/x/tools/go/ssa/subst.go b/vendor/golang.org/x/tools/go/ssa/subst.go index 23d19ae73..a9a6d41e8 100644 --- a/vendor/golang.org/x/tools/go/ssa/subst.go +++ b/vendor/golang.org/x/tools/go/ssa/subst.go @@ -6,8 +6,6 @@ package ssa import ( "go/types" - - "golang.org/x/tools/internal/typeparams" ) // Type substituter for a fixed set of replacement types. @@ -18,11 +16,11 @@ import ( // // Not concurrency-safe. type subster struct { - replacements map[*typeparams.TypeParam]types.Type // values should contain no type params - cache map[types.Type]types.Type // cache of subst results - ctxt *typeparams.Context // cache for instantiation - scope *types.Scope // *types.Named declared within this scope can be substituted (optional) - debug bool // perform extra debugging checks + replacements map[*types.TypeParam]types.Type // values should contain no type params + cache map[types.Type]types.Type // cache of subst results + ctxt *types.Context // cache for instantiation + scope *types.Scope // *types.Named declared within this scope can be substituted (optional) + debug bool // perform extra debugging checks // TODO(taking): consider adding Pos // TODO(zpavlinovic): replacements can contain type params // when generating instances inside of a generic function body. @@ -31,11 +29,11 @@ type subster struct { // Returns a subster that replaces tparams[i] with targs[i]. Uses ctxt as a cache. // targs should not contain any types in tparams. // scope is the (optional) lexical block of the generic function for which we are substituting. -func makeSubster(ctxt *typeparams.Context, scope *types.Scope, tparams *typeparams.TypeParamList, targs []types.Type, debug bool) *subster { +func makeSubster(ctxt *types.Context, scope *types.Scope, tparams *types.TypeParamList, targs []types.Type, debug bool) *subster { assert(tparams.Len() == len(targs), "makeSubster argument count must match") subst := &subster{ - replacements: make(map[*typeparams.TypeParam]types.Type, tparams.Len()), + replacements: make(map[*types.TypeParam]types.Type, tparams.Len()), cache: make(map[types.Type]types.Type), ctxt: ctxt, scope: scope, @@ -82,7 +80,7 @@ func (subst *subster) typ(t types.Type) (res types.Type) { // fall through if result r will be identical to t, types.Identical(r, t). switch t := t.(type) { - case *typeparams.TypeParam: + case *types.TypeParam: r := subst.replacements[t] assert(r != nil, "type param without replacement encountered") return r @@ -131,7 +129,7 @@ func (subst *subster) typ(t types.Type) (res types.Type) { case *types.Signature: return subst.signature(t) - case *typeparams.Union: + case *types.Union: return subst.union(t) case *types.Interface: @@ -220,25 +218,25 @@ func (subst *subster) var_(v *types.Var) *types.Var { return v } -func (subst *subster) union(u *typeparams.Union) *typeparams.Union { - var out []*typeparams.Term // nil => no updates +func (subst *subster) union(u *types.Union) *types.Union { + var out []*types.Term // nil => no updates for i, n := 0, u.Len(); i < n; i++ { t := u.Term(i) r := subst.typ(t.Type()) if r != t.Type() && out == nil { - out = make([]*typeparams.Term, n) + out = make([]*types.Term, n) for j := 0; j < i; j++ { out[j] = u.Term(j) } } if out != nil { - out[i] = typeparams.NewTerm(t.Tilde(), r) + out[i] = types.NewTerm(t.Tilde(), r) } } if out != nil { - return typeparams.NewUnion(out) + return types.NewUnion(out) } return u } @@ -310,7 +308,7 @@ func (subst *subster) named(t *types.Named) types.Type { // (2) locally scoped type, // (3) generic (type parameters but no type arguments), or // (4) instantiated (type parameters and type arguments). - tparams := typeparams.ForNamed(t) + tparams := t.TypeParams() if tparams.Len() == 0 { if subst.scope != nil && !subst.scope.Contains(t.Obj().Pos()) { // Outside the current function scope? @@ -344,7 +342,7 @@ func (subst *subster) named(t *types.Named) types.Type { n.SetUnderlying(subst.typ(t.Underlying())) return n } - targs := typeparams.NamedTypeArgs(t) + targs := t.TypeArgs() // insts are arguments to instantiate using. insts := make([]types.Type, tparams.Len()) @@ -367,13 +365,13 @@ func (subst *subster) named(t *types.Named) types.Type { inst := subst.typ(targs.At(i)) // TODO(generic): Check with rfindley for mutual recursion insts[i] = inst } - r, err := typeparams.Instantiate(subst.ctxt, typeparams.NamedTypeOrigin(t), insts, false) + r, err := types.Instantiate(subst.ctxt, t.Origin(), insts, false) assert(err == nil, "failed to Instantiate Named type") return r } func (subst *subster) signature(t *types.Signature) types.Type { - tparams := typeparams.ForSignature(t) + tparams := t.TypeParams() // We are choosing not to support tparams.Len() > 0 until a need has been observed in practice. // @@ -398,7 +396,7 @@ func (subst *subster) signature(t *types.Signature) types.Type { params := subst.tuple(t.Params()) results := subst.tuple(t.Results()) if recv != t.Recv() || params != t.Params() || results != t.Results() { - return typeparams.NewSignatureType(recv, nil, nil, params, results, t.Variadic()) + return types.NewSignatureType(recv, nil, nil, params, results, t.Variadic()) } return t } @@ -422,7 +420,7 @@ func reaches(t types.Type, c map[types.Type]bool) (res bool) { }() switch t := t.(type) { - case *typeparams.TypeParam, *types.Basic: + case *types.TypeParam, *types.Basic: return false case *types.Array: return reaches(t.Elem(), c) @@ -451,7 +449,7 @@ func reaches(t types.Type, c map[types.Type]bool) (res bool) { return true } return reaches(t.Params(), c) || reaches(t.Results(), c) - case *typeparams.Union: + case *types.Union: for i := 0; i < t.Len(); i++ { if reaches(t.Term(i).Type(), c) { return true diff --git a/vendor/golang.org/x/tools/go/ssa/util.go b/vendor/golang.org/x/tools/go/ssa/util.go index 68cc971b3..6e9f1282b 100644 --- a/vendor/golang.org/x/tools/go/ssa/util.go +++ b/vendor/golang.org/x/tools/go/ssa/util.go @@ -180,24 +180,6 @@ func makeLen(T types.Type) *Builtin { } } -// nonbasicTypes returns a list containing all of the types T in ts that are non-basic. -func nonbasicTypes(ts []types.Type) []types.Type { - if len(ts) == 0 { - return nil - } - added := make(map[types.Type]bool) // additionally filter duplicates - var filtered []types.Type - for _, T := range ts { - if !isBasic(T) { - if !added[T] { - added[T] = true - filtered = append(filtered, T) - } - } - } - return filtered -} - // receiverTypeArgs returns the type arguments to a function's receiver. // Returns an empty list if obj does not have a receiver or its receiver does not have type arguments. func receiverTypeArgs(obj *types.Func) []types.Type { @@ -210,7 +192,7 @@ func receiverTypeArgs(obj *types.Func) []types.Type { if !ok { return nil } - ts := typeparams.NamedTypeArgs(named) + ts := named.TypeArgs() if ts.Len() == 0 { return nil } @@ -229,7 +211,7 @@ func recvAsFirstArg(sig *types.Signature) *types.Signature { for i := 0; i < sig.Params().Len(); i++ { params = append(params, sig.Params().At(i)) } - return typeparams.NewSignatureType(nil, nil, nil, types.NewTuple(params...), sig.Results(), sig.Variadic()) + return types.NewSignatureType(nil, nil, nil, types.NewTuple(params...), sig.Results(), sig.Variadic()) } // instance returns whether an expression is a simple or qualified identifier @@ -246,13 +228,13 @@ func instance(info *types.Info, expr ast.Expr) bool { default: return false } - _, ok := typeparams.GetInstances(info)[id] + _, ok := info.Instances[id] return ok } // instanceArgs returns the Instance[id].TypeArgs as a slice. func instanceArgs(info *types.Info, id *ast.Ident) []types.Type { - targList := typeparams.GetInstances(info)[id].TypeArgs + targList := info.Instances[id].TypeArgs if targList == nil { return nil } @@ -370,13 +352,13 @@ func (m *typeListMap) hash(ts []types.Type) uint32 { } // instantiateMethod instantiates m with targs and returns a canonical representative for this method. -func (canon *canonizer) instantiateMethod(m *types.Func, targs []types.Type, ctxt *typeparams.Context) *types.Func { +func (canon *canonizer) instantiateMethod(m *types.Func, targs []types.Type, ctxt *types.Context) *types.Func { recv := recvType(m) if p, ok := recv.(*types.Pointer); ok { recv = p.Elem() } named := recv.(*types.Named) - inst, err := typeparams.Instantiate(ctxt, typeparams.NamedTypeOrigin(named), targs, false) + inst, err := types.Instantiate(ctxt, named.Origin(), targs, false) if err != nil { panic(err) } @@ -384,3 +366,16 @@ func (canon *canonizer) instantiateMethod(m *types.Func, targs []types.Type, ctx obj, _, _ := types.LookupFieldOrMethod(rep, true, m.Pkg(), m.Name()) return obj.(*types.Func) } + +// Exposed to ssautil using the linkname hack. +func isSyntactic(pkg *Package) bool { return pkg.syntax } + +// mapValues returns a new unordered array of map values. +func mapValues[K comparable, V any](m map[K]V) []V { + vals := make([]V, 0, len(m)) + for _, fn := range m { + vals = append(vals, fn) + } + return vals + +} diff --git a/vendor/golang.org/x/tools/go/ssa/wrappers.go b/vendor/golang.org/x/tools/go/ssa/wrappers.go index 123ea6858..7c7ee4099 100644 --- a/vendor/golang.org/x/tools/go/ssa/wrappers.go +++ b/vendor/golang.org/x/tools/go/ssa/wrappers.go @@ -28,7 +28,7 @@ import ( // -- wrappers ----------------------------------------------------------- -// makeWrapper returns a synthetic method that delegates to the +// createWrapper returns a synthetic method that delegates to the // declared method denoted by meth.Obj(), first performing any // necessary pointer indirections or field selections implied by meth. // @@ -40,21 +40,17 @@ import ( // - optional implicit field selections // - meth.Obj() may denote a concrete or an interface method // - the result may be a thunk or a wrapper. -// -// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu) -func makeWrapper(prog *Program, sel *selection, cr *creator) *Function { +func createWrapper(prog *Program, sel *selection, cr *creator) *Function { obj := sel.obj.(*types.Func) // the declared function sig := sel.typ.(*types.Signature) // type of this wrapper var recv *types.Var // wrapper's receiver or thunk's params[0] name := obj.Name() var description string - var start int // first regular param if sel.kind == types.MethodExpr { name += "$thunk" description = "thunk" recv = sig.Params().At(0) - start = 1 } else { description = "wrapper" recv = sig.Recv() @@ -62,8 +58,9 @@ func makeWrapper(prog *Program, sel *selection, cr *creator) *Function { description = fmt.Sprintf("%s for %s", description, sel.obj) if prog.mode&LogSource != 0 { - defer logStack("make %s to (%s)", description, recv.Type())() + defer logStack("create %s to (%s)", description, recv.Type())() } + /* method wrapper */ fn := &Function{ name: name, method: sel, @@ -72,35 +69,53 @@ func makeWrapper(prog *Program, sel *selection, cr *creator) *Function { Synthetic: description, Prog: prog, pos: obj.Pos(), - info: nil, // info is not set on wrappers. + // wrappers have no syntax + build: (*builder).buildWrapper, + syntax: nil, + info: nil, + goversion: "", } cr.Add(fn) + return fn +} + +// buildWrapper builds fn.Body for a method wrapper. +func (b *builder) buildWrapper(fn *Function) { + var recv *types.Var // wrapper's receiver or thunk's params[0] + var start int // first regular param + if fn.method.kind == types.MethodExpr { + recv = fn.Signature.Params().At(0) + start = 1 + } else { + recv = fn.Signature.Recv() + } + fn.startBody() fn.addSpilledParam(recv) createParams(fn, start) - indices := sel.index + indices := fn.method.index var v Value = fn.Locals[0] // spilled receiver - srdt, ptrRecv := deptr(sel.recv) + srdt, ptrRecv := deptr(fn.method.recv) if ptrRecv { v = emitLoad(fn, v) // For simple indirection wrappers, perform an informative nil-check: // "value method (T).f called using nil *T pointer" - _, ptrObj := deptr(recvType(obj)) + _, ptrObj := deptr(recvType(fn.object)) if len(indices) == 1 && !ptrObj { var c Call c.Call.Value = &Builtin{ name: "ssa:wrapnilchk", sig: types.NewSignature(nil, - types.NewTuple(anonVar(sel.recv), anonVar(tString), anonVar(tString)), - types.NewTuple(anonVar(sel.recv)), false), + types.NewTuple(anonVar(fn.method.recv), anonVar(tString), anonVar(tString)), + types.NewTuple(anonVar(fn.method.recv)), false), } c.Call.Args = []Value{ v, stringConst(srdt.String()), - stringConst(sel.obj.Name()), + stringConst(fn.method.obj.Name()), } c.setType(v.Type()) v = fn.emit(&c) @@ -122,18 +137,14 @@ func makeWrapper(prog *Program, sel *selection, cr *creator) *Function { // address of implicit C field. var c Call - if r := recvType(obj); !types.IsInterface(r) { // concrete method + if r := recvType(fn.object); !types.IsInterface(r) { // concrete method if _, ptrObj := deptr(r); !ptrObj { v = emitLoad(fn, v) } - callee := prog.originFunc(obj) - if callee.typeparams.Len() > 0 { - callee = prog.lookupOrCreateInstance(callee, receiverTypeArgs(obj), cr) - } - c.Call.Value = callee + c.Call.Value = fn.Prog.objectMethod(fn.object, b.created) c.Call.Args = append(c.Call.Args, v) } else { - c.Call.Method = obj + c.Call.Method = fn.object c.Call.Value = emitLoad(fn, v) // interface (possibly a typeparam) } for _, arg := range fn.Params[1:] { @@ -141,8 +152,6 @@ func makeWrapper(prog *Program, sel *selection, cr *creator) *Function { } emitTailCall(fn, &c) fn.finishBody() - fn.done() - return fn } // createParams creates parameters for wrapper method fn based on its @@ -151,13 +160,13 @@ func makeWrapper(prog *Program, sel *selection, cr *creator) *Function { func createParams(fn *Function, start int) { tparams := fn.Signature.Params() for i, n := start, tparams.Len(); i < n; i++ { - fn.addParamObj(tparams.At(i)) + fn.addParamVar(tparams.At(i)) } } // -- bounds ----------------------------------------------------------- -// makeBound returns a bound method wrapper (or "bound"), a synthetic +// createBound returns a bound method wrapper (or "bound"), a synthetic // function that delegates to a concrete or interface method denoted // by obj. The resulting function has no receiver, but has one free // variable which will be used as the method's receiver in the @@ -176,66 +185,57 @@ func createParams(fn *Function, start int) { // // f := func() { return t.meth() } // -// Unlike makeWrapper, makeBound need perform no indirection or field +// Unlike createWrapper, createBound need perform no indirection or field // selections because that can be done before the closure is // constructed. -// -// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu) -func makeBound(prog *Program, obj *types.Func, cr *creator) *Function { - targs := receiverTypeArgs(obj) - key := boundsKey{obj, prog.canon.List(targs)} - - prog.methodsMu.Lock() - defer prog.methodsMu.Unlock() - fn, ok := prog.bounds[key] - if !ok { - description := fmt.Sprintf("bound method wrapper for %s", obj) - if prog.mode&LogSource != 0 { - defer logStack("%s", description)() - } - fn = &Function{ - name: obj.Name() + "$bound", - object: obj, - Signature: changeRecv(obj.Type().(*types.Signature), nil), // drop receiver - Synthetic: description, - Prog: prog, - pos: obj.Pos(), - info: nil, // info is not set on wrappers. - } - cr.Add(fn) - - fv := &FreeVar{name: "recv", typ: recvType(obj), parent: fn} - fn.FreeVars = []*FreeVar{fv} - fn.startBody() - createParams(fn, 0) - var c Call - - if !types.IsInterface(recvType(obj)) { // concrete - callee := prog.originFunc(obj) - if callee.typeparams.Len() > 0 { - callee = prog.lookupOrCreateInstance(callee, targs, cr) - } - c.Call.Value = callee - c.Call.Args = []Value{fv} - } else { - c.Call.Method = obj - c.Call.Value = fv // interface (possibly a typeparam) - } - for _, arg := range fn.Params { - c.Call.Args = append(c.Call.Args, arg) - } - emitTailCall(fn, &c) - fn.finishBody() - fn.done() - - prog.bounds[key] = fn +func createBound(prog *Program, obj *types.Func, cr *creator) *Function { + description := fmt.Sprintf("bound method wrapper for %s", obj) + if prog.mode&LogSource != 0 { + defer logStack("%s", description)() + } + /* bound method wrapper */ + fn := &Function{ + name: obj.Name() + "$bound", + object: obj, + Signature: changeRecv(obj.Type().(*types.Signature), nil), // drop receiver + Synthetic: description, + Prog: prog, + pos: obj.Pos(), + // wrappers have no syntax + build: (*builder).buildBound, + syntax: nil, + info: nil, + goversion: "", } + fn.FreeVars = []*FreeVar{{name: "recv", typ: recvType(obj), parent: fn}} // (cyclic) + cr.Add(fn) return fn } +// buildBound builds fn.Body for a bound method closure. +func (b *builder) buildBound(fn *Function) { + fn.startBody() + createParams(fn, 0) + var c Call + + recv := fn.FreeVars[0] + if !types.IsInterface(recvType(fn.object)) { // concrete + c.Call.Value = fn.Prog.objectMethod(fn.object, b.created) + c.Call.Args = []Value{recv} + } else { + c.Call.Method = fn.object + c.Call.Value = recv // interface (possibly a typeparam) + } + for _, arg := range fn.Params { + c.Call.Args = append(c.Call.Args, arg) + } + emitTailCall(fn, &c) + fn.finishBody() +} + // -- thunks ----------------------------------------------------------- -// makeThunk returns a thunk, a synthetic function that delegates to a +// createThunk returns a thunk, a synthetic function that delegates to a // concrete or interface method denoted by sel.obj. The resulting // function has no receiver, but has an additional (first) regular // parameter. @@ -251,38 +251,16 @@ func makeBound(prog *Program, obj *types.Func, cr *creator) *Function { // f is a synthetic wrapper defined as if by: // // f := func(t T) { return t.meth() } -// -// TODO(adonovan): opt: currently the stub is created even when used -// directly in a function call: C.f(i, 0). This is less efficient -// than inlining the stub. -// -// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu) -func makeThunk(prog *Program, sel *selection, cr *creator) *Function { +func createThunk(prog *Program, sel *selection, cr *creator) *Function { if sel.kind != types.MethodExpr { panic(sel) } - // Canonicalize sel.recv to avoid constructing duplicate thunks. - canonRecv := prog.canon.Type(sel.recv) - key := selectionKey{ - kind: sel.kind, - recv: canonRecv, - obj: sel.obj, - index: fmt.Sprint(sel.index), - indirect: sel.indirect, + fn := createWrapper(prog, sel, cr) + if fn.Signature.Recv() != nil { + panic(fn) // unexpected receiver } - prog.methodsMu.Lock() - defer prog.methodsMu.Unlock() - - fn, ok := prog.thunks[key] - if !ok { - fn = makeWrapper(prog, sel, cr) - if fn.Signature.Recv() != nil { - panic(fn) // unexpected receiver - } - prog.thunks[key] = fn - } return fn } @@ -290,21 +268,6 @@ func changeRecv(s *types.Signature, recv *types.Var) *types.Signature { return types.NewSignature(recv, s.Params(), s.Results(), s.Variadic()) } -// selectionKey is like types.Selection but a usable map key. -type selectionKey struct { - kind types.SelectionKind - recv types.Type // canonicalized via Program.canon - obj types.Object - index string - indirect bool -} - -// boundsKey is a unique for the object and a type instantiation. -type boundsKey struct { - obj types.Object // t.meth - inst *typeList // canonical type instantiation list. -} - // A local version of *types.Selection. // Needed for some additional control, such as creating a MethodExpr for an instantiation. type selection struct { @@ -329,16 +292,16 @@ func toSelection(sel *types.Selection) *selection { // -- instantiations -------------------------------------------------- -// buildInstantiationWrapper creates a body for an instantiation +// buildInstantiationWrapper builds the body of an instantiation // wrapper fn. The body calls the original generic function, // bracketed by ChangeType conversions on its arguments and results. -func buildInstantiationWrapper(fn *Function) { +func (b *builder) buildInstantiationWrapper(fn *Function) { orig := fn.topLevelOrigin sig := fn.Signature fn.startBody() if sig.Recv() != nil { - fn.addParamObj(sig.Recv()) + fn.addParamVar(sig.Recv()) } createParams(fn, 0) diff --git a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go index fa5834baf..11d5c8c3a 100644 --- a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go +++ b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go @@ -26,13 +26,10 @@ package objectpath import ( "fmt" "go/types" - "sort" "strconv" "strings" - _ "unsafe" "golang.org/x/tools/internal/typeparams" - "golang.org/x/tools/internal/typesinternal" ) // A Path is an opaque name that identifies a types.Object @@ -123,20 +120,7 @@ func For(obj types.Object) (Path, error) { // An Encoder amortizes the cost of encoding the paths of multiple objects. // The zero value of an Encoder is ready to use. type Encoder struct { - scopeMemo map[*types.Scope][]types.Object // memoization of scopeObjects - namedMethodsMemo map[*types.Named][]*types.Func // memoization of namedMethods() - skipMethodSorting bool -} - -// Expose back doors so that gopls can avoid method sorting, which can dominate -// analysis on certain repositories. -// -// TODO(golang/go#61443): remove this. -func init() { - typesinternal.SkipEncoderMethodSorting = func(enc interface{}) { - enc.(*Encoder).skipMethodSorting = true - } - typesinternal.ObjectpathObject = object + scopeMemo map[*types.Scope][]types.Object // memoization of scopeObjects } // For returns the path to an object relative to its package, @@ -239,7 +223,7 @@ func (enc *Encoder) For(obj types.Object) (Path, error) { // Reject obviously non-viable cases. switch obj := obj.(type) { case *types.TypeName: - if _, ok := obj.Type().(*typeparams.TypeParam); !ok { + if _, ok := obj.Type().(*types.TypeParam); !ok { // With the exception of type parameters, only package-level type names // have a path. return "", fmt.Errorf("no path for %v", obj) @@ -299,7 +283,7 @@ func (enc *Encoder) For(obj types.Object) (Path, error) { } } else { if named, _ := T.(*types.Named); named != nil { - if r := findTypeParam(obj, typeparams.ForNamed(named), path, nil); r != nil { + if r := findTypeParam(obj, named.TypeParams(), path, nil); r != nil { // generic named type return Path(r), nil } @@ -328,31 +312,18 @@ func (enc *Encoder) For(obj types.Object) (Path, error) { // Inspect declared methods of defined types. if T, ok := o.Type().(*types.Named); ok { path = append(path, opType) - if !enc.skipMethodSorting { - // Note that method index here is always with respect - // to canonical ordering of methods, regardless of how - // they appear in the underlying type. - for i, m := range enc.namedMethods(T) { - path2 := appendOpArg(path, opMethod, i) - if m == obj { - return Path(path2), nil // found declared method - } - if r := find(obj, m.Type(), append(path2, opType), nil); r != nil { - return Path(r), nil - } + // The method index here is always with respect + // to the underlying go/types data structures, + // which ultimately derives from source order + // and must be preserved by export data. + for i := 0; i < T.NumMethods(); i++ { + m := T.Method(i) + path2 := appendOpArg(path, opMethod, i) + if m == obj { + return Path(path2), nil // found declared method } - } else { - // This branch must match the logic in the branch above, using go/types - // APIs without sorting. - for i := 0; i < T.NumMethods(); i++ { - m := T.Method(i) - path2 := appendOpArg(path, opMethod, i) - if m == obj { - return Path(path2), nil // found declared method - } - if r := find(obj, m.Type(), append(path2, opType), nil); r != nil { - return Path(r), nil - } + if r := find(obj, m.Type(), append(path2, opType), nil); r != nil { + return Path(r), nil } } } @@ -448,22 +419,13 @@ func (enc *Encoder) concreteMethod(meth *types.Func) (Path, bool) { path = append(path, name...) path = append(path, opType) - if !enc.skipMethodSorting { - for i, m := range enc.namedMethods(named) { - if m == meth { - path = appendOpArg(path, opMethod, i) - return Path(path), true - } - } - } else { - // This branch must match the logic of the branch above, using go/types - // APIs without sorting. - for i := 0; i < named.NumMethods(); i++ { - m := named.Method(i) - if m == meth { - path = appendOpArg(path, opMethod, i) - return Path(path), true - } + // Method indices are w.r.t. the go/types data structures, + // ultimately deriving from source order, + // which is preserved by export data. + for i := 0; i < named.NumMethods(); i++ { + if named.Method(i) == meth { + path = appendOpArg(path, opMethod, i) + return Path(path), true } } @@ -500,7 +462,7 @@ func find(obj types.Object, T types.Type, path []byte, seen map[*types.TypeName] } return find(obj, T.Elem(), append(path, opElem), seen) case *types.Signature: - if r := findTypeParam(obj, typeparams.ForSignature(T), path, seen); r != nil { + if r := findTypeParam(obj, T.TypeParams(), path, seen); r != nil { return r } if r := find(obj, T.Params(), append(path, opParams), seen); r != nil { @@ -543,7 +505,7 @@ func find(obj types.Object, T types.Type, path []byte, seen map[*types.TypeName] } } return nil - case *typeparams.TypeParam: + case *types.TypeParam: name := T.Obj() if name == obj { return append(path, opObj) @@ -563,7 +525,7 @@ func find(obj types.Object, T types.Type, path []byte, seen map[*types.TypeName] panic(T) } -func findTypeParam(obj types.Object, list *typeparams.TypeParamList, path []byte, seen map[*types.TypeName]bool) []byte { +func findTypeParam(obj types.Object, list *types.TypeParamList, path []byte, seen map[*types.TypeName]bool) []byte { for i := 0; i < list.Len(); i++ { tparam := list.At(i) path2 := appendOpArg(path, opTypeParam, i) @@ -576,12 +538,7 @@ func findTypeParam(obj types.Object, list *typeparams.TypeParamList, path []byte // Object returns the object denoted by path p within the package pkg. func Object(pkg *types.Package, p Path) (types.Object, error) { - return object(pkg, string(p), false) -} - -// Note: the skipMethodSorting parameter must match the value of -// Encoder.skipMethodSorting used during encoding. -func object(pkg *types.Package, pathstr string, skipMethodSorting bool) (types.Object, error) { + pathstr := string(p) if pathstr == "" { return nil, fmt.Errorf("empty path") } @@ -605,7 +562,7 @@ func object(pkg *types.Package, pathstr string, skipMethodSorting bool) (types.O } // abstraction of *types.{Named,Signature} type hasTypeParams interface { - TypeParams() *typeparams.TypeParamList + TypeParams() *types.TypeParamList } // abstraction of *types.{Named,TypeParam} type hasObj interface { @@ -707,7 +664,7 @@ func object(pkg *types.Package, pathstr string, skipMethodSorting bool) (types.O t = tparams.At(index) case opConstraint: - tparam, ok := t.(*typeparams.TypeParam) + tparam, ok := t.(*types.TypeParam) if !ok { return nil, fmt.Errorf("cannot apply %q to %s (got %T, want type parameter)", code, t, t) } @@ -747,12 +704,7 @@ func object(pkg *types.Package, pathstr string, skipMethodSorting bool) (types.O if index >= t.NumMethods() { return nil, fmt.Errorf("method index %d out of range [0-%d)", index, t.NumMethods()) } - if skipMethodSorting { - obj = t.Method(index) - } else { - methods := namedMethods(t) // (unmemoized) - obj = methods[index] // Id-ordered - } + obj = t.Method(index) default: return nil, fmt.Errorf("cannot apply %q to %s (got %T, want interface or named)", code, t, t) @@ -779,33 +731,6 @@ func object(pkg *types.Package, pathstr string, skipMethodSorting bool) (types.O return obj, nil // success } -// namedMethods returns the methods of a Named type in ascending Id order. -func namedMethods(named *types.Named) []*types.Func { - methods := make([]*types.Func, named.NumMethods()) - for i := range methods { - methods[i] = named.Method(i) - } - sort.Slice(methods, func(i, j int) bool { - return methods[i].Id() < methods[j].Id() - }) - return methods -} - -// namedMethods is a memoization of the namedMethods function. Callers must not modify the result. -func (enc *Encoder) namedMethods(named *types.Named) []*types.Func { - m := enc.namedMethodsMemo - if m == nil { - m = make(map[*types.Named][]*types.Func) - enc.namedMethodsMemo = m - } - methods, ok := m[named] - if !ok { - methods = namedMethods(named) // allocates and sorts - m[named] = methods - } - return methods -} - // scopeObjects is a memoization of scope objects. // Callers must not modify the result. func (enc *Encoder) scopeObjects(scope *types.Scope) []types.Object { diff --git a/vendor/golang.org/x/tools/go/types/typeutil/callee.go b/vendor/golang.org/x/tools/go/types/typeutil/callee.go index 90b3ab0e2..90dc541ad 100644 --- a/vendor/golang.org/x/tools/go/types/typeutil/callee.go +++ b/vendor/golang.org/x/tools/go/types/typeutil/callee.go @@ -22,7 +22,7 @@ func Callee(info *types.Info, call *ast.CallExpr) types.Object { // Look through type instantiation if necessary. isInstance := false switch fun.(type) { - case *ast.IndexExpr, *typeparams.IndexListExpr: + case *ast.IndexExpr, *ast.IndexListExpr: // When extracting the callee from an *IndexExpr, we need to check that // it is a *types.Func and not a *types.Var. // Example: Don't match a slice m within the expression `m[0]()`. diff --git a/vendor/golang.org/x/tools/go/types/typeutil/map.go b/vendor/golang.org/x/tools/go/types/typeutil/map.go index 7bd2fdb38..e154be0bd 100644 --- a/vendor/golang.org/x/tools/go/types/typeutil/map.go +++ b/vendor/golang.org/x/tools/go/types/typeutil/map.go @@ -12,6 +12,7 @@ import ( "go/types" "reflect" + "golang.org/x/tools/internal/aliases" "golang.org/x/tools/internal/typeparams" ) @@ -219,7 +220,7 @@ type Hasher struct { // generic types or functions, and instantiated signatures do not have type // parameter lists, we should never encounter a second non-empty type // parameter list when hashing a generic signature. - sigTParams *typeparams.TypeParamList + sigTParams *types.TypeParamList } // MakeHasher returns a new Hasher instance. @@ -259,6 +260,9 @@ func (h Hasher) hashFor(t types.Type) uint32 { case *types.Basic: return uint32(t.Kind()) + case *aliases.Alias: + return h.Hash(t.Underlying()) + case *types.Array: return 9043 + 2*uint32(t.Len()) + 3*h.Hash(t.Elem()) @@ -297,7 +301,7 @@ func (h Hasher) hashFor(t types.Type) uint32 { // We should never encounter a generic signature while hashing another // generic signature, but defensively set sigTParams only if h.mask is // unset. - tparams := typeparams.ForSignature(t) + tparams := t.TypeParams() if h.sigTParams == nil && tparams.Len() != 0 { h = Hasher{ // There may be something more efficient than discarding the existing @@ -318,7 +322,7 @@ func (h Hasher) hashFor(t types.Type) uint32 { return hash + 3*h.hashTuple(t.Params()) + 5*h.hashTuple(t.Results()) - case *typeparams.Union: + case *types.Union: return h.hashUnion(t) case *types.Interface: @@ -354,14 +358,14 @@ func (h Hasher) hashFor(t types.Type) uint32 { case *types.Named: hash := h.hashPtr(t.Obj()) - targs := typeparams.NamedTypeArgs(t) + targs := t.TypeArgs() for i := 0; i < targs.Len(); i++ { targ := targs.At(i) hash += 2 * h.Hash(targ) } return hash - case *typeparams.TypeParam: + case *types.TypeParam: return h.hashTypeParam(t) case *types.Tuple: @@ -381,7 +385,7 @@ func (h Hasher) hashTuple(tuple *types.Tuple) uint32 { return hash } -func (h Hasher) hashUnion(t *typeparams.Union) uint32 { +func (h Hasher) hashUnion(t *types.Union) uint32 { // Hash type restrictions. terms, err := typeparams.UnionTermSet(t) // if err != nil t has invalid type restrictions. Fall back on a non-zero @@ -392,7 +396,7 @@ func (h Hasher) hashUnion(t *typeparams.Union) uint32 { return h.hashTermSet(terms) } -func (h Hasher) hashTermSet(terms []*typeparams.Term) uint32 { +func (h Hasher) hashTermSet(terms []*types.Term) uint32 { hash := 9157 + 2*uint32(len(terms)) for _, term := range terms { // term order is not significant. @@ -416,7 +420,7 @@ func (h Hasher) hashTermSet(terms []*typeparams.Term) uint32 { // are not identical. // // Otherwise the hash of t depends only on t's pointer identity. -func (h Hasher) hashTypeParam(t *typeparams.TypeParam) uint32 { +func (h Hasher) hashTypeParam(t *types.TypeParam) uint32 { if h.sigTParams != nil { i := t.Index() if i >= 0 && i < h.sigTParams.Len() && t == h.sigTParams.At(i) { @@ -457,6 +461,9 @@ func (h Hasher) shallowHash(t types.Type) uint32 { // elements (mostly Slice, Pointer, Basic, Named), // so there's no need to optimize anything else. switch t := t.(type) { + case *aliases.Alias: + return h.shallowHash(t.Underlying()) + case *types.Signature: var hash uint32 = 604171 if t.Variadic() { @@ -489,7 +496,7 @@ func (h Hasher) shallowHash(t types.Type) uint32 { case *types.Pointer: return 4393139 - case *typeparams.Union: + case *types.Union: return 562448657 case *types.Interface: @@ -504,7 +511,7 @@ func (h Hasher) shallowHash(t types.Type) uint32 { case *types.Named: return h.hashPtr(t.Obj()) - case *typeparams.TypeParam: + case *types.TypeParam: return h.hashPtr(t.Obj()) } panic(fmt.Sprintf("shallowHash: %T: %v", t, t)) diff --git a/vendor/golang.org/x/tools/go/types/typeutil/methodsetcache.go b/vendor/golang.org/x/tools/go/types/typeutil/methodsetcache.go index a5d931083..bd71aafaa 100644 --- a/vendor/golang.org/x/tools/go/types/typeutil/methodsetcache.go +++ b/vendor/golang.org/x/tools/go/types/typeutil/methodsetcache.go @@ -9,6 +9,8 @@ package typeutil import ( "go/types" "sync" + + "golang.org/x/tools/internal/aliases" ) // A MethodSetCache records the method set of each type T for which @@ -32,12 +34,12 @@ func (cache *MethodSetCache) MethodSet(T types.Type) *types.MethodSet { cache.mu.Lock() defer cache.mu.Unlock() - switch T := T.(type) { + switch T := aliases.Unalias(T).(type) { case *types.Named: return cache.lookupNamed(T).value case *types.Pointer: - if N, ok := T.Elem().(*types.Named); ok { + if N, ok := aliases.Unalias(T.Elem()).(*types.Named); ok { return cache.lookupNamed(N).pointer } } diff --git a/vendor/golang.org/x/tools/internal/aliases/aliases.go b/vendor/golang.org/x/tools/internal/aliases/aliases.go new file mode 100644 index 000000000..f89112c8e --- /dev/null +++ b/vendor/golang.org/x/tools/internal/aliases/aliases.go @@ -0,0 +1,28 @@ +// Copyright 2024 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 aliases + +import ( + "go/token" + "go/types" +) + +// Package aliases defines backward compatible shims +// for the types.Alias type representation added in 1.22. +// This defines placeholders for x/tools until 1.26. + +// NewAlias creates a new TypeName in Package pkg that +// is an alias for the type rhs. +// +// When GoVersion>=1.22 and GODEBUG=gotypesalias=1, +// the Type() of the return value is a *types.Alias. +func NewAlias(pos token.Pos, pkg *types.Package, name string, rhs types.Type) *types.TypeName { + if enabled() { + tname := types.NewTypeName(pos, pkg, name, nil) + newAlias(tname, rhs) + return tname + } + return types.NewTypeName(pos, pkg, name, rhs) +} diff --git a/vendor/golang.org/x/tools/internal/aliases/aliases_go121.go b/vendor/golang.org/x/tools/internal/aliases/aliases_go121.go new file mode 100644 index 000000000..1872b56ff --- /dev/null +++ b/vendor/golang.org/x/tools/internal/aliases/aliases_go121.go @@ -0,0 +1,30 @@ +// Copyright 2024 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 !go1.22 +// +build !go1.22 + +package aliases + +import ( + "go/types" +) + +// Alias is a placeholder for a go/types.Alias for <=1.21. +// It will never be created by go/types. +type Alias struct{} + +func (*Alias) String() string { panic("unreachable") } + +func (*Alias) Underlying() types.Type { panic("unreachable") } + +func (*Alias) Obj() *types.TypeName { panic("unreachable") } + +// Unalias returns the type t for go <=1.21. +func Unalias(t types.Type) types.Type { return t } + +// Always false for go <=1.21. Ignores GODEBUG. +func enabled() bool { return false } + +func newAlias(name *types.TypeName, rhs types.Type) *Alias { panic("unreachable") } diff --git a/vendor/golang.org/x/tools/internal/aliases/aliases_go122.go b/vendor/golang.org/x/tools/internal/aliases/aliases_go122.go new file mode 100644 index 000000000..8b9211628 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/aliases/aliases_go122.go @@ -0,0 +1,72 @@ +// Copyright 2024 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 go1.22 +// +build go1.22 + +package aliases + +import ( + "go/ast" + "go/parser" + "go/token" + "go/types" + "os" + "strings" + "sync" +) + +// Alias is an alias of types.Alias. +type Alias = types.Alias + +// Unalias is a wrapper of types.Unalias. +func Unalias(t types.Type) types.Type { return types.Unalias(t) } + +// newAlias is an internal alias around types.NewAlias. +// Direct usage is discouraged as the moment. +// Try to use NewAlias instead. +func newAlias(tname *types.TypeName, rhs types.Type) *Alias { + a := types.NewAlias(tname, rhs) + // TODO(go.dev/issue/65455): Remove kludgy workaround to set a.actual as a side-effect. + Unalias(a) + return a +} + +// enabled returns true when types.Aliases are enabled. +func enabled() bool { + // Use the gotypesalias value in GODEBUG if set. + godebug := os.Getenv("GODEBUG") + value := -1 // last set value. + for _, f := range strings.Split(godebug, ",") { + switch f { + case "gotypesalias=1": + value = 1 + case "gotypesalias=0": + value = 0 + } + } + switch value { + case 0: + return false + case 1: + return true + default: + return aliasesDefault() + } +} + +// aliasesDefault reports if aliases are enabled by default. +func aliasesDefault() bool { + // Dynamically check if Aliases will be produced from go/types. + aliasesDefaultOnce.Do(func() { + fset := token.NewFileSet() + f, _ := parser.ParseFile(fset, "a.go", "package p; type A = int", 0) + pkg, _ := new(types.Config).Check("p", fset, []*ast.File{f}, nil) + _, gotypesaliasDefault = pkg.Scope().Lookup("A").Type().(*types.Alias) + }) + return gotypesaliasDefault +} + +var gotypesaliasDefault bool +var aliasesDefaultOnce sync.Once diff --git a/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go b/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go new file mode 100644 index 000000000..b24a0fba9 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go @@ -0,0 +1,390 @@ +// Copyright 2020 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 analysisinternal provides gopls' internal analyses with a +// number of helper functions that operate on typed syntax trees. +package analysisinternal + +import ( + "bytes" + "fmt" + "go/ast" + "go/token" + "go/types" + "strconv" +) + +func TypeErrorEndPos(fset *token.FileSet, src []byte, start token.Pos) token.Pos { + // Get the end position for the type error. + offset, end := fset.PositionFor(start, false).Offset, start + if offset >= len(src) { + return end + } + if width := bytes.IndexAny(src[offset:], " \n,():;[]+-*"); width > 0 { + end = start + token.Pos(width) + } + return end +} + +func ZeroValue(f *ast.File, pkg *types.Package, typ types.Type) ast.Expr { + under := typ + if n, ok := typ.(*types.Named); ok { + under = n.Underlying() + } + switch u := under.(type) { + case *types.Basic: + switch { + case u.Info()&types.IsNumeric != 0: + return &ast.BasicLit{Kind: token.INT, Value: "0"} + case u.Info()&types.IsBoolean != 0: + return &ast.Ident{Name: "false"} + case u.Info()&types.IsString != 0: + return &ast.BasicLit{Kind: token.STRING, Value: `""`} + default: + panic(fmt.Sprintf("unknown basic type %v", u)) + } + case *types.Chan, *types.Interface, *types.Map, *types.Pointer, *types.Signature, *types.Slice, *types.Array: + return ast.NewIdent("nil") + case *types.Struct: + texpr := TypeExpr(f, pkg, typ) // typ because we want the name here. + if texpr == nil { + return nil + } + return &ast.CompositeLit{ + Type: texpr, + } + } + return nil +} + +// IsZeroValue checks whether the given expression is a 'zero value' (as determined by output of +// analysisinternal.ZeroValue) +func IsZeroValue(expr ast.Expr) bool { + switch e := expr.(type) { + case *ast.BasicLit: + return e.Value == "0" || e.Value == `""` + case *ast.Ident: + return e.Name == "nil" || e.Name == "false" + default: + return false + } +} + +// TypeExpr returns syntax for the specified type. References to +// named types from packages other than pkg are qualified by an appropriate +// package name, as defined by the import environment of file. +func TypeExpr(f *ast.File, pkg *types.Package, typ types.Type) ast.Expr { + switch t := typ.(type) { + case *types.Basic: + switch t.Kind() { + case types.UnsafePointer: + return &ast.SelectorExpr{X: ast.NewIdent("unsafe"), Sel: ast.NewIdent("Pointer")} + default: + return ast.NewIdent(t.Name()) + } + case *types.Pointer: + x := TypeExpr(f, pkg, t.Elem()) + if x == nil { + return nil + } + return &ast.UnaryExpr{ + Op: token.MUL, + X: x, + } + case *types.Array: + elt := TypeExpr(f, pkg, t.Elem()) + if elt == nil { + return nil + } + return &ast.ArrayType{ + Len: &ast.BasicLit{ + Kind: token.INT, + Value: fmt.Sprintf("%d", t.Len()), + }, + Elt: elt, + } + case *types.Slice: + elt := TypeExpr(f, pkg, t.Elem()) + if elt == nil { + return nil + } + return &ast.ArrayType{ + Elt: elt, + } + case *types.Map: + key := TypeExpr(f, pkg, t.Key()) + value := TypeExpr(f, pkg, t.Elem()) + if key == nil || value == nil { + return nil + } + return &ast.MapType{ + Key: key, + Value: value, + } + case *types.Chan: + dir := ast.ChanDir(t.Dir()) + if t.Dir() == types.SendRecv { + dir = ast.SEND | ast.RECV + } + value := TypeExpr(f, pkg, t.Elem()) + if value == nil { + return nil + } + return &ast.ChanType{ + Dir: dir, + Value: value, + } + case *types.Signature: + var params []*ast.Field + for i := 0; i < t.Params().Len(); i++ { + p := TypeExpr(f, pkg, t.Params().At(i).Type()) + if p == nil { + return nil + } + params = append(params, &ast.Field{ + Type: p, + Names: []*ast.Ident{ + { + Name: t.Params().At(i).Name(), + }, + }, + }) + } + if t.Variadic() { + last := params[len(params)-1] + last.Type = &ast.Ellipsis{Elt: last.Type.(*ast.ArrayType).Elt} + } + var returns []*ast.Field + for i := 0; i < t.Results().Len(); i++ { + r := TypeExpr(f, pkg, t.Results().At(i).Type()) + if r == nil { + return nil + } + returns = append(returns, &ast.Field{ + Type: r, + }) + } + return &ast.FuncType{ + Params: &ast.FieldList{ + List: params, + }, + Results: &ast.FieldList{ + List: returns, + }, + } + case *types.Named: + if t.Obj().Pkg() == nil { + return ast.NewIdent(t.Obj().Name()) + } + if t.Obj().Pkg() == pkg { + return ast.NewIdent(t.Obj().Name()) + } + pkgName := t.Obj().Pkg().Name() + + // If the file already imports the package under another name, use that. + for _, cand := range f.Imports { + if path, _ := strconv.Unquote(cand.Path.Value); path == t.Obj().Pkg().Path() { + if cand.Name != nil && cand.Name.Name != "" { + pkgName = cand.Name.Name + } + } + } + if pkgName == "." { + return ast.NewIdent(t.Obj().Name()) + } + return &ast.SelectorExpr{ + X: ast.NewIdent(pkgName), + Sel: ast.NewIdent(t.Obj().Name()), + } + case *types.Struct: + return ast.NewIdent(t.String()) + case *types.Interface: + return ast.NewIdent(t.String()) + default: + return nil + } +} + +// StmtToInsertVarBefore returns the ast.Stmt before which we can safely insert a new variable. +// Some examples: +// +// Basic Example: +// z := 1 +// y := z + x +// If x is undeclared, then this function would return `y := z + x`, so that we +// can insert `x := ` on the line before `y := z + x`. +// +// If stmt example: +// if z == 1 { +// } else if z == y {} +// If y is undeclared, then this function would return `if z == 1 {`, because we cannot +// insert a statement between an if and an else if statement. As a result, we need to find +// the top of the if chain to insert `y := ` before. +func StmtToInsertVarBefore(path []ast.Node) ast.Stmt { + enclosingIndex := -1 + for i, p := range path { + if _, ok := p.(ast.Stmt); ok { + enclosingIndex = i + break + } + } + if enclosingIndex == -1 { + return nil + } + enclosingStmt := path[enclosingIndex] + switch enclosingStmt.(type) { + case *ast.IfStmt: + // The enclosingStmt is inside of the if declaration, + // We need to check if we are in an else-if stmt and + // get the base if statement. + return baseIfStmt(path, enclosingIndex) + case *ast.CaseClause: + // Get the enclosing switch stmt if the enclosingStmt is + // inside of the case statement. + for i := enclosingIndex + 1; i < len(path); i++ { + if node, ok := path[i].(*ast.SwitchStmt); ok { + return node + } else if node, ok := path[i].(*ast.TypeSwitchStmt); ok { + return node + } + } + } + if len(path) <= enclosingIndex+1 { + return enclosingStmt.(ast.Stmt) + } + // Check if the enclosing statement is inside another node. + switch expr := path[enclosingIndex+1].(type) { + case *ast.IfStmt: + // Get the base if statement. + return baseIfStmt(path, enclosingIndex+1) + case *ast.ForStmt: + if expr.Init == enclosingStmt || expr.Post == enclosingStmt { + return expr + } + } + return enclosingStmt.(ast.Stmt) +} + +// baseIfStmt walks up the if/else-if chain until we get to +// the top of the current if chain. +func baseIfStmt(path []ast.Node, index int) ast.Stmt { + stmt := path[index] + for i := index + 1; i < len(path); i++ { + if node, ok := path[i].(*ast.IfStmt); ok && node.Else == stmt { + stmt = node + continue + } + break + } + return stmt.(ast.Stmt) +} + +// WalkASTWithParent walks the AST rooted at n. The semantics are +// similar to ast.Inspect except it does not call f(nil). +func WalkASTWithParent(n ast.Node, f func(n ast.Node, parent ast.Node) bool) { + var ancestors []ast.Node + ast.Inspect(n, func(n ast.Node) (recurse bool) { + if n == nil { + ancestors = ancestors[:len(ancestors)-1] + return false + } + + var parent ast.Node + if len(ancestors) > 0 { + parent = ancestors[len(ancestors)-1] + } + ancestors = append(ancestors, n) + return f(n, parent) + }) +} + +// MatchingIdents finds the names of all identifiers in 'node' that match any of the given types. +// 'pos' represents the position at which the identifiers may be inserted. 'pos' must be within +// the scope of each of identifier we select. Otherwise, we will insert a variable at 'pos' that +// is unrecognized. +func MatchingIdents(typs []types.Type, node ast.Node, pos token.Pos, info *types.Info, pkg *types.Package) map[types.Type][]string { + + // Initialize matches to contain the variable types we are searching for. + matches := make(map[types.Type][]string) + for _, typ := range typs { + if typ == nil { + continue // TODO(adonovan): is this reachable? + } + matches[typ] = nil // create entry + } + + seen := map[types.Object]struct{}{} + ast.Inspect(node, func(n ast.Node) bool { + if n == nil { + return false + } + // Prevent circular definitions. If 'pos' is within an assignment statement, do not + // allow any identifiers in that assignment statement to be selected. Otherwise, + // we could do the following, where 'x' satisfies the type of 'f0': + // + // x := fakeStruct{f0: x} + // + if assign, ok := n.(*ast.AssignStmt); ok && pos > assign.Pos() && pos <= assign.End() { + return false + } + if n.End() > pos { + return n.Pos() <= pos + } + ident, ok := n.(*ast.Ident) + if !ok || ident.Name == "_" { + return true + } + obj := info.Defs[ident] + if obj == nil || obj.Type() == nil { + return true + } + if _, ok := obj.(*types.TypeName); ok { + return true + } + // Prevent duplicates in matches' values. + if _, ok = seen[obj]; ok { + return true + } + seen[obj] = struct{}{} + // Find the scope for the given position. Then, check whether the object + // exists within the scope. + innerScope := pkg.Scope().Innermost(pos) + if innerScope == nil { + return true + } + _, foundObj := innerScope.LookupParent(ident.Name, pos) + if foundObj != obj { + return true + } + // The object must match one of the types that we are searching for. + // TODO(adonovan): opt: use typeutil.Map? + if names, ok := matches[obj.Type()]; ok { + matches[obj.Type()] = append(names, ident.Name) + } else { + // If the object type does not exactly match + // any of the target types, greedily find the first + // target type that the object type can satisfy. + for typ := range matches { + if equivalentTypes(obj.Type(), typ) { + matches[typ] = append(matches[typ], ident.Name) + } + } + } + return true + }) + return matches +} + +func equivalentTypes(want, got types.Type) bool { + if types.Identical(want, got) { + return true + } + // Code segment to help check for untyped equality from (golang/go#32146). + if rhs, ok := want.(*types.Basic); ok && rhs.Info()&types.IsUntyped > 0 { + if lhs, ok := got.Underlying().(*types.Basic); ok { + return rhs.Info()&types.IsConstType == lhs.Info()&types.IsConstType + } + } + return types.AssignableTo(want, got) +} diff --git a/vendor/golang.org/x/tools/go/analysis/passes/internal/analysisutil/extractdoc.go b/vendor/golang.org/x/tools/internal/analysisinternal/extractdoc.go index 0e175ca06..39507723d 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/internal/analysisutil/extractdoc.go +++ b/vendor/golang.org/x/tools/internal/analysisinternal/extractdoc.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package analysisutil +package analysisinternal import ( "fmt" diff --git a/vendor/golang.org/x/tools/internal/diff/lcs/git.sh b/vendor/golang.org/x/tools/internal/diff/lcs/git.sh index 6856f8439..b25ba4aac 100644 --- a/vendor/golang.org/x/tools/internal/diff/lcs/git.sh +++ b/vendor/golang.org/x/tools/internal/diff/lcs/git.sh @@ -14,9 +14,9 @@ set -eu # WARNING: This script will install the latest version of $file # The largest real source file in the x/tools repo. -# file=internal/lsp/source/completion/completion.go -# file=internal/lsp/source/diagnostics.go -file=internal/lsp/protocol/tsprotocol.go +# file=internal/golang/completion/completion.go +# file=internal/golang/diagnostics.go +file=internal/protocol/tsprotocol.go tmp=$(mktemp -d) git log $file | diff --git a/vendor/golang.org/x/tools/internal/diff/ndiff.go b/vendor/golang.org/x/tools/internal/diff/ndiff.go index 050b08ded..fbef4d730 100644 --- a/vendor/golang.org/x/tools/internal/diff/ndiff.go +++ b/vendor/golang.org/x/tools/internal/diff/ndiff.go @@ -18,7 +18,7 @@ func Strings(before, after string) []Edit { return nil // common case } - if stringIsASCII(before) && stringIsASCII(after) { + if isASCII(before) && isASCII(after) { // TODO(adonovan): opt: specialize diffASCII for strings. return diffASCII([]byte(before), []byte(after)) } @@ -32,7 +32,7 @@ func Bytes(before, after []byte) []Edit { return nil // common case } - if bytesIsASCII(before) && bytesIsASCII(after) { + if isASCII(before) && isASCII(after) { return diffASCII(before, after) } return diffRunes(runes(before), runes(after)) @@ -88,18 +88,8 @@ func runesLen(runes []rune) (len int) { return len } -// stringIsASCII reports whether s contains only ASCII. -// TODO(adonovan): combine when x/tools allows generics. -func stringIsASCII(s string) bool { - for i := 0; i < len(s); i++ { - if s[i] >= utf8.RuneSelf { - return false - } - } - return true -} - -func bytesIsASCII(s []byte) bool { +// isASCII reports whether s contains only ASCII. +func isASCII[S string | []byte](s S) bool { for i := 0; i < len(s); i++ { if s[i] >= utf8.RuneSelf { return false diff --git a/vendor/golang.org/x/tools/internal/diff/unified.go b/vendor/golang.org/x/tools/internal/diff/unified.go index 1308503f7..cfbda6102 100644 --- a/vendor/golang.org/x/tools/internal/diff/unified.go +++ b/vendor/golang.org/x/tools/internal/diff/unified.go @@ -10,12 +10,16 @@ import ( "strings" ) +// DefaultContextLines is the number of unchanged lines of surrounding +// context displayed by Unified. Use ToUnified to specify a different value. +const DefaultContextLines = 3 + // Unified returns a unified diff of the old and new strings. // The old and new labels are the names of the old and new files. // If the strings are equal, it returns the empty string. func Unified(oldLabel, newLabel, old, new string) string { edits := Strings(old, new) - unified, err := ToUnified(oldLabel, newLabel, old, edits) + unified, err := ToUnified(oldLabel, newLabel, old, edits, DefaultContextLines) if err != nil { // Can't happen: edits are consistent. log.Fatalf("internal error in diff.Unified: %v", err) @@ -23,11 +27,12 @@ func Unified(oldLabel, newLabel, old, new string) string { return unified } -// ToUnified applies the edits to content and returns a unified diff. +// ToUnified applies the edits to content and returns a unified diff, +// with contextLines lines of (unchanged) context around each diff hunk. // The old and new labels are the names of the content and result files. // It returns an error if the edits are inconsistent; see ApplyEdits. -func ToUnified(oldLabel, newLabel, content string, edits []Edit) (string, error) { - u, err := toUnified(oldLabel, newLabel, content, edits) +func ToUnified(oldLabel, newLabel, content string, edits []Edit, contextLines int) (string, error) { + u, err := toUnified(oldLabel, newLabel, content, edits, contextLines) if err != nil { return "", err } @@ -93,14 +98,10 @@ func (k opKind) String() string { } } -const ( - edge = 3 - gap = edge * 2 -) - // toUnified takes a file contents and a sequence of edits, and calculates // a unified diff that represents those edits. -func toUnified(fromName, toName string, content string, edits []Edit) (unified, error) { +func toUnified(fromName, toName string, content string, edits []Edit, contextLines int) (unified, error) { + gap := contextLines * 2 u := unified{ from: fromName, to: toName, @@ -136,7 +137,7 @@ func toUnified(fromName, toName string, content string, edits []Edit) (unified, //need to start a new hunk if h != nil { // add the edge to the previous hunk - addEqualLines(h, lines, last, last+edge) + addEqualLines(h, lines, last, last+contextLines) u.hunks = append(u.hunks, h) } toLine += start - last @@ -145,7 +146,7 @@ func toUnified(fromName, toName string, content string, edits []Edit) (unified, toLine: toLine + 1, } // add the edge to the new hunk - delta := addEqualLines(h, lines, start-edge, start) + delta := addEqualLines(h, lines, start-contextLines, start) h.fromLine -= delta h.toLine -= delta } @@ -163,7 +164,7 @@ func toUnified(fromName, toName string, content string, edits []Edit) (unified, } if h != nil { // add the edge to the final hunk - addEqualLines(h, lines, last, last+edge) + addEqualLines(h, lines, last, last+contextLines) u.hunks = append(u.hunks, h) } return u, nil diff --git a/vendor/golang.org/x/tools/internal/event/keys/util.go b/vendor/golang.org/x/tools/internal/event/keys/util.go new file mode 100644 index 000000000..c0e8e731c --- /dev/null +++ b/vendor/golang.org/x/tools/internal/event/keys/util.go @@ -0,0 +1,21 @@ +// 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 keys + +import ( + "sort" + "strings" +) + +// Join returns a canonical join of the keys in S: +// a sorted comma-separated string list. +func Join[S ~[]T, T ~string](s S) string { + strs := make([]string, 0, len(s)) + for _, v := range s { + strs = append(strs, string(v)) + } + sort.Strings(strs) + return strings.Join(strs, ",") +} diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk.go deleted file mode 100644 index c40c7e931..000000000 --- a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk.go +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright 2016 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 fastwalk provides a faster version of [filepath.Walk] for file system -// scanning tools. -package fastwalk - -import ( - "errors" - "os" - "path/filepath" - "runtime" - "sync" -) - -// ErrTraverseLink is used as a return value from WalkFuncs to indicate that the -// symlink named in the call may be traversed. -var ErrTraverseLink = errors.New("fastwalk: traverse symlink, assuming target is a directory") - -// ErrSkipFiles is a used as a return value from WalkFuncs to indicate that the -// callback should not be called for any other files in the current directory. -// Child directories will still be traversed. -var ErrSkipFiles = errors.New("fastwalk: skip remaining files in directory") - -// Walk is a faster implementation of [filepath.Walk]. -// -// [filepath.Walk]'s design necessarily calls [os.Lstat] on each file, -// even if the caller needs less info. -// Many tools need only the type of each file. -// On some platforms, this information is provided directly by the readdir -// system call, avoiding the need to stat each file individually. -// fastwalk_unix.go contains a fork of the syscall routines. -// -// See golang.org/issue/16399. -// -// Walk walks the file tree rooted at root, calling walkFn for -// each file or directory in the tree, including root. -// -// If Walk returns [filepath.SkipDir], the directory is skipped. -// -// Unlike [filepath.Walk]: -// - file stat calls must be done by the user. -// The only provided metadata is the file type, which does not include -// any permission bits. -// - multiple goroutines stat the filesystem concurrently. The provided -// walkFn must be safe for concurrent use. -// - Walk can follow symlinks if walkFn returns the TraverseLink -// sentinel error. It is the walkFn's responsibility to prevent -// Walk from going into symlink cycles. -func Walk(root string, walkFn func(path string, typ os.FileMode) error) error { - // TODO(bradfitz): make numWorkers configurable? We used a - // minimum of 4 to give the kernel more info about multiple - // things we want, in hopes its I/O scheduling can take - // advantage of that. Hopefully most are in cache. Maybe 4 is - // even too low of a minimum. Profile more. - numWorkers := 4 - if n := runtime.NumCPU(); n > numWorkers { - numWorkers = n - } - - // Make sure to wait for all workers to finish, otherwise - // walkFn could still be called after returning. This Wait call - // runs after close(e.donec) below. - var wg sync.WaitGroup - defer wg.Wait() - - w := &walker{ - fn: walkFn, - enqueuec: make(chan walkItem, numWorkers), // buffered for performance - workc: make(chan walkItem, numWorkers), // buffered for performance - donec: make(chan struct{}), - - // buffered for correctness & not leaking goroutines: - resc: make(chan error, numWorkers), - } - defer close(w.donec) - - for i := 0; i < numWorkers; i++ { - wg.Add(1) - go w.doWork(&wg) - } - todo := []walkItem{{dir: root}} - out := 0 - for { - workc := w.workc - var workItem walkItem - if len(todo) == 0 { - workc = nil - } else { - workItem = todo[len(todo)-1] - } - select { - case workc <- workItem: - todo = todo[:len(todo)-1] - out++ - case it := <-w.enqueuec: - todo = append(todo, it) - case err := <-w.resc: - out-- - if err != nil { - return err - } - if out == 0 && len(todo) == 0 { - // It's safe to quit here, as long as the buffered - // enqueue channel isn't also readable, which might - // happen if the worker sends both another unit of - // work and its result before the other select was - // scheduled and both w.resc and w.enqueuec were - // readable. - select { - case it := <-w.enqueuec: - todo = append(todo, it) - default: - return nil - } - } - } - } -} - -// doWork reads directories as instructed (via workc) and runs the -// user's callback function. -func (w *walker) doWork(wg *sync.WaitGroup) { - defer wg.Done() - for { - select { - case <-w.donec: - return - case it := <-w.workc: - select { - case <-w.donec: - return - case w.resc <- w.walk(it.dir, !it.callbackDone): - } - } - } -} - -type walker struct { - fn func(path string, typ os.FileMode) error - - donec chan struct{} // closed on fastWalk's return - workc chan walkItem // to workers - enqueuec chan walkItem // from workers - resc chan error // from workers -} - -type walkItem struct { - dir string - callbackDone bool // callback already called; don't do it again -} - -func (w *walker) enqueue(it walkItem) { - select { - case w.enqueuec <- it: - case <-w.donec: - } -} - -func (w *walker) onDirEnt(dirName, baseName string, typ os.FileMode) error { - joined := dirName + string(os.PathSeparator) + baseName - if typ == os.ModeDir { - w.enqueue(walkItem{dir: joined}) - return nil - } - - err := w.fn(joined, typ) - if typ == os.ModeSymlink { - if err == ErrTraverseLink { - // Set callbackDone so we don't call it twice for both the - // symlink-as-symlink and the symlink-as-directory later: - w.enqueue(walkItem{dir: joined, callbackDone: true}) - return nil - } - if err == filepath.SkipDir { - // Permit SkipDir on symlinks too. - return nil - } - } - return err -} - -func (w *walker) walk(root string, runUserCallback bool) error { - if runUserCallback { - err := w.fn(root, os.ModeDir) - if err == filepath.SkipDir { - return nil - } - if err != nil { - return err - } - } - - return readDir(root, w.onDirEnt) -} diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_darwin.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_darwin.go deleted file mode 100644 index 0ca55e0d5..000000000 --- a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_darwin.go +++ /dev/null @@ -1,119 +0,0 @@ -// 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. - -//go:build darwin && cgo -// +build darwin,cgo - -package fastwalk - -/* -#include <dirent.h> - -// fastwalk_readdir_r wraps readdir_r so that we don't have to pass a dirent** -// result pointer which triggers CGO's "Go pointer to Go pointer" check unless -// we allocat the result dirent* with malloc. -// -// fastwalk_readdir_r returns 0 on success, -1 upon reaching the end of the -// directory, or a positive error number to indicate failure. -static int fastwalk_readdir_r(DIR *fd, struct dirent *entry) { - struct dirent *result; - int ret = readdir_r(fd, entry, &result); - if (ret == 0 && result == NULL) { - ret = -1; // EOF - } - return ret; -} -*/ -import "C" - -import ( - "os" - "syscall" - "unsafe" -) - -func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) error) error { - fd, err := openDir(dirName) - if err != nil { - return &os.PathError{Op: "opendir", Path: dirName, Err: err} - } - defer C.closedir(fd) - - skipFiles := false - var dirent syscall.Dirent - for { - ret := int(C.fastwalk_readdir_r(fd, (*C.struct_dirent)(unsafe.Pointer(&dirent)))) - if ret != 0 { - if ret == -1 { - break // EOF - } - if ret == int(syscall.EINTR) { - continue - } - return &os.PathError{Op: "readdir", Path: dirName, Err: syscall.Errno(ret)} - } - if dirent.Ino == 0 { - continue - } - typ := dtToType(dirent.Type) - if skipFiles && typ.IsRegular() { - continue - } - name := (*[len(syscall.Dirent{}.Name)]byte)(unsafe.Pointer(&dirent.Name))[:] - name = name[:dirent.Namlen] - for i, c := range name { - if c == 0 { - name = name[:i] - break - } - } - // Check for useless names before allocating a string. - if string(name) == "." || string(name) == ".." { - continue - } - if err := fn(dirName, string(name), typ); err != nil { - if err != ErrSkipFiles { - return err - } - skipFiles = true - } - } - - return nil -} - -func dtToType(typ uint8) os.FileMode { - switch typ { - case syscall.DT_BLK: - return os.ModeDevice - case syscall.DT_CHR: - return os.ModeDevice | os.ModeCharDevice - case syscall.DT_DIR: - return os.ModeDir - case syscall.DT_FIFO: - return os.ModeNamedPipe - case syscall.DT_LNK: - return os.ModeSymlink - case syscall.DT_REG: - return 0 - case syscall.DT_SOCK: - return os.ModeSocket - } - return ^os.FileMode(0) -} - -// openDir wraps opendir(3) and handles any EINTR errors. The returned *DIR -// needs to be closed with closedir(3). -func openDir(path string) (*C.DIR, error) { - name, err := syscall.BytePtrFromString(path) - if err != nil { - return nil, err - } - for { - fd, err := C.opendir((*C.char)(unsafe.Pointer(name))) - if err != syscall.EINTR { - return fd, err - } - } -} diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_fileno.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_fileno.go deleted file mode 100644 index d58595dbd..000000000 --- a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_fileno.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2016 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 freebsd || openbsd || netbsd -// +build freebsd openbsd netbsd - -package fastwalk - -import "syscall" - -func direntInode(dirent *syscall.Dirent) uint64 { - return uint64(dirent.Fileno) -} diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_ino.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_ino.go deleted file mode 100644 index d3922890b..000000000 --- a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_ino.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2016 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 (linux || (darwin && !cgo)) && !appengine -// +build linux darwin,!cgo -// +build !appengine - -package fastwalk - -import "syscall" - -func direntInode(dirent *syscall.Dirent) uint64 { - return dirent.Ino -} diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_bsd.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_bsd.go deleted file mode 100644 index 38a4db6af..000000000 --- a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_bsd.go +++ /dev/null @@ -1,14 +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 (darwin && !cgo) || freebsd || openbsd || netbsd -// +build darwin,!cgo freebsd openbsd netbsd - -package fastwalk - -import "syscall" - -func direntNamlen(dirent *syscall.Dirent) uint64 { - return uint64(dirent.Namlen) -} diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_linux.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_linux.go deleted file mode 100644 index c82e57df8..000000000 --- a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_linux.go +++ /dev/null @@ -1,29 +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 linux && !appengine -// +build linux,!appengine - -package fastwalk - -import ( - "bytes" - "syscall" - "unsafe" -) - -func direntNamlen(dirent *syscall.Dirent) uint64 { - const fixedHdr = uint16(unsafe.Offsetof(syscall.Dirent{}.Name)) - nameBuf := (*[unsafe.Sizeof(dirent.Name)]byte)(unsafe.Pointer(&dirent.Name[0])) - const nameBufLen = uint16(len(nameBuf)) - limit := dirent.Reclen - fixedHdr - if limit > nameBufLen { - limit = nameBufLen - } - nameLen := bytes.IndexByte(nameBuf[:limit], 0) - if nameLen < 0 { - panic("failed to find terminating 0 byte in dirent") - } - return uint64(nameLen) -} diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_portable.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_portable.go deleted file mode 100644 index 27e860243..000000000 --- a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_portable.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2016 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 appengine || (!linux && !darwin && !freebsd && !openbsd && !netbsd) -// +build appengine !linux,!darwin,!freebsd,!openbsd,!netbsd - -package fastwalk - -import ( - "os" -) - -// readDir calls fn for each directory entry in dirName. -// It does not descend into directories or follow symlinks. -// If fn returns a non-nil error, readDir returns with that error -// immediately. -func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) error) error { - fis, err := os.ReadDir(dirName) - if err != nil { - return err - } - skipFiles := false - for _, fi := range fis { - info, err := fi.Info() - if err != nil { - return err - } - if info.Mode().IsRegular() && skipFiles { - continue - } - if err := fn(dirName, fi.Name(), info.Mode()&os.ModeType); err != nil { - if err == ErrSkipFiles { - skipFiles = true - continue - } - return err - } - } - return nil -} diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_unix.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_unix.go deleted file mode 100644 index f12f1a734..000000000 --- a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_unix.go +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2016 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 (linux || freebsd || openbsd || netbsd || (darwin && !cgo)) && !appengine -// +build linux freebsd openbsd netbsd darwin,!cgo -// +build !appengine - -package fastwalk - -import ( - "fmt" - "os" - "syscall" - "unsafe" -) - -const blockSize = 8 << 10 - -// unknownFileMode is a sentinel (and bogus) os.FileMode -// value used to represent a syscall.DT_UNKNOWN Dirent.Type. -const unknownFileMode os.FileMode = os.ModeNamedPipe | os.ModeSocket | os.ModeDevice - -func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) error) error { - fd, err := open(dirName, 0, 0) - if err != nil { - return &os.PathError{Op: "open", Path: dirName, Err: err} - } - defer syscall.Close(fd) - - // The buffer must be at least a block long. - buf := make([]byte, blockSize) // stack-allocated; doesn't escape - bufp := 0 // starting read position in buf - nbuf := 0 // end valid data in buf - skipFiles := false - for { - if bufp >= nbuf { - bufp = 0 - nbuf, err = readDirent(fd, buf) - if err != nil { - return os.NewSyscallError("readdirent", err) - } - if nbuf <= 0 { - return nil - } - } - consumed, name, typ := parseDirEnt(buf[bufp:nbuf]) - bufp += consumed - if name == "" || name == "." || name == ".." { - continue - } - // Fallback for filesystems (like old XFS) that don't - // support Dirent.Type and have DT_UNKNOWN (0) there - // instead. - if typ == unknownFileMode { - fi, err := os.Lstat(dirName + "/" + name) - if err != nil { - // It got deleted in the meantime. - if os.IsNotExist(err) { - continue - } - return err - } - typ = fi.Mode() & os.ModeType - } - if skipFiles && typ.IsRegular() { - continue - } - if err := fn(dirName, name, typ); err != nil { - if err == ErrSkipFiles { - skipFiles = true - continue - } - return err - } - } -} - -func parseDirEnt(buf []byte) (consumed int, name string, typ os.FileMode) { - // golang.org/issue/37269 - dirent := &syscall.Dirent{} - copy((*[unsafe.Sizeof(syscall.Dirent{})]byte)(unsafe.Pointer(dirent))[:], buf) - if v := unsafe.Offsetof(dirent.Reclen) + unsafe.Sizeof(dirent.Reclen); uintptr(len(buf)) < v { - panic(fmt.Sprintf("buf size of %d smaller than dirent header size %d", len(buf), v)) - } - if len(buf) < int(dirent.Reclen) { - panic(fmt.Sprintf("buf size %d < record length %d", len(buf), dirent.Reclen)) - } - consumed = int(dirent.Reclen) - if direntInode(dirent) == 0 { // File absent in directory. - return - } - switch dirent.Type { - case syscall.DT_REG: - typ = 0 - case syscall.DT_DIR: - typ = os.ModeDir - case syscall.DT_LNK: - typ = os.ModeSymlink - case syscall.DT_BLK: - typ = os.ModeDevice - case syscall.DT_FIFO: - typ = os.ModeNamedPipe - case syscall.DT_SOCK: - typ = os.ModeSocket - case syscall.DT_UNKNOWN: - typ = unknownFileMode - default: - // Skip weird things. - // It's probably a DT_WHT (http://lwn.net/Articles/325369/) - // or something. Revisit if/when this package is moved outside - // of goimports. goimports only cares about regular files, - // symlinks, and directories. - return - } - - nameBuf := (*[unsafe.Sizeof(dirent.Name)]byte)(unsafe.Pointer(&dirent.Name[0])) - nameLen := direntNamlen(dirent) - - // Special cases for common things: - if nameLen == 1 && nameBuf[0] == '.' { - name = "." - } else if nameLen == 2 && nameBuf[0] == '.' && nameBuf[1] == '.' { - name = ".." - } else { - name = string(nameBuf[:nameLen]) - } - return -} - -// According to https://golang.org/doc/go1.14#runtime -// A consequence of the implementation of preemption is that on Unix systems, including Linux and macOS -// systems, programs built with Go 1.14 will receive more signals than programs built with earlier releases. -// -// This causes syscall.Open and syscall.ReadDirent sometimes fail with EINTR errors. -// We need to retry in this case. -func open(path string, mode int, perm uint32) (fd int, err error) { - for { - fd, err := syscall.Open(path, mode, perm) - if err != syscall.EINTR { - return fd, err - } - } -} - -func readDirent(fd int, buf []byte) (n int, err error) { - for { - nbuf, err := syscall.ReadDirent(fd, buf) - if err != syscall.EINTR { - return nbuf, err - } - } -} diff --git a/vendor/golang.org/x/tools/internal/gcimporter/iexport.go b/vendor/golang.org/x/tools/internal/gcimporter/iexport.go index 6103dd710..2ee8c7016 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/iexport.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/iexport.go @@ -24,7 +24,6 @@ import ( "golang.org/x/tools/go/types/objectpath" "golang.org/x/tools/internal/tokeninternal" - "golang.org/x/tools/internal/typeparams" ) // IExportShallow encodes "shallow" export data for the specified package. @@ -481,7 +480,7 @@ func (p *iexporter) doDecl(obj types.Object) { } // Function. - if typeparams.ForSignature(sig).Len() == 0 { + if sig.TypeParams().Len() == 0 { w.tag('F') } else { w.tag('G') @@ -494,7 +493,7 @@ func (p *iexporter) doDecl(obj types.Object) { // // While importing the type parameters, tparamList computes and records // their export name, so that it can be later used when writing the index. - if tparams := typeparams.ForSignature(sig); tparams.Len() > 0 { + if tparams := sig.TypeParams(); tparams.Len() > 0 { w.tparamList(obj.Name(), tparams, obj.Pkg()) } w.signature(sig) @@ -507,14 +506,14 @@ func (p *iexporter) doDecl(obj types.Object) { case *types.TypeName: t := obj.Type() - if tparam, ok := t.(*typeparams.TypeParam); ok { + if tparam, ok := t.(*types.TypeParam); ok { w.tag('P') w.pos(obj.Pos()) constraint := tparam.Constraint() if p.version >= iexportVersionGo1_18 { implicit := false if iface, _ := constraint.(*types.Interface); iface != nil { - implicit = typeparams.IsImplicit(iface) + implicit = iface.IsImplicit() } w.bool(implicit) } @@ -535,17 +534,17 @@ func (p *iexporter) doDecl(obj types.Object) { panic(internalErrorf("%s is not a defined type", t)) } - if typeparams.ForNamed(named).Len() == 0 { + if named.TypeParams().Len() == 0 { w.tag('T') } else { w.tag('U') } w.pos(obj.Pos()) - if typeparams.ForNamed(named).Len() > 0 { + if named.TypeParams().Len() > 0 { // While importing the type parameters, tparamList computes and records // their export name, so that it can be later used when writing the index. - w.tparamList(obj.Name(), typeparams.ForNamed(named), obj.Pkg()) + w.tparamList(obj.Name(), named.TypeParams(), obj.Pkg()) } underlying := obj.Type().Underlying() @@ -565,7 +564,7 @@ func (p *iexporter) doDecl(obj types.Object) { // Receiver type parameters are type arguments of the receiver type, so // their name must be qualified before exporting recv. - if rparams := typeparams.RecvTypeParams(sig); rparams.Len() > 0 { + if rparams := sig.RecvTypeParams(); rparams.Len() > 0 { prefix := obj.Name() + "." + m.Name() for i := 0; i < rparams.Len(); i++ { rparam := rparams.At(i) @@ -740,19 +739,19 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) { } switch t := t.(type) { case *types.Named: - if targs := typeparams.NamedTypeArgs(t); targs.Len() > 0 { + if targs := t.TypeArgs(); targs.Len() > 0 { w.startType(instanceType) // TODO(rfindley): investigate if this position is correct, and if it // matters. w.pos(t.Obj().Pos()) w.typeList(targs, pkg) - w.typ(typeparams.NamedTypeOrigin(t), pkg) + w.typ(t.Origin(), pkg) return } w.startType(definedType) w.qualifiedType(t.Obj()) - case *typeparams.TypeParam: + case *types.TypeParam: w.startType(typeParamType) w.qualifiedType(t.Obj()) @@ -868,7 +867,7 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) { w.signature(sig) } - case *typeparams.Union: + case *types.Union: w.startType(unionType) nt := t.Len() w.uint64(uint64(nt)) @@ -948,14 +947,14 @@ func (w *exportWriter) signature(sig *types.Signature) { } } -func (w *exportWriter) typeList(ts *typeparams.TypeList, pkg *types.Package) { +func (w *exportWriter) typeList(ts *types.TypeList, pkg *types.Package) { w.uint64(uint64(ts.Len())) for i := 0; i < ts.Len(); i++ { w.typ(ts.At(i), pkg) } } -func (w *exportWriter) tparamList(prefix string, list *typeparams.TypeParamList, pkg *types.Package) { +func (w *exportWriter) tparamList(prefix string, list *types.TypeParamList, pkg *types.Package) { ll := uint64(list.Len()) w.uint64(ll) for i := 0; i < list.Len(); i++ { @@ -973,7 +972,7 @@ const blankMarker = "$" // differs from its actual object name: it is prefixed with a qualifier, and // blank type parameter names are disambiguated by their index in the type // parameter list. -func tparamExportName(prefix string, tparam *typeparams.TypeParam) string { +func tparamExportName(prefix string, tparam *types.TypeParam) string { assert(prefix != "") name := tparam.Obj().Name() if name == "_" { diff --git a/vendor/golang.org/x/tools/internal/gcimporter/iimport.go b/vendor/golang.org/x/tools/internal/gcimporter/iimport.go index 8e64cf644..9fffa9ad0 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/iimport.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/iimport.go @@ -22,7 +22,6 @@ import ( "strings" "golang.org/x/tools/go/types/objectpath" - "golang.org/x/tools/internal/typeparams" ) type intReader struct { @@ -225,6 +224,7 @@ func iimportCommon(fset *token.FileSet, getPackages GetPackagesFunc, data []byte // Gather the relevant packages from the manifest. items := make([]GetPackagesItem, r.uint64()) + uniquePkgPaths := make(map[string]bool) for i := range items { pkgPathOff := r.uint64() pkgPath := p.stringAt(pkgPathOff) @@ -249,6 +249,12 @@ func iimportCommon(fset *token.FileSet, getPackages GetPackagesFunc, data []byte } items[i].nameIndex = nameIndex + + uniquePkgPaths[pkgPath] = true + } + // Debugging #63822; hypothesis: there are duplicate PkgPaths. + if len(uniquePkgPaths) != len(items) { + reportf("found duplicate PkgPaths while reading export data manifest: %v", items) } // Request packages all at once from the client, @@ -321,7 +327,7 @@ func iimportCommon(fset *token.FileSet, getPackages GetPackagesFunc, data []byte // Therefore, we defer calling SetConstraint there, and call it here instead // after all types are complete. for _, d := range p.later { - typeparams.SetTypeParamConstraint(d.t, d.constraint) + d.t.SetConstraint(d.constraint) } for _, typ := range p.interfaceList { @@ -339,7 +345,7 @@ func iimportCommon(fset *token.FileSet, getPackages GetPackagesFunc, data []byte } type setConstraintArgs struct { - t *typeparams.TypeParam + t *types.TypeParam constraint types.Type } @@ -549,7 +555,7 @@ func (r *importReader) obj(name string) { r.declare(types.NewConst(pos, r.currPkg, name, typ, val)) case 'F', 'G': - var tparams []*typeparams.TypeParam + var tparams []*types.TypeParam if tag == 'G' { tparams = r.tparamList() } @@ -566,7 +572,7 @@ func (r *importReader) obj(name string) { r.declare(obj) if tag == 'U' { tparams := r.tparamList() - typeparams.SetForNamed(named, tparams) + named.SetTypeParams(tparams) } underlying := r.p.typAt(r.uint64(), named).Underlying() @@ -583,12 +589,12 @@ func (r *importReader) obj(name string) { // typeparams being used in the method sig/body). base := baseType(recv.Type()) assert(base != nil) - targs := typeparams.NamedTypeArgs(base) - var rparams []*typeparams.TypeParam + targs := base.TypeArgs() + var rparams []*types.TypeParam if targs.Len() > 0 { - rparams = make([]*typeparams.TypeParam, targs.Len()) + rparams = make([]*types.TypeParam, targs.Len()) for i := range rparams { - rparams[i] = targs.At(i).(*typeparams.TypeParam) + rparams[i] = targs.At(i).(*types.TypeParam) } } msig := r.signature(recv, rparams, nil) @@ -606,7 +612,7 @@ func (r *importReader) obj(name string) { } name0 := tparamName(name) tn := types.NewTypeName(pos, r.currPkg, name0, nil) - t := typeparams.NewTypeParam(tn, nil) + t := types.NewTypeParam(tn, nil) // To handle recursive references to the typeparam within its // bound, save the partial type in tparamIndex before reading the bounds. @@ -622,7 +628,7 @@ func (r *importReader) obj(name string) { if iface == nil { errorf("non-interface constraint marked implicit") } - typeparams.MarkImplicit(iface) + iface.MarkImplicit() } // The constraint type may not be complete, if we // are in the middle of a type recursion involving type @@ -966,7 +972,7 @@ func (r *importReader) doType(base *types.Named) (res types.Type) { // The imported instantiated type doesn't include any methods, so // we must always use the methods of the base (orig) type. // TODO provide a non-nil *Environment - t, _ := typeparams.Instantiate(nil, baseType, targs, false) + t, _ := types.Instantiate(nil, baseType, targs, false) // Workaround for golang/go#61561. See the doc for instanceList for details. r.p.instanceList = append(r.p.instanceList, t) @@ -976,11 +982,11 @@ func (r *importReader) doType(base *types.Named) (res types.Type) { if r.p.version < iexportVersionGenerics { errorf("unexpected instantiation type") } - terms := make([]*typeparams.Term, r.uint64()) + terms := make([]*types.Term, r.uint64()) for i := range terms { - terms[i] = typeparams.NewTerm(r.bool(), r.typ()) + terms[i] = types.NewTerm(r.bool(), r.typ()) } - return typeparams.NewUnion(terms) + return types.NewUnion(terms) } } @@ -1008,23 +1014,23 @@ func (r *importReader) objectPathObject() types.Object { return obj } -func (r *importReader) signature(recv *types.Var, rparams []*typeparams.TypeParam, tparams []*typeparams.TypeParam) *types.Signature { +func (r *importReader) signature(recv *types.Var, rparams []*types.TypeParam, tparams []*types.TypeParam) *types.Signature { params := r.paramList() results := r.paramList() variadic := params.Len() > 0 && r.bool() - return typeparams.NewSignatureType(recv, rparams, tparams, params, results, variadic) + return types.NewSignatureType(recv, rparams, tparams, params, results, variadic) } -func (r *importReader) tparamList() []*typeparams.TypeParam { +func (r *importReader) tparamList() []*types.TypeParam { n := r.uint64() if n == 0 { return nil } - xs := make([]*typeparams.TypeParam, n) + xs := make([]*types.TypeParam, n) for i := range xs { // Note: the standard library importer is tolerant of nil types here, // though would panic in SetTypeParams. - xs[i] = r.typ().(*typeparams.TypeParam) + xs[i] = r.typ().(*types.TypeParam) } return xs } diff --git a/vendor/golang.org/x/tools/internal/gocommand/invoke.go b/vendor/golang.org/x/tools/internal/gocommand/invoke.go index 53cf66da0..55312522d 100644 --- a/vendor/golang.org/x/tools/internal/gocommand/invoke.go +++ b/vendor/golang.org/x/tools/internal/gocommand/invoke.go @@ -13,6 +13,7 @@ import ( "io" "log" "os" + "os/exec" "reflect" "regexp" "runtime" @@ -21,8 +22,6 @@ import ( "sync" "time" - exec "golang.org/x/sys/execabs" - "golang.org/x/tools/internal/event" "golang.org/x/tools/internal/event/keys" "golang.org/x/tools/internal/event/label" @@ -85,6 +84,7 @@ func (runner *Runner) RunPiped(ctx context.Context, inv Invocation, stdout, stde // RunRaw runs the invocation, serializing requests only if they fight over // go.mod changes. +// Postcondition: both error results have same nilness. func (runner *Runner) RunRaw(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) { ctx, done := event.Start(ctx, "gocommand.Runner.RunRaw", invLabels(inv)...) defer done() @@ -95,23 +95,24 @@ func (runner *Runner) RunRaw(ctx context.Context, inv Invocation) (*bytes.Buffer stdout, stderr, friendlyErr, err := runner.runConcurrent(ctx, inv) // If we encounter a load concurrency error, we need to retry serially. - if friendlyErr == nil || !modConcurrencyError.MatchString(friendlyErr.Error()) { - return stdout, stderr, friendlyErr, err + if friendlyErr != nil && modConcurrencyError.MatchString(friendlyErr.Error()) { + event.Error(ctx, "Load concurrency error, will retry serially", err) + + // Run serially by calling runPiped. + stdout.Reset() + stderr.Reset() + friendlyErr, err = runner.runPiped(ctx, inv, stdout, stderr) } - event.Error(ctx, "Load concurrency error, will retry serially", err) - // Run serially by calling runPiped. - stdout.Reset() - stderr.Reset() - friendlyErr, err = runner.runPiped(ctx, inv, stdout, stderr) return stdout, stderr, friendlyErr, err } +// Postcondition: both error results have same nilness. func (runner *Runner) runConcurrent(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) { // Wait for 1 worker to become available. select { case <-ctx.Done(): - return nil, nil, nil, ctx.Err() + return nil, nil, ctx.Err(), ctx.Err() case runner.inFlight <- struct{}{}: defer func() { <-runner.inFlight }() } @@ -121,6 +122,7 @@ func (runner *Runner) runConcurrent(ctx context.Context, inv Invocation) (*bytes return stdout, stderr, friendlyErr, err } +// Postcondition: both error results have same nilness. func (runner *Runner) runPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) (error, error) { // Make sure the runner is always initialized. runner.initialize() @@ -129,7 +131,7 @@ func (runner *Runner) runPiped(ctx context.Context, inv Invocation, stdout, stde // runPiped commands. select { case <-ctx.Done(): - return nil, ctx.Err() + return ctx.Err(), ctx.Err() case runner.serialized <- struct{}{}: defer func() { <-runner.serialized }() } @@ -139,7 +141,7 @@ func (runner *Runner) runPiped(ctx context.Context, inv Invocation, stdout, stde for i := 0; i < maxInFlight; i++ { select { case <-ctx.Done(): - return nil, ctx.Err() + return ctx.Err(), ctx.Err() case runner.inFlight <- struct{}{}: // Make sure we always "return" any workers we took. defer func() { <-runner.inFlight }() @@ -172,6 +174,7 @@ type Invocation struct { Logf func(format string, args ...interface{}) } +// Postcondition: both error results have same nilness. func (i *Invocation) runWithFriendlyError(ctx context.Context, stdout, stderr io.Writer) (friendlyError error, rawError error) { rawError = i.run(ctx, stdout, stderr) if rawError != nil { diff --git a/vendor/golang.org/x/tools/internal/gopathwalk/walk.go b/vendor/golang.org/x/tools/internal/gopathwalk/walk.go index 452e342c5..836151551 100644 --- a/vendor/golang.org/x/tools/internal/gopathwalk/walk.go +++ b/vendor/golang.org/x/tools/internal/gopathwalk/walk.go @@ -9,21 +9,27 @@ package gopathwalk import ( "bufio" "bytes" - "log" + "io" + "io/fs" "os" "path/filepath" + "runtime" "strings" + "sync" "time" - - "golang.org/x/tools/internal/fastwalk" ) // Options controls the behavior of a Walk call. type Options struct { // If Logf is non-nil, debug logging is enabled through this function. Logf func(format string, args ...interface{}) + // Search module caches. Also disables legacy goimports ignore rules. ModulesEnabled bool + + // Maximum number of concurrent calls to user-provided callbacks, + // or 0 for GOMAXPROCS. + Concurrency int } // RootType indicates the type of a Root. @@ -44,22 +50,28 @@ type Root struct { Type RootType } -// Walk walks Go source directories ($GOROOT, $GOPATH, etc) to find packages. -// For each package found, add will be called (concurrently) with the absolute +// Walk concurrently walks Go source directories ($GOROOT, $GOPATH, etc) to find packages. +// +// For each package found, add will be called with the absolute // paths of the containing source directory and the package directory. -// add will be called concurrently. +// +// Unlike filepath.WalkDir, Walk follows symbolic links +// (while guarding against cycles). func Walk(roots []Root, add func(root Root, dir string), opts Options) { WalkSkip(roots, add, func(Root, string) bool { return false }, opts) } -// WalkSkip walks Go source directories ($GOROOT, $GOPATH, etc) to find packages. -// For each package found, add will be called (concurrently) with the absolute +// WalkSkip concurrently walks Go source directories ($GOROOT, $GOPATH, etc) to +// find packages. +// +// For each package found, add will be called with the absolute // paths of the containing source directory and the package directory. -// For each directory that will be scanned, skip will be called (concurrently) +// For each directory that will be scanned, skip will be called // with the absolute paths of the containing source directory and the directory. // If skip returns false on a directory it will be processed. -// add will be called concurrently. -// skip will be called concurrently. +// +// Unlike filepath.WalkDir, WalkSkip follows symbolic links +// (while guarding against cycles). func WalkSkip(roots []Root, add func(root Root, dir string), skip func(root Root, dir string) bool, opts Options) { for _, root := range roots { walkDir(root, add, skip, opts) @@ -68,34 +80,51 @@ func WalkSkip(roots []Root, add func(root Root, dir string), skip func(root Root // walkDir creates a walker and starts fastwalk with this walker. func walkDir(root Root, add func(Root, string), skip func(root Root, dir string) bool, opts Options) { + if opts.Logf == nil { + opts.Logf = func(format string, args ...interface{}) {} + } if _, err := os.Stat(root.Path); os.IsNotExist(err) { - if opts.Logf != nil { - opts.Logf("skipping nonexistent directory: %v", root.Path) - } + opts.Logf("skipping nonexistent directory: %v", root.Path) return } start := time.Now() - if opts.Logf != nil { - opts.Logf("scanning %s", root.Path) + opts.Logf("scanning %s", root.Path) + + concurrency := opts.Concurrency + if concurrency == 0 { + // The walk be either CPU-bound or I/O-bound, depending on what the + // caller-supplied add function does and the details of the user's platform + // and machine. Rather than trying to fine-tune the concurrency level for a + // specific environment, we default to GOMAXPROCS: it is likely to be a good + // choice for a CPU-bound add function, and if it is instead I/O-bound, then + // dealing with I/O saturation is arguably the job of the kernel and/or + // runtime. (Oversaturating I/O seems unlikely to harm performance as badly + // as failing to saturate would.) + concurrency = runtime.GOMAXPROCS(0) } w := &walker{ root: root, add: add, skip: skip, opts: opts, + sem: make(chan struct{}, concurrency), } w.init() - if err := fastwalk.Walk(root.Path, w.walk); err != nil { - logf := opts.Logf - if logf == nil { - logf = log.Printf - } - logf("scanning directory %v: %v", root.Path, err) - } - if opts.Logf != nil { - opts.Logf("scanned %s in %v", root.Path, time.Since(start)) + w.sem <- struct{}{} + path := root.Path + if path == "" { + path = "." } + if fi, err := os.Lstat(path); err == nil { + w.walk(path, nil, fs.FileInfoToDirEntry(fi)) + } else { + w.opts.Logf("scanning directory %v: %v", root.Path, err) + } + <-w.sem + w.walking.Wait() + + opts.Logf("scanned %s in %v", root.Path, time.Since(start)) } // walker is the callback for fastwalk.Walk. @@ -105,7 +134,18 @@ type walker struct { skip func(Root, string) bool // The callback that will be invoked for every dir. dir is skipped if it returns true. opts Options // Options passed to Walk by the user. - ignoredDirs []os.FileInfo // The ignored directories, loaded from .goimportsignore files. + walking sync.WaitGroup + sem chan struct{} // Channel of semaphore tokens; send to acquire, receive to release. + ignoredDirs []string + + added sync.Map // map[string]bool +} + +// A symlinkList is a linked list of os.FileInfos for parent directories +// reached via symlinks. +type symlinkList struct { + info os.FileInfo + prev *symlinkList } // init initializes the walker based on its Options @@ -121,14 +161,8 @@ func (w *walker) init() { for _, p := range ignoredPaths { full := filepath.Join(w.root.Path, p) - if fi, err := os.Stat(full); err == nil { - w.ignoredDirs = append(w.ignoredDirs, fi) - if w.opts.Logf != nil { - w.opts.Logf("Directory added to ignore list: %s", full) - } - } else if w.opts.Logf != nil { - w.opts.Logf("Error statting ignored directory: %v", err) - } + w.ignoredDirs = append(w.ignoredDirs, full) + w.opts.Logf("Directory added to ignore list: %s", full) } } @@ -138,12 +172,10 @@ func (w *walker) init() { func (w *walker) getIgnoredDirs(path string) []string { file := filepath.Join(path, ".goimportsignore") slurp, err := os.ReadFile(file) - if w.opts.Logf != nil { - if err != nil { - w.opts.Logf("%v", err) - } else { - w.opts.Logf("Read %s", file) - } + if err != nil { + w.opts.Logf("%v", err) + } else { + w.opts.Logf("Read %s", file) } if err != nil { return nil @@ -162,9 +194,9 @@ func (w *walker) getIgnoredDirs(path string) []string { } // shouldSkipDir reports whether the file should be skipped or not. -func (w *walker) shouldSkipDir(fi os.FileInfo, dir string) bool { +func (w *walker) shouldSkipDir(dir string) bool { for _, ignoredDir := range w.ignoredDirs { - if os.SameFile(fi, ignoredDir) { + if dir == ignoredDir { return true } } @@ -176,85 +208,130 @@ func (w *walker) shouldSkipDir(fi os.FileInfo, dir string) bool { } // walk walks through the given path. -func (w *walker) walk(path string, typ os.FileMode) error { - if typ.IsRegular() { - dir := filepath.Dir(path) - if dir == w.root.Path && (w.root.Type == RootGOROOT || w.root.Type == RootGOPATH) { - // Doesn't make sense to have regular files - // directly in your $GOPATH/src or $GOROOT/src. - return fastwalk.ErrSkipFiles - } - if !strings.HasSuffix(path, ".go") { - return nil +// +// Errors are logged if w.opts.Logf is non-nil, but otherwise ignored. +func (w *walker) walk(path string, pathSymlinks *symlinkList, d fs.DirEntry) { + if d.Type()&os.ModeSymlink != 0 { + // Walk the symlink's target rather than the symlink itself. + // + // (Note that os.Stat, unlike the lower-lever os.Readlink, + // follows arbitrarily many layers of symlinks, so it will eventually + // reach either a non-symlink or a nonexistent target.) + // + // TODO(bcmills): 'go list all' itself ignores symlinks within GOROOT/src + // and GOPATH/src. Do we really need to traverse them here? If so, why? + + fi, err := os.Stat(path) + if err != nil { + w.opts.Logf("%v", err) + return } - w.add(w.root, dir) - return fastwalk.ErrSkipFiles - } - if typ == os.ModeDir { - base := filepath.Base(path) - if base == "" || base[0] == '.' || base[0] == '_' || - base == "testdata" || - (w.root.Type == RootGOROOT && w.opts.ModulesEnabled && base == "vendor") || - (!w.opts.ModulesEnabled && base == "node_modules") { - return filepath.SkipDir + // Avoid walking symlink cycles: if we have already followed a symlink to + // this directory as a parent of itself, don't follow it again. + // + // This doesn't catch the first time through a cycle, but it also minimizes + // the number of extra stat calls we make if we *don't* encounter a cycle. + // Since we don't actually expect to encounter symlink cycles in practice, + // this seems like the right tradeoff. + for parent := pathSymlinks; parent != nil; parent = parent.prev { + if os.SameFile(fi, parent.info) { + return + } } - fi, err := os.Lstat(path) - if err == nil && w.shouldSkipDir(fi, path) { - return filepath.SkipDir + + pathSymlinks = &symlinkList{ + info: fi, + prev: pathSymlinks, } - return nil + d = fs.FileInfoToDirEntry(fi) } - if typ == os.ModeSymlink { - base := filepath.Base(path) - if strings.HasPrefix(base, ".#") { - // Emacs noise. - return nil + + if d.Type().IsRegular() { + if !strings.HasSuffix(path, ".go") { + return } - if w.shouldTraverse(path) { - return fastwalk.ErrTraverseLink + + dir := filepath.Dir(path) + if dir == w.root.Path && (w.root.Type == RootGOROOT || w.root.Type == RootGOPATH) { + // Doesn't make sense to have regular files + // directly in your $GOPATH/src or $GOROOT/src. + // + // TODO(bcmills): there are many levels of directory within + // RootModuleCache where this also wouldn't make sense, + // Can we generalize this to any directory without a corresponding + // import path? + return } - } - return nil -} -// shouldTraverse reports whether the symlink fi, found in dir, -// should be followed. It makes sure symlinks were never visited -// before to avoid symlink loops. -func (w *walker) shouldTraverse(path string) bool { - ts, err := os.Stat(path) - if err != nil { - logf := w.opts.Logf - if logf == nil { - logf = log.Printf + if _, dup := w.added.LoadOrStore(dir, true); !dup { + w.add(w.root, dir) } - logf("%v", err) - return false } - if !ts.IsDir() { - return false + + if !d.IsDir() { + return + } + + base := filepath.Base(path) + if base == "" || base[0] == '.' || base[0] == '_' || + base == "testdata" || + (w.root.Type == RootGOROOT && w.opts.ModulesEnabled && base == "vendor") || + (!w.opts.ModulesEnabled && base == "node_modules") || + w.shouldSkipDir(path) { + return } - if w.shouldSkipDir(ts, filepath.Dir(path)) { - return false + + // Read the directory and walk its entries. + + f, err := os.Open(path) + if err != nil { + w.opts.Logf("%v", err) + return } - // Check for symlink loops by statting each directory component - // and seeing if any are the same file as ts. + defer f.Close() + for { - parent := filepath.Dir(path) - if parent == path { - // Made it to the root without seeing a cycle. - // Use this symlink. - return true - } - parentInfo, err := os.Stat(parent) + // We impose an arbitrary limit on the number of ReadDir results per + // directory to limit the amount of memory consumed for stale or upcoming + // directory entries. The limit trades off CPU (number of syscalls to read + // the whole directory) against RAM (reachable directory entries other than + // the one currently being processed). + // + // Since we process the directories recursively, we will end up maintaining + // a slice of entries for each level of the directory tree. + // (Compare https://go.dev/issue/36197.) + ents, err := f.ReadDir(1024) if err != nil { - return false + if err != io.EOF { + w.opts.Logf("%v", err) + } + break } - if os.SameFile(ts, parentInfo) { - // Cycle. Don't traverse. - return false + + for _, d := range ents { + nextPath := filepath.Join(path, d.Name()) + if d.IsDir() { + select { + case w.sem <- struct{}{}: + // Got a new semaphore token, so we can traverse the directory concurrently. + d := d + w.walking.Add(1) + go func() { + defer func() { + <-w.sem + w.walking.Done() + }() + w.walk(nextPath, pathSymlinks, d) + }() + continue + + default: + // No tokens available, so traverse serially. + } + } + + w.walk(nextPath, pathSymlinks, d) } - path = parent } - } diff --git a/vendor/golang.org/x/tools/internal/imports/fix.go b/vendor/golang.org/x/tools/internal/imports/fix.go index 01e8ba5fa..6a18f63a4 100644 --- a/vendor/golang.org/x/tools/internal/imports/fix.go +++ b/vendor/golang.org/x/tools/internal/imports/fix.go @@ -13,6 +13,7 @@ import ( "go/build" "go/parser" "go/token" + "go/types" "io/fs" "io/ioutil" "os" @@ -254,7 +255,7 @@ type pass struct { otherFiles []*ast.File // sibling files. // Intermediate state, generated by load. - existingImports map[string]*ImportInfo + existingImports map[string][]*ImportInfo allRefs references missingRefs references @@ -319,7 +320,7 @@ func (p *pass) importIdentifier(imp *ImportInfo) string { func (p *pass) load() ([]*ImportFix, bool) { p.knownPackages = map[string]*packageInfo{} p.missingRefs = references{} - p.existingImports = map[string]*ImportInfo{} + p.existingImports = map[string][]*ImportInfo{} // Load basic information about the file in question. p.allRefs = collectReferences(p.f) @@ -350,7 +351,7 @@ func (p *pass) load() ([]*ImportFix, bool) { } } for _, imp := range imports { - p.existingImports[p.importIdentifier(imp)] = imp + p.existingImports[p.importIdentifier(imp)] = append(p.existingImports[p.importIdentifier(imp)], imp) } // Find missing references. @@ -389,31 +390,33 @@ func (p *pass) fix() ([]*ImportFix, bool) { // Found everything, or giving up. Add the new imports and remove any unused. var fixes []*ImportFix - for _, imp := range p.existingImports { - // We deliberately ignore globals here, because we can't be sure - // they're in the same package. People do things like put multiple - // main packages in the same directory, and we don't want to - // remove imports if they happen to have the same name as a var in - // a different package. - if _, ok := p.allRefs[p.importIdentifier(imp)]; !ok { - fixes = append(fixes, &ImportFix{ - StmtInfo: *imp, - IdentName: p.importIdentifier(imp), - FixType: DeleteImport, - }) - continue - } + for _, identifierImports := range p.existingImports { + for _, imp := range identifierImports { + // We deliberately ignore globals here, because we can't be sure + // they're in the same package. People do things like put multiple + // main packages in the same directory, and we don't want to + // remove imports if they happen to have the same name as a var in + // a different package. + if _, ok := p.allRefs[p.importIdentifier(imp)]; !ok { + fixes = append(fixes, &ImportFix{ + StmtInfo: *imp, + IdentName: p.importIdentifier(imp), + FixType: DeleteImport, + }) + continue + } - // An existing import may need to update its import name to be correct. - if name := p.importSpecName(imp); name != imp.Name { - fixes = append(fixes, &ImportFix{ - StmtInfo: ImportInfo{ - Name: name, - ImportPath: imp.ImportPath, - }, - IdentName: p.importIdentifier(imp), - FixType: SetImportName, - }) + // An existing import may need to update its import name to be correct. + if name := p.importSpecName(imp); name != imp.Name { + fixes = append(fixes, &ImportFix{ + StmtInfo: ImportInfo{ + Name: name, + ImportPath: imp.ImportPath, + }, + IdentName: p.importIdentifier(imp), + FixType: SetImportName, + }) + } } } // Collecting fixes involved map iteration, so sort for stability. See @@ -698,20 +701,21 @@ func ScoreImportPaths(ctx context.Context, env *ProcessEnv, paths []string) (map return result, nil } -func PrimeCache(ctx context.Context, env *ProcessEnv) error { +func PrimeCache(ctx context.Context, resolver Resolver) error { // Fully scan the disk for directories, but don't actually read any Go files. callback := &scanCallback{ - rootFound: func(gopathwalk.Root) bool { - return true + rootFound: func(root gopathwalk.Root) bool { + // See getCandidatePkgs: walking GOROOT is apparently expensive and + // unnecessary. + return root.Type != gopathwalk.RootGOROOT }, dirFound: func(pkg *pkg) bool { return false }, - packageNameLoaded: func(pkg *pkg) bool { - return false - }, + // packageNameLoaded and exportsLoaded must never be called. } - return getCandidatePkgs(ctx, callback, "", "", env) + + return resolver.scan(ctx, callback) } func candidateImportName(pkg *pkg) string { @@ -825,16 +829,45 @@ func GetPackageExports(ctx context.Context, wrapped func(PackageExport), searchP return getCandidatePkgs(ctx, callback, filename, filePkg, env) } -var requiredGoEnvVars = []string{"GO111MODULE", "GOFLAGS", "GOINSECURE", "GOMOD", "GOMODCACHE", "GONOPROXY", "GONOSUMDB", "GOPATH", "GOPROXY", "GOROOT", "GOSUMDB", "GOWORK"} +// TODO(rfindley): we should depend on GOOS and GOARCH, to provide accurate +// imports when doing cross-platform development. +var requiredGoEnvVars = []string{ + "GO111MODULE", + "GOFLAGS", + "GOINSECURE", + "GOMOD", + "GOMODCACHE", + "GONOPROXY", + "GONOSUMDB", + "GOPATH", + "GOPROXY", + "GOROOT", + "GOSUMDB", + "GOWORK", +} // ProcessEnv contains environment variables and settings that affect the use of // the go command, the go/build package, etc. +// +// ...a ProcessEnv *also* overwrites its Env along with derived state in the +// form of the resolver. And because it is lazily initialized, an env may just +// be broken and unusable, but there is no way for the caller to detect that: +// all queries will just fail. +// +// TODO(rfindley): refactor this package so that this type (perhaps renamed to +// just Env or Config) is an immutable configuration struct, to be exchanged +// for an initialized object via a constructor that returns an error. Perhaps +// the signature should be `func NewResolver(*Env) (*Resolver, error)`, where +// resolver is a concrete type used for resolving imports. Via this +// refactoring, we can avoid the need to call ProcessEnv.init and +// ProcessEnv.GoEnv everywhere, and implicitly fix all the places where this +// these are misused. Also, we'd delegate the caller the decision of how to +// handle a broken environment. type ProcessEnv struct { GocmdRunner *gocommand.Runner BuildFlags []string ModFlag string - ModFile string // SkipPathInScan returns true if the path should be skipped from scans of // the RootCurrentModule root type. The function argument is a clean, @@ -844,7 +877,7 @@ type ProcessEnv struct { // Env overrides the OS environment, and can be used to specify // GOPROXY, GO111MODULE, etc. PATH cannot be set here, because // exec.Command will not honor it. - // Specifying all of RequiredGoEnvVars avoids a call to `go env`. + // Specifying all of requiredGoEnvVars avoids a call to `go env`. Env map[string]string WorkingDir string @@ -852,9 +885,17 @@ type ProcessEnv struct { // If Logf is non-nil, debug logging is enabled through this function. Logf func(format string, args ...interface{}) - initialized bool + // If set, ModCache holds a shared cache of directory info to use across + // multiple ProcessEnvs. + ModCache *DirInfoCache + + initialized bool // see TODO above - resolver Resolver + // resolver and resolverErr are lazily evaluated (see GetResolver). + // This is unclean, but see the big TODO in the docstring for ProcessEnv + // above: for now, we can't be sure that the ProcessEnv is fully initialized. + resolver Resolver + resolverErr error } func (e *ProcessEnv) goEnv() (map[string]string, error) { @@ -934,20 +975,31 @@ func (e *ProcessEnv) env() []string { } func (e *ProcessEnv) GetResolver() (Resolver, error) { - if e.resolver != nil { - return e.resolver, nil - } if err := e.init(); err != nil { return nil, err } - if len(e.Env["GOMOD"]) == 0 && len(e.Env["GOWORK"]) == 0 { - e.resolver = newGopathResolver(e) - return e.resolver, nil + + if e.resolver == nil && e.resolverErr == nil { + // TODO(rfindley): we should only use a gopathResolver here if the working + // directory is actually *in* GOPATH. (I seem to recall an open gopls issue + // for this behavior, but I can't find it). + // + // For gopls, we can optionally explicitly choose a resolver type, since we + // already know the view type. + if len(e.Env["GOMOD"]) == 0 && len(e.Env["GOWORK"]) == 0 { + e.resolver = newGopathResolver(e) + } else { + e.resolver, e.resolverErr = newModuleResolver(e, e.ModCache) + } } - e.resolver = newModuleResolver(e) - return e.resolver, nil + + return e.resolver, e.resolverErr } +// buildContext returns the build.Context to use for matching files. +// +// TODO(rfindley): support dynamic GOOS, GOARCH here, when doing cross-platform +// development. func (e *ProcessEnv) buildContext() (*build.Context, error) { ctx := build.Default goenv, err := e.goEnv() @@ -1027,15 +1079,23 @@ func addStdlibCandidates(pass *pass, refs references) error { type Resolver interface { // loadPackageNames loads the package names in importPaths. loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) + // scan works with callback to search for packages. See scanCallback for details. scan(ctx context.Context, callback *scanCallback) error + // loadExports returns the set of exported symbols in the package at dir. // loadExports may be called concurrently. loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []string, error) + // scoreImportPath returns the relevance for an import path. scoreImportPath(ctx context.Context, path string) float64 - ClearForNewScan() + // ClearForNewScan returns a new Resolver based on the receiver that has + // cleared its internal caches of directory contents. + // + // The new resolver should be primed and then set via + // [ProcessEnv.UpdateResolver]. + ClearForNewScan() Resolver } // A scanCallback controls a call to scan and receives its results. @@ -1118,7 +1178,7 @@ func addExternalCandidates(ctx context.Context, pass *pass, refs references, fil go func(pkgName string, symbols map[string]bool) { defer wg.Done() - found, err := findImport(ctx, pass, found[pkgName], pkgName, symbols, filename) + found, err := findImport(ctx, pass, found[pkgName], pkgName, symbols) if err != nil { firstErrOnce.Do(func() { @@ -1149,6 +1209,17 @@ func addExternalCandidates(ctx context.Context, pass *pass, refs references, fil }() for result := range results { + // Don't offer completions that would shadow predeclared + // names, such as github.com/coreos/etcd/error. + if types.Universe.Lookup(result.pkg.name) != nil { // predeclared + // Ideally we would skip this candidate only + // if the predeclared name is actually + // referenced by the file, but that's a lot + // trickier to compute and would still create + // an import that is likely to surprise the + // user before long. + continue + } pass.addCandidate(result.imp, result.pkg) } return firstErr @@ -1191,31 +1262,22 @@ func ImportPathToAssumedName(importPath string) string { type gopathResolver struct { env *ProcessEnv walked bool - cache *dirInfoCache + cache *DirInfoCache scanSema chan struct{} // scanSema prevents concurrent scans. } func newGopathResolver(env *ProcessEnv) *gopathResolver { r := &gopathResolver{ - env: env, - cache: &dirInfoCache{ - dirs: map[string]*directoryPackageInfo{}, - listeners: map[*int]cacheListener{}, - }, + env: env, + cache: NewDirInfoCache(), scanSema: make(chan struct{}, 1), } r.scanSema <- struct{}{} return r } -func (r *gopathResolver) ClearForNewScan() { - <-r.scanSema - r.cache = &dirInfoCache{ - dirs: map[string]*directoryPackageInfo{}, - listeners: map[*int]cacheListener{}, - } - r.walked = false - r.scanSema <- struct{}{} +func (r *gopathResolver) ClearForNewScan() Resolver { + return newGopathResolver(r.env) } func (r *gopathResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) { @@ -1536,7 +1598,7 @@ func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string, incl // findImport searches for a package with the given symbols. // If no package is found, findImport returns ("", false, nil) -func findImport(ctx context.Context, pass *pass, candidates []pkgDistance, pkgName string, symbols map[string]bool, filename string) (*pkg, error) { +func findImport(ctx context.Context, pass *pass, candidates []pkgDistance, pkgName string, symbols map[string]bool) (*pkg, error) { // Sort the candidates by their import package length, // assuming that shorter package names are better than long // ones. Note that this sorts by the de-vendored name, so diff --git a/vendor/golang.org/x/tools/internal/imports/imports.go b/vendor/golang.org/x/tools/internal/imports/imports.go index 58e637b90..660407548 100644 --- a/vendor/golang.org/x/tools/internal/imports/imports.go +++ b/vendor/golang.org/x/tools/internal/imports/imports.go @@ -236,7 +236,7 @@ func parse(fset *token.FileSet, filename string, src []byte, opt *Options) (*ast src = src[:len(src)-len("}\n")] // Gofmt has also indented the function body one level. // Remove that indent. - src = bytes.Replace(src, []byte("\n\t"), []byte("\n"), -1) + src = bytes.ReplaceAll(src, []byte("\n\t"), []byte("\n")) return matchSpace(orig, src) } return file, adjust, nil diff --git a/vendor/golang.org/x/tools/internal/imports/mod.go b/vendor/golang.org/x/tools/internal/imports/mod.go index 5f4d435d3..3d0f38f6c 100644 --- a/vendor/golang.org/x/tools/internal/imports/mod.go +++ b/vendor/golang.org/x/tools/internal/imports/mod.go @@ -23,49 +23,88 @@ import ( "golang.org/x/tools/internal/gopathwalk" ) -// ModuleResolver implements resolver for modules using the go command as little -// as feasible. +// Notes(rfindley): ModuleResolver appears to be heavily optimized for scanning +// as fast as possible, which is desirable for a call to goimports from the +// command line, but it doesn't work as well for gopls, where it suffers from +// slow startup (golang/go#44863) and intermittent hanging (golang/go#59216), +// both caused by populating the cache, albeit in slightly different ways. +// +// A high level list of TODOs: +// - Optimize the scan itself, as there is some redundancy statting and +// reading go.mod files. +// - Invert the relationship between ProcessEnv and Resolver (see the +// docstring of ProcessEnv). +// - Make it easier to use an external resolver implementation. +// +// Smaller TODOs are annotated in the code below. + +// ModuleResolver implements the Resolver interface for a workspace using +// modules. +// +// A goal of the ModuleResolver is to invoke the Go command as little as +// possible. To this end, it runs the Go command only for listing module +// information (i.e. `go list -m -e -json ...`). Package scanning, the process +// of loading package information for the modules, is implemented internally +// via the scan method. +// +// It has two types of state: the state derived from the go command, which +// is populated by init, and the state derived from scans, which is populated +// via scan. A root is considered scanned if it has been walked to discover +// directories. However, if the scan did not require additional information +// from the directory (such as package name or exports), the directory +// information itself may be partially populated. It will be lazily filled in +// as needed by scans, using the scanCallback. type ModuleResolver struct { - env *ProcessEnv - moduleCacheDir string - dummyVendorMod *gocommand.ModuleJSON // If vendoring is enabled, the pseudo-module that represents the /vendor directory. - roots []gopathwalk.Root - scanSema chan struct{} // scanSema prevents concurrent scans and guards scannedRoots. - scannedRoots map[gopathwalk.Root]bool - - initialized bool - mains []*gocommand.ModuleJSON - mainByDir map[string]*gocommand.ModuleJSON - modsByModPath []*gocommand.ModuleJSON // All modules, ordered by # of path components in module Path... - modsByDir []*gocommand.ModuleJSON // ...or number of path components in their Dir. - - // moduleCacheCache stores information about the module cache. - moduleCacheCache *dirInfoCache - otherCache *dirInfoCache + env *ProcessEnv + + // Module state, populated during construction + dummyVendorMod *gocommand.ModuleJSON // if vendoring is enabled, a pseudo-module to represent the /vendor directory + moduleCacheDir string // GOMODCACHE, inferred from GOPATH if unset + roots []gopathwalk.Root // roots to scan, in approximate order of importance + mains []*gocommand.ModuleJSON // main modules + mainByDir map[string]*gocommand.ModuleJSON // module information by dir, to join with roots + modsByModPath []*gocommand.ModuleJSON // all modules, ordered by # of path components in their module path + modsByDir []*gocommand.ModuleJSON // ...or by the number of path components in their Dir. + + // Scanning state, populated by scan + + // scanSema prevents concurrent scans, and guards scannedRoots and the cache + // fields below (though the caches themselves are concurrency safe). + // Receive to acquire, send to release. + scanSema chan struct{} + scannedRoots map[gopathwalk.Root]bool // if true, root has been walked + + // Caches of directory info, populated by scans and scan callbacks + // + // moduleCacheCache stores cached information about roots in the module + // cache, which are immutable and therefore do not need to be invalidated. + // + // otherCache stores information about all other roots (even GOROOT), which + // may change. + moduleCacheCache *DirInfoCache + otherCache *DirInfoCache } -func newModuleResolver(e *ProcessEnv) *ModuleResolver { +// newModuleResolver returns a new module-aware goimports resolver. +// +// Note: use caution when modifying this constructor: changes must also be +// reflected in ModuleResolver.ClearForNewScan. +func newModuleResolver(e *ProcessEnv, moduleCacheCache *DirInfoCache) (*ModuleResolver, error) { r := &ModuleResolver{ env: e, scanSema: make(chan struct{}, 1), } - r.scanSema <- struct{}{} - return r -} - -func (r *ModuleResolver) init() error { - if r.initialized { - return nil - } + r.scanSema <- struct{}{} // release goenv, err := r.env.goEnv() if err != nil { - return err + return nil, err } + + // TODO(rfindley): can we refactor to share logic with r.env.invokeGo? inv := gocommand.Invocation{ BuildFlags: r.env.BuildFlags, ModFlag: r.env.ModFlag, - ModFile: r.env.ModFile, Env: r.env.env(), Logf: r.env.Logf, WorkingDir: r.env.WorkingDir, @@ -77,9 +116,12 @@ func (r *ModuleResolver) init() error { // Module vendor directories are ignored in workspace mode: // https://go.googlesource.com/proposal/+/master/design/45713-workspace.md if len(r.env.Env["GOWORK"]) == 0 { + // TODO(rfindley): VendorEnabled runs the go command to get GOFLAGS, but + // they should be available from the ProcessEnv. Can we avoid the redundant + // invocation? vendorEnabled, mainModVendor, err = gocommand.VendorEnabled(context.TODO(), inv, r.env.GocmdRunner) if err != nil { - return err + return nil, err } } @@ -100,19 +142,14 @@ func (r *ModuleResolver) init() error { // GO111MODULE=on. Other errors are fatal. if err != nil { if errMsg := err.Error(); !strings.Contains(errMsg, "working directory is not part of a module") && !strings.Contains(errMsg, "go.mod file not found") { - return err + return nil, err } } } - if gmc := r.env.Env["GOMODCACHE"]; gmc != "" { - r.moduleCacheDir = gmc - } else { - gopaths := filepath.SplitList(goenv["GOPATH"]) - if len(gopaths) == 0 { - return fmt.Errorf("empty GOPATH") - } - r.moduleCacheDir = filepath.Join(gopaths[0], "/pkg/mod") + r.moduleCacheDir = gomodcacheForEnv(goenv) + if r.moduleCacheDir == "" { + return nil, fmt.Errorf("cannot resolve GOMODCACHE") } sort.Slice(r.modsByModPath, func(i, j int) bool { @@ -141,7 +178,11 @@ func (r *ModuleResolver) init() error { } else { addDep := func(mod *gocommand.ModuleJSON) { if mod.Replace == nil { - // This is redundant with the cache, but we'll skip it cheaply enough. + // This is redundant with the cache, but we'll skip it cheaply enough + // when we encounter it in the module cache scan. + // + // Including it at a lower index in r.roots than the module cache dir + // helps prioritize matches from within existing dependencies. r.roots = append(r.roots, gopathwalk.Root{Path: mod.Dir, Type: gopathwalk.RootModuleCache}) } else { r.roots = append(r.roots, gopathwalk.Root{Path: mod.Dir, Type: gopathwalk.RootOther}) @@ -158,24 +199,40 @@ func (r *ModuleResolver) init() error { addDep(mod) } } + // If provided, share the moduleCacheCache. + // + // TODO(rfindley): The module cache is immutable. However, the loaded + // exports do depend on GOOS and GOARCH. Fortunately, the + // ProcessEnv.buildContext does not adjust these from build.DefaultContext + // (even though it should). So for now, this is OK to share, but we need to + // add logic for handling GOOS/GOARCH. + r.moduleCacheCache = moduleCacheCache r.roots = append(r.roots, gopathwalk.Root{Path: r.moduleCacheDir, Type: gopathwalk.RootModuleCache}) } r.scannedRoots = map[gopathwalk.Root]bool{} if r.moduleCacheCache == nil { - r.moduleCacheCache = &dirInfoCache{ - dirs: map[string]*directoryPackageInfo{}, - listeners: map[*int]cacheListener{}, - } - } - if r.otherCache == nil { - r.otherCache = &dirInfoCache{ - dirs: map[string]*directoryPackageInfo{}, - listeners: map[*int]cacheListener{}, - } + r.moduleCacheCache = NewDirInfoCache() } - r.initialized = true - return nil + r.otherCache = NewDirInfoCache() + return r, nil +} + +// gomodcacheForEnv returns the GOMODCACHE value to use based on the given env +// map, which must have GOMODCACHE and GOPATH populated. +// +// TODO(rfindley): this is defensive refactoring. +// 1. Is this even relevant anymore? Can't we just read GOMODCACHE. +// 2. Use this to separate module cache scanning from other scanning. +func gomodcacheForEnv(goenv map[string]string) string { + if gmc := goenv["GOMODCACHE"]; gmc != "" { + return gmc + } + gopaths := filepath.SplitList(goenv["GOPATH"]) + if len(gopaths) == 0 { + return "" + } + return filepath.Join(gopaths[0], "/pkg/mod") } func (r *ModuleResolver) initAllMods() error { @@ -206,30 +263,82 @@ func (r *ModuleResolver) initAllMods() error { return nil } -func (r *ModuleResolver) ClearForNewScan() { - <-r.scanSema - r.scannedRoots = map[gopathwalk.Root]bool{} - r.otherCache = &dirInfoCache{ - dirs: map[string]*directoryPackageInfo{}, - listeners: map[*int]cacheListener{}, +// ClearForNewScan invalidates the last scan. +// +// It preserves the set of roots, but forgets about the set of directories. +// Though it forgets the set of module cache directories, it remembers their +// contents, since they are assumed to be immutable. +func (r *ModuleResolver) ClearForNewScan() Resolver { + <-r.scanSema // acquire r, to guard scannedRoots + r2 := &ModuleResolver{ + env: r.env, + dummyVendorMod: r.dummyVendorMod, + moduleCacheDir: r.moduleCacheDir, + roots: r.roots, + mains: r.mains, + mainByDir: r.mainByDir, + modsByModPath: r.modsByModPath, + + scanSema: make(chan struct{}, 1), + scannedRoots: make(map[gopathwalk.Root]bool), + otherCache: NewDirInfoCache(), + moduleCacheCache: r.moduleCacheCache, + } + r2.scanSema <- struct{}{} // r2 must start released + // Invalidate root scans. We don't need to invalidate module cache roots, + // because they are immutable. + // (We don't support a use case where GOMODCACHE is cleaned in the middle of + // e.g. a gopls session: the user must restart gopls to get accurate + // imports.) + // + // Scanning for new directories in GOMODCACHE should be handled elsewhere, + // via a call to ScanModuleCache. + for _, root := range r.roots { + if root.Type == gopathwalk.RootModuleCache && r.scannedRoots[root] { + r2.scannedRoots[root] = true + } } - r.scanSema <- struct{}{} + r.scanSema <- struct{}{} // release r + return r2 } -func (r *ModuleResolver) ClearForNewMod() { - <-r.scanSema - *r = ModuleResolver{ - env: r.env, - moduleCacheCache: r.moduleCacheCache, - otherCache: r.otherCache, - scanSema: r.scanSema, +// ClearModuleInfo invalidates resolver state that depends on go.mod file +// contents (essentially, the output of go list -m -json ...). +// +// Notably, it does not forget directory contents, which are reset +// asynchronously via ClearForNewScan. +// +// If the ProcessEnv is a GOPATH environment, ClearModuleInfo is a no op. +// +// TODO(rfindley): move this to a new env.go, consolidating ProcessEnv methods. +func (e *ProcessEnv) ClearModuleInfo() { + if r, ok := e.resolver.(*ModuleResolver); ok { + resolver, resolverErr := newModuleResolver(e, e.ModCache) + if resolverErr == nil { + <-r.scanSema // acquire (guards caches) + resolver.moduleCacheCache = r.moduleCacheCache + resolver.otherCache = r.otherCache + r.scanSema <- struct{}{} // release + } + e.resolver = resolver + e.resolverErr = resolverErr } - r.init() - r.scanSema <- struct{}{} } -// findPackage returns the module and directory that contains the package at -// the given import path, or returns nil, "" if no module is in scope. +// UpdateResolver sets the resolver for the ProcessEnv to use in imports +// operations. Only for use with the result of [Resolver.ClearForNewScan]. +// +// TODO(rfindley): this awkward API is a result of the (arguably) inverted +// relationship between configuration and state described in the doc comment +// for [ProcessEnv]. +func (e *ProcessEnv) UpdateResolver(r Resolver) { + e.resolver = r + e.resolverErr = nil +} + +// findPackage returns the module and directory from within the main modules +// and their dependencies that contains the package at the given import path, +// or returns nil, "" if no module is in scope. func (r *ModuleResolver) findPackage(importPath string) (*gocommand.ModuleJSON, string) { // This can't find packages in the stdlib, but that's harmless for all // the existing code paths. @@ -295,10 +404,6 @@ func (r *ModuleResolver) cacheStore(info directoryPackageInfo) { } } -func (r *ModuleResolver) cacheKeys() []string { - return append(r.moduleCacheCache.Keys(), r.otherCache.Keys()...) -} - // cachePackageName caches the package name for a dir already in the cache. func (r *ModuleResolver) cachePackageName(info directoryPackageInfo) (string, error) { if info.rootType == gopathwalk.RootModuleCache { @@ -367,15 +472,15 @@ func (r *ModuleResolver) dirIsNestedModule(dir string, mod *gocommand.ModuleJSON return modDir != mod.Dir } -func (r *ModuleResolver) modInfo(dir string) (modDir string, modName string) { - readModName := func(modFile string) string { - modBytes, err := os.ReadFile(modFile) - if err != nil { - return "" - } - return modulePath(modBytes) +func readModName(modFile string) string { + modBytes, err := os.ReadFile(modFile) + if err != nil { + return "" } + return modulePath(modBytes) +} +func (r *ModuleResolver) modInfo(dir string) (modDir, modName string) { if r.dirInModuleCache(dir) { if matches := modCacheRegexp.FindStringSubmatch(dir); len(matches) == 3 { index := strings.Index(dir, matches[1]+"@"+matches[2]) @@ -409,11 +514,9 @@ func (r *ModuleResolver) dirInModuleCache(dir string) bool { } func (r *ModuleResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) { - if err := r.init(); err != nil { - return nil, err - } names := map[string]string{} for _, path := range importPaths { + // TODO(rfindley): shouldn't this use the dirInfoCache? _, packageDir := r.findPackage(path) if packageDir == "" { continue @@ -431,10 +534,6 @@ func (r *ModuleResolver) scan(ctx context.Context, callback *scanCallback) error ctx, done := event.Start(ctx, "imports.ModuleResolver.scan") defer done() - if err := r.init(); err != nil { - return err - } - processDir := func(info directoryPackageInfo) { // Skip this directory if we were not able to get the package information successfully. if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil { @@ -444,18 +543,18 @@ func (r *ModuleResolver) scan(ctx context.Context, callback *scanCallback) error if err != nil { return } - if !callback.dirFound(pkg) { return } + pkg.packageName, err = r.cachePackageName(info) if err != nil { return } - if !callback.packageNameLoaded(pkg) { return } + _, exports, err := r.loadExports(ctx, pkg, false) if err != nil { return @@ -494,7 +593,6 @@ func (r *ModuleResolver) scan(ctx context.Context, callback *scanCallback) error return packageScanned } - // Add anything new to the cache, and process it if we're still listening. add := func(root gopathwalk.Root, dir string) { r.cacheStore(r.scanDirForPackage(root, dir)) } @@ -509,9 +607,9 @@ func (r *ModuleResolver) scan(ctx context.Context, callback *scanCallback) error select { case <-ctx.Done(): return - case <-r.scanSema: + case <-r.scanSema: // acquire } - defer func() { r.scanSema <- struct{}{} }() + defer func() { r.scanSema <- struct{}{} }() // release // We have the lock on r.scannedRoots, and no other scans can run. for _, root := range roots { if ctx.Err() != nil { @@ -613,9 +711,6 @@ func (r *ModuleResolver) canonicalize(info directoryPackageInfo) (*pkg, error) { } func (r *ModuleResolver) loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []string, error) { - if err := r.init(); err != nil { - return "", nil, err - } if info, ok := r.cacheLoad(pkg.dir); ok && !includeTest { return r.cacheExports(ctx, r.env, info) } diff --git a/vendor/golang.org/x/tools/internal/imports/mod_cache.go b/vendor/golang.org/x/tools/internal/imports/mod_cache.go index 45690abbb..cfc546576 100644 --- a/vendor/golang.org/x/tools/internal/imports/mod_cache.go +++ b/vendor/golang.org/x/tools/internal/imports/mod_cache.go @@ -7,8 +7,12 @@ package imports import ( "context" "fmt" + "path" + "path/filepath" + "strings" "sync" + "golang.org/x/mod/module" "golang.org/x/tools/internal/gopathwalk" ) @@ -39,6 +43,8 @@ const ( exportsLoaded ) +// directoryPackageInfo holds (possibly incomplete) information about packages +// contained in a given directory. type directoryPackageInfo struct { // status indicates the extent to which this struct has been filled in. status directoryPackageStatus @@ -63,7 +69,10 @@ type directoryPackageInfo struct { packageName string // the package name, as declared in the source. // Set when status >= exportsLoaded. - + // TODO(rfindley): it's hard to see this, but exports depend implicitly on + // the default build context GOOS and GOARCH. + // + // We can make this explicit, and key exports by GOOS, GOARCH. exports []string } @@ -79,7 +88,7 @@ func (info *directoryPackageInfo) reachedStatus(target directoryPackageStatus) ( return true, nil } -// dirInfoCache is a concurrency safe map for storing information about +// DirInfoCache is a concurrency-safe map for storing information about // directories that may contain packages. // // The information in this cache is built incrementally. Entries are initialized in scan. @@ -92,21 +101,26 @@ func (info *directoryPackageInfo) reachedStatus(target directoryPackageStatus) ( // The information in the cache is not expected to change for the cache's // lifetime, so there is no protection against competing writes. Users should // take care not to hold the cache across changes to the underlying files. -// -// TODO(suzmue): consider other concurrency strategies and data structures (RWLocks, sync.Map, etc) -type dirInfoCache struct { +type DirInfoCache struct { mu sync.Mutex // dirs stores information about packages in directories, keyed by absolute path. dirs map[string]*directoryPackageInfo listeners map[*int]cacheListener } +func NewDirInfoCache() *DirInfoCache { + return &DirInfoCache{ + dirs: make(map[string]*directoryPackageInfo), + listeners: make(map[*int]cacheListener), + } +} + type cacheListener func(directoryPackageInfo) // ScanAndListen calls listener on all the items in the cache, and on anything // newly added. The returned stop function waits for all in-flight callbacks to // finish and blocks new ones. -func (d *dirInfoCache) ScanAndListen(ctx context.Context, listener cacheListener) func() { +func (d *DirInfoCache) ScanAndListen(ctx context.Context, listener cacheListener) func() { ctx, cancel := context.WithCancel(ctx) // Flushing out all the callbacks is tricky without knowing how many there @@ -162,8 +176,10 @@ func (d *dirInfoCache) ScanAndListen(ctx context.Context, listener cacheListener } // Store stores the package info for dir. -func (d *dirInfoCache) Store(dir string, info directoryPackageInfo) { +func (d *DirInfoCache) Store(dir string, info directoryPackageInfo) { d.mu.Lock() + // TODO(rfindley, golang/go#59216): should we overwrite an existing entry? + // That seems incorrect as the cache should be idempotent. _, old := d.dirs[dir] d.dirs[dir] = &info var listeners []cacheListener @@ -180,7 +196,7 @@ func (d *dirInfoCache) Store(dir string, info directoryPackageInfo) { } // Load returns a copy of the directoryPackageInfo for absolute directory dir. -func (d *dirInfoCache) Load(dir string) (directoryPackageInfo, bool) { +func (d *DirInfoCache) Load(dir string) (directoryPackageInfo, bool) { d.mu.Lock() defer d.mu.Unlock() info, ok := d.dirs[dir] @@ -191,7 +207,7 @@ func (d *dirInfoCache) Load(dir string) (directoryPackageInfo, bool) { } // Keys returns the keys currently present in d. -func (d *dirInfoCache) Keys() (keys []string) { +func (d *DirInfoCache) Keys() (keys []string) { d.mu.Lock() defer d.mu.Unlock() for key := range d.dirs { @@ -200,7 +216,7 @@ func (d *dirInfoCache) Keys() (keys []string) { return keys } -func (d *dirInfoCache) CachePackageName(info directoryPackageInfo) (string, error) { +func (d *DirInfoCache) CachePackageName(info directoryPackageInfo) (string, error) { if loaded, err := info.reachedStatus(nameLoaded); loaded { return info.packageName, err } @@ -213,7 +229,7 @@ func (d *dirInfoCache) CachePackageName(info directoryPackageInfo) (string, erro return info.packageName, info.err } -func (d *dirInfoCache) CacheExports(ctx context.Context, env *ProcessEnv, info directoryPackageInfo) (string, []string, error) { +func (d *DirInfoCache) CacheExports(ctx context.Context, env *ProcessEnv, info directoryPackageInfo) (string, []string, error) { if reached, _ := info.reachedStatus(exportsLoaded); reached { return info.packageName, info.exports, info.err } @@ -234,3 +250,81 @@ func (d *dirInfoCache) CacheExports(ctx context.Context, env *ProcessEnv, info d d.Store(info.dir, info) return info.packageName, info.exports, info.err } + +// ScanModuleCache walks the given directory, which must be a GOMODCACHE value, +// for directory package information, storing the results in cache. +func ScanModuleCache(dir string, cache *DirInfoCache, logf func(string, ...any)) { + // Note(rfindley): it's hard to see, but this function attempts to implement + // just the side effects on cache of calling PrimeCache with a ProcessEnv + // that has the given dir as its GOMODCACHE. + // + // Teasing out the control flow, we see that we can avoid any handling of + // vendor/ and can infer module info entirely from the path, simplifying the + // logic here. + + root := gopathwalk.Root{ + Path: filepath.Clean(dir), + Type: gopathwalk.RootModuleCache, + } + + directoryInfo := func(root gopathwalk.Root, dir string) directoryPackageInfo { + // This is a copy of ModuleResolver.scanDirForPackage, trimmed down to + // logic that applies to a module cache directory. + + subdir := "" + if dir != root.Path { + subdir = dir[len(root.Path)+len("/"):] + } + + matches := modCacheRegexp.FindStringSubmatch(subdir) + if len(matches) == 0 { + return directoryPackageInfo{ + status: directoryScanned, + err: fmt.Errorf("invalid module cache path: %v", subdir), + } + } + modPath, err := module.UnescapePath(filepath.ToSlash(matches[1])) + if err != nil { + if logf != nil { + logf("decoding module cache path %q: %v", subdir, err) + } + return directoryPackageInfo{ + status: directoryScanned, + err: fmt.Errorf("decoding module cache path %q: %v", subdir, err), + } + } + importPath := path.Join(modPath, filepath.ToSlash(matches[3])) + index := strings.Index(dir, matches[1]+"@"+matches[2]) + modDir := filepath.Join(dir[:index], matches[1]+"@"+matches[2]) + modName := readModName(filepath.Join(modDir, "go.mod")) + return directoryPackageInfo{ + status: directoryScanned, + dir: dir, + rootType: root.Type, + nonCanonicalImportPath: importPath, + moduleDir: modDir, + moduleName: modName, + } + } + + add := func(root gopathwalk.Root, dir string) { + info := directoryInfo(root, dir) + cache.Store(info.dir, info) + } + + skip := func(_ gopathwalk.Root, dir string) bool { + // Skip directories that have already been scanned. + // + // Note that gopathwalk only adds "package" directories, which must contain + // a .go file, and all such package directories in the module cache are + // immutable. So if we can load a dir, it can be skipped. + info, ok := cache.Load(dir) + if !ok { + return false + } + packageScanned, _ := info.reachedStatus(directoryScanned) + return packageScanned + } + + gopathwalk.WalkSkip([]gopathwalk.Root{root}, add, skip, gopathwalk.Options{Logf: logf, ModulesEnabled: true}) +} diff --git a/vendor/golang.org/x/tools/internal/imports/zstdlib.go b/vendor/golang.org/x/tools/internal/imports/zstdlib.go index 9f992c2be..8db24df2f 100644 --- a/vendor/golang.org/x/tools/internal/imports/zstdlib.go +++ b/vendor/golang.org/x/tools/internal/imports/zstdlib.go @@ -151,6 +151,7 @@ var stdlib = map[string][]string{ "cmp": { "Compare", "Less", + "Or", "Ordered", }, "compress/bzip2": { @@ -632,6 +633,8 @@ var stdlib = map[string][]string{ "NameMismatch", "NewCertPool", "NotAuthorizedToSign", + "OID", + "OIDFromInts", "PEMCipher", "PEMCipher3DES", "PEMCipherAES128", @@ -706,6 +709,7 @@ var stdlib = map[string][]string{ "LevelWriteCommitted", "Named", "NamedArg", + "Null", "NullBool", "NullByte", "NullFloat64", @@ -1921,6 +1925,7 @@ var stdlib = map[string][]string{ "R_LARCH_32", "R_LARCH_32_PCREL", "R_LARCH_64", + "R_LARCH_64_PCREL", "R_LARCH_ABS64_HI12", "R_LARCH_ABS64_LO20", "R_LARCH_ABS_HI20", @@ -1928,12 +1933,17 @@ var stdlib = map[string][]string{ "R_LARCH_ADD16", "R_LARCH_ADD24", "R_LARCH_ADD32", + "R_LARCH_ADD6", "R_LARCH_ADD64", "R_LARCH_ADD8", + "R_LARCH_ADD_ULEB128", + "R_LARCH_ALIGN", "R_LARCH_B16", "R_LARCH_B21", "R_LARCH_B26", + "R_LARCH_CFA", "R_LARCH_COPY", + "R_LARCH_DELETE", "R_LARCH_GNU_VTENTRY", "R_LARCH_GNU_VTINHERIT", "R_LARCH_GOT64_HI12", @@ -1953,6 +1963,7 @@ var stdlib = map[string][]string{ "R_LARCH_PCALA64_LO20", "R_LARCH_PCALA_HI20", "R_LARCH_PCALA_LO12", + "R_LARCH_PCREL20_S2", "R_LARCH_RELATIVE", "R_LARCH_RELAX", "R_LARCH_SOP_ADD", @@ -1983,8 +1994,10 @@ var stdlib = map[string][]string{ "R_LARCH_SUB16", "R_LARCH_SUB24", "R_LARCH_SUB32", + "R_LARCH_SUB6", "R_LARCH_SUB64", "R_LARCH_SUB8", + "R_LARCH_SUB_ULEB128", "R_LARCH_TLS_DTPMOD32", "R_LARCH_TLS_DTPMOD64", "R_LARCH_TLS_DTPREL32", @@ -2035,6 +2048,7 @@ var stdlib = map[string][]string{ "R_MIPS_LO16", "R_MIPS_NONE", "R_MIPS_PC16", + "R_MIPS_PC32", "R_MIPS_PJUMP", "R_MIPS_REL16", "R_MIPS_REL32", @@ -2952,6 +2966,8 @@ var stdlib = map[string][]string{ "RegisterName", }, "encoding/hex": { + "AppendDecode", + "AppendEncode", "Decode", "DecodeString", "DecodedLen", @@ -3233,6 +3249,7 @@ var stdlib = map[string][]string{ "TypeSpec", "TypeSwitchStmt", "UnaryExpr", + "Unparen", "ValueSpec", "Var", "Visitor", @@ -3492,6 +3509,7 @@ var stdlib = map[string][]string{ "XOR_ASSIGN", }, "go/types": { + "Alias", "ArgumentError", "Array", "AssertableTo", @@ -3559,6 +3577,7 @@ var stdlib = map[string][]string{ "MethodVal", "MissingMethod", "Named", + "NewAlias", "NewArray", "NewChan", "NewChecker", @@ -3627,6 +3646,7 @@ var stdlib = map[string][]string{ "Uint64", "Uint8", "Uintptr", + "Unalias", "Union", "Universe", "Unsafe", @@ -3643,6 +3663,11 @@ var stdlib = map[string][]string{ "WriteSignature", "WriteType", }, + "go/version": { + "Compare", + "IsValid", + "Lang", + }, "hash": { "Hash", "Hash32", @@ -4078,6 +4103,7 @@ var stdlib = map[string][]string{ "NewTextHandler", "Record", "SetDefault", + "SetLogLoggerLevel", "Source", "SourceKey", "String", @@ -4367,6 +4393,35 @@ var stdlib = map[string][]string{ "Uint64", "Zipf", }, + "math/rand/v2": { + "ChaCha8", + "ExpFloat64", + "Float32", + "Float64", + "Int", + "Int32", + "Int32N", + "Int64", + "Int64N", + "IntN", + "N", + "New", + "NewChaCha8", + "NewPCG", + "NewZipf", + "NormFloat64", + "PCG", + "Perm", + "Rand", + "Shuffle", + "Source", + "Uint32", + "Uint32N", + "Uint64", + "Uint64N", + "UintN", + "Zipf", + }, "mime": { "AddExtensionType", "BEncoding", @@ -4540,6 +4595,7 @@ var stdlib = map[string][]string{ "FS", "File", "FileServer", + "FileServerFS", "FileSystem", "Flusher", "Get", @@ -4566,6 +4622,7 @@ var stdlib = map[string][]string{ "MethodPut", "MethodTrace", "NewFileTransport", + "NewFileTransportFS", "NewRequest", "NewRequestWithContext", "NewResponseController", @@ -4599,6 +4656,7 @@ var stdlib = map[string][]string{ "Serve", "ServeContent", "ServeFile", + "ServeFileFS", "ServeMux", "ServeTLS", "Server", @@ -5106,6 +5164,7 @@ var stdlib = map[string][]string{ "StructTag", "Swapper", "Type", + "TypeFor", "TypeOf", "Uint", "Uint16", @@ -5342,6 +5401,7 @@ var stdlib = map[string][]string{ "CompactFunc", "Compare", "CompareFunc", + "Concat", "Contains", "ContainsFunc", "Delete", @@ -10824,6 +10884,7 @@ var stdlib = map[string][]string{ "Value", }, "testing/slogtest": { + "Run", "TestHandler", }, "text/scanner": { diff --git a/vendor/golang.org/x/tools/internal/packagesinternal/packages.go b/vendor/golang.org/x/tools/internal/packagesinternal/packages.go index d9950b1f0..44719de17 100644 --- a/vendor/golang.org/x/tools/internal/packagesinternal/packages.go +++ b/vendor/golang.org/x/tools/internal/packagesinternal/packages.go @@ -5,10 +5,6 @@ // Package packagesinternal exposes internal-only fields from go/packages. package packagesinternal -import ( - "golang.org/x/tools/internal/gocommand" -) - var GetForTest = func(p interface{}) string { return "" } var GetDepsErrors = func(p interface{}) []*PackageError { return nil } @@ -18,10 +14,6 @@ type PackageError struct { Err string // the error itself } -var GetGoCmdRunner = func(config interface{}) *gocommand.Runner { return nil } - -var SetGoCmdRunner = func(config interface{}, runner *gocommand.Runner) {} - var TypecheckCgo int var DepsErrors int // must be set as a LoadMode to call GetDepsErrors var ForTest int // must be set as a LoadMode to call GetForTest diff --git a/vendor/golang.org/x/tools/internal/testenv/testenv.go b/vendor/golang.org/x/tools/internal/testenv/testenv.go index 4d29ebe7f..511da9d7e 100644 --- a/vendor/golang.org/x/tools/internal/testenv/testenv.go +++ b/vendor/golang.org/x/tools/internal/testenv/testenv.go @@ -11,6 +11,7 @@ import ( "fmt" "go/build" "os" + "os/exec" "path/filepath" "runtime" "runtime/debug" @@ -21,8 +22,6 @@ import ( "golang.org/x/mod/modfile" "golang.org/x/tools/internal/goroot" - - exec "golang.org/x/sys/execabs" ) // packageMainIsDevel reports whether the module containing package main @@ -447,3 +446,32 @@ func NeedsLocalXTools(t testing.TB) { t.Skipf("skipping test: %s module path is %q, not %q", modFilePath, modulePath, want) } } + +// NeedsGoExperiment skips t if the current process environment does not +// have a GOEXPERIMENT flag set. +func NeedsGoExperiment(t testing.TB, flag string) { + t.Helper() + + goexp := os.Getenv("GOEXPERIMENT") + set := false + for _, f := range strings.Split(goexp, ",") { + if f == "" { + continue + } + if f == "none" { + // GOEXPERIMENT=none disables all experiment flags. + set = false + break + } + val := true + if strings.HasPrefix(f, "no") { + f, val = f[2:], false + } + if f == flag { + set = val + } + } + if !set { + t.Skipf("skipping test: flag %q is not set in GOEXPERIMENT=%q", flag, goexp) + } +} diff --git a/vendor/golang.org/x/tools/internal/typeparams/common.go b/vendor/golang.org/x/tools/internal/typeparams/common.go index d0d0649fe..cdab98853 100644 --- a/vendor/golang.org/x/tools/internal/typeparams/common.go +++ b/vendor/golang.org/x/tools/internal/typeparams/common.go @@ -42,7 +42,7 @@ func UnpackIndexExpr(n ast.Node) (x ast.Expr, lbrack token.Pos, indices []ast.Ex switch e := n.(type) { case *ast.IndexExpr: return e.X, e.Lbrack, []ast.Expr{e.Index}, e.Rbrack - case *IndexListExpr: + case *ast.IndexListExpr: return e.X, e.Lbrack, e.Indices, e.Rbrack } return nil, token.NoPos, nil, token.NoPos @@ -63,7 +63,7 @@ func PackIndexExpr(x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack toke Rbrack: rbrack, } default: - return &IndexListExpr{ + return &ast.IndexListExpr{ X: x, Lbrack: lbrack, Indices: indices, @@ -74,7 +74,7 @@ func PackIndexExpr(x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack toke // IsTypeParam reports whether t is a type parameter. func IsTypeParam(t types.Type) bool { - _, ok := t.(*TypeParam) + _, ok := t.(*types.TypeParam) return ok } @@ -100,11 +100,11 @@ func OriginMethod(fn *types.Func) *types.Func { // Receiver is a *types.Interface. return fn } - if ForNamed(named).Len() == 0 { + if named.TypeParams().Len() == 0 { // Receiver base has no type parameters, so we can avoid the lookup below. return fn } - orig := NamedTypeOrigin(named) + orig := named.Origin() gfn, _, _ := types.LookupFieldOrMethod(orig, true, fn.Pkg(), fn.Name()) // This is a fix for a gopls crash (#60628) due to a go/types bug (#60634). In: @@ -157,7 +157,7 @@ func OriginMethod(fn *types.Func) *types.Func { // // In this case, GenericAssignableTo reports that instantiations of Container // are assignable to the corresponding instantiation of Interface. -func GenericAssignableTo(ctxt *Context, V, T types.Type) bool { +func GenericAssignableTo(ctxt *types.Context, V, T types.Type) bool { // If V and T are not both named, or do not have matching non-empty type // parameter lists, fall back on types.AssignableTo. @@ -167,9 +167,9 @@ func GenericAssignableTo(ctxt *Context, V, T types.Type) bool { return types.AssignableTo(V, T) } - vtparams := ForNamed(VN) - ttparams := ForNamed(TN) - if vtparams.Len() == 0 || vtparams.Len() != ttparams.Len() || NamedTypeArgs(VN).Len() != 0 || NamedTypeArgs(TN).Len() != 0 { + vtparams := VN.TypeParams() + ttparams := TN.TypeParams() + if vtparams.Len() == 0 || vtparams.Len() != ttparams.Len() || VN.TypeArgs().Len() != 0 || TN.TypeArgs().Len() != 0 { return types.AssignableTo(V, T) } @@ -182,7 +182,7 @@ func GenericAssignableTo(ctxt *Context, V, T types.Type) bool { // Minor optimization: ensure we share a context across the two // instantiations below. if ctxt == nil { - ctxt = NewContext() + ctxt = types.NewContext() } var targs []types.Type @@ -190,12 +190,12 @@ func GenericAssignableTo(ctxt *Context, V, T types.Type) bool { targs = append(targs, vtparams.At(i)) } - vinst, err := Instantiate(ctxt, V, targs, true) + vinst, err := types.Instantiate(ctxt, V, targs, true) if err != nil { panic("type parameters should satisfy their own constraints") } - tinst, err := Instantiate(ctxt, T, targs, true) + tinst, err := types.Instantiate(ctxt, T, targs, true) if err != nil { return false } diff --git a/vendor/golang.org/x/tools/internal/typeparams/coretype.go b/vendor/golang.org/x/tools/internal/typeparams/coretype.go index 71248209e..7ea8840ea 100644 --- a/vendor/golang.org/x/tools/internal/typeparams/coretype.go +++ b/vendor/golang.org/x/tools/internal/typeparams/coretype.go @@ -108,15 +108,15 @@ func CoreType(T types.Type) types.Type { // // _NormalTerms makes no guarantees about the order of terms, except that it // is deterministic. -func _NormalTerms(typ types.Type) ([]*Term, error) { +func _NormalTerms(typ types.Type) ([]*types.Term, error) { switch typ := typ.(type) { - case *TypeParam: + case *types.TypeParam: return StructuralTerms(typ) - case *Union: + case *types.Union: return UnionTermSet(typ) case *types.Interface: return InterfaceTermSet(typ) default: - return []*Term{NewTerm(false, typ)}, nil + return []*types.Term{types.NewTerm(false, typ)}, nil } } diff --git a/vendor/golang.org/x/tools/internal/typeparams/enabled_go117.go b/vendor/golang.org/x/tools/internal/typeparams/enabled_go117.go deleted file mode 100644 index 18212390e..000000000 --- a/vendor/golang.org/x/tools/internal/typeparams/enabled_go117.go +++ /dev/null @@ -1,12 +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. - -//go:build !go1.18 -// +build !go1.18 - -package typeparams - -// Enabled reports whether type parameters are enabled in the current build -// environment. -const Enabled = false diff --git a/vendor/golang.org/x/tools/internal/typeparams/enabled_go118.go b/vendor/golang.org/x/tools/internal/typeparams/enabled_go118.go deleted file mode 100644 index d67148823..000000000 --- a/vendor/golang.org/x/tools/internal/typeparams/enabled_go118.go +++ /dev/null @@ -1,15 +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. - -//go:build go1.18 -// +build go1.18 - -package typeparams - -// Note: this constant is in a separate file as this is the only acceptable -// diff between the <1.18 API of this package and the 1.18 API. - -// Enabled reports whether type parameters are enabled in the current build -// environment. -const Enabled = true diff --git a/vendor/golang.org/x/tools/internal/typeparams/normalize.go b/vendor/golang.org/x/tools/internal/typeparams/normalize.go index 9c631b651..93c80fdc9 100644 --- a/vendor/golang.org/x/tools/internal/typeparams/normalize.go +++ b/vendor/golang.org/x/tools/internal/typeparams/normalize.go @@ -60,7 +60,7 @@ var ErrEmptyTypeSet = errors.New("empty type set") // // StructuralTerms makes no guarantees about the order of terms, except that it // is deterministic. -func StructuralTerms(tparam *TypeParam) ([]*Term, error) { +func StructuralTerms(tparam *types.TypeParam) ([]*types.Term, error) { constraint := tparam.Constraint() if constraint == nil { return nil, fmt.Errorf("%s has nil constraint", tparam) @@ -78,7 +78,7 @@ func StructuralTerms(tparam *TypeParam) ([]*Term, error) { // // See the documentation of StructuralTerms for more information on // normalization. -func InterfaceTermSet(iface *types.Interface) ([]*Term, error) { +func InterfaceTermSet(iface *types.Interface) ([]*types.Term, error) { return computeTermSet(iface) } @@ -88,11 +88,11 @@ func InterfaceTermSet(iface *types.Interface) ([]*Term, error) { // // See the documentation of StructuralTerms for more information on // normalization. -func UnionTermSet(union *Union) ([]*Term, error) { +func UnionTermSet(union *types.Union) ([]*types.Term, error) { return computeTermSet(union) } -func computeTermSet(typ types.Type) ([]*Term, error) { +func computeTermSet(typ types.Type) ([]*types.Term, error) { tset, err := computeTermSetInternal(typ, make(map[types.Type]*termSet), 0) if err != nil { return nil, err @@ -103,9 +103,9 @@ func computeTermSet(typ types.Type) ([]*Term, error) { if tset.terms.isAll() { return nil, nil } - var terms []*Term + var terms []*types.Term for _, term := range tset.terms { - terms = append(terms, NewTerm(term.tilde, term.typ)) + terms = append(terms, types.NewTerm(term.tilde, term.typ)) } return terms, nil } @@ -162,7 +162,7 @@ func computeTermSetInternal(t types.Type, seen map[types.Type]*termSet, depth in tset.terms = allTermlist for i := 0; i < u.NumEmbeddeds(); i++ { embedded := u.EmbeddedType(i) - if _, ok := embedded.Underlying().(*TypeParam); ok { + if _, ok := embedded.Underlying().(*types.TypeParam); ok { return nil, fmt.Errorf("invalid embedded type %T", embedded) } tset2, err := computeTermSetInternal(embedded, seen, depth+1) @@ -171,7 +171,7 @@ func computeTermSetInternal(t types.Type, seen map[types.Type]*termSet, depth in } tset.terms = tset.terms.intersect(tset2.terms) } - case *Union: + case *types.Union: // The term set of a union is the union of term sets of its terms. tset.terms = nil for i := 0; i < u.Len(); i++ { @@ -184,7 +184,7 @@ func computeTermSetInternal(t types.Type, seen map[types.Type]*termSet, depth in return nil, err } terms = tset2.terms - case *TypeParam, *Union: + case *types.TypeParam, *types.Union: // A stand-alone type parameter or union is not permitted as union // term. return nil, fmt.Errorf("invalid union term %T", t) @@ -199,7 +199,7 @@ func computeTermSetInternal(t types.Type, seen map[types.Type]*termSet, depth in return nil, fmt.Errorf("exceeded max term count %d", maxTermCount) } } - case *TypeParam: + case *types.TypeParam: panic("unreachable") default: // For all other types, the term set is just a single non-tilde term diff --git a/vendor/golang.org/x/tools/internal/typeparams/typeparams_go117.go b/vendor/golang.org/x/tools/internal/typeparams/typeparams_go117.go deleted file mode 100644 index 7ed86e171..000000000 --- a/vendor/golang.org/x/tools/internal/typeparams/typeparams_go117.go +++ /dev/null @@ -1,197 +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. - -//go:build !go1.18 -// +build !go1.18 - -package typeparams - -import ( - "go/ast" - "go/token" - "go/types" -) - -func unsupported() { - panic("type parameters are unsupported at this go version") -} - -// IndexListExpr is a placeholder type, as type parameters are not supported at -// this Go version. Its methods panic on use. -type IndexListExpr struct { - ast.Expr - X ast.Expr // expression - Lbrack token.Pos // position of "[" - Indices []ast.Expr // index expressions - Rbrack token.Pos // position of "]" -} - -// ForTypeSpec returns an empty field list, as type parameters on not supported -// at this Go version. -func ForTypeSpec(*ast.TypeSpec) *ast.FieldList { - return nil -} - -// ForFuncType returns an empty field list, as type parameters are not -// supported at this Go version. -func ForFuncType(*ast.FuncType) *ast.FieldList { - return nil -} - -// TypeParam is a placeholder type, as type parameters are not supported at -// this Go version. Its methods panic on use. -type TypeParam struct{ types.Type } - -func (*TypeParam) Index() int { unsupported(); return 0 } -func (*TypeParam) Constraint() types.Type { unsupported(); return nil } -func (*TypeParam) Obj() *types.TypeName { unsupported(); return nil } - -// TypeParamList is a placeholder for an empty type parameter list. -type TypeParamList struct{} - -func (*TypeParamList) Len() int { return 0 } -func (*TypeParamList) At(int) *TypeParam { unsupported(); return nil } - -// TypeList is a placeholder for an empty type list. -type TypeList struct{} - -func (*TypeList) Len() int { return 0 } -func (*TypeList) At(int) types.Type { unsupported(); return nil } - -// NewTypeParam is unsupported at this Go version, and panics. -func NewTypeParam(name *types.TypeName, constraint types.Type) *TypeParam { - unsupported() - return nil -} - -// SetTypeParamConstraint is unsupported at this Go version, and panics. -func SetTypeParamConstraint(tparam *TypeParam, constraint types.Type) { - unsupported() -} - -// NewSignatureType calls types.NewSignature, panicking if recvTypeParams or -// typeParams is non-empty. -func NewSignatureType(recv *types.Var, recvTypeParams, typeParams []*TypeParam, params, results *types.Tuple, variadic bool) *types.Signature { - if len(recvTypeParams) != 0 || len(typeParams) != 0 { - panic("signatures cannot have type parameters at this Go version") - } - return types.NewSignature(recv, params, results, variadic) -} - -// ForSignature returns an empty slice. -func ForSignature(*types.Signature) *TypeParamList { - return nil -} - -// RecvTypeParams returns a nil slice. -func RecvTypeParams(sig *types.Signature) *TypeParamList { - return nil -} - -// IsComparable returns false, as no interfaces are type-restricted at this Go -// version. -func IsComparable(*types.Interface) bool { - return false -} - -// IsMethodSet returns true, as no interfaces are type-restricted at this Go -// version. -func IsMethodSet(*types.Interface) bool { - return true -} - -// IsImplicit returns false, as no interfaces are implicit at this Go version. -func IsImplicit(*types.Interface) bool { - return false -} - -// MarkImplicit does nothing, because this Go version does not have implicit -// interfaces. -func MarkImplicit(*types.Interface) {} - -// ForNamed returns an empty type parameter list, as type parameters are not -// supported at this Go version. -func ForNamed(*types.Named) *TypeParamList { - return nil -} - -// SetForNamed panics if tparams is non-empty. -func SetForNamed(_ *types.Named, tparams []*TypeParam) { - if len(tparams) > 0 { - unsupported() - } -} - -// NamedTypeArgs returns nil. -func NamedTypeArgs(*types.Named) *TypeList { - return nil -} - -// NamedTypeOrigin is the identity method at this Go version. -func NamedTypeOrigin(named *types.Named) *types.Named { - return named -} - -// Term holds information about a structural type restriction. -type Term struct { - tilde bool - typ types.Type -} - -func (m *Term) Tilde() bool { return m.tilde } -func (m *Term) Type() types.Type { return m.typ } -func (m *Term) String() string { - pre := "" - if m.tilde { - pre = "~" - } - return pre + m.typ.String() -} - -// NewTerm is unsupported at this Go version, and panics. -func NewTerm(tilde bool, typ types.Type) *Term { - return &Term{tilde, typ} -} - -// Union is a placeholder type, as type parameters are not supported at this Go -// version. Its methods panic on use. -type Union struct{ types.Type } - -func (*Union) Len() int { return 0 } -func (*Union) Term(i int) *Term { unsupported(); return nil } - -// NewUnion is unsupported at this Go version, and panics. -func NewUnion(terms []*Term) *Union { - unsupported() - return nil -} - -// InitInstanceInfo is a noop at this Go version. -func InitInstanceInfo(*types.Info) {} - -// Instance is a placeholder type, as type parameters are not supported at this -// Go version. -type Instance struct { - TypeArgs *TypeList - Type types.Type -} - -// GetInstances returns a nil map, as type parameters are not supported at this -// Go version. -func GetInstances(info *types.Info) map[*ast.Ident]Instance { return nil } - -// Context is a placeholder type, as type parameters are not supported at -// this Go version. -type Context struct{} - -// NewContext returns a placeholder Context instance. -func NewContext() *Context { - return &Context{} -} - -// Instantiate is unsupported on this Go version, and panics. -func Instantiate(ctxt *Context, typ types.Type, targs []types.Type, validate bool) (types.Type, error) { - unsupported() - return nil, nil -} diff --git a/vendor/golang.org/x/tools/internal/typeparams/typeparams_go118.go b/vendor/golang.org/x/tools/internal/typeparams/typeparams_go118.go deleted file mode 100644 index cf301af1d..000000000 --- a/vendor/golang.org/x/tools/internal/typeparams/typeparams_go118.go +++ /dev/null @@ -1,151 +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. - -//go:build go1.18 -// +build go1.18 - -package typeparams - -import ( - "go/ast" - "go/types" -) - -// IndexListExpr is an alias for ast.IndexListExpr. -type IndexListExpr = ast.IndexListExpr - -// ForTypeSpec returns n.TypeParams. -func ForTypeSpec(n *ast.TypeSpec) *ast.FieldList { - if n == nil { - return nil - } - return n.TypeParams -} - -// ForFuncType returns n.TypeParams. -func ForFuncType(n *ast.FuncType) *ast.FieldList { - if n == nil { - return nil - } - return n.TypeParams -} - -// TypeParam is an alias for types.TypeParam -type TypeParam = types.TypeParam - -// TypeParamList is an alias for types.TypeParamList -type TypeParamList = types.TypeParamList - -// TypeList is an alias for types.TypeList -type TypeList = types.TypeList - -// NewTypeParam calls types.NewTypeParam. -func NewTypeParam(name *types.TypeName, constraint types.Type) *TypeParam { - return types.NewTypeParam(name, constraint) -} - -// SetTypeParamConstraint calls tparam.SetConstraint(constraint). -func SetTypeParamConstraint(tparam *TypeParam, constraint types.Type) { - tparam.SetConstraint(constraint) -} - -// NewSignatureType calls types.NewSignatureType. -func NewSignatureType(recv *types.Var, recvTypeParams, typeParams []*TypeParam, params, results *types.Tuple, variadic bool) *types.Signature { - return types.NewSignatureType(recv, recvTypeParams, typeParams, params, results, variadic) -} - -// ForSignature returns sig.TypeParams() -func ForSignature(sig *types.Signature) *TypeParamList { - return sig.TypeParams() -} - -// RecvTypeParams returns sig.RecvTypeParams(). -func RecvTypeParams(sig *types.Signature) *TypeParamList { - return sig.RecvTypeParams() -} - -// IsComparable calls iface.IsComparable(). -func IsComparable(iface *types.Interface) bool { - return iface.IsComparable() -} - -// IsMethodSet calls iface.IsMethodSet(). -func IsMethodSet(iface *types.Interface) bool { - return iface.IsMethodSet() -} - -// IsImplicit calls iface.IsImplicit(). -func IsImplicit(iface *types.Interface) bool { - return iface.IsImplicit() -} - -// MarkImplicit calls iface.MarkImplicit(). -func MarkImplicit(iface *types.Interface) { - iface.MarkImplicit() -} - -// ForNamed extracts the (possibly empty) type parameter object list from -// named. -func ForNamed(named *types.Named) *TypeParamList { - return named.TypeParams() -} - -// SetForNamed sets the type params tparams on n. Each tparam must be of -// dynamic type *types.TypeParam. -func SetForNamed(n *types.Named, tparams []*TypeParam) { - n.SetTypeParams(tparams) -} - -// NamedTypeArgs returns named.TypeArgs(). -func NamedTypeArgs(named *types.Named) *TypeList { - return named.TypeArgs() -} - -// NamedTypeOrigin returns named.Orig(). -func NamedTypeOrigin(named *types.Named) *types.Named { - return named.Origin() -} - -// Term is an alias for types.Term. -type Term = types.Term - -// NewTerm calls types.NewTerm. -func NewTerm(tilde bool, typ types.Type) *Term { - return types.NewTerm(tilde, typ) -} - -// Union is an alias for types.Union -type Union = types.Union - -// NewUnion calls types.NewUnion. -func NewUnion(terms []*Term) *Union { - return types.NewUnion(terms) -} - -// InitInstanceInfo initializes info to record information about type and -// function instances. -func InitInstanceInfo(info *types.Info) { - info.Instances = make(map[*ast.Ident]types.Instance) -} - -// Instance is an alias for types.Instance. -type Instance = types.Instance - -// GetInstances returns info.Instances. -func GetInstances(info *types.Info) map[*ast.Ident]Instance { - return info.Instances -} - -// Context is an alias for types.Context. -type Context = types.Context - -// NewContext calls types.NewContext. -func NewContext() *Context { - return types.NewContext() -} - -// Instantiate calls types.Instantiate. -func Instantiate(ctxt *Context, typ types.Type, targs []types.Type, validate bool) (types.Type, error) { - return types.Instantiate(ctxt, typ, targs, validate) -} diff --git a/vendor/golang.org/x/tools/internal/typesinternal/objectpath.go b/vendor/golang.org/x/tools/internal/typesinternal/objectpath.go deleted file mode 100644 index 5e96e8955..000000000 --- a/vendor/golang.org/x/tools/internal/typesinternal/objectpath.go +++ /dev/null @@ -1,24 +0,0 @@ -// 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 typesinternal - -import "go/types" - -// This file contains back doors that allow gopls to avoid method sorting when -// using the objectpath package. -// -// This is performance-critical in certain repositories, but changing the -// behavior of the objectpath package is still being discussed in -// golang/go#61443. If we decide to remove the sorting in objectpath we can -// simply delete these back doors. Otherwise, we should add a new API to -// objectpath that allows controlling the sorting. - -// SkipEncoderMethodSorting marks enc (which must be an *objectpath.Encoder) as -// not requiring sorted methods. -var SkipEncoderMethodSorting func(enc interface{}) - -// ObjectpathObject is like objectpath.Object, but allows suppressing method -// sorting. -var ObjectpathObject func(pkg *types.Package, p string, skipMethodSorting bool) (types.Object, error) diff --git a/vendor/golang.org/x/tools/internal/versions/gover.go b/vendor/golang.org/x/tools/internal/versions/gover.go new file mode 100644 index 000000000..bbabcd22e --- /dev/null +++ b/vendor/golang.org/x/tools/internal/versions/gover.go @@ -0,0 +1,172 @@ +// 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. + +// This is a fork of internal/gover for use by x/tools until +// go1.21 and earlier are no longer supported by x/tools. + +package versions + +import "strings" + +// A gover is a parsed Go gover: major[.Minor[.Patch]][kind[pre]] +// The numbers are the original decimal strings to avoid integer overflows +// and since there is very little actual math. (Probably overflow doesn't matter in practice, +// but at the time this code was written, there was an existing test that used +// go1.99999999999, which does not fit in an int on 32-bit platforms. +// The "big decimal" representation avoids the problem entirely.) +type gover struct { + major string // decimal + minor string // decimal or "" + patch string // decimal or "" + kind string // "", "alpha", "beta", "rc" + pre string // decimal or "" +} + +// compare returns -1, 0, or +1 depending on whether +// x < y, x == y, or x > y, interpreted as toolchain versions. +// The versions x and y must not begin with a "go" prefix: just "1.21" not "go1.21". +// Malformed versions compare less than well-formed versions and equal to each other. +// The language version "1.21" compares less than the release candidate and eventual releases "1.21rc1" and "1.21.0". +func compare(x, y string) int { + vx := parse(x) + vy := parse(y) + + if c := cmpInt(vx.major, vy.major); c != 0 { + return c + } + if c := cmpInt(vx.minor, vy.minor); c != 0 { + return c + } + if c := cmpInt(vx.patch, vy.patch); c != 0 { + return c + } + if c := strings.Compare(vx.kind, vy.kind); c != 0 { // "" < alpha < beta < rc + return c + } + if c := cmpInt(vx.pre, vy.pre); c != 0 { + return c + } + return 0 +} + +// lang returns the Go language version. For example, lang("1.2.3") == "1.2". +func lang(x string) string { + v := parse(x) + if v.minor == "" || v.major == "1" && v.minor == "0" { + return v.major + } + return v.major + "." + v.minor +} + +// isValid reports whether the version x is valid. +func isValid(x string) bool { + return parse(x) != gover{} +} + +// parse parses the Go version string x into a version. +// It returns the zero version if x is malformed. +func parse(x string) gover { + var v gover + + // Parse major version. + var ok bool + v.major, x, ok = cutInt(x) + if !ok { + return gover{} + } + if x == "" { + // Interpret "1" as "1.0.0". + v.minor = "0" + v.patch = "0" + return v + } + + // Parse . before minor version. + if x[0] != '.' { + return gover{} + } + + // Parse minor version. + v.minor, x, ok = cutInt(x[1:]) + if !ok { + return gover{} + } + if x == "" { + // Patch missing is same as "0" for older versions. + // Starting in Go 1.21, patch missing is different from explicit .0. + if cmpInt(v.minor, "21") < 0 { + v.patch = "0" + } + return v + } + + // Parse patch if present. + if x[0] == '.' { + v.patch, x, ok = cutInt(x[1:]) + if !ok || x != "" { + // Note that we are disallowing prereleases (alpha, beta, rc) for patch releases here (x != ""). + // Allowing them would be a bit confusing because we already have: + // 1.21 < 1.21rc1 + // But a prerelease of a patch would have the opposite effect: + // 1.21.3rc1 < 1.21.3 + // We've never needed them before, so let's not start now. + return gover{} + } + return v + } + + // Parse prerelease. + i := 0 + for i < len(x) && (x[i] < '0' || '9' < x[i]) { + if x[i] < 'a' || 'z' < x[i] { + return gover{} + } + i++ + } + if i == 0 { + return gover{} + } + v.kind, x = x[:i], x[i:] + if x == "" { + return v + } + v.pre, x, ok = cutInt(x) + if !ok || x != "" { + return gover{} + } + + return v +} + +// cutInt scans the leading decimal number at the start of x to an integer +// and returns that value and the rest of the string. +func cutInt(x string) (n, rest string, ok bool) { + i := 0 + for i < len(x) && '0' <= x[i] && x[i] <= '9' { + i++ + } + if i == 0 || x[0] == '0' && i != 1 { // no digits or unnecessary leading zero + return "", "", false + } + return x[:i], x[i:], true +} + +// cmpInt returns cmp.Compare(x, y) interpreting x and y as decimal numbers. +// (Copied from golang.org/x/mod/semver's compareInt.) +func cmpInt(x, y string) int { + if x == y { + return 0 + } + if len(x) < len(y) { + return -1 + } + if len(x) > len(y) { + return +1 + } + if x < y { + return -1 + } else { + return +1 + } +} diff --git a/vendor/golang.org/x/tools/internal/versions/types.go b/vendor/golang.org/x/tools/internal/versions/types.go new file mode 100644 index 000000000..562eef21f --- /dev/null +++ b/vendor/golang.org/x/tools/internal/versions/types.go @@ -0,0 +1,19 @@ +// 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 versions + +import ( + "go/types" +) + +// GoVersion returns the Go version of the type package. +// It returns zero if no version can be determined. +func GoVersion(pkg *types.Package) string { + // TODO(taking): x/tools can call GoVersion() [from 1.21] after 1.25. + if pkg, ok := any(pkg).(interface{ GoVersion() string }); ok { + return pkg.GoVersion() + } + return "" +} diff --git a/vendor/golang.org/x/tools/internal/versions/types_go121.go b/vendor/golang.org/x/tools/internal/versions/types_go121.go new file mode 100644 index 000000000..a7b79207a --- /dev/null +++ b/vendor/golang.org/x/tools/internal/versions/types_go121.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. + +//go:build !go1.22 +// +build !go1.22 + +package versions + +import ( + "go/ast" + "go/types" +) + +// FileVersions always reports the a file's Go version as the +// zero version at this Go version. +func FileVersions(info *types.Info, file *ast.File) string { return "" } + +// InitFileVersions is a noop at this Go version. +func InitFileVersions(*types.Info) {} diff --git a/vendor/golang.org/x/tools/internal/versions/types_go122.go b/vendor/golang.org/x/tools/internal/versions/types_go122.go new file mode 100644 index 000000000..7b9ba89a8 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/versions/types_go122.go @@ -0,0 +1,24 @@ +// 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. + +//go:build go1.22 +// +build go1.22 + +package versions + +import ( + "go/ast" + "go/types" +) + +// FileVersions maps a file to the file's semantic Go version. +// The reported version is the zero version if a version cannot be determined. +func FileVersions(info *types.Info, file *ast.File) string { + return info.FileVersions[file] +} + +// InitFileVersions initializes info to record Go versions for Go files. +func InitFileVersions(info *types.Info) { + info.FileVersions = make(map[*ast.File]string) +} diff --git a/vendor/golang.org/x/tools/internal/versions/versions.go b/vendor/golang.org/x/tools/internal/versions/versions.go new file mode 100644 index 000000000..e16f6c33a --- /dev/null +++ b/vendor/golang.org/x/tools/internal/versions/versions.go @@ -0,0 +1,52 @@ +// 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 versions + +// Note: If we use build tags to use go/versions when go >=1.22, +// we run into go.dev/issue/53737. Under some operations users would see an +// import of "go/versions" even if they would not compile the file. +// For example, during `go get -u ./...` (go.dev/issue/64490) we do not try to include +// For this reason, this library just a clone of go/versions for the moment. + +// Lang returns the Go language version for version x. +// If x is not a valid version, Lang returns the empty string. +// For example: +// +// Lang("go1.21rc2") = "go1.21" +// Lang("go1.21.2") = "go1.21" +// Lang("go1.21") = "go1.21" +// Lang("go1") = "go1" +// Lang("bad") = "" +// Lang("1.21") = "" +func Lang(x string) string { + v := lang(stripGo(x)) + if v == "" { + return "" + } + return x[:2+len(v)] // "go"+v without allocation +} + +// Compare returns -1, 0, or +1 depending on whether +// x < y, x == y, or x > y, interpreted as Go versions. +// The versions x and y must begin with a "go" prefix: "go1.21" not "1.21". +// Invalid versions, including the empty string, compare less than +// valid versions and equal to each other. +// The language version "go1.21" compares less than the +// release candidate and eventual releases "go1.21rc1" and "go1.21.0". +// Custom toolchain suffixes are ignored during comparison: +// "go1.21.0" and "go1.21.0-bigcorp" are equal. +func Compare(x, y string) int { return compare(stripGo(x), stripGo(y)) } + +// IsValid reports whether the version x is valid. +func IsValid(x string) bool { return isValid(stripGo(x)) } + +// stripGo converts from a "go1.21" version to a "1.21" version. +// If v does not start with "go", stripGo returns the empty string (a known invalid version). +func stripGo(v string) string { + if len(v) < 2 || v[:2] != "go" { + return "" + } + return v[2:] +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 27a89f017..60ec55682 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -70,7 +70,7 @@ github.com/Antonboom/errname/pkg/analyzer # github.com/Antonboom/nilnil v0.1.7 ## explicit; go 1.20 github.com/Antonboom/nilnil/pkg/analyzer -# github.com/Antonboom/testifylint v0.2.3 +# github.com/Antonboom/testifylint v1.1.2 ## explicit; go 1.20 github.com/Antonboom/testifylint/analyzer github.com/Antonboom/testifylint/internal/analysisutil @@ -84,19 +84,20 @@ github.com/BurntSushi/toml/internal # github.com/Djarvur/go-err113 v0.1.0 ## explicit; go 1.13 github.com/Djarvur/go-err113 -# github.com/GaijinEntertainment/go-exhaustruct/v3 v3.1.0 -## explicit; go 1.20 +# github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 +## explicit; go 1.21 github.com/GaijinEntertainment/go-exhaustruct/v3/analyzer -github.com/GaijinEntertainment/go-exhaustruct/v3/internal/fields +github.com/GaijinEntertainment/go-exhaustruct/v3/internal/comment github.com/GaijinEntertainment/go-exhaustruct/v3/internal/pattern +github.com/GaijinEntertainment/go-exhaustruct/v3/internal/structure # github.com/Masterminds/semver v1.5.0 ## explicit github.com/Masterminds/semver -# github.com/OpenPeeDeeP/depguard/v2 v2.1.0 +# github.com/OpenPeeDeeP/depguard/v2 v2.2.0 ## 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 +# github.com/alecthomas/go-check-sumtype v0.1.4 ## explicit; go 1.18 github.com/alecthomas/go-check-sumtype # github.com/alexkohler/nakedret/v2 v2.0.2 @@ -123,16 +124,16 @@ github.com/bkielbasa/cyclop/pkg/analyzer # github.com/blizzy78/varnamelen v0.8.0 ## explicit; go 1.16 github.com/blizzy78/varnamelen -# github.com/bombsimon/wsl/v3 v3.4.0 -## explicit; go 1.19 -github.com/bombsimon/wsl/v3 +# github.com/bombsimon/wsl/v4 v4.2.1 +## explicit; go 1.21 +github.com/bombsimon/wsl/v4 # github.com/breml/bidichk v0.2.7 ## explicit; go 1.20 github.com/breml/bidichk/pkg/bidichk # github.com/breml/errchkjson v0.3.6 ## explicit; go 1.20 github.com/breml/errchkjson -# github.com/butuzov/ireturn v0.2.2 +# github.com/butuzov/ireturn v0.3.0 ## explicit; go 1.18 github.com/butuzov/ireturn/analyzer github.com/butuzov/ireturn/analyzer/internal/config @@ -141,10 +142,10 @@ 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 +# github.com/catenacyber/perfsprint v0.6.0 ## explicit; go 1.20 github.com/catenacyber/perfsprint/analyzer -# github.com/ccojocar/zxcvbn-go v1.0.1 +# github.com/ccojocar/zxcvbn-go v1.0.2 ## explicit; go 1.20 github.com/ccojocar/zxcvbn-go github.com/ccojocar/zxcvbn-go/adjacency @@ -168,7 +169,7 @@ github.com/chavacava/garif ## explicit; go 1.18 github.com/curioswitch/go-reassign github.com/curioswitch/go-reassign/internal/analyzer -# github.com/daixiang0/gci v0.11.2 +# github.com/daixiang0/gci v0.12.1 ## explicit; go 1.18 github.com/daixiang0/gci/pkg/config github.com/daixiang0/gci/pkg/format @@ -194,10 +195,10 @@ github.com/dvyukov/go-fuzz/internal/go-fuzz-types # github.com/esimonov/ifshort v1.0.4 ## explicit; go 1.17 github.com/esimonov/ifshort/pkg/analyzer -# github.com/ettle/strcase v0.1.1 +# github.com/ettle/strcase v0.2.0 ## explicit; go 1.12 github.com/ettle/strcase -# github.com/fatih/color v1.15.0 +# github.com/fatih/color v1.16.0 ## explicit; go 1.17 github.com/fatih/color # github.com/fatih/structtag v1.2.0 @@ -215,10 +216,10 @@ github.com/fsnotify/fsnotify # github.com/fzipp/gocyclo v0.6.0 ## explicit; go 1.18 github.com/fzipp/gocyclo -# github.com/ghostiam/protogetter v0.2.3 +# github.com/ghostiam/protogetter v0.3.4 ## explicit; go 1.19 github.com/ghostiam/protogetter -# github.com/go-critic/go-critic v0.9.0 +# github.com/go-critic/go-critic v0.11.1 ## explicit; go 1.18 github.com/go-critic/go-critic/checkers github.com/go-critic/go-critic/checkers/internal/astwalk @@ -238,8 +239,8 @@ github.com/go-toolsmith/astcast # github.com/go-toolsmith/astcopy v1.1.0 ## explicit; go 1.16 github.com/go-toolsmith/astcopy -# github.com/go-toolsmith/astequal v1.1.0 -## explicit; go 1.16 +# github.com/go-toolsmith/astequal v1.2.0 +## explicit; go 1.18 github.com/go-toolsmith/astequal # github.com/go-toolsmith/astfmt v1.1.0 ## explicit; go 1.16 @@ -253,6 +254,9 @@ github.com/go-toolsmith/strparse # github.com/go-toolsmith/typep v1.1.0 ## explicit; go 1.16 github.com/go-toolsmith/typep +# github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 +## explicit; go 1.18 +github.com/go-viper/mapstructure/v2 # github.com/go-xmlfmt/xmlfmt v1.1.2 ## explicit github.com/go-xmlfmt/xmlfmt @@ -301,8 +305,8 @@ github.com/golangci/go-misc/deadcode github.com/golangci/gofmt/gofmt github.com/golangci/gofmt/gofmt/internal/diff github.com/golangci/gofmt/goimports -# github.com/golangci/golangci-lint v1.55.2 -## explicit; go 1.20 +# github.com/golangci/golangci-lint v1.56.2 +## explicit; go 1.21 github.com/golangci/golangci-lint/cmd/golangci-lint github.com/golangci/golangci-lint/internal/cache github.com/golangci/golangci-lint/internal/errorutil @@ -398,7 +402,7 @@ github.com/googleapis/gax-go/v2/apierror github.com/googleapis/gax-go/v2/apierror/internal/proto github.com/googleapis/gax-go/v2/callctx github.com/googleapis/gax-go/v2/internal -# github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 +# github.com/gordonklaus/ineffassign v0.1.0 ## explicit; go 1.14 github.com/gordonklaus/ineffassign/pkg/ineffassign # github.com/gorilla/handlers v1.5.2 @@ -417,12 +421,6 @@ github.com/gostaticanalysis/forcetypeassert # github.com/gostaticanalysis/nilerr v0.1.1 ## explicit; go 1.15 github.com/gostaticanalysis/nilerr -# github.com/hashicorp/errwrap v1.1.0 -## explicit -github.com/hashicorp/errwrap -# github.com/hashicorp/go-multierror v1.1.1 -## explicit; go 1.13 -github.com/hashicorp/go-multierror # github.com/hashicorp/go-version v1.6.0 ## explicit github.com/hashicorp/go-version @@ -449,7 +447,7 @@ github.com/ianlancetaylor/demangle # github.com/inconshreveable/mousetrap v1.1.0 ## explicit; go 1.18 github.com/inconshreveable/mousetrap -# github.com/jgautheron/goconst v1.6.0 +# github.com/jgautheron/goconst v1.7.0 ## explicit; go 1.13 github.com/jgautheron/goconst # github.com/jingyugao/rowserrcheck v1.1.1 @@ -458,11 +456,14 @@ github.com/jingyugao/rowserrcheck/passes/rowserr # github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af ## explicit; go 1.13 github.com/jirfag/go-printf-func-name/pkg/analyzer +# github.com/jjti/go-spancheck v0.5.2 +## explicit; go 1.20 +github.com/jjti/go-spancheck # github.com/julz/importas v0.1.0 ## explicit; go 1.15 github.com/julz/importas -# github.com/kisielk/errcheck v1.6.3 -## explicit; go 1.14 +# github.com/kisielk/errcheck v1.7.0 +## explicit; go 1.18 github.com/kisielk/errcheck/errcheck # github.com/kisielk/gotool v1.0.0 ## explicit @@ -474,7 +475,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.8 +# github.com/kunwardeep/paralleltest v1.0.9 ## explicit; go 1.17 github.com/kunwardeep/paralleltest/pkg/paralleltest # github.com/kyoh86/exportloopref v0.1.11 @@ -497,8 +498,8 @@ 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 v0.1.3 +## explicit; go 1.20 github.com/macabu/inamedparam # github.com/magiconair/properties v1.8.7 ## explicit; go 1.19 @@ -515,7 +516,7 @@ github.com/matoous/godox # github.com/mattn/go-colorable v0.1.13 ## explicit; go 1.15 github.com/mattn/go-colorable -# github.com/mattn/go-isatty v0.0.17 +# github.com/mattn/go-isatty v0.0.20 ## explicit; go 1.15 github.com/mattn/go-isatty # github.com/mattn/go-runewidth v0.0.14 @@ -524,7 +525,7 @@ github.com/mattn/go-runewidth # github.com/mbilski/exhaustivestruct v1.2.0 ## explicit; go 1.15 github.com/mbilski/exhaustivestruct/pkg/analyzer -# github.com/mgechev/revive v1.3.4 +# github.com/mgechev/revive v1.3.7 ## explicit; go 1.20 github.com/mgechev/revive/config github.com/mgechev/revive/formatter @@ -546,17 +547,18 @@ github.com/moricho/tparallel/pkg/ssainstr # github.com/nakabonne/nestif v0.3.1 ## explicit; go 1.15 github.com/nakabonne/nestif -# github.com/nishanths/exhaustive v0.11.0 +# github.com/nishanths/exhaustive v0.12.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.14.1 +# github.com/nunnatsa/ginkgolinter v0.15.2 ## explicit; go 1.20 github.com/nunnatsa/ginkgolinter github.com/nunnatsa/ginkgolinter/ginkgohandler github.com/nunnatsa/ginkgolinter/gomegahandler +github.com/nunnatsa/ginkgolinter/interfaces github.com/nunnatsa/ginkgolinter/reverseassertion github.com/nunnatsa/ginkgolinter/types github.com/nunnatsa/ginkgolinter/version @@ -573,7 +575,7 @@ github.com/pelletier/go-toml/v2/unstable # github.com/pmezard/go-difflib v1.0.0 ## explicit github.com/pmezard/go-difflib/difflib -# github.com/polyfloyd/go-errorlint v1.4.5 +# github.com/polyfloyd/go-errorlint v1.4.8 ## explicit; go 1.20 github.com/polyfloyd/go-errorlint/errorlint # github.com/prometheus/client_golang v1.19.0 @@ -640,11 +642,11 @@ 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.24.0 +# github.com/sashamelentyev/usestdlibvars v1.25.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.18.2 +# github.com/securego/gosec/v2 v2.19.0 ## explicit; go 1.20 github.com/securego/gosec/v2 github.com/securego/gosec/v2/analyzers @@ -674,8 +676,8 @@ github.com/sonatard/noctx/reqwithoutctx # github.com/sourcegraph/go-diff v0.7.0 ## explicit; go 1.14 github.com/sourcegraph/go-diff/diff -# github.com/spf13/afero v1.9.3 -## explicit; go 1.16 +# github.com/spf13/afero v1.11.0 +## explicit; go 1.19 github.com/spf13/afero github.com/spf13/afero/internal/common github.com/spf13/afero/mem @@ -724,7 +726,7 @@ 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.15 +# github.com/tetafro/godot v1.4.16 ## explicit; go 1.20 github.com/tetafro/godot # github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 @@ -756,8 +758,8 @@ github.com/ulikunitz/xz/lzma # 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 v0.1.0 +## explicit; go 1.20 github.com/ultraware/whitespace # github.com/uudashr/gocognit v1.1.2 ## explicit; go 1.16 @@ -773,13 +775,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.3 +# github.com/ykadowak/zerologlint v0.1.5 ## explicit; go 1.19 github.com/ykadowak/zerologlint # gitlab.com/bosi/decorder v0.4.1 ## explicit; go 1.20 gitlab.com/bosi/decorder -# go-simpler.org/sloglint v0.1.2 +# go-simpler.org/musttag v0.8.0 +## explicit; go 1.20 +go-simpler.org/musttag +# go-simpler.org/sloglint v0.4.0 ## explicit; go 1.20 go-simpler.org/sloglint # go.opencensus.io v0.24.0 @@ -831,9 +836,6 @@ go.opentelemetry.io/otel/metric/noop ## explicit; go 1.20 go.opentelemetry.io/otel/trace go.opentelemetry.io/otel/trace/embedded -# go.tmz.dev/musttag v0.7.2 -## explicit; go 1.19 -go.tmz.dev/musttag # go.uber.org/atomic v1.10.0 ## explicit; go 1.18 go.uber.org/atomic @@ -858,14 +860,13 @@ golang.org/x/crypto/cryptobyte/asn1 golang.org/x/crypto/hkdf golang.org/x/crypto/internal/alias golang.org/x/crypto/internal/poly1305 -# golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea +# golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc ## explicit; go 1.20 -golang.org/x/exp/constraints -golang.org/x/exp/slices -# golang.org/x/exp/typeparams v0.0.0-20230307190834-24139beb5833 +golang.org/x/exp/maps +# golang.org/x/exp/typeparams v0.0.0-20231219180239-dc181d75b848 ## explicit; go 1.18 golang.org/x/exp/typeparams -# golang.org/x/mod v0.13.0 +# golang.org/x/mod v0.15.0 ## explicit; go 1.18 golang.org/x/mod/internal/lazyregexp golang.org/x/mod/modfile @@ -903,7 +904,6 @@ golang.org/x/sync/semaphore # golang.org/x/sys v0.17.0 ## explicit; go 1.18 golang.org/x/sys/cpu -golang.org/x/sys/execabs golang.org/x/sys/unix golang.org/x/sys/windows # golang.org/x/text v0.14.0 @@ -918,7 +918,7 @@ golang.org/x/text/width # golang.org/x/time v0.5.0 ## explicit; go 1.18 golang.org/x/time/rate -# golang.org/x/tools v0.14.0 +# golang.org/x/tools v0.18.0 ## explicit; go 1.18 golang.org/x/tools/go/analysis golang.org/x/tools/go/analysis/analysistest @@ -984,6 +984,8 @@ golang.org/x/tools/go/ssa/ssautil golang.org/x/tools/go/types/objectpath golang.org/x/tools/go/types/typeutil golang.org/x/tools/imports +golang.org/x/tools/internal/aliases +golang.org/x/tools/internal/analysisinternal golang.org/x/tools/internal/diff golang.org/x/tools/internal/diff/lcs golang.org/x/tools/internal/event @@ -991,7 +993,6 @@ golang.org/x/tools/internal/event/core golang.org/x/tools/internal/event/keys golang.org/x/tools/internal/event/label golang.org/x/tools/internal/event/tag -golang.org/x/tools/internal/fastwalk golang.org/x/tools/internal/gcimporter golang.org/x/tools/internal/gocommand golang.org/x/tools/internal/gopathwalk @@ -1004,6 +1005,7 @@ golang.org/x/tools/internal/testenv golang.org/x/tools/internal/tokeninternal golang.org/x/tools/internal/typeparams golang.org/x/tools/internal/typesinternal +golang.org/x/tools/internal/versions golang.org/x/tools/txtar # google.golang.org/api v0.167.0 ## explicit; go 1.19 @@ -1224,8 +1226,8 @@ honnef.co/go/tools/staticcheck/fakereflect honnef.co/go/tools/staticcheck/fakexml honnef.co/go/tools/stylecheck honnef.co/go/tools/unused -# mvdan.cc/gofumpt v0.5.0 -## explicit; go 1.19 +# mvdan.cc/gofumpt v0.6.0 +## explicit; go 1.20 mvdan.cc/gofumpt/format mvdan.cc/gofumpt/internal/govendor/go/doc/comment mvdan.cc/gofumpt/internal/govendor/go/format @@ -1237,6 +1239,6 @@ mvdan.cc/interfacer/check # mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b ## explicit mvdan.cc/lint -# mvdan.cc/unparam v0.0.0-20230125043941-70a0ce6e7b95 -## explicit; go 1.18 +# mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 +## explicit; go 1.20 mvdan.cc/unparam/check diff --git a/vendor/mvdan.cc/gofumpt/format/format.go b/vendor/mvdan.cc/gofumpt/format/format.go index 787561781..7316eb78b 100644 --- a/vendor/mvdan.cc/gofumpt/format/format.go +++ b/vendor/mvdan.cc/gofumpt/format/format.go @@ -31,17 +31,20 @@ import ( // Options is the set of formatting options which affect gofumpt. type Options struct { - // LangVersion corresponds to the Go language version a piece of code is - // written in. The version is used to decide whether to apply formatting - // rules which require new language features. When inside a Go module, - // LangVersion should be: - // - // go mod edit -json | jq -r '.Go' + // TODO: link to the go/version docs once Go 1.22 is out. + // The old semver docs said: // // LangVersion is treated as a semantic version, which may start with a "v" // prefix. Like Go versions, it may also be incomplete; "1.14" is equivalent // to "1.14.0". When empty, it is equivalent to "v1", to not use language // features which could break programs. + + // LangVersion is the Go version a piece of code is written in. + // The version is used to decide whether to apply formatting + // rules which require new language features. + // When inside a Go module, LangVersion should typically be: + // + // go mod edit -json | jq -r '.Go' LangVersion string // ModulePath corresponds to the Go module path which contains the source @@ -82,22 +85,28 @@ func Source(src []byte, opts Options) ([]byte, error) { return buf.Bytes(), nil } +var rxGoVersionMajorMinor = regexp.MustCompile(`^(v|go)?([1-9]+)\.([0-9]+)`) + // File modifies a file and fset in place to follow gofumpt's format. The // changes might include manipulating adding or removing newlines in fset, // modifying the position of nodes, or modifying literal values. func File(fset *token.FileSet, file *ast.File, opts Options) { simplify(file) + // TODO: replace this hacky mess with go/version once we can rely on Go 1.22, + // as well as replacing our uses of the semver package. + // In particular, we likely want to allow any of 1.21, 1.21.2, or go1.21rc3, + // but we can rely on go/version.Lang to validate and normalize. if opts.LangVersion == "" { - opts.LangVersion = "v1" - } else if opts.LangVersion[0] != 'v' { - opts.LangVersion = "v" + opts.LangVersion + opts.LangVersion = "v1.0" } - if !semver.IsValid(opts.LangVersion) { - panic(fmt.Sprintf("invalid semver string: %q", opts.LangVersion)) + m := rxGoVersionMajorMinor.FindStringSubmatch(opts.LangVersion) + if m == nil { + panic(fmt.Sprintf("invalid Go version: %q", opts.LangVersion)) } + opts.LangVersion = "v" + m[2] + "." + m[3] f := &fumpter{ - File: fset.File(file.Pos()), + file: fset.File(file.Pos()), fset: fset, astFile: file, Options: opts, @@ -170,7 +179,7 @@ var rxOctalInteger = regexp.MustCompile(`\A0[0-7_]+\z`) type fumpter struct { Options - *token.File + file *token.File fset *token.FileSet astFile *ast.File @@ -217,7 +226,8 @@ func (f *fumpter) inlineComment(pos token.Pos) *ast.Comment { func (f *fumpter) addNewline(at token.Pos) { offset := f.Offset(at) - field := reflect.ValueOf(f.File).Elem().FieldByName("lines") + // TODO: replace with the new Lines method once we require Go 1.21 or later + field := reflect.ValueOf(f.file).Elem().FieldByName("lines") n := field.Len() lines := make([]int, 0, n+1) for i := 0; i < n; i++ { @@ -236,7 +246,7 @@ func (f *fumpter) addNewline(at token.Pos) { if offset >= 0 { lines = append(lines, offset) } - if !f.SetLines(lines) { + if !f.file.SetLines(lines) { panic(fmt.Sprintf("could not set lines to %v", lines)) } } @@ -245,7 +255,7 @@ func (f *fumpter) addNewline(at token.Pos) { // up on the same line. func (f *fumpter) removeLines(fromLine, toLine int) { for fromLine < toLine { - f.MergeLine(fromLine) + f.file.MergeLine(fromLine) toLine-- } } @@ -256,6 +266,18 @@ func (f *fumpter) removeLinesBetween(from, to token.Pos) { f.removeLines(f.Line(from)+1, f.Line(to)) } +func (f *fumpter) Position(p token.Pos) token.Position { + return f.file.PositionFor(p, false) +} + +func (f *fumpter) Line(p token.Pos) int { + return f.Position(p).Line +} + +func (f *fumpter) Offset(p token.Pos) int { + return f.file.Offset(p) +} + type byteCounter int func (b *byteCounter) Write(p []byte) (n int, err error) { @@ -285,14 +307,14 @@ func (f *fumpter) lineEnd(line int) token.Pos { if line < 1 { panic("illegal line number") } - total := f.LineCount() + total := f.file.LineCount() if line > total { panic("illegal line number") } if line == total { return f.astFile.End() } - return f.LineStart(line+1) - 1 + return f.file.LineStart(line+1) - 1 } // rxCommentDirective covers all common Go comment directives: @@ -305,10 +327,11 @@ func (f *fumpter) lineEnd(line int) token.Pos { // //sys(nb)? | syscall function wrapper prototypes // //nolint | nolint directive for golangci // //noinspection | noinspection directive for GoLand and friends +// //NOSONAR | NOSONAR directive for SonarQube // // Note that the "some-words:" matching expects a letter afterward, such as // "go:generate", to prevent matching false positives like "https://site". -var rxCommentDirective = regexp.MustCompile(`^([a-z-]+:[a-z]+|line\b|export\b|extern\b|sys(nb)?\b|no(lint|inspection)\b)`) +var rxCommentDirective = regexp.MustCompile(`^([a-z-]+:[a-z]+|line\b|export\b|extern\b|sys(nb)?\b|no(lint|inspection)\b)|NOSONAR\b`) func (f *fumpter) applyPre(c *astutil.Cursor) { f.splitLongLine(c) @@ -865,9 +888,9 @@ func (f *fumpter) stmts(list []ast.Stmt) { continue // not an if following another statement } as, ok := list[i-1].(*ast.AssignStmt) - if !ok || as.Tok != token.DEFINE || + if !ok || (as.Tok != token.DEFINE && as.Tok != token.ASSIGN) || !identEqual(as.Lhs[len(as.Lhs)-1], "err") { - continue // not "..., err := ..." + continue // not ", err :=" nor ", err =" } be, ok := ifs.Cond.(*ast.BinaryExpr) if !ok || ifs.Init != nil || ifs.Else != nil { diff --git a/vendor/mvdan.cc/gofumpt/internal/govendor/go/doc/comment/print.go b/vendor/mvdan.cc/gofumpt/internal/govendor/go/doc/comment/print.go index 4e9da3d1e..e1c070d5a 100644 --- a/vendor/mvdan.cc/gofumpt/internal/govendor/go/doc/comment/print.go +++ b/vendor/mvdan.cc/gofumpt/internal/govendor/go/doc/comment/print.go @@ -148,8 +148,6 @@ func (h *Heading) DefaultID() string { type commentPrinter struct { *Printer - headingPrefix string - needDoc map[string]bool } // Comment returns the standard Go formatting of the Doc, diff --git a/vendor/mvdan.cc/gofumpt/internal/govendor/go/doc/comment/std.go b/vendor/mvdan.cc/gofumpt/internal/govendor/go/doc/comment/std.go index 6786e4d7a..d128eda8c 100644 --- a/vendor/mvdan.cc/gofumpt/internal/govendor/go/doc/comment/std.go +++ b/vendor/mvdan.cc/gofumpt/internal/govendor/go/doc/comment/std.go @@ -10,6 +10,7 @@ package comment var stdPkgs = []string{ "bufio", "bytes", + "cmp", "context", "crypto", "embed", @@ -23,6 +24,7 @@ var stdPkgs = []string{ "image", "io", "log", + "maps", "math", "mime", "net", @@ -32,6 +34,7 @@ var stdPkgs = []string{ "reflect", "regexp", "runtime", + "slices", "sort", "strconv", "strings", diff --git a/vendor/mvdan.cc/gofumpt/internal/govendor/go/printer/comment.go b/vendor/mvdan.cc/gofumpt/internal/govendor/go/printer/comment.go index 5b9775dc8..1f0e7df9d 100644 --- a/vendor/mvdan.cc/gofumpt/internal/govendor/go/printer/comment.go +++ b/vendor/mvdan.cc/gofumpt/internal/govendor/go/printer/comment.go @@ -37,15 +37,16 @@ func formatDocComment(list []*ast.Comment) []*ast.Comment { kind = "//" var b strings.Builder for _, c := range list { - if !strings.HasPrefix(c.Text, "//") { + after, found := strings.CutPrefix(c.Text, "//") + if !found { return list } // Accumulate //go:build etc lines separately. - if isDirective(c.Text[2:]) { + if isDirective(after) { directives = append(directives, c) continue } - b.WriteString(strings.TrimPrefix(c.Text[2:], " ")) + b.WriteString(strings.TrimPrefix(after, " ")) b.WriteString("\n") } text = b.String() diff --git a/vendor/mvdan.cc/gofumpt/internal/govendor/go/printer/printer.go b/vendor/mvdan.cc/gofumpt/internal/govendor/go/printer/printer.go index 576f9690d..2ab0278b0 100644 --- a/vendor/mvdan.cc/gofumpt/internal/govendor/go/printer/printer.go +++ b/vendor/mvdan.cc/gofumpt/internal/govendor/go/printer/printer.go @@ -75,10 +75,11 @@ type printer struct { // white space). If there's a difference and SourcePos is set in // ConfigMode, //line directives are used in the output to restore // original source positions for a reader. - pos token.Position // current position in AST (source) space - out token.Position // current position in output space - last token.Position // value of pos after calling writeString - linePtr *int // if set, record out.Line for the next token in *linePtr + pos token.Position // current position in AST (source) space + out token.Position // current position in output space + last token.Position // value of pos after calling writeString + linePtr *int // if set, record out.Line for the next token in *linePtr + sourcePosErr error // if non-nil, the first error emitting a //line directive // The list of all source comments, in order of appearance. comments []*ast.CommentGroup // may be nil @@ -196,6 +197,13 @@ func (p *printer) lineFor(pos token.Pos) int { // writeLineDirective writes a //line directive if necessary. func (p *printer) writeLineDirective(pos token.Position) { if pos.IsValid() && (p.out.Line != pos.Line || p.out.Filename != pos.Filename) { + if strings.ContainsAny(pos.Filename, "\r\n") { + if p.sourcePosErr == nil { + p.sourcePosErr = fmt.Errorf("mvdan.cc/gofumpt/internal/govendor/go/printer: source filename contains unexpected newline character: %q", pos.Filename) + } + return + } + p.output = append(p.output, tabwriter.Escape) // protect '\n' in //line from tabwriter interpretation p.output = append(p.output, fmt.Sprintf("//line %s:%d\n", pos.Filename, pos.Line)...) p.output = append(p.output, tabwriter.Escape) @@ -804,7 +812,7 @@ func (p *printer) intersperseComments(next token.Position, tok token.Token) (wro return } -// whiteWhitespace writes the first n whitespace entries. +// writeWhitespace writes the first n whitespace entries. func (p *printer) writeWhitespace(n int) { // write entries for i := 0; i < n; i++ { @@ -1168,7 +1176,7 @@ func (p *printer) printNode(node any) error { goto unsupported } - return nil + return p.sourcePosErr unsupported: return fmt.Errorf("mvdan.cc/gofumpt/internal/govendor/go/printer: unsupported node type %T", node) diff --git a/vendor/mvdan.cc/unparam/check/check.go b/vendor/mvdan.cc/unparam/check/check.go index 8f9a3cb0a..39de800d9 100644 --- a/vendor/mvdan.cc/unparam/check/check.go +++ b/vendor/mvdan.cc/unparam/check/check.go @@ -916,7 +916,7 @@ func recvPrefix(recv *ast.FieldList) string { case *ast.IndexListExpr: return expr.X.(*ast.Ident).Name + "." default: - panic(fmt.Sprintf("unexepected receiver AST node: %T", expr)) + panic(fmt.Sprintf("unexpected receiver AST node: %T", expr)) } } |
