yard-lint 1.2.2 → 1.3.0.rc1

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.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +174 -1
  3. data/README.md +118 -3
  4. data/Rakefile +20 -0
  5. data/bin/yard-lint +80 -37
  6. data/lib/yard/lint/config.rb +5 -0
  7. data/lib/yard/lint/config_generator.rb +8 -179
  8. data/lib/yard/lint/config_updater.rb +222 -0
  9. data/lib/yard/lint/errors.rb +6 -0
  10. data/lib/yard/lint/executor/in_process_registry.rb +130 -0
  11. data/lib/yard/lint/executor/query_executor.rb +109 -0
  12. data/lib/yard/lint/executor/result_collector.rb +55 -0
  13. data/lib/yard/lint/executor/warning_dispatcher.rb +79 -0
  14. data/lib/yard/lint/results/base.rb +2 -1
  15. data/lib/yard/lint/runner.rb +88 -35
  16. data/lib/yard/lint/stats_calculator.rb +1 -1
  17. data/lib/yard/lint/templates/default_config.yml +279 -0
  18. data/lib/yard/lint/templates/strict_config.yml +283 -0
  19. data/lib/yard/lint/validators/base.rb +52 -118
  20. data/lib/yard/lint/validators/documentation/blank_line_before_definition/config.rb +25 -0
  21. data/lib/yard/lint/validators/documentation/blank_line_before_definition/messages_builder.rb +39 -0
  22. data/lib/yard/lint/validators/documentation/blank_line_before_definition/parser.rb +59 -0
  23. data/lib/yard/lint/validators/documentation/blank_line_before_definition/result.rb +61 -0
  24. data/lib/yard/lint/validators/documentation/blank_line_before_definition/validator.rb +94 -0
  25. data/lib/yard/lint/validators/documentation/blank_line_before_definition.rb +63 -0
  26. data/lib/yard/lint/validators/documentation/empty_comment_line/config.rb +24 -0
  27. data/lib/yard/lint/validators/documentation/empty_comment_line/messages_builder.rb +34 -0
  28. data/lib/yard/lint/validators/documentation/empty_comment_line/parser.rb +60 -0
  29. data/lib/yard/lint/validators/documentation/empty_comment_line/result.rb +25 -0
  30. data/lib/yard/lint/validators/documentation/empty_comment_line/validator.rb +109 -0
  31. data/lib/yard/lint/validators/documentation/empty_comment_line.rb +58 -0
  32. data/lib/yard/lint/validators/documentation/markdown_syntax/validator.rb +36 -21
  33. data/lib/yard/lint/validators/documentation/markdown_syntax.rb +0 -1
  34. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/validator.rb +19 -29
  35. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods.rb +0 -1
  36. data/lib/yard/lint/validators/documentation/undocumented_method_arguments/validator.rb +18 -34
  37. data/lib/yard/lint/validators/documentation/undocumented_method_arguments.rb +0 -1
  38. data/lib/yard/lint/validators/documentation/undocumented_objects/parser.rb +2 -2
  39. data/lib/yard/lint/validators/documentation/undocumented_objects/validator.rb +17 -25
  40. data/lib/yard/lint/validators/documentation/undocumented_objects.rb +4 -5
  41. data/lib/yard/lint/validators/documentation/undocumented_options/validator.rb +30 -21
  42. data/lib/yard/lint/validators/documentation/undocumented_options.rb +0 -1
  43. data/lib/yard/lint/validators/semantic/abstract_methods/result.rb +2 -2
  44. data/lib/yard/lint/validators/semantic/abstract_methods/validator.rb +31 -43
  45. data/lib/yard/lint/validators/semantic/abstract_methods.rb +0 -1
  46. data/lib/yard/lint/validators/tags/api_tags/validator.rb +24 -39
  47. data/lib/yard/lint/validators/tags/api_tags.rb +0 -1
  48. data/lib/yard/lint/validators/tags/collection_type/parser.rb +1 -1
  49. data/lib/yard/lint/validators/tags/collection_type/validator.rb +37 -66
  50. data/lib/yard/lint/validators/tags/collection_type.rb +0 -1
  51. data/lib/yard/lint/validators/tags/example_syntax/validator.rb +51 -64
  52. data/lib/yard/lint/validators/tags/example_syntax.rb +0 -1
  53. data/lib/yard/lint/validators/tags/informal_notation/config.rb +40 -0
  54. data/lib/yard/lint/validators/tags/informal_notation/messages_builder.rb +35 -0
  55. data/lib/yard/lint/validators/tags/informal_notation/parser.rb +55 -0
  56. data/lib/yard/lint/validators/tags/informal_notation/result.rb +26 -0
  57. data/lib/yard/lint/validators/tags/informal_notation/validator.rb +133 -0
  58. data/lib/yard/lint/validators/tags/informal_notation.rb +45 -0
  59. data/lib/yard/lint/validators/tags/invalid_types/validator.rb +57 -70
  60. data/lib/yard/lint/validators/tags/invalid_types.rb +0 -1
  61. data/lib/yard/lint/validators/tags/meaningless_tag/parser.rb +1 -1
  62. data/lib/yard/lint/validators/tags/meaningless_tag/validator.rb +22 -54
  63. data/lib/yard/lint/validators/tags/meaningless_tag.rb +0 -1
  64. data/lib/yard/lint/validators/tags/non_ascii_type/config.rb +21 -0
  65. data/lib/yard/lint/validators/tags/non_ascii_type/messages_builder.rb +29 -0
  66. data/lib/yard/lint/validators/tags/non_ascii_type/parser.rb +59 -0
  67. data/lib/yard/lint/validators/tags/non_ascii_type/result.rb +25 -0
  68. data/lib/yard/lint/validators/tags/non_ascii_type/validator.rb +50 -0
  69. data/lib/yard/lint/validators/tags/non_ascii_type.rb +39 -0
  70. data/lib/yard/lint/validators/tags/option_tags/result.rb +2 -2
  71. data/lib/yard/lint/validators/tags/option_tags/validator.rb +25 -40
  72. data/lib/yard/lint/validators/tags/option_tags.rb +0 -1
  73. data/lib/yard/lint/validators/tags/order/validator.rb +28 -55
  74. data/lib/yard/lint/validators/tags/order.rb +0 -1
  75. data/lib/yard/lint/validators/tags/redundant_param_description/config.rb +15 -1
  76. data/lib/yard/lint/validators/tags/redundant_param_description/messages_builder.rb +5 -0
  77. data/lib/yard/lint/validators/tags/redundant_param_description/validator.rb +134 -100
  78. data/lib/yard/lint/validators/tags/redundant_param_description.rb +0 -1
  79. data/lib/yard/lint/validators/tags/tag_group_separator/config.rb +29 -0
  80. data/lib/yard/lint/validators/tags/tag_group_separator/messages_builder.rb +49 -0
  81. data/lib/yard/lint/validators/tags/tag_group_separator/parser.rb +67 -0
  82. data/lib/yard/lint/validators/tags/tag_group_separator/result.rb +28 -0
  83. data/lib/yard/lint/validators/tags/tag_group_separator/validator.rb +117 -0
  84. data/lib/yard/lint/validators/tags/tag_group_separator.rb +49 -0
  85. data/lib/yard/lint/validators/tags/tag_type_position/parser.rb +1 -1
  86. data/lib/yard/lint/validators/tags/tag_type_position/validator.rb +53 -84
  87. data/lib/yard/lint/validators/tags/tag_type_position.rb +4 -5
  88. data/lib/yard/lint/validators/tags/type_syntax/parser.rb +8 -3
  89. data/lib/yard/lint/validators/tags/type_syntax/validator.rb +29 -59
  90. data/lib/yard/lint/validators/tags/type_syntax.rb +0 -1
  91. data/lib/yard/lint/validators/warnings/duplicated_parameter_name/validator.rb +1 -18
  92. data/lib/yard/lint/validators/warnings/invalid_directive_format/validator.rb +1 -18
  93. data/lib/yard/lint/validators/warnings/invalid_tag_format/validator.rb +1 -18
  94. data/lib/yard/lint/validators/warnings/unknown_directive/validator.rb +1 -18
  95. data/lib/yard/lint/validators/warnings/unknown_parameter_name/messages_builder.rb +243 -0
  96. data/lib/yard/lint/validators/warnings/unknown_parameter_name/result.rb +4 -3
  97. data/lib/yard/lint/validators/warnings/unknown_parameter_name/validator.rb +1 -18
  98. data/lib/yard/lint/validators/warnings/unknown_tag/messages_builder.rb +144 -0
  99. data/lib/yard/lint/validators/warnings/unknown_tag/result.rb +4 -3
  100. data/lib/yard/lint/validators/warnings/unknown_tag/validator.rb +1 -18
  101. data/lib/yard/lint/validators/warnings/unknown_tag.rb +10 -0
  102. data/lib/yard/lint/version.rb +1 -1
  103. data/lib/yard/lint.rb +81 -13
  104. data/renovate.json +1 -8
  105. metadata +40 -6
  106. data/bin/console +0 -11
  107. data/bin/setup +0 -8
  108. data/lib/yard/lint/command_cache.rb +0 -93
