mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
418 lines
10 KiB
V
418 lines
10 KiB
V
module doc
|
|
|
|
import os
|
|
import time
|
|
import v.ast
|
|
import v.checker
|
|
import v.fmt
|
|
import v.parser
|
|
import v.pref
|
|
import v.scanner
|
|
import v.table
|
|
import v.util
|
|
|
|
// intentionally in order as a guide when arranging the docnodes
|
|
pub enum SymbolKind {
|
|
none_
|
|
const_group
|
|
constant
|
|
variable
|
|
function
|
|
method
|
|
interface_
|
|
typedef
|
|
enum_
|
|
enum_field
|
|
struct_
|
|
struct_field
|
|
}
|
|
|
|
pub struct Doc {
|
|
prefs &pref.Preferences = new_vdoc_preferences()
|
|
pub mut:
|
|
base_path string
|
|
table &table.Table = &table.Table{}
|
|
checker checker.Checker = checker.Checker{
|
|
table: 0
|
|
cur_fn: 0
|
|
pref: 0
|
|
}
|
|
fmt fmt.Fmt
|
|
filename string
|
|
pos int
|
|
pub_only bool = true
|
|
with_comments bool = true
|
|
with_pos bool
|
|
with_head bool = true
|
|
is_vlib bool
|
|
time_generated time.Time
|
|
head DocNode
|
|
contents map[string]DocNode
|
|
scoped_contents map[string]DocNode
|
|
// for storing the contents of the file.
|
|
sources map[string]string
|
|
parent_mod_name string
|
|
orig_mod_name string
|
|
extract_vars bool
|
|
}
|
|
|
|
pub struct DocPos {
|
|
pub:
|
|
line int
|
|
col int
|
|
len int
|
|
}
|
|
|
|
pub struct DocNode {
|
|
pub mut:
|
|
name string
|
|
content string
|
|
comment string
|
|
pos DocPos = DocPos{-1, -1, 0}
|
|
file_path string
|
|
kind SymbolKind
|
|
deprecated bool
|
|
parent_name string
|
|
return_type string
|
|
children []DocNode
|
|
attrs map[string]string
|
|
from_scope bool
|
|
}
|
|
|
|
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:
|
|
return &pref.Preferences{
|
|
enable_globals: true
|
|
}
|
|
}
|
|
|
|
pub fn new(input_path string) Doc {
|
|
mut d := Doc{
|
|
base_path: os.real_path(input_path)
|
|
table: table.new_table()
|
|
head: DocNode{}
|
|
contents: map[string]DocNode{}
|
|
sources: map[string]string{}
|
|
time_generated: time.now()
|
|
}
|
|
d.fmt = fmt.Fmt{
|
|
indent: 0
|
|
is_debug: false
|
|
table: d.table
|
|
}
|
|
d.checker = checker.new_checker(d.table, d.prefs)
|
|
return d
|
|
}
|
|
|
|
pub fn (mut d Doc) stmt(stmt ast.Stmt, filename string) ?DocNode {
|
|
mut node := DocNode{
|
|
name: d.stmt_name(stmt)
|
|
content: d.stmt_signature(stmt)
|
|
comment: ''
|
|
pos: d.convert_pos(filename, stmt.position())
|
|
file_path: os.join_path(d.base_path, filename)
|
|
}
|
|
if (!node.content.starts_with('pub') && d.pub_only) || stmt is ast.GlobalDecl {
|
|
return error('symbol not public')
|
|
}
|
|
if node.name.starts_with(d.orig_mod_name + '.') {
|
|
node.name = node.name.all_after(d.orig_mod_name + '.')
|
|
}
|
|
if node.name.len == 0 && node.comment.len == 0 && node.content.len == 0 {
|
|
return error('empty stmt')
|
|
}
|
|
match stmt {
|
|
ast.ConstDecl {
|
|
node.kind = .const_group
|
|
node.parent_name = 'Constants'
|
|
if d.extract_vars {
|
|
for field in stmt.fields {
|
|
ret_type := if field.typ == 0 { d.expr_typ_to_string(field.expr) } else { d.type_to_str(field.typ) }
|
|
node.children << DocNode{
|
|
name: field.name.all_after(d.orig_mod_name + '.')
|
|
kind: .constant
|
|
pos: d.convert_pos(filename, field.pos)
|
|
return_type: ret_type
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ast.EnumDecl {
|
|
node.kind = .enum_
|
|
if d.extract_vars {
|
|
for field in stmt.fields {
|
|
ret_type := if field.has_expr { d.expr_typ_to_string(field.expr) } else { 'int' }
|
|
node.children << DocNode{
|
|
name: field.name
|
|
kind: .enum_field
|
|
parent_name: node.name
|
|
pos: d.convert_pos(filename, field.pos)
|
|
return_type: ret_type
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ast.InterfaceDecl {
|
|
node.kind = .interface_
|
|
}
|
|
ast.StructDecl {
|
|
node.kind = .struct_
|
|
if d.extract_vars {
|
|
for field in stmt.fields {
|
|
ret_type := if field.typ == 0 && field.has_default_expr { d.expr_typ_to_string(field.default_expr) } else { d.type_to_str(field.typ) }
|
|
node.children << DocNode{
|
|
name: field.name
|
|
kind: .struct_field
|
|
parent_name: node.name
|
|
pos: d.convert_pos(filename, field.pos)
|
|
return_type: ret_type
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ast.TypeDecl {
|
|
node.kind = .typedef
|
|
}
|
|
ast.FnDecl {
|
|
node.deprecated = stmt.is_deprecated
|
|
node.kind = .function
|
|
node.return_type = d.type_to_str(stmt.return_type)
|
|
if stmt.receiver.typ !in [0, 1] {
|
|
method_parent := d.type_to_str(stmt.receiver.typ)
|
|
node.kind = .method
|
|
node.parent_name = method_parent
|
|
}
|
|
if d.extract_vars {
|
|
for param in stmt.params {
|
|
node.children << DocNode{
|
|
name: param.name
|
|
kind: .variable
|
|
parent_name: node.name
|
|
pos: d.convert_pos(filename, param.pos)
|
|
attrs: {
|
|
'mut': param.is_mut.str()
|
|
}
|
|
return_type: d.type_to_str(param.typ)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
return error('invalid stmt type to document')
|
|
}
|
|
}
|
|
return node
|
|
}
|
|
|
|
pub fn (mut d Doc) file_ast(file_ast ast.File) map[string]DocNode {
|
|
mut contents := map[string]DocNode{}
|
|
stmts := file_ast.stmts
|
|
d.fmt.file = file_ast
|
|
d.fmt.set_current_module_name(d.orig_mod_name)
|
|
d.fmt.process_file_imports(file_ast)
|
|
mut last_import_stmt_idx := 0
|
|
for sidx, stmt in stmts {
|
|
if stmt is ast.Import {
|
|
last_import_stmt_idx = sidx
|
|
}
|
|
}
|
|
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
|
|
continue
|
|
}
|
|
}
|
|
// TODO: Fetch head comment once
|
|
if stmt is ast.Module {
|
|
if !d.with_head {
|
|
continue
|
|
}
|
|
// the previous comments were probably a copyright/license one
|
|
module_comment := get_comment_block_right_before(prev_comments)
|
|
prev_comments = []
|
|
if !d.is_vlib && !module_comment.starts_with('Copyright (c)') {
|
|
if module_comment in ['', d.head.comment] {
|
|
continue
|
|
}
|
|
if d.head.comment != '' {
|
|
d.head.comment += '\n'
|
|
}
|
|
d.head.comment += module_comment
|
|
}
|
|
continue
|
|
}
|
|
if last_import_stmt_idx > 0 && sidx == last_import_stmt_idx {
|
|
// the accumulated comments were interspersed before/between the imports;
|
|
// just add them all to the module comment:
|
|
if d.with_head {
|
|
import_comments := merge_comments(prev_comments)
|
|
if d.head.comment != '' {
|
|
d.head.comment += '\n'
|
|
}
|
|
d.head.comment += import_comments
|
|
}
|
|
prev_comments = []
|
|
imports_section = false
|
|
}
|
|
if stmt is ast.Import {
|
|
continue
|
|
}
|
|
mut node := d.stmt(stmt, os.base(file_ast.path)) or {
|
|
prev_comments = []
|
|
continue
|
|
}
|
|
if node.parent_name !in contents {
|
|
parent_node_kind := if node.parent_name == 'Constants' { SymbolKind.const_group } else { SymbolKind.typedef }
|
|
contents[node.parent_name] = DocNode{
|
|
name: node.parent_name
|
|
kind: parent_node_kind
|
|
}
|
|
}
|
|
if d.with_comments && (prev_comments.len > 0) {
|
|
// last_comment := contents[contents.len - 1].comment
|
|
// cmt := last_comment + '\n' + get_comment_block_right_before(prev_comments)
|
|
mut cmt := get_comment_block_right_before(prev_comments)
|
|
len := node.name.len
|
|
// fixed-width symbol name at start of comment
|
|
if cmt.starts_with(node.name) && cmt.len > len && cmt[len] == ` ` {
|
|
cmt = '`${cmt[..len]}`' + cmt[len..]
|
|
}
|
|
node.comment = cmt
|
|
}
|
|
prev_comments = []
|
|
if node.parent_name.len > 0 {
|
|
parent_name := node.parent_name
|
|
if node.parent_name == 'Constants' {
|
|
node.parent_name = ''
|
|
}
|
|
contents[parent_name].children << node
|
|
} else {
|
|
contents[node.name] = node
|
|
}
|
|
}
|
|
d.fmt.mod2alias = map[string]string{}
|
|
if contents[''].kind != .const_group {
|
|
contents.delete('')
|
|
}
|
|
return contents
|
|
}
|
|
|
|
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{}
|
|
for name, val in lscope.objects {
|
|
if val !is ast.Var {
|
|
continue
|
|
}
|
|
vr_data := val as ast.Var
|
|
l_node := DocNode{
|
|
name: name
|
|
pos: d.convert_pos(os.base(file_ast.path), vr_data.pos)
|
|
file_path: file_ast.path
|
|
from_scope: true
|
|
kind: .variable
|
|
return_type: d.expr_typ_to_string(vr_data.expr)
|
|
}
|
|
contents[l_node.name] = l_node
|
|
}
|
|
return contents
|
|
}
|
|
|
|
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)) }
|
|
d.is_vlib = 'vlib' !in d.base_path
|
|
project_files := os.ls(d.base_path) or {
|
|
return error_with_code(err, 0)
|
|
}
|
|
v_files := d.prefs.should_compile_filtered_files(d.base_path, project_files)
|
|
if v_files.len == 0 {
|
|
return error_with_code('vdoc: No valid V files were found.', 1)
|
|
}
|
|
// parse files
|
|
mut comments_mode := scanner.CommentsMode.skip_comments
|
|
if d.with_comments {
|
|
comments_mode = .toplevel_comments
|
|
}
|
|
global_scope := &ast.Scope{
|
|
parent: 0
|
|
}
|
|
mut file_asts := []ast.File{}
|
|
for i, file_path in v_files {
|
|
if i == 0 {
|
|
d.parent_mod_name = get_parent_mod(d.base_path) or {
|
|
''
|
|
}
|
|
}
|
|
filename := os.base(file_path)
|
|
d.sources[filename] = util.read_file(file_path) or {
|
|
''
|
|
}
|
|
file_asts <<
|
|
parser.parse_file(file_path, d.table, comments_mode, d.prefs, global_scope)
|
|
}
|
|
return d.file_asts(file_asts)
|
|
}
|
|
|
|
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
|
|
for i, file_ast in file_asts {
|
|
if d.filename.len > 0 && d.filename in file_ast.path && !fname_has_set {
|
|
d.filename = file_ast.path
|
|
fname_has_set = true
|
|
}
|
|
if d.with_head && i == 0 {
|
|
mut module_name := file_ast.mod.name
|
|
if module_name != 'main' && d.parent_mod_name.len > 0 {
|
|
module_name = d.parent_mod_name + '.' + module_name
|
|
}
|
|
d.head = DocNode{
|
|
name: module_name
|
|
content: 'module $module_name'
|
|
kind: .none_
|
|
}
|
|
} else if file_ast.mod.name != d.orig_mod_name {
|
|
continue
|
|
}
|
|
if file_ast.path == d.filename {
|
|
d.checker.check(file_ast)
|
|
d.scoped_contents = d.file_ast_with_pos(file_ast, d.pos)
|
|
}
|
|
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()
|
|
continue
|
|
}
|
|
d.contents[name] = node
|
|
}
|
|
}
|
|
d.time_generated = time.now()
|
|
}
|
|
|
|
pub fn generate(input_path string, pub_only bool, with_comments bool) ?Doc {
|
|
mut doc := new(input_path)
|
|
doc.pub_only = pub_only
|
|
doc.with_comments = with_comments
|
|
doc.generate() ?
|
|
return doc
|
|
}
|
|
|
|
pub fn generate_with_pos(input_path string, filename string, pos int) ?Doc {
|
|
mut doc := new(input_path)
|
|
doc.pub_only = false
|
|
doc.with_comments = true
|
|
doc.with_pos = true
|
|
doc.filename = filename
|
|
doc.pos = pos
|
|
doc.generate() ?
|
|
return doc
|
|
}
|