diff --git a/main.go b/main.go index 5775343..0994666 100644 --- a/main.go +++ b/main.go @@ -69,6 +69,12 @@ var ( shortenComments = kingpin.Flag( "shorten-comments", "Shorten single-line comments").Default("false").Bool() + funcParamThreshold = kingpin.Flag( + "func-param-threshold", + "Expand function signatures to one-param-per-line when they have this many or more parameters "+ + "(0 to disable)", + ). + Default("0").Int() tabLen = kingpin.Flag( "tab-len", "Length of a tab").Short('t').Default("4").Int() @@ -151,13 +157,14 @@ type Runner struct { func NewRunner() *Runner { config := &shorten.Config{ - MaxLen: deref(maxLen), - TabLen: deref(tabLen), - KeepAnnotations: deref(keepAnnotations), - ShortenComments: deref(shortenComments), - ReformatTags: deref(reformatTags), - DotFile: deref(dotFile), - ChainSplitDots: deref(chainSplitDots), + MaxLen: deref(maxLen), + TabLen: deref(tabLen), + KeepAnnotations: deref(keepAnnotations), + ShortenComments: deref(shortenComments), + ReformatTags: deref(reformatTags), + DotFile: deref(dotFile), + ChainSplitDots: deref(chainSplitDots), + FuncParamThreshold: deref(funcParamThreshold), } return &Runner{ diff --git a/shorten/format.go b/shorten/format.go index c177ee5..b0b4af6 100644 --- a/shorten/format.go +++ b/shorten/format.go @@ -51,7 +51,8 @@ func (s *Shortener) formatNode(node dst.Node) { func (s *Shortener) formatDecl(decl dst.Decl) { switch d := decl.(type) { case *dst.FuncDecl: - if d.Type != nil && d.Type.Params != nil && annotation.HasRecursive(d) { + if d.Type != nil && d.Type.Params != nil && + (annotation.HasRecursive(d) || s.shouldExpandParams(d.Type.Params)) { s.formatFieldList(d.Type.Params) } @@ -233,16 +234,21 @@ func (s *Shortener) formatExpr(expr dst.Expr, force, isChain bool) { s.formatExprs(e.Elts, false, isChain) case *dst.FuncLit: + if e.Type != nil && s.shouldExpandParams(e.Type.Params) { + s.formatFieldList(e.Type.Params) + } + s.formatStmt(e.Body, false) case *dst.FuncType: - if shouldShorten { + if shouldShorten || s.shouldExpandParams(e.Params) { s.formatFieldList(e.Params) } case *dst.InterfaceType: for _, method := range e.Methods.List { - if annotation.Has(method) { + funcType, isFuncType := method.Type.(*dst.FuncType) + if annotation.Has(method) || (isFuncType && s.shouldExpandParams(funcType.Params)) { s.formatExpr(method.Type, true, isChain) } } diff --git a/shorten/shortener.go b/shorten/shortener.go index 413ce4c..75d785a 100644 --- a/shorten/shortener.go +++ b/shorten/shortener.go @@ -42,6 +42,11 @@ type Config struct { // ChainSplitDots Whether to split chain methods by putting dots at the ends of lines ChainSplitDots bool + + // FuncParamThreshold triggers multi-line expansion of function signatures + // with this many or more parameters, regardless of line length. + // Set to 0 (default) to disable. + FuncParamThreshold int } // NewDefaultConfig returns a [Config] with default values. @@ -186,10 +191,34 @@ func (s *Shortener) Process(content []byte) ([]byte, error) { // if there are lines to shorten, // or if this is the first round (0), // and the option to reformat struct tags is enabled, -// and there are struct tags with multiple entries. +// and there are struct tags with multiple entries, +// or if this is the first round (0), +// and func param threshold expansion is enabled. func (s *Shortener) shouldContinue(nbLinesToShorten, round int, lines []string) bool { return nbLinesToShorten > 0 || - round == 0 && s.config.ReformatTags && tags.HasMultipleEntries(lines) + round == 0 && s.config.ReformatTags && tags.HasMultipleEntries(lines) || + round == 0 && s.config.FuncParamThreshold > 0 +} + +// shouldExpandParams reports whether a field list has enough parameters +// to trigger multi-line expansion based on the configured threshold. +// Named parameters with shared types (e.g. a, b int) count individually. +func (s *Shortener) shouldExpandParams(params *dst.FieldList) bool { + if s.config.FuncParamThreshold <= 0 || params == nil { + return false + } + + count := 0 + + for _, field := range params.List { + if len(field.Names) == 0 { + count++ // unnamed parameter: func(int, string) + } else { + count += len(field.Names) // named: func(a, b int) counts as 2 + } + } + + return count >= s.config.FuncParamThreshold } func (s *Shortener) createDot(result dst.Node) error { diff --git a/shorten/testdata/func_param_threshold/func_param_threshold.go b/shorten/testdata/func_param_threshold/func_param_threshold.go new file mode 100644 index 0000000..270f2d3 --- /dev/null +++ b/shorten/testdata/func_param_threshold/func_param_threshold.go @@ -0,0 +1,106 @@ +package fixtures + +// Functions below threshold: unchanged. + +func zeroParams() error { + return nil +} + +func oneParam(a int) error { + return nil +} + +func twoParams(a int, b string) error { + return nil +} + +// Functions at or above threshold: expand. + +func threeParams(a int, b string, c bool) error { + return nil +} + +func fourParams(a int, b string, c bool, d float64) error { + return nil +} + +// Methods: receiver is not counted toward the threshold. + +type Receiver struct{} + +func (r *Receiver) methodTwoParams(a int, b string) error { + return nil +} + +func (r *Receiver) methodThreeParams(a int, b string, c bool) error { + return nil +} + +// Value receiver on a named basic type: receiver still not counted. + +type MyString string + +func (s MyString) basicTypeTwoParams(a int, b string) error { + return nil +} + +func (s MyString) basicTypeThreeParams(a int, b string, c bool) error { + return nil +} + +// Named params with shared types: count names, not fields. +// a, b int, c string = 3 logical params. + +func sharedTypes(a, b int, c string) error { + return nil +} + +// Unnamed params. + +func unnamed(int, string, bool) error { + return nil +} + +// Variadic: a + b = 2 logical params, unchanged. + +func variadic(a int, b ...string) error { + return nil +} + +// Already multi-line: idempotent, no change. + +func alreadyMultiLine( + a int, + b string, + c bool, +) error { + return nil +} + +// Return values are not counted toward the threshold. + +func returnValuesNotCounted(a int) (int, string, bool) { + return 0, "", false +} + +// Interface methods. + +type MyInterface interface { + interfaceMethodTwo(a int, b string) error + interfaceMethodThree(a int, b string, c bool) error +} + +// Function type declarations. + +type MyFunc func(a int, b string, c bool) error + +// Function literals. + +func funcLiterals() { + _ = func(a int, b string) error { + return nil + } + _ = func(a int, b string, c bool) error { + return nil + } +} diff --git a/shorten/testdata/func_param_threshold/func_param_threshold.go.golden b/shorten/testdata/func_param_threshold/func_param_threshold.go.golden new file mode 100644 index 0000000..b2de2cc --- /dev/null +++ b/shorten/testdata/func_param_threshold/func_param_threshold.go.golden @@ -0,0 +1,142 @@ +package fixtures + +// Functions below threshold: unchanged. + +func zeroParams() error { + return nil +} + +func oneParam(a int) error { + return nil +} + +func twoParams(a int, b string) error { + return nil +} + +// Functions at or above threshold: expand. + +func threeParams( + a int, + b string, + c bool, +) error { + return nil +} + +func fourParams( + a int, + b string, + c bool, + d float64, +) error { + return nil +} + +// Methods: receiver is not counted toward the threshold. + +type Receiver struct{} + +func (r *Receiver) methodTwoParams(a int, b string) error { + return nil +} + +func (r *Receiver) methodThreeParams( + a int, + b string, + c bool, +) error { + return nil +} + +// Value receiver on a named basic type: receiver still not counted. + +type MyString string + +func (s MyString) basicTypeTwoParams(a int, b string) error { + return nil +} + +func (s MyString) basicTypeThreeParams( + a int, + b string, + c bool, +) error { + return nil +} + +// Named params with shared types: count names, not fields. +// a, b int, c string = 3 logical params. + +func sharedTypes( + a, b int, + c string, +) error { + return nil +} + +// Unnamed params. + +func unnamed( + int, + string, + bool, +) error { + return nil +} + +// Variadic: a + b = 2 logical params, unchanged. + +func variadic(a int, b ...string) error { + return nil +} + +// Already multi-line: idempotent, no change. + +func alreadyMultiLine( + a int, + b string, + c bool, +) error { + return nil +} + +// Return values are not counted toward the threshold. + +func returnValuesNotCounted(a int) (int, string, bool) { + return 0, "", false +} + +// Interface methods. + +type MyInterface interface { + interfaceMethodTwo(a int, b string) error + interfaceMethodThree( + a int, + b string, + c bool, + ) error +} + +// Function type declarations. + +type MyFunc func( + a int, + b string, + c bool, +) error + +// Function literals. + +func funcLiterals() { + _ = func(a int, b string) error { + return nil + } + _ = func( + a int, + b string, + c bool, + ) error { + return nil + } +} diff --git a/shorten/testdata/func_param_threshold/func_param_threshold.json b/shorten/testdata/func_param_threshold/func_param_threshold.json new file mode 100644 index 0000000..76b1c5b --- /dev/null +++ b/shorten/testdata/func_param_threshold/func_param_threshold.json @@ -0,0 +1,9 @@ +{ + "MaxLen": 100, + "TabLen": 4, + "KeepAnnotations": false, + "ShortenComments": false, + "ReformatTags": true, + "ChainSplitDots": true, + "FuncParamThreshold": 3 +} diff --git a/shorten/testdata/func_param_threshold_disabled/func_param_threshold_disabled.go b/shorten/testdata/func_param_threshold_disabled/func_param_threshold_disabled.go new file mode 100644 index 0000000..c5612e2 --- /dev/null +++ b/shorten/testdata/func_param_threshold_disabled/func_param_threshold_disabled.go @@ -0,0 +1,13 @@ +package fixtures + +func threeParams(a int, b string, c bool) error { + return nil +} + +func fourParams(a int, b string, c bool, d float64) error { + return nil +} + +func fiveParams(a int, b string, c bool, d float64, e []byte) error { + return nil +} diff --git a/shorten/testdata/func_param_threshold_disabled/func_param_threshold_disabled.go.golden b/shorten/testdata/func_param_threshold_disabled/func_param_threshold_disabled.go.golden new file mode 100644 index 0000000..c5612e2 --- /dev/null +++ b/shorten/testdata/func_param_threshold_disabled/func_param_threshold_disabled.go.golden @@ -0,0 +1,13 @@ +package fixtures + +func threeParams(a int, b string, c bool) error { + return nil +} + +func fourParams(a int, b string, c bool, d float64) error { + return nil +} + +func fiveParams(a int, b string, c bool, d float64, e []byte) error { + return nil +} diff --git a/shorten/testdata/func_param_threshold_disabled/func_param_threshold_disabled.json b/shorten/testdata/func_param_threshold_disabled/func_param_threshold_disabled.json new file mode 100644 index 0000000..970cbcf --- /dev/null +++ b/shorten/testdata/func_param_threshold_disabled/func_param_threshold_disabled.json @@ -0,0 +1,9 @@ +{ + "MaxLen": 100, + "TabLen": 4, + "KeepAnnotations": false, + "ShortenComments": false, + "ReformatTags": true, + "ChainSplitDots": true, + "FuncParamThreshold": 0 +} diff --git a/shorten/testdata/func_param_threshold_max_len/func_param_threshold_max_len.go b/shorten/testdata/func_param_threshold_max_len/func_param_threshold_max_len.go new file mode 100644 index 0000000..3420b27 --- /dev/null +++ b/shorten/testdata/func_param_threshold_max_len/func_param_threshold_max_len.go @@ -0,0 +1,19 @@ +package fixtures + +// Short line, 3 params: threshold fires, line-length does not. + +func shortLineThreeParams(a int, b string, c bool) error { + return nil +} + +// Long line, 2 params: line-length fires, threshold does not. + +func longLineTwoParams(aReallyLongParamName string, anotherLongParamName string) error { + return nil +} + +// Long line, 3 params: both triggers fire; single clean expansion. + +func longLineThreeParams(aReallyLongParamName string, anotherLongParamName string, c bool) error { + return nil +} diff --git a/shorten/testdata/func_param_threshold_max_len/func_param_threshold_max_len.go.golden b/shorten/testdata/func_param_threshold_max_len/func_param_threshold_max_len.go.golden new file mode 100644 index 0000000..47d81b9 --- /dev/null +++ b/shorten/testdata/func_param_threshold_max_len/func_param_threshold_max_len.go.golden @@ -0,0 +1,30 @@ +package fixtures + +// Short line, 3 params: threshold fires, line-length does not. + +func shortLineThreeParams( + a int, + b string, + c bool, +) error { + return nil +} + +// Long line, 2 params: line-length fires, threshold does not. + +func longLineTwoParams( + aReallyLongParamName string, + anotherLongParamName string, +) error { + return nil +} + +// Long line, 3 params: both triggers fire; single clean expansion. + +func longLineThreeParams( + aReallyLongParamName string, + anotherLongParamName string, + c bool, +) error { + return nil +} diff --git a/shorten/testdata/func_param_threshold_max_len/func_param_threshold_max_len.json b/shorten/testdata/func_param_threshold_max_len/func_param_threshold_max_len.json new file mode 100644 index 0000000..fa2bc81 --- /dev/null +++ b/shorten/testdata/func_param_threshold_max_len/func_param_threshold_max_len.json @@ -0,0 +1,9 @@ +{ + "MaxLen": 60, + "TabLen": 4, + "KeepAnnotations": false, + "ShortenComments": false, + "ReformatTags": true, + "ChainSplitDots": true, + "FuncParamThreshold": 3 +}