yard-lint 1.0.0 → 1.1.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/README.md +42 -265
  4. data/bin/yard-lint +20 -0
  5. data/lib/yard/lint/config.rb +0 -2
  6. data/lib/yard/lint/config_generator.rb +191 -0
  7. data/lib/yard/lint/validators/base.rb +36 -0
  8. data/lib/yard/lint/validators/documentation/markdown_syntax/config.rb +20 -0
  9. data/lib/yard/lint/validators/documentation/markdown_syntax/messages_builder.rb +44 -0
  10. data/lib/yard/lint/validators/documentation/markdown_syntax/parser.rb +53 -0
  11. data/lib/yard/lint/validators/documentation/markdown_syntax/result.rb +25 -0
  12. data/lib/yard/lint/validators/documentation/markdown_syntax/validator.rb +38 -0
  13. data/lib/yard/lint/validators/documentation/markdown_syntax.rb +37 -0
  14. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods.rb +26 -1
  15. data/lib/yard/lint/validators/documentation/undocumented_method_arguments.rb +26 -1
  16. data/lib/yard/lint/validators/documentation/undocumented_objects.rb +131 -2
  17. data/lib/yard/lint/validators/documentation/undocumented_options/config.rb +20 -0
  18. data/lib/yard/lint/validators/documentation/undocumented_options/parser.rb +53 -0
  19. data/lib/yard/lint/validators/documentation/undocumented_options/result.rb +29 -0
  20. data/lib/yard/lint/validators/documentation/undocumented_options/validator.rb +38 -0
  21. data/lib/yard/lint/validators/documentation/undocumented_options.rb +40 -0
  22. data/lib/yard/lint/validators/semantic/abstract_methods.rb +31 -1
  23. data/lib/yard/lint/validators/tags/api_tags.rb +34 -1
  24. data/lib/yard/lint/validators/tags/collection_type/config.rb +2 -1
  25. data/lib/yard/lint/validators/tags/collection_type/messages_builder.rb +40 -11
  26. data/lib/yard/lint/validators/tags/collection_type/parser.rb +6 -5
  27. data/lib/yard/lint/validators/tags/collection_type/validator.rb +26 -7
  28. data/lib/yard/lint/validators/tags/collection_type.rb +38 -2
  29. data/lib/yard/lint/validators/tags/example_syntax/config.rb +20 -0
  30. data/lib/yard/lint/validators/tags/example_syntax/messages_builder.rb +28 -0
  31. data/lib/yard/lint/validators/tags/example_syntax/parser.rb +79 -0
  32. data/lib/yard/lint/validators/tags/example_syntax/result.rb +42 -0
  33. data/lib/yard/lint/validators/tags/example_syntax/validator.rb +88 -0
  34. data/lib/yard/lint/validators/tags/example_syntax.rb +42 -0
  35. data/lib/yard/lint/validators/tags/invalid_types/validator.rb +2 -2
  36. data/lib/yard/lint/validators/tags/invalid_types.rb +25 -1
  37. data/lib/yard/lint/validators/tags/meaningless_tag/validator.rb +2 -4
  38. data/lib/yard/lint/validators/tags/meaningless_tag.rb +31 -3
  39. data/lib/yard/lint/validators/tags/option_tags/validator.rb +7 -1
  40. data/lib/yard/lint/validators/tags/option_tags.rb +26 -1
  41. data/lib/yard/lint/validators/tags/order.rb +25 -1
  42. data/lib/yard/lint/validators/tags/redundant_param_description/config.rb +33 -0
  43. data/lib/yard/lint/validators/tags/redundant_param_description/messages_builder.rb +61 -0
  44. data/lib/yard/lint/validators/tags/redundant_param_description/parser.rb +67 -0
  45. data/lib/yard/lint/validators/tags/redundant_param_description/result.rb +25 -0
  46. data/lib/yard/lint/validators/tags/redundant_param_description/validator.rb +148 -0
  47. data/lib/yard/lint/validators/tags/redundant_param_description.rb +168 -0
  48. data/lib/yard/lint/validators/tags/tag_type_position/validator.rb +2 -4
  49. data/lib/yard/lint/validators/tags/tag_type_position.rb +39 -2
  50. data/lib/yard/lint/validators/tags/type_syntax.rb +26 -2
  51. data/lib/yard/lint/validators/warnings/duplicated_parameter_name.rb +26 -1
  52. data/lib/yard/lint/validators/warnings/invalid_directive_format.rb +26 -1
  53. data/lib/yard/lint/validators/warnings/invalid_tag_format.rb +25 -1
  54. data/lib/yard/lint/validators/warnings/unknown_directive.rb +26 -1
  55. data/lib/yard/lint/validators/warnings/unknown_parameter_name.rb +23 -1
  56. data/lib/yard/lint/validators/warnings/unknown_tag.rb +26 -1
  57. data/lib/yard/lint/version.rb +1 -1
  58. metadata +39 -1
