github.com/april1989/origin-go-tools@v0.0.32/internal/lsp/analysis/simplifycompositelit/simplifycompositelit.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 simplifycompositelit defines an Analyzer that simplifies composite literals. 6 // https://github.com/golang/go/blob/master/src/cmd/gofmt/simplify.go 7 // https://golang.org/cmd/gofmt/#hdr-The_simplify_command 8 package simplifycompositelit 9 10 import ( 11 "bytes" 12 "fmt" 13 "go/ast" 14 "go/printer" 15 "go/token" 16 "reflect" 17 "unicode" 18 "unicode/utf8" 19 20 "github.com/april1989/origin-go-tools/go/analysis" 21 "github.com/april1989/origin-go-tools/go/analysis/passes/inspect" 22 "github.com/april1989/origin-go-tools/go/ast/inspector" 23 ) 24 25 const Doc = `check for composite literal simplifications 26 27 An array, slice, or map composite literal of the form: 28 []T{T{}, T{}} 29 will be simplified to: 30 []T{{}, {}} 31 32 This is one of the simplifications that "gofmt -s" applies.` 33 34 var Analyzer = &analysis.Analyzer{ 35 Name: "simplifycompositelit", 36 Doc: Doc, 37 Requires: []*analysis.Analyzer{inspect.Analyzer}, 38 Run: run, 39 } 40 41 func run(pass *analysis.Pass) (interface{}, error) { 42 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 43 nodeFilter := []ast.Node{(*ast.CompositeLit)(nil)} 44 inspect.Preorder(nodeFilter, func(n ast.Node) { 45 expr := n.(*ast.CompositeLit) 46 47 outer := expr 48 var keyType, eltType ast.Expr 49 switch typ := outer.Type.(type) { 50 case *ast.ArrayType: 51 eltType = typ.Elt 52 case *ast.MapType: 53 keyType = typ.Key 54 eltType = typ.Value 55 } 56 57 if eltType == nil { 58 return 59 } 60 var ktyp reflect.Value 61 if keyType != nil { 62 ktyp = reflect.ValueOf(keyType) 63 } 64 typ := reflect.ValueOf(eltType) 65 for _, x := range outer.Elts { 66 // look at value of indexed/named elements 67 if t, ok := x.(*ast.KeyValueExpr); ok { 68 if keyType != nil { 69 simplifyLiteral(pass, ktyp, keyType, t.Key) 70 } 71 x = t.Value 72 } 73 simplifyLiteral(pass, typ, eltType, x) 74 } 75 }) 76 return nil, nil 77 } 78 79 func simplifyLiteral(pass *analysis.Pass, typ reflect.Value, astType, x ast.Expr) { 80 // if the element is a composite literal and its literal type 81 // matches the outer literal's element type exactly, the inner 82 // literal type may be omitted 83 if inner, ok := x.(*ast.CompositeLit); ok && match(typ, reflect.ValueOf(inner.Type)) { 84 var b bytes.Buffer 85 printer.Fprint(&b, pass.Fset, inner.Type) 86 createDiagnostic(pass, inner.Type.Pos(), inner.Type.End(), b.String()) 87 } 88 // if the outer literal's element type is a pointer type *T 89 // and the element is & of a composite literal of type T, 90 // the inner &T may be omitted. 91 if ptr, ok := astType.(*ast.StarExpr); ok { 92 if addr, ok := x.(*ast.UnaryExpr); ok && addr.Op == token.AND { 93 if inner, ok := addr.X.(*ast.CompositeLit); ok { 94 if match(reflect.ValueOf(ptr.X), reflect.ValueOf(inner.Type)) { 95 var b bytes.Buffer 96 printer.Fprint(&b, pass.Fset, inner.Type) 97 // Account for the & by subtracting 1 from typ.Pos(). 98 createDiagnostic(pass, inner.Type.Pos()-1, inner.Type.End(), "&"+b.String()) 99 } 100 } 101 } 102 } 103 } 104 105 func createDiagnostic(pass *analysis.Pass, start, end token.Pos, typ string) { 106 pass.Report(analysis.Diagnostic{ 107 Pos: start, 108 End: end, 109 Message: "redundant type from array, slice, or map composite literal", 110 SuggestedFixes: []analysis.SuggestedFix{{ 111 Message: fmt.Sprintf("Remove '%s'", typ), 112 TextEdits: []analysis.TextEdit{{ 113 Pos: start, 114 End: end, 115 NewText: []byte{}, 116 }}, 117 }}, 118 }) 119 } 120 121 // match reports whether pattern matches val, 122 // recording wildcard submatches in m. 123 // If m == nil, match checks whether pattern == val. 124 // from https://github.com/golang/go/blob/26154f31ad6c801d8bad5ef58df1e9263c6beec7/src/cmd/gofmt/rewrite.go#L160 125 func match(pattern, val reflect.Value) bool { 126 // Otherwise, pattern and val must match recursively. 127 if !pattern.IsValid() || !val.IsValid() { 128 return !pattern.IsValid() && !val.IsValid() 129 } 130 if pattern.Type() != val.Type() { 131 return false 132 } 133 134 // Special cases. 135 switch pattern.Type() { 136 case identType: 137 // For identifiers, only the names need to match 138 // (and none of the other *ast.Object information). 139 // This is a common case, handle it all here instead 140 // of recursing down any further via reflection. 141 p := pattern.Interface().(*ast.Ident) 142 v := val.Interface().(*ast.Ident) 143 return p == nil && v == nil || p != nil && v != nil && p.Name == v.Name 144 case objectPtrType, positionType: 145 // object pointers and token positions always match 146 return true 147 case callExprType: 148 // For calls, the Ellipsis fields (token.Position) must 149 // match since that is how f(x) and f(x...) are different. 150 // Check them here but fall through for the remaining fields. 151 p := pattern.Interface().(*ast.CallExpr) 152 v := val.Interface().(*ast.CallExpr) 153 if p.Ellipsis.IsValid() != v.Ellipsis.IsValid() { 154 return false 155 } 156 } 157 158 p := reflect.Indirect(pattern) 159 v := reflect.Indirect(val) 160 if !p.IsValid() || !v.IsValid() { 161 return !p.IsValid() && !v.IsValid() 162 } 163 164 switch p.Kind() { 165 case reflect.Slice: 166 if p.Len() != v.Len() { 167 return false 168 } 169 for i := 0; i < p.Len(); i++ { 170 if !match(p.Index(i), v.Index(i)) { 171 return false 172 } 173 } 174 return true 175 176 case reflect.Struct: 177 for i := 0; i < p.NumField(); i++ { 178 if !match(p.Field(i), v.Field(i)) { 179 return false 180 } 181 } 182 return true 183 184 case reflect.Interface: 185 return match(p.Elem(), v.Elem()) 186 } 187 188 // Handle token integers, etc. 189 return p.Interface() == v.Interface() 190 } 191 192 func isWildcard(s string) bool { 193 rune, size := utf8.DecodeRuneInString(s) 194 return size == len(s) && unicode.IsLower(rune) 195 } 196 197 // Values/types for special cases. 198 var ( 199 identType = reflect.TypeOf((*ast.Ident)(nil)) 200 objectPtrType = reflect.TypeOf((*ast.Object)(nil)) 201 positionType = reflect.TypeOf(token.NoPos) 202 callExprType = reflect.TypeOf((*ast.CallExpr)(nil)) 203 scopePtrType = reflect.TypeOf((*ast.Scope)(nil)) 204 )