Skip to content

Commit 26eb8bf

Browse files
committed
formatter: separate select case clauses
Treat select communication clauses like switch case clauses in the blank-line pass so separator blanks appear between clauses rather than inside the clause header. Suppress blank-before-return insertion when a case or select clause contains only a return statement, even when that return has a leading comment. Add a regression test for select clauses that keeps the first case compact, separates the next case, and preserves the existing blank-before-return behavior for a multi-statement final case.
1 parent 437e389 commit 26eb8bf

7 files changed

Lines changed: 363 additions & 17 deletions

File tree

dsl/action.go

Lines changed: 197 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,7 +1010,6 @@ func (a *BreakArithmeticChainLayoutAction) Execute(caps Captures,
10101010

10111011
switch binExpr.Op {
10121012
case token.ADD, token.SUB, token.MUL, token.QUO, token.REM:
1013-
10141013
default:
10151014
return nil, false
10161015
}
@@ -5163,6 +5162,21 @@ func (c *blankLineBatchContext) initConditions() {
51635162
}
51645163

51655164
func (c *blankLineBatchContext) maybeInsertBlankBefore(n ast.Node) {
5165+
c.maybeInsertBlankBeforeNode(n, true)
5166+
}
5167+
5168+
func (c *blankLineBatchContext) maybeInsertBlankBeforeClause(n ast.Node) {
5169+
insert, attachLeadingComments := clauseSeparatorBehavior(c.ctx, n)
5170+
if !insert {
5171+
return
5172+
}
5173+
5174+
c.maybeInsertBlankBeforeNode(n, attachLeadingComments)
5175+
}
5176+
5177+
func (c *blankLineBatchContext) maybeInsertBlankBeforeNode(n ast.Node,
5178+
attachLeadingComments bool) {
5179+
51665180
if n == nil {
51675181
return
51685182
}
@@ -5171,7 +5185,9 @@ func (c *blankLineBatchContext) maybeInsertBlankBefore(n ast.Node) {
51715185
return
51725186
}
51735187
ls := lineStart(c.ctx.Source, start)
5174-
ls = leadingCommentBlockLineStart(c.ctx.Source, ls)
5188+
if attachLeadingComments {
5189+
ls = leadingCommentBlockLineStart(c.ctx.Source, ls)
5190+
}
51755191
if ls <= 0 || hasBlankLineBeforeLineStart(c.ctx.Source, ls) {
51765192
return
51775193
}
@@ -5191,7 +5207,13 @@ func (c *blankLineBatchContext) inspectFile(file *ast.File) {
51915207
case *ast.CaseClause:
51925208
c.maybeRemoveBlankAfterSingleLineBlockHeader(n)
51935209
if c.caseCond.Eval(caps, c.ctx) {
5194-
c.maybeInsertBlankBefore(n)
5210+
c.maybeInsertBlankBeforeClause(n)
5211+
}
5212+
5213+
case *ast.CommClause:
5214+
c.maybeRemoveBlankAfterSingleLineBlockHeader(n)
5215+
if c.caseCond.Eval(caps, c.ctx) {
5216+
c.maybeInsertBlankBeforeClause(n)
51955217
}
51965218

51975219
case *ast.ReturnStmt:
@@ -5268,12 +5290,34 @@ func (c *blankLineBatchContext) maybeRemoveBlankAfterSingleLineBlockHeader(
52685290
headerEnd = c.ctx.Fset.Position(n.Body.Lbrace)
52695291

52705292
case *ast.CaseClause:
5271-
if n == nil || len(n.Body) == 0 {
5293+
if n == nil {
5294+
return
5295+
}
5296+
headerStart = c.ctx.Fset.Position(n.Pos())
5297+
headerEnd = c.ctx.Fset.Position(n.Colon)
5298+
if len(n.Body) == 0 {
5299+
c.maybeRemoveBlankAfterHeaderLine(
5300+
headerStart, headerEnd,
5301+
)
5302+
52725303
return
52735304
}
52745305
first = n.Body[0]
5306+
5307+
case *ast.CommClause:
5308+
if n == nil {
5309+
return
5310+
}
52755311
headerStart = c.ctx.Fset.Position(n.Pos())
52765312
headerEnd = c.ctx.Fset.Position(n.Colon)
5313+
if len(n.Body) == 0 {
5314+
c.maybeRemoveBlankAfterHeaderLine(
5315+
headerStart, headerEnd,
5316+
)
5317+
5318+
return
5319+
}
5320+
first = n.Body[0]
52775321

52785322
default:
52795323
return
@@ -5300,6 +5344,31 @@ func (c *blankLineBatchContext) maybeRemoveBlankAfterSingleLineBlockHeader(
53005344
c.maybeRemoveBlankBeforeLineStart(lineStartIdx)
53015345
}
53025346

5347+
func (c *blankLineBatchContext) maybeRemoveBlankAfterHeaderLine(
5348+
headerStart, headerEnd token.Position) {
5349+
5350+
if headerStart.Line != headerEnd.Line {
5351+
return
5352+
}
5353+
5354+
headerLineEnd := lineEnd(c.ctx.Source, headerEnd.Offset)
5355+
if headerLineEnd < 0 || headerLineEnd >= len(c.ctx.Source) ||
5356+
c.ctx.Source[headerLineEnd] != '\n' {
5357+
return
5358+
}
5359+
5360+
blankLineStart := headerLineEnd + 1
5361+
blankLineEnd := lineEnd(c.ctx.Source, blankLineStart)
5362+
if blankLineEnd < blankLineStart || blankLineEnd >= len(c.ctx.Source) {
5363+
return
5364+
}
5365+
if !isWhitespaceOnlyLine(c.ctx.Source[blankLineStart:blankLineEnd]) {
5366+
return
5367+
}
5368+
5369+
c.b.Delete(blankLineStart, blankLineEnd+1)
5370+
}
5371+
53035372
func (c *blankLineBatchContext) maybeRemoveBlankBeforeLineStart(
53045373
lineStartIdx int) {
53055374

@@ -5468,6 +5537,130 @@ func (a *InsertBlankBeforeAction) Execute(caps Captures, ctx *Context) ([]byte,
54685537
return out, true
54695538
}
54705539

5540+
// InsertBlankBeforeClauseAction inserts a blank line before a switch/select
5541+
// clause label.
5542+
type InsertBlankBeforeClauseAction struct {
5543+
Target string
5544+
}
5545+
5546+
// Execute implements Action for InsertBlankBeforeClauseAction.
5547+
func (a *InsertBlankBeforeClauseAction) Execute(caps Captures, ctx *Context) (
5548+
[]byte, bool) {
5549+
5550+
node := resolveTarget(caps, a.Target)
5551+
if node == nil {
5552+
return nil, false
5553+
}
5554+
5555+
pos := ctx.Fset.Position(node.Pos())
5556+
insert, attachLeadingComments := clauseSeparatorBehavior(ctx, node)
5557+
if !insert {
5558+
return nil, false
5559+
}
5560+
5561+
ls := lineStart(ctx.Source, pos.Offset)
5562+
if attachLeadingComments {
5563+
ls = leadingCommentBlockLineStart(ctx.Source, ls)
5564+
}
5565+
if hasBlankLineBeforeLineStart(ctx.Source, ls) {
5566+
return nil, false
5567+
}
5568+
5569+
out, err := ApplySingleEdit(ctx.Source, ls, ls, []byte("\n"))
5570+
if err != nil {
5571+
return nil, false
5572+
}
5573+
5574+
return out, true
5575+
}
5576+
5577+
func clauseSeparatorBehavior(ctx *Context,
5578+
node ast.Node) (insert bool, attachLeadingComments bool) {
5579+
5580+
parent, ok := ctx.Parent(node).(*ast.BlockStmt)
5581+
if !ok || parent == nil {
5582+
return true, true
5583+
}
5584+
5585+
for i, stmt := range parent.List {
5586+
if stmt != node {
5587+
continue
5588+
}
5589+
if i == 0 {
5590+
return true, true
5591+
}
5592+
5593+
prev := parent.List[i-1]
5594+
if clauseBodyLen(prev) > 0 {
5595+
return true, true
5596+
}
5597+
5598+
if clauseGapHasComment(ctx, prev, node) {
5599+
return true, false
5600+
}
5601+
5602+
return false, false
5603+
}
5604+
5605+
return true, true
5606+
}
5607+
5608+
func clauseBodyLen(stmt ast.Stmt) int {
5609+
switch n := stmt.(type) {
5610+
case *ast.CaseClause:
5611+
if n == nil {
5612+
return 0
5613+
}
5614+
5615+
return len(n.Body)
5616+
5617+
case *ast.CommClause:
5618+
if n == nil {
5619+
return 0
5620+
}
5621+
5622+
return len(n.Body)
5623+
5624+
default:
5625+
return 0
5626+
}
5627+
}
5628+
5629+
func clauseGapHasComment(ctx *Context, prev ast.Stmt, next ast.Node) bool {
5630+
start := clauseColonOffset(ctx, prev)
5631+
end := ctx.Fset.Position(next.Pos()).Offset
5632+
if start < 0 || end < 0 || start >= end || end > len(ctx.Source) {
5633+
return false
5634+
}
5635+
5636+
gap := ctx.Source[start:end]
5637+
5638+
return bytes.Contains(gap, []byte("//")) ||
5639+
bytes.Contains(gap, []byte("/*"))
5640+
}
5641+
5642+
func clauseColonOffset(ctx *Context, stmt ast.Stmt) int {
5643+
var pos token.Pos
5644+
switch n := stmt.(type) {
5645+
case *ast.CaseClause:
5646+
if n == nil {
5647+
return -1
5648+
}
5649+
pos = n.Colon
5650+
5651+
case *ast.CommClause:
5652+
if n == nil {
5653+
return -1
5654+
}
5655+
pos = n.Colon
5656+
5657+
default:
5658+
return -1
5659+
}
5660+
5661+
return ctx.Fset.Position(pos).Offset
5662+
}
5663+
54715664
// InsertBlankAfterAction inserts a blank line after a node if not already
54725665
// present.
54735666
type InsertBlankAfterAction struct {

dsl/condition.go

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,8 @@ func (c *ExprEditSafeCond) Eval(caps Captures, ctx *Context) bool {
213213
if (&IsInCallArgsCond{Target: c.Target}).Eval(caps, ctx) {
214214
switch c.CallArgsPolicy {
215215
case CallArgsPolicyForce:
216-
217216
// OK.
217+
218218
case CallArgsPolicyAuto:
219219
call := enclosingCallForArg(node, ctx)
220220
if call == nil {
@@ -1103,8 +1103,8 @@ func (c *IsFinalReturnCond) Eval(caps Captures, ctx *Context) bool {
11031103
}
11041104
}
11051105

1106-
// HasPrecedingSiblingCond checks if a case clause has a preceding sibling case.
1107-
// Used to determine if blank line is needed before a case.
1106+
// HasPrecedingSiblingCond checks if a switch/select clause has a preceding
1107+
// sibling clause. Used to determine if a blank line is needed before a clause.
11081108
type HasPrecedingSiblingCond struct {
11091109
Target string
11101110
}
@@ -1116,8 +1116,9 @@ func (c *HasPrecedingSiblingCond) Eval(caps Captures, ctx *Context) bool {
11161116
return false
11171117
}
11181118

1119-
caseClause, ok := node.(*ast.CaseClause)
1120-
if !ok {
1119+
switch node.(type) {
1120+
case *ast.CaseClause, *ast.CommClause:
1121+
default:
11211122
return false
11221123
}
11231124

@@ -1133,9 +1134,9 @@ func (c *HasPrecedingSiblingCond) Eval(caps Captures, ctx *Context) bool {
11331134
return false
11341135
}
11351136

1136-
// Find this case in the block's statements
1137+
// Find this clause in the block's statements.
11371138
for i, stmt := range blockStmt.List {
1138-
if stmt == caseClause {
1139+
if stmt == node {
11391140
return i > 0 // Has preceding sibling if not first
11401141
}
11411142
}
@@ -2115,6 +2116,9 @@ func (c *IsReturnNeedingBlankCond) Eval(caps Captures, ctx *Context) bool {
21152116
if !ok {
21162117
return false
21172118
}
2119+
if onlyStmtInClause(ctx, node) {
2120+
return false
2121+
}
21182122
if onlyStmtInBlockWithoutLeadingComment(ctx, node) {
21192123
return false
21202124
}
@@ -2167,6 +2171,21 @@ func (c *IsReturnNeedingBlankCond) Eval(caps Captures, ctx *Context) bool {
21672171
return true
21682172
}
21692173

2174+
func onlyStmtInClause(ctx *Context, node ast.Node) bool {
2175+
switch parent := ctx.Parent(node).(type) {
2176+
case *ast.CaseClause:
2177+
return parent != nil && len(parent.Body) == 1 &&
2178+
parent.Body[0] == node
2179+
2180+
case *ast.CommClause:
2181+
return parent != nil && len(parent.Body) == 1 &&
2182+
parent.Body[0] == node
2183+
2184+
default:
2185+
return false
2186+
}
2187+
}
2188+
21702189
func onlyStmtInBlockWithoutLeadingComment(ctx *Context, node ast.Node) bool {
21712190
block, ok := ctx.Parent(node).(*ast.BlockStmt)
21722191
if !ok || block == nil || len(block.List) != 1 ||
@@ -2175,7 +2194,6 @@ func onlyStmtInBlockWithoutLeadingComment(ctx *Context, node ast.Node) bool {
21752194
}
21762195
switch ctx.Parent(block).(type) {
21772196
case *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt:
2178-
21792197
default:
21802198
return false
21812199
}

dsl/func_lit_expand_action.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,8 @@ func isTrivialInlineFuncLit(fn *ast.FuncLit) bool {
122122
for _, res := range ret.Results {
123123
switch res.(type) {
124124
case *ast.Ident, *ast.BasicLit:
125-
126125
// OK.
126+
127127
default:
128128
return false
129129
}

dsl/layout/render.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ func RenderAt(doc Doc, colLimit int, tabStop int, indent string,
6868
}
6969

7070
case ForceBreak:
71-
7271
// No output; acts only as a fit-check barrier.
72+
7373
case Concat:
7474
// Push in reverse so we render in order.
7575
for i := len(d) - 1; i >= 0; i-- {
@@ -192,8 +192,8 @@ func fitsAt(doc Doc, remaining int, tabStop int, startCol int) bool {
192192
}
193193

194194
case SoftLine:
195-
196195
// Flat soft line is empty.
196+
197197
case ForceBreak:
198198
return false
199199

dsl/rules.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -917,7 +917,24 @@ func BlankLineRulesWithOptions(opts BlankLineOptions) []Rule {
917917
},
918918
},
919919
Priority: 10,
920-
Action: &InsertBlankBeforeAction{
920+
Action: &InsertBlankBeforeClauseAction{
921+
Target: "node",
922+
},
923+
},
924+
{
925+
Name: "blank_before_comm_clause",
926+
Pattern: &NodePattern{
927+
Type: "CommClause",
928+
},
929+
When: &AndCond{
930+
Conds: []Condition{
931+
&HasPrecedingSiblingCond{
932+
Target: "node",
933+
},
934+
},
935+
},
936+
Priority: 10,
937+
Action: &InsertBlankBeforeClauseAction{
921938
Target: "node",
922939
},
923940
},

0 commit comments

Comments
 (0)