@@ -143,6 +143,42 @@ module Yard
143
143
  def shell(cmd)
144
144
  self.class.command_cache.execute(cmd)
145
145
  end
146
+
147
+ # Retrieves configuration value with fallback to default
148
+ # Automatically determines the validator name from the class namespace
149
+ #
150
+ # @param key [String] the configuration key to retrieve
151
+ # @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
+ # @example Usage in a validator (e.g., Tags::RedundantParamDescription)
156
+ # def config_articles
157
+ # config_or_default('Articles')
158
+ # end
159
+ def config_or_default(key)
160
+ validator_name = self.class.name&.split('::')&.then do |parts|
161
+ idx = parts.index('Validators')
162
+ next nil unless idx && parts[idx + 1] && parts[idx + 2]
163
+
164
+ "#{parts[idx + 1]}/#{parts[idx + 2]}"
165
+ end
166
+
167
+ # Get the validator module's Config class
168
+ validator_config_class = begin
169
+ # Get parent module (e.g., Yard::Lint::Validators::Tags::RedundantParamDescription)
170
+ parent_module = self.class.name.split('::')[0..-2].join('::')
171
+ Object.const_get("#{parent_module}::Config")
172
+ rescue NameError
173
+ nil
174
+ end
175
+
176
+ defaults = validator_config_class&.defaults || {}
177
+
178
+ return defaults[key] unless validator_name
179
+
180
+ config.validator_config(validator_name, key) || defaults[key]
181
+ end
146
182
  end
147
183
  end
