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  )