返回 DeepSeek-Reasonix
main.go
根目录 / tools / repolint / main.go
1 // repolint enforces the repo standards that gofmt/vet/golangci cannot express.
2 package main
3
4 import (
5 "flag"
6 "fmt"
7 "maps"
8 "os"
9 "path/filepath"
10 "slices"
11 "sort"
12 )
13
14 type Finding struct {
15 File string
16 Line int
17 Rule string
18 Msg string
19 // Excess over the rule's limit, so trading a short violation for a long
20 // one still trips the ratchet. One for rules that are pass/fail.
21 Weight int
22 }
23
24 const (
25 ruleEssay = "essay"
26 ruleBanner = "banner"
27 ruleMarker = "marker"
28 ruleDeadCode = "commented-code"
29 ruleNarrative = "narrative"
30 ruleFileSize = "file-size"
31 ruleTestSize = "test-file-size"
32 ruleLayering = "layering"
33 ruleFuncSize = "function-size"
34 ruleComplexity = "complexity"
35 ruleStructState = "struct-state"
36 )
37
38 var allRules = []string{
39 ruleEssay, ruleBanner, ruleMarker, ruleDeadCode,
40 ruleNarrative, ruleFileSize, ruleTestSize, ruleLayering,
41 ruleFuncSize, ruleComplexity, ruleStructState,
42 }
43
44 func main() {
45 root := flag.String("root", ".", "repository root to scan")
46 baselinePath := flag.String("baseline", "", "baseline file (default <root>/tools/repolint/baseline.json)")
47 update := flag.Bool("update", false, "rewrite the baseline from the current tree")
48 strict := flag.Bool("strict", false, "report every finding, ignoring the baseline")
49 flag.Parse()
50
51 if *baselinePath == "" {
52 *baselinePath = filepath.Join(*root, "tools", "repolint", "baseline.json")
53 }
54
55 findings, err := run(*root)
56 if err != nil {
57 fmt.Fprintln(os.Stderr, "repolint:", err)
58 os.Exit(2)
59 }
60 sort.Slice(findings, func(i, j int) bool {
61 if findings[i].File != findings[j].File {
62 return findings[i].File < findings[j].File
63 }
64 return findings[i].Line < findings[j].Line
65 })
66
67 if *update {
68 if err := baselineFrom(findings).write(*baselinePath); err != nil {
69 fmt.Fprintln(os.Stderr, "repolint:", err)
70 os.Exit(2)
71 }
72 fmt.Printf("wrote %s (%d findings across %d files)\n", *baselinePath, len(findings), countFiles(findings))
73 return
74 }
75
76 if *strict {
77 report(findings)
78 fmt.Printf("\n%d findings across %d files\n", len(findings), countFiles(findings))
79 if len(findings) > 0 {
80 os.Exit(1)
81 }
82 return
83 }
84
85 baseline, err := loadBaseline(*baselinePath)
86 if err != nil {
87 fmt.Fprintln(os.Stderr, "repolint:", err)
88 os.Exit(2)
89 }
90 over, msgs := baseline.exceeded(findings)
91 if len(msgs) == 0 {
92 fmt.Printf("repolint: clean (%d baselined findings)\n", len(findings))
93 return
94 }
95 report(over)
96 fmt.Fprintln(os.Stderr)
97 for _, m := range msgs {
98 fmt.Fprintln(os.Stderr, "repolint:", m)
99 }
100 fmt.Fprintf(os.Stderr, "\nNew standards violations. Fix them, or if this is a deliberate\n"+
101 "carry-forward (file rename, extraction), run:\n\n go run ./tools/repolint -update\n\n"+
102 "and justify the baseline diff in the pull request.\n")
103 os.Exit(1)
104 }
105
106 func run(root string) ([]Finding, error) {
107 paths, err := collect(root)
108 if err != nil {
109 return nil, err
110 }
111 var findings []Finding
112 imports := map[string][]importRef{}
113 for _, rel := range paths {
114 src, err := parseSource(root, rel)
115 if err != nil {
116 return nil, err
117 }
118 if src == nil {
119 continue
120 }
121 findings = append(findings, checkSize(src)...)
122 if src.file == nil {
123 continue
124 }
125 findings = append(findings, checkComments(src)...)
126 findings = append(findings, checkComplexity(src)...)
127 findings = append(findings, checkStructState(src)...)
128 imports[rel] = src.importRefs()
129 }
130 return append(findings, checkLayering(imports)...), nil
131 }
132
133 func report(findings []Finding) {
134 for _, f := range findings {
135 fmt.Fprintf(os.Stderr, "%s:%d: [%s] %s\n", f.File, f.Line, f.Rule, f.Msg)
136 }
137 }
138
139 func countFiles(findings []Finding) int {
140 seen := map[string]bool{}
141 for _, f := range findings {
142 seen[f.File] = true
143 }
144 return len(seen)
145 }
146
147 func sortedKeys[V any](m map[string]V) []string {
148 return slices.Sorted(maps.Keys(m))
149 }
150
150 lines GO