github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/go/analysis/passes/composite/composite.go (about)

     1  // Copyright 2012 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 composite defines an Analyzer that checks for unkeyed
     6  // composite literals.
     7  package composite
     8  
     9  import (
    10  	"go/ast"
    11  	"go/types"
    12  	"strings"
    13  
    14  	"github.com/powerman/golang-tools/go/analysis"
    15  	"github.com/powerman/golang-tools/go/analysis/passes/inspect"
    16  	"github.com/powerman/golang-tools/go/ast/inspector"
    17  	"github.com/powerman/golang-tools/internal/typeparams"
    18  )
    19  
    20  const Doc = `check for unkeyed composite literals
    21  
    22  This analyzer reports a diagnostic for composite literals of struct
    23  types imported from another package that do not use the field-keyed
    24  syntax. Such literals are fragile because the addition of a new field
    25  (even if unexported) to the struct will cause compilation to fail.
    26  
    27  As an example,
    28  
    29  	err = &net.DNSConfigError{err}
    30  
    31  should be replaced by:
    32  
    33  	err = &net.DNSConfigError{Err: err}
    34  `
    35  
    36  var Analyzer = &analysis.Analyzer{
    37  	Name:             "composites",
    38  	Doc:              Doc,
    39  	Requires:         []*analysis.Analyzer{inspect.Analyzer},
    40  	RunDespiteErrors: true,
    41  	Run:              run,
    42  }
    43  
    44  var whitelist = true
    45  
    46  func init() {
    47  	Analyzer.Flags.BoolVar(&whitelist, "whitelist", whitelist, "use composite white list; for testing only")
    48  }
    49  
    50  // runUnkeyedLiteral checks if a composite literal is a struct literal with
    51  // unkeyed fields.
    52  func run(pass *analysis.Pass) (interface{}, error) {
    53  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    54  
    55  	nodeFilter := []ast.Node{
    56  		(*ast.CompositeLit)(nil),
    57  	}
    58  	inspect.Preorder(nodeFilter, func(n ast.Node) {
    59  		cl := n.(*ast.CompositeLit)
    60  
    61  		typ := pass.TypesInfo.Types[cl].Type
    62  		if typ == nil {
    63  			// cannot determine composite literals' type, skip it
    64  			return
    65  		}
    66  		typeName := typ.String()
    67  		if whitelist && unkeyedLiteral[typeName] {
    68  			// skip whitelisted types
    69  			return
    70  		}
    71  		var structuralTypes []types.Type
    72  		switch typ := typ.(type) {
    73  		case *typeparams.TypeParam:
    74  			terms, err := typeparams.StructuralTerms(typ)
    75  			if err != nil {
    76  				return // invalid type
    77  			}
    78  			for _, term := range terms {
    79  				structuralTypes = append(structuralTypes, term.Type())
    80  			}
    81  		default:
    82  			structuralTypes = append(structuralTypes, typ)
    83  		}
    84  		for _, typ := range structuralTypes {
    85  			under := deref(typ.Underlying())
    86  			if _, ok := under.(*types.Struct); !ok {
    87  				// skip non-struct composite literals
    88  				continue
    89  			}
    90  			if isLocalType(pass, typ) {
    91  				// allow unkeyed locally defined composite literal
    92  				continue
    93  			}
    94  
    95  			// check if the CompositeLit contains an unkeyed field
    96  			allKeyValue := true
    97  			for _, e := range cl.Elts {
    98  				if _, ok := e.(*ast.KeyValueExpr); !ok {
    99  					allKeyValue = false
   100  					break
   101  				}
   102  			}
   103  			if allKeyValue {
   104  				// all the composite literal fields are keyed
   105  				continue
   106  			}
   107  
   108  			pass.ReportRangef(cl, "%s composite literal uses unkeyed fields", typeName)
   109  			return
   110  		}
   111  	})
   112  	return nil, nil
   113  }
   114  
   115  func deref(typ types.Type) types.Type {
   116  	for {
   117  		ptr, ok := typ.(*types.Pointer)
   118  		if !ok {
   119  			break
   120  		}
   121  		typ = ptr.Elem().Underlying()
   122  	}
   123  	return typ
   124  }
   125  
   126  func isLocalType(pass *analysis.Pass, typ types.Type) bool {
   127  	switch x := typ.(type) {
   128  	case *types.Struct:
   129  		// struct literals are local types
   130  		return true
   131  	case *types.Pointer:
   132  		return isLocalType(pass, x.Elem())
   133  	case *types.Named:
   134  		// names in package foo are local to foo_test too
   135  		return strings.TrimSuffix(x.Obj().Pkg().Path(), "_test") == strings.TrimSuffix(pass.Pkg.Path(), "_test")
   136  	case *typeparams.TypeParam:
   137  		return strings.TrimSuffix(x.Obj().Pkg().Path(), "_test") == strings.TrimSuffix(pass.Pkg.Path(), "_test")
   138  	}
   139  	return false
   140  }