gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/github.com/opennota/check/cmd/structcheck/structcheck.go (about)

     1  // structcheck
     2  // This program is free software: you can redistribute it and/or modify
     3  // it under the terms of the GNU General Public License as published by
     4  // the Free Software Foundation, either version 3 of the License, or
     5  // (at your option) any later version.
     6  //
     7  // This program is distributed in the hope that it will be useful,
     8  // but WITHOUT ANY WARRANTY; without even the implied warranty of
     9  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    10  // GNU General Public License for more details.
    11  //
    12  // You should have received a copy of the GNU General Public License
    13  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    14  
    15  package main
    16  
    17  import (
    18  	"flag"
    19  	"fmt"
    20  	"go/ast"
    21  	"go/build"
    22  	"go/types"
    23  	"os"
    24  	"strings"
    25  
    26  	"github.com/kisielk/gotool"
    27  	"golang.org/x/tools/go/loader"
    28  )
    29  
    30  var (
    31  	assignmentsOnly = flag.Bool("a", false, "Count assignments only")
    32  	loadTestFiles   = flag.Bool("t", false, "Load test files too")
    33  	reportExported  = flag.Bool("e", false, "Report exported fields")
    34  	buildTags       = flag.String("tags", "", "Build tags")
    35  )
    36  
    37  type visitor struct {
    38  	prog *loader.Program
    39  	pkg  *loader.PackageInfo
    40  	m    map[types.Type]map[string]int
    41  	skip map[types.Type]struct{}
    42  }
    43  
    44  func (v *visitor) decl(t types.Type, fieldName string) {
    45  	if _, ok := v.m[t]; !ok {
    46  		v.m[t] = make(map[string]int)
    47  	}
    48  	if _, ok := v.m[t][fieldName]; !ok {
    49  		v.m[t][fieldName] = 0
    50  	}
    51  }
    52  
    53  func (v *visitor) assignment(t types.Type, fieldName string) {
    54  	if _, ok := v.m[t]; !ok {
    55  		v.m[t] = make(map[string]int)
    56  	}
    57  	if _, ok := v.m[t][fieldName]; ok {
    58  		v.m[t][fieldName]++
    59  	} else {
    60  		v.m[t][fieldName] = 1
    61  	}
    62  }
    63  
    64  func (v *visitor) typeSpec(node *ast.TypeSpec) {
    65  	if strukt, ok := node.Type.(*ast.StructType); ok {
    66  		t := v.pkg.Info.Defs[node.Name].Type()
    67  		for _, f := range strukt.Fields.List {
    68  			if len(f.Names) > 0 {
    69  				fieldName := f.Names[0].Name
    70  				v.decl(t, fieldName)
    71  			}
    72  		}
    73  	}
    74  }
    75  
    76  func (v *visitor) typeAndFieldName(expr *ast.SelectorExpr) (types.Type, string, bool) {
    77  	selection := v.pkg.Info.Selections[expr]
    78  	if selection == nil {
    79  		return nil, "", false
    80  	}
    81  	recv := selection.Recv()
    82  	if ptr, ok := recv.(*types.Pointer); ok {
    83  		recv = ptr.Elem()
    84  	}
    85  	return recv, selection.Obj().Name(), true
    86  }
    87  
    88  func (v *visitor) assignStmt(node *ast.AssignStmt) {
    89  	for _, lhs := range node.Lhs {
    90  		var selector *ast.SelectorExpr
    91  		switch expr := lhs.(type) {
    92  		case *ast.SelectorExpr:
    93  			selector = expr
    94  		case *ast.IndexExpr:
    95  			if expr, ok := expr.X.(*ast.SelectorExpr); ok {
    96  				selector = expr
    97  			}
    98  		}
    99  		if selector != nil {
   100  			if t, fn, ok := v.typeAndFieldName(selector); ok {
   101  				v.assignment(t, fn)
   102  			}
   103  		}
   104  	}
   105  }
   106  
   107  func (v *visitor) compositeLiteral(node *ast.CompositeLit) {
   108  	t := v.pkg.Info.Types[node.Type].Type
   109  	for _, expr := range node.Elts {
   110  		if kv, ok := expr.(*ast.KeyValueExpr); ok {
   111  			if ident, ok := kv.Key.(*ast.Ident); ok {
   112  				v.assignment(t, ident.Name)
   113  			}
   114  		} else {
   115  			// Struct literal with positional values.
   116  			// All the fields are assigned.
   117  			v.skip[t] = struct{}{}
   118  			break
   119  		}
   120  	}
   121  }
   122  
   123  func (v *visitor) Visit(node ast.Node) ast.Visitor {
   124  	switch node := node.(type) {
   125  	case *ast.TypeSpec:
   126  		v.typeSpec(node)
   127  
   128  	case *ast.AssignStmt:
   129  		if *assignmentsOnly {
   130  			v.assignStmt(node)
   131  		}
   132  
   133  	case *ast.SelectorExpr:
   134  		if !*assignmentsOnly {
   135  			if t, fn, ok := v.typeAndFieldName(node); ok {
   136  				v.assignment(t, fn)
   137  			}
   138  		}
   139  
   140  	case *ast.CompositeLit:
   141  		v.compositeLiteral(node)
   142  	}
   143  
   144  	return v
   145  }
   146  
   147  func main() {
   148  	flag.Parse()
   149  	exitStatus := 0
   150  	importPaths := gotool.ImportPaths(flag.Args())
   151  	if len(importPaths) == 0 {
   152  		importPaths = []string{"."}
   153  	}
   154  	ctx := build.Default
   155  	if *buildTags != "" {
   156  		ctx.BuildTags = strings.Split(*buildTags, ",")
   157  	}
   158  	loadcfg := loader.Config{
   159  		Build: &ctx,
   160  	}
   161  	rest, err := loadcfg.FromArgs(importPaths, *loadTestFiles)
   162  	if err != nil {
   163  		fmt.Fprintf(os.Stderr, "could not parse arguments: %s", err)
   164  		os.Exit(1)
   165  	}
   166  	if len(rest) > 0 {
   167  		fmt.Fprintf(os.Stderr, "unhandled extra arguments: %v", rest)
   168  		os.Exit(1)
   169  	}
   170  
   171  	program, err := loadcfg.Load()
   172  	if err != nil {
   173  		fmt.Fprintf(os.Stderr, "could not type check: %s", err)
   174  		os.Exit(1)
   175  	}
   176  
   177  	for _, pkg := range program.InitialPackages() {
   178  		visitor := &visitor{
   179  			m:    make(map[types.Type]map[string]int),
   180  			skip: make(map[types.Type]struct{}),
   181  			prog: program,
   182  			pkg:  pkg,
   183  		}
   184  		for _, f := range pkg.Files {
   185  			ast.Walk(visitor, f)
   186  		}
   187  
   188  		for t := range visitor.m {
   189  			if _, skip := visitor.skip[t]; skip {
   190  				continue
   191  			}
   192  			for fieldName, v := range visitor.m[t] {
   193  				if !*reportExported && ast.IsExported(fieldName) {
   194  					continue
   195  				}
   196  				if v == 0 {
   197  					field, _, _ := types.LookupFieldOrMethod(t, false, pkg.Pkg, fieldName)
   198  					if field == nil {
   199  						fmt.Printf("%s: unknown field or method: %s.%s\n", pkg.Pkg.Path(), t, fieldName)
   200  						exitStatus = 1
   201  						continue
   202  					}
   203  					if fieldName == "XMLName" {
   204  						if named, ok := field.Type().(*types.Named); ok && named.Obj().Pkg().Path() == "encoding/xml" {
   205  							continue
   206  						}
   207  					}
   208  					pos := program.Fset.Position(field.Pos())
   209  					fmt.Printf("%s: %s:%d:%d: %s.%s\n",
   210  						pkg.Pkg.Path(), pos.Filename, pos.Line, pos.Column,
   211  						types.TypeString(t, nil), fieldName,
   212  					)
   213  					exitStatus = 1
   214  				}
   215  			}
   216  		}
   217  	}
   218  	os.Exit(exitStatus)
   219  }