aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/cmd/tools/vvet/vvet.v
diff options
context:
space:
mode:
Diffstat (limited to 'v_windows/v/cmd/tools/vvet/vvet.v')
-rw-r--r--v_windows/v/cmd/tools/vvet/vvet.v256
1 files changed, 256 insertions, 0 deletions
diff --git a/v_windows/v/cmd/tools/vvet/vvet.v b/v_windows/v/cmd/tools/vvet/vvet.v
new file mode 100644
index 0000000..fd04b40
--- /dev/null
+++ b/v_windows/v/cmd/tools/vvet/vvet.v
@@ -0,0 +1,256 @@
+// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
+// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
+module main
+
+import os
+import os.cmdline
+import v.vet
+import v.pref
+import v.parser
+import v.token
+import v.ast
+import term
+
+struct Vet {
+ opt Options
+mut:
+ errors []vet.Error
+ warns []vet.Error
+ file string
+}
+
+struct Options {
+ is_force bool
+ is_werror bool
+ is_verbose bool
+ show_warnings bool
+ use_color bool
+}
+
+const term_colors = term.can_show_color_on_stderr()
+
+fn main() {
+ vet_options := cmdline.options_after(os.args, ['vet'])
+ mut vt := Vet{
+ opt: Options{
+ is_force: '-force' in vet_options
+ is_werror: '-W' in vet_options
+ is_verbose: '-verbose' in vet_options || '-v' in vet_options
+ show_warnings: '-hide-warnings' !in vet_options && '-w' !in vet_options
+ use_color: '-color' in vet_options || (term_colors && '-nocolor' !in vet_options)
+ }
+ }
+ mut paths := cmdline.only_non_options(vet_options)
+ vtmp := os.getenv('VTMP')
+ if vtmp != '' {
+ // `v test-cleancode` passes also `-o tmpfolder` as well as all options in VFLAGS
+ paths = paths.filter(!it.starts_with(vtmp))
+ }
+ for path in paths {
+ if !os.exists(path) {
+ eprintln('File/folder $path does not exist')
+ continue
+ }
+ if os.is_file(path) {
+ vt.vet_file(path)
+ }
+ if os.is_dir(path) {
+ vt.vprintln("vetting folder: '$path' ...")
+ vfiles := os.walk_ext(path, '.v')
+ vvfiles := os.walk_ext(path, '.vv')
+ mut files := []string{}
+ files << vfiles
+ files << vvfiles
+ for file in files {
+ vt.vet_file(file)
+ }
+ }
+ }
+ vfmt_err_count := vt.errors.filter(it.fix == .vfmt).len
+ if vt.opt.show_warnings {
+ for w in vt.warns {
+ eprintln(vt.e2string(w))
+ }
+ }
+ for err in vt.errors {
+ eprintln(vt.e2string(err))
+ }
+ if vfmt_err_count > 0 {
+ eprintln('NB: You can run `v fmt -w file.v` to fix these errors automatically')
+ }
+ if vt.errors.len > 0 {
+ exit(1)
+ }
+}
+
+// vet_file vets the file read from `path`.
+fn (mut vt Vet) vet_file(path string) {
+ if path.contains('/tests/') && !vt.opt.is_force {
+ // skip all /tests/ files, since usually their content is not
+ // important enough to be documented/vetted, and they may even
+ // contain intentionally invalid code.
+ vt.vprintln("skipping test file: '$path' ...")
+ return
+ }
+ vt.file = path
+ mut prefs := pref.new_preferences()
+ prefs.is_vet = true
+ prefs.is_vsh = path.ends_with('.vsh')
+ table := ast.new_table()
+ vt.vprintln("vetting file '$path'...")
+ _, errors := parser.parse_vet_file(path, table, prefs)
+ // Transfer errors from scanner and parser
+ vt.errors << errors
+ // Scan each line in file for things to improve
+ source_lines := os.read_lines(vt.file) or { []string{} }
+ for lnumber, line in source_lines {
+ vt.vet_line(source_lines, line, lnumber)
+ }
+}
+
+// vet_line vets the contents of `line` from `vet.file`.
+fn (mut vt Vet) vet_line(lines []string, line string, lnumber int) {
+ // Vet public functions
+ if line.starts_with('pub fn') || (line.starts_with('fn ') && !(line.starts_with('fn C.')
+ || line.starts_with('fn main'))) {
+ // Scan function declarations for missing documentation
+ is_pub_fn := line.starts_with('pub fn')
+ if lnumber > 0 {
+ collect_tags := fn (line string) []string {
+ mut cleaned := line.all_before('/')
+ cleaned = cleaned.replace_each(['[', '', ']', '', ' ', ''])
+ return cleaned.split(',')
+ }
+ ident_fn_name := fn (line string) string {
+ mut fn_idx := line.index(' fn ') or { return '' }
+ if line.len < fn_idx + 5 {
+ return ''
+ }
+ mut tokens := line[fn_idx + 4..].split(' ')
+ // Skip struct identifier
+ if tokens.first().starts_with('(') {
+ fn_idx = line.index(')') or { return '' }
+ tokens = line[fn_idx..].split(' ')
+ if tokens.len > 1 {
+ tokens = [tokens[1]]
+ }
+ }
+ if tokens.len > 0 {
+ return tokens[0].all_before('(')
+ }
+ return ''
+ }
+ mut line_above := lines[lnumber - 1]
+ mut tags := []string{}
+ if !line_above.starts_with('//') {
+ mut grab := true
+ for j := lnumber - 1; j >= 0; j-- {
+ prev_line := lines[j]
+ if prev_line.contains('}') { // We've looked back to the above scope, stop here
+ break
+ } else if prev_line.starts_with('[') {
+ tags << collect_tags(prev_line)
+ continue
+ } else if prev_line.starts_with('//') { // Single-line comment
+ grab = false
+ break
+ }
+ }
+ if grab {
+ clean_line := line.all_before_last('{').trim(' ')
+ if is_pub_fn {
+ vt.warn('Function documentation seems to be missing for "$clean_line".',
+ lnumber, .doc)
+ }
+ }
+ } else {
+ fn_name := ident_fn_name(line)
+ mut grab := true
+ for j := lnumber - 1; j >= 0; j-- {
+ prev_line := lines[j]
+ if prev_line.contains('}') { // We've looked back to the above scope, stop here
+ break
+ } else if prev_line.starts_with('// $fn_name ') {
+ grab = false
+ break
+ } else if prev_line.starts_with('// $fn_name') {
+ grab = false
+ if is_pub_fn {
+ clean_line := line.all_before_last('{').trim(' ')
+ vt.warn('The documentation for "$clean_line" seems incomplete.',
+ lnumber, .doc)
+ }
+ break
+ } else if prev_line.starts_with('[') {
+ tags << collect_tags(prev_line)
+ continue
+ } else if prev_line.starts_with('//') { // Single-line comment
+ continue
+ }
+ }
+ if grab {
+ clean_line := line.all_before_last('{').trim(' ')
+ if is_pub_fn {
+ vt.warn('A function name is missing from the documentation of "$clean_line".',
+ lnumber, .doc)
+ }
+ }
+ }
+ }
+ }
+}
+
+fn (vt &Vet) vprintln(s string) {
+ if !vt.opt.is_verbose {
+ return
+ }
+ println(s)
+}
+
+fn (vt &Vet) e2string(err vet.Error) string {
+ mut kind := '$err.kind:'
+ mut location := '$err.file_path:$err.pos.line_nr:'
+ if vt.opt.use_color {
+ kind = match err.kind {
+ .warning { term.magenta(kind) }
+ .error { term.red(kind) }
+ }
+ kind = term.bold(kind)
+ location = term.bold(location)
+ }
+ return '$location $kind $err.message'
+}
+
+fn (mut vt Vet) error(msg string, line int, fix vet.FixKind) {
+ pos := token.Position{
+ line_nr: line + 1
+ }
+ vt.errors << vet.Error{
+ message: msg
+ file_path: vt.file
+ pos: pos
+ kind: .error
+ fix: fix
+ typ: .default
+ }
+}
+
+fn (mut vt Vet) warn(msg string, line int, fix vet.FixKind) {
+ pos := token.Position{
+ line_nr: line + 1
+ }
+ mut w := vet.Error{
+ message: msg
+ file_path: vt.file
+ pos: pos
+ kind: .warning
+ fix: fix
+ typ: .default
+ }
+ if vt.opt.is_werror {
+ w.kind = .error
+ vt.errors << w
+ } else {
+ vt.warns << w
+ }
+}