1
- // Copyright 2020 The Go Authors. All rights reserved.
1
+ // Copyright 2024 The Go Authors. All rights reserved.
2
2
// Use of this source code is governed by a BSD-style
3
3
// license that can be found in the LICENSE file.
4
4
5
- // Package fillswitch defines an Analyzer that automatically
6
- // fills the missing cases in type switches or switches over named types.
5
+ // Package fillswitch provides diagnostics and fixes to fills the missing cases
6
+ // in type switches or switches over named types.
7
7
//
8
8
// The analyzer's diagnostic is merely a prompt.
9
9
// The actual fix is created by a separate direct call from gopls to
@@ -32,12 +32,6 @@ import (
32
32
33
33
const FixCategory = "fillswitch" // recognized by gopls ApplyFix
34
34
35
- // errNoSuggestedFix is returned when no suggested fix is available. This could
36
- // be because all cases are already covered, or (in the case of a type switch)
37
- // because the remaining cases are for types not accessible by the current
38
- // package.
39
- var errNoSuggestedFix = errors .New ("no suggested fix" )
40
-
41
35
// Diagnose computes diagnostics for switch statements with missing cases
42
36
// overlapping with the provided start and end position.
43
37
//
@@ -49,8 +43,10 @@ func Diagnose(inspect *inspector.Inspector, start, end token.Pos, pkg *types.Pac
49
43
var diags []analysis.Diagnostic
50
44
nodeFilter := []ast.Node {(* ast .SwitchStmt )(nil ), (* ast .TypeSwitchStmt )(nil )}
51
45
inspect .Preorder (nodeFilter , func (n ast.Node ) {
52
- if expr , ok := n .(* ast.SwitchStmt ); ok {
53
- if (start .IsValid () && expr .End () < start ) || (end .IsValid () && expr .Pos () > end ) {
46
+ switch expr := n .(type ) {
47
+ case * ast.SwitchStmt :
48
+ if start .IsValid () && expr .End () < start ||
49
+ end .IsValid () && expr .Pos () > end {
54
50
return // non-overlapping
55
51
}
56
52
@@ -63,7 +59,7 @@ func Diagnose(inspect *inspector.Inspector, start, end token.Pos, pkg *types.Pac
63
59
return
64
60
}
65
61
66
- if _ , err := suggestedFixSwitch (expr , pkg , info ); err != nil {
62
+ if fix , err := suggestedFixSwitch (expr , pkg , info ); err != nil || fix = = nil {
67
63
return
68
64
}
69
65
@@ -77,10 +73,9 @@ func Diagnose(inspect *inspector.Inspector, start, end token.Pos, pkg *types.Pac
77
73
// No TextEdits => computed later by gopls.
78
74
}},
79
75
})
80
- }
81
-
82
- if expr , ok := n .(* ast.TypeSwitchStmt ); ok {
83
- if (start .IsValid () && expr .End () < start ) || (end .IsValid () && expr .Pos () > end ) {
76
+ case * ast.TypeSwitchStmt :
77
+ if start .IsValid () && expr .End () < start ||
78
+ end .IsValid () && expr .Pos () > end {
84
79
return // non-overlapping
85
80
}
86
81
@@ -93,7 +88,7 @@ func Diagnose(inspect *inspector.Inspector, start, end token.Pos, pkg *types.Pac
93
88
return
94
89
}
95
90
96
- if _ , err := suggestedFixTypeSwitch (expr , pkg , info ); err != nil {
91
+ if fix , err := suggestedFixTypeSwitch (expr , pkg , info ); err != nil || fix = = nil {
97
92
return
98
93
}
99
94
@@ -120,43 +115,40 @@ func suggestedFixTypeSwitch(stmt *ast.TypeSwitchStmt, pkg *types.Package, info *
120
115
}
121
116
122
117
scope := namedType .Obj ().Pkg ().Scope ()
123
- variants := make ( []string , 0 )
118
+ var variants []string
124
119
for _ , name := range scope .Names () {
125
120
obj := scope .Lookup (name )
126
121
if _ , ok := obj .(* types.TypeName ); ! ok {
127
- continue
122
+ continue // not a type
128
123
}
129
124
130
125
if types .Identical (obj .Type (), namedType .Obj ().Type ()) {
131
126
continue
132
127
}
133
128
134
- if types .AssignableTo (obj .Type (), namedType .Obj ().Type ()) {
135
- if obj .Pkg ().Name () != pkg .Name () {
136
- if ! obj .Exported () {
137
- continue
138
- }
129
+ if types .IsInterface (obj .Type ()) {
130
+ continue
131
+ }
139
132
140
- variants = append (variants , obj .Pkg ().Name ()+ "." + obj .Name ())
141
- } else {
142
- variants = append (variants , obj .Name ())
133
+ name := obj .Name ()
134
+ samePkg := obj .Pkg () == pkg
135
+ if ! samePkg {
136
+ if ! obj .Exported () {
137
+ continue // inaccessible
143
138
}
144
- } else if types .AssignableTo (types .NewPointer (obj .Type ()), namedType .Obj ().Type ()) {
145
- if obj .Pkg ().Name () != pkg .Name () {
146
- if ! obj .Exported () {
147
- continue
148
- }
139
+ name = obj .Pkg ().Name () + name
140
+ }
149
141
150
- variants = append ( variants , "*" + obj .Pkg (). Name () + "." + obj . Name ())
151
- } else {
152
- variants = append ( variants , "*" + obj . Name ())
153
- }
142
+ if types . AssignableTo ( obj .Type (), namedType . Obj (). Type ()) {
143
+ variants = append ( variants , name )
144
+ } else if types . AssignableTo ( types . NewPointer ( obj . Type ()), namedType . Obj (). Type ()) {
145
+ variants = append ( variants , "*" + name )
154
146
}
155
147
}
156
148
157
- handledVariants := getHandledVariants (stmt .Body )
149
+ handledVariants := caseTypes (stmt .Body , info )
158
150
if len (variants ) == 0 || len (variants ) == len (handledVariants ) {
159
- return nil , errNoSuggestedFix
151
+ return nil , nil
160
152
}
161
153
162
154
newText := buildNewText (variants , handledVariants )
@@ -165,7 +157,7 @@ func suggestedFixTypeSwitch(stmt *ast.TypeSwitchStmt, pkg *types.Package, info *
165
157
TextEdits : []analysis.TextEdit {{
166
158
Pos : stmt .End () - 1 ,
167
159
End : stmt .End () - 1 ,
168
- NewText : indent ([]byte (newText ), []byte { '\t' } ),
160
+ NewText : bytes . ReplaceAll ([]byte (newText ), []byte ( " \n " ), [] byte ( " \n \t " ) ),
169
161
}},
170
162
}, nil
171
163
}
@@ -198,9 +190,9 @@ func suggestedFixSwitch(stmt *ast.SwitchStmt, pkg *types.Package, info *types.In
198
190
}
199
191
}
200
192
201
- handledVariants := getHandledVariants (stmt .Body )
193
+ handledVariants := caseTypes (stmt .Body , info )
202
194
if len (variants ) == 0 || len (variants ) == len (handledVariants ) {
203
- return nil , errNoSuggestedFix
195
+ return nil , nil
204
196
}
205
197
206
198
newText := buildNewText (variants , handledVariants )
@@ -209,7 +201,7 @@ func suggestedFixSwitch(stmt *ast.SwitchStmt, pkg *types.Package, info *types.In
209
201
TextEdits : []analysis.TextEdit {{
210
202
Pos : stmt .End () - 1 ,
211
203
End : stmt .End () - 1 ,
212
- NewText : indent ([]byte (newText ), []byte { '\t' } ),
204
+ NewText : bytes . ReplaceAll ([]byte (newText ), []byte ( " \n " ), [] byte ( " \n \t " ) ),
213
205
}},
214
206
}, nil
215
207
}
@@ -288,20 +280,36 @@ func buildNewText(variants []string, handledVariants []string) string {
288
280
return textBuilder .String ()
289
281
}
290
282
291
- func getHandledVariants (body * ast.BlockStmt ) []string {
292
- out := make ( []string , 0 )
293
- for _ , bl := range body .List {
294
- for _ , c := range bl .(* ast.CaseClause ).List {
295
- switch v := c .(type ) {
283
+ func caseTypes (body * ast.BlockStmt , info * types. Info ) []string {
284
+ var out []string
285
+ for _ , stmt := range body .List {
286
+ for _ , e := range stmt .(* ast.CaseClause ).List {
287
+ switch e := e .(type ) {
296
288
case * ast.Ident :
297
- out = append (out , v .Name )
289
+ out = append (out , e .Name )
298
290
case * ast.SelectorExpr :
299
- out = append (out , v .X .(* ast.Ident ).Name + "." + v .Sel .Name )
291
+ if _ , ok := e .X .(* ast.Ident ); ! ok {
292
+ continue
293
+ }
294
+
295
+ out = append (out , e .X .(* ast.Ident ).Name + "." + e .Sel .Name )
300
296
case * ast.StarExpr :
301
- switch v := v .X .(type ) {
297
+ switch v := e .X .(type ) {
302
298
case * ast.Ident :
299
+ if ! info .Types [v ].IsType () {
300
+ continue
301
+ }
302
+
303
303
out = append (out , "*" + v .Name )
304
304
case * ast.SelectorExpr :
305
+ if ! info .Types [v ].IsType () {
306
+ continue
307
+ }
308
+
309
+ if _ , ok := e .X .(* ast.Ident ); ! ok {
310
+ continue
311
+ }
312
+
305
313
out = append (out , "*" + v .X .(* ast.Ident ).Name + "." + v .Sel .Name )
306
314
}
307
315
}
@@ -339,21 +347,3 @@ func SuggestedFix(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Pack
339
347
return nil , nil , fmt .Errorf ("no switch statement found" )
340
348
}
341
349
}
342
-
343
- // indent works line by line through str, prefixing each line with
344
- // prefix.
345
- func indent (str , prefix []byte ) []byte {
346
- split := bytes .Split (str , []byte ("\n " ))
347
- newText := bytes .NewBuffer (nil )
348
- for i , s := range split {
349
- if i != 0 {
350
- newText .Write (prefix )
351
- }
352
-
353
- newText .Write (s )
354
- if i < len (split )- 1 {
355
- newText .WriteByte ('\n' )
356
- }
357
- }
358
- return newText .Bytes ()
359
- }
0 commit comments