148
184
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Documentation
7
+ module MarkdownSyntax
8
+ # Configuration for MarkdownSyntax validator
9
+ class Config < ::Yard::Lint::Validators::Config
10
+ self.id = :markdown_syntax
11
+ self.defaults = {
12
+ 'Enabled' => true,
13
+ 'Severity' => 'warning'
14
+ }.freeze
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Documentation
7
+ module MarkdownSyntax
8
+ # Builds human-readable messages for MarkdownSyntax violations
9
+ class MessagesBuilder
10
+ # Maps markdown syntax error types to human-readable descriptions
11
+ ERROR_DESCRIPTIONS = {
12
+ 'unclosed_backtick' => 'Unclosed backtick in documentation',
13
+ 'unclosed_code_block' => 'Unclosed code block (```) in documentation',
14
+ 'unclosed_bold' => 'Unclosed bold formatting (**) in documentation',
15
+ 'invalid_list_marker' => 'Invalid list marker (use - or * instead)'
16
+ }.freeze
17
+
18
+ class << self
19
+ # Formats a violation message
20
+ # @param offense [Hash] the offense details
21
+ # @return [String] formatted message
22
+ def call(offense)
23
+ object_name = offense[:object_name]
24
+ errors = offense[:errors]
25
+
26
+ error_messages = errors.map do |error|
27
+ if error.start_with?('invalid_list_marker:')
28
+ line_num = error.split(':').last
29
+ "#{ERROR_DESCRIPTIONS['invalid_list_marker']} at line #{line_num}"
30
+ else
31
+ ERROR_DESCRIPTIONS[error] || error
32
+ end
33
+ end
34
+
35
+ "Markdown syntax errors in '#{object_name}': " \
36
+ "#{error_messages.join(', ')}"
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Documentation
7
+ module MarkdownSyntax
8
+ # Parses YARD output for markdown syntax 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
+ line_number = location_match[2].to_i
27
+ object_name = location_match[3]
28
+
29
+ # Next line contains error types
30
+ i += 1
31
+ next unless i < lines.size
32
+
33
+ errors = lines[i].split('|')
34
+
35
+ violations << {
36
+ location: file_path,
37
+ line: line_number,
38
+ object_name: object_name,
39
+ errors: errors
40
+ }
41
+ end
42
+
43
+ i += 1
44
+ end
45
+
46
+ violations
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Documentation
7
+ module MarkdownSyntax
8
+ # Result object for markdown syntax validation
9
+ class Result < Results::Base
10
+ self.default_severity = 'warning'
11
+ self.offense_type = 'line'
12
+ self.offense_name = 'MarkdownSyntax'
13
+
14
+ # Build human-readable message for markdown syntax offense
15
+ # @param offense [Hash] offense data with :object_name and :errors
16
+ # @return [String] formatted message
17
+ def build_message(offense)
18
+ MessagesBuilder.call(offense)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Documentation
7
+ module MarkdownSyntax
8
+ # Validates markdown syntax in documentation
9
+ class Validator < Validators::Base
10
+ # YARD query to extract docstrings and check for markdown errors
11
+ # @return [String] YARD Ruby query code
12
+ def query
13
+ <<~QUERY.strip
14
+ 'docstring_text = object.docstring.to_s; unless docstring_text.empty?; errors = []; backtick_count = docstring_text.scan(/\\x60/).count; errors << "unclosed_backtick" if backtick_count.odd?; code_block_count = docstring_text.scan(/^```/).count; errors << "unclosed_code_block" if code_block_count.odd?; non_code_text = docstring_text.gsub(/\\x60[^\\x60]*\\x60/, ""); bold_count = non_code_text.scan(/\\*\\*/).count; errors << "unclosed_bold" if bold_count.odd?; lines = docstring_text.lines; lines.each_with_index do |line, line_idx|; stripped = line.strip; errors << "invalid_list_marker:" + (line_idx + 1).to_s if stripped =~ /^[•·]/; end; unless errors.empty?; puts object.file + ":" + object.line.to_s + ": " + object.title; puts errors.join("|"); end; end; false'
15
+ QUERY
16
+ end
17
+
18
+ # Builds and executes the YARD command to detect markdown syntax errors
19
+ # @param dir [String] the directory containing the .yardoc database
20
+ # @param file_list_path [String] path to file containing list of files to analyze
21
+ # @return [String] command output
22
+ def yard_cmd(dir, file_list_path)
23
+ cmd = <<~CMD
24
+ cat #{Shellwords.escape(file_list_path)} | xargs yard list \
25
+ #{shell_arguments} \
26
+ --query #{query} \
27
+ -q \
28
+ -b #{Shellwords.escape(dir)}
29
+ CMD
30
+ cmd = cmd.tr("\n", ' ')
31
+ shell(cmd)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Documentation
7
+ # MarkdownSyntax validator
8
+ #
9
+ # Validates markdown syntax in documentation comments. This validator checks
10
+ # for common markdown errors and formatting issues in YARD documentation
11
+ # strings. This validator is enabled by default.
12
+ #
13
+ # @example Bad - Invalid markdown syntax
14
+ # # This is [broken markdown
15
+ # # Another line with `unclosed code
16
+ # def process
17
+ # end
18
+ #
19
+ # @example Good - Valid markdown syntax
20
+ # # This is [valid markdown](https://example.com)
21
+ # # Another line with `closed code`
22
+ # def process
23
+ # end
24
+ #
25
+ # ## Configuration
26
+ #
27
+ # To disable this validator:
28
+ #
29
+ # Documentation/MarkdownSyntax:
30
+ # Enabled: false
31
+ #
32
+ module MarkdownSyntax
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -4,7 +4,32 @@ module Yard
4
4
  module Lint
5
5
  module Validators
6
6
  module Documentation
7
- # UndocumentedBooleanMethods validator module
7
+ # UndocumentedBooleanMethods validator
8
+ #
9
+ # Ensures that boolean methods (methods ending with `?`) have an explicit
10
+ # `@return [Boolean]` tag. Boolean methods should clearly document that they
11
+ # return true or false values. This validator is enabled by default.
12
+ #
13
+ # @example Bad - Missing @return tag on boolean method
14
+ # # Checks if the user is active
15
+ # def active?
16
+ # @active
17
+ # end
18
+ #
19
+ # @example Good - Boolean return documented
20
+ # # Checks if the user is active
21
+ # # @return [Boolean] true if the user is active
22
+ # def active?
23
+ # @active
24
+ # end
25
+ #
26
+ # ## Configuration
27
+ #
28
+ # To disable this validator:
29
+ #
30
+ # Documentation/UndocumentedBooleanMethods:
31
+ # Enabled: false
32
+ #
8
33
  module UndocumentedBooleanMethods
