diff --git a/cmd/tools/vdoc.v b/cmd/tools/vdoc.v index 4cf5d7e3c5..2e80552cb8 100644 --- a/cmd/tools/vdoc.v +++ b/cmd/tools/vdoc.v @@ -154,9 +154,7 @@ fn (mut cfg DocConfig) serve_html() { } def_name := docs.keys()[0] server_url := 'http://localhost:' + cfg.server_port.str() - server := net.listen_tcp(cfg.server_port) or { - panic(err) - } + server := net.listen_tcp(cfg.server_port) or { panic(err) } println('Serving docs on: $server_url') if cfg.open_docs { open_url(server_url) @@ -178,9 +176,7 @@ fn (mut cfg DocConfig) serve_html() { panic(err) } handle_http_connection(mut conn, server_context) - conn.close() or { - eprintln('error closing the connection: $err') - } + conn.close() or { eprintln('error closing the connection: $err') } } } @@ -191,9 +187,7 @@ struct VdocHttpServerContext { } fn handle_http_connection(mut con net.TcpConn, ctx &VdocHttpServerContext) { - mut reader := io.new_buffered_reader({ - reader: io.make_reader(con) - }) + mut reader := io.new_buffered_reader(reader: io.make_reader(con)) first_line := reader.read_line() or { send_http_response(mut con, 501, ctx.content_type, 'bad request') return @@ -230,15 +224,11 @@ fn send_http_response(mut con net.TcpConn, http_code int, content_type string, h http_response.write('\r\n') http_response.write(html) sresponse := http_response.str() - con.write_str(sresponse) or { - eprintln('error sending http response: $err') - } + con.write_str(sresponse) or { eprintln('error sending http response: $err') } } fn get_src_link(repo_url string, file_name string, line_nr int) string { - mut url := urllib.parse(repo_url) or { - return '' - } + mut url := urllib.parse(repo_url) or { return '' } if url.path.len <= 1 || file_name.len == 0 { return '' } @@ -656,13 +646,13 @@ fn (mut cfg DocConfig) render_static() { return } cfg.assets = { - 'doc_css': cfg.get_resource(css_js_assets[0], true) + 'doc_css': cfg.get_resource(css_js_assets[0], true) 'normalize_css': cfg.get_resource(css_js_assets[1], true) - 'doc_js': cfg.get_resource(css_js_assets[2], !cfg.serve_http) - 'light_icon': cfg.get_resource('light.svg', true) - 'dark_icon': cfg.get_resource('dark.svg', true) - 'menu_icon': cfg.get_resource('menu.svg', true) - 'arrow_icon': cfg.get_resource('arrow.svg', true) + 'doc_js': cfg.get_resource(css_js_assets[2], !cfg.serve_http) + 'light_icon': cfg.get_resource('light.svg', true) + 'dark_icon': cfg.get_resource('dark.svg', true) + 'menu_icon': cfg.get_resource('menu.svg', true) + 'arrow_icon': cfg.get_resource('arrow.svg', true) } } @@ -679,9 +669,7 @@ fn (cfg DocConfig) get_readme(path string) string { } readme_path := os.join_path(path, '${fname}.md') cfg.vprintln('Reading README file from $readme_path') - readme_contents := os.read_file(readme_path) or { - '' - } + readme_contents := os.read_file(readme_path) or { '' } return readme_contents } @@ -809,16 +797,12 @@ fn (mut cfg DocConfig) generate_docs_from_file() { cfg.output_path = os.real_path('.') } if !os.exists(cfg.output_path) { - os.mkdir(cfg.output_path) or { - panic(err) - } + os.mkdir(cfg.output_path) or { panic(err) } } if cfg.is_multi { cfg.output_path = os.join_path(cfg.output_path, '_docs') if !os.exists(cfg.output_path) { - os.mkdir(cfg.output_path) or { - panic(err) - } + os.mkdir(cfg.output_path) or { panic(err) } } else { for fname in css_js_assets { os.rm(os.join_path(cfg.output_path, fname)) @@ -865,9 +849,7 @@ fn get_ignore_paths(path string) ?[]string { } res = final.map(os.join_path(path, it.trim_right('/'))) } else { - mut dirs := os.ls(path) or { - return []string{} - } + mut dirs := os.ls(path) or { return []string{} } res = dirs.map(os.join_path(path, it)).filter(os.is_dir(it)) } return res.map(it.replace('/', os.path_separator)) @@ -887,12 +869,8 @@ fn is_included(path string, ignore_paths []string) bool { } fn get_modules_list(path string, ignore_paths2 []string) []string { - files := os.ls(path) or { - return []string{} - } - mut ignore_paths := get_ignore_paths(path) or { - []string{} - } + files := os.ls(path) or { return []string{} } + mut ignore_paths := get_ignore_paths(path) or { []string{} } ignore_paths << ignore_paths2 mut dirs := []string{} for file in files { @@ -912,9 +890,7 @@ fn get_modules_list(path string, ignore_paths2 []string) []string { fn (cfg DocConfig) get_resource(name string, minify bool) string { path := os.join_path(res_path, name) - mut res := os.read_file(path) or { - panic('vdoc: could not read $path') - } + mut res := os.read_file(path) or { panic('vdoc: could not read $path') } if minify { if name.ends_with('.js') { res = js_compress(res) diff --git a/cmd/tools/vtest-cleancode.v b/cmd/tools/vtest-cleancode.v index 661b00088c..94a8d3a8e4 100644 --- a/cmd/tools/vtest-cleancode.v +++ b/cmd/tools/vtest-cleancode.v @@ -18,6 +18,7 @@ const ( 'nonexistant', ] vfmt_verify_list = [ + 'cmd/tools/vdoc.v' 'cmd/v/v.v', 'vlib/builtin/array.v', 'vlib/os/file.v', diff --git a/vlib/v/doc/doc.v b/vlib/v/doc/doc.v index d410025388..d8d993925b 100644 --- a/vlib/v/doc/doc.v +++ b/vlib/v/doc/doc.v @@ -11,7 +11,8 @@ import v.scanner import v.table import v.util -// intentionally in order as a guide when arranging the docnodes +// SymbolKind categorizes the symbols it documents. +// The names are intentionally not in order as a guide when sorting the nodes. pub enum SymbolKind { none_ const_group @@ -75,10 +76,12 @@ pub mut: parent_name string return_type string children []DocNode - attrs map[string]string + attrs map[string]string [json: attributes] from_scope bool + is_pub bool [json: public] } +// new_vdoc_preferences creates a new instance of pref.Preferences tailored for v.doc. pub fn new_vdoc_preferences() &pref.Preferences { // vdoc should be able to parse as much user code as possible // so its preferences should be permissive: @@ -87,6 +90,7 @@ pub fn new_vdoc_preferences() &pref.Preferences { } } +// new creates a new instance of a `Doc` struct. pub fn new(input_path string) Doc { mut d := Doc{ base_path: os.real_path(input_path) @@ -105,6 +109,9 @@ pub fn new(input_path string) Doc { return d } +// stmt reads the data of an `ast.Stmt` node and returns a `DocNode`. +// An option error is thrown if the symbol is not exposed to the public +// (when `pub_only` is enabled) or the content's of the AST node is empty. pub fn (mut d Doc) stmt(stmt ast.Stmt, filename string) ?DocNode { mut node := DocNode{ name: d.stmt_name(stmt) @@ -112,9 +119,10 @@ pub fn (mut d Doc) stmt(stmt ast.Stmt, filename string) ?DocNode { comment: '' pos: d.convert_pos(filename, stmt.position()) file_path: os.join_path(d.base_path, filename) + is_pub: d.stmt_pub(stmt) } - if (!node.content.starts_with('pub') && d.pub_only) || stmt is ast.GlobalDecl { - return error('symbol not public') + if (!node.is_pub && d.pub_only) || stmt is ast.GlobalDecl { + return error('symbol $node.name not public') } if node.name.starts_with(d.orig_mod_name + '.') { node.name = node.name.all_after(d.orig_mod_name + '.') @@ -205,6 +213,7 @@ pub fn (mut d Doc) stmt(stmt ast.Stmt, filename string) ?DocNode { return node } +// file_ast reads the contents of `ast.File` and returns a map of `DocNode`s. pub fn (mut d Doc) file_ast(file_ast ast.File) map[string]DocNode { mut contents := map[string]DocNode{} stmts := file_ast.stmts @@ -220,7 +229,6 @@ pub fn (mut d Doc) file_ast(file_ast ast.File) map[string]DocNode { mut prev_comments := []ast.Comment{} mut imports_section := true for sidx, stmt in stmts { - // eprintln('stmt typeof: ' + typeof(stmt)) if stmt is ast.ExprStmt { if stmt.expr is ast.Comment { prev_comments << stmt.expr @@ -302,6 +310,8 @@ pub fn (mut d Doc) file_ast(file_ast ast.File) map[string]DocNode { return contents } +// file_ast_with_pos has the same function as the `file_ast` but +// instead returns a list of variables in a given offset-based position. pub fn (mut d Doc) file_ast_with_pos(file_ast ast.File, pos int) map[string]DocNode { lscope := file_ast.scope.innermost(pos) mut contents := map[string]DocNode{} @@ -323,6 +333,8 @@ pub fn (mut d Doc) file_ast_with_pos(file_ast ast.File, pos int) map[string]DocN return contents } +// generate is a `Doc` method that will start documentation +// process based on a file path provided. pub fn (mut d Doc) generate() ? { // get all files d.base_path = if os.is_dir(d.base_path) { d.base_path } else { os.real_path(os.dir(d.base_path)) } @@ -353,6 +365,8 @@ pub fn (mut d Doc) generate() ? { return d.file_asts(file_asts) } +// file_asts has the same function as the `file_ast` function but +// accepts an array of `ast.File` and throws an error if necessary. pub fn (mut d Doc) file_asts(file_asts []ast.File) ? { mut fname_has_set := false d.orig_mod_name = file_asts[0].mod.name @@ -380,17 +394,27 @@ pub fn (mut d Doc) file_asts(file_asts []ast.File) ? { } contents := d.file_ast(file_ast) for name, node in contents { - if name in d.contents && (d.contents[name].kind != .none_ || node.kind == .none_) { - d.contents[name].children << node.children - d.contents[name].children.sort_by_name() + if name !in d.contents { + d.contents[name] = node continue } - d.contents[name] = node + if d.contents[name].kind == .typedef && node.kind !in [.typedef, .none_] { + old_children := d.contents[name].children.clone() + d.contents[name] = node + d.contents[name].children = old_children + } + if d.contents[name].kind != .none_ || node.kind == .none_ { + d.contents[name].children << node.children + d.contents[name].children.sort_by_name() + d.contents[name].children.sort_by_kind() + } } } d.time_generated = time.now() } +// generate documents a certain file directory and returns an +// instance of `Doc` if it is successful. Otherwise, it will throw an error. pub fn generate(input_path string, pub_only bool, with_comments bool) ?Doc { mut doc := new(input_path) doc.pub_only = pub_only @@ -399,6 +423,8 @@ pub fn generate(input_path string, pub_only bool, with_comments bool) ?Doc { return doc } +// generate_with_pos has the same function as the `generate` function but +// accepts an offset-based position and enables the comments by default. pub fn generate_with_pos(input_path string, filename string, pos int) ?Doc { mut doc := new(input_path) doc.pub_only = false diff --git a/vlib/v/doc/module.v b/vlib/v/doc/module.v index 8b26750e29..860d66592c 100644 --- a/vlib/v/doc/module.v +++ b/vlib/v/doc/module.v @@ -6,7 +6,7 @@ import v.parser import v.ast import v.pref -// get_parent_mod - return the parent mod name, in dot format. +// get_parent_mod returns the parent mod name, in dot format. // It works by climbing up the folder hierarchy, until a folder, // that either contains main .v files, or a v.mod file is reached. // For example, given something like /languages/v/vlib/x/websocket/tests/autobahn @@ -55,6 +55,8 @@ fn get_parent_mod(input_dir string) ?string { return file_ast.mod.name } +// lookup_module_with_path looks up the path of a given module name. +// Throws an error if the module was not found. pub fn lookup_module_with_path(mod string, base_path string) ?string { vexe := pref.vexe_path() vroot := os.dir(vexe) @@ -76,10 +78,13 @@ pub fn lookup_module_with_path(mod string, base_path string) ?string { return error('module "$mod" not found.') } +// lookup_module returns the result of the `lookup_module_with_path` +// but with the current directory as the provided base lookup path. pub fn lookup_module(mod string) ?string { return lookup_module_with_path(mod, os.dir('.')) } +// generate_from_mod generates a documentation from a specific module. pub fn generate_from_mod(module_name string, pub_only bool, with_comments bool) ?Doc { mod_path := lookup_module(module_name) ? return generate(mod_path, pub_only, with_comments) diff --git a/vlib/v/doc/node.v b/vlib/v/doc/node.v index 489afc45a3..09f73d5e69 100644 --- a/vlib/v/doc/node.v +++ b/vlib/v/doc/node.v @@ -10,10 +10,12 @@ pub fn (nodes []DocNode) find(symname string) ?DocNode { return error('symbol not found') } +// sort_by_name sorts the array based on the symbol names. pub fn (mut nodes []DocNode) sort_by_name() { nodes.sort_with_compare(compare_nodes_by_name) } +// sort_by_kind sorts the array based on the symbol kind. pub fn (mut nodes []DocNode) sort_by_kind() { nodes.sort_with_compare(compare_nodes_by_kind) } @@ -36,6 +38,7 @@ fn compare_nodes_by_name(a &DocNode, b &DocNode) int { return compare_strings(al, bl) } +// arr() converts the map into an array of `DocNode`. pub fn (cnts map[string]DocNode) arr() []DocNode { mut contents := cnts.keys().map(cnts[it]) contents.sort_by_name() diff --git a/vlib/v/doc/utils.v b/vlib/v/doc/utils.v index 0b5ae53963..38d322157a 100644 --- a/vlib/v/doc/utils.v +++ b/vlib/v/doc/utils.v @@ -7,6 +7,7 @@ import v.token import v.table import os +// merge_comments merges all the comment contents into a single text. pub fn merge_comments(comments []ast.Comment) string { mut res := []string{} for comment in comments { @@ -15,6 +16,8 @@ pub fn merge_comments(comments []ast.Comment) string { return res.join('\n') } +// get_comment_block_right_before merges all the comments starting from +// the last up to the first item of the array. pub fn get_comment_block_right_before(comments []ast.Comment) string { if comments.len == 0 { return '' @@ -60,6 +63,7 @@ pub fn get_comment_block_right_before(comments []ast.Comment) string { return comment } +// convert_pos converts the `token.Position` data into a `DocPos`. fn (mut d Doc) convert_pos(filename string, pos token.Position) DocPos { if filename !in d.sources { d.sources[filename] = util.read_file(os.join_path(d.base_path, filename)) or { '' } @@ -74,6 +78,7 @@ fn (mut d Doc) convert_pos(filename string, pos token.Position) DocPos { } } +// stmt_signature returns the signature of a given `ast.Stmt` node. pub fn (mut d Doc) stmt_signature(stmt ast.Stmt) string { match stmt { ast.Module { @@ -90,6 +95,7 @@ pub fn (mut d Doc) stmt_signature(stmt ast.Stmt) string { } } +// stmt_name returns the name of a given `ast.Stmt` node. pub fn (d Doc) stmt_name(stmt ast.Stmt) string { match stmt { ast.FnDecl, ast.StructDecl, ast.EnumDecl, ast.InterfaceDecl { return stmt.name } @@ -101,10 +107,26 @@ pub fn (d Doc) stmt_name(stmt ast.Stmt) string { } } +// stmt_pub returns a boolean if a given `ast.Stmt` node +// is exposed to the public. +pub fn (d Doc) stmt_pub(stmt ast.Stmt) bool { + match stmt { + ast.FnDecl, ast.StructDecl, ast.EnumDecl, ast.InterfaceDecl, ast.ConstDecl { return stmt.is_pub } + ast.TypeDecl { match stmt { + ast.FnTypeDecl, ast.AliasTypeDecl, ast.SumTypeDecl { return stmt.is_pub } + } } + else { return false } + } +} + +// type_to_str is a wrapper function around `fmt.table.type_to_str`. pub fn (d Doc) type_to_str(typ table.Type) string { return d.fmt.table.type_to_str(typ).all_after('&') } +// expr_typ_to_string has the same function as `Doc.typ_to_str` +// but for `ast.Expr` nodes. The checker will check first the +// node and it executes the `type_to_str` method. pub fn (mut d Doc) expr_typ_to_string(ex ast.Expr) string { expr_typ := d.checker.expr(ex) return d.type_to_str(expr_typ)