From 9b15f3fcb69bcee8e738e2bf23117608e37f84bd Mon Sep 17 00:00:00 2001 From: Marcin Federowicz Date: Wed, 11 Dec 2024 19:35:58 +0100 Subject: [PATCH] refactor: replace panic with error in rules (#1126) Co-authored-by: chavacava Co-authored-by: Oleksandr Redko --- lint/linter.go | 2 +- rule/add_constant.go | 15 ++++--- rule/argument_limit.go | 15 +++++-- rule/banned_characters.go | 28 +++++++++--- rule/cognitive_complexity.go | 14 ++++-- rule/comment_spacings.go | 14 ++++-- rule/comments_density.go | 14 ++++-- rule/context_as_argument.go | 28 ++++++++---- rule/cyclomatic.go | 14 ++++-- rule/defer.go | 26 +++++++---- rule/dot_imports.go | 18 +++++--- rule/enforce_map_style.go | 19 ++++++--- rule/enforce_repeated_arg_type_style.go | 54 ++++++++++++++++------- rule/enforce_slice_style.go | 16 ++++--- rule/error_strings.go | 15 +++++-- rule/exported.go | 17 +++++--- rule/file_header.go | 16 ++++--- rule/file_length_limit.go | 22 ++++++---- rule/filename_format.go | 19 ++++++--- rule/function_length.go | 31 +++++++++----- rule/function_result_limit.go | 17 +++++--- rule/import_alias_naming.go | 46 ++++++++++++++------ rule/imports_blocklist.go | 14 ++++-- rule/line_length_limit.go | 15 +++++-- rule/max_control_nesting.go | 20 ++++++--- rule/max_public_structs.go | 20 ++++++--- rule/receiver_naming.go | 20 ++++++--- rule/string_format.go | 57 ++++++++++++++----------- rule/struct_tag.go | 23 +++++++--- rule/unchecked_type_assertion.go | 21 ++++++--- rule/unhandled_error.go | 19 ++++++--- rule/unused_param.go | 19 ++++++--- rule/unused_receiver.go | 19 ++++++--- rule/utils.go | 10 ++++- rule/var_naming.go | 36 +++++++++++----- 35 files changed, 514 insertions(+), 239 deletions(-) diff --git a/lint/linter.go b/lint/linter.go index f00faca..6ecaa8d 100644 --- a/lint/linter.go +++ b/lint/linter.go @@ -106,7 +106,7 @@ func (l *Linter) Lint(packages [][]string, ruleSet []Rule, config Config) (<-cha for n := range packages { go func(pkg []string, gover *goversion.Version) { if err := l.lintPackage(pkg, gover, ruleSet, config, failures); err != nil { - fmt.Fprintln(os.Stderr, err) + fmt.Fprintln(os.Stderr, "error during linting: "+err.Error()) os.Exit(1) } wg.Done() diff --git a/rule/add_constant.go b/rule/add_constant.go index b8fd10a..9df1db7 100644 --- a/rule/add_constant.go +++ b/rule/add_constant.go @@ -1,6 +1,7 @@ package rule import ( + "errors" "fmt" "go/ast" "regexp" @@ -45,7 +46,7 @@ func (r *AddConstantRule) Apply(file *lint.File, arguments lint.Arguments) []lin var configureErr error r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) if configureErr != nil { - return []lint.Failure{lint.NewInternalFailure(configureErr.Error())} + return newInternalFailureError(configureErr) } var failures []lint.Failure @@ -232,35 +233,35 @@ func (r *AddConstantRule) configure(arguments lint.Arguments) error { } list, ok := v.(string) if !ok { - fmt.Errorf("invalid argument to the add-constant rule, string expected. Got '%v' (%T)", v, v) + return fmt.Errorf("invalid argument to the add-constant rule, string expected. Got '%v' (%T)", v, v) } r.allowList.add(kind, list) case "maxLitCount": sl, ok := v.(string) if !ok { - fmt.Errorf("invalid argument to the add-constant rule, expecting string representation of an integer. Got '%v' (%T)", v, v) + return fmt.Errorf("invalid argument to the add-constant rule, expecting string representation of an integer. Got '%v' (%T)", v, v) } limit, err := strconv.Atoi(sl) if err != nil { - fmt.Errorf("invalid argument to the add-constant rule, expecting string representation of an integer. Got '%v'", v) + return fmt.Errorf("invalid argument to the add-constant rule, expecting string representation of an integer. Got '%v'", v) } r.strLitLimit = limit case "ignoreFuncs": excludes, ok := v.(string) if !ok { - fmt.Errorf("invalid argument to the ignoreFuncs parameter of add-constant rule, string expected. Got '%v' (%T)", v, v) + return fmt.Errorf("invalid argument to the ignoreFuncs parameter of add-constant rule, string expected. Got '%v' (%T)", v, v) } for _, exclude := range strings.Split(excludes, ",") { exclude = strings.Trim(exclude, " ") if exclude == "" { - fmt.Errorf("invalid argument to the ignoreFuncs parameter of add-constant rule, expected regular expression must not be empty.") + return errors.New("invalid argument to the ignoreFuncs parameter of add-constant rule, expected regular expression must not be empty") } exp, err := regexp.Compile(exclude) if err != nil { - fmt.Errorf("invalid argument to the ignoreFuncs parameter of add-constant rule: regexp %q does not compile: %v", exclude, err) + return fmt.Errorf("invalid argument to the ignoreFuncs parameter of add-constant rule: regexp %q does not compile: %w", exclude, err) } r.ignoreFunctions = append(r.ignoreFunctions, exp) diff --git a/rule/argument_limit.go b/rule/argument_limit.go index b44e1fd..045de32 100644 --- a/rule/argument_limit.go +++ b/rule/argument_limit.go @@ -1,6 +1,7 @@ package rule import ( + "errors" "fmt" "go/ast" "sync" @@ -17,22 +18,28 @@ type ArgumentsLimitRule struct { const defaultArgumentsLimit = 8 -func (r *ArgumentsLimitRule) configure(arguments lint.Arguments) { +func (r *ArgumentsLimitRule) configure(arguments lint.Arguments) error { if len(arguments) < 1 { r.max = defaultArgumentsLimit - return + return nil } maxArguments, ok := arguments[0].(int64) // Alt. non panicking version if !ok { - panic(`invalid value passed as argument number to the "argument-limit" rule`) + return errors.New(`invalid value passed as argument number to the "argument-limit" rule`) } r.max = int(maxArguments) + return nil } // Apply applies the rule to given file. func (r *ArgumentsLimitRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(arguments) }) + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } var failures []lint.Failure diff --git a/rule/banned_characters.go b/rule/banned_characters.go index 926b32c..5f90a24 100644 --- a/rule/banned_characters.go +++ b/rule/banned_characters.go @@ -18,16 +18,30 @@ type BannedCharsRule struct { const bannedCharsRuleName = "banned-characters" -func (r *BannedCharsRule) configure(arguments lint.Arguments) { +func (r *BannedCharsRule) configure(arguments lint.Arguments) error { if len(arguments) > 0 { - checkNumberOfArguments(1, arguments, bannedCharsRuleName) - r.bannedCharList = r.getBannedCharsList(arguments) + err := checkNumberOfArguments(1, arguments, bannedCharsRuleName) + if err != nil { + return err + } + list, err := r.getBannedCharsList(arguments) + if err != nil { + return err + } + + r.bannedCharList = list } + return nil } // Apply applied the rule to the given file. func (r *BannedCharsRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(arguments) }) + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } var failures []lint.Failure onFailure := func(failure lint.Failure) { @@ -49,17 +63,17 @@ func (*BannedCharsRule) Name() string { } // getBannedCharsList converts arguments into the banned characters list -func (r *BannedCharsRule) getBannedCharsList(args lint.Arguments) []string { +func (r *BannedCharsRule) getBannedCharsList(args lint.Arguments) ([]string, error) { var bannedChars []string for _, char := range args { charStr, ok := char.(string) if !ok { - panic(fmt.Sprintf("Invalid argument for the %s rule: expecting a string, got %T", r.Name(), char)) + return nil, fmt.Errorf("invalid argument for the %s rule: expecting a string, got %T", r.Name(), char) } bannedChars = append(bannedChars, charStr) } - return bannedChars + return bannedChars, nil } type lintBannedCharsRule struct { diff --git a/rule/cognitive_complexity.go b/rule/cognitive_complexity.go index e943207..524d45e 100644 --- a/rule/cognitive_complexity.go +++ b/rule/cognitive_complexity.go @@ -19,23 +19,29 @@ type CognitiveComplexityRule struct { const defaultMaxCognitiveComplexity = 7 -func (r *CognitiveComplexityRule) configure(arguments lint.Arguments) { +func (r *CognitiveComplexityRule) configure(arguments lint.Arguments) error { if len(arguments) < 1 { r.maxComplexity = defaultMaxCognitiveComplexity - return + return nil } complexity, ok := arguments[0].(int64) if !ok { - panic(fmt.Sprintf("invalid argument type for cognitive-complexity, expected int64, got %T", arguments[0])) + return fmt.Errorf("invalid argument type for cognitive-complexity, expected int64, got %T", arguments[0]) } r.maxComplexity = int(complexity) + return nil } // Apply applies the rule to given file. func (r *CognitiveComplexityRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(arguments) }) + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } var failures []lint.Failure diff --git a/rule/comment_spacings.go b/rule/comment_spacings.go index 7bdc0e7..540c2c5 100644 --- a/rule/comment_spacings.go +++ b/rule/comment_spacings.go @@ -16,20 +16,26 @@ type CommentSpacingsRule struct { configureOnce sync.Once } -func (r *CommentSpacingsRule) configure(arguments lint.Arguments) { +func (r *CommentSpacingsRule) configure(arguments lint.Arguments) error { r.allowList = []string{} for _, arg := range arguments { allow, ok := arg.(string) // Alt. non panicking version if !ok { - panic(fmt.Sprintf("invalid argument %v for %s; expected string but got %T", arg, r.Name(), arg)) + return fmt.Errorf("invalid argument %v for %s; expected string but got %T", arg, r.Name(), arg) } r.allowList = append(r.allowList, `//`+allow) } + return nil } // Apply the rule. -func (r *CommentSpacingsRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(args) }) +func (r *CommentSpacingsRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } var failures []lint.Failure diff --git a/rule/comments_density.go b/rule/comments_density.go index 8cd4928..e60cff5 100644 --- a/rule/comments_density.go +++ b/rule/comments_density.go @@ -18,23 +18,29 @@ type CommentsDensityRule struct { const defaultMinimumCommentsPercentage = 0 -func (r *CommentsDensityRule) configure(arguments lint.Arguments) { +func (r *CommentsDensityRule) configure(arguments lint.Arguments) error { if len(arguments) < 1 { r.minimumCommentsDensity = defaultMinimumCommentsPercentage - return + return nil } var ok bool r.minimumCommentsDensity, ok = arguments[0].(int64) if !ok { - panic(fmt.Sprintf("invalid argument for %q rule: argument should be an int, got %T", r.Name(), arguments[0])) + return fmt.Errorf("invalid argument for %q rule: argument should be an int, got %T", r.Name(), arguments[0]) } + return nil } // Apply applies the rule to given file. func (r *CommentsDensityRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(arguments) }) + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + if configureErr != nil { + return newInternalFailureError(configureErr) + } + commentsLines := countDocLines(file.AST.Comments) statementsCount := countStatements(file.AST) density := (float32(commentsLines) / float32(statementsCount+commentsLines)) * 100 diff --git a/rule/context_as_argument.go b/rule/context_as_argument.go index f2f9aee..3c4b656 100644 --- a/rule/context_as_argument.go +++ b/rule/context_as_argument.go @@ -17,8 +17,13 @@ type ContextAsArgumentRule struct { } // Apply applies the rule to given file. -func (r *ContextAsArgumentRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(args) }) +func (r *ContextAsArgumentRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } var failures []lint.Failure for _, decl := range file.AST.Decls { @@ -59,27 +64,32 @@ func (*ContextAsArgumentRule) Name() string { return "context-as-argument" } -func (r *ContextAsArgumentRule) configure(arguments lint.Arguments) { - r.allowTypes = r.getAllowTypesFromArguments(arguments) +func (r *ContextAsArgumentRule) configure(arguments lint.Arguments) error { + types, err := r.getAllowTypesFromArguments(arguments) + if err != nil { + return err + } + r.allowTypes = types + return nil } -func (r *ContextAsArgumentRule) getAllowTypesFromArguments(args lint.Arguments) map[string]struct{} { +func (r *ContextAsArgumentRule) getAllowTypesFromArguments(args lint.Arguments) (map[string]struct{}, error) { allowTypesBefore := []string{} if len(args) >= 1 { 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])) + return nil, fmt.Errorf("invalid argument to the context-as-argument rule. Expecting a k,v map, got %T", args[0]) } for k, v := range argKV { switch k { case "allowTypesBefore": typesBefore, ok := v.(string) if !ok { - panic(fmt.Sprintf("Invalid argument to the context-as-argument.allowTypesBefore rule. Expecting a string, got %T", v)) + return nil, fmt.Errorf("invalid argument to the context-as-argument.allowTypesBefore rule. Expecting a string, got %T", v) } allowTypesBefore = append(allowTypesBefore, strings.Split(typesBefore, ",")...) default: - panic(fmt.Sprintf("Invalid argument to the context-as-argument rule. Unrecognized key %s", k)) + return nil, fmt.Errorf("invalid argument to the context-as-argument rule. Unrecognized key %s", k) } } } @@ -90,5 +100,5 @@ func (r *ContextAsArgumentRule) getAllowTypesFromArguments(args lint.Arguments) } result["context.Context"] = struct{}{} // context.Context is always allowed before another context.Context - return result + return result, nil } diff --git a/rule/cyclomatic.go b/rule/cyclomatic.go index 7f42a1c..ba20bd0 100644 --- a/rule/cyclomatic.go +++ b/rule/cyclomatic.go @@ -20,22 +20,28 @@ type CyclomaticRule struct { const defaultMaxCyclomaticComplexity = 10 -func (r *CyclomaticRule) configure(arguments lint.Arguments) { +func (r *CyclomaticRule) configure(arguments lint.Arguments) error { if len(arguments) < 1 { r.maxComplexity = defaultMaxCyclomaticComplexity - return + return nil } complexity, ok := arguments[0].(int64) // Alt. non panicking version if !ok { - panic(fmt.Sprintf("invalid argument for cyclomatic complexity; expected int but got %T", arguments[0])) + return fmt.Errorf("invalid argument for cyclomatic complexity; expected int but got %T", arguments[0]) } r.maxComplexity = int(complexity) + return nil } // Apply applies the rule to given file. func (r *CyclomaticRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(arguments) }) + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } var failures []lint.Failure for _, decl := range file.AST.Decls { diff --git a/rule/defer.go b/rule/defer.go index f6aae45..a784fa4 100644 --- a/rule/defer.go +++ b/rule/defer.go @@ -15,13 +15,23 @@ type DeferRule struct { configureOnce sync.Once } -func (r *DeferRule) configure(arguments lint.Arguments) { - r.allow = r.allowFromArgs(arguments) +func (r *DeferRule) configure(arguments lint.Arguments) error { + list, err := r.allowFromArgs(arguments) + if err != nil { + return err + } + r.allow = list + return nil } // Apply applies the rule to given file. func (r *DeferRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(arguments) }) + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } var failures []lint.Failure onFailure := func(failure lint.Failure) { @@ -39,7 +49,7 @@ func (*DeferRule) Name() string { return "defer" } -func (*DeferRule) allowFromArgs(args lint.Arguments) map[string]bool { +func (*DeferRule) allowFromArgs(args lint.Arguments) (map[string]bool, error) { if len(args) < 1 { allow := map[string]bool{ "loop": true, @@ -50,24 +60,24 @@ func (*DeferRule) allowFromArgs(args lint.Arguments) map[string]bool { "immediate-recover": true, } - return allow + return allow, nil } aa, ok := args[0].([]any) if !ok { - panic(fmt.Sprintf("Invalid argument '%v' for 'defer' rule. Expecting []string, got %T", args[0], args[0])) + return nil, fmt.Errorf("invalid argument '%v' for 'defer' rule. Expecting []string, got %T", args[0], args[0]) } allow := make(map[string]bool, len(aa)) for _, subcase := range aa { sc, ok := subcase.(string) if !ok { - panic(fmt.Sprintf("Invalid argument '%v' for 'defer' rule. Expecting string, got %T", subcase, subcase)) + return nil, fmt.Errorf("invalid argument '%v' for 'defer' rule. Expecting string, got %T", subcase, subcase) } allow[sc] = true } - return allow + return allow, nil } type lintDeferRule struct { diff --git a/rule/dot_imports.go b/rule/dot_imports.go index 3bbd37c..a0fc2a2 100644 --- a/rule/dot_imports.go +++ b/rule/dot_imports.go @@ -17,7 +17,12 @@ type DotImportsRule struct { // Apply applies the rule to given file. func (r *DotImportsRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(arguments) }) + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } var failures []lint.Failure @@ -41,30 +46,31 @@ func (*DotImportsRule) Name() string { return "dot-imports" } -func (r *DotImportsRule) configure(arguments lint.Arguments) { +func (r *DotImportsRule) configure(arguments lint.Arguments) error { r.allowedPackages = allowPackages{} if len(arguments) == 0 { - return + return nil } 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])) + return fmt.Errorf("invalid argument to the dot-imports rule. Expecting a k,v map, got %T", arguments[0]) } if allowedPkgArg, ok := args["allowedPackages"]; ok { pkgs, ok := allowedPkgArg.([]any) if !ok { - panic(fmt.Sprintf("Invalid argument to the dot-imports rule, []string expected. Got '%v' (%T)", allowedPkgArg, allowedPkgArg)) + return fmt.Errorf("invalid argument to the dot-imports rule, []string expected. Got '%v' (%T)", allowedPkgArg, allowedPkgArg) } for _, p := range pkgs { pkg, ok := p.(string) if !ok { - panic(fmt.Sprintf("Invalid argument to the dot-imports rule, string expected. Got '%v' (%T)", p, p)) + return fmt.Errorf("invalid argument to the dot-imports rule, string expected. Got '%v' (%T)", p, p) } r.allowedPackages.add(pkg) } } + return nil } type lintImports struct { diff --git a/rule/enforce_map_style.go b/rule/enforce_map_style.go index 7ddf31e..e85efdf 100644 --- a/rule/enforce_map_style.go +++ b/rule/enforce_map_style.go @@ -44,35 +44,40 @@ type EnforceMapStyleRule struct { configureOnce sync.Once } -func (r *EnforceMapStyleRule) configure(arguments lint.Arguments) { +func (r *EnforceMapStyleRule) configure(arguments lint.Arguments) error { if len(arguments) < 1 { r.enforceMapStyle = enforceMapStyleTypeAny - return + return nil } 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])) + return fmt.Errorf("invalid argument '%v' for 'enforce-map-style' rule. Expecting string, got %T", arguments[0], arguments[0]) } var err error r.enforceMapStyle, err = mapStyleFromString(enforceMapStyle) if err != nil { - panic(fmt.Sprintf("Invalid argument to the enforce-map-style rule: %v", err)) + return fmt.Errorf("invalid argument to the enforce-map-style rule: %w", err) } + + return nil } // Apply applies the rule to given file. func (r *EnforceMapStyleRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(arguments) }) + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } if r.enforceMapStyle == enforceMapStyleTypeAny { // this linter is not configured return nil } - var failures []lint.Failure - astFile := file.AST ast.Inspect(astFile, func(n ast.Node) bool { switch v := n.(type) { diff --git a/rule/enforce_repeated_arg_type_style.go b/rule/enforce_repeated_arg_type_style.go index 3f9712a..c5dcba8 100644 --- a/rule/enforce_repeated_arg_type_style.go +++ b/rule/enforce_repeated_arg_type_style.go @@ -16,14 +16,14 @@ const ( enforceRepeatedArgTypeStyleTypeFull enforceRepeatedArgTypeStyleType = "full" ) -func repeatedArgTypeStyleFromString(s string) enforceRepeatedArgTypeStyleType { +func repeatedArgTypeStyleFromString(s string) (enforceRepeatedArgTypeStyleType, error) { switch s { case string(enforceRepeatedArgTypeStyleTypeAny), "": - return enforceRepeatedArgTypeStyleTypeAny + return enforceRepeatedArgTypeStyleTypeAny, nil case string(enforceRepeatedArgTypeStyleTypeShort): - return enforceRepeatedArgTypeStyleTypeShort + return enforceRepeatedArgTypeStyleTypeShort, nil case string(enforceRepeatedArgTypeStyleTypeFull): - return enforceRepeatedArgTypeStyleTypeFull + return enforceRepeatedArgTypeStyleTypeFull, nil default: err := fmt.Errorf( "invalid repeated arg type style: %s (expecting one of %v)", @@ -35,7 +35,7 @@ func repeatedArgTypeStyleFromString(s string) enforceRepeatedArgTypeStyleType { }, ) - panic(fmt.Sprintf("Invalid argument to the enforce-repeated-arg-type-style rule: %v", err)) + return "", fmt.Errorf("invalid argument to the enforce-repeated-arg-type-style rule: %w", err) } } @@ -47,45 +47,67 @@ type EnforceRepeatedArgTypeStyleRule struct { configureOnce sync.Once } -func (r *EnforceRepeatedArgTypeStyleRule) configure(arguments lint.Arguments) { +func (r *EnforceRepeatedArgTypeStyleRule) configure(arguments lint.Arguments) error { r.funcArgStyle = enforceRepeatedArgTypeStyleTypeAny r.funcRetValStyle = enforceRepeatedArgTypeStyleTypeAny if len(arguments) == 0 { - return + return nil } switch funcArgStyle := arguments[0].(type) { case string: - r.funcArgStyle = repeatedArgTypeStyleFromString(funcArgStyle) - r.funcRetValStyle = repeatedArgTypeStyleFromString(funcArgStyle) + argstyle, err := repeatedArgTypeStyleFromString(funcArgStyle) + if err != nil { + return err + } + r.funcArgStyle = argstyle + valstyle, err := repeatedArgTypeStyleFromString(funcArgStyle) + if err != nil { + return err + } + r.funcRetValStyle = valstyle 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)) + return fmt.Errorf("invalid map value type for 'enforce-repeated-arg-type-style' rule. Expecting string, got %T", v) } - r.funcArgStyle = repeatedArgTypeStyleFromString(val) + valstyle, err := repeatedArgTypeStyleFromString(val) + if err != nil { + return err + } + r.funcArgStyle = valstyle 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)) + return fmt.Errorf("invalid map value '%v' for 'enforce-repeated-arg-type-style' rule. Expecting string, got %T", v, v) } - r.funcRetValStyle = repeatedArgTypeStyleFromString(val) + argstyle, err := repeatedArgTypeStyleFromString(val) + if err != nil { + return err + } + r.funcRetValStyle = argstyle default: - panic(fmt.Sprintf("Invalid map key for 'enforce-repeated-arg-type-style' rule. Expecting 'funcArgStyle' or 'funcRetValStyle', got %v", k)) + return fmt.Errorf("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])) + return fmt.Errorf("invalid argument '%v' for 'import-alias-naming' rule. Expecting string or map[string]string, got %T", arguments[0], arguments[0]) } + return nil } // Apply applies the rule to a given file. func (r *EnforceRepeatedArgTypeStyleRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(arguments) }) + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } if r.funcArgStyle == enforceRepeatedArgTypeStyleTypeAny && r.funcRetValStyle == enforceRepeatedArgTypeStyleTypeAny { // This linter is not configured, return no failures. diff --git a/rule/enforce_slice_style.go b/rule/enforce_slice_style.go index 7170379..b09c252 100644 --- a/rule/enforce_slice_style.go +++ b/rule/enforce_slice_style.go @@ -48,27 +48,33 @@ type EnforceSliceStyleRule struct { configureOnce sync.Once } -func (r *EnforceSliceStyleRule) configure(arguments lint.Arguments) { +func (r *EnforceSliceStyleRule) configure(arguments lint.Arguments) error { if len(arguments) < 1 { r.enforceSliceStyle = enforceSliceStyleTypeAny - return + return nil } 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])) + return fmt.Errorf("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)) + return fmt.Errorf("invalid argument to the enforce-slice-style rule: %w", err) } + return nil } // Apply applies the rule to given file. func (r *EnforceSliceStyleRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(arguments) }) + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } if r.enforceSliceStyle == enforceSliceStyleTypeAny { // this linter is not configured diff --git a/rule/error_strings.go b/rule/error_strings.go index a993368..514365d 100644 --- a/rule/error_strings.go +++ b/rule/error_strings.go @@ -1,6 +1,7 @@ package rule import ( + "fmt" "go/ast" "go/token" "strconv" @@ -19,7 +20,7 @@ type ErrorStringsRule struct { configureOnce sync.Once } -func (r *ErrorStringsRule) configure(arguments lint.Arguments) { +func (r *ErrorStringsRule) configure(arguments lint.Arguments) error { r.errorFunctions = map[string]map[string]struct{}{ "fmt": { "Errorf": {}, @@ -46,15 +47,21 @@ func (r *ErrorStringsRule) configure(arguments lint.Arguments) { } } if len(invalidCustomFunctions) != 0 { - panic("found invalid custom function: " + strings.Join(invalidCustomFunctions, ",")) + return fmt.Errorf("found invalid custom function: " + strings.Join(invalidCustomFunctions, ",")) } + return nil } // Apply applies the rule to given file. func (r *ErrorStringsRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { - var failures []lint.Failure + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) - r.configureOnce.Do(func() { r.configure(arguments) }) + if configureErr != nil { + return newInternalFailureError(configureErr) + } + + var failures []lint.Failure fileAst := file.AST walker := lintErrorStrings{ diff --git a/rule/exported.go b/rule/exported.go index 4bb56f4..3cd8452 100644 --- a/rule/exported.go +++ b/rule/exported.go @@ -70,7 +70,7 @@ type ExportedRule struct { configureOnce sync.Once } -func (r *ExportedRule) configure(arguments lint.Arguments) { +func (r *ExportedRule) configure(arguments lint.Arguments) error { r.disabledChecks = disabledChecks{PrivateReceivers: true, PublicInterfaces: true} r.stuttersMsg = "stutters" for _, flag := range arguments { @@ -96,17 +96,24 @@ func (r *ExportedRule) configure(arguments lint.Arguments) { case "disableChecksOnVariables": r.disabledChecks.Var = true default: - panic(fmt.Sprintf("Unknown configuration flag %s for %s rule", flag, r.Name())) + return fmt.Errorf("unknown configuration flag %s for %s rule", flag, r.Name()) } default: - panic(fmt.Sprintf("Invalid argument for the %s rule: expecting a string, got %T", r.Name(), flag)) + return fmt.Errorf("invalid argument for the %s rule: expecting a string, got %T", r.Name(), flag) } } + + return nil } // Apply applies the rule to given file. -func (r *ExportedRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(args) }) +func (r *ExportedRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } var failures []lint.Failure if file.IsTest() { diff --git a/rule/file_header.go b/rule/file_header.go index a8c9ea2..b706557 100644 --- a/rule/file_header.go +++ b/rule/file_header.go @@ -20,21 +20,27 @@ var ( singleRegexp = regexp.MustCompile("^//") ) -func (r *FileHeaderRule) configure(arguments lint.Arguments) { +func (r *FileHeaderRule) configure(arguments lint.Arguments) error { if len(arguments) < 1 { - return + return nil } var ok bool r.header, ok = arguments[0].(string) if !ok { - panic(fmt.Sprintf("invalid argument for \"file-header\" rule: argument should be a string, got %T", arguments[0])) + return fmt.Errorf(`invalid argument for "file-header" rule: argument should be a string, got %T`, arguments[0]) } + return nil } // Apply applies the rule to given file. func (r *FileHeaderRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(arguments) }) + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } if r.header == "" { return nil @@ -69,7 +75,7 @@ func (r *FileHeaderRule) Apply(file *lint.File, arguments lint.Arguments) []lint regex, err := regexp.Compile(r.header) if err != nil { - panic(err.Error()) + return newInternalFailureError(err) } if !regex.MatchString(comment) { diff --git a/rule/file_length_limit.go b/rule/file_length_limit.go index 0fe075c..e451e92 100644 --- a/rule/file_length_limit.go +++ b/rule/file_length_limit.go @@ -26,7 +26,12 @@ type FileLengthLimitRule struct { // Apply applies the rule to given file. func (r *FileLengthLimitRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(arguments) }) + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } if r.max <= 0 { // when max is negative or 0 the rule is disabled @@ -44,7 +49,7 @@ func (r *FileLengthLimitRule) Apply(file *lint.File, arguments lint.Arguments) [ } if err := scanner.Err(); err != nil { - panic(err.Error()) + return newInternalFailureError(err) } lines := all @@ -75,37 +80,38 @@ func (r *FileLengthLimitRule) Apply(file *lint.File, arguments lint.Arguments) [ } } -func (r *FileLengthLimitRule) configure(arguments lint.Arguments) { +func (r *FileLengthLimitRule) configure(arguments lint.Arguments) error { if len(arguments) < 1 { - return // use default + return nil // use default } argKV, ok := arguments[0].(map[string]any) if !ok { - panic(fmt.Sprintf(`invalid argument to the "file-length-limit" rule. Expecting a k,v map, got %T`, arguments[0])) + return fmt.Errorf(`invalid argument to the "file-length-limit" rule. Expecting a k,v map, got %T`, arguments[0]) } for k, v := range argKV { switch k { case "max": maxLines, ok := v.(int64) if !ok || maxLines < 0 { - panic(fmt.Sprintf(`invalid configuration value for max lines in "file-length-limit" rule; need positive int64 but got %T`, arguments[0])) + return fmt.Errorf(`invalid configuration value for max lines in "file-length-limit" rule; need positive int64 but got %T`, arguments[0]) } r.max = int(maxLines) case "skipComments": skipComments, ok := v.(bool) if !ok { - panic(fmt.Sprintf(`invalid configuration value for skip comments in "file-length-limit" rule; need bool but got %T`, arguments[1])) + return fmt.Errorf(`invalid configuration value for skip comments in "file-length-limit" rule; need bool but got %T`, arguments[1]) } r.skipComments = skipComments case "skipBlankLines": skipBlankLines, ok := v.(bool) if !ok { - panic(fmt.Sprintf(`invalid configuration value for skip blank lines in "file-length-limit" rule; need bool but got %T`, arguments[2])) + return fmt.Errorf(`invalid configuration value for skip blank lines in "file-length-limit" rule; need bool but got %T`, arguments[2]) } r.skipBlankLines = skipBlankLines } } + return nil } // Name returns the rule name. diff --git a/rule/filename_format.go b/rule/filename_format.go index dbdb42d..4a5af80 100644 --- a/rule/filename_format.go +++ b/rule/filename_format.go @@ -19,7 +19,12 @@ type FilenameFormatRule struct { // Apply applies the rule to the given file. func (r *FilenameFormatRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(arguments) }) + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } filename := filepath.Base(file.Name) if r.format.MatchString(filename) { @@ -55,27 +60,29 @@ func (*FilenameFormatRule) Name() string { var defaultFormat = regexp.MustCompile(`^[_A-Za-z0-9][_A-Za-z0-9-]*\.go$`) -func (r *FilenameFormatRule) configure(arguments lint.Arguments) { +func (r *FilenameFormatRule) configure(arguments lint.Arguments) error { argsCount := len(arguments) if argsCount == 0 { r.format = defaultFormat - return + return nil } if argsCount > 1 { - panic(fmt.Sprintf("rule %q expects only one argument, got %d %v", r.Name(), argsCount, arguments)) + return fmt.Errorf("rule %q expects only one argument, got %d %v", r.Name(), argsCount, arguments) } arg := arguments[0] str, ok := arg.(string) if !ok { - panic(fmt.Sprintf("rule %q expects a string argument, got %v of type %T", r.Name(), arg, arg)) + return fmt.Errorf("rule %q expects a string argument, got %v of type %T", r.Name(), arg, arg) } format, err := regexp.Compile(str) if err != nil { - panic(fmt.Sprintf("rule %q expects a valid regexp argument, got %v for %s", r.Name(), err, arg)) + return fmt.Errorf("rule %q expects a valid regexp argument, got error for %s: %w", r.Name(), str, err) } r.format = format + + return nil } diff --git a/rule/function_length.go b/rule/function_length.go index 6f5a59e..693625f 100644 --- a/rule/function_length.go +++ b/rule/function_length.go @@ -17,15 +17,24 @@ type FunctionLength struct { configureOnce sync.Once } -func (r *FunctionLength) configure(arguments lint.Arguments) { - maxStmt, maxLines := r.parseArguments(arguments) +func (r *FunctionLength) configure(arguments lint.Arguments) error { + maxStmt, maxLines, err := r.parseArguments(arguments) + if err != nil { + return err + } r.maxStmt = int(maxStmt) r.maxLines = int(maxLines) + return nil } // Apply applies the rule to given file. func (r *FunctionLength) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(arguments) }) + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } var failures []lint.Failure for _, decl := range file.AST.Decls { @@ -74,33 +83,33 @@ func (*FunctionLength) Name() string { const defaultFuncStmtsLimit = 50 const defaultFuncLinesLimit = 75 -func (*FunctionLength) parseArguments(arguments lint.Arguments) (maxStmt, maxLines int64) { +func (*FunctionLength) parseArguments(arguments lint.Arguments) (maxStmt, maxLines int64, err error) { if len(arguments) == 0 { - return defaultFuncStmtsLimit, defaultFuncLinesLimit + return defaultFuncStmtsLimit, defaultFuncLinesLimit, nil } const minArguments = 2 if len(arguments) != minArguments { - panic(fmt.Sprintf(`invalid configuration for "function-length" rule, expected %d arguments but got %d`, minArguments, len(arguments))) + return 0, 0, fmt.Errorf(`invalid configuration for "function-length" rule, expected %d arguments but got %d`, minArguments, len(arguments)) } maxStmt, maxStmtOk := arguments[0].(int64) if !maxStmtOk { - panic(fmt.Sprintf(`invalid configuration value for max statements in "function-length" rule; need int64 but got %T`, arguments[0])) + return 0, 0, fmt.Errorf(`invalid configuration value for max statements in "function-length" rule; need int64 but got %T`, arguments[0]) } if maxStmt < 0 { - panic(fmt.Sprintf(`the configuration value for max statements in "function-length" rule cannot be negative, got %d`, maxStmt)) + return 0, 0, fmt.Errorf(`the configuration value for max statements in "function-length" rule cannot be negative, got %d`, maxStmt) } maxLines, maxLinesOk := arguments[1].(int64) if !maxLinesOk { - panic(fmt.Sprintf(`invalid configuration value for max lines in "function-length" rule; need int64 but got %T`, arguments[1])) + return 0, 0, fmt.Errorf(`invalid configuration value for max lines in "function-length" rule; need int64 but got %T`, arguments[1]) } if maxLines < 0 { - panic(fmt.Sprintf(`the configuration value for max statements in "function-length" rule cannot be negative, got %d`, maxLines)) + return 0, 0, fmt.Errorf(`the configuration value for max statements in "function-length" rule cannot be negative, got %d`, maxLines) } - return maxStmt, maxLines + return maxStmt, maxLines, nil } func (*FunctionLength) countLines(b *ast.BlockStmt, file *lint.File) int { diff --git a/rule/function_result_limit.go b/rule/function_result_limit.go index 0686545..17c4eae 100644 --- a/rule/function_result_limit.go +++ b/rule/function_result_limit.go @@ -1,6 +1,7 @@ package rule import ( + "errors" "fmt" "go/ast" "sync" @@ -17,7 +18,12 @@ type FunctionResultsLimitRule struct { // Apply applies the rule to given file. func (r *FunctionResultsLimitRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(arguments) }) + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } var failures []lint.Failure for _, decl := range file.AST.Decls { @@ -53,19 +59,20 @@ func (*FunctionResultsLimitRule) Name() string { const defaultResultsLimit = 3 -func (r *FunctionResultsLimitRule) configure(arguments lint.Arguments) { +func (r *FunctionResultsLimitRule) configure(arguments lint.Arguments) error { if len(arguments) < 1 { r.max = defaultResultsLimit - return + return nil } maxResults, ok := arguments[0].(int64) // Alt. non panicking version if !ok { - panic(fmt.Sprintf(`invalid value passed as return results number to the "function-result-limit" rule; need int64 but got %T`, arguments[0])) + return fmt.Errorf(`invalid value passed as return results number to the "function-result-limit" rule; need int64 but got %T`, arguments[0]) } if maxResults < 0 { - panic(`the value passed as return results number to the "function-result-limit" rule cannot be negative`) + return errors.New(`the value passed as return results number to the "function-result-limit" rule cannot be negative`) } r.max = int(maxResults) + return nil } diff --git a/rule/import_alias_naming.go b/rule/import_alias_naming.go index 043bf0d..b00f829 100644 --- a/rule/import_alias_naming.go +++ b/rule/import_alias_naming.go @@ -20,38 +20,54 @@ const defaultImportAliasNamingAllowRule = "^[a-z][a-z0-9]{0,}$" var defaultImportAliasNamingAllowRegexp = regexp.MustCompile(defaultImportAliasNamingAllowRule) -func (r *ImportAliasNamingRule) configure(arguments lint.Arguments) { +func (r *ImportAliasNamingRule) configure(arguments lint.Arguments) error { if len(arguments) == 0 { r.allowRegexp = defaultImportAliasNamingAllowRegexp - return + return nil } switch namingRule := arguments[0].(type) { case string: - r.setAllowRule(namingRule) + err := r.setAllowRule(namingRule) + if err != nil { + return err + } case map[string]any: // expecting map[string]string for k, v := range namingRule { switch k { case "allowRegex": - r.setAllowRule(v) + err := r.setAllowRule(v) + if err != nil { + return err + } case "denyRegex": - r.setDenyRule(v) + err := r.setDenyRule(v) + if err != nil { + return err + } + default: - panic(fmt.Sprintf("Invalid map key for 'import-alias-naming' rule. Expecting 'allowRegex' or 'denyRegex', got %v", k)) + return fmt.Errorf("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])) + return fmt.Errorf("invalid argument '%v' for 'import-alias-naming' rule. Expecting string or map[string]string, got %T", arguments[0], arguments[0]) } if r.allowRegexp == nil && r.denyRegexp == nil { r.allowRegexp = defaultImportAliasNamingAllowRegexp } + return nil } // Apply applies the rule to given file. func (r *ImportAliasNamingRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(arguments) }) + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } var failures []lint.Failure @@ -93,28 +109,30 @@ func (*ImportAliasNamingRule) Name() string { return "import-alias-naming" } -func (r *ImportAliasNamingRule) setAllowRule(value any) { +func (r *ImportAliasNamingRule) setAllowRule(value any) error { namingRule, ok := value.(string) if !ok { - panic(fmt.Sprintf("Invalid argument '%v' for import-alias-naming allowRegexp rule. Expecting string, got %T", value, value)) + return fmt.Errorf("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)) + return fmt.Errorf("invalid argument to the import-alias-naming allowRegexp rule. Expecting %q to be a valid regular expression, got: %w", namingRule, err) } r.allowRegexp = namingRuleRegexp + return nil } -func (r *ImportAliasNamingRule) setDenyRule(value any) { +func (r *ImportAliasNamingRule) setDenyRule(value any) error { namingRule, ok := value.(string) if !ok { - panic(fmt.Sprintf("Invalid argument '%v' for import-alias-naming denyRegexp rule. Expecting string, got %T", value, value)) + return fmt.Errorf("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)) + return fmt.Errorf("invalid argument to the import-alias-naming denyRegexp rule. Expecting %q to be a valid regular expression, got: %w", namingRule, err) } r.denyRegexp = namingRuleRegexp + return nil } diff --git a/rule/imports_blocklist.go b/rule/imports_blocklist.go index 6ae8be1..032681d 100644 --- a/rule/imports_blocklist.go +++ b/rule/imports_blocklist.go @@ -17,19 +17,20 @@ type ImportsBlocklistRule struct { var replaceImportRegexp = regexp.MustCompile(`/?\*\*/?`) -func (r *ImportsBlocklistRule) configure(arguments lint.Arguments) { +func (r *ImportsBlocklistRule) configure(arguments lint.Arguments) error { r.blocklist = []*regexp.Regexp{} for _, arg := range arguments { argStr, ok := arg.(string) if !ok { - panic(fmt.Sprintf("Invalid argument to the imports-blocklist rule. Expecting a string, got %T", arg)) + return fmt.Errorf("invalid argument to the imports-blocklist rule. Expecting a string, got %T", arg) } regStr, err := regexp.Compile(fmt.Sprintf(`(?m)"%s"$`, replaceImportRegexp.ReplaceAllString(argStr, `(\W|\w)*`))) if err != nil { - panic(fmt.Sprintf("Invalid argument to the imports-blocklist rule. Expecting %q to be a valid regular expression, got: %v", argStr, err)) + return fmt.Errorf("invalid argument to the imports-blocklist rule. Expecting %q to be a valid regular expression, got: %w", argStr, err) } r.blocklist = append(r.blocklist, regStr) } + return nil } func (r *ImportsBlocklistRule) isBlocklisted(path string) bool { @@ -43,7 +44,12 @@ func (r *ImportsBlocklistRule) isBlocklisted(path string) bool { // Apply applies the rule to given file. func (r *ImportsBlocklistRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(arguments) }) + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } var failures []lint.Failure diff --git a/rule/line_length_limit.go b/rule/line_length_limit.go index dcac2ad..74002d1 100644 --- a/rule/line_length_limit.go +++ b/rule/line_length_limit.go @@ -3,6 +3,7 @@ package rule import ( "bufio" "bytes" + "errors" "fmt" "go/token" "strings" @@ -21,23 +22,29 @@ type LineLengthLimitRule struct { const defaultLineLengthLimit = 80 -func (r *LineLengthLimitRule) configure(arguments lint.Arguments) { +func (r *LineLengthLimitRule) configure(arguments lint.Arguments) error { if len(arguments) < 1 { r.max = defaultLineLengthLimit - return + return nil } maxLength, ok := arguments[0].(int64) // Alt. non panicking version if !ok || maxLength < 0 { - panic(`invalid value passed as argument number to the "line-length-limit" rule`) + return errors.New(`invalid value passed as argument number to the "line-length-limit" rule`) } r.max = int(maxLength) + return nil } // Apply applies the rule to given file. func (r *LineLengthLimitRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(arguments) }) + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } var failures []lint.Failure diff --git a/rule/max_control_nesting.go b/rule/max_control_nesting.go index 2898af6..2f88384 100644 --- a/rule/max_control_nesting.go +++ b/rule/max_control_nesting.go @@ -1,6 +1,7 @@ package rule import ( + "errors" "fmt" "go/ast" "sync" @@ -19,7 +20,12 @@ const defaultMaxControlNesting = 5 // Apply applies the rule to given file. func (r *MaxControlNestingRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(arguments) }) + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } var failures []lint.Failure @@ -107,17 +113,21 @@ func (w *lintMaxControlNesting) walkControlledBlock(b ast.Node) { w.nestingLevelAcc = oldNestingLevel } -func (r *MaxControlNestingRule) configure(arguments lint.Arguments) { +func (r *MaxControlNestingRule) configure(arguments lint.Arguments) error { if len(arguments) < 1 { r.max = defaultMaxControlNesting - return + return nil } - checkNumberOfArguments(1, arguments, r.Name()) + check := checkNumberOfArguments(1, arguments, r.Name()) + if check != nil { + return check + } maxNesting, ok := arguments[0].(int64) // Alt. non panicking version if !ok { - panic(`invalid value passed as argument number to the "max-control-nesting" rule`) + return errors.New(`invalid value passed as argument number to the "max-control-nesting" rule`) } r.max = maxNesting + return nil } diff --git a/rule/max_public_structs.go b/rule/max_public_structs.go index bbb2071..00dcf8c 100644 --- a/rule/max_public_structs.go +++ b/rule/max_public_structs.go @@ -1,6 +1,7 @@ package rule import ( + "errors" "fmt" "go/ast" "strings" @@ -18,24 +19,33 @@ type MaxPublicStructsRule struct { const defaultMaxPublicStructs = 5 -func (r *MaxPublicStructsRule) configure(arguments lint.Arguments) { +func (r *MaxPublicStructsRule) configure(arguments lint.Arguments) error { if len(arguments) < 1 { r.max = defaultMaxPublicStructs - return + return nil } - checkNumberOfArguments(1, arguments, r.Name()) + err := checkNumberOfArguments(1, arguments, r.Name()) + if err != nil { + return err + } maxStructs, ok := arguments[0].(int64) // Alt. non panicking version if !ok { - panic(`invalid value passed as argument number to the "max-public-structs" rule`) + return errors.New(`invalid value passed as argument number to the "max-public-structs" rule`) } r.max = maxStructs + return nil } // Apply applies the rule to given file. func (r *MaxPublicStructsRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(arguments) }) + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } var failures []lint.Failure diff --git a/rule/receiver_naming.go b/rule/receiver_naming.go index 1d66875..3e0cc56 100644 --- a/rule/receiver_naming.go +++ b/rule/receiver_naming.go @@ -18,15 +18,15 @@ type ReceiverNamingRule struct { const defaultReceiverNameMaxLength = -1 // thus will not check -func (r *ReceiverNamingRule) configure(arguments lint.Arguments) { +func (r *ReceiverNamingRule) configure(arguments lint.Arguments) error { r.receiverNameMaxLength = defaultReceiverNameMaxLength if len(arguments) < 1 { - return + return nil } args, ok := arguments[0].(map[string]any) if !ok { - panic(fmt.Sprintf("Unable to get arguments for rule %s. Expected object of key-value-pairs.", r.Name())) + return fmt.Errorf("unable to get arguments for rule %s. Expected object of key-value-pairs", r.Name()) } for k, v := range args { @@ -34,18 +34,24 @@ func (r *ReceiverNamingRule) configure(arguments lint.Arguments) { case "maxLength": value, ok := v.(int64) if !ok { - panic(fmt.Sprintf("Invalid value %v for argument %s of rule %s, expected integer value got %T", v, k, r.Name(), v)) + return fmt.Errorf("invalid value %v for argument %s of rule %s, expected integer value got %T", v, k, r.Name(), v) } r.receiverNameMaxLength = int(value) default: - panic(fmt.Sprintf("Unknown argument %s for %s rule.", k, r.Name())) + return fmt.Errorf("unknown argument %s for %s rule", k, r.Name()) } } + return nil } // Apply applies the rule to given file. -func (r *ReceiverNamingRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(args) }) +func (r *ReceiverNamingRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } typeReceiver := map[string]string{} var failures []lint.Failure diff --git a/rule/string_format.go b/rule/string_format.go index b47d64d..1061131 100644 --- a/rule/string_format.go +++ b/rule/string_format.go @@ -23,7 +23,11 @@ func (*StringFormatRule) Apply(file *lint.File, arguments lint.Arguments) []lint } w := lintStringFormatRule{onFailure: onFailure} - w.parseArguments(arguments) + err := w.parseArguments(arguments) + if err != nil { + return newInternalFailureError(err) + } + ast.Walk(w, file.AST) return failures @@ -41,10 +45,9 @@ func (StringFormatRule) ParseArgumentsTest(arguments lint.Arguments) *string { // 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() { - err := recover() + err := w.parseArguments(arguments) c <- err }() - w.parseArguments(arguments) }() err := <-c if err != nil { @@ -81,9 +84,12 @@ const identRegex = "[_A-Za-z][_A-Za-z0-9]*" var parseStringFormatScope = regexp.MustCompile( fmt.Sprintf("^(%s(?:\\.%s)?)(?:\\[([0-9]+)\\](?:\\.(%s))?)?$", identRegex, identRegex, identRegex)) -func (w *lintStringFormatRule) parseArguments(arguments lint.Arguments) { +func (w *lintStringFormatRule) parseArguments(arguments lint.Arguments) error { for i, argument := range arguments { - scopes, regex, negated, errorMessage := w.parseArgument(argument, i) + scopes, regex, negated, errorMessage, err := w.parseArgument(argument, i) + if err != nil { + return err + } w.rules = append(w.rules, stringFormatSubrule{ parent: w, scopes: scopes, @@ -92,30 +98,31 @@ func (w *lintStringFormatRule) parseArguments(arguments lint.Arguments) { errorMessage: errorMessage, }) } + return nil } -func (w lintStringFormatRule) parseArgument(argument any, ruleNum int) (scopes stringFormatSubruleScopes, regex *regexp.Regexp, negated bool, errorMessage string) { +func (w lintStringFormatRule) parseArgument(argument any, ruleNum int) (scopes stringFormatSubruleScopes, regex *regexp.Regexp, negated bool, errorMessage string, err error) { g, ok := argument.([]any) // Cast to generic slice first if !ok { - w.configError("argument is not a slice", ruleNum, 0) + return stringFormatSubruleScopes{}, regex, false, "", w.configError("argument is not a slice", ruleNum, 0) } if len(g) < 2 { - w.configError("less than two slices found in argument, scope and regex are required", ruleNum, len(g)-1) + return stringFormatSubruleScopes{}, regex, false, "", w.configError("less than two slices found in argument, scope and regex are required", ruleNum, len(g)-1) } rule := make([]string, len(g)) for i, obj := range g { val, ok := obj.(string) if !ok { - w.configError("unexpected value, string was expected", ruleNum, i) + return stringFormatSubruleScopes{}, regex, false, "", w.configError("unexpected value, string was expected", ruleNum, i) } rule[i] = val } // Validate scope and regex length if rule[0] == "" { - w.configError("empty scope provided", ruleNum, 0) + return stringFormatSubruleScopes{}, regex, false, "", w.configError("empty scope provided", ruleNum, 0) } else if len(rule[1]) < 2 { - w.configError("regex is too small (regexes should begin and end with '/')", ruleNum, 1) + return stringFormatSubruleScopes{}, regex, false, "", w.configError("regex is too small (regexes should begin and end with '/')", ruleNum, 1) } // Parse rule scopes @@ -126,24 +133,24 @@ func (w lintStringFormatRule) parseArgument(argument any, ruleNum int) (scopes s rawScope = strings.TrimSpace(rawScope) if len(rawScope) == 0 { - w.parseScopeError("empty scope in rule scopes:", ruleNum, 0, scopeNum) + return stringFormatSubruleScopes{}, regex, false, "", w.parseScopeError("empty scope in rule scopes:", ruleNum, 0, scopeNum) } scope := stringFormatSubruleScope{} matches := parseStringFormatScope.FindStringSubmatch(rawScope) if matches == nil { // The rule's scope didn't match the parsing regex at all, probably a configuration error - w.parseScopeError("unable to parse rule scope", ruleNum, 0, scopeNum) + return stringFormatSubruleScopes{}, regex, false, "", w.parseScopeError("unable to parse rule scope", ruleNum, 0, scopeNum) } else if len(matches) != 4 { // The rule's scope matched the parsing regex, but an unexpected number of submatches was returned, probably a bug - w.parseScopeError(fmt.Sprintf("unexpected number of submatches when parsing scope: %d, expected 4", len(matches)), ruleNum, 0, scopeNum) + return stringFormatSubruleScopes{}, regex, false, "", w.parseScopeError(fmt.Sprintf("unexpected number of submatches when parsing scope: %d, expected 4", len(matches)), ruleNum, 0, scopeNum) } scope.funcName = matches[1] if len(matches[2]) > 0 { var err error scope.argument, err = strconv.Atoi(matches[2]) if err != nil { - w.parseScopeError("unable to parse argument number in rule scope", ruleNum, 0, scopeNum) + return stringFormatSubruleScopes{}, regex, false, "", w.parseScopeError("unable to parse argument number in rule scope", ruleNum, 0, scopeNum) } } if len(matches[3]) > 0 { @@ -159,31 +166,31 @@ func (w lintStringFormatRule) parseArgument(argument any, ruleNum int) (scopes s if negated { offset++ } - regex, err := regexp.Compile(rule[1][offset : len(rule[1])-1]) - if err != nil { - w.parseError(fmt.Sprintf("unable to compile %s as regexp", rule[1]), ruleNum, 1) + regex, errr := regexp.Compile(rule[1][offset : len(rule[1])-1]) + if errr != nil { + return stringFormatSubruleScopes{}, regex, false, "", w.parseError(fmt.Sprintf("unable to compile %s as regexp", rule[1]), ruleNum, 1) } // Use custom error message if provided if len(rule) == 3 { errorMessage = rule[2] } - return scopes, regex, negated, errorMessage + return scopes, regex, negated, errorMessage, nil } // Report an invalid config, this is specifically the user's fault -func (lintStringFormatRule) configError(msg string, ruleNum, option int) { - panic(fmt.Sprintf("invalid configuration for string-format: %s [argument %d, option %d]", msg, ruleNum, option)) +func (lintStringFormatRule) configError(msg string, ruleNum, option int) error { + return fmt.Errorf("invalid configuration for string-format: %s [argument %d, option %d]", msg, ruleNum, option) } // Report a general config parsing failure, this may be the user's fault, but it isn't known for certain -func (lintStringFormatRule) parseError(msg string, ruleNum, option int) { - panic(fmt.Sprintf("failed to parse configuration for string-format: %s [argument %d, option %d]", msg, ruleNum, option)) +func (lintStringFormatRule) parseError(msg string, ruleNum, option int) error { + return fmt.Errorf("failed to parse configuration for string-format: %s [argument %d, option %d]", msg, ruleNum, option) } // Report a general scope config parsing failure, this may be the user's fault, but it isn't known for certain -func (lintStringFormatRule) parseScopeError(msg string, ruleNum, option, scopeNum int) { - panic(fmt.Sprintf("failed to parse configuration for string-format: %s [argument %d, option %d, scope index %d]", msg, ruleNum, option, scopeNum)) +func (lintStringFormatRule) parseScopeError(msg string, ruleNum, option, scopeNum int) error { + return fmt.Errorf("failed to parse configuration for string-format: %s [argument %d, option %d, scope index %d]", msg, ruleNum, option, scopeNum) } func (w lintStringFormatRule) Visit(node ast.Node) ast.Visitor { diff --git a/rule/struct_tag.go b/rule/struct_tag.go index ac7ac21..b0fc327 100644 --- a/rule/struct_tag.go +++ b/rule/struct_tag.go @@ -18,21 +18,24 @@ type StructTagRule struct { configureOnce sync.Once } -func (r *StructTagRule) configure(arguments lint.Arguments) { +func (r *StructTagRule) configure(arguments lint.Arguments) error { if len(arguments) == 0 { - return + return nil } - checkNumberOfArguments(1, arguments, r.Name()) + err := checkNumberOfArguments(1, arguments, r.Name()) + if err != nil { + return err + } r.userDefined = make(map[string][]string, len(arguments)) for _, arg := range arguments { item, ok := arg.(string) if !ok { - panic(fmt.Sprintf("Invalid argument to the %s rule. Expecting a string, got %v (of type %T)", r.Name(), arg, arg)) + return fmt.Errorf("invalid argument to the %s rule. Expecting a string, got %v (of type %T)", r.Name(), arg, arg) } parts := strings.Split(item, ",") if len(parts) < 2 { - panic(fmt.Sprintf("Invalid argument to the %s rule. Expecting a string of the form key[,option]+, got %s", r.Name(), item)) + return fmt.Errorf("invalid argument to the %s rule. Expecting a string of the form key[,option]+, got %s", r.Name(), item) } key := strings.TrimSpace(parts[0]) for i := 1; i < len(parts); i++ { @@ -40,11 +43,17 @@ func (r *StructTagRule) configure(arguments lint.Arguments) { r.userDefined[key] = append(r.userDefined[key], option) } } + return nil } // Apply applies the rule to given file. -func (r *StructTagRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(args) }) +func (r *StructTagRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } var failures []lint.Failure onFailure := func(failure lint.Failure) { diff --git a/rule/unchecked_type_assertion.go b/rule/unchecked_type_assertion.go index 34d854e..987769e 100644 --- a/rule/unchecked_type_assertion.go +++ b/rule/unchecked_type_assertion.go @@ -1,6 +1,7 @@ package rule import ( + "errors" "fmt" "go/ast" "sync" @@ -20,14 +21,14 @@ type UncheckedTypeAssertionRule struct { configureOnce sync.Once } -func (r *UncheckedTypeAssertionRule) configure(arguments lint.Arguments) { +func (r *UncheckedTypeAssertionRule) configure(arguments lint.Arguments) error { if len(arguments) == 0 { - return + return nil } args, ok := arguments[0].(map[string]any) if !ok { - panic("Unable to get arguments. Expected object of key-value-pairs.") + return errors.New("unable to get arguments. Expected object of key-value-pairs") } for k, v := range args { @@ -35,17 +36,23 @@ func (r *UncheckedTypeAssertionRule) configure(arguments lint.Arguments) { case "acceptIgnoredAssertionResult": r.acceptIgnoredAssertionResult, ok = v.(bool) if !ok { - panic(fmt.Sprintf("Unable to parse argument '%s'. Expected boolean.", k)) + return fmt.Errorf("unable to parse argument '%s'. Expected boolean", k) } default: - panic(fmt.Sprintf("Unknown argument: %s", k)) + return fmt.Errorf("unknown argument: %s", k) } } + return nil } // Apply applies the rule to given file. -func (r *UncheckedTypeAssertionRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(args) }) +func (r *UncheckedTypeAssertionRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } var failures []lint.Failure diff --git a/rule/unhandled_error.go b/rule/unhandled_error.go index fabc4fb..3a0753c 100644 --- a/rule/unhandled_error.go +++ b/rule/unhandled_error.go @@ -1,6 +1,7 @@ package rule import ( + "errors" "fmt" "go/ast" "go/types" @@ -18,30 +19,36 @@ type UnhandledErrorRule struct { configureOnce sync.Once } -func (r *UnhandledErrorRule) configure(arguments lint.Arguments) { +func (r *UnhandledErrorRule) configure(arguments lint.Arguments) error { for _, arg := range arguments { argStr, ok := arg.(string) if !ok { - panic(fmt.Sprintf("Invalid argument to the unhandled-error rule. Expecting a string, got %T", arg)) + return fmt.Errorf("invalid argument to the unhandled-error rule. Expecting a string, got %T", arg) } argStr = strings.Trim(argStr, " ") if argStr == "" { - panic("Invalid argument to the unhandled-error rule, expected regular expression must not be empty.") + return errors.New("invalid argument to the unhandled-error rule, expected regular expression must not be empty") } exp, err := regexp.Compile(argStr) if err != nil { - panic(fmt.Sprintf("Invalid argument to the unhandled-error rule: regexp %q does not compile: %v", argStr, err)) + return fmt.Errorf("invalid argument to the unhandled-error rule: regexp %q does not compile: %w", argStr, err) } r.ignoreList = append(r.ignoreList, exp) } + return nil } // Apply applies the rule to given file. -func (r *UnhandledErrorRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(args) }) +func (r *UnhandledErrorRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } var failures []lint.Failure diff --git a/rule/unused_param.go b/rule/unused_param.go index 958cd89..374e899 100644 --- a/rule/unused_param.go +++ b/rule/unused_param.go @@ -20,20 +20,20 @@ type UnusedParamRule struct { configureOnce sync.Once } -func (r *UnusedParamRule) configure(args lint.Arguments) { +func (r *UnusedParamRule) configure(args lint.Arguments) error { // while by default args is an array, i think it's good to provide structures inside it by default, not arrays or primitives // it's more compatible to JSON nature of configurations r.allowRegex = allowBlankIdentifierRegex r.failureMsg = "parameter '%s' seems to be unused, consider removing or renaming it as _" if len(args) == 0 { - return + return nil } // Arguments = [{}] options := args[0].(map[string]any) allowRegexParam, ok := options["allowRegex"] if !ok { - return + return nil } // Arguments = [{allowRegex="^_"}] allowRegexStr, ok := allowRegexParam.(string) @@ -43,14 +43,21 @@ func (r *UnusedParamRule) configure(args lint.Arguments) { var err error r.allowRegex, err = regexp.Compile(allowRegexStr) if err != nil { - panic(fmt.Errorf("error configuring %s rule: allowRegex is not valid regex [%s]: %v", r.Name(), allowRegexStr, err)) + return fmt.Errorf("error configuring %s rule: allowRegex is not valid regex [%s]: %w", r.Name(), allowRegexStr, err) } r.failureMsg = "parameter '%s' seems to be unused, consider removing or renaming it to match " + r.allowRegex.String() + return nil } // Apply applies the rule to given file. -func (r *UnusedParamRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(args) }) +func (r *UnusedParamRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } + var failures []lint.Failure onFailure := func(failure lint.Failure) { diff --git a/rule/unused_receiver.go b/rule/unused_receiver.go index 173306d..2986068 100644 --- a/rule/unused_receiver.go +++ b/rule/unused_receiver.go @@ -18,20 +18,20 @@ type UnusedReceiverRule struct { configureOnce sync.Once } -func (r *UnusedReceiverRule) configure(args lint.Arguments) { +func (r *UnusedReceiverRule) configure(args lint.Arguments) error { // while by default args is an array, i think it's good to provide structures inside it by default, not arrays or primitives // it's more compatible to JSON nature of configurations r.allowRegex = allowBlankIdentifierRegex r.failureMsg = "method receiver '%s' is not referenced in method's body, consider removing or renaming it as _" if len(args) == 0 { - return + return nil } // Arguments = [{}] options := args[0].(map[string]any) allowRegexParam, ok := options["allowRegex"] if !ok { - return + return nil } // Arguments = [{allowRegex="^_"}] allowRegexStr, ok := allowRegexParam.(string) @@ -41,14 +41,21 @@ func (r *UnusedReceiverRule) configure(args lint.Arguments) { var err error r.allowRegex, err = regexp.Compile(allowRegexStr) if err != nil { - panic(fmt.Errorf("error configuring [unused-receiver] rule: allowRegex is not valid regex [%s]: %v", allowRegexStr, err)) + return fmt.Errorf("error configuring [unused-receiver] rule: allowRegex is not valid regex [%s]: %w", allowRegexStr, err) } r.failureMsg = "method receiver '%s' is not referenced in method's body, consider removing or renaming it to match " + r.allowRegex.String() + return nil } // Apply applies the rule to given file. -func (r *UnusedReceiverRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(args) }) +func (r *UnusedReceiverRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } + var failures []lint.Failure for _, decl := range file.AST.Decls { diff --git a/rule/utils.go b/rule/utils.go index ecba547..e17821d 100644 --- a/rule/utils.go +++ b/rule/utils.go @@ -105,10 +105,11 @@ func gofmt(x any) string { } // checkNumberOfArguments fails if the given number of arguments is not, at least, the expected one -func checkNumberOfArguments(expected int, args lint.Arguments, ruleName string) { +func checkNumberOfArguments(expected int, args lint.Arguments, ruleName string) error { if len(args) < expected { - panic(fmt.Sprintf("not enough arguments for %s rule, expected %d, got %d. Please check the rule's documentation", ruleName, expected, len(args))) + return fmt.Errorf("not enough arguments for %s rule, expected %d, got %d. Please check the rule's documentation", ruleName, expected, len(args)) } + return nil } var directiveCommentRE = regexp.MustCompile("^//(line |extern |export |[a-z0-9]+:[a-z0-9])") // see https://go-review.googlesource.com/c/website/+/442516/1..2/_content/doc/comment.md#494 @@ -121,3 +122,8 @@ func isDirectiveComment(line string) bool { func isCallToExitFunction(pkgName, functionName string) bool { return exitFunctions[pkgName] != nil && exitFunctions[pkgName][functionName] } + +// newInternalFailureError returns an slice of Failure with a single internal failure in it +func newInternalFailureError(e error) []lint.Failure { + return []lint.Failure{lint.NewInternalFailure(e.Error())} +} diff --git a/rule/var_naming.go b/rule/var_naming.go index 320eb39..002f722 100644 --- a/rule/var_naming.go +++ b/rule/var_naming.go @@ -33,13 +33,21 @@ type VarNamingRule struct { configureOnce sync.Once } -func (r *VarNamingRule) configure(arguments lint.Arguments) { +func (r *VarNamingRule) configure(arguments lint.Arguments) error { if len(arguments) >= 1 { - r.allowList = getList(arguments[0], "allowlist") + list, err := getList(arguments[0], "allowlist") + if err != nil { + return err + } + r.allowList = list } if len(arguments) >= 2 { - r.blockList = getList(arguments[1], "blocklist") + list, err := getList(arguments[1], "blocklist") + if err != nil { + return err + } + r.blockList = list } if len(arguments) >= 3 { @@ -47,18 +55,19 @@ func (r *VarNamingRule) configure(arguments lint.Arguments) { thirdArgument := arguments[2] 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])) + return fmt.Errorf("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))) + return fmt.Errorf("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]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])) + return fmt.Errorf("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.allowUpperCaseConst = fmt.Sprint(args["upperCaseConst"]) == "true" r.skipPackageNameChecks = fmt.Sprint(args["skipPackageNameChecks"]) == "true" } + return nil } func (r *VarNamingRule) applyPackageCheckRules(walker *lintNames) { @@ -83,7 +92,12 @@ func (r *VarNamingRule) applyPackageCheckRules(walker *lintNames) { // Apply applies the rule to given file. func (r *VarNamingRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { - r.configureOnce.Do(func() { r.configure(arguments) }) + var configureErr error + r.configureOnce.Do(func() { configureErr = r.configure(arguments) }) + + if configureErr != nil { + return newInternalFailureError(configureErr) + } var failures []lint.Failure @@ -263,18 +277,18 @@ func (w *lintNames) Visit(n ast.Node) ast.Visitor { return w } -func getList(arg any, argName string) []string { +func getList(arg any, argName string) ([]string, error) { args, 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)) + return nil, fmt.Errorf("invalid argument to the var-naming rule. Expecting a %s of type slice with initialisms, got %T", argName, arg) } var list []string for _, v := range args { val, ok := v.(string) if !ok { - panic(fmt.Sprintf("Invalid %s values of the var-naming rule. Expecting slice of strings but got element of type %T", val, arg)) + return nil, fmt.Errorf("invalid %s values of the var-naming rule. Expecting slice of strings but got element of type %T", val, arg) } list = append(list, val) } - return list + return list, nil }