github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+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  	currentConstantNode := node.Specs[0].(*ast.ValueSpec)
   114  	constName := currentConstantNode.Names[0].Name
   115  	constType := fmt.Sprint(currentConstantNode.Type)
   116  
   117  	if v.lastFuncDecl != "" {
   118  		v.addWarning(node.Pos(), "File Positioning: constant %s defined after a function declarations", constName)
   119  	}
   120  	if len(v.typeSpecs) != 0 && !isIn(constType, v.typeSpecs) {
   121  		v.addWarning(node.Pos(), "File Positioning: constant %s defined after a type declarations", constName)
   122  	}
   123  	if v.lastVarSpec != "" {
   124  		v.addWarning(node.Pos(), "File Positioning: constant %s defined after a variable declarations", constName)
   125  	}
   126  
   127  	if strings.Compare(constName, v.lastConstSpec) == -1 {
   128  		v.addWarning(node.Pos(), "Alphabetical Ordering: constant %s defined after constant %s", constName, v.lastConstSpec)
   129  	}
   130  
   131  	v.lastConstSpec = constName
   132  }
   133  
   134  func (v *visitor) checkFunc(node *ast.FuncDecl) {
   135  	if node.Recv != nil {
   136  		v.checkFuncWithReceiver(node)
   137  	} else {
   138  		funcName := node.Name.Name
   139  		if funcName == "Execute" ||
   140  			strings.HasPrefix(funcName, "New") ||
   141  			strings.HasPrefix(funcName, "new") ||
   142  			strings.HasPrefix(funcName, "Default") ||
   143  			strings.HasPrefix(funcName, "default") {
   144  			return
   145  		}
   146  
   147  		if strings.Compare(funcName, v.lastFuncDecl) == -1 {
   148  			v.addWarning(node.Pos(), "Alphabetical Ordering: function %s defined after function %s", funcName, v.lastFuncDecl)
   149  		}
   150  
   151  		v.lastFuncDecl = funcName
   152  	}
   153  }
   154  
   155  func (v *visitor) checkFuncWithReceiver(node *ast.FuncDecl) {
   156  	funcName := node.Name.Name
   157  
   158  	var receiver string
   159  	switch typedType := node.Recv.List[0].Type.(type) {
   160  	case *ast.Ident:
   161  		receiver = typedType.Name
   162  	case *ast.StarExpr:
   163  		receiver = typedType.X.(*ast.Ident).Name
   164  	}
   165  	if v.lastFuncDecl != "" {
   166  		v.addWarning(node.Pos(), "method %s.%s defined after function %s", receiver, funcName, v.lastFuncDecl)
   167  	}
   168  	if len(v.typeSpecs) > 0 {
   169  		lastTypeSpec := v.typeSpecs[len(v.typeSpecs)-1]
   170  		if v.typeDefinedInFile(receiver) && receiver != lastTypeSpec {
   171  			v.addWarning(node.Pos(), "method %s.%s should be defined immediately after type %s", receiver, funcName, receiver)
   172  		}
   173  	}
   174  	if receiver == v.lastReceiver {
   175  		if strings.Compare(lowerSansFirst(funcName), lowerSansFirst(v.lastReceiverFunc)) == -1 {
   176  			v.addWarning(node.Pos(), "Alphabetical Ordering: method %s.%s defined after method %s.%s", receiver, funcName, receiver, v.lastReceiverFunc)
   177  		}
   178  	}
   179  
   180  	v.lastReceiver = receiver
   181  	v.lastReceiverFunc = funcName
   182  }
   183  
   184  func (v *visitor) checkType(node *ast.TypeSpec) {
   185  	typeName := node.Name.Name
   186  	if v.lastFuncDecl != "" {
   187  		v.addWarning(node.Pos(), "type declaration %s defined after a function declaration", typeName)
   188  	}
   189  	v.typeSpecs = append(v.typeSpecs, typeName)
   190  }
   191  
   192  func (v *visitor) checkVar(node *ast.GenDecl) {
   193  	varName := node.Specs[0].(*ast.ValueSpec).Names[0].Name
   194  
   195  	if v.lastFuncDecl != "" {
   196  		v.addWarning(node.Pos(), "variable %s defined after a function declaration", varName)
   197  	}
   198  	if len(v.typeSpecs) != 0 {
   199  		v.addWarning(node.Pos(), "variable %s defined after a type declaration", varName)
   200  	}
   201  
   202  	if strings.Compare(varName, v.lastVarSpec) == -1 {
   203  		v.addWarning(node.Pos(), "variable %s defined after variable %s", varName, v.lastVarSpec)
   204  	}
   205  
   206  	v.lastVarSpec = varName
   207  }
   208  
   209  func (v *visitor) typeDefinedInFile(typeName string) bool {
   210  	if v.previousPass == nil {
   211  		return true
   212  	}
   213  
   214  	for _, definedTypeName := range v.previousPass.typeSpecs {
   215  		if definedTypeName == typeName {
   216  			return true
   217  		}
   218  	}
   219  
   220  	return false
   221  }
   222  
   223  func check(fileSet *token.FileSet, path string) ([]warning, error) {
   224  	stat, err := os.Stat(path)
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  
   229  	if stat.IsDir() {
   230  		return checkDir(fileSet, path)
   231  	} else {
   232  		return checkFile(fileSet, path)
   233  	}
   234  }
   235  
   236  func checkDir(fileSet *token.FileSet, path string) ([]warning, error) {
   237  	var warnings []warning
   238  
   239  	err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
   240  		if err != nil {
   241  			return err
   242  		}
   243  
   244  		if !info.IsDir() {
   245  			return nil
   246  		}
   247  
   248  		if shouldSkipDir(path) {
   249  			return filepath.SkipDir
   250  		}
   251  
   252  		packages, err := parser.ParseDir(fileSet, path, shouldParseFile, 0)
   253  		if err != nil {
   254  			return err
   255  		}
   256  
   257  		for _, packag := range packages {
   258  			for _, file := range packag.Files {
   259  				warnings = append(warnings, walkFile(fileSet, file)...)
   260  			}
   261  		}
   262  
   263  		return nil
   264  	})
   265  
   266  	return warnings, err
   267  }
   268  
   269  func checkFile(fileSet *token.FileSet, path string) ([]warning, error) {
   270  	file, err := parser.ParseFile(fileSet, path, nil, 0)
   271  	if err != nil {
   272  		return nil, err
   273  	}
   274  
   275  	return walkFile(fileSet, file), nil
   276  }
   277  
   278  func main() {
   279  	if len(os.Args) < 2 {
   280  		fmt.Fprintf(os.Stderr, "Usage: %s [--] [FILE or DIRECTORY]...\n", os.Args[0])
   281  		os.Exit(1)
   282  	}
   283  
   284  	var allWarnings []warning
   285  
   286  	args := os.Args[1:]
   287  	if args[0] == "--" {
   288  		args = args[1:]
   289  	}
   290  
   291  	fileSet := token.NewFileSet()
   292  
   293  	for _, arg := range args {
   294  		warnings, err := check(fileSet, arg)
   295  		if err != nil {
   296  			panic(err)
   297  		}
   298  		allWarnings = append(allWarnings, warnings...)
   299  	}
   300  
   301  	warningPrinter := warningPrinter{
   302  		warnings: allWarnings,
   303  	}
   304  	warningPrinter.print(os.Stdout)
   305  
   306  	if len(allWarnings) > 0 {
   307  		os.Exit(1)
   308  	}
   309  }
   310  
   311  func isIn(s string, ary []string) bool {
   312  	for _, item := range ary {
   313  		if s == item {
   314  			return true
   315  		}
   316  	}
   317  	return false
   318  }
   319  
   320  func shouldParseFile(info os.FileInfo) bool {
   321  	return !strings.HasSuffix(info.Name(), "_test.go")
   322  }
   323  
   324  func shouldSkipDir(path string) bool {
   325  	base := filepath.Base(path)
   326  	return base == "vendor" || base == ".git" || strings.HasSuffix(base, "fakes")
   327  }
   328  
   329  func walkFile(fileSet *token.FileSet, file *ast.File) []warning {
   330  	firstPass := visitor{
   331  		fileSet: fileSet,
   332  	}
   333  	ast.Walk(&firstPass, file)
   334  
   335  	v := visitor{
   336  		fileSet:      fileSet,
   337  		previousPass: &firstPass,
   338  	}
   339  	ast.Walk(&v, file)
   340  
   341  	return v.warnings
   342  }
   343  
   344  // lowerSansFirst is used to keep the precedence order of public (uppercased)
   345  // methods over private (lowercased) methods.
   346  func lowerSansFirst(str string) string {
   347  	return str[0:1] + strings.ToLower(str[1:])
   348  }