tools: support a -fix option in v check-md file.v to make usage easier on Windows. Add an usage hint on formatting errors.

This commit is contained in:
Delyan Angelov 2024-08-13 18:05:08 +03:00
parent f52a62b2c6
commit e862aa004d
No known key found for this signature in database
GPG Key ID: 66886C0F12D595ED
2 changed files with 69 additions and 58 deletions

View File

@ -146,7 +146,7 @@ Ensure that all .md files in the project are formatted properly,
and that the V code block examples in them can be compiled/formatted too. and that the V code block examples in them can be compiled/formatted too.
Note: if that command finds formatting errors, they can be fixed with: Note: if that command finds formatting errors, they can be fixed with:
`VAUTOFIX=1 ./v check-md -hide-warnings file.md` `VAUTOFIX=1 ./v check-md file.md` or with `v check-md -fix file.md`.
## `v test-self` ## `v test-self`

View File

@ -21,23 +21,25 @@ const show_progress = os.getenv('GITHUB_JOB') == '' && '-silent' !in os.args
const non_option_args = cmdline.only_non_options(os.args[2..]) const non_option_args = cmdline.only_non_options(os.args[2..])
const is_verbose = os.getenv('VERBOSE') != '' const is_verbose = os.getenv('VERBOSE') != ''
const vcheckfolder = os.join_path(os.vtmp_dir(), 'vcheck_${os.getuid()}') const vcheckfolder = os.join_path(os.vtmp_dir(), 'vcheck_${os.getuid()}')
const should_autofix = os.getenv('VAUTOFIX') != '' const should_autofix = os.getenv('VAUTOFIX') != '' || '-fix' in os.args
const vexe = @VEXE const vexe = @VEXE
struct CheckResult { struct CheckResult {
pub mut: pub mut:
files int files int
warnings int
errors int
oks int oks int
warnings int
ferrors int
errors int
} }
fn (v1 CheckResult) + (v2 CheckResult) CheckResult { fn (v1 CheckResult) + (v2 CheckResult) CheckResult {
return CheckResult{ return CheckResult{
files: v1.files + v2.files files: v1.files + v2.files
warnings: v1.warnings + v2.warnings
errors: v1.errors + v2.errors
oks: v1.oks + v2.oks oks: v1.oks + v2.oks
warnings: v1.warnings + v2.warnings
ferrors: v1.ferrors + v2.ferrors
errors: v1.errors + v2.errors
} }
} }
@ -85,7 +87,12 @@ fn main() {
if res.errors == 0 && show_progress { if res.errors == 0 && show_progress {
clear_previous_line() clear_previous_line()
} }
println('Checked .md files: ${res.files} | OKs: ${res.oks} | Warnings: ${res.warnings} | Errors: ${res.errors}') println('Checked .md files: ${res.files} | OKs: ${res.oks} | Warnings: ${res.warnings} | Errors: ${res.errors} | Formatting errors: ${res.ferrors}')
if res.ferrors > 0 && !should_autofix {
println('Note: you can use `VAUTOFIX=1 v check-md file.md`, or `v check-md -fix file.md`,')
println(' to fix the V formatting errors in the markdown code blocks, when possible.')
println(' Run the command 2 times, to verify that all formatting errors were fixed.')
}
if res.errors > 0 { if res.errors > 0 {
exit(1) exit(1)
} }
@ -162,12 +169,17 @@ mut:
examples []VCodeExample examples []VCodeExample
current VCodeExample current VCodeExample
state MDFileParserState = .markdown state MDFileParserState = .markdown
//
oks int
warnings int
errors int // compilation errors + formatting errors
ferrors int // purely formatting errors
} }
fn (mut f MDFile) progress(message string) { fn (mut f MDFile) progress(message string) {
if show_progress { if show_progress {
clear_previous_line() clear_previous_line()
println('File: ${f.path:-30s}, Lines: ${f.lines.len:5}, ${message}') println('File: ${f.path}, ${message}')
} }
} }
@ -177,47 +189,44 @@ struct CheckResultContext {
line string line string
} }
fn (mut res CheckResult) wcheck(actual int, limit int, ctx CheckResultContext, msg_template string) { fn (mut f MDFile) wcheck(actual int, limit int, ctx CheckResultContext, msg_template string) {
if actual > limit { if actual > limit {
wprintln(wline(ctx.path, ctx.line_number, ctx.line.len, msg_template.replace('@', wprintln(wline(ctx.path, ctx.line_number, ctx.line.len, msg_template.replace('@',
limit.str()))) limit.str())))
wprintln(ctx.line) wprintln(ctx.line)
wprintln(ftext('-'.repeat(limit) + '^', term.gray)) wprintln(ftext('-'.repeat(limit) + '^', term.gray))
res.warnings++ f.warnings++
} }
} }
fn (mut res CheckResult) echeck(actual int, limit int, ctx CheckResultContext, msg_template string) { fn (mut f MDFile) echeck(actual int, limit int, ctx CheckResultContext, msg_template string) {
if actual > limit { if actual > limit {
eprintln(eline(ctx.path, ctx.line_number, ctx.line.len, msg_template.replace('@', eprintln(eline(ctx.path, ctx.line_number, ctx.line.len, msg_template.replace('@',
limit.str()))) limit.str())))
eprintln(ctx.line) eprintln(ctx.line)
eprintln(ftext('-'.repeat(limit) + '^', term.gray)) eprintln(ftext('-'.repeat(limit) + '^', term.gray))
res.errors++ f.errors++
} }
} }
fn (mut f MDFile) check() CheckResult { fn (mut f MDFile) check() CheckResult {
mut res := CheckResult{
files: 1
}
mut anchor_data := AnchorData{} mut anchor_data := AnchorData{}
for j, line in f.lines { for j, line in f.lines {
// f.progress('line: $j') // f.progress('line: $j')
if !f.skip_line_length_check { if !f.skip_line_length_check {
ctx := CheckResultContext{f.path, j, line} ctx := CheckResultContext{f.path, j, line}
if f.state == .vexample { if f.state == .vexample {
res.wcheck(line.len, too_long_line_length_example, ctx, 'example lines must be less than @ characters') f.wcheck(line.len, too_long_line_length_example, ctx, 'example lines must be less than @ characters')
} else if f.state == .codeblock { } else if f.state == .codeblock {
res.wcheck(line.len, too_long_line_length_codeblock, ctx, 'code lines must be less than @ characters') f.wcheck(line.len, too_long_line_length_codeblock, ctx, 'code lines must be less than @ characters')
} else if line.starts_with('|') { } else if line.starts_with('|') {
res.wcheck(line.len, too_long_line_length_table, ctx, 'table lines must be less than @ characters') f.wcheck(line.len, too_long_line_length_table, ctx, 'table lines must be less than @ characters')
} else if line.contains('http') { } else if line.contains('http') {
// vfmt off // vfmt off
res.wcheck(line.all_after('https').len, too_long_line_length_link, ctx, 'link lines must be less than @ characters') f.wcheck(line.all_after('https').len, too_long_line_length_link, ctx, 'link lines must be less than @ characters')
// vfmt on // vfmt on
} else { } else {
res.echeck(line.len, too_long_line_length_other, ctx, 'must be less than @ characters') f.echeck(line.len, too_long_line_length_other, ctx, 'must be less than @ characters')
} }
} }
if f.state == .markdown { if f.state == .markdown {
@ -227,9 +236,15 @@ fn (mut f MDFile) check() CheckResult {
f.parse_line(j, line) f.parse_line(j, line)
} }
anchor_data.check_link_target_match(f.path, mut res) f.check_link_target_match(anchor_data)
res += f.check_examples() f.check_examples()
return res return CheckResult{
files: 1
oks: f.oks
warnings: f.warnings
errors: f.errors
ferrors: f.ferrors
}
} }
fn (mut f MDFile) parse_line(lnumber int, line string) { fn (mut f MDFile) parse_line(lnumber int, line string) {
@ -337,7 +352,7 @@ fn (mut ad AnchorData) add_link_targets(line_number int, line string) {
} }
} }
fn (mut ad AnchorData) check_link_target_match(fpath string, mut res CheckResult) { fn (mut f MDFile) check_link_target_match(ad AnchorData) {
mut checked_headlines := []string{} mut checked_headlines := []string{}
mut found_error_warning := false mut found_error_warning := false
for link, linkdata in ad.links { for link, linkdata in ad.links {
@ -345,16 +360,16 @@ fn (mut ad AnchorData) check_link_target_match(fpath string, mut res CheckResult
checked_headlines << link checked_headlines << link
if ad.anchors[link].len > 1 { if ad.anchors[link].len > 1 {
found_error_warning = true found_error_warning = true
res.errors++ f.errors++
for anchordata in ad.anchors[link] { for anchordata in ad.anchors[link] {
eprintln(eline(fpath, anchordata.line, 0, 'multiple link targets of existing link (#${link})')) eprintln(eline(f.path, anchordata.line, 0, 'multiple link targets of existing link (#${link})'))
} }
} }
} else { } else {
found_error_warning = true found_error_warning = true
res.errors++ f.errors++
for brokenlink in linkdata { for brokenlink in linkdata {
eprintln(eline(fpath, brokenlink.line, 0, 'no link target found for existing link [${brokenlink.label}](#${link})')) eprintln(eline(f.path, brokenlink.line, 0, 'no link target found for existing link [${brokenlink.label}](#${link})'))
} }
} }
} }
@ -370,9 +385,9 @@ fn (mut ad AnchorData) check_link_target_match(fpath string, mut res CheckResult
anchor.line anchor.line
} }
} }
wprintln(wline(fpath, line, 0, 'multiple link target for non existing link (#${link})')) wprintln(wline(f.path, line, 0, 'multiple link target for non existing link (#${link})'))
found_error_warning = true found_error_warning = true
res.warnings++ f.warnings++
} }
} }
} }
@ -436,10 +451,8 @@ fn get_fmt_exit_code(vfile string, vexe string) int {
return silent_cmdexecute('${os.quoted_path(vexe)} fmt -verify ${os.quoted_path(vfile)}') return silent_cmdexecute('${os.quoted_path(vexe)} fmt -verify ${os.quoted_path(vfile)}')
} }
fn (mut f MDFile) check_examples() CheckResult { fn (mut f MDFile) check_examples() {
mut errors := 0 recheck_all_examples: for eidx, e in f.examples {
mut oks := 0
recheck_all_examples: for e in f.examples {
if e.command == 'ignore' { if e.command == 'ignore' {
continue continue
} }
@ -458,8 +471,10 @@ fn (mut f MDFile) check_examples() CheckResult {
mut acommands := e.command.split(' ') mut acommands := e.command.split(' ')
nofmt := 'nofmt' in acommands nofmt := 'nofmt' in acommands
for command in acommands { for command in acommands {
f.progress('example from ${e.sline} to ${e.eline}, command: ${command}') f.progress('OK: ${f.oks:3}, W: ${f.warnings:2}, E: ${f.errors:2}, F: ${f.ferrors:2}, example ${
eidx + 1}/${f.examples.len}, from line ${e.sline} to line ${e.eline}, lines: ${f.lines.len:5}, command: ${command}')
fmt_res := if nofmt { 0 } else { get_fmt_exit_code(vfile, vexe) } fmt_res := if nofmt { 0 } else { get_fmt_exit_code(vfile, vexe) }
f.ferrors += fmt_res
match command { match command {
'compile' { 'compile' {
res := cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -o ${os.quoted_path(efile)} ${os.quoted_path(vfile)}') res := cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -o ${os.quoted_path(efile)} ${os.quoted_path(vfile)}')
@ -474,10 +489,10 @@ fn (mut f MDFile) check_examples() CheckResult {
} }
eprintln(vcontent) eprintln(vcontent)
should_cleanup_vfile = false should_cleanup_vfile = false
errors++ f.errors++
continue continue
} }
oks++ f.oks++
} }
'cgen' { 'cgen' {
res := cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -o ${os.quoted_path(cfile)} ${os.quoted_path(vfile)}') res := cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -o ${os.quoted_path(cfile)} ${os.quoted_path(vfile)}')
@ -492,10 +507,10 @@ fn (mut f MDFile) check_examples() CheckResult {
} }
eprintln(vcontent) eprintln(vcontent)
should_cleanup_vfile = false should_cleanup_vfile = false
errors++ f.errors++
continue continue
} }
oks++ f.oks++
} }
'globals' { 'globals' {
res := cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -enable-globals -o ${os.quoted_path(cfile)} ${os.quoted_path(vfile)}') res := cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -enable-globals -o ${os.quoted_path(cfile)} ${os.quoted_path(vfile)}')
@ -510,10 +525,10 @@ fn (mut f MDFile) check_examples() CheckResult {
} }
eprintln(vcontent) eprintln(vcontent)
should_cleanup_vfile = false should_cleanup_vfile = false
errors++ f.errors++
continue continue
} }
oks++ f.oks++
} }
'live' { 'live' {
res := cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -live -o ${os.quoted_path(cfile)} ${os.quoted_path(vfile)}') res := cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -live -o ${os.quoted_path(cfile)} ${os.quoted_path(vfile)}')
@ -528,10 +543,10 @@ fn (mut f MDFile) check_examples() CheckResult {
} }
eprintln(vcontent) eprintln(vcontent)
should_cleanup_vfile = false should_cleanup_vfile = false
errors++ f.errors++
continue continue
} }
oks++ f.oks++
} }
'shared' { 'shared' {
res := cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -shared -o ${os.quoted_path(cfile)} ${os.quoted_path(vfile)}') res := cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -shared -o ${os.quoted_path(cfile)} ${os.quoted_path(vfile)}')
@ -546,10 +561,10 @@ fn (mut f MDFile) check_examples() CheckResult {
} }
eprintln(vcontent) eprintln(vcontent)
should_cleanup_vfile = false should_cleanup_vfile = false
errors++ f.errors++
continue continue
} }
oks++ f.oks++
} }
'failcompile' { 'failcompile' {
res := silent_cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -o ${os.quoted_path(cfile)} ${os.quoted_path(vfile)}') res := silent_cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -o ${os.quoted_path(cfile)} ${os.quoted_path(vfile)}')
@ -564,10 +579,10 @@ fn (mut f MDFile) check_examples() CheckResult {
} }
eprintln(vcontent) eprintln(vcontent)
should_cleanup_vfile = false should_cleanup_vfile = false
errors++ f.errors++
continue continue
} }
oks++ f.oks++
} }
'oksyntax' { 'oksyntax' {
res := cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -check-syntax ${os.quoted_path(vfile)}') res := cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -check-syntax ${os.quoted_path(vfile)}')
@ -582,10 +597,10 @@ fn (mut f MDFile) check_examples() CheckResult {
} }
eprintln(vcontent) eprintln(vcontent)
should_cleanup_vfile = false should_cleanup_vfile = false
errors++ f.errors++
continue continue
} }
oks++ f.oks++
} }
'okfmt' { 'okfmt' {
if fmt_res != 0 { if fmt_res != 0 {
@ -596,10 +611,10 @@ fn (mut f MDFile) check_examples() CheckResult {
} }
eprintln(vcontent) eprintln(vcontent)
should_cleanup_vfile = false should_cleanup_vfile = false
errors++ f.errors++
continue continue
} }
oks++ f.oks++
} }
'badsyntax' { 'badsyntax' {
res := silent_cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -check-syntax ${os.quoted_path(vfile)}') res := silent_cmdexecute('${os.quoted_path(vexe)} -w -Wfatal-errors -check-syntax ${os.quoted_path(vfile)}')
@ -607,10 +622,10 @@ fn (mut f MDFile) check_examples() CheckResult {
eprintln(eline(f.path, e.sline, 0, '`badsyntax` example can be parsed fine')) eprintln(eline(f.path, e.sline, 0, '`badsyntax` example can be parsed fine'))
eprintln(vcontent) eprintln(vcontent)
should_cleanup_vfile = false should_cleanup_vfile = false
errors++ f.errors++
continue continue
} }
oks++ f.oks++
} }
'nofmt' {} 'nofmt' {}
// mark the example as playable inside docs // mark the example as playable inside docs
@ -622,7 +637,7 @@ fn (mut f MDFile) check_examples() CheckResult {
else { else {
eprintln(eline(f.path, e.sline, 0, 'unrecognized command: "${command}", use one of: wip/ignore/compile/failcompile/okfmt/nofmt/oksyntax/badsyntax/cgen/globals/live/shared')) eprintln(eline(f.path, e.sline, 0, 'unrecognized command: "${command}", use one of: wip/ignore/compile/failcompile/okfmt/nofmt/oksyntax/badsyntax/cgen/globals/live/shared'))
should_cleanup_vfile = false should_cleanup_vfile = false
errors++ f.errors++
} }
} }
} }
@ -632,10 +647,6 @@ fn (mut f MDFile) check_examples() CheckResult {
os.rm(vfile) or { panic(err) } os.rm(vfile) or { panic(err) }
} }
} }
return CheckResult{
errors: errors
oks: oks
}
} }
fn verbose_println(message string) { fn verbose_println(message string) {