v

Зеркало из https://github.com/vlang/v
Форк
0
/
vmissdoc.v 
294 строки · 8.3 Кб
1
// Copyright (c) 2020 Lars Pontoppidan. All rights reserved.
2
// Use of this source code is governed by an MIT license
3
// that can be found in the LICENSE file.
4
import os
5
import flag
6

7
const tool_name = 'v missdoc'
8
const tool_version = '0.1.0'
9
const tool_description = 'Prints all V functions in .v files under PATH/, that do not yet have documentation comments.'
10
const work_dir_prefix = normalise_path(os.real_path(os.wd_at_startup) + os.path_separator)
11

12
struct UndocumentedFN {
13
	file      string
14
	line      int
15
	signature string
16
	tags      []string
17
}
18

19
struct Options {
20
	show_help       bool
21
	collect_tags    bool
22
	deprecated      bool
23
	private         bool
24
	js              bool
25
	no_line_numbers bool
26
	exclude         []string
27
	relative_paths  bool
28
mut:
29
	verify          bool
30
	diff            bool
31
	additional_args []string
32
}
33

34
fn (opt Options) collect_undocumented_functions_in_dir(directory string) []UndocumentedFN {
35
	mut files := []string{}
36
	collect(directory, mut files, fn (npath string, mut accumulated_paths []string) {
37
		if !npath.ends_with('.v') {
38
			return
39
		}
40
		if npath.ends_with('_test.v') {
41
			return
42
		}
43
		accumulated_paths << npath
44
	})
45
	mut undocumented_fns := []UndocumentedFN{}
46
	for file in files {
47
		if !opt.js && file.ends_with('.js.v') {
48
			continue
49
		}
50
		if opt.exclude.len > 0 && opt.exclude.any(file.contains(it)) {
51
			continue
52
		}
53
		undocumented_fns << opt.collect_undocumented_functions_in_file(file)
54
	}
55
	return undocumented_fns
56
}
57

58
fn (opt &Options) collect_undocumented_functions_in_file(nfile string) []UndocumentedFN {
59
	file := os.real_path(nfile)
60
	contents := os.read_file(file) or { panic(err) }
61
	lines := contents.split('\n')
62
	mut list := []UndocumentedFN{}
63
	mut comments := []string{}
64
	mut tags := []string{}
65
	for i, line in lines {
66
		line_trimmed := line.trim_space()
67
		if line.starts_with('//') {
68
			comments << line
69
		} else if line_trimmed.starts_with('@[') || line_trimmed.starts_with('[') {
70
			tags << collect_tags(line)
71
		} else if line.starts_with('pub fn')
72
			|| (opt.private && (line.starts_with('fn ') && !(line.starts_with('fn C.')
73
			|| line.starts_with('fn main')))) {
74
			if comments.len == 0 {
75
				clean_line := line.all_before_last(' {')
76
				list << UndocumentedFN{
77
					line:      i + 1
78
					signature: clean_line
79
					tags:      tags
80
					file:      file
81
				}
82
			}
83
			tags = []
84
			comments = []
85
		} else {
86
			tags = []
87
			comments = []
88
		}
89
	}
90
	return list
91
}
92

93
fn (opt &Options) collect_undocumented_functions_in_path(path string) []UndocumentedFN {
94
	mut undocumented_functions := []UndocumentedFN{}
95
	if os.is_file(path) {
96
		undocumented_functions << opt.collect_undocumented_functions_in_file(path)
97
	} else {
98
		undocumented_functions << opt.collect_undocumented_functions_in_dir(path)
99
	}
100
	return undocumented_functions
101
}
102

103
fn (opt &Options) report_undocumented_functions_in_path(path string) int {
104
	list := opt.collect_undocumented_functions_in_path(path)
105
	return opt.report_undocumented_functions(list)
106
}
107

108
fn (opt &Options) report_undocumented_functions(list []UndocumentedFN) int {
109
	mut nreports := 0
110
	if list.len > 0 {
111
		for undocumented_fn in list {
112
			mut line_numbers := '${undocumented_fn.line}:0:'
113
			if opt.no_line_numbers {
114
				line_numbers = ''
115
			}
116
			tags_str := if opt.collect_tags && undocumented_fn.tags.len > 0 {
117
				'${undocumented_fn.tags}'
118
			} else {
119
				''
120
			}
121
			file := undocumented_fn.file
122
			ofile := if opt.relative_paths {
123
				file.replace(work_dir_prefix, '')
124
			} else {
125
				os.real_path(file)
126
			}
127
			if opt.deprecated {
128
				println('${ofile}:${line_numbers}${undocumented_fn.signature} ${tags_str}')
129
				nreports++
130
			} else {
131
				mut has_deprecation_tag := false
132
				for tag in undocumented_fn.tags {
133
					if tag.starts_with('deprecated') {
134
						has_deprecation_tag = true
135
						break
136
					}
137
				}
138
				if !has_deprecation_tag {
139
					println('${ofile}:${line_numbers}${undocumented_fn.signature} ${tags_str}')
140
					nreports++
141
				}
142
			}
143
		}
144
	}
145
	return nreports
146
}
147

