github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+incompatible/bin/style/main.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/parser"
     7  	"go/token"
     8  	"io"
     9  	"os"
    10  	"path/filepath"
    11  	"sort"
    12  	"strings"
    13  
    14  	"github.com/fatih/color"
    15  )
    16  
    17  type warning struct {
    18  	format string
    19  	vars   []interface{}
    20  	token.Position
    21  }
    22  
    23  type warningPrinter struct {
    24  	warnings []warning
    25  }
    26  
    27  func (w warningPrinter) print(writer io.Writer) {
    28  	w.sortWarnings()
    29  
    30  	for _, warning := range w.warnings {
    31  		coloredVars := make([]interface{}, len(warning.vars))
    32  		for i, v := range warning.vars {
    33  			coloredVars[i] = color.CyanString(v.(string))
    34  		}
    35  
    36  		message := fmt.Sprintf(warning.format, coloredVars...)
    37  
    38  		fmt.Printf(
    39  			"%s %s %s\n",
    40  			color.MagentaString(warning.Position.Filename),
    41  			color.MagentaString(fmt.Sprintf("+%d", warning.Position.Line)),
    42  			message)
    43  	}
    44  }
    45  
    46  func (w warningPrinter) sortWarnings() {
    47  	sort.Slice(w.warnings, func(i int, j int) bool {
    48  		if w.warnings[i].Position.Filename < w.warnings[j].Position.Filename {
    49  			return true
    50  		}
    51  		if w.warnings[i].Position.Filename > w.warnings[j].Position.Filename {
    52  			return false
    53  		}
    54  
    55  		if w.warnings[i].Position.Line < w.warnings[j].Position.Line {
    56  			return true
    57  		}
    58  		if w.warnings[i].Position.Line > w.warnings[j].Position.Line {
    59  			return false
    60  		}
    61  
    62  		iMessage := fmt.Sprintf(w.warnings[i].format, w.warnings[i].vars...)
    63  		jMessage := fmt.Sprintf(w.warnings[j].format, w.warnings[j].vars...)
    64  
    65  		return iMessage < jMessage
    66  	})
    67  }
    68  
    69  type visitor struct {
    70  	fileSet *token.FileSet
    71  
    72  	lastConstSpec    string
    73  	lastFuncDecl     string
    74  	lastReceiverFunc string
    75  	lastReceiver     string
    76  	lastVarSpec      string
    77  	typeSpecs        []string
    78  
    79  	warnings []warning
    80  
    81  	previousPass *visitor
    82  }
    83  
    84  func (v *visitor) Visit(node ast.Node) ast.Visitor {
    85  	switch typedNode := node.(type) {
    86  	case *ast.File:
    87  		return v
    88  	case *ast.GenDecl:
    89  		if typedNode.Tok == token.CONST {
    90  			v.checkConst(typedNode)
    91  		} else if typedNode.Tok == token.VAR {
    92  			v.checkVar(typedNode)
    93  		}
    94  		return v
    95  	case *ast.FuncDecl:
    96  		v.checkFunc(typedNode)
    97  	case *ast.TypeSpec:
    98  		v.checkType(typedNode)
    99  	}
   100  
   101  	return nil
   102  }
   103  
   104  func (v *visitor) addWarning(pos token.Pos, format string, vars ...interface{}) {
   105  	v.warnings = append(v.warnings, warning{
   106  		format:   format,
   107  		vars:     vars,
   108  		Position: v.fileSet.Position(pos),
   109  	})
   110  }
   111  
   112  func (v *visitor) checkConst(node *ast.GenDecl) {
   113  	constName := node.Specs[0].(*ast.ValueSpec).Names[0].Name
   114  
   115  	if v.lastFuncDecl != "" {
   116  		v.addWarning(node.Pos(), "constant %s defined after a function declaration", constName)
   117  	}
   118  	if len(v.typeSpecs) != 0 {
   119  		v.addWarning(node.Pos(), "constant %s defined after a type declaration", constName)
   120  	}
   121  	if v.lastVarSpec != "" {
   122  		v.addWarning(node.Pos(), "constant %s defined after a variable declaration", constName)
   123  	}
   124  
   125  	if strings.Compare(constName, v.lastConstSpec) == -1 {
   126  		v.addWarning(node.Pos(), "constant %s defined after constant %s", constName, v.lastConstSpec)
   127  	}
   128  
   129  	v.lastConstSpec = constName
   130  }
   131  
   132  func (v *visitor) checkFunc(node *ast.FuncDecl) {
   133  	if node.Recv != nil {
   134  		v.checkFuncWithReceiver(node)
   135  	} else {
   136  		funcName := node.Name.Name
   137  		if funcName == "Execute" || strings.HasPrefix(funcName, "New") {
   138  			return
   139  		}
   140  
   141  		if strings.Compare(funcName, v.lastFuncDecl) == -1 {
   142  			v.addWarning(node.Pos(), "function %s defined after function %s", funcName, v.lastFuncDecl)
   143  		}
   144  
   145  		v.lastFuncDecl = funcName
   146  	}
   147  }
   148  
   149  func (v *visitor) checkFuncWithReceiver(node *ast.FuncDecl) {
   150  	funcName := node.Name.Name
   151  
   152  	var receiver string
   153  	switch typedType := node.Recv.List[0].Type.(type) {
   154  	case *ast.Ident:
   155  		receiver = typedType.Name
   156  	case *ast.StarExpr:
   157  		receiver = typedType.X.(*ast.Ident).Name
   158  	}
   159  	if v.lastFuncDecl != "" {
   160  		v.addWarning(node.Pos(), "method %s.%s defined after function %s", receiver, funcName, v.lastFuncDecl)
   161  	}
   162  	if len(v.typeSpecs) > 0 {
   163  		lastTypeSpec := v.typeSpecs[len(v.typeSpecs)-1]
   164  		if v.typeDefinedInFile(receiver) && receiver != lastTypeSpec {
   165  			v.addWarning(node.Pos(), "method %s.%s should be defined immediately after type %s", receiver, funcName, receiver)
   166  		}
   167  	}
   168  	if receiver == v.lastReceiver {
   169  		if strings.Compare(funcName, v.lastReceiverFunc) == -1 {
   170  			v.addWarning(node.Pos(), "method %s.%s defined after method %s.%s", receiver, funcName, receiver, v.lastReceiverFunc)
   171  		}
   172  	}
   173  
   174  	v.lastReceiver = receiver
   175  	v.lastReceiverFunc = funcName
   176  }
   177  
   178  func (v *visitor) checkType(node *ast.TypeSpec) {
   179  	typeName := node.Name.Name
   180  	if v.lastFuncDecl != "" {
   181  		v.addWarning(node.Pos(), "type declaration %s defined after a function declaration", typeName)
   182  	}
   183  	v.typeSpecs = append(v.typeSpecs, typeName)
   184  }
   185  
   186  func (v *visitor) checkVar(node *ast.GenDecl) {
   187  	varName := node.Specs[0].(*ast.ValueSpec).Names[0].Name
   188  
   189  	if v.lastFuncDecl != "" {
   190  		v.addWarning(node.Pos(), "variable %s defined after a function declaration", varName)
   191  	}
   192  	if len(v.typeSpecs) != 0 {
   193  		v.addWarning(node.Pos(), "variable %s defined after a type declaration", varName)
   194  	}
   195  
   196  	if strings.Compare(varName, v.lastVarSpec) == -1 {
   197  		v.addWarning(node.Pos(), "variable %s defined after variable %s", varName, v.lastVarSpec)
   198  	}
   199  
   200  	v.lastVarSpec = varName
   201  }
   202  
   203  func (v *visitor) typeDefinedInFile(typeName string) bool {
   204  	if v.previousPass == nil {
   205  		return true
   206  	}
   207  
   208  	for _, definedTypeName := range v.previousPass.typeSpecs {
   209  		if definedTypeName == typeName {
   210  			return true
   211  		}
   212  	}
   213  
   214  	return false
   215  }
   216  
   217  func check(fileSet *token.FileSet, path string) ([]warning, error) {
   218  	stat, err := os.Stat(path)
   219  	if err != nil {
   220  		return nil, err
   221  	}
   222  
   223  	if stat.IsDir() {
   224  		return checkDir(fileSet, path)
   225  	} else {
   226  		return checkFile(fileSet, path)
   227  	}
   228  }
   229  
   230  func checkDir(fileSet *token.FileSet, path string) ([]warning, error) {
   231  	var warnings []warning
   232  
   233  	err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
   234  		if !info.IsDir() {
   235  			return nil
   236  		}
   237  
   238  		if shouldSkipDir(path) {
   239  			return filepath.SkipDir
   240  		}
   241  
   242  		packages, err := parser.ParseDir(fileSet, path, shouldParseFile, 0)
   243  		if err != nil {
   244  			return err
   245  		}
   246  
   247  		for _, packag := range packages {
   248  			for _, file := range packag.Files {
   249  				warnings = append(warnings, walkFile(fileSet, file)...)
   250  			}
   251  		}
   252  
   253  		return nil
   254  	})
   255  
   256  	return warnings, err
   257  }
   258  
   259  func checkFile(fileSet *token.FileSet, path string) ([]warning, error) {
   260  	file, err := parser.ParseFile(fileSet, path, nil, 0)
   261  	if err != nil {
   262  		return nil, err
   263  	}
   264  
   265  	return walkFile(fileSet, file), nil
   266  }
   267  
   268  func main() {
   269  	if len(os.Args) < 2 {
   270  		fmt.Fprintf(os.Stderr, "Usage: %s [--] [FILE or DIRECTORY]...\n", os.Args[0])
   271  		os.Exit(1)
   272  	}
   273  
   274  	var allWarnings []warning
   275  
   276  	args := os.Args[1:]
   277  	if args[0] == "--" {
   278  		args = args[1:]
   279  	}
   280  
   281  	fileSet := token.NewFileSet()
   282  
   283  	for _, arg := range args {
   284  		warnings, err := check(fileSet, arg)
   285  		if err != nil {
   286  			panic(err)
   287  		}
   288  		allWarnings = append(allWarnings, warnings...)
   289  	}
   290  
   291  	warningPrinter := warningPrinter{
   292  		warnings: allWarnings,
   293  	}
   294  	warningPrinter.print(os.Stdout)
   295  
   296  	if len(allWarnings) > 0 {
   297  		os.Exit(1)
   298  	}
   299  }
   300  
   301  func shouldParseFile(info os.FileInfo) bool {
   302  	return !strings.HasSuffix(info.Name(), "_test.go")
   303  }
   304  
   305  func shouldSkipDir(path string) bool {
   306  	base := filepath.Base(path)
   307  	return base == "vendor" || base == ".git" || strings.HasSuffix(base, "fakes")
   308  }
   309  
   310  func walkFile(fileSet *token.FileSet, file *ast.File) []warning {
   311  	firstPass := visitor{
   312  		fileSet: fileSet,
   313  	}
   314  	ast.Walk(&firstPass, file)
   315  
   316  	v := visitor{
   317  		fileSet:      fileSet,
   318  		previousPass: &firstPass,
   319  	}
   320  	ast.Walk(&v, file)
   321  
   322  	return v.warnings
   323  }