yard-lint 1.6.1 → 1.7.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 +69 -1
- data/README.md +4 -3
- 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 +34 -1
- data/lib/yard/lint/templates/strict_config.yml +34 -1
- data/lib/yard/lint/todo_generator.rb +35 -14
- data/lib/yard/lint/validators/base.rb +39 -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 -0
- 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/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 +6 -1
|
@@ -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/)
|
|
@@ -142,6 +148,13 @@ Tags/InvalidTypes:
|
|
|
142
148
|
- param
|
|
143
149
|
- option
|
|
144
150
|
- return
|
|
151
|
+
- yieldreturn
|
|
152
|
+
- yieldparam
|
|
153
|
+
- raise
|
|
154
|
+
# Opt-in: flag CamelCase type names that are neither loaded Ruby constants
|
|
155
|
+
# nor defined in the analyzed codebase (catches typos like `Strng`). Off by
|
|
156
|
+
# default because types from un-analyzed dependencies would also be flagged.
|
|
157
|
+
StrictConstantNames: false
|
|
145
158
|
|
|
146
159
|
Tags/TypeSyntax:
|
|
147
160
|
Description: 'Validates YARD type syntax using YARD parser.'
|
|
@@ -152,6 +165,8 @@ Tags/TypeSyntax:
|
|
|
152
165
|
- option
|
|
153
166
|
- return
|
|
154
167
|
- yieldreturn
|
|
168
|
+
- yieldparam
|
|
169
|
+
- raise
|
|
155
170
|
|
|
156
171
|
Tags/MeaninglessTag:
|
|
157
172
|
Description: 'Detects @param/@option tags on classes, modules, or constants.'
|
|
@@ -180,6 +195,8 @@ Tags/CollectionType:
|
|
|
180
195
|
- option
|
|
181
196
|
- return
|
|
182
197
|
- yieldreturn
|
|
198
|
+
- yieldparam
|
|
199
|
+
- raise
|
|
183
200
|
|
|
184
201
|
Tags/TagTypePosition:
|
|
185
202
|
Description: 'Validates type annotation position in tags.'
|
|
@@ -210,6 +227,10 @@ Tags/ExampleSyntax:
|
|
|
210
227
|
Description: 'Validates Ruby syntax in @example tags.'
|
|
211
228
|
Enabled: true
|
|
212
229
|
Severity: warning
|
|
230
|
+
# Opt-in: skip @example blocks that are interactive console transcripts
|
|
231
|
+
# (irb/pry sessions, their `=>` output, or shell `$` prompts) rather than
|
|
232
|
+
# runnable Ruby. Off by default so a real syntax error is not hidden.
|
|
233
|
+
SkipNonRuby: false
|
|
213
234
|
|
|
214
235
|
Tags/ExampleStyle:
|
|
215
236
|
Description: 'Validates code style in @example tags using RuboCop/StandardRB.'
|
|
@@ -258,14 +279,20 @@ Tags/InformalNotation:
|
|
|
258
279
|
Severity: warning
|
|
259
280
|
CaseSensitive: false
|
|
260
281
|
RequireStartOfLine: true
|
|
282
|
+
# Opt-in: also skip 4-space/tab indented Markdown code blocks (not just
|
|
283
|
+
# fenced ``` blocks). Off by default - indented text is also used for list
|
|
284
|
+
# continuations and wrapped prose, which would then be skipped too.
|
|
285
|
+
SkipIndentedCodeBlocks: false
|
|
261
286
|
Patterns:
|
|
262
287
|
Note: '@note'
|
|
288
|
+
IMPORTANT: '@note'
|
|
289
|
+
Important: '@note'
|
|
263
290
|
Todo: '@todo'
|
|
264
291
|
TODO: '@todo'
|
|
265
292
|
FIXME: '@todo'
|
|
266
293
|
See: '@see'
|
|
267
294
|
See also: '@see'
|
|
268
|
-
Warning: '@
|
|
295
|
+
Warning: '@note'
|
|
269
296
|
Deprecated: '@deprecated'
|
|
270
297
|
Author: '@author'
|
|
271
298
|
Version: '@version'
|
|
@@ -284,6 +311,7 @@ Tags/NonAsciiType:
|
|
|
284
311
|
- return
|
|
285
312
|
- yieldreturn
|
|
286
313
|
- yieldparam
|
|
314
|
+
- raise
|
|
287
315
|
|
|
288
316
|
Tags/TagGroupSeparator:
|
|
289
317
|
Description: 'Enforces blank line separators between different YARD tag groups.'
|
|
@@ -313,6 +341,11 @@ Tags/ForbiddenTags:
|
|
|
313
341
|
# - Tag: api # Forbids @api tag entirely (no Types = any occurrence)
|
|
314
342
|
|
|
315
343
|
# Warnings validators - catches YARD parser errors
|
|
344
|
+
Warnings/SyntaxError:
|
|
345
|
+
Description: 'Detects Ruby files YARD cannot parse (syntax errors).'
|
|
346
|
+
Enabled: true
|
|
347
|
+
Severity: error
|
|
348
|
+
|
|
316
349
|
Warnings/UnknownTag:
|
|
317
350
|
Description: 'Detects unknown YARD tags.'
|
|
318
351
|
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/)
|
|
@@ -146,6 +151,13 @@ Tags/InvalidTypes:
|
|
|
146
151
|
- param
|
|
147
152
|
- option
|
|
148
153
|
- return
|
|
154
|
+
- yieldreturn
|
|
155
|
+
- yieldparam
|
|
156
|
+
- raise
|
|
157
|
+
# The strict preset opts in: flag CamelCase type names that are neither loaded
|
|
158
|
+
# Ruby constants nor defined in the analyzed codebase (catches typos). Add any
|
|
159
|
+
# legitimate dependency types to ExtraTypes to silence false positives.
|
|
160
|
+
StrictConstantNames: true
|
|
149
161
|
|
|
150
162
|
Tags/TypeSyntax:
|
|
151
163
|
Description: 'Validates YARD type syntax using YARD parser.'
|
|
@@ -156,6 +168,8 @@ Tags/TypeSyntax:
|
|
|
156
168
|
- option
|
|
157
169
|
- return
|
|
158
170
|
- yieldreturn
|
|
171
|
+
- yieldparam
|
|
172
|
+
- raise
|
|
159
173
|
|
|
160
174
|
Tags/MeaninglessTag:
|
|
161
175
|
Description: 'Detects @param/@option tags on classes, modules, or constants.'
|
|
@@ -184,6 +198,8 @@ Tags/CollectionType:
|
|
|
184
198
|
- option
|
|
185
199
|
- return
|
|
186
200
|
- yieldreturn
|
|
201
|
+
- yieldparam
|
|
202
|
+
- raise
|
|
187
203
|
|
|
188
204
|
Tags/TagTypePosition:
|
|
189
205
|
Description: 'Validates type annotation position in tags.'
|
|
@@ -214,6 +230,10 @@ Tags/ExampleSyntax:
|
|
|
214
230
|
Description: 'Validates Ruby syntax in @example tags.'
|
|
215
231
|
Enabled: true
|
|
216
232
|
Severity: error
|
|
233
|
+
# Opt-in: skip @example blocks that are interactive console transcripts
|
|
234
|
+
# (irb/pry sessions, their `=>` output, or shell `$` prompts) rather than
|
|
235
|
+
# runnable Ruby. Off by default so a real syntax error is not hidden.
|
|
236
|
+
SkipNonRuby: false
|
|
217
237
|
|
|
218
238
|
Tags/ExampleStyle:
|
|
219
239
|
Description: 'Validates code style in @example tags using RuboCop/StandardRB.'
|
|
@@ -254,6 +274,7 @@ Tags/RedundantParamDescription:
|
|
|
254
274
|
IdPattern: true
|
|
255
275
|
DirectionalDate: true
|
|
256
276
|
TypeGeneric: true
|
|
277
|
+
ArticleParamPhrase: true
|
|
257
278
|
|
|
258
279
|
Tags/InformalNotation:
|
|
259
280
|
Description: 'Detects informal tag notation patterns like "Note:" instead of @note.'
|
|
@@ -261,14 +282,20 @@ Tags/InformalNotation:
|
|
|
261
282
|
Severity: error
|
|
262
283
|
CaseSensitive: false
|
|
263
284
|
RequireStartOfLine: true
|
|
285
|
+
# Opt-in: also skip 4-space/tab indented Markdown code blocks (not just
|
|
286
|
+
# fenced ``` blocks). Off by default - indented text is also used for list
|
|
287
|
+
# continuations and wrapped prose, which would then be skipped too.
|
|
288
|
+
SkipIndentedCodeBlocks: false
|
|
264
289
|
Patterns:
|
|
265
290
|
Note: '@note'
|
|
291
|
+
IMPORTANT: '@note'
|
|
292
|
+
Important: '@note'
|
|
266
293
|
Todo: '@todo'
|
|
267
294
|
TODO: '@todo'
|
|
268
295
|
FIXME: '@todo'
|
|
269
296
|
See: '@see'
|
|
270
297
|
See also: '@see'
|
|
271
|
-
Warning: '@
|
|
298
|
+
Warning: '@note'
|
|
272
299
|
Deprecated: '@deprecated'
|
|
273
300
|
Author: '@author'
|
|
274
301
|
Version: '@version'
|
|
@@ -287,6 +314,7 @@ Tags/NonAsciiType:
|
|
|
287
314
|
- return
|
|
288
315
|
- yieldreturn
|
|
289
316
|
- yieldparam
|
|
317
|
+
- raise
|
|
290
318
|
|
|
291
319
|
Tags/TagGroupSeparator:
|
|
292
320
|
Description: 'Enforces blank line separators between different YARD tag groups.'
|
|
@@ -316,6 +344,11 @@ Tags/ForbiddenTags:
|
|
|
316
344
|
# - Tag: api # Forbids @api tag entirely (no Types = any occurrence)
|
|
317
345
|
|
|
318
346
|
# Warnings validators - catches YARD parser errors
|
|
347
|
+
Warnings/SyntaxError:
|
|
348
|
+
Description: 'Detects Ruby files YARD cannot parse (syntax errors).'
|
|
349
|
+
Enabled: true
|
|
350
|
+
Severity: error
|
|
351
|
+
|
|
319
352
|
Warnings/UnknownTag:
|
|
320
353
|
Description: 'Detects unknown YARD tags.'
|
|
321
354
|
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,41 @@ 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 tag that actually carries a tag's types and description.
|
|
126
|
+
# For most tags that is the tag itself, but @option tags wrap their
|
|
127
|
+
# data in a nested pair tag - tag.types and tag.text are nil on the
|
|
128
|
+
# OptionTag itself, with the documented option living on tag.pair.
|
|
129
|
+
# @param tag [YARD::Tags::Tag] tag whose data holder should be resolved
|
|
130
|
+
# @return [YARD::Tags::Tag] the tag holding types/text data
|
|
131
|
+
def tag_data(tag)
|
|
132
|
+
tag.respond_to?(:pair) && tag.pair ? tag.pair : tag
|
|
133
|
+
end
|
|
134
|
+
|
|
100
135
|
# Checks whether the object's enclosing class (or the object itself if it is
|
|
101
136
|
# a class) has a superclass that appears in the validator's AllowedParentClasses
|
|
102
137
|
# configuration list. When true, validators skip the object so that classes
|
|
@@ -236,7 +271,10 @@ module Yard
|
|
|
236
271
|
|
|
237
272
|
return defaults[key] unless validator_name
|
|
238
273
|
|
|
239
|
-
config.validator_config(validator_name, key)
|
|
274
|
+
value = config.validator_config(validator_name, key)
|
|
275
|
+
# A nil? check (not ||) so that explicitly configured false values
|
|
276
|
+
# are honored instead of falling back to a truthy default
|
|
277
|
+
value.nil? ? defaults[key] : value
|
|
240
278
|
end
|
|
241
279
|
end
|
|
242
280
|
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
|
|
@@ -18,26 +18,30 @@ module Yard
|
|
|
18
18
|
def in_process_query(object, collector)
|
|
19
19
|
docstring_text = object.docstring.to_s
|
|
20
20
|
return if docstring_text.empty?
|
|
21
|
+
return if duplicate_docstring?(object)
|
|
21
22
|
|
|
22
23
|
errors = []
|
|
23
24
|
|
|
24
|
-
# Check for unclosed backticks
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
# Check for unclosed inline backticks, ignoring fenced code blocks
|
|
26
|
+
# (``` ... ```): their fence characters and contents are not
|
|
27
|
+
# inline-code markers and otherwise inflate the count.
|
|
28
|
+
errors << 'unclosed_backtick' if inline_backtick_count(docstring_text).odd?
|
|
27
29
|
|
|
28
30
|
# Check for unclosed code blocks
|
|
29
31
|
code_block_count = docstring_text.scan(/^```/).count
|
|
30
32
|
errors << 'unclosed_code_block' if code_block_count.odd?
|
|
31
33
|
|
|
32
|
-
# Check for unclosed bold markers
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
# Check for unclosed bold markers, ignoring fenced code blocks and
|
|
35
|
+
# inline code spans (their contents are code, not markdown) as well
|
|
36
|
+
# as `**` runs surrounded by whitespace, which cannot delimit
|
|
37
|
+
# CommonMark emphasis (e.g. the exponent operator in `x ** y`).
|
|
38
|
+
errors << 'unclosed_bold' if bold_marker_count(docstring_text).odd?
|
|
36
39
|
|
|
37
|
-
# Check for invalid list markers
|
|
40
|
+
# Check for invalid list markers, reported with their absolute
|
|
41
|
+
# source line rather than a docstring-relative index
|
|
38
42
|
docstring_text.lines.each_with_index do |line, line_idx|
|
|
39
43
|
stripped = line.strip
|
|
40
|
-
errors << "invalid_list_marker:#{line_idx
|
|
44
|
+
errors << "invalid_list_marker:#{docstring_line(object, line_idx)}" if stripped.match?(/^[•·]/)
|
|
41
45
|
end
|
|
42
46
|
|
|
43
47
|
return if errors.empty?
|
|
@@ -45,6 +49,59 @@ module Yard
|
|
|
45
49
|
collector.puts "#{object.file}:#{object.line}: #{object.title}"
|
|
46
50
|
collector.puts errors.join('|')
|
|
47
51
|
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
# Counts inline backticks, skipping fenced code blocks (``` ... ```)
|
|
56
|
+
# entirely - their fence characters and contents are not inline-code
|
|
57
|
+
# markers.
|
|
58
|
+
# @param text [String] the docstring text
|
|
59
|
+
# @return [Integer] number of inline backticks outside fenced blocks
|
|
60
|
+
def inline_backtick_count(text)
|
|
61
|
+
in_fence = false
|
|
62
|
+
count = 0
|
|
63
|
+
text.each_line do |line|
|
|
64
|
+
if line.strip.start_with?('```')
|
|
65
|
+
in_fence = !in_fence
|
|
66
|
+
next
|
|
67
|
+
end
|
|
68
|
+
next if in_fence
|
|
69
|
+
|
|
70
|
+
count += line.count('`')
|
|
71
|
+
end
|
|
72
|
+
count
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Counts `**` emphasis markers, skipping fenced code blocks and
|
|
76
|
+
# inline code spans entirely. A `**` run is only counted when it
|
|
77
|
+
# abuts a non-whitespace character on at least one side, since a run
|
|
78
|
+
# padded by whitespace on both sides can neither open nor close
|
|
79
|
+
# CommonMark emphasis - this excludes the exponent operator (`x ** y`)
|
|
80
|
+
# without dropping genuine `**bold**` markers.
|
|
81
|
+
# @param text [String] the docstring text
|
|
82
|
+
# @return [Integer] number of bold markers that can delimit emphasis
|
|
83
|
+
def bold_marker_count(text)
|
|
84
|
+
in_fence = false
|
|
85
|
+
count = 0
|
|
86
|
+
text.each_line do |line|
|
|
87
|
+
if line.strip.start_with?('```')
|
|
88
|
+
in_fence = !in_fence
|
|
89
|
+
next
|
|
90
|
+
end
|
|
91
|
+
next if in_fence
|
|
92
|
+
|
|
93
|
+
non_code = line.gsub(/`[^`]*`/, '')
|
|
94
|
+
non_code.scan(/\*\*/) do
|
|
95
|
+
match = Regexp.last_match
|
|
96
|
+
# Peek at the surrounding characters without consuming them, so
|
|
97
|
+
# adjacent runs like `**a**` (single-character bold) still pair up.
|
|
98
|
+
before = match.pre_match[-1]
|
|
99
|
+
after = match.post_match[0]
|
|
100
|
+
count += 1 if before&.match?(/\S/) || after&.match?(/\S/)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
count
|
|
104
|
+
end
|
|
48
105
|
end
|
|
49
106
|
end
|
|
50
107
|
end
|
|
@@ -23,9 +23,9 @@ module Yard
|
|
|
23
23
|
return unless object.is_explicit?
|
|
24
24
|
return if parent_class_allowed?(object)
|
|
25
25
|
|
|
26
|
-
# Check if @return tag is missing
|
|
27
|
-
|
|
28
|
-
return unless
|
|
26
|
+
# Check if @return tag is missing; tags nested inside @overload
|
|
27
|
+
# blocks live on the overload's own docstring, so check those too
|
|
28
|
+
return unless all_typed_tags(object.docstring, %w[return]).empty?
|
|
29
29
|
|
|
30
30
|
# Calculate arity (exclude splat and block parameters)
|
|
31
31
|
arity = object.parameters.reject { |p| p[0].to_s.start_with?('*', '&') }.size
|