148
fn (opt &Options) diff_undocumented_functions_in_paths(path_old string, path_new string) []UndocumentedFN {
149
	old := os.real_path(path_old)
150
	new := os.real_path(path_new)
151

152
	mut old_undocumented_functions := opt.collect_undocumented_functions_in_path(old)
153
	mut new_undocumented_functions := opt.collect_undocumented_functions_in_path(new)
154

155
	mut differs := []UndocumentedFN{}
156
	if new_undocumented_functions.len > old_undocumented_functions.len {
157
		for new_undoc_fn in new_undocumented_functions {
158
			new_relative_file := new_undoc_fn.file.replace(new, '').trim_string_left(os.path_separator)
159
			mut found := false
160
			for old_undoc_fn in old_undocumented_functions {
161
				old_relative_file := old_undoc_fn.file.replace(old, '').trim_string_left(os.path_separator)
162
				if new_relative_file == old_relative_file
163
					&& new_undoc_fn.signature == old_undoc_fn.signature {
164
					found = true
165
					break
166
				}
167
			}
168
			if !found {
169
				differs << new_undoc_fn
170
			}
171
		}
172
	}
173
	differs.sort_with_compare(sort_undoc_fns)
174
	return differs
175
}
176

177
fn sort_undoc_fns(a &UndocumentedFN, b &UndocumentedFN) int {
178
	if a.file < b.file {
179
		return -1
180
	}
181
	if a.file > b.file {
182
		return 1
183
	}
184
	// same file sort by signature
185
	else {
186
		if a.signature < b.signature {
187
			return -1
188
		}
189
		if a.signature > b.signature {
190
			return 1
191
		}
192
		return 0
193
	}
194
}
195

196
fn normalise_path(path string) string {
197
	return path.replace('\\', '/')
198
}
199

200
fn collect(path string, mut l []string, f fn (string, mut []string)) {
201
	if !os.is_dir(path) {
202
		return
203
	}
204
	mut files := os.ls(path) or { return }
205
	for file in files {
206
		p := normalise_path(os.join_path_single(path, file))
207
		if os.is_dir(p) && !os.is_link(p) {
208
			collect(p, mut l, f)
209
		} else if os.exists(p) {
210
			f(p, mut l)
211
		}
212
	}
213
	return
214
}
215

216
fn collect_tags(line string) []string {
217
	mut cleaned := line.all_before('/')
218
	cleaned = cleaned.replace_each(['@[', '', '[', '', ']', '', ' ', ''])
219
	return cleaned.split(',')
220
}
221

222
fn main() {
223
	mut fp := flag.new_flag_parser(os.args[1..]) // skip the "v" command.
224
	fp.application(tool_name)
225
	fp.version(tool_version)
226
	fp.description(tool_description)
227
	fp.arguments_description('PATH [PATH]...')
228
	fp.skip_executable() // skip the "missdoc" command.
229

230
	// Collect tool options
231
	mut opt := Options{
232
		show_help:       fp.bool('help', `h`, false, 'Show this help text.')
233
		deprecated:      fp.bool('deprecated', `d`, false, 'Include deprecated functions in output.')
234
		private:         fp.bool('private', `p`, false, 'Include private functions in output.')
235
		js:              fp.bool('js', 0, false, 'Include JavaScript functions in output.')
236
		no_line_numbers: fp.bool('no-line-numbers', `n`, false, 'Exclude line numbers in output.')
237
		collect_tags:    fp.bool('tags', `t`, false, 'Also print function tags if any is found.')
238
		exclude:         fp.string_multi('exclude', `e`, '')
239
		relative_paths:  fp.bool('relative-paths', `r`, false, 'Use relative paths in output.')
240
		diff:            fp.bool('diff', 0, false, 'exit(1) and show difference between two PATH inputs, return 0 otherwise.')
241
		verify:          fp.bool('verify', 0, false, 'exit(1) if documentation is missing, 0 otherwise.')
242
	}
243

244
	opt.additional_args = fp.finalize() or { panic(err) }
245

246
	if opt.show_help {
247
		println(fp.usage())
248
		exit(0)
249
	}
250
	if opt.additional_args.len == 0 {
251
		println(fp.usage())
252
		eprintln('Error: ${tool_name} is missing PATH input')
253
		exit(1)
254
	}
255
	// Allow short-long versions to prevent false positive situations, should
256
	// the user miss a `-`. E.g.: the `-verify` flag would be ignored and missdoc
257
	// will return 0 for success plus a list of any undocumented functions.
258
	if '-verify' in opt.additional_args {
259
		opt.verify = true
260
	}
261
	if '-diff' in opt.additional_args {
262
		opt.diff = true
263
	}
264
	if opt.diff {
265
		if opt.additional_args.len < 2 {
266
			println(fp.usage())
267
			eprintln('Error: ${tool_name} --diff needs two valid PATH inputs')
268
			exit(1)
269
		}
270
		path_old := opt.additional_args[0]
271
		path_new := opt.additional_args[1]
272
		if !(os.is_file(path_old) || os.is_dir(path_old)) || !(os.is_file(path_new)
273
			|| os.is_dir(path_new)) {
274
			println(fp.usage())
275
			eprintln('Error: ${tool_name} --diff needs two valid PATH inputs')
276
			exit(1)
277
		}
278
		list := opt.diff_undocumented_functions_in_paths(path_old, path_new)
279
		nreports := opt.report_undocumented_functions(list)
280
		if nreports > 0 {
281
			exit(1)
282
		}
283
		exit(0)
284
	}
285
	mut total := 0
286
	for path in opt.additional_args {
287
		if os.is_file(path) || os.is_dir(path) {
288
			total += opt.report_undocumented_functions_in_path(path)
289
		}
290
	}
291
	if opt.verify && total > 0 {
292
		exit(1)
293
	}
294
}
295

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.