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