@@ -0,0 +1,283 @@
1
+ # YARD-Lint Configuration (Strict Mode)
2
+ # See https://github.com/mensfeld/yard-lint for documentation
3
+ #
4
+ # This is a strict configuration suitable for new projects with high documentation standards.
5
+ # All validators are set to 'error' severity (no warnings or conventions).
6
+ # Minimum coverage is set to 100%.
7
+
8
+ # Global settings for all validators
9
+ AllValidators:
10
+ # YARD command-line options (applied to all validators by default)
11
+ YardOptions:
12
+ - --private
13
+ - --protected
14
+
15
+ # Global file exclusion patterns
16
+ Exclude:
17
+ - '\.git'
18
+ - 'vendor/**/*'
19
+ - 'node_modules/**/*'
20
+ - 'spec/**/*'
21
+ - 'test/**/*'
22
+
23
+ # Exit code behavior (error, warning, convention, never)
24
+ FailOnSeverity: error
25
+
26
+ # Minimum documentation coverage percentage (0-100)
27
+ # Fails if coverage is below this threshold
28
+ MinCoverage: 100.0
29
+
30
+ # Diff mode settings
31
+ DiffMode:
32
+ # Default base ref for --diff (auto-detects main/master if not specified)
33
+ DefaultBaseRef: ~
34
+
35
+ # Documentation validators
36
+ Documentation/UndocumentedObjects:
37
+ Description: 'Checks for classes, modules, and methods without documentation.'
38
+ Enabled: true
39
+ Severity: error
40
+ ExcludedMethods:
41
+ - 'initialize/0' # Exclude parameter-less initialize
42
+ - '/^_/' # Exclude private methods (by convention)
43
+
44
+ Documentation/UndocumentedMethodArguments:
45
+ Description: 'Checks for method parameters without @param tags.'
46
+ Enabled: true
47
+ Severity: error
48
+
49
+ Documentation/UndocumentedBooleanMethods:
50
+ Description: 'Checks that question mark methods document their boolean return.'
51
+ Enabled: true
52
+ Severity: error
53
+
54
+ Documentation/UndocumentedOptions:
55
+ Description: 'Detects methods with options hash parameters but no @option tags.'
56
+ Enabled: true
57
+ Severity: error
58
+
59
+ Documentation/MarkdownSyntax:
60
+ Description: 'Detects common markdown syntax errors in documentation.'
61
+ Enabled: true
62
+ Severity: error
63
+
64
+ Documentation/EmptyCommentLine:
65
+ Description: 'Detects empty comment lines at the start or end of documentation blocks.'
66
+ Enabled: true
67
+ Severity: error
68
+ EnabledPatterns:
69
+ Leading: true
70
+ Trailing: true
71
+
72
+ Documentation/BlankLineBeforeDefinition:
73
+ Description: 'Detects blank lines between YARD documentation and method definition.'
74
+ Enabled: true
75
+ Severity: error
76
+ OrphanedSeverity: error
77
+ EnabledPatterns:
78
+ SingleBlankLine: true
79
+ OrphanedDocs: true
80
+
81
+ # Tags validators
82
+ Tags/Order:
83
+ Description: 'Enforces consistent ordering of YARD tags.'
84
+ Enabled: true
85
+ Severity: error
86
+ EnforcedOrder:
87
+ - param
88
+ - option
89
+ - yield
90
+ - yieldparam
91
+ - yieldreturn
92
+ - return
93
+ - raise
94
+ - see
95
+ - example
96
+ - note
97
+ - todo
98
+
99
+ Tags/InvalidTypes:
100
+ Description: 'Validates type definitions in @param, @return, @option tags.'
101
+ Enabled: true
102
+ Severity: error
103
+ ValidatedTags:
104
+ - param
105
+ - option
106
+ - return
107
+
108
+ Tags/TypeSyntax:
109
+ Description: 'Validates YARD type syntax using YARD parser.'
110
+ Enabled: true
111
+ Severity: error
112
+ ValidatedTags:
113
+ - param
114
+ - option
115
+ - return
116
+ - yieldreturn
117
+
118
+ Tags/MeaninglessTag:
119
+ Description: 'Detects @param/@option tags on classes, modules, or constants.'
120
+ Enabled: true
121
+ Severity: error
122
+ CheckedTags:
123
+ - param
124
+ - option
125
+ InvalidObjectTypes:
126
+ - class
127
+ - module
128
+ - constant
129
+
130
+ Tags/CollectionType:
131
+ Description: 'Validates Hash collection syntax consistency.'
132
+ Enabled: true
133
+ Severity: error
134
+ EnforcedStyle: long # 'long' for Hash{K => V} (YARD standard), 'short' for {K => V}
135
+ ValidatedTags:
136
+ - param
137
+ - option
138
+ - return
139
+ - yieldreturn
140
+
141
+ Tags/TagTypePosition:
142
+ Description: 'Validates type annotation position in tags.'
143
+ Enabled: true
144
+ Severity: error
145
+ CheckedTags:
146
+ - param
147
+ - option
148
+ # EnforcedStyle: 'type_after_name' (YARD standard: @param name [Type])
149
+ # or 'type_first' (@param [Type] name)
150
+ EnforcedStyle: type_after_name
151
+
152
+ Tags/ApiTags:
153
+ Description: 'Enforces @api tags on public objects.'
154
+ Enabled: false # Opt-in validator
155
+ Severity: error
156
+ AllowedApis:
157
+ - public
158
+ - private
159
+ - internal
160
+
161
+ Tags/OptionTags:
162
+ Description: 'Requires @option tags for methods with options parameters.'
163
+ Enabled: true
164
+ Severity: error
165
+
166
+ Tags/ExampleSyntax:
167
+ Description: 'Validates Ruby syntax in @example tags.'
168
+ Enabled: true
169
+ Severity: error
170
+
171
+ Tags/RedundantParamDescription:
172
+ Description: 'Detects meaningless parameter descriptions that add no value.'
173
+ Enabled: true
174
+ Severity: error
175
+ CheckedTags:
176
+ - param
177
+ - option
178
+ Articles:
179
+ - The
180
+ - the
181
+ - A
182
+ - a
183
+ - An
184
+ - an
185
+ MaxRedundantWords: 6
186
+ GenericTerms:
187
+ - object
188
+ - instance
189
+ - value
190
+ - data
191
+ - item
192
+ - element
193
+ EnabledPatterns:
194
+ ArticleParam: true
195
+ PossessiveParam: true
196
+ TypeRestatement: true
197
+ ParamToVerb: true
198
+ IdPattern: true
199
+ DirectionalDate: true
200
+ TypeGeneric: true
201
+
202
+ Tags/InformalNotation:
203
+ Description: 'Detects informal tag notation patterns like "Note:" instead of @note.'
204
+ Enabled: true
205
+ Severity: error
206
+ CaseSensitive: false
207
+ RequireStartOfLine: true
208
+ Patterns:
209
+ Note: '@note'
210
+ Todo: '@todo'
211
+ TODO: '@todo'
212
+ FIXME: '@todo'
213
+ See: '@see'
214
+ See also: '@see'
215
+ Warning: '@deprecated'
216
+ Deprecated: '@deprecated'
217
+ Author: '@author'
218
+ Version: '@version'
219
+ Since: '@since'
220
+ Returns: '@return'
221
+ Raises: '@raise'
222
+ Example: '@example'
223
+
224
+ Tags/NonAsciiType:
225
+ Description: 'Detects non-ASCII characters in type annotations.'
226
+ Enabled: true
227
+ Severity: error
228
+ ValidatedTags:
229
+ - param
230
+ - option
231
+ - return
232
+ - yieldreturn
233
+ - yieldparam
234
+
235
+ Tags/TagGroupSeparator:
236
+ Description: 'Enforces blank line separators between different YARD tag groups.'
237
+ Enabled: false # Opt-in validator
238
+ Severity: error
239
+ TagGroups:
240
+ param: [param, option]
241
+ return: [return]
242
+ error: [raise, throws]
243
+ example: [example]
244
+ meta: [see, note, todo, deprecated, since, version, api]
245
+ yield: [yield, yieldparam, yieldreturn]
246
+ RequireAfterDescription: false
247
+
248
+ # Warnings validators - catches YARD parser errors
249
+ Warnings/UnknownTag:
250
+ Description: 'Detects unknown YARD tags.'
251
+ Enabled: true
252
+ Severity: error
253
+
254
+ Warnings/UnknownDirective:
255
+ Description: 'Detects unknown YARD directives.'
256
+ Enabled: true
257
+ Severity: error
258
+
259
+ Warnings/InvalidTagFormat:
260
+ Description: 'Detects malformed tag syntax.'
261
+ Enabled: true
262
+ Severity: error
263
+
264
+ Warnings/InvalidDirectiveFormat:
265
+ Description: 'Detects malformed directive syntax.'
266
+ Enabled: true
267
+ Severity: error
268
+
269
+ Warnings/DuplicatedParameterName:
270
+ Description: 'Detects duplicate @param tags.'
271
+ Enabled: true
272
+ Severity: error
273
+
274
+ Warnings/UnknownParameterName:
275
+ Description: 'Detects @param tags for non-existent parameters.'
276
+ Enabled: true
277
+ Severity: error
278
+
279
+ # Semantic validators
280
+ Semantic/AbstractMethods:
281
+ Description: 'Ensures @abstract methods do not have real implementations.'
282
+ Enabled: true
283
+ Severity: error
@@ -6,47 +6,52 @@ module Yard
6
6
  module Validators
