From 9e502d7bf2a0e1dcf33a38685e60c4f45ff2b555 Mon Sep 17 00:00:00 2001
From: Delyan Angelov <delian66@gmail.com>
Date: Sat, 30 Apr 2022 16:01:35 +0300
Subject: [PATCH] tools: support a `v vet -p file.v` option that will warn
 about private functions with missing documentation

---
 cmd/tools/vvet/vvet.v | 180 ++++++++++++++++++++++--------------------
 cmd/v/help/vet.txt    |   3 +
 2 files changed, 98 insertions(+), 85 deletions(-)

diff --git a/cmd/tools/vvet/vvet.v b/cmd/tools/vvet/vvet.v
index 42bc430d07..1b9cc3f9b9 100644
--- a/cmd/tools/vvet/vvet.v
+++ b/cmd/tools/vvet/vvet.v
@@ -20,11 +20,12 @@ mut:
 }
 
 struct Options {
-	is_force      bool
-	is_werror     bool
-	is_verbose    bool
-	show_warnings bool
-	use_color     bool
+	is_force            bool
+	is_werror           bool
+	is_verbose          bool
+	show_warnings       bool
+	use_color           bool
+	doc_private_fns_too bool
 }
 
 const term_colors = term.can_show_color_on_stderr()
@@ -38,6 +39,7 @@ fn main() {
 			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)
+			doc_private_fns_too: '-p' in vet_options
 		}
 	}
 	mut paths := cmdline.only_non_options(vet_options)
@@ -110,92 +112,100 @@ fn (mut vt Vet) vet_file(path string) {
 
 // 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('(')
-				}
+	vt.vet_fn_documentation(lines, line, lnumber)
+}
+
+// vet_fn_documentation ensures that functions are documented
+fn (mut vt Vet) vet_fn_documentation(lines []string, line string, lnumber int) {
+	if line.starts_with('fn C.') {
+		return
+	}
+	is_pub_fn := line.starts_with('pub fn ')
+	is_fn := is_pub_fn || line.starts_with('fn ')
+	if !is_fn {
+		return
+	}
+	if line.starts_with('fn main') {
+		return
+	}
+	if !(is_pub_fn || vt.opt.doc_private_fns_too) {
+		return
+	}
+	// Scan function declarations for missing documentation
+	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 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
-					}
+			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 grab {
+			}
+			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(' ')
+				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
 					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)
-					}
+					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(' ')
+				vt.warn('A function name is missing from the documentation of "$clean_line".',
+					lnumber, .doc)
+			}
 		}
 	}
 }
diff --git a/cmd/v/help/vet.txt b/cmd/v/help/vet.txt
index 7a0d2bf33f..a996b17811 100644
--- a/cmd/v/help/vet.txt
+++ b/cmd/v/help/vet.txt
@@ -14,5 +14,8 @@ Options:
   -v, -verbose
      Enable verbose logging.
 
+  -p
+     Report private functions with missing documentation too (by default, only the `pub fn` functions will be reported).
+
   -force
      (NB: vet development only!) Do not skip the vet regression tests.