From 75988a412abaa0b3f38f7d11aba5e6e69103b1ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 03:31:38 +0000 Subject: [PATCH] build(deps): bump github.com/golangci/golangci-lint Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.56.2 to 1.57.1. - [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.56.2...v1.57.1) --- updated-dependencies: - dependency-name: github.com/golangci/golangci-lint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 61 +- go.sum | 124 +- .../github.com/Abirdcfly/dupword/dupword.go | 4 +- .../testifylint/analyzer/checkers_factory.go | 3 + .../internal/checkers/bool_compare.go | 88 +- .../internal/checkers/go_require.go | 34 +- .../testifylint/internal/config/config.go | 8 + .../perfsprint/analyzer/analyzer.go | 12 +- .../github.com/ckaznocha/intrange/.gitignore | 1 + .../ckaznocha/intrange/.golangci.yml | 99 + .../ifshort => ckaznocha/intrange}/LICENSE | 4 +- .../github.com/ckaznocha/intrange/README.md | 54 + .../github.com/ckaznocha/intrange/SECURITY.md | 5 + vendor/github.com/ckaznocha/intrange/go.work | 6 + .../github.com/ckaznocha/intrange/intrange.go | 300 +++ .../daixiang0/gci/pkg/gci/testdata.go | 20 + .../daixiang0/gci/pkg/parse/parse.go | 3 + .../gci/pkg/section/standard_list.go | 4 +- .../denis-tingaikin/go-header/.go-header.yml | 2 +- .../denis-tingaikin/go-header/README.md | 4 +- .../denis-tingaikin/go-header/analyzer.go | 134 +- .../denis-tingaikin/go-header/config.go | 10 +- .../denis-tingaikin/go-header/issue.go | 21 +- .../denis-tingaikin/go-header/value.go | 34 +- .../esimonov/ifshort/pkg/analyzer/analyzer.go | 280 --- .../ifshort/pkg/analyzer/occurrences.go | 268 --- .../ghostiam/protogetter/processor.go | 6 +- .../checkers/commentFormatting_checker.go | 16 +- .../check/cmd/structcheck/structcheck.go | 193 -- .../golangci/check/cmd/varcheck/varcheck.go | 163 -- vendor/github.com/golangci/go-misc/LICENSE | 27 - .../golangci/go-misc/deadcode/README.md | 18 - .../golangci/go-misc/deadcode/deadcode.go | 138 -- .../golangci-lint/cmd/golangci-lint/main.go | 64 +- .../cmd/golangci-lint/plugins.go | 3 + .../golangci-lint/pkg/commands/cache.go | 52 +- .../golangci-lint/pkg/commands/config.go | 115 +- .../pkg/commands/config_verify.go | 176 ++ .../golangci-lint/pkg/commands/custom.go | 79 + .../golangci-lint/pkg/commands/executor.go | 254 --- .../golangci-lint/pkg/commands/flagsets.go | 137 ++ .../golangci-lint/pkg/commands/help.go | 118 +- .../pkg/commands/internal/builder.go | 219 +++ .../pkg/commands/internal/configuration.go | 138 ++ .../pkg/commands/internal/imports.go | 69 + .../pkg/commands/internal/vibra.go | 59 + .../golangci-lint/pkg/commands/linters.go | 82 +- .../golangci-lint/pkg/commands/root.go | 248 ++- .../golangci-lint/pkg/commands/run.go | 878 +++++---- .../golangci-lint/pkg/commands/version.go | 113 +- .../golangci-lint/pkg/config/config.go | 67 +- .../golangci-lint/pkg/config/issues.go | 30 + .../golangci-lint/pkg/config/linters.go | 54 + .../pkg/config/linters_settings.go | 308 +-- .../golangci-lint/pkg/config/loader.go | 398 ++++ .../golangci-lint/pkg/config/output.go | 93 +- .../golangci-lint/pkg/config/reader.go | 248 --- .../golangci/golangci-lint/pkg/config/run.go | 47 +- .../golangci-lint/pkg/config/severity.go | 23 + .../golangci-lint/pkg/fsutils/fsutils.go | 10 +- .../golinters/{ifshort.go => copyloopvar.go} | 13 +- .../golangci-lint/pkg/golinters/deadcode.go | 61 - .../golangci-lint/pkg/golinters/dogsled.go | 1 - .../golangci-lint/pkg/golinters/dupl.go | 1 - .../pkg/golinters/exhaustivestruct.go | 31 - .../golangci-lint/pkg/golinters/forbidigo.go | 1 - .../golangci-lint/pkg/golinters/funlen.go | 1 - .../pkg/golinters/ginkgolinter.go | 33 +- .../pkg/golinters/goanalysis/adapters.go | 41 - .../pkg/golinters/goanalysis/issue.go | 5 +- .../pkg/golinters/goanalysis/runner.go | 25 +- .../pkg/golinters/goanalysis/runners.go | 33 +- .../pkg/golinters/gochecknoglobals.go | 19 +- .../golangci-lint/pkg/golinters/gocognit.go | 1 - .../golangci-lint/pkg/golinters/goconst.go | 1 - .../golangci-lint/pkg/golinters/gocritic.go | 589 +++--- .../golangci-lint/pkg/golinters/gocyclo.go | 1 - .../golangci-lint/pkg/golinters/godot.go | 2 +- .../golangci-lint/pkg/golinters/godox.go | 1 - .../pkg/golinters/gofmt_common.go | 4 +- .../golangci-lint/pkg/golinters/goheader.go | 11 + .../golangci-lint/pkg/golinters/golint.go | 84 - .../golangci-lint/pkg/golinters/gosec.go | 16 +- .../golangci-lint/pkg/golinters/govet.go | 49 +- .../golangci-lint/pkg/golinters/importas.go | 4 +- .../golangci-lint/pkg/golinters/interfacer.go | 82 - .../golinters/{nosnakecase.go => intrange.go} | 6 +- .../golangci-lint/pkg/golinters/lll.go | 1 - .../golangci-lint/pkg/golinters/makezero.go | 1 - .../golangci-lint/pkg/golinters/maligned.go | 74 - .../golangci-lint/pkg/golinters/misspell.go | 33 + .../golangci-lint/pkg/golinters/nestif.go | 1 - .../golangci-lint/pkg/golinters/nolintlint.go | 1 - .../pkg/golinters/nolintlint/nolintlint.go | 12 +- .../pkg/golinters/paralleltest.go | 14 +- .../golangci-lint/pkg/golinters/perfsprint.go | 1 + .../golangci-lint/pkg/golinters/prealloc.go | 1 - .../golangci-lint/pkg/golinters/revive.go | 12 +- .../golangci-lint/pkg/golinters/scopelint.go | 190 -- .../golangci-lint/pkg/golinters/sloglint.go | 1 + .../pkg/golinters/structcheck.go | 71 - .../pkg/golinters/testifylint.go | 2 + .../pkg/golinters/testpackage.go | 2 +- .../golangci-lint/pkg/golinters/unconvert.go | 23 +- .../golangci-lint/pkg/golinters/util.go | 8 - .../golangci-lint/pkg/golinters/varcheck.go | 72 - .../golangci/golangci-lint/pkg/goutil/env.go | 15 +- .../golangci-lint/pkg/lint/context.go | 64 + .../golangci-lint/pkg/lint/linter/config.go | 27 +- .../golangci-lint/pkg/lint/linter/context.go | 1 - .../golangci-lint/pkg/lint/linter/linter.go | 36 +- .../pkg/lint/lintersdb/builder_linter.go | 711 +++++++ ...custom_linters.go => builder_plugin_go.go} | 63 +- .../lint/lintersdb/builder_plugin_module.go | 85 + .../pkg/lint/lintersdb/enabled_set.go | 219 --- .../pkg/lint/lintersdb/manager.go | 1180 +++--------- .../pkg/lint/lintersdb/validator.go | 85 +- .../pkg/lint/{load.go => package.go} | 361 ++-- .../golangci/golangci-lint/pkg/lint/runner.go | 174 +- .../golangci-lint/pkg/logutils/logutils.go | 12 + .../golangci-lint/pkg/logutils/out.go | 6 +- .../golangci-lint/pkg/logutils/stderr_log.go | 2 +- .../pkg/packages/{skip.go => exclude.go} | 0 .../golangci-lint/pkg/printers/github.go | 10 +- .../golangci-lint/pkg/printers/json.go | 2 +- .../golangci-lint/pkg/printers/printer.go | 136 +- .../golangci-lint/pkg/printers/tab.go | 12 +- .../golangci-lint/pkg/printers/text.go | 24 +- .../golangci-lint/pkg/result/issue.go | 4 +- .../processors/autogenerated_exclude.go | 142 +- .../pkg/result/processors/base_rule.go | 4 +- .../pkg/result/processors/cgo.go | 14 +- .../pkg/result/processors/diff.go | 10 +- .../pkg/result/processors/exclude.go | 53 +- .../pkg/result/processors/exclude_rules.go | 98 +- .../result/processors/filename_unadjuster.go | 26 +- .../pkg/result/processors/fixer.go | 18 +- .../result/processors/identifier_marker.go | 86 +- .../pkg/result/processors/invalid_issue.go | 54 + .../pkg/result/processors/issues.go | 12 +- .../pkg/result/processors/max_from_linter.go | 8 +- .../processors/max_per_file_from_linter.go | 18 +- .../pkg/result/processors/max_same_issues.go | 8 +- .../pkg/result/processors/nolint.go | 54 +- .../pkg/result/processors/path_prettifier.go | 16 +- .../pkg/result/processors/path_shortener.go | 10 +- .../pkg/result/processors/severity.go | 126 ++ .../pkg/result/processors/severity_rules.go | 108 -- .../pkg/result/processors/skip_dirs.go | 10 +- .../pkg/result/processors/skip_files.go | 4 +- .../pkg/result/processors/sort_results.go | 216 ++- .../pkg/result/processors/source_code.go | 16 +- .../pkg/result/processors/uniq_by_line.go | 18 +- vendor/github.com/golangci/lint-1/.travis.yml | 19 - .../golangci/lint-1/CONTRIBUTING.md | 15 - vendor/github.com/golangci/lint-1/LICENSE | 27 - vendor/github.com/golangci/lint-1/README.md | 88 - vendor/github.com/golangci/lint-1/lint.go | 1655 ----------------- vendor/github.com/golangci/maligned/LICENSE | 27 - vendor/github.com/golangci/maligned/README | 7 - .../github.com/golangci/maligned/maligned.go | 253 --- .../{check => plugin-module-register}/LICENSE | 16 +- .../register/register.go | 73 + vendor/github.com/golangci/unconvert/README | 36 - .../github.com/golangci/unconvert/README.md | 6 + .../github.com/golangci/unconvert/golangci.go | 78 + .../golangci/unconvert/unconvert.go | 259 ++- vendor/github.com/jjti/go-spancheck/README.md | 47 +- .../github.com/jjti/go-spancheck/spancheck.go | 146 +- .../karamaru-alpha/copyloopvar/.gitignore | 2 + .../copyloopvar}/LICENSE | 2 +- .../karamaru-alpha/copyloopvar/README.md | 27 + .../karamaru-alpha/copyloopvar/copyloopvar.go | 135 ++ vendor/github.com/kisielk/gotool/.travis.yml | 23 - vendor/github.com/kisielk/gotool/LEGAL | 32 - vendor/github.com/kisielk/gotool/LICENSE | 20 - vendor/github.com/kisielk/gotool/README.md | 6 - vendor/github.com/kisielk/gotool/go13.go | 15 - vendor/github.com/kisielk/gotool/go14-15.go | 15 - vendor/github.com/kisielk/gotool/go16-18.go | 15 - .../kisielk/gotool/internal/load/path.go | 27 - .../kisielk/gotool/internal/load/pkg.go | 25 - .../kisielk/gotool/internal/load/search.go | 354 ---- vendor/github.com/kisielk/gotool/match.go | 56 - vendor/github.com/kisielk/gotool/match18.go | 317 ---- vendor/github.com/kisielk/gotool/tool.go | 48 - .../pkg/paralleltest/paralleltest.go | 4 +- .../exhaustivestruct/pkg/analyzer/analyzer.go | 187 -- .../nunnatsa/ginkgolinter/README.md | 124 +- .../nunnatsa/ginkgolinter/analyzer.go | 58 + .../github.com/nunnatsa/ginkgolinter/doc.go | 99 + .../{ => internal}/ginkgohandler/handler.go | 40 +- .../{ => internal}/gomegahandler/handler.go | 7 +- .../{ => internal}/interfaces/interfaces.go | 0 .../internal/intervals/intervals.go | 285 +++ .../internal/reports/report-builder.go | 98 + .../reverseassertion/reverse_assertion.go | 0 .../{ => linter}/ginkgo_linter.go | 694 ++++--- .../nunnatsa/ginkgolinter/types/config.go | 41 +- .../pelletier/go-toml/v2/.gitignore | 3 +- .../pelletier/go-toml/v2/.goreleaser.yaml | 3 + .../pelletier/go-toml/v2/CONTRIBUTING.md | 31 +- .../github.com/pelletier/go-toml/v2/LICENSE | 3 +- .../github.com/pelletier/go-toml/v2/README.md | 172 +- .../pelletier/go-toml/v2/SECURITY.md | 3 - vendor/github.com/pelletier/go-toml/v2/ci.sh | 23 +- .../github.com/pelletier/go-toml/v2/decode.go | 90 +- .../github.com/pelletier/go-toml/v2/errors.go | 32 +- .../go-toml/v2/internal/ast/builder.go | 51 - .../go-toml/v2/internal/characters/ascii.go | 42 + .../v2/{ => internal/characters}/utf8.go | 53 +- .../go-toml/v2/internal/tracker/key.go | 12 +- .../go-toml/v2/internal/tracker/seen.go | 78 +- .../pelletier/go-toml/v2/localtime.go | 6 +- .../pelletier/go-toml/v2/marshaler.go | 105 +- .../github.com/pelletier/go-toml/v2/strict.go | 34 +- .../github.com/pelletier/go-toml/v2/types.go | 10 +- .../pelletier/go-toml/v2/unmarshaler.go | 260 ++- .../v2/{internal/ast => unstable}/ast.go | 68 +- .../pelletier/go-toml/v2/unstable/builder.go | 71 + .../pelletier/go-toml/v2/unstable/doc.go | 3 + .../v2/{internal/ast => unstable}/kind.go | 12 +- .../go-toml/v2/{ => unstable}/parser.go | 421 +++-- .../go-toml/v2/{ => unstable}/scanner.go | 51 +- .../go-toml/v2/unstable/unmarshaler.go | 7 + .../go-ruleguard/ruleguard/ir_loader.go | 2 +- .../ruleguard/textmatch/textmatch.go | 2 +- .../quasilyte/go-ruleguard/ruleguard/utils.go | 2 +- .../ryancurrah/gomodguard/processor.go | 2 +- .../santhosh-tekuri/jsonschema/v5/.gitignore | 4 + .../santhosh-tekuri/jsonschema/v5/.gitmodules | 3 + .../santhosh-tekuri/jsonschema/v5/LICENSE | 175 ++ .../santhosh-tekuri/jsonschema/v5/README.md | 220 +++ .../santhosh-tekuri/jsonschema/v5/compiler.go | 812 ++++++++ .../santhosh-tekuri/jsonschema/v5/content.go | 29 + .../santhosh-tekuri/jsonschema/v5/doc.go | 49 + .../santhosh-tekuri/jsonschema/v5/draft.go | 1454 +++++++++++++++ .../santhosh-tekuri/jsonschema/v5/errors.go | 129 ++ .../jsonschema/v5/extension.go | 116 ++ .../santhosh-tekuri/jsonschema/v5/format.go | 567 ++++++ .../jsonschema/v5/httploader/httploader.go | 38 + .../santhosh-tekuri/jsonschema/v5/loader.go | 60 + .../santhosh-tekuri/jsonschema/v5/output.go | 77 + .../santhosh-tekuri/jsonschema/v5/resource.go | 280 +++ .../santhosh-tekuri/jsonschema/v5/schema.go | 900 +++++++++ .../sivchari/nosnakecase/.gitignore | 15 - .../sivchari/nosnakecase/.golangci.yml | 40 - .../github.com/sivchari/nosnakecase/README.md | 224 --- .../sivchari/nosnakecase/nosnakecase.go | 63 - .../wrapcheck/v2/wrapcheck/wrapcheck.go | 24 + vendor/go-simpler.org/musttag/README.md | 2 +- vendor/go-simpler.org/musttag/musttag.go | 21 +- vendor/go-simpler.org/sloglint/Makefile | 28 + vendor/go-simpler.org/sloglint/README.md | 12 + vendor/go-simpler.org/sloglint/sloglint.go | 59 +- .../automaxprocs}/LICENSE | 12 +- .../automaxprocs/internal/cgroups/cgroup.go | 79 + .../automaxprocs/internal/cgroups/cgroups.go | 118 ++ .../automaxprocs/internal/cgroups/cgroups2.go | 176 ++ .../automaxprocs/internal/cgroups/doc.go | 23 + .../automaxprocs/internal/cgroups/errors.go | 52 + .../internal/cgroups/mountpoint.go | 171 ++ .../automaxprocs/internal/cgroups/subsys.go | 103 + .../internal/runtime/cpu_quota_linux.go | 71 + .../internal/runtime/cpu_quota_unsupported.go | 31 + .../automaxprocs/internal/runtime/runtime.go | 33 + .../automaxprocs/maxprocs/maxprocs.go | 130 ++ .../automaxprocs/maxprocs/version.go | 24 + .../x/exp/constraints/constraints.go | 50 + vendor/golang.org/x/exp/slices/cmp.go | 44 + vendor/golang.org/x/exp/slices/slices.go | 499 +++++ vendor/golang.org/x/exp/slices/sort.go | 195 ++ .../golang.org/x/exp/slices/zsortanyfunc.go | 479 +++++ .../golang.org/x/exp/slices/zsortordered.go | 481 +++++ vendor/golang.org/x/mod/modfile/rule.go | 7 +- .../analysis/passes/asmdecl/arches_go118.go | 12 - .../analysis/passes/asmdecl/arches_go119.go | 14 - .../go/analysis/passes/asmdecl/asmdecl.go | 3 +- .../go/analysis/passes/composite/composite.go | 18 +- .../go/analysis/passes/copylock/copylock.go | 3 +- .../passes/deepequalerrors/deepequalerrors.go | 3 +- .../passes/httpresponse/httpresponse.go | 7 +- .../passes/ifaceassert/parameterized.go | 5 + .../passes/internal/analysisutil/util.go | 3 +- .../passes/loopclosure/loopclosure.go | 13 +- .../go/analysis/passes/nilness/nilness.go | 79 +- .../tools/go/analysis/passes/printf/printf.go | 7 +- .../tools/go/analysis/passes/printf/types.go | 5 +- .../x/tools/go/analysis/passes/shift/shift.go | 6 +- .../x/tools/go/analysis/passes/slog/slog.go | 26 +- .../analysis/passes/stringintconv/string.go | 6 +- .../testinggoroutine/testinggoroutine.go | 3 +- .../analysis/passes/testinggoroutine/util.go | 2 +- .../x/tools/go/analysis/passes/tests/tests.go | 2 + .../analysis/passes/timeformat/timeformat.go | 2 +- .../go/analysis/passes/unmarshal/unmarshal.go | 9 +- .../go/analysis/passes/unsafeptr/unsafeptr.go | 3 +- .../passes/unusedwrite/unusedwrite.go | 19 +- vendor/golang.org/x/tools/go/cfg/builder.go | 86 +- vendor/golang.org/x/tools/go/cfg/cfg.go | 115 +- .../x/tools/go/gcexportdata/gcexportdata.go | 2 +- .../x/tools/go/internal/cgo/cgo_pkgconfig.go | 5 +- vendor/golang.org/x/tools/go/ssa/builder.go | 26 +- vendor/golang.org/x/tools/go/ssa/const.go | 5 +- vendor/golang.org/x/tools/go/ssa/coretype.go | 3 +- vendor/golang.org/x/tools/go/ssa/create.go | 3 +- vendor/golang.org/x/tools/go/ssa/emit.go | 13 +- vendor/golang.org/x/tools/go/ssa/func.go | 10 +- vendor/golang.org/x/tools/go/ssa/lift.go | 6 +- vendor/golang.org/x/tools/go/ssa/lvalue.go | 4 +- vendor/golang.org/x/tools/go/ssa/methods.go | 4 + .../x/tools/go/ssa/parameterized.go | 4 + vendor/golang.org/x/tools/go/ssa/print.go | 7 +- vendor/golang.org/x/tools/go/ssa/sanity.go | 2 +- vendor/golang.org/x/tools/go/ssa/subst.go | 7 +- vendor/golang.org/x/tools/go/ssa/util.go | 44 +- .../x/tools/go/types/objectpath/objectpath.go | 20 +- .../x/tools/go/types/typeutil/ui.go | 8 +- .../internal/analysisinternal/analysis.go | 7 +- .../x/tools/internal/gcimporter/gcimporter.go | 7 - .../x/tools/internal/gcimporter/iexport.go | 9 +- .../x/tools/internal/gcimporter/iimport.go | 27 +- .../internal/gcimporter/support_go117.go | 16 - .../internal/gcimporter/support_go118.go | 3 - .../x/tools/internal/gcimporter/unified_no.go | 4 +- .../tools/internal/gcimporter/unified_yes.go | 4 +- .../x/tools/internal/gcimporter/ureader_no.go | 19 - .../tools/internal/gcimporter/ureader_yes.go | 6 +- .../internal/tokeninternal/tokeninternal.go | 28 +- .../x/tools/internal/typeparams/common.go | 37 +- .../x/tools/internal/typeparams/coretype.go | 17 +- .../x/tools/internal/typesinternal/recv.go | 43 + .../tools/internal/typesinternal/types_118.go | 3 - .../x/tools/internal/versions/features.go | 43 + .../x/tools/internal/versions/toolchain.go | 14 + .../internal/versions/toolchain_go119.go | 14 + .../internal/versions/toolchain_go120.go | 14 + .../internal/versions/toolchain_go121.go | 14 + .../x/tools/internal/versions/types_go121.go | 18 +- .../x/tools/internal/versions/types_go122.go | 25 +- .../x/tools/internal/versions/versions.go | 5 + vendor/honnef.co/go/tools/go/ir/UPSTREAM | 2 +- vendor/honnef.co/go/tools/go/ir/builder.go | 39 +- vendor/honnef.co/go/tools/go/ir/lift.go | 19 +- vendor/honnef.co/go/tools/go/ir/lvalue.go | 34 + vendor/modules.txt | 127 +- vendor/mvdan.cc/interfacer/LICENSE | 27 - vendor/mvdan.cc/interfacer/check/cache.go | 50 - vendor/mvdan.cc/interfacer/check/check.go | 462 ----- vendor/mvdan.cc/interfacer/check/types.go | 170 -- vendor/mvdan.cc/lint/.travis.yml | 7 - vendor/mvdan.cc/lint/LICENSE | 27 - vendor/mvdan.cc/lint/README.md | 27 - vendor/mvdan.cc/lint/lint.go | 28 - 354 files changed, 17703 insertions(+), 11876 deletions(-) create mode 100644 vendor/github.com/ckaznocha/intrange/.gitignore create mode 100644 vendor/github.com/ckaznocha/intrange/.golangci.yml rename vendor/github.com/{esimonov/ifshort => ckaznocha/intrange}/LICENSE (94%) create mode 100644 vendor/github.com/ckaznocha/intrange/README.md create mode 100644 vendor/github.com/ckaznocha/intrange/SECURITY.md create mode 100644 vendor/github.com/ckaznocha/intrange/go.work create mode 100644 vendor/github.com/ckaznocha/intrange/intrange.go delete mode 100644 vendor/github.com/esimonov/ifshort/pkg/analyzer/analyzer.go delete mode 100644 vendor/github.com/esimonov/ifshort/pkg/analyzer/occurrences.go delete mode 100644 vendor/github.com/golangci/check/cmd/structcheck/structcheck.go delete mode 100644 vendor/github.com/golangci/check/cmd/varcheck/varcheck.go delete mode 100644 vendor/github.com/golangci/go-misc/LICENSE delete mode 100644 vendor/github.com/golangci/go-misc/deadcode/README.md delete mode 100644 vendor/github.com/golangci/go-misc/deadcode/deadcode.go create mode 100644 vendor/github.com/golangci/golangci-lint/cmd/golangci-lint/plugins.go create mode 100644 vendor/github.com/golangci/golangci-lint/pkg/commands/config_verify.go create mode 100644 vendor/github.com/golangci/golangci-lint/pkg/commands/custom.go delete mode 100644 vendor/github.com/golangci/golangci-lint/pkg/commands/executor.go create mode 100644 vendor/github.com/golangci/golangci-lint/pkg/commands/flagsets.go create mode 100644 vendor/github.com/golangci/golangci-lint/pkg/commands/internal/builder.go create mode 100644 vendor/github.com/golangci/golangci-lint/pkg/commands/internal/configuration.go create mode 100644 vendor/github.com/golangci/golangci-lint/pkg/commands/internal/imports.go create mode 100644 vendor/github.com/golangci/golangci-lint/pkg/commands/internal/vibra.go create mode 100644 vendor/github.com/golangci/golangci-lint/pkg/config/loader.go delete mode 100644 vendor/github.com/golangci/golangci-lint/pkg/config/reader.go rename vendor/github.com/golangci/golangci-lint/pkg/golinters/{ifshort.go => copyloopvar.go} (61%) delete mode 100644 vendor/github.com/golangci/golangci-lint/pkg/golinters/deadcode.go delete mode 100644 vendor/github.com/golangci/golangci-lint/pkg/golinters/exhaustivestruct.go delete mode 100644 vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/adapters.go delete mode 100644 vendor/github.com/golangci/golangci-lint/pkg/golinters/golint.go delete mode 100644 vendor/github.com/golangci/golangci-lint/pkg/golinters/interfacer.go rename vendor/github.com/golangci/golangci-lint/pkg/golinters/{nosnakecase.go => intrange.go} (70%) delete mode 100644 vendor/github.com/golangci/golangci-lint/pkg/golinters/maligned.go delete mode 100644 vendor/github.com/golangci/golangci-lint/pkg/golinters/scopelint.go delete mode 100644 vendor/github.com/golangci/golangci-lint/pkg/golinters/structcheck.go delete mode 100644 vendor/github.com/golangci/golangci-lint/pkg/golinters/varcheck.go create mode 100644 vendor/github.com/golangci/golangci-lint/pkg/lint/context.go create mode 100644 vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/builder_linter.go rename vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/{custom_linters.go => builder_plugin_go.go} (56%) create mode 100644 vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/builder_plugin_module.go delete mode 100644 vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/enabled_set.go rename vendor/github.com/golangci/golangci-lint/pkg/lint/{load.go => package.go} (52%) rename vendor/github.com/golangci/golangci-lint/pkg/packages/{skip.go => exclude.go} (100%) create mode 100644 vendor/github.com/golangci/golangci-lint/pkg/result/processors/invalid_issue.go create mode 100644 vendor/github.com/golangci/golangci-lint/pkg/result/processors/severity.go delete mode 100644 vendor/github.com/golangci/golangci-lint/pkg/result/processors/severity_rules.go delete mode 100644 vendor/github.com/golangci/lint-1/.travis.yml delete mode 100644 vendor/github.com/golangci/lint-1/CONTRIBUTING.md delete mode 100644 vendor/github.com/golangci/lint-1/LICENSE delete mode 100644 vendor/github.com/golangci/lint-1/README.md delete mode 100644 vendor/github.com/golangci/lint-1/lint.go delete mode 100644 vendor/github.com/golangci/maligned/LICENSE delete mode 100644 vendor/github.com/golangci/maligned/README delete mode 100644 vendor/github.com/golangci/maligned/maligned.go rename vendor/github.com/golangci/{check => plugin-module-register}/LICENSE (98%) create mode 100644 vendor/github.com/golangci/plugin-module-register/register/register.go delete mode 100644 vendor/github.com/golangci/unconvert/README create mode 100644 vendor/github.com/golangci/unconvert/README.md create mode 100644 vendor/github.com/golangci/unconvert/golangci.go create mode 100644 vendor/github.com/karamaru-alpha/copyloopvar/.gitignore rename vendor/github.com/{sivchari/nosnakecase => karamaru-alpha/copyloopvar}/LICENSE (96%) create mode 100644 vendor/github.com/karamaru-alpha/copyloopvar/README.md create mode 100644 vendor/github.com/karamaru-alpha/copyloopvar/copyloopvar.go delete mode 100644 vendor/github.com/kisielk/gotool/.travis.yml delete mode 100644 vendor/github.com/kisielk/gotool/LEGAL delete mode 100644 vendor/github.com/kisielk/gotool/LICENSE delete mode 100644 vendor/github.com/kisielk/gotool/README.md delete mode 100644 vendor/github.com/kisielk/gotool/go13.go delete mode 100644 vendor/github.com/kisielk/gotool/go14-15.go delete mode 100644 vendor/github.com/kisielk/gotool/go16-18.go delete mode 100644 vendor/github.com/kisielk/gotool/internal/load/path.go delete mode 100644 vendor/github.com/kisielk/gotool/internal/load/pkg.go delete mode 100644 vendor/github.com/kisielk/gotool/internal/load/search.go delete mode 100644 vendor/github.com/kisielk/gotool/match.go delete mode 100644 vendor/github.com/kisielk/gotool/match18.go delete mode 100644 vendor/github.com/kisielk/gotool/tool.go delete mode 100644 vendor/github.com/mbilski/exhaustivestruct/pkg/analyzer/analyzer.go create mode 100644 vendor/github.com/nunnatsa/ginkgolinter/analyzer.go create mode 100644 vendor/github.com/nunnatsa/ginkgolinter/doc.go rename vendor/github.com/nunnatsa/ginkgolinter/{ => internal}/ginkgohandler/handler.go (72%) rename vendor/github.com/nunnatsa/ginkgolinter/{ => internal}/gomegahandler/handler.go (97%) rename vendor/github.com/nunnatsa/ginkgolinter/{ => internal}/interfaces/interfaces.go (100%) create mode 100644 vendor/github.com/nunnatsa/ginkgolinter/internal/intervals/intervals.go create mode 100644 vendor/github.com/nunnatsa/ginkgolinter/internal/reports/report-builder.go rename vendor/github.com/nunnatsa/ginkgolinter/{ => internal}/reverseassertion/reverse_assertion.go (100%) rename vendor/github.com/nunnatsa/ginkgolinter/{ => linter}/ginkgo_linter.go (65%) delete mode 100644 vendor/github.com/pelletier/go-toml/v2/internal/ast/builder.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/internal/characters/ascii.go rename vendor/github.com/pelletier/go-toml/v2/{ => internal/characters}/utf8.go (87%) rename vendor/github.com/pelletier/go-toml/v2/{internal/ast => unstable}/ast.go (60%) create mode 100644 vendor/github.com/pelletier/go-toml/v2/unstable/builder.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/unstable/doc.go rename vendor/github.com/pelletier/go-toml/v2/{internal/ast => unstable}/kind.go (81%) rename vendor/github.com/pelletier/go-toml/v2/{ => unstable}/parser.go (62%) rename vendor/github.com/pelletier/go-toml/v2/{ => unstable}/scanner.go (79%) create mode 100644 vendor/github.com/pelletier/go-toml/v2/unstable/unmarshaler.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/v5/.gitignore create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/v5/.gitmodules create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/v5/LICENSE create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/v5/README.md create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/v5/compiler.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/v5/content.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/v5/doc.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/v5/draft.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/v5/errors.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/v5/extension.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/v5/format.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/v5/httploader/httploader.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/v5/loader.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/v5/output.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/v5/resource.go create mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/v5/schema.go delete mode 100644 vendor/github.com/sivchari/nosnakecase/.gitignore delete mode 100644 vendor/github.com/sivchari/nosnakecase/.golangci.yml delete mode 100644 vendor/github.com/sivchari/nosnakecase/README.md delete mode 100644 vendor/github.com/sivchari/nosnakecase/nosnakecase.go create mode 100644 vendor/go-simpler.org/sloglint/Makefile rename vendor/{github.com/mbilski/exhaustivestruct => go.uber.org/automaxprocs}/LICENSE (87%) create mode 100644 vendor/go.uber.org/automaxprocs/internal/cgroups/cgroup.go create mode 100644 vendor/go.uber.org/automaxprocs/internal/cgroups/cgroups.go create mode 100644 vendor/go.uber.org/automaxprocs/internal/cgroups/cgroups2.go create mode 100644 vendor/go.uber.org/automaxprocs/internal/cgroups/doc.go create mode 100644 vendor/go.uber.org/automaxprocs/internal/cgroups/errors.go create mode 100644 vendor/go.uber.org/automaxprocs/internal/cgroups/mountpoint.go create mode 100644 vendor/go.uber.org/automaxprocs/internal/cgroups/subsys.go create mode 100644 vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_linux.go create mode 100644 vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_unsupported.go create mode 100644 vendor/go.uber.org/automaxprocs/internal/runtime/runtime.go create mode 100644 vendor/go.uber.org/automaxprocs/maxprocs/maxprocs.go create mode 100644 vendor/go.uber.org/automaxprocs/maxprocs/version.go create mode 100644 vendor/golang.org/x/exp/constraints/constraints.go create mode 100644 vendor/golang.org/x/exp/slices/cmp.go create mode 100644 vendor/golang.org/x/exp/slices/slices.go create mode 100644 vendor/golang.org/x/exp/slices/sort.go create mode 100644 vendor/golang.org/x/exp/slices/zsortanyfunc.go create mode 100644 vendor/golang.org/x/exp/slices/zsortordered.go delete mode 100644 vendor/golang.org/x/tools/go/analysis/passes/asmdecl/arches_go118.go delete mode 100644 vendor/golang.org/x/tools/go/analysis/passes/asmdecl/arches_go119.go delete mode 100644 vendor/golang.org/x/tools/internal/gcimporter/support_go117.go delete mode 100644 vendor/golang.org/x/tools/internal/gcimporter/ureader_no.go create mode 100644 vendor/golang.org/x/tools/internal/typesinternal/recv.go create mode 100644 vendor/golang.org/x/tools/internal/versions/features.go create mode 100644 vendor/golang.org/x/tools/internal/versions/toolchain.go create mode 100644 vendor/golang.org/x/tools/internal/versions/toolchain_go119.go create mode 100644 vendor/golang.org/x/tools/internal/versions/toolchain_go120.go create mode 100644 vendor/golang.org/x/tools/internal/versions/toolchain_go121.go delete mode 100644 vendor/mvdan.cc/interfacer/LICENSE delete mode 100644 vendor/mvdan.cc/interfacer/check/cache.go delete mode 100644 vendor/mvdan.cc/interfacer/check/check.go delete mode 100644 vendor/mvdan.cc/interfacer/check/types.go delete mode 100644 vendor/mvdan.cc/lint/.travis.yml delete mode 100644 vendor/mvdan.cc/lint/LICENSE delete mode 100644 vendor/mvdan.cc/lint/README.md delete mode 100644 vendor/mvdan.cc/lint/lint.go diff --git a/go.mod b/go.mod index 554d5fc3ff..d78a2a8e0c 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.22.0 require ( github.com/client9/misspell v0.3.4 - github.com/golangci/golangci-lint v1.56.2 + github.com/golangci/golangci-lint v1.57.1 github.com/google/go-github/v57 v57.0.0 github.com/google/uuid v1.6.0 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 @@ -22,10 +22,10 @@ require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.2.1 // indirect github.com/4meepo/tagalign v1.3.3 // indirect - github.com/Abirdcfly/dupword v0.0.13 // indirect + github.com/Abirdcfly/dupword v0.0.14 // indirect github.com/Antonboom/errname v0.1.12 // indirect github.com/Antonboom/nilnil v0.1.7 // indirect - github.com/Antonboom/testifylint v1.1.2 // indirect + github.com/Antonboom/testifylint v1.2.0 // indirect github.com/BurntSushi/toml v1.3.2 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 // indirect @@ -34,7 +34,7 @@ require ( github.com/ProtonMail/go-crypto v1.1.0-alpha.0 // indirect github.com/agext/levenshtein v1.2.2 // indirect github.com/alecthomas/go-check-sumtype v0.1.4 // indirect - github.com/alexkohler/nakedret/v2 v2.0.2 // indirect + github.com/alexkohler/nakedret/v2 v2.0.4 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect @@ -48,25 +48,25 @@ require ( github.com/breml/errchkjson v0.3.6 // indirect github.com/butuzov/ireturn v0.3.0 // indirect github.com/butuzov/mirror v1.1.0 // indirect - github.com/catenacyber/perfsprint v0.6.0 // indirect + github.com/catenacyber/perfsprint v0.7.1 // 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/ckaznocha/intrange v0.1.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect - github.com/daixiang0/gci v0.12.1 // indirect + github.com/daixiang0/gci v0.12.3 // 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/denis-tingaikin/go-header v0.5.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/firefart/nonamedreturns v1.0.4 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/ghostiam/protogetter v0.3.4 // indirect - github.com/go-critic/go-critic v0.11.1 // indirect + github.com/ghostiam/protogetter v0.3.5 // indirect + github.com/go-critic/go-critic v0.11.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.2.0 // indirect @@ -79,15 +79,12 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 // indirect github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect - github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe // indirect github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e // indirect - github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 // indirect - github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca // indirect github.com/golangci/misspell v0.4.1 // indirect + github.com/golangci/plugin-module-register v0.1.1 // indirect github.com/golangci/revgrep v0.5.2 // indirect - github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect + github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/gordonklaus/ineffassign v0.1.0 // indirect @@ -119,13 +116,13 @@ require ( 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/jjti/go-spancheck v0.5.3 // indirect github.com/julz/importas v0.1.0 // indirect + github.com/karamaru-alpha/copyloopvar v1.0.8 // 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.9 // indirect + github.com/kunwardeep/paralleltest v1.0.10 // 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 @@ -140,7 +137,6 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mbilski/exhaustivestruct v1.2.0 // indirect github.com/mgechev/revive v1.3.7 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -153,24 +149,25 @@ require ( github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.15.2 // indirect + github.com/nunnatsa/ginkgolinter v0.16.1 // indirect github.com/oklog/run v1.0.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pelletier/go-toml/v2 v2.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polyfloyd/go-errorlint v1.4.8 // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect - github.com/quasilyte/go-ruleguard v0.4.0 // indirect + github.com/quasilyte/go-ruleguard v0.4.2 // indirect github.com/quasilyte/gogrep v0.5.0 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect - github.com/ryancurrah/gomodguard v1.3.0 // indirect + github.com/ryancurrah/gomodguard v1.3.1 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect + github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.25.0 // indirect github.com/securego/gosec/v2 v2.19.0 // indirect @@ -178,7 +175,6 @@ require ( github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect - github.com/sivchari/nosnakecase v1.7.0 // indirect 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 @@ -197,7 +193,7 @@ require ( 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/tomarrell/wrapcheck/v2 v2.8.3 // 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.1.0 // indirect @@ -211,19 +207,20 @@ require ( github.com/ykadowak/zerologlint v0.1.5 // indirect github.com/zclconf/go-cty v1.14.2 // indirect gitlab.com/bosi/decorder v0.4.1 // indirect - go-simpler.org/musttag v0.8.0 // indirect - go-simpler.org/sloglint v0.4.0 // indirect + go-simpler.org/musttag v0.9.0 // indirect + go-simpler.org/sloglint v0.5.0 // indirect go.uber.org/atomic v1.7.0 // indirect + go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.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/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect + golang.org/x/mod v0.16.0 // indirect golang.org/x/net v0.22.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.18.0 // indirect + golang.org/x/tools v0.19.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect google.golang.org/grpc v1.61.1 // indirect @@ -232,9 +229,7 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - honnef.co/go/tools v0.4.6 // indirect + honnef.co/go/tools v0.4.7 // 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-20240104100049-c549a3470d14 // indirect ) diff --git a/go.sum b/go.sum index 9922dcac4e..8327b8c11f 100644 --- a/go.sum +++ b/go.sum @@ -39,14 +39,14 @@ dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/4meepo/tagalign v1.3.3 h1:ZsOxcwGD/jP4U/aw7qeWu58i7dwYemfy5Y+IF1ACoNw= github.com/4meepo/tagalign v1.3.3/go.mod h1:Q9c1rYMZJc9dPRkbQPpcBNCLEmY2njbAsXhQOZFE2dE= -github.com/Abirdcfly/dupword v0.0.13 h1:SMS17YXypwP000fA7Lr+kfyBQyW14tTT+nRv9ASwUUo= -github.com/Abirdcfly/dupword v0.0.13/go.mod h1:Ut6Ue2KgF/kCOawpW4LnExT+xZLQviJPE4klBPMK/5Y= +github.com/Abirdcfly/dupword v0.0.14 h1:3U4ulkc8EUo+CaT105/GJ1BQwtgyj6+VaBVbAX11Ba8= +github.com/Abirdcfly/dupword v0.0.14/go.mod h1:VKDAbxdY8YbKUByLGg8EETzYSuC4crm9WwI6Y3S0cLI= github.com/Antonboom/errname v0.1.12 h1:oh9ak2zUtsLp5oaEd/erjB4GPu9w19NyoIskZClDcQY= github.com/Antonboom/errname v0.1.12/go.mod h1:bK7todrzvlaZoQagP1orKzWXv59X/x0W0Io2XT1Ssro= github.com/Antonboom/nilnil v0.1.7 h1:ofgL+BA7vlA1K2wNQOsHzLJ2Pw5B5DpWRLdDAVvvTow= github.com/Antonboom/nilnil v0.1.7/go.mod h1:TP+ScQWVEq0eSIxqU8CbdT5DFWoHp0MbP+KMUO1BKYQ= -github.com/Antonboom/testifylint v1.1.2 h1:IdLRermiLRogxY5AumBL4sP0A+qKHQM/AP1Xd7XOTKc= -github.com/Antonboom/testifylint v1.1.2/go.mod h1:9PFi+vWa8zzl4/B/kqmFJcw85ZUv8ReyBzuQCd30+WI= +github.com/Antonboom/testifylint v1.2.0 h1:015bxD8zc5iY8QwTp4+RG9I4kIbqwvGX9TrBbb7jGdM= +github.com/Antonboom/testifylint v1.2.0/go.mod h1:rkmEqjqVnHDRNsinyN6fPSLnoajzFwsCcguJgwADBkw= 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= @@ -76,8 +76,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alexkohler/nakedret/v2 v2.0.2 h1:qnXuZNvv3/AxkAb22q/sEsEpcA99YxLFACDtEw9TPxE= -github.com/alexkohler/nakedret/v2 v2.0.2/go.mod h1:2b8Gkk0GsOrqQv/gPWjNLDSKwG8I5moSXG1K4VIBcTQ= +github.com/alexkohler/nakedret/v2 v2.0.4 h1:yZuKmjqGi0pSmjGpOC016LtPJysIL0WEUiaXW5SUnNg= +github.com/alexkohler/nakedret/v2 v2.0.4/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= @@ -111,8 +111,8 @@ 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.6.0 h1:VSv95RRkk5+BxrU/YTPcnxuMEWar1iMK5Vyh3fWcBfs= -github.com/catenacyber/perfsprint v0.6.0/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= +github.com/catenacyber/perfsprint v0.7.1 h1:PGW5G/Kxn+YrN04cRAZKC+ZuvlVwolYMrIyyTJ/rMmc= +github.com/catenacyber/perfsprint v0.7.1/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= @@ -127,6 +127,8 @@ github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+U github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/ckaznocha/intrange v0.1.0 h1:ZiGBhvrdsKpoEfzh9CjBfDSZof6QB0ORY5tXasUtiew= +github.com/ckaznocha/intrange v0.1.0/go.mod h1:Vwa9Ekex2BrEQMg6zlrWwbs/FtYw7eS5838Q7UjK7TQ= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= @@ -137,21 +139,19 @@ github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDU github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= -github.com/daixiang0/gci v0.12.1 h1:ugsG+KRYny1VK4oqrX4Vtj70bo4akYKa0tgT1DXMYiY= -github.com/daixiang0/gci v0.12.1/go.mod h1:xtHP9N7AHdNvtRNfcx9gwTDfw7FRJx4bZUsiEfiNNAI= +github.com/daixiang0/gci v0.12.3 h1:yOZI7VAxAGPQmkb1eqt5g/11SUlwoat1fSblGLmdiQc= +github.com/daixiang0/gci v0.12.3/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= -github.com/denis-tingaikin/go-header v0.4.3 h1:tEaZKAlqql6SKCY++utLmkPLd6K8IBM20Ha7UVm+mtU= -github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c= +github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= +github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= 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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -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.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= @@ -167,10 +167,10 @@ github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwV github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/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/ghostiam/protogetter v0.3.5 h1:+f7UiF8XNd4w3a//4DnusQ2SZjPkUjxkMEfjbxOK4Ug= +github.com/ghostiam/protogetter v0.3.5/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw= +github.com/go-critic/go-critic v0.11.2 h1:81xH/2muBphEgPtcwH1p6QD+KzXl2tMSi3hXjBSxDnM= +github.com/go-critic/go-critic v0.11.2/go.mod h1:OePaicfjsf+KPy33yq4gzv6CO7TEQ9Rom6ns1KsJnl8= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= @@ -253,26 +253,20 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0= -github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= -github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe h1:6RGUuS7EGotKx6J5HIP8ZtyMdiDscjMLfRBSPuzVVeo= -github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe/go.mod h1:gjqyPShc/m8pEMpk0a3SeagVb0kaqvhscv+i9jI5ZhQ= github.com/golangci/gofmt v0.0.0-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.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= -github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= +github.com/golangci/golangci-lint v1.57.1 h1:cqhpzkzjDwdN12rfMf1SUyyKyp88a1SltNqEYGS0nJw= +github.com/golangci/golangci-lint v1.57.1/go.mod h1:zLcHhz3NHc88T5zV2j75lyc0zH3LdOPOybblYa4p0oI= github.com/golangci/misspell v0.4.1 h1:+y73iSicVy2PqyX7kmUefHusENlrP9YwuHZHPLGQj/g= github.com/golangci/misspell v0.4.1/go.mod h1:9mAN1quEo3DlpbaIKKyEvRxK1pwqR9s/Sea1bJCtlNI= +github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= +github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= github.com/golangci/revgrep v0.5.2 h1:EndcWoRhcnfj2NHQ+28hyuXpLMF+dQmCN+YaeeIl4FU= github.com/golangci/revgrep v0.5.2/go.mod h1:bjAMA+Sh/QUfTDcHzxfyHxr4xKvllVr/0sCv2e7jJHA= -github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys= -github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= +github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed h1:IURFTjxeTfNFP0hTEi1YKjB/ub8zkpaOqFFMApi2EAs= +github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed/go.mod h1:XLXN8bNw4CGRPaqgl3bv/lhz7bsGPh4/xSaMTbo2vkQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -386,8 +380,8 @@ github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjz 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/jjti/go-spancheck v0.5.3 h1:vfq4s2IB8T3HvbpiwDTYgVPj1Ze/ZSXrTtaZRTc7CuM= +github.com/jjti/go-spancheck v0.5.3/go.mod h1:eQdOX1k3T+nAKvZDyLC3Eby0La4dZ+I19iOl5NzSPFE= 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= @@ -399,11 +393,12 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 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/karamaru-alpha/copyloopvar v1.0.8 h1:gieLARwuByhEMxRwM3GRS/juJqFbLraftXIKDDNJ50Q= +github.com/karamaru-alpha/copyloopvar v1.0.8/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 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= @@ -419,8 +414,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.9 h1:3Sr2IfFNcsMmlqPk1cjTUbJ4zofKPGyHxenwPebgTug= -github.com/kunwardeep/paralleltest v1.0.9/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= +github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCTdvWJ/lDDs= +github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ymEyhQ= @@ -458,8 +453,6 @@ github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/Qd github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 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.7 h1:502QY0vQGe9KtYJ9FpxMz9rL+Fc/P13CI5POL4uHCcE= github.com/mgechev/revive v1.3.7/go.mod h1:RJ16jUbF0OWC3co/+XTxmFNgEpUPwnnA0BRllX2aDNA= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= @@ -491,8 +484,8 @@ github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhK 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.15.2 h1:N2ORxUxPU56R9gsfLIlVVvCv/V/VVou5qVI1oBKBNHg= -github.com/nunnatsa/ginkgolinter v0.15.2/go.mod h1:oYxE7dt1vZI8cK2rZOs3RgTaBN2vggkqnENmoJ8kVvc= +github.com/nunnatsa/ginkgolinter v0.16.1 h1:uDIPSxgVHZ7PgbJElRDGzymkXH+JaF7mjew+Thjnt6Q= +github.com/nunnatsa/ginkgolinter v0.16.1/go.mod h1:4tWRinDN1FeJgU+iJANW/kz7xKN5nYRAOfJDQUS9dOQ= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= @@ -510,8 +503,8 @@ github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT9 github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= -github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= +github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -522,6 +515,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polyfloyd/go-errorlint v1.4.8 h1:jiEjKDH33ouFktyez7sckv6pHWif9B7SuS8cutDXFHw= github.com/polyfloyd/go-errorlint v1.4.8/go.mod h1:NNCxFcFjZcw3xNjVdCchERkEM6Oz7wta2XJVxRftwO4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= 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,8 +539,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/quasilyte/go-ruleguard v0.4.0 h1:DyM6r+TKL+xbKB4Nm7Afd1IQh9kEUKQs2pboWGKtvQo= -github.com/quasilyte/go-ruleguard v0.4.0/go.mod h1:Eu76Z/R8IXtViWUIHkE3p8gdH3/PKk1eh3YGfaEof10= +github.com/quasilyte/go-ruleguard v0.4.2 h1:htXcXDK6/rO12kiTHKfHuqR4kr3Y4M0J0rOL6CH/BYs= +github.com/quasilyte/go-ruleguard v0.4.2/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= @@ -556,12 +551,14 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryancurrah/gomodguard v1.3.0 h1:q15RT/pd6UggBXVBuLps8BXRvl5GPBcwVA7BJHMLuTw= -github.com/ryancurrah/gomodguard v1.3.0/go.mod h1:ggBxb3luypPEzqVtq33ee7YSN35V28XeGnid8dnni50= +github.com/ryancurrah/gomodguard v1.3.1 h1:fH+fUg+ngsQO0ruZXXHnA/2aNllWA1whly4a6UvyzGE= +github.com/ryancurrah/gomodguard v1.3.1/go.mod h1:DGFHzEhi6iJ0oIDfMuo3TgrS+L9gZvrEfmjjuelnRU0= github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/NsJFwFK7Uxc= github.com/sanposhiho/wastedassign/v2 v2.0.7/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= 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.25.0 h1:IK8SI2QyFzy/2OD2PYnhy84dpfNo9qADrRt6LH8vSzU= @@ -585,8 +582,6 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= -github.com/sivchari/nosnakecase v1.7.0 h1:7QkpWIRMe8x25gckkFd2A5Pi6Ymo0qgr4JrhGt95do8= -github.com/sivchari/nosnakecase v1.7.0/go.mod h1:CwDzrzPea40/GB6uynrNLiorAlgFRvRbFSgJx2Gs+QY= github.com/sivchari/tenv v1.7.1 h1:PSpuD4bu6fSmtWMxSGWcvqUUgIn7k3yOJhOIzVWn8Ak= github.com/sivchari/tenv v1.7.1/go.mod h1:64yStXKSOxDfX47NlhVwND4dHwfZDdbp2Lyl018Icvg= github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= @@ -644,8 +639,8 @@ github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 h1:quvGphlmUVU+n 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= github.com/timonwong/loggercheck v0.9.4/go.mod h1:caz4zlPcgvpEkXgVnAJGowHAMW2NwHaNlpS8xDbVhTg= -github.com/tomarrell/wrapcheck/v2 v2.8.1 h1:HxSqDSN0sAt0yJYsrcYVoEeyM4aI9yAm3KQpIXDJRhQ= -github.com/tomarrell/wrapcheck/v2 v2.8.1/go.mod h1:/n2Q3NZ4XFT50ho6Hbxg+RV1uyo2Uow/Vdm9NQcl5SE= +github.com/tomarrell/wrapcheck/v2 v2.8.3 h1:5ov+Cbhlgi7s/a42BprYoxsr73CbdMUTzE3bRDFASUs= +github.com/tomarrell/wrapcheck/v2 v2.8.3/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= github.com/ultraware/funlen v0.1.0 h1:BuqclbkY6pO+cvxoq7OsktIXZpgBSkYTQtmwhAK81vI= @@ -684,10 +679,10 @@ 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.7.0 h1:OzWWZqfNxt8cLS+MlUp6Tgk1HjPkmgdKBq9qvy8lZsA= go-simpler.org/assert v0.7.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= -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-simpler.org/musttag v0.9.0 h1:Dzt6/tyP9ONr5g9h9P3cnYWCxeBFRkd0uJL/w+1Mxos= +go-simpler.org/musttag v0.9.0/go.mod h1:gA9nThnalvNSKpEoyp3Ko4/vCX2xTpqKoUtNqXOnVR4= +go-simpler.org/sloglint v0.5.0 h1:2YCcd+YMuYpuqthCgubcF5lBSjb6berc5VMOYUHKrpY= +go-simpler.org/sloglint v0.5.0/go.mod h1:EUknX5s8iXqf18KQxKnaBHUPVriiPnOrPjjJcsaTcSQ= 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= @@ -695,6 +690,8 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= +go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= @@ -725,8 +722,8 @@ golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcH 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-20231219180239-dc181d75b848 h1:UhRVJ0i7bF9n/Hd8YjW3eKjlPVBHzbQdxrBgjbSKl64= -golang.org/x/exp/typeparams v0.0.0-20231219180239-dc181d75b848/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8= +golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -755,8 +752,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.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= +golang.org/x/mod v0.16.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= @@ -951,7 +948,6 @@ 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-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.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= @@ -966,8 +962,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.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= -golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= +golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= +golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= 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= @@ -1085,14 +1081,10 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.4.6 h1:oFEHCKeID7to/3autwsWfnuv69j3NsfcXbvJKuIcep8= -honnef.co/go/tools v0.4.6/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= +honnef.co/go/tools v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs= +honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= 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-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= diff --git a/vendor/github.com/Abirdcfly/dupword/dupword.go b/vendor/github.com/Abirdcfly/dupword/dupword.go index c291eab527..9a78fb6cca 100644 --- a/vendor/github.com/Abirdcfly/dupword/dupword.go +++ b/vendor/github.com/Abirdcfly/dupword/dupword.go @@ -75,7 +75,7 @@ type ignore struct { } func (a *ignore) String() string { - t := make([]string,0, len(ignoreWord)) + t := make([]string, 0, len(ignoreWord)) for k := range ignoreWord { t = append(t, k) } @@ -83,7 +83,7 @@ func (a *ignore) String() string { } func (a *ignore) Set(w string) error { - for _, k := range strings.Split(w, ","){ + for _, k := range strings.Split(w, ",") { ignoreWord[k] = true } return nil diff --git a/vendor/github.com/Antonboom/testifylint/analyzer/checkers_factory.go b/vendor/github.com/Antonboom/testifylint/analyzer/checkers_factory.go index de1d6017f5..77573e3952 100644 --- a/vendor/github.com/Antonboom/testifylint/analyzer/checkers_factory.go +++ b/vendor/github.com/Antonboom/testifylint/analyzer/checkers_factory.go @@ -49,6 +49,9 @@ func newCheckers(cfg config.Config) ([]checkers.RegularChecker, []checkers.Advan } switch c := ch.(type) { + case *checkers.BoolCompare: + c.SetIgnoreCustomTypes(cfg.BoolCompare.IgnoreCustomTypes) + case *checkers.ExpectedActual: c.SetExpVarPattern(cfg.ExpectedActual.ExpVarPattern.Regexp) 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 c8db9420e7..43907123bd 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/bool_compare.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/bool_compare.go @@ -25,14 +25,31 @@ import ( // // assert.False(t, result) // assert.True(t, result) -type BoolCompare struct{} // +type BoolCompare struct { + ignoreCustomTypes bool +} // NewBoolCompare constructs BoolCompare checker. -func NewBoolCompare() BoolCompare { return BoolCompare{} } -func (BoolCompare) Name() string { return "bool-compare" } +func NewBoolCompare() *BoolCompare { return new(BoolCompare) } +func (BoolCompare) Name() string { return "bool-compare" } + +func (checker *BoolCompare) SetIgnoreCustomTypes(v bool) *BoolCompare { + checker.ignoreCustomTypes = v + return checker +} func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { - newUseFnDiagnostic := func(proposed string, survivingArg ast.Node, replaceStart, replaceEnd token.Pos) *analysis.Diagnostic { + newBoolCast := func(e ast.Expr) ast.Expr { + return &ast.CallExpr{Fun: &ast.Ident{Name: "bool"}, Args: []ast.Expr{e}} + } + + newUseFnDiagnostic := func(proposed string, survivingArg ast.Expr, replaceStart, replaceEnd token.Pos) *analysis.Diagnostic { + if !isBuiltinBool(pass, survivingArg) { + if checker.ignoreCustomTypes { + return nil + } + survivingArg = newBoolCast(survivingArg) + } return newUseFunctionDiagnostic(checker.Name(), call, proposed, newSuggestedFuncReplacement(call, proposed, analysis.TextEdit{ Pos: replaceStart, @@ -42,15 +59,21 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. ) } - newUseTrueDiagnostic := func(survivingArg ast.Node, replaceStart, replaceEnd token.Pos) *analysis.Diagnostic { + newUseTrueDiagnostic := func(survivingArg ast.Expr, replaceStart, replaceEnd token.Pos) *analysis.Diagnostic { return newUseFnDiagnostic("True", survivingArg, replaceStart, replaceEnd) } - newUseFalseDiagnostic := func(survivingArg ast.Node, replaceStart, replaceEnd token.Pos) *analysis.Diagnostic { + newUseFalseDiagnostic := func(survivingArg ast.Expr, replaceStart, replaceEnd token.Pos) *analysis.Diagnostic { return newUseFnDiagnostic("False", survivingArg, replaceStart, replaceEnd) } - newNeedSimplifyDiagnostic := func(survivingArg ast.Node, replaceStart, replaceEnd token.Pos) *analysis.Diagnostic { + newNeedSimplifyDiagnostic := func(survivingArg ast.Expr, replaceStart, replaceEnd token.Pos) *analysis.Diagnostic { + if !isBuiltinBool(pass, survivingArg) { + if checker.ignoreCustomTypes { + return nil + } + survivingArg = newBoolCast(survivingArg) + } return newDiagnostic(checker.Name(), call, "need to simplify the assertion", &analysis.SuggestedFix{ Message: "Simplify the assertion", @@ -70,7 +93,10 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. } arg1, arg2 := call.Args[0], call.Args[1] - if isEmptyInterface(pass, arg1) || isEmptyInterface(pass, arg2) { + if anyCondSatisfaction(pass, isEmptyInterface, arg1, arg2) { + return nil + } + if anyCondSatisfaction(pass, isBoolOverride, arg1, arg2) { return nil } @@ -80,10 +106,18 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. switch { case xor(t1, t2): survivingArg, _ := anyVal([]bool{t1, t2}, arg2, arg1) + if call.Fn.NameFTrimmed == "Exactly" && !isBuiltinBool(pass, survivingArg) { + // NOTE(a.telyshev): `Exactly` assumes no type casting. + return nil + } return newUseTrueDiagnostic(survivingArg, arg1.Pos(), arg2.End()) case xor(f1, f2): survivingArg, _ := anyVal([]bool{f1, f2}, arg2, arg1) + if call.Fn.NameFTrimmed == "Exactly" && !isBuiltinBool(pass, survivingArg) { + // NOTE(a.telyshev): `Exactly` assumes no type casting. + return nil + } return newUseFalseDiagnostic(survivingArg, arg1.Pos(), arg2.End()) } @@ -93,7 +127,10 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. } arg1, arg2 := call.Args[0], call.Args[1] - if isEmptyInterface(pass, arg1) || isEmptyInterface(pass, arg2) { + if anyCondSatisfaction(pass, isEmptyInterface, arg1, arg2) { + return nil + } + if anyCondSatisfaction(pass, isBoolOverride, arg1, arg2) { return nil } @@ -167,6 +204,26 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. return nil } +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 +} + +func isBuiltinBool(pass *analysis.Pass, e ast.Expr) bool { + basicType, ok := pass.TypesInfo.TypeOf(e).(*types.Basic) + return ok && basicType.Kind() == types.Bool +} + +func isBoolOverride(pass *analysis.Pass, e ast.Expr) bool { + namedType, ok := pass.TypesInfo.TypeOf(e).(*types.Named) + return ok && namedType.Obj().Name() == "bool" +} + var ( falseObj = types.Universe.Lookup("false") trueObj = types.Universe.Lookup("true") @@ -237,12 +294,11 @@ func anyVal[T any](bools []bool, vals ...T) (T, bool) { return _default, false } -func isEmptyInterface(pass *analysis.Pass, expr ast.Expr) bool { - t, ok := pass.TypesInfo.Types[expr] - if !ok { - return false +func anyCondSatisfaction(pass *analysis.Pass, p predicate, vals ...ast.Expr) bool { + for _, v := range vals { + if p(pass, v) { + return true + } } - - iface, ok := t.Type.Underlying().(*types.Interface) - return ok && iface.NumMethods() == 0 + return false } diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/go_require.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/go_require.go index 4c0c9d1fd7..0844f15a09 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/go_require.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/go_require.go @@ -66,6 +66,7 @@ func (checker GoRequire) Check(pass *analysis.Pass, inspector *inspector.Inspect nodesFilter := []ast.Node{ (*ast.FuncDecl)(nil), + (*ast.FuncType)(nil), (*ast.GoStmt)(nil), (*ast.CallExpr)(nil), } @@ -83,6 +84,19 @@ func (checker GoRequire) Check(pass *analysis.Pass, inspector *inspector.Inspect return true } + if ft, ok := node.(*ast.FuncType); ok { + if !isTestingAnonymousFunc(pass, ft) { + return false + } + + if push { + inGoroutineRunningTestFunc.Push(true) + } else { + inGoroutineRunningTestFunc.Pop() + } + return true + } + if _, ok := node.(*ast.GoStmt); ok { if push { inGoroutineRunningTestFunc.Push(false) @@ -107,6 +121,10 @@ func (checker GoRequire) Check(pass *analysis.Pass, inspector *inspector.Inspect if !push { return false } + if inGoroutineRunningTestFunc.Len() == 0 { + // Insufficient info. + return true + } if inGoroutineRunningTestFunc.Last() { // We are in testing goroutine and can skip any assertion checks. return true @@ -252,6 +270,10 @@ func (fd funcDeclarations) Get(pass *analysis.Pass, ce *ast.CallExpr) *ast.FuncD type boolStack []bool +func (s boolStack) Len() int { + return len(s) +} + func (s *boolStack) Push(v bool) { *s = append(*s, v) } @@ -284,15 +306,19 @@ func isSubTestRun(pass *analysis.Pass, ce *ast.CallExpr) bool { } func isTestingFuncOrMethod(pass *analysis.Pass, fd *ast.FuncDecl) bool { - return hasTestingTParam(pass, fd) || isTestifySuiteMethod(pass, fd) + return hasTestingTParam(pass, fd.Type) || isTestifySuiteMethod(pass, fd) +} + +func isTestingAnonymousFunc(pass *analysis.Pass, ft *ast.FuncType) bool { + return hasTestingTParam(pass, ft) } -func hasTestingTParam(pass *analysis.Pass, fd *ast.FuncDecl) bool { - if fd.Type == nil || fd.Type.Params == nil { +func hasTestingTParam(pass *analysis.Pass, ft *ast.FuncType) bool { + if ft == nil || ft.Params == nil { return false } - for _, param := range fd.Type.Params.List { + for _, param := range ft.Params.List { if isTestingTPtr(pass, param.Type) { return true } diff --git a/vendor/github.com/Antonboom/testifylint/internal/config/config.go b/vendor/github.com/Antonboom/testifylint/internal/config/config.go index 6dcfbdb52e..7eba0ea328 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/config/config.go +++ b/vendor/github.com/Antonboom/testifylint/internal/config/config.go @@ -34,11 +34,17 @@ type Config struct { DisableAll bool EnabledCheckers KnownCheckersValue + BoolCompare BoolCompareConfig ExpectedActual ExpectedActualConfig RequireError RequireErrorConfig SuiteExtraAssertCall SuiteExtraAssertCallConfig } +// BoolCompareConfig implements configuration of checkers.BoolCompare. +type BoolCompareConfig struct { + IgnoreCustomTypes bool +} + // ExpectedActualConfig implements configuration of checkers.ExpectedActual. type ExpectedActualConfig struct { ExpVarPattern RegexpValue @@ -91,6 +97,8 @@ func BindToFlags(cfg *Config, fs *flag.FlagSet) { 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.BoolVar(&cfg.BoolCompare.IgnoreCustomTypes, "bool-compare.ignore-custom-types", false, + "ignore user defined types (over builtin bool)") 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), diff --git a/vendor/github.com/catenacyber/perfsprint/analyzer/analyzer.go b/vendor/github.com/catenacyber/perfsprint/analyzer/analyzer.go index ad312ca697..543b4bdbc7 100644 --- a/vendor/github.com/catenacyber/perfsprint/analyzer/analyzer.go +++ b/vendor/github.com/catenacyber/perfsprint/analyzer/analyzer.go @@ -22,6 +22,7 @@ type perfSprint struct { errorf bool sprintf1 bool fiximports bool + strconcat bool } func newPerfSprint() *perfSprint { @@ -31,6 +32,7 @@ func newPerfSprint() *perfSprint { errorf: true, sprintf1: true, fiximports: true, + strconcat: true, } } @@ -47,6 +49,7 @@ func New() *analysis.Analyzer { 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") + r.Flags.BoolVar(&n.strconcat, "strconcat", true, "optimizes into strings concatenation") return r } @@ -59,6 +62,9 @@ func isConcatable(verb string) bool { (strings.HasSuffix(verb, "%s") && !strings.Contains(verb, "%[1]s")) || (strings.HasSuffix(verb, "%[1]s") && !strings.Contains(verb, "%s")) + if strings.Count(verb, "%[1]s") > 1 { + return false + } return (hasPrefix || hasSuffix) && !(hasPrefix && hasSuffix) } @@ -143,7 +149,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { switch verb { default: - if fn == "fmt.Sprintf" && isConcatable(verb) { + if fn == "fmt.Sprintf" && isConcatable(verb) && n.strconcat { break } return @@ -470,10 +476,10 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), - Message: fn + " can be replaced with string addition", + Message: fn + " can be replaced with string concatenation", SuggestedFixes: []analysis.SuggestedFix{ { - Message: "Use string addition", + Message: "Use string concatenation", TextEdits: []analysis.TextEdit{{ Pos: call.Pos(), End: call.End(), diff --git a/vendor/github.com/ckaznocha/intrange/.gitignore b/vendor/github.com/ckaznocha/intrange/.gitignore new file mode 100644 index 0000000000..eb97422caf --- /dev/null +++ b/vendor/github.com/ckaznocha/intrange/.gitignore @@ -0,0 +1 @@ +go.work.sum diff --git a/vendor/github.com/ckaznocha/intrange/.golangci.yml b/vendor/github.com/ckaznocha/intrange/.golangci.yml new file mode 100644 index 0000000000..2ad830d1b2 --- /dev/null +++ b/vendor/github.com/ckaznocha/intrange/.golangci.yml @@ -0,0 +1,99 @@ +linters-settings: + gci: + local-prefixes: github.com/ckaznocha/intrange + gocritic: + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + goimports: + local-prefixes: github.com/ckaznocha/intrange + golint: + min-confidence: 0 + govet: + check-shadowing: true + enable: + - asmdecl + - assign + - atomic + - atomicalign + - bools + - buildtag + - cgocall + - composite + - copylock + - deepequalerrors + - errorsas + - fieldalignment + - findcall + - framepointer + - httpresponse + - ifaceassert + - loopclosure + - lostcancel + - nilfunc + - nilness + - printf + - shadow + - shift + - sortslice + - stdmethods + - stringintconv + - structtag + - testinggoroutine + - tests + - unmarshal + - unreachable + - unsafeptr + - unusedresult + misspell: + locale: US +linters: + disable-all: true + enable: + - asciicheck + - dupl + - errcheck + - errorlint + - exportloopref + - gci + - gochecknoinits + - goconst + - gocritic + - godot + - godox + - goerr113 + - gofmt + - gofumpt + - goimports + - gomnd + - goprintffuncname + - gosec + - gosimple + - govet + - ineffassign + - lll + - misspell + - nakedret + - nestif + - nilerr + - nlreturn + - noctx + - nolintlint + - prealloc + - predeclared + - revive + - rowserrcheck + - staticcheck + - stylecheck + - typecheck + - unconvert + - unused + - wastedassign + - whitespace + - wsl +run: + skip-dirs: + - testdata/ diff --git a/vendor/github.com/esimonov/ifshort/LICENSE b/vendor/github.com/ckaznocha/intrange/LICENSE similarity index 94% rename from vendor/github.com/esimonov/ifshort/LICENSE rename to vendor/github.com/ckaznocha/intrange/LICENSE index a04e339c01..b68bde54b5 100644 --- a/vendor/github.com/esimonov/ifshort/LICENSE +++ b/vendor/github.com/ckaznocha/intrange/LICENSE @@ -1,6 +1,6 @@ -MIT License +The MIT License (MIT) -Copyright (c) 2020 Eugene Simonov +Copyright (c) 2024 Clifton Kaznocha Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/github.com/ckaznocha/intrange/README.md b/vendor/github.com/ckaznocha/intrange/README.md new file mode 100644 index 0000000000..05d78b63a9 --- /dev/null +++ b/vendor/github.com/ckaznocha/intrange/README.md @@ -0,0 +1,54 @@ +# intrange + +[![Build Status](https://github.com/ckaznocha/intrange/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/ckaznocha/intrange/actions/workflows/ci.yml) +[![Release](http://img.shields.io/github/release/ckaznocha/intrange.svg)](https://github.com/ckaznocha/intrange/releases/latest) +[![GoDoc](https://godoc.org/github.com/ckaznocha/intrange?status.svg)](https://godoc.org/github.com/ckaznocha/intrange) + +intrange is a program for checking for loops that could use the [Go 1.22](https://go.dev/ref/spec#Go_1.22) integer +range feature. + +## Installation + +```bash +go install github.com/ckaznocha/intrange/cmd/intrange@latest +``` + +## Usage + +```bash +go vet -vettool=$(which intrange) ./... +``` + +## Example + +```go +package main + +import "fmt" + +func main() { + for i := 0; i < 10; i++ { + fmt.Println(i) + } +} +``` + +Running `intrange` on the above code will produce the following output: + +```bash +main.go:5:2: for loop can be changed to use an integer range (Go 1.22+) +``` + +The loop can be rewritten as: + +```go +package main + +import "fmt" + +func main() { + for i := range 10 { + fmt.Println(i) + } +} +``` diff --git a/vendor/github.com/ckaznocha/intrange/SECURITY.md b/vendor/github.com/ckaznocha/intrange/SECURITY.md new file mode 100644 index 0000000000..e2c44c4e21 --- /dev/null +++ b/vendor/github.com/ckaznocha/intrange/SECURITY.md @@ -0,0 +1,5 @@ +# Security Policy + +## Reporting a Vulnerability + +Please open a [github issue](https://github.com/ckaznocha/intrange/issues) diff --git a/vendor/github.com/ckaznocha/intrange/go.work b/vendor/github.com/ckaznocha/intrange/go.work new file mode 100644 index 0000000000..f41a04a2fb --- /dev/null +++ b/vendor/github.com/ckaznocha/intrange/go.work @@ -0,0 +1,6 @@ +go 1.22.0 + +use ( + . + ./testdata +) diff --git a/vendor/github.com/ckaznocha/intrange/intrange.go b/vendor/github.com/ckaznocha/intrange/intrange.go new file mode 100644 index 0000000000..0a7ae3d95f --- /dev/null +++ b/vendor/github.com/ckaznocha/intrange/intrange.go @@ -0,0 +1,300 @@ +package intrange + +import ( + "errors" + "fmt" + "go/ast" + "go/token" + "strconv" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" +) + +var ( + Analyzer = &analysis.Analyzer{ + Name: "intrange", + Doc: "intrange is a linter to find places where for loops could make use of an integer range.", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + } + + errFailedAnalysis = errors.New("failed analysis") +) + +const msg = "for loop can be changed to use an integer range (Go 1.22+)" + +func run(pass *analysis.Pass) (any, error) { + result, ok := pass.ResultOf[inspect.Analyzer] + if !ok { + return nil, fmt.Errorf( + "%w: %s", + errFailedAnalysis, + inspect.Analyzer.Name, + ) + } + + resultInspector, ok := result.(*inspector.Inspector) + if !ok { + return nil, fmt.Errorf( + "%w: %s", + errFailedAnalysis, + inspect.Analyzer.Name, + ) + } + + resultInspector.Preorder([]ast.Node{(*ast.ForStmt)(nil)}, check(pass)) + + return nil, nil +} + +func check(pass *analysis.Pass) func(node ast.Node) { + return func(node ast.Node) { + forStmt, ok := node.(*ast.ForStmt) + if !ok { + return + } + + if forStmt.Init == nil || forStmt.Cond == nil || forStmt.Post == nil { + return + } + + // i := 0;; + init, ok := forStmt.Init.(*ast.AssignStmt) + if !ok { + return + } + + if len(init.Lhs) != 1 || len(init.Rhs) != 1 { + return + } + + initIdent, ok := init.Lhs[0].(*ast.Ident) + if !ok { + return + } + + if !compareNumberLit(init.Rhs[0], 0) { + return + } + + cond, ok := forStmt.Cond.(*ast.BinaryExpr) + if !ok { + return + } + + switch cond.Op { + case token.LSS: // ;i < x; + x, ok := cond.X.(*ast.Ident) + if !ok { + return + } + + if x.Name != initIdent.Name { + return + } + case token.GTR: // ;x > i; + y, ok := cond.Y.(*ast.Ident) + if !ok { + return + } + + if y.Name != initIdent.Name { + return + } + default: + return + } + + switch post := forStmt.Post.(type) { + case *ast.IncDecStmt: // ;;i++ + if post.Tok != token.INC { + return + } + + ident, ok := post.X.(*ast.Ident) + if !ok { + return + } + + if ident.Name != initIdent.Name { + return + } + case *ast.AssignStmt: + switch post.Tok { + case token.ADD_ASSIGN: // ;;i += 1 + if len(post.Lhs) != 1 { + return + } + + ident, ok := post.Lhs[0].(*ast.Ident) + if !ok { + return + } + + if ident.Name != initIdent.Name { + return + } + + if len(post.Rhs) != 1 { + return + } + + if !compareNumberLit(post.Rhs[0], 1) { + return + } + case token.ASSIGN: // ;;i = i + 1 && ;;i = 1 + i + if len(post.Lhs) != 1 || len(post.Rhs) != 1 { + return + } + + ident, ok := post.Lhs[0].(*ast.Ident) + if !ok { + return + } + + if ident.Name != initIdent.Name { + return + } + + bin, ok := post.Rhs[0].(*ast.BinaryExpr) + if !ok { + return + } + + if bin.Op != token.ADD { + return + } + + switch x := bin.X.(type) { + case *ast.Ident: // ;;i = i + 1 + if x.Name != initIdent.Name { + return + } + + if !compareNumberLit(bin.Y, 1) { + return + } + case *ast.BasicLit: // ;;i = 1 + i + if !compareNumberLit(x, 1) { + return + } + + ident, ok := bin.Y.(*ast.Ident) + if !ok { + return + } + + if ident.Name != initIdent.Name { + return + } + default: + return + } + default: + return + } + default: + return + } + + bc := &bodyChecker{ + initIdent: initIdent, + } + + ast.Inspect(forStmt.Body, bc.check) + + if bc.modified { + return + } + + pass.Report(analysis.Diagnostic{ + Pos: forStmt.Pos(), + Message: msg, + }) + } +} + +type bodyChecker struct { + initIdent *ast.Ident + modified bool +} + +func (b *bodyChecker) check(n ast.Node) bool { + switch stmt := n.(type) { + case *ast.AssignStmt: + for _, lhs := range stmt.Lhs { + ident, ok := lhs.(*ast.Ident) + if !ok { + continue + } + + if b.initIdent.Name == ident.Name { + b.modified = true + + return false + } + } + case *ast.IncDecStmt: + ident, ok := stmt.X.(*ast.Ident) + if !ok { + return true + } + + if b.initIdent.Name == ident.Name { + b.modified = true + + return false + } + } + + return true +} + +func compareNumberLit(exp ast.Expr, val int) bool { + switch lit := exp.(type) { + case *ast.BasicLit: + if lit.Kind != token.INT { + return false + } + + n := strconv.Itoa(val) + + switch lit.Value { + case n, "0x" + n, "0X" + n: + return true + default: + return false + } + case *ast.CallExpr: + switch fun := lit.Fun.(type) { + case *ast.Ident: + switch fun.Name { + case + "int", + "int8", + "int16", + "int32", + "int64", + "uint", + "uint8", + "uint16", + "uint32", + "uint64": + default: + return false + } + default: + return false + } + + if len(lit.Args) != 1 { + return false + } + + return compareNumberLit(lit.Args[0], val) + default: + return false + } +} diff --git a/vendor/github.com/daixiang0/gci/pkg/gci/testdata.go b/vendor/github.com/daixiang0/gci/pkg/gci/testdata.go index 77c06dc630..4f60e88069 100644 --- a/vendor/github.com/daixiang0/gci/pkg/gci/testdata.go +++ b/vendor/github.com/daixiang0/gci/pkg/gci/testdata.go @@ -1273,6 +1273,26 @@ import ( testing "github.com/daixiang0/test" g "github.com/golang" ) +`, + }, + { + "no-trailing-newline", + + `sections: + - Standard +`, + `package main + +import ( + "net" + "fmt" +)`, + `package main + +import ( + "fmt" + "net" +) `, }, } diff --git a/vendor/github.com/daixiang0/gci/pkg/parse/parse.go b/vendor/github.com/daixiang0/gci/pkg/parse/parse.go index 33d6e1705d..e8532f850d 100644 --- a/vendor/github.com/daixiang0/gci/pkg/parse/parse.go +++ b/vendor/github.com/daixiang0/gci/pkg/parse/parse.go @@ -111,6 +111,9 @@ func ParseFile(src []byte, filename string) (ImportList, int, int, int, int, err headEnd = int(decl.Pos()) - 1 } tailStart = int(decl.End()) + if tailStart > len(src) { + tailStart = len(src) + } for _, spec := range genDecl.Specs { imp := spec.(*ast.ImportSpec) diff --git a/vendor/github.com/daixiang0/gci/pkg/section/standard_list.go b/vendor/github.com/daixiang0/gci/pkg/section/standard_list.go index 05e7999396..551bba4285 100644 --- a/vendor/github.com/daixiang0/gci/pkg/section/standard_list.go +++ b/vendor/github.com/daixiang0/gci/pkg/section/standard_list.go @@ -1,6 +1,6 @@ package section -// Code generated based on go1.21.0 X:arenas. DO NOT EDIT. +// Code generated based on go1.22.0 X:arenas. DO NOT EDIT. var standardPackages = map[string]struct{}{ "archive/tar": {}, @@ -78,6 +78,7 @@ var standardPackages = map[string]struct{}{ "go/scanner": {}, "go/token": {}, "go/types": {}, + "go/version": {}, "hash": {}, "hash/adler32": {}, "hash/crc32": {}, @@ -106,6 +107,7 @@ var standardPackages = map[string]struct{}{ "math/bits": {}, "math/cmplx": {}, "math/rand": {}, + "math/rand/v2": {}, "mime": {}, "mime/multipart": {}, "mime/quotedprintable": {}, diff --git a/vendor/github.com/denis-tingaikin/go-header/.go-header.yml b/vendor/github.com/denis-tingaikin/go-header/.go-header.yml index 3f87c8798d..3aa6d060db 100644 --- a/vendor/github.com/denis-tingaikin/go-header/.go-header.yml +++ b/vendor/github.com/denis-tingaikin/go-header/.go-header.yml @@ -1,6 +1,6 @@ values: regexp: - copyright-holder: Copyright \(c\) {{year-range}} Denis Tingaikin + copyright-holder: Copyright \(c\) {{mod-year-range}} Denis Tingaikin template: | {{copyright-holder}} diff --git a/vendor/github.com/denis-tingaikin/go-header/README.md b/vendor/github.com/denis-tingaikin/go-header/README.md index c2044ec96e..fcddad1fa1 100644 --- a/vendor/github.com/denis-tingaikin/go-header/README.md +++ b/vendor/github.com/denis-tingaikin/go-header/README.md @@ -8,7 +8,7 @@ Go source code linter providing checks for license headers. For installation you can simply use `go get`. ```bash -go get github.com/denis-tingaikin/go-header/cmd/go-header +go install github.com/denis-tingaikin/go-header/cmd/go-header ``` ## Configuration @@ -38,6 +38,8 @@ values: ## Bult-in values +- **MOD-YEAR** - Returns the year when the file was modified. +- **MOD-YEAR-RANGE** - Returns a year-range where the range starts from the year when the file was modified. - **YEAR** - Expects current year. Example header value: `2020`. Example of template using: `{{YEAR}}` or `{{year}}`. - **YEAR-RANGE** - Expects any valid year interval or current year. Example header value: `2020` or `2000-2020`. Example of template using: `{{year-range}}` or `{{YEAR-RANGE}}`. diff --git a/vendor/github.com/denis-tingaikin/go-header/analyzer.go b/vendor/github.com/denis-tingaikin/go-header/analyzer.go index 785a02e79d..c6b361f01d 100644 --- a/vendor/github.com/denis-tingaikin/go-header/analyzer.go +++ b/vendor/github.com/denis-tingaikin/go-header/analyzer.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022 Denis Tingaikin +// Copyright (c) 2020-2024 Denis Tingaikin // // SPDX-License-Identifier: Apache-2.0 // @@ -52,15 +52,31 @@ type Analyzer struct { template string } -func (a *Analyzer) Analyze(target *Target) Issue { +func (a *Analyzer) processPerTargetValues(target *Target) error { + a.values["mod-year"] = a.values["year"] + a.values["mod-year-range"] = a.values["year-range"] + if t, err := target.ModTime(); err == nil { + a.values["mod-year"] = &ConstValue{RawValue: fmt.Sprint(t.Year())} + a.values["mod-year-range"] = &RegexpValue{RawValue: `((20\d\d\-{{mod-year}})|({{mod-year}}))`} + } + + for _, v := range a.values { + if err := v.Calculate(a.values); err != nil { + return err + } + } + return nil +} + +func (a *Analyzer) Analyze(target *Target) (i Issue) { if a.template == "" { return NewIssue("Missed template for check") } - if t, err := target.ModTime(); err == nil { - if t.Year() != time.Now().Year() { - return nil - } + + if err := a.processPerTargetValues(target); err != nil { + return &issue{msg: err.Error()} } + file := target.File var header string var offset = Location{ @@ -74,6 +90,16 @@ func (a *Analyzer) Analyze(target *Target) Issue { offset.Position += 3 } } + defer func() { + if i == nil { + return + } + fix, ok := a.generateFix(i, file, header) + if !ok { + return + } + i = NewIssueWithFix(i.Message(), i.Location(), fix) + }() header = strings.TrimSpace(header) if header == "" { return NewIssue("Missed header for check") @@ -132,15 +158,99 @@ func (a *Analyzer) readField(reader *Reader) string { } func New(options ...Option) *Analyzer { - a := &Analyzer{} + a := &Analyzer{values: make(map[string]Value)} for _, o := range options { o.apply(a) } - for _, v := range a.values { - err := v.Calculate(a.values) - if err != nil { - panic(err.Error()) + return a +} + +func (a *Analyzer) generateFix(i Issue, file *ast.File, header string) (Fix, bool) { + var expect string + t := NewReader(a.template) + for !t.Done() { + ch := t.Peek() + if ch == '{' { + f := a.values[a.readField(t)] + if f == nil { + return Fix{}, false + } + if f.Calculate(a.values) != nil { + return Fix{}, false + } + expect += f.Get() + continue } + + expect += string(ch) + t.Next() } - return a + + fix := Fix{Expected: strings.Split(expect, "\n")} + if !(len(file.Comments) > 0 && file.Comments[0].Pos() < file.Package) { + for i := range fix.Expected { + fix.Expected[i] = "// " + fix.Expected[i] + } + return fix, true + } + + actual := file.Comments[0].List[0].Text + if !strings.HasPrefix(actual, "/*") { + for i := range fix.Expected { + fix.Expected[i] = "// " + fix.Expected[i] + } + for _, c := range file.Comments[0].List { + fix.Actual = append(fix.Actual, c.Text) + } + i = NewIssueWithFix(i.Message(), i.Location(), fix) + return fix, true + } + + gets := func(i int, end bool) string { + if i < 0 { + return header + } + if end { + return header[i+1:] + } + return header[:i] + } + start := strings.Index(actual, gets(strings.IndexByte(header, '\n'), false)) + if start < 0 { + return Fix{}, false // Should be impossible + } + nl := strings.LastIndexByte(actual[:start], '\n') + if nl >= 0 { + fix.Actual = strings.Split(actual[:nl], "\n") + fix.Expected = append(fix.Actual, fix.Expected...) + actual = actual[nl+1:] + start -= nl + 1 + } + + prefix := actual[:start] + if nl < 0 { + fix.Expected[0] = prefix + fix.Expected[0] + } else { + n := len(fix.Actual) + for i := range fix.Expected[n:] { + fix.Expected[n+i] = prefix + fix.Expected[n+i] + } + } + + last := gets(strings.LastIndexByte(header, '\n'), true) + end := strings.Index(actual, last) + if end < 0 { + return Fix{}, false // Should be impossible + } + + trailing := actual[end+len(last):] + if i := strings.IndexRune(trailing, '\n'); i < 0 { + fix.Expected[len(fix.Expected)-1] += trailing + } else { + fix.Expected[len(fix.Expected)-1] += trailing[:i] + fix.Expected = append(fix.Expected, strings.Split(trailing[i+1:], "\n")...) + } + + fix.Actual = append(fix.Actual, strings.Split(actual, "\n")...) + return fix, true } diff --git a/vendor/github.com/denis-tingaikin/go-header/config.go b/vendor/github.com/denis-tingaikin/go-header/config.go index 9576b949fd..c881b63acd 100644 --- a/vendor/github.com/denis-tingaikin/go-header/config.go +++ b/vendor/github.com/denis-tingaikin/go-header/config.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022 Denis Tingaikin +// Copyright (c) 2020-2024 Denis Tingaikin // // SPDX-License-Identifier: Apache-2.0 // @@ -19,7 +19,7 @@ package goheader import ( "errors" "fmt" - "io/ioutil" + "os" "strings" "time" @@ -40,7 +40,7 @@ func (c *Configuration) builtInValues() map[string]Value { var result = make(map[string]Value) year := fmt.Sprint(time.Now().Year()) result["year-range"] = &RegexpValue{ - RawValue: strings.ReplaceAll(`((20\d\d\-YEAR)|(YEAR))`, "YEAR", year), + RawValue: `((20\d\d\-{{YEAR}})|({{YEAR}}))`, } result["year"] = &ConstValue{ RawValue: year, @@ -82,7 +82,7 @@ func (c *Configuration) GetTemplate() (string, error) { if c.TemplatePath == "" { return "", errors.New("template has not passed") } - if b, err := ioutil.ReadFile(c.TemplatePath); err != nil { + if b, err := os.ReadFile(c.TemplatePath); err != nil { return "", err } else { c.Template = strings.TrimSpace(string(b)) @@ -91,7 +91,7 @@ func (c *Configuration) GetTemplate() (string, error) { } func (c *Configuration) Parse(p string) error { - b, err := ioutil.ReadFile(p) + b, err := os.ReadFile(p) if err != nil { return err } diff --git a/vendor/github.com/denis-tingaikin/go-header/issue.go b/vendor/github.com/denis-tingaikin/go-header/issue.go index 0ada0d62cf..e92279793c 100644 --- a/vendor/github.com/denis-tingaikin/go-header/issue.go +++ b/vendor/github.com/denis-tingaikin/go-header/issue.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022 Denis Tingaikin +// Copyright (c) 2020-2024 Denis Tingaikin // // SPDX-License-Identifier: Apache-2.0 // @@ -19,11 +19,18 @@ package goheader type Issue interface { Location() Location Message() string + Fix() *Fix } type issue struct { msg string location Location + fix *Fix +} + +type Fix struct { + Actual []string + Expected []string } func (i *issue) Location() Location { @@ -34,6 +41,10 @@ func (i *issue) Message() string { return i.msg } +func (i *issue) Fix() *Fix { + return i.fix +} + func NewIssueWithLocation(msg string, location Location) Issue { return &issue{ msg: msg, @@ -41,6 +52,14 @@ func NewIssueWithLocation(msg string, location Location) Issue { } } +func NewIssueWithFix(msg string, location Location, fix Fix) Issue { + return &issue{ + msg: msg, + location: location, + fix: &fix, + } +} + func NewIssue(msg string) Issue { return &issue{ msg: msg, diff --git a/vendor/github.com/denis-tingaikin/go-header/value.go b/vendor/github.com/denis-tingaikin/go-header/value.go index dcb206d353..706a84f18a 100644 --- a/vendor/github.com/denis-tingaikin/go-header/value.go +++ b/vendor/github.com/denis-tingaikin/go-header/value.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022 Denis Tingaikin +// Copyright (c) 2020-2024 Denis Tingaikin // // SPDX-License-Identifier: Apache-2.0 // @@ -26,6 +26,7 @@ import ( type Calculable interface { Calculate(map[string]Value) error Get() string + Raw() string } type Value interface { @@ -35,7 +36,7 @@ type Value interface { func calculateValue(calculable Calculable, values map[string]Value) (string, error) { sb := strings.Builder{} - r := calculable.Get() + r := calculable.Raw() var endIndex int var startIndex int for startIndex = strings.Index(r, "{{"); startIndex >= 0; startIndex = strings.Index(r, "{{") { @@ -61,7 +62,7 @@ func calculateValue(calculable Calculable, values map[string]Value) (string, err } type ConstValue struct { - RawValue string + RawValue, Value string } func (c *ConstValue) Calculate(values map[string]Value) error { @@ -69,14 +70,25 @@ func (c *ConstValue) Calculate(values map[string]Value) error { if err != nil { return err } - c.RawValue = v + c.Value = v return nil } +func (c *ConstValue) Raw() string { + return c.RawValue +} + func (c *ConstValue) Get() string { + if c.Value != "" { + return c.Value + } return c.RawValue } +func (c *ConstValue) String() string { + return c.Get() +} + func (c *ConstValue) Read(s *Reader) Issue { l := s.Location() p := s.Position() @@ -94,7 +106,7 @@ func (c *ConstValue) Read(s *Reader) Issue { } type RegexpValue struct { - RawValue string + RawValue, Value string } func (r *RegexpValue) Calculate(values map[string]Value) error { @@ -102,14 +114,24 @@ func (r *RegexpValue) Calculate(values map[string]Value) error { if err != nil { return err } - r.RawValue = v + r.Value = v return nil } +func (r *RegexpValue) Raw() string { + return r.RawValue +} func (r *RegexpValue) Get() string { + if r.Value != "" { + return r.Value + } return r.RawValue } +func (r *RegexpValue) String() string { + return r.Get() +} + func (r *RegexpValue) Read(s *Reader) Issue { l := s.Location() p := regexp.MustCompile(r.Get()) diff --git a/vendor/github.com/esimonov/ifshort/pkg/analyzer/analyzer.go b/vendor/github.com/esimonov/ifshort/pkg/analyzer/analyzer.go deleted file mode 100644 index b2d06881d7..0000000000 --- a/vendor/github.com/esimonov/ifshort/pkg/analyzer/analyzer.go +++ /dev/null @@ -1,280 +0,0 @@ -package analyzer - -import ( - "go/ast" - "go/token" - - "golang.org/x/tools/go/analysis" - "golang.org/x/tools/go/analysis/passes/inspect" - "golang.org/x/tools/go/ast/inspector" -) - -var maxDeclChars, maxDeclLines int - -const ( - maxDeclLinesUsage = `maximum length of variable declaration measured in number of lines, after which the linter won't suggest using short syntax. -Has precedence over max-decl-chars.` - maxDeclCharsUsage = `maximum length of variable declaration measured in number of characters, after which the linter won't suggest using short syntax.` -) - -func init() { - Analyzer.Flags.IntVar(&maxDeclLines, "max-decl-lines", 1, maxDeclLinesUsage) - Analyzer.Flags.IntVar(&maxDeclChars, "max-decl-chars", 30, maxDeclCharsUsage) -} - -// Analyzer is an analysis.Analyzer instance for ifshort linter. -var Analyzer = &analysis.Analyzer{ - Name: "ifshort", - Doc: "Checks that your code uses short syntax for if-statements whenever possible.", - Run: run, - Requires: []*analysis.Analyzer{inspect.Analyzer}, -} - -func run(pass *analysis.Pass) (interface{}, error) { - inspector := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) - nodeFilter := []ast.Node{ - (*ast.FuncDecl)(nil), - } - - inspector.Preorder(nodeFilter, func(node ast.Node) { - fdecl := node.(*ast.FuncDecl) - - /*if fdecl.Name.Name != "notUsed_BinaryExpressionInIndex_OK" { - return - }*/ - - if fdecl == nil || fdecl.Body == nil { - return - } - - candidates := getNamedOccurrenceMap(fdecl, pass) - - for _, stmt := range fdecl.Body.List { - candidates.checkStatement(stmt, token.NoPos) - } - - for varName := range candidates { - for marker, occ := range candidates[varName] { - // If two or more vars with the same scope marker - skip them. - if candidates.isFoundByScopeMarker(marker) { - continue - } - - pass.Reportf(occ.declarationPos, - "variable '%s' is only used in the if-statement (%s); consider using short syntax", - varName, pass.Fset.Position(occ.ifStmtPos)) - } - } - }) - return nil, nil -} - -func (nom namedOccurrenceMap) checkStatement(stmt ast.Stmt, ifPos token.Pos) { - switch v := stmt.(type) { - case *ast.AssignStmt: - for _, el := range v.Rhs { - nom.checkExpression(el, ifPos) - } - if isAssign(v.Tok) { - for _, el := range v.Lhs { - nom.checkExpression(el, ifPos) - } - } - case *ast.DeferStmt: - for _, a := range v.Call.Args { - nom.checkExpression(a, ifPos) - } - case *ast.ExprStmt: - switch v.X.(type) { - case *ast.CallExpr, *ast.UnaryExpr: - nom.checkExpression(v.X, ifPos) - } - case *ast.ForStmt: - for _, el := range v.Body.List { - nom.checkStatement(el, ifPos) - } - - if bexpr, ok := v.Cond.(*ast.BinaryExpr); ok { - nom.checkExpression(bexpr.X, ifPos) - nom.checkExpression(bexpr.Y, ifPos) - } - - nom.checkStatement(v.Post, ifPos) - case *ast.GoStmt: - for _, a := range v.Call.Args { - nom.checkExpression(a, ifPos) - } - case *ast.IfStmt: - for _, el := range v.Body.List { - nom.checkStatement(el, v.If) - } - if elseBlock, ok := v.Else.(*ast.BlockStmt); ok { - for _, el := range elseBlock.List { - nom.checkStatement(el, v.If) - } - } - - switch cond := v.Cond.(type) { - case *ast.UnaryExpr: - nom.checkExpression(cond.X, v.If) - case *ast.BinaryExpr: - nom.checkExpression(cond.X, v.If) - nom.checkExpression(cond.Y, v.If) - case *ast.CallExpr: - nom.checkExpression(cond, v.If) - } - - if init, ok := v.Init.(*ast.AssignStmt); ok { - for _, e := range init.Rhs { - nom.checkExpression(e, v.If) - } - } - case *ast.IncDecStmt: - nom.checkExpression(v.X, ifPos) - case *ast.RangeStmt: - nom.checkExpression(v.X, ifPos) - if v.Body != nil { - for _, e := range v.Body.List { - nom.checkStatement(e, ifPos) - } - } - case *ast.ReturnStmt: - for _, r := range v.Results { - nom.checkExpression(r, ifPos) - } - case *ast.SendStmt: - nom.checkExpression(v.Chan, ifPos) - nom.checkExpression(v.Value, ifPos) - case *ast.SwitchStmt: - nom.checkExpression(v.Tag, ifPos) - - for _, el := range v.Body.List { - clauses, ok := el.(*ast.CaseClause) - if !ok { - continue - } - - for _, c := range clauses.List { - switch v := c.(type) { - case *ast.BinaryExpr: - nom.checkExpression(v.X, ifPos) - nom.checkExpression(v.Y, ifPos) - case *ast.Ident: - nom.checkExpression(v, ifPos) - } - } - - for _, c := range clauses.Body { - switch v := c.(type) { - case *ast.AssignStmt: - for _, el := range v.Lhs { - nom.checkExpression(el, ifPos) - } - for _, el := range v.Rhs { - nom.checkExpression(el, ifPos) - } - case *ast.ExprStmt: - nom.checkExpression(v.X, ifPos) - } - } - } - case *ast.SelectStmt: - for _, el := range v.Body.List { - clause := el.(*ast.CommClause) - - nom.checkStatement(clause.Comm, ifPos) - - for _, c := range clause.Body { - switch v := c.(type) { - case *ast.AssignStmt: - for _, el := range v.Lhs { - nom.checkExpression(el, ifPos) - } - for _, el := range v.Rhs { - nom.checkExpression(el, ifPos) - } - case *ast.ExprStmt: - nom.checkExpression(v.X, ifPos) - } - } - } - case *ast.LabeledStmt: - nom.checkStatement(v.Stmt, ifPos) - } -} - -func (nom namedOccurrenceMap) checkExpression(candidate ast.Expr, ifPos token.Pos) { - switch v := candidate.(type) { - case *ast.BinaryExpr: - nom.checkExpression(v.X, ifPos) - nom.checkExpression(v.Y, ifPos) - case *ast.CallExpr: - for _, arg := range v.Args { - nom.checkExpression(arg, ifPos) - } - nom.checkExpression(v.Fun, ifPos) - if fun, ok := v.Fun.(*ast.SelectorExpr); ok { - nom.checkExpression(fun.X, ifPos) - } - case *ast.CompositeLit: - for _, el := range v.Elts { - switch v := el.(type) { - case *ast.Ident, *ast.CompositeLit: - nom.checkExpression(v, ifPos) - case *ast.KeyValueExpr: - nom.checkExpression(v.Key, ifPos) - nom.checkExpression(v.Value, ifPos) - case *ast.SelectorExpr: - nom.checkExpression(v.X, ifPos) - } - } - case *ast.FuncLit: - for _, el := range v.Body.List { - nom.checkStatement(el, ifPos) - } - case *ast.Ident: - if _, ok := nom[v.Name]; !ok || nom[v.Name].isEmponymousKey(ifPos) { - return - } - - scopeMarker1 := nom[v.Name].getScopeMarkerForPosition(v.Pos()) - - delete(nom[v.Name], scopeMarker1) - - for k := range nom { - for scopeMarker2 := range nom[k] { - if scopeMarker1 == scopeMarker2 { - delete(nom[k], scopeMarker2) - } - } - } - case *ast.StarExpr: - nom.checkExpression(v.X, ifPos) - case *ast.IndexExpr: - nom.checkExpression(v.X, ifPos) - switch index := v.Index.(type) { - case *ast.BinaryExpr: - nom.checkExpression(index.X, ifPos) - case *ast.Ident: - nom.checkExpression(index, ifPos) - } - case *ast.SelectorExpr: - nom.checkExpression(v.X, ifPos) - case *ast.SliceExpr: - nom.checkExpression(v.High, ifPos) - nom.checkExpression(v.Low, ifPos) - nom.checkExpression(v.X, ifPos) - case *ast.TypeAssertExpr: - nom.checkExpression(v.X, ifPos) - case *ast.UnaryExpr: - nom.checkExpression(v.X, ifPos) - } -} - -func isAssign(tok token.Token) bool { - return (tok == token.ASSIGN || - tok == token.ADD_ASSIGN || tok == token.SUB_ASSIGN || - tok == token.MUL_ASSIGN || tok == token.QUO_ASSIGN || tok == token.REM_ASSIGN || - tok == token.AND_ASSIGN || tok == token.OR_ASSIGN || tok == token.XOR_ASSIGN || tok == token.AND_NOT_ASSIGN || - tok == token.SHL_ASSIGN || tok == token.SHR_ASSIGN) -} diff --git a/vendor/github.com/esimonov/ifshort/pkg/analyzer/occurrences.go b/vendor/github.com/esimonov/ifshort/pkg/analyzer/occurrences.go deleted file mode 100644 index 0d3793a57e..0000000000 --- a/vendor/github.com/esimonov/ifshort/pkg/analyzer/occurrences.go +++ /dev/null @@ -1,268 +0,0 @@ -package analyzer - -import ( - "go/ast" - "go/token" - "time" - - "golang.org/x/tools/go/analysis" -) - -// occurrence is a variable occurrence. -type occurrence struct { - declarationPos token.Pos - ifStmtPos token.Pos -} - -func (occ *occurrence) isComplete() bool { - return occ.ifStmtPos != token.NoPos && occ.declarationPos != token.NoPos -} - -// scopeMarkeredOccurences is a map of scope markers to variable occurrences. -type scopeMarkeredOccurences map[int64]occurrence - -func (smo scopeMarkeredOccurences) getGreatestMarker() int64 { - var maxScopeMarker int64 - - for marker := range smo { - if marker > maxScopeMarker { - maxScopeMarker = marker - } - } - return maxScopeMarker -} - -// find scope marker of the greatest token.Pos that is smaller than provided. -func (smo scopeMarkeredOccurences) getScopeMarkerForPosition(pos token.Pos) int64 { - var m int64 - var foundPos token.Pos - - for marker, occ := range smo { - if occ.declarationPos < pos && occ.declarationPos >= foundPos { - m = marker - foundPos = occ.declarationPos - } - } - return m -} - -func (smo scopeMarkeredOccurences) isEmponymousKey(pos token.Pos) bool { - if pos == token.NoPos { - return false - } - - for _, occ := range smo { - if occ.ifStmtPos == pos { - return true - } - } - return false -} - -// namedOccurrenceMap is a map of variable names to scopeMarkeredOccurences. -type namedOccurrenceMap map[string]scopeMarkeredOccurences - -func getNamedOccurrenceMap(fdecl *ast.FuncDecl, pass *analysis.Pass) namedOccurrenceMap { - nom := namedOccurrenceMap(map[string]scopeMarkeredOccurences{}) - - if fdecl == nil || fdecl.Body == nil { - return nom - } - - for _, stmt := range fdecl.Body.List { - switch v := stmt.(type) { - case *ast.AssignStmt: - nom.addFromAssignment(pass, v) - case *ast.IfStmt: - nom.addFromCondition(v) - nom.addFromIfClause(v) - nom.addFromElseClause(v) - } - } - - candidates := namedOccurrenceMap(map[string]scopeMarkeredOccurences{}) - - for varName, markeredOccs := range nom { - for marker, occ := range markeredOccs { - if !occ.isComplete() && !nom.isFoundByScopeMarker(marker) { - continue - } - if _, ok := candidates[varName]; !ok { - candidates[varName] = scopeMarkeredOccurences{ - marker: occ, - } - } else { - candidates[varName][marker] = occ - } - } - } - return candidates -} - -func (nom namedOccurrenceMap) isFoundByScopeMarker(scopeMarker int64) bool { - var i int - - for _, markeredOccs := range nom { - for marker := range markeredOccs { - if marker == scopeMarker { - i++ - } - } - } - return i >= 2 -} - -func (nom namedOccurrenceMap) addFromAssignment(pass *analysis.Pass, assignment *ast.AssignStmt) { - if assignment.Tok != token.DEFINE { - return - } - - scopeMarker := time.Now().UnixNano() - - for i, el := range assignment.Lhs { - ident, ok := el.(*ast.Ident) - if !ok { - continue - } - - if ident.Name == "_" || ident.Obj == nil || isUnshortenableAssignment(ident.Obj.Decl) { - continue - } - - if markeredOccs, ok := nom[ident.Name]; ok { - markeredOccs[scopeMarker] = occurrence{ - declarationPos: ident.Pos(), - } - nom[ident.Name] = markeredOccs - } else { - newOcc := occurrence{} - if areFlagSettingsSatisfied(pass, assignment, i) { - newOcc.declarationPos = ident.Pos() - } - nom[ident.Name] = scopeMarkeredOccurences{scopeMarker: newOcc} - } - } -} - -func isUnshortenableAssignment(decl interface{}) bool { - assign, ok := decl.(*ast.AssignStmt) - if !ok { - return false - } - - for _, el := range assign.Rhs { - u, ok := el.(*ast.UnaryExpr) - if !ok { - continue - } - - if u.Op == token.AND { - if _, ok := u.X.(*ast.CompositeLit); ok { - return true - } - } - } - return false -} - -func areFlagSettingsSatisfied(pass *analysis.Pass, assignment *ast.AssignStmt, i int) bool { - lh := assignment.Lhs[i] - rh := assignment.Rhs[len(assignment.Rhs)-1] - - if len(assignment.Rhs) == len(assignment.Lhs) { - rh = assignment.Rhs[i] - } - - if pass.Fset.Position(rh.End()).Line-pass.Fset.Position(rh.Pos()).Line > maxDeclLines { - return false - } - if int(rh.End()-lh.Pos()) > maxDeclChars { - return false - } - return true -} - -func (nom namedOccurrenceMap) addFromCondition(stmt *ast.IfStmt) { - switch v := stmt.Cond.(type) { - case *ast.BinaryExpr: - for _, v := range [2]ast.Expr{v.X, v.Y} { - switch e := v.(type) { - case *ast.CallExpr: - nom.addFromCallExpr(stmt.If, e) - case *ast.Ident: - nom.addFromIdent(stmt.If, e) - case *ast.SelectorExpr: - nom.addFromIdent(stmt.If, e.X) - } - } - case *ast.CallExpr: - for _, a := range v.Args { - switch e := a.(type) { - case *ast.Ident: - nom.addFromIdent(stmt.If, e) - case *ast.CallExpr: - nom.addFromCallExpr(stmt.If, e) - } - } - case *ast.Ident: - nom.addFromIdent(stmt.If, v) - case *ast.UnaryExpr: - switch e := v.X.(type) { - case *ast.Ident: - nom.addFromIdent(stmt.If, e) - case *ast.SelectorExpr: - nom.addFromIdent(stmt.If, e.X) - } - } -} - -func (nom namedOccurrenceMap) addFromIfClause(stmt *ast.IfStmt) { - nom.addFromBlockStmt(stmt.Body, stmt.If) -} - -func (nom namedOccurrenceMap) addFromElseClause(stmt *ast.IfStmt) { - nom.addFromBlockStmt(stmt.Else, stmt.If) -} - -func (nom namedOccurrenceMap) addFromBlockStmt(stmt ast.Stmt, ifPos token.Pos) { - blockStmt, ok := stmt.(*ast.BlockStmt) - if !ok { - return - } - - for _, el := range blockStmt.List { - exptStmt, ok := el.(*ast.ExprStmt) - if !ok { - continue - } - - if callExpr, ok := exptStmt.X.(*ast.CallExpr); ok { - nom.addFromCallExpr(ifPos, callExpr) - } - } -} - -func (nom namedOccurrenceMap) addFromCallExpr(ifPos token.Pos, callExpr *ast.CallExpr) { - for _, arg := range callExpr.Args { - nom.addFromIdent(ifPos, arg) - } -} - -func (nom namedOccurrenceMap) addFromIdent(ifPos token.Pos, v ast.Expr) { - ident, ok := v.(*ast.Ident) - if !ok { - return - } - - if markeredOccs, ok := nom[ident.Name]; ok { - marker := nom[ident.Name].getGreatestMarker() - - occ := markeredOccs[marker] - if occ.isComplete() { - return - } - - occ.ifStmtPos = ifPos - nom[ident.Name][marker] = occ - } -} diff --git a/vendor/github.com/ghostiam/protogetter/processor.go b/vendor/github.com/ghostiam/protogetter/processor.go index d65199dd2e..ed52fb6ebf 100644 --- a/vendor/github.com/ghostiam/protogetter/processor.go +++ b/vendor/github.com/ghostiam/protogetter/processor.go @@ -218,10 +218,8 @@ func (c *processor) processInner(expr ast.Expr) { c.write("*") c.processInner(x.X) - case *ast.CompositeLit: - c.write(formatNode(x)) - - case *ast.TypeAssertExpr: + case *ast.CompositeLit, *ast.TypeAssertExpr, *ast.ArrayType: + // Process the node as is. c.write(formatNode(x)) default: diff --git a/vendor/github.com/go-critic/go-critic/checkers/commentFormatting_checker.go b/vendor/github.com/go-critic/go-critic/checkers/commentFormatting_checker.go index 5a9564a0f9..b834158eca 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/commentFormatting_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/commentFormatting_checker.go @@ -27,12 +27,16 @@ func init() { "//nolint", } parts := []string{ - "//go:generate ", // e.g.: go:generate value - "//line /", // e.g.: line /path/to/file:123 - "//nolint ", // e.g.: nolint - "//noinspection ", // e.g.: noinspection ALL, some GoLand and friends versions - "//export ", // e.g.: export Foo - "///", // e.g.: vertical breaker ///////////// + "//go:generate ", // e.g.: go:generate value + "//line /", // e.g.: line /path/to/file:123 + "//nolint ", // e.g.: nolint + "//noinspection ", // e.g.: noinspection ALL, some GoLand and friends versions + "//region", // e.g.: region awawa, used by GoLand and friends for custom folding + "//endregion", // e.g.: endregion awawa or endregion, closes GoLand regions + "// or , used by VSCode for custom folding + "//", // e.g.: , closes VSCode regions + "//export ", // e.g.: export Foo + "///", // e.g.: vertical breaker ///////////// "//+", "//#", "//-", diff --git a/vendor/github.com/golangci/check/cmd/structcheck/structcheck.go b/vendor/github.com/golangci/check/cmd/structcheck/structcheck.go deleted file mode 100644 index 5dc5f83804..0000000000 --- a/vendor/github.com/golangci/check/cmd/structcheck/structcheck.go +++ /dev/null @@ -1,193 +0,0 @@ -// structcheck -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package structcheck - -import ( - "flag" - "fmt" - "go/ast" - "go/token" - "go/types" - - "golang.org/x/tools/go/loader" -) - -var ( - assignmentsOnly = flag.Bool("structcheck.a", false, "Count assignments only") - loadTestFiles = flag.Bool("structcheck.t", false, "Load test files too") - buildTags = flag.String("structcheck.tags", "", "Build tags") -) - -type visitor struct { - prog *loader.Program - pkg *loader.PackageInfo - m map[types.Type]map[string]int - skip map[types.Type]struct{} -} - -func (v *visitor) decl(t types.Type, fieldName string) { - if _, ok := v.m[t]; !ok { - v.m[t] = make(map[string]int) - } - if _, ok := v.m[t][fieldName]; !ok { - v.m[t][fieldName] = 0 - } -} - -func (v *visitor) assignment(t types.Type, fieldName string) { - if _, ok := v.m[t]; !ok { - v.m[t] = make(map[string]int) - } - if _, ok := v.m[t][fieldName]; ok { - v.m[t][fieldName]++ - } else { - v.m[t][fieldName] = 1 - } -} - -func (v *visitor) typeSpec(node *ast.TypeSpec) { - if strukt, ok := node.Type.(*ast.StructType); ok { - t := v.pkg.Info.Defs[node.Name].Type() - for _, f := range strukt.Fields.List { - if len(f.Names) > 0 { - fieldName := f.Names[0].Name - v.decl(t, fieldName) - } - } - } -} - -func (v *visitor) typeAndFieldName(expr *ast.SelectorExpr) (types.Type, string, bool) { - selection := v.pkg.Info.Selections[expr] - if selection == nil { - return nil, "", false - } - recv := selection.Recv() - if ptr, ok := recv.(*types.Pointer); ok { - recv = ptr.Elem() - } - return recv, selection.Obj().Name(), true -} - -func (v *visitor) assignStmt(node *ast.AssignStmt) { - for _, lhs := range node.Lhs { - var selector *ast.SelectorExpr - switch expr := lhs.(type) { - case *ast.SelectorExpr: - selector = expr - case *ast.IndexExpr: - if expr, ok := expr.X.(*ast.SelectorExpr); ok { - selector = expr - } - } - if selector != nil { - if t, fn, ok := v.typeAndFieldName(selector); ok { - v.assignment(t, fn) - } - } - } -} - -func (v *visitor) compositeLiteral(node *ast.CompositeLit) { - t := v.pkg.Info.Types[node.Type].Type - for _, expr := range node.Elts { - if kv, ok := expr.(*ast.KeyValueExpr); ok { - if ident, ok := kv.Key.(*ast.Ident); ok { - v.assignment(t, ident.Name) - } - } else { - // Struct literal with positional values. - // All the fields are assigned. - v.skip[t] = struct{}{} - break - } - } -} - -func (v *visitor) Visit(node ast.Node) ast.Visitor { - switch node := node.(type) { - case *ast.TypeSpec: - v.typeSpec(node) - - case *ast.AssignStmt: - if *assignmentsOnly { - v.assignStmt(node) - } - - case *ast.SelectorExpr: - if !*assignmentsOnly { - if t, fn, ok := v.typeAndFieldName(node); ok { - v.assignment(t, fn) - } - } - - case *ast.CompositeLit: - v.compositeLiteral(node) - } - - return v -} - -type Issue struct { - Pos token.Position - Type string - FieldName string -} - -func Run(program *loader.Program, reportExported bool) []Issue { - var issues []Issue - for _, pkg := range program.InitialPackages() { - visitor := &visitor{ - m: make(map[types.Type]map[string]int), - skip: make(map[types.Type]struct{}), - prog: program, - pkg: pkg, - } - for _, f := range pkg.Files { - ast.Walk(visitor, f) - } - - for t := range visitor.m { - if _, skip := visitor.skip[t]; skip { - continue - } - for fieldName, v := range visitor.m[t] { - if !reportExported && ast.IsExported(fieldName) { - continue - } - if v == 0 { - field, _, _ := types.LookupFieldOrMethod(t, false, pkg.Pkg, fieldName) - if field == nil { - fmt.Printf("%s: unknown field or method: %s.%s\n", pkg.Pkg.Path(), t, fieldName) - continue - } - if fieldName == "XMLName" { - if named, ok := field.Type().(*types.Named); ok && named.Obj().Pkg().Path() == "encoding/xml" { - continue - } - } - pos := program.Fset.Position(field.Pos()) - issues = append(issues, Issue{ - Pos: pos, - Type: types.TypeString(t, nil), - FieldName: fieldName, - }) - } - } - } - } - - return issues -} diff --git a/vendor/github.com/golangci/check/cmd/varcheck/varcheck.go b/vendor/github.com/golangci/check/cmd/varcheck/varcheck.go deleted file mode 100644 index 8e93e0473b..0000000000 --- a/vendor/github.com/golangci/check/cmd/varcheck/varcheck.go +++ /dev/null @@ -1,163 +0,0 @@ -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package varcheck - -import ( - "flag" - "go/ast" - "go/token" - "strings" - - "go/types" - - "golang.org/x/tools/go/loader" -) - -var ( - buildTags = flag.String("varcheck.tags", "", "Build tags") -) - -type object struct { - pkgPath string - name string -} - -type visitor struct { - prog *loader.Program - pkg *loader.PackageInfo - uses map[object]int - positions map[object]token.Position - insideFunc bool -} - -func getKey(obj types.Object) object { - if obj == nil { - return object{} - } - - pkg := obj.Pkg() - pkgPath := "" - if pkg != nil { - pkgPath = pkg.Path() - } - - return object{ - pkgPath: pkgPath, - name: obj.Name(), - } -} - -func (v *visitor) decl(obj types.Object) { - key := getKey(obj) - if _, ok := v.uses[key]; !ok { - v.uses[key] = 0 - } - if _, ok := v.positions[key]; !ok { - v.positions[key] = v.prog.Fset.Position(obj.Pos()) - } -} - -func (v *visitor) use(obj types.Object) { - key := getKey(obj) - if _, ok := v.uses[key]; ok { - v.uses[key]++ - } else { - v.uses[key] = 1 - } -} - -func isReserved(name string) bool { - return name == "_" || strings.HasPrefix(strings.ToLower(name), "_cgo_") -} - -func (v *visitor) Visit(node ast.Node) ast.Visitor { - switch node := node.(type) { - case *ast.Ident: - v.use(v.pkg.Info.Uses[node]) - - case *ast.ValueSpec: - if !v.insideFunc { - for _, ident := range node.Names { - if !isReserved(ident.Name) { - v.decl(v.pkg.Info.Defs[ident]) - } - } - } - for _, val := range node.Values { - ast.Walk(v, val) - } - if node.Type != nil { - ast.Walk(v, node.Type) - } - return nil - - case *ast.FuncDecl: - if node.Body != nil { - v.insideFunc = true - ast.Walk(v, node.Body) - v.insideFunc = false - } - - if node.Recv != nil { - ast.Walk(v, node.Recv) - } - if node.Type != nil { - ast.Walk(v, node.Type) - } - - return nil - } - - return v -} - -type Issue struct { - Pos token.Position - VarName string -} - -func Run(program *loader.Program, reportExported bool) []Issue { - var issues []Issue - uses := make(map[object]int) - positions := make(map[object]token.Position) - - for _, pkgInfo := range program.InitialPackages() { - if pkgInfo.Pkg.Path() == "unsafe" { - continue - } - - v := &visitor{ - prog: program, - pkg: pkgInfo, - uses: uses, - positions: positions, - } - - for _, f := range v.pkg.Files { - ast.Walk(v, f) - } - } - - for obj, useCount := range uses { - if useCount == 0 && (reportExported || !ast.IsExported(obj.name)) { - pos := positions[obj] - issues = append(issues, Issue{ - Pos: pos, - VarName: obj.name, - }) - } - } - - return issues -} diff --git a/vendor/github.com/golangci/go-misc/LICENSE b/vendor/github.com/golangci/go-misc/LICENSE deleted file mode 100644 index cc42dd45d1..0000000000 --- a/vendor/github.com/golangci/go-misc/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2012 Rémy Oudompheng. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * The name of Rémy Oudompheng may not be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - diff --git a/vendor/github.com/golangci/go-misc/deadcode/README.md b/vendor/github.com/golangci/go-misc/deadcode/README.md deleted file mode 100644 index 5504231281..0000000000 --- a/vendor/github.com/golangci/go-misc/deadcode/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# deadcode - -`deadcode` is a very simple utility which detects unused declarations in a Go package. - -## Usage -``` -deadcode [-test] [packages] - - -test Include test files - packages A list of packages using the same conventions as the go tool -``` - -## Limitations - -* Self-referential unused code is not currently reported -* A single package can be tested at a time -* Unused methods are not reported - diff --git a/vendor/github.com/golangci/go-misc/deadcode/deadcode.go b/vendor/github.com/golangci/go-misc/deadcode/deadcode.go deleted file mode 100644 index c154a576b3..0000000000 --- a/vendor/github.com/golangci/go-misc/deadcode/deadcode.go +++ /dev/null @@ -1,138 +0,0 @@ -package deadcode - -import ( - "fmt" - "go/ast" - "go/token" - "go/types" - "os" - "path/filepath" - "sort" - "strings" - - "golang.org/x/tools/go/loader" -) - -var exitCode int - -var ( - withTestFiles bool -) - -type Issue struct { - Pos token.Position - UnusedIdentName string -} - -func Run(program *loader.Program) ([]Issue, error) { - ctx := &Context{ - program: program, - } - report := ctx.Process() - var issues []Issue - for _, obj := range report { - issues = append(issues, Issue{ - Pos: program.Fset.Position(obj.Pos()), - UnusedIdentName: obj.Name(), - }) - } - - return issues, nil -} - -func fatalf(format string, args ...interface{}) { - panic(fmt.Errorf(format, args...)) -} - -type Context struct { - cwd string - withTests bool - - program *loader.Program -} - -// pos resolves a compact position encoding into a verbose one -func (ctx *Context) pos(pos token.Pos) token.Position { - if ctx.cwd == "" { - ctx.cwd, _ = os.Getwd() - } - p := ctx.program.Fset.Position(pos) - f, err := filepath.Rel(ctx.cwd, p.Filename) - if err == nil { - p.Filename = f - } - return p -} - -// error formats the error to standard error, adding program -// identification and a newline -func (ctx *Context) errorf(pos token.Pos, format string, args ...interface{}) { - p := ctx.pos(pos) - fmt.Fprintf(os.Stderr, p.String()+": "+format+"\n", args...) - exitCode = 2 -} - -func (ctx *Context) Load(args ...string) { - // TODO -} - -func (ctx *Context) Process() []types.Object { - prog := ctx.program - var allUnused []types.Object - for _, pkg := range prog.Imported { - unused := ctx.doPackage(prog, pkg) - allUnused = append(allUnused, unused...) - } - for _, pkg := range prog.Created { - unused := ctx.doPackage(prog, pkg) - allUnused = append(allUnused, unused...) - } - sort.Sort(objects(allUnused)) - return allUnused -} - -func isTestFuncByName(name string) bool { - return strings.HasPrefix(name, "Test") || - strings.HasPrefix(name, "Benchmark") || - strings.HasPrefix(name, "Fuzz") || - strings.HasPrefix(name, "Example") -} - -func (ctx *Context) doPackage(prog *loader.Program, pkg *loader.PackageInfo) []types.Object { - used := make(map[types.Object]bool) - for _, file := range pkg.Files { - ast.Inspect(file, func(n ast.Node) bool { - id, ok := n.(*ast.Ident) - if !ok { - return true - } - obj := pkg.Info.Uses[id] - if obj != nil { - used[obj] = true - } - return false - }) - } - - global := pkg.Pkg.Scope() - var unused []types.Object - for _, name := range global.Names() { - if pkg.Pkg.Name() == "main" && name == "main" { - continue - } - obj := global.Lookup(name) - _, isSig := obj.Type().(*types.Signature) - pos := ctx.pos(obj.Pos()) - isTestMethod := isSig && isTestFuncByName(obj.Name()) && strings.HasSuffix(pos.Filename, "_test.go") - if !used[obj] && ((pkg.Pkg.Name() == "main" && !isTestMethod) || !ast.IsExported(name)) { - unused = append(unused, obj) - } - } - return unused -} - -type objects []types.Object - -func (s objects) Len() int { return len(s) } -func (s objects) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s objects) Less(i, j int) bool { return s[i].Pos() < s[j].Pos() } diff --git a/vendor/github.com/golangci/golangci-lint/cmd/golangci-lint/main.go b/vendor/github.com/golangci/golangci-lint/cmd/golangci-lint/main.go index 9d1daa81df..413e071d65 100644 --- a/vendor/github.com/golangci/golangci-lint/cmd/golangci-lint/main.go +++ b/vendor/github.com/golangci/golangci-lint/cmd/golangci-lint/main.go @@ -13,33 +13,69 @@ var ( goVersion = "unknown" // Populated by goreleaser during build - version = "master" + version = "unknown" commit = "?" date = "" ) func main() { - if buildInfo, available := debug.ReadBuildInfo(); available { - goVersion = buildInfo.GoVersion + info := createBuildInfo() - if date == "" { - version = buildInfo.Main.Version - commit = fmt.Sprintf("(unknown, mod sum: %q)", buildInfo.Main.Sum) - date = "(unknown)" - } + if err := commands.Execute(info); err != nil { + _, _ = fmt.Fprintf(os.Stderr, "Failed executing command with error: %v\n", err) + os.Exit(exitcodes.Failure) } +} +func createBuildInfo() commands.BuildInfo { info := commands.BuildInfo{ - GoVersion: goVersion, - Version: version, Commit: commit, + Version: version, + GoVersion: goVersion, Date: date, } - e := commands.NewExecutor(info) + buildInfo, available := debug.ReadBuildInfo() + if !available { + return info + } - if err := e.Execute(); err != nil { - fmt.Fprintf(os.Stderr, "failed executing command with error %v\n", err) - os.Exit(exitcodes.Failure) + info.GoVersion = buildInfo.GoVersion + + if date != "" { + return info + } + + info.Version = buildInfo.Main.Version + + var revision string + var modified string + for _, setting := range buildInfo.Settings { + // The `vcs.xxx` information is only available with `go build`. + // This information is not available with `go install` or `go run`. + switch setting.Key { + case "vcs.time": + info.Date = setting.Value + case "vcs.revision": + revision = setting.Value + case "vcs.modified": + modified = setting.Value + } } + + if revision == "" { + revision = "unknown" + } + + if modified == "" { + modified = "?" + } + + if info.Date == "" { + info.Date = "(unknown)" + } + + info.Commit = fmt.Sprintf("(%s, modified: %s, mod sum: %q)", revision, modified, buildInfo.Main.Sum) + + return info } diff --git a/vendor/github.com/golangci/golangci-lint/cmd/golangci-lint/plugins.go b/vendor/github.com/golangci/golangci-lint/cmd/golangci-lint/plugins.go new file mode 100644 index 0000000000..541ff76242 --- /dev/null +++ b/vendor/github.com/golangci/golangci-lint/cmd/golangci-lint/plugins.go @@ -0,0 +1,3 @@ +package main + +// This file is used to declare module plugins. diff --git a/vendor/github.com/golangci/golangci-lint/pkg/commands/cache.go b/vendor/github.com/golangci/golangci-lint/pkg/commands/cache.go index 6fdaebaede..4aa8130518 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/commands/cache.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/cache.go @@ -12,7 +12,13 @@ import ( "github.com/golangci/golangci-lint/pkg/logutils" ) -func (e *Executor) initCache() { +type cacheCommand struct { + cmd *cobra.Command +} + +func newCacheCommand() *cacheCommand { + c := &cacheCommand{} + cacheCmd := &cobra.Command{ Use: "cache", Short: "Cache control and information", @@ -21,28 +27,32 @@ func (e *Executor) initCache() { return cmd.Help() }, } - e.rootCmd.AddCommand(cacheCmd) - cacheCmd.AddCommand(&cobra.Command{ - Use: "clean", - Short: "Clean cache", - Args: cobra.NoArgs, - ValidArgsFunction: cobra.NoFileCompletions, - RunE: e.executeCleanCache, - }) - cacheCmd.AddCommand(&cobra.Command{ - Use: "status", - Short: "Show cache status", - Args: cobra.NoArgs, - ValidArgsFunction: cobra.NoFileCompletions, - Run: e.executeCacheStatus, - }) + cacheCmd.AddCommand( + &cobra.Command{ + Use: "clean", + Short: "Clean cache", + Args: cobra.NoArgs, + ValidArgsFunction: cobra.NoFileCompletions, + RunE: c.executeClean, + }, + &cobra.Command{ + Use: "status", + Short: "Show cache status", + Args: cobra.NoArgs, + ValidArgsFunction: cobra.NoFileCompletions, + Run: c.executeStatus, + }, + ) - // TODO: add trim command? + c.cmd = cacheCmd + + return c } -func (e *Executor) executeCleanCache(_ *cobra.Command, _ []string) error { +func (c *cacheCommand) executeClean(_ *cobra.Command, _ []string) error { cacheDir := cache.DefaultDir() + if err := os.RemoveAll(cacheDir); err != nil { return fmt.Errorf("failed to remove dir %s: %w", cacheDir, err) } @@ -50,13 +60,13 @@ func (e *Executor) executeCleanCache(_ *cobra.Command, _ []string) error { return nil } -func (e *Executor) executeCacheStatus(_ *cobra.Command, _ []string) { +func (c *cacheCommand) executeStatus(_ *cobra.Command, _ []string) { cacheDir := cache.DefaultDir() - fmt.Fprintf(logutils.StdOut, "Dir: %s\n", cacheDir) + _, _ = fmt.Fprintf(logutils.StdOut, "Dir: %s\n", cacheDir) cacheSizeBytes, err := dirSizeBytes(cacheDir) if err == nil { - fmt.Fprintf(logutils.StdOut, "Size: %s\n", fsutils.PrettifyBytesCount(cacheSizeBytes)) + _, _ = fmt.Fprintf(logutils.StdOut, "Size: %s\n", fsutils.PrettifyBytesCount(cacheSizeBytes)) } } 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 45c4fcd77c..cfb7d67ac3 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/commands/config.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/config.go @@ -4,70 +4,123 @@ import ( "fmt" "os" + "github.com/fatih/color" "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" + "github.com/golangci/golangci-lint/pkg/logutils" ) -func (e *Executor) initConfig() { - cmd := &cobra.Command{ +type configCommand struct { + viper *viper.Viper + cmd *cobra.Command + + opts config.LoaderOptions + verifyOpts verifyOptions + + buildInfo BuildInfo + + log logutils.Log +} + +func newConfigCommand(log logutils.Log, info BuildInfo) *configCommand { + c := &configCommand{ + viper: viper.New(), + log: log, + buildInfo: info, + } + + configCmd := &cobra.Command{ Use: "config", Short: "Config file information", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, _ []string) error { return cmd.Help() }, + PersistentPreRunE: c.preRunE, } - e.rootCmd.AddCommand(cmd) - pathCmd := &cobra.Command{ - Use: "path", - Short: "Print used config path", + verifyCommand := &cobra.Command{ + Use: "verify", + Short: "Verify configuration against JSON schema", Args: cobra.NoArgs, ValidArgsFunction: cobra.NoFileCompletions, - Run: e.executePathCmd, + RunE: c.executeVerify, } - fs := pathCmd.Flags() - fs.SortFlags = false // sort them as they are defined here + configCmd.AddCommand( + &cobra.Command{ + Use: "path", + Short: "Print used config path", + Args: cobra.NoArgs, + ValidArgsFunction: cobra.NoFileCompletions, + Run: c.executePath, + }, + verifyCommand, + ) - initConfigFileFlagSet(fs, &e.cfg.Run) + flagSet := configCmd.PersistentFlags() + flagSet.SortFlags = false // sort them as they are defined here - cmd.AddCommand(pathCmd) + setupConfigFileFlagSet(flagSet, &c.opts) + + // ex: --schema jsonschema/golangci.next.jsonschema.json + verifyFlagSet := verifyCommand.Flags() + verifyFlagSet.StringVar(&c.verifyOpts.schemaURL, "schema", "", color.GreenString("JSON schema URL")) + _ = verifyFlagSet.MarkHidden("schema") + + c.cmd = configCmd + + return c } -// getUsedConfig returns the resolved path to the golangci config file, or the empty string -// if no configuration could be found. -func (e *Executor) getUsedConfig() string { - usedConfigFile := viper.ConfigFileUsed() - if usedConfigFile == "" { - return "" - } +func (c *configCommand) preRunE(cmd *cobra.Command, _ []string) error { + // The command doesn't depend on the real configuration. + // It only needs to know the path of the configuration file. + cfg := config.NewDefault() - prettyUsedConfigFile, err := fsutils.ShortestRelPath(usedConfigFile, "") - if err != nil { - e.log.Warnf("Can't pretty print config file path: %s", err) - return usedConfigFile + // Hack to hide deprecation messages related to `--skip-dirs-use-default`: + // Flags are not bound then the default values, defined only through flags, are not applied. + // In this command, file path and file information are the only requirements, i.e. it don't need flag values. + // + // TODO(ldez) add an option (check deprecation) to `Loader.Load()` but this require a dedicated PR. + cfg.Run.UseDefaultSkipDirs = true + + loader := config.NewLoader(c.log.Child(logutils.DebugKeyConfigReader), c.viper, cmd.Flags(), c.opts, cfg) + + if err := loader.Load(); err != nil { + return fmt.Errorf("can't load config: %w", err) } - return prettyUsedConfigFile + return nil } -func (e *Executor) executePathCmd(_ *cobra.Command, _ []string) { - usedConfigFile := e.getUsedConfig() +func (c *configCommand) executePath(cmd *cobra.Command, _ []string) { + usedConfigFile := c.getUsedConfig() if usedConfigFile == "" { - e.log.Warnf("No config file detected") + c.log.Warnf("No config file detected") os.Exit(exitcodes.NoConfigFileDetected) } - fmt.Println(usedConfigFile) + cmd.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")) +// getUsedConfig returns the resolved path to the golangci config file, +// or the empty string if no configuration could be found. +func (c *configCommand) getUsedConfig() string { + usedConfigFile := c.viper.ConfigFileUsed() + if usedConfigFile == "" { + return "" + } + + prettyUsedConfigFile, err := fsutils.ShortestRelPath(usedConfigFile, "") + if err != nil { + c.log.Warnf("Can't pretty print config file path: %s", err) + return usedConfigFile + } + + return prettyUsedConfigFile } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/commands/config_verify.go b/vendor/github.com/golangci/golangci-lint/pkg/commands/config_verify.go new file mode 100644 index 0000000000..291c99a025 --- /dev/null +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/config_verify.go @@ -0,0 +1,176 @@ +package commands + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + hcversion "github.com/hashicorp/go-version" + "github.com/pelletier/go-toml/v2" + "github.com/santhosh-tekuri/jsonschema/v5" + _ "github.com/santhosh-tekuri/jsonschema/v5/httploader" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "gopkg.in/yaml.v3" + + "github.com/golangci/golangci-lint/pkg/exitcodes" +) + +type verifyOptions struct { + schemaURL string // For debugging purpose only (Flag only). +} + +func (c *configCommand) executeVerify(cmd *cobra.Command, _ []string) error { + usedConfigFile := c.getUsedConfig() + if usedConfigFile == "" { + c.log.Warnf("No config file detected") + os.Exit(exitcodes.NoConfigFileDetected) + } + + schemaURL, err := createSchemaURL(cmd.Flags(), c.buildInfo) + if err != nil { + return fmt.Errorf("get JSON schema: %w", err) + } + + err = validateConfiguration(schemaURL, usedConfigFile) + if err != nil { + var v *jsonschema.ValidationError + if !errors.As(err, &v) { + return fmt.Errorf("[%s] validate: %w", usedConfigFile, err) + } + + detail := v.DetailedOutput() + + printValidationDetail(cmd, &detail) + + return fmt.Errorf("the configuration contains invalid elements") + } + + return nil +} + +func createSchemaURL(flags *pflag.FlagSet, buildInfo BuildInfo) (string, error) { + schemaURL, err := flags.GetString("schema") + if err != nil { + return "", fmt.Errorf("get schema flag: %w", err) + } + + if schemaURL != "" { + return schemaURL, nil + } + + switch { + case buildInfo.Version != "" && buildInfo.Version != "(devel)": + version, err := hcversion.NewVersion(buildInfo.Version) + if err != nil { + return "", fmt.Errorf("parse version: %w", err) + } + + schemaURL = fmt.Sprintf("https://golangci-lint.run/jsonschema/golangci.v%d.%d.jsonschema.json", + version.Segments()[0], version.Segments()[1]) + + case buildInfo.Commit != "" && buildInfo.Commit != "?": + if buildInfo.Commit == "unknown" { + return "", errors.New("unknown commit information") + } + + commit := buildInfo.Commit + + if strings.HasPrefix(commit, "(") { + c, _, ok := strings.Cut(strings.TrimPrefix(commit, "("), ",") + if !ok { + return "", errors.New("commit information not found") + } + + commit = c + } + + schemaURL = fmt.Sprintf("https://raw.githubusercontent.com/golangci/golangci-lint/%s/jsonschema/golangci.next.jsonschema.json", + commit) + + default: + return "", errors.New("version not found") + } + + return schemaURL, nil +} + +func validateConfiguration(schemaPath, targetFile string) error { + compiler := jsonschema.NewCompiler() + compiler.Draft = jsonschema.Draft7 + + schema, err := compiler.Compile(schemaPath) + if err != nil { + return fmt.Errorf("compile schema: %w", err) + } + + var m any + + switch strings.ToLower(filepath.Ext(targetFile)) { + case ".yaml", ".yml", ".json": + m, err = decodeYamlFile(targetFile) + if err != nil { + return err + } + + case ".toml": + m, err = decodeTomlFile(targetFile) + if err != nil { + return err + } + + default: + // unsupported + return errors.New("unsupported configuration format") + } + + return schema.Validate(m) +} + +func printValidationDetail(cmd *cobra.Command, detail *jsonschema.Detailed) { + if detail.Error != "" { + cmd.PrintErrf("jsonschema: %q does not validate with %q: %s\n", + strings.ReplaceAll(strings.TrimPrefix(detail.InstanceLocation, "/"), "/", "."), detail.KeywordLocation, detail.Error) + } + + for _, d := range detail.Errors { + d := d + printValidationDetail(cmd, &d) + } +} + +func decodeYamlFile(filename string) (any, error) { + file, err := os.Open(filename) + if err != nil { + return nil, fmt.Errorf("[%s] file open: %w", filename, err) + } + + defer func() { _ = file.Close() }() + + var m any + err = yaml.NewDecoder(file).Decode(&m) + if err != nil { + return nil, fmt.Errorf("[%s] YAML decode: %w", filename, err) + } + + return m, nil +} + +func decodeTomlFile(filename string) (any, error) { + file, err := os.Open(filename) + if err != nil { + return nil, fmt.Errorf("[%s] file open: %w", filename, err) + } + + defer func() { _ = file.Close() }() + + var m any + err = toml.NewDecoder(file).Decode(&m) + if err != nil { + return nil, fmt.Errorf("[%s] TOML decode: %w", filename, err) + } + + return m, nil +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/commands/custom.go b/vendor/github.com/golangci/golangci-lint/pkg/commands/custom.go new file mode 100644 index 0000000000..1bc9f90146 --- /dev/null +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/custom.go @@ -0,0 +1,79 @@ +package commands + +import ( + "fmt" + "log" + "os" + + "github.com/spf13/cobra" + + "github.com/golangci/golangci-lint/pkg/commands/internal" + "github.com/golangci/golangci-lint/pkg/logutils" +) + +const envKeepTempFiles = "CUSTOM_GCL_KEEP_TEMP_FILES" + +type customCommand struct { + cmd *cobra.Command + + cfg *internal.Configuration + + log logutils.Log +} + +func newCustomCommand(logger logutils.Log) *customCommand { + c := &customCommand{log: logger} + + customCmd := &cobra.Command{ + Use: "custom", + Short: "Build a version of golangci-lint with custom linters", + Args: cobra.NoArgs, + PreRunE: c.preRunE, + RunE: c.runE, + SilenceUsage: true, + } + + c.cmd = customCmd + + return c +} + +func (c *customCommand) preRunE(_ *cobra.Command, _ []string) error { + cfg, err := internal.LoadConfiguration() + if err != nil { + return err + } + + err = cfg.Validate() + if err != nil { + return err + } + + c.cfg = cfg + + return nil +} + +func (c *customCommand) runE(cmd *cobra.Command, _ []string) error { + tmp, err := os.MkdirTemp(os.TempDir(), "custom-gcl") + if err != nil { + return fmt.Errorf("create temporary directory: %w", err) + } + + defer func() { + if os.Getenv(envKeepTempFiles) != "" { + log.Printf("WARN: The env var %s has been detected: the temporary directory is preserved: %s", envKeepTempFiles, tmp) + + return + } + + _ = os.RemoveAll(tmp) + }() + + err = internal.NewBuilder(c.log, c.cfg, tmp).Build(cmd.Context()) + if err != nil { + return fmt.Errorf("build process: %w", err) + } + + return nil +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/commands/executor.go b/vendor/github.com/golangci/golangci-lint/pkg/commands/executor.go deleted file mode 100644 index d241f5656a..0000000000 --- a/vendor/github.com/golangci/golangci-lint/pkg/commands/executor.go +++ /dev/null @@ -1,254 +0,0 @@ -package commands - -import ( - "bytes" - "context" - "crypto/sha256" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "strings" - "time" - - "github.com/fatih/color" - "github.com/gofrs/flock" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "gopkg.in/yaml.v3" - - "github.com/golangci/golangci-lint/internal/cache" - "github.com/golangci/golangci-lint/internal/pkgcache" - "github.com/golangci/golangci-lint/pkg/config" - "github.com/golangci/golangci-lint/pkg/fsutils" - "github.com/golangci/golangci-lint/pkg/golinters/goanalysis/load" - "github.com/golangci/golangci-lint/pkg/goutil" - "github.com/golangci/golangci-lint/pkg/lint" - "github.com/golangci/golangci-lint/pkg/lint/lintersdb" - "github.com/golangci/golangci-lint/pkg/logutils" - "github.com/golangci/golangci-lint/pkg/report" - "github.com/golangci/golangci-lint/pkg/timeutils" -) - -type BuildInfo struct { - GoVersion string `json:"goVersion"` - Version string `json:"version"` - Commit string `json:"commit"` - Date string `json:"date"` -} - -type Executor struct { - rootCmd *cobra.Command - runCmd *cobra.Command - lintersCmd *cobra.Command - - exitCode int - buildInfo BuildInfo - - cfg *config.Config // cfg is the unmarshaled data from the golangci config file. - log logutils.Log - reportData report.Data - DBManager *lintersdb.Manager - EnabledLintersSet *lintersdb.EnabledSet - contextLoader *lint.ContextLoader - goenv *goutil.Env - fileCache *fsutils.FileCache - lineCache *fsutils.LineCache - pkgCache *pkgcache.Cache - debugf logutils.DebugFunc - sw *timeutils.Stopwatch - - loadGuard *load.Guard - flock *flock.Flock -} - -// NewExecutor creates and initializes a new command executor. -func NewExecutor(buildInfo BuildInfo) *Executor { - startedAt := time.Now() - e := &Executor{ - cfg: config.NewDefault(), - buildInfo: buildInfo, - DBManager: lintersdb.NewManager(nil, nil), - debugf: logutils.Debug(logutils.DebugKeyExec), - } - - e.debugf("Starting execution...") - e.log = report.NewLogWrapper(logutils.NewStderrLog(logutils.DebugKeyEmpty), &e.reportData) - - // 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 && !errors.Is(err, pflag.ErrHelp) { - e.log.Fatalf("Can't get config for command line: %s", err) - } - if commandLineCfg != nil { - logutils.SetupVerboseLog(e.log, commandLineCfg.Run.IsVerbose) - - switch commandLineCfg.Output.Color { - case "always": - color.NoColor = false - case "never": - color.NoColor = true - case "auto": - // nothing - default: - e.log.Fatalf("invalid value %q for --color; must be 'always', 'auto', or 'never'", commandLineCfg.Output.Color) - } - } - - // init of commands must be done before config file reading because - // init sets config with the default values of flags - e.initRoot() - e.initRun() - e.initHelp() - e.initLinters() - e.initConfig() - e.initVersion() - e.initCache() - - // init e.cfg by values from config: flags parse will see these values - // like the default ones. It will overwrite them only if the same option - // is found in command-line: it's ok, command-line has higher priority. - - r := config.NewFileReader(e.cfg, commandLineCfg, e.log.Child(logutils.DebugKeyConfigReader)) - if err = r.Read(); err != nil { - e.log.Fatalf("Can't read config: %s", err) - } - - if (commandLineCfg == nil || commandLineCfg.Run.Go == "") && e.cfg != nil && e.cfg.Run.Go == "" { - e.cfg.Run.Go = config.DetectGoVersion() - } - - // recreate after getting config - e.DBManager = lintersdb.NewManager(e.cfg, e.log) - - // Slice options must be explicitly set for proper merging of config and command-line options. - fixSlicesFlags(e.runCmd.Flags()) - fixSlicesFlags(e.lintersCmd.Flags()) - - e.EnabledLintersSet = lintersdb.NewEnabledSet(e.DBManager, - lintersdb.NewValidator(e.DBManager), e.log.Child(logutils.DebugKeyLintersDB), e.cfg) - e.goenv = goutil.NewEnv(e.log.Child(logutils.DebugKeyGoEnv)) - e.fileCache = fsutils.NewFileCache() - e.lineCache = fsutils.NewLineCache(e.fileCache) - - e.sw = timeutils.NewStopwatch("pkgcache", e.log.Child(logutils.DebugKeyStopwatch)) - e.pkgCache, err = pkgcache.NewCache(e.sw, e.log.Child(logutils.DebugKeyPkgCache)) - if err != nil { - e.log.Fatalf("Failed to build packages cache: %s", err) - } - e.loadGuard = load.NewGuard() - e.contextLoader = lint.NewContextLoader(e.cfg, e.log.Child(logutils.DebugKeyLoader), e.goenv, - e.lineCache, e.fileCache, e.pkgCache, e.loadGuard) - if err = e.initHashSalt(buildInfo.Version); err != nil { - e.log.Fatalf("Failed to init hash salt: %s", err) - } - e.debugf("Initialized executor in %s", time.Since(startedAt)) - return e -} - -func (e *Executor) Execute() error { - return e.rootCmd.Execute() -} - -func (e *Executor) initHashSalt(version string) error { - binSalt, err := computeBinarySalt(version) - if err != nil { - return fmt.Errorf("failed to calculate binary salt: %w", err) - } - - configSalt, err := computeConfigSalt(e.cfg) - if err != nil { - return fmt.Errorf("failed to calculate config salt: %w", err) - } - - b := bytes.NewBuffer(binSalt) - b.Write(configSalt) - cache.SetSalt(b.Bytes()) - return nil -} - -func computeBinarySalt(version string) ([]byte, error) { - if version != "" && version != "(devel)" { - return []byte(version), nil - } - - if logutils.HaveDebugTag(logutils.DebugKeyBinSalt) { - return []byte("debug"), nil - } - - p, err := os.Executable() - if err != nil { - return nil, err - } - f, err := os.Open(p) - if err != nil { - return nil, err - } - defer f.Close() - h := sha256.New() - if _, err := io.Copy(h, f); err != nil { - return nil, err - } - return h.Sum(nil), nil -} - -func computeConfigSalt(cfg *config.Config) ([]byte, error) { - // We don't hash all config fields to reduce meaningless cache - // invalidations. At least, it has a huge impact on tests speed. - - lintersSettingsBytes, err := yaml.Marshal(cfg.LintersSettings) - if err != nil { - return nil, fmt.Errorf("failed to json marshal config linter settings: %w", err) - } - - configData := bytes.NewBufferString("linters-settings=") - configData.Write(lintersSettingsBytes) - configData.WriteString("\nbuild-tags=%s" + strings.Join(cfg.Run.BuildTags, ",")) - - h := sha256.New() - if _, err := h.Write(configData.Bytes()); err != nil { - return nil, err - } - return h.Sum(nil), nil -} - -func (e *Executor) acquireFileLock() bool { - if e.cfg.Run.AllowParallelRunners { - e.debugf("Parallel runners are allowed, no locking") - return true - } - - lockFile := filepath.Join(os.TempDir(), "golangci-lint.lock") - e.debugf("Locking on file %s...", lockFile) - f := flock.New(lockFile) - const retryDelay = time.Second - - ctx := context.Background() - if !e.cfg.Run.AllowSerialRunners { - const totalTimeout = 5 * time.Second - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, totalTimeout) - defer cancel() - } - if ok, _ := f.TryLockContext(ctx, retryDelay); !ok { - return false - } - - e.flock = f - return true -} - -func (e *Executor) releaseFileLock() { - if e.cfg.Run.AllowParallelRunners { - return - } - - if err := e.flock.Unlock(); err != nil { - e.debugf("Failed to unlock on file: %s", err) - } - if err := os.Remove(e.flock.Path()); err != nil { - e.debugf("Failed to remove lock file: %s", err) - } -} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/commands/flagsets.go b/vendor/github.com/golangci/golangci-lint/pkg/commands/flagsets.go new file mode 100644 index 0000000000..af5c351c5e --- /dev/null +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/flagsets.go @@ -0,0 +1,137 @@ +package commands + +import ( + "fmt" + "strings" + + "github.com/fatih/color" + "github.com/spf13/pflag" + "github.com/spf13/viper" + + "github.com/golangci/golangci-lint/pkg/commands/internal" + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/exitcodes" + "github.com/golangci/golangci-lint/pkg/lint/lintersdb" + "github.com/golangci/golangci-lint/pkg/packages" +) + +func setupLintersFlagSet(v *viper.Viper, fs *pflag.FlagSet) { + internal.AddHackedStringSliceP(fs, "disable", "D", color.GreenString("Disable specific linter")) + internal.AddFlagAndBind(v, fs, fs.Bool, "disable-all", "linters.disable-all", false, color.GreenString("Disable all linters")) + + internal.AddHackedStringSliceP(fs, "enable", "E", color.GreenString("Enable specific linter")) + internal.AddFlagAndBind(v, fs, fs.Bool, "enable-all", "linters.enable-all", false, color.GreenString("Enable all linters")) + + internal.AddFlagAndBind(v, fs, fs.Bool, "fast", "linters.fast", false, + color.GreenString("Enable only fast linters from enabled linters set (first run won't be fast)")) + + internal.AddHackedStringSliceP(fs, "presets", "p", + color.GreenString(fmt.Sprintf("Enable presets (%s) of linters. Run 'golangci-lint help linters' to see them. "+ + "This option implies option --disable-all", strings.Join(lintersdb.AllPresets(), "|")))) + + fs.StringSlice("enable-only", nil, + color.GreenString("Override linters configuration section to only run the specific linter(s)")) // Flags only. +} + +func setupRunFlagSet(v *viper.Viper, fs *pflag.FlagSet) { + internal.AddFlagAndBindP(v, fs, fs.IntP, "concurrency", "j", "run.concurrency", getDefaultConcurrency(), + color.GreenString("Number of CPUs to use (Default: number of logical CPUs)")) + + internal.AddFlagAndBind(v, fs, fs.String, "modules-download-mode", "run.modules-download-mode", "", + color.GreenString("Modules download mode. If not empty, passed as -mod= to go tools")) + internal.AddFlagAndBind(v, fs, fs.Int, "issues-exit-code", "run.issues-exit-code", exitcodes.IssuesFound, + color.GreenString("Exit code when issues were found")) + internal.AddFlagAndBind(v, fs, fs.String, "go", "run.go", "", color.GreenString("Targeted Go version")) + internal.AddHackedStringSlice(fs, "build-tags", color.GreenString("Build tags")) + + internal.AddFlagAndBind(v, fs, fs.Duration, "timeout", "run.timeout", defaultTimeout, color.GreenString("Timeout for total work")) + + internal.AddFlagAndBind(v, fs, fs.Bool, "tests", "run.tests", true, color.GreenString("Analyze tests (*_test.go)")) + + internal.AddDeprecatedHackedStringSlice(fs, "skip-files", color.GreenString("Regexps of files to skip")) + internal.AddDeprecatedHackedStringSlice(fs, "skip-dirs", color.GreenString("Regexps of directories to skip")) + internal.AddDeprecatedFlagAndBind(v, fs, fs.Bool, "skip-dirs-use-default", "run.skip-dirs-use-default", true, + getDefaultDirectoryExcludeHelp()) + + const allowParallelDesc = "Allow multiple parallel golangci-lint instances running. " + + "If false (default) - golangci-lint acquires file lock on start." + internal.AddFlagAndBind(v, fs, fs.Bool, "allow-parallel-runners", "run.allow-parallel-runners", false, + color.GreenString(allowParallelDesc)) + 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." + internal.AddFlagAndBind(v, fs, fs.Bool, "allow-serial-runners", "run.allow-serial-runners", false, color.GreenString(allowSerialDesc)) +} + +func setupOutputFlagSet(v *viper.Viper, fs *pflag.FlagSet) { + internal.AddFlagAndBind(v, fs, fs.String, "out-format", "output.formats", config.OutFormatColoredLineNumber, + color.GreenString(fmt.Sprintf("Formats of output: %s", strings.Join(config.AllOutputFormats, "|")))) + internal.AddFlagAndBind(v, fs, fs.Bool, "print-issued-lines", "output.print-issued-lines", true, + color.GreenString("Print lines of code with issue")) + internal.AddFlagAndBind(v, fs, fs.Bool, "print-linter-name", "output.print-linter-name", true, + color.GreenString("Print linter name in issue line")) + internal.AddFlagAndBind(v, fs, fs.Bool, "uniq-by-line", "output.uniq-by-line", true, + color.GreenString("Make issues output unique by line")) + internal.AddFlagAndBind(v, fs, fs.Bool, "sort-results", "output.sort-results", false, + color.GreenString("Sort linter results")) + internal.AddFlagAndBind(v, fs, fs.StringSlice, "sort-order", "output.sort-order", nil, + color.GreenString("Sort order of linter results")) + internal.AddFlagAndBind(v, fs, fs.String, "path-prefix", "output.path-prefix", "", + color.GreenString("Path prefix to add to output")) + internal.AddFlagAndBind(v, fs, fs.Bool, "show-stats", "output.show-stats", false, color.GreenString("Show statistics per linter")) +} + +//nolint:gomnd // magic numbers here is ok +func setupIssuesFlagSet(v *viper.Viper, fs *pflag.FlagSet) { + internal.AddHackedStringSliceP(fs, "exclude", "e", color.GreenString("Exclude issue by regexp")) + internal.AddFlagAndBind(v, fs, fs.Bool, "exclude-use-default", "issues.exclude-use-default", true, + getDefaultIssueExcludeHelp()) + internal.AddFlagAndBind(v, fs, fs.Bool, "exclude-case-sensitive", "issues.exclude-case-sensitive", false, + color.GreenString("If set to true exclude and exclude rules regular expressions are case-sensitive")) + + internal.AddFlagAndBind(v, fs, fs.Int, "max-issues-per-linter", "issues.max-issues-per-linter", 50, + color.GreenString("Maximum issues count per one linter. Set to 0 to disable")) + internal.AddFlagAndBind(v, fs, fs.Int, "max-same-issues", "issues.max-same-issues", 3, + color.GreenString("Maximum count of issues with the same text. Set to 0 to disable")) + + internal.AddHackedStringSlice(fs, "exclude-files", color.GreenString("Regexps of files to exclude")) + internal.AddHackedStringSlice(fs, "exclude-dirs", color.GreenString("Regexps of directories to exclude")) + internal.AddFlagAndBind(v, fs, fs.Bool, "exclude-dirs-use-default", "issues.exclude-dirs-use-default", true, + getDefaultDirectoryExcludeHelp()) + + const newDesc = "Show only new issues: if there are unstaged changes or untracked files, only those changes " + + "are analyzed, else only changes in HEAD~ are analyzed.\nIt's a super-useful option for integration " + + "of golangci-lint into existing large codebase.\nIt's not practical to fix all existing issues at " + + "the moment of integration: much better to not allow issues in new code.\nFor CI setups, prefer " + + "--new-from-rev=HEAD~, as --new can skip linting the current patch if any scripts generate " + + "unstaged files before golangci-lint runs." + internal.AddFlagAndBindP(v, fs, fs.BoolP, "new", "n", "issues.new", false, color.GreenString(newDesc)) + internal.AddFlagAndBind(v, fs, fs.String, "new-from-rev", "issues.new-from-rev", "", + color.GreenString("Show only new issues created after git revision `REV`")) + internal.AddFlagAndBind(v, fs, fs.String, "new-from-patch", "issues.new-from-patch", "", + color.GreenString("Show only new issues created in git patch with file path `PATH`")) + internal.AddFlagAndBind(v, fs, fs.Bool, "whole-files", "issues.whole-files", false, + color.GreenString("Show issues in any part of update files (requires new-from-rev or new-from-patch)")) + internal.AddFlagAndBind(v, fs, fs.Bool, "fix", "issues.fix", false, + color.GreenString("Fix found issues (if it's supported by the linter)")) +} + +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") +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/commands/help.go b/vendor/github.com/golangci/golangci-lint/pkg/commands/help.go index a06d508f27..42da4a3dc9 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/commands/help.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/help.go @@ -8,11 +8,23 @@ import ( "github.com/fatih/color" "github.com/spf13/cobra" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/lint/lintersdb" "github.com/golangci/golangci-lint/pkg/logutils" ) -func (e *Executor) initHelp() { +type helpCommand struct { + cmd *cobra.Command + + dbManager *lintersdb.Manager + + log logutils.Log +} + +func newHelpCommand(logger logutils.Log) *helpCommand { + c := &helpCommand{log: logger} + helpCmd := &cobra.Command{ Use: "help", Short: "Help", @@ -21,48 +33,39 @@ func (e *Executor) initHelp() { return cmd.Help() }, } - e.rootCmd.SetHelpCommand(helpCmd) - - lintersHelpCmd := &cobra.Command{ - Use: "linters", - Short: "Help about linters", - Args: cobra.NoArgs, - ValidArgsFunction: cobra.NoFileCompletions, - Run: e.executeLintersHelp, - } - helpCmd.AddCommand(lintersHelpCmd) -} -func printLinterConfigs(lcs []*linter.Config) { - sort.Slice(lcs, func(i, j int) bool { - return lcs[i].Name() < lcs[j].Name() - }) - for _, lc := range lcs { - altNamesStr := "" - if len(lc.AlternativeNames) != 0 { - altNamesStr = fmt.Sprintf(" (%s)", strings.Join(lc.AlternativeNames, ", ")) - } + helpCmd.AddCommand( + &cobra.Command{ + Use: "linters", + Short: "Help about linters", + Args: cobra.NoArgs, + ValidArgsFunction: cobra.NoFileCompletions, + Run: c.execute, + PreRunE: c.preRunE, + }, + ) - // If the linter description spans multiple lines, truncate everything following the first newline - linterDescription := lc.Linter.Desc() - firstNewline := strings.IndexRune(linterDescription, '\n') - if firstNewline > 0 { - linterDescription = linterDescription[:firstNewline] - } + c.cmd = helpCmd - deprecatedMark := "" - if lc.IsDeprecated() { - deprecatedMark = " [" + color.RedString("deprecated") + "]" - } + return c +} - fmt.Fprintf(logutils.StdOut, "%s%s%s: %s [fast: %t, auto-fix: %t]\n", color.YellowString(lc.Name()), - altNamesStr, deprecatedMark, linterDescription, !lc.IsSlowLinter(), lc.CanAutoFix) +func (c *helpCommand) preRunE(_ *cobra.Command, _ []string) error { + // The command doesn't depend on the real configuration. + // It just needs the list of all plugins and all presets. + dbManager, err := lintersdb.NewManager(c.log.Child(logutils.DebugKeyLintersDB), config.NewDefault(), lintersdb.NewLinterBuilder()) + if err != nil { + return err } + + c.dbManager = dbManager + + return nil } -func (e *Executor) executeLintersHelp(_ *cobra.Command, _ []string) { +func (c *helpCommand) execute(_ *cobra.Command, _ []string) { var enabledLCs, disabledLCs []*linter.Config - for _, lc := range e.DBManager.GetAllSupportedLinterConfigs() { + for _, lc := range c.dbManager.GetAllSupportedLinterConfigs() { if lc.Internal { continue } @@ -75,13 +78,19 @@ func (e *Executor) executeLintersHelp(_ *cobra.Command, _ []string) { } color.Green("Enabled by default linters:\n") - printLinterConfigs(enabledLCs) + printLinters(enabledLCs) + color.Red("\nDisabled by default linters:\n") - printLinterConfigs(disabledLCs) + printLinters(disabledLCs) color.Green("\nLinters presets:") - for _, p := range e.DBManager.AllPresets() { - linters := e.DBManager.GetAllLinterConfigsForPreset(p) + c.printPresets() +} + +func (c *helpCommand) printPresets() { + for _, p := range lintersdb.AllPresets() { + linters := c.dbManager.GetAllLinterConfigsForPreset(p) + var linterNames []string for _, lc := range linters { if lc.Internal { @@ -91,6 +100,35 @@ func (e *Executor) executeLintersHelp(_ *cobra.Command, _ []string) { linterNames = append(linterNames, lc.Name()) } sort.Strings(linterNames) - fmt.Fprintf(logutils.StdOut, "%s: %s\n", color.YellowString(p), strings.Join(linterNames, ", ")) + + _, _ = fmt.Fprintf(logutils.StdOut, "%s: %s\n", color.YellowString(p), strings.Join(linterNames, ", ")) + } +} + +func printLinters(lcs []*linter.Config) { + sort.Slice(lcs, func(i, j int) bool { + return lcs[i].Name() < lcs[j].Name() + }) + + for _, lc := range lcs { + altNamesStr := "" + if len(lc.AlternativeNames) != 0 { + altNamesStr = fmt.Sprintf(" (%s)", strings.Join(lc.AlternativeNames, ", ")) + } + + // If the linter description spans multiple lines, truncate everything following the first newline + linterDescription := lc.Linter.Desc() + firstNewline := strings.IndexRune(linterDescription, '\n') + if firstNewline > 0 { + linterDescription = linterDescription[:firstNewline] + } + + deprecatedMark := "" + if lc.IsDeprecated() { + deprecatedMark = " [" + color.RedString("deprecated") + "]" + } + + _, _ = fmt.Fprintf(logutils.StdOut, "%s%s%s: %s [fast: %t, auto-fix: %t]\n", + color.YellowString(lc.Name()), altNamesStr, deprecatedMark, linterDescription, !lc.IsSlowLinter(), lc.CanAutoFix) } } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/commands/internal/builder.go b/vendor/github.com/golangci/golangci-lint/pkg/commands/internal/builder.go new file mode 100644 index 0000000000..39ec2a251c --- /dev/null +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/internal/builder.go @@ -0,0 +1,219 @@ +package internal + +import ( + "context" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "time" + "unicode" + + "github.com/golangci/golangci-lint/pkg/logutils" +) + +// Builder runs all the required commands to build a binary. +type Builder struct { + cfg *Configuration + + log logutils.Log + + root string + repo string +} + +// NewBuilder creates a new Builder. +func NewBuilder(logger logutils.Log, cfg *Configuration, root string) *Builder { + return &Builder{ + cfg: cfg, + log: logger, + root: root, + repo: filepath.Join(root, "golangci-lint"), + } +} + +// Build builds the custom binary. +func (b Builder) Build(ctx context.Context) error { + b.log.Infof("Cloning golangci-lint repository") + + err := b.clone(ctx) + if err != nil { + return fmt.Errorf("clone golangci-lint: %w", err) + } + + b.log.Infof("Adding plugin imports") + + err = b.updatePluginsFile() + if err != nil { + return fmt.Errorf("update plugin file: %w", err) + } + + b.log.Infof("Adding replace directives") + + err = b.addReplaceDirectives(ctx) + if err != nil { + return fmt.Errorf("add replace directives: %w", err) + } + + b.log.Infof("Running go mod tidy") + + err = b.goModTidy(ctx) + if err != nil { + return fmt.Errorf("go mod tidy: %w", err) + } + + b.log.Infof("Building golangci-lint binary") + + binaryName := b.getBinaryName() + + err = b.goBuild(ctx, binaryName) + if err != nil { + return fmt.Errorf("build golangci-lint binary: %w", err) + } + + b.log.Infof("Moving golangci-lint binary") + + err = b.copyBinary(binaryName) + if err != nil { + return fmt.Errorf("move golangci-lint binary: %w", err) + } + + return nil +} + +func (b Builder) clone(ctx context.Context) error { + //nolint:gosec // the variable is sanitized. + cmd := exec.CommandContext(ctx, + "git", "clone", "--branch", sanitizeVersion(b.cfg.Version), + "--single-branch", "--depth", "1", "-c advice.detachedHead=false", "-q", + "https://github.com/golangci/golangci-lint.git", + ) + cmd.Dir = b.root + + output, err := cmd.CombinedOutput() + if err != nil { + b.log.Infof(string(output)) + + return fmt.Errorf("%s: %w", strings.Join(cmd.Args, " "), err) + } + + return nil +} + +func (b Builder) addReplaceDirectives(ctx context.Context) error { + for _, plugin := range b.cfg.Plugins { + if plugin.Path == "" { + continue + } + + replace := fmt.Sprintf("%s=%s", plugin.Module, plugin.Path) + + cmd := exec.CommandContext(ctx, "go", "mod", "edit", "-replace", replace) + cmd.Dir = b.repo + + b.log.Infof("run: %s", strings.Join(cmd.Args, " ")) + + output, err := cmd.CombinedOutput() + if err != nil { + b.log.Warnf(string(output)) + + return fmt.Errorf("%s: %w", strings.Join(cmd.Args, " "), err) + } + } + + return nil +} + +func (b Builder) goModTidy(ctx context.Context) error { + cmd := exec.CommandContext(ctx, "go", "mod", "tidy") + cmd.Dir = b.repo + + output, err := cmd.CombinedOutput() + if err != nil { + b.log.Warnf(string(output)) + + return fmt.Errorf("%s: %w", strings.Join(cmd.Args, " "), err) + } + + return nil +} + +func (b Builder) goBuild(ctx context.Context, binaryName string) error { + //nolint:gosec // the variable is sanitized. + cmd := exec.CommandContext(ctx, "go", "build", + "-ldflags", + fmt.Sprintf( + "-s -w -X 'main.version=%s-custom-gcl' -X 'main.date=%s'", + sanitizeVersion(b.cfg.Version), time.Now().UTC().String(), + ), + "-o", binaryName, + "./cmd/golangci-lint", + ) + cmd.Dir = b.repo + + output, err := cmd.CombinedOutput() + if err != nil { + b.log.Warnf(string(output)) + + return fmt.Errorf("%s: %w", strings.Join(cmd.Args, " "), err) + } + + return nil +} + +func (b Builder) copyBinary(binaryName string) error { + src := filepath.Join(b.repo, binaryName) + + source, err := os.Open(filepath.Clean(src)) + if err != nil { + return fmt.Errorf("open source file: %w", err) + } + + defer func() { _ = source.Close() }() + + info, err := source.Stat() + if err != nil { + return fmt.Errorf("stat source file: %w", err) + } + + if b.cfg.Destination != "" { + err = os.MkdirAll(b.cfg.Destination, os.ModePerm) + if err != nil { + return fmt.Errorf("create destination directory: %w", err) + } + } + + dst, err := os.OpenFile(filepath.Join(b.cfg.Destination, binaryName), os.O_RDWR|os.O_CREATE|os.O_TRUNC, info.Mode()) + if err != nil { + return fmt.Errorf("create destination file: %w", err) + } + + defer func() { _ = dst.Close() }() + + _, err = io.Copy(dst, source) + if err != nil { + return fmt.Errorf("copy source to destination: %w", err) + } + + return nil +} + +func (b Builder) getBinaryName() string { + name := b.cfg.Name + if runtime.GOOS == "windows" { + name += ".exe" + } + + return name +} + +func sanitizeVersion(v string) string { + fn := func(c rune) bool { + return !(unicode.IsLetter(c) || unicode.IsNumber(c) || c == '.' || c == '/') + } + + return strings.Join(strings.FieldsFunc(v, fn), "") +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/commands/internal/configuration.go b/vendor/github.com/golangci/golangci-lint/pkg/commands/internal/configuration.go new file mode 100644 index 0000000000..5327025946 --- /dev/null +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/internal/configuration.go @@ -0,0 +1,138 @@ +package internal + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "gopkg.in/yaml.v3" +) + +const base = ".custom-gcl" + +const defaultBinaryName = "custom-gcl" + +// Configuration represents the configuration file. +type Configuration struct { + // golangci-lint version. + Version string `yaml:"version"` + + // Name of the binary. + Name string `yaml:"name,omitempty"` + + // Destination is the path to a directory to store the binary. + Destination string `yaml:"destination,omitempty"` + + // Plugins information. + Plugins []*Plugin `yaml:"plugins,omitempty"` +} + +// Validate checks and clean the configuration. +func (c *Configuration) Validate() error { + if strings.TrimSpace(c.Version) == "" { + return errors.New("root field 'version' is required") + } + + if strings.TrimSpace(c.Name) == "" { + c.Name = defaultBinaryName + } + + if len(c.Plugins) == 0 { + return errors.New("no plugins defined") + } + + for _, plugin := range c.Plugins { + if strings.TrimSpace(plugin.Module) == "" { + return errors.New("field 'module' is required") + } + + if strings.TrimSpace(plugin.Import) == "" { + plugin.Import = plugin.Module + } + + if strings.TrimSpace(plugin.Path) == "" && strings.TrimSpace(plugin.Version) == "" { + return errors.New("missing information: 'version' or 'path' should be provided") + } + + if strings.TrimSpace(plugin.Path) != "" && strings.TrimSpace(plugin.Version) != "" { + return errors.New("invalid configuration: 'version' and 'path' should not be provided at the same time") + } + + if strings.TrimSpace(plugin.Path) == "" { + continue + } + + abs, err := filepath.Abs(plugin.Path) + if err != nil { + return err + } + + plugin.Path = abs + } + + return nil +} + +// Plugin represents information about a plugin. +type Plugin struct { + // Module name. + Module string `yaml:"module"` + + // Import to use. + Import string `yaml:"import,omitempty"` + + // Version of the module. + // Only for module available through a Go proxy. + Version string `yaml:"version,omitempty"` + + // Path to the local module. + // Only for local module. + Path string `yaml:"path,omitempty"` +} + +func LoadConfiguration() (*Configuration, error) { + configFilePath, err := findConfigurationFile() + if err != nil { + return nil, fmt.Errorf("file %s not found: %w", configFilePath, err) + } + + file, err := os.Open(configFilePath) + if err != nil { + return nil, fmt.Errorf("file %s open: %w", configFilePath, err) + } + + var cfg Configuration + + err = yaml.NewDecoder(file).Decode(&cfg) + if err != nil { + return nil, fmt.Errorf("YAML decoding: %w", err) + } + + return &cfg, nil +} + +func findConfigurationFile() (string, error) { + entries, err := os.ReadDir(".") + if err != nil { + return "", fmt.Errorf("read directory: %w", err) + } + + for _, entry := range entries { + ext := filepath.Ext(entry.Name()) + + switch strings.ToLower(strings.TrimPrefix(ext, ".")) { + case "yml", "yaml", "json": + if isConf(ext, entry.Name()) { + return entry.Name(), nil + } + } + } + + return "", errors.New("configuration file not found") +} + +func isConf(ext, name string) bool { + return base+ext == name +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/commands/internal/imports.go b/vendor/github.com/golangci/golangci-lint/pkg/commands/internal/imports.go new file mode 100644 index 0000000000..3bebf596b1 --- /dev/null +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/internal/imports.go @@ -0,0 +1,69 @@ +package internal + +import ( + "bytes" + "fmt" + "go/format" + "os" + "path/filepath" + "text/template" +) + +const importsTemplate = ` +package main + +import ( +{{range .Imports -}} + _ "{{.}}" +{{end -}} +) +` + +func (b Builder) updatePluginsFile() error { + importsDest := filepath.Join(b.repo, "cmd", "golangci-lint", "plugins.go") + + info, err := os.Stat(importsDest) + if err != nil { + return fmt.Errorf("file %s not found: %w", importsDest, err) + } + + source, err := generateImports(b.cfg) + if err != nil { + return fmt.Errorf("generate imports: %w", err) + } + + b.log.Infof("generated imports info %s:\n%s\n", importsDest, source) + + err = os.WriteFile(filepath.Clean(importsDest), source, info.Mode()) + if err != nil { + return fmt.Errorf("write file %s: %w", importsDest, err) + } + + return nil +} + +func generateImports(cfg *Configuration) ([]byte, error) { + impTmpl, err := template.New("plugins.go").Parse(importsTemplate) + if err != nil { + return nil, fmt.Errorf("parse template: %w", err) + } + + var imps []string + for _, plugin := range cfg.Plugins { + imps = append(imps, plugin.Import) + } + + buf := &bytes.Buffer{} + + err = impTmpl.Execute(buf, map[string]any{"Imports": imps}) + if err != nil { + return nil, fmt.Errorf("execute template: %w", err) + } + + source, err := format.Source(buf.Bytes()) + if err != nil { + return nil, fmt.Errorf("format source: %w", err) + } + + return source, nil +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/commands/internal/vibra.go b/vendor/github.com/golangci/golangci-lint/pkg/commands/internal/vibra.go new file mode 100644 index 0000000000..ece2483fe0 --- /dev/null +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/internal/vibra.go @@ -0,0 +1,59 @@ +package internal + +import ( + "fmt" + + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +type FlagFunc[T any] func(name string, value T, usage string) *T + +type FlagPFunc[T any] func(name, shorthand string, value T, usage string) *T + +// AddFlagAndBind adds a Cobra/pflag flag and binds it with Viper. +func AddFlagAndBind[T any](v *viper.Viper, fs *pflag.FlagSet, pfn FlagFunc[T], name, bind string, value T, usage string) { + pfn(name, value, usage) + + err := v.BindPFlag(bind, fs.Lookup(name)) + if err != nil { + panic(fmt.Sprintf("failed to bind flag %s: %v", name, err)) + } +} + +// AddFlagAndBindP adds a Cobra/pflag flag and binds it with Viper. +func AddFlagAndBindP[T any](v *viper.Viper, fs *pflag.FlagSet, pfn FlagPFunc[T], name, shorthand, bind string, value T, usage string) { + pfn(name, shorthand, value, usage) + + err := v.BindPFlag(bind, fs.Lookup(name)) + if err != nil { + panic(fmt.Sprintf("failed to bind flag %s: %v", name, err)) + } +} + +// AddDeprecatedFlagAndBind similar to AddFlagAndBind but deprecate the flag. +func AddDeprecatedFlagAndBind[T any](v *viper.Viper, fs *pflag.FlagSet, pfn FlagFunc[T], name, bind string, value T, usage string) { + AddFlagAndBind(v, fs, pfn, name, bind, value, usage) + deprecateFlag(fs, name) +} + +// AddHackedStringSliceP Hack for slice, see Loader.applyStringSliceHack. +func AddHackedStringSliceP(fs *pflag.FlagSet, name, shorthand, usage string) { + fs.StringSliceP(name, shorthand, nil, usage) +} + +// AddHackedStringSlice Hack for slice, see Loader.applyStringSliceHack. +func AddHackedStringSlice(fs *pflag.FlagSet, name, usage string) { + AddHackedStringSliceP(fs, name, "", usage) +} + +// AddDeprecatedHackedStringSlice similar to AddHackedStringSlice but deprecate the flag. +func AddDeprecatedHackedStringSlice(fs *pflag.FlagSet, name, usage string) { + AddHackedStringSlice(fs, name, usage) + deprecateFlag(fs, name) +} + +func deprecateFlag(fs *pflag.FlagSet, name string) { + _ = fs.MarkHidden(name) + _ = fs.MarkDeprecated(name, "check the documentation for more information.") +} 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 69df211542..61ed0f22f2 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/commands/linters.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/linters.go @@ -2,48 +2,82 @@ package commands import ( "fmt" - "strings" "github.com/fatih/color" "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/lint/linter" + "github.com/golangci/golangci-lint/pkg/lint/lintersdb" + "github.com/golangci/golangci-lint/pkg/logutils" ) -func (e *Executor) initLinters() { - e.lintersCmd = &cobra.Command{ +type lintersOptions struct { + config.LoaderOptions +} + +type lintersCommand struct { + viper *viper.Viper + cmd *cobra.Command + + opts lintersOptions + + cfg *config.Config + + log logutils.Log + + dbManager *lintersdb.Manager +} + +func newLintersCommand(logger logutils.Log) *lintersCommand { + c := &lintersCommand{ + viper: viper.New(), + cfg: config.NewDefault(), + log: logger, + } + + lintersCmd := &cobra.Command{ Use: "linters", Short: "List current linters configuration", Args: cobra.NoArgs, ValidArgsFunction: cobra.NoFileCompletions, - RunE: e.executeLinters, + RunE: c.execute, + PreRunE: c.preRunE, + SilenceUsage: true, } - fs := e.lintersCmd.Flags() + fs := lintersCmd.Flags() fs.SortFlags = false // sort them as they are defined here - initConfigFileFlagSet(fs, &e.cfg.Run) - e.initLintersFlagSet(fs, &e.cfg.Linters) + setupConfigFileFlagSet(fs, &c.opts.LoaderOptions) + setupLintersFlagSet(c.viper, fs) - e.rootCmd.AddCommand(e.lintersCmd) + c.cmd = lintersCmd + + return c } -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(), "|")))) +func (c *lintersCommand) preRunE(cmd *cobra.Command, _ []string) error { + loader := config.NewLoader(c.log.Child(logutils.DebugKeyConfigReader), c.viper, cmd.Flags(), c.opts.LoaderOptions, c.cfg) + + if err := loader.Load(); err != nil { + return fmt.Errorf("can't load config: %w", err) + } + + dbManager, err := lintersdb.NewManager(c.log.Child(logutils.DebugKeyLintersDB), c.cfg, + lintersdb.NewLinterBuilder(), lintersdb.NewPluginModuleBuilder(c.log), lintersdb.NewPluginGoBuilder(c.log)) + if err != nil { + return err + } + + c.dbManager = dbManager + + return nil } -// executeLinters runs the 'linters' CLI command, which displays the supported linters. -func (e *Executor) executeLinters(_ *cobra.Command, _ []string) error { - enabledLintersMap, err := e.EnabledLintersSet.GetEnabledLintersMap() +func (c *lintersCommand) execute(_ *cobra.Command, _ []string) error { + enabledLintersMap, err := c.dbManager.GetEnabledLintersMap() if err != nil { return fmt.Errorf("can't get enabled linters: %w", err) } @@ -51,7 +85,7 @@ func (e *Executor) executeLinters(_ *cobra.Command, _ []string) error { var enabledLinters []*linter.Config var disabledLCs []*linter.Config - for _, lc := range e.DBManager.GetAllSupportedLinterConfigs() { + for _, lc := range c.dbManager.GetAllSupportedLinterConfigs() { if lc.Internal { continue } @@ -64,9 +98,9 @@ func (e *Executor) executeLinters(_ *cobra.Command, _ []string) error { } color.Green("Enabled by your configuration linters:\n") - printLinterConfigs(enabledLinters) + printLinters(enabledLinters) color.Red("\nDisabled by your configuration linters:\n") - printLinterConfigs(disabledLCs) + printLinters(disabledLCs) return nil } 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 4425be10fd..cbb838aac2 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/commands/root.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/root.go @@ -1,175 +1,167 @@ package commands import ( + "errors" "fmt" "os" - "runtime" - "runtime/pprof" - "runtime/trace" - "strconv" + "slices" + "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/exitcodes" "github.com/golangci/golangci-lint/pkg/logutils" ) -const ( - // envHelpRun value: "1". - envHelpRun = "HELP_RUN" - envMemProfileRate = "GL_MEM_PROFILE_RATE" -) +func Execute(info BuildInfo) error { + return newRootCommand(info).Execute() +} -func (e *Executor) persistentPreRun(_ *cobra.Command, _ []string) error { - if e.cfg.Run.PrintVersion { - _ = printVersion(logutils.StdOut, e.buildInfo) - os.Exit(exitcodes.Success) // a return nil is not enough to stop the process because we are inside the `preRun`. - } +type rootOptions struct { + PrintVersion bool // Flag only. - runtime.GOMAXPROCS(e.cfg.Run.Concurrency) + Verbose bool // Flag only. + Color string // Flag only. +} - if e.cfg.Run.CPUProfilePath != "" { - f, err := os.Create(e.cfg.Run.CPUProfilePath) - if err != nil { - return fmt.Errorf("can't create file %s: %w", e.cfg.Run.CPUProfilePath, err) - } - if err := pprof.StartCPUProfile(f); err != nil { - return fmt.Errorf("can't start CPU profiling: %w", err) - } - } +type rootCommand struct { + cmd *cobra.Command + opts rootOptions - if e.cfg.Run.MemProfilePath != "" { - if rate := os.Getenv(envMemProfileRate); rate != "" { - runtime.MemProfileRate, _ = strconv.Atoi(rate) - } - } + log logutils.Log +} - if e.cfg.Run.TracePath != "" { - f, err := os.Create(e.cfg.Run.TracePath) - if err != nil { - return fmt.Errorf("can't create file %s: %w", e.cfg.Run.TracePath, err) - } - if err = trace.Start(f); err != nil { - return fmt.Errorf("can't start tracing: %w", err) - } - } +func newRootCommand(info BuildInfo) *rootCommand { + c := &rootCommand{} - return nil -} + rootCmd := &cobra.Command{ + Use: "golangci-lint", + Short: "golangci-lint is a smart linters runner.", + Long: `Smart, fast linters runner.`, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + if c.opts.PrintVersion { + _ = printVersion(logutils.StdOut, info) + return nil + } -func (e *Executor) persistentPostRun(_ *cobra.Command, _ []string) error { - if e.cfg.Run.CPUProfilePath != "" { - pprof.StopCPUProfile() + return cmd.Help() + }, } - if e.cfg.Run.MemProfilePath != "" { - f, err := os.Create(e.cfg.Run.MemProfilePath) - if err != nil { - return fmt.Errorf("can't create file %s: %w", e.cfg.Run.MemProfilePath, err) - } + fs := rootCmd.Flags() + fs.BoolVar(&c.opts.PrintVersion, "version", false, color.GreenString("Print version")) - var ms runtime.MemStats - runtime.ReadMemStats(&ms) - printMemStats(&ms, e.log) + setupRootPersistentFlags(rootCmd.PersistentFlags(), &c.opts) - if err := pprof.WriteHeapProfile(f); err != nil { - return fmt.Errorf("cCan't write heap profile: %w", err) - } - _ = f.Close() - } + log := logutils.NewStderrLog(logutils.DebugKeyEmpty) - if e.cfg.Run.TracePath != "" { - trace.Stop() - } + // Each command uses a dedicated configuration structure to avoid side effects of bindings. + rootCmd.AddCommand( + newLintersCommand(log).cmd, + newRunCommand(log, info).cmd, + newCacheCommand().cmd, + newConfigCommand(log, info).cmd, + newVersionCommand(info).cmd, + newCustomCommand(log).cmd, + ) - os.Exit(e.exitCode) + rootCmd.SetHelpCommand(newHelpCommand(log).cmd) - return nil + c.log = log + c.cmd = rootCmd + + return c } -func printMemStats(ms *runtime.MemStats, logger logutils.Log) { - logger.Infof("Mem stats: alloc=%s total_alloc=%s sys=%s "+ - "heap_alloc=%s heap_sys=%s heap_idle=%s heap_released=%s heap_in_use=%s "+ - "stack_in_use=%s stack_sys=%s "+ - "mspan_sys=%s mcache_sys=%s buck_hash_sys=%s gc_sys=%s other_sys=%s "+ - "mallocs_n=%d frees_n=%d heap_objects_n=%d gc_cpu_fraction=%.2f", - formatMemory(ms.Alloc), formatMemory(ms.TotalAlloc), formatMemory(ms.Sys), - formatMemory(ms.HeapAlloc), formatMemory(ms.HeapSys), - formatMemory(ms.HeapIdle), formatMemory(ms.HeapReleased), formatMemory(ms.HeapInuse), - formatMemory(ms.StackInuse), formatMemory(ms.StackSys), - formatMemory(ms.MSpanSys), formatMemory(ms.MCacheSys), formatMemory(ms.BuckHashSys), - formatMemory(ms.GCSys), formatMemory(ms.OtherSys), - ms.Mallocs, ms.Frees, ms.HeapObjects, ms.GCCPUFraction) +func (c *rootCommand) Execute() error { + err := setupLogger(c.log) + if err != nil { + return err + } + + return c.cmd.Execute() } -func formatMemory(memBytes uint64) string { - const Kb = 1024 - const Mb = Kb * 1024 +func setupRootPersistentFlags(fs *pflag.FlagSet, opts *rootOptions) { + fs.BoolP("help", "h", false, color.GreenString("Help for a command")) + fs.BoolVarP(&opts.Verbose, "verbose", "v", false, color.GreenString("Verbose output")) + fs.StringVar(&opts.Color, "color", "auto", color.GreenString("Use color when printing; can be 'always', 'auto', or 'never'")) +} - if memBytes < Kb { - return fmt.Sprintf("%db", memBytes) +func setupLogger(logger logutils.Log) error { + opts, err := forceRootParsePersistentFlags() + if err != nil && !errors.Is(err, pflag.ErrHelp) { + return err } - if memBytes < Mb { - return fmt.Sprintf("%dkb", memBytes/Kb) + + if opts == nil { + return nil } - return fmt.Sprintf("%dmb", memBytes/Mb) -} -func getDefaultConcurrency() int { - if os.Getenv(envHelpRun) == "1" { - // Make stable concurrency for generating help documentation. - const prettyConcurrency = 8 - return prettyConcurrency + logutils.SetupVerboseLog(logger, opts.Verbose) + + switch opts.Color { + case "always": + color.NoColor = false + case "never": + color.NoColor = true + case "auto": + // nothing + default: + logger.Fatalf("invalid value %q for --color; must be 'always', 'auto', or 'never'", opts.Color) } - return runtime.NumCPU() + return nil } -func (e *Executor) initRoot() { - rootCmd := &cobra.Command{ - Use: "golangci-lint", - Short: "golangci-lint is a smart linters runner.", - Long: `Smart, fast linters runner.`, - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, _ []string) error { - return cmd.Help() - }, - PersistentPreRunE: e.persistentPreRun, - PersistentPostRunE: e.persistentPostRun, - } +func forceRootParsePersistentFlags() (*rootOptions, error) { + // We use another pflag.FlagSet here to not set `changed` flag on cmd.Flags() options. + // Otherwise, string slice options will be duplicated. + fs := pflag.NewFlagSet("config flag set", pflag.ContinueOnError) - initRootFlagSet(rootCmd.PersistentFlags(), e.cfg, e.needVersionOption()) - e.rootCmd = rootCmd -} + // Ignore unknown flags because we will parse the command flags later. + fs.ParseErrorsWhitelist = pflag.ParseErrorsWhitelist{UnknownFlags: true} -func (e *Executor) needVersionOption() bool { - return e.buildInfo.Date != "" -} + opts := &rootOptions{} -func initRootFlagSet(fs *pflag.FlagSet, cfg *config.Config, needVersionOption bool) { - fs.BoolVarP(&cfg.Run.IsVerbose, "verbose", "v", false, wh("Verbose output")) + // Don't do `fs.AddFlagSet(cmd.Flags())` because it shares flags representations: + // `changed` variable inside string slice vars will be shared. + // Use another config variable here, + // to not affect main parsing by this parsing of only config option. + setupRootPersistentFlags(fs, opts) - var silent bool - fs.BoolVarP(&silent, "silent", "s", false, wh("Disables congrats outputs")) - if err := fs.MarkHidden("silent"); err != nil { - panic(err) - } - err := fs.MarkDeprecated("silent", - "now golangci-lint by default is silent: it doesn't print Congrats message") - if err != nil { - panic(err) + fs.Usage = func() {} // otherwise, help text will be printed twice + + if err := fs.Parse(safeArgs(fs, os.Args)); err != nil { + if errors.Is(err, pflag.ErrHelp) { + return nil, err + } + + return nil, fmt.Errorf("can't parse args: %w", err) } - 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("Number of CPUs to use (Default: number of logical CPUs)")) - if needVersionOption { - fs.BoolVar(&cfg.Run.PrintVersion, "version", false, wh("Print version")) + return opts, nil +} + +// Shorthands are a problem because pflag, with UnknownFlags, will try to parse all the letters as options. +// A shorthand can aggregate several letters (ex `ps -aux`) +// The function replaces non-supported shorthands by a dumb flag. +func safeArgs(fs *pflag.FlagSet, args []string) []string { + var shorthands []string + fs.VisitAll(func(flag *pflag.Flag) { + shorthands = append(shorthands, flag.Shorthand) + }) + + var cleanArgs []string + for _, arg := range args { + if len(arg) > 1 && arg[0] == '-' && arg[1] != '-' && !slices.Contains(shorthands, string(arg[1])) { + cleanArgs = append(cleanArgs, "--potato") + continue + } + + cleanArgs = append(cleanArgs, arg) } - fs.StringVar(&cfg.Output.Color, "color", "auto", wh("Use color when printing; can be 'always', 'auto', or 'never'")) + return cleanArgs } 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 5c7083c307..8ed8c5eed2 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/commands/run.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/run.go @@ -1,34 +1,49 @@ package commands import ( + "bytes" "context" + "crypto/sha256" "errors" "fmt" "io" "log" "os" + "path/filepath" "runtime" + "runtime/pprof" + "runtime/trace" "sort" + "strconv" "strings" "time" "github.com/fatih/color" + "github.com/gofrs/flock" "github.com/spf13/cobra" "github.com/spf13/pflag" + "github.com/spf13/viper" + "go.uber.org/automaxprocs/maxprocs" "golang.org/x/exp/maps" + "gopkg.in/yaml.v3" + "github.com/golangci/golangci-lint/internal/cache" + "github.com/golangci/golangci-lint/internal/pkgcache" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/exitcodes" + "github.com/golangci/golangci-lint/pkg/fsutils" + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis/load" + "github.com/golangci/golangci-lint/pkg/goutil" "github.com/golangci/golangci-lint/pkg/lint" + "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/lint/lintersdb" "github.com/golangci/golangci-lint/pkg/logutils" - "github.com/golangci/golangci-lint/pkg/packages" "github.com/golangci/golangci-lint/pkg/printers" + "github.com/golangci/golangci-lint/pkg/report" "github.com/golangci/golangci-lint/pkg/result" + "github.com/golangci/golangci-lint/pkg/timeutils" ) -const defaultFileMode = 0644 - const defaultTimeout = time.Minute const ( @@ -38,437 +53,386 @@ const ( envMemLogEvery = "GL_MEM_LOG_EVERY" ) -//nolint:funlen,gomnd -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) - } +const ( + // envHelpRun value: "1". + envHelpRun = "HELP_RUN" + envMemProfileRate = "GL_MEM_PROFILE_RATE" +) - // we run initFlagSet multiple times, but we wouldn't like to see deprecation message multiple times - if isFinalInit { - const deprecateMessage = "flag will be removed soon, please, use .golangci.yml config" - if err := fs.MarkDeprecated(name, deprecateMessage); err != nil { - panic(err) - } - } - } +type runOptions struct { + config.LoaderOptions + + CPUProfilePath string // Flag only. + MemProfilePath string // Flag only. + TracePath string // Flag only. - // Config file config - rc := &cfg.Run - initConfigFileFlagSet(fs, rc) - - // Output config - oc := &cfg.Output - fs.StringVar(&oc.Format, "out-format", - config.OutFormatColoredLineNumber, - wh(fmt.Sprintf("Format of output: %s", strings.Join(config.OutFormats, "|")))) - fs.BoolVar(&oc.PrintIssuedLine, "print-issued-lines", true, wh("Print lines of code with issue")) - fs.BoolVar(&oc.PrintLinterName, "print-linter-name", true, wh("Print linter name in issue line")) - fs.BoolVar(&oc.UniqByLine, "uniq-by-line", true, wh("Make issues output unique by line")) - fs.BoolVar(&oc.SortResults, "sort-results", false, wh("Sort linter results")) - fs.BoolVar(&oc.PrintWelcomeMessage, "print-welcome", false, wh("Print welcome message")) - fs.StringVar(&oc.PathPrefix, "path-prefix", "", wh("Path prefix to add to output")) - hideFlag("print-welcome") // no longer used - - fs.BoolVar(&cfg.InternalCmdTest, "internal-cmd-test", false, wh("Option is used only for testing golangci-lint command, don't use it")) - if err := fs.MarkHidden("internal-cmd-test"); err != nil { - panic(err) - } - - // Run config - fs.StringVar(&rc.ModulesDownloadMode, "modules-download-mode", "", - wh("Modules download mode. If not empty, passed as -mod= 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")) - fs.StringSliceVar(&rc.BuildTags, "build-tags", nil, wh("Build tags")) - - fs.DurationVar(&rc.Timeout, "deadline", defaultTimeout, wh("Deadline for total work")) - if err := fs.MarkHidden("deadline"); err != nil { - panic(err) - } - fs.DurationVar(&rc.Timeout, "timeout", defaultTimeout, wh("Timeout for total work")) - - 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.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")) - - 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. " + - "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 - - // Hide all linters settings flags: they were initially visible, - // but when number of linters started to grow it became obvious that - // we can't fill 90% of flags by linters settings: common flags became hard to find. - // New linters settings should be done only through config file. - fs.BoolVar(&lsc.Errcheck.CheckTypeAssertions, "errcheck.check-type-assertions", - false, "Errcheck: check for ignored type assertion results") - hideFlag("errcheck.check-type-assertions") - fs.BoolVar(&lsc.Errcheck.CheckAssignToBlank, "errcheck.check-blank", false, - "Errcheck: check for errors assigned to blank identifier: _ = errFunc()") - hideFlag("errcheck.check-blank") - fs.StringVar(&lsc.Errcheck.Exclude, "errcheck.exclude", "", - "Path to a file containing a list of functions to exclude from checking") - hideFlag("errcheck.exclude") - fs.StringVar(&lsc.Errcheck.Ignore, "errcheck.ignore", "fmt:.*", - `Comma-separated list of pairs of the form pkg:regex. The regex is used to ignore names within pkg`) - hideFlag("errcheck.ignore") - - fs.BoolVar(&lsc.Govet.CheckShadowing, "govet.check-shadowing", false, - "Govet: check for shadowed variables") - hideFlag("govet.check-shadowing") - - fs.Float64Var(&lsc.Golint.MinConfidence, "golint.min-confidence", 0.8, - "Golint: minimum confidence of a problem to print it") - hideFlag("golint.min-confidence") - - fs.BoolVar(&lsc.Gofmt.Simplify, "gofmt.simplify", true, "Gofmt: simplify code") - hideFlag("gofmt.simplify") - - fs.IntVar(&lsc.Gocyclo.MinComplexity, "gocyclo.min-complexity", - 30, "Minimal complexity of function to report it") - hideFlag("gocyclo.min-complexity") - - fs.BoolVar(&lsc.Maligned.SuggestNewOrder, "maligned.suggest-new", false, - "Maligned: print suggested more optimal struct fields ordering") - hideFlag("maligned.suggest-new") - - fs.IntVar(&lsc.Dupl.Threshold, "dupl.threshold", - 150, "Dupl: Minimal threshold to detect copy-paste") - hideFlag("dupl.threshold") - - fs.BoolVar(&lsc.Goconst.MatchWithConstants, "goconst.match-constant", - true, "Goconst: look for existing constants matching the values") - hideFlag("goconst.match-constant") - fs.IntVar(&lsc.Goconst.MinStringLen, "goconst.min-len", - 3, "Goconst: minimum constant string length") - hideFlag("goconst.min-len") - fs.IntVar(&lsc.Goconst.MinOccurrencesCount, "goconst.min-occurrences", - 3, "Goconst: minimum occurrences of constant string count to trigger issue") - hideFlag("goconst.min-occurrences") - fs.BoolVar(&lsc.Goconst.ParseNumbers, "goconst.numbers", - false, "Goconst: search also for duplicated numbers") - hideFlag("goconst.numbers") - fs.IntVar(&lsc.Goconst.NumberMin, "goconst.min", - 3, "minimum value, only works with goconst.numbers") - hideFlag("goconst.min") - fs.IntVar(&lsc.Goconst.NumberMax, "goconst.max", - 3, "maximum value, only works with goconst.numbers") - hideFlag("goconst.max") - fs.BoolVar(&lsc.Goconst.IgnoreCalls, "goconst.ignore-calls", - true, "Goconst: ignore when constant is not used as function argument") - hideFlag("goconst.ignore-calls") - - fs.IntVar(&lsc.Lll.TabWidth, "lll.tab-width", 1, - "Lll: tab width in spaces") - hideFlag("lll.tab-width") - - // Linters config - lc := &cfg.Linters - e.initLintersFlagSet(fs, lc) - - // Issues config - ic := &cfg.Issues - fs.StringSliceVarP(&ic.ExcludePatterns, "exclude", "e", nil, wh("Exclude issue by regexp")) - fs.BoolVar(&ic.UseDefaultExcludes, "exclude-use-default", true, getDefaultIssueExcludeHelp()) - fs.BoolVar(&ic.ExcludeCaseSensitive, "exclude-case-sensitive", false, wh("If set to true exclude "+ - "and exclude rules regular expressions are case sensitive")) - - fs.IntVar(&ic.MaxIssuesPerLinter, "max-issues-per-linter", 50, - wh("Maximum issues count per one linter. Set to 0 to disable")) - fs.IntVar(&ic.MaxSameIssues, "max-same-issues", 3, - wh("Maximum count of issues with the same text. Set to 0 to disable")) - - fs.BoolVarP(&ic.Diff, "new", "n", false, - wh("Show only new issues: if there are unstaged changes or untracked files, only those changes "+ - "are analyzed, else only changes in HEAD~ are analyzed.\nIt's a super-useful option for integration "+ - "of golangci-lint into existing large codebase.\nIt's not practical to fix all existing issues at "+ - "the moment of integration: much better to not allow issues in new code.\nFor CI setups, prefer "+ - "--new-from-rev=HEAD~, as --new can skip linting the current patch if any scripts generate "+ - "unstaged files before golangci-lint runs.")) - fs.StringVar(&ic.DiffFromRevision, "new-from-rev", "", - wh("Show only new issues created after git revision `REV`")) - fs.StringVar(&ic.DiffPatchFilePath, "new-from-patch", "", - 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, wh("Fix found issues (if it's supported by the linter)")) + PrintResourcesUsage bool // Flag only. } -func (e *Executor) initRunConfiguration(cmd *cobra.Command) { - fs := cmd.Flags() - fs.SortFlags = false // sort them as they are defined here - e.initFlagSet(fs, e.cfg, true) +type runCommand struct { + viper *viper.Viper + cmd *cobra.Command + + opts runOptions + + cfg *config.Config + + buildInfo BuildInfo + + dbManager *lintersdb.Manager + + printer *printers.Printer + + log logutils.Log + debugf logutils.DebugFunc + reportData *report.Data + + contextBuilder *lint.ContextBuilder + goenv *goutil.Env + + fileCache *fsutils.FileCache + lineCache *fsutils.LineCache + + flock *flock.Flock + + exitCode int } -func (e *Executor) getConfigForCommandLine() (*config.Config, error) { - // We use another pflag.FlagSet here to not set `changed` flag - // on cmd.Flags() options. Otherwise, string slice options will be duplicated. - fs := pflag.NewFlagSet("config flag set", pflag.ContinueOnError) - - var cfg config.Config - // Don't do `fs.AddFlagSet(cmd.Flags())` because it shares flags representations: - // `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. - e.initFlagSet(fs, &cfg, false) - initVersionFlagSet(fs, &cfg) - - // Parse max options, even force version option: don't want - // to get access to Executor here: it's error-prone to use - // cfg vs e.cfg. - initRootFlagSet(fs, &cfg, true) - - fs.Usage = func() {} // otherwise, help text will be printed twice - if err := fs.Parse(os.Args); err != nil { - if errors.Is(err, pflag.ErrHelp) { - return nil, err - } +func newRunCommand(logger logutils.Log, info BuildInfo) *runCommand { + reportData := &report.Data{} + + c := &runCommand{ + viper: viper.New(), + log: report.NewLogWrapper(logger, reportData), + debugf: logutils.Debug(logutils.DebugKeyExec), + cfg: config.NewDefault(), + reportData: reportData, + buildInfo: info, + } - return nil, fmt.Errorf("can't parse args: %w", err) + runCmd := &cobra.Command{ + Use: "run", + Short: "Run the linters", + Run: c.execute, + PreRunE: c.preRunE, + PostRun: c.postRun, + PersistentPreRunE: c.persistentPreRunE, + PersistentPostRunE: c.persistentPostRunE, + SilenceUsage: true, } - return &cfg, nil + runCmd.SetOut(logutils.StdOut) // use custom output to properly color it in Windows terminals + runCmd.SetErr(logutils.StdErr) + + fs := runCmd.Flags() + fs.SortFlags = false // sort them as they are defined here + + // Only for testing purpose. + // Don't add other flags here. + fs.BoolVar(&c.cfg.InternalCmdTest, "internal-cmd-test", false, + color.GreenString("Option is used only for testing golangci-lint command, don't use it")) + _ = fs.MarkHidden("internal-cmd-test") + + setupConfigFileFlagSet(fs, &c.opts.LoaderOptions) + + setupLintersFlagSet(c.viper, fs) + setupRunFlagSet(c.viper, fs) + setupOutputFlagSet(c.viper, fs) + setupIssuesFlagSet(c.viper, fs) + + setupRunPersistentFlags(runCmd.PersistentFlags(), &c.opts) + + c.cmd = runCmd + + return c } -func (e *Executor) initRun() { - e.runCmd = &cobra.Command{ - Use: "run", - Short: "Run the linters", - Run: e.executeRun, - PreRunE: func(_ *cobra.Command, _ []string) error { - if ok := e.acquireFileLock(); !ok { - return errors.New("parallel golangci-lint is running") - } - return nil - }, - PostRun: func(_ *cobra.Command, _ []string) { - e.releaseFileLock() - }, +func (c *runCommand) persistentPreRunE(cmd *cobra.Command, _ []string) error { + if err := c.startTracing(); err != nil { + return err } - e.rootCmd.AddCommand(e.runCmd) - e.runCmd.SetOut(logutils.StdOut) // use custom output to properly color it in Windows terminals - e.runCmd.SetErr(logutils.StdErr) + loader := config.NewLoader(c.log.Child(logutils.DebugKeyConfigReader), c.viper, cmd.Flags(), c.opts.LoaderOptions, c.cfg) - e.initRunConfiguration(e.runCmd) -} + if err := loader.Load(); err != nil { + return fmt.Errorf("can't load config: %w", err) + } -func fixSlicesFlags(fs *pflag.FlagSet) { - // It's a dirty hack to set flag.Changed to true for every string slice flag. - // It's necessary to merge config and command-line slices: otherwise command-line - // flags will always overwrite ones from the config. - fs.VisitAll(func(f *pflag.Flag) { - if f.Value.Type() != "stringSlice" { - return - } + if c.cfg.Run.Concurrency == 0 { + backup := runtime.GOMAXPROCS(0) - s, err := fs.GetStringSlice(f.Name) + // Automatically set GOMAXPROCS to match Linux container CPU quota. + _, err := maxprocs.Set(maxprocs.Logger(c.log.Infof)) if err != nil { - return + runtime.GOMAXPROCS(backup) } + } else { + runtime.GOMAXPROCS(c.cfg.Run.Concurrency) + } - if s == nil { // assume that every string slice flag has nil as the default - return - } + return c.startTracing() +} - var safe []string - for _, v := range s { - // add quotes to escape comma because spf13/pflag use a CSV parser: - // https://github.com/spf13/pflag/blob/85dd5c8bc61cfa382fecd072378089d4e856579d/string_slice.go#L43 - safe = append(safe, `"`+v+`"`) - } +func (c *runCommand) persistentPostRunE(_ *cobra.Command, _ []string) error { + if err := c.stopTracing(); err != nil { + return err + } - // calling Set sets Changed to true: next Set calls will append, not overwrite - _ = f.Value.Set(strings.Join(safe, ",")) - }) + os.Exit(c.exitCode) + + return nil } -// runAnalysis executes the linters that have been enabled in the configuration. -func (e *Executor) runAnalysis(ctx context.Context, args []string) ([]result.Issue, error) { - e.cfg.Run.Args = args +func (c *runCommand) preRunE(_ *cobra.Command, args []string) error { + dbManager, err := lintersdb.NewManager(c.log.Child(logutils.DebugKeyLintersDB), c.cfg, + lintersdb.NewLinterBuilder(), lintersdb.NewPluginModuleBuilder(c.log), lintersdb.NewPluginGoBuilder(c.log)) + if err != nil { + return err + } + + c.dbManager = dbManager - lintersToRun, err := e.EnabledLintersSet.GetOptimizedLinters() + printer, err := printers.NewPrinter(c.log, &c.cfg.Output, c.reportData) if err != nil { - return nil, err + return err } - enabledLintersMap, err := e.EnabledLintersSet.GetEnabledLintersMap() + c.printer = printer + + c.goenv = goutil.NewEnv(c.log.Child(logutils.DebugKeyGoEnv)) + + c.fileCache = fsutils.NewFileCache() + c.lineCache = fsutils.NewLineCache(c.fileCache) + + sw := timeutils.NewStopwatch("pkgcache", c.log.Child(logutils.DebugKeyStopwatch)) + + pkgCache, err := pkgcache.NewCache(sw, c.log.Child(logutils.DebugKeyPkgCache)) if err != nil { - return nil, err + return fmt.Errorf("failed to build packages cache: %w", err) } - for _, lc := range e.DBManager.GetAllSupportedLinterConfigs() { - isEnabled := enabledLintersMap[lc.Name()] != nil - e.reportData.AddLinter(lc.Name(), isEnabled, lc.EnabledByDefault) + guard := load.NewGuard() + + pkgLoader := lint.NewPackageLoader(c.log.Child(logutils.DebugKeyLoader), c.cfg, args, c.goenv, guard) + + c.contextBuilder = lint.NewContextBuilder(c.cfg, pkgLoader, c.fileCache, pkgCache, guard) + + if err = initHashSalt(c.buildInfo.Version, c.cfg); err != nil { + return fmt.Errorf("failed to init hash salt: %w", err) } - lintCtx, err := e.contextLoader.Load(ctx, lintersToRun) - if err != nil { - return nil, fmt.Errorf("context loading failed: %w", err) + if ok := c.acquireFileLock(); !ok { + return errors.New("parallel golangci-lint is running") } - lintCtx.Log = e.log.Child(logutils.DebugKeyLintersContext) - runner, err := lint.NewRunner(e.cfg, e.log.Child(logutils.DebugKeyRunner), - e.goenv, e.EnabledLintersSet, e.lineCache, e.fileCache, e.DBManager, lintCtx.Packages) - if err != nil { - return nil, err + return nil +} + +func (c *runCommand) postRun(_ *cobra.Command, _ []string) { + c.releaseFileLock() +} + +func (c *runCommand) execute(_ *cobra.Command, args []string) { + needTrackResources := logutils.IsVerbose() || c.opts.PrintResourcesUsage + + trackResourcesEndCh := make(chan struct{}) + defer func() { // XXX: this defer must be before ctx.cancel defer + if needTrackResources { // wait until resource tracking finished to print properly + <-trackResourcesEndCh + } + }() + + ctx, cancel := context.WithTimeout(context.Background(), c.cfg.Run.Timeout) + defer cancel() + + if needTrackResources { + go watchResources(ctx, trackResourcesEndCh, c.log, c.debugf) + } + + if err := c.runAndPrint(ctx, args); err != nil { + c.log.Errorf("Running error: %s", err) + if c.exitCode == exitcodes.Success { + var exitErr *exitcodes.ExitError + if errors.As(err, &exitErr) { + c.exitCode = exitErr.Code + } else { + c.exitCode = exitcodes.Failure + } + } } - return runner.Run(ctx, lintersToRun, lintCtx) + c.setupExitCode(ctx) } -func (e *Executor) setOutputToDevNull() (savedStdout, savedStderr *os.File) { - savedStdout, savedStderr = os.Stdout, os.Stderr - devNull, err := os.Open(os.DevNull) - if err != nil { - e.log.Warnf("Can't open null device %q: %s", os.DevNull, err) - return +func (c *runCommand) startTracing() error { + if c.opts.CPUProfilePath != "" { + f, err := os.Create(c.opts.CPUProfilePath) + if err != nil { + return fmt.Errorf("can't create file %s: %w", c.opts.CPUProfilePath, err) + } + if err := pprof.StartCPUProfile(f); err != nil { + return fmt.Errorf("can't start CPU profiling: %w", err) + } } - os.Stdout, os.Stderr = devNull, devNull - return + if c.opts.MemProfilePath != "" { + if rate := os.Getenv(envMemProfileRate); rate != "" { + runtime.MemProfileRate, _ = strconv.Atoi(rate) + } + } + + if c.opts.TracePath != "" { + f, err := os.Create(c.opts.TracePath) + if err != nil { + return fmt.Errorf("can't create file %s: %w", c.opts.TracePath, err) + } + if err = trace.Start(f); err != nil { + return fmt.Errorf("can't start tracing: %w", err) + } + } + + return nil } -func (e *Executor) setExitCodeIfIssuesFound(issues []result.Issue) { - if len(issues) != 0 { - e.exitCode = e.cfg.Run.ExitCodeIfIssuesFound +func (c *runCommand) stopTracing() error { + if c.opts.CPUProfilePath != "" { + pprof.StopCPUProfile() + } + + if c.opts.MemProfilePath != "" { + f, err := os.Create(c.opts.MemProfilePath) + if err != nil { + return fmt.Errorf("can't create file %s: %w", c.opts.MemProfilePath, err) + } + + var ms runtime.MemStats + runtime.ReadMemStats(&ms) + printMemStats(&ms, c.log) + + if err := pprof.WriteHeapProfile(f); err != nil { + return fmt.Errorf("can't write heap profile: %w", err) + } + _ = f.Close() } + + if c.opts.TracePath != "" { + trace.Stop() + } + + return nil } -func (e *Executor) runAndPrint(ctx context.Context, args []string) error { - if err := e.goenv.Discover(ctx); err != nil { - e.log.Warnf("Failed to discover go env: %s", err) +func (c *runCommand) runAndPrint(ctx context.Context, args []string) error { + if err := c.goenv.Discover(ctx); err != nil { + c.log.Warnf("Failed to discover go env: %s", err) } if !logutils.HaveDebugTag(logutils.DebugKeyLintersOutput) { // Don't allow linters and loader to print anything log.SetOutput(io.Discard) - savedStdout, savedStderr := e.setOutputToDevNull() + savedStdout, savedStderr := c.setOutputToDevNull() defer func() { os.Stdout, os.Stderr = savedStdout, savedStderr }() } - issues, err := e.runAnalysis(ctx, args) + enabledLintersMap, err := c.dbManager.GetEnabledLintersMap() if err != nil { - return err // XXX: don't loose type + return err } - formats := strings.Split(e.cfg.Output.Format, ",") - for _, format := range formats { - out := strings.SplitN(format, ":", 2) - if len(out) < 2 { - out = append(out, "") - } + c.printDeprecatedLinterMessages(enabledLintersMap) - err := e.printReports(issues, out[1], out[0]) - if err != nil { - return err - } + issues, err := c.runAnalysis(ctx, args) + if err != nil { + return err // XXX: don't lose type } - e.printStats(issues) + // Fills linters information for the JSON printer. + for _, lc := range c.dbManager.GetAllSupportedLinterConfigs() { + isEnabled := enabledLintersMap[lc.Name()] != nil + c.reportData.AddLinter(lc.Name(), isEnabled, lc.EnabledByDefault) + } - e.setExitCodeIfIssuesFound(issues) + err = c.printer.Print(issues) + if err != nil { + return err + } + + c.printStats(issues) - e.fileCache.PrintStats(e.log) + c.setExitCodeIfIssuesFound(issues) + + c.fileCache.PrintStats(c.log) return nil } -func (e *Executor) printReports(issues []result.Issue, path, format string) error { - w, shouldClose, err := e.createWriter(path) +// runAnalysis executes the linters that have been enabled in the configuration. +func (c *runCommand) runAnalysis(ctx context.Context, args []string) ([]result.Issue, error) { + lintersToRun, err := c.dbManager.GetOptimizedLinters() if err != nil { - return fmt.Errorf("can't create output for %s: %w", path, err) + return nil, err } - p, err := e.createPrinter(format, w) + lintCtx, err := c.contextBuilder.Build(ctx, c.log.Child(logutils.DebugKeyLintersContext), lintersToRun) if err != nil { - if file, ok := w.(io.Closer); shouldClose && ok { - _ = file.Close() - } - return err + return nil, fmt.Errorf("context loading failed: %w", err) } - if err = p.Print(issues); err != nil { - if file, ok := w.(io.Closer); shouldClose && ok { - _ = file.Close() - } - return fmt.Errorf("can't print %d issues: %w", len(issues), err) + runner, err := lint.NewRunner(c.log.Child(logutils.DebugKeyRunner), c.cfg, args, + c.goenv, c.lineCache, c.fileCache, c.dbManager, lintCtx) + if err != nil { + return nil, err } - if file, ok := w.(io.Closer); shouldClose && ok { - _ = file.Close() + return runner.Run(ctx, lintersToRun) +} + +func (c *runCommand) setOutputToDevNull() (savedStdout, savedStderr *os.File) { + savedStdout, savedStderr = os.Stdout, os.Stderr + devNull, err := os.Open(os.DevNull) + if err != nil { + c.log.Warnf("Can't open null device %q: %s", os.DevNull, err) + return } - return nil + os.Stdout, os.Stderr = devNull, devNull + return } -func (e *Executor) createWriter(path string) (io.Writer, bool, error) { - if path == "" || path == "stdout" { - return logutils.StdOut, false, nil - } - if path == "stderr" { - return logutils.StdErr, false, nil - } - f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, defaultFileMode) - if err != nil { - return nil, false, err +func (c *runCommand) setExitCodeIfIssuesFound(issues []result.Issue) { + if len(issues) != 0 { + c.exitCode = c.cfg.Run.ExitCodeIfIssuesFound } - return f, true, nil } -func (e *Executor) createPrinter(format string, w io.Writer) (printers.Printer, error) { - var p printers.Printer - switch format { - case config.OutFormatJSON: - p = printers.NewJSON(&e.reportData, w) - case config.OutFormatColoredLineNumber, config.OutFormatLineNumber: - p = printers.NewText(e.cfg.Output.PrintIssuedLine, - format == config.OutFormatColoredLineNumber, e.cfg.Output.PrintLinterName, - e.log.Child(logutils.DebugKeyTextPrinter), w) - case config.OutFormatTab, config.OutFormatColoredTab: - p = printers.NewTab(e.cfg.Output.PrintLinterName, - format == config.OutFormatColoredTab, - e.log.Child(logutils.DebugKeyTabPrinter), w) - case config.OutFormatCheckstyle: - p = printers.NewCheckstyle(w) - case config.OutFormatCodeClimate: - p = printers.NewCodeClimate(w) - case config.OutFormatHTML: - p = printers.NewHTML(w) - case config.OutFormatJunitXML: - p = printers.NewJunitXML(w) - case config.OutFormatGithubActions: - p = printers.NewGithub(w) - case config.OutFormatTeamCity: - p = printers.NewTeamCity(w) - default: - return nil, fmt.Errorf("unknown output format %s", format) - } - - return p, nil +func (c *runCommand) printDeprecatedLinterMessages(enabledLinters map[string]*linter.Config) { + if c.cfg.InternalCmdTest { + return + } + + for name, lc := range enabledLinters { + if !lc.IsDeprecated() { + continue + } + + var extra string + if lc.Deprecation.Replacement != "" { + extra = fmt.Sprintf("Replaced by %s.", lc.Deprecation.Replacement) + } + + c.log.Warnf("The linter '%s' is deprecated (since %s) due to: %s %s", name, lc.Deprecation.Since, lc.Deprecation.Message, extra) + } } -func (e *Executor) printStats(issues []result.Issue) { - if !e.cfg.Run.ShowStats { +func (c *runCommand) printStats(issues []result.Issue) { + if !c.cfg.Output.ShowStats { return } if len(issues) == 0 { - e.runCmd.Println("0 issues.") + c.cmd.Println("0 issues.") return } @@ -477,78 +441,76 @@ func (e *Executor) printStats(issues []result.Issue) { stats[issues[idx].FromLinter]++ } - e.runCmd.Printf("%d issues:\n", len(issues)) + c.cmd.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]) + c.cmd.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 - trackResourcesEndCh := make(chan struct{}) - defer func() { // XXX: this defer must be before ctx.cancel defer - if needTrackResources { // wait until resource tracking finished to print properly - <-trackResourcesEndCh - } - }() - - e.setTimeoutToDeadlineIfOnlyDeadlineIsSet() - ctx, cancel := context.WithTimeout(context.Background(), e.cfg.Run.Timeout) - defer cancel() - - if needTrackResources { - go watchResources(ctx, trackResourcesEndCh, e.log, e.debugf) +func (c *runCommand) setupExitCode(ctx context.Context) { + if ctx.Err() != nil { + c.exitCode = exitcodes.Timeout + c.log.Errorf("Timeout exceeded: try increasing it by passing --timeout option") + return } - if err := e.runAndPrint(ctx, args); err != nil { - e.log.Errorf("Running error: %s", err) - if e.exitCode == exitcodes.Success { - var exitErr *exitcodes.ExitError - if errors.As(err, &exitErr) { - e.exitCode = exitErr.Code - } else { - e.exitCode = exitcodes.Failure - } - } + if c.exitCode != exitcodes.Success { + return } - e.setupExitCode(ctx) -} + needFailOnWarnings := os.Getenv(logutils.EnvTestRun) == "1" || os.Getenv(envFailOnWarnings) == "1" + if needFailOnWarnings && len(c.reportData.Warnings) != 0 { + c.exitCode = exitcodes.WarningInTest + return + } -// to be removed when deadline is finally decommissioned -func (e *Executor) setTimeoutToDeadlineIfOnlyDeadlineIsSet() { - deadlineValue := e.cfg.Run.Deadline - if deadlineValue != 0 && e.cfg.Run.Timeout == defaultTimeout { - e.cfg.Run.Timeout = deadlineValue + if c.reportData.Error != "" { + // it's a case e.g. when typecheck linter couldn't parse and error and just logged it + c.exitCode = exitcodes.ErrorWasLogged + return } } -func (e *Executor) setupExitCode(ctx context.Context) { - if ctx.Err() != nil { - e.exitCode = exitcodes.Timeout - e.log.Errorf("Timeout exceeded: try increasing it by passing --timeout option") - return +func (c *runCommand) acquireFileLock() bool { + if c.cfg.Run.AllowParallelRunners { + c.debugf("Parallel runners are allowed, no locking") + return true } - if e.exitCode != exitcodes.Success { - return + lockFile := filepath.Join(os.TempDir(), "golangci-lint.lock") + c.debugf("Locking on file %s...", lockFile) + f := flock.New(lockFile) + const retryDelay = time.Second + + ctx := context.Background() + if !c.cfg.Run.AllowSerialRunners { + const totalTimeout = 5 * time.Second + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, totalTimeout) + defer cancel() + } + if ok, _ := f.TryLockContext(ctx, retryDelay); !ok { + return false } - needFailOnWarnings := os.Getenv(lintersdb.EnvTestRun) == "1" || os.Getenv(envFailOnWarnings) == "1" - if needFailOnWarnings && len(e.reportData.Warnings) != 0 { - e.exitCode = exitcodes.WarningInTest + c.flock = f + return true +} + +func (c *runCommand) releaseFileLock() { + if c.cfg.Run.AllowParallelRunners { return } - if e.reportData.Error != "" { - // it's a case e.g. when typecheck linter couldn't parse and error and just logged it - e.exitCode = exitcodes.ErrorWasLogged - return + if err := c.flock.Unlock(); err != nil { + c.debugf("Failed to unlock on file: %s", err) + } + if err := os.Remove(c.flock.Path()); err != nil { + c.debugf("Failed to remove lock file: %s", err) } } @@ -608,27 +570,119 @@ func watchResources(ctx context.Context, done chan struct{}, logger logutils.Log 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)), - "", - ) +func setupConfigFileFlagSet(fs *pflag.FlagSet, cfg *config.LoaderOptions) { + fs.StringVarP(&cfg.Config, "config", "c", "", color.GreenString("Read config from file path `PATH`")) + fs.BoolVar(&cfg.NoConfig, "no-config", false, color.GreenString("Don't read config file")) +} + +func setupRunPersistentFlags(fs *pflag.FlagSet, opts *runOptions) { + fs.BoolVar(&opts.PrintResourcesUsage, "print-resources-usage", false, + color.GreenString("Print avg and max memory usage of golangci-lint and total time")) + + fs.StringVar(&opts.CPUProfilePath, "cpu-profile-path", "", color.GreenString("Path to CPU profile output file")) + fs.StringVar(&opts.MemProfilePath, "mem-profile-path", "", color.GreenString("Path to memory profile output file")) + fs.StringVar(&opts.TracePath, "trace-path", "", color.GreenString("Path to trace output file")) +} + +func getDefaultConcurrency() int { + if os.Getenv(envHelpRun) == "1" { + // Make stable concurrency for generating help documentation. + const prettyConcurrency = 8 + return prettyConcurrency } - return strings.Join(parts, "\n") + + return runtime.NumCPU() +} + +func printMemStats(ms *runtime.MemStats, logger logutils.Log) { + logger.Infof("Mem stats: alloc=%s total_alloc=%s sys=%s "+ + "heap_alloc=%s heap_sys=%s heap_idle=%s heap_released=%s heap_in_use=%s "+ + "stack_in_use=%s stack_sys=%s "+ + "mspan_sys=%s mcache_sys=%s buck_hash_sys=%s gc_sys=%s other_sys=%s "+ + "mallocs_n=%d frees_n=%d heap_objects_n=%d gc_cpu_fraction=%.2f", + formatMemory(ms.Alloc), formatMemory(ms.TotalAlloc), formatMemory(ms.Sys), + formatMemory(ms.HeapAlloc), formatMemory(ms.HeapSys), + formatMemory(ms.HeapIdle), formatMemory(ms.HeapReleased), formatMemory(ms.HeapInuse), + formatMemory(ms.StackInuse), formatMemory(ms.StackSys), + formatMemory(ms.MSpanSys), formatMemory(ms.MCacheSys), formatMemory(ms.BuckHashSys), + formatMemory(ms.GCSys), formatMemory(ms.OtherSys), + ms.Mallocs, ms.Frees, ms.HeapObjects, ms.GCCPUFraction) } -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))) +func formatMemory(memBytes uint64) string { + const Kb = 1024 + const Mb = Kb * 1024 + + if memBytes < Kb { + return fmt.Sprintf("%db", memBytes) } - parts = append(parts, "") - return strings.Join(parts, "\n") + if memBytes < Mb { + return fmt.Sprintf("%dkb", memBytes/Kb) + } + return fmt.Sprintf("%dmb", memBytes/Mb) } -func wh(text string) string { - return color.GreenString(text) +// Related to cache. + +func initHashSalt(version string, cfg *config.Config) error { + binSalt, err := computeBinarySalt(version) + if err != nil { + return fmt.Errorf("failed to calculate binary salt: %w", err) + } + + configSalt, err := computeConfigSalt(cfg) + if err != nil { + return fmt.Errorf("failed to calculate config salt: %w", err) + } + + b := bytes.NewBuffer(binSalt) + b.Write(configSalt) + cache.SetSalt(b.Bytes()) + return nil +} + +func computeBinarySalt(version string) ([]byte, error) { + if version != "" && version != "(devel)" { + return []byte(version), nil + } + + if logutils.HaveDebugTag(logutils.DebugKeyBinSalt) { + return []byte("debug"), nil + } + + p, err := os.Executable() + if err != nil { + return nil, err + } + f, err := os.Open(p) + if err != nil { + return nil, err + } + defer f.Close() + h := sha256.New() + if _, err := io.Copy(h, f); err != nil { + return nil, err + } + return h.Sum(nil), nil +} + +// computeConfigSalt computes configuration hash. +// We don't hash all config fields to reduce meaningless cache invalidations. +// At least, it has a huge impact on tests speed. +// Fields: `LintersSettings` and `Run.BuildTags`. +func computeConfigSalt(cfg *config.Config) ([]byte, error) { + lintersSettingsBytes, err := yaml.Marshal(cfg.LintersSettings) + if err != nil { + return nil, fmt.Errorf("failed to json marshal config linter settings: %w", err) + } + + configData := bytes.NewBufferString("linters-settings=") + configData.Write(lintersSettingsBytes) + configData.WriteString("\nbuild-tags=%s" + strings.Join(cfg.Run.BuildTags, ",")) + + h := sha256.New() + if _, err := h.Write(configData.Bytes()); err != nil { + return nil, err + } + return h.Sum(nil), nil } 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 10ab7c1bb1..a03e46e221 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/commands/version.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/version.go @@ -8,76 +8,91 @@ import ( "runtime/debug" "strings" + "github.com/fatih/color" "github.com/spf13/cobra" - "github.com/spf13/pflag" - - "github.com/golangci/golangci-lint/pkg/config" ) +type BuildInfo struct { + GoVersion string `json:"goVersion"` + Version string `json:"version"` + Commit string `json:"commit"` + Date string `json:"date"` +} + type versionInfo struct { Info BuildInfo BuildInfo *debug.BuildInfo } -func (e *Executor) initVersionConfiguration(cmd *cobra.Command) { - fs := cmd.Flags() - fs.SortFlags = false // sort them as they are defined here - initVersionFlagSet(fs, e.cfg) +type versionOptions struct { + Format string + Debug bool } -func initVersionFlagSet(fs *pflag.FlagSet, cfg *config.Config) { - // Version config - vc := &cfg.Version - fs.StringVar(&vc.Format, "format", "", wh("The version's format can be: 'short', 'json'")) - fs.BoolVar(&vc.Debug, "debug", false, wh("Add build information")) +type versionCommand struct { + cmd *cobra.Command + opts versionOptions + + info BuildInfo } -func (e *Executor) initVersion() { +func newVersionCommand(info BuildInfo) *versionCommand { + c := &versionCommand{info: info} + versionCmd := &cobra.Command{ Use: "version", Short: "Version", Args: cobra.NoArgs, ValidArgsFunction: cobra.NoFileCompletions, - RunE: func(_ *cobra.Command, _ []string) error { - if e.cfg.Version.Debug { - info, ok := debug.ReadBuildInfo() - if !ok { - return nil - } - - switch strings.ToLower(e.cfg.Version.Format) { - case "json": - return json.NewEncoder(os.Stdout).Encode(versionInfo{ - Info: e.buildInfo, - BuildInfo: info, - }) - - default: - fmt.Println(info.String()) - return printVersion(os.Stdout, e.buildInfo) - } - } - - switch strings.ToLower(e.cfg.Version.Format) { - case "short": - fmt.Println(e.buildInfo.Version) - return nil - - case "json": - return json.NewEncoder(os.Stdout).Encode(e.buildInfo) - - default: - return printVersion(os.Stdout, e.buildInfo) - } - }, + RunE: c.execute, } - e.rootCmd.AddCommand(versionCmd) - e.initVersionConfiguration(versionCmd) + fs := versionCmd.Flags() + fs.SortFlags = false // sort them as they are defined here + + fs.StringVar(&c.opts.Format, "format", "", color.GreenString("The version's format can be: 'short', 'json'")) + fs.BoolVar(&c.opts.Debug, "debug", false, color.GreenString("Add build information")) + + c.cmd = versionCmd + + return c +} + +func (c *versionCommand) execute(_ *cobra.Command, _ []string) error { + if c.opts.Debug { + info, ok := debug.ReadBuildInfo() + if !ok { + return nil + } + + switch strings.ToLower(c.opts.Format) { + case "json": + return json.NewEncoder(os.Stdout).Encode(versionInfo{ + Info: c.info, + BuildInfo: info, + }) + + default: + fmt.Println(info.String()) + return printVersion(os.Stdout, c.info) + } + } + + switch strings.ToLower(c.opts.Format) { + case "short": + fmt.Println(c.info.Version) + return nil + + case "json": + return json.NewEncoder(os.Stdout).Encode(c.info) + + default: + return printVersion(os.Stdout, c.info) + } } -func printVersion(w io.Writer, buildInfo BuildInfo) error { +func printVersion(w io.Writer, info BuildInfo) error { _, err := fmt.Fprintf(w, "golangci-lint has version %s built with %s from %s on %s\n", - buildInfo.Version, buildInfo.GoVersion, buildInfo.Commit, buildInfo.Date) + info.Version, info.GoVersion, info.Commit, info.Date) return err } 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 a5483a20e3..af27a91b55 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/config/config.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/config/config.go @@ -2,26 +2,27 @@ package config import ( "os" + "regexp" "strings" hcversion "github.com/hashicorp/go-version" "github.com/ldez/gomoddirectives" ) -// Config encapsulates the config data specified in the golangci yaml config file. +// Config encapsulates the config data specified in the golangci-lint yaml config file. type Config struct { - cfgDir string // The directory containing the golangci config file. - Run Run + cfgDir string // The directory containing the golangci-lint config file. - Output Output + Run Run `mapstructure:"run"` + + Output Output `mapstructure:"output"` LintersSettings LintersSettings `mapstructure:"linters-settings"` - Linters Linters - Issues Issues - Severity Severity - Version Version + Linters Linters `mapstructure:"linters"` + Issues Issues `mapstructure:"issues"` + Severity Severity `mapstructure:"severity"` - InternalCmdTest bool `mapstructure:"internal-cmd-test"` // Option is used only for testing golangci-lint command, don't use it + InternalCmdTest bool // Option is used only for testing golangci-lint command, don't use it InternalTest bool // Option is used only for testing golangci-lint code, don't use it } @@ -30,6 +31,25 @@ func (c *Config) GetConfigDir() string { return c.cfgDir } +func (c *Config) Validate() error { + validators := []func() error{ + c.Issues.Validate, + c.Severity.Validate, + c.LintersSettings.Validate, + c.Linters.Validate, + c.Output.Validate, + c.Run.Validate, + } + + for _, v := range validators { + if err := v(); err != nil { + return err + } + } + + return nil +} + func NewDefault() *Config { return &Config{ LintersSettings: defaultLintersSettings, @@ -41,21 +61,21 @@ type Version struct { Debug bool `mapstructure:"debug"` } -func IsGreaterThanOrEqualGo122(v string) bool { - v1, err := hcversion.NewVersion(strings.TrimPrefix(v, "go")) +func IsGoGreaterThanOrEqual(current, limit string) bool { + v1, err := hcversion.NewVersion(strings.TrimPrefix(current, "go")) if err != nil { return false } - limit, err := hcversion.NewVersion("1.22") + l, err := hcversion.NewVersion(limit) if err != nil { return false } - return v1.GreaterThanOrEqual(limit) + return v1.GreaterThanOrEqual(l) } -func DetectGoVersion() string { +func detectGoVersion() string { file, _ := gomoddirectives.GetModuleFile() if file != nil && file.Go != nil && file.Go.Version != "" { @@ -69,3 +89,22 @@ func DetectGoVersion() string { return "1.17" } + +// Trims the Go version to keep only M.m. +// Since Go 1.21 the version inside the go.mod can be a patched version (ex: 1.21.0). +// The version can also include information which we want to remove (ex: 1.21alpha1) +// https://go.dev/doc/toolchain#versions +// This a problem with staticcheck and gocritic. +func trimGoVersion(v string) string { + if v == "" { + return "" + } + + exp := regexp.MustCompile(`(\d\.\d+)(?:\.\d+|[a-z]+\d)`) + + if exp.MatchString(v) { + return exp.FindStringSubmatch(v)[1] + } + + return v +} 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 27dcf81056..45424b1793 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/config/issues.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/config/issues.go @@ -1,6 +1,7 @@ package config import ( + "errors" "fmt" "regexp" ) @@ -108,8 +109,13 @@ type Issues struct { ExcludeCaseSensitive bool `mapstructure:"exclude-case-sensitive"` ExcludePatterns []string `mapstructure:"exclude"` ExcludeRules []ExcludeRule `mapstructure:"exclude-rules"` + ExcludeGeneratedStrict bool `mapstructure:"exclude-generated-strict"` UseDefaultExcludes bool `mapstructure:"exclude-use-default"` + ExcludeFiles []string `mapstructure:"exclude-files"` + ExcludeDirs []string `mapstructure:"exclude-dirs"` + UseDefaultExcludeDirs bool `mapstructure:"exclude-dirs-use-default"` + MaxIssuesPerLinter int `mapstructure:"max-issues-per-linter"` MaxSameIssues int `mapstructure:"max-same-issues"` @@ -121,6 +127,16 @@ type Issues struct { NeedFix bool `mapstructure:"fix"` } +func (i *Issues) Validate() error { + for i, rule := range i.ExcludeRules { + if err := rule.Validate(); err != nil { + return fmt.Errorf("error in exclude rule #%d: %w", i, err) + } + } + + return nil +} + type ExcludeRule struct { BaseRule `mapstructure:",squash"` } @@ -141,34 +157,47 @@ func (b *BaseRule) Validate(minConditionsCount int) error { if err := validateOptionalRegex(b.Path); err != nil { return fmt.Errorf("invalid path regex: %w", err) } + if err := validateOptionalRegex(b.PathExcept); err != nil { return fmt.Errorf("invalid path-except regex: %w", err) } + if err := validateOptionalRegex(b.Text); err != nil { return fmt.Errorf("invalid text regex: %w", err) } + if err := validateOptionalRegex(b.Source); err != nil { return fmt.Errorf("invalid source regex: %w", err) } + + if b.Path != "" && b.PathExcept != "" { + return errors.New("path and path-except should not be set at the same time") + } + nonBlank := 0 if len(b.Linters) > 0 { nonBlank++ } + // Filtering by path counts as one condition, regardless how it is done (one or both). // Otherwise, a rule with Path and PathExcept set would pass validation // whereas before the introduction of path-except that wouldn't have been precise enough. if b.Path != "" || b.PathExcept != "" { nonBlank++ } + if b.Text != "" { nonBlank++ } + if b.Source != "" { nonBlank++ } + if nonBlank < minConditionsCount { return fmt.Errorf("at least %d of (text, source, path[-except], linters) should be set", minConditionsCount) } + return nil } @@ -176,6 +205,7 @@ func validateOptionalRegex(value string) error { if value == "" { return nil } + _, err := regexp.Compile(value) return err } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/config/linters.go b/vendor/github.com/golangci/golangci-lint/pkg/config/linters.go index ccbdc123a1..5c2628272c 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/config/linters.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/config/linters.go @@ -1,5 +1,10 @@ package config +import ( + "errors" + "fmt" +) + type Linters struct { Enable []string Disable []string @@ -9,3 +14,52 @@ type Linters struct { Presets []string } + +func (l *Linters) Validate() error { + if err := l.validateAllDisableEnableOptions(); err != nil { + return err + } + + if err := l.validateDisabledAndEnabledAtOneMoment(); err != nil { + return err + } + + return nil +} + +func (l *Linters) validateAllDisableEnableOptions() error { + if l.EnableAll && l.DisableAll { + return errors.New("--enable-all and --disable-all options must not be combined") + } + + if l.DisableAll { + if len(l.Enable) == 0 && len(l.Presets) == 0 { + return errors.New("all linters were disabled, but no one linter was enabled: must enable at least one") + } + + if len(l.Disable) != 0 { + return errors.New("can't combine options --disable-all and --disable") + } + } + + if l.EnableAll && len(l.Enable) != 0 && !l.Fast { + return errors.New("can't combine options --enable-all and --enable") + } + + return nil +} + +func (l *Linters) validateDisabledAndEnabledAtOneMoment() error { + enabledLintersSet := map[string]bool{} + for _, name := range l.Enable { + enabledLintersSet[name] = true + } + + for _, name := range l.Disable { + if enabledLintersSet[name] { + return fmt.Errorf("linter %q can't be disabled and enabled at one moment", name) + } + } + + return nil +} 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 a3206f5978..6097be06b1 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 @@ -3,6 +3,7 @@ package config import ( "encoding" "errors" + "fmt" "runtime" "gopkg.in/yaml.v3" @@ -21,6 +22,12 @@ var defaultLintersSettings = LintersSettings{ Dogsled: DogsledSettings{ MaxBlankIdentifiers: 2, }, + Dupl: DuplSettings{ + Threshold: 150, + }, + Errcheck: ErrcheckSettings{ + Ignore: "fmt:.*", + }, ErrorLint: ErrorLintSettings{ Errorf: true, ErrorfMulti: true, @@ -46,9 +53,20 @@ var defaultLintersSettings = LintersSettings{ Gocognit: GocognitSettings{ MinComplexity: 30, }, + Goconst: GoConstSettings{ + MatchWithConstants: true, + MinStringLen: 3, + MinOccurrencesCount: 3, + NumberMin: 3, + NumberMax: 3, + IgnoreCalls: true, + }, Gocritic: GoCriticSettings{ SettingsPerCheck: map[string]GoCriticCheckSettings{}, }, + Gocyclo: GoCycloSettings{ + MinComplexity: 30, + }, Godox: GodoxSettings{ Keywords: []string{}, }, @@ -56,6 +74,9 @@ var defaultLintersSettings = LintersSettings{ Scope: "declarations", Period: true, }, + Gofmt: GoFmtSettings{ + Simplify: true, + }, Gofumpt: GofumptSettings{ LangVersion: "", ModulePath: "", @@ -70,10 +91,6 @@ var defaultLintersSettings = LintersSettings{ IgnoreTests: true, WatchForScripts: []string{"Han"}, }, - Ifshort: IfshortSettings{ - MaxDeclLines: 1, - MaxDeclChars: 30, - }, Inamedparam: INamedParamSettings{ SkipSingleParam: false, }, @@ -112,6 +129,7 @@ var defaultLintersSettings = LintersSettings{ ErrError: false, ErrorF: true, SprintF1: true, + StrConcat: true, }, Prealloc: PreallocSettings{ Simple: true, @@ -180,93 +198,103 @@ var defaultLintersSettings = LintersSettings{ } type LintersSettings struct { - Asasalint AsasalintSettings - BiDiChk BiDiChkSettings - Cyclop Cyclop - Decorder DecorderSettings - Depguard DepGuardSettings - Dogsled DogsledSettings - Dupl DuplSettings - DupWord DupWordSettings - Errcheck ErrcheckSettings - ErrChkJSON ErrChkJSONSettings - ErrorLint ErrorLintSettings - Exhaustive ExhaustiveSettings - ExhaustiveStruct ExhaustiveStructSettings - Exhaustruct ExhaustructSettings - Forbidigo ForbidigoSettings - Funlen FunlenSettings - Gci GciSettings - GinkgoLinter GinkgoLinterSettings - Gocognit GocognitSettings - Goconst GoConstSettings - Gocritic GoCriticSettings - Gocyclo GoCycloSettings - Godot GodotSettings - Godox GodoxSettings - Gofmt GoFmtSettings - Gofumpt GofumptSettings - Goheader GoHeaderSettings - Goimports GoImportsSettings - Golint GoLintSettings - Gomnd GoMndSettings - GoModDirectives GoModDirectivesSettings - Gomodguard GoModGuardSettings - Gosec GoSecSettings - Gosimple StaticCheckSettings - Gosmopolitan GosmopolitanSettings - Govet GovetSettings - Grouper GrouperSettings - Ifshort IfshortSettings - ImportAs ImportAsSettings - Inamedparam INamedParamSettings - InterfaceBloat InterfaceBloatSettings - Ireturn IreturnSettings - Lll LllSettings - LoggerCheck LoggerCheckSettings - MaintIdx MaintIdxSettings - Makezero MakezeroSettings - Maligned MalignedSettings - Misspell MisspellSettings - MustTag MustTagSettings - Nakedret NakedretSettings - Nestif NestifSettings - NilNil NilNilSettings - Nlreturn NlreturnSettings - 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 - Tenv TenvSettings - Testifylint TestifylintSettings - Testpackage TestpackageSettings - Thelper ThelperSettings - Unparam UnparamSettings - Unused UnusedSettings - UseStdlibVars UseStdlibVarsSettings - Varcheck VarCheckSettings - Varnamelen VarnamelenSettings - Whitespace WhitespaceSettings - Wrapcheck WrapcheckSettings - WSL WSLSettings + Asasalint AsasalintSettings + BiDiChk BiDiChkSettings + CopyLoopVar CopyLoopVarSettings + Cyclop Cyclop + Decorder DecorderSettings + Depguard DepGuardSettings + Dogsled DogsledSettings + Dupl DuplSettings + DupWord DupWordSettings + Errcheck ErrcheckSettings + ErrChkJSON ErrChkJSONSettings + ErrorLint ErrorLintSettings + Exhaustive ExhaustiveSettings + Exhaustruct ExhaustructSettings + Forbidigo ForbidigoSettings + Funlen FunlenSettings + Gci GciSettings + GinkgoLinter GinkgoLinterSettings + Gocognit GocognitSettings + Goconst GoConstSettings + Gocritic GoCriticSettings + Gocyclo GoCycloSettings + Godot GodotSettings + Godox GodoxSettings + Gofmt GoFmtSettings + Gofumpt GofumptSettings + Goheader GoHeaderSettings + Goimports GoImportsSettings + Gomnd GoMndSettings + GoModDirectives GoModDirectivesSettings + Gomodguard GoModGuardSettings + Gosec GoSecSettings + Gosimple StaticCheckSettings + Gosmopolitan GosmopolitanSettings + Govet GovetSettings + Grouper GrouperSettings + ImportAs ImportAsSettings + Inamedparam INamedParamSettings + InterfaceBloat InterfaceBloatSettings + Ireturn IreturnSettings + Lll LllSettings + LoggerCheck LoggerCheckSettings + MaintIdx MaintIdxSettings + Makezero MakezeroSettings + Misspell MisspellSettings + MustTag MustTagSettings + Nakedret NakedretSettings + Nestif NestifSettings + NilNil NilNilSettings + Nlreturn NlreturnSettings + 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 + Stylecheck StaticCheckSettings + TagAlign TagAlignSettings + Tagliatelle TagliatelleSettings + Tenv TenvSettings + Testifylint TestifylintSettings + Testpackage TestpackageSettings + Thelper ThelperSettings + Unconvert UnconvertSettings + Unparam UnparamSettings + Unused UnusedSettings + UseStdlibVars UseStdlibVarsSettings + Varnamelen VarnamelenSettings + Whitespace WhitespaceSettings + Wrapcheck WrapcheckSettings + WSL WSLSettings Custom map[string]CustomLinterSettings } +func (s *LintersSettings) Validate() error { + if err := s.Govet.Validate(); err != nil { + return err + } + + for name, settings := range s.Custom { + if err := settings.Validate(); err != nil { + return fmt.Errorf("custom linter %q: %w", name, err) + } + } + + return nil +} + type AsasalintSettings struct { Exclude []string `mapstructure:"exclude"` UseBuiltinExclusions bool `mapstructure:"use-builtin-exclusions"` @@ -285,6 +313,10 @@ type BiDiChkSettings struct { PopDirectionalIsolate bool `mapstructure:"pop-directional-isolate"` } +type CopyLoopVarSettings struct { + IgnoreAlias bool `mapstructure:"ignore-alias"` +} + type Cyclop struct { MaxComplexity int `mapstructure:"max-complexity"` PackageAverage float64 `mapstructure:"package-average"` @@ -366,10 +398,6 @@ type ExhaustiveSettings struct { DefaultCaseRequired bool `mapstructure:"default-case-required"` } -type ExhaustiveStructSettings struct { - StructPatterns []string `mapstructure:"struct-patterns"` -} - type ExhaustructSettings struct { Include []string `mapstructure:"include"` Exclude []string `mapstructure:"exclude"` @@ -439,6 +467,9 @@ type GinkgoLinterSettings struct { SuppressTypeCompareWarning bool `mapstructure:"suppress-type-compare-assertion"` ForbidFocusContainer bool `mapstructure:"forbid-focus-container"` AllowHaveLenZero bool `mapstructure:"allow-havelen-zero"` + ForceExpectTo bool `mapstructure:"force-expect-to"` + ValidateAsyncIntervals bool `mapstructure:"validate-async-intervals"` + ForbidSpecPollution bool `mapstructure:"forbid-spec-pollution"` } type GocognitSettings struct { @@ -459,7 +490,9 @@ type GoConstSettings struct { type GoCriticSettings struct { Go string `mapstructure:"-"` + DisableAll bool `mapstructure:"disable-all"` EnabledChecks []string `mapstructure:"enabled-checks"` + EnableAll bool `mapstructure:"enable-all"` DisabledChecks []string `mapstructure:"disabled-checks"` EnabledTags []string `mapstructure:"enabled-tags"` DisabledTags []string `mapstructure:"disabled-tags"` @@ -514,10 +547,6 @@ type GoImportsSettings struct { LocalPrefixes string `mapstructure:"local-prefixes"` } -type GoLintSettings struct { - MinConfidence float64 `mapstructure:"min-confidence"` -} - type GoMndSettings struct { Settings map[string]map[string]any // Deprecated Checks []string `mapstructure:"checks"` @@ -569,25 +598,28 @@ type GosmopolitanSettings struct { } type GovetSettings struct { - Go string `mapstructure:"-"` - CheckShadowing bool `mapstructure:"check-shadowing"` - Settings map[string]map[string]any + Go string `mapstructure:"-"` Enable []string Disable []string EnableAll bool `mapstructure:"enable-all"` DisableAll bool `mapstructure:"disable-all"` + + Settings map[string]map[string]any + + // Deprecated: the linter should be enabled inside `Enable`. + CheckShadowing bool `mapstructure:"check-shadowing"` } func (cfg *GovetSettings) Validate() error { if cfg.EnableAll && cfg.DisableAll { - return errors.New("enable-all and disable-all can't be combined") + return errors.New("govet: enable-all and disable-all can't be combined") } if cfg.EnableAll && len(cfg.Enable) != 0 { - return errors.New("enable-all and enable can't be combined") + return errors.New("govet: enable-all and enable can't be combined") } if cfg.DisableAll && len(cfg.Disable) != 0 { - return errors.New("disable-all and disable can't be combined") + return errors.New("govet: disable-all and disable can't be combined") } return nil } @@ -603,11 +635,6 @@ type GrouperSettings struct { VarRequireGrouping bool `mapstructure:"var-require-grouping"` } -type IfshortSettings struct { - MaxDeclLines int `mapstructure:"max-decl-lines"` - MaxDeclChars int `mapstructure:"max-decl-chars"` -} - type ImportAsSettings struct { Alias []ImportAsAlias NoUnaliased bool `mapstructure:"no-unaliased"` @@ -655,17 +682,19 @@ type MakezeroSettings struct { Always bool } -type MalignedSettings struct { - SuggestNewOrder bool `mapstructure:"suggest-new"` -} - type MisspellSettings struct { - Mode string `mapstructure:"mode"` - Locale string + Mode string `mapstructure:"mode"` + Locale string `mapstructure:"locale"` + ExtraWords []MisspellExtraWords `mapstructure:"extra-words"` // TODO(ldez): v2 the option must be renamed to `IgnoredRules`. IgnoreWords []string `mapstructure:"ignore-words"` } +type MisspellExtraWords struct { + Typo string `mapstructure:"typo"` + Correction string `mapstructure:"correction"` +} + type MustTagSettings struct { Functions []struct { Name string `mapstructure:"name"` @@ -702,8 +731,9 @@ type NoNamedReturnsSettings struct { } type ParallelTestSettings struct { - IgnoreMissing bool `mapstructure:"ignore-missing"` - IgnoreMissingSubtests bool `mapstructure:"ignore-missing-subtests"` + Go string `mapstructure:"-"` + IgnoreMissing bool `mapstructure:"ignore-missing"` + IgnoreMissingSubtests bool `mapstructure:"ignore-missing-subtests"` } type PerfSprintSettings struct { @@ -711,6 +741,7 @@ type PerfSprintSettings struct { ErrError bool `mapstructure:"err-error"` ErrorF bool `mapstructure:"errorf"` SprintF1 bool `mapstructure:"sprintf1"` + StrConcat bool `mapstructure:"strconcat"` } type PreallocSettings struct { @@ -751,6 +782,7 @@ type ReviveSettings struct { Arguments []any Severity string Disabled bool + Exclude []string } ErrorCode int `mapstructure:"error-code"` WarningCode int `mapstructure:"warning-code"` @@ -767,6 +799,7 @@ type RowsErrCheckSettings struct { type SlogLintSettings struct { NoMixedArgs bool `mapstructure:"no-mixed-args"` KVOnly bool `mapstructure:"kv-only"` + NoGlobal string `mapstructure:"no-global"` AttrOnly bool `mapstructure:"attr-only"` ContextOnly bool `mapstructure:"context-only"` StaticMsg bool `mapstructure:"static-msg"` @@ -794,10 +827,6 @@ func (s *StaticCheckSettings) HasConfiguration() bool { return len(s.Initialisms) > 0 || len(s.HTTPStatusCodeWhitelist) > 0 || len(s.DotImportWhitelist) > 0 || len(s.Checks) > 0 } -type StructCheckSettings struct { - CheckExportedFields bool `mapstructure:"exported-fields"` -} - type TagAlignSettings struct { Align bool `mapstructure:"align"` Sort bool `mapstructure:"sort"` @@ -818,6 +847,10 @@ type TestifylintSettings struct { EnabledCheckers []string `mapstructure:"enable"` DisabledCheckers []string `mapstructure:"disable"` + BoolCompare struct { + IgnoreCustomTypes bool `mapstructure:"ignore-custom-types"` + } `mapstructure:"bool-compare"` + ExpectedActual struct { ExpVarPattern string `mapstructure:"pattern"` } `mapstructure:"expected-actual"` @@ -868,6 +901,11 @@ type UseStdlibVarsSettings struct { SyslogPriority bool `mapstructure:"syslog-priority"` } +type UnconvertSettings struct { + FastMath bool `mapstructure:"fast-math"` + Safe bool `mapstructure:"safe"` +} + type UnparamSettings struct { CheckExported bool `mapstructure:"check-exported"` Algo string @@ -883,10 +921,6 @@ type UnusedSettings struct { GeneratedIsUsed bool `mapstructure:"generated-is-used"` } -type VarCheckSettings struct { - CheckExportedFields bool `mapstructure:"exported-fields"` -} - type VarnamelenSettings struct { MaxDistance int `mapstructure:"max-distance"` MinNameLength int `mapstructure:"min-name-length"` @@ -930,17 +964,15 @@ type WSLSettings struct { } // CustomLinterSettings encapsulates the meta-data of a private linter. -// For example, a private linter may be added to the golangci config file as shown below. -// -// linters-settings: -// custom: -// example: -// path: /example.so -// description: The description of the linter -// original-url: github.com/golangci/example-linter type CustomLinterSettings struct { + // Type plugin type. + // It can be `goplugin` or `module`. + Type string `mapstructure:"type"` + // Path to a plugin *.so file that implements the private linter. + // Only for Go plugin system. Path string + // Description describes the purpose of the private linter. Description string // OriginalURL The URL containing the source code for the private linter. @@ -949,3 +981,19 @@ type CustomLinterSettings struct { // Settings plugin settings only work with linterdb.PluginConstructor symbol. Settings any } + +func (s *CustomLinterSettings) Validate() error { + if s.Type == "module" { + if s.Path != "" { + return errors.New("path not supported with module type") + } + + return nil + } + + if s.Path == "" { + return errors.New("path is required") + } + + return nil +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/config/loader.go b/vendor/github.com/golangci/golangci-lint/pkg/config/loader.go new file mode 100644 index 0000000000..782daa4c92 --- /dev/null +++ b/vendor/github.com/golangci/golangci-lint/pkg/config/loader.go @@ -0,0 +1,398 @@ +package config + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "slices" + "strings" + + "github.com/go-viper/mapstructure/v2" + "github.com/mitchellh/go-homedir" + "github.com/spf13/pflag" + "github.com/spf13/viper" + + "github.com/golangci/golangci-lint/pkg/exitcodes" + "github.com/golangci/golangci-lint/pkg/fsutils" + "github.com/golangci/golangci-lint/pkg/logutils" +) + +var errConfigDisabled = errors.New("config is disabled by --no-config") + +type LoaderOptions struct { + Config string // Flag only. The path to the golangci config file, as specified with the --config argument. + NoConfig bool // Flag only. +} + +type Loader struct { + opts LoaderOptions + + viper *viper.Viper + fs *pflag.FlagSet + + log logutils.Log + + cfg *Config +} + +func NewLoader(log logutils.Log, v *viper.Viper, fs *pflag.FlagSet, opts LoaderOptions, cfg *Config) *Loader { + return &Loader{ + opts: opts, + viper: v, + fs: fs, + log: log, + cfg: cfg, + } +} + +func (l *Loader) Load() error { + err := l.setConfigFile() + if err != nil { + return err + } + + err = l.parseConfig() + if err != nil { + return err + } + + l.applyStringSliceHack() + + l.handleGoVersion() + + err = l.handleDeprecation() + if err != nil { + return err + } + + err = l.handleEnableOnlyOption() + if err != nil { + return err + } + + return nil +} + +func (l *Loader) setConfigFile() error { + configFile, err := l.evaluateOptions() + if err != nil { + if errors.Is(err, errConfigDisabled) { + return nil + } + + return fmt.Errorf("can't parse --config option: %w", err) + } + + if configFile != "" { + l.viper.SetConfigFile(configFile) + + // Assume YAML if the file has no extension. + if filepath.Ext(configFile) == "" { + l.viper.SetConfigType("yaml") + } + } else { + l.setupConfigFileSearch() + } + + return nil +} + +func (l *Loader) evaluateOptions() (string, error) { + if l.opts.NoConfig && l.opts.Config != "" { + return "", errors.New("can't combine option --config and --no-config") + } + + if l.opts.NoConfig { + return "", errConfigDisabled + } + + configFile, err := homedir.Expand(l.opts.Config) + if err != nil { + return "", errors.New("failed to expand configuration path") + } + + return configFile, nil +} + +func (l *Loader) setupConfigFileSearch() { + firstArg := extractFirstPathArg() + + absStartPath, err := filepath.Abs(firstArg) + if err != nil { + l.log.Warnf("Can't make abs path for %q: %s", firstArg, err) + absStartPath = filepath.Clean(firstArg) + } + + // start from it + var curDir string + if fsutils.IsDir(absStartPath) { + curDir = absStartPath + } else { + curDir = filepath.Dir(absStartPath) + } + + // find all dirs from it up to the root + configSearchPaths := []string{"./"} + + for { + configSearchPaths = append(configSearchPaths, curDir) + + newCurDir := filepath.Dir(curDir) + if curDir == newCurDir || newCurDir == "" { + break + } + + curDir = newCurDir + } + + // find home directory for global config + if home, err := homedir.Dir(); err != nil { + l.log.Warnf("Can't get user's home directory: %s", err.Error()) + } else if !slices.Contains(configSearchPaths, home) { + configSearchPaths = append(configSearchPaths, home) + } + + l.log.Infof("Config search paths: %s", configSearchPaths) + + l.viper.SetConfigName(".golangci") + + for _, p := range configSearchPaths { + l.viper.AddConfigPath(p) + } +} + +func (l *Loader) parseConfig() error { + if err := l.viper.ReadInConfig(); err != nil { + var configFileNotFoundError viper.ConfigFileNotFoundError + if errors.As(err, &configFileNotFoundError) { + // Load configuration from flags only. + err = l.viper.Unmarshal(l.cfg, customDecoderHook()) + if err != nil { + return fmt.Errorf("can't unmarshal config by viper (flags): %w", err) + } + + return nil + } + + return fmt.Errorf("can't read viper config: %w", err) + } + + err := l.setConfigDir() + if err != nil { + return err + } + + // Load configuration from all sources (flags, file). + if err := l.viper.Unmarshal(l.cfg, customDecoderHook()); err != nil { + return fmt.Errorf("can't unmarshal config by viper (flags, file): %w", err) + } + + if l.cfg.InternalTest { // just for testing purposes: to detect config file usage + _, _ = fmt.Fprintln(logutils.StdOut, "test") + os.Exit(exitcodes.Success) + } + + return nil +} + +func (l *Loader) setConfigDir() error { + usedConfigFile := l.viper.ConfigFileUsed() + if usedConfigFile == "" { + return nil + } + + if usedConfigFile == os.Stdin.Name() { + usedConfigFile = "" + l.log.Infof("Reading config file stdin") + } else { + var err error + usedConfigFile, err = fsutils.ShortestRelPath(usedConfigFile, "") + if err != nil { + l.log.Warnf("Can't pretty print config file path: %v", err) + } + + l.log.Infof("Used config file %s", usedConfigFile) + } + + usedConfigDir, err := filepath.Abs(filepath.Dir(usedConfigFile)) + if err != nil { + return errors.New("can't get config directory") + } + + l.cfg.cfgDir = usedConfigDir + + return nil +} + +// Hack to append values from StringSlice flags. +// Viper always overrides StringSlice values. +// https://github.com/spf13/viper/issues/1448 +// So StringSlice flags are not bind to Viper like that their values are obtain via Cobra Flags. +func (l *Loader) applyStringSliceHack() { + if l.fs == nil { + return + } + + l.appendStringSlice("enable", &l.cfg.Linters.Enable) + l.appendStringSlice("disable", &l.cfg.Linters.Disable) + l.appendStringSlice("presets", &l.cfg.Linters.Presets) + l.appendStringSlice("build-tags", &l.cfg.Run.BuildTags) + l.appendStringSlice("exclude", &l.cfg.Issues.ExcludePatterns) + + l.appendStringSlice("skip-dirs", &l.cfg.Run.SkipDirs) + l.appendStringSlice("skip-files", &l.cfg.Run.SkipFiles) + l.appendStringSlice("exclude-dirs", &l.cfg.Issues.ExcludeDirs) + l.appendStringSlice("exclude-files", &l.cfg.Issues.ExcludeFiles) +} + +func (l *Loader) appendStringSlice(name string, current *[]string) { + if l.fs.Changed(name) { + val, _ := l.fs.GetStringSlice(name) + *current = append(*current, val...) + } +} + +func (l *Loader) handleGoVersion() { + if l.cfg.Run.Go == "" { + l.cfg.Run.Go = detectGoVersion() + } + + l.cfg.LintersSettings.Govet.Go = l.cfg.Run.Go + + l.cfg.LintersSettings.ParallelTest.Go = l.cfg.Run.Go + + if l.cfg.LintersSettings.Gofumpt.LangVersion == "" { + l.cfg.LintersSettings.Gofumpt.LangVersion = l.cfg.Run.Go + } + + trimmedGoVersion := trimGoVersion(l.cfg.Run.Go) + + l.cfg.LintersSettings.Gocritic.Go = trimmedGoVersion + + // staticcheck related linters. + if l.cfg.LintersSettings.Staticcheck.GoVersion == "" { + l.cfg.LintersSettings.Staticcheck.GoVersion = trimmedGoVersion + } + if l.cfg.LintersSettings.Gosimple.GoVersion == "" { + l.cfg.LintersSettings.Gosimple.GoVersion = trimmedGoVersion + } + if l.cfg.LintersSettings.Stylecheck.GoVersion != "" { + l.cfg.LintersSettings.Stylecheck.GoVersion = trimmedGoVersion + } +} + +func (l *Loader) handleDeprecation() error { + // Deprecated since v1.57.0 + if len(l.cfg.Run.SkipFiles) > 0 { + l.warn("The configuration option `run.skip-files` is deprecated, please use `issues.exclude-files`.") + l.cfg.Issues.ExcludeFiles = l.cfg.Run.SkipFiles + } + + // Deprecated since v1.57.0 + if len(l.cfg.Run.SkipDirs) > 0 { + l.warn("The configuration option `run.skip-dirs` is deprecated, please use `issues.exclude-dirs`.") + l.cfg.Issues.ExcludeDirs = l.cfg.Run.SkipDirs + } + + // The 2 options are true by default. + // Deprecated since v1.57.0 + if !l.cfg.Run.UseDefaultSkipDirs { + l.warn("The configuration option `run.skip-dirs-use-default` is deprecated, please use `issues.exclude-dirs-use-default`.") + } + l.cfg.Issues.UseDefaultExcludeDirs = l.cfg.Run.UseDefaultSkipDirs && l.cfg.Issues.UseDefaultExcludeDirs + + // The 2 options are false by default. + // Deprecated since v1.57.0 + if l.cfg.Run.ShowStats { + l.warn("The configuration option `run.show-stats` is deprecated, please use `output.show-stats`") + } + l.cfg.Output.ShowStats = l.cfg.Run.ShowStats || l.cfg.Output.ShowStats + + // Deprecated since v1.57.0 + if l.cfg.Output.Format != "" { + l.warn("The configuration option `output.format` is deprecated, please use `output.formats`") + + var f OutputFormats + err := f.UnmarshalText([]byte(l.cfg.Output.Format)) + if err != nil { + return fmt.Errorf("unmarshal output format: %w", err) + } + + l.cfg.Output.Formats = f + } + + // Deprecated since v1.57.0, + // but it was unofficially deprecated since v1.19 (2019) (https://github.com/golangci/golangci-lint/pull/697). + if l.cfg.LintersSettings.Govet.CheckShadowing { + l.warn("The configuration option `govet.check-shadowing` is deprecated. " + + "Please enable `shadow` instead, if you are not using `enable-all`.") + } + + return nil +} + +func (l *Loader) handleEnableOnlyOption() error { + lookup := l.fs.Lookup("enable-only") + if lookup == nil { + return nil + } + + only, err := l.fs.GetStringSlice("enable-only") + if err != nil { + return err + } + + if len(only) > 0 { + l.cfg.Linters = Linters{ + Enable: only, + DisableAll: true, + } + } + + return nil +} + +func (l *Loader) warn(format string) { + if l.cfg.InternalTest || l.cfg.InternalCmdTest || os.Getenv(logutils.EnvTestRun) == "1" { + return + } + + l.log.Warnf(format) +} + +func customDecoderHook() viper.DecoderConfigOption { + return viper.DecodeHook(mapstructure.ComposeDecodeHookFunc( + // Default hooks (https://github.com/spf13/viper/blob/518241257478c557633ab36e474dfcaeb9a3c623/viper.go#L135-L138). + mapstructure.StringToTimeDurationHookFunc(), + mapstructure.StringToSliceHookFunc(","), + + // Needed for forbidigo, and output.formats. + mapstructure.TextUnmarshallerHookFunc(), + )) +} + +func extractFirstPathArg() string { + args := os.Args + + // skip all args ([golangci-lint, run/linters]) before files/dirs list + for len(args) != 0 { + if args[0] == "run" { + args = args[1:] + break + } + + args = args[1:] + } + + // find first file/dir arg + firstArg := "./..." + for _, arg := range args { + if !strings.HasPrefix(arg, "-") { + firstArg = arg + break + } + } + + return firstArg +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/config/output.go b/vendor/github.com/golangci/golangci-lint/pkg/config/output.go index e872639205..672b1c7d42 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/config/output.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/config/output.go @@ -1,5 +1,12 @@ package config +import ( + "errors" + "fmt" + "slices" + "strings" +) + const ( OutFormatJSON = "json" OutFormatLineNumber = "line-number" @@ -14,7 +21,7 @@ const ( OutFormatTeamCity = "teamcity" ) -var OutFormats = []string{ +var AllOutputFormats = []string{ OutFormatColoredLineNumber, OutFormatLineNumber, OutFormatJSON, @@ -28,14 +35,78 @@ var OutFormats = []string{ } type Output struct { - Format string - PrintIssuedLine bool `mapstructure:"print-issued-lines"` - PrintLinterName bool `mapstructure:"print-linter-name"` - UniqByLine bool `mapstructure:"uniq-by-line"` - SortResults bool `mapstructure:"sort-results"` - PrintWelcomeMessage bool `mapstructure:"print-welcome"` - PathPrefix string `mapstructure:"path-prefix"` - - // only work with CLI flags because the setup of logs is done before the config file parsing. - Color string + Formats OutputFormats `mapstructure:"formats"` + PrintIssuedLine bool `mapstructure:"print-issued-lines"` + PrintLinterName bool `mapstructure:"print-linter-name"` + UniqByLine bool `mapstructure:"uniq-by-line"` + SortResults bool `mapstructure:"sort-results"` + SortOrder []string `mapstructure:"sort-order"` + PathPrefix string `mapstructure:"path-prefix"` + ShowStats bool `mapstructure:"show-stats"` + + // Deprecated: use Formats instead. + Format string `mapstructure:"format"` +} + +func (o *Output) Validate() error { + if !o.SortResults && len(o.SortOrder) > 0 { + return errors.New("sort-results should be 'true' to use sort-order") + } + + validOrders := []string{"linter", "file", "severity"} + + all := strings.Join(o.SortOrder, " ") + + for _, order := range o.SortOrder { + if strings.Count(all, order) > 1 { + return fmt.Errorf("the sort-order name %q is repeated several times", order) + } + + if !slices.Contains(validOrders, order) { + return fmt.Errorf("unsupported sort-order name %q", order) + } + } + + for _, format := range o.Formats { + err := format.Validate() + if err != nil { + return err + } + } + + return nil +} + +type OutputFormat struct { + Format string `mapstructure:"format"` + Path string `mapstructure:"path"` +} + +func (o *OutputFormat) Validate() error { + if o.Format == "" { + return errors.New("the format is required") + } + + if !slices.Contains(AllOutputFormats, o.Format) { + return fmt.Errorf("unsupported output format %q", o.Format) + } + + return nil +} + +type OutputFormats []OutputFormat + +func (p *OutputFormats) UnmarshalText(text []byte) error { + formats := strings.Split(string(text), ",") + + for _, item := range formats { + format, path, _ := strings.Cut(item, ":") + + *p = append(*p, OutputFormat{ + Path: path, + Format: format, + }) + } + + return nil } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/config/reader.go b/vendor/github.com/golangci/golangci-lint/pkg/config/reader.go deleted file mode 100644 index 0c4fa13b06..0000000000 --- a/vendor/github.com/golangci/golangci-lint/pkg/config/reader.go +++ /dev/null @@ -1,248 +0,0 @@ -package config - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "slices" - "strings" - - "github.com/go-viper/mapstructure/v2" - "github.com/mitchellh/go-homedir" - "github.com/spf13/viper" - - "github.com/golangci/golangci-lint/pkg/exitcodes" - "github.com/golangci/golangci-lint/pkg/fsutils" - "github.com/golangci/golangci-lint/pkg/logutils" -) - -type FileReader struct { - log logutils.Log - cfg *Config - commandLineCfg *Config -} - -func NewFileReader(toCfg, commandLineCfg *Config, log logutils.Log) *FileReader { - return &FileReader{ - log: log, - cfg: toCfg, - commandLineCfg: commandLineCfg, - } -} - -func (r *FileReader) Read() error { - // XXX: hack with double parsing for 2 purposes: - // 1. to access "config" option here. - // 2. to give config less priority than command line. - - configFile, err := r.parseConfigOption() - if err != nil { - if errors.Is(err, errConfigDisabled) { - return nil - } - - return fmt.Errorf("can't parse --config option: %w", err) - } - - if configFile != "" { - viper.SetConfigFile(configFile) - - // Assume YAML if the file has no extension. - if filepath.Ext(configFile) == "" { - viper.SetConfigType("yaml") - } - } else { - r.setupConfigFileSearch() - } - - return r.parseConfig() -} - -func (r *FileReader) parseConfig() error { - if err := viper.ReadInConfig(); err != nil { - if _, ok := err.(viper.ConfigFileNotFoundError); ok { - return nil - } - - return fmt.Errorf("can't read viper config: %w", err) - } - - usedConfigFile := viper.ConfigFileUsed() - if usedConfigFile == "" { - return nil - } - - if usedConfigFile == os.Stdin.Name() { - usedConfigFile = "" - r.log.Infof("Reading config file stdin") - } else { - var err error - usedConfigFile, err = fsutils.ShortestRelPath(usedConfigFile, "") - if err != nil { - r.log.Warnf("Can't pretty print config file path: %v", err) - } - - r.log.Infof("Used config file %s", usedConfigFile) - } - - usedConfigDir, err := filepath.Abs(filepath.Dir(usedConfigFile)) - if err != nil { - return errors.New("can't get config directory") - } - r.cfg.cfgDir = usedConfigDir - - if err := viper.Unmarshal(r.cfg, viper.DecodeHook(mapstructure.ComposeDecodeHookFunc( - // Default hooks (https://github.com/spf13/viper/blob/518241257478c557633ab36e474dfcaeb9a3c623/viper.go#L135-L138). - mapstructure.StringToTimeDurationHookFunc(), - mapstructure.StringToSliceHookFunc(","), - - // Needed for forbidigo. - mapstructure.TextUnmarshallerHookFunc(), - ))); err != nil { - return fmt.Errorf("can't unmarshal config by viper: %w", err) - } - - if err := r.validateConfig(); err != nil { - return fmt.Errorf("can't validate config: %w", err) - } - - if r.cfg.InternalTest { // just for testing purposes: to detect config file usage - fmt.Fprintln(logutils.StdOut, "test") - os.Exit(exitcodes.Success) - } - - return nil -} - -func (r *FileReader) validateConfig() error { - c := r.cfg - if len(c.Run.Args) != 0 { - return errors.New("option run.args in config isn't supported now") - } - - if c.Run.CPUProfilePath != "" { - return errors.New("option run.cpuprofilepath in config isn't allowed") - } - - if c.Run.MemProfilePath != "" { - return errors.New("option run.memprofilepath in config isn't allowed") - } - - if c.Run.TracePath != "" { - return errors.New("option run.tracepath in config isn't allowed") - } - - if c.Run.IsVerbose { - return errors.New("can't set run.verbose option with config: only on command-line") - } - for i, rule := range c.Issues.ExcludeRules { - if err := rule.Validate(); err != nil { - return fmt.Errorf("error in exclude rule #%d: %w", i, err) - } - } - if len(c.Severity.Rules) > 0 && c.Severity.Default == "" { - return errors.New("can't set severity rule option: no default severity defined") - } - for i, rule := range c.Severity.Rules { - if err := rule.Validate(); err != nil { - 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: %w", err) - } - return nil -} - -func getFirstPathArg() string { - args := os.Args - - // skip all args ([golangci-lint, run/linters]) before files/dirs list - for len(args) != 0 { - if args[0] == "run" { - args = args[1:] - break - } - - args = args[1:] - } - - // find first file/dir arg - firstArg := "./..." - for _, arg := range args { - if !strings.HasPrefix(arg, "-") { - firstArg = arg - break - } - } - - return firstArg -} - -func (r *FileReader) setupConfigFileSearch() { - firstArg := getFirstPathArg() - absStartPath, err := filepath.Abs(firstArg) - if err != nil { - r.log.Warnf("Can't make abs path for %q: %s", firstArg, err) - absStartPath = filepath.Clean(firstArg) - } - - // start from it - var curDir string - if fsutils.IsDir(absStartPath) { - curDir = absStartPath - } else { - curDir = filepath.Dir(absStartPath) - } - - // find all dirs from it up to the root - configSearchPaths := []string{"./"} - - for { - configSearchPaths = append(configSearchPaths, curDir) - newCurDir := filepath.Dir(curDir) - if curDir == newCurDir || newCurDir == "" { - break - } - curDir = newCurDir - } - - // find home directory for global config - if home, err := homedir.Dir(); err != nil { - r.log.Warnf("Can't get user's home directory: %s", err.Error()) - } else if !slices.Contains(configSearchPaths, home) { - configSearchPaths = append(configSearchPaths, home) - } - - r.log.Infof("Config search paths: %s", configSearchPaths) - viper.SetConfigName(".golangci") - for _, p := range configSearchPaths { - viper.AddConfigPath(p) - } -} - -var errConfigDisabled = errors.New("config is disabled by --no-config") - -func (r *FileReader) parseConfigOption() (string, error) { - cfg := r.commandLineCfg - if cfg == nil { - return "", nil - } - - configFile := cfg.Run.Config - if cfg.Run.NoConfig && configFile != "" { - return "", errors.New("can't combine option --config and --no-config") - } - - if cfg.Run.NoConfig { - return "", errConfigDisabled - } - - configFile, err := homedir.Expand(configFile) - if err != nil { - return "", errors.New("failed to expand configuration path") - } - - return configFile, 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 2bb21d9fa2..1531ab8830 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/config/run.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/config/run.go @@ -1,21 +1,17 @@ package config -import "time" +import ( + "fmt" + "slices" + "strings" + "time" +) // Run encapsulates the config options for running the linter analysis. type Run struct { - IsVerbose bool `mapstructure:"verbose"` - Silent bool - CPUProfilePath string - MemProfilePath string - TracePath string - Concurrency int - PrintResourcesUsage bool `mapstructure:"print-resources-usage"` + Timeout time.Duration `mapstructure:"timeout"` - Config string // The path to the golangci config file, as specified with the --config argument. - NoConfig bool - - Args []string + Concurrency int `mapstructure:"concurrency"` Go string `mapstructure:"go"` @@ -25,18 +21,27 @@ type Run struct { ExitCodeIfIssuesFound int `mapstructure:"issues-exit-code"` AnalyzeTests bool `mapstructure:"tests"` - // Deprecated: Deadline exists for historical compatibility - // and should not be used. To set run timeout use Timeout instead. - Deadline time.Duration - Timeout time.Duration - - PrintVersion bool - SkipFiles []string `mapstructure:"skip-files"` - SkipDirs []string `mapstructure:"skip-dirs"` - UseDefaultSkipDirs bool `mapstructure:"skip-dirs-use-default"` + // Deprecated: use Issues.ExcludeFiles instead. + SkipFiles []string `mapstructure:"skip-files"` + // Deprecated: use Issues.ExcludeDirs instead. + SkipDirs []string `mapstructure:"skip-dirs"` + // Deprecated: use Issues.UseDefaultExcludeDirs instead. + UseDefaultSkipDirs bool `mapstructure:"skip-dirs-use-default"` AllowParallelRunners bool `mapstructure:"allow-parallel-runners"` AllowSerialRunners bool `mapstructure:"allow-serial-runners"` + // Deprecated: use Output.ShowStats instead. ShowStats bool `mapstructure:"show-stats"` } + +func (r *Run) Validate() error { + // go help modules + allowedMods := []string{"mod", "readonly", "vendor"} + + if r.ModulesDownloadMode != "" && !slices.Contains(allowedMods, r.ModulesDownloadMode) { + return fmt.Errorf("invalid modules download path %s, only (%s) allowed", r.ModulesDownloadMode, strings.Join(allowedMods, "|")) + } + + return nil +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/config/severity.go b/vendor/github.com/golangci/golangci-lint/pkg/config/severity.go index 3068a0ed69..a6d2c9ec3f 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/config/severity.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/config/severity.go @@ -1,5 +1,10 @@ package config +import ( + "errors" + "fmt" +) + const severityRuleMinConditionsCount = 1 type Severity struct { @@ -8,11 +13,29 @@ type Severity struct { Rules []SeverityRule `mapstructure:"rules"` } +func (s *Severity) Validate() error { + if len(s.Rules) > 0 && s.Default == "" { + return errors.New("can't set severity rule option: no default severity defined") + } + + for i, rule := range s.Rules { + if err := rule.Validate(); err != nil { + return fmt.Errorf("error in severity rule #%d: %w", i, err) + } + } + + return nil +} + type SeverityRule struct { BaseRule `mapstructure:",squash"` Severity string } func (s *SeverityRule) Validate() error { + if s.Severity == "" { + return errors.New("severity should be set") + } + return s.BaseRule.Validate(severityRuleMinConditionsCount) } 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 715a3a4af0..80bb9c5b44 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/fsutils/fsutils.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/fsutils/fsutils.go @@ -12,10 +12,12 @@ func IsDir(filename string) bool { return err == nil && fi.IsDir() } -var cachedWd string -var cachedWdError error -var getWdOnce sync.Once -var useCache = true +var ( + cachedWd string + cachedWdError error + getWdOnce sync.Once + useCache = true +) func UseWdCache(use bool) { useCache = use diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/ifshort.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/copyloopvar.go similarity index 61% rename from vendor/github.com/golangci/golangci-lint/pkg/golinters/ifshort.go rename to vendor/github.com/golangci/golangci-lint/pkg/golinters/copyloopvar.go index 50e2c172ef..02ce5f37a6 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/ifshort.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/copyloopvar.go @@ -1,26 +1,25 @@ package golinters import ( - "github.com/esimonov/ifshort/pkg/analyzer" + "github.com/karamaru-alpha/copyloopvar" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" ) -func NewIfshort(settings *config.IfshortSettings) *goanalysis.Linter { +func NewCopyLoopVar(settings *config.CopyLoopVarSettings) *goanalysis.Linter { + a := copyloopvar.NewAnalyzer() + var cfg map[string]map[string]any if settings != nil { cfg = map[string]map[string]any{ - analyzer.Analyzer.Name: { - "max-decl-lines": settings.MaxDeclLines, - "max-decl-chars": settings.MaxDeclChars, + a.Name: { + "ignore-alias": settings.IgnoreAlias, }, } } - a := analyzer.Analyzer - return goanalysis.NewLinter( a.Name, a.Doc, diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/deadcode.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/deadcode.go deleted file mode 100644 index 4f563c3813..0000000000 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/deadcode.go +++ /dev/null @@ -1,61 +0,0 @@ -package golinters - -import ( - "fmt" - "sync" - - deadcodeAPI "github.com/golangci/go-misc/deadcode" - "golang.org/x/tools/go/analysis" - - "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" - "github.com/golangci/golangci-lint/pkg/lint/linter" - "github.com/golangci/golangci-lint/pkg/result" -) - -const deadcodeName = "deadcode" - -func NewDeadcode() *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - - analyzer := &analysis.Analyzer{ - Name: deadcodeName, - Doc: goanalysis.TheOnlyanalyzerDoc, - Run: func(pass *analysis.Pass) (any, error) { - prog := goanalysis.MakeFakeLoaderProgram(pass) - - issues, err := deadcodeAPI.Run(prog) - if err != nil { - return nil, err - } - - res := make([]goanalysis.Issue, 0, len(issues)) - for _, i := range issues { - res = append(res, goanalysis.NewIssue(&result.Issue{ - Pos: i.Pos, - Text: fmt.Sprintf("%s is unused", formatCode(i.UnusedIdentName, nil)), - FromLinter: deadcodeName, - }, pass)) - } - - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, res...) - mu.Unlock() - - return nil, nil - }, - } - - return goanalysis.NewLinter( - deadcodeName, - "Finds unused code", - []*analysis.Analyzer{analyzer}, - nil, - ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeTypesInfo) -} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/dogsled.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/dogsled.go index 79502fe8be..de567ce7cf 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/dogsled.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/dogsled.go @@ -16,7 +16,6 @@ import ( const dogsledName = "dogsled" -//nolint:dupl func NewDogsled(settings *config.DogsledSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/dupl.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/dupl.go index 5d772a5f2f..eec51eabab 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/dupl.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/dupl.go @@ -17,7 +17,6 @@ import ( const duplName = "dupl" -//nolint:dupl func NewDupl(settings *config.DuplSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/exhaustivestruct.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/exhaustivestruct.go deleted file mode 100644 index 9bc9bbfb0b..0000000000 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/exhaustivestruct.go +++ /dev/null @@ -1,31 +0,0 @@ -package golinters - -import ( - "strings" - - "github.com/mbilski/exhaustivestruct/pkg/analyzer" - "golang.org/x/tools/go/analysis" - - "github.com/golangci/golangci-lint/pkg/config" - "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" -) - -func NewExhaustiveStruct(settings *config.ExhaustiveStructSettings) *goanalysis.Linter { - a := analyzer.Analyzer - - var cfg map[string]map[string]any - if settings != nil { - cfg = map[string]map[string]any{ - a.Name: { - "struct_patterns": strings.Join(settings.StructPatterns, ","), - }, - } - } - - return goanalysis.NewLinter( - a.Name, - a.Doc, - []*analysis.Analyzer{a}, - cfg, - ).WithLoadMode(goanalysis.LoadModeTypesInfo) -} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/forbidigo.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/forbidigo.go index 6aced29226..797484ba2c 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/forbidigo.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/forbidigo.go @@ -16,7 +16,6 @@ import ( const forbidigoName = "forbidigo" -//nolint:dupl func NewForbidigo(settings *config.ForbidigoSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/funlen.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/funlen.go index 8def9c1f67..d421e174b7 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/funlen.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/funlen.go @@ -16,7 +16,6 @@ import ( const funlenName = "funlen" -//nolint:dupl func NewFunlen(settings *config.FunlenSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/ginkgolinter.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/ginkgolinter.go index 8919de15bf..3d20a9f49e 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/ginkgolinter.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/ginkgolinter.go @@ -2,33 +2,38 @@ package golinters import ( "github.com/nunnatsa/ginkgolinter" + "github.com/nunnatsa/ginkgolinter/types" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" ) -func NewGinkgoLinter(cfg *config.GinkgoLinterSettings) *goanalysis.Linter { - a := ginkgolinter.NewAnalyzer() +func NewGinkgoLinter(settings *config.GinkgoLinterSettings) *goanalysis.Linter { + cfg := &types.Config{} - cfgMap := make(map[string]map[string]any) - if cfg != nil { - cfgMap[a.Name] = map[string]any{ - "suppress-len-assertion": cfg.SuppressLenAssertion, - "suppress-nil-assertion": cfg.SuppressNilAssertion, - "suppress-err-assertion": cfg.SuppressErrAssertion, - "suppress-compare-assertion": cfg.SuppressCompareAssertion, - "suppress-async-assertion": cfg.SuppressAsyncAssertion, - "suppress-type-compare-assertion": cfg.SuppressTypeCompareWarning, - "forbid-focus-container": cfg.ForbidFocusContainer, - "allow-havelen-0": cfg.AllowHaveLenZero, + if settings != nil { + cfg = &types.Config{ + SuppressLen: types.Boolean(settings.SuppressLenAssertion), + SuppressNil: types.Boolean(settings.SuppressNilAssertion), + SuppressErr: types.Boolean(settings.SuppressErrAssertion), + SuppressCompare: types.Boolean(settings.SuppressCompareAssertion), + SuppressAsync: types.Boolean(settings.SuppressAsyncAssertion), + ForbidFocus: types.Boolean(settings.ForbidFocusContainer), + SuppressTypeCompare: types.Boolean(settings.SuppressTypeCompareWarning), + AllowHaveLen0: types.Boolean(settings.AllowHaveLenZero), + ForceExpectTo: types.Boolean(settings.ForceExpectTo), + ValidateAsyncIntervals: types.Boolean(settings.ForbidSpecPollution), + ForbidSpecPollution: types.Boolean(settings.ValidateAsyncIntervals), } } + a := ginkgolinter.NewAnalyzerWithConfig(cfg) + return goanalysis.NewLinter( a.Name, "enforces standards of using ginkgo and gomega", []*analysis.Analyzer{a}, - cfgMap, + nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/adapters.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/adapters.go deleted file mode 100644 index 284215dfc6..0000000000 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/adapters.go +++ /dev/null @@ -1,41 +0,0 @@ -package goanalysis - -import ( - "go/types" - - "golang.org/x/tools/go/analysis" - "golang.org/x/tools/go/loader" //nolint:staticcheck // it's an adapter for golang.org/x/tools/go/packages -) - -func MakeFakeLoaderProgram(pass *analysis.Pass) *loader.Program { - var info types.Info - if pass.TypesInfo != nil { - info = *pass.TypesInfo - } - - prog := &loader.Program{ - Fset: pass.Fset, - Created: []*loader.PackageInfo{ - { - Pkg: pass.Pkg, - Importable: true, // not used - TransitivelyErrorFree: true, // TODO ??? - - Files: pass.Files, - Errors: nil, - Info: info, - }, - }, - AllPackages: map[*types.Package]*loader.PackageInfo{ - pass.Pkg: { - Pkg: pass.Pkg, - Importable: true, - TransitivelyErrorFree: true, - Files: pass.Files, - Errors: nil, - Info: info, - }, - }, - } - return prog -} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/issue.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/issue.go index f331a3ab9f..15d8dd2b33 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/issue.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/issue.go @@ -13,9 +13,9 @@ type Issue struct { Pass *analysis.Pass } -func NewIssue(i *result.Issue, pass *analysis.Pass) Issue { +func NewIssue(issue *result.Issue, pass *analysis.Pass) Issue { return Issue{ - Issue: *i, + Issue: *issue, Pass: pass, } } @@ -23,6 +23,7 @@ func NewIssue(i *result.Issue, pass *analysis.Pass) Issue { type EncodingIssue struct { FromLinter string Text string + Severity string Pos token.Position LineRange *result.Range Replacement *result.Replacement 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 a8660b6121..b75e3fa0f5 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 @@ -61,7 +61,8 @@ type runner struct { } func newRunner(prefix string, logger logutils.Log, pkgCache *pkgcache.Cache, loadGuard *load.Guard, - loadMode LoadMode, sw *timeutils.Stopwatch) *runner { + loadMode LoadMode, sw *timeutils.Stopwatch, +) *runner { return &runner{ prefix: prefix, log: logger, @@ -80,7 +81,8 @@ func newRunner(prefix string, logger logutils.Log, pkgCache *pkgcache.Cache, loa // singlechecker and the multi-analysis commands. // It returns the appropriate exit code. func (r *runner) run(analyzers []*analysis.Analyzer, initialPackages []*packages.Package) ([]Diagnostic, - []error, map[*analysis.Pass]*packages.Package) { + []error, map[*analysis.Pass]*packages.Package, +) { debugf("Analyzing %d packages on load mode %s", len(initialPackages), r.loadMode) defer r.pkgCache.Trim() @@ -116,7 +118,8 @@ func (r *runner) markAllActions(a *analysis.Analyzer, pkg *packages.Package, mar } func (r *runner) makeAction(a *analysis.Analyzer, pkg *packages.Package, - initialPkgs map[*packages.Package]bool, actions map[actKey]*action, actAlloc *actionAllocator) *action { + initialPkgs map[*packages.Package]bool, actions map[actKey]*action, actAlloc *actionAllocator, +) *action { k := actKey{a, pkg} act, ok := actions[k] if ok { @@ -150,7 +153,8 @@ func (r *runner) makeAction(a *analysis.Analyzer, pkg *packages.Package, } func (r *runner) buildActionFactDeps(act *action, a *analysis.Analyzer, pkg *packages.Package, - initialPkgs map[*packages.Package]bool, actions map[actKey]*action, actAlloc *actionAllocator) { + initialPkgs map[*packages.Package]bool, actions map[actKey]*action, actAlloc *actionAllocator, +) { // An analysis that consumes/produces facts // must run on the package's dependencies too. if len(a.FactTypes) == 0 { @@ -173,9 +177,9 @@ func (r *runner) buildActionFactDeps(act *action, a *analysis.Analyzer, pkg *pac } } -//nolint:gocritic func (r *runner) prepareAnalysis(pkgs []*packages.Package, - analyzers []*analysis.Analyzer) (map[*packages.Package]bool, []*action, []*action) { + analyzers []*analysis.Analyzer, +) (initialPkgs map[*packages.Package]bool, allActions, roots []*action) { // Construct the action graph. // Each graph node (action) is one unit of analysis. @@ -195,13 +199,13 @@ func (r *runner) prepareAnalysis(pkgs []*packages.Package, actions := make(map[actKey]*action, totalActionsCount) actAlloc := newActionAllocator(totalActionsCount) - initialPkgs := make(map[*packages.Package]bool, len(pkgs)) + initialPkgs = make(map[*packages.Package]bool, len(pkgs)) for _, pkg := range pkgs { initialPkgs[pkg] = true } // Build nodes for initial packages. - roots := make([]*action, 0, len(pkgs)*len(analyzers)) + roots = make([]*action, 0, len(pkgs)*len(analyzers)) for _, a := range analyzers { for _, pkg := range pkgs { root := r.makeAction(a, pkg, initialPkgs, actions, actAlloc) @@ -210,7 +214,7 @@ func (r *runner) prepareAnalysis(pkgs []*packages.Package, } } - allActions := maps.Values(actions) + allActions = maps.Values(actions) debugf("Built %d actions", len(actions)) @@ -276,7 +280,6 @@ func (r *runner) analyze(pkgs []*packages.Package, analyzers []*analysis.Analyze return rootActions } -//nolint:nakedret func extractDiagnostics(roots []*action) (retDiags []Diagnostic, retErrors []error) { extracted := make(map[*action]bool) var extract func(*action) @@ -333,5 +336,5 @@ func extractDiagnostics(roots []*action) (retDiags []Diagnostic, retErrors []err } } visitAll(roots) - return + return retDiags, retErrors } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/runners.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/runners.go index 559fb1392b..b832fc32da 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/runners.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/runners.go @@ -124,7 +124,8 @@ func getIssuesCacheKey(analyzers []*analysis.Analyzer) string { } func saveIssuesToCache(allPkgs []*packages.Package, pkgsFromCache map[*packages.Package]bool, - issues []result.Issue, lintCtx *linter.Context, analyzers []*analysis.Analyzer) { + issues []result.Issue, lintCtx *linter.Context, analyzers []*analysis.Analyzer, +) { startedAt := time.Now() perPkgIssues := map[*packages.Package][]result.Issue{} for ind := range issues { @@ -151,6 +152,7 @@ func saveIssuesToCache(allPkgs []*packages.Package, pkgsFromCache map[*packages. encodedIssues = append(encodedIssues, EncodingIssue{ FromLinter: i.FromLinter, Text: i.Text, + Severity: i.Severity, Pos: i.Pos, LineRange: i.LineRange, Replacement: i.Replacement, @@ -182,9 +184,9 @@ func saveIssuesToCache(allPkgs []*packages.Package, pkgsFromCache map[*packages. issuesCacheDebugf("Saved %d issues from %d packages to cache in %s", savedIssuesCount, len(allPkgs), time.Since(startedAt)) } -//nolint:gocritic func loadIssuesFromCache(pkgs []*packages.Package, lintCtx *linter.Context, - analyzers []*analysis.Analyzer) ([]result.Issue, map[*packages.Package]bool) { + analyzers []*analysis.Analyzer, +) (issuesFromCache []result.Issue, pkgsFromCache map[*packages.Package]bool) { startedAt := time.Now() lintResKey := getIssuesCacheKey(analyzers) @@ -218,16 +220,18 @@ func loadIssuesFromCache(pkgs []*packages.Package, lintCtx *linter.Context, } issues := make([]result.Issue, 0, len(pkgIssues)) - for _, i := range pkgIssues { + for i := range pkgIssues { + issue := &pkgIssues[i] issues = append(issues, result.Issue{ - FromLinter: i.FromLinter, - Text: i.Text, - Pos: i.Pos, - LineRange: i.LineRange, - Replacement: i.Replacement, + FromLinter: issue.FromLinter, + Text: issue.Text, + Severity: issue.Severity, + Pos: issue.Pos, + LineRange: issue.LineRange, + Replacement: issue.Replacement, Pkg: pkg, - ExpectNoLint: i.ExpectNoLint, - ExpectedNoLintLinter: i.ExpectedNoLintLinter, + ExpectNoLint: issue.ExpectNoLint, + ExpectedNoLintLinter: issue.ExpectedNoLintLinter, }) } cacheRes.issues = issues @@ -242,13 +246,12 @@ func loadIssuesFromCache(pkgs []*packages.Package, lintCtx *linter.Context, wg.Wait() loadedIssuesCount := 0 - var issues []result.Issue - pkgsFromCache := map[*packages.Package]bool{} + pkgsFromCache = map[*packages.Package]bool{} for pkg, cacheRes := range pkgToCacheRes { if cacheRes.loadErr == nil { loadedIssuesCount += len(cacheRes.issues) pkgsFromCache[pkg] = true - issues = append(issues, cacheRes.issues...) + issuesFromCache = append(issuesFromCache, cacheRes.issues...) issuesCacheDebugf("Loaded package %s issues (%d) from cache", pkg, len(cacheRes.issues)) } else { issuesCacheDebugf("Didn't load package %s issues from cache: %s", pkg, cacheRes.loadErr) @@ -256,7 +259,7 @@ func loadIssuesFromCache(pkgs []*packages.Package, lintCtx *linter.Context, } issuesCacheDebugf("Loaded %d issues from cache in %s, analyzing %d/%d packages", loadedIssuesCount, time.Since(startedAt), len(pkgs)-len(pkgsFromCache), len(pkgs)) - return issues, pkgsFromCache + return issuesFromCache, pkgsFromCache } func analyzersHashID(analyzers []*analysis.Analyzer) string { diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/gochecknoglobals.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/gochecknoglobals.go index 6e18aeb27d..e94778a32a 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/gochecknoglobals.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/gochecknoglobals.go @@ -8,22 +8,19 @@ import ( ) func NewGochecknoglobals() *goanalysis.Linter { - gochecknoglobals := checknoglobals.Analyzer() + a := checknoglobals.Analyzer() - // gochecknoglobals only lints test files if the `-t` flag is passed, so we - // pass the `t` flag as true to the analyzer before running it. This can be - // turned off by using the regular golangci-lint flags such as `--tests` or - // `--skip-files`. + // gochecknoglobals only lints test files if the `-t` flag is passed, + // so we pass the `t` flag as true to the analyzer before running it. + // This can be turned off by using the regular golangci-lint flags such as `--tests` or `--exclude-files`. linterConfig := map[string]map[string]any{ - gochecknoglobals.Name: { - "t": true, - }, + a.Name: {"t": true}, } return goanalysis.NewLinter( - gochecknoglobals.Name, - gochecknoglobals.Doc, - []*analysis.Analyzer{gochecknoglobals}, + a.Name, + "Check that no global variables exist.", + []*analysis.Analyzer{a}, linterConfig, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/gocognit.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/gocognit.go index 406d34ed6c..f5cff840e4 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/gocognit.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/gocognit.go @@ -16,7 +16,6 @@ import ( const gocognitName = "gocognit" -//nolint:dupl func NewGocognit(settings *config.GocognitSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue 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 b518852754..204e2496a6 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goconst.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goconst.go @@ -15,7 +15,6 @@ import ( const goconstName = "goconst" -//nolint:dupl func NewGoconst(settings *config.GoConstSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue 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 3cf43afc62..f78c2cf909 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/gocritic.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/gocritic.go @@ -15,6 +15,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/exp/slices" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" @@ -31,12 +32,11 @@ var ( isGoCriticDebug = logutils.HaveDebugTag(logutils.DebugKeyGoCritic) ) -func NewGoCritic(settings *config.GoCriticSettings, cfg *config.Config) *goanalysis.Linter { +func NewGoCritic(settings *config.GoCriticSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue wrapper := &goCriticWrapper{ - cfg: cfg, sizes: types.SizesFor("gc", runtime.GOARCH), } @@ -70,21 +70,24 @@ Dynamic rules are written declaratively with AST patterns, filters, report messa nil, ). WithContextSetter(func(context *linter.Context) { - wrapper.init(settings, context.Log) + wrapper.configDir = context.Cfg.GetConfigDir() + + wrapper.init(context.Log, settings) }). WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { return resIssues - }).WithLoadMode(goanalysis.LoadModeTypesInfo) + }). + WithLoadMode(goanalysis.LoadModeTypesInfo) } type goCriticWrapper struct { settingsWrapper *goCriticSettingsWrapper - cfg *config.Config + configDir string sizes types.Sizes once sync.Once } -func (w *goCriticWrapper) init(settings *config.GoCriticSettings, logger logutils.Log) { +func (w *goCriticWrapper) init(logger logutils.Log, settings *config.GoCriticSettings) { if settings == nil { return } @@ -92,15 +95,15 @@ func (w *goCriticWrapper) init(settings *config.GoCriticSettings, logger logutil w.once.Do(func() { err := checkers.InitEmbeddedRules() if err != nil { - logger.Fatalf("%s: %v: setting an explicit GOROOT can fix this problem.", goCriticName, err) + logger.Fatalf("%s: %v: setting an explicit GOROOT can fix this problem", goCriticName, err) } }) settingsWrapper := newGoCriticSettingsWrapper(settings, logger) - - settingsWrapper.inferEnabledChecks() - - if err := settingsWrapper.validate(); err != nil { + settingsWrapper.InferEnabledChecks() + // Validate must be after InferEnabledChecks, not before. + // Because it uses gathered information about tags set and finally enabled checks. + if err := settingsWrapper.Validate(); err != nil { logger.Fatalf("%s: invalid settings: %s", goCriticName, err) } @@ -109,7 +112,7 @@ func (w *goCriticWrapper) init(settings *config.GoCriticSettings, logger logutil func (w *goCriticWrapper) run(pass *analysis.Pass) ([]goanalysis.Issue, error) { if w.settingsWrapper == nil { - return nil, fmt.Errorf("the settings wrapper is nil") + return nil, errors.New("the settings wrapper is nil") } linterCtx := gocriticlinter.NewContext(pass.Fset, w.sizes) @@ -123,7 +126,7 @@ func (w *goCriticWrapper) run(pass *analysis.Pass) ([]goanalysis.Issue, error) { linterCtx.SetPackageInfo(pass.TypesInfo, pass.Pkg) - pkgIssues := runGocriticOnPackage(linterCtx, enabledCheckers, pass.Files) + pkgIssues := runGoCriticOnPackage(linterCtx, enabledCheckers, pass.Files) issues := make([]goanalysis.Issue, 0, len(pkgIssues)) for i := range pkgIssues { @@ -134,15 +137,15 @@ func (w *goCriticWrapper) run(pass *analysis.Pass) ([]goanalysis.Issue, error) { } func (w *goCriticWrapper) buildEnabledCheckers(linterCtx *gocriticlinter.Context) ([]*gocriticlinter.Checker, error) { - allParams := w.settingsWrapper.getLowerCasedParams() + allLowerCasedParams := w.settingsWrapper.GetLowerCasedParams() var enabledCheckers []*gocriticlinter.Checker for _, info := range gocriticlinter.GetCheckersInfo() { - if !w.settingsWrapper.isCheckEnabled(info.Name) { + if !w.settingsWrapper.IsCheckEnabled(info.Name) { continue } - if err := w.configureCheckerInfo(info, allParams); err != nil { + if err := w.configureCheckerInfo(info, allLowerCasedParams); err != nil { return nil, err } @@ -156,57 +159,17 @@ func (w *goCriticWrapper) buildEnabledCheckers(linterCtx *gocriticlinter.Context return enabledCheckers, nil } -func runGocriticOnPackage(linterCtx *gocriticlinter.Context, checks []*gocriticlinter.Checker, - files []*ast.File) []result.Issue { - var res []result.Issue - for _, f := range files { - filename := filepath.Base(linterCtx.FileSet.Position(f.Pos()).Filename) - linterCtx.SetFileInfo(filename, f) - - issues := runGocriticOnFile(linterCtx, f, checks) - res = append(res, issues...) - } - return res -} - -func runGocriticOnFile(linterCtx *gocriticlinter.Context, f *ast.File, checks []*gocriticlinter.Checker) []result.Issue { - var res []result.Issue - - for _, c := range checks { - // All checkers are expected to use *lint.Context - // as read-only structure, so no copying is required. - for _, warn := range c.Check(f) { - pos := linterCtx.FileSet.Position(warn.Pos) - issue := result.Issue{ - Pos: pos, - Text: fmt.Sprintf("%s: %s", c.Info.Name, warn.Text), - FromLinter: goCriticName, - } - - if warn.HasQuickFix() { - issue.Replacement = &result.Replacement{ - Inline: &result.InlineFix{ - StartCol: pos.Column - 1, - Length: int(warn.Suggestion.To - warn.Suggestion.From), - NewString: string(warn.Suggestion.Replacement), - }, - } - } - - res = append(res, issue) - } - } - - return res -} - -func (w *goCriticWrapper) configureCheckerInfo(info *gocriticlinter.CheckerInfo, allParams map[string]config.GoCriticCheckSettings) error { - params := allParams[strings.ToLower(info.Name)] +func (w *goCriticWrapper) configureCheckerInfo( + info *gocriticlinter.CheckerInfo, + allLowerCasedParams map[string]config.GoCriticCheckSettings, +) error { + params := allLowerCasedParams[strings.ToLower(info.Name)] if params == nil { // no config for this checker return nil } - infoParams := normalizeCheckerInfoParams(info) + // To lowercase info param keys here because golangci-lint's config parser lowercases all strings. + infoParams := normalizeMap(info.Params) for k, p := range params { v, ok := infoParams[k] if ok { @@ -230,16 +193,6 @@ func (w *goCriticWrapper) configureCheckerInfo(info *gocriticlinter.CheckerInfo, return nil } -func normalizeCheckerInfoParams(info *gocriticlinter.CheckerInfo) gocriticlinter.CheckerParams { - // lowercase info param keys here because golangci-lint's config parser lowercases all strings - ret := gocriticlinter.CheckerParams{} - for k, v := range info.Params { - ret[strings.ToLower(k)] = v - } - - return ret -} - // normalizeCheckerParamsValue normalizes value types. // go-critic asserts that CheckerParam.Value has some specific types, // but the file parsers (TOML, YAML, JSON) don't create the same representation for raw type. @@ -254,362 +207,372 @@ func (w *goCriticWrapper) normalizeCheckerParamsValue(p any) any { return rv.Bool() case reflect.String: // Perform variable substitution. - return strings.ReplaceAll(rv.String(), "${configDir}", w.cfg.GetConfigDir()) + return strings.ReplaceAll(rv.String(), "${configDir}", w.configDir) default: return p } } -// TODO(ldez): rewrite and simplify goCriticSettingsWrapper. - -type goCriticSettingsWrapper struct { - *config.GoCriticSettings +func runGoCriticOnPackage(linterCtx *gocriticlinter.Context, checks []*gocriticlinter.Checker, files []*ast.File) []result.Issue { + var res []result.Issue + for _, f := range files { + filename := filepath.Base(linterCtx.FileSet.Position(f.Pos()).Filename) + linterCtx.SetFileInfo(filename, f) - logger logutils.Log + issues := runGoCriticOnFile(linterCtx, f, checks) + res = append(res, issues...) + } + return res +} - allCheckers []*gocriticlinter.CheckerInfo - allCheckerMap map[string]*gocriticlinter.CheckerInfo +func runGoCriticOnFile(linterCtx *gocriticlinter.Context, f *ast.File, checks []*gocriticlinter.Checker) []result.Issue { + var res []result.Issue - inferredEnabledChecks map[string]bool -} + for _, c := range checks { + // All checkers are expected to use *lint.Context + // as read-only structure, so no copying is required. + for _, warn := range c.Check(f) { + pos := linterCtx.FileSet.Position(warn.Pos) + issue := result.Issue{ + Pos: pos, + Text: fmt.Sprintf("%s: %s", c.Info.Name, warn.Text), + FromLinter: goCriticName, + } -func newGoCriticSettingsWrapper(settings *config.GoCriticSettings, logger logutils.Log) *goCriticSettingsWrapper { - allCheckers := gocriticlinter.GetCheckersInfo() + if warn.HasQuickFix() { + issue.Replacement = &result.Replacement{ + Inline: &result.InlineFix{ + StartCol: pos.Column - 1, + Length: int(warn.Suggestion.To - warn.Suggestion.From), + NewString: string(warn.Suggestion.Replacement), + }, + } + } - allCheckerMap := make(map[string]*gocriticlinter.CheckerInfo) - for _, checkInfo := range allCheckers { - allCheckerMap[checkInfo.Name] = checkInfo + res = append(res, issue) + } } - return &goCriticSettingsWrapper{ - GoCriticSettings: settings, - logger: logger, - allCheckers: allCheckers, - allCheckerMap: allCheckerMap, - inferredEnabledChecks: map[string]bool{}, - } + return res } -func (s *goCriticSettingsWrapper) buildTagToCheckersMap() map[string][]string { - tagToCheckers := map[string][]string{} +type goCriticChecks[T any] map[string]T - for _, checker := range s.allCheckers { - for _, tag := range checker.Tags { - tagToCheckers[tag] = append(tagToCheckers[tag], checker.Name) - } - } - - return tagToCheckers +func (m goCriticChecks[T]) has(name string) bool { + _, ok := m[name] + return ok } -func (s *goCriticSettingsWrapper) checkerTagsDebugf() { - if !isGoCriticDebug { - return - } +type goCriticSettingsWrapper struct { + *config.GoCriticSettings - tagToCheckers := s.buildTagToCheckersMap() + logger logutils.Log - allTags := maps.Keys(tagToCheckers) - sort.Strings(allTags) + allCheckers []*gocriticlinter.CheckerInfo - goCriticDebugf("All gocritic existing tags and checks:") - for _, tag := range allTags { - debugChecksListf(tagToCheckers[tag], " tag %q", tag) - } + allChecks goCriticChecks[struct{}] + allChecksByTag goCriticChecks[[]string] + allTagsSorted []string + inferredEnabledChecks goCriticChecks[struct{}] + + // *LowerCased fields are used for GoCriticSettings.SettingsPerCheck validation only. + + allChecksLowerCased goCriticChecks[struct{}] + inferredEnabledChecksLowerCased goCriticChecks[struct{}] } -func (s *goCriticSettingsWrapper) disabledCheckersDebugf() { - if !isGoCriticDebug { - return - } +func newGoCriticSettingsWrapper(settings *config.GoCriticSettings, logger logutils.Log) *goCriticSettingsWrapper { + allCheckers := gocriticlinter.GetCheckersInfo() - var disabledCheckers []string - for _, checker := range s.allCheckers { - if s.inferredEnabledChecks[strings.ToLower(checker.Name)] { - continue - } + allChecks := make(goCriticChecks[struct{}], len(allCheckers)) + allChecksLowerCased := make(goCriticChecks[struct{}], len(allCheckers)) + allChecksByTag := make(goCriticChecks[[]string]) + for _, checker := range allCheckers { + allChecks[checker.Name] = struct{}{} + allChecksLowerCased[strings.ToLower(checker.Name)] = struct{}{} - disabledCheckers = append(disabledCheckers, checker.Name) + for _, tag := range checker.Tags { + allChecksByTag[tag] = append(allChecksByTag[tag], checker.Name) + } } - if len(disabledCheckers) == 0 { - goCriticDebugf("All checks are enabled") - } else { - debugChecksListf(disabledCheckers, "Final not used") + allTagsSorted := maps.Keys(allChecksByTag) + sort.Strings(allTagsSorted) + + return &goCriticSettingsWrapper{ + GoCriticSettings: settings, + logger: logger, + allCheckers: allCheckers, + allChecks: allChecks, + allChecksLowerCased: allChecksLowerCased, + allChecksByTag: allChecksByTag, + allTagsSorted: allTagsSorted, + inferredEnabledChecks: make(goCriticChecks[struct{}]), + inferredEnabledChecksLowerCased: make(goCriticChecks[struct{}]), } } -func (s *goCriticSettingsWrapper) inferEnabledChecks() { - s.checkerTagsDebugf() +func (s *goCriticSettingsWrapper) IsCheckEnabled(name string) bool { + return s.inferredEnabledChecks.has(name) +} - enabledByDefaultChecks := s.getDefaultEnabledCheckersNames() - debugChecksListf(enabledByDefaultChecks, "Enabled by default") +func (s *goCriticSettingsWrapper) GetLowerCasedParams() map[string]config.GoCriticCheckSettings { + return normalizeMap(s.SettingsPerCheck) +} + +// InferEnabledChecks tries to be consistent with (lintersdb.EnabledSet).build. +func (s *goCriticSettingsWrapper) InferEnabledChecks() { + s.debugChecksInitialState() - disabledByDefaultChecks := s.getDefaultDisabledCheckersNames() + enabledByDefaultChecks, disabledByDefaultChecks := s.buildEnabledAndDisabledByDefaultChecks() + debugChecksListf(enabledByDefaultChecks, "Enabled by default") debugChecksListf(disabledByDefaultChecks, "Disabled by default") - enabledChecks := make([]string, 0, len(s.EnabledTags)+len(enabledByDefaultChecks)) + enabledChecks := make(goCriticChecks[struct{}]) - // EnabledTags - if len(s.EnabledTags) != 0 { - tagToCheckers := s.buildTagToCheckersMap() - for _, tag := range s.EnabledTags { - enabledChecks = append(enabledChecks, tagToCheckers[tag]...) + if s.EnableAll { + enabledChecks = make(goCriticChecks[struct{}], len(s.allCheckers)) + for _, info := range s.allCheckers { + enabledChecks[info.Name] = struct{}{} + } + } else if !s.DisableAll { + // enable-all/disable-all revokes the default settings. + enabledChecks = make(goCriticChecks[struct{}], len(enabledByDefaultChecks)) + for _, check := range enabledByDefaultChecks { + enabledChecks[check] = struct{}{} } - - debugChecksListf(enabledChecks, "Enabled by config tags %s", sprintStrings(s.EnabledTags)) } - if !(len(s.EnabledTags) == 0 && len(s.EnabledChecks) != 0) { - // don't use default checks only if we have no enabled tags and enable some checks manually - enabledChecks = append(enabledChecks, enabledByDefaultChecks...) - } + if len(s.EnabledTags) != 0 { + enabledFromTags := s.expandTagsToChecks(s.EnabledTags) + debugChecksListf(enabledFromTags, "Enabled by config tags %s", sprintSortedStrings(s.EnabledTags)) - // DisabledTags - if len(s.DisabledTags) != 0 { - enabledChecks = s.filterByDisableTags(enabledChecks, s.DisabledTags) + for _, check := range enabledFromTags { + enabledChecks[check] = struct{}{} + } } - // EnabledChecks if len(s.EnabledChecks) != 0 { debugChecksListf(s.EnabledChecks, "Enabled by config") - alreadyEnabledChecksSet := stringsSliceToSet(enabledChecks) - for _, enabledCheck := range s.EnabledChecks { - if alreadyEnabledChecksSet[enabledCheck] { - s.logger.Warnf("%s: no need to enable check %q: it's already enabled", goCriticName, enabledCheck) + for _, check := range s.EnabledChecks { + if enabledChecks.has(check) { + s.logger.Warnf("%s: no need to enable check %q: it's already enabled", goCriticName, check) continue } - enabledChecks = append(enabledChecks, enabledCheck) + enabledChecks[check] = struct{}{} + } + } + + if len(s.DisabledTags) != 0 { + disabledFromTags := s.expandTagsToChecks(s.DisabledTags) + debugChecksListf(disabledFromTags, "Disabled by config tags %s", sprintSortedStrings(s.DisabledTags)) + + for _, check := range disabledFromTags { + delete(enabledChecks, check) } } - // DisabledChecks if len(s.DisabledChecks) != 0 { debugChecksListf(s.DisabledChecks, "Disabled by config") - enabledChecksSet := stringsSliceToSet(enabledChecks) - for _, disabledCheck := range s.DisabledChecks { - if !enabledChecksSet[disabledCheck] { - s.logger.Warnf("%s: check %q was explicitly disabled via config. However, as this check "+ - "is disabled by default, there is no need to explicitly disable it via config.", goCriticName, disabledCheck) + for _, check := range s.DisabledChecks { + if !enabledChecks.has(check) { + s.logger.Warnf("%s: no need to disable check %q: it's already disabled", goCriticName, check) continue } - delete(enabledChecksSet, disabledCheck) + delete(enabledChecks, check) } + } - enabledChecks = nil - for enabledCheck := range enabledChecksSet { - enabledChecks = append(enabledChecks, enabledCheck) + s.inferredEnabledChecks = enabledChecks + s.inferredEnabledChecksLowerCased = normalizeMap(s.inferredEnabledChecks) + s.debugChecksFinalState() +} + +func (s *goCriticSettingsWrapper) buildEnabledAndDisabledByDefaultChecks() (enabled, disabled []string) { + for _, info := range s.allCheckers { + if enabledByDef := isEnabledByDefaultGoCriticChecker(info); enabledByDef { + enabled = append(enabled, info.Name) + } else { + disabled = append(disabled, info.Name) } } + return enabled, disabled +} - s.inferredEnabledChecks = map[string]bool{} - for _, check := range enabledChecks { - s.inferredEnabledChecks[strings.ToLower(check)] = true +func (s *goCriticSettingsWrapper) expandTagsToChecks(tags []string) []string { + var checks []string + for _, tag := range tags { + checks = append(checks, s.allChecksByTag[tag]...) } - - debugChecksListf(enabledChecks, "Final used") - - s.disabledCheckersDebugf() + return checks } -func (s *goCriticSettingsWrapper) validate() error { - if len(s.EnabledTags) == 0 { - if len(s.EnabledChecks) != 0 && len(s.DisabledChecks) != 0 { - return errors.New("both enabled and disabled check aren't allowed for gocritic") - } - } else { - if err := validateStringsUniq(s.EnabledTags); err != nil { - return fmt.Errorf("validate enabled tags: %w", err) - } +func (s *goCriticSettingsWrapper) debugChecksInitialState() { + if !isGoCriticDebug { + return + } - tagToCheckers := s.buildTagToCheckersMap() + goCriticDebugf("All gocritic existing tags and checks:") + for _, tag := range s.allTagsSorted { + debugChecksListf(s.allChecksByTag[tag], " tag %q", tag) + } +} - for _, tag := range s.EnabledTags { - if _, ok := tagToCheckers[tag]; !ok { - return fmt.Errorf("gocritic [enabled]tag %q doesn't exist", tag) - } - } +func (s *goCriticSettingsWrapper) debugChecksFinalState() { + if !isGoCriticDebug { + return } - if len(s.DisabledTags) > 0 { - tagToCheckers := s.buildTagToCheckersMap() - for _, tag := range s.EnabledTags { - if _, ok := tagToCheckers[tag]; !ok { - return fmt.Errorf("gocritic [disabled]tag %q doesn't exist", tag) - } + var enabledChecks []string + var disabledChecks []string + + for _, checker := range s.allCheckers { + name := checker.Name + if s.inferredEnabledChecks.has(name) { + enabledChecks = append(enabledChecks, name) + } else { + disabledChecks = append(disabledChecks, name) } } - if err := validateStringsUniq(s.EnabledChecks); err != nil { - return fmt.Errorf("validate enabled checks: %w", err) - } + debugChecksListf(enabledChecks, "Final used") - if err := validateStringsUniq(s.DisabledChecks); err != nil { - return fmt.Errorf("validate disabled checks: %w", err) + if len(disabledChecks) == 0 { + goCriticDebugf("All checks are enabled") + } else { + debugChecksListf(disabledChecks, "Final not used") } +} - if err := s.validateCheckerNames(); err != nil { - return fmt.Errorf("validation failed: %w", err) +// Validate tries to be consistent with (lintersdb.Validator).validateEnabledDisabledLintersConfig. +func (s *goCriticSettingsWrapper) Validate() error { + for _, v := range []func() error{ + s.validateOptionsCombinations, + s.validateCheckerTags, + s.validateCheckerNames, + s.validateDisabledAndEnabledAtOneMoment, + s.validateAtLeastOneCheckerEnabled, + } { + if err := v(); err != nil { + return err + } } - return nil } -func (s *goCriticSettingsWrapper) isCheckEnabled(name string) bool { - return s.inferredEnabledChecks[strings.ToLower(name)] -} +func (s *goCriticSettingsWrapper) validateOptionsCombinations() error { + if s.EnableAll { + if s.DisableAll { + return errors.New("enable-all and disable-all options must not be combined") + } -// getAllCheckerNames returns a map containing all checker names supported by gocritic. -func (s *goCriticSettingsWrapper) getAllCheckerNames() map[string]bool { - allCheckerNames := make(map[string]bool, len(s.allCheckers)) + if len(s.EnabledTags) != 0 { + return errors.New("enable-all and enabled-tags options must not be combined") + } - for _, checker := range s.allCheckers { - allCheckerNames[strings.ToLower(checker.Name)] = true + if len(s.EnabledChecks) != 0 { + return errors.New("enable-all and enabled-checks options must not be combined") + } } - return allCheckerNames -} + if s.DisableAll { + if len(s.DisabledTags) != 0 { + return errors.New("disable-all and disabled-tags options must not be combined") + } -func (s *goCriticSettingsWrapper) getDefaultEnabledCheckersNames() []string { - var enabled []string + if len(s.DisabledChecks) != 0 { + return errors.New("disable-all and disabled-checks options must not be combined") + } - for _, info := range s.allCheckers { - enable := s.isEnabledByDefaultCheck(info) - if enable { - enabled = append(enabled, info.Name) + if len(s.EnabledTags) == 0 && len(s.EnabledChecks) == 0 { + return errors.New("all checks were disabled, but no one check was enabled: at least one must be enabled") } } - return enabled + return nil } -func (s *goCriticSettingsWrapper) getDefaultDisabledCheckersNames() []string { - var disabled []string +func (s *goCriticSettingsWrapper) validateCheckerTags() error { + for _, tag := range s.EnabledTags { + if !s.allChecksByTag.has(tag) { + return fmt.Errorf("enabled tag %q doesn't exist, see %s's documentation", tag, goCriticName) + } + } - for _, info := range s.allCheckers { - enable := s.isEnabledByDefaultCheck(info) - if !enable { - disabled = append(disabled, info.Name) + for _, tag := range s.DisabledTags { + if !s.allChecksByTag.has(tag) { + return fmt.Errorf("disabled tag %q doesn't exist, see %s's documentation", tag, goCriticName) } } - return disabled + return nil } func (s *goCriticSettingsWrapper) validateCheckerNames() error { - allowedNames := s.getAllCheckerNames() - for _, name := range s.EnabledChecks { - if !allowedNames[strings.ToLower(name)] { - return fmt.Errorf("enabled checker %s doesn't exist, all existing checkers: %s", - name, sprintAllowedCheckerNames(allowedNames)) + if !s.allChecks.has(name) { + return fmt.Errorf("enabled check %q doesn't exist, see %s's documentation", name, goCriticName) } } for _, name := range s.DisabledChecks { - if !allowedNames[strings.ToLower(name)] { - return fmt.Errorf("disabled checker %s doesn't exist, all existing checkers: %s", - name, sprintAllowedCheckerNames(allowedNames)) + if !s.allChecks.has(name) { + return fmt.Errorf("disabled check %q doesn't exist, see %s documentation", name, goCriticName) } } - for checkName := range s.SettingsPerCheck { - if _, ok := allowedNames[checkName]; !ok { - return fmt.Errorf("invalid setting, checker %s doesn't exist, all existing checkers: %s", - checkName, sprintAllowedCheckerNames(allowedNames)) + for name := range s.SettingsPerCheck { + lcName := strings.ToLower(name) + if !s.allChecksLowerCased.has(lcName) { + return fmt.Errorf("invalid check settings: check %q doesn't exist, see %s documentation", name, goCriticName) } - - if !s.isCheckEnabled(checkName) { - s.logger.Warnf("%s: settings were provided for not enabled check %q", goCriticName, checkName) + if !s.inferredEnabledChecksLowerCased.has(lcName) { + s.logger.Warnf("%s: settings were provided for disabled check %q", goCriticName, name) } } return nil } -func (s *goCriticSettingsWrapper) getLowerCasedParams() map[string]config.GoCriticCheckSettings { - ret := make(map[string]config.GoCriticCheckSettings, len(s.SettingsPerCheck)) - - for checker, params := range s.SettingsPerCheck { - ret[strings.ToLower(checker)] = params - } - - return ret -} - -func (s *goCriticSettingsWrapper) filterByDisableTags(enabledChecks, disableTags []string) []string { - enabledChecksSet := stringsSliceToSet(enabledChecks) - - for _, enabledCheck := range enabledChecks { - checkInfo, checkInfoExists := s.allCheckerMap[enabledCheck] - if !checkInfoExists { - s.logger.Warnf("%s: check %q was not exists via filtering disabled tags", goCriticName, enabledCheck) - continue +func (s *goCriticSettingsWrapper) validateDisabledAndEnabledAtOneMoment() error { + for _, tag := range s.DisabledTags { + if slices.Contains(s.EnabledTags, tag) { + return fmt.Errorf("tag %q disabled and enabled at one moment", tag) } - - hitTags := intersectStringSlice(checkInfo.Tags, disableTags) - if len(hitTags) != 0 { - delete(enabledChecksSet, enabledCheck) - } - } - - debugChecksListf(enabledChecks, "Disabled by config tags %s", sprintStrings(disableTags)) - - enabledChecks = nil - for enabledCheck := range enabledChecksSet { - enabledChecks = append(enabledChecks, enabledCheck) } - return enabledChecks -} - -func (s *goCriticSettingsWrapper) isEnabledByDefaultCheck(info *gocriticlinter.CheckerInfo) bool { - return !info.HasTag("experimental") && - !info.HasTag("opinionated") && - !info.HasTag("performance") -} - -func validateStringsUniq(ss []string) error { - set := map[string]bool{} - - for _, s := range ss { - _, ok := set[s] - if ok { - return fmt.Errorf("%q occurs multiple times in list", s) + for _, check := range s.DisabledChecks { + if slices.Contains(s.EnabledChecks, check) { + return fmt.Errorf("check %q disabled and enabled at one moment", check) } - set[s] = true } return nil } -func intersectStringSlice(s1, s2 []string) []string { - s1Map := make(map[string]struct{}, len(s1)) - - for _, s := range s1 { - s1Map[s] = struct{}{} +func (s *goCriticSettingsWrapper) validateAtLeastOneCheckerEnabled() error { + if len(s.inferredEnabledChecks) == 0 { + return errors.New("eventually all checks were disabled: at least one must be enabled") } - - results := make([]string, 0) - for _, s := range s2 { - if _, exists := s1Map[s]; exists { - results = append(results, s) - } - } - - return results + return nil } -func sprintAllowedCheckerNames(allowedNames map[string]bool) string { - namesSlice := maps.Keys(allowedNames) - return sprintStrings(namesSlice) +func normalizeMap[ValueT any](in map[string]ValueT) map[string]ValueT { + ret := make(map[string]ValueT, len(in)) + for k, v := range in { + ret[strings.ToLower(k)] = v + } + return ret } -func sprintStrings(ss []string) string { - sort.Strings(ss) - return fmt.Sprint(ss) +func isEnabledByDefaultGoCriticChecker(info *gocriticlinter.CheckerInfo) bool { + // https://github.com/go-critic/go-critic/blob/5b67cfd487ae9fe058b4b19321901b3131810f65/cmd/gocritic/check.go#L342-L345 + return !info.HasTag(gocriticlinter.ExperimentalTag) && + !info.HasTag(gocriticlinter.OpinionatedTag) && + !info.HasTag(gocriticlinter.PerformanceTag) && + !info.HasTag(gocriticlinter.SecurityTag) } func debugChecksListf(checks []string, format string, args ...any) { @@ -617,14 +580,10 @@ func debugChecksListf(checks []string, format string, args ...any) { return } - goCriticDebugf("%s checks (%d): %s", fmt.Sprintf(format, args...), len(checks), sprintStrings(checks)) + goCriticDebugf("%s checks (%d): %s", fmt.Sprintf(format, args...), len(checks), sprintSortedStrings(checks)) } -func stringsSliceToSet(ss []string) map[string]bool { - ret := make(map[string]bool, len(ss)) - for _, s := range ss { - ret[s] = true - } - - return ret +func sprintSortedStrings(v []string) string { + sort.Strings(slices.Clone(v)) + return fmt.Sprint(v) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/gocyclo.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/gocyclo.go index b502623ba6..63aefb09fe 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/gocyclo.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/gocyclo.go @@ -15,7 +15,6 @@ import ( const gocycloName = "gocyclo" -//nolint:dupl func NewGocyclo(settings *config.GoCycloSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/godot.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/godot.go index b0ee644349..06c160fec6 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/godot.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/godot.go @@ -30,7 +30,7 @@ func NewGodot(settings *config.GodotSettings) *goanalysis.Linter { // Convert deprecated setting // todo(butuzov): remove on v2 release - if settings.CheckAll { //nolint:staticcheck // Keep for retro-compatibility. + if settings.CheckAll { dotSettings.Scope = godot.AllScope } } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/godox.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/godox.go index 955810417d..208bade576 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/godox.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/godox.go @@ -16,7 +16,6 @@ import ( const godoxName = "godox" -//nolint:dupl func NewGodox(settings *config.GodoxSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/gofmt_common.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/gofmt_common.go index 6b7184d65b..afa7afbf89 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/gofmt_common.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/gofmt_common.go @@ -133,8 +133,8 @@ func (p *hunkChangesParser) handleDeletedLines(deletedLines []diffLine, addedLin } if len(addedLines) != 0 { - //nolint:gocritic - change.Replacement.NewLines = append(p.replacementLinesToPrepend, addedLines...) + change.Replacement.NewLines = append([]string{}, p.replacementLinesToPrepend...) + change.Replacement.NewLines = append(change.Replacement.NewLines, addedLines...) if len(p.replacementLinesToPrepend) != 0 { p.replacementLinesToPrepend = nil } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goheader.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goheader.go index d3cfefa90b..5ed5d9d8de 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goheader.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goheader.go @@ -97,6 +97,17 @@ func runGoHeader(pass *analysis.Pass, conf *goheader.Configuration) ([]goanalysi FromLinter: goHeaderName, } + if fix := i.Fix(); fix != nil { + issue.LineRange = &result.Range{ + From: issue.Line(), + To: issue.Line() + len(fix.Actual) - 1, + } + issue.Replacement = &result.Replacement{ + NeedOnlyDelete: len(fix.Expected) == 0, + NewLines: fix.Expected, + } + } + issues = append(issues, goanalysis.NewIssue(&issue, pass)) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/golint.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/golint.go deleted file mode 100644 index 22ca590481..0000000000 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/golint.go +++ /dev/null @@ -1,84 +0,0 @@ -package golinters - -import ( - "fmt" - "sync" - - lintAPI "github.com/golangci/lint-1" - "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 golintName = "golint" - -//nolint:dupl -func NewGolint(settings *config.GoLintSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - - analyzer := &analysis.Analyzer{ - Name: golintName, - Doc: goanalysis.TheOnlyanalyzerDoc, - Run: func(pass *analysis.Pass) (any, error) { - issues, err := runGoLint(pass, settings) - if err != nil { - return nil, err - } - - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() - - return nil, nil - }, - } - - return goanalysis.NewLinter( - golintName, - "Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes", - []*analysis.Analyzer{analyzer}, - nil, - ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeTypesInfo) -} - -func runGoLint(pass *analysis.Pass, settings *config.GoLintSettings) ([]goanalysis.Issue, error) { - l := new(lintAPI.Linter) - - ps, err := l.LintPkg(pass.Files, pass.Fset, pass.Pkg, pass.TypesInfo) - if err != nil { - return nil, fmt.Errorf("can't lint %d files: %w", len(pass.Files), err) - } - - if len(ps) == 0 { - return nil, nil - } - - lintIssues := make([]*result.Issue, 0, len(ps)) // This is worst case - for idx := range ps { - if ps[idx].Confidence >= settings.MinConfidence { - lintIssues = append(lintIssues, &result.Issue{ - Pos: ps[idx].Position, - Text: ps[idx].Text, - FromLinter: golintName, - }) - // TODO: use p.Link and p.Category - } - } - - issues := make([]goanalysis.Issue, 0, len(lintIssues)) - for _, issue := range lintIssues { - issues = append(issues, goanalysis.NewIssue(issue, pass)) - } - - return issues, nil -} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/gosec.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/gosec.go index 235f0e9141..0c09c59672 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/gosec.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/gosec.go @@ -97,7 +97,7 @@ func runGoSec(lintCtx *linter.Context, pass *analysis.Pass, settings *config.GoS issues := make([]goanalysis.Issue, 0, len(secIssues)) for _, i := range secIssues { - text := fmt.Sprintf("%s: %s", i.RuleID, i.What) // TODO: use severity and confidence + text := fmt.Sprintf("%s: %s", i.RuleID, i.What) var r *result.Range @@ -118,6 +118,7 @@ func runGoSec(lintCtx *linter.Context, pass *analysis.Pass, settings *config.GoS } issues = append(issues, goanalysis.NewIssue(&result.Issue{ + Severity: convertScoreToString(i.Severity), Pos: token.Position{ Filename: i.File, Line: line, @@ -149,6 +150,19 @@ func toGosecConfig(settings *config.GoSecSettings) gosec.Config { return conf } +func convertScoreToString(score issue.Score) string { + switch score { + case issue.Low: + return "low" + case issue.Medium: + return "medium" + case issue.High: + return "high" + default: + return "" + } +} + // based on https://github.com/securego/gosec/blob/47bfd4eb6fc7395940933388550b547538b4c946/config.go#L52-L62 func convertGosecGlobals(globalOptionFromConfig any, conf gosec.Config) { globalOptionMap, ok := globalOptionFromConfig.(map[string]any) 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 a0d33835df..ecd406e3bf 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/govet.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/govet.go @@ -2,6 +2,7 @@ package golinters import ( "slices" + "sort" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/appends" @@ -52,6 +53,7 @@ import ( "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" + "github.com/golangci/golangci-lint/pkg/logutils" ) var ( @@ -136,6 +138,11 @@ var ( } ) +var ( + govetDebugf = logutils.Debug(logutils.DebugKeyGovet) + isGovetDebug = logutils.HaveDebugTag(logutils.DebugKeyGovet) +) + func NewGovet(settings *config.GovetSettings) *goanalysis.Linter { var conf map[string]map[string]any if settings != nil { @@ -144,23 +151,21 @@ func NewGovet(settings *config.GovetSettings) *goanalysis.Linter { return goanalysis.NewLinter( "govet", - "Vet examines Go source code and reports suspicious constructs, "+ - "such as Printf calls whose arguments do not align with the format string", + "Vet examines Go source code and reports suspicious constructs. "+ + "It is roughly the same as 'go vet' and uses its passes.", analyzersFromConfig(settings), conf, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } func analyzersFromConfig(settings *config.GovetSettings) []*analysis.Analyzer { + debugAnalyzersListf(allAnalyzers, "All available analyzers") + debugAnalyzersListf(defaultAnalyzers, "Default analyzers") + if settings == nil { return defaultAnalyzers } - if settings.CheckShadowing { - // Keeping for backward compatibility. - settings.Enable = append(settings.Enable, shadow.Analyzer.Name) - } - var enabledAnalyzers []*analysis.Analyzer for _, a := range allAnalyzers { if isAnalyzerEnabled(a.Name, settings, defaultAnalyzers) { @@ -168,15 +173,28 @@ func analyzersFromConfig(settings *config.GovetSettings) []*analysis.Analyzer { } } + debugAnalyzersListf(enabledAnalyzers, "Enabled by config analyzers") + return enabledAnalyzers } func isAnalyzerEnabled(name string, cfg *config.GovetSettings, defaultAnalyzers []*analysis.Analyzer) bool { // TODO(ldez) remove loopclosure when go1.23 - if name == loopclosure.Analyzer.Name && config.IsGreaterThanOrEqualGo122(cfg.Go) { + if name == loopclosure.Analyzer.Name && config.IsGoGreaterThanOrEqual(cfg.Go, "1.22") { + return false + } + + // TODO(ldez) re-enable httpresponse once https://github.com/golangci/golangci-lint/issues/4482 is fixed. + if name == httpresponse.Analyzer.Name { + govetDebugf("httpresponse is disabled due to panic. See https://github.com/golang/go/issues/66259") return false } + // Keeping for backward compatibility. + if cfg.CheckShadowing && name == shadow.Analyzer.Name { + return true + } + switch { case cfg.EnableAll: return !slices.Contains(cfg.Disable, name) @@ -194,3 +212,18 @@ func isAnalyzerEnabled(name string, cfg *config.GovetSettings, defaultAnalyzers return slices.ContainsFunc(defaultAnalyzers, func(a *analysis.Analyzer) bool { return a.Name == name }) } } + +func debugAnalyzersListf(analyzers []*analysis.Analyzer, message string) { + if !isGovetDebug { + return + } + + analyzerNames := make([]string, 0, len(analyzers)) + for _, a := range analyzers { + analyzerNames = append(analyzerNames, a.Name) + } + + sort.Strings(analyzerNames) + + govetDebugf("%s (%d): %s", message, len(analyzerNames), analyzerNames) +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/importas.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/importas.go index b06aec7a3b..a426333dbf 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/importas.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/importas.go @@ -5,7 +5,7 @@ import ( "strconv" "strings" - "github.com/julz/importas" //nolint:misspell + "github.com/julz/importas" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" @@ -26,7 +26,7 @@ func NewImportAs(settings *config.ImportAsSettings) *goanalysis.Linter { return } if len(settings.Alias) == 0 { - lintCtx.Log.Infof("importas settings found, but no aliases listed. List aliases under alias: key.") //nolint:misspell + lintCtx.Log.Infof("importas settings found, but no aliases listed. List aliases under alias: key.") } if err := analyzer.Flags.Set("no-unaliased", strconv.FormatBool(settings.NoUnaliased)); err != nil { diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/interfacer.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/interfacer.go deleted file mode 100644 index 71bdfddbe8..0000000000 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/interfacer.go +++ /dev/null @@ -1,82 +0,0 @@ -package golinters - -import ( - "sync" - - "golang.org/x/tools/go/analysis" - "golang.org/x/tools/go/analysis/passes/buildssa" - "mvdan.cc/interfacer/check" - - "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" - "github.com/golangci/golangci-lint/pkg/lint/linter" - "github.com/golangci/golangci-lint/pkg/result" -) - -const interfacerName = "interfacer" - -func NewInterfacer() *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - - analyzer := &analysis.Analyzer{ - Name: interfacerName, - Doc: goanalysis.TheOnlyanalyzerDoc, - Requires: []*analysis.Analyzer{buildssa.Analyzer}, - Run: func(pass *analysis.Pass) (any, error) { - issues, err := runInterfacer(pass) - if err != nil { - return nil, err - } - - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() - - return nil, nil - }, - } - - return goanalysis.NewLinter( - interfacerName, - "Linter that suggests narrower interface types", - []*analysis.Analyzer{analyzer}, - nil, - ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeTypesInfo) -} - -func runInterfacer(pass *analysis.Pass) ([]goanalysis.Issue, error) { - c := &check.Checker{} - - prog := goanalysis.MakeFakeLoaderProgram(pass) - c.Program(prog) - - ssa := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA) - ssaPkg := ssa.Pkg - c.ProgramSSA(ssaPkg.Prog) - - lintIssues, err := c.Check() - if err != nil { - return nil, err - } - if len(lintIssues) == 0 { - return nil, nil - } - - issues := make([]goanalysis.Issue, 0, len(lintIssues)) - for _, i := range lintIssues { - pos := pass.Fset.Position(i.Pos()) - issues = append(issues, goanalysis.NewIssue(&result.Issue{ - Pos: pos, - Text: i.Message(), - FromLinter: interfacerName, - }, pass)) - } - - return issues, nil -} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/nosnakecase.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/intrange.go similarity index 70% rename from vendor/github.com/golangci/golangci-lint/pkg/golinters/nosnakecase.go rename to vendor/github.com/golangci/golangci-lint/pkg/golinters/intrange.go index 26d5d6d4c8..8de956dc12 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/nosnakecase.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/intrange.go @@ -1,14 +1,14 @@ package golinters import ( - "github.com/sivchari/nosnakecase" + "github.com/ckaznocha/intrange" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" ) -func NewNoSnakeCase() *goanalysis.Linter { - a := nosnakecase.Analyzer +func NewIntrange() *goanalysis.Linter { + a := intrange.Analyzer return goanalysis.NewLinter( a.Name, 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 61498087a9..84ce61a9ee 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/lll.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/lll.go @@ -22,7 +22,6 @@ const lllName = "lll" const goCommentDirectivePrefix = "//go:" -//nolint:dupl func NewLLL(settings *config.LllSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/makezero.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/makezero.go index a9828629a2..7a14c8e09d 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/makezero.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/makezero.go @@ -15,7 +15,6 @@ import ( const makezeroName = "makezero" -//nolint:dupl func NewMakezero(settings *config.MakezeroSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/maligned.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/maligned.go deleted file mode 100644 index 0455be76aa..0000000000 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/maligned.go +++ /dev/null @@ -1,74 +0,0 @@ -package golinters - -import ( - "fmt" - "sync" - - malignedAPI "github.com/golangci/maligned" - "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 malignedName = "maligned" - -//nolint:dupl -func NewMaligned(settings *config.MalignedSettings) *goanalysis.Linter { - var mu sync.Mutex - var res []goanalysis.Issue - - analyzer := &analysis.Analyzer{ - Name: malignedName, - Doc: goanalysis.TheOnlyanalyzerDoc, - Run: func(pass *analysis.Pass) (any, error) { - issues := runMaligned(pass, settings) - - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - res = append(res, issues...) - mu.Unlock() - - return nil, nil - }, - } - - return goanalysis.NewLinter( - malignedName, - "Tool to detect Go structs that would take less memory if their fields were sorted", - []*analysis.Analyzer{analyzer}, - nil, - ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return res - }).WithLoadMode(goanalysis.LoadModeTypesInfo) -} - -func runMaligned(pass *analysis.Pass, settings *config.MalignedSettings) []goanalysis.Issue { - prog := goanalysis.MakeFakeLoaderProgram(pass) - - malignedIssues := malignedAPI.Run(prog) - if len(malignedIssues) == 0 { - return nil - } - - issues := make([]goanalysis.Issue, 0, len(malignedIssues)) - for _, i := range malignedIssues { - text := fmt.Sprintf("struct of size %d bytes could be of size %d bytes", i.OldSize, i.NewSize) - if settings.SuggestNewOrder { - text += fmt.Sprintf(":\n%s", formatCodeBlock(i.NewStructDef, nil)) - } - - issues = append(issues, goanalysis.NewIssue(&result.Issue{ - Pos: i.Pos, - Text: text, - FromLinter: malignedName, - }, pass)) - } - - return issues -} 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 0f69cdb870..8a97534c58 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/misspell.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/misspell.go @@ -5,6 +5,7 @@ import ( "go/token" "strings" "sync" + "unicode" "github.com/golangci/misspell" "golang.org/x/tools/go/analysis" @@ -95,6 +96,11 @@ func createMisspellReplacer(settings *config.MisspellSettings) (*misspell.Replac return nil, fmt.Errorf("unknown locale: %q", settings.Locale) } + err := appendExtraWords(replacer, settings.ExtraWords) + if err != nil { + return nil, fmt.Errorf("process extra words: %w", err) + } + if len(settings.IgnoreWords) != 0 { replacer.RemoveRule(settings.IgnoreWords) } @@ -153,3 +159,30 @@ func runMisspellOnFile(lintCtx *linter.Context, filename string, replacer *missp return res, nil } + +func appendExtraWords(replacer *misspell.Replacer, extraWords []config.MisspellExtraWords) error { + if len(extraWords) == 0 { + return nil + } + + extra := make([]string, 0, len(extraWords)*2) + + for _, word := range extraWords { + if word.Typo == "" || word.Correction == "" { + return fmt.Errorf("typo (%q) and correction (%q) fields should not be empty", word.Typo, word.Correction) + } + + if strings.ContainsFunc(word.Typo, func(r rune) bool { return !unicode.IsLetter(r) }) { + return fmt.Errorf("the word %q in the 'typo' field should only contain letters", word.Typo) + } + if strings.ContainsFunc(word.Correction, func(r rune) bool { return !unicode.IsLetter(r) }) { + return fmt.Errorf("the word %q in the 'correction' field should only contain letters", word.Correction) + } + + extra = append(extra, strings.ToLower(word.Typo), strings.ToLower(word.Correction)) + } + + replacer.AddRuleList(extra) + + return nil +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/nestif.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/nestif.go index 12ad69eceb..4125ad9afc 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/nestif.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/nestif.go @@ -15,7 +15,6 @@ import ( const nestifName = "nestif" -//nolint:dupl func NewNestif(settings *config.NestifSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue 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 ae372ab795..7000620d19 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/nolintlint.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/nolintlint.go @@ -16,7 +16,6 @@ import ( const NoLintLintName = "nolintlint" -//nolint:dupl func NewNoLintLint(settings *config.NoLintLintSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []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 a245561a08..1bce5ef5d2 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 @@ -107,8 +107,8 @@ func (i UnusedCandidate) Details() string { func (i UnusedCandidate) String() string { return toString(i) } -func toString(i Issue) string { - return fmt.Sprintf("%s at %s", i.Details(), i.Position()) +func toString(issue Issue) string { + return fmt.Sprintf("%s at %s", issue.Details(), issue.Position()) } type Issue interface { @@ -151,10 +151,12 @@ func NewLinter(needs Needs, excludes []string) (*Linter, error) { }, nil } -var leadingSpacePattern = regexp.MustCompile(`^//(\s*)`) -var trailingBlankExplanation = regexp.MustCompile(`\s*(//\s*)?$`) +var ( + leadingSpacePattern = regexp.MustCompile(`^//(\s*)`) + trailingBlankExplanation = regexp.MustCompile(`\s*(//\s*)?$`) +) -//nolint:funlen,gocyclo +//nolint:funlen,gocyclo // the function is going to be refactored in the future func (l Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) { var issues []Issue 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 c49f74aa2f..8215619c73 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/paralleltest.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/paralleltest.go @@ -13,12 +13,16 @@ func NewParallelTest(settings *config.ParallelTestSettings) *goanalysis.Linter { var cfg map[string]map[string]any if settings != nil { - cfg = map[string]map[string]any{ - a.Name: { - "i": settings.IgnoreMissing, - "ignoremissingsubtests": settings.IgnoreMissingSubtests, - }, + d := map[string]any{ + "i": settings.IgnoreMissing, + "ignoremissingsubtests": settings.IgnoreMissingSubtests, } + + if config.IsGoGreaterThanOrEqual(settings.Go, "1.22") { + d["ignoreloopVar"] = true + } + + cfg = map[string]map[string]any{a.Name: d} } return goanalysis.NewLinter( 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 acaa3a5225..6fe315fdea 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/perfsprint.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/perfsprint.go @@ -20,6 +20,7 @@ func NewPerfSprint(settings *config.PerfSprintSettings) *goanalysis.Linter { cfg[a.Name]["err-error"] = settings.ErrError cfg[a.Name]["errorf"] = settings.ErrorF cfg[a.Name]["sprintf1"] = settings.SprintF1 + cfg[a.Name]["strconcat"] = settings.StrConcat } return goanalysis.NewLinter( diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/prealloc.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/prealloc.go index f48d57562e..4777614939 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/prealloc.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/prealloc.go @@ -15,7 +15,6 @@ import ( const preallocName = "prealloc" -//nolint:dupl func NewPreAlloc(settings *config.PreallocSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue 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 46d8b1c9ca..fafdedd5d2 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/revive.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/revive.go @@ -160,7 +160,8 @@ func reviveToIssue(pass *analysis.Pass, object *jsonObject) goanalysis.Issue { // This function mimics the GetConfig function of revive. // This allows to get default values and right types. // https://github.com/golangci/golangci-lint/issues/1745 -// https://github.com/mgechev/revive/blob/v1.1.4/config/config.go#L182 +// https://github.com/mgechev/revive/blob/v1.3.7/config/config.go#L217 +// https://github.com/mgechev/revive/blob/v1.3.7/config/config.go#L169-L174 func getReviveConfig(cfg *config.ReviveSettings) (*lint.Config, error) { conf := defaultConfig() @@ -182,6 +183,14 @@ func getReviveConfig(cfg *config.ReviveSettings) (*lint.Config, error) { normalizeConfig(conf) + for k, r := range conf.Rules { + err := r.Initialize() + if err != nil { + return nil, fmt.Errorf("error in config of rule %q: %w", k, err) + } + conf.Rules[k] = r + } + reviveDebugf("revive configuration: %#v", conf) return conf, nil @@ -214,6 +223,7 @@ func createConfigMap(cfg *config.ReviveSettings) map[string]any { "severity": s.Severity, "arguments": safeTomlSlice(s.Arguments), "disabled": s.Disabled, + "exclude": s.Exclude, } } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/scopelint.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/scopelint.go deleted file mode 100644 index e6ef15ede0..0000000000 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/scopelint.go +++ /dev/null @@ -1,190 +0,0 @@ -package golinters - -import ( - "fmt" - "go/ast" - "go/token" - "sync" - - "golang.org/x/tools/go/analysis" - - "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" - "github.com/golangci/golangci-lint/pkg/lint/linter" - "github.com/golangci/golangci-lint/pkg/result" -) - -const scopelintName = "scopelint" - -//nolint:dupl -func NewScopelint() *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - - analyzer := &analysis.Analyzer{ - Name: scopelintName, - Doc: goanalysis.TheOnlyanalyzerDoc, - Run: func(pass *analysis.Pass) (any, error) { - issues := runScopeLint(pass) - - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() - - return nil, nil - }, - } - - return goanalysis.NewLinter( - scopelintName, - "Scopelint checks for unpinned variables in go programs", - []*analysis.Analyzer{analyzer}, - nil, - ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeSyntax) -} - -func runScopeLint(pass *analysis.Pass) []goanalysis.Issue { - var lintIssues []result.Issue - - for _, file := range pass.Files { - n := Node{ - fset: pass.Fset, - DangerObjects: map[*ast.Object]int{}, - UnsafeObjects: map[*ast.Object]int{}, - SkipFuncs: map[*ast.FuncLit]int{}, - issues: &lintIssues, - } - ast.Walk(&n, file) - } - - var issues []goanalysis.Issue - for i := range lintIssues { - issues = append(issues, goanalysis.NewIssue(&lintIssues[i], pass)) - } - - return issues -} - -// The code below is copy-pasted from https://github.com/kyoh86/scopelint 92cbe2cc9276abda0e309f52cc9e309d407f174e - -// Node represents a Node being linted. -type Node struct { - fset *token.FileSet - DangerObjects map[*ast.Object]int - UnsafeObjects map[*ast.Object]int - SkipFuncs map[*ast.FuncLit]int - issues *[]result.Issue -} - -// Visit method is invoked for each node encountered by Walk. -// If the result visitor w is not nil, Walk visits each of the children -// of node with the visitor w, followed by a call of w.Visit(nil). -// -//nolint:gocyclo,gocritic -func (f *Node) Visit(node ast.Node) ast.Visitor { - switch typedNode := node.(type) { - case *ast.ForStmt: - switch init := typedNode.Init.(type) { - case *ast.AssignStmt: - for _, lh := range init.Lhs { - switch tlh := lh.(type) { - case *ast.Ident: - f.UnsafeObjects[tlh.Obj] = 0 - } - } - } - - case *ast.RangeStmt: - // Memory variables declared in range statement - switch k := typedNode.Key.(type) { - case *ast.Ident: - f.UnsafeObjects[k.Obj] = 0 - } - switch v := typedNode.Value.(type) { - case *ast.Ident: - f.UnsafeObjects[v.Obj] = 0 - } - - case *ast.UnaryExpr: - if typedNode.Op == token.AND { - switch ident := typedNode.X.(type) { - case *ast.Ident: - if _, unsafe := f.UnsafeObjects[ident.Obj]; unsafe { - f.errorf(ident, "Using a reference for the variable on range scope %s", formatCode(ident.Name, nil)) - } - } - } - - case *ast.Ident: - if _, obj := f.DangerObjects[typedNode.Obj]; obj { - // It is the naked variable in scope of range statement. - f.errorf(node, "Using the variable on range scope %s in function literal", formatCode(typedNode.Name, nil)) - break - } - - case *ast.CallExpr: - // Ignore func literals that'll be called immediately. - switch funcLit := typedNode.Fun.(type) { - case *ast.FuncLit: - f.SkipFuncs[funcLit] = 0 - } - - case *ast.FuncLit: - if _, skip := f.SkipFuncs[typedNode]; !skip { - dangers := map[*ast.Object]int{} - for d := range f.DangerObjects { - dangers[d] = 0 - } - for u := range f.UnsafeObjects { - dangers[u] = 0 - f.UnsafeObjects[u]++ - } - return &Node{ - fset: f.fset, - DangerObjects: dangers, - UnsafeObjects: f.UnsafeObjects, - SkipFuncs: f.SkipFuncs, - issues: f.issues, - } - } - - case *ast.ReturnStmt: - unsafe := map[*ast.Object]int{} - for u := range f.UnsafeObjects { - if f.UnsafeObjects[u] == 0 { - continue - } - unsafe[u] = f.UnsafeObjects[u] - } - return &Node{ - fset: f.fset, - DangerObjects: f.DangerObjects, - UnsafeObjects: unsafe, - SkipFuncs: f.SkipFuncs, - issues: f.issues, - } - } - return f -} - -// The variadic arguments may start with link and category types, -// and must end with a format string and any arguments. -// -//nolint:interfacer -func (f *Node) errorf(n ast.Node, format string, args ...any) { - pos := f.fset.Position(n.Pos()) - f.errorAtf(pos, format, args...) -} - -func (f *Node) errorAtf(pos token.Position, format string, args ...any) { - *f.issues = append(*f.issues, result.Issue{ - Pos: pos, - Text: fmt.Sprintf(format, args...), - FromLinter: scopelintName, - }) -} 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 acea90d536..67977453e4 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/sloglint.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/sloglint.go @@ -14,6 +14,7 @@ func NewSlogLint(settings *config.SlogLintSettings) *goanalysis.Linter { opts = &sloglint.Options{ NoMixedArgs: settings.NoMixedArgs, KVOnly: settings.KVOnly, + NoGlobal: settings.NoGlobal, AttrOnly: settings.AttrOnly, ContextOnly: settings.ContextOnly, StaticMsg: settings.StaticMsg, diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/structcheck.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/structcheck.go deleted file mode 100644 index f3df0c2f35..0000000000 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/structcheck.go +++ /dev/null @@ -1,71 +0,0 @@ -package golinters - -import ( - "fmt" - "sync" - - structcheckAPI "github.com/golangci/check/cmd/structcheck" - "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 structcheckName = "structcheck" - -//nolint:dupl -func NewStructcheck(settings *config.StructCheckSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - - analyzer := &analysis.Analyzer{ - Name: structcheckName, - Doc: goanalysis.TheOnlyanalyzerDoc, - Run: func(pass *analysis.Pass) (any, error) { - issues := runStructCheck(pass, settings) - - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() - - return nil, nil - }, - } - - return goanalysis.NewLinter( - structcheckName, - "Finds unused struct fields", - []*analysis.Analyzer{analyzer}, - nil, - ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeTypesInfo) -} - -//nolint:dupl -func runStructCheck(pass *analysis.Pass, settings *config.StructCheckSettings) []goanalysis.Issue { - prog := goanalysis.MakeFakeLoaderProgram(pass) - - lintIssues := structcheckAPI.Run(prog, settings.CheckExportedFields) - if len(lintIssues) == 0 { - return nil - } - - issues := make([]goanalysis.Issue, 0, len(lintIssues)) - - for _, i := range lintIssues { - issues = append(issues, goanalysis.NewIssue(&result.Issue{ - Pos: i.Pos, - Text: fmt.Sprintf("%s is unused", formatCode(i.FieldName, nil)), - FromLinter: structcheckName, - }, pass)) - } - - return issues -} 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 d9cfc04f6e..32b194f00f 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/testifylint.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/testifylint.go @@ -16,6 +16,8 @@ func NewTestifylint(settings *config.TestifylintSettings) *goanalysis.Linter { cfg[a.Name] = map[string]any{ "enable-all": settings.EnableAll, "disable-all": settings.DisableAll, + + "bool-compare.ignore-custom-types": settings.BoolCompare.IgnoreCustomTypes, } if len(settings.EnabledCheckers) > 0 { cfg[a.Name]["enable"] = settings.EnabledCheckers diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/testpackage.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/testpackage.go index db1ead9661..b8a867eb60 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/testpackage.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/testpackage.go @@ -11,7 +11,7 @@ import ( ) func NewTestpackage(cfg *config.TestpackageSettings) *goanalysis.Linter { - var a = testpackage.NewAnalyzer() + a := testpackage.NewAnalyzer() var settings map[string]map[string]any if cfg != nil { diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/unconvert.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/unconvert.go index aad858dfd6..08e493aef0 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/unconvert.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/unconvert.go @@ -3,9 +3,10 @@ package golinters import ( "sync" - unconvertAPI "github.com/golangci/unconvert" + "github.com/golangci/unconvert" "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" @@ -13,8 +14,7 @@ import ( const unconvertName = "unconvert" -//nolint:dupl -func NewUnconvert() *goanalysis.Linter { +func NewUnconvert(settings *config.UnconvertSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue @@ -22,7 +22,7 @@ func NewUnconvert() *goanalysis.Linter { Name: unconvertName, Doc: goanalysis.TheOnlyanalyzerDoc, Run: func(pass *analysis.Pass) (any, error) { - issues := runUnconvert(pass) + issues := runUnconvert(pass, settings) if len(issues) == 0 { return nil, nil @@ -46,18 +46,13 @@ func NewUnconvert() *goanalysis.Linter { }).WithLoadMode(goanalysis.LoadModeTypesInfo) } -func runUnconvert(pass *analysis.Pass) []goanalysis.Issue { - prog := goanalysis.MakeFakeLoaderProgram(pass) +func runUnconvert(pass *analysis.Pass, settings *config.UnconvertSettings) []goanalysis.Issue { + positions := unconvert.Run(pass, settings.FastMath, settings.Safe) - positions := unconvertAPI.Run(prog) - if len(positions) == 0 { - return nil - } - - issues := make([]goanalysis.Issue, 0, len(positions)) - for _, pos := range positions { + var issues []goanalysis.Issue + for _, position := range positions { issues = append(issues, goanalysis.NewIssue(&result.Issue{ - Pos: pos, + Pos: position, Text: "unnecessary conversion", FromLinter: unconvertName, }, pass)) diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/util.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/util.go index 1044567a95..932cbfcf78 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/util.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/util.go @@ -18,14 +18,6 @@ func formatCode(code string, _ *config.Config) string { return fmt.Sprintf("`%s`", code) } -func formatCodeBlock(code string, _ *config.Config) string { - if strings.Contains(code, "`") { - return code // TODO: properly escape or remove - } - - return fmt.Sprintf("```\n%s\n```", code) -} - func getFileNames(pass *analysis.Pass) []string { var fileNames []string for _, f := range pass.Files { diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/varcheck.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/varcheck.go deleted file mode 100644 index ea735672f6..0000000000 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/varcheck.go +++ /dev/null @@ -1,72 +0,0 @@ -package golinters - -import ( - "fmt" - "sync" - - varcheckAPI "github.com/golangci/check/cmd/varcheck" - "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 varcheckName = "varcheck" - -func NewVarcheck(settings *config.VarCheckSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - - analyzer := &analysis.Analyzer{ - Name: varcheckName, - Doc: goanalysis.TheOnlyanalyzerDoc, - Run: goanalysis.DummyRun, - } - - return goanalysis.NewLinter( - varcheckName, - "Finds unused global variables and constants", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(_ *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (any, error) { - issues := runVarCheck(pass, settings) - - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() - - return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeTypesInfo) -} - -//nolint:dupl -func runVarCheck(pass *analysis.Pass, settings *config.VarCheckSettings) []goanalysis.Issue { - prog := goanalysis.MakeFakeLoaderProgram(pass) - - lintIssues := varcheckAPI.Run(prog, settings.CheckExportedFields) - if len(lintIssues) == 0 { - return nil - } - - issues := make([]goanalysis.Issue, 0, len(lintIssues)) - - for _, i := range lintIssues { - issues = append(issues, goanalysis.NewIssue(&result.Issue{ - Pos: i.Pos, - Text: fmt.Sprintf("%s is unused", formatCode(i.VarName, nil)), - FromLinter: varcheckName, - }, pass)) - } - - return issues -} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/goutil/env.go b/vendor/github.com/golangci/golangci-lint/pkg/goutil/env.go index 93922f85a7..7b748d8e90 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/goutil/env.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/goutil/env.go @@ -33,20 +33,23 @@ func NewEnv(log logutils.Log) *Env { } } -func (e *Env) Discover(ctx context.Context) error { +func (e Env) Discover(ctx context.Context) error { startedAt := time.Now() - args := []string{"env", "-json"} - args = append(args, string(EnvGoCache), string(EnvGoRoot)) - out, err := exec.CommandContext(ctx, "go", args...).Output() + + //nolint:gosec // Everything is static here. + cmd := exec.CommandContext(ctx, "go", "env", "-json", string(EnvGoCache), string(EnvGoRoot)) + + out, err := cmd.Output() if err != nil { - return fmt.Errorf("failed to run 'go env': %w", err) + return fmt.Errorf("failed to run '%s': %w", strings.Join(cmd.Args, " "), err) } if err = json.Unmarshal(out, &e.vars); err != nil { - return fmt.Errorf("failed to parse 'go %s' json: %w", strings.Join(args, " "), err) + return fmt.Errorf("failed to parse '%s' json: %w", strings.Join(cmd.Args, " "), err) } e.debugf("Read go env for %s: %#v", time.Since(startedAt), e.vars) + return nil } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/lint/context.go b/vendor/github.com/golangci/golangci-lint/pkg/lint/context.go new file mode 100644 index 0000000000..22c95e08af --- /dev/null +++ b/vendor/github.com/golangci/golangci-lint/pkg/lint/context.go @@ -0,0 +1,64 @@ +package lint + +import ( + "context" + "fmt" + + "github.com/golangci/golangci-lint/internal/pkgcache" + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/exitcodes" + "github.com/golangci/golangci-lint/pkg/fsutils" + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis/load" + "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/logutils" +) + +type ContextBuilder struct { + cfg *config.Config + + pkgLoader *PackageLoader + + fileCache *fsutils.FileCache + pkgCache *pkgcache.Cache + + loadGuard *load.Guard +} + +func NewContextBuilder(cfg *config.Config, pkgLoader *PackageLoader, + fileCache *fsutils.FileCache, pkgCache *pkgcache.Cache, loadGuard *load.Guard, +) *ContextBuilder { + return &ContextBuilder{ + cfg: cfg, + pkgLoader: pkgLoader, + fileCache: fileCache, + pkgCache: pkgCache, + loadGuard: loadGuard, + } +} + +func (cl *ContextBuilder) Build(ctx context.Context, log logutils.Log, linters []*linter.Config) (*linter.Context, error) { + pkgs, deduplicatedPkgs, err := cl.pkgLoader.Load(ctx, linters) + if err != nil { + return nil, fmt.Errorf("failed to load packages: %w", err) + } + + if len(deduplicatedPkgs) == 0 { + return nil, fmt.Errorf("%w: running `go mod tidy` may solve the problem", exitcodes.ErrNoGoFiles) + } + + ret := &linter.Context{ + Packages: deduplicatedPkgs, + + // At least `unused` linters works properly only on original (not deduplicated) packages, + // see https://github.com/golangci/golangci-lint/pull/585. + OriginalPackages: pkgs, + + Cfg: cl.cfg, + Log: log, + FileCache: cl.fileCache, + PkgCache: cl.pkgCache, + LoadGuard: cl.loadGuard, + } + + return ret, nil +} 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 ed5e5508ce..8e57d6bdf6 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 @@ -1,7 +1,8 @@ package linter import ( - "golang.org/x/tools/go/analysis" + "fmt" + "golang.org/x/tools/go/packages" "github.com/golangci/golangci-lint/pkg/config" @@ -133,23 +134,27 @@ func (lc *Config) Name() string { return lc.Linter.Name() } -func (lc *Config) WithNoopFallback(cfg *config.Config) *Config { - if cfg != nil && config.IsGreaterThanOrEqualGo122(cfg.Run.Go) { - lc.Linter = &Noop{ - name: lc.Linter.Name(), - desc: lc.Linter.Desc(), - run: func(_ *analysis.Pass) (any, error) { - return nil, nil - }, - } - +func (lc *Config) WithNoopFallback(cfg *config.Config, cond func(cfg *config.Config) error) *Config { + if err := cond(cfg); err != nil { + lc.Linter = NewNoop(lc.Linter, err.Error()) lc.LoadMode = 0 + return lc.WithLoadFiles() } return lc } +func IsGoLowerThanGo122() func(cfg *config.Config) error { + return func(cfg *config.Config) error { + if cfg == nil || config.IsGoGreaterThanOrEqual(cfg.Run.Go, "1.22") { + return nil + } + + return fmt.Errorf("this linter is disabled because the Go version (%s) of your project is lower than Go 1.22", cfg.Run.Go) + } +} + func NewConfig(linter Linter) *Config { lc := &Config{ Linter: linter, diff --git a/vendor/github.com/golangci/golangci-lint/pkg/lint/linter/context.go b/vendor/github.com/golangci/golangci-lint/pkg/lint/linter/context.go index a9f9d7d7f2..81d1560f7d 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/lint/linter/context.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/lint/linter/context.go @@ -22,7 +22,6 @@ type Context struct { Cfg *config.Config FileCache *fsutils.FileCache - LineCache *fsutils.LineCache Log logutils.Log PkgCache *pkgcache.Cache diff --git a/vendor/github.com/golangci/golangci-lint/pkg/lint/linter/linter.go b/vendor/github.com/golangci/golangci-lint/pkg/lint/linter/linter.go index a65d6b9278..1d4e7b04cc 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/lint/linter/linter.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/lint/linter/linter.go @@ -3,8 +3,7 @@ package linter import ( "context" - "golang.org/x/tools/go/analysis" - + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/result" ) @@ -15,14 +14,37 @@ type Linter interface { } type Noop struct { - name string - desc string - run func(pass *analysis.Pass) (any, error) + name string + desc string + reason string +} + +func NewNoop(l Linter, reason string) Noop { + return Noop{ + name: l.Name(), + desc: l.Desc(), + reason: reason, + } +} + +func NewNoopDeprecated(name string, cfg *config.Config) Noop { + noop := Noop{ + name: name, + desc: "Deprecated", + reason: "This linter is fully inactivated: it will not produce any reports.", + } + + if cfg.InternalCmdTest { + noop.reason = "" + } + + return noop } func (n Noop) Run(_ context.Context, lintCtx *Context) ([]result.Issue, error) { - lintCtx.Log.Warnf("%s is disabled because of generics."+ - " You can track the evolution of the generics support by following the https://github.com/golangci/golangci-lint/issues/2649.", n.name) + if n.reason != "" { + lintCtx.Log.Warnf("%s: %s", n.name, n.reason) + } return nil, nil } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/builder_linter.go b/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/builder_linter.go new file mode 100644 index 0000000000..08041dc58f --- /dev/null +++ b/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/builder_linter.go @@ -0,0 +1,711 @@ +package lintersdb + +import ( + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/golinters" + "github.com/golangci/golangci-lint/pkg/lint/linter" +) + +// LinterBuilder builds the "internal" linters based on the configuration. +type LinterBuilder struct{} + +// NewLinterBuilder creates a new LinterBuilder. +func NewLinterBuilder() *LinterBuilder { + return &LinterBuilder{} +} + +// Build loads all the "internal" linters. +// The configuration is use for the linter settings. +func (b LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { + if cfg == nil { + return nil, nil + } + + const megacheckName = "megacheck" + + // The linters are sorted in the alphabetical order (case-insensitive). + // When a new linter is added the version in `WithSince(...)` must be the next minor version of golangci-lint. + return []*linter.Config{ + linter.NewConfig(golinters.NewAsasalint(&cfg.LintersSettings.Asasalint)). + WithSince("1.47.0"). + WithPresets(linter.PresetBugs). + WithLoadForGoAnalysis(). + WithURL("https://github.com/alingse/asasalint"), + + linter.NewConfig(golinters.NewAsciicheck()). + WithSince("v1.26.0"). + WithPresets(linter.PresetBugs, linter.PresetStyle). + WithURL("https://github.com/tdakkota/asciicheck"), + + linter.NewConfig(golinters.NewBiDiChkFuncName(&cfg.LintersSettings.BiDiChk)). + WithSince("1.43.0"). + WithPresets(linter.PresetBugs). + WithURL("https://github.com/breml/bidichk"), + + linter.NewConfig(golinters.NewBodyclose()). + WithSince("v1.18.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetPerformance, linter.PresetBugs). + WithURL("https://github.com/timakin/bodyclose"), + + linter.NewConfig(golinters.NewContainedCtx()). + WithSince("1.44.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/sivchari/containedctx"), + + linter.NewConfig(golinters.NewContextCheck()). + WithSince("v1.43.0"). + WithPresets(linter.PresetBugs). + WithLoadForGoAnalysis(). + WithURL("https://github.com/kkHAIKE/contextcheck"), + + linter.NewConfig(golinters.NewCopyLoopVar(&cfg.LintersSettings.CopyLoopVar)). + WithSince("v1.57.0"). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/karamaru-alpha/copyloopvar"). + WithNoopFallback(cfg, linter.IsGoLowerThanGo122()), + + linter.NewConfig(golinters.NewCyclop(&cfg.LintersSettings.Cyclop)). + WithSince("v1.37.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetComplexity). + WithURL("https://github.com/bkielbasa/cyclop"), + + linter.NewConfig(golinters.NewDecorder(&cfg.LintersSettings.Decorder)). + WithSince("v1.44.0"). + WithPresets(linter.PresetFormatting, linter.PresetStyle). + WithURL("https://gitlab.com/bosi/decorder"), + + linter.NewConfig(linter.NewNoopDeprecated("deadcode", cfg)). + WithSince("v1.0.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetUnused). + WithURL("https://github.com/remyoudompheng/go-misc/tree/master/deadcode"). + Deprecated("The owner seems to have abandoned the linter.", "v1.49.0", "unused"), + + linter.NewConfig(golinters.NewDepguard(&cfg.LintersSettings.Depguard)). + WithSince("v1.4.0"). + WithPresets(linter.PresetStyle, linter.PresetImport, linter.PresetModule). + WithURL("https://github.com/OpenPeeDeeP/depguard"), + + linter.NewConfig(golinters.NewDogsled(&cfg.LintersSettings.Dogsled)). + WithSince("v1.19.0"). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/alexkohler/dogsled"), + + linter.NewConfig(golinters.NewDupl(&cfg.LintersSettings.Dupl)). + WithSince("v1.0.0"). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/mibk/dupl"), + + linter.NewConfig(golinters.NewDupWord(&cfg.LintersSettings.DupWord)). + WithSince("1.50.0"). + WithPresets(linter.PresetComment). + WithURL("https://github.com/Abirdcfly/dupword"), + + linter.NewConfig(golinters.NewDurationCheck()). + WithSince("v1.37.0"). + WithPresets(linter.PresetBugs). + WithLoadForGoAnalysis(). + WithURL("https://github.com/charithe/durationcheck"), + + linter.NewConfig(golinters.NewErrcheck(&cfg.LintersSettings.Errcheck)). + WithEnabledByDefault(). + WithSince("v1.0.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetBugs, linter.PresetError). + WithURL("https://github.com/kisielk/errcheck"), + + linter.NewConfig(golinters.NewErrChkJSONFuncName(&cfg.LintersSettings.ErrChkJSON)). + WithSince("1.44.0"). + WithPresets(linter.PresetBugs). + WithLoadForGoAnalysis(). + WithURL("https://github.com/breml/errchkjson"), + + linter.NewConfig(golinters.NewErrName()). + WithSince("v1.42.0"). + WithPresets(linter.PresetStyle). + WithLoadForGoAnalysis(). + WithURL("https://github.com/Antonboom/errname"), + + linter.NewConfig(golinters.NewErrorLint(&cfg.LintersSettings.ErrorLint)). + WithSince("v1.32.0"). + WithPresets(linter.PresetBugs, linter.PresetError). + WithLoadForGoAnalysis(). + WithURL("https://github.com/polyfloyd/go-errorlint"), + + linter.NewConfig(golinters.NewExecInQuery()). + WithSince("v1.46.0"). + WithPresets(linter.PresetSQL). + WithLoadForGoAnalysis(). + WithURL("https://github.com/lufeee/execinquery"), + + linter.NewConfig(golinters.NewExhaustive(&cfg.LintersSettings.Exhaustive)). + WithSince(" v1.28.0"). + WithPresets(linter.PresetBugs). + WithLoadForGoAnalysis(). + WithURL("https://github.com/nishanths/exhaustive"), + + linter.NewConfig(linter.NewNoopDeprecated("exhaustivestruct", cfg)). + WithSince("v1.32.0"). + WithPresets(linter.PresetStyle, linter.PresetTest). + WithLoadForGoAnalysis(). + WithURL("https://github.com/mbilski/exhaustivestruct"). + Deprecated("The repository of the linter has been deprecated by the owner.", "v1.46.0", "exhaustruct"), + + linter.NewConfig(golinters.NewExhaustruct(&cfg.LintersSettings.Exhaustruct)). + WithSince("v1.46.0"). + WithPresets(linter.PresetStyle, linter.PresetTest). + WithLoadForGoAnalysis(). + WithURL("https://github.com/GaijinEntertainment/go-exhaustruct"), + + linter.NewConfig(golinters.NewExportLoopRef()). + WithSince("v1.28.0"). + WithPresets(linter.PresetBugs). + WithLoadForGoAnalysis(). + WithURL("https://github.com/kyoh86/exportloopref"), + + linter.NewConfig(golinters.NewForbidigo(&cfg.LintersSettings.Forbidigo)). + WithSince("v1.34.0"). + WithPresets(linter.PresetStyle). + // Strictly speaking, + // the additional information is only needed when forbidigoCfg.AnalyzeTypes is chosen by the user. + // But we don't know that here in all cases (sometimes config is not loaded), + // so we have to assume that it is needed to be on the safe side. + WithLoadForGoAnalysis(). + WithURL("https://github.com/ashanbrown/forbidigo"), + + linter.NewConfig(golinters.NewForceTypeAssert()). + WithSince("v1.38.0"). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/gostaticanalysis/forcetypeassert"), + + linter.NewConfig(golinters.NewFunlen(&cfg.LintersSettings.Funlen)). + WithSince("v1.18.0"). + WithPresets(linter.PresetComplexity). + WithURL("https://github.com/ultraware/funlen"), + + linter.NewConfig(golinters.NewGci(&cfg.LintersSettings.Gci)). + WithSince("v1.30.0"). + WithPresets(linter.PresetFormatting, linter.PresetImport). + WithURL("https://github.com/daixiang0/gci"), + + linter.NewConfig(golinters.NewGinkgoLinter(&cfg.LintersSettings.GinkgoLinter)). + WithSince("v1.51.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/nunnatsa/ginkgolinter"), + + linter.NewConfig(golinters.NewGoCheckCompilerDirectives()). + WithSince("v1.51.0"). + WithPresets(linter.PresetBugs). + WithURL("https://github.com/leighmcculloch/gocheckcompilerdirectives"), + + linter.NewConfig(golinters.NewGochecknoglobals()). + WithSince("v1.12.0"). + WithPresets(linter.PresetStyle). + WithLoadForGoAnalysis(). + WithURL("https://github.com/leighmcculloch/gochecknoglobals"), + + linter.NewConfig(golinters.NewGochecknoinits()). + WithSince("v1.12.0"). + WithPresets(linter.PresetStyle), + + linter.NewConfig(golinters.NewGoCheckSumType()). + WithSince("v1.55.0"). + WithPresets(linter.PresetBugs). + WithLoadForGoAnalysis(). + WithURL("https://github.com/alecthomas/go-check-sumtype"), + + linter.NewConfig(golinters.NewGocognit(&cfg.LintersSettings.Gocognit)). + WithSince("v1.20.0"). + WithPresets(linter.PresetComplexity). + WithURL("https://github.com/uudashr/gocognit"), + + linter.NewConfig(golinters.NewGoconst(&cfg.LintersSettings.Goconst)). + WithSince("v1.0.0"). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/jgautheron/goconst"), + + linter.NewConfig(golinters.NewGoCritic(&cfg.LintersSettings.Gocritic)). + WithSince("v1.12.0"). + WithPresets(linter.PresetStyle, linter.PresetMetaLinter). + WithLoadForGoAnalysis(). + WithURL("https://github.com/go-critic/go-critic"), + + linter.NewConfig(golinters.NewGocyclo(&cfg.LintersSettings.Gocyclo)). + WithSince("v1.0.0"). + WithPresets(linter.PresetComplexity). + WithURL("https://github.com/fzipp/gocyclo"), + + linter.NewConfig(golinters.NewGodot(&cfg.LintersSettings.Godot)). + WithSince("v1.25.0"). + WithPresets(linter.PresetStyle, linter.PresetComment). + WithAutoFix(). + WithURL("https://github.com/tetafro/godot"), + + linter.NewConfig(golinters.NewGodox(&cfg.LintersSettings.Godox)). + WithSince("v1.19.0"). + WithPresets(linter.PresetStyle, linter.PresetComment). + WithURL("https://github.com/matoous/godox"), + + linter.NewConfig(golinters.NewGoerr113()). + WithSince("v1.26.0"). + WithPresets(linter.PresetStyle, linter.PresetError). + WithLoadForGoAnalysis(). + WithURL("https://github.com/Djarvur/go-err113"), + + linter.NewConfig(golinters.NewGofmt(&cfg.LintersSettings.Gofmt)). + WithSince("v1.0.0"). + WithPresets(linter.PresetFormatting). + WithAutoFix(). + WithURL("https://pkg.go.dev/cmd/gofmt"), + + linter.NewConfig(golinters.NewGofumpt(&cfg.LintersSettings.Gofumpt)). + WithSince("v1.28.0"). + WithPresets(linter.PresetFormatting). + WithAutoFix(). + WithURL("https://github.com/mvdan/gofumpt"), + + linter.NewConfig(golinters.NewGoHeader(&cfg.LintersSettings.Goheader)). + WithSince("v1.28.0"). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/denis-tingaikin/go-header"), + + linter.NewConfig(golinters.NewGoimports(&cfg.LintersSettings.Goimports)). + WithSince("v1.20.0"). + WithPresets(linter.PresetFormatting, linter.PresetImport). + WithAutoFix(). + WithURL("https://pkg.go.dev/golang.org/x/tools/cmd/goimports"), + + linter.NewConfig(linter.NewNoopDeprecated("golint", cfg)). + WithSince("v1.0.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/golang/lint"). + Deprecated("The repository of the linter has been archived by the owner.", "v1.41.0", "revive"), + + linter.NewConfig(golinters.NewGoMND(&cfg.LintersSettings.Gomnd)). + WithSince("v1.22.0"). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/tommy-muehle/go-mnd"), + + linter.NewConfig(golinters.NewGoModDirectives(&cfg.LintersSettings.GoModDirectives)). + WithSince("v1.39.0"). + WithPresets(linter.PresetStyle, linter.PresetModule). + WithURL("https://github.com/ldez/gomoddirectives"), + + linter.NewConfig(golinters.NewGomodguard(&cfg.LintersSettings.Gomodguard)). + WithSince("v1.25.0"). + WithPresets(linter.PresetStyle, linter.PresetImport, linter.PresetModule). + WithURL("https://github.com/ryancurrah/gomodguard"), + + linter.NewConfig(golinters.NewGoPrintfFuncName()). + WithSince("v1.23.0"). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/jirfag/go-printf-func-name"), + + linter.NewConfig(golinters.NewGosec(&cfg.LintersSettings.Gosec)). + WithSince("v1.0.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetBugs). + WithURL("https://github.com/securego/gosec"). + WithAlternativeNames("gas"), + + linter.NewConfig(golinters.NewGosimple(&cfg.LintersSettings.Gosimple)). + WithEnabledByDefault(). + WithSince("v1.20.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetStyle). + WithAlternativeNames(megacheckName). + WithURL("https://github.com/dominikh/go-tools/tree/master/simple"), + + linter.NewConfig(golinters.NewGosmopolitan(&cfg.LintersSettings.Gosmopolitan)). + WithSince("v1.53.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetBugs). + WithURL("https://github.com/xen0n/gosmopolitan"), + + linter.NewConfig(golinters.NewGovet(&cfg.LintersSettings.Govet)). + WithEnabledByDefault(). + WithSince("v1.0.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetBugs, linter.PresetMetaLinter). + WithAlternativeNames("vet", "vetshadow"). + WithURL("https://pkg.go.dev/cmd/vet"), + + linter.NewConfig(golinters.NewGrouper(&cfg.LintersSettings.Grouper)). + WithSince("v1.44.0"). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/leonklingele/grouper"), + + linter.NewConfig(linter.NewNoopDeprecated("ifshort", cfg)). + WithSince("v1.36.0"). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/esimonov/ifshort"). + Deprecated("The repository of the linter has been deprecated by the owner.", "v1.48.0", ""), + + linter.NewConfig(golinters.NewImportAs(&cfg.LintersSettings.ImportAs)). + WithSince("v1.38.0"). + WithPresets(linter.PresetStyle). + WithLoadForGoAnalysis(). + WithURL("https://github.com/julz/importas"), + + linter.NewConfig(golinters.NewINamedParam(&cfg.LintersSettings.Inamedparam)). + WithSince("v1.55.0"). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/macabu/inamedparam"), + + linter.NewConfig(golinters.NewIneffassign()). + WithEnabledByDefault(). + WithSince("v1.0.0"). + WithPresets(linter.PresetUnused). + WithURL("https://github.com/gordonklaus/ineffassign"), + + linter.NewConfig(golinters.NewInterfaceBloat(&cfg.LintersSettings.InterfaceBloat)). + WithSince("v1.49.0"). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/sashamelentyev/interfacebloat"), + + linter.NewConfig(linter.NewNoopDeprecated("interfacer", cfg)). + WithSince("v1.0.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/mvdan/interfacer"). + Deprecated("The repository of the linter has been archived by the owner.", "v1.38.0", ""), + + linter.NewConfig(golinters.NewIntrange()). + WithSince("v1.57.0"). + WithURL("https://github.com/ckaznocha/intrange"). + WithNoopFallback(cfg, linter.IsGoLowerThanGo122()), + + linter.NewConfig(golinters.NewIreturn(&cfg.LintersSettings.Ireturn)). + WithSince("v1.43.0"). + WithPresets(linter.PresetStyle). + WithLoadForGoAnalysis(). + WithURL("https://github.com/butuzov/ireturn"), + + linter.NewConfig(golinters.NewLLL(&cfg.LintersSettings.Lll)). + WithSince("v1.8.0"). + WithPresets(linter.PresetStyle), + + linter.NewConfig(golinters.NewLoggerCheck(&cfg.LintersSettings.LoggerCheck)). + WithSince("v1.49.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetStyle, linter.PresetBugs). + WithAlternativeNames("logrlint"). + WithURL("https://github.com/timonwong/loggercheck"), + + linter.NewConfig(golinters.NewMaintIdx(&cfg.LintersSettings.MaintIdx)). + WithSince("v1.44.0"). + WithPresets(linter.PresetComplexity). + WithURL("https://github.com/yagipy/maintidx"), + + linter.NewConfig(golinters.NewMakezero(&cfg.LintersSettings.Makezero)). + WithSince("v1.34.0"). + WithPresets(linter.PresetStyle, linter.PresetBugs). + WithLoadForGoAnalysis(). + WithURL("https://github.com/ashanbrown/makezero"), + + linter.NewConfig(linter.NewNoopDeprecated("maligned", cfg)). + WithSince("v1.0.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetPerformance). + WithURL("https://github.com/mdempsky/maligned"). + Deprecated("The repository of the linter has been archived by the owner.", "v1.38.0", "govet 'fieldalignment'"), + + linter.NewConfig(golinters.NewMirror()). + WithSince("v1.53.0"). + WithPresets(linter.PresetStyle). + WithLoadForGoAnalysis(). + WithURL("https://github.com/butuzov/mirror"), + + linter.NewConfig(golinters.NewMisspell(&cfg.LintersSettings.Misspell)). + WithSince("v1.8.0"). + WithPresets(linter.PresetStyle, linter.PresetComment). + WithAutoFix(). + WithURL("https://github.com/client9/misspell"), + + linter.NewConfig(golinters.NewMustTag(&cfg.LintersSettings.MustTag)). + WithSince("v1.51.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetStyle, linter.PresetBugs). + WithURL("https://github.com/go-simpler/musttag"), + + linter.NewConfig(golinters.NewNakedret(&cfg.LintersSettings.Nakedret)). + WithSince("v1.19.0"). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/alexkohler/nakedret"), + + linter.NewConfig(golinters.NewNestif(&cfg.LintersSettings.Nestif)). + WithSince("v1.25.0"). + WithPresets(linter.PresetComplexity). + WithURL("https://github.com/nakabonne/nestif"), + + linter.NewConfig(golinters.NewNilErr()). + WithSince("v1.38.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetBugs). + WithURL("https://github.com/gostaticanalysis/nilerr"), + + linter.NewConfig(golinters.NewNilNil(&cfg.LintersSettings.NilNil)). + WithSince("v1.43.0"). + WithPresets(linter.PresetStyle). + WithLoadForGoAnalysis(). + WithURL("https://github.com/Antonboom/nilnil"), + + linter.NewConfig(golinters.NewNLReturn(&cfg.LintersSettings.Nlreturn)). + WithSince("v1.30.0"). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/ssgreg/nlreturn"), + + linter.NewConfig(golinters.NewNoctx()). + WithSince("v1.28.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetPerformance, linter.PresetBugs). + WithURL("https://github.com/sonatard/noctx"), + + linter.NewConfig(golinters.NewNoNamedReturns(&cfg.LintersSettings.NoNamedReturns)). + WithSince("v1.46.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/firefart/nonamedreturns"), + + linter.NewConfig(linter.NewNoopDeprecated("nosnakecase", cfg)). + WithSince("v1.47.0"). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/sivchari/nosnakecase"). + Deprecated("The repository of the linter has been deprecated by the owner.", "v1.48.1", "revive 'var-naming'"), + + linter.NewConfig(golinters.NewNoSprintfHostPort()). + WithSince("v1.46.0"). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/stbenjam/no-sprintf-host-port"), + + linter.NewConfig(golinters.NewParallelTest(&cfg.LintersSettings.ParallelTest)). + WithSince("v1.33.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetStyle, linter.PresetTest). + WithURL("https://github.com/kunwardeep/paralleltest"), + + linter.NewConfig(golinters.NewPerfSprint(&cfg.LintersSettings.PerfSprint)). + WithSince("v1.55.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetPerformance). + WithURL("https://github.com/catenacyber/perfsprint"), + + linter.NewConfig(golinters.NewPreAlloc(&cfg.LintersSettings.Prealloc)). + WithSince("v1.19.0"). + WithPresets(linter.PresetPerformance). + WithURL("https://github.com/alexkohler/prealloc"), + + linter.NewConfig(golinters.NewPredeclared(&cfg.LintersSettings.Predeclared)). + WithSince("v1.35.0"). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/nishanths/predeclared"), + + linter.NewConfig(golinters.NewPromlinter(&cfg.LintersSettings.Promlinter)). + WithSince("v1.40.0"). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/yeya24/promlinter"), + + linter.NewConfig(golinters.NewProtoGetter(&cfg.LintersSettings.ProtoGetter)). + WithSince("v1.55.0"). + WithPresets(linter.PresetBugs). + WithLoadForGoAnalysis(). + WithAutoFix(). + WithURL("https://github.com/ghostiam/protogetter"), + + linter.NewConfig(golinters.NewReassign(&cfg.LintersSettings.Reassign)). + WithSince("1.49.0"). + WithPresets(linter.PresetBugs). + WithLoadForGoAnalysis(). + WithURL("https://github.com/curioswitch/go-reassign"), + + linter.NewConfig(golinters.NewRevive(&cfg.LintersSettings.Revive)). + WithSince("v1.37.0"). + WithPresets(linter.PresetStyle, linter.PresetMetaLinter). + ConsiderSlow(). + WithURL("https://github.com/mgechev/revive"), + + linter.NewConfig(golinters.NewRowsErrCheck(&cfg.LintersSettings.RowsErrCheck)). + WithSince("v1.23.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetBugs, linter.PresetSQL). + WithURL("https://github.com/jingyugao/rowserrcheck"), + + linter.NewConfig(golinters.NewSlogLint(&cfg.LintersSettings.SlogLint)). + WithSince("v1.55.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetStyle, linter.PresetFormatting). + WithURL("https://github.com/go-simpler/sloglint"), + + linter.NewConfig(linter.NewNoopDeprecated("scopelint", cfg)). + WithSince("v1.12.0"). + WithPresets(linter.PresetBugs). + WithURL("https://github.com/kyoh86/scopelint"). + Deprecated("The repository of the linter has been deprecated by the owner.", "v1.39.0", "exportloopref"), + + linter.NewConfig(golinters.NewSQLCloseCheck()). + WithSince("v1.28.0"). + WithPresets(linter.PresetBugs, linter.PresetSQL). + WithLoadForGoAnalysis(). + WithURL("https://github.com/ryanrolds/sqlclosecheck"), + + linter.NewConfig(golinters.NewSpancheck(&cfg.LintersSettings.Spancheck)). + WithSince("v1.56.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetBugs). + WithURL("https://github.com/jjti/go-spancheck"), + + linter.NewConfig(golinters.NewStaticcheck(&cfg.LintersSettings.Staticcheck)). + WithEnabledByDefault(). + WithSince("v1.0.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetBugs, linter.PresetMetaLinter). + WithAlternativeNames(megacheckName). + WithURL("https://staticcheck.io/"), + + linter.NewConfig(linter.NewNoopDeprecated("structcheck", cfg)). + WithSince("v1.0.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetUnused). + WithURL("https://github.com/opennota/check"). + Deprecated("The owner seems to have abandoned the linter.", "v1.49.0", "unused"), + + linter.NewConfig(golinters.NewStylecheck(&cfg.LintersSettings.Stylecheck)). + WithSince("v1.20.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/dominikh/go-tools/tree/master/stylecheck"), + + linter.NewConfig(golinters.NewTagAlign(&cfg.LintersSettings.TagAlign)). + WithSince("v1.53.0"). + WithPresets(linter.PresetStyle, linter.PresetFormatting). + WithAutoFix(). + WithURL("https://github.com/4meepo/tagalign"), + + linter.NewConfig(golinters.NewTagliatelle(&cfg.LintersSettings.Tagliatelle)). + WithSince("v1.40.0"). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/ldez/tagliatelle"), + + linter.NewConfig(golinters.NewTenv(&cfg.LintersSettings.Tenv)). + WithSince("v1.43.0"). + WithPresets(linter.PresetStyle). + WithLoadForGoAnalysis(). + WithURL("https://github.com/sivchari/tenv"), + + linter.NewConfig(golinters.NewTestableexamples()). + WithSince("v1.50.0"). + WithPresets(linter.PresetTest). + WithURL("https://github.com/maratori/testableexamples"), + + linter.NewConfig(golinters.NewTestifylint(&cfg.LintersSettings.Testifylint)). + WithSince("v1.55.0"). + WithPresets(linter.PresetTest, linter.PresetBugs). + WithLoadForGoAnalysis(). + WithURL("https://github.com/Antonboom/testifylint"), + + linter.NewConfig(golinters.NewTestpackage(&cfg.LintersSettings.Testpackage)). + WithSince("v1.25.0"). + WithPresets(linter.PresetStyle, linter.PresetTest). + WithURL("https://github.com/maratori/testpackage"), + + linter.NewConfig(golinters.NewThelper(&cfg.LintersSettings.Thelper)). + WithSince("v1.34.0"). + WithPresets(linter.PresetStyle). + WithLoadForGoAnalysis(). + WithURL("https://github.com/kulti/thelper"), + + linter.NewConfig(golinters.NewTparallel()). + WithSince("v1.32.0"). + WithPresets(linter.PresetStyle, linter.PresetTest). + WithLoadForGoAnalysis(). + WithURL("https://github.com/moricho/tparallel"), + + linter.NewConfig(golinters.NewTypecheck()). + WithInternal(). + WithEnabledByDefault(). + WithSince("v1.3.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetBugs). + WithURL(""), + + linter.NewConfig(golinters.NewUnconvert(&cfg.LintersSettings.Unconvert)). + WithSince("v1.0.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/mdempsky/unconvert"), + + linter.NewConfig(golinters.NewUnparam(&cfg.LintersSettings.Unparam)). + WithSince("v1.9.0"). + WithPresets(linter.PresetUnused). + WithLoadForGoAnalysis(). + WithURL("https://github.com/mvdan/unparam"), + + linter.NewConfig(golinters.NewUnused(&cfg.LintersSettings.Unused, &cfg.LintersSettings.Staticcheck)). + WithEnabledByDefault(). + WithSince("v1.20.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetUnused). + WithAlternativeNames(megacheckName). + ConsiderSlow(). + WithChangeTypes(). + WithURL("https://github.com/dominikh/go-tools/tree/master/unused"), + + linter.NewConfig(golinters.NewUseStdlibVars(&cfg.LintersSettings.UseStdlibVars)). + WithSince("v1.48.0"). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/sashamelentyev/usestdlibvars"), + + linter.NewConfig(linter.NewNoopDeprecated("varcheck", cfg)). + WithSince("v1.0.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetUnused). + WithURL("https://github.com/opennota/check"). + Deprecated("The owner seems to have abandoned the linter.", "v1.49.0", "unused"), + + linter.NewConfig(golinters.NewVarnamelen(&cfg.LintersSettings.Varnamelen)). + WithSince("v1.43.0"). + WithPresets(linter.PresetStyle). + WithLoadForGoAnalysis(). + WithURL("https://github.com/blizzy78/varnamelen"), + + linter.NewConfig(golinters.NewWastedAssign()). + WithSince("v1.38.0"). + WithPresets(linter.PresetStyle). + WithLoadForGoAnalysis(). + WithURL("https://github.com/sanposhiho/wastedassign"), + + linter.NewConfig(golinters.NewWhitespace(&cfg.LintersSettings.Whitespace)). + WithSince("v1.19.0"). + WithPresets(linter.PresetStyle). + WithAutoFix(). + WithURL("https://github.com/ultraware/whitespace"), + + linter.NewConfig(golinters.NewWrapcheck(&cfg.LintersSettings.Wrapcheck)). + WithSince("v1.32.0"). + WithPresets(linter.PresetStyle, linter.PresetError). + WithLoadForGoAnalysis(). + WithURL("https://github.com/tomarrell/wrapcheck"), + + linter.NewConfig(golinters.NewWSL(&cfg.LintersSettings.WSL)). + WithSince("v1.20.0"). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/bombsimon/wsl"), + + linter.NewConfig(golinters.NewZerologLint()). + WithSince("v1.53.0"). + WithPresets(linter.PresetBugs). + WithLoadForGoAnalysis(). + WithURL("https://github.com/ykadowak/zerologlint"), + + // nolintlint must be last because it looks at the results of all the previous linters for unused nolint directives + linter.NewConfig(golinters.NewNoLintLint(&cfg.LintersSettings.NoLintLint)). + WithSince("v1.26.0"). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/golangci/golangci-lint/blob/master/pkg/golinters/nolintlint/README.md"), + }, 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/builder_plugin_go.go similarity index 56% rename from vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/custom_linters.go rename to vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/builder_plugin_go.go index 188c14d913..4c6b4b5988 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/custom_linters.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/builder_plugin_go.go @@ -6,47 +6,65 @@ import ( "path/filepath" "plugin" - "github.com/spf13/viper" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/logutils" ) +const goPluginType = "goplugin" + type AnalyzerPlugin interface { GetAnalyzers() []*analysis.Analyzer } -// getCustomLinterConfigs loads private linters that are specified in the golangci config file. -func (m *Manager) getCustomLinterConfigs() []*linter.Config { - if m.cfg == nil || m.log == nil { - return nil +// PluginGoBuilder builds the custom linters (Go plugin) based on the configuration. +type PluginGoBuilder struct { + log logutils.Log +} + +// NewPluginGoBuilder creates new PluginGoBuilder. +func NewPluginGoBuilder(log logutils.Log) *PluginGoBuilder { + return &PluginGoBuilder{log: log} +} + +// Build loads custom linters that are specified in the golangci-lint config file. +func (b *PluginGoBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { + if cfg == nil || b.log == nil { + return nil, nil } var linters []*linter.Config - for name, settings := range m.cfg.LintersSettings.Custom { - lc, err := m.loadCustomLinterConfig(name, settings) + for name, settings := range cfg.LintersSettings.Custom { + if settings.Type != goPluginType && settings.Type != "" { + continue + } + + settings := settings + + lc, err := b.loadConfig(cfg, name, &settings) if err != nil { - m.log.Errorf("Unable to load custom analyzer %s:%s, %v", name, settings.Path, err) + return nil, fmt.Errorf("unable to load custom analyzer %q: %s, %w", name, settings.Path, err) } else { linters = append(linters, lc) } } - return linters + return linters, nil } -// loadCustomLinterConfig loads the configuration of private linters. +// loadConfig loads the configuration of private linters. // Private linters are dynamically loaded from .so plugin files. -func (m *Manager) loadCustomLinterConfig(name string, settings config.CustomLinterSettings) (*linter.Config, error) { - analyzers, err := m.getAnalyzerPlugin(settings.Path, settings.Settings) +func (b *PluginGoBuilder) loadConfig(cfg *config.Config, name string, settings *config.CustomLinterSettings) (*linter.Config, error) { + analyzers, err := b.getAnalyzerPlugin(cfg, settings.Path, settings.Settings) if err != nil { return nil, err } - m.log.Infof("Loaded %s: %s", settings.Path, name) + b.log.Infof("Loaded %s: %s", settings.Path, name) customLinter := goanalysis.NewLinter(name, settings.Description, analyzers, nil). WithLoadMode(goanalysis.LoadModeTypesInfo) @@ -64,15 +82,10 @@ func (m *Manager) loadCustomLinterConfig(name string, settings config.CustomLint // and returns the 'AnalyzerPlugin' interface implemented by the private plugin. // An error is returned if the private linter cannot be loaded // or the linter does not implement the AnalyzerPlugin interface. -func (m *Manager) getAnalyzerPlugin(path string, settings any) ([]*analysis.Analyzer, error) { +func (b *PluginGoBuilder) getAnalyzerPlugin(cfg *config.Config, path string, settings any) ([]*analysis.Analyzer, error) { if !filepath.IsAbs(path) { // resolve non-absolute paths relative to config file's directory - configFilePath := viper.ConfigFileUsed() - absConfigFilePath, err := filepath.Abs(configFilePath) - if err != nil { - return nil, fmt.Errorf("could not get absolute representation of config file path %q: %w", configFilePath, err) - } - path = filepath.Join(filepath.Dir(absConfigFilePath), path) + path = filepath.Join(cfg.GetConfigDir(), path) } plug, err := plugin.Open(path) @@ -80,7 +93,7 @@ func (m *Manager) getAnalyzerPlugin(path string, settings any) ([]*analysis.Anal return nil, err } - analyzers, err := m.lookupPlugin(plug, settings) + analyzers, err := b.lookupPlugin(plug, settings) if err != nil { return nil, fmt.Errorf("lookup plugin %s: %w", path, err) } @@ -88,10 +101,10 @@ func (m *Manager) getAnalyzerPlugin(path string, settings any) ([]*analysis.Anal return analyzers, nil } -func (m *Manager) lookupPlugin(plug *plugin.Plugin, settings any) ([]*analysis.Analyzer, error) { +func (b *PluginGoBuilder) lookupPlugin(plug *plugin.Plugin, settings any) ([]*analysis.Analyzer, error) { symbol, err := plug.Lookup("New") if err != nil { - analyzers, errP := m.lookupAnalyzerPlugin(plug) + analyzers, errP := b.lookupAnalyzerPlugin(plug) if errP != nil { return nil, errors.Join(err, errP) } @@ -108,13 +121,13 @@ func (m *Manager) lookupPlugin(plug *plugin.Plugin, settings any) ([]*analysis.A return constructor(settings) } -func (m *Manager) lookupAnalyzerPlugin(plug *plugin.Plugin) ([]*analysis.Analyzer, error) { +func (b *PluginGoBuilder) lookupAnalyzerPlugin(plug *plugin.Plugin) ([]*analysis.Analyzer, error) { symbol, err := plug.Lookup("AnalyzerPlugin") if err != nil { return nil, err } - m.log.Warnf("plugin: 'AnalyzerPlugin' plugins are deprecated, please use the new plugin signature: " + + b.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) diff --git a/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/builder_plugin_module.go b/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/builder_plugin_module.go new file mode 100644 index 0000000000..904b18bd5e --- /dev/null +++ b/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/builder_plugin_module.go @@ -0,0 +1,85 @@ +package lintersdb + +import ( + "fmt" + "strings" + + "github.com/golangci/plugin-module-register/register" + + "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/logutils" +) + +const modulePluginType = "module" + +// PluginModuleBuilder builds the custom linters (module plugin) based on the configuration. +type PluginModuleBuilder struct { + log logutils.Log +} + +// NewPluginModuleBuilder creates new PluginModuleBuilder. +func NewPluginModuleBuilder(log logutils.Log) *PluginModuleBuilder { + return &PluginModuleBuilder{log: log} +} + +// Build loads custom linters that are specified in the golangci-lint config file. +func (b *PluginModuleBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { + if cfg == nil || b.log == nil { + return nil, nil + } + + var linters []*linter.Config + + for name, settings := range cfg.LintersSettings.Custom { + if settings.Type != modulePluginType { + continue + } + + b.log.Infof("Loaded %s: %s", settings.Path, name) + + newPlugin, err := register.GetPlugin(name) + if err != nil { + return nil, fmt.Errorf("plugin(%s): %w", name, err) + } + + p, err := newPlugin(settings.Settings) + if err != nil { + return nil, fmt.Errorf("plugin(%s): newPlugin %w", name, err) + } + + analyzers, err := p.BuildAnalyzers() + if err != nil { + return nil, fmt.Errorf("plugin(%s): BuildAnalyzers %w", name, err) + } + + customLinter := goanalysis.NewLinter(name, settings.Description, analyzers, nil) + + switch strings.ToLower(p.GetLoadMode()) { + case register.LoadModeSyntax: + customLinter = customLinter.WithLoadMode(goanalysis.LoadModeSyntax) + case register.LoadModeTypesInfo: + customLinter = customLinter.WithLoadMode(goanalysis.LoadModeTypesInfo) + default: + customLinter = customLinter.WithLoadMode(goanalysis.LoadModeTypesInfo) + } + + lc := linter.NewConfig(customLinter). + WithEnabledByDefault(). + WithURL(settings.OriginalURL) + + switch strings.ToLower(p.GetLoadMode()) { + case register.LoadModeSyntax: + // noop + case register.LoadModeTypesInfo: + lc = lc.WithLoadForGoAnalysis() + default: + lc = lc.WithLoadForGoAnalysis() + } + + linters = append(linters, lc) + } + + return linters, nil +} 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 deleted file mode 100644 index 6f7b91b4d1..0000000000 --- a/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/enabled_set.go +++ /dev/null @@ -1,219 +0,0 @@ -package lintersdb - -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" - "github.com/golangci/golangci-lint/pkg/logutils" -) - -// EnvTestRun value: "1" -const EnvTestRun = "GL_TEST_RUN" - -type EnabledSet struct { - m *Manager - v *Validator - log logutils.Log - cfg *config.Config - debugf logutils.DebugFunc -} - -func NewEnabledSet(m *Manager, v *Validator, log logutils.Log, cfg *config.Config) *EnabledSet { - return &EnabledSet{ - m: m, - v: v, - log: log, - cfg: cfg, - debugf: logutils.Debug(logutils.DebugKeyEnabledLinters), - } -} - -//nolint:gocyclo // the complexity cannot be reduced. -func (es EnabledSet) build(lcfg *config.Linters, enabledByDefaultLinters []*linter.Config) map[string]*linter.Config { - es.debugf("Linters config: %#v", lcfg) - - resultLintersSet := map[string]*linter.Config{} - switch { - case len(lcfg.Presets) != 0: - break // imply --disable-all - case lcfg.EnableAll: - resultLintersSet = linterConfigsToMap(es.m.GetAllSupportedLinterConfigs()) - case lcfg.DisableAll: - break - default: - resultLintersSet = linterConfigsToMap(enabledByDefaultLinters) - } - - // --presets can only add linters to default set - for _, p := range lcfg.Presets { - for _, lc := range es.m.GetAllLinterConfigsForPreset(p) { - lc := lc - resultLintersSet[lc.Name()] = lc - } - } - - // --fast removes slow linters from current set. - // It should be after --presets to be able to run only fast linters in preset. - // It should be before --enable and --disable to be able to enable or disable specific linter. - if lcfg.Fast { - for name, lc := range resultLintersSet { - if lc.IsSlowLinter() { - delete(resultLintersSet, name) - } - } - } - - for _, name := range lcfg.Enable { - for _, lc := range es.m.GetLinterConfigs(name) { - // it's important to use lc.Name() nor name because name can be alias - resultLintersSet[lc.Name()] = lc - } - } - - for _, name := range lcfg.Disable { - for _, lc := range es.m.GetLinterConfigs(name) { - // it's important to use lc.Name() nor name because name can be alias - delete(resultLintersSet, lc.Name()) - } - } - - // typecheck is not a real linter and cannot be disabled. - if _, ok := resultLintersSet["typecheck"]; !ok && (es.cfg == nil || !es.cfg.InternalCmdTest) { - for _, lc := range es.m.GetLinterConfigs("typecheck") { - // it's important to use lc.Name() nor name because name can be alias - resultLintersSet[lc.Name()] = lc - } - } - - return resultLintersSet -} - -func (es EnabledSet) GetEnabledLintersMap() (map[string]*linter.Config, error) { - if err := es.v.validateEnabledDisabledLintersConfig(&es.cfg.Linters); err != nil { - return nil, err - } - - enabledLinters := es.build(&es.cfg.Linters, es.m.GetAllEnabledByDefaultLinters()) - if os.Getenv(EnvTestRun) == "1" { - es.verbosePrintLintersStatus(enabledLinters) - } - return enabledLinters, nil -} - -// GetOptimizedLinters returns enabled linters after optimization (merging) of multiple linters -// into a fewer number of linters. E.g. some go/analysis linters can be optimized into -// one metalinter for data reuse and speed up. -func (es EnabledSet) GetOptimizedLinters() ([]*linter.Config, error) { - if err := es.v.validateEnabledDisabledLintersConfig(&es.cfg.Linters); err != nil { - return nil, err - } - - resultLintersSet := es.build(&es.cfg.Linters, es.m.GetAllEnabledByDefaultLinters()) - es.verbosePrintLintersStatus(resultLintersSet) - es.combineGoAnalysisLinters(resultLintersSet) - - resultLinters := maps.Values(resultLintersSet) - - // Make order of execution of linters (go/analysis metalinter and unused) stable. - sort.Slice(resultLinters, func(i, j int) bool { - a, b := resultLinters[i], resultLinters[j] - - if b.Name() == linter.LastLinter { - return true - } - - if a.Name() == linter.LastLinter { - return false - } - - if a.DoesChangeTypes != b.DoesChangeTypes { - return b.DoesChangeTypes // move type-changing linters to the end to optimize speed - } - return a.Name() < b.Name() - }) - - return resultLinters, nil -} - -func (es EnabledSet) combineGoAnalysisLinters(linters map[string]*linter.Config) { - var goanalysisLinters []*goanalysis.Linter - goanalysisPresets := map[string]bool{} - for _, lc := range linters { - lnt, ok := lc.Linter.(*goanalysis.Linter) - if !ok { - continue - } - if lnt.LoadMode() == goanalysis.LoadModeWholeProgram { - // It's ineffective by CPU and memory to run whole-program and incremental analyzers at once. - continue - } - goanalysisLinters = append(goanalysisLinters, lnt) - for _, p := range lc.InPresets { - goanalysisPresets[p] = true - } - } - - if len(goanalysisLinters) <= 1 { - es.debugf("Didn't combine go/analysis linters: got only %d linters", len(goanalysisLinters)) - return - } - - for _, lnt := range goanalysisLinters { - delete(linters, lnt.Name()) - } - - // Make order of execution of go/analysis analyzers stable. - sort.Slice(goanalysisLinters, func(i, j int) bool { - a, b := goanalysisLinters[i], goanalysisLinters[j] - - if b.Name() == linter.LastLinter { - return true - } - - if a.Name() == linter.LastLinter { - return false - } - - return a.Name() <= b.Name() - }) - - ml := goanalysis.NewMetaLinter(goanalysisLinters) - - presets := maps.Keys(goanalysisPresets) - - mlConfig := &linter.Config{ - Linter: ml, - EnabledByDefault: false, - InPresets: presets, - AlternativeNames: nil, - OriginalURL: "", - } - - mlConfig = mlConfig.WithLoadForGoAnalysis() - - linters[ml.Name()] = mlConfig - es.debugf("Combined %d go/analysis linters into one metalinter", len(goanalysisLinters)) -} - -func (es EnabledSet) verbosePrintLintersStatus(lcs map[string]*linter.Config) { - var linterNames []string - for _, lc := range lcs { - if lc.Internal { - continue - } - - linterNames = append(linterNames, lc.Name()) - } - sort.StringSlice(linterNames).Sort() - es.log.Infof("Active %d linters: %s", len(linterNames), linterNames) - - if len(es.cfg.Linters.Presets) != 0 { - sort.StringSlice(es.cfg.Linters.Presets).Sort() - es.log.Infof("Active presets: %s", es.cfg.Linters.Presets) - } -} 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 fdc73ec734..9816605892 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 @@ -1,948 +1,140 @@ package lintersdb import ( - "regexp" + "fmt" + "os" + "slices" + "sort" + + "golang.org/x/exp/maps" "github.com/golangci/golangci-lint/pkg/config" - "github.com/golangci/golangci-lint/pkg/golinters" + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/logutils" ) +type Builder interface { + Build(cfg *config.Config) ([]*linter.Config, error) +} + +// Manager is a type of database for all linters (internals or plugins). +// It provides methods to access to the linter sets. type Manager struct { + log logutils.Log + debugf logutils.DebugFunc + cfg *config.Config - log logutils.Log - nameToLCs map[string][]*linter.Config - customLinters []*linter.Config + linters []*linter.Config + + nameToLCs map[string][]*linter.Config } -func NewManager(cfg *config.Config, log logutils.Log) *Manager { - m := &Manager{cfg: cfg, log: log} - m.customLinters = m.getCustomLinterConfigs() +// NewManager creates a new Manager. +// This constructor will call the builders to build and store the linters. +func NewManager(log logutils.Log, cfg *config.Config, builders ...Builder) (*Manager, error) { + m := &Manager{ + log: log, + debugf: logutils.Debug(logutils.DebugKeyEnabledLinters), + nameToLCs: make(map[string][]*linter.Config), + } + + m.cfg = cfg + if cfg == nil { + m.cfg = config.NewDefault() + } - nameToLCs := make(map[string][]*linter.Config) - for _, lc := range m.GetAllSupportedLinterConfigs() { + for _, builder := range builders { + linters, err := builder.Build(m.cfg) + if err != nil { + return nil, fmt.Errorf("build linters: %w", err) + } + + m.linters = append(m.linters, linters...) + } + + for _, lc := range m.linters { for _, name := range lc.AllNames() { - nameToLCs[name] = append(nameToLCs[name], lc) + m.nameToLCs[name] = append(m.nameToLCs[name], lc) } } - m.nameToLCs = nameToLCs + err := NewValidator(m).Validate(m.cfg) + if err != nil { + return nil, err + } - return m + return m, nil } -func (Manager) AllPresets() []string { - return []string{ - linter.PresetBugs, - linter.PresetComment, - linter.PresetComplexity, - linter.PresetError, - linter.PresetFormatting, - linter.PresetImport, - linter.PresetMetaLinter, - linter.PresetModule, - linter.PresetPerformance, - linter.PresetSQL, - linter.PresetStyle, - linter.PresetTest, - linter.PresetUnused, - } +func (m *Manager) GetLinterConfigs(name string) []*linter.Config { + return m.nameToLCs[name] +} + +func (m *Manager) GetAllSupportedLinterConfigs() []*linter.Config { + return m.linters } -func (m Manager) allPresetsSet() map[string]bool { - ret := map[string]bool{} - for _, p := range m.AllPresets() { - ret[p] = true +func (m *Manager) GetAllLinterConfigsForPreset(p string) []*linter.Config { + var ret []*linter.Config + for _, lc := range m.linters { + if lc.IsDeprecated() { + continue + } + + if slices.Contains(lc.InPresets, p) { + ret = append(ret, lc) + } } + return ret } -func (m Manager) GetLinterConfigs(name string) []*linter.Config { - return m.nameToLCs[name] +func (m *Manager) GetEnabledLintersMap() (map[string]*linter.Config, error) { + enabledLinters := m.build(m.GetAllEnabledByDefaultLinters()) + + if os.Getenv(logutils.EnvTestRun) == "1" { + m.verbosePrintLintersStatus(enabledLinters) + } + + return enabledLinters, nil } -//nolint:funlen -func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { - var ( - asasalintCfg *config.AsasalintSettings - bidichkCfg *config.BiDiChkSettings - cyclopCfg *config.Cyclop - decorderCfg *config.DecorderSettings - depGuardCfg *config.DepGuardSettings - dogsledCfg *config.DogsledSettings - duplCfg *config.DuplSettings - dupwordCfg *config.DupWordSettings - errcheckCfg *config.ErrcheckSettings - errchkjsonCfg *config.ErrChkJSONSettings - errorlintCfg *config.ErrorLintSettings - exhaustiveCfg *config.ExhaustiveSettings - exhaustiveStructCfg *config.ExhaustiveStructSettings - exhaustructCfg *config.ExhaustructSettings - forbidigoCfg *config.ForbidigoSettings - funlenCfg *config.FunlenSettings - gciCfg *config.GciSettings - ginkgolinterCfg *config.GinkgoLinterSettings - gocognitCfg *config.GocognitSettings - goconstCfg *config.GoConstSettings - gocriticCfg *config.GoCriticSettings - gocycloCfg *config.GoCycloSettings - godotCfg *config.GodotSettings - godoxCfg *config.GodoxSettings - gofmtCfg *config.GoFmtSettings - gofumptCfg *config.GofumptSettings - goheaderCfg *config.GoHeaderSettings - goimportsCfg *config.GoImportsSettings - golintCfg *config.GoLintSettings - goMndCfg *config.GoMndSettings - goModDirectivesCfg *config.GoModDirectivesSettings - gomodguardCfg *config.GoModGuardSettings - gosecCfg *config.GoSecSettings - gosimpleCfg *config.StaticCheckSettings - gosmopolitanCfg *config.GosmopolitanSettings - govetCfg *config.GovetSettings - grouperCfg *config.GrouperSettings - ifshortCfg *config.IfshortSettings - importAsCfg *config.ImportAsSettings - inamedparamCfg *config.INamedParamSettings - interfaceBloatCfg *config.InterfaceBloatSettings - ireturnCfg *config.IreturnSettings - lllCfg *config.LllSettings - loggerCheckCfg *config.LoggerCheckSettings - maintIdxCfg *config.MaintIdxSettings - makezeroCfg *config.MakezeroSettings - malignedCfg *config.MalignedSettings - misspellCfg *config.MisspellSettings - musttagCfg *config.MustTagSettings - nakedretCfg *config.NakedretSettings - nestifCfg *config.NestifSettings - nilNilCfg *config.NilNilSettings - nlreturnCfg *config.NlreturnSettings - 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 - tagalignCfg *config.TagAlignSettings - tagliatelleCfg *config.TagliatelleSettings - tenvCfg *config.TenvSettings - testifylintCfg *config.TestifylintSettings - testpackageCfg *config.TestpackageSettings - thelperCfg *config.ThelperSettings - unparamCfg *config.UnparamSettings - unusedCfg *config.UnusedSettings - usestdlibvars *config.UseStdlibVarsSettings - varcheckCfg *config.VarCheckSettings - varnamelenCfg *config.VarnamelenSettings - whitespaceCfg *config.WhitespaceSettings - wrapcheckCfg *config.WrapcheckSettings - wslCfg *config.WSLSettings - ) - - if m.cfg != nil { - asasalintCfg = &m.cfg.LintersSettings.Asasalint - bidichkCfg = &m.cfg.LintersSettings.BiDiChk - cyclopCfg = &m.cfg.LintersSettings.Cyclop - decorderCfg = &m.cfg.LintersSettings.Decorder - depGuardCfg = &m.cfg.LintersSettings.Depguard - dogsledCfg = &m.cfg.LintersSettings.Dogsled - duplCfg = &m.cfg.LintersSettings.Dupl - dupwordCfg = &m.cfg.LintersSettings.DupWord - errcheckCfg = &m.cfg.LintersSettings.Errcheck - errchkjsonCfg = &m.cfg.LintersSettings.ErrChkJSON - errorlintCfg = &m.cfg.LintersSettings.ErrorLint - exhaustiveCfg = &m.cfg.LintersSettings.Exhaustive - exhaustiveStructCfg = &m.cfg.LintersSettings.ExhaustiveStruct - exhaustructCfg = &m.cfg.LintersSettings.Exhaustruct - forbidigoCfg = &m.cfg.LintersSettings.Forbidigo - funlenCfg = &m.cfg.LintersSettings.Funlen - gciCfg = &m.cfg.LintersSettings.Gci - ginkgolinterCfg = &m.cfg.LintersSettings.GinkgoLinter - gocognitCfg = &m.cfg.LintersSettings.Gocognit - goconstCfg = &m.cfg.LintersSettings.Goconst - gocriticCfg = &m.cfg.LintersSettings.Gocritic - gocycloCfg = &m.cfg.LintersSettings.Gocyclo - godotCfg = &m.cfg.LintersSettings.Godot - godoxCfg = &m.cfg.LintersSettings.Godox - gofmtCfg = &m.cfg.LintersSettings.Gofmt - gofumptCfg = &m.cfg.LintersSettings.Gofumpt - goheaderCfg = &m.cfg.LintersSettings.Goheader - goimportsCfg = &m.cfg.LintersSettings.Goimports - golintCfg = &m.cfg.LintersSettings.Golint - goMndCfg = &m.cfg.LintersSettings.Gomnd - goModDirectivesCfg = &m.cfg.LintersSettings.GoModDirectives - gomodguardCfg = &m.cfg.LintersSettings.Gomodguard - gosecCfg = &m.cfg.LintersSettings.Gosec - gosimpleCfg = &m.cfg.LintersSettings.Gosimple - gosmopolitanCfg = &m.cfg.LintersSettings.Gosmopolitan - govetCfg = &m.cfg.LintersSettings.Govet - 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 - loggerCheckCfg = &m.cfg.LintersSettings.LoggerCheck - maintIdxCfg = &m.cfg.LintersSettings.MaintIdx - makezeroCfg = &m.cfg.LintersSettings.Makezero - malignedCfg = &m.cfg.LintersSettings.Maligned - misspellCfg = &m.cfg.LintersSettings.Misspell - musttagCfg = &m.cfg.LintersSettings.MustTag - nakedretCfg = &m.cfg.LintersSettings.Nakedret - nestifCfg = &m.cfg.LintersSettings.Nestif - nilNilCfg = &m.cfg.LintersSettings.NilNil - nlreturnCfg = &m.cfg.LintersSettings.Nlreturn - noLintLintCfg = &m.cfg.LintersSettings.NoLintLint - noNamedReturnsCfg = &m.cfg.LintersSettings.NoNamedReturns - 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 - tagalignCfg = &m.cfg.LintersSettings.TagAlign - tagliatelleCfg = &m.cfg.LintersSettings.Tagliatelle - tenvCfg = &m.cfg.LintersSettings.Tenv - testifylintCfg = &m.cfg.LintersSettings.Testifylint - testpackageCfg = &m.cfg.LintersSettings.Testpackage - thelperCfg = &m.cfg.LintersSettings.Thelper - unparamCfg = &m.cfg.LintersSettings.Unparam - unusedCfg = &m.cfg.LintersSettings.Unused - usestdlibvars = &m.cfg.LintersSettings.UseStdlibVars - varcheckCfg = &m.cfg.LintersSettings.Varcheck - varnamelenCfg = &m.cfg.LintersSettings.Varnamelen - whitespaceCfg = &m.cfg.LintersSettings.Whitespace - wrapcheckCfg = &m.cfg.LintersSettings.Wrapcheck - wslCfg = &m.cfg.LintersSettings.WSL - - govetCfg.Go = m.cfg.Run.Go - - gocriticCfg.Go = trimGoVersion(m.cfg.Run.Go) - - if gofumptCfg.LangVersion == "" { - gofumptCfg.LangVersion = m.cfg.Run.Go - } +// GetOptimizedLinters returns enabled linters after optimization (merging) of multiple linters into a fewer number of linters. +// E.g. some go/analysis linters can be optimized into one metalinter for data reuse and speed up. +func (m *Manager) GetOptimizedLinters() ([]*linter.Config, error) { + resultLintersSet := m.build(m.GetAllEnabledByDefaultLinters()) + m.verbosePrintLintersStatus(resultLintersSet) + + m.combineGoAnalysisLinters(resultLintersSet) - // staticcheck related linters. - if staticcheckCfg.GoVersion == "" { - staticcheckCfg.GoVersion = trimGoVersion(m.cfg.Run.Go) + resultLinters := maps.Values(resultLintersSet) + + // Make order of execution of linters (go/analysis metalinter and unused) stable. + sort.Slice(resultLinters, func(i, j int) bool { + a, b := resultLinters[i], resultLinters[j] + + if b.Name() == linter.LastLinter { + return true } - if gosimpleCfg.GoVersion == "" { - gosimpleCfg.GoVersion = trimGoVersion(m.cfg.Run.Go) + + if a.Name() == linter.LastLinter { + return false } - if stylecheckCfg.GoVersion != "" { - stylecheckCfg.GoVersion = trimGoVersion(m.cfg.Run.Go) + + if a.DoesChangeTypes != b.DoesChangeTypes { + return b.DoesChangeTypes // move type-changing linters to the end to optimize speed } - } + return a.Name() < b.Name() + }) - const megacheckName = "megacheck" - - var linters []*linter.Config - linters = append(linters, m.customLinters...) - - // The linters are sorted in the alphabetical order (case-insensitive). - // When a new linter is added the version in `WithSince(...)` must be the next minor version of golangci-lint. - linters = append(linters, - linter.NewConfig(golinters.NewAsasalint(asasalintCfg)). - WithSince("1.47.0"). - WithPresets(linter.PresetBugs). - WithLoadForGoAnalysis(). - WithURL("https://github.com/alingse/asasalint"), - - linter.NewConfig(golinters.NewAsciicheck()). - WithSince("v1.26.0"). - WithPresets(linter.PresetBugs, linter.PresetStyle). - WithURL("https://github.com/tdakkota/asciicheck"), - - linter.NewConfig(golinters.NewBiDiChkFuncName(bidichkCfg)). - WithSince("1.43.0"). - WithPresets(linter.PresetBugs). - WithURL("https://github.com/breml/bidichk"), - - linter.NewConfig(golinters.NewBodyclose()). - WithSince("v1.18.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetPerformance, linter.PresetBugs). - WithURL("https://github.com/timakin/bodyclose"), - - linter.NewConfig(golinters.NewContainedCtx()). - WithSince("1.44.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/sivchari/containedctx"), - - linter.NewConfig(golinters.NewContextCheck()). - WithSince("v1.43.0"). - WithPresets(linter.PresetBugs). - WithLoadForGoAnalysis(). - WithURL("https://github.com/kkHAIKE/contextcheck"), - - linter.NewConfig(golinters.NewCyclop(cyclopCfg)). - WithSince("v1.37.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetComplexity). - WithURL("https://github.com/bkielbasa/cyclop"), - - linter.NewConfig(golinters.NewDecorder(decorderCfg)). - WithSince("v1.44.0"). - WithPresets(linter.PresetFormatting, linter.PresetStyle). - WithURL("https://gitlab.com/bosi/decorder"), - - linter.NewConfig(golinters.NewDeadcode()). - WithSince("v1.0.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetUnused). - WithURL("https://github.com/remyoudompheng/go-misc/tree/master/deadcode"). - Deprecated("The owner seems to have abandoned the linter.", "v1.49.0", "unused"), - - linter.NewConfig(golinters.NewDepguard(depGuardCfg)). - WithSince("v1.4.0"). - WithPresets(linter.PresetStyle, linter.PresetImport, linter.PresetModule). - WithURL("https://github.com/OpenPeeDeeP/depguard"), - - linter.NewConfig(golinters.NewDogsled(dogsledCfg)). - WithSince("v1.19.0"). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/alexkohler/dogsled"), - - linter.NewConfig(golinters.NewDupl(duplCfg)). - WithSince("v1.0.0"). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/mibk/dupl"), - - linter.NewConfig(golinters.NewDupWord(dupwordCfg)). - WithSince("1.50.0"). - WithPresets(linter.PresetComment). - WithAutoFix(). - WithURL("https://github.com/Abirdcfly/dupword"), - - linter.NewConfig(golinters.NewDurationCheck()). - WithSince("v1.37.0"). - WithPresets(linter.PresetBugs). - WithLoadForGoAnalysis(). - WithURL("https://github.com/charithe/durationcheck"), - - linter.NewConfig(golinters.NewErrcheck(errcheckCfg)). - WithEnabledByDefault(). - WithSince("v1.0.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetBugs, linter.PresetError). - WithURL("https://github.com/kisielk/errcheck"), - - linter.NewConfig(golinters.NewErrChkJSONFuncName(errchkjsonCfg)). - WithSince("1.44.0"). - WithPresets(linter.PresetBugs). - WithLoadForGoAnalysis(). - WithURL("https://github.com/breml/errchkjson"), - - linter.NewConfig(golinters.NewErrName()). - WithSince("v1.42.0"). - WithPresets(linter.PresetStyle). - WithLoadForGoAnalysis(). - WithURL("https://github.com/Antonboom/errname"), - - linter.NewConfig(golinters.NewErrorLint(errorlintCfg)). - WithSince("v1.32.0"). - WithPresets(linter.PresetBugs, linter.PresetError). - WithLoadForGoAnalysis(). - WithURL("https://github.com/polyfloyd/go-errorlint"), - - linter.NewConfig(golinters.NewExecInQuery()). - WithSince("v1.46.0"). - WithPresets(linter.PresetSQL). - WithLoadForGoAnalysis(). - WithURL("https://github.com/lufeee/execinquery"), - - linter.NewConfig(golinters.NewExhaustive(exhaustiveCfg)). - WithSince(" v1.28.0"). - WithPresets(linter.PresetBugs). - WithLoadForGoAnalysis(). - WithURL("https://github.com/nishanths/exhaustive"), - - linter.NewConfig(golinters.NewExhaustiveStruct(exhaustiveStructCfg)). - WithSince("v1.32.0"). - WithPresets(linter.PresetStyle, linter.PresetTest). - WithLoadForGoAnalysis(). - WithURL("https://github.com/mbilski/exhaustivestruct"). - Deprecated("The owner seems to have abandoned the linter.", "v1.46.0", "exhaustruct"), - - linter.NewConfig(golinters.NewExhaustruct(exhaustructCfg)). - WithSince("v1.46.0"). - WithPresets(linter.PresetStyle, linter.PresetTest). - WithLoadForGoAnalysis(). - WithURL("https://github.com/GaijinEntertainment/go-exhaustruct"), - - linter.NewConfig(golinters.NewExportLoopRef()). - WithSince("v1.28.0"). - WithPresets(linter.PresetBugs). - WithLoadForGoAnalysis(). - WithURL("https://github.com/kyoh86/exportloopref"), - - linter.NewConfig(golinters.NewForbidigo(forbidigoCfg)). - WithSince("v1.34.0"). - WithPresets(linter.PresetStyle). - // Strictly speaking, - // the additional information is only needed when forbidigoCfg.AnalyzeTypes is chosen by the user. - // But we don't know that here in all cases (sometimes config is not loaded), - // so we have to assume that it is needed to be on the safe side. - WithLoadForGoAnalysis(). - WithURL("https://github.com/ashanbrown/forbidigo"), - - linter.NewConfig(golinters.NewForceTypeAssert()). - WithSince("v1.38.0"). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/gostaticanalysis/forcetypeassert"), - - linter.NewConfig(golinters.NewFunlen(funlenCfg)). - WithSince("v1.18.0"). - WithPresets(linter.PresetComplexity). - WithURL("https://github.com/ultraware/funlen"), - - linter.NewConfig(golinters.NewGci(gciCfg)). - WithSince("v1.30.0"). - WithPresets(linter.PresetFormatting, linter.PresetImport). - WithURL("https://github.com/daixiang0/gci"), - - linter.NewConfig(golinters.NewGinkgoLinter(ginkgolinterCfg)). - WithSince("v1.51.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/nunnatsa/ginkgolinter"), - - linter.NewConfig(golinters.NewGoCheckCompilerDirectives()). - WithSince("v1.51.0"). - WithPresets(linter.PresetBugs). - WithURL("https://github.com/leighmcculloch/gocheckcompilerdirectives"), - - linter.NewConfig(golinters.NewGochecknoglobals()). - WithSince("v1.12.0"). - WithPresets(linter.PresetStyle). - WithLoadForGoAnalysis(). - WithURL("https://github.com/leighmcculloch/gochecknoglobals"), - - linter.NewConfig(golinters.NewGochecknoinits()). - WithSince("v1.12.0"). - WithPresets(linter.PresetStyle), - - linter.NewConfig(golinters.NewGoCheckSumType()). - WithSince("v1.55.0"). - WithPresets(linter.PresetBugs). - WithLoadForGoAnalysis(). - WithURL("https://github.com/alecthomas/go-check-sumtype"), - - linter.NewConfig(golinters.NewGocognit(gocognitCfg)). - WithSince("v1.20.0"). - WithPresets(linter.PresetComplexity). - WithURL("https://github.com/uudashr/gocognit"), - - linter.NewConfig(golinters.NewGoconst(goconstCfg)). - WithSince("v1.0.0"). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/jgautheron/goconst"), - - linter.NewConfig(golinters.NewGoCritic(gocriticCfg, m.cfg)). - WithSince("v1.12.0"). - WithPresets(linter.PresetStyle, linter.PresetMetaLinter). - WithLoadForGoAnalysis(). - WithURL("https://github.com/go-critic/go-critic"), - - linter.NewConfig(golinters.NewGocyclo(gocycloCfg)). - WithSince("v1.0.0"). - WithPresets(linter.PresetComplexity). - WithURL("https://github.com/fzipp/gocyclo"), - - linter.NewConfig(golinters.NewGodot(godotCfg)). - WithSince("v1.25.0"). - WithPresets(linter.PresetStyle, linter.PresetComment). - WithAutoFix(). - WithURL("https://github.com/tetafro/godot"), - - linter.NewConfig(golinters.NewGodox(godoxCfg)). - WithSince("v1.19.0"). - WithPresets(linter.PresetStyle, linter.PresetComment). - WithURL("https://github.com/matoous/godox"), - - linter.NewConfig(golinters.NewGoerr113()). - WithSince("v1.26.0"). - WithPresets(linter.PresetStyle, linter.PresetError). - WithLoadForGoAnalysis(). - WithURL("https://github.com/Djarvur/go-err113"), - - linter.NewConfig(golinters.NewGofmt(gofmtCfg)). - WithSince("v1.0.0"). - WithPresets(linter.PresetFormatting). - WithAutoFix(). - WithURL("https://pkg.go.dev/cmd/gofmt"), - - linter.NewConfig(golinters.NewGofumpt(gofumptCfg)). - WithSince("v1.28.0"). - WithPresets(linter.PresetFormatting). - WithAutoFix(). - WithURL("https://github.com/mvdan/gofumpt"), - - linter.NewConfig(golinters.NewGoHeader(goheaderCfg)). - WithSince("v1.28.0"). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/denis-tingaikin/go-header"), - - linter.NewConfig(golinters.NewGoimports(goimportsCfg)). - WithSince("v1.20.0"). - WithPresets(linter.PresetFormatting, linter.PresetImport). - WithAutoFix(). - WithURL("https://pkg.go.dev/golang.org/x/tools/cmd/goimports"), - - linter.NewConfig(golinters.NewGolint(golintCfg)). - WithSince("v1.0.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/golang/lint"). - Deprecated("The repository of the linter has been archived by the owner.", "v1.41.0", "revive"), - - linter.NewConfig(golinters.NewGoMND(goMndCfg)). - WithSince("v1.22.0"). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/tommy-muehle/go-mnd"), - - linter.NewConfig(golinters.NewGoModDirectives(goModDirectivesCfg)). - WithSince("v1.39.0"). - WithPresets(linter.PresetStyle, linter.PresetModule). - WithURL("https://github.com/ldez/gomoddirectives"), - - linter.NewConfig(golinters.NewGomodguard(gomodguardCfg)). - WithSince("v1.25.0"). - WithPresets(linter.PresetStyle, linter.PresetImport, linter.PresetModule). - WithURL("https://github.com/ryancurrah/gomodguard"), - - linter.NewConfig(golinters.NewGoPrintfFuncName()). - WithSince("v1.23.0"). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/jirfag/go-printf-func-name"), - - linter.NewConfig(golinters.NewGosec(gosecCfg)). - WithSince("v1.0.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetBugs). - WithURL("https://github.com/securego/gosec"). - WithAlternativeNames("gas"), - - linter.NewConfig(golinters.NewGosimple(gosimpleCfg)). - WithEnabledByDefault(). - WithSince("v1.20.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetStyle). - WithAlternativeNames(megacheckName). - WithURL("https://github.com/dominikh/go-tools/tree/master/simple"), - - linter.NewConfig(golinters.NewGosmopolitan(gosmopolitanCfg)). - WithSince("v1.53.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetBugs). - WithURL("https://github.com/xen0n/gosmopolitan"), - - linter.NewConfig(golinters.NewGovet(govetCfg)). - WithEnabledByDefault(). - WithSince("v1.0.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetBugs, linter.PresetMetaLinter). - WithAlternativeNames("vet", "vetshadow"). - WithURL("https://pkg.go.dev/cmd/vet"), - - linter.NewConfig(golinters.NewGrouper(grouperCfg)). - WithSince("v1.44.0"). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/leonklingele/grouper"), - - linter.NewConfig(golinters.NewIfshort(ifshortCfg)). - WithSince("v1.36.0"). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/esimonov/ifshort"). - Deprecated("The repository of the linter has been deprecated by the owner.", "v1.48.0", ""), - - linter.NewConfig(golinters.NewImportAs(importAsCfg)). - WithSince("v1.38.0"). - WithPresets(linter.PresetStyle). - WithLoadForGoAnalysis(). - WithURL("https://github.com/julz/importas"), - - linter.NewConfig(golinters.NewINamedParam(inamedparamCfg)). - WithSince("v1.55.0"). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/macabu/inamedparam"), - - linter.NewConfig(golinters.NewIneffassign()). - WithEnabledByDefault(). - WithSince("v1.0.0"). - WithPresets(linter.PresetUnused). - WithURL("https://github.com/gordonklaus/ineffassign"), - - linter.NewConfig(golinters.NewInterfaceBloat(interfaceBloatCfg)). - WithSince("v1.49.0"). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/sashamelentyev/interfacebloat"), - - linter.NewConfig(golinters.NewInterfacer()). - WithSince("v1.0.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/mvdan/interfacer"). - Deprecated("The repository of the linter has been archived by the owner.", "v1.38.0", ""), - - linter.NewConfig(golinters.NewIreturn(ireturnCfg)). - WithSince("v1.43.0"). - WithPresets(linter.PresetStyle). - WithLoadForGoAnalysis(). - WithURL("https://github.com/butuzov/ireturn"), - - linter.NewConfig(golinters.NewLLL(lllCfg)). - WithSince("v1.8.0"). - WithPresets(linter.PresetStyle), - - linter.NewConfig(golinters.NewLoggerCheck(loggerCheckCfg)). - WithSince("v1.49.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetStyle, linter.PresetBugs). - WithAlternativeNames("logrlint"). - WithURL("https://github.com/timonwong/loggercheck"), - - linter.NewConfig(golinters.NewMaintIdx(maintIdxCfg)). - WithSince("v1.44.0"). - WithPresets(linter.PresetComplexity). - WithURL("https://github.com/yagipy/maintidx"), - - linter.NewConfig(golinters.NewMakezero(makezeroCfg)). - WithSince("v1.34.0"). - WithPresets(linter.PresetStyle, linter.PresetBugs). - WithLoadForGoAnalysis(). - WithURL("https://github.com/ashanbrown/makezero"), - - linter.NewConfig(golinters.NewMaligned(malignedCfg)). - WithSince("v1.0.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetPerformance). - WithURL("https://github.com/mdempsky/maligned"). - Deprecated("The repository of the linter has been archived by the owner.", "v1.38.0", "govet 'fieldalignment'"), - - linter.NewConfig(golinters.NewMirror()). - WithSince("v1.53.0"). - WithPresets(linter.PresetStyle). - WithLoadForGoAnalysis(). - WithURL("https://github.com/butuzov/mirror"), - - linter.NewConfig(golinters.NewMisspell(misspellCfg)). - WithSince("v1.8.0"). - WithPresets(linter.PresetStyle, linter.PresetComment). - WithAutoFix(). - WithURL("https://github.com/client9/misspell"), - - linter.NewConfig(golinters.NewMustTag(musttagCfg)). - WithSince("v1.51.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetStyle, linter.PresetBugs). - WithURL("https://github.com/go-simpler/musttag"), - - linter.NewConfig(golinters.NewNakedret(nakedretCfg)). - WithSince("v1.19.0"). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/alexkohler/nakedret"), - - linter.NewConfig(golinters.NewNestif(nestifCfg)). - WithSince("v1.25.0"). - WithPresets(linter.PresetComplexity). - WithURL("https://github.com/nakabonne/nestif"), - - linter.NewConfig(golinters.NewNilErr()). - WithSince("v1.38.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetBugs). - WithURL("https://github.com/gostaticanalysis/nilerr"), - - linter.NewConfig(golinters.NewNilNil(nilNilCfg)). - WithSince("v1.43.0"). - WithPresets(linter.PresetStyle). - WithLoadForGoAnalysis(). - WithURL("https://github.com/Antonboom/nilnil"), - - linter.NewConfig(golinters.NewNLReturn(nlreturnCfg)). - WithSince("v1.30.0"). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/ssgreg/nlreturn"), - - linter.NewConfig(golinters.NewNoctx()). - WithSince("v1.28.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetPerformance, linter.PresetBugs). - WithURL("https://github.com/sonatard/noctx"), - - linter.NewConfig(golinters.NewNoNamedReturns(noNamedReturnsCfg)). - WithSince("v1.46.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/firefart/nonamedreturns"), - - linter.NewConfig(golinters.NewNoSnakeCase()). - WithSince("v1.47.0"). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/sivchari/nosnakecase"). - Deprecated("The repository of the linter has been deprecated by the owner.", "v1.48.1", "revive(var-naming)"), - - linter.NewConfig(golinters.NewNoSprintfHostPort()). - WithSince("v1.46.0"). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/stbenjam/no-sprintf-host-port"), - - linter.NewConfig(golinters.NewParallelTest(parallelTestCfg)). - WithSince("v1.33.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetStyle, linter.PresetTest). - WithURL("https://github.com/kunwardeep/paralleltest"), - - linter.NewConfig(golinters.NewPerfSprint(perfSprintCfg)). - WithSince("v1.55.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetPerformance). - WithURL("https://github.com/catenacyber/perfsprint"), - - linter.NewConfig(golinters.NewPreAlloc(preallocCfg)). - WithSince("v1.19.0"). - WithPresets(linter.PresetPerformance). - WithURL("https://github.com/alexkohler/prealloc"), - - linter.NewConfig(golinters.NewPredeclared(predeclaredCfg)). - WithSince("v1.35.0"). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/nishanths/predeclared"), - - linter.NewConfig(golinters.NewPromlinter(promlinterCfg)). - WithSince("v1.40.0"). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/yeya24/promlinter"), - - linter.NewConfig(golinters.NewProtoGetter(protogetterCfg)). - WithSince("v1.55.0"). - WithPresets(linter.PresetBugs). - WithLoadForGoAnalysis(). - WithAutoFix(). - WithURL("https://github.com/ghostiam/protogetter"), - - linter.NewConfig(golinters.NewReassign(reassignCfg)). - WithSince("1.49.0"). - WithPresets(linter.PresetBugs). - WithLoadForGoAnalysis(). - WithURL("https://github.com/curioswitch/go-reassign"), - - linter.NewConfig(golinters.NewRevive(reviveCfg)). - WithSince("v1.37.0"). - WithPresets(linter.PresetStyle, linter.PresetMetaLinter). - ConsiderSlow(). - WithURL("https://github.com/mgechev/revive"), - - linter.NewConfig(golinters.NewRowsErrCheck(rowserrcheckCfg)). - WithSince("v1.23.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetBugs, linter.PresetSQL). - WithURL("https://github.com/jingyugao/rowserrcheck"), - - linter.NewConfig(golinters.NewSlogLint(sloglintCfg)). - WithSince("v1.55.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetStyle, linter.PresetFormatting). - WithURL("https://github.com/go-simpler/sloglint"), - - linter.NewConfig(golinters.NewScopelint()). - WithSince("v1.12.0"). - WithPresets(linter.PresetBugs). - WithURL("https://github.com/kyoh86/scopelint"). - Deprecated("The repository of the linter has been deprecated by the owner.", "v1.39.0", "exportloopref"), - - linter.NewConfig(golinters.NewSQLCloseCheck()). - WithSince("v1.28.0"). - WithPresets(linter.PresetBugs, linter.PresetSQL). - 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"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetBugs, linter.PresetMetaLinter). - WithAlternativeNames(megacheckName). - WithURL("https://staticcheck.io/"), - - linter.NewConfig(golinters.NewStructcheck(structcheckCfg)). - WithSince("v1.0.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetUnused). - WithURL("https://github.com/opennota/check"). - Deprecated("The owner seems to have abandoned the linter.", "v1.49.0", "unused"), - - linter.NewConfig(golinters.NewStylecheck(stylecheckCfg)). - WithSince("v1.20.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/dominikh/go-tools/tree/master/stylecheck"), - - linter.NewConfig(golinters.NewTagAlign(tagalignCfg)). - WithSince("v1.53.0"). - WithPresets(linter.PresetStyle, linter.PresetFormatting). - WithAutoFix(). - WithURL("https://github.com/4meepo/tagalign"), - - linter.NewConfig(golinters.NewTagliatelle(tagliatelleCfg)). - WithSince("v1.40.0"). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/ldez/tagliatelle"), - - linter.NewConfig(golinters.NewTenv(tenvCfg)). - WithSince("v1.43.0"). - WithPresets(linter.PresetStyle). - WithLoadForGoAnalysis(). - WithURL("https://github.com/sivchari/tenv"), - - linter.NewConfig(golinters.NewTestableexamples()). - WithSince("v1.50.0"). - WithPresets(linter.PresetTest). - WithURL("https://github.com/maratori/testableexamples"), - - linter.NewConfig(golinters.NewTestifylint(testifylintCfg)). - WithSince("v1.55.0"). - WithPresets(linter.PresetTest, linter.PresetBugs). - WithLoadForGoAnalysis(). - WithURL("https://github.com/Antonboom/testifylint"), - - linter.NewConfig(golinters.NewTestpackage(testpackageCfg)). - WithSince("v1.25.0"). - WithPresets(linter.PresetStyle, linter.PresetTest). - WithURL("https://github.com/maratori/testpackage"), - - linter.NewConfig(golinters.NewThelper(thelperCfg)). - WithSince("v1.34.0"). - WithPresets(linter.PresetStyle). - WithLoadForGoAnalysis(). - WithURL("https://github.com/kulti/thelper"), - - linter.NewConfig(golinters.NewTparallel()). - WithSince("v1.32.0"). - WithPresets(linter.PresetStyle, linter.PresetTest). - WithLoadForGoAnalysis(). - WithURL("https://github.com/moricho/tparallel"), - - linter.NewConfig(golinters.NewTypecheck()). - WithInternal(). - WithEnabledByDefault(). - WithSince("v1.3.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetBugs). - WithURL(""), - - linter.NewConfig(golinters.NewUnconvert()). - WithSince("v1.0.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/mdempsky/unconvert"), - - linter.NewConfig(golinters.NewUnparam(unparamCfg)). - WithSince("v1.9.0"). - WithPresets(linter.PresetUnused). - WithLoadForGoAnalysis(). - WithURL("https://github.com/mvdan/unparam"), - - linter.NewConfig(golinters.NewUnused(unusedCfg, staticcheckCfg)). - WithEnabledByDefault(). - WithSince("v1.20.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetUnused). - WithAlternativeNames(megacheckName). - ConsiderSlow(). - WithChangeTypes(). - WithURL("https://github.com/dominikh/go-tools/tree/master/unused"), - - linter.NewConfig(golinters.NewUseStdlibVars(usestdlibvars)). - WithSince("v1.48.0"). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/sashamelentyev/usestdlibvars"), - - linter.NewConfig(golinters.NewVarcheck(varcheckCfg)). - WithSince("v1.0.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetUnused). - WithURL("https://github.com/opennota/check"). - Deprecated("The owner seems to have abandoned the linter.", "v1.49.0", "unused"), - - linter.NewConfig(golinters.NewVarnamelen(varnamelenCfg)). - WithSince("v1.43.0"). - WithPresets(linter.PresetStyle). - WithLoadForGoAnalysis(). - WithURL("https://github.com/blizzy78/varnamelen"), - - linter.NewConfig(golinters.NewWastedAssign()). - WithSince("v1.38.0"). - WithPresets(linter.PresetStyle). - WithLoadForGoAnalysis(). - WithURL("https://github.com/sanposhiho/wastedassign"), - - linter.NewConfig(golinters.NewWhitespace(whitespaceCfg)). - WithSince("v1.19.0"). - WithPresets(linter.PresetStyle). - WithAutoFix(). - WithURL("https://github.com/ultraware/whitespace"), - - linter.NewConfig(golinters.NewWrapcheck(wrapcheckCfg)). - WithSince("v1.32.0"). - WithPresets(linter.PresetStyle, linter.PresetError). - WithLoadForGoAnalysis(). - WithURL("https://github.com/tomarrell/wrapcheck"), - - linter.NewConfig(golinters.NewWSL(wslCfg)). - WithSince("v1.20.0"). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/bombsimon/wsl"), - - linter.NewConfig(golinters.NewZerologLint()). - WithSince("v1.53.0"). - WithPresets(linter.PresetBugs). - WithLoadForGoAnalysis(). - WithURL("https://github.com/ykadowak/zerologlint"), - - // nolintlint must be last because it looks at the results of all the previous linters for unused nolint directives - linter.NewConfig(golinters.NewNoLintLint(noLintLintCfg)). - WithSince("v1.26.0"). - WithPresets(linter.PresetStyle). - WithURL("https://github.com/golangci/golangci-lint/blob/master/pkg/golinters/nolintlint/README.md"), - ) - - return linters + return resultLinters, nil } -func (m Manager) GetAllEnabledByDefaultLinters() []*linter.Config { +func (m *Manager) GetAllEnabledByDefaultLinters() []*linter.Config { var ret []*linter.Config - for _, lc := range m.GetAllSupportedLinterConfigs() { + for _, lc := range m.linters { if lc.EnabledByDefault { ret = append(ret, lc) } @@ -951,48 +143,168 @@ func (m Manager) GetAllEnabledByDefaultLinters() []*linter.Config { return ret } -func linterConfigsToMap(lcs []*linter.Config) map[string]*linter.Config { - ret := map[string]*linter.Config{} - for _, lc := range lcs { - lc := lc // local copy - ret[lc.Name()] = lc +//nolint:gocyclo // the complexity cannot be reduced. +func (m *Manager) build(enabledByDefaultLinters []*linter.Config) map[string]*linter.Config { + m.debugf("Linters config: %#v", m.cfg.Linters) + + resultLintersSet := map[string]*linter.Config{} + switch { + case m.cfg.Linters.DisableAll: + // no default linters + case len(m.cfg.Linters.Presets) != 0: + // imply --disable-all + case m.cfg.Linters.EnableAll: + resultLintersSet = linterConfigsToMap(m.linters) + default: + resultLintersSet = linterConfigsToMap(enabledByDefaultLinters) } - return ret + // --presets can only add linters to default set + for _, p := range m.cfg.Linters.Presets { + for _, lc := range m.GetAllLinterConfigsForPreset(p) { + lc := lc + resultLintersSet[lc.Name()] = lc + } + } + + // --fast removes slow linters from current set. + // It should be after --presets to be able to run only fast linters in preset. + // It should be before --enable and --disable to be able to enable or disable specific linter. + if m.cfg.Linters.Fast { + for name, lc := range resultLintersSet { + if lc.IsSlowLinter() { + delete(resultLintersSet, name) + } + } + } + + for _, name := range m.cfg.Linters.Enable { + for _, lc := range m.GetLinterConfigs(name) { + // it's important to use lc.Name() nor name because name can be alias + resultLintersSet[lc.Name()] = lc + } + } + + for _, name := range m.cfg.Linters.Disable { + for _, lc := range m.GetLinterConfigs(name) { + // it's important to use lc.Name() nor name because name can be alias + delete(resultLintersSet, lc.Name()) + } + } + + // typecheck is not a real linter and cannot be disabled. + if _, ok := resultLintersSet["typecheck"]; !ok && (m.cfg == nil || !m.cfg.InternalCmdTest) { + for _, lc := range m.GetLinterConfigs("typecheck") { + // it's important to use lc.Name() nor name because name can be alias + resultLintersSet[lc.Name()] = lc + } + } + + return resultLintersSet } -func (m Manager) GetAllLinterConfigsForPreset(p string) []*linter.Config { - var ret []*linter.Config - for _, lc := range m.GetAllSupportedLinterConfigs() { - if lc.IsDeprecated() { +func (m *Manager) combineGoAnalysisLinters(linters map[string]*linter.Config) { + var goanalysisLinters []*goanalysis.Linter + goanalysisPresets := map[string]bool{} + for _, lc := range linters { + lnt, ok := lc.Linter.(*goanalysis.Linter) + if !ok { + continue + } + if lnt.LoadMode() == goanalysis.LoadModeWholeProgram { + // It's ineffective by CPU and memory to run whole-program and incremental analyzers at once. continue } + goanalysisLinters = append(goanalysisLinters, lnt) + for _, p := range lc.InPresets { + goanalysisPresets[p] = true + } + } - for _, ip := range lc.InPresets { - if p == ip { - ret = append(ret, lc) - break - } + if len(goanalysisLinters) <= 1 { + m.debugf("Didn't combine go/analysis linters: got only %d linters", len(goanalysisLinters)) + return + } + + for _, lnt := range goanalysisLinters { + delete(linters, lnt.Name()) + } + + // Make order of execution of go/analysis analyzers stable. + sort.Slice(goanalysisLinters, func(i, j int) bool { + a, b := goanalysisLinters[i], goanalysisLinters[j] + + if b.Name() == linter.LastLinter { + return true } + + if a.Name() == linter.LastLinter { + return false + } + + return a.Name() <= b.Name() + }) + + ml := goanalysis.NewMetaLinter(goanalysisLinters) + + presets := maps.Keys(goanalysisPresets) + sort.Strings(presets) + + mlConfig := &linter.Config{ + Linter: ml, + EnabledByDefault: false, + InPresets: presets, + AlternativeNames: nil, + OriginalURL: "", } - return ret + mlConfig = mlConfig.WithLoadForGoAnalysis() + + linters[ml.Name()] = mlConfig + m.debugf("Combined %d go/analysis linters into one metalinter", len(goanalysisLinters)) } -// Trims the Go version to keep only M.m. -// Since Go 1.21 the version inside the go.mod can be a patched version (ex: 1.21.0). -// https://go.dev/doc/toolchain#versions -// This a problem with staticcheck and gocritic. -func trimGoVersion(v string) string { - if v == "" { - return "" +func (m *Manager) verbosePrintLintersStatus(lcs map[string]*linter.Config) { + var linterNames []string + for _, lc := range lcs { + if lc.Internal { + continue + } + + linterNames = append(linterNames, lc.Name()) } + sort.Strings(linterNames) + m.log.Infof("Active %d linters: %s", len(linterNames), linterNames) - exp := regexp.MustCompile(`(\d\.\d+)(?:\.\d+|[a-z]+\d)`) + if len(m.cfg.Linters.Presets) != 0 { + sort.Strings(m.cfg.Linters.Presets) + m.log.Infof("Active presets: %s", m.cfg.Linters.Presets) + } +} - if exp.MatchString(v) { - return exp.FindStringSubmatch(v)[1] +func AllPresets() []string { + return []string{ + linter.PresetBugs, + linter.PresetComment, + linter.PresetComplexity, + linter.PresetError, + linter.PresetFormatting, + linter.PresetImport, + linter.PresetMetaLinter, + linter.PresetModule, + linter.PresetPerformance, + linter.PresetSQL, + linter.PresetStyle, + linter.PresetTest, + linter.PresetUnused, + } +} + +func linterConfigsToMap(lcs []*linter.Config) map[string]*linter.Config { + ret := map[string]*linter.Config{} + for _, lc := range lcs { + ret[lc.Name()] = lc } - return v + return ret } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/validator.go b/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/validator.go index 52a70d8590..364d5082a5 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/validator.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/validator.go @@ -3,6 +3,7 @@ package lintersdb import ( "errors" "fmt" + "slices" "strings" "github.com/golangci/golangci-lint/pkg/config" @@ -13,9 +14,29 @@ type Validator struct { } func NewValidator(m *Manager) *Validator { - return &Validator{ - m: m, + return &Validator{m: m} +} + +// Validate validates the configuration by calling all other validators for different +// sections in the configuration and then some additional linter validation functions. +func (v Validator) Validate(cfg *config.Config) error { + err := cfg.Validate() + if err != nil { + return err + } + + validators := []func(cfg *config.Linters) error{ + v.validateLintersNames, + v.validatePresets, + } + + for _, v := range validators { + if err := v(&cfg.Linters); err != nil { + return err + } } + + return nil } func (v Validator) validateLintersNames(cfg *config.Linters) error { @@ -39,11 +60,12 @@ func (v Validator) validateLintersNames(cfg *config.Linters) error { } func (v Validator) validatePresets(cfg *config.Linters) error { - allPresets := v.m.allPresetsSet() + presets := AllPresets() + for _, p := range cfg.Presets { - if !allPresets[p] { + if !slices.Contains(presets, p) { return fmt.Errorf("no such preset %q: only next presets exist: (%s)", - p, strings.Join(v.m.AllPresets(), "|")) + p, strings.Join(presets, "|")) } } @@ -53,56 +75,3 @@ func (v Validator) validatePresets(cfg *config.Linters) error { return nil } - -func (v Validator) validateAllDisableEnableOptions(cfg *config.Linters) error { - if cfg.EnableAll && cfg.DisableAll { - return errors.New("--enable-all and --disable-all options must not be combined") - } - - if cfg.DisableAll { - if len(cfg.Enable) == 0 && len(cfg.Presets) == 0 { - return errors.New("all linters were disabled, but no one linter was enabled: must enable at least one") - } - - if len(cfg.Disable) != 0 { - return fmt.Errorf("can't combine options --disable-all and --disable %s", cfg.Disable[0]) - } - } - - if cfg.EnableAll && len(cfg.Enable) != 0 && !cfg.Fast { - return fmt.Errorf("can't combine options --enable-all and --enable %s", cfg.Enable[0]) - } - - return nil -} - -func (v Validator) validateDisabledAndEnabledAtOneMoment(cfg *config.Linters) error { - enabledLintersSet := map[string]bool{} - for _, name := range cfg.Enable { - enabledLintersSet[name] = true - } - - for _, name := range cfg.Disable { - if enabledLintersSet[name] { - return fmt.Errorf("linter %q can't be disabled and enabled at one moment", name) - } - } - - return nil -} - -func (v Validator) validateEnabledDisabledLintersConfig(cfg *config.Linters) error { - validators := []func(cfg *config.Linters) error{ - v.validateLintersNames, - v.validatePresets, - v.validateAllDisableEnableOptions, - v.validateDisabledAndEnabledAtOneMoment, - } - for _, v := range validators { - if err := v(cfg); err != nil { - return err - } - } - - return nil -} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/lint/load.go b/vendor/github.com/golangci/golangci-lint/pkg/lint/package.go similarity index 52% rename from vendor/github.com/golangci/golangci-lint/pkg/lint/load.go rename to vendor/github.com/golangci/golangci-lint/pkg/lint/package.go index babad5ba60..e8222a4554 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/lint/load.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/lint/package.go @@ -13,151 +13,97 @@ import ( "golang.org/x/tools/go/packages" - "github.com/golangci/golangci-lint/internal/pkgcache" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/exitcodes" - "github.com/golangci/golangci-lint/pkg/fsutils" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis/load" "github.com/golangci/golangci-lint/pkg/goutil" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/logutils" ) -type ContextLoader struct { - cfg *config.Config - log logutils.Log - debugf logutils.DebugFunc - goenv *goutil.Env +// PackageLoader loads packages based on [golang.org/x/tools/go/packages.Load]. +type PackageLoader struct { + log logutils.Log + debugf logutils.DebugFunc + + cfg *config.Config + + args []string + pkgTestIDRe *regexp.Regexp - lineCache *fsutils.LineCache - fileCache *fsutils.FileCache - pkgCache *pkgcache.Cache - loadGuard *load.Guard + + goenv *goutil.Env + + loadGuard *load.Guard } -func NewContextLoader(cfg *config.Config, log logutils.Log, goenv *goutil.Env, - lineCache *fsutils.LineCache, fileCache *fsutils.FileCache, pkgCache *pkgcache.Cache, loadGuard *load.Guard) *ContextLoader { - return &ContextLoader{ +// NewPackageLoader creates a new PackageLoader. +func NewPackageLoader(log logutils.Log, cfg *config.Config, args []string, goenv *goutil.Env, loadGuard *load.Guard) *PackageLoader { + return &PackageLoader{ cfg: cfg, + args: args, log: log, debugf: logutils.Debug(logutils.DebugKeyLoader), goenv: goenv, pkgTestIDRe: regexp.MustCompile(`^(.*) \[(.*)\.test\]`), - lineCache: lineCache, - fileCache: fileCache, - pkgCache: pkgCache, loadGuard: loadGuard, } } -func (cl *ContextLoader) prepareBuildContext() { - // Set GOROOT to have working cross-compilation: cross-compiled binaries - // have invalid GOROOT. XXX: can't use runtime.GOROOT(). - goroot := cl.goenv.Get(goutil.EnvGoRoot) - if goroot == "" { - return - } - - os.Setenv(string(goutil.EnvGoRoot), goroot) - build.Default.GOROOT = goroot - build.Default.BuildTags = cl.cfg.Run.BuildTags -} +// Load loads packages. +func (l *PackageLoader) Load(ctx context.Context, linters []*linter.Config) (pkgs, deduplicatedPkgs []*packages.Package, err error) { + loadMode := findLoadMode(linters) -func (cl *ContextLoader) findLoadMode(linters []*linter.Config) packages.LoadMode { - loadMode := packages.LoadMode(0) - for _, lc := range linters { - loadMode |= lc.LoadMode - } - - return loadMode -} - -func (cl *ContextLoader) buildArgs() []string { - args := cl.cfg.Run.Args - if len(args) == 0 { - return []string{"./..."} - } - - var retArgs []string - for _, arg := range args { - if strings.HasPrefix(arg, ".") || filepath.IsAbs(arg) { - retArgs = append(retArgs, arg) - } else { - // go/packages doesn't work well if we don't have the prefix ./ for local packages - retArgs = append(retArgs, fmt.Sprintf(".%c%s", filepath.Separator, arg)) - } + pkgs, err = l.loadPackages(ctx, loadMode) + if err != nil { + return nil, nil, fmt.Errorf("failed to load packages: %w", err) } - return retArgs + return pkgs, l.filterDuplicatePackages(pkgs), nil } -func (cl *ContextLoader) makeBuildFlags() ([]string, error) { - var buildFlags []string - - if len(cl.cfg.Run.BuildTags) != 0 { - // go help build - buildFlags = append(buildFlags, "-tags", strings.Join(cl.cfg.Run.BuildTags, " ")) - cl.log.Infof("Using build tags: %v", cl.cfg.Run.BuildTags) - } +func (l *PackageLoader) loadPackages(ctx context.Context, loadMode packages.LoadMode) ([]*packages.Package, error) { + defer func(startedAt time.Time) { + l.log.Infof("Go packages loading at mode %s took %s", stringifyLoadMode(loadMode), time.Since(startedAt)) + }(time.Now()) - mod := cl.cfg.Run.ModulesDownloadMode - if mod != "" { - // go help modules - allowedMods := []string{"mod", "readonly", "vendor"} - var ok bool - for _, am := range allowedMods { - if am == mod { - ok = true - break - } - } - if !ok { - return nil, fmt.Errorf("invalid modules download path %s, only (%s) allowed", mod, strings.Join(allowedMods, "|")) - } + l.prepareBuildContext() - buildFlags = append(buildFlags, fmt.Sprintf("-mod=%s", cl.cfg.Run.ModulesDownloadMode)) + conf := &packages.Config{ + Mode: loadMode, + Tests: l.cfg.Run.AnalyzeTests, + Context: ctx, + BuildFlags: l.makeBuildFlags(), + Logf: l.debugf, + // TODO: use fset, parsefile, overlay } - return buildFlags, nil -} - -func stringifyLoadMode(mode packages.LoadMode) string { - m := map[packages.LoadMode]string{ - packages.NeedCompiledGoFiles: "compiled_files", - packages.NeedDeps: "deps", - packages.NeedExportFile: "exports_file", - packages.NeedFiles: "files", - packages.NeedImports: "imports", - packages.NeedName: "name", - packages.NeedSyntax: "syntax", - packages.NeedTypes: "types", - packages.NeedTypesInfo: "types_info", - packages.NeedTypesSizes: "types_sizes", + args := l.buildArgs() + l.debugf("Built loader args are %s", args) + pkgs, err := packages.Load(conf, args...) + if err != nil { + return nil, fmt.Errorf("failed to load with go/packages: %w", err) } - var flags []string - for flag, flagStr := range m { - if mode&flag != 0 { - flags = append(flags, flagStr) - } + if loadMode&packages.NeedSyntax == 0 { + // Needed e.g. for go/analysis loading. + fset := token.NewFileSet() + packages.Visit(pkgs, nil, func(pkg *packages.Package) { + pkg.Fset = fset + l.loadGuard.AddMutexForPkg(pkg) + }) } - return fmt.Sprintf("%d (%s)", mode, strings.Join(flags, "|")) -} + l.debugPrintLoadedPackages(pkgs) -func (cl *ContextLoader) debugPrintLoadedPackages(pkgs []*packages.Package) { - cl.debugf("loaded %d pkgs", len(pkgs)) - for i, pkg := range pkgs { - var syntaxFiles []string - for _, sf := range pkg.Syntax { - syntaxFiles = append(syntaxFiles, pkg.Fset.Position(sf.Pos()).Filename) - } - cl.debugf("Loaded pkg #%d: ID=%s GoFiles=%s CompiledGoFiles=%s Syntax=%s", - i, pkg.ID, pkg.GoFiles, pkg.CompiledGoFiles, syntaxFiles) + if err := l.parseLoadedPackagesErrors(pkgs); err != nil { + return nil, err } + + return l.filterTestMainPackages(pkgs), nil } -func (cl *ContextLoader) parseLoadedPackagesErrors(pkgs []*packages.Package) error { +func (l *PackageLoader) parseLoadedPackagesErrors(pkgs []*packages.Package) error { for _, pkg := range pkgs { var errs []packages.Error for _, err := range pkg.Errors { @@ -185,61 +131,8 @@ func (cl *ContextLoader) parseLoadedPackagesErrors(pkgs []*packages.Package) err return nil } -func (cl *ContextLoader) loadPackages(ctx context.Context, loadMode packages.LoadMode) ([]*packages.Package, error) { - defer func(startedAt time.Time) { - cl.log.Infof("Go packages loading at mode %s took %s", stringifyLoadMode(loadMode), time.Since(startedAt)) - }(time.Now()) - - cl.prepareBuildContext() - - buildFlags, err := cl.makeBuildFlags() - if err != nil { - return nil, fmt.Errorf("failed to make build flags for go list: %w", err) - } - - conf := &packages.Config{ - Mode: loadMode, - Tests: cl.cfg.Run.AnalyzeTests, - Context: ctx, - BuildFlags: buildFlags, - Logf: cl.debugf, - // TODO: use fset, parsefile, overlay - } - - args := cl.buildArgs() - cl.debugf("Built loader args are %s", args) - pkgs, err := packages.Load(conf, args...) - if err != nil { - return nil, fmt.Errorf("failed to load with go/packages: %w", err) - } - - // Currently, go/packages doesn't guarantee that error will be returned - // if context was canceled. See - // https://github.com/golang/tools/commit/c5cec6710e927457c3c29d6c156415e8539a5111#r39261855 - if ctx.Err() != nil { - return nil, fmt.Errorf("timed out to load packages: %w", ctx.Err()) - } - - if loadMode&packages.NeedSyntax == 0 { - // Needed e.g. for go/analysis loading. - fset := token.NewFileSet() - packages.Visit(pkgs, nil, func(pkg *packages.Package) { - pkg.Fset = fset - cl.loadGuard.AddMutexForPkg(pkg) - }) - } - - cl.debugPrintLoadedPackages(pkgs) - - if err := cl.parseLoadedPackagesErrors(pkgs); err != nil { - return nil, err - } - - return cl.filterTestMainPackages(pkgs), nil -} - -func (cl *ContextLoader) tryParseTestPackage(pkg *packages.Package) (name string, isTest bool) { - matches := cl.pkgTestIDRe.FindStringSubmatch(pkg.ID) +func (l *PackageLoader) tryParseTestPackage(pkg *packages.Package) (name string, isTest bool) { + matches := l.pkgTestIDRe.FindStringSubmatch(pkg.ID) if matches == nil { return "", false } @@ -247,36 +140,21 @@ func (cl *ContextLoader) tryParseTestPackage(pkg *packages.Package) (name string return matches[1], true } -func (cl *ContextLoader) filterTestMainPackages(pkgs []*packages.Package) []*packages.Package { - var retPkgs []*packages.Package - for _, pkg := range pkgs { - if pkg.Name == "main" && strings.HasSuffix(pkg.PkgPath, ".test") { - // it's an implicit testmain package - cl.debugf("skip pkg ID=%s", pkg.ID) - continue - } - - retPkgs = append(retPkgs, pkg) - } - - return retPkgs -} - -func (cl *ContextLoader) filterDuplicatePackages(pkgs []*packages.Package) []*packages.Package { +func (l *PackageLoader) filterDuplicatePackages(pkgs []*packages.Package) []*packages.Package { packagesWithTests := map[string]bool{} for _, pkg := range pkgs { - name, isTest := cl.tryParseTestPackage(pkg) + name, isTest := l.tryParseTestPackage(pkg) if !isTest { continue } packagesWithTests[name] = true } - cl.debugf("package with tests: %#v", packagesWithTests) + l.debugf("package with tests: %#v", packagesWithTests) var retPkgs []*packages.Package for _, pkg := range pkgs { - _, isTest := cl.tryParseTestPackage(pkg) + _, isTest := l.tryParseTestPackage(pkg) if !isTest && packagesWithTests[pkg.PkgPath] { // If tests loading is enabled, // for package with files a.go and a_test.go go/packages loads two packages: @@ -284,7 +162,7 @@ func (cl *ContextLoader) filterDuplicatePackages(pkgs []*packages.Package) []*pa // 2. ID=".../a [.../a.test]" GoFiles=[a.go a_test.go] // We need only the second package, otherwise we can get warnings about unused variables/fields/functions // in a.go if they are used only in a_test.go. - cl.debugf("skip pkg ID=%s because we load it with test package", pkg.ID) + l.debugf("skip pkg ID=%s because we load it with test package", pkg.ID) continue } @@ -294,33 +172,110 @@ func (cl *ContextLoader) filterDuplicatePackages(pkgs []*packages.Package) []*pa return retPkgs } -func (cl *ContextLoader) Load(ctx context.Context, linters []*linter.Config) (*linter.Context, error) { - loadMode := cl.findLoadMode(linters) - pkgs, err := cl.loadPackages(ctx, loadMode) - if err != nil { - return nil, fmt.Errorf("failed to load packages: %w", err) +func (l *PackageLoader) filterTestMainPackages(pkgs []*packages.Package) []*packages.Package { + var retPkgs []*packages.Package + for _, pkg := range pkgs { + if pkg.Name == "main" && strings.HasSuffix(pkg.PkgPath, ".test") { + // it's an implicit testmain package + l.debugf("skip pkg ID=%s", pkg.ID) + continue + } + + retPkgs = append(retPkgs, pkg) + } + + return retPkgs +} + +func (l *PackageLoader) debugPrintLoadedPackages(pkgs []*packages.Package) { + l.debugf("loaded %d pkgs", len(pkgs)) + for i, pkg := range pkgs { + var syntaxFiles []string + for _, sf := range pkg.Syntax { + syntaxFiles = append(syntaxFiles, pkg.Fset.Position(sf.Pos()).Filename) + } + l.debugf("Loaded pkg #%d: ID=%s GoFiles=%s CompiledGoFiles=%s Syntax=%s", + i, pkg.ID, pkg.GoFiles, pkg.CompiledGoFiles, syntaxFiles) + } +} + +func (l *PackageLoader) prepareBuildContext() { + // Set GOROOT to have working cross-compilation: cross-compiled binaries + // have invalid GOROOT. XXX: can't use runtime.GOROOT(). + goroot := l.goenv.Get(goutil.EnvGoRoot) + if goroot == "" { + return } - deduplicatedPkgs := cl.filterDuplicatePackages(pkgs) + os.Setenv(string(goutil.EnvGoRoot), goroot) + build.Default.GOROOT = goroot + build.Default.BuildTags = l.cfg.Run.BuildTags +} - if len(deduplicatedPkgs) == 0 { - return nil, exitcodes.ErrNoGoFiles +func (l *PackageLoader) buildArgs() []string { + if len(l.args) == 0 { + return []string{"./..."} } - ret := &linter.Context{ - Packages: deduplicatedPkgs, + var retArgs []string + for _, arg := range l.args { + if strings.HasPrefix(arg, ".") || filepath.IsAbs(arg) { + retArgs = append(retArgs, arg) + } else { + // go/packages doesn't work well if we don't have the prefix ./ for local packages + retArgs = append(retArgs, fmt.Sprintf(".%c%s", filepath.Separator, arg)) + } + } - // At least `unused` linters works properly only on original (not deduplicated) packages, - // see https://github.com/golangci/golangci-lint/pull/585. - OriginalPackages: pkgs, + return retArgs +} + +func (l *PackageLoader) makeBuildFlags() []string { + var buildFlags []string - Cfg: cl.cfg, - Log: cl.log, - FileCache: cl.fileCache, - LineCache: cl.lineCache, - PkgCache: cl.pkgCache, - LoadGuard: cl.loadGuard, + if len(l.cfg.Run.BuildTags) != 0 { + // go help build + buildFlags = append(buildFlags, "-tags", strings.Join(l.cfg.Run.BuildTags, " ")) + l.log.Infof("Using build tags: %v", l.cfg.Run.BuildTags) + } + + if l.cfg.Run.ModulesDownloadMode != "" { + // go help modules + buildFlags = append(buildFlags, fmt.Sprintf("-mod=%s", l.cfg.Run.ModulesDownloadMode)) } - return ret, nil + return buildFlags +} + +func findLoadMode(linters []*linter.Config) packages.LoadMode { + loadMode := packages.LoadMode(0) + for _, lc := range linters { + loadMode |= lc.LoadMode + } + + return loadMode +} + +func stringifyLoadMode(mode packages.LoadMode) string { + m := map[packages.LoadMode]string{ + packages.NeedCompiledGoFiles: "compiled_files", + packages.NeedDeps: "deps", + packages.NeedExportFile: "exports_file", + packages.NeedFiles: "files", + packages.NeedImports: "imports", + packages.NeedName: "name", + packages.NeedSyntax: "syntax", + packages.NeedTypes: "types", + packages.NeedTypesInfo: "types_info", + packages.NeedTypesSizes: "types_sizes", + } + + var flags []string + for flag, flagStr := range m { + if mode&flag != 0 { + flags = append(flags, flagStr) + } + } + + return fmt.Sprintf("%d (%s)", mode, strings.Join(flags, "|")) } 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 e7cb17555a..b1d604c964 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/lint/runner.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/lint/runner.go @@ -7,8 +7,6 @@ import ( "runtime/debug" "strings" - gopackages "golang.org/x/tools/go/packages" - "github.com/golangci/golangci-lint/internal/errorutil" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/fsutils" @@ -22,68 +20,63 @@ import ( "github.com/golangci/golangci-lint/pkg/timeutils" ) +type processorStat struct { + inCount int + outCount int +} + type Runner struct { + Log logutils.Log + + lintCtx *linter.Context Processors []processors.Processor - Log logutils.Log } -func NewRunner(cfg *config.Config, log logutils.Log, goenv *goutil.Env, - es *lintersdb.EnabledSet, +func NewRunner(log logutils.Log, cfg *config.Config, args []string, goenv *goutil.Env, lineCache *fsutils.LineCache, fileCache *fsutils.FileCache, - dbManager *lintersdb.Manager, pkgs []*gopackages.Package) (*Runner, error) { + dbManager *lintersdb.Manager, lintCtx *linter.Context, +) (*Runner, error) { // Beware that some processors need to add the path prefix when working with paths // because they get invoked before the path prefixer (exclude and severity rules) // or process other paths (skip files). files := fsutils.NewFiles(lineCache, cfg.Output.PathPrefix) - skipFilesProcessor, err := processors.NewSkipFiles(cfg.Run.SkipFiles, cfg.Output.PathPrefix) + skipFilesProcessor, err := processors.NewSkipFiles(cfg.Issues.ExcludeFiles, cfg.Output.PathPrefix) if err != nil { return nil, err } - skipDirs := cfg.Run.SkipDirs - if cfg.Run.UseDefaultSkipDirs { + skipDirs := cfg.Issues.ExcludeDirs + if cfg.Issues.UseDefaultExcludeDirs { skipDirs = append(skipDirs, packages.StdExcludeDirRegexps...) } - skipDirsProcessor, err := processors.NewSkipDirs(skipDirs, log.Child(logutils.DebugKeySkipDirs), cfg.Run.Args, cfg.Output.PathPrefix) + + skipDirsProcessor, err := processors.NewSkipDirs(skipDirs, log.Child(logutils.DebugKeySkipDirs), args, cfg.Output.PathPrefix) if err != nil { return nil, err } - enabledLinters, err := es.GetEnabledLintersMap() + enabledLinters, err := dbManager.GetEnabledLintersMap() if err != nil { return nil, fmt.Errorf("failed to get enabled linters: %w", err) } - // print deprecated messages - if !cfg.InternalCmdTest { - for name, lc := range enabledLinters { - if !lc.IsDeprecated() { - continue - } - - var extra string - if lc.Deprecation.Replacement != "" { - extra = fmt.Sprintf("Replaced by %s.", lc.Deprecation.Replacement) - } - - log.Warnf("The linter '%s' is deprecated (since %s) due to: %s %s", name, lc.Deprecation.Since, lc.Deprecation.Message, extra) - } - } - return &Runner{ Processors: []processors.Processor{ processors.NewCgo(goenv), // Must go after Cgo. - processors.NewFilenameUnadjuster(pkgs, log.Child(logutils.DebugKeyFilenameUnadjuster)), + processors.NewFilenameUnadjuster(lintCtx.Packages, log.Child(logutils.DebugKeyFilenameUnadjuster)), + + // Must go after FilenameUnadjuster. + processors.NewInvalidIssue(log.Child(logutils.DebugKeyInvalidIssue)), // Must be before diff, nolint and exclude autogenerated processor at least. processors.NewPathPrettifier(), skipFilesProcessor, skipDirsProcessor, // must be after path prettifier - processors.NewAutogeneratedExclude(), + processors.NewAutogeneratedExclude(cfg.Issues.ExcludeGeneratedStrict), // Must be before exclude because users see already marked output and configure excluding by it. processors.NewIdentifierMarker(), @@ -108,12 +101,41 @@ func NewRunner(cfg *config.Config, log logutils.Log, goenv *goutil.Env, processors.NewPathPrefixer(cfg.Output.PathPrefix), processors.NewSortResults(cfg), }, - Log: log, + lintCtx: lintCtx, + Log: log, }, nil } +func (r *Runner) Run(ctx context.Context, linters []*linter.Config) ([]result.Issue, error) { + sw := timeutils.NewStopwatch("linters", r.Log) + defer sw.Print() + + var ( + lintErrors error + issues []result.Issue + ) + + for _, lc := range linters { + lc := lc + sw.TrackStage(lc.Name(), func() { + linterIssues, err := r.runLinterSafe(ctx, r.lintCtx, lc) + if err != nil { + 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 + } + + issues = append(issues, linterIssues...) + }) + } + + return r.processLintResults(issues), lintErrors +} + func (r *Runner) runLinterSafe(ctx context.Context, lintCtx *linter.Context, - lc *linter.Config) (ret []result.Issue, err error) { + lc *linter.Config, +) (ret []result.Issue, err error) { defer func() { if panicData := recover(); panicData != nil { if pe, ok := panicData.(*errorutil.PanicError); ok { @@ -152,12 +174,7 @@ func (r *Runner) runLinterSafe(ctx context.Context, lintCtx *linter.Context, return issues, nil } -type processorStat struct { - inCount int - outCount int -} - -func (r Runner) processLintResults(inIssues []result.Issue) []result.Issue { +func (r *Runner) processLintResults(inIssues []result.Issue) []result.Issue { sw := timeutils.NewStopwatch("processing", r.Log) var issuesBefore, issuesAfter int @@ -188,7 +205,7 @@ func (r Runner) processLintResults(inIssues []result.Issue) []result.Issue { return outIssues } -func (r Runner) printPerProcessorStat(stat map[string]processorStat) { +func (r *Runner) printPerProcessorStat(stat map[string]processorStat) { parts := make([]string, 0, len(stat)) for name, ps := range stat { if ps.inCount != 0 { @@ -200,33 +217,6 @@ func (r Runner) printPerProcessorStat(stat map[string]processorStat) { } } -func (r Runner) Run(ctx context.Context, linters []*linter.Config, lintCtx *linter.Context) ([]result.Issue, error) { - sw := timeutils.NewStopwatch("linters", r.Log) - defer sw.Print() - - var ( - lintErrors error - issues []result.Issue - ) - - for _, lc := range linters { - lc := lc - sw.TrackStage(lc.Name(), func() { - linterIssues, err := r.runLinterSafe(ctx, lintCtx, lc) - if err != nil { - 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 - } - - issues = append(issues, linterIssues...) - }) - } - - return r.processLintResults(issues), lintErrors -} - func (r *Runner) processIssues(issues []result.Issue, sw *timeutils.Stopwatch, statPerProcessor map[string]processorStat) []result.Issue { for _, p := range r.Processors { var newIssues []result.Issue @@ -255,20 +245,15 @@ func (r *Runner) processIssues(issues []result.Issue, sw *timeutils.Stopwatch, s } func getExcludeProcessor(cfg *config.Issues) processors.Processor { - var excludeTotalPattern string - - if len(cfg.ExcludePatterns) != 0 { - excludeTotalPattern = fmt.Sprintf("(%s)", strings.Join(cfg.ExcludePatterns, "|")) + opts := processors.ExcludeOptions{ + CaseSensitive: cfg.ExcludeCaseSensitive, } - var excludeProcessor processors.Processor - if cfg.ExcludeCaseSensitive { - excludeProcessor = processors.NewExcludeCaseSensitive(excludeTotalPattern) - } else { - excludeProcessor = processors.NewExclude(excludeTotalPattern) + if len(cfg.ExcludePatterns) != 0 { + opts.Pattern = fmt.Sprintf("(%s)", strings.Join(cfg.ExcludePatterns, "|")) } - return excludeProcessor + return processors.NewExclude(opts) } func getExcludeRulesProcessor(cfg *config.Issues, log logutils.Log, files *fsutils.Files) processors.Processor { @@ -296,22 +281,12 @@ func getExcludeRulesProcessor(cfg *config.Issues, log logutils.Log, files *fsuti } } - var excludeRulesProcessor processors.Processor - if cfg.ExcludeCaseSensitive { - excludeRulesProcessor = processors.NewExcludeRulesCaseSensitive( - excludeRules, - files, - log.Child(logutils.DebugKeyExcludeRules), - ) - } else { - excludeRulesProcessor = processors.NewExcludeRules( - excludeRules, - files, - log.Child(logutils.DebugKeyExcludeRules), - ) + opts := processors.ExcludeRulesOptions{ + Rules: excludeRules, + CaseSensitive: cfg.ExcludeCaseSensitive, } - return excludeRulesProcessor + return processors.NewExcludeRules(log.Child(logutils.DebugKeyExcludeRules), files, opts) } func getSeverityRulesProcessor(cfg *config.Severity, log logutils.Log, files *fsutils.Files) processors.Processor { @@ -329,22 +304,11 @@ func getSeverityRulesProcessor(cfg *config.Severity, log logutils.Log, files *fs }) } - var severityRulesProcessor processors.Processor - if cfg.CaseSensitive { - severityRulesProcessor = processors.NewSeverityRulesCaseSensitive( - cfg.Default, - severityRules, - files, - log.Child(logutils.DebugKeySeverityRules), - ) - } else { - severityRulesProcessor = processors.NewSeverityRules( - cfg.Default, - severityRules, - files, - log.Child(logutils.DebugKeySeverityRules), - ) + severityOpts := processors.SeverityOptions{ + Default: cfg.Default, + Rules: severityRules, + CaseSensitive: cfg.CaseSensitive, } - return severityRulesProcessor + return processors.NewSeverity(log.Child(logutils.DebugKeySeverityRules), files, severityOpts) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/logutils/logutils.go b/vendor/github.com/golangci/golangci-lint/pkg/logutils/logutils.go index 94479bc7b9..e4bb98109d 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/logutils/logutils.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/logutils/logutils.go @@ -5,6 +5,9 @@ import ( "strings" ) +// EnvTestRun value: "1" +const EnvTestRun = "GL_TEST_RUN" + // envDebug value: one or several debug keys. // examples: // - Remove output to `/dev/null`: `GL_DEBUG=linters_output ./golangci-lint run` @@ -22,6 +25,7 @@ const ( DebugKeyExcludeRules = "exclude_rules" DebugKeyExec = "exec" DebugKeyFilenameUnadjuster = "filename_unadjuster" + DebugKeyInvalidIssue = "invalid_issue" DebugKeyForbidigo = "forbidigo" DebugKeyGoEnv = "goenv" DebugKeyLinter = "linter" @@ -57,6 +61,7 @@ const ( const ( DebugKeyGoCritic = "gocritic" // Debugs `go-critic` linter. + DebugKeyGovet = "govet" // Debugs `govet` linter. DebugKeyMegacheck = "megacheck" // Debugs `staticcheck` related linters. DebugKeyNolint = "nolint" // Debugs a filter excluding issues by `//nolint` comments. DebugKeyRevive = "revive" // Debugs `revive` linter. @@ -99,8 +104,15 @@ func HaveDebugTag(tag string) bool { return enabledDebugs[tag] } +var verbose bool + func SetupVerboseLog(log Log, isVerbose bool) { if isVerbose { + verbose = isVerbose log.SetLevel(LogLevelInfo) } } + +func IsVerbose() bool { + return verbose +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/logutils/out.go b/vendor/github.com/golangci/golangci-lint/pkg/logutils/out.go index 67c70dc8f2..ef13754867 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/logutils/out.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/logutils/out.go @@ -5,5 +5,7 @@ import ( colorable "github.com/mattn/go-colorable" ) -var StdOut = color.Output // https://github.com/golangci/golangci-lint/issues/14 -var StdErr = colorable.NewColorableStderr() +var ( + StdOut = color.Output // https://github.com/golangci/golangci-lint/issues/14 + StdErr = colorable.NewColorableStderr() +) diff --git a/vendor/github.com/golangci/golangci-lint/pkg/logutils/stderr_log.go b/vendor/github.com/golangci/golangci-lint/pkg/logutils/stderr_log.go index 367c94f385..569a177a7c 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/logutils/stderr_log.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/logutils/stderr_log.go @@ -5,7 +5,7 @@ import ( "os" "time" - "github.com/sirupsen/logrus" //nolint:depguard + "github.com/sirupsen/logrus" "github.com/golangci/golangci-lint/pkg/exitcodes" ) diff --git a/vendor/github.com/golangci/golangci-lint/pkg/packages/skip.go b/vendor/github.com/golangci/golangci-lint/pkg/packages/exclude.go similarity index 100% rename from vendor/github.com/golangci/golangci-lint/pkg/packages/skip.go rename to vendor/github.com/golangci/golangci-lint/pkg/packages/exclude.go 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 f471d5a866..e396119da1 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/printers/github.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/printers/github.go @@ -8,16 +8,16 @@ import ( "github.com/golangci/golangci-lint/pkg/result" ) -type github struct { +type GitHub struct { w io.Writer } const defaultGithubSeverity = "error" -// NewGithub output format outputs issues according to GitHub actions format: +// NewGitHub output format outputs issues according to GitHub actions format: // 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} +func NewGitHub(w io.Writer) *GitHub { + return &GitHub{w: w} } // print each line as: ::error file=app.js,line=10,col=15::Something went wrong @@ -41,7 +41,7 @@ func formatIssueAsGithub(issue *result.Issue) string { return ret } -func (p *github) Print(issues []result.Issue) error { +func (p *GitHub) Print(issues []result.Issue) error { for ind := range issues { _, err := fmt.Fprintln(p.w, formatIssueAsGithub(&issues[ind])) if err != nil { diff --git a/vendor/github.com/golangci/golangci-lint/pkg/printers/json.go b/vendor/github.com/golangci/golangci-lint/pkg/printers/json.go index 4bae526b87..28509cac45 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/printers/json.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/printers/json.go @@ -9,7 +9,7 @@ import ( ) type JSON struct { - rd *report.Data + rd *report.Data // TODO(ldez) should be drop in v2. Only use by JSON reporter. w io.Writer } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/printers/printer.go b/vendor/github.com/golangci/golangci-lint/pkg/printers/printer.go index ce3116fa4e..08c34234a9 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/printers/printer.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/printers/printer.go @@ -1,9 +1,143 @@ package printers import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/logutils" + "github.com/golangci/golangci-lint/pkg/report" "github.com/golangci/golangci-lint/pkg/result" ) -type Printer interface { +const defaultFileMode = 0o644 + +type issuePrinter interface { Print(issues []result.Issue) error } + +// Printer prints issues +type Printer struct { + cfg *config.Output + reportData *report.Data + + log logutils.Log + + stdOut io.Writer + stdErr io.Writer +} + +// NewPrinter creates a new Printer. +func NewPrinter(log logutils.Log, cfg *config.Output, reportData *report.Data) (*Printer, error) { + if log == nil { + return nil, errors.New("missing log argument in constructor") + } + if cfg == nil { + return nil, errors.New("missing config argument in constructor") + } + if reportData == nil { + return nil, errors.New("missing reportData argument in constructor") + } + + return &Printer{ + cfg: cfg, + reportData: reportData, + log: log, + stdOut: logutils.StdOut, + stdErr: logutils.StdErr, + }, nil +} + +// Print prints issues based on the formats defined +func (c *Printer) Print(issues []result.Issue) error { + for _, format := range c.cfg.Formats { + err := c.printReports(issues, format) + if err != nil { + return err + } + } + + return nil +} + +func (c *Printer) printReports(issues []result.Issue, format config.OutputFormat) error { + w, shouldClose, err := c.createWriter(format.Path) + if err != nil { + return fmt.Errorf("can't create output for %s: %w", format.Path, err) + } + + defer func() { + if file, ok := w.(io.Closer); shouldClose && ok { + _ = file.Close() + } + }() + + p, err := c.createPrinter(format.Format, w) + if err != nil { + return err + } + + if err = p.Print(issues); err != nil { + return fmt.Errorf("can't print %d issues: %w", len(issues), err) + } + + return nil +} + +func (c *Printer) createWriter(path string) (io.Writer, bool, error) { + if path == "" || path == "stdout" { + return c.stdOut, false, nil + } + + if path == "stderr" { + return c.stdErr, false, nil + } + + err := os.MkdirAll(filepath.Dir(path), os.ModePerm) + if err != nil { + return nil, false, err + } + + f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, defaultFileMode) + if err != nil { + return nil, false, err + } + + return f, true, nil +} + +func (c *Printer) createPrinter(format string, w io.Writer) (issuePrinter, error) { + var p issuePrinter + + switch format { + case config.OutFormatJSON: + p = NewJSON(c.reportData, w) + case config.OutFormatColoredLineNumber, config.OutFormatLineNumber: + p = NewText(c.cfg.PrintIssuedLine, + format == config.OutFormatColoredLineNumber, c.cfg.PrintLinterName, + c.log.Child(logutils.DebugKeyTextPrinter), w) + case config.OutFormatTab, config.OutFormatColoredTab: + p = NewTab(c.cfg.PrintLinterName, + format == config.OutFormatColoredTab, + c.log.Child(logutils.DebugKeyTabPrinter), w) + case config.OutFormatCheckstyle: + p = NewCheckstyle(w) + case config.OutFormatCodeClimate: + p = NewCodeClimate(w) + case config.OutFormatHTML: + p = NewHTML(w) + case config.OutFormatJunitXML: + p = NewJunitXML(w) + case config.OutFormatGithubActions: + p = NewGitHub(w) + case config.OutFormatTeamCity: + p = NewTeamCity(w) + default: + return nil, fmt.Errorf("unknown output format %q", format) + } + + return p, nil +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/printers/tab.go b/vendor/github.com/golangci/golangci-lint/pkg/printers/tab.go index 8ede897402..c6d390d188 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/printers/tab.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/printers/tab.go @@ -52,15 +52,15 @@ func (p *Tab) Print(issues []result.Issue) error { return nil } -func (p *Tab) printIssue(i *result.Issue, w io.Writer) { - text := p.SprintfColored(color.FgRed, "%s", i.Text) +func (p *Tab) printIssue(issue *result.Issue, w io.Writer) { + text := p.SprintfColored(color.FgRed, "%s", issue.Text) if p.printLinterName { - text = fmt.Sprintf("%s\t%s", i.FromLinter, text) + text = fmt.Sprintf("%s\t%s", issue.FromLinter, text) } - pos := p.SprintfColored(color.Bold, "%s:%d", i.FilePath(), i.Line()) - if i.Pos.Column != 0 { - pos += fmt.Sprintf(":%d", i.Pos.Column) + pos := p.SprintfColored(color.Bold, "%s:%d", issue.FilePath(), issue.Line()) + if issue.Pos.Column != 0 { + pos += fmt.Sprintf(":%d", issue.Pos.Column) } fmt.Fprintf(w, "%s\t%s\n", pos, text) diff --git a/vendor/github.com/golangci/golangci-lint/pkg/printers/text.go b/vendor/github.com/golangci/golangci-lint/pkg/printers/text.go index 6e29c4b50f..56cced7696 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/printers/text.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/printers/text.go @@ -55,32 +55,32 @@ func (p *Text) Print(issues []result.Issue) error { return nil } -func (p *Text) printIssue(i *result.Issue) { - text := p.SprintfColored(color.FgRed, "%s", strings.TrimSpace(i.Text)) +func (p *Text) printIssue(issue *result.Issue) { + text := p.SprintfColored(color.FgRed, "%s", strings.TrimSpace(issue.Text)) if p.printLinterName { - text += fmt.Sprintf(" (%s)", i.FromLinter) + text += fmt.Sprintf(" (%s)", issue.FromLinter) } - pos := p.SprintfColored(color.Bold, "%s:%d", i.FilePath(), i.Line()) - if i.Pos.Column != 0 { - pos += fmt.Sprintf(":%d", i.Pos.Column) + pos := p.SprintfColored(color.Bold, "%s:%d", issue.FilePath(), issue.Line()) + if issue.Pos.Column != 0 { + pos += fmt.Sprintf(":%d", issue.Pos.Column) } fmt.Fprintf(p.w, "%s: %s\n", pos, text) } -func (p *Text) printSourceCode(i *result.Issue) { - for _, line := range i.SourceLines { +func (p *Text) printSourceCode(issue *result.Issue) { + for _, line := range issue.SourceLines { fmt.Fprintln(p.w, line) } } -func (p *Text) printUnderLinePointer(i *result.Issue) { +func (p *Text) printUnderLinePointer(issue *result.Issue) { // if column == 0 it means column is unknown (e.g. for gosec) - if len(i.SourceLines) != 1 || i.Pos.Column == 0 { + if len(issue.SourceLines) != 1 || issue.Pos.Column == 0 { return } - col0 := i.Pos.Column - 1 - line := i.SourceLines[0] + col0 := issue.Pos.Column - 1 + line := issue.SourceLines[0] prefixRunes := make([]rune, 0, len(line)) for j := 0; j < len(line) && j < col0; j++ { if line[j] == '\t' { diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/issue.go b/vendor/github.com/golangci/golangci-lint/pkg/result/issue.go index 1e8cd30524..32246a6df4 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/issue.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/issue.go @@ -1,7 +1,7 @@ package result import ( - "crypto/md5" //nolint:gosec + "crypto/md5" //nolint:gosec // for md5 hash "fmt" "go/token" @@ -91,7 +91,7 @@ func (i *Issue) Fingerprint() string { firstLine = i.SourceLines[0] } - hash := md5.New() //nolint:gosec + hash := md5.New() //nolint:gosec // we don't need a strong hash here _, _ = fmt.Fprintf(hash, "%s%s%s", i.Pos.Filename, i.Text, firstLine) return fmt.Sprintf("%X", hash.Sum(nil)) diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/autogenerated_exclude.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/autogenerated_exclude.go index c7675fce8f..b5944cd1d0 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/autogenerated_exclude.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/autogenerated_exclude.go @@ -1,37 +1,47 @@ package processors import ( - "errors" "fmt" "go/parser" "go/token" "path/filepath" + "regexp" "strings" "github.com/golangci/golangci-lint/pkg/logutils" "github.com/golangci/golangci-lint/pkg/result" ) -var autogenDebugf = logutils.Debug(logutils.DebugKeyAutogenExclude) +const ( + genCodeGenerated = "code generated" + genDoNotEdit = "do not edit" + genAutoFile = "autogenerated file" // easyjson +) -type ageFileSummary struct { - isGenerated bool -} +var _ Processor = &AutogeneratedExclude{} -type ageFileSummaryCache map[string]*ageFileSummary +type fileSummary struct { + generated bool +} type AutogeneratedExclude struct { - fileSummaryCache ageFileSummaryCache + debugf logutils.DebugFunc + + strict bool + strictPattern *regexp.Regexp + + fileSummaryCache map[string]*fileSummary } -func NewAutogeneratedExclude() *AutogeneratedExclude { +func NewAutogeneratedExclude(strict bool) *AutogeneratedExclude { return &AutogeneratedExclude{ - fileSummaryCache: ageFileSummaryCache{}, + debugf: logutils.Debug(logutils.DebugKeyAutogenExclude), + strict: strict, + strictPattern: regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`), + fileSummaryCache: map[string]*fileSummary{}, } } -var _ Processor = &AutogeneratedExclude{} - func (p *AutogeneratedExclude) Name() string { return "autogenerated_exclude" } @@ -40,82 +50,100 @@ func (p *AutogeneratedExclude) Process(issues []result.Issue) ([]result.Issue, e return filterIssuesErr(issues, p.shouldPassIssue) } -func isSpecialAutogeneratedFile(filePath string) bool { - fileName := filepath.Base(filePath) - // fake files or generation definitions to which //line points to for generated files - return filepath.Ext(fileName) != ".go" -} +func (p *AutogeneratedExclude) Finish() {} -func (p *AutogeneratedExclude) shouldPassIssue(i *result.Issue) (bool, error) { - if i.FromLinter == "typecheck" { +func (p *AutogeneratedExclude) shouldPassIssue(issue *result.Issue) (bool, error) { + if issue.FromLinter == "typecheck" { // don't hide typechecking errors in generated files: users expect to see why the project isn't compiling return true, nil } - if filepath.Base(i.FilePath()) == "go.mod" { - return true, nil + // The file is already known. + fs := p.fileSummaryCache[issue.FilePath()] + if fs != nil { + return !fs.generated, nil } - if isSpecialAutogeneratedFile(i.FilePath()) { - return false, nil - } + fs = &fileSummary{} + p.fileSummaryCache[issue.FilePath()] = fs - fs, err := p.getOrCreateFileSummary(i) - if err != nil { - return false, err + if p.strict { + var err error + fs.generated, err = p.isGeneratedFileStrict(issue.FilePath()) + if err != nil { + return false, fmt.Errorf("failed to get doc (strict) of file %s: %w", issue.FilePath(), err) + } + } else { + doc, err := getComments(issue.FilePath()) + if err != nil { + return false, fmt.Errorf("failed to get doc (lax) of file %s: %w", issue.FilePath(), err) + } + + fs.generated = p.isGeneratedFileLax(doc) } + p.debugf("file %q is generated: %t", issue.FilePath(), fs.generated) + // don't report issues for autogenerated files - return !fs.isGenerated, nil + return !fs.generated, nil } -// isGenerated reports whether the source file is generated code. -// Using a bit laxer rules than https://go.dev/s/generatedcode to -// match more generated code. See #48 and #72. -func isGeneratedFileByComment(doc string) bool { - const ( - genCodeGenerated = "code generated" - genDoNotEdit = "do not edit" - genAutoFile = "autogenerated file" // easyjson - ) - +// isGeneratedFileLax reports whether the source file is generated code. +// The function uses a bit laxer rules than isGeneratedFileStrict to match more generated code. +// See https://github.com/golangci/golangci-lint/issues/48 and https://github.com/golangci/golangci-lint/issues/72. +func (p *AutogeneratedExclude) isGeneratedFileLax(doc string) bool { markers := []string{genCodeGenerated, genDoNotEdit, genAutoFile} + doc = strings.ToLower(doc) + for _, marker := range markers { if strings.Contains(doc, marker) { - autogenDebugf("doc contains marker %q: file is generated", marker) + p.debugf("doc contains marker %q: file is generated", marker) + return true } } - autogenDebugf("doc of len %d doesn't contain any of markers: %s", len(doc), markers) + p.debugf("doc of len %d doesn't contain any of markers: %s", len(doc), markers) + return false } -func (p *AutogeneratedExclude) getOrCreateFileSummary(i *result.Issue) (*ageFileSummary, error) { - fs := p.fileSummaryCache[i.FilePath()] - if fs != nil { - return fs, nil +// isGeneratedFileStrict returns true if the source file has a line that matches the regular expression: +// +// ^// Code generated .* DO NOT EDIT\.$ +// +// This line must appear before the first non-comment, non-blank text in the file. +// Based on https://go.dev/s/generatedcode. +func (p *AutogeneratedExclude) isGeneratedFileStrict(filePath string) (bool, error) { + file, err := parser.ParseFile(token.NewFileSet(), filePath, nil, parser.PackageClauseOnly|parser.ParseComments) + if err != nil { + return false, fmt.Errorf("failed to parse file: %w", err) } - fs = &ageFileSummary{} - p.fileSummaryCache[i.FilePath()] = fs - - if i.FilePath() == "" { - return nil, errors.New("no file path for issue") + if file == nil || len(file.Comments) == 0 { + return false, nil } - doc, err := getDoc(i.FilePath()) - if err != nil { - return nil, fmt.Errorf("failed to get doc of file %s: %w", i.FilePath(), err) + for _, comment := range file.Comments { + if comment.Pos() > file.Package { + return false, nil + } + + for _, line := range comment.List { + generated := p.strictPattern.MatchString(line.Text) + if generated { + p.debugf("doc contains ignore expression: file is generated") + + return true, nil + } + } } - fs.isGenerated = isGeneratedFileByComment(doc) - autogenDebugf("file %q is generated: %t", i.FilePath(), fs.isGenerated) - return fs, nil + return false, nil } -func getDoc(filePath string) (string, error) { +func getComments(filePath string) (string, error) { fset := token.NewFileSet() syntax, err := parser.ParseFile(fset, filePath, nil, parser.PackageClauseOnly|parser.ParseComments) if err != nil { @@ -130,4 +158,6 @@ func getDoc(filePath string) (string, error) { return strings.Join(docLines, "\n"), nil } -func (p *AutogeneratedExclude) Finish() {} +func isGoFile(name string) bool { + return filepath.Ext(name) == ".go" +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/base_rule.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/base_rule.go index b5e138806b..12333c898d 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/base_rule.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/base_rule.go @@ -8,6 +8,8 @@ import ( "github.com/golangci/golangci-lint/pkg/result" ) +const caseInsensitivePrefix = "(?i)" + type BaseRule struct { Text string Source string @@ -63,7 +65,7 @@ func (r *baseRule) matchLinter(issue *result.Issue) bool { return false } -func (r *baseRule) matchSource(issue *result.Issue, lineCache *fsutils.LineCache, log logutils.Log) bool { //nolint:interfacer +func (r *baseRule) matchSource(issue *result.Issue, lineCache *fsutils.LineCache, log logutils.Log) bool { sourceLine, errSourceLine := lineCache.GetLine(issue.FilePath(), issue.Line()) if errSourceLine != nil { log.Warnf("Failed to get line %s:%d from line cache: %s", issue.FilePath(), issue.Line(), errSourceLine) diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/cgo.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/cgo.go index 8e77237518..1ad73c31af 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/cgo.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/cgo.go @@ -26,17 +26,17 @@ func (p Cgo) Name() string { } func (p Cgo) Process(issues []result.Issue) ([]result.Issue, error) { - return filterIssuesErr(issues, func(i *result.Issue) (bool, error) { - // some linters (.e.g gosec, deadcode) return incorrect filepaths for cgo issues, + return filterIssuesErr(issues, func(issue *result.Issue) (bool, error) { + // some linters (e.g. gosec, deadcode) return incorrect filepaths for cgo issues, // also cgo files have strange issues looking like false positives. // cache dir contains all preprocessed files including cgo files - issueFilePath := i.FilePath() - if !filepath.IsAbs(i.FilePath()) { - absPath, err := filepath.Abs(i.FilePath()) + issueFilePath := issue.FilePath() + if !filepath.IsAbs(issue.FilePath()) { + absPath, err := filepath.Abs(issue.FilePath()) if err != nil { - return false, fmt.Errorf("failed to build abs path for %q: %w", i.FilePath(), err) + return false, fmt.Errorf("failed to build abs path for %q: %w", issue.FilePath(), err) } issueFilePath = absPath } @@ -45,7 +45,7 @@ func (p Cgo) Process(issues []result.Issue) ([]result.Issue, error) { return false, nil } - if filepath.Base(i.FilePath()) == "_cgo_gotypes.go" { + if filepath.Base(issue.FilePath()) == "_cgo_gotypes.go" { // skip cgo warning for go1.10 return false, nil } 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 496d9c865c..d607b02182 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 @@ -63,15 +63,15 @@ func (p Diff) Process(issues []result.Issue) ([]result.Issue, error) { return nil, fmt.Errorf("can't prepare diff by revgrep: %w", err) } - return transformIssues(issues, func(i *result.Issue) *result.Issue { - hunkPos, isNew := c.IsNewIssue(i) + return transformIssues(issues, func(issue *result.Issue) *result.Issue { + hunkPos, isNew := c.IsNewIssue(issue) if !isNew { return nil } - newI := *i - newI.HunkPos = hunkPos - return &newI + newIssue := *issue + newIssue.HunkPos = hunkPos + return &newIssue }), nil } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/exclude.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/exclude.go index 92959a328c..05a56ef965 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/exclude.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/exclude.go @@ -6,24 +6,37 @@ import ( "github.com/golangci/golangci-lint/pkg/result" ) +var _ Processor = Exclude{} + type Exclude struct { + name string + pattern *regexp.Regexp } -var _ Processor = Exclude{} +type ExcludeOptions struct { + Pattern string + CaseSensitive bool +} -func NewExclude(pattern string) *Exclude { - var patternRe *regexp.Regexp - if pattern != "" { - patternRe = regexp.MustCompile("(?i)" + pattern) +func NewExclude(opts ExcludeOptions) *Exclude { + p := &Exclude{name: "exclude"} + + prefix := caseInsensitivePrefix + if opts.CaseSensitive { + p.name = "exclude-case-sensitive" + prefix = "" } - return &Exclude{ - pattern: patternRe, + + if opts.Pattern != "" { + p.pattern = regexp.MustCompile(prefix + opts.Pattern) } + + return p } func (p Exclude) Name() string { - return "exclude" + return p.name } func (p Exclude) Process(issues []result.Issue) ([]result.Issue, error) { @@ -31,29 +44,9 @@ func (p Exclude) Process(issues []result.Issue) ([]result.Issue, error) { return issues, nil } - return filterIssues(issues, func(i *result.Issue) bool { - return !p.pattern.MatchString(i.Text) + return filterIssues(issues, func(issue *result.Issue) bool { + return !p.pattern.MatchString(issue.Text) }), nil } func (p Exclude) Finish() {} - -type ExcludeCaseSensitive struct { - *Exclude -} - -var _ Processor = ExcludeCaseSensitive{} - -func NewExcludeCaseSensitive(pattern string) *ExcludeCaseSensitive { - var patternRe *regexp.Regexp - if pattern != "" { - patternRe = regexp.MustCompile(pattern) - } - return &ExcludeCaseSensitive{ - &Exclude{pattern: patternRe}, - } -} - -func (p ExcludeCaseSensitive) Name() string { - return "exclude-case-sensitive" -} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/exclude_rules.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/exclude_rules.go index 2f7e30b430..a20d56d05d 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/exclude_rules.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/exclude_rules.go @@ -8,6 +8,8 @@ import ( "github.com/golangci/golangci-lint/pkg/result" ) +var _ Processor = ExcludeRules{} + type excludeRule struct { baseRule } @@ -17,53 +19,45 @@ type ExcludeRule struct { } type ExcludeRules struct { - rules []excludeRule - files *fsutils.Files + name string + log logutils.Log + files *fsutils.Files + + rules []excludeRule } -func NewExcludeRules(rules []ExcludeRule, files *fsutils.Files, log logutils.Log) *ExcludeRules { - r := &ExcludeRules{ +type ExcludeRulesOptions struct { + Rules []ExcludeRule + CaseSensitive bool +} + +func NewExcludeRules(log logutils.Log, files *fsutils.Files, opts ExcludeRulesOptions) *ExcludeRules { + p := &ExcludeRules{ + name: "exclude-rules", files: files, log: log, } - r.rules = createRules(rules, "(?i)") - - return r -} -func createRules(rules []ExcludeRule, prefix string) []excludeRule { - parsedRules := make([]excludeRule, 0, len(rules)) - for _, rule := range rules { - parsedRule := excludeRule{} - parsedRule.linters = rule.Linters - if rule.Text != "" { - parsedRule.text = regexp.MustCompile(prefix + rule.Text) - } - if rule.Source != "" { - parsedRule.source = regexp.MustCompile(prefix + rule.Source) - } - if rule.Path != "" { - path := fsutils.NormalizePathInRegex(rule.Path) - parsedRule.path = regexp.MustCompile(path) - } - if rule.PathExcept != "" { - pathExcept := fsutils.NormalizePathInRegex(rule.PathExcept) - parsedRule.pathExcept = regexp.MustCompile(pathExcept) - } - parsedRules = append(parsedRules, parsedRule) + prefix := caseInsensitivePrefix + if opts.CaseSensitive { + prefix = "" + p.name = "exclude-rules-case-sensitive" } - return parsedRules + + p.rules = createRules(opts.Rules, prefix) + + return p } func (p ExcludeRules) Process(issues []result.Issue) ([]result.Issue, error) { if len(p.rules) == 0 { return issues, nil } - return filterIssues(issues, func(i *result.Issue) bool { + return filterIssues(issues, func(issue *result.Issue) bool { for _, rule := range p.rules { rule := rule - if rule.match(i, p.files, p.log) { + if rule.match(issue, p.files, p.log) { return false } } @@ -71,25 +65,35 @@ func (p ExcludeRules) Process(issues []result.Issue) ([]result.Issue, error) { }), nil } -func (ExcludeRules) Name() string { return "exclude-rules" } -func (ExcludeRules) Finish() {} +func (p ExcludeRules) Name() string { return p.name } -var _ Processor = ExcludeRules{} +func (ExcludeRules) Finish() {} -type ExcludeRulesCaseSensitive struct { - *ExcludeRules -} +func createRules(rules []ExcludeRule, prefix string) []excludeRule { + parsedRules := make([]excludeRule, 0, len(rules)) -func NewExcludeRulesCaseSensitive(rules []ExcludeRule, files *fsutils.Files, log logutils.Log) *ExcludeRulesCaseSensitive { - r := &ExcludeRules{ - files: files, - log: log, - } - r.rules = createRules(rules, "") + for _, rule := range rules { + parsedRule := excludeRule{} + parsedRule.linters = rule.Linters - return &ExcludeRulesCaseSensitive{r} -} + if rule.Text != "" { + parsedRule.text = regexp.MustCompile(prefix + rule.Text) + } -func (ExcludeRulesCaseSensitive) Name() string { return "exclude-rules-case-sensitive" } + if rule.Source != "" { + parsedRule.source = regexp.MustCompile(prefix + rule.Source) + } + + if rule.Path != "" { + parsedRule.path = regexp.MustCompile(fsutils.NormalizePathInRegex(rule.Path)) + } + + if rule.PathExcept != "" { + parsedRule.pathExcept = regexp.MustCompile(fsutils.NormalizePathInRegex(rule.PathExcept)) + } -var _ Processor = ExcludeCaseSensitive{} + parsedRules = append(parsedRules, parsedRule) + } + + return parsedRules +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/filename_unadjuster.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/filename_unadjuster.go index 2aaafbf58b..adf82c823c 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/filename_unadjuster.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/filename_unadjuster.go @@ -102,29 +102,29 @@ func (p *FilenameUnadjuster) Name() string { } func (p *FilenameUnadjuster) Process(issues []result.Issue) ([]result.Issue, error) { - return transformIssues(issues, func(i *result.Issue) *result.Issue { - issueFilePath := i.FilePath() - if !filepath.IsAbs(i.FilePath()) { - absPath, err := filepath.Abs(i.FilePath()) + return transformIssues(issues, func(issue *result.Issue) *result.Issue { + issueFilePath := issue.FilePath() + if !filepath.IsAbs(issue.FilePath()) { + absPath, err := filepath.Abs(issue.FilePath()) if err != nil { - p.log.Warnf("failed to build abs path for %q: %s", i.FilePath(), err) - return i + p.log.Warnf("failed to build abs path for %q: %s", issue.FilePath(), err) + return issue } issueFilePath = absPath } mapper := p.m[issueFilePath] if mapper == nil { - return i + return issue } - newI := *i - newI.Pos = mapper(i.Pos) - if !p.loggedUnadjustments[i.Pos.Filename] { - p.log.Infof("Unadjusted from %v to %v", i.Pos, newI.Pos) - p.loggedUnadjustments[i.Pos.Filename] = true + newIssue := *issue + newIssue.Pos = mapper(issue.Pos) + if !p.loggedUnadjustments[issue.Pos.Filename] { + p.log.Infof("Unadjusted from %v to %v", issue.Pos, newIssue.Pos) + p.loggedUnadjustments[issue.Pos.Filename] = true } - return &newI + return &newIssue }), nil } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/fixer.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/fixer.go index a79a846288..2879beb48f 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/fixer.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/fixer.go @@ -133,20 +133,22 @@ func (f Fixer) mergeLineIssues(lineNum int, lineIssues []result.Issue, origFileL // check issues first for ind := range lineIssues { - i := &lineIssues[ind] - if i.LineRange != nil { + li := &lineIssues[ind] + + if li.LineRange != nil { f.log.Infof("Line %d has multiple issues but at least one of them is ranged: %#v", lineNum, lineIssues) return &lineIssues[0] } - r := i.Replacement - if r.Inline == nil || len(r.NewLines) != 0 || r.NeedOnlyDelete { + inline := li.Replacement.Inline + + if inline == nil || len(li.Replacement.NewLines) != 0 || li.Replacement.NeedOnlyDelete { f.log.Infof("Line %d has multiple issues but at least one of them isn't inline: %#v", lineNum, lineIssues) - return &lineIssues[0] + return li } - if r.Inline.StartCol < 0 || r.Inline.Length <= 0 || r.Inline.StartCol+r.Inline.Length > len(origLine) { - f.log.Warnf("Line %d (%q) has invalid inline fix: %#v, %#v", lineNum, origLine, i, r.Inline) + if inline.StartCol < 0 || inline.Length <= 0 || inline.StartCol+inline.Length > len(origLine) { + f.log.Warnf("Line %d (%q) has invalid inline fix: %#v, %#v", lineNum, origLine, li, inline) return nil } } @@ -162,7 +164,7 @@ func (f Fixer) applyInlineFixes(lineIssues []result.Issue, origLine []byte, line var newLineBuf bytes.Buffer newLineBuf.Grow(len(origLine)) - //nolint:misspell + //nolint:misspell // misspelling is intentional // example: origLine="it's becouse of them", StartCol=5, Length=7, NewString="because" curOrigLinePos := 0 diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/identifier_marker.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/identifier_marker.go index 5cc4e56ba2..1975f6d086 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/identifier_marker.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/identifier_marker.go @@ -31,27 +31,39 @@ var replacePatterns = []replacePattern{ {`^composites: (\S+) composite literal uses unkeyed fields$`, "composites: `${1}` composite literal uses unkeyed fields"}, // gosec - {`^(\S+): Blacklisted import (\S+): weak cryptographic primitive$`, - "${1}: Blacklisted import `${2}`: weak cryptographic primitive"}, + { + `^(\S+): Blacklisted import (\S+): weak cryptographic primitive$`, + "${1}: Blacklisted import `${2}`: weak cryptographic primitive", + }, {`^TLS InsecureSkipVerify set true.$`, "TLS `InsecureSkipVerify` set true."}, // gosimple {`should replace loop with (.*)$`, "should replace loop with `${1}`"}, - {`should use a simple channel send/receive instead of select with a single case`, - "should use a simple channel send/receive instead of `select` with a single case"}, - {`should omit comparison to bool constant, can be simplified to (.+)$`, - "should omit comparison to bool constant, can be simplified to `${1}`"}, + { + `should use a simple channel send/receive instead of select with a single case`, + "should use a simple channel send/receive instead of `select` with a single case", + }, + { + `should omit comparison to bool constant, can be simplified to (.+)$`, + "should omit comparison to bool constant, can be simplified to `${1}`", + }, {`should write (.+) instead of (.+)$`, "should write `${1}` instead of `${2}`"}, {`redundant return statement$`, "redundant `return` statement"}, - {`should replace this if statement with an unconditional strings.TrimPrefix`, - "should replace this `if` statement with an unconditional `strings.TrimPrefix`"}, + { + `should replace this if statement with an unconditional strings.TrimPrefix`, + "should replace this `if` statement with an unconditional `strings.TrimPrefix`", + }, // staticcheck {`this value of (\S+) is never used$`, "this value of `${1}` is never used"}, - {`should use time.Since instead of time.Now\(\).Sub$`, - "should use `time.Since` instead of `time.Now().Sub`"}, - {`should check returned error before deferring response.Close\(\)$`, - "should check returned error before deferring `response.Close()`"}, + { + `should use time.Since instead of time.Now\(\).Sub$`, + "should use `time.Since` instead of `time.Now().Sub`", + }, + { + `should check returned error before deferring response.Close\(\)$`, + "should check returned error before deferring `response.Close()`", + }, {`no value of type uint is less than 0$`, "no value of type `uint` is less than `0`"}, // unused @@ -59,26 +71,40 @@ var replacePatterns = []replacePattern{ // typecheck {`^unknown field (\S+) in struct literal$`, "unknown field `${1}` in struct literal"}, - {`^invalid operation: (\S+) \(variable of type (\S+)\) has no field or method (\S+)$`, - "invalid operation: `${1}` (variable of type `${2}`) has no field or method `${3}`"}, + { + `^invalid operation: (\S+) \(variable of type (\S+)\) has no field or method (\S+)$`, + "invalid operation: `${1}` (variable of type `${2}`) has no field or method `${3}`", + }, {`^undeclared name: (\S+)$`, "undeclared name: `${1}`"}, - {`^cannot use addr \(variable of type (\S+)\) as (\S+) value in argument to (\S+)$`, - "cannot use addr (variable of type `${1}`) as `${2}` value in argument to `${3}`"}, + { + `^cannot use addr \(variable of type (\S+)\) as (\S+) value in argument to (\S+)$`, + "cannot use addr (variable of type `${1}`) as `${2}` value in argument to `${3}`", + }, {`^other declaration of (\S+)$`, "other declaration of `${1}`"}, {`^(\S+) redeclared in this block$`, "`${1}` redeclared in this block"}, // golint - {`^exported (type|method|function|var|const) (\S+) should have comment or be unexported$`, - "exported ${1} `${2}` should have comment or be unexported"}, - {`^comment on exported (type|method|function|var|const) (\S+) should be of the form "(\S+) ..."$`, - "comment on exported ${1} `${2}` should be of the form `${3} ...`"}, + { + `^exported (type|method|function|var|const) (\S+) should have comment or be unexported$`, + "exported ${1} `${2}` should have comment or be unexported", + }, + { + `^comment on exported (type|method|function|var|const) (\S+) should be of the form "(\S+) ..."$`, + "comment on exported ${1} `${2}` should be of the form `${3} ...`", + }, {`^should replace (.+) with (.+)$`, "should replace `${1}` with `${2}`"}, - {`^if block ends with a return statement, so drop this else and outdent its block$`, - "`if` block ends with a `return` statement, so drop this `else` and outdent its block"}, - {`^(struct field|var|range var|const|type|(?:func|method|interface method) (?:parameter|result)) (\S+) should be (\S+)$`, - "${1} `${2}` should be `${3}`"}, - {`^don't use underscores in Go names; var (\S+) should be (\S+)$`, - "don't use underscores in Go names; var `${1}` should be `${2}`"}, + { + `^if block ends with a return statement, so drop this else and outdent its block$`, + "`if` block ends with a `return` statement, so drop this `else` and outdent its block", + }, + { + `^(struct field|var|range var|const|type|(?:func|method|interface method) (?:parameter|result)) (\S+) should be (\S+)$`, + "${1} `${2}` should be `${3}`", + }, + { + `^don't use underscores in Go names; var (\S+) should be (\S+)$`, + "don't use underscores in Go names; var `${1}` should be `${2}`", + }, } type IdentifierMarker struct { @@ -101,10 +127,10 @@ func NewIdentifierMarker() *IdentifierMarker { } func (im IdentifierMarker) Process(issues []result.Issue) ([]result.Issue, error) { - return transformIssues(issues, func(i *result.Issue) *result.Issue { - iCopy := *i - iCopy.Text = im.markIdentifiers(iCopy.Text) - return &iCopy + return transformIssues(issues, func(issue *result.Issue) *result.Issue { + newIssue := *issue + newIssue.Text = im.markIdentifiers(newIssue.Text) + return &newIssue }), nil } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/invalid_issue.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/invalid_issue.go new file mode 100644 index 0000000000..a8cfef8920 --- /dev/null +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/invalid_issue.go @@ -0,0 +1,54 @@ +package processors + +import ( + "path/filepath" + + "github.com/golangci/golangci-lint/pkg/logutils" + "github.com/golangci/golangci-lint/pkg/result" +) + +var _ Processor = InvalidIssue{} + +type InvalidIssue struct { + log logutils.Log +} + +func NewInvalidIssue(log logutils.Log) *InvalidIssue { + return &InvalidIssue{log: log} +} + +func (p InvalidIssue) Process(issues []result.Issue) ([]result.Issue, error) { + return filterIssuesErr(issues, p.shouldPassIssue) +} + +func (p InvalidIssue) Name() string { + return "invalid_issue" +} + +func (p InvalidIssue) Finish() {} + +func (p InvalidIssue) shouldPassIssue(issue *result.Issue) (bool, error) { + if issue.FromLinter == "typecheck" { + return true, nil + } + + if issue.FilePath() == "" { + // contextcheck has a known bug https://github.com/kkHAIKE/contextcheck/issues/21 + if issue.FromLinter != "contextcheck" { + p.log.Warnf("no file path for the issue: probably a bug inside the linter %q: %#v", issue.FromLinter, issue) + } + + return false, nil + } + + if filepath.Base(issue.FilePath()) == "go.mod" { + return true, nil + } + + if !isGoFile(issue.FilePath()) { + p.log.Infof("issue related to file %s is skipped", issue.FilePath()) + return false, nil + } + + return true, nil +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/issues.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/issues.go index 4691be38a4..a65b0c2b0c 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/issues.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/issues.go @@ -6,7 +6,7 @@ import ( "github.com/golangci/golangci-lint/pkg/result" ) -func filterIssues(issues []result.Issue, filter func(i *result.Issue) bool) []result.Issue { +func filterIssues(issues []result.Issue, filter func(issue *result.Issue) bool) []result.Issue { retIssues := make([]result.Issue, 0, len(issues)) for i := range issues { if filter(&issues[i]) { @@ -17,7 +17,7 @@ func filterIssues(issues []result.Issue, filter func(i *result.Issue) bool) []re return retIssues } -func filterIssuesErr(issues []result.Issue, filter func(i *result.Issue) (bool, error)) ([]result.Issue, error) { +func filterIssuesErr(issues []result.Issue, filter func(issue *result.Issue) (bool, error)) ([]result.Issue, error) { retIssues := make([]result.Issue, 0, len(issues)) for i := range issues { ok, err := filter(&issues[i]) @@ -33,12 +33,12 @@ func filterIssuesErr(issues []result.Issue, filter func(i *result.Issue) (bool, return retIssues, nil } -func transformIssues(issues []result.Issue, transform func(i *result.Issue) *result.Issue) []result.Issue { +func transformIssues(issues []result.Issue, transform func(issue *result.Issue) *result.Issue) []result.Issue { retIssues := make([]result.Issue, 0, len(issues)) for i := range issues { - newI := transform(&issues[i]) - if newI != nil { - retIssues = append(retIssues, *newI) + newIssue := transform(&issues[i]) + if newIssue != nil { + retIssues = append(retIssues, *newIssue) } } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/max_from_linter.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/max_from_linter.go index 649ed86ae9..65b04272ba 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/max_from_linter.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/max_from_linter.go @@ -33,14 +33,14 @@ func (p *MaxFromLinter) Process(issues []result.Issue) ([]result.Issue, error) { return issues, nil } - return filterIssues(issues, func(i *result.Issue) bool { - if i.Replacement != nil && p.cfg.Issues.NeedFix { + return filterIssues(issues, func(issue *result.Issue) bool { + if issue.Replacement != nil && p.cfg.Issues.NeedFix { // we need to fix all issues at once => we need to return all of them return true } - p.lc[i.FromLinter]++ // always inc for stat - return p.lc[i.FromLinter] <= p.limit + p.lc[issue.FromLinter]++ // always inc for stat + return p.lc[issue.FromLinter] <= p.limit }), nil } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/max_per_file_from_linter.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/max_per_file_from_linter.go index 64182e3e22..372f40cc58 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/max_per_file_from_linter.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/max_per_file_from_linter.go @@ -5,8 +5,10 @@ import ( "github.com/golangci/golangci-lint/pkg/result" ) -type linterToCountMap map[string]int -type fileToLinterToCountMap map[string]linterToCountMap +type ( + linterToCountMap map[string]int + fileToLinterToCountMap map[string]linterToCountMap +) type MaxPerFileFromLinter struct { flc fileToLinterToCountMap @@ -36,22 +38,22 @@ func (p *MaxPerFileFromLinter) Name() string { } func (p *MaxPerFileFromLinter) Process(issues []result.Issue) ([]result.Issue, error) { - return filterIssues(issues, func(i *result.Issue) bool { - limit := p.maxPerFileFromLinterConfig[i.FromLinter] + return filterIssues(issues, func(issue *result.Issue) bool { + limit := p.maxPerFileFromLinterConfig[issue.FromLinter] if limit == 0 { return true } - lm := p.flc[i.FilePath()] + lm := p.flc[issue.FilePath()] if lm == nil { - p.flc[i.FilePath()] = linterToCountMap{} + p.flc[issue.FilePath()] = linterToCountMap{} } - count := p.flc[i.FilePath()][i.FromLinter] + count := p.flc[issue.FilePath()][issue.FromLinter] if count >= limit { return false } - p.flc[i.FilePath()][i.FromLinter]++ + p.flc[issue.FilePath()][issue.FromLinter]++ return true }), nil } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/max_same_issues.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/max_same_issues.go index 391ae5fa7f..a3ceeb5953 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/max_same_issues.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/max_same_issues.go @@ -37,14 +37,14 @@ func (p *MaxSameIssues) Process(issues []result.Issue) ([]result.Issue, error) { return issues, nil } - return filterIssues(issues, func(i *result.Issue) bool { - if i.Replacement != nil && p.cfg.Issues.NeedFix { + return filterIssues(issues, func(issue *result.Issue) bool { + if issue.Replacement != nil && p.cfg.Issues.NeedFix { // we need to fix all issues at once => we need to return all of them return true } - p.tc[i.Text]++ // always inc for stat - return p.tc[i.Text] <= p.limit + p.tc[issue.Text]++ // always inc for stat + return p.tc[issue.Text] <= p.limit }), nil } 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 a72dd1ef29..df8e814959 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 @@ -1,7 +1,6 @@ package processors import ( - "errors" "go/ast" "go/parser" "go/token" @@ -18,8 +17,10 @@ import ( "github.com/golangci/golangci-lint/pkg/result" ) -var nolintDebugf = logutils.Debug(logutils.DebugKeyNolint) -var nolintRe = regexp.MustCompile(`^nolint( |:|$)`) +var ( + nolintDebugf = logutils.Debug(logutils.DebugKeyNolint) + nolintRe = regexp.MustCompile(`^nolint( |:|$)`) +) type ignoredRange struct { linters []string @@ -97,33 +98,31 @@ func (p *Nolint) Process(issues []result.Issue) ([]result.Issue, error) { return filterIssuesErr(issues, p.shouldPassIssue) } -func (p *Nolint) getOrCreateFileData(i *result.Issue) (*fileData, error) { - fd := p.cache[i.FilePath()] +func (p *Nolint) getOrCreateFileData(issue *result.Issue) *fileData { + fd := p.cache[issue.FilePath()] if fd != nil { - return fd, nil + return fd } fd = &fileData{} - p.cache[i.FilePath()] = fd - - if i.FilePath() == "" { - return nil, errors.New("no file path for issue") - } + p.cache[issue.FilePath()] = fd // TODO: migrate this parsing to go/analysis facts // or cache them somehow per file. // Don't use cached AST because they consume a lot of memory on large projects. fset := token.NewFileSet() - f, err := parser.ParseFile(fset, i.FilePath(), nil, parser.ParseComments) + f, err := parser.ParseFile(fset, issue.FilePath(), nil, parser.ParseComments) if err != nil { // Don't report error because it's already must be reporter by typecheck or go/analysis. - return fd, nil + return fd } - fd.ignoredRanges = p.buildIgnoredRangesForFile(f, fset, i.FilePath()) - nolintDebugf("file %s: built nolint ranges are %+v", i.FilePath(), fd.ignoredRanges) - return fd, nil + fd.ignoredRanges = p.buildIgnoredRangesForFile(f, fset, issue.FilePath()) + + nolintDebugf("file %s: built nolint ranges are %+v", issue.FilePath(), fd.ignoredRanges) + + return fd } func (p *Nolint) buildIgnoredRangesForFile(f *ast.File, fset *token.FileSet, filePath string) []ignoredRange { @@ -148,28 +147,25 @@ func (p *Nolint) buildIgnoredRangesForFile(f *ast.File, fset *token.FileSet, fil return allRanges } -func (p *Nolint) shouldPassIssue(i *result.Issue) (bool, error) { - nolintDebugf("got issue: %v", *i) - if i.FromLinter == golinters.NoLintLintName && i.ExpectNoLint && i.ExpectedNoLintLinter != "" { +func (p *Nolint) shouldPassIssue(issue *result.Issue) (bool, error) { + nolintDebugf("got issue: %v", *issue) + if issue.FromLinter == golinters.NoLintLintName && issue.ExpectNoLint && issue.ExpectedNoLintLinter != "" { // don't expect disabled linters to cover their nolint statements nolintDebugf("enabled linters: %v", p.enabledLinters) - if p.enabledLinters[i.ExpectedNoLintLinter] == nil { + if p.enabledLinters[issue.ExpectedNoLintLinter] == nil { return false, nil } - nolintDebugf("checking that lint issue was used for %s: %v", i.ExpectedNoLintLinter, i) + nolintDebugf("checking that lint issue was used for %s: %v", issue.ExpectedNoLintLinter, issue) } - fd, err := p.getOrCreateFileData(i) - if err != nil { - return false, err - } + fd := p.getOrCreateFileData(issue) for _, ir := range fd.ignoredRanges { - if ir.doesMatch(i) { - nolintDebugf("found ignored range for issue %v: %v", i, ir) - ir.matchedIssueFromLinter[i.FromLinter] = true + if ir.doesMatch(issue) { + nolintDebugf("found ignored range for issue %v: %v", issue, ir) + ir.matchedIssueFromLinter[issue.FromLinter] = true if ir.originalRange != nil { - ir.originalRange.matchedIssueFromLinter[i.FromLinter] = true + ir.originalRange.matchedIssueFromLinter[issue.FromLinter] = true } return false, nil } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/path_prettifier.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/path_prettifier.go index 3a140999c0..79cdd7473c 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/path_prettifier.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/path_prettifier.go @@ -29,19 +29,19 @@ func (p PathPrettifier) Name() string { } func (p PathPrettifier) Process(issues []result.Issue) ([]result.Issue, error) { - return transformIssues(issues, func(i *result.Issue) *result.Issue { - if !filepath.IsAbs(i.FilePath()) { - return i + return transformIssues(issues, func(issue *result.Issue) *result.Issue { + if !filepath.IsAbs(issue.FilePath()) { + return issue } - rel, err := fsutils.ShortestRelPath(i.FilePath(), "") + rel, err := fsutils.ShortestRelPath(issue.FilePath(), "") if err != nil { - return i + return issue } - newI := i - newI.Pos.Filename = rel - return newI + newIssue := issue + newIssue.Pos.Filename = rel + return newIssue }), nil } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/path_shortener.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/path_shortener.go index 6b66bea8b0..d7fa5ea91d 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/path_shortener.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/path_shortener.go @@ -29,11 +29,11 @@ func (p PathShortener) Name() string { } func (p PathShortener) Process(issues []result.Issue) ([]result.Issue, error) { - return transformIssues(issues, func(i *result.Issue) *result.Issue { - newI := i - newI.Text = strings.ReplaceAll(newI.Text, p.wd+"/", "") - newI.Text = strings.ReplaceAll(newI.Text, p.wd, "") - return newI + return transformIssues(issues, func(issue *result.Issue) *result.Issue { + newIssue := issue + newIssue.Text = strings.ReplaceAll(newIssue.Text, p.wd+"/", "") + newIssue.Text = strings.ReplaceAll(newIssue.Text, p.wd, "") + return newIssue }), nil } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/severity.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/severity.go new file mode 100644 index 0000000000..2568ba45c4 --- /dev/null +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/severity.go @@ -0,0 +1,126 @@ +package processors + +import ( + "regexp" + + "github.com/golangci/golangci-lint/pkg/fsutils" + "github.com/golangci/golangci-lint/pkg/logutils" + "github.com/golangci/golangci-lint/pkg/result" +) + +const severityFromLinter = "@linter" + +var _ Processor = &Severity{} + +type severityRule struct { + baseRule + severity string +} + +type SeverityRule struct { + BaseRule + Severity string +} + +type SeverityOptions struct { + Default string + Rules []SeverityRule + CaseSensitive bool +} + +type Severity struct { + name string + + log logutils.Log + + files *fsutils.Files + + defaultSeverity string + rules []severityRule +} + +func NewSeverity(log logutils.Log, files *fsutils.Files, opts SeverityOptions) *Severity { + p := &Severity{ + name: "severity-rules", + files: files, + log: log, + defaultSeverity: opts.Default, + } + + prefix := caseInsensitivePrefix + if opts.CaseSensitive { + prefix = "" + p.name = "severity-rules-case-sensitive" + } + + p.rules = createSeverityRules(opts.Rules, prefix) + + return p +} + +func (p *Severity) Process(issues []result.Issue) ([]result.Issue, error) { + if len(p.rules) == 0 && p.defaultSeverity == "" { + return issues, nil + } + + return transformIssues(issues, p.transform), nil +} + +func (p *Severity) transform(issue *result.Issue) *result.Issue { + for _, rule := range p.rules { + if rule.match(issue, p.files, p.log) { + if rule.severity == severityFromLinter || (rule.severity == "" && p.defaultSeverity == severityFromLinter) { + return issue + } + + issue.Severity = rule.severity + if issue.Severity == "" { + issue.Severity = p.defaultSeverity + } + + return issue + } + } + + if p.defaultSeverity != severityFromLinter { + issue.Severity = p.defaultSeverity + } + + return issue +} + +func (p *Severity) Name() string { return p.name } + +func (*Severity) Finish() {} + +func createSeverityRules(rules []SeverityRule, prefix string) []severityRule { + parsedRules := make([]severityRule, 0, len(rules)) + + for _, rule := range rules { + parsedRule := severityRule{} + parsedRule.linters = rule.Linters + parsedRule.severity = rule.Severity + + if rule.Text != "" { + parsedRule.text = regexp.MustCompile(prefix + rule.Text) + } + + if rule.Source != "" { + parsedRule.source = regexp.MustCompile(prefix + rule.Source) + } + + if rule.Path != "" { + path := fsutils.NormalizePathInRegex(rule.Path) + parsedRule.path = regexp.MustCompile(path) + } + + if rule.PathExcept != "" { + pathExcept := fsutils.NormalizePathInRegex(rule.PathExcept) + parsedRule.pathExcept = regexp.MustCompile(pathExcept) + } + + parsedRules = append(parsedRules, parsedRule) + } + + return parsedRules +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/severity_rules.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/severity_rules.go deleted file mode 100644 index 0a4a643b71..0000000000 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/severity_rules.go +++ /dev/null @@ -1,108 +0,0 @@ -package processors - -import ( - "regexp" - - "github.com/golangci/golangci-lint/pkg/fsutils" - "github.com/golangci/golangci-lint/pkg/logutils" - "github.com/golangci/golangci-lint/pkg/result" -) - -type severityRule struct { - baseRule - severity string -} - -type SeverityRule struct { - BaseRule - Severity string -} - -type SeverityRules struct { - defaultSeverity string - rules []severityRule - files *fsutils.Files - log logutils.Log -} - -func NewSeverityRules(defaultSeverity string, rules []SeverityRule, files *fsutils.Files, log logutils.Log) *SeverityRules { - r := &SeverityRules{ - files: files, - log: log, - defaultSeverity: defaultSeverity, - } - r.rules = createSeverityRules(rules, "(?i)") - - return r -} - -func createSeverityRules(rules []SeverityRule, prefix string) []severityRule { - parsedRules := make([]severityRule, 0, len(rules)) - for _, rule := range rules { - parsedRule := severityRule{} - parsedRule.linters = rule.Linters - parsedRule.severity = rule.Severity - if rule.Text != "" { - parsedRule.text = regexp.MustCompile(prefix + rule.Text) - } - if rule.Source != "" { - parsedRule.source = regexp.MustCompile(prefix + rule.Source) - } - if rule.Path != "" { - path := fsutils.NormalizePathInRegex(rule.Path) - parsedRule.path = regexp.MustCompile(path) - } - if rule.PathExcept != "" { - pathExcept := fsutils.NormalizePathInRegex(rule.PathExcept) - parsedRule.pathExcept = regexp.MustCompile(pathExcept) - } - parsedRules = append(parsedRules, parsedRule) - } - return parsedRules -} - -func (p SeverityRules) Process(issues []result.Issue) ([]result.Issue, error) { - if len(p.rules) == 0 && p.defaultSeverity == "" { - return issues, nil - } - return transformIssues(issues, func(i *result.Issue) *result.Issue { - for _, rule := range p.rules { - rule := rule - - ruleSeverity := p.defaultSeverity - if rule.severity != "" { - ruleSeverity = rule.severity - } - - if rule.match(i, p.files, p.log) { - i.Severity = ruleSeverity - return i - } - } - i.Severity = p.defaultSeverity - return i - }), nil -} - -func (SeverityRules) Name() string { return "severity-rules" } -func (SeverityRules) Finish() {} - -var _ Processor = SeverityRules{} - -type SeverityRulesCaseSensitive struct { - *SeverityRules -} - -func NewSeverityRulesCaseSensitive(defaultSeverity string, rules []SeverityRule, - files *fsutils.Files, log logutils.Log) *SeverityRulesCaseSensitive { - r := &SeverityRules{ - files: files, - log: log, - defaultSeverity: defaultSeverity, - } - r.rules = createSeverityRules(rules, "") - - return &SeverityRulesCaseSensitive{r} -} - -func (SeverityRulesCaseSensitive) Name() string { return "severity-rules-case-sensitive" } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/skip_dirs.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/skip_dirs.go index e71495fd0b..7c4e0b9c0c 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/skip_dirs.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/skip_dirs.go @@ -79,15 +79,15 @@ func (p *SkipDirs) Process(issues []result.Issue) ([]result.Issue, error) { return filterIssues(issues, p.shouldPassIssue), nil } -func (p *SkipDirs) shouldPassIssue(i *result.Issue) bool { - if filepath.IsAbs(i.FilePath()) { - if !isSpecialAutogeneratedFile(i.FilePath()) { - p.log.Warnf("Got abs path %s in skip dirs processor, it should be relative", i.FilePath()) +func (p *SkipDirs) shouldPassIssue(issue *result.Issue) bool { + if filepath.IsAbs(issue.FilePath()) { + if isGoFile(issue.FilePath()) { + p.log.Warnf("Got abs path %s in skip dirs processor, it should be relative", issue.FilePath()) } return true } - issueRelDir := filepath.Dir(i.FilePath()) + issueRelDir := filepath.Dir(issue.FilePath()) if toPass, ok := p.skippedDirsCache[issueRelDir]; ok { if !toPass { 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 6c1c586950..f1873a376c 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 @@ -41,8 +41,8 @@ func (p SkipFiles) Process(issues []result.Issue) ([]result.Issue, error) { return issues, nil } - return filterIssues(issues, func(i *result.Issue) bool { - path := fsutils.WithPathPrefix(p.pathPrefix, i.FilePath()) + return filterIssues(issues, func(issue *result.Issue) bool { + path := fsutils.WithPathPrefix(p.pathPrefix, issue.FilePath()) for _, pattern := range p.patterns { if pattern.MatchString(path) { return false diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/sort_results.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/sort_results.go index 740c4fa8c3..8e4af57e63 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/sort_results.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/sort_results.go @@ -1,6 +1,9 @@ package processors import ( + "errors" + "fmt" + "slices" "sort" "strings" @@ -13,89 +16,123 @@ import ( // by sorting results.Issues using processor step, and chain based // rules that can compare different properties of the Issues struct. +const ( + orderNameFile = "file" + orderNameLinter = "linter" + orderNameSeverity = "severity" +) + var _ Processor = (*SortResults)(nil) type SortResults struct { - cmp comparator - cfg *config.Config + cmps map[string]*comparator + + cfg *config.Output } func NewSortResults(cfg *config.Config) *SortResults { - // For sorting we are comparing (in next order): file names, line numbers, - // position, and finally - giving up. return &SortResults{ - cmp: ByName{ - next: ByLine{ - next: ByColumn{}, - }, + cmps: map[string]*comparator{ + // For sorting we are comparing (in next order): + // file names, line numbers, position, and finally - giving up. + orderNameFile: byFileName().SetNext(byLine().SetNext(byColumn())), + // For sorting we are comparing: linter name + orderNameLinter: byLinter(), + // For sorting we are comparing: severity + orderNameSeverity: bySeverity(), }, - cfg: cfg, + cfg: &cfg.Output, } } // Process is performing sorting of the result issues. func (sr SortResults) Process(issues []result.Issue) ([]result.Issue, error) { - if !sr.cfg.Output.SortResults { + if !sr.cfg.SortResults { return issues, nil } + if len(sr.cfg.SortOrder) == 0 { + sr.cfg.SortOrder = []string{orderNameFile} + } + + var cmps []*comparator + for _, name := range sr.cfg.SortOrder { + if c, ok := sr.cmps[name]; ok { + cmps = append(cmps, c) + } else { + return nil, fmt.Errorf("unsupported sort-order name %q", name) + } + } + + cmp, err := mergeComparators(cmps) + if err != nil { + return nil, err + } + sort.Slice(issues, func(i, j int) bool { - return sr.cmp.Compare(&issues[i], &issues[j]) == Less + return cmp.Compare(&issues[i], &issues[j]) == less }) return issues, nil } func (sr SortResults) Name() string { return "sort_results" } -func (sr SortResults) Finish() {} + +func (sr SortResults) Finish() {} type compareResult int const ( - Less compareResult = iota - 1 - Equal - Greater - None + less compareResult = iota - 1 + equal + greater + none ) func (c compareResult) isNeutral() bool { // return true if compare result is incomparable or equal. - return c == None || c == Equal + return c == none || c == equal } func (c compareResult) String() string { switch c { - case Less: - return "Less" - case Equal: - return "Equal" - case Greater: - return "Greater" + case less: + return "less" + case equal: + return "equal" + case greater: + return "greater" + default: + return "none" } - - return "None" } -// comparator describe how to implement compare for two "issues" lexicographically -type comparator interface { - Compare(a, b *result.Issue) compareResult - Next() comparator +// comparator describes how to implement compare for two "issues". +type comparator struct { + name string + compare func(a, b *result.Issue) compareResult + next *comparator } -var ( - _ comparator = (*ByName)(nil) - _ comparator = (*ByLine)(nil) - _ comparator = (*ByColumn)(nil) -) +func (cmp *comparator) Next() *comparator { return cmp.next } -type ByName struct{ next comparator } +func (cmp *comparator) SetNext(c *comparator) *comparator { + cmp.next = c + return cmp +} -func (cmp ByName) Next() comparator { return cmp.next } +func (cmp *comparator) String() string { + s := cmp.name + if cmp.Next() != nil { + s += " > " + cmp.Next().String() + } -func (cmp ByName) Compare(a, b *result.Issue) compareResult { - var res compareResult + return s +} - if res = compareResult(strings.Compare(a.FilePath(), b.FilePath())); !res.isNeutral() { +func (cmp *comparator) Compare(a, b *result.Issue) compareResult { + res := cmp.compare(a, b) + if !res.isNeutral() { return res } @@ -106,40 +143,95 @@ func (cmp ByName) Compare(a, b *result.Issue) compareResult { return res } -type ByLine struct{ next comparator } +func byFileName() *comparator { + return &comparator{ + name: "byFileName", + compare: func(a, b *result.Issue) compareResult { + return compareResult(strings.Compare(a.FilePath(), b.FilePath())) + }, + } +} + +func byLine() *comparator { + return &comparator{ + name: "byLine", + compare: func(a, b *result.Issue) compareResult { + return numericCompare(a.Line(), b.Line()) + }, + } +} + +func byColumn() *comparator { + return &comparator{ + name: "byColumn", + compare: func(a, b *result.Issue) compareResult { + return numericCompare(a.Column(), b.Column()) + }, + } +} -func (cmp ByLine) Next() comparator { return cmp.next } +func byLinter() *comparator { + return &comparator{ + name: "byLinter", + compare: func(a, b *result.Issue) compareResult { + return compareResult(strings.Compare(a.FromLinter, b.FromLinter)) + }, + } +} -func (cmp ByLine) Compare(a, b *result.Issue) compareResult { - var res compareResult +func bySeverity() *comparator { + return &comparator{ + name: "bySeverity", + compare: func(a, b *result.Issue) compareResult { + return severityCompare(a.Severity, b.Severity) + }, + } +} - if res = numericCompare(a.Line(), b.Line()); !res.isNeutral() { - return res +func mergeComparators(cmps []*comparator) (*comparator, error) { + if len(cmps) == 0 { + return nil, errors.New("no comparator") } - if next := cmp.Next(); next != nil { - return next.Compare(a, b) + for i := 0; i < len(cmps)-1; i++ { + findComparatorTip(cmps[i]).SetNext(cmps[i+1]) } - return res + return cmps[0], nil } -type ByColumn struct{ next comparator } +func findComparatorTip(cmp *comparator) *comparator { + if cmp.Next() != nil { + return findComparatorTip(cmp.Next()) + } -func (cmp ByColumn) Next() comparator { return cmp.next } + return cmp +} -func (cmp ByColumn) Compare(a, b *result.Issue) compareResult { - var res compareResult +func severityCompare(a, b string) compareResult { + // The position inside the slice define the importance (lower to higher). + classic := []string{"low", "medium", "high", "warning", "error"} + + if slices.Contains(classic, a) && slices.Contains(classic, b) { + switch { + case slices.Index(classic, a) > slices.Index(classic, b): + return greater + case slices.Index(classic, a) < slices.Index(classic, b): + return less + default: + return equal + } + } - if res = numericCompare(a.Column(), b.Column()); !res.isNeutral() { - return res + if slices.Contains(classic, a) { + return greater } - if next := cmp.Next(); next != nil { - return next.Compare(a, b) + if slices.Contains(classic, b) { + return less } - return res + return compareResult(strings.Compare(a, b)) } func numericCompare(a, b int) compareResult { @@ -153,14 +245,14 @@ func numericCompare(a, b int) compareResult { switch { case isZeroValuesBoth || isEqual: - return Equal + return equal case isValuesInvalid || isZeroValueInA || isZeroValueInB: - return None + return none case a > b: - return Greater + return greater case a < b: - return Less + return less } - return Equal + return equal } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/source_code.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/source_code.go index cfd73cb98e..005b3143f8 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/source_code.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/source_code.go @@ -25,22 +25,22 @@ func (p SourceCode) Name() string { } func (p SourceCode) Process(issues []result.Issue) ([]result.Issue, error) { - return transformIssues(issues, func(i *result.Issue) *result.Issue { - newI := *i + return transformIssues(issues, func(issue *result.Issue) *result.Issue { + newIssue := *issue - lineRange := i.GetLineRange() + lineRange := issue.GetLineRange() for lineNumber := lineRange.From; lineNumber <= lineRange.To; lineNumber++ { - line, err := p.lineCache.GetLine(i.FilePath(), lineNumber) + line, err := p.lineCache.GetLine(issue.FilePath(), lineNumber) if err != nil { p.log.Warnf("Failed to get line %d for file %s: %s", - lineNumber, i.FilePath(), err) - return i + lineNumber, issue.FilePath(), err) + return issue } - newI.SourceLines = append(newI.SourceLines, line) + newIssue.SourceLines = append(newIssue.SourceLines, line) } - return &newI + return &newIssue }), nil } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/uniq_by_line.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/uniq_by_line.go index dc0e1e8cf6..aad1e019e3 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/uniq_by_line.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/uniq_by_line.go @@ -5,8 +5,10 @@ import ( "github.com/golangci/golangci-lint/pkg/result" ) -type lineToCount map[int]int -type fileToLineToCount map[string]lineToCount +type ( + lineToCount map[int]int + fileToLineToCount map[string]lineToCount +) type UniqByLine struct { flc fileToLineToCount @@ -31,26 +33,26 @@ func (p *UniqByLine) Process(issues []result.Issue) ([]result.Issue, error) { return issues, nil } - return filterIssues(issues, func(i *result.Issue) bool { - if i.Replacement != nil && p.cfg.Issues.NeedFix { + return filterIssues(issues, func(issue *result.Issue) bool { + if issue.Replacement != nil && p.cfg.Issues.NeedFix { // if issue will be auto-fixed we shouldn't collapse issues: // e.g. one line can contain 2 misspellings, they will be in 2 issues and misspell should fix both of them. return true } - lc := p.flc[i.FilePath()] + lc := p.flc[issue.FilePath()] if lc == nil { lc = lineToCount{} - p.flc[i.FilePath()] = lc + p.flc[issue.FilePath()] = lc } const limit = 1 - count := lc[i.Line()] + count := lc[issue.Line()] if count == limit { return false } - lc[i.Line()]++ + lc[issue.Line()]++ return true }), nil } diff --git a/vendor/github.com/golangci/lint-1/.travis.yml b/vendor/github.com/golangci/lint-1/.travis.yml deleted file mode 100644 index bc2f4b311e..0000000000 --- a/vendor/github.com/golangci/lint-1/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -sudo: false -language: go -go: - - 1.10.x - - 1.11.x - - master - -go_import_path: github.com/golangci/lint-1 - -install: - - go get -t -v ./... - -script: - - go test -v -race ./... - -matrix: - allow_failures: - - go: master - fast_finish: true diff --git a/vendor/github.com/golangci/lint-1/CONTRIBUTING.md b/vendor/github.com/golangci/lint-1/CONTRIBUTING.md deleted file mode 100644 index 2e39a1c677..0000000000 --- a/vendor/github.com/golangci/lint-1/CONTRIBUTING.md +++ /dev/null @@ -1,15 +0,0 @@ -# Contributing to Golint - -## Before filing an issue: - -### Are you having trouble building golint? - -Check you have the latest version of its dependencies. Run -``` -go get -u github.com/golangci/lint-1/golint -``` -If you still have problems, consider searching for existing issues before filing a new issue. - -## Before sending a pull request: - -Have you understood the purpose of golint? Make sure to carefully read `README`. diff --git a/vendor/github.com/golangci/lint-1/LICENSE b/vendor/github.com/golangci/lint-1/LICENSE deleted file mode 100644 index 65d761bc9f..0000000000 --- a/vendor/github.com/golangci/lint-1/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2013 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/golangci/lint-1/README.md b/vendor/github.com/golangci/lint-1/README.md deleted file mode 100644 index 2de6ee835c..0000000000 --- a/vendor/github.com/golangci/lint-1/README.md +++ /dev/null @@ -1,88 +0,0 @@ -Golint is a linter for Go source code. - -[![Build Status](https://travis-ci.org/golang/lint.svg?branch=master)](https://travis-ci.org/golang/lint) - -## Installation - -Golint requires a -[supported release of Go](https://golang.org/doc/devel/release.html#policy). - - go get -u github.com/golangci/lint-1/golint - -To find out where `golint` was installed you can run `go list -f {{.Target}} github.com/golangci/lint-1/golint`. For `golint` to be used globally add that directory to the `$PATH` environment setting. - -## Usage - -Invoke `golint` with one or more filenames, directories, or packages named -by its import path. Golint uses the same -[import path syntax](https://golang.org/cmd/go/#hdr-Import_path_syntax) as -the `go` command and therefore -also supports relative import paths like `./...`. Additionally the `...` -wildcard can be used as suffix on relative and absolute file paths to recurse -into them. - -The output of this tool is a list of suggestions in Vim quickfix format, -which is accepted by lots of different editors. - -## Purpose - -Golint differs from gofmt. Gofmt reformats Go source code, whereas -golint prints out style mistakes. - -Golint differs from govet. Govet is concerned with correctness, whereas -golint is concerned with coding style. Golint is in use at Google, and it -seeks to match the accepted style of the open source Go project. - -The suggestions made by golint are exactly that: suggestions. -Golint is not perfect, and has both false positives and false negatives. -Do not treat its output as a gold standard. We will not be adding pragmas -or other knobs to suppress specific warnings, so do not expect or require -code to be completely "lint-free". -In short, this tool is not, and will never be, trustworthy enough for its -suggestions to be enforced automatically, for example as part of a build process. -Golint makes suggestions for many of the mechanically checkable items listed in -[Effective Go](https://golang.org/doc/effective_go.html) and the -[CodeReviewComments wiki page](https://golang.org/wiki/CodeReviewComments). - -## Scope - -Golint is meant to carry out the stylistic conventions put forth in -[Effective Go](https://golang.org/doc/effective_go.html) and -[CodeReviewComments](https://golang.org/wiki/CodeReviewComments). -Changes that are not aligned with those documents will not be considered. - -## Contributions - -Contributions to this project are welcome provided they are [in scope](#scope), -though please send mail before starting work on anything major. -Contributors retain their copyright, so we need you to fill out -[a short form](https://developers.google.com/open-source/cla/individual) -before we can accept your contribution. - -## Vim - -Add this to your ~/.vimrc: - - set rtp+=$GOPATH/src/github.com/golangci/lint-1/misc/vim - -If you have multiple entries in your GOPATH, replace `$GOPATH` with the right value. - -Running `:Lint` will run golint on the current file and populate the quickfix list. - -Optionally, add this to your `~/.vimrc` to automatically run `golint` on `:w` - - autocmd BufWritePost,FileWritePost *.go execute 'Lint' | cwindow - - -## Emacs - -Add this to your `.emacs` file: - - (add-to-list 'load-path (concat (getenv "GOPATH") "/src/github.com/golang/lint/misc/emacs")) - (require 'golint) - -If you have multiple entries in your GOPATH, replace `$GOPATH` with the right value. - -Running M-x golint will run golint on the current file. - -For more usage, see [Compilation-Mode](http://www.gnu.org/software/emacs/manual/html_node/emacs/Compilation-Mode.html). diff --git a/vendor/github.com/golangci/lint-1/lint.go b/vendor/github.com/golangci/lint-1/lint.go deleted file mode 100644 index 886c85bf09..0000000000 --- a/vendor/github.com/golangci/lint-1/lint.go +++ /dev/null @@ -1,1655 +0,0 @@ -// Copyright (c) 2013 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 or at -// https://developers.google.com/open-source/licenses/bsd. - -// Package lint contains a linter for Go source code. -package lint // import "github.com/golangci/lint-1" - -import ( - "bufio" - "bytes" - "fmt" - "go/ast" - "go/parser" - "go/printer" - "go/token" - "go/types" - "io/ioutil" - "regexp" - "sort" - "strconv" - "strings" - "unicode" - "unicode/utf8" - - "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/go/gcexportdata" -) - -const styleGuideBase = "https://golang.org/wiki/CodeReviewComments" - -// A Linter lints Go source code. -type Linter struct { -} - -// Problem represents a problem in some source code. -type Problem struct { - Position token.Position // position in source file - Text string // the prose that describes the problem - Link string // (optional) the link to the style guide for the problem - Confidence float64 // a value in (0,1] estimating the confidence in this problem's correctness - LineText string // the source line - Category string // a short name for the general category of the problem - - // If the problem has a suggested fix (the minority case), - // ReplacementLine is a full replacement for the relevant line of the source file. - ReplacementLine string -} - -func (p *Problem) String() string { - if p.Link != "" { - return p.Text + "\n\n" + p.Link - } - return p.Text -} - -type byPosition []Problem - -func (p byPosition) Len() int { return len(p) } -func (p byPosition) Swap(i, j int) { p[i], p[j] = p[j], p[i] } - -func (p byPosition) Less(i, j int) bool { - pi, pj := p[i].Position, p[j].Position - - if pi.Filename != pj.Filename { - return pi.Filename < pj.Filename - } - if pi.Line != pj.Line { - return pi.Line < pj.Line - } - if pi.Column != pj.Column { - return pi.Column < pj.Column - } - - return p[i].Text < p[j].Text -} - -// Lint lints src. -func (l *Linter) Lint(filename string, src []byte) ([]Problem, error) { - return l.LintFiles(map[string][]byte{filename: src}) -} - -// LintFiles lints a set of files of a single package. -// The argument is a map of filename to source. -func (l *Linter) LintFiles(files map[string][]byte) ([]Problem, error) { - pkg := &pkg{ - fset: token.NewFileSet(), - files: make(map[string]*file), - } - var pkgName string - for filename, src := range files { - if isGenerated(src) { - continue // See issue #239 - } - f, err := parser.ParseFile(pkg.fset, filename, src, parser.ParseComments) - if err != nil { - return nil, err - } - if pkgName == "" { - pkgName = f.Name.Name - } else if f.Name.Name != pkgName { - return nil, fmt.Errorf("%s is in package %s, not %s", filename, f.Name.Name, pkgName) - } - pkg.files[filename] = &file{ - pkg: pkg, - f: f, - fset: pkg.fset, - src: src, - filename: filename, - } - } - if len(pkg.files) == 0 { - return nil, nil - } - return pkg.lint(), nil -} - -// LintFiles lints a set of files of a single package. -// The argument is a map of filename to source. -func (l *Linter) LintPkg(files []*ast.File, fset *token.FileSet, typesPkg *types.Package, typesInfo *types.Info) ([]Problem, error) { - pkg := &pkg{ - fset: fset, - files: make(map[string]*file), - typesPkg: typesPkg, - typesInfo: typesInfo, - } - var pkgName string - for _, f := range files { - // use PositionFor, not Position because of //line directives: - // this filename will be used for source lines extraction. - filename := fset.PositionFor(f.Pos(), false).Filename - if filename == "" { - return nil, fmt.Errorf("no file name for file %+v", f) - } - - if pkgName == "" { - pkgName = f.Name.Name - } else if f.Name.Name != pkgName { - return nil, fmt.Errorf("%s is in package %s, not %s", filename, f.Name.Name, pkgName) - } - - // TODO: reuse golangci-lint lines cache - src, err := ioutil.ReadFile(filename) - if err != nil { - return nil, fmt.Errorf("can't read file %s: %s", filename, err) - } - - pkg.files[filename] = &file{ - pkg: pkg, - f: f, - fset: pkg.fset, - src: src, - filename: filename, - } - } - if len(pkg.files) == 0 { - return nil, nil - } - return pkg.lint(), nil -} - -var ( - genHdr = []byte("// Code generated ") - genFtr = []byte(" DO NOT EDIT.") -) - -// isGenerated reports whether the source file is generated code -// according the rules from https://golang.org/s/generatedcode. -func isGenerated(src []byte) bool { - sc := bufio.NewScanner(bytes.NewReader(src)) - for sc.Scan() { - b := sc.Bytes() - if bytes.HasPrefix(b, genHdr) && bytes.HasSuffix(b, genFtr) && len(b) >= len(genHdr)+len(genFtr) { - return true - } - } - return false -} - -// pkg represents a package being linted. -type pkg struct { - fset *token.FileSet - files map[string]*file - - typesPkg *types.Package - typesInfo *types.Info - - // sortable is the set of types in the package that implement sort.Interface. - sortable map[string]bool - // main is whether this is a "main" package. - main bool - - problems []Problem -} - -func (p *pkg) lint() []Problem { - p.scanSortable() - p.main = p.isMain() - - for _, f := range p.files { - f.lint() - } - - sort.Sort(byPosition(p.problems)) - - return p.problems -} - -// file represents a file being linted. -type file struct { - pkg *pkg - f *ast.File - fset *token.FileSet - src []byte - filename string -} - -func (f *file) isTest() bool { return strings.HasSuffix(f.filename, "_test.go") } - -func (f *file) lint() { - f.lintPackageComment() - f.lintImports() - f.lintBlankImports() - f.lintExported() - f.lintNames() - f.lintElses() - f.lintRanges() - f.lintErrorf() - f.lintErrors() - f.lintErrorStrings() - f.lintReceiverNames() - f.lintIncDec() - f.lintErrorReturn() - f.lintUnexportedReturn() - f.lintTimeNames() - f.lintContextKeyTypes() - f.lintContextArgs() -} - -type link string -type category string - -// The variadic arguments may start with link and category types, -// and must end with a format string and any arguments. -// It returns the new Problem. -func (f *file) errorf(n ast.Node, confidence float64, args ...interface{}) *Problem { - pos := f.fset.Position(n.Pos()) - if pos.Filename == "" { - pos.Filename = f.filename - } - return f.pkg.errorfAt(pos, confidence, args...) -} - -func (p *pkg) errorfAt(pos token.Position, confidence float64, args ...interface{}) *Problem { - problem := Problem{ - Position: pos, - Confidence: confidence, - } - if pos.Filename != "" { - // The file might not exist in our mapping if a //line directive was encountered. - if f, ok := p.files[pos.Filename]; ok { - problem.LineText = srcLine(f.src, pos) - } - } - -argLoop: - for len(args) > 1 { // always leave at least the format string in args - switch v := args[0].(type) { - case link: - problem.Link = string(v) - case category: - problem.Category = string(v) - default: - break argLoop - } - args = args[1:] - } - - problem.Text = fmt.Sprintf(args[0].(string), args[1:]...) - - p.problems = append(p.problems, problem) - return &p.problems[len(p.problems)-1] -} - -var newImporter = func(fset *token.FileSet) types.ImporterFrom { - return gcexportdata.NewImporter(fset, make(map[string]*types.Package)) -} - -func (p *pkg) typeCheck() error { - config := &types.Config{ - // By setting a no-op error reporter, the type checker does as much work as possible. - Error: func(error) {}, - Importer: newImporter(p.fset), - } - info := &types.Info{ - Types: make(map[ast.Expr]types.TypeAndValue), - Defs: make(map[*ast.Ident]types.Object), - Uses: make(map[*ast.Ident]types.Object), - Scopes: make(map[ast.Node]*types.Scope), - } - var anyFile *file - var astFiles []*ast.File - for _, f := range p.files { - anyFile = f - astFiles = append(astFiles, f.f) - } - pkg, err := config.Check(anyFile.f.Name.Name, p.fset, astFiles, info) - // Remember the typechecking info, even if config.Check failed, - // since we will get partial information. - p.typesPkg = pkg - p.typesInfo = info - return err -} - -func (p *pkg) typeOf(expr ast.Expr) types.Type { - if p.typesInfo == nil { - return nil - } - return p.typesInfo.TypeOf(expr) -} - -func (p *pkg) isNamedType(typ types.Type, importPath, name string) bool { - n, ok := typ.(*types.Named) - if !ok { - return false - } - tn := n.Obj() - return tn != nil && tn.Pkg() != nil && tn.Pkg().Path() == importPath && tn.Name() == name -} - -// scopeOf returns the tightest scope encompassing id. -func (p *pkg) scopeOf(id *ast.Ident) *types.Scope { - var scope *types.Scope - if obj := p.typesInfo.ObjectOf(id); obj != nil { - scope = obj.Parent() - } - if scope == p.typesPkg.Scope() { - // We were given a top-level identifier. - // Use the file-level scope instead of the package-level scope. - pos := id.Pos() - for _, f := range p.files { - if f.f.Pos() <= pos && pos < f.f.End() { - scope = p.typesInfo.Scopes[f.f] - break - } - } - } - return scope -} - -func (p *pkg) scanSortable() { - p.sortable = make(map[string]bool) - - // bitfield for which methods exist on each type. - const ( - Len = 1 << iota - Less - Swap - ) - nmap := map[string]int{"Len": Len, "Less": Less, "Swap": Swap} - has := make(map[string]int) - for _, f := range p.files { - f.walk(func(n ast.Node) bool { - fn, ok := n.(*ast.FuncDecl) - if !ok || fn.Recv == nil || len(fn.Recv.List) == 0 { - return true - } - // TODO(dsymonds): We could check the signature to be more precise. - recv := receiverType(fn) - if i, ok := nmap[fn.Name.Name]; ok { - has[recv] |= i - } - return false - }) - } - for typ, ms := range has { - if ms == Len|Less|Swap { - p.sortable[typ] = true - } - } -} - -func (p *pkg) isMain() bool { - for _, f := range p.files { - if f.isMain() { - return true - } - } - return false -} - -func (f *file) isMain() bool { - if f.f.Name.Name == "main" { - return true - } - return false -} - -// lintPackageComment checks package comments. It complains if -// there is no package comment, or if it is not of the right form. -// This has a notable false positive in that a package comment -// could rightfully appear in a different file of the same package, -// but that's not easy to fix since this linter is file-oriented. -func (f *file) lintPackageComment() { - if f.isTest() { - return - } - - const ref = styleGuideBase + "#package-comments" - prefix := "Package " + f.f.Name.Name + " " - - // Look for a detached package comment. - // First, scan for the last comment that occurs before the "package" keyword. - var lastCG *ast.CommentGroup - for _, cg := range f.f.Comments { - if cg.Pos() > f.f.Package { - // Gone past "package" keyword. - break - } - lastCG = cg - } - if lastCG != nil && strings.HasPrefix(lastCG.Text(), prefix) { - endPos := f.fset.Position(lastCG.End()) - pkgPos := f.fset.Position(f.f.Package) - if endPos.Line+1 < pkgPos.Line { - // There isn't a great place to anchor this error; - // the start of the blank lines between the doc and the package statement - // is at least pointing at the location of the problem. - pos := token.Position{ - Filename: endPos.Filename, - // Offset not set; it is non-trivial, and doesn't appear to be needed. - Line: endPos.Line + 1, - Column: 1, - } - f.pkg.errorfAt(pos, 0.9, link(ref), category("comments"), "package comment is detached; there should be no blank lines between it and the package statement") - return - } - } - - if f.f.Doc == nil { - f.errorf(f.f, 0.2, link(ref), category("comments"), "should have a package comment, unless it's in another file for this package") - return - } - s := f.f.Doc.Text() - if ts := strings.TrimLeft(s, " \t"); ts != s { - f.errorf(f.f.Doc, 1, link(ref), category("comments"), "package comment should not have leading space") - s = ts - } - // Only non-main packages need to keep to this form. - if !f.pkg.main && !strings.HasPrefix(s, prefix) { - f.errorf(f.f.Doc, 1, link(ref), category("comments"), `package comment should be of the form "%s..."`, prefix) - } -} - -func (f *file) isCgo() bool { - if f.src == nil { - return false - } - newLinePos := bytes.Index(f.src, []byte("\n")) - if newLinePos < 0 { - return false - } - firstLine := string(f.src[:newLinePos]) - - // files using cgo have implicitly added comment "Created by cgo - DO NOT EDIT" for go <= 1.10 - // and "Code generated by cmd/cgo" for go >= 1.11 - return strings.Contains(firstLine, "Created by cgo") || strings.Contains(firstLine, "Code generated by cmd/cgo") -} - -// lintBlankImports complains if a non-main package has blank imports that are -// not documented. -func (f *file) lintBlankImports() { - // In package main and in tests, we don't complain about blank imports. - if f.pkg.main || f.isTest() || f.isCgo() { - return - } - - // The first element of each contiguous group of blank imports should have - // an explanatory comment of some kind. - for i, imp := range f.f.Imports { - pos := f.fset.Position(imp.Pos()) - - if !isBlank(imp.Name) { - continue // Ignore non-blank imports. - } - if i > 0 { - prev := f.f.Imports[i-1] - prevPos := f.fset.Position(prev.Pos()) - if isBlank(prev.Name) && prevPos.Line+1 == pos.Line { - continue // A subsequent blank in a group. - } - } - - // This is the first blank import of a group. - if imp.Doc == nil && imp.Comment == nil { - ref := "" - f.errorf(imp, 1, link(ref), category("imports"), "a blank import should be only in a main or test package, or have a comment justifying it") - } - } -} - -// lintImports examines import blocks. -func (f *file) lintImports() { - for i, is := range f.f.Imports { - _ = i - if is.Name != nil && is.Name.Name == "." && !f.isTest() { - f.errorf(is, 1, link(styleGuideBase+"#import-dot"), category("imports"), "should not use dot imports") - } - - } -} - -const docCommentsLink = styleGuideBase + "#doc-comments" - -// lintExported examines the exported names. -// It complains if any required doc comments are missing, -// or if they are not of the right form. The exact rules are in -// lintFuncDoc, lintTypeDoc and lintValueSpecDoc; this function -// also tracks the GenDecl structure being traversed to permit -// doc comments for constants to be on top of the const block. -// It also complains if the names stutter when combined with -// the package name. -func (f *file) lintExported() { - if f.isTest() { - return - } - - var lastGen *ast.GenDecl // last GenDecl entered. - - // Set of GenDecls that have already had missing comments flagged. - genDeclMissingComments := make(map[*ast.GenDecl]bool) - - f.walk(func(node ast.Node) bool { - switch v := node.(type) { - case *ast.GenDecl: - if v.Tok == token.IMPORT { - return false - } - // token.CONST, token.TYPE or token.VAR - lastGen = v - return true - case *ast.FuncDecl: - f.lintFuncDoc(v) - if v.Recv == nil { - // Only check for stutter on functions, not methods. - // Method names are not used package-qualified. - f.checkStutter(v.Name, "func") - } - // Don't proceed inside funcs. - return false - case *ast.TypeSpec: - // inside a GenDecl, which usually has the doc - doc := v.Doc - if doc == nil { - doc = lastGen.Doc - } - f.lintTypeDoc(v, doc) - f.checkStutter(v.Name, "type") - // Don't proceed inside types. - return false - case *ast.ValueSpec: - f.lintValueSpecDoc(v, lastGen, genDeclMissingComments) - return false - } - return true - }) -} - -var ( - allCapsRE = regexp.MustCompile(`^[A-Z0-9_]+$`) - anyCapsRE = regexp.MustCompile(`[A-Z]`) -) - -// knownNameExceptions is a set of names that are known to be exempt from naming checks. -// This is usually because they are constrained by having to match names in the -// standard library. -var knownNameExceptions = map[string]bool{ - "LastInsertId": true, // must match database/sql - "kWh": true, -} - -func isInTopLevel(f *ast.File, ident *ast.Ident) bool { - path, _ := astutil.PathEnclosingInterval(f, ident.Pos(), ident.End()) - for _, f := range path { - switch f.(type) { - case *ast.File, *ast.GenDecl, *ast.ValueSpec, *ast.Ident: - continue - } - return false - } - return true -} - -// lintNames examines all names in the file. -// It complains if any use underscores or incorrect known initialisms. -func (f *file) lintNames() { - // Package names need slightly different handling than other names. - if strings.Contains(f.f.Name.Name, "_") && !strings.HasSuffix(f.f.Name.Name, "_test") { - f.errorf(f.f, 1, link("http://golang.org/doc/effective_go.html#package-names"), category("naming"), "don't use an underscore in package name") - } - if anyCapsRE.MatchString(f.f.Name.Name) { - f.errorf(f.f, 1, link("http://golang.org/doc/effective_go.html#package-names"), category("mixed-caps"), "don't use MixedCaps in package name; %s should be %s", f.f.Name.Name, strings.ToLower(f.f.Name.Name)) - } - - check := func(id *ast.Ident, thing string) { - if id.Name == "_" { - return - } - if knownNameExceptions[id.Name] { - return - } - - // Handle two common styles from other languages that don't belong in Go. - if len(id.Name) >= 5 && allCapsRE.MatchString(id.Name) && strings.Contains(id.Name, "_") { - capCount := 0 - for _, c := range id.Name { - if 'A' <= c && c <= 'Z' { - capCount++ - } - } - if capCount >= 2 { - f.errorf(id, 0.8, link(styleGuideBase+"#mixed-caps"), category("naming"), "don't use ALL_CAPS in Go names; use CamelCase") - return - } - } - if thing == "const" || (thing == "var" && isInTopLevel(f.f, id)) { - if len(id.Name) > 2 && id.Name[0] == 'k' && id.Name[1] >= 'A' && id.Name[1] <= 'Z' { - should := string(id.Name[1]+'a'-'A') + id.Name[2:] - f.errorf(id, 0.8, link(styleGuideBase+"#mixed-caps"), category("naming"), "don't use leading k in Go names; %s %s should be %s", thing, id.Name, should) - } - } - - should := lintName(id.Name) - if id.Name == should { - return - } - - if len(id.Name) > 2 && strings.Contains(id.Name[1:], "_") { - f.errorf(id, 0.9, link("http://golang.org/doc/effective_go.html#mixed-caps"), category("naming"), "don't use underscores in Go names; %s %s should be %s", thing, id.Name, should) - return - } - f.errorf(id, 0.8, link(styleGuideBase+"#initialisms"), category("naming"), "%s %s should be %s", thing, id.Name, should) - } - checkList := func(fl *ast.FieldList, thing string) { - if fl == nil { - return - } - for _, f := range fl.List { - for _, id := range f.Names { - check(id, thing) - } - } - } - f.walk(func(node ast.Node) bool { - switch v := node.(type) { - case *ast.AssignStmt: - if v.Tok == token.ASSIGN { - return true - } - for _, exp := range v.Lhs { - if id, ok := exp.(*ast.Ident); ok { - check(id, "var") - } - } - case *ast.FuncDecl: - if f.isTest() && (strings.HasPrefix(v.Name.Name, "Example") || strings.HasPrefix(v.Name.Name, "Test") || strings.HasPrefix(v.Name.Name, "Benchmark")) { - return true - } - - thing := "func" - if v.Recv != nil { - thing = "method" - } - - // Exclude naming warnings for functions that are exported to C but - // not exported in the Go API. - // See https://github.com/golang/lint/issues/144. - if ast.IsExported(v.Name.Name) || !isCgoExported(v) { - check(v.Name, thing) - } - - checkList(v.Type.Params, thing+" parameter") - checkList(v.Type.Results, thing+" result") - case *ast.GenDecl: - if v.Tok == token.IMPORT { - return true - } - var thing string - switch v.Tok { - case token.CONST: - thing = "const" - case token.TYPE: - thing = "type" - case token.VAR: - thing = "var" - } - for _, spec := range v.Specs { - switch s := spec.(type) { - case *ast.TypeSpec: - check(s.Name, thing) - case *ast.ValueSpec: - for _, id := range s.Names { - check(id, thing) - } - } - } - case *ast.InterfaceType: - // Do not check interface method names. - // They are often constrainted by the method names of concrete types. - for _, x := range v.Methods.List { - ft, ok := x.Type.(*ast.FuncType) - if !ok { // might be an embedded interface name - continue - } - checkList(ft.Params, "interface method parameter") - checkList(ft.Results, "interface method result") - } - case *ast.RangeStmt: - if v.Tok == token.ASSIGN { - return true - } - if id, ok := v.Key.(*ast.Ident); ok { - check(id, "range var") - } - if id, ok := v.Value.(*ast.Ident); ok { - check(id, "range var") - } - case *ast.StructType: - for _, f := range v.Fields.List { - for _, id := range f.Names { - check(id, "struct field") - } - } - } - return true - }) -} - -// lintName returns a different name if it should be different. -func lintName(name string) (should string) { - // Fast path for simple cases: "_" and all lowercase. - if name == "_" { - return name - } - allLower := true - for _, r := range name { - if !unicode.IsLower(r) { - allLower = false - break - } - } - if allLower { - return name - } - - // Split camelCase at any lower->upper transition, and split on underscores. - // Check each word for common initialisms. - runes := []rune(name) - w, i := 0, 0 // index of start of word, scan - for i+1 <= len(runes) { - eow := false // whether we hit the end of a word - if i+1 == len(runes) { - eow = true - } else if runes[i+1] == '_' { - // underscore; shift the remainder forward over any run of underscores - eow = true - n := 1 - for i+n+1 < len(runes) && runes[i+n+1] == '_' { - n++ - } - - // Leave at most one underscore if the underscore is between two digits - if i+n+1 < len(runes) && unicode.IsDigit(runes[i]) && unicode.IsDigit(runes[i+n+1]) { - n-- - } - - copy(runes[i+1:], runes[i+n+1:]) - runes = runes[:len(runes)-n] - } else if unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]) { - // lower->non-lower - eow = true - } - i++ - if !eow { - continue - } - - // [w,i) is a word. - word := string(runes[w:i]) - if u := strings.ToUpper(word); commonInitialisms[u] { - // Keep consistent case, which is lowercase only at the start. - if w == 0 && unicode.IsLower(runes[w]) { - u = strings.ToLower(u) - } - // All the common initialisms are ASCII, - // so we can replace the bytes exactly. - copy(runes[w:], []rune(u)) - } else if w > 0 && strings.ToLower(word) == word { - // already all lowercase, and not the first word, so uppercase the first character. - runes[w] = unicode.ToUpper(runes[w]) - } - w = i - } - return string(runes) -} - -// commonInitialisms is a set of common initialisms. -// Only add entries that are highly unlikely to be non-initialisms. -// For instance, "ID" is fine (Freudian code is rare), but "AND" is not. -var commonInitialisms = map[string]bool{ - "ACL": true, - "API": true, - "ASCII": true, - "CPU": true, - "CSS": true, - "DNS": true, - "EOF": true, - "GUID": true, - "HTML": true, - "HTTP": true, - "HTTPS": true, - "ID": true, - "IP": true, - "JSON": true, - "LHS": true, - "QPS": true, - "RAM": true, - "RHS": true, - "RPC": true, - "SLA": true, - "SMTP": true, - "SQL": true, - "SSH": true, - "TCP": true, - "TLS": true, - "TTL": true, - "UDP": true, - "UI": true, - "UID": true, - "UUID": true, - "URI": true, - "URL": true, - "UTF8": true, - "VM": true, - "XML": true, - "XMPP": true, - "XSRF": true, - "XSS": true, -} - -// lintTypeDoc examines the doc comment on a type. -// It complains if they are missing from an exported type, -// or if they are not of the standard form. -func (f *file) lintTypeDoc(t *ast.TypeSpec, doc *ast.CommentGroup) { - if !ast.IsExported(t.Name.Name) { - return - } - if doc == nil { - f.errorf(t, 1, link(docCommentsLink), category("comments"), "exported type %v should have comment or be unexported", t.Name) - return - } - - s := doc.Text() - articles := [...]string{"A", "An", "The"} - for _, a := range articles { - if strings.HasPrefix(s, a+" ") { - s = s[len(a)+1:] - break - } - } - if !strings.HasPrefix(s, t.Name.Name+" ") { - f.errorf(doc, 1, link(docCommentsLink), category("comments"), `comment on exported type %v should be of the form "%v ..." (with optional leading article)`, t.Name, t.Name) - } -} - -var commonMethods = map[string]bool{ - "Error": true, - "Read": true, - "ServeHTTP": true, - "String": true, - "Write": true, -} - -// lintFuncDoc examines doc comments on functions and methods. -// It complains if they are missing, or not of the right form. -// It has specific exclusions for well-known methods (see commonMethods above). -func (f *file) lintFuncDoc(fn *ast.FuncDecl) { - if !ast.IsExported(fn.Name.Name) { - // func is unexported - return - } - kind := "function" - name := fn.Name.Name - if fn.Recv != nil && len(fn.Recv.List) > 0 { - // method - kind = "method" - recv := receiverType(fn) - if !ast.IsExported(recv) { - // receiver is unexported - return - } - if commonMethods[name] { - return - } - switch name { - case "Len", "Less", "Swap": - if f.pkg.sortable[recv] { - return - } - } - name = recv + "." + name - } - if fn.Doc == nil { - f.errorf(fn, 1, link(docCommentsLink), category("comments"), "exported %s %s should have comment or be unexported", kind, name) - return - } - s := fn.Doc.Text() - prefix := fn.Name.Name + " " - if !strings.HasPrefix(s, prefix) { - f.errorf(fn.Doc, 1, link(docCommentsLink), category("comments"), `comment on exported %s %s should be of the form "%s..."`, kind, name, prefix) - } -} - -// lintValueSpecDoc examines package-global variables and constants. -// It complains if they are not individually declared, -// or if they are not suitably documented in the right form (unless they are in a block that is commented). -func (f *file) lintValueSpecDoc(vs *ast.ValueSpec, gd *ast.GenDecl, genDeclMissingComments map[*ast.GenDecl]bool) { - kind := "var" - if gd.Tok == token.CONST { - kind = "const" - } - - if len(vs.Names) > 1 { - // Check that none are exported except for the first. - for _, n := range vs.Names[1:] { - if ast.IsExported(n.Name) { - f.errorf(vs, 1, category("comments"), "exported %s %s should have its own declaration", kind, n.Name) - return - } - } - } - - // Only one name. - name := vs.Names[0].Name - if !ast.IsExported(name) { - return - } - - if vs.Doc == nil && gd.Doc == nil { - if genDeclMissingComments[gd] { - return - } - block := "" - if kind == "const" && gd.Lparen.IsValid() { - block = " (or a comment on this block)" - } - f.errorf(vs, 1, link(docCommentsLink), category("comments"), "exported %s %s should have comment%s or be unexported", kind, name, block) - genDeclMissingComments[gd] = true - return - } - // If this GenDecl has parens and a comment, we don't check its comment form. - if gd.Lparen.IsValid() && gd.Doc != nil { - return - } - // The relevant text to check will be on either vs.Doc or gd.Doc. - // Use vs.Doc preferentially. - doc := vs.Doc - if doc == nil { - doc = gd.Doc - } - prefix := name + " " - if !strings.HasPrefix(doc.Text(), prefix) { - f.errorf(doc, 1, link(docCommentsLink), category("comments"), `comment on exported %s %s should be of the form "%s..."`, kind, name, prefix) - } -} - -func (f *file) checkStutter(id *ast.Ident, thing string) { - pkg, name := f.f.Name.Name, id.Name - if !ast.IsExported(name) { - // unexported name - return - } - // A name stutters if the package name is a strict prefix - // and the next character of the name starts a new word. - if len(name) <= len(pkg) { - // name is too short to stutter. - // This permits the name to be the same as the package name. - return - } - if !strings.EqualFold(pkg, name[:len(pkg)]) { - return - } - // We can assume the name is well-formed UTF-8. - // If the next rune after the package name is uppercase or an underscore - // the it's starting a new word and thus this name stutters. - rem := name[len(pkg):] - if next, _ := utf8.DecodeRuneInString(rem); next == '_' || unicode.IsUpper(next) { - f.errorf(id, 0.8, link(styleGuideBase+"#package-names"), category("naming"), "%s name will be used as %s.%s by other packages, and that stutters; consider calling this %s", thing, pkg, name, rem) - } -} - -// zeroLiteral is a set of ast.BasicLit values that are zero values. -// It is not exhaustive. -var zeroLiteral = map[string]bool{ - "false": true, // bool - // runes - `'\x00'`: true, - `'\000'`: true, - // strings - `""`: true, - "``": true, - // numerics - "0": true, - "0.": true, - "0.0": true, - "0i": true, -} - -// lintElses examines else blocks. It complains about any else block whose if block ends in a return. -func (f *file) lintElses() { - // We don't want to flag if { } else if { } else { } constructions. - // They will appear as an IfStmt whose Else field is also an IfStmt. - // Record such a node so we ignore it when we visit it. - ignore := make(map[*ast.IfStmt]bool) - - f.walk(func(node ast.Node) bool { - ifStmt, ok := node.(*ast.IfStmt) - if !ok || ifStmt.Else == nil { - return true - } - if elseif, ok := ifStmt.Else.(*ast.IfStmt); ok { - ignore[elseif] = true - return true - } - if ignore[ifStmt] { - return true - } - if _, ok := ifStmt.Else.(*ast.BlockStmt); !ok { - // only care about elses without conditions - return true - } - if len(ifStmt.Body.List) == 0 { - return true - } - shortDecl := false // does the if statement have a ":=" initialization statement? - if ifStmt.Init != nil { - if as, ok := ifStmt.Init.(*ast.AssignStmt); ok && as.Tok == token.DEFINE { - shortDecl = true - } - } - lastStmt := ifStmt.Body.List[len(ifStmt.Body.List)-1] - if _, ok := lastStmt.(*ast.ReturnStmt); ok { - extra := "" - if shortDecl { - extra = " (move short variable declaration to its own line if necessary)" - } - f.errorf(ifStmt.Else, 1, link(styleGuideBase+"#indent-error-flow"), category("indent"), "if block ends with a return statement, so drop this else and outdent its block"+extra) - } - return true - }) -} - -// lintRanges examines range clauses. It complains about redundant constructions. -func (f *file) lintRanges() { - f.walk(func(node ast.Node) bool { - rs, ok := node.(*ast.RangeStmt) - if !ok { - return true - } - - if isIdent(rs.Key, "_") && (rs.Value == nil || isIdent(rs.Value, "_")) { - p := f.errorf(rs.Key, 1, category("range-loop"), "should omit values from range; this loop is equivalent to `for range ...`") - - newRS := *rs // shallow copy - newRS.Value = nil - newRS.Key = nil - p.ReplacementLine = f.firstLineOf(&newRS, rs) - - return true - } - - if isIdent(rs.Value, "_") { - p := f.errorf(rs.Value, 1, category("range-loop"), "should omit 2nd value from range; this loop is equivalent to `for %s %s range ...`", f.render(rs.Key), rs.Tok) - - newRS := *rs // shallow copy - newRS.Value = nil - p.ReplacementLine = f.firstLineOf(&newRS, rs) - } - - return true - }) -} - -// lintErrorf examines errors.New and testing.Error calls. It complains if its only argument is an fmt.Sprintf invocation. -func (f *file) lintErrorf() { - f.walk(func(node ast.Node) bool { - ce, ok := node.(*ast.CallExpr) - if !ok || len(ce.Args) != 1 { - return true - } - isErrorsNew := isPkgDot(ce.Fun, "errors", "New") - var isTestingError bool - se, ok := ce.Fun.(*ast.SelectorExpr) - if ok && se.Sel.Name == "Error" { - if typ := f.pkg.typeOf(se.X); typ != nil { - isTestingError = typ.String() == "*testing.T" - } - } - if !isErrorsNew && !isTestingError { - return true - } - if !f.imports("errors") { - return true - } - arg := ce.Args[0] - ce, ok = arg.(*ast.CallExpr) - if !ok || !isPkgDot(ce.Fun, "fmt", "Sprintf") { - return true - } - errorfPrefix := "fmt" - if isTestingError { - errorfPrefix = f.render(se.X) - } - p := f.errorf(node, 1, category("errors"), "should replace %s(fmt.Sprintf(...)) with %s.Errorf(...)", f.render(se), errorfPrefix) - - m := f.srcLineWithMatch(ce, `^(.*)`+f.render(se)+`\(fmt\.Sprintf\((.*)\)\)(.*)$`) - if m != nil { - p.ReplacementLine = m[1] + errorfPrefix + ".Errorf(" + m[2] + ")" + m[3] - } - - return true - }) -} - -// lintErrors examines global error vars. It complains if they aren't named in the standard way. -func (f *file) lintErrors() { - for _, decl := range f.f.Decls { - gd, ok := decl.(*ast.GenDecl) - if !ok || gd.Tok != token.VAR { - continue - } - for _, spec := range gd.Specs { - spec := spec.(*ast.ValueSpec) - if len(spec.Names) != 1 || len(spec.Values) != 1 { - continue - } - ce, ok := spec.Values[0].(*ast.CallExpr) - if !ok { - continue - } - if !isPkgDot(ce.Fun, "errors", "New") && !isPkgDot(ce.Fun, "fmt", "Errorf") { - continue - } - - id := spec.Names[0] - prefix := "err" - if id.IsExported() { - prefix = "Err" - } - if !strings.HasPrefix(id.Name, prefix) { - f.errorf(id, 0.9, category("naming"), "error var %s should have name of the form %sFoo", id.Name, prefix) - } - } - } -} - -func lintErrorString(s string) (isClean bool, conf float64) { - const basicConfidence = 0.8 - const capConfidence = basicConfidence - 0.2 - first, firstN := utf8.DecodeRuneInString(s) - last, _ := utf8.DecodeLastRuneInString(s) - if last == '.' || last == ':' || last == '!' || last == '\n' { - return false, basicConfidence - } - if unicode.IsUpper(first) { - // People use proper nouns and exported Go identifiers in error strings, - // so decrease the confidence of warnings for capitalization. - if len(s) <= firstN { - return false, capConfidence - } - // Flag strings starting with something that doesn't look like an initialism. - if second, _ := utf8.DecodeRuneInString(s[firstN:]); !unicode.IsUpper(second) { - return false, capConfidence - } - } - return true, 0 -} - -// lintErrorStrings examines error strings. -// It complains if they are capitalized or end in punctuation or a newline. -func (f *file) lintErrorStrings() { - f.walk(func(node ast.Node) bool { - ce, ok := node.(*ast.CallExpr) - if !ok { - return true - } - if !isPkgDot(ce.Fun, "errors", "New") && !isPkgDot(ce.Fun, "fmt", "Errorf") { - return true - } - if len(ce.Args) < 1 { - return true - } - str, ok := ce.Args[0].(*ast.BasicLit) - if !ok || str.Kind != token.STRING { - return true - } - s, _ := strconv.Unquote(str.Value) // can assume well-formed Go - if s == "" { - return true - } - clean, conf := lintErrorString(s) - if clean { - return true - } - - f.errorf(str, conf, link(styleGuideBase+"#error-strings"), category("errors"), - "error strings should not be capitalized or end with punctuation or a newline") - return true - }) -} - -// lintReceiverNames examines receiver names. It complains about inconsistent -// names used for the same type and names such as "this". -func (f *file) lintReceiverNames() { - typeReceiver := map[string]string{} - f.walk(func(n ast.Node) bool { - fn, ok := n.(*ast.FuncDecl) - if !ok || fn.Recv == nil || len(fn.Recv.List) == 0 { - return true - } - names := fn.Recv.List[0].Names - if len(names) < 1 { - return true - } - name := names[0].Name - const ref = styleGuideBase + "#receiver-names" - if name == "_" { - f.errorf(n, 1, link(ref), category("naming"), `receiver name should not be an underscore, omit the name if it is unused`) - return true - } - if name == "this" || name == "self" { - f.errorf(n, 1, link(ref), category("naming"), `receiver name should be a reflection of its identity; don't use generic names such as "this" or "self"`) - return true - } - recv := receiverType(fn) - if prev, ok := typeReceiver[recv]; ok && prev != name { - f.errorf(n, 1, link(ref), category("naming"), "receiver name %s should be consistent with previous receiver name %s for %s", name, prev, recv) - return true - } - typeReceiver[recv] = name - return true - }) -} - -// lintIncDec examines statements that increment or decrement a variable. -// It complains if they don't use x++ or x--. -func (f *file) lintIncDec() { - f.walk(func(n ast.Node) bool { - as, ok := n.(*ast.AssignStmt) - if !ok { - return true - } - if len(as.Lhs) != 1 { - return true - } - if !isOne(as.Rhs[0]) { - return true - } - var suffix string - switch as.Tok { - case token.ADD_ASSIGN: - suffix = "++" - case token.SUB_ASSIGN: - suffix = "--" - default: - return true - } - f.errorf(as, 0.8, category("unary-op"), "should replace %s with %s%s", f.render(as), f.render(as.Lhs[0]), suffix) - return true - }) -} - -// lintErrorReturn examines function declarations that return an error. -// It complains if the error isn't the last parameter. -func (f *file) lintErrorReturn() { - f.walk(func(n ast.Node) bool { - fn, ok := n.(*ast.FuncDecl) - if !ok || fn.Type.Results == nil { - return true - } - ret := fn.Type.Results.List - if len(ret) <= 1 { - return true - } - if isIdent(ret[len(ret)-1].Type, "error") { - return true - } - // An error return parameter should be the last parameter. - // Flag any error parameters found before the last. - for _, r := range ret[:len(ret)-1] { - if isIdent(r.Type, "error") { - f.errorf(fn, 0.9, category("arg-order"), "error should be the last type when returning multiple items") - break // only flag one - } - } - return true - }) -} - -// lintUnexportedReturn examines exported function declarations. -// It complains if any return an unexported type. -func (f *file) lintUnexportedReturn() { - f.walk(func(n ast.Node) bool { - fn, ok := n.(*ast.FuncDecl) - if !ok { - return true - } - if fn.Type.Results == nil { - return false - } - if !fn.Name.IsExported() { - return false - } - thing := "func" - if fn.Recv != nil && len(fn.Recv.List) > 0 { - thing = "method" - if !ast.IsExported(receiverType(fn)) { - // Don't report exported methods of unexported types, - // such as private implementations of sort.Interface. - return false - } - } - for _, ret := range fn.Type.Results.List { - typ := f.pkg.typeOf(ret.Type) - if exportedType(typ) { - continue - } - f.errorf(ret.Type, 0.8, category("unexported-type-in-api"), - "exported %s %s returns unexported type %s, which can be annoying to use", - thing, fn.Name.Name, typ) - break // only flag one - } - return false - }) -} - -// exportedType reports whether typ is an exported type. -// It is imprecise, and will err on the side of returning true, -// such as for composite types. -func exportedType(typ types.Type) bool { - switch T := typ.(type) { - case *types.Named: - // Builtin types have no package. - return T.Obj().Pkg() == nil || T.Obj().Exported() - case *types.Map: - return exportedType(T.Key()) && exportedType(T.Elem()) - case interface { - Elem() types.Type - }: // array, slice, pointer, chan - return exportedType(T.Elem()) - } - // Be conservative about other types, such as struct, interface, etc. - return true -} - -// timeSuffixes is a list of name suffixes that imply a time unit. -// This is not an exhaustive list. -var timeSuffixes = []string{ - "Sec", "Secs", "Seconds", - "Msec", "Msecs", - "Milli", "Millis", "Milliseconds", - "Usec", "Usecs", "Microseconds", - "MS", "Ms", -} - -func (f *file) lintTimeNames() { - f.walk(func(node ast.Node) bool { - v, ok := node.(*ast.ValueSpec) - if !ok { - return true - } - for _, name := range v.Names { - origTyp := f.pkg.typeOf(name) - // Look for time.Duration or *time.Duration; - // the latter is common when using flag.Duration. - typ := origTyp - if pt, ok := typ.(*types.Pointer); ok { - typ = pt.Elem() - } - if !f.pkg.isNamedType(typ, "time", "Duration") { - continue - } - suffix := "" - for _, suf := range timeSuffixes { - if strings.HasSuffix(name.Name, suf) { - suffix = suf - break - } - } - if suffix == "" { - continue - } - f.errorf(v, 0.9, category("time"), "var %s is of type %v; don't use unit-specific suffix %q", name.Name, origTyp, suffix) - } - return true - }) -} - -// lintContextKeyTypes checks for call expressions to context.WithValue with -// basic types used for the key argument. -// See: https://golang.org/issue/17293 -func (f *file) lintContextKeyTypes() { - f.walk(func(node ast.Node) bool { - switch node := node.(type) { - case *ast.CallExpr: - f.checkContextKeyType(node) - } - - return true - }) -} - -// checkContextKeyType reports an error if the call expression calls -// context.WithValue with a key argument of basic type. -func (f *file) checkContextKeyType(x *ast.CallExpr) { - sel, ok := x.Fun.(*ast.SelectorExpr) - if !ok { - return - } - pkg, ok := sel.X.(*ast.Ident) - if !ok || pkg.Name != "context" { - return - } - if sel.Sel.Name != "WithValue" { - return - } - - // key is second argument to context.WithValue - if len(x.Args) != 3 { - return - } - key := f.pkg.typesInfo.Types[x.Args[1]] - - if ktyp, ok := key.Type.(*types.Basic); ok && ktyp.Kind() != types.Invalid { - f.errorf(x, 1.0, category("context"), fmt.Sprintf("should not use basic type %s as key in context.WithValue", key.Type)) - } -} - -// lintContextArgs examines function declarations that contain an -// argument with a type of context.Context -// It complains if that argument isn't the first parameter. -func (f *file) lintContextArgs() { - f.walk(func(n ast.Node) bool { - fn, ok := n.(*ast.FuncDecl) - if !ok || len(fn.Type.Params.List) <= 1 { - return true - } - // A context.Context should be the first parameter of a function. - // Flag any that show up after the first. - for _, arg := range fn.Type.Params.List[1:] { - if isPkgDot(arg.Type, "context", "Context") { - f.errorf(fn, 0.9, link("https://golang.org/pkg/context/"), category("arg-order"), "context.Context should be the first parameter of a function") - break // only flag one - } - } - return true - }) -} - -// containsComments returns whether the interval [start, end) contains any -// comments without "// MATCH " prefix. -func (f *file) containsComments(start, end token.Pos) bool { - for _, cgroup := range f.f.Comments { - comments := cgroup.List - if comments[0].Slash >= end { - // All comments starting with this group are after end pos. - return false - } - if comments[len(comments)-1].Slash < start { - // Comments group ends before start pos. - continue - } - for _, c := range comments { - if start <= c.Slash && c.Slash < end && !strings.HasPrefix(c.Text, "// MATCH ") { - return true - } - } - } - return false -} - -// receiverType returns the named type of the method receiver, sans "*", -// or "invalid-type" if fn.Recv is ill formed. -func receiverType(fn *ast.FuncDecl) string { - switch e := fn.Recv.List[0].Type.(type) { - case *ast.Ident: - return e.Name - case *ast.StarExpr: - if id, ok := e.X.(*ast.Ident); ok { - return id.Name - } - } - // The parser accepts much more than just the legal forms. - return "invalid-type" -} - -func (f *file) walk(fn func(ast.Node) bool) { - ast.Walk(walker(fn), f.f) -} - -func (f *file) render(x interface{}) string { - var buf bytes.Buffer - if err := printer.Fprint(&buf, f.fset, x); err != nil { - panic(err) - } - return buf.String() -} - -func (f *file) debugRender(x interface{}) string { - var buf bytes.Buffer - if err := ast.Fprint(&buf, f.fset, x, nil); err != nil { - panic(err) - } - return buf.String() -} - -// walker adapts a function to satisfy the ast.Visitor interface. -// The function return whether the walk should proceed into the node's children. -type walker func(ast.Node) bool - -func (w walker) Visit(node ast.Node) ast.Visitor { - if w(node) { - return w - } - return nil -} - -func isIdent(expr ast.Expr, ident string) bool { - id, ok := expr.(*ast.Ident) - return ok && id.Name == ident -} - -// isBlank returns whether id is the blank identifier "_". -// If id == nil, the answer is false. -func isBlank(id *ast.Ident) bool { return id != nil && id.Name == "_" } - -func isPkgDot(expr ast.Expr, pkg, name string) bool { - sel, ok := expr.(*ast.SelectorExpr) - return ok && isIdent(sel.X, pkg) && isIdent(sel.Sel, name) -} - -func isOne(expr ast.Expr) bool { - lit, ok := expr.(*ast.BasicLit) - return ok && lit.Kind == token.INT && lit.Value == "1" -} - -func isCgoExported(f *ast.FuncDecl) bool { - if f.Recv != nil || f.Doc == nil { - return false - } - - cgoExport := regexp.MustCompile(fmt.Sprintf("(?m)^//export %s$", regexp.QuoteMeta(f.Name.Name))) - for _, c := range f.Doc.List { - if cgoExport.MatchString(c.Text) { - return true - } - } - return false -} - -var basicTypeKinds = map[types.BasicKind]string{ - types.UntypedBool: "bool", - types.UntypedInt: "int", - types.UntypedRune: "rune", - types.UntypedFloat: "float64", - types.UntypedComplex: "complex128", - types.UntypedString: "string", -} - -// isUntypedConst reports whether expr is an untyped constant, -// and indicates what its default type is. -// scope may be nil. -func (f *file) isUntypedConst(expr ast.Expr) (defType string, ok bool) { - // Re-evaluate expr outside of its context to see if it's untyped. - // (An expr evaluated within, for example, an assignment context will get the type of the LHS.) - exprStr := f.render(expr) - tv, err := types.Eval(f.fset, f.pkg.typesPkg, expr.Pos(), exprStr) - if err != nil { - return "", false - } - if b, ok := tv.Type.(*types.Basic); ok { - if dt, ok := basicTypeKinds[b.Kind()]; ok { - return dt, true - } - } - - return "", false -} - -// firstLineOf renders the given node and returns its first line. -// It will also match the indentation of another node. -func (f *file) firstLineOf(node, match ast.Node) string { - line := f.render(node) - if i := strings.Index(line, "\n"); i >= 0 { - line = line[:i] - } - return f.indentOf(match) + line -} - -func (f *file) indentOf(node ast.Node) string { - line := srcLine(f.src, f.fset.Position(node.Pos())) - for i, r := range line { - switch r { - case ' ', '\t': - default: - return line[:i] - } - } - return line // unusual or empty line -} - -func (f *file) srcLineWithMatch(node ast.Node, pattern string) (m []string) { - line := srcLine(f.src, f.fset.Position(node.Pos())) - line = strings.TrimSuffix(line, "\n") - rx := regexp.MustCompile(pattern) - return rx.FindStringSubmatch(line) -} - -// imports returns true if the current file imports the specified package path. -func (f *file) imports(importPath string) bool { - all := astutil.Imports(f.fset, f.f) - for _, p := range all { - for _, i := range p { - uq, err := strconv.Unquote(i.Path.Value) - if err == nil && importPath == uq { - return true - } - } - } - return false -} - -// srcLine returns the complete line at p, including the terminating newline. -func srcLine(src []byte, p token.Position) string { - // Run to end of line in both directions if not at line start/end. - lo, hi := p.Offset, p.Offset+1 - for lo > 0 && src[lo-1] != '\n' { - lo-- - } - for hi < len(src) && src[hi-1] != '\n' { - hi++ - } - return string(src[lo:hi]) -} diff --git a/vendor/github.com/golangci/maligned/LICENSE b/vendor/github.com/golangci/maligned/LICENSE deleted file mode 100644 index 7448756763..0000000000 --- a/vendor/github.com/golangci/maligned/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2012 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/golangci/maligned/README b/vendor/github.com/golangci/maligned/README deleted file mode 100644 index 4e57f6eab2..0000000000 --- a/vendor/github.com/golangci/maligned/README +++ /dev/null @@ -1,7 +0,0 @@ -Install: - - go get github.com/mdempsky/maligned - -Usage: - - maligned cmd/compile/internal/gc cmd/link/internal/ld diff --git a/vendor/github.com/golangci/maligned/maligned.go b/vendor/github.com/golangci/maligned/maligned.go deleted file mode 100644 index c2492b2ffa..0000000000 --- a/vendor/github.com/golangci/maligned/maligned.go +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright 2013 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 maligned - -import ( - "fmt" - "go/ast" - "go/build" - "go/token" - "go/types" - "sort" - "strings" - - "golang.org/x/tools/go/loader" -) - -var fset = token.NewFileSet() - -type Issue struct { - OldSize, NewSize int - NewStructDef string - Pos token.Position -} - -func Run(prog *loader.Program) []Issue { - flagVerbose := true - fset = prog.Fset - - var issues []Issue - - for _, pkg := range prog.InitialPackages() { - for _, file := range pkg.Files { - ast.Inspect(file, func(node ast.Node) bool { - if s, ok := node.(*ast.StructType); ok { - i := malign(node.Pos(), pkg.Types[s].Type.(*types.Struct), flagVerbose) - if i != nil { - issues = append(issues, *i) - } - } - return true - }) - } - } - - return issues -} - -func malign(pos token.Pos, str *types.Struct, verbose bool) *Issue { - wordSize := int64(8) - maxAlign := int64(8) - switch build.Default.GOARCH { - case "386", "arm": - wordSize, maxAlign = 4, 4 - case "amd64p32": - wordSize = 4 - } - - s := gcSizes{wordSize, maxAlign} - sz := s.Sizeof(str) - opt, fields := optimalSize(str, &s, verbose) - if sz == opt { - return nil - } - - newStructDefParts := []string{"struct{"} - - var w int - for _, f := range fields { - if n := len(f.Name()); n > w { - w = n - } - } - spaces := strings.Repeat(" ", w) - for _, f := range fields { - line := fmt.Sprintf("\t%s%s\t%s,", f.Name(), spaces[len(f.Name()):], f.Type().String()) - newStructDefParts = append(newStructDefParts, line) - } - newStructDefParts = append(newStructDefParts, "}") - - return &Issue{ - OldSize: int(sz), - NewSize: int(opt), - NewStructDef: strings.Join(newStructDefParts, "\n"), - Pos: fset.Position(pos), - } -} - -func optimalSize(str *types.Struct, sizes *gcSizes, stable bool) (int64, []*types.Var) { - nf := str.NumFields() - fields := make([]*types.Var, nf) - alignofs := make([]int64, nf) - sizeofs := make([]int64, nf) - for i := 0; i < nf; i++ { - fields[i] = str.Field(i) - ft := fields[i].Type() - alignofs[i] = sizes.Alignof(ft) - sizeofs[i] = sizes.Sizeof(ft) - } - if stable { // Stable keeps as much of the order as possible, but slower - sort.Stable(&byAlignAndSize{fields, alignofs, sizeofs}) - } else { - sort.Sort(&byAlignAndSize{fields, alignofs, sizeofs}) - } - return sizes.Sizeof(types.NewStruct(fields, nil)), fields -} - -type byAlignAndSize struct { - fields []*types.Var - alignofs []int64 - sizeofs []int64 -} - -func (s *byAlignAndSize) Len() int { return len(s.fields) } -func (s *byAlignAndSize) Swap(i, j int) { - s.fields[i], s.fields[j] = s.fields[j], s.fields[i] - s.alignofs[i], s.alignofs[j] = s.alignofs[j], s.alignofs[i] - s.sizeofs[i], s.sizeofs[j] = s.sizeofs[j], s.sizeofs[i] -} - -func (s *byAlignAndSize) Less(i, j int) bool { - // Place zero sized objects before non-zero sized objects. - if s.sizeofs[i] == 0 && s.sizeofs[j] != 0 { - return true - } - if s.sizeofs[j] == 0 && s.sizeofs[i] != 0 { - return false - } - - // Next, place more tightly aligned objects before less tightly aligned objects. - if s.alignofs[i] != s.alignofs[j] { - return s.alignofs[i] > s.alignofs[j] - } - - // Lastly, order by size. - if s.sizeofs[i] != s.sizeofs[j] { - return s.sizeofs[i] > s.sizeofs[j] - } - - return false -} - -// Code below based on go/types.StdSizes. - -type gcSizes struct { - WordSize int64 - MaxAlign int64 -} - -func (s *gcSizes) Alignof(T types.Type) int64 { - // NOTE: On amd64, complex64 is 8 byte aligned, - // even though float32 is only 4 byte aligned. - - // For arrays and structs, alignment is defined in terms - // of alignment of the elements and fields, respectively. - switch t := T.Underlying().(type) { - case *types.Array: - // spec: "For a variable x of array type: unsafe.Alignof(x) - // is the same as unsafe.Alignof(x[0]), but at least 1." - return s.Alignof(t.Elem()) - case *types.Struct: - // spec: "For a variable x of struct type: unsafe.Alignof(x) - // is the largest of the values unsafe.Alignof(x.f) for each - // field f of x, but at least 1." - max := int64(1) - for i, nf := 0, t.NumFields(); i < nf; i++ { - if a := s.Alignof(t.Field(i).Type()); a > max { - max = a - } - } - return max - } - a := s.Sizeof(T) // may be 0 - // spec: "For a variable x of any type: unsafe.Alignof(x) is at least 1." - if a < 1 { - return 1 - } - if a > s.MaxAlign { - return s.MaxAlign - } - return a -} - -var basicSizes = [...]byte{ - types.Bool: 1, - types.Int8: 1, - types.Int16: 2, - types.Int32: 4, - types.Int64: 8, - types.Uint8: 1, - types.Uint16: 2, - types.Uint32: 4, - types.Uint64: 8, - types.Float32: 4, - types.Float64: 8, - types.Complex64: 8, - types.Complex128: 16, -} - -func (s *gcSizes) Sizeof(T types.Type) int64 { - switch t := T.Underlying().(type) { - case *types.Basic: - k := t.Kind() - if int(k) < len(basicSizes) { - if s := basicSizes[k]; s > 0 { - return int64(s) - } - } - if k == types.String { - return s.WordSize * 2 - } - case *types.Array: - n := t.Len() - if n == 0 { - return 0 - } - a := s.Alignof(t.Elem()) - z := s.Sizeof(t.Elem()) - return align(z, a)*(n-1) + z - case *types.Slice: - return s.WordSize * 3 - case *types.Struct: - nf := t.NumFields() - if nf == 0 { - return 0 - } - - var o int64 - max := int64(1) - for i := 0; i < nf; i++ { - ft := t.Field(i).Type() - a, sz := s.Alignof(ft), s.Sizeof(ft) - if a > max { - max = a - } - if i == nf-1 && sz == 0 && o != 0 { - sz = 1 - } - o = align(o, a) + sz - } - return align(o, max) - case *types.Interface: - return s.WordSize * 2 - } - return s.WordSize // catch-all -} - -// align returns the smallest y >= x such that y % a == 0. -func align(x, a int64) int64 { - y := x + a - 1 - return y - y%a -} diff --git a/vendor/github.com/golangci/check/LICENSE b/vendor/github.com/golangci/plugin-module-register/LICENSE similarity index 98% rename from vendor/github.com/golangci/check/LICENSE rename to vendor/github.com/golangci/plugin-module-register/LICENSE index 5a1774b8e6..e72bfddabc 100644 --- a/vendor/github.com/golangci/check/LICENSE +++ b/vendor/github.com/golangci/plugin-module-register/LICENSE @@ -1,7 +1,7 @@ -GNU GENERAL PUBLIC LICENSE + GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - Copyright (C) 2007 Free Software Foundation, Inc. {http://fsf.org/} + Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -631,8 +631,8 @@ to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - {one line to give the program's name and a brief idea of what it does.} - Copyright (C) {year} {name of author} + + Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -645,14 +645,14 @@ the "copyright" line and a pointer to where the full notice is found. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see {http://www.gnu.org/licenses/}. + along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: - opennota Copyright (C) 2013 opennota + Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. @@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see -{http://www.gnu.org/licenses/}. +. The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read -{http://www.gnu.org/philosophy/why-not-lgpl.html}. +. \ No newline at end of file diff --git a/vendor/github.com/golangci/plugin-module-register/register/register.go b/vendor/github.com/golangci/plugin-module-register/register/register.go new file mode 100644 index 0000000000..72ad7f46f2 --- /dev/null +++ b/vendor/github.com/golangci/plugin-module-register/register/register.go @@ -0,0 +1,73 @@ +package register + +import ( + "bytes" + "encoding/json" + "fmt" + "sync" + + "golang.org/x/tools/go/analysis" +) + +// Plugins load mode. +const ( + LoadModeSyntax = "syntax" + LoadModeTypesInfo = "typesinfo" +) + +var ( + pluginsMu sync.RWMutex + plugins = make(map[string]NewPlugin) +) + +// LinterPlugin the interface of the plugin structure. +type LinterPlugin interface { + BuildAnalyzers() ([]*analysis.Analyzer, error) + GetLoadMode() string +} + +// NewPlugin the contract of the constructor of a plugin. +type NewPlugin func(conf any) (LinterPlugin, error) + +// Plugin registers a plugin. +func Plugin(name string, p NewPlugin) { + pluginsMu.Lock() + + plugins[name] = p + + pluginsMu.Unlock() +} + +// GetPlugin gets a plugin by name. +func GetPlugin(name string) (NewPlugin, error) { + pluginsMu.Lock() + defer pluginsMu.Unlock() + + p, ok := plugins[name] + if !ok { + return nil, fmt.Errorf("plugin %q not found", name) + } + + return p, nil +} + +// DecodeSettings decode settings from golangci-lint to the structure of the plugin configuration. +func DecodeSettings[T any](rawSettings any) (T, error) { + var buffer bytes.Buffer + + if err := json.NewEncoder(&buffer).Encode(rawSettings); err != nil { + var zero T + return zero, fmt.Errorf("encoding settings: %w", err) + } + + decoder := json.NewDecoder(&buffer) + decoder.DisallowUnknownFields() + + s := new(T) + if err := decoder.Decode(s); err != nil { + var zero T + return zero, fmt.Errorf("decoding settings: %w", err) + } + + return *s, nil +} diff --git a/vendor/github.com/golangci/unconvert/README b/vendor/github.com/golangci/unconvert/README deleted file mode 100644 index dbaea4f572..0000000000 --- a/vendor/github.com/golangci/unconvert/README +++ /dev/null @@ -1,36 +0,0 @@ -About: - -The unconvert program analyzes Go packages to identify unnecessary -type conversions; i.e., expressions T(x) where x already has type T. - -Install: - - $ go get github.com/mdempsky/unconvert - -Usage: - - $ unconvert -v bytes fmt - GOROOT/src/bytes/reader.go:117:14: unnecessary conversion - abs = int64(r.i) + offset - ^ - GOROOT/src/fmt/print.go:411:21: unnecessary conversion - p.fmt.integer(int64(v), 16, unsigned, udigits) - ^ - -Flags: - -Using the -v flag, unconvert will also print the source line and a -caret to indicate the unnecessary conversion's position therein. - -Using the -apply flag, unconvert will rewrite the Go source files -without the unnecessary type conversions. - -Using the -all flag, unconvert will analyze the Go packages under all -possible GOOS/GOARCH combinations, and only identify conversions that -are unnecessary in all cases. - -E.g., syscall.Timespec's Sec and Nsec fields are int64 under -linux/amd64 but int32 under linux/386. An int64(ts.Sec) conversion -that appears in a linux/amd64-only file will be identified as -unnecessary, but it will be preserved if it occurs in a file that's -compiled for both linux/amd64 and linux/386. diff --git a/vendor/github.com/golangci/unconvert/README.md b/vendor/github.com/golangci/unconvert/README.md new file mode 100644 index 0000000000..e9230c2183 --- /dev/null +++ b/vendor/github.com/golangci/unconvert/README.md @@ -0,0 +1,6 @@ +Fork of [unconvert](https://github.com/mdempsky/unconvert) to be usable as a library. + +The specific elements are inside the file `golangci.go`. + +The only modification of the file `unconvert.go` is the remove of the global variables for the flags. +The tests will never work because of that, then the CI is disabled. diff --git a/vendor/github.com/golangci/unconvert/golangci.go b/vendor/github.com/golangci/unconvert/golangci.go new file mode 100644 index 0000000000..306c44e5ec --- /dev/null +++ b/vendor/github.com/golangci/unconvert/golangci.go @@ -0,0 +1,78 @@ +package unconvert + +import ( + "go/ast" + "go/token" + "strings" + "sync" + + "golang.org/x/tools/go/analysis" +) + +// Transformed version of the original unconvert flags section. +// The section has been removed inside `unconvert.go` +var ( + flagAll = pointer(false) + flagApply = pointer(false) + flagCPUProfile = pointer("") + flagSafe = pointer(false) + flagV = pointer(false) + flagTests = pointer(true) + flagFastMath = pointer(false) + flagTags = pointer("") + flagConfigs = pointer("") +) + +func pointer[T string | int | int32 | int64 | bool](v T) *T { return &v } + +func Run(pass *analysis.Pass, fastMath, safe bool) []token.Position { + type res struct { + file string + edits editSet + } + + flagFastMath = pointer(fastMath) + flagSafe = pointer(safe) + + ch := make(chan res) + var wg sync.WaitGroup + for _, file := range pass.Files { + file := file + + tokenFile := pass.Fset.File(file.Package) + filename := tokenFile.Position(file.Package).Filename + + // Hack to recognize _cgo_gotypes.go. + if strings.HasSuffix(filename, "-d") || strings.HasSuffix(filename, "/_cgo_gotypes.go") { + continue + } + + wg.Add(1) + go func() { + defer wg.Done() + + v := visitor{info: pass.TypesInfo, file: tokenFile, edits: make(editSet)} + ast.Walk(&v, file) + + ch <- res{filename, v.edits} + }() + } + go func() { + wg.Wait() + close(ch) + }() + + m := make(fileToEditSet) + for r := range ch { + m[r.file] = r.edits + } + + var positions []token.Position + for _, edit := range m { + for position, _ := range edit { + positions = append(positions, position) + } + } + + return positions +} diff --git a/vendor/github.com/golangci/unconvert/unconvert.go b/vendor/github.com/golangci/unconvert/unconvert.go index 38737d39f7..222aeadf88 100644 --- a/vendor/github.com/golangci/unconvert/unconvert.go +++ b/vendor/github.com/golangci/unconvert/unconvert.go @@ -2,15 +2,15 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Unconvert removes redundant type conversions from Go packages. +// Package unconvert Unconvert removes redundant type conversions from Go packages. package unconvert import ( "bytes" + "encoding/json" "flag" "fmt" "go/ast" - "go/build" "go/format" "go/parser" "go/token" @@ -18,15 +18,16 @@ import ( "io/ioutil" "log" "os" + "os/exec" "reflect" "runtime/pprof" "sort" + "strings" "sync" "unicode" - "github.com/kisielk/gotool" "golang.org/x/text/width" - "golang.org/x/tools/go/loader" + "golang.org/x/tools/go/packages" ) // Unnecessary conversions are identified by the position @@ -34,6 +35,31 @@ import ( type editSet map[token.Position]struct{} +func (e editSet) add(pos token.Position) { + pos.Offset = 0 + e[pos] = struct{}{} +} + +func (e editSet) has(pos token.Position) bool { + pos.Offset = 0 + _, ok := e[pos] + return ok +} + +func (e editSet) remove(pos token.Position) { + pos.Offset = 0 + delete(e, pos) +} + +// intersect removes positions from e that are not present in x. +func (e editSet) intersect(x editSet) { + for pos := range e { + if _, ok := x[pos]; !ok { + delete(e, pos) + } + } +} + type fileToEditSet map[string]editSet func apply(file string, edits editSet) { @@ -97,11 +123,11 @@ func (e *editor) rewrite(f *ast.Expr) { } pos := e.file.Position(call.Lparen) - if _, ok := e.edits[pos]; !ok { + if !e.edits.has(pos) { return } *f = call.Args[0] - delete(e.edits, pos) + e.edits.remove(pos) } var ( @@ -161,21 +187,12 @@ func rub(buf []byte) []byte { return res.Bytes() } -var ( - flagAll = flag.Bool("unconvert.all", false, "type check all GOOS and GOARCH combinations") - flagApply = flag.Bool("unconvert.apply", false, "apply edits to source files") - flagCPUProfile = flag.String("unconvert.cpuprofile", "", "write CPU profile to file") - // TODO(mdempsky): Better description and maybe flag name. - flagSafe = flag.Bool("unconvert.safe", false, "be more conservative (experimental)") - flagV = flag.Bool("unconvert.v", false, "verbose output") -) - func usage() { fmt.Fprintf(os.Stderr, "usage: unconvert [flags] [package ...]\n") flag.PrintDefaults() } -func nomain() { +func main() { flag.Usage = usage flag.Parse() @@ -188,18 +205,29 @@ func nomain() { defer pprof.StopCPUProfile() } - importPaths := gotool.ImportPaths(flag.Args()) - if len(importPaths) == 0 { - return - } + patterns := flag.Args() // 0 or more import path patterns. + + var configs [][]string + if *flagConfigs != "" { + if os.Getenv("UNCONVERT_CONFIGS_EXPERIMENT") != "1" { + fmt.Println("WARNING: -configs is experimental and subject to change without notice.") + fmt.Println("Please comment at https://github.com/mdempsky/unconvert/issues/26") + fmt.Println("if you'd like to rely on this interface.") + fmt.Println("(Set UNCONVERT_CONFIGS_EXPERIMENT=1 to silence this warning.)") + fmt.Println() + } - var m fileToEditSet - if *flagAll { - m = mergeEdits(importPaths) + if err := json.Unmarshal([]byte(*flagConfigs), &configs); err != nil { + log.Fatal(err) + } + } else if *flagAll { + configs = allConfigs() } else { - m = computeEdits(importPaths, build.Default.GOOS, build.Default.GOARCH, build.Default.CgoEnabled) + configs = [][]string{nil} } + m := mergeEdits(patterns, configs) + if *flagApply { var wg sync.WaitGroup for f, e := range m { @@ -226,69 +254,36 @@ func nomain() { } } -func Run(prog *loader.Program) []token.Position { - m := computeEditsFromProg(prog) - var conversions []token.Position - for _, positions := range m { - for pos := range positions { - conversions = append(conversions, pos) - } +func allConfigs() [][]string { + out, err := exec.Command("go", "tool", "dist", "list", "-json").Output() + if err != nil { + log.Fatal(err) + } + + var platforms []struct { + GOOS, GOARCH string + } + err = json.Unmarshal(out, &platforms) + if err != nil { + log.Fatal(err) } - return conversions -} -var plats = [...]struct { - goos, goarch string -}{ - // TODO(mdempsky): buildall.bash also builds linux-386-387 and linux-arm-arm5. - {"android", "386"}, - {"android", "amd64"}, - {"android", "arm"}, - {"android", "arm64"}, - {"darwin", "386"}, - {"darwin", "amd64"}, - {"darwin", "arm"}, - {"darwin", "arm64"}, - {"dragonfly", "amd64"}, - {"freebsd", "386"}, - {"freebsd", "amd64"}, - {"freebsd", "arm"}, - {"linux", "386"}, - {"linux", "amd64"}, - {"linux", "arm"}, - {"linux", "arm64"}, - {"linux", "mips64"}, - {"linux", "mips64le"}, - {"linux", "ppc64"}, - {"linux", "ppc64le"}, - {"linux", "s390x"}, - {"nacl", "386"}, - {"nacl", "amd64p32"}, - {"nacl", "arm"}, - {"netbsd", "386"}, - {"netbsd", "amd64"}, - {"netbsd", "arm"}, - {"openbsd", "386"}, - {"openbsd", "amd64"}, - {"openbsd", "arm"}, - {"plan9", "386"}, - {"plan9", "amd64"}, - {"plan9", "arm"}, - {"solaris", "amd64"}, - {"windows", "386"}, - {"windows", "amd64"}, + var res [][]string + for _, platform := range platforms { + res = append(res, []string{ + "GOOS=" + platform.GOOS, + "GOARCH=" + platform.GOARCH, + }) + } + return res } -func mergeEdits(importPaths []string) fileToEditSet { +func mergeEdits(patterns []string, configs [][]string) fileToEditSet { m := make(fileToEditSet) - for _, plat := range plats { - for f, e := range computeEdits(importPaths, plat.goos, plat.goarch, false) { + for _, config := range configs { + for f, e := range computeEdits(patterns, config) { if e0, ok := m[f]; ok { - for k := range e0 { - if _, ok := e[k]; !ok { - delete(e0, k) - } - } + e0.intersect(e) } else { m[f] = e } @@ -297,48 +292,48 @@ func mergeEdits(importPaths []string) fileToEditSet { return m } -type noImporter struct{} - -func (noImporter) Import(path string) (*types.Package, error) { - panic("golang.org/x/tools/go/loader said this wouldn't be called") -} - -func computeEdits(importPaths []string, os, arch string, cgoEnabled bool) fileToEditSet { - ctxt := build.Default - ctxt.GOOS = os - ctxt.GOARCH = arch - ctxt.CgoEnabled = cgoEnabled - - var conf loader.Config - conf.Build = &ctxt - conf.TypeChecker.Importer = noImporter{} - for _, importPath := range importPaths { - conf.Import(importPath) +func computeEdits(patterns []string, config []string) fileToEditSet { + // TODO(mdempsky): Move into config? + var buildFlags []string + if *flagTags != "" { + buildFlags = []string{"-tags", *flagTags} } - prog, err := conf.Load() + + pkgs, err := packages.Load(&packages.Config{ + Mode: packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo, + Env: append(os.Environ(), config...), + BuildFlags: buildFlags, + Tests: *flagTests, + }, patterns...) if err != nil { log.Fatal(err) } + packages.PrintErrors(pkgs) - return computeEditsFromProg(prog) -} - -func computeEditsFromProg(prog *loader.Program) fileToEditSet { type res struct { file string edits editSet } + ch := make(chan res) var wg sync.WaitGroup - for _, pkg := range prog.InitialPackages() { - for _, file := range pkg.Files { + for _, pkg := range pkgs { + for _, file := range pkg.Syntax { pkg, file := pkg, file + tokenFile := pkg.Fset.File(file.Package) + filename := tokenFile.Position(file.Package).Filename + + // Hack to recognize _cgo_gotypes.go. + if strings.HasSuffix(filename, "-d") || strings.HasSuffix(filename, "/_cgo_gotypes.go") { + continue + } + wg.Add(1) go func() { defer wg.Done() - v := visitor{pkg: pkg, file: prog.Fset.File(file.Package), edits: make(editSet)} + v := visitor{info: pkg.TypesInfo, file: tokenFile, edits: make(editSet)} ast.Walk(&v, file) - ch <- res{v.file.Name(), v.edits} + ch <- res{filename, v.edits} }() } } @@ -360,7 +355,7 @@ type step struct { } type visitor struct { - pkg *loader.PackageInfo + info *types.Info file *token.File edits editSet path []step @@ -390,7 +385,7 @@ func (v *visitor) unconvert(call *ast.CallExpr) { if len(call.Args) != 1 || call.Ellipsis != token.NoPos { return } - ft, ok := v.pkg.Types[call.Fun] + ft, ok := v.info.Types[call.Fun] if !ok { fmt.Println("Missing type for function") return @@ -399,7 +394,7 @@ func (v *visitor) unconvert(call *ast.CallExpr) { // Function call; not a conversion. return } - at, ok := v.pkg.Types[call.Args[0]] + at, ok := v.info.Types[call.Args[0]] if !ok { fmt.Println("Missing type for argument") return @@ -408,7 +403,13 @@ func (v *visitor) unconvert(call *ast.CallExpr) { // A real conversion. return } - if isUntypedValue(call.Args[0], &v.pkg.Info) { + if !*flagFastMath && isFloatingPoint(ft.Type) { + // As of Go 1.9, explicit floating-point type + // conversions are always significant because they + // force rounding and prevent operation fusing. + return + } + if isUntypedValue(call.Args[0], v.info) { // Workaround golang.org/issue/13061. return } @@ -417,31 +418,15 @@ func (v *visitor) unconvert(call *ast.CallExpr) { fmt.Println("Skipped a possible type conversion because of -safe at", v.file.Position(call.Pos())) return } - if v.isCgoCheckPointerContext() { - // cmd/cgo generates explicit type conversions that - // are often redundant when introducing - // _cgoCheckPointer calls (issue #16). Users can't do - // anything about these, so skip over them. - return - } - v.edits[v.file.Position(call.Lparen)] = struct{}{} + v.edits.add(v.file.Position(call.Lparen)) } -func (v *visitor) isCgoCheckPointerContext() bool { - ctxt := &v.path[len(v.path)-2] - if ctxt.i != 1 { - return false - } - call, ok := ctxt.n.(*ast.CallExpr) - if !ok { - return false - } - ident, ok := call.Fun.(*ast.Ident) - if !ok { - return false - } - return ident.Name == "_cgoCheckPointer" +// isFloatingPointer reports whether t's underlying type is a floating +// point type. +func isFloatingPoint(t types.Type) bool { + ut, ok := t.Underlying().(*types.Basic) + return ok && ut.Info()&(types.IsFloat|types.IsComplex) != 0 } // isSafeContext reports whether the current context requires @@ -463,7 +448,7 @@ func (v *visitor) isSafeContext(t types.Type) bool { } // We're a conversion in the pos'th element of n.Rhs. // Check that the corresponding element of n.Lhs is of type t. - lt, ok := v.pkg.Types[n.Lhs[pos]] + lt, ok := v.info.Types[n.Lhs[pos]] if !ok { fmt.Println("Missing type for LHS expression") return false @@ -485,7 +470,7 @@ func (v *visitor) isSafeContext(t types.Type) bool { } else { other = n.X } - ot, ok := v.pkg.Types[other] + ot, ok := v.info.Types[other] if !ok { fmt.Println("Missing type for other binop subexpr") return false @@ -497,7 +482,7 @@ func (v *visitor) isSafeContext(t types.Type) bool { // Type conversion in the function subexpr is okay. return true } - ft, ok := v.pkg.Types[n.Fun] + ft, ok := v.info.Types[n.Fun] if !ok { fmt.Println("Missing type for function expression") return false @@ -550,7 +535,7 @@ func (v *visitor) isSafeContext(t types.Type) bool { if typeExpr == nil { fmt.Println(ctxt) } - pt, ok := v.pkg.Types[typeExpr] + pt, ok := v.info.Types[typeExpr] if !ok { fmt.Println("Missing type for return parameter at", v.file.Position(n.Pos())) return false diff --git a/vendor/github.com/jjti/go-spancheck/README.md b/vendor/github.com/jjti/go-spancheck/README.md index 98aedec287..953489d7a3 100644 --- a/vendor/github.com/jjti/go-spancheck/README.md +++ b/vendor/github.com/jjti/go-spancheck/README.md @@ -10,13 +10,6 @@ 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 @@ -43,6 +36,44 @@ func _() error { ## Configuration +### golangci-lint + +Docs on configuring the linter are also available at [https://golangci-lint.run/usage/linters/#spancheck](https://golangci-lint.run/usage/linters/#spancheck): + +```yaml +linters: + enable: + - spancheck + +linters-settings: + spancheck: + # Checks to enable. + # Options include: + # - `end`: check that `span.End()` is called + # - `record-error`: check that `span.RecordError(err)` is called when an error is returned + # - `set-status`: check that `span.SetStatus(codes.Error, msg)` is called when an error is returned + # Default: ["end"] + checks: + - end + - record-error + - set-status + # A list of regexes for function signatures that silence `record-error` and `set-status` reports + # if found in the call path to a returned error. + # https://github.com/jjti/go-spancheck#ignore-check-signatures + # Default: [] + ignore-check-signatures: + - "telemetry.RecordError" +``` + +### CLI + +To install the linter as a CLI: + +```bash +go install github.com/jjti/go-spancheck/cmd/spancheck@latest +spancheck ./... +``` + Only the `span.End()` check is enabled by default. The others can be enabled with `-checks 'end,set-status,record-error'`. ```txt @@ -55,7 +86,7 @@ Flags: comma-separated list of regex for function signatures that disable checks on errors ``` -### Ignore check signatures +### Ignore Check Signatures The `span.SetStatus()` and `span.RecordError()` checks warn when there is: diff --git a/vendor/github.com/jjti/go-spancheck/spancheck.go b/vendor/github.com/jjti/go-spancheck/spancheck.go index ebfc1ac68c..6f069a0335 100644 --- a/vendor/github.com/jjti/go-spancheck/spancheck.go +++ b/vendor/github.com/jjti/go-spancheck/spancheck.go @@ -3,7 +3,6 @@ package spancheck import ( "go/ast" "go/types" - "log" "regexp" "golang.org/x/tools/go/analysis" @@ -170,7 +169,7 @@ func runFunc(pass *analysis.Pass, node ast.Node, config *Config) { 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 { + if ret := getMissingSpanCalls(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()) } @@ -178,7 +177,7 @@ func runFunc(pass *analysis.Pass, node ast.Node, config *Config) { 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 { + if ret := getMissingSpanCalls(pass, g, sv, "SetStatus", getErrorReturn, 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()) } @@ -186,7 +185,7 @@ func runFunc(pass *analysis.Pass, node ast.Node, config *Config) { 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 { + if ret := getMissingSpanCalls(pass, g, sv, "RecordError", getErrorReturn, 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()) } @@ -236,9 +235,9 @@ func getID(node ast.Node) *ast.Ident { return nil } -// missingSpanCalls finds a path through the CFG, from stmt (which defines +// getMissingSpanCalls 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( +func getMissingSpanCalls( pass *analysis.Pass, g *cfg.CFG, sv spanVar, @@ -246,66 +245,12 @@ func missingSpanCalls( 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) + res = usesCall(pass, b.Nodes, sv, selName, ignoreCheckSig, 0) memo[b] = res } return res @@ -325,12 +270,9 @@ 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) { + if usesCall(pass, rest, sv, selName, ignoreCheckSig, 0) { return nil } @@ -356,12 +298,12 @@ outer: } // Found path to return statement? - if ret := returnsErr(pass, b.Return()); ret != nil { + if ret := getErrorReturn(pass, b.Return()); ret != nil { return ret // found } // Recur - if ret := returnsErr(pass, search(b.Succs)); ret != nil { + if ret := getErrorReturn(pass, search(b.Succs)); ret != nil { return ret } } @@ -371,7 +313,75 @@ outer: return search(defBlock.Succs) } -func returnsErr(pass *analysis.Pass, ret *ast.ReturnStmt) *ast.ReturnStmt { +// usesCall reports whether stmts contain a use of the selName call on variable v. +func usesCall(pass *analysis.Pass, stmts []ast.Node, sv spanVar, selName string, ignoreCheckSig *regexp.Regexp, depth int) bool { + if depth > 1 { // for perf reasons, do not dive too deep thru func literals, just one level deep check. + return false + } + + 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 { + cfgs := pass.ResultOf[ctrlflow.Analyzer].(*ctrlflow.CFGs) + g := cfgs.FuncLit(n) + if g != nil && len(g.Blocks) > 0 { + return usesCall(pass, g.Blocks[0].Nodes, sv, selName, ignoreCheckSig, depth+1) + } + + return false + } + 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: + if len(stack) > 0 { + stack = stack[:len(stack)-1] // pop + return true + } + return false + } + 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 +} + +func getErrorReturn(pass *analysis.Pass, ret *ast.ReturnStmt) *ast.ReturnStmt { if ret == nil { return nil } diff --git a/vendor/github.com/karamaru-alpha/copyloopvar/.gitignore b/vendor/github.com/karamaru-alpha/copyloopvar/.gitignore new file mode 100644 index 0000000000..816abbd923 --- /dev/null +++ b/vendor/github.com/karamaru-alpha/copyloopvar/.gitignore @@ -0,0 +1,2 @@ +.idea/ +copyloopvar diff --git a/vendor/github.com/sivchari/nosnakecase/LICENSE b/vendor/github.com/karamaru-alpha/copyloopvar/LICENSE similarity index 96% rename from vendor/github.com/sivchari/nosnakecase/LICENSE rename to vendor/github.com/karamaru-alpha/copyloopvar/LICENSE index fb41276774..e2567fd0c5 100644 --- a/vendor/github.com/sivchari/nosnakecase/LICENSE +++ b/vendor/github.com/karamaru-alpha/copyloopvar/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 sivchari +Copyright (c) 2024 Ryosei Karaki Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/github.com/karamaru-alpha/copyloopvar/README.md b/vendor/github.com/karamaru-alpha/copyloopvar/README.md new file mode 100644 index 0000000000..d31d1abd97 --- /dev/null +++ b/vendor/github.com/karamaru-alpha/copyloopvar/README.md @@ -0,0 +1,27 @@ +# copyloopvar + +copyloopvar is a linter detects places where loop variables are copied. + +cf. [Fixing For Loops in Go 1.22](https://go.dev/blog/loopvar-preview) + +## Example + +```go +for i, v := range []int{1, 2, 3} { + i := i // The copy of the 'for' variable "i" can be deleted (Go 1.22+) + v := v // The copy of the 'for' variable "v" can be deleted (Go 1.22+) + _, _ = i, v +} + +for i := 1; i <= 3; i++ { + i := i // The copy of the 'for' variable "i" can be deleted (Go 1.22+) + _ = i +} +``` + +## Install + +```bash +go install github.com/karamaru-alpha/copyloopvar/cmd/copyloopvar@latest +go vet -vettool=`which copyloopvar` ./... +``` diff --git a/vendor/github.com/karamaru-alpha/copyloopvar/copyloopvar.go b/vendor/github.com/karamaru-alpha/copyloopvar/copyloopvar.go new file mode 100644 index 0000000000..dfb0e7d673 --- /dev/null +++ b/vendor/github.com/karamaru-alpha/copyloopvar/copyloopvar.go @@ -0,0 +1,135 @@ +package copyloopvar + +import ( + "fmt" + "go/ast" + "go/token" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" +) + +var ignoreAlias bool + +func NewAnalyzer() *analysis.Analyzer { + analyzer := &analysis.Analyzer{ + Name: "copyloopvar", + Doc: "copyloopvar is a linter detects places where loop variables are copied", + Run: run, + Requires: []*analysis.Analyzer{ + inspect.Analyzer, + }, + } + analyzer.Flags.BoolVar(&ignoreAlias, "ignore-alias", false, "ignore aliasing of loop variables") + return analyzer +} + +func run(pass *analysis.Pass) (any, error) { + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + + nodeFilter := []ast.Node{ + (*ast.RangeStmt)(nil), + (*ast.ForStmt)(nil), + } + + inspect.Preorder(nodeFilter, func(n ast.Node) { + switch node := n.(type) { + case *ast.RangeStmt: + checkRangeStmt(pass, node) + case *ast.ForStmt: + checkForStmt(pass, node) + } + }) + + return nil, nil +} + +func checkRangeStmt(pass *analysis.Pass, rangeStmt *ast.RangeStmt) { + key, ok := rangeStmt.Key.(*ast.Ident) + if !ok { + return + } + var value *ast.Ident + if rangeStmt.Value != nil { + value = rangeStmt.Value.(*ast.Ident) + } + for _, stmt := range rangeStmt.Body.List { + assignStmt, ok := stmt.(*ast.AssignStmt) + if !ok { + continue + } + if assignStmt.Tok != token.DEFINE { + continue + } + for i, rh := range assignStmt.Rhs { + right, ok := rh.(*ast.Ident) + if !ok { + continue + } + if right.Name != key.Name && (value != nil && right.Name != value.Name) { + continue + } + if ignoreAlias { + left, ok := assignStmt.Lhs[i].(*ast.Ident) + if !ok { + continue + } + if left.Name != right.Name { + continue + } + } + pass.Report(analysis.Diagnostic{ + Pos: assignStmt.Pos(), + Message: fmt.Sprintf(`The copy of the 'for' variable "%s" can be deleted (Go 1.22+)`, right.Name), + }) + } + } +} + +func checkForStmt(pass *analysis.Pass, forStmt *ast.ForStmt) { + if forStmt.Init == nil { + return + } + initAssignStmt, ok := forStmt.Init.(*ast.AssignStmt) + if !ok { + return + } + initVarNameMap := make(map[string]interface{}, len(initAssignStmt.Lhs)) + for _, lh := range initAssignStmt.Lhs { + if initVar, ok := lh.(*ast.Ident); ok { + initVarNameMap[initVar.Name] = struct{}{} + } + } + for _, stmt := range forStmt.Body.List { + assignStmt, ok := stmt.(*ast.AssignStmt) + if !ok { + continue + } + if assignStmt.Tok != token.DEFINE { + continue + } + for i, rh := range assignStmt.Rhs { + right, ok := rh.(*ast.Ident) + if !ok { + continue + } + if _, ok := initVarNameMap[right.Name]; !ok { + continue + } + if ignoreAlias { + left, ok := assignStmt.Lhs[i].(*ast.Ident) + if !ok { + continue + } + if left.Name != right.Name { + continue + } + } + pass.Report(analysis.Diagnostic{ + Pos: assignStmt.Pos(), + Message: fmt.Sprintf(`The copy of the 'for' variable "%s" can be deleted (Go 1.22+)`, right.Name), + }) + } + } +} diff --git a/vendor/github.com/kisielk/gotool/.travis.yml b/vendor/github.com/kisielk/gotool/.travis.yml deleted file mode 100644 index d1784e1e23..0000000000 --- a/vendor/github.com/kisielk/gotool/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -sudo: false -language: go -go: - - 1.2 - - 1.3 - - 1.4 - - 1.5 - - 1.6 - - 1.7 - - 1.8 - - 1.9 - - master -matrix: - allow_failures: - - go: master - fast_finish: true -install: - - # Skip. -script: - - go get -t -v ./... - - diff -u <(echo -n) <(gofmt -d .) - - go tool vet . - - go test -v -race ./... diff --git a/vendor/github.com/kisielk/gotool/LEGAL b/vendor/github.com/kisielk/gotool/LEGAL deleted file mode 100644 index 72b859cd62..0000000000 --- a/vendor/github.com/kisielk/gotool/LEGAL +++ /dev/null @@ -1,32 +0,0 @@ -All the files in this distribution are covered under either the MIT -license (see the file LICENSE) except some files mentioned below. - -match.go, match_test.go: - - Copyright (c) 2009 The Go Authors. All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer - in the documentation and/or other materials provided with the - distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/kisielk/gotool/LICENSE b/vendor/github.com/kisielk/gotool/LICENSE deleted file mode 100644 index 1cbf651e2f..0000000000 --- a/vendor/github.com/kisielk/gotool/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2013 Kamil Kisiel - -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/kisielk/gotool/README.md b/vendor/github.com/kisielk/gotool/README.md deleted file mode 100644 index 6e4e92b2f6..0000000000 --- a/vendor/github.com/kisielk/gotool/README.md +++ /dev/null @@ -1,6 +0,0 @@ -gotool -====== -[![GoDoc](https://godoc.org/github.com/kisielk/gotool?status.svg)](https://godoc.org/github.com/kisielk/gotool) -[![Build Status](https://travis-ci.org/kisielk/gotool.svg?branch=master)](https://travis-ci.org/kisielk/gotool) - -Package gotool contains utility functions used to implement the standard "cmd/go" tool, provided as a convenience to developers who want to write tools with similar semantics. diff --git a/vendor/github.com/kisielk/gotool/go13.go b/vendor/github.com/kisielk/gotool/go13.go deleted file mode 100644 index 2dd9b3fdf0..0000000000 --- a/vendor/github.com/kisielk/gotool/go13.go +++ /dev/null @@ -1,15 +0,0 @@ -// +build !go1.4 - -package gotool - -import ( - "go/build" - "path/filepath" - "runtime" -) - -var gorootSrc = filepath.Join(runtime.GOROOT(), "src", "pkg") - -func shouldIgnoreImport(p *build.Package) bool { - return true -} diff --git a/vendor/github.com/kisielk/gotool/go14-15.go b/vendor/github.com/kisielk/gotool/go14-15.go deleted file mode 100644 index aa99a32270..0000000000 --- a/vendor/github.com/kisielk/gotool/go14-15.go +++ /dev/null @@ -1,15 +0,0 @@ -// +build go1.4,!go1.6 - -package gotool - -import ( - "go/build" - "path/filepath" - "runtime" -) - -var gorootSrc = filepath.Join(runtime.GOROOT(), "src") - -func shouldIgnoreImport(p *build.Package) bool { - return true -} diff --git a/vendor/github.com/kisielk/gotool/go16-18.go b/vendor/github.com/kisielk/gotool/go16-18.go deleted file mode 100644 index f25cec14a8..0000000000 --- a/vendor/github.com/kisielk/gotool/go16-18.go +++ /dev/null @@ -1,15 +0,0 @@ -// +build go1.6,!go1.9 - -package gotool - -import ( - "go/build" - "path/filepath" - "runtime" -) - -var gorootSrc = filepath.Join(runtime.GOROOT(), "src") - -func shouldIgnoreImport(p *build.Package) bool { - return p == nil || len(p.InvalidGoFiles) == 0 -} diff --git a/vendor/github.com/kisielk/gotool/internal/load/path.go b/vendor/github.com/kisielk/gotool/internal/load/path.go deleted file mode 100644 index 74e15b9d32..0000000000 --- a/vendor/github.com/kisielk/gotool/internal/load/path.go +++ /dev/null @@ -1,27 +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. - -// +build go1.9 - -package load - -import ( - "strings" -) - -// hasPathPrefix reports whether the path s begins with the -// elements in prefix. -func hasPathPrefix(s, prefix string) bool { - switch { - default: - return false - case len(s) == len(prefix): - return s == prefix - case len(s) > len(prefix): - if prefix != "" && prefix[len(prefix)-1] == '/' { - return strings.HasPrefix(s, prefix) - } - return s[len(prefix)] == '/' && s[:len(prefix)] == prefix - } -} diff --git a/vendor/github.com/kisielk/gotool/internal/load/pkg.go b/vendor/github.com/kisielk/gotool/internal/load/pkg.go deleted file mode 100644 index b937ede759..0000000000 --- a/vendor/github.com/kisielk/gotool/internal/load/pkg.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2011 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. - -// +build go1.9 - -// Package load loads packages. -package load - -import ( - "strings" -) - -// isStandardImportPath reports whether $GOROOT/src/path should be considered -// part of the standard distribution. For historical reasons we allow people to add -// their own code to $GOROOT instead of using $GOPATH, but we assume that -// code will start with a domain name (dot in the first element). -func isStandardImportPath(path string) bool { - i := strings.Index(path, "/") - if i < 0 { - i = len(path) - } - elem := path[:i] - return !strings.Contains(elem, ".") -} diff --git a/vendor/github.com/kisielk/gotool/internal/load/search.go b/vendor/github.com/kisielk/gotool/internal/load/search.go deleted file mode 100644 index 17ed62ddae..0000000000 --- a/vendor/github.com/kisielk/gotool/internal/load/search.go +++ /dev/null @@ -1,354 +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. - -// +build go1.9 - -package load - -import ( - "fmt" - "go/build" - "log" - "os" - "path" - "path/filepath" - "regexp" - "strings" -) - -// Context specifies values for operation of ImportPaths that would -// otherwise come from cmd/go/internal/cfg package. -// -// This is a construct added for gotool purposes and doesn't have -// an equivalent upstream in cmd/go. -type Context struct { - // BuildContext is the build context to use. - BuildContext build.Context - - // GOROOTsrc is the location of the src directory in GOROOT. - // At this time, it's used only in MatchPackages to skip - // GOOROOT/src entry from BuildContext.SrcDirs output. - GOROOTsrc string -} - -// allPackages returns all the packages that can be found -// under the $GOPATH directories and $GOROOT matching pattern. -// The pattern is either "all" (all packages), "std" (standard packages), -// "cmd" (standard commands), or a path including "...". -func (c *Context) allPackages(pattern string) []string { - pkgs := c.MatchPackages(pattern) - if len(pkgs) == 0 { - fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) - } - return pkgs -} - -// allPackagesInFS is like allPackages but is passed a pattern -// beginning ./ or ../, meaning it should scan the tree rooted -// at the given directory. There are ... in the pattern too. -func (c *Context) allPackagesInFS(pattern string) []string { - pkgs := c.MatchPackagesInFS(pattern) - if len(pkgs) == 0 { - fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) - } - return pkgs -} - -// MatchPackages returns a list of package paths matching pattern -// (see go help packages for pattern syntax). -func (c *Context) MatchPackages(pattern string) []string { - match := func(string) bool { return true } - treeCanMatch := func(string) bool { return true } - if !IsMetaPackage(pattern) { - match = matchPattern(pattern) - treeCanMatch = treeCanMatchPattern(pattern) - } - - have := map[string]bool{ - "builtin": true, // ignore pseudo-package that exists only for documentation - } - if !c.BuildContext.CgoEnabled { - have["runtime/cgo"] = true // ignore during walk - } - var pkgs []string - - for _, src := range c.BuildContext.SrcDirs() { - if (pattern == "std" || pattern == "cmd") && src != c.GOROOTsrc { - continue - } - src = filepath.Clean(src) + string(filepath.Separator) - root := src - if pattern == "cmd" { - root += "cmd" + string(filepath.Separator) - } - filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { - if err != nil || path == src { - return nil - } - - want := true - // Avoid .foo, _foo, and testdata directory trees. - _, elem := filepath.Split(path) - if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { - want = false - } - - name := filepath.ToSlash(path[len(src):]) - if pattern == "std" && (!isStandardImportPath(name) || name == "cmd") { - // The name "std" is only the standard library. - // If the name is cmd, it's the root of the command tree. - want = false - } - if !treeCanMatch(name) { - want = false - } - - if !fi.IsDir() { - if fi.Mode()&os.ModeSymlink != 0 && want { - if target, err := os.Stat(path); err == nil && target.IsDir() { - fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path) - } - } - return nil - } - if !want { - return filepath.SkipDir - } - - if have[name] { - return nil - } - have[name] = true - if !match(name) { - return nil - } - pkg, err := c.BuildContext.ImportDir(path, 0) - if err != nil { - if _, noGo := err.(*build.NoGoError); noGo { - return nil - } - } - - // If we are expanding "cmd", skip main - // packages under cmd/vendor. At least as of - // March, 2017, there is one there for the - // vendored pprof tool. - if pattern == "cmd" && strings.HasPrefix(pkg.ImportPath, "cmd/vendor") && pkg.Name == "main" { - return nil - } - - pkgs = append(pkgs, name) - return nil - }) - } - return pkgs -} - -// MatchPackagesInFS returns a list of package paths matching pattern, -// which must begin with ./ or ../ -// (see go help packages for pattern syntax). -func (c *Context) MatchPackagesInFS(pattern string) []string { - // Find directory to begin the scan. - // Could be smarter but this one optimization - // is enough for now, since ... is usually at the - // end of a path. - i := strings.Index(pattern, "...") - dir, _ := path.Split(pattern[:i]) - - // pattern begins with ./ or ../. - // path.Clean will discard the ./ but not the ../. - // We need to preserve the ./ for pattern matching - // and in the returned import paths. - prefix := "" - if strings.HasPrefix(pattern, "./") { - prefix = "./" - } - match := matchPattern(pattern) - - var pkgs []string - filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { - if err != nil || !fi.IsDir() { - return nil - } - if path == dir { - // filepath.Walk starts at dir and recurses. For the recursive case, - // the path is the result of filepath.Join, which calls filepath.Clean. - // The initial case is not Cleaned, though, so we do this explicitly. - // - // This converts a path like "./io/" to "io". Without this step, running - // "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io - // package, because prepending the prefix "./" to the unclean path would - // result in "././io", and match("././io") returns false. - path = filepath.Clean(path) - } - - // Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..". - _, elem := filepath.Split(path) - dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".." - if dot || strings.HasPrefix(elem, "_") || elem == "testdata" { - return filepath.SkipDir - } - - name := prefix + filepath.ToSlash(path) - if !match(name) { - return nil - } - - // We keep the directory if we can import it, or if we can't import it - // due to invalid Go source files. This means that directories containing - // parse errors will be built (and fail) instead of being silently skipped - // as not matching the pattern. Go 1.5 and earlier skipped, but that - // behavior means people miss serious mistakes. - // See golang.org/issue/11407. - if p, err := c.BuildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) { - if _, noGo := err.(*build.NoGoError); !noGo { - log.Print(err) - } - return nil - } - pkgs = append(pkgs, name) - return nil - }) - return pkgs -} - -// treeCanMatchPattern(pattern)(name) reports whether -// name or children of name can possibly match pattern. -// Pattern is the same limited glob accepted by matchPattern. -func treeCanMatchPattern(pattern string) func(name string) bool { - wildCard := false - if i := strings.Index(pattern, "..."); i >= 0 { - wildCard = true - pattern = pattern[:i] - } - return func(name string) bool { - return len(name) <= len(pattern) && hasPathPrefix(pattern, name) || - wildCard && strings.HasPrefix(name, pattern) - } -} - -// 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-separted 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.Replace(re, `\.\.\.`, `[^`+vendorChar+`]*`, -1) - - 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, "/") -} - -// ImportPaths returns the import paths to use for the given command line. -func (c *Context) ImportPaths(args []string) []string { - args = c.ImportPathsNoDotExpansion(args) - var out []string - for _, a := range args { - if strings.Contains(a, "...") { - if build.IsLocalImport(a) { - out = append(out, c.allPackagesInFS(a)...) - } else { - out = append(out, c.allPackages(a)...) - } - continue - } - out = append(out, a) - } - return out -} - -// ImportPathsNoDotExpansion returns the import paths to use for the given -// command line, but it does no ... expansion. -func (c *Context) ImportPathsNoDotExpansion(args []string) []string { - if len(args) == 0 { - return []string{"."} - } - var out []string - for _, a := range args { - // Arguments are supposed to be import paths, but - // as a courtesy to Windows developers, rewrite \ to / - // in command-line arguments. Handles .\... and so on. - if filepath.Separator == '\\' { - a = strings.Replace(a, `\`, `/`, -1) - } - - // Put argument in canonical form, but preserve leading ./. - if strings.HasPrefix(a, "./") { - a = "./" + path.Clean(a) - if a == "./." { - a = "." - } - } else { - a = path.Clean(a) - } - if IsMetaPackage(a) { - out = append(out, c.allPackages(a)...) - continue - } - out = append(out, a) - } - return out -} - -// IsMetaPackage checks if name is a reserved package name that expands to multiple packages. -func IsMetaPackage(name string) bool { - return name == "std" || name == "cmd" || name == "all" -} diff --git a/vendor/github.com/kisielk/gotool/match.go b/vendor/github.com/kisielk/gotool/match.go deleted file mode 100644 index 4dbdbff47f..0000000000 --- a/vendor/github.com/kisielk/gotool/match.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2009 The Go Authors. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// +build go1.9 - -package gotool - -import ( - "path/filepath" - - "github.com/kisielk/gotool/internal/load" -) - -// importPaths returns the import paths to use for the given command line. -func (c *Context) importPaths(args []string) []string { - lctx := load.Context{ - BuildContext: c.BuildContext, - GOROOTsrc: c.joinPath(c.BuildContext.GOROOT, "src"), - } - return lctx.ImportPaths(args) -} - -// joinPath calls c.BuildContext.JoinPath (if not nil) or else filepath.Join. -// -// It's a copy of the unexported build.Context.joinPath helper. -func (c *Context) joinPath(elem ...string) string { - if f := c.BuildContext.JoinPath; f != nil { - return f(elem...) - } - return filepath.Join(elem...) -} diff --git a/vendor/github.com/kisielk/gotool/match18.go b/vendor/github.com/kisielk/gotool/match18.go deleted file mode 100644 index 6d6b1368c8..0000000000 --- a/vendor/github.com/kisielk/gotool/match18.go +++ /dev/null @@ -1,317 +0,0 @@ -// Copyright (c) 2009 The Go Authors. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// +build !go1.9 - -package gotool - -import ( - "fmt" - "go/build" - "log" - "os" - "path" - "path/filepath" - "regexp" - "strings" -) - -// This file contains code from the Go distribution. - -// 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. -func matchPattern(pattern string) func(name string) bool { - re := regexp.QuoteMeta(pattern) - re = strings.Replace(re, `\.\.\.`, `.*`, -1) - // Special case: foo/... matches foo too. - if strings.HasSuffix(re, `/.*`) { - re = re[:len(re)-len(`/.*`)] + `(/.*)?` - } - reg := regexp.MustCompile(`^` + re + `$`) - return reg.MatchString -} - -// matchPackages returns a list of package paths matching pattern -// (see go help packages for pattern syntax). -func (c *Context) matchPackages(pattern string) []string { - match := func(string) bool { return true } - treeCanMatch := func(string) bool { return true } - if !isMetaPackage(pattern) { - match = matchPattern(pattern) - treeCanMatch = treeCanMatchPattern(pattern) - } - - have := map[string]bool{ - "builtin": true, // ignore pseudo-package that exists only for documentation - } - if !c.BuildContext.CgoEnabled { - have["runtime/cgo"] = true // ignore during walk - } - var pkgs []string - - for _, src := range c.BuildContext.SrcDirs() { - if (pattern == "std" || pattern == "cmd") && src != gorootSrc { - continue - } - src = filepath.Clean(src) + string(filepath.Separator) - root := src - if pattern == "cmd" { - root += "cmd" + string(filepath.Separator) - } - filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { - if err != nil || !fi.IsDir() || path == src { - return nil - } - - // Avoid .foo, _foo, and testdata directory trees. - _, elem := filepath.Split(path) - if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { - return filepath.SkipDir - } - - name := filepath.ToSlash(path[len(src):]) - if pattern == "std" && (!isStandardImportPath(name) || name == "cmd") { - // The name "std" is only the standard library. - // If the name is cmd, it's the root of the command tree. - return filepath.SkipDir - } - if !treeCanMatch(name) { - return filepath.SkipDir - } - if have[name] { - return nil - } - have[name] = true - if !match(name) { - return nil - } - _, err = c.BuildContext.ImportDir(path, 0) - if err != nil { - if _, noGo := err.(*build.NoGoError); noGo { - return nil - } - } - pkgs = append(pkgs, name) - return nil - }) - } - return pkgs -} - -// importPathsNoDotExpansion returns the import paths to use for the given -// command line, but it does no ... expansion. -func (c *Context) importPathsNoDotExpansion(args []string) []string { - if len(args) == 0 { - return []string{"."} - } - var out []string - for _, a := range args { - // Arguments are supposed to be import paths, but - // as a courtesy to Windows developers, rewrite \ to / - // in command-line arguments. Handles .\... and so on. - if filepath.Separator == '\\' { - a = strings.Replace(a, `\`, `/`, -1) - } - - // Put argument in canonical form, but preserve leading ./. - if strings.HasPrefix(a, "./") { - a = "./" + path.Clean(a) - if a == "./." { - a = "." - } - } else { - a = path.Clean(a) - } - if isMetaPackage(a) { - out = append(out, c.allPackages(a)...) - continue - } - out = append(out, a) - } - return out -} - -// importPaths returns the import paths to use for the given command line. -func (c *Context) importPaths(args []string) []string { - args = c.importPathsNoDotExpansion(args) - var out []string - for _, a := range args { - if strings.Contains(a, "...") { - if build.IsLocalImport(a) { - out = append(out, c.allPackagesInFS(a)...) - } else { - out = append(out, c.allPackages(a)...) - } - continue - } - out = append(out, a) - } - return out -} - -// allPackages returns all the packages that can be found -// under the $GOPATH directories and $GOROOT matching pattern. -// The pattern is either "all" (all packages), "std" (standard packages), -// "cmd" (standard commands), or a path including "...". -func (c *Context) allPackages(pattern string) []string { - pkgs := c.matchPackages(pattern) - if len(pkgs) == 0 { - fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) - } - return pkgs -} - -// allPackagesInFS is like allPackages but is passed a pattern -// beginning ./ or ../, meaning it should scan the tree rooted -// at the given directory. There are ... in the pattern too. -func (c *Context) allPackagesInFS(pattern string) []string { - pkgs := c.matchPackagesInFS(pattern) - if len(pkgs) == 0 { - fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) - } - return pkgs -} - -// matchPackagesInFS returns a list of package paths matching pattern, -// which must begin with ./ or ../ -// (see go help packages for pattern syntax). -func (c *Context) matchPackagesInFS(pattern string) []string { - // Find directory to begin the scan. - // Could be smarter but this one optimization - // is enough for now, since ... is usually at the - // end of a path. - i := strings.Index(pattern, "...") - dir, _ := path.Split(pattern[:i]) - - // pattern begins with ./ or ../. - // path.Clean will discard the ./ but not the ../. - // We need to preserve the ./ for pattern matching - // and in the returned import paths. - prefix := "" - if strings.HasPrefix(pattern, "./") { - prefix = "./" - } - match := matchPattern(pattern) - - var pkgs []string - filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { - if err != nil || !fi.IsDir() { - return nil - } - if path == dir { - // filepath.Walk starts at dir and recurses. For the recursive case, - // the path is the result of filepath.Join, which calls filepath.Clean. - // The initial case is not Cleaned, though, so we do this explicitly. - // - // This converts a path like "./io/" to "io". Without this step, running - // "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io - // package, because prepending the prefix "./" to the unclean path would - // result in "././io", and match("././io") returns false. - path = filepath.Clean(path) - } - - // Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..". - _, elem := filepath.Split(path) - dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".." - if dot || strings.HasPrefix(elem, "_") || elem == "testdata" { - return filepath.SkipDir - } - - name := prefix + filepath.ToSlash(path) - if !match(name) { - return nil - } - - // We keep the directory if we can import it, or if we can't import it - // due to invalid Go source files. This means that directories containing - // parse errors will be built (and fail) instead of being silently skipped - // as not matching the pattern. Go 1.5 and earlier skipped, but that - // behavior means people miss serious mistakes. - // See golang.org/issue/11407. - if p, err := c.BuildContext.ImportDir(path, 0); err != nil && shouldIgnoreImport(p) { - if _, noGo := err.(*build.NoGoError); !noGo { - log.Print(err) - } - return nil - } - pkgs = append(pkgs, name) - return nil - }) - return pkgs -} - -// isMetaPackage checks if name is a reserved package name that expands to multiple packages. -func isMetaPackage(name string) bool { - return name == "std" || name == "cmd" || name == "all" -} - -// isStandardImportPath reports whether $GOROOT/src/path should be considered -// part of the standard distribution. For historical reasons we allow people to add -// their own code to $GOROOT instead of using $GOPATH, but we assume that -// code will start with a domain name (dot in the first element). -func isStandardImportPath(path string) bool { - i := strings.Index(path, "/") - if i < 0 { - i = len(path) - } - elem := path[:i] - return !strings.Contains(elem, ".") -} - -// hasPathPrefix reports whether the path s begins with the -// elements in prefix. -func hasPathPrefix(s, prefix string) bool { - switch { - default: - return false - case len(s) == len(prefix): - return s == prefix - case len(s) > len(prefix): - if prefix != "" && prefix[len(prefix)-1] == '/' { - return strings.HasPrefix(s, prefix) - } - return s[len(prefix)] == '/' && s[:len(prefix)] == prefix - } -} - -// treeCanMatchPattern(pattern)(name) reports whether -// name or children of name can possibly match pattern. -// Pattern is the same limited glob accepted by matchPattern. -func treeCanMatchPattern(pattern string) func(name string) bool { - wildCard := false - if i := strings.Index(pattern, "..."); i >= 0 { - wildCard = true - pattern = pattern[:i] - } - return func(name string) bool { - return len(name) <= len(pattern) && hasPathPrefix(pattern, name) || - wildCard && strings.HasPrefix(name, pattern) - } -} diff --git a/vendor/github.com/kisielk/gotool/tool.go b/vendor/github.com/kisielk/gotool/tool.go deleted file mode 100644 index c7409e11e6..0000000000 --- a/vendor/github.com/kisielk/gotool/tool.go +++ /dev/null @@ -1,48 +0,0 @@ -// Package gotool contains utility functions used to implement the standard -// "cmd/go" tool, provided as a convenience to developers who want to write -// tools with similar semantics. -package gotool - -import "go/build" - -// Export functions here to make it easier to keep the implementations up to date with upstream. - -// DefaultContext is the default context that uses build.Default. -var DefaultContext = Context{ - BuildContext: build.Default, -} - -// A Context specifies the supporting context. -type Context struct { - // BuildContext is the build.Context that is used when computing import paths. - BuildContext build.Context -} - -// ImportPaths returns the import paths to use for the given command line. -// -// The path "all" is expanded to all packages in $GOPATH and $GOROOT. -// The path "std" is expanded to all packages in the Go standard library. -// The path "cmd" is expanded to all Go standard commands. -// The string "..." is treated as a wildcard within a path. -// When matching recursively, directories are ignored if they are prefixed with -// a dot or an underscore (such as ".foo" or "_foo"), or are named "testdata". -// Relative import paths are not converted to full import paths. -// If args is empty, a single element "." is returned. -func (c *Context) ImportPaths(args []string) []string { - return c.importPaths(args) -} - -// ImportPaths returns the import paths to use for the given command line -// using default context. -// -// The path "all" is expanded to all packages in $GOPATH and $GOROOT. -// The path "std" is expanded to all packages in the Go standard library. -// The path "cmd" is expanded to all Go standard commands. -// The string "..." is treated as a wildcard within a path. -// When matching recursively, directories are ignored if they are prefixed with -// a dot or an underscore (such as ".foo" or "_foo"), or are named "testdata". -// Relative import paths are not converted to full import paths. -// If args is empty, a single element "." is returned. -func ImportPaths(args []string) []string { - return DefaultContext.importPaths(args) -} diff --git a/vendor/github.com/kunwardeep/paralleltest/pkg/paralleltest/paralleltest.go b/vendor/github.com/kunwardeep/paralleltest/pkg/paralleltest/paralleltest.go index 1aecc1a515..e9187d6fdb 100644 --- a/vendor/github.com/kunwardeep/paralleltest/pkg/paralleltest/paralleltest.go +++ b/vendor/github.com/kunwardeep/paralleltest/pkg/paralleltest/paralleltest.go @@ -26,6 +26,7 @@ type parallelAnalyzer struct { analyzer *analysis.Analyzer ignoreMissing bool ignoreMissingSubtests bool + ignoreLoopVar bool } func newParallelAnalyzer() *parallelAnalyzer { @@ -34,6 +35,7 @@ func newParallelAnalyzer() *parallelAnalyzer { var flags flag.FlagSet flags.BoolVar(&a.ignoreMissing, "i", false, "ignore missing calls to t.Parallel") flags.BoolVar(&a.ignoreMissingSubtests, "ignoremissingsubtests", false, "ignore missing calls to t.Parallel in subtests") + flags.BoolVar(&a.ignoreLoopVar, "ignoreloopVar", false, "ignore loop variable detection") a.analyzer = &analysis.Analyzer{ Name: "paralleltest", @@ -137,7 +139,7 @@ func (a *parallelAnalyzer) run(pass *analysis.Pass) (interface{}, error) { rangeStatementCantParallelMethod = methodSetenvIsCalledInMethodRun(r.X, innerTestVar) } - if loopVariableUsedInRun == nil { + if !a.ignoreLoopVar && loopVariableUsedInRun == nil { if run, ok := r.X.(*ast.CallExpr); ok { loopVariableUsedInRun = loopVarReferencedInRun(run, loopVars, pass.TypesInfo) } diff --git a/vendor/github.com/mbilski/exhaustivestruct/pkg/analyzer/analyzer.go b/vendor/github.com/mbilski/exhaustivestruct/pkg/analyzer/analyzer.go deleted file mode 100644 index 0dfb713c5a..0000000000 --- a/vendor/github.com/mbilski/exhaustivestruct/pkg/analyzer/analyzer.go +++ /dev/null @@ -1,187 +0,0 @@ -package analyzer - -import ( - "flag" - "fmt" - "go/ast" - "go/types" - "path" - "strings" - - "golang.org/x/tools/go/analysis/passes/inspect" - "golang.org/x/tools/go/ast/inspector" - - "golang.org/x/tools/go/analysis" -) - -// Analyzer that checks if all struct's fields are initialized -var Analyzer = &analysis.Analyzer{ - Name: "exhaustivestruct", - Doc: "Checks if all struct's fields are initialized", - Run: run, - Requires: []*analysis.Analyzer{inspect.Analyzer}, - Flags: newFlagSet(), -} - -// StructPatternList is a comma separated list of expressions to match struct packages and names -// The struct packages have the form example.com/package.ExampleStruct -// The matching patterns can use matching syntax from https://pkg.go.dev/path#Match -// If this list is empty, all structs are tested. -var StructPatternList string - -func newFlagSet() flag.FlagSet { - fs := flag.NewFlagSet("", flag.PanicOnError) - fs.StringVar(&StructPatternList, "struct_patterns", "", "This is a comma separated list of expressions to match struct packages and names") - return *fs -} - -func run(pass *analysis.Pass) (interface{}, error) { - splitFn := func(c rune) bool { return c == ',' } - inspector := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) - structPatterns := strings.FieldsFunc(StructPatternList, splitFn) - // validate the pattern syntax - for _, pattern := range structPatterns { - _, err := path.Match(pattern, "") - if err != nil { - return nil, fmt.Errorf("invalid struct pattern %s: %w", pattern, err) - } - } - - nodeFilter := []ast.Node{ - (*ast.CompositeLit)(nil), - (*ast.ReturnStmt)(nil), - } - - var returnStmt *ast.ReturnStmt - - inspector.Preorder(nodeFilter, func(node ast.Node) { - var name string - - compositeLit, ok := node.(*ast.CompositeLit) - if !ok { - // Keep track of the last return statement whilte iterating - retLit, ok := node.(*ast.ReturnStmt) - if ok { - returnStmt = retLit - } - return - } - - i, ok := compositeLit.Type.(*ast.Ident) - - if ok { - name = i.Name - } else { - s, ok := compositeLit.Type.(*ast.SelectorExpr) - - if !ok { - return - } - - name = s.Sel.Name - } - - if compositeLit.Type == nil { - return - } - - t := pass.TypesInfo.TypeOf(compositeLit.Type) - - if t == nil { - return - } - - if len(structPatterns) > 0 { - shouldLint := false - for _, pattern := range structPatterns { - // We check the patterns for vailidy ahead of time, so we don't need to check the error here - if match, _ := path.Match(pattern, t.String()); match { - shouldLint = true - break - } - } - if !shouldLint { - return - } - } - - str, ok := t.Underlying().(*types.Struct) - - if !ok { - return - } - - // Don't report an error if: - // 1. This composite literal contains no fields and - // 2. It's in a return statement and - // 3. The return statement contains a non-nil error - if len(compositeLit.Elts) == 0 { - // Check if this composite is one of the results the last return statement - isInResults := false - if returnStmt != nil { - for _, result := range returnStmt.Results { - compareComposite, ok := result.(*ast.CompositeLit) - if ok { - if compareComposite == compositeLit { - isInResults = true - } - } - } - } - nonNilError := false - if isInResults { - // Check if any of the results has an error type and if that error is set to non-nil (if it's set to nil, the type would be "untyped nil") - for _, result := range returnStmt.Results { - if pass.TypesInfo.TypeOf(result).String() == "error" { - nonNilError = true - } - } - } - - if nonNilError { - return - } - } - - samePackage := strings.HasPrefix(t.String(), pass.Pkg.Path()+".") - - missing := []string{} - - for i := 0; i < str.NumFields(); i++ { - fieldName := str.Field(i).Name() - exists := false - - if !samePackage && !str.Field(i).Exported() { - continue - } - - for eIndex, e := range compositeLit.Elts { - if k, ok := e.(*ast.KeyValueExpr); ok { - if i, ok := k.Key.(*ast.Ident); ok { - if i.Name == fieldName { - exists = true - break - } - } - } else { - if eIndex == i { - exists = true - break - } - } - } - - if !exists { - missing = append(missing, fieldName) - } - } - - if len(missing) == 1 { - pass.Reportf(node.Pos(), "%s is missing in %s", missing[0], name) - } else if len(missing) > 1 { - pass.Reportf(node.Pos(), "%s are missing in %s", strings.Join(missing, ", "), name) - } - }) - - return nil, nil -} diff --git a/vendor/github.com/nunnatsa/ginkgolinter/README.md b/vendor/github.com/nunnatsa/ginkgolinter/README.md index 3832f610df..977cec903e 100644 --- a/vendor/github.com/nunnatsa/ginkgolinter/README.md +++ b/vendor/github.com/nunnatsa/ginkgolinter/README.md @@ -227,6 +227,54 @@ ginkgolinter checks the following: * 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. +### Async timing interval: timeout is shorter than polling interval [BUG] +***Note***: Only applied when the `suppress-async-assertion` flag is **not set** *and* the `validate-async-intervals` +flag **is** set. + +***Note***: This rule work with best-effort approach. It can't find many cases, like const defined not in the same +package, or when using variables. + +The timeout and polling intervals may be passed as optional arguments to the `Eventually` or `Constanly` functions, or +using the `WithTimeout` or , `Within` methods (timeout), and `WithPolling` or `ProbeEvery` methods (polling). + +This rule checks if the async (`Eventually` or `Consistently`) timeout duration, is not shorter than the polling interval. + +For example: + ```go + Eventually(aFunc).WithTimeout(500 * time.Millisecond).WithPolling(10 * time.Second).Should(Succeed()) + ``` + +This will probably happen when using the old format: + ```go + Eventually(aFunc, 500 * time.Millisecond /*timeout*/, 10 * time.Second /*polling*/).Should(Succeed()) + ``` + +### Avoid Spec Pollution: Don't Initialize Variables in Container Nodes [BUG/STYLE]: +***Note***: Only applied when the `--forbid-spec-pollution=true` flag is set (disabled by default). + +According to [ginkgo documentation](https://onsi.github.io/ginkgo/#avoid-spec-pollution-dont-initialize-variables-in-container-nodes), +no variable should be assigned within a container node (`Describe`, `Context`, `When` or their `F`, `P` or `X` forms) + +For example: +```go +var _ = Describe("description", func(){ + var x = 10 + ... +}) +``` + +Instead, use `BeforeEach()`; e.g. +```go +var _ = Describe("description", func (){ + var x int + + BeforeEach(func (){ + x = 10 + }) + ... +}) +``` + ### 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. @@ -259,6 +307,20 @@ The output of the linter,when finding issues, looks like this: ./testdata/src/a/a.go:18:5: ginkgo-linter: wrong length assertion; consider using `Expect("").Should(BeEmpty())` instead ./testdata/src/a/a.go:22:5: ginkgo-linter: wrong length assertion; consider using `Expect("").Should(BeEmpty())` instead ``` + +### Wrong Cap Assertion [STYLE] +The linter finds assertion of the golang built-in `cap` function, with all kind of matchers, while there are already +gomega matchers for these usecases; We want to assert the item, rather than its cap. + +There are several wrong patterns: +```go +Expect(cap(x)).To(Equal(0)) // should be: Expect(x).To(HaveCap(0)) +Expect(cap(x)).To(BeZero()) // should be: Expect(x).To(HaveCap(0)) +Expect(cap(x)).To(BeNumeric(">", 0)) // should be: Expect(x).ToNot(HaveCap(0)) +Expect(cap(x)).To(BeNumeric("==", 2)) // should be: Expect(x).To(HaveCap(2)) +Expect(cap(x)).To(BeNumeric("!=", 3)) // should be: Expect(x).ToNot(HaveCap(3)) +``` + #### use the `HaveLen(0)` matcher. [STYLE] The linter will also warn about the `HaveLen(0)` matcher, and will suggest to replace it with `BeEmpty()` @@ -356,9 +418,67 @@ Expect(x1 == c1).Should(BeTrue()) // ==> Expect(x1).Should(Equal(c1)) Expect(c1 == x1).Should(BeTrue()) // ==> Expect(x1).Should(Equal(c1)) ``` +### Don't Allow Using `Expect` with `Should` or `ShouldNot` [STYLE] +This optional rule forces the usage of the `Expect` method only with the `To`, `ToNot` or `NotTo` +assertion methods; e.g. +```go +Expect("abc").Should(HaveLen(3)) // => Expect("abc").To(HaveLen(3)) +Expect("abc").ShouldNot(BeEmpty()) // => Expect("abc").ToNot(BeEmpty()) +``` +This rule support auto fixing. + +***This rule is disabled by default***. Use the `--force-expect-to=true` command line flag to enable it. + +### Async timing interval: multiple timeout or polling intervals [STYLE] +***Note***: Only applied when the `suppress-async-assertion` flag is **not set** *and* the `validate-async-intervals` +flag **is** set. + +The timeout and polling intervals may be passed as optional arguments to the `Eventually` or `Constanly` functions, or +using the `WithTimeout` or , `Within` methods (timeout), and `WithPolling` or `ProbeEvery` methods (polling). + +The linter checks that there is up to one polling argument and up to one timeout argument. + +For example: + +```go +// both WithTimeout() and Within() +Eventually(aFunc).WithTimeout(time.Second * 10).Within(time.Second * 10).WithPolling(time.Millisecond * 500).Should(BeTrue()) +// both polling argument, and WithPolling() method +Eventually(aFunc, time.Second*10, time.Millisecond * 500).WithPolling(time.Millisecond * 500).Should(BeTrue()) +``` + +### Async timing interval: non-time.Duration intervals [STYLE] +***Note***: Only applied when the `suppress-async-assertion` flag is **not set** *and* the `validate-async-intervals` +flag **is** set. + +gomega supports a few formats for timeout and polling intervals, when using the old format (the last two parameters of Eventually and Constantly): +* a `time.Duration` value +* any kind of numeric value (int(8/16/32/64), uint(8/16/32/64) or float(32/64), as the number of seconds. +* duration string like `"12s"` + +The linter triggers a warning for any duration value that is not of the `time.Duration` type, assuming that this is +the desired type, given the type of the argument of the newer "WithTimeout", "WithPolling", "Within" and "ProbeEvery" +methods. + +For example: + ```go + Eventually(func() bool { return true }, "1s").Should(BeTrue()) + Eventually(context.Background(), func() bool { return true }, time.Second*60, float64(2)).Should(BeTrue()) + ``` + +This rule offers a limited auto fix: for integer values, or integer consts, the linter will suggest multiply the +value with `time.Second`; e.g. +```go +const polling = 1 +Eventually(aFunc, 5, polling) +``` +will be changed to: +```go +Eventually(aFunc, time.Second*5, time.Second*polling) +``` ## Suppress the linter ### Suppress warning from command line -* Use the `--suppress-len-assertion=true` flag to suppress the wrong length assertion warning +* Use the `--suppress-len-assertion=true` flag to suppress the wrong length and cap assertions warning * Use the `--suppress-nil-assertion=true` flag to suppress the wrong nil assertion warning * Use the `--suppress-err-assertion=true` flag to suppress the wrong error assertion warning * Use the `--suppress-compare-assertion=true` flag to suppress the wrong comparison assertion warning @@ -369,7 +489,7 @@ Expect(c1 == x1).Should(BeTrue()) // ==> Expect(x1).Should(Equal(c1)) command line, and not from a comment. ### Suppress warning from the code -To suppress the wrong length assertion warning, add a comment with (only) +To suppress the wrong length and cap assertions warning, add a comment with (only) `ginkgo-linter:ignore-len-assert-warning`. diff --git a/vendor/github.com/nunnatsa/ginkgolinter/analyzer.go b/vendor/github.com/nunnatsa/ginkgolinter/analyzer.go new file mode 100644 index 0000000000..edff57acd1 --- /dev/null +++ b/vendor/github.com/nunnatsa/ginkgolinter/analyzer.go @@ -0,0 +1,58 @@ +package ginkgolinter + +import ( + "flag" + "fmt" + + "golang.org/x/tools/go/analysis" + + "github.com/nunnatsa/ginkgolinter/linter" + "github.com/nunnatsa/ginkgolinter/types" + "github.com/nunnatsa/ginkgolinter/version" +) + +// NewAnalyzerWithConfig returns an Analyzer. +func NewAnalyzerWithConfig(config *types.Config) *analysis.Analyzer { + theLinter := linter.NewGinkgoLinter(config) + + return &analysis.Analyzer{ + Name: "ginkgolinter", + Doc: fmt.Sprintf(doc, version.Version()), + Run: theLinter.Run, + } +} + +// NewAnalyzer returns an Analyzer - the package interface with nogo +func NewAnalyzer() *analysis.Analyzer { + config := &types.Config{ + SuppressLen: false, + SuppressNil: false, + SuppressErr: false, + SuppressCompare: false, + ForbidFocus: false, + AllowHaveLen0: false, + ForceExpectTo: false, + } + + a := NewAnalyzerWithConfig(config) + + var ignored bool + a.Flags.Init("ginkgolinter", flag.ExitOnError) + a.Flags.Var(&config.SuppressLen, "suppress-len-assertion", "Suppress warning for wrong length assertions") + a.Flags.Var(&config.SuppressNil, "suppress-nil-assertion", "Suppress warning for wrong nil assertions") + a.Flags.Var(&config.SuppressErr, "suppress-err-assertion", "Suppress warning for wrong error assertions") + a.Flags.Var(&config.SuppressCompare, "suppress-compare-assertion", "Suppress warning for wrong comparison assertions") + a.Flags.Var(&config.SuppressAsync, "suppress-async-assertion", "Suppress warning for function call in async assertion, like Eventually") + a.Flags.Var(&config.ValidateAsyncIntervals, "validate-async-intervals", "best effort validation of async intervals (timeout and polling); ignored the suppress-async-assertion flag is true") + a.Flags.Var(&config.SuppressTypeCompare, "suppress-type-compare-assertion", "Suppress warning for comparing values from different types, like int32 and uint32") + a.Flags.Var(&config.AllowHaveLen0, "allow-havelen-0", "Do not warn for HaveLen(0); default = false") + a.Flags.Var(&config.ForceExpectTo, "force-expect-to", "force using `Expect` with `To`, `ToNot` or `NotTo`. reject using `Expect` with `Should` or `ShouldNot`; default = false (not forced)") + a.Flags.BoolVar(&ignored, "suppress-focus-container", true, "Suppress warning for ginkgo focus containers like FDescribe, FContext, FWhen or FIt. Deprecated and ignored: use --forbid-focus-container instead") + a.Flags.Var(&config.ForbidFocus, "forbid-focus-container", "trigger a warning for ginkgo focus containers like FDescribe, FContext, FWhen or FIt; default = false.") + a.Flags.Var(&config.ForbidSpecPollution, "forbid-spec-pollution", "trigger a warning for variable assignments in ginkgo containers like Describe, Context and When, instead of in BeforeEach(); default = false.") + + return a +} + +// Analyzer is the interface to go_vet +var Analyzer = NewAnalyzer() diff --git a/vendor/github.com/nunnatsa/ginkgolinter/doc.go b/vendor/github.com/nunnatsa/ginkgolinter/doc.go new file mode 100644 index 0000000000..dd9ecf58a8 --- /dev/null +++ b/vendor/github.com/nunnatsa/ginkgolinter/doc.go @@ -0,0 +1,99 @@ +package ginkgolinter + +const doc = `enforces standards of using ginkgo and gomega + +or + ginkgolinter version + +version: %s + +currently, the linter searches for following: +* trigger a warning when using Eventually or Consistently with a function call. This is in order to prevent the case when + using a function call instead of a function. Function call returns a value only once, and so the original value + is tested again and again and is never changed. [Bug] + +* trigger a warning when comparing a pointer to a value. [Bug] + +* trigger a warning for missing assertion method: [Bug] + Eventually(checkSomething) + +* trigger a warning when a ginkgo focus container (FDescribe, FContext, FWhen or FIt) is found. [Bug] + +* 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. + +* async timing interval: timeout is shorter than polling interval [Bug] +For example: + Eventually(aFunc).WithTimeout(500 * time.Millisecond).WithPolling(10 * time.Second).Should(Succeed()) +This will probably happen when using the old format: + Eventually(aFunc, 500 * time.Millisecond, 10 * time.Second).Should(Succeed()) + +* reject variable assignments in ginkgo containers [Bug/Style]: +For example: + var _ = Describe("description", func(){ + var x = 10 + }) + +Should use BeforeEach instead; e.g. + var _ = Describe("description", func(){ + var x int + BeforeEach(func(){ + x = 10 + }) + }) + +* wrong length assertions. We want to assert the item rather than its length. [Style] +For example: + Expect(len(x)).Should(Equal(1)) +This should be replaced with: + Expect(x)).Should(HavelLen(1)) + +* wrong cap assertions. We want to assert the item rather than its cap. [Style] +For example: + Expect(cap(x)).Should(Equal(1)) +This should be replaced with: + Expect(x)).Should(HavelCap(1)) + +* wrong nil assertions. We want to assert the item rather than a comparison result. [Style] +For example: + Expect(x == nil).Should(BeTrue()) +This should be replaced with: + Expect(x).Should(BeNil()) + +* wrong error assertions. For example: [Style] + Expect(err == nil).Should(BeTrue()) +This should be replaced with: + Expect(err).ShouldNot(HaveOccurred()) + +* wrong boolean comparison, for example: [Style] + Expect(x == 8).Should(BeTrue()) +This should be replaced with: + Expect(x).Should(BeEqual(8)) + +* replaces Equal(true/false) with BeTrue()/BeFalse() [Style] + +* replaces HaveLen(0) with BeEmpty() [Style] + +* replaces Expect(...).Should(...) with Expect(...).To() [Style] + +* async timing interval: multiple timeout or polling interval [Style] +For example: + Eventually(context.Background(), func() bool { return true }, time.Second*10).WithTimeout(time.Second * 10).WithPolling(time.Millisecond * 500).Should(BeTrue()) + Eventually(context.Background(), func() bool { return true }, time.Second*10).Within(time.Second * 10).WithPolling(time.Millisecond * 500).Should(BeTrue()) + Eventually(func() bool { return true }, time.Second*10, 500*time.Millisecond).WithPolling(time.Millisecond * 500).Should(BeTrue()) + Eventually(func() bool { return true }, time.Second*10, 500*time.Millisecond).ProbeEvery(time.Millisecond * 500).Should(BeTrue()) + +* async timing interval: non-time.Duration intervals [Style] +gomega supports a few formats for timeout and polling intervals, when using the old format (the last two parameters of Eventually and Constantly): + * time.Duration + * any kind of numeric value, as number of seconds + * duration string like "12s" +The linter triggers a warning for any duration value that is not of the time.Duration type, assuming that this is +the desired type, given the type of the argument of the newer "WithTimeout", "WithPolling", "Within" and "ProbeEvery" +methods. +For example: + Eventually(context.Background(), func() bool { return true }, "1s").Should(BeTrue()) + Eventually(context.Background(), func() bool { return true }, time.Second*60, 15).Should(BeTrue()) +` diff --git a/vendor/github.com/nunnatsa/ginkgolinter/ginkgohandler/handler.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/handler.go similarity index 72% rename from vendor/github.com/nunnatsa/ginkgolinter/ginkgohandler/handler.go rename to vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/handler.go index c0829c4695..f10d831840 100644 --- a/vendor/github.com/nunnatsa/ginkgolinter/ginkgohandler/handler.go +++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/ginkgohandler/handler.go @@ -15,6 +15,7 @@ const ( // in imported with "." name, custom name or without any name. type Handler interface { GetFocusContainerName(*ast.CallExpr) (bool, *ast.Ident) + IsWrapContainer(*ast.CallExpr) bool IsFocusSpec(ident ast.Expr) bool } @@ -49,6 +50,13 @@ func (h dotHandler) GetFocusContainerName(exp *ast.CallExpr) (bool, *ast.Ident) return false, nil } +func (h dotHandler) IsWrapContainer(exp *ast.CallExpr) bool { + if fun, ok := exp.Fun.(*ast.Ident); ok { + return IsWrapContainer(fun.Name) + } + return false +} + func (h dotHandler) IsFocusSpec(exp ast.Expr) bool { id, ok := exp.(*ast.Ident) return ok && id.Name == focusSpec @@ -70,6 +78,16 @@ func (h nameHandler) GetFocusContainerName(exp *ast.CallExpr) (bool, *ast.Ident) return false, nil } +func (h nameHandler) IsWrapContainer(exp *ast.CallExpr) bool { + if sel, ok := exp.Fun.(*ast.SelectorExpr); ok { + if id, ok := sel.X.(*ast.Ident); ok && id.Name == string(h) { + return IsWrapContainer(sel.Sel.Name) + } + } + return false + +} + func (h nameHandler) IsFocusSpec(exp ast.Expr) bool { if selExp, ok := exp.(*ast.SelectorExpr); ok { if x, ok := selExp.X.(*ast.Ident); ok && x.Name == string(h) { @@ -88,10 +106,24 @@ func isFocusContainer(name string) bool { return false } -func IsContainer(id *ast.Ident) bool { - switch id.Name { - case "It", "When", "Context", "Describe", "DescribeTable", "Entry": +func IsContainer(name string) bool { + switch name { + case "It", "When", "Context", "Describe", "DescribeTable", "Entry", + "PIt", "PWhen", "PContext", "PDescribe", "PDescribeTable", "PEntry", + "XIt", "XWhen", "XContext", "XDescribe", "XDescribeTable", "XEntry": + return true + } + return isFocusContainer(name) +} + +func IsWrapContainer(name string) bool { + switch name { + case "When", "Context", "Describe", + "FWhen", "FContext", "FDescribe", + "PWhen", "PContext", "PDescribe", + "XWhen", "XContext", "XDescribe": return true } - return isFocusContainer(id.Name) + + return false } diff --git a/vendor/github.com/nunnatsa/ginkgolinter/gomegahandler/handler.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/gomegahandler/handler.go similarity index 97% rename from vendor/github.com/nunnatsa/ginkgolinter/gomegahandler/handler.go rename to vendor/github.com/nunnatsa/ginkgolinter/internal/gomegahandler/handler.go index 419145b75a..4290e73736 100644 --- a/vendor/github.com/nunnatsa/ginkgolinter/gomegahandler/handler.go +++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/gomegahandler/handler.go @@ -70,7 +70,12 @@ func (h dotHandler) GetActualFuncName(expr *ast.CallExpr) (string, bool) { // ReplaceFunction replaces the function with another one, for fix suggestions func (dotHandler) ReplaceFunction(caller *ast.CallExpr, newExpr *ast.Ident) { - caller.Fun = newExpr + switch f := caller.Fun.(type) { + case *ast.Ident: + caller.Fun = newExpr + case *ast.SelectorExpr: + f.Sel = newExpr + } } func (dotHandler) getDefFuncName(expr *ast.CallExpr) string { diff --git a/vendor/github.com/nunnatsa/ginkgolinter/interfaces/interfaces.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/interfaces/interfaces.go similarity index 100% rename from vendor/github.com/nunnatsa/ginkgolinter/interfaces/interfaces.go rename to vendor/github.com/nunnatsa/ginkgolinter/internal/interfaces/interfaces.go diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/intervals/intervals.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/intervals/intervals.go new file mode 100644 index 0000000000..b8166bdb21 --- /dev/null +++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/intervals/intervals.go @@ -0,0 +1,285 @@ +package intervals + +import ( + "errors" + "go/ast" + "go/constant" + "go/token" + gotypes "go/types" + "strconv" + "time" + + "golang.org/x/tools/go/analysis" + + "github.com/nunnatsa/ginkgolinter/internal/gomegahandler" + "github.com/nunnatsa/ginkgolinter/internal/reports" +) + +type noDurationIntervalErr struct { + value string +} + +func (err noDurationIntervalErr) Error() string { + return "only use time.Duration for timeout and polling in Eventually() or Consistently()" +} + +func CheckIntervals(pass *analysis.Pass, expr *ast.CallExpr, actualExpr *ast.CallExpr, reportBuilder *reports.Builder, handler gomegahandler.Handler, timePkg string, funcIndex int) { + var ( + timeout time.Duration + polling time.Duration + err error + ) + + timeoutOffset := funcIndex + 1 + if len(actualExpr.Args) > timeoutOffset { + timeout, err = getDuration(pass, actualExpr.Args[timeoutOffset], timePkg) + if err != nil { + suggestFix := false + if tryFixIntDuration(expr, err, handler, timePkg, timeoutOffset) { + suggestFix = true + } + reportBuilder.AddIssue(suggestFix, err.Error()) + } + pollingOffset := funcIndex + 2 + if len(actualExpr.Args) > pollingOffset { + polling, err = getDuration(pass, actualExpr.Args[pollingOffset], timePkg) + if err != nil { + suggestFix := false + if tryFixIntDuration(expr, err, handler, timePkg, pollingOffset) { + suggestFix = true + } + reportBuilder.AddIssue(suggestFix, err.Error()) + } + } + } + + selExp := expr.Fun.(*ast.SelectorExpr) + for { + call, ok := selExp.X.(*ast.CallExpr) + if !ok { + break + } + + fun, ok := call.Fun.(*ast.SelectorExpr) + if !ok { + break + } + + switch fun.Sel.Name { + case "WithTimeout", "Within": + if timeout != 0 { + reportBuilder.AddIssue(false, "timeout defined more than once") + } else if len(call.Args) == 1 { + timeout, err = getDurationFromValue(pass, call.Args[0], timePkg) + if err != nil { + reportBuilder.AddIssue(false, err.Error()) + } + } + + case "WithPolling", "ProbeEvery": + if polling != 0 { + reportBuilder.AddIssue(false, "polling defined more than once") + } else if len(call.Args) == 1 { + polling, err = getDurationFromValue(pass, call.Args[0], timePkg) + if err != nil { + reportBuilder.AddIssue(false, err.Error()) + } + } + } + + selExp = fun + } + + if timeout != 0 && polling != 0 && timeout < polling { + reportBuilder.AddIssue(false, "timeout must not be shorter than the polling interval") + } +} + +func tryFixIntDuration(expr *ast.CallExpr, err error, handler gomegahandler.Handler, timePkg string, offset int) bool { + suggestFix := false + var durErr noDurationIntervalErr + if errors.As(err, &durErr) { + if len(durErr.value) > 0 { + actualExpr := handler.GetActualExpr(expr.Fun.(*ast.SelectorExpr)) + var newArg ast.Expr + second := &ast.SelectorExpr{ + Sel: ast.NewIdent("Second"), + X: ast.NewIdent(timePkg), + } + if durErr.value == "1" { + newArg = second + } else { + newArg = &ast.BinaryExpr{ + X: second, + Op: token.MUL, + Y: actualExpr.Args[offset], + } + } + actualExpr.Args[offset] = newArg + suggestFix = true + } + } + + return suggestFix +} + +func getDuration(pass *analysis.Pass, interval ast.Expr, timePkg string) (time.Duration, error) { + argType := pass.TypesInfo.TypeOf(interval) + if durType, ok := argType.(*gotypes.Named); ok { + if durType.Obj().Name() == "Duration" && durType.Obj().Pkg().Name() == "time" { + return getDurationFromValue(pass, interval, timePkg) + } + } + + value := "" + switch val := interval.(type) { + case *ast.BasicLit: + if val.Kind == token.INT { + value = val.Value + } + case *ast.Ident: + i, err := getConstDuration(pass, val, timePkg) + if err != nil || i == 0 { + return 0, nil + } + value = val.Name + } + + return 0, noDurationIntervalErr{value: value} +} + +func getDurationFromValue(pass *analysis.Pass, interval ast.Expr, timePkg string) (time.Duration, error) { + switch dur := interval.(type) { + case *ast.SelectorExpr: + ident, ok := dur.X.(*ast.Ident) + if ok { + if ident.Name == timePkg { + return getTimeDurationValue(dur) + } + return getDurationFromValue(pass, dur.Sel, timePkg) + } + case *ast.BinaryExpr: + return getBinaryExprDuration(pass, dur, timePkg) + + case *ast.Ident: + return getConstDuration(pass, dur, timePkg) + } + + return 0, nil +} + +func getConstDuration(pass *analysis.Pass, ident *ast.Ident, timePkg string) (time.Duration, error) { + o := pass.TypesInfo.ObjectOf(ident) + if o != nil { + if c, ok := o.(*gotypes.Const); ok { + if c.Val().Kind() == constant.Int { + i, err := strconv.Atoi(c.Val().String()) + if err != nil { + return 0, nil + } + return time.Duration(i), nil + } + } + } + + if ident.Obj != nil && ident.Obj.Kind == ast.Con && ident.Obj.Decl != nil { + if vals, ok := ident.Obj.Decl.(*ast.ValueSpec); ok { + if len(vals.Values) == 1 { + switch val := vals.Values[0].(type) { + case *ast.BasicLit: + if val.Kind == token.INT { + i, err := strconv.Atoi(val.Value) + if err != nil { + return 0, nil + } + return time.Duration(i), nil + } + return 0, nil + case *ast.BinaryExpr: + return getBinaryExprDuration(pass, val, timePkg) + } + } + } + } + + return 0, nil +} + +func getTimeDurationValue(dur *ast.SelectorExpr) (time.Duration, error) { + switch dur.Sel.Name { + case "Nanosecond": + return time.Nanosecond, nil + case "Microsecond": + return time.Microsecond, nil + case "Millisecond": + return time.Millisecond, nil + case "Second": + return time.Second, nil + case "Minute": + return time.Minute, nil + case "Hour": + return time.Hour, nil + default: + return 0, errors.New("unknown duration value") // should never happen + } +} + +func getBinaryExprDuration(pass *analysis.Pass, expr *ast.BinaryExpr, timePkg string) (time.Duration, error) { + x, err := getBinaryDurValue(pass, expr.X, timePkg) + if err != nil || x == 0 { + return 0, nil + } + y, err := getBinaryDurValue(pass, expr.Y, timePkg) + if err != nil || y == 0 { + return 0, nil + } + + switch expr.Op { + case token.ADD: + return x + y, nil + case token.SUB: + val := x - y + if val > 0 { + return val, nil + } + return 0, nil + case token.MUL: + return x * y, nil + case token.QUO: + if y == 0 { + return 0, nil + } + return x / y, nil + case token.REM: + if y == 0 { + return 0, nil + } + return x % y, nil + default: + return 0, nil + } +} + +func getBinaryDurValue(pass *analysis.Pass, expr ast.Expr, timePkg string) (time.Duration, error) { + switch x := expr.(type) { + case *ast.SelectorExpr: + return getDurationFromValue(pass, x, timePkg) + case *ast.BinaryExpr: + return getBinaryExprDuration(pass, x, timePkg) + case *ast.BasicLit: + if x.Kind == token.INT { + val, err := strconv.Atoi(x.Value) + if err != nil { + return 0, err + } + return time.Duration(val), nil + } + case *ast.ParenExpr: + return getBinaryDurValue(pass, x.X, timePkg) + + case *ast.Ident: + return getConstDuration(pass, x, timePkg) + } + + return 0, nil +} diff --git a/vendor/github.com/nunnatsa/ginkgolinter/internal/reports/report-builder.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/reports/report-builder.go new file mode 100644 index 0000000000..c7f931ca75 --- /dev/null +++ b/vendor/github.com/nunnatsa/ginkgolinter/internal/reports/report-builder.go @@ -0,0 +1,98 @@ +package reports + +import ( + "bytes" + "fmt" + "go/ast" + "go/printer" + "go/token" + "strings" + + "golang.org/x/tools/go/analysis" +) + +type Builder struct { + pos token.Pos + end token.Pos + oldExpr string + issues []string + fixOffer string + suggestFix bool +} + +func NewBuilder(fset *token.FileSet, oldExpr ast.Expr) *Builder { + b := &Builder{ + pos: oldExpr.Pos(), + end: oldExpr.End(), + oldExpr: goFmt(fset, oldExpr), + suggestFix: false, + } + + return b +} + +func (b *Builder) AddIssue(suggestFix bool, issue string, args ...any) { + if len(args) > 0 { + issue = fmt.Sprintf(issue, args...) + } + b.issues = append(b.issues, issue) + + if suggestFix { + b.suggestFix = true + } +} + +func (b *Builder) SetFixOffer(fset *token.FileSet, fixOffer ast.Expr) { + if offer := goFmt(fset, fixOffer); offer != b.oldExpr { + b.fixOffer = offer + } +} + +func (b *Builder) HasReport() bool { + return len(b.issues) > 0 +} + +func (b *Builder) Build() analysis.Diagnostic { + diagnostic := analysis.Diagnostic{ + Pos: b.pos, + Message: b.getMessage(), + } + + if b.suggestFix && len(b.fixOffer) > 0 { + diagnostic.SuggestedFixes = []analysis.SuggestedFix{ + { + Message: fmt.Sprintf("should replace %s with %s", b.oldExpr, b.fixOffer), + TextEdits: []analysis.TextEdit{ + { + Pos: b.pos, + End: b.end, + NewText: []byte(b.fixOffer), + }, + }, + }, + } + } + + return diagnostic +} + +func goFmt(fset *token.FileSet, x ast.Expr) string { + var b bytes.Buffer + _ = printer.Fprint(&b, fset, x) + return b.String() +} + +func (b *Builder) getMessage() string { + sb := strings.Builder{} + sb.WriteString("ginkgo-linter: ") + if len(b.issues) > 1 { + sb.WriteString("multiple issues: ") + } + sb.WriteString(strings.Join(b.issues, "; ")) + + if b.suggestFix && len(b.fixOffer) != 0 { + sb.WriteString(fmt.Sprintf(". Consider using `%s` instead", b.fixOffer)) + } + + return sb.String() +} diff --git a/vendor/github.com/nunnatsa/ginkgolinter/reverseassertion/reverse_assertion.go b/vendor/github.com/nunnatsa/ginkgolinter/internal/reverseassertion/reverse_assertion.go similarity index 100% rename from vendor/github.com/nunnatsa/ginkgolinter/reverseassertion/reverse_assertion.go rename to vendor/github.com/nunnatsa/ginkgolinter/internal/reverseassertion/reverse_assertion.go diff --git a/vendor/github.com/nunnatsa/ginkgolinter/ginkgo_linter.go b/vendor/github.com/nunnatsa/ginkgolinter/linter/ginkgo_linter.go similarity index 65% rename from vendor/github.com/nunnatsa/ginkgolinter/ginkgo_linter.go rename to vendor/github.com/nunnatsa/ginkgolinter/linter/ginkgo_linter.go index a0aff1612d..b158c3a3ae 100644 --- a/vendor/github.com/nunnatsa/ginkgolinter/ginkgo_linter.go +++ b/vendor/github.com/nunnatsa/ginkgolinter/linter/ginkgo_linter.go @@ -1,8 +1,7 @@ -package ginkgolinter +package linter import ( "bytes" - "flag" "fmt" "go/ast" "go/constant" @@ -14,12 +13,13 @@ import ( "github.com/go-toolsmith/astcopy" "golang.org/x/tools/go/analysis" - "github.com/nunnatsa/ginkgolinter/ginkgohandler" - "github.com/nunnatsa/ginkgolinter/gomegahandler" - "github.com/nunnatsa/ginkgolinter/interfaces" - "github.com/nunnatsa/ginkgolinter/reverseassertion" + "github.com/nunnatsa/ginkgolinter/internal/ginkgohandler" + "github.com/nunnatsa/ginkgolinter/internal/gomegahandler" + "github.com/nunnatsa/ginkgolinter/internal/interfaces" + "github.com/nunnatsa/ginkgolinter/internal/intervals" + "github.com/nunnatsa/ginkgolinter/internal/reports" + "github.com/nunnatsa/ginkgolinter/internal/reverseassertion" "github.com/nunnatsa/ginkgolinter/types" - "github.com/nunnatsa/ginkgolinter/version" ) // The ginkgolinter enforces standards of using ginkgo and gomega. @@ -28,24 +28,26 @@ import ( const ( linterName = "ginkgo-linter" - wrongLengthWarningTemplate = linterName + ": wrong length assertion; consider using `%s` instead" - wrongNilWarningTemplate = linterName + ": wrong nil assertion; consider using `%s` instead" - wrongBoolWarningTemplate = linterName + ": wrong boolean assertion; consider using `%s` instead" - wrongErrWarningTemplate = linterName + ": wrong error assertion; consider using `%s` instead" - wrongCompareWarningTemplate = linterName + ": wrong comparison assertion; consider using `%s` instead" - doubleNegativeWarningTemplate = linterName + ": avoid double negative assertion; consider using `%s` instead" - valueInEventually = linterName + ": use a function call in %s. This actually checks nothing, because %s receives the function returned value, instead of function itself, and this value is never changed" - comparePointerToValue = linterName + ": comparing a pointer to a value will always fail. consider using `%s` instead" - missingAssertionMessage = linterName + `: %q: missing assertion method. Expected "Should()", "To()", "ShouldNot()", "ToNot()" or "NotTo()"` - missingAsyncAssertionMessage = linterName + `: %q: missing assertion method. Expected "Should()" or "ShouldNot()"` - focusContainerFound = linterName + ": Focus container found. This is used only for local debug and should not be part of the actual source code, consider to replace with %q" - focusSpecFound = linterName + ": Focus spec found. This is used only for local debug and should not be part of the actual source code, consider to remove it" - compareDifferentTypes = linterName + ": use %[1]s with different types: Comparing %[2]s with %[3]s; either change the expected value type if possible, or use the BeEquivalentTo() matcher, instead of %[1]s()" - 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)" + wrongLengthWarningTemplate = "wrong length assertion" + wrongCapWarningTemplate = "wrong cap assertion" + wrongNilWarningTemplate = "wrong nil assertion" + wrongBoolWarningTemplate = "wrong boolean assertion" + wrongErrWarningTemplate = "wrong error assertion" + wrongCompareWarningTemplate = "wrong comparison assertion" + doubleNegativeWarningTemplate = "avoid double negative assertion" + valueInEventually = "use a function call in %s. This actually checks nothing, because %s receives the function returned value, instead of function itself, and this value is never changed" + comparePointerToValue = "comparing a pointer to a value will always fail" + missingAssertionMessage = linterName + `: %q: missing assertion method. Expected %s` + 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 = "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()" + matchErrorArgWrongType = "the MatchError matcher used to assert a non error type (%s)" + matchErrorWrongTypeAssertion = "MatchError first parameter (%s) must be error, string, GomegaMatcher or func(error)bool are allowed" + matchErrorMissingDescription = "missing function description as second parameter of MatchError" + matchErrorRedundantArg = "redundant MatchError arguments; consider removing them" + matchErrorNoFuncDescription = "The second parameter of MatchError must be the function description (string)" + forceExpectToTemplate = "must not use Expect with %s" + useBeforeEachTemplate = "use BeforeEach() to assign variable %s" ) const ( // gomega matchers @@ -59,6 +61,7 @@ const ( // gomega matchers beZero = "BeZero" equal = "Equal" haveLen = "HaveLen" + haveCap = "HaveCap" haveOccurred = "HaveOccurred" haveValue = "HaveValue" not = "Not" @@ -79,101 +82,19 @@ const ( // gomega actuals consistentlyWithOffset = "ConsistentlyWithOffset" ) -// Analyzer is the interface to go_vet -var Analyzer = NewAnalyzer() - -type ginkgoLinter struct { +type GinkgoLinter struct { config *types.Config } -// NewAnalyzer returns an Analyzer - the package interface with nogo -func NewAnalyzer() *analysis.Analyzer { - linter := ginkgoLinter{ - config: &types.Config{ - SuppressLen: false, - SuppressNil: false, - SuppressErr: false, - SuppressCompare: false, - ForbidFocus: false, - AllowHaveLen0: false, - }, - } - - a := &analysis.Analyzer{ - Name: "ginkgolinter", - Doc: fmt.Sprintf(doc, version.Version()), - Run: linter.run, +// NewGinkgoLinter return new ginkgolinter object +func NewGinkgoLinter(config *types.Config) *GinkgoLinter { + return &GinkgoLinter{ + config: config, } - - var ignored bool - a.Flags.Init("ginkgolinter", flag.ExitOnError) - a.Flags.Var(&linter.config.SuppressLen, "suppress-len-assertion", "Suppress warning for wrong length assertions") - a.Flags.Var(&linter.config.SuppressNil, "suppress-nil-assertion", "Suppress warning for wrong nil assertions") - a.Flags.Var(&linter.config.SuppressErr, "suppress-err-assertion", "Suppress warning for wrong error assertions") - a.Flags.Var(&linter.config.SuppressCompare, "suppress-compare-assertion", "Suppress warning for wrong comparison assertions") - a.Flags.Var(&linter.config.SuppressAsync, "suppress-async-assertion", "Suppress warning for function call in async assertion, like Eventually") - a.Flags.Var(&linter.config.SuppressTypeCompare, "suppress-type-compare-assertion", "Suppress warning for comparing values from different types, like int32 and uint32") - a.Flags.Var(&linter.config.AllowHaveLen0, "allow-havelen-0", "Do not warn for HaveLen(0); default = false") - - a.Flags.BoolVar(&ignored, "suppress-focus-container", true, "Suppress warning for ginkgo focus containers like FDescribe, FContext, FWhen or FIt. Deprecated and ignored: use --forbid-focus-container instead") - a.Flags.Var(&linter.config.ForbidFocus, "forbid-focus-container", "trigger a warning for ginkgo focus containers like FDescribe, FContext, FWhen or FIt; default = false.") - - return a } -const doc = `enforces standards of using ginkgo and gomega - -or - ginkgolinter version - -version: %s - -currently, the linter searches for following: -* trigger a warning when using Eventually or Constantly with a function call. This is in order to prevent the case when - using a function call instead of a function. Function call returns a value only once, and so the original value - is tested again and again and is never changed. [Bug] - -* trigger a warning when comparing a pointer to a value. [Bug] - -* trigger a warning for missing assertion method: [Bug] - Eventually(checkSomething) - -* trigger a warning when a ginkgo focus container (FDescribe, FContext, FWhen or FIt) is found. [Bug] - -* 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. - -* wrong length assertions. We want to assert the item rather than its length. [Style] -For example: - Expect(len(x)).Should(Equal(1)) -This should be replaced with: - Expect(x)).Should(HavelLen(1)) - -* wrong nil assertions. We want to assert the item rather than a comparison result. [Style] -For example: - Expect(x == nil).Should(BeTrue()) -This should be replaced with: - Expect(x).Should(BeNil()) - -* wrong error assertions. For example: [Style] - Expect(err == nil).Should(BeTrue()) -This should be replaced with: - Expect(err).ShouldNot(HaveOccurred()) - -* wrong boolean comparison, for example: [Style] - Expect(x == 8).Should(BeTrue()) -This should be replaced with: - Expect(x).Should(BeEqual(8)) - -* replaces Equal(true/false) with BeTrue()/BeFalse() [Style] - -* replaces HaveLen(0) with BeEmpty() [Style] -` - -// main assertion function -func (l *ginkgoLinter) run(pass *analysis.Pass) (interface{}, error) { +// Run is the main assertion function +func (l *GinkgoLinter) Run(pass *analysis.Pass) (interface{}, error) { for _, file := range pass.Files { fileConfig := l.config.Clone() @@ -188,20 +109,37 @@ func (l *ginkgoLinter) run(pass *analysis.Pass) (interface{}, error) { continue } - //gomegaMatcher = getMatcherInterface(pass, file) + timePks := "" + for _, imp := range file.Imports { + if imp.Path.Value == `"time"` { + if imp.Name == nil { + timePks = "time" + } else { + timePks = imp.Name.Name + } + } + } ast.Inspect(file, func(n ast.Node) bool { - if ginkgoHndlr != nil && fileConfig.ForbidFocus { + if ginkgoHndlr != nil { + goDeeper := false spec, ok := n.(*ast.ValueSpec) if ok { for _, val := range spec.Values { if exp, ok := val.(*ast.CallExpr); ok { - if checkFocusContainer(pass, ginkgoHndlr, exp) { - return true + if bool(fileConfig.ForbidFocus) && checkFocusContainer(pass, ginkgoHndlr, exp) { + goDeeper = true + } + + if bool(fileConfig.ForbidSpecPollution) && checkAssignmentsInContainer(pass, ginkgoHndlr, exp) { + goDeeper = true } } } } + if goDeeper { + return true + } } stmt, ok := n.(*ast.ExprStmt) @@ -221,8 +159,17 @@ func (l *ginkgoLinter) run(pass *analysis.Pass) (interface{}, error) { return true } - if ginkgoHndlr != nil && bool(config.ForbidFocus) && checkFocusContainer(pass, ginkgoHndlr, assertionExp) { - return true + if ginkgoHndlr != nil { + goDeeper := false + if bool(config.ForbidFocus) && checkFocusContainer(pass, ginkgoHndlr, assertionExp) { + goDeeper = true + } + if bool(config.ForbidSpecPollution) && checkAssignmentsInContainer(pass, ginkgoHndlr, assertionExp) { + goDeeper = true + } + if goDeeper { + return true + } } // no more ginkgo checks. From here it's only gomega. So if there is no gomega handler, exit here. This is @@ -247,12 +194,89 @@ func (l *ginkgoLinter) run(pass *analysis.Pass) (interface{}, error) { return true } - return checkExpression(pass, config, assertionExp, actualExpr, gomegaHndlr) + return checkExpression(pass, config, assertionExp, actualExpr, gomegaHndlr, timePks) }) } return nil, nil } +func checkAssignmentsInContainer(pass *analysis.Pass, ginkgoHndlr ginkgohandler.Handler, exp *ast.CallExpr) bool { + foundSomething := false + if ginkgoHndlr.IsWrapContainer(exp) { + for _, arg := range exp.Args { + if fn, ok := arg.(*ast.FuncLit); ok { + if fn.Body != nil { + if checkAssignments(pass, fn.Body.List) { + foundSomething = true + } + break + } + } + } + } + + return foundSomething +} + +func checkAssignments(pass *analysis.Pass, list []ast.Stmt) bool { + foundSomething := false + for _, stmt := range list { + switch st := stmt.(type) { + case *ast.DeclStmt: + if gen, ok := st.Decl.(*ast.GenDecl); ok { + if gen.Tok != token.VAR { + continue + } + for _, spec := range gen.Specs { + if valSpec, ok := spec.(*ast.ValueSpec); ok { + if checkAssignmentsValues(pass, valSpec.Names, valSpec.Values) { + foundSomething = true + } + } + } + } + + case *ast.AssignStmt: + for i, val := range st.Rhs { + if _, isFunc := val.(*ast.FuncLit); !isFunc { + if id, isIdent := st.Lhs[i].(*ast.Ident); isIdent { + reportNoFix(pass, id.Pos(), useBeforeEachTemplate, id.Name) + foundSomething = true + } + } + } + + case *ast.IfStmt: + if st.Body != nil { + if checkAssignments(pass, st.Body.List) { + foundSomething = true + } + } + if st.Else != nil { + if block, isBlock := st.Else.(*ast.BlockStmt); isBlock { + if checkAssignments(pass, block.List) { + foundSomething = true + } + } + } + } + } + + return foundSomething +} + +func checkAssignmentsValues(pass *analysis.Pass, names []*ast.Ident, values []ast.Expr) bool { + foundSomething := false + for i, val := range values { + if _, isFunc := val.(*ast.FuncLit); !isFunc { + reportNoFix(pass, names[i].Pos(), useBeforeEachTemplate, names[i].Name) + foundSomething = true + } + } + + return foundSomething +} + func checkFocusContainer(pass *analysis.Pass, ginkgoHndlr ginkgohandler.Handler, exp *ast.CallExpr) bool { foundFocus := false isFocus, id := ginkgoHndlr.GetFocusContainerName(exp) @@ -261,7 +285,7 @@ func checkFocusContainer(pass *analysis.Pass, ginkgoHndlr ginkgohandler.Handler, foundFocus = true } - if id != nil && ginkgohandler.IsContainer(id) { + if id != nil && ginkgohandler.IsContainer(id.Name) { for _, arg := range exp.Args { if ginkgoHndlr.IsFocusSpec(arg) { reportNoFix(pass, arg.Pos(), focusSpecFound) @@ -277,21 +301,69 @@ func checkFocusContainer(pass *analysis.Pass, ginkgoHndlr ginkgohandler.Handler, return foundFocus } -func checkExpression(pass *analysis.Pass, config types.Config, assertionExp *ast.CallExpr, actualExpr *ast.CallExpr, handler gomegahandler.Handler) bool { +func checkExpression(pass *analysis.Pass, config types.Config, assertionExp *ast.CallExpr, actualExpr *ast.CallExpr, handler gomegahandler.Handler, timePkg string) bool { expr := astcopy.CallExpr(assertionExp) - oldExpr := goFmt(pass.Fset, expr) - if checkAsyncAssertion(pass, config, expr, actualExpr, handler, oldExpr) { - return true + reportBuilder := reports.NewBuilder(pass.Fset, expr) + + goNested := false + if checkAsyncAssertion(pass, config, expr, actualExpr, handler, reportBuilder, timePkg) { + goNested = true + } else { + + actualArg := getActualArg(actualExpr, handler) + if actualArg == nil { + return true + } + + if config.ForceExpectTo { + goNested = forceExpectTo(expr, handler, reportBuilder) || goNested + } + + goNested = doCheckExpression(pass, config, assertionExp, actualArg, expr, handler, reportBuilder) || goNested } - actualArg := getActualArg(actualExpr, handler) - if actualArg == nil { - return true + if reportBuilder.HasReport() { + reportBuilder.SetFixOffer(pass.Fset, expr) + pass.Report(reportBuilder.Build()) + } + + return goNested +} + +func forceExpectTo(expr *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool { + if asrtFun, ok := expr.Fun.(*ast.SelectorExpr); ok { + if actualFuncName, ok := handler.GetActualFuncName(expr); ok && actualFuncName == expect { + var ( + name string + newIdent *ast.Ident + ) + + switch name = asrtFun.Sel.Name; name { + case "Should": + newIdent = ast.NewIdent("To") + case "ShouldNot": + newIdent = ast.NewIdent("ToNot") + default: + return false + } + + handler.ReplaceFunction(expr, newIdent) + reportBuilder.AddIssue(true, fmt.Sprintf(forceExpectToTemplate, name)) + return true + } } + return false +} + +func doCheckExpression(pass *analysis.Pass, config types.Config, assertionExp *ast.CallExpr, actualArg ast.Expr, expr *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool { if !bool(config.SuppressLen) && isActualIsLenFunc(actualArg) { - return checkLengthMatcher(expr, pass, handler, oldExpr) + return checkLengthMatcher(expr, pass, handler, reportBuilder) + + } else if !bool(config.SuppressLen) && isActualIsCapFunc(actualArg) { + return checkCapMatcher(expr, handler, reportBuilder) + } else if nilable, compOp := getNilableFromComparison(actualArg); nilable != nil { if isExprError(pass, nilable) { if config.SuppressErr { @@ -301,46 +373,53 @@ func checkExpression(pass *analysis.Pass, config types.Config, assertionExp *ast return true } - return checkNilMatcher(expr, pass, nilable, handler, compOp == token.NEQ, oldExpr) + return checkNilMatcher(expr, pass, nilable, handler, compOp == token.NEQ, reportBuilder) } else if first, second, op, ok := isComparison(pass, actualArg); ok { matcher, shouldContinue := startCheckComparison(expr, handler) if !shouldContinue { return false } - if !bool(config.SuppressLen) && isActualIsLenFunc(first) { - if handleLenComparison(pass, expr, matcher, first, second, op, handler, oldExpr) { - return false + if !config.SuppressLen { + if isActualIsLenFunc(first) { + if handleLenComparison(pass, expr, matcher, first, second, op, handler, reportBuilder) { + return false + } + } + if isActualIsCapFunc(first) { + if handleCapComparison(expr, matcher, first, second, op, handler, reportBuilder) { + return false + } } } - return bool(config.SuppressCompare) || checkComparison(expr, pass, matcher, handler, first, second, op, oldExpr) + return bool(config.SuppressCompare) || checkComparison(expr, pass, matcher, handler, first, second, op, reportBuilder) - } else if checkMatchError(pass, assertionExp, actualArg, handler, oldExpr) { + } else if checkMatchError(pass, assertionExp, actualArg, handler, reportBuilder) { return false } else if isExprError(pass, actualArg) { - return bool(config.SuppressErr) || checkNilError(pass, expr, handler, actualArg, oldExpr) + return bool(config.SuppressErr) || checkNilError(pass, expr, handler, actualArg, reportBuilder) - } else if checkPointerComparison(pass, config, assertionExp, expr, actualArg, handler, oldExpr) { + } else if checkPointerComparison(pass, config, assertionExp, expr, actualArg, handler, reportBuilder) { return false - } else if !handleAssertionOnly(pass, config, expr, handler, actualArg, oldExpr, true) { + } else if !handleAssertionOnly(pass, config, expr, handler, actualArg, reportBuilder) { return false } else if !config.SuppressTypeCompare { - return !checkEqualWrongType(pass, assertionExp, actualArg, handler, oldExpr) + return !checkEqualWrongType(pass, assertionExp, actualArg, handler, reportBuilder) } return true } -func checkMatchError(pass *analysis.Pass, origExp *ast.CallExpr, actualArg ast.Expr, handler gomegahandler.Handler, oldExpr string) bool { +func checkMatchError(pass *analysis.Pass, origExp *ast.CallExpr, actualArg ast.Expr, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool { matcher, ok := origExp.Args[0].(*ast.CallExpr) if !ok { return false } - return doCheckMatchError(pass, origExp, matcher, actualArg, handler, oldExpr) + return doCheckMatchError(pass, origExp, matcher, actualArg, handler, reportBuilder) } -func doCheckMatchError(pass *analysis.Pass, origExp *ast.CallExpr, matcher *ast.CallExpr, actualArg ast.Expr, handler gomegahandler.Handler, oldExpr string) bool { +func doCheckMatchError(pass *analysis.Pass, origExp *ast.CallExpr, matcher *ast.CallExpr, actualArg ast.Expr, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool { name, ok := handler.GetActualFuncName(matcher) if !ok { return false @@ -353,13 +432,13 @@ func doCheckMatchError(pass *analysis.Pass, origExp *ast.CallExpr, matcher *ast. return false } - return doCheckMatchError(pass, origExp, nested, actualArg, handler, oldExpr) + return doCheckMatchError(pass, origExp, nested, actualArg, handler, reportBuilder) case and, or: - res := true + res := false for _, arg := range matcher.Args { if nested, ok := arg.(*ast.CallExpr); ok { - if !doCheckMatchError(pass, origExp, nested, actualArg, handler, oldExpr) { - res = false + if valid := doCheckMatchError(pass, origExp, nested, actualArg, handler, reportBuilder); valid { + res = true } } } @@ -369,15 +448,14 @@ func doCheckMatchError(pass *analysis.Pass, origExp *ast.CallExpr, matcher *ast. } if !isExprError(pass, actualArg) { - reportNoFix(pass, origExp.Pos(), matchErrorArgWrongType, goFmt(pass.Fset, actualArg)) + reportBuilder.AddIssue(false, 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 + reportBuilder.AddIssue(false, matchErrorWrongTypeAssertion, goFmt(pass.Fset, matcher.Args[0])) } numParams := len(matcher.Args) @@ -385,16 +463,16 @@ func doCheckMatchError(pass *analysis.Pass, origExp *ast.CallExpr, matcher *ast. 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 + reportBuilder.AddIssue(false, matchErrorNoFuncDescription) + return true } } return true } if requiredParams == 2 && numParams == 1 { - reportNoFix(pass, expr.Pos(), matchErrorMissingDescription) - return false + reportBuilder.AddIssue(false, matchErrorMissingDescription) + return true } var newArgsSuggestion = []ast.Expr{expr.Args[0]} @@ -402,8 +480,9 @@ func doCheckMatchError(pass *analysis.Pass, origExp *ast.CallExpr, matcher *ast. newArgsSuggestion = append(newArgsSuggestion, expr.Args[1]) } expr.Args = newArgsSuggestion - report(pass, expr, matchErrorRedundantArg, oldExpr) - return false + + reportBuilder.AddIssue(true, matchErrorRedundantArg) + return true } func checkMatchErrorAssertion(pass *analysis.Pass, matcher *ast.CallExpr) (bool, int) { @@ -455,16 +534,16 @@ func isErrorMatcherValidArg(pass *analysis.Pass, arg ast.Expr) bool { return interfaces.ImplementsGomegaMatcher(t) } -func checkEqualWrongType(pass *analysis.Pass, origExp *ast.CallExpr, actualArg ast.Expr, handler gomegahandler.Handler, old string) bool { +func checkEqualWrongType(pass *analysis.Pass, origExp *ast.CallExpr, actualArg ast.Expr, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool { matcher, ok := origExp.Args[0].(*ast.CallExpr) if !ok { return false } - return checkEqualDifferentTypes(pass, matcher, actualArg, handler, old, false) + return checkEqualDifferentTypes(pass, matcher, actualArg, handler, false, reportBuilder) } -func checkEqualDifferentTypes(pass *analysis.Pass, matcher *ast.CallExpr, actualArg ast.Expr, handler gomegahandler.Handler, old string, parentPointer bool) bool { +func checkEqualDifferentTypes(pass *analysis.Pass, matcher *ast.CallExpr, actualArg ast.Expr, handler gomegahandler.Handler, parentPointer bool, reportBuilder *reports.Builder) bool { matcherFuncName, ok := handler.GetActualFuncName(matcher) if !ok { return false @@ -481,7 +560,7 @@ func checkEqualDifferentTypes(pass *analysis.Pass, matcher *ast.CallExpr, actual if !ok { continue } - if checkEqualDifferentTypes(pass, nested, actualArg, handler, old, parentPointer) { + if checkEqualDifferentTypes(pass, nested, actualArg, handler, parentPointer, reportBuilder) { foundIssue = true } } @@ -497,7 +576,7 @@ func checkEqualDifferentTypes(pass *analysis.Pass, matcher *ast.CallExpr, actual switch matcherFuncName { case equal, beIdenticalTo: case not: - return checkEqualDifferentTypes(pass, nested, actualArg, handler, old, parentPointer) + return checkEqualDifferentTypes(pass, nested, actualArg, handler, parentPointer, reportBuilder) default: return false } @@ -510,7 +589,7 @@ func checkEqualDifferentTypes(pass *analysis.Pass, matcher *ast.CallExpr, actual return false } } else { - return checkEqualDifferentTypes(pass, nested, actualArg, handler, old, parentPointer) + return checkEqualDifferentTypes(pass, nested, actualArg, handler, parentPointer, reportBuilder) } case not: @@ -519,7 +598,7 @@ func checkEqualDifferentTypes(pass *analysis.Pass, matcher *ast.CallExpr, actual return false } - return checkEqualDifferentTypes(pass, nested, actualArg, handler, old, parentPointer) + return checkEqualDifferentTypes(pass, nested, actualArg, handler, parentPointer, reportBuilder) case haveValue: nested, ok := matcher.Args[0].(*ast.CallExpr) @@ -527,7 +606,7 @@ func checkEqualDifferentTypes(pass *analysis.Pass, matcher *ast.CallExpr, actual return false } - return checkEqualDifferentTypes(pass, nested, actualArg, handler, old, true) + return checkEqualDifferentTypes(pass, nested, actualArg, handler, true, reportBuilder) default: return false } @@ -551,7 +630,7 @@ func checkEqualDifferentTypes(pass *analysis.Pass, matcher *ast.CallExpr, actual return false } - reportNoFix(pass, matcher.Pos(), compareDifferentTypes, matcherFuncName, actualType, matcherType) + reportBuilder.AddIssue(false, compareDifferentTypes, matcherFuncName, actualType, matcherType) return true } @@ -596,7 +675,7 @@ func isImplementing(ifs, impl gotypes.Type) bool { } // be careful - never change origExp!!! only modify its clone, expr!!! -func checkPointerComparison(pass *analysis.Pass, config types.Config, origExp *ast.CallExpr, expr *ast.CallExpr, actualArg ast.Expr, handler gomegahandler.Handler, oldExpr string) bool { +func checkPointerComparison(pass *analysis.Pass, config types.Config, origExp *ast.CallExpr, expr *ast.CallExpr, actualArg ast.Expr, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool { if !isPointer(pass, actualArg) { return false } @@ -643,20 +722,19 @@ func checkPointerComparison(pass *analysis.Pass, config types.Config, origExp *a return false } - handleAssertionOnly(pass, config, expr, handler, actualArg, oldExpr, false) + handleAssertionOnly(pass, config, expr, handler, actualArg, reportBuilder) args := []ast.Expr{astcopy.CallExpr(expr.Args[0].(*ast.CallExpr))} handler.ReplaceFunction(expr.Args[0].(*ast.CallExpr), ast.NewIdent(haveValue)) expr.Args[0].(*ast.CallExpr).Args = args - report(pass, expr, comparePointerToValue, oldExpr) + reportBuilder.AddIssue(true, comparePointerToValue) return true - } // check async assertion does not assert function call. This is a real bug in the test. In this case, the assertion is // done on the returned value, instead of polling the result of a function, for instance. -func checkAsyncAssertion(pass *analysis.Pass, config types.Config, expr *ast.CallExpr, actualExpr *ast.CallExpr, handler gomegahandler.Handler, oldExpr string) bool { +func checkAsyncAssertion(pass *analysis.Pass, config types.Config, expr *ast.CallExpr, actualExpr *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder, timePkg string) bool { funcName, ok := handler.GetActualFuncName(actualExpr) if !ok { return false @@ -702,19 +780,22 @@ func checkAsyncAssertion(pass *analysis.Pass, config types.Config, expr *ast.Cal actualExpr.Fun = call actualExpr.Args = fun.Args + actualExpr = actualExpr.Fun.(*ast.SelectorExpr).X.(*ast.CallExpr) } else { actualExpr.Args[funcIndex] = fun.Fun } - handleAssertionOnly(pass, config, expr, handler, actualExpr, oldExpr, false) - report(pass, expr, fmt.Sprintf(valueInEventually, funcName, funcName)+"; consider using `%s` instead", oldExpr) - return true + reportBuilder.AddIssue(true, valueInEventually, funcName, funcName) } } } + + if config.ValidateAsyncIntervals { + intervals.CheckIntervals(pass, expr, actualExpr, reportBuilder, handler, timePkg, funcIndex) + } } - handleAssertionOnly(pass, config, expr, handler, actualExpr, oldExpr, true) + handleAssertionOnly(pass, config, expr, handler, actualExpr, reportBuilder) return true } @@ -769,7 +850,7 @@ func startCheckComparison(exp *ast.CallExpr, handler gomegahandler.Handler) (*as return matcher, true } -func checkComparison(exp *ast.CallExpr, pass *analysis.Pass, matcher *ast.CallExpr, handler gomegahandler.Handler, first ast.Expr, second ast.Expr, op token.Token, oldExp string) bool { +func checkComparison(exp *ast.CallExpr, pass *analysis.Pass, matcher *ast.CallExpr, handler gomegahandler.Handler, first ast.Expr, second ast.Expr, op token.Token, reportBuilder *reports.Builder) bool { fun, ok := exp.Fun.(*ast.SelectorExpr) if !ok { return true @@ -801,7 +882,7 @@ func checkComparison(exp *ast.CallExpr, pass *analysis.Pass, matcher *ast.CallEx } call.Args = []ast.Expr{first} - report(pass, exp, wrongCompareWarningTemplate, oldExp) + reportBuilder.AddIssue(true, wrongCompareWarningTemplate) return false } @@ -823,7 +904,7 @@ func handleEqualComparison(pass *analysis.Pass, matcher *ast.CallExpr, first ast } } -func handleLenComparison(pass *analysis.Pass, exp *ast.CallExpr, matcher *ast.CallExpr, first ast.Expr, second ast.Expr, op token.Token, handler gomegahandler.Handler, oldExpr string) bool { +func handleLenComparison(pass *analysis.Pass, exp *ast.CallExpr, matcher *ast.CallExpr, first ast.Expr, second ast.Expr, op token.Token, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool { switch op { case token.EQL: case token.NEQ: @@ -850,23 +931,92 @@ func handleLenComparison(pass *analysis.Pass, exp *ast.CallExpr, matcher *ast.Ca fun := handler.GetActualExpr(exp.Fun.(*ast.SelectorExpr)) fun.Args = []ast.Expr{val} - report(pass, exp, wrongLengthWarningTemplate, oldExpr) + reportBuilder.AddIssue(true, wrongLengthWarningTemplate) + return true +} + +func handleCapComparison(exp *ast.CallExpr, matcher *ast.CallExpr, first ast.Expr, second ast.Expr, op token.Token, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool { + switch op { + case token.EQL: + case token.NEQ: + reverseAssertionFuncLogic(exp) + default: + return false + } + + eql := ast.NewIdent(haveCap) + matcher.Args = []ast.Expr{second} + + handler.ReplaceFunction(matcher, eql) + firstLen, ok := first.(*ast.CallExpr) // assuming it's len() + if !ok { + return false // should never happen + } + + val := firstLen.Args[0] + fun := handler.GetActualExpr(exp.Fun.(*ast.SelectorExpr)) + fun.Args = []ast.Expr{val} + + reportBuilder.AddIssue(true, wrongCapWarningTemplate) return true } // Check if the "actual" argument is a call to the golang built-in len() function func isActualIsLenFunc(actualArg ast.Expr) bool { + return checkActualFuncName(actualArg, "len") +} + +// Check if the "actual" argument is a call to the golang built-in len() function +func isActualIsCapFunc(actualArg ast.Expr) bool { + return checkActualFuncName(actualArg, "cap") +} + +func checkActualFuncName(actualArg ast.Expr, name string) bool { lenArgExp, ok := actualArg.(*ast.CallExpr) if !ok { return false } lenFunc, ok := lenArgExp.Fun.(*ast.Ident) - return ok && lenFunc.Name == "len" + return ok && lenFunc.Name == name +} + +// Check if matcher function is in one of the patterns we want to avoid +func checkLengthMatcher(exp *ast.CallExpr, pass *analysis.Pass, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool { + matcher, ok := exp.Args[0].(*ast.CallExpr) + if !ok { + return true + } + + matcherFuncName, ok := handler.GetActualFuncName(matcher) + if !ok { + return true + } + + switch matcherFuncName { + case equal: + handleEqualLenMatcher(matcher, pass, exp, handler, reportBuilder) + return false + + case beZero: + handleBeZero(exp, handler, reportBuilder) + return false + + case beNumerically: + return handleBeNumerically(matcher, pass, exp, handler, reportBuilder) + + case not: + reverseAssertionFuncLogic(exp) + exp.Args[0] = exp.Args[0].(*ast.CallExpr).Args[0] + return checkLengthMatcher(exp, pass, handler, reportBuilder) + + default: + return true + } } // Check if matcher function is in one of the patterns we want to avoid -func checkLengthMatcher(exp *ast.CallExpr, pass *analysis.Pass, handler gomegahandler.Handler, oldExp string) bool { +func checkCapMatcher(exp *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool { matcher, ok := exp.Args[0].(*ast.CallExpr) if !ok { return true @@ -879,20 +1029,20 @@ func checkLengthMatcher(exp *ast.CallExpr, pass *analysis.Pass, handler gomegaha switch matcherFuncName { case equal: - handleEqualMatcher(matcher, pass, exp, handler, oldExp) + handleEqualCapMatcher(matcher, exp, handler, reportBuilder) return false case beZero: - handleBeZero(pass, exp, handler, oldExp) + handleCapBeZero(exp, handler, reportBuilder) return false case beNumerically: - return handleBeNumerically(matcher, pass, exp, handler, oldExp) + return handleCapBeNumerically(matcher, exp, handler, reportBuilder) case not: reverseAssertionFuncLogic(exp) exp.Args[0] = exp.Args[0].(*ast.CallExpr).Args[0] - return checkLengthMatcher(exp, pass, handler, oldExp) + return checkCapMatcher(exp, handler, reportBuilder) default: return true @@ -900,7 +1050,7 @@ func checkLengthMatcher(exp *ast.CallExpr, pass *analysis.Pass, handler gomegaha } // Check if matcher function is in one of the patterns we want to avoid -func checkNilMatcher(exp *ast.CallExpr, pass *analysis.Pass, nilable ast.Expr, handler gomegahandler.Handler, notEqual bool, oldExp string) bool { +func checkNilMatcher(exp *ast.CallExpr, pass *analysis.Pass, nilable ast.Expr, handler gomegahandler.Handler, notEqual bool, reportBuilder *reports.Builder) bool { matcher, ok := exp.Args[0].(*ast.CallExpr) if !ok { return true @@ -913,19 +1063,19 @@ func checkNilMatcher(exp *ast.CallExpr, pass *analysis.Pass, nilable ast.Expr, h switch matcherFuncName { case equal: - handleEqualNilMatcher(matcher, pass, exp, handler, nilable, notEqual, oldExp) + handleEqualNilMatcher(matcher, pass, exp, handler, nilable, notEqual, reportBuilder) case beTrue: - handleNilBeBoolMatcher(pass, exp, handler, nilable, notEqual, oldExp) + handleNilBeBoolMatcher(pass, exp, handler, nilable, notEqual, reportBuilder) case beFalse: reverseAssertionFuncLogic(exp) - handleNilBeBoolMatcher(pass, exp, handler, nilable, notEqual, oldExp) + handleNilBeBoolMatcher(pass, exp, handler, nilable, notEqual, reportBuilder) case not: reverseAssertionFuncLogic(exp) exp.Args[0] = exp.Args[0].(*ast.CallExpr).Args[0] - return checkNilMatcher(exp, pass, nilable, handler, notEqual, oldExp) + return checkNilMatcher(exp, pass, nilable, handler, notEqual, reportBuilder) default: return true @@ -933,7 +1083,7 @@ func checkNilMatcher(exp *ast.CallExpr, pass *analysis.Pass, nilable ast.Expr, h return false } -func checkNilError(pass *analysis.Pass, assertionExp *ast.CallExpr, handler gomegahandler.Handler, actualArg ast.Expr, oldExpr string) bool { +func checkNilError(pass *analysis.Pass, assertionExp *ast.CallExpr, handler gomegahandler.Handler, actualArg ast.Expr, reportBuilder *reports.Builder) bool { if len(assertionExp.Args) == 0 { return true } @@ -964,7 +1114,7 @@ func checkNilError(pass *analysis.Pass, assertionExp *ast.CallExpr, handler gome case not: reverseAssertionFuncLogic(assertionExp) assertionExp.Args[0] = assertionExp.Args[0].(*ast.CallExpr).Args[0] - return checkNilError(pass, assertionExp, handler, actualArg, oldExpr) + return checkNilError(pass, assertionExp, handler, actualArg, reportBuilder) default: return true } @@ -980,7 +1130,7 @@ func checkNilError(pass *analysis.Pass, assertionExp *ast.CallExpr, handler gome handler.ReplaceFunction(equalFuncExpr, ast.NewIdent(newFuncName)) equalFuncExpr.Args = nil - report(pass, assertionExp, wrongErrWarningTemplate, oldExpr) + reportBuilder.AddIssue(true, wrongErrWarningTemplate) return false } @@ -991,7 +1141,7 @@ func checkNilError(pass *analysis.Pass, assertionExp *ast.CallExpr, handler gome // Equal(true) => BeTrue() // Equal(false) => BeFalse() // HaveLen(0) => BeEmpty() -func handleAssertionOnly(pass *analysis.Pass, config types.Config, expr *ast.CallExpr, handler gomegahandler.Handler, actualArg ast.Expr, oldExpr string, shouldReport bool) bool { +func handleAssertionOnly(pass *analysis.Pass, config types.Config, expr *ast.CallExpr, handler gomegahandler.Handler, actualArg ast.Expr, reportBuilder *reports.Builder) bool { if len(expr.Args) == 0 { return true } @@ -1044,19 +1194,15 @@ func handleAssertionOnly(pass *analysis.Pass, config types.Config, expr *ast.Cal handler.ReplaceFunction(equalFuncExpr, ast.NewIdent(replacement)) equalFuncExpr.Args = nil - if shouldReport { - report(pass, expr, template, oldExpr) - } - + reportBuilder.AddIssue(true, template) return false case beFalse: if isNegativeAssertion(expr) { reverseAssertionFuncLogic(expr) handler.ReplaceFunction(equalFuncExpr, ast.NewIdent(beTrue)) - if shouldReport { - report(pass, expr, doubleNegativeWarningTemplate, oldExpr) - } + reportBuilder.AddIssue(true, doubleNegativeWarningTemplate) + return false } return false @@ -1069,9 +1215,7 @@ func handleAssertionOnly(pass *analysis.Pass, config types.Config, expr *ast.Cal if isZero(pass, equalFuncExpr.Args[0]) { handler.ReplaceFunction(equalFuncExpr, ast.NewIdent(beEmpty)) equalFuncExpr.Args = nil - if shouldReport { - report(pass, expr, wrongLengthWarningTemplate, oldExpr) - } + reportBuilder.AddIssue(true, wrongLengthWarningTemplate) return false } } @@ -1081,7 +1225,7 @@ func handleAssertionOnly(pass *analysis.Pass, config types.Config, expr *ast.Cal case not: reverseAssertionFuncLogic(expr) expr.Args[0] = expr.Args[0].(*ast.CallExpr).Args[0] - return handleAssertionOnly(pass, config, expr, handler, actualArg, oldExpr, shouldReport) + return handleAssertionOnly(pass, config, expr, handler, actualArg, reportBuilder) default: return true } @@ -1139,13 +1283,13 @@ func replaceLenActualArg(actualExpr *ast.CallExpr, handler gomegahandler.Handler switch name { case expect, omega: arg := actualExpr.Args[0] - if isActualIsLenFunc(arg) { + if isActualIsLenFunc(arg) || isActualIsCapFunc(arg) { // replace the len function call by its parameter, to create a fix suggestion actualExpr.Args[0] = arg.(*ast.CallExpr).Args[0] } case expectWithOffset: arg := actualExpr.Args[1] - if isActualIsLenFunc(arg) { + if isActualIsLenFunc(arg) || isActualIsCapFunc(arg) { // replace the len function call by its parameter, to create a fix suggestion actualExpr.Args[1] = arg.(*ast.CallExpr).Args[0] } @@ -1174,7 +1318,7 @@ func replaceNilActualArg(actualExpr *ast.CallExpr, handler gomegahandler.Handler } // For the BeNumerically matcher, we want to avoid the assertion of length to be > 0 or >= 1, or just == number -func handleBeNumerically(matcher *ast.CallExpr, pass *analysis.Pass, exp *ast.CallExpr, handler gomegahandler.Handler, oldExp string) bool { +func handleBeNumerically(matcher *ast.CallExpr, pass *analysis.Pass, exp *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool { opExp, ok1 := matcher.Args[0].(*ast.BasicLit) valExp, ok2 := matcher.Args[1].(*ast.BasicLit) @@ -1186,20 +1330,45 @@ func handleBeNumerically(matcher *ast.CallExpr, pass *analysis.Pass, exp *ast.Ca reverseAssertionFuncLogic(exp) handler.ReplaceFunction(exp.Args[0].(*ast.CallExpr), ast.NewIdent(beEmpty)) exp.Args[0].(*ast.CallExpr).Args = nil - reportLengthAssertion(pass, exp, handler, oldExp) - return false } else if op == `"=="` { chooseNumericMatcher(pass, exp, handler, valExp) - reportLengthAssertion(pass, exp, handler, oldExp) - - return false } else if op == `"!="` { reverseAssertionFuncLogic(exp) chooseNumericMatcher(pass, exp, handler, valExp) - reportLengthAssertion(pass, exp, handler, oldExp) + } else { + return true + } - return false + reportLengthAssertion(exp, handler, reportBuilder) + return false + } + return true +} + +// For the BeNumerically matcher, we want to avoid the assertion of length to be > 0 or >= 1, or just == number +func handleCapBeNumerically(matcher *ast.CallExpr, exp *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder) bool { + opExp, ok1 := matcher.Args[0].(*ast.BasicLit) + valExp, ok2 := matcher.Args[1].(*ast.BasicLit) + + if ok1 && ok2 { + op := opExp.Value + val := valExp.Value + + if (op == `">"` && val == "0") || (op == `">="` && val == "1") { + reverseAssertionFuncLogic(exp) + handler.ReplaceFunction(exp.Args[0].(*ast.CallExpr), ast.NewIdent(haveCap)) + exp.Args[0].(*ast.CallExpr).Args = []ast.Expr{&ast.BasicLit{Kind: token.INT, Value: "0"}} + } else if op == `"=="` { + replaceNumericCapMatcher(exp, handler, valExp) + } else if op == `"!="` { + reverseAssertionFuncLogic(exp) + replaceNumericCapMatcher(exp, handler, valExp) + } else { + return true } + + reportCapAssertion(exp, handler, reportBuilder) + return false } return true } @@ -1215,6 +1384,12 @@ func chooseNumericMatcher(pass *analysis.Pass, exp *ast.CallExpr, handler gomega } } +func replaceNumericCapMatcher(exp *ast.CallExpr, handler gomegahandler.Handler, valExp ast.Expr) { + caller := exp.Args[0].(*ast.CallExpr) + handler.ReplaceFunction(caller, ast.NewIdent(haveCap)) + exp.Args[0].(*ast.CallExpr).Args = []ast.Expr{valExp} +} + func reverseAssertionFuncLogic(exp *ast.CallExpr) { assertionFunc := exp.Fun.(*ast.SelectorExpr).Sel assertionFunc.Name = reverseassertion.ChangeAssertionLogic(assertionFunc.Name) @@ -1225,7 +1400,7 @@ func isNegativeAssertion(exp *ast.CallExpr) bool { return reverseassertion.IsNegativeLogic(assertionFunc.Name) } -func handleEqualMatcher(matcher *ast.CallExpr, pass *analysis.Pass, exp *ast.CallExpr, handler gomegahandler.Handler, oldExp string) { +func handleEqualLenMatcher(matcher *ast.CallExpr, pass *analysis.Pass, exp *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder) { equalTo, ok := matcher.Args[0].(*ast.BasicLit) if ok { chooseNumericMatcher(pass, exp, handler, equalTo) @@ -1233,16 +1408,29 @@ func handleEqualMatcher(matcher *ast.CallExpr, pass *analysis.Pass, exp *ast.Cal handler.ReplaceFunction(exp.Args[0].(*ast.CallExpr), ast.NewIdent(haveLen)) exp.Args[0].(*ast.CallExpr).Args = []ast.Expr{matcher.Args[0]} } - reportLengthAssertion(pass, exp, handler, oldExp) + reportLengthAssertion(exp, handler, reportBuilder) } -func handleBeZero(pass *analysis.Pass, exp *ast.CallExpr, handler gomegahandler.Handler, oldExp string) { +func handleEqualCapMatcher(matcher *ast.CallExpr, exp *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder) { + handler.ReplaceFunction(exp.Args[0].(*ast.CallExpr), ast.NewIdent(haveCap)) + exp.Args[0].(*ast.CallExpr).Args = []ast.Expr{matcher.Args[0]} + reportCapAssertion(exp, handler, reportBuilder) +} + +func handleBeZero(exp *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder) { exp.Args[0].(*ast.CallExpr).Args = nil handler.ReplaceFunction(exp.Args[0].(*ast.CallExpr), ast.NewIdent(beEmpty)) - reportLengthAssertion(pass, exp, handler, oldExp) + reportLengthAssertion(exp, handler, reportBuilder) } -func handleEqualNilMatcher(matcher *ast.CallExpr, pass *analysis.Pass, exp *ast.CallExpr, handler gomegahandler.Handler, nilable ast.Expr, notEqual bool, oldExp string) { +func handleCapBeZero(exp *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder) { + exp.Args[0].(*ast.CallExpr).Args = nil + handler.ReplaceFunction(exp.Args[0].(*ast.CallExpr), ast.NewIdent(haveCap)) + exp.Args[0].(*ast.CallExpr).Args = []ast.Expr{&ast.BasicLit{Kind: token.INT, Value: "0"}} + reportCapAssertion(exp, handler, reportBuilder) +} + +func handleEqualNilMatcher(matcher *ast.CallExpr, pass *analysis.Pass, exp *ast.CallExpr, handler gomegahandler.Handler, nilable ast.Expr, notEqual bool, reportBuilder *reports.Builder) { equalTo, ok := matcher.Args[0].(*ast.Ident) if !ok { return @@ -1259,15 +1447,15 @@ func handleEqualNilMatcher(matcher *ast.CallExpr, pass *analysis.Pass, exp *ast. handler.ReplaceFunction(exp.Args[0].(*ast.CallExpr), ast.NewIdent(newFuncName)) exp.Args[0].(*ast.CallExpr).Args = nil - reportNilAssertion(pass, exp, handler, nilable, notEqual, oldExp, isItError) + reportNilAssertion(exp, handler, nilable, notEqual, isItError, reportBuilder) } -func handleNilBeBoolMatcher(pass *analysis.Pass, exp *ast.CallExpr, handler gomegahandler.Handler, nilable ast.Expr, notEqual bool, oldExp string) { +func handleNilBeBoolMatcher(pass *analysis.Pass, exp *ast.CallExpr, handler gomegahandler.Handler, nilable ast.Expr, notEqual bool, reportBuilder *reports.Builder) { newFuncName, isItError := handleNilComparisonErr(pass, exp, nilable) handler.ReplaceFunction(exp.Args[0].(*ast.CallExpr), ast.NewIdent(newFuncName)) exp.Args[0].(*ast.CallExpr).Args = nil - reportNilAssertion(pass, exp, handler, nilable, notEqual, oldExp, isItError) + reportNilAssertion(exp, handler, nilable, notEqual, isItError, reportBuilder) } func handleNilComparisonErr(pass *analysis.Pass, exp *ast.CallExpr, nilable ast.Expr) (string, bool) { @@ -1293,14 +1481,21 @@ func isAssertionFunc(name string) bool { return false } -func reportLengthAssertion(pass *analysis.Pass, expr *ast.CallExpr, handler gomegahandler.Handler, oldExpr string) { +func reportLengthAssertion(expr *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder) { + actualExpr := handler.GetActualExpr(expr.Fun.(*ast.SelectorExpr)) + replaceLenActualArg(actualExpr, handler) + + reportBuilder.AddIssue(true, wrongLengthWarningTemplate) +} + +func reportCapAssertion(expr *ast.CallExpr, handler gomegahandler.Handler, reportBuilder *reports.Builder) { actualExpr := handler.GetActualExpr(expr.Fun.(*ast.SelectorExpr)) replaceLenActualArg(actualExpr, handler) - report(pass, expr, wrongLengthWarningTemplate, oldExpr) + reportBuilder.AddIssue(true, wrongCapWarningTemplate) } -func reportNilAssertion(pass *analysis.Pass, expr *ast.CallExpr, handler gomegahandler.Handler, nilable ast.Expr, notEqual bool, oldExpr string, isItError bool) { +func reportNilAssertion(expr *ast.CallExpr, handler gomegahandler.Handler, nilable ast.Expr, notEqual bool, isItError bool, reportBuilder *reports.Builder) { actualExpr := handler.GetActualExpr(expr.Fun.(*ast.SelectorExpr)) changed := replaceNilActualArg(actualExpr, handler, nilable) if !changed { @@ -1315,27 +1510,7 @@ func reportNilAssertion(pass *analysis.Pass, expr *ast.CallExpr, handler gomegah template = wrongErrWarningTemplate } - report(pass, expr, template, oldExpr) -} - -func report(pass *analysis.Pass, expr ast.Expr, messageTemplate, oldExpr string) { - newExp := goFmt(pass.Fset, expr) - pass.Report(analysis.Diagnostic{ - Pos: expr.Pos(), - Message: fmt.Sprintf(messageTemplate, newExp), - SuggestedFixes: []analysis.SuggestedFix{ - { - Message: fmt.Sprintf("should replace %s with %s", oldExpr, newExp), - TextEdits: []analysis.TextEdit{ - { - Pos: expr.Pos(), - End: expr.End(), - NewText: []byte(newExp), - }, - }, - }, - }, - }) + reportBuilder.AddIssue(true, template) } func reportNewName(pass *analysis.Pass, id *ast.Ident, newName string, messageTemplate, oldExpr string) { @@ -1478,22 +1653,17 @@ func isNumeric(pass *analysis.Pass, node ast.Expr) bool { func checkNoAssertion(pass *analysis.Pass, expr *ast.CallExpr, handler gomegahandler.Handler) { funcName, ok := handler.GetActualFuncName(expr) if ok { - if isActualFunc(funcName) { - reportNoFix(pass, expr.Pos(), missingAssertionMessage, funcName) - } else if isActualAsyncFunc(funcName) { - reportNoFix(pass, expr.Pos(), missingAsyncAssertionMessage, funcName) + var allowedFunction string + switch funcName { + case expect, expectWithOffset: + allowedFunction = `"To()", "ToNot()" or "NotTo()"` + case eventually, eventuallyWithOffset, consistently, consistentlyWithOffset: + allowedFunction = `"Should()" or "ShouldNot()"` + case omega: + allowedFunction = `"Should()", "To()", "ShouldNot()", "ToNot()" or "NotTo()"` + default: + return } + reportNoFix(pass, expr.Pos(), missingAssertionMessage, funcName, allowedFunction) } } - -func isActualFunc(name string) bool { - return name == expect || name == expectWithOffset -} - -func isActualAsyncFunc(name string) bool { - switch name { - case eventually, eventuallyWithOffset, consistently, consistentlyWithOffset: - return true - } - return false -} diff --git a/vendor/github.com/nunnatsa/ginkgolinter/types/config.go b/vendor/github.com/nunnatsa/ginkgolinter/types/config.go index 6de22738dd..b6838e5244 100644 --- a/vendor/github.com/nunnatsa/ginkgolinter/types/config.go +++ b/vendor/github.com/nunnatsa/ginkgolinter/types/config.go @@ -1,9 +1,8 @@ package types import ( - "strings" - "go/ast" + "strings" ) const ( @@ -18,14 +17,17 @@ const ( ) type Config struct { - SuppressLen Boolean - SuppressNil Boolean - SuppressErr Boolean - SuppressCompare Boolean - SuppressAsync Boolean - ForbidFocus Boolean - SuppressTypeCompare Boolean - AllowHaveLen0 Boolean + SuppressLen Boolean + SuppressNil Boolean + SuppressErr Boolean + SuppressCompare Boolean + SuppressAsync Boolean + ForbidFocus Boolean + SuppressTypeCompare Boolean + AllowHaveLen0 Boolean + ForceExpectTo Boolean + ValidateAsyncIntervals Boolean + ForbidSpecPollution Boolean } func (s *Config) AllTrue() bool { @@ -34,14 +36,17 @@ func (s *Config) AllTrue() bool { func (s *Config) Clone() Config { return Config{ - SuppressLen: s.SuppressLen, - SuppressNil: s.SuppressNil, - SuppressErr: s.SuppressErr, - SuppressCompare: s.SuppressCompare, - SuppressAsync: s.SuppressAsync, - ForbidFocus: s.ForbidFocus, - SuppressTypeCompare: s.SuppressTypeCompare, - AllowHaveLen0: s.AllowHaveLen0, + SuppressLen: s.SuppressLen, + SuppressNil: s.SuppressNil, + SuppressErr: s.SuppressErr, + SuppressCompare: s.SuppressCompare, + SuppressAsync: s.SuppressAsync, + ForbidFocus: s.ForbidFocus, + SuppressTypeCompare: s.SuppressTypeCompare, + AllowHaveLen0: s.AllowHaveLen0, + ForceExpectTo: s.ForceExpectTo, + ValidateAsyncIntervals: s.ValidateAsyncIntervals, + ForbidSpecPollution: s.ForbidSpecPollution, } } diff --git a/vendor/github.com/pelletier/go-toml/v2/.gitignore b/vendor/github.com/pelletier/go-toml/v2/.gitignore index a69e2b0ebd..4b7c4eda3a 100644 --- a/vendor/github.com/pelletier/go-toml/v2/.gitignore +++ b/vendor/github.com/pelletier/go-toml/v2/.gitignore @@ -3,4 +3,5 @@ fuzz/ cmd/tomll/tomll cmd/tomljson/tomljson cmd/tomltestgen/tomltestgen -dist \ No newline at end of file +dist +tests/ diff --git a/vendor/github.com/pelletier/go-toml/v2/.goreleaser.yaml b/vendor/github.com/pelletier/go-toml/v2/.goreleaser.yaml index 3aa1840ec4..1d8b69e65e 100644 --- a/vendor/github.com/pelletier/go-toml/v2/.goreleaser.yaml +++ b/vendor/github.com/pelletier/go-toml/v2/.goreleaser.yaml @@ -18,6 +18,7 @@ builds: - linux_amd64 - linux_arm64 - linux_arm + - linux_riscv64 - windows_amd64 - windows_arm64 - windows_arm @@ -37,6 +38,7 @@ builds: - linux_amd64 - linux_arm64 - linux_arm + - linux_riscv64 - windows_amd64 - windows_arm64 - windows_arm @@ -55,6 +57,7 @@ builds: targets: - linux_amd64 - linux_arm64 + - linux_riscv64 - linux_arm - windows_amd64 - windows_arm64 diff --git a/vendor/github.com/pelletier/go-toml/v2/CONTRIBUTING.md b/vendor/github.com/pelletier/go-toml/v2/CONTRIBUTING.md index 04dd12bcbc..96ecf9e2b3 100644 --- a/vendor/github.com/pelletier/go-toml/v2/CONTRIBUTING.md +++ b/vendor/github.com/pelletier/go-toml/v2/CONTRIBUTING.md @@ -165,25 +165,22 @@ Checklist: ### New release -1. Decide on the next version number. Use semver. -2. Generate release notes using [`gh`][gh]. Example: +1. Decide on the next version number. Use semver. Review commits since last + version to assess. +2. Tag release. For example: ``` -$ gh api -X POST \ - -F tag_name='v2.0.0-beta.5' \ - -F target_commitish='v2' \ - -F previous_tag_name='v2.0.0-beta.4' \ - --jq '.body' \ - repos/pelletier/go-toml/releases/generate-notes +git checkout v2 +git pull +git tag v2.2.0 +git push --tags ``` -3. Look for "Other changes". That would indicate a pull request not labeled - properly. Tweak labels and pull request titles until changelog looks good for - users. -4. [Draft new release][new-release]. -5. Fill tag and target with the same value used to generate the changelog. -6. Set title to the new tag value. -7. Paste the generated changelog. -8. Check "create discussion", in the "Releases" category. -9. Check pre-release if new version is an alpha or beta. +3. CI automatically builds a draft Github release. Review it and edit as + necessary. Look for "Other changes". That would indicate a pull request not + labeled properly. Tweak labels and pull request titles until changelog looks + good for users. +4. Check "create discussion" box, in the "Releases" category. +5. If new version is an alpha or beta only, check pre-release box. + [issues-tracker]: https://github.com/pelletier/go-toml/issues [bug-report]: https://github.com/pelletier/go-toml/issues/new?template=bug_report.md diff --git a/vendor/github.com/pelletier/go-toml/v2/LICENSE b/vendor/github.com/pelletier/go-toml/v2/LICENSE index 6839d51cd4..991e2ae966 100644 --- a/vendor/github.com/pelletier/go-toml/v2/LICENSE +++ b/vendor/github.com/pelletier/go-toml/v2/LICENSE @@ -1,6 +1,7 @@ The MIT License (MIT) -Copyright (c) 2013 - 2022 Thomas Pelletier, Eric Anderton +go-toml v2 +Copyright (c) 2021 - 2023 Thomas Pelletier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/github.com/pelletier/go-toml/v2/README.md b/vendor/github.com/pelletier/go-toml/v2/README.md index a63c3a7967..d964b25fe1 100644 --- a/vendor/github.com/pelletier/go-toml/v2/README.md +++ b/vendor/github.com/pelletier/go-toml/v2/README.md @@ -45,16 +45,15 @@ to check for typos. [See example in the documentation][strict]. ### Contextualized errors -When most decoding errors occur, go-toml returns [`DecodeError`][decode-err]), +When most decoding errors occur, go-toml returns [`DecodeError`][decode-err], which contains a human readable contextualized version of the error. For example: ``` -2| key1 = "value1" -3| key2 = "missing2" - | ~~~~ missing field -4| key3 = "missing3" -5| key4 = "value4" +1| [server] +2| path = 100 + | ~~~ cannot decode TOML integer into struct field toml_test.Server.Path of type string +3| port = 50 ``` [decode-err]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#DecodeError @@ -73,15 +72,35 @@ representation. [tlt]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#LocalTime [tldt]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#LocalDateTime +### Commented config + +Since TOML is often used for configuration files, go-toml can emit documents +annotated with [comments and commented-out values][comments-example]. For +example, it can generate the following file: + +```toml +# Host IP to connect to. +host = '127.0.0.1' +# Port of the remote server. +port = 4242 + +# Encryption parameters (optional) +# [TLS] +# cipher = 'AEAD-AES128-GCM-SHA256' +# version = 'TLS 1.3' +``` + +[comments-example]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#example-Marshal-Commented + ## Getting started Given the following struct, let's see how to read it and write it as TOML: ```go type MyConfig struct { - Version int - Name string - Tags []string + Version int + Name string + Tags []string } ``` @@ -100,7 +119,7 @@ tags = ["go", "toml"] var cfg MyConfig err := toml.Unmarshal([]byte(doc), &cfg) if err != nil { - panic(err) + panic(err) } fmt.Println("version:", cfg.Version) fmt.Println("name:", cfg.Name) @@ -121,14 +140,14 @@ as a TOML document: ```go cfg := MyConfig{ - Version: 2, - Name: "go-toml", - Tags: []string{"go", "toml"}, + Version: 2, + Name: "go-toml", + Tags: []string{"go", "toml"}, } b, err := toml.Marshal(cfg) if err != nil { - panic(err) + panic(err) } fmt.Println(string(b)) @@ -140,22 +159,33 @@ fmt.Println(string(b)) [marshal]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#Marshal +## Unstable API + +This API does not yet follow the backward compatibility guarantees of this +library. They provide early access to features that may have rough edges or an +API subject to change. + +### Parser + +Parser is the unstable API that allows iterative parsing of a TOML document at +the AST level. See https://pkg.go.dev/github.com/pelletier/go-toml/v2/unstable. + ## Benchmarks Execution time speedup compared to other Go TOML libraries: - - - - - - - - - - - + + + + + + + + + + +
Benchmarkgo-toml v1BurntSushi/toml
Marshal/HugoFrontMatter-21.9x1.9x
Marshal/ReferenceFile/map-21.7x1.8x
Marshal/ReferenceFile/struct-22.2x2.5x
Unmarshal/HugoFrontMatter-22.9x2.9x
Unmarshal/ReferenceFile/map-22.6x2.9x
Unmarshal/ReferenceFile/struct-24.4x5.3x
Benchmarkgo-toml v1BurntSushi/toml
Marshal/HugoFrontMatter-21.9x2.2x
Marshal/ReferenceFile/map-21.7x2.1x
Marshal/ReferenceFile/struct-22.2x3.0x
Unmarshal/HugoFrontMatter-22.9x2.7x
Unmarshal/ReferenceFile/map-22.6x2.7x
Unmarshal/ReferenceFile/struct-24.6x5.1x
See more

The table above has the results of the most common use-cases. The table below @@ -163,22 +193,22 @@ contains the results of all benchmarks, including unrealistic ones. It is provided for completeness.

- - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + +
Benchmarkgo-toml v1BurntSushi/toml
Marshal/SimpleDocument/map-21.8x2.9x
Marshal/SimpleDocument/struct-22.7x4.2x
Unmarshal/SimpleDocument/map-24.5x3.1x
Unmarshal/SimpleDocument/struct-26.2x3.9x
UnmarshalDataset/example-23.1x3.5x
UnmarshalDataset/code-22.3x3.1x
UnmarshalDataset/twitter-22.5x2.6x
UnmarshalDataset/citm_catalog-22.1x2.2x
UnmarshalDataset/canada-21.6x1.3x
UnmarshalDataset/config-24.3x3.2x
[Geo mean]2.7x2.8x
Benchmarkgo-toml v1BurntSushi/toml
Marshal/SimpleDocument/map-21.8x2.7x
Marshal/SimpleDocument/struct-22.7x3.8x
Unmarshal/SimpleDocument/map-23.8x3.0x
Unmarshal/SimpleDocument/struct-25.6x4.1x
UnmarshalDataset/example-23.0x3.2x
UnmarshalDataset/code-22.3x2.9x
UnmarshalDataset/twitter-22.6x2.7x
UnmarshalDataset/citm_catalog-22.2x2.3x
UnmarshalDataset/canada-21.8x1.5x
UnmarshalDataset/config-24.1x2.9x
geomean2.7x2.8x

This table can be generated with ./ci.sh benchmark -a -html.

@@ -203,24 +233,24 @@ Go-toml provides three handy command line tools: * `tomljson`: Reads a TOML file and outputs its JSON representation. - ``` - $ go install github.com/pelletier/go-toml/v2/cmd/tomljson@latest - $ tomljson --help - ``` + ``` + $ go install github.com/pelletier/go-toml/v2/cmd/tomljson@latest + $ tomljson --help + ``` * `jsontoml`: Reads a JSON file and outputs a TOML representation. - ``` - $ go install github.com/pelletier/go-toml/v2/cmd/jsontoml@latest - $ jsontoml --help - ``` + ``` + $ go install github.com/pelletier/go-toml/v2/cmd/jsontoml@latest + $ jsontoml --help + ``` * `tomll`: Lints and reformats a TOML file. - ``` - $ go install github.com/pelletier/go-toml/v2/cmd/tomll@latest - $ tomll --help - ``` + ``` + $ go install github.com/pelletier/go-toml/v2/cmd/tomll@latest + $ tomll --help + ``` ### Docker image @@ -231,7 +261,7 @@ Those tools are also available as a [Docker image][docker]. For example, to use docker run -i ghcr.io/pelletier/go-toml:v2 tomljson < example.toml ``` -Multiple versions are availble on [ghcr.io][docker]. +Multiple versions are available on [ghcr.io][docker]. [docker]: https://github.com/pelletier/go-toml/pkgs/container/go-toml @@ -263,16 +293,16 @@ element in the interface to decode the object. For example: ```go type inner struct { - B interface{} + B interface{} } type doc struct { - A interface{} + A interface{} } d := doc{ - A: inner{ - B: "Before", - }, + A: inner{ + B: "Before", + }, } data := ` @@ -311,7 +341,7 @@ contained in the doc is superior to the capacity of the array. For example: ```go type doc struct { - A [2]string + A [2]string } d := doc{} err := toml.Unmarshal([]byte(`A = ["one", "two", "many"]`), &d) @@ -486,27 +516,20 @@ is not necessary anymore. V1 used to provide multiple struct tags: `comment`, `commented`, `multiline`, `toml`, and `omitempty`. To behave more like the standard library, v2 has merged -`toml`, `multiline`, and `omitempty`. For example: +`toml`, `multiline`, `commented`, and `omitempty`. For example: ```go type doc struct { // v1 - F string `toml:"field" multiline:"true" omitempty:"true"` + F string `toml:"field" multiline:"true" omitempty:"true" commented:"true"` // v2 - F string `toml:"field,multiline,omitempty"` + F string `toml:"field,multiline,omitempty,commented"` } ``` Has a result, the `Encoder.SetTag*` methods have been removed, as there is just one tag now. - -#### `commented` tag has been removed - -There is no replacement for the `commented` tag. This feature would be better -suited in a proper document model for go-toml v2, which has been [cut from -scope][nodoc] at the moment. - #### `Encoder.ArraysWithOneElementPerLine` has been renamed The new name is `Encoder.SetArraysMultiline`. The behavior should be the same. @@ -542,10 +565,11 @@ complete solutions exist out there. ## Versioning -Go-toml follows [Semantic Versioning](http://semver.org/). The supported version -of [TOML](https://github.com/toml-lang/toml) is indicated at the beginning of -this document. The last two major versions of Go are supported -(see [Go Release Policy](https://golang.org/doc/devel/release.html#policy)). +Expect for parts explicitely marked otherwise, go-toml follows [Semantic +Versioning](https://semver.org). The supported version of +[TOML](https://github.com/toml-lang/toml) is indicated at the beginning of this +document. The last two major versions of Go are supported (see [Go Release +Policy](https://golang.org/doc/devel/release.html#policy)). ## License diff --git a/vendor/github.com/pelletier/go-toml/v2/SECURITY.md b/vendor/github.com/pelletier/go-toml/v2/SECURITY.md index b2f21cfc92..d4d554fda9 100644 --- a/vendor/github.com/pelletier/go-toml/v2/SECURITY.md +++ b/vendor/github.com/pelletier/go-toml/v2/SECURITY.md @@ -2,9 +2,6 @@ ## Supported Versions -Use this section to tell people about which versions of your project are -currently being supported with security updates. - | Version | Supported | | ---------- | ------------------ | | Latest 2.x | :white_check_mark: | diff --git a/vendor/github.com/pelletier/go-toml/v2/ci.sh b/vendor/github.com/pelletier/go-toml/v2/ci.sh index d916c5f237..86217a9b09 100644 --- a/vendor/github.com/pelletier/go-toml/v2/ci.sh +++ b/vendor/github.com/pelletier/go-toml/v2/ci.sh @@ -77,8 +77,9 @@ cover() { pushd "$dir" go test -covermode=atomic -coverpkg=./... -coverprofile=coverage.out.tmp ./... - cat coverage.out.tmp | grep -v testsuite | grep -v tomltestgen | grep -v gotoml-test-decoder > coverage.out + grep -Ev '(fuzz|testsuite|tomltestgen|gotoml-test-decoder|gotoml-test-encoder)' coverage.out.tmp > coverage.out go tool cover -func=coverage.out + echo "Coverage profile for ${branch}: ${dir}/coverage.out" >&2 popd if [ "${branch}" != "HEAD" ]; then @@ -151,7 +152,7 @@ bench() { fi export GOMAXPROCS=2 - nice -n -19 taskset --cpu-list 0,1 go test '-bench=^Benchmark(Un)?[mM]arshal' -count=5 -run=Nothing ./... | tee "${out}" + go test '-bench=^Benchmark(Un)?[mM]arshal' -count=10 -run=Nothing ./... | tee "${out}" popd if [ "${branch}" != "HEAD" ]; then @@ -160,10 +161,12 @@ bench() { } fmktemp() { - if mktemp --version|grep GNU >/dev/null; then - mktemp --suffix=-$1; + if mktemp --version &> /dev/null; then + # GNU + mktemp --suffix=-$1 else - mktemp -t $1; + # BSD + mktemp -t $1 fi } @@ -183,12 +186,14 @@ with open(sys.argv[1]) as f: lines.append(line.split(',')) results = [] -for line in reversed(lines[1:]): +for line in reversed(lines[2:]): + if len(line) < 8 or line[0] == "": + continue v2 = float(line[1]) results.append([ line[0].replace("-32", ""), "%.1fx" % (float(line[3])/v2), # v1 - "%.1fx" % (float(line[5])/v2), # bs + "%.1fx" % (float(line[7])/v2), # bs ]) # move geomean to the end results.append(results[0]) @@ -259,10 +264,10 @@ benchmark() { if [ "$1" = "-html" ]; then tmpcsv=`fmktemp csv` - benchstat -csv -geomean go-toml-v2.txt go-toml-v1.txt bs-toml.txt > $tmpcsv + benchstat -format csv go-toml-v2.txt go-toml-v1.txt bs-toml.txt > $tmpcsv benchstathtml $tmpcsv else - benchstat -geomean go-toml-v2.txt go-toml-v1.txt bs-toml.txt + benchstat go-toml-v2.txt go-toml-v1.txt bs-toml.txt fi rm -f go-toml-v2.txt go-toml-v1.txt bs-toml.txt diff --git a/vendor/github.com/pelletier/go-toml/v2/decode.go b/vendor/github.com/pelletier/go-toml/v2/decode.go index 4af965360a..f0ec3b1705 100644 --- a/vendor/github.com/pelletier/go-toml/v2/decode.go +++ b/vendor/github.com/pelletier/go-toml/v2/decode.go @@ -5,6 +5,8 @@ import ( "math" "strconv" "time" + + "github.com/pelletier/go-toml/v2/unstable" ) func parseInteger(b []byte) (int64, error) { @@ -32,7 +34,7 @@ func parseLocalDate(b []byte) (LocalDate, error) { var date LocalDate if len(b) != 10 || b[4] != '-' || b[7] != '-' { - return date, newDecodeError(b, "dates are expected to have the format YYYY-MM-DD") + return date, unstable.NewParserError(b, "dates are expected to have the format YYYY-MM-DD") } var err error @@ -53,7 +55,7 @@ func parseLocalDate(b []byte) (LocalDate, error) { } if !isValidDate(date.Year, date.Month, date.Day) { - return LocalDate{}, newDecodeError(b, "impossible date") + return LocalDate{}, unstable.NewParserError(b, "impossible date") } return date, nil @@ -64,7 +66,7 @@ func parseDecimalDigits(b []byte) (int, error) { for i, c := range b { if c < '0' || c > '9' { - return 0, newDecodeError(b[i:i+1], "expected digit (0-9)") + return 0, unstable.NewParserError(b[i:i+1], "expected digit (0-9)") } v *= 10 v += int(c - '0') @@ -97,7 +99,7 @@ func parseDateTime(b []byte) (time.Time, error) { } else { const dateTimeByteLen = 6 if len(b) != dateTimeByteLen { - return time.Time{}, newDecodeError(b, "invalid date-time timezone") + return time.Time{}, unstable.NewParserError(b, "invalid date-time timezone") } var direction int switch b[0] { @@ -106,11 +108,11 @@ func parseDateTime(b []byte) (time.Time, error) { case '+': direction = +1 default: - return time.Time{}, newDecodeError(b[:1], "invalid timezone offset character") + return time.Time{}, unstable.NewParserError(b[:1], "invalid timezone offset character") } if b[3] != ':' { - return time.Time{}, newDecodeError(b[3:4], "expected a : separator") + return time.Time{}, unstable.NewParserError(b[3:4], "expected a : separator") } hours, err := parseDecimalDigits(b[1:3]) @@ -118,7 +120,7 @@ func parseDateTime(b []byte) (time.Time, error) { return time.Time{}, err } if hours > 23 { - return time.Time{}, newDecodeError(b[:1], "invalid timezone offset hours") + return time.Time{}, unstable.NewParserError(b[:1], "invalid timezone offset hours") } minutes, err := parseDecimalDigits(b[4:6]) @@ -126,7 +128,7 @@ func parseDateTime(b []byte) (time.Time, error) { return time.Time{}, err } if minutes > 59 { - return time.Time{}, newDecodeError(b[:1], "invalid timezone offset minutes") + return time.Time{}, unstable.NewParserError(b[:1], "invalid timezone offset minutes") } seconds := direction * (hours*3600 + minutes*60) @@ -139,7 +141,7 @@ func parseDateTime(b []byte) (time.Time, error) { } if len(b) > 0 { - return time.Time{}, newDecodeError(b, "extra bytes at the end of the timezone") + return time.Time{}, unstable.NewParserError(b, "extra bytes at the end of the timezone") } t := time.Date( @@ -160,7 +162,7 @@ func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) { const localDateTimeByteMinLen = 11 if len(b) < localDateTimeByteMinLen { - return dt, nil, newDecodeError(b, "local datetimes are expected to have the format YYYY-MM-DDTHH:MM:SS[.NNNNNNNNN]") + return dt, nil, unstable.NewParserError(b, "local datetimes are expected to have the format YYYY-MM-DDTHH:MM:SS[.NNNNNNNNN]") } date, err := parseLocalDate(b[:10]) @@ -171,7 +173,7 @@ func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) { sep := b[10] if sep != 'T' && sep != ' ' && sep != 't' { - return dt, nil, newDecodeError(b[10:11], "datetime separator is expected to be T or a space") + return dt, nil, unstable.NewParserError(b[10:11], "datetime separator is expected to be T or a space") } t, rest, err := parseLocalTime(b[11:]) @@ -195,7 +197,7 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) { // check if b matches to have expected format HH:MM:SS[.NNNNNN] const localTimeByteLen = 8 if len(b) < localTimeByteLen { - return t, nil, newDecodeError(b, "times are expected to have the format HH:MM:SS[.NNNNNN]") + return t, nil, unstable.NewParserError(b, "times are expected to have the format HH:MM:SS[.NNNNNN]") } var err error @@ -206,10 +208,10 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) { } if t.Hour > 23 { - return t, nil, newDecodeError(b[0:2], "hour cannot be greater 23") + return t, nil, unstable.NewParserError(b[0:2], "hour cannot be greater 23") } if b[2] != ':' { - return t, nil, newDecodeError(b[2:3], "expecting colon between hours and minutes") + return t, nil, unstable.NewParserError(b[2:3], "expecting colon between hours and minutes") } t.Minute, err = parseDecimalDigits(b[3:5]) @@ -217,10 +219,10 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) { return t, nil, err } if t.Minute > 59 { - return t, nil, newDecodeError(b[3:5], "minutes cannot be greater 59") + return t, nil, unstable.NewParserError(b[3:5], "minutes cannot be greater 59") } if b[5] != ':' { - return t, nil, newDecodeError(b[5:6], "expecting colon between minutes and seconds") + return t, nil, unstable.NewParserError(b[5:6], "expecting colon between minutes and seconds") } t.Second, err = parseDecimalDigits(b[6:8]) @@ -229,7 +231,7 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) { } if t.Second > 60 { - return t, nil, newDecodeError(b[6:8], "seconds cannot be greater 60") + return t, nil, unstable.NewParserError(b[6:8], "seconds cannot be greater 60") } b = b[8:] @@ -242,7 +244,7 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) { for i, c := range b[1:] { if !isDigit(c) { if i == 0 { - return t, nil, newDecodeError(b[0:1], "need at least one digit after fraction point") + return t, nil, unstable.NewParserError(b[0:1], "need at least one digit after fraction point") } break } @@ -266,7 +268,7 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) { } if precision == 0 { - return t, nil, newDecodeError(b[:1], "nanoseconds need at least one digit") + return t, nil, unstable.NewParserError(b[:1], "nanoseconds need at least one digit") } t.Nanosecond = frac * nspow[precision] @@ -289,24 +291,24 @@ func parseFloat(b []byte) (float64, error) { } if cleaned[0] == '.' { - return 0, newDecodeError(b, "float cannot start with a dot") + return 0, unstable.NewParserError(b, "float cannot start with a dot") } if cleaned[len(cleaned)-1] == '.' { - return 0, newDecodeError(b, "float cannot end with a dot") + return 0, unstable.NewParserError(b, "float cannot end with a dot") } dotAlreadySeen := false for i, c := range cleaned { if c == '.' { if dotAlreadySeen { - return 0, newDecodeError(b[i:i+1], "float can have at most one decimal point") + return 0, unstable.NewParserError(b[i:i+1], "float can have at most one decimal point") } if !isDigit(cleaned[i-1]) { - return 0, newDecodeError(b[i-1:i+1], "float decimal point must be preceded by a digit") + return 0, unstable.NewParserError(b[i-1:i+1], "float decimal point must be preceded by a digit") } if !isDigit(cleaned[i+1]) { - return 0, newDecodeError(b[i:i+2], "float decimal point must be followed by a digit") + return 0, unstable.NewParserError(b[i:i+2], "float decimal point must be followed by a digit") } dotAlreadySeen = true } @@ -316,13 +318,13 @@ func parseFloat(b []byte) (float64, error) { if cleaned[0] == '+' || cleaned[0] == '-' { start = 1 } - if cleaned[start] == '0' && isDigit(cleaned[start+1]) { - return 0, newDecodeError(b, "float integer part cannot have leading zeroes") + if cleaned[start] == '0' && len(cleaned) > start+1 && isDigit(cleaned[start+1]) { + return 0, unstable.NewParserError(b, "float integer part cannot have leading zeroes") } f, err := strconv.ParseFloat(string(cleaned), 64) if err != nil { - return 0, newDecodeError(b, "unable to parse float: %w", err) + return 0, unstable.NewParserError(b, "unable to parse float: %w", err) } return f, nil @@ -336,7 +338,7 @@ func parseIntHex(b []byte) (int64, error) { i, err := strconv.ParseInt(string(cleaned), 16, 64) if err != nil { - return 0, newDecodeError(b, "couldn't parse hexadecimal number: %w", err) + return 0, unstable.NewParserError(b, "couldn't parse hexadecimal number: %w", err) } return i, nil @@ -350,7 +352,7 @@ func parseIntOct(b []byte) (int64, error) { i, err := strconv.ParseInt(string(cleaned), 8, 64) if err != nil { - return 0, newDecodeError(b, "couldn't parse octal number: %w", err) + return 0, unstable.NewParserError(b, "couldn't parse octal number: %w", err) } return i, nil @@ -364,7 +366,7 @@ func parseIntBin(b []byte) (int64, error) { i, err := strconv.ParseInt(string(cleaned), 2, 64) if err != nil { - return 0, newDecodeError(b, "couldn't parse binary number: %w", err) + return 0, unstable.NewParserError(b, "couldn't parse binary number: %w", err) } return i, nil @@ -387,12 +389,12 @@ func parseIntDec(b []byte) (int64, error) { } if len(cleaned) > startIdx+1 && cleaned[startIdx] == '0' { - return 0, newDecodeError(b, "leading zero not allowed on decimal number") + return 0, unstable.NewParserError(b, "leading zero not allowed on decimal number") } i, err := strconv.ParseInt(string(cleaned), 10, 64) if err != nil { - return 0, newDecodeError(b, "couldn't parse decimal number: %w", err) + return 0, unstable.NewParserError(b, "couldn't parse decimal number: %w", err) } return i, nil @@ -409,11 +411,11 @@ func checkAndRemoveUnderscoresIntegers(b []byte) ([]byte, error) { } if b[start] == '_' { - return nil, newDecodeError(b[start:start+1], "number cannot start with underscore") + return nil, unstable.NewParserError(b[start:start+1], "number cannot start with underscore") } if b[len(b)-1] == '_' { - return nil, newDecodeError(b[len(b)-1:], "number cannot end with underscore") + return nil, unstable.NewParserError(b[len(b)-1:], "number cannot end with underscore") } // fast path @@ -435,7 +437,7 @@ func checkAndRemoveUnderscoresIntegers(b []byte) ([]byte, error) { c := b[i] if c == '_' { if !before { - return nil, newDecodeError(b[i-1:i+1], "number must have at least one digit between underscores") + return nil, unstable.NewParserError(b[i-1:i+1], "number must have at least one digit between underscores") } before = false } else { @@ -449,11 +451,11 @@ func checkAndRemoveUnderscoresIntegers(b []byte) ([]byte, error) { func checkAndRemoveUnderscoresFloats(b []byte) ([]byte, error) { if b[0] == '_' { - return nil, newDecodeError(b[0:1], "number cannot start with underscore") + return nil, unstable.NewParserError(b[0:1], "number cannot start with underscore") } if b[len(b)-1] == '_' { - return nil, newDecodeError(b[len(b)-1:], "number cannot end with underscore") + return nil, unstable.NewParserError(b[len(b)-1:], "number cannot end with underscore") } // fast path @@ -476,10 +478,10 @@ func checkAndRemoveUnderscoresFloats(b []byte) ([]byte, error) { switch c { case '_': if !before { - return nil, newDecodeError(b[i-1:i+1], "number must have at least one digit between underscores") + return nil, unstable.NewParserError(b[i-1:i+1], "number must have at least one digit between underscores") } if i < len(b)-1 && (b[i+1] == 'e' || b[i+1] == 'E') { - return nil, newDecodeError(b[i+1:i+2], "cannot have underscore before exponent") + return nil, unstable.NewParserError(b[i+1:i+2], "cannot have underscore before exponent") } before = false case '+', '-': @@ -488,15 +490,15 @@ func checkAndRemoveUnderscoresFloats(b []byte) ([]byte, error) { before = false case 'e', 'E': if i < len(b)-1 && b[i+1] == '_' { - return nil, newDecodeError(b[i+1:i+2], "cannot have underscore after exponent") + return nil, unstable.NewParserError(b[i+1:i+2], "cannot have underscore after exponent") } cleaned = append(cleaned, c) case '.': if i < len(b)-1 && b[i+1] == '_' { - return nil, newDecodeError(b[i+1:i+2], "cannot have underscore after decimal point") + return nil, unstable.NewParserError(b[i+1:i+2], "cannot have underscore after decimal point") } if i > 0 && b[i-1] == '_' { - return nil, newDecodeError(b[i-1:i], "cannot have underscore before decimal point") + return nil, unstable.NewParserError(b[i-1:i], "cannot have underscore before decimal point") } cleaned = append(cleaned, c) default: @@ -542,3 +544,7 @@ func daysIn(m int, year int) int { func isLeap(year int) bool { return year%4 == 0 && (year%100 != 0 || year%400 == 0) } + +func isDigit(r byte) bool { + return r >= '0' && r <= '9' +} diff --git a/vendor/github.com/pelletier/go-toml/v2/errors.go b/vendor/github.com/pelletier/go-toml/v2/errors.go index 2e7f0ffdf8..309733f1f9 100644 --- a/vendor/github.com/pelletier/go-toml/v2/errors.go +++ b/vendor/github.com/pelletier/go-toml/v2/errors.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/pelletier/go-toml/v2/internal/danger" + "github.com/pelletier/go-toml/v2/unstable" ) // DecodeError represents an error encountered during the parsing or decoding @@ -55,25 +56,6 @@ func (s *StrictMissingError) String() string { type Key []string -// internal version of DecodeError that is used as the base to create a -// DecodeError with full context. -type decodeError struct { - highlight []byte - message string - key Key // optional -} - -func (de *decodeError) Error() string { - return de.message -} - -func newDecodeError(highlight []byte, format string, args ...interface{}) error { - return &decodeError{ - highlight: highlight, - message: fmt.Errorf(format, args...).Error(), - } -} - // Error returns the error message contained in the DecodeError. func (e *DecodeError) Error() string { return "toml: " + e.message @@ -105,12 +87,12 @@ func (e *DecodeError) Key() Key { // highlight can be freely deallocated. // //nolint:funlen -func wrapDecodeError(document []byte, de *decodeError) *DecodeError { - offset := danger.SubsliceOffset(document, de.highlight) +func wrapDecodeError(document []byte, de *unstable.ParserError) *DecodeError { + offset := danger.SubsliceOffset(document, de.Highlight) errMessage := de.Error() errLine, errColumn := positionAtEnd(document[:offset]) - before, after := linesOfContext(document, de.highlight, offset, 3) + before, after := linesOfContext(document, de.Highlight, offset, 3) var buf strings.Builder @@ -140,7 +122,7 @@ func wrapDecodeError(document []byte, de *decodeError) *DecodeError { buf.Write(before[0]) } - buf.Write(de.highlight) + buf.Write(de.Highlight) if len(after) > 0 { buf.Write(after[0]) @@ -158,7 +140,7 @@ func wrapDecodeError(document []byte, de *decodeError) *DecodeError { buf.WriteString(strings.Repeat(" ", len(before[0]))) } - buf.WriteString(strings.Repeat("~", len(de.highlight))) + buf.WriteString(strings.Repeat("~", len(de.Highlight))) if len(errMessage) > 0 { buf.WriteString(" ") @@ -183,7 +165,7 @@ func wrapDecodeError(document []byte, de *decodeError) *DecodeError { message: errMessage, line: errLine, column: errColumn, - key: de.key, + key: de.Key, human: buf.String(), } } diff --git a/vendor/github.com/pelletier/go-toml/v2/internal/ast/builder.go b/vendor/github.com/pelletier/go-toml/v2/internal/ast/builder.go deleted file mode 100644 index 120f16e5ce..0000000000 --- a/vendor/github.com/pelletier/go-toml/v2/internal/ast/builder.go +++ /dev/null @@ -1,51 +0,0 @@ -package ast - -type Reference int - -const InvalidReference Reference = -1 - -func (r Reference) Valid() bool { - return r != InvalidReference -} - -type Builder struct { - tree Root - lastIdx int -} - -func (b *Builder) Tree() *Root { - return &b.tree -} - -func (b *Builder) NodeAt(ref Reference) *Node { - return b.tree.at(ref) -} - -func (b *Builder) Reset() { - b.tree.nodes = b.tree.nodes[:0] - b.lastIdx = 0 -} - -func (b *Builder) Push(n Node) Reference { - b.lastIdx = len(b.tree.nodes) - b.tree.nodes = append(b.tree.nodes, n) - return Reference(b.lastIdx) -} - -func (b *Builder) PushAndChain(n Node) Reference { - newIdx := len(b.tree.nodes) - b.tree.nodes = append(b.tree.nodes, n) - if b.lastIdx >= 0 { - b.tree.nodes[b.lastIdx].next = newIdx - b.lastIdx - } - b.lastIdx = newIdx - return Reference(b.lastIdx) -} - -func (b *Builder) AttachChild(parent Reference, child Reference) { - b.tree.nodes[parent].child = int(child) - int(parent) -} - -func (b *Builder) Chain(from Reference, to Reference) { - b.tree.nodes[from].next = int(to) - int(from) -} diff --git a/vendor/github.com/pelletier/go-toml/v2/internal/characters/ascii.go b/vendor/github.com/pelletier/go-toml/v2/internal/characters/ascii.go new file mode 100644 index 0000000000..80f698db4b --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/internal/characters/ascii.go @@ -0,0 +1,42 @@ +package characters + +var invalidAsciiTable = [256]bool{ + 0x00: true, + 0x01: true, + 0x02: true, + 0x03: true, + 0x04: true, + 0x05: true, + 0x06: true, + 0x07: true, + 0x08: true, + // 0x09 TAB + // 0x0A LF + 0x0B: true, + 0x0C: true, + // 0x0D CR + 0x0E: true, + 0x0F: true, + 0x10: true, + 0x11: true, + 0x12: true, + 0x13: true, + 0x14: true, + 0x15: true, + 0x16: true, + 0x17: true, + 0x18: true, + 0x19: true, + 0x1A: true, + 0x1B: true, + 0x1C: true, + 0x1D: true, + 0x1E: true, + 0x1F: true, + // 0x20 - 0x7E Printable ASCII characters + 0x7F: true, +} + +func InvalidAscii(b byte) bool { + return invalidAsciiTable[b] +} diff --git a/vendor/github.com/pelletier/go-toml/v2/utf8.go b/vendor/github.com/pelletier/go-toml/v2/internal/characters/utf8.go similarity index 87% rename from vendor/github.com/pelletier/go-toml/v2/utf8.go rename to vendor/github.com/pelletier/go-toml/v2/internal/characters/utf8.go index d47a4f20c5..db4f45acbf 100644 --- a/vendor/github.com/pelletier/go-toml/v2/utf8.go +++ b/vendor/github.com/pelletier/go-toml/v2/internal/characters/utf8.go @@ -1,4 +1,4 @@ -package toml +package characters import ( "unicode/utf8" @@ -32,7 +32,7 @@ func (u utf8Err) Zero() bool { // 0x9 => tab, ok // 0xA - 0x1F => invalid // 0x7F => invalid -func utf8TomlValidAlreadyEscaped(p []byte) (err utf8Err) { +func Utf8TomlValidAlreadyEscaped(p []byte) (err utf8Err) { // Fast path. Check for and skip 8 bytes of ASCII characters per iteration. offset := 0 for len(p) >= 8 { @@ -48,7 +48,7 @@ func utf8TomlValidAlreadyEscaped(p []byte) (err utf8Err) { } for i, b := range p[:8] { - if invalidAscii(b) { + if InvalidAscii(b) { err.Index = offset + i err.Size = 1 return @@ -62,7 +62,7 @@ func utf8TomlValidAlreadyEscaped(p []byte) (err utf8Err) { for i := 0; i < n; { pi := p[i] if pi < utf8.RuneSelf { - if invalidAscii(pi) { + if InvalidAscii(pi) { err.Index = offset + i err.Size = 1 return @@ -106,11 +106,11 @@ func utf8TomlValidAlreadyEscaped(p []byte) (err utf8Err) { } // Return the size of the next rune if valid, 0 otherwise. -func utf8ValidNext(p []byte) int { +func Utf8ValidNext(p []byte) int { c := p[0] if c < utf8.RuneSelf { - if invalidAscii(c) { + if InvalidAscii(c) { return 0 } return 1 @@ -140,47 +140,6 @@ func utf8ValidNext(p []byte) int { return size } -var invalidAsciiTable = [256]bool{ - 0x00: true, - 0x01: true, - 0x02: true, - 0x03: true, - 0x04: true, - 0x05: true, - 0x06: true, - 0x07: true, - 0x08: true, - // 0x09 TAB - // 0x0A LF - 0x0B: true, - 0x0C: true, - // 0x0D CR - 0x0E: true, - 0x0F: true, - 0x10: true, - 0x11: true, - 0x12: true, - 0x13: true, - 0x14: true, - 0x15: true, - 0x16: true, - 0x17: true, - 0x18: true, - 0x19: true, - 0x1A: true, - 0x1B: true, - 0x1C: true, - 0x1D: true, - 0x1E: true, - 0x1F: true, - // 0x20 - 0x7E Printable ASCII characters - 0x7F: true, -} - -func invalidAscii(b byte) bool { - return invalidAsciiTable[b] -} - // acceptRange gives the range of valid values for the second byte in a UTF-8 // sequence. type acceptRange struct { diff --git a/vendor/github.com/pelletier/go-toml/v2/internal/tracker/key.go b/vendor/github.com/pelletier/go-toml/v2/internal/tracker/key.go index 7c148f48d4..149b17f538 100644 --- a/vendor/github.com/pelletier/go-toml/v2/internal/tracker/key.go +++ b/vendor/github.com/pelletier/go-toml/v2/internal/tracker/key.go @@ -1,8 +1,6 @@ package tracker -import ( - "github.com/pelletier/go-toml/v2/internal/ast" -) +import "github.com/pelletier/go-toml/v2/unstable" // KeyTracker is a tracker that keeps track of the current Key as the AST is // walked. @@ -11,19 +9,19 @@ type KeyTracker struct { } // UpdateTable sets the state of the tracker with the AST table node. -func (t *KeyTracker) UpdateTable(node *ast.Node) { +func (t *KeyTracker) UpdateTable(node *unstable.Node) { t.reset() t.Push(node) } // UpdateArrayTable sets the state of the tracker with the AST array table node. -func (t *KeyTracker) UpdateArrayTable(node *ast.Node) { +func (t *KeyTracker) UpdateArrayTable(node *unstable.Node) { t.reset() t.Push(node) } // Push the given key on the stack. -func (t *KeyTracker) Push(node *ast.Node) { +func (t *KeyTracker) Push(node *unstable.Node) { it := node.Key() for it.Next() { t.k = append(t.k, string(it.Node().Data)) @@ -31,7 +29,7 @@ func (t *KeyTracker) Push(node *ast.Node) { } // Pop key from stack. -func (t *KeyTracker) Pop(node *ast.Node) { +func (t *KeyTracker) Pop(node *unstable.Node) { it := node.Key() for it.Next() { t.k = t.k[:len(t.k)-1] diff --git a/vendor/github.com/pelletier/go-toml/v2/internal/tracker/seen.go b/vendor/github.com/pelletier/go-toml/v2/internal/tracker/seen.go index a7ee05ba65..ce7dd4af17 100644 --- a/vendor/github.com/pelletier/go-toml/v2/internal/tracker/seen.go +++ b/vendor/github.com/pelletier/go-toml/v2/internal/tracker/seen.go @@ -5,7 +5,7 @@ import ( "fmt" "sync" - "github.com/pelletier/go-toml/v2/internal/ast" + "github.com/pelletier/go-toml/v2/unstable" ) type keyKind uint8 @@ -149,24 +149,25 @@ func (s *SeenTracker) setExplicitFlag(parentIdx int) { // CheckExpression takes a top-level node and checks that it does not contain // keys that have been seen in previous calls, and validates that types are -// consistent. -func (s *SeenTracker) CheckExpression(node *ast.Node) error { +// consistent. It returns true if it is the first time this node's key is seen. +// Useful to clear array tables on first use. +func (s *SeenTracker) CheckExpression(node *unstable.Node) (bool, error) { if s.entries == nil { s.reset() } switch node.Kind { - case ast.KeyValue: + case unstable.KeyValue: return s.checkKeyValue(node) - case ast.Table: + case unstable.Table: return s.checkTable(node) - case ast.ArrayTable: + case unstable.ArrayTable: return s.checkArrayTable(node) default: panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind)) } } -func (s *SeenTracker) checkTable(node *ast.Node) error { +func (s *SeenTracker) checkTable(node *unstable.Node) (bool, error) { if s.currentIdx >= 0 { s.setExplicitFlag(s.currentIdx) } @@ -192,7 +193,7 @@ func (s *SeenTracker) checkTable(node *ast.Node) error { } else { entry := s.entries[idx] if entry.kind == valueKind { - return fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), entry.kind) + return false, fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), entry.kind) } } parentIdx = idx @@ -201,25 +202,27 @@ func (s *SeenTracker) checkTable(node *ast.Node) error { k := it.Node().Data idx := s.find(parentIdx, k) + first := false if idx >= 0 { kind := s.entries[idx].kind if kind != tableKind { - return fmt.Errorf("toml: key %s should be a table, not a %s", string(k), kind) + return false, fmt.Errorf("toml: key %s should be a table, not a %s", string(k), kind) } if s.entries[idx].explicit { - return fmt.Errorf("toml: table %s already exists", string(k)) + return false, fmt.Errorf("toml: table %s already exists", string(k)) } s.entries[idx].explicit = true } else { idx = s.create(parentIdx, k, tableKind, true, false) + first = true } s.currentIdx = idx - return nil + return first, nil } -func (s *SeenTracker) checkArrayTable(node *ast.Node) error { +func (s *SeenTracker) checkArrayTable(node *unstable.Node) (bool, error) { if s.currentIdx >= 0 { s.setExplicitFlag(s.currentIdx) } @@ -242,7 +245,7 @@ func (s *SeenTracker) checkArrayTable(node *ast.Node) error { } else { entry := s.entries[idx] if entry.kind == valueKind { - return fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), entry.kind) + return false, fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), entry.kind) } } @@ -252,22 +255,23 @@ func (s *SeenTracker) checkArrayTable(node *ast.Node) error { k := it.Node().Data idx := s.find(parentIdx, k) - if idx >= 0 { + firstTime := idx < 0 + if firstTime { + idx = s.create(parentIdx, k, arrayTableKind, true, false) + } else { kind := s.entries[idx].kind if kind != arrayTableKind { - return fmt.Errorf("toml: key %s already exists as a %s, but should be an array table", kind, string(k)) + return false, fmt.Errorf("toml: key %s already exists as a %s, but should be an array table", kind, string(k)) } s.clear(idx) - } else { - idx = s.create(parentIdx, k, arrayTableKind, true, false) } s.currentIdx = idx - return nil + return firstTime, nil } -func (s *SeenTracker) checkKeyValue(node *ast.Node) error { +func (s *SeenTracker) checkKeyValue(node *unstable.Node) (bool, error) { parentIdx := s.currentIdx it := node.Key() @@ -281,11 +285,11 @@ func (s *SeenTracker) checkKeyValue(node *ast.Node) error { } else { entry := s.entries[idx] if it.IsLast() { - return fmt.Errorf("toml: key %s is already defined", string(k)) + return false, fmt.Errorf("toml: key %s is already defined", string(k)) } else if entry.kind != tableKind { - return fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), entry.kind) + return false, fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), entry.kind) } else if entry.explicit { - return fmt.Errorf("toml: cannot redefine table %s that has already been explicitly defined", string(k)) + return false, fmt.Errorf("toml: cannot redefine table %s that has already been explicitly defined", string(k)) } } @@ -297,36 +301,36 @@ func (s *SeenTracker) checkKeyValue(node *ast.Node) error { value := node.Value() switch value.Kind { - case ast.InlineTable: + case unstable.InlineTable: return s.checkInlineTable(value) - case ast.Array: + case unstable.Array: return s.checkArray(value) } - return nil + return false, nil } -func (s *SeenTracker) checkArray(node *ast.Node) error { +func (s *SeenTracker) checkArray(node *unstable.Node) (first bool, err error) { it := node.Children() for it.Next() { n := it.Node() switch n.Kind { - case ast.InlineTable: - err := s.checkInlineTable(n) + case unstable.InlineTable: + first, err = s.checkInlineTable(n) if err != nil { - return err + return false, err } - case ast.Array: - err := s.checkArray(n) + case unstable.Array: + first, err = s.checkArray(n) if err != nil { - return err + return false, err } } } - return nil + return first, nil } -func (s *SeenTracker) checkInlineTable(node *ast.Node) error { +func (s *SeenTracker) checkInlineTable(node *unstable.Node) (first bool, err error) { if pool.New == nil { pool.New = func() interface{} { return &SeenTracker{} @@ -339,9 +343,9 @@ func (s *SeenTracker) checkInlineTable(node *ast.Node) error { it := node.Children() for it.Next() { n := it.Node() - err := s.checkKeyValue(n) + first, err = s.checkKeyValue(n) if err != nil { - return err + return false, err } } @@ -352,5 +356,5 @@ func (s *SeenTracker) checkInlineTable(node *ast.Node) error { // redefinition of its keys: check* functions cannot walk into // a value. pool.Put(s) - return nil + return first, nil } diff --git a/vendor/github.com/pelletier/go-toml/v2/localtime.go b/vendor/github.com/pelletier/go-toml/v2/localtime.go index 30a31dcbde..a856bfdb0d 100644 --- a/vendor/github.com/pelletier/go-toml/v2/localtime.go +++ b/vendor/github.com/pelletier/go-toml/v2/localtime.go @@ -4,6 +4,8 @@ import ( "fmt" "strings" "time" + + "github.com/pelletier/go-toml/v2/unstable" ) // LocalDate represents a calendar day in no specific timezone. @@ -75,7 +77,7 @@ func (d LocalTime) MarshalText() ([]byte, error) { func (d *LocalTime) UnmarshalText(b []byte) error { res, left, err := parseLocalTime(b) if err == nil && len(left) != 0 { - err = newDecodeError(left, "extra characters") + err = unstable.NewParserError(left, "extra characters") } if err != nil { return err @@ -109,7 +111,7 @@ func (d LocalDateTime) MarshalText() ([]byte, error) { func (d *LocalDateTime) UnmarshalText(data []byte) error { res, left, err := parseLocalDateTime(data) if err == nil && len(left) != 0 { - err = newDecodeError(left, "extra characters") + err = unstable.NewParserError(left, "extra characters") } if err != nil { return err diff --git a/vendor/github.com/pelletier/go-toml/v2/marshaler.go b/vendor/github.com/pelletier/go-toml/v2/marshaler.go index acb288315b..ffc9927208 100644 --- a/vendor/github.com/pelletier/go-toml/v2/marshaler.go +++ b/vendor/github.com/pelletier/go-toml/v2/marshaler.go @@ -3,6 +3,7 @@ package toml import ( "bytes" "encoding" + "encoding/json" "fmt" "io" "math" @@ -12,6 +13,8 @@ import ( "strings" "time" "unicode" + + "github.com/pelletier/go-toml/v2/internal/characters" ) // Marshal serializes a Go value as a TOML document. @@ -35,10 +38,11 @@ type Encoder struct { w io.Writer // global settings - tablesInline bool - arraysMultiline bool - indentSymbol string - indentTables bool + tablesInline bool + arraysMultiline bool + indentSymbol string + indentTables bool + marshalJsonNumbers bool } // NewEncoder returns a new Encoder that writes to w. @@ -85,6 +89,17 @@ func (enc *Encoder) SetIndentTables(indent bool) *Encoder { return enc } +// SetMarshalJsonNumbers forces the encoder to serialize `json.Number` as a +// float or integer instead of relying on TextMarshaler to emit a string. +// +// *Unstable:* This method does not follow the compatibility guarantees of +// semver. It can be changed or removed without a new major version being +// issued. +func (enc *Encoder) SetMarshalJsonNumbers(indent bool) *Encoder { + enc.marshalJsonNumbers = indent + return enc +} + // Encode writes a TOML representation of v to the stream. // // If v cannot be represented to TOML it returns an error. @@ -146,6 +161,9 @@ func (enc *Encoder) SetIndentTables(indent bool) *Encoder { // // The "omitempty" option prevents empty values or groups from being emitted. // +// The "commented" option prefixes the value and all its children with a comment +// symbol. +// // In addition to the "toml" tag struct tag, a "comment" tag can be used to emit // a TOML comment before the value being annotated. Comments are ignored inside // inline tables. For array tables, the comment is only present before the first @@ -178,6 +196,7 @@ func (enc *Encoder) Encode(v interface{}) error { type valueOptions struct { multiline bool omitempty bool + commented bool comment string } @@ -203,6 +222,9 @@ type encoderCtx struct { // Indentation level indent int + // Prefix the current value with a comment. + commented bool + // Options coming from struct tags options valueOptions } @@ -243,6 +265,18 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e return append(b, x.String()...), nil case LocalDateTime: return append(b, x.String()...), nil + case json.Number: + if enc.marshalJsonNumbers { + if x == "" { /// Useful zero value. + return append(b, "0"...), nil + } else if v, err := x.Int64(); err == nil { + return enc.encode(b, ctx, reflect.ValueOf(v)) + } else if f, err := x.Float64(); err == nil { + return enc.encode(b, ctx, reflect.ValueOf(f)) + } else { + return nil, fmt.Errorf("toml: unable to convert %q to int64 or float64", x) + } + } } hasTextMarshaler := v.Type().Implements(textMarshalerType) @@ -271,7 +305,7 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e return enc.encodeMap(b, ctx, v) case reflect.Struct: return enc.encodeStruct(b, ctx, v) - case reflect.Slice: + case reflect.Slice, reflect.Array: return enc.encodeSlice(b, ctx, v) case reflect.Interface: if v.IsNil() { @@ -355,9 +389,10 @@ func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v r if !ctx.inline { b = enc.encodeComment(ctx.indent, options.comment, b) + b = enc.commented(ctx.commented, b) + b = enc.indent(ctx.indent, b) } - b = enc.indent(ctx.indent, b) b = enc.encodeKey(b, ctx.key) b = append(b, " = "...) @@ -376,6 +411,13 @@ func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v r return b, nil } +func (enc *Encoder) commented(commented bool, b []byte) []byte { + if commented { + return append(b, "# "...) + } + return b +} + func isEmptyValue(v reflect.Value) bool { switch v.Kind() { case reflect.Struct: @@ -437,7 +479,7 @@ func (enc *Encoder) encodeString(b []byte, v string, options valueOptions) []byt func needsQuoting(v string) bool { // TODO: vectorize for _, b := range []byte(v) { - if b == '\'' || b == '\r' || b == '\n' || invalidAscii(b) { + if b == '\'' || b == '\r' || b == '\n' || characters.InvalidAscii(b) { return true } } @@ -524,6 +566,8 @@ func (enc *Encoder) encodeTableHeader(ctx encoderCtx, b []byte) ([]byte, error) b = enc.encodeComment(ctx.indent, ctx.options.comment, b) + b = enc.commented(ctx.commented, b) + b = enc.indent(ctx.indent, b) b = append(b, '[') @@ -575,11 +619,23 @@ func (enc *Encoder) encodeKey(b []byte, k string) []byte { } } -func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { - if v.Type().Key().Kind() != reflect.String { - return nil, fmt.Errorf("toml: type %s is not supported as a map key", v.Type().Key().Kind()) +func (enc *Encoder) keyToString(k reflect.Value) (string, error) { + keyType := k.Type() + switch { + case keyType.Kind() == reflect.String: + return k.String(), nil + + case keyType.Implements(textMarshalerType): + keyB, err := k.Interface().(encoding.TextMarshaler).MarshalText() + if err != nil { + return "", fmt.Errorf("toml: error marshalling key %v from text: %w", k, err) + } + return string(keyB), nil } + return "", fmt.Errorf("toml: type %s is not supported as a map key", keyType.Kind()) +} +func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { var ( t table emptyValueOptions valueOptions @@ -587,13 +643,17 @@ func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte iter := v.MapRange() for iter.Next() { - k := iter.Key().String() v := iter.Value() if isNil(v) { continue } + k, err := enc.keyToString(iter.Key()) + if err != nil { + return nil, err + } + if willConvertToTableOrArrayTable(ctx, v) { t.pushTable(k, v, emptyValueOptions) } else { @@ -672,6 +732,8 @@ func walkStruct(ctx encoderCtx, t *table, v reflect.Value) { if fieldType.Anonymous { if fieldType.Type.Kind() == reflect.Struct { walkStruct(ctx, t, f) + } else if fieldType.Type.Kind() == reflect.Pointer && !f.IsNil() && f.Elem().Kind() == reflect.Struct { + walkStruct(ctx, t, f.Elem()) } continue } else { @@ -686,6 +748,7 @@ func walkStruct(ctx encoderCtx, t *table, v reflect.Value) { options := valueOptions{ multiline: opts.multiline, omitempty: opts.omitempty, + commented: opts.commented, comment: fieldType.Tag.Get("comment"), } @@ -745,6 +808,7 @@ type tagOptions struct { multiline bool inline bool omitempty bool + commented bool } func parseTag(tag string) (string, tagOptions) { @@ -772,6 +836,8 @@ func parseTag(tag string) (string, tagOptions) { opts.inline = true case "omitempty": opts.omitempty = true + case "commented": + opts.commented = true } } @@ -807,8 +873,10 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro hasNonEmptyKV = true ctx.setKey(kv.Key) + ctx2 := ctx + ctx2.commented = kv.Options.commented || ctx2.commented - b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value) + b, err = enc.encodeKv(b, ctx2, kv.Options, kv.Value) if err != nil { return nil, err } @@ -833,8 +901,10 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro ctx.setKey(table.Key) ctx.options = table.Options + ctx2 := ctx + ctx2.commented = ctx2.commented || ctx.options.commented - b, err = enc.encode(b, ctx, table.Value) + b, err = enc.encode(b, ctx2, table.Value) if err != nil { return nil, err } @@ -912,7 +982,7 @@ func willConvertToTableOrArrayTable(ctx encoderCtx, v reflect.Value) bool { return willConvertToTableOrArrayTable(ctx, v.Elem()) } - if t.Kind() == reflect.Slice { + if t.Kind() == reflect.Slice || t.Kind() == reflect.Array { if v.Len() == 0 { // An empty slice should be a kv = []. return false @@ -952,6 +1022,9 @@ func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect. ctx.shiftKey() scratch := make([]byte, 0, 64) + + scratch = enc.commented(ctx.commented, scratch) + scratch = append(scratch, "[["...) for i, k := range ctx.parentKey { @@ -967,6 +1040,10 @@ func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect. b = enc.encodeComment(ctx.indent, ctx.options.comment, b) + if enc.indentTables { + ctx.indent++ + } + for i := 0; i < v.Len(); i++ { if i != 0 { b = append(b, "\n"...) diff --git a/vendor/github.com/pelletier/go-toml/v2/strict.go b/vendor/github.com/pelletier/go-toml/v2/strict.go index b7830d139c..802e7e4d15 100644 --- a/vendor/github.com/pelletier/go-toml/v2/strict.go +++ b/vendor/github.com/pelletier/go-toml/v2/strict.go @@ -1,9 +1,9 @@ package toml import ( - "github.com/pelletier/go-toml/v2/internal/ast" "github.com/pelletier/go-toml/v2/internal/danger" "github.com/pelletier/go-toml/v2/internal/tracker" + "github.com/pelletier/go-toml/v2/unstable" ) type strict struct { @@ -12,10 +12,10 @@ type strict struct { // Tracks the current key being processed. key tracker.KeyTracker - missing []decodeError + missing []unstable.ParserError } -func (s *strict) EnterTable(node *ast.Node) { +func (s *strict) EnterTable(node *unstable.Node) { if !s.Enabled { return } @@ -23,7 +23,7 @@ func (s *strict) EnterTable(node *ast.Node) { s.key.UpdateTable(node) } -func (s *strict) EnterArrayTable(node *ast.Node) { +func (s *strict) EnterArrayTable(node *unstable.Node) { if !s.Enabled { return } @@ -31,7 +31,7 @@ func (s *strict) EnterArrayTable(node *ast.Node) { s.key.UpdateArrayTable(node) } -func (s *strict) EnterKeyValue(node *ast.Node) { +func (s *strict) EnterKeyValue(node *unstable.Node) { if !s.Enabled { return } @@ -39,7 +39,7 @@ func (s *strict) EnterKeyValue(node *ast.Node) { s.key.Push(node) } -func (s *strict) ExitKeyValue(node *ast.Node) { +func (s *strict) ExitKeyValue(node *unstable.Node) { if !s.Enabled { return } @@ -47,27 +47,27 @@ func (s *strict) ExitKeyValue(node *ast.Node) { s.key.Pop(node) } -func (s *strict) MissingTable(node *ast.Node) { +func (s *strict) MissingTable(node *unstable.Node) { if !s.Enabled { return } - s.missing = append(s.missing, decodeError{ - highlight: keyLocation(node), - message: "missing table", - key: s.key.Key(), + s.missing = append(s.missing, unstable.ParserError{ + Highlight: keyLocation(node), + Message: "missing table", + Key: s.key.Key(), }) } -func (s *strict) MissingField(node *ast.Node) { +func (s *strict) MissingField(node *unstable.Node) { if !s.Enabled { return } - s.missing = append(s.missing, decodeError{ - highlight: keyLocation(node), - message: "missing field", - key: s.key.Key(), + s.missing = append(s.missing, unstable.ParserError{ + Highlight: keyLocation(node), + Message: "missing field", + Key: s.key.Key(), }) } @@ -88,7 +88,7 @@ func (s *strict) Error(doc []byte) error { return err } -func keyLocation(node *ast.Node) []byte { +func keyLocation(node *unstable.Node) []byte { k := node.Key() hasOne := k.Next() diff --git a/vendor/github.com/pelletier/go-toml/v2/types.go b/vendor/github.com/pelletier/go-toml/v2/types.go index 630a454663..3c6b8fe570 100644 --- a/vendor/github.com/pelletier/go-toml/v2/types.go +++ b/vendor/github.com/pelletier/go-toml/v2/types.go @@ -6,9 +6,9 @@ import ( "time" ) -var timeType = reflect.TypeOf(time.Time{}) -var textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem() -var textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem() -var mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{}) -var sliceInterfaceType = reflect.TypeOf([]interface{}{}) +var timeType = reflect.TypeOf((*time.Time)(nil)).Elem() +var textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() +var textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() +var mapStringInterfaceType = reflect.TypeOf(map[string]interface{}(nil)) +var sliceInterfaceType = reflect.TypeOf([]interface{}(nil)) var stringType = reflect.TypeOf("") diff --git a/vendor/github.com/pelletier/go-toml/v2/unmarshaler.go b/vendor/github.com/pelletier/go-toml/v2/unmarshaler.go index d0d7a72d08..98231bae65 100644 --- a/vendor/github.com/pelletier/go-toml/v2/unmarshaler.go +++ b/vendor/github.com/pelletier/go-toml/v2/unmarshaler.go @@ -12,16 +12,16 @@ import ( "sync/atomic" "time" - "github.com/pelletier/go-toml/v2/internal/ast" "github.com/pelletier/go-toml/v2/internal/danger" "github.com/pelletier/go-toml/v2/internal/tracker" + "github.com/pelletier/go-toml/v2/unstable" ) // Unmarshal deserializes a TOML document into a Go value. // // It is a shortcut for Decoder.Decode() with the default options. func Unmarshal(data []byte, v interface{}) error { - p := parser{} + p := unstable.Parser{} p.Reset(data) d := decoder{p: &p} @@ -35,6 +35,9 @@ type Decoder struct { // global settings strict bool + + // toggles unmarshaler interface + unmarshalerInterface bool } // NewDecoder creates a new Decoder that will read from r. @@ -54,13 +57,31 @@ func (d *Decoder) DisallowUnknownFields() *Decoder { return d } +// EnableUnmarshalerInterface allows to enable unmarshaler interface. +// +// With this feature enabled, types implementing the unstable/Unmarshaler +// interface can be decoded from any structure of the document. It allows types +// that don't have a straightfoward TOML representation to provide their own +// decoding logic. +// +// Currently, types can only decode from a single value. Tables and array tables +// are not supported. +// +// *Unstable:* This method does not follow the compatibility guarantees of +// semver. It can be changed or removed without a new major version being +// issued. +func (d *Decoder) EnableUnmarshalerInterface() *Decoder { + d.unmarshalerInterface = true + return d +} + // Decode the whole content of r into v. // // By default, values in the document that don't exist in the target Go value // are ignored. See Decoder.DisallowUnknownFields() to change this behavior. // // When a TOML local date, time, or date-time is decoded into a time.Time, its -// value is represented in time.Local timezone. Otherwise the approriate Local* +// value is represented in time.Local timezone. Otherwise the appropriate Local* // structure is used. For time values, precision up to the nanosecond is // supported by truncating extra digits. // @@ -101,13 +122,14 @@ func (d *Decoder) Decode(v interface{}) error { return fmt.Errorf("toml: %w", err) } - p := parser{} + p := unstable.Parser{} p.Reset(b) dec := decoder{ p: &p, strict: strict{ Enabled: d.strict, }, + unmarshalerInterface: d.unmarshalerInterface, } return dec.FromParser(v) @@ -115,7 +137,7 @@ func (d *Decoder) Decode(v interface{}) error { type decoder struct { // Which parser instance in use for this decoding session. - p *parser + p *unstable.Parser // Flag indicating that the current expression is stashed. // If set to true, calling nextExpr will not actually pull a new expression @@ -127,6 +149,10 @@ type decoder struct { // need to be skipped. skipUntilTable bool + // Flag indicating that the current array/slice table should be cleared because + // it is the first encounter of an array table. + clearArrayTable bool + // Tracks position in Go arrays. // This is used when decoding [[array tables]] into Go arrays. Given array // tables are separate TOML expression, we need to keep track of where we @@ -139,6 +165,9 @@ type decoder struct { // Strict mode strict strict + // Flag that enables/disables unmarshaler interface. + unmarshalerInterface bool + // Current context for the error. errorContext *errorContext } @@ -149,15 +178,19 @@ type errorContext struct { } func (d *decoder) typeMismatchError(toml string, target reflect.Type) error { + return fmt.Errorf("toml: %s", d.typeMismatchString(toml, target)) +} + +func (d *decoder) typeMismatchString(toml string, target reflect.Type) string { if d.errorContext != nil && d.errorContext.Struct != nil { ctx := d.errorContext f := ctx.Struct.FieldByIndex(ctx.Field) - return fmt.Errorf("toml: cannot decode TOML %s into struct field %s.%s of type %s", toml, ctx.Struct, f.Name, f.Type) + return fmt.Sprintf("cannot decode TOML %s into struct field %s.%s of type %s", toml, ctx.Struct, f.Name, f.Type) } - return fmt.Errorf("toml: cannot decode TOML %s into a Go value of type %s", toml, target) + return fmt.Sprintf("cannot decode TOML %s into a Go value of type %s", toml, target) } -func (d *decoder) expr() *ast.Node { +func (d *decoder) expr() *unstable.Node { return d.p.Expression() } @@ -208,12 +241,12 @@ func (d *decoder) FromParser(v interface{}) error { err := d.fromParser(r) if err == nil { - return d.strict.Error(d.p.data) + return d.strict.Error(d.p.Data()) } - var e *decodeError + var e *unstable.ParserError if errors.As(err, &e) { - return wrapDecodeError(d.p.data, e) + return wrapDecodeError(d.p.Data(), e) } return err @@ -234,42 +267,44 @@ func (d *decoder) fromParser(root reflect.Value) error { Rules for the unmarshal code: - The stack is used to keep track of which values need to be set where. -- handle* functions <=> switch on a given ast.Kind. +- handle* functions <=> switch on a given unstable.Kind. - unmarshalX* functions need to unmarshal a node of kind X. - An "object" is either a struct or a map. */ -func (d *decoder) handleRootExpression(expr *ast.Node, v reflect.Value) error { +func (d *decoder) handleRootExpression(expr *unstable.Node, v reflect.Value) error { var x reflect.Value var err error + var first bool // used for to clear array tables on first use - if !(d.skipUntilTable && expr.Kind == ast.KeyValue) { - err = d.seen.CheckExpression(expr) + if !(d.skipUntilTable && expr.Kind == unstable.KeyValue) { + first, err = d.seen.CheckExpression(expr) if err != nil { return err } } switch expr.Kind { - case ast.KeyValue: + case unstable.KeyValue: if d.skipUntilTable { return nil } x, err = d.handleKeyValue(expr, v) - case ast.Table: + case unstable.Table: d.skipUntilTable = false d.strict.EnterTable(expr) x, err = d.handleTable(expr.Key(), v) - case ast.ArrayTable: + case unstable.ArrayTable: d.skipUntilTable = false d.strict.EnterArrayTable(expr) + d.clearArrayTable = first x, err = d.handleArrayTable(expr.Key(), v) default: panic(fmt.Errorf("parser should not permit expression of kind %s at document root", expr.Kind)) } if d.skipUntilTable { - if expr.Kind == ast.Table || expr.Kind == ast.ArrayTable { + if expr.Kind == unstable.Table || expr.Kind == unstable.ArrayTable { d.strict.MissingTable(expr) } } else if err == nil && x.IsValid() { @@ -279,14 +314,14 @@ func (d *decoder) handleRootExpression(expr *ast.Node, v reflect.Value) error { return err } -func (d *decoder) handleArrayTable(key ast.Iterator, v reflect.Value) (reflect.Value, error) { +func (d *decoder) handleArrayTable(key unstable.Iterator, v reflect.Value) (reflect.Value, error) { if key.Next() { return d.handleArrayTablePart(key, v) } return d.handleKeyValues(v) } -func (d *decoder) handleArrayTableCollectionLast(key ast.Iterator, v reflect.Value) (reflect.Value, error) { +func (d *decoder) handleArrayTableCollectionLast(key unstable.Iterator, v reflect.Value) (reflect.Value, error) { switch v.Kind() { case reflect.Interface: elem := v.Elem() @@ -303,6 +338,10 @@ func (d *decoder) handleArrayTableCollectionLast(key ast.Iterator, v reflect.Val reflect.Copy(nelem, elem) elem = nelem } + if d.clearArrayTable && elem.Len() > 0 { + elem.SetLen(0) + d.clearArrayTable = false + } } return d.handleArrayTableCollectionLast(key, elem) case reflect.Ptr: @@ -321,6 +360,10 @@ func (d *decoder) handleArrayTableCollectionLast(key ast.Iterator, v reflect.Val return v, nil case reflect.Slice: + if d.clearArrayTable && v.Len() > 0 { + v.SetLen(0) + d.clearArrayTable = false + } elemType := v.Type().Elem() var elem reflect.Value if elemType.Kind() == reflect.Interface { @@ -339,13 +382,13 @@ func (d *decoder) handleArrayTableCollectionLast(key ast.Iterator, v reflect.Val case reflect.Array: idx := d.arrayIndex(true, v) if idx >= v.Len() { - return v, fmt.Errorf("toml: cannot decode array table into %s at position %d", v.Type(), idx) + return v, fmt.Errorf("%s at position %d", d.typeMismatchError("array table", v.Type()), idx) } elem := v.Index(idx) _, err := d.handleArrayTable(key, elem) return v, err default: - return reflect.Value{}, fmt.Errorf("toml: cannot decode array table into a %s", v.Type()) + return reflect.Value{}, d.typeMismatchError("array table", v.Type()) } } @@ -353,7 +396,7 @@ func (d *decoder) handleArrayTableCollectionLast(key ast.Iterator, v reflect.Val // evaluated like a normal key, but if it returns a collection, it also needs to // point to the last element of the collection. Unless it is the last part of // the key, then it needs to create a new element at the end. -func (d *decoder) handleArrayTableCollection(key ast.Iterator, v reflect.Value) (reflect.Value, error) { +func (d *decoder) handleArrayTableCollection(key unstable.Iterator, v reflect.Value) (reflect.Value, error) { if key.IsLast() { return d.handleArrayTableCollectionLast(key, v) } @@ -390,7 +433,7 @@ func (d *decoder) handleArrayTableCollection(key ast.Iterator, v reflect.Value) case reflect.Array: idx := d.arrayIndex(false, v) if idx >= v.Len() { - return v, fmt.Errorf("toml: cannot decode array table into %s at position %d", v.Type(), idx) + return v, fmt.Errorf("%s at position %d", d.typeMismatchError("array table", v.Type()), idx) } elem := v.Index(idx) _, err := d.handleArrayTable(key, elem) @@ -400,7 +443,7 @@ func (d *decoder) handleArrayTableCollection(key ast.Iterator, v reflect.Value) return d.handleArrayTable(key, v) } -func (d *decoder) handleKeyPart(key ast.Iterator, v reflect.Value, nextFn handlerFn, makeFn valueMakerFn) (reflect.Value, error) { +func (d *decoder) handleKeyPart(key unstable.Iterator, v reflect.Value, nextFn handlerFn, makeFn valueMakerFn) (reflect.Value, error) { var rv reflect.Value // First, dispatch over v to make sure it is a valid object. @@ -417,7 +460,10 @@ func (d *decoder) handleKeyPart(key ast.Iterator, v reflect.Value, nextFn handle vt := v.Type() // Create the key for the map element. Convert to key type. - mk := reflect.ValueOf(string(key.Node().Data)).Convert(vt.Key()) + mk, err := d.keyFromData(vt.Key(), key.Node().Data) + if err != nil { + return reflect.Value{}, err + } // If the map does not exist, create it. if v.IsNil() { @@ -518,7 +564,7 @@ func (d *decoder) handleKeyPart(key ast.Iterator, v reflect.Value, nextFn handle // HandleArrayTablePart navigates the Go structure v using the key v. It is // only used for the prefix (non-last) parts of an array-table. When // encountering a collection, it should go to the last element. -func (d *decoder) handleArrayTablePart(key ast.Iterator, v reflect.Value) (reflect.Value, error) { +func (d *decoder) handleArrayTablePart(key unstable.Iterator, v reflect.Value) (reflect.Value, error) { var makeFn valueMakerFn if key.IsLast() { makeFn = makeSliceInterface @@ -530,10 +576,10 @@ func (d *decoder) handleArrayTablePart(key ast.Iterator, v reflect.Value) (refle // HandleTable returns a reference when it has checked the next expression but // cannot handle it. -func (d *decoder) handleTable(key ast.Iterator, v reflect.Value) (reflect.Value, error) { +func (d *decoder) handleTable(key unstable.Iterator, v reflect.Value) (reflect.Value, error) { if v.Kind() == reflect.Slice { if v.Len() == 0 { - return reflect.Value{}, newDecodeError(key.Node().Data, "cannot store a table in a slice") + return reflect.Value{}, unstable.NewParserError(key.Node().Data, "cannot store a table in a slice") } elem := v.Index(v.Len() - 1) x, err := d.handleTable(key, elem) @@ -560,7 +606,7 @@ func (d *decoder) handleKeyValues(v reflect.Value) (reflect.Value, error) { var rv reflect.Value for d.nextExpr() { expr := d.expr() - if expr.Kind != ast.KeyValue { + if expr.Kind != unstable.KeyValue { // Stash the expression so that fromParser can just loop and use // the right handler. // We could just recurse ourselves here, but at least this gives a @@ -569,7 +615,7 @@ func (d *decoder) handleKeyValues(v reflect.Value) (reflect.Value, error) { break } - err := d.seen.CheckExpression(expr) + _, err := d.seen.CheckExpression(expr) if err != nil { return reflect.Value{}, err } @@ -587,7 +633,7 @@ func (d *decoder) handleKeyValues(v reflect.Value) (reflect.Value, error) { } type ( - handlerFn func(key ast.Iterator, v reflect.Value) (reflect.Value, error) + handlerFn func(key unstable.Iterator, v reflect.Value) (reflect.Value, error) valueMakerFn func() reflect.Value ) @@ -599,11 +645,11 @@ func makeSliceInterface() reflect.Value { return reflect.MakeSlice(sliceInterfaceType, 0, 16) } -func (d *decoder) handleTablePart(key ast.Iterator, v reflect.Value) (reflect.Value, error) { +func (d *decoder) handleTablePart(key unstable.Iterator, v reflect.Value) (reflect.Value, error) { return d.handleKeyPart(key, v, d.handleTable, makeMapStringInterface) } -func (d *decoder) tryTextUnmarshaler(node *ast.Node, v reflect.Value) (bool, error) { +func (d *decoder) tryTextUnmarshaler(node *unstable.Node, v reflect.Value) (bool, error) { // Special case for time, because we allow to unmarshal to it from // different kind of AST nodes. if v.Type() == timeType { @@ -613,7 +659,7 @@ func (d *decoder) tryTextUnmarshaler(node *ast.Node, v reflect.Value) (bool, err if v.CanAddr() && v.Addr().Type().Implements(textUnmarshalerType) { err := v.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText(node.Data) if err != nil { - return false, newDecodeError(d.p.Raw(node.Raw), "%w", err) + return false, unstable.NewParserError(d.p.Raw(node.Raw), "%w", err) } return true, nil @@ -622,43 +668,51 @@ func (d *decoder) tryTextUnmarshaler(node *ast.Node, v reflect.Value) (bool, err return false, nil } -func (d *decoder) handleValue(value *ast.Node, v reflect.Value) error { +func (d *decoder) handleValue(value *unstable.Node, v reflect.Value) error { for v.Kind() == reflect.Ptr { v = initAndDereferencePointer(v) } + if d.unmarshalerInterface { + if v.CanAddr() && v.Addr().CanInterface() { + if outi, ok := v.Addr().Interface().(unstable.Unmarshaler); ok { + return outi.UnmarshalTOML(value) + } + } + } + ok, err := d.tryTextUnmarshaler(value, v) if ok || err != nil { return err } switch value.Kind { - case ast.String: + case unstable.String: return d.unmarshalString(value, v) - case ast.Integer: + case unstable.Integer: return d.unmarshalInteger(value, v) - case ast.Float: + case unstable.Float: return d.unmarshalFloat(value, v) - case ast.Bool: + case unstable.Bool: return d.unmarshalBool(value, v) - case ast.DateTime: + case unstable.DateTime: return d.unmarshalDateTime(value, v) - case ast.LocalDate: + case unstable.LocalDate: return d.unmarshalLocalDate(value, v) - case ast.LocalTime: + case unstable.LocalTime: return d.unmarshalLocalTime(value, v) - case ast.LocalDateTime: + case unstable.LocalDateTime: return d.unmarshalLocalDateTime(value, v) - case ast.InlineTable: + case unstable.InlineTable: return d.unmarshalInlineTable(value, v) - case ast.Array: + case unstable.Array: return d.unmarshalArray(value, v) default: panic(fmt.Errorf("handleValue not implemented for %s", value.Kind)) } } -func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error { +func (d *decoder) unmarshalArray(array *unstable.Node, v reflect.Value) error { switch v.Kind() { case reflect.Slice: if v.IsNil() { @@ -729,7 +783,7 @@ func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error { return nil } -func (d *decoder) unmarshalInlineTable(itable *ast.Node, v reflect.Value) error { +func (d *decoder) unmarshalInlineTable(itable *unstable.Node, v reflect.Value) error { // Make sure v is an initialized object. switch v.Kind() { case reflect.Map: @@ -746,7 +800,7 @@ func (d *decoder) unmarshalInlineTable(itable *ast.Node, v reflect.Value) error } return d.unmarshalInlineTable(itable, elem) default: - return newDecodeError(itable.Data, "cannot store inline table in Go type %s", v.Kind()) + return unstable.NewParserError(d.p.Raw(itable.Raw), "cannot store inline table in Go type %s", v.Kind()) } it := itable.Children() @@ -765,7 +819,7 @@ func (d *decoder) unmarshalInlineTable(itable *ast.Node, v reflect.Value) error return nil } -func (d *decoder) unmarshalDateTime(value *ast.Node, v reflect.Value) error { +func (d *decoder) unmarshalDateTime(value *unstable.Node, v reflect.Value) error { dt, err := parseDateTime(value.Data) if err != nil { return err @@ -775,7 +829,7 @@ func (d *decoder) unmarshalDateTime(value *ast.Node, v reflect.Value) error { return nil } -func (d *decoder) unmarshalLocalDate(value *ast.Node, v reflect.Value) error { +func (d *decoder) unmarshalLocalDate(value *unstable.Node, v reflect.Value) error { ld, err := parseLocalDate(value.Data) if err != nil { return err @@ -792,28 +846,28 @@ func (d *decoder) unmarshalLocalDate(value *ast.Node, v reflect.Value) error { return nil } -func (d *decoder) unmarshalLocalTime(value *ast.Node, v reflect.Value) error { +func (d *decoder) unmarshalLocalTime(value *unstable.Node, v reflect.Value) error { lt, rest, err := parseLocalTime(value.Data) if err != nil { return err } if len(rest) > 0 { - return newDecodeError(rest, "extra characters at the end of a local time") + return unstable.NewParserError(rest, "extra characters at the end of a local time") } v.Set(reflect.ValueOf(lt)) return nil } -func (d *decoder) unmarshalLocalDateTime(value *ast.Node, v reflect.Value) error { +func (d *decoder) unmarshalLocalDateTime(value *unstable.Node, v reflect.Value) error { ldt, rest, err := parseLocalDateTime(value.Data) if err != nil { return err } if len(rest) > 0 { - return newDecodeError(rest, "extra characters at the end of a local date time") + return unstable.NewParserError(rest, "extra characters at the end of a local date time") } if v.Type() == timeType { @@ -828,7 +882,7 @@ func (d *decoder) unmarshalLocalDateTime(value *ast.Node, v reflect.Value) error return nil } -func (d *decoder) unmarshalBool(value *ast.Node, v reflect.Value) error { +func (d *decoder) unmarshalBool(value *unstable.Node, v reflect.Value) error { b := value.Data[0] == 't' switch v.Kind() { @@ -837,13 +891,13 @@ func (d *decoder) unmarshalBool(value *ast.Node, v reflect.Value) error { case reflect.Interface: v.Set(reflect.ValueOf(b)) default: - return newDecodeError(value.Data, "cannot assign boolean to a %t", b) + return unstable.NewParserError(value.Data, "cannot assign boolean to a %t", b) } return nil } -func (d *decoder) unmarshalFloat(value *ast.Node, v reflect.Value) error { +func (d *decoder) unmarshalFloat(value *unstable.Node, v reflect.Value) error { f, err := parseFloat(value.Data) if err != nil { return err @@ -854,13 +908,13 @@ func (d *decoder) unmarshalFloat(value *ast.Node, v reflect.Value) error { v.SetFloat(f) case reflect.Float32: if f > math.MaxFloat32 { - return newDecodeError(value.Data, "number %f does not fit in a float32", f) + return unstable.NewParserError(value.Data, "number %f does not fit in a float32", f) } v.SetFloat(f) case reflect.Interface: v.Set(reflect.ValueOf(f)) default: - return newDecodeError(value.Data, "float cannot be assigned to %s", v.Kind()) + return unstable.NewParserError(value.Data, "float cannot be assigned to %s", v.Kind()) } return nil @@ -886,7 +940,12 @@ func init() { } } -func (d *decoder) unmarshalInteger(value *ast.Node, v reflect.Value) error { +func (d *decoder) unmarshalInteger(value *unstable.Node, v reflect.Value) error { + kind := v.Kind() + if kind == reflect.Float32 || kind == reflect.Float64 { + return d.unmarshalFloat(value, v) + } + i, err := parseInteger(value.Data) if err != nil { return err @@ -894,7 +953,7 @@ func (d *decoder) unmarshalInteger(value *ast.Node, v reflect.Value) error { var r reflect.Value - switch v.Kind() { + switch kind { case reflect.Int64: v.SetInt(i) return nil @@ -955,7 +1014,7 @@ func (d *decoder) unmarshalInteger(value *ast.Node, v reflect.Value) error { case reflect.Interface: r = reflect.ValueOf(i) default: - return d.typeMismatchError("integer", v.Type()) + return unstable.NewParserError(d.p.Raw(value.Raw), d.typeMismatchString("integer", v.Type())) } if !r.Type().AssignableTo(v.Type()) { @@ -967,20 +1026,20 @@ func (d *decoder) unmarshalInteger(value *ast.Node, v reflect.Value) error { return nil } -func (d *decoder) unmarshalString(value *ast.Node, v reflect.Value) error { +func (d *decoder) unmarshalString(value *unstable.Node, v reflect.Value) error { switch v.Kind() { case reflect.String: v.SetString(string(value.Data)) case reflect.Interface: v.Set(reflect.ValueOf(string(value.Data))) default: - return newDecodeError(d.p.Raw(value.Raw), "cannot store TOML string into a Go %s", v.Kind()) + return unstable.NewParserError(d.p.Raw(value.Raw), d.typeMismatchString("string", v.Type())) } return nil } -func (d *decoder) handleKeyValue(expr *ast.Node, v reflect.Value) (reflect.Value, error) { +func (d *decoder) handleKeyValue(expr *unstable.Node, v reflect.Value) (reflect.Value, error) { d.strict.EnterKeyValue(expr) v, err := d.handleKeyValueInner(expr.Key(), expr.Value(), v) @@ -994,7 +1053,7 @@ func (d *decoder) handleKeyValue(expr *ast.Node, v reflect.Value) (reflect.Value return v, err } -func (d *decoder) handleKeyValueInner(key ast.Iterator, value *ast.Node, v reflect.Value) (reflect.Value, error) { +func (d *decoder) handleKeyValueInner(key unstable.Iterator, value *unstable.Node, v reflect.Value) (reflect.Value, error) { if key.Next() { // Still scoping the key return d.handleKeyValuePart(key, value, v) @@ -1004,7 +1063,32 @@ func (d *decoder) handleKeyValueInner(key ast.Iterator, value *ast.Node, v refle return reflect.Value{}, d.handleValue(value, v) } -func (d *decoder) handleKeyValuePart(key ast.Iterator, value *ast.Node, v reflect.Value) (reflect.Value, error) { +func (d *decoder) keyFromData(keyType reflect.Type, data []byte) (reflect.Value, error) { + switch { + case stringType.AssignableTo(keyType): + return reflect.ValueOf(string(data)), nil + + case stringType.ConvertibleTo(keyType): + return reflect.ValueOf(string(data)).Convert(keyType), nil + + case keyType.Implements(textUnmarshalerType): + mk := reflect.New(keyType.Elem()) + if err := mk.Interface().(encoding.TextUnmarshaler).UnmarshalText(data); err != nil { + return reflect.Value{}, fmt.Errorf("toml: error unmarshalling key type %s from text: %w", stringType, err) + } + return mk, nil + + case reflect.PtrTo(keyType).Implements(textUnmarshalerType): + mk := reflect.New(keyType) + if err := mk.Interface().(encoding.TextUnmarshaler).UnmarshalText(data); err != nil { + return reflect.Value{}, fmt.Errorf("toml: error unmarshalling key type %s from text: %w", stringType, err) + } + return mk.Elem(), nil + } + return reflect.Value{}, fmt.Errorf("toml: cannot convert map key of type %s to expected type %s", stringType, keyType) +} + +func (d *decoder) handleKeyValuePart(key unstable.Iterator, value *unstable.Node, v reflect.Value) (reflect.Value, error) { // contains the replacement for v var rv reflect.Value @@ -1014,16 +1098,9 @@ func (d *decoder) handleKeyValuePart(key ast.Iterator, value *ast.Node, v reflec case reflect.Map: vt := v.Type() - mk := reflect.ValueOf(string(key.Node().Data)) - mkt := stringType - - keyType := vt.Key() - if !mkt.AssignableTo(keyType) { - if !mkt.ConvertibleTo(keyType) { - return reflect.Value{}, fmt.Errorf("toml: cannot convert map key of type %s to expected type %s", mkt, keyType) - } - - mk = mk.Convert(keyType) + mk, err := d.keyFromData(vt.Key(), key.Node().Data) + if err != nil { + return reflect.Value{}, err } // If the map does not exist, create it. @@ -1034,15 +1111,9 @@ func (d *decoder) handleKeyValuePart(key ast.Iterator, value *ast.Node, v reflec mv := v.MapIndex(mk) set := false - if !mv.IsValid() { + if !mv.IsValid() || key.IsLast() { set = true mv = reflect.New(v.Type().Elem()).Elem() - } else { - if key.IsLast() { - var x interface{} - mv = reflect.ValueOf(&x).Elem() - set = true - } } nv, err := d.handleKeyValueInner(key, value, mv) @@ -1072,6 +1143,19 @@ func (d *decoder) handleKeyValuePart(key ast.Iterator, value *ast.Node, v reflec d.errorContext.Field = path f := fieldByIndex(v, path) + + if !f.CanAddr() { + // If the field is not addressable, need to take a slower path and + // make a copy of the struct itself to a new location. + nvp := reflect.New(v.Type()) + nvp.Elem().Set(v) + v = nvp.Elem() + _, err := d.handleKeyValuePart(key, value, v) + if err != nil { + return reflect.Value{}, err + } + return nvp.Elem(), nil + } x, err := d.handleKeyValueInner(key, value, f) if err != nil { return reflect.Value{}, err @@ -1137,10 +1221,10 @@ func initAndDereferencePointer(v reflect.Value) reflect.Value { // Same as reflect.Value.FieldByIndex, but creates pointers if needed. func fieldByIndex(v reflect.Value, path []int) reflect.Value { - for i, x := range path { + for _, x := range path { v = v.Field(x) - if i < len(path)-1 && v.Kind() == reflect.Ptr { + if v.Kind() == reflect.Ptr { if v.IsNil() { v.Set(reflect.New(v.Type().Elem())) } diff --git a/vendor/github.com/pelletier/go-toml/v2/internal/ast/ast.go b/vendor/github.com/pelletier/go-toml/v2/unstable/ast.go similarity index 60% rename from vendor/github.com/pelletier/go-toml/v2/internal/ast/ast.go rename to vendor/github.com/pelletier/go-toml/v2/unstable/ast.go index 9dec2e0007..f526bf2c09 100644 --- a/vendor/github.com/pelletier/go-toml/v2/internal/ast/ast.go +++ b/vendor/github.com/pelletier/go-toml/v2/unstable/ast.go @@ -1,4 +1,4 @@ -package ast +package unstable import ( "fmt" @@ -7,13 +7,16 @@ import ( "github.com/pelletier/go-toml/v2/internal/danger" ) -// Iterator starts uninitialized, you need to call Next() first. +// Iterator over a sequence of nodes. +// +// Starts uninitialized, you need to call Next() first. // // For example: // // it := n.Children() // for it.Next() { -// it.Node() +// n := it.Node() +// // do something with n // } type Iterator struct { started bool @@ -32,42 +35,31 @@ func (c *Iterator) Next() bool { } // IsLast returns true if the current node of the iterator is the last -// one. Subsequent call to Next() will return false. +// one. Subsequent calls to Next() will return false. func (c *Iterator) IsLast() bool { return c.node.next == 0 } -// Node returns a copy of the node pointed at by the iterator. +// Node returns a pointer to the node pointed at by the iterator. func (c *Iterator) Node() *Node { return c.node } -// Root contains a full AST. +// Node in a TOML expression AST. // -// It is immutable once constructed with Builder. -type Root struct { - nodes []Node -} - -// Iterator over the top level nodes. -func (r *Root) Iterator() Iterator { - it := Iterator{} - if len(r.nodes) > 0 { - it.node = &r.nodes[0] - } - return it -} - -func (r *Root) at(idx Reference) *Node { - return &r.nodes[idx] -} - -// Arrays have one child per element in the array. InlineTables have -// one child per key-value pair in the table. KeyValues have at least -// two children. The first one is the value. The rest make a -// potentially dotted key. Table and Array table have one child per -// element of the key they represent (same as KeyValue, but without -// the last node being the value). +// Depending on Kind, its sequence of children should be interpreted +// differently. +// +// - Array have one child per element in the array. +// - InlineTable have one child per key-value in the table (each of kind +// InlineTable). +// - KeyValue have at least two children. The first one is the value. The rest +// make a potentially dotted key. +// - Table and ArrayTable's children represent a dotted key (same as +// KeyValue, but without the first node being the value). +// +// When relevant, Raw describes the range of bytes this node is referring to in +// the input document. Use Parser.Raw() to retrieve the actual bytes. type Node struct { Kind Kind Raw Range // Raw bytes from the input. @@ -80,13 +72,13 @@ type Node struct { child int // 0 if no child } +// Range of bytes in the document. type Range struct { Offset uint32 Length uint32 } -// Next returns a copy of the next node, or an invalid Node if there -// is no next node. +// Next returns a pointer to the next node, or nil if there is no next node. func (n *Node) Next() *Node { if n.next == 0 { return nil @@ -96,9 +88,9 @@ func (n *Node) Next() *Node { return (*Node)(danger.Stride(ptr, size, n.next)) } -// Child returns a copy of the first child node of this node. Other -// children can be accessed calling Next on the first child. Returns -// an invalid Node if there is none. +// Child returns a pointer to the first child node of this node. Other children +// can be accessed calling Next on the first child. Returns an nil if this Node +// has no child. func (n *Node) Child() *Node { if n.child == 0 { return nil @@ -113,9 +105,9 @@ func (n *Node) Valid() bool { return n != nil } -// Key returns the child nodes making the Key on a supported -// node. Panics otherwise. They are guaranteed to be all be of the -// Kind Key. A simple key would return just one element. +// Key returns the children nodes making the Key on a supported node. Panics +// otherwise. They are guaranteed to be all be of the Kind Key. A simple key +// would return just one element. func (n *Node) Key() Iterator { switch n.Kind { case KeyValue: diff --git a/vendor/github.com/pelletier/go-toml/v2/unstable/builder.go b/vendor/github.com/pelletier/go-toml/v2/unstable/builder.go new file mode 100644 index 0000000000..9538e30df9 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/unstable/builder.go @@ -0,0 +1,71 @@ +package unstable + +// root contains a full AST. +// +// It is immutable once constructed with Builder. +type root struct { + nodes []Node +} + +// Iterator over the top level nodes. +func (r *root) Iterator() Iterator { + it := Iterator{} + if len(r.nodes) > 0 { + it.node = &r.nodes[0] + } + return it +} + +func (r *root) at(idx reference) *Node { + return &r.nodes[idx] +} + +type reference int + +const invalidReference reference = -1 + +func (r reference) Valid() bool { + return r != invalidReference +} + +type builder struct { + tree root + lastIdx int +} + +func (b *builder) Tree() *root { + return &b.tree +} + +func (b *builder) NodeAt(ref reference) *Node { + return b.tree.at(ref) +} + +func (b *builder) Reset() { + b.tree.nodes = b.tree.nodes[:0] + b.lastIdx = 0 +} + +func (b *builder) Push(n Node) reference { + b.lastIdx = len(b.tree.nodes) + b.tree.nodes = append(b.tree.nodes, n) + return reference(b.lastIdx) +} + +func (b *builder) PushAndChain(n Node) reference { + newIdx := len(b.tree.nodes) + b.tree.nodes = append(b.tree.nodes, n) + if b.lastIdx >= 0 { + b.tree.nodes[b.lastIdx].next = newIdx - b.lastIdx + } + b.lastIdx = newIdx + return reference(b.lastIdx) +} + +func (b *builder) AttachChild(parent reference, child reference) { + b.tree.nodes[parent].child = int(child) - int(parent) +} + +func (b *builder) Chain(from reference, to reference) { + b.tree.nodes[from].next = int(to) - int(from) +} diff --git a/vendor/github.com/pelletier/go-toml/v2/unstable/doc.go b/vendor/github.com/pelletier/go-toml/v2/unstable/doc.go new file mode 100644 index 0000000000..7ff26c53c7 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/unstable/doc.go @@ -0,0 +1,3 @@ +// Package unstable provides APIs that do not meet the backward compatibility +// guarantees yet. +package unstable diff --git a/vendor/github.com/pelletier/go-toml/v2/internal/ast/kind.go b/vendor/github.com/pelletier/go-toml/v2/unstable/kind.go similarity index 81% rename from vendor/github.com/pelletier/go-toml/v2/internal/ast/kind.go rename to vendor/github.com/pelletier/go-toml/v2/unstable/kind.go index 2b50c67fce..ff9df1bef8 100644 --- a/vendor/github.com/pelletier/go-toml/v2/internal/ast/kind.go +++ b/vendor/github.com/pelletier/go-toml/v2/unstable/kind.go @@ -1,25 +1,26 @@ -package ast +package unstable import "fmt" +// Kind represents the type of TOML structure contained in a given Node. type Kind int const ( - // meta + // Meta Invalid Kind = iota Comment Key - // top level structures + // Top level structures Table ArrayTable KeyValue - // containers values + // Containers values Array InlineTable - // values + // Values String Bool Float @@ -30,6 +31,7 @@ const ( DateTime ) +// String implementation of fmt.Stringer. func (k Kind) String() string { switch k { case Invalid: diff --git a/vendor/github.com/pelletier/go-toml/v2/parser.go b/vendor/github.com/pelletier/go-toml/v2/unstable/parser.go similarity index 62% rename from vendor/github.com/pelletier/go-toml/v2/parser.go rename to vendor/github.com/pelletier/go-toml/v2/unstable/parser.go index 9859a795bd..50358a44ff 100644 --- a/vendor/github.com/pelletier/go-toml/v2/parser.go +++ b/vendor/github.com/pelletier/go-toml/v2/unstable/parser.go @@ -1,50 +1,108 @@ -package toml +package unstable import ( "bytes" + "fmt" "unicode" - "github.com/pelletier/go-toml/v2/internal/ast" + "github.com/pelletier/go-toml/v2/internal/characters" "github.com/pelletier/go-toml/v2/internal/danger" ) -type parser struct { - builder ast.Builder - ref ast.Reference +// ParserError describes an error relative to the content of the document. +// +// It cannot outlive the instance of Parser it refers to, and may cause panics +// if the parser is reset. +type ParserError struct { + Highlight []byte + Message string + Key []string // optional +} + +// Error is the implementation of the error interface. +func (e *ParserError) Error() string { + return e.Message +} + +// NewParserError is a convenience function to create a ParserError +// +// Warning: Highlight needs to be a subslice of Parser.data, so only slices +// returned by Parser.Raw are valid candidates. +func NewParserError(highlight []byte, format string, args ...interface{}) error { + return &ParserError{ + Highlight: highlight, + Message: fmt.Errorf(format, args...).Error(), + } +} + +// Parser scans over a TOML-encoded document and generates an iterative AST. +// +// To prime the Parser, first reset it with the contents of a TOML document. +// Then, process all top-level expressions sequentially. See Example. +// +// Don't forget to check Error() after you're done parsing. +// +// Each top-level expression needs to be fully processed before calling +// NextExpression() again. Otherwise, calls to various Node methods may panic if +// the parser has moved on the next expression. +// +// For performance reasons, go-toml doesn't make a copy of the input bytes to +// the parser. Make sure to copy all the bytes you need to outlive the slice +// given to the parser. +type Parser struct { data []byte + builder builder + ref reference left []byte err error first bool + + KeepComments bool +} + +// Data returns the slice provided to the last call to Reset. +func (p *Parser) Data() []byte { + return p.data } -func (p *parser) Range(b []byte) ast.Range { - return ast.Range{ +// Range returns a range description that corresponds to a given slice of the +// input. If the argument is not a subslice of the parser input, this function +// panics. +func (p *Parser) Range(b []byte) Range { + return Range{ Offset: uint32(danger.SubsliceOffset(p.data, b)), Length: uint32(len(b)), } } -func (p *parser) Raw(raw ast.Range) []byte { +// Raw returns the slice corresponding to the bytes in the given range. +func (p *Parser) Raw(raw Range) []byte { return p.data[raw.Offset : raw.Offset+raw.Length] } -func (p *parser) Reset(b []byte) { +// Reset brings the parser to its initial state for a given input. It wipes an +// reuses internal storage to reduce allocation. +func (p *Parser) Reset(b []byte) { p.builder.Reset() - p.ref = ast.InvalidReference + p.ref = invalidReference p.data = b p.left = b p.err = nil p.first = true } -//nolint:cyclop -func (p *parser) NextExpression() bool { +// NextExpression parses the next top-level expression. If an expression was +// successfully parsed, it returns true. If the parser is at the end of the +// document or an error occurred, it returns false. +// +// Retrieve the parsed expression with Expression(). +func (p *Parser) NextExpression() bool { if len(p.left) == 0 || p.err != nil { return false } p.builder.Reset() - p.ref = ast.InvalidReference + p.ref = invalidReference for { if len(p.left) == 0 || p.err != nil { @@ -73,15 +131,56 @@ func (p *parser) NextExpression() bool { } } -func (p *parser) Expression() *ast.Node { +// Expression returns a pointer to the node representing the last successfully +// parsed expression. +func (p *Parser) Expression() *Node { return p.builder.NodeAt(p.ref) } -func (p *parser) Error() error { +// Error returns any error that has occurred during parsing. +func (p *Parser) Error() error { return p.err } -func (p *parser) parseNewline(b []byte) ([]byte, error) { +// Position describes a position in the input. +type Position struct { + // Number of bytes from the beginning of the input. + Offset int + // Line number, starting at 1. + Line int + // Column number, starting at 1. + Column int +} + +// Shape describes the position of a range in the input. +type Shape struct { + Start Position + End Position +} + +func (p *Parser) position(b []byte) Position { + offset := danger.SubsliceOffset(p.data, b) + + lead := p.data[:offset] + + return Position{ + Offset: offset, + Line: bytes.Count(lead, []byte{'\n'}) + 1, + Column: len(lead) - bytes.LastIndex(lead, []byte{'\n'}), + } +} + +// Shape returns the shape of the given range in the input. Will +// panic if the range is not a subslice of the input. +func (p *Parser) Shape(r Range) Shape { + raw := p.Raw(r) + return Shape{ + Start: p.position(raw), + End: p.position(raw[r.Length:]), + } +} + +func (p *Parser) parseNewline(b []byte) ([]byte, error) { if b[0] == '\n' { return b[1:], nil } @@ -91,14 +190,27 @@ func (p *parser) parseNewline(b []byte) ([]byte, error) { return rest, err } - return nil, newDecodeError(b[0:1], "expected newline but got %#U", b[0]) + return nil, NewParserError(b[0:1], "expected newline but got %#U", b[0]) +} + +func (p *Parser) parseComment(b []byte) (reference, []byte, error) { + ref := invalidReference + data, rest, err := scanComment(b) + if p.KeepComments && err == nil { + ref = p.builder.Push(Node{ + Kind: Comment, + Raw: p.Range(data), + Data: data, + }) + } + return ref, rest, err } -func (p *parser) parseExpression(b []byte) (ast.Reference, []byte, error) { +func (p *Parser) parseExpression(b []byte) (reference, []byte, error) { // expression = ws [ comment ] // expression =/ ws keyval ws [ comment ] // expression =/ ws table ws [ comment ] - ref := ast.InvalidReference + ref := invalidReference b = p.parseWhitespace(b) @@ -107,7 +219,7 @@ func (p *parser) parseExpression(b []byte) (ast.Reference, []byte, error) { } if b[0] == '#' { - _, rest, err := scanComment(b) + ref, rest, err := p.parseComment(b) return ref, rest, err } @@ -129,14 +241,17 @@ func (p *parser) parseExpression(b []byte) (ast.Reference, []byte, error) { b = p.parseWhitespace(b) if len(b) > 0 && b[0] == '#' { - _, rest, err := scanComment(b) + cref, rest, err := p.parseComment(b) + if cref != invalidReference { + p.builder.Chain(ref, cref) + } return ref, rest, err } return ref, b, nil } -func (p *parser) parseTable(b []byte) (ast.Reference, []byte, error) { +func (p *Parser) parseTable(b []byte) (reference, []byte, error) { // table = std-table / array-table if len(b) > 1 && b[1] == '[' { return p.parseArrayTable(b) @@ -145,12 +260,12 @@ func (p *parser) parseTable(b []byte) (ast.Reference, []byte, error) { return p.parseStdTable(b) } -func (p *parser) parseArrayTable(b []byte) (ast.Reference, []byte, error) { +func (p *Parser) parseArrayTable(b []byte) (reference, []byte, error) { // array-table = array-table-open key array-table-close // array-table-open = %x5B.5B ws ; [[ Double left square bracket // array-table-close = ws %x5D.5D ; ]] Double right square bracket - ref := p.builder.Push(ast.Node{ - Kind: ast.ArrayTable, + ref := p.builder.Push(Node{ + Kind: ArrayTable, }) b = b[2:] @@ -174,12 +289,12 @@ func (p *parser) parseArrayTable(b []byte) (ast.Reference, []byte, error) { return ref, b, err } -func (p *parser) parseStdTable(b []byte) (ast.Reference, []byte, error) { +func (p *Parser) parseStdTable(b []byte) (reference, []byte, error) { // std-table = std-table-open key std-table-close // std-table-open = %x5B ws ; [ Left square bracket // std-table-close = ws %x5D ; ] Right square bracket - ref := p.builder.Push(ast.Node{ - Kind: ast.Table, + ref := p.builder.Push(Node{ + Kind: Table, }) b = b[1:] @@ -199,15 +314,15 @@ func (p *parser) parseStdTable(b []byte) (ast.Reference, []byte, error) { return ref, b, err } -func (p *parser) parseKeyval(b []byte) (ast.Reference, []byte, error) { +func (p *Parser) parseKeyval(b []byte) (reference, []byte, error) { // keyval = key keyval-sep val - ref := p.builder.Push(ast.Node{ - Kind: ast.KeyValue, + ref := p.builder.Push(Node{ + Kind: KeyValue, }) key, b, err := p.parseKey(b) if err != nil { - return ast.InvalidReference, nil, err + return invalidReference, nil, err } // keyval-sep = ws %x3D ws ; = @@ -215,12 +330,12 @@ func (p *parser) parseKeyval(b []byte) (ast.Reference, []byte, error) { b = p.parseWhitespace(b) if len(b) == 0 { - return ast.InvalidReference, nil, newDecodeError(b, "expected = after a key, but the document ends there") + return invalidReference, nil, NewParserError(b, "expected = after a key, but the document ends there") } b, err = expect('=', b) if err != nil { - return ast.InvalidReference, nil, err + return invalidReference, nil, err } b = p.parseWhitespace(b) @@ -237,12 +352,12 @@ func (p *parser) parseKeyval(b []byte) (ast.Reference, []byte, error) { } //nolint:cyclop,funlen -func (p *parser) parseVal(b []byte) (ast.Reference, []byte, error) { +func (p *Parser) parseVal(b []byte) (reference, []byte, error) { // val = string / boolean / array / inline-table / date-time / float / integer - ref := ast.InvalidReference + ref := invalidReference if len(b) == 0 { - return ref, nil, newDecodeError(b, "expected value, not eof") + return ref, nil, NewParserError(b, "expected value, not eof") } var err error @@ -259,8 +374,8 @@ func (p *parser) parseVal(b []byte) (ast.Reference, []byte, error) { } if err == nil { - ref = p.builder.Push(ast.Node{ - Kind: ast.String, + ref = p.builder.Push(Node{ + Kind: String, Raw: p.Range(raw), Data: v, }) @@ -277,8 +392,8 @@ func (p *parser) parseVal(b []byte) (ast.Reference, []byte, error) { } if err == nil { - ref = p.builder.Push(ast.Node{ - Kind: ast.String, + ref = p.builder.Push(Node{ + Kind: String, Raw: p.Range(raw), Data: v, }) @@ -287,22 +402,22 @@ func (p *parser) parseVal(b []byte) (ast.Reference, []byte, error) { return ref, b, err case 't': if !scanFollowsTrue(b) { - return ref, nil, newDecodeError(atmost(b, 4), "expected 'true'") + return ref, nil, NewParserError(atmost(b, 4), "expected 'true'") } - ref = p.builder.Push(ast.Node{ - Kind: ast.Bool, + ref = p.builder.Push(Node{ + Kind: Bool, Data: b[:4], }) return ref, b[4:], nil case 'f': if !scanFollowsFalse(b) { - return ref, nil, newDecodeError(atmost(b, 5), "expected 'false'") + return ref, nil, NewParserError(atmost(b, 5), "expected 'false'") } - ref = p.builder.Push(ast.Node{ - Kind: ast.Bool, + ref = p.builder.Push(Node{ + Kind: Bool, Data: b[:5], }) @@ -324,7 +439,7 @@ func atmost(b []byte, n int) []byte { return b[:n] } -func (p *parser) parseLiteralString(b []byte) ([]byte, []byte, []byte, error) { +func (p *Parser) parseLiteralString(b []byte) ([]byte, []byte, []byte, error) { v, rest, err := scanLiteralString(b) if err != nil { return nil, nil, nil, err @@ -333,19 +448,20 @@ func (p *parser) parseLiteralString(b []byte) ([]byte, []byte, []byte, error) { return v, v[1 : len(v)-1], rest, nil } -func (p *parser) parseInlineTable(b []byte) (ast.Reference, []byte, error) { +func (p *Parser) parseInlineTable(b []byte) (reference, []byte, error) { // inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close // inline-table-open = %x7B ws ; { // inline-table-close = ws %x7D ; } // inline-table-sep = ws %x2C ws ; , Comma // inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ] - parent := p.builder.Push(ast.Node{ - Kind: ast.InlineTable, + parent := p.builder.Push(Node{ + Kind: InlineTable, + Raw: p.Range(b[:1]), }) first := true - var child ast.Reference + var child reference b = b[1:] @@ -356,7 +472,7 @@ func (p *parser) parseInlineTable(b []byte) (ast.Reference, []byte, error) { b = p.parseWhitespace(b) if len(b) == 0 { - return parent, nil, newDecodeError(previousB[:1], "inline table is incomplete") + return parent, nil, NewParserError(previousB[:1], "inline table is incomplete") } if b[0] == '}' { @@ -371,7 +487,7 @@ func (p *parser) parseInlineTable(b []byte) (ast.Reference, []byte, error) { b = p.parseWhitespace(b) } - var kv ast.Reference + var kv reference kv, b, err = p.parseKeyval(b) if err != nil { @@ -394,7 +510,7 @@ func (p *parser) parseInlineTable(b []byte) (ast.Reference, []byte, error) { } //nolint:funlen,cyclop -func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { +func (p *Parser) parseValArray(b []byte) (reference, []byte, error) { // array = array-open [ array-values ] ws-comment-newline array-close // array-open = %x5B ; [ // array-close = %x5D ; ] @@ -405,23 +521,39 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { arrayStart := b b = b[1:] - parent := p.builder.Push(ast.Node{ - Kind: ast.Array, + parent := p.builder.Push(Node{ + Kind: Array, }) + // First indicates whether the parser is looking for the first element + // (non-comment) of the array. first := true - var lastChild ast.Reference + lastChild := invalidReference + + addChild := func(valueRef reference) { + if lastChild == invalidReference { + p.builder.AttachChild(parent, valueRef) + } else { + p.builder.Chain(lastChild, valueRef) + } + lastChild = valueRef + } var err error for len(b) > 0 { - b, err = p.parseOptionalWhitespaceCommentNewline(b) + cref := invalidReference + cref, b, err = p.parseOptionalWhitespaceCommentNewline(b) if err != nil { return parent, nil, err } + if cref != invalidReference { + addChild(cref) + } + if len(b) == 0 { - return parent, nil, newDecodeError(arrayStart[:1], "array is incomplete") + return parent, nil, NewParserError(arrayStart[:1], "array is incomplete") } if b[0] == ']' { @@ -430,16 +562,19 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { if b[0] == ',' { if first { - return parent, nil, newDecodeError(b[0:1], "array cannot start with comma") + return parent, nil, NewParserError(b[0:1], "array cannot start with comma") } b = b[1:] - b, err = p.parseOptionalWhitespaceCommentNewline(b) + cref, b, err = p.parseOptionalWhitespaceCommentNewline(b) if err != nil { return parent, nil, err } + if cref != invalidReference { + addChild(cref) + } } else if !first { - return parent, nil, newDecodeError(b[0:1], "array elements must be separated by commas") + return parent, nil, NewParserError(b[0:1], "array elements must be separated by commas") } // TOML allows trailing commas in arrays. @@ -447,23 +582,22 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { break } - var valueRef ast.Reference + var valueRef reference valueRef, b, err = p.parseVal(b) if err != nil { return parent, nil, err } - if first { - p.builder.AttachChild(parent, valueRef) - } else { - p.builder.Chain(lastChild, valueRef) - } - lastChild = valueRef + addChild(valueRef) - b, err = p.parseOptionalWhitespaceCommentNewline(b) + cref, b, err = p.parseOptionalWhitespaceCommentNewline(b) if err != nil { return parent, nil, err } + if cref != invalidReference { + addChild(cref) + } + first = false } @@ -472,15 +606,34 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { return parent, rest, err } -func (p *parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) { +func (p *Parser) parseOptionalWhitespaceCommentNewline(b []byte) (reference, []byte, error) { + rootCommentRef := invalidReference + latestCommentRef := invalidReference + + addComment := func(ref reference) { + if rootCommentRef == invalidReference { + rootCommentRef = ref + } else if latestCommentRef == invalidReference { + p.builder.AttachChild(rootCommentRef, ref) + latestCommentRef = ref + } else { + p.builder.Chain(latestCommentRef, ref) + latestCommentRef = ref + } + } + for len(b) > 0 { var err error b = p.parseWhitespace(b) if len(b) > 0 && b[0] == '#' { - _, b, err = scanComment(b) + var ref reference + ref, b, err = p.parseComment(b) if err != nil { - return nil, err + return invalidReference, nil, err + } + if ref != invalidReference { + addComment(ref) } } @@ -491,17 +644,17 @@ func (p *parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) if b[0] == '\n' || b[0] == '\r' { b, err = p.parseNewline(b) if err != nil { - return nil, err + return invalidReference, nil, err } } else { break } } - return b, nil + return rootCommentRef, b, nil } -func (p *parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, []byte, error) { +func (p *Parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, []byte, error) { token, rest, err := scanMultilineLiteralString(b) if err != nil { return nil, nil, nil, err @@ -520,7 +673,7 @@ func (p *parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, []byte, } //nolint:funlen,gocognit,cyclop -func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, error) { +func (p *Parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, error) { // ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body // ml-basic-string-delim // ml-basic-string-delim = 3quotation-mark @@ -551,11 +704,11 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, er if !escaped { str := token[startIdx:endIdx] - verr := utf8TomlValidAlreadyEscaped(str) + verr := characters.Utf8TomlValidAlreadyEscaped(str) if verr.Zero() { return token, str, rest, nil } - return nil, nil, nil, newDecodeError(str[verr.Index:verr.Index+verr.Size], "invalid UTF-8") + return nil, nil, nil, NewParserError(str[verr.Index:verr.Index+verr.Size], "invalid UTF-8") } var builder bytes.Buffer @@ -635,13 +788,13 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, er builder.WriteRune(x) i += 8 default: - return nil, nil, nil, newDecodeError(token[i:i+1], "invalid escaped character %#U", c) + return nil, nil, nil, NewParserError(token[i:i+1], "invalid escaped character %#U", c) } i++ } else { - size := utf8ValidNext(token[i:]) + size := characters.Utf8ValidNext(token[i:]) if size == 0 { - return nil, nil, nil, newDecodeError(token[i:i+1], "invalid character %#U", c) + return nil, nil, nil, NewParserError(token[i:i+1], "invalid character %#U", c) } builder.Write(token[i : i+size]) i += size @@ -651,7 +804,7 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, er return token, builder.Bytes(), rest, nil } -func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) { +func (p *Parser) parseKey(b []byte) (reference, []byte, error) { // key = simple-key / dotted-key // simple-key = quoted-key / unquoted-key // @@ -662,11 +815,11 @@ func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) { // dot-sep = ws %x2E ws ; . Period raw, key, b, err := p.parseSimpleKey(b) if err != nil { - return ast.InvalidReference, nil, err + return invalidReference, nil, err } - ref := p.builder.Push(ast.Node{ - Kind: ast.Key, + ref := p.builder.Push(Node{ + Kind: Key, Raw: p.Range(raw), Data: key, }) @@ -681,8 +834,8 @@ func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) { return ref, nil, err } - p.builder.PushAndChain(ast.Node{ - Kind: ast.Key, + p.builder.PushAndChain(Node{ + Kind: Key, Raw: p.Range(raw), Data: key, }) @@ -694,9 +847,9 @@ func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) { return ref, b, nil } -func (p *parser) parseSimpleKey(b []byte) (raw, key, rest []byte, err error) { +func (p *Parser) parseSimpleKey(b []byte) (raw, key, rest []byte, err error) { if len(b) == 0 { - return nil, nil, nil, newDecodeError(b, "expected key but found none") + return nil, nil, nil, NewParserError(b, "expected key but found none") } // simple-key = quoted-key / unquoted-key @@ -711,12 +864,12 @@ func (p *parser) parseSimpleKey(b []byte) (raw, key, rest []byte, err error) { key, rest = scanUnquotedKey(b) return key, key, rest, nil default: - return nil, nil, nil, newDecodeError(b[0:1], "invalid character at start of key: %c", b[0]) + return nil, nil, nil, NewParserError(b[0:1], "invalid character at start of key: %c", b[0]) } } //nolint:funlen,cyclop -func (p *parser) parseBasicString(b []byte) ([]byte, []byte, []byte, error) { +func (p *Parser) parseBasicString(b []byte) ([]byte, []byte, []byte, error) { // basic-string = quotation-mark *basic-char quotation-mark // quotation-mark = %x22 ; " // basic-char = basic-unescaped / escaped @@ -744,11 +897,11 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, []byte, error) { // validate the string and return a direct reference to the buffer. if !escaped { str := token[startIdx:endIdx] - verr := utf8TomlValidAlreadyEscaped(str) + verr := characters.Utf8TomlValidAlreadyEscaped(str) if verr.Zero() { return token, str, rest, nil } - return nil, nil, nil, newDecodeError(str[verr.Index:verr.Index+verr.Size], "invalid UTF-8") + return nil, nil, nil, NewParserError(str[verr.Index:verr.Index+verr.Size], "invalid UTF-8") } i := startIdx @@ -795,13 +948,13 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, []byte, error) { builder.WriteRune(x) i += 8 default: - return nil, nil, nil, newDecodeError(token[i:i+1], "invalid escaped character %#U", c) + return nil, nil, nil, NewParserError(token[i:i+1], "invalid escaped character %#U", c) } i++ } else { - size := utf8ValidNext(token[i:]) + size := characters.Utf8ValidNext(token[i:]) if size == 0 { - return nil, nil, nil, newDecodeError(token[i:i+1], "invalid character %#U", c) + return nil, nil, nil, NewParserError(token[i:i+1], "invalid character %#U", c) } builder.Write(token[i : i+size]) i += size @@ -813,7 +966,7 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, []byte, error) { func hexToRune(b []byte, length int) (rune, error) { if len(b) < length { - return -1, newDecodeError(b, "unicode point needs %d character, not %d", length, len(b)) + return -1, NewParserError(b, "unicode point needs %d character, not %d", length, len(b)) } b = b[:length] @@ -828,19 +981,19 @@ func hexToRune(b []byte, length int) (rune, error) { case 'A' <= c && c <= 'F': d = uint32(c - 'A' + 10) default: - return -1, newDecodeError(b[i:i+1], "non-hex character") + return -1, NewParserError(b[i:i+1], "non-hex character") } r = r*16 + d } if r > unicode.MaxRune || 0xD800 <= r && r < 0xE000 { - return -1, newDecodeError(b, "escape sequence is invalid Unicode code point") + return -1, NewParserError(b, "escape sequence is invalid Unicode code point") } return rune(r), nil } -func (p *parser) parseWhitespace(b []byte) []byte { +func (p *Parser) parseWhitespace(b []byte) []byte { // ws = *wschar // wschar = %x20 ; Space // wschar =/ %x09 ; Horizontal tab @@ -850,25 +1003,27 @@ func (p *parser) parseWhitespace(b []byte) []byte { } //nolint:cyclop -func (p *parser) parseIntOrFloatOrDateTime(b []byte) (ast.Reference, []byte, error) { +func (p *Parser) parseIntOrFloatOrDateTime(b []byte) (reference, []byte, error) { switch b[0] { case 'i': if !scanFollowsInf(b) { - return ast.InvalidReference, nil, newDecodeError(atmost(b, 3), "expected 'inf'") + return invalidReference, nil, NewParserError(atmost(b, 3), "expected 'inf'") } - return p.builder.Push(ast.Node{ - Kind: ast.Float, + return p.builder.Push(Node{ + Kind: Float, Data: b[:3], + Raw: p.Range(b[:3]), }), b[3:], nil case 'n': if !scanFollowsNan(b) { - return ast.InvalidReference, nil, newDecodeError(atmost(b, 3), "expected 'nan'") + return invalidReference, nil, NewParserError(atmost(b, 3), "expected 'nan'") } - return p.builder.Push(ast.Node{ - Kind: ast.Float, + return p.builder.Push(Node{ + Kind: Float, Data: b[:3], + Raw: p.Range(b[:3]), }), b[3:], nil case '+', '-': return p.scanIntOrFloat(b) @@ -898,7 +1053,7 @@ func (p *parser) parseIntOrFloatOrDateTime(b []byte) (ast.Reference, []byte, err return p.scanIntOrFloat(b) } -func (p *parser) scanDateTime(b []byte) (ast.Reference, []byte, error) { +func (p *Parser) scanDateTime(b []byte) (reference, []byte, error) { // scans for contiguous characters in [0-9T:Z.+-], and up to one space if // followed by a digit. hasDate := false @@ -941,30 +1096,30 @@ byteLoop: } } - var kind ast.Kind + var kind Kind if hasTime { if hasDate { if hasTz { - kind = ast.DateTime + kind = DateTime } else { - kind = ast.LocalDateTime + kind = LocalDateTime } } else { - kind = ast.LocalTime + kind = LocalTime } } else { - kind = ast.LocalDate + kind = LocalDate } - return p.builder.Push(ast.Node{ + return p.builder.Push(Node{ Kind: kind, Data: b[:i], }), b[i:], nil } //nolint:funlen,gocognit,cyclop -func (p *parser) scanIntOrFloat(b []byte) (ast.Reference, []byte, error) { +func (p *Parser) scanIntOrFloat(b []byte) (reference, []byte, error) { i := 0 if len(b) > 2 && b[0] == '0' && b[1] != '.' && b[1] != 'e' && b[1] != 'E' { @@ -990,9 +1145,10 @@ func (p *parser) scanIntOrFloat(b []byte) (ast.Reference, []byte, error) { } } - return p.builder.Push(ast.Node{ - Kind: ast.Integer, + return p.builder.Push(Node{ + Kind: Integer, Data: b[:i], + Raw: p.Range(b[:i]), }), b[i:], nil } @@ -1013,42 +1169,45 @@ func (p *parser) scanIntOrFloat(b []byte) (ast.Reference, []byte, error) { if c == 'i' { if scanFollowsInf(b[i:]) { - return p.builder.Push(ast.Node{ - Kind: ast.Float, + return p.builder.Push(Node{ + Kind: Float, Data: b[:i+3], + Raw: p.Range(b[:i+3]), }), b[i+3:], nil } - return ast.InvalidReference, nil, newDecodeError(b[i:i+1], "unexpected character 'i' while scanning for a number") + return invalidReference, nil, NewParserError(b[i:i+1], "unexpected character 'i' while scanning for a number") } if c == 'n' { if scanFollowsNan(b[i:]) { - return p.builder.Push(ast.Node{ - Kind: ast.Float, + return p.builder.Push(Node{ + Kind: Float, Data: b[:i+3], + Raw: p.Range(b[:i+3]), }), b[i+3:], nil } - return ast.InvalidReference, nil, newDecodeError(b[i:i+1], "unexpected character 'n' while scanning for a number") + return invalidReference, nil, NewParserError(b[i:i+1], "unexpected character 'n' while scanning for a number") } break } if i == 0 { - return ast.InvalidReference, b, newDecodeError(b, "incomplete number") + return invalidReference, b, NewParserError(b, "incomplete number") } - kind := ast.Integer + kind := Integer if isFloat { - kind = ast.Float + kind = Float } - return p.builder.Push(ast.Node{ + return p.builder.Push(Node{ Kind: kind, Data: b[:i], + Raw: p.Range(b[:i]), }), b[i:], nil } @@ -1075,11 +1234,11 @@ func isValidBinaryRune(r byte) bool { func expect(x byte, b []byte) ([]byte, error) { if len(b) == 0 { - return nil, newDecodeError(b, "expected character %c but the document ended here", x) + return nil, NewParserError(b, "expected character %c but the document ended here", x) } if b[0] != x { - return nil, newDecodeError(b[0:1], "expected character %c", x) + return nil, NewParserError(b[0:1], "expected character %c", x) } return b[1:], nil diff --git a/vendor/github.com/pelletier/go-toml/v2/scanner.go b/vendor/github.com/pelletier/go-toml/v2/unstable/scanner.go similarity index 79% rename from vendor/github.com/pelletier/go-toml/v2/scanner.go rename to vendor/github.com/pelletier/go-toml/v2/unstable/scanner.go index bb445fab4c..0512181d28 100644 --- a/vendor/github.com/pelletier/go-toml/v2/scanner.go +++ b/vendor/github.com/pelletier/go-toml/v2/unstable/scanner.go @@ -1,4 +1,6 @@ -package toml +package unstable + +import "github.com/pelletier/go-toml/v2/internal/characters" func scanFollows(b []byte, pattern string) bool { n := len(pattern) @@ -54,16 +56,16 @@ func scanLiteralString(b []byte) ([]byte, []byte, error) { case '\'': return b[:i+1], b[i+1:], nil case '\n', '\r': - return nil, nil, newDecodeError(b[i:i+1], "literal strings cannot have new lines") + return nil, nil, NewParserError(b[i:i+1], "literal strings cannot have new lines") } - size := utf8ValidNext(b[i:]) + size := characters.Utf8ValidNext(b[i:]) if size == 0 { - return nil, nil, newDecodeError(b[i:i+1], "invalid character") + return nil, nil, NewParserError(b[i:i+1], "invalid character") } i += size } - return nil, nil, newDecodeError(b[len(b):], "unterminated literal string") + return nil, nil, NewParserError(b[len(b):], "unterminated literal string") } func scanMultilineLiteralString(b []byte) ([]byte, []byte, error) { @@ -98,39 +100,39 @@ func scanMultilineLiteralString(b []byte) ([]byte, []byte, error) { i++ if i < len(b) && b[i] == '\'' { - return nil, nil, newDecodeError(b[i-3:i+1], "''' not allowed in multiline literal string") + return nil, nil, NewParserError(b[i-3:i+1], "''' not allowed in multiline literal string") } return b[:i], b[i:], nil } case '\r': if len(b) < i+2 { - return nil, nil, newDecodeError(b[len(b):], `need a \n after \r`) + return nil, nil, NewParserError(b[len(b):], `need a \n after \r`) } if b[i+1] != '\n' { - return nil, nil, newDecodeError(b[i:i+2], `need a \n after \r`) + return nil, nil, NewParserError(b[i:i+2], `need a \n after \r`) } i += 2 // skip the \n continue } - size := utf8ValidNext(b[i:]) + size := characters.Utf8ValidNext(b[i:]) if size == 0 { - return nil, nil, newDecodeError(b[i:i+1], "invalid character") + return nil, nil, NewParserError(b[i:i+1], "invalid character") } i += size } - return nil, nil, newDecodeError(b[len(b):], `multiline literal string not terminated by '''`) + return nil, nil, NewParserError(b[len(b):], `multiline literal string not terminated by '''`) } func scanWindowsNewline(b []byte) ([]byte, []byte, error) { const lenCRLF = 2 if len(b) < lenCRLF { - return nil, nil, newDecodeError(b, "windows new line expected") + return nil, nil, NewParserError(b, "windows new line expected") } if b[1] != '\n' { - return nil, nil, newDecodeError(b, `windows new line should be \r\n`) + return nil, nil, NewParserError(b, `windows new line should be \r\n`) } return b[:lenCRLF], b[lenCRLF:], nil @@ -149,7 +151,6 @@ func scanWhitespace(b []byte) ([]byte, []byte) { return b, b[len(b):] } -//nolint:unparam func scanComment(b []byte) ([]byte, []byte, error) { // comment-start-symbol = %x23 ; # // non-ascii = %x80-D7FF / %xE000-10FFFF @@ -165,11 +166,11 @@ func scanComment(b []byte) ([]byte, []byte, error) { if i+1 < len(b) && b[i+1] == '\n' { return b[:i+1], b[i+1:], nil } - return nil, nil, newDecodeError(b[i:i+1], "invalid character in comment") + return nil, nil, NewParserError(b[i:i+1], "invalid character in comment") } - size := utf8ValidNext(b[i:]) + size := characters.Utf8ValidNext(b[i:]) if size == 0 { - return nil, nil, newDecodeError(b[i:i+1], "invalid character in comment") + return nil, nil, NewParserError(b[i:i+1], "invalid character in comment") } i += size @@ -192,17 +193,17 @@ func scanBasicString(b []byte) ([]byte, bool, []byte, error) { case '"': return b[:i+1], escaped, b[i+1:], nil case '\n', '\r': - return nil, escaped, nil, newDecodeError(b[i:i+1], "basic strings cannot have new lines") + return nil, escaped, nil, NewParserError(b[i:i+1], "basic strings cannot have new lines") case '\\': if len(b) < i+2 { - return nil, escaped, nil, newDecodeError(b[i:i+1], "need a character after \\") + return nil, escaped, nil, NewParserError(b[i:i+1], "need a character after \\") } escaped = true i++ // skip the next character } } - return nil, escaped, nil, newDecodeError(b[len(b):], `basic string not terminated by "`) + return nil, escaped, nil, NewParserError(b[len(b):], `basic string not terminated by "`) } func scanMultilineBasicString(b []byte) ([]byte, bool, []byte, error) { @@ -243,27 +244,27 @@ func scanMultilineBasicString(b []byte) ([]byte, bool, []byte, error) { i++ if i < len(b) && b[i] == '"' { - return nil, escaped, nil, newDecodeError(b[i-3:i+1], `""" not allowed in multiline basic string`) + return nil, escaped, nil, NewParserError(b[i-3:i+1], `""" not allowed in multiline basic string`) } return b[:i], escaped, b[i:], nil } case '\\': if len(b) < i+2 { - return nil, escaped, nil, newDecodeError(b[len(b):], "need a character after \\") + return nil, escaped, nil, NewParserError(b[len(b):], "need a character after \\") } escaped = true i++ // skip the next character case '\r': if len(b) < i+2 { - return nil, escaped, nil, newDecodeError(b[len(b):], `need a \n after \r`) + return nil, escaped, nil, NewParserError(b[len(b):], `need a \n after \r`) } if b[i+1] != '\n' { - return nil, escaped, nil, newDecodeError(b[i:i+2], `need a \n after \r`) + return nil, escaped, nil, NewParserError(b[i:i+2], `need a \n after \r`) } i++ // skip the \n } } - return nil, escaped, nil, newDecodeError(b[len(b):], `multiline basic string not terminated by """`) + return nil, escaped, nil, NewParserError(b[len(b):], `multiline basic string not terminated by """`) } diff --git a/vendor/github.com/pelletier/go-toml/v2/unstable/unmarshaler.go b/vendor/github.com/pelletier/go-toml/v2/unstable/unmarshaler.go new file mode 100644 index 0000000000..00cfd6de45 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/unstable/unmarshaler.go @@ -0,0 +1,7 @@ +package unstable + +// The Unmarshaler interface may be implemented by types to customize their +// behavior when being unmarshaled from a TOML document. +type Unmarshaler interface { + UnmarshalTOML(value *Node) error +} diff --git a/vendor/github.com/quasilyte/go-ruleguard/ruleguard/ir_loader.go b/vendor/github.com/quasilyte/go-ruleguard/ruleguard/ir_loader.go index dcb2fd2afa..90dea56acd 100644 --- a/vendor/github.com/quasilyte/go-ruleguard/ruleguard/ir_loader.go +++ b/vendor/github.com/quasilyte/go-ruleguard/ruleguard/ir_loader.go @@ -195,7 +195,7 @@ func (l *irLoader) compileFilterFuncs(filename string, irfile *ir.File) error { if err != nil { // If this ever happens, user will get unexpected error // lines for it; but we should trust that 99.9% errors - // should be catched at irconv phase so we get a valid Go + // should be caught at irconv phase so we get a valid Go // source here as well? return fmt.Errorf("parse custom decls: %w", err) } diff --git a/vendor/github.com/quasilyte/go-ruleguard/ruleguard/textmatch/textmatch.go b/vendor/github.com/quasilyte/go-ruleguard/ruleguard/textmatch/textmatch.go index a3787e2c16..135f95740e 100644 --- a/vendor/github.com/quasilyte/go-ruleguard/ruleguard/textmatch/textmatch.go +++ b/vendor/github.com/quasilyte/go-ruleguard/ruleguard/textmatch/textmatch.go @@ -9,7 +9,7 @@ type Pattern interface { } // Compile parses a regular expression and returns a compiled -// pattern that can match inputs descriped by the regexp. +// pattern that can match inputs described by the regexp. // // Semantically it's close to the regexp.Compile, but // it does recognize some common patterns and creates diff --git a/vendor/github.com/quasilyte/go-ruleguard/ruleguard/utils.go b/vendor/github.com/quasilyte/go-ruleguard/ruleguard/utils.go index d3226db22b..6403d91cdc 100644 --- a/vendor/github.com/quasilyte/go-ruleguard/ruleguard/utils.go +++ b/vendor/github.com/quasilyte/go-ruleguard/ruleguard/utils.go @@ -273,7 +273,7 @@ func isTypeExpr(info *types.Info, x ast.Expr) bool { case *ast.Ident: // Identifier may be a type expression if object - // it reffers to is a type name. + // it refers to is a type name. _, ok := info.ObjectOf(x).(*types.TypeName) return ok diff --git a/vendor/github.com/ryancurrah/gomodguard/processor.go b/vendor/github.com/ryancurrah/gomodguard/processor.go index 8457e3b070..51038f37f2 100644 --- a/vendor/github.com/ryancurrah/gomodguard/processor.go +++ b/vendor/github.com/ryancurrah/gomodguard/processor.go @@ -141,7 +141,7 @@ func (p *Processor) addError(fileset *token.FileSet, pos token.Pos, reason strin // // It works by iterating over the dependant modules specified in the require // directive, checking if the module domain or full name is in the allowed list. -func (p *Processor) SetBlockedModules() { //nolint:gocognit,funlen +func (p *Processor) SetBlockedModules() { //nolint:funlen blockedModules := make(map[string][]string, len(p.Modfile.Require)) currentModuleName := p.Modfile.Module.Mod.Path lintedModules := p.Modfile.Require diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v5/.gitignore b/vendor/github.com/santhosh-tekuri/jsonschema/v5/.gitignore new file mode 100644 index 0000000000..3c0af38259 --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v5/.gitignore @@ -0,0 +1,4 @@ +.vscode +.idea +*.swp +cmd/jv/jv diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v5/.gitmodules b/vendor/github.com/santhosh-tekuri/jsonschema/v5/.gitmodules new file mode 100644 index 0000000000..314da31c5e --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v5/.gitmodules @@ -0,0 +1,3 @@ +[submodule "testdata/JSON-Schema-Test-Suite"] + path = testdata/JSON-Schema-Test-Suite + url = https://github.com/json-schema-org/JSON-Schema-Test-Suite.git diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v5/LICENSE b/vendor/github.com/santhosh-tekuri/jsonschema/v5/LICENSE new file mode 100644 index 0000000000..19dc35b243 --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v5/LICENSE @@ -0,0 +1,175 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. \ No newline at end of file diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v5/README.md b/vendor/github.com/santhosh-tekuri/jsonschema/v5/README.md new file mode 100644 index 0000000000..b0d05054ca --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v5/README.md @@ -0,0 +1,220 @@ +# jsonschema v5.3.1 + +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![GoDoc](https://godoc.org/github.com/santhosh-tekuri/jsonschema?status.svg)](https://pkg.go.dev/github.com/santhosh-tekuri/jsonschema/v5) +[![Go Report Card](https://goreportcard.com/badge/github.com/santhosh-tekuri/jsonschema/v5)](https://goreportcard.com/report/github.com/santhosh-tekuri/jsonschema/v5) +[![Build Status](https://github.com/santhosh-tekuri/jsonschema/actions/workflows/go.yaml/badge.svg?branch=master)](https://github.com/santhosh-tekuri/jsonschema/actions/workflows/go.yaml) +[![codecov](https://codecov.io/gh/santhosh-tekuri/jsonschema/branch/master/graph/badge.svg?token=JMVj1pFT2l)](https://codecov.io/gh/santhosh-tekuri/jsonschema) + +Package jsonschema provides json-schema compilation and validation. + +[Benchmarks](https://dev.to/vearutop/benchmarking-correctness-and-performance-of-go-json-schema-validators-3247) + +### Features: + - implements + [draft 2020-12](https://json-schema.org/specification-links.html#2020-12), + [draft 2019-09](https://json-schema.org/specification-links.html#draft-2019-09-formerly-known-as-draft-8), + [draft-7](https://json-schema.org/specification-links.html#draft-7), + [draft-6](https://json-schema.org/specification-links.html#draft-6), + [draft-4](https://json-schema.org/specification-links.html#draft-4) + - fully compliant with [JSON-Schema-Test-Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite), (excluding some optional) + - list of optional tests that are excluded can be found in schema_test.go(variable [skipTests](https://github.com/santhosh-tekuri/jsonschema/blob/master/schema_test.go#L24)) + - validates schemas against meta-schema + - full support of remote references + - support of recursive references between schemas + - detects infinite loop in schemas + - thread safe validation + - rich, intuitive hierarchial error messages with json-pointers to exact location + - supports output formats flag, basic and detailed + - supports enabling format and content Assertions in draft2019-09 or above + - change `Compiler.AssertFormat`, `Compiler.AssertContent` to `true` + - compiled schema can be introspected. easier to develop tools like generating go structs given schema + - supports user-defined keywords via [extensions](https://pkg.go.dev/github.com/santhosh-tekuri/jsonschema/v5/#example-package-Extension) + - implements following formats (supports [user-defined](https://pkg.go.dev/github.com/santhosh-tekuri/jsonschema/v5/#example-package-UserDefinedFormat)) + - date-time, date, time, duration, period (supports leap-second) + - uuid, hostname, email + - ip-address, ipv4, ipv6 + - uri, uriref, uri-template(limited validation) + - json-pointer, relative-json-pointer + - regex, format + - implements following contentEncoding (supports [user-defined](https://pkg.go.dev/github.com/santhosh-tekuri/jsonschema/v5/#example-package-UserDefinedContent)) + - base64 + - implements following contentMediaType (supports [user-defined](https://pkg.go.dev/github.com/santhosh-tekuri/jsonschema/v5/#example-package-UserDefinedContent)) + - application/json + - can load from files/http/https/[string](https://pkg.go.dev/github.com/santhosh-tekuri/jsonschema/v5/#example-package-FromString)/[]byte/io.Reader (supports [user-defined](https://pkg.go.dev/github.com/santhosh-tekuri/jsonschema/v5/#example-package-UserDefinedLoader)) + + +see examples in [godoc](https://pkg.go.dev/github.com/santhosh-tekuri/jsonschema/v5) + +The schema is compiled against the version specified in `$schema` property. +If "$schema" property is missing, it uses latest draft which currently implemented +by this library. + +You can force to use specific version, when `$schema` is missing, as follows: + +```go +compiler := jsonschema.NewCompiler() +compiler.Draft = jsonschema.Draft4 +``` + +This package supports loading json-schema from filePath and fileURL. + +To load json-schema from HTTPURL, add following import: + +```go +import _ "github.com/santhosh-tekuri/jsonschema/v5/httploader" +``` + +## Rich Errors + +The ValidationError returned by Validate method contains detailed context to understand why and where the error is. + +schema.json: +```json +{ + "$ref": "t.json#/definitions/employee" +} +``` + +t.json: +```json +{ + "definitions": { + "employee": { + "type": "string" + } + } +} +``` + +doc.json: +```json +1 +``` + +assuming `err` is the ValidationError returned when `doc.json` validated with `schema.json`, +```go +fmt.Printf("%#v\n", err) // using %#v prints errors hierarchy +``` +Prints: +``` +[I#] [S#] doesn't validate with file:///Users/santhosh/jsonschema/schema.json# + [I#] [S#/$ref] doesn't validate with 'file:///Users/santhosh/jsonschema/t.json#/definitions/employee' + [I#] [S#/definitions/employee/type] expected string, but got number +``` + +Here `I` stands for instance document and `S` stands for schema document. +The json-fragments that caused error in instance and schema documents are represented using json-pointer notation. +Nested causes are printed with indent. + +To output `err` in `flag` output format: +```go +b, _ := json.MarshalIndent(err.FlagOutput(), "", " ") +fmt.Println(string(b)) +``` +Prints: +```json +{ + "valid": false +} +``` +To output `err` in `basic` output format: +```go +b, _ := json.MarshalIndent(err.BasicOutput(), "", " ") +fmt.Println(string(b)) +``` +Prints: +```json +{ + "valid": false, + "errors": [ + { + "keywordLocation": "", + "absoluteKeywordLocation": "file:///Users/santhosh/jsonschema/schema.json#", + "instanceLocation": "", + "error": "doesn't validate with file:///Users/santhosh/jsonschema/schema.json#" + }, + { + "keywordLocation": "/$ref", + "absoluteKeywordLocation": "file:///Users/santhosh/jsonschema/schema.json#/$ref", + "instanceLocation": "", + "error": "doesn't validate with 'file:///Users/santhosh/jsonschema/t.json#/definitions/employee'" + }, + { + "keywordLocation": "/$ref/type", + "absoluteKeywordLocation": "file:///Users/santhosh/jsonschema/t.json#/definitions/employee/type", + "instanceLocation": "", + "error": "expected string, but got number" + } + ] +} +``` +To output `err` in `detailed` output format: +```go +b, _ := json.MarshalIndent(err.DetailedOutput(), "", " ") +fmt.Println(string(b)) +``` +Prints: +```json +{ + "valid": false, + "keywordLocation": "", + "absoluteKeywordLocation": "file:///Users/santhosh/jsonschema/schema.json#", + "instanceLocation": "", + "errors": [ + { + "valid": false, + "keywordLocation": "/$ref", + "absoluteKeywordLocation": "file:///Users/santhosh/jsonschema/schema.json#/$ref", + "instanceLocation": "", + "errors": [ + { + "valid": false, + "keywordLocation": "/$ref/type", + "absoluteKeywordLocation": "file:///Users/santhosh/jsonschema/t.json#/definitions/employee/type", + "instanceLocation": "", + "error": "expected string, but got number" + } + ] + } + ] +} +``` + +## CLI + +to install `go install github.com/santhosh-tekuri/jsonschema/cmd/jv@latest` + +```bash +jv [-draft INT] [-output FORMAT] [-assertformat] [-assertcontent] []... + -assertcontent + enable content assertions with draft >= 2019 + -assertformat + enable format assertions with draft >= 2019 + -draft int + draft used when '$schema' attribute is missing. valid values 4, 5, 7, 2019, 2020 (default 2020) + -output string + output format. valid values flag, basic, detailed +``` + +if no `` arguments are passed, it simply validates the ``. +if `$schema` attribute is missing in schema, it uses latest version. this can be overridden by passing `-draft` flag + +exit-code is 1, if there are any validation errors + +`jv` can also validate yaml files. It also accepts schema from yaml files. + +## Validating YAML Documents + +since yaml supports non-string keys, such yaml documents are rendered as invalid json documents. + +most yaml parser use `map[interface{}]interface{}` for object, +whereas json parser uses `map[string]interface{}`. + +so we need to manually convert them to `map[string]interface{}`. +below code shows such conversion by `toStringKeys` function. + +https://play.golang.org/p/Hhax3MrtD8r + +NOTE: if you are using `gopkg.in/yaml.v3`, then you do not need such conversion. since this library +returns `map[string]interface{}` if all keys are strings. \ No newline at end of file diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v5/compiler.go b/vendor/github.com/santhosh-tekuri/jsonschema/v5/compiler.go new file mode 100644 index 0000000000..fdb68e6480 --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v5/compiler.go @@ -0,0 +1,812 @@ +package jsonschema + +import ( + "encoding/json" + "fmt" + "io" + "math/big" + "regexp" + "strconv" + "strings" +) + +// A Compiler represents a json-schema compiler. +type Compiler struct { + // Draft represents the draft used when '$schema' attribute is missing. + // + // This defaults to latest supported draft (currently 2020-12). + Draft *Draft + resources map[string]*resource + + // Extensions is used to register extensions. + extensions map[string]extension + + // ExtractAnnotations tells whether schema annotations has to be extracted + // in compiled Schema or not. + ExtractAnnotations bool + + // LoadURL loads the document at given absolute URL. + // + // If nil, package global LoadURL is used. + LoadURL func(s string) (io.ReadCloser, error) + + // Formats can be registered by adding to this map. Key is format name, + // value is function that knows how to validate that format. + Formats map[string]func(interface{}) bool + + // AssertFormat for specifications >= draft2019-09. + AssertFormat bool + + // Decoders can be registered by adding to this map. Key is encoding name, + // value is function that knows how to decode string in that format. + Decoders map[string]func(string) ([]byte, error) + + // MediaTypes can be registered by adding to this map. Key is mediaType name, + // value is function that knows how to validate that mediaType. + MediaTypes map[string]func([]byte) error + + // AssertContent for specifications >= draft2019-09. + AssertContent bool +} + +// Compile parses json-schema at given url returns, if successful, +// a Schema object that can be used to match against json. +// +// Returned error can be *SchemaError +func Compile(url string) (*Schema, error) { + return NewCompiler().Compile(url) +} + +// MustCompile is like Compile but panics if the url cannot be compiled to *Schema. +// It simplifies safe initialization of global variables holding compiled Schemas. +func MustCompile(url string) *Schema { + return NewCompiler().MustCompile(url) +} + +// CompileString parses and compiles the given schema with given base url. +func CompileString(url, schema string) (*Schema, error) { + c := NewCompiler() + if err := c.AddResource(url, strings.NewReader(schema)); err != nil { + return nil, err + } + return c.Compile(url) +} + +// MustCompileString is like CompileString but panics on error. +// It simplified safe initialization of global variables holding compiled Schema. +func MustCompileString(url, schema string) *Schema { + c := NewCompiler() + if err := c.AddResource(url, strings.NewReader(schema)); err != nil { + panic(err) + } + return c.MustCompile(url) +} + +// NewCompiler returns a json-schema Compiler object. +// if '$schema' attribute is missing, it is treated as draft7. to change this +// behavior change Compiler.Draft value +func NewCompiler() *Compiler { + return &Compiler{ + Draft: latest, + resources: make(map[string]*resource), + Formats: make(map[string]func(interface{}) bool), + Decoders: make(map[string]func(string) ([]byte, error)), + MediaTypes: make(map[string]func([]byte) error), + extensions: make(map[string]extension), + } +} + +// AddResource adds in-memory resource to the compiler. +// +// Note that url must not have fragment +func (c *Compiler) AddResource(url string, r io.Reader) error { + res, err := newResource(url, r) + if err != nil { + return err + } + c.resources[res.url] = res + return nil +} + +// MustCompile is like Compile but panics if the url cannot be compiled to *Schema. +// It simplifies safe initialization of global variables holding compiled Schemas. +func (c *Compiler) MustCompile(url string) *Schema { + s, err := c.Compile(url) + if err != nil { + panic(fmt.Sprintf("jsonschema: %#v", err)) + } + return s +} + +// Compile parses json-schema at given url returns, if successful, +// a Schema object that can be used to match against json. +// +// error returned will be of type *SchemaError +func (c *Compiler) Compile(url string) (*Schema, error) { + // make url absolute + u, err := toAbs(url) + if err != nil { + return nil, &SchemaError{url, err} + } + url = u + + sch, err := c.compileURL(url, nil, "#") + if err != nil { + err = &SchemaError{url, err} + } + return sch, err +} + +func (c *Compiler) findResource(url string) (*resource, error) { + if _, ok := c.resources[url]; !ok { + // load resource + var rdr io.Reader + if sch, ok := vocabSchemas[url]; ok { + rdr = strings.NewReader(sch) + } else { + loadURL := LoadURL + if c.LoadURL != nil { + loadURL = c.LoadURL + } + r, err := loadURL(url) + if err != nil { + return nil, err + } + defer r.Close() + rdr = r + } + if err := c.AddResource(url, rdr); err != nil { + return nil, err + } + } + + r := c.resources[url] + if r.draft != nil { + return r, nil + } + + // set draft + r.draft = c.Draft + if m, ok := r.doc.(map[string]interface{}); ok { + if sch, ok := m["$schema"]; ok { + sch, ok := sch.(string) + if !ok { + return nil, fmt.Errorf("jsonschema: invalid $schema in %s", url) + } + if !isURI(sch) { + return nil, fmt.Errorf("jsonschema: $schema must be uri in %s", url) + } + r.draft = findDraft(sch) + if r.draft == nil { + sch, _ := split(sch) + if sch == url { + return nil, fmt.Errorf("jsonschema: unsupported draft in %s", url) + } + mr, err := c.findResource(sch) + if err != nil { + return nil, err + } + r.draft = mr.draft + } + } + } + + id, err := r.draft.resolveID(r.url, r.doc) + if err != nil { + return nil, err + } + if id != "" { + r.url = id + } + + if err := r.fillSubschemas(c, r); err != nil { + return nil, err + } + + return r, nil +} + +func (c *Compiler) compileURL(url string, stack []schemaRef, ptr string) (*Schema, error) { + // if url points to a draft, return Draft.meta + if d := findDraft(url); d != nil && d.meta != nil { + return d.meta, nil + } + + b, f := split(url) + r, err := c.findResource(b) + if err != nil { + return nil, err + } + return c.compileRef(r, stack, ptr, r, f) +} + +func (c *Compiler) compileRef(r *resource, stack []schemaRef, refPtr string, res *resource, ref string) (*Schema, error) { + base := r.baseURL(res.floc) + ref, err := resolveURL(base, ref) + if err != nil { + return nil, err + } + + u, f := split(ref) + sr := r.findResource(u) + if sr == nil { + // external resource + return c.compileURL(ref, stack, refPtr) + } + + // ensure root resource is always compiled first. + // this is required to get schema.meta from root resource + if r.schema == nil { + r.schema = newSchema(r.url, r.floc, r.draft, r.doc) + if _, err := c.compile(r, nil, schemaRef{"#", r.schema, false}, r); err != nil { + return nil, err + } + } + + sr, err = r.resolveFragment(c, sr, f) + if err != nil { + return nil, err + } + if sr == nil { + return nil, fmt.Errorf("jsonschema: %s not found", ref) + } + + if sr.schema != nil { + if err := checkLoop(stack, schemaRef{refPtr, sr.schema, false}); err != nil { + return nil, err + } + return sr.schema, nil + } + + sr.schema = newSchema(r.url, sr.floc, r.draft, sr.doc) + return c.compile(r, stack, schemaRef{refPtr, sr.schema, false}, sr) +} + +func (c *Compiler) compileDynamicAnchors(r *resource, res *resource) error { + if r.draft.version < 2020 { + return nil + } + + rr := r.listResources(res) + rr = append(rr, res) + for _, sr := range rr { + if m, ok := sr.doc.(map[string]interface{}); ok { + if _, ok := m["$dynamicAnchor"]; ok { + sch, err := c.compileRef(r, nil, "IGNORED", r, sr.floc) + if err != nil { + return err + } + res.schema.dynamicAnchors = append(res.schema.dynamicAnchors, sch) + } + } + } + return nil +} + +func (c *Compiler) compile(r *resource, stack []schemaRef, sref schemaRef, res *resource) (*Schema, error) { + if err := c.compileDynamicAnchors(r, res); err != nil { + return nil, err + } + + switch v := res.doc.(type) { + case bool: + res.schema.Always = &v + return res.schema, nil + default: + return res.schema, c.compileMap(r, stack, sref, res) + } +} + +func (c *Compiler) compileMap(r *resource, stack []schemaRef, sref schemaRef, res *resource) error { + m := res.doc.(map[string]interface{}) + + if err := checkLoop(stack, sref); err != nil { + return err + } + stack = append(stack, sref) + + var s = res.schema + var err error + + if r == res { // root schema + if sch, ok := m["$schema"]; ok { + sch := sch.(string) + if d := findDraft(sch); d != nil { + s.meta = d.meta + } else { + if s.meta, err = c.compileRef(r, stack, "$schema", res, sch); err != nil { + return err + } + } + } + } + + if ref, ok := m["$ref"]; ok { + s.Ref, err = c.compileRef(r, stack, "$ref", res, ref.(string)) + if err != nil { + return err + } + if r.draft.version < 2019 { + // All other properties in a "$ref" object MUST be ignored + return nil + } + } + + if r.draft.version >= 2019 { + if r == res { // root schema + if vocab, ok := m["$vocabulary"]; ok { + for url, reqd := range vocab.(map[string]interface{}) { + if reqd, ok := reqd.(bool); ok && !reqd { + continue + } + if !r.draft.isVocab(url) { + return fmt.Errorf("jsonschema: unsupported vocab %q in %s", url, res) + } + s.vocab = append(s.vocab, url) + } + } else { + s.vocab = r.draft.defaultVocab + } + } + + if ref, ok := m["$recursiveRef"]; ok { + s.RecursiveRef, err = c.compileRef(r, stack, "$recursiveRef", res, ref.(string)) + if err != nil { + return err + } + } + } + if r.draft.version >= 2020 { + if dref, ok := m["$dynamicRef"]; ok { + s.DynamicRef, err = c.compileRef(r, stack, "$dynamicRef", res, dref.(string)) + if err != nil { + return err + } + if dref, ok := dref.(string); ok { + _, frag := split(dref) + if frag != "#" && !strings.HasPrefix(frag, "#/") { + // frag is anchor + s.dynamicRefAnchor = frag[1:] + } + } + } + } + + loadInt := func(pname string) int { + if num, ok := m[pname]; ok { + i, _ := num.(json.Number).Float64() + return int(i) + } + return -1 + } + + loadRat := func(pname string) *big.Rat { + if num, ok := m[pname]; ok { + r, _ := new(big.Rat).SetString(string(num.(json.Number))) + return r + } + return nil + } + + if r.draft.version < 2019 || r.schema.meta.hasVocab("validation") { + if t, ok := m["type"]; ok { + switch t := t.(type) { + case string: + s.Types = []string{t} + case []interface{}: + s.Types = toStrings(t) + } + } + + if e, ok := m["enum"]; ok { + s.Enum = e.([]interface{}) + allPrimitives := true + for _, item := range s.Enum { + switch jsonType(item) { + case "object", "array": + allPrimitives = false + break + } + } + s.enumError = "enum failed" + if allPrimitives { + if len(s.Enum) == 1 { + s.enumError = fmt.Sprintf("value must be %#v", s.Enum[0]) + } else { + strEnum := make([]string, len(s.Enum)) + for i, item := range s.Enum { + strEnum[i] = fmt.Sprintf("%#v", item) + } + s.enumError = fmt.Sprintf("value must be one of %s", strings.Join(strEnum, ", ")) + } + } + } + + s.Minimum = loadRat("minimum") + if exclusive, ok := m["exclusiveMinimum"]; ok { + if exclusive, ok := exclusive.(bool); ok { + if exclusive { + s.Minimum, s.ExclusiveMinimum = nil, s.Minimum + } + } else { + s.ExclusiveMinimum = loadRat("exclusiveMinimum") + } + } + + s.Maximum = loadRat("maximum") + if exclusive, ok := m["exclusiveMaximum"]; ok { + if exclusive, ok := exclusive.(bool); ok { + if exclusive { + s.Maximum, s.ExclusiveMaximum = nil, s.Maximum + } + } else { + s.ExclusiveMaximum = loadRat("exclusiveMaximum") + } + } + + s.MultipleOf = loadRat("multipleOf") + + s.MinProperties, s.MaxProperties = loadInt("minProperties"), loadInt("maxProperties") + + if req, ok := m["required"]; ok { + s.Required = toStrings(req.([]interface{})) + } + + s.MinItems, s.MaxItems = loadInt("minItems"), loadInt("maxItems") + + if unique, ok := m["uniqueItems"]; ok { + s.UniqueItems = unique.(bool) + } + + s.MinLength, s.MaxLength = loadInt("minLength"), loadInt("maxLength") + + if pattern, ok := m["pattern"]; ok { + s.Pattern = regexp.MustCompile(pattern.(string)) + } + + if r.draft.version >= 2019 { + s.MinContains, s.MaxContains = loadInt("minContains"), loadInt("maxContains") + if s.MinContains == -1 { + s.MinContains = 1 + } + + if deps, ok := m["dependentRequired"]; ok { + deps := deps.(map[string]interface{}) + s.DependentRequired = make(map[string][]string, len(deps)) + for pname, pvalue := range deps { + s.DependentRequired[pname] = toStrings(pvalue.([]interface{})) + } + } + } + } + + compile := func(stack []schemaRef, ptr string) (*Schema, error) { + return c.compileRef(r, stack, ptr, res, r.url+res.floc+"/"+ptr) + } + + loadSchema := func(pname string, stack []schemaRef) (*Schema, error) { + if _, ok := m[pname]; ok { + return compile(stack, escape(pname)) + } + return nil, nil + } + + loadSchemas := func(pname string, stack []schemaRef) ([]*Schema, error) { + if pvalue, ok := m[pname]; ok { + pvalue := pvalue.([]interface{}) + schemas := make([]*Schema, len(pvalue)) + for i := range pvalue { + sch, err := compile(stack, escape(pname)+"/"+strconv.Itoa(i)) + if err != nil { + return nil, err + } + schemas[i] = sch + } + return schemas, nil + } + return nil, nil + } + + if r.draft.version < 2019 || r.schema.meta.hasVocab("applicator") { + if s.Not, err = loadSchema("not", stack); err != nil { + return err + } + if s.AllOf, err = loadSchemas("allOf", stack); err != nil { + return err + } + if s.AnyOf, err = loadSchemas("anyOf", stack); err != nil { + return err + } + if s.OneOf, err = loadSchemas("oneOf", stack); err != nil { + return err + } + + if props, ok := m["properties"]; ok { + props := props.(map[string]interface{}) + s.Properties = make(map[string]*Schema, len(props)) + for pname := range props { + s.Properties[pname], err = compile(nil, "properties/"+escape(pname)) + if err != nil { + return err + } + } + } + + if regexProps, ok := m["regexProperties"]; ok { + s.RegexProperties = regexProps.(bool) + } + + if patternProps, ok := m["patternProperties"]; ok { + patternProps := patternProps.(map[string]interface{}) + s.PatternProperties = make(map[*regexp.Regexp]*Schema, len(patternProps)) + for pattern := range patternProps { + s.PatternProperties[regexp.MustCompile(pattern)], err = compile(nil, "patternProperties/"+escape(pattern)) + if err != nil { + return err + } + } + } + + if additionalProps, ok := m["additionalProperties"]; ok { + switch additionalProps := additionalProps.(type) { + case bool: + s.AdditionalProperties = additionalProps + case map[string]interface{}: + s.AdditionalProperties, err = compile(nil, "additionalProperties") + if err != nil { + return err + } + } + } + + if deps, ok := m["dependencies"]; ok { + deps := deps.(map[string]interface{}) + s.Dependencies = make(map[string]interface{}, len(deps)) + for pname, pvalue := range deps { + switch pvalue := pvalue.(type) { + case []interface{}: + s.Dependencies[pname] = toStrings(pvalue) + default: + s.Dependencies[pname], err = compile(stack, "dependencies/"+escape(pname)) + if err != nil { + return err + } + } + } + } + + if r.draft.version >= 6 { + if s.PropertyNames, err = loadSchema("propertyNames", nil); err != nil { + return err + } + if s.Contains, err = loadSchema("contains", nil); err != nil { + return err + } + } + + if r.draft.version >= 7 { + if m["if"] != nil { + if s.If, err = loadSchema("if", stack); err != nil { + return err + } + if s.Then, err = loadSchema("then", stack); err != nil { + return err + } + if s.Else, err = loadSchema("else", stack); err != nil { + return err + } + } + } + if r.draft.version >= 2019 { + if deps, ok := m["dependentSchemas"]; ok { + deps := deps.(map[string]interface{}) + s.DependentSchemas = make(map[string]*Schema, len(deps)) + for pname := range deps { + s.DependentSchemas[pname], err = compile(stack, "dependentSchemas/"+escape(pname)) + if err != nil { + return err + } + } + } + } + + if r.draft.version >= 2020 { + if s.PrefixItems, err = loadSchemas("prefixItems", nil); err != nil { + return err + } + if s.Items2020, err = loadSchema("items", nil); err != nil { + return err + } + } else { + if items, ok := m["items"]; ok { + switch items.(type) { + case []interface{}: + s.Items, err = loadSchemas("items", nil) + if err != nil { + return err + } + if additionalItems, ok := m["additionalItems"]; ok { + switch additionalItems := additionalItems.(type) { + case bool: + s.AdditionalItems = additionalItems + case map[string]interface{}: + s.AdditionalItems, err = compile(nil, "additionalItems") + if err != nil { + return err + } + } + } + default: + s.Items, err = compile(nil, "items") + if err != nil { + return err + } + } + } + } + + } + + // unevaluatedXXX keywords were in "applicator" vocab in 2019, but moved to new vocab "unevaluated" in 2020 + if (r.draft.version == 2019 && r.schema.meta.hasVocab("applicator")) || (r.draft.version >= 2020 && r.schema.meta.hasVocab("unevaluated")) { + if s.UnevaluatedProperties, err = loadSchema("unevaluatedProperties", nil); err != nil { + return err + } + if s.UnevaluatedItems, err = loadSchema("unevaluatedItems", nil); err != nil { + return err + } + if r.draft.version >= 2020 { + // any item in an array that passes validation of the contains schema is considered "evaluated" + s.ContainsEval = true + } + } + + if format, ok := m["format"]; ok { + s.Format = format.(string) + if r.draft.version < 2019 || c.AssertFormat || r.schema.meta.hasVocab("format-assertion") { + if format, ok := c.Formats[s.Format]; ok { + s.format = format + } else { + s.format, _ = Formats[s.Format] + } + } + } + + if c.ExtractAnnotations { + if title, ok := m["title"]; ok { + s.Title = title.(string) + } + if description, ok := m["description"]; ok { + s.Description = description.(string) + } + s.Default = m["default"] + } + + if r.draft.version >= 6 { + if c, ok := m["const"]; ok { + s.Constant = []interface{}{c} + } + } + + if r.draft.version >= 7 { + if encoding, ok := m["contentEncoding"]; ok { + s.ContentEncoding = encoding.(string) + if decoder, ok := c.Decoders[s.ContentEncoding]; ok { + s.decoder = decoder + } else { + s.decoder, _ = Decoders[s.ContentEncoding] + } + } + if mediaType, ok := m["contentMediaType"]; ok { + s.ContentMediaType = mediaType.(string) + if mediaType, ok := c.MediaTypes[s.ContentMediaType]; ok { + s.mediaType = mediaType + } else { + s.mediaType, _ = MediaTypes[s.ContentMediaType] + } + if s.ContentSchema, err = loadSchema("contentSchema", stack); err != nil { + return err + } + } + if c.ExtractAnnotations { + if comment, ok := m["$comment"]; ok { + s.Comment = comment.(string) + } + if readOnly, ok := m["readOnly"]; ok { + s.ReadOnly = readOnly.(bool) + } + if writeOnly, ok := m["writeOnly"]; ok { + s.WriteOnly = writeOnly.(bool) + } + if examples, ok := m["examples"]; ok { + s.Examples = examples.([]interface{}) + } + } + } + + if r.draft.version >= 2019 { + if !c.AssertContent { + s.decoder = nil + s.mediaType = nil + s.ContentSchema = nil + } + if c.ExtractAnnotations { + if deprecated, ok := m["deprecated"]; ok { + s.Deprecated = deprecated.(bool) + } + } + } + + for name, ext := range c.extensions { + es, err := ext.compiler.Compile(CompilerContext{c, r, stack, res}, m) + if err != nil { + return err + } + if es != nil { + if s.Extensions == nil { + s.Extensions = make(map[string]ExtSchema) + } + s.Extensions[name] = es + } + } + + return nil +} + +func (c *Compiler) validateSchema(r *resource, v interface{}, vloc string) error { + validate := func(meta *Schema) error { + if meta == nil { + return nil + } + return meta.validateValue(v, vloc) + } + + if err := validate(r.draft.meta); err != nil { + return err + } + for _, ext := range c.extensions { + if err := validate(ext.meta); err != nil { + return err + } + } + return nil +} + +func toStrings(arr []interface{}) []string { + s := make([]string, len(arr)) + for i, v := range arr { + s[i] = v.(string) + } + return s +} + +// SchemaRef captures schema and the path referring to it. +type schemaRef struct { + path string // relative-json-pointer to schema + schema *Schema // target schema + discard bool // true when scope left +} + +func (sr schemaRef) String() string { + return fmt.Sprintf("(%s)%v", sr.path, sr.schema) +} + +func checkLoop(stack []schemaRef, sref schemaRef) error { + for _, ref := range stack { + if ref.schema == sref.schema { + return infiniteLoopError(stack, sref) + } + } + return nil +} + +func keywordLocation(stack []schemaRef, path string) string { + var loc string + for _, ref := range stack[1:] { + loc += "/" + ref.path + } + if path != "" { + loc = loc + "/" + path + } + return loc +} diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v5/content.go b/vendor/github.com/santhosh-tekuri/jsonschema/v5/content.go new file mode 100644 index 0000000000..7570b8b5a9 --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v5/content.go @@ -0,0 +1,29 @@ +package jsonschema + +import ( + "encoding/base64" + "encoding/json" +) + +// Decoders is a registry of functions, which know how to decode +// string encoded in specific format. +// +// New Decoders can be registered by adding to this map. Key is encoding name, +// value is function that knows how to decode string in that format. +var Decoders = map[string]func(string) ([]byte, error){ + "base64": base64.StdEncoding.DecodeString, +} + +// MediaTypes is a registry of functions, which know how to validate +// whether the bytes represent data of that mediaType. +// +// New mediaTypes can be registered by adding to this map. Key is mediaType name, +// value is function that knows how to validate that mediaType. +var MediaTypes = map[string]func([]byte) error{ + "application/json": validateJSON, +} + +func validateJSON(b []byte) error { + var v interface{} + return json.Unmarshal(b, &v) +} diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v5/doc.go b/vendor/github.com/santhosh-tekuri/jsonschema/v5/doc.go new file mode 100644 index 0000000000..a124262a51 --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v5/doc.go @@ -0,0 +1,49 @@ +/* +Package jsonschema provides json-schema compilation and validation. + +Features: + - implements draft 2020-12, 2019-09, draft-7, draft-6, draft-4 + - fully compliant with JSON-Schema-Test-Suite, (excluding some optional) + - list of optional tests that are excluded can be found in schema_test.go(variable skipTests) + - validates schemas against meta-schema + - full support of remote references + - support of recursive references between schemas + - detects infinite loop in schemas + - thread safe validation + - rich, intuitive hierarchial error messages with json-pointers to exact location + - supports output formats flag, basic and detailed + - supports enabling format and content Assertions in draft2019-09 or above + - change Compiler.AssertFormat, Compiler.AssertContent to true + - compiled schema can be introspected. easier to develop tools like generating go structs given schema + - supports user-defined keywords via extensions + - implements following formats (supports user-defined) + - date-time, date, time, duration (supports leap-second) + - uuid, hostname, email + - ip-address, ipv4, ipv6 + - uri, uriref, uri-template(limited validation) + - json-pointer, relative-json-pointer + - regex, format + - implements following contentEncoding (supports user-defined) + - base64 + - implements following contentMediaType (supports user-defined) + - application/json + - can load from files/http/https/string/[]byte/io.Reader (supports user-defined) + +The schema is compiled against the version specified in "$schema" property. +If "$schema" property is missing, it uses latest draft which currently implemented +by this library. + +You can force to use specific draft, when "$schema" is missing, as follows: + + compiler := jsonschema.NewCompiler() + compiler.Draft = jsonschema.Draft4 + +This package supports loading json-schema from filePath and fileURL. + +To load json-schema from HTTPURL, add following import: + + import _ "github.com/santhosh-tekuri/jsonschema/v5/httploader" + +you can validate yaml documents. see https://play.golang.org/p/sJy1qY7dXgA +*/ +package jsonschema diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v5/draft.go b/vendor/github.com/santhosh-tekuri/jsonschema/v5/draft.go new file mode 100644 index 0000000000..154fa5837d --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v5/draft.go @@ -0,0 +1,1454 @@ +package jsonschema + +import ( + "fmt" + "strconv" + "strings" +) + +// A Draft represents json-schema draft +type Draft struct { + version int + meta *Schema + id string // property name used to represent schema id. + boolSchema bool // is boolean valid schema + vocab []string // built-in vocab + defaultVocab []string // vocabs when $vocabulary is not used + subschemas map[string]position +} + +func (d *Draft) URL() string { + switch d.version { + case 2020: + return "https://json-schema.org/draft/2020-12/schema" + case 2019: + return "https://json-schema.org/draft/2019-09/schema" + case 7: + return "https://json-schema.org/draft-07/schema" + case 6: + return "https://json-schema.org/draft-06/schema" + case 4: + return "https://json-schema.org/draft-04/schema" + } + return "" +} + +func (d *Draft) String() string { + return fmt.Sprintf("Draft%d", d.version) +} + +func (d *Draft) loadMeta(url, schema string) { + c := NewCompiler() + c.AssertFormat = true + if err := c.AddResource(url, strings.NewReader(schema)); err != nil { + panic(err) + } + d.meta = c.MustCompile(url) + d.meta.meta = d.meta +} + +func (d *Draft) getID(sch interface{}) string { + m, ok := sch.(map[string]interface{}) + if !ok { + return "" + } + if _, ok := m["$ref"]; ok && d.version <= 7 { + // $ref prevents a sibling id from changing the base uri + return "" + } + v, ok := m[d.id] + if !ok { + return "" + } + id, ok := v.(string) + if !ok { + return "" + } + return id +} + +func (d *Draft) resolveID(base string, sch interface{}) (string, error) { + id, _ := split(d.getID(sch)) // strip fragment + if id == "" { + return "", nil + } + url, err := resolveURL(base, id) + url, _ = split(url) // strip fragment + return url, err +} + +func (d *Draft) anchors(sch interface{}) []string { + m, ok := sch.(map[string]interface{}) + if !ok { + return nil + } + + var anchors []string + + // before draft2019, anchor is specified in id + _, f := split(d.getID(m)) + if f != "#" { + anchors = append(anchors, f[1:]) + } + + if v, ok := m["$anchor"]; ok && d.version >= 2019 { + anchors = append(anchors, v.(string)) + } + if v, ok := m["$dynamicAnchor"]; ok && d.version >= 2020 { + anchors = append(anchors, v.(string)) + } + return anchors +} + +// listSubschemas collects subschemas in r into rr. +func (d *Draft) listSubschemas(r *resource, base string, rr map[string]*resource) error { + add := func(loc string, sch interface{}) error { + url, err := d.resolveID(base, sch) + if err != nil { + return err + } + floc := r.floc + "/" + loc + sr := &resource{url: url, floc: floc, doc: sch} + rr[floc] = sr + + base := base + if url != "" { + base = url + } + return d.listSubschemas(sr, base, rr) + } + + sch, ok := r.doc.(map[string]interface{}) + if !ok { + return nil + } + for kw, pos := range d.subschemas { + v, ok := sch[kw] + if !ok { + continue + } + if pos&self != 0 { + switch v := v.(type) { + case map[string]interface{}: + if err := add(kw, v); err != nil { + return err + } + case bool: + if d.boolSchema { + if err := add(kw, v); err != nil { + return err + } + } + } + } + if pos&item != 0 { + if v, ok := v.([]interface{}); ok { + for i, item := range v { + if err := add(kw+"/"+strconv.Itoa(i), item); err != nil { + return err + } + } + } + } + if pos&prop != 0 { + if v, ok := v.(map[string]interface{}); ok { + for pname, pval := range v { + if err := add(kw+"/"+escape(pname), pval); err != nil { + return err + } + } + } + } + } + return nil +} + +// isVocab tells whether url is built-in vocab. +func (d *Draft) isVocab(url string) bool { + for _, v := range d.vocab { + if url == v { + return true + } + } + return false +} + +type position uint + +const ( + self position = 1 << iota + prop + item +) + +// supported drafts +var ( + Draft4 = &Draft{version: 4, id: "id", boolSchema: false} + Draft6 = &Draft{version: 6, id: "$id", boolSchema: true} + Draft7 = &Draft{version: 7, id: "$id", boolSchema: true} + Draft2019 = &Draft{ + version: 2019, + id: "$id", + boolSchema: true, + vocab: []string{ + "https://json-schema.org/draft/2019-09/vocab/core", + "https://json-schema.org/draft/2019-09/vocab/applicator", + "https://json-schema.org/draft/2019-09/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/meta-data", + "https://json-schema.org/draft/2019-09/vocab/format", + "https://json-schema.org/draft/2019-09/vocab/content", + }, + defaultVocab: []string{ + "https://json-schema.org/draft/2019-09/vocab/core", + "https://json-schema.org/draft/2019-09/vocab/applicator", + "https://json-schema.org/draft/2019-09/vocab/validation", + }, + } + Draft2020 = &Draft{ + version: 2020, + id: "$id", + boolSchema: true, + vocab: []string{ + "https://json-schema.org/draft/2020-12/vocab/core", + "https://json-schema.org/draft/2020-12/vocab/applicator", + "https://json-schema.org/draft/2020-12/vocab/unevaluated", + "https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2020-12/vocab/meta-data", + "https://json-schema.org/draft/2020-12/vocab/format-annotation", + "https://json-schema.org/draft/2020-12/vocab/format-assertion", + "https://json-schema.org/draft/2020-12/vocab/content", + }, + defaultVocab: []string{ + "https://json-schema.org/draft/2020-12/vocab/core", + "https://json-schema.org/draft/2020-12/vocab/applicator", + "https://json-schema.org/draft/2020-12/vocab/unevaluated", + "https://json-schema.org/draft/2020-12/vocab/validation", + }, + } + + latest = Draft2020 +) + +func findDraft(url string) *Draft { + if strings.HasPrefix(url, "http://") { + url = "https://" + strings.TrimPrefix(url, "http://") + } + if strings.HasSuffix(url, "#") || strings.HasSuffix(url, "#/") { + url = url[:strings.IndexByte(url, '#')] + } + switch url { + case "https://json-schema.org/schema": + return latest + case "https://json-schema.org/draft/2020-12/schema": + return Draft2020 + case "https://json-schema.org/draft/2019-09/schema": + return Draft2019 + case "https://json-schema.org/draft-07/schema": + return Draft7 + case "https://json-schema.org/draft-06/schema": + return Draft6 + case "https://json-schema.org/draft-04/schema": + return Draft4 + } + return nil +} + +func init() { + subschemas := map[string]position{ + // type agnostic + "definitions": prop, + "not": self, + "allOf": item, + "anyOf": item, + "oneOf": item, + // object + "properties": prop, + "additionalProperties": self, + "patternProperties": prop, + // array + "items": self | item, + "additionalItems": self, + "dependencies": prop, + } + Draft4.subschemas = clone(subschemas) + + subschemas["propertyNames"] = self + subschemas["contains"] = self + Draft6.subschemas = clone(subschemas) + + subschemas["if"] = self + subschemas["then"] = self + subschemas["else"] = self + Draft7.subschemas = clone(subschemas) + + subschemas["$defs"] = prop + subschemas["dependentSchemas"] = prop + subschemas["unevaluatedProperties"] = self + subschemas["unevaluatedItems"] = self + subschemas["contentSchema"] = self + Draft2019.subschemas = clone(subschemas) + + subschemas["prefixItems"] = item + Draft2020.subschemas = clone(subschemas) + + Draft4.loadMeta("http://json-schema.org/draft-04/schema", `{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "positiveInteger": { + "type": "integer", + "minimum": 0 + }, + "positiveIntegerDefault0": { + "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ] + }, + "simpleTypes": { + "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1, + "uniqueItems": true + } + }, + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uriref" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": {}, + "multipleOf": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + "maxLength": { "$ref": "#/definitions/positiveInteger" }, + "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} + }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": {} + }, + "maxItems": { "$ref": "#/definitions/positiveInteger" }, + "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxProperties": { "$ref": "#/definitions/positiveInteger" }, + "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} + }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "regexProperties": true, + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "regexProperties": { "type": "boolean" }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" }, + "format": { "type": "string" }, + "$ref": { "type": "string" } + }, + "dependencies": { + "exclusiveMaximum": [ "maximum" ], + "exclusiveMinimum": [ "minimum" ] + }, + "default": {} + }`) + Draft6.loadMeta("http://json-schema.org/draft-06/schema", `{ + "$schema": "http://json-schema.org/draft-06/schema#", + "$id": "http://json-schema.org/draft-06/schema#", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "allOf": [ + { "$ref": "#/definitions/nonNegativeInteger" }, + { "default": 0 } + ] + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + }, + "type": ["object", "boolean"], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": {}, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, + "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { "$ref": "#" }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": {} + }, + "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, + "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { "$ref": "#" }, + "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, + "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { "$ref": "#" }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "regexProperties": true, + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "propertyNames": { "$ref": "#" }, + "const": {}, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { "type": "string" }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "default": {} + }`) + Draft7.loadMeta("http://json-schema.org/draft-07/schema", `{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://json-schema.org/draft-07/schema#", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "allOf": [ + { "$ref": "#/definitions/nonNegativeInteger" }, + { "default": 0 } + ] + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + }, + "type": ["object", "boolean"], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "$comment": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, + "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { "$ref": "#" }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": true + }, + "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, + "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { "$ref": "#" }, + "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, + "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { "$ref": "#" }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "propertyNames": { "format": "regex" }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "propertyNames": { "$ref": "#" }, + "const": true, + "enum": { + "type": "array", + "items": true, + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { "type": "string" }, + "contentMediaType": { "type": "string" }, + "contentEncoding": { "type": "string" }, + "if": { "$ref": "#" }, + "then": { "$ref": "#" }, + "else": { "$ref": "#" }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "default": true + }`) + Draft2019.loadMeta("https://json-schema.org/draft/2019-09/schema", `{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/schema", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/core": true, + "https://json-schema.org/draft/2019-09/vocab/applicator": true, + "https://json-schema.org/draft/2019-09/vocab/validation": true, + "https://json-schema.org/draft/2019-09/vocab/meta-data": true, + "https://json-schema.org/draft/2019-09/vocab/format": false, + "https://json-schema.org/draft/2019-09/vocab/content": true + }, + "$recursiveAnchor": true, + + "title": "Core and Validation specifications meta-schema", + "allOf": [ + {"$ref": "meta/core"}, + {"$ref": "meta/applicator"}, + {"$ref": "meta/validation"}, + {"$ref": "meta/meta-data"}, + {"$ref": "meta/format"}, + {"$ref": "meta/content"} + ], + "type": ["object", "boolean"], + "properties": { + "definitions": { + "$comment": "While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.", + "type": "object", + "additionalProperties": { "$recursiveRef": "#" }, + "default": {} + }, + "dependencies": { + "$comment": "\"dependencies\" is no longer a keyword, but schema authors should avoid redefining it to facilitate a smooth transition to \"dependentSchemas\" and \"dependentRequired\"", + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$recursiveRef": "#" }, + { "$ref": "meta/validation#/$defs/stringArray" } + ] + } + } + } + }`) + Draft2020.loadMeta("https://json-schema.org/draft/2020-12/schema", `{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/schema", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/core": true, + "https://json-schema.org/draft/2020-12/vocab/applicator": true, + "https://json-schema.org/draft/2020-12/vocab/unevaluated": true, + "https://json-schema.org/draft/2020-12/vocab/validation": true, + "https://json-schema.org/draft/2020-12/vocab/meta-data": true, + "https://json-schema.org/draft/2020-12/vocab/format-annotation": true, + "https://json-schema.org/draft/2020-12/vocab/content": true + }, + "$dynamicAnchor": "meta", + + "title": "Core and Validation specifications meta-schema", + "allOf": [ + {"$ref": "meta/core"}, + {"$ref": "meta/applicator"}, + {"$ref": "meta/unevaluated"}, + {"$ref": "meta/validation"}, + {"$ref": "meta/meta-data"}, + {"$ref": "meta/format-annotation"}, + {"$ref": "meta/content"} + ], + "type": ["object", "boolean"], + "$comment": "This meta-schema also defines keywords that have appeared in previous drafts in order to prevent incompatible extensions as they remain in common use.", + "properties": { + "definitions": { + "$comment": "\"definitions\" has been replaced by \"$defs\".", + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" }, + "deprecated": true, + "default": {} + }, + "dependencies": { + "$comment": "\"dependencies\" has been split and replaced by \"dependentSchemas\" and \"dependentRequired\" in order to serve their differing semantics.", + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$dynamicRef": "#meta" }, + { "$ref": "meta/validation#/$defs/stringArray" } + ] + }, + "deprecated": true, + "default": {} + }, + "$recursiveAnchor": { + "$comment": "\"$recursiveAnchor\" has been replaced by \"$dynamicAnchor\".", + "$ref": "meta/core#/$defs/anchorString", + "deprecated": true + }, + "$recursiveRef": { + "$comment": "\"$recursiveRef\" has been replaced by \"$dynamicRef\".", + "$ref": "meta/core#/$defs/uriReferenceString", + "deprecated": true + } + } + }`) +} + +var vocabSchemas = map[string]string{ + "https://json-schema.org/draft/2019-09/meta/core": `{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/core", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/core": true + }, + "$recursiveAnchor": true, + + "title": "Core vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference", + "$comment": "Non-empty fragments not allowed.", + "pattern": "^[^#]*#?$" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$anchor": { + "type": "string", + "pattern": "^[A-Za-z][-A-Za-z0-9.:_]*$" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "$recursiveRef": { + "type": "string", + "format": "uri-reference" + }, + "$recursiveAnchor": { + "type": "boolean", + "default": false + }, + "$vocabulary": { + "type": "object", + "propertyNames": { + "type": "string", + "format": "uri" + }, + "additionalProperties": { + "type": "boolean" + } + }, + "$comment": { + "type": "string" + }, + "$defs": { + "type": "object", + "additionalProperties": { "$recursiveRef": "#" }, + "default": {} + } + } + }`, + "https://json-schema.org/draft/2019-09/meta/applicator": `{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/applicator", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/applicator": true + }, + "$recursiveAnchor": true, + + "title": "Applicator vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "additionalItems": { "$recursiveRef": "#" }, + "unevaluatedItems": { "$recursiveRef": "#" }, + "items": { + "anyOf": [ + { "$recursiveRef": "#" }, + { "$ref": "#/$defs/schemaArray" } + ] + }, + "contains": { "$recursiveRef": "#" }, + "additionalProperties": { "$recursiveRef": "#" }, + "unevaluatedProperties": { "$recursiveRef": "#" }, + "properties": { + "type": "object", + "additionalProperties": { "$recursiveRef": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$recursiveRef": "#" }, + "propertyNames": { "format": "regex" }, + "default": {} + }, + "dependentSchemas": { + "type": "object", + "additionalProperties": { + "$recursiveRef": "#" + } + }, + "propertyNames": { "$recursiveRef": "#" }, + "if": { "$recursiveRef": "#" }, + "then": { "$recursiveRef": "#" }, + "else": { "$recursiveRef": "#" }, + "allOf": { "$ref": "#/$defs/schemaArray" }, + "anyOf": { "$ref": "#/$defs/schemaArray" }, + "oneOf": { "$ref": "#/$defs/schemaArray" }, + "not": { "$recursiveRef": "#" } + }, + "$defs": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$recursiveRef": "#" } + } + } + }`, + "https://json-schema.org/draft/2019-09/meta/validation": `{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/validation", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/validation": true + }, + "$recursiveAnchor": true, + + "title": "Validation vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/$defs/nonNegativeInteger" }, + "minLength": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "maxItems": { "$ref": "#/$defs/nonNegativeInteger" }, + "minItems": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxContains": { "$ref": "#/$defs/nonNegativeInteger" }, + "minContains": { + "$ref": "#/$defs/nonNegativeInteger", + "default": 1 + }, + "maxProperties": { "$ref": "#/$defs/nonNegativeInteger" }, + "minProperties": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/$defs/stringArray" }, + "dependentRequired": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/stringArray" + } + }, + "const": true, + "enum": { + "type": "array", + "items": true + }, + "type": { + "anyOf": [ + { "$ref": "#/$defs/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/$defs/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + } + }, + "$defs": { + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "$ref": "#/$defs/nonNegativeInteger", + "default": 0 + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + } + }`, + "https://json-schema.org/draft/2019-09/meta/meta-data": `{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/meta-data", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/meta-data": true + }, + "$recursiveAnchor": true, + + "title": "Meta-data vocabulary meta-schema", + + "type": ["object", "boolean"], + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "deprecated": { + "type": "boolean", + "default": false + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + } + } + }`, + "https://json-schema.org/draft/2019-09/meta/format": `{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/format", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/format": true + }, + "$recursiveAnchor": true, + + "title": "Format vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "format": { "type": "string" } + } + }`, + "https://json-schema.org/draft/2019-09/meta/content": `{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/content", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/content": true + }, + "$recursiveAnchor": true, + + "title": "Content vocabulary meta-schema", + + "type": ["object", "boolean"], + "properties": { + "contentMediaType": { "type": "string" }, + "contentEncoding": { "type": "string" }, + "contentSchema": { "$recursiveRef": "#" } + } + }`, + "https://json-schema.org/draft/2020-12/meta/core": `{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/core", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/core": true + }, + "$dynamicAnchor": "meta", + + "title": "Core vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "$id": { + "$ref": "#/$defs/uriReferenceString", + "$comment": "Non-empty fragments not allowed.", + "pattern": "^[^#]*#?$" + }, + "$schema": { "$ref": "#/$defs/uriString" }, + "$ref": { "$ref": "#/$defs/uriReferenceString" }, + "$anchor": { "$ref": "#/$defs/anchorString" }, + "$dynamicRef": { "$ref": "#/$defs/uriReferenceString" }, + "$dynamicAnchor": { "$ref": "#/$defs/anchorString" }, + "$vocabulary": { + "type": "object", + "propertyNames": { "$ref": "#/$defs/uriString" }, + "additionalProperties": { + "type": "boolean" + } + }, + "$comment": { + "type": "string" + }, + "$defs": { + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" } + } + }, + "$defs": { + "anchorString": { + "type": "string", + "pattern": "^[A-Za-z_][-A-Za-z0-9._]*$" + }, + "uriString": { + "type": "string", + "format": "uri" + }, + "uriReferenceString": { + "type": "string", + "format": "uri-reference" + } + } + }`, + "https://json-schema.org/draft/2020-12/meta/applicator": `{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/applicator", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/applicator": true + }, + "$dynamicAnchor": "meta", + + "title": "Applicator vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "prefixItems": { "$ref": "#/$defs/schemaArray" }, + "items": { "$dynamicRef": "#meta" }, + "contains": { "$dynamicRef": "#meta" }, + "additionalProperties": { "$dynamicRef": "#meta" }, + "properties": { + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" }, + "propertyNames": { "format": "regex" }, + "default": {} + }, + "dependentSchemas": { + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" }, + "default": {} + }, + "propertyNames": { "$dynamicRef": "#meta" }, + "if": { "$dynamicRef": "#meta" }, + "then": { "$dynamicRef": "#meta" }, + "else": { "$dynamicRef": "#meta" }, + "allOf": { "$ref": "#/$defs/schemaArray" }, + "anyOf": { "$ref": "#/$defs/schemaArray" }, + "oneOf": { "$ref": "#/$defs/schemaArray" }, + "not": { "$dynamicRef": "#meta" } + }, + "$defs": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$dynamicRef": "#meta" } + } + } + }`, + "https://json-schema.org/draft/2020-12/meta/unevaluated": `{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/unevaluated", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/unevaluated": true + }, + "$dynamicAnchor": "meta", + + "title": "Unevaluated applicator vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "unevaluatedItems": { "$dynamicRef": "#meta" }, + "unevaluatedProperties": { "$dynamicRef": "#meta" } + } + }`, + "https://json-schema.org/draft/2020-12/meta/validation": `{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/validation", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/validation": true + }, + "$dynamicAnchor": "meta", + + "title": "Validation vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "type": { + "anyOf": [ + { "$ref": "#/$defs/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/$defs/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "const": true, + "enum": { + "type": "array", + "items": true + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/$defs/nonNegativeInteger" }, + "minLength": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "maxItems": { "$ref": "#/$defs/nonNegativeInteger" }, + "minItems": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxContains": { "$ref": "#/$defs/nonNegativeInteger" }, + "minContains": { + "$ref": "#/$defs/nonNegativeInteger", + "default": 1 + }, + "maxProperties": { "$ref": "#/$defs/nonNegativeInteger" }, + "minProperties": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/$defs/stringArray" }, + "dependentRequired": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/stringArray" + } + } + }, + "$defs": { + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "$ref": "#/$defs/nonNegativeInteger", + "default": 0 + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + } + }`, + "https://json-schema.org/draft/2020-12/meta/meta-data": `{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/meta-data", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/meta-data": true + }, + "$dynamicAnchor": "meta", + + "title": "Meta-data vocabulary meta-schema", + + "type": ["object", "boolean"], + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "deprecated": { + "type": "boolean", + "default": false + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + } + } + }`, + "https://json-schema.org/draft/2020-12/meta/format-annotation": `{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/format-annotation", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/format-annotation": true + }, + "$dynamicAnchor": "meta", + + "title": "Format vocabulary meta-schema for annotation results", + "type": ["object", "boolean"], + "properties": { + "format": { "type": "string" } + } + }`, + "https://json-schema.org/draft/2020-12/meta/format-assertion": `{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/format-assertion", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/format-assertion": true + }, + "$dynamicAnchor": "meta", + + "title": "Format vocabulary meta-schema for assertion results", + "type": ["object", "boolean"], + "properties": { + "format": { "type": "string" } + } + }`, + "https://json-schema.org/draft/2020-12/meta/content": `{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/content", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/content": true + }, + "$dynamicAnchor": "meta", + + "title": "Content vocabulary meta-schema", + + "type": ["object", "boolean"], + "properties": { + "contentEncoding": { "type": "string" }, + "contentMediaType": { "type": "string" }, + "contentSchema": { "$dynamicRef": "#meta" } + } + }`, +} + +func clone(m map[string]position) map[string]position { + mm := make(map[string]position) + for k, v := range m { + mm[k] = v + } + return mm +} diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v5/errors.go b/vendor/github.com/santhosh-tekuri/jsonschema/v5/errors.go new file mode 100644 index 0000000000..deaded89f7 --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v5/errors.go @@ -0,0 +1,129 @@ +package jsonschema + +import ( + "fmt" + "strings" +) + +// InvalidJSONTypeError is the error type returned by ValidateInterface. +// this tells that specified go object is not valid jsonType. +type InvalidJSONTypeError string + +func (e InvalidJSONTypeError) Error() string { + return fmt.Sprintf("jsonschema: invalid jsonType: %s", string(e)) +} + +// InfiniteLoopError is returned by Compile/Validate. +// this gives url#keywordLocation that lead to infinity loop. +type InfiniteLoopError string + +func (e InfiniteLoopError) Error() string { + return "jsonschema: infinite loop " + string(e) +} + +func infiniteLoopError(stack []schemaRef, sref schemaRef) InfiniteLoopError { + var path string + for _, ref := range stack { + if path == "" { + path += ref.schema.Location + } else { + path += "/" + ref.path + } + } + return InfiniteLoopError(path + "/" + sref.path) +} + +// SchemaError is the error type returned by Compile. +type SchemaError struct { + // SchemaURL is the url to json-schema that filed to compile. + // This is helpful, if your schema refers to external schemas + SchemaURL string + + // Err is the error that occurred during compilation. + // It could be ValidationError, because compilation validates + // given schema against the json meta-schema + Err error +} + +func (se *SchemaError) Unwrap() error { + return se.Err +} + +func (se *SchemaError) Error() string { + s := fmt.Sprintf("jsonschema %s compilation failed", se.SchemaURL) + if se.Err != nil { + return fmt.Sprintf("%s: %v", s, strings.TrimPrefix(se.Err.Error(), "jsonschema: ")) + } + return s +} + +func (se *SchemaError) GoString() string { + if _, ok := se.Err.(*ValidationError); ok { + return fmt.Sprintf("jsonschema %s compilation failed\n%#v", se.SchemaURL, se.Err) + } + return se.Error() +} + +// ValidationError is the error type returned by Validate. +type ValidationError struct { + KeywordLocation string // validation path of validating keyword or schema + AbsoluteKeywordLocation string // absolute location of validating keyword or schema + InstanceLocation string // location of the json value within the instance being validated + Message string // describes error + Causes []*ValidationError // nested validation errors +} + +func (ve *ValidationError) add(causes ...error) error { + for _, cause := range causes { + ve.Causes = append(ve.Causes, cause.(*ValidationError)) + } + return ve +} + +func (ve *ValidationError) causes(err error) error { + if err := err.(*ValidationError); err.Message == "" { + ve.Causes = err.Causes + } else { + ve.add(err) + } + return ve +} + +func (ve *ValidationError) Error() string { + leaf := ve + for len(leaf.Causes) > 0 { + leaf = leaf.Causes[0] + } + u, _ := split(ve.AbsoluteKeywordLocation) + return fmt.Sprintf("jsonschema: %s does not validate with %s: %s", quote(leaf.InstanceLocation), u+"#"+leaf.KeywordLocation, leaf.Message) +} + +func (ve *ValidationError) GoString() string { + sloc := ve.AbsoluteKeywordLocation + sloc = sloc[strings.IndexByte(sloc, '#')+1:] + msg := fmt.Sprintf("[I#%s] [S#%s] %s", ve.InstanceLocation, sloc, ve.Message) + for _, c := range ve.Causes { + for _, line := range strings.Split(c.GoString(), "\n") { + msg += "\n " + line + } + } + return msg +} + +func joinPtr(ptr1, ptr2 string) string { + if len(ptr1) == 0 { + return ptr2 + } + if len(ptr2) == 0 { + return ptr1 + } + return ptr1 + "/" + ptr2 +} + +// quote returns single-quoted string +func quote(s string) string { + s = fmt.Sprintf("%q", s) + s = strings.ReplaceAll(s, `\"`, `"`) + s = strings.ReplaceAll(s, `'`, `\'`) + return "'" + s[1:len(s)-1] + "'" +} diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v5/extension.go b/vendor/github.com/santhosh-tekuri/jsonschema/v5/extension.go new file mode 100644 index 0000000000..452ba118c5 --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v5/extension.go @@ -0,0 +1,116 @@ +package jsonschema + +// ExtCompiler compiles custom keyword(s) into ExtSchema. +type ExtCompiler interface { + // Compile compiles the custom keywords in schema m and returns its compiled representation. + // if the schema m does not contain the keywords defined by this extension, + // compiled representation nil should be returned. + Compile(ctx CompilerContext, m map[string]interface{}) (ExtSchema, error) +} + +// ExtSchema is schema representation of custom keyword(s) +type ExtSchema interface { + // Validate validates the json value v with this ExtSchema. + // Returned error must be *ValidationError. + Validate(ctx ValidationContext, v interface{}) error +} + +type extension struct { + meta *Schema + compiler ExtCompiler +} + +// RegisterExtension registers custom keyword(s) into this compiler. +// +// name is extension name, used only to avoid name collisions. +// meta captures the metaschema for the new keywords. +// This is used to validate the schema before calling ext.Compile. +func (c *Compiler) RegisterExtension(name string, meta *Schema, ext ExtCompiler) { + c.extensions[name] = extension{meta, ext} +} + +// CompilerContext --- + +// CompilerContext provides additional context required in compiling for extension. +type CompilerContext struct { + c *Compiler + r *resource + stack []schemaRef + res *resource +} + +// Compile compiles given value at ptr into *Schema. This is useful in implementing +// keyword like allOf/not/patternProperties. +// +// schPath is the relative-json-pointer to the schema to be compiled from parent schema. +// +// applicableOnSameInstance tells whether current schema and the given schema +// are applied on same instance value. this is used to detect infinite loop in schema. +func (ctx CompilerContext) Compile(schPath string, applicableOnSameInstance bool) (*Schema, error) { + var stack []schemaRef + if applicableOnSameInstance { + stack = ctx.stack + } + return ctx.c.compileRef(ctx.r, stack, schPath, ctx.res, ctx.r.url+ctx.res.floc+"/"+schPath) +} + +// CompileRef compiles the schema referenced by ref uri +// +// refPath is the relative-json-pointer to ref. +// +// applicableOnSameInstance tells whether current schema and the given schema +// are applied on same instance value. this is used to detect infinite loop in schema. +func (ctx CompilerContext) CompileRef(ref string, refPath string, applicableOnSameInstance bool) (*Schema, error) { + var stack []schemaRef + if applicableOnSameInstance { + stack = ctx.stack + } + return ctx.c.compileRef(ctx.r, stack, refPath, ctx.res, ref) +} + +// ValidationContext --- + +// ValidationContext provides additional context required in validating for extension. +type ValidationContext struct { + result validationResult + validate func(sch *Schema, schPath string, v interface{}, vpath string) error + validateInplace func(sch *Schema, schPath string) error + validationError func(keywordPath string, format string, a ...interface{}) *ValidationError +} + +// EvaluatedProp marks given property of object as evaluated. +func (ctx ValidationContext) EvaluatedProp(prop string) { + delete(ctx.result.unevalProps, prop) +} + +// EvaluatedItem marks given index of array as evaluated. +func (ctx ValidationContext) EvaluatedItem(index int) { + delete(ctx.result.unevalItems, index) +} + +// Validate validates schema s with value v. Extension must use this method instead of +// *Schema.ValidateInterface method. This will be useful in implementing keywords like +// allOf/oneOf +// +// spath is relative-json-pointer to s +// vpath is relative-json-pointer to v. +func (ctx ValidationContext) Validate(s *Schema, spath string, v interface{}, vpath string) error { + if vpath == "" { + return ctx.validateInplace(s, spath) + } + return ctx.validate(s, spath, v, vpath) +} + +// Error used to construct validation error by extensions. +// +// keywordPath is relative-json-pointer to keyword. +func (ctx ValidationContext) Error(keywordPath string, format string, a ...interface{}) *ValidationError { + return ctx.validationError(keywordPath, format, a...) +} + +// Group is used by extensions to group multiple errors as causes to parent error. +// This is useful in implementing keywords like allOf where each schema specified +// in allOf can result a validationError. +func (ValidationError) Group(parent *ValidationError, causes ...error) error { + return parent.add(causes...) +} diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v5/format.go b/vendor/github.com/santhosh-tekuri/jsonschema/v5/format.go new file mode 100644 index 0000000000..05686073f0 --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v5/format.go @@ -0,0 +1,567 @@ +package jsonschema + +import ( + "errors" + "net" + "net/mail" + "net/url" + "regexp" + "strconv" + "strings" + "time" +) + +// Formats is a registry of functions, which know how to validate +// a specific format. +// +// New Formats can be registered by adding to this map. Key is format name, +// value is function that knows how to validate that format. +var Formats = map[string]func(interface{}) bool{ + "date-time": isDateTime, + "date": isDate, + "time": isTime, + "duration": isDuration, + "period": isPeriod, + "hostname": isHostname, + "email": isEmail, + "ip-address": isIPV4, + "ipv4": isIPV4, + "ipv6": isIPV6, + "uri": isURI, + "iri": isURI, + "uri-reference": isURIReference, + "uriref": isURIReference, + "iri-reference": isURIReference, + "uri-template": isURITemplate, + "regex": isRegex, + "json-pointer": isJSONPointer, + "relative-json-pointer": isRelativeJSONPointer, + "uuid": isUUID, +} + +// isDateTime tells whether given string is a valid date representation +// as defined by RFC 3339, section 5.6. +// +// see https://datatracker.ietf.org/doc/html/rfc3339#section-5.6, for details +func isDateTime(v interface{}) bool { + s, ok := v.(string) + if !ok { + return true + } + if len(s) < 20 { // yyyy-mm-ddThh:mm:ssZ + return false + } + if s[10] != 'T' && s[10] != 't' { + return false + } + return isDate(s[:10]) && isTime(s[11:]) +} + +// isDate tells whether given string is a valid full-date production +// as defined by RFC 3339, section 5.6. +// +// see https://datatracker.ietf.org/doc/html/rfc3339#section-5.6, for details +func isDate(v interface{}) bool { + s, ok := v.(string) + if !ok { + return true + } + _, err := time.Parse("2006-01-02", s) + return err == nil +} + +// isTime tells whether given string is a valid full-time production +// as defined by RFC 3339, section 5.6. +// +// see https://datatracker.ietf.org/doc/html/rfc3339#section-5.6, for details +func isTime(v interface{}) bool { + str, ok := v.(string) + if !ok { + return true + } + + // golang time package does not support leap seconds. + // so we are parsing it manually here. + + // hh:mm:ss + // 01234567 + if len(str) < 9 || str[2] != ':' || str[5] != ':' { + return false + } + isInRange := func(str string, min, max int) (int, bool) { + n, err := strconv.Atoi(str) + if err != nil { + return 0, false + } + if n < min || n > max { + return 0, false + } + return n, true + } + var h, m, s int + if h, ok = isInRange(str[0:2], 0, 23); !ok { + return false + } + if m, ok = isInRange(str[3:5], 0, 59); !ok { + return false + } + if s, ok = isInRange(str[6:8], 0, 60); !ok { + return false + } + str = str[8:] + + // parse secfrac if present + if str[0] == '.' { + // dot following more than one digit + str = str[1:] + var numDigits int + for str != "" { + if str[0] < '0' || str[0] > '9' { + break + } + numDigits++ + str = str[1:] + } + if numDigits == 0 { + return false + } + } + + if len(str) == 0 { + return false + } + + if str[0] == 'z' || str[0] == 'Z' { + if len(str) != 1 { + return false + } + } else { + // time-numoffset + // +hh:mm + // 012345 + if len(str) != 6 || str[3] != ':' { + return false + } + + var sign int + if str[0] == '+' { + sign = -1 + } else if str[0] == '-' { + sign = +1 + } else { + return false + } + + var zh, zm int + if zh, ok = isInRange(str[1:3], 0, 23); !ok { + return false + } + if zm, ok = isInRange(str[4:6], 0, 59); !ok { + return false + } + + // apply timezone offset + hm := (h*60 + m) + sign*(zh*60+zm) + if hm < 0 { + hm += 24 * 60 + } + h, m = hm/60, hm%60 + } + + // check leapsecond + if s == 60 { // leap second + if h != 23 || m != 59 { + return false + } + } + + return true +} + +// isDuration tells whether given string is a valid duration format +// from the ISO 8601 ABNF as given in Appendix A of RFC 3339. +// +// see https://datatracker.ietf.org/doc/html/rfc3339#appendix-A, for details +func isDuration(v interface{}) bool { + s, ok := v.(string) + if !ok { + return true + } + if len(s) == 0 || s[0] != 'P' { + return false + } + s = s[1:] + parseUnits := func() (units string, ok bool) { + for len(s) > 0 && s[0] != 'T' { + digits := false + for { + if len(s) == 0 { + break + } + if s[0] < '0' || s[0] > '9' { + break + } + digits = true + s = s[1:] + } + if !digits || len(s) == 0 { + return units, false + } + units += s[:1] + s = s[1:] + } + return units, true + } + units, ok := parseUnits() + if !ok { + return false + } + if units == "W" { + return len(s) == 0 // P_W + } + if len(units) > 0 { + if strings.Index("YMD", units) == -1 { + return false + } + if len(s) == 0 { + return true // "P" dur-date + } + } + if len(s) == 0 || s[0] != 'T' { + return false + } + s = s[1:] + units, ok = parseUnits() + return ok && len(s) == 0 && len(units) > 0 && strings.Index("HMS", units) != -1 +} + +// isPeriod tells whether given string is a valid period format +// from the ISO 8601 ABNF as given in Appendix A of RFC 3339. +// +// see https://datatracker.ietf.org/doc/html/rfc3339#appendix-A, for details +func isPeriod(v interface{}) bool { + s, ok := v.(string) + if !ok { + return true + } + slash := strings.IndexByte(s, '/') + if slash == -1 { + return false + } + start, end := s[:slash], s[slash+1:] + if isDateTime(start) { + return isDateTime(end) || isDuration(end) + } + return isDuration(start) && isDateTime(end) +} + +// isHostname tells whether given string is a valid representation +// for an Internet host name, as defined by RFC 1034 section 3.1 and +// RFC 1123 section 2.1. +// +// See https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names, for details. +func isHostname(v interface{}) bool { + s, ok := v.(string) + if !ok { + return true + } + // entire hostname (including the delimiting dots but not a trailing dot) has a maximum of 253 ASCII characters + s = strings.TrimSuffix(s, ".") + if len(s) > 253 { + return false + } + + // Hostnames are composed of series of labels concatenated with dots, as are all domain names + for _, label := range strings.Split(s, ".") { + // Each label must be from 1 to 63 characters long + if labelLen := len(label); labelLen < 1 || labelLen > 63 { + return false + } + + // labels must not start with a hyphen + // RFC 1123 section 2.1: restriction on the first character + // is relaxed to allow either a letter or a digit + if first := s[0]; first == '-' { + return false + } + + // must not end with a hyphen + if label[len(label)-1] == '-' { + return false + } + + // labels may contain only the ASCII letters 'a' through 'z' (in a case-insensitive manner), + // the digits '0' through '9', and the hyphen ('-') + for _, c := range label { + if valid := (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '-'); !valid { + return false + } + } + } + + return true +} + +// isEmail tells whether given string is a valid Internet email address +// as defined by RFC 5322, section 3.4.1. +// +// See https://en.wikipedia.org/wiki/Email_address, for details. +func isEmail(v interface{}) bool { + s, ok := v.(string) + if !ok { + return true + } + // entire email address to be no more than 254 characters long + if len(s) > 254 { + return false + } + + // email address is generally recognized as having two parts joined with an at-sign + at := strings.LastIndexByte(s, '@') + if at == -1 { + return false + } + local := s[0:at] + domain := s[at+1:] + + // local part may be up to 64 characters long + if len(local) > 64 { + return false + } + + // domain if enclosed in brackets, must match an IP address + if len(domain) >= 2 && domain[0] == '[' && domain[len(domain)-1] == ']' { + ip := domain[1 : len(domain)-1] + if strings.HasPrefix(ip, "IPv6:") { + return isIPV6(strings.TrimPrefix(ip, "IPv6:")) + } + return isIPV4(ip) + } + + // domain must match the requirements for a hostname + if !isHostname(domain) { + return false + } + + _, err := mail.ParseAddress(s) + return err == nil +} + +// isIPV4 tells whether given string is a valid representation of an IPv4 address +// according to the "dotted-quad" ABNF syntax as defined in RFC 2673, section 3.2. +func isIPV4(v interface{}) bool { + s, ok := v.(string) + if !ok { + return true + } + groups := strings.Split(s, ".") + if len(groups) != 4 { + return false + } + for _, group := range groups { + n, err := strconv.Atoi(group) + if err != nil { + return false + } + if n < 0 || n > 255 { + return false + } + if n != 0 && group[0] == '0' { + return false // leading zeroes should be rejected, as they are treated as octals + } + } + return true +} + +// isIPV6 tells whether given string is a valid representation of an IPv6 address +// as defined in RFC 2373, section 2.2. +func isIPV6(v interface{}) bool { + s, ok := v.(string) + if !ok { + return true + } + if !strings.Contains(s, ":") { + return false + } + return net.ParseIP(s) != nil +} + +// isURI tells whether given string is valid URI, according to RFC 3986. +func isURI(v interface{}) bool { + s, ok := v.(string) + if !ok { + return true + } + u, err := urlParse(s) + return err == nil && u.IsAbs() +} + +func urlParse(s string) (*url.URL, error) { + u, err := url.Parse(s) + if err != nil { + return nil, err + } + + // if hostname is ipv6, validate it + hostname := u.Hostname() + if strings.IndexByte(hostname, ':') != -1 { + if strings.IndexByte(u.Host, '[') == -1 || strings.IndexByte(u.Host, ']') == -1 { + return nil, errors.New("ipv6 address is not enclosed in brackets") + } + if !isIPV6(hostname) { + return nil, errors.New("invalid ipv6 address") + } + } + return u, nil +} + +// isURIReference tells whether given string is a valid URI Reference +// (either a URI or a relative-reference), according to RFC 3986. +func isURIReference(v interface{}) bool { + s, ok := v.(string) + if !ok { + return true + } + _, err := urlParse(s) + return err == nil && !strings.Contains(s, `\`) +} + +// isURITemplate tells whether given string is a valid URI Template +// according to RFC6570. +// +// Current implementation does minimal validation. +func isURITemplate(v interface{}) bool { + s, ok := v.(string) + if !ok { + return true + } + u, err := urlParse(s) + if err != nil { + return false + } + for _, item := range strings.Split(u.RawPath, "/") { + depth := 0 + for _, ch := range item { + switch ch { + case '{': + depth++ + if depth != 1 { + return false + } + case '}': + depth-- + if depth != 0 { + return false + } + } + } + if depth != 0 { + return false + } + } + return true +} + +// isRegex tells whether given string is a valid regular expression, +// according to the ECMA 262 regular expression dialect. +// +// The implementation uses go-lang regexp package. +func isRegex(v interface{}) bool { + s, ok := v.(string) + if !ok { + return true + } + _, err := regexp.Compile(s) + return err == nil +} + +// isJSONPointer tells whether given string is a valid JSON Pointer. +// +// Note: It returns false for JSON Pointer URI fragments. +func isJSONPointer(v interface{}) bool { + s, ok := v.(string) + if !ok { + return true + } + if s != "" && !strings.HasPrefix(s, "/") { + return false + } + for _, item := range strings.Split(s, "/") { + for i := 0; i < len(item); i++ { + if item[i] == '~' { + if i == len(item)-1 { + return false + } + switch item[i+1] { + case '0', '1': + // valid + default: + return false + } + } + } + } + return true +} + +// isRelativeJSONPointer tells whether given string is a valid Relative JSON Pointer. +// +// see https://tools.ietf.org/html/draft-handrews-relative-json-pointer-01#section-3 +func isRelativeJSONPointer(v interface{}) bool { + s, ok := v.(string) + if !ok { + return true + } + if s == "" { + return false + } + if s[0] == '0' { + s = s[1:] + } else if s[0] >= '0' && s[0] <= '9' { + for s != "" && s[0] >= '0' && s[0] <= '9' { + s = s[1:] + } + } else { + return false + } + return s == "#" || isJSONPointer(s) +} + +// isUUID tells whether given string is a valid uuid format +// as specified in RFC4122. +// +// see https://datatracker.ietf.org/doc/html/rfc4122#page-4, for details +func isUUID(v interface{}) bool { + s, ok := v.(string) + if !ok { + return true + } + parseHex := func(n int) bool { + for n > 0 { + if len(s) == 0 { + return false + } + hex := (s[0] >= '0' && s[0] <= '9') || (s[0] >= 'a' && s[0] <= 'f') || (s[0] >= 'A' && s[0] <= 'F') + if !hex { + return false + } + s = s[1:] + n-- + } + return true + } + groups := []int{8, 4, 4, 4, 12} + for i, numDigits := range groups { + if !parseHex(numDigits) { + return false + } + if i == len(groups)-1 { + break + } + if len(s) == 0 || s[0] != '-' { + return false + } + s = s[1:] + } + return len(s) == 0 +} diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v5/httploader/httploader.go b/vendor/github.com/santhosh-tekuri/jsonschema/v5/httploader/httploader.go new file mode 100644 index 0000000000..4198cfe37c --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v5/httploader/httploader.go @@ -0,0 +1,38 @@ +// Package httploader implements loader.Loader for http/https url. +// +// The package is typically only imported for the side effect of +// registering its Loaders. +// +// To use httploader, link this package into your program: +// +// import _ "github.com/santhosh-tekuri/jsonschema/v5/httploader" +package httploader + +import ( + "fmt" + "io" + "net/http" + + "github.com/santhosh-tekuri/jsonschema/v5" +) + +// Client is the default HTTP Client used to Get the resource. +var Client = http.DefaultClient + +// Load loads resource from given http(s) url. +func Load(url string) (io.ReadCloser, error) { + resp, err := Client.Get(url) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + _ = resp.Body.Close() + return nil, fmt.Errorf("%s returned status code %d", url, resp.StatusCode) + } + return resp.Body, nil +} + +func init() { + jsonschema.Loaders["http"] = Load + jsonschema.Loaders["https"] = Load +} diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v5/loader.go b/vendor/github.com/santhosh-tekuri/jsonschema/v5/loader.go new file mode 100644 index 0000000000..c94195c335 --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v5/loader.go @@ -0,0 +1,60 @@ +package jsonschema + +import ( + "fmt" + "io" + "net/url" + "os" + "path/filepath" + "runtime" + "strings" +) + +func loadFileURL(s string) (io.ReadCloser, error) { + u, err := url.Parse(s) + if err != nil { + return nil, err + } + f := u.Path + if runtime.GOOS == "windows" { + f = strings.TrimPrefix(f, "/") + f = filepath.FromSlash(f) + } + return os.Open(f) +} + +// Loaders is a registry of functions, which know how to load +// absolute url of specific schema. +// +// New loaders can be registered by adding to this map. Key is schema, +// value is function that knows how to load url of that schema +var Loaders = map[string]func(url string) (io.ReadCloser, error){ + "file": loadFileURL, +} + +// LoaderNotFoundError is the error type returned by Load function. +// It tells that no Loader is registered for that URL Scheme. +type LoaderNotFoundError string + +func (e LoaderNotFoundError) Error() string { + return fmt.Sprintf("jsonschema: no Loader found for %s", string(e)) +} + +// LoadURL loads document at given absolute URL. The default implementation +// uses Loaders registry to lookup by schema and uses that loader. +// +// Users can change this variable, if they would like to take complete +// responsibility of loading given URL. Used by Compiler if its LoadURL +// field is nil. +var LoadURL = func(s string) (io.ReadCloser, error) { + u, err := url.Parse(s) + if err != nil { + return nil, err + } + loader, ok := Loaders[u.Scheme] + if !ok { + return nil, LoaderNotFoundError(s) + + } + return loader(s) +} diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v5/output.go b/vendor/github.com/santhosh-tekuri/jsonschema/v5/output.go new file mode 100644 index 0000000000..d65ae2a929 --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v5/output.go @@ -0,0 +1,77 @@ +package jsonschema + +// Flag is output format with simple boolean property valid. +type Flag struct { + Valid bool `json:"valid"` +} + +// FlagOutput returns output in flag format +func (ve *ValidationError) FlagOutput() Flag { + return Flag{} +} + +// Basic --- + +// Basic is output format with flat list of output units. +type Basic struct { + Valid bool `json:"valid"` + Errors []BasicError `json:"errors"` +} + +// BasicError is output unit in basic format. +type BasicError struct { + KeywordLocation string `json:"keywordLocation"` + AbsoluteKeywordLocation string `json:"absoluteKeywordLocation"` + InstanceLocation string `json:"instanceLocation"` + Error string `json:"error"` +} + +// BasicOutput returns output in basic format +func (ve *ValidationError) BasicOutput() Basic { + var errors []BasicError + var flatten func(*ValidationError) + flatten = func(ve *ValidationError) { + errors = append(errors, BasicError{ + KeywordLocation: ve.KeywordLocation, + AbsoluteKeywordLocation: ve.AbsoluteKeywordLocation, + InstanceLocation: ve.InstanceLocation, + Error: ve.Message, + }) + for _, cause := range ve.Causes { + flatten(cause) + } + } + flatten(ve) + return Basic{Errors: errors} +} + +// Detailed --- + +// Detailed is output format based on structure of schema. +type Detailed struct { + Valid bool `json:"valid"` + KeywordLocation string `json:"keywordLocation"` + AbsoluteKeywordLocation string `json:"absoluteKeywordLocation"` + InstanceLocation string `json:"instanceLocation"` + Error string `json:"error,omitempty"` + Errors []Detailed `json:"errors,omitempty"` +} + +// DetailedOutput returns output in detailed format +func (ve *ValidationError) DetailedOutput() Detailed { + var errors []Detailed + for _, cause := range ve.Causes { + errors = append(errors, cause.DetailedOutput()) + } + var message = ve.Message + if len(ve.Causes) > 0 { + message = "" + } + return Detailed{ + KeywordLocation: ve.KeywordLocation, + AbsoluteKeywordLocation: ve.AbsoluteKeywordLocation, + InstanceLocation: ve.InstanceLocation, + Error: message, + Errors: errors, + } +} diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v5/resource.go b/vendor/github.com/santhosh-tekuri/jsonschema/v5/resource.go new file mode 100644 index 0000000000..18349daac7 --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v5/resource.go @@ -0,0 +1,280 @@ +package jsonschema + +import ( + "encoding/json" + "fmt" + "io" + "net/url" + "path/filepath" + "runtime" + "strconv" + "strings" +) + +type resource struct { + url string // base url of resource. can be empty + floc string // fragment with json-pointer from root resource + doc interface{} + draft *Draft + subresources map[string]*resource // key is floc. only applicable for root resource + schema *Schema +} + +func (r *resource) String() string { + return r.url + r.floc +} + +func newResource(url string, r io.Reader) (*resource, error) { + if strings.IndexByte(url, '#') != -1 { + panic(fmt.Sprintf("BUG: newResource(%q)", url)) + } + doc, err := unmarshal(r) + if err != nil { + return nil, fmt.Errorf("jsonschema: invalid json %s: %v", url, err) + } + url, err = toAbs(url) + if err != nil { + return nil, err + } + return &resource{ + url: url, + floc: "#", + doc: doc, + }, nil +} + +// fillSubschemas fills subschemas in res into r.subresources +func (r *resource) fillSubschemas(c *Compiler, res *resource) error { + if err := c.validateSchema(r, res.doc, res.floc[1:]); err != nil { + return err + } + + if r.subresources == nil { + r.subresources = make(map[string]*resource) + } + if err := r.draft.listSubschemas(res, r.baseURL(res.floc), r.subresources); err != nil { + return err + } + + // ensure subresource.url uniqueness + url2floc := make(map[string]string) + for _, sr := range r.subresources { + if sr.url != "" { + if floc, ok := url2floc[sr.url]; ok { + return fmt.Errorf("jsonschema: %q and %q in %s have same canonical-uri", floc[1:], sr.floc[1:], r.url) + } + url2floc[sr.url] = sr.floc + } + } + + return nil +} + +// listResources lists all subresources in res +func (r *resource) listResources(res *resource) []*resource { + var result []*resource + prefix := res.floc + "/" + for _, sr := range r.subresources { + if strings.HasPrefix(sr.floc, prefix) { + result = append(result, sr) + } + } + return result +} + +func (r *resource) findResource(url string) *resource { + if r.url == url { + return r + } + for _, res := range r.subresources { + if res.url == url { + return res + } + } + return nil +} + +// resolve fragment f with sr as base +func (r *resource) resolveFragment(c *Compiler, sr *resource, f string) (*resource, error) { + if f == "#" || f == "#/" { + return sr, nil + } + + // resolve by anchor + if !strings.HasPrefix(f, "#/") { + // check in given resource + for _, anchor := range r.draft.anchors(sr.doc) { + if anchor == f[1:] { + return sr, nil + } + } + + // check in subresources that has same base url + prefix := sr.floc + "/" + for _, res := range r.subresources { + if strings.HasPrefix(res.floc, prefix) && r.baseURL(res.floc) == sr.url { + for _, anchor := range r.draft.anchors(res.doc) { + if anchor == f[1:] { + return res, nil + } + } + } + } + return nil, nil + } + + // resolve by ptr + floc := sr.floc + f[1:] + if res, ok := r.subresources[floc]; ok { + return res, nil + } + + // non-standrad location + doc := r.doc + for _, item := range strings.Split(floc[2:], "/") { + item = strings.Replace(item, "~1", "/", -1) + item = strings.Replace(item, "~0", "~", -1) + item, err := url.PathUnescape(item) + if err != nil { + return nil, err + } + switch d := doc.(type) { + case map[string]interface{}: + if _, ok := d[item]; !ok { + return nil, nil + } + doc = d[item] + case []interface{}: + index, err := strconv.Atoi(item) + if err != nil { + return nil, err + } + if index < 0 || index >= len(d) { + return nil, nil + } + doc = d[index] + default: + return nil, nil + } + } + + id, err := r.draft.resolveID(r.baseURL(floc), doc) + if err != nil { + return nil, err + } + res := &resource{url: id, floc: floc, doc: doc} + r.subresources[floc] = res + if err := r.fillSubschemas(c, res); err != nil { + return nil, err + } + return res, nil +} + +func (r *resource) baseURL(floc string) string { + for { + if sr, ok := r.subresources[floc]; ok { + if sr.url != "" { + return sr.url + } + } + slash := strings.LastIndexByte(floc, '/') + if slash == -1 { + break + } + floc = floc[:slash] + } + return r.url +} + +// url helpers --- + +func toAbs(s string) (string, error) { + // if windows absolute file path, convert to file url + // because: net/url parses driver name as scheme + if runtime.GOOS == "windows" && len(s) >= 3 && s[1:3] == `:\` { + s = "file:///" + filepath.ToSlash(s) + } + + u, err := url.Parse(s) + if err != nil { + return "", err + } + if u.IsAbs() { + return s, nil + } + + // s is filepath + if s, err = filepath.Abs(s); err != nil { + return "", err + } + if runtime.GOOS == "windows" { + s = "file:///" + filepath.ToSlash(s) + } else { + s = "file://" + s + } + u, err = url.Parse(s) // to fix spaces in filepath + return u.String(), err +} + +func resolveURL(base, ref string) (string, error) { + if ref == "" { + return base, nil + } + if strings.HasPrefix(ref, "urn:") { + return ref, nil + } + + refURL, err := url.Parse(ref) + if err != nil { + return "", err + } + if refURL.IsAbs() { + return ref, nil + } + + if strings.HasPrefix(base, "urn:") { + base, _ = split(base) + return base + ref, nil + } + + baseURL, err := url.Parse(base) + if err != nil { + return "", err + } + return baseURL.ResolveReference(refURL).String(), nil +} + +func split(uri string) (string, string) { + hash := strings.IndexByte(uri, '#') + if hash == -1 { + return uri, "#" + } + f := uri[hash:] + if f == "#/" { + f = "#" + } + return uri[0:hash], f +} + +func (s *Schema) url() string { + u, _ := split(s.Location) + return u +} + +func (s *Schema) loc() string { + _, f := split(s.Location) + return f[1:] +} + +func unmarshal(r io.Reader) (interface{}, error) { + decoder := json.NewDecoder(r) + decoder.UseNumber() + var doc interface{} + if err := decoder.Decode(&doc); err != nil { + return nil, err + } + if t, _ := decoder.Token(); t != nil { + return nil, fmt.Errorf("invalid character %v after top-level value", t) + } + return doc, nil +} diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v5/schema.go b/vendor/github.com/santhosh-tekuri/jsonschema/v5/schema.go new file mode 100644 index 0000000000..688f0a6fee --- /dev/null +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v5/schema.go @@ -0,0 +1,900 @@ +package jsonschema + +import ( + "bytes" + "encoding/json" + "fmt" + "hash/maphash" + "math/big" + "net/url" + "regexp" + "sort" + "strconv" + "strings" + "unicode/utf8" +) + +// A Schema represents compiled version of json-schema. +type Schema struct { + Location string // absolute location + + Draft *Draft // draft used by schema. + meta *Schema + vocab []string + dynamicAnchors []*Schema + + // type agnostic validations + Format string + format func(interface{}) bool + Always *bool // always pass/fail. used when booleans are used as schemas in draft-07. + Ref *Schema + RecursiveAnchor bool + RecursiveRef *Schema + DynamicAnchor string + DynamicRef *Schema + dynamicRefAnchor string + Types []string // allowed types. + Constant []interface{} // first element in slice is constant value. note: slice is used to capture nil constant. + Enum []interface{} // allowed values. + enumError string // error message for enum fail. captured here to avoid constructing error message every time. + Not *Schema + AllOf []*Schema + AnyOf []*Schema + OneOf []*Schema + If *Schema + Then *Schema // nil, when If is nil. + Else *Schema // nil, when If is nil. + + // object validations + MinProperties int // -1 if not specified. + MaxProperties int // -1 if not specified. + Required []string // list of required properties. + Properties map[string]*Schema + PropertyNames *Schema + RegexProperties bool // property names must be valid regex. used only in draft4 as workaround in metaschema. + PatternProperties map[*regexp.Regexp]*Schema + AdditionalProperties interface{} // nil or bool or *Schema. + Dependencies map[string]interface{} // map value is *Schema or []string. + DependentRequired map[string][]string + DependentSchemas map[string]*Schema + UnevaluatedProperties *Schema + + // array validations + MinItems int // -1 if not specified. + MaxItems int // -1 if not specified. + UniqueItems bool + Items interface{} // nil or *Schema or []*Schema + AdditionalItems interface{} // nil or bool or *Schema. + PrefixItems []*Schema + Items2020 *Schema // items keyword reintroduced in draft 2020-12 + Contains *Schema + ContainsEval bool // whether any item in an array that passes validation of the contains schema is considered "evaluated" + MinContains int // 1 if not specified + MaxContains int // -1 if not specified + UnevaluatedItems *Schema + + // string validations + MinLength int // -1 if not specified. + MaxLength int // -1 if not specified. + Pattern *regexp.Regexp + ContentEncoding string + decoder func(string) ([]byte, error) + ContentMediaType string + mediaType func([]byte) error + ContentSchema *Schema + + // number validators + Minimum *big.Rat + ExclusiveMinimum *big.Rat + Maximum *big.Rat + ExclusiveMaximum *big.Rat + MultipleOf *big.Rat + + // annotations. captured only when Compiler.ExtractAnnotations is true. + Title string + Description string + Default interface{} + Comment string + ReadOnly bool + WriteOnly bool + Examples []interface{} + Deprecated bool + + // user defined extensions + Extensions map[string]ExtSchema +} + +func (s *Schema) String() string { + return s.Location +} + +func newSchema(url, floc string, draft *Draft, doc interface{}) *Schema { + // fill with default values + s := &Schema{ + Location: url + floc, + Draft: draft, + MinProperties: -1, + MaxProperties: -1, + MinItems: -1, + MaxItems: -1, + MinContains: 1, + MaxContains: -1, + MinLength: -1, + MaxLength: -1, + } + + if doc, ok := doc.(map[string]interface{}); ok { + if ra, ok := doc["$recursiveAnchor"]; ok { + if ra, ok := ra.(bool); ok { + s.RecursiveAnchor = ra + } + } + if da, ok := doc["$dynamicAnchor"]; ok { + if da, ok := da.(string); ok { + s.DynamicAnchor = da + } + } + } + return s +} + +func (s *Schema) hasVocab(name string) bool { + if s == nil { // during bootstrap + return true + } + if name == "core" { + return true + } + for _, url := range s.vocab { + if url == "https://json-schema.org/draft/2019-09/vocab/"+name { + return true + } + if url == "https://json-schema.org/draft/2020-12/vocab/"+name { + return true + } + } + return false +} + +// Validate validates given doc, against the json-schema s. +// +// the v must be the raw json value. for number precision +// unmarshal with json.UseNumber(). +// +// returns *ValidationError if v does not confirm with schema s. +// returns InfiniteLoopError if it detects loop during validation. +// returns InvalidJSONTypeError if it detects any non json value in v. +func (s *Schema) Validate(v interface{}) (err error) { + return s.validateValue(v, "") +} + +func (s *Schema) validateValue(v interface{}, vloc string) (err error) { + defer func() { + if r := recover(); r != nil { + switch r := r.(type) { + case InfiniteLoopError, InvalidJSONTypeError: + err = r.(error) + default: + panic(r) + } + } + }() + if _, err := s.validate(nil, 0, "", v, vloc); err != nil { + ve := ValidationError{ + KeywordLocation: "", + AbsoluteKeywordLocation: s.Location, + InstanceLocation: vloc, + Message: fmt.Sprintf("doesn't validate with %s", s.Location), + } + return ve.causes(err) + } + return nil +} + +// validate validates given value v with this schema. +func (s *Schema) validate(scope []schemaRef, vscope int, spath string, v interface{}, vloc string) (result validationResult, err error) { + validationError := func(keywordPath string, format string, a ...interface{}) *ValidationError { + return &ValidationError{ + KeywordLocation: keywordLocation(scope, keywordPath), + AbsoluteKeywordLocation: joinPtr(s.Location, keywordPath), + InstanceLocation: vloc, + Message: fmt.Sprintf(format, a...), + } + } + + sref := schemaRef{spath, s, false} + if err := checkLoop(scope[len(scope)-vscope:], sref); err != nil { + panic(err) + } + scope = append(scope, sref) + vscope++ + + // populate result + switch v := v.(type) { + case map[string]interface{}: + result.unevalProps = make(map[string]struct{}) + for pname := range v { + result.unevalProps[pname] = struct{}{} + } + case []interface{}: + result.unevalItems = make(map[int]struct{}) + for i := range v { + result.unevalItems[i] = struct{}{} + } + } + + validate := func(sch *Schema, schPath string, v interface{}, vpath string) error { + vloc := vloc + if vpath != "" { + vloc += "/" + vpath + } + _, err := sch.validate(scope, 0, schPath, v, vloc) + return err + } + + validateInplace := func(sch *Schema, schPath string) error { + vr, err := sch.validate(scope, vscope, schPath, v, vloc) + if err == nil { + // update result + for pname := range result.unevalProps { + if _, ok := vr.unevalProps[pname]; !ok { + delete(result.unevalProps, pname) + } + } + for i := range result.unevalItems { + if _, ok := vr.unevalItems[i]; !ok { + delete(result.unevalItems, i) + } + } + } + return err + } + + if s.Always != nil { + if !*s.Always { + return result, validationError("", "not allowed") + } + return result, nil + } + + if len(s.Types) > 0 { + vType := jsonType(v) + matched := false + for _, t := range s.Types { + if vType == t { + matched = true + break + } else if t == "integer" && vType == "number" { + num, _ := new(big.Rat).SetString(fmt.Sprint(v)) + if num.IsInt() { + matched = true + break + } + } + } + if !matched { + return result, validationError("type", "expected %s, but got %s", strings.Join(s.Types, " or "), vType) + } + } + + var errors []error + + if len(s.Constant) > 0 { + if !equals(v, s.Constant[0]) { + switch jsonType(s.Constant[0]) { + case "object", "array": + errors = append(errors, validationError("const", "const failed")) + default: + errors = append(errors, validationError("const", "value must be %#v", s.Constant[0])) + } + } + } + + if len(s.Enum) > 0 { + matched := false + for _, item := range s.Enum { + if equals(v, item) { + matched = true + break + } + } + if !matched { + errors = append(errors, validationError("enum", s.enumError)) + } + } + + if s.format != nil && !s.format(v) { + var val = v + if v, ok := v.(string); ok { + val = quote(v) + } + errors = append(errors, validationError("format", "%v is not valid %s", val, quote(s.Format))) + } + + switch v := v.(type) { + case map[string]interface{}: + if s.MinProperties != -1 && len(v) < s.MinProperties { + errors = append(errors, validationError("minProperties", "minimum %d properties allowed, but found %d properties", s.MinProperties, len(v))) + } + if s.MaxProperties != -1 && len(v) > s.MaxProperties { + errors = append(errors, validationError("maxProperties", "maximum %d properties allowed, but found %d properties", s.MaxProperties, len(v))) + } + if len(s.Required) > 0 { + var missing []string + for _, pname := range s.Required { + if _, ok := v[pname]; !ok { + missing = append(missing, quote(pname)) + } + } + if len(missing) > 0 { + errors = append(errors, validationError("required", "missing properties: %s", strings.Join(missing, ", "))) + } + } + + for pname, sch := range s.Properties { + if pvalue, ok := v[pname]; ok { + delete(result.unevalProps, pname) + if err := validate(sch, "properties/"+escape(pname), pvalue, escape(pname)); err != nil { + errors = append(errors, err) + } + } + } + + if s.PropertyNames != nil { + for pname := range v { + if err := validate(s.PropertyNames, "propertyNames", pname, escape(pname)); err != nil { + errors = append(errors, err) + } + } + } + + if s.RegexProperties { + for pname := range v { + if !isRegex(pname) { + errors = append(errors, validationError("", "patternProperty %s is not valid regex", quote(pname))) + } + } + } + for pattern, sch := range s.PatternProperties { + for pname, pvalue := range v { + if pattern.MatchString(pname) { + delete(result.unevalProps, pname) + if err := validate(sch, "patternProperties/"+escape(pattern.String()), pvalue, escape(pname)); err != nil { + errors = append(errors, err) + } + } + } + } + if s.AdditionalProperties != nil { + if allowed, ok := s.AdditionalProperties.(bool); ok { + if !allowed && len(result.unevalProps) > 0 { + errors = append(errors, validationError("additionalProperties", "additionalProperties %s not allowed", result.unevalPnames())) + } + } else { + schema := s.AdditionalProperties.(*Schema) + for pname := range result.unevalProps { + if pvalue, ok := v[pname]; ok { + if err := validate(schema, "additionalProperties", pvalue, escape(pname)); err != nil { + errors = append(errors, err) + } + } + } + } + result.unevalProps = nil + } + for dname, dvalue := range s.Dependencies { + if _, ok := v[dname]; ok { + switch dvalue := dvalue.(type) { + case *Schema: + if err := validateInplace(dvalue, "dependencies/"+escape(dname)); err != nil { + errors = append(errors, err) + } + case []string: + for i, pname := range dvalue { + if _, ok := v[pname]; !ok { + errors = append(errors, validationError("dependencies/"+escape(dname)+"/"+strconv.Itoa(i), "property %s is required, if %s property exists", quote(pname), quote(dname))) + } + } + } + } + } + for dname, dvalue := range s.DependentRequired { + if _, ok := v[dname]; ok { + for i, pname := range dvalue { + if _, ok := v[pname]; !ok { + errors = append(errors, validationError("dependentRequired/"+escape(dname)+"/"+strconv.Itoa(i), "property %s is required, if %s property exists", quote(pname), quote(dname))) + } + } + } + } + for dname, sch := range s.DependentSchemas { + if _, ok := v[dname]; ok { + if err := validateInplace(sch, "dependentSchemas/"+escape(dname)); err != nil { + errors = append(errors, err) + } + } + } + + case []interface{}: + if s.MinItems != -1 && len(v) < s.MinItems { + errors = append(errors, validationError("minItems", "minimum %d items required, but found %d items", s.MinItems, len(v))) + } + if s.MaxItems != -1 && len(v) > s.MaxItems { + errors = append(errors, validationError("maxItems", "maximum %d items required, but found %d items", s.MaxItems, len(v))) + } + if s.UniqueItems { + if len(v) <= 20 { + outer1: + for i := 1; i < len(v); i++ { + for j := 0; j < i; j++ { + if equals(v[i], v[j]) { + errors = append(errors, validationError("uniqueItems", "items at index %d and %d are equal", j, i)) + break outer1 + } + } + } + } else { + m := make(map[uint64][]int) + var h maphash.Hash + outer2: + for i, item := range v { + h.Reset() + hash(item, &h) + k := h.Sum64() + if err != nil { + panic(err) + } + arr, ok := m[k] + if ok { + for _, j := range arr { + if equals(v[j], item) { + errors = append(errors, validationError("uniqueItems", "items at index %d and %d are equal", j, i)) + break outer2 + } + } + } + arr = append(arr, i) + m[k] = arr + } + } + } + + // items + additionalItems + switch items := s.Items.(type) { + case *Schema: + for i, item := range v { + if err := validate(items, "items", item, strconv.Itoa(i)); err != nil { + errors = append(errors, err) + } + } + result.unevalItems = nil + case []*Schema: + for i, item := range v { + if i < len(items) { + delete(result.unevalItems, i) + if err := validate(items[i], "items/"+strconv.Itoa(i), item, strconv.Itoa(i)); err != nil { + errors = append(errors, err) + } + } else if sch, ok := s.AdditionalItems.(*Schema); ok { + delete(result.unevalItems, i) + if err := validate(sch, "additionalItems", item, strconv.Itoa(i)); err != nil { + errors = append(errors, err) + } + } else { + break + } + } + if additionalItems, ok := s.AdditionalItems.(bool); ok { + if additionalItems { + result.unevalItems = nil + } else if len(v) > len(items) { + errors = append(errors, validationError("additionalItems", "only %d items are allowed, but found %d items", len(items), len(v))) + } + } + } + + // prefixItems + items + for i, item := range v { + if i < len(s.PrefixItems) { + delete(result.unevalItems, i) + if err := validate(s.PrefixItems[i], "prefixItems/"+strconv.Itoa(i), item, strconv.Itoa(i)); err != nil { + errors = append(errors, err) + } + } else if s.Items2020 != nil { + delete(result.unevalItems, i) + if err := validate(s.Items2020, "items", item, strconv.Itoa(i)); err != nil { + errors = append(errors, err) + } + } else { + break + } + } + + // contains + minContains + maxContains + if s.Contains != nil && (s.MinContains != -1 || s.MaxContains != -1) { + matched := 0 + var causes []error + for i, item := range v { + if err := validate(s.Contains, "contains", item, strconv.Itoa(i)); err != nil { + causes = append(causes, err) + } else { + matched++ + if s.ContainsEval { + delete(result.unevalItems, i) + } + } + } + if s.MinContains != -1 && matched < s.MinContains { + errors = append(errors, validationError("minContains", "valid must be >= %d, but got %d", s.MinContains, matched).add(causes...)) + } + if s.MaxContains != -1 && matched > s.MaxContains { + errors = append(errors, validationError("maxContains", "valid must be <= %d, but got %d", s.MaxContains, matched)) + } + } + + case string: + // minLength + maxLength + if s.MinLength != -1 || s.MaxLength != -1 { + length := utf8.RuneCount([]byte(v)) + if s.MinLength != -1 && length < s.MinLength { + errors = append(errors, validationError("minLength", "length must be >= %d, but got %d", s.MinLength, length)) + } + if s.MaxLength != -1 && length > s.MaxLength { + errors = append(errors, validationError("maxLength", "length must be <= %d, but got %d", s.MaxLength, length)) + } + } + + if s.Pattern != nil && !s.Pattern.MatchString(v) { + errors = append(errors, validationError("pattern", "does not match pattern %s", quote(s.Pattern.String()))) + } + + // contentEncoding + contentMediaType + if s.decoder != nil || s.mediaType != nil { + decoded := s.ContentEncoding == "" + var content []byte + if s.decoder != nil { + b, err := s.decoder(v) + if err != nil { + errors = append(errors, validationError("contentEncoding", "value is not %s encoded", s.ContentEncoding)) + } else { + content, decoded = b, true + } + } + if decoded && s.mediaType != nil { + if s.decoder == nil { + content = []byte(v) + } + if err := s.mediaType(content); err != nil { + errors = append(errors, validationError("contentMediaType", "value is not of mediatype %s", quote(s.ContentMediaType))) + } + } + if decoded && s.ContentSchema != nil { + contentJSON, err := unmarshal(bytes.NewReader(content)) + if err != nil { + errors = append(errors, validationError("contentSchema", "value is not valid json")) + } else { + err := validate(s.ContentSchema, "contentSchema", contentJSON, "") + if err != nil { + errors = append(errors, err) + } + } + } + } + + case json.Number, float32, float64, int, int8, int32, int64, uint, uint8, uint32, uint64: + // lazy convert to *big.Rat to avoid allocation + var numVal *big.Rat + num := func() *big.Rat { + if numVal == nil { + numVal, _ = new(big.Rat).SetString(fmt.Sprint(v)) + } + return numVal + } + f64 := func(r *big.Rat) float64 { + f, _ := r.Float64() + return f + } + if s.Minimum != nil && num().Cmp(s.Minimum) < 0 { + errors = append(errors, validationError("minimum", "must be >= %v but found %v", f64(s.Minimum), v)) + } + if s.ExclusiveMinimum != nil && num().Cmp(s.ExclusiveMinimum) <= 0 { + errors = append(errors, validationError("exclusiveMinimum", "must be > %v but found %v", f64(s.ExclusiveMinimum), v)) + } + if s.Maximum != nil && num().Cmp(s.Maximum) > 0 { + errors = append(errors, validationError("maximum", "must be <= %v but found %v", f64(s.Maximum), v)) + } + if s.ExclusiveMaximum != nil && num().Cmp(s.ExclusiveMaximum) >= 0 { + errors = append(errors, validationError("exclusiveMaximum", "must be < %v but found %v", f64(s.ExclusiveMaximum), v)) + } + if s.MultipleOf != nil { + if q := new(big.Rat).Quo(num(), s.MultipleOf); !q.IsInt() { + errors = append(errors, validationError("multipleOf", "%v not multipleOf %v", v, f64(s.MultipleOf))) + } + } + } + + // $ref + $recursiveRef + $dynamicRef + validateRef := func(sch *Schema, refPath string) error { + if sch != nil { + if err := validateInplace(sch, refPath); err != nil { + var url = sch.Location + if s.url() == sch.url() { + url = sch.loc() + } + return validationError(refPath, "doesn't validate with %s", quote(url)).causes(err) + } + } + return nil + } + if err := validateRef(s.Ref, "$ref"); err != nil { + errors = append(errors, err) + } + if s.RecursiveRef != nil { + sch := s.RecursiveRef + if sch.RecursiveAnchor { + // recursiveRef based on scope + for _, e := range scope { + if e.schema.RecursiveAnchor { + sch = e.schema + break + } + } + } + if err := validateRef(sch, "$recursiveRef"); err != nil { + errors = append(errors, err) + } + } + if s.DynamicRef != nil { + sch := s.DynamicRef + if s.dynamicRefAnchor != "" && sch.DynamicAnchor == s.dynamicRefAnchor { + // dynamicRef based on scope + for i := len(scope) - 1; i >= 0; i-- { + sr := scope[i] + if sr.discard { + break + } + for _, da := range sr.schema.dynamicAnchors { + if da.DynamicAnchor == s.DynamicRef.DynamicAnchor && da != s.DynamicRef { + sch = da + break + } + } + } + } + if err := validateRef(sch, "$dynamicRef"); err != nil { + errors = append(errors, err) + } + } + + if s.Not != nil && validateInplace(s.Not, "not") == nil { + errors = append(errors, validationError("not", "not failed")) + } + + for i, sch := range s.AllOf { + schPath := "allOf/" + strconv.Itoa(i) + if err := validateInplace(sch, schPath); err != nil { + errors = append(errors, validationError(schPath, "allOf failed").add(err)) + } + } + + if len(s.AnyOf) > 0 { + matched := false + var causes []error + for i, sch := range s.AnyOf { + if err := validateInplace(sch, "anyOf/"+strconv.Itoa(i)); err == nil { + matched = true + } else { + causes = append(causes, err) + } + } + if !matched { + errors = append(errors, validationError("anyOf", "anyOf failed").add(causes...)) + } + } + + if len(s.OneOf) > 0 { + matched := -1 + var causes []error + for i, sch := range s.OneOf { + if err := validateInplace(sch, "oneOf/"+strconv.Itoa(i)); err == nil { + if matched == -1 { + matched = i + } else { + errors = append(errors, validationError("oneOf", "valid against schemas at indexes %d and %d", matched, i)) + break + } + } else { + causes = append(causes, err) + } + } + if matched == -1 { + errors = append(errors, validationError("oneOf", "oneOf failed").add(causes...)) + } + } + + // if + then + else + if s.If != nil { + err := validateInplace(s.If, "if") + // "if" leaves dynamic scope + scope[len(scope)-1].discard = true + if err == nil { + if s.Then != nil { + if err := validateInplace(s.Then, "then"); err != nil { + errors = append(errors, validationError("then", "if-then failed").add(err)) + } + } + } else { + if s.Else != nil { + if err := validateInplace(s.Else, "else"); err != nil { + errors = append(errors, validationError("else", "if-else failed").add(err)) + } + } + } + // restore dynamic scope + scope[len(scope)-1].discard = false + } + + for _, ext := range s.Extensions { + if err := ext.Validate(ValidationContext{result, validate, validateInplace, validationError}, v); err != nil { + errors = append(errors, err) + } + } + + // unevaluatedProperties + unevaluatedItems + switch v := v.(type) { + case map[string]interface{}: + if s.UnevaluatedProperties != nil { + for pname := range result.unevalProps { + if pvalue, ok := v[pname]; ok { + if err := validate(s.UnevaluatedProperties, "unevaluatedProperties", pvalue, escape(pname)); err != nil { + errors = append(errors, err) + } + } + } + result.unevalProps = nil + } + case []interface{}: + if s.UnevaluatedItems != nil { + for i := range result.unevalItems { + if err := validate(s.UnevaluatedItems, "unevaluatedItems", v[i], strconv.Itoa(i)); err != nil { + errors = append(errors, err) + } + } + result.unevalItems = nil + } + } + + switch len(errors) { + case 0: + return result, nil + case 1: + return result, errors[0] + default: + return result, validationError("", "").add(errors...) // empty message, used just for wrapping + } +} + +type validationResult struct { + unevalProps map[string]struct{} + unevalItems map[int]struct{} +} + +func (vr validationResult) unevalPnames() string { + pnames := make([]string, 0, len(vr.unevalProps)) + for pname := range vr.unevalProps { + pnames = append(pnames, quote(pname)) + } + return strings.Join(pnames, ", ") +} + +// jsonType returns the json type of given value v. +// +// It panics if the given value is not valid json value +func jsonType(v interface{}) string { + switch v.(type) { + case nil: + return "null" + case bool: + return "boolean" + case json.Number, float32, float64, int, int8, int32, int64, uint, uint8, uint32, uint64: + return "number" + case string: + return "string" + case []interface{}: + return "array" + case map[string]interface{}: + return "object" + } + panic(InvalidJSONTypeError(fmt.Sprintf("%T", v))) +} + +// equals tells if given two json values are equal or not. +func equals(v1, v2 interface{}) bool { + v1Type := jsonType(v1) + if v1Type != jsonType(v2) { + return false + } + switch v1Type { + case "array": + arr1, arr2 := v1.([]interface{}), v2.([]interface{}) + if len(arr1) != len(arr2) { + return false + } + for i := range arr1 { + if !equals(arr1[i], arr2[i]) { + return false + } + } + return true + case "object": + obj1, obj2 := v1.(map[string]interface{}), v2.(map[string]interface{}) + if len(obj1) != len(obj2) { + return false + } + for k, v1 := range obj1 { + if v2, ok := obj2[k]; ok { + if !equals(v1, v2) { + return false + } + } else { + return false + } + } + return true + case "number": + num1, _ := new(big.Rat).SetString(fmt.Sprint(v1)) + num2, _ := new(big.Rat).SetString(fmt.Sprint(v2)) + return num1.Cmp(num2) == 0 + default: + return v1 == v2 + } +} + +func hash(v interface{}, h *maphash.Hash) { + switch v := v.(type) { + case nil: + h.WriteByte(0) + case bool: + h.WriteByte(1) + if v { + h.WriteByte(1) + } else { + h.WriteByte(0) + } + case json.Number, float32, float64, int, int8, int32, int64, uint, uint8, uint32, uint64: + h.WriteByte(2) + num, _ := new(big.Rat).SetString(fmt.Sprint(v)) + h.Write(num.Num().Bytes()) + h.Write(num.Denom().Bytes()) + case string: + h.WriteByte(3) + h.WriteString(v) + case []interface{}: + h.WriteByte(4) + for _, item := range v { + hash(item, h) + } + case map[string]interface{}: + h.WriteByte(5) + props := make([]string, 0, len(v)) + for prop := range v { + props = append(props, prop) + } + sort.Slice(props, func(i, j int) bool { + return props[i] < props[j] + }) + for _, prop := range props { + hash(prop, h) + hash(v[prop], h) + } + default: + panic(InvalidJSONTypeError(fmt.Sprintf("%T", v))) + } +} + +// escape converts given token to valid json-pointer token +func escape(token string) string { + token = strings.ReplaceAll(token, "~", "~0") + token = strings.ReplaceAll(token, "/", "~1") + return url.PathEscape(token) +} diff --git a/vendor/github.com/sivchari/nosnakecase/.gitignore b/vendor/github.com/sivchari/nosnakecase/.gitignore deleted file mode 100644 index 66fd13c903..0000000000 --- a/vendor/github.com/sivchari/nosnakecase/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -# 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/ diff --git a/vendor/github.com/sivchari/nosnakecase/.golangci.yml b/vendor/github.com/sivchari/nosnakecase/.golangci.yml deleted file mode 100644 index 31e05c4ee1..0000000000 --- a/vendor/github.com/sivchari/nosnakecase/.golangci.yml +++ /dev/null @@ -1,40 +0,0 @@ -run: - timeout: 5m - skip-files: [] - go: '1.17' - -linters-settings: - govet: - enable-all: true - disable: - - fieldalignment - gocyclo: - min-complexity: 18 - misspell: - locale: US - godox: - keywords: - - FIXME - gofumpt: - extra-rules: true - -linters: - disable-all: true - enable: - - govet - - revive - - goimports - - staticcheck - - gosimple - - unused - - godox - - gofumpt - - misspell - - gocyclo - -issues: - exclude-use-default: true - max-per-linter: 0 - max-same-issues: 0 - exclude: [] - diff --git a/vendor/github.com/sivchari/nosnakecase/README.md b/vendor/github.com/sivchari/nosnakecase/README.md deleted file mode 100644 index 69bb660462..0000000000 --- a/vendor/github.com/sivchari/nosnakecase/README.md +++ /dev/null @@ -1,224 +0,0 @@ -# nosnakecase -nosnakecase is a linter that detects snake case of variable naming and function name. - -## Instruction - -```sh -go install github.com/sivchari/nosnakecase/cmd/nosnakecase@latest -``` - -## Usage - -```go -package sandbox - -// global variable name with underscore. -var v_v = 0 // want "v_v is used under score. You should use mixedCap or MixedCap." - -// global constant name with underscore. -const c_c = 0 // want "c_c is used under score. You should use mixedCap or MixedCap." - -// struct name with underscore. -type S_a struct { // want "S_a is used under score. You should use mixedCap or MixedCap." - fi int -} - -// non-exported struct field name with underscore. -type Sa struct { - fi_a int // // want "fi_a is used under score. You should use mixedCap or MixedCap." -} - -// function as struct field, with parameter name with underscore. -type Sb struct { - fib func(p_a int) // want "p_a is used under score. You should use mixedCap or MixedCap." -} - -// exported struct field with underscore. -type Sc struct { - Fi_A int // want "Fi_A is used under score. You should use mixedCap or MixedCap." -} - -// function as struct field, with return name with underscore. -type Sd struct { - fib func(p int) (r_a int) // want "r_a is used under score. You should use mixedCap or MixedCap." -} - -// interface name with underscore. -type I_a interface { // want "I_a is used under score. You should use mixedCap or MixedCap." - fn(p int) -} - -// interface with parameter name with underscore. -type Ia interface { - fn(p_a int) // want "p_a is used under score. You should use mixedCap or MixedCap." -} - -// interface with parameter name with underscore. -type Ib interface { - Fn(p_a int) // want "p_a is used under score. You should use mixedCap or MixedCap." -} - -// function as struct field, with return name with underscore. -type Ic interface { - Fn_a() // want "Fn_a is used under score. You should use mixedCap or MixedCap." -} - -// interface with return name with underscore. -type Id interface { - Fn() (r_a int) // want "r_a is used under score. You should use mixedCap or MixedCap." -} - -// function name with underscore. -func f_a() {} // want "f_a is used under score. You should use mixedCap or MixedCap." - -// function's parameter name with underscore. -func fb(p_a int) {} // want "p_a is used under score. You should use mixedCap or MixedCap." - -// named return with underscore. -func fc() (r_b int) { // want "r_b is used under score. You should use mixedCap or MixedCap." - return 0 -} - -// local variable (short declaration) with underscore. -func fd(p int) int { - v_b := p * 2 // want "v_b is used under score. You should use mixedCap or MixedCap." - - return v_b // want "v_b is used under score. You should use mixedCap or MixedCap." -} - -// local constant with underscore. -func fe(p int) int { - const v_b = 2 // want "v_b is used under score. You should use mixedCap or MixedCap." - - return v_b * p // want "v_b is used under score. You should use mixedCap or MixedCap." -} - -// local variable with underscore. -func ff(p int) int { - var v_b = 2 // want "v_b is used under score. You should use mixedCap or MixedCap." - - return v_b * p // want "v_b is used under score. You should use mixedCap or MixedCap." -} - -// inner function, parameter name with underscore. -func fg() { - fgl := func(p_a int) {} // want "p_a is used under score. You should use mixedCap or MixedCap." - fgl(1) -} - -type Foo struct{} - -// method name with underscore. -func (f Foo) f_a() {} // want "f_a is used under score. You should use mixedCap or MixedCap." - -// method's parameter name with underscore. -func (f Foo) fb(p_a int) {} // want "p_a is used under score. You should use mixedCap or MixedCap." - -// named return with underscore. -func (f Foo) fc() (r_b int) { return 0 } // want "r_b is used under score. You should use mixedCap or MixedCap." - -// local variable (short declaration) with underscore. -func (f Foo) fd(p int) int { - v_b := p * 2 // want "v_b is used under score. You should use mixedCap or MixedCap." - - return v_b // want "v_b is used under score. You should use mixedCap or MixedCap." -} - -// local constant with underscore. -func (f Foo) fe(p int) int { - const v_b = 2 // want "v_b is used under score. You should use mixedCap or MixedCap." - - return v_b * p // want "v_b is used under score. You should use mixedCap or MixedCap." -} - -// local variable with underscore. -func (f Foo) ff(p int) int { - var v_b = 2 // want "v_b is used under score. You should use mixedCap or MixedCap." - - return v_b * p // want "v_b is used under score. You should use mixedCap or MixedCap." -} - -func fna(a, p_a int) {} // want "p_a is used under score. You should use mixedCap or MixedCap." - -func fna1(a string, p_a int) {} // want "p_a is used under score. You should use mixedCap or MixedCap." - -func fnb(a, b, p_a int) {} // want "p_a is used under score. You should use mixedCap or MixedCap." - -func fnb1(a, b string, p_a int) {} // want "p_a is used under score. You should use mixedCap or MixedCap." - -func fnd( - p_a int, // want "p_a is used under score. You should use mixedCap or MixedCap." - p_b int, // want "p_b is used under score. You should use mixedCap or MixedCap." - p_c int, // want "p_c is used under score. You should use mixedCap or MixedCap." -) { -} -``` - -```console -go vet -vettool=(which nosnakecase) ./... - -# command-line-arguments -# a -./a.go:4:5: v_v is used under score. You should use mixedCap or MixedCap. -./a.go:7:7: c_c is used under score. You should use mixedCap or MixedCap. -./a.go:10:6: S_a is used under score. You should use mixedCap or MixedCap. -./a.go:16:2: fi_a is used under score. You should use mixedCap or MixedCap. -./a.go:21:11: p_a is used under score. You should use mixedCap or MixedCap. -./a.go:26:2: Fi_A is used under score. You should use mixedCap or MixedCap. -./a.go:31:19: r_a is used under score. You should use mixedCap or MixedCap. -./a.go:35:6: I_a is used under score. You should use mixedCap or MixedCap. -./a.go:41:5: p_a is used under score. You should use mixedCap or MixedCap. -./a.go:46:5: p_a is used under score. You should use mixedCap or MixedCap. -./a.go:51:2: Fn_a is used under score. You should use mixedCap or MixedCap. -./a.go:56:8: r_a is used under score. You should use mixedCap or MixedCap. -./a.go:60:6: f_a is used under score. You should use mixedCap or MixedCap. -./a.go:63:9: p_a is used under score. You should use mixedCap or MixedCap. -./a.go:66:12: r_b is used under score. You should use mixedCap or MixedCap. -./a.go:72:2: v_b is used under score. You should use mixedCap or MixedCap. -./a.go:74:9: v_b is used under score. You should use mixedCap or MixedCap. -./a.go:79:8: v_b is used under score. You should use mixedCap or MixedCap. -./a.go:81:9: v_b is used under score. You should use mixedCap or MixedCap. -./a.go:86:6: v_b is used under score. You should use mixedCap or MixedCap. -./a.go:88:9: v_b is used under score. You should use mixedCap or MixedCap. -./a.go:93:14: p_a is used under score. You should use mixedCap or MixedCap. -./a.go:100:14: f_a is used under score. You should use mixedCap or MixedCap. -./a.go:103:17: p_a is used under score. You should use mixedCap or MixedCap. -./a.go:106:20: r_b is used under score. You should use mixedCap or MixedCap. -./a.go:110:2: v_b is used under score. You should use mixedCap or MixedCap. -./a.go:112:9: v_b is used under score. You should use mixedCap or MixedCap. -./a.go:117:8: v_b is used under score. You should use mixedCap or MixedCap. -./a.go:119:9: v_b is used under score. You should use mixedCap or MixedCap. -./a.go:124:6: v_b is used under score. You should use mixedCap or MixedCap. -./a.go:126:9: v_b is used under score. You should use mixedCap or MixedCap. -./a.go:129:13: p_a is used under score. You should use mixedCap or MixedCap. -./a.go:131:21: p_a is used under score. You should use mixedCap or MixedCap. -./a.go:133:16: p_a is used under score. You should use mixedCap or MixedCap. -./a.go:135:24: p_a is used under score. You should use mixedCap or MixedCap. -./a.go:138:2: p_a is used under score. You should use mixedCap or MixedCap. -./a.go:139:2: p_b is used under score. You should use mixedCap or MixedCap. -./a.go:140:2: p_c is used under score. You should use mixedCap or MixedCap. -``` - -## CI - -### CircleCI - -```yaml -- run: - name: install nosnakecase - command: go install github.com/sivchari/nosnakecase/cmd/nosnakecase@latest - -- run: - name: run nosnakecase - command: go vet -vettool=`which nosnakecase` ./... -``` - -### GitHub Actions - -```yaml -- name: install nosnakecase - run: go install github.com/sivchari/nosnakecase/cmd/nosnakecase@latest - -- name: run nosnakecase - run: go vet -vettool=`which nosnakecase` ./... -``` diff --git a/vendor/github.com/sivchari/nosnakecase/nosnakecase.go b/vendor/github.com/sivchari/nosnakecase/nosnakecase.go deleted file mode 100644 index 88cf70e3f0..0000000000 --- a/vendor/github.com/sivchari/nosnakecase/nosnakecase.go +++ /dev/null @@ -1,63 +0,0 @@ -package nosnakecase - -import ( - "go/ast" - "go/token" - "strings" - - "golang.org/x/tools/go/analysis" - "golang.org/x/tools/go/analysis/passes/inspect" - "golang.org/x/tools/go/ast/inspector" -) - -const doc = "nosnakecase is a linter that detects snake case of variable naming and function name." - -// Analyzer is a nosnakecase linter. -var Analyzer = &analysis.Analyzer{ - Name: "nosnakecase", - Doc: doc, - Run: run, - Requires: []*analysis.Analyzer{ - inspect.Analyzer, - }, -} - -func run(pass *analysis.Pass) (interface{}, error) { - result := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) - - nodeFilter := []ast.Node{ - (*ast.Ident)(nil), - } - - result.Preorder(nodeFilter, func(n ast.Node) { - switch n := n.(type) { - case *ast.Ident: - report(pass, n.Pos(), n.Name) - } - }) - - return nil, nil -} - -func report(pass *analysis.Pass, pos token.Pos, name string) { - // skip import _ "xxx" - if name == "_" { - return - } - - // skip package xxx_test - if strings.Contains(name, "_test") { - return - } - - // If prefix is Test or Benchmark, Fuzz, skip - // FYI https://go.dev/blog/examples - if strings.HasPrefix(name, "Test") || strings.HasPrefix(name, "Benchmark") || strings.HasPrefix(name, "Fuzz") { - return - } - - if strings.Contains(name, "_") { - pass.Reportf(pos, "%s contains underscore. You should use mixedCap or MixedCap.", name) - return - } -} diff --git a/vendor/github.com/tomarrell/wrapcheck/v2/wrapcheck/wrapcheck.go b/vendor/github.com/tomarrell/wrapcheck/v2/wrapcheck/wrapcheck.go index 6da17bd867..79e7bba863 100644 --- a/vendor/github.com/tomarrell/wrapcheck/v2/wrapcheck/wrapcheck.go +++ b/vendor/github.com/tomarrell/wrapcheck/v2/wrapcheck/wrapcheck.go @@ -121,7 +121,20 @@ func run(cfg WrapcheckConfig) func(*analysis.Pass) (interface{}, error) { } for _, file := range pass.Files { + // Keep track of parents so that can can traverse upwards to check for + // FuncDecls and FuncLits. + var parents []ast.Node + ast.Inspect(file, func(n ast.Node) bool { + if n == nil { + // Pop, since we're done with this node and its children. + parents = parents[:len(parents)-1] + } else { + // Push this node on the stack, since its children will be visited + // next. + parents = append(parents, n) + } + ret, ok := n.(*ast.ReturnStmt) if !ok { return true @@ -137,6 +150,17 @@ func run(cfg WrapcheckConfig) func(*analysis.Pass) (interface{}, error) { // to handle it by checking the return params of the function. retFn, ok := expr.(*ast.CallExpr) if ok { + // If you go up, and the parent is a FuncLit, then don't report an + // error as you are in an anonymous function. If you are inside a + // FuncDecl, then continue as normal. + for i := len(parents) - 1; i > 0; i-- { + if _, ok := parents[i].(*ast.FuncLit); ok { + return true + } else if _, ok := parents[i].(*ast.FuncDecl); ok { + break + } + } + // If the return type of the function is a single error. This will not // match an error within multiple return values, for that, the below // tuple check is required. diff --git a/vendor/go-simpler.org/musttag/README.md b/vendor/go-simpler.org/musttag/README.md index 54c2744ccf..3f3a253302 100644 --- a/vendor/go-simpler.org/musttag/README.md +++ b/vendor/go-simpler.org/musttag/README.md @@ -64,7 +64,7 @@ Alternatively, you can download a prebuilt binary from the [Releases][9] page to 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. +When using `musttag` standalone, pass the options as flags. ### Custom packages diff --git a/vendor/go-simpler.org/musttag/musttag.go b/vendor/go-simpler.org/musttag/musttag.go index b7e41f3d36..d7911770fb 100644 --- a/vendor/go-simpler.org/musttag/musttag.go +++ b/vendor/go-simpler.org/musttag/musttag.go @@ -150,16 +150,19 @@ type checker 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.Pointer: + return c.parseStruct(typ.Elem()) + + case *types.Array: + return c.parseStruct(typ.Elem()) + + case *types.Slice: + return c.parseStruct(typ.Elem()) + + case *types.Map: + return c.parseStruct(typ.Elem()) + case *types.Named: // a struct of the named type. pkg := typ.Obj().Pkg() if pkg == nil { diff --git a/vendor/go-simpler.org/sloglint/Makefile b/vendor/go-simpler.org/sloglint/Makefile new file mode 100644 index 0000000000..6165b16f47 --- /dev/null +++ b/vendor/go-simpler.org/sloglint/Makefile @@ -0,0 +1,28 @@ +.POSIX: +.SUFFIXES: + +all: test lint + +test: + go test -race -shuffle=on -cover ./... + +test/cover: + go test -race -shuffle=on -coverprofile=coverage.out ./... + go tool cover -html=coverage.out + +lint: + golangci-lint run + +tidy: + go mod tidy + +generate: + go generate ./... + +# run `make pre-commit` once to install the hook. +pre-commit: .git/hooks/pre-commit test lint tidy generate + git diff --exit-code + +.git/hooks/pre-commit: + echo "make pre-commit" > .git/hooks/pre-commit + chmod +x .git/hooks/pre-commit diff --git a/vendor/go-simpler.org/sloglint/README.md b/vendor/go-simpler.org/sloglint/README.md index fc4d9debb9..7f6455c1c7 100644 --- a/vendor/go-simpler.org/sloglint/README.md +++ b/vendor/go-simpler.org/sloglint/README.md @@ -17,6 +17,7 @@ With `sloglint` you can enforce various rules for `log/slog` based on your prefe * Enforce not mixing key-value pairs and attributes (default) * Enforce using either key-value pairs only or attributes only (optional) +* Enforce not using global loggers (optional) * Enforce using methods that accept a context (optional) * Enforce using static log messages (optional) * Enforce using constants instead of raw keys (optional) @@ -70,6 +71,17 @@ In contrast, the `attr-only` option causes `sloglint` to report any use of key-v slog.Info("a user has logged in", "user_id", 42) // sloglint: key-value pairs should not be used ``` +### No global + +Some projects prefer to pass loggers as explicit dependencies. +The `no-global` option causes `sloglint` to report the usage of global loggers. + +```go +slog.Info("a user has logged in", "user_id", 42) // sloglint: global logger should not be used +``` + +Possible values are `all` (report all global loggers) and `default` (report only the default `slog` logger). + ### Context only Some `slog.Handler` implementations make use of the given `context.Context` (e.g. to access context values). diff --git a/vendor/go-simpler.org/sloglint/sloglint.go b/vendor/go-simpler.org/sloglint/sloglint.go index b7f72ce485..35cac14d13 100644 --- a/vendor/go-simpler.org/sloglint/sloglint.go +++ b/vendor/go-simpler.org/sloglint/sloglint.go @@ -9,6 +9,7 @@ import ( "go/token" "go/types" "strconv" + "strings" "github.com/ettle/strcase" "golang.org/x/tools/go/analysis" @@ -22,6 +23,7 @@ type Options struct { 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). + NoGlobal string // Enforce not using global loggers ("all" or "default"). 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. @@ -43,11 +45,19 @@ func New(opts *Options) *analysis.Analyzer { if opts.KVOnly && opts.AttrOnly { return nil, fmt.Errorf("sloglint: Options.KVOnly and Options.AttrOnly: %w", errIncompatible) } + + switch opts.NoGlobal { + case "", "all", "default": + default: + return nil, fmt.Errorf("sloglint: Options.NoGlobal=%s: %w", opts.NoGlobal, errInvalidValue) + } + 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 }, @@ -60,30 +70,34 @@ var ( ) func flags(opts *Options) flag.FlagSet { - fs := flag.NewFlagSet("sloglint", flag.ContinueOnError) + fset := flag.NewFlagSet("sloglint", flag.ContinueOnError) boolVar := func(value *bool, name, usage string) { - fs.Func(name, usage, func(s string) error { + fset.Func(name, usage, func(s string) error { v, err := strconv.ParseBool(s) *value = v return err }) } + strVar := func(value *string, name, usage string) { + fset.Func(name, usage, func(s string) error { + *value = s + return nil + }) + } + 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)") + strVar(&opts.NoGlobal, "no-global", "enforce not using global loggers (all|default)") 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") + strVar(&opts.KeyNamingCase, "key-naming-case", "enforce a single key naming convention (snake|kebab|camel|pascal)") 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 + return *fset } var slogFuncs = map[string]int{ // funcName:argsPos @@ -139,17 +153,30 @@ func run(pass *analysis.Pass, opts *Options) { return } - argsPos, ok := slogFuncs[fn.FullName()] + name := fn.FullName() + argsPos, ok := slogFuncs[name] if !ok { return } + switch opts.NoGlobal { + case "all": + if strings.HasPrefix(name, "log/slog.") || globalLoggerUsed(pass.TypesInfo, call.Fun) { + pass.Reportf(call.Pos(), "global logger should not be used") + } + case "default": + if strings.HasPrefix(name, "log/slog.") { + pass.Reportf(call.Pos(), "default logger should not be used") + } + } + 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") } @@ -189,6 +216,7 @@ func run(pass *analysis.Pass, opts *Options) { if opts.NoRawKeys && rawKeysUsed(pass.TypesInfo, keys, attrs) { pass.Reportf(call.Pos(), "raw keys should not be used") } + if opts.ArgsOnSepLines && argsOnSameLine(pass.Fset, call, keys, attrs) { pass.Reportf(call.Pos(), "arguments should be put on separate lines") } @@ -206,6 +234,19 @@ func run(pass *analysis.Pass, opts *Options) { }) } +func globalLoggerUsed(info *types.Info, expr ast.Expr) bool { + selector, ok := expr.(*ast.SelectorExpr) + if !ok { + return false + } + ident, ok := selector.X.(*ast.Ident) + if !ok { + return false + } + obj := info.ObjectOf(ident) + return obj.Parent() == obj.Pkg().Scope() +} + func staticMsg(expr ast.Expr) bool { switch msg := expr.(type) { case *ast.BasicLit: // e.g. slog.Info("msg") diff --git a/vendor/github.com/mbilski/exhaustivestruct/LICENSE b/vendor/go.uber.org/automaxprocs/LICENSE similarity index 87% rename from vendor/github.com/mbilski/exhaustivestruct/LICENSE rename to vendor/go.uber.org/automaxprocs/LICENSE index 893eb73b9f..20dcf51d96 100644 --- a/vendor/github.com/mbilski/exhaustivestruct/LICENSE +++ b/vendor/go.uber.org/automaxprocs/LICENSE @@ -1,6 +1,4 @@ -MIT License - -Copyright (c) 2020 Mateusz Bilski +Copyright (c) 2017 Uber Technologies, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -9,13 +7,13 @@ 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 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. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroup.go b/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroup.go new file mode 100644 index 0000000000..fe4ecf561e --- /dev/null +++ b/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroup.go @@ -0,0 +1,79 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// 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. + +//go:build linux +// +build linux + +package cgroups + +import ( + "bufio" + "io" + "os" + "path/filepath" + "strconv" +) + +// CGroup represents the data structure for a Linux control group. +type CGroup struct { + path string +} + +// NewCGroup returns a new *CGroup from a given path. +func NewCGroup(path string) *CGroup { + return &CGroup{path: path} +} + +// Path returns the path of the CGroup*. +func (cg *CGroup) Path() string { + return cg.path +} + +// ParamPath returns the path of the given cgroup param under itself. +func (cg *CGroup) ParamPath(param string) string { + return filepath.Join(cg.path, param) +} + +// readFirstLine reads the first line from a cgroup param file. +func (cg *CGroup) readFirstLine(param string) (string, error) { + paramFile, err := os.Open(cg.ParamPath(param)) + if err != nil { + return "", err + } + defer paramFile.Close() + + scanner := bufio.NewScanner(paramFile) + if scanner.Scan() { + return scanner.Text(), nil + } + if err := scanner.Err(); err != nil { + return "", err + } + return "", io.ErrUnexpectedEOF +} + +// readInt parses the first line from a cgroup param file as int. +func (cg *CGroup) readInt(param string) (int, error) { + text, err := cg.readFirstLine(param) + if err != nil { + return 0, err + } + return strconv.Atoi(text) +} diff --git a/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroups.go b/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroups.go new file mode 100644 index 0000000000..e89f543602 --- /dev/null +++ b/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroups.go @@ -0,0 +1,118 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// 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. + +//go:build linux +// +build linux + +package cgroups + +const ( + // _cgroupFSType is the Linux CGroup file system type used in + // `/proc/$PID/mountinfo`. + _cgroupFSType = "cgroup" + // _cgroupSubsysCPU is the CPU CGroup subsystem. + _cgroupSubsysCPU = "cpu" + // _cgroupSubsysCPUAcct is the CPU accounting CGroup subsystem. + _cgroupSubsysCPUAcct = "cpuacct" + // _cgroupSubsysCPUSet is the CPUSet CGroup subsystem. + _cgroupSubsysCPUSet = "cpuset" + // _cgroupSubsysMemory is the Memory CGroup subsystem. + _cgroupSubsysMemory = "memory" + + // _cgroupCPUCFSQuotaUsParam is the file name for the CGroup CFS quota + // parameter. + _cgroupCPUCFSQuotaUsParam = "cpu.cfs_quota_us" + // _cgroupCPUCFSPeriodUsParam is the file name for the CGroup CFS period + // parameter. + _cgroupCPUCFSPeriodUsParam = "cpu.cfs_period_us" +) + +const ( + _procPathCGroup = "/proc/self/cgroup" + _procPathMountInfo = "/proc/self/mountinfo" +) + +// CGroups is a map that associates each CGroup with its subsystem name. +type CGroups map[string]*CGroup + +// NewCGroups returns a new *CGroups from given `mountinfo` and `cgroup` files +// under for some process under `/proc` file system (see also proc(5) for more +// information). +func NewCGroups(procPathMountInfo, procPathCGroup string) (CGroups, error) { + cgroupSubsystems, err := parseCGroupSubsystems(procPathCGroup) + if err != nil { + return nil, err + } + + cgroups := make(CGroups) + newMountPoint := func(mp *MountPoint) error { + if mp.FSType != _cgroupFSType { + return nil + } + + for _, opt := range mp.SuperOptions { + subsys, exists := cgroupSubsystems[opt] + if !exists { + continue + } + + cgroupPath, err := mp.Translate(subsys.Name) + if err != nil { + return err + } + cgroups[opt] = NewCGroup(cgroupPath) + } + + return nil + } + + if err := parseMountInfo(procPathMountInfo, newMountPoint); err != nil { + return nil, err + } + return cgroups, nil +} + +// NewCGroupsForCurrentProcess returns a new *CGroups instance for the current +// process. +func NewCGroupsForCurrentProcess() (CGroups, error) { + return NewCGroups(_procPathMountInfo, _procPathCGroup) +} + +// CPUQuota returns the CPU quota applied with the CPU cgroup controller. +// It is a result of `cpu.cfs_quota_us / cpu.cfs_period_us`. If the value of +// `cpu.cfs_quota_us` was not set (-1), the method returns `(-1, nil)`. +func (cg CGroups) CPUQuota() (float64, bool, error) { + cpuCGroup, exists := cg[_cgroupSubsysCPU] + if !exists { + return -1, false, nil + } + + cfsQuotaUs, err := cpuCGroup.readInt(_cgroupCPUCFSQuotaUsParam) + if defined := cfsQuotaUs > 0; err != nil || !defined { + return -1, defined, err + } + + cfsPeriodUs, err := cpuCGroup.readInt(_cgroupCPUCFSPeriodUsParam) + if defined := cfsPeriodUs > 0; err != nil || !defined { + return -1, defined, err + } + + return float64(cfsQuotaUs) / float64(cfsPeriodUs), true, nil +} diff --git a/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroups2.go b/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroups2.go new file mode 100644 index 0000000000..78556062fe --- /dev/null +++ b/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroups2.go @@ -0,0 +1,176 @@ +// Copyright (c) 2022 Uber Technologies, Inc. +// +// 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. + +//go:build linux +// +build linux + +package cgroups + +import ( + "bufio" + "errors" + "fmt" + "io" + "os" + "path" + "strconv" + "strings" +) + +const ( + // _cgroupv2CPUMax is the file name for the CGroup-V2 CPU max and period + // parameter. + _cgroupv2CPUMax = "cpu.max" + // _cgroupFSType is the Linux CGroup-V2 file system type used in + // `/proc/$PID/mountinfo`. + _cgroupv2FSType = "cgroup2" + + _cgroupv2MountPoint = "/sys/fs/cgroup" + + _cgroupV2CPUMaxDefaultPeriod = 100000 + _cgroupV2CPUMaxQuotaMax = "max" +) + +const ( + _cgroupv2CPUMaxQuotaIndex = iota + _cgroupv2CPUMaxPeriodIndex +) + +// ErrNotV2 indicates that the system is not using cgroups2. +var ErrNotV2 = errors.New("not using cgroups2") + +// CGroups2 provides access to cgroups data for systems using cgroups2. +type CGroups2 struct { + mountPoint string + groupPath string + cpuMaxFile string +} + +// NewCGroups2ForCurrentProcess builds a CGroups2 for the current process. +// +// This returns ErrNotV2 if the system is not using cgroups2. +func NewCGroups2ForCurrentProcess() (*CGroups2, error) { + return newCGroups2From(_procPathMountInfo, _procPathCGroup) +} + +func newCGroups2From(mountInfoPath, procPathCGroup string) (*CGroups2, error) { + isV2, err := isCGroupV2(mountInfoPath) + if err != nil { + return nil, err + } + + if !isV2 { + return nil, ErrNotV2 + } + + subsystems, err := parseCGroupSubsystems(procPathCGroup) + if err != nil { + return nil, err + } + + // Find v2 subsystem by looking for the `0` id + var v2subsys *CGroupSubsys + for _, subsys := range subsystems { + if subsys.ID == 0 { + v2subsys = subsys + break + } + } + + if v2subsys == nil { + return nil, ErrNotV2 + } + + return &CGroups2{ + mountPoint: _cgroupv2MountPoint, + groupPath: v2subsys.Name, + cpuMaxFile: _cgroupv2CPUMax, + }, nil +} + +func isCGroupV2(procPathMountInfo string) (bool, error) { + var ( + isV2 bool + newMountPoint = func(mp *MountPoint) error { + isV2 = isV2 || (mp.FSType == _cgroupv2FSType && mp.MountPoint == _cgroupv2MountPoint) + return nil + } + ) + + if err := parseMountInfo(procPathMountInfo, newMountPoint); err != nil { + return false, err + } + + return isV2, nil +} + +// CPUQuota returns the CPU quota applied with the CPU cgroup2 controller. +// It is a result of reading cpu quota and period from cpu.max file. +// It will return `cpu.max / cpu.period`. If cpu.max is set to max, it returns +// (-1, false, nil) +func (cg *CGroups2) CPUQuota() (float64, bool, error) { + cpuMaxParams, err := os.Open(path.Join(cg.mountPoint, cg.groupPath, cg.cpuMaxFile)) + if err != nil { + if os.IsNotExist(err) { + return -1, false, nil + } + return -1, false, err + } + defer cpuMaxParams.Close() + + scanner := bufio.NewScanner(cpuMaxParams) + if scanner.Scan() { + fields := strings.Fields(scanner.Text()) + if len(fields) == 0 || len(fields) > 2 { + return -1, false, fmt.Errorf("invalid format") + } + + if fields[_cgroupv2CPUMaxQuotaIndex] == _cgroupV2CPUMaxQuotaMax { + return -1, false, nil + } + + max, err := strconv.Atoi(fields[_cgroupv2CPUMaxQuotaIndex]) + if err != nil { + return -1, false, err + } + + var period int + if len(fields) == 1 { + period = _cgroupV2CPUMaxDefaultPeriod + } else { + period, err = strconv.Atoi(fields[_cgroupv2CPUMaxPeriodIndex]) + if err != nil { + return -1, false, err + } + + if period == 0 { + return -1, false, errors.New("zero value for period is not allowed") + } + } + + return float64(max) / float64(period), true, nil + } + + if err := scanner.Err(); err != nil { + return -1, false, err + } + + return 0, false, io.ErrUnexpectedEOF +} diff --git a/vendor/go.uber.org/automaxprocs/internal/cgroups/doc.go b/vendor/go.uber.org/automaxprocs/internal/cgroups/doc.go new file mode 100644 index 0000000000..113555f63d --- /dev/null +++ b/vendor/go.uber.org/automaxprocs/internal/cgroups/doc.go @@ -0,0 +1,23 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// 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. + +// Package cgroups provides utilities to access Linux control group (CGroups) +// parameters (CPU quota, for example) for a given process. +package cgroups diff --git a/vendor/go.uber.org/automaxprocs/internal/cgroups/errors.go b/vendor/go.uber.org/automaxprocs/internal/cgroups/errors.go new file mode 100644 index 0000000000..94ac75a46e --- /dev/null +++ b/vendor/go.uber.org/automaxprocs/internal/cgroups/errors.go @@ -0,0 +1,52 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// 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. + +//go:build linux +// +build linux + +package cgroups + +import "fmt" + +type cgroupSubsysFormatInvalidError struct { + line string +} + +type mountPointFormatInvalidError struct { + line string +} + +type pathNotExposedFromMountPointError struct { + mountPoint string + root string + path string +} + +func (err cgroupSubsysFormatInvalidError) Error() string { + return fmt.Sprintf("invalid format for CGroupSubsys: %q", err.line) +} + +func (err mountPointFormatInvalidError) Error() string { + return fmt.Sprintf("invalid format for MountPoint: %q", err.line) +} + +func (err pathNotExposedFromMountPointError) Error() string { + return fmt.Sprintf("path %q is not a descendant of mount point root %q and cannot be exposed from %q", err.path, err.root, err.mountPoint) +} diff --git a/vendor/go.uber.org/automaxprocs/internal/cgroups/mountpoint.go b/vendor/go.uber.org/automaxprocs/internal/cgroups/mountpoint.go new file mode 100644 index 0000000000..f3877f78aa --- /dev/null +++ b/vendor/go.uber.org/automaxprocs/internal/cgroups/mountpoint.go @@ -0,0 +1,171 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// 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. + +//go:build linux +// +build linux + +package cgroups + +import ( + "bufio" + "os" + "path/filepath" + "strconv" + "strings" +) + +const ( + _mountInfoSep = " " + _mountInfoOptsSep = "," + _mountInfoOptionalFieldsSep = "-" +) + +const ( + _miFieldIDMountID = iota + _miFieldIDParentID + _miFieldIDDeviceID + _miFieldIDRoot + _miFieldIDMountPoint + _miFieldIDOptions + _miFieldIDOptionalFields + + _miFieldCountFirstHalf +) + +const ( + _miFieldOffsetFSType = iota + _miFieldOffsetMountSource + _miFieldOffsetSuperOptions + + _miFieldCountSecondHalf +) + +const _miFieldCountMin = _miFieldCountFirstHalf + _miFieldCountSecondHalf + +// MountPoint is the data structure for the mount points in +// `/proc/$PID/mountinfo`. See also proc(5) for more information. +type MountPoint struct { + MountID int + ParentID int + DeviceID string + Root string + MountPoint string + Options []string + OptionalFields []string + FSType string + MountSource string + SuperOptions []string +} + +// NewMountPointFromLine parses a line read from `/proc/$PID/mountinfo` and +// returns a new *MountPoint. +func NewMountPointFromLine(line string) (*MountPoint, error) { + fields := strings.Split(line, _mountInfoSep) + + if len(fields) < _miFieldCountMin { + return nil, mountPointFormatInvalidError{line} + } + + mountID, err := strconv.Atoi(fields[_miFieldIDMountID]) + if err != nil { + return nil, err + } + + parentID, err := strconv.Atoi(fields[_miFieldIDParentID]) + if err != nil { + return nil, err + } + + for i, field := range fields[_miFieldIDOptionalFields:] { + if field == _mountInfoOptionalFieldsSep { + // End of optional fields. + fsTypeStart := _miFieldIDOptionalFields + i + 1 + + // Now we know where the optional fields end, split the line again with a + // limit to avoid issues with spaces in super options as present on WSL. + fields = strings.SplitN(line, _mountInfoSep, fsTypeStart+_miFieldCountSecondHalf) + if len(fields) != fsTypeStart+_miFieldCountSecondHalf { + return nil, mountPointFormatInvalidError{line} + } + + miFieldIDFSType := _miFieldOffsetFSType + fsTypeStart + miFieldIDMountSource := _miFieldOffsetMountSource + fsTypeStart + miFieldIDSuperOptions := _miFieldOffsetSuperOptions + fsTypeStart + + return &MountPoint{ + MountID: mountID, + ParentID: parentID, + DeviceID: fields[_miFieldIDDeviceID], + Root: fields[_miFieldIDRoot], + MountPoint: fields[_miFieldIDMountPoint], + Options: strings.Split(fields[_miFieldIDOptions], _mountInfoOptsSep), + OptionalFields: fields[_miFieldIDOptionalFields:(fsTypeStart - 1)], + FSType: fields[miFieldIDFSType], + MountSource: fields[miFieldIDMountSource], + SuperOptions: strings.Split(fields[miFieldIDSuperOptions], _mountInfoOptsSep), + }, nil + } + } + + return nil, mountPointFormatInvalidError{line} +} + +// Translate converts an absolute path inside the *MountPoint's file system to +// the host file system path in the mount namespace the *MountPoint belongs to. +func (mp *MountPoint) Translate(absPath string) (string, error) { + relPath, err := filepath.Rel(mp.Root, absPath) + + if err != nil { + return "", err + } + if relPath == ".." || strings.HasPrefix(relPath, "../") { + return "", pathNotExposedFromMountPointError{ + mountPoint: mp.MountPoint, + root: mp.Root, + path: absPath, + } + } + + return filepath.Join(mp.MountPoint, relPath), nil +} + +// parseMountInfo parses procPathMountInfo (usually at `/proc/$PID/mountinfo`) +// and yields parsed *MountPoint into newMountPoint. +func parseMountInfo(procPathMountInfo string, newMountPoint func(*MountPoint) error) error { + mountInfoFile, err := os.Open(procPathMountInfo) + if err != nil { + return err + } + defer mountInfoFile.Close() + + scanner := bufio.NewScanner(mountInfoFile) + + for scanner.Scan() { + mountPoint, err := NewMountPointFromLine(scanner.Text()) + if err != nil { + return err + } + if err := newMountPoint(mountPoint); err != nil { + return err + } + } + + return scanner.Err() +} diff --git a/vendor/go.uber.org/automaxprocs/internal/cgroups/subsys.go b/vendor/go.uber.org/automaxprocs/internal/cgroups/subsys.go new file mode 100644 index 0000000000..cddc3eaec3 --- /dev/null +++ b/vendor/go.uber.org/automaxprocs/internal/cgroups/subsys.go @@ -0,0 +1,103 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// 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. + +//go:build linux +// +build linux + +package cgroups + +import ( + "bufio" + "os" + "strconv" + "strings" +) + +const ( + _cgroupSep = ":" + _cgroupSubsysSep = "," +) + +const ( + _csFieldIDID = iota + _csFieldIDSubsystems + _csFieldIDName + _csFieldCount +) + +// CGroupSubsys represents the data structure for entities in +// `/proc/$PID/cgroup`. See also proc(5) for more information. +type CGroupSubsys struct { + ID int + Subsystems []string + Name string +} + +// NewCGroupSubsysFromLine returns a new *CGroupSubsys by parsing a string in +// the format of `/proc/$PID/cgroup` +func NewCGroupSubsysFromLine(line string) (*CGroupSubsys, error) { + fields := strings.SplitN(line, _cgroupSep, _csFieldCount) + + if len(fields) != _csFieldCount { + return nil, cgroupSubsysFormatInvalidError{line} + } + + id, err := strconv.Atoi(fields[_csFieldIDID]) + if err != nil { + return nil, err + } + + cgroup := &CGroupSubsys{ + ID: id, + Subsystems: strings.Split(fields[_csFieldIDSubsystems], _cgroupSubsysSep), + Name: fields[_csFieldIDName], + } + + return cgroup, nil +} + +// parseCGroupSubsystems parses procPathCGroup (usually at `/proc/$PID/cgroup`) +// and returns a new map[string]*CGroupSubsys. +func parseCGroupSubsystems(procPathCGroup string) (map[string]*CGroupSubsys, error) { + cgroupFile, err := os.Open(procPathCGroup) + if err != nil { + return nil, err + } + defer cgroupFile.Close() + + scanner := bufio.NewScanner(cgroupFile) + subsystems := make(map[string]*CGroupSubsys) + + for scanner.Scan() { + cgroup, err := NewCGroupSubsysFromLine(scanner.Text()) + if err != nil { + return nil, err + } + for _, subsys := range cgroup.Subsystems { + subsystems[subsys] = cgroup + } + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + return subsystems, nil +} diff --git a/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_linux.go b/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_linux.go new file mode 100644 index 0000000000..3b974754c3 --- /dev/null +++ b/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_linux.go @@ -0,0 +1,71 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// 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. + +//go:build linux +// +build linux + +package runtime + +import ( + "errors" + "math" + + cg "go.uber.org/automaxprocs/internal/cgroups" +) + +// CPUQuotaToGOMAXPROCS converts the CPU quota applied to the calling process +// to a valid GOMAXPROCS value. +func CPUQuotaToGOMAXPROCS(minValue int) (int, CPUQuotaStatus, error) { + cgroups, err := newQueryer() + if err != nil { + return -1, CPUQuotaUndefined, err + } + + quota, defined, err := cgroups.CPUQuota() + if !defined || err != nil { + return -1, CPUQuotaUndefined, err + } + + maxProcs := int(math.Floor(quota)) + if minValue > 0 && maxProcs < minValue { + return minValue, CPUQuotaMinUsed, nil + } + return maxProcs, CPUQuotaUsed, nil +} + +type queryer interface { + CPUQuota() (float64, bool, error) +} + +var ( + _newCgroups2 = cg.NewCGroups2ForCurrentProcess + _newCgroups = cg.NewCGroupsForCurrentProcess +) + +func newQueryer() (queryer, error) { + cgroups, err := _newCgroups2() + if err == nil { + return cgroups, nil + } + if errors.Is(err, cg.ErrNotV2) { + return _newCgroups() + } + return nil, err +} diff --git a/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_unsupported.go b/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_unsupported.go new file mode 100644 index 0000000000..6922554484 --- /dev/null +++ b/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_unsupported.go @@ -0,0 +1,31 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// 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. + +//go:build !linux +// +build !linux + +package runtime + +// CPUQuotaToGOMAXPROCS converts the CPU quota applied to the calling process +// to a valid GOMAXPROCS value. This is Linux-specific and not supported in the +// current OS. +func CPUQuotaToGOMAXPROCS(_ int) (int, CPUQuotaStatus, error) { + return -1, CPUQuotaUndefined, nil +} diff --git a/vendor/go.uber.org/automaxprocs/internal/runtime/runtime.go b/vendor/go.uber.org/automaxprocs/internal/runtime/runtime.go new file mode 100644 index 0000000000..df6eacf053 --- /dev/null +++ b/vendor/go.uber.org/automaxprocs/internal/runtime/runtime.go @@ -0,0 +1,33 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// 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. + +package runtime + +// CPUQuotaStatus presents the status of how CPU quota is used +type CPUQuotaStatus int + +const ( + // CPUQuotaUndefined is returned when CPU quota is undefined + CPUQuotaUndefined CPUQuotaStatus = iota + // CPUQuotaUsed is returned when a valid CPU quota can be used + CPUQuotaUsed + // CPUQuotaMinUsed is returned when CPU quota is smaller than the min value + CPUQuotaMinUsed +) diff --git a/vendor/go.uber.org/automaxprocs/maxprocs/maxprocs.go b/vendor/go.uber.org/automaxprocs/maxprocs/maxprocs.go new file mode 100644 index 0000000000..98176d6457 --- /dev/null +++ b/vendor/go.uber.org/automaxprocs/maxprocs/maxprocs.go @@ -0,0 +1,130 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// 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. + +// Package maxprocs lets Go programs easily configure runtime.GOMAXPROCS to +// match the configured Linux CPU quota. Unlike the top-level automaxprocs +// package, it lets the caller configure logging and handle errors. +package maxprocs // import "go.uber.org/automaxprocs/maxprocs" + +import ( + "os" + "runtime" + + iruntime "go.uber.org/automaxprocs/internal/runtime" +) + +const _maxProcsKey = "GOMAXPROCS" + +func currentMaxProcs() int { + return runtime.GOMAXPROCS(0) +} + +type config struct { + printf func(string, ...interface{}) + procs func(int) (int, iruntime.CPUQuotaStatus, error) + minGOMAXPROCS int +} + +func (c *config) log(fmt string, args ...interface{}) { + if c.printf != nil { + c.printf(fmt, args...) + } +} + +// An Option alters the behavior of Set. +type Option interface { + apply(*config) +} + +// Logger uses the supplied printf implementation for log output. By default, +// Set doesn't log anything. +func Logger(printf func(string, ...interface{})) Option { + return optionFunc(func(cfg *config) { + cfg.printf = printf + }) +} + +// Min sets the minimum GOMAXPROCS value that will be used. +// Any value below 1 is ignored. +func Min(n int) Option { + return optionFunc(func(cfg *config) { + if n >= 1 { + cfg.minGOMAXPROCS = n + } + }) +} + +type optionFunc func(*config) + +func (of optionFunc) apply(cfg *config) { of(cfg) } + +// Set GOMAXPROCS to match the Linux container CPU quota (if any), returning +// any error encountered and an undo function. +// +// Set is a no-op on non-Linux systems and in Linux environments without a +// configured CPU quota. +func Set(opts ...Option) (func(), error) { + cfg := &config{ + procs: iruntime.CPUQuotaToGOMAXPROCS, + minGOMAXPROCS: 1, + } + for _, o := range opts { + o.apply(cfg) + } + + undoNoop := func() { + cfg.log("maxprocs: No GOMAXPROCS change to reset") + } + + // Honor the GOMAXPROCS environment variable if present. Otherwise, amend + // `runtime.GOMAXPROCS()` with the current process' CPU quota if the OS is + // Linux, and guarantee a minimum value of 1. The minimum guaranteed value + // can be overridden using `maxprocs.Min()`. + if max, exists := os.LookupEnv(_maxProcsKey); exists { + cfg.log("maxprocs: Honoring GOMAXPROCS=%q as set in environment", max) + return undoNoop, nil + } + + maxProcs, status, err := cfg.procs(cfg.minGOMAXPROCS) + if err != nil { + return undoNoop, err + } + + if status == iruntime.CPUQuotaUndefined { + cfg.log("maxprocs: Leaving GOMAXPROCS=%v: CPU quota undefined", currentMaxProcs()) + return undoNoop, nil + } + + prev := currentMaxProcs() + undo := func() { + cfg.log("maxprocs: Resetting GOMAXPROCS to %v", prev) + runtime.GOMAXPROCS(prev) + } + + switch status { + case iruntime.CPUQuotaMinUsed: + cfg.log("maxprocs: Updating GOMAXPROCS=%v: using minimum allowed GOMAXPROCS", maxProcs) + case iruntime.CPUQuotaUsed: + cfg.log("maxprocs: Updating GOMAXPROCS=%v: determined from CPU quota", maxProcs) + } + + runtime.GOMAXPROCS(maxProcs) + return undo, nil +} diff --git a/vendor/go.uber.org/automaxprocs/maxprocs/version.go b/vendor/go.uber.org/automaxprocs/maxprocs/version.go new file mode 100644 index 0000000000..108a95535e --- /dev/null +++ b/vendor/go.uber.org/automaxprocs/maxprocs/version.go @@ -0,0 +1,24 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// 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. + +package maxprocs + +// Version is the current package version. +const Version = "1.5.2" diff --git a/vendor/golang.org/x/exp/constraints/constraints.go b/vendor/golang.org/x/exp/constraints/constraints.go new file mode 100644 index 0000000000..2c033dff47 --- /dev/null +++ b/vendor/golang.org/x/exp/constraints/constraints.go @@ -0,0 +1,50 @@ +// 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/slices/cmp.go b/vendor/golang.org/x/exp/slices/cmp.go new file mode 100644 index 0000000000..fbf1934a06 --- /dev/null +++ b/vendor/golang.org/x/exp/slices/cmp.go @@ -0,0 +1,44 @@ +// 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 slices + +import "golang.org/x/exp/constraints" + +// min is a version of the predeclared function from the Go 1.21 release. +func min[T constraints.Ordered](a, b T) T { + if a < b || isNaN(a) { + return a + } + return b +} + +// max is a version of the predeclared function from the Go 1.21 release. +func max[T constraints.Ordered](a, b T) T { + if a > b || isNaN(a) { + return a + } + return b +} + +// cmpLess is a copy of cmp.Less from the Go 1.21 release. +func cmpLess[T constraints.Ordered](x, y T) bool { + return (isNaN(x) && !isNaN(y)) || x < y +} + +// cmpCompare is a copy of cmp.Compare from the Go 1.21 release. +func cmpCompare[T constraints.Ordered](x, y T) int { + xNaN := isNaN(x) + yNaN := isNaN(y) + if xNaN && yNaN { + return 0 + } + if xNaN || x < y { + return -1 + } + if yNaN || x > y { + return +1 + } + return 0 +} diff --git a/vendor/golang.org/x/exp/slices/slices.go b/vendor/golang.org/x/exp/slices/slices.go new file mode 100644 index 0000000000..5e8158bba8 --- /dev/null +++ b/vendor/golang.org/x/exp/slices/slices.go @@ -0,0 +1,499 @@ +// 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. +package slices + +import ( + "unsafe" + + "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[S ~[]E, E comparable](s1, s2 S) 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 an equality +// 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[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, 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, using [cmp.Compare] on each pair +// of elements. 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. +func Compare[S ~[]E, E constraints.Ordered](s1, s2 S) int { + for i, v1 := range s1 { + if i >= len(s2) { + return +1 + } + v2 := s2[i] + if c := cmpCompare(v1, v2); c != 0 { + return c + } + } + if len(s1) < len(s2) { + return -1 + } + return 0 +} + +// CompareFunc is like [Compare] but uses a custom comparison function on each +// pair of elements. +// 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[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, cmp func(E1, E2) int) int { + for i, v1 := range s1 { + if i >= len(s2) { + return +1 + } + v2 := s2[i] + if c := cmp(v1, v2); c != 0 { + return c + } + } + if len(s1) < len(s2) { + return -1 + } + return 0 +} + +// Index returns the index of the first occurrence of v in s, +// or -1 if not present. +func Index[S ~[]E, E comparable](s S, 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[S ~[]E, E any](s S, 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[S ~[]E, E comparable](s S, v E) bool { + return Index(s, v) >= 0 +} + +// ContainsFunc reports whether at least one +// element e of s satisfies f(e). +func ContainsFunc[S ~[]E, E any](s S, f func(E) bool) bool { + return IndexFunc(s, f) >= 0 +} + +// Insert inserts the values v... into s at index i, +// returning the modified slice. +// The elements at s[i:] are shifted up to make room. +// In the returned slice r, r[i] == v[0], +// and r[i+len(v)] == value originally at r[i]. +// 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 { + m := len(v) + if m == 0 { + return s + } + n := len(s) + if i == n { + return append(s, v...) + } + if n+m > cap(s) { + // Use append rather than make so that we bump the size of + // the slice up to the next storage class. + // This is what Grow does but we don't call Grow because + // that might copy the values twice. + s2 := append(s[:i], make(S, n+m-i)...) + copy(s2[i:], v) + copy(s2[i+m:], s[i:]) + return s2 + } + s = s[:n+m] + + // before: + // s: aaaaaaaabbbbccccccccdddd + // ^ ^ ^ ^ + // i i+m n n+m + // after: + // s: aaaaaaaavvvvbbbbcccccccc + // ^ ^ ^ ^ + // i i+m n n+m + // + // a are the values that don't move in s. + // v are the values copied in from v. + // b and c are the values from s that are shifted up in index. + // d are the values that get overwritten, never to be seen again. + + if !overlaps(v, s[i+m:]) { + // Easy case - v does not overlap either the c or d regions. + // (It might be in some of a or b, or elsewhere entirely.) + // The data we copy up doesn't write to v at all, so just do it. + + copy(s[i+m:], s[i:]) + + // Now we have + // s: aaaaaaaabbbbbbbbcccccccc + // ^ ^ ^ ^ + // i i+m n n+m + // Note the b values are duplicated. + + copy(s[i:], v) + + // Now we have + // s: aaaaaaaavvvvbbbbcccccccc + // ^ ^ ^ ^ + // i i+m n n+m + // That's the result we want. + return s + } + + // The hard case - v overlaps c or d. We can't just shift up + // the data because we'd move or clobber the values we're trying + // to insert. + // So instead, write v on top of d, then rotate. + copy(s[n:], v) + + // Now we have + // s: aaaaaaaabbbbccccccccvvvv + // ^ ^ ^ ^ + // i i+m n n+m + + rotateRight(s[i:], m) + + // Now we have + // s: aaaaaaaavvvvbbbbcccccccc + // ^ ^ ^ ^ + // i i+m n n+m + // That's the result we want. + return s +} + +// 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 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:]...) +} + +// DeleteFunc removes any elements from s for which del returns true, +// returning the modified slice. +// When DeleteFunc removes m elements, 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 DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S { + i := IndexFunc(s, del) + if i == -1 { + return s + } + // Don't start copying elements until we find one to delete. + for j := i + 1; j < len(s); j++ { + if v := s[j]; !del(v) { + s[i] = v + i++ + } + } + return s[:i] +} + +// 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 + + if i == j { + return Insert(s, i, v...) + } + if j == len(s) { + return append(s[:i], v...) + } + + tot := len(s[:i]) + len(v) + len(s[j:]) + if tot > cap(s) { + // Too big to fit, allocate and copy over. + s2 := append(s[:i], make(S, tot-i)...) // See Insert + copy(s2[i:], v) + copy(s2[i+len(v):], s[j:]) + return s2 + } + + r := s[:tot] + + if i+len(v) <= j { + // Easy, as v fits in the deleted portion. + copy(r[i:], v) + if i+len(v) != j { + copy(r[i+len(v):], s[j:]) + } + return r + } + + // We are expanding (v is bigger than j-i). + // The situation is something like this: + // (example has i=4,j=8,len(s)=16,len(v)=6) + // s: aaaaxxxxbbbbbbbbyy + // ^ ^ ^ ^ + // i j len(s) tot + // a: prefix of s + // x: deleted range + // b: more of s + // y: area to expand into + + if !overlaps(r[i+len(v):], v) { + // Easy, as v is not clobbered by the first copy. + copy(r[i+len(v):], s[j:]) + copy(r[i:], v) + return r + } + + // This is a situation where we don't have a single place to which + // we can copy v. Parts of it need to go to two different places. + // We want to copy the prefix of v into y and the suffix into x, then + // rotate |y| spots to the right. + // + // v[2:] v[:2] + // | | + // s: aaaavvvvbbbbbbbbvv + // ^ ^ ^ ^ + // i j len(s) tot + // + // If either of those two destinations don't alias v, then we're good. + y := len(v) - (j - i) // length of y portion + + if !overlaps(r[i:j], v) { + copy(r[i:j], v[y:]) + copy(r[len(s):], v[:y]) + rotateRight(r[i:], y) + return r + } + if !overlaps(r[len(s):], v) { + copy(r[len(s):], v[:y]) + copy(r[i:j], v[y:]) + rotateRight(r[i:], y) + return r + } + + // Now we know that v overlaps both x and y. + // That means that the entirety of b is *inside* v. + // So we don't need to preserve b at all; instead we + // can copy v first, then copy the b part of v out of + // v to the right destination. + k := startIdx(v, s[j:]) + copy(r[i:], v) + copy(r[i+len(v):], r[i+k:]) + return r +} + +// 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 and returns the modified slice, +// which may have a smaller length. +// 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 an equality function to compare elements. +// For runs of elements that compare equal, CompactFunc keeps the first one. +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)] +} + +// Rotation algorithm explanation: +// +// rotate left by 2 +// start with +// 0123456789 +// split up like this +// 01 234567 89 +// swap first 2 and last 2 +// 89 234567 01 +// join first parts +// 89234567 01 +// recursively rotate first left part by 2 +// 23456789 01 +// join at the end +// 2345678901 +// +// rotate left by 8 +// start with +// 0123456789 +// split up like this +// 01 234567 89 +// swap first 2 and last 2 +// 89 234567 01 +// join last parts +// 89 23456701 +// recursively rotate second part left by 6 +// 89 01234567 +// join at the end +// 8901234567 + +// TODO: There are other rotate algorithms. +// This algorithm has the desirable property that it moves each element exactly twice. +// The triple-reverse algorithm is simpler and more cache friendly, but takes more writes. +// The follow-cycles algorithm can be 1-write but it is not very cache friendly. + +// rotateLeft rotates b left by n spaces. +// s_final[i] = s_orig[i+r], wrapping around. +func rotateLeft[E any](s []E, r int) { + for r != 0 && r != len(s) { + if r*2 <= len(s) { + swap(s[:r], s[len(s)-r:]) + s = s[:len(s)-r] + } else { + swap(s[:len(s)-r], s[r:]) + s, r = s[len(s)-r:], r*2-len(s) + } + } +} +func rotateRight[E any](s []E, r int) { + rotateLeft(s, len(s)-r) +} + +// swap swaps the contents of x and y. x and y must be equal length and disjoint. +func swap[E any](x, y []E) { + for i := 0; i < len(x); i++ { + x[i], y[i] = y[i], x[i] + } +} + +// overlaps reports whether the memory ranges a[0:len(a)] and b[0:len(b)] overlap. +func overlaps[E any](a, b []E) bool { + if len(a) == 0 || len(b) == 0 { + return false + } + elemSize := unsafe.Sizeof(a[0]) + if elemSize == 0 { + return false + } + // TODO: use a runtime/unsafe facility once one becomes available. See issue 12445. + // Also see crypto/internal/alias/alias.go:AnyOverlap + return uintptr(unsafe.Pointer(&a[0])) <= uintptr(unsafe.Pointer(&b[len(b)-1]))+(elemSize-1) && + uintptr(unsafe.Pointer(&b[0])) <= uintptr(unsafe.Pointer(&a[len(a)-1]))+(elemSize-1) +} + +// startIdx returns the index in haystack where the needle starts. +// prerequisite: the needle must be aliased entirely inside the haystack. +func startIdx[E any](haystack, needle []E) int { + p := &needle[0] + for i := range haystack { + if p == &haystack[i] { + return i + } + } + // TODO: what if the overlap is by a non-integral number of Es? + panic("needle not found") +} + +// Reverse reverses the elements of the slice in place. +func Reverse[S ~[]E, E any](s S) { + for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { + s[i], s[j] = s[j], s[i] + } +} diff --git a/vendor/golang.org/x/exp/slices/sort.go b/vendor/golang.org/x/exp/slices/sort.go new file mode 100644 index 0000000000..b67897f76b --- /dev/null +++ b/vendor/golang.org/x/exp/slices/sort.go @@ -0,0 +1,195 @@ +// 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:generate go run $GOROOT/src/sort/gen_sort_variants.go -exp + +package slices + +import ( + "math/bits" + + "golang.org/x/exp/constraints" +) + +// Sort sorts a slice of any ordered type in ascending order. +// When sorting floating-point numbers, NaNs are ordered before other values. +func Sort[S ~[]E, E constraints.Ordered](x S) { + n := len(x) + pdqsortOrdered(x, 0, n, bits.Len(uint(n))) +} + +// SortFunc sorts the slice x in ascending order as determined by the cmp +// function. This sort is not guaranteed to be stable. +// cmp(a, b) should return a negative number when a < b, a positive number when +// a > b and zero when a == b. +// +// SortFunc requires that cmp is a strict weak ordering. +// See https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings. +func SortFunc[S ~[]E, E any](x S, cmp func(a, b E) int) { + n := len(x) + pdqsortCmpFunc(x, 0, n, bits.Len(uint(n)), cmp) +} + +// SortStableFunc sorts the slice x while keeping the original order of equal +// elements, using cmp to compare elements in the same way as [SortFunc]. +func SortStableFunc[S ~[]E, E any](x S, cmp func(a, b E) int) { + stableCmpFunc(x, len(x), cmp) +} + +// IsSorted reports whether x is sorted in ascending order. +func IsSorted[S ~[]E, E constraints.Ordered](x S) bool { + for i := len(x) - 1; i > 0; i-- { + if cmpLess(x[i], x[i-1]) { + return false + } + } + return true +} + +// IsSortedFunc reports whether x is sorted in ascending order, with cmp as the +// comparison function as defined by [SortFunc]. +func IsSortedFunc[S ~[]E, E any](x S, cmp func(a, b E) int) bool { + for i := len(x) - 1; i > 0; i-- { + if cmp(x[i], x[i-1]) < 0 { + return false + } + } + return true +} + +// Min returns the minimal value in x. It panics if x is empty. +// For floating-point numbers, Min propagates NaNs (any NaN value in x +// forces the output to be NaN). +func Min[S ~[]E, E constraints.Ordered](x S) E { + if len(x) < 1 { + panic("slices.Min: empty list") + } + m := x[0] + for i := 1; i < len(x); i++ { + m = min(m, x[i]) + } + return m +} + +// MinFunc returns the minimal value in x, using cmp to compare elements. +// It panics if x is empty. If there is more than one minimal element +// according to the cmp function, MinFunc returns the first one. +func MinFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E { + if len(x) < 1 { + panic("slices.MinFunc: empty list") + } + m := x[0] + for i := 1; i < len(x); i++ { + if cmp(x[i], m) < 0 { + m = x[i] + } + } + return m +} + +// Max returns the maximal value in x. It panics if x is empty. +// For floating-point E, Max propagates NaNs (any NaN value in x +// forces the output to be NaN). +func Max[S ~[]E, E constraints.Ordered](x S) E { + if len(x) < 1 { + panic("slices.Max: empty list") + } + m := x[0] + for i := 1; i < len(x); i++ { + m = max(m, x[i]) + } + return m +} + +// MaxFunc returns the maximal value in x, using cmp to compare elements. +// It panics if x is empty. If there is more than one maximal element +// according to the cmp function, MaxFunc returns the first one. +func MaxFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E { + if len(x) < 1 { + panic("slices.MaxFunc: empty list") + } + m := x[0] + for i := 1; i < len(x); i++ { + if cmp(x[i], m) > 0 { + m = x[i] + } + } + return m +} + +// 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[S ~[]E, E constraints.Ordered](x S, 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 cmpLess(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 || (isNaN(x[i]) && isNaN(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[S ~[]E, E, T any](x S, 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)) +} + +// isNaN reports whether x is a NaN without requiring the math package. +// This will always return false if T is not floating-point. +func isNaN[T constraints.Ordered](x T) bool { + return x != x +} diff --git a/vendor/golang.org/x/exp/slices/zsortanyfunc.go b/vendor/golang.org/x/exp/slices/zsortanyfunc.go new file mode 100644 index 0000000000..06f2c7a248 --- /dev/null +++ b/vendor/golang.org/x/exp/slices/zsortanyfunc.go @@ -0,0 +1,479 @@ +// 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 + +// insertionSortCmpFunc sorts data[a:b] using insertion sort. +func insertionSortCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) { + for i := a + 1; i < b; i++ { + for j := i; j > a && (cmp(data[j], data[j-1]) < 0); j-- { + data[j], data[j-1] = data[j-1], data[j] + } + } +} + +// siftDownCmpFunc implements the heap property on data[lo:hi]. +// first is an offset into the array where the root of the heap lies. +func siftDownCmpFunc[E any](data []E, lo, hi, first int, cmp func(a, b E) int) { + root := lo + for { + child := 2*root + 1 + if child >= hi { + break + } + if child+1 < hi && (cmp(data[first+child], data[first+child+1]) < 0) { + child++ + } + if !(cmp(data[first+root], data[first+child]) < 0) { + return + } + data[first+root], data[first+child] = data[first+child], data[first+root] + root = child + } +} + +func heapSortCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) { + first := a + lo := 0 + hi := b - a + + // Build heap with greatest element at top. + for i := (hi - 1) / 2; i >= 0; i-- { + siftDownCmpFunc(data, i, hi, first, cmp) + } + + // 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] + siftDownCmpFunc(data, lo, i, first, cmp) + } +} + +// pdqsortCmpFunc 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 pdqsortCmpFunc[E any](data []E, a, b, limit int, cmp func(a, b E) 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 { + insertionSortCmpFunc(data, a, b, cmp) + return + } + + // Fall back to heapsort if too many bad choices were made. + if limit == 0 { + heapSortCmpFunc(data, a, b, cmp) + return + } + + // If the last partitioning was imbalanced, we need to breaking patterns. + if !wasBalanced { + breakPatternsCmpFunc(data, a, b, cmp) + limit-- + } + + pivot, hint := choosePivotCmpFunc(data, a, b, cmp) + if hint == decreasingHint { + reverseRangeCmpFunc(data, a, b, cmp) + // 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 partialInsertionSortCmpFunc(data, a, b, cmp) { + return + } + } + + // Probably the slice contains many duplicate elements, partition the slice into + // elements equal to and elements greater than the pivot. + if a > 0 && !(cmp(data[a-1], data[pivot]) < 0) { + mid := partitionEqualCmpFunc(data, a, b, pivot, cmp) + a = mid + continue + } + + mid, alreadyPartitioned := partitionCmpFunc(data, a, b, pivot, cmp) + wasPartitioned = alreadyPartitioned + + leftLen, rightLen := mid-a, b-mid + balanceThreshold := length / 8 + if leftLen < rightLen { + wasBalanced = leftLen >= balanceThreshold + pdqsortCmpFunc(data, a, mid, limit, cmp) + a = mid + 1 + } else { + wasBalanced = rightLen >= balanceThreshold + pdqsortCmpFunc(data, mid+1, b, limit, cmp) + b = mid + } + } +} + +// partitionCmpFunc does one quicksort partition. +// Let p = data[pivot] +// Moves elements in data[a:b] around, so that data[i]

=p for inewpivot. +// On return, data[newpivot] = p +func partitionCmpFunc[E any](data []E, a, b, pivot int, cmp func(a, b E) 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 && (cmp(data[i], data[a]) < 0) { + i++ + } + for i <= j && !(cmp(data[j], data[a]) < 0) { + 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 && (cmp(data[i], data[a]) < 0) { + i++ + } + for i <= j && !(cmp(data[j], data[a]) < 0) { + 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 +} + +// partitionEqualCmpFunc 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 partitionEqualCmpFunc[E any](data []E, a, b, pivot int, cmp func(a, b E) 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 && !(cmp(data[a], data[i]) < 0) { + i++ + } + for i <= j && (cmp(data[a], data[j]) < 0) { + j-- + } + if i > j { + break + } + data[i], data[j] = data[j], data[i] + i++ + j-- + } + return i +} + +// partialInsertionSortCmpFunc partially sorts a slice, returns true if the slice is sorted at the end. +func partialInsertionSortCmpFunc[E any](data []E, a, b int, cmp func(a, b E) 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 && !(cmp(data[i], data[i-1]) < 0) { + 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 !(cmp(data[j], data[j-1]) < 0) { + 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 !(cmp(data[j], data[j-1]) < 0) { + break + } + data[j], data[j-1] = data[j-1], data[j] + } + } + } + return false +} + +// breakPatternsCmpFunc scatters some elements around in an attempt to break some patterns +// that might cause imbalanced partitions in quicksort. +func breakPatternsCmpFunc[E any](data []E, a, b int, cmp func(a, b E) 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] + } + } +} + +// choosePivotCmpFunc 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 choosePivotCmpFunc[E any](data []E, a, b int, cmp func(a, b E) 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 = medianAdjacentCmpFunc(data, i, &swaps, cmp) + j = medianAdjacentCmpFunc(data, j, &swaps, cmp) + k = medianAdjacentCmpFunc(data, k, &swaps, cmp) + } + // Find the median among i, j, k and stores it into j. + j = medianCmpFunc(data, i, j, k, &swaps, cmp) + } + + switch swaps { + case 0: + return j, increasingHint + case maxSwaps: + return j, decreasingHint + default: + return j, unknownHint + } +} + +// order2CmpFunc returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a. +func order2CmpFunc[E any](data []E, a, b int, swaps *int, cmp func(a, b E) int) (int, int) { + if cmp(data[b], data[a]) < 0 { + *swaps++ + return b, a + } + return a, b +} + +// medianCmpFunc returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c. +func medianCmpFunc[E any](data []E, a, b, c int, swaps *int, cmp func(a, b E) int) int { + a, b = order2CmpFunc(data, a, b, swaps, cmp) + b, c = order2CmpFunc(data, b, c, swaps, cmp) + a, b = order2CmpFunc(data, a, b, swaps, cmp) + return b +} + +// medianAdjacentCmpFunc finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a. +func medianAdjacentCmpFunc[E any](data []E, a int, swaps *int, cmp func(a, b E) int) int { + return medianCmpFunc(data, a-1, a, a+1, swaps, cmp) +} + +func reverseRangeCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) { + i := a + j := b - 1 + for i < j { + data[i], data[j] = data[j], data[i] + i++ + j-- + } +} + +func swapRangeCmpFunc[E any](data []E, a, b, n int, cmp func(a, b E) int) { + for i := 0; i < n; i++ { + data[a+i], data[b+i] = data[b+i], data[a+i] + } +} + +func stableCmpFunc[E any](data []E, n int, cmp func(a, b E) int) { + blockSize := 20 // must be > 0 + a, b := 0, blockSize + for b <= n { + insertionSortCmpFunc(data, a, b, cmp) + a = b + b += blockSize + } + insertionSortCmpFunc(data, a, n, cmp) + + for blockSize < n { + a, b = 0, 2*blockSize + for b <= n { + symMergeCmpFunc(data, a, a+blockSize, b, cmp) + a = b + b += 2 * blockSize + } + if m := a + blockSize; m < n { + symMergeCmpFunc(data, a, m, n, cmp) + } + blockSize *= 2 + } +} + +// symMergeCmpFunc 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 symMergeCmpFunc[E any](data []E, a, m, b int, cmp func(a, b E) 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 cmp(data[h], data[a]) < 0 { + 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 !(cmp(data[m], data[h]) < 0) { + 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 !(cmp(data[p-c], data[c]) < 0) { + start = c + 1 + } else { + r = c + } + } + + end := n - start + if start < m && m < end { + rotateCmpFunc(data, start, m, end, cmp) + } + if a < start && start < mid { + symMergeCmpFunc(data, a, start, mid, cmp) + } + if mid < end && end < b { + symMergeCmpFunc(data, mid, end, b, cmp) + } +} + +// rotateCmpFunc 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 rotateCmpFunc[E any](data []E, a, m, b int, cmp func(a, b E) int) { + i := m - a + j := b - m + + for i != j { + if i > j { + swapRangeCmpFunc(data, m-i, m, j, cmp) + i -= j + } else { + swapRangeCmpFunc(data, m-i, m+j-i, i, cmp) + j -= i + } + } + // i == j + swapRangeCmpFunc(data, m-i, m, i, cmp) +} diff --git a/vendor/golang.org/x/exp/slices/zsortordered.go b/vendor/golang.org/x/exp/slices/zsortordered.go new file mode 100644 index 0000000000..99b47c3986 --- /dev/null +++ b/vendor/golang.org/x/exp/slices/zsortordered.go @@ -0,0 +1,481 @@ +// 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 && cmpLess(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 && cmpLess(data[first+child], data[first+child+1]) { + child++ + } + if !cmpLess(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 && !cmpLess(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 for inewpivot. +// 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 && cmpLess(data[i], data[a]) { + i++ + } + for i <= j && !cmpLess(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 && cmpLess(data[i], data[a]) { + i++ + } + for i <= j && !cmpLess(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 && !cmpLess(data[a], data[i]) { + i++ + } + for i <= j && cmpLess(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 && !cmpLess(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 !cmpLess(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 !cmpLess(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 cmpLess(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 cmpLess(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 !cmpLess(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 !cmpLess(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/mod/modfile/rule.go b/vendor/golang.org/x/mod/modfile/rule.go index 35fd1f534c..26acaa5f7c 100644 --- a/vendor/golang.org/x/mod/modfile/rule.go +++ b/vendor/golang.org/x/mod/modfile/rule.go @@ -308,6 +308,7 @@ var laxGoVersionRE = lazyregexp.New(`^v?(([1-9][0-9]*)\.(0|[1-9][0-9]*))([^0-9]. // Toolchains must be named beginning with `go1`, // like "go1.20.3" or "go1.20.3-gccgo". As a special case, "default" is also permitted. +// TODO(samthanawalla): Replace regex with https://pkg.go.dev/go/version#IsValid in 1.23+ var ToolchainRE = lazyregexp.New(`^default$|^go1($|\.)`) func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, args []string, fix VersionFixer, strict bool) { @@ -384,7 +385,7 @@ func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, a errorf("toolchain directive expects exactly one argument") return } else if strict && !ToolchainRE.MatchString(args[0]) { - errorf("invalid toolchain version '%s': must match format go1.23.0 or local", args[0]) + errorf("invalid toolchain version '%s': must match format go1.23.0 or default", args[0]) return } f.Toolchain = &Toolchain{Syntax: line} @@ -630,7 +631,7 @@ func (f *WorkFile) add(errs *ErrorList, line *Line, verb string, args []string, errorf("go directive expects exactly one argument") return } else if !GoVersionRE.MatchString(args[0]) { - errorf("invalid go version '%s': must match format 1.23", args[0]) + errorf("invalid go version '%s': must match format 1.23.0", args[0]) return } @@ -646,7 +647,7 @@ func (f *WorkFile) add(errs *ErrorList, line *Line, verb string, args []string, errorf("toolchain directive expects exactly one argument") return } else if !ToolchainRE.MatchString(args[0]) { - errorf("invalid toolchain version '%s': must match format go1.23 or local", args[0]) + errorf("invalid toolchain version '%s': must match format go1.23.0 or default", args[0]) return } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/arches_go118.go b/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/arches_go118.go deleted file mode 100644 index d8211afdc8..0000000000 --- a/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/arches_go118.go +++ /dev/null @@ -1,12 +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 -// +build !go1.19 - -package asmdecl - -func additionalArches() []*asmArch { - return nil -} diff --git a/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/arches_go119.go b/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/arches_go119.go deleted file mode 100644 index 3018383e7f..0000000000 --- a/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/arches_go119.go +++ /dev/null @@ -1,14 +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 -// +build go1.19 - -package asmdecl - -var asmArchLoong64 = asmArch{name: "loong64", bigEndian: false, stack: "R3", lr: true} - -func additionalArches() []*asmArch { - return []*asmArch{&asmArchLoong64} -} diff --git a/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/asmdecl.go b/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/asmdecl.go index e24dac9865..f2ca95aa9e 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/asmdecl.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/asmdecl.go @@ -96,6 +96,7 @@ var ( asmArchRISCV64 = asmArch{name: "riscv64", bigEndian: false, stack: "SP", lr: true, retRegs: []string{"X10", "F10"}} asmArchS390X = asmArch{name: "s390x", bigEndian: true, stack: "R15", lr: true} asmArchWasm = asmArch{name: "wasm", bigEndian: false, stack: "SP", lr: false} + asmArchLoong64 = asmArch{name: "loong64", bigEndian: false, stack: "R3", lr: true} arches = []*asmArch{ &asmArch386, @@ -111,11 +112,11 @@ var ( &asmArchRISCV64, &asmArchS390X, &asmArchWasm, + &asmArchLoong64, } ) func init() { - arches = append(arches, additionalArches()...) for _, arch := range arches { arch.sizes = types.SizesFor("gc", arch.name) if arch.sizes == nil { 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 847063bb32..6b126f897d 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 @@ -15,6 +15,7 @@ import ( "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/internal/aliases" "golang.org/x/tools/internal/typeparams" ) @@ -71,7 +72,7 @@ func run(pass *analysis.Pass) (interface{}, error) { return } var structuralTypes []types.Type - switch typ := typ.(type) { + switch typ := aliases.Unalias(typ).(type) { case *types.TypeParam: terms, err := typeparams.StructuralTerms(typ) if err != nil { @@ -84,7 +85,8 @@ func run(pass *analysis.Pass) (interface{}, error) { structuralTypes = append(structuralTypes, typ) } for _, typ := range structuralTypes { - under := deref(typ.Underlying()) + // TODO(adonovan): this operation is questionable. + under := aliases.Unalias(deref(typ.Underlying())) strct, ok := under.(*types.Struct) if !ok { // skip non-struct composite literals @@ -142,9 +144,11 @@ func run(pass *analysis.Pass) (interface{}, error) { return nil, nil } +// Note: this is not the usual deref operator! +// It strips off all Pointer constructors (and their Aliases). func deref(typ types.Type) types.Type { for { - ptr, ok := typ.(*types.Pointer) + ptr, ok := aliases.Unalias(typ).(*types.Pointer) if !ok { break } @@ -153,18 +157,18 @@ func deref(typ types.Type) types.Type { return typ } +// isLocalType reports whether typ belongs to the same package as pass. +// TODO(adonovan): local means "internal to a function"; rename to isSamePackageType. func isLocalType(pass *analysis.Pass, typ types.Type) bool { - switch x := typ.(type) { + switch x := aliases.Unalias(typ).(type) { case *types.Struct: // struct literals are local types return true case *types.Pointer: return isLocalType(pass, x.Elem()) - case *types.Named: + case interface{ Obj() *types.TypeName }: // *Named or *TypeParam (aliases were removed already) // 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 *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 6cbbc7e814..8f39159c0f 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 @@ -18,6 +18,7 @@ import ( "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/aliases" "golang.org/x/tools/internal/typeparams" ) @@ -255,7 +256,7 @@ func lockPath(tpkg *types.Package, typ types.Type, seen map[types.Type]bool) typ } seen[typ] = true - if tpar, ok := typ.(*types.TypeParam); ok { + if tpar, ok := aliases.Unalias(typ).(*types.TypeParam); ok { terms, err := typeparams.StructuralTerms(tpar) if err != nil { return nil // invalid type 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 5e17bd1ab9..95cd9a061e 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 @@ -102,8 +102,7 @@ func containsError(typ types.Type) bool { return true } } - case *types.Named, - *aliases.Alias: + 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/httpresponse/httpresponse.go b/vendor/golang.org/x/tools/go/analysis/passes/httpresponse/httpresponse.go index c6b6c81b42..047ae07cca 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 @@ -14,6 +14,8 @@ 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/typesinternal" ) const Doc = `check for mistakes using HTTP responses @@ -116,7 +118,8 @@ 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 || !analysisutil.IsNamedType(ptr.Elem(), "net/http", "Response") { + isPtr, named := typesinternal.ReceiverNamed(res.At(0)) + if !isPtr || !analysisutil.IsNamedType(named, "net/http", "Response") { return false // the first return type is not *http.Response. } @@ -134,7 +137,7 @@ func isHTTPFuncOrMethodOnClient(info *types.Info, expr *ast.CallExpr) bool { if analysisutil.IsNamedType(typ, "net/http", "Client") { return true // method on http.Client. } - ptr, ok := typ.(*types.Pointer) + ptr, ok := aliases.Unalias(typ).(*types.Pointer) return ok && analysisutil.IsNamedType(ptr.Elem(), "net/http", "Client") // method on *http.Client. } 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 12507f9967..a077d44024 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 @@ -7,6 +7,7 @@ package ifaceassert import ( "go/types" + "golang.org/x/tools/internal/aliases" "golang.org/x/tools/internal/typeparams" ) @@ -94,6 +95,10 @@ func (w *tpWalker) isParameterized(typ types.Type) (res bool) { case *types.Chan: return w.isParameterized(t.Elem()) + case *aliases.Alias: + // TODO(adonovan): think about generic aliases. + return w.isParameterized(aliases.Unalias(t)) + case *types.Named: list := t.TypeArgs() for i, n := 0, list.Len(); i < n; i++ { 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 3f01b3b55d..89291602a5 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 @@ -14,6 +14,7 @@ import ( "go/types" "os" + "golang.org/x/tools/internal/aliases" "golang.org/x/tools/internal/analysisinternal" ) @@ -115,7 +116,7 @@ func Imports(pkg *types.Package, path string) bool { // 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) + n, ok := aliases.Unalias(t).(*types.Named) if !ok { return false } 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 4724c9f3b1..fe05eda44e 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/typesinternal" "golang.org/x/tools/internal/versions" ) @@ -54,9 +55,8 @@ func run(pass *analysis.Pass) (interface{}, error) { 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 + goversion := versions.FileVersion(pass.TypesInfo, n) + return versions.Before(goversion, versions.Go1_22) case *ast.RangeStmt: body = n.Body addVar(n.Key) @@ -367,9 +367,6 @@ func isMethodCall(info *types.Info, expr ast.Expr, pkgPath, typeName, method str // Check that the receiver is a . or // *.. - rtype := recv.Type() - if ptr, ok := recv.Type().(*types.Pointer); ok { - rtype = ptr.Elem() - } - return analysisutil.IsNamedType(rtype, pkgPath, typeName) + _, named := typesinternal.ReceiverNamed(recv) + return analysisutil.IsNamedType(named, pkgPath, typeName) } 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 5e14c096ab..774f04c94a 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 @@ -52,7 +52,7 @@ func runFunc(pass *analysis.Pass, fn *ssa.Function) { // notNil reports an error if v is provably nil. notNil := func(stack []fact, instr ssa.Instruction, v ssa.Value, descr string) { if nilnessOf(stack, v) == isnil { - reportf("nilderef", instr.Pos(), "nil dereference in "+descr) + reportf("nilderef", instr.Pos(), descr) } } @@ -77,29 +77,50 @@ func runFunc(pass *analysis.Pass, fn *ssa.Function) { // A nil receiver may be okay for type params. cc := instr.Common() if !(cc.IsInvoke() && typeparams.IsTypeParam(cc.Value.Type())) { - notNil(stack, instr, cc.Value, cc.Description()) + notNil(stack, instr, cc.Value, "nil dereference in "+cc.Description()) } case *ssa.FieldAddr: - notNil(stack, instr, instr.X, "field selection") + notNil(stack, instr, instr.X, "nil dereference in field selection") case *ssa.IndexAddr: - notNil(stack, instr, instr.X, "index operation") + switch typeparams.CoreType(instr.X.Type()).(type) { + case *types.Pointer: // *array + notNil(stack, instr, instr.X, "nil dereference in array index operation") + case *types.Slice: + // This is not necessarily a runtime error, because + // it is usually dominated by a bounds check. + if isRangeIndex(instr) { + notNil(stack, instr, instr.X, "range of nil slice") + } else { + notNil(stack, instr, instr.X, "index of nil slice") + } + } case *ssa.MapUpdate: - notNil(stack, instr, instr.Map, "map update") + notNil(stack, instr, instr.Map, "nil dereference in map update") + case *ssa.Range: + // (Not a runtime error, but a likely mistake.) + notNil(stack, instr, instr.X, "range over nil map") case *ssa.Slice: // A nilcheck occurs in ptr[:] iff ptr is a pointer to an array. - if _, ok := instr.X.Type().Underlying().(*types.Pointer); ok { - notNil(stack, instr, instr.X, "slice operation") + if is[*types.Pointer](instr.X.Type().Underlying()) { + notNil(stack, instr, instr.X, "nil dereference in slice operation") } case *ssa.Store: - notNil(stack, instr, instr.Addr, "store") + notNil(stack, instr, instr.Addr, "nil dereference in store") case *ssa.TypeAssert: if !instr.CommaOk { - notNil(stack, instr, instr.X, "type assertion") + notNil(stack, instr, instr.X, "nil dereference in type assertion") } case *ssa.UnOp: - if instr.Op == token.MUL { // *X - notNil(stack, instr, instr.X, "load") + switch instr.Op { + case token.MUL: // *X + notNil(stack, instr, instr.X, "nil dereference in load") + case token.ARROW: // <-ch + // (Not a runtime error, but a likely mistake.) + notNil(stack, instr, instr.X, "receive from nil channel") } + case *ssa.Send: + // (Not a runtime error, but a likely mistake.) + notNil(stack, instr, instr.Chan, "send to nil channel") } } @@ -416,3 +437,39 @@ func isNillable(t types.Type) bool { } return false } + +// isRangeIndex reports whether the instruction is a slice indexing +// operation slice[i] within a "for range slice" loop. The operation +// could be explicit, such as slice[i] within (or even after) the +// loop, or it could be implicit, such as "for i, v := range slice {}". +// (These cannot be reliably distinguished.) +func isRangeIndex(instr *ssa.IndexAddr) bool { + // Here we reverse-engineer the go/ssa lowering of range-over-slice: + // + // n = len(x) + // jump loop + // loop: "rangeindex.loop" + // phi = φ(-1, incr) #rangeindex + // incr = phi + 1 + // cond = incr < n + // if cond goto body else done + // body: "rangeindex.body" + // instr = &x[incr] + // ... + // done: + if incr, ok := instr.Index.(*ssa.BinOp); ok && incr.Op == token.ADD { + if b := incr.Block(); b.Comment == "rangeindex.loop" { + if If, ok := b.Instrs[len(b.Instrs)-1].(*ssa.If); ok { + if cond := If.Cond.(*ssa.BinOp); cond.X == incr && cond.Op == token.LSS { + if call, ok := cond.Y.(*ssa.Call); ok { + common := call.Common() + if blt, ok := common.Value.(*ssa.Builtin); ok && blt.Name() == "len" { + return common.Args[0] == instr.X + } + } + } + } + } + } + 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 070654f012..3235019258 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 @@ -24,6 +24,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" "golang.org/x/tools/internal/typeparams" ) @@ -959,6 +960,8 @@ func isStringer(sig *types.Signature) bool { // It is almost always a mistake to print a function value. func isFunctionValue(pass *analysis.Pass, e ast.Expr) bool { if typ := pass.TypesInfo.Types[e].Type; typ != nil { + // Don't call Underlying: a named func type with a String method is ok. + // TODO(adonovan): it would be more precise to check isStringer. _, ok := typ.(*types.Signature) return ok } @@ -1010,7 +1013,7 @@ func checkPrint(pass *analysis.Pass, call *ast.CallExpr, fn *types.Func) { // Skip checking functions with unknown type. return } - if sig, ok := typ.(*types.Signature); ok { + if sig, ok := typ.Underlying().(*types.Signature); ok { if !sig.Variadic() { // Skip checking non-variadic functions. return @@ -1020,7 +1023,7 @@ func checkPrint(pass *analysis.Pass, call *ast.CallExpr, fn *types.Func) { typ := params.At(firstArg).Type() typ = typ.(*types.Slice).Elem() - it, ok := typ.(*types.Interface) + it, ok := aliases.Unalias(typ).(*types.Interface) if !ok || !it.Empty() { // Skip variadic functions accepting non-interface{} args. return 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 ab98e56998..017c8a247e 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 @@ -10,6 +10,7 @@ import ( "go/types" "golang.org/x/tools/go/analysis" + "golang.org/x/tools/internal/aliases" "golang.org/x/tools/internal/typeparams" ) @@ -72,7 +73,7 @@ func (m *argMatcher) match(typ types.Type, topLevel bool) bool { return true } - if typ, _ := typ.(*types.TypeParam); typ != nil { + if typ, _ := aliases.Unalias(typ).(*types.TypeParam); typ != nil { // Avoid infinite recursion through type parameters. if m.seen[typ] { return true @@ -275,7 +276,7 @@ func (m *argMatcher) match(typ types.Type, topLevel bool) bool { } func isConvertibleToString(typ types.Type) bool { - if bt, ok := typ.(*types.Basic); ok && bt.Kind() == types.UntypedNil { + if bt, ok := aliases.Unalias(typ).(*types.Basic); ok && bt.Kind() == types.UntypedNil { // We explicitly don't want untyped nil, which is // convertible to both of the interfaces below, as it // would just panic anyway. 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 e272df709f..d01eb1eebe 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 @@ -21,6 +21,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" ) @@ -89,7 +90,8 @@ func checkLongShift(pass *analysis.Pass, node ast.Node, x, y ast.Expr) { if v == nil { return } - amt, ok := constant.Int64Val(v) + u := constant.ToInt(v) // either an Int or Unknown + amt, ok := constant.Int64Val(u) if !ok { return } @@ -98,7 +100,7 @@ func checkLongShift(pass *analysis.Pass, node ast.Node, x, y ast.Expr) { return } var structuralTypes []types.Type - switch t := t.(type) { + switch t := aliases.Unalias(t).(type) { case *types.TypeParam: terms, err := typeparams.StructuralTerms(t) if err != nil { 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 a1323c3e66..b3c683b61c 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 @@ -20,6 +20,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/typesinternal" ) //go:embed doc.go @@ -150,14 +151,10 @@ func isAttr(t types.Type) bool { func shortName(fn *types.Func) string { var r string if recv := fn.Type().(*types.Signature).Recv(); recv != nil { - t := recv.Type() - if pt, ok := t.(*types.Pointer); ok { - t = pt.Elem() - } - if nt, ok := t.(*types.Named); ok { - r = nt.Obj().Name() + if _, named := typesinternal.ReceiverNamed(recv); named != nil { + r = named.Obj().Name() } else { - r = recv.Type().String() + r = recv.Type().String() // anon struct/interface } r += "." } @@ -173,17 +170,12 @@ func kvFuncSkipArgs(fn *types.Func) (int, bool) { return 0, false } var recvName string // by default a slog package function - recv := fn.Type().(*types.Signature).Recv() - if recv != nil { - t := recv.Type() - if pt, ok := t.(*types.Pointer); ok { - t = pt.Elem() - } - if nt, ok := t.(*types.Named); !ok { - return 0, false - } else { - recvName = nt.Obj().Name() + if recv := fn.Type().(*types.Signature).Recv(); recv != nil { + _, named := typesinternal.ReceiverNamed(recv) + if named == nil { + return 0, false // anon struct/interface } + recvName = named.Obj().Name() } skip, ok := kvFuncs[recvName][fn.Name()] return skip, ok 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 005e2e54b7..16a4b3e551 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 @@ -60,10 +60,12 @@ func describe(typ, inType types.Type, inName string) string { } func typeName(typ types.Type) string { - if v, _ := typ.(interface{ Name() string }); v != nil { + typ = aliases.Unalias(typ) + // TODO(adonovan): don't discard alias type, return its name. + if v, _ := typ.(*types.Basic); v != nil { return v.Name() } - if v, _ := typ.(interface{ Obj() *types.TypeName }); v != nil { + if v, _ := typ.(interface{ Obj() *types.TypeName }); v != nil { // Named, TypeParam return v.Obj().Name() } return "" 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 dc5307a15d..828f95bc86 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 @@ -17,6 +17,7 @@ import ( "golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/types/typeutil" + "golang.org/x/tools/internal/aliases" ) //go:embed doc.go @@ -270,7 +271,7 @@ func forbiddenMethod(info *types.Info, call *ast.CallExpr) (*types.Var, *types.S func formatMethod(sel *types.Selection, fn *types.Func) string { var ptr string rtype := sel.Recv() - if p, ok := rtype.(*types.Pointer); ok { + if p, ok := aliases.Unalias(rtype).(*types.Pointer); ok { ptr = "*" rtype = p.Elem() } 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 index d156851db1..ad815f1901 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/util.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/util.go @@ -30,7 +30,7 @@ func localFunctionDecls(info *types.Info, files []*ast.File) func(*types.Func) * } } } - // TODO: once we only support go1.19+, set f = f.Origin() here. + // TODO: set f = f.Origin() here. return fnDecls[f] } } 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 6db12f3cb9..39d0d9e429 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 @@ -252,6 +252,8 @@ func validateFuzzArgs(pass *analysis.Pass, params *types.Tuple, expr ast.Expr) b } func isTestingType(typ types.Type, testingType string) bool { + // No Unalias here: I doubt "go test" recognizes + // "type A = *testing.T; func Test(A) {}" as a test. ptr, ok := typ.(*types.Pointer) if !ok { return false 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 eb84502bd9..4a6c6b8bc6 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 @@ -107,7 +107,7 @@ func badFormatAt(info *types.Info, e ast.Expr) int { return -1 } - t, ok := tv.Type.(*types.Basic) + t, ok := tv.Type.(*types.Basic) // sic, no unalias if !ok || t.Info()&types.IsString == 0 { return -1 } 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 f4e73528b4..a7889fa459 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,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/typesinternal" ) //go:embed doc.go @@ -69,12 +70,8 @@ func run(pass *analysis.Pass) (interface{}, error) { // (*"encoding/json".Decoder).Decode // (* "encoding/gob".Decoder).Decode // (* "encoding/xml".Decoder).Decode - t := recv.Type() - if ptr, ok := t.(*types.Pointer); ok { - t = ptr.Elem() - } - tname := t.(*types.Named).Obj() - if tname.Name() == "Decoder" { + _, named := typesinternal.ReceiverNamed(recv) + if tname := named.Obj(); tname.Name() == "Decoder" { switch tname.Pkg().Path() { case "encoding/json", "encoding/xml", "encoding/gob": argidx = 0 // func(interface{}) 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 32e71ef979..14e4a6c1e4 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 @@ -17,6 +17,7 @@ import ( "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/aliases" ) //go:embed doc.go @@ -88,7 +89,7 @@ func isSafeUintptr(info *types.Info, x ast.Expr) bool { // by the time we get to the conversion at the end. // For now approximate by saying that *Header is okay // but Header is not. - pt, ok := info.Types[x.X].Type.(*types.Pointer) + pt, ok := aliases.Unalias(info.Types[x.X].Type).(*types.Pointer) if ok && isReflectHeader(pt.Elem()) { return true } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/unusedwrite/unusedwrite.go b/vendor/golang.org/x/tools/go/analysis/passes/unusedwrite/unusedwrite.go index f5d0f116ca..a01cbb8f83 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/unusedwrite/unusedwrite.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/unusedwrite/unusedwrite.go @@ -13,6 +13,7 @@ import ( "golang.org/x/tools/go/analysis/passes/buildssa" "golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/ssa" + "golang.org/x/tools/internal/aliases" ) //go:embed doc.go @@ -124,10 +125,7 @@ func isDeadStore(store *ssa.Store, obj ssa.Value, addr ssa.Instruction) bool { // isStructOrArray returns whether the underlying type is struct or array. func isStructOrArray(tp types.Type) bool { - if named, ok := tp.(*types.Named); ok { - tp = named.Underlying() - } - switch tp.(type) { + switch tp.Underlying().(type) { case *types.Array: return true case *types.Struct: @@ -145,7 +143,7 @@ func hasStructOrArrayType(v ssa.Value) bool { // func (t T) f() { ...} // the receiver object is of type *T: // t0 = local T (t) *T - if tp, ok := alloc.Type().(*types.Pointer); ok { + if tp, ok := aliases.Unalias(alloc.Type()).(*types.Pointer); ok { return isStructOrArray(tp.Elem()) } return false @@ -159,13 +157,14 @@ func hasStructOrArrayType(v ssa.Value) bool { // // For example, for struct T {x int, y int), getFieldName(*T, 1) returns "y". func getFieldName(tp types.Type, index int) string { - if pt, ok := tp.(*types.Pointer); ok { + // TODO(adonovan): use + // stp, ok := typeparams.Deref(tp).Underlying().(*types.Struct); ok { + // when Deref is defined. But see CL 565456 for a better fix. + + if pt, ok := aliases.Unalias(tp).(*types.Pointer); ok { tp = pt.Elem() } - if named, ok := tp.(*types.Named); ok { - tp = named.Underlying() - } - if stp, ok := tp.(*types.Struct); ok { + if stp, ok := tp.Underlying().(*types.Struct); ok { return stp.Field(index).Name() } return fmt.Sprintf("%d", index) diff --git a/vendor/golang.org/x/tools/go/cfg/builder.go b/vendor/golang.org/x/tools/go/cfg/builder.go index dad6a444d8..ac4d63c400 100644 --- a/vendor/golang.org/x/tools/go/cfg/builder.go +++ b/vendor/golang.org/x/tools/go/cfg/builder.go @@ -16,8 +16,8 @@ type builder struct { cfg *CFG mayReturn func(*ast.CallExpr) bool current *Block - lblocks map[*ast.Object]*lblock // labeled blocks - targets *targets // linked stack of branch targets + lblocks map[string]*lblock // labeled blocks + targets *targets // linked stack of branch targets } func (b *builder) stmt(_s ast.Stmt) { @@ -42,7 +42,7 @@ start: b.add(s) if call, ok := s.X.(*ast.CallExpr); ok && !b.mayReturn(call) { // Calls to panic, os.Exit, etc, never return. - b.current = b.newBlock("unreachable.call") + b.current = b.newBlock(KindUnreachable, s) } case *ast.DeclStmt: @@ -57,7 +57,7 @@ start: } case *ast.LabeledStmt: - label = b.labeledBlock(s.Label) + label = b.labeledBlock(s.Label, s) b.jump(label._goto) b.current = label._goto _s = s.Stmt @@ -65,7 +65,7 @@ start: case *ast.ReturnStmt: b.add(s) - b.current = b.newBlock("unreachable.return") + b.current = b.newBlock(KindUnreachable, s) case *ast.BranchStmt: b.branchStmt(s) @@ -77,11 +77,11 @@ start: if s.Init != nil { b.stmt(s.Init) } - then := b.newBlock("if.then") - done := b.newBlock("if.done") + then := b.newBlock(KindIfThen, s) + done := b.newBlock(KindIfDone, s) _else := done if s.Else != nil { - _else = b.newBlock("if.else") + _else = b.newBlock(KindIfElse, s) } b.add(s.Cond) b.ifelse(then, _else) @@ -128,7 +128,7 @@ func (b *builder) branchStmt(s *ast.BranchStmt) { switch s.Tok { case token.BREAK: if s.Label != nil { - if lb := b.labeledBlock(s.Label); lb != nil { + if lb := b.labeledBlock(s.Label, nil); lb != nil { block = lb._break } } else { @@ -139,7 +139,7 @@ func (b *builder) branchStmt(s *ast.BranchStmt) { case token.CONTINUE: if s.Label != nil { - if lb := b.labeledBlock(s.Label); lb != nil { + if lb := b.labeledBlock(s.Label, nil); lb != nil { block = lb._continue } } else { @@ -155,14 +155,14 @@ func (b *builder) branchStmt(s *ast.BranchStmt) { case token.GOTO: if s.Label != nil { - block = b.labeledBlock(s.Label)._goto + block = b.labeledBlock(s.Label, nil)._goto } } - if block == nil { - block = b.newBlock("undefined.branch") + if block == nil { // ill-typed (e.g. undefined label) + block = b.newBlock(KindUnreachable, s) } b.jump(block) - b.current = b.newBlock("unreachable.branch") + b.current = b.newBlock(KindUnreachable, s) } func (b *builder) switchStmt(s *ast.SwitchStmt, label *lblock) { @@ -172,7 +172,7 @@ func (b *builder) switchStmt(s *ast.SwitchStmt, label *lblock) { if s.Tag != nil { b.add(s.Tag) } - done := b.newBlock("switch.done") + done := b.newBlock(KindSwitchDone, s) if label != nil { label._break = done } @@ -188,13 +188,13 @@ func (b *builder) switchStmt(s *ast.SwitchStmt, label *lblock) { for i, clause := range s.Body.List { body := fallthru if body == nil { - body = b.newBlock("switch.body") // first case only + body = b.newBlock(KindSwitchCaseBody, clause) // first case only } // Preallocate body block for the next case. fallthru = done if i+1 < ncases { - fallthru = b.newBlock("switch.body") + fallthru = b.newBlock(KindSwitchCaseBody, s.Body.List[i+1]) } cc := clause.(*ast.CaseClause) @@ -208,7 +208,7 @@ func (b *builder) switchStmt(s *ast.SwitchStmt, label *lblock) { var nextCond *Block for _, cond := range cc.List { - nextCond = b.newBlock("switch.next") + nextCond = b.newBlock(KindSwitchNextCase, cc) b.add(cond) // one half of the tag==cond condition b.ifelse(body, nextCond) b.current = nextCond @@ -247,7 +247,7 @@ func (b *builder) typeSwitchStmt(s *ast.TypeSwitchStmt, label *lblock) { b.add(s.Assign) } - done := b.newBlock("typeswitch.done") + done := b.newBlock(KindSwitchDone, s) if label != nil { label._break = done } @@ -258,10 +258,10 @@ func (b *builder) typeSwitchStmt(s *ast.TypeSwitchStmt, label *lblock) { default_ = cc continue } - body := b.newBlock("typeswitch.body") + body := b.newBlock(KindSwitchCaseBody, cc) var next *Block for _, casetype := range cc.List { - next = b.newBlock("typeswitch.next") + next = b.newBlock(KindSwitchNextCase, cc) // casetype is a type, so don't call b.add(casetype). // This block logically contains a type assertion, // x.(casetype), but it's unclear how to represent x. @@ -300,7 +300,7 @@ func (b *builder) selectStmt(s *ast.SelectStmt, label *lblock) { } } - done := b.newBlock("select.done") + done := b.newBlock(KindSelectDone, s) if label != nil { label._break = done } @@ -312,8 +312,8 @@ func (b *builder) selectStmt(s *ast.SelectStmt, label *lblock) { defaultBody = &clause.Body continue } - body := b.newBlock("select.body") - next := b.newBlock("select.next") + body := b.newBlock(KindSelectCaseBody, clause) + next := b.newBlock(KindSelectAfterCase, clause) b.ifelse(body, next) b.current = body b.targets = &targets{ @@ -358,15 +358,15 @@ func (b *builder) forStmt(s *ast.ForStmt, label *lblock) { if s.Init != nil { b.stmt(s.Init) } - body := b.newBlock("for.body") - done := b.newBlock("for.done") // target of 'break' - loop := body // target of back-edge + body := b.newBlock(KindForBody, s) + done := b.newBlock(KindForDone, s) // target of 'break' + loop := body // target of back-edge if s.Cond != nil { - loop = b.newBlock("for.loop") + loop = b.newBlock(KindForLoop, s) } cont := loop // target of 'continue' if s.Post != nil { - cont = b.newBlock("for.post") + cont = b.newBlock(KindForPost, s) } if label != nil { label._break = done @@ -414,12 +414,12 @@ func (b *builder) rangeStmt(s *ast.RangeStmt, label *lblock) { // jump loop // done: (target of break) - loop := b.newBlock("range.loop") + loop := b.newBlock(KindRangeLoop, s) b.jump(loop) b.current = loop - body := b.newBlock("range.body") - done := b.newBlock("range.done") + body := b.newBlock(KindRangeBody, s) + done := b.newBlock(KindRangeDone, s) b.ifelse(body, done) b.current = body @@ -461,14 +461,19 @@ type lblock struct { // labeledBlock returns the branch target associated with the // specified label, creating it if needed. -func (b *builder) labeledBlock(label *ast.Ident) *lblock { - lb := b.lblocks[label.Obj] +func (b *builder) labeledBlock(label *ast.Ident, stmt *ast.LabeledStmt) *lblock { + lb := b.lblocks[label.Name] if lb == nil { - lb = &lblock{_goto: b.newBlock(label.Name)} + lb = &lblock{_goto: b.newBlock(KindLabel, nil)} if b.lblocks == nil { - b.lblocks = make(map[*ast.Object]*lblock) + b.lblocks = make(map[string]*lblock) } - b.lblocks[label.Obj] = lb + b.lblocks[label.Name] = lb + } + // Fill in the label later (in case of forward goto). + // Stmt may be set already if labels are duplicated (ill-typed). + if stmt != nil && lb._goto.Stmt == nil { + lb._goto.Stmt = stmt } return lb } @@ -477,11 +482,12 @@ func (b *builder) labeledBlock(label *ast.Ident) *lblock { // slice and returns it. // It does not automatically become the current block. // comment is an optional string for more readable debugging output. -func (b *builder) newBlock(comment string) *Block { +func (b *builder) newBlock(kind BlockKind, stmt ast.Stmt) *Block { g := b.cfg block := &Block{ - Index: int32(len(g.Blocks)), - comment: comment, + Index: int32(len(g.Blocks)), + Kind: kind, + Stmt: stmt, } block.Succs = block.succs2[:0] g.Blocks = append(g.Blocks, block) diff --git a/vendor/golang.org/x/tools/go/cfg/cfg.go b/vendor/golang.org/x/tools/go/cfg/cfg.go index e9c48d51da..01668359af 100644 --- a/vendor/golang.org/x/tools/go/cfg/cfg.go +++ b/vendor/golang.org/x/tools/go/cfg/cfg.go @@ -9,7 +9,10 @@ // // The blocks of the CFG contain all the function's non-control // statements. The CFG does not contain control statements such as If, -// Switch, Select, and Branch, but does contain their subexpressions. +// Switch, Select, and Branch, but does contain their subexpressions; +// also, each block records the control statement (Block.Stmt) that +// gave rise to it and its relationship (Block.Kind) to that statement. +// // For example, this source code: // // if x := f(); x != nil { @@ -20,14 +23,14 @@ // // produces this CFG: // -// 1: x := f() +// 1: x := f() Body // x != nil // succs: 2, 3 -// 2: T() +// 2: T() IfThen // succs: 4 -// 3: F() +// 3: F() IfElse // succs: 4 -// 4: +// 4: IfDone // // The CFG does contain Return statements; even implicit returns are // materialized (at the position of the function's closing brace). @@ -50,6 +53,7 @@ import ( // // The entry point is Blocks[0]; there may be multiple return blocks. type CFG struct { + fset *token.FileSet Blocks []*Block // block[0] is entry; order otherwise undefined } @@ -64,9 +68,63 @@ type Block struct { Succs []*Block // successor nodes in the graph Index int32 // index within CFG.Blocks Live bool // block is reachable from entry + Kind BlockKind // block kind + Stmt ast.Stmt // statement that gave rise to this block (see BlockKind for details) - comment string // for debugging - succs2 [2]*Block // underlying array for Succs + succs2 [2]*Block // underlying array for Succs +} + +// A BlockKind identifies the purpose of a block. +// It also determines the possible types of its Stmt field. +type BlockKind uint8 + +const ( + KindInvalid BlockKind = iota // Stmt=nil + + KindUnreachable // unreachable block after {Branch,Return}Stmt / no-return call ExprStmt + KindBody // function body BlockStmt + KindForBody // body of ForStmt + KindForDone // block after ForStmt + KindForLoop // head of ForStmt + KindForPost // post condition of ForStmt + KindIfDone // block after IfStmt + KindIfElse // else block of IfStmt + KindIfThen // then block of IfStmt + KindLabel // labeled block of BranchStmt (Stmt may be nil for dangling label) + KindRangeBody // body of RangeStmt + KindRangeDone // block after RangeStmt + KindRangeLoop // head of RangeStmt + KindSelectCaseBody // body of SelectStmt + KindSelectDone // block after SelectStmt + KindSelectAfterCase // block after a CommClause + KindSwitchCaseBody // body of CaseClause + KindSwitchDone // block after {Type.}SwitchStmt + KindSwitchNextCase // secondary expression of a multi-expression CaseClause +) + +func (kind BlockKind) String() string { + return [...]string{ + KindInvalid: "Invalid", + KindUnreachable: "Unreachable", + KindBody: "Body", + KindForBody: "ForBody", + KindForDone: "ForDone", + KindForLoop: "ForLoop", + KindForPost: "ForPost", + KindIfDone: "IfDone", + KindIfElse: "IfElse", + KindIfThen: "IfThen", + KindLabel: "Label", + KindRangeBody: "RangeBody", + KindRangeDone: "RangeDone", + KindRangeLoop: "RangeLoop", + KindSelectCaseBody: "SelectCaseBody", + KindSelectDone: "SelectDone", + KindSelectAfterCase: "SelectAfterCase", + KindSwitchCaseBody: "SwitchCaseBody", + KindSwitchDone: "SwitchDone", + KindSwitchNextCase: "SwitchNextCase", + }[kind] } // New returns a new control-flow graph for the specified function body, @@ -82,7 +140,7 @@ func New(body *ast.BlockStmt, mayReturn func(*ast.CallExpr) bool) *CFG { mayReturn: mayReturn, cfg: new(CFG), } - b.current = b.newBlock("entry") + b.current = b.newBlock(KindBody, body) b.stmt(body) // Compute liveness (reachability from entry point), breadth-first. @@ -110,7 +168,15 @@ func New(body *ast.BlockStmt, mayReturn func(*ast.CallExpr) bool) *CFG { } func (b *Block) String() string { - return fmt.Sprintf("block %d (%s)", b.Index, b.comment) + return fmt.Sprintf("block %d (%s)", b.Index, b.comment(nil)) +} + +func (b *Block) comment(fset *token.FileSet) string { + s := b.Kind.String() + if fset != nil && b.Stmt != nil { + s = fmt.Sprintf("%s@L%d", s, fset.Position(b.Stmt.Pos()).Line) + } + return s } // Return returns the return statement at the end of this block if present, nil @@ -129,7 +195,7 @@ func (b *Block) Return() (ret *ast.ReturnStmt) { func (g *CFG) Format(fset *token.FileSet) string { var buf bytes.Buffer for _, b := range g.Blocks { - fmt.Fprintf(&buf, ".%d: # %s\n", b.Index, b.comment) + fmt.Fprintf(&buf, ".%d: # %s\n", b.Index, b.comment(fset)) for _, n := range b.Nodes { fmt.Fprintf(&buf, "\t%s\n", formatNode(fset, n)) } @@ -145,6 +211,35 @@ func (g *CFG) Format(fset *token.FileSet) string { return buf.String() } +// digraph emits AT&T GraphViz (dot) syntax for the CFG. +// TODO(adonovan): publish; needs a proposal. +func (g *CFG) digraph(fset *token.FileSet) string { + var buf bytes.Buffer + buf.WriteString("digraph CFG {\n") + buf.WriteString(" node [shape=box];\n") + for _, b := range g.Blocks { + // node label + var text bytes.Buffer + text.WriteString(b.comment(fset)) + for _, n := range b.Nodes { + fmt.Fprintf(&text, "\n%s", formatNode(fset, n)) + } + + // node and edges + fmt.Fprintf(&buf, " n%d [label=%q];\n", b.Index, &text) + for _, succ := range b.Succs { + fmt.Fprintf(&buf, " n%d -> n%d;\n", b.Index, succ.Index) + } + } + buf.WriteString("}\n") + return buf.String() +} + +// exposed to main.go +func digraph(g *CFG, fset *token.FileSet) string { + return g.digraph(fset) +} + func formatNode(fset *token.FileSet, n ast.Node) string { var buf bytes.Buffer format.Node(&buf, fset, n) diff --git a/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go b/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go index 03543bd4bb..137cc8df1d 100644 --- a/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go +++ b/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go @@ -47,7 +47,7 @@ import ( func Find(importPath, srcDir string) (filename, path string) { cmd := exec.Command("go", "list", "-json", "-export", "--", importPath) cmd.Dir = srcDir - out, err := cmd.CombinedOutput() + out, err := cmd.Output() if err != nil { return "", "" } 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 b5bb95a63e..2455be54f6 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 @@ -15,12 +15,15 @@ import ( // pkgConfig runs pkg-config with the specified arguments and returns the flags it prints. func pkgConfig(mode string, pkgs []string) (flags []string, err error) { cmd := exec.Command("pkg-config", append([]string{mode}, pkgs...)...) - out, err := cmd.CombinedOutput() + out, err := cmd.Output() if err != nil { s := fmt.Sprintf("%s failed: %v", strings.Join(cmd.Args, " "), err) if len(out) > 0 { s = fmt.Sprintf("%s: %s", s, out) } + if err, ok := err.(*exec.ExitError); ok && len(err.Stderr) > 0 { + s = fmt.Sprintf("%s\nstderr:\n%s", s, err.Stderr) + } return nil, errors.New(s) } if len(out) > 0 { diff --git a/vendor/golang.org/x/tools/go/ssa/builder.go b/vendor/golang.org/x/tools/go/ssa/builder.go index 8622dfc53a..72e906c385 100644 --- a/vendor/golang.org/x/tools/go/ssa/builder.go +++ b/vendor/golang.org/x/tools/go/ssa/builder.go @@ -81,16 +81,15 @@ import ( "os" "sync" + "golang.org/x/tools/internal/aliases" "golang.org/x/tools/internal/typeparams" "golang.org/x/tools/internal/versions" ) -type opaqueType struct { - types.Type - name string -} +type opaqueType struct{ name string } -func (t *opaqueType) String() string { return t.name } +func (t *opaqueType) String() string { return t.name } +func (t *opaqueType) Underlying() types.Type { return t } var ( varOk = newVar("ok", tBool) @@ -103,7 +102,7 @@ var ( tInvalid = types.Typ[types.Invalid] tString = types.Typ[types.String] tUntypedNil = types.Typ[types.UntypedNil] - tRangeIter = &opaqueType{nil, "iter"} // the type of all "range" iterators + tRangeIter = &opaqueType{"iter"} // the type of all "range" iterators tEface = types.NewInterfaceType(nil, nil).Complete() // SSA Value constants. @@ -328,7 +327,7 @@ func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ } case "new": - return emitNew(fn, mustDeref(typ), pos, "new") + return emitNew(fn, typeparams.MustDeref(typ), pos, "new") case "len", "cap": // Special case: len or cap of an array or *array is @@ -419,7 +418,7 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue { wantAddr := true v := b.receiver(fn, e.X, wantAddr, escaping, sel) index := sel.index[len(sel.index)-1] - fld := fieldOf(mustDeref(v.Type()), index) // v is an addr. + fld := fieldOf(typeparams.MustDeref(v.Type()), index) // v is an addr. // Due to the two phases of resolving AssignStmt, a panic from x.f = p() // when x is nil is required to come after the side-effects of @@ -468,7 +467,7 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue { v.setType(et) return fn.emit(v) } - return &lazyAddress{addr: emit, t: mustDeref(et), pos: e.Lbrack, expr: e} + return &lazyAddress{addr: emit, t: typeparams.MustDeref(et), pos: e.Lbrack, expr: e} case *ast.StarExpr: return &address{addr: b.expr(fn, e.X), pos: e.Star, expr: e} @@ -802,7 +801,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.(*types.TypeParam); ok { + if recv, ok := aliases.Unalias(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 { @@ -1253,7 +1252,7 @@ func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero case *types.Array, *types.Slice: var at *types.Array var array Value - switch t := t.(type) { + switch t := aliases.Unalias(t).(type) { case *types.Slice: at = types.NewArray(t.Elem(), b.arrayLen(fn, e.Elts)) array = emitNew(fn, at, e.Lbrace, "slicelit") @@ -1748,8 +1747,7 @@ func (b *builder) forStmt(fn *Function, s *ast.ForStmt, label *lblock) { // 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 { + if versions.AtLeast(fn.goversion, versions.Go1_22) { b.forStmtGo122(fn, s, label) return } @@ -2244,7 +2242,7 @@ func (b *builder) rangeStmt(fn *Function, s *ast.RangeStmt, label *lblock) { } } - afterGo122 := versions.Compare(fn.goversion, "go1.21") > 0 + afterGo122 := versions.AtLeast(fn.goversion, versions.Go1_22) if s.Tok == token.DEFINE && !afterGo122 { // pre-go1.22: If iteration variables are defined (:=), this // occurs once outside the loop. diff --git a/vendor/golang.org/x/tools/go/ssa/const.go b/vendor/golang.org/x/tools/go/ssa/const.go index 2a6ac5882a..e0d79f5ef7 100644 --- a/vendor/golang.org/x/tools/go/ssa/const.go +++ b/vendor/golang.org/x/tools/go/ssa/const.go @@ -14,6 +14,7 @@ import ( "strconv" "strings" + "golang.org/x/tools/internal/aliases" "golang.org/x/tools/internal/typeparams" ) @@ -47,7 +48,7 @@ func soleTypeKind(typ types.Type) types.BasicInfo { state := types.IsBoolean | types.IsInteger | types.IsString underIs(typeSetOf(typ), func(t types.Type) bool { var c types.BasicInfo - if t, ok := t.(*types.Basic); ok { + if t, ok := aliases.Unalias(t).(*types.Basic); ok { c = t.Info() } if c&types.IsNumeric != 0 { // int/float/complex @@ -113,7 +114,7 @@ func zeroString(t types.Type, from *types.Package) string { } case *types.Pointer, *types.Slice, *types.Interface, *types.Chan, *types.Map, *types.Signature: return "nil" - case *types.Named: + case *types.Named, *aliases.Alias: return zeroString(t.Underlying(), from) case *types.Array, *types.Struct: return relType(t, from) + "{}" diff --git a/vendor/golang.org/x/tools/go/ssa/coretype.go b/vendor/golang.org/x/tools/go/ssa/coretype.go index 88136b4384..3a512830b1 100644 --- a/vendor/golang.org/x/tools/go/ssa/coretype.go +++ b/vendor/golang.org/x/tools/go/ssa/coretype.go @@ -7,6 +7,7 @@ package ssa import ( "go/types" + "golang.org/x/tools/internal/aliases" "golang.org/x/tools/internal/typeparams" ) @@ -49,7 +50,7 @@ func typeSetOf(typ types.Type) termList { // This is a adaptation of x/exp/typeparams.NormalTerms which x/tools cannot depend on. var terms []*types.Term var err error - switch typ := typ.(type) { + switch typ := aliases.Unalias(typ).(type) { case *types.TypeParam: terms, err = typeparams.StructuralTerms(typ) case *types.Union: diff --git a/vendor/golang.org/x/tools/go/ssa/create.go b/vendor/golang.org/x/tools/go/ssa/create.go index c4da35d0b0..f8f584a1a5 100644 --- a/vendor/golang.org/x/tools/go/ssa/create.go +++ b/vendor/golang.org/x/tools/go/ssa/create.go @@ -245,7 +245,7 @@ func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info * if len(files) > 0 { // Go source package. for _, file := range files { - goversion := versions.Lang(versions.FileVersions(p.info, file)) + goversion := versions.Lang(versions.FileVersion(p.info, file)) for _, decl := range file.Decls { membersFromDecl(p, decl, goversion) } @@ -259,6 +259,7 @@ func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info * obj := scope.Lookup(name) memberFromObject(p, obj, nil, "") if obj, ok := obj.(*types.TypeName); ok { + // No Unalias: aliases should not duplicate methods. if named, ok := obj.Type().(*types.Named); ok { for i, n := 0, named.NumMethods(); i < n; i++ { memberFromObject(p, named.Method(i), nil, "") diff --git a/vendor/golang.org/x/tools/go/ssa/emit.go b/vendor/golang.org/x/tools/go/ssa/emit.go index d77b4407a8..549c9114d4 100644 --- a/vendor/golang.org/x/tools/go/ssa/emit.go +++ b/vendor/golang.org/x/tools/go/ssa/emit.go @@ -11,6 +11,9 @@ import ( "go/ast" "go/token" "go/types" + + "golang.org/x/tools/internal/aliases" + "golang.org/x/tools/internal/typeparams" ) // emitAlloc emits to f a new Alloc instruction allocating a variable @@ -64,7 +67,7 @@ func emitLocalVar(f *Function, v *types.Var) *Alloc { // new temporary, and returns the value so defined. func emitLoad(f *Function, addr Value) *UnOp { v := &UnOp{Op: token.MUL, X: addr} - v.setType(mustDeref(addr.Type())) + v.setType(typeparams.MustDeref(addr.Type())) f.emit(v) return v } @@ -182,7 +185,7 @@ func emitCompare(f *Function, op token.Token, x, y Value, pos token.Pos) Value { // isValuePreserving returns true if a conversion from ut_src to // ut_dst is value-preserving, i.e. just a change of type. -// Precondition: neither argument is a named type. +// Precondition: neither argument is a named or alias type. func isValuePreserving(ut_src, ut_dst types.Type) bool { // Identical underlying types? if types.IdenticalIgnoreTags(ut_dst, ut_src) { @@ -281,11 +284,11 @@ func emitConv(f *Function, val Value, typ types.Type) Value { } // Conversion from slice to array or slice to array pointer? - if slice, ok := s.(*types.Slice); ok { + if slice, ok := aliases.Unalias(s).(*types.Slice); ok { var arr *types.Array var ptr bool // Conversion from slice to array pointer? - switch d := d.(type) { + switch d := aliases.Unalias(d).(type) { case *types.Array: arr = d case *types.Pointer: @@ -414,7 +417,7 @@ func emitTypeCoercion(f *Function, v Value, typ types.Type) Value { // emitStore emits to f an instruction to store value val at location // addr, applying implicit conversions as required by assignability rules. func emitStore(f *Function, addr, val Value, pos token.Pos) *Store { - typ := mustDeref(addr.Type()) + typ := typeparams.MustDeref(addr.Type()) s := &Store{ Addr: addr, Val: emitConv(f, val, typ), diff --git a/vendor/golang.org/x/tools/go/ssa/func.go b/vendor/golang.org/x/tools/go/ssa/func.go index 22f878d4ed..4d3e39129c 100644 --- a/vendor/golang.org/x/tools/go/ssa/func.go +++ b/vendor/golang.org/x/tools/go/ssa/func.go @@ -14,6 +14,8 @@ import ( "io" "os" "strings" + + "golang.org/x/tools/internal/typeparams" ) // Like ObjectOf, but panics instead of returning nil. @@ -531,7 +533,7 @@ func WriteFunction(buf *bytes.Buffer, f *Function) { if len(f.Locals) > 0 { buf.WriteString("# Locals:\n") for i, l := range f.Locals { - fmt.Fprintf(buf, "# % 3d:\t%s %s\n", i, l.Name(), relType(mustDeref(l.Type()), from)) + fmt.Fprintf(buf, "# % 3d:\t%s %s\n", i, l.Name(), relType(typeparams.MustDeref(l.Type()), from)) } } writeSignature(buf, from, f.Name(), f.Signature) @@ -586,6 +588,12 @@ func WriteFunction(buf *bytes.Buffer, f *Function) { default: buf.WriteString(instr.String()) } + // -mode=S: show line numbers + if f.Prog.mode&LogSource != 0 { + if pos := instr.Pos(); pos.IsValid() { + fmt.Fprintf(buf, " L%d", f.Prog.Fset.Position(pos).Line) + } + } buf.WriteString("\n") } } diff --git a/vendor/golang.org/x/tools/go/ssa/lift.go b/vendor/golang.org/x/tools/go/ssa/lift.go index da49fe9f17..8bb1949449 100644 --- a/vendor/golang.org/x/tools/go/ssa/lift.go +++ b/vendor/golang.org/x/tools/go/ssa/lift.go @@ -43,6 +43,8 @@ import ( "go/token" "math/big" "os" + + "golang.org/x/tools/internal/typeparams" ) // If true, show diagnostic information at each step of lifting. @@ -465,7 +467,7 @@ func liftAlloc(df domFrontier, alloc *Alloc, newPhis newPhiMap, fresh *int) bool *fresh++ phi.pos = alloc.Pos() - phi.setType(mustDeref(alloc.Type())) + phi.setType(typeparams.MustDeref(alloc.Type())) phi.block = v if debugLifting { fmt.Fprintf(os.Stderr, "\tplace %s = %s at block %s\n", phi.Name(), phi, v) @@ -510,7 +512,7 @@ func replaceAll(x, y Value) { func renamed(renaming []Value, alloc *Alloc) Value { v := renaming[alloc.index] if v == nil { - v = zeroConst(mustDeref(alloc.Type())) + v = zeroConst(typeparams.MustDeref(alloc.Type())) renaming[alloc.index] = v } return v diff --git a/vendor/golang.org/x/tools/go/ssa/lvalue.go b/vendor/golang.org/x/tools/go/ssa/lvalue.go index 186cfcae70..eede307eab 100644 --- a/vendor/golang.org/x/tools/go/ssa/lvalue.go +++ b/vendor/golang.org/x/tools/go/ssa/lvalue.go @@ -11,6 +11,8 @@ import ( "go/ast" "go/token" "go/types" + + "golang.org/x/tools/internal/typeparams" ) // An lvalue represents an assignable location that may appear on the @@ -52,7 +54,7 @@ func (a *address) address(fn *Function) Value { } func (a *address) typ() types.Type { - return mustDeref(a.addr.Type()) + return typeparams.MustDeref(a.addr.Type()) } // An element is an lvalue represented by m[k], the location of an diff --git a/vendor/golang.org/x/tools/go/ssa/methods.go b/vendor/golang.org/x/tools/go/ssa/methods.go index 4797b39286..5f46a18484 100644 --- a/vendor/golang.org/x/tools/go/ssa/methods.go +++ b/vendor/golang.org/x/tools/go/ssa/methods.go @@ -11,6 +11,7 @@ import ( "go/types" "golang.org/x/tools/go/types/typeutil" + "golang.org/x/tools/internal/aliases" "golang.org/x/tools/internal/typeparams" ) @@ -209,6 +210,9 @@ func forEachReachable(msets *typeutil.MethodSetCache, T types.Type, f func(types } switch T := T.(type) { + case *aliases.Alias: + visit(aliases.Unalias(T), false) + case *types.Basic: // nop diff --git a/vendor/golang.org/x/tools/go/ssa/parameterized.go b/vendor/golang.org/x/tools/go/ssa/parameterized.go index 84db49d392..74c541107e 100644 --- a/vendor/golang.org/x/tools/go/ssa/parameterized.go +++ b/vendor/golang.org/x/tools/go/ssa/parameterized.go @@ -8,6 +8,7 @@ import ( "go/types" "sync" + "golang.org/x/tools/internal/aliases" "golang.org/x/tools/internal/typeparams" ) @@ -48,6 +49,9 @@ func (w *tpWalker) isParameterizedLocked(typ types.Type) (res bool) { case nil, *types.Basic: // TODO(gri) should nil be handled here? break + case *aliases.Alias: + return w.isParameterizedLocked(aliases.Unalias(t)) + case *types.Array: return w.isParameterizedLocked(t.Elem()) diff --git a/vendor/golang.org/x/tools/go/ssa/print.go b/vendor/golang.org/x/tools/go/ssa/print.go index 727a735026..38d8404fdc 100644 --- a/vendor/golang.org/x/tools/go/ssa/print.go +++ b/vendor/golang.org/x/tools/go/ssa/print.go @@ -17,6 +17,7 @@ import ( "strings" "golang.org/x/tools/go/types/typeutil" + "golang.org/x/tools/internal/typeparams" ) // relName returns the name of v relative to i. @@ -94,7 +95,7 @@ func (v *Alloc) String() string { op = "new" } from := v.Parent().relPkg() - return fmt.Sprintf("%s %s (%s)", op, relType(mustDeref(v.Type()), from), v.Comment) + return fmt.Sprintf("%s %s (%s)", op, relType(typeparams.MustDeref(v.Type()), from), v.Comment) } func (v *Phi) String() string { @@ -260,7 +261,7 @@ func (v *MakeChan) String() string { func (v *FieldAddr) String() string { // Be robust against a bad index. name := "?" - if fld := fieldOf(mustDeref(v.X.Type()), v.Field); fld != nil { + if fld := fieldOf(typeparams.MustDeref(v.X.Type()), v.Field); fld != nil { name = fld.Name() } return fmt.Sprintf("&%s.%s [#%d]", relName(v.X, v), name, v.Field) @@ -449,7 +450,7 @@ func WritePackage(buf *bytes.Buffer, p *Package) { case *Global: fmt.Fprintf(buf, " var %-*s %s\n", - maxname, name, relType(mustDeref(mem.Type()), from)) + maxname, name, relType(typeparams.MustDeref(mem.Type()), from)) } } diff --git a/vendor/golang.org/x/tools/go/ssa/sanity.go b/vendor/golang.org/x/tools/go/ssa/sanity.go index 22a3c6bc3d..13bd39fe86 100644 --- a/vendor/golang.org/x/tools/go/ssa/sanity.go +++ b/vendor/golang.org/x/tools/go/ssa/sanity.go @@ -349,7 +349,7 @@ func (s *sanity) checkBlock(b *BasicBlock, index int) { // Check that "untyped" types only appear on constant operands. if _, ok := (*op).(*Const); !ok { - if basic, ok := (*op).Type().(*types.Basic); ok { + if basic, ok := (*op).Type().Underlying().(*types.Basic); ok { if basic.Info()&types.IsUntyped != 0 { s.errorf("operand #%d of %s is untyped: %s", i, instr, basic) } diff --git a/vendor/golang.org/x/tools/go/ssa/subst.go b/vendor/golang.org/x/tools/go/ssa/subst.go index a9a6d41e81..9f2f2f3000 100644 --- a/vendor/golang.org/x/tools/go/ssa/subst.go +++ b/vendor/golang.org/x/tools/go/ssa/subst.go @@ -6,6 +6,8 @@ package ssa import ( "go/types" + + "golang.org/x/tools/internal/aliases" ) // Type substituter for a fixed set of replacement types. @@ -80,6 +82,9 @@ 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 *aliases.Alias: + return subst.typ(aliases.Unalias(t)) + case *types.TypeParam: r := subst.replacements[t] assert(r != nil, "type param without replacement encountered") @@ -466,7 +471,7 @@ func reaches(t types.Type, c map[types.Type]bool) (res bool) { return true } } - case *types.Named: + case *types.Named, *aliases.Alias: return reaches(t.Underlying(), c) default: panic("unreachable") diff --git a/vendor/golang.org/x/tools/go/ssa/util.go b/vendor/golang.org/x/tools/go/ssa/util.go index 6e9f1282b1..4d65259ed9 100644 --- a/vendor/golang.org/x/tools/go/ssa/util.go +++ b/vendor/golang.org/x/tools/go/ssa/util.go @@ -17,7 +17,9 @@ import ( "golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/types/typeutil" + "golang.org/x/tools/internal/aliases" "golang.org/x/tools/internal/typeparams" + "golang.org/x/tools/internal/typesinternal" ) //// Sanity checking utilities @@ -50,16 +52,19 @@ func isNonTypeParamInterface(t types.Type) bool { // isBasic reports whether t is a basic type. func isBasic(t types.Type) bool { - _, ok := t.(*types.Basic) + _, ok := aliases.Unalias(t).(*types.Basic) return ok } // isString reports whether t is exactly a string type. +// t is assumed to be an Underlying type (not Named or Alias). func isString(t types.Type) bool { - return isBasic(t) && t.(*types.Basic).Info()&types.IsString != 0 + basic, ok := t.(*types.Basic) + return ok && basic.Info()&types.IsString != 0 } // isByteSlice reports whether t is of the form []~bytes. +// t is assumed to be an Underlying type (not Named or Alias). func isByteSlice(t types.Type) bool { if b, ok := t.(*types.Slice); ok { e, _ := b.Elem().Underlying().(*types.Basic) @@ -69,6 +74,7 @@ func isByteSlice(t types.Type) bool { } // isRuneSlice reports whether t is of the form []~runes. +// t is assumed to be an Underlying type (not Named or Alias). func isRuneSlice(t types.Type) bool { if b, ok := t.(*types.Slice); ok { e, _ := b.Elem().Underlying().(*types.Basic) @@ -114,15 +120,6 @@ func deref(typ types.Type) (types.Type, bool) { return typ, false } -// mustDeref returns the element type of a type with a pointer core type. -// Panics on failure. -func mustDeref(typ types.Type) types.Type { - if et, ok := deref(typ); ok { - return et - } - panic("cannot dereference type " + typ.String()) -} - // recvType returns the receiver type of method obj. func recvType(obj *types.Func) types.Type { return obj.Type().(*types.Signature).Recv().Type() @@ -139,8 +136,9 @@ func fieldOf(typ types.Type, index int) *types.Var { return nil } -// isUntyped returns true for types that are untyped. +// isUntyped reports whether typ is the type of an untyped constant. func isUntyped(typ types.Type) bool { + // No Underlying/Unalias: untyped constant types cannot be Named or Alias. b, ok := typ.(*types.Basic) return ok && b.Info()&types.IsUntyped != 0 } @@ -180,17 +178,13 @@ func makeLen(T types.Type) *Builtin { } } -// 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 { - rtype := recvType(obj) - if rtype == nil { - return nil - } - rtype, _ = deptr(rtype) - named, ok := rtype.(*types.Named) - if !ok { - return nil +// receiverTypeArgs returns the type arguments to a method's receiver. +// Returns an empty list if the receiver does not have type arguments. +func receiverTypeArgs(method *types.Func) []types.Type { + recv := method.Type().(*types.Signature).Recv() + _, named := typesinternal.ReceiverNamed(recv) + if named == nil { + return nil // recv is anonymous struct/interface } ts := named.TypeArgs() if ts.Len() == 0 { @@ -354,10 +348,10 @@ 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 *types.Context) *types.Func { recv := recvType(m) - if p, ok := recv.(*types.Pointer); ok { + if p, ok := aliases.Unalias(recv).(*types.Pointer); ok { recv = p.Elem() } - named := recv.(*types.Named) + named := aliases.Unalias(recv).(*types.Named) inst, err := types.Instantiate(ctxt, named.Origin(), targs, false) if err != nil { panic(err) 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 11d5c8c3ad..6a57ce3b13 100644 --- a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go +++ b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go @@ -29,9 +29,13 @@ import ( "strconv" "strings" + "golang.org/x/tools/internal/aliases" "golang.org/x/tools/internal/typeparams" + "golang.org/x/tools/internal/typesinternal" ) +// TODO(adonovan): think about generic aliases. + // A Path is an opaque name that identifies a types.Object // relative to its package. Conceptually, the name consists of a // sequence of destructuring operations applied to the package scope @@ -223,7 +227,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().(*types.TypeParam); !ok { + if _, ok := aliases.Unalias(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) @@ -310,7 +314,7 @@ func (enc *Encoder) For(obj types.Object) (Path, error) { } // Inspect declared methods of defined types. - if T, ok := o.Type().(*types.Named); ok { + if T, ok := aliases.Unalias(o.Type()).(*types.Named); ok { path = append(path, opType) // The method index here is always with respect // to the underlying go/types data structures, @@ -395,13 +399,8 @@ func (enc *Encoder) concreteMethod(meth *types.Func) (Path, bool) { return "", false } - recvT := meth.Type().(*types.Signature).Recv().Type() - if ptr, ok := recvT.(*types.Pointer); ok { - recvT = ptr.Elem() - } - - named, ok := recvT.(*types.Named) - if !ok { + _, named := typesinternal.ReceiverNamed(meth.Type().(*types.Signature).Recv()) + if named == nil { return "", false } @@ -444,6 +443,8 @@ func (enc *Encoder) concreteMethod(meth *types.Func) (Path, bool) { // nil, it will be allocated as necessary. func find(obj types.Object, T types.Type, path []byte, seen map[*types.TypeName]bool) []byte { switch T := T.(type) { + case *aliases.Alias: + return find(obj, aliases.Unalias(T), path, seen) case *types.Basic, *types.Named: // Named types belonging to pkg were handled already, // so T must belong to another package. No path. @@ -616,6 +617,7 @@ func Object(pkg *types.Package, p Path) (types.Object, error) { // Inv: t != nil, obj == nil + t = aliases.Unalias(t) switch code { case opElem: hasElem, ok := t.(hasElem) // Pointer, Slice, Array, Chan, Map diff --git a/vendor/golang.org/x/tools/go/types/typeutil/ui.go b/vendor/golang.org/x/tools/go/types/typeutil/ui.go index fa55b0a1e6..a0c1a60ac0 100644 --- a/vendor/golang.org/x/tools/go/types/typeutil/ui.go +++ b/vendor/golang.org/x/tools/go/types/typeutil/ui.go @@ -6,7 +6,11 @@ package typeutil // This file defines utilities for user interfaces that display types. -import "go/types" +import ( + "go/types" + + "golang.org/x/tools/internal/aliases" +) // IntuitiveMethodSet returns the intuitive method set of a type T, // which is the set of methods you can call on an addressable value of @@ -24,7 +28,7 @@ import "go/types" // The order of the result is as for types.MethodSet(T). func IntuitiveMethodSet(T types.Type, msets *MethodSetCache) []*types.Selection { isPointerToConcrete := func(T types.Type) bool { - ptr, ok := T.(*types.Pointer) + ptr, ok := aliases.Unalias(T).(*types.Pointer) return ok && !types.IsInterface(ptr.Elem()) } diff --git a/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go b/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go index b24a0fba9e..c3022a2862 100644 --- a/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go +++ b/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go @@ -13,6 +13,8 @@ import ( "go/token" "go/types" "strconv" + + "golang.org/x/tools/internal/aliases" ) func TypeErrorEndPos(fset *token.FileSet, src []byte, start token.Pos) token.Pos { @@ -28,7 +30,10 @@ func TypeErrorEndPos(fset *token.FileSet, src []byte, start token.Pos) token.Pos } func ZeroValue(f *ast.File, pkg *types.Package, typ types.Type) ast.Expr { - under := typ + // TODO(adonovan): think about generics, and also generic aliases. + under := aliases.Unalias(typ) + // Don't call Underlying unconditionally: although it removed + // Named and Alias, it also removes TypeParam. if n, ok := typ.(*types.Named); ok { under = n.Underlying() } diff --git a/vendor/golang.org/x/tools/internal/gcimporter/gcimporter.go b/vendor/golang.org/x/tools/internal/gcimporter/gcimporter.go index 2d078ccb19..39df91124a 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/gcimporter.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/gcimporter.go @@ -259,13 +259,6 @@ func Import(packages map[string]*types.Package, path, srcDir string, lookup func return } -func deref(typ types.Type) types.Type { - if p, _ := typ.(*types.Pointer); p != nil { - return p.Elem() - } - return typ -} - type byPath []*types.Package func (a byPath) Len() int { return len(a) } diff --git a/vendor/golang.org/x/tools/internal/gcimporter/iexport.go b/vendor/golang.org/x/tools/internal/gcimporter/iexport.go index 2ee8c70164..638fc1d3b8 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/iexport.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/iexport.go @@ -23,6 +23,7 @@ import ( "strings" "golang.org/x/tools/go/types/objectpath" + "golang.org/x/tools/internal/aliases" "golang.org/x/tools/internal/tokeninternal" ) @@ -506,13 +507,13 @@ func (p *iexporter) doDecl(obj types.Object) { case *types.TypeName: t := obj.Type() - if tparam, ok := t.(*types.TypeParam); ok { + if tparam, ok := aliases.Unalias(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 { + if iface, _ := aliases.Unalias(constraint).(*types.Interface); iface != nil { implicit = iface.IsImplicit() } w.bool(implicit) @@ -738,6 +739,8 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) { }() } switch t := t.(type) { + // TODO(adonovan): support types.Alias. + case *types.Named: if targs := t.TypeArgs(); targs.Len() > 0 { w.startType(instanceType) @@ -843,7 +846,7 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) { for i := 0; i < n; i++ { ft := t.EmbeddedType(i) tPkg := pkg - if named, _ := ft.(*types.Named); named != nil { + if named, _ := aliases.Unalias(ft).(*types.Named); named != nil { w.pos(named.Obj().Pos()) } else { w.pos(token.NoPos) diff --git a/vendor/golang.org/x/tools/internal/gcimporter/iimport.go b/vendor/golang.org/x/tools/internal/gcimporter/iimport.go index 9fffa9ad05..4d50eb8e58 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/iimport.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/iimport.go @@ -22,6 +22,8 @@ import ( "strings" "golang.org/x/tools/go/types/objectpath" + "golang.org/x/tools/internal/aliases" + "golang.org/x/tools/internal/typesinternal" ) type intReader struct { @@ -522,7 +524,7 @@ func canReuse(def *types.Named, rhs types.Type) bool { if def == nil { return true } - iface, _ := rhs.(*types.Interface) + iface, _ := aliases.Unalias(rhs).(*types.Interface) if iface == nil { return true } @@ -587,14 +589,13 @@ func (r *importReader) obj(name string) { // If the receiver has any targs, set those as the // rparams of the method (since those are the // typeparams being used in the method sig/body). - base := baseType(recv.Type()) - assert(base != nil) - targs := base.TypeArgs() + _, recvNamed := typesinternal.ReceiverNamed(recv) + targs := recvNamed.TypeArgs() var rparams []*types.TypeParam if targs.Len() > 0 { rparams = make([]*types.TypeParam, targs.Len()) for i := range rparams { - rparams[i] = targs.At(i).(*types.TypeParam) + rparams[i] = aliases.Unalias(targs.At(i)).(*types.TypeParam) } } msig := r.signature(recv, rparams, nil) @@ -624,7 +625,7 @@ func (r *importReader) obj(name string) { } constraint := r.typ() if implicit { - iface, _ := constraint.(*types.Interface) + iface, _ := aliases.Unalias(constraint).(*types.Interface) if iface == nil { errorf("non-interface constraint marked implicit") } @@ -831,7 +832,7 @@ func (r *importReader) typ() types.Type { } func isInterface(t types.Type) bool { - _, ok := t.(*types.Interface) + _, ok := aliases.Unalias(t).(*types.Interface) return ok } @@ -1030,7 +1031,7 @@ func (r *importReader) tparamList() []*types.TypeParam { for i := range xs { // Note: the standard library importer is tolerant of nil types here, // though would panic in SetTypeParams. - xs[i] = r.typ().(*types.TypeParam) + xs[i] = aliases.Unalias(r.typ()).(*types.TypeParam) } return xs } @@ -1077,13 +1078,3 @@ func (r *importReader) byte() byte { } return x } - -func baseType(typ types.Type) *types.Named { - // pointer receivers are never types.Named types - if p, _ := typ.(*types.Pointer); p != nil { - typ = p.Elem() - } - // receiver base types are always (possibly generic) types.Named types - n, _ := typ.(*types.Named) - return n -} diff --git a/vendor/golang.org/x/tools/internal/gcimporter/support_go117.go b/vendor/golang.org/x/tools/internal/gcimporter/support_go117.go deleted file mode 100644 index d892273efb..0000000000 --- a/vendor/golang.org/x/tools/internal/gcimporter/support_go117.go +++ /dev/null @@ -1,16 +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 gcimporter - -import "go/types" - -const iexportVersion = iexportVersionGo1_11 - -func additionalPredeclared() []types.Type { - return nil -} diff --git a/vendor/golang.org/x/tools/internal/gcimporter/support_go118.go b/vendor/golang.org/x/tools/internal/gcimporter/support_go118.go index edbe6ea704..0cd3b91b65 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/support_go118.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/support_go118.go @@ -2,9 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build go1.18 -// +build go1.18 - package gcimporter import "go/types" diff --git a/vendor/golang.org/x/tools/internal/gcimporter/unified_no.go b/vendor/golang.org/x/tools/internal/gcimporter/unified_no.go index 286bf44548..38b624cada 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/unified_no.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/unified_no.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !(go1.18 && goexperiment.unified) -// +build !go1.18 !goexperiment.unified +//go:build !goexperiment.unified +// +build !goexperiment.unified package gcimporter diff --git a/vendor/golang.org/x/tools/internal/gcimporter/unified_yes.go b/vendor/golang.org/x/tools/internal/gcimporter/unified_yes.go index b5d69ffbe6..b5118d0b3a 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/unified_yes.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/unified_yes.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build go1.18 && goexperiment.unified -// +build go1.18,goexperiment.unified +//go:build goexperiment.unified +// +build goexperiment.unified package gcimporter diff --git a/vendor/golang.org/x/tools/internal/gcimporter/ureader_no.go b/vendor/golang.org/x/tools/internal/gcimporter/ureader_no.go deleted file mode 100644 index 8eb20729c2..0000000000 --- a/vendor/golang.org/x/tools/internal/gcimporter/ureader_no.go +++ /dev/null @@ -1,19 +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.18 -// +build !go1.18 - -package gcimporter - -import ( - "fmt" - "go/token" - "go/types" -) - -func UImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (_ int, pkg *types.Package, err error) { - err = fmt.Errorf("go/tools compiled with a Go version earlier than 1.18 cannot read unified IR export data") - return -} diff --git a/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go b/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go index b977435f62..f4edc46ab7 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go @@ -4,9 +4,6 @@ // Derived from go/internal/gcimporter/ureader.go -//go:build go1.18 -// +build go1.18 - package gcimporter import ( @@ -16,6 +13,7 @@ import ( "sort" "strings" + "golang.org/x/tools/internal/aliases" "golang.org/x/tools/internal/pkgbits" ) @@ -553,7 +551,7 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types.Package, string) { // If the underlying type is an interface, we need to // duplicate its methods so we can replace the receiver // parameter's type (#49906). - if iface, ok := underlying.(*types.Interface); ok && iface.NumExplicitMethods() != 0 { + if iface, ok := aliases.Unalias(underlying).(*types.Interface); ok && iface.NumExplicitMethods() != 0 { methods := make([]*types.Func, iface.NumExplicitMethods()) for i := range methods { fn := iface.ExplicitMethod(i) diff --git a/vendor/golang.org/x/tools/internal/tokeninternal/tokeninternal.go b/vendor/golang.org/x/tools/internal/tokeninternal/tokeninternal.go index 7e638ec24f..ff9437a36c 100644 --- a/vendor/golang.org/x/tools/internal/tokeninternal/tokeninternal.go +++ b/vendor/golang.org/x/tools/internal/tokeninternal/tokeninternal.go @@ -34,30 +34,16 @@ func GetLines(file *token.File) []int { lines []int _ []struct{} } - type tokenFile118 struct { - _ *token.FileSet // deleted in go1.19 - tokenFile119 - } - - type uP = unsafe.Pointer - switch unsafe.Sizeof(*file) { - case unsafe.Sizeof(tokenFile118{}): - var ptr *tokenFile118 - *(*uP)(uP(&ptr)) = uP(file) - ptr.mu.Lock() - defer ptr.mu.Unlock() - return ptr.lines - case unsafe.Sizeof(tokenFile119{}): - var ptr *tokenFile119 - *(*uP)(uP(&ptr)) = uP(file) - ptr.mu.Lock() - defer ptr.mu.Unlock() - return ptr.lines - - default: + if unsafe.Sizeof(*file) != unsafe.Sizeof(tokenFile119{}) { panic("unexpected token.File size") } + var ptr *tokenFile119 + type uP = unsafe.Pointer + *(*uP)(uP(&ptr)) = uP(file) + ptr.mu.Lock() + defer ptr.mu.Unlock() + return ptr.lines } // AddExistingFiles adds the specified files to the FileSet if they diff --git a/vendor/golang.org/x/tools/internal/typeparams/common.go b/vendor/golang.org/x/tools/internal/typeparams/common.go index cdab988531..8c3a42dc31 100644 --- a/vendor/golang.org/x/tools/internal/typeparams/common.go +++ b/vendor/golang.org/x/tools/internal/typeparams/common.go @@ -2,20 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package typeparams contains common utilities for writing tools that interact -// with generic Go code, as introduced with Go 1.18. -// -// Many of the types and functions in this package are proxies for the new APIs -// introduced in the standard library with Go 1.18. For example, the -// typeparams.Union type is an alias for go/types.Union, and the ForTypeSpec -// function returns the value of the go/ast.TypeSpec.TypeParams field. At Go -// versions older than 1.18 these helpers are implemented as stubs, allowing -// users of this package to write code that handles generic constructs inline, -// even if the Go version being used to compile does not support generics. -// -// Additionally, this package contains common utilities for working with the -// new generic constructs, to supplement the standard library APIs. Notably, -// the StructuralTerms API computes a minimal representation of the structural +// Package typeparams contains common utilities for writing tools that +// interact with generic Go code, as introduced with Go 1.18. It +// supplements the standard library APIs. Notably, the StructuralTerms +// API computes a minimal representation of the structural // restrictions on a type parameter. // // An external version of these APIs is available in the @@ -27,6 +17,9 @@ import ( "go/ast" "go/token" "go/types" + + "golang.org/x/tools/internal/aliases" + "golang.org/x/tools/internal/typesinternal" ) // UnpackIndexExpr extracts data from AST nodes that represent index @@ -72,9 +65,9 @@ func PackIndexExpr(x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack toke } } -// IsTypeParam reports whether t is a type parameter. +// IsTypeParam reports whether t is a type parameter (or an alias of one). func IsTypeParam(t types.Type) bool { - _, ok := t.(*types.TypeParam) + _, ok := aliases.Unalias(t).(*types.TypeParam) return ok } @@ -90,13 +83,8 @@ func OriginMethod(fn *types.Func) *types.Func { if recv == nil { return fn } - base := recv.Type() - p, isPtr := base.(*types.Pointer) - if isPtr { - base = p.Elem() - } - named, isNamed := base.(*types.Named) - if !isNamed { + _, named := typesinternal.ReceiverNamed(recv) + if named == nil { // Receiver is a *types.Interface. return fn } @@ -158,6 +146,9 @@ 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 *types.Context, V, T types.Type) bool { + V = aliases.Unalias(V) + T = aliases.Unalias(T) + // If V and T are not both named, or do not have matching non-empty type // parameter lists, fall back on types.AssignableTo. diff --git a/vendor/golang.org/x/tools/internal/typeparams/coretype.go b/vendor/golang.org/x/tools/internal/typeparams/coretype.go index 7ea8840eab..e66e9d0f48 100644 --- a/vendor/golang.org/x/tools/internal/typeparams/coretype.go +++ b/vendor/golang.org/x/tools/internal/typeparams/coretype.go @@ -5,7 +5,10 @@ package typeparams import ( + "fmt" "go/types" + + "golang.org/x/tools/internal/aliases" ) // CoreType returns the core type of T or nil if T does not have a core type. @@ -109,7 +112,7 @@ 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) ([]*types.Term, error) { - switch typ := typ.(type) { + switch typ := aliases.Unalias(typ).(type) { case *types.TypeParam: return StructuralTerms(typ) case *types.Union: @@ -120,3 +123,15 @@ func _NormalTerms(typ types.Type) ([]*types.Term, error) { return []*types.Term{types.NewTerm(false, typ)}, nil } } + +// MustDeref returns the type of the variable pointed to by t. +// It panics if t's core type is not a pointer. +// +// TODO(adonovan): ideally this would live in typesinternal, but that +// creates an import cycle. Move there when we melt this package down. +func MustDeref(t types.Type) types.Type { + if ptr, ok := CoreType(t).(*types.Pointer); ok { + return ptr.Elem() + } + panic(fmt.Sprintf("%v is not a pointer", t)) +} diff --git a/vendor/golang.org/x/tools/internal/typesinternal/recv.go b/vendor/golang.org/x/tools/internal/typesinternal/recv.go new file mode 100644 index 0000000000..fea7c8b75e --- /dev/null +++ b/vendor/golang.org/x/tools/internal/typesinternal/recv.go @@ -0,0 +1,43 @@ +// 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 typesinternal + +import ( + "go/types" + + "golang.org/x/tools/internal/aliases" +) + +// ReceiverNamed returns the named type (if any) associated with the +// type of recv, which may be of the form N or *N, or aliases thereof. +// It also reports whether a Pointer was present. +func ReceiverNamed(recv *types.Var) (isPtr bool, named *types.Named) { + t := recv.Type() + if ptr, ok := aliases.Unalias(t).(*types.Pointer); ok { + isPtr = true + t = ptr.Elem() + } + named, _ = aliases.Unalias(t).(*types.Named) + return +} + +// Unpointer returns T given *T or an alias thereof. +// For all other types it is the identity function. +// It does not look at underlying types. +// The result may be an alias. +// +// Use this function to strip off the optional pointer on a receiver +// in a field or method selection, without losing the named type +// (which is needed to compute the method set). +// +// See also [typeparams.MustDeref], which removes one level of +// indirection from the type, regardless of named types (analogous to +// a LOAD instruction). +func Unpointer(t types.Type) types.Type { + if ptr, ok := aliases.Unalias(t).(*types.Pointer); ok { + return ptr.Elem() + } + return t +} diff --git a/vendor/golang.org/x/tools/internal/typesinternal/types_118.go b/vendor/golang.org/x/tools/internal/typesinternal/types_118.go index a42b072a67..ef7ea290c0 100644 --- a/vendor/golang.org/x/tools/internal/typesinternal/types_118.go +++ b/vendor/golang.org/x/tools/internal/typesinternal/types_118.go @@ -2,9 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build go1.18 -// +build go1.18 - package typesinternal import ( diff --git a/vendor/golang.org/x/tools/internal/versions/features.go b/vendor/golang.org/x/tools/internal/versions/features.go new file mode 100644 index 0000000000..b53f178616 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/versions/features.go @@ -0,0 +1,43 @@ +// 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 + +// This file contains predicates for working with file versions to +// decide when a tool should consider a language feature enabled. + +// GoVersions that features in x/tools can be gated to. +const ( + Go1_18 = "go1.18" + Go1_19 = "go1.19" + Go1_20 = "go1.20" + Go1_21 = "go1.21" + Go1_22 = "go1.22" +) + +// Future is an invalid unknown Go version sometime in the future. +// Do not use directly with Compare. +const Future = "" + +// AtLeast reports whether the file version v comes after a Go release. +// +// Use this predicate to enable a behavior once a certain Go release +// has happened (and stays enabled in the future). +func AtLeast(v, release string) bool { + if v == Future { + return true // an unknown future version is always after y. + } + return Compare(Lang(v), Lang(release)) >= 0 +} + +// Before reports whether the file version v is strictly before a Go release. +// +// Use this predicate to disable a behavior once a certain Go release +// has happened (and stays enabled in the future). +func Before(v, release string) bool { + if v == Future { + return false // an unknown future version happens after y. + } + return Compare(Lang(v), Lang(release)) < 0 +} diff --git a/vendor/golang.org/x/tools/internal/versions/toolchain.go b/vendor/golang.org/x/tools/internal/versions/toolchain.go new file mode 100644 index 0000000000..377bf7a53b --- /dev/null +++ b/vendor/golang.org/x/tools/internal/versions/toolchain.go @@ -0,0 +1,14 @@ +// 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 versions + +// toolchain is maximum version (<1.22) that the go toolchain used +// to build the current tool is known to support. +// +// When a tool is built with >=1.22, the value of toolchain is unused. +// +// x/tools does not support building with go <1.18. So we take this +// as the minimum possible maximum. +var toolchain string = Go1_18 diff --git a/vendor/golang.org/x/tools/internal/versions/toolchain_go119.go b/vendor/golang.org/x/tools/internal/versions/toolchain_go119.go new file mode 100644 index 0000000000..f65beed9d8 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/versions/toolchain_go119.go @@ -0,0 +1,14 @@ +// 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.19 +// +build go1.19 + +package versions + +func init() { + if Compare(toolchain, Go1_19) < 0 { + toolchain = Go1_19 + } +} diff --git a/vendor/golang.org/x/tools/internal/versions/toolchain_go120.go b/vendor/golang.org/x/tools/internal/versions/toolchain_go120.go new file mode 100644 index 0000000000..1a9efa126c --- /dev/null +++ b/vendor/golang.org/x/tools/internal/versions/toolchain_go120.go @@ -0,0 +1,14 @@ +// 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.20 +// +build go1.20 + +package versions + +func init() { + if Compare(toolchain, Go1_20) < 0 { + toolchain = Go1_20 + } +} diff --git a/vendor/golang.org/x/tools/internal/versions/toolchain_go121.go b/vendor/golang.org/x/tools/internal/versions/toolchain_go121.go new file mode 100644 index 0000000000..b7ef216dfe --- /dev/null +++ b/vendor/golang.org/x/tools/internal/versions/toolchain_go121.go @@ -0,0 +1,14 @@ +// 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.21 +// +build go1.21 + +package versions + +func init() { + if Compare(toolchain, Go1_21) < 0 { + toolchain = Go1_21 + } +} diff --git a/vendor/golang.org/x/tools/internal/versions/types_go121.go b/vendor/golang.org/x/tools/internal/versions/types_go121.go index a7b79207ae..b4345d3349 100644 --- a/vendor/golang.org/x/tools/internal/versions/types_go121.go +++ b/vendor/golang.org/x/tools/internal/versions/types_go121.go @@ -12,9 +12,19 @@ import ( "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 "" } +// FileVersion returns a language version (<=1.21) derived from runtime.Version() +// or an unknown future version. +func FileVersion(info *types.Info, file *ast.File) string { + // In x/tools built with Go <= 1.21, we do not have Info.FileVersions + // available. We use a go version derived from the toolchain used to + // compile the tool by default. + // This will be <= go1.21. We take this as the maximum version that + // this tool can support. + // + // There are no features currently in x/tools that need to tell fine grained + // differences for versions <1.22. + return toolchain +} -// InitFileVersions is a noop at this Go version. +// InitFileVersions is a noop when compiled with 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 index 7b9ba89a82..e8180632a5 100644 --- a/vendor/golang.org/x/tools/internal/versions/types_go122.go +++ b/vendor/golang.org/x/tools/internal/versions/types_go122.go @@ -12,10 +12,27 @@ import ( "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] +// FileVersions returns a file's Go version. +// The reported version is an unknown Future version if a +// version cannot be determined. +func FileVersion(info *types.Info, file *ast.File) string { + // In tools built with Go >= 1.22, the Go version of a file + // follow a cascades of sources: + // 1) types.Info.FileVersion, which follows the cascade: + // 1.a) file version (ast.File.GoVersion), + // 1.b) the package version (types.Config.GoVersion), or + // 2) is some unknown Future version. + // + // File versions require a valid package version to be provided to types + // in Config.GoVersion. Config.GoVersion is either from the package's module + // or the toolchain (go run). This value should be provided by go/packages + // or unitchecker.Config.GoVersion. + if v := info.FileVersions[file]; IsValid(v) { + return v + } + // Note: we could instead return runtime.Version() [if valid]. + // This would act as a max version on what a tool can support. + return Future } // InitFileVersions initializes info to record Go versions for Go files. diff --git a/vendor/golang.org/x/tools/internal/versions/versions.go b/vendor/golang.org/x/tools/internal/versions/versions.go index e16f6c33a5..8d1f7453db 100644 --- a/vendor/golang.org/x/tools/internal/versions/versions.go +++ b/vendor/golang.org/x/tools/internal/versions/versions.go @@ -4,6 +4,10 @@ package versions +import ( + "strings" +) + // 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. @@ -45,6 +49,7 @@ 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 { + v, _, _ = strings.Cut(v, "-") // strip -bigcorp suffix. if len(v) < 2 || v[:2] != "go" { return "" } diff --git a/vendor/honnef.co/go/tools/go/ir/UPSTREAM b/vendor/honnef.co/go/tools/go/ir/UPSTREAM index 757ebfd599..e92b016b39 100644 --- a/vendor/honnef.co/go/tools/go/ir/UPSTREAM +++ b/vendor/honnef.co/go/tools/go/ir/UPSTREAM @@ -5,5 +5,5 @@ The changes are too many to list here, and it is best to consider this package i Upstream changes still get applied when they address bugs in portions of code we have inherited. The last upstream commit we've looked at was: -915f6209478fe61eb90dbe155a8a1c58655b931f +e854e0228e2ef1cc6e42bbfde1951925096a1272 diff --git a/vendor/honnef.co/go/tools/go/ir/builder.go b/vendor/honnef.co/go/tools/go/ir/builder.go index 1a77ed0429..82ca94ba14 100644 --- a/vendor/honnef.co/go/tools/go/ir/builder.go +++ b/vendor/honnef.co/go/tools/go/ir/builder.go @@ -353,11 +353,16 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) (RET lvalue) { } wantAddr := true v := b.receiver(fn, e.X, wantAddr, escaping, sel, e) - last := len(sel.Index()) - 1 - return &address{ - addr: emitFieldSelection(fn, v, sel.Index()[last], true, e.Sel), - expr: e.Sel, + index := sel.Index()[len(sel.Index())-1] + vut := typeutil.CoreType(deref(v.Type())).Underlying().(*types.Struct) + fld := vut.Field(index) + // Due to the two phases of resolving AssignStmt, a panic from x.f = p() + // when x is nil is required to come after the side-effects of + // evaluating x and p(). + emit := func(fn *Function) Value { + return emitFieldSelection(fn, v, index, true, e.Sel) } + return &lazyAddress{addr: emit, t: fld.Type(), expr: e.Sel} case *ast.IndexExpr: var x Value @@ -411,12 +416,19 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) (RET lvalue) { panic("unexpected container type in IndexExpr: " + t.String()) } - v := &IndexAddr{ - X: x, - Index: b.expr(fn, e.Index), + // Due to the two phases of resolving AssignStmt, a panic from x[i] = p() + // when x is nil or i is out-of-bounds is required to come after the + // side-effects of evaluating x, i and p(). + index := b.expr(fn, e.Index) + emit := func(fn *Function) Value { + v := &IndexAddr{ + X: x, + Index: index, + } + v.setType(et) + return fn.emit(v, e) } - v.setType(et) - return &address{addr: fn.emit(v, e), expr: e} + return &lazyAddress{addr: emit, t: deref(et), expr: e} case *ast.StarExpr: return &address{addr: b.expr(fn, e.X), expr: e} @@ -680,12 +692,12 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { } var low, high, max Value - if e.High != nil { - high = b.expr(fn, e.High) - } if e.Low != nil { low = b.expr(fn, e.Low) } + if e.High != nil { + high = b.expr(fn, e.High) + } if e.Slice3 { max = b.expr(fn, e.Max) } @@ -1027,8 +1039,7 @@ func (b *builder) setCall(fn *Function, e *ast.CallExpr, c *CallCommon) { // assignOp emits to fn code to perform loc = val. func (b *builder) assignOp(fn *Function, loc lvalue, val Value, op token.Token, source ast.Node) { - oldv := loc.load(fn, source) - loc.store(fn, emitArith(fn, op, oldv, emitConv(fn, val, oldv.Type(), source), loc.typ(), source), source) + loc.store(fn, emitArith(fn, op, loc.load(fn, source), val, loc.typ(), source), source) } // localValueSpec emits to fn code to define all of the vars in the diff --git a/vendor/honnef.co/go/tools/go/ir/lift.go b/vendor/honnef.co/go/tools/go/ir/lift.go index 8ac8330dc2..1a4cd3026d 100644 --- a/vendor/honnef.co/go/tools/go/ir/lift.go +++ b/vendor/honnef.co/go/tools/go/ir/lift.go @@ -970,7 +970,24 @@ func liftable(alloc *Alloc, instructions BlockMap[liftInstructions]) bool { for i := range blocks { // Update firstUnliftable to be one after lastLiftable. We do this to include the unliftable's preceding // DebugRefs in the renaming. - blocks[i].firstUnliftable = blocks[i].lastLiftable + 1 + if blocks[i].lastLiftable == -1 && !blocks[i].storeInPreds { + // There are no liftable instructions (for this alloc) in this block. Set firstUnliftable to the + // first non-head instruction to avoid inserting the store before phi instructions, which would + // fail validation. + first := -1 + instrLoop: + for i, instr := range fn.Blocks[i].Instrs { + switch instr.(type) { + case *Phi, *Sigma: + default: + first = i + break instrLoop + } + } + blocks[i].firstUnliftable = first + } else { + blocks[i].firstUnliftable = blocks[i].lastLiftable + 1 + } } // If a block is reachable by a (partially) unliftable block, then the entirety of the block is unliftable. In that diff --git a/vendor/honnef.co/go/tools/go/ir/lvalue.go b/vendor/honnef.co/go/tools/go/ir/lvalue.go index 119eed6c3b..86eb4a5d12 100644 --- a/vendor/honnef.co/go/tools/go/ir/lvalue.go +++ b/vendor/honnef.co/go/tools/go/ir/lvalue.go @@ -114,6 +114,40 @@ func (e *element) typ() types.Type { return e.t } +// A lazyAddress is an lvalue whose address is the result of an instruction. +// These work like an *address except a new address.address() Value +// is created on each load, store and address call. +// A lazyAddress can be used to control when a side effect (nil pointer +// dereference, index out of bounds) of using a location happens. +type lazyAddress struct { + addr func(fn *Function) Value // emit to fn the computation of the address + t types.Type // type of the location + expr ast.Expr // source syntax of the value (not address) [debug mode] +} + +func (l *lazyAddress) load(fn *Function, source ast.Node) Value { + load := emitLoad(fn, l.addr(fn), source) + return load +} + +func (l *lazyAddress) store(fn *Function, v Value, source ast.Node) { + store := emitStore(fn, l.addr(fn), v, source) + if l.expr != nil { + // store.Val is v, converted for assignability. + emitDebugRef(fn, l.expr, store.Val, false) + } +} + +func (l *lazyAddress) address(fn *Function) Value { + addr := l.addr(fn) + if l.expr != nil { + emitDebugRef(fn, l.expr, addr, true) + } + return addr +} + +func (l *lazyAddress) typ() types.Type { return l.t } + // A blank is a dummy variable whose name is "_". // It is not reified: loads are illegal and stores are ignored. type blank struct{} diff --git a/vendor/modules.txt b/vendor/modules.txt index 37c9db8296..e9c213f615 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -7,7 +7,7 @@ # github.com/4meepo/tagalign v1.3.3 ## explicit; go 1.19 github.com/4meepo/tagalign -# github.com/Abirdcfly/dupword v0.0.13 +# github.com/Abirdcfly/dupword v0.0.14 ## explicit; go 1.20 github.com/Abirdcfly/dupword # github.com/Antonboom/errname v0.1.12 @@ -16,7 +16,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 v1.1.2 +# github.com/Antonboom/testifylint v1.2.0 ## explicit; go 1.20 github.com/Antonboom/testifylint/analyzer github.com/Antonboom/testifylint/internal/analysisutil @@ -73,8 +73,8 @@ github.com/agext/levenshtein # 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 -## explicit; go 1.18 +# github.com/alexkohler/nakedret/v2 v2.0.4 +## explicit; go 1.21 github.com/alexkohler/nakedret/v2 # github.com/alexkohler/prealloc v1.0.0 ## explicit; go 1.15 @@ -118,7 +118,7 @@ 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.6.0 +# github.com/catenacyber/perfsprint v0.7.1 ## explicit; go 1.20 github.com/catenacyber/perfsprint/analyzer # github.com/ccojocar/zxcvbn-go v1.0.2 @@ -141,6 +141,9 @@ github.com/charithe/durationcheck # github.com/chavacava/garif v0.1.0 ## explicit; go 1.16 github.com/chavacava/garif +# github.com/ckaznocha/intrange v0.1.0 +## explicit; go 1.21 +github.com/ckaznocha/intrange # github.com/client9/misspell v0.3.4 ## explicit github.com/client9/misspell @@ -163,7 +166,7 @@ github.com/cloudflare/circl/sign/ed448 ## explicit; go 1.18 github.com/curioswitch/go-reassign github.com/curioswitch/go-reassign/internal/analyzer -# github.com/daixiang0/gci v0.12.1 +# github.com/daixiang0/gci v0.12.3 ## explicit; go 1.18 github.com/daixiang0/gci/pkg/config github.com/daixiang0/gci/pkg/format @@ -177,12 +180,9 @@ github.com/daixiang0/gci/pkg/utils # github.com/davecgh/go-spew v1.1.1 ## explicit github.com/davecgh/go-spew/spew -# github.com/denis-tingaikin/go-header v0.4.3 -## explicit; go 1.17 +# github.com/denis-tingaikin/go-header v0.5.0 +## explicit; go 1.21 github.com/denis-tingaikin/go-header -# github.com/esimonov/ifshort v1.0.4 -## explicit; go 1.17 -github.com/esimonov/ifshort/pkg/analyzer # github.com/ettle/strcase v0.2.0 ## explicit; go 1.12 github.com/ettle/strcase @@ -201,10 +201,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.3.4 +# github.com/ghostiam/protogetter v0.3.5 ## explicit; go 1.19 github.com/ghostiam/protogetter -# github.com/go-critic/go-critic v0.11.1 +# github.com/go-critic/go-critic v0.11.2 ## explicit; go 1.18 github.com/go-critic/go-critic/checkers github.com/go-critic/go-critic/checkers/internal/astwalk @@ -260,10 +260,6 @@ github.com/golang/protobuf/ptypes/any github.com/golang/protobuf/ptypes/duration github.com/golang/protobuf/ptypes/empty github.com/golang/protobuf/ptypes/timestamp -# github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 -## explicit -github.com/golangci/check/cmd/structcheck -github.com/golangci/check/cmd/varcheck # github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a ## explicit github.com/golangci/dupl @@ -272,15 +268,12 @@ github.com/golangci/dupl/printer github.com/golangci/dupl/suffixtree github.com/golangci/dupl/syntax github.com/golangci/dupl/syntax/golang -# github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe -## explicit; go 1.17 -github.com/golangci/go-misc/deadcode # github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e ## explicit; go 1.20 github.com/golangci/gofmt/gofmt github.com/golangci/gofmt/gofmt/internal/diff github.com/golangci/gofmt/goimports -# github.com/golangci/golangci-lint v1.56.2 +# github.com/golangci/golangci-lint v1.57.1 ## explicit; go 1.21 github.com/golangci/golangci-lint/cmd/golangci-lint github.com/golangci/golangci-lint/internal/cache @@ -289,6 +282,7 @@ github.com/golangci/golangci-lint/internal/pkgcache github.com/golangci/golangci-lint/internal/renameio github.com/golangci/golangci-lint/internal/robustio github.com/golangci/golangci-lint/pkg/commands +github.com/golangci/golangci-lint/pkg/commands/internal github.com/golangci/golangci-lint/pkg/config github.com/golangci/golangci-lint/pkg/exitcodes github.com/golangci/golangci-lint/pkg/fsutils @@ -307,20 +301,17 @@ github.com/golangci/golangci-lint/pkg/report github.com/golangci/golangci-lint/pkg/result github.com/golangci/golangci-lint/pkg/result/processors github.com/golangci/golangci-lint/pkg/timeutils -# github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 -## explicit -github.com/golangci/lint-1 -# github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca -## explicit -github.com/golangci/maligned # github.com/golangci/misspell v0.4.1 ## explicit; go 1.19 github.com/golangci/misspell +# github.com/golangci/plugin-module-register v0.1.1 +## explicit; go 1.21 +github.com/golangci/plugin-module-register/register # github.com/golangci/revgrep v0.5.2 ## explicit; go 1.19 github.com/golangci/revgrep -# github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 -## explicit +# github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed +## explicit; go 1.20 github.com/golangci/unconvert # github.com/google/go-cmp v0.6.0 ## explicit; go 1.13 @@ -510,26 +501,25 @@ 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 +# github.com/jjti/go-spancheck v0.5.3 ## 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/karamaru-alpha/copyloopvar v1.0.8 +## explicit; go 1.21 +github.com/karamaru-alpha/copyloopvar # github.com/kisielk/errcheck v1.7.0 ## explicit; go 1.18 github.com/kisielk/errcheck/errcheck -# github.com/kisielk/gotool v1.0.0 -## explicit -github.com/kisielk/gotool -github.com/kisielk/gotool/internal/load # github.com/kkHAIKE/contextcheck v1.1.4 ## explicit; go 1.20 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.9 +# github.com/kunwardeep/paralleltest v1.0.10 ## explicit; go 1.17 github.com/kunwardeep/paralleltest/pkg/paralleltest # github.com/kyoh86/exportloopref v0.1.11 @@ -579,9 +569,6 @@ github.com/mattn/go-runewidth # github.com/matttproud/golang_protobuf_extensions v1.0.1 ## explicit github.com/matttproud/golang_protobuf_extensions/pbutil -# github.com/mbilski/exhaustivestruct v1.2.0 -## explicit; go 1.15 -github.com/mbilski/exhaustivestruct/pkg/analyzer # github.com/mgechev/revive v1.3.7 ## explicit; go 1.20 github.com/mgechev/revive/config @@ -624,13 +611,16 @@ 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.15.2 -## explicit; go 1.20 +# github.com/nunnatsa/ginkgolinter v0.16.1 +## explicit; go 1.21 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/internal/ginkgohandler +github.com/nunnatsa/ginkgolinter/internal/gomegahandler +github.com/nunnatsa/ginkgolinter/internal/interfaces +github.com/nunnatsa/ginkgolinter/internal/intervals +github.com/nunnatsa/ginkgolinter/internal/reports +github.com/nunnatsa/ginkgolinter/internal/reverseassertion +github.com/nunnatsa/ginkgolinter/linter github.com/nunnatsa/ginkgolinter/types github.com/nunnatsa/ginkgolinter/version # github.com/oklog/run v1.0.0 @@ -642,12 +632,13 @@ github.com/olekukonko/tablewriter # github.com/pelletier/go-toml v1.9.5 ## explicit; go 1.12 github.com/pelletier/go-toml -# github.com/pelletier/go-toml/v2 v2.0.5 +# github.com/pelletier/go-toml/v2 v2.2.0 ## explicit; go 1.16 github.com/pelletier/go-toml/v2 -github.com/pelletier/go-toml/v2/internal/ast +github.com/pelletier/go-toml/v2/internal/characters github.com/pelletier/go-toml/v2/internal/danger github.com/pelletier/go-toml/v2/internal/tracker +github.com/pelletier/go-toml/v2/unstable # github.com/pmezard/go-difflib v1.0.0 ## explicit github.com/pmezard/go-difflib/difflib @@ -672,7 +663,7 @@ github.com/prometheus/common/model github.com/prometheus/procfs github.com/prometheus/procfs/internal/fs github.com/prometheus/procfs/internal/util -# github.com/quasilyte/go-ruleguard v0.4.0 +# github.com/quasilyte/go-ruleguard v0.4.2 ## explicit; go 1.19 github.com/quasilyte/go-ruleguard/internal/goenv github.com/quasilyte/go-ruleguard/internal/golist @@ -700,8 +691,8 @@ github.com/quasilyte/regex/syntax # github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 ## explicit; go 1.17 github.com/quasilyte/stdinfo -# github.com/ryancurrah/gomodguard v1.3.0 -## explicit; go 1.19 +# github.com/ryancurrah/gomodguard v1.3.1 +## explicit; go 1.21 github.com/ryancurrah/gomodguard # github.com/ryanrolds/sqlclosecheck v0.5.1 ## explicit; go 1.20 @@ -709,6 +700,10 @@ github.com/ryanrolds/sqlclosecheck/pkg/analyzer # github.com/sanposhiho/wastedassign/v2 v2.0.7 ## explicit; go 1.14 github.com/sanposhiho/wastedassign/v2 +# github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 +## explicit; go 1.19 +github.com/santhosh-tekuri/jsonschema/v5 +github.com/santhosh-tekuri/jsonschema/v5/httploader # github.com/sashamelentyev/interfacebloat v1.1.0 ## explicit; go 1.18 github.com/sashamelentyev/interfacebloat/pkg/analyzer @@ -740,9 +735,6 @@ github.com/sirupsen/logrus # github.com/sivchari/containedctx v1.0.3 ## explicit; go 1.17 github.com/sivchari/containedctx -# github.com/sivchari/nosnakecase v1.7.0 -## explicit; go 1.18 -github.com/sivchari/nosnakecase # github.com/sivchari/tenv v1.7.1 ## explicit; go 1.18 github.com/sivchari/tenv @@ -819,8 +811,8 @@ github.com/timonwong/loggercheck/internal/checkers/printf github.com/timonwong/loggercheck/internal/rules github.com/timonwong/loggercheck/internal/sets github.com/timonwong/loggercheck/internal/stringutil -# github.com/tomarrell/wrapcheck/v2 v2.8.1 -## explicit; go 1.18 +# github.com/tomarrell/wrapcheck/v2 v2.8.3 +## explicit; go 1.21 github.com/tomarrell/wrapcheck/v2/wrapcheck # github.com/tommy-muehle/go-mnd/v2 v2.5.1 ## explicit; go 1.12 @@ -876,15 +868,20 @@ github.com/zclconf/go-cty/cty/set # gitlab.com/bosi/decorder v0.4.1 ## explicit; go 1.20 gitlab.com/bosi/decorder -# go-simpler.org/musttag v0.8.0 +# go-simpler.org/musttag v0.9.0 ## explicit; go 1.20 go-simpler.org/musttag -# go-simpler.org/sloglint v0.4.0 +# go-simpler.org/sloglint v0.5.0 ## explicit; go 1.20 go-simpler.org/sloglint # go.uber.org/atomic v1.7.0 ## explicit; go 1.13 go.uber.org/atomic +# go.uber.org/automaxprocs v1.5.3 +## explicit; go 1.18 +go.uber.org/automaxprocs/internal/cgroups +go.uber.org/automaxprocs/internal/runtime +go.uber.org/automaxprocs/maxprocs # go.uber.org/multierr v1.6.0 ## explicit; go 1.12 go.uber.org/multierr @@ -919,11 +916,13 @@ golang.org/x/crypto/ssh golang.org/x/crypto/ssh/internal/bcrypt_pbkdf # golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc ## explicit; go 1.20 +golang.org/x/exp/constraints golang.org/x/exp/maps -# golang.org/x/exp/typeparams v0.0.0-20231219180239-dc181d75b848 +golang.org/x/exp/slices +# golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f ## explicit; go 1.18 golang.org/x/exp/typeparams -# golang.org/x/mod v0.15.0 +# golang.org/x/mod v0.16.0 ## explicit; go 1.18 golang.org/x/mod/internal/lazyregexp golang.org/x/mod/modfile @@ -959,8 +958,8 @@ golang.org/x/text/transform golang.org/x/text/unicode/bidi golang.org/x/text/unicode/norm golang.org/x/text/width -# golang.org/x/tools v0.18.0 -## explicit; go 1.18 +# golang.org/x/tools v0.19.0 +## explicit; go 1.19 golang.org/x/tools/go/analysis golang.org/x/tools/go/analysis/passes/appends golang.org/x/tools/go/analysis/passes/asmdecl @@ -1169,7 +1168,7 @@ gopkg.in/yaml.v2 # gopkg.in/yaml.v3 v3.0.1 ## explicit gopkg.in/yaml.v3 -# honnef.co/go/tools v0.4.6 +# honnef.co/go/tools v0.4.7 ## explicit; go 1.19 honnef.co/go/tools/analysis/code honnef.co/go/tools/analysis/edit @@ -1206,12 +1205,6 @@ mvdan.cc/gofumpt/internal/govendor/go/doc/comment mvdan.cc/gofumpt/internal/govendor/go/format mvdan.cc/gofumpt/internal/govendor/go/printer mvdan.cc/gofumpt/internal/version -# mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed -## explicit -mvdan.cc/interfacer/check -# mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b -## explicit -mvdan.cc/lint # mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 ## explicit; go 1.20 mvdan.cc/unparam/check diff --git a/vendor/mvdan.cc/interfacer/LICENSE b/vendor/mvdan.cc/interfacer/LICENSE deleted file mode 100644 index 7d71d51a5e..0000000000 --- a/vendor/mvdan.cc/interfacer/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2015, Daniel Martí. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of the copyright holder nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/mvdan.cc/interfacer/check/cache.go b/vendor/mvdan.cc/interfacer/check/cache.go deleted file mode 100644 index 757eca55e1..0000000000 --- a/vendor/mvdan.cc/interfacer/check/cache.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) 2015, Daniel Martí -// See LICENSE for licensing information - -package check - -import ( - "go/ast" - "go/types" -) - -type pkgTypes struct { - ifaces map[string]string - funcSigns map[string]bool -} - -func (p *pkgTypes) getTypes(pkg *types.Package) { - p.ifaces = make(map[string]string) - p.funcSigns = make(map[string]bool) - done := make(map[*types.Package]bool) - addTypes := func(pkg *types.Package, top bool) { - if done[pkg] { - return - } - done[pkg] = true - ifs, funs := fromScope(pkg.Scope()) - fullName := func(name string) string { - if !top { - return pkg.Path() + "." + name - } - return name - } - for iftype, name := range ifs { - // only suggest exported interfaces - if ast.IsExported(name) { - p.ifaces[iftype] = fullName(name) - } - } - for ftype := range funs { - // ignore non-exported func signatures too - p.funcSigns[ftype] = true - } - } - for _, imp := range pkg.Imports() { - addTypes(imp, false) - for _, imp2 := range imp.Imports() { - addTypes(imp2, false) - } - } - addTypes(pkg, true) -} diff --git a/vendor/mvdan.cc/interfacer/check/check.go b/vendor/mvdan.cc/interfacer/check/check.go deleted file mode 100644 index f4d3b4037b..0000000000 --- a/vendor/mvdan.cc/interfacer/check/check.go +++ /dev/null @@ -1,462 +0,0 @@ -// Copyright (c) 2015, Daniel Martí -// See LICENSE for licensing information - -package check // import "mvdan.cc/interfacer/check" - -import ( - "fmt" - "go/ast" - "go/token" - "go/types" - "os" - "strings" - - "golang.org/x/tools/go/loader" - "golang.org/x/tools/go/ssa" - "golang.org/x/tools/go/ssa/ssautil" - - "github.com/kisielk/gotool" - "mvdan.cc/lint" -) - -func toDiscard(usage *varUsage) bool { - if usage.discard { - return true - } - for to := range usage.assigned { - if toDiscard(to) { - return true - } - } - return false -} - -func allCalls(usage *varUsage, all, ftypes map[string]string) { - for fname := range usage.calls { - all[fname] = ftypes[fname] - } - for to := range usage.assigned { - allCalls(to, all, ftypes) - } -} - -func (c *Checker) interfaceMatching(param *types.Var, usage *varUsage) (string, string) { - if toDiscard(usage) { - return "", "" - } - ftypes := typeFuncMap(param.Type()) - called := make(map[string]string, len(usage.calls)) - allCalls(usage, called, ftypes) - s := funcMapString(called) - return c.ifaces[s], s -} - -type varUsage struct { - calls map[string]struct{} - discard bool - - assigned map[*varUsage]struct{} -} - -type funcDecl struct { - astDecl *ast.FuncDecl - ssaFn *ssa.Function -} - -// CheckArgs checks the packages specified by their import paths in -// args. -func CheckArgs(args []string) ([]string, error) { - paths := gotool.ImportPaths(args) - conf := loader.Config{} - conf.AllowErrors = true - rest, err := conf.FromArgs(paths, false) - if err != nil { - return nil, err - } - if len(rest) > 0 { - return nil, fmt.Errorf("unwanted extra args: %v", rest) - } - lprog, err := conf.Load() - if err != nil { - return nil, err - } - prog := ssautil.CreateProgram(lprog, 0) - prog.Build() - c := new(Checker) - c.Program(lprog) - c.ProgramSSA(prog) - issues, err := c.Check() - if err != nil { - return nil, err - } - wd, err := os.Getwd() - if err != nil { - return nil, err - } - lines := make([]string, len(issues)) - for i, issue := range issues { - fpos := prog.Fset.Position(issue.Pos()).String() - if strings.HasPrefix(fpos, wd) { - fpos = fpos[len(wd)+1:] - } - lines[i] = fmt.Sprintf("%s: %s", fpos, issue.Message()) - } - return lines, nil -} - -type Checker struct { - lprog *loader.Program - prog *ssa.Program - - pkgTypes - *loader.PackageInfo - - funcs []*funcDecl - - ssaByPos map[token.Pos]*ssa.Function - - discardFuncs map[*types.Signature]struct{} - - vars map[*types.Var]*varUsage -} - -var ( - _ lint.Checker = (*Checker)(nil) - _ lint.WithSSA = (*Checker)(nil) -) - -func (c *Checker) Program(lprog *loader.Program) { - c.lprog = lprog -} - -func (c *Checker) ProgramSSA(prog *ssa.Program) { - c.prog = prog -} - -func (c *Checker) Check() ([]lint.Issue, error) { - var total []lint.Issue - c.ssaByPos = make(map[token.Pos]*ssa.Function) - wantPkg := make(map[*types.Package]bool) - for _, pinfo := range c.lprog.InitialPackages() { - wantPkg[pinfo.Pkg] = true - } - for fn := range ssautil.AllFunctions(c.prog) { - if fn.Pkg == nil { // builtin? - continue - } - if len(fn.Blocks) == 0 { // stub - continue - } - if !wantPkg[fn.Pkg.Pkg] { // not part of given pkgs - continue - } - c.ssaByPos[fn.Pos()] = fn - } - for _, pinfo := range c.lprog.InitialPackages() { - pkg := pinfo.Pkg - c.getTypes(pkg) - c.PackageInfo = c.lprog.AllPackages[pkg] - total = append(total, c.checkPkg()...) - } - return total, nil -} - -func (c *Checker) checkPkg() []lint.Issue { - c.discardFuncs = make(map[*types.Signature]struct{}) - c.vars = make(map[*types.Var]*varUsage) - c.funcs = c.funcs[:0] - findFuncs := func(node ast.Node) bool { - decl, ok := node.(*ast.FuncDecl) - if !ok { - return true - } - ssaFn := c.ssaByPos[decl.Name.Pos()] - if ssaFn == nil { - return true - } - fd := &funcDecl{ - astDecl: decl, - ssaFn: ssaFn, - } - if c.funcSigns[signString(fd.ssaFn.Signature)] { - // implements interface - return true - } - c.funcs = append(c.funcs, fd) - ast.Walk(c, decl.Body) - return true - } - for _, f := range c.Files { - ast.Inspect(f, findFuncs) - } - return c.packageIssues() -} - -func paramVarAndType(sign *types.Signature, i int) (*types.Var, types.Type) { - params := sign.Params() - extra := sign.Variadic() && i >= params.Len()-1 - if !extra { - if i >= params.Len() { - // builtins with multiple signatures - return nil, nil - } - vr := params.At(i) - return vr, vr.Type() - } - last := params.At(params.Len() - 1) - switch x := last.Type().(type) { - case *types.Slice: - return nil, x.Elem() - default: - return nil, x - } -} - -func (c *Checker) varUsage(e ast.Expr) *varUsage { - id, ok := e.(*ast.Ident) - if !ok { - return nil - } - param, ok := c.ObjectOf(id).(*types.Var) - if !ok { - // not a variable - return nil - } - if usage, e := c.vars[param]; e { - return usage - } - if !interesting(param.Type()) { - return nil - } - usage := &varUsage{ - calls: make(map[string]struct{}), - assigned: make(map[*varUsage]struct{}), - } - c.vars[param] = usage - return usage -} - -func (c *Checker) addUsed(e ast.Expr, as types.Type) { - if as == nil { - return - } - if usage := c.varUsage(e); usage != nil { - // using variable - iface, ok := as.Underlying().(*types.Interface) - if !ok { - usage.discard = true - return - } - for i := 0; i < iface.NumMethods(); i++ { - m := iface.Method(i) - usage.calls[m.Name()] = struct{}{} - } - } else if t, ok := c.TypeOf(e).(*types.Signature); ok { - // using func - c.discardFuncs[t] = struct{}{} - } -} - -func (c *Checker) addAssign(to, from ast.Expr) { - pto := c.varUsage(to) - pfrom := c.varUsage(from) - if pto == nil || pfrom == nil { - // either isn't interesting - return - } - pfrom.assigned[pto] = struct{}{} -} - -func (c *Checker) discard(e ast.Expr) { - if usage := c.varUsage(e); usage != nil { - usage.discard = true - } -} - -func (c *Checker) comparedWith(e, with ast.Expr) { - if _, ok := with.(*ast.BasicLit); ok { - c.discard(e) - } -} - -func (c *Checker) Visit(node ast.Node) ast.Visitor { - switch x := node.(type) { - case *ast.SelectorExpr: - if _, ok := c.TypeOf(x.Sel).(*types.Signature); !ok { - c.discard(x.X) - } - case *ast.StarExpr: - c.discard(x.X) - case *ast.UnaryExpr: - c.discard(x.X) - case *ast.IndexExpr: - c.discard(x.X) - case *ast.IncDecStmt: - c.discard(x.X) - case *ast.BinaryExpr: - switch x.Op { - case token.EQL, token.NEQ: - c.comparedWith(x.X, x.Y) - c.comparedWith(x.Y, x.X) - default: - c.discard(x.X) - c.discard(x.Y) - } - case *ast.ValueSpec: - for _, val := range x.Values { - c.addUsed(val, c.TypeOf(x.Type)) - } - case *ast.AssignStmt: - for i, val := range x.Rhs { - left := x.Lhs[i] - if x.Tok == token.ASSIGN { - c.addUsed(val, c.TypeOf(left)) - } - c.addAssign(left, val) - } - case *ast.CompositeLit: - for i, e := range x.Elts { - switch y := e.(type) { - case *ast.KeyValueExpr: - c.addUsed(y.Key, c.TypeOf(y.Value)) - c.addUsed(y.Value, c.TypeOf(y.Key)) - case *ast.Ident: - c.addUsed(y, compositeIdentType(c.TypeOf(x), i)) - } - } - case *ast.CallExpr: - switch y := c.TypeOf(x.Fun).Underlying().(type) { - case *types.Signature: - c.onMethodCall(x, y) - default: - // type conversion - if len(x.Args) == 1 { - c.addUsed(x.Args[0], y) - } - } - } - return c -} - -func compositeIdentType(t types.Type, i int) types.Type { - switch x := t.(type) { - case *types.Named: - return compositeIdentType(x.Underlying(), i) - case *types.Struct: - return x.Field(i).Type() - case *types.Array: - return x.Elem() - case *types.Slice: - return x.Elem() - } - return nil -} - -func (c *Checker) onMethodCall(ce *ast.CallExpr, sign *types.Signature) { - for i, e := range ce.Args { - paramObj, t := paramVarAndType(sign, i) - // Don't if this is a parameter being re-used as itself - // in a recursive call - if id, ok := e.(*ast.Ident); ok { - if paramObj == c.ObjectOf(id) { - continue - } - } - c.addUsed(e, t) - } - sel, ok := ce.Fun.(*ast.SelectorExpr) - if !ok { - return - } - // receiver func call on the left side - if usage := c.varUsage(sel.X); usage != nil { - usage.calls[sel.Sel.Name] = struct{}{} - } -} - -func (fd *funcDecl) paramGroups() [][]*types.Var { - astList := fd.astDecl.Type.Params.List - groups := make([][]*types.Var, len(astList)) - signIndex := 0 - for i, field := range astList { - group := make([]*types.Var, len(field.Names)) - for j := range field.Names { - group[j] = fd.ssaFn.Signature.Params().At(signIndex) - signIndex++ - } - groups[i] = group - } - return groups -} - -func (c *Checker) packageIssues() []lint.Issue { - var issues []lint.Issue - for _, fd := range c.funcs { - if _, e := c.discardFuncs[fd.ssaFn.Signature]; e { - continue - } - for _, group := range fd.paramGroups() { - issues = append(issues, c.groupIssues(fd, group)...) - } - } - return issues -} - -type Issue struct { - pos token.Pos - msg string -} - -func (i Issue) Pos() token.Pos { return i.pos } -func (i Issue) Message() string { return i.msg } - -func (c *Checker) groupIssues(fd *funcDecl, group []*types.Var) []lint.Issue { - var issues []lint.Issue - for _, param := range group { - usage := c.vars[param] - if usage == nil { - return nil - } - newType := c.paramNewType(fd.astDecl.Name.Name, param, usage) - if newType == "" { - return nil - } - issues = append(issues, Issue{ - pos: param.Pos(), - msg: fmt.Sprintf("%s can be %s", param.Name(), newType), - }) - } - return issues -} - -func willAddAllocation(t types.Type) bool { - switch t.Underlying().(type) { - case *types.Pointer, *types.Interface: - return false - } - return true -} - -func (c *Checker) paramNewType(funcName string, param *types.Var, usage *varUsage) string { - t := param.Type() - if !ast.IsExported(funcName) && willAddAllocation(t) { - return "" - } - if named := typeNamed(t); named != nil { - tname := named.Obj().Name() - vname := param.Name() - if mentionsName(funcName, tname) || mentionsName(funcName, vname) { - return "" - } - } - ifname, iftype := c.interfaceMatching(param, usage) - if ifname == "" { - return "" - } - if types.IsInterface(t.Underlying()) { - if have := funcMapString(typeFuncMap(t)); have == iftype { - return "" - } - } - return ifname -} diff --git a/vendor/mvdan.cc/interfacer/check/types.go b/vendor/mvdan.cc/interfacer/check/types.go deleted file mode 100644 index 393bb0b9fa..0000000000 --- a/vendor/mvdan.cc/interfacer/check/types.go +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright (c) 2015, Daniel Martí -// See LICENSE for licensing information - -package check - -import ( - "bytes" - "fmt" - "go/types" - "sort" - "strings" -) - -type methoder interface { - NumMethods() int - Method(int) *types.Func -} - -func methoderFuncMap(m methoder, skip bool) map[string]string { - ifuncs := make(map[string]string, m.NumMethods()) - for i := 0; i < m.NumMethods(); i++ { - f := m.Method(i) - if !f.Exported() { - if skip { - continue - } - return nil - } - sign := f.Type().(*types.Signature) - ifuncs[f.Name()] = signString(sign) - } - return ifuncs -} - -func typeFuncMap(t types.Type) map[string]string { - switch x := t.(type) { - case *types.Pointer: - return typeFuncMap(x.Elem()) - case *types.Named: - u := x.Underlying() - if types.IsInterface(u) { - return typeFuncMap(u) - } - return methoderFuncMap(x, true) - case *types.Interface: - return methoderFuncMap(x, false) - default: - return nil - } -} - -func funcMapString(iface map[string]string) string { - fnames := make([]string, 0, len(iface)) - for fname := range iface { - fnames = append(fnames, fname) - } - sort.Strings(fnames) - var b bytes.Buffer - for i, fname := range fnames { - if i > 0 { - fmt.Fprint(&b, "; ") - } - fmt.Fprint(&b, fname, iface[fname]) - } - return b.String() -} - -func tupleJoin(buf *bytes.Buffer, t *types.Tuple) { - buf.WriteByte('(') - for i := 0; i < t.Len(); i++ { - if i > 0 { - buf.WriteString(", ") - } - buf.WriteString(t.At(i).Type().String()) - } - buf.WriteByte(')') -} - -// signString is similar to Signature.String(), but it ignores -// param/result names. -func signString(sign *types.Signature) string { - var buf bytes.Buffer - tupleJoin(&buf, sign.Params()) - tupleJoin(&buf, sign.Results()) - return buf.String() -} - -func interesting(t types.Type) bool { - switch x := t.(type) { - case *types.Interface: - return x.NumMethods() > 1 - case *types.Named: - if u := x.Underlying(); types.IsInterface(u) { - return interesting(u) - } - return x.NumMethods() >= 1 - case *types.Pointer: - return interesting(x.Elem()) - default: - return false - } -} - -func anyInteresting(params *types.Tuple) bool { - for i := 0; i < params.Len(); i++ { - t := params.At(i).Type() - if interesting(t) { - return true - } - } - return false -} - -func fromScope(scope *types.Scope) (ifaces map[string]string, funcs map[string]bool) { - ifaces = make(map[string]string) - funcs = make(map[string]bool) - for _, name := range scope.Names() { - tn, ok := scope.Lookup(name).(*types.TypeName) - if !ok { - continue - } - switch x := tn.Type().Underlying().(type) { - case *types.Interface: - iface := methoderFuncMap(x, false) - if len(iface) == 0 { - continue - } - for i := 0; i < x.NumMethods(); i++ { - f := x.Method(i) - sign := f.Type().(*types.Signature) - if !anyInteresting(sign.Params()) { - continue - } - funcs[signString(sign)] = true - } - s := funcMapString(iface) - if _, e := ifaces[s]; !e { - ifaces[s] = tn.Name() - } - case *types.Signature: - if !anyInteresting(x.Params()) { - continue - } - funcs[signString(x)] = true - } - } - return ifaces, funcs -} - -func mentionsName(fname, name string) bool { - if len(name) < 2 { - return false - } - capit := strings.ToUpper(name[:1]) + name[1:] - lower := strings.ToLower(name) - return strings.Contains(fname, capit) || strings.HasPrefix(fname, lower) -} - -func typeNamed(t types.Type) *types.Named { - for { - switch x := t.(type) { - case *types.Named: - return x - case *types.Pointer: - t = x.Elem() - default: - return nil - } - } -} diff --git a/vendor/mvdan.cc/lint/.travis.yml b/vendor/mvdan.cc/lint/.travis.yml deleted file mode 100644 index 2ccdeab9ad..0000000000 --- a/vendor/mvdan.cc/lint/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: go - -go: - - 1.8.x - - 1.9.x - -go_import_path: mvdan.cc/lint diff --git a/vendor/mvdan.cc/lint/LICENSE b/vendor/mvdan.cc/lint/LICENSE deleted file mode 100644 index a06c5ebfc8..0000000000 --- a/vendor/mvdan.cc/lint/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2017, Daniel Martí. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of the copyright holder nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/mvdan.cc/lint/README.md b/vendor/mvdan.cc/lint/README.md deleted file mode 100644 index 8a9c8b51c3..0000000000 --- a/vendor/mvdan.cc/lint/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# lint - -[![GoDoc](https://godoc.org/mvdan.cc/lint?status.svg)](https://godoc.org/mvdan.cc/lint) -[![Build Status](https://travis-ci.org/mvdan/lint.svg?branch=master)](https://travis-ci.org/mvdan/lint) - -Work in progress. Its API might change before the 1.0 release. - -This package intends to define simple interfaces that Go code checkers -can implement. This would simplify calling them from Go code, as well as -running multiple linters while sharing initial loading work. - -### metalint - - go get -u mvdan.cc/lint/cmd/metalint - -The start of a linter that runs many linters leveraging the common -interface. Not stable yet. - -Linters included: - -* [unparam](https://mvdan.cc/unparam) -* [interfacer](https://github.com/mvdan/interfacer) - -### Related projects - -* [golinters](https://github.com/thomasheller/golinters) - Report on - linter support diff --git a/vendor/mvdan.cc/lint/lint.go b/vendor/mvdan.cc/lint/lint.go deleted file mode 100644 index a16789fad5..0000000000 --- a/vendor/mvdan.cc/lint/lint.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) 2017, Daniel Martí -// See LICENSE for licensing information - -// Package lint defines common interfaces for Go code checkers. -package lint // import "mvdan.cc/lint" - -import ( - "go/token" - - "golang.org/x/tools/go/loader" - "golang.org/x/tools/go/ssa" -) - -// A Checker points out issues in a program. -type Checker interface { - Program(*loader.Program) - Check() ([]Issue, error) -} - -type WithSSA interface { - ProgramSSA(*ssa.Program) -} - -// Issue represents an issue somewhere in a source code file. -type Issue interface { - Pos() token.Pos - Message() string -}