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 }