Skip to content

Commit 4b19e9e

Browse files
authored
tools: support .vcheckignore pattern files for v check-md . (#26623)
1 parent e823ac2 commit 4b19e9e

3 files changed

Lines changed: 545 additions & 4 deletions

File tree

cmd/tools/vcheck-md.v

Lines changed: 174 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,25 @@ pub mut:
3535
errors int
3636
}
3737

38+
struct VCheckIgnoreRule {
39+
base_dir string
40+
pattern string
41+
}
42+
43+
struct VCheckIgnoreContext {
44+
repo_root string
45+
}
46+
47+
struct VCheckIgnoreMatch {
48+
ignore_file string
49+
pattern string
50+
}
51+
52+
struct MDPathScanResult {
53+
files []string
54+
skipped int
55+
}
56+
3857
fn (v1 CheckResult) + (v2 CheckResult) CheckResult {
3958
return CheckResult{
4059
files: v1.files + v2.files
@@ -67,10 +86,13 @@ fn main() {
6786
os.rmdir_all(vcheckfolder) or {}
6887
}
6988
mut all_mdfiles := []MDFile{}
89+
mut skipped_mdfiles := 0
7090
for i := 0; i < files_paths.len; i++ {
7191
file_path := files_paths[i]
7292
if os.is_dir(file_path) {
73-
files_paths << md_file_paths(file_path)
93+
scan_result := md_file_paths(file_path)
94+
files_paths << scan_result.files
95+
skipped_mdfiles += scan_result.skipped
7496
continue
7597
}
7698
real_path := os.real_path(file_path)
@@ -85,7 +107,12 @@ fn main() {
85107
lines: lines
86108
}
87109
}
88-
println('> Found: ${all_mdfiles.len} .md files.')
110+
println('> Found: ${all_mdfiles.len} .md files. Skipped by .vcheckignore: ${skipped_mdfiles}.')
111+
if is_verbose {
112+
for idx, mdfile in all_mdfiles {
113+
println('> file ${idx + 1} is ${mdfile.path}')
114+
}
115+
}
89116
if show_progress {
90117
// this is intended to be replaced by the progress lines
91118
println('')
@@ -110,17 +137,160 @@ fn main() {
110137
}
111138
}
112139

113-
fn md_file_paths(dir string) []string {
140+
fn md_file_paths(dir string) MDPathScanResult {
114141
mut files_to_check := []string{}
142+
mut skipped := 0
143+
vcheckignore := collect_vcheckignore_context(dir)
115144
md_files := os.walk_ext(dir, '.md')
116145
for file in md_files {
117146
nfile := file.replace('\\', '/')
118147
if nfile.contains_any_substr(['/thirdparty/', 'CHANGELOG', '/testdata/']) {
119148
continue
120149
}
150+
if skip_match := vcheckignore.skip_match(file) {
151+
if is_verbose {
152+
println('SKIP: ${vcheckignore.repo_relative_path(file)} (from ${vcheckignore.repo_relative_path(skip_match.ignore_file)}: ${skip_match.pattern})')
153+
}
154+
skipped++
155+
continue
156+
}
121157
files_to_check << file
122158
}
123-
return files_to_check
159+
return MDPathScanResult{
160+
files: files_to_check
161+
skipped: skipped
162+
}
163+
}
164+
165+
fn collect_vcheckignore_context(cwd string) VCheckIgnoreContext {
166+
repo_root := find_repo_root(cwd)
167+
return VCheckIgnoreContext{
168+
repo_root: repo_root
169+
}
170+
}
171+
172+
fn find_repo_root(cwd string) string {
173+
mut dir := os.real_path(cwd)
174+
for {
175+
if os.exists(os.join_path(dir, '.git')) {
176+
return dir
177+
}
178+
parent := os.dir(dir)
179+
if parent == dir || parent == '' {
180+
return dir
181+
}
182+
dir = parent
183+
}
184+
return dir
185+
}
186+
187+
fn (ctx VCheckIgnoreContext) skip_match(file_path string) ?VCheckIgnoreMatch {
188+
file := os.real_path(file_path).replace('\\', '/')
189+
mut dir := os.dir(file)
190+
repo_root := ctx.repo_root.replace('\\', '/')
191+
for {
192+
ignore_path := os.join_path(dir, '.vcheckignore')
193+
if os.is_file(ignore_path) {
194+
lines := os.read_lines(ignore_path) or { []string{} }
195+
for line in lines {
196+
pattern := normalize_vcheckignore_line(line)
197+
if pattern == '' || pattern.starts_with('#') {
198+
continue
199+
}
200+
if matches_vcheckignore_rule(file, VCheckIgnoreRule{
201+
base_dir: dir
202+
pattern: pattern
203+
})
204+
{
205+
return VCheckIgnoreMatch{
206+
ignore_file: ignore_path
207+
pattern: pattern
208+
}
209+
}
210+
}
211+
}
212+
if dir.replace('\\', '/') == repo_root {
213+
break
214+
}
215+
parent := os.dir(dir)
216+
if parent == dir || parent == '' {
217+
break
218+
}
219+
dir = parent
220+
}
221+
return none
222+
}
223+
224+
fn normalize_vcheckignore_line(line string) string {
225+
trimmed := line.trim_space()
226+
if trimmed == '' {
227+
return ''
228+
}
229+
if comment_idx := trimmed.index('#') {
230+
return trimmed[..comment_idx].trim_space()
231+
}
232+
return trimmed
233+
}
234+
235+
fn (ctx VCheckIgnoreContext) repo_relative_path(file_path string) string {
236+
file := os.real_path(file_path).replace('\\', '/')
237+
root := ctx.repo_root.replace('\\', '/')
238+
root_prefix := root + '/'
239+
if file.starts_with(root_prefix) {
240+
return file.all_after(root_prefix)
241+
}
242+
return file
243+
}
244+
245+
fn matches_vcheckignore_rule(file string, rule VCheckIgnoreRule) bool {
246+
base := rule.base_dir.replace('\\', '/')
247+
base_prefix := base + '/'
248+
if !file.starts_with(base_prefix) {
249+
return false
250+
}
251+
relative_file := file.all_after(base_prefix)
252+
mut pattern := rule.pattern.replace('\\', '/')
253+
if pattern.starts_with('!') {
254+
return false
255+
}
256+
mut anchored := false
257+
if pattern.starts_with('/') {
258+
anchored = true
259+
pattern = pattern.trim_left('/')
260+
}
261+
if pattern.ends_with('/') {
262+
pattern = pattern.trim_right('/')
263+
return matches_vcheckignore_directory_pattern(relative_file, pattern, anchored)
264+
}
265+
if anchored {
266+
return relative_file.match_glob(pattern)
267+
}
268+
if pattern.contains('/') {
269+
return relative_file.match_glob(pattern)
270+
}
271+
return os.file_name(relative_file).match_glob(pattern)
272+
}
273+
274+
fn matches_vcheckignore_directory_pattern(relative_file string, pattern string, anchored bool) bool {
275+
mut relative_dir := os.dir(relative_file).replace('\\', '/')
276+
if relative_dir == '.' || relative_dir == '' {
277+
return false
278+
}
279+
if anchored {
280+
return relative_dir.match_glob(pattern) || relative_dir.match_glob(pattern + '/*')
281+
}
282+
mut candidate := relative_dir
283+
for {
284+
if candidate.match_glob(pattern) || candidate.match_glob(pattern + '/*') {
285+
return true
286+
}
287+
if slash_idx := candidate.index('/') {
288+
candidate = candidate[slash_idx + 1..]
289+
continue
290+
}
291+
break
292+
}
293+
return false
124294
}
125295

126296
fn wprintln(s string) {

0 commit comments

Comments
 (0)