clone

an offline map explorer where each world, from a game map to a city's open data, ships as one .atlas file

main
atlas / tools / depcheck / main.go
main.go 3.7K
1// Command depcheck enforces the architecture of issue #5 as static analysis.2//3// The pseudo-contracts in the issue are encoded as analyzers on the4// golang.org/x/tools/go/analysis framework — the machinery go vet is built on5// — so a crossed boundary is an immediate mechanical "we don't do that here"6// rather than a convention someone has to remember (issue #5 §9).7//8// Usage:9//10// go run ./tools/depcheck # every clean-room package that exists11// go run ./tools/depcheck ./format/... # an explicit pattern12// go run ./tools/depcheck help laneimports13//14// With no package pattern, depcheck scopes itself to the clean-room roots of15// §3.1 that exist on disk. That is deliberate: the pre-rewrite tree (archived16// on the golden-reference tag) is not the subject, and a rule about a lane17// nobody has written yet passes by having nothing to say.18package main1920import (21 "fmt"22 "os"23 "path/filepath"24 "strings"2526 "golang.org/x/tools/go/analysis"27 "golang.org/x/tools/go/analysis/multichecker"28)2930// analyzers is the guardrail set. Milestones extend it: rules land with the31// contracts they enforce (issue #5 §9).32func analyzers() []*analysis.Analyzer {33 return []*analysis.Analyzer{34 laneImports,35 hostenvPurity,36 netConfine,37 semconvLiterals,38 }39}4041func main() {42 if hasPatterns(os.Args[1:]) {43 multichecker.Main(analyzers()...)44 return45 }4647 root, err := moduleRoot()48 if err != nil {49 fmt.Fprintln(os.Stderr, "depcheck:", err)50 os.Exit(1)51 }52 patterns := defaultPatterns(root)53 if len(patterns) == 0 {54 fmt.Fprintf(os.Stderr, "depcheck: %d rules armed; no clean-room package exists yet (roots: %s)\n",55 len(analyzers()), strings.Join(cleanRoomRoots, " "))56 return57 }58 fmt.Fprintf(os.Stderr, "depcheck: %d rules over %s\n", len(analyzers()), strings.Join(patterns, " "))5960 os.Args = append(os.Args[:1:1], append(flagsOf(os.Args[1:]), patterns...)...)61 multichecker.Main(analyzers()...)62}6364// hasPatterns reports whether the caller named packages itself. Analysis flags65// are all of the -flag=value form, so a bare argument is a pattern (or the66// "help" subcommand, which multichecker handles).67func hasPatterns(args []string) bool {68 for _, a := range args {69 if !strings.HasPrefix(a, "-") {70 return true71 }72 }73 return false74}7576func flagsOf(args []string) []string {77 flags := make([]string, 0, len(args))78 for _, a := range args {79 if strings.HasPrefix(a, "-") {80 flags = append(flags, a)81 }82 }83 return flags84}8586// defaultPatterns turns the clean-room roots into package patterns, skipping87// roots that do not exist yet. Scoping this way — rather than running ./... —88// is what keeps the old tree out of the analysis entirely: it is neither89// loaded nor judged.90func defaultPatterns(root string) []string {91 var patterns []string92 for _, dir := range cleanRoomRoots {93 // The module root is the desktop shell's one package, not the tree94 // beneath it: every other root is named on its own line below.95 if dir == shellRoot {96 patterns = append(patterns, modulePath)97 continue98 }99 if info, err := os.Stat(filepath.Join(root, filepath.FromSlash(dir))); err == nil && info.IsDir() {100 patterns = append(patterns, modulePath+"/"+dir+"/...")101 }102 }103 return patterns104}105106// moduleRoot walks up from the working directory to the go.mod, so depcheck107// behaves the same from the repository root and from a lane directory.108func moduleRoot() (string, error) {109 dir, err := os.Getwd()110 if err != nil {111 return "", fmt.Errorf("locate module root: %w", err)112 }113 for {114 if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {115 return dir, nil116 }117 parent := filepath.Dir(dir)118 if parent == dir {119 return "", fmt.Errorf("no go.mod above %s", dir)120 }121 dir = parent122 }123}