github.com/googleapis/api-linter@v1.65.2/.github/quality-checker/main.go (about) 1 // Copyright 2019 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package main 16 17 import ( 18 "fmt" 19 "log" 20 "os" 21 "path/filepath" 22 "regexp" 23 "strconv" 24 "strings" 25 26 "bitbucket.org/creachadair/stringset" 27 ) 28 29 // The set of checkers that are run on every discovered rule. 30 var checkers = []func(aip int, name string) []error{ 31 checkRuleDocumented, 32 checkRuleName, 33 checkRuleRegistered, 34 } 35 36 func main() { 37 errors := []error{} 38 39 // Keep track of rules processed, and which ones have one or more failures. 40 passedRules := stringset.New() 41 failedRules := stringset.New() 42 43 // Begin walking the rules directory looking for rules. 44 err := filepath.Walk("./rules", func(path string, info os.FileInfo, err error) error { 45 // Sanity check: Bubble errors. 46 if err != nil { 47 errors = append(errors, err) 48 return nil 49 } 50 51 // Weed out files that are not rules (tests, etc.). 52 for _, exempt := range []func(path string, info os.FileInfo) bool{ 53 func(_ string, i os.FileInfo) bool { return i.IsDir() }, 54 func(p string, _ os.FileInfo) bool { return strings.Contains(p, "/internal/") }, 55 func(p string, _ os.FileInfo) bool { return p == "rules/rules.go" }, 56 func(p string, _ os.FileInfo) bool { return strings.HasSuffix(p, "_test.go") }, 57 func(p string, _ os.FileInfo) bool { return aipIndex.MatchString(p) }, 58 } { 59 if exempt(path, info) { 60 return nil 61 } 62 } 63 64 // This represents a rule. Get the AIP and rule name from the path. 65 match := ruleFile.FindStringSubmatch(path) 66 if match == nil { 67 errors = append(errors, fmt.Errorf("unexpected path: %s", path)) 68 return nil 69 } 70 71 // Get the AIP number and final rule segment. 72 aip, err := strconv.Atoi(match[1]) 73 if err != nil { 74 errors = append(errors, err) 75 return nil 76 } 77 name := strings.ReplaceAll(match[2], "_", "-") 78 token := fmt.Sprintf("%04d-%s", aip, name) 79 80 // Run each checker and run up the list of errors. 81 for _, checker := range checkers { 82 if errs := checker(aip, name); len(errs) > 0 { 83 errors = append(errors, errs...) 84 failedRules.Add(token) 85 } 86 } 87 88 // All checkers are done; add this to the success list if nothing failed. 89 if !failedRules.Contains(token) { 90 passedRules.Add(token) 91 } 92 93 return nil 94 }) 95 // Ensure the rollup error is nil. (It should be, since our walk function 96 // never returns an error but always appends instead.) 97 if err != nil { 98 errors = append(errors, err) 99 } 100 101 // If we got complaints, complain about them. 102 if len(errors) > 0 { 103 for _, e := range errors { 104 log.Println(fmt.Sprintf("ERROR: %s", e.Error())) 105 } 106 } 107 108 // Provide a summary. 109 fmt.Printf( 110 "%d rules scanned: %d passed, %d failed.\n", 111 len(passedRules)+len(failedRules), 112 len(passedRules), 113 len(failedRules), 114 ) 115 116 // Exit. 117 if len(errors) > 0 { 118 os.Exit(1) 119 } 120 } 121 122 var ( 123 ruleFile = regexp.MustCompile(`rules/aip([\d]{4})/([a-z0-9_]+).go`) 124 aipIndex = regexp.MustCompile(`rules/aip[\d]{4}/aip[\d]{4}\.go`) 125 )