// Command checksilent reports the failures in this repository that do not // announce themselves: no error, no log line, behaviour quietly wrong. // // Seven checks, six of them ERROR and one WARN. An ERROR fails the run; a WARN // prints and does not. The split is not about how bad the consequence is - all // seven are bad - but about how certain the detection is. Everything reported as // an ERROR is decided from this repository's own syntax. The one WARN compares // against a second repository through a regular expression, and a check that // can be wrong must not be able to stop a build, or the first response to it // will be an ignore comment. // // Usage: // // go run ./tools/checksilent // go run ./tools/checksilent -ui-dir ../go-admin-ui/src // go run ./tools/checksilent -json // // Or through the Makefile, which is what CI runs: // // make checksilent package main import ( "encoding/json" "flag" "fmt" "io" "os" "strings" ) func main() { var ( root = flag.String("root", ".", "repository root to scan") uiDir = flag.String("ui-dir", "", "go-admin-ui src directory; enables the menu-name check, which is skipped without it") asJSON = flag.Bool("json", false, "print findings as JSON") ) flag.Parse() code, err := run(os.Stdout, *root, options{UIDir: *uiDir}, *asJSON) if err != nil { fmt.Fprintln(os.Stderr, "checksilent:", err) os.Exit(2) } os.Exit(code) } // run returns the process exit code: 0 when nothing or only warnings were // found, 1 when at least one error was. func run(w io.Writer, root string, opt options, asJSON bool) (int, error) { s, err := load(root) if err != nil { return 0, err } findings, err := runChecks(s, opt) if err != nil { return 0, err } if asJSON { enc := json.NewEncoder(w) enc.SetIndent("", " ") if err = enc.Encode(findings); err != nil { return 0, err } } else { for _, f := range findings { fmt.Fprintln(w, f) } printSummary(w, findings, opt, s) } if hasError(findings) { return 1, nil } return 0, nil } func printSummary(w io.Writer, findings []Finding, opt options, s *snapshot) { var errors, warnings int for _, f := range findings { if f.severity == Error { errors++ } else { warnings++ } } if len(findings) > 0 { fmt.Fprintln(w) } fmt.Fprintf(w, "checksilent: %d error(s), %d warning(s)\n", errors, warnings) if warnings > 0 { fmt.Fprintln(w, "Warnings do not affect the exit code.") } if opt.UIDir == "" { fmt.Fprintf(w, "The %s check was skipped: pass -ui-dir /src to run it.\n", checkMenuName) } // Said out loud so nobody reads a clean run as "the boundary holds // everywhere it was declared". core/ is a separate module and has no // directory here, so this repository's copy of the check cannot cover it. if scanned, absent := ScannedContractRoots(s); len(absent) > 0 { fmt.Fprintf(w, "The %s check covered %s; %s does not exist here and was not scanned.\n", checkImportBoundary, strings.Join(scanned, ", "), strings.Join(absent, ", ")) } // Same reason: a tree with no alias into core's contract packages gives // this check nothing to look at, and its silence must not be read as a // pass. That is now the interesting case rather than the expected one - // the shims exist, so a count of zero means they stopped being aliases, // or stopped being here. if n := ScannedShimAliases(s); n == 0 { fmt.Fprintf(w, "The %s check found no type alias into core's contract packages and guarded nothing.\n", checkShimAlias) } else { fmt.Fprintf(w, "The %s check covered %d type alias(es) into core's contract packages.\n", checkShimAlias, n) } }