diff --git a/cmd/tools/vcomplete.v b/cmd/tools/vcomplete.v index 555838250d..b3c0c7870e 100644 --- a/cmd/tools/vcomplete.v +++ b/cmd/tools/vcomplete.v @@ -94,6 +94,7 @@ const ( '-apk', '-show-timings', '-check-syntax', + '-check', '-v', '-progress', '-silent', diff --git a/cmd/v/help/build.txt b/cmd/v/help/build.txt index 9291376c5c..f43605989c 100644 --- a/cmd/v/help/build.txt +++ b/cmd/v/help/build.txt @@ -125,6 +125,9 @@ NB: the build flags are shared with the run command too: that it uses to detect whether or not to use ANSI colors may not work in all cases. These options allow you to override the default detection. + -check + Scans, parses, and checks the files without compiling the program. + -check-syntax Only scan and parse the files, but then stop. Useful for very quick syntax checks. diff --git a/vlib/v/builder/builder.v b/vlib/v/builder/builder.v index 12998b24c1..bc710646af 100644 --- a/vlib/v/builder/builder.v +++ b/vlib/v/builder/builder.v @@ -3,6 +3,7 @@ module builder import os import v.token import v.pref +import v.errors import v.util import v.ast import v.vmod @@ -26,6 +27,9 @@ mut: out_name_js string stats_lines int // size of backend generated source code in lines stats_bytes int // size of backend generated source code in bytes + nr_errors int // accumulated error count of scanner, parser, checker, and builder + nr_warnings int // accumulated warning count of scanner, parser, checker, and builder + nr_notices int // accumulated notice count of scanner, parser, checker, and builder pub mut: module_search_paths []string parsed_files []&ast.File @@ -86,6 +90,9 @@ pub fn (mut b Builder) middle_stages() ? { b.checker.check_files(b.parsed_files) util.timing_measure('CHECK') b.print_warnings_and_errors() + if b.pref.check_only { + return error('stop_after_checker') + } util.timing_start('TRANSFORM') b.transformer.transform_files(b.parsed_files) util.timing_measure('TRANSFORM') @@ -133,7 +140,8 @@ pub fn (mut b Builder) parse_imports() { for imp in ast_file.imports { mod := imp.mod if mod == 'builtin' { - error_with_pos('cannot import module "builtin"', ast_file.path, imp.pos) + b.parsed_files[i].errors << b.error_with_pos('cannot import module "builtin"', + ast_file.path, imp.pos) break } if mod in done_imports { @@ -142,15 +150,16 @@ pub fn (mut b Builder) parse_imports() { import_path := b.find_module_path(mod, ast_file.path) or { // v.parsers[i].error_with_token_index('cannot import module "$mod" (not found)', v.parsers[i].import_ast.get_import_tok_idx(mod)) // break - error_with_pos('cannot import module "$mod" (not found)', ast_file.path, - imp.pos) + b.parsed_files[i].errors << b.error_with_pos('cannot import module "$mod" (not found)', + ast_file.path, imp.pos) break } v_files := b.v_files_from_dir(import_path) if v_files.len == 0 { // v.parsers[i].error_with_token_index('cannot import module "$mod" (no .v files in "$import_path")', v.parsers[i].import_ast.get_import_tok_idx(mod)) - error_with_pos('cannot import module "$mod" (no .v files in "$import_path")', + b.parsed_files[i].errors << b.error_with_pos('cannot import module "$mod" (no .v files in "$import_path")', ast_file.path, imp.pos) + continue } // Add all imports referenced by these libs parsed_files := parser.parse_files(v_files, b.table, b.pref) @@ -161,7 +170,7 @@ pub fn (mut b Builder) parse_imports() { } if name != mod { // v.parsers[pidx].error_with_token_index('bad module definition: ${v.parsers[pidx].file_path} imports module "$mod" but $file is defined as module `$p_mod`', 1 - error_with_pos('bad module definition: $ast_file.path imports module "$mod" but $file.path is defined as module `$name`', + b.parsed_files[i].errors << b.error_with_pos('bad module definition: $ast_file.path imports module "$mod" but $file.path is defined as module `$name`', ast_file.path, imp.pos) } } @@ -335,27 +344,104 @@ pub fn (b &Builder) find_module_path(mod string, fpath string) ?string { } fn (b &Builder) show_total_warns_and_errors_stats() { - if b.checker.nr_errors == 0 && b.checker.nr_warnings == 0 && b.checker.nr_notices == 0 { + if b.nr_errors == 0 && b.nr_warnings == 0 && b.nr_notices == 0 { return } if b.pref.is_stats { - estring := util.bold(b.checker.errors.len.str()) - wstring := util.bold(b.checker.warnings.len.str()) - nstring := util.bold(b.checker.nr_notices.str()) - println('checker summary: $estring V errors, $wstring V warnings, $nstring V notices') + mut nr_errors := b.checker.errors.len + mut nr_warnings := b.checker.warnings.len + mut nr_notices := b.checker.notices.len + + if b.pref.check_only { + nr_errors = b.nr_errors + nr_warnings = b.nr_warnings + nr_notices = b.nr_notices + } + + estring := util.bold(nr_errors.str()) + wstring := util.bold(nr_warnings.str()) + nstring := util.bold(nr_notices.str()) + + if b.pref.check_only { + println('summary: $estring V errors, $wstring V warnings, $nstring V notices') + } else { + println('checker summary: $estring V errors, $wstring V warnings, $nstring V notices') + } } } -fn (b &Builder) print_warnings_and_errors() { +fn (mut b Builder) print_warnings_and_errors() { defer { b.show_total_warns_and_errors_stats() } + + for file in b.parsed_files { + b.nr_errors += file.errors.len + b.nr_warnings += file.warnings.len + b.nr_notices += file.notices.len + } + if b.pref.output_mode == .silent { - if b.checker.nr_errors > 0 { + if b.nr_errors > 0 { exit(1) } return } + + if b.pref.check_only { + for file in b.parsed_files { + if !b.pref.skip_warnings { + for err in file.notices { + kind := if b.pref.is_verbose { + '$err.reporter notice #$b.nr_notices:' + } else { + 'notice:' + } + ferror := util.formatted_error(kind, err.message, err.file_path, err.pos) + eprintln(ferror) + if err.details.len > 0 { + eprintln('Details: $err.details') + } + } + } + } + + for file in b.parsed_files { + for err in file.errors { + kind := if b.pref.is_verbose { + '$err.reporter error #$b.nr_errors:' + } else { + 'error:' + } + ferror := util.formatted_error(kind, err.message, err.file_path, err.pos) + eprintln(ferror) + if err.details.len > 0 { + eprintln('Details: $err.details') + } + } + } + + for file in b.parsed_files { + if !b.pref.skip_warnings { + for err in file.warnings { + kind := if b.pref.is_verbose { + '$err.reporter warning #$b.nr_warnings:' + } else { + 'warning:' + } + ferror := util.formatted_error(kind, err.message, err.file_path, err.pos) + eprintln(ferror) + if err.details.len > 0 { + eprintln('Details: $err.details') + } + } + } + } + + b.show_total_warns_and_errors_stats() + exit(1) + } + if b.pref.is_verbose && b.checker.nr_warnings > 1 { println('$b.checker.nr_warnings warnings') } @@ -455,10 +541,19 @@ struct FunctionRedefinition { f ast.FnDecl } -fn error_with_pos(s string, fpath string, pos token.Position) { - ferror := util.formatted_error('builder error:', s, fpath, pos) - eprintln(ferror) - exit(1) +fn (b &Builder) error_with_pos(s string, fpath string, pos token.Position) errors.Error { + if !b.pref.check_only { + ferror := util.formatted_error('builder error:', s, fpath, pos) + eprintln(ferror) + exit(1) + } + + return errors.Error{ + file_path: fpath + pos: pos + reporter: .builder + message: s + } } [noreturn] diff --git a/vlib/v/builder/cc.v b/vlib/v/builder/cc.v index b7b0d1392b..d71b72a465 100644 --- a/vlib/v/builder/cc.v +++ b/vlib/v/builder/cc.v @@ -489,6 +489,12 @@ fn (mut v Builder) cc() { } return } + if v.pref.check_only { + if v.pref.is_verbose { + println('builder.cc returning early, since pref.check_only is true') + } + return + } if v.pref.should_output_to_stdout() { // output to stdout content := os.read_file(v.out_name_c) or { panic(err) } diff --git a/vlib/v/builder/compile.v b/vlib/v/builder/compile.v index ac59121a50..2ad79ad55d 100644 --- a/vlib/v/builder/compile.v +++ b/vlib/v/builder/compile.v @@ -106,7 +106,7 @@ fn (mut b Builder) run_compiled_executable_and_exit() { if b.pref.skip_running { return } - if b.pref.only_check_syntax { + if b.pref.only_check_syntax || b.pref.check_only { return } if b.pref.should_output_to_stdout() { diff --git a/vlib/v/errors/errors.v b/vlib/v/errors/errors.v index 16f31ed402..37a1fd24d3 100644 --- a/vlib/v/errors/errors.v +++ b/vlib/v/errors/errors.v @@ -6,6 +6,7 @@ pub enum Reporter { scanner parser checker + builder gen } diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 449ad07465..80fefcdde6 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -273,6 +273,17 @@ pub fn (mut p Parser) parse() &ast.File { } } p.scope.end_pos = p.tok.pos + + mut errors := p.errors + mut warnings := p.warnings + mut notices := p.notices + + if p.pref.check_only { + errors << p.scanner.errors + warnings << p.scanner.warnings + notices << p.scanner.notices + } + return &ast.File{ path: p.file_name path_base: p.file_base @@ -286,8 +297,9 @@ pub fn (mut p Parser) parse() &ast.File { stmts: stmts scope: p.scope global_scope: p.table.global_scope - errors: p.errors - warnings: p.warnings + errors: errors + warnings: warnings + notices: notices global_labels: p.global_labels } } @@ -1613,7 +1625,7 @@ pub fn (mut p Parser) error_with_pos(s string, pos token.Position) ast.NodeError exit(1) } mut kind := 'error:' - if p.pref.output_mode == .stdout { + if p.pref.output_mode == .stdout && !p.pref.check_only { if p.pref.is_verbose { print_backtrace() kind = 'parser error:' @@ -1628,6 +1640,12 @@ pub fn (mut p Parser) error_with_pos(s string, pos token.Position) ast.NodeError reporter: .parser message: s } + + // To avoid getting stuck after an error, the parser + // will proceed to the next token. + if p.pref.check_only { + p.next() + } } if p.pref.output_mode == .silent { // Normally, parser errors mean that the parser exits immediately, so there can be only 1 parser error. @@ -1647,7 +1665,7 @@ pub fn (mut p Parser) error_with_error(error errors.Error) { exit(1) } mut kind := 'error:' - if p.pref.output_mode == .stdout { + if p.pref.output_mode == .stdout && !p.pref.check_only { if p.pref.is_verbose { print_backtrace() kind = 'parser error:' @@ -1679,7 +1697,7 @@ pub fn (mut p Parser) warn_with_pos(s string, pos token.Position) { if p.pref.skip_warnings { return } - if p.pref.output_mode == .stdout { + if p.pref.output_mode == .stdout && !p.pref.check_only { ferror := util.formatted_error('warning:', s, p.file_name, pos) eprintln(ferror) } else { @@ -1700,7 +1718,7 @@ pub fn (mut p Parser) note_with_pos(s string, pos token.Position) { if p.pref.skip_warnings { return } - if p.pref.output_mode == .stdout { + if p.pref.output_mode == .stdout && !p.pref.check_only { ferror := util.formatted_error('notice:', s, p.file_name, pos) eprintln(ferror) } else { diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index bc8a77654a..ae410f8fd3 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -178,6 +178,7 @@ pub mut: is_parallel bool is_vweb bool // skip _ var warning in templates only_check_syntax bool // when true, just parse the files, then stop, before running checker + check_only bool // same as only_check_syntax, but also runs the checker experimental bool // enable experimental features skip_unused bool // skip generating C code for functions, that are not used show_timings bool // show how much time each compiler stage took @@ -245,6 +246,9 @@ pub fn parse_args(known_external_commands []string, args []string) (&Preferences '-check-syntax' { res.only_check_syntax = true } + '-check' { + res.check_only = true + } '-h', '-help', '--help' { // NB: help is *very important*, just respond to all variations: res.is_help = true diff --git a/vlib/v/scanner/scanner.v b/vlib/v/scanner/scanner.v index 4b933ecf92..832ef82559 100644 --- a/vlib/v/scanner/scanner.v +++ b/vlib/v/scanner/scanner.v @@ -1336,7 +1336,7 @@ pub fn (mut s Scanner) note(msg string) { line_nr: s.line_nr pos: s.pos } - if s.pref.output_mode == .stdout { + if s.pref.output_mode == .stdout && !s.pref.check_only { eprintln(util.formatted_error('notice:', msg, s.file_path, pos)) } else { s.notices << errors.Notice{ @@ -1358,7 +1358,7 @@ pub fn (mut s Scanner) warn(msg string) { pos: s.pos col: s.current_column() - 1 } - if s.pref.output_mode == .stdout { + if s.pref.output_mode == .stdout && !s.pref.check_only { eprintln(util.formatted_error('warning:', msg, s.file_path, pos)) } else { if s.pref.message_limit >= 0 && s.warnings.len >= s.pref.message_limit { @@ -1380,7 +1380,7 @@ pub fn (mut s Scanner) error(msg string) { pos: s.pos col: s.current_column() - 1 } - if s.pref.output_mode == .stdout { + if s.pref.output_mode == .stdout && !s.pref.check_only { eprintln(util.formatted_error('error:', msg, s.file_path, pos)) exit(1) } else {