7
7
  # Base YARD validator class
8
8
  class Base
9
- # Class-level cache shared across ALL validator classes
10
- # Must be stored on Base itself, not on subclasses
11
- @shared_command_cache = nil
12
-
13
- # Default YARD command options that we need to use
14
- DEFAULT_OPTIONS = [
15
- '--charset utf-8',
16
- '--markup markdown',
17
- '--no-progress'
18
- ].freeze
19
-
20
- # Base temp directory for YARD databases
21
- # Each unique set of arguments gets its own subdirectory to prevent contamination
22
- YARDOC_BASE_TEMP_DIR = Dir.mktmpdir.freeze
23
-
24
- private_constant :YARDOC_BASE_TEMP_DIR
9
+ # Class-level settings for in-process execution
10
+ # These must be set on each subclass, not on Base
11
+ @in_process_enabled = nil
12
+ @in_process_visibility = nil
25
13
 
26
14
  attr_reader :config, :selection
27
15
 
28
16
  class << self
29
- # Lazy-initialized command cache shared across all validator instances
30
- # This allows different validators to reuse results from identical YARD commands
31
- # @return [CommandCache] the command cache instance
32
- def command_cache
33
- # Use Base's cache, not subclass's cache
34
- Base.instance_variable_get(:@shared_command_cache) ||
35
- Base.instance_variable_set(:@shared_command_cache, CommandCache.new)
17
+ # Declare that this validator supports in-process execution
18
+ # @param visibility [Symbol] visibility filter for objects (:public or :all)
19
+ # :public - only include public methods (default, no --private/--protected)
20
+ # :all - include all methods (equivalent to --private --protected)
21
+ # @return [void]
22
+ # @example
23
+ # class Validator < Base
24
+ # in_process visibility: :all
25
+ # end
26
+ def in_process(visibility: :public)
27
+ @in_process_enabled = true
28
+ @in_process_visibility = visibility
36
29
  end
