...

Source file src/golang.org/x/tools/go/analysis/passes/composite/composite.go

Documentation: golang.org/x/tools/go/analysis/passes/composite

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

View as plain text