github.com/swisscom/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 }