Home | History | Annotate | Download | only in vet
      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/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 }
    125