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