Merge pull request #6109 from kolyshkin/golangci-v2

Golangci v2
This commit is contained in:
openshift-merge-bot[bot] 2025-04-08 15:36:10 +00:00 committed by GitHub
commit bb240a6e40
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3263 changed files with 232 additions and 813194 deletions

View File

@ -1,16 +1,29 @@
---
version: "2"
run:
build-tags:
- apparmor
- seccomp
- selinux
# Don't exceed number of threads available when running under CI
concurrency: 4
formatters:
enable:
- gofumpt
linters:
enable:
- gofmt
- gofumpt
- nolintlint
- revive
- unconvert
- unparam
- unused
- whitespace
exclusions:
presets:
- comments
- std-error-handling
settings:
staticcheck:
checks:
- all
- -QF1008 # https://staticcheck.dev/docs/checks/#QF1008 Omit embedded fields from selector expression.

View File

@ -52,6 +52,9 @@ ifeq ($(BUILDDEBUG), 1)
override GOGCFLAGS += -N -l
endif
# Managed by renovate.
export GOLANGCI_LINT_VERSION := 2.0.2
# make all BUILDDEBUG=1
# Note: Uses the -N -l go compiler options to disable compiler optimizations
# and inlining. Using these build options allows you to subsequently
@ -192,6 +195,7 @@ vendor:
.PHONY: lint
lint: install.tools
./tests/tools/build/golangci-lint run $(LINTFLAGS)
./tests/tools/build/golangci-lint run --tests=false $(LINTFLAGS)
# CAUTION: This is not a replacement for RPMs provided by your distro.
# Only intended to build and test the latest unreleased changes.

7
add.go
View File