9
34
  end
10
35
  end
@@ -4,7 +4,32 @@ module Yard
4
4
  module Lint
5
5
  module Validators
6
6
  module Documentation
7
- # UndocumentedMethodArguments validator module
7
+ # UndocumentedMethodArguments validator
8
+ #
9
+ # Ensures that all method parameters are documented with `@param` tags.
10
+ # This validator checks that every parameter in a method signature has
11
+ # a corresponding `@param` documentation tag. This validator is enabled
12
+ # by default.
13
+ #
14
+ # @example Bad - Missing @param tags
15
+ # # Does something with data
16
+ # def process(name, options)
17
+ # end
18
+ #
19
+ # @example Good - All parameters documented
20
+ # # Does something with data
21
+ # # @param name [String] the name to process
22
+ # # @param options [Hash] configuration options
23
+ # def process(name, options)
24
+ # end
25
+ #
26
+ # ## Configuration
27
+ #
28
+ # To disable this validator:
29
+ #
30
+ # Documentation/UndocumentedMethodArguments:
31
+ # Enabled: false
32
+ #
8
33
  module UndocumentedMethodArguments
9
34
  end
10
35
  end
@@ -4,8 +4,137 @@ module Yard
4
4
  module Lint
5
5
  module Validators
6
6
  module Documentation
