diff options
Diffstat (limited to 'vendor/github.com')
280 files changed, 10182 insertions, 4393 deletions
diff --git a/vendor/github.com/Antonboom/testifylint/analyzer/analyzer.go b/vendor/github.com/Antonboom/testifylint/analyzer/analyzer.go index c0b98f83c..84d7e815d 100644 --- a/vendor/github.com/Antonboom/testifylint/analyzer/analyzer.go +++ b/vendor/github.com/Antonboom/testifylint/analyzer/analyzer.go @@ -3,8 +3,6 @@ package analyzer import ( "fmt" "go/ast" - "go/types" - "strings" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/ast/inspector" @@ -79,58 +77,11 @@ func (tl *testifyLint) run(pass *analysis.Pass) (any, error) { } func (tl *testifyLint) regularCheck(pass *analysis.Pass, ce *ast.CallExpr) { - se, ok := ce.Fun.(*ast.SelectorExpr) - if !ok || se.Sel == nil { - return - } - fnName := se.Sel.Name - - initiatorPkg, isPkgCall := func() (*types.Package, bool) { - // Examples: - // s.Assert -> method of *suite.Suite -> package suite ("vendor/github.com/stretchr/testify/suite") - // s.Assert().Equal -> method of *assert.Assertions -> package assert ("vendor/github.com/stretchr/testify/assert") - // s.Equal -> method of *assert.Assertions -> package assert ("vendor/github.com/stretchr/testify/assert") - // reqObj.Falsef -> method of *require.Assertions -> package require ("vendor/github.com/stretchr/testify/require") - if sel, ok := pass.TypesInfo.Selections[se]; ok { - return sel.Obj().Pkg(), false - } - - // Examples: - // assert.False -> assert -> package assert ("vendor/github.com/stretchr/testify/assert") - // require.NotEqualf -> require -> package require ("vendor/github.com/stretchr/testify/require") - if id, ok := se.X.(*ast.Ident); ok { - if selObj := pass.TypesInfo.ObjectOf(id); selObj != nil { - if pkg, ok := selObj.(*types.PkgName); ok { - return pkg.Imported(), true - } - } - } - return nil, false - }() - if initiatorPkg == nil { + call := checkers.NewCallMeta(pass, ce) + if nil == call { return } - isAssert := analysisutil.IsPkg(initiatorPkg, testify.AssertPkgName, testify.AssertPkgPath) - isRequire := analysisutil.IsPkg(initiatorPkg, testify.RequirePkgName, testify.RequirePkgPath) - if !(isAssert || isRequire) { - return - } - - call := &checkers.CallMeta{ - Range: ce, - IsPkg: isPkgCall, - IsAssert: isAssert, - Selector: se, - SelectorXStr: analysisutil.NodeString(pass.Fset, se.X), - Fn: checkers.FnMeta{ - Range: se.Sel, - Name: fnName, - IsFmt: strings.HasSuffix(fnName, "f"), - }, - Args: trimTArg(pass, isAssert, ce.Args), - ArgsRaw: ce.Args, - } for _, ch := range tl.regularCheckers { if d := ch.Check(pass, call); d != nil { pass.Report(*d) @@ -140,36 +91,3 @@ func (tl *testifyLint) regularCheck(pass *analysis.Pass, ce *ast.CallExpr) { } } } - -func trimTArg(pass *analysis.Pass, isAssert bool, args []ast.Expr) []ast.Expr { - if len(args) == 0 { - return args - } - - if isTestingTPtr(pass, isAssert, args[0]) { - return args[1:] - } - return args -} - -func isTestingTPtr(pass *analysis.Pass, isAssert bool, arg ast.Expr) bool { - pkgPath := testify.RequirePkgPath - if isAssert { - pkgPath = testify.AssertPkgPath - } - - testingInterfaceObj := analysisutil.ObjectOf(pass.Pkg, pkgPath, "TestingT") - if testingInterfaceObj == nil { - return false - } - - argType := pass.TypesInfo.TypeOf(arg) - if argType == nil { - return false - } - - return types.Implements( - argType, - testingInterfaceObj.Type().Underlying().(*types.Interface), - ) -} diff --git a/vendor/github.com/Antonboom/testifylint/analyzer/checkers_factory.go b/vendor/github.com/Antonboom/testifylint/analyzer/checkers_factory.go index e87e35e50..de1d6017f 100644 --- a/vendor/github.com/Antonboom/testifylint/analyzer/checkers_factory.go +++ b/vendor/github.com/Antonboom/testifylint/analyzer/checkers_factory.go @@ -9,14 +9,34 @@ import ( // newCheckers accepts linter config and returns slices of enabled checkers sorted by priority. func newCheckers(cfg config.Config) ([]checkers.RegularChecker, []checkers.AdvancedChecker, error) { - enabledCheckers := cfg.EnabledCheckers - if len(enabledCheckers) == 0 { - enabledCheckers = checkers.EnabledByDefault() + if err := cfg.Validate(); err != nil { + return nil, nil, err } + + enabledCheckersSet := make(map[string]struct{}) + if cfg.EnableAll { - enabledCheckers = checkers.All() + for _, checker := range checkers.All() { + enabledCheckersSet[checker] = struct{}{} + } + } else if !cfg.DisableAll { + for _, checker := range checkers.EnabledByDefault() { + enabledCheckersSet[checker] = struct{}{} + } + } + + for _, checker := range cfg.EnabledCheckers { + enabledCheckersSet[checker] = struct{}{} + } + + for _, checker := range cfg.DisabledCheckers { + delete(enabledCheckersSet, checker) } + enabledCheckers := make([]string, 0, len(enabledCheckersSet)) + for v := range enabledCheckersSet { + enabledCheckers = append(enabledCheckers, v) + } checkers.SortByPriority(enabledCheckers) regularCheckers := make([]checkers.RegularChecker, 0, len(enabledCheckers)) @@ -32,6 +52,9 @@ func newCheckers(cfg config.Config) ([]checkers.RegularChecker, []checkers.Advan case *checkers.ExpectedActual: c.SetExpVarPattern(cfg.ExpectedActual.ExpVarPattern.Regexp) + case *checkers.RequireError: + c.SetFnPattern(cfg.RequireError.FnPattern.Regexp) + case *checkers.SuiteExtraAssertCall: c.SetMode(cfg.SuiteExtraAssertCall.Mode) } diff --git a/vendor/github.com/Antonboom/testifylint/internal/analysisutil/object.go b/vendor/github.com/Antonboom/testifylint/internal/analysisutil/object.go index e01fba5c1..4e0346d2b 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/analysisutil/object.go +++ b/vendor/github.com/Antonboom/testifylint/internal/analysisutil/object.go @@ -30,5 +30,5 @@ func IsObj(typesInfo *types.Info, expr ast.Expr, expected types.Object) bool { } obj := typesInfo.ObjectOf(id) - return obj.Id() == expected.Id() + return obj == expected } diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/blank_import.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/blank_import.go new file mode 100644 index 000000000..403691e27 --- /dev/null +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/blank_import.go @@ -0,0 +1,69 @@ +package checkers + +import ( + "fmt" + "strconv" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/ast/inspector" + + "github.com/Antonboom/testifylint/internal/testify" +) + +// BlankImport detects useless blank imports of testify's packages. +// These imports are useless since testify doesn't do any magic with init() function. +// +// The checker detects situations like +// +// import ( +// "testing" +// +// _ "github.com/stretchr/testify" +// _ "github.com/stretchr/testify/assert" +// _ "github.com/stretchr/testify/http" +// _ "github.com/stretchr/testify/mock" +// _ "github.com/stretchr/testify/require" +// _ "github.com/stretchr/testify/suite" +// ) +// +// and requires +// +// import ( +// "testing" +// ) +type BlankImport struct{} + +// NewBlankImport constructs BlankImport checker. +func NewBlankImport() BlankImport { return BlankImport{} } +func (BlankImport) Name() string { return "blank-import" } + +func (checker BlankImport) Check(pass *analysis.Pass, _ *inspector.Inspector) (diagnostics []analysis.Diagnostic) { + for _, file := range pass.Files { + for _, imp := range file.Imports { + if imp.Name == nil || imp.Name.Name != "_" { + continue + } + + pkg, err := strconv.Unquote(imp.Path.Value) + if err != nil { + continue + } + if _, ok := packagesNotIntendedForBlankImport[pkg]; !ok { + continue + } + + msg := fmt.Sprintf("avoid blank import of %s as it does nothing", pkg) + diagnostics = append(diagnostics, *newDiagnostic(checker.Name(), imp, msg, nil)) + } + } + return diagnostics +} + +var packagesNotIntendedForBlankImport = map[string]struct{}{ + testify.ModulePath: {}, + testify.AssertPkgPath: {}, + testify.HTTPPkgPath: {}, + testify.MockPkgPath: {}, + testify.RequirePkgPath: {}, + testify.SuitePkgPath: {}, +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/bool_compare.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/bool_compare.go index 8245ab58e..c8db9420e 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/bool_compare.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/bool_compare.go @@ -13,7 +13,10 @@ import ( // BoolCompare detects situations like // // assert.Equal(t, false, result) -// assert.NotEqual(t, result, true) +// assert.EqualValues(t, false, result) +// assert.Exactly(t, false, result) +// assert.NotEqual(t, true, result) +// assert.NotEqualValues(t, true, result) // assert.False(t, !result) // assert.True(t, result == true) // ... @@ -60,13 +63,17 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. ) } - switch call.Fn.Name { - case "Equal", "Equalf": + switch call.Fn.NameFTrimmed { + case "Equal", "EqualValues", "Exactly": if len(call.Args) < 2 { return nil } arg1, arg2 := call.Args[0], call.Args[1] + if isEmptyInterface(pass, arg1) || isEmptyInterface(pass, arg2) { + return nil + } + t1, t2 := isUntypedTrue(pass, arg1), isUntypedTrue(pass, arg2) f1, f2 := isUntypedFalse(pass, arg1), isUntypedFalse(pass, arg2) @@ -80,12 +87,16 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. return newUseFalseDiagnostic(survivingArg, arg1.Pos(), arg2.End()) } - case "NotEqual", "NotEqualf": + case "NotEqual", "NotEqualValues": if len(call.Args) < 2 { return nil } arg1, arg2 := call.Args[0], call.Args[1] + if isEmptyInterface(pass, arg1) || isEmptyInterface(pass, arg2) { + return nil + } + t1, t2 := isUntypedTrue(pass, arg1), isUntypedTrue(pass, arg2) f1, f2 := isUntypedFalse(pass, arg1), isUntypedFalse(pass, arg2) @@ -99,7 +110,7 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. return newUseTrueDiagnostic(survivingArg, arg1.Pos(), arg2.End()) } - case "True", "Truef": + case "True": if len(call.Args) < 1 { return nil } @@ -109,7 +120,8 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. arg1, ok1 := isComparisonWithTrue(pass, expr, token.EQL) arg2, ok2 := isComparisonWithFalse(pass, expr, token.NEQ) - if survivingArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2); ok { + survivingArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2) + if ok && !isEmptyInterface(pass, survivingArg) { return newNeedSimplifyDiagnostic(survivingArg, expr.Pos(), expr.End()) } } @@ -119,12 +131,13 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. arg2, ok2 := isComparisonWithFalse(pass, expr, token.EQL) arg3, ok3 := isNegation(expr) - if survivingArg, ok := anyVal([]bool{ok1, ok2, ok3}, arg1, arg2, arg3); ok { + survivingArg, ok := anyVal([]bool{ok1, ok2, ok3}, arg1, arg2, arg3) + if ok && !isEmptyInterface(pass, survivingArg) { return newUseFalseDiagnostic(survivingArg, expr.Pos(), expr.End()) } } - case "False", "Falsef": + case "False": if len(call.Args) < 1 { return nil } @@ -134,7 +147,8 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. arg1, ok1 := isComparisonWithTrue(pass, expr, token.EQL) arg2, ok2 := isComparisonWithFalse(pass, expr, token.NEQ) - if survivingArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2); ok { + survivingArg, ok := anyVal([]bool{ok1, ok2}, arg1, arg2) + if ok && !isEmptyInterface(pass, survivingArg) { return newNeedSimplifyDiagnostic(survivingArg, expr.Pos(), expr.End()) } } @@ -144,7 +158,8 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis. arg2, ok2 := isComparisonWithFalse(pass, expr, token.EQL) arg3, ok3 := isNegation(expr) - if survivingArg, ok := anyVal([]bool{ok1, ok2, ok3}, arg1, arg2, arg3); ok { + survivingArg, ok := anyVal([]bool{ok1, ok2, ok3}, arg1, arg2, arg3) + if ok && !isEmptyInterface(pass, survivingArg) { return newUseTrueDiagnostic(survivingArg, expr.Pos(), expr.End()) } } @@ -221,3 +236,13 @@ func anyVal[T any](bools []bool, vals ...T) (T, bool) { var _default T return _default, false } + +func isEmptyInterface(pass *analysis.Pass, expr ast.Expr) bool { + t, ok := pass.TypesInfo.Types[expr] + if !ok { + return false + } + + iface, ok := t.Type.Underlying().(*types.Interface) + return ok && iface.NumMethods() == 0 +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/call_meta.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/call_meta.go new file mode 100644 index 000000000..44eed49a6 --- /dev/null +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/call_meta.go @@ -0,0 +1,136 @@ +package checkers + +import ( + "go/ast" + "go/types" + "strings" + + "golang.org/x/tools/go/analysis" + + "github.com/Antonboom/testifylint/internal/analysisutil" + "github.com/Antonboom/testifylint/internal/testify" +) + +// CallMeta stores meta info about assertion function/method call, for example +// +// assert.Equal(t, 42, result, "helpful comment") +type CallMeta struct { + // Range contains start and end position of assertion call. + analysis.Range + // IsPkg true if this is package (not object) call. + IsPkg bool + // IsAssert true if this is "testify/assert" package (or object) call. + IsAssert bool + // Selector is the AST expression of "assert.Equal". + Selector *ast.SelectorExpr + // SelectorXStr is a string representation of Selector's left part – value before point, e.g. "assert". + SelectorXStr string + // Fn stores meta info about assertion function itself. + Fn FnMeta + // Args stores assertion call arguments but without `t *testing.T` argument. + // E.g [42, result, "helpful comment"]. + Args []ast.Expr + // ArgsRaw stores assertion call initial arguments. + // E.g [t, 42, result, "helpful comment"]. + ArgsRaw []ast.Expr +} + +func (c CallMeta) String() string { + return c.SelectorXStr + "." + c.Fn.Name +} + +// FnMeta stores meta info about assertion function itself, for example "Equal". +type FnMeta struct { + // Range contains start and end position of function Name. + analysis.Range + // Name is a function name. + Name string + // NameFTrimmed is a function name without "f" suffix. + NameFTrimmed string + // IsFmt is true if function is formatted, e.g. "Equalf". + IsFmt bool +} + +// NewCallMeta returns meta information about testify assertion call. +// Returns nil if ast.CallExpr is not testify call. +func NewCallMeta(pass *analysis.Pass, ce *ast.CallExpr) *CallMeta { + se, ok := ce.Fun.(*ast.SelectorExpr) + if !ok || se.Sel == nil { + return nil + } + fnName := se.Sel.Name + + initiatorPkg, isPkgCall := func() (*types.Package, bool) { + // Examples: + // s.Assert -> method of *suite.Suite -> package suite ("vendor/github.com/stretchr/testify/suite") + // s.Assert().Equal -> method of *assert.Assertions -> package assert ("vendor/github.com/stretchr/testify/assert") + // s.Equal -> method of *assert.Assertions -> package assert ("vendor/github.com/stretchr/testify/assert") + // reqObj.Falsef -> method of *require.Assertions -> package require ("vendor/github.com/stretchr/testify/require") + if sel, ok := pass.TypesInfo.Selections[se]; ok { + return sel.Obj().Pkg(), false + } + + // Examples: + // assert.False -> assert -> package assert ("vendor/github.com/stretchr/testify/assert") + // require.NotEqualf -> require -> package require ("vendor/github.com/stretchr/testify/require") + if id, ok := se.X.(*ast.Ident); ok { + if selObj := pass.TypesInfo.ObjectOf(id); selObj != nil { + if pkg, ok := selObj.(*types.PkgName); ok { + return pkg.Imported(), true + } + } + } + return nil, false + }() + if initiatorPkg == nil { + return nil + } + + isAssert := analysisutil.IsPkg(initiatorPkg, testify.AssertPkgName, testify.AssertPkgPath) + isRequire := analysisutil.IsPkg(initiatorPkg, testify.RequirePkgName, testify.RequirePkgPath) + if !(isAssert || isRequire) { + return nil + } + + return &CallMeta{ + Range: ce, + IsPkg: isPkgCall, + IsAssert: isAssert, + Selector: se, + SelectorXStr: analysisutil.NodeString(pass.Fset, se.X), + Fn: FnMeta{ + Range: se.Sel, + Name: fnName, + NameFTrimmed: strings.TrimSuffix(fnName, "f"), + IsFmt: strings.HasSuffix(fnName, "f"), + }, + Args: trimTArg(pass, ce.Args), + ArgsRaw: ce.Args, + } +} + +func trimTArg(pass *analysis.Pass, args []ast.Expr) []ast.Expr { + if len(args) == 0 { + return args + } + + if isTestingTPtr(pass, args[0]) { + return args[1:] + } + return args +} + +func isTestingTPtr(pass *analysis.Pass, arg ast.Expr) bool { + assertTestingTObj := analysisutil.ObjectOf(pass.Pkg, testify.AssertPkgPath, "TestingT") + requireTestingTObj := analysisutil.ObjectOf(pass.Pkg, testify.RequirePkgPath, "TestingT") + + argType := pass.TypesInfo.TypeOf(arg) + if argType == nil { + return false + } + + return ((assertTestingTObj != nil) && + types.Implements(argType, assertTestingTObj.Type().Underlying().(*types.Interface))) || + ((requireTestingTObj != nil) && + types.Implements(argType, requireTestingTObj.Type().Underlying().(*types.Interface))) +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/checker.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/checker.go index f3249dc3c..ac23af6f6 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/checker.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/checker.go @@ -1,46 +1,10 @@ package checkers import ( - "go/ast" - "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/ast/inspector" ) -// CallMeta stores meta info about assertion function/method call, for example -// -// assert.Equal(t, 42, result, "helpful comment") -type CallMeta struct { - // Range contains start and end position of assertion call. - analysis.Range - // IsPkg true if this is package (not object) call. - IsPkg bool - // IsAssert true if this is "testify/assert" package (or object) call. - IsAssert bool - // Selector is the AST expression of "assert.Equal". - Selector *ast.SelectorExpr - // SelectorXStr is a string representation of Selector's left part – value before point, e.g. "assert". - SelectorXStr string - // Fn stores meta info about assertion function itself. - Fn FnMeta - // Args stores assertion call arguments but without `t *testing.T` argument. - // E.g [42, result, "helpful comment"]. - Args []ast.Expr - // ArgsRaw stores assertion call initial arguments. - // E.g [t, 42, result, "helpful comment"]. - ArgsRaw []ast.Expr -} - -// FnMeta stores meta info about assertion function itself, for example "Equal". -type FnMeta struct { - // Range contains start and end position of function Name. - analysis.Range - // Name is a function name. - Name string - // IsFmt is true if function is formatted, e.g. "Equalf". - IsFmt bool -} - // Checker describes named checker. type Checker interface { Name() string diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/checkers_registry.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/checkers_registry.go index 47eaafb76..e34a21bf9 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/checkers_registry.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/checkers_registry.go @@ -13,12 +13,16 @@ var registry = checkersRegistry{ {factory: asCheckerFactory(NewLen), enabledByDefault: true}, {factory: asCheckerFactory(NewCompares), enabledByDefault: true}, {factory: asCheckerFactory(NewErrorNil), enabledByDefault: true}, + {factory: asCheckerFactory(NewNilCompare), enabledByDefault: true}, {factory: asCheckerFactory(NewErrorIsAs), enabledByDefault: true}, - {factory: asCheckerFactory(NewRequireError), enabledByDefault: true}, {factory: asCheckerFactory(NewExpectedActual), enabledByDefault: true}, {factory: asCheckerFactory(NewSuiteExtraAssertCall), enabledByDefault: true}, {factory: asCheckerFactory(NewSuiteDontUsePkg), enabledByDefault: true}, + {factory: asCheckerFactory(NewUselessAssert), enabledByDefault: true}, // Advanced checkers. + {factory: asCheckerFactory(NewBlankImport), enabledByDefault: true}, + {factory: asCheckerFactory(NewGoRequire), enabledByDefault: true}, + {factory: asCheckerFactory(NewRequireError), enabledByDefault: true}, {factory: asCheckerFactory(NewSuiteTHelper), enabledByDefault: false}, } diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/compares.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/compares.go index afc829f97..336a34512 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/compares.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/compares.go @@ -18,6 +18,7 @@ import ( // assert.True(t, a >= b) // assert.True(t, a < b) // assert.True(t, a <= b) +// assert.False(t, a == b) // ... // // and requires @@ -46,10 +47,10 @@ func (checker Compares) Check(pass *analysis.Pass, call *CallMeta) *analysis.Dia var tokenToProposedFn map[token.Token]string - switch call.Fn.Name { - case "True", "Truef": + switch call.Fn.NameFTrimmed { + case "True": tokenToProposedFn = tokenToProposedFnInsteadOfTrue - case "False", "Falsef": + case "False": tokenToProposedFn = tokenToProposedFnInsteadOfFalse default: return nil diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/empty.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/empty.go index 79c64205f..5ad371bb4 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/empty.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/empty.go @@ -15,9 +15,19 @@ import ( // // assert.Len(t, arr, 0) // assert.Equal(t, 0, len(arr)) +// assert.EqualValues(t, 0, len(arr)) +// assert.Exactly(t, 0, len(arr)) +// assert.LessOrEqual(t, len(arr), 0) +// assert.GreaterOrEqual(t, 0, len(arr)) +// assert.Less(t, len(arr), 0) +// assert.Greater(t, 0, len(arr)) +// assert.Less(t, len(arr), 1) +// assert.Greater(t, 1, len(arr)) +// // assert.NotEqual(t, 0, len(arr)) -// assert.GreaterOrEqual(t, len(arr), 1) -// ... +// assert.NotEqualValues(t, 0, len(arr)) +// assert.Less(t, 0, len(arr)) +// assert.Greater(t, len(arr), 0) // // and requires // @@ -53,13 +63,13 @@ func (checker Empty) checkEmpty(pass *analysis.Pass, call *CallMeta) *analysis.D } a, b := call.Args[0], call.Args[1] - switch call.Fn.Name { - case "Len", "Lenf": + switch call.Fn.NameFTrimmed { + case "Len": if isZero(b) { return newUseEmptyDiagnostic(a.Pos(), b.End(), a) } - case "Equal", "Equalf": + case "Equal", "EqualValues", "Exactly": arg1, ok1 := isLenCallAndZero(pass, a, b) arg2, ok2 := isLenCallAndZero(pass, b, a) @@ -67,23 +77,23 @@ func (checker Empty) checkEmpty(pass *analysis.Pass, call *CallMeta) *analysis.D return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg) } - case "LessOrEqual", "LessOrEqualf": + case "LessOrEqual": if lenArg, ok := isBuiltinLenCall(pass, a); ok && isZero(b) { return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg) } - case "GreaterOrEqual", "GreaterOrEqualf": + case "GreaterOrEqual": if lenArg, ok := isBuiltinLenCall(pass, b); ok && isZero(a) { return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg) } - case "Less", "Lessf": - if lenArg, ok := isBuiltinLenCall(pass, a); ok && isOne(b) { + case "Less": + if lenArg, ok := isBuiltinLenCall(pass, a); ok && (isOne(b) || isZero(b)) { return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg) } - case "Greater", "Greaterf": - if lenArg, ok := isBuiltinLenCall(pass, b); ok && isOne(a) { + case "Greater": + if lenArg, ok := isBuiltinLenCall(pass, b); ok && (isOne(a) || isZero(a)) { return newUseEmptyDiagnostic(a.Pos(), b.End(), lenArg) } } @@ -107,8 +117,8 @@ func (checker Empty) checkNotEmpty(pass *analysis.Pass, call *CallMeta) *analysi } a, b := call.Args[0], call.Args[1] - switch call.Fn.Name { - case "NotEqual", "NotEqualf": + switch call.Fn.NameFTrimmed { + case "NotEqual", "NotEqualValues": arg1, ok1 := isLenCallAndZero(pass, a, b) arg2, ok2 := isLenCallAndZero(pass, b, a) @@ -116,23 +126,13 @@ func (checker Empty) checkNotEmpty(pass *analysis.Pass, call *CallMeta) *analysi return newUseNotEmptyDiagnostic(a.Pos(), b.End(), lenArg) } - case "Greater", "Greaterf": - if lenArg, ok := isBuiltinLenCall(pass, a); ok && isZero(b) || isOne(b) { - return newUseNotEmptyDiagnostic(a.Pos(), b.End(), lenArg) - } - - case "Less", "Lessf": - if lenArg, ok := isBuiltinLenCall(pass, b); ok && isZero(a) || isOne(a) { - return newUseNotEmptyDiagnostic(a.Pos(), b.End(), lenArg) - } - - case "GreaterOrEqual", "GreaterOrEqualf": - if lenArg, ok := isBuiltinLenCall(pass, a); ok && isOne(b) { + case "Less": + if lenArg, ok := isBuiltinLenCall(pass, b); ok && isZero(a) { return newUseNotEmptyDiagnostic(a.Pos(), b.End(), lenArg) } - case "LessOrEqual", "LessOrEqualf": - if lenArg, ok := isBuiltinLenCall(pass, b); ok && isOne(a) { + case "Greater": + if lenArg, ok := isBuiltinLenCall(pass, a); ok && isZero(b) { return newUseNotEmptyDiagnostic(a.Pos(), b.End(), lenArg) } } diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/error_is_as.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/error_is_as.go index e6abd0ba4..0363873a6 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/error_is_as.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/error_is_as.go @@ -3,6 +3,7 @@ package checkers import ( "fmt" "go/ast" + "go/types" "golang.org/x/tools/go/analysis" @@ -22,6 +23,8 @@ import ( // assert.ErrorIs(t, err, errSentinel) // assert.NotErrorIs(t, err, errSentinel) // assert.ErrorAs(t, err, &target) +// +// Also ErrorIsAs repeats go vet's "errorsas" check logic. type ErrorIsAs struct{} // NewErrorIsAs constructs ErrorIsAs checker. @@ -29,22 +32,22 @@ func NewErrorIsAs() ErrorIsAs { return ErrorIsAs{} } func (ErrorIsAs) Name() string { return "error-is-as" } func (checker ErrorIsAs) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { - switch call.Fn.Name { - case "Error", "Errorf": + switch call.Fn.NameFTrimmed { + case "Error": if len(call.Args) >= 2 && isError(pass, call.Args[1]) { const proposed = "ErrorIs" msg := fmt.Sprintf("invalid usage of %[1]s.Error, use %[1]s.%[2]s instead", call.SelectorXStr, proposed) return newDiagnostic(checker.Name(), call, msg, newSuggestedFuncReplacement(call, proposed)) } - case "NoError", "NoErrorf": + case "NoError": if len(call.Args) >= 2 && isError(pass, call.Args[1]) { const proposed = "NotErrorIs" msg := fmt.Sprintf("invalid usage of %[1]s.NoError, use %[1]s.%[2]s instead", call.SelectorXStr, proposed) return newDiagnostic(checker.Name(), call, msg, newSuggestedFuncReplacement(call, proposed)) } - case "True", "Truef": + case "True": if len(call.Args) < 1 { return nil } @@ -74,7 +77,7 @@ func (checker ErrorIsAs) Check(pass *analysis.Pass, call *CallMeta) *analysis.Di ) } - case "False", "Falsef": + case "False": if len(call.Args) < 1 { return nil } @@ -97,6 +100,45 @@ func (checker ErrorIsAs) Check(pass *analysis.Pass, call *CallMeta) *analysis.Di }), ) } + + case "ErrorAs": + if len(call.Args) < 2 { + return nil + } + + // NOTE(a.telyshev): Logic below must be consistent with + // https://cs.opensource.google/go/x/tools/+/master:go/analysis/passes/errorsas/errorsas.go + + var ( + defaultReport = fmt.Sprintf("second argument to %s must be a non-nil pointer to either a type that implements error, or to any interface type", call) //nolint:lll + errorPtrReport = fmt.Sprintf("second argument to %s should not be *error", call) + ) + + target := call.Args[1] + + if isEmptyInterface(pass, target) { + // `any` interface case. It is always allowed, since it often indicates + // a value forwarded from another source. + return nil + } + + tv, ok := pass.TypesInfo.Types[target] + if !ok { + return nil + } + + pt, ok := tv.Type.Underlying().(*types.Pointer) + if !ok { + return newDiagnostic(checker.Name(), call, defaultReport, nil) + } + if pt.Elem() == errorType { + return newDiagnostic(checker.Name(), call, errorPtrReport, nil) + } + + _, isInterface := pt.Elem().Underlying().(*types.Interface) + if !isInterface && !types.Implements(pt.Elem(), errorIface) { + return newDiagnostic(checker.Name(), call, defaultReport, nil) + } } return nil } diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/error_nil.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/error_nil.go index b45629a48..5b0af7458 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/error_nil.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/error_nil.go @@ -14,8 +14,14 @@ import ( // // assert.Nil(t, err) // assert.NotNil(t, err) -// assert.Equal(t, err, nil) -// assert.NotEqual(t, err, nil) +// assert.Equal(t, nil, err) +// assert.EqualValues(t, nil, err) +// assert.Exactly(t, nil, err) +// assert.ErrorIs(t, err, nil) +// +// assert.NotEqual(t, nil, err) +// assert.NotEqualValues(t, nil, err) +// assert.NotErrorIs(t, err, nil) // // and requires // @@ -34,40 +40,40 @@ func (checker ErrorNil) Check(pass *analysis.Pass, call *CallMeta) *analysis.Dia ) proposedFn, survivingArg, replacementEndPos := func() (string, ast.Expr, token.Pos) { - switch call.Fn.Name { - case "NotNil", "NotNilf": + switch call.Fn.NameFTrimmed { + case "Nil": if len(call.Args) >= 1 && isError(pass, call.Args[0]) { - return errorFn, call.Args[0], call.Args[0].End() + return noErrorFn, call.Args[0], call.Args[0].End() } - case "Nil", "Nilf": + case "NotNil": if len(call.Args) >= 1 && isError(pass, call.Args[0]) { - return noErrorFn, call.Args[0], call.Args[0].End() + return errorFn, call.Args[0], call.Args[0].End() } - case "Equal", "Equalf": + case "Equal", "EqualValues", "Exactly", "ErrorIs": if len(call.Args) < 2 { return "", nil, token.NoPos } a, b := call.Args[0], call.Args[1] switch { - case isError(pass, a) && isNil(pass, b): + case isError(pass, a) && isNil(b): return noErrorFn, a, b.End() - case isNil(pass, a) && isError(pass, b): + case isNil(a) && isError(pass, b): return noErrorFn, b, b.End() } - case "NotEqual", "NotEqualf": + case "NotEqual", "NotEqualValues", "NotErrorIs": if len(call.Args) < 2 { return "", nil, token.NoPos } a, b := call.Args[0], call.Args[1] switch { - case isError(pass, a) && isNil(pass, b): + case isError(pass, a) && isNil(b): return errorFn, a, b.End() - case isNil(pass, a) && isError(pass, b): + case isNil(a) && isError(pass, b): return errorFn, b, b.End() } } @@ -86,7 +92,10 @@ func (checker ErrorNil) Check(pass *analysis.Pass, call *CallMeta) *analysis.Dia return nil } -var errIface = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) +var ( + errorType = types.Universe.Lookup("error").Type() + errorIface = errorType.Underlying().(*types.Interface) +) func isError(pass *analysis.Pass, expr ast.Expr) bool { t := pass.TypesInfo.TypeOf(expr) @@ -95,15 +104,10 @@ func isError(pass *analysis.Pass, expr ast.Expr) bool { } _, ok := t.Underlying().(*types.Interface) - return ok && types.Implements(t, errIface) + return ok && types.Implements(t, errorIface) } -func isNil(pass *analysis.Pass, expr ast.Expr) bool { - t := pass.TypesInfo.TypeOf(expr) - if t == nil { - return false - } - - b, ok := t.(*types.Basic) - return ok && b.Kind()&types.UntypedNil > 0 +func isNil(expr ast.Expr) bool { + ident, ok := expr.(*ast.Ident) + return ok && ident.Name == "nil" } diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/expected_actual.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/expected_actual.go index ff8243980..e6825eaa6 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/expected_actual.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/expected_actual.go @@ -2,10 +2,13 @@ package checkers import ( "go/ast" + "go/token" "go/types" "regexp" "golang.org/x/tools/go/analysis" + + "github.com/Antonboom/testifylint/internal/analysisutil" ) // DefaultExpectedVarPattern matches variables with "expected" or "wanted" prefix or suffix in the name. @@ -14,11 +17,30 @@ var DefaultExpectedVarPattern = regexp.MustCompile( // ExpectedActual detects situation like // -// assert.NotEqual(t, result, "expected value") +// assert.Equal(t, result, expected) +// assert.EqualExportedValues(t, resultObj, User{Name: "Anton"}) +// assert.EqualValues(t, result, 42) +// assert.Exactly(t, result, int64(42)) +// assert.JSONEq(t, result, `{"version": 3}`) +// assert.InDelta(t, result, 42.42, 1.0) +// assert.InDeltaMapValues(t, result, map[string]float64{"score": 0.99}, 1.0) +// assert.InDeltaSlice(t, result, []float64{0.98, 0.99}, 1.0) +// assert.InEpsilon(t, result, 42.42, 0.0001) +// assert.InEpsilonSlice(t, result, []float64{0.9801, 0.9902}, 0.0001) +// assert.IsType(t, result, (*User)(nil)) +// assert.NotEqual(t, result, "expected") +// assert.NotEqualValues(t, result, "expected") +// assert.NotSame(t, resultPtr, &value) +// assert.Same(t, resultPtr, &value) +// assert.WithinDuration(t, resultTime, time.Date(2023, 01, 12, 11, 46, 33, 0, nil), time.Second) +// assert.YAMLEq(t, result, "version: '3'") // // and requires // -// assert.NotEqual(t, "expected value", result) +// assert.Equal(t, expected, result) +// assert.EqualExportedValues(t, User{Name: "Anton"}, resultObj) +// assert.EqualValues(t, 42, result) +// ... type ExpectedActual struct { expVarPattern *regexp.Regexp } @@ -38,9 +60,24 @@ func (checker *ExpectedActual) SetExpVarPattern(p *regexp.Regexp) *ExpectedActua } func (checker ExpectedActual) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { - switch call.Fn.Name { - case "Equal", "Equalf", "NotEqual", "NotEqualf", - "JSONEq", "JSONEqf", "YAMLEq", "YAMLEqf": + switch call.Fn.NameFTrimmed { + case "Equal", + "EqualExportedValues", + "EqualValues", + "Exactly", + "InDelta", + "InDeltaMapValues", + "InDeltaSlice", + "InEpsilon", + "InEpsilonSlice", + "IsType", + "JSONEq", + "NotEqual", + "NotEqualValues", + "NotSame", + "Same", + "WithinDuration", + "YAMLEq": default: return nil } @@ -73,21 +110,37 @@ func (checker ExpectedActual) isWrongExpectedActualOrder(pass *analysis.Pass, fi func (checker ExpectedActual) isExpectedValueCandidate(pass *analysis.Pass, expr ast.Expr) bool { switch v := expr.(type) { + case *ast.ParenExpr: + return checker.isExpectedValueCandidate(pass, v.X) + + case *ast.StarExpr: // *value + return checker.isExpectedValueCandidate(pass, v.X) + + case *ast.UnaryExpr: + return (v.Op == token.AND) && checker.isExpectedValueCandidate(pass, v.X) // &value + case *ast.CompositeLit: return true case *ast.CallExpr: - return isCastedBasicLitOrExpectedValue(v, checker.expVarPattern) || - isExpectedValueFactory(v, checker.expVarPattern) + return isParenExpr(v) || + isCastedBasicLitOrExpectedValue(v, checker.expVarPattern) || + isExpectedValueFactory(pass, v, checker.expVarPattern) } return isBasicLit(expr) || isUntypedConst(pass, expr) || isTypedConst(pass, expr) || isIdentNamedAsExpected(checker.expVarPattern, expr) || + isStructVarNamedAsExpected(checker.expVarPattern, expr) || isStructFieldNamedAsExpected(checker.expVarPattern, expr) } +func isParenExpr(ce *ast.CallExpr) bool { + _, ok := ce.Fun.(*ast.ParenExpr) + return ok +} + func isCastedBasicLitOrExpectedValue(ce *ast.CallExpr, pattern *regexp.Regexp) bool { if len(ce.Args) != 1 { return false @@ -111,15 +164,16 @@ func isCastedBasicLitOrExpectedValue(ce *ast.CallExpr, pattern *regexp.Regexp) b return false } -func isExpectedValueFactory(ce *ast.CallExpr, pattern *regexp.Regexp) bool { - if len(ce.Args) != 0 { - return false - } - +func isExpectedValueFactory(pass *analysis.Pass, ce *ast.CallExpr, pattern *regexp.Regexp) bool { switch fn := ce.Fun.(type) { case *ast.Ident: return pattern.MatchString(fn.Name) + case *ast.SelectorExpr: + timeDateFn := analysisutil.ObjectOf(pass.Pkg, "time", "Date") + if timeDateFn != nil && analysisutil.IsObj(pass.TypesInfo, fn.Sel, timeDateFn) { + return true + } return pattern.MatchString(fn.Sel.Name) } return false @@ -150,6 +204,11 @@ func isIdentNamedAsExpected(pattern *regexp.Regexp, e ast.Expr) bool { return ok && pattern.MatchString(id.Name) } +func isStructVarNamedAsExpected(pattern *regexp.Regexp, e ast.Expr) bool { + s, ok := e.(*ast.SelectorExpr) + return ok && isIdentNamedAsExpected(pattern, s.X) +} + func isStructFieldNamedAsExpected(pattern *regexp.Regexp, e ast.Expr) bool { s, ok := e.(*ast.SelectorExpr) return ok && isIdentNamedAsExpected(pattern, s.Sel) diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/float_compare.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/float_compare.go index 7d5b358b3..10b1330de 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/float_compare.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/float_compare.go @@ -1,6 +1,7 @@ package checkers import ( + "fmt" "go/ast" "go/token" "go/types" @@ -10,13 +11,15 @@ import ( // FloatCompare detects situation like // -// assert.Equal(t, 42.42, a) -// assert.True(t, a == 42.42) -// assert.False(t, a != 42.42) +// assert.Equal(t, 42.42, result) +// assert.EqualValues(t, 42.42, result) +// assert.Exactly(t, 42.42, result) +// assert.True(t, result == 42.42) +// assert.False(t, result != 42.42) // // and requires // -// assert.InEpsilon(t, 42.42, a, 0.0001) // Or assert.InDelta +// assert.InEpsilon(t, 42.42, result, 0.0001) // Or assert.InDelta type FloatCompare struct{} // NewFloatCompare constructs FloatCompare checker. @@ -25,21 +28,25 @@ func (FloatCompare) Name() string { return "float-compare" } func (checker FloatCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { invalid := func() bool { - switch call.Fn.Name { - case "Equal", "Equalf": - return len(call.Args) > 1 && isFloat(pass, call.Args[0]) && isFloat(pass, call.Args[1]) + switch call.Fn.NameFTrimmed { + case "Equal", "EqualValues", "Exactly": + return len(call.Args) > 1 && (isFloat(pass, call.Args[0]) || isFloat(pass, call.Args[1])) - case "True", "Truef": + case "True": return len(call.Args) > 0 && isFloatCompare(pass, call.Args[0], token.EQL) - case "False", "Falsef": + case "False": return len(call.Args) > 0 && isFloatCompare(pass, call.Args[0], token.NEQ) } return false }() if invalid { - return newUseFunctionDiagnostic(checker.Name(), call, "InEpsilon (or InDelta)", nil) + format := "use %s.InEpsilon (or InDelta)" + if call.Fn.IsFmt { + format = "use %s.InEpsilonf (or InDeltaf)" + } + return newDiagnostic(checker.Name(), call, fmt.Sprintf(format, call.SelectorXStr), nil) } return nil } diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/go_require.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/go_require.go new file mode 100644 index 000000000..4c0c9d1fd --- /dev/null +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/go_require.go @@ -0,0 +1,301 @@ +package checkers + +import ( + "fmt" + "go/ast" + "go/types" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/ast/inspector" + + "github.com/Antonboom/testifylint/internal/analysisutil" +) + +const ( + goRequireFnReportFormat = "%s contains assertions that must only be used in the goroutine running the test function" + goRequireCallReportFormat = "%s must only be used in the goroutine running the test function" +) + +// GoRequire takes idea from go vet's "testinggoroutine" check +// and detects usage of require package's functions or assert.FailNow in the non-test goroutines +// +// go func() { +// conn, err = lis.Accept() +// require.NoError(t, err) +// +// if assert.Error(err) { +// assert.FailNow(t, msg) +// } +// }() +type GoRequire struct{} + +// NewGoRequire constructs GoRequire checker. +func NewGoRequire() GoRequire { return GoRequire{} } +func (GoRequire) Name() string { return "go-require" } + +// Check should be consistent with +// https://cs.opensource.google/go/x/tools/+/master:go/analysis/passes/testinggoroutine/testinggoroutine.go +// +// But due to the fact that the Check covers cases missed by go vet, +// the implementation turned out to be terribly complicated. +// +// In simple words, the algorithm is as follows: +// - we walk along the call tree and store the status, whether we are in the test goroutine or not; +// - if we are in a test goroutine, then require is allowed, otherwise not; +// - when we encounter the launch of a subtest or `go` statement, the status changes; +// - in order to correctly handle the return to the correct status when exiting the current function, +// we have to store a stack of statuses (inGoroutineRunningTestFunc). +// +// Other test functions called in the test function are also analyzed to make a verdict about the current function. +// This leads to recursion, which the cache of processed functions (processedFuncs) helps reduce the impact of. +// Also, because of this, we have to pre-collect a list of test function declarations (testsDecls). +func (checker GoRequire) Check(pass *analysis.Pass, inspector *inspector.Inspector) (diagnostics []analysis.Diagnostic) { + testsDecls := make(funcDeclarations) + inspector.Preorder([]ast.Node{(*ast.FuncDecl)(nil)}, func(node ast.Node) { + fd := node.(*ast.FuncDecl) + + if isTestingFuncOrMethod(pass, fd) { + if tf, ok := pass.TypesInfo.ObjectOf(fd.Name).(*types.Func); ok { + testsDecls[tf] = fd + } + } + }) + + var inGoroutineRunningTestFunc boolStack + processedFuncs := make(map[*ast.FuncDecl]goRequireVerdict) + + nodesFilter := []ast.Node{ + (*ast.FuncDecl)(nil), + (*ast.GoStmt)(nil), + (*ast.CallExpr)(nil), + } + inspector.Nodes(nodesFilter, func(node ast.Node, push bool) bool { + if fd, ok := node.(*ast.FuncDecl); ok { + if !isTestingFuncOrMethod(pass, fd) { + return false + } + + if push { + inGoroutineRunningTestFunc.Push(true) + } else { + inGoroutineRunningTestFunc.Pop() + } + return true + } + + if _, ok := node.(*ast.GoStmt); ok { + if push { + inGoroutineRunningTestFunc.Push(false) + } else { + inGoroutineRunningTestFunc.Pop() + } + return true + } + + ce := node.(*ast.CallExpr) + if isSubTestRun(pass, ce) { + if push { + // t.Run spawns the new testing goroutine and declines + // possible warnings from previous "simple" goroutine. + inGoroutineRunningTestFunc.Push(true) + } else { + inGoroutineRunningTestFunc.Pop() + } + return true + } + + if !push { + return false + } + if inGoroutineRunningTestFunc.Last() { + // We are in testing goroutine and can skip any assertion checks. + return true + } + + testifyCall := NewCallMeta(pass, ce) + if testifyCall != nil { + switch checker.checkCall(testifyCall) { + case goRequireVerdictRequire: + d := newDiagnostic(checker.Name(), testifyCall, fmt.Sprintf(goRequireCallReportFormat, "require"), nil) + diagnostics = append(diagnostics, *d) + + case goRequireVerdictAssertFailNow: + d := newDiagnostic(checker.Name(), testifyCall, fmt.Sprintf(goRequireCallReportFormat, testifyCall), nil) + diagnostics = append(diagnostics, *d) + + case goRequireVerdictNoExit: + } + return false + } + + // Case of nested function call. + { + calledFd := testsDecls.Get(pass, ce) + if calledFd == nil { + return true + } + + if v := checker.checkFunc(pass, calledFd, testsDecls, processedFuncs); v != goRequireVerdictNoExit { + caller := analysisutil.NodeString(pass.Fset, ce.Fun) + d := newDiagnostic(checker.Name(), ce, fmt.Sprintf(goRequireFnReportFormat, caller), nil) + diagnostics = append(diagnostics, *d) + } + } + return true + }) + + return diagnostics +} + +func (checker GoRequire) checkFunc( + pass *analysis.Pass, + fd *ast.FuncDecl, + testsDecls funcDeclarations, + processedFuncs map[*ast.FuncDecl]goRequireVerdict, +) (result goRequireVerdict) { + if v, ok := processedFuncs[fd]; ok { + return v + } + + ast.Inspect(fd, func(node ast.Node) bool { + if result != goRequireVerdictNoExit { + return false + } + + if _, ok := node.(*ast.GoStmt); ok { + return false + } + + ce, ok := node.(*ast.CallExpr) + if !ok { + return true + } + + testifyCall := NewCallMeta(pass, ce) + if testifyCall != nil { + if v := checker.checkCall(testifyCall); v != goRequireVerdictNoExit { + result, processedFuncs[fd] = v, v + } + return false + } + + // Case of nested function call. + { + calledFd := testsDecls.Get(pass, ce) + if calledFd == nil { + return true + } + if calledFd == fd { + // Recursion. + return true + } + + if v := checker.checkFunc(pass, calledFd, testsDecls, processedFuncs); v != goRequireVerdictNoExit { + result = v + return false + } + return true + } + }) + + return result +} + +type goRequireVerdict int + +const ( + goRequireVerdictNoExit goRequireVerdict = iota + goRequireVerdictRequire + goRequireVerdictAssertFailNow +) + +func (checker GoRequire) checkCall(call *CallMeta) goRequireVerdict { + if !call.IsAssert { + return goRequireVerdictRequire + } + if call.Fn.NameFTrimmed == "FailNow" { + return goRequireVerdictAssertFailNow + } + return goRequireVerdictNoExit +} + +type funcDeclarations map[*types.Func]*ast.FuncDecl + +// Get returns the declaration of a called function or method. +// Currently, only static calls within the same package are supported, otherwise returns nil. +func (fd funcDeclarations) Get(pass *analysis.Pass, ce *ast.CallExpr) *ast.FuncDecl { + var obj types.Object + + switch fun := ce.Fun.(type) { + case *ast.SelectorExpr: + obj = pass.TypesInfo.ObjectOf(fun.Sel) + + case *ast.Ident: + obj = pass.TypesInfo.ObjectOf(fun) + + case *ast.IndexExpr: + if id, ok := fun.X.(*ast.Ident); ok { + obj = pass.TypesInfo.ObjectOf(id) + } + + case *ast.IndexListExpr: + if id, ok := fun.X.(*ast.Ident); ok { + obj = pass.TypesInfo.ObjectOf(id) + } + } + + if tf, ok := obj.(*types.Func); ok { + return fd[tf] + } + return nil +} + +type boolStack []bool + +func (s *boolStack) Push(v bool) { + *s = append(*s, v) +} + +func (s *boolStack) Pop() bool { + n := len(*s) + if n == 0 { + return false + } + + last := (*s)[n-1] + *s = (*s)[:n-1] + return last +} + +func (s boolStack) Last() bool { + n := len(s) + if n == 0 { + return false + } + return s[n-1] +} + +func isSubTestRun(pass *analysis.Pass, ce *ast.CallExpr) bool { + se, ok := ce.Fun.(*ast.SelectorExpr) + if !ok || se.Sel == nil { + return false + } + return (isTestingTPtr(pass, se.X) || implementsTestifySuiteIface(pass, se.X)) && se.Sel.Name == "Run" +} + +func isTestingFuncOrMethod(pass *analysis.Pass, fd *ast.FuncDecl) bool { + return hasTestingTParam(pass, fd) || isTestifySuiteMethod(pass, fd) +} + +func hasTestingTParam(pass *analysis.Pass, fd *ast.FuncDecl) bool { + if fd.Type == nil || fd.Type.Params == nil { + return false + } + + for _, param := range fd.Type.Params.List { + if isTestingTPtr(pass, param.Type) { + return true + } + } + return false +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/len.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/len.go index f10412f63..d4e6a48b5 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/len.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/len.go @@ -10,6 +10,8 @@ import ( // Len detects situations like // // assert.Equal(t, 3, len(arr)) +// assert.EqualValues(t, 3, len(arr)) +// assert.Exactly(t, 3, len(arr)) // assert.True(t, len(arr) == 3) // // and requires @@ -24,14 +26,18 @@ func (Len) Name() string { return "len" } func (checker Len) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { const proposedFn = "Len" - switch call.Fn.Name { - case "Equal", "Equalf": + switch call.Fn.NameFTrimmed { + case "Equal", "EqualValues", "Exactly": if len(call.Args) < 2 { return nil } a, b := call.Args[0], call.Args[1] if lenArg, expectedLen, ok := xorLenCall(pass, a, b); ok { + if expectedLen == b && !isIntBasicLit(expectedLen) { + // https://github.com/Antonboom/testifylint/issues/9 + return nil + } return newUseFunctionDiagnostic(checker.Name(), call, proposedFn, newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{ Pos: a.Pos(), @@ -41,13 +47,13 @@ func (checker Len) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnost ) } - case "True", "Truef": + case "True": if len(call.Args) < 1 { return nil } expr := call.Args[0] - if lenArg, expectedLen, ok := isLenEquality(pass, expr); ok { + if lenArg, expectedLen, ok := isLenEquality(pass, expr); ok && isIntBasicLit(expectedLen) { return newUseFunctionDiagnostic(checker.Name(), call, proposedFn, newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{ Pos: expr.Pos(), @@ -84,3 +90,8 @@ func isLenEquality(pass *analysis.Pass, e ast.Expr) (ast.Expr, ast.Expr, bool) { } return xorLenCall(pass, be.X, be.Y) } + +func isIntBasicLit(e ast.Expr) bool { + bl, ok := e.(*ast.BasicLit) + return ok && bl.Kind == token.INT +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/nil_compare.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/nil_compare.go new file mode 100644 index 000000000..89680a069 --- /dev/null +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/nil_compare.go @@ -0,0 +1,69 @@ +package checkers + +import ( + "go/ast" + + "golang.org/x/tools/go/analysis" + + "github.com/Antonboom/testifylint/internal/analysisutil" +) + +// NilCompare detects situations like +// +// assert.Equal(t, nil, value) +// assert.EqualValues(t, nil, value) +// assert.Exactly(t, nil, value) +// +// assert.NotEqual(t, nil, value) +// assert.NotEqualValues(t, nil, value) +// +// and requires +// +// assert.Nil(t, value) +// assert.NotNil(t, value) +type NilCompare struct{} + +// NewNilCompare constructs NilCompare checker. +func NewNilCompare() NilCompare { return NilCompare{} } +func (NilCompare) Name() string { return "nil-compare" } + +func (checker NilCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { + if len(call.Args) < 2 { + return nil + } + + survivingArg, ok := xorNil(call.Args[0], call.Args[1]) + if !ok { + return nil + } + + var proposedFn string + + switch call.Fn.NameFTrimmed { + case "Equal", "EqualValues", "Exactly": + proposedFn = "Nil" + case "NotEqual", "NotEqualValues": + proposedFn = "NotNil" + default: + return nil + } + + return newUseFunctionDiagnostic(checker.Name(), call, proposedFn, + newSuggestedFuncReplacement(call, proposedFn, analysis.TextEdit{ + Pos: call.Args[0].Pos(), + End: call.Args[1].End(), + NewText: analysisutil.NodeBytes(pass.Fset, survivingArg), + }), + ) +} + +func xorNil(first, second ast.Expr) (ast.Expr, bool) { + a, b := isNil(first), isNil(second) + if xor(a, b) { + if a { + return second, true + } + return first, true + } + return nil, false +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/require_error.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/require_error.go index 2da71ed81..ab09dd447 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/checkers/require_error.go +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/require_error.go @@ -1,35 +1,338 @@ package checkers -import "golang.org/x/tools/go/analysis" +import ( + "fmt" + "go/ast" + "go/token" + "regexp" -// RequireError detects situations like + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/ast/inspector" +) + +const requireErrorReport = "for error assertions use require" + +// RequireError detects error assertions like // +// assert.Error(t, err) // s.Error(err), s.Assert().Error(err) +// assert.ErrorIs(t, err, io.EOF) +// assert.ErrorAs(t, err, &target) +// assert.EqualError(t, err, "end of file") +// assert.ErrorContains(t, err, "end of file") // assert.NoError(t, err) -// s.ErrorIs(err, io.EOF) -// s.Assert().Error(err) +// assert.NotErrorIs(t, err, io.EOF) // // and requires // -// require.NoError(t, err) -// s.Require().ErrorIs(err, io.EOF) -// s.Require().Error(err) -type RequireError struct{} +// require.Error(t, err) // s.Require().Error(err), s.Require().Error(err) +// require.ErrorIs(t, err, io.EOF) +// require.ErrorAs(t, err, &target) +// ... +// +// RequireError ignores: +// - assertion in the `if` condition; +// - the entire `if-else[-if]` block, if there is an assertion in any `if` condition; +// - the last assertion in the block, if there are no methods/functions calls after it; +// - assertions in an explicit goroutine; +// - assertions in an explicit testing cleanup function or suite teardown methods; +// - sequence of NoError assertions. +type RequireError struct { + fnPattern *regexp.Regexp +} // NewRequireError constructs RequireError checker. -func NewRequireError() RequireError { return RequireError{} } -func (RequireError) Name() string { return "require-error" } +func NewRequireError() *RequireError { return new(RequireError) } +func (RequireError) Name() string { return "require-error" } + +func (checker *RequireError) SetFnPattern(p *regexp.Regexp) *RequireError { + if p != nil { + checker.fnPattern = p + } + return checker +} + +func (checker RequireError) Check(pass *analysis.Pass, inspector *inspector.Inspector) []analysis.Diagnostic { + callsByFunc := make(map[funcID][]*callMeta) + + // Stage 1. Collect meta information about any calls inside functions. + + inspector.WithStack([]ast.Node{(*ast.CallExpr)(nil)}, func(node ast.Node, push bool, stack []ast.Node) bool { + if !push { + return false + } + if len(stack) < 3 { + return true + } + + fID := findSurroundingFunc(pass, stack) + if fID == nil { + return true + } + + _, prevIsIfStmt := stack[len(stack)-2].(*ast.IfStmt) + _, prevIsAssignStmt := stack[len(stack)-2].(*ast.AssignStmt) + _, prevPrevIsIfStmt := stack[len(stack)-3].(*ast.IfStmt) + inIfCond := prevIsIfStmt || (prevPrevIsIfStmt && prevIsAssignStmt) + + callExpr := node.(*ast.CallExpr) + testifyCall := NewCallMeta(pass, callExpr) + + call := &callMeta{ + call: callExpr, + testifyCall: testifyCall, + rootIf: findRootIf(stack), + parentIf: findNearestNode[*ast.IfStmt](stack), + parentBlock: findNearestNode[*ast.BlockStmt](stack), + inIfCond: inIfCond, + inNoErrorSeq: false, // Will be filled in below. + } + + callsByFunc[*fID] = append(callsByFunc[*fID], call) + return testifyCall == nil // Do not support asserts in asserts. + }) + + // Stage 2. Analyze calls and block context. + + var diagnostics []analysis.Diagnostic + + callsByBlock := map[*ast.BlockStmt][]*callMeta{} + for _, calls := range callsByFunc { + for _, c := range calls { + if b := c.parentBlock; b != nil { + callsByBlock[b] = append(callsByBlock[b], c) + } + } + } + + markCallsInNoErrorSequence(callsByBlock) + + for funcInfo, calls := range callsByFunc { + for i, c := range calls { + if funcInfo.isTestCleanup { + continue + } + if funcInfo.isGoroutine { + continue + } + + if c.testifyCall == nil { + continue + } + if !c.testifyCall.IsAssert { + continue + } + switch c.testifyCall.Fn.NameFTrimmed { + default: + continue + case "Error", "ErrorIs", "ErrorAs", "EqualError", "ErrorContains", "NoError", "NotErrorIs": + } + + if needToSkipBasedOnContext(c, i, calls, callsByBlock) { + continue + } + if p := checker.fnPattern; p != nil && !p.MatchString(c.testifyCall.Fn.Name) { + continue + } + + diagnostics = append(diagnostics, + *newDiagnostic(checker.Name(), c.testifyCall, requireErrorReport, nil)) + } + } + + return diagnostics +} + +func needToSkipBasedOnContext( + currCall *callMeta, + currCallIndex int, + otherCalls []*callMeta, + callsByBlock map[*ast.BlockStmt][]*callMeta, +) bool { + if currCall.inNoErrorSeq { + // Skip `assert.NoError` sequence. + return true + } + + if currCall.inIfCond { + // Skip assertions in the "if condition". + return true + } + + if currCall.rootIf != nil { + for _, rootCall := range otherCalls { + if (rootCall.rootIf == currCall.rootIf) && rootCall.inIfCond { + // Skip assertions in the entire if-else[-if] block, if some of "if condition" contains assertion. + return true + } + } + } + + block := currCall.parentBlock + blockCalls := callsByBlock[block] + isLastCallInBlock := blockCalls[len(blockCalls)-1] == currCall + + noCallsAfter := true -func (checker RequireError) Check(_ *analysis.Pass, call *CallMeta) *analysis.Diagnostic { - if !call.IsAssert { - return nil + _, blockEndWithReturn := block.List[len(block.List)-1].(*ast.ReturnStmt) + if !blockEndWithReturn { + for i := currCallIndex + 1; i < len(otherCalls); i++ { + nextCall := otherCalls[i] + nextCallInElseBlock := false + + if pIf := currCall.parentIf; pIf != nil && pIf.Else != nil { + ast.Inspect(pIf.Else, func(n ast.Node) bool { + if n == nextCall.call { + nextCallInElseBlock = true + return false + } + return true + }) + } + + if !nextCallInElseBlock { + noCallsAfter = false + break + } + } } - const msg = "for error assertions use require" + // Skip assertion if this is the last operation in the test. + return isLastCallInBlock && noCallsAfter +} + +func findSurroundingFunc(pass *analysis.Pass, stack []ast.Node) *funcID { + for i := len(stack) - 2; i >= 0; i-- { + var fType *ast.FuncType + var fName string + var isTestCleanup bool + var isGoroutine bool + + switch fd := stack[i].(type) { + case *ast.FuncDecl: + fType, fName = fd.Type, fd.Name.Name + + if isTestifySuiteMethod(pass, fd) { + if ident := fd.Name; ident != nil && isAfterTestMethod(ident.Name) { + isTestCleanup = true + } + } + + case *ast.FuncLit: + fType, fName = fd.Type, "anonymous" + + if i >= 2 { //nolint:nestif + if ce, ok := stack[i-1].(*ast.CallExpr); ok { + if se, ok := ce.Fun.(*ast.SelectorExpr); ok { + isTestCleanup = isTestingTPtr(pass, se.X) && se.Sel != nil && (se.Sel.Name == "Cleanup") + } + + if _, ok := stack[i-2].(*ast.GoStmt); ok { + isGoroutine = true + } + } + } - switch call.Fn.Name { - case "Error", "ErrorIs", "ErrorAs", "EqualError", "ErrorContains", "NoError", "NotErrorIs", - "Errorf", "ErrorIsf", "ErrorAsf", "EqualErrorf", "ErrorContainsf", "NoErrorf", "NotErrorIsf": - return newDiagnostic(checker.Name(), call, msg, nil) + default: + continue + } + + return &funcID{ + pos: fType.Pos(), + posStr: pass.Fset.Position(fType.Pos()).String(), + name: fName, + isTestCleanup: isTestCleanup, + isGoroutine: isGoroutine, + } } return nil } + +func findRootIf(stack []ast.Node) *ast.IfStmt { + nearestIf, i := findNearestNodeWithIdx[*ast.IfStmt](stack) + for ; i > 0; i-- { + parent, ok := stack[i-1].(*ast.IfStmt) + if ok { + nearestIf = parent + } else { + break + } + } + return nearestIf +} + +func findNearestNode[T ast.Node](stack []ast.Node) (v T) { + v, _ = findNearestNodeWithIdx[T](stack) + return +} + +func findNearestNodeWithIdx[T ast.Node](stack []ast.Node) (v T, index int) { + for i := len(stack) - 2; i >= 0; i-- { + if n, ok := stack[i].(T); ok { + return n, i + } + } + return +} + +func markCallsInNoErrorSequence(callsByBlock map[*ast.BlockStmt][]*callMeta) { + for _, calls := range callsByBlock { + for i, c := range calls { + if c.testifyCall == nil { + continue + } + + var prevIsNoError bool + if i > 0 { + if prev := calls[i-1].testifyCall; prev != nil { + prevIsNoError = isNoErrorAssertion(prev.Fn.Name) + } + } + + var nextIsNoError bool + if i < len(calls)-1 { + if next := calls[i+1].testifyCall; next != nil { + nextIsNoError = isNoErrorAssertion(next.Fn.Name) + } + } + + if isNoErrorAssertion(c.testifyCall.Fn.Name) && (prevIsNoError || nextIsNoError) { + calls[i].inNoErrorSeq = true + } + } + } +} + +type callMeta struct { + call *ast.CallExpr + testifyCall *CallMeta + rootIf *ast.IfStmt // The root `if` in if-else[-if] chain. + parentIf *ast.IfStmt // The nearest `if`, can be equal with rootIf. + parentBlock *ast.BlockStmt + inIfCond bool // True for code like `if assert.ErrorAs(t, err, &target) {`. + inNoErrorSeq bool // True for sequence of `assert.NoError` assertions. +} + +type funcID struct { + pos token.Pos + posStr string + name string + isTestCleanup bool + isGoroutine bool +} + +func (id funcID) String() string { + return fmt.Sprintf("%s at %s", id.name, id.posStr) +} + +func isAfterTestMethod(name string) bool { + // https://github.com/stretchr/testify/blob/master/suite/interfaces.go + switch name { + case "TearDownSuite", "TearDownTest", "AfterTest", "HandleStats", "TearDownSubTest": + return true + } + return false +} + +func isNoErrorAssertion(fnName string) bool { + return (fnName == "NoError") || (fnName == "NoErrorf") +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/checkers/useless_assert.go b/vendor/github.com/Antonboom/testifylint/internal/checkers/useless_assert.go new file mode 100644 index 000000000..669f9d187 --- /dev/null +++ b/vendor/github.com/Antonboom/testifylint/internal/checkers/useless_assert.go @@ -0,0 +1,71 @@ +package checkers + +import ( + "golang.org/x/tools/go/analysis" + + "github.com/Antonboom/testifylint/internal/analysisutil" +) + +// UselessAssert detects useless asserts like +// +// 1) Asserting of the same variable +// +// assert.Equal(t, tt.value, tt.value) +// assert.ElementsMatch(t, users, users) +// ... +// +// 2) Open for contribution... +type UselessAssert struct{} + +// NewUselessAssert constructs UselessAssert checker. +func NewUselessAssert() UselessAssert { return UselessAssert{} } +func (UselessAssert) Name() string { return "useless-assert" } + +func (checker UselessAssert) Check(pass *analysis.Pass, call *CallMeta) *analysis.Diagnostic { + switch call.Fn.NameFTrimmed { + case + "Contains", + "ElementsMatch", + "Equal", + "EqualExportedValues", + "EqualValues", + "ErrorAs", + "ErrorIs", + "Exactly", + "Greater", + "GreaterOrEqual", + "Implements", + "InDelta", + "InDeltaMapValues", + "InDeltaSlice", + "InEpsilon", + "InEpsilonSlice", + "IsType", + "JSONEq", + "Less", + "LessOrEqual", + "NotEqual", + "NotEqualValues", + "NotErrorIs", + "NotRegexp", + "NotSame", + "NotSubset", + "Regexp", + "Same", + "Subset", + "WithinDuration", + "YAMLEq": + default: + return nil + } + + if len(call.Args) < 2 { + return nil + } + first, second := call.Args[0], call.Args[1] + + if analysisutil.NodeString(pass.Fset, first) == analysisutil.NodeString(pass.Fset, second) { + return newDiagnostic(checker.Name(), call, "asserting of the same variable", nil) + } + return nil +} diff --git a/vendor/github.com/Antonboom/testifylint/internal/config/config.go b/vendor/github.com/Antonboom/testifylint/internal/config/config.go index 51f627008..6dcfbdb52 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/config/config.go +++ b/vendor/github.com/Antonboom/testifylint/internal/config/config.go @@ -1,7 +1,9 @@ package config import ( + "errors" "flag" + "fmt" "github.com/Antonboom/testifylint/internal/checkers" ) @@ -9,11 +11,16 @@ import ( // NewDefault builds default testifylint config. func NewDefault() Config { return Config{ - EnableAll: false, - EnabledCheckers: checkers.EnabledByDefault(), + EnableAll: false, + DisabledCheckers: nil, + DisableAll: false, + EnabledCheckers: nil, ExpectedActual: ExpectedActualConfig{ ExpVarPattern: RegexpValue{checkers.DefaultExpectedVarPattern}, }, + RequireError: RequireErrorConfig{ + FnPattern: RegexpValue{nil}, + }, SuiteExtraAssertCall: SuiteExtraAssertCallConfig{ Mode: checkers.DefaultSuiteExtraAssertCallMode, }, @@ -22,9 +29,13 @@ func NewDefault() Config { // Config implements testifylint configuration. type Config struct { - EnableAll bool - EnabledCheckers KnownCheckersValue + EnableAll bool + DisabledCheckers KnownCheckersValue + DisableAll bool + EnabledCheckers KnownCheckersValue + ExpectedActual ExpectedActualConfig + RequireError RequireErrorConfig SuiteExtraAssertCall SuiteExtraAssertCallConfig } @@ -33,16 +44,55 @@ type ExpectedActualConfig struct { ExpVarPattern RegexpValue } +// RequireErrorConfig implements configuration of checkers.RequireError. +type RequireErrorConfig struct { + FnPattern RegexpValue +} + // SuiteExtraAssertCallConfig implements configuration of checkers.SuiteExtraAssertCall. type SuiteExtraAssertCallConfig struct { Mode checkers.SuiteExtraAssertCallMode } +func (cfg Config) Validate() error { + if cfg.EnableAll { + if cfg.DisableAll { + return errors.New("enable-all and disable-all options must not be combined") + } + + if len(cfg.EnabledCheckers) != 0 { + return errors.New("enable-all and enable options must not be combined") + } + } + + if cfg.DisableAll { + if len(cfg.DisabledCheckers) != 0 { + return errors.New("disable-all and disable options must not be combined") + } + + if len(cfg.EnabledCheckers) == 0 { + return errors.New("all checkers were disabled, but no one checker was enabled: at least one must be enabled") + } + } + + for _, checker := range cfg.DisabledCheckers { + if cfg.EnabledCheckers.Contains(checker) { + return fmt.Errorf("checker %q disabled and enabled at one moment", checker) + } + } + + return nil +} + // BindToFlags binds Config fields to according flags. func BindToFlags(cfg *Config, fs *flag.FlagSet) { fs.BoolVar(&cfg.EnableAll, "enable-all", false, "enable all checkers") - fs.Var(&cfg.EnabledCheckers, "enable", "comma separated list of enabled checkers") + fs.Var(&cfg.DisabledCheckers, "disable", "comma separated list of disabled checkers (to exclude from enabled by default)") + fs.BoolVar(&cfg.DisableAll, "disable-all", false, "disable all checkers") + fs.Var(&cfg.EnabledCheckers, "enable", "comma separated list of enabled checkers (in addition to enabled by default)") + fs.Var(&cfg.ExpectedActual.ExpVarPattern, "expected-actual.pattern", "regexp for expected variable name") + fs.Var(&cfg.RequireError.FnPattern, "require-error.fn-pattern", "regexp for error assertions that should only be analyzed") fs.Var(NewEnumValue(suiteExtraAssertCallModeAsString, &cfg.SuiteExtraAssertCall.Mode), "suite-extra-assert-call.mode", "to require or remove extra Assert() call") } diff --git a/vendor/github.com/Antonboom/testifylint/internal/config/flag_value_types.go b/vendor/github.com/Antonboom/testifylint/internal/config/flag_value_types.go index 2f0ee978f..5b08ec47b 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/config/flag_value_types.go +++ b/vendor/github.com/Antonboom/testifylint/internal/config/flag_value_types.go @@ -35,6 +35,15 @@ func (kcv *KnownCheckersValue) Set(v string) error { return nil } +func (kcv KnownCheckersValue) Contains(v string) bool { + for _, checker := range kcv { + if checker == v { + return true + } + } + return false +} + // RegexpValue is a special wrapper for support of flag.FlagSet over regexp.Regexp. // Original regexp is available through RegexpValue.Regexp. type RegexpValue struct { diff --git a/vendor/github.com/Antonboom/testifylint/internal/testify/const.go b/vendor/github.com/Antonboom/testifylint/internal/testify/const.go index 45731aa97..3476e4040 100644 --- a/vendor/github.com/Antonboom/testifylint/internal/testify/const.go +++ b/vendor/github.com/Antonboom/testifylint/internal/testify/const.go @@ -4,10 +4,14 @@ const ( ModulePath = "github.com/stretchr/testify" AssertPkgName = "assert" + HTTPPkgName = "http" + MockPkgName = "mock" RequirePkgName = "require" SuitePkgName = "suite" AssertPkgPath = ModulePath + "/" + AssertPkgName + HTTPPkgPath = ModulePath + "/" + HTTPPkgName + MockPkgPath = ModulePath + "/" + MockPkgName RequirePkgPath = ModulePath + "/" + RequirePkgName SuitePkgPath = ModulePath + "/" + SuitePkgName ) diff --git a/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/analyzer/analyzer.go b/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/analyzer/analyzer.go index d0cd2d5bb..b490f1c64 100644 --- a/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/analyzer/analyzer.go +++ b/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/analyzer/analyzer.go @@ -12,16 +12,17 @@ import ( "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/ast/inspector" - "github.com/GaijinEntertainment/go-exhaustruct/v3/internal/fields" + "github.com/GaijinEntertainment/go-exhaustruct/v3/internal/comment" "github.com/GaijinEntertainment/go-exhaustruct/v3/internal/pattern" + "github.com/GaijinEntertainment/go-exhaustruct/v3/internal/structure" ) type analyzer struct { include pattern.List `exhaustruct:"optional"` exclude pattern.List `exhaustruct:"optional"` - fieldsCache map[types.Type]fields.StructFields - fieldsCacheMu sync.RWMutex `exhaustruct:"optional"` + structFields structure.FieldsCache `exhaustruct:"optional"` + comments comment.Cache `exhaustruct:"optional"` typeProcessingNeed map[string]bool typeProcessingNeedMu sync.RWMutex `exhaustruct:"optional"` @@ -29,8 +30,8 @@ type analyzer struct { func NewAnalyzer(include, exclude []string) (*analysis.Analyzer, error) { a := analyzer{ - fieldsCache: make(map[types.Type]fields.StructFields), typeProcessingNeed: make(map[string]bool), + comments: comment.Cache{}, } var err error @@ -74,12 +75,7 @@ Anonymous structs can be matched by '<anonymous>' alias. func (a *analyzer) run(pass *analysis.Pass) (any, error) { insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) //nolint:forcetypeassert - insp.WithStack( - []ast.Node{ - (*ast.CompositeLit)(nil), - }, - a.newVisitor(pass), - ) + insp.WithStack([]ast.Node{(*ast.CompositeLit)(nil)}, a.newVisitor(pass)) return nil, nil //nolint:nilnil } @@ -115,7 +111,10 @@ func (a *analyzer) newVisitor(pass *analysis.Pass) func(n ast.Node, push bool, s } } - pos, msg := a.processStruct(pass, lit, structTyp, typeInfo) + file := a.comments.Get(pass.Fset, stack[0].(*ast.File)) //nolint:forcetypeassert + rc := getCompositeLitRelatedComments(stack, file) + pos, msg := a.processStruct(pass, lit, structTyp, typeInfo, rc) + if pos != nil { pass.Reportf(*pos, msg) } @@ -124,6 +123,35 @@ func (a *analyzer) newVisitor(pass *analysis.Pass) func(n ast.Node, push bool, s } } +// getCompositeLitRelatedComments returns all comments that are related to checked node. We +// have to traverse the stack manually as ast do not associate comments with +// [ast.CompositeLit]. +func getCompositeLitRelatedComments(stack []ast.Node, cm ast.CommentMap) []*ast.CommentGroup { + comments := make([]*ast.CommentGroup, 0) + + for i := len(stack) - 1; i >= 0; i-- { + node := stack[i] + + switch node.(type) { + case *ast.CompositeLit, // stack[len(stack)-1] + *ast.ReturnStmt, // return ... + *ast.IndexExpr, // map[enum]...{...}[key] + *ast.CallExpr, // myfunc(map...) + *ast.UnaryExpr, // &map... + *ast.AssignStmt, // variable assignment (without var keyword) + *ast.DeclStmt, // var declaration, parent of *ast.GenDecl + *ast.GenDecl, // var declaration, parent of *ast.ValueSpec + *ast.ValueSpec: // var declaration + comments = append(comments, cm[node]...) + + default: + return comments + } + } + + return comments +} + func getStructType(pass *analysis.Pass, lit *ast.CompositeLit) (*types.Struct, *TypeInfo, bool) { switch typ := pass.TypesInfo.TypeOf(lit).(type) { case *types.Named: // named type @@ -179,8 +207,15 @@ func (a *analyzer) processStruct( lit *ast.CompositeLit, structTyp *types.Struct, info *TypeInfo, + comments []*ast.CommentGroup, ) (*token.Pos, string) { - if !a.shouldProcessType(info) { + shouldProcess := a.shouldProcessType(info) + + if shouldProcess && comment.HasDirective(comments, comment.DirectiveIgnore) { + return nil, "" + } + + if !shouldProcess && !comment.HasDirective(comments, comment.DirectiveEnforce) { return nil, "" } @@ -233,24 +268,12 @@ func (a *analyzer) shouldProcessType(info *TypeInfo) bool { return res } -//revive:disable-next-line:unused-receiver func (a *analyzer) litSkippedFields( lit *ast.CompositeLit, typ *types.Struct, onlyExported bool, -) fields.StructFields { - a.fieldsCacheMu.RLock() - f, ok := a.fieldsCache[typ] - a.fieldsCacheMu.RUnlock() - - if !ok { - a.fieldsCacheMu.Lock() - f = fields.NewStructFields(typ) - a.fieldsCache[typ] = f - a.fieldsCacheMu.Unlock() - } - - return f.SkippedFields(lit, onlyExported) +) structure.Fields { + return a.structFields.Get(typ).Skipped(lit, onlyExported) } type TypeInfo struct { diff --git a/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/comment/cache.go b/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/comment/cache.go new file mode 100644 index 000000000..88edef638 --- /dev/null +++ b/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/comment/cache.go @@ -0,0 +1,35 @@ +package comment + +import ( + "go/ast" + "go/token" + "sync" +) + +type Cache struct { + comments map[*ast.File]ast.CommentMap + mu sync.RWMutex +} + +// Get returns a comment map for a given file. In case if a comment map is not +// found, it creates a new one. +func (c *Cache) Get(fset *token.FileSet, f *ast.File) ast.CommentMap { + c.mu.RLock() + if cm, ok := c.comments[f]; ok { + c.mu.RUnlock() + return cm + } + c.mu.RUnlock() + + c.mu.Lock() + defer c.mu.Unlock() + + if c.comments == nil { + c.comments = make(map[*ast.File]ast.CommentMap) + } + + cm := ast.NewCommentMap(fset, f, f.Comments) + c.comments[f] = cm + + return cm +} diff --git a/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/comment/directive.go b/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/comment/directive.go new file mode 100644 index 000000000..a39a8076f --- /dev/null +++ b/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/comment/directive.go @@ -0,0 +1,28 @@ +package comment + +import ( + "go/ast" + "strings" +) + +type Directive string + +const ( + prefix = `//exhaustruct:` + DirectiveIgnore Directive = prefix + `ignore` + DirectiveEnforce Directive = prefix + `enforce` +) + +// HasDirective parses a directive from a given list of comments. +// If no directive is found, the second return value is `false`. +func HasDirective(comments []*ast.CommentGroup, expected Directive) bool { + for _, cg := range comments { + for _, commentLine := range cg.List { + if strings.HasPrefix(commentLine.Text, string(expected)) { + return true + } + } + } + + return false +} diff --git a/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/structure/fields-cache.go b/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/structure/fields-cache.go new file mode 100644 index 000000000..12a379692 --- /dev/null +++ b/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/structure/fields-cache.go @@ -0,0 +1,35 @@ +package structure + +import ( + "go/types" + "sync" +) + +type FieldsCache struct { + fields map[*types.Struct]Fields + mu sync.RWMutex +} + +// Get returns a struct fields for a given type. In case if a struct fields is +// not found, it creates a new one from type definition. +func (c *FieldsCache) Get(typ *types.Struct) Fields { + c.mu.RLock() + fields, ok := c.fields[typ] + c.mu.RUnlock() + + if ok { + return fields + } + + c.mu.Lock() + defer c.mu.Unlock() + + if c.fields == nil { + c.fields = make(map[*types.Struct]Fields) + } + + fields = NewFields(typ) + c.fields[typ] = fields + + return fields +} diff --git a/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/fields/struct.go b/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/structure/fields.go index af2390e87..b6b1a48c8 100644 --- a/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/fields/struct.go +++ b/vendor/github.com/GaijinEntertainment/go-exhaustruct/v3/internal/structure/fields.go @@ -1,33 +1,34 @@ -package fields +package structure import ( "go/ast" "go/types" "reflect" + "strings" ) const ( - TagName = "exhaustruct" - OptionalTagValue = "optional" + tagName = "exhaustruct" + optionalTagValue = "optional" ) -type StructField struct { +type Field struct { Name string Exported bool Optional bool } -type StructFields []*StructField +type Fields []*Field -// NewStructFields creates a new [StructFields] from a given struct type. -// StructFields items are listed in order they appear in the struct. -func NewStructFields(strct *types.Struct) StructFields { - sf := make(StructFields, 0, strct.NumFields()) +// NewFields creates a new [Fields] from a given struct type. +// Fields items are listed in order they appear in the struct. +func NewFields(strct *types.Struct) Fields { + sf := make(Fields, 0, strct.NumFields()) for i := 0; i < strct.NumFields(); i++ { f := strct.Field(i) - sf = append(sf, &StructField{ + sf = append(sf, &Field{ Name: f.Name(), Exported: f.Exported(), Optional: HasOptionalTag(strct.Tag(i)), @@ -38,27 +39,29 @@ func NewStructFields(strct *types.Struct) StructFields { } func HasOptionalTag(tags string) bool { - return reflect.StructTag(tags).Get(TagName) == OptionalTagValue + return reflect.StructTag(tags).Get(tagName) == optionalTagValue } // String returns a comma-separated list of field names. -func (sf StructFields) String() (res string) { +func (sf Fields) String() string { + b := strings.Builder{} + for i := 0; i < len(sf); i++ { - if res != "" { - res += ", " + if b.Len() != 0 { + b.WriteString(", ") } - res += sf[i].Name + b.WriteString(sf[i].Name) } - return res + return b.String() } -// SkippedFields returns a list of fields that are not present in the given +// Skipped returns a list of fields that are not present in the given // literal, but expected to. // //revive:disable-next-line:cyclomatic -func (sf StructFields) SkippedFields(lit *ast.CompositeLit, onlyExported bool) StructFields { +func (sf Fields) Skipped(lit *ast.CompositeLit, onlyExported bool) Fields { if len(lit.Elts) != 0 && !isNamedLiteral(lit) { if len(lit.Elts) == len(sf) { return nil @@ -68,7 +71,7 @@ func (sf StructFields) SkippedFields(lit *ast.CompositeLit, onlyExported bool) S } em := sf.existenceMap() - res := make(StructFields, 0, len(sf)) + res := make(Fields, 0, len(sf)) for i := 0; i < len(lit.Elts); i++ { kv, ok := lit.Elts[i].(*ast.KeyValueExpr) @@ -99,7 +102,7 @@ func (sf StructFields) SkippedFields(lit *ast.CompositeLit, onlyExported bool) S return res } -func (sf StructFields) existenceMap() map[string]bool { +func (sf Fields) existenceMap() map[string]bool { m := make(map[string]bool, len(sf)) for i := 0; i < len(sf); i++ { diff --git a/vendor/github.com/OpenPeeDeeP/depguard/v2/.gitignore b/vendor/github.com/OpenPeeDeeP/depguard/v2/.gitignore index 97cca67c6..e189bdb22 100644 --- a/vendor/github.com/OpenPeeDeeP/depguard/v2/.gitignore +++ b/vendor/github.com/OpenPeeDeeP/depguard/v2/.gitignore @@ -12,3 +12,4 @@ *.out .idea +.null-ls*.go diff --git a/vendor/github.com/OpenPeeDeeP/depguard/v2/README.md b/vendor/github.com/OpenPeeDeeP/depguard/v2/README.md index 3de3f6317..2ccfa22c5 100644 --- a/vendor/github.com/OpenPeeDeeP/depguard/v2/README.md +++ b/vendor/github.com/OpenPeeDeeP/depguard/v2/README.md @@ -7,13 +7,12 @@ allow specific packages within a repository. ## Install ```bash -go get github.com/OpenPeeDeeP/depguard/v2 +go install github.com/OpenPeeDeeP/depguard@latest ``` ## Config -The Depguard binary looks for a file named `^\.?depguard\.(yaml|yml|json|toml)$` in the current -current working directory. Examples include (`.depguard.yml` or `depguard.toml`). +The Depguard binary looks for a file named `^\.?depguard\.(yaml|yml|json|toml)$` in the current working directory. Examples include (`.depguard.yml` or `depguard.toml`). The following is an example configuration file. @@ -24,6 +23,7 @@ The following is an example configuration file. "$all", "!$test" ], + "listMode": "Strict", "allow": [ "$gostd", "github.com/OpenPeeDeeP" @@ -36,6 +36,7 @@ The following is an example configuration file. "files": [ "$test" ], + "listMode": "Lax", "deny": { "github.com/stretchr/testify": "Please use standard library for tests" } @@ -48,6 +49,7 @@ the linter's output. - `files` - list of file globs that will match this list of settings to compare against - `allow` - list of allowed packages - `deny` - map of packages that are not allowed where the value is a suggestion += `listMode` - the mode to use for package matching Files are matched using [Globs](https://github.com/gobwas/glob). If the files list is empty, then all files will match that list. Prefixing a file @@ -67,6 +69,21 @@ A Prefix List just means that a package will match a value, if the value is a prefix of the package. Example `github.com/OpenPeeDeeP/depguard` package will match a value of `github.com/OpenPeeDeeP` but won't match `github.com/OpenPeeDeeP/depguard/v2`. +ListMode is used to determine the package matching priority. There are three +different modes; Original, Strict, and Lax. + +Original is the original way that the package was written to use. It is not recommended +to stay with this and is only here for backwards compatibility. + +Strict, at its roots, is everything is denied unless in allowed. + +Lax, at its roots, is everything is allowed unless it is denied. + +There are cases where a package can be matched in both the allow and denied lists. +You may allow a subpackage but deny the root or vice versa. The `settings_tests.go` file +has many scenarios listed out under `TestListImportAllowed`. These tests will stay +up to date as features are added. + ### Variables There are variable replacements for each type of list (file or package). This is @@ -74,7 +91,7 @@ to reduce repetition and tedious behaviors. #### File Variables -> you can still use and exclamation mark `!` in front of a variable to say not to +> you can still use an exclamation mark `!` in front of a variable to say not to use it. Example `!$test` will match any file that is not a go test file. - `$all` - matches all go files diff --git a/vendor/github.com/OpenPeeDeeP/depguard/v2/settings.go b/vendor/github.com/OpenPeeDeeP/depguard/v2/settings.go index 440f32985..311cacc88 100644 --- a/vendor/github.com/OpenPeeDeeP/depguard/v2/settings.go +++ b/vendor/github.com/OpenPeeDeeP/depguard/v2/settings.go @@ -11,12 +11,22 @@ import ( ) type List struct { - Files []string `json:"files" yaml:"files" toml:"files" mapstructure:"files"` - Allow []string `json:"allow" yaml:"allow" toml:"allow" mapstructure:"allow"` - Deny map[string]string `json:"deny" yaml:"deny" toml:"deny" mapstructure:"deny"` + ListMode string `json:"listMode" yaml:"listMode" toml:"listMode" mapstructure:"listMode"` + Files []string `json:"files" yaml:"files" toml:"files" mapstructure:"files"` + Allow []string `json:"allow" yaml:"allow" toml:"allow" mapstructure:"allow"` + Deny map[string]string `json:"deny" yaml:"deny" toml:"deny" mapstructure:"deny"` } +type listMode int + +const ( + listModeOriginal listMode = iota + listModeStrict + listModeLax +) + type list struct { + listMode listMode name string files []glob.Glob negFiles []glob.Glob @@ -33,6 +43,20 @@ func (l *List) compile() (*list, error) { var errs utils.MultiError var err error + // Determine List Mode + switch strings.ToLower(l.ListMode) { + case "": + li.listMode = listModeOriginal + case "original": + li.listMode = listModeOriginal + case "strict": + li.listMode = listModeStrict + case "lax": + li.listMode = listModeLax + default: + errs = append(errs, fmt.Errorf("%s is not a known list mode", l.ListMode)) + } + // Compile Files for _, f := range l.Files { var negate bool @@ -113,16 +137,25 @@ func (l *list) fileMatch(fileName string) bool { } func (l *list) importAllowed(imp string) (bool, string) { - inAllowed := len(l.allow) == 0 - if !inAllowed { - inAllowed, _ = strInPrefixList(imp, l.allow) + inAllowed, aIdx := strInPrefixList(imp, l.allow) + inDenied, dIdx := strInPrefixList(imp, l.deny) + var allowed bool + switch l.listMode { + case listModeOriginal: + inAllowed = len(l.allow) == 0 || inAllowed + allowed = inAllowed && !inDenied + case listModeStrict: + allowed = inAllowed && (!inDenied || len(l.allow[aIdx]) > len(l.deny[dIdx])) + case listModeLax: + allowed = !inDenied || (inAllowed && len(l.allow[aIdx]) > len(l.deny[dIdx])) + default: + allowed = false } - inDenied, suggIdx := strInPrefixList(imp, l.deny) sugg := "" - if inDenied && suggIdx != -1 { - sugg = l.suggestions[suggIdx] + if !allowed && inDenied && dIdx != -1 { + sugg = l.suggestions[dIdx] } - return inAllowed && !inDenied, sugg + return allowed, sugg } type LinterSettings map[string]*List diff --git a/vendor/github.com/alecthomas/go-check-sumtype/decl.go b/vendor/github.com/alecthomas/go-check-sumtype/decl.go index ea2cd06df..9dec9eefd 100644 --- a/vendor/github.com/alecthomas/go-check-sumtype/decl.go +++ b/vendor/github.com/alecthomas/go-check-sumtype/decl.go @@ -57,6 +57,7 @@ func findSumTypeDecls(pkgs []*packages.Package) ([]sumTypeDecl, error) { } pos = pkg.Fset.Position(tspec.Pos()) decl := sumTypeDecl{Package: pkg, TypeName: tspec.Name.Name, Pos: pos} + debugf("found sum type decl: %s.%s", decl.Package.PkgPath, decl.TypeName) decls = append(decls, decl) break } diff --git a/vendor/github.com/alecthomas/go-check-sumtype/def.go b/vendor/github.com/alecthomas/go-check-sumtype/def.go index 811b98f98..24729ac01 100644 --- a/vendor/github.com/alecthomas/go-check-sumtype/def.go +++ b/vendor/github.com/alecthomas/go-check-sumtype/def.go @@ -1,11 +1,21 @@ package gochecksumtype import ( + "flag" "fmt" "go/token" "go/types" + "log" ) +var debug = flag.Bool("debug", false, "enable debug logging") + +func debugf(format string, args ...interface{}) { + if *debug { + log.Printf(format, args...) + } +} + // Error as returned by Run() type Error interface { error @@ -107,6 +117,7 @@ func newSumTypeDef(pkg *types.Package, decl sumTypeDecl) (*sumTypeDef, error) { Decl: decl, Ty: iface, } + debugf("searching for variants of %s.%s\n", pkg.Path(), decl.TypeName) for _, name := range pkg.Scope().Names() { obj, ok := pkg.Scope().Lookup(name).(*types.TypeName) if !ok { @@ -116,7 +127,12 @@ func newSumTypeDef(pkg *types.Package, decl sumTypeDecl) (*sumTypeDef, error) { if types.Identical(ty.Underlying(), iface) { continue } + // Skip generic types. + if named, ok := ty.(*types.Named); ok && named.TypeParams() != nil { + continue + } if types.Implements(ty, iface) || types.Implements(types.NewPointer(ty), iface) { + debugf(" found variant: %s.%s\n", pkg.Path(), obj.Name()) def.Variants = append(def.Variants, obj) } } diff --git a/vendor/github.com/bombsimon/wsl/v3/.gitignore b/vendor/github.com/bombsimon/wsl/v4/.gitignore index 1c8eba613..1c8eba613 100644 --- a/vendor/github.com/bombsimon/wsl/v3/.gitignore +++ b/vendor/github.com/bombsimon/wsl/v4/.gitignore diff --git a/vendor/github.com/bombsimon/wsl/v3/.golangci.yml b/vendor/github.com/bombsimon/wsl/v4/.golangci.yml index 336ad4bc8..543012008 100644 --- a/vendor/github.com/bombsimon/wsl/v3/.golangci.yml +++ b/vendor/github.com/bombsimon/wsl/v4/.golangci.yml @@ -39,6 +39,7 @@ linters: disable: - cyclop - deadcode + - depguard - dupl - dupword - exhaustivestruct @@ -62,11 +63,13 @@ linters: - nosnakecase - paralleltest - prealloc + - rowserrcheck - scopelint - structcheck - testpackage - varcheck - varnamelen + - wastedassign fast: false diff --git a/vendor/github.com/bombsimon/wsl/v3/LICENSE b/vendor/github.com/bombsimon/wsl/v4/LICENSE index 4dade6d1c..4dade6d1c 100644 --- a/vendor/github.com/bombsimon/wsl/v3/LICENSE +++ b/vendor/github.com/bombsimon/wsl/v4/LICENSE diff --git a/vendor/github.com/bombsimon/wsl/v3/README.md b/vendor/github.com/bombsimon/wsl/v4/README.md index 8ff74392b..0bcf01d96 100644 --- a/vendor/github.com/bombsimon/wsl/v3/README.md +++ b/vendor/github.com/bombsimon/wsl/v4/README.md @@ -1,4 +1,4 @@ -# WSL - Whitespace Linter +# wsl - Whitespace Linter [](https://forthebadge.com) [](https://forthebadge.com) @@ -6,83 +6,58 @@ [](https://github.com/bombsimon/wsl/actions/workflows/go.yml) [](https://coveralls.io/github/bombsimon/wsl?branch=master) -WSL is a linter that enforces a very **non scientific** vision of how to make +`wsl` is a linter that enforces a very **non scientific** vision of how to make code more readable by enforcing empty lines at the right places. -I think too much code out there is to cuddly and a bit too warm for it's own -good, making it harder for other people to read and understand. The linter will -warn about newlines in and around blocks, in the beginning of files and other -places in the code. - -**I know this linter is aggressive** and a lot of projects I've tested it on -have failed miserably. For this linter to be useful at all I want to be open to -new ideas, configurations and discussions! Also note that some of the warnings -might be bugs or unintentional false positives so I would love an +**This linter is aggressive** and a lot of projects I've tested it on have +failed miserably. For this linter to be useful at all I want to be open to new +ideas, configurations and discussions! Also note that some of the warnings might +be bugs or unintentional false positives so I would love an [issue](https://github.com/bombsimon/wsl/issues/new) to fix, discuss, change or make something configurable! ## Installation -### By `go get` (local installation) - -You can do that by using: - ```sh -go get -u github.com/bombsimon/wsl/v3/cmd/... -``` - -### By golangci-lint (CI automation) +# Latest release +go install github.com/bombsimon/wsl/v4/cmd/wsl -`wsl` is already integrated with -[golangci-lint](https://github.com/golangci/golangci-lint). Please refer to the -instructions there. +# Main branch +go install github.com/bombsimon/wsl/v4/cmd/wsl@master +``` ## Usage -How to use depends on how you install `wsl`. +> **Note**: This linter provides a fixer that can fix most issues with the +> `--fix` flag. However, currently `golangci-lint` [does not support suggested +> fixes](https://github.com/golangci/golangci-lint/issues/1779) so the `--fix` +> flag in `golangci-lint` will **not** work. -### With local binary - -The general command format for `wsl` is: +`wsl` uses the [analysis](https://pkg.go.dev/golang.org/x/tools/go/analysis) +package meaning it will operate on package level with the default analysis flags +and way of working. ```sh -$ wsl [flags] <file1> [files...] -$ wsl [flags] </path/to/package/...> - -# Examples +wsl --help +wsl [flags] </path/to/package/...> -$ wsl ./main.go -$ wsl --no-test ./main.go -$ wsl --allow-cuddle-declarations ./main.go -$ wsl --no-test --allow-cuddle-declaration ./main.go -$ wsl --no-test --allow-trailing-comment ./myProject/... +wsl --allow-cuddle-declarations --fix ./... ``` -The "..." wildcard is not used like other `go` commands but instead can only -be to a relative or absolute path. - -By default, the linter will run on `./...` which means all go files in the -current path and all subsequent paths, including test files. To disable linting -test files, use `-n` or `--no-test`. - -### By `golangci-lint` (CI automation) - -The recommended command is: +`wsl` is also integrated in [`golangci-lint`](https://golangci-lint.run) ```sh -golangci-lint run --disable-all --enable wsl +golangci-lint run --no-config --disable-all --enable wsl ``` -For more information, please refer to -[golangci-lint](https://github.com/golangci/golangci-lint)'s documentation. - ## Issues and configuration The linter suppers a few ways to configure it to satisfy more than one kind of code style. These settings could be set either with flags or with YAML configuration if used via `golangci-lint`. -The supported configuration can be found [in the documentation](doc/configuration.md). +The supported configuration can be found [in the +documentation](doc/configuration.md). Below are the available checklist for any hit from `wsl`. If you do not see any, feel free to raise an [issue](https://github.com/bombsimon/wsl/issues/new). @@ -106,14 +81,11 @@ feel free to raise an [issue](https://github.com/bombsimon/wsl/issues/new). * [For statements should only be cuddled with assignments used in the iteration](doc/rules.md#for-statements-should-only-be-cuddled-with-assignments-used-in-the-iteration) * [Go statements can only invoke functions assigned on line above](doc/rules.md#go-statements-can-only-invoke-functions-assigned-on-line-above) * [If statements should only be cuddled with assignments](doc/rules.md#if-statements-should-only-be-cuddled-with-assignments) -* [If statements should only be cuddled with assignments used in the if - statement - itself](doc/rules.md#if-statements-should-only-be-cuddled-with-assignments-used-in-the-if-statement-itself) +* [If statements should only be cuddled with assignments used in the if statement itself](doc/rules.md#if-statements-should-only-be-cuddled-with-assignments-used-in-the-if-statement-itself) * [If statements that check an error must be cuddled with the statement that assigned the error](doc/rules.md#if-statements-that-check-an-error-must-be-cuddled-with-the-statement-that-assigned-the-error) -* [Only cuddled expressions if assigning variable or using from line - above](doc/rules.md#only-cuddled-expressions-if-assigning-variable-or-using-from-line-above) +* [Only cuddled expressions if assigning variable or using from line above](doc/rules.md#only-cuddled-expressions-if-assigning-variable-or-using-from-line-above) * [Only one cuddle assignment allowed before defer statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-defer-statement) -* [Only one cuddle assginment allowed before for statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-for-statement) +* [Only one cuddle assignment allowed before for statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-for-statement) * [Only one cuddle assignment allowed before go statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-go-statement) * [Only one cuddle assignment allowed before if statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-if-statement) * [Only one cuddle assignment allowed before range statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-range-statement) diff --git a/vendor/github.com/bombsimon/wsl/v4/analyzer.go b/vendor/github.com/bombsimon/wsl/v4/analyzer.go new file mode 100644 index 000000000..b8eac1587 --- /dev/null +++ b/vendor/github.com/bombsimon/wsl/v4/analyzer.go @@ -0,0 +1,141 @@ +package wsl + +import ( + "flag" + "strings" + + "golang.org/x/tools/go/analysis" +) + +func NewAnalyzer(config *Configuration) *analysis.Analyzer { + wa := &wslAnalyzer{config: config} + + return &analysis.Analyzer{ + Name: "wsl", + Doc: "add or remove empty lines", + Flags: wa.flags(), + Run: wa.run, + RunDespiteErrors: true, + } +} + +func defaultConfig() *Configuration { + return &Configuration{ + AllowAssignAndAnythingCuddle: false, + AllowAssignAndCallCuddle: true, + AllowCuddleDeclaration: false, + AllowMultiLineAssignCuddle: true, + AllowSeparatedLeadingComment: false, + AllowTrailingComment: false, + ForceCuddleErrCheckAndAssign: false, + ForceExclusiveShortDeclarations: false, + StrictAppend: true, + AllowCuddleWithCalls: []string{"Lock", "RLock"}, + AllowCuddleWithRHS: []string{"Unlock", "RUnlock"}, + ErrorVariableNames: []string{"err"}, + ForceCaseTrailingWhitespaceLimit: 0, + } +} + +// wslAnalyzer is a wrapper around the configuration which is used to be able to +// set the configuration when creating the analyzer and later be able to update +// flags and running method. +type wslAnalyzer struct { + config *Configuration +} + +func (wa *wslAnalyzer) flags() flag.FlagSet { + flags := flag.NewFlagSet("", flag.ExitOnError) + + // If we have a configuration set we're not running from the command line so + // we don't use any flags. + if wa.config != nil { + return *flags + } + + wa.config = defaultConfig() + + flags.BoolVar(&wa.config.AllowAssignAndAnythingCuddle, "allow-assign-and-anything", false, "Allow assignments and anything to be cuddled") + flags.BoolVar(&wa.config.AllowAssignAndCallCuddle, "allow-assign-and-call", true, "Allow assignments and calls to be cuddled (if using same variable/type)") + flags.BoolVar(&wa.config.AllowCuddleDeclaration, "allow-cuddle-declarations", false, "Allow declarations to be cuddled") + flags.BoolVar(&wa.config.AllowMultiLineAssignCuddle, "allow-multi-line-assign", true, "Allow cuddling with multi line assignments") + flags.BoolVar(&wa.config.AllowSeparatedLeadingComment, "allow-separated-leading-comment", false, "Allow empty newlines in leading comments") + flags.BoolVar(&wa.config.AllowTrailingComment, "allow-trailing-comment", false, "Allow blocks to end with a comment") + flags.BoolVar(&wa.config.ForceCuddleErrCheckAndAssign, "force-err-cuddling", false, "Force cuddling of error checks with error var assignment") + flags.BoolVar(&wa.config.ForceExclusiveShortDeclarations, "force-short-decl-cuddling", false, "Force short declarations to cuddle by themselves") + flags.BoolVar(&wa.config.StrictAppend, "strict-append", true, "Strict rules for append") + flags.IntVar(&wa.config.ForceCaseTrailingWhitespaceLimit, "force-case-trailing-whitespace", 0, "Force newlines for case blocks > this number.") + + flags.Var(&multiStringValue{slicePtr: &wa.config.AllowCuddleWithCalls}, "allow-cuddle-with-calls", "Comma separated list of idents that can have cuddles after") + flags.Var(&multiStringValue{slicePtr: &wa.config.AllowCuddleWithRHS}, "allow-cuddle-with-rhs", "Comma separated list of idents that can have cuddles before") + flags.Var(&multiStringValue{slicePtr: &wa.config.ErrorVariableNames}, "error-variable-names", "Comma separated list of error variable names") + + return *flags +} + +func (wa *wslAnalyzer) run(pass *analysis.Pass) (interface{}, error) { + for _, file := range pass.Files { + filename := pass.Fset.PositionFor(file.Pos(), false).Filename + if !strings.HasSuffix(filename, ".go") { + continue + } + + processor := newProcessorWithConfig(file, pass.Fset, wa.config) + processor.parseAST() + + for pos, fix := range processor.result { + textEdits := []analysis.TextEdit{} + for _, f := range fix.fixRanges { + textEdits = append(textEdits, analysis.TextEdit{ + Pos: f.fixRangeStart, + End: f.fixRangeEnd, + NewText: []byte("\n"), + }) + } + + pass.Report(analysis.Diagnostic{ + Pos: pos, + Category: "whitespace", + Message: fix.reason, + SuggestedFixes: []analysis.SuggestedFix{ + { + TextEdits: textEdits, + }, + }, + }) + } + } + + //nolint:nilnil // A pass don't need to return anything. + return nil, nil +} + +// multiStringValue is a flag that supports multiple values. It's implemented to +// contain a pointer to a string slice that will be overwritten when the flag's +// `Set` method is called. +type multiStringValue struct { + slicePtr *[]string +} + +// Set implements the flag.Value interface and will overwrite the pointer to the +// slice with a new pointer after splitting the flag by comma. +func (m *multiStringValue) Set(value string) error { + s := []string{} + + for _, v := range strings.Split(value, ",") { + s = append(s, strings.TrimSpace(v)) + } + + *m.slicePtr = s + + return nil +} + +// Set implements the flag.Value interface. +func (m *multiStringValue) String() string { + if m.slicePtr == nil { + return "" + } + + return strings.Join(*m.slicePtr, ", ") +} diff --git a/vendor/github.com/bombsimon/wsl/v3/wsl.go b/vendor/github.com/bombsimon/wsl/v4/wsl.go index 1b139c047..6fd33335a 100644 --- a/vendor/github.com/bombsimon/wsl/v3/wsl.go +++ b/vendor/github.com/bombsimon/wsl/v4/wsl.go @@ -3,45 +3,44 @@ package wsl import ( "fmt" "go/ast" - "go/parser" "go/token" - "os" "reflect" + "sort" "strings" ) // Error reason strings. const ( - reasonMustCuddleErrCheck = "if statements that check an error must be cuddled with the statement that assigned the error" - reasonOnlyCuddleIfWithAssign = "if statements should only be cuddled with assignments" - reasonOnlyOneCuddle = "only one cuddle assignment allowed before if statement" - reasonOnlyCuddleWithUsedAssign = "if statements should only be cuddled with assignments used in the if statement itself" - reasonOnlyCuddle2LineReturn = "return statements should not be cuddled if block has more than two lines" - reasonMultiLineBranchCuddle = "branch statements should not be cuddled if block has more than two lines" + reasonAnonSwitchCuddled = "anonymous switch statements should never be cuddled" reasonAppendCuddledWithoutUse = "append only allowed to cuddle with appended value" reasonAssignsCuddleAssign = "assignments should only be cuddled with other assignments" - reasonNeverCuddleDeclare = "declarations should never be cuddled" - reasonExpressionCuddledWithDeclOrRet = "expressions should not be cuddled with declarations or returns" - reasonExpressionCuddledWithBlock = "expressions should not be cuddled with blocks" - reasonExprCuddlingNonAssignedVar = "only cuddled expressions if assigning variable or using from line above" - reasonOneCuddleBeforeRange = "only one cuddle assignment allowed before range statement" - reasonRangeCuddledWithoutUse = "ranges should only be cuddled with assignments used in the iteration" - reasonOneCuddleBeforeDefer = "only one cuddle assignment allowed before defer statement" + reasonBlockEndsWithWS = "block should not end with a whitespace (or comment)" + reasonBlockStartsWithWS = "block should not start with a whitespace" + reasonCaseBlockTooCuddly = "case block should end with newline at this size" reasonDeferCuddledWithOtherVar = "defer statements should only be cuddled with expressions on same variable" - reasonForWithoutCondition = "for statement without condition should never be cuddled" - reasonForWithMoreThanOneCuddle = "only one cuddle assignment allowed before for statement" + reasonExprCuddlingNonAssignedVar = "only cuddled expressions if assigning variable or using from line above" + reasonExpressionCuddledWithBlock = "expressions should not be cuddled with blocks" + reasonExpressionCuddledWithDeclOrRet = "expressions should not be cuddled with declarations or returns" reasonForCuddledAssignWithoutUse = "for statements should only be cuddled with assignments used in the iteration" - reasonOneCuddleBeforeGo = "only one cuddle assignment allowed before go statement" + reasonForWithoutCondition = "for statement without condition should never be cuddled" reasonGoFuncWithoutAssign = "go statements can only invoke functions assigned on line above" - reasonSwitchManyCuddles = "only one cuddle assignment allowed before switch statement" - reasonAnonSwitchCuddled = "anonymous switch statements should never be cuddled" + reasonMultiLineBranchCuddle = "branch statements should not be cuddled if block has more than two lines" + reasonMustCuddleErrCheck = "if statements that check an error must be cuddled with the statement that assigned the error" + reasonNeverCuddleDeclare = "declarations should never be cuddled" + reasonOnlyCuddle2LineReturn = "return statements should not be cuddled if block has more than two lines" + reasonOnlyCuddleIfWithAssign = "if statements should only be cuddled with assignments" + reasonOnlyCuddleWithUsedAssign = "if statements should only be cuddled with assignments used in the if statement itself" + reasonOnlyOneCuddleBeforeDefer = "only one cuddle assignment allowed before defer statement" + reasonOnlyOneCuddleBeforeFor = "only one cuddle assignment allowed before for statement" + reasonOnlyOneCuddleBeforeGo = "only one cuddle assignment allowed before go statement" + reasonOnlyOneCuddleBeforeIf = "only one cuddle assignment allowed before if statement" + reasonOnlyOneCuddleBeforeRange = "only one cuddle assignment allowed before range statement" + reasonOnlyOneCuddleBeforeSwitch = "only one cuddle assignment allowed before switch statement" + reasonOnlyOneCuddleBeforeTypeSwitch = "only one cuddle assignment allowed before type switch statement" + reasonRangeCuddledWithoutUse = "ranges should only be cuddled with assignments used in the iteration" + reasonShortDeclNotExclusive = "short declaration should cuddle only with other short declarations" reasonSwitchCuddledWithoutUse = "switch statements should only be cuddled with variables switched" - reasonTypeSwitchTooCuddled = "only one cuddle assignment allowed before type switch statement" reasonTypeSwitchCuddledWithoutUse = "type switch statements should only be cuddled with variables switched" - reasonBlockStartsWithWS = "block should not start with a whitespace" - reasonBlockEndsWithWS = "block should not end with a whitespace (or comment)" - reasonCaseBlockTooCuddly = "case block should end with newline at this size" - reasonShortDeclNotExclusive = "short declaration should cuddle only with other short declarations" ) // Warning strings. @@ -54,6 +53,7 @@ const ( warnUnknownRHS = "UNKNOWN RHS" ) +// Configuration represents configurable settings for the linter. type Configuration struct { // StrictAppend will do strict checking when assigning from append (x = // append(x, y)). If this is set to true the append call must append either @@ -82,7 +82,7 @@ type Configuration struct { // x = AnotherAssign() AllowAssignAndCallCuddle bool - // AllowAssignAndCallCuddle allows assignments to be cuddled with anything. + // AllowAssignAndAnythingCuddle allows assignments to be cuddled with anything. // Example supported with this set to true: // if x == 1 { // x = 0 @@ -176,94 +176,40 @@ type Configuration struct { ForceExclusiveShortDeclarations bool } -// DefaultConfig returns default configuration. -func DefaultConfig() Configuration { - return Configuration{ - StrictAppend: true, - AllowAssignAndCallCuddle: true, - AllowAssignAndAnythingCuddle: false, - AllowMultiLineAssignCuddle: true, - AllowTrailingComment: false, - AllowSeparatedLeadingComment: false, - ForceCuddleErrCheckAndAssign: false, - ForceExclusiveShortDeclarations: false, - ForceCaseTrailingWhitespaceLimit: 0, - AllowCuddleWithCalls: []string{"Lock", "RLock"}, - AllowCuddleWithRHS: []string{"Unlock", "RUnlock"}, - ErrorVariableNames: []string{"err"}, - } +// fix is a range to fixup. +type fix struct { + fixRangeStart token.Pos + fixRangeEnd token.Pos } -// Result represents the result of one error. -type Result struct { - FileName string - LineNumber int - Position token.Position - Reason string +// result represents the result of one error. +type result struct { + fixRanges []fix + reason string } -// String returns the filename, line number and reason of a Result. -func (r *Result) String() string { - return fmt.Sprintf("%s:%d: %s", r.FileName, r.LineNumber, r.Reason) -} - -type Processor struct { - config Configuration - result []Result - warnings []string - fileSet *token.FileSet +// processor is the type that keeps track of the file and fileset and holds the +// results from parsing the AST. +type processor struct { + config *Configuration file *ast.File + fileSet *token.FileSet + result map[token.Pos]result + warnings []string } -// NewProcessor will create a Processor. -// -//nolint:gocritic // It's fine to copy config struct -func NewProcessorWithConfig(cfg Configuration) *Processor { - return &Processor{ - result: []Result{}, - config: cfg, - } -} - -// NewProcessor will create a Processor. -func NewProcessor() *Processor { - return NewProcessorWithConfig(DefaultConfig()) -} - -// ProcessFiles takes a string slice with file names (full paths) and lints -// them. -// -//nolint:gocritic // Don't want named returns -func (p *Processor) ProcessFiles(filenames []string) ([]Result, []string) { - for _, filename := range filenames { - data, err := os.ReadFile(filename) - if err != nil { - panic(err) - } - - p.process(filename, data) +// newProcessorWithConfig will create a Processor with the passed configuration. +func newProcessorWithConfig(file *ast.File, fileSet *token.FileSet, cfg *Configuration) *processor { + return &processor{ + config: cfg, + file: file, + fileSet: fileSet, + result: make(map[token.Pos]result), } - - return p.result, p.warnings } -func (p *Processor) process(filename string, data []byte) { - fileSet := token.NewFileSet() - file, err := parser.ParseFile(fileSet, filename, data, parser.ParseComments) - // If the file is not parsable let's add a syntax error and move on. - if err != nil { - p.result = append(p.result, Result{ - FileName: filename, - LineNumber: 0, - Reason: fmt.Sprintf("invalid syntax, file cannot be linted (%s)", err.Error()), - }) - - return - } - - p.fileSet = fileSet - p.file = file - +// parseAST will parse the AST attached to the Processor instance. +func (p *processor) parseAST() { for _, d := range p.file.Decls { switch v := d.(type) { case *ast.FuncDecl: @@ -279,7 +225,7 @@ func (p *Processor) process(filename string, data []byte) { // parseBlockBody will parse any kind of block statements such as switch cases // and if statements. A list of Result is returned. -func (p *Processor) parseBlockBody(ident *ast.Ident, block *ast.BlockStmt) { +func (p *processor) parseBlockBody(ident *ast.Ident, block *ast.BlockStmt) { // Nothing to do if there's no value. if reflect.ValueOf(block).IsNil() { return @@ -294,7 +240,7 @@ func (p *Processor) parseBlockBody(ident *ast.Ident, block *ast.BlockStmt) { // parseBlockStatements will parse all the statements found in the body of a // node. A list of Result is returned. -func (p *Processor) parseBlockStatements(statements []ast.Stmt) { +func (p *processor) parseBlockStatements(statements []ast.Stmt) { for i, stmt := range statements { // Start by checking if this statement is another block (other than if, // for and range). This could be assignment to a function, defer or go @@ -380,14 +326,38 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { continue } - moreThanOneStatementAbove := func() bool { - if i < 2 { + nStatementsBefore := func(n int) bool { + if i < n { return false } - statementBeforePreviousStatement := statements[i-2] + for j := 1; j < n; j++ { + s1 := statements[i-j] + s2 := statements[i-(j+1)] + + if p.nodeStart(s1)-1 != p.nodeEnd(s2) { + return false + } + } - return p.nodeStart(previousStatement)-1 == p.nodeEnd(statementBeforePreviousStatement) + return true + } + + nStatementsAfter := func(n int) bool { + if len(statements)-1 < i+n { + return false + } + + for j := 0; j < n; j++ { + s1 := statements[i+j] + s2 := statements[i+j+1] + + if p.nodeEnd(s1)+1 != p.nodeStart(s2) { + return false + } + } + + return true } isLastStatementInBlockOfOnlyTwoLines := func() bool { @@ -413,9 +383,30 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { // it was and use *that* statement's position if p.config.ForceExclusiveShortDeclarations && cuddledWithLastStmt { if p.isShortDecl(stmt) && !p.isShortDecl(previousStatement) { - p.addError(stmt.Pos(), reasonShortDeclNotExclusive) + var reportNode ast.Node = previousStatement + + cm := ast.NewCommentMap(p.fileSet, stmt, p.file.Comments) + if cg, ok := cm[stmt]; ok && len(cg) > 0 { + for _, c := range cg { + if c.Pos() > previousStatement.End() && c.End() < stmt.Pos() { + reportNode = c + } + } + } + + p.addErrorRange( + stmt.Pos(), + reportNode.End(), + reportNode.End(), + reasonShortDeclNotExclusive, + ) } else if p.isShortDecl(previousStatement) && !p.isShortDecl(stmt) { - p.addError(previousStatement.Pos(), reasonShortDeclNotExclusive) + p.addErrorRange( + previousStatement.Pos(), + stmt.Pos(), + stmt.Pos(), + reasonShortDeclNotExclusive, + ) } } @@ -428,6 +419,31 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { } } + reportNewlineTwoLinesAbove := func(n1, n2 ast.Node, reason string) { + if atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) || + atLeastOneInListsMatch(assignedOnLineAbove, calledOrAssignedFirstInBlock) { + // If both the assignment on the line above _and_ the assignment + // two lines above is part of line or first in block, add the + // newline as if non were. + _, isAssignmentTwoLinesAbove := statements[i-2].(*ast.AssignStmt) + assignedTwoLinesAbove := p.findLHS(statements[i-2]) + + if isAssignmentTwoLinesAbove && + (atLeastOneInListsMatch(rightAndLeftHandSide, assignedTwoLinesAbove) || + atLeastOneInListsMatch(assignedTwoLinesAbove, calledOrAssignedFirstInBlock)) { + p.addWhitespaceBeforeError(n1, reason) + } else { + // If the variable on the line above is allowed to be + // cuddled, break two lines above so we keep the proper + // cuddling. + p.addErrorRange(n1.Pos(), n2.Pos(), n2.Pos(), reason) + } + } else { + // If not, break here so we separate the cuddled variable. + p.addWhitespaceBeforeError(n1, reason) + } + } + switch t := stmt.(type) { case *ast.IfStmt: checkingErrInitializedInline := func() bool { @@ -460,7 +476,12 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { } if atLeastOneInListsMatch(assignedOnLineAbove, p.config.ErrorVariableNames) { - p.addError(t.Pos(), reasonMustCuddleErrCheck) + p.addErrorRange( + stmt.Pos(), + previousStatement.End(), + stmt.Pos(), + reasonMustCuddleErrCheck, + ) } } @@ -468,12 +489,12 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { } if len(assignedOnLineAbove) == 0 { - p.addError(t.Pos(), reasonOnlyCuddleIfWithAssign) + p.addWhitespaceBeforeError(t, reasonOnlyCuddleIfWithAssign) continue } - if moreThanOneStatementAbove() { - p.addError(t.Pos(), reasonOnlyOneCuddle) + if nStatementsBefore(2) { + reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeIf) continue } @@ -485,33 +506,34 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { continue } - p.addError(t.Pos(), reasonOnlyCuddleWithUsedAssign) + p.addWhitespaceBeforeError(t, reasonOnlyCuddleWithUsedAssign) case *ast.ReturnStmt: if isLastStatementInBlockOfOnlyTwoLines() { continue } - p.addError(t.Pos(), reasonOnlyCuddle2LineReturn) + p.addWhitespaceBeforeError(t, reasonOnlyCuddle2LineReturn) case *ast.BranchStmt: if isLastStatementInBlockOfOnlyTwoLines() { continue } - p.addError(t.Pos(), reasonMultiLineBranchCuddle) + p.addWhitespaceBeforeError(t, reasonMultiLineBranchCuddle) case *ast.AssignStmt: // append is usually an assignment but should not be allowed to be // cuddled with anything not appended. if len(rightHandSide) > 0 && rightHandSide[len(rightHandSide)-1] == "append" { if p.config.StrictAppend { if !atLeastOneInListsMatch(calledOrAssignedOnLineAbove, rightHandSide) { - p.addError(t.Pos(), reasonAppendCuddledWithoutUse) + p.addWhitespaceBeforeError(t, reasonAppendCuddledWithoutUse) } } continue } - if _, ok := previousStatement.(*ast.AssignStmt); ok { + switch previousStatement.(type) { + case *ast.AssignStmt, *ast.IncDecStmt: continue } @@ -535,10 +557,18 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { } } - p.addError(t.Pos(), reasonAssignsCuddleAssign) + p.addWhitespaceBeforeError(t, reasonAssignsCuddleAssign) + case *ast.IncDecStmt: + switch previousStatement.(type) { + case *ast.AssignStmt, *ast.IncDecStmt: + continue + } + + p.addWhitespaceBeforeError(t, reasonAssignsCuddleAssign) + case *ast.DeclStmt: if !p.config.AllowCuddleDeclaration { - p.addError(t.Pos(), reasonNeverCuddleDeclare) + p.addWhitespaceBeforeError(t, reasonNeverCuddleDeclare) } case *ast.ExprStmt: switch previousStatement.(type) { @@ -547,9 +577,9 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { continue } - p.addError(t.Pos(), reasonExpressionCuddledWithDeclOrRet) + p.addWhitespaceBeforeError(t, reasonExpressionCuddledWithDeclOrRet) case *ast.IfStmt, *ast.RangeStmt, *ast.SwitchStmt: - p.addError(t.Pos(), reasonExpressionCuddledWithBlock) + p.addWhitespaceBeforeError(t, reasonExpressionCuddledWithBlock) } // If the expression is called on a type or variable used or @@ -567,17 +597,17 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { // If we assigned variables on the line above but didn't use them in // this expression there should probably be a newline between them. if len(assignedOnLineAbove) > 0 && !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) { - p.addError(t.Pos(), reasonExprCuddlingNonAssignedVar) + p.addWhitespaceBeforeError(t, reasonExprCuddlingNonAssignedVar) } case *ast.RangeStmt: - if moreThanOneStatementAbove() { - p.addError(t.Pos(), reasonOneCuddleBeforeRange) + if nStatementsBefore(2) { + reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeRange) continue } if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) { if !atLeastOneInListsMatch(assignedOnLineAbove, calledOrAssignedFirstInBlock) { - p.addError(t.Pos(), reasonRangeCuddledWithoutUse) + p.addWhitespaceBeforeError(t, reasonRangeCuddledWithoutUse) } } case *ast.DeferStmt: @@ -586,27 +616,38 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { continue } - // Special treatment of deferring body closes after error checking - // according to best practices. See - // https://github.com/bombsimon/wsl/issues/31 which links to - // discussion about error handling after HTTP requests. This is hard - // coded and very specific but for now this is to be seen as a - // special case. What this does is that it *only* allows a defer - // statement with `Close` on the right hand side to be cuddled with - // an if-statement to support this: - // resp, err := client.Do(req) - // if err != nil { - // return err - // } - // defer resp.Body.Close() - if _, ok := previousStatement.(*ast.IfStmt); ok { - if atLeastOneInListsMatch(rightHandSide, []string{"Close"}) { - continue + if nStatementsBefore(2) { + // We allow cuddling defer if the defer references something + // used two lines above. + // There are several reasons to why we do this. + // Originally there was a special use case only for "Close" + // + // https://github.com/bombsimon/wsl/issues/31 which links to + // resp, err := client.Do(req) + // if err != nil { + // return err + // } + // defer resp.Body.Close() + // + // After a discussion in a followup issue it makes sense to not + // only hard code `Close` but for anything that's referenced two + // statements above. + // + // https://github.com/bombsimon/wsl/issues/85 + // db, err := OpenDB() + // require.NoError(t, err) + // defer db.Close() + // + // All of this is only allowed if there's exactly three cuddled + // statements, otherwise the regular rules apply. + if !nStatementsBefore(3) && !nStatementsAfter(1) { + variablesTwoLinesAbove := append(p.findLHS(statements[i-2]), p.findRHS(statements[i-2])...) + if atLeastOneInListsMatch(rightHandSide, variablesTwoLinesAbove) { + continue + } } - } - if moreThanOneStatementAbove() { - p.addError(t.Pos(), reasonOneCuddleBeforeDefer) + reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeDefer) continue } @@ -620,7 +661,7 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { } // Allow use to cuddled defer func literals with usages on line - // abouve. Example: + // above. Example: // b := getB() // defer func() { // makesSenseToUse(b) @@ -638,18 +679,16 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { } if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) { - p.addError(t.Pos(), reasonDeferCuddledWithOtherVar) + p.addWhitespaceBeforeError(t, reasonDeferCuddledWithOtherVar) } case *ast.ForStmt: if len(rightAndLeftHandSide) == 0 { - p.addError(t.Pos(), reasonForWithoutCondition) - + p.addWhitespaceBeforeError(t, reasonForWithoutCondition) continue } - if moreThanOneStatementAbove() { - p.addError(t.Pos(), reasonForWithMoreThanOneCuddle) - + if nStatementsBefore(2) { + reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeFor) continue } @@ -658,7 +697,7 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { // first line in the block for details. if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) { if !atLeastOneInListsMatch(assignedOnLineAbove, calledOrAssignedFirstInBlock) { - p.addError(t.Pos(), reasonForCuddledAssignWithoutUse) + p.addWhitespaceBeforeError(t, reasonForCuddledAssignWithoutUse) } } case *ast.GoStmt: @@ -666,9 +705,8 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { continue } - if moreThanOneStatementAbove() { - p.addError(t.Pos(), reasonOneCuddleBeforeGo) - + if nStatementsBefore(2) { + reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeGo) continue } @@ -689,26 +727,24 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { } if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) { - p.addError(t.Pos(), reasonGoFuncWithoutAssign) + p.addWhitespaceBeforeError(t, reasonGoFuncWithoutAssign) } case *ast.SwitchStmt: - if moreThanOneStatementAbove() { - p.addError(t.Pos(), reasonSwitchManyCuddles) - + if nStatementsBefore(2) { + reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeSwitch) continue } if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) { if len(rightAndLeftHandSide) == 0 { - p.addError(t.Pos(), reasonAnonSwitchCuddled) + p.addWhitespaceBeforeError(t, reasonAnonSwitchCuddled) } else { - p.addError(t.Pos(), reasonSwitchCuddledWithoutUse) + p.addWhitespaceBeforeError(t, reasonSwitchCuddledWithoutUse) } } case *ast.TypeSwitchStmt: - if moreThanOneStatementAbove() { - p.addError(t.Pos(), reasonTypeSwitchTooCuddled) - + if nStatementsBefore(2) { + reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeTypeSwitch) continue } @@ -717,11 +753,11 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { // Allow type assertion on variables used in the first case // immediately. if !atLeastOneInListsMatch(assignedOnLineAbove, calledOrAssignedFirstInBlock) { - p.addError(t.Pos(), reasonTypeSwitchCuddledWithoutUse) + p.addWhitespaceBeforeError(t, reasonTypeSwitchCuddledWithoutUse) } } case *ast.CaseClause, *ast.CommClause: - // Case clauses will be checked by not allowing leading ot trailing + // Case clauses will be checked by not allowing leading of trailing // whitespaces within the block. There's nothing in the case itself // that may be cuddled. default: @@ -735,7 +771,7 @@ func (p *Processor) parseBlockStatements(statements []ast.Stmt) { // directly as the first argument inside a body. // The body will then be parsed as a *ast.BlockStmt (regular block) or as a list // of []ast.Stmt (case block). -func (p *Processor) firstBodyStatement(i int, allStmt []ast.Stmt) ast.Node { +func (p *processor) firstBodyStatement(i int, allStmt []ast.Stmt) ast.Node { stmt := allStmt[i] // Start by checking if the statement has a body (probably if-statement, @@ -766,7 +802,14 @@ func (p *Processor) firstBodyStatement(i int, allStmt []ast.Stmt) ast.Node { } } - p.parseBlockBody(nil, statementBodyContent) + // If statement bodies will be parsed already when finding block bodies. + // The reason is because if/else-if/else chains is nested in the AST + // where the else bit is a part of the if statement. Since if statements + // is the only statement that can be chained like this we exclude it + // from parsing it again here. + if _, ok := stmt.(*ast.IfStmt); !ok { + p.parseBlockBody(nil, statementBodyContent) + } case []ast.Stmt: // The Body field for an *ast.CaseClause or *ast.CommClause is of type // []ast.Stmt. We must check leading and trailing whitespaces and then @@ -791,7 +834,7 @@ func (p *Processor) firstBodyStatement(i int, allStmt []ast.Stmt) ast.Node { return firstBodyStatement } -func (p *Processor) findLHS(node ast.Node) []string { +func (p *processor) findLHS(node ast.Node) []string { var lhs []string if node == nil { @@ -850,7 +893,7 @@ func (p *Processor) findLHS(node ast.Node) []string { return lhs } -func (p *Processor) findRHS(node ast.Node) []string { +func (p *processor) findRHS(node ast.Node) []string { var rhs []string if node == nil { @@ -934,7 +977,7 @@ func (p *Processor) findRHS(node ast.Node) []string { return rhs } -func (p *Processor) isShortDecl(node ast.Node) bool { +func (p *processor) isShortDecl(node ast.Node) bool { if t, ok := node.(*ast.AssignStmt); ok { return t.Tok == token.DEFINE } @@ -942,16 +985,22 @@ func (p *Processor) isShortDecl(node ast.Node) bool { return false } -func (p *Processor) findBlockStmt(node ast.Node) []*ast.BlockStmt { +func (p *processor) findBlockStmt(node ast.Node) []*ast.BlockStmt { var blocks []*ast.BlockStmt switch t := node.(type) { + case *ast.BlockStmt: + return []*ast.BlockStmt{t} case *ast.AssignStmt: for _, x := range t.Rhs { blocks = append(blocks, p.findBlockStmt(x)...) } case *ast.CallExpr: blocks = append(blocks, p.findBlockStmt(t.Fun)...) + + for _, x := range t.Args { + blocks = append(blocks, p.findBlockStmt(x)...) + } case *ast.FuncLit: blocks = append(blocks, t.Body) case *ast.ExprStmt: @@ -964,6 +1013,8 @@ func (p *Processor) findBlockStmt(node ast.Node) []*ast.BlockStmt { blocks = append(blocks, p.findBlockStmt(t.Call)...) case *ast.GoStmt: blocks = append(blocks, p.findBlockStmt(t.Call)...) + case *ast.IfStmt: + blocks = append([]*ast.BlockStmt{t.Body}, p.findBlockStmt(t.Else)...) } return blocks @@ -1020,15 +1071,15 @@ func atLeastOneInListsMatch(listOne, listTwo []string) bool { // findLeadingAndTrailingWhitespaces will find leading and trailing whitespaces // in a node. The method takes comments in consideration which will make the // parser more gentle. -func (p *Processor) findLeadingAndTrailingWhitespaces(ident *ast.Ident, stmt, nextStatement ast.Node) { +func (p *processor) findLeadingAndTrailingWhitespaces(ident *ast.Ident, stmt, nextStatement ast.Node) { var ( - allowedLinesBeforeFirstStatement = 1 - commentMap = ast.NewCommentMap(p.fileSet, stmt, p.file.Comments) - blockStatements []ast.Stmt - blockStartLine int - blockEndLine int - blockStartPos token.Pos - blockEndPos token.Pos + commentMap = ast.NewCommentMap(p.fileSet, stmt, p.file.Comments) + blockStatements []ast.Stmt + blockStartLine int + blockEndLine int + blockStartPos token.Pos + blockEndPos token.Pos + isCase bool ) // Depending on the block type, get the statements in the block and where @@ -1041,9 +1092,11 @@ func (p *Processor) findLeadingAndTrailingWhitespaces(ident *ast.Ident, stmt, ne case *ast.CaseClause: blockStatements = t.Body blockStartPos = t.Colon + isCase = true case *ast.CommClause: blockStatements = t.Body blockStartPos = t.Colon + isCase = true default: p.addWarning(warnWSNodeTypeNotImplemented, stmt.Pos(), stmt) @@ -1055,8 +1108,8 @@ func (p *Processor) findLeadingAndTrailingWhitespaces(ident *ast.Ident, stmt, ne return } - blockStartLine = p.fileSet.Position(blockStartPos).Line - blockEndLine = p.fileSet.Position(blockEndPos).Line + blockStartLine = p.fileSet.PositionFor(blockStartPos, false).Line + blockEndLine = p.fileSet.PositionFor(blockEndPos, false).Line // No whitespace possible if LBrace and RBrace is on the same line. if blockStartLine == blockEndLine { @@ -1064,152 +1117,238 @@ func (p *Processor) findLeadingAndTrailingWhitespaces(ident *ast.Ident, stmt, ne } var ( - firstStatement = blockStatements[0] - lastStatement = blockStatements[len(blockStatements)-1] - seenCommentGroups = 0 + firstStatement = blockStatements[0] + lastStatement = blockStatements[len(blockStatements)-1] ) - // Get the comment related to the first statement, we do allow commends in + // Get the comment related to the first statement, we do allow comments in // the beginning of a block before the first statement. - if c, ok := commentMap[firstStatement]; ok { - for _, commentGroup := range c { - // If the comment group is on the same line as the block start - // (LBrace) we should not consider it. - if p.nodeStart(commentGroup) == blockStartLine { + var ( + openingNodePos = blockStartPos + 1 + lastLeadingComment ast.Node + ) + + var ( + firstStatementCommentGroups []*ast.CommentGroup + lastStatementCommentGroups []*ast.CommentGroup + ) + + if cg, ok := commentMap[firstStatement]; ok && !isCase { + firstStatementCommentGroups = cg + } else { + // TODO: Just like with trailing whitespaces comments in a case block is + // tied to the last token of the first statement. For now we iterate over + // all comments in the stmt and grab those that's after colon and before + // first statement. + for _, cg := range commentMap { + if len(cg) < 1 { continue } - // We only care about comments before our statement from the comment - // map. As soon as we hit comments after our statement let's break - // out! - if commentGroup.Pos() > firstStatement.Pos() { - break + // If we have comments and the last comment ends before the first + // statement and the node is after the colon, this must be the node + // mapped to comments. + for _, c := range cg { + if c.End() < firstStatement.Pos() && c.Pos() > blockStartPos { + firstStatementCommentGroups = append(firstStatementCommentGroups, c) + } } - // We store number of seen comment groups because we allow multiple - // groups with a newline between them; but if the first one has WS - // before it, we're not going to count it to force an error. - if p.config.AllowSeparatedLeadingComment { - cg := p.fileSet.Position(commentGroup.Pos()).Line + // And same if we have comments where the first comment is after the + // last statement but before the next statement (next case). As with + // the other things, if there is not next statement it's no next + // case and the logic will be handled when parsing the block. + if nextStatement == nil { + continue + } - if seenCommentGroups > 0 || cg == blockStartLine+1 { - seenCommentGroups++ + for _, c := range cg { + if c.Pos() > lastStatement.End() && c.End() < nextStatement.Pos() { + lastStatementCommentGroups = append(lastStatementCommentGroups, c) } - } else { - seenCommentGroups++ } + } + + // Since the comments come from a map they might not be ordered meaning + // that the last and first comment groups can be in the wrong order. We + // fix this by sorting all comments by pos after adding them all to the + // slice. + sort.Slice(firstStatementCommentGroups, func(i, j int) bool { + return firstStatementCommentGroups[i].Pos() < firstStatementCommentGroups[j].Pos() + }) + + sort.Slice(lastStatementCommentGroups, func(i, j int) bool { + return lastStatementCommentGroups[i].Pos() < lastStatementCommentGroups[j].Pos() + }) + } - // Support both /* multiline */ and //single line comments - for _, c := range commentGroup.List { - allowedLinesBeforeFirstStatement += len(strings.Split(c.Text, "\n")) + for _, commentGroup := range firstStatementCommentGroups { + // If the comment group is on the same line as the block start + // (LBrace) we should not consider it. + if p.nodeEnd(commentGroup) == blockStartLine { + openingNodePos = commentGroup.End() + continue + } + + // We only care about comments before our statement from the comment + // map. As soon as we hit comments after our statement let's break + // out! + if commentGroup.Pos() > firstStatement.Pos() { + break + } + + // We never allow leading whitespace for the first comment. + if lastLeadingComment == nil && p.nodeStart(commentGroup)-1 != blockStartLine { + p.addErrorRange( + openingNodePos, + openingNodePos, + commentGroup.Pos(), + reasonBlockStartsWithWS, + ) + } + + // If lastLeadingComment is set this is not the first comment so we + // should remove whitespace between them if we don't explicitly + // allow it. + if lastLeadingComment != nil && !p.config.AllowSeparatedLeadingComment { + if p.nodeStart(commentGroup)+1 != p.nodeEnd(lastLeadingComment) { + p.addErrorRange( + openingNodePos, + lastLeadingComment.End(), + commentGroup.Pos(), + reasonBlockStartsWithWS, + ) } } + + lastLeadingComment = commentGroup } - // If we allow separated comments, allow for a space after each group - if p.config.AllowSeparatedLeadingComment { - if seenCommentGroups > 1 { - allowedLinesBeforeFirstStatement += seenCommentGroups - 1 - } else if seenCommentGroups == 1 { - allowedLinesBeforeFirstStatement++ - } + lastNodePos := openingNodePos + if lastLeadingComment != nil { + lastNodePos = lastLeadingComment.End() + blockStartLine = p.nodeEnd(lastLeadingComment) } - // And now if the first statement is passed the number of allowed lines, - // then we had extra WS, possibly before the first comment group. - if p.nodeStart(firstStatement) > blockStartLine+allowedLinesBeforeFirstStatement { - p.addError( - blockStartPos, + // Check if we have a whitespace between the last node which can be the + // Lbrace, a comment on the same line or the last comment if we have + // comments inside the actual block and the first statement. This is never + // allowed. + if p.nodeStart(firstStatement)-1 != blockStartLine { + p.addErrorRange( + openingNodePos, + lastNodePos, + firstStatement.Pos(), reasonBlockStartsWithWS, ) } // If the blockEndLine is not 0 we're a regular block (not case). if blockEndLine != 0 { - if p.config.AllowTrailingComment { - if lastComment, ok := commentMap[lastStatement]; ok { - var ( - lastCommentGroup = lastComment[len(lastComment)-1] - lastCommentLine = lastCommentGroup.List[len(lastCommentGroup.List)-1] - countNewlines = 0 - ) + // We don't want to reject example functions since they have to end with + // a comment. + if isExampleFunc(ident) { + return + } - countNewlines += len(strings.Split(lastCommentLine.Text, "\n")) + var ( + lastNode ast.Node = lastStatement + trailingComments []ast.Node + ) - // No newlines between trailing comments and end of block. - if p.nodeStart(lastCommentLine)+countNewlines != blockEndLine-1 { - return + // Check if we have an comments _after_ the last statement and update + // the last node if so. + if c, ok := commentMap[lastStatement]; ok { + lastComment := c[len(c)-1] + if lastComment.Pos() > lastStatement.End() && lastComment.Pos() < stmt.End() { + lastNode = lastComment + } + } + + // TODO: This should be improved. + // The trailing comments are mapped to the last statement item which can + // be anything depending on what the last statement is. + // In `fmt.Println("hello")`, trailing comments will be mapped to + // `*ast.BasicLit` for the "hello" string. + // A short term improvement can be to cache this but for now we naively + // iterate over all items when we check a block. + for _, commentGroups := range commentMap { + for _, commentGroup := range commentGroups { + if commentGroup.Pos() < lastNode.End() || commentGroup.End() > stmt.End() { + continue } + + trailingComments = append(trailingComments, commentGroup) } } - if p.nodeEnd(lastStatement) != blockEndLine-1 && !isExampleFunc(ident) { - p.addError(blockEndPos, reasonBlockEndsWithWS) + // TODO: Should this be relaxed? + // Given the old code we only allowed trailing newline if it was + // directly tied to the last statement so for backwards compatibility + // we'll do the same. This means we fail all but the last whitespace + // even when allowing trailing comments. + for _, comment := range trailingComments { + if p.nodeStart(comment)-p.nodeEnd(lastNode) > 1 { + p.addErrorRange( + blockEndPos, + lastNode.End(), + comment.Pos(), + reasonBlockEndsWithWS, + ) + } + + lastNode = comment + } + + if !p.config.AllowTrailingComment && p.nodeEnd(stmt)-1 != p.nodeEnd(lastStatement) { + p.addErrorRange( + blockEndPos, + lastNode.End(), + stmt.End()-1, + reasonBlockEndsWithWS, + ) } return } + // Nothing to do if we're not looking for enforced newline. + if p.config.ForceCaseTrailingWhitespaceLimit == 0 { + return + } + // If we don't have any nextStatement the trailing whitespace will be // handled when parsing the switch. If we do have a next statement we can // see where it starts by getting it's colon position. We set the end of the // current case to the position of the next case. - switch n := nextStatement.(type) { - case *ast.CaseClause: - blockEndPos = n.Case - case *ast.CommClause: - blockEndPos = n.Case + switch nextStatement.(type) { + case *ast.CaseClause, *ast.CommClause: default: // No more cases return } - blockEndLine = p.fileSet.Position(blockEndPos).Line - 1 - - var ( - blockSize = blockEndLine - blockStartLine - caseTrailingCommentLines int - ) - - // TODO: I don't know what comments are bound to in cases. For regular - // blocks the last comment is bound to the last statement but for cases - // they are bound to the case clause expression. This will however get us all - // comments and depending on the case expression this gets tricky. - // - // To handle this I get the comment map from the current statement (the case - // itself) and iterate through all groups and all comment within all groups. - // I then get the comments after the last statement but before the next case - // clause and just map each line of comment that way. - for _, commentGroups := range commentMap { - for _, commentGroup := range commentGroups { - for _, comment := range commentGroup.List { - commentLine := p.fileSet.Position(comment.Pos()).Line - - // Ignore comments before the last statement. - if commentLine <= p.nodeStart(lastStatement) { - continue - } - - // Ignore comments after the end of this case. - if commentLine > blockEndLine { - continue - } - - // This allows /* multiline */ comments with newlines as well - // as regular (//) ones - caseTrailingCommentLines += len(strings.Split(comment.Text, "\n")) - } - } + var closingNode ast.Node = lastStatement + for _, commentGroup := range lastStatementCommentGroups { + // TODO: In future versions we might want to close the gaps between + // comments. However this is not currently reported in v3 so we + // won't add this for now. + // if p.nodeStart(commentGroup)-1 != p.nodeEnd(closingNode) {} + closingNode = commentGroup } - hasTrailingWhitespace := p.nodeEnd(lastStatement)+caseTrailingCommentLines != blockEndLine + totalRowsInCase := p.nodeEnd(closingNode) - blockStartLine + if totalRowsInCase < p.config.ForceCaseTrailingWhitespaceLimit { + return + } - // If the force trailing limit is configured and we don't end with a newline. - if p.config.ForceCaseTrailingWhitespaceLimit > 0 && !hasTrailingWhitespace { - // Check if the block size is too big to miss the newline. - if blockSize >= p.config.ForceCaseTrailingWhitespaceLimit { - p.addError(lastStatement.Pos(), reasonCaseBlockTooCuddly) - } + if p.nodeEnd(closingNode)+1 == p.nodeStart(nextStatement) { + p.addErrorRange( + closingNode.Pos(), + closingNode.End(), + closingNode.End(), + reasonCaseBlockTooCuddly, + ) } } @@ -1217,15 +1356,15 @@ func isExampleFunc(ident *ast.Ident) bool { return ident != nil && strings.HasPrefix(ident.Name, "Example") } -func (p *Processor) nodeStart(node ast.Node) int { - return p.fileSet.Position(node.Pos()).Line +func (p *processor) nodeStart(node ast.Node) int { + return p.fileSet.PositionFor(node.Pos(), false).Line } -func (p *Processor) nodeEnd(node ast.Node) int { - line := p.fileSet.Position(node.End()).Line +func (p *processor) nodeEnd(node ast.Node) int { + line := p.fileSet.PositionFor(node.End(), false).Line if isEmptyLabeledStmt(node) { - return p.fileSet.Position(node.Pos()).Line + return p.fileSet.PositionFor(node.Pos(), false).Line } return line @@ -1242,21 +1381,29 @@ func isEmptyLabeledStmt(node ast.Node) bool { return empty } -// Add an error for the file and line number for the current token.Pos with the -// given reason. -func (p *Processor) addError(pos token.Pos, reason string) { - position := p.fileSet.Position(pos) +func (p *processor) addWhitespaceBeforeError(node ast.Node, reason string) { + p.addErrorRange(node.Pos(), node.Pos(), node.Pos(), reason) +} + +func (p *processor) addErrorRange(reportAt, start, end token.Pos, reason string) { + report, ok := p.result[reportAt] + if !ok { + report = result{ + reason: reason, + fixRanges: []fix{}, + } + } - p.result = append(p.result, Result{ - FileName: position.Filename, - LineNumber: position.Line, - Position: position, - Reason: reason, + report.fixRanges = append(report.fixRanges, fix{ + fixRangeStart: start, + fixRangeEnd: end, }) + + p.result[reportAt] = report } -func (p *Processor) addWarning(w string, pos token.Pos, t interface{}) { - position := p.fileSet.Position(pos) +func (p *processor) addWarning(w string, pos token.Pos, t interface{}) { + position := p.fileSet.PositionFor(pos, false) p.warnings = append(p.warnings, fmt.Sprintf("%s:%d: %s (%T)", position.Filename, position.Line, w, t), diff --git a/vendor/github.com/butuzov/ireturn/analyzer/analyzer.go b/vendor/github.com/butuzov/ireturn/analyzer/analyzer.go index 21e5897b2..f68170fb3 100644 --- a/vendor/github.com/butuzov/ireturn/analyzer/analyzer.go +++ b/vendor/github.com/butuzov/ireturn/analyzer/analyzer.go @@ -4,6 +4,7 @@ import ( "flag" "go/ast" gotypes "go/types" + "runtime" "strings" "sync" @@ -22,10 +23,11 @@ type validator interface { } type analyzer struct { - once sync.Once - mu sync.RWMutex - handler validator - err error + once sync.Once + mu sync.RWMutex + handler validator + err error + diabledNolint bool found []analysis.Diagnostic } @@ -61,8 +63,7 @@ func (a *analyzer) run(pass *analysis.Pass) (interface{}, error) { } // 003. Is it allowed to be checked? - // TODO(butuzov): add inline comment - if hasDisallowDirective(f.Doc) { + if !a.diabledNolint && hasDisallowDirective(f.Doc) { return } @@ -70,7 +71,6 @@ func (a *analyzer) run(pass *analysis.Pass) (interface{}, error) { // 004. Filtering Results. for _, issue := range filterInterfaces(pass, f.Type, dotImportedStd) { - if a.handler.IsValid(issue) { continue } @@ -112,6 +112,13 @@ func (a *analyzer) readConfiguration(fs *flag.FlagSet) { return } + // First: checking nonolint directive + val := fs.Lookup("nonolint") + if val != nil { + a.diabledNolint = fs.Lookup("nonolint").Value.String() == "true" + } + + // Second: validators implementation next if validatorImpl, ok := cnf.(validator); ok { a.handler = validatorImpl return @@ -136,6 +143,7 @@ func flags() flag.FlagSet { set := flag.NewFlagSet("", flag.PanicOnError) set.String("allow", "", "accept-list of the comma-separated interfaces") set.String("reject", "", "reject-list of the comma-separated interfaces") + set.Bool("nonolint", false, "disable nolint checks") return *set } @@ -146,13 +154,10 @@ func filterInterfaces(p *analysis.Pass, ft *ast.FuncType, di map[string]struct{} return results } - tp := newTypeParams(ft.TypeParams) - for _, el := range ft.Results.List { switch v := el.Type.(type) { // ----- empty or anonymous interfaces case *ast.InterfaceType: - if len(v.Methods.List) == 0 { results = append(results, types.NewIssue("interface{}", types.EmptyInterface)) continue @@ -164,35 +169,65 @@ func filterInterfaces(p *analysis.Pass, ft *ast.FuncType, di map[string]struct{} case *ast.Ident: t1 := p.TypesInfo.TypeOf(el.Type) - if !gotypes.IsInterface(t1.Underlying()) { + val, ok := t1.Underlying().(*gotypes.Interface) + if !ok { continue } - word := t1.String() - // only build in interface is error - if obj := gotypes.Universe.Lookup(word); obj != nil { - results = append(results, types.NewIssue(obj.Name(), types.ErrorInterface)) + var ( + name = t1.String() + isNamed = strings.Contains(name, ".") + isEmpty = val.Empty() + ) + + // catching any + if isEmpty && name == "any" { + results = append(results, types.NewIssue(name, types.EmptyInterface)) + continue + } + + // NOTE: FIXED! + if name == "error" { + results = append(results, types.NewIssue(name, types.ErrorInterface)) continue } - // found in type params - if tp.In(word) { - results = append(results, types.NewIssue(word, types.Generic)) + if !isNamed { + + typeParams := val.String() + prefix, suffix := "interface{", "}" + if strings.HasPrefix(typeParams, prefix) { // nolint: gosimple + typeParams = typeParams[len(prefix):] + } + if strings.HasSuffix(typeParams, suffix) { + typeParams = typeParams[:len(typeParams)-1] + } + + goVersion := runtime.Version() + if strings.HasPrefix(goVersion, "go1.18") || strings.HasPrefix(goVersion, "go1.19") { + typeParams = strings.ReplaceAll(typeParams, "|", " | ") + } + + results = append(results, types.IFace{ + Name: name, + Type: types.Generic, + OfType: typeParams, + }) continue } // is it dot-imported package? // handling cases when stdlib package imported via "." dot-import if len(di) > 0 { - name := stdPkgInterface(word) - if _, ok := di[name]; ok { - results = append(results, types.NewIssue(word, types.NamedStdInterface)) + pkgName := stdPkgInterface(name) + if _, ok := di[pkgName]; ok { + results = append(results, types.NewIssue(name, types.NamedStdInterface)) continue } } - results = append(results, types.NewIssue(word, types.NamedInterface)) + results = append(results, types.NewIssue(name, types.NamedInterface)) // ------- standard library and 3rd party interfaces case *ast.SelectorExpr: diff --git a/vendor/github.com/butuzov/ireturn/analyzer/internal/types/iface.go b/vendor/github.com/butuzov/ireturn/analyzer/internal/types/iface.go index 13f19a3e2..5e576374d 100644 --- a/vendor/github.com/butuzov/ireturn/analyzer/internal/types/iface.go +++ b/vendor/github.com/butuzov/ireturn/analyzer/internal/types/iface.go @@ -14,6 +14,7 @@ type IFace struct { Pos token.Pos // Token Position FuncName string // + OfType string } func NewIssue(name string, interfaceType IType) IFace { @@ -30,11 +31,15 @@ func (i *IFace) Enrich(f *ast.FuncDecl) { } func (i IFace) String() string { - if i.Type == Generic { - return fmt.Sprintf("%s returns generic interface (%s)", i.FuncName, i.Name) + if i.Type != Generic { + return fmt.Sprintf("%s returns interface (%s)", i.FuncName, i.Name) } - return fmt.Sprintf("%s returns interface (%s)", i.FuncName, i.Name) + if i.OfType != "" { + return fmt.Sprintf("%s returns generic interface (%s) of type param %s", i.FuncName, i.Name, i.OfType) + } + + return fmt.Sprintf("%s returns generic interface (%s)", i.FuncName, i.Name) } func (i IFace) HashString() string { diff --git a/vendor/github.com/butuzov/ireturn/analyzer/std.go b/vendor/github.com/butuzov/ireturn/analyzer/std.go index 4c6c4e420..cac464612 100644 --- a/vendor/github.com/butuzov/ireturn/analyzer/std.go +++ b/vendor/github.com/butuzov/ireturn/analyzer/std.go @@ -197,4 +197,7 @@ var std = map[string]struct{}{ "maps": {}, "slices": {}, "testing/slogtest": {}, + // added in Go v1.22 in compare to v1.21 (docker image) + "go/version": {}, + "math/rand/v2": {}, } diff --git a/vendor/github.com/butuzov/ireturn/analyzer/typeparams.go b/vendor/github.com/butuzov/ireturn/analyzer/typeparams.go deleted file mode 100644 index 14193c355..000000000 --- a/vendor/github.com/butuzov/ireturn/analyzer/typeparams.go +++ /dev/null @@ -1,38 +0,0 @@ -package analyzer - -import ( - "go/ast" -) - -type typeParams struct { - found []string -} - -func newTypeParams(fl *ast.FieldList) typeParams { - tp := typeParams{} - - if fl == nil { - return tp - } - - for _, el := range fl.List { - if el == nil { - continue - } - - for _, name := range el.Names { - tp.found = append(tp.found, name.Name) - } - } - - return tp -} - -func (tp typeParams) In(t string) bool { - for _, i := range tp.found { - if i == t { - return true - } - } - return false -} diff --git a/vendor/github.com/catenacyber/perfsprint/analyzer/analyzer.go b/vendor/github.com/catenacyber/perfsprint/analyzer/analyzer.go index 69087802a..ad312ca69 100644 --- a/vendor/github.com/catenacyber/perfsprint/analyzer/analyzer.go +++ b/vendor/github.com/catenacyber/perfsprint/analyzer/analyzer.go @@ -6,7 +6,9 @@ import ( "go/format" "go/token" "go/types" + "sort" "strconv" + "strings" "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/ast/inspector" @@ -14,24 +16,66 @@ import ( "golang.org/x/tools/go/analysis" ) -var Analyzer = &analysis.Analyzer{ - Name: "perfsprint", - Doc: "Checks that fmt.Sprintf can be replaced with a faster alternative.", - Run: run, - Requires: []*analysis.Analyzer{inspect.Analyzer}, +type perfSprint struct { + intConv bool + errError bool + errorf bool + sprintf1 bool + fiximports bool } -func run(pass *analysis.Pass) (interface{}, error) { - var fmtSprintObj, fmtSprintfObj types.Object +func newPerfSprint() *perfSprint { + return &perfSprint{ + intConv: true, + errError: false, + errorf: true, + sprintf1: true, + fiximports: true, + } +} + +func New() *analysis.Analyzer { + n := newPerfSprint() + r := &analysis.Analyzer{ + Name: "perfsprint", + Doc: "Checks that fmt.Sprintf can be replaced with a faster alternative.", + Run: n.run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + } + r.Flags.BoolVar(&n.intConv, "int-conversion", true, "optimizes even if it requires an int or uint type cast") + r.Flags.BoolVar(&n.errError, "err-error", false, "optimizes into err.Error() even if it is only equivalent for non-nil errors") + r.Flags.BoolVar(&n.errorf, "errorf", true, "optimizes fmt.Errorf") + r.Flags.BoolVar(&n.sprintf1, "sprintf1", true, "optimizes fmt.Sprintf with only one argument") + r.Flags.BoolVar(&n.fiximports, "fiximports", true, "fix needed imports from other fixes") + return r +} + +// true if verb is a format string that could be replaced with concatenation. +func isConcatable(verb string) bool { + hasPrefix := + (strings.HasPrefix(verb, "%s") && !strings.Contains(verb, "%[1]s")) || + (strings.HasPrefix(verb, "%[1]s") && !strings.Contains(verb, "%s")) + hasSuffix := + (strings.HasSuffix(verb, "%s") && !strings.Contains(verb, "%[1]s")) || + (strings.HasSuffix(verb, "%[1]s") && !strings.Contains(verb, "%s")) + + return (hasPrefix || hasSuffix) && !(hasPrefix && hasSuffix) +} + +func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { + var fmtSprintObj, fmtSprintfObj, fmtErrorfObj types.Object for _, pkg := range pass.Pkg.Imports() { if pkg.Path() == "fmt" { fmtSprintObj = pkg.Scope().Lookup("Sprint") fmtSprintfObj = pkg.Scope().Lookup("Sprintf") + fmtErrorfObj = pkg.Scope().Lookup("Errorf") } } - if fmtSprintfObj == nil { + if fmtSprintfObj == nil && fmtSprintObj == nil && fmtErrorfObj == nil { return nil, nil } + removedFmtUsages := make(map[string]int) + neededPackages := make(map[string]map[string]bool) insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) nodeFilter := []ast.Node{ @@ -52,11 +96,29 @@ func run(pass *analysis.Pass) (interface{}, error) { err error ) switch { + case calledObj == fmtErrorfObj && len(call.Args) == 1: + if n.errorf { + fn = "fmt.Errorf" + verb = "%s" + value = call.Args[0] + } else { + return + } + case calledObj == fmtSprintObj && len(call.Args) == 1: fn = "fmt.Sprint" verb = "%v" value = call.Args[0] + case calledObj == fmtSprintfObj && len(call.Args) == 1: + if n.sprintf1 { + fn = "fmt.Sprintf" + verb = "%s" + value = call.Args[0] + } else { + return + } + case calledObj == fmtSprintfObj && len(call.Args) == 2: verbLit, ok := call.Args[0].(*ast.BasicLit) if !ok { @@ -67,6 +129,10 @@ func run(pass *analysis.Pass) (interface{}, error) { // Probably unreachable. return } + // one single explicit arg is simplified + if strings.HasPrefix(verb, "%[1]") { + verb = "%" + verb[4:] + } fn = "fmt.Sprintf" value = call.Args[1] @@ -77,6 +143,9 @@ func run(pass *analysis.Pass) (interface{}, error) { switch verb { default: + if fn == "fmt.Sprintf" && isConcatable(verb) { + break + } return case "%d", "%v", "%x", "%t", "%s": } @@ -88,24 +157,52 @@ func run(pass *analysis.Pass) (interface{}, error) { var d *analysis.Diagnostic switch { case isBasicType(valueType, types.String) && oneOf(verb, "%v", "%s"): - d = &analysis.Diagnostic{ - Pos: call.Pos(), - End: call.End(), - Message: fn + " can be replaced with just using the string", - SuggestedFixes: []analysis.SuggestedFix{ - { - Message: "Just use string value", - TextEdits: []analysis.TextEdit{{ - Pos: call.Pos(), - End: call.End(), - NewText: []byte(formatNode(pass.Fset, value)), - }}, + fname := pass.Fset.File(call.Pos()).Name() + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + removedFmtUsages[fname]++ + if fn == "fmt.Errorf" { + neededPackages[fname]["errors"] = true + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: fn + " can be replaced with errors.New", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Use errors.New", + TextEdits: []analysis.TextEdit{{ + Pos: call.Pos(), + End: value.Pos(), + NewText: []byte("errors.New("), + }}, + }, }, - }, + } + } else { + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: fn + " can be replaced with just using the string", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Just use string value", + TextEdits: []analysis.TextEdit{{ + Pos: call.Pos(), + End: call.End(), + NewText: []byte(formatNode(pass.Fset, value)), + }}, + }, + }, + } } - - case types.Implements(valueType, errIface) && oneOf(verb, "%v", "%s"): + case types.Implements(valueType, errIface) && oneOf(verb, "%v", "%s") && n.errError: + // known false positive if this error is nil + // fmt.Sprint(nil) does not panic like nil.Error() does errMethodCall := formatNode(pass.Fset, value) + ".Error()" + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -123,6 +220,13 @@ func run(pass *analysis.Pass) (interface{}, error) { } case isBasicType(valueType, types.Bool) && oneOf(verb, "%v", "%t"): + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -145,6 +249,13 @@ func run(pass *analysis.Pass) (interface{}, error) { return } + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["encoding/hex"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -168,6 +279,13 @@ func run(pass *analysis.Pass) (interface{}, error) { }, } case isSlice && isBasicType(s.Elem(), types.Uint8) && oneOf(verb, "%x"): + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["encoding/hex"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -184,7 +302,14 @@ func run(pass *analysis.Pass) (interface{}, error) { }, } - case isBasicType(valueType, types.Int8, types.Int16, types.Int32) && oneOf(verb, "%v", "%d"): + case isBasicType(valueType, types.Int8, types.Int16, types.Int32) && oneOf(verb, "%v", "%d") && n.intConv: + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -208,6 +333,13 @@ func run(pass *analysis.Pass) (interface{}, error) { }, } case isBasicType(valueType, types.Int) && oneOf(verb, "%v", "%d"): + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -224,6 +356,13 @@ func run(pass *analysis.Pass) (interface{}, error) { }, } case isBasicType(valueType, types.Int64) && oneOf(verb, "%v", "%d"): + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -247,7 +386,18 @@ func run(pass *analysis.Pass) (interface{}, error) { }, } - case isBasicType(valueType, types.Uint8, types.Uint16, types.Uint32, types.Uint) && oneOf(verb, "%v", "%d"): + case isBasicType(valueType, types.Uint8, types.Uint16, types.Uint32, types.Uint) && oneOf(verb, "%v", "%d", "%x") && n.intConv: + base := []byte("), 10") + if verb == "%x" { + base = []byte("), 16") + } + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -264,13 +414,24 @@ func run(pass *analysis.Pass) (interface{}, error) { { Pos: value.End(), End: value.End(), - NewText: []byte("), 10"), + NewText: base, }, }, }, }, } - case isBasicType(valueType, types.Uint64) && oneOf(verb, "%v", "%d"): + case isBasicType(valueType, types.Uint64) && oneOf(verb, "%v", "%d", "%x"): + base := []byte(", 10") + if verb == "%x" { + base = []byte(", 16") + } + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -287,20 +448,123 @@ func run(pass *analysis.Pass) (interface{}, error) { { Pos: value.End(), End: value.End(), - NewText: []byte(", 10"), + NewText: base, }, }, }, }, } + case isBasicType(valueType, types.String) && fn == "fmt.Sprintf" && isConcatable(verb): + var fix string + if strings.HasSuffix(verb, "%s") { + fix = strconv.Quote(verb[:len(verb)-2]) + "+" + formatNode(pass.Fset, value) + } else if strings.HasSuffix(verb, "%[1]s") { + fix = strconv.Quote(verb[:len(verb)-5]) + "+" + formatNode(pass.Fset, value) + } else if strings.HasPrefix(verb, "%s") { + fix = formatNode(pass.Fset, value) + "+" + strconv.Quote(verb[2:]) + } else { + fix = formatNode(pass.Fset, value) + "+" + strconv.Quote(verb[5:]) + } + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: fn + " can be replaced with string addition", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Use string addition", + TextEdits: []analysis.TextEdit{{ + Pos: call.Pos(), + End: call.End(), + NewText: []byte(fix), + }}, + }, + }, + } } if d != nil { - // Need to run goimports to fix using of fmt, strconv or encoding/hex afterwards. pass.Report(*d) } }) + if len(removedFmtUsages) > 0 && n.fiximports { + for _, pkg := range pass.Pkg.Imports() { + if pkg.Path() == "fmt" { + insp = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter = []ast.Node{ + (*ast.SelectorExpr)(nil), + } + insp.Preorder(nodeFilter, func(node ast.Node) { + selec := node.(*ast.SelectorExpr) + selecok, ok := selec.X.(*ast.Ident) + if ok { + pkgname, ok := pass.TypesInfo.ObjectOf(selecok).(*types.PkgName) + if ok && pkgname.Name() == pkg.Name() { + fname := pass.Fset.File(pkgname.Pos()).Name() + removedFmtUsages[fname]-- + } + } + }) + } else if pkg.Path() == "errors" || pkg.Path() == "strconv" || pkg.Path() == "encoding/hex" { + insp = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter = []ast.Node{ + (*ast.ImportSpec)(nil), + } + insp.Preorder(nodeFilter, func(node ast.Node) { + gd := node.(*ast.ImportSpec) + if gd.Path.Value == strconv.Quote(pkg.Path()) { + fname := pass.Fset.File(gd.Pos()).Name() + _, ok := neededPackages[fname] + if ok { + delete(neededPackages[fname], pkg.Path()) + } + } + }) + } + } + insp = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter = []ast.Node{ + (*ast.ImportSpec)(nil), + } + insp.Preorder(nodeFilter, func(node ast.Node) { + gd := node.(*ast.ImportSpec) + if gd.Path.Value == `"fmt"` { + fix := "" + fname := pass.Fset.File(gd.Pos()).Name() + if removedFmtUsages[fname] < 0 { + fix += `"fmt"` + if len(neededPackages[fname]) == 0 { + return + } + } + keys := make([]string, 0, len(neededPackages[fname])) + for k := range neededPackages[fname] { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + fix = fix + "\n\t\"" + k + `"` + } + pass.Report(analysis.Diagnostic{ + Pos: gd.Pos(), + End: gd.End(), + Message: "Fix imports", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Fix imports", + TextEdits: []analysis.TextEdit{{ + Pos: gd.Pos(), + End: gd.End(), + NewText: []byte(fix), + }}, + }, + }}) + } + }) + } + return nil, nil } diff --git a/vendor/github.com/ccojocar/zxcvbn-go/match/match.go b/vendor/github.com/ccojocar/zxcvbn-go/match/match.go index 998dde111..da3e894ec 100644 --- a/vendor/github.com/ccojocar/zxcvbn-go/match/match.go +++ b/vendor/github.com/ccojocar/zxcvbn-go/match/match.go @@ -16,9 +16,8 @@ func (s Matches) Less(i, j int) bool { return true } else if s[i].I == s[j].I { return s[i].J < s[j].J - } else { - return false } + return false } // Match represents different matches diff --git a/vendor/github.com/ccojocar/zxcvbn-go/renovate.json b/vendor/github.com/ccojocar/zxcvbn-go/renovate.json new file mode 100644 index 000000000..58ee1e0ea --- /dev/null +++ b/vendor/github.com/ccojocar/zxcvbn-go/renovate.json @@ -0,0 +1,25 @@ +{ + "dependencyDashboard": true, + "dependencyDashboardTitle" : "Renovate(bot) : dependency dashboard", + "vulnerabilityAlerts": { + "enabled": true + }, + "extends": [ + ":preserveSemverRanges", + "group:all", + "schedule:weekly" + ], + "lockFileMaintenance": { + "commitMessageAction": "Update", + "enabled": true, + "extends": [ + "group:all", + "schedule:weekly" + ] + }, + "postUpdateOptions": [ + "gomodTidy", + "gomodUpdateImportPaths" + ], + "separateMajorMinor": false +} diff --git a/vendor/github.com/ccojocar/zxcvbn-go/scoring/scoring.go b/vendor/github.com/ccojocar/zxcvbn-go/scoring/scoring.go index dbe331884..f25606a8d 100644 --- a/vendor/github.com/ccojocar/zxcvbn-go/scoring/scoring.go +++ b/vendor/github.com/ccojocar/zxcvbn-go/scoring/scoring.go @@ -161,9 +161,8 @@ func displayTime(seconds float64) string { return fmt.Sprintf(formater, (1 + math.Ceil(seconds/month)), "months") } else if seconds < century { return fmt.Sprintf(formater, (1 + math.Ceil(seconds/century)), "years") - } else { - return "centuries" } + return "centuries" } func crackTimeToScore(seconds float64) int { diff --git a/vendor/github.com/daixiang0/gci/pkg/config/config.go b/vendor/github.com/daixiang0/gci/pkg/config/config.go index 98513c056..51f6ccf3b 100644 --- a/vendor/github.com/daixiang0/gci/pkg/config/config.go +++ b/vendor/github.com/daixiang0/gci/pkg/config/config.go @@ -15,6 +15,7 @@ var defaultOrder = map[string]int{ section.CustomType: 2, section.BlankType: 3, section.DotType: 4, + section.AliasType: 5, } type BoolConfig struct { diff --git a/vendor/github.com/daixiang0/gci/pkg/gci/testdata.go b/vendor/github.com/daixiang0/gci/pkg/gci/testdata.go index a48ce356c..77c06dc63 100644 --- a/vendor/github.com/daixiang0/gci/pkg/gci/testdata.go +++ b/vendor/github.com/daixiang0/gci/pkg/gci/testdata.go @@ -1242,4 +1242,37 @@ import ( ) `, }, + { + "alias", + + `sections: + - Standard + - Default + - Alias +`, + `package main + +import ( + testing "github.com/daixiang0/test" + "fmt" + + g "github.com/golang" + + "github.com/daixiang0/gci" + "github.com/daixiang0/gci/subtest" +) +`, + `package main + +import ( + "fmt" + + "github.com/daixiang0/gci" + "github.com/daixiang0/gci/subtest" + + testing "github.com/daixiang0/test" + g "github.com/golang" +) +`, + }, } diff --git a/vendor/github.com/daixiang0/gci/pkg/section/alias.go b/vendor/github.com/daixiang0/gci/pkg/section/alias.go new file mode 100644 index 000000000..423e96acf --- /dev/null +++ b/vendor/github.com/daixiang0/gci/pkg/section/alias.go @@ -0,0 +1,25 @@ +package section + +import ( + "github.com/daixiang0/gci/pkg/parse" + "github.com/daixiang0/gci/pkg/specificity" +) + +type Alias struct{} + +const AliasType = "alias" + +func (b Alias) MatchSpecificity(spec *parse.GciImports) specificity.MatchSpecificity { + if spec.Name != "." && spec.Name != "_" && spec.Name != "" { + return specificity.NameMatch{} + } + return specificity.MisMatch{} +} + +func (b Alias) String() string { + return AliasType +} + +func (b Alias) Type() string { + return AliasType +} diff --git a/vendor/github.com/daixiang0/gci/pkg/section/parser.go b/vendor/github.com/daixiang0/gci/pkg/section/parser.go index 9834dcd13..38435f540 100644 --- a/vendor/github.com/daixiang0/gci/pkg/section/parser.go +++ b/vendor/github.com/daixiang0/gci/pkg/section/parser.go @@ -33,6 +33,8 @@ func Parse(data []string) (SectionList, error) { list = append(list, Dot{}) } else if s == "blank" { list = append(list, Blank{}) + } else if s == "alias" { + list = append(list, Alias{}) } else { errString += fmt.Sprintf(" %s", s) } diff --git a/vendor/github.com/ettle/strcase/.golangci.yml b/vendor/github.com/ettle/strcase/.golangci.yml index 4d31fcc5b..b7ce85d42 100644 --- a/vendor/github.com/ettle/strcase/.golangci.yml +++ b/vendor/github.com/ettle/strcase/.golangci.yml @@ -14,8 +14,6 @@ linters-settings: - ifElseChain - whyNoLint - wrapperFunc - golint: - min-confidence: 0.5 govet: check-shadowing: true lll: @@ -37,7 +35,6 @@ linters: disable-all: true enable: - bodyclose - - deadcode - depguard - dogsled - dupl @@ -47,26 +44,23 @@ linters: - gocyclo - gofmt - goimports - - golint - goprintffuncname - gosec - gosimple - govet - ineffassign - - interfacer - lll - misspell - nakedret - nolintlint + - revive - rowserrcheck - staticcheck - - structcheck - stylecheck - typecheck - unconvert - unparam - unused - - varcheck - whitespace # don't enable: diff --git a/vendor/github.com/ettle/strcase/.readme.tmpl b/vendor/github.com/ettle/strcase/.readme.tmpl index 135765c40..4d7a894f0 100644 --- a/vendor/github.com/ettle/strcase/.readme.tmpl +++ b/vendor/github.com/ettle/strcase/.readme.tmpl @@ -16,10 +16,10 @@ Convert strings to `snake_case`, `camelCase`, `PascalCase`, `kebab-case` and mor ## <a name="pkg-index">Index</a>{{if .Consts}} * [Constants](#pkg-constants){{end}}{{if .Vars}} * [Variables](#pkg-variables){{end}}{{- range .Funcs -}}{{$name_html := html .Name}} -* [{{node_html $ .Decl false | sanitize}}](#{{$name_html}}){{- end}}{{- range .Types}}{{$tname_html := html .Name}} -* [type {{$tname_html}}](#{{$tname_html}}){{- range .Funcs}}{{$name_html := html .Name}} - * [{{node_html $ .Decl false | sanitize}}](#{{$name_html}}){{- end}}{{- range .Methods}}{{$name_html := html .Name}} - * [{{node_html $ .Decl false | sanitize}}](#{{$tname_html}}.{{$name_html}}){{- end}}{{- end}}{{- if $.Notes}}{{- range $marker, $item := $.Notes}} +* [{{node_html $ .Decl false | sanitize}}](#func-{{$name_html}}){{- end}}{{- range .Types}}{{$tname_html := html .Name}} +* [type {{$tname_html}}](#type-{{$tname_html}}){{- range .Funcs}}{{$name_html := html .Name}} + * [{{node_html $ .Decl false | sanitize}}](#func-{{$name_html}}){{- end}}{{- range .Methods}}{{$name_html := html .Name}} + * [{{node_html $ .Decl false | sanitize}}](#type-{{$tname_html}}.{{$name_html}}){{- end}}{{- end}}{{- if $.Notes}}{{- range $marker, $item := $.Notes}} * [{{noteTitle $marker | html}}s](#pkg-note-{{$marker}}){{end}}{{end}} {{if $.Examples}} #### <a name="pkg-examples">Examples</a>{{- range $.Examples}} diff --git a/vendor/github.com/ettle/strcase/Makefile b/vendor/github.com/ettle/strcase/Makefile index 462f8b473..ac98b4aa5 100644 --- a/vendor/github.com/ettle/strcase/Makefile +++ b/vendor/github.com/ettle/strcase/Makefile @@ -1,16 +1,19 @@ .PHONY: benchmark docs lint test docs: - which godoc2ghmd || ( go get github.com/DevotedHealth/godoc2ghmd && go mod tidy ) + which godoc2ghmd || go get github.com/DevotedHealth/godoc2ghmd godoc2ghmd -template .readme.tmpl github.com/ettle/strcase > README.md + go mod tidy test: go test -cover ./... lint: - which golangci-lint || ( go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.27.0 && go mod tidy ) + which golangci-lint || go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.50.1 golangci-lint run golangci-lint run benchmark/*.go + go mod tidy benchmark: - cd benchmark && go test -bench=. -test.benchmem && go mod tidy + cd benchmark && go test -bench=. -test.benchmem + go mod tidy diff --git a/vendor/github.com/ettle/strcase/README.md b/vendor/github.com/ettle/strcase/README.md index ee165e3e5..a984da80d 100644 --- a/vendor/github.com/ettle/strcase/README.md +++ b/vendor/github.com/ettle/strcase/README.md @@ -32,21 +32,24 @@ Example usage strcase.ToSnake("FOOBar") // foo_bar // Support Go initialisms - strcase.ToGoCamel("http_response") // HTTPResponse + strcase.ToGoPascal("http_response") // HTTPResponse // Specify case and delimiter strcase.ToCase("HelloWorld", strcase.UpperCase, '.') // HELLO.WORLD -### Why this package +## Why this package + String strcase is pretty straight forward and there are a number of methods to do it. This package is fully featured, more customizable, better tested, and -faster* than other packages and what you would probably whip up yourself. +faster than other packages and what you would probably whip up yourself. ### Unicode support + We work for with unicode strings and pay very little performance penalty for it as we optimized for the common use case of ASCII only strings. ### Customization + You can create a custom caser that changes the behavior to what you want. This customization also reduces the pressure for us to change the default behavior which means that things are more stable for everyone involved. The goal is to @@ -71,19 +74,22 @@ make the common path easy and fast, while making the uncommon path possible. assert.Equal(t, "http_200", c.ToSnake("http200")) ### Initialism support + By default, we use the golint intialisms list. You can customize and override the initialisms if you wish to add additional ones, such as "SSL" or "CMS" or domain specific ones to your industry. - ToGoCamel("http_response") // HTTPResponse + ToGoPascal("http_response") // HTTPResponse ToGoSnake("http_response") // HTTP_response ### Test coverage + We have a wide ranging test suite to make sure that we understand our behavior. Test coverage isn't everything, but we aim for 100% coverage. ### Fast + Optimized to reduce memory allocations with Builder. Benchmarked and optimized around common cases. @@ -96,56 +102,57 @@ Hopefully I was fair to each library and happy to rerun benchmarks differently or reword my commentary based on suggestions or updates. - // This package - // Go intialisms and custom casers are slower - BenchmarkToTitle-4 992491 1559 ns/op 32 B/op 1 allocs/op - BenchmarkToSnake-4 1000000 1475 ns/op 32 B/op 1 allocs/op - BenchmarkToSNAKE-4 1000000 1609 ns/op 32 B/op 1 allocs/op - BenchmarkToGoSnake-4 275010 3697 ns/op 44 B/op 4 allocs/op - BenchmarkToCustomCaser-4 342704 4191 ns/op 56 B/op 4 allocs/op + // This package - faster then almost all libraries + // Initialisms are more complicated and slightly slower, but still fast + BenchmarkToTitle-96 9617142 125.7 ns/op 16 B/op 1 allocs/op + BenchmarkToSnake-96 10659919 120.7 ns/op 16 B/op 1 allocs/op + BenchmarkToSNAKE-96 9018282 126.4 ns/op 16 B/op 1 allocs/op + BenchmarkToGoSnake-96 4903687 254.5 ns/op 26 B/op 4 allocs/op + BenchmarkToCustomCaser-96 4434489 265.0 ns/op 28 B/op 4 allocs/op // Segment has very fast snake case and camel case libraries // No features or customization, but very very fast - BenchmarkSegment-4 1303809 938 ns/op 16 B/op 1 allocs/op + BenchmarkSegment-96 33625734 35.54 ns/op 16 B/op 1 allocs/op - // Stdlib strings.Title for comparison, even though it only splits on spaces - BenchmarkToTitleStrings-4 1213467 1164 ns/op 16 B/op 1 allocs/op + // Iancoleman has gotten some performance improvements, but remains + // without unicode support and lacks fine-grained customization + BenchmarkToSnakeIan-96 13141522 92.99 ns/op 16 B/op 1 allocs/op + + // Stdlib strings.Title is deprecated; using golang.org/x.text + BenchmarkGolangOrgXTextCases-96 4665676 262.5 ns/op 272 B/op 2 allocs/op // Other libraries or code snippets // - Most are slower, by up to an order of magnitude - // - None support initialisms or customization + // - No support for initialisms or customization // - Some generate only camelCase or snake_case // - Many lack unicode support - BenchmarkToSnakeStoewer-4 973200 2075 ns/op 64 B/op 2 allocs/op + BenchmarkToSnakeStoewer-96 8095468 148.9 ns/op 64 B/op 2 allocs/op // Copying small rune arrays is slow - BenchmarkToSnakeSiongui-4 264315 4229 ns/op 48 B/op 10 allocs/op - BenchmarkGoValidator-4 206811 5152 ns/op 184 B/op 9 allocs/op + BenchmarkToSnakeSiongui-96 2912593 401.7 ns/op 112 B/op 19 allocs/op + BenchmarkGoValidator-96 3493800 342.6 ns/op 184 B/op 9 allocs/op // String alloction is slow - BenchmarkToSnakeFatih-4 82675 12280 ns/op 392 B/op 26 allocs/op - BenchmarkToSnakeIanColeman-4 83276 13903 ns/op 145 B/op 13 allocs/op + BenchmarkToSnakeFatih-96 1282648 945.1 ns/op 616 B/op 26 allocs/op // Regexp is slow - BenchmarkToSnakeGolangPrograms-4 74448 18586 ns/op 176 B/op 11 allocs/op + BenchmarkToSnakeGolangPrograms-96 778674 1495 ns/op 227 B/op 11 allocs/op // These results aren't a surprise - my initial version of this library was // painfully slow. I think most of us, without spending some time with // profilers and benchmarks, would write also something on the slower side. -### Why not this package +### Zero dependencies + +That's right - zero. We only import the Go standard library. No hassles with +dependencies, licensing, security alerts. + +## Why not this package + If every nanosecond matters and this is used in a tight loop, use segment.io's libraries (<a href="https://github.com/segmentio/go-snakecase">https://github.com/segmentio/go-snakecase</a> and <a href="https://github.com/segmentio/go-camelcase">https://github.com/segmentio/go-camelcase</a>). They lack features, but make up for -it by being blazing fast. Alternatively, if you need your code to work slightly -differently, fork them and tailor it for your use case. - -If you don't like having external imports, I get it. This package only imports -packages for testing, otherwise it only uses the standard library. If that's -not enough, you can use this repo as the foundation for your own. MIT Licensed. +it by being blazing fast. -This package is still relatively new and while I've used it for a while -personally, it doesn't have the miles that other packages do. I've tested this -code agains't their test cases to make sure that there aren't any surprises. +## Migrating from other packages -### Migrating from other packages If you are migrating from from another package, you may find slight differences in output. To reduce the delta, you may find it helpful to use the following custom casers to mimic the behavior of the other package. @@ -161,32 +168,32 @@ custom casers to mimic the behavior of the other package. ## <a name="pkg-index">Index</a> -* [func ToCamel(s string) string](#ToCamel) -* [func ToCase(s string, wordCase WordCase, delimiter rune) string](#ToCase) -* [func ToGoCamel(s string) string](#ToGoCamel) -* [func ToGoCase(s string, wordCase WordCase, delimiter rune) string](#ToGoCase) -* [func ToGoKebab(s string) string](#ToGoKebab) -* [func ToGoPascal(s string) string](#ToGoPascal) -* [func ToGoSnake(s string) string](#ToGoSnake) -* [func ToKEBAB(s string) string](#ToKEBAB) -* [func ToKebab(s string) string](#ToKebab) -* [func ToPascal(s string) string](#ToPascal) -* [func ToSNAKE(s string) string](#ToSNAKE) -* [func ToSnake(s string) string](#ToSnake) -* [type Caser](#Caser) - * [func NewCaser(goInitialisms bool, initialismOverrides map[string]bool, splitFn SplitFn) *Caser](#NewCaser) - * [func (c *Caser) ToCamel(s string) string](#Caser.ToCamel) - * [func (c *Caser) ToCase(s string, wordCase WordCase, delimiter rune) string](#Caser.ToCase) - * [func (c *Caser) ToKEBAB(s string) string](#Caser.ToKEBAB) - * [func (c *Caser) ToKebab(s string) string](#Caser.ToKebab) - * [func (c *Caser) ToPascal(s string) string](#Caser.ToPascal) - * [func (c *Caser) ToSNAKE(s string) string](#Caser.ToSNAKE) - * [func (c *Caser) ToSnake(s string) string](#Caser.ToSnake) -* [type SplitAction](#SplitAction) -* [type SplitFn](#SplitFn) - * [func NewSplitFn(delimiters []rune, splitOptions ...SplitOption) SplitFn](#NewSplitFn) -* [type SplitOption](#SplitOption) -* [type WordCase](#WordCase) +* [func ToCamel(s string) string](#func-ToCamel) +* [func ToCase(s string, wordCase WordCase, delimiter rune) string](#func-ToCase) +* [func ToGoCamel(s string) string](#func-ToGoCamel) +* [func ToGoCase(s string, wordCase WordCase, delimiter rune) string](#func-ToGoCase) +* [func ToGoKebab(s string) string](#func-ToGoKebab) +* [func ToGoPascal(s string) string](#func-ToGoPascal) +* [func ToGoSnake(s string) string](#func-ToGoSnake) +* [func ToKEBAB(s string) string](#func-ToKEBAB) +* [func ToKebab(s string) string](#func-ToKebab) +* [func ToPascal(s string) string](#func-ToPascal) +* [func ToSNAKE(s string) string](#func-ToSNAKE) +* [func ToSnake(s string) string](#func-ToSnake) +* [type Caser](#type-Caser) + * [func NewCaser(goInitialisms bool, initialismOverrides map[string]bool, splitFn SplitFn) *Caser](#func-NewCaser) + * [func (c *Caser) ToCamel(s string) string](#type-Caser.ToCamel) + * [func (c *Caser) ToCase(s string, wordCase WordCase, delimiter rune) string](#type-Caser.ToCase) + * [func (c *Caser) ToKEBAB(s string) string](#type-Caser.ToKEBAB) + * [func (c *Caser) ToKebab(s string) string](#type-Caser.ToKebab) + * [func (c *Caser) ToPascal(s string) string](#type-Caser.ToPascal) + * [func (c *Caser) ToSNAKE(s string) string](#type-Caser.ToSNAKE) + * [func (c *Caser) ToSnake(s string) string](#type-Caser.ToSnake) +* [type SplitAction](#type-SplitAction) +* [type SplitFn](#type-SplitFn) + * [func NewSplitFn(delimiters []rune, splitOptions ...SplitOption) SplitFn](#func-NewSplitFn) +* [type SplitOption](#type-SplitOption) +* [type WordCase](#type-WordCase) @@ -201,7 +208,7 @@ Also known as lowerCamelCase or mixedCase. -## <a name="ToCase">func</a> [ToCase](./strcase.go#L70) +## <a name="ToCase">func</a> [ToCase](./strcase.go#L72) ``` go func ToCase(s string, wordCase WordCase, delimiter rune) string ``` @@ -209,18 +216,20 @@ ToCase returns words in given case and delimiter. -## <a name="ToGoCamel">func</a> [ToGoCamel](./strcase.go#L65) +## <a name="ToGoCamel">func</a> [ToGoCamel](./strcase.go#L67) ``` go func ToGoCamel(s string) string ``` ToGoCamel returns words in camelCase (capitalized words concatenated together, with first word lower case). Also known as lowerCamelCase or mixedCase. -Respects Go's common initialisms (e.g. httpResponse -> HTTPResponse). +Respects Go's common initialisms, but first word remains lowercased which is +important for code generator use cases (e.g. toJson -> toJSON, httpResponse +-> httpResponse). -## <a name="ToGoCase">func</a> [ToGoCase](./strcase.go#L77) +## <a name="ToGoCase">func</a> [ToGoCase](./strcase.go#L79) ``` go func ToGoCase(s string, wordCase WordCase, delimiter rune) string ``` @@ -415,7 +424,7 @@ ToSnake returns words in snake_case (lower case words with underscores). -## <a name="SplitAction">type</a> [SplitAction](./split.go#L110) +## <a name="SplitAction">type</a> [SplitAction](./split.go#L111) ``` go type SplitAction int ``` @@ -457,7 +466,7 @@ SplitFn defines how to split a string into words -### <a name="NewSplitFn">func</a> [NewSplitFn](./split.go#L14-L17) +### <a name="NewSplitFn">func</a> [NewSplitFn](./split.go#L15-L18) ``` go func NewSplitFn( delimiters []rune, @@ -469,13 +478,12 @@ NewSplitFn returns a SplitFn based on the options provided. NewSplitFn covers the majority of common options that other strcase libraries provide and should allow you to simply create a custom caser. For more complicated use cases, feel free to write your own SplitFn -nolint:gocyclo -## <a name="SplitOption">type</a> [SplitOption](./split.go#L93) +## <a name="SplitOption">type</a> [SplitOption](./split.go#L94) ``` go type SplitOption int ``` @@ -524,6 +532,9 @@ const ( // TitleCase - Only first letter upper cased (Example) TitleCase // CamelCase - TitleCase except lower case first word (exampleText) + // Notably, even if the first word is an initialism, it will be lower + // cased. This is important for code generators where capital letters + // mean exported functions. i.e. jsonString(), not JSONString() CamelCase ) ``` diff --git a/vendor/github.com/ettle/strcase/assert.go b/vendor/github.com/ettle/strcase/assert.go new file mode 100644 index 000000000..09344e40f --- /dev/null +++ b/vendor/github.com/ettle/strcase/assert.go @@ -0,0 +1,24 @@ +package strcase + +// We use a lightweight replacement for testify/assert to reduce dependencies + +// testingT interface allows us to test our assert functions +type testingT interface { + Logf(format string, args ...interface{}) + Fail() +} + +// assertTrue will fail if the value is not true +func assertTrue(t testingT, value bool) { + if !value { + t.Fail() + } +} + +// assertEqual will fail if the two strings are not equal +func assertEqual(t testingT, expected, actual string) { + if expected != actual { + t.Logf("Expected: %s Actual: %s", expected, actual) + t.Fail() + } +} diff --git a/vendor/github.com/ettle/strcase/caser.go b/vendor/github.com/ettle/strcase/caser.go index 891a67189..2e7eb955b 100644 --- a/vendor/github.com/ettle/strcase/caser.go +++ b/vendor/github.com/ettle/strcase/caser.go @@ -10,17 +10,17 @@ type Caser struct { // // A Caser should be created when you want fine grained control over how the words are split. // -// Notes on function arguments +// Notes on function arguments // -// goInitialisms: Whether to use Golint's intialisms +// goInitialisms: Whether to use Golint's intialisms // -// initialismOverrides: A mapping of extra initialisms -// Keys must be in ALL CAPS. Merged with Golint's if goInitialisms is set. -// Setting a key to false will override Golint's. +// initialismOverrides: A mapping of extra initialisms +// Keys must be in ALL CAPS. Merged with Golint's if goInitialisms is set. +// Setting a key to false will override Golint's. // -// splitFn: How to separate words -// Override the default split function. Consider using NewSplitFn to -// configure one instead of writing your own. +// splitFn: How to separate words +// Override the default split function. Consider using NewSplitFn to +// configure one instead of writing your own. func NewCaser(goInitialisms bool, initialismOverrides map[string]bool, splitFn SplitFn) *Caser { c := &Caser{ initialisms: golintInitialisms, diff --git a/vendor/github.com/ettle/strcase/convert.go b/vendor/github.com/ettle/strcase/convert.go index 70fedb144..cb901d079 100644 --- a/vendor/github.com/ettle/strcase/convert.go +++ b/vendor/github.com/ettle/strcase/convert.go @@ -29,6 +29,7 @@ const ( // Case 2: UpperCase words, which don't need to support initialisms since everything is in upper case // convertWithoutInitialims only works for to UpperCase and LowerCase +// //nolint:gocyclo func convertWithoutInitialisms(input string, delimiter rune, wordCase WordCase) string { input = strings.TrimSpace(input) @@ -38,7 +39,7 @@ func convertWithoutInitialisms(input string, delimiter rune, wordCase WordCase) } var b strings.Builder - b.Grow(len(input) * 2) // In case we need to write delimiters where they weren't before + b.Grow(len(input) + 4) // In case we need to write delimiters where they weren't before var prev, curr rune next := runes[0] // 0 length will have already returned so safe to index @@ -90,13 +91,14 @@ func convertWithoutInitialisms(input string, delimiter rune, wordCase WordCase) // Must be original case b.WriteRune(curr) } - inWord = inWord || true + inWord = true } return b.String() } // convertWithGoInitialisms changes a input string to a certain case with a // delimiter, respecting go initialisms but not skip runes +// //nolint:gocyclo func convertWithGoInitialisms(input string, delimiter rune, wordCase WordCase) string { input = strings.TrimSpace(input) @@ -106,7 +108,7 @@ func convertWithGoInitialisms(input string, delimiter rune, wordCase WordCase) s } var b strings.Builder - b.Grow(len(input) * 2) // In case we need to write delimiters where they weren't before + b.Grow(len(input) + 4) // In case we need to write delimiters where they weren't before firstWord := true @@ -122,10 +124,15 @@ func convertWithGoInitialisms(input string, delimiter rune, wordCase WordCase) s // Don't bother with initialisms if the word is longer than 5 // A quick proxy to avoid the extra memory allocations if end-start <= 5 { - key := strings.ToUpper(string(runes[start:end])) - if golintInitialisms[key] { + var word strings.Builder + word.Grow(end - start) + for i := start; i < end; i++ { + word.WriteRune(toUpper(runes[i])) + } + w := word.String() + if golintInitialisms[w] { if !firstWord || wordCase != CamelCase { - b.WriteString(key) + b.WriteString(w) firstWord = false return } @@ -188,6 +195,7 @@ func convertWithGoInitialisms(input string, delimiter rune, wordCase WordCase) s // convert changes a input string to a certain case with a delimiter, // respecting arbitrary initialisms and skip characters +// //nolint:gocyclo func convert(input string, fn SplitFn, delimiter rune, wordCase WordCase, initialisms map[string]bool) string { @@ -198,7 +206,7 @@ func convert(input string, fn SplitFn, delimiter rune, wordCase WordCase, } var b strings.Builder - b.Grow(len(input) * 2) // In case we need to write delimiters where they weren't before + b.Grow(len(input) + 4) // In case we need to write delimiters where they weren't before firstWord := true var skipIndexes []int @@ -221,13 +229,14 @@ func convert(input string, fn SplitFn, delimiter rune, wordCase WordCase, // I'm open to it if there is a use case if initialisms != nil { var word strings.Builder + word.Grow(end - start) for i := start; i < end; i++ { word.WriteRune(toUpper(runes[i])) } - key := word.String() - if initialisms[key] { + w := word.String() + if initialisms[w] { if !firstWord || wordCase != CamelCase { - b.WriteString(key) + b.WriteString(w) firstWord = false return } diff --git a/vendor/github.com/ettle/strcase/doc.go b/vendor/github.com/ettle/strcase/doc.go index b898a4e45..c3bf14a8f 100644 --- a/vendor/github.com/ettle/strcase/doc.go +++ b/vendor/github.com/ettle/strcase/doc.go @@ -2,78 +2,78 @@ Package strcase is a package for converting strings into various word cases (e.g. snake_case, camelCase) - go get -u github.com/ettle/strcase + go get -u github.com/ettle/strcase Example usage - strcase.ToSnake("Hello World") // hello_world - strcase.ToSNAKE("Hello World") // HELLO_WORLD + strcase.ToSnake("Hello World") // hello_world + strcase.ToSNAKE("Hello World") // HELLO_WORLD - strcase.ToKebab("helloWorld") // hello-world - strcase.ToKEBAB("helloWorld") // HELLO-WORLD + strcase.ToKebab("helloWorld") // hello-world + strcase.ToKEBAB("helloWorld") // HELLO-WORLD - strcase.ToPascal("hello-world") // HelloWorld - strcase.ToCamel("hello-world") // helloWorld + strcase.ToPascal("hello-world") // HelloWorld + strcase.ToCamel("hello-world") // helloWorld - // Handle odd cases - strcase.ToSnake("FOOBar") // foo_bar + // Handle odd cases + strcase.ToSnake("FOOBar") // foo_bar - // Support Go initialisms - strcase.ToGoPascal("http_response") // HTTPResponse + // Support Go initialisms + strcase.ToGoPascal("http_response") // HTTPResponse - // Specify case and delimiter - strcase.ToCase("HelloWorld", strcase.UpperCase, '.') // HELLO.WORLD + // Specify case and delimiter + strcase.ToCase("HelloWorld", strcase.UpperCase, '.') // HELLO.WORLD -Why this package +## Why this package String strcase is pretty straight forward and there are a number of methods to do it. This package is fully featured, more customizable, better tested, and -faster* than other packages and what you would probably whip up yourself. +faster than other packages and what you would probably whip up yourself. -Unicode support +### Unicode support We work for with unicode strings and pay very little performance penalty for it as we optimized for the common use case of ASCII only strings. -Customization +### Customization You can create a custom caser that changes the behavior to what you want. This customization also reduces the pressure for us to change the default behavior which means that things are more stable for everyone involved. The goal is to make the common path easy and fast, while making the uncommon path possible. - c := NewCaser( - // Use Go's default initialisms e.g. ID, HTML - true, - // Override initialisms (e.g. don't initialize HTML but initialize SSL - map[string]bool{"SSL": true, "HTML": false}, - // Write your own custom SplitFn - // - NewSplitFn( - []rune{'*', '.', ','}, - SplitCase, - SplitAcronym, - PreserveNumberFormatting, - SplitBeforeNumber, - SplitAfterNumber, - )) - assert.Equal(t, "http_200", c.ToSnake("http200")) - -Initialism support + c := NewCaser( + // Use Go's default initialisms e.g. ID, HTML + true, + // Override initialisms (e.g. don't initialize HTML but initialize SSL + map[string]bool{"SSL": true, "HTML": false}, + // Write your own custom SplitFn + // + NewSplitFn( + []rune{'*', '.', ','}, + SplitCase, + SplitAcronym, + PreserveNumberFormatting, + SplitBeforeNumber, + SplitAfterNumber, + )) + assert.Equal(t, "http_200", c.ToSnake("http200")) + +### Initialism support By default, we use the golint intialisms list. You can customize and override the initialisms if you wish to add additional ones, such as "SSL" or "CMS" or domain specific ones to your industry. - ToGoPascal("http_response") // HTTPResponse - ToGoSnake("http_response") // HTTP_response + ToGoPascal("http_response") // HTTPResponse + ToGoSnake("http_response") // HTTP_response -Test coverage +### Test coverage We have a wide ranging test suite to make sure that we understand our behavior. Test coverage isn't everything, but we aim for 100% coverage. -Fast +### Fast Optimized to reduce memory allocations with Builder. Benchmarked and optimized around common cases. @@ -86,70 +86,65 @@ common cases have a large performance impact. Hopefully I was fair to each library and happy to rerun benchmarks differently or reword my commentary based on suggestions or updates. - // This package - faster then almost all libraries - // Initialisms are more complicated and slightly slower, but still faster then other libraries that do less - BenchmarkToTitle-4 7821166 221 ns/op 32 B/op 1 allocs/op - BenchmarkToSnake-4 9378589 202 ns/op 32 B/op 1 allocs/op - BenchmarkToSNAKE-4 6174453 223 ns/op 32 B/op 1 allocs/op - BenchmarkToGoSnake-4 3114266 434 ns/op 44 B/op 4 allocs/op - BenchmarkToCustomCaser-4 2973855 448 ns/op 56 B/op 4 allocs/op - - // Segment has very fast snake case and camel case libraries - // No features or customization, but very very fast - BenchmarkSegment-4 24003495 64.9 ns/op 16 B/op 1 allocs/op - - // Stdlib strings.Title for comparison, even though it only splits on spaces - BenchmarkToTitleStrings-4 11259376 161 ns/op 16 B/op 1 allocs/op - - // Other libraries or code snippets - // - Most are slower, by up to an order of magnitude - // - None support initialisms or customization - // - Some generate only camelCase or snake_case - // - Many lack unicode support - BenchmarkToSnakeStoewer-4 7103268 297 ns/op 64 B/op 2 allocs/op - // Copying small rune arrays is slow - BenchmarkToSnakeSiongui-4 3710768 413 ns/op 48 B/op 10 allocs/op - BenchmarkGoValidator-4 2416479 1049 ns/op 184 B/op 9 allocs/op - // String alloction is slow - BenchmarkToSnakeFatih-4 1000000 2407 ns/op 624 B/op 26 allocs/op - BenchmarkToSnakeIanColeman-4 1005766 1426 ns/op 160 B/op 13 allocs/op - // Regexp is slow - BenchmarkToSnakeGolangPrograms-4 614689 2237 ns/op 225 B/op 11 allocs/op - - - - // These results aren't a surprise - my initial version of this library was - // painfully slow. I think most of us, without spending some time with - // profilers and benchmarks, would write also something on the slower side. - - -Why not this package + // This package - faster then almost all libraries + // Initialisms are more complicated and slightly slower, but still fast + BenchmarkToTitle-96 9617142 125.7 ns/op 16 B/op 1 allocs/op + BenchmarkToSnake-96 10659919 120.7 ns/op 16 B/op 1 allocs/op + BenchmarkToSNAKE-96 9018282 126.4 ns/op 16 B/op 1 allocs/op + BenchmarkToGoSnake-96 4903687 254.5 ns/op 26 B/op 4 allocs/op + BenchmarkToCustomCaser-96 4434489 265.0 ns/op 28 B/op 4 allocs/op + + // Segment has very fast snake case and camel case libraries + // No features or customization, but very very fast + BenchmarkSegment-96 33625734 35.54 ns/op 16 B/op 1 allocs/op + + // Iancoleman has gotten some performance improvements, but remains + // without unicode support and lacks fine-grained customization + BenchmarkToSnakeIan-96 13141522 92.99 ns/op 16 B/op 1 allocs/op + + // Stdlib strings.Title is deprecated; using golang.org/x.text + BenchmarkGolangOrgXTextCases-96 4665676 262.5 ns/op 272 B/op 2 allocs/op + + // Other libraries or code snippets + // - Most are slower, by up to an order of magnitude + // - No support for initialisms or customization + // - Some generate only camelCase or snake_case + // - Many lack unicode support + BenchmarkToSnakeStoewer-96 8095468 148.9 ns/op 64 B/op 2 allocs/op + // Copying small rune arrays is slow + BenchmarkToSnakeSiongui-96 2912593 401.7 ns/op 112 B/op 19 allocs/op + BenchmarkGoValidator-96 3493800 342.6 ns/op 184 B/op 9 allocs/op + // String alloction is slow + BenchmarkToSnakeFatih-96 1282648 945.1 ns/op 616 B/op 26 allocs/op + // Regexp is slow + BenchmarkToSnakeGolangPrograms-96 778674 1495 ns/op 227 B/op 11 allocs/op + + // These results aren't a surprise - my initial version of this library was + // painfully slow. I think most of us, without spending some time with + // profilers and benchmarks, would write also something on the slower side. + +### Zero dependencies + +That's right - zero. We only import the Go standard library. No hassles with +dependencies, licensing, security alerts. + +## Why not this package If every nanosecond matters and this is used in a tight loop, use segment.io's libraries (https://github.com/segmentio/go-snakecase and https://github.com/segmentio/go-camelcase). They lack features, but make up for -it by being blazing fast. Alternatively, if you need your code to work slightly -differently, fork them and tailor it for your use case. - -If you don't like having external imports, I get it. This package only imports -packages for testing, otherwise it only uses the standard library. If that's -not enough, you can use this repo as the foundation for your own. MIT Licensed. +it by being blazing fast. -This package is still relatively new and while I've used it for a while -personally, it doesn't have the miles that other packages do. I've tested this -code agains't their test cases to make sure that there aren't any surprises. - -Migrating from other packages +## Migrating from other packages If you are migrating from from another package, you may find slight differences in output. To reduce the delta, you may find it helpful to use the following custom casers to mimic the behavior of the other package. - // From https://github.com/iancoleman/strcase - var c = NewCaser(false, nil, NewSplitFn([]rune{'_', '-', '.'}, SplitCase, SplitAcronym, SplitBeforeNumber)) - - // From https://github.com/stoewer/go-strcase - var c = NewCaser(false, nil, NewSplitFn([]rune{'_', '-'}, SplitCase), SplitAcronym) + // From https://github.com/iancoleman/strcase + var c = NewCaser(false, nil, NewSplitFn([]rune{'_', '-', '.'}, SplitCase, SplitAcronym, SplitBeforeNumber)) + // From https://github.com/stoewer/go-strcase + var c = NewCaser(false, nil, NewSplitFn([]rune{'_', '-'}, SplitCase), SplitAcronym) */ package strcase diff --git a/vendor/github.com/ettle/strcase/split.go b/vendor/github.com/ettle/strcase/split.go index 84381106b..32bc29759 100644 --- a/vendor/github.com/ettle/strcase/split.go +++ b/vendor/github.com/ettle/strcase/split.go @@ -10,6 +10,7 @@ type SplitFn func(prev, curr, next rune) SplitAction // NewSplitFn covers the majority of common options that other strcase // libraries provide and should allow you to simply create a custom caser. // For more complicated use cases, feel free to write your own SplitFn +// //nolint:gocyclo func NewSplitFn( delimiters []rune, diff --git a/vendor/github.com/fatih/color/color.go b/vendor/github.com/fatih/color/color.go index 889f9e77b..c4234287d 100644 --- a/vendor/github.com/fatih/color/color.go +++ b/vendor/github.com/fatih/color/color.go @@ -65,6 +65,29 @@ const ( CrossedOut ) +const ( + ResetBold Attribute = iota + 22 + ResetItalic + ResetUnderline + ResetBlinking + _ + ResetReversed + ResetConcealed + ResetCrossedOut +) + +var mapResetAttributes map[Attribute]Attribute = map[Attribute]Attribute{ + Bold: ResetBold, + Faint: ResetBold, + Italic: ResetItalic, + Underline: ResetUnderline, + BlinkSlow: ResetBlinking, + BlinkRapid: ResetBlinking, + ReverseVideo: ResetReversed, + Concealed: ResetConcealed, + CrossedOut: ResetCrossedOut, +} + // Foreground text colors const ( FgBlack Attribute = iota + 30 @@ -246,10 +269,7 @@ func (c *Color) Printf(format string, a ...interface{}) (n int, err error) { // On Windows, users should wrap w with colorable.NewColorable() if w is of // type *os.File. func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) { - c.SetWriter(w) - defer c.UnsetWriter(w) - - return fmt.Fprintln(w, a...) + return fmt.Fprintln(w, c.wrap(fmt.Sprint(a...))) } // Println formats using the default formats for its operands and writes to @@ -258,10 +278,7 @@ func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) { // encountered. This is the standard fmt.Print() method wrapped with the given // color. func (c *Color) Println(a ...interface{}) (n int, err error) { - c.Set() - defer c.unset() - - return fmt.Fprintln(Output, a...) + return fmt.Fprintln(Output, c.wrap(fmt.Sprint(a...))) } // Sprint is just like Print, but returns a string instead of printing it. @@ -271,7 +288,7 @@ func (c *Color) Sprint(a ...interface{}) string { // Sprintln is just like Println, but returns a string instead of printing it. func (c *Color) Sprintln(a ...interface{}) string { - return c.wrap(fmt.Sprintln(a...)) + return fmt.Sprintln(c.Sprint(a...)) } // Sprintf is just like Printf, but returns a string instead of printing it. @@ -353,7 +370,7 @@ func (c *Color) SprintfFunc() func(format string, a ...interface{}) string { // string. Windows users should use this in conjunction with color.Output. func (c *Color) SprintlnFunc() func(a ...interface{}) string { return func(a ...interface{}) string { - return c.wrap(fmt.Sprintln(a...)) + return fmt.Sprintln(c.Sprint(a...)) } } @@ -383,7 +400,18 @@ func (c *Color) format() string { } func (c *Color) unformat() string { - return fmt.Sprintf("%s[%dm", escape, Reset) + //return fmt.Sprintf("%s[%dm", escape, Reset) + //for each element in sequence let's use the speficic reset escape, ou the generic one if not found + format := make([]string, len(c.params)) + for i, v := range c.params { + format[i] = strconv.Itoa(int(Reset)) + ra, ok := mapResetAttributes[v] + if ok { + format[i] = strconv.Itoa(int(ra)) + } + } + + return fmt.Sprintf("%s[%sm", escape, strings.Join(format, ";")) } // DisableColor disables the color output. Useful to not change any existing @@ -411,6 +439,12 @@ func (c *Color) isNoColorSet() bool { // Equals returns a boolean value indicating whether two colors are equal. func (c *Color) Equals(c2 *Color) bool { + if c == nil && c2 == nil { + return true + } + if c == nil || c2 == nil { + return false + } if len(c.params) != len(c2.params) { return false } diff --git a/vendor/github.com/ghostiam/protogetter/Makefile b/vendor/github.com/ghostiam/protogetter/Makefile index af4b62bdf..4c2a62af1 100644 --- a/vendor/github.com/ghostiam/protogetter/Makefile +++ b/vendor/github.com/ghostiam/protogetter/Makefile @@ -1,6 +1,6 @@ .PHONY: test test: - cd testdata && make vendor + $(MAKE) -C testdata vendor go test -v ./... .PHONY: install diff --git a/vendor/github.com/ghostiam/protogetter/processor.go b/vendor/github.com/ghostiam/protogetter/processor.go index 445f136b8..d65199dd2 100644 --- a/vendor/github.com/ghostiam/protogetter/processor.go +++ b/vendor/github.com/ghostiam/protogetter/processor.go @@ -12,16 +12,18 @@ import ( type processor struct { info *types.Info filter *PosFilter + cfg *Config to strings.Builder from strings.Builder err error } -func Process(info *types.Info, filter *PosFilter, n ast.Node) (*Result, error) { +func Process(info *types.Info, filter *PosFilter, n ast.Node, cfg *Config) (*Result, error) { p := &processor{ info: info, filter: filter, + cfg: cfg, } return p.process(n) @@ -31,8 +33,12 @@ func (c *processor) process(n ast.Node) (*Result, error) { switch x := n.(type) { case *ast.AssignStmt: // Skip any assignment to the field. - for _, lhs := range x.Lhs { - c.filter.AddPos(lhs.Pos()) + for _, s := range x.Lhs { + c.filter.AddPos(s.Pos()) + + if se, ok := s.(*ast.StarExpr); ok { + c.filter.AddPos(se.X.Pos()) + } } case *ast.IncDecStmt: @@ -47,6 +53,14 @@ func (c *processor) process(n ast.Node) (*Result, error) { } case *ast.CallExpr: + if !c.cfg.ReplaceFirstArgInAppend && len(x.Args) > 0 { + if v, ok := x.Fun.(*ast.Ident); ok && v.Name == "append" { + // Skip first argument of append function. + c.filter.AddPos(x.Args[0].Pos()) + break + } + } + f, ok := x.Fun.(*ast.SelectorExpr) if !ok { return &Result{}, nil @@ -66,6 +80,67 @@ func (c *processor) process(n ast.Node) (*Result, error) { c.processInner(x) + case *ast.StarExpr: + f, ok := x.X.(*ast.SelectorExpr) + if !ok { + return &Result{}, nil + } + + if !isProtoMessage(c.info, f.X) { + return &Result{}, nil + } + + // proto2 generates fields as pointers. Hence, the indirection + // must be removed when generating the fix for the case. + // The `*` is retained in `c.from`, but excluded from the fix + // present in the `c.to`. + c.writeFrom("*") + c.processInner(x.X) + + case *ast.BinaryExpr: + // Check if the expression is a comparison. + if x.Op != token.EQL && x.Op != token.NEQ { + return &Result{}, nil + } + + // Check if one of the operands is nil. + + xIdent, xOk := x.X.(*ast.Ident) + yIdent, yOk := x.Y.(*ast.Ident) + + xIsNil := xOk && xIdent.Name == "nil" + yIsNil := yOk && yIdent.Name == "nil" + + if !xIsNil && !yIsNil { + return &Result{}, nil + } + + // Extract the non-nil operand for further checks + + var expr ast.Expr + if xIsNil { + expr = x.Y + } else { + expr = x.X + } + + se, ok := expr.(*ast.SelectorExpr) + if !ok { + return &Result{}, nil + } + + if !isProtoMessage(c.info, se.X) { + return &Result{}, nil + } + + // Check if the Getter function of the protobuf message returns a pointer. + hasPointer, ok := getterResultHasPointer(c.info, se.X, se.Sel.Name) + if !ok || hasPointer { + return &Result{}, nil + } + + c.filter.AddPos(x.X.Pos()) + default: return nil, fmt.Errorf("not implemented for type: %s (%s)", reflect.TypeOf(x), formatNode(n)) } @@ -204,14 +279,14 @@ func isProtoMessage(info *types.Info, expr ast.Expr) bool { return false } -func methodIsExists(info *types.Info, x ast.Expr, name string) bool { +func typesNamed(info *types.Info, x ast.Expr) (*types.Named, bool) { if info == nil { - return false + return nil, false } t := info.TypeOf(x) if t == nil { - return false + return nil, false } ptr, ok := t.Underlying().(*types.Pointer) @@ -221,6 +296,15 @@ func methodIsExists(info *types.Info, x ast.Expr, name string) bool { named, ok := t.(*types.Named) if !ok { + return nil, false + } + + return named, true +} + +func methodIsExists(info *types.Info, x ast.Expr, name string) bool { + named, ok := typesNamed(info, x) + if !ok { return false } @@ -232,3 +316,38 @@ func methodIsExists(info *types.Info, x ast.Expr, name string) bool { return false } + +func getterResultHasPointer(info *types.Info, x ast.Expr, name string) (hasPointer, ok bool) { + named, ok := typesNamed(info, x) + if !ok { + return false, false + } + + for i := 0; i < named.NumMethods(); i++ { + method := named.Method(i) + if method.Name() != "Get"+name { + continue + } + + var sig *types.Signature + sig, ok = method.Type().(*types.Signature) + if !ok { + return false, false + } + + results := sig.Results() + if results.Len() == 0 { + return false, false + } + + firstType := results.At(0) + _, ok = firstType.Type().(*types.Pointer) + if !ok { + return false, true + } + + return true, true + } + + return false, false +} diff --git a/vendor/github.com/ghostiam/protogetter/protogetter.go b/vendor/github.com/ghostiam/protogetter/protogetter.go index 80a829672..31eee8572 100644 --- a/vendor/github.com/ghostiam/protogetter/protogetter.go +++ b/vendor/github.com/ghostiam/protogetter/protogetter.go @@ -2,13 +2,16 @@ package protogetter import ( "bytes" + "flag" "fmt" "go/ast" "go/format" "go/token" "log" + "path/filepath" "strings" + "github.com/gobwas/glob" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/ast/inspector" ) @@ -22,34 +25,101 @@ const ( const msgFormat = "avoid direct access to proto field %s, use %s instead" -func NewAnalyzer() *analysis.Analyzer { +func NewAnalyzer(cfg *Config) *analysis.Analyzer { + if cfg == nil { + cfg = &Config{} + } + return &analysis.Analyzer{ - Name: "protogetter", - Doc: "Reports direct reads from proto message fields when getters should be used", + Name: "protogetter", + Doc: "Reports direct reads from proto message fields when getters should be used", + Flags: flags(cfg), Run: func(pass *analysis.Pass) (any, error) { - Run(pass, StandaloneMode) - return nil, nil + _, err := Run(pass, cfg) + return nil, err }, } } -func Run(pass *analysis.Pass, mode Mode) []Issue { +func flags(opts *Config) flag.FlagSet { + fs := flag.NewFlagSet("protogetter", flag.ContinueOnError) + + fs.Func("skip-generated-by", "skip files generated with the given prefixes", func(s string) error { + for _, prefix := range strings.Split(s, ",") { + opts.SkipGeneratedBy = append(opts.SkipGeneratedBy, prefix) + } + return nil + }) + fs.Func("skip-files", "skip files with the given glob patterns", func(s string) error { + for _, pattern := range strings.Split(s, ",") { + opts.SkipFiles = append(opts.SkipFiles, pattern) + } + return nil + }) + fs.BoolVar(&opts.SkipAnyGenerated, "skip-any-generated", false, "skip any generated files") + + return *fs +} + +type Config struct { + Mode Mode // Zero value is StandaloneMode. + SkipGeneratedBy []string + SkipFiles []string + SkipAnyGenerated bool + ReplaceFirstArgInAppend bool +} + +func Run(pass *analysis.Pass, cfg *Config) ([]Issue, error) { + skipGeneratedBy := make([]string, 0, len(cfg.SkipGeneratedBy)+3) + // Always skip files generated by protoc-gen-go, protoc-gen-go-grpc and protoc-gen-grpc-gateway. + skipGeneratedBy = append(skipGeneratedBy, "protoc-gen-go", "protoc-gen-go-grpc", "protoc-gen-grpc-gateway") + for _, s := range cfg.SkipGeneratedBy { + s = strings.TrimSpace(s) + if s == "" { + continue + } + skipGeneratedBy = append(skipGeneratedBy, s) + } + + skipFilesGlobPatterns := make([]glob.Glob, 0, len(cfg.SkipFiles)) + for _, s := range cfg.SkipFiles { + s = strings.TrimSpace(s) + if s == "" { + continue + } + + compile, err := glob.Compile(s) + if err != nil { + return nil, fmt.Errorf("invalid glob pattern: %w", err) + } + + skipFilesGlobPatterns = append(skipFilesGlobPatterns, compile) + } + nodeTypes := []ast.Node{ (*ast.AssignStmt)(nil), + (*ast.BinaryExpr)(nil), (*ast.CallExpr)(nil), (*ast.SelectorExpr)(nil), + (*ast.StarExpr)(nil), (*ast.IncDecStmt)(nil), (*ast.UnaryExpr)(nil), } - // Skip protoc-generated files. + // Skip filtered files. var files []*ast.File for _, f := range pass.Files { - if !isProtocGeneratedFile(f) { - files = append(files, f) + if skipGeneratedFile(f, skipGeneratedBy, cfg.SkipAnyGenerated) { + continue + } - // ast.Print(pass.Fset, f) + if skipFilesByGlob(pass.Fset.File(f.Pos()).Name(), skipFilesGlobPatterns) { + continue } + + files = append(files, f) + + // ast.Print(pass.Fset, f) } ins := inspector.New(files) @@ -58,12 +128,12 @@ func Run(pass *analysis.Pass, mode Mode) []Issue { filter := NewPosFilter() ins.Preorder(nodeTypes, func(node ast.Node) { - report := analyse(pass, filter, node) + report := analyse(pass, filter, node, cfg) if report == nil { return } - switch mode { + switch cfg.Mode { case StandaloneMode: pass.Report(report.ToDiagReport()) case GolangciLintMode: @@ -71,10 +141,10 @@ func Run(pass *analysis.Pass, mode Mode) []Issue { } }) - return issues + return issues, nil } -func analyse(pass *analysis.Pass, filter *PosFilter, n ast.Node) *Report { +func analyse(pass *analysis.Pass, filter *PosFilter, n ast.Node, cfg *Config) *Report { // fmt.Printf("\n>>> check: %s\n", formatNode(n)) // ast.Print(pass.Fset, n) if filter.IsFiltered(n.Pos()) { @@ -82,7 +152,7 @@ func analyse(pass *analysis.Pass, filter *PosFilter, n ast.Node) *Report { return nil } - result, err := Process(pass.TypesInfo, filter, n) + result, err := Process(pass.TypesInfo, filter, n, cfg) if err != nil { pass.Report(analysis.Diagnostic{ Pos: n.Pos(), @@ -168,8 +238,34 @@ func (r *Report) ToIssue(fset *token.FileSet) Issue { } } -func isProtocGeneratedFile(f *ast.File) bool { - return len(f.Comments) > 0 && strings.HasPrefix(f.Comments[0].Text(), "Code generated by protoc-gen-go") +func skipGeneratedFile(f *ast.File, prefixes []string, skipAny bool) bool { + if len(f.Comments) == 0 { + return false + } + firstComment := f.Comments[0].Text() + + // https://golang.org/s/generatedcode + if skipAny && strings.HasPrefix(firstComment, "Code generated") { + return true + } + + for _, prefix := range prefixes { + if strings.HasPrefix(firstComment, "Code generated by "+prefix) { + return true + } + } + + return false +} + +func skipFilesByGlob(filename string, patterns []glob.Glob) bool { + for _, pattern := range patterns { + if pattern.Match(filename) || pattern.Match(filepath.Base(filename)) { + return true + } + } + + return false } func formatNode(node ast.Node) string { diff --git a/vendor/github.com/go-critic/go-critic/checkers/commentedOutCode_checker.go b/vendor/github.com/go-critic/go-critic/checkers/commentedOutCode_checker.go index 402ba3306..8595b7951 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/commentedOutCode_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/commentedOutCode_checker.go @@ -6,6 +6,7 @@ import ( "go/token" "regexp" "strings" + "unicode/utf8" "github.com/go-critic/go-critic/checkers/internal/astwalk" "github.com/go-critic/go-critic/linter" @@ -18,6 +19,12 @@ func init() { info.Name = "commentedOutCode" info.Tags = []string{linter.DiagnosticTag, linter.ExperimentalTag} info.Summary = "Detects commented-out code inside function bodies" + info.Params = linter.CheckerParams{ + "minLength": { + Value: 15, + Usage: "min length of the comment that triggers a warning", + }, + } info.Before = ` // fmt.Println("Debugging hard") foo(1, 2)` @@ -27,6 +34,7 @@ foo(1, 2)` return astwalk.WalkerForLocalComment(&commentedOutCodeChecker{ ctx: ctx, notQuiteFuncCall: regexp.MustCompile(`\w+\s+\([^)]*\)\s*$`), + minLength: info.Params.Int("minLength"), }), nil }) } @@ -37,6 +45,7 @@ type commentedOutCodeChecker struct { fn *ast.FuncDecl notQuiteFuncCall *regexp.Regexp + minLength int } func (c *commentedOutCodeChecker) EnterFunc(fn *ast.FuncDecl) bool { @@ -69,7 +78,7 @@ func (c *commentedOutCodeChecker) VisitLocalComment(cg *ast.CommentGroup) { // Some very short comment that can be skipped. // Usually triggering on these results in false positive. // Unless there is a very popular call like print/println. - cond := len(s) < len("quite too short") && + cond := utf8.RuneCountInString(s) < c.minLength && !strings.Contains(s, "print") && !strings.Contains(s, "fmt.") && !strings.Contains(s, "log.") diff --git a/vendor/github.com/go-critic/go-critic/checkers/dupImports_checker.go b/vendor/github.com/go-critic/go-critic/checkers/dupImports_checker.go index 19079871f..ed674eb85 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/dupImports_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/dupImports_checker.go @@ -15,7 +15,7 @@ func init() { info.Before = ` import ( "fmt" - priting "fmt" // Imported the second time + printing "fmt" // Imported the second time )` info.After = ` import( diff --git a/vendor/github.com/go-critic/go-critic/checkers/flagName_checker.go b/vendor/github.com/go-critic/go-critic/checkers/flagName_checker.go index 98b76e261..701066860 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/flagName_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/flagName_checker.go @@ -64,7 +64,7 @@ func (c *flagNameChecker) checkFlagName(call *ast.CallExpr, arg ast.Expr) { case name == "": c.warnEmpty(call) case strings.HasPrefix(name, "-"): - c.warnHypenPrefix(call, name) + c.warnHyphenPrefix(call, name) case strings.Contains(name, "="): c.warnEq(call, name) case strings.Contains(name, " "): @@ -76,8 +76,8 @@ func (c *flagNameChecker) warnEmpty(cause ast.Node) { c.ctx.Warn(cause, "empty flag name") } -func (c *flagNameChecker) warnHypenPrefix(cause ast.Node, name string) { - c.ctx.Warn(cause, "flag name %q should not start with a hypen", name) +func (c *flagNameChecker) warnHyphenPrefix(cause ast.Node, name string) { + c.ctx.Warn(cause, "flag name %q should not start with a hyphen", name) } func (c *flagNameChecker) warnEq(cause ast.Node, name string) { diff --git a/vendor/github.com/go-critic/go-critic/checkers/hugeParam_checker.go b/vendor/github.com/go-critic/go-critic/checkers/hugeParam_checker.go index 3b7f1d12b..7b7a3c538 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/hugeParam_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/hugeParam_checker.go @@ -5,6 +5,8 @@ import ( "github.com/go-critic/go-critic/checkers/internal/astwalk" "github.com/go-critic/go-critic/linter" + + "github.com/go-toolsmith/astcast" ) func init() { @@ -39,12 +41,30 @@ type hugeParamChecker struct { func (c *hugeParamChecker) VisitFuncDecl(decl *ast.FuncDecl) { // TODO(quasilyte): maybe it's worthwhile to permit skipping // test files for this checker? + if c.isImplementStringer(decl) { + return + } + if decl.Recv != nil { c.checkParams(decl.Recv.List) } c.checkParams(decl.Type.Params.List) } +// isImplementStringer check method signature is: String() string. +func (*hugeParamChecker) isImplementStringer(decl *ast.FuncDecl) bool { + if decl.Recv != nil && + decl.Name.Name == "String" && + decl.Type != nil && + len(decl.Type.Params.List) == 0 && + len(decl.Type.Results.List) == 1 && + astcast.ToIdent(decl.Type.Results.List[0].Type).Name == "string" { + return true + } + + return false +} + func (c *hugeParamChecker) checkParams(params []*ast.Field) { for _, p := range params { for _, id := range p.Names { diff --git a/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astflow.go b/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astflow.go index 63d181e5e..f64907d69 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astflow.go +++ b/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astflow.go @@ -18,7 +18,7 @@ import ( // // If proven really useful, can be moved to go-toolsmith library. -// IsImmutable reports whether n can be midified through any operation. +// IsImmutable reports whether n can be modified through any operation. func IsImmutable(info *types.Info, n ast.Expr) bool { if astp.IsBasicLit(n) { return true diff --git a/vendor/github.com/go-critic/go-critic/checkers/mapKey_checker.go b/vendor/github.com/go-critic/go-critic/checkers/mapKey_checker.go index ebc61c12a..2885dc725 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/mapKey_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/mapKey_checker.go @@ -117,7 +117,7 @@ func (c *mapKeyChecker) checkWhitespace(lit *ast.CompositeLit) { } func (c *mapKeyChecker) warnWhitespace(key ast.Node) { - c.ctx.Warn(key, "suspucious whitespace in %s key", key) + c.ctx.Warn(key, "suspicious whitespace in %s key", key) } func (c *mapKeyChecker) warnDupKey(key ast.Node) { diff --git a/vendor/github.com/go-critic/go-critic/checkers/rulesdata/rulesdata.go b/vendor/github.com/go-critic/go-critic/checkers/rulesdata/rulesdata.go index 503118c7e..4ab31076f 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/rulesdata/rulesdata.go +++ b/vendor/github.com/go-critic/go-critic/checkers/rulesdata/rulesdata.go @@ -363,7 +363,7 @@ var PrecompiledRules = &ir.File{ Line: 114, Name: "sloppyLen", MatcherName: "m", - DocTags: []string{"style"}, + DocTags: []string{"diagnostic"}, DocSummary: "Detects usage of `len` when result is obvious or doesn't make sense", DocBefore: "len(arr) <= 0", DocAfter: "len(arr) == 0", @@ -493,21 +493,45 @@ var PrecompiledRules = &ir.File{ }, }, { - Line: 164, - SyntaxPatterns: []ir.PatternString{{Line: 164, Value: "len($s) == 0"}}, + Line: 163, + SyntaxPatterns: []ir.PatternString{{Line: 163, Value: "len($s) > 0"}}, + ReportTemplate: "replace `$$` with `$s != \"\"`", + WhereExpr: ir.FilterExpr{ + Line: 164, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"s\"].Type.Is(`string`)", + Value: "s", + Args: []ir.FilterExpr{{Line: 164, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + }, + }, + { + Line: 167, + SyntaxPatterns: []ir.PatternString{{Line: 167, Value: "len($s) == 0"}}, + ReportTemplate: "replace `$$` with `$s == \"\"`", + WhereExpr: ir.FilterExpr{ + Line: 168, + Op: ir.FilterVarTypeIsOp, + Src: "m[\"s\"].Type.Is(`string`)", + Value: "s", + Args: []ir.FilterExpr{{Line: 168, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + }, + }, + { + Line: 170, + SyntaxPatterns: []ir.PatternString{{Line: 170, Value: "len($s) <= 0"}}, ReportTemplate: "replace `$$` with `$s == \"\"`", WhereExpr: ir.FilterExpr{ - Line: 165, + Line: 171, Op: ir.FilterVarTypeIsOp, Src: "m[\"s\"].Type.Is(`string`)", Value: "s", - Args: []ir.FilterExpr{{Line: 165, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + Args: []ir.FilterExpr{{Line: 171, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, }, }, }, }, { - Line: 173, + Line: 179, Name: "stringXbytes", MatcherName: "m", DocTags: []string{"performance"}, @@ -516,180 +540,180 @@ var PrecompiledRules = &ir.File{ DocAfter: "copy(b, s)", Rules: []ir.Rule{ { - Line: 174, - SyntaxPatterns: []ir.PatternString{{Line: 174, Value: "copy($_, []byte($s))"}}, + Line: 180, + SyntaxPatterns: []ir.PatternString{{Line: 180, Value: "copy($_, []byte($s))"}}, ReportTemplate: "can simplify `[]byte($s)` to `$s`", }, { - Line: 176, - SyntaxPatterns: []ir.PatternString{{Line: 176, Value: "string($b) == \"\""}}, + Line: 182, + SyntaxPatterns: []ir.PatternString{{Line: 182, Value: "string($b) == \"\""}}, ReportTemplate: "suggestion: len($b) == 0", SuggestTemplate: "len($b) == 0", WhereExpr: ir.FilterExpr{ - Line: 176, + Line: 182, Op: ir.FilterVarTypeIsOp, Src: "m[\"b\"].Type.Is(`[]byte`)", Value: "b", - Args: []ir.FilterExpr{{Line: 176, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, + Args: []ir.FilterExpr{{Line: 182, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, }, }, { - Line: 177, - SyntaxPatterns: []ir.PatternString{{Line: 177, Value: "string($b) != \"\""}}, + Line: 183, + SyntaxPatterns: []ir.PatternString{{Line: 183, Value: "string($b) != \"\""}}, ReportTemplate: "suggestion: len($b) != 0", SuggestTemplate: "len($b) != 0", WhereExpr: ir.FilterExpr{ - Line: 177, + Line: 183, Op: ir.FilterVarTypeIsOp, Src: "m[\"b\"].Type.Is(`[]byte`)", Value: "b", - Args: []ir.FilterExpr{{Line: 177, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, + Args: []ir.FilterExpr{{Line: 183, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, }, }, { - Line: 179, - SyntaxPatterns: []ir.PatternString{{Line: 179, Value: "len(string($b))"}}, + Line: 185, + SyntaxPatterns: []ir.PatternString{{Line: 185, Value: "len(string($b))"}}, ReportTemplate: "suggestion: len($b)", SuggestTemplate: "len($b)", WhereExpr: ir.FilterExpr{ - Line: 179, + Line: 185, Op: ir.FilterVarTypeIsOp, Src: "m[\"b\"].Type.Is(`[]byte`)", Value: "b", - Args: []ir.FilterExpr{{Line: 179, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, + Args: []ir.FilterExpr{{Line: 185, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, }, }, { - Line: 181, - SyntaxPatterns: []ir.PatternString{{Line: 181, Value: "string($x) == string($y)"}}, + Line: 187, + SyntaxPatterns: []ir.PatternString{{Line: 187, Value: "string($x) == string($y)"}}, ReportTemplate: "suggestion: bytes.Equal($x, $y)", SuggestTemplate: "bytes.Equal($x, $y)", WhereExpr: ir.FilterExpr{ - Line: 182, + Line: 188, Op: ir.FilterAndOp, Src: "m[\"x\"].Type.Is(`[]byte`) && m[\"y\"].Type.Is(`[]byte`)", Args: []ir.FilterExpr{ { - Line: 182, + Line: 188, Op: ir.FilterVarTypeIsOp, Src: "m[\"x\"].Type.Is(`[]byte`)", Value: "x", - Args: []ir.FilterExpr{{Line: 182, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, + Args: []ir.FilterExpr{{Line: 188, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, }, { - Line: 182, + Line: 188, Op: ir.FilterVarTypeIsOp, Src: "m[\"y\"].Type.Is(`[]byte`)", Value: "y", - Args: []ir.FilterExpr{{Line: 182, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, + Args: []ir.FilterExpr{{Line: 188, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, }, }, }, }, { - Line: 185, - SyntaxPatterns: []ir.PatternString{{Line: 185, Value: "string($x) != string($y)"}}, + Line: 191, + SyntaxPatterns: []ir.PatternString{{Line: 191, Value: "string($x) != string($y)"}}, ReportTemplate: "suggestion: !bytes.Equal($x, $y)", SuggestTemplate: "!bytes.Equal($x, $y)", WhereExpr: ir.FilterExpr{ - Line: 186, + Line: 192, Op: ir.FilterAndOp, Src: "m[\"x\"].Type.Is(`[]byte`) && m[\"y\"].Type.Is(`[]byte`)", Args: []ir.FilterExpr{ { - Line: 186, + Line: 192, Op: ir.FilterVarTypeIsOp, Src: "m[\"x\"].Type.Is(`[]byte`)", Value: "x", - Args: []ir.FilterExpr{{Line: 186, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, + Args: []ir.FilterExpr{{Line: 192, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, }, { - Line: 186, + Line: 192, Op: ir.FilterVarTypeIsOp, Src: "m[\"y\"].Type.Is(`[]byte`)", Value: "y", - Args: []ir.FilterExpr{{Line: 186, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, + Args: []ir.FilterExpr{{Line: 192, Op: ir.FilterStringOp, Src: "`[]byte`", Value: "[]byte"}}, }, }, }, }, { - Line: 189, - SyntaxPatterns: []ir.PatternString{{Line: 189, Value: "$re.Match([]byte($s))"}}, + Line: 195, + SyntaxPatterns: []ir.PatternString{{Line: 195, Value: "$re.Match([]byte($s))"}}, ReportTemplate: "suggestion: $re.MatchString($s)", SuggestTemplate: "$re.MatchString($s)", WhereExpr: ir.FilterExpr{ - Line: 190, + Line: 196, Op: ir.FilterAndOp, Src: "m[\"re\"].Type.Is(`*regexp.Regexp`) && m[\"s\"].Type.Is(`string`)", Args: []ir.FilterExpr{ { - Line: 190, + Line: 196, Op: ir.FilterVarTypeIsOp, Src: "m[\"re\"].Type.Is(`*regexp.Regexp`)", Value: "re", - Args: []ir.FilterExpr{{Line: 190, Op: ir.FilterStringOp, Src: "`*regexp.Regexp`", Value: "*regexp.Regexp"}}, + Args: []ir.FilterExpr{{Line: 196, Op: ir.FilterStringOp, Src: "`*regexp.Regexp`", Value: "*regexp.Regexp"}}, }, { - Line: 190, + Line: 196, Op: ir.FilterVarTypeIsOp, Src: "m[\"s\"].Type.Is(`string`)", Value: "s", - Args: []ir.FilterExpr{{Line: 190, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + Args: []ir.FilterExpr{{Line: 196, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, }, }, }, }, { - Line: 193, - SyntaxPatterns: []ir.PatternString{{Line: 193, Value: "$re.FindIndex([]byte($s))"}}, + Line: 199, + SyntaxPatterns: []ir.PatternString{{Line: 199, Value: "$re.FindIndex([]byte($s))"}}, ReportTemplate: "suggestion: $re.FindStringIndex($s)", SuggestTemplate: "$re.FindStringIndex($s)", WhereExpr: ir.FilterExpr{ - Line: 194, + Line: 200, Op: ir.FilterAndOp, Src: "m[\"re\"].Type.Is(`*regexp.Regexp`) && m[\"s\"].Type.Is(`string`)", Args: []ir.FilterExpr{ { - Line: 194, + Line: 200, Op: ir.FilterVarTypeIsOp, Src: "m[\"re\"].Type.Is(`*regexp.Regexp`)", Value: "re", - Args: []ir.FilterExpr{{Line: 194, Op: ir.FilterStringOp, Src: "`*regexp.Regexp`", Value: "*regexp.Regexp"}}, + Args: []ir.FilterExpr{{Line: 200, Op: ir.FilterStringOp, Src: "`*regexp.Regexp`", Value: "*regexp.Regexp"}}, }, { - Line: 194, + Line: 200, Op: ir.FilterVarTypeIsOp, Src: "m[\"s\"].Type.Is(`string`)", Value: "s", - Args: []ir.FilterExpr{{Line: 194, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + Args: []ir.FilterExpr{{Line: 200, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, }, }, }, }, { - Line: 197, - SyntaxPatterns: []ir.PatternString{{Line: 197, Value: "$re.FindAllIndex([]byte($s), $n)"}}, + Line: 203, + SyntaxPatterns: []ir.PatternString{{Line: 203, Value: "$re.FindAllIndex([]byte($s), $n)"}}, ReportTemplate: "suggestion: $re.FindAllStringIndex($s, $n)", SuggestTemplate: "$re.FindAllStringIndex($s, $n)", WhereExpr: ir.FilterExpr{ - Line: 198, + Line: 204, Op: ir.FilterAndOp, Src: "m[\"re\"].Type.Is(`*regexp.Regexp`) && m[\"s\"].Type.Is(`string`)", Args: []ir.FilterExpr{ { - Line: 198, + Line: 204, Op: ir.FilterVarTypeIsOp, Src: "m[\"re\"].Type.Is(`*regexp.Regexp`)", Value: "re", - Args: []ir.FilterExpr{{Line: 198, Op: ir.FilterStringOp, Src: "`*regexp.Regexp`", Value: "*regexp.Regexp"}}, + Args: []ir.FilterExpr{{Line: 204, Op: ir.FilterStringOp, Src: "`*regexp.Regexp`", Value: "*regexp.Regexp"}}, }, { - Line: 198, + Line: 204, Op: ir.FilterVarTypeIsOp, Src: "m[\"s\"].Type.Is(`string`)", Value: "s", - Args: []ir.FilterExpr{{Line: 198, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + Args: []ir.FilterExpr{{Line: 204, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, }, }, }, @@ -697,7 +721,7 @@ var PrecompiledRules = &ir.File{ }, }, { - Line: 207, + Line: 213, Name: "indexAlloc", MatcherName: "m", DocTags: []string{"performance"}, @@ -706,22 +730,22 @@ var PrecompiledRules = &ir.File{ DocAfter: "bytes.Index(x, []byte(y))", DocNote: "See Go issue for details: https://github.com/golang/go/issues/25864", Rules: []ir.Rule{{ - Line: 208, - SyntaxPatterns: []ir.PatternString{{Line: 208, Value: "strings.Index(string($x), $y)"}}, + Line: 214, + SyntaxPatterns: []ir.PatternString{{Line: 214, Value: "strings.Index(string($x), $y)"}}, ReportTemplate: "consider replacing $$ with bytes.Index($x, []byte($y))", WhereExpr: ir.FilterExpr{ - Line: 209, + Line: 215, Op: ir.FilterAndOp, Src: "m[\"x\"].Pure && m[\"y\"].Pure", Args: []ir.FilterExpr{ - {Line: 209, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, - {Line: 209, Op: ir.FilterVarPureOp, Src: "m[\"y\"].Pure", Value: "y"}, + {Line: 215, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + {Line: 215, Op: ir.FilterVarPureOp, Src: "m[\"y\"].Pure", Value: "y"}, }, }, }}, }, { - Line: 217, + Line: 223, Name: "wrapperFunc", MatcherName: "m", DocTags: []string{"style"}, @@ -730,169 +754,169 @@ var PrecompiledRules = &ir.File{ DocAfter: "wg.Done()", Rules: []ir.Rule{ { - Line: 218, - SyntaxPatterns: []ir.PatternString{{Line: 218, Value: "$wg.Add(-1)"}}, + Line: 224, + SyntaxPatterns: []ir.PatternString{{Line: 224, Value: "$wg.Add(-1)"}}, ReportTemplate: "use WaitGroup.Done method in `$$`", WhereExpr: ir.FilterExpr{ - Line: 219, + Line: 225, Op: ir.FilterVarTypeIsOp, Src: "m[\"wg\"].Type.Is(`sync.WaitGroup`)", Value: "wg", - Args: []ir.FilterExpr{{Line: 219, Op: ir.FilterStringOp, Src: "`sync.WaitGroup`", Value: "sync.WaitGroup"}}, + Args: []ir.FilterExpr{{Line: 225, Op: ir.FilterStringOp, Src: "`sync.WaitGroup`", Value: "sync.WaitGroup"}}, }, }, { - Line: 222, - SyntaxPatterns: []ir.PatternString{{Line: 222, Value: "$buf.Truncate(0)"}}, + Line: 228, + SyntaxPatterns: []ir.PatternString{{Line: 228, Value: "$buf.Truncate(0)"}}, ReportTemplate: "use Buffer.Reset method in `$$`", WhereExpr: ir.FilterExpr{ - Line: 223, + Line: 229, Op: ir.FilterVarTypeIsOp, Src: "m[\"buf\"].Type.Is(`bytes.Buffer`)", Value: "buf", - Args: []ir.FilterExpr{{Line: 223, Op: ir.FilterStringOp, Src: "`bytes.Buffer`", Value: "bytes.Buffer"}}, + Args: []ir.FilterExpr{{Line: 229, Op: ir.FilterStringOp, Src: "`bytes.Buffer`", Value: "bytes.Buffer"}}, }, }, { - Line: 226, - SyntaxPatterns: []ir.PatternString{{Line: 226, Value: "http.HandlerFunc(http.NotFound)"}}, + Line: 232, + SyntaxPatterns: []ir.PatternString{{Line: 232, Value: "http.HandlerFunc(http.NotFound)"}}, ReportTemplate: "use http.NotFoundHandler method in `$$`", }, { - Line: 228, - SyntaxPatterns: []ir.PatternString{{Line: 228, Value: "strings.SplitN($_, $_, -1)"}}, + Line: 234, + SyntaxPatterns: []ir.PatternString{{Line: 234, Value: "strings.SplitN($_, $_, -1)"}}, ReportTemplate: "use strings.Split method in `$$`", }, { - Line: 229, - SyntaxPatterns: []ir.PatternString{{Line: 229, Value: "strings.Replace($_, $_, $_, -1)"}}, + Line: 235, + SyntaxPatterns: []ir.PatternString{{Line: 235, Value: "strings.Replace($_, $_, $_, -1)"}}, ReportTemplate: "use strings.ReplaceAll method in `$$`", }, { - Line: 230, - SyntaxPatterns: []ir.PatternString{{Line: 230, Value: "strings.Map(unicode.ToTitle, $_)"}}, + Line: 236, + SyntaxPatterns: []ir.PatternString{{Line: 236, Value: "strings.Map(unicode.ToTitle, $_)"}}, ReportTemplate: "use strings.ToTitle method in `$$`", }, { - Line: 231, + Line: 237, SyntaxPatterns: []ir.PatternString{ - {Line: 231, Value: "strings.Index($s1, $s2) >= 0"}, - {Line: 231, Value: "strings.Index($s1, $s2) != -1"}, + {Line: 237, Value: "strings.Index($s1, $s2) >= 0"}, + {Line: 237, Value: "strings.Index($s1, $s2) != -1"}, }, ReportTemplate: "suggestion: strings.Contains($s1, $s2)", SuggestTemplate: "strings.Contains($s1, $s2)", }, { - Line: 232, + Line: 238, SyntaxPatterns: []ir.PatternString{ - {Line: 232, Value: "strings.IndexAny($s1, $s2) >= 0"}, - {Line: 232, Value: "strings.IndexAny($s1, $s2) != -1"}, + {Line: 238, Value: "strings.IndexAny($s1, $s2) >= 0"}, + {Line: 238, Value: "strings.IndexAny($s1, $s2) != -1"}, }, ReportTemplate: "suggestion: strings.ContainsAny($s1, $s2)", SuggestTemplate: "strings.ContainsAny($s1, $s2)", }, { - Line: 233, + Line: 239, SyntaxPatterns: []ir.PatternString{ - {Line: 233, Value: "strings.IndexRune($s1, $s2) >= 0"}, - {Line: 233, Value: "strings.IndexRune($s1, $s2) != -1"}, + {Line: 239, Value: "strings.IndexRune($s1, $s2) >= 0"}, + {Line: 239, Value: "strings.IndexRune($s1, $s2) != -1"}, }, ReportTemplate: "suggestion: strings.ContainsRune($s1, $s2)", SuggestTemplate: "strings.ContainsRune($s1, $s2)", }, { - Line: 235, + Line: 241, SyntaxPatterns: []ir.PatternString{ - {Line: 235, Value: "$i := strings.Index($s, $sep); $*_; $x, $y = $s[:$i], $s[$i+1:]"}, - {Line: 236, Value: "$i := strings.Index($s, $sep); $*_; $x = $s[:$i]; $*_; $y = $s[$i+1:]"}, + {Line: 241, Value: "$i := strings.Index($s, $sep); $*_; $x, $y = $s[:$i], $s[$i+1:]"}, + {Line: 242, Value: "$i := strings.Index($s, $sep); $*_; $x = $s[:$i]; $*_; $y = $s[$i+1:]"}, }, ReportTemplate: "suggestion: $x, $y, _ = strings.Cut($s, $sep)", SuggestTemplate: "$x, $y, _ = strings.Cut($s, $sep)", WhereExpr: ir.FilterExpr{ - Line: 237, + Line: 243, Op: ir.FilterGoVersionGreaterEqThanOp, Src: "m.GoVersion().GreaterEqThan(\"1.18\")", Value: "1.18", }, }, { - Line: 240, + Line: 246, SyntaxPatterns: []ir.PatternString{ - {Line: 241, Value: "if $i := strings.Index($s, $sep); $i != -1 { $*_; $x, $y = $s[:$i], $s[$i+1:]; $*_ }"}, - {Line: 242, Value: "if $i := strings.Index($s, $sep); $i != -1 { $*_; $x = $s[:$i]; $*_; $y = $s[$i+1:]; $*_ }"}, - {Line: 243, Value: "if $i := strings.Index($s, $sep); $i >= 0 { $*_; $x, $y = $s[:$i], $s[$i+1:]; $*_ }"}, - {Line: 244, Value: "if $i := strings.Index($s, $sep); $i >= 0 { $*_; $x = $s[:$i]; $*_; $y = $s[$i+1:]; $*_ }"}, + {Line: 247, Value: "if $i := strings.Index($s, $sep); $i != -1 { $*_; $x, $y = $s[:$i], $s[$i+1:]; $*_ }"}, + {Line: 248, Value: "if $i := strings.Index($s, $sep); $i != -1 { $*_; $x = $s[:$i]; $*_; $y = $s[$i+1:]; $*_ }"}, + {Line: 249, Value: "if $i := strings.Index($s, $sep); $i >= 0 { $*_; $x, $y = $s[:$i], $s[$i+1:]; $*_ }"}, + {Line: 250, Value: "if $i := strings.Index($s, $sep); $i >= 0 { $*_; $x = $s[:$i]; $*_; $y = $s[$i+1:]; $*_ }"}, }, ReportTemplate: "suggestion: if $x, $y, ok = strings.Cut($s, $sep); ok { ... }", SuggestTemplate: "if $x, $y, ok = strings.Cut($s, $sep); ok { ... }", WhereExpr: ir.FilterExpr{ - Line: 245, + Line: 251, Op: ir.FilterGoVersionGreaterEqThanOp, Src: "m.GoVersion().GreaterEqThan(\"1.18\")", Value: "1.18", }, }, { - Line: 248, - SyntaxPatterns: []ir.PatternString{{Line: 248, Value: "bytes.SplitN(b, []byte(\".\"), -1)"}}, + Line: 254, + SyntaxPatterns: []ir.PatternString{{Line: 254, Value: "bytes.SplitN(b, []byte(\".\"), -1)"}}, ReportTemplate: "use bytes.Split method in `$$`", }, { - Line: 249, - SyntaxPatterns: []ir.PatternString{{Line: 249, Value: "bytes.Replace($_, $_, $_, -1)"}}, + Line: 255, + SyntaxPatterns: []ir.PatternString{{Line: 255, Value: "bytes.Replace($_, $_, $_, -1)"}}, ReportTemplate: "use bytes.ReplaceAll method in `$$`", }, { - Line: 250, - SyntaxPatterns: []ir.PatternString{{Line: 250, Value: "bytes.Map(unicode.ToUpper, $_)"}}, + Line: 256, + SyntaxPatterns: []ir.PatternString{{Line: 256, Value: "bytes.Map(unicode.ToUpper, $_)"}}, ReportTemplate: "use bytes.ToUpper method in `$$`", }, { - Line: 251, - SyntaxPatterns: []ir.PatternString{{Line: 251, Value: "bytes.Map(unicode.ToLower, $_)"}}, + Line: 257, + SyntaxPatterns: []ir.PatternString{{Line: 257, Value: "bytes.Map(unicode.ToLower, $_)"}}, ReportTemplate: "use bytes.ToLower method in `$$`", }, { - Line: 252, - SyntaxPatterns: []ir.PatternString{{Line: 252, Value: "bytes.Map(unicode.ToTitle, $_)"}}, + Line: 258, + SyntaxPatterns: []ir.PatternString{{Line: 258, Value: "bytes.Map(unicode.ToTitle, $_)"}}, ReportTemplate: "use bytes.ToTitle method in `$$`", }, { - Line: 253, + Line: 259, SyntaxPatterns: []ir.PatternString{ - {Line: 253, Value: "bytes.Index($b1, $b2) >= 0"}, - {Line: 253, Value: "bytes.Index($b1, $b2) != -1"}, + {Line: 259, Value: "bytes.Index($b1, $b2) >= 0"}, + {Line: 259, Value: "bytes.Index($b1, $b2) != -1"}, }, ReportTemplate: "suggestion: bytes.Contains($b1, $b2)", SuggestTemplate: "bytes.Contains($b1, $b2)", }, { - Line: 254, + Line: 260, SyntaxPatterns: []ir.PatternString{ - {Line: 254, Value: "bytes.IndexAny($b1, $b2) >= 0"}, - {Line: 254, Value: "bytes.IndexAny($b1, $b2) != -1"}, + {Line: 260, Value: "bytes.IndexAny($b1, $b2) >= 0"}, + {Line: 260, Value: "bytes.IndexAny($b1, $b2) != -1"}, }, ReportTemplate: "suggestion: bytes.ContainsAny($b1, $b2)", SuggestTemplate: "bytes.ContainsAny($b1, $b2)", }, { - Line: 255, + Line: 261, SyntaxPatterns: []ir.PatternString{ - {Line: 255, Value: "bytes.IndexRune($b1, $b2) >= 0"}, - {Line: 255, Value: "bytes.IndexRune($b1, $b2) != -1"}, + {Line: 261, Value: "bytes.IndexRune($b1, $b2) >= 0"}, + {Line: 261, Value: "bytes.IndexRune($b1, $b2) != -1"}, }, ReportTemplate: "suggestion: bytes.ContainsRune($b1, $b2)", SuggestTemplate: "bytes.ContainsRune($b1, $b2)", }, { - Line: 257, - SyntaxPatterns: []ir.PatternString{{Line: 257, Value: "draw.DrawMask($_, $_, $_, $_, nil, image.Point{}, $_)"}}, + Line: 263, + SyntaxPatterns: []ir.PatternString{{Line: 263, Value: "draw.DrawMask($_, $_, $_, $_, nil, image.Point{}, $_)"}}, ReportTemplate: "use draw.Draw method in `$$`", }, }, }, { - Line: 265, + Line: 271, Name: "regexpMust", MatcherName: "m", DocTags: []string{"style"}, @@ -901,22 +925,22 @@ var PrecompiledRules = &ir.File{ DocAfter: "re := regexp.MustCompile(\"const pattern\")", Rules: []ir.Rule{ { - Line: 266, - SyntaxPatterns: []ir.PatternString{{Line: 266, Value: "regexp.Compile($pat)"}}, + Line: 272, + SyntaxPatterns: []ir.PatternString{{Line: 272, Value: "regexp.Compile($pat)"}}, ReportTemplate: "for const patterns like $pat, use regexp.MustCompile", WhereExpr: ir.FilterExpr{ - Line: 267, + Line: 273, Op: ir.FilterVarConstOp, Src: "m[\"pat\"].Const", Value: "pat", }, }, { - Line: 270, - SyntaxPatterns: []ir.PatternString{{Line: 270, Value: "regexp.CompilePOSIX($pat)"}}, + Line: 276, + SyntaxPatterns: []ir.PatternString{{Line: 276, Value: "regexp.CompilePOSIX($pat)"}}, ReportTemplate: "for const patterns like $pat, use regexp.MustCompilePOSIX", WhereExpr: ir.FilterExpr{ - Line: 271, + Line: 277, Op: ir.FilterVarConstOp, Src: "m[\"pat\"].Const", Value: "pat", @@ -925,7 +949,7 @@ var PrecompiledRules = &ir.File{ }, }, { - Line: 279, + Line: 285, Name: "badCall", MatcherName: "m", DocTags: []string{"diagnostic"}, @@ -934,22 +958,22 @@ var PrecompiledRules = &ir.File{ DocAfter: "strings.Replace(s, from, to, -1)", Rules: []ir.Rule{ { - Line: 280, - SyntaxPatterns: []ir.PatternString{{Line: 280, Value: "strings.Replace($_, $_, $_, $zero)"}}, + Line: 286, + SyntaxPatterns: []ir.PatternString{{Line: 286, Value: "strings.Replace($_, $_, $_, $zero)"}}, ReportTemplate: "suspicious arg 0, probably meant -1", WhereExpr: ir.FilterExpr{ - Line: 281, + Line: 287, Op: ir.FilterEqOp, Src: "m[\"zero\"].Value.Int() == 0", Args: []ir.FilterExpr{ { - Line: 281, + Line: 287, Op: ir.FilterVarValueIntOp, Src: "m[\"zero\"].Value.Int()", Value: "zero", }, { - Line: 281, + Line: 287, Op: ir.FilterIntOp, Src: "0", Value: int64(0), @@ -959,22 +983,22 @@ var PrecompiledRules = &ir.File{ LocationVar: "zero", }, { - Line: 283, - SyntaxPatterns: []ir.PatternString{{Line: 283, Value: "bytes.Replace($_, $_, $_, $zero)"}}, + Line: 289, + SyntaxPatterns: []ir.PatternString{{Line: 289, Value: "bytes.Replace($_, $_, $_, $zero)"}}, ReportTemplate: "suspicious arg 0, probably meant -1", WhereExpr: ir.FilterExpr{ - Line: 284, + Line: 290, Op: ir.FilterEqOp, Src: "m[\"zero\"].Value.Int() == 0", Args: []ir.FilterExpr{ { - Line: 284, + Line: 290, Op: ir.FilterVarValueIntOp, Src: "m[\"zero\"].Value.Int()", Value: "zero", }, { - Line: 284, + Line: 290, Op: ir.FilterIntOp, Src: "0", Value: int64(0), @@ -984,22 +1008,22 @@ var PrecompiledRules = &ir.File{ LocationVar: "zero", }, { - Line: 287, - SyntaxPatterns: []ir.PatternString{{Line: 287, Value: "strings.SplitN($_, $_, $zero)"}}, + Line: 293, + SyntaxPatterns: []ir.PatternString{{Line: 293, Value: "strings.SplitN($_, $_, $zero)"}}, ReportTemplate: "suspicious arg 0, probably meant -1", WhereExpr: ir.FilterExpr{ - Line: 288, + Line: 294, Op: ir.FilterEqOp, Src: "m[\"zero\"].Value.Int() == 0", Args: []ir.FilterExpr{ { - Line: 288, + Line: 294, Op: ir.FilterVarValueIntOp, Src: "m[\"zero\"].Value.Int()", Value: "zero", }, { - Line: 288, + Line: 294, Op: ir.FilterIntOp, Src: "0", Value: int64(0), @@ -1009,22 +1033,22 @@ var PrecompiledRules = &ir.File{ LocationVar: "zero", }, { - Line: 290, - SyntaxPatterns: []ir.PatternString{{Line: 290, Value: "bytes.SplitN($_, $_, $zero)"}}, + Line: 296, + SyntaxPatterns: []ir.PatternString{{Line: 296, Value: "bytes.SplitN($_, $_, $zero)"}}, ReportTemplate: "suspicious arg 0, probably meant -1", WhereExpr: ir.FilterExpr{ - Line: 291, + Line: 297, Op: ir.FilterEqOp, Src: "m[\"zero\"].Value.Int() == 0", Args: []ir.FilterExpr{ { - Line: 291, + Line: 297, Op: ir.FilterVarValueIntOp, Src: "m[\"zero\"].Value.Int()", Value: "zero", }, { - Line: 291, + Line: 297, Op: ir.FilterIntOp, Src: "0", Value: int64(0), @@ -1034,19 +1058,19 @@ var PrecompiledRules = &ir.File{ LocationVar: "zero", }, { - Line: 294, - SyntaxPatterns: []ir.PatternString{{Line: 294, Value: "append($_)"}}, + Line: 300, + SyntaxPatterns: []ir.PatternString{{Line: 300, Value: "append($_)"}}, ReportTemplate: "no-op append call, probably missing arguments", }, { - Line: 296, - SyntaxPatterns: []ir.PatternString{{Line: 296, Value: "filepath.Join($_)"}}, + Line: 302, + SyntaxPatterns: []ir.PatternString{{Line: 302, Value: "filepath.Join($_)"}}, ReportTemplate: "suspicious Join on 1 argument", }, }, }, { - Line: 303, + Line: 309, Name: "assignOp", MatcherName: "m", DocTags: []string{"style"}, @@ -1055,87 +1079,87 @@ var PrecompiledRules = &ir.File{ DocAfter: "x *= 2", Rules: []ir.Rule{ { - Line: 304, - SyntaxPatterns: []ir.PatternString{{Line: 304, Value: "$x = $x + 1"}}, + Line: 310, + SyntaxPatterns: []ir.PatternString{{Line: 310, Value: "$x = $x + 1"}}, ReportTemplate: "replace `$$` with `$x++`", - WhereExpr: ir.FilterExpr{Line: 304, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 310, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 305, - SyntaxPatterns: []ir.PatternString{{Line: 305, Value: "$x = $x - 1"}}, + Line: 311, + SyntaxPatterns: []ir.PatternString{{Line: 311, Value: "$x = $x - 1"}}, ReportTemplate: "replace `$$` with `$x--`", - WhereExpr: ir.FilterExpr{Line: 305, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 311, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 307, - SyntaxPatterns: []ir.PatternString{{Line: 307, Value: "$x = $x + $y"}}, + Line: 313, + SyntaxPatterns: []ir.PatternString{{Line: 313, Value: "$x = $x + $y"}}, ReportTemplate: "replace `$$` with `$x += $y`", - WhereExpr: ir.FilterExpr{Line: 307, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 313, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 308, - SyntaxPatterns: []ir.PatternString{{Line: 308, Value: "$x = $x - $y"}}, + Line: 314, + SyntaxPatterns: []ir.PatternString{{Line: 314, Value: "$x = $x - $y"}}, ReportTemplate: "replace `$$` with `$x -= $y`", - WhereExpr: ir.FilterExpr{Line: 308, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 314, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 310, - SyntaxPatterns: []ir.PatternString{{Line: 310, Value: "$x = $x * $y"}}, + Line: 316, + SyntaxPatterns: []ir.PatternString{{Line: 316, Value: "$x = $x * $y"}}, ReportTemplate: "replace `$$` with `$x *= $y`", - WhereExpr: ir.FilterExpr{Line: 310, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 316, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 311, - SyntaxPatterns: []ir.PatternString{{Line: 311, Value: "$x = $x / $y"}}, + Line: 317, + SyntaxPatterns: []ir.PatternString{{Line: 317, Value: "$x = $x / $y"}}, ReportTemplate: "replace `$$` with `$x /= $y`", - WhereExpr: ir.FilterExpr{Line: 311, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 317, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 312, - SyntaxPatterns: []ir.PatternString{{Line: 312, Value: "$x = $x % $y"}}, + Line: 318, + SyntaxPatterns: []ir.PatternString{{Line: 318, Value: "$x = $x % $y"}}, ReportTemplate: "replace `$$` with `$x %= $y`", - WhereExpr: ir.FilterExpr{Line: 312, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 318, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 313, - SyntaxPatterns: []ir.PatternString{{Line: 313, Value: "$x = $x & $y"}}, + Line: 319, + SyntaxPatterns: []ir.PatternString{{Line: 319, Value: "$x = $x & $y"}}, ReportTemplate: "replace `$$` with `$x &= $y`", - WhereExpr: ir.FilterExpr{Line: 313, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 319, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 314, - SyntaxPatterns: []ir.PatternString{{Line: 314, Value: "$x = $x | $y"}}, + Line: 320, + SyntaxPatterns: []ir.PatternString{{Line: 320, Value: "$x = $x | $y"}}, ReportTemplate: "replace `$$` with `$x |= $y`", - WhereExpr: ir.FilterExpr{Line: 314, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 320, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 315, - SyntaxPatterns: []ir.PatternString{{Line: 315, Value: "$x = $x ^ $y"}}, + Line: 321, + SyntaxPatterns: []ir.PatternString{{Line: 321, Value: "$x = $x ^ $y"}}, ReportTemplate: "replace `$$` with `$x ^= $y`", - WhereExpr: ir.FilterExpr{Line: 315, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 321, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 316, - SyntaxPatterns: []ir.PatternString{{Line: 316, Value: "$x = $x << $y"}}, + Line: 322, + SyntaxPatterns: []ir.PatternString{{Line: 322, Value: "$x = $x << $y"}}, ReportTemplate: "replace `$$` with `$x <<= $y`", - WhereExpr: ir.FilterExpr{Line: 316, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 322, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 317, - SyntaxPatterns: []ir.PatternString{{Line: 317, Value: "$x = $x >> $y"}}, + Line: 323, + SyntaxPatterns: []ir.PatternString{{Line: 323, Value: "$x = $x >> $y"}}, ReportTemplate: "replace `$$` with `$x >>= $y`", - WhereExpr: ir.FilterExpr{Line: 317, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 323, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 318, - SyntaxPatterns: []ir.PatternString{{Line: 318, Value: "$x = $x &^ $y"}}, + Line: 324, + SyntaxPatterns: []ir.PatternString{{Line: 324, Value: "$x = $x &^ $y"}}, ReportTemplate: "replace `$$` with `$x &^= $y`", - WhereExpr: ir.FilterExpr{Line: 318, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 324, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, }, }, { - Line: 325, + Line: 331, Name: "preferWriteByte", MatcherName: "m", DocTags: []string{"performance", "experimental", "opinionated"}, @@ -1143,45 +1167,45 @@ var PrecompiledRules = &ir.File{ DocBefore: "w.WriteRune('\\n')", DocAfter: "w.WriteByte('\\n')", Rules: []ir.Rule{{ - Line: 329, - SyntaxPatterns: []ir.PatternString{{Line: 329, Value: "$w.WriteRune($c)"}}, + Line: 335, + SyntaxPatterns: []ir.PatternString{{Line: 335, Value: "$w.WriteRune($c)"}}, ReportTemplate: "consider writing single byte rune $c with $w.WriteByte($c)", WhereExpr: ir.FilterExpr{ - Line: 330, + Line: 336, Op: ir.FilterAndOp, Src: "m[\"w\"].Type.Implements(\"io.ByteWriter\") && (m[\"c\"].Const && m[\"c\"].Value.Int() < runeSelf)", Args: []ir.FilterExpr{ { - Line: 330, + Line: 336, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.ByteWriter\")", Value: "w", - Args: []ir.FilterExpr{{Line: 330, Op: ir.FilterStringOp, Src: "\"io.ByteWriter\"", Value: "io.ByteWriter"}}, + Args: []ir.FilterExpr{{Line: 336, Op: ir.FilterStringOp, Src: "\"io.ByteWriter\"", Value: "io.ByteWriter"}}, }, { - Line: 330, + Line: 336, Op: ir.FilterAndOp, Src: "(m[\"c\"].Const && m[\"c\"].Value.Int() < runeSelf)", Args: []ir.FilterExpr{ { - Line: 330, + Line: 336, Op: ir.FilterVarConstOp, Src: "m[\"c\"].Const", Value: "c", }, { - Line: 330, + Line: 336, Op: ir.FilterLtOp, Src: "m[\"c\"].Value.Int() < runeSelf", Args: []ir.FilterExpr{ { - Line: 330, + Line: 336, Op: ir.FilterVarValueIntOp, Src: "m[\"c\"].Value.Int()", Value: "c", }, { - Line: 330, + Line: 336, Op: ir.FilterIntOp, Src: "runeSelf", Value: int64(128), @@ -1195,7 +1219,7 @@ var PrecompiledRules = &ir.File{ }}, }, { - Line: 338, + Line: 344, Name: "preferFprint", MatcherName: "m", DocTags: []string{"performance", "experimental"}, @@ -1204,139 +1228,139 @@ var PrecompiledRules = &ir.File{ DocAfter: "fmt.Fprintf(w, \"%x\", 10)", Rules: []ir.Rule{ { - Line: 339, - SyntaxPatterns: []ir.PatternString{{Line: 339, Value: "$w.Write([]byte(fmt.Sprint($*args)))"}}, + Line: 345, + SyntaxPatterns: []ir.PatternString{{Line: 345, Value: "$w.Write([]byte(fmt.Sprint($*args)))"}}, ReportTemplate: "fmt.Fprint($w, $args) should be preferred to the $$", SuggestTemplate: "fmt.Fprint($w, $args)", WhereExpr: ir.FilterExpr{ - Line: 340, + Line: 346, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.Writer\")", Value: "w", - Args: []ir.FilterExpr{{Line: 340, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, + Args: []ir.FilterExpr{{Line: 346, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, }, }, { - Line: 344, - SyntaxPatterns: []ir.PatternString{{Line: 344, Value: "$w.Write([]byte(fmt.Sprintf($*args)))"}}, + Line: 350, + SyntaxPatterns: []ir.PatternString{{Line: 350, Value: "$w.Write([]byte(fmt.Sprintf($*args)))"}}, ReportTemplate: "fmt.Fprintf($w, $args) should be preferred to the $$", SuggestTemplate: "fmt.Fprintf($w, $args)", WhereExpr: ir.FilterExpr{ - Line: 345, + Line: 351, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.Writer\")", Value: "w", - Args: []ir.FilterExpr{{Line: 345, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, + Args: []ir.FilterExpr{{Line: 351, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, }, }, { - Line: 349, - SyntaxPatterns: []ir.PatternString{{Line: 349, Value: "$w.Write([]byte(fmt.Sprintln($*args)))"}}, + Line: 355, + SyntaxPatterns: []ir.PatternString{{Line: 355, Value: "$w.Write([]byte(fmt.Sprintln($*args)))"}}, ReportTemplate: "fmt.Fprintln($w, $args) should be preferred to the $$", SuggestTemplate: "fmt.Fprintln($w, $args)", WhereExpr: ir.FilterExpr{ - Line: 350, + Line: 356, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.Writer\")", Value: "w", - Args: []ir.FilterExpr{{Line: 350, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, + Args: []ir.FilterExpr{{Line: 356, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, }, }, { - Line: 354, - SyntaxPatterns: []ir.PatternString{{Line: 354, Value: "io.WriteString($w, fmt.Sprint($*args))"}}, + Line: 360, + SyntaxPatterns: []ir.PatternString{{Line: 360, Value: "io.WriteString($w, fmt.Sprint($*args))"}}, ReportTemplate: "suggestion: fmt.Fprint($w, $args)", SuggestTemplate: "fmt.Fprint($w, $args)", }, { - Line: 355, - SyntaxPatterns: []ir.PatternString{{Line: 355, Value: "io.WriteString($w, fmt.Sprintf($*args))"}}, + Line: 361, + SyntaxPatterns: []ir.PatternString{{Line: 361, Value: "io.WriteString($w, fmt.Sprintf($*args))"}}, ReportTemplate: "suggestion: fmt.Fprintf($w, $args)", SuggestTemplate: "fmt.Fprintf($w, $args)", }, { - Line: 356, - SyntaxPatterns: []ir.PatternString{{Line: 356, Value: "io.WriteString($w, fmt.Sprintln($*args))"}}, + Line: 362, + SyntaxPatterns: []ir.PatternString{{Line: 362, Value: "io.WriteString($w, fmt.Sprintln($*args))"}}, ReportTemplate: "suggestion: fmt.Fprintln($w, $args)", SuggestTemplate: "fmt.Fprintln($w, $args)", }, { - Line: 358, - SyntaxPatterns: []ir.PatternString{{Line: 358, Value: "$w.WriteString(fmt.Sprint($*args))"}}, + Line: 364, + SyntaxPatterns: []ir.PatternString{{Line: 364, Value: "$w.WriteString(fmt.Sprint($*args))"}}, ReportTemplate: "suggestion: fmt.Fprint($w, $args)", SuggestTemplate: "fmt.Fprint($w, $args)", WhereExpr: ir.FilterExpr{ - Line: 359, + Line: 365, Op: ir.FilterAndOp, Src: "m[\"w\"].Type.Implements(\"io.Writer\") && m[\"w\"].Type.Implements(\"io.StringWriter\")", Args: []ir.FilterExpr{ { - Line: 359, + Line: 365, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.Writer\")", Value: "w", - Args: []ir.FilterExpr{{Line: 359, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, + Args: []ir.FilterExpr{{Line: 365, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, }, { - Line: 359, + Line: 365, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.StringWriter\")", Value: "w", - Args: []ir.FilterExpr{{Line: 359, Op: ir.FilterStringOp, Src: "\"io.StringWriter\"", Value: "io.StringWriter"}}, + Args: []ir.FilterExpr{{Line: 365, Op: ir.FilterStringOp, Src: "\"io.StringWriter\"", Value: "io.StringWriter"}}, }, }, }, }, { - Line: 361, - SyntaxPatterns: []ir.PatternString{{Line: 361, Value: "$w.WriteString(fmt.Sprintf($*args))"}}, + Line: 367, + SyntaxPatterns: []ir.PatternString{{Line: 367, Value: "$w.WriteString(fmt.Sprintf($*args))"}}, ReportTemplate: "suggestion: fmt.Fprintf($w, $args)", SuggestTemplate: "fmt.Fprintf($w, $args)", WhereExpr: ir.FilterExpr{ - Line: 362, + Line: 368, Op: ir.FilterAndOp, Src: "m[\"w\"].Type.Implements(\"io.Writer\") && m[\"w\"].Type.Implements(\"io.StringWriter\")", Args: []ir.FilterExpr{ { - Line: 362, + Line: 368, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.Writer\")", Value: "w", - Args: []ir.FilterExpr{{Line: 362, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, + Args: []ir.FilterExpr{{Line: 368, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, }, { - Line: 362, + Line: 368, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.StringWriter\")", Value: "w", - Args: []ir.FilterExpr{{Line: 362, Op: ir.FilterStringOp, Src: "\"io.StringWriter\"", Value: "io.StringWriter"}}, + Args: []ir.FilterExpr{{Line: 368, Op: ir.FilterStringOp, Src: "\"io.StringWriter\"", Value: "io.StringWriter"}}, }, }, }, }, { - Line: 364, - SyntaxPatterns: []ir.PatternString{{Line: 364, Value: "$w.WriteString(fmt.Sprintln($*args))"}}, + Line: 370, + SyntaxPatterns: []ir.PatternString{{Line: 370, Value: "$w.WriteString(fmt.Sprintln($*args))"}}, ReportTemplate: "suggestion: fmt.Fprintln($w, $args)", SuggestTemplate: "fmt.Fprintln($w, $args)", WhereExpr: ir.FilterExpr{ - Line: 365, + Line: 371, Op: ir.FilterAndOp, Src: "m[\"w\"].Type.Implements(\"io.Writer\") && m[\"w\"].Type.Implements(\"io.StringWriter\")", Args: []ir.FilterExpr{ { - Line: 365, + Line: 371, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.Writer\")", Value: "w", - Args: []ir.FilterExpr{{Line: 365, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, + Args: []ir.FilterExpr{{Line: 371, Op: ir.FilterStringOp, Src: "\"io.Writer\"", Value: "io.Writer"}}, }, { - Line: 365, + Line: 371, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.StringWriter\")", Value: "w", - Args: []ir.FilterExpr{{Line: 365, Op: ir.FilterStringOp, Src: "\"io.StringWriter\"", Value: "io.StringWriter"}}, + Args: []ir.FilterExpr{{Line: 371, Op: ir.FilterStringOp, Src: "\"io.StringWriter\"", Value: "io.StringWriter"}}, }, }, }, @@ -1344,7 +1368,7 @@ var PrecompiledRules = &ir.File{ }, }, { - Line: 373, + Line: 379, Name: "dupArg", MatcherName: "m", DocTags: []string{"diagnostic"}, @@ -1353,62 +1377,62 @@ var PrecompiledRules = &ir.File{ DocAfter: "copy(dst, src)", Rules: []ir.Rule{ { - Line: 374, + Line: 380, SyntaxPatterns: []ir.PatternString{ - {Line: 374, Value: "$x.Equal($x)"}, - {Line: 374, Value: "$x.Equals($x)"}, - {Line: 374, Value: "$x.Compare($x)"}, - {Line: 374, Value: "$x.Cmp($x)"}, + {Line: 380, Value: "$x.Equal($x)"}, + {Line: 380, Value: "$x.Equals($x)"}, + {Line: 380, Value: "$x.Compare($x)"}, + {Line: 380, Value: "$x.Cmp($x)"}, }, ReportTemplate: "suspicious method call with the same argument and receiver", - WhereExpr: ir.FilterExpr{Line: 375, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 381, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, { - Line: 378, + Line: 384, SyntaxPatterns: []ir.PatternString{ - {Line: 378, Value: "copy($x, $x)"}, - {Line: 379, Value: "math.Max($x, $x)"}, - {Line: 380, Value: "math.Min($x, $x)"}, - {Line: 381, Value: "reflect.Copy($x, $x)"}, - {Line: 382, Value: "reflect.DeepEqual($x, $x)"}, - {Line: 383, Value: "strings.Contains($x, $x)"}, - {Line: 384, Value: "strings.Compare($x, $x)"}, - {Line: 385, Value: "strings.EqualFold($x, $x)"}, - {Line: 386, Value: "strings.HasPrefix($x, $x)"}, - {Line: 387, Value: "strings.HasSuffix($x, $x)"}, - {Line: 388, Value: "strings.Index($x, $x)"}, - {Line: 389, Value: "strings.LastIndex($x, $x)"}, - {Line: 390, Value: "strings.Split($x, $x)"}, - {Line: 391, Value: "strings.SplitAfter($x, $x)"}, - {Line: 392, Value: "strings.SplitAfterN($x, $x, $_)"}, - {Line: 393, Value: "strings.SplitN($x, $x, $_)"}, - {Line: 394, Value: "strings.Replace($_, $x, $x, $_)"}, - {Line: 395, Value: "strings.ReplaceAll($_, $x, $x)"}, - {Line: 396, Value: "bytes.Contains($x, $x)"}, - {Line: 397, Value: "bytes.Compare($x, $x)"}, - {Line: 398, Value: "bytes.Equal($x, $x)"}, - {Line: 399, Value: "bytes.EqualFold($x, $x)"}, - {Line: 400, Value: "bytes.HasPrefix($x, $x)"}, - {Line: 401, Value: "bytes.HasSuffix($x, $x)"}, - {Line: 402, Value: "bytes.Index($x, $x)"}, - {Line: 403, Value: "bytes.LastIndex($x, $x)"}, - {Line: 404, Value: "bytes.Split($x, $x)"}, - {Line: 405, Value: "bytes.SplitAfter($x, $x)"}, - {Line: 406, Value: "bytes.SplitAfterN($x, $x, $_)"}, - {Line: 407, Value: "bytes.SplitN($x, $x, $_)"}, - {Line: 408, Value: "bytes.Replace($_, $x, $x, $_)"}, - {Line: 409, Value: "bytes.ReplaceAll($_, $x, $x)"}, - {Line: 410, Value: "types.Identical($x, $x)"}, - {Line: 411, Value: "types.IdenticalIgnoreTags($x, $x)"}, - {Line: 412, Value: "draw.Draw($x, $_, $x, $_, $_)"}, + {Line: 384, Value: "copy($x, $x)"}, + {Line: 385, Value: "math.Max($x, $x)"}, + {Line: 386, Value: "math.Min($x, $x)"}, + {Line: 387, Value: "reflect.Copy($x, $x)"}, + {Line: 388, Value: "reflect.DeepEqual($x, $x)"}, + {Line: 389, Value: "strings.Contains($x, $x)"}, + {Line: 390, Value: "strings.Compare($x, $x)"}, + {Line: 391, Value: "strings.EqualFold($x, $x)"}, + {Line: 392, Value: "strings.HasPrefix($x, $x)"}, + {Line: 393, Value: "strings.HasSuffix($x, $x)"}, + {Line: 394, Value: "strings.Index($x, $x)"}, + {Line: 395, Value: "strings.LastIndex($x, $x)"}, + {Line: 396, Value: "strings.Split($x, $x)"}, + {Line: 397, Value: "strings.SplitAfter($x, $x)"}, + {Line: 398, Value: "strings.SplitAfterN($x, $x, $_)"}, + {Line: 399, Value: "strings.SplitN($x, $x, $_)"}, + {Line: 400, Value: "strings.Replace($_, $x, $x, $_)"}, + {Line: 401, Value: "strings.ReplaceAll($_, $x, $x)"}, + {Line: 402, Value: "bytes.Contains($x, $x)"}, + {Line: 403, Value: "bytes.Compare($x, $x)"}, + {Line: 404, Value: "bytes.Equal($x, $x)"}, + {Line: 405, Value: "bytes.EqualFold($x, $x)"}, + {Line: 406, Value: "bytes.HasPrefix($x, $x)"}, + {Line: 407, Value: "bytes.HasSuffix($x, $x)"}, + {Line: 408, Value: "bytes.Index($x, $x)"}, + {Line: 409, Value: "bytes.LastIndex($x, $x)"}, + {Line: 410, Value: "bytes.Split($x, $x)"}, + {Line: 411, Value: "bytes.SplitAfter($x, $x)"}, + {Line: 412, Value: "bytes.SplitAfterN($x, $x, $_)"}, + {Line: 413, Value: "bytes.SplitN($x, $x, $_)"}, + {Line: 414, Value: "bytes.Replace($_, $x, $x, $_)"}, + {Line: 415, Value: "bytes.ReplaceAll($_, $x, $x)"}, + {Line: 416, Value: "types.Identical($x, $x)"}, + {Line: 417, Value: "types.IdenticalIgnoreTags($x, $x)"}, + {Line: 418, Value: "draw.Draw($x, $_, $x, $_, $_)"}, }, ReportTemplate: "suspicious duplicated args in $$", - WhereExpr: ir.FilterExpr{Line: 413, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + WhereExpr: ir.FilterExpr{Line: 419, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, }, }, }, { - Line: 421, + Line: 427, Name: "returnAfterHttpError", MatcherName: "m", DocTags: []string{"diagnostic", "experimental"}, @@ -1416,14 +1440,14 @@ var PrecompiledRules = &ir.File{ DocBefore: "if err != nil { http.Error(...); }", DocAfter: "if err != nil { http.Error(...); return; }", Rules: []ir.Rule{{ - Line: 422, - SyntaxPatterns: []ir.PatternString{{Line: 422, Value: "if $_ { $*_; http.Error($w, $err, $code) }"}}, + Line: 428, + SyntaxPatterns: []ir.PatternString{{Line: 428, Value: "if $_ { $*_; http.Error($w, $err, $code) }"}}, ReportTemplate: "Possibly return is missed after the http.Error call", LocationVar: "w", }}, }, { - Line: 431, + Line: 437, Name: "preferFilepathJoin", MatcherName: "m", DocTags: []string{"style", "experimental"}, @@ -1431,35 +1455,35 @@ var PrecompiledRules = &ir.File{ DocBefore: "x + string(os.PathSeparator) + y", DocAfter: "filepath.Join(x, y)", Rules: []ir.Rule{{ - Line: 432, - SyntaxPatterns: []ir.PatternString{{Line: 432, Value: "$x + string(os.PathSeparator) + $y"}}, + Line: 438, + SyntaxPatterns: []ir.PatternString{{Line: 438, Value: "$x + string(os.PathSeparator) + $y"}}, ReportTemplate: "filepath.Join($x, $y) should be preferred to the $$", SuggestTemplate: "filepath.Join($x, $y)", WhereExpr: ir.FilterExpr{ - Line: 433, + Line: 439, Op: ir.FilterAndOp, Src: "m[\"x\"].Type.Is(`string`) && m[\"y\"].Type.Is(`string`)", Args: []ir.FilterExpr{ { - Line: 433, + Line: 439, Op: ir.FilterVarTypeIsOp, Src: "m[\"x\"].Type.Is(`string`)", Value: "x", - Args: []ir.FilterExpr{{Line: 433, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + Args: []ir.FilterExpr{{Line: 439, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, }, { - Line: 433, + Line: 439, Op: ir.FilterVarTypeIsOp, Src: "m[\"y\"].Type.Is(`string`)", Value: "y", - Args: []ir.FilterExpr{{Line: 433, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + Args: []ir.FilterExpr{{Line: 439, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, }, }, }, }}, }, { - Line: 442, + Line: 448, Name: "preferStringWriter", MatcherName: "m", DocTags: []string{"performance", "experimental"}, @@ -1468,35 +1492,35 @@ var PrecompiledRules = &ir.File{ DocAfter: "w.WriteString(\"foo\")", Rules: []ir.Rule{ { - Line: 443, - SyntaxPatterns: []ir.PatternString{{Line: 443, Value: "$w.Write([]byte($s))"}}, + Line: 449, + SyntaxPatterns: []ir.PatternString{{Line: 449, Value: "$w.Write([]byte($s))"}}, ReportTemplate: "$w.WriteString($s) should be preferred to the $$", SuggestTemplate: "$w.WriteString($s)", WhereExpr: ir.FilterExpr{ - Line: 444, + Line: 450, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.StringWriter\")", Value: "w", - Args: []ir.FilterExpr{{Line: 444, Op: ir.FilterStringOp, Src: "\"io.StringWriter\"", Value: "io.StringWriter"}}, + Args: []ir.FilterExpr{{Line: 450, Op: ir.FilterStringOp, Src: "\"io.StringWriter\"", Value: "io.StringWriter"}}, }, }, { - Line: 448, - SyntaxPatterns: []ir.PatternString{{Line: 448, Value: "io.WriteString($w, $s)"}}, + Line: 454, + SyntaxPatterns: []ir.PatternString{{Line: 454, Value: "io.WriteString($w, $s)"}}, ReportTemplate: "$w.WriteString($s) should be preferred to the $$", SuggestTemplate: "$w.WriteString($s)", WhereExpr: ir.FilterExpr{ - Line: 449, + Line: 455, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"w\"].Type.Implements(\"io.StringWriter\")", Value: "w", - Args: []ir.FilterExpr{{Line: 449, Op: ir.FilterStringOp, Src: "\"io.StringWriter\"", Value: "io.StringWriter"}}, + Args: []ir.FilterExpr{{Line: 455, Op: ir.FilterStringOp, Src: "\"io.StringWriter\"", Value: "io.StringWriter"}}, }, }, }, }, { - Line: 458, + Line: 464, Name: "sliceClear", MatcherName: "m", DocTags: []string{"performance", "experimental"}, @@ -1504,22 +1528,22 @@ var PrecompiledRules = &ir.File{ DocBefore: "for i := 0; i < len(buf); i++ { buf[i] = 0 }", DocAfter: "for i := range buf { buf[i] = 0 }", Rules: []ir.Rule{{ - Line: 459, - SyntaxPatterns: []ir.PatternString{{Line: 459, Value: "for $i := 0; $i < len($xs); $i++ { $xs[$i] = $zero }"}}, + Line: 465, + SyntaxPatterns: []ir.PatternString{{Line: 465, Value: "for $i := 0; $i < len($xs); $i++ { $xs[$i] = $zero }"}}, ReportTemplate: "rewrite as for-range so compiler can recognize this pattern", WhereExpr: ir.FilterExpr{ - Line: 460, + Line: 466, Op: ir.FilterEqOp, Src: "m[\"zero\"].Value.Int() == 0", Args: []ir.FilterExpr{ { - Line: 460, + Line: 466, Op: ir.FilterVarValueIntOp, Src: "m[\"zero\"].Value.Int()", Value: "zero", }, { - Line: 460, + Line: 466, Op: ir.FilterIntOp, Src: "0", Value: int64(0), @@ -1529,7 +1553,7 @@ var PrecompiledRules = &ir.File{ }}, }, { - Line: 468, + Line: 474, Name: "syncMapLoadAndDelete", MatcherName: "m", DocTags: []string{"diagnostic", "experimental"}, @@ -1537,33 +1561,33 @@ var PrecompiledRules = &ir.File{ DocBefore: "v, ok := m.Load(k); if ok { m.Delete($k); f(v); }", DocAfter: "v, deleted := m.LoadAndDelete(k); if deleted { f(v) }", Rules: []ir.Rule{{ - Line: 469, - SyntaxPatterns: []ir.PatternString{{Line: 469, Value: "$_, $ok := $m.Load($k); if $ok { $m.Delete($k); $*_ }"}}, + Line: 475, + SyntaxPatterns: []ir.PatternString{{Line: 475, Value: "$_, $ok := $m.Load($k); if $ok { $m.Delete($k); $*_ }"}}, ReportTemplate: "use $m.LoadAndDelete to perform load+delete operations atomically", WhereExpr: ir.FilterExpr{ - Line: 470, + Line: 476, Op: ir.FilterAndOp, Src: "m.GoVersion().GreaterEqThan(\"1.15\") &&\n\tm[\"m\"].Type.Is(`*sync.Map`)", Args: []ir.FilterExpr{ { - Line: 470, + Line: 476, Op: ir.FilterGoVersionGreaterEqThanOp, Src: "m.GoVersion().GreaterEqThan(\"1.15\")", Value: "1.15", }, { - Line: 471, + Line: 477, Op: ir.FilterVarTypeIsOp, Src: "m[\"m\"].Type.Is(`*sync.Map`)", Value: "m", - Args: []ir.FilterExpr{{Line: 471, Op: ir.FilterStringOp, Src: "`*sync.Map`", Value: "*sync.Map"}}, + Args: []ir.FilterExpr{{Line: 477, Op: ir.FilterStringOp, Src: "`*sync.Map`", Value: "*sync.Map"}}, }, }, }, }}, }, { - Line: 479, + Line: 485, Name: "sprintfQuotedString", MatcherName: "m", DocTags: []string{"diagnostic", "experimental"}, @@ -1571,34 +1595,34 @@ var PrecompiledRules = &ir.File{ DocBefore: "fmt.Sprintf(`\"%s\"`, s)", DocAfter: "fmt.Sprintf(`%q`, s)", Rules: []ir.Rule{{ - Line: 480, - SyntaxPatterns: []ir.PatternString{{Line: 480, Value: "fmt.Sprintf($s, $*_)"}}, + Line: 486, + SyntaxPatterns: []ir.PatternString{{Line: 486, Value: "fmt.Sprintf($s, $*_)"}}, ReportTemplate: "use %q instead of \"%s\" for quoted strings", WhereExpr: ir.FilterExpr{ - Line: 481, + Line: 487, Op: ir.FilterOrOp, Src: "m[\"s\"].Text.Matches(\"^`.*\\\"%s\\\".*`$\") ||\n\tm[\"s\"].Text.Matches(`^\".*\\\\\"%s\\\\\".*\"$`)", Args: []ir.FilterExpr{ { - Line: 481, + Line: 487, Op: ir.FilterVarTextMatchesOp, Src: "m[\"s\"].Text.Matches(\"^`.*\\\"%s\\\".*`$\")", Value: "s", - Args: []ir.FilterExpr{{Line: 481, Op: ir.FilterStringOp, Src: "\"^`.*\\\"%s\\\".*`$\"", Value: "^`.*\"%s\".*`$"}}, + Args: []ir.FilterExpr{{Line: 487, Op: ir.FilterStringOp, Src: "\"^`.*\\\"%s\\\".*`$\"", Value: "^`.*\"%s\".*`$"}}, }, { - Line: 482, + Line: 488, Op: ir.FilterVarTextMatchesOp, Src: "m[\"s\"].Text.Matches(`^\".*\\\\\"%s\\\\\".*\"$`)", Value: "s", - Args: []ir.FilterExpr{{Line: 482, Op: ir.FilterStringOp, Src: "`^\".*\\\\\"%s\\\\\".*\"$`", Value: "^\".*\\\\\"%s\\\\\".*\"$"}}, + Args: []ir.FilterExpr{{Line: 488, Op: ir.FilterStringOp, Src: "`^\".*\\\\\"%s\\\\\".*\"$`", Value: "^\".*\\\\\"%s\\\\\".*\"$"}}, }, }, }, }}, }, { - Line: 490, + Line: 496, Name: "offBy1", MatcherName: "m", DocTags: []string{"diagnostic"}, @@ -1607,80 +1631,80 @@ var PrecompiledRules = &ir.File{ DocAfter: "xs[len(xs)-1]", Rules: []ir.Rule{ { - Line: 491, - SyntaxPatterns: []ir.PatternString{{Line: 491, Value: "$x[len($x)]"}}, + Line: 497, + SyntaxPatterns: []ir.PatternString{{Line: 497, Value: "$x[len($x)]"}}, ReportTemplate: "index expr always panics; maybe you wanted $x[len($x)-1]?", SuggestTemplate: "$x[len($x)-1]", WhereExpr: ir.FilterExpr{ - Line: 492, + Line: 498, Op: ir.FilterAndOp, Src: "m[\"x\"].Pure && m[\"x\"].Type.Is(`[]$_`)", Args: []ir.FilterExpr{ - {Line: 492, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + {Line: 498, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, { - Line: 492, + Line: 498, Op: ir.FilterVarTypeIsOp, Src: "m[\"x\"].Type.Is(`[]$_`)", Value: "x", - Args: []ir.FilterExpr{{Line: 492, Op: ir.FilterStringOp, Src: "`[]$_`", Value: "[]$_"}}, + Args: []ir.FilterExpr{{Line: 498, Op: ir.FilterStringOp, Src: "`[]$_`", Value: "[]$_"}}, }, }, }, }, { - Line: 499, + Line: 505, SyntaxPatterns: []ir.PatternString{ - {Line: 500, Value: "$i := strings.Index($s, $_); $_ := $slicing[$i:]"}, - {Line: 501, Value: "$i := strings.Index($s, $_); $_ = $slicing[$i:]"}, - {Line: 502, Value: "$i := bytes.Index($s, $_); $_ := $slicing[$i:]"}, - {Line: 503, Value: "$i := bytes.Index($s, $_); $_ = $slicing[$i:]"}, + {Line: 506, Value: "$i := strings.Index($s, $_); $_ := $slicing[$i:]"}, + {Line: 507, Value: "$i := strings.Index($s, $_); $_ = $slicing[$i:]"}, + {Line: 508, Value: "$i := bytes.Index($s, $_); $_ := $slicing[$i:]"}, + {Line: 509, Value: "$i := bytes.Index($s, $_); $_ = $slicing[$i:]"}, }, ReportTemplate: "Index() can return -1; maybe you wanted to do $s[$i+1:]", WhereExpr: ir.FilterExpr{ - Line: 504, + Line: 510, Op: ir.FilterEqOp, Src: "m[\"s\"].Text == m[\"slicing\"].Text", Args: []ir.FilterExpr{ - {Line: 504, Op: ir.FilterVarTextOp, Src: "m[\"s\"].Text", Value: "s"}, - {Line: 504, Op: ir.FilterVarTextOp, Src: "m[\"slicing\"].Text", Value: "slicing"}, + {Line: 510, Op: ir.FilterVarTextOp, Src: "m[\"s\"].Text", Value: "s"}, + {Line: 510, Op: ir.FilterVarTextOp, Src: "m[\"slicing\"].Text", Value: "slicing"}, }, }, LocationVar: "slicing", }, { - Line: 508, + Line: 514, SyntaxPatterns: []ir.PatternString{ - {Line: 509, Value: "$i := strings.Index($s, $_); $_ := $slicing[:$i]"}, - {Line: 510, Value: "$i := strings.Index($s, $_); $_ = $slicing[:$i]"}, - {Line: 511, Value: "$i := bytes.Index($s, $_); $_ := $slicing[:$i]"}, - {Line: 512, Value: "$i := bytes.Index($s, $_); $_ = $slicing[:$i]"}, + {Line: 515, Value: "$i := strings.Index($s, $_); $_ := $slicing[:$i]"}, + {Line: 516, Value: "$i := strings.Index($s, $_); $_ = $slicing[:$i]"}, + {Line: 517, Value: "$i := bytes.Index($s, $_); $_ := $slicing[:$i]"}, + {Line: 518, Value: "$i := bytes.Index($s, $_); $_ = $slicing[:$i]"}, }, ReportTemplate: "Index() can return -1; maybe you wanted to do $s[:$i+1]", WhereExpr: ir.FilterExpr{ - Line: 513, + Line: 519, Op: ir.FilterEqOp, Src: "m[\"s\"].Text == m[\"slicing\"].Text", Args: []ir.FilterExpr{ - {Line: 513, Op: ir.FilterVarTextOp, Src: "m[\"s\"].Text", Value: "s"}, - {Line: 513, Op: ir.FilterVarTextOp, Src: "m[\"slicing\"].Text", Value: "slicing"}, + {Line: 519, Op: ir.FilterVarTextOp, Src: "m[\"s\"].Text", Value: "s"}, + {Line: 519, Op: ir.FilterVarTextOp, Src: "m[\"slicing\"].Text", Value: "slicing"}, }, }, LocationVar: "slicing", }, { - Line: 517, + Line: 523, SyntaxPatterns: []ir.PatternString{ - {Line: 518, Value: "$s[strings.Index($s, $_):]"}, - {Line: 519, Value: "$s[:strings.Index($s, $_)]"}, - {Line: 520, Value: "$s[bytes.Index($s, $_):]"}, - {Line: 521, Value: "$s[:bytes.Index($s, $_)]"}, + {Line: 524, Value: "$s[strings.Index($s, $_):]"}, + {Line: 525, Value: "$s[:strings.Index($s, $_)]"}, + {Line: 526, Value: "$s[bytes.Index($s, $_):]"}, + {Line: 527, Value: "$s[:bytes.Index($s, $_)]"}, }, ReportTemplate: "Index() can return -1; maybe you wanted to do Index()+1", }, }, }, { - Line: 529, + Line: 535, Name: "unslice", MatcherName: "m", DocTags: []string{"style"}, @@ -1688,35 +1712,35 @@ var PrecompiledRules = &ir.File{ DocBefore: "copy(b[:], values...)", DocAfter: "copy(b, values...)", Rules: []ir.Rule{{ - Line: 530, - SyntaxPatterns: []ir.PatternString{{Line: 530, Value: "$s[:]"}}, + Line: 536, + SyntaxPatterns: []ir.PatternString{{Line: 536, Value: "$s[:]"}}, ReportTemplate: "could simplify $$ to $s", SuggestTemplate: "$s", WhereExpr: ir.FilterExpr{ - Line: 531, + Line: 537, Op: ir.FilterOrOp, Src: "m[\"s\"].Type.Is(`string`) || m[\"s\"].Type.Is(`[]$_`)", Args: []ir.FilterExpr{ { - Line: 531, + Line: 537, Op: ir.FilterVarTypeIsOp, Src: "m[\"s\"].Type.Is(`string`)", Value: "s", - Args: []ir.FilterExpr{{Line: 531, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, + Args: []ir.FilterExpr{{Line: 537, Op: ir.FilterStringOp, Src: "`string`", Value: "string"}}, }, { - Line: 531, + Line: 537, Op: ir.FilterVarTypeIsOp, Src: "m[\"s\"].Type.Is(`[]$_`)", Value: "s", - Args: []ir.FilterExpr{{Line: 531, Op: ir.FilterStringOp, Src: "`[]$_`", Value: "[]$_"}}, + Args: []ir.FilterExpr{{Line: 537, Op: ir.FilterStringOp, Src: "`[]$_`", Value: "[]$_"}}, }, }, }, }}, }, { - Line: 540, + Line: 546, Name: "yodaStyleExpr", MatcherName: "m", DocTags: []string{"style", "experimental"}, @@ -1725,105 +1749,105 @@ var PrecompiledRules = &ir.File{ DocAfter: "return ptr != nil", Rules: []ir.Rule{ { - Line: 541, - SyntaxPatterns: []ir.PatternString{{Line: 541, Value: "$constval != $x"}}, + Line: 547, + SyntaxPatterns: []ir.PatternString{{Line: 547, Value: "$constval != $x"}}, ReportTemplate: "consider to change order in expression to $x != $constval", WhereExpr: ir.FilterExpr{ - Line: 541, + Line: 547, Op: ir.FilterAndOp, Src: "m[\"constval\"].Node.Is(`BasicLit`) && !m[\"x\"].Node.Is(`BasicLit`)", Args: []ir.FilterExpr{ { - Line: 541, + Line: 547, Op: ir.FilterVarNodeIsOp, Src: "m[\"constval\"].Node.Is(`BasicLit`)", Value: "constval", - Args: []ir.FilterExpr{{Line: 541, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, + Args: []ir.FilterExpr{{Line: 547, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, }, { - Line: 541, + Line: 547, Op: ir.FilterNotOp, Src: "!m[\"x\"].Node.Is(`BasicLit`)", Args: []ir.FilterExpr{{ - Line: 541, + Line: 547, Op: ir.FilterVarNodeIsOp, Src: "m[\"x\"].Node.Is(`BasicLit`)", Value: "x", - Args: []ir.FilterExpr{{Line: 541, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, + Args: []ir.FilterExpr{{Line: 547, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, }}, }, }, }, }, { - Line: 543, - SyntaxPatterns: []ir.PatternString{{Line: 543, Value: "$constval == $x"}}, + Line: 549, + SyntaxPatterns: []ir.PatternString{{Line: 549, Value: "$constval == $x"}}, ReportTemplate: "consider to change order in expression to $x == $constval", WhereExpr: ir.FilterExpr{ - Line: 543, + Line: 549, Op: ir.FilterAndOp, Src: "m[\"constval\"].Node.Is(`BasicLit`) && !m[\"x\"].Node.Is(`BasicLit`)", Args: []ir.FilterExpr{ { - Line: 543, + Line: 549, Op: ir.FilterVarNodeIsOp, Src: "m[\"constval\"].Node.Is(`BasicLit`)", Value: "constval", - Args: []ir.FilterExpr{{Line: 543, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, + Args: []ir.FilterExpr{{Line: 549, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, }, { - Line: 543, + Line: 549, Op: ir.FilterNotOp, Src: "!m[\"x\"].Node.Is(`BasicLit`)", Args: []ir.FilterExpr{{ - Line: 543, + Line: 549, Op: ir.FilterVarNodeIsOp, Src: "m[\"x\"].Node.Is(`BasicLit`)", Value: "x", - Args: []ir.FilterExpr{{Line: 543, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, + Args: []ir.FilterExpr{{Line: 549, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, }}, }, }, }, }, { - Line: 546, - SyntaxPatterns: []ir.PatternString{{Line: 546, Value: "nil != $x"}}, + Line: 552, + SyntaxPatterns: []ir.PatternString{{Line: 552, Value: "nil != $x"}}, ReportTemplate: "consider to change order in expression to $x != nil", WhereExpr: ir.FilterExpr{ - Line: 546, + Line: 552, Op: ir.FilterNotOp, Src: "!m[\"x\"].Node.Is(`BasicLit`)", Args: []ir.FilterExpr{{ - Line: 546, + Line: 552, Op: ir.FilterVarNodeIsOp, Src: "m[\"x\"].Node.Is(`BasicLit`)", Value: "x", - Args: []ir.FilterExpr{{Line: 546, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, + Args: []ir.FilterExpr{{Line: 552, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, }}, }, }, { - Line: 548, - SyntaxPatterns: []ir.PatternString{{Line: 548, Value: "nil == $x"}}, + Line: 554, + SyntaxPatterns: []ir.PatternString{{Line: 554, Value: "nil == $x"}}, ReportTemplate: "consider to change order in expression to $x == nil", WhereExpr: ir.FilterExpr{ - Line: 548, + Line: 554, Op: ir.FilterNotOp, Src: "!m[\"x\"].Node.Is(`BasicLit`)", Args: []ir.FilterExpr{{ - Line: 548, + Line: 554, Op: ir.FilterVarNodeIsOp, Src: "m[\"x\"].Node.Is(`BasicLit`)", Value: "x", - Args: []ir.FilterExpr{{Line: 548, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, + Args: []ir.FilterExpr{{Line: 554, Op: ir.FilterStringOp, Src: "`BasicLit`", Value: "BasicLit"}}, }}, }, }, }, }, { - Line: 556, + Line: 562, Name: "equalFold", MatcherName: "m", DocTags: []string{"performance", "experimental"}, @@ -1832,114 +1856,114 @@ var PrecompiledRules = &ir.File{ DocAfter: "strings.EqualFold(x, y)", Rules: []ir.Rule{ { - Line: 565, + Line: 571, SyntaxPatterns: []ir.PatternString{ - {Line: 566, Value: "strings.ToLower($x) == $y"}, - {Line: 567, Value: "strings.ToLower($x) == strings.ToLower($y)"}, - {Line: 568, Value: "$x == strings.ToLower($y)"}, - {Line: 569, Value: "strings.ToUpper($x) == $y"}, - {Line: 570, Value: "strings.ToUpper($x) == strings.ToUpper($y)"}, - {Line: 571, Value: "$x == strings.ToUpper($y)"}, + {Line: 572, Value: "strings.ToLower($x) == $y"}, + {Line: 573, Value: "strings.ToLower($x) == strings.ToLower($y)"}, + {Line: 574, Value: "$x == strings.ToLower($y)"}, + {Line: 575, Value: "strings.ToUpper($x) == $y"}, + {Line: 576, Value: "strings.ToUpper($x) == strings.ToUpper($y)"}, + {Line: 577, Value: "$x == strings.ToUpper($y)"}, }, ReportTemplate: "consider replacing with strings.EqualFold($x, $y)", SuggestTemplate: "strings.EqualFold($x, $y)", WhereExpr: ir.FilterExpr{ - Line: 572, + Line: 578, Op: ir.FilterAndOp, Src: "m[\"x\"].Pure && m[\"y\"].Pure && m[\"x\"].Text != m[\"y\"].Text", Args: []ir.FilterExpr{ { - Line: 572, + Line: 578, Op: ir.FilterAndOp, Src: "m[\"x\"].Pure && m[\"y\"].Pure", Args: []ir.FilterExpr{ - {Line: 572, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, - {Line: 572, Op: ir.FilterVarPureOp, Src: "m[\"y\"].Pure", Value: "y"}, + {Line: 578, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + {Line: 578, Op: ir.FilterVarPureOp, Src: "m[\"y\"].Pure", Value: "y"}, }, }, { - Line: 572, + Line: 578, Op: ir.FilterNeqOp, Src: "m[\"x\"].Text != m[\"y\"].Text", Args: []ir.FilterExpr{ - {Line: 572, Op: ir.FilterVarTextOp, Src: "m[\"x\"].Text", Value: "x"}, - {Line: 572, Op: ir.FilterVarTextOp, Src: "m[\"y\"].Text", Value: "y"}, + {Line: 578, Op: ir.FilterVarTextOp, Src: "m[\"x\"].Text", Value: "x"}, + {Line: 578, Op: ir.FilterVarTextOp, Src: "m[\"y\"].Text", Value: "y"}, }, }, }, }, }, { - Line: 577, + Line: 583, SyntaxPatterns: []ir.PatternString{ - {Line: 578, Value: "strings.ToLower($x) != $y"}, - {Line: 579, Value: "strings.ToLower($x) != strings.ToLower($y)"}, - {Line: 580, Value: "$x != strings.ToLower($y)"}, - {Line: 581, Value: "strings.ToUpper($x) != $y"}, - {Line: 582, Value: "strings.ToUpper($x) != strings.ToUpper($y)"}, - {Line: 583, Value: "$x != strings.ToUpper($y)"}, + {Line: 584, Value: "strings.ToLower($x) != $y"}, + {Line: 585, Value: "strings.ToLower($x) != strings.ToLower($y)"}, + {Line: 586, Value: "$x != strings.ToLower($y)"}, + {Line: 587, Value: "strings.ToUpper($x) != $y"}, + {Line: 588, Value: "strings.ToUpper($x) != strings.ToUpper($y)"}, + {Line: 589, Value: "$x != strings.ToUpper($y)"}, }, ReportTemplate: "consider replacing with !strings.EqualFold($x, $y)", SuggestTemplate: "!strings.EqualFold($x, $y)", WhereExpr: ir.FilterExpr{ - Line: 584, + Line: 590, Op: ir.FilterAndOp, Src: "m[\"x\"].Pure && m[\"y\"].Pure && m[\"x\"].Text != m[\"y\"].Text", Args: []ir.FilterExpr{ { - Line: 584, + Line: 590, Op: ir.FilterAndOp, Src: "m[\"x\"].Pure && m[\"y\"].Pure", Args: []ir.FilterExpr{ - {Line: 584, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, - {Line: 584, Op: ir.FilterVarPureOp, Src: "m[\"y\"].Pure", Value: "y"}, + {Line: 590, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + {Line: 590, Op: ir.FilterVarPureOp, Src: "m[\"y\"].Pure", Value: "y"}, }, }, { - Line: 584, + Line: 590, Op: ir.FilterNeqOp, Src: "m[\"x\"].Text != m[\"y\"].Text", Args: []ir.FilterExpr{ - {Line: 584, Op: ir.FilterVarTextOp, Src: "m[\"x\"].Text", Value: "x"}, - {Line: 584, Op: ir.FilterVarTextOp, Src: "m[\"y\"].Text", Value: "y"}, + {Line: 590, Op: ir.FilterVarTextOp, Src: "m[\"x\"].Text", Value: "x"}, + {Line: 590, Op: ir.FilterVarTextOp, Src: "m[\"y\"].Text", Value: "y"}, }, }, }, }, }, { - Line: 589, + Line: 595, SyntaxPatterns: []ir.PatternString{ - {Line: 590, Value: "bytes.Equal(bytes.ToLower($x), $y)"}, - {Line: 591, Value: "bytes.Equal(bytes.ToLower($x), bytes.ToLower($y))"}, - {Line: 592, Value: "bytes.Equal($x, bytes.ToLower($y))"}, - {Line: 593, Value: "bytes.Equal(bytes.ToUpper($x), $y)"}, - {Line: 594, Value: "bytes.Equal(bytes.ToUpper($x), bytes.ToUpper($y))"}, - {Line: 595, Value: "bytes.Equal($x, bytes.ToUpper($y))"}, + {Line: 596, Value: "bytes.Equal(bytes.ToLower($x), $y)"}, + {Line: 597, Value: "bytes.Equal(bytes.ToLower($x), bytes.ToLower($y))"}, + {Line: 598, Value: "bytes.Equal($x, bytes.ToLower($y))"}, + {Line: 599, Value: "bytes.Equal(bytes.ToUpper($x), $y)"}, + {Line: 600, Value: "bytes.Equal(bytes.ToUpper($x), bytes.ToUpper($y))"}, + {Line: 601, Value: "bytes.Equal($x, bytes.ToUpper($y))"}, }, ReportTemplate: "consider replacing with bytes.EqualFold($x, $y)", SuggestTemplate: "bytes.EqualFold($x, $y)", WhereExpr: ir.FilterExpr{ - Line: 596, + Line: 602, Op: ir.FilterAndOp, Src: "m[\"x\"].Pure && m[\"y\"].Pure && m[\"x\"].Text != m[\"y\"].Text", Args: []ir.FilterExpr{ { - Line: 596, + Line: 602, Op: ir.FilterAndOp, Src: "m[\"x\"].Pure && m[\"y\"].Pure", Args: []ir.FilterExpr{ - {Line: 596, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, - {Line: 596, Op: ir.FilterVarPureOp, Src: "m[\"y\"].Pure", Value: "y"}, + {Line: 602, Op: ir.FilterVarPureOp, Src: "m[\"x\"].Pure", Value: "x"}, + {Line: 602, Op: ir.FilterVarPureOp, Src: "m[\"y\"].Pure", Value: "y"}, }, }, { - Line: 596, + Line: 602, Op: ir.FilterNeqOp, Src: "m[\"x\"].Text != m[\"y\"].Text", Args: []ir.FilterExpr{ - {Line: 596, Op: ir.FilterVarTextOp, Src: "m[\"x\"].Text", Value: "x"}, - {Line: 596, Op: ir.FilterVarTextOp, Src: "m[\"y\"].Text", Value: "y"}, + {Line: 602, Op: ir.FilterVarTextOp, Src: "m[\"x\"].Text", Value: "x"}, + {Line: 602, Op: ir.FilterVarTextOp, Src: "m[\"y\"].Text", Value: "y"}, }, }, }, @@ -1948,7 +1972,7 @@ var PrecompiledRules = &ir.File{ }, }, { - Line: 605, + Line: 611, Name: "argOrder", MatcherName: "m", DocTags: []string{"diagnostic"}, @@ -1956,45 +1980,45 @@ var PrecompiledRules = &ir.File{ DocBefore: "strings.HasPrefix(\"#\", userpass)", DocAfter: "strings.HasPrefix(userpass, \"#\")", Rules: []ir.Rule{{ - Line: 606, + Line: 612, SyntaxPatterns: []ir.PatternString{ - {Line: 607, Value: "strings.HasPrefix($lit, $s)"}, - {Line: 608, Value: "bytes.HasPrefix($lit, $s)"}, - {Line: 609, Value: "strings.HasSuffix($lit, $s)"}, - {Line: 610, Value: "bytes.HasSuffix($lit, $s)"}, - {Line: 611, Value: "strings.Contains($lit, $s)"}, - {Line: 612, Value: "bytes.Contains($lit, $s)"}, - {Line: 613, Value: "strings.TrimPrefix($lit, $s)"}, - {Line: 614, Value: "bytes.TrimPrefix($lit, $s)"}, - {Line: 615, Value: "strings.TrimSuffix($lit, $s)"}, - {Line: 616, Value: "bytes.TrimSuffix($lit, $s)"}, - {Line: 617, Value: "strings.Split($lit, $s)"}, - {Line: 618, Value: "bytes.Split($lit, $s)"}, + {Line: 613, Value: "strings.HasPrefix($lit, $s)"}, + {Line: 614, Value: "bytes.HasPrefix($lit, $s)"}, + {Line: 615, Value: "strings.HasSuffix($lit, $s)"}, + {Line: 616, Value: "bytes.HasSuffix($lit, $s)"}, + {Line: 617, Value: "strings.Contains($lit, $s)"}, + {Line: 618, Value: "bytes.Contains($lit, $s)"}, + {Line: 619, Value: "strings.TrimPrefix($lit, $s)"}, + {Line: 620, Value: "bytes.TrimPrefix($lit, $s)"}, + {Line: 621, Value: "strings.TrimSuffix($lit, $s)"}, + {Line: 622, Value: "bytes.TrimSuffix($lit, $s)"}, + {Line: 623, Value: "strings.Split($lit, $s)"}, + {Line: 624, Value: "bytes.Split($lit, $s)"}, }, ReportTemplate: "$lit and $s arguments order looks reversed", WhereExpr: ir.FilterExpr{ - Line: 619, + Line: 625, Op: ir.FilterAndOp, Src: "(m[\"lit\"].Const || m[\"lit\"].ConstSlice) &&\n\t!(m[\"s\"].Const || m[\"s\"].ConstSlice) &&\n\t!m[\"lit\"].Node.Is(`Ident`)", Args: []ir.FilterExpr{ { - Line: 619, + Line: 625, Op: ir.FilterAndOp, Src: "(m[\"lit\"].Const || m[\"lit\"].ConstSlice) &&\n\t!(m[\"s\"].Const || m[\"s\"].ConstSlice)", Args: []ir.FilterExpr{ { - Line: 619, + Line: 625, Op: ir.FilterOrOp, Src: "(m[\"lit\"].Const || m[\"lit\"].ConstSlice)", Args: []ir.FilterExpr{ { - Line: 619, + Line: 625, Op: ir.FilterVarConstOp, Src: "m[\"lit\"].Const", Value: "lit", }, { - Line: 619, + Line: 625, Op: ir.FilterVarConstSliceOp, Src: "m[\"lit\"].ConstSlice", Value: "lit", @@ -2002,22 +2026,22 @@ var PrecompiledRules = &ir.File{ }, }, { - Line: 620, + Line: 626, Op: ir.FilterNotOp, Src: "!(m[\"s\"].Const || m[\"s\"].ConstSlice)", Args: []ir.FilterExpr{{ - Line: 620, + Line: 626, Op: ir.FilterOrOp, Src: "(m[\"s\"].Const || m[\"s\"].ConstSlice)", Args: []ir.FilterExpr{ { - Line: 620, + Line: 626, Op: ir.FilterVarConstOp, Src: "m[\"s\"].Const", Value: "s", }, { - Line: 620, + Line: 626, Op: ir.FilterVarConstSliceOp, Src: "m[\"s\"].ConstSlice", Value: "s", @@ -2028,15 +2052,15 @@ var PrecompiledRules = &ir.File{ }, }, { - Line: 621, + Line: 627, Op: ir.FilterNotOp, Src: "!m[\"lit\"].Node.Is(`Ident`)", Args: []ir.FilterExpr{{ - Line: 621, + Line: 627, Op: ir.FilterVarNodeIsOp, Src: "m[\"lit\"].Node.Is(`Ident`)", Value: "lit", - Args: []ir.FilterExpr{{Line: 621, Op: ir.FilterStringOp, Src: "`Ident`", Value: "Ident"}}, + Args: []ir.FilterExpr{{Line: 627, Op: ir.FilterStringOp, Src: "`Ident`", Value: "Ident"}}, }}, }, }, @@ -2044,7 +2068,7 @@ var PrecompiledRules = &ir.File{ }}, }, { - Line: 629, + Line: 635, Name: "stringConcatSimplify", MatcherName: "m", DocTags: []string{"style", "experimental"}, @@ -2053,27 +2077,27 @@ var PrecompiledRules = &ir.File{ DocAfter: "x + \"_\" + y", Rules: []ir.Rule{ { - Line: 630, - SyntaxPatterns: []ir.PatternString{{Line: 630, Value: "strings.Join([]string{$x, $y}, \"\")"}}, + Line: 636, + SyntaxPatterns: []ir.PatternString{{Line: 636, Value: "strings.Join([]string{$x, $y}, \"\")"}}, ReportTemplate: "suggestion: $x + $y", SuggestTemplate: "$x + $y", }, { - Line: 631, - SyntaxPatterns: []ir.PatternString{{Line: 631, Value: "strings.Join([]string{$x, $y, $z}, \"\")"}}, + Line: 637, + SyntaxPatterns: []ir.PatternString{{Line: 637, Value: "strings.Join([]string{$x, $y, $z}, \"\")"}}, ReportTemplate: "suggestion: $x + $y + $z", SuggestTemplate: "$x + $y + $z", }, { - Line: 632, - SyntaxPatterns: []ir.PatternString{{Line: 632, Value: "strings.Join([]string{$x, $y}, $glue)"}}, + Line: 638, + SyntaxPatterns: []ir.PatternString{{Line: 638, Value: "strings.Join([]string{$x, $y}, $glue)"}}, ReportTemplate: "suggestion: $x + $glue + $y", SuggestTemplate: "$x + $glue + $y", }, }, }, { - Line: 639, + Line: 645, Name: "timeExprSimplify", MatcherName: "m", DocTags: []string{"style", "experimental"}, @@ -2082,39 +2106,39 @@ var PrecompiledRules = &ir.File{ DocAfter: "t.UnixMilli()", Rules: []ir.Rule{ { - Line: 644, - SyntaxPatterns: []ir.PatternString{{Line: 644, Value: "$t.Unix() / 1000"}}, + Line: 650, + SyntaxPatterns: []ir.PatternString{{Line: 650, Value: "$t.Unix() / 1000"}}, ReportTemplate: "use $t.UnixMilli() instead of $$", SuggestTemplate: "$t.UnixMilli()", WhereExpr: ir.FilterExpr{ - Line: 645, + Line: 651, Op: ir.FilterAndOp, Src: "m.GoVersion().GreaterEqThan(\"1.17\") && isTime(m[\"t\"])", Args: []ir.FilterExpr{ { - Line: 645, + Line: 651, Op: ir.FilterGoVersionGreaterEqThanOp, Src: "m.GoVersion().GreaterEqThan(\"1.17\")", Value: "1.17", }, { - Line: 645, + Line: 651, Op: ir.FilterOrOp, Src: "isTime(m[\"t\"])", Args: []ir.FilterExpr{ { - Line: 645, + Line: 651, Op: ir.FilterVarTypeIsOp, Src: "m[\"t\"].Type.Is(`time.Time`)", Value: "t", - Args: []ir.FilterExpr{{Line: 641, Op: ir.FilterStringOp, Src: "`time.Time`", Value: "time.Time"}}, + Args: []ir.FilterExpr{{Line: 647, Op: ir.FilterStringOp, Src: "`time.Time`", Value: "time.Time"}}, }, { - Line: 645, + Line: 651, Op: ir.FilterVarTypeIsOp, Src: "m[\"t\"].Type.Is(`*time.Time`)", Value: "t", - Args: []ir.FilterExpr{{Line: 641, Op: ir.FilterStringOp, Src: "`*time.Time`", Value: "*time.Time"}}, + Args: []ir.FilterExpr{{Line: 647, Op: ir.FilterStringOp, Src: "`*time.Time`", Value: "*time.Time"}}, }, }, }, @@ -2122,39 +2146,39 @@ var PrecompiledRules = &ir.File{ }, }, { - Line: 649, - SyntaxPatterns: []ir.PatternString{{Line: 649, Value: "$t.UnixNano() * 1000"}}, + Line: 655, + SyntaxPatterns: []ir.PatternString{{Line: 655, Value: "$t.UnixNano() * 1000"}}, ReportTemplate: "use $t.UnixMicro() instead of $$", SuggestTemplate: "$t.UnixMicro()", WhereExpr: ir.FilterExpr{ - Line: 650, + Line: 656, Op: ir.FilterAndOp, Src: "m.GoVersion().GreaterEqThan(\"1.17\") && isTime(m[\"t\"])", Args: []ir.FilterExpr{ { - Line: 650, + Line: 656, Op: ir.FilterGoVersionGreaterEqThanOp, Src: "m.GoVersion().GreaterEqThan(\"1.17\")", Value: "1.17", }, { - Line: 650, + Line: 656, Op: ir.FilterOrOp, Src: "isTime(m[\"t\"])", Args: []ir.FilterExpr{ { - Line: 650, + Line: 656, Op: ir.FilterVarTypeIsOp, Src: "m[\"t\"].Type.Is(`time.Time`)", Value: "t", - Args: []ir.FilterExpr{{Line: 641, Op: ir.FilterStringOp, Src: "`time.Time`", Value: "time.Time"}}, + Args: []ir.FilterExpr{{Line: 647, Op: ir.FilterStringOp, Src: "`time.Time`", Value: "time.Time"}}, }, { - Line: 650, + Line: 656, Op: ir.FilterVarTypeIsOp, Src: "m[\"t\"].Type.Is(`*time.Time`)", Value: "t", - Args: []ir.FilterExpr{{Line: 641, Op: ir.FilterStringOp, Src: "`*time.Time`", Value: "*time.Time"}}, + Args: []ir.FilterExpr{{Line: 647, Op: ir.FilterStringOp, Src: "`*time.Time`", Value: "*time.Time"}}, }, }, }, @@ -2164,72 +2188,7 @@ var PrecompiledRules = &ir.File{ }, }, { - Line: 659, - Name: "timeCmpSimplify", - MatcherName: "m", - DocTags: []string{"style", "experimental"}, - DocSummary: "Detects Before/After call of time.Time that can be simplified", - DocBefore: "!t.Before(tt)", - DocAfter: "t.After(tt)", - Rules: []ir.Rule{ - { - Line: 664, - SyntaxPatterns: []ir.PatternString{{Line: 664, Value: "!$t.Before($tt)"}}, - ReportTemplate: "suggestion: $t.After($tt)", - SuggestTemplate: "$t.After($tt)", - WhereExpr: ir.FilterExpr{ - Line: 665, - Op: ir.FilterOrOp, - Src: "isTime(m[\"t\"])", - Args: []ir.FilterExpr{ - { - Line: 665, - Op: ir.FilterVarTypeIsOp, - Src: "m[\"t\"].Type.Is(`time.Time`)", - Value: "t", - Args: []ir.FilterExpr{{Line: 661, Op: ir.FilterStringOp, Src: "`time.Time`", Value: "time.Time"}}, - }, - { - Line: 665, - Op: ir.FilterVarTypeIsOp, - Src: "m[\"t\"].Type.Is(`*time.Time`)", - Value: "t", - Args: []ir.FilterExpr{{Line: 661, Op: ir.FilterStringOp, Src: "`*time.Time`", Value: "*time.Time"}}, - }, - }, - }, - }, - { - Line: 668, - SyntaxPatterns: []ir.PatternString{{Line: 668, Value: "!$t.After($tt)"}}, - ReportTemplate: "suggestion: $t.Before($tt)", - SuggestTemplate: "$t.Before($tt)", - WhereExpr: ir.FilterExpr{ - Line: 669, - Op: ir.FilterOrOp, - Src: "isTime(m[\"t\"])", - Args: []ir.FilterExpr{ - { - Line: 669, - Op: ir.FilterVarTypeIsOp, - Src: "m[\"t\"].Type.Is(`time.Time`)", - Value: "t", - Args: []ir.FilterExpr{{Line: 661, Op: ir.FilterStringOp, Src: "`time.Time`", Value: "time.Time"}}, - }, - { - Line: 669, - Op: ir.FilterVarTypeIsOp, - Src: "m[\"t\"].Type.Is(`*time.Time`)", - Value: "t", - Args: []ir.FilterExpr{{Line: 661, Op: ir.FilterStringOp, Src: "`*time.Time`", Value: "*time.Time"}}, - }, - }, - }, - }, - }, - }, - { - Line: 677, + Line: 665, Name: "exposedSyncMutex", MatcherName: "m", DocTags: []string{"style", "experimental"}, @@ -2238,57 +2197,57 @@ var PrecompiledRules = &ir.File{ DocAfter: "type Foo struct{ ...; mu sync.Mutex; ... }", Rules: []ir.Rule{ { - Line: 682, - SyntaxPatterns: []ir.PatternString{{Line: 682, Value: "type $x struct { $*_; sync.Mutex; $*_ }"}}, + Line: 670, + SyntaxPatterns: []ir.PatternString{{Line: 670, Value: "type $x struct { $*_; sync.Mutex; $*_ }"}}, ReportTemplate: "don't embed sync.Mutex", WhereExpr: ir.FilterExpr{ - Line: 683, + Line: 671, Op: ir.FilterVarTextMatchesOp, Src: "isExported(m[\"x\"])", Value: "x", - Args: []ir.FilterExpr{{Line: 679, Op: ir.FilterStringOp, Src: "`^\\p{Lu}`", Value: "^\\p{Lu}"}}, + Args: []ir.FilterExpr{{Line: 667, Op: ir.FilterStringOp, Src: "`^\\p{Lu}`", Value: "^\\p{Lu}"}}, }, }, { - Line: 686, - SyntaxPatterns: []ir.PatternString{{Line: 686, Value: "type $x struct { $*_; *sync.Mutex; $*_ }"}}, + Line: 674, + SyntaxPatterns: []ir.PatternString{{Line: 674, Value: "type $x struct { $*_; *sync.Mutex; $*_ }"}}, ReportTemplate: "don't embed *sync.Mutex", WhereExpr: ir.FilterExpr{ - Line: 687, + Line: 675, Op: ir.FilterVarTextMatchesOp, Src: "isExported(m[\"x\"])", Value: "x", - Args: []ir.FilterExpr{{Line: 679, Op: ir.FilterStringOp, Src: "`^\\p{Lu}`", Value: "^\\p{Lu}"}}, + Args: []ir.FilterExpr{{Line: 667, Op: ir.FilterStringOp, Src: "`^\\p{Lu}`", Value: "^\\p{Lu}"}}, }, }, { - Line: 690, - SyntaxPatterns: []ir.PatternString{{Line: 690, Value: "type $x struct { $*_; sync.RWMutex; $*_ }"}}, + Line: 678, + SyntaxPatterns: []ir.PatternString{{Line: 678, Value: "type $x struct { $*_; sync.RWMutex; $*_ }"}}, ReportTemplate: "don't embed sync.RWMutex", WhereExpr: ir.FilterExpr{ - Line: 691, + Line: 679, Op: ir.FilterVarTextMatchesOp, Src: "isExported(m[\"x\"])", Value: "x", - Args: []ir.FilterExpr{{Line: 679, Op: ir.FilterStringOp, Src: "`^\\p{Lu}`", Value: "^\\p{Lu}"}}, + Args: []ir.FilterExpr{{Line: 667, Op: ir.FilterStringOp, Src: "`^\\p{Lu}`", Value: "^\\p{Lu}"}}, }, }, { - Line: 694, - SyntaxPatterns: []ir.PatternString{{Line: 694, Value: "type $x struct { $*_; *sync.RWMutex; $*_ }"}}, + Line: 682, + SyntaxPatterns: []ir.PatternString{{Line: 682, Value: "type $x struct { $*_; *sync.RWMutex; $*_ }"}}, ReportTemplate: "don't embed *sync.RWMutex", WhereExpr: ir.FilterExpr{ - Line: 695, + Line: 683, Op: ir.FilterVarTextMatchesOp, Src: "isExported(m[\"x\"])", Value: "x", - Args: []ir.FilterExpr{{Line: 679, Op: ir.FilterStringOp, Src: "`^\\p{Lu}`", Value: "^\\p{Lu}"}}, + Args: []ir.FilterExpr{{Line: 667, Op: ir.FilterStringOp, Src: "`^\\p{Lu}`", Value: "^\\p{Lu}"}}, }, }, }, }, { - Line: 703, + Line: 691, Name: "badSorting", MatcherName: "m", DocTags: []string{"diagnostic", "experimental"}, @@ -2297,83 +2256,83 @@ var PrecompiledRules = &ir.File{ DocAfter: "sort.Strings(xs)", Rules: []ir.Rule{ { - Line: 704, - SyntaxPatterns: []ir.PatternString{{Line: 704, Value: "$x = sort.IntSlice($x)"}}, + Line: 692, + SyntaxPatterns: []ir.PatternString{{Line: 692, Value: "$x = sort.IntSlice($x)"}}, ReportTemplate: "suspicious sort.IntSlice usage, maybe sort.Ints was intended?", SuggestTemplate: "sort.Ints($x)", WhereExpr: ir.FilterExpr{ - Line: 705, + Line: 693, Op: ir.FilterVarTypeIsOp, Src: "m[\"x\"].Type.Is(`[]int`)", Value: "x", - Args: []ir.FilterExpr{{Line: 705, Op: ir.FilterStringOp, Src: "`[]int`", Value: "[]int"}}, + Args: []ir.FilterExpr{{Line: 693, Op: ir.FilterStringOp, Src: "`[]int`", Value: "[]int"}}, }, }, { - Line: 709, - SyntaxPatterns: []ir.PatternString{{Line: 709, Value: "$x = sort.Float64Slice($x)"}}, + Line: 697, + SyntaxPatterns: []ir.PatternString{{Line: 697, Value: "$x = sort.Float64Slice($x)"}}, ReportTemplate: "suspicious sort.Float64s usage, maybe sort.Float64s was intended?", SuggestTemplate: "sort.Float64s($x)", WhereExpr: ir.FilterExpr{ - Line: 710, + Line: 698, Op: ir.FilterVarTypeIsOp, Src: "m[\"x\"].Type.Is(`[]float64`)", Value: "x", - Args: []ir.FilterExpr{{Line: 710, Op: ir.FilterStringOp, Src: "`[]float64`", Value: "[]float64"}}, + Args: []ir.FilterExpr{{Line: 698, Op: ir.FilterStringOp, Src: "`[]float64`", Value: "[]float64"}}, }, }, { - Line: 714, - SyntaxPatterns: []ir.PatternString{{Line: 714, Value: "$x = sort.StringSlice($x)"}}, + Line: 702, + SyntaxPatterns: []ir.PatternString{{Line: 702, Value: "$x = sort.StringSlice($x)"}}, ReportTemplate: "suspicious sort.StringSlice usage, maybe sort.Strings was intended?", SuggestTemplate: "sort.Strings($x)", WhereExpr: ir.FilterExpr{ - Line: 715, + Line: 703, Op: ir.FilterVarTypeIsOp, Src: "m[\"x\"].Type.Is(`[]string`)", Value: "x", - Args: []ir.FilterExpr{{Line: 715, Op: ir.FilterStringOp, Src: "`[]string`", Value: "[]string"}}, + Args: []ir.FilterExpr{{Line: 703, Op: ir.FilterStringOp, Src: "`[]string`", Value: "[]string"}}, }, }, }, }, { - Line: 724, + Line: 712, Name: "externalErrorReassign", MatcherName: "m", DocTags: []string{"diagnostic", "experimental"}, - DocSummary: "Detects suspicious reassigment of error from another package", + DocSummary: "Detects suspicious reassignment of error from another package", DocBefore: "io.EOF = nil", DocAfter: "/* don't do it */", Rules: []ir.Rule{{ - Line: 725, - SyntaxPatterns: []ir.PatternString{{Line: 725, Value: "$pkg.$err = $x"}}, - ReportTemplate: "suspicious reassigment of error from another package", + Line: 713, + SyntaxPatterns: []ir.PatternString{{Line: 713, Value: "$pkg.$err = $x"}}, + ReportTemplate: "suspicious reassignment of error from another package", WhereExpr: ir.FilterExpr{ - Line: 726, + Line: 714, Op: ir.FilterAndOp, Src: "m[\"err\"].Type.Is(`error`) && m[\"pkg\"].Object.Is(`PkgName`)", Args: []ir.FilterExpr{ { - Line: 726, + Line: 714, Op: ir.FilterVarTypeIsOp, Src: "m[\"err\"].Type.Is(`error`)", Value: "err", - Args: []ir.FilterExpr{{Line: 726, Op: ir.FilterStringOp, Src: "`error`", Value: "error"}}, + Args: []ir.FilterExpr{{Line: 714, Op: ir.FilterStringOp, Src: "`error`", Value: "error"}}, }, { - Line: 726, + Line: 714, Op: ir.FilterVarObjectIsOp, Src: "m[\"pkg\"].Object.Is(`PkgName`)", Value: "pkg", - Args: []ir.FilterExpr{{Line: 726, Op: ir.FilterStringOp, Src: "`PkgName`", Value: "PkgName"}}, + Args: []ir.FilterExpr{{Line: 714, Op: ir.FilterStringOp, Src: "`PkgName`", Value: "PkgName"}}, }, }, }, }}, }, { - Line: 734, + Line: 722, Name: "emptyDecl", MatcherName: "m", DocTags: []string{"diagnostic", "experimental"}, @@ -2382,24 +2341,24 @@ var PrecompiledRules = &ir.File{ DocAfter: "/* nothing */", Rules: []ir.Rule{ { - Line: 735, - SyntaxPatterns: []ir.PatternString{{Line: 735, Value: "var()"}}, + Line: 723, + SyntaxPatterns: []ir.PatternString{{Line: 723, Value: "var()"}}, ReportTemplate: "empty var() block", }, { - Line: 736, - SyntaxPatterns: []ir.PatternString{{Line: 736, Value: "const()"}}, + Line: 724, + SyntaxPatterns: []ir.PatternString{{Line: 724, Value: "const()"}}, ReportTemplate: "empty const() block", }, { - Line: 737, - SyntaxPatterns: []ir.PatternString{{Line: 737, Value: "type()"}}, + Line: 725, + SyntaxPatterns: []ir.PatternString{{Line: 725, Value: "type()"}}, ReportTemplate: "empty type() block", }, }, }, { - Line: 744, + Line: 732, Name: "dynamicFmtString", MatcherName: "m", DocTags: []string{"diagnostic", "experimental"}, @@ -2408,16 +2367,16 @@ var PrecompiledRules = &ir.File{ DocAfter: "fmt.Errorf(\"%s\", msg)", Rules: []ir.Rule{ { - Line: 745, - SyntaxPatterns: []ir.PatternString{{Line: 745, Value: "fmt.Errorf($f)"}}, + Line: 733, + SyntaxPatterns: []ir.PatternString{{Line: 733, Value: "fmt.Errorf($f)"}}, ReportTemplate: "use errors.New($f) or fmt.Errorf(\"%s\", $f) instead", SuggestTemplate: "errors.New($f)", WhereExpr: ir.FilterExpr{ - Line: 746, + Line: 734, Op: ir.FilterNotOp, Src: "!m[\"f\"].Const", Args: []ir.FilterExpr{{ - Line: 746, + Line: 734, Op: ir.FilterVarConstOp, Src: "m[\"f\"].Const", Value: "f", @@ -2425,15 +2384,15 @@ var PrecompiledRules = &ir.File{ }, }, { - Line: 750, - SyntaxPatterns: []ir.PatternString{{Line: 750, Value: "fmt.Errorf($f($*args))"}}, + Line: 738, + SyntaxPatterns: []ir.PatternString{{Line: 738, Value: "fmt.Errorf($f($*args))"}}, ReportTemplate: "use errors.New($f($*args)) or fmt.Errorf(\"%s\", $f($*args)) instead", SuggestTemplate: "errors.New($f($*args))", }, }, }, { - Line: 759, + Line: 747, Name: "stringsCompare", MatcherName: "m", DocTags: []string{"style", "experimental"}, @@ -2442,25 +2401,25 @@ var PrecompiledRules = &ir.File{ DocAfter: "x < y", Rules: []ir.Rule{ { - Line: 760, - SyntaxPatterns: []ir.PatternString{{Line: 760, Value: "strings.Compare($s1, $s2) == 0"}}, + Line: 748, + SyntaxPatterns: []ir.PatternString{{Line: 748, Value: "strings.Compare($s1, $s2) == 0"}}, ReportTemplate: "suggestion: $s1 == $s2", SuggestTemplate: "$s1 == $s2", }, { - Line: 763, + Line: 751, SyntaxPatterns: []ir.PatternString{ - {Line: 763, Value: "strings.Compare($s1, $s2) == -1"}, - {Line: 764, Value: "strings.Compare($s1, $s2) < 0"}, + {Line: 751, Value: "strings.Compare($s1, $s2) == -1"}, + {Line: 752, Value: "strings.Compare($s1, $s2) < 0"}, }, ReportTemplate: "suggestion: $s1 < $s2", SuggestTemplate: "$s1 < $s2", }, { - Line: 767, + Line: 755, SyntaxPatterns: []ir.PatternString{ - {Line: 767, Value: "strings.Compare($s1, $s2) == 1"}, - {Line: 768, Value: "strings.Compare($s1, $s2) > 0"}, + {Line: 755, Value: "strings.Compare($s1, $s2) == 1"}, + {Line: 756, Value: "strings.Compare($s1, $s2) > 0"}, }, ReportTemplate: "suggestion: $s1 > $s2", SuggestTemplate: "$s1 > $s2", @@ -2468,7 +2427,7 @@ var PrecompiledRules = &ir.File{ }, }, { - Line: 776, + Line: 764, Name: "uncheckedInlineErr", MatcherName: "m", DocTags: []string{"diagnostic", "experimental"}, @@ -2476,47 +2435,47 @@ var PrecompiledRules = &ir.File{ DocBefore: "if err := expr(); err2 != nil { /*...*/ }", DocAfter: "if err := expr(); err != nil { /*...*/ }", Rules: []ir.Rule{{ - Line: 777, + Line: 765, SyntaxPatterns: []ir.PatternString{ - {Line: 778, Value: "if $err := $_($*_); $err2 != nil { $*_ }"}, - {Line: 779, Value: "if $err = $_($*_); $err2 != nil { $*_ }"}, - {Line: 780, Value: "if $*_, $err := $_($*_); $err2 != nil { $*_ }"}, - {Line: 781, Value: "if $*_, $err = $_($*_); $err2 != nil { $*_ }"}, + {Line: 766, Value: "if $err := $_($*_); $err2 != nil { $*_ }"}, + {Line: 767, Value: "if $err = $_($*_); $err2 != nil { $*_ }"}, + {Line: 768, Value: "if $*_, $err := $_($*_); $err2 != nil { $*_ }"}, + {Line: 769, Value: "if $*_, $err = $_($*_); $err2 != nil { $*_ }"}, }, ReportTemplate: "$err error is unchecked, maybe intended to check it instead of $err2", WhereExpr: ir.FilterExpr{ - Line: 782, + Line: 770, Op: ir.FilterAndOp, Src: "m[\"err\"].Type.Implements(\"error\") && m[\"err2\"].Type.Implements(\"error\") &&\n\tm[\"err\"].Text != m[\"err2\"].Text", Args: []ir.FilterExpr{ { - Line: 782, + Line: 770, Op: ir.FilterAndOp, Src: "m[\"err\"].Type.Implements(\"error\") && m[\"err2\"].Type.Implements(\"error\")", Args: []ir.FilterExpr{ { - Line: 782, + Line: 770, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"err\"].Type.Implements(\"error\")", Value: "err", - Args: []ir.FilterExpr{{Line: 782, Op: ir.FilterStringOp, Src: "\"error\"", Value: "error"}}, + Args: []ir.FilterExpr{{Line: 770, Op: ir.FilterStringOp, Src: "\"error\"", Value: "error"}}, }, { - Line: 782, + Line: 770, Op: ir.FilterVarTypeImplementsOp, Src: "m[\"err2\"].Type.Implements(\"error\")", Value: "err2", - Args: []ir.FilterExpr{{Line: 782, Op: ir.FilterStringOp, Src: "\"error\"", Value: "error"}}, + Args: []ir.FilterExpr{{Line: 770, Op: ir.FilterStringOp, Src: "\"error\"", Value: "error"}}, }, }, }, { - Line: 783, + Line: 771, Op: ir.FilterNeqOp, Src: "m[\"err\"].Text != m[\"err2\"].Text", Args: []ir.FilterExpr{ - {Line: 783, Op: ir.FilterVarTextOp, Src: "m[\"err\"].Text", Value: "err"}, - {Line: 783, Op: ir.FilterVarTextOp, Src: "m[\"err2\"].Text", Value: "err2"}, + {Line: 771, Op: ir.FilterVarTextOp, Src: "m[\"err\"].Text", Value: "err"}, + {Line: 771, Op: ir.FilterVarTextOp, Src: "m[\"err2\"].Text", Value: "err2"}, }, }, }, @@ -2525,108 +2484,34 @@ var PrecompiledRules = &ir.File{ }}, }, { - Line: 792, - Name: "sloppyTestFuncName", + Line: 780, + Name: "badSyncOnceFunc", MatcherName: "m", DocTags: []string{"diagnostic", "experimental"}, - DocSummary: "Detects unsupported test and benchmark funcs", - DocBefore: "func TessstUnit(t *testing.T)", - DocAfter: "func TestUnit(t *testing.T)", + DocSummary: "Detects bad usage of sync.OnceFunc", + DocBefore: "sync.OnceFunc(foo)()", + DocAfter: "fooOnce := sync.OnceFunc(foo); ...; fooOnce()", Rules: []ir.Rule{ { - Line: 793, - SyntaxPatterns: []ir.PatternString{{Line: 793, Value: "func $test($_ *testing.T) { $*_ }"}}, - ReportTemplate: "function $test should be of form TestXXX(t *testing.T)", + Line: 781, + SyntaxPatterns: []ir.PatternString{{Line: 781, Value: "$*_; sync.OnceFunc($x); $*_;"}}, + ReportTemplate: "possible sync.OnceFunc misuse, sync.OnceFunc($x) result is not used", WhereExpr: ir.FilterExpr{ - Line: 794, - Op: ir.FilterAndOp, - Src: "!m[\"test\"].Text.Matches(\"Test.*\") &&\n\t!m[\"test\"].Text.Matches(\"test.*\")", - Args: []ir.FilterExpr{ - { - Line: 794, - Op: ir.FilterNotOp, - Src: "!m[\"test\"].Text.Matches(\"Test.*\")", - Args: []ir.FilterExpr{{ - Line: 794, - Op: ir.FilterVarTextMatchesOp, - Src: "m[\"test\"].Text.Matches(\"Test.*\")", - Value: "test", - Args: []ir.FilterExpr{{Line: 794, Op: ir.FilterStringOp, Src: "\"Test.*\"", Value: "Test.*"}}, - }}, - }, - { - Line: 795, - Op: ir.FilterNotOp, - Src: "!m[\"test\"].Text.Matches(\"test.*\")", - Args: []ir.FilterExpr{{ - Line: 795, - Op: ir.FilterVarTextMatchesOp, - Src: "m[\"test\"].Text.Matches(\"test.*\")", - Value: "test", - Args: []ir.FilterExpr{{Line: 795, Op: ir.FilterStringOp, Src: "\"test.*\"", Value: "test.*"}}, - }}, - }, - }, - }, - }, - { - Line: 798, - SyntaxPatterns: []ir.PatternString{{Line: 798, Value: "func $bench($_ *testing.B) { $*_ }"}}, - ReportTemplate: "function $bench should be of form BenchmarkXXX(b *testing.B)", - WhereExpr: ir.FilterExpr{ - Line: 799, - Op: ir.FilterAndOp, - Src: "!m[\"bench\"].Text.Matches(\"Benchmark.*\") &&\n\t!m[\"bench\"].Text.Matches(\"bench.*\")", - Args: []ir.FilterExpr{ - { - Line: 799, - Op: ir.FilterNotOp, - Src: "!m[\"bench\"].Text.Matches(\"Benchmark.*\")", - Args: []ir.FilterExpr{{ - Line: 799, - Op: ir.FilterVarTextMatchesOp, - Src: "m[\"bench\"].Text.Matches(\"Benchmark.*\")", - Value: "bench", - Args: []ir.FilterExpr{{Line: 799, Op: ir.FilterStringOp, Src: "\"Benchmark.*\"", Value: "Benchmark.*"}}, - }}, - }, - { - Line: 800, - Op: ir.FilterNotOp, - Src: "!m[\"bench\"].Text.Matches(\"bench.*\")", - Args: []ir.FilterExpr{{ - Line: 800, - Op: ir.FilterVarTextMatchesOp, - Src: "m[\"bench\"].Text.Matches(\"bench.*\")", - Value: "bench", - Args: []ir.FilterExpr{{Line: 800, Op: ir.FilterStringOp, Src: "\"bench.*\"", Value: "bench.*"}}, - }}, - }, - }, - }, - }, - { - Line: 803, - SyntaxPatterns: []ir.PatternString{{Line: 803, Value: "func $test($_ *testing.T) { $*_ }"}}, - ReportTemplate: "function $test looks like a test helper, consider to change 1st param to 'tb testing.TB'", - WhereExpr: ir.FilterExpr{ - Line: 804, - Op: ir.FilterVarTextMatchesOp, - Src: "m[\"test\"].Text.Matches(\"^test.*\")", - Value: "test", - Args: []ir.FilterExpr{{Line: 804, Op: ir.FilterStringOp, Src: "\"^test.*\"", Value: "^test.*"}}, + Line: 783, + Op: ir.FilterGoVersionGreaterEqThanOp, + Src: "m.GoVersion().GreaterEqThan(\"1.21\")", + Value: "1.21", }, }, { - Line: 807, - SyntaxPatterns: []ir.PatternString{{Line: 807, Value: "func $bench($_ *testing.B) { $*_ }"}}, - ReportTemplate: "function $bench looks like a benchmark helper, consider to change 1st param to 'tb testing.TB'", + Line: 785, + SyntaxPatterns: []ir.PatternString{{Line: 785, Value: "sync.OnceFunc($x)()"}}, + ReportTemplate: "possible sync.OnceFunc misuse, consider to assign sync.OnceFunc($x) to a variable", WhereExpr: ir.FilterExpr{ - Line: 808, - Op: ir.FilterVarTextMatchesOp, - Src: "m[\"bench\"].Text.Matches(\"^bench(mark)?.*\")", - Value: "bench", - Args: []ir.FilterExpr{{Line: 808, Op: ir.FilterStringOp, Src: "\"^bench(mark)?.*\"", Value: "^bench(mark)?.*"}}, + Line: 787, + Op: ir.FilterGoVersionGreaterEqThanOp, + Src: "m.GoVersion().GreaterEqThan(\"1.21\")", + Value: "1.21", }, }, }, diff --git a/vendor/github.com/go-critic/go-critic/checkers/typeUnparen_checker.go b/vendor/github.com/go-critic/go-critic/checkers/typeUnparen_checker.go index d270268bd..e2e225ebf 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/typeUnparen_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/typeUnparen_checker.go @@ -14,7 +14,7 @@ func init() { var info linter.CheckerInfo info.Name = "typeUnparen" info.Tags = []string{linter.StyleTag, linter.OpinionatedTag} - info.Summary = "Detects unneded parenthesis inside type expressions and suggests to remove them" + info.Summary = "Detects unneeded parenthesis inside type expressions and suggests to remove them" info.Before = `type foo [](func([](func())))` info.After = `type foo []func([]func())` diff --git a/vendor/github.com/go-critic/go-critic/checkers/unlambda_checker.go b/vendor/github.com/go-critic/go-critic/checkers/unlambda_checker.go index bcfe5a0c4..0401bf5d3 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/unlambda_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/unlambda_checker.go @@ -91,7 +91,7 @@ func (c *unlambdaChecker) VisitExpr(x ast.Expr) { } } - if len(result.Args) == n { + if c.lenArgs(result.Args) == n { c.warn(fn, callable) } } @@ -99,3 +99,20 @@ func (c *unlambdaChecker) VisitExpr(x ast.Expr) { func (c *unlambdaChecker) warn(cause ast.Node, suggestion string) { c.ctx.Warn(cause, "replace `%s` with `%s`", cause, suggestion) } + +func (c *unlambdaChecker) lenArgs(args []ast.Expr) int { + lenArgs := len(args) + + for _, arg := range args { + callExp, ok := arg.(*ast.CallExpr) + if !ok { + continue + } + + // Don't count function call. only args. + lenArgs-- + lenArgs += c.lenArgs(callExp.Args) + } + + return lenArgs +} diff --git a/vendor/github.com/go-critic/go-critic/checkers/unnecessaryDefer_checker.go b/vendor/github.com/go-critic/go-critic/checkers/unnecessaryDefer_checker.go index 4358ab171..4c1ed41f6 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/unnecessaryDefer_checker.go +++ b/vendor/github.com/go-critic/go-critic/checkers/unnecessaryDefer_checker.go @@ -37,7 +37,7 @@ type unnecessaryDeferChecker struct { // Visit implements the ast.Visitor. This visitor keeps track of the block // statement belongs to a function or any other block. If the block is not a // function and ends with a defer statement that should be OK since it's -// defering the outer function. +// deferring the outer function. func (c *unnecessaryDeferChecker) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.FuncDecl, *ast.FuncLit: diff --git a/vendor/github.com/go-critic/go-critic/checkers/utils.go b/vendor/github.com/go-critic/go-critic/checkers/utils.go index e9123352d..4757bbf5f 100644 --- a/vendor/github.com/go-critic/go-critic/checkers/utils.go +++ b/vendor/github.com/go-critic/go-critic/checkers/utils.go @@ -260,7 +260,7 @@ func isUnitTestFunc(ctx *linter.CheckerContext, fn *ast.FuncDecl) bool { return false } -// qualifiedName returns called expr fully-quallified name. +// qualifiedName returns called expr fully-qualified name. // // It works for simple identifiers like f => "f" and identifiers // from other package like pkg.f => "pkg.f". diff --git a/vendor/github.com/go-toolsmith/astequal/astequal.go b/vendor/github.com/go-toolsmith/astequal/astequal.go index 3d8db4af9..d1a04e942 100644 --- a/vendor/github.com/go-toolsmith/astequal/astequal.go +++ b/vendor/github.com/go-toolsmith/astequal/astequal.go @@ -4,8 +4,6 @@ package astequal import ( "go/ast" "go/token" - - "golang.org/x/exp/typeparams" ) // Node reports whether two AST nodes are structurally (deep) equal. @@ -109,8 +107,8 @@ func astExprEq(x, y ast.Expr) bool { y, ok := y.(*ast.IndexExpr) return ok && astIndexExprEq(x, y) - case *typeparams.IndexListExpr: - y, ok := y.(*typeparams.IndexListExpr) + case *ast.IndexListExpr: + y, ok := y.(*ast.IndexListExpr) return ok && astIndexListExprEq(x, y) case *ast.SliceExpr: @@ -323,7 +321,7 @@ func astFuncTypeEq(x, y *ast.FuncType) bool { } return astFieldListEq(x.Params, y.Params) && astFieldListEq(x.Results, y.Results) && - astFieldListEq(typeparams.ForFuncType(x), typeparams.ForFuncType(y)) + astFieldListEq(forFuncType(x), forFuncType(y)) } func astBasicLitEq(x, y *ast.BasicLit) bool { @@ -378,7 +376,7 @@ func astIndexExprEq(x, y *ast.IndexExpr) bool { return astExprEq(x.X, y.X) && astExprEq(x.Index, y.Index) } -func astIndexListExprEq(x, y *typeparams.IndexListExpr) bool { +func astIndexListExprEq(x, y *ast.IndexListExpr) bool { if x == nil || y == nil { return x == y } @@ -690,7 +688,7 @@ func astTypeSpecEq(x, y *ast.TypeSpec) bool { return x == y } return astIdentEq(x.Name, y.Name) && astExprEq(x.Type, y.Type) && - astFieldListEq(typeparams.ForTypeSpec(x), typeparams.ForTypeSpec(y)) + astFieldListEq(forTypeSpec(x), forTypeSpec(y)) } func astValueSpecEq(x, y *ast.ValueSpec) bool { @@ -755,3 +753,19 @@ func astExprSliceEq(xs, ys []ast.Expr) bool { } return true } + +// forTypeSpec returns n.TypeParams. +func forTypeSpec(n *ast.TypeSpec) *ast.FieldList { + if n == nil { + return nil + } + return n.TypeParams +} + +// forFuncType returns n.TypeParams. +func forFuncType(n *ast.FuncType) *ast.FieldList { + if n == nil { + return nil + } + return n.TypeParams +} diff --git a/vendor/github.com/go-toolsmith/astequal/diff.go b/vendor/github.com/go-toolsmith/astequal/diff.go new file mode 100644 index 000000000..cd69b4525 --- /dev/null +++ b/vendor/github.com/go-toolsmith/astequal/diff.go @@ -0,0 +1,23 @@ +package astequal + +import ( + "bytes" + "go/ast" + "go/format" + "go/token" + + "github.com/google/go-cmp/cmp" +) + +func Diff(x, y ast.Node) string { + var buf bytes.Buffer + format.Node(&buf, token.NewFileSet(), x) + s1 := buf.String() + + buf.Reset() + format.Node(&buf, token.NewFileSet(), y) + s2 := buf.String() + + // TODO(cristaloleg): replace with a more lightweight diff impl. + return cmp.Diff(s1, s2) +} diff --git a/vendor/github.com/go-viper/mapstructure/v2/.editorconfig b/vendor/github.com/go-viper/mapstructure/v2/.editorconfig new file mode 100644 index 000000000..1f664d13a --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.go] +indent_style = tab + +[{Makefile,*.mk}] +indent_style = tab + +[*.nix] +indent_size = 2 diff --git a/vendor/github.com/go-viper/mapstructure/v2/.envrc b/vendor/github.com/go-viper/mapstructure/v2/.envrc new file mode 100644 index 000000000..c66fc0d35 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/.envrc @@ -0,0 +1,4 @@ +if ! has nix_direnv_version || ! nix_direnv_version 3.0.1; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.1/direnvrc" "sha256-17G+Mvt/JsyJrwsf7bqMr7ho7liHP+0Lo4RMIHgp0F8=" +fi +use flake . --impure diff --git a/vendor/github.com/go-viper/mapstructure/v2/.gitignore b/vendor/github.com/go-viper/mapstructure/v2/.gitignore new file mode 100644 index 000000000..470e7ca2b --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/.gitignore @@ -0,0 +1,6 @@ +/.devenv/ +/.direnv/ +/.pre-commit-config.yaml +/bin/ +/build/ +/var/ diff --git a/vendor/github.com/go-viper/mapstructure/v2/.golangci.yaml b/vendor/github.com/go-viper/mapstructure/v2/.golangci.yaml new file mode 100644 index 000000000..763143aa7 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/.golangci.yaml @@ -0,0 +1,23 @@ +run: + timeout: 5m + +linters-settings: + gci: + sections: + - standard + - default + - prefix(github.com/go-viper/mapstructure) + golint: + min-confidence: 0 + goimports: + local-prefixes: github.com/go-viper/maptstructure + +linters: + disable-all: true + enable: + - gci + - gofmt + - gofumpt + - goimports + - staticcheck + # - stylecheck diff --git a/vendor/github.com/go-viper/mapstructure/v2/CHANGELOG.md b/vendor/github.com/go-viper/mapstructure/v2/CHANGELOG.md new file mode 100644 index 000000000..ae634d1cc --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/CHANGELOG.md @@ -0,0 +1,101 @@ +## 1.5.1 + +* Wrap errors so they're compatible with `errors.Is` and `errors.As` [GH-282] +* Fix map of slices not decoding properly in certain cases. [GH-266] + +## 1.5.0 + +* New option `IgnoreUntaggedFields` to ignore decoding to any fields + without `mapstructure` (or the configured tag name) set [GH-277] +* New option `ErrorUnset` which makes it an error if any fields + in a target struct are not set by the decoding process. [GH-225] +* New function `OrComposeDecodeHookFunc` to help compose decode hooks. [GH-240] +* Decoding to slice from array no longer crashes [GH-265] +* Decode nested struct pointers to map [GH-271] +* Fix issue where `,squash` was ignored if `Squash` option was set. [GH-280] +* Fix issue where fields with `,omitempty` would sometimes decode + into a map with an empty string key [GH-281] + +## 1.4.3 + +* Fix cases where `json.Number` didn't decode properly [GH-261] + +## 1.4.2 + +* Custom name matchers to support any sort of casing, formatting, etc. for + field names. [GH-250] +* Fix possible panic in ComposeDecodeHookFunc [GH-251] + +## 1.4.1 + +* Fix regression where `*time.Time` value would be set to empty and not be sent + to decode hooks properly [GH-232] + +## 1.4.0 + +* A new decode hook type `DecodeHookFuncValue` has been added that has + access to the full values. [GH-183] +* Squash is now supported with embedded fields that are struct pointers [GH-205] +* Empty strings will convert to 0 for all numeric types when weakly decoding [GH-206] + +## 1.3.3 + +* Decoding maps from maps creates a settable value for decode hooks [GH-203] + +## 1.3.2 + +* Decode into interface type with a struct value is supported [GH-187] + +## 1.3.1 + +* Squash should only squash embedded structs. [GH-194] + +## 1.3.0 + +* Added `",omitempty"` support. This will ignore zero values in the source + structure when encoding. [GH-145] + +## 1.2.3 + +* Fix duplicate entries in Keys list with pointer values. [GH-185] + +## 1.2.2 + +* Do not add unsettable (unexported) values to the unused metadata key + or "remain" value. [GH-150] + +## 1.2.1 + +* Go modules checksum mismatch fix + +## 1.2.0 + +* Added support to capture unused values in a field using the `",remain"` value + in the mapstructure tag. There is an example to showcase usage. +* Added `DecoderConfig` option to always squash embedded structs +* `json.Number` can decode into `uint` types +* Empty slices are preserved and not replaced with nil slices +* Fix panic that can occur in when decoding a map into a nil slice of structs +* Improved package documentation for godoc + +## 1.1.2 + +* Fix error when decode hook decodes interface implementation into interface + type. [GH-140] + +## 1.1.1 + +* Fix panic that can happen in `decodePtr` + +## 1.1.0 + +* Added `StringToIPHookFunc` to convert `string` to `net.IP` and `net.IPNet` [GH-133] +* Support struct to struct decoding [GH-137] +* If source map value is nil, then destination map value is nil (instead of empty) +* If source slice value is nil, then destination slice value is nil (instead of empty) +* If source pointer is nil, then destination pointer is set to nil (instead of + allocated zero value of type) + +## 1.0.0 + +* Initial tagged stable release. diff --git a/vendor/github.com/go-viper/mapstructure/v2/LICENSE b/vendor/github.com/go-viper/mapstructure/v2/LICENSE new file mode 100644 index 000000000..f9c841a51 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 Mitchell Hashimoto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/go-viper/mapstructure/v2/README.md b/vendor/github.com/go-viper/mapstructure/v2/README.md new file mode 100644 index 000000000..2b28db894 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/README.md @@ -0,0 +1,59 @@ +# mapstructure + +[](https://github.com/go-viper/mapstructure/actions?query=workflow%3ACI) +[](https://pkg.go.dev/mod/github.com/go-viper/mapstructure/v2) + + +mapstructure is a Go library for decoding generic map values to structures +and vice versa, while providing helpful error handling. + +This library is most useful when decoding values from some data stream (JSON, +Gob, etc.) where you don't _quite_ know the structure of the underlying data +until you read a part of it. You can therefore read a `map[string]interface{}` +and use this library to decode it into the proper underlying native Go +structure. + +## Installation + +```shell +go get github.com/go-viper/mapstructure/v2 +``` + +## Usage & Example + +For usage and examples see the [documentation](https://pkg.go.dev/mod/github.com/go-viper/mapstructure/v2). + +The `Decode` function has examples associated with it there. + +## But Why?! + +Go offers fantastic standard libraries for decoding formats such as JSON. +The standard method is to have a struct pre-created, and populate that struct +from the bytes of the encoded format. This is great, but the problem is if +you have configuration or an encoding that changes slightly depending on +specific fields. For example, consider this JSON: + +```json +{ + "type": "person", + "name": "Mitchell" +} +``` + +Perhaps we can't populate a specific structure without first reading +the "type" field from the JSON. We could always do two passes over the +decoding of the JSON (reading the "type" first, and the rest later). +However, it is much simpler to just decode this into a `map[string]interface{}` +structure, read the "type" key, then use something like this library +to decode it into the proper structure. + +## Credits + +Mapstructure was originally created by [@mitchellh](https://github.com/mitchellh). +This is a maintained fork of the original library. + +Read more about the reasons for the fork [here](https://github.com/mitchellh/mapstructure/issues/349). + +## License + +The project is licensed under the [MIT License](LICENSE). diff --git a/vendor/github.com/go-viper/mapstructure/v2/decode_hooks.go b/vendor/github.com/go-viper/mapstructure/v2/decode_hooks.go new file mode 100644 index 000000000..840d6adce --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/decode_hooks.go @@ -0,0 +1,334 @@ +package mapstructure + +import ( + "encoding" + "errors" + "fmt" + "net" + "net/netip" + "reflect" + "strconv" + "strings" + "time" +) + +// typedDecodeHook takes a raw DecodeHookFunc (an interface{}) and turns +// it into the proper DecodeHookFunc type, such as DecodeHookFuncType. +func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc { + // Create variables here so we can reference them with the reflect pkg + var f1 DecodeHookFuncType + var f2 DecodeHookFuncKind + var f3 DecodeHookFuncValue + + // Fill in the variables into this interface and the rest is done + // automatically using the reflect package. + potential := []interface{}{f1, f2, f3} + + v := reflect.ValueOf(h) + vt := v.Type() + for _, raw := range potential { + pt := reflect.ValueOf(raw).Type() + if vt.ConvertibleTo(pt) { + return v.Convert(pt).Interface() + } + } + + return nil +} + +// DecodeHookExec executes the given decode hook. This should be used +// since it'll naturally degrade to the older backwards compatible DecodeHookFunc +// that took reflect.Kind instead of reflect.Type. +func DecodeHookExec( + raw DecodeHookFunc, + from reflect.Value, to reflect.Value, +) (interface{}, error) { + switch f := typedDecodeHook(raw).(type) { + case DecodeHookFuncType: + return f(from.Type(), to.Type(), from.Interface()) + case DecodeHookFuncKind: + return f(from.Kind(), to.Kind(), from.Interface()) + case DecodeHookFuncValue: + return f(from, to) + default: + return nil, errors.New("invalid decode hook signature") + } +} + +// ComposeDecodeHookFunc creates a single DecodeHookFunc that +// automatically composes multiple DecodeHookFuncs. +// +// The composed funcs are called in order, with the result of the +// previous transformation. +func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc { + return func(f reflect.Value, t reflect.Value) (interface{}, error) { + var err error + data := f.Interface() + + newFrom := f + for _, f1 := range fs { + data, err = DecodeHookExec(f1, newFrom, t) + if err != nil { + return nil, err + } + newFrom = reflect.ValueOf(data) + } + + return data, nil + } +} + +// OrComposeDecodeHookFunc executes all input hook functions until one of them returns no error. In that case its value is returned. +// If all hooks return an error, OrComposeDecodeHookFunc returns an error concatenating all error messages. +func OrComposeDecodeHookFunc(ff ...DecodeHookFunc) DecodeHookFunc { + return func(a, b reflect.Value) (interface{}, error) { + var allErrs string + var out interface{} + var err error + + for _, f := range ff { + out, err = DecodeHookExec(f, a, b) + if err != nil { + allErrs += err.Error() + "\n" + continue + } + + return out, nil + } + + return nil, errors.New(allErrs) + } +} + +// StringToSliceHookFunc returns a DecodeHookFunc that converts +// string to []string by splitting on the given sep. +func StringToSliceHookFunc(sep string) DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.SliceOf(f) { + return data, nil + } + + raw := data.(string) + if raw == "" { + return []string{}, nil + } + + return strings.Split(raw, sep), nil + } +} + +// StringToTimeDurationHookFunc returns a DecodeHookFunc that converts +// strings to time.Duration. +func StringToTimeDurationHookFunc() DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(time.Duration(5)) { + return data, nil + } + + // Convert it by parsing + return time.ParseDuration(data.(string)) + } +} + +// StringToIPHookFunc returns a DecodeHookFunc that converts +// strings to net.IP +func StringToIPHookFunc() DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(net.IP{}) { + return data, nil + } + + // Convert it by parsing + ip := net.ParseIP(data.(string)) + if ip == nil { + return net.IP{}, fmt.Errorf("failed parsing ip %v", data) + } + + return ip, nil + } +} + +// StringToIPNetHookFunc returns a DecodeHookFunc that converts +// strings to net.IPNet +func StringToIPNetHookFunc() DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(net.IPNet{}) { + return data, nil + } + + // Convert it by parsing + _, net, err := net.ParseCIDR(data.(string)) + return net, err + } +} + +// StringToTimeHookFunc returns a DecodeHookFunc that converts +// strings to time.Time. +func StringToTimeHookFunc(layout string) DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(time.Time{}) { + return data, nil + } + + // Convert it by parsing + return time.Parse(layout, data.(string)) + } +} + +// WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to +// the decoder. +// +// Note that this is significantly different from the WeaklyTypedInput option +// of the DecoderConfig. +func WeaklyTypedHook( + f reflect.Kind, + t reflect.Kind, + data interface{}, +) (interface{}, error) { + dataVal := reflect.ValueOf(data) + switch t { + case reflect.String: + switch f { + case reflect.Bool: + if dataVal.Bool() { + return "1", nil + } + return "0", nil + case reflect.Float32: + return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil + case reflect.Int: + return strconv.FormatInt(dataVal.Int(), 10), nil + case reflect.Slice: + dataType := dataVal.Type() + elemKind := dataType.Elem().Kind() + if elemKind == reflect.Uint8 { + return string(dataVal.Interface().([]uint8)), nil + } + case reflect.Uint: + return strconv.FormatUint(dataVal.Uint(), 10), nil + } + } + + return data, nil +} + +func RecursiveStructToMapHookFunc() DecodeHookFunc { + return func(f reflect.Value, t reflect.Value) (interface{}, error) { + if f.Kind() != reflect.Struct { + return f.Interface(), nil + } + + var i interface{} = struct{}{} + if t.Type() != reflect.TypeOf(&i).Elem() { + return f.Interface(), nil + } + + m := make(map[string]interface{}) + t.Set(reflect.ValueOf(m)) + + return f.Interface(), nil + } +} + +// TextUnmarshallerHookFunc returns a DecodeHookFunc that applies +// strings to the UnmarshalText function, when the target type +// implements the encoding.TextUnmarshaler interface +func TextUnmarshallerHookFunc() DecodeHookFuncType { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + result := reflect.New(t).Interface() + unmarshaller, ok := result.(encoding.TextUnmarshaler) + if !ok { + return data, nil + } + str, ok := data.(string) + if !ok { + str = reflect.Indirect(reflect.ValueOf(&data)).Elem().String() + } + if err := unmarshaller.UnmarshalText([]byte(str)); err != nil { + return nil, err + } + return result, nil + } +} + +// StringToNetIPAddrHookFunc returns a DecodeHookFunc that converts +// strings to netip.Addr. +func StringToNetIPAddrHookFunc() DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(netip.Addr{}) { + return data, nil + } + + // Convert it by parsing + return netip.ParseAddr(data.(string)) + } +} + +// StringToNetIPAddrPortHookFunc returns a DecodeHookFunc that converts +// strings to netip.AddrPort. +func StringToNetIPAddrPortHookFunc() DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(netip.AddrPort{}) { + return data, nil + } + + // Convert it by parsing + return netip.ParseAddrPort(data.(string)) + } +} diff --git a/vendor/github.com/go-viper/mapstructure/v2/error.go b/vendor/github.com/go-viper/mapstructure/v2/error.go new file mode 100644 index 000000000..47a99e5af --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/error.go @@ -0,0 +1,50 @@ +package mapstructure + +import ( + "errors" + "fmt" + "sort" + "strings" +) + +// Error implements the error interface and can represents multiple +// errors that occur in the course of a single decode. +type Error struct { + Errors []string +} + +func (e *Error) Error() string { + points := make([]string, len(e.Errors)) + for i, err := range e.Errors { + points[i] = fmt.Sprintf("* %s", err) + } + + sort.Strings(points) + return fmt.Sprintf( + "%d error(s) decoding:\n\n%s", + len(e.Errors), strings.Join(points, "\n")) +} + +// WrappedErrors implements the errwrap.Wrapper interface to make this +// return value more useful with the errwrap and go-multierror libraries. +func (e *Error) WrappedErrors() []error { + if e == nil { + return nil + } + + result := make([]error, len(e.Errors)) + for i, e := range e.Errors { + result[i] = errors.New(e) + } + + return result +} + +func appendErrors(errors []string, err error) []string { + switch e := err.(type) { + case *Error: + return append(errors, e.Errors...) + default: + return append(errors, e.Error()) + } +} diff --git a/vendor/github.com/go-viper/mapstructure/v2/flake.lock b/vendor/github.com/go-viper/mapstructure/v2/flake.lock new file mode 100644 index 000000000..5a387d329 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/flake.lock @@ -0,0 +1,273 @@ +{ + "nodes": { + "devenv": { + "inputs": { + "flake-compat": "flake-compat", + "nix": "nix", + "nixpkgs": "nixpkgs", + "pre-commit-hooks": "pre-commit-hooks" + }, + "locked": { + "lastModified": 1702549996, + "narHash": "sha256-mEN+8gjWUXRxBCcixeth+jlDNuzxbpFwZNOEc4K22vw=", + "owner": "cachix", + "repo": "devenv", + "rev": "e681a99ffe2d2882f413a5d771129223c838ddce", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1701473968, + "narHash": "sha256-YcVE5emp1qQ8ieHUnxt1wCZCC3ZfAS+SRRWZ2TMda7E=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "34fed993f1674c8d06d58b37ce1e0fe5eebcb9f5", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1685518550, + "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "devenv", + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1660459072, + "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "lowdown-src": { + "flake": false, + "locked": { + "lastModified": 1633514407, + "narHash": "sha256-Dw32tiMjdK9t3ETl5fzGrutQTzh2rufgZV4A/BbxuD4=", + "owner": "kristapsdz", + "repo": "lowdown", + "rev": "d2c2b44ff6c27b936ec27358a2653caaef8f73b8", + "type": "github" + }, + "original": { + "owner": "kristapsdz", + "repo": "lowdown", + "type": "github" + } + }, + "nix": { + "inputs": { + "lowdown-src": "lowdown-src", + "nixpkgs": [ + "devenv", + "nixpkgs" + ], + "nixpkgs-regression": "nixpkgs-regression" + }, + "locked": { + "lastModified": 1676545802, + "narHash": "sha256-EK4rZ+Hd5hsvXnzSzk2ikhStJnD63odF7SzsQ8CuSPU=", + "owner": "domenkozar", + "repo": "nix", + "rev": "7c91803598ffbcfe4a55c44ac6d49b2cf07a527f", + "type": "github" + }, + "original": { + "owner": "domenkozar", + "ref": "relaxed-flakes", + "repo": "nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1678875422, + "narHash": "sha256-T3o6NcQPwXjxJMn2shz86Chch4ljXgZn746c2caGxd8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "126f49a01de5b7e35a43fd43f891ecf6d3a51459", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "dir": "lib", + "lastModified": 1701253981, + "narHash": "sha256-ztaDIyZ7HrTAfEEUt9AtTDNoCYxUdSd6NrRHaYOIxtk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e92039b55bcd58469325ded85d4f58dd5a4eaf58", + "type": "github" + }, + "original": { + "dir": "lib", + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-regression": { + "locked": { + "lastModified": 1643052045, + "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1685801374, + "narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "c37ca420157f4abc31e26f436c1145f8951ff373", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1702539185, + "narHash": "sha256-KnIRG5NMdLIpEkZTnN5zovNYc0hhXjAgv6pfd5Z4c7U=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "aa9d4729cbc99dabacb50e3994dcefb3ea0f7447", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": [ + "devenv", + "flake-compat" + ], + "flake-utils": "flake-utils", + "gitignore": "gitignore", + "nixpkgs": [ + "devenv", + "nixpkgs" + ], + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1688056373, + "narHash": "sha256-2+SDlNRTKsgo3LBRiMUcoEUb6sDViRNQhzJquZ4koOI=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "5843cf069272d92b60c3ed9e55b7a8989c01d4c7", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs_2" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/vendor/github.com/go-viper/mapstructure/v2/flake.nix b/vendor/github.com/go-viper/mapstructure/v2/flake.nix new file mode 100644 index 000000000..4ed0f5331 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/flake.nix @@ -0,0 +1,39 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + flake-parts.url = "github:hercules-ci/flake-parts"; + devenv.url = "github:cachix/devenv"; + }; + + outputs = inputs@{ flake-parts, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + imports = [ + inputs.devenv.flakeModule + ]; + + systems = [ "x86_64-linux" "x86_64-darwin" "aarch64-darwin" ]; + + perSystem = { config, self', inputs', pkgs, system, ... }: rec { + devenv.shells = { + default = { + languages = { + go.enable = true; + }; + + pre-commit.hooks = { + nixpkgs-fmt.enable = true; + }; + + packages = with pkgs; [ + golangci-lint + ]; + + # https://github.com/cachix/devenv/issues/528#issuecomment-1556108767 + containers = pkgs.lib.mkForce { }; + }; + + ci = devenv.shells.default; + }; + }; + }; +} diff --git a/vendor/github.com/go-viper/mapstructure/v2/mapstructure.go b/vendor/github.com/go-viper/mapstructure/v2/mapstructure.go new file mode 100644 index 000000000..27f21bc72 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/mapstructure.go @@ -0,0 +1,1562 @@ +// Package mapstructure exposes functionality to convert one arbitrary +// Go type into another, typically to convert a map[string]interface{} +// into a native Go structure. +// +// The Go structure can be arbitrarily complex, containing slices, +// other structs, etc. and the decoder will properly decode nested +// maps and so on into the proper structures in the native Go struct. +// See the examples to see what the decoder is capable of. +// +// The simplest function to start with is Decode. +// +// # Field Tags +// +// When decoding to a struct, mapstructure will use the field name by +// default to perform the mapping. For example, if a struct has a field +// "Username" then mapstructure will look for a key in the source value +// of "username" (case insensitive). +// +// type User struct { +// Username string +// } +// +// You can change the behavior of mapstructure by using struct tags. +// The default struct tag that mapstructure looks for is "mapstructure" +// but you can customize it using DecoderConfig. +// +// # Renaming Fields +// +// To rename the key that mapstructure looks for, use the "mapstructure" +// tag and set a value directly. For example, to change the "username" example +// above to "user": +// +// type User struct { +// Username string `mapstructure:"user"` +// } +// +// # Embedded Structs and Squashing +// +// Embedded structs are treated as if they're another field with that name. +// By default, the two structs below are equivalent when decoding with +// mapstructure: +// +// type Person struct { +// Name string +// } +// +// type Friend struct { +// Person +// } +// +// type Friend struct { +// Person Person +// } +// +// This would require an input that looks like below: +// +// map[string]interface{}{ +// "person": map[string]interface{}{"name": "alice"}, +// } +// +// If your "person" value is NOT nested, then you can append ",squash" to +// your tag value and mapstructure will treat it as if the embedded struct +// were part of the struct directly. Example: +// +// type Friend struct { +// Person `mapstructure:",squash"` +// } +// +// Now the following input would be accepted: +// +// map[string]interface{}{ +// "name": "alice", +// } +// +// When decoding from a struct to a map, the squash tag squashes the struct +// fields into a single map. Using the example structs from above: +// +// Friend{Person: Person{Name: "alice"}} +// +// Will be decoded into a map: +// +// map[string]interface{}{ +// "name": "alice", +// } +// +// DecoderConfig has a field that changes the behavior of mapstructure +// to always squash embedded structs. +// +// # Remainder Values +// +// If there are any unmapped keys in the source value, mapstructure by +// default will silently ignore them. You can error by setting ErrorUnused +// in DecoderConfig. If you're using Metadata you can also maintain a slice +// of the unused keys. +// +// You can also use the ",remain" suffix on your tag to collect all unused +// values in a map. The field with this tag MUST be a map type and should +// probably be a "map[string]interface{}" or "map[interface{}]interface{}". +// See example below: +// +// type Friend struct { +// Name string +// Other map[string]interface{} `mapstructure:",remain"` +// } +// +// Given the input below, Other would be populated with the other +// values that weren't used (everything but "name"): +// +// map[string]interface{}{ +// "name": "bob", +// "address": "123 Maple St.", +// } +// +// # Omit Empty Values +// +// When decoding from a struct to any other value, you may use the +// ",omitempty" suffix on your tag to omit that value if it equates to +// the zero value. The zero value of all types is specified in the Go +// specification. +// +// For example, the zero type of a numeric type is zero ("0"). If the struct +// field value is zero and a numeric type, the field is empty, and it won't +// be encoded into the destination type. +// +// type Source struct { +// Age int `mapstructure:",omitempty"` +// } +// +// # Unexported fields +// +// Since unexported (private) struct fields cannot be set outside the package +// where they are defined, the decoder will simply skip them. +// +// For this output type definition: +// +// type Exported struct { +// private string // this unexported field will be skipped +// Public string +// } +// +// Using this map as input: +// +// map[string]interface{}{ +// "private": "I will be ignored", +// "Public": "I made it through!", +// } +// +// The following struct will be decoded: +// +// type Exported struct { +// private: "" // field is left with an empty string (zero value) +// Public: "I made it through!" +// } +// +// # Other Configuration +// +// mapstructure is highly configurable. See the DecoderConfig struct +// for other features and options that are supported. +package mapstructure + +import ( + "encoding/json" + "errors" + "fmt" + "reflect" + "sort" + "strconv" + "strings" +) + +// DecodeHookFunc is the callback function that can be used for +// data transformations. See "DecodeHook" in the DecoderConfig +// struct. +// +// The type must be one of DecodeHookFuncType, DecodeHookFuncKind, or +// DecodeHookFuncValue. +// Values are a superset of Types (Values can return types), and Types are a +// superset of Kinds (Types can return Kinds) and are generally a richer thing +// to use, but Kinds are simpler if you only need those. +// +// The reason DecodeHookFunc is multi-typed is for backwards compatibility: +// we started with Kinds and then realized Types were the better solution, +// but have a promise to not break backwards compat so we now support +// both. +type DecodeHookFunc interface{} + +// DecodeHookFuncType is a DecodeHookFunc which has complete information about +// the source and target types. +type DecodeHookFuncType func(reflect.Type, reflect.Type, interface{}) (interface{}, error) + +// DecodeHookFuncKind is a DecodeHookFunc which knows only the Kinds of the +// source and target types. +type DecodeHookFuncKind func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error) + +// DecodeHookFuncValue is a DecodeHookFunc which has complete access to both the source and target +// values. +type DecodeHookFuncValue func(from reflect.Value, to reflect.Value) (interface{}, error) + +// DecoderConfig is the configuration that is used to create a new decoder +// and allows customization of various aspects of decoding. +type DecoderConfig struct { + // DecodeHook, if set, will be called before any decoding and any + // type conversion (if WeaklyTypedInput is on). This lets you modify + // the values before they're set down onto the resulting struct. The + // DecodeHook is called for every map and value in the input. This means + // that if a struct has embedded fields with squash tags the decode hook + // is called only once with all of the input data, not once for each + // embedded struct. + // + // If an error is returned, the entire decode will fail with that error. + DecodeHook DecodeHookFunc + + // If ErrorUnused is true, then it is an error for there to exist + // keys in the original map that were unused in the decoding process + // (extra keys). + ErrorUnused bool + + // If ErrorUnset is true, then it is an error for there to exist + // fields in the result that were not set in the decoding process + // (extra fields). This only applies to decoding to a struct. This + // will affect all nested structs as well. + ErrorUnset bool + + // ZeroFields, if set to true, will zero fields before writing them. + // For example, a map will be emptied before decoded values are put in + // it. If this is false, a map will be merged. + ZeroFields bool + + // If WeaklyTypedInput is true, the decoder will make the following + // "weak" conversions: + // + // - bools to string (true = "1", false = "0") + // - numbers to string (base 10) + // - bools to int/uint (true = 1, false = 0) + // - strings to int/uint (base implied by prefix) + // - int to bool (true if value != 0) + // - string to bool (accepts: 1, t, T, TRUE, true, True, 0, f, F, + // FALSE, false, False. Anything else is an error) + // - empty array = empty map and vice versa + // - negative numbers to overflowed uint values (base 10) + // - slice of maps to a merged map + // - single values are converted to slices if required. Each + // element is weakly decoded. For example: "4" can become []int{4} + // if the target type is an int slice. + // + WeaklyTypedInput bool + + // Squash will squash embedded structs. A squash tag may also be + // added to an individual struct field using a tag. For example: + // + // type Parent struct { + // Child `mapstructure:",squash"` + // } + Squash bool + + // Metadata is the struct that will contain extra metadata about + // the decoding. If this is nil, then no metadata will be tracked. + Metadata *Metadata + + // Result is a pointer to the struct that will contain the decoded + // value. + Result interface{} + + // The tag name that mapstructure reads for field names. This + // defaults to "mapstructure" + TagName string + + // IgnoreUntaggedFields ignores all struct fields without explicit + // TagName, comparable to `mapstructure:"-"` as default behaviour. + IgnoreUntaggedFields bool + + // MatchName is the function used to match the map key to the struct + // field name or tag. Defaults to `strings.EqualFold`. This can be used + // to implement case-sensitive tag values, support snake casing, etc. + MatchName func(mapKey, fieldName string) bool +} + +// A Decoder takes a raw interface value and turns it into structured +// data, keeping track of rich error information along the way in case +// anything goes wrong. Unlike the basic top-level Decode method, you can +// more finely control how the Decoder behaves using the DecoderConfig +// structure. The top-level Decode method is just a convenience that sets +// up the most basic Decoder. +type Decoder struct { + config *DecoderConfig +} + +// Metadata contains information about decoding a structure that +// is tedious or difficult to get otherwise. +type Metadata struct { + // Keys are the keys of the structure which were successfully decoded + Keys []string + + // Unused is a slice of keys that were found in the raw value but + // weren't decoded since there was no matching field in the result interface + Unused []string + + // Unset is a slice of field names that were found in the result interface + // but weren't set in the decoding process since there was no matching value + // in the input + Unset []string +} + +// Decode takes an input structure and uses reflection to translate it to +// the output structure. output must be a pointer to a map or struct. +func Decode(input interface{}, output interface{}) error { + config := &DecoderConfig{ + Metadata: nil, + Result: output, + } + + decoder, err := NewDecoder(config) + if err != nil { + return err + } + + return decoder.Decode(input) +} + +// WeakDecode is the same as Decode but is shorthand to enable +// WeaklyTypedInput. See DecoderConfig for more info. +func WeakDecode(input, output interface{}) error { + config := &DecoderConfig{ + Metadata: nil, + Result: output, + WeaklyTypedInput: true, + } + + decoder, err := NewDecoder(config) + if err != nil { + return err + } + + return decoder.Decode(input) +} + +// DecodeMetadata is the same as Decode, but is shorthand to +// enable metadata collection. See DecoderConfig for more info. +func DecodeMetadata(input interface{}, output interface{}, metadata *Metadata) error { + config := &DecoderConfig{ + Metadata: metadata, + Result: output, + } + + decoder, err := NewDecoder(config) + if err != nil { + return err + } + + return decoder.Decode(input) +} + +// WeakDecodeMetadata is the same as Decode, but is shorthand to +// enable both WeaklyTypedInput and metadata collection. See +// DecoderConfig for more info. +func WeakDecodeMetadata(input interface{}, output interface{}, metadata *Metadata) error { + config := &DecoderConfig{ + Metadata: metadata, + Result: output, + WeaklyTypedInput: true, + } + + decoder, err := NewDecoder(config) + if err != nil { + return err + } + + return decoder.Decode(input) +} + +// NewDecoder returns a new decoder for the given configuration. Once +// a decoder has been returned, the same configuration must not be used +// again. +func NewDecoder(config *DecoderConfig) (*Decoder, error) { + val := reflect.ValueOf(config.Result) + if val.Kind() != reflect.Ptr { + return nil, errors.New("result must be a pointer") + } + + val = val.Elem() + if !val.CanAddr() { + return nil, errors.New("result must be addressable (a pointer)") + } + + if config.Metadata != nil { + if config.Metadata.Keys == nil { + config.Metadata.Keys = make([]string, 0) + } + + if config.Metadata.Unused == nil { + config.Metadata.Unused = make([]string, 0) + } + + if config.Metadata.Unset == nil { + config.Metadata.Unset = make([]string, 0) + } + } + + if config.TagName == "" { + config.TagName = "mapstructure" + } + + if config.MatchName == nil { + config.MatchName = strings.EqualFold + } + + result := &Decoder{ + config: config, + } + + return result, nil +} + +// Decode decodes the given raw interface to the target pointer specified +// by the configuration. +func (d *Decoder) Decode(input interface{}) error { + return d.decode("", input, reflect.ValueOf(d.config.Result).Elem()) +} + +// Decodes an unknown data type into a specific reflection value. +func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) error { + var inputVal reflect.Value + if input != nil { + inputVal = reflect.ValueOf(input) + + // We need to check here if input is a typed nil. Typed nils won't + // match the "input == nil" below so we check that here. + if inputVal.Kind() == reflect.Ptr && inputVal.IsNil() { + input = nil + } + } + + if input == nil { + // If the data is nil, then we don't set anything, unless ZeroFields is set + // to true. + if d.config.ZeroFields { + outVal.Set(reflect.Zero(outVal.Type())) + + if d.config.Metadata != nil && name != "" { + d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) + } + } + return nil + } + + if !inputVal.IsValid() { + // If the input value is invalid, then we just set the value + // to be the zero value. + outVal.Set(reflect.Zero(outVal.Type())) + if d.config.Metadata != nil && name != "" { + d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) + } + return nil + } + + if d.config.DecodeHook != nil { + // We have a DecodeHook, so let's pre-process the input. + var err error + input, err = DecodeHookExec(d.config.DecodeHook, inputVal, outVal) + if err != nil { + return fmt.Errorf("error decoding '%s': %w", name, err) + } + } + + var err error + outputKind := getKind(outVal) + addMetaKey := true + switch outputKind { + case reflect.Bool: + err = d.decodeBool(name, input, outVal) + case reflect.Interface: + err = d.decodeBasic(name, input, outVal) + case reflect.String: + err = d.decodeString(name, input, outVal) + case reflect.Int: + err = d.decodeInt(name, input, outVal) + case reflect.Uint: + err = d.decodeUint(name, input, outVal) + case reflect.Float32: + err = d.decodeFloat(name, input, outVal) + case reflect.Struct: + err = d.decodeStruct(name, input, outVal) + case reflect.Map: + err = d.decodeMap(name, input, outVal) + case reflect.Ptr: + addMetaKey, err = d.decodePtr(name, input, outVal) + case reflect.Slice: + err = d.decodeSlice(name, input, outVal) + case reflect.Array: + err = d.decodeArray(name, input, outVal) + case reflect.Func: + err = d.decodeFunc(name, input, outVal) + default: + // If we reached this point then we weren't able to decode it + return fmt.Errorf("%s: unsupported type: %s", name, outputKind) + } + + // If we reached here, then we successfully decoded SOMETHING, so + // mark the key as used if we're tracking metainput. + if addMetaKey && d.config.Metadata != nil && name != "" { + d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) + } + + return err +} + +// This decodes a basic type (bool, int, string, etc.) and sets the +// value to "data" of that type. +func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) error { + if val.IsValid() && val.Elem().IsValid() { + elem := val.Elem() + + // If we can't address this element, then its not writable. Instead, + // we make a copy of the value (which is a pointer and therefore + // writable), decode into that, and replace the whole value. + copied := false + if !elem.CanAddr() { + copied = true + + // Make *T + copy := reflect.New(elem.Type()) + + // *T = elem + copy.Elem().Set(elem) + + // Set elem so we decode into it + elem = copy + } + + // Decode. If we have an error then return. We also return right + // away if we're not a copy because that means we decoded directly. + if err := d.decode(name, data, elem); err != nil || !copied { + return err + } + + // If we're a copy, we need to set te final result + val.Set(elem.Elem()) + return nil + } + + dataVal := reflect.ValueOf(data) + + // If the input data is a pointer, and the assigned type is the dereference + // of that exact pointer, then indirect it so that we can assign it. + // Example: *string to string + if dataVal.Kind() == reflect.Ptr && dataVal.Type().Elem() == val.Type() { + dataVal = reflect.Indirect(dataVal) + } + + if !dataVal.IsValid() { + dataVal = reflect.Zero(val.Type()) + } + + dataValType := dataVal.Type() + if !dataValType.AssignableTo(val.Type()) { + return fmt.Errorf( + "'%s' expected type '%s', got '%s'", + name, val.Type(), dataValType) + } + + val.Set(dataVal) + return nil +} + +func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + + converted := true + switch { + case dataKind == reflect.String: + val.SetString(dataVal.String()) + case dataKind == reflect.Bool && d.config.WeaklyTypedInput: + if dataVal.Bool() { + val.SetString("1") + } else { + val.SetString("0") + } + case dataKind == reflect.Int && d.config.WeaklyTypedInput: + val.SetString(strconv.FormatInt(dataVal.Int(), 10)) + case dataKind == reflect.Uint && d.config.WeaklyTypedInput: + val.SetString(strconv.FormatUint(dataVal.Uint(), 10)) + case dataKind == reflect.Float32 && d.config.WeaklyTypedInput: + val.SetString(strconv.FormatFloat(dataVal.Float(), 'f', -1, 64)) + case dataKind == reflect.Slice && d.config.WeaklyTypedInput, + dataKind == reflect.Array && d.config.WeaklyTypedInput: + dataType := dataVal.Type() + elemKind := dataType.Elem().Kind() + switch elemKind { + case reflect.Uint8: + var uints []uint8 + if dataKind == reflect.Array { + uints = make([]uint8, dataVal.Len(), dataVal.Len()) + for i := range uints { + uints[i] = dataVal.Index(i).Interface().(uint8) + } + } else { + uints = dataVal.Interface().([]uint8) + } + val.SetString(string(uints)) + default: + converted = false + } + default: + converted = false + } + + if !converted { + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", + name, val.Type(), dataVal.Type(), data) + } + + return nil +} + +func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + dataType := dataVal.Type() + + switch { + case dataKind == reflect.Int: + val.SetInt(dataVal.Int()) + case dataKind == reflect.Uint: + val.SetInt(int64(dataVal.Uint())) + case dataKind == reflect.Float32: + val.SetInt(int64(dataVal.Float())) + case dataKind == reflect.Bool && d.config.WeaklyTypedInput: + if dataVal.Bool() { + val.SetInt(1) + } else { + val.SetInt(0) + } + case dataKind == reflect.String && d.config.WeaklyTypedInput: + str := dataVal.String() + if str == "" { + str = "0" + } + + i, err := strconv.ParseInt(str, 0, val.Type().Bits()) + if err == nil { + val.SetInt(i) + } else { + return fmt.Errorf("cannot parse '%s' as int: %s", name, err) + } + case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": + jn := data.(json.Number) + i, err := jn.Int64() + if err != nil { + return fmt.Errorf( + "error decoding json.Number into %s: %s", name, err) + } + val.SetInt(i) + default: + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", + name, val.Type(), dataVal.Type(), data) + } + + return nil +} + +func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + dataType := dataVal.Type() + + switch { + case dataKind == reflect.Int: + i := dataVal.Int() + if i < 0 && !d.config.WeaklyTypedInput { + return fmt.Errorf("cannot parse '%s', %d overflows uint", + name, i) + } + val.SetUint(uint64(i)) + case dataKind == reflect.Uint: + val.SetUint(dataVal.Uint()) + case dataKind == reflect.Float32: + f := dataVal.Float() + if f < 0 && !d.config.WeaklyTypedInput { + return fmt.Errorf("cannot parse '%s', %f overflows uint", + name, f) + } + val.SetUint(uint64(f)) + case dataKind == reflect.Bool && d.config.WeaklyTypedInput: + if dataVal.Bool() { + val.SetUint(1) + } else { + val.SetUint(0) + } + case dataKind == reflect.String && d.config.WeaklyTypedInput: + str := dataVal.String() + if str == "" { + str = "0" + } + + i, err := strconv.ParseUint(str, 0, val.Type().Bits()) + if err == nil { + val.SetUint(i) + } else { + return fmt.Errorf("cannot parse '%s' as uint: %s", name, err) + } + case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": + jn := data.(json.Number) + i, err := strconv.ParseUint(string(jn), 0, 64) + if err != nil { + return fmt.Errorf( + "error decoding json.Number into %s: %s", name, err) + } + val.SetUint(i) + default: + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", + name, val.Type(), dataVal.Type(), data) + } + + return nil +} + +func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + + switch { + case dataKind == reflect.Bool: + val.SetBool(dataVal.Bool()) + case dataKind == reflect.Int && d.config.WeaklyTypedInput: + val.SetBool(dataVal.Int() != 0) + case dataKind == reflect.Uint && d.config.WeaklyTypedInput: + val.SetBool(dataVal.Uint() != 0) + case dataKind == reflect.Float32 && d.config.WeaklyTypedInput: + val.SetBool(dataVal.Float() != 0) + case dataKind == reflect.String && d.config.WeaklyTypedInput: + b, err := strconv.ParseBool(dataVal.String()) + if err == nil { + val.SetBool(b) + } else if dataVal.String() == "" { + val.SetBool(false) + } else { + return fmt.Errorf("cannot parse '%s' as bool: %s", name, err) + } + default: + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", + name, val.Type(), dataVal.Type(), data) + } + + return nil +} + +func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + dataType := dataVal.Type() + + switch { + case dataKind == reflect.Int: + val.SetFloat(float64(dataVal.Int())) + case dataKind == reflect.Uint: + val.SetFloat(float64(dataVal.Uint())) + case dataKind == reflect.Float32: + val.SetFloat(dataVal.Float()) + case dataKind == reflect.Bool && d.config.WeaklyTypedInput: + if dataVal.Bool() { + val.SetFloat(1) + } else { + val.SetFloat(0) + } + case dataKind == reflect.String && d.config.WeaklyTypedInput: + str := dataVal.String() + if str == "" { + str = "0" + } + + f, err := strconv.ParseFloat(str, val.Type().Bits()) + if err == nil { + val.SetFloat(f) + } else { + return fmt.Errorf("cannot parse '%s' as float: %s", name, err) + } + case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": + jn := data.(json.Number) + i, err := jn.Float64() + if err != nil { + return fmt.Errorf( + "error decoding json.Number into %s: %s", name, err) + } + val.SetFloat(i) + default: + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", + name, val.Type(), dataVal.Type(), data) + } + + return nil +} + +func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) error { + valType := val.Type() + valKeyType := valType.Key() + valElemType := valType.Elem() + + // By default we overwrite keys in the current map + valMap := val + + // If the map is nil or we're purposely zeroing fields, make a new map + if valMap.IsNil() || d.config.ZeroFields { + // Make a new map to hold our result + mapType := reflect.MapOf(valKeyType, valElemType) + valMap = reflect.MakeMap(mapType) + } + + dataVal := reflect.ValueOf(data) + + // Resolve any levels of indirection + for dataVal.Kind() == reflect.Pointer { + dataVal = reflect.Indirect(dataVal) + } + + // Check input type and based on the input type jump to the proper func + switch dataVal.Kind() { + case reflect.Map: + return d.decodeMapFromMap(name, dataVal, val, valMap) + + case reflect.Struct: + return d.decodeMapFromStruct(name, dataVal, val, valMap) + + case reflect.Array, reflect.Slice: + if d.config.WeaklyTypedInput { + return d.decodeMapFromSlice(name, dataVal, val, valMap) + } + + fallthrough + + default: + return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind()) + } +} + +func (d *Decoder) decodeMapFromSlice(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { + // Special case for BC reasons (covered by tests) + if dataVal.Len() == 0 { + val.Set(valMap) + return nil + } + + for i := 0; i < dataVal.Len(); i++ { + err := d.decode( + name+"["+strconv.Itoa(i)+"]", + dataVal.Index(i).Interface(), val) + if err != nil { + return err + } + } + + return nil +} + +func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { + valType := val.Type() + valKeyType := valType.Key() + valElemType := valType.Elem() + + // Accumulate errors + errors := make([]string, 0) + + // If the input data is empty, then we just match what the input data is. + if dataVal.Len() == 0 { + if dataVal.IsNil() { + if !val.IsNil() { + val.Set(dataVal) + } + } else { + // Set to empty allocated value + val.Set(valMap) + } + + return nil + } + + for _, k := range dataVal.MapKeys() { + fieldName := name + "[" + k.String() + "]" + + // First decode the key into the proper type + currentKey := reflect.Indirect(reflect.New(valKeyType)) + if err := d.decode(fieldName, k.Interface(), currentKey); err != nil { + errors = appendErrors(errors, err) + continue + } + + // Next decode the data into the proper type + v := dataVal.MapIndex(k).Interface() + currentVal := reflect.Indirect(reflect.New(valElemType)) + if err := d.decode(fieldName, v, currentVal); err != nil { + errors = appendErrors(errors, err) + continue + } + + valMap.SetMapIndex(currentKey, currentVal) + } + + // Set the built up map to the value + val.Set(valMap) + + // If we had errors, return those + if len(errors) > 0 { + return &Error{errors} + } + + return nil +} + +func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { + typ := dataVal.Type() + for i := 0; i < typ.NumField(); i++ { + // Get the StructField first since this is a cheap operation. If the + // field is unexported, then ignore it. + f := typ.Field(i) + if f.PkgPath != "" { + continue + } + + // Next get the actual value of this field and verify it is assignable + // to the map value. + v := dataVal.Field(i) + if !v.Type().AssignableTo(valMap.Type().Elem()) { + return fmt.Errorf("cannot assign type '%s' to map value field of type '%s'", v.Type(), valMap.Type().Elem()) + } + + tagValue := f.Tag.Get(d.config.TagName) + keyName := f.Name + + if tagValue == "" && d.config.IgnoreUntaggedFields { + continue + } + + // If Squash is set in the config, we squash the field down. + squash := d.config.Squash && v.Kind() == reflect.Struct && f.Anonymous + + v = dereferencePtrToStructIfNeeded(v, d.config.TagName) + + // Determine the name of the key in the map + if index := strings.Index(tagValue, ","); index != -1 { + if tagValue[:index] == "-" { + continue + } + // If "omitempty" is specified in the tag, it ignores empty values. + if strings.Index(tagValue[index+1:], "omitempty") != -1 && isEmptyValue(v) { + continue + } + + // If "squash" is specified in the tag, we squash the field down. + squash = squash || strings.Index(tagValue[index+1:], "squash") != -1 + if squash { + // When squashing, the embedded type can be a pointer to a struct. + if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct { + v = v.Elem() + } + + // The final type must be a struct + if v.Kind() != reflect.Struct { + return fmt.Errorf("cannot squash non-struct type '%s'", v.Type()) + } + } else { + if strings.Index(tagValue[index+1:], "remain") != -1 { + if v.Kind() != reflect.Map { + return fmt.Errorf("error remain-tag field with invalid type: '%s'", v.Type()) + } + + ptr := v.MapRange() + for ptr.Next() { + valMap.SetMapIndex(ptr.Key(), ptr.Value()) + } + continue + } + } + if keyNameTagValue := tagValue[:index]; keyNameTagValue != "" { + keyName = keyNameTagValue + } + } else if len(tagValue) > 0 { + if tagValue == "-" { + continue + } + keyName = tagValue + } + + switch v.Kind() { + // this is an embedded struct, so handle it differently + case reflect.Struct: + x := reflect.New(v.Type()) + x.Elem().Set(v) + + vType := valMap.Type() + vKeyType := vType.Key() + vElemType := vType.Elem() + mType := reflect.MapOf(vKeyType, vElemType) + vMap := reflect.MakeMap(mType) + + // Creating a pointer to a map so that other methods can completely + // overwrite the map if need be (looking at you decodeMapFromMap). The + // indirection allows the underlying map to be settable (CanSet() == true) + // where as reflect.MakeMap returns an unsettable map. + addrVal := reflect.New(vMap.Type()) + reflect.Indirect(addrVal).Set(vMap) + + err := d.decode(keyName, x.Interface(), reflect.Indirect(addrVal)) + if err != nil { + return err + } + + // the underlying map may have been completely overwritten so pull + // it indirectly out of the enclosing value. + vMap = reflect.Indirect(addrVal) + + if squash { + for _, k := range vMap.MapKeys() { + valMap.SetMapIndex(k, vMap.MapIndex(k)) + } + } else { + valMap.SetMapIndex(reflect.ValueOf(keyName), vMap) + } + + default: + valMap.SetMapIndex(reflect.ValueOf(keyName), v) + } + } + + if val.CanAddr() { + val.Set(valMap) + } + + return nil +} + +func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) (bool, error) { + // If the input data is nil, then we want to just set the output + // pointer to be nil as well. + isNil := data == nil + if !isNil { + switch v := reflect.Indirect(reflect.ValueOf(data)); v.Kind() { + case reflect.Chan, + reflect.Func, + reflect.Interface, + reflect.Map, + reflect.Ptr, + reflect.Slice: + isNil = v.IsNil() + } + } + if isNil { + if !val.IsNil() && val.CanSet() { + nilValue := reflect.New(val.Type()).Elem() + val.Set(nilValue) + } + + return true, nil + } + + // Create an element of the concrete (non pointer) type and decode + // into that. Then set the value of the pointer to this type. + valType := val.Type() + valElemType := valType.Elem() + if val.CanSet() { + realVal := val + if realVal.IsNil() || d.config.ZeroFields { + realVal = reflect.New(valElemType) + } + + if err := d.decode(name, data, reflect.Indirect(realVal)); err != nil { + return false, err + } + + val.Set(realVal) + } else { + if err := d.decode(name, data, reflect.Indirect(val)); err != nil { + return false, err + } + } + return false, nil +} + +func (d *Decoder) decodeFunc(name string, data interface{}, val reflect.Value) error { + // Create an element of the concrete (non pointer) type and decode + // into that. Then set the value of the pointer to this type. + dataVal := reflect.Indirect(reflect.ValueOf(data)) + if val.Type() != dataVal.Type() { + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", + name, val.Type(), dataVal.Type(), data) + } + val.Set(dataVal) + return nil +} + +func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataValKind := dataVal.Kind() + valType := val.Type() + valElemType := valType.Elem() + sliceType := reflect.SliceOf(valElemType) + + // If we have a non array/slice type then we first attempt to convert. + if dataValKind != reflect.Array && dataValKind != reflect.Slice { + if d.config.WeaklyTypedInput { + switch { + // Slice and array we use the normal logic + case dataValKind == reflect.Slice, dataValKind == reflect.Array: + break + + // Empty maps turn into empty slices + case dataValKind == reflect.Map: + if dataVal.Len() == 0 { + val.Set(reflect.MakeSlice(sliceType, 0, 0)) + return nil + } + // Create slice of maps of other sizes + return d.decodeSlice(name, []interface{}{data}, val) + + case dataValKind == reflect.String && valElemType.Kind() == reflect.Uint8: + return d.decodeSlice(name, []byte(dataVal.String()), val) + + // All other types we try to convert to the slice type + // and "lift" it into it. i.e. a string becomes a string slice. + default: + // Just re-try this function with data as a slice. + return d.decodeSlice(name, []interface{}{data}, val) + } + } + + return fmt.Errorf( + "'%s': source data must be an array or slice, got %s", name, dataValKind) + } + + // If the input value is nil, then don't allocate since empty != nil + if dataValKind != reflect.Array && dataVal.IsNil() { + return nil + } + + valSlice := val + if valSlice.IsNil() || d.config.ZeroFields { + // Make a new slice to hold our result, same size as the original data. + valSlice = reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len()) + } else if valSlice.Len() > dataVal.Len() { + valSlice = valSlice.Slice(0, dataVal.Len()) + } + + // Accumulate any errors + errors := make([]string, 0) + + for i := 0; i < dataVal.Len(); i++ { + currentData := dataVal.Index(i).Interface() + for valSlice.Len() <= i { + valSlice = reflect.Append(valSlice, reflect.Zero(valElemType)) + } + currentField := valSlice.Index(i) + + fieldName := name + "[" + strconv.Itoa(i) + "]" + if err := d.decode(fieldName, currentData, currentField); err != nil { + errors = appendErrors(errors, err) + } + } + + // Finally, set the value to the slice we built up + val.Set(valSlice) + + // If there were errors, we return those + if len(errors) > 0 { + return &Error{errors} + } + + return nil +} + +func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataValKind := dataVal.Kind() + valType := val.Type() + valElemType := valType.Elem() + arrayType := reflect.ArrayOf(valType.Len(), valElemType) + + valArray := val + + if isComparable(valArray) && valArray.Interface() == reflect.Zero(valArray.Type()).Interface() || d.config.ZeroFields { + // Check input type + if dataValKind != reflect.Array && dataValKind != reflect.Slice { + if d.config.WeaklyTypedInput { + switch { + // Empty maps turn into empty arrays + case dataValKind == reflect.Map: + if dataVal.Len() == 0 { + val.Set(reflect.Zero(arrayType)) + return nil + } + + // All other types we try to convert to the array type + // and "lift" it into it. i.e. a string becomes a string array. + default: + // Just re-try this function with data as a slice. + return d.decodeArray(name, []interface{}{data}, val) + } + } + + return fmt.Errorf( + "'%s': source data must be an array or slice, got %s", name, dataValKind) + + } + if dataVal.Len() > arrayType.Len() { + return fmt.Errorf( + "'%s': expected source data to have length less or equal to %d, got %d", name, arrayType.Len(), dataVal.Len()) + } + + // Make a new array to hold our result, same size as the original data. + valArray = reflect.New(arrayType).Elem() + } + + // Accumulate any errors + errors := make([]string, 0) + + for i := 0; i < dataVal.Len(); i++ { + currentData := dataVal.Index(i).Interface() + currentField := valArray.Index(i) + + fieldName := name + "[" + strconv.Itoa(i) + "]" + if err := d.decode(fieldName, currentData, currentField); err != nil { + errors = appendErrors(errors, err) + } + } + + // Finally, set the value to the array we built up + val.Set(valArray) + + // If there were errors, we return those + if len(errors) > 0 { + return &Error{errors} + } + + return nil +} + +func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + + // If the type of the value to write to and the data match directly, + // then we just set it directly instead of recursing into the structure. + if dataVal.Type() == val.Type() { + val.Set(dataVal) + return nil + } + + dataValKind := dataVal.Kind() + switch dataValKind { + case reflect.Map: + return d.decodeStructFromMap(name, dataVal, val) + + case reflect.Struct: + // Not the most efficient way to do this but we can optimize later if + // we want to. To convert from struct to struct we go to map first + // as an intermediary. + + // Make a new map to hold our result + mapType := reflect.TypeOf((map[string]interface{})(nil)) + mval := reflect.MakeMap(mapType) + + // Creating a pointer to a map so that other methods can completely + // overwrite the map if need be (looking at you decodeMapFromMap). The + // indirection allows the underlying map to be settable (CanSet() == true) + // where as reflect.MakeMap returns an unsettable map. + addrVal := reflect.New(mval.Type()) + + reflect.Indirect(addrVal).Set(mval) + if err := d.decodeMapFromStruct(name, dataVal, reflect.Indirect(addrVal), mval); err != nil { + return err + } + + result := d.decodeStructFromMap(name, reflect.Indirect(addrVal), val) + return result + + default: + return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind()) + } +} + +func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) error { + dataValType := dataVal.Type() + if kind := dataValType.Key().Kind(); kind != reflect.String && kind != reflect.Interface { + return fmt.Errorf( + "'%s' needs a map with string keys, has '%s' keys", + name, dataValType.Key().Kind()) + } + + dataValKeys := make(map[reflect.Value]struct{}) + dataValKeysUnused := make(map[interface{}]struct{}) + for _, dataValKey := range dataVal.MapKeys() { + dataValKeys[dataValKey] = struct{}{} + dataValKeysUnused[dataValKey.Interface()] = struct{}{} + } + + targetValKeysUnused := make(map[interface{}]struct{}) + errors := make([]string, 0) + + // This slice will keep track of all the structs we'll be decoding. + // There can be more than one struct if there are embedded structs + // that are squashed. + structs := make([]reflect.Value, 1, 5) + structs[0] = val + + // Compile the list of all the fields that we're going to be decoding + // from all the structs. + type field struct { + field reflect.StructField + val reflect.Value + } + + // remainField is set to a valid field set with the "remain" tag if + // we are keeping track of remaining values. + var remainField *field + + fields := []field{} + for len(structs) > 0 { + structVal := structs[0] + structs = structs[1:] + + structType := structVal.Type() + + for i := 0; i < structType.NumField(); i++ { + fieldType := structType.Field(i) + fieldVal := structVal.Field(i) + if fieldVal.Kind() == reflect.Ptr && fieldVal.Elem().Kind() == reflect.Struct { + // Handle embedded struct pointers as embedded structs. + fieldVal = fieldVal.Elem() + } + + // If "squash" is specified in the tag, we squash the field down. + squash := d.config.Squash && fieldVal.Kind() == reflect.Struct && fieldType.Anonymous + remain := false + + // We always parse the tags cause we're looking for other tags too + tagParts := strings.Split(fieldType.Tag.Get(d.config.TagName), ",") + for _, tag := range tagParts[1:] { + if tag == "squash" { + squash = true + break + } + + if tag == "remain" { + remain = true + break + } + } + + if squash { + if fieldVal.Kind() != reflect.Struct { + errors = appendErrors(errors, + fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldVal.Kind())) + } else { + structs = append(structs, fieldVal) + } + continue + } + + // Build our field + if remain { + remainField = &field{fieldType, fieldVal} + } else { + // Normal struct field, store it away + fields = append(fields, field{fieldType, fieldVal}) + } + } + } + + // for fieldType, field := range fields { + for _, f := range fields { + field, fieldValue := f.field, f.val + fieldName := field.Name + + tagValue := field.Tag.Get(d.config.TagName) + if tagValue == "" && d.config.IgnoreUntaggedFields { + continue + } + tagValue = strings.SplitN(tagValue, ",", 2)[0] + if tagValue != "" { + fieldName = tagValue + } + + rawMapKey := reflect.ValueOf(fieldName) + rawMapVal := dataVal.MapIndex(rawMapKey) + if !rawMapVal.IsValid() { + // Do a slower search by iterating over each key and + // doing case-insensitive search. + for dataValKey := range dataValKeys { + mK, ok := dataValKey.Interface().(string) + if !ok { + // Not a string key + continue + } + + if d.config.MatchName(mK, fieldName) { + rawMapKey = dataValKey + rawMapVal = dataVal.MapIndex(dataValKey) + break + } + } + + if !rawMapVal.IsValid() { + // There was no matching key in the map for the value in + // the struct. Remember it for potential errors and metadata. + targetValKeysUnused[fieldName] = struct{}{} + continue + } + } + + if !fieldValue.IsValid() { + // This should never happen + panic("field is not valid") + } + + // If we can't set the field, then it is unexported or something, + // and we just continue onwards. + if !fieldValue.CanSet() { + continue + } + + // Delete the key we're using from the unused map so we stop tracking + delete(dataValKeysUnused, rawMapKey.Interface()) + + // If the name is empty string, then we're at the root, and we + // don't dot-join the fields. + if name != "" { + fieldName = name + "." + fieldName + } + + if err := d.decode(fieldName, rawMapVal.Interface(), fieldValue); err != nil { + errors = appendErrors(errors, err) + } + } + + // If we have a "remain"-tagged field and we have unused keys then + // we put the unused keys directly into the remain field. + if remainField != nil && len(dataValKeysUnused) > 0 { + // Build a map of only the unused values + remain := map[interface{}]interface{}{} + for key := range dataValKeysUnused { + remain[key] = dataVal.MapIndex(reflect.ValueOf(key)).Interface() + } + + // Decode it as-if we were just decoding this map onto our map. + if err := d.decodeMap(name, remain, remainField.val); err != nil { + errors = appendErrors(errors, err) + } + + // Set the map to nil so we have none so that the next check will + // not error (ErrorUnused) + dataValKeysUnused = nil + } + + if d.config.ErrorUnused && len(dataValKeysUnused) > 0 { + keys := make([]string, 0, len(dataValKeysUnused)) + for rawKey := range dataValKeysUnused { + keys = append(keys, rawKey.(string)) + } + sort.Strings(keys) + + err := fmt.Errorf("'%s' has invalid keys: %s", name, strings.Join(keys, ", ")) + errors = appendErrors(errors, err) + } + + if d.config.ErrorUnset && len(targetValKeysUnused) > 0 { + keys := make([]string, 0, len(targetValKeysUnused)) + for rawKey := range targetValKeysUnused { + keys = append(keys, rawKey.(string)) + } + sort.Strings(keys) + + err := fmt.Errorf("'%s' has unset fields: %s", name, strings.Join(keys, ", ")) + errors = appendErrors(errors, err) + } + + if len(errors) > 0 { + return &Error{errors} + } + + // Add the unused keys to the list of unused keys if we're tracking metadata + if d.config.Metadata != nil { + for rawKey := range dataValKeysUnused { + key := rawKey.(string) + if name != "" { + key = name + "." + key + } + + d.config.Metadata.Unused = append(d.config.Metadata.Unused, key) + } + for rawKey := range targetValKeysUnused { + key := rawKey.(string) + if name != "" { + key = name + "." + key + } + + d.config.Metadata.Unset = append(d.config.Metadata.Unset, key) + } + } + + return nil +} + +func isEmptyValue(v reflect.Value) bool { + switch getKind(v) { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + return false +} + +func getKind(val reflect.Value) reflect.Kind { + kind := val.Kind() + + switch { + case kind >= reflect.Int && kind <= reflect.Int64: + return reflect.Int + case kind >= reflect.Uint && kind <= reflect.Uint64: + return reflect.Uint + case kind >= reflect.Float32 && kind <= reflect.Float64: + return reflect.Float32 + default: + return kind + } +} + +func isStructTypeConvertibleToMap(typ reflect.Type, checkMapstructureTags bool, tagName string) bool { + for i := 0; i < typ.NumField(); i++ { + f := typ.Field(i) + if f.PkgPath == "" && !checkMapstructureTags { // check for unexported fields + return true + } + if checkMapstructureTags && f.Tag.Get(tagName) != "" { // check for mapstructure tags inside + return true + } + } + return false +} + +func dereferencePtrToStructIfNeeded(v reflect.Value, tagName string) reflect.Value { + if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct { + return v + } + deref := v.Elem() + derefT := deref.Type() + if isStructTypeConvertibleToMap(derefT, true, tagName) { + return deref + } + return v +} diff --git a/vendor/github.com/go-viper/mapstructure/v2/reflect_go1_19.go b/vendor/github.com/go-viper/mapstructure/v2/reflect_go1_19.go new file mode 100644 index 000000000..d0913fff6 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/reflect_go1_19.go @@ -0,0 +1,44 @@ +//go:build !go1.20 + +package mapstructure + +import "reflect" + +func isComparable(v reflect.Value) bool { + k := v.Kind() + switch k { + case reflect.Invalid: + return false + + case reflect.Array: + switch v.Type().Elem().Kind() { + case reflect.Interface, reflect.Array, reflect.Struct: + for i := 0; i < v.Type().Len(); i++ { + // if !v.Index(i).Comparable() { + if !isComparable(v.Index(i)) { + return false + } + } + return true + } + return v.Type().Comparable() + + case reflect.Interface: + // return v.Elem().Comparable() + return isComparable(v.Elem()) + + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + return false + + // if !v.Field(i).Comparable() { + if !isComparable(v.Field(i)) { + return false + } + } + return true + + default: + return v.Type().Comparable() + } +} diff --git a/vendor/github.com/go-viper/mapstructure/v2/reflect_go1_20.go b/vendor/github.com/go-viper/mapstructure/v2/reflect_go1_20.go new file mode 100644 index 000000000..f8255a1b1 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/reflect_go1_20.go @@ -0,0 +1,10 @@ +//go:build go1.20 + +package mapstructure + +import "reflect" + +// TODO: remove once we drop support for Go <1.20 +func isComparable(v reflect.Value) bool { + return v.Comparable() +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/commands/config.go b/vendor/github.com/golangci/golangci-lint/pkg/commands/config.go index a16ef6310..45c4fcd77 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/commands/config.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/config.go @@ -5,8 +5,10 @@ import ( "os" "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/spf13/viper" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/exitcodes" "github.com/golangci/golangci-lint/pkg/fsutils" ) @@ -14,7 +16,7 @@ import ( func (e *Executor) initConfig() { cmd := &cobra.Command{ Use: "config", - Short: "Config", + Short: "Config file information", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, _ []string) error { return cmd.Help() @@ -29,7 +31,12 @@ func (e *Executor) initConfig() { ValidArgsFunction: cobra.NoFileCompletions, Run: e.executePathCmd, } - e.initRunConfiguration(pathCmd) // allow --config + + fs := pathCmd.Flags() + fs.SortFlags = false // sort them as they are defined here + + initConfigFileFlagSet(fs, &e.cfg.Run) + cmd.AddCommand(pathCmd) } @@ -59,3 +66,8 @@ func (e *Executor) executePathCmd(_ *cobra.Command, _ []string) { fmt.Println(usedConfigFile) } + +func initConfigFileFlagSet(fs *pflag.FlagSet, cfg *config.Run) { + fs.StringVarP(&cfg.Config, "config", "c", "", wh("Read config from file path `PATH`")) + fs.BoolVar(&cfg.NoConfig, "no-config", false, wh("Don't read config file")) +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/commands/executor.go b/vendor/github.com/golangci/golangci-lint/pkg/commands/executor.go index 61e221cb8..d241f5656 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/commands/executor.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/executor.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "crypto/sha256" + "errors" "fmt" "io" "os" @@ -78,7 +79,7 @@ func NewExecutor(buildInfo BuildInfo) *Executor { // to setup log level early we need to parse config from command line extra time to // find `-v` option commandLineCfg, err := e.getConfigForCommandLine() - if err != nil && err != pflag.ErrHelp { + if err != nil && !errors.Is(err, pflag.ErrHelp) { e.log.Fatalf("Can't get config for command line: %s", err) } if commandLineCfg != nil { diff --git a/vendor/github.com/golangci/golangci-lint/pkg/commands/linters.go b/vendor/github.com/golangci/golangci-lint/pkg/commands/linters.go index 292713ec9..69df21154 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/commands/linters.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/linters.go @@ -2,10 +2,13 @@ package commands import ( "fmt" + "strings" "github.com/fatih/color" "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/lint/linter" ) @@ -17,8 +20,25 @@ func (e *Executor) initLinters() { ValidArgsFunction: cobra.NoFileCompletions, RunE: e.executeLinters, } + + fs := e.lintersCmd.Flags() + fs.SortFlags = false // sort them as they are defined here + + initConfigFileFlagSet(fs, &e.cfg.Run) + e.initLintersFlagSet(fs, &e.cfg.Linters) + e.rootCmd.AddCommand(e.lintersCmd) - e.initRunConfiguration(e.lintersCmd) +} + +func (e *Executor) initLintersFlagSet(fs *pflag.FlagSet, cfg *config.Linters) { + fs.StringSliceVarP(&cfg.Disable, "disable", "D", nil, wh("Disable specific linter")) + fs.BoolVar(&cfg.DisableAll, "disable-all", false, wh("Disable all linters")) + fs.StringSliceVarP(&cfg.Enable, "enable", "E", nil, wh("Enable specific linter")) + fs.BoolVar(&cfg.EnableAll, "enable-all", false, wh("Enable all linters")) + fs.BoolVar(&cfg.Fast, "fast", false, wh("Enable only fast linters from enabled linters set (first run won't be fast)")) + fs.StringSliceVarP(&cfg.Presets, "presets", "p", nil, + wh(fmt.Sprintf("Enable presets (%s) of linters. Run 'golangci-lint help linters' to see "+ + "them. This option implies option --disable-all", strings.Join(e.DBManager.AllPresets(), "|")))) } // executeLinters runs the 'linters' CLI command, which displays the supported linters. @@ -28,18 +48,9 @@ func (e *Executor) executeLinters(_ *cobra.Command, _ []string) error { return fmt.Errorf("can't get enabled linters: %w", err) } - color.Green("Enabled by your configuration linters:\n") var enabledLinters []*linter.Config - for _, lc := range enabledLintersMap { - if lc.Internal { - continue - } - - enabledLinters = append(enabledLinters, lc) - } - printLinterConfigs(enabledLinters) - var disabledLCs []*linter.Config + for _, lc := range e.DBManager.GetAllSupportedLinterConfigs() { if lc.Internal { continue @@ -47,9 +58,13 @@ func (e *Executor) executeLinters(_ *cobra.Command, _ []string) error { if enabledLintersMap[lc.Name()] == nil { disabledLCs = append(disabledLCs, lc) + } else { + enabledLinters = append(enabledLinters, lc) } } + color.Green("Enabled by your configuration linters:\n") + printLinterConfigs(enabledLinters) color.Red("\nDisabled by your configuration linters:\n") printLinterConfigs(disabledLCs) diff --git a/vendor/github.com/golangci/golangci-lint/pkg/commands/root.go b/vendor/github.com/golangci/golangci-lint/pkg/commands/root.go index efe62ced2..4425be10f 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/commands/root.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/root.go @@ -119,7 +119,7 @@ func formatMemory(memBytes uint64) string { func getDefaultConcurrency() int { if os.Getenv(envHelpRun) == "1" { - // Make stable concurrency for README help generating builds. + // Make stable concurrency for generating help documentation. const prettyConcurrency = 8 return prettyConcurrency } @@ -165,7 +165,8 @@ func initRootFlagSet(fs *pflag.FlagSet, cfg *config.Config, needVersionOption bo fs.StringVar(&cfg.Run.CPUProfilePath, "cpu-profile-path", "", wh("Path to CPU profile output file")) fs.StringVar(&cfg.Run.MemProfilePath, "mem-profile-path", "", wh("Path to memory profile output file")) fs.StringVar(&cfg.Run.TracePath, "trace-path", "", wh("Path to trace output file")) - fs.IntVarP(&cfg.Run.Concurrency, "concurrency", "j", getDefaultConcurrency(), wh("Concurrency (default NumCPU)")) + fs.IntVarP(&cfg.Run.Concurrency, "concurrency", "j", getDefaultConcurrency(), + wh("Number of CPUs to use (Default: number of logical CPUs)")) if needVersionOption { fs.BoolVar(&cfg.Run.PrintVersion, "version", false, wh("Print version")) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/commands/run.go b/vendor/github.com/golangci/golangci-lint/pkg/commands/run.go index 9149b177b..5c7083c30 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/commands/run.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/run.go @@ -8,12 +8,14 @@ import ( "log" "os" "runtime" + "sort" "strings" "time" "github.com/fatih/color" "github.com/spf13/cobra" "github.com/spf13/pflag" + "golang.org/x/exp/maps" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/exitcodes" @@ -27,6 +29,8 @@ import ( const defaultFileMode = 0644 +const defaultTimeout = time.Minute + const ( // envFailOnWarnings value: "1" envFailOnWarnings = "FAIL_ON_WARNINGS" @@ -34,35 +38,8 @@ const ( envMemLogEvery = "GL_MEM_LOG_EVERY" ) -func getDefaultIssueExcludeHelp() string { - parts := []string{"Use or not use default excludes:"} - for _, ep := range config.DefaultExcludePatterns { - parts = append(parts, - fmt.Sprintf(" # %s %s: %s", ep.ID, ep.Linter, ep.Why), - fmt.Sprintf(" - %s", color.YellowString(ep.Pattern)), - "", - ) - } - return strings.Join(parts, "\n") -} - -func getDefaultDirectoryExcludeHelp() string { - parts := []string{"Use or not use default excluded directories:"} - for _, dir := range packages.StdExcludeDirRegexps { - parts = append(parts, fmt.Sprintf(" - %s", color.YellowString(dir))) - } - parts = append(parts, "") - return strings.Join(parts, "\n") -} - -func wh(text string) string { - return color.GreenString(text) -} - -const defaultTimeout = time.Minute - //nolint:funlen,gomnd -func initFlagSet(fs *pflag.FlagSet, cfg *config.Config, m *lintersdb.Manager, isFinalInit bool) { +func (e *Executor) initFlagSet(fs *pflag.FlagSet, cfg *config.Config, isFinalInit bool) { hideFlag := func(name string) { if err := fs.MarkHidden(name); err != nil { panic(err) @@ -77,6 +54,10 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config, m *lintersdb.Manager, is } } + // Config file config + rc := &cfg.Run + initConfigFileFlagSet(fs, rc) + // Output config oc := &cfg.Output fs.StringVar(&oc.Format, "out-format", @@ -96,9 +77,8 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config, m *lintersdb.Manager, is } // Run config - rc := &cfg.Run fs.StringVar(&rc.ModulesDownloadMode, "modules-download-mode", "", - "Modules download mode. If not empty, passed as -mod=<mode> to go tools") + wh("Modules download mode. If not empty, passed as -mod=<mode> to go tools")) fs.IntVar(&rc.ExitCodeIfIssuesFound, "issues-exit-code", exitcodes.IssuesFound, wh("Exit code when issues were found")) fs.StringVar(&rc.Go, "go", "", wh("Targeted Go version")) @@ -113,8 +93,6 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config, m *lintersdb.Manager, is fs.BoolVar(&rc.AnalyzeTests, "tests", true, wh("Analyze tests (*_test.go)")) fs.BoolVar(&rc.PrintResourcesUsage, "print-resources-usage", false, wh("Print avg and max memory usage of golangci-lint and total time")) - fs.StringVarP(&rc.Config, "config", "c", "", wh("Read config from file path `PATH`")) - fs.BoolVar(&rc.NoConfig, "no-config", false, wh("Don't read config")) fs.StringSliceVar(&rc.SkipDirs, "skip-dirs", nil, wh("Regexps of directories to skip")) fs.BoolVar(&rc.UseDefaultSkipDirs, "skip-dirs-use-default", true, getDefaultDirectoryExcludeHelp()) fs.StringSliceVar(&rc.SkipFiles, "skip-files", nil, wh("Regexps of files to skip")) @@ -122,9 +100,10 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config, m *lintersdb.Manager, is const allowParallelDesc = "Allow multiple parallel golangci-lint instances running. " + "If false (default) - golangci-lint acquires file lock on start." fs.BoolVar(&rc.AllowParallelRunners, "allow-parallel-runners", false, wh(allowParallelDesc)) - const allowSerialDesc = "Allow multiple golangci-lint instances running, but serialize them around a lock. " + + const allowSerialDesc = "Allow multiple golangci-lint instances running, but serialize them around a lock. " + "If false (default) - golangci-lint exits with an error if it fails to acquire file lock on start." fs.BoolVar(&rc.AllowSerialRunners, "allow-serial-runners", false, wh(allowSerialDesc)) + fs.BoolVar(&rc.ShowStats, "show-stats", false, wh("Show statistics per linter")) // Linters settings config lsc := &cfg.LintersSettings @@ -197,15 +176,7 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config, m *lintersdb.Manager, is // Linters config lc := &cfg.Linters - fs.StringSliceVarP(&lc.Enable, "enable", "E", nil, wh("Enable specific linter")) - fs.StringSliceVarP(&lc.Disable, "disable", "D", nil, wh("Disable specific linter")) - fs.BoolVar(&lc.EnableAll, "enable-all", false, wh("Enable all linters")) - - fs.BoolVar(&lc.DisableAll, "disable-all", false, wh("Disable all linters")) - fs.StringSliceVarP(&lc.Presets, "presets", "p", nil, - wh(fmt.Sprintf("Enable presets (%s) of linters. Run 'golangci-lint linters' to see "+ - "them. This option implies option --disable-all", strings.Join(m.AllPresets(), "|")))) - fs.BoolVar(&lc.Fast, "fast", false, wh("Run only fast linters from enabled linters set (first run won't be fast)")) + e.initLintersFlagSet(fs, lc) // Issues config ic := &cfg.Issues @@ -232,13 +203,13 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config, m *lintersdb.Manager, is wh("Show only new issues created in git patch with file path `PATH`")) fs.BoolVar(&ic.WholeFiles, "whole-files", false, wh("Show issues in any part of update files (requires new-from-rev or new-from-patch)")) - fs.BoolVar(&ic.NeedFix, "fix", false, "Fix found issues (if it's supported by the linter)") + fs.BoolVar(&ic.NeedFix, "fix", false, wh("Fix found issues (if it's supported by the linter)")) } func (e *Executor) initRunConfiguration(cmd *cobra.Command) { fs := cmd.Flags() fs.SortFlags = false // sort them as they are defined here - initFlagSet(fs, e.cfg, e.DBManager, true) + e.initFlagSet(fs, e.cfg, true) } func (e *Executor) getConfigForCommandLine() (*config.Config, error) { @@ -251,7 +222,7 @@ func (e *Executor) getConfigForCommandLine() (*config.Config, error) { // `changed` variable inside string slice vars will be shared. // Use another config variable here, not e.cfg, to not // affect main parsing by this parsing of only config option. - initFlagSet(fs, &cfg, e.DBManager, false) + e.initFlagSet(fs, &cfg, false) initVersionFlagSet(fs, &cfg) // Parse max options, even force version option: don't want @@ -261,11 +232,11 @@ func (e *Executor) getConfigForCommandLine() (*config.Config, error) { fs.Usage = func() {} // otherwise, help text will be printed twice if err := fs.Parse(os.Args); err != nil { - if err == pflag.ErrHelp { + if errors.Is(err, pflag.ErrHelp) { return nil, err } - return nil, fmt.Errorf("can't parse args: %s", err) + return nil, fmt.Errorf("can't parse args: %w", err) } return &cfg, nil @@ -408,6 +379,8 @@ func (e *Executor) runAndPrint(ctx context.Context, args []string) error { } } + e.printStats(issues) + e.setExitCodeIfIssuesFound(issues) e.fileCache.PrintStats(e.log) @@ -433,7 +406,7 @@ func (e *Executor) printReports(issues []result.Issue, path, format string) erro if file, ok := w.(io.Closer); shouldClose && ok { _ = file.Close() } - return fmt.Errorf("can't print %d issues: %s", len(issues), err) + return fmt.Errorf("can't print %d issues: %w", len(issues), err) } if file, ok := w.(io.Closer); shouldClose && ok { @@ -489,6 +462,31 @@ func (e *Executor) createPrinter(format string, w io.Writer) (printers.Printer, return p, nil } +func (e *Executor) printStats(issues []result.Issue) { + if !e.cfg.Run.ShowStats { + return + } + + if len(issues) == 0 { + e.runCmd.Println("0 issues.") + return + } + + stats := map[string]int{} + for idx := range issues { + stats[issues[idx].FromLinter]++ + } + + e.runCmd.Printf("%d issues:\n", len(issues)) + + keys := maps.Keys(stats) + sort.Strings(keys) + + for _, key := range keys { + e.runCmd.Printf("* %s: %d\n", key, stats[key]) + } +} + // executeRun executes the 'run' CLI command, which runs the linters. func (e *Executor) executeRun(_ *cobra.Command, args []string) { needTrackResources := e.cfg.Run.IsVerbose || e.cfg.Run.PrintResourcesUsage @@ -609,3 +607,28 @@ func watchResources(ctx context.Context, done chan struct{}, logger logutils.Log logger.Infof("Execution took %s", time.Since(startedAt)) close(done) } + +func getDefaultIssueExcludeHelp() string { + parts := []string{color.GreenString("Use or not use default excludes:")} + for _, ep := range config.DefaultExcludePatterns { + parts = append(parts, + fmt.Sprintf(" # %s %s: %s", ep.ID, ep.Linter, ep.Why), + fmt.Sprintf(" - %s", color.YellowString(ep.Pattern)), + "", + ) + } + return strings.Join(parts, "\n") +} + +func getDefaultDirectoryExcludeHelp() string { + parts := []string{color.GreenString("Use or not use default excluded directories:")} + for _, dir := range packages.StdExcludeDirRegexps { + parts = append(parts, fmt.Sprintf(" - %s", color.YellowString(dir))) + } + parts = append(parts, "") + return strings.Join(parts, "\n") +} + +func wh(text string) string { + return color.GreenString(text) +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/commands/version.go b/vendor/github.com/golangci/golangci-lint/pkg/commands/version.go index bb7732250..10ab7c1bb 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/commands/version.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/commands/version.go @@ -38,7 +38,7 @@ func (e *Executor) initVersion() { Short: "Version", Args: cobra.NoArgs, ValidArgsFunction: cobra.NoFileCompletions, - RunE: func(cmd *cobra.Command, _ []string) error { + RunE: func(_ *cobra.Command, _ []string) error { if e.cfg.Version.Debug { info, ok := debug.ReadBuildInfo() if !ok { diff --git a/vendor/github.com/golangci/golangci-lint/pkg/config/config.go b/vendor/github.com/golangci/golangci-lint/pkg/config/config.go index 7941f428f..a5483a20e 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/config/config.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/config/config.go @@ -41,13 +41,13 @@ type Version struct { Debug bool `mapstructure:"debug"` } -func IsGreaterThanOrEqualGo121(v string) bool { +func IsGreaterThanOrEqualGo122(v string) bool { v1, err := hcversion.NewVersion(strings.TrimPrefix(v, "go")) if err != nil { return false } - limit, err := hcversion.NewVersion("1.21") + limit, err := hcversion.NewVersion("1.22") if err != nil { return false } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/config/issues.go b/vendor/github.com/golangci/golangci-lint/pkg/config/issues.go index 5968d83d5..27dcf8105 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/config/issues.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/config/issues.go @@ -139,16 +139,16 @@ type BaseRule struct { func (b *BaseRule) Validate(minConditionsCount int) error { if err := validateOptionalRegex(b.Path); err != nil { - return fmt.Errorf("invalid path regex: %v", err) + return fmt.Errorf("invalid path regex: %w", err) } if err := validateOptionalRegex(b.PathExcept); err != nil { - return fmt.Errorf("invalid path-except regex: %v", err) + return fmt.Errorf("invalid path-except regex: %w", err) } if err := validateOptionalRegex(b.Text); err != nil { - return fmt.Errorf("invalid text regex: %v", err) + return fmt.Errorf("invalid text regex: %w", err) } if err := validateOptionalRegex(b.Source); err != nil { - return fmt.Errorf("invalid source regex: %v", err) + return fmt.Errorf("invalid source regex: %w", err) } nonBlank := 0 if len(b.Linters) > 0 { diff --git a/vendor/github.com/golangci/golangci-lint/pkg/config/linters_settings.go b/vendor/github.com/golangci/golangci-lint/pkg/config/linters_settings.go index 0fee9f81e..a3206f597 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/config/linters_settings.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/config/linters_settings.go @@ -74,6 +74,9 @@ var defaultLintersSettings = LintersSettings{ MaxDeclLines: 1, MaxDeclChars: 30, }, + Inamedparam: INamedParamSettings{ + SkipSingleParam: false, + }, InterfaceBloat: InterfaceBloatSettings{ Max: 10, }, @@ -104,6 +107,12 @@ var defaultLintersSettings = LintersSettings{ RequireSpecific: false, AllowUnused: false, }, + PerfSprint: PerfSprintSettings{ + IntConversion: true, + ErrError: false, + ErrorF: true, + SprintF1: true, + }, Prealloc: PreallocSettings{ Simple: true, RangeLoops: true, @@ -114,9 +123,13 @@ var defaultLintersSettings = LintersSettings{ Qualified: false, }, SlogLint: SlogLintSettings{ + NoMixedArgs: true, KVOnly: false, AttrOnly: false, + ContextOnly: false, + StaticMsg: false, NoRawKeys: false, + KeyNamingCase: "", ArgsOnSepLines: false, }, TagAlign: TagAlignSettings{ @@ -206,6 +219,7 @@ type LintersSettings struct { Grouper GrouperSettings Ifshort IfshortSettings ImportAs ImportAsSettings + Inamedparam INamedParamSettings InterfaceBloat InterfaceBloatSettings Ireturn IreturnSettings Lll LllSettings @@ -222,20 +236,23 @@ type LintersSettings struct { NoLintLint NoLintLintSettings NoNamedReturns NoNamedReturnsSettings ParallelTest ParallelTestSettings + PerfSprint PerfSprintSettings Prealloc PreallocSettings Predeclared PredeclaredSettings Promlinter PromlinterSettings + ProtoGetter ProtoGetterSettings Reassign ReassignSettings Revive ReviveSettings RowsErrCheck RowsErrCheckSettings SlogLint SlogLintSettings + Spancheck SpancheckSettings Staticcheck StaticCheckSettings Structcheck StructCheckSettings Stylecheck StaticCheckSettings TagAlign TagAlignSettings Tagliatelle TagliatelleSettings - Testifylint TestifylintSettings Tenv TenvSettings + Testifylint TestifylintSettings Testpackage TestpackageSettings Thelper ThelperSettings Unparam UnparamSettings @@ -279,9 +296,10 @@ type DepGuardSettings struct { } type DepGuardList struct { - Files []string `mapstructure:"files"` - Allow []string `mapstructure:"allow"` - Deny []DepGuardDeny `mapstructure:"deny"` + ListMode string `mapstructure:"list-mode"` + Files []string `mapstructure:"files"` + Allow []string `mapstructure:"allow"` + Deny []DepGuardDeny `mapstructure:"deny"` } type DepGuardDeny struct { @@ -345,6 +363,7 @@ type ExhaustiveSettings struct { PackageScopeOnly bool `mapstructure:"package-scope-only"` ExplicitExhaustiveMap bool `mapstructure:"explicit-exhaustive-map"` ExplicitExhaustiveSwitch bool `mapstructure:"explicit-exhaustive-switch"` + DefaultCaseRequired bool `mapstructure:"default-case-required"` } type ExhaustiveStructSettings struct { @@ -427,14 +446,15 @@ type GocognitSettings struct { } type GoConstSettings struct { - IgnoreTests bool `mapstructure:"ignore-tests"` - MatchWithConstants bool `mapstructure:"match-constant"` - MinStringLen int `mapstructure:"min-len"` - MinOccurrencesCount int `mapstructure:"min-occurrences"` - ParseNumbers bool `mapstructure:"numbers"` - NumberMin int `mapstructure:"min"` - NumberMax int `mapstructure:"max"` - IgnoreCalls bool `mapstructure:"ignore-calls"` + IgnoreStrings string `mapstructure:"ignore-strings"` + IgnoreTests bool `mapstructure:"ignore-tests"` + MatchWithConstants bool `mapstructure:"match-constant"` + MinStringLen int `mapstructure:"min-len"` + MinOccurrencesCount int `mapstructure:"min-occurrences"` + ParseNumbers bool `mapstructure:"numbers"` + NumberMin int `mapstructure:"min"` + NumberMax int `mapstructure:"max"` + IgnoreCalls bool `mapstructure:"ignore-calls"` } type GoCriticSettings struct { @@ -599,6 +619,10 @@ type ImportAsAlias struct { Alias string } +type INamedParamSettings struct { + SkipSingleParam bool `mapstructure:"skip-single-param"` +} + type InterfaceBloatSettings struct { Max int `mapstructure:"max"` } @@ -636,8 +660,9 @@ type MalignedSettings struct { } type MisspellSettings struct { + Mode string `mapstructure:"mode"` Locale string - // TODO(ldez): v2 the options must be renamed to `IgnoredRules`. + // TODO(ldez): v2 the option must be renamed to `IgnoredRules`. IgnoreWords []string `mapstructure:"ignore-words"` } @@ -675,11 +700,19 @@ type NoLintLintSettings struct { type NoNamedReturnsSettings struct { ReportErrorInDefer bool `mapstructure:"report-error-in-defer"` } + type ParallelTestSettings struct { IgnoreMissing bool `mapstructure:"ignore-missing"` IgnoreMissingSubtests bool `mapstructure:"ignore-missing-subtests"` } +type PerfSprintSettings struct { + IntConversion bool `mapstructure:"int-conversion"` + ErrError bool `mapstructure:"err-error"` + ErrorF bool `mapstructure:"errorf"` + SprintF1 bool `mapstructure:"sprintf1"` +} + type PreallocSettings struct { Simple bool RangeLoops bool `mapstructure:"range-loops"` @@ -696,6 +729,13 @@ type PromlinterSettings struct { DisabledLinters []string `mapstructure:"disabled-linters"` } +type ProtoGetterSettings struct { + SkipGeneratedBy []string `mapstructure:"skip-generated-by"` + SkipFiles []string `mapstructure:"skip-files"` + SkipAnyGenerated bool `mapstructure:"skip-any-generated"` + ReplaceFirstArgInAppend bool `mapstructure:"replace-first-arg-in-append"` +} + type ReassignSettings struct { Patterns []string `mapstructure:"patterns"` } @@ -725,10 +765,19 @@ type RowsErrCheckSettings struct { } type SlogLintSettings struct { - KVOnly bool `mapstructure:"kv-only"` - AttrOnly bool `mapstructure:"attr-only"` - NoRawKeys bool `mapstructure:"no-raw-keys"` - ArgsOnSepLines bool `mapstructure:"args-on-sep-lines"` + NoMixedArgs bool `mapstructure:"no-mixed-args"` + KVOnly bool `mapstructure:"kv-only"` + AttrOnly bool `mapstructure:"attr-only"` + ContextOnly bool `mapstructure:"context-only"` + StaticMsg bool `mapstructure:"static-msg"` + NoRawKeys bool `mapstructure:"no-raw-keys"` + KeyNamingCase string `mapstructure:"key-naming-case"` + ArgsOnSepLines bool `mapstructure:"args-on-sep-lines"` +} + +type SpancheckSettings struct { + Checks []string `mapstructure:"checks"` + IgnoreCheckSignatures []string `mapstructure:"ignore-check-signatures"` } type StaticCheckSettings struct { @@ -764,13 +813,19 @@ type TagliatelleSettings struct { } type TestifylintSettings struct { - EnableAll bool `mapstructure:"enable-all"` - EnabledCheckers []string `mapstructure:"enable"` + EnableAll bool `mapstructure:"enable-all"` + DisableAll bool `mapstructure:"disable-all"` + EnabledCheckers []string `mapstructure:"enable"` + DisabledCheckers []string `mapstructure:"disable"` ExpectedActual struct { ExpVarPattern string `mapstructure:"pattern"` } `mapstructure:"expected-actual"` + RequireError struct { + FnPattern string `mapstructure:"fn-pattern"` + } `mapstructure:"require-error"` + SuiteExtraAssertCall struct { Mode string `mapstructure:"mode"` } `mapstructure:"suite-extra-assert-call"` diff --git a/vendor/github.com/golangci/golangci-lint/pkg/config/reader.go b/vendor/github.com/golangci/golangci-lint/pkg/config/reader.go index de203876e..0c4fa13b0 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/config/reader.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/config/reader.go @@ -5,12 +5,12 @@ import ( "fmt" "os" "path/filepath" + "slices" "strings" + "github.com/go-viper/mapstructure/v2" "github.com/mitchellh/go-homedir" - "github.com/mitchellh/mapstructure" "github.com/spf13/viper" - "golang.org/x/exp/slices" "github.com/golangci/golangci-lint/pkg/exitcodes" "github.com/golangci/golangci-lint/pkg/fsutils" @@ -38,11 +38,11 @@ func (r *FileReader) Read() error { configFile, err := r.parseConfigOption() if err != nil { - if err == errConfigDisabled { + if errors.Is(err, errConfigDisabled) { return nil } - return fmt.Errorf("can't parse --config option: %s", err) + return fmt.Errorf("can't parse --config option: %w", err) } if configFile != "" { @@ -65,7 +65,7 @@ func (r *FileReader) parseConfig() error { return nil } - return fmt.Errorf("can't read viper config: %s", err) + return fmt.Errorf("can't read viper config: %w", err) } usedConfigFile := viper.ConfigFileUsed() @@ -100,11 +100,11 @@ func (r *FileReader) parseConfig() error { // Needed for forbidigo. mapstructure.TextUnmarshallerHookFunc(), ))); err != nil { - return fmt.Errorf("can't unmarshal config by viper: %s", err) + return fmt.Errorf("can't unmarshal config by viper: %w", err) } if err := r.validateConfig(); err != nil { - return fmt.Errorf("can't validate config: %s", err) + return fmt.Errorf("can't validate config: %w", err) } if r.cfg.InternalTest { // just for testing purposes: to detect config file usage @@ -138,7 +138,7 @@ func (r *FileReader) validateConfig() error { } for i, rule := range c.Issues.ExcludeRules { if err := rule.Validate(); err != nil { - return fmt.Errorf("error in exclude rule #%d: %v", i, err) + return fmt.Errorf("error in exclude rule #%d: %w", i, err) } } if len(c.Severity.Rules) > 0 && c.Severity.Default == "" { @@ -146,11 +146,11 @@ func (r *FileReader) validateConfig() error { } for i, rule := range c.Severity.Rules { if err := rule.Validate(); err != nil { - return fmt.Errorf("error in severity rule #%d: %v", i, err) + return fmt.Errorf("error in severity rule #%d: %w", i, err) } } if err := c.LintersSettings.Govet.Validate(); err != nil { - return fmt.Errorf("error in govet config: %v", err) + return fmt.Errorf("error in govet config: %w", err) } return nil } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/config/run.go b/vendor/github.com/golangci/golangci-lint/pkg/config/run.go index ff812d0a2..2bb21d9fa 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/config/run.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/config/run.go @@ -37,4 +37,6 @@ type Run struct { AllowParallelRunners bool `mapstructure:"allow-parallel-runners"` AllowSerialRunners bool `mapstructure:"allow-serial-runners"` + + ShowStats bool `mapstructure:"show-stats"` } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/fsutils/fsutils.go b/vendor/github.com/golangci/golangci-lint/pkg/fsutils/fsutils.go index a39c105e4..715a3a4af 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/fsutils/fsutils.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/fsutils/fsutils.go @@ -34,7 +34,7 @@ func Getwd() (string, error) { evaledWd, err := EvalSymlinks(cachedWd) if err != nil { - cachedWd, cachedWdError = "", fmt.Errorf("can't eval symlinks on wd %s: %s", cachedWd, err) + cachedWd, cachedWdError = "", fmt.Errorf("can't eval symlinks on wd %s: %w", cachedWd, err) return } @@ -70,13 +70,13 @@ func ShortestRelPath(path, wd string) (string, error) { var err error wd, err = Getwd() if err != nil { - return "", fmt.Errorf("can't get working directory: %s", err) + return "", fmt.Errorf("can't get working directory: %w", err) } } evaledPath, err := EvalSymlinks(path) if err != nil { - return "", fmt.Errorf("can't eval symlinks for path %s: %s", path, err) + return "", fmt.Errorf("can't eval symlinks for path %s: %w", path, err) } path = evaledPath @@ -92,7 +92,7 @@ func ShortestRelPath(path, wd string) (string, error) { relPath, err := filepath.Rel(wd, absPath) if err != nil { - return "", fmt.Errorf("can't get relative path for path %s and root %s: %s", + return "", fmt.Errorf("can't get relative path for path %s and root %s: %w", absPath, wd, err) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/asciicheck.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/asciicheck.go index df301b417..7e7ee05e5 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/asciicheck.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/asciicheck.go @@ -8,10 +8,12 @@ import ( ) func NewAsciicheck() *goanalysis.Linter { + a := asciicheck.NewAnalyzer() + return goanalysis.NewLinter( - "asciicheck", - "Simple linter to check that your code does not contain non-ASCII identifiers", - []*analysis.Analyzer{asciicheck.NewAnalyzer()}, + a.Name, + a.Doc, + []*analysis.Analyzer{a}, nil, ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/bidichk.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/bidichk.go index e1b347176..ef266f55c 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/bidichk.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/bidichk.go @@ -51,7 +51,7 @@ func NewBiDiChkFuncName(cfg *config.BiDiChkSettings) *goanalysis.Linter { } return goanalysis.NewLinter( - "bidichk", + a.Name, "Checks for dangerous unicode character sequences", []*analysis.Analyzer{a}, cfgMap, diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/cyclop.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/cyclop.go index 5ad65f122..f76c55552 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/cyclop.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/cyclop.go @@ -8,8 +8,6 @@ import ( "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" ) -const cyclopName = "cyclop" - func NewCyclop(settings *config.Cyclop) *goanalysis.Linter { a := analyzer.NewAnalyzer() @@ -31,7 +29,7 @@ func NewCyclop(settings *config.Cyclop) *goanalysis.Linter { } return goanalysis.NewLinter( - cyclopName, + a.Name, "checks function and package cyclomatic complexity", []*analysis.Analyzer{a}, cfg, diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/depguard.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/depguard.go index 23986708c..49e471df8 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/depguard.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/depguard.go @@ -15,8 +15,9 @@ func NewDepguard(settings *config.DepGuardSettings) *goanalysis.Linter { if settings != nil { for s, rule := range settings.Rules { list := &depguard.List{ - Files: rule.Files, - Allow: rule.Allow, + ListMode: rule.ListMode, + Files: rule.Files, + Allow: rule.Allow, } // because of bug with Viper parsing (split on dot) we use a list of struct instead of a map. diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/errchkjson.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/errchkjson.go index 171de00a4..1af4450b4 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/errchkjson.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/errchkjson.go @@ -23,10 +23,8 @@ func NewErrChkJSONFuncName(cfg *config.ErrChkJSONSettings) *goanalysis.Linter { } return goanalysis.NewLinter( - "errchkjson", - "Checks types passed to the json encoding functions. "+ - "Reports unsupported types and optionally reports occasions, "+ - "where the check for the returned error can be omitted.", + a.Name, + a.Doc, []*analysis.Analyzer{a}, cfgMap, ).WithLoadMode(goanalysis.LoadModeTypesInfo) diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/errname.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/errname.go index 96564cfa8..193a7aba7 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/errname.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/errname.go @@ -8,10 +8,12 @@ import ( ) func NewErrName() *goanalysis.Linter { + a := analyzer.New() + return goanalysis.NewLinter( - "errname", - "Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`.", - []*analysis.Analyzer{analyzer.New()}, + a.Name, + a.Doc, + []*analysis.Analyzer{a}, nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/exhaustive.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/exhaustive.go index 3824afa0b..fe58e10f0 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/exhaustive.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/exhaustive.go @@ -23,6 +23,7 @@ func NewExhaustive(settings *config.ExhaustiveSettings) *goanalysis.Linter { exhaustive.PackageScopeOnlyFlag: settings.PackageScopeOnly, exhaustive.ExplicitExhaustiveMapFlag: settings.ExplicitExhaustiveMap, exhaustive.ExplicitExhaustiveSwitchFlag: settings.ExplicitExhaustiveSwitch, + exhaustive.DefaultCaseRequiredFlag: settings.DefaultCaseRequired, }, } } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/runner.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/runner.go index 46871bc5b..a8660b612 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/runner.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/runner.go @@ -17,6 +17,7 @@ import ( "sort" "sync" + "golang.org/x/exp/maps" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/packages" @@ -159,10 +160,7 @@ func (r *runner) buildActionFactDeps(act *action, a *analysis.Analyzer, pkg *pac act.objectFacts = make(map[objectFactKey]analysis.Fact) act.packageFacts = make(map[packageFactKey]analysis.Fact) - paths := make([]string, 0, len(pkg.Imports)) - for path := range pkg.Imports { - paths = append(paths, path) - } + paths := maps.Keys(pkg.Imports) sort.Strings(paths) // for determinism for _, path := range paths { dep := r.makeAction(a, pkg.Imports[path], initialPkgs, actions, actAlloc) @@ -212,10 +210,7 @@ func (r *runner) prepareAnalysis(pkgs []*packages.Package, } } - allActions := make([]*action, 0, len(actions)) - for _, act := range actions { - allActions = append(allActions, act) - } + allActions := maps.Values(actions) debugf("Built %d actions", len(actions)) diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/runner_action.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/runner_action.go index 5ded9fac9..6b57cb0c9 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/runner_action.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goanalysis/runner_action.go @@ -9,7 +9,6 @@ import ( "runtime/debug" "time" - "github.com/hashicorp/go-multierror" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/packages" "golang.org/x/tools/go/types/objectpath" @@ -126,20 +125,16 @@ func (act *action) analyze() { }(time.Now()) // Report an error if any dependency failures. - var depErrors *multierror.Error + var depErrors error for _, dep := range act.deps { if dep.err == nil { continue } - depErrors = multierror.Append(depErrors, errors.Unwrap(dep.err)) + depErrors = errors.Join(depErrors, errors.Unwrap(dep.err)) } if depErrors != nil { - depErrors.ErrorFormat = func(e []error) string { - return fmt.Sprintf("failed prerequisites: %v", e) - } - - act.err = depErrors + act.err = fmt.Errorf("failed prerequisites: %w", depErrors) return } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/gochecksumtype.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/gochecksumtype.go index fcc0cad58..5516179df 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/gochecksumtype.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/gochecksumtype.go @@ -45,7 +45,7 @@ func NewGoCheckSumType() *goanalysis.Linter { `Run exhaustiveness checks on Go "sum types"`, []*analysis.Analyzer{analyzer}, nil, - ).WithIssuesReporter(func(ctx *linter.Context) []goanalysis.Issue { + ).WithIssuesReporter(func(_ *linter.Context) []goanalysis.Issue { return resIssues }).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goconst.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goconst.go index e277509d2..b51885275 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goconst.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goconst.go @@ -53,6 +53,7 @@ func NewGoconst(settings *config.GoConstSettings) *goanalysis.Linter { func runGoconst(pass *analysis.Pass, settings *config.GoConstSettings) ([]goanalysis.Issue, error) { cfg := goconstAPI.Config{ + IgnoreStrings: settings.IgnoreStrings, IgnoreTests: settings.IgnoreTests, MatchWithConstants: settings.MatchWithConstants, MinStringLength: settings.MinStringLen, diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/gocritic.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/gocritic.go index 1319c72d9..3cf43afc6 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/gocritic.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/gocritic.go @@ -14,6 +14,7 @@ import ( "github.com/go-critic/go-critic/checkers" gocriticlinter "github.com/go-critic/go-critic/linter" + "golang.org/x/exp/maps" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" @@ -219,10 +220,7 @@ func (w *goCriticWrapper) configureCheckerInfo(info *gocriticlinter.CheckerInfo, info.Name, k) } - var supportedKeys []string - for sk := range info.Params { - supportedKeys = append(supportedKeys, sk) - } + supportedKeys := maps.Keys(info.Params) sort.Strings(supportedKeys) return fmt.Errorf("checker %s config param %s doesn't exist, all existing: %s", @@ -311,11 +309,7 @@ func (s *goCriticSettingsWrapper) checkerTagsDebugf() { tagToCheckers := s.buildTagToCheckersMap() - allTags := make([]string, 0, len(tagToCheckers)) - for tag := range tagToCheckers { - allTags = append(allTags, tag) - } - + allTags := maps.Keys(tagToCheckers) sort.Strings(allTags) goCriticDebugf("All gocritic existing tags and checks:") @@ -609,12 +603,7 @@ func intersectStringSlice(s1, s2 []string) []string { } func sprintAllowedCheckerNames(allowedNames map[string]bool) string { - namesSlice := make([]string, 0, len(allowedNames)) - - for name := range allowedNames { - namesSlice = append(namesSlice, name) - } - + namesSlice := maps.Keys(allowedNames) return sprintStrings(namesSlice) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/golint.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/golint.go index a6fc73c9e..22ca59048 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/golint.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/golint.go @@ -56,7 +56,7 @@ func runGoLint(pass *analysis.Pass, settings *config.GoLintSettings) ([]goanalys ps, err := l.LintPkg(pass.Files, pass.Fset, pass.Pkg, pass.TypesInfo) if err != nil { - return nil, fmt.Errorf("can't lint %d files: %s", len(pass.Files), err) + return nil, fmt.Errorf("can't lint %d files: %w", len(pass.Files), err) } if len(ps) == 0 { diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goprintffuncname.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goprintffuncname.go index c5516dc7f..e513718ba 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/goprintffuncname.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/goprintffuncname.go @@ -8,10 +8,12 @@ import ( ) func NewGoPrintfFuncName() *goanalysis.Linter { + a := analyzer.Analyzer + return goanalysis.NewLinter( - "goprintffuncname", - "Checks that printf-like functions are named with `f` at the end", - []*analysis.Analyzer{analyzer.Analyzer}, + a.Name, + a.Doc, + []*analysis.Analyzer{a}, nil, ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/govet.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/govet.go index 4e16fb142..a0d33835d 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/govet.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/govet.go @@ -1,6 +1,8 @@ package golinters import ( + "slices" + "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/appends" "golang.org/x/tools/go/analysis/passes/asmdecl" @@ -170,36 +172,25 @@ func analyzersFromConfig(settings *config.GovetSettings) []*analysis.Analyzer { } func isAnalyzerEnabled(name string, cfg *config.GovetSettings, defaultAnalyzers []*analysis.Analyzer) bool { - if cfg.EnableAll { - for _, n := range cfg.Disable { - if n == name { - return false - } - } - return true + // TODO(ldez) remove loopclosure when go1.23 + if name == loopclosure.Analyzer.Name && config.IsGreaterThanOrEqualGo122(cfg.Go) { + return false } - // Raw for loops should be OK on small slice lengths. - for _, n := range cfg.Enable { - if n == name { - return true - } - } + switch { + case cfg.EnableAll: + return !slices.Contains(cfg.Disable, name) - for _, n := range cfg.Disable { - if n == name { - return false - } - } + case slices.Contains(cfg.Enable, name): + return true - if cfg.DisableAll { + case slices.Contains(cfg.Disable, name): return false - } - for _, a := range defaultAnalyzers { - if a.Name == name { - return true - } + case cfg.DisableAll: + return false + + default: + return slices.ContainsFunc(defaultAnalyzers, func(a *analysis.Analyzer) bool { return a.Name == name }) } - return false } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/grouper.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/grouper.go index 9feecf3ba..41761f2ae 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/grouper.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/grouper.go @@ -25,7 +25,7 @@ func NewGrouper(settings *config.GrouperSettings) *goanalysis.Linter { return goanalysis.NewLinter( "grouper", - "An analyzer to analyze expression groups.", + "Analyze expression groups.", []*analysis.Analyzer{grouper.New()}, linterCfg, ).WithLoadMode(goanalysis.LoadModeSyntax) diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/ifshort.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/ifshort.go index 1574eaf70..50e2c172e 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/ifshort.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/ifshort.go @@ -19,10 +19,12 @@ func NewIfshort(settings *config.IfshortSettings) *goanalysis.Linter { } } + a := analyzer.Analyzer + return goanalysis.NewLinter( - "ifshort", - "Checks that your code uses short syntax for if-statements whenever possible", - []*analysis.Analyzer{analyzer.Analyzer}, + a.Name, + a.Doc, + []*analysis.Analyzer{a}, cfg, ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/inamedparam.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/inamedparam.go index 290630755..887f3db2a 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/inamedparam.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/inamedparam.go @@ -4,16 +4,27 @@ import ( "github.com/macabu/inamedparam" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" ) -func NewINamedParam() *goanalysis.Linter { +func NewINamedParam(settings *config.INamedParamSettings) *goanalysis.Linter { a := inamedparam.Analyzer + var cfg map[string]map[string]any + + if settings != nil { + cfg = map[string]map[string]any{ + a.Name: { + "skip-single-param": settings.SkipSingleParam, + }, + } + } + return goanalysis.NewLinter( a.Name, a.Doc, []*analysis.Analyzer{a}, - nil, + cfg, ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/ineffassign.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/ineffassign.go index c87bb2fa5..ac5eb20ad 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/ineffassign.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/ineffassign.go @@ -8,10 +8,12 @@ import ( ) func NewIneffassign() *goanalysis.Linter { + a := ineffassign.Analyzer + return goanalysis.NewLinter( - "ineffassign", + a.Name, "Detects when assignments to existing variables are not used", - []*analysis.Analyzer{ineffassign.Analyzer}, + []*analysis.Analyzer{a}, nil, ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/ireturn.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/ireturn.go index 34dc09d26..dc09dad0e 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/ireturn.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/ireturn.go @@ -16,8 +16,9 @@ func NewIreturn(settings *config.IreturnSettings) *goanalysis.Linter { cfg := map[string]map[string]any{} if settings != nil { cfg[a.Name] = map[string]any{ - "allow": strings.Join(settings.Allow, ","), - "reject": strings.Join(settings.Reject, ","), + "allow": strings.Join(settings.Allow, ","), + "reject": strings.Join(settings.Reject, ","), + "nonolint": true, } } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/lll.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/lll.go index 9ed320120..61498087a 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/lll.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/lll.go @@ -2,6 +2,7 @@ package golinters import ( "bufio" + "errors" "fmt" "go/token" "os" @@ -82,7 +83,7 @@ func getLLLIssuesForFile(filename string, maxLineLen int, tabSpaces string) ([]r f, err := os.Open(filename) if err != nil { - return nil, fmt.Errorf("can't open file %s: %s", filename, err) + return nil, fmt.Errorf("can't open file %s: %w", filename, err) } defer f.Close() @@ -127,7 +128,7 @@ func getLLLIssuesForFile(filename string, maxLineLen int, tabSpaces string) ([]r } if err := scanner.Err(); err != nil { - if err == bufio.ErrTooLong && maxLineLen < bufio.MaxScanTokenSize { + if errors.Is(err, bufio.ErrTooLong) && maxLineLen < bufio.MaxScanTokenSize { // scanner.Scan() might fail if the line is longer than bufio.MaxScanTokenSize // In the case where the specified maxLineLen is smaller than bufio.MaxScanTokenSize // we can return this line as a long line instead of returning an error. @@ -148,7 +149,7 @@ func getLLLIssuesForFile(filename string, maxLineLen int, tabSpaces string) ([]r FromLinter: lllName, }) } else { - return nil, fmt.Errorf("can't scan file %s: %s", filename, err) + return nil, fmt.Errorf("can't scan file %s: %w", filename, err) } } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/mirror.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/mirror.go index 4adc001a1..d6e2bb06a 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/mirror.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/mirror.go @@ -39,7 +39,7 @@ func NewMirror() *goanalysis.Linter { Pos: i.Start, } - if len(i.InlineFix) > 0 { + if i.InlineFix != "" { issue.Replacement = &result.Replacement{ Inline: &result.InlineFix{ StartCol: i.Start.Column - 1, diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/misspell.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/misspell.go index ce2b79a7c..0f69cdb87 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/misspell.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/misspell.go @@ -29,7 +29,7 @@ func NewMisspell(settings *config.MisspellSettings) *goanalysis.Linter { return goanalysis.NewLinter( misspellName, - "Finds commonly misspelled English words in comments", + "Finds commonly misspelled English words", []*analysis.Analyzer{analyzer}, nil, ).WithContextSetter(func(lintCtx *linter.Context) { @@ -40,7 +40,7 @@ func NewMisspell(settings *config.MisspellSettings) *goanalysis.Linter { return nil, ruleErr } - issues, err := runMisspell(lintCtx, pass, replacer) + issues, err := runMisspell(lintCtx, pass, replacer, settings.Mode) if err != nil { return nil, err } @@ -60,15 +60,16 @@ func NewMisspell(settings *config.MisspellSettings) *goanalysis.Linter { }).WithLoadMode(goanalysis.LoadModeSyntax) } -func runMisspell(lintCtx *linter.Context, pass *analysis.Pass, replacer *misspell.Replacer) ([]goanalysis.Issue, error) { +func runMisspell(lintCtx *linter.Context, pass *analysis.Pass, replacer *misspell.Replacer, mode string) ([]goanalysis.Issue, error) { fileNames := getFileNames(pass) var issues []goanalysis.Issue for _, filename := range fileNames { - lintIssues, err := runMisspellOnFile(lintCtx, filename, replacer) + lintIssues, err := runMisspellOnFile(lintCtx, filename, replacer, mode) if err != nil { return nil, err } + for i := range lintIssues { issues = append(issues, goanalysis.NewIssue(&lintIssues[i], pass)) } @@ -104,25 +105,36 @@ func createMisspellReplacer(settings *config.MisspellSettings) (*misspell.Replac return replacer, nil } -func runMisspellOnFile(lintCtx *linter.Context, filename string, replacer *misspell.Replacer) ([]result.Issue, error) { - var res []result.Issue +func runMisspellOnFile(lintCtx *linter.Context, filename string, replacer *misspell.Replacer, mode string) ([]result.Issue, error) { fileContent, err := lintCtx.FileCache.GetFileBytes(filename) if err != nil { - return nil, fmt.Errorf("can't get file %s contents: %s", filename, err) + return nil, fmt.Errorf("can't get file %s contents: %w", filename, err) } - // use r.Replace, not r.ReplaceGo because r.ReplaceGo doesn't find - // issues inside strings: it searches only inside comments. r.Replace - // searches all words: it treats input as a plain text. A standalone misspell - // tool uses r.Replace by default. - _, diffs := replacer.Replace(string(fileContent)) + // `r.ReplaceGo` doesn't find issues inside strings: it searches only inside comments. + // `r.Replace` searches all words: it treats input as a plain text. + // The standalone misspell tool uses `r.Replace` by default. + var replace func(input string) (string, []misspell.Diff) + switch strings.ToLower(mode) { + case "restricted": + replace = replacer.ReplaceGo + default: + replace = replacer.Replace + } + + _, diffs := replace(string(fileContent)) + + var res []result.Issue + for _, diff := range diffs { text := fmt.Sprintf("`%s` is a misspelling of `%s`", diff.Original, diff.Corrected) + pos := token.Position{ Filename: filename, Line: diff.Line, Column: diff.Column + 1, } + replacement := &result.Replacement{ Inline: &result.InlineFix{ StartCol: diff.Column, diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/musttag.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/musttag.go index d9ea7efc7..72d919582 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/musttag.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/musttag.go @@ -1,7 +1,7 @@ package golinters import ( - "go.tmz.dev/musttag" + "go-simpler.org/musttag" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/nakedret.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/nakedret.go index d276ac6a9..6153860fb 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/nakedret.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/nakedret.go @@ -8,20 +8,18 @@ import ( "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" ) -const nakedretName = "nakedret" - func NewNakedret(settings *config.NakedretSettings) *goanalysis.Linter { var maxLines int if settings != nil { maxLines = settings.MaxFuncLines } - analyzer := nakedret.NakedReturnAnalyzer(uint(maxLines)) + a := nakedret.NakedReturnAnalyzer(uint(maxLines)) return goanalysis.NewLinter( - nakedretName, - "Finds naked returns in functions greater than a specified function length", - []*analysis.Analyzer{analyzer}, + a.Name, + a.Doc, + []*analysis.Analyzer{a}, nil, ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/noctx.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/noctx.go index b7fcd2a73..cff9c97dc 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/noctx.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/noctx.go @@ -8,10 +8,12 @@ import ( ) func NewNoctx() *goanalysis.Linter { + a := noctx.Analyzer + return goanalysis.NewLinter( - "noctx", - "noctx finds sending http request without context.Context", - []*analysis.Analyzer{noctx.Analyzer}, + a.Name, + "Finds sending http request without context.Context", + []*analysis.Analyzer{a}, nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/nolintlint.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/nolintlint.go index 00ef1f833..ae372ab79 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/nolintlint.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/nolintlint.go @@ -76,7 +76,7 @@ func runNoLintLint(pass *analysis.Pass, settings *config.NoLintLintSettings) ([] lintIssues, err := lnt.Run(pass.Fset, nodes...) if err != nil { - return nil, fmt.Errorf("linter failed to run: %s", err) + return nil, fmt.Errorf("linter failed to run: %w", err) } var issues []goanalysis.Issue diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/nolintlint/nolintlint.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/nolintlint/nolintlint.go index 9c6b10f38..a245561a0 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/nolintlint/nolintlint.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/nolintlint/nolintlint.go @@ -19,12 +19,12 @@ type BaseIssue struct { replacement *result.Replacement } -//nolint:gocritic // TODO must be change in the future. +//nolint:gocritic // TODO(ldez) must be change in the future. func (b BaseIssue) Position() token.Position { return b.position } -//nolint:gocritic // TODO must be change in the future. +//nolint:gocritic // TODO(ldez) must be change in the future. func (b BaseIssue) Replacement() *result.Replacement { return b.replacement } @@ -33,53 +33,49 @@ type ExtraLeadingSpace struct { BaseIssue } -//nolint:gocritic // TODO must be change in the future. +//nolint:gocritic // TODO(ldez) must be change in the future. func (i ExtraLeadingSpace) Details() string { return fmt.Sprintf("directive `%s` should not have more than one leading space", i.fullDirective) } -//nolint:gocritic // TODO must be change in the future. func (i ExtraLeadingSpace) String() string { return toString(i) } type NotMachine struct { BaseIssue } -//nolint:gocritic // TODO must be change in the future. +//nolint:gocritic // TODO(ldez) must be change in the future. func (i NotMachine) Details() string { expected := i.fullDirective[:2] + strings.TrimLeftFunc(i.fullDirective[2:], unicode.IsSpace) return fmt.Sprintf("directive `%s` should be written without leading space as `%s`", i.fullDirective, expected) } -//nolint:gocritic // TODO must be change in the future. func (i NotMachine) String() string { return toString(i) } type NotSpecific struct { BaseIssue } -//nolint:gocritic // TODO must be change in the future. +//nolint:gocritic // TODO(ldez) must be change in the future. func (i NotSpecific) Details() string { return fmt.Sprintf("directive `%s` should mention specific linter such as `%s:my-linter`", i.fullDirective, i.directiveWithOptionalLeadingSpace) } -//nolint:gocritic // TODO must be change in the future. func (i NotSpecific) String() string { return toString(i) } type ParseError struct { BaseIssue } -//nolint:gocritic // TODO must be change in the future. +//nolint:gocritic // TODO(ldez) must be change in the future. func (i ParseError) Details() string { return fmt.Sprintf("directive `%s` should match `%s[:<comma-separated-linters>] [// <explanation>]`", i.fullDirective, i.directiveWithOptionalLeadingSpace) } -//nolint:gocritic // TODO must be change in the future. func (i ParseError) String() string { return toString(i) } type NoExplanation struct { @@ -87,13 +83,12 @@ type NoExplanation struct { fullDirectiveWithoutExplanation string } -//nolint:gocritic // TODO must be change in the future. +//nolint:gocritic // TODO(ldez) must be change in the future. func (i NoExplanation) Details() string { return fmt.Sprintf("directive `%s` should provide explanation such as `%s // this is why`", i.fullDirective, i.fullDirectiveWithoutExplanation) } -//nolint:gocritic // TODO must be change in the future. func (i NoExplanation) String() string { return toString(i) } type UnusedCandidate struct { @@ -101,7 +96,7 @@ type UnusedCandidate struct { ExpectedLinter string } -//nolint:gocritic // TODO must be change in the future. +//nolint:gocritic // TODO(ldez) must be change in the future. func (i UnusedCandidate) Details() string { details := fmt.Sprintf("directive `%s` is unused", i.fullDirective) if i.ExpectedLinter != "" { @@ -110,7 +105,6 @@ func (i UnusedCandidate) Details() string { return details } -//nolint:gocritic // TODO must be change in the future. func (i UnusedCandidate) String() string { return toString(i) } func toString(i Issue) string { @@ -185,7 +179,7 @@ func (l Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) { } directiveWithOptionalLeadingSpace := "//" - if len(leadingSpace) > 0 { + if leadingSpace != "" { directiveWithOptionalLeadingSpace += " " } @@ -202,7 +196,7 @@ func (l Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) { } // check for, report and eliminate leading spaces, so we can check for other issues - if len(leadingSpace) > 0 { + if leadingSpace != "" { removeWhitespace := &result.Replacement{ Inline: &result.InlineFix{ StartCol: pos.Column + 1, @@ -231,7 +225,7 @@ func (l Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) { lintersText, explanation := fullMatches[1], fullMatches[2] var linters []string - if len(lintersText) > 0 && !strings.HasPrefix(lintersText, "all") { + if lintersText != "" && !strings.HasPrefix(lintersText, "all") { lls := strings.Split(lintersText, ",") linters = make([]string, 0, len(lls)) rangeStart := (pos.Column - 1) + len("//") + len(leadingSpace) + len("nolint:") diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/paralleltest.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/paralleltest.go index 4c03952c1..c49f74aa2 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/paralleltest.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/paralleltest.go @@ -22,8 +22,8 @@ func NewParallelTest(settings *config.ParallelTestSettings) *goanalysis.Linter { } return goanalysis.NewLinter( - "paralleltest", - "paralleltest detects missing usage of t.Parallel() method in your Go test", + a.Name, + "Detects missing usage of t.Parallel() method in your Go test", []*analysis.Analyzer{a}, cfg, ).WithLoadMode(goanalysis.LoadModeTypesInfo) diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/perfsprint.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/perfsprint.go index fb248a85d..acaa3a522 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/perfsprint.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/perfsprint.go @@ -4,16 +4,28 @@ import ( "github.com/catenacyber/perfsprint/analyzer" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" ) -func NewPerfSprint() *goanalysis.Linter { - a := analyzer.Analyzer +func NewPerfSprint(settings *config.PerfSprintSettings) *goanalysis.Linter { + a := analyzer.New() + + cfg := map[string]map[string]any{ + a.Name: {"fiximports": false}, + } + + if settings != nil { + cfg[a.Name]["int-conversion"] = settings.IntConversion + cfg[a.Name]["err-error"] = settings.ErrError + cfg[a.Name]["errorf"] = settings.ErrorF + cfg[a.Name]["sprintf1"] = settings.SprintF1 + } return goanalysis.NewLinter( a.Name, a.Doc, []*analysis.Analyzer{a}, - nil, + cfg, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/protogetter.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/protogetter.go index 23325ad55..9a5e7b4db 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/protogetter.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/protogetter.go @@ -6,18 +6,33 @@ import ( "github.com/ghostiam/protogetter" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/result" ) -func NewProtoGetter() *goanalysis.Linter { +func NewProtoGetter(settings *config.ProtoGetterSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue - a := protogetter.NewAnalyzer() + var cfg protogetter.Config + if settings != nil { + cfg = protogetter.Config{ + SkipGeneratedBy: settings.SkipGeneratedBy, + SkipFiles: settings.SkipFiles, + SkipAnyGenerated: settings.SkipAnyGenerated, + ReplaceFirstArgInAppend: settings.ReplaceFirstArgInAppend, + } + } + cfg.Mode = protogetter.GolangciLintMode + + a := protogetter.NewAnalyzer(&cfg) a.Run = func(pass *analysis.Pass) (any, error) { - pgIssues := protogetter.Run(pass, protogetter.GolangciLintMode) + pgIssues, err := protogetter.Run(pass, &cfg) + if err != nil { + return nil, err + } issues := make([]goanalysis.Issue, len(pgIssues)) for i, issue := range pgIssues { diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/revive.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/revive.go index 28231957c..46d8b1c9c 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/revive.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/revive.go @@ -34,7 +34,7 @@ type jsonObject struct { // NewRevive returns a new Revive linter. // -//nolint:dupl + func NewRevive(settings *config.ReviveSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue @@ -247,7 +247,7 @@ func safeTomlSlice(r []any) []any { } // This element is not exported by revive, so we need copy the code. -// Extracted from https://github.com/mgechev/revive/blob/v1.3.4/config/config.go#L15 +// Extracted from https://github.com/mgechev/revive/blob/v1.3.7/config/config.go#L15 var defaultRules = []lint.Rule{ &rule.VarDeclarationsRule{}, &rule.PackageCommentsRule{}, @@ -290,7 +290,7 @@ var allRules = append([]lint.Rule{ &rule.ModifiesValRecRule{}, &rule.ConstantLogicalExprRule{}, &rule.BoolLiteralRule{}, - &rule.ImportsBlacklistRule{}, + &rule.ImportsBlocklistRule{}, &rule.FunctionResultsLimitRule{}, &rule.MaxPublicStructsRule{}, &rule.RangeValInClosureRule{}, @@ -327,6 +327,9 @@ var allRules = append([]lint.Rule{ &rule.RedundantImportAlias{}, &rule.ImportAliasNamingRule{}, &rule.EnforceMapStyleRule{}, + &rule.EnforceRepeatedArgTypeStyleRule{}, + &rule.EnforceSliceStyleRule{}, + &rule.MaxControlNestingRule{}, }, defaultRules...) const defaultConfidence = 0.8 @@ -349,8 +352,8 @@ func normalizeConfig(cfg *lint.Config) { } if cfg.EnableAllRules { // Add to the configuration all rules not yet present in it - for _, rule := range allRules { - ruleName := rule.Name() + for _, r := range allRules { + ruleName := r.Name() _, alreadyInConf := cfg.Rules[ruleName] if alreadyInConf { continue diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/rowserrcheck.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/rowserrcheck.go index 5a66d62e7..d67efd069 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/rowserrcheck.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/rowserrcheck.go @@ -14,12 +14,12 @@ func NewRowsErrCheck(settings *config.RowsErrCheckSettings) *goanalysis.Linter { pkgs = settings.Packages } - analyzer := rowserr.NewAnalyzer(pkgs...) + a := rowserr.NewAnalyzer(pkgs...) return goanalysis.NewLinter( - "rowserrcheck", - "checks whether Err of rows is checked successfully", - []*analysis.Analyzer{analyzer}, + a.Name, + "checks whether Rows.Err of rows is checked successfully", + []*analysis.Analyzer{a}, nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/sloglint.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/sloglint.go index b506d187f..acea90d53 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/sloglint.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/sloglint.go @@ -12,9 +12,13 @@ func NewSlogLint(settings *config.SlogLintSettings) *goanalysis.Linter { var opts *sloglint.Options if settings != nil { opts = &sloglint.Options{ + NoMixedArgs: settings.NoMixedArgs, KVOnly: settings.KVOnly, AttrOnly: settings.AttrOnly, + ContextOnly: settings.ContextOnly, + StaticMsg: settings.StaticMsg, NoRawKeys: settings.NoRawKeys, + KeyNamingCase: settings.KeyNamingCase, ArgsOnSepLines: settings.ArgsOnSepLines, } } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/spancheck.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/spancheck.go new file mode 100644 index 000000000..934124477 --- /dev/null +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/spancheck.go @@ -0,0 +1,29 @@ +package golinters + +import ( + "github.com/jjti/go-spancheck" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" +) + +func NewSpancheck(settings *config.SpancheckSettings) *goanalysis.Linter { + cfg := spancheck.NewDefaultConfig() + + if settings != nil { + if settings.Checks != nil { + cfg.EnabledChecks = settings.Checks + } + + if settings.IgnoreCheckSignatures != nil { + cfg.IgnoreChecksSignaturesSlice = settings.IgnoreCheckSignatures + } + } + + a := spancheck.NewAnalyzerWithConfig(cfg) + + return goanalysis. + NewLinter(a.Name, a.Doc, []*analysis.Analyzer{a}, nil). + WithLoadMode(goanalysis.LoadModeTypesInfo) +} diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/sqlclosecheck.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/sqlclosecheck.go index ff2c0c08f..e63b292a2 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/sqlclosecheck.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/sqlclosecheck.go @@ -8,12 +8,12 @@ import ( ) func NewSQLCloseCheck() *goanalysis.Linter { + a := analyzer.NewAnalyzer() + return goanalysis.NewLinter( - "sqlclosecheck", - "Checks that sql.Rows and sql.Stmt are closed.", - []*analysis.Analyzer{ - analyzer.NewAnalyzer(), - }, + a.Name, + a.Doc, + []*analysis.Analyzer{a}, nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/stylecheck.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/stylecheck.go index 2e1e21c5b..d9b0f87c8 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/stylecheck.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/stylecheck.go @@ -15,7 +15,7 @@ func NewStylecheck(settings *config.StaticCheckSettings) *goanalysis.Linter { // `scconfig.Analyzer` is a singleton, then it's not possible to have more than one instance for all staticcheck "sub-linters". // When we will merge the 4 "sub-linters", the problem will disappear: https://github.com/golangci/golangci-lint/issues/357 // Currently only stylecheck analyzer has a configuration in staticcheck. - scconfig.Analyzer.Run = func(pass *analysis.Pass) (any, error) { + scconfig.Analyzer.Run = func(_ *analysis.Pass) (any, error) { return cfg, nil } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/testifylint.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/testifylint.go index 83bae2868..d9cfc04f6 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/testifylint.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/testifylint.go @@ -14,14 +14,22 @@ func NewTestifylint(settings *config.TestifylintSettings) *goanalysis.Linter { cfg := make(map[string]map[string]any) if settings != nil { cfg[a.Name] = map[string]any{ - "enable-all": settings.EnableAll, + "enable-all": settings.EnableAll, + "disable-all": settings.DisableAll, } if len(settings.EnabledCheckers) > 0 { cfg[a.Name]["enable"] = settings.EnabledCheckers } + if len(settings.DisabledCheckers) > 0 { + cfg[a.Name]["disable"] = settings.DisabledCheckers + } + if p := settings.ExpectedActual.ExpVarPattern; p != "" { cfg[a.Name]["expected-actual.pattern"] = p } + if p := settings.RequireError.FnPattern; p != "" { + cfg[a.Name]["require-error.fn-pattern"] = p + } if m := settings.SuiteExtraAssertCall.Mode; m != "" { cfg[a.Name]["suite-extra-assert-call.mode"] = m } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/thelper.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/thelper.go index 84a8e9e8b..1ae85ef42 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/thelper.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/thelper.go @@ -4,6 +4,7 @@ import ( "strings" "github.com/kulti/thelper/pkg/analyzer" + "golang.org/x/exp/maps" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" @@ -42,10 +43,7 @@ func NewThelper(cfg *config.ThelperSettings) *goanalysis.Linter { linterLogger.Fatalf("thelper: at least one option must be enabled") } - var args []string - for k := range opts { - args = append(args, k) - } + args := maps.Keys(opts) cfgMap := map[string]map[string]any{ a.Name: { @@ -54,8 +52,8 @@ func NewThelper(cfg *config.ThelperSettings) *goanalysis.Linter { } return goanalysis.NewLinter( - "thelper", - "thelper detects Go test helpers without t.Helper() call and checks the consistency of test helpers", + a.Name, + a.Doc, []*analysis.Analyzer{a}, cfgMap, ).WithLoadMode(goanalysis.LoadModeTypesInfo) diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/tparallel.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/tparallel.go index cbe97516c..643f2c271 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/tparallel.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/tparallel.go @@ -8,10 +8,11 @@ import ( ) func NewTparallel() *goanalysis.Linter { + a := tparallel.Analyzer return goanalysis.NewLinter( - "tparallel", - "tparallel detects inappropriate usage of t.Parallel() method in your Go test codes", - []*analysis.Analyzer{tparallel.Analyzer}, + a.Name, + a.Doc, + []*analysis.Analyzer{a}, nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/unused.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/unused.go index 89ae7e98a..a93061c96 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/unused.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/unused.go @@ -47,7 +47,7 @@ func NewUnused(settings *config.UnusedSettings, scSettings *config.StaticCheckSe "Checks Go code for unused constants, variables, functions and types", []*analysis.Analyzer{analyzer}, nil, - ).WithIssuesReporter(func(lintCtx *linter.Context) []goanalysis.Issue { + ).WithIssuesReporter(func(_ *linter.Context) []goanalysis.Issue { return resIssues }).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/varcheck.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/varcheck.go index 495c5b59f..ea735672f 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/varcheck.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/varcheck.go @@ -30,7 +30,7 @@ func NewVarcheck(settings *config.VarCheckSettings) *goanalysis.Linter { "Finds unused global variables and constants", []*analysis.Analyzer{analyzer}, nil, - ).WithContextSetter(func(lintCtx *linter.Context) { + ).WithContextSetter(func(_ *linter.Context) { analyzer.Run = func(pass *analysis.Pass) (any, error) { issues := runVarCheck(pass, settings) diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/wastedassign.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/wastedassign.go index 92798d4f7..9038c827d 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/wastedassign.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/wastedassign.go @@ -8,10 +8,12 @@ import ( ) func NewWastedAssign() *goanalysis.Linter { + a := wastedassign.Analyzer + return goanalysis.NewLinter( - "wastedassign", - "wastedassign finds wasted assignment statements.", - []*analysis.Analyzer{wastedassign.Analyzer}, + a.Name, + "Finds wasted assignment statements", + []*analysis.Analyzer{a}, nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/whitespace.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/whitespace.go index e5941fa5d..5487b1016 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/whitespace.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/whitespace.go @@ -2,7 +2,6 @@ package golinters import ( "fmt" - "go/token" "sync" "github.com/ultraware/whitespace" @@ -16,7 +15,6 @@ import ( const whitespaceName = "whitespace" -//nolint:dupl func NewWhitespace(settings *config.WhitespaceSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue @@ -24,25 +22,22 @@ func NewWhitespace(settings *config.WhitespaceSettings) *goanalysis.Linter { var wsSettings whitespace.Settings if settings != nil { wsSettings = whitespace.Settings{ + Mode: whitespace.RunningModeGolangCI, MultiIf: settings.MultiIf, MultiFunc: settings.MultiFunc, } } - analyzer := &analysis.Analyzer{ - Name: whitespaceName, - Doc: goanalysis.TheOnlyanalyzerDoc, - Run: goanalysis.DummyRun, - } + a := whitespace.NewAnalyzer(&wsSettings) return goanalysis.NewLinter( - whitespaceName, - "Tool for detection of leading and trailing whitespace", - []*analysis.Analyzer{analyzer}, + a.Name, + a.Doc, + []*analysis.Analyzer{a}, nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (any, error) { - issues, err := runWhitespace(lintCtx, pass, wsSettings) + ).WithContextSetter(func(_ *linter.Context) { + a.Run = func(pass *analysis.Pass) (any, error) { + issues, err := runWhitespace(pass, wsSettings) if err != nil { return nil, err } @@ -62,46 +57,45 @@ func NewWhitespace(settings *config.WhitespaceSettings) *goanalysis.Linter { }).WithLoadMode(goanalysis.LoadModeSyntax) } -func runWhitespace(lintCtx *linter.Context, pass *analysis.Pass, wsSettings whitespace.Settings) ([]goanalysis.Issue, error) { - var messages []whitespace.Message - for _, file := range pass.Files { - messages = append(messages, whitespace.Run(file, pass.Fset, wsSettings)...) - } - - if len(messages) == 0 { - return nil, nil - } +func runWhitespace(pass *analysis.Pass, wsSettings whitespace.Settings) ([]goanalysis.Issue, error) { + lintIssues := whitespace.Run(pass, &wsSettings) - issues := make([]goanalysis.Issue, len(messages)) - for k, i := range messages { - issue := result.Issue{ - Pos: token.Position{ - Filename: i.Pos.Filename, - Line: i.Pos.Line, - }, - LineRange: &result.Range{From: i.Pos.Line, To: i.Pos.Line}, - Text: i.Message, - FromLinter: whitespaceName, - Replacement: &result.Replacement{}, + issues := make([]goanalysis.Issue, len(lintIssues)) + for i, issue := range lintIssues { + report := &result.Issue{ + FromLinter: whitespaceName, + Pos: pass.Fset.PositionFor(issue.Diagnostic, false), + Text: issue.Message, } - bracketLine, err := lintCtx.LineCache.GetLine(issue.Pos.Filename, issue.Pos.Line) - if err != nil { - return nil, fmt.Errorf("failed to get line %s:%d: %w", issue.Pos.Filename, issue.Pos.Line, err) - } + switch issue.MessageType { + case whitespace.MessageTypeRemove: + if len(issue.LineNumbers) == 0 { + continue + } + + report.LineRange = &result.Range{ + From: issue.LineNumbers[0], + To: issue.LineNumbers[len(issue.LineNumbers)-1], + } + + report.Replacement = &result.Replacement{NeedOnlyDelete: true} + + case whitespace.MessageTypeAdd: + report.Pos = pass.Fset.PositionFor(issue.FixStart, false) + report.Replacement = &result.Replacement{ + Inline: &result.InlineFix{ + StartCol: 0, + Length: 1, + NewString: "\n\t", + }, + } - switch i.Type { - case whitespace.MessageTypeLeading: - issue.LineRange.To++ // cover two lines by the issue: opening bracket "{" (issue.Pos.Line) and following empty line - case whitespace.MessageTypeTrailing: - issue.LineRange.From-- // cover two lines by the issue: closing bracket "}" (issue.Pos.Line) and preceding empty line - issue.Pos.Line-- // set in sync with LineRange.From to not break fixer and other code features - case whitespace.MessageTypeAddAfter: - bracketLine += "\n" + default: + return nil, fmt.Errorf("unknown message type: %v", issue.MessageType) } - issue.Replacement.NewLines = []string{bracketLine} - issues[k] = goanalysis.NewIssue(&issue, pass) + issues[i] = goanalysis.NewIssue(report, pass) } return issues, nil diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/wrapcheck.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/wrapcheck.go index 098eb87ba..6d25db427 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/wrapcheck.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/wrapcheck.go @@ -8,8 +8,6 @@ import ( "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" ) -const wrapcheckName = "wrapcheck" - func NewWrapcheck(settings *config.WrapcheckSettings) *goanalysis.Linter { cfg := wrapcheck.NewDefaultConfig() if settings != nil { @@ -30,7 +28,7 @@ func NewWrapcheck(settings *config.WrapcheckSettings) *goanalysis.Linter { a := wrapcheck.NewAnalyzer(cfg) return goanalysis.NewLinter( - wrapcheckName, + a.Name, a.Doc, []*analysis.Analyzer{a}, nil, diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/wsl.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/wsl.go index 05697a629..3b090a686 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/wsl.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/wsl.go @@ -1,89 +1,39 @@ package golinters import ( - "sync" - - "github.com/bombsimon/wsl/v3" + "github.com/bombsimon/wsl/v4" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" - "github.com/golangci/golangci-lint/pkg/lint/linter" - "github.com/golangci/golangci-lint/pkg/result" ) -const wslName = "wsl" - -// NewWSL returns a new WSL linter. func NewWSL(settings *config.WSLSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - - conf := wsl.DefaultConfig() - + var conf *wsl.Configuration if settings != nil { - conf.StrictAppend = settings.StrictAppend - conf.AllowAssignAndCallCuddle = settings.AllowAssignAndCallCuddle - conf.AllowAssignAndAnythingCuddle = settings.AllowAssignAndAnythingCuddle - conf.AllowMultiLineAssignCuddle = settings.AllowMultiLineAssignCuddle - conf.ForceCaseTrailingWhitespaceLimit = settings.ForceCaseTrailingWhitespaceLimit - conf.AllowTrailingComment = settings.AllowTrailingComment - conf.AllowSeparatedLeadingComment = settings.AllowSeparatedLeadingComment - conf.AllowCuddleDeclaration = settings.AllowCuddleDeclaration - conf.AllowCuddleWithCalls = settings.AllowCuddleWithCalls - conf.AllowCuddleWithRHS = settings.AllowCuddleWithRHS - conf.ForceCuddleErrCheckAndAssign = settings.ForceCuddleErrCheckAndAssign - conf.ErrorVariableNames = settings.ErrorVariableNames - conf.ForceExclusiveShortDeclarations = settings.ForceExclusiveShortDeclarations + conf = &wsl.Configuration{ + StrictAppend: settings.StrictAppend, + AllowAssignAndCallCuddle: settings.AllowAssignAndCallCuddle, + AllowAssignAndAnythingCuddle: settings.AllowAssignAndAnythingCuddle, + AllowMultiLineAssignCuddle: settings.AllowMultiLineAssignCuddle, + ForceCaseTrailingWhitespaceLimit: settings.ForceCaseTrailingWhitespaceLimit, + AllowTrailingComment: settings.AllowTrailingComment, + AllowSeparatedLeadingComment: settings.AllowSeparatedLeadingComment, + AllowCuddleDeclaration: settings.AllowCuddleDeclaration, + AllowCuddleWithCalls: settings.AllowCuddleWithCalls, + AllowCuddleWithRHS: settings.AllowCuddleWithRHS, + ForceCuddleErrCheckAndAssign: settings.ForceCuddleErrCheckAndAssign, + ErrorVariableNames: settings.ErrorVariableNames, + ForceExclusiveShortDeclarations: settings.ForceExclusiveShortDeclarations, + } } - analyzer := &analysis.Analyzer{ - Name: goanalysis.TheOnlyAnalyzerName, - Doc: goanalysis.TheOnlyanalyzerDoc, - Run: func(pass *analysis.Pass) (any, error) { - issues := runWSL(pass, &conf) - - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() - - return nil, nil - }, - } + a := wsl.NewAnalyzer(conf) return goanalysis.NewLinter( - wslName, - "Whitespace Linter - Forces you to use empty lines!", - []*analysis.Analyzer{analyzer}, + a.Name, + a.Doc, + []*analysis.Analyzer{a}, nil, - ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeSyntax) -} - -func runWSL(pass *analysis.Pass, conf *wsl.Configuration) []goanalysis.Issue { - if conf == nil { - return nil - } - - files := getFileNames(pass) - wslErrors, _ := wsl.NewProcessorWithConfig(*conf).ProcessFiles(files) - if len(wslErrors) == 0 { - return nil - } - - var issues []goanalysis.Issue - for _, err := range wslErrors { - issues = append(issues, goanalysis.NewIssue(&result.Issue{ - FromLinter: wslName, - Pos: err.Position, - Text: err.Reason, - }, pass)) - } - - return issues + ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/golinters/zerologlint.go b/vendor/github.com/golangci/golangci-lint/pkg/golinters/zerologlint.go index a37bca12e..edde72665 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/golinters/zerologlint.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/golinters/zerologlint.go @@ -8,10 +8,12 @@ import ( ) func NewZerologLint() *goanalysis.Linter { + a := zerologlint.Analyzer + return goanalysis.NewLinter( - "zerologlint", - "Detects the wrong usage of `zerolog` that a user forgets to dispatch with `Send` or `Msg`.", - []*analysis.Analyzer{zerologlint.Analyzer}, + a.Name, + a.Doc, + []*analysis.Analyzer{a}, nil, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/lint/linter/config.go b/vendor/github.com/golangci/golangci-lint/pkg/lint/linter/config.go index c911b5613..ed5e5508c 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/lint/linter/config.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/lint/linter/config.go @@ -134,11 +134,11 @@ func (lc *Config) Name() string { } func (lc *Config) WithNoopFallback(cfg *config.Config) *Config { - if cfg != nil && config.IsGreaterThanOrEqualGo121(cfg.Run.Go) { + if cfg != nil && config.IsGreaterThanOrEqualGo122(cfg.Run.Go) { lc.Linter = &Noop{ name: lc.Linter.Name(), desc: lc.Linter.Desc(), - run: func(pass *analysis.Pass) (any, error) { + run: func(_ *analysis.Pass) (any, error) { return nil, nil }, } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/custom_linters.go b/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/custom_linters.go index d0eaa7905..188c14d91 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/custom_linters.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/custom_linters.go @@ -1,12 +1,11 @@ package lintersdb import ( + "errors" "fmt" - "os" "path/filepath" "plugin" - "github.com/hashicorp/go-multierror" "github.com/spf13/viper" "golang.org/x/tools/go/analysis" @@ -71,7 +70,7 @@ func (m *Manager) getAnalyzerPlugin(path string, settings any) ([]*analysis.Anal configFilePath := viper.ConfigFileUsed() absConfigFilePath, err := filepath.Abs(configFilePath) if err != nil { - return nil, fmt.Errorf("could not get absolute representation of config file path %q: %v", configFilePath, err) + return nil, fmt.Errorf("could not get absolute representation of config file path %q: %w", configFilePath, err) } path = filepath.Join(filepath.Dir(absConfigFilePath), path) } @@ -94,8 +93,7 @@ func (m *Manager) lookupPlugin(plug *plugin.Plugin, settings any) ([]*analysis.A if err != nil { analyzers, errP := m.lookupAnalyzerPlugin(plug) if errP != nil { - // TODO(ldez): use `errors.Join` when we will upgrade to go1.20. - return nil, multierror.Append(err, errP) + return nil, errors.Join(err, errP) } return analyzers, nil @@ -116,11 +114,8 @@ func (m *Manager) lookupAnalyzerPlugin(plug *plugin.Plugin) ([]*analysis.Analyze return nil, err } - // TODO(ldez): remove this env var (but keep the log) in the next minor version (v1.55.0) - if _, ok := os.LookupEnv("GOLANGCI_LINT_HIDE_WARNING_ABOUT_PLUGIN_API_DEPRECATION"); !ok { - m.log.Warnf("plugin: 'AnalyzerPlugin' plugins are deprecated, please use the new plugin signature: " + - "https://golangci-lint.run/contributing/new-linters/#create-a-plugin") - } + m.log.Warnf("plugin: 'AnalyzerPlugin' plugins are deprecated, please use the new plugin signature: " + + "https://golangci-lint.run/contributing/new-linters/#create-a-plugin") analyzerPlugin, ok := symbol.(AnalyzerPlugin) if !ok { diff --git a/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/enabled_set.go b/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/enabled_set.go index c5c7874e4..6f7b91b4d 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/enabled_set.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/enabled_set.go @@ -4,6 +4,8 @@ import ( "os" "sort" + "golang.org/x/exp/maps" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" @@ -115,10 +117,7 @@ func (es EnabledSet) GetOptimizedLinters() ([]*linter.Config, error) { es.verbosePrintLintersStatus(resultLintersSet) es.combineGoAnalysisLinters(resultLintersSet) - var resultLinters []*linter.Config - for _, lc := range resultLintersSet { - resultLinters = append(resultLinters, lc) - } + resultLinters := maps.Values(resultLintersSet) // Make order of execution of linters (go/analysis metalinter and unused) stable. sort.Slice(resultLinters, func(i, j int) bool { @@ -185,10 +184,7 @@ func (es EnabledSet) combineGoAnalysisLinters(linters map[string]*linter.Config) ml := goanalysis.NewMetaLinter(goanalysisLinters) - var presets []string - for p := range goanalysisPresets { - presets = append(presets, p) - } + presets := maps.Keys(goanalysisPresets) mlConfig := &linter.Config{ Linter: ml, diff --git a/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/manager.go b/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/manager.go index fd329ce57..fdc73ec73 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/manager.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/lint/lintersdb/manager.go @@ -105,6 +105,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { grouperCfg *config.GrouperSettings ifshortCfg *config.IfshortSettings importAsCfg *config.ImportAsSettings + inamedparamCfg *config.INamedParamSettings interfaceBloatCfg *config.InterfaceBloatSettings ireturnCfg *config.IreturnSettings lllCfg *config.LllSettings @@ -121,13 +122,16 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { noLintLintCfg *config.NoLintLintSettings noNamedReturnsCfg *config.NoNamedReturnsSettings parallelTestCfg *config.ParallelTestSettings + perfSprintCfg *config.PerfSprintSettings preallocCfg *config.PreallocSettings predeclaredCfg *config.PredeclaredSettings promlinterCfg *config.PromlinterSettings + protogetterCfg *config.ProtoGetterSettings reassignCfg *config.ReassignSettings reviveCfg *config.ReviveSettings rowserrcheckCfg *config.RowsErrCheckSettings sloglintCfg *config.SlogLintSettings + spancheckCfg *config.SpancheckSettings staticcheckCfg *config.StaticCheckSettings structcheckCfg *config.StructCheckSettings stylecheckCfg *config.StaticCheckSettings @@ -187,6 +191,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { grouperCfg = &m.cfg.LintersSettings.Grouper ifshortCfg = &m.cfg.LintersSettings.Ifshort importAsCfg = &m.cfg.LintersSettings.ImportAs + inamedparamCfg = &m.cfg.LintersSettings.Inamedparam interfaceBloatCfg = &m.cfg.LintersSettings.InterfaceBloat ireturnCfg = &m.cfg.LintersSettings.Ireturn lllCfg = &m.cfg.LintersSettings.Lll @@ -202,14 +207,17 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { nlreturnCfg = &m.cfg.LintersSettings.Nlreturn noLintLintCfg = &m.cfg.LintersSettings.NoLintLint noNamedReturnsCfg = &m.cfg.LintersSettings.NoNamedReturns - preallocCfg = &m.cfg.LintersSettings.Prealloc parallelTestCfg = &m.cfg.LintersSettings.ParallelTest + perfSprintCfg = &m.cfg.LintersSettings.PerfSprint + preallocCfg = &m.cfg.LintersSettings.Prealloc predeclaredCfg = &m.cfg.LintersSettings.Predeclared promlinterCfg = &m.cfg.LintersSettings.Promlinter + protogetterCfg = &m.cfg.LintersSettings.ProtoGetter reassignCfg = &m.cfg.LintersSettings.Reassign reviveCfg = &m.cfg.LintersSettings.Revive rowserrcheckCfg = &m.cfg.LintersSettings.RowsErrCheck sloglintCfg = &m.cfg.LintersSettings.SlogLint + spancheckCfg = &m.cfg.LintersSettings.Spancheck staticcheckCfg = &m.cfg.LintersSettings.Staticcheck structcheckCfg = &m.cfg.LintersSettings.Structcheck stylecheckCfg = &m.cfg.LintersSettings.Stylecheck @@ -228,25 +236,22 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { wrapcheckCfg = &m.cfg.LintersSettings.Wrapcheck wslCfg = &m.cfg.LintersSettings.WSL - if govetCfg != nil { - govetCfg.Go = m.cfg.Run.Go - } + govetCfg.Go = m.cfg.Run.Go - if gocriticCfg != nil { - gocriticCfg.Go = trimGoVersion(m.cfg.Run.Go) - } + gocriticCfg.Go = trimGoVersion(m.cfg.Run.Go) - if gofumptCfg != nil && gofumptCfg.LangVersion == "" { + if gofumptCfg.LangVersion == "" { gofumptCfg.LangVersion = m.cfg.Run.Go } - if staticcheckCfg != nil && staticcheckCfg.GoVersion == "" { + // staticcheck related linters. + if staticcheckCfg.GoVersion == "" { staticcheckCfg.GoVersion = trimGoVersion(m.cfg.Run.Go) } - if gosimpleCfg != nil && gosimpleCfg.GoVersion == "" { + if gosimpleCfg.GoVersion == "" { gosimpleCfg.GoVersion = trimGoVersion(m.cfg.Run.Go) } - if stylecheckCfg != nil && stylecheckCfg.GoVersion != "" { + if stylecheckCfg.GoVersion != "" { stylecheckCfg.GoVersion = trimGoVersion(m.cfg.Run.Go) } } @@ -580,7 +585,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithLoadForGoAnalysis(). WithURL("https://github.com/julz/importas"), - linter.NewConfig(golinters.NewINamedParam()). + linter.NewConfig(golinters.NewINamedParam(inamedparamCfg)). WithSince("v1.55.0"). WithPresets(linter.PresetStyle). WithURL("https://github.com/macabu/inamedparam"), @@ -654,7 +659,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithSince("v1.51.0"). WithLoadForGoAnalysis(). WithPresets(linter.PresetStyle, linter.PresetBugs). - WithURL("https://github.com/tmzane/musttag"), + WithURL("https://github.com/go-simpler/musttag"), linter.NewConfig(golinters.NewNakedret(nakedretCfg)). WithSince("v1.19.0"). @@ -712,7 +717,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetStyle, linter.PresetTest). WithURL("https://github.com/kunwardeep/paralleltest"), - linter.NewConfig(golinters.NewPerfSprint()). + linter.NewConfig(golinters.NewPerfSprint(perfSprintCfg)). WithSince("v1.55.0"). WithLoadForGoAnalysis(). WithPresets(linter.PresetPerformance). @@ -733,7 +738,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetStyle). WithURL("https://github.com/yeya24/promlinter"), - linter.NewConfig(golinters.NewProtoGetter()). + linter.NewConfig(golinters.NewProtoGetter(protogetterCfg)). WithSince("v1.55.0"). WithPresets(linter.PresetBugs). WithLoadForGoAnalysis(). @@ -776,6 +781,12 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithLoadForGoAnalysis(). WithURL("https://github.com/ryanrolds/sqlclosecheck"), + linter.NewConfig(golinters.NewSpancheck(spancheckCfg)). + WithSince("v1.56.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetBugs). + WithURL("https://github.com/jjti/go-spancheck"), + linter.NewConfig(golinters.NewStaticcheck(staticcheckCfg)). WithEnabledByDefault(). WithSince("v1.0.0"). @@ -977,7 +988,7 @@ func trimGoVersion(v string) string { return "" } - exp := regexp.MustCompile(`(\d\.\d+)\.\d+`) + exp := regexp.MustCompile(`(\d\.\d+)(?:\.\d+|[a-z]+\d)`) if exp.MatchString(v) { return exp.FindStringSubmatch(v)[1] diff --git a/vendor/github.com/golangci/golangci-lint/pkg/lint/runner.go b/vendor/github.com/golangci/golangci-lint/pkg/lint/runner.go index d270892d5..e7cb17555 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/lint/runner.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/lint/runner.go @@ -2,11 +2,11 @@ package lint import ( "context" + "errors" "fmt" "runtime/debug" "strings" - "github.com/hashicorp/go-multierror" gopackages "golang.org/x/tools/go/packages" "github.com/golangci/golangci-lint/internal/errorutil" @@ -205,7 +205,7 @@ func (r Runner) Run(ctx context.Context, linters []*linter.Config, lintCtx *lint defer sw.Print() var ( - lintErrors *multierror.Error + lintErrors error issues []result.Issue ) @@ -214,7 +214,7 @@ func (r Runner) Run(ctx context.Context, linters []*linter.Config, lintCtx *lint sw.TrackStage(lc.Name(), func() { linterIssues, err := r.runLinterSafe(ctx, lintCtx, lc) if err != nil { - lintErrors = multierror.Append(lintErrors, fmt.Errorf("can't run linter %s: %w", lc.Linter.Name(), err)) + lintErrors = errors.Join(lintErrors, fmt.Errorf("can't run linter %s", lc.Linter.Name()), err) r.Log.Warnf("Can't run linter %s: %v", lc.Linter.Name(), err) return @@ -224,7 +224,7 @@ func (r Runner) Run(ctx context.Context, linters []*linter.Config, lintCtx *lint }) } - return r.processLintResults(issues), lintErrors.ErrorOrNil() + return r.processLintResults(issues), lintErrors } func (r *Runner) processIssues(issues []result.Issue, sw *timeutils.Stopwatch, statPerProcessor map[string]processorStat) []result.Issue { diff --git a/vendor/github.com/golangci/golangci-lint/pkg/packages/errors.go b/vendor/github.com/golangci/golangci-lint/pkg/packages/errors.go index 489836712..ff37651af 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/packages/errors.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/packages/errors.go @@ -18,7 +18,7 @@ func ParseErrorPosition(pos string) (*token.Position, error) { file := parts[0] line, err := strconv.Atoi(parts[1]) if err != nil { - return nil, fmt.Errorf("can't parse line number %q: %s", parts[1], err) + return nil, fmt.Errorf("can't parse line number %q: %w", parts[1], err) } var column int diff --git a/vendor/github.com/golangci/golangci-lint/pkg/printers/checkstyle.go b/vendor/github.com/golangci/golangci-lint/pkg/printers/checkstyle.go index 3762ca056..e32eef7f5 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/printers/checkstyle.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/printers/checkstyle.go @@ -7,6 +7,7 @@ import ( "sort" "github.com/go-xmlfmt/xmlfmt" + "golang.org/x/exp/maps" "github.com/golangci/golangci-lint/pkg/result" ) @@ -74,10 +75,7 @@ func (p Checkstyle) Print(issues []result.Issue) error { file.Errors = append(file.Errors, newError) } - out.Files = make([]*checkstyleFile, 0, len(files)) - for _, file := range files { - out.Files = append(out.Files, file) - } + out.Files = maps.Values(files) sort.Slice(out.Files, func(i, j int) bool { return out.Files[i].Name < out.Files[j].Name diff --git a/vendor/github.com/golangci/golangci-lint/pkg/printers/github.go b/vendor/github.com/golangci/golangci-lint/pkg/printers/github.go index c1da9df9c..f471d5a86 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/printers/github.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/printers/github.go @@ -15,7 +15,7 @@ type github struct { const defaultGithubSeverity = "error" // NewGithub output format outputs issues according to GitHub actions format: -// https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message +// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message func NewGithub(w io.Writer) Printer { return &github{w: w} } diff --git a/vendor/github.com/golangci/golangci-lint/pkg/printers/junitxml.go b/vendor/github.com/golangci/golangci-lint/pkg/printers/junitxml.go index 86a3811e4..3e3f82f58 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/printers/junitxml.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/printers/junitxml.go @@ -7,6 +7,8 @@ import ( "sort" "strings" + "golang.org/x/exp/maps" + "github.com/golangci/golangci-lint/pkg/result" ) @@ -71,9 +73,7 @@ func (p JunitXML) Print(issues []result.Issue) error { } var res testSuitesXML - for _, val := range suites { - res.TestSuites = append(res.TestSuites, val) - } + res.TestSuites = maps.Values(suites) sort.Slice(res.TestSuites, func(i, j int) bool { return res.TestSuites[i].Suite < res.TestSuites[j].Suite diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/diff.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/diff.go index 67104bab0..496d9c865 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/diff.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/diff.go @@ -47,7 +47,7 @@ func (p Diff) Process(issues []result.Issue) ([]result.Issue, error) { if p.patchFilePath != "" { patch, err := os.ReadFile(p.patchFilePath) if err != nil { - return nil, fmt.Errorf("can't read from patch file %s: %s", p.patchFilePath, err) + return nil, fmt.Errorf("can't read from patch file %s: %w", p.patchFilePath, err) } patchReader = bytes.NewReader(patch) } else if p.patch != "" { @@ -60,7 +60,7 @@ func (p Diff) Process(issues []result.Issue) ([]result.Issue, error) { WholeFiles: p.wholeFiles, } if err := c.Prepare(); err != nil { - return nil, fmt.Errorf("can't prepare diff by revgrep: %s", err) + return nil, fmt.Errorf("can't prepare diff by revgrep: %w", err) } return transformIssues(issues, func(i *result.Issue) *result.Issue { diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/nolint.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/nolint.go index 181d3bf1f..a72dd1ef2 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/nolint.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/nolint.go @@ -9,6 +9,8 @@ import ( "sort" "strings" + "golang.org/x/exp/maps" + "github.com/golangci/golangci-lint/pkg/golinters" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/lint/lintersdb" @@ -289,10 +291,7 @@ func (p *Nolint) Finish() { return } - unknownLinters := make([]string, 0, len(p.unknownLintersSet)) - for name := range p.unknownLintersSet { - unknownLinters = append(unknownLinters, name) - } + unknownLinters := maps.Keys(p.unknownLintersSet) sort.Strings(unknownLinters) p.log.Warnf("Found unknown linters in //nolint directives: %s", strings.Join(unknownLinters, ", ")) diff --git a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/skip_files.go b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/skip_files.go index 9579bee84..6c1c58695 100644 --- a/vendor/github.com/golangci/golangci-lint/pkg/result/processors/skip_files.go +++ b/vendor/github.com/golangci/golangci-lint/pkg/result/processors/skip_files.go @@ -21,7 +21,7 @@ func NewSkipFiles(patterns []string, pathPrefix string) (*SkipFiles, error) { p = fsutils.NormalizePathInRegex(p) patternRe, err := regexp.Compile(p) if err != nil { - return nil, fmt.Errorf("can't compile regexp %q: %s", p, err) + return nil, fmt.Errorf("can't compile regexp %q: %w", p, err) } patternsRe = append(patternsRe, patternRe) } diff --git a/vendor/github.com/hashicorp/errwrap/LICENSE b/vendor/github.com/hashicorp/errwrap/LICENSE deleted file mode 100644 index c33dcc7c9..000000000 --- a/vendor/github.com/hashicorp/errwrap/LICENSE +++ /dev/null @@ -1,354 +0,0 @@ -Mozilla Public License, version 2.0 - -1. Definitions - -1.1. “Contributor” - - means each individual or legal entity that creates, contributes to the - creation of, or owns Covered Software. - -1.2. “Contributor Version” - - means the combination of the Contributions of others (if any) used by a - Contributor and that particular Contributor’s Contribution. - -1.3. “Contribution” - - means Covered Software of a particular Contributor. - -1.4. “Covered Software” - - means Source Code Form to which the initial Contributor has attached the - notice in Exhibit A, the Executable Form of such Source Code Form, and - Modifications of such Source Code Form, in each case including portions - thereof. - -1.5. “Incompatible With Secondary Licenses” - means - - a. that the initial Contributor has attached the notice described in - Exhibit B to the Covered Software; or - - b. that the Covered Software was made available under the terms of version - 1.1 or earlier of the License, but not also under the terms of a - Secondary License. - -1.6. “Executable Form” - - means any form of the work other than Source Code Form. - -1.7. “Larger Work” - - means a work that combines Covered Software with other material, in a separate - file or files, that is not Covered Software. - -1.8. “License” - - means this document. - -1.9. “Licensable” - - means having the right to grant, to the maximum extent possible, whether at the - time of the initial grant or subsequently, any and all of the rights conveyed by - this License. - -1.10. “Modifications” - - means any of the following: - - a. any file in Source Code Form that results from an addition to, deletion - from, or modification of the contents of Covered Software; or - - b. any new file in Source Code Form that contains any Covered Software. - -1.11. “Patent Claims” of a Contributor - - means any patent claim(s), including without limitation, method, process, - and apparatus claims, in any patent Licensable by such Contributor that - would be infringed, but for the grant of the License, by the making, - using, selling, offering for sale, having made, import, or transfer of - either its Contributions or its Contributor Version. - -1.12. “Secondary License” - - means either the GNU General Public License, Version 2.0, the GNU Lesser - General Public License, Version 2.1, the GNU Affero General Public - License, Version 3.0, or any later versions of those licenses. - -1.13. “Source Code Form” - - means the form of the work preferred for making modifications. - -1.14. “You” (or “Your”) - - means an individual or a legal entity exercising rights under this - License. For legal entities, “You” includes any entity that controls, is - controlled by, or is under common control with You. For purposes of this - definition, “control” means (a) the power, direct or indirect, to cause - the direction or management of such entity, whether by contract or - otherwise, or (b) ownership of more than fifty percent (50%) of the - outstanding shares or beneficial ownership of such entity. - - -2. License Grants and Conditions - -2.1. Grants - - Each Contributor hereby grants You a world-wide, royalty-free, - non-exclusive license: - - a. under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or as - part of a Larger Work; and - - b. under Patent Claims of such Contributor to make, use, sell, offer for - sale, have made, import, and otherwise transfer either its Contributions - or its Contributor Version. - -2.2. Effective Date - - The licenses granted in Section 2.1 with respect to any Contribution become - effective for each Contribution on the date the Contributor first distributes - such Contribution. - -2.3. Limitations on Grant Scope - - The licenses granted in this Section 2 are the only rights granted under this - License. No additional rights or licenses will be implied from the distribution - or licensing of Covered Software under this License. Notwithstanding Section - 2.1(b) above, no patent license is granted by a Contributor: - - a. for any code that a Contributor has removed from Covered Software; or - - b. for infringements caused by: (i) Your and any other third party’s - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - - c. under Patent Claims infringed by Covered Software in the absence of its - Contributions. - - This License does not grant any rights in the trademarks, service marks, or - logos of any Contributor (except as may be necessary to comply with the - notice requirements in Section 3.4). - -2.4. Subsequent Licenses - - No Contributor makes additional grants as a result of Your choice to - distribute the Covered Software under a subsequent version of this License - (see Section 10.2) or under the terms of a Secondary License (if permitted - under the terms of Section 3.3). - -2.5. Representation - - Each Contributor represents that the Contributor believes its Contributions - are its original creation(s) or it has sufficient rights to grant the - rights to its Contributions conveyed by this License. - -2.6. Fair Use - - This License is not intended to limit any rights You have under applicable - copyright doctrines of fair use, fair dealing, or other equivalents. - -2.7. Conditions - - Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in - Section 2.1. - - -3. Responsibilities - -3.1. Distribution of Source Form - - All distribution of Covered Software in Source Code Form, including any - Modifications that You create or to which You contribute, must be under the - terms of this License. You must inform recipients that the Source Code Form - of the Covered Software is governed by the terms of this License, and how - they can obtain a copy of this License. You may not attempt to alter or - restrict the recipients’ rights in the Source Code Form. - -3.2. Distribution of Executable Form - - If You distribute Covered Software in Executable Form then: - - a. such Covered Software must also be made available in Source Code Form, - as described in Section 3.1, and You must inform recipients of the - Executable Form how they can obtain a copy of such Source Code Form by - reasonable means in a timely manner, at a charge no more than the cost - of distribution to the recipient; and - - b. You may distribute such Executable Form under the terms of this License, - or sublicense it under different terms, provided that the license for - the Executable Form does not attempt to limit or alter the recipients’ - rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - - You may create and distribute a Larger Work under terms of Your choice, - provided that You also comply with the requirements of this License for the - Covered Software. If the Larger Work is a combination of Covered Software - with a work governed by one or more Secondary Licenses, and the Covered - Software is not Incompatible With Secondary Licenses, this License permits - You to additionally distribute such Covered Software under the terms of - such Secondary License(s), so that the recipient of the Larger Work may, at - their option, further distribute the Covered Software under the terms of - either this License or such Secondary License(s). - -3.4. Notices - - You may not remove or alter the substance of any license notices (including - copyright notices, patent notices, disclaimers of warranty, or limitations - of liability) contained within the Source Code Form of the Covered - Software, except that You may alter any license notices to the extent - required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - - You may choose to offer, and to charge a fee for, warranty, support, - indemnity or liability obligations to one or more recipients of Covered - Software. However, You may do so only on Your own behalf, and not on behalf - of any Contributor. You must make it absolutely clear that any such - warranty, support, indemnity, or liability obligation is offered by You - alone, and You hereby agree to indemnify every Contributor for any - liability incurred by such Contributor as a result of warranty, support, - indemnity or liability terms You offer. You may include additional - disclaimers of warranty and limitations of liability specific to any - jurisdiction. - -4. Inability to Comply Due to Statute or Regulation - - If it is impossible for You to comply with any of the terms of this License - with respect to some or all of the Covered Software due to statute, judicial - order, or regulation then You must: (a) comply with the terms of this License - to the maximum extent possible; and (b) describe the limitations and the code - they affect. Such description must be placed in a text file included with all - distributions of the Covered Software under this License. Except to the - extent prohibited by statute or regulation, such description must be - sufficiently detailed for a recipient of ordinary skill to be able to - understand it. - -5. Termination - -5.1. The rights granted under this License will terminate automatically if You - fail to comply with any of its terms. However, if You become compliant, - then the rights granted under this License from a particular Contributor - are reinstated (a) provisionally, unless and until such Contributor - explicitly and finally terminates Your grants, and (b) on an ongoing basis, - if such Contributor fails to notify You of the non-compliance by some - reasonable means prior to 60 days after You have come back into compliance. - Moreover, Your grants from a particular Contributor are reinstated on an - ongoing basis if such Contributor notifies You of the non-compliance by - some reasonable means, this is the first time You have received notice of - non-compliance with this License from such Contributor, and You become - compliant prior to 30 days after Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent - infringement claim (excluding declaratory judgment actions, counter-claims, - and cross-claims) alleging that a Contributor Version directly or - indirectly infringes any patent, then the rights granted to You by any and - all Contributors for the Covered Software under Section 2.1 of this License - shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user - license agreements (excluding distributors and resellers) which have been - validly granted by You or Your distributors under this License prior to - termination shall survive termination. - -6. Disclaimer of Warranty - - Covered Software is provided under this License on an “as is” basis, without - warranty of any kind, either expressed, implied, or statutory, including, - without limitation, warranties that the Covered Software is free of defects, - merchantable, fit for a particular purpose or non-infringing. The entire - risk as to the quality and performance of the Covered Software is with You. - Should any Covered Software prove defective in any respect, You (not any - Contributor) assume the cost of any necessary servicing, repair, or - correction. This disclaimer of warranty constitutes an essential part of this - License. No use of any Covered Software is authorized under this License - except under this disclaimer. - -7. Limitation of Liability - - Under no circumstances and under no legal theory, whether tort (including - negligence), contract, or otherwise, shall any Contributor, or anyone who - distributes Covered Software as permitted above, be liable to You for any - direct, indirect, special, incidental, or consequential damages of any - character including, without limitation, damages for lost profits, loss of - goodwill, work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses, even if such party shall have been - informed of the possibility of such damages. This limitation of liability - shall not apply to liability for death or personal injury resulting from such - party’s negligence to the extent applicable law prohibits such limitation. - Some jurisdictions do not allow the exclusion or limitation of incidental or - consequential damages, so this exclusion and limitation may not apply to You. - -8. Litigation - - Any litigation relating to this License may be brought only in the courts of - a jurisdiction where the defendant maintains its principal place of business - and such litigation shall be governed by laws of that jurisdiction, without - reference to its conflict-of-law provisions. Nothing in this Section shall - prevent a party’s ability to bring cross-claims or counter-claims. - -9. Miscellaneous - - This License represents the complete agreement concerning the subject matter - hereof. If any provision of this License is held to be unenforceable, such - provision shall be reformed only to the extent necessary to make it - enforceable. Any law or regulation which provides that the language of a - contract shall be construed against the drafter shall not be used to construe - this License against a Contributor. - - -10. Versions of the License - -10.1. New Versions - - Mozilla Foundation is the license steward. Except as provided in Section - 10.3, no one other than the license steward has the right to modify or - publish new versions of this License. Each version will be given a - distinguishing version number. - -10.2. Effect of New Versions - - You may distribute the Covered Software under the terms of the version of - the License under which You originally received the Covered Software, or - under the terms of any subsequent version published by the license - steward. - -10.3. Modified Versions - - If you create software not governed by this License, and you want to - create a new license for such software, you may create and use a modified - version of this License if you rename the license and remove any - references to the name of the license steward (except to note that such - modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses - If You choose to distribute Source Code Form that is Incompatible With - Secondary Licenses under the terms of this version of the License, the - notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice - - This Source Code Form is subject to the - terms of the Mozilla Public License, v. - 2.0. If a copy of the MPL was not - distributed with this file, You can - obtain one at - http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular file, then -You may include the notice in a location (such as a LICENSE file in a relevant -directory) where a recipient would be likely to look for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - “Incompatible With Secondary Licenses” Notice - - This Source Code Form is “Incompatible - With Secondary Licenses”, as defined by - the Mozilla Public License, v. 2.0. - diff --git a/vendor/github.com/hashicorp/errwrap/README.md b/vendor/github.com/hashicorp/errwrap/README.md deleted file mode 100644 index 444df08f8..000000000 --- a/vendor/github.com/hashicorp/errwrap/README.md +++ /dev/null @@ -1,89 +0,0 @@ -# errwrap - -`errwrap` is a package for Go that formalizes the pattern of wrapping errors -and checking if an error contains another error. - -There is a common pattern in Go of taking a returned `error` value and -then wrapping it (such as with `fmt.Errorf`) before returning it. The problem -with this pattern is that you completely lose the original `error` structure. - -Arguably the _correct_ approach is that you should make a custom structure -implementing the `error` interface, and have the original error as a field -on that structure, such [as this example](http://golang.org/pkg/os/#PathError). -This is a good approach, but you have to know the entire chain of possible -rewrapping that happens, when you might just care about one. - -`errwrap` formalizes this pattern (it doesn't matter what approach you use -above) by giving a single interface for wrapping errors, checking if a specific -error is wrapped, and extracting that error. - -## Installation and Docs - -Install using `go get github.com/hashicorp/errwrap`. - -Full documentation is available at -http://godoc.org/github.com/hashicorp/errwrap - -## Usage - -#### Basic Usage - -Below is a very basic example of its usage: - -```go -// A function that always returns an error, but wraps it, like a real -// function might. -func tryOpen() error { - _, err := os.Open("/i/dont/exist") - if err != nil { - return errwrap.Wrapf("Doesn't exist: {{err}}", err) - } - - return nil -} - -func main() { - err := tryOpen() - - // We can use the Contains helpers to check if an error contains - // another error. It is safe to do this with a nil error, or with - // an error that doesn't even use the errwrap package. - if errwrap.Contains(err, "does not exist") { - // Do something - } - if errwrap.ContainsType(err, new(os.PathError)) { - // Do something - } - - // Or we can use the associated `Get` functions to just extract - // a specific error. This would return nil if that specific error doesn't - // exist. - perr := errwrap.GetType(err, new(os.PathError)) -} -``` - -#### Custom Types - -If you're already making custom types that properly wrap errors, then -you can get all the functionality of `errwraps.Contains` and such by -implementing the `Wrapper` interface with just one function. Example: - -```go -type AppError { - Code ErrorCode - Err error -} - -func (e *AppError) WrappedErrors() []error { - return []error{e.Err} -} -``` - -Now this works: - -```go -err := &AppError{Err: fmt.Errorf("an error")} -if errwrap.ContainsType(err, fmt.Errorf("")) { - // This will work! -} -``` diff --git a/vendor/github.com/hashicorp/errwrap/errwrap.go b/vendor/github.com/hashicorp/errwrap/errwrap.go deleted file mode 100644 index 44e368e56..000000000 --- a/vendor/github.com/hashicorp/errwrap/errwrap.go +++ /dev/null @@ -1,178 +0,0 @@ -// Package errwrap implements methods to formalize error wrapping in Go. -// -// All of the top-level functions that take an `error` are built to be able -// to take any error, not just wrapped errors. This allows you to use errwrap -// without having to type-check and type-cast everywhere. -package errwrap - -import ( - "errors" - "reflect" - "strings" -) - -// WalkFunc is the callback called for Walk. -type WalkFunc func(error) - -// Wrapper is an interface that can be implemented by custom types to -// have all the Contains, Get, etc. functions in errwrap work. -// -// When Walk reaches a Wrapper, it will call the callback for every -// wrapped error in addition to the wrapper itself. Since all the top-level -// functions in errwrap use Walk, this means that all those functions work -// with your custom type. -type Wrapper interface { - WrappedErrors() []error -} - -// Wrap defines that outer wraps inner, returning an error type that -// can be cleanly used with the other methods in this package, such as -// Contains, GetAll, etc. -// -// This function won't modify the error message at all (the outer message -// will be used). -func Wrap(outer, inner error) error { - return &wrappedError{ - Outer: outer, - Inner: inner, - } -} - -// Wrapf wraps an error with a formatting message. This is similar to using -// `fmt.Errorf` to wrap an error. If you're using `fmt.Errorf` to wrap -// errors, you should replace it with this. -// -// format is the format of the error message. The string '{{err}}' will -// be replaced with the original error message. -// -// Deprecated: Use fmt.Errorf() -func Wrapf(format string, err error) error { - outerMsg := "<nil>" - if err != nil { - outerMsg = err.Error() - } - - outer := errors.New(strings.Replace( - format, "{{err}}", outerMsg, -1)) - - return Wrap(outer, err) -} - -// Contains checks if the given error contains an error with the -// message msg. If err is not a wrapped error, this will always return -// false unless the error itself happens to match this msg. -func Contains(err error, msg string) bool { - return len(GetAll(err, msg)) > 0 -} - -// ContainsType checks if the given error contains an error with -// the same concrete type as v. If err is not a wrapped error, this will -// check the err itself. -func ContainsType(err error, v interface{}) bool { - return len(GetAllType(err, v)) > 0 -} - -// Get is the same as GetAll but returns the deepest matching error. -func Get(err error, msg string) error { - es := GetAll(err, msg) - if len(es) > 0 { - return es[len(es)-1] - } - - return nil -} - -// GetType is the same as GetAllType but returns the deepest matching error. -func GetType(err error, v interface{}) error { - es := GetAllType(err, v) - if len(es) > 0 { - return es[len(es)-1] - } - - return nil -} - -// GetAll gets all the errors that might be wrapped in err with the -// given message. The order of the errors is such that the outermost -// matching error (the most recent wrap) is index zero, and so on. -func GetAll(err error, msg string) []error { - var result []error - - Walk(err, func(err error) { - if err.Error() == msg { - result = append(result, err) - } - }) - - return result -} - -// GetAllType gets all the errors that are the same type as v. -// -// The order of the return value is the same as described in GetAll. -func GetAllType(err error, v interface{}) []error { - var result []error - - var search string - if v != nil { - search = reflect.TypeOf(v).String() - } - Walk(err, func(err error) { - var needle string - if err != nil { - needle = reflect.TypeOf(err).String() - } - - if needle == search { - result = append(result, err) - } - }) - - return result -} - -// Walk walks all the wrapped errors in err and calls the callback. If -// err isn't a wrapped error, this will be called once for err. If err -// is a wrapped error, the callback will be called for both the wrapper -// that implements error as well as the wrapped error itself. -func Walk(err error, cb WalkFunc) { - if err == nil { - return - } - - switch e := err.(type) { - case *wrappedError: - cb(e.Outer) - Walk(e.Inner, cb) - case Wrapper: - cb(err) - - for _, err := range e.WrappedErrors() { - Walk(err, cb) - } - case interface{ Unwrap() error }: - cb(err) - Walk(e.Unwrap(), cb) - default: - cb(err) - } -} - -// wrappedError is an implementation of error that has both the -// outer and inner errors. -type wrappedError struct { - Outer error - Inner error -} - -func (w *wrappedError) Error() string { - return w.Outer.Error() -} - -func (w *wrappedError) WrappedErrors() []error { - return []error{w.Outer, w.Inner} -} - -func (w *wrappedError) Unwrap() error { - return w.Inner -} diff --git a/vendor/github.com/hashicorp/go-multierror/LICENSE b/vendor/github.com/hashicorp/go-multierror/LICENSE deleted file mode 100644 index 82b4de97c..000000000 --- a/vendor/github.com/hashicorp/go-multierror/LICENSE +++ /dev/null @@ -1,353 +0,0 @@ -Mozilla Public License, version 2.0 - -1. Definitions - -1.1. “Contributor” - - means each individual or legal entity that creates, contributes to the - creation of, or owns Covered Software. - -1.2. “Contributor Version” - - means the combination of the Contributions of others (if any) used by a - Contributor and that particular Contributor’s Contribution. - -1.3. “Contribution” - - means Covered Software of a particular Contributor. - -1.4. “Covered Software” - - means Source Code Form to which the initial Contributor has attached the - notice in Exhibit A, the Executable Form of such Source Code Form, and - Modifications of such Source Code Form, in each case including portions - thereof. - -1.5. “Incompatible With Secondary Licenses” - means - - a. that the initial Contributor has attached the notice described in - Exhibit B to the Covered Software; or - - b. that the Covered Software was made available under the terms of version - 1.1 or earlier of the License, but not also under the terms of a - Secondary License. - -1.6. “Executable Form” - - means any form of the work other than Source Code Form. - -1.7. “Larger Work” - - means a work that combines Covered Software with other material, in a separate - file or files, that is not Covered Software. - -1.8. “License” - - means this document. - -1.9. “Licensable” - - means having the right to grant, to the maximum extent possible, whether at the - time of the initial grant or subsequently, any and all of the rights conveyed by - this License. - -1.10. “Modifications” - - means any of the following: - - a. any file in Source Code Form that results from an addition to, deletion - from, or modification of the contents of Covered Software; or - - b. any new file in Source Code Form that contains any Covered Software. - -1.11. “Patent Claims” of a Contributor - - means any patent claim(s), including without limitation, method, process, - and apparatus claims, in any patent Licensable by such Contributor that - would be infringed, but for the grant of the License, by the making, - using, selling, offering for sale, having made, import, or transfer of - either its Contributions or its Contributor Version. - -1.12. “Secondary License” - - means either the GNU General Public License, Version 2.0, the GNU Lesser - General Public License, Version 2.1, the GNU Affero General Public - License, Version 3.0, or any later versions of those licenses. - -1.13. “Source Code Form” - - means the form of the work preferred for making modifications. - -1.14. “You” (or “Your”) - - means an individual or a legal entity exercising rights under this - License. For legal entities, “You” includes any entity that controls, is - controlled by, or is under common control with You. For purposes of this - definition, “control” means (a) the power, direct or indirect, to cause - the direction or management of such entity, whether by contract or - otherwise, or (b) ownership of more than fifty percent (50%) of the - outstanding shares or beneficial ownership of such entity. - - -2. License Grants and Conditions - -2.1. Grants - - Each Contributor hereby grants You a world-wide, royalty-free, - non-exclusive license: - - a. under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or as - part of a Larger Work; and - - b. under Patent Claims of such Contributor to make, use, sell, offer for - sale, have made, import, and otherwise transfer either its Contributions - or its Contributor Version. - -2.2. Effective Date - - The licenses granted in Section 2.1 with respect to any Contribution become - effective for each Contribution on the date the Contributor first distributes - such Contribution. - -2.3. Limitations on Grant Scope - - The licenses granted in this Section 2 are the only rights granted under this - License. No additional rights or licenses will be implied from the distribution - or licensing of Covered Software under this License. Notwithstanding Section - 2.1(b) above, no patent license is granted by a Contributor: - - a. for any code that a Contributor has removed from Covered Software; or - - b. for infringements caused by: (i) Your and any other third party’s - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - - c. under Patent Claims infringed by Covered Software in the absence of its - Contributions. - - This License does not grant any rights in the trademarks, service marks, or - logos of any Contributor (except as may be necessary to comply with the - notice requirements in Section 3.4). - -2.4. Subsequent Licenses - - No Contributor makes additional grants as a result of Your choice to - distribute the Covered Software under a subsequent version of this License - (see Section 10.2) or under the terms of a Secondary License (if permitted - under the terms of Section 3.3). - -2.5. Representation - - Each Contributor represents that the Contributor believes its Contributions - are its original creation(s) or it has sufficient rights to grant the - rights to its Contributions conveyed by this License. - -2.6. Fair Use - - This License is not intended to limit any rights You have under applicable - copyright doctrines of fair use, fair dealing, or other equivalents. - -2.7. Conditions - - Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in - Section 2.1. - - -3. Responsibilities - -3.1. Distribution of Source Form - - All distribution of Covered Software in Source Code Form, including any - Modifications that You create or to which You contribute, must be under the - terms of this License. You must inform recipients that the Source Code Form - of the Covered Software is governed by the terms of this License, and how - they can obtain a copy of this License. You may not attempt to alter or - restrict the recipients’ rights in the Source Code Form. - -3.2. Distribution of Executable Form - - If You distribute Covered Software in Executable Form then: - - a. such Covered Software must also be made available in Source Code Form, - as described in Section 3.1, and You must inform recipients of the - Executable Form how they can obtain a copy of such Source Code Form by - reasonable means in a timely manner, at a charge no more than the cost - of distribution to the recipient; and - - b. You may distribute such Executable Form under the terms of this License, - or sublicense it under different terms, provided that the license for - the Executable Form does not attempt to limit or alter the recipients’ - rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - - You may create and distribute a Larger Work under terms of Your choice, - provided that You also comply with the requirements of this License for the - Covered Software. If the Larger Work is a combination of Covered Software - with a work governed by one or more Secondary Licenses, and the Covered - Software is not Incompatible With Secondary Licenses, this License permits - You to additionally distribute such Covered Software under the terms of - such Secondary License(s), so that the recipient of the Larger Work may, at - their option, further distribute the Covered Software under the terms of - either this License or such Secondary License(s). - -3.4. Notices - - You may not remove or alter the substance of any license notices (including - copyright notices, patent notices, disclaimers of warranty, or limitations - of liability) contained within the Source Code Form of the Covered - Software, except that You may alter any license notices to the extent - required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - - You may choose to offer, and to charge a fee for, warranty, support, - indemnity or liability obligations to one or more recipients of Covered - Software. However, You may do so only on Your own behalf, and not on behalf - of any Contributor. You must make it absolutely clear that any such - warranty, support, indemnity, or liability obligation is offered by You - alone, and You hereby agree to indemnify every Contributor for any - liability incurred by such Contributor as a result of warranty, support, - indemnity or liability terms You offer. You may include additional - disclaimers of warranty and limitations of liability specific to any - jurisdiction. - -4. Inability to Comply Due to Statute or Regulation - - If it is impossible for You to comply with any of the terms of this License - with respect to some or all of the Covered Software due to statute, judicial - order, or regulation then You must: (a) comply with the terms of this License - to the maximum extent possible; and (b) describe the limitations and the code - they affect. Such description must be placed in a text file included with all - distributions of the Covered Software under this License. Except to the - extent prohibited by statute or regulation, such description must be - sufficiently detailed for a recipient of ordinary skill to be able to - understand it. - -5. Termination - -5.1. The rights granted under this License will terminate automatically if You - fail to comply with any of its terms. However, if You become compliant, - then the rights granted under this License from a particular Contributor - are reinstated (a) provisionally, unless and until such Contributor - explicitly and finally terminates Your grants, and (b) on an ongoing basis, - if such Contributor fails to notify You of the non-compliance by some - reasonable means prior to 60 days after You have come back into compliance. - Moreover, Your grants from a particular Contributor are reinstated on an - ongoing basis if such Contributor notifies You of the non-compliance by - some reasonable means, this is the first time You have received notice of - non-compliance with this License from such Contributor, and You become - compliant prior to 30 days after Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent - infringement claim (excluding declaratory judgment actions, counter-claims, - and cross-claims) alleging that a Contributor Version directly or - indirectly infringes any patent, then the rights granted to You by any and - all Contributors for the Covered Software under Section 2.1 of this License - shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user - license agreements (excluding distributors and resellers) which have been - validly granted by You or Your distributors under this License prior to - termination shall survive termination. - -6. Disclaimer of Warranty - - Covered Software is provided under this License on an “as is” basis, without - warranty of any kind, either expressed, implied, or statutory, including, - without limitation, warranties that the Covered Software is free of defects, - merchantable, fit for a particular purpose or non-infringing. The entire - risk as to the quality and performance of the Covered Software is with You. - Should any Covered Software prove defective in any respect, You (not any - Contributor) assume the cost of any necessary servicing, repair, or - correction. This disclaimer of warranty constitutes an essential part of this - License. No use of any Covered Software is authorized under this License - except under this disclaimer. - -7. Limitation of Liability - - Under no circumstances and under no legal theory, whether tort (including - negligence), contract, or otherwise, shall any Contributor, or anyone who - distributes Covered Software as permitted above, be liable to You for any - direct, indirect, special, incidental, or consequential damages of any - character including, without limitation, damages for lost profits, loss of - goodwill, work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses, even if such party shall have been - informed of the possibility of such damages. This limitation of liability - shall not apply to liability for death or personal injury resulting from such - party’s negligence to the extent applicable law prohibits such limitation. - Some jurisdictions do not allow the exclusion or limitation of incidental or - consequential damages, so this exclusion and limitation may not apply to You. - -8. Litigation - - Any litigation relating to this License may be brought only in the courts of - a jurisdiction where the defendant maintains its principal place of business - and such litigation shall be governed by laws of that jurisdiction, without - reference to its conflict-of-law provisions. Nothing in this Section shall - prevent a party’s ability to bring cross-claims or counter-claims. - -9. Miscellaneous - - This License represents the complete agreement concerning the subject matter - hereof. If any provision of this License is held to be unenforceable, such - provision shall be reformed only to the extent necessary to make it - enforceable. Any law or regulation which provides that the language of a - contract shall be construed against the drafter shall not be used to construe - this License against a Contributor. - - -10. Versions of the License - -10.1. New Versions - - Mozilla Foundation is the license steward. Except as provided in Section - 10.3, no one other than the license steward has the right to modify or - publish new versions of this License. Each version will be given a - distinguishing version number. - -10.2. Effect of New Versions - - You may distribute the Covered Software under the terms of the version of - the License under which You originally received the Covered Software, or - under the terms of any subsequent version published by the license - steward. - -10.3. Modified Versions - - If you create software not governed by this License, and you want to - create a new license for such software, you may create and use a modified - version of this License if you rename the license and remove any - references to the name of the license steward (except to note that such - modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses - If You choose to distribute Source Code Form that is Incompatible With - Secondary Licenses under the terms of this version of the License, the - notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice - - This Source Code Form is subject to the - terms of the Mozilla Public License, v. - 2.0. If a copy of the MPL was not - distributed with this file, You can - obtain one at - http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular file, then -You may include the notice in a location (such as a LICENSE file in a relevant -directory) where a recipient would be likely to look for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - “Incompatible With Secondary Licenses” Notice - - This Source Code Form is “Incompatible - With Secondary Licenses”, as defined by - the Mozilla Public License, v. 2.0. diff --git a/vendor/github.com/hashicorp/go-multierror/Makefile b/vendor/github.com/hashicorp/go-multierror/Makefile deleted file mode 100644 index b97cd6ed0..000000000 --- a/vendor/github.com/hashicorp/go-multierror/Makefile +++ /dev/null @@ -1,31 +0,0 @@ -TEST?=./... - -default: test - -# test runs the test suite and vets the code. -test: generate - @echo "==> Running tests..." - @go list $(TEST) \ - | grep -v "/vendor/" \ - | xargs -n1 go test -timeout=60s -parallel=10 ${TESTARGS} - -# testrace runs the race checker -testrace: generate - @echo "==> Running tests (race)..." - @go list $(TEST) \ - | grep -v "/vendor/" \ - | xargs -n1 go test -timeout=60s -race ${TESTARGS} - -# updatedeps installs all the dependencies needed to run and build. -updatedeps: - @sh -c "'${CURDIR}/scripts/deps.sh' '${NAME}'" - -# generate runs `go generate` to build the dynamically generated source files. -generate: - @echo "==> Generating..." - @find . -type f -name '.DS_Store' -delete - @go list ./... \ - | grep -v "/vendor/" \ - | xargs -n1 go generate - -.PHONY: default test testrace updatedeps generate diff --git a/vendor/github.com/hashicorp/go-multierror/README.md b/vendor/github.com/hashicorp/go-multierror/README.md deleted file mode 100644 index 71dd308ed..000000000 --- a/vendor/github.com/hashicorp/go-multierror/README.md +++ /dev/null @@ -1,150 +0,0 @@ -# go-multierror - -[](https://circleci.com/gh/hashicorp/go-multierror) -[](https://pkg.go.dev/github.com/hashicorp/go-multierror) - - -[circleci]: https://app.circleci.com/pipelines/github/hashicorp/go-multierror -[godocs]: https://pkg.go.dev/github.com/hashicorp/go-multierror - -`go-multierror` is a package for Go that provides a mechanism for -representing a list of `error` values as a single `error`. - -This allows a function in Go to return an `error` that might actually -be a list of errors. If the caller knows this, they can unwrap the -list and access the errors. If the caller doesn't know, the error -formats to a nice human-readable format. - -`go-multierror` is fully compatible with the Go standard library -[errors](https://golang.org/pkg/errors/) package, including the -functions `As`, `Is`, and `Unwrap`. This provides a standardized approach -for introspecting on error values. - -## Installation and Docs - -Install using `go get github.com/hashicorp/go-multierror`. - -Full documentation is available at -https://pkg.go.dev/github.com/hashicorp/go-multierror - -### Requires go version 1.13 or newer - -`go-multierror` requires go version 1.13 or newer. Go 1.13 introduced -[error wrapping](https://golang.org/doc/go1.13#error_wrapping), which -this library takes advantage of. - -If you need to use an earlier version of go, you can use the -[v1.0.0](https://github.com/hashicorp/go-multierror/tree/v1.0.0) -tag, which doesn't rely on features in go 1.13. - -If you see compile errors that look like the below, it's likely that -you're on an older version of go: - -``` -/go/src/github.com/hashicorp/go-multierror/multierror.go:112:9: undefined: errors.As -/go/src/github.com/hashicorp/go-multierror/multierror.go:117:9: undefined: errors.Is -``` - -## Usage - -go-multierror is easy to use and purposely built to be unobtrusive in -existing Go applications/libraries that may not be aware of it. - -**Building a list of errors** - -The `Append` function is used to create a list of errors. This function -behaves a lot like the Go built-in `append` function: it doesn't matter -if the first argument is nil, a `multierror.Error`, or any other `error`, -the function behaves as you would expect. - -```go -var result error - -if err := step1(); err != nil { - result = multierror.Append(result, err) -} -if err := step2(); err != nil { - result = multierror.Append(result, err) -} - -return result -``` - -**Customizing the formatting of the errors** - -By specifying a custom `ErrorFormat`, you can customize the format -of the `Error() string` function: - -```go -var result *multierror.Error - -// ... accumulate errors here, maybe using Append - -if result != nil { - result.ErrorFormat = func([]error) string { - return "errors!" - } -} -``` - -**Accessing the list of errors** - -`multierror.Error` implements `error` so if the caller doesn't know about -multierror, it will work just fine. But if you're aware a multierror might -be returned, you can use type switches to access the list of errors: - -```go -if err := something(); err != nil { - if merr, ok := err.(*multierror.Error); ok { - // Use merr.Errors - } -} -``` - -You can also use the standard [`errors.Unwrap`](https://golang.org/pkg/errors/#Unwrap) -function. This will continue to unwrap into subsequent errors until none exist. - -**Extracting an error** - -The standard library [`errors.As`](https://golang.org/pkg/errors/#As) -function can be used directly with a multierror to extract a specific error: - -```go -// Assume err is a multierror value -err := somefunc() - -// We want to know if "err" has a "RichErrorType" in it and extract it. -var errRich RichErrorType -if errors.As(err, &errRich) { - // It has it, and now errRich is populated. -} -``` - -**Checking for an exact error value** - -Some errors are returned as exact errors such as the [`ErrNotExist`](https://golang.org/pkg/os/#pkg-variables) -error in the `os` package. You can check if this error is present by using -the standard [`errors.Is`](https://golang.org/pkg/errors/#Is) function. - -```go -// Assume err is a multierror value -err := somefunc() -if errors.Is(err, os.ErrNotExist) { - // err contains os.ErrNotExist -} -``` - -**Returning a multierror only if there are errors** - -If you build a `multierror.Error`, you can use the `ErrorOrNil` function -to return an `error` implementation only if there are errors to return: - -```go -var result *multierror.Error - -// ... accumulate errors here - -// Return the `error` only if errors were added to the multierror, otherwise -// return nil since there are no errors. -return result.ErrorOrNil() -``` diff --git a/vendor/github.com/hashicorp/go-multierror/append.go b/vendor/github.com/hashicorp/go-multierror/append.go deleted file mode 100644 index 3e2589bfd..000000000 --- a/vendor/github.com/hashicorp/go-multierror/append.go +++ /dev/null @@ -1,43 +0,0 @@ -package multierror - -// Append is a helper function that will append more errors -// onto an Error in order to create a larger multi-error. -// -// If err is not a multierror.Error, then it will be turned into -// one. If any of the errs are multierr.Error, they will be flattened -// one level into err. -// Any nil errors within errs will be ignored. If err is nil, a new -// *Error will be returned. -func Append(err error, errs ...error) *Error { - switch err := err.(type) { - case *Error: - // Typed nils can reach here, so initialize if we are nil - if err == nil { - err = new(Error) - } - - // Go through each error and flatten - for _, e := range errs { - switch e := e.(type) { - case *Error: - if e != nil { - err.Errors = append(err.Errors, e.Errors...) - } - default: - if e != nil { - err.Errors = append(err.Errors, e) - } - } - } - - return err - default: - newErrs := make([]error, 0, len(errs)+1) - if err != nil { - newErrs = append(newErrs, err) - } - newErrs = append(newErrs, errs...) - - return Append(&Error{}, newErrs...) - } -} diff --git a/vendor/github.com/hashicorp/go-multierror/flatten.go b/vendor/github.com/hashicorp/go-multierror/flatten.go deleted file mode 100644 index aab8e9abe..000000000 --- a/vendor/github.com/hashicorp/go-multierror/flatten.go +++ /dev/null @@ -1,26 +0,0 @@ -package multierror - -// Flatten flattens the given error, merging any *Errors together into -// a single *Error. -func Flatten(err error) error { - // If it isn't an *Error, just return the error as-is - if _, ok := err.(*Error); !ok { - return err - } - - // Otherwise, make the result and flatten away! - flatErr := new(Error) - flatten(err, flatErr) - return flatErr -} - -func flatten(err error, flatErr *Error) { - switch err := err.(type) { - case *Error: - for _, e := range err.Errors { - flatten(e, flatErr) - } - default: - flatErr.Errors = append(flatErr.Errors, err) - } -} diff --git a/vendor/github.com/hashicorp/go-multierror/format.go b/vendor/github.com/hashicorp/go-multierror/format.go deleted file mode 100644 index 47f13c49a..000000000 --- a/vendor/github.com/hashicorp/go-multierror/format.go +++ /dev/null @@ -1,27 +0,0 @@ -package multierror - -import ( - "fmt" - "strings" -) - -// ErrorFormatFunc is a function callback that is called by Error to -// turn the list of errors into a string. -type ErrorFormatFunc func([]error) string - -// ListFormatFunc is a basic formatter that outputs the number of errors -// that occurred along with a bullet point list of the errors. -func ListFormatFunc(es []error) string { - if len(es) == 1 { - return fmt.Sprintf("1 error occurred:\n\t* %s\n\n", es[0]) - } - - points := make([]string, len(es)) - for i, err := range es { - points[i] = fmt.Sprintf("* %s", err) - } - - return fmt.Sprintf( - "%d errors occurred:\n\t%s\n\n", - len(es), strings.Join(points, "\n\t")) -} diff --git a/vendor/github.com/hashicorp/go-multierror/group.go b/vendor/github.com/hashicorp/go-multierror/group.go deleted file mode 100644 index 9c29efb7f..000000000 --- a/vendor/github.com/hashicorp/go-multierror/group.go +++ /dev/null @@ -1,38 +0,0 @@ -package multierror - -import "sync" - -// Group is a collection of goroutines which return errors that need to be -// coalesced. -type Group struct { - mutex sync.Mutex - err *Error - wg sync.WaitGroup -} - -// Go calls the given function in a new goroutine. -// -// If the function returns an error it is added to the group multierror which -// is returned by Wait. -func (g *Group) Go(f func() error) { - g.wg.Add(1) - - go func() { - defer g.wg.Done() - - if err := f(); err != nil { - g.mutex.Lock() - g.err = Append(g.err, err) - g.mutex.Unlock() - } - }() -} - -// Wait blocks until all function calls from the Go method have returned, then -// returns the multierror. -func (g *Group) Wait() *Error { - g.wg.Wait() - g.mutex.Lock() - defer g.mutex.Unlock() - return g.err -} diff --git a/vendor/github.com/hashicorp/go-multierror/multierror.go b/vendor/github.com/hashicorp/go-multierror/multierror.go deleted file mode 100644 index f54574326..000000000 --- a/vendor/github.com/hashicorp/go-multierror/multierror.go +++ /dev/null @@ -1,121 +0,0 @@ -package multierror - -import ( - "errors" - "fmt" -) - -// Error is an error type to track multiple errors. This is used to -// accumulate errors in cases and return them as a single "error". -type Error struct { - Errors []error - ErrorFormat ErrorFormatFunc -} - -func (e *Error) Error() string { - fn := e.ErrorFormat - if fn == nil { - fn = ListFormatFunc - } - - return fn(e.Errors) -} - -// ErrorOrNil returns an error interface if this Error represents -// a list of errors, or returns nil if the list of errors is empty. This -// function is useful at the end of accumulation to make sure that the value -// returned represents the existence of errors. -func (e *Error) ErrorOrNil() error { - if e == nil { - return nil - } - if len(e.Errors) == 0 { - return nil - } - - return e -} - -func (e *Error) GoString() string { - return fmt.Sprintf("*%#v", *e) -} - -// WrappedErrors returns the list of errors that this Error is wrapping. It is -// an implementation of the errwrap.Wrapper interface so that multierror.Error -// can be used with that library. -// -// This method is not safe to be called concurrently. Unlike accessing the -// Errors field directly, this function also checks if the multierror is nil to -// prevent a null-pointer panic. It satisfies the errwrap.Wrapper interface. -func (e *Error) WrappedErrors() []error { - if e == nil { - return nil - } - return e.Errors -} - -// Unwrap returns an error from Error (or nil if there are no errors). -// This error returned will further support Unwrap to get the next error, -// etc. The order will match the order of Errors in the multierror.Error -// at the time of calling. -// -// The resulting error supports errors.As/Is/Unwrap so you can continue -// to use the stdlib errors package to introspect further. -// -// This will perform a shallow copy of the errors slice. Any errors appended -// to this error after calling Unwrap will not be available until a new -// Unwrap is called on the multierror.Error. -func (e *Error) Unwrap() error { - // If we have no errors then we do nothing - if e == nil || len(e.Errors) == 0 { - return nil - } - - // If we have exactly one error, we can just return that directly. - if len(e.Errors) == 1 { - return e.Errors[0] - } - - // Shallow copy the slice - errs := make([]error, len(e.Errors)) - copy(errs, e.Errors) - return chain(errs) -} - -// chain implements the interfaces necessary for errors.Is/As/Unwrap to -// work in a deterministic way with multierror. A chain tracks a list of -// errors while accounting for the current represented error. This lets -// Is/As be meaningful. -// -// Unwrap returns the next error. In the cleanest form, Unwrap would return -// the wrapped error here but we can't do that if we want to properly -// get access to all the errors. Instead, users are recommended to use -// Is/As to get the correct error type out. -// -// Precondition: []error is non-empty (len > 0) -type chain []error - -// Error implements the error interface -func (e chain) Error() string { - return e[0].Error() -} - -// Unwrap implements errors.Unwrap by returning the next error in the -// chain or nil if there are no more errors. -func (e chain) Unwrap() error { - if len(e) == 1 { - return nil - } - - return e[1:] -} - -// As implements errors.As by attempting to map to the current value. -func (e chain) As(target interface{}) bool { - return errors.As(e[0], target) -} - -// Is implements errors.Is by comparing the current value directly. -func (e chain) Is(target error) bool { - return errors.Is(e[0], target) -} diff --git a/vendor/github.com/hashicorp/go-multierror/prefix.go b/vendor/github.com/hashicorp/go-multierror/prefix.go deleted file mode 100644 index 5c477abe4..000000000 --- a/vendor/github.com/hashicorp/go-multierror/prefix.go +++ /dev/null @@ -1,37 +0,0 @@ -package multierror - -import ( - "fmt" - - "github.com/hashicorp/errwrap" -) - -// Prefix is a helper function that will prefix some text -// to the given error. If the error is a multierror.Error, then -// it will be prefixed to each wrapped error. -// -// This is useful to use when appending multiple multierrors -// together in order to give better scoping. -func Prefix(err error, prefix string) error { - if err == nil { - return nil - } - - format := fmt.Sprintf("%s {{err}}", prefix) - switch err := err.(type) { - case *Error: - // Typed nils can reach here, so initialize if we are nil - if err == nil { - err = new(Error) - } - - // Wrap each of the errors - for i, e := range err.Errors { - err.Errors[i] = errwrap.Wrapf(format, e) - } - - return err - default: - return errwrap.Wrapf(format, err) - } -} diff --git a/vendor/github.com/hashicorp/go-multierror/sort.go b/vendor/github.com/hashicorp/go-multierror/sort.go deleted file mode 100644 index fecb14e81..000000000 --- a/vendor/github.com/hashicorp/go-multierror/sort.go +++ /dev/null @@ -1,16 +0,0 @@ -package multierror - -// Len implements sort.Interface function for length -func (err Error) Len() int { - return len(err.Errors) -} - -// Swap implements sort.Interface function for swapping elements -func (err Error) Swap(i, j int) { - err.Errors[i], err.Errors[j] = err.Errors[j], err.Errors[i] -} - -// Less implements sort.Interface function for determining order -func (err Error) Less(i, j int) bool { - return err.Errors[i].Error() < err.Errors[j].Error() -} diff --git a/vendor/github.com/jgautheron/goconst/README.md b/vendor/github.com/jgautheron/goconst/README.md index 8dd093baf..c671eb541 100644 --- a/vendor/github.com/jgautheron/goconst/README.md +++ b/vendor/github.com/jgautheron/goconst/README.md @@ -23,6 +23,7 @@ Usage: Flags: -ignore exclude files matching the given regular expression + -ignore-strings exclude strings matching the given regular expression -ignore-tests exclude tests from the search (default: true) -min-occurrences report from how many occurrences (default: 2) -min-length only report strings with the minimum given length (default: 3) diff --git a/vendor/github.com/jgautheron/goconst/api.go b/vendor/github.com/jgautheron/goconst/api.go index d56fcd6c2..b838e035f 100644 --- a/vendor/github.com/jgautheron/goconst/api.go +++ b/vendor/github.com/jgautheron/goconst/api.go @@ -14,6 +14,7 @@ type Issue struct { } type Config struct { + IgnoreStrings string IgnoreTests bool MatchWithConstants bool MinStringLength int @@ -28,6 +29,7 @@ func Run(files []*ast.File, fset *token.FileSet, cfg *Config) ([]Issue, error) { p := New( "", "", + cfg.IgnoreStrings, cfg.IgnoreTests, cfg.MatchWithConstants, cfg.ParseNumbers, diff --git a/vendor/github.com/jgautheron/goconst/parser.go b/vendor/github.com/jgautheron/goconst/parser.go index c1edd4c78..2f32740b9 100644 --- a/vendor/github.com/jgautheron/goconst/parser.go +++ b/vendor/github.com/jgautheron/goconst/parser.go @@ -24,11 +24,11 @@ const ( type Parser struct { // Meant to be passed via New() - path, ignore string - ignoreTests, matchConstant bool - minLength, minOccurrences int - numberMin, numberMax int - excludeTypes map[Type]bool + path, ignore, ignoreStrings string + ignoreTests, matchConstant bool + minLength, minOccurrences int + numberMin, numberMax int + excludeTypes map[Type]bool supportedTokens []token.Token @@ -39,7 +39,7 @@ type Parser struct { // New creates a new instance of the parser. // This is your entry point if you'd like to use goconst as an API. -func New(path, ignore string, ignoreTests, matchConstant, numbers bool, numberMin, numberMax, minLength, minOccurrences int, excludeTypes map[Type]bool) *Parser { +func New(path, ignore, ignoreStrings string, ignoreTests, matchConstant, numbers bool, numberMin, numberMax, minLength, minOccurrences int, excludeTypes map[Type]bool) *Parser { supportedTokens := []token.Token{token.STRING} if numbers { supportedTokens = append(supportedTokens, token.INT, token.FLOAT) @@ -48,6 +48,7 @@ func New(path, ignore string, ignoreTests, matchConstant, numbers bool, numberMi return &Parser{ path: path, ignore: ignore, + ignoreStrings: ignoreStrings, ignoreTests: ignoreTests, matchConstant: matchConstant, minLength: minLength, @@ -98,6 +99,16 @@ func (p *Parser) ProcessResults() { delete(p.strs, str) } + if p.ignoreStrings != "" { + match, err := regexp.MatchString(p.ignoreStrings, str) + if err != nil { + log.Println(err) + } + if match { + delete(p.strs, str) + } + } + // If the value is a number if i, err := strconv.ParseInt(str, 0, 0); err == nil { if p.numberMin != 0 && i < int64(p.numberMin) { diff --git a/vendor/github.com/jjti/go-spancheck/.gitignore b/vendor/github.com/jjti/go-spancheck/.gitignore new file mode 100644 index 000000000..1f83be414 --- /dev/null +++ b/vendor/github.com/jjti/go-spancheck/.gitignore @@ -0,0 +1,19 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ +src/ diff --git a/vendor/github.com/jjti/go-spancheck/.golangci.yml b/vendor/github.com/jjti/go-spancheck/.golangci.yml new file mode 100644 index 000000000..15d8513d6 --- /dev/null +++ b/vendor/github.com/jjti/go-spancheck/.golangci.yml @@ -0,0 +1,103 @@ +## A good ref for this: https://gist.github.com/maratori/47a4d00457a92aa426dbd48a18776322 + +run: + timeout: 5m + tests: true +linters: + enable: + - asasalint # checks for pass []any as any in variadic func(...any) + - asciicheck # checks that your code does not contain non-ASCII identifiers + - bidichk # checks for dangerous unicode character sequences + - bodyclose + - containedctx + - decorder # checks declaration order and count of types, constants, variables and functions + - dogsled + - dupword # checks for duplicate words in the source code + - durationcheck # checks for two durations multiplied together + - errcheck + - errname + - errorlint + - exhaustive # checks exhaustiveness of enum switch statements + - exportloopref # checks for pointers to enclosing loop variables + - gci + - gochecknoinits # checks that no init functions are present in Go code + - gocritic + - gomnd + - gosimple + - govet + - importas # enforces consistent import aliases + - ineffassign + - loggercheck + - makezero # finds slice declarations with non-zero initial length + - mirror + - misspell + - musttag # enforces field tags in (un)marshaled structs + - nakedret + - nestif # reports deeply nested if statements + - nilerr # finds the code that returns nil even if it checks that the error is not nil + - noctx # finds sending http request without context.Context + - nolintlint # reports ill-formed or insufficient nolint directives + - predeclared # finds code that shadows one of Go's predeclared identifiers + - promlinter + - reassign # checks that package variables are not reassigned + - revive # fast, configurable, extensible, flexible, and beautiful linter for Go, drop-in replacement of golint + - staticcheck + - stylecheck + - tenv + - thelper # detects golang test helpers without t.Helper() call and checks the consistency of test helpers + - unconvert # removes unnecessary type conversions + - unparam # reports unused function parameters + - unused + - usestdlibvars # detects the possibility to use variables/constants from the Go standard library + - wastedassign # finds wasted assignment statements + - whitespace # detects leading and trailing whitespace +linters-settings: + gci: + skip-generated: true + custom-order: true + sections: + - standard # Standard section: captures all standard packages. + - default # Default section: contains all imports that could not be matched to another section type. + - prefix(github.com/jjti) + exhaustive: + # Program elements to check for exhaustiveness. + # Default: [ switch ] + check: + - switch + - map + gocritic: + settings: + captLocal: + # Whether to restrict checker to params only. + # Default: true + paramsOnly: false + underef: + # Whether to skip (*x).method() calls where x is a pointer receiver. + # Default: true + skipRecvDeref: false + govet: + enable-all: true + disable: + - fieldalignment # too strict + - shadow # bunch of false positive, doesn't realize when we return from a func + misspell: + locale: US + nakedret: + max-func-lines: 0 + nestif: + # Minimal complexity of if statements to report. + # Default: 5 + min-complexity: 4 + nolintlint: + # Enable to require an explanation of nonzero length after each nolint directive. + # Default: false + require-explanation: true + stylecheck: + checks: ["all"] +issues: + include: + - EXC0001 # Error return value of x is not checked + - EXC0013 # package comment should be of the form "(.+)... + - EXC0014 # comment on exported (.+) should be of the form "(.+)..." + exclude: + - ifElseChain diff --git a/vendor/github.com/jjti/go-spancheck/CONTRIBUTING.md b/vendor/github.com/jjti/go-spancheck/CONTRIBUTING.md new file mode 100644 index 000000000..32932fae1 --- /dev/null +++ b/vendor/github.com/jjti/go-spancheck/CONTRIBUTING.md @@ -0,0 +1,51 @@ +# Contributing guideline + +Contributions are welcome + appreciated. + +## Open Requests + +These are a couple contributions I would especially appreciate: + +1. Add check for SetAttributes: https://github.com/jjti/go-spancheck/issues/1 +1. Add SuggestedFix(es): https://github.com/jjti/go-spancheck/issues/2 + +## Steps + +### 1. Create an Issue + +If one does not exist already, open a bug report or feature request in [https://github.com/jjti/go-spancheck/issues](https://github.com/jjti/go-spancheck/issues). + +### 2. Add a test case + +Test cases are in `/testdata`. + +If fixing a bug, you can add it to `testdata/enableall/enable_all.go` (for example): + +```go +func _() { + ctx, span := otel.Tracer("foo").Start(context.Background(), "bar") // want "span.End is not called on all paths, possible memory leak" + print(ctx.Done(), span.IsRecording()) +} // want "return can be reached without calling span.End" +``` + +If adding a new feature with a new combination of flags, create a new module within `testdata`: + +1. Create a new module, eg `testdata/setattributes` +1. Copy/paste go.mod/sum into the new module directory and update the module definition, eg `module github.com/jjti/go-spancheck/testdata/setattributes` +1. Add the module to the workspace in [go.work](./go.work) +1. Add the module's directory to the `testvendor` Make target in [Makefile](./Makefile) + +### 3. Run tests + +```bash +make test +``` + +### 4. Open a PR + +Eg of a GitHub snippet for PRs: + +```bash +alias gpr='gh pr view --web 2>/dev/null || gh pr create --web --fill' +gpr +``` diff --git a/vendor/github.com/jjti/go-spancheck/LICENSE b/vendor/github.com/jjti/go-spancheck/LICENSE new file mode 100644 index 000000000..552ddf2dc --- /dev/null +++ b/vendor/github.com/jjti/go-spancheck/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Joshua Timmons + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/jjti/go-spancheck/Makefile b/vendor/github.com/jjti/go-spancheck/Makefile new file mode 100644 index 000000000..39d80f7c6 --- /dev/null +++ b/vendor/github.com/jjti/go-spancheck/Makefile @@ -0,0 +1,27 @@ +.PHONY: fmt +fmt: + golangci-lint run --fix --config ./.golangci.yml + +.PHONY: test +test: testvendor + go test -v ./... + +# note: I'm copying https://github.com/ghostiam/protogetter/blob/main/testdata/Makefile +# +# x/tools/go/analysis/analysistest does not support go modules. To work around this issue +# we need to vendor any external modules to `./src`. +# +# Follow https://github.com/golang/go/issues/37054 for more details. +.PHONY: testvendor +testvendor: + @rm -rf base/src + @cd testdata/base && go mod vendor + @cp -r testdata/base/vendor testdata/base/src + @cp -r testdata/base/vendor testdata/disableerrorchecks/src + @cp -r testdata/base/vendor testdata/enableall/src + @rm -rf testdata/base/vendor + +.PHONY: install +install: + go install ./cmd/spancheck + @echo "Installed in $(shell which spancheck)"
\ No newline at end of file diff --git a/vendor/github.com/jjti/go-spancheck/README.md b/vendor/github.com/jjti/go-spancheck/README.md new file mode 100644 index 000000000..98aedec28 --- /dev/null +++ b/vendor/github.com/jjti/go-spancheck/README.md @@ -0,0 +1,210 @@ +# go-spancheck + + +[](https://github.com/jjti/go-spancheck/actions/workflows/ci.yaml) +[](https://goreportcard.com/report/github.com/jjti/go-spancheck) +[](LICENSE) + +Checks usage of: + +- [OpenTelemetry spans](https://opentelemetry.io/docs/instrumentation/go/manual/) from [go.opentelemetry.io/otel/trace](go.opentelemetry.io/otel/trace) +- [OpenCensus spans](https://opencensus.io/quickstart/go/tracing/) from [go.opencensus.io/trace](https://pkg.go.dev/go.opencensus.io/trace#Span) + +## Installation & Usage + +```bash +go install github.com/jjti/go-spancheck/cmd/spancheck@latest +spancheck ./... +``` + +## Example + +```bash +spancheck -checks 'end,set-status,record-error' ./... +``` + +```go +func _() error { + // span.End is not called on all paths, possible memory leak + // span.SetStatus is not called on all paths + // span.RecordError is not called on all paths + _, span := otel.Tracer("foo").Start(context.Background(), "bar") + + if true { + // return can be reached without calling span.End + // return can be reached without calling span.SetStatus + // return can be reached without calling span.RecordError + return errors.New("err") + } + + return nil // return can be reached without calling span.End +} +``` + +## Configuration + +Only the `span.End()` check is enabled by default. The others can be enabled with `-checks 'end,set-status,record-error'`. + +```txt +$ spancheck -h +... +Flags: + -checks string + comma-separated list of checks to enable (options: end, set-status, record-error) (default "end") + -ignore-check-signatures string + comma-separated list of regex for function signatures that disable checks on errors +``` + +### Ignore check signatures + +The `span.SetStatus()` and `span.RecordError()` checks warn when there is: + +1. a path to return statement +1. that returns an error +1. without a call (to `SetStatus` or `RecordError`, respectively) + +But it's convenient to call `SetStatus` and `RecordError` from utility methods [[1](https://andydote.co.uk/2023/09/19/tracing-is-better/#step-2-wrap-the-errors)]. To support that, the `ignore-*-check-signatures` settings will suppress warnings if the configured function is present in the path. + +For example, by default, the code below would have warnings as shown: + +```go +func task(ctx context.Context) error { + ctx, span := otel.Tracer("foo").Start(ctx, "bar") // span.SetStatus is not called on all paths + defer span.End() + + if err := subTask(ctx); err != nil { + return recordErr(span, err) // return can be reached without calling span.SetStatus + } + + return nil +} + +func recordErr(span trace.Span, err error) error { + span.SetStatus(codes.Error, err.Error()) + span.RecordError(err) + return err +} +``` + +The warnings are can be ignored by setting `-ignore-check-signatures` flag to `recordErr`: + +```bash +spancheck -checks 'end,set-status,record-error' -ignore-check-signatures 'recordErr' ./... +``` + +## Problem Statement + +Tracing is a celebrated [[1](https://andydote.co.uk/2023/09/19/tracing-is-better/),[2](https://charity.wtf/2022/08/15/live-your-best-life-with-structured-events/)] and well marketed [[3](https://docs.datadoghq.com/tracing/),[4](https://www.honeycomb.io/distributed-tracing)] pillar of observability. But self-instrumented tracing requires a lot of easy-to-forget boilerplate: + +```go +import ( + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/codes" +) + +func task(ctx context.Context) error { + ctx, span := otel.Tracer("foo").Start(ctx, "bar") + defer span.End() // call `.End()` + + if err := subTask(ctx); err != nil { + span.SetStatus(codes.Error, err.Error()) // call SetStatus(codes.Error, msg) to set status:error + span.RecordError(err) // call RecordError(err) to record an error event + return err + } + + return nil +} +``` + +For spans to be _really_ useful, developers need to: + +1. call `span.End()` always +1. call `span.SetStatus(codes.Error, msg)` on error +1. call `span.RecordError(err)` on error +1. call `span.SetAttributes()` liberally + +- OpenTelemetry: [Creating spans](https://opentelemetry.io/docs/instrumentation/go/manual/#creating-spans) +- Uptrace: [OpenTelemetry Go Tracing API](https://uptrace.dev/opentelemetry/go-tracing.html#quickstart) + +This linter helps developers with steps 1-3. + +## Checks + +This linter supports three checks, each documented below. Only the check for `span.End()` is enabled by default. See [Configuration](#configuration) for instructions on enabling the others. + +### `span.End()` + +Enabled by default. + +Not calling `End` can cause memory leaks and prevents spans from being closed. + +> Any Span that is created MUST also be ended. This is the responsibility of the user. Implementations of this API may leak memory or other resources if Spans are not ended. + +[source: trace.go](https://github.com/open-telemetry/opentelemetry-go/blob/98b32a6c3a87fbee5d34c063b9096f416b250897/trace/trace.go#L523) + +```go +func task(ctx context.Context) error { + otel.Tracer("app").Start(ctx, "foo") // span is unassigned, probable memory leak + _, span := otel.Tracer().Start(ctx, "foo") // span.End is not called on all paths, possible memory leak + return nil // return can be reached without calling span.End +} +``` + +### `span.SetStatus(codes.Error, "msg")` + +Disabled by default. Enable with `-checks 'set-status'`. + +Developers should call `SetStatus` on spans. The status attribute is an important, first-class attribute: + +1. observability platforms and APMs differentiate "success" vs "failure" using [span's status codes](https://docs.datadoghq.com/tracing/metrics/). +1. telemetry collector agents, like the [Open Telemetry Collector's Tail Sampling Processor](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/processor/tailsamplingprocessor/README.md#:~:text=Sampling%20Processor.-,status_code,-%3A%20Sample%20based%20upon), are configurable to sample `Error` spans at a higher rate than `OK` spans. +1. observability platforms, like [DataDog, have trace retention filters that use spans' status](https://docs.datadoghq.com/tracing/trace_pipeline/trace_retention/). In other words, `status:error` spans often receive special treatment with the assumption they are more useful for debugging. And forgetting to set the status can lead to spans, with useful debugging information, being dropped. + +```go +func _() error { + _, span := otel.Tracer("foo").Start(context.Background(), "bar") // span.SetStatus is not called on all paths + defer span.End() + + if err := subTask(); err != nil { + span.RecordError(err) + return errors.New(err) // return can be reached without calling span.SetStatus + } + + return nil +} +``` + +OpenTelemetry docs: [Set span status](https://opentelemetry.io/docs/instrumentation/go/manual/#set-span-status). + +### `span.RecordError(err)` + +Disabled by default. Enable with `-checks 'record-error'`. + +Calling `RecordError` creates a new exception-type [event (structured log message)](https://opentelemetry.io/docs/concepts/signals/traces/#span-events) on the span. This is recommended to capture the error's stack trace. + +```go +func _() error { + _, span := otel.Tracer("foo").Start(context.Background(), "bar") // span.RecordError is not called on all paths + defer span.End() + + if err := subTask(); err != nil { + span.SetStatus(codes.Error, err.Error()) + return errors.New(err) // return can be reached without calling span.RecordError + } + + return nil +} +``` + +OpenTelemetry docs: [Record errors](https://opentelemetry.io/docs/instrumentation/go/manual/#record-errors). + +Note: this check is not applied to [OpenCensus spans](https://pkg.go.dev/go.opencensus.io/trace#SpanInterface) because they have no `RecordError` method. + +## Attribution + +This linter is the product of liberal copying of: + +- [github.com/golang/tools/go/analysis/passes/lostcancel](https://github.com/golang/tools/tree/master/go/analysis/passes/lostcancel) (half the linter) +- [github.com/tomarrell/wrapcheck](https://github.com/tomarrell/wrapcheck) (error type checking and config) +- [github.com/Antonboom/testifylint](https://github.com/Antonboom/testifylint) (README) +- [github.com/ghostiam/protogetter](https://github.com/ghostiam/protogetter/blob/main/testdata/Makefile) (test setup) diff --git a/vendor/github.com/jjti/go-spancheck/config.go b/vendor/github.com/jjti/go-spancheck/config.go new file mode 100644 index 000000000..4005f49e0 --- /dev/null +++ b/vendor/github.com/jjti/go-spancheck/config.go @@ -0,0 +1,141 @@ +package spancheck + +import ( + "flag" + "fmt" + "log" + "regexp" + "strings" +) + +// Check is a type of check that can be enabled or disabled. +type Check int + +const ( + // EndCheck if enabled, checks that span.End() is called after span creation and before the function returns. + EndCheck Check = iota + + // SetStatusCheck if enabled, checks that `span.SetStatus(codes.Error, msg)` is called when returning an error. + SetStatusCheck + + // RecordErrorCheck if enabled, checks that span.RecordError(err) is called when returning an error. + RecordErrorCheck +) + +func (c Check) String() string { + switch c { + case EndCheck: + return "end" + case SetStatusCheck: + return "set-status" + case RecordErrorCheck: + return "record-error" + default: + return "" + } +} + +var ( + // Checks is a list of all checks by name. + Checks = map[string]Check{ + EndCheck.String(): EndCheck, + SetStatusCheck.String(): SetStatusCheck, + RecordErrorCheck.String(): RecordErrorCheck, + } +) + +// Config is a configuration for the spancheck analyzer. +type Config struct { + fs flag.FlagSet + + // EnabledChecks is a list of checks to enable by name. + EnabledChecks []string + + // IgnoreChecksSignaturesSlice is a slice of strings that are turned into + // the IgnoreSetStatusCheckSignatures regex. + IgnoreChecksSignaturesSlice []string + + endCheckEnabled bool + setStatusEnabled bool + recordErrorEnabled bool + + // ignoreChecksSignatures is a regex that, if matched, disables the + // SetStatus and RecordError checks on error. + ignoreChecksSignatures *regexp.Regexp +} + +// NewDefaultConfig returns a new Config with default values. +func NewDefaultConfig() *Config { + return &Config{ + EnabledChecks: []string{EndCheck.String()}, + } +} + +// finalize parses checks and signatures from the public string slices of Config. +func (c *Config) finalize() { + c.parseSignatures() + + checks := parseChecks(c.EnabledChecks) + c.endCheckEnabled = contains(checks, EndCheck) + c.setStatusEnabled = contains(checks, SetStatusCheck) + c.recordErrorEnabled = contains(checks, RecordErrorCheck) +} + +// parseSignatures sets the Ignore*CheckSignatures regex from the string slices. +func (c *Config) parseSignatures() { + if c.ignoreChecksSignatures == nil && len(c.IgnoreChecksSignaturesSlice) > 0 { + if len(c.IgnoreChecksSignaturesSlice) == 1 && c.IgnoreChecksSignaturesSlice[0] == "" { + return + } + + c.ignoreChecksSignatures = createRegex(c.IgnoreChecksSignaturesSlice) + } +} + +func parseChecks(checksSlice []string) []Check { + if len(checksSlice) == 0 { + return nil + } + + checks := []Check{} + for _, check := range checksSlice { + checkName := strings.TrimSpace(check) + if checkName == "" { + continue + } + + check, ok := Checks[checkName] + if !ok { + continue + } + + checks = append(checks, check) + } + + return checks +} + +func createRegex(sigs []string) *regexp.Regexp { + if len(sigs) == 0 { + return nil + } + + regex := fmt.Sprintf("(%s)", strings.Join(sigs, "|")) + regexCompiled, err := regexp.Compile(regex) + if err != nil { + log.Default().Print("[WARN] failed to compile regex from signature flag", "regex", regex, "err", err) + return nil + } + + return regexCompiled +} + +func contains(s []Check, e Check) bool { + for _, a := range s { + if a == e { + return true + } + } + + return false +} diff --git a/vendor/github.com/jjti/go-spancheck/doc.go b/vendor/github.com/jjti/go-spancheck/doc.go new file mode 100644 index 000000000..f9dec043f --- /dev/null +++ b/vendor/github.com/jjti/go-spancheck/doc.go @@ -0,0 +1,37 @@ +// Package spancheck defines a linter that checks for mistakes with OTEL trace spans. +// +// # Analyzer spancheck +// +// spancheck: check for mistakes with OpenTelemetry trace spans. +// +// Common mistakes with OTEL trace spans include forgetting to call End: +// +// func(ctx context.Context) { +// ctx, span := otel.Tracer("app").Start(ctx, "span") +// // defer span.End() should be here +// +// // do stuff +// } +// +// Forgetting to set an Error status: +// +// ctx, span := otel.Tracer("app").Start(ctx, "span") +// defer span.End() +// +// if err := task(); err != nil { +// // span.SetStatus(codes.Error, err.Error()) should be here +// span.RecordError(err) +// return fmt.Errorf("failed to run task: %w", err) +// } +// +// Forgetting to record the Error: +// +// ctx, span := otel.Tracer("app").Start(ctx, "span") +// defer span.End() +// +// if err := task(); err != nil { +// span.SetStatus(codes.Error, err.Error()) +// // span.RecordError(err) should be here +// return fmt.Errorf("failed to run task: %w", err) +// } +package spancheck diff --git a/vendor/github.com/jjti/go-spancheck/go.work b/vendor/github.com/jjti/go-spancheck/go.work new file mode 100644 index 000000000..7d0a87b9e --- /dev/null +++ b/vendor/github.com/jjti/go-spancheck/go.work @@ -0,0 +1,8 @@ +go 1.20 + +use ( + . + ./testdata/base + ./testdata/disableerrorchecks + ./testdata/enableall +) diff --git a/vendor/github.com/jjti/go-spancheck/go.work.sum b/vendor/github.com/jjti/go-spancheck/go.work.sum new file mode 100644 index 000000000..f3cdef790 --- /dev/null +++ b/vendor/github.com/jjti/go-spancheck/go.work.sum @@ -0,0 +1,3 @@ +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= diff --git a/vendor/github.com/jjti/go-spancheck/spancheck.go b/vendor/github.com/jjti/go-spancheck/spancheck.go new file mode 100644 index 000000000..ebfc1ac68 --- /dev/null +++ b/vendor/github.com/jjti/go-spancheck/spancheck.go @@ -0,0 +1,431 @@ +package spancheck + +import ( + "go/ast" + "go/types" + "log" + "regexp" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/ctrlflow" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" + "golang.org/x/tools/go/cfg" +) + +const stackLen = 32 + +// spanType differentiates span types. +type spanType int + +const ( + spanUnset spanType = iota // not a span + spanOpenTelemetry // from go.opentelemetry.io/otel + spanOpenCensus // from go.opencensus.io/trace +) + +var ( + // this approach stolen from errcheck + // https://github.com/kisielk/errcheck/blob/7f94c385d0116ccc421fbb4709e4a484d98325ee/errcheck/errcheck.go#L22 + errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) +) + +// NewAnalyzerWithConfig returns a new analyzer configured with the Config passed in. +// Its config can be set for testing. +func NewAnalyzerWithConfig(config *Config) *analysis.Analyzer { + return newAnalyzer(config) +} + +func newAnalyzer(config *Config) *analysis.Analyzer { + config.finalize() + + return &analysis.Analyzer{ + Name: "spancheck", + Doc: "Checks for mistakes with OpenTelemetry/Census spans.", + Flags: config.fs, + Run: run(config), + Requires: []*analysis.Analyzer{ + ctrlflow.Analyzer, + inspect.Analyzer, + }, + } +} + +func run(config *Config) func(*analysis.Pass) (interface{}, error) { + return func(pass *analysis.Pass) (interface{}, error) { + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + + nodeFilter := []ast.Node{ + (*ast.FuncLit)(nil), // f := func() {} + (*ast.FuncDecl)(nil), // func foo() {} + } + inspect.Preorder(nodeFilter, func(n ast.Node) { + runFunc(pass, n, config) + }) + + return nil, nil + } +} + +type spanVar struct { + stmt ast.Node + id *ast.Ident + vr *types.Var + spanType spanType +} + +// runFunc checks if the node is a function, has a span, and the span never has SetStatus set. +func runFunc(pass *analysis.Pass, node ast.Node, config *Config) { + // copying https://cs.opensource.google/go/x/tools/+/master:go/analysis/passes/lostcancel/lostcancel.go + + // Find scope of function node + var funcScope *types.Scope + switch v := node.(type) { + case *ast.FuncLit: + funcScope = pass.TypesInfo.Scopes[v.Type] + case *ast.FuncDecl: + funcScope = pass.TypesInfo.Scopes[v.Type] + } + + // Maps each span variable to its defining ValueSpec/AssignStmt. + spanVars := make(map[*ast.Ident]spanVar) + + // Find the set of span vars to analyze. + stack := make([]ast.Node, 0, stackLen) + ast.Inspect(node, func(n ast.Node) bool { + switch n.(type) { + case *ast.FuncLit: + if len(stack) > 0 { + return false // don't stray into nested functions + } + case nil: + stack = stack[:len(stack)-1] // pop + return true + } + stack = append(stack, n) // push + + // Look for [{AssignStmt,ValueSpec} CallExpr SelectorExpr]: + // + // ctx, span := otel.Tracer("app").Start(...) + // ctx, span = otel.Tracer("app").Start(...) + // var ctx, span = otel.Tracer("app").Start(...) + sType, sStart := isSpanStart(pass.TypesInfo, n) + if !sStart || !isCall(stack[len(stack)-2]) { + return true + } + + stmt := stack[len(stack)-3] + id := getID(stmt) + if id == nil { + pass.ReportRangef(n, "span is unassigned, probable memory leak") + return true + } + + if id.Name == "_" { + pass.ReportRangef(id, "span is unassigned, probable memory leak") + } else if v, ok := pass.TypesInfo.Uses[id].(*types.Var); ok { + // If the span variable is defined outside function scope, + // do not analyze it. + if funcScope.Contains(v.Pos()) { + spanVars[id] = spanVar{ + vr: v, + stmt: stmt, + id: id, + spanType: sType, + } + } + } else if v, ok := pass.TypesInfo.Defs[id].(*types.Var); ok { + spanVars[id] = spanVar{ + vr: v, + stmt: stmt, + id: id, + spanType: sType, + } + } + + return true + }) + + if len(spanVars) == 0 { + return // no need to inspect CFG + } + + // Obtain the CFG. + cfgs := pass.ResultOf[ctrlflow.Analyzer].(*ctrlflow.CFGs) + var g *cfg.CFG + var sig *types.Signature + switch node := node.(type) { + case *ast.FuncDecl: + sig, _ = pass.TypesInfo.Defs[node.Name].Type().(*types.Signature) + g = cfgs.FuncDecl(node) + case *ast.FuncLit: + sig, _ = pass.TypesInfo.Types[node.Type].Type.(*types.Signature) + g = cfgs.FuncLit(node) + } + if sig == nil { + return // missing type information + } + + // Check for missing calls. + for _, sv := range spanVars { + if config.endCheckEnabled { + // Check if there's no End to the span. + if ret := missingSpanCalls(pass, g, sv, "End", func(pass *analysis.Pass, ret *ast.ReturnStmt) *ast.ReturnStmt { return ret }, nil); ret != nil { + pass.ReportRangef(sv.stmt, "%s.End is not called on all paths, possible memory leak", sv.vr.Name()) + pass.ReportRangef(ret, "return can be reached without calling %s.End", sv.vr.Name()) + } + } + + if config.setStatusEnabled { + // Check if there's no SetStatus to the span setting an error. + if ret := missingSpanCalls(pass, g, sv, "SetStatus", returnsErr, config.ignoreChecksSignatures); ret != nil { + pass.ReportRangef(sv.stmt, "%s.SetStatus is not called on all paths", sv.vr.Name()) + pass.ReportRangef(ret, "return can be reached without calling %s.SetStatus", sv.vr.Name()) + } + } + + if config.recordErrorEnabled && sv.spanType == spanOpenTelemetry { // RecordError only exists in OpenTelemetry + // Check if there's no RecordError to the span setting an error. + if ret := missingSpanCalls(pass, g, sv, "RecordError", returnsErr, config.ignoreChecksSignatures); ret != nil { + pass.ReportRangef(sv.stmt, "%s.RecordError is not called on all paths", sv.vr.Name()) + pass.ReportRangef(ret, "return can be reached without calling %s.RecordError", sv.vr.Name()) + } + } + } +} + +// isSpanStart reports whether n is tracer.Start() +func isSpanStart(info *types.Info, n ast.Node) (spanType, bool) { + sel, ok := n.(*ast.SelectorExpr) + if !ok { + return spanUnset, false + } + + switch sel.Sel.Name { + case "Start": // https://github.com/open-telemetry/opentelemetry-go/blob/98b32a6c3a87fbee5d34c063b9096f416b250897/trace/trace.go#L523 + obj, ok := info.Uses[sel.Sel] + return spanOpenTelemetry, ok && obj.Pkg().Path() == "go.opentelemetry.io/otel/trace" + case "StartSpan": // https://pkg.go.dev/go.opencensus.io/trace#StartSpan + obj, ok := info.Uses[sel.Sel] + return spanOpenCensus, ok && obj.Pkg().Path() == "go.opencensus.io/trace" + case "StartSpanWithRemoteParent": // https://github.com/census-instrumentation/opencensus-go/blob/v0.24.0/trace/trace_api.go#L66 + obj, ok := info.Uses[sel.Sel] + return spanOpenCensus, ok && obj.Pkg().Path() == "go.opencensus.io/trace" + default: + return spanUnset, false + } +} + +func isCall(n ast.Node) bool { + _, ok := n.(*ast.CallExpr) + return ok +} + +func getID(node ast.Node) *ast.Ident { + switch stmt := node.(type) { + case *ast.ValueSpec: + if len(stmt.Names) > 1 { + return stmt.Names[1] + } + case *ast.AssignStmt: + if len(stmt.Lhs) > 1 { + id, _ := stmt.Lhs[1].(*ast.Ident) + return id + } + } + return nil +} + +// missingSpanCalls finds a path through the CFG, from stmt (which defines +// the 'span' variable v) to a return statement, that doesn't call the passed selector on the span. +func missingSpanCalls( + pass *analysis.Pass, + g *cfg.CFG, + sv spanVar, + selName string, + checkErr func(pass *analysis.Pass, ret *ast.ReturnStmt) *ast.ReturnStmt, + ignoreCheckSig *regexp.Regexp, +) *ast.ReturnStmt { + // usesCall reports whether stmts contain a use of the selName call on variable v. + usesCall := func(pass *analysis.Pass, stmts []ast.Node) bool { + found, reAssigned := false, false + for _, subStmt := range stmts { + stack := []ast.Node{} + ast.Inspect(subStmt, func(n ast.Node) bool { + switch n := n.(type) { + case *ast.FuncLit: + if len(stack) > 0 { + return false // don't stray into nested functions + } + case *ast.CallExpr: + if ident, ok := n.Fun.(*ast.Ident); ok { + fnSig := pass.TypesInfo.ObjectOf(ident).String() + if ignoreCheckSig != nil && ignoreCheckSig.MatchString(fnSig) { + found = true + return false + } + } + case nil: + stack = stack[:len(stack)-1] // pop + return true + } + stack = append(stack, n) // push + + // Check whether the span was assigned over top of its old value. + _, spanStart := isSpanStart(pass.TypesInfo, n) + if spanStart { + if id := getID(stack[len(stack)-3]); id != nil && id.Obj.Decl == sv.id.Obj.Decl { + reAssigned = true + return false + } + } + + if n, ok := n.(*ast.SelectorExpr); ok { + // Selector (End, SetStatus, RecordError) hit. + if n.Sel.Name == selName { + id, ok := n.X.(*ast.Ident) + found = ok && id.Obj.Decl == sv.id.Obj.Decl + } + + // Check if an ignore signature matches. + fnSig := pass.TypesInfo.ObjectOf(n.Sel).String() + if ignoreCheckSig != nil && ignoreCheckSig.MatchString(fnSig) { + found = true + } + } + + return !found + }) + } + return found && !reAssigned + } + + // blockUses computes "uses" for each block, caching the result. + memo := make(map[*cfg.Block]bool) + blockUses := func(pass *analysis.Pass, b *cfg.Block) bool { + res, ok := memo[b] + if !ok { + res = usesCall(pass, b.Nodes) + memo[b] = res + } + return res + } + + // Find the var's defining block in the CFG, + // plus the rest of the statements of that block. + var defBlock *cfg.Block + var rest []ast.Node +outer: + for _, b := range g.Blocks { + for i, n := range b.Nodes { + if n == sv.stmt { + defBlock = b + rest = b.Nodes[i+1:] + break outer + } + } + } + if defBlock == nil { + log.Default().Print("[ERROR] internal error: can't find defining block for span var") + } + + // Is the call "used" in the remainder of its defining block? + if usesCall(pass, rest) { + return nil + } + + // Does the defining block return without making the call? + if ret := defBlock.Return(); ret != nil { + return checkErr(pass, ret) + } + + // Search the CFG depth-first for a path, from defblock to a + // return block, in which v is never "used". + seen := make(map[*cfg.Block]bool) + var search func(blocks []*cfg.Block) *ast.ReturnStmt + search = func(blocks []*cfg.Block) *ast.ReturnStmt { + for _, b := range blocks { + if seen[b] { + continue + } + seen[b] = true + + // Prune the search if the block uses v. + if blockUses(pass, b) { + continue + } + + // Found path to return statement? + if ret := returnsErr(pass, b.Return()); ret != nil { + return ret // found + } + + // Recur + if ret := returnsErr(pass, search(b.Succs)); ret != nil { + return ret + } + } + return nil + } + + return search(defBlock.Succs) +} + +func returnsErr(pass *analysis.Pass, ret *ast.ReturnStmt) *ast.ReturnStmt { + if ret == nil { + return nil + } + + for _, r := range ret.Results { + if isErrorType(pass.TypesInfo.TypeOf(r)) { + return ret + } + + if r, ok := r.(*ast.CallExpr); ok { + for _, err := range errorsByArg(pass, r) { + if err { + return ret + } + } + } + } + + return nil +} + +// errorsByArg returns a slice s such that +// len(s) == number of return types of call +// s[i] == true iff return type at position i from left is an error type +// +// copied from https://github.com/kisielk/errcheck/blob/master/errcheck/errcheck.go +func errorsByArg(pass *analysis.Pass, call *ast.CallExpr) []bool { + switch t := pass.TypesInfo.Types[call].Type.(type) { + case *types.Named: + // Single return + return []bool{isErrorType(t)} + case *types.Pointer: + // Single return via pointer + return []bool{isErrorType(t)} + case *types.Tuple: + // Multiple returns + s := make([]bool, t.Len()) + for i := 0; i < t.Len(); i++ { + switch et := t.At(i).Type().(type) { + case *types.Named: + // Single return + s[i] = isErrorType(et) + case *types.Pointer: + // Single return via pointer + s[i] = isErrorType(et) + default: + s[i] = false + } + } + return s + } + return []bool{false} +} + +func isErrorType(t types.Type) bool { + return types.Implements(t, errorType) +} diff --git a/vendor/github.com/kisielk/errcheck/errcheck/analyzer.go b/vendor/github.com/kisielk/errcheck/errcheck/analyzer.go index 68593cc9a..82ab6298a 100644 --- a/vendor/github.com/kisielk/errcheck/errcheck/analyzer.go +++ b/vendor/github.com/kisielk/errcheck/errcheck/analyzer.go @@ -31,7 +31,6 @@ func init() { } func runAnalyzer(pass *analysis.Pass) (interface{}, error) { - exclude := map[string]bool{} if !argExcludeOnly { for _, name := range DefaultExcludedSymbols { @@ -65,8 +64,9 @@ func runAnalyzer(pass *analysis.Pass) (interface{}, error) { for _, err := range v.errors { pass.Report(analysis.Diagnostic{ - Pos: pass.Fset.File(f.Pos()).Pos(err.Pos.Offset), - Message: "unchecked error", + Pos: pass.Fset.File(f.Pos()).Pos(err.Pos.Offset), + Message: "unchecked error", + Category: "errcheck", }) } diff --git a/vendor/github.com/kisielk/errcheck/errcheck/errcheck.go b/vendor/github.com/kisielk/errcheck/errcheck/errcheck.go index a5ee3711c..d61d348f7 100644 --- a/vendor/github.com/kisielk/errcheck/errcheck/errcheck.go +++ b/vendor/github.com/kisielk/errcheck/errcheck/errcheck.go @@ -167,7 +167,7 @@ func (c *Checker) LoadPackages(paths ...string) ([]*packages.Package, error) { buildFlags = append(buildFlags, fmt.Sprintf("-mod=%s", c.Mod)) } cfg := &packages.Config{ - Mode: packages.LoadAllSyntax, + Mode: packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo, Tests: !c.Exclusions.TestFiles, BuildFlags: buildFlags, } @@ -205,7 +205,7 @@ func (c *Checker) CheckPackage(pkg *packages.Package) Result { ignore := map[string]*regexp.Regexp{} // Apply SymbolRegexpsByPackage first so that if the same path appears in - // Packages, a more narrow regexp will be superceded by dotStar below. + // Packages, a more narrow regexp will be superseded by dotStar below. if regexps := c.Exclusions.SymbolRegexpsByPackage; regexps != nil { for pkg, re := range regexps { // TODO warn if previous entry overwritten? @@ -337,7 +337,7 @@ func (v *visitor) selectorName(call *ast.CallExpr) string { // names are returned. If the function is package-qualified (like "fmt.Printf()") // then just that function's fullName is returned. // -// Otherwise, we walk through all the potentially embeddded interfaces of the receiver +// Otherwise, we walk through all the potentially embedded interfaces of the receiver // the collect a list of type-qualified function names that we will check. func (v *visitor) namesForExcludeCheck(call *ast.CallExpr) []string { sel, fn, ok := v.selectorAndFunc(call) diff --git a/vendor/github.com/kisielk/errcheck/errcheck/excludes.go b/vendor/github.com/kisielk/errcheck/errcheck/excludes.go index 22db9fe11..a783b5a76 100644 --- a/vendor/github.com/kisielk/errcheck/errcheck/excludes.go +++ b/vendor/github.com/kisielk/errcheck/errcheck/excludes.go @@ -3,64 +3,60 @@ package errcheck import ( "bufio" "bytes" - "io/ioutil" + "os" "strings" ) -var ( - // DefaultExcludedSymbols is a list of symbol names that are usually excluded from checks by default. - // - // Note, that they still need to be explicitly copied to Checker.Exclusions.Symbols - DefaultExcludedSymbols = []string{ - // bytes - "(*bytes.Buffer).Write", - "(*bytes.Buffer).WriteByte", - "(*bytes.Buffer).WriteRune", - "(*bytes.Buffer).WriteString", +// DefaultExcludedSymbols is a list of symbol names that are usually excluded from checks by default. +// +// Note, that they still need to be explicitly copied to Checker.Exclusions.Symbols +var DefaultExcludedSymbols = []string{ + // bytes + "(*bytes.Buffer).Write", + "(*bytes.Buffer).WriteByte", + "(*bytes.Buffer).WriteRune", + "(*bytes.Buffer).WriteString", - // fmt - "fmt.Errorf", - "fmt.Print", - "fmt.Printf", - "fmt.Println", - "fmt.Fprint(*bytes.Buffer)", - "fmt.Fprintf(*bytes.Buffer)", - "fmt.Fprintln(*bytes.Buffer)", - "fmt.Fprint(*strings.Builder)", - "fmt.Fprintf(*strings.Builder)", - "fmt.Fprintln(*strings.Builder)", - "fmt.Fprint(os.Stderr)", - "fmt.Fprintf(os.Stderr)", - "fmt.Fprintln(os.Stderr)", + // fmt + "fmt.Print", + "fmt.Printf", + "fmt.Println", + "fmt.Fprint(*bytes.Buffer)", + "fmt.Fprintf(*bytes.Buffer)", + "fmt.Fprintln(*bytes.Buffer)", + "fmt.Fprint(*strings.Builder)", + "fmt.Fprintf(*strings.Builder)", + "fmt.Fprintln(*strings.Builder)", + "fmt.Fprint(os.Stderr)", + "fmt.Fprintf(os.Stderr)", + "fmt.Fprintln(os.Stderr)", - // io - "(*io.PipeReader).CloseWithError", - "(*io.PipeWriter).CloseWithError", + // io + "(*io.PipeReader).CloseWithError", + "(*io.PipeWriter).CloseWithError", - // math/rand - "math/rand.Read", - "(*math/rand.Rand).Read", + // math/rand + "math/rand.Read", + "(*math/rand.Rand).Read", - // strings - "(*strings.Builder).Write", - "(*strings.Builder).WriteByte", - "(*strings.Builder).WriteRune", - "(*strings.Builder).WriteString", + // strings + "(*strings.Builder).Write", + "(*strings.Builder).WriteByte", + "(*strings.Builder).WriteRune", + "(*strings.Builder).WriteString", - // hash - "(hash.Hash).Write", - } -) + // hash + "(hash.Hash).Write", +} // ReadExcludes reads an excludes file, a newline delimited file that lists // patterns for which to allow unchecked errors. // // Lines that start with two forward slashes are considered comments and are ignored. -// func ReadExcludes(path string) ([]string, error) { var excludes []string - buf, err := ioutil.ReadFile(path) + buf, err := os.ReadFile(path) if err != nil { return nil, err } diff --git a/vendor/github.com/kunwardeep/paralleltest/pkg/paralleltest/paralleltest.go b/vendor/github.com/kunwardeep/paralleltest/pkg/paralleltest/paralleltest.go index e21f278cf..1aecc1a51 100644 --- a/vendor/github.com/kunwardeep/paralleltest/pkg/paralleltest/paralleltest.go +++ b/vendor/github.com/kunwardeep/paralleltest/pkg/paralleltest/paralleltest.go @@ -54,8 +54,10 @@ func (a *parallelAnalyzer) run(pass *analysis.Pass) (interface{}, error) { inspector.Preorder(nodeFilter, func(node ast.Node) { funcDecl := node.(*ast.FuncDecl) var funcHasParallelMethod, + funcCantParallelMethod, rangeStatementOverTestCasesExists, - rangeStatementHasParallelMethod bool + rangeStatementHasParallelMethod, + rangeStatementCantParallelMethod bool var loopVariableUsedInRun *string var numberOfTestRun int var positionOfTestRunNode []ast.Node @@ -77,20 +79,29 @@ func (a *parallelAnalyzer) run(pass *analysis.Pass) (interface{}, error) { funcHasParallelMethod = methodParallelIsCalledInTestFunction(n, testVar) } + // Check if the test calls t.Setenv, cannot be used in parallel tests or tests with parallel ancestors + if !funcCantParallelMethod { + funcCantParallelMethod = methodSetenvIsCalledInTestFunction(n, testVar) + } + // Check if the t.Run within the test function is calling t.Parallel if methodRunIsCalledInTestFunction(n, testVar) { // n is a call to t.Run; find out the name of the subtest's *testing.T parameter. innerTestVar := getRunCallbackParameterName(n) hasParallel := false + cantParallel := false numberOfTestRun++ ast.Inspect(v, func(p ast.Node) bool { if !hasParallel { hasParallel = methodParallelIsCalledInTestFunction(p, innerTestVar) } + if !cantParallel { + cantParallel = methodSetenvIsCalledInTestFunction(p, innerTestVar) + } return true }) - if !hasParallel { + if !hasParallel && !cantParallel { positionOfTestRunNode = append(positionOfTestRunNode, n) } } @@ -122,6 +133,10 @@ func (a *parallelAnalyzer) run(pass *analysis.Pass) (interface{}, error) { rangeStatementHasParallelMethod = methodParallelIsCalledInMethodRun(r.X, innerTestVar) } + if !rangeStatementCantParallelMethod { + rangeStatementCantParallelMethod = methodSetenvIsCalledInMethodRun(r.X, innerTestVar) + } + if loopVariableUsedInRun == nil { if run, ok := r.X.(*ast.CallExpr); ok { loopVariableUsedInRun = loopVarReferencedInRun(run, loopVars, pass.TypesInfo) @@ -134,12 +149,17 @@ func (a *parallelAnalyzer) run(pass *analysis.Pass) (interface{}, error) { } } - if !a.ignoreMissing && !funcHasParallelMethod { + // Descendents which call Setenv, also prevent tests from calling Parallel + if rangeStatementCantParallelMethod { + funcCantParallelMethod = true + } + + if !a.ignoreMissing && !funcHasParallelMethod && !funcCantParallelMethod { pass.Reportf(node.Pos(), "Function %s missing the call to method parallel\n", funcDecl.Name.Name) } if rangeStatementOverTestCasesExists && rangeNode != nil { - if !rangeStatementHasParallelMethod { + if !rangeStatementHasParallelMethod && !rangeStatementCantParallelMethod { if !a.ignoreMissing && !a.ignoreMissingSubtests { pass.Reportf(rangeNode.Pos(), "Range statement for test %s missing the call to method parallel in test Run\n", funcDecl.Name.Name) } @@ -162,15 +182,23 @@ func (a *parallelAnalyzer) run(pass *analysis.Pass) (interface{}, error) { } func methodParallelIsCalledInMethodRun(node ast.Node, testVar string) bool { - var methodParallelCalled bool + return targetMethodIsCalledInMethodRun(node, testVar, "Parallel") +} + +func methodSetenvIsCalledInMethodRun(node ast.Node, testVar string) bool { + return targetMethodIsCalledInMethodRun(node, testVar, "Setenv") +} + +func targetMethodIsCalledInMethodRun(node ast.Node, testVar, targetMethod string) bool { + var called bool // nolint: gocritic switch callExp := node.(type) { case *ast.CallExpr: for _, arg := range callExp.Args { - if !methodParallelCalled { + if !called { ast.Inspect(arg, func(n ast.Node) bool { - if !methodParallelCalled { - methodParallelCalled = methodParallelIsCalledInRunMethod(n, testVar) + if !called { + called = exprCallHasMethod(n, testVar, targetMethod) return true } return false @@ -178,11 +206,7 @@ func methodParallelIsCalledInMethodRun(node ast.Node, testVar string) bool { } } } - return methodParallelCalled -} - -func methodParallelIsCalledInRunMethod(node ast.Node, testVar string) bool { - return exprCallHasMethod(node, testVar, "Parallel") + return called } func methodParallelIsCalledInTestFunction(node ast.Node, testVar string) bool { @@ -196,6 +220,11 @@ func methodRunIsCalledInRangeStatement(node ast.Node, testVar string) bool { func methodRunIsCalledInTestFunction(node ast.Node, testVar string) bool { return exprCallHasMethod(node, testVar, "Run") } + +func methodSetenvIsCalledInTestFunction(node ast.Node, testVar string) bool { + return exprCallHasMethod(node, testVar, "Setenv") +} + func exprCallHasMethod(node ast.Node, receiverName, methodName string) bool { // nolint: gocritic switch n := node.(type) { diff --git a/vendor/github.com/macabu/inamedparam/.gitignore b/vendor/github.com/macabu/inamedparam/.gitignore index 3b735ec4a..f8d51e94c 100644 --- a/vendor/github.com/macabu/inamedparam/.gitignore +++ b/vendor/github.com/macabu/inamedparam/.gitignore @@ -7,6 +7,7 @@ *.dll *.so *.dylib +inamedparam # Test binary, built with `go test -c` *.test diff --git a/vendor/github.com/macabu/inamedparam/.golangci.yml b/vendor/github.com/macabu/inamedparam/.golangci.yml new file mode 100644 index 000000000..f0efa1cb6 --- /dev/null +++ b/vendor/github.com/macabu/inamedparam/.golangci.yml @@ -0,0 +1,33 @@ +run: + deadline: 30s + +linters: + enable-all: true + disable: + - cyclop + - deadcode + - depguard + - exhaustivestruct + - exhaustruct + - forcetypeassert + - gochecknoglobals + - gocognit + - golint + - ifshort + - interfacer + - maligned + - nilnil + - nosnakecase + - paralleltest + - scopelint + - structcheck + - varcheck + +linters-settings: + gci: + sections: + - standard + - default + - prefix(github.com/macabu/inamedparam) + section-separators: + - newLine diff --git a/vendor/github.com/macabu/inamedparam/README.md b/vendor/github.com/macabu/inamedparam/README.md index 80911c9d7..3336cb950 100644 --- a/vendor/github.com/macabu/inamedparam/README.md +++ b/vendor/github.com/macabu/inamedparam/README.md @@ -2,10 +2,16 @@ A linter that reports interfaces with unnamed method parameters. +## Flags/Config +```sh +-skip-single-param + skip interfaces with a single unnamed parameter +``` + ## Usage ### Standalone -You can also run it standalone through `go vet`. +You can run it standalone through `go vet`. You must install the binary to your `$GOBIN` folder like so: ```sh @@ -16,3 +22,17 @@ And then navigate to your Go project's root folder, where can run `go vet` in th ```sh $ go vet -vettool=$(which inamedparam) ./... ``` + +### golangci-lint +`inamedparam` was added as a linter to `golangci-lint` on version `v1.55.0`. It is disabled by default. + +To enable it, you can add it to your `.golangci.yml` file, as such: +```yaml +run: + deadline: 30s + +linters: + disable-all: true + enable: + - inamedparam +``` diff --git a/vendor/github.com/macabu/inamedparam/inamedparam.go b/vendor/github.com/macabu/inamedparam/inamedparam.go index 433e04e5b..8ba7fe188 100644 --- a/vendor/github.com/macabu/inamedparam/inamedparam.go +++ b/vendor/github.com/macabu/inamedparam/inamedparam.go @@ -1,6 +1,7 @@ package inamedparam import ( + "flag" "go/ast" "golang.org/x/tools/go/analysis" @@ -8,15 +9,30 @@ import ( "golang.org/x/tools/go/ast/inspector" ) +const ( + analyzerName = "inamedparam" + + flagSkipSingleParam = "skip-single-param" +) + var Analyzer = &analysis.Analyzer{ - Name: "inamedparam", - Doc: "reports interfaces with unnamed method parameters", - Run: run, + Name: analyzerName, + Doc: "reports interfaces with unnamed method parameters", + Run: run, + Flags: flags(), Requires: []*analysis.Analyzer{ inspect.Analyzer, }, } +func flags() flag.FlagSet { + flags := flag.NewFlagSet(analyzerName, flag.ExitOnError) + + flags.Bool(flagSkipSingleParam, false, "skip interface methods with a single unnamed parameter") + + return *flags +} + func run(pass *analysis.Pass) (interface{}, error) { inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) @@ -24,6 +40,8 @@ func run(pass *analysis.Pass) (interface{}, error) { &ast.InterfaceType{}, } + skipSingleParam := pass.Analyzer.Flags.Lookup(flagSkipSingleParam).Value.(flag.Getter).Get().(bool) + inspect.Preorder(types, func(n ast.Node) { interfaceType, ok := n.(*ast.InterfaceType) if !ok || interfaceType == nil || interfaceType.Methods == nil { @@ -36,8 +54,17 @@ func run(pass *analysis.Pass) (interface{}, error) { continue } + // Improvement: add test case to reproduce this. Help wanted. + if len(method.Names) == 0 { + continue + } + methodName := method.Names[0].Name + if skipSingleParam && len(interfaceFunc.Params.List) == 1 { + continue + } + for _, param := range interfaceFunc.Params.List { if param.Names == nil { var builtParamType string diff --git a/vendor/github.com/mattn/go-isatty/isatty_bsd.go b/vendor/github.com/mattn/go-isatty/isatty_bsd.go index d569c0c94..d0ea68f40 100644 --- a/vendor/github.com/mattn/go-isatty/isatty_bsd.go +++ b/vendor/github.com/mattn/go-isatty/isatty_bsd.go @@ -1,6 +1,7 @@ -//go:build (darwin || freebsd || openbsd || netbsd || dragonfly || hurd) && !appengine +//go:build (darwin || freebsd || openbsd || netbsd || dragonfly || hurd) && !appengine && !tinygo // +build darwin freebsd openbsd netbsd dragonfly hurd // +build !appengine +// +build !tinygo package isatty diff --git a/vendor/github.com/mattn/go-isatty/isatty_others.go b/vendor/github.com/mattn/go-isatty/isatty_others.go index 31503226f..7402e0618 100644 --- a/vendor/github.com/mattn/go-isatty/isatty_others.go +++ b/vendor/github.com/mattn/go-isatty/isatty_others.go @@ -1,5 +1,6 @@ -//go:build appengine || js || nacl || wasm -// +build appengine js nacl wasm +//go:build (appengine || js || nacl || tinygo || wasm) && !windows +// +build appengine js nacl tinygo wasm +// +build !windows package isatty diff --git a/vendor/github.com/mattn/go-isatty/isatty_tcgets.go b/vendor/github.com/mattn/go-isatty/isatty_tcgets.go index 67787657f..0337d8cf6 100644 --- a/vendor/github.com/mattn/go-isatty/isatty_tcgets.go +++ b/vendor/github.com/mattn/go-isatty/isatty_tcgets.go @@ -1,6 +1,7 @@ -//go:build (linux || aix || zos) && !appengine +//go:build (linux || aix || zos) && !appengine && !tinygo // +build linux aix zos // +build !appengine +// +build !tinygo package isatty diff --git a/vendor/github.com/mgechev/revive/config/config.go b/vendor/github.com/mgechev/revive/config/config.go index abd554a9f..50a2b8966 100644 --- a/vendor/github.com/mgechev/revive/config/config.go +++ b/vendor/github.com/mgechev/revive/config/config.go @@ -1,3 +1,4 @@ +// Package config implements revive's configuration data structures and related methods package config import ( @@ -5,9 +6,9 @@ import ( "fmt" "os" - "github.com/mgechev/revive/formatter" - "github.com/BurntSushi/toml" + + "github.com/mgechev/revive/formatter" "github.com/mgechev/revive/lint" "github.com/mgechev/revive/rule" ) @@ -54,7 +55,7 @@ var allRules = append([]lint.Rule{ &rule.ModifiesValRecRule{}, &rule.ConstantLogicalExprRule{}, &rule.BoolLiteralRule{}, - &rule.ImportsBlacklistRule{}, + &rule.ImportsBlocklistRule{}, &rule.FunctionResultsLimitRule{}, &rule.MaxPublicStructsRule{}, &rule.RangeValInClosureRule{}, @@ -91,6 +92,9 @@ var allRules = append([]lint.Rule{ &rule.RedundantImportAlias{}, &rule.ImportAliasNamingRule{}, &rule.EnforceMapStyleRule{}, + &rule.EnforceRepeatedArgTypeStyleRule{}, + &rule.EnforceSliceStyleRule{}, + &rule.MaxControlNestingRule{}, }, defaultRules...) var allFormatters = []lint.Formatter{ @@ -128,7 +132,8 @@ func GetLintingRules(config *lint.Config, extraRules []lint.Rule) ([]lint.Rule, var lintingRules []lint.Rule for name, ruleConfig := range config.Rules { - r, ok := rulesMap[name] + actualName := actualRuleName(name) + r, ok := rulesMap[actualName] if !ok { return nil, fmt.Errorf("cannot find rule: %s", name) } @@ -143,6 +148,15 @@ func GetLintingRules(config *lint.Config, extraRules []lint.Rule) ([]lint.Rule, return lintingRules, nil } +func actualRuleName(name string) string { + switch name { + case "imports-blacklist": + return "imports-blocklist" + default: + return name + } +} + func parseConfig(path string, config *lint.Config) error { file, err := os.ReadFile(path) if err != nil { diff --git a/vendor/github.com/mgechev/revive/formatter/checkstyle.go b/vendor/github.com/mgechev/revive/formatter/checkstyle.go index 33a3b2ca1..f45b63c92 100644 --- a/vendor/github.com/mgechev/revive/formatter/checkstyle.go +++ b/vendor/github.com/mgechev/revive/formatter/checkstyle.go @@ -3,7 +3,7 @@ package formatter import ( "bytes" "encoding/xml" - plainTemplate "text/template" + plain "text/template" "github.com/mgechev/revive/lint" ) @@ -50,7 +50,7 @@ func (*Checkstyle) Format(failures <-chan lint.Failure, config lint.Config) (str issues[fn] = append(issues[fn], iss) } - t, err := plainTemplate.New("revive").Parse(checkstyleTemplate) + t, err := plain.New("revive").Parse(checkstyleTemplate) if err != nil { return "", err } diff --git a/vendor/github.com/mgechev/revive/formatter/default.go b/vendor/github.com/mgechev/revive/formatter/default.go index f76a7b29a..2d5a04434 100644 --- a/vendor/github.com/mgechev/revive/formatter/default.go +++ b/vendor/github.com/mgechev/revive/formatter/default.go @@ -1,6 +1,7 @@ package formatter import ( + "bytes" "fmt" "github.com/mgechev/revive/lint" @@ -19,8 +20,9 @@ func (*Default) Name() string { // Format formats the failures gotten from the lint. func (*Default) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) { + var buf bytes.Buffer for failure := range failures { - fmt.Printf("%v: %s\n", failure.Position.Start, failure.Failure) + fmt.Fprintf(&buf, "%v: %s\n", failure.Position.Start, failure.Failure) } - return "", nil + return buf.String(), nil } diff --git a/vendor/github.com/mgechev/revive/formatter/doc.go b/vendor/github.com/mgechev/revive/formatter/doc.go new file mode 100644 index 000000000..bb89f20ea --- /dev/null +++ b/vendor/github.com/mgechev/revive/formatter/doc.go @@ -0,0 +1,2 @@ +// Package formatter implements the linter output formatters. +package formatter diff --git a/vendor/github.com/mgechev/revive/formatter/friendly.go b/vendor/github.com/mgechev/revive/formatter/friendly.go index ced8fa46c..5ff329a23 100644 --- a/vendor/github.com/mgechev/revive/formatter/friendly.go +++ b/vendor/github.com/mgechev/revive/formatter/friendly.go @@ -3,6 +3,7 @@ package formatter import ( "bytes" "fmt" + "io" "sort" "github.com/fatih/color" @@ -31,13 +32,14 @@ func (*Friendly) Name() string { // Format formats the failures gotten from the lint. func (f *Friendly) Format(failures <-chan lint.Failure, config lint.Config) (string, error) { + var buf bytes.Buffer errorMap := map[string]int{} warningMap := map[string]int{} totalErrors := 0 totalWarnings := 0 for failure := range failures { sev := severity(config, failure) - f.printFriendlyFailure(failure, sev) + f.printFriendlyFailure(&buf, failure, sev) if sev == lint.SeverityWarning { warningMap[failure.RuleName]++ totalWarnings++ @@ -47,29 +49,29 @@ func (f *Friendly) Format(failures <-chan lint.Failure, config lint.Config) (str totalErrors++ } } - f.printSummary(totalErrors, totalWarnings) - f.printStatistics(color.RedString("Errors:"), errorMap) - f.printStatistics(color.YellowString("Warnings:"), warningMap) - return "", nil + f.printSummary(&buf, totalErrors, totalWarnings) + f.printStatistics(&buf, color.RedString("Errors:"), errorMap) + f.printStatistics(&buf, color.YellowString("Warnings:"), warningMap) + return buf.String(), nil } -func (f *Friendly) printFriendlyFailure(failure lint.Failure, severity lint.Severity) { - f.printHeaderRow(failure, severity) - f.printFilePosition(failure) - fmt.Println() - fmt.Println() +func (f *Friendly) printFriendlyFailure(w io.Writer, failure lint.Failure, severity lint.Severity) { + f.printHeaderRow(w, failure, severity) + f.printFilePosition(w, failure) + fmt.Fprintln(w) + fmt.Fprintln(w) } -func (f *Friendly) printHeaderRow(failure lint.Failure, severity lint.Severity) { +func (f *Friendly) printHeaderRow(w io.Writer, failure lint.Failure, severity lint.Severity) { emoji := getWarningEmoji() if severity == lint.SeverityError { emoji = getErrorEmoji() } - fmt.Print(f.table([][]string{{emoji, "https://revive.run/r#" + failure.RuleName, color.GreenString(failure.Failure)}})) + fmt.Fprint(w, f.table([][]string{{emoji, "https://revive.run/r#" + failure.RuleName, color.GreenString(failure.Failure)}})) } -func (*Friendly) printFilePosition(failure lint.Failure) { - fmt.Printf(" %s:%d:%d", failure.GetFilename(), failure.Position.Start.Line, failure.Position.Start.Column) +func (*Friendly) printFilePosition(w io.Writer, failure lint.Failure) { + fmt.Fprintf(w, " %s:%d:%d", failure.GetFilename(), failure.Position.Start.Line, failure.Position.Start.Column) } type statEntry struct { @@ -77,7 +79,7 @@ type statEntry struct { failures int } -func (*Friendly) printSummary(errors, warnings int) { +func (*Friendly) printSummary(w io.Writer, errors, warnings int) { emoji := getWarningEmoji() if errors > 0 { emoji = getErrorEmoji() @@ -96,18 +98,18 @@ func (*Friendly) printSummary(errors, warnings int) { } str := fmt.Sprintf("%d %s (%d %s, %d %s)", errors+warnings, problemsLabel, errors, errorsLabel, warnings, warningsLabel) if errors > 0 { - fmt.Printf("%s %s\n", emoji, color.RedString(str)) - fmt.Println() + fmt.Fprintf(w, "%s %s\n", emoji, color.RedString(str)) + fmt.Fprintln(w) return } if warnings > 0 { - fmt.Printf("%s %s\n", emoji, color.YellowString(str)) - fmt.Println() + fmt.Fprintf(w, "%s %s\n", emoji, color.YellowString(str)) + fmt.Fprintln(w) return } } -func (f *Friendly) printStatistics(header string, stats map[string]int) { +func (f *Friendly) printStatistics(w io.Writer, header string, stats map[string]int) { if len(stats) == 0 { return } @@ -122,8 +124,8 @@ func (f *Friendly) printStatistics(header string, stats map[string]int) { for _, entry := range data { formatted = append(formatted, []string{color.GreenString(fmt.Sprintf("%d", entry.failures)), entry.name}) } - fmt.Println(header) - fmt.Println(f.table(formatted)) + fmt.Fprintln(w, header) + fmt.Fprintln(w, f.table(formatted)) } func (*Friendly) table(rows [][]string) string { diff --git a/vendor/github.com/mgechev/revive/formatter/ndjson.go b/vendor/github.com/mgechev/revive/formatter/ndjson.go index a02d9c80f..58b35dc44 100644 --- a/vendor/github.com/mgechev/revive/formatter/ndjson.go +++ b/vendor/github.com/mgechev/revive/formatter/ndjson.go @@ -1,8 +1,8 @@ package formatter import ( + "bytes" "encoding/json" - "os" "github.com/mgechev/revive/lint" ) @@ -20,7 +20,8 @@ func (*NDJSON) Name() string { // Format formats the failures gotten from the lint. func (*NDJSON) Format(failures <-chan lint.Failure, config lint.Config) (string, error) { - enc := json.NewEncoder(os.Stdout) + var buf bytes.Buffer + enc := json.NewEncoder(&buf) for failure := range failures { obj := jsonObject{} obj.Severity = severity(config, failure) @@ -30,5 +31,5 @@ func (*NDJSON) Format(failures <-chan lint.Failure, config lint.Config) (string, return "", err } } - return "", nil + return buf.String(), nil } diff --git a/vendor/github.com/mgechev/revive/formatter/plain.go b/vendor/github.com/mgechev/revive/formatter/plain.go index 6e083bcfd..09ebf6cdc 100644 --- a/vendor/github.com/mgechev/revive/formatter/plain.go +++ b/vendor/github.com/mgechev/revive/formatter/plain.go @@ -1,6 +1,7 @@ package formatter import ( + "bytes" "fmt" "github.com/mgechev/revive/lint" @@ -19,8 +20,9 @@ func (*Plain) Name() string { // Format formats the failures gotten from the lint. func (*Plain) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) { + var buf bytes.Buffer for failure := range failures { - fmt.Printf("%v: %s %s\n", failure.Position.Start, failure.Failure, "https://revive.run/r#"+failure.RuleName) + fmt.Fprintf(&buf, "%v: %s %s\n", failure.Position.Start, failure.Failure, "https://revive.run/r#"+failure.RuleName) } - return "", nil + return buf.String(), nil } diff --git a/vendor/github.com/mgechev/revive/formatter/unix.go b/vendor/github.com/mgechev/revive/formatter/unix.go index ef2f1613a..e46f3c275 100644 --- a/vendor/github.com/mgechev/revive/formatter/unix.go +++ b/vendor/github.com/mgechev/revive/formatter/unix.go @@ -1,6 +1,7 @@ package formatter import ( + "bytes" "fmt" "github.com/mgechev/revive/lint" @@ -8,7 +9,8 @@ import ( // Unix is an implementation of the Formatter interface // which formats the errors to a simple line based error format -// main.go:24:9: [errorf] should replace errors.New(fmt.Sprintf(...)) with fmt.Errorf(...) +// +// main.go:24:9: [errorf] should replace errors.New(fmt.Sprintf(...)) with fmt.Errorf(...) type Unix struct { Metadata lint.FormatterMetadata } @@ -20,8 +22,9 @@ func (*Unix) Name() string { // Format formats the failures gotten from the lint. func (*Unix) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) { + var buf bytes.Buffer for failure := range failures { - fmt.Printf("%v: [%s] %s\n", failure.Position.Start, failure.RuleName, failure.Failure) + fmt.Fprintf(&buf, "%v: [%s] %s\n", failure.Position.Start, failure.RuleName, failure.Failure) } - return "", nil + return buf.String(), nil } diff --git a/vendor/github.com/mgechev/revive/lint/config.go b/vendor/github.com/mgechev/revive/lint/config.go index 9b26d5841..7e51a93c2 100644 --- a/vendor/github.com/mgechev/revive/lint/config.go +++ b/vendor/github.com/mgechev/revive/lint/config.go @@ -3,6 +3,7 @@ package lint // Arguments is type used for the arguments of a rule. type Arguments = []interface{} +// FileFilters is type used for modeling file filters to apply to rules. type FileFilters = []*FileFilter // RuleConfig is type used for the rule configuration. @@ -32,8 +33,8 @@ func (rc *RuleConfig) Initialize() error { type RulesConfig = map[string]RuleConfig // MustExclude - checks if given filename `name` must be excluded -func (rcfg *RuleConfig) MustExclude(name string) bool { - for _, exclude := range rcfg.excludeFilters { +func (rc *RuleConfig) MustExclude(name string) bool { + for _, exclude := range rc.excludeFilters { if exclude.MatchFileName(name) { return true } diff --git a/vendor/github.com/mgechev/revive/lint/doc.go b/vendor/github.com/mgechev/revive/lint/doc.go new file mode 100644 index 000000000..7048adf4b --- /dev/null +++ b/vendor/github.com/mgechev/revive/lint/doc.go @@ -0,0 +1,2 @@ +// Package lint implements the linting machinery. +package lint diff --git a/vendor/github.com/mgechev/revive/lint/utils.go b/vendor/github.com/mgechev/revive/lint/utils.go index 28657c6df..6ccfb0ef2 100644 --- a/vendor/github.com/mgechev/revive/lint/utils.go +++ b/vendor/github.com/mgechev/revive/lint/utils.go @@ -6,7 +6,7 @@ import ( ) // Name returns a different name if it should be different. -func Name(name string, whitelist, blacklist []string) (should string) { +func Name(name string, allowlist, blocklist []string) (should string) { // Fast path for simple cases: "_" and all lowercase. if name == "_" { return name @@ -57,12 +57,12 @@ func Name(name string, whitelist, blacklist []string) (should string) { // [w,i) is a word. word := string(runes[w:i]) ignoreInitWarnings := map[string]bool{} - for _, i := range whitelist { + for _, i := range allowlist { ignoreInitWarnings[i] = true } extraInits := map[string]bool{} - for _, i := range blacklist { + for _, i := range blocklist { extraInits[i] = true } @@ -71,6 +71,10 @@ func Name(name string, whitelist, blacklist []string) (should string) { if w == 0 && unicode.IsLower(runes[w]) { u = strings.ToLower(u) } + // Keep lowercase s for IDs + if u == "IDS" { + u = "IDs" + } // All the common initialisms are ASCII, // so we can replace the bytes exactly. copy(runes[w:], []rune(u)) @@ -99,6 +103,7 @@ var commonInitialisms = map[string]bool{ "HTTP": true, "HTTPS": true, "ID": true, + "IDS": true, "IP": true, "JSON": true, "LHS": true, diff --git a/vendor/github.com/mgechev/revive/rule/add-constant.go b/vendor/github.com/mgechev/revive/rule/add-constant.go index 36a7003da..86182623a 100644 --- a/vendor/github.com/mgechev/revive/rule/add-constant.go +++ b/vendor/github.com/mgechev/revive/rule/add-constant.go @@ -18,13 +18,13 @@ const ( kindSTRING = "STRING" ) -type whiteList map[string]map[string]bool +type allowList map[string]map[string]bool -func newWhiteList() whiteList { +func newAllowList() allowList { return map[string]map[string]bool{kindINT: {}, kindFLOAT: {}, kindSTRING: {}} } -func (wl whiteList) add(kind, list string) { +func (wl allowList) add(kind, list string) { elems := strings.Split(list, ",") for _, e := range elems { wl[kind][e] = true @@ -33,7 +33,7 @@ func (wl whiteList) add(kind, list string) { // AddConstantRule lints unused params in functions. type AddConstantRule struct { - whiteList whiteList + allowList allowList ignoreFunctions []*regexp.Regexp strLitLimit int sync.Mutex @@ -49,12 +49,13 @@ func (r *AddConstantRule) Apply(file *lint.File, arguments lint.Arguments) []lin failures = append(failures, failure) } - w := lintAddConstantRule{ + w := &lintAddConstantRule{ onFailure: onFailure, strLits: make(map[string]int), strLitLimit: r.strLitLimit, - whiteLst: r.whiteList, + allowList: r.allowList, ignoreFunctions: r.ignoreFunctions, + structTags: make(map[*ast.BasicLit]struct{}), } ast.Walk(w, file.AST) @@ -71,11 +72,16 @@ type lintAddConstantRule struct { onFailure func(lint.Failure) strLits map[string]int strLitLimit int - whiteLst whiteList + allowList allowList ignoreFunctions []*regexp.Regexp + structTags map[*ast.BasicLit]struct{} } -func (w lintAddConstantRule) Visit(node ast.Node) ast.Visitor { +func (w *lintAddConstantRule) Visit(node ast.Node) ast.Visitor { + if node == nil { + return nil + } + switch n := node.(type) { case *ast.CallExpr: w.checkFunc(n) @@ -83,13 +89,23 @@ func (w lintAddConstantRule) Visit(node ast.Node) ast.Visitor { case *ast.GenDecl: return nil // skip declarations case *ast.BasicLit: - w.checkLit(n) + if !w.isStructTag(n) { + w.checkLit(n) + } + case *ast.StructType: + if n.Fields != nil { + for _, field := range n.Fields.List { + if field.Tag != nil { + w.structTags[field.Tag] = struct{}{} + } + } + } } return w } -func (w lintAddConstantRule) checkFunc(expr *ast.CallExpr) { +func (w *lintAddConstantRule) checkFunc(expr *ast.CallExpr) { fName := w.getFuncName(expr) for _, arg := range expr.Args { @@ -105,7 +121,7 @@ func (w lintAddConstantRule) checkFunc(expr *ast.CallExpr) { } } -func (w lintAddConstantRule) getFuncName(expr *ast.CallExpr) string { +func (*lintAddConstantRule) getFuncName(expr *ast.CallExpr) string { switch f := expr.Fun.(type) { case *ast.SelectorExpr: switch prefix := f.X.(type) { @@ -119,7 +135,7 @@ func (w lintAddConstantRule) getFuncName(expr *ast.CallExpr) string { return "" } -func (w lintAddConstantRule) checkLit(n *ast.BasicLit) { +func (w *lintAddConstantRule) checkLit(n *ast.BasicLit) { switch kind := n.Kind.String(); kind { case kindFLOAT, kindINT: w.checkNumLit(kind, n) @@ -128,7 +144,7 @@ func (w lintAddConstantRule) checkLit(n *ast.BasicLit) { } } -func (w lintAddConstantRule) isIgnoredFunc(fName string) bool { +func (w *lintAddConstantRule) isIgnoredFunc(fName string) bool { for _, pattern := range w.ignoreFunctions { if pattern.MatchString(fName) { return true @@ -138,8 +154,8 @@ func (w lintAddConstantRule) isIgnoredFunc(fName string) bool { return false } -func (w lintAddConstantRule) checkStrLit(n *ast.BasicLit) { - if w.whiteLst[kindSTRING][n.Value] { +func (w *lintAddConstantRule) checkStrLit(n *ast.BasicLit) { + if w.allowList[kindSTRING][n.Value] { return } @@ -158,8 +174,8 @@ func (w lintAddConstantRule) checkStrLit(n *ast.BasicLit) { } } -func (w lintAddConstantRule) checkNumLit(kind string, n *ast.BasicLit) { - if w.whiteLst[kind][n.Value] { +func (w *lintAddConstantRule) checkNumLit(kind string, n *ast.BasicLit) { + if w.allowList[kind][n.Value] { return } @@ -171,15 +187,20 @@ func (w lintAddConstantRule) checkNumLit(kind string, n *ast.BasicLit) { }) } +func (w *lintAddConstantRule) isStructTag(n *ast.BasicLit) bool { + _, ok := w.structTags[n] + return ok +} + func (r *AddConstantRule) configure(arguments lint.Arguments) { r.Lock() defer r.Unlock() - if r.whiteList == nil { + if r.allowList == nil { r.strLitLimit = defaultStrLitLimit - r.whiteList = newWhiteList() + r.allowList = newAllowList() if len(arguments) > 0 { - args, ok := arguments[0].(map[string]interface{}) + args, ok := arguments[0].(map[string]any) if !ok { panic(fmt.Sprintf("Invalid argument to the add-constant rule. Expecting a k,v map, got %T", arguments[0])) } @@ -202,7 +223,7 @@ func (r *AddConstantRule) configure(arguments lint.Arguments) { if !ok { panic(fmt.Sprintf("Invalid argument to the add-constant rule, string expected. Got '%v' (%T)", v, v)) } - r.whiteList.add(kind, list) + r.allowList.add(kind, list) case "maxLitCount": sl, ok := v.(string) if !ok { diff --git a/vendor/github.com/mgechev/revive/rule/comment-spacings.go b/vendor/github.com/mgechev/revive/rule/comment-spacings.go index 0d75c55f3..2b8240ca5 100644 --- a/vendor/github.com/mgechev/revive/rule/comment-spacings.go +++ b/vendor/github.com/mgechev/revive/rule/comment-spacings.go @@ -8,7 +8,7 @@ import ( "github.com/mgechev/revive/lint" ) -// CommentSpacings Rule check the whether there is a space between +// CommentSpacingsRule check the whether there is a space between // the comment symbol( // ) and the start of the comment text type CommentSpacingsRule struct { allowList []string @@ -36,6 +36,7 @@ func (r *CommentSpacingsRule) configure(arguments lint.Arguments) { } } +// Apply the rule. func (r *CommentSpacingsRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure { r.configure(args) @@ -74,6 +75,7 @@ func (r *CommentSpacingsRule) Apply(file *lint.File, args lint.Arguments) []lint return failures } +// Name yields this rule name. func (*CommentSpacingsRule) Name() string { return "comment-spacings" } diff --git a/vendor/github.com/mgechev/revive/rule/confusing-naming.go b/vendor/github.com/mgechev/revive/rule/confusing-naming.go index 8b1c3eac4..febfd8824 100644 --- a/vendor/github.com/mgechev/revive/rule/confusing-naming.go +++ b/vendor/github.com/mgechev/revive/rule/confusing-naming.go @@ -71,7 +71,6 @@ func (*ConfusingNamingRule) Name() string { // checkMethodName checks if a given method/function name is similar (just case differences) to other method/function of the same struct/file. func checkMethodName(holder string, id *ast.Ident, w *lintConfusingNames) { - if id.Name == "init" && holder == defaultStructName { // ignore init functions return @@ -112,7 +111,7 @@ func checkMethodName(holder string, id *ast.Ident, w *lintConfusingNames) { pkgm.methods[holder] = make(map[string]*referenceMethod, 1) } - // update the black list + // update the block list if pkgm.methods[holder] == nil { println("no entry for '", holder, "'") } diff --git a/vendor/github.com/mgechev/revive/rule/constant-logical-expr.go b/vendor/github.com/mgechev/revive/rule/constant-logical-expr.go index 9abc95d67..36cd641f7 100644 --- a/vendor/github.com/mgechev/revive/rule/constant-logical-expr.go +++ b/vendor/github.com/mgechev/revive/rule/constant-logical-expr.go @@ -11,7 +11,7 @@ import ( type ConstantLogicalExprRule struct{} // Apply applies the rule to given file. -func (r *ConstantLogicalExprRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { +func (*ConstantLogicalExprRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { var failures []lint.Failure onFailure := func(failure lint.Failure) { @@ -63,7 +63,7 @@ func (w *lintConstantLogicalExpr) Visit(node ast.Node) ast.Visitor { return w } -func (w *lintConstantLogicalExpr) isOperatorWithLogicalResult(t token.Token) bool { +func (*lintConstantLogicalExpr) isOperatorWithLogicalResult(t token.Token) bool { switch t { case token.LAND, token.LOR, token.EQL, token.LSS, token.GTR, token.NEQ, token.LEQ, token.GEQ: return true @@ -72,7 +72,7 @@ func (w *lintConstantLogicalExpr) isOperatorWithLogicalResult(t token.Token) boo return false } -func (w *lintConstantLogicalExpr) isEqualityOperator(t token.Token) bool { +func (*lintConstantLogicalExpr) isEqualityOperator(t token.Token) bool { switch t { case token.EQL, token.LEQ, token.GEQ: return true @@ -81,7 +81,7 @@ func (w *lintConstantLogicalExpr) isEqualityOperator(t token.Token) bool { return false } -func (w *lintConstantLogicalExpr) isInequalityOperator(t token.Token) bool { +func (*lintConstantLogicalExpr) isInequalityOperator(t token.Token) bool { switch t { case token.LSS, token.GTR, token.NEQ: return true diff --git a/vendor/github.com/mgechev/revive/rule/context-as-argument.go b/vendor/github.com/mgechev/revive/rule/context-as-argument.go index 3c400065e..e0c8cfa5e 100644 --- a/vendor/github.com/mgechev/revive/rule/context-as-argument.go +++ b/vendor/github.com/mgechev/revive/rule/context-as-argument.go @@ -82,7 +82,7 @@ func (w lintContextArguments) Visit(n ast.Node) ast.Visitor { func getAllowTypesFromArguments(args lint.Arguments) map[string]struct{} { allowTypesBefore := []string{} if len(args) >= 1 { - argKV, ok := args[0].(map[string]interface{}) + argKV, ok := args[0].(map[string]any) if !ok { panic(fmt.Sprintf("Invalid argument to the context-as-argument rule. Expecting a k,v map, got %T", args[0])) } diff --git a/vendor/github.com/mgechev/revive/rule/datarace.go b/vendor/github.com/mgechev/revive/rule/datarace.go index 26fcadcdc..39e96696a 100644 --- a/vendor/github.com/mgechev/revive/rule/datarace.go +++ b/vendor/github.com/mgechev/revive/rule/datarace.go @@ -53,7 +53,7 @@ func (w lintDataRaces) Visit(n ast.Node) ast.Visitor { return nil } -func (w lintDataRaces) ExtractReturnIDs(fields []*ast.Field) map[*ast.Object]struct{} { +func (lintDataRaces) ExtractReturnIDs(fields []*ast.Field) map[*ast.Object]struct{} { r := map[*ast.Object]struct{}{} for _, f := range fields { for _, id := range f.Names { @@ -111,7 +111,7 @@ func (w lintFunctionForDataRaces) Visit(node ast.Node) ast.Visitor { return ok } - ids := pick(funcLit.Body, selectIDs, nil) + ids := pick(funcLit.Body, selectIDs) for _, id := range ids { id := id.(*ast.Ident) _, isRangeID := w.rangeIDs[id.Obj] diff --git a/vendor/github.com/mgechev/revive/rule/defer.go b/vendor/github.com/mgechev/revive/rule/defer.go index f3ea17920..adc6478ae 100644 --- a/vendor/github.com/mgechev/revive/rule/defer.go +++ b/vendor/github.com/mgechev/revive/rule/defer.go @@ -56,7 +56,7 @@ func (*DeferRule) allowFromArgs(args lint.Arguments) map[string]bool { return allow } - aa, ok := args[0].([]interface{}) + aa, ok := args[0].([]any) if !ok { panic(fmt.Sprintf("Invalid argument '%v' for 'defer' rule. Expecting []string, got %T", args[0], args[0])) } @@ -144,8 +144,8 @@ func (w lintDeferRule) Visit(node ast.Node) ast.Visitor { w.newFailure("be careful when deferring calls to methods without pointer receiver", fn, 0.8, "bad practice", "method-call") } } - } + return nil } diff --git a/vendor/github.com/mgechev/revive/rule/doc.go b/vendor/github.com/mgechev/revive/rule/doc.go new file mode 100644 index 000000000..55bf6caa6 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/doc.go @@ -0,0 +1,2 @@ +// Package rule implements revive's linting rules. +package rule diff --git a/vendor/github.com/mgechev/revive/rule/dot-imports.go b/vendor/github.com/mgechev/revive/rule/dot-imports.go index db78f1957..6b877677d 100644 --- a/vendor/github.com/mgechev/revive/rule/dot-imports.go +++ b/vendor/github.com/mgechev/revive/rule/dot-imports.go @@ -1,16 +1,23 @@ package rule import ( + "fmt" "go/ast" + "sync" "github.com/mgechev/revive/lint" ) // DotImportsRule lints given else constructs. -type DotImportsRule struct{} +type DotImportsRule struct { + sync.Mutex + allowedPackages allowPackages +} // Apply applies the rule to given file. -func (*DotImportsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { +func (r *DotImportsRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + r.configure(arguments) + var failures []lint.Failure fileAst := file.AST @@ -20,6 +27,7 @@ func (*DotImportsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { onFailure: func(failure lint.Failure) { failures = append(failures, failure) }, + allowPackages: r.allowedPackages, } ast.Walk(walker, fileAst) @@ -32,15 +40,49 @@ func (*DotImportsRule) Name() string { return "dot-imports" } +func (r *DotImportsRule) configure(arguments lint.Arguments) { + r.Lock() + defer r.Unlock() + + if r.allowedPackages != nil { + return + } + + r.allowedPackages = make(allowPackages) + if len(arguments) == 0 { + return + } + + args, ok := arguments[0].(map[string]any) + if !ok { + panic(fmt.Sprintf("Invalid argument to the dot-imports rule. Expecting a k,v map, got %T", arguments[0])) + } + + if allowedPkgArg, ok := args["allowedPackages"]; ok { + if pkgs, ok := allowedPkgArg.([]any); ok { + for _, p := range pkgs { + if pkg, ok := p.(string); ok { + r.allowedPackages.add(pkg) + } else { + panic(fmt.Sprintf("Invalid argument to the dot-imports rule, string expected. Got '%v' (%T)", p, p)) + } + } + } else { + panic(fmt.Sprintf("Invalid argument to the dot-imports rule, []string expected. Got '%v' (%T)", allowedPkgArg, allowedPkgArg)) + } + } +} + type lintImports struct { - file *lint.File - fileAst *ast.File - onFailure func(lint.Failure) + file *lint.File + fileAst *ast.File + onFailure func(lint.Failure) + allowPackages allowPackages } func (w lintImports) Visit(_ ast.Node) ast.Visitor { for _, is := range w.fileAst.Imports { - if is.Name != nil && is.Name.Name == "." { + if is.Name != nil && is.Name.Name == "." && !w.allowPackages.isAllowedPackage(is.Path.Value) { w.onFailure(lint.Failure{ Confidence: 1, Failure: "should not use dot imports", @@ -51,3 +93,14 @@ func (w lintImports) Visit(_ ast.Node) ast.Visitor { } return nil } + +type allowPackages map[string]struct{} + +func (ap allowPackages) add(pkg string) { + ap[fmt.Sprintf(`"%s"`, pkg)] = struct{}{} // import path strings are with double quotes +} + +func (ap allowPackages) isAllowedPackage(pkg string) bool { + _, allowed := ap[pkg] + return allowed +} diff --git a/vendor/github.com/mgechev/revive/rule/early-return.go b/vendor/github.com/mgechev/revive/rule/early-return.go index 90a0acc55..9c04a1dbe 100644 --- a/vendor/github.com/mgechev/revive/rule/early-return.go +++ b/vendor/github.com/mgechev/revive/rule/early-return.go @@ -22,7 +22,7 @@ func (*EarlyReturnRule) Name() string { } // CheckIfElse evaluates the rule against an ifelse.Chain. -func (e *EarlyReturnRule) CheckIfElse(chain ifelse.Chain, args ifelse.Args) (failMsg string) { +func (*EarlyReturnRule) CheckIfElse(chain ifelse.Chain, args ifelse.Args) (failMsg string) { if !chain.Else.Deviates() { // this rule only applies if the else-block deviates control flow return diff --git a/vendor/github.com/mgechev/revive/rule/enforce-map-style.go b/vendor/github.com/mgechev/revive/rule/enforce-map-style.go index ae27b654f..36ac2374c 100644 --- a/vendor/github.com/mgechev/revive/rule/enforce-map-style.go +++ b/vendor/github.com/mgechev/revive/rule/enforce-map-style.go @@ -47,7 +47,7 @@ type EnforceMapStyleRule struct { func (r *EnforceMapStyleRule) configure(arguments lint.Arguments) { r.Lock() defer r.Unlock() - + if r.configured { return } @@ -58,7 +58,7 @@ func (r *EnforceMapStyleRule) configure(arguments lint.Arguments) { return } - enforceMapStyle, ok := arguments[0].(string) + enforceMapStyle, ok := arguments[0].(string) if !ok { panic(fmt.Sprintf("Invalid argument '%v' for 'enforce-map-style' rule. Expecting string, got %T", arguments[0], arguments[0])) } @@ -141,7 +141,7 @@ func (r *EnforceMapStyleRule) Apply(file *lint.File, arguments lint.Arguments) [ } // Name returns the rule name. -func (r *EnforceMapStyleRule) Name() string { +func (*EnforceMapStyleRule) Name() string { return "enforce-map-style" } diff --git a/vendor/github.com/mgechev/revive/rule/enforce-repeated-arg-type-style.go b/vendor/github.com/mgechev/revive/rule/enforce-repeated-arg-type-style.go new file mode 100644 index 000000000..067082b1b --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/enforce-repeated-arg-type-style.go @@ -0,0 +1,191 @@ +package rule + +import ( + "fmt" + "go/ast" + "go/types" + "sync" + + "github.com/mgechev/revive/lint" +) + +type enforceRepeatedArgTypeStyleType string + +const ( + enforceRepeatedArgTypeStyleTypeAny enforceRepeatedArgTypeStyleType = "any" + enforceRepeatedArgTypeStyleTypeShort enforceRepeatedArgTypeStyleType = "short" + enforceRepeatedArgTypeStyleTypeFull enforceRepeatedArgTypeStyleType = "full" +) + +func repeatedArgTypeStyleFromString(s string) enforceRepeatedArgTypeStyleType { + switch s { + case string(enforceRepeatedArgTypeStyleTypeAny), "": + return enforceRepeatedArgTypeStyleTypeAny + case string(enforceRepeatedArgTypeStyleTypeShort): + return enforceRepeatedArgTypeStyleTypeShort + case string(enforceRepeatedArgTypeStyleTypeFull): + return enforceRepeatedArgTypeStyleTypeFull + default: + err := fmt.Errorf( + "invalid repeated arg type style: %s (expecting one of %v)", + s, + []enforceRepeatedArgTypeStyleType{ + enforceRepeatedArgTypeStyleTypeAny, + enforceRepeatedArgTypeStyleTypeShort, + enforceRepeatedArgTypeStyleTypeFull, + }, + ) + + panic(fmt.Sprintf("Invalid argument to the enforce-repeated-arg-type-style rule: %v", err)) + } +} + +// EnforceRepeatedArgTypeStyleRule implements a rule to enforce repeated argument type style. +type EnforceRepeatedArgTypeStyleRule struct { + configured bool + funcArgStyle enforceRepeatedArgTypeStyleType + funcRetValStyle enforceRepeatedArgTypeStyleType + + sync.Mutex +} + +func (r *EnforceRepeatedArgTypeStyleRule) configure(arguments lint.Arguments) { + r.Lock() + defer r.Unlock() + + if r.configured { + return + } + r.configured = true + + r.funcArgStyle = enforceRepeatedArgTypeStyleTypeAny + r.funcRetValStyle = enforceRepeatedArgTypeStyleTypeAny + + if len(arguments) == 0 { + return + } + + switch funcArgStyle := arguments[0].(type) { + case string: + r.funcArgStyle = repeatedArgTypeStyleFromString(funcArgStyle) + r.funcRetValStyle = repeatedArgTypeStyleFromString(funcArgStyle) + case map[string]any: // expecting map[string]string + for k, v := range funcArgStyle { + switch k { + case "funcArgStyle": + val, ok := v.(string) + if !ok { + panic(fmt.Sprintf("Invalid map value type for 'enforce-repeated-arg-type-style' rule. Expecting string, got %T", v)) + } + r.funcArgStyle = repeatedArgTypeStyleFromString(val) + case "funcRetValStyle": + val, ok := v.(string) + if !ok { + panic(fmt.Sprintf("Invalid map value '%v' for 'enforce-repeated-arg-type-style' rule. Expecting string, got %T", v, v)) + } + r.funcRetValStyle = repeatedArgTypeStyleFromString(val) + default: + panic(fmt.Sprintf("Invalid map key for 'enforce-repeated-arg-type-style' rule. Expecting 'funcArgStyle' or 'funcRetValStyle', got %v", k)) + } + } + default: + panic(fmt.Sprintf("Invalid argument '%v' for 'import-alias-naming' rule. Expecting string or map[string]string, got %T", arguments[0], arguments[0])) + } +} + +// Apply applies the rule to a given file. +func (r *EnforceRepeatedArgTypeStyleRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + r.configure(arguments) + + if r.funcArgStyle == enforceRepeatedArgTypeStyleTypeAny && r.funcRetValStyle == enforceRepeatedArgTypeStyleTypeAny { + // This linter is not configured, return no failures. + return nil + } + + var failures []lint.Failure + + err := file.Pkg.TypeCheck() + if err != nil { + // the file has other issues + return nil + } + typesInfo := file.Pkg.TypesInfo() + + astFile := file.AST + ast.Inspect(astFile, func(n ast.Node) bool { + switch fn := n.(type) { + case *ast.FuncDecl: + if r.funcArgStyle == enforceRepeatedArgTypeStyleTypeFull { + if fn.Type.Params != nil { + for _, field := range fn.Type.Params.List { + if len(field.Names) > 1 { + failures = append(failures, lint.Failure{ + Confidence: 1, + Node: field, + Category: "style", + Failure: "argument types should not be omitted", + }) + } + } + } + } + + if r.funcArgStyle == enforceRepeatedArgTypeStyleTypeShort { + var prevType ast.Expr + if fn.Type.Params != nil { + for _, field := range fn.Type.Params.List { + if types.Identical(typesInfo.Types[field.Type].Type, typesInfo.Types[prevType].Type) { + failures = append(failures, lint.Failure{ + Confidence: 1, + Node: field, + Category: "style", + Failure: "repeated argument type can be omitted", + }) + } + prevType = field.Type + } + } + } + + if r.funcRetValStyle == enforceRepeatedArgTypeStyleTypeFull { + if fn.Type.Results != nil { + for _, field := range fn.Type.Results.List { + if len(field.Names) > 1 { + failures = append(failures, lint.Failure{ + Confidence: 1, + Node: field, + Category: "style", + Failure: "return types should not be omitted", + }) + } + } + } + } + + if r.funcRetValStyle == enforceRepeatedArgTypeStyleTypeShort { + var prevType ast.Expr + if fn.Type.Results != nil { + for _, field := range fn.Type.Results.List { + if field.Names != nil && types.Identical(typesInfo.Types[field.Type].Type, typesInfo.Types[prevType].Type) { + failures = append(failures, lint.Failure{ + Confidence: 1, + Node: field, + Category: "style", + Failure: "repeated return type can be omitted", + }) + } + prevType = field.Type + } + } + } + } + return true + }) + + return failures +} + +// Name returns the name of the linter rule. +func (*EnforceRepeatedArgTypeStyleRule) Name() string { + return "enforce-repeated-arg-type-style" +} diff --git a/vendor/github.com/mgechev/revive/rule/enforce-slice-style.go b/vendor/github.com/mgechev/revive/rule/enforce-slice-style.go new file mode 100644 index 000000000..abaf20be0 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/enforce-slice-style.go @@ -0,0 +1,193 @@ +package rule + +import ( + "fmt" + "go/ast" + "sync" + + "github.com/mgechev/revive/lint" +) + +type enforceSliceStyleType string + +const ( + enforceSliceStyleTypeAny enforceSliceStyleType = "any" + enforceSliceStyleTypeMake enforceSliceStyleType = "make" + enforceSliceStyleTypeLiteral enforceSliceStyleType = "literal" +) + +func sliceStyleFromString(s string) (enforceSliceStyleType, error) { + switch s { + case string(enforceSliceStyleTypeAny), "": + return enforceSliceStyleTypeAny, nil + case string(enforceSliceStyleTypeMake): + return enforceSliceStyleTypeMake, nil + case string(enforceSliceStyleTypeLiteral): + return enforceSliceStyleTypeLiteral, nil + default: + return enforceSliceStyleTypeAny, fmt.Errorf( + "invalid slice style: %s (expecting one of %v)", + s, + []enforceSliceStyleType{ + enforceSliceStyleTypeAny, + enforceSliceStyleTypeMake, + enforceSliceStyleTypeLiteral, + }, + ) + } +} + +// EnforceSliceStyleRule implements a rule to enforce `make([]type)` over `[]type{}`. +type EnforceSliceStyleRule struct { + configured bool + enforceSliceStyle enforceSliceStyleType + sync.Mutex +} + +func (r *EnforceSliceStyleRule) configure(arguments lint.Arguments) { + r.Lock() + defer r.Unlock() + + if r.configured { + return + } + r.configured = true + + if len(arguments) < 1 { + r.enforceSliceStyle = enforceSliceStyleTypeAny + return + } + + enforceSliceStyle, ok := arguments[0].(string) + if !ok { + panic(fmt.Sprintf("Invalid argument '%v' for 'enforce-slice-style' rule. Expecting string, got %T", arguments[0], arguments[0])) + } + + var err error + r.enforceSliceStyle, err = sliceStyleFromString(enforceSliceStyle) + + if err != nil { + panic(fmt.Sprintf("Invalid argument to the enforce-slice-style rule: %v", err)) + } +} + +// Apply applies the rule to given file. +func (r *EnforceSliceStyleRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + r.configure(arguments) + + if r.enforceSliceStyle == enforceSliceStyleTypeAny { + // this linter is not configured + return nil + } + + var failures []lint.Failure + + astFile := file.AST + ast.Inspect(astFile, func(n ast.Node) bool { + switch v := n.(type) { + case *ast.CompositeLit: + if r.enforceSliceStyle != enforceSliceStyleTypeMake { + return true + } + + if !r.isSliceType(v.Type) { + return true + } + + if len(v.Elts) > 0 { + // not an empty slice + return true + } + + failures = append(failures, lint.Failure{ + Confidence: 1, + Node: v, + Category: "style", + Failure: "use make([]type) instead of []type{} (or declare nil slice)", + }) + case *ast.CallExpr: + if r.enforceSliceStyle != enforceSliceStyleTypeLiteral { + // skip any function calls, even if it's make([]type) + // we don't want to report it if literals are not enforced + return true + } + + ident, ok := v.Fun.(*ast.Ident) + if !ok || ident.Name != "make" { + return true + } + + if len(v.Args) < 2 { + // skip invalid make declarations + return true + } + + if !r.isSliceType(v.Args[0]) { + // not a slice type + return true + } + + arg, ok := v.Args[1].(*ast.BasicLit) + if !ok { + // skip invalid make declarations + return true + } + + if arg.Value != "0" { + // skip slice with non-zero size + return true + } + + if len(v.Args) > 2 { + arg, ok := v.Args[2].(*ast.BasicLit) + if !ok { + // skip invalid make declarations + return true + } + + if arg.Value != "0" { + // skip non-zero capacity slice + return true + } + } + + failures = append(failures, lint.Failure{ + Confidence: 1, + Node: v.Args[0], + Category: "style", + Failure: "use []type{} instead of make([]type, 0) (or declare nil slice)", + }) + } + return true + }) + + return failures +} + +// Name returns the rule name. +func (*EnforceSliceStyleRule) Name() string { + return "enforce-slice-style" +} + +func (r *EnforceSliceStyleRule) isSliceType(v ast.Expr) bool { + switch t := v.(type) { + case *ast.ArrayType: + if t.Len != nil { + // array + return false + } + // slice + return true + case *ast.Ident: + if t.Obj == nil { + return false + } + typeSpec, ok := t.Obj.Decl.(*ast.TypeSpec) + if !ok { + return false + } + return r.isSliceType(typeSpec.Type) + default: + return false + } +} diff --git a/vendor/github.com/mgechev/revive/rule/flag-param.go b/vendor/github.com/mgechev/revive/rule/flag-param.go index 19a05f9fe..f9bfb712c 100644 --- a/vendor/github.com/mgechev/revive/rule/flag-param.go +++ b/vendor/github.com/mgechev/revive/rule/flag-param.go @@ -88,7 +88,7 @@ func (w conditionVisitor) Visit(node ast.Node) ast.Visitor { return false } - uses := pick(ifStmt.Cond, fselect, nil) + uses := pick(ifStmt.Cond, fselect) if len(uses) < 1 { return w diff --git a/vendor/github.com/mgechev/revive/rule/function-length.go b/vendor/github.com/mgechev/revive/rule/function-length.go index f7ab8b98e..fd65884e9 100644 --- a/vendor/github.com/mgechev/revive/rule/function-length.go +++ b/vendor/github.com/mgechev/revive/rule/function-length.go @@ -171,7 +171,7 @@ func (w lintFuncLength) countFuncLitStmts(stmt ast.Expr) int { return 0 } -func (w lintFuncLength) countBodyListStmts(t interface{}) int { +func (w lintFuncLength) countBodyListStmts(t any) int { i := reflect.ValueOf(t).Elem().FieldByName(`Body`).Elem().FieldByName(`List`).Interface() return w.countStmts(i.([]ast.Stmt)) } diff --git a/vendor/github.com/mgechev/revive/rule/import-alias-naming.go b/vendor/github.com/mgechev/revive/rule/import-alias-naming.go index 292392b5d..a6d096c8b 100644 --- a/vendor/github.com/mgechev/revive/rule/import-alias-naming.go +++ b/vendor/github.com/mgechev/revive/rule/import-alias-naming.go @@ -10,14 +10,15 @@ import ( // ImportAliasNamingRule lints import alias naming. type ImportAliasNamingRule struct { - configured bool - namingRuleRegexp *regexp.Regexp + configured bool + allowRegexp *regexp.Regexp + denyRegexp *regexp.Regexp sync.Mutex } -const defaultNamingRule = "^[a-z][a-z0-9]{0,}$" +const defaultImportAliasNamingAllowRule = "^[a-z][a-z0-9]{0,}$" -var defaultNamingRuleRegexp = regexp.MustCompile(defaultNamingRule) +var defaultImportAliasNamingAllowRegexp = regexp.MustCompile(defaultImportAliasNamingAllowRule) func (r *ImportAliasNamingRule) configure(arguments lint.Arguments) { r.Lock() @@ -26,20 +27,31 @@ func (r *ImportAliasNamingRule) configure(arguments lint.Arguments) { return } - if len(arguments) < 1 { - r.namingRuleRegexp = defaultNamingRuleRegexp + if len(arguments) == 0 { + r.allowRegexp = defaultImportAliasNamingAllowRegexp return } - namingRule, ok := arguments[0].(string) // Alt. non panicking version - if !ok { - panic(fmt.Sprintf("Invalid argument '%v' for 'import-alias-naming' rule. Expecting string, got %T", arguments[0], arguments[0])) + switch namingRule := arguments[0].(type) { + case string: + r.setAllowRule(namingRule) + case map[string]any: // expecting map[string]string + for k, v := range namingRule { + switch k { + case "allowRegex": + r.setAllowRule(v) + case "denyRegex": + r.setDenyRule(v) + default: + panic(fmt.Sprintf("Invalid map key for 'import-alias-naming' rule. Expecting 'allowRegex' or 'denyRegex', got %v", k)) + } + } + default: + panic(fmt.Sprintf("Invalid argument '%v' for 'import-alias-naming' rule. Expecting string or map[string]string, got %T", arguments[0], arguments[0])) } - var err error - r.namingRuleRegexp, err = regexp.Compile(namingRule) - if err != nil { - panic(fmt.Sprintf("Invalid argument to the import-alias-naming rule. Expecting %q to be a valid regular expression, got: %v", namingRule, err)) + if r.allowRegexp == nil && r.denyRegexp == nil { + r.allowRegexp = defaultImportAliasNamingAllowRegexp } } @@ -56,14 +68,23 @@ func (r *ImportAliasNamingRule) Apply(file *lint.File, arguments lint.Arguments) } alias := is.Name - if alias == nil || alias.Name == "_" { + if alias == nil || alias.Name == "_" || alias.Name == "." { // "_" and "." are special types of import aiases and should be processed by another linter rule continue } - if !r.namingRuleRegexp.MatchString(alias.Name) { + if r.allowRegexp != nil && !r.allowRegexp.MatchString(alias.Name) { + failures = append(failures, lint.Failure{ + Confidence: 1, + Failure: fmt.Sprintf("import name (%s) must match the regular expression: %s", alias.Name, r.allowRegexp.String()), + Node: alias, + Category: "imports", + }) + } + + if r.denyRegexp != nil && r.denyRegexp.MatchString(alias.Name) { failures = append(failures, lint.Failure{ Confidence: 1, - Failure: fmt.Sprintf("import name (%s) must match the regular expression: %s", alias.Name, r.namingRuleRegexp.String()), + Failure: fmt.Sprintf("import name (%s) must NOT match the regular expression: %s", alias.Name, r.denyRegexp.String()), Node: alias, Category: "imports", }) @@ -77,3 +98,29 @@ func (r *ImportAliasNamingRule) Apply(file *lint.File, arguments lint.Arguments) func (*ImportAliasNamingRule) Name() string { return "import-alias-naming" } + +func (r *ImportAliasNamingRule) setAllowRule(value any) { + namingRule, ok := value.(string) + if !ok { + panic(fmt.Sprintf("Invalid argument '%v' for import-alias-naming allowRegexp rule. Expecting string, got %T", value, value)) + } + + namingRuleRegexp, err := regexp.Compile(namingRule) + if err != nil { + panic(fmt.Sprintf("Invalid argument to the import-alias-naming allowRegexp rule. Expecting %q to be a valid regular expression, got: %v", namingRule, err)) + } + r.allowRegexp = namingRuleRegexp +} + +func (r *ImportAliasNamingRule) setDenyRule(value any) { + namingRule, ok := value.(string) + if !ok { + panic(fmt.Sprintf("Invalid argument '%v' for import-alias-naming denyRegexp rule. Expecting string, got %T", value, value)) + } + + namingRuleRegexp, err := regexp.Compile(namingRule) + if err != nil { + panic(fmt.Sprintf("Invalid argument to the import-alias-naming denyRegexp rule. Expecting %q to be a valid regular expression, got: %v", namingRule, err)) + } + r.denyRegexp = namingRuleRegexp +} diff --git a/vendor/github.com/mgechev/revive/rule/imports-blacklist.go b/vendor/github.com/mgechev/revive/rule/imports-blocklist.go index bb8262cae..431066403 100644 --- a/vendor/github.com/mgechev/revive/rule/imports-blacklist.go +++ b/vendor/github.com/mgechev/revive/rule/imports-blocklist.go @@ -8,37 +8,37 @@ import ( "github.com/mgechev/revive/lint" ) -// ImportsBlacklistRule lints given else constructs. -type ImportsBlacklistRule struct { - blacklist []*regexp.Regexp +// ImportsBlocklistRule lints given else constructs. +type ImportsBlocklistRule struct { + blocklist []*regexp.Regexp sync.Mutex } -var replaceRegexp = regexp.MustCompile(`/?\*\*/?`) +var replaceImportRegexp = regexp.MustCompile(`/?\*\*/?`) -func (r *ImportsBlacklistRule) configure(arguments lint.Arguments) { +func (r *ImportsBlocklistRule) configure(arguments lint.Arguments) { r.Lock() defer r.Unlock() - if r.blacklist == nil { - r.blacklist = make([]*regexp.Regexp, 0) + if r.blocklist == nil { + r.blocklist = make([]*regexp.Regexp, 0) for _, arg := range arguments { argStr, ok := arg.(string) if !ok { - panic(fmt.Sprintf("Invalid argument to the imports-blacklist rule. Expecting a string, got %T", arg)) + panic(fmt.Sprintf("Invalid argument to the imports-blocklist rule. Expecting a string, got %T", arg)) } - regStr, err := regexp.Compile(fmt.Sprintf(`(?m)"%s"$`, replaceRegexp.ReplaceAllString(argStr, `(\W|\w)*`))) + regStr, err := regexp.Compile(fmt.Sprintf(`(?m)"%s"$`, replaceImportRegexp.ReplaceAllString(argStr, `(\W|\w)*`))) if err != nil { - panic(fmt.Sprintf("Invalid argument to the imports-blacklist rule. Expecting %q to be a valid regular expression, got: %v", argStr, err)) + panic(fmt.Sprintf("Invalid argument to the imports-blocklist rule. Expecting %q to be a valid regular expression, got: %v", argStr, err)) } - r.blacklist = append(r.blacklist, regStr) + r.blocklist = append(r.blocklist, regStr) } } } -func (r *ImportsBlacklistRule) isBlacklisted(path string) bool { - for _, regex := range r.blacklist { +func (r *ImportsBlocklistRule) isBlocklisted(path string) bool { + for _, regex := range r.blocklist { if regex.MatchString(path) { return true } @@ -47,17 +47,17 @@ func (r *ImportsBlacklistRule) isBlacklisted(path string) bool { } // Apply applies the rule to given file. -func (r *ImportsBlacklistRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { +func (r *ImportsBlocklistRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { r.configure(arguments) var failures []lint.Failure for _, is := range file.AST.Imports { path := is.Path - if path != nil && r.isBlacklisted(path.Value) { + if path != nil && r.isBlocklisted(path.Value) { failures = append(failures, lint.Failure{ Confidence: 1, - Failure: "should not use the following blacklisted import: " + path.Value, + Failure: "should not use the following blocklisted import: " + path.Value, Node: is, Category: "imports", }) @@ -68,6 +68,6 @@ func (r *ImportsBlacklistRule) Apply(file *lint.File, arguments lint.Arguments) } // Name returns the rule name. -func (*ImportsBlacklistRule) Name() string { - return "imports-blacklist" +func (*ImportsBlocklistRule) Name() string { + return "imports-blocklist" } diff --git a/vendor/github.com/mgechev/revive/rule/indent-error-flow.go b/vendor/github.com/mgechev/revive/rule/indent-error-flow.go index b80e6486c..294ceef84 100644 --- a/vendor/github.com/mgechev/revive/rule/indent-error-flow.go +++ b/vendor/github.com/mgechev/revive/rule/indent-error-flow.go @@ -19,7 +19,7 @@ func (*IndentErrorFlowRule) Name() string { } // CheckIfElse evaluates the rule against an ifelse.Chain. -func (e *IndentErrorFlowRule) CheckIfElse(chain ifelse.Chain, args ifelse.Args) (failMsg string) { +func (*IndentErrorFlowRule) CheckIfElse(chain ifelse.Chain, args ifelse.Args) (failMsg string) { if !chain.If.Deviates() { // this rule only applies if the if-block deviates control flow return diff --git a/vendor/github.com/mgechev/revive/rule/max-control-nesting.go b/vendor/github.com/mgechev/revive/rule/max-control-nesting.go new file mode 100644 index 000000000..c4eb36193 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/max-control-nesting.go @@ -0,0 +1,128 @@ +package rule + +import ( + "fmt" + "go/ast" + "sync" + + "github.com/mgechev/revive/lint" +) + +// MaxControlNestingRule lints given else constructs. +type MaxControlNestingRule struct { + max int64 + sync.Mutex +} + +const defaultMaxControlNesting = 5 + +// Apply applies the rule to given file. +func (r *MaxControlNestingRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + r.configure(arguments) + + var failures []lint.Failure + + fileAst := file.AST + + walker := &lintMaxControlNesting{ + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + max: int(r.max), + } + + ast.Walk(walker, fileAst) + + return failures +} + +// Name returns the rule name. +func (*MaxControlNestingRule) Name() string { + return "max-control-nesting" +} + +type lintMaxControlNesting struct { + max int + onFailure func(lint.Failure) + nestingLevelAcc int + lastCtrlStmt ast.Node +} + +func (w *lintMaxControlNesting) Visit(n ast.Node) ast.Visitor { + if w.nestingLevelAcc > w.max { // we are visiting a node beyond the max nesting level + w.onFailure(lint.Failure{ + Failure: fmt.Sprintf("control flow nesting exceeds %d", w.max), + Confidence: 1, + Node: w.lastCtrlStmt, + Category: "complexity", + }) + return nil // stop visiting deeper + } + + switch v := n.(type) { + case *ast.IfStmt: + w.lastCtrlStmt = v + w.walkControlledBlock(v.Body) // "then" branch block + if v.Else != nil { + w.walkControlledBlock(v.Else) // "else" branch block + } + return nil // stop re-visiting nesting blocks (already visited by w.walkControlledBlock) + + case *ast.ForStmt: + w.lastCtrlStmt = v + w.walkControlledBlock(v.Body) + return nil // stop re-visiting nesting blocks (already visited by w.walkControlledBlock) + + case *ast.CaseClause: // switch case + w.lastCtrlStmt = v + for _, s := range v.Body { // visit each statement in the case clause + w.walkControlledBlock(s) + } + return nil // stop re-visiting nesting blocks (already visited by w.walkControlledBlock) + + case *ast.CommClause: // select case + w.lastCtrlStmt = v + for _, s := range v.Body { // visit each statement in the select case clause + w.walkControlledBlock(s) + } + return nil // stop re-visiting nesting blocks (already visited by w.walkControlledBlock) + + case *ast.FuncLit: + walker := &lintMaxControlNesting{ + onFailure: w.onFailure, + max: w.max, + } + ast.Walk(walker, v.Body) + return nil + } + + return w +} + +func (w *lintMaxControlNesting) walkControlledBlock(b ast.Node) { + oldNestingLevel := w.nestingLevelAcc + w.nestingLevelAcc++ + ast.Walk(w, b) + w.nestingLevelAcc = oldNestingLevel +} + +func (r *MaxControlNestingRule) configure(arguments lint.Arguments) { + r.Lock() + defer r.Unlock() + if !(r.max < 1) { + return // max already set + } + + if len(arguments) < 1 { + r.max = defaultMaxControlNesting + return + } + + checkNumberOfArguments(1, arguments, r.Name()) + + max, ok := arguments[0].(int64) // Alt. non panicking version + if !ok { + panic(`invalid value passed as argument number to the "max-control-nesting" rule`) + } + r.max = max +} diff --git a/vendor/github.com/mgechev/revive/rule/modifies-value-receiver.go b/vendor/github.com/mgechev/revive/rule/modifies-value-receiver.go index 34e651557..e9e64b9a6 100644 --- a/vendor/github.com/mgechev/revive/rule/modifies-value-receiver.go +++ b/vendor/github.com/mgechev/revive/rule/modifies-value-receiver.go @@ -78,11 +78,6 @@ func (w lintModifiesValRecRule) Visit(node ast.Node) ast.Visitor { if name == "" || name != receiverName { continue } - - if w.skipType(ast.Expr(e.Sel)) { - continue - } - case *ast.Ident: // receiver := ... if e.Name != receiverName { continue @@ -97,7 +92,7 @@ func (w lintModifiesValRecRule) Visit(node ast.Node) ast.Visitor { return false } - assignmentsToReceiver := pick(n.Body, fselect, nil) + assignmentsToReceiver := pick(n.Body, fselect) for _, assignment := range assignmentsToReceiver { w.onFailure(lint.Failure{ diff --git a/vendor/github.com/mgechev/revive/rule/optimize-operands-order.go b/vendor/github.com/mgechev/revive/rule/optimize-operands-order.go index 88928bb98..841bde56c 100644 --- a/vendor/github.com/mgechev/revive/rule/optimize-operands-order.go +++ b/vendor/github.com/mgechev/revive/rule/optimize-operands-order.go @@ -54,13 +54,13 @@ func (w lintOptimizeOperandsOrderlExpr) Visit(node ast.Node) ast.Visitor { } // check if the left sub-expression contains a function call - nodes := pick(binExpr.X, isCaller, nil) + nodes := pick(binExpr.X, isCaller) if len(nodes) < 1 { return w } // check if the right sub-expression does not contain a function call - nodes = pick(binExpr.Y, isCaller, nil) + nodes = pick(binExpr.Y, isCaller) if len(nodes) > 0 { return w } diff --git a/vendor/github.com/mgechev/revive/rule/string-format.go b/vendor/github.com/mgechev/revive/rule/string-format.go index 3edd62477..70edf7387 100644 --- a/vendor/github.com/mgechev/revive/rule/string-format.go +++ b/vendor/github.com/mgechev/revive/rule/string-format.go @@ -38,7 +38,7 @@ func (*StringFormatRule) Name() string { // ParseArgumentsTest is a public wrapper around w.parseArguments used for testing. Returns the error message provided to panic, or nil if no error was encountered func (StringFormatRule) ParseArgumentsTest(arguments lint.Arguments) *string { w := lintStringFormatRule{} - c := make(chan interface{}) + c := make(chan any) // Parse the arguments in a goroutine, defer a recover() call, return the error encountered (or nil if there was no error) go func() { defer func() { @@ -101,8 +101,8 @@ func (w *lintStringFormatRule) parseArguments(arguments lint.Arguments) { } } -func (w lintStringFormatRule) parseArgument(argument interface{}, ruleNum int) (scope stringFormatSubruleScope, regex *regexp.Regexp, negated bool, errorMessage string) { - g, ok := argument.([]interface{}) // Cast to generic slice first +func (w lintStringFormatRule) parseArgument(argument any, ruleNum int) (scope stringFormatSubruleScope, regex *regexp.Regexp, negated bool, errorMessage string) { + g, ok := argument.([]any) // Cast to generic slice first if !ok { w.configError("argument is not a slice", ruleNum, 0) } diff --git a/vendor/github.com/mgechev/revive/rule/struct-tag.go b/vendor/github.com/mgechev/revive/rule/struct-tag.go index d1c8056aa..f6ee47a73 100644 --- a/vendor/github.com/mgechev/revive/rule/struct-tag.go +++ b/vendor/github.com/mgechev/revive/rule/struct-tag.go @@ -140,7 +140,7 @@ func (lintStructTagRule) getTagName(tag *structtag.Tag) string { return strings.TrimPrefix(option, "name=") } } - return "" //protobuf tag lacks 'name' option + return "" // protobuf tag lacks 'name' option default: return tag.Name } diff --git a/vendor/github.com/mgechev/revive/rule/superfluous-else.go b/vendor/github.com/mgechev/revive/rule/superfluous-else.go index 1ef67bf1a..2aa1b6b2c 100644 --- a/vendor/github.com/mgechev/revive/rule/superfluous-else.go +++ b/vendor/github.com/mgechev/revive/rule/superfluous-else.go @@ -20,7 +20,7 @@ func (*SuperfluousElseRule) Name() string { } // CheckIfElse evaluates the rule against an ifelse.Chain. -func (e *SuperfluousElseRule) CheckIfElse(chain ifelse.Chain, args ifelse.Args) (failMsg string) { +func (*SuperfluousElseRule) CheckIfElse(chain ifelse.Chain, args ifelse.Args) (failMsg string) { if !chain.If.Deviates() { // this rule only applies if the if-block deviates control flow return diff --git a/vendor/github.com/mgechev/revive/rule/unconditional-recursion.go b/vendor/github.com/mgechev/revive/rule/unconditional-recursion.go index bad907533..9ac2648cd 100644 --- a/vendor/github.com/mgechev/revive/rule/unconditional-recursion.go +++ b/vendor/github.com/mgechev/revive/rule/unconditional-recursion.go @@ -45,8 +45,9 @@ type funcStatus struct { } type lintUnconditionalRecursionRule struct { - onFailure func(lint.Failure) - currentFunc *funcStatus + onFailure func(lint.Failure) + currentFunc *funcStatus + inGoStatement bool } // Visit will traverse the file AST. @@ -68,9 +69,13 @@ func (w lintUnconditionalRecursionRule) Visit(node ast.Node) ast.Visitor { default: rec = n.Recv.List[0].Names[0] } - w.currentFunc = &funcStatus{&funcDesc{rec, n.Name}, false} case *ast.CallExpr: + // check if call arguments has a recursive call + for _, arg := range n.Args { + ast.Walk(w, arg) + } + var funcID *ast.Ident var selector *ast.Ident switch c := n.Fun.(type) { @@ -84,6 +89,9 @@ func (w lintUnconditionalRecursionRule) Visit(node ast.Node) ast.Visitor { return nil } funcID = c.Sel + case *ast.FuncLit: + ast.Walk(w, c.Body) // analyze the body of the function literal + return nil default: return w } @@ -93,11 +101,12 @@ func (w lintUnconditionalRecursionRule) Visit(node ast.Node) ast.Visitor { w.currentFunc.funcDesc.equal(&funcDesc{selector, funcID}) { w.onFailure(lint.Failure{ Category: "logic", - Confidence: 1, + Confidence: 0.8, Node: n, Failure: "unconditional recursive call", }) } + return nil case *ast.IfStmt: w.updateFuncStatus(n.Body) w.updateFuncStatus(n.Else) @@ -115,16 +124,21 @@ func (w lintUnconditionalRecursionRule) Visit(node ast.Node) ast.Visitor { w.updateFuncStatus(n.Body) return nil case *ast.GoStmt: - for _, a := range n.Call.Args { - ast.Walk(w, a) // check if arguments have a recursive call - } - return nil // recursive async call is not an issue + w.inGoStatement = true + ast.Walk(w, n.Call) + w.inGoStatement = false + return nil case *ast.ForStmt: if n.Cond != nil { return nil } // unconditional loop return w + case *ast.FuncLit: + if w.inGoStatement { + return w + } + return nil // literal call (closure) is not necessarily an issue } return w @@ -181,5 +195,5 @@ func (lintUnconditionalRecursionRule) hasControlExit(node ast.Node) bool { return false } - return len(pick(node, isExit, nil)) != 0 + return len(pick(node, isExit)) != 0 } diff --git a/vendor/github.com/mgechev/revive/rule/unhandled-error.go b/vendor/github.com/mgechev/revive/rule/unhandled-error.go index 32a5fe48b..ce6fa3864 100644 --- a/vendor/github.com/mgechev/revive/rule/unhandled-error.go +++ b/vendor/github.com/mgechev/revive/rule/unhandled-error.go @@ -119,7 +119,7 @@ func (w *lintUnhandledErrors) addFailure(n *ast.CallExpr) { Category: "bad practice", Confidence: 1, Node: n, - Failure: fmt.Sprintf("Unhandled error in call to function %v", gofmt(n.Fun)), + Failure: fmt.Sprintf("Unhandled error in call to function %v", name), }) } diff --git a/vendor/github.com/mgechev/revive/rule/unused-param.go b/vendor/github.com/mgechev/revive/rule/unused-param.go index df6cd9af0..4b04ee916 100644 --- a/vendor/github.com/mgechev/revive/rule/unused-param.go +++ b/vendor/github.com/mgechev/revive/rule/unused-param.go @@ -35,7 +35,7 @@ func (r *UnusedParamRule) configure(args lint.Arguments) { r.failureMsg = "parameter '%s' seems to be unused, consider removing or renaming it as _" } else { // Arguments = [{}] - options := args[0].(map[string]interface{}) + options := args[0].(map[string]any) // Arguments = [{allowedRegex="^_"}] if allowedRegexParam, ok := options["allowRegex"]; ok { @@ -87,54 +87,64 @@ type lintUnusedParamRule struct { } func (w lintUnusedParamRule) Visit(node ast.Node) ast.Visitor { + var ( + funcType *ast.FuncType + funcBody *ast.BlockStmt + ) switch n := node.(type) { + case *ast.FuncLit: + funcType = n.Type + funcBody = n.Body case *ast.FuncDecl: - params := retrieveNamedParams(n.Type.Params) - if len(params) < 1 { - return nil // skip, func without parameters - } - if n.Body == nil { return nil // skip, is a function prototype } - // inspect the func body looking for references to parameters - fselect := func(n ast.Node) bool { - ident, isAnID := n.(*ast.Ident) + funcType = n.Type + funcBody = n.Body + default: + return w // skip, not a function + } - if !isAnID { - return false - } + params := retrieveNamedParams(funcType.Params) + if len(params) < 1 { + return w // skip, func without parameters + } - _, isAParam := params[ident.Obj] - if isAParam { - params[ident.Obj] = false // mark as used - } + // inspect the func body looking for references to parameters + fselect := func(n ast.Node) bool { + ident, isAnID := n.(*ast.Ident) + if !isAnID { return false } - _ = pick(n.Body, fselect, nil) - - for _, p := range n.Type.Params.List { - for _, n := range p.Names { - if w.allowRegex.FindStringIndex(n.Name) != nil { - continue - } - if params[n.Obj] { - w.onFailure(lint.Failure{ - Confidence: 1, - Node: n, - Category: "bad practice", - Failure: fmt.Sprintf(w.failureMsg, n.Name), - }) - } - } + + _, isAParam := params[ident.Obj] + if isAParam { + params[ident.Obj] = false // mark as used } - return nil // full method body already inspected + return false + } + _ = pick(funcBody, fselect) + + for _, p := range funcType.Params.List { + for _, n := range p.Names { + if w.allowRegex.FindStringIndex(n.Name) != nil { + continue + } + if params[n.Obj] { + w.onFailure(lint.Failure{ + Confidence: 1, + Node: n, + Category: "bad practice", + Failure: fmt.Sprintf(w.failureMsg, n.Name), + }) + } + } } - return w + return w // full method body was inspected } func retrieveNamedParams(params *ast.FieldList) map[*ast.Object]bool { diff --git a/vendor/github.com/mgechev/revive/rule/unused-receiver.go b/vendor/github.com/mgechev/revive/rule/unused-receiver.go index 488572b7b..715dba338 100644 --- a/vendor/github.com/mgechev/revive/rule/unused-receiver.go +++ b/vendor/github.com/mgechev/revive/rule/unused-receiver.go @@ -35,7 +35,7 @@ func (r *UnusedReceiverRule) configure(args lint.Arguments) { r.failureMsg = "method receiver '%s' is not referenced in method's body, consider removing or renaming it as _" } else { // Arguments = [{}] - options := args[0].(map[string]interface{}) + options := args[0].(map[string]any) // Arguments = [{allowedRegex="^_"}] if allowedRegexParam, ok := options["allowRegex"]; ok { @@ -113,7 +113,7 @@ func (w lintUnusedReceiverRule) Visit(node ast.Node) ast.Visitor { return isAnID && ident.Obj == recID.Obj } - refs2recID := pick(n.Body, fselect, nil) + refs2recID := pick(n.Body, fselect) if len(refs2recID) > 0 { return nil // the receiver is referenced in the func body diff --git a/vendor/github.com/mgechev/revive/rule/utils.go b/vendor/github.com/mgechev/revive/rule/utils.go index dca1674ca..5778e7696 100644 --- a/vendor/github.com/mgechev/revive/rule/utils.go +++ b/vendor/github.com/mgechev/revive/rule/utils.go @@ -93,21 +93,15 @@ func srcLine(src []byte, p token.Position) string { // pick yields a list of nodes by picking them from a sub-ast with root node n. // Nodes are selected by applying the fselect function -// f function is applied to each selected node before inserting it in the final result. -// If f==nil then it defaults to the identity function (ie it returns the node itself) -func pick(n ast.Node, fselect func(n ast.Node) bool, f func(n ast.Node) []ast.Node) []ast.Node { +func pick(n ast.Node, fselect func(n ast.Node) bool) []ast.Node { var result []ast.Node if n == nil { return result } - if f == nil { - f = func(n ast.Node) []ast.Node { return []ast.Node{n} } - } - onSelect := func(n ast.Node) { - result = append(result, f(n)...) + result = append(result, n) } p := picker{fselect: fselect, onSelect: onSelect} ast.Walk(p, n) @@ -158,7 +152,7 @@ func isExprABooleanLit(n ast.Node) (lexeme string, ok bool) { } // gofmt returns a string representation of an AST subtree. -func gofmt(x interface{}) string { +func gofmt(x any) string { buf := bytes.Buffer{} fs := token.NewFileSet() printer.Fprint(&buf, fs, x) diff --git a/vendor/github.com/mgechev/revive/rule/var-naming.go b/vendor/github.com/mgechev/revive/rule/var-naming.go index 286ff9d75..e91c22dc2 100644 --- a/vendor/github.com/mgechev/revive/rule/var-naming.go +++ b/vendor/github.com/mgechev/revive/rule/var-naming.go @@ -18,10 +18,11 @@ var upperCaseConstRE = regexp.MustCompile(`^_?[A-Z][A-Z\d]*(_[A-Z\d]+)*$`) // VarNamingRule lints given else constructs. type VarNamingRule struct { - configured bool - whitelist []string - blacklist []string - upperCaseConst bool // if true - allows to use UPPER_SOME_NAMES for constants + configured bool + allowlist []string + blocklist []string + upperCaseConst bool // if true - allows to use UPPER_SOME_NAMES for constants + skipPackageNameChecks bool sync.Mutex } @@ -34,31 +35,53 @@ func (r *VarNamingRule) configure(arguments lint.Arguments) { r.configured = true if len(arguments) >= 1 { - r.whitelist = getList(arguments[0], "whitelist") + r.allowlist = getList(arguments[0], "allowlist") } if len(arguments) >= 2 { - r.blacklist = getList(arguments[1], "blacklist") + r.blocklist = getList(arguments[1], "blocklist") } if len(arguments) >= 3 { // not pretty code because should keep compatibility with TOML (no mixed array types) and new map parameters thirdArgument := arguments[2] - asSlice, ok := thirdArgument.([]interface{}) + asSlice, ok := thirdArgument.([]any) if !ok { panic(fmt.Sprintf("Invalid third argument to the var-naming rule. Expecting a %s of type slice, got %T", "options", arguments[2])) } if len(asSlice) != 1 { panic(fmt.Sprintf("Invalid third argument to the var-naming rule. Expecting a %s of type slice, of len==1, but %d", "options", len(asSlice))) } - args, ok := asSlice[0].(map[string]interface{}) + args, ok := asSlice[0].(map[string]any) if !ok { panic(fmt.Sprintf("Invalid third argument to the var-naming rule. Expecting a %s of type slice, of len==1, with map, but %T", "options", asSlice[0])) } r.upperCaseConst = fmt.Sprint(args["upperCaseConst"]) == "true" + r.skipPackageNameChecks = fmt.Sprint(args["skipPackageNameChecks"]) == "true" } } +func (r *VarNamingRule) applyPackageCheckRules(walker *lintNames) { + // Package names need slightly different handling than other names. + if strings.Contains(walker.fileAst.Name.Name, "_") && !strings.HasSuffix(walker.fileAst.Name.Name, "_test") { + walker.onFailure(lint.Failure{ + Failure: "don't use an underscore in package name", + Confidence: 1, + Node: walker.fileAst.Name, + Category: "naming", + }) + } + if anyCapsRE.MatchString(walker.fileAst.Name.Name) { + walker.onFailure(lint.Failure{ + Failure: fmt.Sprintf("don't use MixedCaps in package name; %s should be %s", walker.fileAst.Name.Name, strings.ToLower(walker.fileAst.Name.Name)), + Confidence: 1, + Node: walker.fileAst.Name, + Category: "naming", + }) + } + +} + // Apply applies the rule to given file. func (r *VarNamingRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { r.configure(arguments) @@ -70,30 +93,16 @@ func (r *VarNamingRule) Apply(file *lint.File, arguments lint.Arguments) []lint. walker := lintNames{ file: file, fileAst: fileAst, - whitelist: r.whitelist, - blacklist: r.blacklist, + allowlist: r.allowlist, + blocklist: r.blocklist, onFailure: func(failure lint.Failure) { failures = append(failures, failure) }, upperCaseConst: r.upperCaseConst, } - // Package names need slightly different handling than other names. - if strings.Contains(walker.fileAst.Name.Name, "_") && !strings.HasSuffix(walker.fileAst.Name.Name, "_test") { - walker.onFailure(lint.Failure{ - Failure: "don't use an underscore in package name", - Confidence: 1, - Node: walker.fileAst.Name, - Category: "naming", - }) - } - if anyCapsRE.MatchString(walker.fileAst.Name.Name) { - walker.onFailure(lint.Failure{ - Failure: fmt.Sprintf("don't use MixedCaps in package name; %s should be %s", walker.fileAst.Name.Name, strings.ToLower(walker.fileAst.Name.Name)), - Confidence: 1, - Node: walker.fileAst.Name, - Category: "naming", - }) + if !r.skipPackageNameChecks { + r.applyPackageCheckRules(&walker) } ast.Walk(&walker, fileAst) @@ -127,7 +136,7 @@ func (w *lintNames) check(id *ast.Ident, thing string) { // #851 upperCaseConst support // if it's const - if thing == token.CONST.String() && w.upperCaseConst && upperCaseConstRE.Match([]byte(id.Name)) { + if thing == token.CONST.String() && w.upperCaseConst && upperCaseConstRE.MatchString(id.Name) { return } @@ -142,7 +151,7 @@ func (w *lintNames) check(id *ast.Ident, thing string) { return } - should := lint.Name(id.Name, w.whitelist, w.blacklist) + should := lint.Name(id.Name, w.allowlist, w.blocklist) if id.Name == should { return } @@ -168,8 +177,8 @@ type lintNames struct { file *lint.File fileAst *ast.File onFailure func(lint.Failure) - whitelist []string - blacklist []string + allowlist []string + blocklist []string upperCaseConst bool } @@ -255,8 +264,8 @@ func (w *lintNames) Visit(n ast.Node) ast.Visitor { return w } -func getList(arg interface{}, argName string) []string { - temp, ok := arg.([]interface{}) +func getList(arg any, argName string) []string { + temp, ok := arg.([]any) if !ok { panic(fmt.Sprintf("Invalid argument to the var-naming rule. Expecting a %s of type slice with initialisms, got %T", argName, arg)) } diff --git a/vendor/github.com/nishanths/exhaustive/comment.go b/vendor/github.com/nishanths/exhaustive/comment.go index cc84beaf7..123e0181b 100644 --- a/vendor/github.com/nishanths/exhaustive/comment.go +++ b/vendor/github.com/nishanths/exhaustive/comment.go @@ -3,45 +3,14 @@ package exhaustive import ( "go/ast" "go/token" - "regexp" "strings" ) -// For definition of generated file see: -// http://golang.org/s/generatedcode - -var generatedCodeRe = regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`) - -func isGeneratedFile(file *ast.File) bool { - // NOTE: file.Comments includes file.Doc as well, so no need - // to separately check file.Doc. - for _, c := range file.Comments { - for _, cc := range c.List { - // This check handles the "must appear before the first - // non-comment, non-blank text in the file" requirement. - // - // According to https://golang.org/ref/spec#Source_file_organization - // the package clause is the first element in a file, which - // should make it the first non-comment, non-blank text. - if c.Pos() >= file.Package { - return false - } - // According to the docs: - // '\r' has been removed. - // '\n' has been removed for //-style comments - // This has also been manually verified. - if generatedCodeRe.MatchString(cc.Text) { - return true - } - } - } - - return false -} - const ( - ignoreComment = "//exhaustive:ignore" - enforceComment = "//exhaustive:enforce" + ignoreComment = "//exhaustive:ignore" + enforceComment = "//exhaustive:enforce" + ignoreDefaultCaseRequiredComment = "//exhaustive:ignore-default-case-required" + enforceDefaultCaseRequiredComment = "//exhaustive:enforce-default-case-required" ) func hasCommentPrefix(comments []*ast.CommentGroup, comment string) bool { diff --git a/vendor/github.com/nishanths/exhaustive/comment_go121.go b/vendor/github.com/nishanths/exhaustive/comment_go121.go new file mode 100644 index 000000000..a7bbc8881 --- /dev/null +++ b/vendor/github.com/nishanths/exhaustive/comment_go121.go @@ -0,0 +1,11 @@ +//go:build go1.21 + +package exhaustive + +import ( + "go/ast" +) + +func isGeneratedFile(file *ast.File) bool { + return ast.IsGenerated(file) +} diff --git a/vendor/github.com/nishanths/exhaustive/comment_pre_go121.go b/vendor/github.com/nishanths/exhaustive/comment_pre_go121.go new file mode 100644 index 000000000..28d2ed493 --- /dev/null +++ b/vendor/github.com/nishanths/exhaustive/comment_pre_go121.go @@ -0,0 +1,27 @@ +//go:build !go1.21 + +package exhaustive + +import ( + "go/ast" + "regexp" +) + +// For definition of generated file see: +// http://golang.org/s/generatedcode + +var generatedCodeRe = regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`) + +func isGeneratedFile(file *ast.File) bool { + for _, c := range file.Comments { + for _, cc := range c.List { + if cc.Pos() > file.Package { + break + } + if generatedCodeRe.MatchString(cc.Text) { + return true + } + } + } + return false +} diff --git a/vendor/github.com/nishanths/exhaustive/doc.go b/vendor/github.com/nishanths/exhaustive/doc.go index 8435e5d24..a745247db 100644 --- a/vendor/github.com/nishanths/exhaustive/doc.go +++ b/vendor/github.com/nishanths/exhaustive/doc.go @@ -10,8 +10,8 @@ The Go [language spec] does not have an explicit definition for enums. For the purpose of this analyzer, and by convention, an enum type is any named type that: - - has underlying type float, string, or integer (includes byte and rune); - and + - has an [underlying type] of float, string, or integer (includes byte + and rune); and - has at least one constant of its type defined in the same [block]. In the example below, Biome is an enum type. The three constants are its @@ -209,6 +209,7 @@ To ignore specific types, specify the -ignore-enum-types flag: exhaustive -ignore-enum-types '^time\.Duration$|^example\.org/measure\.Unit$' [language spec]: https://golang.org/ref/spec +[underlying type]: https://golang.org/ref/spec#Underlying_types [block]: https://golang.org/ref/spec#Blocks [BasicKind]: https://pkg.go.dev/go/types#BasicKind */ diff --git a/vendor/github.com/nishanths/exhaustive/exhaustive.go b/vendor/github.com/nishanths/exhaustive/exhaustive.go index d67a60c32..013ac47bb 100644 --- a/vendor/github.com/nishanths/exhaustive/exhaustive.go +++ b/vendor/github.com/nishanths/exhaustive/exhaustive.go @@ -19,6 +19,7 @@ func registerFlags() { Analyzer.Flags.BoolVar(&fExplicitExhaustiveMap, ExplicitExhaustiveMapFlag, false, `check map literal only if associated with "//exhaustive:enforce" comment`) Analyzer.Flags.BoolVar(&fCheckGenerated, CheckGeneratedFlag, false, "check generated files") Analyzer.Flags.BoolVar(&fDefaultSignifiesExhaustive, DefaultSignifiesExhaustiveFlag, false, "switch statement is unconditionally exhaustive if it has a default case") + Analyzer.Flags.BoolVar(&fDefaultCaseRequired, DefaultCaseRequiredFlag, false, "switch statement requires default case even if exhaustive") Analyzer.Flags.Var(&fIgnoreEnumMembers, IgnoreEnumMembersFlag, "ignore constants matching `regexp`") Analyzer.Flags.Var(&fIgnoreEnumTypes, IgnoreEnumTypesFlag, "ignore types matching `regexp`") Analyzer.Flags.BoolVar(&fPackageScopeOnly, PackageScopeOnlyFlag, false, "only discover enums declared in file-level blocks") @@ -36,6 +37,7 @@ const ( ExplicitExhaustiveMapFlag = "explicit-exhaustive-map" CheckGeneratedFlag = "check-generated" DefaultSignifiesExhaustiveFlag = "default-signifies-exhaustive" + DefaultCaseRequiredFlag = "default-case-required" IgnoreEnumMembersFlag = "ignore-enum-members" IgnoreEnumTypesFlag = "ignore-enum-types" PackageScopeOnlyFlag = "package-scope-only" @@ -52,6 +54,7 @@ var ( fExplicitExhaustiveMap bool fCheckGenerated bool fDefaultSignifiesExhaustive bool + fDefaultCaseRequired bool fIgnoreEnumMembers regexpFlag fIgnoreEnumTypes regexpFlag fPackageScopeOnly bool @@ -65,6 +68,7 @@ func resetFlags() { fExplicitExhaustiveMap = false fCheckGenerated = false fDefaultSignifiesExhaustive = false + fDefaultCaseRequired = false fIgnoreEnumMembers = regexpFlag{} fIgnoreEnumTypes = regexpFlag{} fPackageScopeOnly = false @@ -121,6 +125,7 @@ func run(pass *analysis.Pass) (interface{}, error) { conf := switchConfig{ explicit: fExplicitExhaustiveSwitch, defaultSignifiesExhaustive: fDefaultSignifiesExhaustive, + defaultCaseRequired: fDefaultCaseRequired, checkGenerated: fCheckGenerated, ignoreConstant: fIgnoreEnumMembers.re, ignoreType: fIgnoreEnumTypes.re, diff --git a/vendor/github.com/nishanths/exhaustive/switch.go b/vendor/github.com/nishanths/exhaustive/switch.go index 000ef9886..d235fbec4 100644 --- a/vendor/github.com/nishanths/exhaustive/switch.go +++ b/vendor/github.com/nishanths/exhaustive/switch.go @@ -5,6 +5,7 @@ import ( "go/ast" "go/types" "regexp" + "strings" "golang.org/x/tools/go/analysis" ) @@ -44,6 +45,7 @@ const ( resultNoEnforceComment = "has no enforce comment" resultEnumMembersAccounted = "required enum members accounted for" resultDefaultCaseSuffices = "default case satisfies exhaustiveness" + resultMissingDefaultCase = "missing required default case" resultReportedDiagnostic = "reported diagnostic" resultEnumTypes = "invalid or empty composing enum types" ) @@ -52,11 +54,47 @@ const ( type switchConfig struct { explicit bool defaultSignifiesExhaustive bool + defaultCaseRequired bool checkGenerated bool ignoreConstant *regexp.Regexp // can be nil ignoreType *regexp.Regexp // can be nil } +// There are few possibilities, and often none, so we use a possibly-nil slice +func userDirectives(comments []*ast.CommentGroup) []string { + var directives []string + for _, c := range comments { + for _, cc := range c.List { + // The order matters here: we always want to check the longest first. + for _, d := range []string{ + enforceDefaultCaseRequiredComment, + ignoreDefaultCaseRequiredComment, + enforceComment, + ignoreComment, + } { + if strings.HasPrefix(cc.Text, d) { + directives = append(directives, d) + // The break here is important: once we associate a comment + // with a particular (longest-possible) directive, we don't want + // to map to another! + break + } + } + } + } + return directives +} + +// Can be replaced with slices.Contains with go1.21 +func directivesIncludes(directives []string, d string) bool { + for _, ud := range directives { + if ud == d { + return true + } + } + return false +} + // switchChecker returns a node visitor that checks exhaustiveness of // enum switch statements for the supplied pass, and reports // diagnostics. The node visitor expects only *ast.SwitchStmt nodes. @@ -80,17 +118,27 @@ func switchChecker(pass *analysis.Pass, cfg switchConfig, generated boolCache, c sw := n.(*ast.SwitchStmt) switchComments := comments.get(pass.Fset, file)[sw] - if !cfg.explicit && hasCommentPrefix(switchComments, ignoreComment) { + uDirectives := userDirectives(switchComments) + if !cfg.explicit && directivesIncludes(uDirectives, ignoreComment) { // Skip checking of this switch statement due to ignore // comment. Still return true because there may be nested // switch statements that are not to be ignored. return true, resultIgnoreComment } - if cfg.explicit && !hasCommentPrefix(switchComments, enforceComment) { + if cfg.explicit && !directivesIncludes(uDirectives, enforceComment) { // Skip checking of this switch statement due to missing // enforce comment. return true, resultNoEnforceComment } + requireDefaultCase := cfg.defaultCaseRequired + if directivesIncludes(uDirectives, ignoreDefaultCaseRequiredComment) { + requireDefaultCase = false + } + if directivesIncludes(uDirectives, enforceDefaultCaseRequiredComment) { + // We have "if" instead of "else if" here in case of conflicting ignore/enforce directives. + // In that case, because this is second, we will default to enforcing. + requireDefaultCase = true + } if sw.Tag == nil { return true, resultNoSwitchTag // never possible for valid Go program? @@ -114,13 +162,21 @@ func switchChecker(pass *analysis.Pass, cfg switchConfig, generated boolCache, c checkl.add(e.typ, e.members, pass.Pkg == e.typ.Pkg()) } - def := analyzeSwitchClauses(sw, pass.TypesInfo, checkl.found) + defaultCaseExists := analyzeSwitchClauses(sw, pass.TypesInfo, checkl.found) + if !defaultCaseExists && requireDefaultCase { + // Even if the switch explicitly enumerates all the + // enum values, the user has still required all switches + // to have a default case. We check this first to avoid + // early-outs + pass.Report(makeMissingDefaultDiagnostic(sw, dedupEnumTypes(toEnumTypes(es)))) + return true, resultMissingDefaultCase + } if len(checkl.remaining()) == 0 { // All enum members accounted for. // Nothing to report. return true, resultEnumMembersAccounted } - if def && cfg.defaultSignifiesExhaustive { + if defaultCaseExists && cfg.defaultSignifiesExhaustive { // Though enum members are not accounted for, the // existence of the default case signifies // exhaustiveness. So don't report. @@ -167,3 +223,14 @@ func makeSwitchDiagnostic(sw *ast.SwitchStmt, enumTypes []enumType, missing map[ ), } } + +func makeMissingDefaultDiagnostic(sw *ast.SwitchStmt, enumTypes []enumType) analysis.Diagnostic { + return analysis.Diagnostic{ + Pos: sw.Pos(), + End: sw.End(), + Message: fmt.Sprintf( + "missing default case in switch of type %s", + diagnosticEnumTypes(enumTypes), + ), + } +} diff --git a/vendor/github.com/nunnatsa/ginkgolinter/Makefile b/vendor/github.com/nunnatsa/ginkgolinter/Makefile index e8efae583..586633006 100644 --- a/vendor/github.com/nunnatsa/ginkgolinter/Makefile +++ b/vendor/github.com/nunnatsa/ginkgolinter/Makefile @@ -5,9 +5,12 @@ HASH_FLAG := -X github.com/nunnatsa/ginkgolinter/version.gitHash=$(COMMIT_HASH) BUILD_ARGS := -ldflags "$(VERSION_FLAG) $(HASH_FLAG)" -build: +build: unit-test go build $(BUILD_ARGS) -o ginkgolinter ./cmd/ginkgolinter +unit-test: + go test ./... + build-for-windows: GOOS=windows GOARCH=amd64 go build $(BUILD_ARGS) -o bin/ginkgolinter-amd64.exe ./cmd/ginkgolinter diff --git a/vendor/github.com/nunnatsa/ginkgolinter/README.md b/vendor/github.com/nunnatsa/ginkgolinter/README.md index 6c95917d2..3832f610d 100644 --- a/vendor/github.com/nunnatsa/ginkgolinter/README.md +++ b/vendor/github.com/nunnatsa/ginkgolinter/README.md @@ -44,7 +44,7 @@ It is not enabled by default, though. There are two ways to run ginkgolinter wit enable: - ginkgolinter ``` -## Linter Checks +## Linter Rules The linter checks the ginkgo and gomega assertions in golang test code. Gomega may be used together with ginkgo tests, For example: ```go @@ -177,7 +177,8 @@ var _ = Describe("checking something", Focus, func() { }) ``` -These container, or the `Focus` spec, must not be part of the final source code, and should only be used locally by the developer. +These container, or the `Focus` spec, must not be part of the final source code, and should only be used locally by the +developer. ***This rule is disabled by default***. Use the `--forbid-focus-container=true` command line flag to enable it. @@ -205,8 +206,30 @@ To suppress this warning entirely, use the `--suppress-type-compare-assertion=tr To suppress a specific file or line, use the `// ginkgo-linter:ignore-type-compare-warning` comment (see [below](#suppress-warning-from-the-code)) +### Wrong Usage of the `MatchError` gomega Matcher [BUG] +The `MatchError` gomega matcher asserts an error value (and if it's not nil). +There are four valid formats for using this Matcher: +* error value; e.g. `Expect(err).To(MatchError(anotherErr))` +* string, to be equal to the output of the `Error()` method; e.g. `Expect(err).To(MatchError("Not Found"))` +* A gomega matcher that asserts strings; e.g. `Expect(err).To(MatchError(ContainSubstring("Found")))` +* [from v0.29.0] a function that receive a single error parameter and returns a single boolean value. + In this format, an additional single string parameter, with the function description, is also required; e.g. + `Expect(err).To(MatchError(isNotFound, "is the error is a not found error"))` + +These four format are checked on runtime, but sometimes it's too late. ginkgolinter performs a static analysis and so it +will find these issues on build time. + +ginkgolinter checks the following: +* Is the first parameter is one of the four options above. +* That there are no additional parameters passed to the matcher; e.g. + `MatchError(isNotFoundFunc, "a valid description" , "not used string")`. In this case, the matcher won't fail on run + time, but the additional parameters are not in use and ignored. +* If the first parameter is a function with the format of `func(error)bool`, ginkgolinter makes sure that the second + parameter exists and its type is string. + ### Wrong Length Assertion [STYLE] -The linter finds assertion of the golang built-in `len` function, with all kind of matchers, while there are already gomega matchers for these usecases; We want to assert the item, rather than its length. +The linter finds assertion of the golang built-in `len` function, with all kind of matchers, while there are already +gomega matchers for these usecases; We want to assert the item, rather than its length. There are several wrong patterns: ```go @@ -240,7 +263,8 @@ The output of the linter,when finding issues, looks like this: The linter will also warn about the `HaveLen(0)` matcher, and will suggest to replace it with `BeEmpty()` ### Wrong `nil` Assertion [STYLE] -The linter finds assertion of the comparison to nil, with all kind of matchers, instead of using the existing `BeNil()` matcher; We want to assert the item, rather than a comparison result. +The linter finds assertion of the comparison to nil, with all kind of matchers, instead of using the existing `BeNil()` +matcher; We want to assert the item, rather than a comparison result. There are several wrong patterns: diff --git a/vendor/github.com/nunnatsa/ginkgolinter/ginkgo_linter.go b/vendor/github.com/nunnatsa/ginkgolinter/ginkgo_linter.go index d1e5164fa..a0aff1612 100644 --- a/vendor/github.com/nunnatsa/ginkgolinter/ginkgo_linter.go +++ b/vendor/github.com/nunnatsa/ginkgolinter/ginkgo_linter.go @@ -16,6 +16,7 @@ import ( "github.com/nunnatsa/ginkgolinter/ginkgohandler" "github.com/nunnatsa/ginkgolinter/gomegahandler" + "github.com/nunnatsa/ginkgolinter/interfaces" "github.com/nunnatsa/ginkgolinter/reverseassertion" "github.com/nunnatsa/ginkgolinter/types" "github.com/nunnatsa/ginkgolinter/version" @@ -40,8 +41,13 @@ const ( focusContainerFound = linterName + ": Focus container found. This is used only for local debug and should not be part of the actual source code, consider to replace with %q" focusSpecFound = linterName + ": Focus spec found. This is used only for local debug and should not be part of the actual source code, consider to remove it" compareDifferentTypes = linterName + ": use %[1]s with different types: Comparing %[2]s with %[3]s; either change the expected value type if possible, or use the BeEquivalentTo() matcher, instead of %[1]s()" - compareInterfaces = linterName + ": be careful comparing interfaces. This can fail in runtime, if the actual implementing types are different" + matchErrorArgWrongType = linterName + ": the MatchError matcher used to assert a non error type (%s)" + matchErrorWrongTypeAssertion = linterName + ": MatchError first parameter (%s) must be error, string, GomegaMatcher or func(error)bool are allowed" + matchErrorMissingDescription = linterName + ": missing function description as second parameter of MatchError" + matchErrorRedundantArg = linterName + ": redundant MatchError arguments; consider removing them; consider using `%s` instead" + matchErrorNoFuncDescription = linterName + ": The second parameter of MatchError must be the function description (string)" ) + const ( // gomega matchers beEmpty = "BeEmpty" beEquivalentTo = "BeEquivalentTo" @@ -61,6 +67,7 @@ const ( // gomega matchers and = "And" or = "Or" withTransform = "WithTransform" + matchError = "MatchError" ) const ( // gomega actuals @@ -133,6 +140,8 @@ currently, the linter searches for following: * trigger a warning when a ginkgo focus container (FDescribe, FContext, FWhen or FIt) is found. [Bug] +* validate the MatchError gomega matcher [Bug] + * trigger a warning when using the Equal or the BeIdentical matcher with two different types, as these matchers will fail in runtime. @@ -179,6 +188,8 @@ func (l *ginkgoLinter) run(pass *analysis.Pass) (interface{}, error) { continue } + //gomegaMatcher = getMatcherInterface(pass, file) + ast.Inspect(file, func(n ast.Node) bool { if ginkgoHndlr != nil && fileConfig.ForbidFocus { spec, ok := n.(*ast.ValueSpec) @@ -304,6 +315,8 @@ func checkExpression(pass *analysis.Pass, config types.Config, assertionExp *ast } return bool(config.SuppressCompare) || checkComparison(expr, pass, matcher, handler, first, second, op, oldExpr) + } else if checkMatchError(pass, assertionExp, actualArg, handler, oldExpr) { + return false } else if isExprError(pass, actualArg) { return bool(config.SuppressErr) || checkNilError(pass, expr, handler, actualArg, oldExpr) @@ -318,6 +331,130 @@ func checkExpression(pass *analysis.Pass, config types.Config, assertionExp *ast return true } +func checkMatchError(pass *analysis.Pass, origExp *ast.CallExpr, actualArg ast.Expr, handler gomegahandler.Handler, oldExpr string) bool { + matcher, ok := origExp.Args[0].(*ast.CallExpr) + if !ok { + return false + } + + return doCheckMatchError(pass, origExp, matcher, actualArg, handler, oldExpr) +} + +func doCheckMatchError(pass *analysis.Pass, origExp *ast.CallExpr, matcher *ast.CallExpr, actualArg ast.Expr, handler gomegahandler.Handler, oldExpr string) bool { + name, ok := handler.GetActualFuncName(matcher) + if !ok { + return false + } + switch name { + case matchError: + case not: + nested, ok := matcher.Args[0].(*ast.CallExpr) + if !ok { + return false + } + + return doCheckMatchError(pass, origExp, nested, actualArg, handler, oldExpr) + case and, or: + res := true + for _, arg := range matcher.Args { + if nested, ok := arg.(*ast.CallExpr); ok { + if !doCheckMatchError(pass, origExp, nested, actualArg, handler, oldExpr) { + res = false + } + } + } + return res + default: + return false + } + + if !isExprError(pass, actualArg) { + reportNoFix(pass, origExp.Pos(), matchErrorArgWrongType, goFmt(pass.Fset, actualArg)) + } + + expr := astcopy.CallExpr(matcher) + + validAssertion, requiredParams := checkMatchErrorAssertion(pass, matcher) + if !validAssertion { + reportNoFix(pass, expr.Pos(), matchErrorWrongTypeAssertion, goFmt(pass.Fset, matcher.Args[0])) + return false + } + + numParams := len(matcher.Args) + if numParams == requiredParams { + if numParams == 2 { + t := pass.TypesInfo.TypeOf(matcher.Args[1]) + if !gotypes.Identical(t, gotypes.Typ[gotypes.String]) { + pass.Reportf(expr.Pos(), matchErrorNoFuncDescription) + return false + } + } + return true + } + + if requiredParams == 2 && numParams == 1 { + reportNoFix(pass, expr.Pos(), matchErrorMissingDescription) + return false + } + + var newArgsSuggestion = []ast.Expr{expr.Args[0]} + if requiredParams == 2 { + newArgsSuggestion = append(newArgsSuggestion, expr.Args[1]) + } + expr.Args = newArgsSuggestion + report(pass, expr, matchErrorRedundantArg, oldExpr) + return false +} + +func checkMatchErrorAssertion(pass *analysis.Pass, matcher *ast.CallExpr) (bool, int) { + if isErrorMatcherValidArg(pass, matcher.Args[0]) { + return true, 1 + } + + t1 := pass.TypesInfo.TypeOf(matcher.Args[0]) + if isFuncErrBool(t1) { + return true, 2 + } + + return false, 0 +} + +// isFuncErrBool checks if a function is with the signature `func(error) bool` +func isFuncErrBool(t gotypes.Type) bool { + sig, ok := t.(*gotypes.Signature) + if !ok { + return false + } + if sig.Params().Len() != 1 || sig.Results().Len() != 1 { + return false + } + + if !interfaces.ImplementsError(sig.Params().At(0).Type()) { + return false + } + + b, ok := sig.Results().At(0).Type().(*gotypes.Basic) + if ok && b.Name() == "bool" && b.Info() == gotypes.IsBoolean && b.Kind() == gotypes.Bool { + return true + } + + return false +} + +func isErrorMatcherValidArg(pass *analysis.Pass, arg ast.Expr) bool { + if isExprError(pass, arg) { + return true + } + + if t, ok := pass.TypesInfo.TypeOf(arg).(*gotypes.Basic); ok && t.Kind() == gotypes.String { + return true + } + + t := pass.TypesInfo.TypeOf(arg) + + return interfaces.ImplementsGomegaMatcher(t) +} + func checkEqualWrongType(pass *analysis.Pass, origExp *ast.CallExpr, actualArg ast.Expr, handler gomegahandler.Handler, old string) bool { matcher, ok := origExp.Args[0].(*ast.CallExpr) if !ok { @@ -1297,28 +1434,18 @@ func goFmt(fset *token.FileSet, x ast.Expr) string { return b.String() } -var errorType *gotypes.Interface - -func init() { - errorType = gotypes.Universe.Lookup("error").Type().Underlying().(*gotypes.Interface) -} - -func isError(t gotypes.Type) bool { - return gotypes.Implements(t, errorType) -} - func isExprError(pass *analysis.Pass, expr ast.Expr) bool { actualArgType := pass.TypesInfo.TypeOf(expr) switch t := actualArgType.(type) { case *gotypes.Named: - if isError(actualArgType) { + if interfaces.ImplementsError(actualArgType) { return true } case *gotypes.Tuple: if t.Len() > 0 { switch t0 := t.At(0).Type().(type) { case *gotypes.Named, *gotypes.Pointer: - if isError(t0) { + if interfaces.ImplementsError(t0) { return true } } diff --git a/vendor/github.com/nunnatsa/ginkgolinter/gomegahandler/handler.go b/vendor/github.com/nunnatsa/ginkgolinter/gomegahandler/handler.go index 0c34cb7c1..419145b75 100644 --- a/vendor/github.com/nunnatsa/ginkgolinter/gomegahandler/handler.go +++ b/vendor/github.com/nunnatsa/ginkgolinter/gomegahandler/handler.go @@ -5,6 +5,10 @@ import ( "go/token" ) +const ( + importPath = `"github.com/onsi/gomega"` +) + // Handler provide different handling, depend on the way gomega was imported, whether // in imported with "." name, custom name or without any name. type Handler interface { @@ -23,7 +27,7 @@ type Handler interface { // GetGomegaHandler returns a gomegar handler according to the way gomega was imported in the specific file func GetGomegaHandler(file *ast.File) Handler { for _, imp := range file.Imports { - if imp.Path.Value != `"github.com/onsi/gomega"` { + if imp.Path.Value != importPath { continue } diff --git a/vendor/github.com/nunnatsa/ginkgolinter/interfaces/interfaces.go b/vendor/github.com/nunnatsa/ginkgolinter/interfaces/interfaces.go new file mode 100644 index 000000000..dafeacd4f --- /dev/null +++ b/vendor/github.com/nunnatsa/ginkgolinter/interfaces/interfaces.go @@ -0,0 +1,76 @@ +package interfaces + +import ( + "go/token" + gotypes "go/types" +) + +var ( + errorType *gotypes.Interface + gomegaMatcherType *gotypes.Interface +) + +func init() { + errorType = gotypes.Universe.Lookup("error").Type().Underlying().(*gotypes.Interface) + gomegaMatcherType = generateTheGomegaMatcherInfType() +} + +// generateTheGomegaMatcherInfType generates a types.Interface instance that represents the +// GomegaMatcher interface. +// The original code is (copied from https://github.com/nunnatsa/ginkgolinter/blob/8fdd05eee922578d4699f49d267001c01e0b9f1e/testdata/src/a/vendor/github.com/onsi/gomega/types/types.go) +// +// type GomegaMatcher interface { +// Match(actual interface{}) (success bool, err error) +// FailureMessage(actual interface{}) (message string) +// NegatedFailureMessage(actual interface{}) (message string) +// } +func generateTheGomegaMatcherInfType() *gotypes.Interface { + err := gotypes.Universe.Lookup("error").Type() + bl := gotypes.Typ[gotypes.Bool] + str := gotypes.Typ[gotypes.String] + anyType := gotypes.Universe.Lookup("any").Type() + + return gotypes.NewInterfaceType([]*gotypes.Func{ + // Match(actual interface{}) (success bool, err error) + gotypes.NewFunc(token.NoPos, nil, "Match", gotypes.NewSignatureType( + nil, nil, nil, + gotypes.NewTuple( + gotypes.NewVar(token.NoPos, nil, "actual", anyType), + ), + gotypes.NewTuple( + gotypes.NewVar(token.NoPos, nil, "", bl), + gotypes.NewVar(token.NoPos, nil, "", err), + ), false), + ), + // FailureMessage(actual interface{}) (message string) + gotypes.NewFunc(token.NoPos, nil, "FailureMessage", gotypes.NewSignatureType( + nil, nil, nil, + gotypes.NewTuple( + gotypes.NewVar(token.NoPos, nil, "", anyType), + ), + gotypes.NewTuple( + gotypes.NewVar(token.NoPos, nil, "", str), + ), + false), + ), + //NegatedFailureMessage(actual interface{}) (message string) + gotypes.NewFunc(token.NoPos, nil, "NegatedFailureMessage", gotypes.NewSignatureType( + nil, nil, nil, + gotypes.NewTuple( + gotypes.NewVar(token.NoPos, nil, "", anyType), + ), + gotypes.NewTuple( + gotypes.NewVar(token.NoPos, nil, "", str), + ), + false), + ), + }, nil) +} + +func ImplementsError(t gotypes.Type) bool { + return gotypes.Implements(t, errorType) +} + +func ImplementsGomegaMatcher(t gotypes.Type) bool { + return gotypes.Implements(t, gomegaMatcherType) +} diff --git a/vendor/github.com/polyfloyd/go-errorlint/errorlint/allowed.go b/vendor/github.com/polyfloyd/go-errorlint/errorlint/allowed.go index df8d5f713..8bfb4c9b2 100644 --- a/vendor/github.com/polyfloyd/go-errorlint/errorlint/allowed.go +++ b/vendor/github.com/polyfloyd/go-errorlint/errorlint/allowed.go @@ -41,6 +41,7 @@ var allowedErrors = []struct { {err: "io.EOF", fun: "debug/elf.Open"}, {err: "io.EOF", fun: "debug/elf.NewFile"}, // pkg/io + {err: "io.EOF", fun: "(io.ReadCloser).Read"}, {err: "io.EOF", fun: "(io.Reader).Read"}, {err: "io.EOF", fun: "(io.ReaderAt).ReadAt"}, {err: "io.EOF", fun: "(*io.LimitedReader).Read"}, @@ -71,6 +72,9 @@ var allowedErrors = []struct { {err: "io.EOF", fun: "(*strings.Reader).ReadAt"}, {err: "io.EOF", fun: "(*strings.Reader).ReadByte"}, {err: "io.EOF", fun: "(*strings.Reader).ReadRune"}, + // pkg/context + {err: "context.DeadlineExceeded", fun: "(context.Context).Err"}, + {err: "context.Canceled", fun: "(context.Context).Err"}, } var allowedErrorWildcards = []struct { diff --git a/vendor/github.com/polyfloyd/go-errorlint/errorlint/lint.go b/vendor/github.com/polyfloyd/go-errorlint/errorlint/lint.go index 817cd6904..572a3816d 100644 --- a/vendor/github.com/polyfloyd/go-errorlint/errorlint/lint.go +++ b/vendor/github.com/polyfloyd/go-errorlint/errorlint/lint.go @@ -307,6 +307,11 @@ func LintErrorTypeAssertions(fset *token.FileSet, info *TypesInfoExt) []analysis continue } + // If the asserted type is not an error, allow the expression. + if !implementsError(info.TypesInfo.Types[typeAssert.Type].Type) { + continue + } + lints = append(lints, analysis.Diagnostic{ Message: "type assertion on error will fail on wrapped errors. Use errors.As to check for specific errors", Pos: typeAssert.Pos(), diff --git a/vendor/github.com/polyfloyd/go-errorlint/errorlint/printf.go b/vendor/github.com/polyfloyd/go-errorlint/errorlint/printf.go index 973752592..4c0e12525 100644 --- a/vendor/github.com/polyfloyd/go-errorlint/errorlint/printf.go +++ b/vendor/github.com/polyfloyd/go-errorlint/errorlint/printf.go @@ -7,23 +7,6 @@ import ( "strings" ) -func verbOrder(verbs []verb, numArgs int) [][]verb { - orderedVerbs := make([][]verb, numArgs) - i := 0 - for _, v := range verbs { - if v.index != -1 { - i = v.index - 1 - } - if i >= len(orderedVerbs) { - continue - } - orderedVerbs[i] = append(orderedVerbs[i], v) - verbs = verbs[1:] - i++ - } - return orderedVerbs -} - type verb struct { format string formatOffset int diff --git a/vendor/github.com/securego/gosec/v2/.goreleaser.yml b/vendor/github.com/securego/gosec/v2/.goreleaser.yml index e3c903e7a..bd85bab3a 100644 --- a/vendor/github.com/securego/gosec/v2/.goreleaser.yml +++ b/vendor/github.com/securego/gosec/v2/.goreleaser.yml @@ -19,6 +19,7 @@ builds: - amd64 - arm64 - s390x + - ppc64le ldflags: -X main.Version={{.Version}} -X main.GitTag={{.Tag}} -X main.BuildDate={{.Date}} env: - CGO_ENABLED=0 diff --git a/vendor/github.com/securego/gosec/v2/Makefile b/vendor/github.com/securego/gosec/v2/Makefile index dcfb4b2ed..4f6cce765 100644 --- a/vendor/github.com/securego/gosec/v2/Makefile +++ b/vendor/github.com/securego/gosec/v2/Makefile @@ -17,7 +17,7 @@ GOSEC ?= $(GOBIN)/gosec GINKGO ?= $(GOBIN)/ginkgo GO_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2) GOVULN_MIN_VERSION = 17 -GO_VERSION = 1.20 +GO_VERSION = 1.22 default: $(MAKE) build @@ -76,7 +76,7 @@ release: goreleaser release build-linux: - CGO_ENABLED=$(CGO_ENABLED) GOOS=linux GOARCH=amd64 go build -ldflags=$(BUILDFLAGS) -o $(BIN) ./cmd/gosec/ + CGO_ENABLED=$(CGO_ENABLED) GOOS=linux go build -ldflags=$(BUILDFLAGS) -o $(BIN) ./cmd/gosec/ image: @echo "Building the Docker image..." diff --git a/vendor/github.com/securego/gosec/v2/README.md b/vendor/github.com/securego/gosec/v2/README.md index d9a33f12a..f7b41df2e 100644 --- a/vendor/github.com/securego/gosec/v2/README.md +++ b/vendor/github.com/securego/gosec/v2/README.md @@ -1,5 +1,5 @@ -# gosec - Golang Security Checker +# gosec - Go Security Checker Inspects source code for security problems by scanning the Go AST and SSA code representation. @@ -105,7 +105,7 @@ jobs: # we let the report trigger content trigger a failure using the GitHub Security features. args: '-no-fail -fmt sarif -out results.sarif ./...' - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@v1 + uses: github/codeql-action/upload-sarif@v2 with: # Path to SARIF file relative to the root of the repository sarif_file: results.sarif @@ -113,18 +113,10 @@ jobs: ### Local Installation -#### Go 1.16+ - ```bash go install github.com/securego/gosec/v2/cmd/gosec@latest ``` -#### Go version < 1.16 - -```bash -go get -u github.com/securego/gosec/v2/cmd/gosec -``` - ## Usage Gosec can be configured to only run a subset of rules, to exclude certain file @@ -393,7 +385,7 @@ schema-generate -i sarif-schema-2.1.0.json -o mypath/types.go ``` Most of the MarshallJSON/UnmarshalJSON are removed except the one for PropertyBag which is handy to inline the additional properties. The rest can be removed. -The URI,ID, UUID, GUID were renamed so it fits the Golang convention defined [here](https://github.com/golang/lint/blob/master/lint.go#L700) +The URI,ID, UUID, GUID were renamed so it fits the Go convention defined [here](https://github.com/golang/lint/blob/master/lint.go#L700) ### Tests diff --git a/vendor/github.com/securego/gosec/v2/action.yml b/vendor/github.com/securego/gosec/v2/action.yml index aba47b60c..3097075ce 100644 --- a/vendor/github.com/securego/gosec/v2/action.yml +++ b/vendor/github.com/securego/gosec/v2/action.yml @@ -10,7 +10,7 @@ inputs: runs: using: 'docker' - image: 'docker://securego/gosec:2.18.1' + image: 'docker://securego/gosec:2.18.2' args: - ${{ inputs.args }} diff --git a/vendor/github.com/securego/gosec/v2/analyzer.go b/vendor/github.com/securego/gosec/v2/analyzer.go index 1fd1f5649..0b1225b9b 100644 --- a/vendor/github.com/securego/gosec/v2/analyzer.go +++ b/vendor/github.com/securego/gosec/v2/analyzer.go @@ -123,7 +123,7 @@ func (i ignores) get(file string, line string) map[string][]issue.SuppressionInf start, end := i.parseLine(line) if is, ok := i[file]; ok { for _, i := range is { - if i.start <= start && i.end >= end { + if start <= i.start && end >= i.end { return i.suppressions } } @@ -414,6 +414,9 @@ func (gosec *Analyzer) CheckAnalyzers(pkg *packages.Package) { SSA: ssaResult.(*buildssa.SSA), }, } + + generatedFiles := gosec.generatedFiles(pkg) + for _, analyzer := range gosec.analyzerList { pass := &analysis.Pass{ Analyzer: analyzer, @@ -441,6 +444,11 @@ func (gosec *Analyzer) CheckAnalyzers(pkg *packages.Package) { if result != nil { if passIssues, ok := result.([]*issue.Issue); ok { for _, iss := range passIssues { + if gosec.excludeGenerated { + if _, ok := generatedFiles[iss.File]; ok { + continue + } + } gosec.updateIssues(iss) } } @@ -448,6 +456,21 @@ func (gosec *Analyzer) CheckAnalyzers(pkg *packages.Package) { } } +func (gosec *Analyzer) generatedFiles(pkg *packages.Package) map[string]bool { + generatedFiles := map[string]bool{} + for _, file := range pkg.Syntax { + if isGeneratedFile(file) { + fp := pkg.Fset.File(file.Pos()) + if fp == nil { + // skip files which cannot be located + continue + } + generatedFiles[fp.Name()] = true + } + } + return generatedFiles +} + // buildSSA runs the SSA pass which builds the SSA representation of the package. It handles gracefully any panic. func (gosec *Analyzer) buildSSA(pkg *packages.Package) (interface{}, error) { defer func() { @@ -557,8 +580,8 @@ func (gosec *Analyzer) ignore(n ast.Node) map[string]issue.SuppressionInfo { for _, group := range groups { comment := strings.TrimSpace(group.Text()) - foundDefaultTag := strings.HasPrefix(comment, noSecDefaultTag) || regexp.MustCompile("\n *"+noSecDefaultTag).Match([]byte(comment)) - foundAlternativeTag := strings.HasPrefix(comment, noSecAlternativeTag) || regexp.MustCompile("\n *"+noSecAlternativeTag).Match([]byte(comment)) + foundDefaultTag := strings.HasPrefix(comment, noSecDefaultTag) || regexp.MustCompile("\n *"+noSecDefaultTag).MatchString(comment) + foundAlternativeTag := strings.HasPrefix(comment, noSecAlternativeTag) || regexp.MustCompile("\n *"+noSecAlternativeTag).MatchString(comment) if foundDefaultTag || foundAlternativeTag { gosec.stats.NumNosec++ diff --git a/vendor/github.com/spf13/afero/afero.go b/vendor/github.com/spf13/afero/afero.go index 199480cd0..39f658520 100644 --- a/vendor/github.com/spf13/afero/afero.go +++ b/vendor/github.com/spf13/afero/afero.go @@ -97,7 +97,7 @@ type Fs interface { // Chown changes the uid and gid of the named file. Chown(name string, uid, gid int) error - //Chtimes changes the access and modification times of the named file + // Chtimes changes the access and modification times of the named file Chtimes(name string, atime time.Time, mtime time.Time) error } diff --git a/vendor/github.com/spf13/afero/basepath.go b/vendor/github.com/spf13/afero/basepath.go index 70a1d9168..2e72793a3 100644 --- a/vendor/github.com/spf13/afero/basepath.go +++ b/vendor/github.com/spf13/afero/basepath.go @@ -40,7 +40,6 @@ func (f *BasePathFile) Name() string { func (f *BasePathFile) ReadDir(n int) ([]fs.DirEntry, error) { if rdf, ok := f.File.(fs.ReadDirFile); ok { return rdf.ReadDir(n) - } return readDirFile{f.File}.ReadDir(n) } diff --git a/vendor/github.com/spf13/afero/const_bsds.go b/vendor/github.com/spf13/afero/const_bsds.go index eed0f225f..30855de57 100644 --- a/vendor/github.com/spf13/afero/const_bsds.go +++ b/vendor/github.com/spf13/afero/const_bsds.go @@ -11,8 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build aix || darwin || openbsd || freebsd || netbsd || dragonfly -// +build aix darwin openbsd freebsd netbsd dragonfly +//go:build aix || darwin || openbsd || freebsd || netbsd || dragonfly || zos +// +build aix darwin openbsd freebsd netbsd dragonfly zos package afero diff --git a/vendor/github.com/spf13/afero/const_win_unix.go b/vendor/github.com/spf13/afero/const_win_unix.go index 004d57e2f..12792d21e 100644 --- a/vendor/github.com/spf13/afero/const_win_unix.go +++ b/vendor/github.com/spf13/afero/const_win_unix.go @@ -10,8 +10,8 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -//go:build !darwin && !openbsd && !freebsd && !dragonfly && !netbsd && !aix -// +build !darwin,!openbsd,!freebsd,!dragonfly,!netbsd,!aix +//go:build !darwin && !openbsd && !freebsd && !dragonfly && !netbsd && !aix && !zos +// +build !darwin,!openbsd,!freebsd,!dragonfly,!netbsd,!aix,!zos package afero diff --git a/vendor/github.com/spf13/afero/copyOnWriteFs.go b/vendor/github.com/spf13/afero/copyOnWriteFs.go index 6ff8f3099..184d6dd70 100644 --- a/vendor/github.com/spf13/afero/copyOnWriteFs.go +++ b/vendor/github.com/spf13/afero/copyOnWriteFs.go @@ -223,7 +223,7 @@ func (u *CopyOnWriteFs) OpenFile(name string, flag int, perm os.FileMode) (File, return nil, err } if isaDir { - if err = u.layer.MkdirAll(dir, 0777); err != nil { + if err = u.layer.MkdirAll(dir, 0o777); err != nil { return nil, err } return u.layer.OpenFile(name, flag, perm) @@ -247,8 +247,9 @@ func (u *CopyOnWriteFs) OpenFile(name string, flag int, perm os.FileMode) (File, // This function handles the 9 different possibilities caused // by the union which are the intersection of the following... -// layer: doesn't exist, exists as a file, and exists as a directory -// base: doesn't exist, exists as a file, and exists as a directory +// +// layer: doesn't exist, exists as a file, and exists as a directory +// base: doesn't exist, exists as a file, and exists as a directory func (u *CopyOnWriteFs) Open(name string) (File, error) { // Since the overlay overrides the base we check that first b, err := u.isBaseFile(name) @@ -322,5 +323,5 @@ func (u *CopyOnWriteFs) MkdirAll(name string, perm os.FileMode) error { } func (u *CopyOnWriteFs) Create(name string) (File, error) { - return u.OpenFile(name, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0666) + return u.OpenFile(name, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0o666) } diff --git a/vendor/github.com/spf13/afero/ioutil.go b/vendor/github.com/spf13/afero/ioutil.go index 386c9cdc2..fa6abe1ee 100644 --- a/vendor/github.com/spf13/afero/ioutil.go +++ b/vendor/github.com/spf13/afero/ioutil.go @@ -141,8 +141,10 @@ func WriteFile(fs Fs, filename string, data []byte, perm os.FileMode) error { // We generate random temporary file names so that there's a good // chance the file doesn't exist yet - keeps the number of tries in // TempFile to a minimum. -var randNum uint32 -var randmu sync.Mutex +var ( + randNum uint32 + randmu sync.Mutex +) func reseed() uint32 { return uint32(time.Now().UnixNano() + int64(os.Getpid())) @@ -190,7 +192,7 @@ func TempFile(fs Fs, dir, pattern string) (f File, err error) { nconflict := 0 for i := 0; i < 10000; i++ { name := filepath.Join(dir, prefix+nextRandom()+suffix) - f, err = fs.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) + f, err = fs.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o600) if os.IsExist(err) { if nconflict++; nconflict > 10 { randmu.Lock() @@ -214,6 +216,7 @@ func TempFile(fs Fs, dir, pattern string) (f File, err error) { func (a Afero) TempDir(dir, prefix string) (name string, err error) { return TempDir(a.Fs, dir, prefix) } + func TempDir(fs Fs, dir, prefix string) (name string, err error) { if dir == "" { dir = os.TempDir() @@ -222,7 +225,7 @@ func TempDir(fs Fs, dir, prefix string) (name string, err error) { nconflict := 0 for i := 0; i < 10000; i++ { try := filepath.Join(dir, prefix+nextRandom()) - err = fs.Mkdir(try, 0700) + err = fs.Mkdir(try, 0o700) if os.IsExist(err) { if nconflict++; nconflict > 10 { randmu.Lock() diff --git a/vendor/github.com/spf13/afero/mem/file.go b/vendor/github.com/spf13/afero/mem/file.go index 3cf4693b5..62fe4498e 100644 --- a/vendor/github.com/spf13/afero/mem/file.go +++ b/vendor/github.com/spf13/afero/mem/file.go @@ -245,7 +245,7 @@ func (f *File) Truncate(size int64) error { defer f.fileData.Unlock() if size > int64(len(f.fileData.data)) { diff := size - int64(len(f.fileData.data)) - f.fileData.data = append(f.fileData.data, bytes.Repeat([]byte{00}, int(diff))...) + f.fileData.data = append(f.fileData.data, bytes.Repeat([]byte{0o0}, int(diff))...) } else { f.fileData.data = f.fileData.data[0:size] } @@ -285,7 +285,7 @@ func (f *File) Write(b []byte) (n int, err error) { tail = f.fileData.data[n+int(cur):] } if diff > 0 { - f.fileData.data = append(f.fileData.data, append(bytes.Repeat([]byte{00}, int(diff)), b...)...) + f.fileData.data = append(f.fileData.data, append(bytes.Repeat([]byte{0o0}, int(diff)), b...)...) f.fileData.data = append(f.fileData.data, tail...) } else { f.fileData.data = append(f.fileData.data[:cur], b...) @@ -321,16 +321,19 @@ func (s *FileInfo) Name() string { s.Unlock() return name } + func (s *FileInfo) Mode() os.FileMode { s.Lock() defer s.Unlock() return s.mode } + func (s *FileInfo) ModTime() time.Time { s.Lock() defer s.Unlock() return s.modtime } + func (s *FileInfo) IsDir() bool { s.Lock() defer s.Unlock() diff --git a/vendor/github.com/spf13/afero/memmap.go b/vendor/github.com/spf13/afero/memmap.go index d06975e71..d6c744e8d 100644 --- a/vendor/github.com/spf13/afero/memmap.go +++ b/vendor/github.com/spf13/afero/memmap.go @@ -15,9 +15,13 @@ package afero import ( "fmt" + "io" + "log" "os" "path/filepath" + + "sort" "strings" "sync" "time" @@ -43,7 +47,7 @@ func (m *MemMapFs) getData() map[string]*mem.FileData { // Root should always exist, right? // TODO: what about windows? root := mem.CreateDir(FilePathSeparator) - mem.SetMode(root, os.ModeDir|0755) + mem.SetMode(root, os.ModeDir|0o755) m.data[FilePathSeparator] = root }) return m.data @@ -87,6 +91,24 @@ func (m *MemMapFs) findParent(f *mem.FileData) *mem.FileData { return pfile } +func (m *MemMapFs) findDescendants(name string) []*mem.FileData { + fData := m.getData() + descendants := make([]*mem.FileData, 0, len(fData)) + for p, dFile := range fData { + if strings.HasPrefix(p, name+FilePathSeparator) { + descendants = append(descendants, dFile) + } + } + + sort.Slice(descendants, func(i, j int) bool { + cur := len(strings.Split(descendants[i].Name(), FilePathSeparator)) + next := len(strings.Split(descendants[j].Name(), FilePathSeparator)) + return cur < next + }) + + return descendants +} + func (m *MemMapFs) registerWithParent(f *mem.FileData, perm os.FileMode) { if f == nil { return @@ -96,12 +118,12 @@ func (m *MemMapFs) registerWithParent(f *mem.FileData, perm os.FileMode) { pdir := filepath.Dir(filepath.Clean(f.Name())) err := m.lockfreeMkdir(pdir, perm) if err != nil { - //log.Println("Mkdir error:", err) + // log.Println("Mkdir error:", err) return } parent, err = m.lockfreeOpen(pdir) if err != nil { - //log.Println("Open after Mkdir error:", err) + // log.Println("Open after Mkdir error:", err) return } } @@ -237,7 +259,7 @@ func (m *MemMapFs) OpenFile(name string, flag int, perm os.FileMode) (File, erro file = mem.NewReadOnlyFileHandle(file.(*mem.File).Data()) } if flag&os.O_APPEND > 0 { - _, err = file.Seek(0, os.SEEK_END) + _, err = file.Seek(0, io.SeekEnd) if err != nil { file.Close() return nil, err @@ -308,11 +330,22 @@ func (m *MemMapFs) Rename(oldname, newname string) error { if _, ok := m.getData()[oldname]; ok { m.mu.RUnlock() m.mu.Lock() - m.unRegisterWithParent(oldname) + err := m.unRegisterWithParent(oldname) + if err != nil { + return err + } + fileData := m.getData()[oldname] - delete(m.getData(), oldname) mem.ChangeFileName(fileData, newname) m.getData()[newname] = fileData + + err = m.renameDescendants(oldname, newname) + if err != nil { + return err + } + + delete(m.getData(), oldname) + m.registerWithParent(fileData, 0) m.mu.Unlock() m.mu.RLock() @@ -322,6 +355,29 @@ func (m *MemMapFs) Rename(oldname, newname string) error { return nil } +func (m *MemMapFs) renameDescendants(oldname, newname string) error { + descendants := m.findDescendants(oldname) + removes := make([]string, 0, len(descendants)) + for _, desc := range descendants { + descNewName := strings.Replace(desc.Name(), oldname, newname, 1) + err := m.unRegisterWithParent(desc.Name()) + if err != nil { + return err + } + + removes = append(removes, desc.Name()) + mem.ChangeFileName(desc, descNewName) + m.getData()[descNewName] = desc + + m.registerWithParent(desc, 0) + } + for _, r := range removes { + delete(m.getData(), r) + } + + return nil +} + func (m *MemMapFs) LstatIfPossible(name string) (os.FileInfo, bool, error) { fileInfo, err := m.Stat(name) return fileInfo, false, err diff --git a/vendor/github.com/spf13/afero/regexpfs.go b/vendor/github.com/spf13/afero/regexpfs.go index ac359c62a..218f3b235 100644 --- a/vendor/github.com/spf13/afero/regexpfs.go +++ b/vendor/github.com/spf13/afero/regexpfs.go @@ -10,7 +10,6 @@ import ( // The RegexpFs filters files (not directories) by regular expression. Only // files matching the given regexp will be allowed, all others get a ENOENT error ( // "No such file or directory"). -// type RegexpFs struct { re *regexp.Regexp source Fs diff --git a/vendor/github.com/spf13/afero/symlink.go b/vendor/github.com/spf13/afero/symlink.go index d1c6ea53d..aa6ae125b 100644 --- a/vendor/github.com/spf13/afero/symlink.go +++ b/vendor/github.com/spf13/afero/symlink.go @@ -21,9 +21,9 @@ import ( // filesystems saying so. // It indicates support for 3 symlink related interfaces that implement the // behaviors of the os methods: -// - Lstat -// - Symlink, and -// - Readlink +// - Lstat +// - Symlink, and +// - Readlink type Symlinker interface { Lstater Linker diff --git a/vendor/github.com/spf13/afero/unionFile.go b/vendor/github.com/spf13/afero/unionFile.go index 333d367f4..62dd6c93c 100644 --- a/vendor/github.com/spf13/afero/unionFile.go +++ b/vendor/github.com/spf13/afero/unionFile.go @@ -47,7 +47,7 @@ func (f *UnionFile) Read(s []byte) (int, error) { if (err == nil || err == io.EOF) && f.Base != nil { // advance the file position also in the base file, the next // call may be a write at this position (or a seek with SEEK_CUR) - if _, seekErr := f.Base.Seek(int64(n), os.SEEK_CUR); seekErr != nil { + if _, seekErr := f.Base.Seek(int64(n), io.SeekCurrent); seekErr != nil { // only overwrite err in case the seek fails: we need to // report an eventual io.EOF to the caller err = seekErr @@ -130,7 +130,7 @@ func (f *UnionFile) Name() string { type DirsMerger func(lofi, bofi []os.FileInfo) ([]os.FileInfo, error) var defaultUnionMergeDirsFn = func(lofi, bofi []os.FileInfo) ([]os.FileInfo, error) { - var files = make(map[string]os.FileInfo) + files := make(map[string]os.FileInfo) for _, fi := range lofi { files[fi.Name()] = fi @@ -151,7 +151,6 @@ var defaultUnionMergeDirsFn = func(lofi, bofi []os.FileInfo) ([]os.FileInfo, err } return rfi, nil - } // Readdir will weave the two directories together and @@ -275,7 +274,7 @@ func copyFile(base Fs, layer Fs, name string, bfh File) error { return err } if !exists { - err = layer.MkdirAll(filepath.Dir(name), 0777) // FIXME? + err = layer.MkdirAll(filepath.Dir(name), 0o777) // FIXME? if err != nil { return err } diff --git a/vendor/github.com/spf13/afero/util.go b/vendor/github.com/spf13/afero/util.go index cb7de23f2..9e4cba274 100644 --- a/vendor/github.com/spf13/afero/util.go +++ b/vendor/github.com/spf13/afero/util.go @@ -43,7 +43,7 @@ func WriteReader(fs Fs, path string, r io.Reader) (err error) { ospath := filepath.FromSlash(dir) if ospath != "" { - err = fs.MkdirAll(ospath, 0777) // rwx, rw, r + err = fs.MkdirAll(ospath, 0o777) // rwx, rw, r if err != nil { if err != os.ErrExist { return err @@ -71,7 +71,7 @@ func SafeWriteReader(fs Fs, path string, r io.Reader) (err error) { ospath := filepath.FromSlash(dir) if ospath != "" { - err = fs.MkdirAll(ospath, 0777) // rwx, rw, r + err = fs.MkdirAll(ospath, 0o777) // rwx, rw, r if err != nil { return } @@ -124,7 +124,7 @@ func GetTempDir(fs Fs, subPath string) string { return addSlash(dir) } - err := fs.MkdirAll(dir, 0777) + err := fs.MkdirAll(dir, 0o777) if err != nil { panic(err) } @@ -197,7 +197,6 @@ func FileContainsAnyBytes(fs Fs, filename string, subslices [][]byte) (bool, err // readerContains reports whether any of the subslices is within r. func readerContainsAny(r io.Reader, subslices ...[]byte) bool { - if r == nil || len(subslices) == 0 { return false } diff --git a/vendor/github.com/tetafro/godot/.golangci.yml b/vendor/github.com/tetafro/godot/.golangci.yml index 920135d40..ea380eb83 100644 --- a/vendor/github.com/tetafro/godot/.golangci.yml +++ b/vendor/github.com/tetafro/godot/.golangci.yml @@ -8,45 +8,53 @@ skip-dirs: linters: disable-all: true enable: - - deadcode - - errcheck - - gosimple - - govet - - ineffassign - - staticcheck - - structcheck - - typecheck - - unused - - varcheck + - asciicheck - bodyclose + - cyclop - dogsled - - dupl - - funlen + - durationcheck + - errcheck + - errname + - errorlint + - exhaustive + - exportloopref + - exportloopref - gochecknoinits + - gocognit - goconst - gocritic - gocyclo - godot + - goerr113 - gofmt - gofumpt - goimports - - golint - - gomnd - - gomodguard - goprintffuncname - gosec + - gosimple + - govet + - importas + - ineffassign - lll - - maligned - misspell - nakedret - nestif + - noctx + - nolintlint - prealloc + - revive - rowserrcheck - - scopelint + - sqlclosecheck + - sqlclosecheck + - staticcheck - stylecheck + - typecheck - unconvert - unparam + - unused + - wastedassign - whitespace + - wrapcheck linters-settings: godot: @@ -54,13 +62,19 @@ linters-settings: issues: exclude-use-default: false + exclude: + - "do not define dynamic errors, use wrapped static errors instead" exclude-rules: - path: _test\.go linters: - dupl - errcheck - funlen + - gocognit + - cyclop - gosec - - path: cmd/godot/main\.go + - noctx + - path: main\.go linters: + - cyclop - gomnd diff --git a/vendor/github.com/tetafro/godot/checks.go b/vendor/github.com/tetafro/godot/checks.go index f5471cdf7..0e53c220a 100644 --- a/vendor/github.com/tetafro/godot/checks.go +++ b/vendor/github.com/tetafro/godot/checks.go @@ -21,7 +21,7 @@ var ( // Abbreviations to exclude from capital letters check. abbreviations = []string{"i.e.", "i. e.", "e.g.", "e. g.", "etc."} - // Special tags in comments like "// nolint:", or "// +k8s:". + // Special tags in comments like "//nolint:", or "//+k8s:". tags = regexp.MustCompile(`^\+?[a-z0-9]+:`) // Special hashtags in comments like "// #nosec". @@ -31,18 +31,24 @@ var ( endURL = regexp.MustCompile(`[a-z]+://[^\s]+$`) ) +// position is a position inside a comment (might be multiline comment). +type position struct { + line int // starts at 1 + column int // starts at 1, byte count +} + // checkComments checks every comment accordings to the rules from // `settings` argument. func checkComments(comments []comment, settings Settings) []Issue { - var issues []Issue // nolint: prealloc + var issues []Issue for _, c := range comments { if settings.Period { - if iss := checkCommentForPeriod(c); iss != nil { + if iss := checkPeriod(c); iss != nil { issues = append(issues, *iss) } } if settings.Capital { - if iss := checkCommentForCapital(c); len(iss) > 0 { + if iss := checkCapital(c); len(iss) > 0 { issues = append(issues, iss...) } } @@ -50,14 +56,34 @@ func checkComments(comments []comment, settings Settings) []Issue { return issues } -// checkCommentForPeriod checks that the last sentense of the comment ends +// checkPeriod checks that the last sentense of the comment ends // in a period. -func checkCommentForPeriod(c comment) *Issue { - pos, ok := checkPeriod(c.text) - if ok { +func checkPeriod(c comment) *Issue { + // Check last non-empty line + var found bool + var line string + var pos position + lines := strings.Split(c.text, "\n") + for i := len(lines) - 1; i >= 0; i-- { + line = strings.TrimRightFunc(lines[i], unicode.IsSpace) + if line == "" { + continue + } + found = true + pos.line = i + 1 + break + } + // All lines are empty + if !found { + return nil + } + // Correct line + if hasSuffix(line, lastChars) { return nil } + pos.column = len(line) + 1 + // Shift position to its real value. `c.text` doesn't contain comment's // special symbols: /* or //, and line indentations inside. It also // contains */ in the end in case of block comment. @@ -94,95 +120,15 @@ func checkCommentForPeriod(c comment) *Issue { return &iss } -// checkCommentForCapital checks that each sentense of the comment starts with -// a capital letter. -// nolint: unparam -func checkCommentForCapital(c comment) []Issue { - pp := checkCapital(c.text, c.decl) - if len(pp) == 0 { - return nil - } - - issues := make([]Issue, len(pp)) - for i, pos := range pp { - // Shift position by the length of comment's special symbols: /* or // - isBlock := strings.HasPrefix(c.lines[0], "/*") - if (isBlock && pos.line == 1) || !isBlock { - pos.column += 2 - } - - iss := Issue{ - Pos: token.Position{ - Filename: c.start.Filename, - Offset: c.start.Offset, - Line: pos.line + c.start.Line - 1, - Column: pos.column + c.start.Column - 1, - }, - Message: noCapitalMessage, - } - - // Make a replacement. Use `pos.original` to get an original original from - // attached lines. Use `iss.Pos.Column` because it's a position in - // the original original. - original := c.lines[pos.line-1] - col := byteToRuneColumn(original, iss.Pos.Column) - 1 - rep := string(unicode.ToTitle([]rune(original)[col])) // capital letter - if len(original) < iss.Pos.Column-1+len(rep) { - // This should never happen. Avoid panics, skip this check. - continue - } - iss.Replacement = original[:iss.Pos.Column-1] + rep + - original[iss.Pos.Column-1+len(rep):] - - // Save replacement to raw lines to be able to combine it with - // further replacements - c.lines[pos.line-1] = iss.Replacement - - issues[i] = iss - } - - return issues -} - -// checkPeriod checks that the last sentense of the text ends in a period. -// NOTE: Returned position is a position inside given text, not in the -// original file. -func checkPeriod(comment string) (pos position, ok bool) { - // Check last non-empty line - var found bool - var line string - lines := strings.Split(comment, "\n") - for i := len(lines) - 1; i >= 0; i-- { - line = strings.TrimRightFunc(lines[i], unicode.IsSpace) - if line == "" { - continue - } - found = true - pos.line = i + 1 - break - } - // All lines are empty - if !found { - return position{}, true - } - // Correct line - if hasSuffix(line, lastChars) { - return position{}, true - } - - pos.column = len(line) + 1 - return pos, false -} - -// checkCapital checks that each sentense of the text starts with +// checkCapital checks that each sentense of the comment starts with // a capital letter. -// NOTE: First letter is not checked in declaration comments, because they -// can describe unexported functions, which start with small letter. -func checkCapital(comment string, skipFirst bool) (pp []position) { +// +//nolint:cyclop,funlen +func checkCapital(c comment) []Issue { // Remove common abbreviations from the comment for _, abbr := range abbreviations { repl := strings.ReplaceAll(abbr, ".", "_") - comment = strings.ReplaceAll(comment, abbr, repl) + c.text = strings.ReplaceAll(c.text, abbr, repl) } // List of states during the scan: `empty` - nothing special, @@ -190,12 +136,14 @@ func checkCapital(comment string, skipFirst bool) (pp []position) { // `endOfSentence` - found `endChar`, and then space or newline. const empty, endChar, endOfSentence = 1, 2, 3 + var pp []position pos := position{line: 1} state := endOfSentence - if skipFirst { + if c.decl { + // Skip first state = empty } - for _, r := range comment { + for _, r := range c.text { s := string(r) pos.column++ @@ -223,12 +171,54 @@ func checkCapital(comment string, skipFirst bool) (pp []position) { if state == endOfSentence && unicode.IsLower(r) { pp = append(pp, position{ line: pos.line, - column: runeToByteColumn(comment, pos.column), + column: runeToByteColumn(c.text, pos.column), }) } state = empty } - return pp + if len(pp) == 0 { + return nil + } + + issues := make([]Issue, len(pp)) + for i, pos := range pp { + // Shift position by the length of comment's special symbols: /* or // + isBlock := strings.HasPrefix(c.lines[0], "/*") + if (isBlock && pos.line == 1) || !isBlock { + pos.column += 2 + } + + iss := Issue{ + Pos: token.Position{ + Filename: c.start.Filename, + Offset: c.start.Offset, + Line: pos.line + c.start.Line - 1, + Column: pos.column + c.start.Column - 1, + }, + Message: noCapitalMessage, + } + + // Make a replacement. Use `pos.original` to get an original original from + // attached lines. Use `iss.Pos.Column` because it's a position in + // the original original. + original := c.lines[pos.line-1] + col := byteToRuneColumn(original, iss.Pos.Column) - 1 + rep := string(unicode.ToTitle([]rune(original)[col])) // capital letter + if len(original) < iss.Pos.Column-1+len(rep) { + // This should never happen. Avoid panics, skip this check. + continue + } + iss.Replacement = original[:iss.Pos.Column-1] + rep + + original[iss.Pos.Column-1+len(rep):] + + // Save replacement to raw lines to be able to combine it with + // further replacements + c.lines[pos.line-1] = iss.Replacement + + issues[i] = iss + } + + return issues } // isSpecialBlock checks that given block of comment lines is special and @@ -246,7 +236,7 @@ func isSpecialBlock(comment string) bool { return false } -// isSpecialBlock checks that given comment line is special and +// isSpecialLine checks that given comment line is special and // shouldn't be checked as a regular sentence. func isSpecialLine(comment string) bool { // Skip cgo export tags: https://golang.org/cmd/cgo/#hdr-C_references_to_Go diff --git a/vendor/github.com/tetafro/godot/getters.go b/vendor/github.com/tetafro/godot/getters.go index 1a47c824f..7d3d22fb1 100644 --- a/vendor/github.com/tetafro/godot/getters.go +++ b/vendor/github.com/tetafro/godot/getters.go @@ -44,7 +44,7 @@ func newParsedFile(file *ast.File, fset *token.FileSet) (*parsedFile, error) { // from "go/format" won't help here if the original file is not gofmt-ed. pf.lines, err = readFile(file, fset) if err != nil { - return nil, fmt.Errorf("read file: %v", err) + return nil, fmt.Errorf("read file: %w", err) } // Dirty hack. For some cases Go generates temporary files during @@ -58,9 +58,13 @@ func newParsedFile(file *ast.File, fset *token.FileSet) (*parsedFile, error) { return nil, errUnsuitableInput } - // Check consistency to avoid checking slice indexes in each function + // Check consistency to avoid checking slice indexes in each function. + // Note that `PositionFor` is used with `adjusted=false` to skip `//line` + // directives that can set references to other files (e.g. templates) + // instead of the real ones, and break consistency here. + // Issue: https://github.com/tetafro/godot/issues/32 lastComment := pf.file.Comments[len(pf.file.Comments)-1] - if p := pf.fset.Position(lastComment.End()); len(pf.lines) < p.Line { + if p := pf.fset.PositionFor(lastComment.End(), false); len(pf.lines) < p.Line { return nil, fmt.Errorf("inconsistency between file and AST: %s", p.Filename) } @@ -82,7 +86,7 @@ func (pf *parsedFile) getComments(scope Scope, exclude []*regexp.Regexp) []comme pf.getBlockComments(exclude), pf.getTopLevelComments(exclude)..., ) - default: + case DeclScope: // Top level declaration comments and comments from the inside // of top level blocks comments = append(pf.getBlockComments(exclude), decl...) @@ -118,7 +122,7 @@ func (pf *parsedFile) getBlockComments(exclude []*regexp.Regexp) []comment { // Skip comments that are not top-level for this block // (the block itself is top level, so comments inside this block // would be on column 2) - // nolint: gomnd + //nolint:gomnd if pf.fset.Position(c.Pos()).Column != 2 { continue } @@ -136,7 +140,7 @@ func (pf *parsedFile) getBlockComments(exclude []*regexp.Regexp) []comment { // getTopLevelComments gets all top level comments. func (pf *parsedFile) getTopLevelComments(exclude []*regexp.Regexp) []comment { - var comments []comment // nolint: prealloc + var comments []comment //nolint:prealloc for _, c := range pf.file.Comments { if c == nil || len(c.List) == 0 { continue @@ -157,7 +161,7 @@ func (pf *parsedFile) getTopLevelComments(exclude []*regexp.Regexp) []comment { // getDeclarationComments gets top level declaration comments. func (pf *parsedFile) getDeclarationComments(exclude []*regexp.Regexp) []comment { - var comments []comment // nolint: prealloc + var comments []comment //nolint:prealloc for _, decl := range pf.file.Decls { var cg *ast.CommentGroup switch d := decl.(type) { @@ -184,7 +188,7 @@ func (pf *parsedFile) getDeclarationComments(exclude []*regexp.Regexp) []comment // getAllComments gets every single comment from the file. func (pf *parsedFile) getAllComments(exclude []*regexp.Regexp) []comment { - var comments []comment //nolint: prealloc + var comments []comment //nolint:prealloc for _, c := range pf.file.Comments { if c == nil || len(c.List) == 0 { continue @@ -205,6 +209,8 @@ func (pf *parsedFile) getAllComments(exclude []*regexp.Regexp) []comment { // special lines (e.g., tags or indented code examples), they are replaced // with `specialReplacer` to skip checks for them. // The result can be multiline. +// +//nolint:cyclop func getText(comment *ast.CommentGroup, exclude []*regexp.Regexp) (s string) { if len(comment.List) == 1 && strings.HasPrefix(comment.List[0].Text, "/*") && @@ -246,7 +252,7 @@ func readFile(file *ast.File, fset *token.FileSet) ([]string, error) { fname := fset.File(file.Package) f, err := os.ReadFile(fname.Name()) if err != nil { - return nil, err + return nil, err //nolint:wrapcheck } return strings.Split(string(f), "\n"), nil } diff --git a/vendor/github.com/tetafro/godot/godot.go b/vendor/github.com/tetafro/godot/godot.go index df9271296..e825e9a6d 100644 --- a/vendor/github.com/tetafro/godot/godot.go +++ b/vendor/github.com/tetafro/godot/godot.go @@ -3,6 +3,7 @@ package godot import ( + "errors" "fmt" "go/ast" "go/token" @@ -24,12 +25,6 @@ type Issue struct { Replacement string } -// position is a position inside a comment (might be multiline comment). -type position struct { - line int // starts at 1 - column int // starts at 1, byte count -} - // comment is an internal representation of AST comment entity with additional // data attached. The latter is used for creating a full replacement for // the line with issues. @@ -43,18 +38,18 @@ type comment struct { // Run runs this linter on the provided code. func Run(file *ast.File, fset *token.FileSet, settings Settings) ([]Issue, error) { pf, err := newParsedFile(file, fset) - if err == errEmptyInput || err == errUnsuitableInput { + if errors.Is(err, errEmptyInput) || errors.Is(err, errUnsuitableInput) { return nil, nil } if err != nil { - return nil, fmt.Errorf("parse input file: %v", err) + return nil, fmt.Errorf("parse input file: %w", err) } exclude := make([]*regexp.Regexp, len(settings.Exclude)) for i := 0; i < len(settings.Exclude); i++ { exclude[i], err = regexp.Compile(settings.Exclude[i]) if err != nil { - return nil, fmt.Errorf("invalid regexp: %v", err) + return nil, fmt.Errorf("invalid regexp: %w", err) } } @@ -68,9 +63,9 @@ func Run(file *ast.File, fset *token.FileSet, settings Settings) ([]Issue, error // Fix fixes all issues and returns new version of file content. func Fix(path string, file *ast.File, fset *token.FileSet, settings Settings) ([]byte, error) { // Read file - content, err := os.ReadFile(path) // nolint: gosec + content, err := os.ReadFile(path) //nolint:gosec if err != nil { - return nil, fmt.Errorf("read file: %v", err) + return nil, fmt.Errorf("read file: %w", err) } if len(content) == 0 { return nil, nil @@ -78,7 +73,7 @@ func Fix(path string, file *ast.File, fset *token.FileSet, settings Settings) ([ issues, err := Run(file, fset, settings) if err != nil { - return nil, fmt.Errorf("run linter: %v", err) + return nil, fmt.Errorf("run linter: %w", err) } // slice -> map @@ -105,17 +100,17 @@ func Fix(path string, file *ast.File, fset *token.FileSet, settings Settings) ([ func Replace(path string, file *ast.File, fset *token.FileSet, settings Settings) error { info, err := os.Stat(path) if err != nil { - return fmt.Errorf("check file: %v", err) + return fmt.Errorf("check file: %w", err) } mode := info.Mode() fixed, err := Fix(path, file, fset, settings) if err != nil { - return fmt.Errorf("fix issues: %v", err) + return fmt.Errorf("fix issues: %w", err) } if err := os.WriteFile(path, fixed, mode); err != nil { - return fmt.Errorf("write file: %v", err) + return fmt.Errorf("write file: %w", err) } return nil } diff --git a/vendor/github.com/ultraware/whitespace/README.md b/vendor/github.com/ultraware/whitespace/README.md index 2a88f1338..f99ecce36 100644 --- a/vendor/github.com/ultraware/whitespace/README.md +++ b/vendor/github.com/ultraware/whitespace/README.md @@ -4,4 +4,6 @@ Whitespace is a linter that checks for unnecessary newlines at the start and end ## Installation guide -Whitespace is included in [golangci-lint](https://github.com/golangci/golangci-lint/). Install it and enable whitespace. +To install as a standalone linter, run `go install github.com/ultraware/whitespace`. + +Whitespace is also included in [golangci-lint](https://github.com/golangci/golangci-lint/). Install it and enable whitespace. diff --git a/vendor/github.com/ultraware/whitespace/main.go b/vendor/github.com/ultraware/whitespace/main.go deleted file mode 100644 index d178ea293..000000000 --- a/vendor/github.com/ultraware/whitespace/main.go +++ /dev/null @@ -1,162 +0,0 @@ -package whitespace - -import ( - "go/ast" - "go/token" -) - -// Message contains a message -type Message struct { - Pos token.Position - Type MessageType - Message string -} - -// MessageType describes what should happen to fix the warning -type MessageType uint8 - -// List of MessageTypes -const ( - MessageTypeLeading MessageType = iota + 1 - MessageTypeTrailing - MessageTypeAddAfter -) - -// Settings contains settings for edge-cases -type Settings struct { - MultiIf bool - MultiFunc bool -} - -// Run runs this linter on the provided code -func Run(file *ast.File, fset *token.FileSet, settings Settings) []Message { - var messages []Message - - for _, f := range file.Decls { - decl, ok := f.(*ast.FuncDecl) - if !ok || decl.Body == nil { // decl.Body can be nil for e.g. cgo - continue - } - - vis := visitor{file.Comments, fset, nil, make(map[*ast.BlockStmt]bool), settings} - ast.Walk(&vis, decl) - - messages = append(messages, vis.messages...) - } - - return messages -} - -type visitor struct { - comments []*ast.CommentGroup - fset *token.FileSet - messages []Message - wantNewline map[*ast.BlockStmt]bool - settings Settings -} - -func (v *visitor) Visit(node ast.Node) ast.Visitor { - if node == nil { - return v - } - - if stmt, ok := node.(*ast.IfStmt); ok && v.settings.MultiIf { - checkMultiLine(v, stmt.Body, stmt.Cond) - } - - if stmt, ok := node.(*ast.FuncLit); ok && v.settings.MultiFunc { - checkMultiLine(v, stmt.Body, stmt.Type) - } - - if stmt, ok := node.(*ast.FuncDecl); ok && v.settings.MultiFunc { - checkMultiLine(v, stmt.Body, stmt.Type) - } - - if stmt, ok := node.(*ast.BlockStmt); ok { - wantNewline := v.wantNewline[stmt] - - comments := v.comments - if wantNewline { - comments = nil // Comments also count as a newline if we want a newline - } - first, last := firstAndLast(comments, v.fset, stmt.Pos(), stmt.End(), stmt.List) - - startMsg := checkStart(v.fset, stmt.Lbrace, first) - - if wantNewline && startMsg == nil { - v.messages = append(v.messages, Message{v.fset.Position(stmt.Pos()), MessageTypeAddAfter, `multi-line statement should be followed by a newline`}) - } else if !wantNewline && startMsg != nil { - v.messages = append(v.messages, *startMsg) - } - - if msg := checkEnd(v.fset, stmt.Rbrace, last); msg != nil { - v.messages = append(v.messages, *msg) - } - } - - return v -} - -func checkMultiLine(v *visitor, body *ast.BlockStmt, stmtStart ast.Node) { - start, end := posLine(v.fset, stmtStart.Pos()), posLine(v.fset, stmtStart.End()) - - if end > start { // Check only multi line conditions - v.wantNewline[body] = true - } -} - -func posLine(fset *token.FileSet, pos token.Pos) int { - return fset.Position(pos).Line -} - -func firstAndLast(comments []*ast.CommentGroup, fset *token.FileSet, start, end token.Pos, stmts []ast.Stmt) (ast.Node, ast.Node) { - if len(stmts) == 0 { - return nil, nil - } - - first, last := ast.Node(stmts[0]), ast.Node(stmts[len(stmts)-1]) - - for _, c := range comments { - if posLine(fset, c.Pos()) == posLine(fset, start) || posLine(fset, c.End()) == posLine(fset, end) { - continue - } - - if c.Pos() < start || c.End() > end { - continue - } - if c.Pos() < first.Pos() { - first = c - } - if c.End() > last.End() { - last = c - } - } - - return first, last -} - -func checkStart(fset *token.FileSet, start token.Pos, first ast.Node) *Message { - if first == nil { - return nil - } - - if posLine(fset, start)+1 < posLine(fset, first.Pos()) { - pos := fset.Position(start) - return &Message{pos, MessageTypeLeading, `unnecessary leading newline`} - } - - return nil -} - -func checkEnd(fset *token.FileSet, end token.Pos, last ast.Node) *Message { - if last == nil { - return nil - } - - if posLine(fset, end)-1 > posLine(fset, last.End()) { - pos := fset.Position(end) - return &Message{pos, MessageTypeTrailing, `unnecessary trailing newline`} - } - - return nil -} diff --git a/vendor/github.com/ultraware/whitespace/whitespace.go b/vendor/github.com/ultraware/whitespace/whitespace.go new file mode 100644 index 000000000..350e9b7e4 --- /dev/null +++ b/vendor/github.com/ultraware/whitespace/whitespace.go @@ -0,0 +1,307 @@ +package whitespace + +import ( + "flag" + "go/ast" + "go/token" + "strings" + + "golang.org/x/tools/go/analysis" +) + +// MessageType describes what should happen to fix the warning. +type MessageType uint8 + +// List of MessageTypes. +const ( + MessageTypeRemove MessageType = iota + 1 + MessageTypeAdd +) + +// RunningMode describes the mode the linter is run in. This can be either +// native or golangci-lint. +type RunningMode uint8 + +const ( + RunningModeNative RunningMode = iota + RunningModeGolangCI +) + +// Message contains a message and diagnostic information. +type Message struct { + // Diagnostic is what position the diagnostic should be put at. This isn't + // always the same as the fix start, f.ex. when we fix trailing newlines we + // put the diagnostic at the right bracket but we fix between the end of the + // last statement and the bracket. + Diagnostic token.Pos + + // FixStart is the span start of the fix. + FixStart token.Pos + + // FixEnd is the span end of the fix. + FixEnd token.Pos + + // LineNumbers represent the actual line numbers in the file. This is set + // when finding the diagnostic to make it easier to suggest fixes in + // golangci-lint. + LineNumbers []int + + // MessageType represents the type of message it is. + MessageType MessageType + + // Message is the diagnostic to show. + Message string +} + +// Settings contains settings for edge-cases. +type Settings struct { + Mode RunningMode + MultiIf bool + MultiFunc bool +} + +// NewAnalyzer creates a new whitespace analyzer. +func NewAnalyzer(settings *Settings) *analysis.Analyzer { + if settings == nil { + settings = &Settings{} + } + + return &analysis.Analyzer{ + Name: "whitespace", + Doc: "Whitespace is a linter that checks for unnecessary newlines at the start and end of functions, if, for, etc.", + Flags: flags(settings), + Run: func(p *analysis.Pass) (any, error) { + Run(p, settings) + return nil, nil + }, + RunDespiteErrors: true, + } +} + +func flags(settings *Settings) flag.FlagSet { + flags := flag.NewFlagSet("", flag.ExitOnError) + flags.BoolVar(&settings.MultiIf, "multi-if", settings.MultiIf, "Check that multi line if-statements have a leading newline") + flags.BoolVar(&settings.MultiFunc, "multi-func", settings.MultiFunc, "Check that multi line functions have a leading newline") + + return *flags +} + +func Run(pass *analysis.Pass, settings *Settings) []Message { + messages := []Message{} + + for _, file := range pass.Files { + filename := pass.Fset.Position(file.Pos()).Filename + if !strings.HasSuffix(filename, ".go") { + continue + } + + fileMessages := runFile(file, pass.Fset, *settings) + + if settings.Mode == RunningModeGolangCI { + messages = append(messages, fileMessages...) + continue + } + + for _, message := range fileMessages { + pass.Report(analysis.Diagnostic{ + Pos: message.Diagnostic, + Category: "whitespace", + Message: message.Message, + SuggestedFixes: []analysis.SuggestedFix{ + { + TextEdits: []analysis.TextEdit{ + { + Pos: message.FixStart, + End: message.FixEnd, + NewText: []byte("\n"), + }, + }, + }, + }, + }) + } + } + + return messages +} + +func runFile(file *ast.File, fset *token.FileSet, settings Settings) []Message { + var messages []Message + + for _, f := range file.Decls { + decl, ok := f.(*ast.FuncDecl) + if !ok || decl.Body == nil { // decl.Body can be nil for e.g. cgo + continue + } + + vis := visitor{file.Comments, fset, nil, make(map[*ast.BlockStmt]bool), settings} + ast.Walk(&vis, decl) + + messages = append(messages, vis.messages...) + } + + return messages +} + +type visitor struct { + comments []*ast.CommentGroup + fset *token.FileSet + messages []Message + wantNewline map[*ast.BlockStmt]bool + settings Settings +} + +func (v *visitor) Visit(node ast.Node) ast.Visitor { + if node == nil { + return v + } + + if stmt, ok := node.(*ast.IfStmt); ok && v.settings.MultiIf { + checkMultiLine(v, stmt.Body, stmt.Cond) + } + + if stmt, ok := node.(*ast.FuncLit); ok && v.settings.MultiFunc { + checkMultiLine(v, stmt.Body, stmt.Type) + } + + if stmt, ok := node.(*ast.FuncDecl); ok && v.settings.MultiFunc { + checkMultiLine(v, stmt.Body, stmt.Type) + } + + if stmt, ok := node.(*ast.BlockStmt); ok { + wantNewline := v.wantNewline[stmt] + + comments := v.comments + if wantNewline { + comments = nil // Comments also count as a newline if we want a newline + } + + opening, first, last := firstAndLast(comments, v.fset, stmt) + startMsg := checkStart(v.fset, opening, first) + + if wantNewline && startMsg == nil && len(stmt.List) >= 1 { + v.messages = append(v.messages, Message{ + Diagnostic: opening, + FixStart: stmt.List[0].Pos(), + FixEnd: stmt.List[0].Pos(), + LineNumbers: []int{v.fset.PositionFor(stmt.List[0].Pos(), false).Line}, + MessageType: MessageTypeAdd, + Message: "multi-line statement should be followed by a newline", + }) + } else if !wantNewline && startMsg != nil { + v.messages = append(v.messages, *startMsg) + } + + if msg := checkEnd(v.fset, stmt.Rbrace, last); msg != nil { + v.messages = append(v.messages, *msg) + } + } + + return v +} + +func checkMultiLine(v *visitor, body *ast.BlockStmt, stmtStart ast.Node) { + start, end := posLine(v.fset, stmtStart.Pos()), posLine(v.fset, stmtStart.End()) + + if end > start { // Check only multi line conditions + v.wantNewline[body] = true + } +} + +func posLine(fset *token.FileSet, pos token.Pos) int { + return fset.PositionFor(pos, false).Line +} + +func firstAndLast(comments []*ast.CommentGroup, fset *token.FileSet, stmt *ast.BlockStmt) (token.Pos, ast.Node, ast.Node) { + openingPos := stmt.Lbrace + 1 + + if len(stmt.List) == 0 { + return openingPos, nil, nil + } + + first, last := ast.Node(stmt.List[0]), ast.Node(stmt.List[len(stmt.List)-1]) + + for _, c := range comments { + // If the comment is on the same line as the opening pos (initially the + // left bracket) but it starts after the pos the comment must be after + // the bracket and where that comment ends should be considered where + // the fix should start. + if posLine(fset, c.Pos()) == posLine(fset, openingPos) && c.Pos() > openingPos { + if posLine(fset, c.End()) != posLine(fset, openingPos) { + // This is a multiline comment that spans from the `LBrace` line + // to a line further down. This should always be seen as ok! + first = c + } else { + openingPos = c.End() + } + } + + if posLine(fset, c.Pos()) == posLine(fset, stmt.Pos()) || posLine(fset, c.End()) == posLine(fset, stmt.End()) { + continue + } + + if c.Pos() < stmt.Pos() || c.End() > stmt.End() { + continue + } + + if c.Pos() < first.Pos() { + first = c + } + + if c.End() > last.End() { + last = c + } + } + + return openingPos, first, last +} + +func checkStart(fset *token.FileSet, start token.Pos, first ast.Node) *Message { + if first == nil { + return nil + } + + if posLine(fset, start)+1 < posLine(fset, first.Pos()) { + return &Message{ + Diagnostic: start, + FixStart: start, + FixEnd: first.Pos(), + LineNumbers: linesBetween(fset, start, first.Pos()), + MessageType: MessageTypeRemove, + Message: "unnecessary leading newline", + } + } + + return nil +} + +func checkEnd(fset *token.FileSet, end token.Pos, last ast.Node) *Message { + if last == nil { + return nil + } + + if posLine(fset, end)-1 > posLine(fset, last.End()) { + return &Message{ + Diagnostic: end, + FixStart: last.End(), + FixEnd: end, + LineNumbers: linesBetween(fset, last.End(), end), + MessageType: MessageTypeRemove, + Message: "unnecessary trailing newline", + } + } + + return nil +} + +func linesBetween(fset *token.FileSet, a, b token.Pos) []int { + lines := []int{} + aPosition := fset.PositionFor(a, false) + bPosition := fset.PositionFor(b, false) + + for i := aPosition.Line + 1; i < bPosition.Line; i++ { + lines = append(lines, i) + } + + return lines +} diff --git a/vendor/github.com/ykadowak/zerologlint/.goreleaser.yaml b/vendor/github.com/ykadowak/zerologlint/.goreleaser.yaml index c1b23f00e..f3af3f212 100644 --- a/vendor/github.com/ykadowak/zerologlint/.goreleaser.yaml +++ b/vendor/github.com/ykadowak/zerologlint/.goreleaser.yaml @@ -11,13 +11,6 @@ builds: - linux - windows - darwin -archives: - - replacements: - darwin: Darwin - linux: Linux - windows: Windows - 386: i386 - amd64: x86_64 checksum: name_template: 'checksums.txt' snapshot: diff --git a/vendor/github.com/ykadowak/zerologlint/zerologlint.go b/vendor/github.com/ykadowak/zerologlint/zerologlint.go index bec50e52b..8c8fb74fc 100644 --- a/vendor/github.com/ykadowak/zerologlint/zerologlint.go +++ b/vendor/github.com/ykadowak/zerologlint/zerologlint.go @@ -13,8 +13,8 @@ import ( ) var Analyzer = &analysis.Analyzer{ - Name: "zerologlinter", - Doc: "finds cases where zerolog methods are not followed by Msg or Send", + Name: "zerologlint", + Doc: "Detects the wrong usage of `zerolog` that a user forgets to dispatch with `Send` or `Msg`", Run: run, Requires: []*analysis.Analyzer{ buildssa.Analyzer, @@ -26,42 +26,65 @@ type posser interface { Pos() token.Pos } -// posser is an interface just to hold both ssa.Call and ssa.Defer in our set +// callDefer is an interface just to hold both ssa.Call and ssa.Defer in our set type callDefer interface { Common() *ssa.CallCommon Pos() token.Pos } -func run(pass *analysis.Pass) (interface{}, error) { - srcFuncs := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs - - // This set holds all the ssa block that is a zerolog.Event type instance +type linter struct { + // eventSet holds all the ssa block that is a zerolog.Event type instance // that should be dispatched. // Everytime the zerolog.Event is dispatched with Msg() or Send(), // deletes that block from this set. // At the end, check if the set is empty, or report the not dispatched block. - set := make(map[posser]struct{}) + eventSet map[posser]struct{} + // deleteLater holds the ssa block that should be deleted from eventSet after + // all the inspection is done. + // this is required because `else` ssa block comes after the dispatch of `if`` block. + // e.g., if err != nil { log.Error() } else { log.Info() } log.Send() + // deleteLater takes care of the log.Info() block. + deleteLater map[posser]struct{} + recLimit uint +} + +func run(pass *analysis.Pass) (interface{}, error) { + srcFuncs := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs + + l := &linter{ + eventSet: make(map[posser]struct{}), + deleteLater: make(map[posser]struct{}), + recLimit: 100, + } for _, sf := range srcFuncs { for _, b := range sf.Blocks { for _, instr := range b.Instrs { if c, ok := instr.(*ssa.Call); ok { - inspect(c, &set) + l.inspect(c) } else if c, ok := instr.(*ssa.Defer); ok { - inspect(c, &set) + l.inspect(c) } } } } + + // apply deleteLater to envetSet for else branches of if-else cases + + for k := range l.deleteLater { + delete(l.eventSet, k) + } + // At the end, if the set is clear -> ok. - // Otherwise, there must be a left zerolog.Event var that weren't dispached. So report it. - for k := range set { + // Otherwise, there must be a left zerolog.Event var that weren't dispatched. So report it. + for k := range l.eventSet { pass.Reportf(k.Pos(), "must be dispatched by Msg or Send method") } + return nil, nil } -func inspect(cd callDefer, set *map[posser]struct{}) { +func (l *linter) inspect(cd callDefer) { c := cd.Common() // check if it's in github.com/rs/zerolog/log since there's some @@ -70,7 +93,7 @@ func inspect(cd callDefer, set *map[posser]struct{}) { if isInLogPkg(*c) || isLoggerRecv(*c) { if isZerologEvent(c.Value) { // this ssa block should be dispatched afterwards at some point - (*set)[cd] = struct{}{} + l.eventSet[cd] = struct{}{} return } } @@ -88,7 +111,7 @@ func inspect(cd callDefer, set *map[posser]struct{}) { for _, p := range f.Params { if isZerologEvent(p) { // check if this zerolog.Event as a parameter is dispatched in the function - // TODO: specifically, it can be dispatched in another function that is called in this function, and + // TODO: technically, it can be dispatched in another function that is called in this function, and // this algorithm cannot track that. But I'm tired of thinking about that for now. for _, b := range f.Blocks { for _, instr := range b.Instrs { @@ -96,10 +119,12 @@ func inspect(cd callDefer, set *map[posser]struct{}) { case *ssa.Call: if inspectDispatchInFunction(v.Common()) { shouldReturn = false + break } case *ssa.Defer: if inspectDispatchInFunction(v.Common()) { shouldReturn = false + break } } } @@ -112,19 +137,56 @@ func inspect(cd callDefer, set *map[posser]struct{}) { } for _, arg := range c.Args { if isZerologEvent(arg) { - val := getRootSsaValue(arg) - // if there's branch, remove both ways from the set - if phi, ok := val.(*ssa.Phi); ok { + // if there's branch, track both ways + // this is for the case like: + // logger := log.Info() + // if err != nil { + // logger = log.Error() + // } + // logger.Send() + // + // Similar case like below goes to the same root but that doesn't + // have any side effect. + // logger := log.Info() + // if err != nil { + // logger = logger.Str("a", "b") + // } + // logger.Send() + if phi, ok := arg.(*ssa.Phi); ok { for _, edge := range phi.Edges { - delete(*set, edge) + l.dfsEdge(edge, make(map[ssa.Value]struct{}), 0) } } else { - delete(*set, val) + val := getRootSsaValue(arg) + delete(l.eventSet, val) } } } } +func (l *linter) dfsEdge(v ssa.Value, visit map[ssa.Value]struct{}, cnt uint) { + // only for safety + if cnt > l.recLimit { + return + } + cnt++ + + if _, ok := visit[v]; ok { + return + } + visit[v] = struct{}{} + + val := getRootSsaValue(v) + phi, ok := val.(*ssa.Phi) + if !ok { + l.deleteLater[val] = struct{}{} + return + } + for _, edge := range phi.Edges { + l.dfsEdge(edge, visit, cnt) + } +} + func inspectDispatchInFunction(cc *ssa.CallCommon) bool { if isDispatchMethod(cc.StaticCallee()) { for _, arg := range cc.Args { |
