@@ -17,6 +17,7 @@ import (
1717 "fmt"
1818 "go/ast"
1919 "go/format"
20+ "go/printer"
2021 "go/token"
2122 "go/types"
2223 "strings"
@@ -168,26 +169,16 @@ func SuggestedFix(fset *token.FileSet, start, end token.Pos, content []byte, fil
168169 // Check which types have already been filled in. (we only want to fill in
169170 // the unfilled types, or else we'll blat user-supplied details)
170171 prefilledFields := map [string ]ast.Expr {}
172+ var elts []ast.Expr
171173 for _ , e := range expr .Elts {
172174 if kv , ok := e .(* ast.KeyValueExpr ); ok {
173175 if key , ok := kv .Key .(* ast.Ident ); ok {
174176 prefilledFields [key .Name ] = kv .Value
177+ elts = append (elts , kv )
175178 }
176179 }
177180 }
178181
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
191182 var fieldTyps []types.Type
192183 for i := 0 ; i < fieldCount ; i ++ {
193184 field := tStruct .Field (i )
@@ -200,69 +191,48 @@ func SuggestedFix(fset *token.FileSet, start, end token.Pos, content []byte, fil
200191 }
201192 matches := analysisinternal .MatchingIdents (fieldTyps , file , start , info , pkg )
202193 qual := typesinternal .FileQualifier (file , pkg )
203- var elts []ast. Expr
194+
204195 for i , fieldTyp := range fieldTyps {
205196 if fieldTyp == nil {
206197 continue // TODO(adonovan): is this reachable?
207198 }
208199 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
213204 }
214- pos := tok .LineStart (line )
215205
216206 kv := & ast.KeyValueExpr {
217207 Key : & ast.Ident {
218- NamePos : pos ,
219- Name : fieldName ,
208+ Name : fieldName ,
220209 },
221- Colon : pos ,
222210 }
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 {
224223 kv .Value = expr
225224 } 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
241226 }
227+
242228 elts = append (elts , kv )
243- line ++
244229 }
245230
246231 // If all of the struct's fields are unexported, we have nothing to do.
247232 if len (elts ) == 0 {
248233 return nil , nil , fmt .Errorf ("no elements to fill" )
249234 }
250235
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-
266236 // Find the line on which the composite literal is declared.
267237 split := bytes .Split (content , []byte ("\n " ))
268238 lineNumber := safetoken .StartPosition (fset , expr .Lbrace ).Line
@@ -274,26 +244,66 @@ func SuggestedFix(fset *token.FileSet, start, end token.Pos, content []byte, fil
274244 index := bytes .Index (firstLine , trimmed )
275245 whitespace := firstLine [:index ]
276246
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 ("," )
283271
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 :]
289275 }
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
290295 }
291296
297+ sug := indent (formatted , whitespace )
298+ // Remove _
299+ idx := bytes .IndexByte (sug , '{' ) // cannot fail
300+ sug = sug [idx :]
301+
292302 return fset , & analysis.SuggestedFix {
293303 TextEdits : []analysis.TextEdit {
294304 {
295- Pos : expr .Pos () ,
296- End : expr .End ( ),
305+ Pos : expr .Lbrace ,
306+ End : expr .Rbrace + token . Pos ( len ( "}" ) ),
297307 NewText : sug ,
298308 },
299309 },
0 commit comments