7
- # UndocumentedObjects validator module
8
- # This validator checks for missing documentation on objects
7
+ # UndocumentedObjects validator
8
+ #
9
+ # Checks for missing documentation on classes, modules, and methods.
10
+ # This validator supports flexible method exclusions through the `ExcludedMethods`
11
+ # configuration option.
12
+ #
13
+ # ## Pattern Types
14
+ #
15
+ # The `ExcludedMethods` feature supports three pattern types for maximum flexibility:
16
+ #
17
+ # ### 1. Exact Name Matching
18
+ #
19
+ # Excludes methods with the specified name, regardless of arity:
20
+ #
21
+ # ExcludedMethods:
22
+ # - 'to_s' # Excludes ALL to_s methods regardless of parameters
23
+ # - 'inspect' # Excludes ALL inspect methods
24
+ #
25
+ # Note: Exact name matching excludes the method with **any arity**. If you need
26
+ # arity-specific exclusions, use arity notation instead.
27
+ #
28
+ # ### 2. Arity Notation (method_name/N)
29
+ #
30
+ # Excludes methods with specific parameter counts:
31
+ #
32
+ # ExcludedMethods:
33
+ # - 'initialize/0' # Only excludes initialize with NO parameters (default)
34
+ # - 'call/1' # Only excludes call methods with exactly 1 parameter
35
+ # - 'initialize/2' # Only excludes initialize with exactly 2 parameters
36
+ #
37
+ # Note: Arity counts total parameters (required + optional) excluding splat (*)
38
+ # and block (&) parameters.
39
+ #
40
+ # ### 3. Regex Patterns
41
+ #
42
+ # Excludes methods matching a regular expression:
43
+ #
44
+ # ExcludedMethods:
45
+ # - '/^_/' # Excludes all methods starting with underscore (private convention)
46
+ # - '/^test_/' # Excludes all test methods
47
+ # - '/_(helper|util)$/' # Excludes methods ending with _helper or _util
48
+ #
49
+ # ## Configuration Examples
50
+ #
51
+ # ### Minimal setup - Only exclude parameter-less initialize
52
+ #
53
+ # Documentation/UndocumentedObjects:
54
+ # ExcludedMethods:
55
+ # - 'initialize/0'
56
+ #
57
+ # ### Common Rails/Ruby patterns
58
+ #
59
+ # Documentation/UndocumentedObjects:
60
+ # ExcludedMethods:
61
+ # - 'initialize/0' # Parameter-less constructors
62
+ # - '/^_/' # Private methods (by convention)
63
+ # - 'to_s' # String conversion
64
+ # - 'inspect' # Object inspection
65
+ # - 'hash' # Hash code generation
66
+ # - 'eql?' # Equality comparison
67
+ # - '==' # Binary equality operator
68
+ # - '<=>' # Spaceship operator (comparison)
69
+ # - '+' # Addition operator
70
+ # - '-' # Subtraction operator
71
+ # - '+@' # Unary plus operator
72
+ # - '-@' # Unary minus operator
73
+ #
74
+ # ### Test framework exclusions
75
+ #
76
+ # Documentation/UndocumentedObjects:
77
+ # ExcludedMethods:
78
+ # - '/^test_/' # Minitest methods
79
+ # - '/^should_/' # Shoulda methods
80
+ # - 'setup/0' # Setup with no params
81
+ # - 'teardown/0' # Teardown with no params
82
+ #
83
+ # ## Pattern Validation & Edge Cases
84
+ #
85
+ # The `ExcludedMethods` feature includes robust validation and error handling:
86
+ #
87
+ # **Automatic Pattern Sanitization:**
88
+ # - **Nil values** are automatically removed
89
+ # - **Empty strings** and whitespace-only patterns are filtered out
90
+ # - **Whitespace trimming** is applied to all patterns
91
+ # - **Empty regex patterns** (`//`) are rejected (would match everything)
92
+ # - **Non-array values** are automatically converted to arrays
93
+ #
94
+ # **Invalid Pattern Handling:**
95
+ # - **Invalid regex patterns** (e.g., `/[/`, `/(unclosed`) are silently skipped without crashing
96
+ # - **Invalid arity notation** (e.g., `method/abc`, `method/`) is silently skipped
97
+ # - **Pattern matching is case-sensitive** for both exact names and regex
98
+ #
99
+ # **Operator Method Support:**
100
+ # YARD-Lint fully supports Ruby operator methods including:
101
+ # - Binary operators: `+`, `-`, `*`, `/`, `%`, `**`, `==`, `!=`, `===`, `<`, `>`,
102
+ # `<=`, `>=`, `<=>`, `&`, `|`, `^`, `<<`, `>>`
103
+ # - Unary operators: `+@`, `-@`, `!`, `~`
104
+ # - Other special methods: `[]`, `[]=`, `=~`
105
+ #
106
+ # **Pattern Matching Behavior:**
107
+ # - **Any match excludes**: If a method matches any pattern, it is excluded from validation
108
+ # - **Patterns are evaluated in order** as defined in the configuration
109
+ # - **Exact names have no arity restriction**: `'initialize'` excludes all initialize
110
+ # methods, regardless of parameters
111
+ # - **Arity notation is strict**: `'initialize/0'` only excludes initialize with
112
+ # exactly 0 parameters
113
+ #
114
+ # ## Troubleshooting
115
+ #
116
+ # ### Methods still showing as undocumented
117
+ #
118
+ # 1. Verify the method name matches exactly (case-sensitive)
119
+ # 2. Check if you're using arity notation - ensure the arity count is correct
120
+ # 3. For regex patterns, test your regex independently to ensure it matches
121
+ # 4. Remember: Arity counts `required + optional` parameters, **excluding**
122
+ # splat (`*args`) and block (`&block`)
123
+ #
124
+ # ### Regex patterns not working
125
+ #
126
+ # 1. Ensure you're using `/pattern/` format with forward slashes
127
+ # 2. Test the regex in Ruby: `Regexp.new('your_pattern').match?('method_name')`
128
+ # 3. Escape special regex characters: `\.`, `\(`, `\)`, `\[`, `\]`, etc.
129
+ # 4. Invalid regex patterns are silently skipped - check for syntax errors
130
+ #
131
+ # ### Arity not matching
132
+ #
133
+ # 1. Count parameters correctly: `def method(a, b = 1)` has arity 2 (required + optional)
134
+ # 2. Splat parameters don't count: `def method(a, *rest)` has arity 1
135
+ # 3. Block parameters don't count: `def method(a, &block)` has arity 1
136
+ # 4. Keyword arguments count as individual parameters: `def method(a:, b:)` has arity 2
137
+ #
9
138
  module UndocumentedObjects
10
139
  end
