v / cmd / tools / vdoc / document / utils.v
239 lines · 227 sloc · 6.44 KB · a92b5a36ba604b704fd85a7554c60454cdbe298b
Raw
1module document
2
3import strings
4import v.ast
5import v.token
6
7const highlight_keys = ['note:', 'fixme:', 'todo:']
8const horizontal_rule_chars = ['-', '=', '*', '_', '~']
9
10// merge_comments merges all the comment contents into a single text.
11pub fn merge_comments(comments []ast.Comment) string {
12 mut res := []string{}
13 for comment in comments {
14 res << comment.text.trim_left('\x01')
15 }
16 return res.join('\n')
17}
18
19// ast_comment_to_doc_comment converts an `ast.Comment` node type to a `DocComment`
20pub fn ast_comment_to_doc_comment(ast_node ast.Comment) DocComment {
21 text := ast_node.text // TODO: .trim_left('\x01') // BUG why are this byte here in the first place?
22 return DocComment{
23 text: text
24 is_multi: ast_node.is_multi
25 pos: token.Pos{
26 line_nr: ast_node.pos.line_nr
27 col: 0 // ast_node.pos.pos - ast_node.text.len
28 len: text.len
29 }
30 }
31}
32
33// ast_comments_to_doc_comments converts an array of `ast.Comment` nodes to
34// an array of `DocComment` nodes
35pub fn ast_comments_to_doc_comments(ast_nodes []ast.Comment) []DocComment {
36 mut doc_comments := []DocComment{len: ast_nodes.len}
37 for ast_comment in ast_nodes {
38 doc_comments << ast_comment_to_doc_comment(ast_comment)
39 }
40 return doc_comments
41}
42
43// merge_doc_comments merges all the comments starting from
44// the last up to the first item of the array.
45pub fn merge_doc_comments(comments []DocComment) string {
46 if comments.len == 0 {
47 return ''
48 }
49 if raw_markdown := merge_raw_markdown_comments(comments) {
50 return raw_markdown
51 }
52 mut doc_comments := []string{}
53 for i := comments.len - 1; i >= 0; i-- {
54 if comments[i].is_multi {
55 // `/*foo*/` block comments are deliberately NOT supported as doc comments, ignore them.
56 continue
57 }
58 doc_comments << comments[i].text
59 if cmt_above := comments[i - 1] {
60 if cmt_above.pos.line_nr + 1 < comments[i].pos.line_nr {
61 // Stop when a doc comment reaches the top of its contiguous block.
62 break
63 }
64 }
65 }
66 mut comment := ''
67 mut next_on_newline := true
68 mut is_codeblock := false
69 mut trimmed_indent := ''
70 for cmt in doc_comments.reverse() {
71 line_loop: for line in cmt.split_into_lines() {
72 l_normalized := line.trim_left('\x01')
73 l := l_normalized.trim_space()
74 last_ends_with_lb := comment.ends_with('\n')
75 if l == '' {
76 comment += if last_ends_with_lb { '\n' } else { '\n\n' }
77 next_on_newline = true
78 continue
79 }
80 has_codeblock_quote := l.starts_with('```')
81 if is_codeblock {
82 comment += l_normalized.trim_string_left(trimmed_indent) + '\n'
83 if has_codeblock_quote {
84 is_codeblock = !is_codeblock
85 }
86 continue
87 }
88 if has_codeblock_quote {
89 if !is_codeblock && !last_ends_with_lb {
90 comment += '\n'
91 }
92 comment += l + '\n'
93 is_codeblock = !is_codeblock
94 trimmed_indent = l_normalized.all_before(l)
95 next_on_newline = true
96 continue
97 }
98 if l.starts_with('>') {
99 if !last_ends_with_lb {
100 comment += '\n'
101 }
102 comment += l + '\n'
103 next_on_newline = true
104 continue
105 }
106 is_list := l.len > 1 && ((l[1] == ` ` && l[0] in [`-`, `*`, `+`])
107 || (l.len > 2 && l[2] == ` ` && l[1] == `.` && l[0].is_digit()))
108 line_before_spaces := l.before(' ')
109 if is_list || (l.starts_with('|') && l.ends_with('|'))
110 || (l.starts_with('#') && line_before_spaces.count('#') == line_before_spaces.len) {
111 comment += l + '\n'
112 next_on_newline = true
113 continue
114 }
115 // Use own paragraph for "highlight" comments.
116 ll := l.to_lower_ascii()
117 mut continue_line_loop := false
118 for key in highlight_keys {
119 if ll.starts_with(key) {
120 comment += '\n\n${key.title()}${l[key.len..]}'
121 // Workaround for compiling with `v -cstrict -cc gcc cmd/tools/vdoc/document/doc_test.v`
122 // and using multiple continue `<label>`.
123 continue_line_loop = true
124 break
125 }
126 }
127 if continue_line_loop {
128 continue
129 }
130 line_no_spaces := l.replace(' ', '')
131 for ch in horizontal_rule_chars {
132 if line_no_spaces.starts_with(ch.repeat(3))
133 && line_no_spaces.count(ch) == line_no_spaces.len {
134 comment += '\n' + l + '\n'
135 next_on_newline = true
136 continue line_loop
137 }
138 }
139 if !next_on_newline {
140 comment += ' '
141 }
142 comment += l
143 next_on_newline = false
144 }
145 }
146 return comment
147}
148
149fn merge_raw_markdown_comments(comments []DocComment) ?string {
150 if !comments.all(it.is_readme) {
151 return none
152 }
153 mut raw_markdown := []string{}
154 for i := comments.len - 1; i >= 0; i-- {
155 if comments[i].is_multi {
156 continue
157 }
158 raw_markdown << comments[i].text.trim_left('\x01')
159 }
160 return raw_markdown.reverse().join('\n')
161}
162
163// stmt_signature returns the signature of a given `ast.Stmt` node.
164pub fn (mut d Doc) stmt_signature(stmt ast.Stmt) string {
165 match stmt {
166 ast.Module {
167 return 'module ${stmt.name}'
168 }
169 ast.FnDecl {
170 return d.table.stringify_fn_decl(&stmt, d.fmt.cur_mod, d.fmt.mod2alias, false)
171 }
172 else {
173 d.fmt.out = strings.new_builder(1000)
174 d.fmt.stmt(stmt)
175 return d.fmt.out.str().trim_space()
176 }
177 }
178}
179
180// stmt_name returns the name of a given `ast.Stmt` node.
181pub fn (d Doc) stmt_name(stmt ast.Stmt) string {
182 match stmt {
183 ast.StructDecl, ast.EnumDecl, ast.InterfaceDecl {
184 return stmt.name
185 }
186 ast.FnDecl {
187 if stmt.is_static_type_method {
188 return stmt.name.replace('__static__', '.')
189 } else {
190 return stmt.name
191 }
192 }
193 ast.TypeDecl {
194 match stmt {
195 ast.FnTypeDecl, ast.AliasTypeDecl, ast.SumTypeDecl { return stmt.name }
196 }
197 }
198 ast.ConstDecl {
199 return ''
200 } // leave it blank
201 else {
202 return ''
203 }
204 }
205}
206
207// stmt_pub returns a boolean if a given `ast.Stmt` node
208// is exposed to the public.
209pub fn (d Doc) stmt_pub(stmt ast.Stmt) bool {
210 match stmt {
211 ast.FnDecl, ast.StructDecl, ast.EnumDecl, ast.InterfaceDecl, ast.ConstDecl {
212 return stmt.is_pub
213 }
214 ast.TypeDecl {
215 match stmt {
216 ast.FnTypeDecl, ast.AliasTypeDecl, ast.SumTypeDecl { return stmt.is_pub }
217 }
218 }
219 else {
220 return false
221 }
222 }
223}
224
225// type_to_str is a wrapper function around `fmt.ast.type_to_str`.
226pub fn (mut d Doc) type_to_str(typ ast.Type) string {
227 // why is it the default behaviour of ast.type_to_str
228 // to convert math.bits.Type to bits.Type?
229 d.table.cmod_prefix = d.orig_mod_name + '.'
230 return d.fmt.table.type_to_str(typ).all_after('&')
231}
232
233// expr_typ_to_string has the same function as `Doc.typ_to_str`
234// but for `ast.Expr` nodes. The checker will check first the
235// node and it executes the `type_to_str` method.
236pub fn (mut d Doc) expr_typ_to_string(mut expr ast.Expr) string {
237 expr_typ := d.checker.expr(mut expr)
238 return d.type_to_str(expr_typ)
239}
240