@ -454,10 +454,7 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
}
// if the destination is a directory that doesn't yet exist, let's copy it.
newDestDirFound := false
if (len(destStats) == 1 || len(destStats[0].Globbed) == 0) && destMustBeDirectory && !destCanBeFile {
newDestDirFound = true
}
newDestDirFound := (len(destStats) == 1 || len(destStats[0].Globbed) == 0) && destMustBeDirectory && !destCanBeFile
if len(destStats) == 1 && len(destStats[0].Globbed) == 1 && destStats[0].Results[destStats[0].Globbed[0]].IsRegular {
if destMustBeDirectory {
@ -625,7 +622,7 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
}
// Check for dockerignore-style exclusion of this item.
if rel != "." {
excluded, err := pm.Matches(filepath.ToSlash(rel)) // nolint:staticcheck
excluded, err := pm.Matches(filepath.ToSlash(rel)) //nolint:staticcheck
if err != nil {
return fmt.Errorf("checking if %q(%q) is excluded: %w", globbed, rel, err)
}

View File

@ -316,7 +316,7 @@ func TestProcessRlimits(t *testing.T) {
rlim = &report.Spec.Process.Rlimits[i]
}
}
if limit == unix.RLIM_INFINITY && !(rlim == nil || (rlim.Soft == unix.RLIM_INFINITY && rlim.Hard == unix.RLIM_INFINITY)) {
if limit == unix.RLIM_INFINITY && rlim != nil && (rlim.Soft != unix.RLIM_INFINITY || rlim.Hard != unix.RLIM_INFINITY) {
t.Fatalf("wasn't supposed to set limit on number of open files: %#v", rlim)
}
if limit != unix.RLIM_INFINITY && rlim == nil {

View File

@ -4,16 +4,12 @@ package chroot
import (
"fmt"
"os"
"github.com/containers/common/pkg/seccomp"
specs "github.com/opencontainers/runtime-spec/specs-go"
libseccomp "github.com/seccomp/libseccomp-golang"
"github.com/sirupsen/logrus"
)
const seccompAvailable = true
// setSeccomp sets the seccomp filter for ourselves and any processes that we'll start.
func setSeccomp(spec *specs.Spec) error {
logrus.Debugf("setting seccomp configuration")
@ -178,27 +174,3 @@ func setSeccomp(spec *specs.Spec) error {
}
return nil
}
func setupSeccomp(spec *specs.Spec, seccompProfilePath string) error {
switch seccompProfilePath {
case "unconfined":
spec.Linux.Seccomp = nil
case "":
seccompConfig, err := seccomp.GetDefaultProfile(spec)
if err != nil {
return fmt.Errorf("loading default seccomp profile failed: %w", err)
}
spec.Linux.Seccomp = seccompConfig
default:
seccompProfile, err := os.ReadFile(seccompProfilePath)
if err != nil {
return fmt.Errorf("opening seccomp profile failed: %w", err)
}
seccompConfig, err := seccomp.LoadProfile(string(seccompProfile), spec)
if err != nil {
return fmt.Errorf("loading seccomp profile (%s) failed: %w", seccompProfilePath, err)
}
spec.Linux.Seccomp = seccompConfig
}
return nil
}

37
chroot/seccomp_test.go Normal file
View File

@ -0,0 +1,37 @@
//go:build linux && seccomp
package chroot
import (
"fmt"
"os"
"github.com/containers/common/pkg/seccomp"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
const seccompAvailable = true
func setupSeccomp(spec *specs.Spec, seccompProfilePath string) error {
switch seccompProfilePath {
case "unconfined":
spec.Linux.Seccomp = nil
case "":
seccompConfig, err := seccomp.GetDefaultProfile(spec)
if err != nil {
return fmt.Errorf("loading default seccomp profile failed: %w", err)
}
spec.Linux.Seccomp = seccompConfig
default:
seccompProfile, err := os.ReadFile(seccompProfilePath)
if err != nil {
return fmt.Errorf("opening seccomp profile failed: %w", err)
}
seccompConfig, err := seccomp.LoadProfile(string(seccompProfile), spec)
if err != nil {
return fmt.Errorf("loading seccomp profile (%s) failed: %w", seccompProfilePath, err)
}
spec.Linux.Seccomp = seccompConfig
}
return nil
}

View File

@ -8,19 +8,9 @@ import (
"github.com/opencontainers/runtime-spec/specs-go"
)
const seccompAvailable = false
func setSeccomp(spec *specs.Spec) error {
if spec.Linux.Seccomp != nil {
return errors.New("configured a seccomp filter without seccomp support?")
}
return nil
}
func setupSeccomp(spec *specs.Spec, seccompProfilePath string) error {
if spec.Linux != nil {
// runtime-tools may have supplied us with a default filter
spec.Linux.Seccomp = nil
}
return nil
}

View File

@ -0,0 +1,17 @@
//go:build (!linux && !freebsd) || !seccomp
package chroot
import (
"github.com/opencontainers/runtime-spec/specs-go"
)
const seccompAvailable = false
func setupSeccomp(spec *specs.Spec, _ string) error {
if spec.Linux != nil {
// runtime-tools may have supplied us with a default filter
spec.Linux.Seccomp = nil
}
return nil
}

View File

@ -189,7 +189,7 @@ func imagesCmd(c *cobra.Command, args []string, iopts *imageResults) error {
func outputHeader(opts imageOptions) string {
if opts.format != "" {
return strings.Replace(opts.format, `\t`, "\t", -1)
return strings.ReplaceAll(opts.format, `\t`, "\t")
}
if opts.quiet {
return formats.IDString

View File

@ -73,7 +73,7 @@ func infoCmd(c *cobra.Command, iopts infoResults) error {
}
t, err := template.New("format").Parse(format)
if err != nil {
return fmt.Errorf("Template parsing error: %w", err)
return fmt.Errorf("template parsing error: %w", err)
}
if err = t.Execute(os.Stdout, info); err != nil {
return err

View File

@ -115,7 +115,7 @@ func inspectCmd(c *cobra.Command, args []string, iopts inspectResults) error {
}
t, err := template.New("format").Parse(format)
if err != nil {
return fmt.Errorf("Template parsing error: %w", err)
return fmt.Errorf("template parsing error: %w", err)
}
if err = t.Execute(os.Stdout, out); err != nil {
return err

View File

@ -27,7 +27,6 @@ import (
"github.com/hashicorp/go-multierror"
digest "github.com/opencontainers/go-digest"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@ -148,7 +147,7 @@ func init() {
flags.StringVar(&manifestAddOpts.artifactConfigType, "artifact-config-type", imgspecv1.DescriptorEmptyJSON.MediaType, "artifact config media type")
flags.StringVar(&manifestAddOpts.artifactConfigFile, "artifact-config", "", "artifact config file")
flags.StringVar(&manifestAddOpts.artifactLayerType, "artifact-layer-type", "", "artifact layer media type")
flags.BoolVar(&manifestAddOpts.artifactExcludeTitles, "artifact-exclude-titles", false, fmt.Sprintf(`refrain from setting %q annotations on "layers"`, v1.AnnotationTitle))
flags.BoolVar(&manifestAddOpts.artifactExcludeTitles, "artifact-exclude-titles", false, fmt.Sprintf(`refrain from setting %q annotations on "layers"`, imgspecv1.AnnotationTitle))
flags.StringVar(&manifestAddOpts.artifactSubject, "artifact-subject", "", "artifact subject reference")
flags.StringSliceVar(&manifestAddOpts.artifactAnnotations, "artifact-annotation", nil, "artifact annotation")
flags.StringVar(&manifestAddOpts.authfile, "authfile", auth.GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
@ -291,7 +290,7 @@ func init() {
func manifestExistsCmd(c *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("At least a name must be specified for the list")
return errors.New("at least a name must be specified for the list")
}
name := args[0]
@ -322,7 +321,7 @@ func manifestExistsCmd(c *cobra.Command, args []string) error {
func manifestCreateCmd(c *cobra.Command, args []string, opts manifestCreateOpts) error {
if len(args) == 0 {
return errors.New("At least a name must be specified for the list")
return errors.New("at least a name must be specified for the list")
}
listImageSpec := args[0]
imageSpecs := args[1:]
@ -433,21 +432,21 @@ func manifestAddCmd(c *cobra.Command, args []string, opts manifestAddOpts) error
artifactSpec := []string{}
switch len(args) {
case 0, 1:
return errors.New("At least a list image and an image or artifact to add must be specified")
return errors.New("at least a list image and an image or artifact to add must be specified")
default:
listImageSpec = args[0]
if listImageSpec == "" {
return fmt.Errorf("Invalid image name %q", args[0])
return fmt.Errorf("invalid image name %q", args[0])
}
if opts.artifact {
artifactSpec = args[1:]
} else {
if len(args) > 2 {
return errors.New("Too many arguments: expected list and image add to list")
return errors.New("too many arguments: expected list and image add to list")
}
imageSpec = args[1]
if imageSpec == "" {
return fmt.Errorf("Invalid image name %q", args[1])
return fmt.Errorf("invalid image name %q", args[1])
}
}
}
@ -635,18 +634,18 @@ func manifestRemoveCmd(c *cobra.Command, args []string, _ manifestRemoveOpts) er
var instanceSpec string
switch len(args) {
case 0, 1:
return errors.New("At least a list image and one or more instance digests must be specified")
return errors.New("at least a list image and one or more instance digests must be specified")
case 2:
listImageSpec = args[0]
if listImageSpec == "" {
return fmt.Errorf(`Invalid image name "%s"`, args[0])
return fmt.Errorf(`invalid image name "%s"`, args[0])
}
instanceSpec = args[1]
if instanceSpec == "" {
return fmt.Errorf(`Invalid instance "%s"`, args[1])
return fmt.Errorf(`invalid instance "%s"`, args[1])
}
default:
return errors.New("At least two arguments are necessary: list and digest of instance to remove from list")
return errors.New("at least two arguments are necessary: list and digest of instance to remove from list")
}
store, err := getStore(c)
@ -677,23 +676,23 @@ func manifestRemoveCmd(c *cobra.Command, args []string, _ manifestRemoveOpts) er
if err != nil {
if instanceRef, err = alltransports.ParseImageName(util.DefaultTransport + instanceSpec); err != nil {
if instanceRef, _, err = util.FindImage(store, "", systemContext, instanceSpec); err != nil {
return fmt.Errorf(`Invalid instance "%s": %v`, instanceSpec, err)
return fmt.Errorf(`invalid instance "%s": %v`, instanceSpec, err)
}
}
}
ctx := getContext()
instanceImg, err := instanceRef.NewImageSource(ctx, systemContext)
if err != nil {
return fmt.Errorf("Reading image instance: %w", err)
return fmt.Errorf("reading image instance: %w", err)
}
defer instanceImg.Close()
manifestBytes, _, err := image.UnparsedInstance(instanceImg, nil).Manifest(ctx)
if err != nil {
return fmt.Errorf("Reading image instance manifest: %w", err)
return fmt.Errorf("reading image instance manifest: %w", err)
}
d, err = manifest.Digest(manifestBytes)
if err != nil {
return fmt.Errorf("Digesting image instance manifest: %w", err)
return fmt.Errorf("digesting image instance manifest: %w", err)
}
}
instanceDigest = d
@ -752,29 +751,29 @@ func manifestAnnotateCmd(c *cobra.Command, args []string, opts manifestAnnotateO
}
switch len(args) {
case 0:
return errors.New("At least a list image must be specified")
return errors.New("at least a list image must be specified")
case 1:
listImageSpec = args[0]
if listImageSpec == "" {
return fmt.Errorf(`Invalid image name "%s"`, args[0])
return fmt.Errorf(`invalid image name "%s"`, args[0])
}
if !opts.index {
return errors.New(`Expected an instance digest, image name, or artifact name`)
return errors.New(`expected an instance digest, image name, or artifact name`)
}
case 2:
listImageSpec = args[0]
if listImageSpec == "" {
return fmt.Errorf(`Invalid image name "%s"`, args[0])
return fmt.Errorf(`invalid image name "%s"`, args[0])
}
if opts.index {
return fmt.Errorf(`Did not expect image or artifact name "%s" when modifying the entire index`, args[1])
return fmt.Errorf(`did not expect image or artifact name "%s" when modifying the entire index`, args[1])
}
instanceSpec = args[1]
if instanceSpec == "" {
return fmt.Errorf(`Invalid instance digest, image name, or artifact name "%s"`, instanceSpec)
return fmt.Errorf(`invalid instance digest, image name, or artifact name "%s"`, instanceSpec)
}
default:
return errors.New("Expected either a list name and --index or a list name and an image digest or image name or artifact name")
return errors.New("expected either a list name and --index or a list name and an image digest or image name or artifact name")
}
store, err := getStore(c)
@ -817,23 +816,23 @@ func manifestAnnotateCmd(c *cobra.Command, args []string, opts manifestAnnotateO
if instanceRef, err = alltransports.ParseImageName(util.DefaultTransport + instanceSpec); err != nil {
// check if the local image exists
if instanceRef, _, err = util.FindImage(store, "", systemContext, instanceSpec); err != nil {
return fmt.Errorf(`Invalid instance "%s": %v`, instanceSpec, err)
return fmt.Errorf(`invalid instance "%s": %v`, instanceSpec, err)
}
}
}
ctx := getContext()
instanceImg, err := instanceRef.NewImageSource(ctx, systemContext)
if err != nil {
return fmt.Errorf("Reading image instance: %w", err)
return fmt.Errorf("reading image instance: %w", err)
}
defer instanceImg.Close()
manifestBytes, _, err := image.UnparsedInstance(instanceImg, nil).Manifest(ctx)
if err != nil {
return fmt.Errorf("Reading image instance manifest: %w", err)
return fmt.Errorf("reading image instance manifest: %w", err)
}
d, err = manifest.Digest(manifestBytes)
if err != nil {
return fmt.Errorf("Digesting image instance manifest: %w", err)
return fmt.Errorf("digesting image instance manifest: %w", err)
}
}
instance = d
@ -964,14 +963,14 @@ func manifestInspectCmd(c *cobra.Command, args []string, opts manifestInspectOpt
imageSpec := ""
switch len(args) {
case 0:
return errors.New("At least a source list ID must be specified")
return errors.New("at least a source list ID must be specified")
case 1:
imageSpec = args[0]
if imageSpec == "" {
return fmt.Errorf(`Invalid image name "%s"`, imageSpec)
return fmt.Errorf(`invalid image name "%s"`, imageSpec)
}
default:
return errors.New("Only one argument is necessary for inspect: an image name")
return errors.New("only one argument is necessary for inspect: an image name")
}
store, err := getStore(c)
@ -1093,7 +1092,7 @@ func manifestPushCmd(c *cobra.Command, args []string, opts pushOptions) error {
destSpec := ""
switch len(args) {
case 0:
return errors.New("At least a source list ID must be specified")
return errors.New("at least a source list ID must be specified")
case 1:
listImageSpec = args[0]
destSpec = "docker://" + listImageSpec
@ -1101,7 +1100,7 @@ func manifestPushCmd(c *cobra.Command, args []string, opts pushOptions) error {
listImageSpec = args[0]
destSpec = args[1]
default:
return errors.New("Only two arguments are necessary to push: source and destination")
return errors.New("only two arguments are necessary to push: source and destination")
}
if listImageSpec == "" {
return fmt.Errorf(`invalid image name "%s"`, listImageSpec)

View File

@ -135,7 +135,7 @@ func pushCmd(c *cobra.Command, args []string, iopts pushOptions) error {
return fmt.Errorf(`invalid image name "%s"`, args[0])
}
default:
return errors.New("Only two arguments are necessary to push: source and destination")
return errors.New("only two arguments are necessary to push: source and destination")
}
compress := define.Gzip

View File

@ -47,7 +47,7 @@ func renameCmd(c *cobra.Command, args []string) error {
}
if build, err := openBuilder(getContext(), store, newName); err == nil {
return fmt.Errorf("The container name %q is already in use by container %q", newName, build.ContainerID)
return fmt.Errorf("the container name %q is already in use by container %q", newName, build.ContainerID)
}
err = store.SetNames(builder.ContainerID, []string{newName})

View File

@ -30,10 +30,8 @@ func init() {
}
func umountCmd(c *cobra.Command, args []string) error {
umountAll := false
if c.Flag("all").Changed {
umountAll = true
}
umountAll := c.Flag("all").Changed
umountContainerErrStr := "error unmounting container"
if len(args) == 0 && !umountAll {
return errors.New("at least one container ID must be specified")

View File

@ -701,9 +701,9 @@ func copierWithSubprocess(bulkReader io.Reader, bulkWriter io.Writer, req reques
bulkReaderRead = nil
bulkWriterWrite.Close()
bulkWriterWrite = nil
killAndReturn := func(err error, step string) (*response, error) { // nolint: unparam
killAndReturn := func(err error, step string) error {
if err2 := cmd.Process.Kill(); err2 != nil {
return nil, fmt.Errorf("killing subprocess: %v; %s: %w", err2, step, err)
return fmt.Errorf("killing subprocess: %v; %s: %w", err2, step, err)
}
if errors.Is(err, io.ErrClosedPipe) || errors.Is(err, syscall.EPIPE) {
err2 := cmd.Wait()
@ -711,22 +711,22 @@ func copierWithSubprocess(bulkReader io.Reader, bulkWriter io.Writer, req reques
err = fmt.Errorf("%s: %w", errorText, err)
}
if err2 != nil {
return nil, fmt.Errorf("waiting on subprocess: %v; %s: %w", err2, step, err)
return fmt.Errorf("waiting on subprocess: %v; %s: %w", err2, step, err)
}
}
return nil, fmt.Errorf("%v: %w", step, err)
return fmt.Errorf("%v: %w", step, err)
}
if err = encoder.Encode(req); err != nil {
return killAndReturn(err, "error encoding work request for copier subprocess")
return nil, killAndReturn(err, "error encoding work request for copier subprocess")
}
if err = decoder.Decode(&resp); err != nil {
if errors.Is(err, io.EOF) && errorBuffer.Len() > 0 {
return killAndReturn(errors.New(errorBuffer.String()), "error in copier subprocess")
return nil, killAndReturn(errors.New(errorBuffer.String()), "error in copier subprocess")
}
return killAndReturn(err, "error decoding response from copier subprocess")
return nil, killAndReturn(err, "error decoding response from copier subprocess")
}
if err = encoder.Encode(&request{Request: requestQuit}); err != nil {
return killAndReturn(err, "error encoding quit request for copier subprocess")
return nil, killAndReturn(err, "error encoding quit request for copier subprocess")
}
stdinWrite.Close()
stdinWrite = nil
@ -975,7 +975,7 @@ func pathIsExcluded(root, path string, pm *fileutils.PatternMatcher) (string, bo
// Matches uses filepath.FromSlash() to convert candidates before
// checking if they match the patterns it's been given, implying that
// it expects Unix-style paths.
matches, err := pm.Matches(filepath.ToSlash(rel)) // nolint:staticcheck
matches, err := pm.Matches(filepath.ToSlash(rel)) //nolint:staticcheck
if err != nil {
return rel, false, fmt.Errorf("copier: error checking if %q is excluded: %w", rel, err)
}
@ -1009,7 +1009,7 @@ func resolvePath(root, path string, evaluateFinalComponent bool, pm *fileutils.P
}
excluded = excluded || thisExcluded
if !excluded {
if target, err := os.Readlink(filepath.Join(workingPath, components[0])); err == nil && !(len(components) == 1 && !evaluateFinalComponent) {
if target, err := os.Readlink(filepath.Join(workingPath, components[0])); err == nil && (len(components) != 1 || evaluateFinalComponent) {
followed++
if followed > maxLoopsFollowed {
return "", &os.PathError{
@ -1661,14 +1661,15 @@ func copierHandlerGetOne(srcfi os.FileInfo, symlinkTarget, name, contentPath str
return fmt.Errorf("getting fflags: %w", err)
}
var f *os.File
if hdr.Typeflag == tar.TypeReg {
switch hdr.Typeflag {
case tar.TypeReg:
// open the file first so that we don't write a header for it if we can't actually read it
f, err = os.Open(contentPath)
if err != nil {
return fmt.Errorf("opening file for adding its contents to archive: %w", err)
}
defer f.Close()
} else if hdr.Typeflag == tar.TypeDir {
case tar.TypeDir:
// open the directory file first to make sure we can access it.
f, err = os.Open(contentPath)
if err != nil {
@ -2080,7 +2081,7 @@ func copierHandlerPut(bulkReader io.Reader, req request, idMappings *idtools.IDM
// set xattrs, including some that might have been reset by chown()
if !req.PutOptions.StripXattrs {
xattrs := mapWithPrefixedKeysWithoutKeyPrefix(hdr.PAXRecords, xattrPAXRecordNamespace)
if err = Lsetxattrs(path, xattrs); err != nil { // nolint:staticcheck
if err = Lsetxattrs(path, xattrs); err != nil {
if !req.PutOptions.IgnoreXattrErrors {
return fmt.Errorf("copier: put: error setting extended attributes on %q: %w", path, err)
}

View File

@ -48,7 +48,7 @@ func makeContents(length int64) io.ReadCloser {
for count < length {
if _, err := buffered.Write([]byte{"0123456789abcdef"[count%16]}); err != nil {
buffered.Flush()
pipeWriter.CloseWithError(err) // nolint:errcheck
pipeWriter.CloseWithError(err)
return
}
count++
@ -111,7 +111,7 @@ func makeArchive(headers []tar.Header, contents map[string][]byte) io.ReadCloser
tw.Close()
buffered.Flush()
if err != nil {
pipeWriter.CloseWithError(err) // nolint:errcheck
pipeWriter.CloseWithError(err)
} else {
pipeWriter.Close()
}

View File

@ -3,9 +3,15 @@
package copier
import (
"os"
"testing"
)
const (
testModeMask = int64(os.ModePerm)
testIgnoreSymlinkDates = false
)
func TestPutChroot(t *testing.T) {
if uid != 0 {
t.Skip("chroot() requires root privileges, skipping")

View File

@ -0,0 +1,8 @@
//go:build windows
package copier
const (
testModeMask = int64(0o600)
testIgnoreSymlinkDates = true
)

View File

@ -84,8 +84,3 @@ func sameDevice(a, b os.FileInfo) bool {
}
return uA.Dev == uB.Dev
}
const (
testModeMask = int64(os.ModePerm)
testIgnoreSymlinkDates = false
)

View File

@ -81,8 +81,3 @@ func lutimes(isSymlink bool, path string, atime, mtime time.Time) error {
func sameDevice(a, b os.FileInfo) bool {
return true
}
const (
testModeMask = int64(0o600)
testIgnoreSymlinkDates = true
)

View File

@ -164,7 +164,7 @@ type V1Image struct {
// V2Image stores the image configuration
type V2Image struct {
V1Image
Parent ID `json:"parent,omitempty"` // nolint:govet
Parent ID `json:"parent,omitempty"`
RootFS *V2S2RootFS `json:"rootfs,omitempty"`
History []V2S2History `json:"history,omitempty"`
OSVersion string `json:"os.version,omitempty"`

View File

@ -451,7 +451,7 @@ func buildDockerfilesOnce(ctx context.Context, store storage.Store, logger *logr
if options.Target != "" {
stagesTargeted, ok := stages.ThroughTarget(options.Target)
if !ok {
return "", nil, fmt.Errorf("The target %q was not found in the provided Dockerfile", options.Target)
return "", nil, fmt.Errorf("the target %q was not found in the provided Dockerfile", options.Target)
}
stages = stagesTargeted
}

View File

@ -622,7 +622,7 @@ func (s *StageExecutor) runStageMountPoints(mountList []string) (map[string]inte
if strings.Contains(flag, "from") {
tokens := strings.Split(flag, ",")
if len(tokens) < 2 {
return nil, fmt.Errorf("Invalid --mount command: %s", flag)
return nil, fmt.Errorf("invalid --mount command: %s", flag)
}
for _, token := range tokens {
key, val, hasVal := strings.Cut(token, "=")
@ -712,8 +712,8 @@ func (s *StageExecutor) runStageMountPoints(mountList []string) (map[string]inte
MountPoint: otherStage.mountPoint,
}
break
} else {
// Treat the source's name as the name of an image.
}
// Otherwise, treat the source's name as the name of an image.
mountPoint, err := s.getImageRootfs(s.ctx, from)
if err != nil {
return nil, fmt.Errorf("%s from=%s: no stage or image found with that name", flag, from)
@ -723,8 +723,6 @@ func (s *StageExecutor) runStageMountPoints(mountList []string) (map[string]inte
DidExecute: true,
MountPoint: mountPoint,
}
break
}
default:
continue
}
@ -1547,7 +1545,7 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
// we need to call ib.Run() to correctly put the args together before
// determining if a cached layer with the same build args already exists
// and that is done in the if block below.
if checkForLayers && step.Command != "arg" && !(s.executor.squash && lastInstruction && lastStage) && !avoidLookingCache {
if checkForLayers && step.Command != "arg" && (!s.executor.squash || !lastInstruction || !lastStage) && !avoidLookingCache {
// For `COPY` and `ADD`, history entries include digests computed from
// the content that's copied in. We need to compute that information so that
// it can be used to evaluate the cache, which means we need to go ahead

View File

@ -19,7 +19,7 @@ import (
func importBuilderDataFromImage(ctx context.Context, store storage.Store, systemContext *types.SystemContext, imageID, containerName, containerID string) (*Builder, error) {
if imageID == "" {
return nil, errors.New("Internal error: imageID is empty in importBuilderDataFromImage")
return nil, errors.New("internal error: imageID is empty in importBuilderDataFromImage")
}
storeopts, err := storage.DefaultStoreOptions()

View File

@ -26,17 +26,18 @@ type TeeConfigFlags struct {
// TeeConfigFlagBits are bits representing run-time expectations.
type TeeConfigFlagBits int
//nolint:revive,staticcheck // Don't warn about bad naming.
const (
SEV_CONFIG_NO_DEBUG TeeConfigFlagBits = 0b00000001 //revive:disable-line:var-naming no debugging of guests
SEV_CONFIG_NO_KEY_SHARING TeeConfigFlagBits = 0b00000010 //revive:disable-line:var-naming no sharing keys between guests
SEV_CONFIG_ENCRYPTED_STATE TeeConfigFlagBits = 0b00000100 //revive:disable-line:var-naming requires SEV-ES
SEV_CONFIG_NO_SEND TeeConfigFlagBits = 0b00001000 //revive:disable-line:var-naming no transferring the guest to another platform
SEV_CONFIG_DOMAIN TeeConfigFlagBits = 0b00010000 //revive:disable-line:var-naming no transferring the guest out of the domain (?)
SEV_CONFIG_SEV TeeConfigFlagBits = 0b00100000 //revive:disable-line:var-naming no transferring the guest to non-SEV platforms
SNP_CONFIG_SMT TeeConfigFlagBits = 0b00000001 //revive:disable-line:var-naming SMT is enabled on the host machine
SNP_CONFIG_MANDATORY TeeConfigFlagBits = 0b00000010 //revive:disable-line:var-naming reserved bit which should always be set
SNP_CONFIG_MIGRATE_MA TeeConfigFlagBits = 0b00000100 //revive:disable-line:var-naming allowed to use a migration agent
SNP_CONFIG_DEBUG TeeConfigFlagBits = 0b00001000 //revive:disable-line:var-naming allow debugging
SEV_CONFIG_NO_DEBUG TeeConfigFlagBits = 0b00000001 // no debugging of guests
SEV_CONFIG_NO_KEY_SHARING TeeConfigFlagBits = 0b00000010 // no sharing keys between guests
SEV_CONFIG_ENCRYPTED_STATE TeeConfigFlagBits = 0b00000100 // requires SEV-ES
SEV_CONFIG_NO_SEND TeeConfigFlagBits = 0b00001000 // no transferring the guest to another platform
SEV_CONFIG_DOMAIN TeeConfigFlagBits = 0b00010000 // no transferring the guest out of the domain (?)
SEV_CONFIG_SEV TeeConfigFlagBits = 0b00100000 // no transferring the guest to non-SEV platforms
SNP_CONFIG_SMT TeeConfigFlagBits = 0b00000001 // SMT is enabled on the host machine
SNP_CONFIG_MANDATORY TeeConfigFlagBits = 0b00000010 // reserved bit which should always be set
SNP_CONFIG_MIGRATE_MA TeeConfigFlagBits = 0b00000100 // allowed to use a migration agent
SNP_CONFIG_DEBUG TeeConfigFlagBits = 0b00001000 // allow debugging
)
// TeeConfigFlagMinFW corresponds to a minimum version of the kernel+initrd

View File

@ -28,7 +28,8 @@ type SnpWorkloadData struct {
Generation string `json:"gen"` // "milan" (naples=1, rome=2, milan=3, genoa/bergamo/siena=4, turin=5)
}
//nolint:revive,staticcheck // Don't warn about bad naming.
const (
// SEV_NO_ES is a known trusted execution environment type: AMD-SEV (secure encrypted virtualization without encrypted state, requires epyc 1000 "naples")
SEV_NO_ES define.TeeType = "sev_no_es" //revive:disable-line:var-naming
SEV_NO_ES define.TeeType = "sev_no_es"
)

View File

@ -28,18 +28,23 @@ type (
const (
maxWorkloadConfigSize = 1024 * 1024
preferredPaddingBoundary = 4096
// SEV is a known trusted execution environment type: AMD-SEV
SEV = define.SEV
// SEV_NO_ES is a known trusted execution environment type: AMD-SEV without encrypted state
SEV_NO_ES = types.SEV_NO_ES //revive:disable-line:var-naming
// SNP is a known trusted execution environment type: AMD-SNP
SNP = define.SNP
// krun looks for its configuration JSON directly in a disk image if the last twelve bytes
// of the disk image are this magic value followed by a little-endian 64-bit
// length-of-the-configuration
krunMagic = "KRUN"
)
//nolint:revive,staticcheck
const (
// SEV is a known trusted execution environment type: AMD-SEV
SEV = define.SEV
// SEV_NO_ES is a known trusted execution environment type: AMD-SEV without encrypted state
SEV_NO_ES = types.SEV_NO_ES
// SNP is a known trusted execution environment type: AMD-SNP
SNP = define.SNP
)
// ReadWorkloadConfigFromImage reads the workload configuration from the
// specified disk image file
func ReadWorkloadConfigFromImage(path string) (WorkloadConfig, error) {

View File

@ -94,7 +94,7 @@ func (t StdoutTemplateArray) Out() error {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
if strings.HasPrefix(t.Template, "table") {
// replace any spaces with tabs in template so that tabwriter can align it
t.Template = strings.Replace(strings.TrimSpace(t.Template[5:]), " ", "\t", -1)
t.Template = strings.ReplaceAll(strings.TrimSpace(t.Template[5:]), " ", "\t")
headerTmpl, err := template.New("header").Funcs(headerFunctions).Parse(t.Template)
if err != nil {
return fmt.Errorf("%v: %w", parsingErrorStr, err)
@ -105,7 +105,7 @@ func (t StdoutTemplateArray) Out() error {
}
fmt.Fprintln(w, "")
}
t.Template = strings.Replace(t.Template, " ", "\t", -1)
t.Template = strings.ReplaceAll(t.Template, " ", "\t")
tmpl, err := template.New("image").Funcs(basicFunctions).Parse(t.Template)
if err != nil {
return fmt.Errorf("%v: %w", parsingErrorStr, err)

View File

@ -705,11 +705,12 @@ func GetBuildOutput(buildOutput string) (define.BuildOutputOption, error) {
return define.BuildOutputOption{}, fmt.Errorf("duplicate %q not supported", arr[0])
}
typeSelected = true
if arr[1] == "local" {
switch arr[1] {
case "local":
isDir = true
} else if arr[1] == "tar" {
case "tar":
isDir = false
} else {
default:
return define.BuildOutputOption{}, fmt.Errorf("invalid type %q selected for build output options %q", arr[1], buildOutput)
}
case "dest":

View File

@ -22,8 +22,8 @@ func get() (Rusage, error) {
Date: time.Now(),
Utime: mkduration(rusage.Utime),
Stime: mkduration(rusage.Stime),
Inblock: int64(rusage.Inblock), // nolint: unconvert
Outblock: int64(rusage.Oublock), // nolint: unconvert
Inblock: int64(rusage.Inblock), //nolint:unconvert
Outblock: int64(rusage.Oublock), //nolint:unconvert
}
return r, nil
}

View File

@ -734,7 +734,7 @@ func runUsingRuntime(options RunOptions, configureNetwork bool, moreCreateArgs [
return wstatus, nil
}
func runCollectOutput(logger *logrus.Logger, fds, closeBeforeReadingFds []int) string { //nolint:interfacer
func runCollectOutput(logger *logrus.Logger, fds, closeBeforeReadingFds []int) string {
for _, fd := range closeBeforeReadingFds {
unix.Close(fd)
}
@ -780,7 +780,7 @@ func runCollectOutput(logger *logrus.Logger, fds, closeBeforeReadingFds []int) s
return b.String()
}
func setNonblock(logger *logrus.Logger, fd int, description string, nonblocking bool) (bool, error) { //nolint:interfacer
func setNonblock(logger *logrus.Logger, fd int, description string, nonblocking bool) (bool, error) {
mask, err := unix.FcntlInt(uintptr(fd), unix.F_GETFL, 0)
if err != nil {
return false, err
@ -870,13 +870,13 @@ func runCopyStdio(logger *logrus.Logger, stdio *sync.WaitGroup, copyPipes bool,
return
}
if blocked {
defer setNonblock(logger, rfd, readDesc[rfd], false) // nolint:errcheck
defer setNonblock(logger, rfd, readDesc[rfd], false) //nolint:errcheck
}
setNonblock(logger, wfd, writeDesc[wfd], false) // nolint:errcheck
setNonblock(logger, wfd, writeDesc[wfd], false) //nolint:errcheck
}
if copyPipes {
setNonblock(logger, stdioPipe[unix.Stdin][1], writeDesc[stdioPipe[unix.Stdin][1]], true) // nolint:errcheck
setNonblock(logger, stdioPipe[unix.Stdin][1], writeDesc[stdioPipe[unix.Stdin][1]], true) //nolint:errcheck
}
runCopyStdioPassData(copyPipes, stdioPipe, finishCopy, relayMap, relayBuffer, readDesc, writeDesc)

View File

@ -456,7 +456,7 @@ func (b *Builder) Run(command []string, options RunOptions) error {
}
resolvFile := ""
if !slices.Contains(volumes, resolvconf.DefaultResolvConf) && options.ConfigureNetwork != define.NetworkDisabled && !(len(b.CommonBuildOpts.DNSServers) == 1 && strings.ToLower(b.CommonBuildOpts.DNSServers[0]) == "none") {
if !slices.Contains(volumes, resolvconf.DefaultResolvConf) && options.ConfigureNetwork != define.NetworkDisabled && (len(b.CommonBuildOpts.DNSServers) != 1 || strings.ToLower(b.CommonBuildOpts.DNSServers[0]) != "none") {
resolvFile, err = b.createResolvConf(path, rootIDPair)
if err != nil {
return err
@ -691,9 +691,9 @@ func setupSlirp4netnsNetwork(config *config.Config, netns, cid string, options,
}
return func() {
syscall.Kill(res.Pid, syscall.SIGKILL) // nolint:errcheck
syscall.Kill(res.Pid, syscall.SIGKILL) //nolint:errcheck
var status syscall.WaitStatus
syscall.Wait4(res.Pid, &status, 0, nil) // nolint:errcheck
syscall.Wait4(res.Pid, &status, 0, nil) //nolint:errcheck
}, result, nil
}
@ -1062,28 +1062,28 @@ func addRlimits(ulimit []string, g *generate.Generator, defaultUlimits []string)
g.AddProcessRlimits("RLIMIT_"+strings.ToUpper(ul.Name), uint64(ul.Hard), uint64(ul.Soft))
}
if !nofileSet {
max := define.RLimitDefaultValue
lim := define.RLimitDefaultValue
var rlimit unix.Rlimit
if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rlimit); err == nil {
if max < rlimit.Max || unshare.IsRootless() {
max = rlimit.Max
if lim < rlimit.Max || unshare.IsRootless() {
lim = rlimit.Max
}
} else {
logrus.Warnf("Failed to return RLIMIT_NOFILE ulimit %q", err)
}
g.AddProcessRlimits("RLIMIT_NOFILE", max, max)
g.AddProcessRlimits("RLIMIT_NOFILE", lim, lim)
}
if !nprocSet {
max := define.RLimitDefaultValue
lim := define.RLimitDefaultValue
var rlimit unix.Rlimit
if err := unix.Getrlimit(unix.RLIMIT_NPROC, &rlimit); err == nil {
if max < rlimit.Max || unshare.IsRootless() {
max = rlimit.Max
if lim < rlimit.Max || unshare.IsRootless() {
lim = rlimit.Max
}
} else {
logrus.Warnf("Failed to return RLIMIT_NPROC ulimit %q", err)
}
g.AddProcessRlimits("RLIMIT_NPROC", max, max)
g.AddProcessRlimits("RLIMIT_NPROC", lim, lim)
}
return nil

View File

@ -35,7 +35,6 @@ import (
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/pkg/compression"
is "github.com/containers/image/v5/storage"
istorage "github.com/containers/image/v5/storage"
"github.com/containers/image/v5/transports"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
@ -648,7 +647,7 @@ func buildUsingBuildah(ctx context.Context, t *testing.T, store storage.Store, t
// return a reference to the new image, if we succeeded
if err == nil {
buildahRef, err = istorage.Transport.ParseStoreReference(store, imageID)
buildahRef, err = is.Transport.ParseStoreReference(store, imageID)
assert.Nil(t, err, "error parsing reference to newly-built image with ID %q", imageID)
}
return buildahRef, []byte(outputString)
@ -935,7 +934,7 @@ func fsHeaderForEntry(hdr *tar.Header) FSHeader {
ModTime: hdr.ModTime,
Devmajor: hdr.Devmajor,
Devminor: hdr.Devminor,
Xattrs: hdr.Xattrs, // nolint:staticcheck
Xattrs: hdr.Xattrs, //nolint:staticcheck
}
}
@ -1358,7 +1357,7 @@ func configCompareResult(miss, left, diff []string, notDocker string) string {
if len(diff) > 0 {
buffer.WriteString("Fields present in both versions have different values:\n")
tw := tabwriter.NewWriter(&buffer, 1, 1, 8, ' ', 0)
if _, err := tw.Write([]byte(fmt.Sprintf("Field\tDocker\t%s\n", notDocker))); err != nil {
if _, err := fmt.Fprintf(tw, "Field\tDocker\t%s\n", notDocker); err != nil {
panic(err)
}
for _, d := range diff {
@ -1390,7 +1389,7 @@ func fsCompareResult(miss, left, diff []string, notDocker string) string {
if len(diff) > 0 {
buffer.WriteString("File attributes in both versions have different values:\n")
tw := tabwriter.NewWriter(&buffer, 1, 1, 8, ' ', 0)
if _, err := tw.Write([]byte(fmt.Sprintf("File:attr\tDocker\t%s\n", notDocker))); err != nil {
if _, err := fmt.Fprintf(tw, "File:attr\tDocker\t%s\n", notDocker); err != nil {
panic(err)
}
for _, d := range fixup(diff) {

View File

@ -1,5 +1,3 @@
//go:build linux
package conformance
import (

View File

@ -97,7 +97,7 @@ func main() {
errors := false
defer func() {
store.Shutdown(false) // nolint:errcheck
store.Shutdown(false) //nolint:errcheck
if errors {
os.Exit(1)
}

View File

@ -3,7 +3,6 @@ package main
import (
"context"
"errors"
"fmt"
"log"
"net"
"net/http"
@ -49,14 +48,10 @@ func main() {
key = args[5]
}
if len(args) > 6 && args[6] != "" {
f, err := os.Create(args[6])
err := os.WriteFile(args[6], []byte(strconv.Itoa(os.Getpid())), 0o644)
if err != nil {
log.Fatalf("%v", err)
}
if _, err := f.WriteString(fmt.Sprintf("%d", os.Getpid())); err != nil {
log.Fatalf("%v", err)
}
f.Close()
}
http.HandleFunc("/", sendThatFile(basedir))
server := http.Server{

View File

@ -325,7 +325,7 @@ func getLinuxSysctl(r *types.TestReport) error {
return fmt.Errorf("reading sysctl %q: %w", path, err)
}
path = strings.TrimPrefix(path, "/proc/sys/")
sysctl := strings.Replace(path, "/", ".", -1)
sysctl := strings.ReplaceAll(path, "/", ".")
val := strings.TrimRight(string(value), "\r\n")
if strings.ContainsAny(val, "\r\n") {
val = string(value)

View File

@ -33,5 +33,16 @@ $(BUILDDIR): \
$(BUILDDIR)/go-md2man: $(SOURCES)
$(GO_BUILD) -o $@ ./vendor/github.com/cpuguy83/go-md2man/v2
$(BUILDDIR)/golangci-lint: $(SOURCES)
$(GO_BUILD) -o $@ ./vendor/github.com/golangci/golangci-lint/cmd/golangci-lint
# Use GOLANGCI_LINT_VERSION exported in top-level Makefile,
# or, if called directly, use "latest".
$(BUILDDIR)/golangci-lint: VERSION=$(if $(GOLANGCI_LINT_VERSION),v$(GOLANGCI_LINT_VERSION),latest)
$(BUILDDIR)/golangci-lint: check-lint-version
@test -f $(BUILDDIR)/golangci-lint || \
curl -fsSL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b ./$(BUILDDIR) $(VERSION)
.PHONY: check-lint-version
check-lint-version: VERSION=$(GOLANGCI_LINT_VERSION)
check-lint-version:
@test -n "$(VERSION)" && \
$(BUILDDIR)/golangci-lint version 2>/dev/null | grep -F $(VERSION) || \
rm -f $(BUILDDIR)/golangci-lint

View File

@ -2,190 +2,6 @@ module github.com/containers/buildah/tests/tools
go 1.22.1
require (
github.com/cpuguy83/go-md2man/v2 v2.0.4
github.com/golangci/golangci-lint v1.61.0
)
require github.com/cpuguy83/go-md2man/v2 v2.0.4
require (
4d63.com/gocheckcompilerdirectives v1.2.1 // indirect
4d63.com/gochecknoglobals v0.2.1 // indirect
github.com/4meepo/tagalign v1.3.4 // indirect
github.com/Abirdcfly/dupword v0.1.1 // indirect
github.com/Antonboom/errname v0.1.13 // indirect
github.com/Antonboom/nilnil v0.1.9 // indirect
github.com/Antonboom/testifylint v1.4.3 // indirect
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect
github.com/Crocmagnon/fatcontext v0.5.2 // indirect
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect
github.com/alecthomas/go-check-sumtype v0.1.4 // indirect
github.com/alexkohler/nakedret/v2 v2.0.4 // indirect
github.com/alexkohler/prealloc v1.0.0 // indirect
github.com/alingse/asasalint v0.0.11 // indirect
github.com/ashanbrown/forbidigo v1.6.0 // indirect
github.com/ashanbrown/makezero v1.1.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bkielbasa/cyclop v1.2.1 // indirect
github.com/blizzy78/varnamelen v0.8.0 // indirect
github.com/bombsimon/wsl/v4 v4.4.1 // indirect
github.com/breml/bidichk v0.2.7 // indirect
github.com/breml/errchkjson v0.3.6 // indirect
github.com/butuzov/ireturn v0.3.0 // indirect
github.com/butuzov/mirror v1.2.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.1.2 // indirect
github.com/charithe/durationcheck v0.0.10 // indirect
github.com/chavacava/garif v0.1.0 // indirect
github.com/ckaznocha/intrange v0.2.0 // indirect
github.com/curioswitch/go-reassign v0.2.0 // indirect
github.com/daixiang0/gci v0.13.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/denis-tingaikin/go-header v0.5.0 // indirect
github.com/ettle/strcase v0.2.0 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
github.com/firefart/nonamedreturns v1.0.5 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/fzipp/gocyclo v0.6.0 // indirect
github.com/ghostiam/protogetter v0.3.6 // indirect
github.com/go-critic/go-critic v0.11.4 // 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
github.com/go-toolsmith/astfmt v1.1.0 // indirect
github.com/go-toolsmith/astp v1.1.0 // indirect
github.com/go-toolsmith/strparse v1.1.0 // indirect
github.com/go-toolsmith/typep v1.1.0 // indirect
github.com/go-viper/mapstructure/v2 v2.1.0 // indirect
github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gofrs/flock v0.12.1 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect
github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 // indirect
github.com/golangci/misspell v0.6.0 // indirect
github.com/golangci/modinfo v0.3.4 // indirect
github.com/golangci/plugin-module-register v0.1.1 // indirect
github.com/golangci/revgrep v0.5.3 // indirect
github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/gordonklaus/ineffassign v0.1.0 // indirect
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
github.com/gostaticanalysis/comment v1.4.2 // indirect
github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect
github.com/gostaticanalysis/nilerr v0.1.1 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jgautheron/goconst v1.7.1 // 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.6.2 // indirect
github.com/julz/importas v0.1.0 // indirect
github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect
github.com/kisielk/errcheck v1.7.0 // indirect
github.com/kkHAIKE/contextcheck v1.1.5 // indirect
github.com/kulti/thelper v0.6.3 // indirect
github.com/kunwardeep/paralleltest v1.0.10 // indirect
github.com/kyoh86/exportloopref v0.1.11 // indirect
github.com/lasiar/canonicalheader v1.1.1 // indirect
github.com/ldez/gomoddirectives v0.2.4 // indirect
github.com/ldez/tagliatelle v0.5.0 // indirect
github.com/leonklingele/grouper v1.1.2 // indirect
github.com/lufeee/execinquery v1.2.1 // indirect
github.com/macabu/inamedparam v0.1.3 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/maratori/testableexamples v1.0.0 // indirect
github.com/maratori/testpackage v1.1.1 // indirect
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mgechev/revive v1.3.9 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moricho/tparallel v0.3.2 // indirect
github.com/nakabonne/nestif v0.3.1 // indirect
github.com/nishanths/exhaustive v0.12.0 // indirect
github.com/nishanths/predeclared v0.2.2 // indirect
github.com/nunnatsa/ginkgolinter v0.16.2 // 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.2.3 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/polyfloyd/go-errorlint v1.6.0 // 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.3-0.20240823090925-0fe6f58b47b1 // indirect
github.com/quasilyte/go-ruleguard/dsl v0.3.22 // 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/russross/blackfriday/v2 v2.1.0 // indirect
github.com/ryancurrah/gomodguard v1.3.5 // 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.27.0 // indirect
github.com/securego/gosec/v2 v2.21.2 // indirect
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sivchari/containedctx v1.0.3 // indirect
github.com/sivchari/tenv v1.10.0 // indirect
github.com/sonatard/noctx v0.0.2 // indirect
github.com/sourcegraph/go-diff v0.7.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/cobra v1.8.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.12.0 // indirect
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
github.com/tdakkota/asciicheck v0.2.0 // indirect
github.com/tetafro/godot v1.4.17 // 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.9.0 // 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.1 // indirect
github.com/uudashr/gocognit v1.1.3 // indirect
github.com/xen0n/gosmopolitan v1.2.2 // indirect
github.com/yagipy/maintidx v1.0.0 // indirect
github.com/yeya24/promlinter v0.3.0 // indirect
github.com/ykadowak/zerologlint v0.1.5 // indirect
gitlab.com/bosi/decorder v0.4.2 // indirect
go-simpler.org/musttag v0.12.2 // indirect
go-simpler.org/sloglint v0.7.2 // 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-20240904232852-e7e105dedf7e // indirect
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/tools v0.24.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
honnef.co/go/tools v0.5.1 // indirect
mvdan.cc/gofumpt v0.7.0 // indirect
mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect
)
require github.com/russross/blackfriday/v2 v2.1.0 // indirect

View File

@ -1,954 +1,4 @@
4d63.com/gocheckcompilerdirectives v1.2.1 h1:AHcMYuw56NPjq/2y615IGg2kYkBdTvOaojYCBcRE7MA=
4d63.com/gocheckcompilerdirectives v1.2.1/go.mod h1:yjDJSxmDTtIHHCqX0ufRYZDL6vQtMG7tJdKVeWwsqvs=
4d63.com/gochecknoglobals v0.2.1 h1:1eiorGsgHOFOuoOiJDy2psSrQbRdIHrlge0IJIkUgDc=
4d63.com/gochecknoglobals v0.2.1/go.mod h1:KRE8wtJB3CXCsb1xy421JfTHIIbmT3U5ruxw2Qu8fSU=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/4meepo/tagalign v1.3.4 h1:P51VcvBnf04YkHzjfclN6BbsopfJR5rxs1n+5zHt+w8=
github.com/4meepo/tagalign v1.3.4/go.mod h1:M+pnkHH2vG8+qhE5bVc/zeP7HS/j910Fwa9TUSyZVI0=
github.com/Abirdcfly/dupword v0.1.1 h1:Bsxe0fIw6OwBtXMIncaTxCLHYO5BB+3mcsR5E8VXloY=
github.com/Abirdcfly/dupword v0.1.1/go.mod h1:B49AcJdTYYkpd4HjgAcutNGG9HZ2JWwKunH9Y2BA6sM=
github.com/Antonboom/errname v0.1.13 h1:JHICqsewj/fNckzrfVSe+T33svwQxmjC+1ntDsHOVvM=
github.com/Antonboom/errname v0.1.13/go.mod h1:uWyefRYRN54lBg6HseYCFhs6Qjcy41Y3Jl/dVhA87Ns=
github.com/Antonboom/nilnil v0.1.9 h1:eKFMejSxPSA9eLSensFmjW2XTgTwJMjZ8hUHtV4s/SQ=
github.com/Antonboom/nilnil v0.1.9/go.mod h1:iGe2rYwCq5/Me1khrysB4nwI7swQvjclR8/YRPl5ihQ=
github.com/Antonboom/testifylint v1.4.3 h1:ohMt6AHuHgttaQ1xb6SSnxCeK4/rnK7KKzbvs7DmEck=
github.com/Antonboom/testifylint v1.4.3/go.mod h1:+8Q9+AOLsz5ZiQiiYujJKs9mNz398+M6UgslP4qgJLA=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Crocmagnon/fatcontext v0.5.2 h1:vhSEg8Gqng8awhPju2w7MKHqMlg4/NI+gSDHtR3xgwA=
github.com/Crocmagnon/fatcontext v0.5.2/go.mod h1:87XhRMaInHP44Q7Tlc7jkgKKB7kZAOPiDkFMdKCC+74=
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM=
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 h1:/fTUt5vmbkAcMBt4YQiuC23cV0kEsN1MVMNqeOW43cU=
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0/go.mod h1:ONJg5sxcbsdQQ4pOW8TGdTidT2TMAUy/2Xhr8mrYaao=
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA=
github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ=
github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk=
github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
github.com/alecthomas/go-check-sumtype v0.1.4 h1:WCvlB3l5Vq5dZQTFmodqL2g68uHiSwwlWcT5a2FGK0c=
github.com/alecthomas/go-check-sumtype v0.1.4/go.mod h1:WyYPfhfkdhyrdaligV6svFopZV8Lqdzn5pyVBaV6jhQ=
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
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.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=
github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I=
github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY=
github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU=
github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s=
github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bkielbasa/cyclop v1.2.1 h1:AeF71HZDob1P2/pRm1so9cd1alZnrpyc4q2uP2l0gJY=
github.com/bkielbasa/cyclop v1.2.1/go.mod h1:K/dT/M0FPAiYjBgQGau7tz+3TMh4FWAEqlMhzFWCrgM=
github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M=
github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k=
github.com/bombsimon/wsl/v4 v4.4.1 h1:jfUaCkN+aUpobrMO24zwyAMwMAV5eSziCkOKEauOLdw=
github.com/bombsimon/wsl/v4 v4.4.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo=
github.com/breml/bidichk v0.2.7 h1:dAkKQPLl/Qrk7hnP6P+E0xOodrq8Us7+U0o4UBOAlQY=
github.com/breml/bidichk v0.2.7/go.mod h1:YodjipAGI9fGcYM7II6wFvGhdMYsC5pHDlGzqvEW3tQ=
github.com/breml/errchkjson v0.3.6 h1:VLhVkqSBH96AvXEyclMR37rZslRrY2kcyq+31HCsVrA=
github.com/breml/errchkjson v0.3.6/go.mod h1:jhSDoFheAF2RSDOlCfhHO9KqhZgAYLyvHe7bRCX8f/U=
github.com/butuzov/ireturn v0.3.0 h1:hTjMqWw3y5JC3kpnC5vXmFJAWI/m31jaCYQqzkS6PL0=
github.com/butuzov/ireturn v0.3.0/go.mod h1:A09nIiwiqzN/IoVo9ogpa0Hzi9fex1kd9PSD6edP5ZA=
github.com/butuzov/mirror v1.2.0 h1:9YVK1qIjNspaqWutSv8gsge2e/Xpq1eqEkslEUHy5cs=
github.com/butuzov/mirror v1.2.0/go.mod h1:DqZZDtzm42wIAIyHXeN8W/qb1EPlb9Qn/if9icBOpdQ=
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=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4=
github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ=
github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc=
github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/ckaznocha/intrange v0.2.0 h1:FykcZuJ8BD7oX93YbO1UY9oZtkRbp+1/kJcDjkefYLs=
github.com/ckaznocha/intrange v0.2.0/go.mod h1:r5I7nUlAAG56xmkOpw4XVr16BXhwYTUdcuRFeevn1oE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo=
github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc=
github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c=
github.com/daixiang0/gci v0.13.5/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk=
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.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8=
github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY=
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/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.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA=
github.com/firefart/nonamedreturns v1.0.5/go.mod h1:gHJjDqhGM4WyPt639SOZs+G89Ko7QKH5R5BhnO6xJhw=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
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.6 h1:R7qEWaSgFCsy20yYHNIJsU9ZOb8TziSRRxuAOTVKeOk=
github.com/ghostiam/protogetter v0.3.6/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw=
github.com/go-critic/go-critic v0.11.4 h1:O7kGOCx0NDIni4czrkRIXTnit0mkyKOCePh3My6OyEU=
github.com/go-critic/go-critic v0.11.4/go.mod h1:2QAdo4iuLik5S9YG0rT4wcZ8QxwHYkrr6/2MWAiv/vc=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8=
github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU=
github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s=
github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw=
github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4=
github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ=
github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw=
github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY=
github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco=
github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4=
github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA=
github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA=
github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk=
github.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus=
github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=
github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw=
github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ=
github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus=
github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig=
github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w=
github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U=
github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
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/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/gofmt v0.0.0-20240816233607-d8596aa466a9 h1:/1322Qns6BtQxUZDTAT4SdcoxknUki7IAoK4SAXr8ME=
github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9/go.mod h1:Oesb/0uFAyWoaw1U1qS5zyjCg5NP9C9iwjnI4tIsXEE=
github.com/golangci/golangci-lint v1.61.0 h1:VvbOLaRVWmyxCnUIMTbf1kDsaJbTzH20FAMXTAlQGu8=
github.com/golangci/golangci-lint v1.61.0/go.mod h1:e4lztIrJJgLPhWvFPDkhiMwEFRrWlmFbrZea3FsJyN8=
github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs=
github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo=
github.com/golangci/modinfo v0.3.4 h1:oU5huX3fbxqQXdfspamej74DFX0kyGLkw1ppvXoJ8GA=
github.com/golangci/modinfo v0.3.4/go.mod h1:wytF1M5xl9u0ij8YSvhkEVPP3M5Mc7XLl1pxH3B2aUM=
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.3 h1:3tL7c1XBMtWHHqVpS5ChmiAAoe4PF/d5+ULzV9sLAzs=
github.com/golangci/revgrep v0.5.3/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k=
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=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA=
github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s=
github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk=
github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc=
github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado=
github.com/gostaticanalysis/comment v1.4.2 h1:hlnx5+S2fY9Zo9ePo4AhgYsYHbM2+eAv8m/s1JiCd6Q=
github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM=
github.com/gostaticanalysis/forcetypeassert v0.1.0 h1:6eUflI3DiGusXGK6X7cCcIgVCpZ2CiZ1Q7jl6ZxNV70=
github.com/gostaticanalysis/forcetypeassert v0.1.0/go.mod h1:qZEedyP/sY1lTGV1uJ3VhWZ2mqag3IkWsDHVbplHXak=
github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk=
github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A=
github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M=
github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY=
github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU=
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk=
github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4=
github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs=
github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c=
github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48=
github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0=
github.com/jjti/go-spancheck v0.6.2 h1:iYtoxqPMzHUPp7St+5yA8+cONdyXD3ug6KK15n7Pklk=
github.com/jjti/go-spancheck v0.6.2/go.mod h1:+X7lvIrR5ZdUTkxFYqzJ0abr8Sb5LOo80uOhWNqIrYA=
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=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
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.1.0 h1:x7gNyKcC2vRBO1H2Mks5u1VxQtYvFiym7fCjIP8RPos=
github.com/karamaru-alpha/copyloopvar v1.1.0/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k=
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/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kkHAIKE/contextcheck v1.1.5 h1:CdnJh63tcDe53vG+RebdpdXJTc9atMgGqdx8LXxiilg=
github.com/kkHAIKE/contextcheck v1.1.5/go.mod h1:O930cpht4xb1YQpK+1+AgoM3mFsvxr7uyFptcnWTYUA=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/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.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCTdvWJ/lDDs=
github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY=
github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ymEyhQ=
github.com/kyoh86/exportloopref v0.1.11/go.mod h1:qkV4UF1zGl6EkF1ox8L5t9SwyeBAZ3qLMd6up458uqA=
github.com/lasiar/canonicalheader v1.1.1 h1:wC+dY9ZfiqiPwAexUApFush/csSPXeIi4QqyxXmng8I=
github.com/lasiar/canonicalheader v1.1.1/go.mod h1:cXkb3Dlk6XXy+8MVQnF23CYKWlyA7kfQhSw2CcZtZb0=
github.com/ldez/gomoddirectives v0.2.4 h1:j3YjBIjEBbqZ0NKtBNzr8rtMHTOrLPeiwTkfUJZ3alg=
github.com/ldez/gomoddirectives v0.2.4/go.mod h1:oWu9i62VcQDYp9EQ0ONTfqLNh+mDLWWDO+SO0qSQw5g=
github.com/ldez/tagliatelle v0.5.0 h1:epgfuYt9v0CG3fms0pEgIMNPuFf/LpPIfjk4kyqSioo=
github.com/ldez/tagliatelle v0.5.0/go.mod h1:rj1HmWiL1MiKQuOONhd09iySTEkUuE/8+5jtPYz9xa4=
github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY=
github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA=
github.com/lufeee/execinquery v1.2.1 h1:hf0Ems4SHcUGBxpGN7Jz78z1ppVkP/837ZlETPCEtOM=
github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM=
github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk=
github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI=
github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE=
github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04=
github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc=
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 h1:gWg6ZQ4JhDfJPqlo2srm/LN17lpybq15AryXIRcWYLE=
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
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/mgechev/revive v1.3.9 h1:18Y3R4a2USSBF+QZKFQwVkBROUda7uoBlkEuBD+YD1A=
github.com/mgechev/revive v1.3.9/go.mod h1:+uxEIr5UH0TjXWHTno3xh4u7eg6jDpXKzQccA9UGhHU=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI=
github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U=
github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE=
github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg=
github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs=
github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk=
github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c=
github.com/nunnatsa/ginkgolinter v0.16.2 h1:8iLqHIZvN4fTLDC0Ke9tbSZVcyVHoBs0HIbnVSxfHJk=
github.com/nunnatsa/ginkgolinter v0.16.2/go.mod h1:4tWRinDN1FeJgU+iJANW/kz7xKN5nYRAOfJDQUS9dOQ=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4=
github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag=
github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
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.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/polyfloyd/go-errorlint v1.6.0 h1:tftWV9DE7txiFzPpztTAwyoRLKNj9gpVm2cg8/OwcYY=
github.com/polyfloyd/go-errorlint v1.6.0/go.mod h1:HR7u8wuP1kb1NeN1zqTd1ZMlqUKPPHF+Id4vIPvDqVw=
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=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
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.3-0.20240823090925-0fe6f58b47b1 h1:+Wl/0aFp0hpuHM3H//KMft64WQ1yX9LdJY64Qm/gFCo=
github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI=
github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE=
github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
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=
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0=
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs=
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV3oJmPU=
github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE=
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.27.0 h1:t/3jZpSXtRPRf2xr0m63i32ZrusyurIGT9E5wAvXQnI=
github.com/sashamelentyev/usestdlibvars v1.27.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8=
github.com/securego/gosec/v2 v2.21.2 h1:deZp5zmYf3TWwU7A7cR2+SolbTpZ3HQiwFqnzQyEl3M=
github.com/securego/gosec/v2 v2.21.2/go.mod h1:au33kg78rNseF5PwPnTWhuYBFf534bvJRvOrgZ/bFzU=
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU=
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
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/tenv v1.10.0 h1:g/hzMA+dBCKqGXgW8AV/1xIWhAvDrx0zFKNR48NFMg0=
github.com/sivchari/tenv v1.10.0/go.mod h1:tdY24masnVoZFxYrHv/nD6Tc8FbkEtAQEEziXpyMgqY=
github.com/sonatard/noctx v0.0.2 h1:L7Dz4De2zDQhW8S0t+KUjY0MAQJd6SgVwhzNIc4ok00=
github.com/sonatard/noctx v0.0.2/go.mod h1:kzFz+CzWSjQ2OzIm46uJZoXuBpa2+0y3T36U18dWqIo=
github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0=
github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ=
github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI=
github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0=
github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I=
github.com/stbenjam/no-sprintf-host-port v0.1.1 h1:tYugd/yrm1O0dV+ThCbaKZh195Dfm07ysF0U6JQXczc=
github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8LHsN9N74I+PhRquPsxpL0I=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/tdakkota/asciicheck v0.2.0 h1:o8jvnUANo0qXtnslk2d3nMKTFNlOnJjRrNcj0j9qkHM=
github.com/tdakkota/asciicheck v0.2.0/go.mod h1:Qb7Y9EgjCLJGup51gDHFzbI08/gbGhL/UVhYIPWG2rg=
github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA=
github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0=
github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag=
github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY=
github.com/tetafro/godot v1.4.17 h1:pGzu+Ye7ZUEFx7LHU0dAKmCOXWsPjl7qA6iMGndsjPs=
github.com/tetafro/godot v1.4.17/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio=
github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 h1:quvGphlmUVU+nhpFa4gg4yJyTRJ13reZMDHrKwYw53M=
github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966/go.mod h1:27bSVNWSBOHm+qRp1T9qzaIpsWEP6TbUnei/43HK+PQ=
github.com/timonwong/loggercheck v0.9.4 h1:HKKhqrjcVj8sxL7K77beXh0adEm6DLjV/QOGeMXEVi4=
github.com/timonwong/loggercheck v0.9.4/go.mod h1:caz4zlPcgvpEkXgVnAJGowHAMW2NwHaNlpS8xDbVhTg=
github.com/tomarrell/wrapcheck/v2 v2.9.0 h1:801U2YCAjLhdN8zhZ/7tdjB3EnAoRlJHt/s+9hijLQ4=
github.com/tomarrell/wrapcheck/v2 v2.9.0/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=
github.com/ultraware/funlen v0.1.0/go.mod h1:XJqmOQja6DpxarLj6Jj1U7JuoS8PvL4nEqDaQhy22p4=
github.com/ultraware/whitespace v0.1.1 h1:bTPOGejYFulW3PkcrqkeQwOd6NKOOXvmGD9bo/Gk8VQ=
github.com/ultraware/whitespace v0.1.1/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8=
github.com/uudashr/gocognit v1.1.3 h1:l+a111VcDbKfynh+airAy/DJQKaXh2m9vkoysMPSZyM=
github.com/uudashr/gocognit v1.1.3/go.mod h1:aKH8/e8xbTRBwjbCkwZ8qt4l2EpKXl31KMHgSS+lZ2U=
github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU=
github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg=
github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM=
github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk=
github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs=
github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4=
github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw=
github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo=
gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8=
go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ=
go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28=
go-simpler.org/musttag v0.12.2 h1:J7lRc2ysXOq7eM8rwaTYnNrHd5JwjppzB6mScysB2Cs=
go-simpler.org/musttag v0.12.2/go.mod h1:uN1DVIasMTQKk6XSik7yrJoEysGtR2GRqvWnI9S7TYM=
go-simpler.org/sloglint v0.7.2 h1:Wc9Em/Zeuu7JYpl+oKoYOsQSy2X560aVueCW/m6IijY=
go-simpler.org/sloglint v0.7.2/go.mod h1:US+9C80ppl7VsThQclkM7BkCHQAzuz8kHLsW3ppuluo=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.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=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
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-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=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
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.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
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=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190321232350-e250d351ecad/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
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-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=
golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
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=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
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.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I=
honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs=
mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU=
mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo=
mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U=
mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f/go.mod h1:RSLa7mKKCNeTTMHBw5Hsy2rfJmd6O2ivt9Dw9ZqCQpQ=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View File

@ -1,5 +1,4 @@
//go:build tools
// +build tools
package tools
@ -8,5 +7,4 @@ package tools
import (
_ "github.com/cpuguy83/go-md2man/v2"
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
)

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2023 Leigh McCulloch
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.

View File

@ -1,105 +0,0 @@
package checkcompilerdirectives
import (
"strings"
"golang.org/x/tools/go/analysis"
)
func Analyzer() *analysis.Analyzer {
return &analysis.Analyzer{
Name: "gocheckcompilerdirectives",
Doc: "Checks that go compiler directive comments (//go:) are valid.",
Run: run,
}
}
func run(pass *analysis.Pass) (interface{}, error) {
for _, file := range pass.Files {
for _, group := range file.Comments {
for _, comment := range group.List {
text := comment.Text
if !strings.HasPrefix(text, "//") {
continue
}
start := 2
spaces := 0
for _, c := range text[start:] {
if c == ' ' {
spaces++
continue
}
break
}
start += spaces
if !strings.HasPrefix(text[start:], "go:") {
continue
}
start += 3
end := strings.Index(text[start:], " ")
if end == -1 {
continue
}
directive := text[start : start+end]
if len(directive) == 0 {
continue
}
prefix := text[:start+end]
// Leading whitespace will cause the go directive to be ignored
// by the compiler with no error, causing it not to work. This
// is an easy mistake.
if spaces > 0 {
pass.ReportRangef(comment, "compiler directive contains space: %s", prefix)
}
// If the directive is unknown it will be ignored by the
// compiler with no error. This is an easy mistake to make,
// especially if you typo a directive.
if !isKnown(directive) {
pass.ReportRangef(comment, "compiler directive unrecognized: %s", prefix)
}
}
}
}
return nil, nil
}
func isKnown(directive string) bool {
for _, k := range known {
if directive == k {
return true
}
}
return false
}
var known = []string{
// Found by running the following command on the source of go.
// git grep -o -E -h '//go:[a-z_]+' -- ':!**/*_test.go' ':!test/' ':!**/testdata/**' | sort -u
"binary",
"build",
"buildsomethingelse",
"cgo_dynamic_linker",
"cgo_export_dynamic",
"cgo_export_static",
"cgo_import_dynamic",
"cgo_import_static",
"cgo_ldflag",
"cgo_unsafe_args",
"embed",
"generate",
"linkname",
"name",
"nocheckptr",
"noescape",
"noinline",
"nointerface",
"norace",
"nosplit",
"notinheap",
"nowritebarrier",
"nowritebarrierrec",
"systemstack",
"uintptrescapes",
"uintptrkeepalive",
"yeswritebarrierrec",
}

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2018 Leigh McCulloch
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.

View File

@ -1,200 +0,0 @@
package checknoglobals
import (
"flag"
"fmt"
"go/ast"
"go/token"
"go/types"
"strings"
"golang.org/x/tools/go/analysis"
)
// allowedExpression is a struct representing packages and methods that will
// be an allowed combination to use as a global variable, f.ex. Name `regexp`
// and SelName `MustCompile`.
type allowedExpression struct {
Name string
SelName string
}
const Doc = `check that no global variables exist
This analyzer checks for global variables and errors on any found.
A global variable is a variable declared in package scope and that can be read
and written to by any function within the package. Global variables can cause
side effects which are difficult to keep track of. A code in one function may
change the variables state while another unrelated chunk of code may be
effected by it.`
// Analyzer provides an Analyzer that checks that there are no global
// variables, except for errors and variables containing regular
// expressions.
func Analyzer() *analysis.Analyzer {
return &analysis.Analyzer{
Name: "gochecknoglobals",
Doc: Doc,
Run: checkNoGlobals,
Flags: flags(),
RunDespiteErrors: true,
}
}
func flags() flag.FlagSet {
flags := flag.NewFlagSet("", flag.ExitOnError)
flags.Bool("t", false, "Include tests")
return *flags
}
func isAllowed(cm ast.CommentMap, v ast.Node, ti *types.Info) bool {
switch i := v.(type) {
case *ast.GenDecl:
return hasEmbedComment(cm, i)
case *ast.Ident:
return i.Name == "_" || i.Name == "version" || isError(i, ti) || identHasEmbedComment(cm, i)
case *ast.CallExpr:
if expr, ok := i.Fun.(*ast.SelectorExpr); ok {
return isAllowedSelectorExpression(expr)
}
case *ast.CompositeLit:
if expr, ok := i.Type.(*ast.SelectorExpr); ok {
return isAllowedSelectorExpression(expr)
}
}
return false
}
func isAllowedSelectorExpression(v *ast.SelectorExpr) bool {
x, ok := v.X.(*ast.Ident)
if !ok {
return false
}
allowList := []allowedExpression{
{Name: "regexp", SelName: "MustCompile"},
}
for _, i := range allowList {
if x.Name == i.Name && v.Sel.Name == i.SelName {
return true
}
}
return false
}
// isError reports whether the AST identifier looks like
// an error and implements the error interface.
func isError(i *ast.Ident, ti *types.Info) bool {
return looksLikeError(i) && implementsError(i, ti)
}
// looksLikeError returns true if the AST identifier starts
// with 'err' or 'Err', or false otherwise.
func looksLikeError(i *ast.Ident) bool {
prefix := "err"
if i.IsExported() {
prefix = "Err"
}
return strings.HasPrefix(i.Name, prefix)
}
// implementsError reports whether the AST identifier
// implements the error interface.
func implementsError(i *ast.Ident, ti *types.Info) bool {
t := ti.TypeOf(i)
et := types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
return types.Implements(t, et)
}
func identHasEmbedComment(cm ast.CommentMap, i *ast.Ident) bool {
if i.Obj == nil {
return false
}
spec, ok := i.Obj.Decl.(*ast.ValueSpec)
if !ok {
return false
}
return hasEmbedComment(cm, spec)
}
// hasEmbedComment returns true if the AST node has
// a '//go:embed ' comment, or false otherwise.
func hasEmbedComment(cm ast.CommentMap, n ast.Node) bool {
for _, g := range cm[n] {
for _, c := range g.List {
if strings.HasPrefix(c.Text, "//go:embed ") {
return true
}
}
}
return false
}
func checkNoGlobals(pass *analysis.Pass) (interface{}, error) {
includeTests := pass.Analyzer.Flags.Lookup("t").Value.(flag.Getter).Get().(bool)
for _, file := range pass.Files {
filename := pass.Fset.Position(file.Pos()).Filename
if !strings.HasSuffix(filename, ".go") {
continue
}
if !includeTests && strings.HasSuffix(filename, "_test.go") {
continue
}
fileCommentMap := ast.NewCommentMap(pass.Fset, file, file.Comments)
for _, decl := range file.Decls {
genDecl, ok := decl.(*ast.GenDecl)
if !ok {
continue
}
if genDecl.Tok != token.VAR {
continue
}
if isAllowed(fileCommentMap, genDecl, pass.TypesInfo) {
continue
}
for _, spec := range genDecl.Specs {
valueSpec := spec.(*ast.ValueSpec)
onlyAllowedValues := false
for _, vn := range valueSpec.Values {
if isAllowed(fileCommentMap, vn, pass.TypesInfo) {
onlyAllowedValues = true
continue
}
onlyAllowedValues = false
break
}
if onlyAllowedValues {
continue
}
for _, vn := range valueSpec.Names {
if isAllowed(fileCommentMap, vn, pass.TypesInfo) {
continue
}
message := fmt.Sprintf("%s is a global variable", vn.Name)
pass.Report(analysis.Diagnostic{
Pos: vn.Pos(),
Category: "global",
Message: message,
})
}
}
}
}
return nil, nil
}

View File

@ -1,75 +0,0 @@
# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,go
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,macos,go
### Go ###
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
.vscode
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,go
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)

View File

@ -1,106 +0,0 @@
# See https://golangci-lint.run/usage/configuration/
linters-settings:
revive:
# see https://github.com/mgechev/revive#available-rules for details.
ignore-generated-header: true
severity: warning
rules:
- name: atomic
- name: blank-imports
- name: bool-literal-in-expr
- name: call-to-gc
- name: confusing-naming
- name: confusing-results
- name: constant-logical-expr
- name: context-as-argument
- name: context-keys-type
- name: deep-exit
- name: defer
- name: dot-imports
- name: duplicated-imports
- name: early-return
- name: empty-block
- name: empty-lines
- name: error-naming
- name: error-return
- name: error-strings
- name: errorf
- name: exported
- name: get-return
- name: identical-branches
- name: if-return
- name: import-shadowing
- name: increment-decrement
- name: indent-error-flow
- name: modifies-parameter
- name: modifies-value-receiver
- name: package-comments
- name: range
- name: range-val-address
- name: range-val-in-closure
- name: receiver-naming
- name: redefines-builtin-id
- name: string-of-int
- name: superfluous-else
- name: time-naming
- name: unconditional-recursion
- name: unexported-naming
- name: unexported-return
- name: unnecessary-stmt
- name: unreachable-code
- name: unused-parameter
- name: var-declaration
- name: var-naming
- name: waitgroup-by-value
linters:
disable-all: true
enable:
- asciicheck
- bodyclose
- dogsled
- dupl
- durationcheck
- errcheck
- errorlint
- exhaustive
- exportloopref
- forcetypeassert
- gochecknoinits
- gocognit
- goconst
- gocritic
- gocyclo
- godot
- godox
- goimports
- gomoddirectives
- gomodguard
- goprintffuncname
- gosec
- gosimple
# - govet
- importas
- ineffassign
- makezero
- misspell
- nakedret
- nestif
- nilerr
- noctx
- nolintlint
- prealloc
- predeclared
- revive
- rowserrcheck
- sqlclosecheck
- staticcheck
- stylecheck
- thelper
- tparallel
- typecheck
- unconvert
- unparam
- unused
- whitespace

View File

@ -1,32 +0,0 @@
---
project_name: tagalign
release:
github:
owner: 4meepo
name: tagalign
builds:
- binary: tagalign
goos:
- darwin
- windows
- linux
- freebsd
goarch:
- amd64
- arm64
- arm
goarm:
- 6
- 7
gomips:
- hardfloat
env:
- CGO_ENABLED=0
ignore:
- goos: darwin
goarch: 386
- goos: freebsd
goarch: arm64
main: ./cmd/tagalign/

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2023 Yifei Liu
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.

View File

@ -1,7 +0,0 @@
.PHONY: lint
lint:
golangci-lint run ./...
.PHONY: build
build:
go build -o tagalign cmd/tagalign/tagalign.go

View File

@ -1,130 +0,0 @@
# Go Tag Align
![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/4meepo/tagalign?style=flat-square)
[![codecov](https://codecov.io/github/4meepo/tagalign/branch/main/graph/badge.svg?token=1R1T61UNBQ)](https://codecov.io/github/4meepo/tagalign)
[![GoDoc](https://godoc.org/github.com/4meepo/tagalign?status.svg)](https://pkg.go.dev/github.com/4meepo/tagalign)
[![Go Report Card](https://goreportcard.com/badge/github.com/4meepo/tagalign)](https://goreportcard.com/report/github.com/4meepo/tagalign)
TagAlign is used to align and sort tags in Go struct. It can make the struct more readable and easier to maintain.
For example, this struct
```go
type FooBar struct {
Foo int `json:"foo" validate:"required"`
Bar string `json:"bar" validate:"required"`
FooFoo int8 `json:"foo_foo" validate:"required"`
BarBar int `json:"bar_bar" validate:"required"`
FooBar struct {
Foo int `json:"foo" yaml:"foo" validate:"required"`
Bar222 string `json:"bar222" validate:"required" yaml:"bar"`
} `json:"foo_bar" validate:"required"`
BarFoo string `json:"bar_foo" validate:"required"`
BarFooBar string `json:"bar_foo_bar" validate:"required"`
}
```
can be aligned to:
```go
type FooBar struct {
Foo int `json:"foo" validate:"required"`
Bar string `json:"bar" validate:"required"`
FooFoo int8 `json:"foo_foo" validate:"required"`
BarBar int `json:"bar_bar" validate:"required"`
FooBar struct {
Foo int `json:"foo" yaml:"foo" validate:"required"`
Bar222 string `json:"bar222" validate:"required" yaml:"bar"`
} `json:"foo_bar" validate:"required"`
BarFoo string `json:"bar_foo" validate:"required"`
BarFooBar string `json:"bar_foo_bar" validate:"required"`
}
```
## Usage
By default tagalign will only align tags, but not sort them. But alignment and [sort feature](https://github.com/4meepo/tagalign#sort-tag) can work together or separately.
* As a Golangci Linter (Recommended)
Tagalign is a built-in linter in [Golangci Lint](https://golangci-lint.run/usage/linters/#tagalign) since `v1.53`.
> Note: In order to have the best experience, add the `--fix` flag to `golangci-lint` to enable the autofix feature.
* Standalone Mode
Install it using `GO` or download it [here](https://github.com/4meepo/tagalign/releases).
```bash
go install github.com/4meepo/tagalign/cmd/tagalign@latest
```
Run it in your terminal.
```bash
# Only align tags.
tagalign -fix {package path}
# Only sort tags with fixed order.
tagalign -fix -noalign -sort -order "json,xml" {package path}
# Align and sort together.
tagalign -fix -sort -order "json,xml" {package path}
# Align and sort together in strict style.
tagalign -fix -sort -order "json,xml" -strict {package path}
```
## Advanced Features
### Sort Tag
In addition to alignment, it can also sort tags with fixed order. If we enable sort with fixed order `json,xml`, the following code
```go
type SortExample struct {
Foo int `json:"foo,omitempty" yaml:"bar" xml:"baz" binding:"required" gorm:"column:foo" zip:"foo" validate:"required"`
Bar int `validate:"required" yaml:"foo" xml:"bar" binding:"required" json:"bar,omitempty" gorm:"column:bar" zip:"bar" `
FooBar int `gorm:"column:bar" validate:"required" xml:"bar" binding:"required" json:"bar,omitempty" zip:"bar" yaml:"foo"`
}
```
will be sorted and aligned to:
```go
type SortExample struct {
Foo int `json:"foo,omitempty" xml:"baz" binding:"required" gorm:"column:foo" validate:"required" yaml:"bar" zip:"foo"`
Bar int `json:"bar,omitempty" xml:"bar" binding:"required" gorm:"column:bar" validate:"required" yaml:"foo" zip:"bar"`
FooBar int `json:"bar,omitempty" xml:"bar" binding:"required" gorm:"column:bar" validate:"required" yaml:"foo" zip:"bar"`
}
```
The fixed order is `json,xml`, so the tags `json` and `xml` will be sorted and aligned first, and the rest tags will be sorted and aligned in the dictionary order.
### Strict Style
Sometimes, you may want to align your tags in strict style. In this style, the tags will be sorted and aligned in the dictionary order, and the tags with the same name will be aligned together. For example, the following code
```go
type StrictStyleExample struct {
Foo int ` xml:"baz" yaml:"bar" zip:"foo" binding:"required" gorm:"column:foo" validate:"required"`
Bar int `validate:"required" gorm:"column:bar" yaml:"foo" xml:"bar" binding:"required" json:"bar,omitempty" `
}
```
will be aligned to
```go
type StrictStyleExample struct {
Foo int `binding:"required" gorm:"column:foo" validate:"required" xml:"baz" yaml:"bar" zip:"foo"`
Bar int `binding:"required" gorm:"column:bar" json:"bar,omitempty" validate:"required" xml:"bar" yaml:"foo"`
}
```
> ⚠Note: The strict style can't run without the align or sort feature enabled.
## References
[Golang AST Visualizer](http://goast.yuroyoro.net/)
[Create New Golang CI Linter](https://golangci-lint.run/contributing/new-linters/)
[Autofix Example](https://github.com/golangci/golangci-lint/pull/2450/files)
[Integrating](https://disaev.me/p/writing-useful-go-analysis-linter/#integrating)

View File

@ -1,37 +0,0 @@
package tagalign
type Option func(*Helper)
// WithMode specify the mode of tagalign.
func WithMode(mode Mode) Option {
return func(h *Helper) {
h.mode = mode
}
}
// WithSort enable tags sort.
// fixedOrder specify the order of tags, the other tags will be sorted by name.
// Sory is disabled by default.
func WithSort(fixedOrder ...string) Option {
return func(h *Helper) {
h.sort = true
h.fixedTagOrder = fixedOrder
}
}
// WithAlign configure whether enable tags align.
// Align is enabled by default.
func WithAlign(enabled bool) Option {
return func(h *Helper) {
h.align = enabled
}
}
// WithStrictStyle configure whether enable strict style.
// StrictStyle is disabled by default.
// Note: StrictStyle must be used with WithAlign(true) and WithSort(...) together, or it will be ignored.
func WithStrictStyle() Option {
return func(h *Helper) {
h.style = StrictStyle
}
}

View File

@ -1,459 +0,0 @@
package tagalign
import (
"fmt"
"go/ast"
"go/token"
"log"
"reflect"
"sort"
"strconv"
"strings"
"github.com/fatih/structtag"
"golang.org/x/tools/go/analysis"
)
type Mode int
const (
StandaloneMode Mode = iota
GolangciLintMode
)
type Style int
const (
DefaultStyle Style = iota
StrictStyle
)
const (
errTagValueSyntax = "bad syntax for struct tag value"
)
func NewAnalyzer(options ...Option) *analysis.Analyzer {
return &analysis.Analyzer{
Name: "tagalign",
Doc: "check that struct tags are well aligned",
Run: func(p *analysis.Pass) (any, error) {
Run(p, options...)
return nil, nil
},
}
}
func Run(pass *analysis.Pass, options ...Option) []Issue {
var issues []Issue
for _, f := range pass.Files {
h := &Helper{
mode: StandaloneMode,
style: DefaultStyle,
align: true,
}
for _, opt := range options {
opt(h)
}
// StrictStyle must be used with WithAlign(true) and WithSort(...) together, or it will be ignored.
if h.style == StrictStyle && (!h.align || !h.sort) {
h.style = DefaultStyle
}
if !h.align && !h.sort {
// do nothing
return nil
}
ast.Inspect(f, func(n ast.Node) bool {
h.find(pass, n)
return true
})
h.Process(pass)
issues = append(issues, h.issues...)
}
return issues
}
type Helper struct {
mode Mode
style Style
align bool // whether enable tags align.
sort bool // whether enable tags sort.
fixedTagOrder []string // the order of tags, the other tags will be sorted by name.
singleFields []*ast.Field
consecutiveFieldsGroups [][]*ast.Field // fields in this group, must be consecutive in struct.
issues []Issue
}
// Issue is used to integrate with golangci-lint's inline auto fix.
type Issue struct {
Pos token.Position
Message string
InlineFix InlineFix
}
type InlineFix struct {
StartCol int // zero-based
Length int
NewString string
}
func (w *Helper) find(pass *analysis.Pass, n ast.Node) {
v, ok := n.(*ast.StructType)
if !ok {
return
}
fields := v.Fields.List
if len(fields) == 0 {
return
}
fs := make([]*ast.Field, 0)
split := func() {
n := len(fs)
if n > 1 {
w.consecutiveFieldsGroups = append(w.consecutiveFieldsGroups, fs)
} else if n == 1 {
w.singleFields = append(w.singleFields, fs[0])
}
fs = nil
}
for i, field := range fields {
if field.Tag == nil {
// field without tags
split()
continue
}
if i > 0 {
if fields[i-1].Tag == nil {
// if previous filed do not have a tag
fs = append(fs, field)
continue
}
preLineNum := pass.Fset.Position(fields[i-1].Tag.Pos()).Line
lineNum := pass.Fset.Position(field.Tag.Pos()).Line
if lineNum-preLineNum > 1 {
// fields with tags are not consecutive, including two case:
// 1. splited by lines
// 2. splited by a struct
split()
// check if the field is a struct
if _, ok := field.Type.(*ast.StructType); ok {
continue
}
}
}
fs = append(fs, field)
}
split()
}
func (w *Helper) report(pass *analysis.Pass, field *ast.Field, startCol int, msg, replaceStr string) {
if w.mode == GolangciLintMode {
iss := Issue{
Pos: pass.Fset.Position(field.Tag.Pos()),
Message: msg,
InlineFix: InlineFix{
StartCol: startCol,
Length: len(field.Tag.Value),
NewString: replaceStr,
},
}
w.issues = append(w.issues, iss)
}
if w.mode == StandaloneMode {
pass.Report(analysis.Diagnostic{
Pos: field.Tag.Pos(),
End: field.Tag.End(),
Message: msg,
SuggestedFixes: []analysis.SuggestedFix{
{
Message: msg,
TextEdits: []analysis.TextEdit{
{
Pos: field.Tag.Pos(),
End: field.Tag.End(),
NewText: []byte(replaceStr),
},
},
},
},
})
}
}
func (w *Helper) Process(pass *analysis.Pass) { //nolint:gocognit
// process grouped fields
for _, fields := range w.consecutiveFieldsGroups {
offsets := make([]int, len(fields))
var maxTagNum int
var tagsGroup, notSortedTagsGroup [][]*structtag.Tag
var uniqueKeys []string
addKey := func(k string) {
for _, key := range uniqueKeys {
if key == k {
return
}
}
uniqueKeys = append(uniqueKeys, k)
}
for i := 0; i < len(fields); {
field := fields[i]
column := pass.Fset.Position(field.Tag.Pos()).Column - 1
offsets[i] = column
tag, err := strconv.Unquote(field.Tag.Value)
if err != nil {
// if tag value is not a valid string, report it directly
w.report(pass, field, column, errTagValueSyntax, field.Tag.Value)
fields = removeField(fields, i)
continue
}
tags, err := structtag.Parse(tag)
if err != nil {
// if tag value is not a valid struct tag, report it directly
w.report(pass, field, column, err.Error(), field.Tag.Value)
fields = removeField(fields, i)
continue
}
maxTagNum = max(maxTagNum, tags.Len())
if w.sort {
cp := make([]*structtag.Tag, tags.Len())
for i, tag := range tags.Tags() {
cp[i] = tag
}
notSortedTagsGroup = append(notSortedTagsGroup, cp)
sortBy(w.fixedTagOrder, tags)
}
for _, t := range tags.Tags() {
addKey(t.Key)
}
tagsGroup = append(tagsGroup, tags.Tags())
i++
}
if w.sort && StrictStyle == w.style {
sortAllKeys(w.fixedTagOrder, uniqueKeys)
maxTagNum = len(uniqueKeys)
}
// record the max length of each column tag
type tagLen struct {
Key string // present only when sort enabled
Len int
}
tagMaxLens := make([]tagLen, maxTagNum)
for j := 0; j < maxTagNum; j++ {
var maxLength int
var key string
for i := 0; i < len(tagsGroup); i++ {
if w.style == StrictStyle {
key = uniqueKeys[j]
// search by key
for _, tag := range tagsGroup[i] {
if tag.Key == key {
maxLength = max(maxLength, len(tag.String()))
break
}
}
} else {
if len(tagsGroup[i]) <= j {
// in case of index out of range
continue
}
maxLength = max(maxLength, len(tagsGroup[i][j].String()))
}
}
tagMaxLens[j] = tagLen{key, maxLength}
}
for i, field := range fields {
tags := tagsGroup[i]
var newTagStr string
if w.align {
// if align enabled, align tags.
newTagBuilder := strings.Builder{}
for i, n := 0, 0; i < len(tags) && n < len(tagMaxLens); {
tag := tags[i]
var format string
if w.style == StrictStyle {
if tagMaxLens[n].Key == tag.Key {
// match
format = alignFormat(tagMaxLens[n].Len + 1) // with an extra space
newTagBuilder.WriteString(fmt.Sprintf(format, tag.String()))
i++
n++
} else {
// tag missing
format = alignFormat(tagMaxLens[n].Len + 1)
newTagBuilder.WriteString(fmt.Sprintf(format, ""))
n++
}
} else {
format = alignFormat(tagMaxLens[n].Len + 1) // with an extra space
newTagBuilder.WriteString(fmt.Sprintf(format, tag.String()))
i++
n++
}
}
newTagStr = newTagBuilder.String()
} else {
// otherwise check if tags order changed
if w.sort && reflect.DeepEqual(notSortedTagsGroup[i], tags) {
// if tags order not changed, do nothing
continue
}
tagsStr := make([]string, len(tags))
for i, tag := range tags {
tagsStr[i] = tag.String()
}
newTagStr = strings.Join(tagsStr, " ")
}
unquoteTag := strings.TrimRight(newTagStr, " ")
// unquoteTag := newTagStr
newTagValue := fmt.Sprintf("`%s`", unquoteTag)
if field.Tag.Value == newTagValue {
// nothing changed
continue
}
msg := "tag is not aligned, should be: " + unquoteTag
w.report(pass, field, offsets[i], msg, newTagValue)
}
}
// process single fields
for _, field := range w.singleFields {
column := pass.Fset.Position(field.Tag.Pos()).Column - 1
tag, err := strconv.Unquote(field.Tag.Value)
if err != nil {
w.report(pass, field, column, errTagValueSyntax, field.Tag.Value)
continue
}
tags, err := structtag.Parse(tag)
if err != nil {
w.report(pass, field, column, err.Error(), field.Tag.Value)
continue
}
originalTags := append([]*structtag.Tag(nil), tags.Tags()...)
if w.sort {
sortBy(w.fixedTagOrder, tags)
}
newTagValue := fmt.Sprintf("`%s`", tags.String())
if reflect.DeepEqual(originalTags, tags.Tags()) && field.Tag.Value == newTagValue {
// if tags order not changed, do nothing
continue
}
msg := "tag is not aligned , should be: " + tags.String()
w.report(pass, field, column, msg, newTagValue)
}
}
// Issues returns all issues found by the analyzer.
// It is used to integrate with golangci-lint.
func (w *Helper) Issues() []Issue {
log.Println("tagalign 's Issues() should only be called in golangci-lint mode")
return w.issues
}
// sortBy sorts tags by fixed order.
// If a tag is not in the fixed order, it will be sorted by name.
func sortBy(fixedOrder []string, tags *structtag.Tags) {
// sort by fixed order
sort.Slice(tags.Tags(), func(i, j int) bool {
ti := tags.Tags()[i]
tj := tags.Tags()[j]
oi := findIndex(fixedOrder, ti.Key)
oj := findIndex(fixedOrder, tj.Key)
if oi == -1 && oj == -1 {
return ti.Key < tj.Key
}
if oi == -1 {
return false
}
if oj == -1 {
return true
}
return oi < oj
})
}
func sortAllKeys(fixedOrder []string, keys []string) {
sort.Slice(keys, func(i, j int) bool {
oi := findIndex(fixedOrder, keys[i])
oj := findIndex(fixedOrder, keys[j])
if oi == -1 && oj == -1 {
return keys[i] < keys[j]
}
if oi == -1 {
return false
}
if oj == -1 {
return true
}
return oi < oj
})
}
func findIndex(s []string, e string) int {
for i, a := range s {
if a == e {
return i
}
}
return -1
}
func alignFormat(length int) string {
return "%" + fmt.Sprintf("-%ds", length)
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func removeField(fields []*ast.Field, index int) []*ast.Field {
if index < 0 || index >= len(fields) {
return fields
}
return append(fields[:index], fields[index+1:]...)
}

View File

@ -1,183 +0,0 @@
# Godot-specific ignores
.import/
export.cfg
export_presets.cfg
# Mono-specific ignores
.mono/
data_*/
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
.idea/artifacts
.idea/compiler.xml
.idea/jarRepositories.xml
.idea/modules.xml
.idea/*.iml
.idea/modules
*.iml
*.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Emacs template
# -*- mode: gitignore; -*-
*~
\#*\#
/.emacs.desktop
/.emacs.desktop.lock
*.elc
auto-save-list
tramp
.\#*
# Org-mode
.org-id-locations
*_archive
# flymake-mode
*_flymake.*
# eshell files
/eshell/history
/eshell/lastdir
# elpa packages
/elpa/
# reftex files
*.rel
# AUCTeX auto folder
/auto/
# cask packages
.cask/
dist/
# Flycheck
flycheck_*.el
# server auth directory
/server/
# projectiles files
.projectile
# directory configuration
.dir-locals.el
# network security
/network-security.data
### Vim template
# Swap
[._]*.s[a-v][a-z]
!*.svg # comment out if you don't need vector files
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
# Session
Session.vim
Sessionx.vim
# Temporary
.netrwhist
*~
# Auto-generated tag files
tags
# Persistent undo
[._]*.un~
### macOS template
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

View File

@ -1,72 +0,0 @@
---
project_name: dupword
release:
github:
owner: Abirdcfly
name: dupword
builds:
- binary: dupword
goos:
- darwin
- windows
- linux
- freebsd
goarch:
- amd64
- arm64
- arm
- 386
- ppc64le
- s390x
- mips64
- mips64le
- riscv64
goarm:
- 6
- 7
gomips:
- hardfloat
env:
- CGO_ENABLED=0
ignore:
- goos: darwin
goarch: 386
- goos: freebsd
goarch: arm64
main: ./cmd/dupword/
flags:
- -trimpath
ldflags: -s -w -X main.version={{.Version}} -X main.commit={{.ShortCommit}} -X main.date={{.Date}}
archives:
- format: tar.gz
wrap_in_directory: true
format_overrides:
- goos: windows
format: zip
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
files:
- LICENSE
- README.md
snapshot:
name_template: SNAPSHOT-{{ .Commit }}
checksum:
name_template: '{{ .ProjectName }}-{{ .Version }}-checksums.txt'
changelog:
sort: asc
filters:
exclude:
- '(?i)^docs?:'
- '(?i)^docs\([^:]+\):'
- '(?i)^docs\[[^:]+\]:'
- '^tests?:'
- '(?i)^dev:'
- '^build\(deps\): bump .* in /docs \(#\d+\)'
- '^build\(deps\): bump .* in /\.github/peril \(#\d+\)'
- Merge pull request
- Merge branch

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2022 Abirdcfly
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.

View File

@ -1,153 +0,0 @@
# dupword
![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/Abirdcfly/dupword?style=flat-square)
[![GoDoc](https://godoc.org/github.com/Abirdcfly/dupword?status.svg)](https://pkg.go.dev/github.com/Abirdcfly/dupword)
[![Actions Status](https://github.com/Abirdcfly/dupword/actions/workflows/lint.yml/badge.svg)](https://github.com/Abirdcfly/dupword/actions)
[![Go Report Card](https://goreportcard.com/badge/github.com/Abirdcfly/dupword)](https://goreportcard.com/report/github.com/Abirdcfly/dupword)
A linter that checks for duplicate words in the source code (usually miswritten)
Examples in real code and related issues can be viewed in [dupword#3](https://github.com/Abirdcfly/dupword/issues/3)
## example
1. Repeated words appear on two adjacent lines [commit](https://github.com/golang/go/commit/d8f90ce0f8119bf593efb6fb91825de5b61fcda7)
```diff
--- a/src/cmd/compile/internal/ssa/schedule.go
+++ b/src/cmd/compile/internal/ssa/schedule.go
@@ -179,7 +179,7 @@ func schedule(f *Func) {
// scored CarryChainTail (and prove w is not a tail).
score[w.ID] = ScoreFlags
}
- // Verify v has not been scored. If v has not been visited, v may be the
+ // Verify v has not been scored. If v has not been visited, v may be
// the final (tail) operation in a carry chain. If v is not, v will be
// rescored above when v's carry-using op is scored. When scoring is done,
// only tail operations will retain the CarryChainTail score.
```
2. Repeated words appearing on the same line [commit](https://github.com/golang/go/commit/48da729e8468b630ee003ac51cbaac595d53bec8)
```diff
--- a/src/net/http/cookiejar/jar.go
+++ b/src/net/http/cookiejar/jar.go
@@ -465,7 +465,7 @@ func (j *Jar) domainAndType(host, domain string) (string, bool, error) {
// dot in the domain-attribute before processing the cookie.
//
// Most browsers don't do that for IP addresses, only curl
- // version 7.54) and and IE (version 11) do not reject a
+ // version 7.54) and IE (version 11) do not reject a
// Set-Cookie: a=1; domain=.127.0.0.1
// This leading dot is optional and serves only as hint for
// humans to indicate that a cookie with "domain=.bbc.co.uk"
```
## Install
```bash
go install github.com/Abirdcfly/dupword/cmd/dupword@latest
```
**Or** install the main branch (including the last commit) with:
```bash
go install github.com/Abirdcfly/dupword/cmd/dupword@main
```
## Usage
### 1. default
Run with default settings(include test file):
**But note that not all repeated words are wrong** see [dupword#4](https://github.com/Abirdcfly/dupword/issues/4) for real code example.
```bash
$ dupword ./...
/Users/xxx/go/src/dupword/dupword_test.go:88:10: Duplicate words (the) found
exit status 3
```
### 2. skip test file
Skip detection test file(`*_test.go`):
```bash
$ dupword -test=false ./...
```
### 3. auto-fix
```bash
$ dupword -fix ./...
```
### 4. all options
All options:
```bash
$ dupword --help
dupword: checks for duplicate words in the source code (usually miswritten)
Usage: dupword [-flag] [package]
This analyzer checks miswritten duplicate words in comments or package doc or string declaration
Flags:
-V print version and exit
-all
no effect (deprecated)
-c int
display offending line with this many lines of context (default -1)
-cpuprofile string
write CPU profile to this file
-debug string
debug flags, any subset of "fpstv"
-fix
apply all suggested fixes
-flags
print analyzer flags in JSON
-ignore value
ignore words
-json
emit JSON output
-keyword value
keywords for detecting duplicate words
-memprofile string
write memory profile to this file
-source
no effect (deprecated)
-tags string
no effect (deprecated)
-test
indicates whether test files should be analyzed, too (default true)
-trace string
write trace log to this file
-v no effect (deprecated)
```
### 5. my advice
use `--keyword=the,and,a` and `-fix` together. I think that specifying only commonly repeated prepositions can effectively avoid false positives.
see [dupword#4](https://github.com/Abirdcfly/dupword/issues/4) for real code example.
```bash
$ dupword --keyword=the,and,a -fix ./...
```
## TODO
- [x] add this linter to golangci-lint
- [ ] rewrite the detection logic to make it more efficient
## Limitation
1. Only for `*.go` file.But some miswritten occurs in `*.md` or `*.json` file.(example: kubernetes), In this case, my advice is to use [rg](https://github.com/BurntSushi/ripgrep) to do the lookup and replace manually.
2. When use `-fix`, also running `go fmt` in the dark.([This logic is determined upstream](https://github.com/golang/tools/blob/248c34b88a4148128f89e41923498bd86f805b7d/go/analysis/internal/checker/checker.go#L424-L433), the project does not have this part of the code.)
## License
MIT

View File

@ -1,343 +0,0 @@
// MIT License
//
// Copyright (c) 2022 Abirdcfly
//
// 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 dupword defines an Analyzer that checks that duplicate words
// int the source code.
package dupword
import (
"flag"
"fmt"
"go/ast"
"go/token"
"sort"
"strconv"
"strings"
"unicode"
"unicode/utf8"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)
const (
Name = "dupword"
Doc = `checks for duplicate words in the source code (usually miswritten)
This analyzer checks miswritten duplicate words in comments or package doc or string declaration`
Message = "Duplicate words (%s) found"
CommentPrefix = `//`
)
var (
defaultWord = []string{}
// defaultWord = []string{"the", "and", "a"}
ignoreWord = map[string]bool{}
)
type analyzer struct {
KeyWord []string
}
func (a *analyzer) String() string {
return strings.Join(a.KeyWord, ",")
}
func (a *analyzer) Set(w string) error {
if len(w) != 0 {
a.KeyWord = make([]string, 0)
a.KeyWord = append(a.KeyWord, strings.Split(w, ",")...)
}
return nil
}
type ignore struct {
}
func (a *ignore) String() string {
t := make([]string, 0, len(ignoreWord))
for k := range ignoreWord {
t = append(t, k)
}
return strings.Join(t, ",")
}
func (a *ignore) Set(w string) error {
for _, k := range strings.Split(w, ",") {
ignoreWord[k] = true
}
return nil
}
// for test only
func ClearIgnoreWord() {
ignoreWord = map[string]bool{}
}
func NewAnalyzer() *analysis.Analyzer {
ignore := &ignore{}
analyzer := &analyzer{KeyWord: defaultWord}
a := &analysis.Analyzer{
Name: Name,
Doc: Doc,
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: analyzer.run,
RunDespiteErrors: true,
}
a.Flags.Init(Name, flag.ExitOnError)
a.Flags.Var(analyzer, "keyword", "keywords for detecting duplicate words")
a.Flags.Var(ignore, "ignore", "ignore words")
a.Flags.Var(version{}, "V", "print version and exit")
return a
}
func (a *analyzer) run(pass *analysis.Pass) (interface{}, error) {
for _, file := range pass.Files {
a.fixDuplicateWordInComment(pass, file)
}
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.BasicLit)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
if lit, ok := n.(*ast.BasicLit); ok {
a.fixDuplicateWordInString(pass, lit)
}
})
return nil, nil
}
func (a *analyzer) fixDuplicateWordInComment(pass *analysis.Pass, f *ast.File) {
isTestFile := strings.HasSuffix(pass.Fset.File(f.FileStart).Name(), "_test.go")
for _, cg := range f.Comments {
// avoid checking example outputs for duplicate words
if isTestFile && isExampleOutputStart(cg.List[0].Text) {
continue
}
var preLine *ast.Comment
for _, c := range cg.List {
update, keyword, find := a.Check(c.Text)
if find {
pass.Report(analysis.Diagnostic{Pos: c.Slash, End: c.End(), Message: fmt.Sprintf(Message, keyword), SuggestedFixes: []analysis.SuggestedFix{{
Message: "Update",
TextEdits: []analysis.TextEdit{{
Pos: c.Slash,
End: c.End(),
NewText: []byte(update),
}},
}}})
}
if preLine != nil {
fields := strings.Fields(preLine.Text)
if len(fields) < 1 {
continue
}
preLineContent := fields[len(fields)-1] + "\n"
thisLineContent := c.Text
if find {
thisLineContent = update
}
before, after, _ := strings.Cut(thisLineContent, CommentPrefix)
update, keyword, find := a.Check(preLineContent + after)
if find {
var suggestedFixes []analysis.SuggestedFix
if strings.Contains(update, preLineContent) {
update = before + CommentPrefix + strings.TrimPrefix(update, preLineContent)
suggestedFixes = []analysis.SuggestedFix{{
Message: "Update",
TextEdits: []analysis.TextEdit{{
Pos: c.Slash,
End: c.End(),
NewText: []byte(update),
}},
}}
}
pass.Report(analysis.Diagnostic{Pos: c.Slash, End: c.End(), Message: fmt.Sprintf(Message, keyword), SuggestedFixes: suggestedFixes})
}
}
preLine = c
}
}
}
func (a *analyzer) fixDuplicateWordInString(pass *analysis.Pass, lit *ast.BasicLit) {
if lit.Kind != token.STRING {
return
}
value, err := strconv.Unquote(lit.Value)
if err != nil {
fmt.Printf("lit.Value:%v, err: %v\n", lit.Value, err)
// fall back to default
value = lit.Value
}
quote := value != lit.Value
update, keyword, find := a.Check(value)
if quote {
update = strconv.Quote(update)
}
if find {
pass.Report(analysis.Diagnostic{Pos: lit.Pos(), End: lit.End(), Message: fmt.Sprintf(Message, keyword), SuggestedFixes: []analysis.SuggestedFix{{
Message: "Update",
TextEdits: []analysis.TextEdit{{
Pos: lit.Pos(),
End: lit.End(),
NewText: []byte(update),
}},
}}})
}
}
// CheckOneKey use to check there is a defined duplicate word in a string.
// raw is checked line. key is the keyword to check. empty means just check duplicate word.
func CheckOneKey(raw, key string) (new string, findWord string, find bool) {
if key == "" {
has := false
fields := strings.Fields(raw)
for i := range fields {
if i == len(fields)-1 {
break
}
if fields[i] == fields[i+1] {
has = true
}
}
if !has {
return
}
} else {
if x := strings.Split(raw, key); len(x) < 2 {
return
}
}
findWordMap := make(map[string]bool, 4)
newLine := strings.Builder{}
wordStart, spaceStart := 0, 0
curWord, preWord := "", ""
lastSpace := ""
var lastRune int32
for i, r := range raw {
if !unicode.IsSpace(r) && unicode.IsSpace(lastRune) {
// word start position
/*
i
|
hello[ spaceA ]the[ spaceB ]the[ spaceC ]word
^ ^
| curWord: the
preWord: the
*/
symbol := raw[spaceStart:i]
if ((key != "" && curWord == key) || key == "") && curWord == preWord && curWord != "" {
if !ExcludeWords(curWord) {
find = true
findWordMap[curWord] = true
newLine.WriteString(lastSpace)
symbol = ""
}
} else {
newLine.WriteString(lastSpace)
newLine.WriteString(curWord)
}
lastSpace = symbol
preWord = curWord
wordStart = i
} else if unicode.IsSpace(r) && !unicode.IsSpace(lastRune) {
// space start position
spaceStart = i
curWord = raw[wordStart:i]
} else if i == len(raw)-1 {
// last position
word := raw[wordStart:]
if ((key != "" && word == key) || key == "") && word == preWord {
if !ExcludeWords(word) {
find = true
findWordMap[word] = true
}
} else {
newLine.WriteString(lastSpace)
newLine.WriteString(word)
}
}
lastRune = r
}
if find {
new = newLine.String()
findWordSlice := make([]string, len(findWordMap))
i := 0
for k := range findWordMap {
findWordSlice[i] = k
i++
}
sort.Strings(findWordSlice)
findWord = strings.Join(findWordSlice, ",")
}
return
}
func (a *analyzer) Check(raw string) (update string, keyword string, find bool) {
for _, key := range a.KeyWord {
updateOne, _, findOne := CheckOneKey(raw, key)
if findOne {
raw = updateOne
find = findOne
update = updateOne
if keyword == "" {
keyword = key
} else {
keyword = keyword + "," + key
}
}
}
if len(a.KeyWord) == 0 {
return CheckOneKey(raw, "")
}
return
}
// ExcludeWords determines whether duplicate words should be reported,
//
// e.g. %s, </div> should not be reported.
func ExcludeWords(word string) (exclude bool) {
firstRune, _ := utf8.DecodeRuneInString(word)
if unicode.IsDigit(firstRune) {
return true
}
if unicode.IsPunct(firstRune) {
return true
}
if unicode.IsSymbol(firstRune) {
return true
}
if _, exist := ignoreWord[word]; exist {
return true
}
return false
}
func isExampleOutputStart(comment string) bool {
return strings.HasPrefix(comment, "// Output:") ||
strings.HasPrefix(comment, "// output:") ||
strings.HasPrefix(comment, "// Unordered output:") ||
strings.HasPrefix(comment, "// unordered output:")
}

View File

@ -1,41 +0,0 @@
// MIT License
//
// # Copyright (c) 2022 Abirdcfly
//
// 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 dupword
import (
"fmt"
"os"
)
var Version = "dev"
type version struct{}
func (version) IsBoolFlag() bool { return true }
func (version) Get() interface{} { return nil }
func (version) String() string { return "" }
func (version) Set(_ string) error {
fmt.Println(Version)
os.Exit(0)
return nil
}

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2021 Anton Telyshev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,135 +0,0 @@
package analyzer
import (
"fmt"
"go/ast"
"go/token"
"strconv"
"strings"
"unicode"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)
// New returns new errname analyzer.
func New() *analysis.Analyzer {
return &analysis.Analyzer{
Name: "errname",
Doc: "Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`.",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
}
}
type stringSet = map[string]struct{}
var (
importNodes = []ast.Node{(*ast.ImportSpec)(nil)}
typeNodes = []ast.Node{(*ast.TypeSpec)(nil)}
funcNodes = []ast.Node{(*ast.FuncDecl)(nil)}
)
func run(pass *analysis.Pass) (interface{}, error) {
insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
pkgAliases := map[string]string{}
insp.Preorder(importNodes, func(node ast.Node) {
i := node.(*ast.ImportSpec)
if n := i.Name; n != nil && i.Path != nil {
if path, err := strconv.Unquote(i.Path.Value); err == nil {
pkgAliases[n.Name] = getPkgFromPath(path)
}
}
})
allTypes := stringSet{}
typesSpecs := map[string]*ast.TypeSpec{}
insp.Preorder(typeNodes, func(node ast.Node) {
t := node.(*ast.TypeSpec)
allTypes[t.Name.Name] = struct{}{}
typesSpecs[t.Name.Name] = t
})
errorTypes := stringSet{}
insp.Preorder(funcNodes, func(node ast.Node) {
f := node.(*ast.FuncDecl)
t, ok := isMethodError(f)
if !ok {
return
}
errorTypes[t] = struct{}{}
tSpec, ok := typesSpecs[t]
if !ok {
panic(fmt.Sprintf("no specification for type %q", t))
}
if _, ok := tSpec.Type.(*ast.ArrayType); ok {
if !isValidErrorArrayTypeName(t) {
reportAboutErrorType(pass, tSpec.Pos(), t, true)
}
} else if !isValidErrorTypeName(t) {
reportAboutErrorType(pass, tSpec.Pos(), t, false)
}
})
errorFuncs := stringSet{}
insp.Preorder(funcNodes, func(node ast.Node) {
f := node.(*ast.FuncDecl)
if isFuncReturningErr(f.Type, allTypes, errorTypes) {
errorFuncs[f.Name.Name] = struct{}{}
}
})
inspectPkgLevelVarsOnly := func(node ast.Node) bool {
switch v := node.(type) {
case *ast.FuncDecl:
return false
case *ast.ValueSpec:
if name, ok := isSentinelError(v, pkgAliases, allTypes, errorTypes, errorFuncs); ok && !isValidErrorVarName(name) {
reportAboutErrorVar(pass, v.Pos(), name)
}
}
return true
}
for _, f := range pass.Files {
ast.Inspect(f, inspectPkgLevelVarsOnly)
}
return nil, nil //nolint:nilnil
}
func reportAboutErrorType(pass *analysis.Pass, typePos token.Pos, typeName string, isArrayType bool) {
var form string
if unicode.IsLower([]rune(typeName)[0]) {
form = "xxxError"
} else {
form = "XxxError"
}
if isArrayType {
form += "s"
}
pass.Reportf(typePos, "the type name `%s` should conform to the `%s` format", typeName, form)
}
func reportAboutErrorVar(pass *analysis.Pass, pos token.Pos, varName string) {
var form string
if unicode.IsLower([]rune(varName)[0]) {
form = "errXxx"
} else {
form = "ErrXxx"
}
pass.Reportf(pos, "the variable name `%s` should conform to the `%s` format", varName, form)
}
func getPkgFromPath(p string) string {
idx := strings.LastIndex(p, "/")
if idx == -1 {
return p
}
return p[idx+1:]
}

View File

@ -1,272 +0,0 @@
package analyzer
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"strings"
"unicode"
)
func isMethodError(f *ast.FuncDecl) (typeName string, ok bool) {
if f.Recv == nil || len(f.Recv.List) != 1 {
return "", false
}
if f.Name == nil || f.Name.Name != "Error" {
return "", false
}
if f.Type == nil || f.Type.Results == nil || len(f.Type.Results.List) != 1 {
return "", false
}
returnType, ok := f.Type.Results.List[0].Type.(*ast.Ident)
if !ok {
return "", false
}
var receiverType string
unwrapIdentName := func(e ast.Expr) string {
switch v := e.(type) {
case *ast.Ident:
return v.Name
case *ast.IndexExpr:
if i, ok := v.X.(*ast.Ident); ok {
return i.Name
}
case *ast.IndexListExpr:
if i, ok := v.X.(*ast.Ident); ok {
return i.Name
}
}
panic(fmt.Errorf("unsupported Error() receiver type %q", types.ExprString(e)))
}
switch rt := f.Recv.List[0].Type; v := rt.(type) {
case *ast.Ident, *ast.IndexExpr, *ast.IndexListExpr: // SomeError, SomeError[T], SomeError[T1, T2, ...]
receiverType = unwrapIdentName(rt)
case *ast.StarExpr: // *SomeError, *SomeError[T], *SomeError[T1, T2, ...]
receiverType = unwrapIdentName(v.X)
}
return receiverType, returnType.Name == "string"
}
func isValidErrorTypeName(s string) bool {
if isInitialism(s) {
return true
}
words := split(s)
wordsCnt := wordsCount(words)
if wordsCnt["error"] != 1 {
return false
}
return words[len(words)-1] == "error"
}
func isValidErrorArrayTypeName(s string) bool {
if isInitialism(s) {
return true
}
words := split(s)
wordsCnt := wordsCount(words)
if wordsCnt["errors"] != 1 {
return false
}
return words[len(words)-1] == "errors"
}
func isFuncReturningErr(fType *ast.FuncType, allTypes, errorTypes stringSet) bool {
if fType == nil || fType.Results == nil || len(fType.Results.List) != 1 {
return false
}
var returnTypeName string
switch rt := fType.Results.List[0].Type.(type) {
case *ast.Ident:
returnTypeName = rt.Name
case *ast.StarExpr:
if i, ok := rt.X.(*ast.Ident); ok {
returnTypeName = i.Name
}
}
return isErrorType(returnTypeName, allTypes, errorTypes)
}
func isErrorType(tName string, allTypes, errorTypes stringSet) bool {
_, isUserType := allTypes[tName]
_, isErrType := errorTypes[tName]
return isErrType || (tName == "error" && !isUserType)
}
var knownErrConstructors = stringSet{
"fmt.Errorf": {},
"errors.Errorf": {},
"errors.New": {},
"errors.Newf": {},
"errors.NewWithDepth": {},
"errors.NewWithDepthf": {},
"errors.NewAssertionErrorWithWrappedErrf": {},
}
func isSentinelError( //nolint:gocognit,gocyclo
v *ast.ValueSpec,
pkgAliases map[string]string,
allTypes, errorTypes, errorFuncs stringSet,
) (varName string, ok bool) {
if len(v.Names) != 1 {
return "", false
}
varName = v.Names[0].Name
switch vv := v.Type.(type) {
// var ErrEndOfFile error
// var ErrEndOfFile SomeErrType
case *ast.Ident:
if isErrorType(vv.Name, allTypes, errorTypes) {
return varName, true
}
// var ErrEndOfFile *SomeErrType
case *ast.StarExpr:
if i, ok := vv.X.(*ast.Ident); ok && isErrorType(i.Name, allTypes, errorTypes) {
return varName, true
}
}
if len(v.Values) != 1 {
return "", false
}
switch vv := v.Values[0].(type) {
case *ast.CallExpr:
switch fun := vv.Fun.(type) {
// var ErrEndOfFile = errors.New("end of file")
case *ast.SelectorExpr:
pkg, ok := fun.X.(*ast.Ident)
if !ok {
return "", false
}
pkgFun := fun.Sel
pkgName := pkg.Name
if a, ok := pkgAliases[pkgName]; ok {
pkgName = a
}
_, ok = knownErrConstructors[pkgName+"."+pkgFun.Name]
return varName, ok
// var ErrEndOfFile = newErrEndOfFile()
// var ErrEndOfFile = new(EndOfFileError)
// const ErrEndOfFile = constError("end of file")
// var statusCodeError = new(SomePtrError[string])
case *ast.Ident:
if isErrorType(fun.Name, allTypes, errorTypes) {
return varName, true
}
if _, ok := errorFuncs[fun.Name]; ok {
return varName, true
}
if fun.Name == "new" && len(vv.Args) == 1 {
switch i := vv.Args[0].(type) {
case *ast.Ident:
return varName, isErrorType(i.Name, allTypes, errorTypes)
case *ast.IndexExpr:
if ii, ok := i.X.(*ast.Ident); ok {
return varName, isErrorType(ii.Name, allTypes, errorTypes)
}
}
}
// var ErrEndOfFile = func() error { ... }
case *ast.FuncLit:
return varName, isFuncReturningErr(fun.Type, allTypes, errorTypes)
}
// var ErrEndOfFile = &EndOfFileError{}
// var ErrOK = &SomePtrError[string]{Code: "200 OK"}
case *ast.UnaryExpr:
if vv.Op == token.AND { // &
if lit, ok := vv.X.(*ast.CompositeLit); ok {
switch i := lit.Type.(type) {
case *ast.Ident:
return varName, isErrorType(i.Name, allTypes, errorTypes)
case *ast.IndexExpr:
if ii, ok := i.X.(*ast.Ident); ok {
return varName, isErrorType(ii.Name, allTypes, errorTypes)
}
}
}
}
// var ErrEndOfFile = EndOfFileError{}
// var ErrNotFound = SomeError[string]{Code: "Not Found"}
case *ast.CompositeLit:
switch i := vv.Type.(type) {
case *ast.Ident:
return varName, isErrorType(i.Name, allTypes, errorTypes)
case *ast.IndexExpr:
if ii, ok := i.X.(*ast.Ident); ok {
return varName, isErrorType(ii.Name, allTypes, errorTypes)
}
}
}
return "", false
}
func isValidErrorVarName(s string) bool {
if isInitialism(s) {
return true
}
words := split(s)
wordsCnt := wordsCount(words)
if wordsCnt["err"] != 1 {
return false
}
return words[0] == "err"
}
func isInitialism(s string) bool {
return strings.ToLower(s) == s || strings.ToUpper(s) == s
}
func split(s string) []string {
var words []string
ss := []rune(s)
var b strings.Builder
b.WriteRune(ss[0])
for _, r := range ss[1:] {
if unicode.IsUpper(r) {
words = append(words, strings.ToLower(b.String()))
b.Reset()
}
b.WriteRune(r)
}
words = append(words, strings.ToLower(b.String()))
return words
}
func wordsCount(w []string) map[string]int {
result := make(map[string]int, len(w))
for _, ww := range w {
result[ww]++
}
return result
}

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2021 Anton Telyshev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,184 +0,0 @@
package analyzer
import (
"go/ast"
"go/token"
"go/types"
"strconv"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)
const (
name = "nilnil"
doc = "Checks that there is no simultaneous return of `nil` error and an invalid value."
reportMsg = "return both the `nil` error and invalid value: use a sentinel error instead"
)
// New returns new nilnil analyzer.
func New() *analysis.Analyzer {
n := newNilNil()
a := &analysis.Analyzer{
Name: name,
Doc: doc,
Run: n.run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
}
a.Flags.Var(&n.checkedTypes, "checked-types", "coma separated list")
return a
}
type nilNil struct {
checkedTypes checkedTypes
}
func newNilNil() *nilNil {
return &nilNil{
checkedTypes: newDefaultCheckedTypes(),
}
}
var funcAndReturns = []ast.Node{
(*ast.FuncDecl)(nil),
(*ast.FuncLit)(nil),
(*ast.ReturnStmt)(nil),
}
func (n *nilNil) run(pass *analysis.Pass) (interface{}, error) {
insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
var fs funcTypeStack
insp.Nodes(funcAndReturns, func(node ast.Node, push bool) (proceed bool) {
switch v := node.(type) {
case *ast.FuncLit:
if push {
fs.Push(v.Type)
} else {
fs.Pop()
}
case *ast.FuncDecl:
if push {
fs.Push(v.Type)
} else {
fs.Pop()
}
case *ast.ReturnStmt:
ft := fs.Top() // Current function.
if !push || len(v.Results) != 2 || ft == nil || ft.Results == nil || len(ft.Results.List) != 2 {
return false
}
fRes1Type := pass.TypesInfo.TypeOf(ft.Results.List[0].Type)
if fRes1Type == nil {
return false
}
fRes2Type := pass.TypesInfo.TypeOf(ft.Results.List[1].Type)
if fRes2Type == nil {
return false
}
ok, zv := n.isDangerNilType(fRes1Type)
if !(ok && isErrorType(fRes2Type)) {
return false
}
retVal, retErr := v.Results[0], v.Results[1]
var needWarn bool
switch zv {
case zeroValueNil:
needWarn = isNil(pass, retVal) && isNil(pass, retErr)
case zeroValueZero:
needWarn = isZero(retVal) && isNil(pass, retErr)
}
if needWarn {
pass.Reportf(v.Pos(), reportMsg)
}
}
return true
})
return nil, nil //nolint:nilnil
}
type zeroValue int
const (
zeroValueNil = iota + 1
zeroValueZero
)
func (n *nilNil) isDangerNilType(t types.Type) (bool, zeroValue) {
switch v := t.(type) {
case *types.Pointer:
return n.checkedTypes.Contains(ptrType), zeroValueNil
case *types.Signature:
return n.checkedTypes.Contains(funcType), zeroValueNil
case *types.Interface:
return n.checkedTypes.Contains(ifaceType), zeroValueNil
case *types.Map:
return n.checkedTypes.Contains(mapType), zeroValueNil
case *types.Chan:
return n.checkedTypes.Contains(chanType), zeroValueNil
case *types.Basic:
if v.Kind() == types.Uintptr {
return n.checkedTypes.Contains(uintptrType), zeroValueZero
}
if v.Kind() == types.UnsafePointer {
return n.checkedTypes.Contains(unsafeptrType), zeroValueNil
}
case *types.Named:
return n.isDangerNilType(v.Underlying())
}
return false, 0
}
var errorIface = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
func isErrorType(t types.Type) bool {
_, ok := t.Underlying().(*types.Interface)
return ok && types.Implements(t, errorIface)
}
func isNil(pass *analysis.Pass, e ast.Expr) bool {
i, ok := e.(*ast.Ident)
if !ok {
return false
}
_, ok = pass.TypesInfo.ObjectOf(i).(*types.Nil)
return ok
}
func isZero(e ast.Expr) bool {
bl, ok := e.(*ast.BasicLit)
if !ok {
return false
}
if bl.Kind != token.INT {
return false
}
v, err := strconv.ParseInt(bl.Value, 0, 64)
if err != nil {
return false
}
return v == 0
}

View File

@ -1,79 +0,0 @@
package analyzer
import (
"fmt"
"sort"
"strings"
)
func newDefaultCheckedTypes() checkedTypes {
return checkedTypes{
ptrType: {},
funcType: {},
ifaceType: {},
mapType: {},
chanType: {},
uintptrType: {},
unsafeptrType: {},
}
}
const separator = ','
type typeName string
func (t typeName) S() string {
return string(t)
}
const (
ptrType typeName = "ptr"
funcType typeName = "func"
ifaceType typeName = "iface"
mapType typeName = "map"
chanType typeName = "chan"
uintptrType typeName = "uintptr"
unsafeptrType typeName = "unsafeptr"
)
type checkedTypes map[typeName]struct{}
func (c checkedTypes) Contains(t typeName) bool {
_, ok := c[t]
return ok
}
func (c checkedTypes) String() string {
result := make([]string, 0, len(c))
for t := range c {
result = append(result, t.S())
}
sort.Strings(result)
return strings.Join(result, string(separator))
}
func (c checkedTypes) Set(s string) error {
types := strings.FieldsFunc(s, func(c rune) bool { return c == separator })
if len(types) == 0 {
return nil
}
c.disableAll()
for _, t := range types {
switch tt := typeName(t); tt {
case ptrType, funcType, ifaceType, mapType, chanType, uintptrType, unsafeptrType:
c[tt] = struct{}{}
default:
return fmt.Errorf("unknown checked type name %q (see help)", t)
}
}
return nil
}
func (c checkedTypes) disableAll() {
for k := range c {
delete(c, k)
}
}

View File

@ -1,29 +0,0 @@
package analyzer
import (
"go/ast"
)
type funcTypeStack []*ast.FuncType
func (s *funcTypeStack) Push(f *ast.FuncType) {
*s = append(*s, f)
}
func (s *funcTypeStack) Pop() *ast.FuncType {
if len(*s) == 0 {
return nil
}
last := len(*s) - 1
f := (*s)[last]
*s = (*s)[:last]
return f
}
func (s *funcTypeStack) Top() *ast.FuncType {
if len(*s) == 0 {
return nil
}
return (*s)[len(*s)-1]
}

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2022 Anton Telyshev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,93 +0,0 @@
package analyzer
import (
"fmt"
"go/ast"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/ast/inspector"
"github.com/Antonboom/testifylint/internal/analysisutil"
"github.com/Antonboom/testifylint/internal/checkers"
"github.com/Antonboom/testifylint/internal/config"
"github.com/Antonboom/testifylint/internal/testify"
)
const (
name = "testifylint"
doc = "Checks usage of " + testify.ModulePath + "."
url = "https://github.com/antonboom/" + name
)
// New returns a new instance of testifylint analyzer.
func New() *analysis.Analyzer {
cfg := config.NewDefault()
analyzer := &analysis.Analyzer{
Name: name,
Doc: doc,
URL: url,
Run: func(pass *analysis.Pass) (any, error) {
regularCheckers, advancedCheckers, err := newCheckers(cfg)
if err != nil {
return nil, fmt.Errorf("build checkers: %v", err)
}
tl := &testifyLint{
regularCheckers: regularCheckers,
advancedCheckers: advancedCheckers,
}
return tl.run(pass)
},
}
config.BindToFlags(&cfg, &analyzer.Flags)
return analyzer
}
type testifyLint struct {
regularCheckers []checkers.RegularChecker
advancedCheckers []checkers.AdvancedChecker
}
func (tl *testifyLint) run(pass *analysis.Pass) (any, error) {
filesToAnalysis := make([]*ast.File, 0, len(pass.Files))
for _, f := range pass.Files {
if !analysisutil.Imports(f, testify.AssertPkgPath, testify.RequirePkgPath, testify.SuitePkgPath) {
continue
}
filesToAnalysis = append(filesToAnalysis, f)
}
insp := inspector.New(filesToAnalysis)
// Regular checkers.
insp.Preorder([]ast.Node{(*ast.CallExpr)(nil)}, func(node ast.Node) {
tl.regularCheck(pass, node.(*ast.CallExpr))
})
// Advanced checkers.
for _, ch := range tl.advancedCheckers {
for _, d := range ch.Check(pass, insp) {
pass.Report(d)
}
}
return nil, nil
}
func (tl *testifyLint) regularCheck(pass *analysis.Pass, ce *ast.CallExpr) {
call := checkers.NewCallMeta(pass, ce)
if nil == call {
return
}
for _, ch := range tl.regularCheckers {
if d := ch.Check(pass, call); d != nil {
pass.Report(*d)
// NOTE(a.telyshev): I'm not interested in multiple diagnostics per assertion.
// This simplifies the code and also makes the linter more efficient.
return
}
}
}

View File

@ -1,81 +0,0 @@
package analyzer
import (
"fmt"
"github.com/Antonboom/testifylint/internal/checkers"
"github.com/Antonboom/testifylint/internal/config"
)
// newCheckers accepts linter config and returns slices of enabled checkers sorted by priority.
func newCheckers(cfg config.Config) ([]checkers.RegularChecker, []checkers.AdvancedChecker, error) {
if err := cfg.Validate(); err != nil {
return nil, nil, err
}
enabledCheckersSet := make(map[string]struct{})
if cfg.EnableAll {
for _, checker := range checkers.All() {
enabledCheckersSet[checker] = struct{}{}
}
} else if !cfg.DisableAll {
for _, checker := range checkers.EnabledByDefault() {
enabledCheckersSet[checker] = struct{}{}
}
}
for _, checker := range cfg.EnabledCheckers {
enabledCheckersSet[checker] = struct{}{}
}
for _, checker := range cfg.DisabledCheckers {
delete(enabledCheckersSet, checker)
}
enabledCheckers := make([]string, 0, len(enabledCheckersSet))
for v := range enabledCheckersSet {
enabledCheckers = append(enabledCheckers, v)
}
checkers.SortByPriority(enabledCheckers)
regularCheckers := make([]checkers.RegularChecker, 0, len(enabledCheckers))
advancedCheckers := make([]checkers.AdvancedChecker, 0, len(enabledCheckers)/2)
for _, name := range enabledCheckers {
ch, ok := checkers.Get(name)
if !ok {
return nil, nil, fmt.Errorf("unknown checker %q", name)
}
switch c := ch.(type) {
case *checkers.BoolCompare:
c.SetIgnoreCustomTypes(cfg.BoolCompare.IgnoreCustomTypes)
case *checkers.ExpectedActual:
c.SetExpVarPattern(cfg.ExpectedActual.ExpVarPattern.Regexp)
case *checkers.Formatter:
c.SetCheckFormatString(cfg.Formatter.CheckFormatString)
c.SetRequireFFuncs(cfg.Formatter.RequireFFuncs)
case *checkers.GoRequire:
c.SetIgnoreHTTPHandlers(cfg.GoRequire.IgnoreHTTPHandlers)
case *checkers.RequireError:
c.SetFnPattern(cfg.RequireError.FnPattern.Regexp)
case *checkers.SuiteExtraAssertCall:
c.SetMode(cfg.SuiteExtraAssertCall.Mode)
}
switch casted := ch.(type) {
case checkers.RegularChecker:
regularCheckers = append(regularCheckers, casted)
case checkers.AdvancedChecker:
advancedCheckers = append(advancedCheckers, casted)
}
}
return regularCheckers, advancedCheckers, nil
}

View File

@ -1,9 +0,0 @@
// Package analysisutil contains functions common for `analyzer` and `internal/checkers` packages.
// In addition, it is intended to "lighten" these packages.
//
// If the function is common to several packages, or it makes sense to test it separately without
// "polluting" the target package with tests of private functionality, then you can put function in this package.
//
// It's important to avoid dependency on `golang.org/x/tools/go/analysis` in the helpers API.
// This makes the API "narrower" and also allows you to test functions without some "abstraction leaks".
package analysisutil

View File

@ -1,28 +0,0 @@
package analysisutil
import (
"go/ast"
"strconv"
)
// Imports tells if the file imports at least one of the packages.
// If no packages provided then function returns false.
func Imports(file *ast.File, pkgs ...string) bool {
for _, i := range file.Imports {
if i.Path == nil {
continue
}
path, err := strconv.Unquote(i.Path.Value)
if err != nil {
continue
}
// NOTE(a.telyshev): Don't use `slices.Contains` to keep the minimum module version 1.20.
for _, pkg := range pkgs { // Small O(n).
if pkg == path {
return true
}
}
}
return false
}

View File

@ -1,34 +0,0 @@
package analysisutil
import (
"bytes"
"go/ast"
"go/format"
"go/token"
)
// NodeString is a more powerful analogue of types.ExprString.
// Return empty string if node AST is invalid.
func NodeString(fset *token.FileSet, node ast.Node) string {
if v := formatNode(fset, node); v != nil {
return v.String()
}
return ""
}
// NodeBytes works as NodeString but returns a byte slice.
// Return nil if node AST is invalid.
func NodeBytes(fset *token.FileSet, node ast.Node) []byte {
if v := formatNode(fset, node); v != nil {
return v.Bytes()
}
return nil
}
func formatNode(fset *token.FileSet, node ast.Node) *bytes.Buffer {
buf := new(bytes.Buffer)
if err := format.Node(buf, fset, node); err != nil {
return nil
}
return buf
}

View File

@ -1,34 +0,0 @@
package analysisutil
import (
"go/ast"
"go/types"
)
// ObjectOf works in context of Golang package and returns types.Object for the given object's package and name.
// The search is based on the provided package and its dependencies (imports).
// Returns nil if the object is not found.
func ObjectOf(pkg *types.Package, objPkg, objName string) types.Object {
if pkg.Path() == objPkg {
return pkg.Scope().Lookup(objName)
}
for _, i := range pkg.Imports() {
if trimVendor(i.Path()) == objPkg {
return i.Scope().Lookup(objName)
}
}
return nil
}
// IsObj returns true if expression is identifier which notes to given types.Object.
// Useful in combination with types.Universe objects.
func IsObj(typesInfo *types.Info, expr ast.Expr, expected types.Object) bool {
id, ok := expr.(*ast.Ident)
if !ok {
return false
}
obj := typesInfo.ObjectOf(id)
return obj == expected
}

View File

@ -1,19 +0,0 @@
package analysisutil
import (
"go/types"
"strings"
)
// IsPkg checks that package has corresponding objName and path.
// Supports vendored packages.
func IsPkg(pkg *types.Package, name, path string) bool {
return pkg.Name() == name && trimVendor(pkg.Path()) == path
}
func trimVendor(path string) string {
if strings.HasPrefix(path, "vendor/") {
return path[len("vendor/"):]
}
return path
}

View File

@ -1,69 +0,0 @@
package checkers
import (
"fmt"
"strconv"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/ast/inspector"
"github.com/Antonboom/testifylint/internal/testify"
)
// BlankImport detects useless blank imports of testify's packages.
// These imports are useless since testify doesn't do any magic with init() function.
//
// The checker detects situations like
//
// import (
// "testing"
//
// _ "github.com/stretchr/testify"
// _ "github.com/stretchr/testify/assert"
// _ "github.com/stretchr/testify/http"
// _ "github.com/stretchr/testify/mock"
// _ "github.com/stretchr/testify/require"
// _ "github.com/stretchr/testify/suite"
// )
//
// and requires
//
// import (
// "testing"
// )
type BlankImport struct{}
// NewBlankImport constructs BlankImport checker.
func NewBlankImport() BlankImport { return BlankImport{} }
func (BlankImport) Name() string { return "blank-import" }
func (checker BlankImport) Check(pass *analysis.Pass, _ *inspector.Inspector) (diagnostics []analysis.Diagnostic) {
for _, file := range pass.Files {
for _, imp := range file.Imports {
if imp.Name == nil || imp.Name.Name != "_" {
continue
}
pkg, err := strconv.Unquote(imp.Path.Value)
if err != nil {
continue
}
if _, ok := packagesNotIntendedForBlankImport[pkg]; !ok {
continue
}
msg := fmt.Sprintf("avoid blank import of %s as it does nothing", pkg)
diagnostics = append(diagnostics, *newDiagnostic(checker.Name(), imp, msg, nil))
}
}
return diagnostics
}
var packagesNotIntendedForBlankImport = map[string]struct{}{
testify.ModulePath: {},
testify.AssertPkgPath: {},
testify.HTTPPkgPath: {},
testify.MockPkgPath: {},
testify.RequirePkgPath: {},
testify.SuitePkgPath: {},
}

View File

@ -1,212 +0,0 @@
package checkers
import (
"go/ast"
"go/token"
"golang.org/x/tools/go/analysis"
"github.com/Antonboom/testifylint/internal/analysisutil"
)
// BoolCompare detects situations like
//
// assert.Equal(t, false, result)
// assert.EqualValues(t, false, result)
// assert.Exactly(t, false, result)
// assert.NotEqual(t, true, result)
// assert.NotEqualValues(t, true, result)
// assert.False(t, !result)
// assert.True(t, result == true)
// ...
//
// and requires
//
// assert.False(t, result)
// assert.True(t, result)
type BoolCompare struct {
ignoreCustomTypes bool
}
// NewBoolCompare constructs BoolCompare checker.
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 {
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,
End: replaceEnd,
NewText: analysisutil.NodeBytes(pass.Fset, survivingArg),
}),
)
}
newUseTrueDiagnostic := func(survivingArg ast.Expr, replaceStart, replaceEnd token.Pos) *analysis.Diagnostic {
return newUseFnDiagnostic("True", survivingArg, replaceStart, replaceEnd)
}
newUseFalseDiagnostic := func(survivingArg ast.Expr, replaceStart, replaceEnd token.Pos) *analysis.Diagnostic {
return newUseFnDiagnostic("False", survivingArg, replaceStart, replaceEnd)
}
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",
TextEdits: []analysis.TextEdit{{
Pos: replaceStart,
End: replaceEnd,
NewText: analysisutil.NodeBytes(pass.Fset, survivingArg),
}},
},
)
}
switch call.Fn.NameFTrimmed {
case "Equal", "EqualValues", "Exactly":
if len(call.Args) < 2 {
return nil
}
arg1, arg2 := call.Args[0], call.Args[1]
if anyCondSatisfaction(pass, isEmptyInterface, arg1, arg2) {
return nil
}
if anyCondSatisfaction(pass, isBoolOverride, arg1, arg2) {
return nil
}
t1, t2 := isUntypedTrue(pass, arg1), isUntypedTrue(pass, arg2)
f1, f2 := isUntypedFalse(pass, arg1), isUntypedFalse(pass, arg2)
switch {
case xor(t1, t2):
survivingArg, _ := anyVal([]bool{t1, t2}, arg2, arg1)
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())
}
case "NotEqual", "NotEqualValues":
if len(call.Args) < 2 {
return nil
}
arg1, arg2 := call.Args[0], call.Args[1]
if anyCondSatisfaction(pass, isEmptyInterface, arg1, arg2) {
return nil
}
if anyCondSatisfaction(pass, isBoolOverride, arg1, arg2) {
return nil
}
t1, t2 := isUntypedTrue(pass, arg1), isUntypedTrue(pass, arg2)
f1, f2 := isUntypedFalse(pass, arg1), isUntypedFalse(pass, arg2)
switch {
case xor(t1, t2):
survivingArg, _ := anyVal([]bool{t1, t2}, arg2, arg1)
return newUseFalseDiagnostic(survivingArg, arg1.Pos(), arg2.End())
case xor(f1, f2):
survivingArg, _ := anyVal([]bool{f1, f2}, arg2, arg1)
return newUseTrueDiagnostic(survivingArg, arg1.Pos(), arg2.End())
}
case "True":
if len(call.Args) < 1 {
return nil
}
expr := call.Args[0]
{
arg1, ok1 := isComparisonWithTrue(pass, expr, token.EQL)
arg2, ok2 := isComparisonWithFalse(pass, expr, token.NEQ)
survivingArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2)
if ok && !isEmptyInterface(pass, survivingArg) {
return newNeedSimplifyDiagnostic(survivingArg, expr.Pos(), expr.End())
}
}
{
arg1, ok1 := isComparisonWithTrue(pass, expr, token.NEQ)
arg2, ok2 := isComparisonWithFalse(pass, expr, token.EQL)
arg3, ok3 := isNegation(expr)
survivingArg, ok := anyVal([]bool{ok1, ok2, ok3}, arg1, arg2, arg3)
if ok && !isEmptyInterface(pass, survivingArg) {
return newUseFalseDiagnostic(survivingArg, expr.Pos(), expr.End())
}
}
case "False":
if len(call.Args) < 1 {
return nil
}
expr := call.Args[0]
{
arg1, ok1 := isComparisonWithTrue(pass, expr, token.EQL)
arg2, ok2 := isComparisonWithFalse(pass, expr, token.NEQ)
survivingArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2)
if ok && !isEmptyInterface(pass, survivingArg) {
return newNeedSimplifyDiagnostic(survivingArg, expr.Pos(), expr.End())
}
}
{
arg1, ok1 := isComparisonWithTrue(pass, expr, token.NEQ)
arg2, ok2 := isComparisonWithFalse(pass, expr, token.EQL)
arg3, ok3 := isNegation(expr)
survivingArg, ok := anyVal([]bool{ok1, ok2, ok3}, arg1, arg2, arg3)
if ok && !isEmptyInterface(pass, survivingArg) {
return newUseTrueDiagnostic(survivingArg, expr.Pos(), expr.End())
}
}
}
return nil
}
func isNegation(e ast.Expr) (ast.Expr, bool) {
ue, ok := e.(*ast.UnaryExpr)
if !ok {
return nil, false
}
return ue.X, ue.Op == token.NOT
}

View File

@ -1,133 +0,0 @@
package checkers
import (
"go/ast"
"go/types"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/types/typeutil"
"github.com/Antonboom/testifylint/internal/analysisutil"
"github.com/Antonboom/testifylint/internal/testify"
)
// CallMeta stores meta info about assertion function/method call, for example
//
// assert.Equal(t, 42, result, "helpful comment")
type CallMeta struct {
// Call stores the original AST call expression.
Call *ast.CallExpr
// Range contains start and end position of assertion call.
analysis.Range
// IsPkg true if this is package (not object) call.
IsPkg bool
// IsAssert true if this is "testify/assert" package (or object) call.
IsAssert bool
// Selector is the AST expression of "assert.Equal".
Selector *ast.SelectorExpr
// SelectorXStr is a string representation of Selector's left part value before point, e.g. "assert".
SelectorXStr string
// Fn stores meta info about assertion function itself.
Fn FnMeta
// Args stores assertion call arguments but without `t *testing.T` argument.
// E.g [42, result, "helpful comment"].
Args []ast.Expr
// ArgsRaw stores assertion call initial arguments.
// E.g [t, 42, result, "helpful comment"].
ArgsRaw []ast.Expr
}
func (c CallMeta) String() string {
return c.SelectorXStr + "." + c.Fn.Name
}
// FnMeta stores meta info about assertion function itself, for example "Equal".
type FnMeta struct {
// Range contains start and end position of function Name.
analysis.Range
// Name is a function name.
Name string
// NameFTrimmed is a function name without "f" suffix.
NameFTrimmed string
// IsFmt is true if function is formatted, e.g. "Equalf".
IsFmt bool
// Signature represents assertion signature.
Signature *types.Signature
}
// NewCallMeta returns meta information about testify assertion call.
// Returns nil if ast.CallExpr is not testify call.
func NewCallMeta(pass *analysis.Pass, ce *ast.CallExpr) *CallMeta {
se, ok := ce.Fun.(*ast.SelectorExpr)
if !ok || se.Sel == nil {
return nil
}
fnName := se.Sel.Name
initiatorPkg, isPkgCall := func() (*types.Package, bool) {
// Examples:
// s.Assert -> method of *suite.Suite -> package suite ("vendor/github.com/stretchr/testify/suite")
// s.Assert().Equal -> method of *assert.Assertions -> package assert ("vendor/github.com/stretchr/testify/assert")
// s.Equal -> method of *assert.Assertions -> package assert ("vendor/github.com/stretchr/testify/assert")
// reqObj.Falsef -> method of *require.Assertions -> package require ("vendor/github.com/stretchr/testify/require")
if sel, isSel := pass.TypesInfo.Selections[se]; isSel {
return sel.Obj().Pkg(), false
}
// Examples:
// assert.False -> assert -> package assert ("vendor/github.com/stretchr/testify/assert")
// require.NotEqualf -> require -> package require ("vendor/github.com/stretchr/testify/require")
if id, isIdent := se.X.(*ast.Ident); isIdent {
if selObj := pass.TypesInfo.ObjectOf(id); selObj != nil {
if pkg, isPkgName := selObj.(*types.PkgName); isPkgName {
return pkg.Imported(), true
}
}
}
return nil, false
}()
if initiatorPkg == nil {
return nil
}
isAssert := analysisutil.IsPkg(initiatorPkg, testify.AssertPkgName, testify.AssertPkgPath)
isRequire := analysisutil.IsPkg(initiatorPkg, testify.RequirePkgName, testify.RequirePkgPath)
if !(isAssert || isRequire) {
return nil
}
funcObj, ok := typeutil.Callee(pass.TypesInfo, ce).(*types.Func)
if !ok {
return nil
}
return &CallMeta{
Call: ce,
Range: ce,
IsPkg: isPkgCall,
IsAssert: isAssert,
Selector: se,
SelectorXStr: analysisutil.NodeString(pass.Fset, se.X),
Fn: FnMeta{
Range: se.Sel,
Name: fnName,
NameFTrimmed: strings.TrimSuffix(fnName, "f"),
IsFmt: strings.HasSuffix(fnName, "f"),
Signature: funcObj.Type().(*types.Signature), // NOTE(a.telyshev): Func's Type() is always a *Signature.
},
Args: trimTArg(pass, ce.Args),
ArgsRaw: ce.Args,
}
}
func trimTArg(pass *analysis.Pass, args []ast.Expr) []ast.Expr {
if len(args) == 0 {
return args
}
if implementsTestingT(pass, args[0]) {
return args[1:]
}
return args
}

View File

@ -1,23 +0,0 @@
package checkers
import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/ast/inspector"
)
// Checker describes named checker.
type Checker interface {
Name() string
}
// RegularChecker check assertion call presented in CallMeta form.
type RegularChecker interface {
Checker
Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic
}
// AdvancedChecker implements complex Check logic different from trivial CallMeta check.
type AdvancedChecker interface {
Checker
Check(pass *analysis.Pass, inspector *inspector.Inspector) []analysis.Diagnostic
}

View File

@ -1,109 +0,0 @@
package checkers
import (
"sort"
)
// registry stores checkers meta information in checkers' priority order.
var registry = checkersRegistry{
// Regular checkers.
{factory: asCheckerFactory(NewFloatCompare), enabledByDefault: true},
{factory: asCheckerFactory(NewBoolCompare), enabledByDefault: true},
{factory: asCheckerFactory(NewEmpty), enabledByDefault: true},
{factory: asCheckerFactory(NewLen), enabledByDefault: true},
{factory: asCheckerFactory(NewNegativePositive), enabledByDefault: true},
{factory: asCheckerFactory(NewCompares), enabledByDefault: true},
{factory: asCheckerFactory(NewErrorNil), enabledByDefault: true},
{factory: asCheckerFactory(NewNilCompare), enabledByDefault: true},
{factory: asCheckerFactory(NewErrorIsAs), enabledByDefault: true},
{factory: asCheckerFactory(NewExpectedActual), enabledByDefault: true},
{factory: asCheckerFactory(NewSuiteExtraAssertCall), enabledByDefault: true},
{factory: asCheckerFactory(NewSuiteDontUsePkg), enabledByDefault: true},
{factory: asCheckerFactory(NewUselessAssert), enabledByDefault: true},
{factory: asCheckerFactory(NewFormatter), enabledByDefault: true},
// Advanced checkers.
{factory: asCheckerFactory(NewBlankImport), enabledByDefault: true},
{factory: asCheckerFactory(NewGoRequire), enabledByDefault: true},
{factory: asCheckerFactory(NewRequireError), enabledByDefault: true},
{factory: asCheckerFactory(NewSuiteBrokenParallel), enabledByDefault: true},
{factory: asCheckerFactory(NewSuiteSubtestRun), enabledByDefault: true},
{factory: asCheckerFactory(NewSuiteTHelper), enabledByDefault: false},
}
type checkersRegistry []checkerMeta
type checkerMeta struct {
factory checkerFactory
enabledByDefault bool
}
type checkerFactory func() Checker
func asCheckerFactory[T Checker](fn func() T) checkerFactory {
return func() Checker {
return fn()
}
}
func (r checkersRegistry) get(name string) (m checkerMeta, priority int, found bool) {
for i, meta := range r {
if meta.factory().Name() == name {
return meta, i, true
}
}
return checkerMeta{}, 0, false
}
// All returns all checkers names sorted by checker's priority.
func All() []string {
result := make([]string, 0, len(registry))
for _, meta := range registry {
result = append(result, meta.factory().Name())
}
return result
}
// EnabledByDefault returns checkers enabled by default sorted by checker's priority.
func EnabledByDefault() []string {
result := make([]string, 0, len(registry))
for _, meta := range registry {
if meta.enabledByDefault {
result = append(result, meta.factory().Name())
}
}
return result
}
// Get returns new checker instance by checker's name.
func Get(name string) (Checker, bool) {
meta, _, ok := registry.get(name)
if ok {
return meta.factory(), true
}
return nil, false
}
// IsKnown checks if there is a checker with that name.
func IsKnown(name string) bool {
_, _, ok := registry.get(name)
return ok
}
// IsEnabledByDefault returns true if a checker is enabled by default.
// Returns false if there is no such checker in the registry.
// For pre-validation use Get or IsKnown.
func IsEnabledByDefault(name string) bool {
meta, _, ok := registry.get(name)
return ok && meta.enabledByDefault
}
// SortByPriority mutates the input checkers names by sorting them in checker priority order.
// Ignores unknown checkers. For pre-validation use Get or IsKnown.
func SortByPriority(checkers []string) {
sort.Slice(checkers, func(i, j int) bool {
lhs, rhs := checkers[i], checkers[j]
_, lhsPriority, _ := registry.get(lhs)
_, rhsPriority, _ := registry.get(rhs)
return lhsPriority < rhsPriority
})
}

View File

@ -1,99 +0,0 @@
package checkers
import (
"go/ast"
"go/token"
"golang.org/x/tools/go/analysis"
)
// Compares detects situations like
//
// assert.True(t, a == b)
// assert.True(t, a != b)
// assert.True(t, a > b)
// assert.True(t, a >= b)
// assert.True(t, a < b)
// assert.True(t, a <= b)
// assert.False(t, a == b)
// ...
//
// and requires
//
// assert.Equal(t, a, b)
// assert.NotEqual(t, a, b)
// assert.Greater(t, a, b)
// assert.GreaterOrEqual(t, a, b)
// assert.Less(t, a, b)
// assert.LessOrEqual(t, a, b)
//
// If `a` and `b` are pointers then `assert.Same`/`NotSame` is required instead,
// due to the inappropriate recursive nature of `assert.Equal` (based on `reflect.DeepEqual`).
type Compares struct{}
// NewCompares constructs Compares checker.
func NewCompares() Compares { return Compares{} }
func (Compares) Name() string { return "compares" }
func (checker Compares) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic {
if len(call.Args) < 1 {
return nil
}
be, ok := call.Args[0].(*ast.BinaryExpr)
if !ok {
return nil
}
var tokenToProposedFn map[token.Token]string
switch call.Fn.NameFTrimmed {
case "True":
tokenToProposedFn = tokenToProposedFnInsteadOfTrue
case "False":
tokenToProposedFn = tokenToProposedFnInsteadOfFalse
default:
return nil
}
proposedFn, ok := tokenToProposedFn[be.Op]
if !ok {
return nil
}
if isPointer(pass, be.X) && isPointer(pass, be.Y) {
switch proposedFn {
case "Equal":
proposedFn = "Same"
case "NotEqual":
proposedFn = "NotSame"
}
}
a, b := be.X, be.Y
return newUseFunctionDiagnostic(checker.Name(), call, proposedFn,
newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{
Pos: be.X.Pos(),
End: be.Y.End(),
NewText: formatAsCallArgs(pass, a, b),
}),
)
}
var tokenToProposedFnInsteadOfTrue = map[token.Token]string{
token.EQL: "Equal",
token.NEQ: "NotEqual",
token.GTR: "Greater",
token.GEQ: "GreaterOrEqual",
token.LSS: "Less",
token.LEQ: "LessOrEqual",
}
var tokenToProposedFnInsteadOfFalse = map[token.Token]string{
token.EQL: "NotEqual",
token.NEQ: "Equal",
token.GTR: "LessOrEqual",
token.GEQ: "Less",
token.LSS: "GreaterOrEqual",
token.LEQ: "Greater",
}

View File

@ -1,169 +0,0 @@
package checkers
import (
"go/ast"
"go/token"
"golang.org/x/tools/go/analysis"
"github.com/Antonboom/testifylint/internal/analysisutil"
)
// Empty detects situations like
//
// assert.Len(t, arr, 0)
// assert.Equal(t, 0, len(arr))
// assert.EqualValues(t, 0, len(arr))
// assert.Exactly(t, 0, len(arr))
// assert.LessOrEqual(t, len(arr), 0)
// assert.GreaterOrEqual(t, 0, len(arr))
// assert.Less(t, len(arr), 0)
// assert.Greater(t, 0, len(arr))
// assert.Less(t, len(arr), 1)
// assert.Greater(t, 1, len(arr))
// assert.Zero(t, len(arr))
// assert.Empty(t, len(arr))
//
// assert.NotEqual(t, 0, len(arr))
// assert.NotEqualValues(t, 0, len(arr))
// assert.Less(t, 0, len(arr))
// assert.Greater(t, len(arr), 0)
// assert.Positive(t, len(arr))
// assert.NotZero(t, len(arr))
// assert.NotEmpty(t, len(arr))
//
// and requires
//
// assert.Empty(t, arr)
// assert.NotEmpty(t, arr)
type Empty struct{}
// NewEmpty constructs Empty checker.
func NewEmpty() Empty { return Empty{} }
func (Empty) Name() string { return "empty" }
func (checker Empty) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic {
if d := checker.checkEmpty(pass, call); d != nil {
return d
}
return checker.checkNotEmpty(pass, call)
}
func (checker Empty) checkEmpty(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { //nolint:gocognit
newUseEmptyDiagnostic := func(replaceStart, replaceEnd token.Pos, replaceWith ast.Expr) *analysis.Diagnostic {
const proposed = "Empty"
return newUseFunctionDiagnostic(checker.Name(), call, proposed,
newSuggestedFuncReplacement(call, proposed, analysis.TextEdit{
Pos: replaceStart,
End: replaceEnd,
NewText: analysisutil.NodeBytes(pass.Fset, replaceWith),
}),
)
}
if len(call.Args) == 0 {
return nil
}
a := call.Args[0]
switch call.Fn.NameFTrimmed {
case "Zero", "Empty":
lenArg, ok := isBuiltinLenCall(pass, a)
if ok {
return newUseEmptyDiagnostic(a.Pos(), a.End(), lenArg)
}
}
if len(call.Args) < 2 {
return nil
}
b := call.Args[1]
switch call.Fn.NameFTrimmed {
case "Len":
if isZero(b) {
return newUseEmptyDiagnostic(a.Pos(), b.End(), a)
}
case "Equal", "EqualValues", "Exactly":
arg1, ok1 := isLenCallAndZero(pass, a, b)
arg2, ok2 := isLenCallAndZero(pass, b, a)
if lenArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2); ok {
return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg)
}
case "LessOrEqual":
if lenArg, ok := isBuiltinLenCall(pass, a); ok && isZero(b) {
return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg)
}
case "GreaterOrEqual":
if lenArg, ok := isBuiltinLenCall(pass, b); ok && isZero(a) {
return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg)
}
case "Less":
if lenArg, ok := isBuiltinLenCall(pass, a); ok && (isOne(b) || isZero(b)) {
return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg)
}
case "Greater":
if lenArg, ok := isBuiltinLenCall(pass, b); ok && (isOne(a) || isZero(a)) {
return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg)
}
}
return nil
}
func (checker Empty) checkNotEmpty(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { //nolint:gocognit
newUseNotEmptyDiagnostic := func(replaceStart, replaceEnd token.Pos, replaceWith ast.Expr) *analysis.Diagnostic {
const proposed = "NotEmpty"
return newUseFunctionDiagnostic(checker.Name(), call, proposed,
newSuggestedFuncReplacement(call, proposed, analysis.TextEdit{
Pos: replaceStart,
End: replaceEnd,
NewText: analysisutil.NodeBytes(pass.Fset, replaceWith),
}),
)
}
if len(call.Args) == 0 {
return nil
}
a := call.Args[0]
switch call.Fn.NameFTrimmed {
case "NotZero", "NotEmpty", "Positive":
lenArg, ok := isBuiltinLenCall(pass, a)
if ok {
return newUseNotEmptyDiagnostic(a.Pos(), a.End(), lenArg)
}
}
if len(call.Args) < 2 {
return nil
}
b := call.Args[1]
switch call.Fn.NameFTrimmed {
case "NotEqual", "NotEqualValues":
arg1, ok1 := isLenCallAndZero(pass, a, b)
arg2, ok2 := isLenCallAndZero(pass, b, a)
if lenArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2); ok {
return newUseNotEmptyDiagnostic(a.Pos(), b.End(), lenArg)
}
case "Less":
if lenArg, ok := isBuiltinLenCall(pass, b); ok && isZero(a) {
return newUseNotEmptyDiagnostic(a.Pos(), b.End(), lenArg)
}
case "Greater":
if lenArg, ok := isBuiltinLenCall(pass, a); ok && isZero(b) {
return newUseNotEmptyDiagnostic(a.Pos(), b.End(), lenArg)
}
}
return nil
}

View File

@ -1,142 +0,0 @@
package checkers
import (
"fmt"
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
)
// ErrorIsAs detects situations like
//
// assert.Error(t, err, errSentinel)
// assert.NoError(t, err, errSentinel)
// assert.True(t, errors.Is(err, errSentinel))
// assert.False(t, errors.Is(err, errSentinel))
// assert.True(t, errors.As(err, &target))
//
// and requires
//
// assert.ErrorIs(t, err, errSentinel)
// assert.NotErrorIs(t, err, errSentinel)
// assert.ErrorAs(t, err, &target)
//
// Also ErrorIsAs repeats go vet's "errorsas" check logic.
type ErrorIsAs struct{}
// NewErrorIsAs constructs ErrorIsAs checker.
func NewErrorIsAs() ErrorIsAs { return ErrorIsAs{} }
func (ErrorIsAs) Name() string { return "error-is-as" }
func (checker ErrorIsAs) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic {
switch call.Fn.NameFTrimmed {
case "Error":
if len(call.Args) >= 2 && isError(pass, call.Args[1]) {
const proposed = "ErrorIs"
msg := fmt.Sprintf("invalid usage of %[1]s.Error, use %[1]s.%[2]s instead", call.SelectorXStr, proposed)
return newDiagnostic(checker.Name(), call, msg, newSuggestedFuncReplacement(call, proposed))
}
case "NoError":
if len(call.Args) >= 2 && isError(pass, call.Args[1]) {
const proposed = "NotErrorIs"
msg := fmt.Sprintf("invalid usage of %[1]s.NoError, use %[1]s.%[2]s instead", call.SelectorXStr, proposed)
return newDiagnostic(checker.Name(), call, msg, newSuggestedFuncReplacement(call, proposed))
}
case "True":
if len(call.Args) < 1 {
return nil
}
ce, ok := call.Args[0].(*ast.CallExpr)
if !ok {
return nil
}
if len(ce.Args) != 2 {
return nil
}
var proposed string
switch {
case isErrorsIsCall(pass, ce):
proposed = "ErrorIs"
case isErrorsAsCall(pass, ce):
proposed = "ErrorAs"
}
if proposed != "" {
return newUseFunctionDiagnostic(checker.Name(), call, proposed,
newSuggestedFuncReplacement(call, proposed, analysis.TextEdit{
Pos: ce.Pos(),
End: ce.End(),
NewText: formatAsCallArgs(pass, ce.Args[0], ce.Args[1]),
}),
)
}
case "False":
if len(call.Args) < 1 {
return nil
}
ce, ok := call.Args[0].(*ast.CallExpr)
if !ok {
return nil
}
if len(ce.Args) != 2 {
return nil
}
if isErrorsIsCall(pass, ce) {
const proposed = "NotErrorIs"
return newUseFunctionDiagnostic(checker.Name(), call, proposed,
newSuggestedFuncReplacement(call, proposed, analysis.TextEdit{
Pos: ce.Pos(),
End: ce.End(),
NewText: formatAsCallArgs(pass, ce.Args[0], ce.Args[1]),
}),
)
}
case "ErrorAs":
if len(call.Args) < 2 {
return nil
}
// NOTE(a.telyshev): Logic below must be consistent with
// https://cs.opensource.google/go/x/tools/+/master:go/analysis/passes/errorsas/errorsas.go
var (
defaultReport = fmt.Sprintf("second argument to %s must be a non-nil pointer to either a type that implements error, or to any interface type", call) //nolint:lll
errorPtrReport = fmt.Sprintf("second argument to %s should not be *error", call)
)
target := call.Args[1]
if isEmptyInterface(pass, target) {
// `any` interface case. It is always allowed, since it often indicates
// a value forwarded from another source.
return nil
}
tv, ok := pass.TypesInfo.Types[target]
if !ok {
return nil
}
pt, ok := tv.Type.Underlying().(*types.Pointer)
if !ok {
return newDiagnostic(checker.Name(), call, defaultReport, nil)
}
if pt.Elem() == errorType {
return newDiagnostic(checker.Name(), call, errorPtrReport, nil)
}
_, isInterface := pt.Elem().Underlying().(*types.Interface)
if !isInterface && !types.Implements(pt.Elem(), errorIface) {
return newDiagnostic(checker.Name(), call, defaultReport, nil)
}
}
return nil
}

View File

@ -1,92 +0,0 @@
package checkers
import (
"go/ast"
"go/token"
"golang.org/x/tools/go/analysis"
"github.com/Antonboom/testifylint/internal/analysisutil"
)
// ErrorNil detects situations like
//
// assert.Nil(t, err)
// assert.NotNil(t, err)
// assert.Equal(t, nil, err)
// assert.EqualValues(t, nil, err)
// assert.Exactly(t, nil, err)
// assert.ErrorIs(t, err, nil)
//
// assert.NotEqual(t, nil, err)
// assert.NotEqualValues(t, nil, err)
// assert.NotErrorIs(t, err, nil)
//
// and requires
//
// assert.NoError(t, err)
// assert.Error(t, err)
type ErrorNil struct{}
// NewErrorNil constructs ErrorNil checker.
func NewErrorNil() ErrorNil { return ErrorNil{} }
func (ErrorNil) Name() string { return "error-nil" }
func (checker ErrorNil) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic {
const (
errorFn = "Error"
noErrorFn = "NoError"
)
proposedFn, survivingArg, replacementEndPos := func() (string, ast.Expr, token.Pos) {
switch call.Fn.NameFTrimmed {
case "Nil":
if len(call.Args) >= 1 && isError(pass, call.Args[0]) {
return noErrorFn, call.Args[0], call.Args[0].End()
}
case "NotNil":
if len(call.Args) >= 1 && isError(pass, call.Args[0]) {
return errorFn, call.Args[0], call.Args[0].End()
}
case "Equal", "EqualValues", "Exactly", "ErrorIs":
if len(call.Args) < 2 {
return "", nil, token.NoPos
}
a, b := call.Args[0], call.Args[1]
switch {
case isError(pass, a) && isNil(b):
return noErrorFn, a, b.End()
case isNil(a) && isError(pass, b):
return noErrorFn, b, b.End()
}
case "NotEqual", "NotEqualValues", "NotErrorIs":
if len(call.Args) < 2 {
return "", nil, token.NoPos
}
a, b := call.Args[0], call.Args[1]
switch {
case isError(pass, a) && isNil(b):
return errorFn, a, b.End()
case isNil(a) && isError(pass, b):
return errorFn, b, b.End()
}
}
return "", nil, token.NoPos
}()
if proposedFn != "" {
return newUseFunctionDiagnostic(checker.Name(), call, proposedFn,
newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{
Pos: call.Args[0].Pos(),
End: replacementEndPos,
NewText: analysisutil.NodeBytes(pass.Fset, survivingArg),
}),
)
}
return nil
}

View File

@ -1,179 +0,0 @@
package checkers
import (
"go/ast"
"go/token"
"regexp"
"golang.org/x/tools/go/analysis"
"github.com/Antonboom/testifylint/internal/analysisutil"
)
// DefaultExpectedVarPattern matches variables with "expected" or "wanted" prefix or suffix in the name.
var DefaultExpectedVarPattern = regexp.MustCompile(
`(^(exp(ected)?|want(ed)?)([A-Z]\w*)?$)|(^(\w*[a-z])?(Exp(ected)?|Want(ed)?)$)`)
// ExpectedActual detects situations like
//
// assert.Equal(t, result, expected)
// assert.EqualExportedValues(t, resultObj, User{Name: "Anton"})
// assert.EqualValues(t, result, 42)
// assert.Exactly(t, result, int64(42))
// assert.JSONEq(t, result, `{"version": 3}`)
// assert.InDelta(t, result, 42.42, 1.0)
// assert.InDeltaMapValues(t, result, map[string]float64{"score": 0.99}, 1.0)
// assert.InDeltaSlice(t, result, []float64{0.98, 0.99}, 1.0)
// assert.InEpsilon(t, result, 42.42, 0.0001)
// assert.InEpsilonSlice(t, result, []float64{0.9801, 0.9902}, 0.0001)
// assert.IsType(t, result, (*User)(nil))
// assert.NotEqual(t, result, "expected")
// assert.NotEqualValues(t, result, "expected")
// assert.NotSame(t, resultPtr, &value)
// assert.Same(t, resultPtr, &value)
// assert.WithinDuration(t, resultTime, time.Date(2023, 01, 12, 11, 46, 33, 0, nil), time.Second)
// assert.YAMLEq(t, result, "version: '3'")
//
// and requires
//
// assert.Equal(t, expected, result)
// assert.EqualExportedValues(t, User{Name: "Anton"}, resultObj)
// assert.EqualValues(t, 42, result)
// ...
type ExpectedActual struct {
expVarPattern *regexp.Regexp
}
// NewExpectedActual constructs ExpectedActual checker using DefaultExpectedVarPattern.
func NewExpectedActual() *ExpectedActual {
return &ExpectedActual{expVarPattern: DefaultExpectedVarPattern}
}
func (ExpectedActual) Name() string { return "expected-actual" }
func (checker *ExpectedActual) SetExpVarPattern(p *regexp.Regexp) *ExpectedActual {
if p != nil {
checker.expVarPattern = p
}
return checker
}
func (checker ExpectedActual) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic {
switch call.Fn.NameFTrimmed {
case "Equal",
"EqualExportedValues",
"EqualValues",
"Exactly",
"InDelta",
"InDeltaMapValues",
"InDeltaSlice",
"InEpsilon",
"InEpsilonSlice",
"IsType",
"JSONEq",
"NotEqual",
"NotEqualValues",
"NotSame",
"Same",
"WithinDuration",
"YAMLEq":
default:
return nil
}
if len(call.Args) < 2 {
return nil
}
first, second := call.Args[0], call.Args[1]
if checker.isWrongExpectedActualOrder(pass, first, second) {
return newDiagnostic(checker.Name(), call, "need to reverse actual and expected values", &analysis.SuggestedFix{
Message: "Reverse actual and expected values",
TextEdits: []analysis.TextEdit{
{
Pos: first.Pos(),
End: second.End(),
NewText: formatAsCallArgs(pass, second, first),
},
},
})
}
return nil
}
func (checker ExpectedActual) isWrongExpectedActualOrder(pass *analysis.Pass, first, second ast.Expr) bool {
leftIsCandidate := checker.isExpectedValueCandidate(pass, first)
rightIsCandidate := checker.isExpectedValueCandidate(pass, second)
return rightIsCandidate && !leftIsCandidate
}
func (checker ExpectedActual) isExpectedValueCandidate(pass *analysis.Pass, expr ast.Expr) bool {
switch v := expr.(type) {
case *ast.ParenExpr:
return checker.isExpectedValueCandidate(pass, v.X)
case *ast.StarExpr: // *value
return checker.isExpectedValueCandidate(pass, v.X)
case *ast.UnaryExpr:
return (v.Op == token.AND) && checker.isExpectedValueCandidate(pass, v.X) // &value
case *ast.CompositeLit:
return true
case *ast.CallExpr:
return isParenExpr(v) ||
isCastedBasicLitOrExpectedValue(v, checker.expVarPattern) ||
isExpectedValueFactory(pass, v, checker.expVarPattern)
}
return isBasicLit(expr) ||
isUntypedConst(pass, expr) ||
isTypedConst(pass, expr) ||
isIdentNamedAfterPattern(checker.expVarPattern, expr) ||
isStructVarNamedAfterPattern(checker.expVarPattern, expr) ||
isStructFieldNamedAfterPattern(checker.expVarPattern, expr)
}
func isParenExpr(ce *ast.CallExpr) bool {
_, ok := ce.Fun.(*ast.ParenExpr)
return ok
}
func isCastedBasicLitOrExpectedValue(ce *ast.CallExpr, pattern *regexp.Regexp) bool {
if len(ce.Args) != 1 {
return false
}
fn, ok := ce.Fun.(*ast.Ident)
if !ok {
return false
}
switch fn.Name {
case "complex64", "complex128":
return true
case "uint", "uint8", "uint16", "uint32", "uint64",
"int", "int8", "int16", "int32", "int64",
"float32", "float64",
"rune", "string":
return isBasicLit(ce.Args[0]) || isIdentNamedAfterPattern(pattern, ce.Args[0])
}
return false
}
func isExpectedValueFactory(pass *analysis.Pass, ce *ast.CallExpr, pattern *regexp.Regexp) bool {
switch fn := ce.Fun.(type) {
case *ast.Ident:
return pattern.MatchString(fn.Name)
case *ast.SelectorExpr:
timeDateFn := analysisutil.ObjectOf(pass.Pkg, "time", "Date")
if timeDateFn != nil && analysisutil.IsObj(pass.TypesInfo, fn.Sel, timeDateFn) {
return true
}
return pattern.MatchString(fn.Sel.Name)
}
return false
}

View File

@ -1,50 +0,0 @@
package checkers
import (
"fmt"
"go/token"
"golang.org/x/tools/go/analysis"
)
// FloatCompare detects situations like
//
// assert.Equal(t, 42.42, result)
// assert.EqualValues(t, 42.42, result)
// assert.Exactly(t, 42.42, result)
// assert.True(t, result == 42.42)
// assert.False(t, result != 42.42)
//
// and requires
//
// assert.InEpsilon(t, 42.42, result, 0.0001) // Or assert.InDelta
type FloatCompare struct{}
// NewFloatCompare constructs FloatCompare checker.
func NewFloatCompare() FloatCompare { return FloatCompare{} }
func (FloatCompare) Name() string { return "float-compare" }
func (checker FloatCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic {
invalid := func() bool {
switch call.Fn.NameFTrimmed {
case "Equal", "EqualValues", "Exactly":
return len(call.Args) > 1 && (isFloat(pass, call.Args[0]) || isFloat(pass, call.Args[1]))
case "True":
return len(call.Args) > 0 && isComparisonWithFloat(pass, call.Args[0], token.EQL)
case "False":
return len(call.Args) > 0 && isComparisonWithFloat(pass, call.Args[0], token.NEQ)
}
return false
}()
if invalid {
format := "use %s.InEpsilon (or InDelta)"
if call.Fn.IsFmt {
format = "use %s.InEpsilonf (or InDeltaf)"
}
return newDiagnostic(checker.Name(), call, fmt.Sprintf(format, call.SelectorXStr), nil)
}
return nil
}

View File

@ -1,187 +0,0 @@
package checkers
import (
"fmt"
"go/ast"
"go/types"
"strconv"
"golang.org/x/tools/go/analysis"
"github.com/Antonboom/testifylint/internal/analysisutil"
"github.com/Antonboom/testifylint/internal/checkers/printf"
"github.com/Antonboom/testifylint/internal/testify"
)
// Formatter detects situations like
//
// assert.ElementsMatch(t, certConfig.Org, csr.Subject.Org, "organizations not equal")
// assert.Error(t, err, fmt.Sprintf("Profile %s should not be valid", test.profile))
// assert.Errorf(t, err, fmt.Sprintf("test %s", test.testName))
// assert.Truef(t, targetTs.Equal(ts), "the timestamp should be as expected (%s) but was %s", targetTs)
// ...
//
// and requires
//
// assert.ElementsMatchf(t, certConfig.Org, csr.Subject.Org, "organizations not equal")
// assert.Errorf(t, err, "Profile %s should not be valid", test.profile)
// assert.Errorf(t, err, "test %s", test.testName)
// assert.Truef(t, targetTs.Equal(ts), "the timestamp should be as expected (%s) but was %s", targetTs, ts)
type Formatter struct {
checkFormatString bool
requireFFuncs bool
}
// NewFormatter constructs Formatter checker.
func NewFormatter() *Formatter {
return &Formatter{
checkFormatString: true,
requireFFuncs: false,
}
}
func (Formatter) Name() string { return "formatter" }
func (checker *Formatter) SetCheckFormatString(v bool) *Formatter {
checker.checkFormatString = v
return checker
}
func (checker *Formatter) SetRequireFFuncs(v bool) *Formatter {
checker.requireFFuncs = v
return checker
}
func (checker Formatter) Check(pass *analysis.Pass, call *CallMeta) (result *analysis.Diagnostic) {
if call.Fn.IsFmt {
return checker.checkFmtAssertion(pass, call)
}
return checker.checkNotFmtAssertion(pass, call)
}
func (checker Formatter) checkNotFmtAssertion(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic {
msgAndArgsPos, ok := isPrintfLikeCall(pass, call, call.Fn.Signature)
if !ok {
return nil
}
fFunc := call.Fn.Name + "f"
if msgAndArgsPos == len(call.ArgsRaw)-1 {
msgAndArgs := call.ArgsRaw[msgAndArgsPos]
if args, ok := isFmtSprintfCall(pass, msgAndArgs); ok {
if checker.requireFFuncs {
msg := fmt.Sprintf("remove unnecessary fmt.Sprintf and use %s.%s", call.SelectorXStr, fFunc)
return newDiagnostic(checker.Name(), call, msg,
newSuggestedFuncReplacement(call, fFunc, analysis.TextEdit{
Pos: msgAndArgs.Pos(),
End: msgAndArgs.End(),
NewText: formatAsCallArgs(pass, args...),
}),
)
}
return newRemoveSprintfDiagnostic(pass, checker.Name(), call, msgAndArgs, args)
}
}
if checker.requireFFuncs {
return newUseFunctionDiagnostic(checker.Name(), call, fFunc, newSuggestedFuncReplacement(call, fFunc))
}
return nil
}
func (checker Formatter) checkFmtAssertion(pass *analysis.Pass, call *CallMeta) (result *analysis.Diagnostic) {
formatPos := getMsgPosition(call.Fn.Signature)
if formatPos < 0 {
return nil
}
msg := call.ArgsRaw[formatPos]
if formatPos == len(call.ArgsRaw)-1 {
if args, ok := isFmtSprintfCall(pass, msg); ok {
return newRemoveSprintfDiagnostic(pass, checker.Name(), call, msg, args)
}
}
if checker.checkFormatString {
report := pass.Report
defer func() { pass.Report = report }()
pass.Report = func(d analysis.Diagnostic) {
result = newDiagnostic(checker.Name(), call, d.Message, nil)
}
format, err := strconv.Unquote(analysisutil.NodeString(pass.Fset, msg))
if err != nil {
return nil
}
printf.CheckPrintf(pass, call.Call, call.String(), format, formatPos)
}
return result
}
func isPrintfLikeCall(pass *analysis.Pass, call *CallMeta, sig *types.Signature) (int, bool) {
msgAndArgsPos := getMsgAndArgsPosition(sig)
if msgAndArgsPos < 0 {
return -1, false
}
fmtFn := analysisutil.ObjectOf(pass.Pkg, testify.AssertPkgPath, call.Fn.Name+"f")
if fmtFn == nil {
// NOTE(a.telyshev): No formatted analogue of assertion.
return -1, false
}
return msgAndArgsPos, len(call.ArgsRaw) > msgAndArgsPos
}
func getMsgAndArgsPosition(sig *types.Signature) int {
params := sig.Params()
if params.Len() < 1 {
return -1
}
lastIdx := params.Len() - 1
lastParam := params.At(lastIdx)
_, isSlice := lastParam.Type().(*types.Slice)
if lastParam.Name() == "msgAndArgs" && isSlice {
return lastIdx
}
return -1
}
func getMsgPosition(sig *types.Signature) int {
for i := 0; i < sig.Params().Len(); i++ {
param := sig.Params().At(i)
if b, ok := param.Type().(*types.Basic); ok && b.Kind() == types.String && param.Name() == "msg" {
return i
}
}
return -1
}
func isFmtSprintfCall(pass *analysis.Pass, expr ast.Expr) ([]ast.Expr, bool) {
ce, ok := expr.(*ast.CallExpr)
if !ok {
return nil, false
}
se, ok := ce.Fun.(*ast.SelectorExpr)
if !ok {
return nil, false
}
sprintfObj := analysisutil.ObjectOf(pass.Pkg, "fmt", "Sprintf")
if sprintfObj == nil {
return nil, false
}
if !analysisutil.IsObj(pass.TypesInfo, se.Sel, sprintfObj) {
return nil, false
}
return ce.Args, true
}

View File

@ -1,345 +0,0 @@
package checkers
import (
"fmt"
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/ast/inspector"
"github.com/Antonboom/testifylint/internal/analysisutil"
)
const (
goRequireFnReportFormat = "%s contains assertions that must only be used in the goroutine running the test function"
goRequireCallReportFormat = "%s must only be used in the goroutine running the test function"
goRequireHTTPHandlerReportFormat = "do not use %s in http handlers"
)
// GoRequire takes idea from go vet's "testinggoroutine" check
// and detects usage of require package's functions or assert.FailNow in the non-test goroutines
//
// go func() {
// conn, err = lis.Accept()
// require.NoError(t, err)
//
// if assert.Error(err) {
// assert.FailNow(t, msg)
// }
// }()
type GoRequire struct {
ignoreHTTPHandlers bool
}
// NewGoRequire constructs GoRequire checker.
func NewGoRequire() *GoRequire { return new(GoRequire) }
func (GoRequire) Name() string { return "go-require" }
func (checker *GoRequire) SetIgnoreHTTPHandlers(v bool) *GoRequire {
checker.ignoreHTTPHandlers = v
return checker
}
// Check should be consistent with
// https://cs.opensource.google/go/x/tools/+/master:go/analysis/passes/testinggoroutine/testinggoroutine.go
//
// But due to the fact that the Check covers cases missed by go vet,
// the implementation turned out to be terribly complicated.
//
// In simple words, the algorithm is as follows:
// - we walk along the call tree and store the status, whether we are in the test goroutine or not;
// - if we are in a test goroutine, then require is allowed, otherwise not;
// - when we encounter the launch of a subtest or `go` statement, the status changes;
// - in order to correctly handle the return to the correct status when exiting the current function,
// we have to store a stack of statuses (inGoroutineRunningTestFunc).
//
// Other test functions called in the test function are also analyzed to make a verdict about the current function.
// This leads to recursion, which the cache of processed functions (processedFuncs) helps reduce the impact of.
// Also, because of this, we have to pre-collect a list of test function declarations (testsDecls).
func (checker GoRequire) Check(pass *analysis.Pass, inspector *inspector.Inspector) (diagnostics []analysis.Diagnostic) {
testsDecls := make(funcDeclarations)
inspector.Preorder([]ast.Node{(*ast.FuncDecl)(nil)}, func(node ast.Node) {
fd := node.(*ast.FuncDecl)
if isTestingFuncOrMethod(pass, fd) {
if tf, ok := pass.TypesInfo.ObjectOf(fd.Name).(*types.Func); ok {
testsDecls[tf] = fd
}
}
})
var inGoroutineRunningTestFunc boolStack
processedFuncs := make(map[*ast.FuncDecl]goRequireVerdict)
nodesFilter := []ast.Node{
(*ast.FuncDecl)(nil),
(*ast.FuncType)(nil),
(*ast.GoStmt)(nil),
(*ast.CallExpr)(nil),
}
inspector.Nodes(nodesFilter, func(node ast.Node, push bool) bool {
if fd, ok := node.(*ast.FuncDecl); ok {
if !isTestingFuncOrMethod(pass, fd) {
return false
}
if push {
inGoroutineRunningTestFunc.Push(true)
} else {
inGoroutineRunningTestFunc.Pop()
}
return true
}
if 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)
} else {
inGoroutineRunningTestFunc.Pop()
}
return true
}
ce := node.(*ast.CallExpr)
if isSubTestRun(pass, ce) {
if push {
// t.Run spawns the new testing goroutine and declines
// possible warnings from previous "simple" goroutine.
inGoroutineRunningTestFunc.Push(true)
} else {
inGoroutineRunningTestFunc.Pop()
}
return true
}
if !push {
return false
}
if inGoroutineRunningTestFunc.Len() == 0 {
// Insufficient info.
return true
}
if inGoroutineRunningTestFunc.Last() {
// We are in testing goroutine and can skip any assertion checks.
return true
}
testifyCall := NewCallMeta(pass, ce)
if testifyCall != nil {
switch checker.checkCall(testifyCall) {
case goRequireVerdictRequire:
d := newDiagnostic(checker.Name(), testifyCall, fmt.Sprintf(goRequireCallReportFormat, "require"), nil)
diagnostics = append(diagnostics, *d)
case goRequireVerdictAssertFailNow:
d := newDiagnostic(checker.Name(), testifyCall, fmt.Sprintf(goRequireCallReportFormat, testifyCall), nil)
diagnostics = append(diagnostics, *d)
case goRequireVerdictNoExit:
}
return false
}
// Case of nested function call.
{
calledFd := testsDecls.Get(pass, ce)
if calledFd == nil {
return true
}
if v := checker.checkFunc(pass, calledFd, testsDecls, processedFuncs); v != goRequireVerdictNoExit {
caller := analysisutil.NodeString(pass.Fset, ce.Fun)
d := newDiagnostic(checker.Name(), ce, fmt.Sprintf(goRequireFnReportFormat, caller), nil)
diagnostics = append(diagnostics, *d)
}
}
return true
})
if !checker.ignoreHTTPHandlers {
diagnostics = append(diagnostics, checker.checkHTTPHandlers(pass, inspector)...)
}
return diagnostics
}
func (checker GoRequire) checkHTTPHandlers(pass *analysis.Pass, insp *inspector.Inspector) (diagnostics []analysis.Diagnostic) {
insp.WithStack([]ast.Node{(*ast.CallExpr)(nil)}, func(node ast.Node, push bool, stack []ast.Node) bool {
if !push {
return false
}
if len(stack) < 3 {
return true
}
fID := findSurroundingFunc(pass, stack)
if fID == nil || !fID.meta.isHTTPHandler {
return true
}
testifyCall := NewCallMeta(pass, node.(*ast.CallExpr))
if testifyCall == nil {
return true
}
switch checker.checkCall(testifyCall) {
case goRequireVerdictRequire:
d := newDiagnostic(checker.Name(), testifyCall, fmt.Sprintf(goRequireHTTPHandlerReportFormat, "require"), nil)
diagnostics = append(diagnostics, *d)
case goRequireVerdictAssertFailNow:
d := newDiagnostic(checker.Name(), testifyCall, fmt.Sprintf(goRequireHTTPHandlerReportFormat, testifyCall), nil)
diagnostics = append(diagnostics, *d)
case goRequireVerdictNoExit:
}
return false
})
return diagnostics
}
func (checker GoRequire) checkFunc(
pass *analysis.Pass,
fd *ast.FuncDecl,
testsDecls funcDeclarations,
processedFuncs map[*ast.FuncDecl]goRequireVerdict,
) (result goRequireVerdict) {
if v, ok := processedFuncs[fd]; ok {
return v
}
ast.Inspect(fd, func(node ast.Node) bool {
if result != goRequireVerdictNoExit {
return false
}
if _, ok := node.(*ast.GoStmt); ok {
return false
}
ce, ok := node.(*ast.CallExpr)
if !ok {
return true
}
testifyCall := NewCallMeta(pass, ce)
if testifyCall != nil {
if v := checker.checkCall(testifyCall); v != goRequireVerdictNoExit {
result, processedFuncs[fd] = v, v
}
return false
}
// Case of nested function call.
{
calledFd := testsDecls.Get(pass, ce)
if calledFd == nil {
return true
}
if calledFd == fd {
// Recursion.
return true
}
if v := checker.checkFunc(pass, calledFd, testsDecls, processedFuncs); v != goRequireVerdictNoExit {
result = v
return false
}
return true
}
})
return result
}
type goRequireVerdict int
const (
goRequireVerdictNoExit goRequireVerdict = iota
goRequireVerdictRequire
goRequireVerdictAssertFailNow
)
func (checker GoRequire) checkCall(call *CallMeta) goRequireVerdict {
if !call.IsAssert {
return goRequireVerdictRequire
}
if call.Fn.NameFTrimmed == "FailNow" {
return goRequireVerdictAssertFailNow
}
return goRequireVerdictNoExit
}
type funcDeclarations map[*types.Func]*ast.FuncDecl
// Get returns the declaration of a called function or method.
// Currently, only static calls within the same package are supported, otherwise returns nil.
func (fd funcDeclarations) Get(pass *analysis.Pass, ce *ast.CallExpr) *ast.FuncDecl {
var obj types.Object
switch fun := ce.Fun.(type) {
case *ast.SelectorExpr:
obj = pass.TypesInfo.ObjectOf(fun.Sel)
case *ast.Ident:
obj = pass.TypesInfo.ObjectOf(fun)
case *ast.IndexExpr:
if id, ok := fun.X.(*ast.Ident); ok {
obj = pass.TypesInfo.ObjectOf(id)
}
case *ast.IndexListExpr:
if id, ok := fun.X.(*ast.Ident); ok {
obj = pass.TypesInfo.ObjectOf(id)
}
}
if tf, ok := obj.(*types.Func); ok {
return fd[tf]
}
return nil
}
type boolStack []bool
func (s boolStack) Len() int {
return len(s)
}
func (s *boolStack) Push(v bool) {
*s = append(*s, v)
}
func (s *boolStack) Pop() bool {
n := len(*s)
if n == 0 {
return false
}
last := (*s)[n-1]
*s = (*s)[:n-1]
return last
}
func (s boolStack) Last() bool {
n := len(s)
if n == 0 {
return false
}
return s[n-1]
}

View File

@ -1,43 +0,0 @@
package checkers
import (
"go/ast"
"golang.org/x/tools/go/analysis"
)
func xor(a, b bool) bool {
return a != b
}
// anyVal returns the first value[i] for which bools[i] is true.
func anyVal[T any](bools []bool, vals ...T) (T, bool) {
if len(bools) != len(vals) {
panic("inconsistent usage of valOr") //nolint:forbidigo // Does not depend on the code being analyzed.
}
for i, b := range bools {
if b {
return vals[i], true
}
}
var _default T
return _default, false
}
func anyCondSatisfaction(pass *analysis.Pass, p predicate, vals ...ast.Expr) bool {
for _, v := range vals {
if p(pass, v) {
return true
}
}
return false
}
// p transforms simple is-function in a predicate.
func p(fn func(e ast.Expr) bool) predicate {
return func(_ *analysis.Pass, e ast.Expr) bool {
return fn(e)
}
}

View File

@ -1,113 +0,0 @@
package checkers
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
)
func isZero(e ast.Expr) bool { return isIntNumber(e, 0) }
func isOne(e ast.Expr) bool { return isIntNumber(e, 1) }
func isAnyZero(e ast.Expr) bool {
return isIntNumber(e, 0) || isTypedSignedIntNumber(e, 0) || isTypedUnsignedIntNumber(e, 0)
}
func isNotAnyZero(e ast.Expr) bool {
return !isAnyZero(e)
}
func isZeroOrSignedZero(e ast.Expr) bool {
return isIntNumber(e, 0) || isTypedSignedIntNumber(e, 0)
}
func isSignedNotZero(pass *analysis.Pass, e ast.Expr) bool {
return !isUnsigned(pass, e) && !isZeroOrSignedZero(e)
}
func isTypedSignedIntNumber(e ast.Expr, v int) bool {
return isTypedIntNumber(e, v, "int", "int8", "int16", "int32", "int64")
}
func isTypedUnsignedIntNumber(e ast.Expr, v int) bool {
return isTypedIntNumber(e, v, "uint", "uint8", "uint16", "uint32", "uint64")
}
func isTypedIntNumber(e ast.Expr, v int, types ...string) bool {
ce, ok := e.(*ast.CallExpr)
if !ok || len(ce.Args) != 1 {
return false
}
fn, ok := ce.Fun.(*ast.Ident)
if !ok {
return false
}
for _, t := range types {
if fn.Name == t {
return isIntNumber(ce.Args[0], v)
}
}
return false
}
func isIntNumber(e ast.Expr, v int) bool {
bl, ok := e.(*ast.BasicLit)
return ok && bl.Kind == token.INT && bl.Value == fmt.Sprintf("%d", v)
}
func isBasicLit(e ast.Expr) bool {
_, ok := e.(*ast.BasicLit)
return ok
}
func isIntBasicLit(e ast.Expr) bool {
bl, ok := e.(*ast.BasicLit)
return ok && bl.Kind == token.INT
}
func isUntypedConst(pass *analysis.Pass, e ast.Expr) bool {
return isUnderlying(pass, e, types.IsUntyped)
}
func isTypedConst(pass *analysis.Pass, e ast.Expr) bool {
tt, ok := pass.TypesInfo.Types[e]
return ok && tt.IsValue() && tt.Value != nil
}
func isFloat(pass *analysis.Pass, e ast.Expr) bool {
return isUnderlying(pass, e, types.IsFloat)
}
func isUnsigned(pass *analysis.Pass, e ast.Expr) bool {
return isUnderlying(pass, e, types.IsUnsigned)
}
func isUnderlying(pass *analysis.Pass, e ast.Expr, flag types.BasicInfo) bool {
t := pass.TypesInfo.TypeOf(e)
if t == nil {
return false
}
bt, ok := t.Underlying().(*types.Basic)
return ok && (bt.Info()&flag > 0)
}
func isPointer(pass *analysis.Pass, e ast.Expr) bool {
_, ok := pass.TypesInfo.TypeOf(e).(*types.Pointer)
return ok
}
// untype returns v from type(v) expression or v itself if there is no type cast.
func untype(e ast.Expr) ast.Expr {
ce, ok := e.(*ast.CallExpr)
if !ok || len(ce.Args) != 1 {
return e
}
return ce.Args[0]
}

View File

@ -1,33 +0,0 @@
package checkers
import (
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
"github.com/Antonboom/testifylint/internal/analysisutil"
)
var (
falseObj = types.Universe.Lookup("false")
trueObj = types.Universe.Lookup("true")
)
func isUntypedTrue(pass *analysis.Pass, e ast.Expr) bool {
return analysisutil.IsObj(pass.TypesInfo, e, trueObj)
}
func isUntypedFalse(pass *analysis.Pass, e ast.Expr) bool {
return analysisutil.IsObj(pass.TypesInfo, e, falseObj)
}
func 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"
}

View File

@ -1,68 +0,0 @@
package checkers
import (
"go/ast"
"go/token"
"golang.org/x/tools/go/analysis"
)
func isComparisonWithFloat(p *analysis.Pass, e ast.Expr, op token.Token) bool {
be, ok := e.(*ast.BinaryExpr)
if !ok {
return false
}
return be.Op == op && (isFloat(p, be.X) || isFloat(p, be.Y))
}
func isComparisonWithTrue(pass *analysis.Pass, e ast.Expr, op token.Token) (ast.Expr, bool) {
return isComparisonWith(pass, e, isUntypedTrue, op)
}
func isComparisonWithFalse(pass *analysis.Pass, e ast.Expr, op token.Token) (ast.Expr, bool) {
return isComparisonWith(pass, e, isUntypedFalse, op)
}
type predicate func(pass *analysis.Pass, e ast.Expr) bool
func isComparisonWith(
pass *analysis.Pass,
e ast.Expr,
predicate predicate,
op token.Token,
) (ast.Expr, bool) {
be, ok := e.(*ast.BinaryExpr)
if !ok {
return nil, false
}
if be.Op != op {
return nil, false
}
t1, t2 := predicate(pass, be.X), predicate(pass, be.Y)
if xor(t1, t2) {
if t1 {
return be.Y, true
}
return be.X, true
}
return nil, false
}
func isStrictComparisonWith(
pass *analysis.Pass,
e ast.Expr,
lhs predicate,
op token.Token,
rhs predicate,
) (ast.Expr, ast.Expr, bool) {
be, ok := e.(*ast.BinaryExpr)
if !ok {
return nil, nil, false
}
if be.Op == op && lhs(pass, be.X) && rhs(pass, be.Y) {
return be.X, be.Y, true
}
return nil, nil, false
}

View File

@ -1,126 +0,0 @@
package checkers
import (
"fmt"
"go/ast"
"go/token"
"golang.org/x/tools/go/analysis"
)
type funcID struct {
pos token.Pos
posStr string
name string
meta funcMeta
}
type funcMeta struct {
isTestCleanup bool
isGoroutine bool
isHTTPHandler bool
}
func (id funcID) String() string {
return fmt.Sprintf("%s at %s", id.name, id.posStr)
}
func findSurroundingFunc(pass *analysis.Pass, stack []ast.Node) *funcID {
for i := len(stack) - 2; i >= 0; i-- {
var fType *ast.FuncType
var fName string
var isTestCleanup bool
var isGoroutine bool
var isHTTPHandler bool
switch fd := stack[i].(type) {
case *ast.FuncDecl:
fType, fName = fd.Type, fd.Name.Name
if isSuiteMethod(pass, fd) {
if ident := fd.Name; ident != nil && isSuiteAfterTestMethod(ident.Name) {
isTestCleanup = true
}
}
if mimicHTTPHandler(pass, fd.Type) {
isHTTPHandler = true
}
case *ast.FuncLit:
fType, fName = fd.Type, "anonymous"
if mimicHTTPHandler(pass, fType) {
isHTTPHandler = true
}
if i >= 2 { //nolint:nestif
if ce, ok := stack[i-1].(*ast.CallExpr); ok {
if se, ok := ce.Fun.(*ast.SelectorExpr); ok {
isTestCleanup = implementsTestingT(pass, se.X) && se.Sel != nil && (se.Sel.Name == "Cleanup")
}
if _, ok := stack[i-2].(*ast.GoStmt); ok {
isGoroutine = true
}
}
}
default:
continue
}
return &funcID{
pos: fType.Pos(),
posStr: pass.Fset.Position(fType.Pos()).String(),
name: fName,
meta: funcMeta{
isTestCleanup: isTestCleanup,
isGoroutine: isGoroutine,
isHTTPHandler: isHTTPHandler,
},
}
}
return nil
}
func findNearestNode[T ast.Node](stack []ast.Node) (v T) {
v, _ = findNearestNodeWithIdx[T](stack)
return
}
func findNearestNodeWithIdx[T ast.Node](stack []ast.Node) (v T, index int) {
for i := len(stack) - 2; i >= 0; i-- {
if n, ok := stack[i].(T); ok {
return n, i
}
}
return
}
func fnContainsAssertions(pass *analysis.Pass, fn *ast.FuncDecl) bool {
if fn.Body == nil {
return false
}
for _, s := range fn.Body.List {
if isAssertionStmt(pass, s) {
return true
}
}
return false
}
func isAssertionStmt(pass *analysis.Pass, stmt ast.Stmt) bool {
expr, ok := stmt.(*ast.ExprStmt)
if !ok {
return false
}
ce, ok := expr.X.(*ast.CallExpr)
if !ok {
return false
}
return NewCallMeta(pass, ce) != nil
}

View File

@ -1,80 +0,0 @@
package checkers
import (
"fmt"
"go/ast"
"golang.org/x/tools/go/analysis"
)
func newUseFunctionDiagnostic(
checker string,
call *CallMeta,
proposedFn string,
fix *analysis.SuggestedFix,
) *analysis.Diagnostic {
f := proposedFn
if call.Fn.IsFmt {
f += "f"
}
msg := fmt.Sprintf("use %s.%s", call.SelectorXStr, f)
return newDiagnostic(checker, call, msg, fix)
}
func newRemoveSprintfDiagnostic(
pass *analysis.Pass,
checker string,
call *CallMeta,
sprintfPos analysis.Range,
sprintfArgs []ast.Expr,
) *analysis.Diagnostic {
return newDiagnostic(checker, call, "remove unnecessary fmt.Sprintf", &analysis.SuggestedFix{
Message: "Remove `fmt.Sprintf`",
TextEdits: []analysis.TextEdit{
{
Pos: sprintfPos.Pos(),
End: sprintfPos.End(),
NewText: formatAsCallArgs(pass, sprintfArgs...),
},
},
})
}
func newDiagnostic(
checker string,
rng analysis.Range,
msg string,
fix *analysis.SuggestedFix,
) *analysis.Diagnostic {
d := analysis.Diagnostic{
Pos: rng.Pos(),
End: rng.End(),
Category: checker,
Message: checker + ": " + msg,
}
if fix != nil {
d.SuggestedFixes = []analysis.SuggestedFix{*fix}
}
return &d
}
func newSuggestedFuncReplacement(
call *CallMeta,
proposedFn string,
additionalEdits ...analysis.TextEdit,
) *analysis.SuggestedFix {
if call.Fn.IsFmt {
proposedFn += "f"
}
return &analysis.SuggestedFix{
Message: fmt.Sprintf("Replace `%s` with `%s`", call.Fn.Name, proposedFn),
TextEdits: append([]analysis.TextEdit{
{
Pos: call.Fn.Pos(),
End: call.Fn.End(),
NewText: []byte(proposedFn),
},
}, additionalEdits...),
}
}

View File

@ -1,42 +0,0 @@
package checkers
import (
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
"github.com/Antonboom/testifylint/internal/analysisutil"
)
var (
errorObj = types.Universe.Lookup("error")
errorType = errorObj.Type()
errorIface = errorType.Underlying().(*types.Interface)
)
func isError(pass *analysis.Pass, expr ast.Expr) bool {
return pass.TypesInfo.TypeOf(expr) == errorType
}
func isErrorsIsCall(pass *analysis.Pass, ce *ast.CallExpr) bool {
return isErrorsPkgFnCall(pass, ce, "Is")
}
func isErrorsAsCall(pass *analysis.Pass, ce *ast.CallExpr) bool {
return isErrorsPkgFnCall(pass, ce, "As")
}
func isErrorsPkgFnCall(pass *analysis.Pass, ce *ast.CallExpr, fn string) bool {
se, ok := ce.Fun.(*ast.SelectorExpr)
if !ok {
return false
}
errorsIsObj := analysisutil.ObjectOf(pass.Pkg, "errors", fn)
if errorsIsObj == nil {
return false
}
return analysisutil.IsObj(pass.TypesInfo, se.Sel, errorsIsObj)
}

View File

@ -1,26 +0,0 @@
package checkers
import (
"bytes"
"go/ast"
"golang.org/x/tools/go/analysis"
"github.com/Antonboom/testifylint/internal/analysisutil"
)
// formatAsCallArgs joins a, b and c and returns bytes like `a, b, c`.
func formatAsCallArgs(pass *analysis.Pass, args ...ast.Expr) []byte {
if len(args) == 0 {
return []byte("")
}
var buf bytes.Buffer
for i, arg := range args {
buf.Write(analysisutil.NodeBytes(pass.Fset, arg))
if i != len(args)-1 {
buf.WriteString(", ")
}
}
return buf.Bytes()
}

View File

@ -1,35 +0,0 @@
package checkers
import (
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
"github.com/Antonboom/testifylint/internal/analysisutil"
)
func mimicHTTPHandler(pass *analysis.Pass, fType *ast.FuncType) bool {
httpHandlerFuncObj := analysisutil.ObjectOf(pass.Pkg, "net/http", "HandlerFunc")
if httpHandlerFuncObj == nil {
return false
}
sig, ok := httpHandlerFuncObj.Type().Underlying().(*types.Signature)
if !ok {
return false
}
if len(fType.Params.List) != sig.Params().Len() {
return false
}
for i := 0; i < sig.Params().Len(); i++ {
lhs := sig.Params().At(i).Type()
rhs := pass.TypesInfo.TypeOf(fType.Params.List[i].Type)
if !types.Identical(lhs, rhs) {
return false
}
}
return true
}

View File

@ -1,48 +0,0 @@
package checkers
import (
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
"github.com/Antonboom/testifylint/internal/analysisutil"
"github.com/Antonboom/testifylint/internal/testify"
)
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 implementsTestifySuite(pass *analysis.Pass, e ast.Expr) bool {
suiteIfaceObj := analysisutil.ObjectOf(pass.Pkg, testify.SuitePkgPath, "TestingSuite")
return (suiteIfaceObj != nil) && implements(pass, e, suiteIfaceObj)
}
func implementsTestingT(pass *analysis.Pass, e ast.Expr) bool {
return implementsAssertTestingT(pass, e) || implementsRequireTestingT(pass, e)
}
func implementsAssertTestingT(pass *analysis.Pass, e ast.Expr) bool {
assertTestingTObj := analysisutil.ObjectOf(pass.Pkg, testify.AssertPkgPath, "TestingT")
return (assertTestingTObj != nil) && implements(pass, e, assertTestingTObj)
}
func implementsRequireTestingT(pass *analysis.Pass, e ast.Expr) bool {
requireTestingTObj := analysisutil.ObjectOf(pass.Pkg, testify.RequirePkgPath, "TestingT")
return (requireTestingTObj != nil) && implements(pass, e, requireTestingTObj)
}
func implements(pass *analysis.Pass, e ast.Expr, ifaceObj types.Object) bool {
t := pass.TypesInfo.TypeOf(e)
if t == nil {
return false
}
return types.Implements(t, ifaceObj.Type().Underlying().(*types.Interface))
}

View File

@ -1,55 +0,0 @@
package checkers
import (
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"github.com/Antonboom/testifylint/internal/analysisutil"
)
var lenObj = types.Universe.Lookup("len")
func isLenEquality(pass *analysis.Pass, e ast.Expr) (ast.Expr, ast.Expr, bool) {
be, ok := e.(*ast.BinaryExpr)
if !ok {
return nil, nil, false
}
if be.Op != token.EQL {
return nil, nil, false
}
return xorLenCall(pass, be.X, be.Y)
}
func xorLenCall(pass *analysis.Pass, a, b ast.Expr) (lenArg ast.Expr, expectedLen ast.Expr, ok bool) {
arg1, ok1 := isBuiltinLenCall(pass, a)
arg2, ok2 := isBuiltinLenCall(pass, b)
if xor(ok1, ok2) {
if ok1 {
return arg1, b, true
}
return arg2, a, true
}
return nil, nil, false
}
func isLenCallAndZero(pass *analysis.Pass, a, b ast.Expr) (ast.Expr, bool) {
lenArg, ok := isBuiltinLenCall(pass, a)
return lenArg, ok && isZero(b)
}
func isBuiltinLenCall(pass *analysis.Pass, e ast.Expr) (ast.Expr, bool) {
ce, ok := e.(*ast.CallExpr)
if !ok {
return nil, false
}
if analysisutil.IsObj(pass.TypesInfo, ce.Fun, lenObj) && len(ce.Args) == 1 {
return ce.Args[0], true
}
return nil, false
}

Some files were not shown because too many files have changed in this diff Show More