37
30
 
38
- # Reset the command cache (primarily for testing)
39
- # @return [void]
40
- def reset_command_cache!
41
- Base.instance_variable_set(:@shared_command_cache, nil)
31
+ # Check if this validator supports in-process execution
32
+ # @return [Boolean]
33
+ def in_process?
34
+ @in_process_enabled == true
42
35
  end
43
36
 
44
- # Clear all YARD databases (primarily for testing)
45
- # @return [void]
46
- def clear_yard_database!
47
- return unless defined?(YARDOC_BASE_TEMP_DIR)
37
+ # Get the visibility setting for in-process execution
38
+ # @return [Symbol, nil] :public, :all, or nil if not set
39
+ def in_process_visibility
40
+ @in_process_visibility
41
+ end
48
42
 
49
- FileUtils.rm_rf(Dir.glob(File.join(YARDOC_BASE_TEMP_DIR, '*')))
43
+ # Get the validator name from the class namespace
44
+ # @return [String, nil] validator name like 'Tags/Order' or nil
45
+ # @example
46
+ # Yard::Lint::Validators::Tags::Order::Validator.validator_name
47
+ # # => 'Tags/Order'
48
+ def validator_name
49
+ name&.split('::')&.then do |parts|
50
+ idx = parts.index('Validators')
51
+ return nil unless idx && parts[idx + 1] && parts[idx + 2]
52
+
53
+ "#{parts[idx + 1]}/#{parts[idx + 2]}"
54
+ end
50
55
  end
