// Copyright 2012 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package composite defines an Analyzer that checks for unkeyed // composite literals. package composite import ( "go/ast" "go/types" "strings" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/ast/inspector" ) const Doc = `check for unkeyed composite literals This analyzer reports a diagnostic for composite literals of struct types imported from another package that do not use the field-keyed syntax. Such literals are fragile because the addition of a new field (even if unexported) to the struct will cause compilation to fail. As an example, err = &net.DNSConfigError{err} should be replaced by: err = &net.DNSConfigError{Err: err} ` var Analyzer = &analysis.Analyzer{ Name: "composites", Doc: Doc, Requires: []*analysis.Analyzer{inspect.Analyzer}, RunDespiteErrors: true, Run: run, } var whitelist = true func init() { Analyzer.Flags.BoolVar(&whitelist, "whitelist", whitelist, "use composite white list; for testing only") } // runUnkeyedLiteral checks if a composite literal is a struct literal with // unkeyed fields. func run(pass *analysis.Pass) (interface{}, error) { inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) nodeFilter := []ast.Node{ (*ast.CompositeLit)(nil), } inspect.Preorder(nodeFilter, func(n ast.Node) { cl := n.(*ast.CompositeLit) typ := pass.TypesInfo.Types[cl].Type if typ == nil { // cannot determine composite literals' type, skip it return } typeName := typ.String() if whitelist && unkeyedLiteral[typeName] { // skip whitelisted types return } under := typ.Underlying() for { ptr, ok := under.(*types.Pointer) if !ok { break } under = ptr.Elem().Underlying() } if _, ok := under.(*types.Struct); !ok { // skip non-struct composite literals return } if isLocalType(pass, typ) { // allow unkeyed locally defined composite literal return } // check if the CompositeLit contains an unkeyed field allKeyValue := true for _, e := range cl.Elts { if _, ok := e.(*ast.KeyValueExpr); !ok { allKeyValue = false break } } if allKeyValue { // all the composite literal fields are keyed return } pass.ReportRangef(cl, "%s composite literal uses unkeyed fields", typeName) }) return nil, nil } func isLocalType(pass *analysis.Pass, typ types.Type) bool { switch x := typ.(type) { case *types.Struct: // struct literals are local types return true case *types.Pointer: return isLocalType(pass, x.Elem()) case *types.Named: // names in package foo are local to foo_test too return strings.TrimSuffix(x.Obj().Pkg().Path(), "_test") == strings.TrimSuffix(pass.Pkg.Path(), "_test") } return false }