github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/simple/s1016/s1016.go (about)

     1  package s1016
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/token"
     7  	"go/types"
     8  
     9  	"github.com/amarpal/go-tools/analysis/code"
    10  	"github.com/amarpal/go-tools/analysis/edit"
    11  	"github.com/amarpal/go-tools/analysis/facts/generated"
    12  	"github.com/amarpal/go-tools/analysis/lint"
    13  	"github.com/amarpal/go-tools/analysis/report"
    14  
    15  	"golang.org/x/tools/go/analysis"
    16  	"golang.org/x/tools/go/analysis/passes/inspect"
    17  )
    18  
    19  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
    20  	Analyzer: &analysis.Analyzer{
    21  		Name:     "S1016",
    22  		Run:      run,
    23  		Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
    24  	},
    25  	Doc: &lint.Documentation{
    26  		Title: `Use a type conversion instead of manually copying struct fields`,
    27  		Text: `Two struct types with identical fields can be converted between each
    28  other. In older versions of Go, the fields had to have identical
    29  struct tags. Since Go 1.8, however, struct tags are ignored during
    30  conversions. It is thus not necessary to manually copy every field
    31  individually.`,
    32  		Before: `
    33  var x T1
    34  y := T2{
    35      Field1: x.Field1,
    36      Field2: x.Field2,
    37  }`,
    38  		After: `
    39  var x T1
    40  y := T2(x)`,
    41  		Since:   "2017.1",
    42  		MergeIf: lint.MergeIfAll,
    43  	},
    44  })
    45  
    46  var Analyzer = SCAnalyzer.Analyzer
    47  
    48  func run(pass *analysis.Pass) (interface{}, error) {
    49  	// TODO(dh): support conversions between type parameters
    50  	fn := func(node ast.Node, stack []ast.Node) {
    51  		if unary, ok := stack[len(stack)-2].(*ast.UnaryExpr); ok && unary.Op == token.AND {
    52  			// Do not suggest type conversion between pointers
    53  			return
    54  		}
    55  
    56  		lit := node.(*ast.CompositeLit)
    57  		typ1, _ := pass.TypesInfo.TypeOf(lit.Type).(*types.Named)
    58  		if typ1 == nil {
    59  			return
    60  		}
    61  		s1, ok := typ1.Underlying().(*types.Struct)
    62  		if !ok {
    63  			return
    64  		}
    65  
    66  		var typ2 *types.Named
    67  		var ident *ast.Ident
    68  		getSelType := func(expr ast.Expr) (types.Type, *ast.Ident, bool) {
    69  			sel, ok := expr.(*ast.SelectorExpr)
    70  			if !ok {
    71  				return nil, nil, false
    72  			}
    73  			ident, ok := sel.X.(*ast.Ident)
    74  			if !ok {
    75  				return nil, nil, false
    76  			}
    77  			typ := pass.TypesInfo.TypeOf(sel.X)
    78  			return typ, ident, typ != nil
    79  		}
    80  		if len(lit.Elts) == 0 {
    81  			return
    82  		}
    83  		if s1.NumFields() != len(lit.Elts) {
    84  			return
    85  		}
    86  		for i, elt := range lit.Elts {
    87  			var t types.Type
    88  			var id *ast.Ident
    89  			var ok bool
    90  			switch elt := elt.(type) {
    91  			case *ast.SelectorExpr:
    92  				t, id, ok = getSelType(elt)
    93  				if !ok {
    94  					return
    95  				}
    96  				if i >= s1.NumFields() || s1.Field(i).Name() != elt.Sel.Name {
    97  					return
    98  				}
    99  			case *ast.KeyValueExpr:
   100  				var sel *ast.SelectorExpr
   101  				sel, ok = elt.Value.(*ast.SelectorExpr)
   102  				if !ok {
   103  					return
   104  				}
   105  
   106  				if elt.Key.(*ast.Ident).Name != sel.Sel.Name {
   107  					return
   108  				}
   109  				t, id, ok = getSelType(elt.Value)
   110  			}
   111  			if !ok {
   112  				return
   113  			}
   114  			// All fields must be initialized from the same object
   115  			if ident != nil && pass.TypesInfo.ObjectOf(ident) != pass.TypesInfo.ObjectOf(id) {
   116  				return
   117  			}
   118  			typ2, _ = t.(*types.Named)
   119  			if typ2 == nil {
   120  				return
   121  			}
   122  			ident = id
   123  		}
   124  
   125  		if typ2 == nil {
   126  			return
   127  		}
   128  
   129  		if typ1.Obj().Pkg() != typ2.Obj().Pkg() {
   130  			// Do not suggest type conversions between different
   131  			// packages. Types in different packages might only match
   132  			// by coincidence. Furthermore, if the dependency ever
   133  			// adds more fields to its type, it could break the code
   134  			// that relies on the type conversion to work.
   135  			return
   136  		}
   137  
   138  		s2, ok := typ2.Underlying().(*types.Struct)
   139  		if !ok {
   140  			return
   141  		}
   142  		if typ1 == typ2 {
   143  			return
   144  		}
   145  		if code.LanguageVersion(pass, node) >= 8 {
   146  			if !types.IdenticalIgnoreTags(s1, s2) {
   147  				return
   148  			}
   149  		} else {
   150  			if !types.Identical(s1, s2) {
   151  				return
   152  			}
   153  		}
   154  
   155  		r := &ast.CallExpr{
   156  			Fun:  lit.Type,
   157  			Args: []ast.Expr{ident},
   158  		}
   159  		report.Report(pass, node,
   160  			fmt.Sprintf("should convert %s (type %s) to %s instead of using struct literal", ident.Name, types.TypeString(typ2, types.RelativeTo(pass.Pkg)), types.TypeString(typ1, types.RelativeTo(pass.Pkg))),
   161  			report.FilterGenerated(),
   162  			report.Fixes(edit.Fix("use type conversion", edit.ReplaceWithNode(pass.Fset, node, r))))
   163  	}
   164  	code.PreorderStack(pass, fn, (*ast.CompositeLit)(nil))
   165  	return nil, nil
   166  }