11
140
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Documentation
7
+ module UndocumentedOptions
8
+ # Configuration for UndocumentedOptions validator
9
+ class Config < ::Yard::Lint::Validators::Config
10
+ self.id = :undocumented_options
11
+ self.defaults = {
12
+ 'Enabled' => true,
13
+ 'Severity' => 'warning'
14
+ }.freeze
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Documentation
7
+ module UndocumentedOptions
8
+ # Parses YARD output for undocumented options 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
+ line_number = location_match[2].to_i
27
+ object_name = location_match[3]
28
+
29
+ # Next line contains parameter list
30
+ i += 1
31
+ next unless i < lines.size
32
+
33
+ params = lines[i]
34
+
35
+ violations << {
36
+ location: file_path,
37
+ line: line_number,
38
+ object_name: object_name,
39
+ params: params
40
+ }
41
+ end
42
+
43
+ i += 1
44
+ end
45
+
46
+ violations
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Documentation
7
+ module UndocumentedOptions
8
+ # Result object for undocumented options validation
9
+ class Result < Results::Base
10
+ self.default_severity = 'warning'
11
+ self.offense_type = 'line'
12
+ self.offense_name = 'UndocumentedOptions'
13
+
14
+ # Build human-readable message for undocumented options offense
15
+ # @param offense [Hash] offense data with :object_name and :params
16
+ # @return [String] formatted message
17
+ def build_message(offense)
18
+ object_name = offense[:object_name]
19
+ params = offense[:params]
20
+
21
+ "Method '#{object_name}' has options parameter (#{params}) " \
22
+ 'but no @option tags in documentation.'
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Documentation
7
+ module UndocumentedOptions
8
+ # Validates that methods with options hash parameters have @option tags
9
+ class Validator < Validators::Base
10
+ # YARD query to detect methods with options parameters but no @option tags
11
+ # @return [String] YARD Ruby query code
12
+ def query
13
+ <<~QUERY.strip
14
+ 'if object.is_a?(YARD::CodeObjects::MethodObject); params = object.parameters || []; has_options_param = params.any? { |p| p[0] =~ /^(options?|opts?|kwargs)$/ || p[0] =~ /^\\*\\*/ || (p[0] =~ /^(options?|opts?|kwargs)$/ && p[1] =~ /^\\{\\}/) }; if has_options_param; option_tags = object.tags(:option); if option_tags.empty?; puts object.file + ":" + object.line.to_s + ": " + object.title; puts params.map { |p| p.join(" ") }.join(", "); end; end; end; false'
15
+ QUERY
16
+ end
17
+
18
+ # Builds and executes the YARD command to detect undocumented options
19
+ # @param dir [String] the directory containing the .yardoc database
20
+ # @param file_list_path [String] path to file containing list of files to analyze
21
+ # @return [String] command output
22
+ def yard_cmd(dir, file_list_path)
23
+ cmd = <<~CMD
24
+ cat #{Shellwords.escape(file_list_path)} | xargs yard list \
25
+ #{shell_arguments} \
26
+ --query #{query} \
27
+ -q \
28
+ -b #{Shellwords.escape(dir)}
29
+ CMD
30
+ cmd = cmd.tr("\n", ' ')
31
+ shell(cmd)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Documentation
7
+ # UndocumentedOptions validator
8
+ #
9
+ # Checks that options hashes have detailed documentation about their keys.
10
+ # When a method accepts an options hash parameter, the individual option
11
+ # keys should be documented using `@option` tags. This validator is enabled
12
+ # by default.
13
+ #
14
+ # @example Bad - Options parameter without @option tags
15
+ # # Configures the service
16
+ # # @param options [Hash] configuration options
17
+ # def configure(options)
18
+ # end
19
+ #
20
+ # @example Good - Options keys documented with @option tags
21
+ # # Configures the service
22
+ # # @param options [Hash] configuration options
23
+ # # @option options [Boolean] :enabled Whether to enable the feature
24
+ # # @option options [Integer] :timeout Connection timeout in seconds
25
+ # def configure(options)
26
+ # end
27
+ #
28
+ # ## Configuration
29
+ #
30
+ # To disable this validator:
31
+ #
32
+ # Documentation/UndocumentedOptions:
33
+ # Enabled: false
34
+ #
35
+ module UndocumentedOptions
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end