返回 DeepSeek-Reasonix
source.go
根目录 / tools / repolint / source.go
1 package main
2
3 import (
4 "go/ast"
5 "go/parser"
6 "go/token"
7 "io/fs"
8 "os"
9 "path/filepath"
10 "regexp"
11 "sort"
12 "strconv"
13 "strings"
14 )
15
16 // wailsjs holds Wails-generated Go bindings and is gitignored: measuring it
17 // reports debt against a build product nobody can edit, and its presence
18 // depends on whether a desktop build has run locally.
19 var skipDirs = map[string]bool{
20 "node_modules": true,
21 "third_party": true,
22 "vendor": true,
23 "testdata": true,
24 "dist": true,
25 "bin": true,
26 "wailsjs": true,
27 }
28
29 var generatedRe = regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`)
30
31 type sourceFile struct {
32 rel string
33 fset *token.FileSet
34 file *ast.File
35 src []string
36 lines int
37 }
38
39 // A comment sharing its line with code annotates that code; only a comment
40 // that owns its line can be commented-out code.
41 func (s *sourceFile) trailing(pos token.Pos) bool {
42 p := s.fset.Position(pos)
43 if p.Line < 1 || p.Line > len(s.src) || p.Column < 2 {
44 return false
45 }
46 line := s.src[p.Line-1]
47 if p.Column-1 > len(line) {
48 return false
49 }
50 return strings.TrimSpace(line[:p.Column-1]) != ""
51 }
52
53 func collect(root string) ([]string, error) {
54 var out []string
55 err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
56 if err != nil {
57 return err
58 }
59 name := d.Name()
60 if d.IsDir() {
61 if path == root {
62 return nil
63 }
64 if skipDirs[name] || strings.HasPrefix(name, ".") || strings.HasSuffix(name, ".root_bak") || strings.HasSuffix(name, ".bak") {
65 return filepath.SkipDir
66 }
67 return nil
68 }
69 if !lintable(name) || strings.Contains(name, "_generated.") {
70 return nil
71 }
72 rel, err := filepath.Rel(root, path)
73 if err != nil {
74 return err
75 }
76 out = append(out, filepath.ToSlash(rel))
77 return nil
78 })
79 sort.Strings(out)
80 return out, err
81 }
82
83 func parseSource(root, rel string) (*sourceFile, error) {
84 data, err := os.ReadFile(filepath.Join(root, filepath.FromSlash(rel)))
85 if err != nil {
86 return nil, err
87 }
88 return parseBytes(rel, data), nil
89 }
90
91 // lintable reports whether repolint reads this file at all. Go files take every
92 // rule; TypeScript files take only the ones expressed on raw lines, since the
93 // rest are written against the Go AST.
94 func lintable(name string) bool {
95 return strings.HasSuffix(name, ".go") ||
96 strings.HasSuffix(name, ".ts") ||
97 strings.HasSuffix(name, ".tsx")
98 }
99
100 func parseBytes(rel string, data []byte) *sourceFile {
101 if isGenerated(data) {
102 return nil
103 }
104 lines := splitLines(data)
105 if !strings.HasSuffix(rel, ".go") {
106 // No fset/file: run() gives these the line-based rules only.
107 return &sourceFile{rel: rel, src: lines, lines: len(lines)}
108 }
109 fset := token.NewFileSet()
110 file, err := parser.ParseFile(fset, rel, data, parser.ParseComments|parser.SkipObjectResolution)
111 if err != nil {
112 return nil
113 }
114 return &sourceFile{rel: rel, fset: fset, file: file, src: lines, lines: len(lines)}
115 }
116
117 func splitLines(data []byte) []string {
118 text := strings.ReplaceAll(string(data), "\r\n", "\n")
119 lines := strings.Split(text, "\n")
120 if n := len(lines); n > 0 && lines[n-1] == "" {
121 lines = lines[:n-1]
122 }
123 return lines
124 }
125
126 func isGenerated(data []byte) bool {
127 for i, line := range strings.SplitN(string(data), "\n", 12) {
128 if i >= 11 {
129 break
130 }
131 if generatedRe.MatchString(strings.TrimRight(line, "\r")) {
132 return true
133 }
134 }
135 return false
136 }
137
138 func (s *sourceFile) isTest() bool {
139 return strings.HasSuffix(s.rel, "_test.go") ||
140 strings.Contains(s.rel, "/__tests__/") ||
141 strings.Contains(s.rel, ".test.") ||
142 strings.Contains(s.rel, ".spec.")
143 }
144
145 func (s *sourceFile) line(p token.Pos) int { return s.fset.Position(p).Line }
146
147 type importRef struct {
148 path string
149 line int
150 }
151
152 func (s *sourceFile) importRefs() []importRef {
153 if s.isTest() || s.file == nil {
154 return nil
155 }
156 out := make([]importRef, 0, len(s.file.Imports))
157 for _, spec := range s.file.Imports {
158 path, err := strconv.Unquote(spec.Path.Value)
159 if err != nil {
160 continue
161 }
162 out = append(out, importRef{path: path, line: s.line(spec.Pos())})
163 }
164 return out
165 }
166
166 lines GO