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  )