51
56
  end
52
57
 
@@ -57,105 +62,34 @@ module Yard
57
62
  @selection = selection
58
63
  end
59
64
 
60
- # Performs the validation and returns raw results
61
- # @return [Hash] hash with stdout, stderr and exit_code keys
62
- def call
63
- # There might be a case when there were no files because someone ignored all
64
- # then we need to return empty result
65
- return raw if selection.nil? || selection.empty?
66
-
67
- # Anything that goes to shell needs to be escaped
68
- escaped_file_names = escape(selection)
69
-
70
- # Use a unique YARD database per set of arguments to prevent contamination
71
- # between validators with different file selections or options
72
- yardoc_dir = yardoc_temp_dir_for_arguments(escaped_file_names.join(' '))
73
-
74
- # For large file lists, use a temporary file to avoid ARG_MAX limits
75
- # Write file paths to temp file, one per line
76
- Tempfile.create(['yard_files', '.txt']) do |f|
77
- escaped_file_names.each { |file| f.puts(file) }
78
- f.flush
79
-
80
- yard_cmd(yardoc_dir, f.path)
81
- end
65
+ # Execute query for a single object during in-process execution.
66
+ # Override this method in validators that support in-process execution.
67
+ # @param object [YARD::CodeObjects::Base] the code object to query
68
+ # @param collector [Executor::ResultCollector] collector for output
69
+ # @return [void]
70
+ # @example
71
+ # def in_process_query(object, collector)
72
+ # return unless object.docstring.all.empty?
73
+ # collector.puts "#{object.file}:#{object.line}: #{object.title}"
74
+ # end
75
+ def in_process_query(object, collector)
76
+ raise NotImplementedError, "#{self.class} must implement in_process_query for in-process execution"
82
77
  end
