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 }