golang.org/x/tools/gopls@v0.15.3/internal/analysis/fillstruct/fillstruct.go (about)

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package fillstruct defines an Analyzer that automatically
     6  // fills in a struct declaration with zero value elements for each field.
     7  //
     8  // The analyzer's diagnostic is merely a prompt.
     9  // The actual fix is created by a separate direct call from gopls to
    10  // the SuggestedFixes function.
    11  // Tests of Analyzer.Run can be found in ./testdata/src.
    12  // Tests of the SuggestedFixes logic live in ../../testdata/fillstruct.
    13  package fillstruct
    14  
    15  import (
    16  	"bytes"
    17  	"fmt"
    18  	"go/ast"
    19  	"go/format"
    20  	"go/token"
    21  	"go/types"
    22  	"strings"
    23  	"unicode"
    24  
    25  	"golang.org/x/tools/go/analysis"
    26  	"golang.org/x/tools/go/ast/astutil"
    27  	"golang.org/x/tools/go/ast/inspector"
    28  	"golang.org/x/tools/gopls/internal/util/safetoken"
    29  	"golang.org/x/tools/internal/analysisinternal"
    30  	"golang.org/x/tools/internal/fuzzy"
    31  )
    32  
    33  // Diagnose computes diagnostics for fillable struct literals overlapping with
    34  // the provided start and end position.
    35  //
    36  // The diagnostic contains a lazy fix; the actual patch is computed
    37  // (via the ApplyFix command) by a call to [SuggestedFix].
    38  //
    39  // If either start or end is invalid, the entire package is inspected.
    40  func Diagnose(inspect *inspector.Inspector, start, end token.Pos, pkg *types.Package, info *types.Info) []analysis.Diagnostic {
    41  	var diags []analysis.Diagnostic
    42  	nodeFilter := []ast.Node{(*ast.CompositeLit)(nil)}
    43  	inspect.Preorder(nodeFilter, func(n ast.Node) {
    44  		expr := n.(*ast.CompositeLit)
    45  
    46  		if (start.IsValid() && expr.End() < start) || (end.IsValid() && expr.Pos() > end) {
    47  			return // non-overlapping
    48  		}
    49  
    50  		typ := info.TypeOf(expr)
    51  		if typ == nil {
    52  			return
    53  		}
    54  
    55  		// Find reference to the type declaration of the struct being initialized.
    56  		typ = deref(typ)
    57  		tStruct, ok := typ.Underlying().(*types.Struct)
    58  		if !ok {
    59  			return
    60  		}
    61  		// Inv: typ is the possibly-named struct type.
    62  
    63  		fieldCount := tStruct.NumFields()
    64  
    65  		// Skip any struct that is already populated or that has no fields.
    66  		if fieldCount == 0 || fieldCount == len(expr.Elts) {
    67  			return
    68  		}
    69  
    70  		// Are any fields in need of filling?
    71  		var fillableFields []string
    72  		for i := 0; i < fieldCount; i++ {
    73  			field := tStruct.Field(i)
    74  			// Ignore fields that are not accessible in the current package.
    75  			if field.Pkg() != nil && field.Pkg() != pkg && !field.Exported() {
    76  				continue
    77  			}
    78  			fillableFields = append(fillableFields, fmt.Sprintf("%s: %s", field.Name(), field.Type().String()))
    79  		}
    80  		if len(fillableFields) == 0 {
    81  			return
    82  		}
    83  
    84  		// Derive a name for the struct type.
    85  		var name string
    86  		if typ != tStruct {
    87  			// named struct type (e.g. pkg.S[T])
    88  			name = types.TypeString(typ, types.RelativeTo(pkg))
    89  		} else {
    90  			// anonymous struct type
    91  			totalFields := len(fillableFields)
    92  			const maxLen = 20
    93  			// Find the index to cut off printing of fields.
    94  			var i, fieldLen int
    95  			for i = range fillableFields {
    96  				if fieldLen > maxLen {
    97  					break
    98  				}
    99  				fieldLen += len(fillableFields[i])
   100  			}
   101  			fillableFields = fillableFields[:i]
   102  			if i < totalFields {
   103  				fillableFields = append(fillableFields, "...")
   104  			}
   105  			name = fmt.Sprintf("anonymous struct{ %s }", strings.Join(fillableFields, ", "))
   106  		}
   107  		diags = append(diags, analysis.Diagnostic{
   108  			Message:  fmt.Sprintf("%s literal has missing fields", name),
   109  			Pos:      expr.Pos(),
   110  			End:      expr.End(),
   111  			Category: FixCategory,
   112  			SuggestedFixes: []analysis.SuggestedFix{{
   113  				Message: fmt.Sprintf("Fill %s", name),
   114  				// No TextEdits => computed later by gopls.
   115  			}},
   116  		})
   117  	})
   118  
   119  	return diags
   120  }
   121  
   122  const FixCategory = "fillstruct" // recognized by gopls ApplyFix
   123  
   124  // SuggestedFix computes the suggested fix for the kinds of
   125  // diagnostics produced by the Analyzer above.
   126  func SuggestedFix(fset *token.FileSet, start, end token.Pos, content []byte, file *ast.File, pkg *types.Package, info *types.Info) (*token.FileSet, *analysis.SuggestedFix, error) {
   127  	if info == nil {
   128  		return nil, nil, fmt.Errorf("nil types.Info")
   129  	}
   130  
   131  	pos := start // don't use the end
   132  
   133  	// TODO(rstambler): Using ast.Inspect would probably be more efficient than
   134  	// calling PathEnclosingInterval. Switch this approach.
   135  	path, _ := astutil.PathEnclosingInterval(file, pos, pos)
   136  	if len(path) == 0 {
   137  		return nil, nil, fmt.Errorf("no enclosing ast.Node")
   138  	}
   139  	var expr *ast.CompositeLit
   140  	for _, n := range path {
   141  		if node, ok := n.(*ast.CompositeLit); ok {
   142  			expr = node
   143  			break
   144  		}
   145  	}
   146  
   147  	typ := info.TypeOf(expr)
   148  	if typ == nil {
   149  		return nil, nil, fmt.Errorf("no composite literal")
   150  	}
   151  
   152  	// Find reference to the type declaration of the struct being initialized.
   153  	typ = deref(typ)
   154  	tStruct, ok := typ.Underlying().(*types.Struct)
   155  	if !ok {
   156  		return nil, nil, fmt.Errorf("%s is not a (pointer to) struct type",
   157  			types.TypeString(typ, types.RelativeTo(pkg)))
   158  	}
   159  	// Inv: typ is the possibly-named struct type.
   160  
   161  	fieldCount := tStruct.NumFields()
   162  
   163  	// Check which types have already been filled in. (we only want to fill in
   164  	// the unfilled types, or else we'll blat user-supplied details)
   165  	prefilledFields := map[string]ast.Expr{}
   166  	for _, e := range expr.Elts {
   167  		if kv, ok := e.(*ast.KeyValueExpr); ok {
   168  			if key, ok := kv.Key.(*ast.Ident); ok {
   169  				prefilledFields[key.Name] = kv.Value
   170  			}
   171  		}
   172  	}
   173  
   174  	// Use a new fileset to build up a token.File for the new composite
   175  	// literal. We need one line for foo{, one line for }, and one line for
   176  	// each field we're going to set. format.Node only cares about line
   177  	// numbers, so we don't need to set columns, and each line can be
   178  	// 1 byte long.
   179  	// TODO(adonovan): why is this necessary? The position information
   180  	// is going to be wrong for the existing trees in prefilledFields.
   181  	// Can't the formatter just do its best with an empty fileset?
   182  	fakeFset := token.NewFileSet()
   183  	tok := fakeFset.AddFile("", -1, fieldCount+2)
   184  
   185  	line := 2 // account for 1-based lines and the left brace
   186  	var fieldTyps []types.Type
   187  	for i := 0; i < fieldCount; i++ {
   188  		field := tStruct.Field(i)
   189  		// Ignore fields that are not accessible in the current package.
   190  		if field.Pkg() != nil && field.Pkg() != pkg && !field.Exported() {
   191  			fieldTyps = append(fieldTyps, nil)
   192  			continue
   193  		}
   194  		fieldTyps = append(fieldTyps, field.Type())
   195  	}
   196  	matches := analysisinternal.MatchingIdents(fieldTyps, file, start, info, pkg)
   197  	var elts []ast.Expr
   198  	for i, fieldTyp := range fieldTyps {
   199  		if fieldTyp == nil {
   200  			continue // TODO(adonovan): is this reachable?
   201  		}
   202  		fieldName := tStruct.Field(i).Name()
   203  
   204  		tok.AddLine(line - 1) // add 1 byte per line
   205  		if line > tok.LineCount() {
   206  			panic(fmt.Sprintf("invalid line number %v (of %v) for fillstruct", line, tok.LineCount()))
   207  		}
   208  		pos := tok.LineStart(line)
   209  
   210  		kv := &ast.KeyValueExpr{
   211  			Key: &ast.Ident{
   212  				NamePos: pos,
   213  				Name:    fieldName,
   214  			},
   215  			Colon: pos,
   216  		}
   217  		if expr, ok := prefilledFields[fieldName]; ok {
   218  			kv.Value = expr
   219  		} else {
   220  			names, ok := matches[fieldTyp]
   221  			if !ok {
   222  				return nil, nil, fmt.Errorf("invalid struct field type: %v", fieldTyp)
   223  			}
   224  
   225  			// Find the name most similar to the field name.
   226  			// If no name matches the pattern, generate a zero value.
   227  			// NOTE: We currently match on the name of the field key rather than the field type.
   228  			if best := fuzzy.BestMatch(fieldName, names); best != "" {
   229  				kv.Value = ast.NewIdent(best)
   230  			} else if v := populateValue(file, pkg, fieldTyp); v != nil {
   231  				kv.Value = v
   232  			} else {
   233  				return nil, nil, nil // no fix to suggest
   234  			}
   235  		}
   236  		elts = append(elts, kv)
   237  		line++
   238  	}
   239  
   240  	// If all of the struct's fields are unexported, we have nothing to do.
   241  	if len(elts) == 0 {
   242  		return nil, nil, fmt.Errorf("no elements to fill")
   243  	}
   244  
   245  	// Add the final line for the right brace. Offset is the number of
   246  	// bytes already added plus 1.
   247  	tok.AddLine(len(elts) + 1)
   248  	line = len(elts) + 2
   249  	if line > tok.LineCount() {
   250  		panic(fmt.Sprintf("invalid line number %v (of %v) for fillstruct", line, tok.LineCount()))
   251  	}
   252  
   253  	cl := &ast.CompositeLit{
   254  		Type:   expr.Type,
   255  		Lbrace: tok.LineStart(1),
   256  		Elts:   elts,
   257  		Rbrace: tok.LineStart(line),
   258  	}
   259  
   260  	// Find the line on which the composite literal is declared.
   261  	split := bytes.Split(content, []byte("\n"))
   262  	lineNumber := safetoken.StartPosition(fset, expr.Lbrace).Line
   263  	firstLine := split[lineNumber-1] // lines are 1-indexed
   264  
   265  	// Trim the whitespace from the left of the line, and use the index
   266  	// to get the amount of whitespace on the left.
   267  	trimmed := bytes.TrimLeftFunc(firstLine, unicode.IsSpace)
   268  	index := bytes.Index(firstLine, trimmed)
   269  	whitespace := firstLine[:index]
   270  
   271  	// First pass through the formatter: turn the expr into a string.
   272  	var formatBuf bytes.Buffer
   273  	if err := format.Node(&formatBuf, fakeFset, cl); err != nil {
   274  		return nil, nil, fmt.Errorf("failed to run first format on:\n%s\ngot err: %v", cl.Type, err)
   275  	}
   276  	sug := indent(formatBuf.Bytes(), whitespace)
   277  
   278  	if len(prefilledFields) > 0 {
   279  		// Attempt a second pass through the formatter to line up columns.
   280  		sourced, err := format.Source(sug)
   281  		if err == nil {
   282  			sug = indent(sourced, whitespace)
   283  		}
   284  	}
   285  
   286  	return fset, &analysis.SuggestedFix{
   287  		TextEdits: []analysis.TextEdit{
   288  			{
   289  				Pos:     expr.Pos(),
   290  				End:     expr.End(),
   291  				NewText: sug,
   292  			},
   293  		},
   294  	}, nil
   295  }
   296  
   297  // indent works line by line through str, indenting (prefixing) each line with
   298  // ind.
   299  func indent(str, ind []byte) []byte {
   300  	split := bytes.Split(str, []byte("\n"))
   301  	newText := bytes.NewBuffer(nil)
   302  	for i, s := range split {
   303  		if len(s) == 0 {
   304  			continue
   305  		}
   306  		// Don't add the extra indentation to the first line.
   307  		if i != 0 {
   308  			newText.Write(ind)
   309  		}
   310  		newText.Write(s)
   311  		if i < len(split)-1 {
   312  			newText.WriteByte('\n')
   313  		}
   314  	}
   315  	return newText.Bytes()
   316  }
   317  
   318  // populateValue constructs an expression to fill the value of a struct field.
   319  //
   320  // When the type of a struct field is a basic literal or interface, we return
   321  // default values. For other types, such as maps, slices, and channels, we create
   322  // empty expressions such as []T{} or make(chan T) rather than using default values.
   323  //
   324  // The reasoning here is that users will call fillstruct with the intention of
   325  // initializing the struct, in which case setting these fields to nil has no effect.
   326  //
   327  // populateValue returns nil if the value cannot be filled.
   328  func populateValue(f *ast.File, pkg *types.Package, typ types.Type) ast.Expr {
   329  	switch u := typ.Underlying().(type) {
   330  	case *types.Basic:
   331  		switch {
   332  		case u.Info()&types.IsNumeric != 0:
   333  			return &ast.BasicLit{Kind: token.INT, Value: "0"}
   334  		case u.Info()&types.IsBoolean != 0:
   335  			return &ast.Ident{Name: "false"}
   336  		case u.Info()&types.IsString != 0:
   337  			return &ast.BasicLit{Kind: token.STRING, Value: `""`}
   338  		case u.Kind() == types.UnsafePointer:
   339  			return ast.NewIdent("nil")
   340  		case u.Kind() == types.Invalid:
   341  			return nil
   342  		default:
   343  			panic(fmt.Sprintf("unknown basic type %v", u))
   344  		}
   345  
   346  	case *types.Map:
   347  		k := analysisinternal.TypeExpr(f, pkg, u.Key())
   348  		v := analysisinternal.TypeExpr(f, pkg, u.Elem())
   349  		if k == nil || v == nil {
   350  			return nil
   351  		}
   352  		return &ast.CompositeLit{
   353  			Type: &ast.MapType{
   354  				Key:   k,
   355  				Value: v,
   356  			},
   357  		}
   358  	case *types.Slice:
   359  		s := analysisinternal.TypeExpr(f, pkg, u.Elem())
   360  		if s == nil {
   361  			return nil
   362  		}
   363  		return &ast.CompositeLit{
   364  			Type: &ast.ArrayType{
   365  				Elt: s,
   366  			},
   367  		}
   368  
   369  	case *types.Array:
   370  		a := analysisinternal.TypeExpr(f, pkg, u.Elem())
   371  		if a == nil {
   372  			return nil
   373  		}
   374  		return &ast.CompositeLit{
   375  			Type: &ast.ArrayType{
   376  				Elt: a,
   377  				Len: &ast.BasicLit{
   378  					Kind: token.INT, Value: fmt.Sprintf("%v", u.Len()),
   379  				},
   380  			},
   381  		}
   382  
   383  	case *types.Chan:
   384  		v := analysisinternal.TypeExpr(f, pkg, u.Elem())
   385  		if v == nil {
   386  			return nil
   387  		}
   388  		dir := ast.ChanDir(u.Dir())
   389  		if u.Dir() == types.SendRecv {
   390  			dir = ast.SEND | ast.RECV
   391  		}
   392  		return &ast.CallExpr{
   393  			Fun: ast.NewIdent("make"),
   394  			Args: []ast.Expr{
   395  				&ast.ChanType{
   396  					Dir:   dir,
   397  					Value: v,
   398  				},
   399  			},
   400  		}
   401  
   402  	case *types.Struct:
   403  		s := analysisinternal.TypeExpr(f, pkg, typ)
   404  		if s == nil {
   405  			return nil
   406  		}
   407  		return &ast.CompositeLit{
   408  			Type: s,
   409  		}
   410  
   411  	case *types.Signature:
   412  		var params []*ast.Field
   413  		for i := 0; i < u.Params().Len(); i++ {
   414  			p := analysisinternal.TypeExpr(f, pkg, u.Params().At(i).Type())
   415  			if p == nil {
   416  				return nil
   417  			}
   418  			params = append(params, &ast.Field{
   419  				Type: p,
   420  				Names: []*ast.Ident{
   421  					{
   422  						Name: u.Params().At(i).Name(),
   423  					},
   424  				},
   425  			})
   426  		}
   427  		var returns []*ast.Field
   428  		for i := 0; i < u.Results().Len(); i++ {
   429  			r := analysisinternal.TypeExpr(f, pkg, u.Results().At(i).Type())
   430  			if r == nil {
   431  				return nil
   432  			}
   433  			returns = append(returns, &ast.Field{
   434  				Type: r,
   435  			})
   436  		}
   437  		return &ast.FuncLit{
   438  			Type: &ast.FuncType{
   439  				Params: &ast.FieldList{
   440  					List: params,
   441  				},
   442  				Results: &ast.FieldList{
   443  					List: returns,
   444  				},
   445  			},
   446  			Body: &ast.BlockStmt{},
   447  		}
   448  
   449  	case *types.Pointer:
   450  		switch u.Elem().(type) {
   451  		case *types.Basic:
   452  			return &ast.CallExpr{
   453  				Fun: &ast.Ident{
   454  					Name: "new",
   455  				},
   456  				Args: []ast.Expr{
   457  					&ast.Ident{
   458  						Name: u.Elem().String(),
   459  					},
   460  				},
   461  			}
   462  		default:
   463  			x := populateValue(f, pkg, u.Elem())
   464  			if x == nil {
   465  				return nil
   466  			}
   467  			return &ast.UnaryExpr{
   468  				Op: token.AND,
   469  				X:  x,
   470  			}
   471  		}
   472  
   473  	case *types.Interface:
   474  		if param, ok := typ.(*types.TypeParam); ok {
   475  			// *new(T) is the zero value of a type parameter T.
   476  			// TODO(adonovan): one could give a more specific zero
   477  			// value if the type has a core type that is, say,
   478  			// always a number or a pointer. See go/ssa for details.
   479  			return &ast.StarExpr{
   480  				X: &ast.CallExpr{
   481  					Fun: ast.NewIdent("new"),
   482  					Args: []ast.Expr{
   483  						ast.NewIdent(param.Obj().Name()),
   484  					},
   485  				},
   486  			}
   487  		}
   488  
   489  		return ast.NewIdent("nil")
   490  	}
   491  	return nil
   492  }
   493  
   494  func deref(t types.Type) types.Type {
   495  	for {
   496  		ptr, ok := t.Underlying().(*types.Pointer)
   497  		if !ok {
   498  			return t
   499  		}
   500  		t = ptr.Elem()
   501  	}
   502  }