83
78
 
84
79
  private
85
80
 
86
- # Returns a unique YARD database directory for the given arguments
87
- # Uses SHA256 hash of the normalized arguments to ensure different file sets
88
- # get separate databases, preventing contamination
89
- # @param escaped_file_names [String] escaped file names to process
90
- # @return [String] path to the YARD database directory
91
- def yardoc_temp_dir_for_arguments(escaped_file_names)
92
- # Combine all arguments that affect YARD output
93
- all_args = "#{shell_arguments} #{escaped_file_names}"
94
-
95
- # Create a hash of the arguments for a unique directory name
96
- args_hash = Digest::SHA256.hexdigest(all_args)
97
-
98
- # Create subdirectory under base temp dir
99
- dir = File.join(YARDOC_BASE_TEMP_DIR, args_hash)
100
- FileUtils.mkdir_p(dir) unless File.directory?(dir)
101
-
102
- dir
103
- end
104
-
105
- # @return [String] all arguments with which YARD command should be executed
106
- def shell_arguments
107
- validator_name = self.class.name&.split('::')&.then do |parts|
108
- idx = parts.index('Validators')
109
- next config.options unless idx && parts[idx + 1] && parts[idx + 2]
110
-
111
- "#{parts[idx + 1]}/#{parts[idx + 2]}"
112
- end || config.options
113
-
114
- yard_options = config.validator_yard_options(validator_name)
115
- args = escape(yard_options).join(' ')
116
- "#{args} #{DEFAULT_OPTIONS.join(' ')}"
117
- end
118
-
119
- # @param array [Array] escape all elements in an array
120
- # @return [Array] array with escaped elements
121
- def escape(array)
122
- array.map { |cmd| Shellwords.escape(cmd) }
123
- end
124
-
125
- # Builds a raw hash that can be used for further processing
126
- # @param stdout [String, Hash, Array] anything that we want to return as stdout
127
- # @param stderr [String, Hash, Array] any errors that occurred
128
- # @param exit_code [Integer, false] result exit code or false if we want to decide it based
129
- # on the stderr content
130
- # @return [Hash] hash with stdout, stderr and exit_code keys
131
- def raw(stdout = '', stderr = '', exit_code = false)
132
- {
133
- stdout: stdout,
134
- stderr: stderr,
135
- exit_code: exit_code || (stderr.empty? ? 0 : 1)
136
- }
137
- end
138
-
139
- # Executes a shell command and returns the result
140
- # Routes through command cache to avoid duplicate executions
141
- # @param cmd [String] shell command to execute
142
- # @return [Hash] hash with stdout, stderr and exit_code keys
143
- def shell(cmd)
144
- self.class.command_cache.execute(cmd)
145
- end
146
-
147
81
  # Retrieves configuration value with fallback to default
