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 }