@@ -17,6 +17,7 @@ import (
17
17
"fmt"
18
18
"go/ast"
19
19
"go/format"
20
+ "go/printer"
20
21
"go/token"
21
22
"go/types"
22
23
"strings"
@@ -168,26 +169,16 @@ func SuggestedFix(fset *token.FileSet, start, end token.Pos, content []byte, fil
168
169
// Check which types have already been filled in. (we only want to fill in
169
170
// the unfilled types, or else we'll blat user-supplied details)
170
171
prefilledFields := map [string ]ast.Expr {}
172
+ var elts []ast.Expr
171
173
for _ , e := range expr .Elts {
172
174
if kv , ok := e .(* ast.KeyValueExpr ); ok {
173
175
if key , ok := kv .Key .(* ast.Ident ); ok {
174
176
prefilledFields [key .Name ] = kv .Value
177
+ elts = append (elts , kv )
175
178
}
176
179
}
177
180
}
178
181
179
- // Use a new fileset to build up a token.File for the new composite
180
- // literal. We need one line for foo{, one line for }, and one line for
181
- // each field we're going to set. format.Node only cares about line
182
- // numbers, so we don't need to set columns, and each line can be
183
- // 1 byte long.
184
- // TODO(adonovan): why is this necessary? The position information
185
- // is going to be wrong for the existing trees in prefilledFields.
186
- // Can't the formatter just do its best with an empty fileset?
187
- fakeFset := token .NewFileSet ()
188
- tok := fakeFset .AddFile ("" , - 1 , fieldCount + 2 )
189
-
190
- line := 2 // account for 1-based lines and the left brace
191
182
var fieldTyps []types.Type
192
183
for i := 0 ; i < fieldCount ; i ++ {
193
184
field := tStruct .Field (i )
@@ -200,69 +191,48 @@ func SuggestedFix(fset *token.FileSet, start, end token.Pos, content []byte, fil
200
191
}
201
192
matches := analysisinternal .MatchingIdents (fieldTyps , file , start , info , pkg )
202
193
qual := typesinternal .FileQualifier (file , pkg )
203
- var elts []ast. Expr
194
+
204
195
for i , fieldTyp := range fieldTyps {
205
196
if fieldTyp == nil {
206
197
continue // TODO(adonovan): is this reachable?
207
198
}
208
199
fieldName := tStruct .Field (i ).Name ()
209
-
210
- tok . AddLine ( line - 1 ) // add 1 byte per line
211
- if line > tok . LineCount () {
212
- panic ( fmt . Sprintf ( "invalid line number %v (of %v) for fillstruct" , line , tok . LineCount ()))
200
+ if _ , ok := prefilledFields [ fieldName ]; ok {
201
+ // We already stored these when looping over expr.Elt.
202
+ // Want to preserve the original order of prefilled fields
203
+ continue
213
204
}
214
- pos := tok .LineStart (line )
215
205
216
206
kv := & ast.KeyValueExpr {
217
207
Key : & ast.Ident {
218
- NamePos : pos ,
219
- Name : fieldName ,
208
+ Name : fieldName ,
220
209
},
221
- Colon : pos ,
222
210
}
223
- if expr , ok := prefilledFields [fieldName ]; ok {
211
+
212
+ names , ok := matches [fieldTyp ]
213
+ if ! ok {
214
+ return nil , nil , fmt .Errorf ("invalid struct field type: %v" , fieldTyp )
215
+ }
216
+
217
+ // Find the name most similar to the field name.
218
+ // If no name matches the pattern, generate a zero value.
219
+ // NOTE: We currently match on the name of the field key rather than the field type.
220
+ if best := fuzzy .BestMatch (fieldName , names ); best != "" {
221
+ kv .Value = ast .NewIdent (best )
222
+ } else if expr , isValid := populateValue (fieldTyp , qual ); isValid {
224
223
kv .Value = expr
225
224
} else {
226
- names , ok := matches [fieldTyp ]
227
- if ! ok {
228
- return nil , nil , fmt .Errorf ("invalid struct field type: %v" , fieldTyp )
229
- }
230
-
231
- // Find the name most similar to the field name.
232
- // If no name matches the pattern, generate a zero value.
233
- // NOTE: We currently match on the name of the field key rather than the field type.
234
- if best := fuzzy .BestMatch (fieldName , names ); best != "" {
235
- kv .Value = ast .NewIdent (best )
236
- } else if expr , isValid := populateValue (fieldTyp , qual ); isValid {
237
- kv .Value = expr
238
- } else {
239
- return nil , nil , nil // no fix to suggest
240
- }
225
+ return nil , nil , nil // no fix to suggest
241
226
}
227
+
242
228
elts = append (elts , kv )
243
- line ++
244
229
}
245
230
246
231
// If all of the struct's fields are unexported, we have nothing to do.
247
232
if len (elts ) == 0 {
248
233
return nil , nil , fmt .Errorf ("no elements to fill" )
249
234
}
250
235
251
- // Add the final line for the right brace. Offset is the number of
252
- // bytes already added plus 1.
253
- tok .AddLine (len (elts ) + 1 )
254
- line = len (elts ) + 2
255
- if line > tok .LineCount () {
256
- panic (fmt .Sprintf ("invalid line number %v (of %v) for fillstruct" , line , tok .LineCount ()))
257
- }
258
-
259
- cl := & ast.CompositeLit {
260
- Type : expr .Type ,
261
- Lbrace : tok .LineStart (1 ),
262
- Elts : elts ,
263
- Rbrace : tok .LineStart (line ),
264
- }
265
-
266
236
// Find the line on which the composite literal is declared.
267
237
split := bytes .Split (content , []byte ("\n " ))
268
238
lineNumber := safetoken .StartPosition (fset , expr .Lbrace ).Line
@@ -274,26 +244,66 @@ func SuggestedFix(fset *token.FileSet, start, end token.Pos, content []byte, fil
274
244
index := bytes .Index (firstLine , trimmed )
275
245
whitespace := firstLine [:index ]
276
246
277
- // First pass through the formatter: turn the expr into a string.
278
- var formatBuf bytes.Buffer
279
- if err := format .Node (& formatBuf , fakeFset , cl ); err != nil {
280
- return nil , nil , fmt .Errorf ("failed to run first format on:\n %s\n got err: %v" , cl .Type , err )
281
- }
282
- sug := indent (formatBuf .Bytes (), whitespace )
247
+ // Write a new composite literal "_{...}" composed of all prefilled and new elements,
248
+ // preserving existing formatting and comments.
249
+ // An alternative would be to only format the new fields,
250
+ // but by printing the entire composite literal, we ensure
251
+ // that the result is gofmt'ed.
252
+ var buf bytes.Buffer
253
+ buf .WriteString ("_{\n " )
254
+ fcmap := ast .NewCommentMap (fset , file , file .Comments )
255
+ comments := fcmap .Filter (expr ).Comments () // comments inside the expr, in source order
256
+ for _ , elt := range elts {
257
+ // Print comments before the current elt
258
+ for len (comments ) > 0 && comments [0 ].Pos () < elt .Pos () {
259
+ for _ , co := range comments [0 ].List {
260
+ fmt .Fprintln (& buf , co .Text )
261
+ }
262
+ comments = comments [1 :]
263
+ }
264
+
265
+ // Print the current elt with comments
266
+ eltcomments := fcmap .Filter (elt ).Comments ()
267
+ if err := format .Node (& buf , fset , & printer.CommentedNode {Node : elt , Comments : eltcomments }); err != nil {
268
+ return nil , nil , err
269
+ }
270
+ buf .WriteString ("," )
283
271
284
- if len (prefilledFields ) > 0 {
285
- // Attempt a second pass through the formatter to line up columns.
286
- sourced , err := format .Source (sug )
287
- if err == nil {
288
- sug = indent (sourced , whitespace )
272
+ // Prune comments up to the end of the elt
273
+ for len (comments ) > 0 && comments [0 ].Pos () < elt .End () {
274
+ comments = comments [1 :]
289
275
}
276
+
277
+ // Write comments associated with the current elt that appear after it
278
+ // printer.CommentedNode only prints comments inside the elt.
279
+ for _ , cg := range eltcomments {
280
+ for _ , co := range cg .List {
281
+ if co .Pos () >= elt .End () {
282
+ fmt .Fprintln (& buf , co .Text )
283
+ if len (comments ) > 0 {
284
+ comments = comments [1 :]
285
+ }
286
+ }
287
+ }
288
+ }
289
+ buf .WriteString ("\n " )
290
+ }
291
+ buf .WriteString ("}" )
292
+ formatted , err := format .Source (buf .Bytes ())
293
+ if err != nil {
294
+ return nil , nil , err
290
295
}
291
296
297
+ sug := indent (formatted , whitespace )
298
+ // Remove _
299
+ idx := bytes .IndexByte (sug , '{' ) // cannot fail
300
+ sug = sug [idx :]
301
+
292
302
return fset , & analysis.SuggestedFix {
293
303
TextEdits : []analysis.TextEdit {
294
304
{
295
- Pos : expr .Pos () ,
296
- End : expr .End ( ),
305
+ Pos : expr .Lbrace ,
306
+ End : expr .Rbrace + token . Pos ( len ( "}" ) ),
297
307
NewText : sug ,
298
308
},
299
309
},
0 commit comments