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