github.com/orange-cloudfoundry/cli@v7.1.0+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  		v.lastFuncDecl = funcName
   148  	}
   149  }
   150  
   151  func (v *visitor) checkFuncWithReceiver(node *ast.FuncDecl) {
   152  	funcName := node.Name.Name
   153  
   154  	var receiver string
   155  	switch typedType := node.Recv.List[0].Type.(type) {
   156  	case *ast.Ident:
   157  		receiver = typedType.Name
   158  	case *ast.StarExpr:
   159  		receiver = typedType.X.(*ast.Ident).Name
   160  	}
   161  	if v.lastFuncDecl != "" {
   162  		v.addWarning(node.Pos(), "method %s.%s defined after function %s", receiver, funcName, v.lastFuncDecl)
   163  	}
   164  	if len(v.typeSpecs) > 0 {
   165  		lastTypeSpec := v.typeSpecs[len(v.typeSpecs)-1]
   166  		if v.typeDefinedInFile(receiver) && receiver != lastTypeSpec {
   167  			v.addWarning(node.Pos(), "method %s.%s should be defined immediately after type %s", receiver, funcName, receiver)
   168  		}
   169  	}
   170  
   171  	v.lastReceiver = receiver
   172  	v.lastReceiverFunc = funcName
   173  }
   174  
   175  func (v *visitor) checkType(node *ast.TypeSpec) {
   176  	typeName := node.Name.Name
   177  	if v.lastFuncDecl != "" {
   178  		v.addWarning(node.Pos(), "type declaration %s defined after a function declaration", typeName)
   179  	}
   180  	v.typeSpecs = append(v.typeSpecs, typeName)
   181  }
   182  
   183  func (v *visitor) checkVar(node *ast.GenDecl) {
   184  	varName := node.Specs[0].(*ast.ValueSpec).Names[0].Name
   185  
   186  	if v.lastFuncDecl != "" {
   187  		v.addWarning(node.Pos(), "variable %s defined after a function declaration", varName)
   188  	}
   189  	if len(v.typeSpecs) != 0 {
   190  		v.addWarning(node.Pos(), "variable %s defined after a type declaration", varName)
   191  	}
   192  
   193  	if strings.Compare(varName, v.lastVarSpec) == -1 {
   194  		v.addWarning(node.Pos(), "variable %s defined after variable %s", varName, v.lastVarSpec)
   195  	}
   196  
   197  	v.lastVarSpec = varName
   198  }
   199  
   200  func (v *visitor) typeDefinedInFile(typeName string) bool {
   201  	if v.previousPass == nil {
   202  		return true
   203  	}
   204  
   205  	for _, definedTypeName := range v.previousPass.typeSpecs {
   206  		if definedTypeName == typeName {
   207  			return true
   208  		}
   209  	}
   210  
   211  	return false
   212  }
   213  
   214  func check(fileSet *token.FileSet, path string) ([]warning, error) {
   215  	stat, err := os.Stat(path)
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  
   220  	if stat.IsDir() {
   221  		return checkDir(fileSet, path)
   222  	} else {
   223  		return checkFile(fileSet, path)
   224  	}
   225  }
   226  
   227  func checkDir(fileSet *token.FileSet, path string) ([]warning, error) {
   228  	var warnings []warning
   229  
   230  	err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
   231  		if err != nil {
   232  			return err
   233  		}
   234  
   235  		if !info.IsDir() {
   236  			return nil
   237  		}
   238  
   239  		if shouldSkipDir(path) {
   240  			return filepath.SkipDir
   241  		}
   242  
   243  		packages, err := parser.ParseDir(fileSet, path, shouldParseFile, 0)
   244  		if err != nil {
   245  			return err
   246  		}
   247  
   248  		for _, packag := range packages {
   249  			for _, file := range packag.Files {
   250  				warnings = append(warnings, walkFile(fileSet, file)...)
   251  			}
   252  		}
   253  
   254  		return nil
   255  	})
   256  
   257  	return warnings, err
   258  }
   259  
   260  func checkFile(fileSet *token.FileSet, path string) ([]warning, error) {
   261  	file, err := parser.ParseFile(fileSet, path, nil, 0)
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  
   266  	return walkFile(fileSet, file), nil
   267  }
   268  
   269  func main() {
   270  	if len(os.Args) < 2 {
   271  		fmt.Fprintf(os.Stderr, "Usage: %s [--] [FILE or DIRECTORY]...\n", os.Args[0])
   272  		os.Exit(1)
   273  	}
   274  
   275  	var allWarnings []warning
   276  
   277  	args := os.Args[1:]
   278  	if args[0] == "--" {
   279  		args = args[1:]
   280  	}
   281  
   282  	fileSet := token.NewFileSet()
   283  
   284  	for _, arg := range args {
   285  		warnings, err := check(fileSet, arg)
   286  		if err != nil {
   287  			panic(err)
   288  		}
   289  		allWarnings = append(allWarnings, warnings...)
   290  	}
   291  
   292  	warningPrinter := warningPrinter{
   293  		warnings: allWarnings,
   294  	}
   295  	warningPrinter.print(os.Stdout)
   296  
   297  	if len(allWarnings) > 0 {
   298  		os.Exit(1)
   299  	}
   300  }
   301  
   302  func isIn(s string, ary []string) bool {
   303  	for _, item := range ary {
   304  		if s == item {
   305  			return true
   306  		}
   307  	}
   308  	return false
   309  }
   310  
   311  func shouldParseFile(info os.FileInfo) bool {
   312  	return !strings.HasSuffix(info.Name(), "_test.go")
   313  }
   314  
   315  func shouldSkipDir(path string) bool {
   316  	base := filepath.Base(path)
   317  	return base == "vendor" || base == ".git" || strings.HasSuffix(base, "fakes")
   318  }
   319  
   320  func walkFile(fileSet *token.FileSet, file *ast.File) []warning {
   321  	firstPass := visitor{
   322  		fileSet: fileSet,
   323  	}
   324  	ast.Walk(&firstPass, file)
   325  
   326  	v := visitor{
   327  		fileSet:      fileSet,
   328  		previousPass: &firstPass,
   329  	}
   330  	ast.Walk(&v, file)
   331  
   332  	return v.warnings
   333  }