| // Copyright 2020 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package simplifycompositelit defines an Analyzer that simplifies composite literals. |
| // https://github.com/golang/go/blob/master/src/cmd/gofmt/simplify.go |
| // https://golang.org/cmd/gofmt/#hdr-The_simplify_command |
| package simplifycompositelit |
| |
| import ( |
| "bytes" |
| "fmt" |
| "go/ast" |
| "go/printer" |
| "go/token" |
| "reflect" |
| "unicode" |
| "unicode/utf8" |
| |
| "golang.org/x/tools/go/analysis" |
| "golang.org/x/tools/go/analysis/passes/inspect" |
| "golang.org/x/tools/go/ast/inspector" |
| ) |
| |
| const Doc = `check for composite literal simplifications |
| |
| An array, slice, or map composite literal of the form: |
| []T{T{}, T{}} |
| will be simplified to: |
| []T{{}, {}} |
| |
| This is one of the simplifications that "gofmt -s" applies.` |
| |
| var Analyzer = &analysis.Analyzer{ |
| Name: "simplifycompositelit", |
| Doc: Doc, |
| Requires: []*analysis.Analyzer{inspect.Analyzer}, |
| Run: run, |
| } |
| |
| func run(pass *analysis.Pass) (interface{}, error) { |
| inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) |
| nodeFilter := []ast.Node{(*ast.CompositeLit)(nil)} |
| inspect.Preorder(nodeFilter, func(n ast.Node) { |
| expr := n.(*ast.CompositeLit) |
| |
| outer := expr |
| var keyType, eltType ast.Expr |
| switch typ := outer.Type.(type) { |
| case *ast.ArrayType: |
| eltType = typ.Elt |
| case *ast.MapType: |
| keyType = typ.Key |
| eltType = typ.Value |
| } |
| |
| if eltType == nil { |
| return |
| } |
| var ktyp reflect.Value |
| if keyType != nil { |
| ktyp = reflect.ValueOf(keyType) |
| } |
| typ := reflect.ValueOf(eltType) |
| for _, x := range outer.Elts { |
| // look at value of indexed/named elements |
| if t, ok := x.(*ast.KeyValueExpr); ok { |
| if keyType != nil { |
| simplifyLiteral(pass, ktyp, keyType, t.Key) |
| } |
| x = t.Value |
| } |
| simplifyLiteral(pass, typ, eltType, x) |
| } |
| }) |
| return nil, nil |
| } |
| |
| func simplifyLiteral(pass *analysis.Pass, typ reflect.Value, astType, x ast.Expr) { |
| // if the element is a composite literal and its literal type |
| // matches the outer literal's element type exactly, the inner |
| // literal type may be omitted |
| if inner, ok := x.(*ast.CompositeLit); ok && match(typ, reflect.ValueOf(inner.Type)) { |
| var b bytes.Buffer |
| printer.Fprint(&b, pass.Fset, inner.Type) |
| createDiagnostic(pass, inner.Type.Pos(), inner.Type.End(), b.String()) |
| } |
| // if the outer literal's element type is a pointer type *T |
| // and the element is & of a composite literal of type T, |
| // the inner &T may be omitted. |
| if ptr, ok := astType.(*ast.StarExpr); ok { |
| if addr, ok := x.(*ast.UnaryExpr); ok && addr.Op == token.AND { |
| if inner, ok := addr.X.(*ast.CompositeLit); ok { |
| if match(reflect.ValueOf(ptr.X), reflect.ValueOf(inner.Type)) { |
| var b bytes.Buffer |
| printer.Fprint(&b, pass.Fset, inner.Type) |
| // Account for the & by subtracting 1 from typ.Pos(). |
| createDiagnostic(pass, inner.Type.Pos()-1, inner.Type.End(), "&"+b.String()) |
| } |
| } |
| } |
| } |
| } |
| |
| func createDiagnostic(pass *analysis.Pass, start, end token.Pos, typ string) { |
| pass.Report(analysis.Diagnostic{ |
| Pos: start, |
| End: end, |
| Message: "redundant type from array, slice, or map composite literal", |
| SuggestedFixes: []analysis.SuggestedFix{{ |
| Message: fmt.Sprintf("Remove '%s'", typ), |
| TextEdits: []analysis.TextEdit{{ |
| Pos: start, |
| End: end, |
| NewText: []byte{}, |
| }}, |
| }}, |
| }) |
| } |
| |
| // match reports whether pattern matches val, |
| // recording wildcard submatches in m. |
| // If m == nil, match checks whether pattern == val. |
| // from https://github.com/golang/go/blob/26154f31ad6c801d8bad5ef58df1e9263c6beec7/src/cmd/gofmt/rewrite.go#L160 |
| func match(pattern, val reflect.Value) bool { |
| // Otherwise, pattern and val must match recursively. |
| if !pattern.IsValid() || !val.IsValid() { |
| return !pattern.IsValid() && !val.IsValid() |
| } |
| if pattern.Type() != val.Type() { |
| return false |
| } |
| |
| // Special cases. |
| switch pattern.Type() { |
| case identType: |
| // For identifiers, only the names need to match |
| // (and none of the other *ast.Object information). |
| // This is a common case, handle it all here instead |
| // of recursing down any further via reflection. |
| p := pattern.Interface().(*ast.Ident) |
| v := val.Interface().(*ast.Ident) |
| return p == nil && v == nil || p != nil && v != nil && p.Name == v.Name |
| case objectPtrType, positionType: |
| // object pointers and token positions always match |
| return true |
| case callExprType: |
| // For calls, the Ellipsis fields (token.Position) must |
| // match since that is how f(x) and f(x...) are different. |
| // Check them here but fall through for the remaining fields. |
| p := pattern.Interface().(*ast.CallExpr) |
| v := val.Interface().(*ast.CallExpr) |
| if p.Ellipsis.IsValid() != v.Ellipsis.IsValid() { |
| return false |
| } |
| } |
| |
| p := reflect.Indirect(pattern) |
| v := reflect.Indirect(val) |
| if !p.IsValid() || !v.IsValid() { |
| return !p.IsValid() && !v.IsValid() |
| } |
| |
| switch p.Kind() { |
| case reflect.Slice: |
| if p.Len() != v.Len() { |
| return false |
| } |
| for i := 0; i < p.Len(); i++ { |
| if !match(p.Index(i), v.Index(i)) { |
| return false |
| } |
| } |
| return true |
| |
| case reflect.Struct: |
| for i := 0; i < p.NumField(); i++ { |
| if !match(p.Field(i), v.Field(i)) { |
| return false |
| } |
| } |
| return true |
| |
| case reflect.Interface: |
| return match(p.Elem(), v.Elem()) |
| } |
| |
| // Handle token integers, etc. |
| return p.Interface() == v.Interface() |
| } |
| |
| func isWildcard(s string) bool { |
| rune, size := utf8.DecodeRuneInString(s) |
| return size == len(s) && unicode.IsLower(rune) |
| } |
| |
| // Values/types for special cases. |
| var ( |
| identType = reflect.TypeOf((*ast.Ident)(nil)) |
| objectPtrType = reflect.TypeOf((*ast.Object)(nil)) |
| positionType = reflect.TypeOf(token.NoPos) |
| callExprType = reflect.TypeOf((*ast.CallExpr)(nil)) |
| scopePtrType = reflect.TypeOf((*ast.Scope)(nil)) |
| ) |