148
82
  # Automatically determines the validator name from the class namespace
149
83
  #
150
84
  # @param key [String] the configuration key to retrieve
151
85
  # @return [Object] the configured value or default value from the validator's Config.defaults
152
- # @note The validator name is automatically extracted from the class namespace.
153
- # For example, Yard::Lint::Validators::Tags::RedundantParamDescription::Validator
154
- # becomes 'Tags/RedundantParamDescription'
155
86
  # @example Usage in a validator (e.g., Tags::RedundantParamDescription)
156
87
  # def config_articles
157
88
  # config_or_default('Articles')
158
89
  # end
90
+ # @note The validator name is automatically extracted from the class namespace.
91
+ # For example, Yard::Lint::Validators::Tags::RedundantParamDescription::Validator
92
+ # becomes 'Tags/RedundantParamDescription'
159
93
  def config_or_default(key)
160
94
  validator_name = self.class.name&.split('::')&.then do |parts|
161
95
  idx = parts.index('Validators')
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Documentation
7
+ module BlankLineBeforeDefinition
8
+ # Configuration for BlankLineBeforeDefinition validator
9
+ class Config < ::Yard::Lint::Validators::Config
10
+ self.id = :blank_line_before_definition
11
+ self.defaults = {
12
+ 'Enabled' => true,
13
+ 'Severity' => 'convention',
14
+ 'OrphanedSeverity' => 'convention',
15
+ 'EnabledPatterns' => {
16
+ 'SingleBlankLine' => true,
17
+ 'OrphanedDocs' => true
18
+ }
19
+ }.freeze
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Documentation
7
+ module BlankLineBeforeDefinition
8
+ # Builds human-readable messages for blank line before definition violations
9
+ class MessagesBuilder
10
+ # Maps violation types to human-readable descriptions
11
+ ERROR_DESCRIPTIONS = {
12
+ 'single' => 'Blank line between documentation and definition',
13
+ 'orphaned' => 'Documentation is orphaned (YARD ignores it due to blank lines)'
14
+ }.freeze
15
+
16
+ class << self
17
+ # Formats a violation message
18
+ # @param offense [Hash] the offense details
19
+ # @return [String] formatted message
20
+ def call(offense)
21
+ type = offense[:violation_type]
22
+ object_name = offense[:object_name]
23
+ blank_count = offense[:blank_count]
24
+
25
+ description = ERROR_DESCRIPTIONS[type] || 'Blank line before definition'
26
+
27
+ if type == 'orphaned'
28
+ "#{description} for '#{object_name}' (#{blank_count} blank lines)"
29
+ else
30
+ "#{description} for '#{object_name}'"
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Documentation
7
+ module BlankLineBeforeDefinition
8
+ # Parses YARD output for blank line before definition violations
9
+ class Parser < Parsers::Base
10
+ # Parse YARD output into structured violations
11
+ # @param output [String] raw YARD output
12
+ # @return [Array<Hash>] array of violation hashes
13
+ def call(output)
14
+ return [] if output.nil? || output.empty?
15
+
16
+ violations = []
17
+ lines = output.lines.map(&:chomp)
18
+
19
+ i = 0
20
+ while i < lines.size
21
+ line = lines[i]
22
+
23
+ # Match location line: "file:line: object_name"
24
+ if (location_match = line.match(/^(.+):(\d+): (.+)$/))
25
+ file_path = location_match[1]
26
+ object_line = location_match[2].to_i
27
+ object_name = location_match[3]
28
+
29
+ # Next line contains violation details
30
+ i += 1
31
+ next unless i < lines.size
32
+
33
+ # Parse violation: "single:1" or "orphaned:3"
34
+ detail_parts = lines[i].split(':', 2)
35
+ next unless detail_parts.size == 2
36
+
37
+ violation_type = detail_parts[0]
38
+ blank_count = detail_parts[1].to_i
39
+
40
+ violations << {
41
+ location: file_path,
42
+ line: object_line,
43
+ object_name: object_name,
44
+ violation_type: violation_type,
45
+ blank_count: blank_count
46
+ }
47
+ end
48
+
49
+ i += 1
50
+ end
51
+
52
+ violations
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Documentation
7
+ module BlankLineBeforeDefinition
8
+ # Result builder for blank line before definition violations
9
+ class Result < Results::Base
10
+ self.default_severity = 'convention'
11
+ self.offense_type = 'line'
12
+ self.offense_name = 'BlankLineBeforeDefinition'
13
+
14
+ # Build human-readable message for blank line violation
15
+ # @param offense [Hash] offense data with violation details
16
+ # @return [String] formatted message
17
+ def build_message(offense)
18
+ MessagesBuilder.call(offense)
19
+ end
20
+
21
+ private
22
+
23
+ # Override to handle per-violation severity based on violation type
24
+ # @return [Array<Hash>] array of offense hashes
25
+ def build_offenses
26
+ @parsed_data.map do |offense_data|
27
+ severity = severity_for_violation(offense_data[:violation_type])
28
+
29
+ offense_data.merge(
30
+ severity: severity,
31
+ type: self.class.offense_type,
32
+ name: computed_offense_name,
33
+ message: build_message(offense_data),
34
+ location: offense_data[:location] || offense_data[:file],
35
+ location_line: offense_data[:line] || offense_data[:location_line] || 0
36
+ )
37
+ end
38
+ end
39
+
40
+ # Get severity for a specific violation type
41
+ # @param violation_type [String] 'single' or 'orphaned'
42
+ # @return [String] severity level
43
+ def severity_for_violation(violation_type)
44
+ default = self.class.default_severity
45
+ return default unless config
46
+
47
+ case violation_type
48
+ when 'orphaned'
49
+ config.validator_config(validator_name, 'OrphanedSeverity') ||
50
+ config.validator_severity(validator_name) ||
51
+ default
52
+ else
53
+ config.validator_severity(validator_name) || default
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end