github.com/miolini/go@v0.0.0-20160405192216-fca68c8cb408/src/cmd/vet/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  // This file contains the test for unkeyed struct literals.
     6  
     7  package main
     8  
     9  import (
    10  	"cmd/vet/internal/whitelist"
    11  	"flag"
    12  	"go/ast"
    13  	"strings"
    14  )
    15  
    16  var compositeWhiteList = flag.Bool("compositewhitelist", true, "use composite white list; for testing only")
    17  
    18  func init() {
    19  	register("composites",
    20  		"check that composite literals used field-keyed elements",
    21  		checkUnkeyedLiteral,
    22  		compositeLit)
    23  }
    24  
    25  // checkUnkeyedLiteral checks if a composite literal is a struct literal with
    26  // unkeyed fields.
    27  func checkUnkeyedLiteral(f *File, node ast.Node) {
    28  	c := node.(*ast.CompositeLit)
    29  	typ := c.Type
    30  	for {
    31  		if typ1, ok := c.Type.(*ast.ParenExpr); ok {
    32  			typ = typ1
    33  			continue
    34  		}
    35  		break
    36  	}
    37  
    38  	switch typ.(type) {
    39  	case *ast.ArrayType:
    40  		return
    41  	case *ast.MapType:
    42  		return
    43  	case *ast.StructType:
    44  		return // a literal struct type does not need to use keys
    45  	case *ast.Ident:
    46  		// A simple type name like t or T does not need keys either,
    47  		// since it is almost certainly declared in the current package.
    48  		// (The exception is names being used via import . "pkg", but
    49  		// those are already breaking the Go 1 compatibility promise,
    50  		// so not reporting potential additional breakage seems okay.)
    51  		return
    52  	}
    53  
    54  	// Otherwise the type is a selector like pkg.Name.
    55  	// We only care if pkg.Name is a struct, not if it's a map, array, or slice.
    56  	isStruct, typeString := f.pkg.isStruct(c)
    57  	if !isStruct {
    58  		return
    59  	}
    60  
    61  	if typeString == "" { // isStruct doesn't know
    62  		typeString = f.gofmt(typ)
    63  	}
    64  
    65  	// It's a struct, or we can't tell it's not a struct because we don't have types.
    66  
    67  	// Check if the CompositeLit contains an unkeyed field.
    68  	allKeyValue := true
    69  	for _, e := range c.Elts {
    70  		if _, ok := e.(*ast.KeyValueExpr); !ok {
    71  			allKeyValue = false
    72  			break
    73  		}
    74  	}
    75  	if allKeyValue {
    76  		return
    77  	}
    78  
    79  	// Check that the CompositeLit's type has the form pkg.Typ.
    80  	s, ok := c.Type.(*ast.SelectorExpr)
    81  	if !ok {
    82  		return
    83  	}
    84  	pkg, ok := s.X.(*ast.Ident)
    85  	if !ok {
    86  		return
    87  	}
    88  
    89  	// Convert the package name to an import path, and compare to a whitelist.
    90  	path := pkgPath(f, pkg.Name)
    91  	if path == "" {
    92  		f.Badf(c.Pos(), "unresolvable package for %s.%s literal", pkg.Name, s.Sel.Name)
    93  		return
    94  	}
    95  	typeName := path + "." + s.Sel.Name
    96  	if *compositeWhiteList && whitelist.UnkeyedLiteral[typeName] {
    97  		return
    98  	}
    99  
   100  	f.Bad(c.Pos(), typeString+" composite literal uses unkeyed fields")
   101  }
   102  
   103  // pkgPath returns the import path "image/png" for the package name "png".
   104  //
   105  // This is based purely on syntax and convention, and not on the imported
   106  // package's contents. It will be incorrect if a package name differs from the
   107  // leaf element of the import path, or if the package was a dot import.
   108  func pkgPath(f *File, pkgName string) (path string) {
   109  	for _, x := range f.file.Imports {
   110  		s := strings.Trim(x.Path.Value, `"`)
   111  		if x.Name != nil {
   112  			// Catch `import pkgName "foo/bar"`.
   113  			if x.Name.Name == pkgName {
   114  				return s
   115  			}
   116  		} else {
   117  			// Catch `import "pkgName"` or `import "foo/bar/pkgName"`.
   118  			if s == pkgName || strings.HasSuffix(s, "/"+pkgName) {
   119  				return s
   120  			}
   121  		}
   122  	}
   123  	return ""
   124  }