diff --git a/checkers.go b/checkers.go index 3eccece..96181db 100644 --- a/checkers.go +++ b/checkers.go @@ -640,8 +640,26 @@ func newArgListParensChecker(ctxt *context) checker { } func (c *argListParensChecker) Visit(n ast.Node) bool { - call, ok := n.(*ast.CallExpr) - if !ok || len(call.Args) < 2 { + switch node := n.(type) { + case *ast.CallExpr: + return c.checkCallExpr(node) + case *ast.CompositeLit: + return c.checkCompositeLit(node) + case *ast.FuncDecl: + return c.checkFuncDecl(node) + case *ast.FuncLit: + return c.checkFuncLit(node) + case *ast.FuncType: + // Only check FuncType if it's not part of FuncDecl or FuncLit + // This handles interface method signatures + return c.checkFuncTypeIfStandalone(node) + } + return true +} + +// checkCallExpr handles function calls (original logic) +func (c *argListParensChecker) checkCallExpr(call *ast.CallExpr) bool { + if len(call.Args) < 2 { return true } lastArg := call.Args[len(call.Args)-1] @@ -654,9 +672,104 @@ func (c *argListParensChecker) Visit(n ast.Node) bool { rparenLine := c.ctxt.fset.Position(call.Rparen).Line switch rparenLine { case lastArgLine: - c.ctxt.mark(n, &c.sameLine) + c.ctxt.mark(call, &c.sameLine) case lastArgLine + 1: - c.ctxt.mark(n, &c.nextLine) + c.ctxt.mark(call, &c.nextLine) + } + return true +} + +// checkCompositeLit handles struct, slice, map literals +func (c *argListParensChecker) checkCompositeLit(lit *ast.CompositeLit) bool { + if len(lit.Elts) < 2 { + return true + } + lastElt := lit.Elts[len(lit.Elts)-1] + lastEltLine := c.ctxt.fset.Position(lastElt.Pos()).Line + firstEltLine := c.ctxt.fset.Position(lit.Elts[0].Pos()).Line + if firstEltLine == lastEltLine { + // Don't track single-line literals. + return true + } + rbracePos := lit.Rbrace + if rbracePos == 0 { + // No closing brace found + return true + } + rbraceLine := c.ctxt.fset.Position(rbracePos).Line + switch rbraceLine { + case lastEltLine: + c.ctxt.mark(lit, &c.sameLine) + case lastEltLine + 1: + c.ctxt.mark(lit, &c.nextLine) + } + return true +} + +// checkFuncDecl handles function declarations +func (c *argListParensChecker) checkFuncDecl(decl *ast.FuncDecl) bool { + if decl.Type == nil || decl.Type.Params == nil { + return true + } + return c.checkFieldList(decl.Type.Params, decl) +} + +// checkFuncLit handles function literals (anonymous functions) +func (c *argListParensChecker) checkFuncLit(lit *ast.FuncLit) bool { + if lit.Type == nil || lit.Type.Params == nil { + return true + } + return c.checkFieldList(lit.Type.Params, lit) +} + +// checkFuncType handles function types (including interface methods) +func (c *argListParensChecker) checkFuncType(typ *ast.FuncType) bool { + if typ.Params == nil { + return true + } + return c.checkFieldList(typ.Params, typ) +} + +// checkFuncTypeIfStandalone handles function types that are not part of FuncDecl/FuncLit +// This is mainly for interface method signatures +func (c *argListParensChecker) checkFuncTypeIfStandalone(typ *ast.FuncType) bool { + // Check if this FuncType has a parent that is FuncDecl or FuncLit + // If so, skip it to avoid duplication + if c.ctxt.astinfo.Parents != nil { + if parent := c.ctxt.astinfo.Parents[typ]; parent != nil { + switch parent.(type) { + case *ast.FuncDecl, *ast.FuncLit: + // Skip if it's part of a function declaration or literal + return true + } + } + } + return c.checkFuncType(typ) +} + +// checkFieldList is a helper to check parameter lists +func (c *argListParensChecker) checkFieldList(params *ast.FieldList, node ast.Node) bool { + if len(params.List) < 2 { + return true + } + lastParam := params.List[len(params.List)-1] + lastParamLine := c.ctxt.fset.Position(lastParam.Pos()).Line + firstParamLine := c.ctxt.fset.Position(params.List[0].Pos()).Line + if firstParamLine == lastParamLine { + // Don't track single-line parameter lists. + return true + } + rparenPos := params.Closing + if rparenPos == 0 { + // No closing paren found + return true + } + rparenLine := c.ctxt.fset.Position(rparenPos).Line + switch rparenLine { + case lastParamLine: + c.ctxt.mark(node, &c.sameLine) + case lastParamLine + 1: + c.ctxt.mark(node, &c.nextLine) } return true } diff --git a/testdata/negative_tests4.go b/testdata/negative_tests4.go new file mode 100644 index 0000000..fe8869c --- /dev/null +++ b/testdata/negative_tests4.go @@ -0,0 +1,211 @@ +package arglisttests + +type Person struct { + Name string + Age int +} + +func structLiterals() { + // All closing braces on same line as last element - consistent style + _ = Person{ + Name: "John", + Age: 30} + + _ = Person{Name: "Jane", + Age: 25} + + _ = Person{ + Name: "Bob", + Age: 40} +} + +func sliceLiterals() { + // All closing braces on same line as last element + _ = []string{ + "first", + "second", + "third"} + + _ = []int{1, + 2, + 3} + + _ = []string{ + "alpha", + "beta", + "gamma"} + + _ = []Person{ + {Name: "Alice", Age: 28}, + {Name: "Charlie", Age: 35}} +} + +func mapLiterals() { + // All closing braces on same line as last element + _ = map[string]int{ + "one": 1, + "two": 2, + "three": 3} + + _ = map[int]string{1: "first", + 2: "second", + 3: "third"} + + _ = map[string]Person{ + "john": {Name: "John", Age: 30}, + "jane": {Name: "Jane", Age: 25}} + + _ = map[string]interface{}{ + "name": "test", + "age": 42} +} + +func functionWithMultipleParams( + name string, + age int, + active bool) { +} + +func anotherFunction(param1 string, + param2 int, + param3 bool) { +} + +func complexFunction( + param1 string, + param2 []int, + param3 map[string]interface{}) error { + return nil +} + +type Calculator struct{} + +func (c Calculator) Add( + x int, + y int) int { + return x + y +} + +func (c Calculator) Multiply(a int, + b int, + z int) int { + return a * b * z +} + +func (c Calculator) Divide( + dividend float64, + divisor float64) float64 { + return dividend / divisor +} + +func (c *Calculator) ComplexOperation( + param1 string, + param2 []int, + param3 map[string]interface{}) (result interface{}, err error) { + return nil, nil +} + +type MathInterface interface { + Calculate( + x int, + y int) int + + Process(input string, + options map[string]interface{}, + callback func(string) error) error + + Transform( + data []byte, + format string) ([]byte, error) + + Validate( + input interface{}, + rules []string, + strict bool) bool +} + +type AdvancedInterface interface { + ComplexMethod( + param1 string, + param2 func(int) error, + param3 chan string) <-chan result + + SimpleMethod( + a int, + b string) error +} + +type result struct { + Value interface{} + Error error +} + +func functionLiterals() { + // All closing parens on same line as last parameter + fn1 := func( + x int, + y int) int { + return x + y + } + + fn2 := func(a string, + b string, + c string) string { + return a + b + c + } + + fn3 := func( + param1 string, + param2 int) error { + return nil + } + + fn4 := func( + data []byte, + callback func(error), + options map[string]interface{}) { + // Implementation + } + + _ = fn1 + _ = fn2 + _ = fn3 + _ = fn4 +} + +func nestedLiterals() { + // All closing braces on same line as last element + _ = []map[string]Person{ + { + "manager": {Name: "Alice", Age: 35}, + "employee": {Name: "Bob", Age: 28}}, + { + "lead": {Name: "Charlie", Age: 40}, + "junior": {Name: "David", Age: 22}}} + + _ = map[string][]Person{ + "team1": { + {Name: "Eve", Age: 30}, + {Name: "Frank", Age: 32}}, + "team2": { + {Name: "Grace", Age: 29}, + {Name: "Henry", Age: 31}}} +} + +func singleLineCases() { + _ = Person{Name: "Test", Age: 25} + _ = []int{1, 2, 3} + _ = map[string]int{"a": 1, "b": 2} + + fn := func(a, b int) int { return a + b } + _ = fn +} + +func fewElementsCases() { + _ = Person{Name: "Single"} + _ = []int{42} + _ = map[string]int{"single": 1} +} + +func singleParam(name string) {} +func noParams() {} diff --git a/testdata/positive_tests4.go b/testdata/positive_tests4.go new file mode 100644 index 0000000..b1b922e --- /dev/null +++ b/testdata/positive_tests4.go @@ -0,0 +1,225 @@ +package arglisttests + +type Person struct { + Name string + Age int +} + +func structLiterals() { + _ = Person{ + Name: "John", + Age: 30} + + _ = Person{Name: "Jane", + Age: 25} + + //= arg list parens: align `)` to a same line with last argument + _ = Person{ + Name: "Bob", + Age: 40, + } +} + +func sliceLiterals() { + _ = []string{ + "first", + "second", + "third"} + + _ = []int{1, + 2, + 3} + + //= arg list parens: align `)` to a same line with last argument + _ = []string{ + "alpha", + "beta", + "gamma", + } + + //= arg list parens: align `)` to a same line with last argument + _ = []Person{ + {Name: "Alice", Age: 28}, + {Name: "Charlie", Age: 35}, + } +} + +func mapLiterals() { + _ = map[string]int{ + "one": 1, + "two": 2, + "three": 3} + + _ = map[int]string{1: "first", + 2: "second", + 3: "third"} + + //= arg list parens: align `)` to a same line with last argument + _ = map[string]Person{ + "john": {Name: "John", Age: 30}, + "jane": {Name: "Jane", Age: 25}, + } + + //= arg list parens: align `)` to a same line with last argument + _ = map[string]interface{}{ + "name": "test", + "age": 42, + } +} + +func functionWithMultipleParams( + name string, + age int, + active bool) { +} + +func anotherFunction(param1 string, + param2 int, + param3 bool) { +} + +// = arg list parens: align `)` to a same line with last argument +func functionWithTrailingComma( + name string, + age int, + active bool, +) { +} + +// = arg list parens: align `)` to a same line with last argument +func complexFunction( + param1 string, + param2 []int, + param3 map[string]interface{}, +) error { + return nil +} + +type Calculator struct{} + +func (c Calculator) Add( + x int, + y int) int { + return x + y +} + +func (c Calculator) Multiply(a int, + b int, + z int) int { + return a * b * z +} + +// = arg list parens: align `)` to a same line with last argument +func (c Calculator) Divide( + dividend float64, + divisor float64, +) float64 { + return dividend / divisor +} + +// = arg list parens: align `)` to a same line with last argument +func (c *Calculator) ComplexOperation( + param1 string, + param2 []int, + param3 map[string]interface{}, +) (result interface{}, err error) { + return nil, nil +} + +type MathInterface interface { + Calculate( + x int, + y int) int + + Process(input string, + options map[string]interface{}, + callback func(string) error) error + + //= arg list parens: align `)` to a same line with last argument + Transform( + data []byte, + format string, + ) ([]byte, error) + + //= arg list parens: align `)` to a same line with last argument + Validate( + input interface{}, + rules []string, + strict bool, + ) bool +} + +type AdvancedInterface interface { + //= arg list parens: align `)` to a same line with last argument + ComplexMethod( + param1 string, + param2 func(int) error, + param3 chan string, + ) <-chan result + + SimpleMethod( + a int, + b string) error +} + +type result struct { + Value interface{} + Error error +} + +func functionLiterals() { + fn1 := func( + x int, + y int) int { + return x + y + } + + fn2 := func(a string, + b string, + c string) string { + return a + b + c + } + + //= arg list parens: align `)` to a same line with last argument + fn3 := func( + param1 string, + param2 int, + ) error { + return nil + } + + //= arg list parens: align `)` to a same line with last argument + fn4 := func( + data []byte, + callback func(error), + options map[string]interface{}, + ) { + } + + _ = fn1 + _ = fn2 + _ = fn3 + _ = fn4 +} + +func nestedLiterals() { + _ = []map[string]Person{ + { + "manager": {Name: "Alice", Age: 35}, + "employee": {Name: "Bob", Age: 28}}, + { + "lead": {Name: "Charlie", Age: 40}, + "junior": {Name: "David", Age: 22}}} + + //= arg list parens: align `)` to a same line with last argument + _ = map[string][]Person{ + "team1": { + {Name: "Eve", Age: 30}, + {Name: "Frank", Age: 32}, + }, + "team2": { + {Name: "Grace", Age: 29}, + {Name: "Henry", Age: 31}, + }, + } +}