yard-lint 1.6.1 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +73 -1
- data/README.md +52 -8
- data/bin/yard-lint +35 -4
- data/lib/yard/lint/config.rb +21 -4
- data/lib/yard/lint/config_loader.rb +31 -6
- data/lib/yard/lint/config_updater.rb +22 -1
- data/lib/yard/lint/config_validator.rb +2 -1
- data/lib/yard/lint/executor/in_process_registry.rb +58 -23
- data/lib/yard/lint/executor/warning_dispatcher.rb +1 -0
- data/lib/yard/lint/git.rb +44 -3
- data/lib/yard/lint/path_grouper.rb +4 -1
- data/lib/yard/lint/results/aggregate.rb +15 -7
- data/lib/yard/lint/stats_calculator.rb +8 -2
- data/lib/yard/lint/templates/default_config.yml +49 -1
- data/lib/yard/lint/templates/strict_config.yml +44 -1
- data/lib/yard/lint/todo_generator.rb +35 -14
- data/lib/yard/lint/validators/base.rb +50 -1
- data/lib/yard/lint/validators/documentation/blank_line_before_definition/validator.rb +17 -2
- data/lib/yard/lint/validators/documentation/empty_comment_line/validator.rb +5 -2
- data/lib/yard/lint/validators/documentation/line_length/validator.rb +1 -11
- data/lib/yard/lint/validators/documentation/markdown_syntax/validator.rb +66 -9
- data/lib/yard/lint/validators/documentation/missing_return/validator.rb +3 -3
- data/lib/yard/lint/validators/documentation/orphaned_doc_comment/validator.rb +79 -11
- data/lib/yard/lint/validators/documentation/orphaned_doc_comment.rb +4 -4
- data/lib/yard/lint/validators/documentation/text_substitution/validator.rb +10 -2
- data/lib/yard/lint/validators/documentation/underfilled_lines/config.rb +36 -0
- data/lib/yard/lint/validators/documentation/underfilled_lines/messages_builder.rb +31 -0
- data/lib/yard/lint/validators/documentation/underfilled_lines/parser.rb +64 -0
- data/lib/yard/lint/validators/documentation/underfilled_lines/result.rb +26 -0
- data/lib/yard/lint/validators/documentation/underfilled_lines/validator.rb +266 -0
- data/lib/yard/lint/validators/documentation/underfilled_lines.rb +74 -0
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/config.rb +10 -1
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/parser.rb +18 -4
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/validator.rb +38 -4
- data/lib/yard/lint/validators/documentation/undocumented_objects/parser.rb +6 -0
- data/lib/yard/lint/validators/documentation/undocumented_options/validator.rb +30 -0
- data/lib/yard/lint/validators/semantic/abstract_methods/result.rb +1 -0
- data/lib/yard/lint/validators/semantic/abstract_methods/validator.rb +43 -4
- data/lib/yard/lint/validators/tags/api_tags/validator.rb +11 -1
- data/lib/yard/lint/validators/tags/collection_type/messages_builder.rb +36 -5
- data/lib/yard/lint/validators/tags/collection_type/validator.rb +10 -6
- data/lib/yard/lint/validators/tags/example_style/validator.rb +1 -1
- data/lib/yard/lint/validators/tags/example_syntax/config.rb +6 -1
- data/lib/yard/lint/validators/tags/example_syntax/validator.rb +74 -3
- data/lib/yard/lint/validators/tags/forbidden_tags/validator.rb +7 -3
- data/lib/yard/lint/validators/tags/informal_notation/config.rb +6 -1
- data/lib/yard/lint/validators/tags/informal_notation/validator.rb +24 -3
- data/lib/yard/lint/validators/tags/invalid_types/config.rb +7 -1
- data/lib/yard/lint/validators/tags/invalid_types/parser.rb +22 -5
- data/lib/yard/lint/validators/tags/invalid_types/validator.rb +24 -10
- data/lib/yard/lint/validators/tags/missing_yield/validator.rb +44 -4
- data/lib/yard/lint/validators/tags/missing_yield.rb +8 -8
- data/lib/yard/lint/validators/tags/non_ascii_type/validator.rb +13 -1
- data/lib/yard/lint/validators/tags/option_tags/result.rb +1 -0
- data/lib/yard/lint/validators/tags/option_tags/validator.rb +30 -2
- data/lib/yard/lint/validators/tags/order/parser.rb +12 -5
- data/lib/yard/lint/validators/tags/order/validator.rb +7 -2
- data/lib/yard/lint/validators/tags/redundant_param_description/validator.rb +21 -8
- data/lib/yard/lint/validators/tags/tag_group_separator/parser.rb +12 -5
- data/lib/yard/lint/validators/tags/tag_group_separator/validator.rb +9 -7
- data/lib/yard/lint/validators/tags/tag_type_position/validator.rb +8 -2
- data/lib/yard/lint/validators/tags/type_syntax/validator.rb +1 -1
- data/lib/yard/lint/validators/warnings/invalid_directive_format/parser.rb +2 -2
- data/lib/yard/lint/validators/warnings/invalid_tag_format/parser.rb +2 -2
- data/lib/yard/lint/validators/warnings/syntax_error/config.rb +22 -0
- data/lib/yard/lint/validators/warnings/syntax_error/parser.rb +28 -0
- data/lib/yard/lint/validators/warnings/syntax_error/result.rb +27 -0
- data/lib/yard/lint/validators/warnings/syntax_error/validator.rb +15 -0
- data/lib/yard/lint/validators/warnings/syntax_error.rb +34 -0
- data/lib/yard/lint/validators/warnings/unknown_directive/parser.rb +2 -2
- data/lib/yard/lint/validators/warnings/unknown_parameter_name/messages_builder.rb +51 -46
- data/lib/yard/lint/validators/warnings/unknown_tag/messages_builder.rb +20 -3
- data/lib/yard/lint/validators/warnings/unknown_tag/parser.rb +2 -2
- data/lib/yard/lint/version.rb +1 -1
- data/lib/yard/lint.rb +4 -1
- metadata +12 -1
data/lib/yard/lint/git.rb
CHANGED
|
@@ -71,18 +71,33 @@ module Yard
|
|
|
71
71
|
def uncommitted_files(path)
|
|
72
72
|
ensure_git_repository!
|
|
73
73
|
|
|
74
|
-
# Get both staged and unstaged changes
|
|
74
|
+
# Get both staged and unstaged changes to tracked files
|
|
75
75
|
stdout, stderr, status = Open3.capture3('git', 'diff', '--name-only', 'HEAD')
|
|
76
76
|
|
|
77
77
|
unless status.success?
|
|
78
78
|
raise Error, "Git diff failed: #{stderr.strip}"
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
files = stdout.split("\n")
|
|
82
|
+
|
|
83
|
+
# Also include untracked (but not git-ignored) files - "all changes in
|
|
84
|
+
# the working directory" should cover newly added, not-yet-staged files.
|
|
85
|
+
others_stdout, _stderr, others_status =
|
|
86
|
+
Open3.capture3('git', 'ls-files', '--others', '--exclude-standard')
|
|
87
|
+
files += others_stdout.split("\n") if others_status.success?
|
|
88
|
+
|
|
89
|
+
filter_ruby_files(files.uniq, path)
|
|
82
90
|
end
|
|
83
91
|
|
|
84
92
|
private
|
|
85
93
|
|
|
94
|
+
# Absolute path to the repository root (git reports paths relative to it)
|
|
95
|
+
# @return [String] repo root, or the current directory if it can't be determined
|
|
96
|
+
def repository_root
|
|
97
|
+
stdout, _stderr, status = Open3.capture3('git', 'rev-parse', '--show-toplevel')
|
|
98
|
+
status.success? ? stdout.strip : Dir.pwd
|
|
99
|
+
end
|
|
100
|
+
|
|
86
101
|
# Ensure we're in a git repository
|
|
87
102
|
# @raise [Error] if not in a git repository
|
|
88
103
|
def ensure_git_repository!
|
|
@@ -99,14 +114,40 @@ module Yard
|
|
|
99
114
|
# @return [Array<String>] absolute paths to Ruby files that exist
|
|
100
115
|
def filter_ruby_files(files, path)
|
|
101
116
|
base_path = File.expand_path(path)
|
|
117
|
+
# git reports paths relative to the repository root, which is not
|
|
118
|
+
# necessarily the current working directory, so expand against the
|
|
119
|
+
# repo root - otherwise running from a subdirectory finds nothing.
|
|
120
|
+
root = repository_root
|
|
102
121
|
|
|
103
122
|
files
|
|
123
|
+
.map { |f| unquote_git_path(f) }
|
|
104
124
|
.select { |f| f.end_with?('.rb') }
|
|
105
|
-
.map { |f| File.expand_path(f) }
|
|
125
|
+
.map { |f| File.expand_path(f, root) }
|
|
106
126
|
.select { |f| File.exist?(f) } # Skip deleted files
|
|
107
127
|
.select { |f| file_within_path?(f, base_path) }
|
|
108
128
|
end
|
|
109
129
|
|
|
130
|
+
# Unquotes a path as git emits it with the default core.quotepath=true:
|
|
131
|
+
# paths containing non-ASCII (or special) bytes are wrapped in double
|
|
132
|
+
# quotes with C-style escapes (e.g. "caf\303\251.rb"). Without this such
|
|
133
|
+
# files end with `"` rather than `.rb` and are silently dropped.
|
|
134
|
+
# @param path [String] a path from git output
|
|
135
|
+
# @return [String] the unquoted path
|
|
136
|
+
def unquote_git_path(path)
|
|
137
|
+
return path unless path.start_with?('"') && path.end_with?('"')
|
|
138
|
+
|
|
139
|
+
inner = path[1..-2]
|
|
140
|
+
unescaped = inner.gsub(/\\(?:(\d{3})|(.))/) do
|
|
141
|
+
octal = Regexp.last_match(1)
|
|
142
|
+
if octal
|
|
143
|
+
octal.to_i(8).chr
|
|
144
|
+
else
|
|
145
|
+
{ 't' => "\t", 'n' => "\n", 'r' => "\r" }.fetch(Regexp.last_match(2), Regexp.last_match(2))
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
unescaped.force_encoding(Encoding::UTF_8)
|
|
149
|
+
end
|
|
150
|
+
|
|
110
151
|
# Check if file is within the specified path
|
|
111
152
|
# @param file [String] absolute file path
|
|
112
153
|
# @param base_path [String] absolute base path
|
|
@@ -33,7 +33,10 @@ module Yard
|
|
|
33
33
|
|
|
34
34
|
by_dir.each do |dir, dir_files|
|
|
35
35
|
if should_group_directory?(dir, dir_files.uniq, limit)
|
|
36
|
-
|
|
36
|
+
# For root-level files File.dirname is ".", and "./**/*" matches
|
|
37
|
+
# nothing under File.fnmatch (FNM_PATHNAME), so the generated todo
|
|
38
|
+
# file would fail to exclude them. Use a plain recursive glob.
|
|
39
|
+
result << (dir == '.' ? '**/*' : "#{dir}/**/*")
|
|
37
40
|
else
|
|
38
41
|
result.concat(dir_files.uniq)
|
|
39
42
|
end
|
|
@@ -26,9 +26,14 @@ module Yard
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
# Get all offenses from all validators
|
|
29
|
-
#
|
|
29
|
+
# Identical offenses are reported once: objects sharing one docstring
|
|
30
|
+
# (e.g. the reader and writer generated by attr_accessor) produce the
|
|
31
|
+
# same offense for each generated method
|
|
32
|
+
# @return [Array<Hash>] flattened array of unique offenses
|
|
30
33
|
def offenses
|
|
31
|
-
@results.flat_map(&:offenses)
|
|
34
|
+
@offenses ||= @results.flat_map(&:offenses).uniq do |offense|
|
|
35
|
+
offense.values_at(:validator, :name, :location, :location_line, :message)
|
|
36
|
+
end
|
|
32
37
|
end
|
|
33
38
|
|
|
34
39
|
# Total number of offenses
|
|
@@ -78,10 +83,11 @@ module Yard
|
|
|
78
83
|
# @return [Integer] 0 for success, 1 for failure
|
|
79
84
|
def exit_code
|
|
80
85
|
# Check minimum coverage requirement first
|
|
81
|
-
if @config&.min_coverage
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
if @config&.min_coverage
|
|
87
|
+
coverage = documentation_coverage
|
|
88
|
+
# Fail safe: if a minimum is required but coverage could not be
|
|
89
|
+
# determined (e.g. the YARD stats subprocess failed), do not pass.
|
|
90
|
+
return 1 if coverage.nil? || coverage[:coverage] < @config.min_coverage
|
|
85
91
|
end
|
|
86
92
|
|
|
87
93
|
return 0 if offenses.empty?
|
|
@@ -95,7 +101,9 @@ module Yard
|
|
|
95
101
|
when SEVERITY_WARNING
|
|
96
102
|
(statistics[:error] + statistics[:warning]).positive? ? 1 : 0
|
|
97
103
|
when SEVERITY_CONVENTION
|
|
98
|
-
offenses
|
|
104
|
+
# Exclude 'never'-severity offenses, which are meant to run without
|
|
105
|
+
# ever failing the build (they are also omitted from #statistics).
|
|
106
|
+
(statistics[:error] + statistics[:warning] + statistics[:convention]).positive? ? 1 : 0
|
|
99
107
|
else
|
|
100
108
|
0
|
|
101
109
|
end
|
|
@@ -20,6 +20,10 @@ module Yard
|
|
|
20
20
|
return default_stats if files.empty?
|
|
21
21
|
|
|
22
22
|
raw_stats = run_yard_stats_query
|
|
23
|
+
# nil means the YARD subprocess failed - return nil (coverage unknown)
|
|
24
|
+
# rather than default_stats, which would falsely report 100% coverage
|
|
25
|
+
# and let a MinCoverage gate pass.
|
|
26
|
+
return nil if raw_stats.nil?
|
|
23
27
|
return default_stats if raw_stats.empty?
|
|
24
28
|
|
|
25
29
|
parsed_stats = parse_stats_output(raw_stats)
|
|
@@ -52,8 +56,10 @@ module Yard
|
|
|
52
56
|
|
|
53
57
|
stdout, _stderr, status = Open3.capture3(cmd)
|
|
54
58
|
|
|
55
|
-
# Return
|
|
56
|
-
|
|
59
|
+
# Return nil if the YARD command fails (non-zero exit, or killed by
|
|
60
|
+
# a signal so exitstatus is nil) - distinct from genuinely empty
|
|
61
|
+
# output, so coverage is reported as unknown rather than 100%.
|
|
62
|
+
return nil unless status.exitstatus&.zero?
|
|
57
63
|
|
|
58
64
|
stdout
|
|
59
65
|
end
|
|
@@ -44,6 +44,12 @@ Documentation/UndocumentedMethodArguments:
|
|
|
44
44
|
Description: 'Checks for method parameters without @param tags.'
|
|
45
45
|
Enabled: true
|
|
46
46
|
Severity: warning
|
|
47
|
+
# Match each parameter to a @param tag by name (catches a misnamed @param).
|
|
48
|
+
# Set to false to fall back to a lenient count-only comparison.
|
|
49
|
+
CheckParameterNames: true
|
|
50
|
+
# Opt-in: skip methods with no documentation at all (reported by
|
|
51
|
+
# Documentation/UndocumentedObjects) instead of flagging them here too.
|
|
52
|
+
SkipFullyUndocumented: false
|
|
47
53
|
# AllowedParentClasses:
|
|
48
54
|
# - StandardError
|
|
49
55
|
# AllowedMethods: skip @param checks for specific methods (exact name, name/arity, /regex/)
|
|
@@ -108,6 +114,21 @@ Documentation/LineLength:
|
|
|
108
114
|
Severity: convention
|
|
109
115
|
MaxLength: 120
|
|
110
116
|
|
|
117
|
+
Documentation/UnderfilledLines:
|
|
118
|
+
Description: 'Detects documentation prose that wraps before using the available line width.'
|
|
119
|
+
Enabled: false # Opt-in validator (the inverse of LineLength)
|
|
120
|
+
Severity: convention
|
|
121
|
+
MaxLength: 120
|
|
122
|
+
# Only flag when the widest non-final line wastes at least this many columns.
|
|
123
|
+
MinTrailingSpace: 20
|
|
124
|
+
# Single-line descriptions are never flagged.
|
|
125
|
+
MinParagraphLines: 2
|
|
126
|
+
# A non-final line ending in one of these is a deliberate break (skipped).
|
|
127
|
+
# Add ',' to also respect comma breaks (suppresses more, catches less).
|
|
128
|
+
SentenceEndChars: ['.', '?', '!', ':', ';']
|
|
129
|
+
# Skip paragraphs with non-ASCII text (String#length is not display width).
|
|
130
|
+
SkipNonAscii: true
|
|
131
|
+
|
|
111
132
|
Documentation/TextSubstitution:
|
|
112
133
|
Description: 'Detects forbidden characters or strings in documentation and suggests replacements.'
|
|
113
134
|
Enabled: false # Opt-in validator
|
|
@@ -142,6 +163,13 @@ Tags/InvalidTypes:
|
|
|
142
163
|
- param
|
|
143
164
|
- option
|
|
144
165
|
- return
|
|
166
|
+
- yieldreturn
|
|
167
|
+
- yieldparam
|
|
168
|
+
- raise
|
|
169
|
+
# Opt-in: flag CamelCase type names that are neither loaded Ruby constants
|
|
170
|
+
# nor defined in the analyzed codebase (catches typos like `Strng`). Off by
|
|
171
|
+
# default because types from un-analyzed dependencies would also be flagged.
|
|
172
|
+
StrictConstantNames: false
|
|
145
173
|
|
|
146
174
|
Tags/TypeSyntax:
|
|
147
175
|
Description: 'Validates YARD type syntax using YARD parser.'
|
|
@@ -152,6 +180,8 @@ Tags/TypeSyntax:
|
|
|
152
180
|
- option
|
|
153
181
|
- return
|
|
154
182
|
- yieldreturn
|
|
183
|
+
- yieldparam
|
|
184
|
+
- raise
|
|
155
185
|
|
|
156
186
|
Tags/MeaninglessTag:
|
|
157
187
|
Description: 'Detects @param/@option tags on classes, modules, or constants.'
|
|
@@ -180,6 +210,8 @@ Tags/CollectionType:
|
|
|
180
210
|
- option
|
|
181
211
|
- return
|
|
182
212
|
- yieldreturn
|
|
213
|
+
- yieldparam
|
|
214
|
+
- raise
|
|
183
215
|
|
|
184
216
|
Tags/TagTypePosition:
|
|
185
217
|
Description: 'Validates type annotation position in tags.'
|
|
@@ -210,6 +242,10 @@ Tags/ExampleSyntax:
|
|
|
210
242
|
Description: 'Validates Ruby syntax in @example tags.'
|
|
211
243
|
Enabled: true
|
|
212
244
|
Severity: warning
|
|
245
|
+
# Opt-in: skip @example blocks that are interactive console transcripts
|
|
246
|
+
# (irb/pry sessions, their `=>` output, or shell `$` prompts) rather than
|
|
247
|
+
# runnable Ruby. Off by default so a real syntax error is not hidden.
|
|
248
|
+
SkipNonRuby: false
|
|
213
249
|
|
|
214
250
|
Tags/ExampleStyle:
|
|
215
251
|
Description: 'Validates code style in @example tags using RuboCop/StandardRB.'
|
|
@@ -258,14 +294,20 @@ Tags/InformalNotation:
|
|
|
258
294
|
Severity: warning
|
|
259
295
|
CaseSensitive: false
|
|
260
296
|
RequireStartOfLine: true
|
|
297
|
+
# Opt-in: also skip 4-space/tab indented Markdown code blocks (not just
|
|
298
|
+
# fenced ``` blocks). Off by default - indented text is also used for list
|
|
299
|
+
# continuations and wrapped prose, which would then be skipped too.
|
|
300
|
+
SkipIndentedCodeBlocks: false
|
|
261
301
|
Patterns:
|
|
262
302
|
Note: '@note'
|
|
303
|
+
IMPORTANT: '@note'
|
|
304
|
+
Important: '@note'
|
|
263
305
|
Todo: '@todo'
|
|
264
306
|
TODO: '@todo'
|
|
265
307
|
FIXME: '@todo'
|
|
266
308
|
See: '@see'
|
|
267
309
|
See also: '@see'
|
|
268
|
-
Warning: '@
|
|
310
|
+
Warning: '@note'
|
|
269
311
|
Deprecated: '@deprecated'
|
|
270
312
|
Author: '@author'
|
|
271
313
|
Version: '@version'
|
|
@@ -284,6 +326,7 @@ Tags/NonAsciiType:
|
|
|
284
326
|
- return
|
|
285
327
|
- yieldreturn
|
|
286
328
|
- yieldparam
|
|
329
|
+
- raise
|
|
287
330
|
|
|
288
331
|
Tags/TagGroupSeparator:
|
|
289
332
|
Description: 'Enforces blank line separators between different YARD tag groups.'
|
|
@@ -313,6 +356,11 @@ Tags/ForbiddenTags:
|
|
|
313
356
|
# - Tag: api # Forbids @api tag entirely (no Types = any occurrence)
|
|
314
357
|
|
|
315
358
|
# Warnings validators - catches YARD parser errors
|
|
359
|
+
Warnings/SyntaxError:
|
|
360
|
+
Description: 'Detects Ruby files YARD cannot parse (syntax errors).'
|
|
361
|
+
Enabled: true
|
|
362
|
+
Severity: error
|
|
363
|
+
|
|
316
364
|
Warnings/UnknownTag:
|
|
317
365
|
Description: 'Detects unknown YARD tags.'
|
|
318
366
|
Enabled: true
|
|
@@ -48,6 +48,11 @@ Documentation/UndocumentedMethodArguments:
|
|
|
48
48
|
Description: 'Checks for method parameters without @param tags.'
|
|
49
49
|
Enabled: true
|
|
50
50
|
Severity: error
|
|
51
|
+
# Match each parameter to a @param tag by name (catches a misnamed @param).
|
|
52
|
+
CheckParameterNames: true
|
|
53
|
+
# Opt-in: skip methods with no documentation at all (reported by
|
|
54
|
+
# Documentation/UndocumentedObjects) instead of flagging them here too.
|
|
55
|
+
SkipFullyUndocumented: false
|
|
51
56
|
# AllowedParentClasses:
|
|
52
57
|
# - StandardError
|
|
53
58
|
# AllowedMethods: skip @param checks for specific methods (exact name, name/arity, /regex/)
|
|
@@ -112,6 +117,16 @@ Documentation/LineLength:
|
|
|
112
117
|
Severity: error
|
|
113
118
|
MaxLength: 120
|
|
114
119
|
|
|
120
|
+
Documentation/UnderfilledLines:
|
|
121
|
+
Description: 'Detects documentation prose that wraps before using the available line width.'
|
|
122
|
+
Enabled: false # Opt-in validator (heuristic; stays convention even in strict)
|
|
123
|
+
Severity: convention
|
|
124
|
+
MaxLength: 120
|
|
125
|
+
MinTrailingSpace: 20
|
|
126
|
+
MinParagraphLines: 2
|
|
127
|
+
SentenceEndChars: ['.', '?', '!', ':', ';']
|
|
128
|
+
SkipNonAscii: true
|
|
129
|
+
|
|
115
130
|
Documentation/TextSubstitution:
|
|
116
131
|
Description: 'Detects forbidden characters or strings in documentation and suggests replacements.'
|
|
117
132
|
Enabled: true
|
|
@@ -146,6 +161,13 @@ Tags/InvalidTypes:
|
|
|
146
161
|
- param
|
|
147
162
|
- option
|
|
148
163
|
- return
|
|
164
|
+
- yieldreturn
|
|
165
|
+
- yieldparam
|
|
166
|
+
- raise
|
|
167
|
+
# The strict preset opts in: flag CamelCase type names that are neither loaded
|
|
168
|
+
# Ruby constants nor defined in the analyzed codebase (catches typos). Add any
|
|
169
|
+
# legitimate dependency types to ExtraTypes to silence false positives.
|
|
170
|
+
StrictConstantNames: true
|
|
149
171
|
|
|
150
172
|
Tags/TypeSyntax:
|
|
151
173
|
Description: 'Validates YARD type syntax using YARD parser.'
|
|
@@ -156,6 +178,8 @@ Tags/TypeSyntax:
|
|
|
156
178
|
- option
|
|
157
179
|
- return
|
|
158
180
|
- yieldreturn
|
|
181
|
+
- yieldparam
|
|
182
|
+
- raise
|
|
159
183
|
|
|
160
184
|
Tags/MeaninglessTag:
|
|
161
185
|
Description: 'Detects @param/@option tags on classes, modules, or constants.'
|
|
@@ -184,6 +208,8 @@ Tags/CollectionType:
|
|
|
184
208
|
- option
|
|
185
209
|
- return
|
|
186
210
|
- yieldreturn
|
|
211
|
+
- yieldparam
|
|
212
|
+
- raise
|
|
187
213
|
|
|
188
214
|
Tags/TagTypePosition:
|
|
189
215
|
Description: 'Validates type annotation position in tags.'
|
|
@@ -214,6 +240,10 @@ Tags/ExampleSyntax:
|
|
|
214
240
|
Description: 'Validates Ruby syntax in @example tags.'
|
|
215
241
|
Enabled: true
|
|
216
242
|
Severity: error
|
|
243
|
+
# Opt-in: skip @example blocks that are interactive console transcripts
|
|
244
|
+
# (irb/pry sessions, their `=>` output, or shell `$` prompts) rather than
|
|
245
|
+
# runnable Ruby. Off by default so a real syntax error is not hidden.
|
|
246
|
+
SkipNonRuby: false
|
|
217
247
|
|
|
218
248
|
Tags/ExampleStyle:
|
|
219
249
|
Description: 'Validates code style in @example tags using RuboCop/StandardRB.'
|
|
@@ -254,6 +284,7 @@ Tags/RedundantParamDescription:
|
|
|
254
284
|
IdPattern: true
|
|
255
285
|
DirectionalDate: true
|
|
256
286
|
TypeGeneric: true
|
|
287
|
+
ArticleParamPhrase: true
|
|
257
288
|
|
|
258
289
|
Tags/InformalNotation:
|
|
259
290
|
Description: 'Detects informal tag notation patterns like "Note:" instead of @note.'
|
|
@@ -261,14 +292,20 @@ Tags/InformalNotation:
|
|
|
261
292
|
Severity: error
|
|
262
293
|
CaseSensitive: false
|
|
263
294
|
RequireStartOfLine: true
|
|
295
|
+
# Opt-in: also skip 4-space/tab indented Markdown code blocks (not just
|
|
296
|
+
# fenced ``` blocks). Off by default - indented text is also used for list
|
|
297
|
+
# continuations and wrapped prose, which would then be skipped too.
|
|
298
|
+
SkipIndentedCodeBlocks: false
|
|
264
299
|
Patterns:
|
|
265
300
|
Note: '@note'
|
|
301
|
+
IMPORTANT: '@note'
|
|
302
|
+
Important: '@note'
|
|
266
303
|
Todo: '@todo'
|
|
267
304
|
TODO: '@todo'
|
|
268
305
|
FIXME: '@todo'
|
|
269
306
|
See: '@see'
|
|
270
307
|
See also: '@see'
|
|
271
|
-
Warning: '@
|
|
308
|
+
Warning: '@note'
|
|
272
309
|
Deprecated: '@deprecated'
|
|
273
310
|
Author: '@author'
|
|
274
311
|
Version: '@version'
|
|
@@ -287,6 +324,7 @@ Tags/NonAsciiType:
|
|
|
287
324
|
- return
|
|
288
325
|
- yieldreturn
|
|
289
326
|
- yieldparam
|
|
327
|
+
- raise
|
|
290
328
|
|
|
291
329
|
Tags/TagGroupSeparator:
|
|
292
330
|
Description: 'Enforces blank line separators between different YARD tag groups.'
|
|
@@ -316,6 +354,11 @@ Tags/ForbiddenTags:
|
|
|
316
354
|
# - Tag: api # Forbids @api tag entirely (no Types = any occurrence)
|
|
317
355
|
|
|
318
356
|
# Warnings validators - catches YARD parser errors
|
|
357
|
+
Warnings/SyntaxError:
|
|
358
|
+
Description: 'Detects Ruby files YARD cannot parse (syntax errors).'
|
|
359
|
+
Enabled: true
|
|
360
|
+
Severity: error
|
|
361
|
+
|
|
319
362
|
Warnings/UnknownTag:
|
|
320
363
|
Description: 'Detects unknown YARD tags.'
|
|
321
364
|
Enabled: true
|
|
@@ -13,9 +13,12 @@ module Yard
|
|
|
13
13
|
# @param config [Config] yard-lint configuration object with validator settings
|
|
14
14
|
# @param force [Boolean] whether to overwrite existing todo file if present
|
|
15
15
|
# @param exclude_limit [Integer] minimum files in directory before grouping into wildcard patterns
|
|
16
|
+
# @param config_path [String, nil] config file to link the todo into
|
|
17
|
+
# (defaults to .yard-lint.yml in the current directory)
|
|
16
18
|
# @return [Hash] result with :message, :offense_count, :validator_count
|
|
17
|
-
def generate(path:, config:, force: false, exclude_limit: DEFAULT_EXCLUDE_LIMIT)
|
|
18
|
-
new(path: path, config: config, force: force, exclude_limit: exclude_limit
|
|
19
|
+
def generate(path:, config:, force: false, exclude_limit: DEFAULT_EXCLUDE_LIMIT, config_path: nil)
|
|
20
|
+
new(path: path, config: config, force: force, exclude_limit: exclude_limit,
|
|
21
|
+
config_path: config_path).generate
|
|
19
22
|
end
|
|
20
23
|
end
|
|
21
24
|
|
|
@@ -24,13 +27,14 @@ module Yard
|
|
|
24
27
|
# @param config [Config] yard-lint configuration object
|
|
25
28
|
# @param force [Boolean] whether to overwrite existing todo file
|
|
26
29
|
# @param exclude_limit [Integer] minimum files before grouping into patterns
|
|
27
|
-
|
|
30
|
+
# @param config_path [String, nil] config file to link the todo into
|
|
31
|
+
def initialize(path:, config:, force:, exclude_limit:, config_path: nil)
|
|
28
32
|
@path = path
|
|
29
33
|
@config = config
|
|
30
34
|
@force = force
|
|
31
35
|
@exclude_limit = exclude_limit
|
|
32
36
|
@todo_path = File.join(Dir.pwd, '.yard-lint-todo.yml')
|
|
33
|
-
@config_path = File.join(Dir.pwd, Config::DEFAULT_CONFIG_FILE)
|
|
37
|
+
@config_path = config_path || File.join(Dir.pwd, Config::DEFAULT_CONFIG_FILE)
|
|
34
38
|
end
|
|
35
39
|
|
|
36
40
|
# Generate the .yard-lint-todo.yml file with exclusions for current violations
|
|
@@ -93,6 +97,12 @@ module Yard
|
|
|
93
97
|
next unless @config.validator_enabled?(validator_name)
|
|
94
98
|
|
|
95
99
|
validator_result = result_builder.build(validator_name, raw_results)
|
|
100
|
+
next unless validator_result
|
|
101
|
+
|
|
102
|
+
# Apply per-validator Exclude patterns (and drop no-location offenses)
|
|
103
|
+
# exactly as a normal run does, so the baseline does not silence files
|
|
104
|
+
# that are already excluded.
|
|
105
|
+
validator_result = runner.send(:filter_result_offenses, validator_name, validator_result)
|
|
96
106
|
next unless validator_result && validator_result.offenses.any?
|
|
97
107
|
|
|
98
108
|
# Extract file paths from offenses, skipping those without a location
|
|
@@ -206,17 +216,28 @@ module Yard
|
|
|
206
216
|
# Update existing config file to add inherit_from todo file
|
|
207
217
|
# @return [void]
|
|
208
218
|
def update_existing_config
|
|
209
|
-
config_yaml =
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
219
|
+
config_yaml = ConfigLoader.load_yaml_file(@config_path)
|
|
220
|
+
return if Array(config_yaml['inherit_from']).include?('.yard-lint-todo.yml')
|
|
221
|
+
|
|
222
|
+
raw = File.read(@config_path)
|
|
223
|
+
|
|
224
|
+
# Edit the file textually rather than round-tripping through to_yaml,
|
|
225
|
+
# which would strip every comment and reformat the user's config.
|
|
226
|
+
updated =
|
|
227
|
+
if config_yaml.key?('inherit_from') && raw.match?(/^inherit_from:[ \t]*$/)
|
|
228
|
+
# Existing block list - prepend our entry as the first item.
|
|
229
|
+
raw.sub(/^inherit_from:[ \t]*$\n/) { "#{Regexp.last_match(0)} - .yard-lint-todo.yml\n" }
|
|
230
|
+
elsif config_yaml.key?('inherit_from')
|
|
231
|
+
# Existing inline/array form (rare) - fall back to a structured
|
|
232
|
+
# rewrite; comments cannot be preserved in this case.
|
|
233
|
+
config_yaml['inherit_from'] = ['.yard-lint-todo.yml'] + Array(config_yaml['inherit_from'])
|
|
234
|
+
config_yaml.to_yaml
|
|
235
|
+
else
|
|
236
|
+
# No inherit_from yet - prepend a block, preserving the file verbatim.
|
|
237
|
+
"inherit_from:\n - .yard-lint-todo.yml\n\n#{raw}"
|
|
238
|
+
end
|
|
216
239
|
|
|
217
|
-
|
|
218
|
-
File.write(@config_path, config_yaml.to_yaml)
|
|
219
|
-
end
|
|
240
|
+
File.write(@config_path, updated)
|
|
220
241
|
end
|
|
221
242
|
|
|
222
243
|
# Create a minimal config file that inherits from todo file
|
|
@@ -97,6 +97,52 @@ module Yard
|
|
|
97
97
|
tags
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
+
# Tracks docstring locations already processed by this validator
|
|
101
|
+
# instance. Objects generated from a single comment block (e.g. the
|
|
102
|
+
# reader and writer created by attr_accessor) share one docstring;
|
|
103
|
+
# content-scanning validators use this to report each docstring once.
|
|
104
|
+
# @param object [YARD::CodeObjects::Base] the code object to check
|
|
105
|
+
# @return [Boolean] true if this object's docstring was already seen
|
|
106
|
+
def duplicate_docstring?(object)
|
|
107
|
+
@scanned_docstrings ||= Set.new
|
|
108
|
+
key = [object.file, object.docstring.line_range&.first || object.line]
|
|
109
|
+
|
|
110
|
+
!@scanned_docstrings.add?(key)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Converts a zero-based line offset within a docstring's text into an
|
|
114
|
+
# absolute line number in the source file, so offenses can point at
|
|
115
|
+
# the offending documentation line instead of the definition line.
|
|
116
|
+
# @param object [YARD::CodeObjects::Base] the documented object
|
|
117
|
+
# @param line_offset [Integer] zero-based offset within the docstring text
|
|
118
|
+
# @return [Integer] absolute source line number
|
|
119
|
+
def docstring_line(object, line_offset)
|
|
120
|
+
start_line = object.docstring.line_range&.first || object.line
|
|
121
|
+
|
|
122
|
+
start_line + line_offset
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Returns the lines of a source file, reading from disk only on the first
|
|
126
|
+
# call for each unique path. Invalid bytes are scrubbed so that callers
|
|
127
|
+
# matching regexes against the lines never raise Encoding::CompatibilityError
|
|
128
|
+
# on a non-UTF-8 source file.
|
|
129
|
+
# @param file [String] absolute path to the source file
|
|
130
|
+
# @return [Array<String>] lines of the file, memoized per path
|
|
131
|
+
def cached_lines(file)
|
|
132
|
+
@file_cache ||= {}
|
|
133
|
+
@file_cache[file] ||= File.readlines(file).map!(&:scrub)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Returns the tag that actually carries a tag's types and description.
|
|
137
|
+
# For most tags that is the tag itself, but @option tags wrap their
|
|
138
|
+
# data in a nested pair tag - tag.types and tag.text are nil on the
|
|
139
|
+
# OptionTag itself, with the documented option living on tag.pair.
|
|
140
|
+
# @param tag [YARD::Tags::Tag] tag whose data holder should be resolved
|
|
141
|
+
# @return [YARD::Tags::Tag] the tag holding types/text data
|
|
142
|
+
def tag_data(tag)
|
|
143
|
+
tag.respond_to?(:pair) && tag.pair ? tag.pair : tag
|
|
144
|
+
end
|
|
145
|
+
|
|
100
146
|
# Checks whether the object's enclosing class (or the object itself if it is
|
|
101
147
|
# a class) has a superclass that appears in the validator's AllowedParentClasses
|
|
102
148
|
# configuration list. When true, validators skip the object so that classes
|
|
@@ -236,7 +282,10 @@ module Yard
|
|
|
236
282
|
|
|
237
283
|
return defaults[key] unless validator_name
|
|
238
284
|
|
|
239
|
-
config.validator_config(validator_name, key)
|
|
285
|
+
value = config.validator_config(validator_name, key)
|
|
286
|
+
# A nil? check (not ||) so that explicitly configured false values
|
|
287
|
+
# are honored instead of falling back to a truthy default
|
|
288
|
+
value.nil? ? defaults[key] : value
|
|
240
289
|
end
|
|
241
290
|
end
|
|
242
291
|
end
|
|
@@ -50,8 +50,11 @@ module Yard
|
|
|
50
50
|
if stripped.empty?
|
|
51
51
|
blank_count += 1
|
|
52
52
|
elsif stripped.start_with?('#')
|
|
53
|
-
# Skip
|
|
54
|
-
|
|
53
|
+
# Skip lines that are not YARD documentation: magic comments,
|
|
54
|
+
# shebangs, tool sigils/directives (Sorbet, RuboCop, Standard),
|
|
55
|
+
# and bare `#` separators. Treating them as a doc block caused
|
|
56
|
+
# spurious blank-line offenses for undocumented definitions.
|
|
57
|
+
next if non_documentation_comment?(stripped)
|
|
55
58
|
|
|
56
59
|
has_doc_block = true
|
|
57
60
|
break
|
|
@@ -72,6 +75,18 @@ module Yard
|
|
|
72
75
|
line.match?(/^#\s*(frozen[_-]string[_-]literal|encoding|warn[_-]indent|shareable[_-]constant[_-]value)\s*:/i)
|
|
73
76
|
end
|
|
74
77
|
|
|
78
|
+
# Check if a comment line is not YARD documentation (magic comment,
|
|
79
|
+
# shebang, tool sigil/directive, or a bare `#` separator).
|
|
80
|
+
# @param line [String] stripped comment line
|
|
81
|
+
# @return [Boolean] true if the line should not count as documentation
|
|
82
|
+
def non_documentation_comment?(line)
|
|
83
|
+
magic_comment?(line) ||
|
|
84
|
+
line.start_with?('#!') || # shebang
|
|
85
|
+
line.match?(/\A#\s*(rubocop|standard):/i) || # linter directives
|
|
86
|
+
line.match?(/\A#\s*typed:/i) || # Sorbet sigil
|
|
87
|
+
line.match?(/\A#+\s*\z/) # bare # separator
|
|
88
|
+
end
|
|
89
|
+
|
|
75
90
|
# Check if the given pattern is enabled in configuration
|
|
76
91
|
# @param violation_type [String] 'single' or 'orphaned'
|
|
77
92
|
# @return [Boolean] whether the pattern is enabled
|
|
@@ -33,8 +33,11 @@ module Yard
|
|
|
33
33
|
stripped = line.strip
|
|
34
34
|
|
|
35
35
|
if stripped.empty? && comment_end.nil?
|
|
36
|
-
#
|
|
37
|
-
|
|
36
|
+
# A blank line between the definition and any comment above
|
|
37
|
+
# means there is no attached docstring (YARD only attaches a
|
|
38
|
+
# comment immediately above the definition), so stop scanning -
|
|
39
|
+
# otherwise a detached file header is mistaken for the doc.
|
|
40
|
+
break
|
|
38
41
|
elsif stripped.start_with?('#')
|
|
39
42
|
comment_end ||= i
|
|
40
43
|
comment_start = i
|
|
@@ -23,6 +23,7 @@ module Yard
|
|
|
23
23
|
|
|
24
24
|
line_range = object.docstring.line_range
|
|
25
25
|
return unless line_range
|
|
26
|
+
return if duplicate_docstring?(object)
|
|
26
27
|
|
|
27
28
|
max_length = config_or_default('MaxLength').to_i
|
|
28
29
|
source_lines = cached_lines(object.file)
|
|
@@ -40,17 +41,6 @@ module Yard
|
|
|
40
41
|
collector.puts "#{object.file}:#{object.line}: #{object.title}"
|
|
41
42
|
collector.puts "#{max_length}|#{violations.join('|')}"
|
|
42
43
|
end
|
|
43
|
-
|
|
44
|
-
private
|
|
45
|
-
|
|
46
|
-
# Returns the lines of a source file, reading from disk only on the first call
|
|
47
|
-
# for each unique path.
|
|
48
|
-
# @param file [String] absolute path to the source file
|
|
49
|
-
# @return [Array<String>] lines of the file, memoized per path
|
|
50
|
-
def cached_lines(file)
|
|
51
|
-
@file_cache ||= {}
|
|
52
|
-
@file_cache[file] ||= File.readlines(file)
|
|
53
|
-
end
|
|
54
44
|
end
|
|
55
45
|
end
|
|
56
46
|
end
|