yard-lint 0.2.1 → 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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +73 -1
  3. data/README.md +121 -89
  4. data/bin/yard-lint +44 -0
  5. data/lib/yard/lint/config.rb +35 -3
  6. data/lib/yard/lint/config_generator.rb +191 -0
  7. data/lib/yard/lint/config_loader.rb +1 -1
  8. data/lib/yard/lint/result_builder.rb +10 -1
  9. data/lib/yard/lint/validators/base.rb +77 -12
  10. data/lib/yard/lint/validators/documentation/markdown_syntax/config.rb +20 -0
  11. data/lib/yard/lint/validators/documentation/markdown_syntax/messages_builder.rb +44 -0
  12. data/lib/yard/lint/validators/documentation/markdown_syntax/parser.rb +53 -0
  13. data/lib/yard/lint/validators/documentation/markdown_syntax/result.rb +25 -0
  14. data/lib/yard/lint/validators/documentation/markdown_syntax/validator.rb +38 -0
  15. data/lib/yard/lint/validators/documentation/markdown_syntax.rb +37 -0
  16. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/validator.rb +7 -6
  17. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods.rb +26 -1
  18. data/lib/yard/lint/validators/documentation/undocumented_method_arguments/validator.rb +4 -5
  19. data/lib/yard/lint/validators/documentation/undocumented_method_arguments.rb +26 -1
  20. data/lib/yard/lint/validators/documentation/undocumented_objects/config.rb +2 -1
  21. data/lib/yard/lint/validators/documentation/undocumented_objects/parser.rb +95 -5
  22. data/lib/yard/lint/validators/documentation/undocumented_objects/validator.rb +15 -11
  23. data/lib/yard/lint/validators/documentation/undocumented_objects.rb +131 -2
  24. data/lib/yard/lint/validators/documentation/undocumented_options/config.rb +20 -0
  25. data/lib/yard/lint/validators/documentation/undocumented_options/parser.rb +53 -0
  26. data/lib/yard/lint/validators/documentation/undocumented_options/result.rb +29 -0
  27. data/lib/yard/lint/validators/documentation/undocumented_options/validator.rb +38 -0
  28. data/lib/yard/lint/validators/documentation/undocumented_options.rb +40 -0
  29. data/lib/yard/lint/validators/semantic/abstract_methods/validator.rb +4 -5
  30. data/lib/yard/lint/validators/semantic/abstract_methods.rb +31 -1
  31. data/lib/yard/lint/validators/tags/api_tags/validator.rb +4 -5
  32. data/lib/yard/lint/validators/tags/api_tags.rb +34 -1
  33. data/lib/yard/lint/validators/tags/collection_type/config.rb +22 -0
  34. data/lib/yard/lint/validators/tags/collection_type/messages_builder.rb +73 -0
  35. data/lib/yard/lint/validators/tags/collection_type/parser.rb +50 -0
  36. data/lib/yard/lint/validators/tags/collection_type/result.rb +25 -0
  37. data/lib/yard/lint/validators/tags/collection_type/validator.rb +92 -0
  38. data/lib/yard/lint/validators/tags/collection_type.rb +50 -0
  39. data/lib/yard/lint/validators/tags/example_syntax/config.rb +20 -0
  40. data/lib/yard/lint/validators/tags/example_syntax/messages_builder.rb +28 -0
  41. data/lib/yard/lint/validators/tags/example_syntax/parser.rb +79 -0
  42. data/lib/yard/lint/validators/tags/example_syntax/result.rb +42 -0
  43. data/lib/yard/lint/validators/tags/example_syntax/validator.rb +88 -0
  44. data/lib/yard/lint/validators/tags/example_syntax.rb +42 -0
  45. data/lib/yard/lint/validators/tags/invalid_types/validator.rb +8 -8
  46. data/lib/yard/lint/validators/tags/invalid_types.rb +25 -1
  47. data/lib/yard/lint/validators/tags/meaningless_tag/config.rb +22 -0
  48. data/lib/yard/lint/validators/tags/meaningless_tag/messages_builder.rb +28 -0
  49. data/lib/yard/lint/validators/tags/meaningless_tag/parser.rb +53 -0
  50. data/lib/yard/lint/validators/tags/meaningless_tag/result.rb +26 -0
  51. data/lib/yard/lint/validators/tags/meaningless_tag/validator.rb +82 -0
  52. data/lib/yard/lint/validators/tags/meaningless_tag.rb +43 -0
  53. data/lib/yard/lint/validators/tags/option_tags/validator.rb +11 -6
  54. data/lib/yard/lint/validators/tags/option_tags.rb +26 -1
  55. data/lib/yard/lint/validators/tags/order/validator.rb +4 -5
  56. data/lib/yard/lint/validators/tags/order.rb +25 -1
  57. data/lib/yard/lint/validators/tags/redundant_param_description/config.rb +33 -0
  58. data/lib/yard/lint/validators/tags/redundant_param_description/messages_builder.rb +61 -0
  59. data/lib/yard/lint/validators/tags/redundant_param_description/parser.rb +67 -0
  60. data/lib/yard/lint/validators/tags/redundant_param_description/result.rb +25 -0
  61. data/lib/yard/lint/validators/tags/redundant_param_description/validator.rb +148 -0
  62. data/lib/yard/lint/validators/tags/redundant_param_description.rb +168 -0
  63. data/lib/yard/lint/validators/tags/tag_type_position/config.rb +22 -0
  64. data/lib/yard/lint/validators/tags/tag_type_position/messages_builder.rb +38 -0
  65. data/lib/yard/lint/validators/tags/tag_type_position/parser.rb +51 -0
  66. data/lib/yard/lint/validators/tags/tag_type_position/result.rb +25 -0
  67. data/lib/yard/lint/validators/tags/tag_type_position/validator.rb +111 -0
  68. data/lib/yard/lint/validators/tags/tag_type_position.rb +51 -0
  69. data/lib/yard/lint/validators/tags/type_syntax/config.rb +21 -0
  70. data/lib/yard/lint/validators/tags/type_syntax/messages_builder.rb +27 -0
  71. data/lib/yard/lint/validators/tags/type_syntax/parser.rb +54 -0
  72. data/lib/yard/lint/validators/tags/type_syntax/result.rb +25 -0
  73. data/lib/yard/lint/validators/tags/type_syntax/validator.rb +76 -0
  74. data/lib/yard/lint/validators/tags/type_syntax.rb +38 -0
  75. data/lib/yard/lint/validators/warnings/duplicated_parameter_name/validator.rb +4 -5
  76. data/lib/yard/lint/validators/warnings/duplicated_parameter_name.rb +26 -1
  77. data/lib/yard/lint/validators/warnings/invalid_directive_format/validator.rb +4 -5
  78. data/lib/yard/lint/validators/warnings/invalid_directive_format.rb +26 -1
  79. data/lib/yard/lint/validators/warnings/invalid_tag_format/validator.rb +4 -5
  80. data/lib/yard/lint/validators/warnings/invalid_tag_format.rb +25 -1
  81. data/lib/yard/lint/validators/warnings/unknown_directive/validator.rb +4 -5
  82. data/lib/yard/lint/validators/warnings/unknown_directive.rb +26 -1
  83. data/lib/yard/lint/validators/warnings/unknown_parameter_name/parser.rb +4 -1
  84. data/lib/yard/lint/validators/warnings/unknown_parameter_name/validator.rb +4 -5
  85. data/lib/yard/lint/validators/warnings/unknown_parameter_name.rb +23 -1
  86. data/lib/yard/lint/validators/warnings/unknown_tag/validator.rb +4 -5
  87. data/lib/yard/lint/validators/warnings/unknown_tag.rb +26 -1
  88. data/lib/yard/lint/version.rb +1 -1
  89. data/lib/yard/lint.rb +1 -0
  90. data/misc/logo.png +0 -0
  91. metadata +64 -1
@@ -109,7 +109,16 @@ module Yard
109
109
  return [] unless stdout
110
110
 
111
111
  parsers = discover_parsers(validator_module)
112
- parsers.flat_map { |parser| parser.new.call(stdout) }
112
+ parsers.flat_map do |parser|
113
+ parser_instance = parser.new
114
+ # Try passing config to parser if it accepts it (for filtering)
115
+ # Otherwise, call without config for backwards compatibility
116
+ begin
117
+ parser_instance.call(stdout, config: config)
118
+ rescue ArgumentError
119
+ parser_instance.call(stdout)
120
+ end
121
+ end
113
122
  end
114
123
 
115
124
  # Auto-discover parser classes in a validator module
@@ -17,12 +17,11 @@ module Yard
17
17
  '--no-progress'
18
18
  ].freeze
19
19
 
20
- # String with a temp dir to store the YARD database
21
- # @note We run YARD multiple times and in order not to rebuild db over and over
22
- # again but reuse the same one, we have a single tmp dir for it
23
- YARDOC_TEMP_DIR = Dir.mktmpdir.freeze
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
24
23
 
25
- private_constant :YARDOC_TEMP_DIR
24
+ private_constant :YARDOC_BASE_TEMP_DIR
26
25
 
27
26
  attr_reader :config, :selection
28
27
 
@@ -42,12 +41,12 @@ module Yard
42
41
  Base.instance_variable_set(:@shared_command_cache, nil)
43
42
  end
44
43
 
45
- # Clear the YARD database (primarily for testing)
44
+ # Clear all YARD databases (primarily for testing)
46
45
  # @return [void]
47
46
  def clear_yard_database!
48
- return unless defined?(YARDOC_TEMP_DIR)
47
+ return unless defined?(YARDOC_BASE_TEMP_DIR)
49
48
 
50
- FileUtils.rm_rf(Dir.glob(File.join(YARDOC_TEMP_DIR, '*')))
49
+ FileUtils.rm_rf(Dir.glob(File.join(YARDOC_BASE_TEMP_DIR, '*')))
51
50
  end
52
51
  end
53
52
 
@@ -66,21 +65,51 @@ module Yard
66
65
  return raw if selection.nil? || selection.empty?
67
66
 
68
67
  # Anything that goes to shell needs to be escaped
69
- escaped_file_names = escape(selection).join(' ')
68
+ escaped_file_names = escape(selection)
70
69
 
71
- yard_cmd(YARDOC_TEMP_DIR, escaped_file_names)
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
72
82
  end
73
83
 
74
84
  private
75
85
 
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
+
76
105
  # @return [String] all arguments with which YARD command should be executed
77
106
  def shell_arguments
78
- validator_name = self.class.name.split('::').then do |parts|
107
+ validator_name = self.class.name&.split('::')&.then do |parts|
79
108
  idx = parts.index('Validators')
80
109
  next config.options unless idx && parts[idx + 1] && parts[idx + 2]
81
110
 
82
111
  "#{parts[idx + 1]}/#{parts[idx + 2]}"
83
- end
112
+ end || config.options
84
113
 
85
114
  yard_options = config.validator_yard_options(validator_name)
86
115
  args = escape(yard_options).join(' ')
@@ -114,6 +143,42 @@ module Yard
114
143
  def shell(cmd)
115
144
  self.class.command_cache.execute(cmd)
116
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
117
182
  end
118
183
  end
119
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
@@ -9,13 +9,15 @@ module Yard
9
9
  # do not have a return type or return description documented
10
10
  class Validator < Base
11
11
  # Query to find all the boolean methods without proper return documentation
12
+ # Requires either: no @return tag, OR @return tag with no types specified
13
+ # Accepts @return [Boolean] without description text as valid documentation
12
14
  QUERY = <<~QUERY.tr("\n", ' ')
13
15
  '
14
16
  type == :method &&
15
17
  !is_alias? &&
16
18
  is_explicit? &&
17
19
  name.to_s.end_with?("?") &&
18
- (tag("return").nil? || tag("return").text.to_s.strip.empty?)
20
+ (tag("return").nil? || tag("return").types.to_a.empty?)
19
21
  '
20
22
  QUERY
21
23
 
@@ -25,16 +27,15 @@ module Yard
25
27
 
26
28
  # Runs yard list query with proper settings on a given dir and files
27
29
  # @param dir [String] dir where we should generate the temp docs
28
- # @param escaped_file_names [String] files for which we want to get the stats
30
+ # @param file_list_path [String] path to temp file containing file paths (one per line)
29
31
  # @return [Hash] shell command execution hash results
30
- def yard_cmd(dir, escaped_file_names)
32
+ def yard_cmd(dir, file_list_path)
31
33
  cmd = <<~CMD
32
- yard list \
34
+ cat #{Shellwords.escape(file_list_path)} | xargs yard list \
33
35
  #{shell_arguments} \
34
36
  --query #{QUERY} \
35
37
  -q \
36
- -b #{Shellwords.escape(dir)} \
37
- #{escaped_file_names}
38
+ -b #{Shellwords.escape(dir)}
38
39
  CMD
39
40
  cmd = cmd.tr("\n", ' ')
40
41
 
@@ -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
@@ -29,19 +29,18 @@ module Yard
29
29
 
30
30
  # Runs yard list query with proper settings on a given dir and files
31
31
  # @param dir [String] dir where we should generate the temp docs
32
- # @param escaped_file_names [String] files for which we want to get the stats
32
+ # @param file_list_path [String] path to temp file containing file paths (one per line)
33
33
  # @return [Hash] shell command execution hash results
34
- def yard_cmd(dir, escaped_file_names)
34
+ def yard_cmd(dir, file_list_path)
35
35
  shell_args = shell_arguments
36
36
  UNWANTED_OPTIONS.each { |opt| shell_args.gsub!(opt, '') }
37
37
 
38
38
  cmd = <<~CMD
39
- yard list \
39
+ cat #{Shellwords.escape(file_list_path)} | xargs yard list \
40
40
  #{shell_args} \
41
41
  --query #{QUERY} \
42
42
  -q \
43
- -b #{Shellwords.escape(dir)} \
44
- #{escaped_file_names}
43
+ -b #{Shellwords.escape(dir)}
45
44
  CMD
46
45
  cmd = cmd.tr("\n", ' ')
47
46
 
@@ -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
@@ -10,7 +10,8 @@ module Yard
10
10
  self.id = :undocumented_objects
11
11
  self.defaults = {
12
12
  'Enabled' => true,
13
- 'Severity' => 'warning'
13
+ 'Severity' => 'warning',
14
+ 'ExcludedMethods' => ['initialize/0']
14
15
  }.freeze
15
16
  self.combines_with = ['Documentation/UndocumentedBooleanMethods'].freeze
16
17
  end
@@ -8,14 +8,28 @@ module Yard
8
8
  # Class used to extract details about undocumented objects from raw yard list output
9
9
  # @example
10
10
  # /path/to/file.rb:3: UndocumentedClass
11
- # /path/to/file.rb:4: UndocumentedClass#method_one
11
+ # /path/to/file.rb:4: UndocumentedClass#method_one|2
12
12
  class Parser < ::Yard::Lint::Parsers::Base
13
- # Regex used to parse yard list output format: file.rb:LINE: ObjectName
14
- LINE_REGEX = /^(.+):(\d+): (.+)$/
13
+ # Regex used to parse yard list output format
14
+ # Format: file.rb:LINE: ObjectName or ObjectName|ARITY
15
+ LINE_REGEX = /^(.+):(\d+): (.+?)(?:\|(\d+))?$/
15
16
 
16
17
  # @param yard_list_output [String] raw yard list results string
18
+ # @param config [Yard::Lint::Config, nil] configuration object (optional)
19
+ # @param _kwargs [Hash] unused keyword arguments (for compatibility)
17
20
  # @return [Array<Hash>] Array with undocumented objects details
18
- def call(yard_list_output)
21
+ def call(yard_list_output, config: nil, **_kwargs)
22
+ excluded_methods = config&.validator_config(
23
+ 'Documentation/UndocumentedObjects',
24
+ 'ExcludedMethods'
25
+ ) || []
26
+
27
+ # Ensure excluded_methods is an Array
28
+ excluded_methods = Array(excluded_methods)
29
+
30
+ # Sanitize patterns: remove nil, empty, whitespace-only, and normalize
31
+ excluded_methods = sanitize_patterns(excluded_methods)
32
+
19
33
  yard_list_output
20
34
  .split("\n")
21
35
  .map(&:strip)
@@ -24,13 +38,89 @@ module Yard
24
38
  match = line.match(LINE_REGEX)
25
39
  next unless match
26
40
 
41
+ element = match[3]
42
+ arity = match[4]&.to_i
43
+
44
+ # Skip if method is in excluded list
45
+ next if method_excluded?(element, arity, excluded_methods)
46
+
27
47
  {
28
48
  location: match[1],
29
49
  line: match[2].to_i,
30
- element: match[3]
50
+ element: element
31
51
  }
32
52
  end
33
53
  end
54
+
55
+ private
56
+
57
+ # Checks if a method should be excluded based on ExcludedMethods config
58
+ # Supports: simple names, arity notation, and regex patterns
59
+ # @param element [String] the element name (e.g., "Class#method")
60
+ # @param arity [Integer, nil] number of parameters (required + optional,
61
+ # excluding splat and block)
62
+ # @param excluded_methods [Array<String>] list of exclusion patterns
63
+ # @return [Boolean] true if method should be excluded
64
+ def method_excluded?(element, arity, excluded_methods)
65
+ # Extract method name from element (e.g., "Foo::Bar#baz" -> "baz")
66
+ method_name = element.split(/[#.]/).last
67
+ return false unless method_name
68
+
69
+ excluded_methods.any? do |pattern|
70
+ case pattern
71
+ when %r{^/(.+)/$}
72
+ # Regex pattern: '/^_/' matches methods starting with _
73
+ match_regex_pattern(method_name, Regexp.last_match(1))
74
+ when %r{/\d+$}
75
+ # Arity pattern: 'initialize/0' checks method name and parameter count
76
+ match_arity_pattern(method_name, arity, pattern)
77
+ else
78
+ # Simple name match: 'initialize'
79
+ # Simple names match any arity (use arity notation for specific arity)
80
+ method_name == pattern
81
+ end
82
+ end
83
+ end
84
+
85
+ # Sanitize exclusion patterns
86
+ # @param patterns [Array] raw patterns from config
87
+ # @return [Array<String>] cleaned and validated patterns
88
+ def sanitize_patterns(patterns)
89
+ patterns
90
+ .compact # Remove nil values
91
+ .map { |p| p.to_s.strip } # Convert to strings and trim whitespace
92
+ .reject(&:empty?) # Remove empty strings
93
+ .reject { |p| p == '//' } # Reject empty regex (matches everything)
94
+ end
95
+
96
+ # Match a regex pattern against method name with error handling
97
+ # @param method_name [String] the method name to match
98
+ # @param regex_pattern [String] the regex pattern (without delimiters)
99
+ # @return [Boolean] true if matches, false if invalid regex or no match
100
+ def match_regex_pattern(method_name, regex_pattern)
101
+ return false if regex_pattern.empty? # Empty regex would match everything
102
+
103
+ Regexp.new(regex_pattern).match?(method_name)
104
+ rescue RegexpError
105
+ # Invalid regex - skip this pattern
106
+ false
107
+ end
108
+
109
+ # Match an arity pattern like "initialize/0"
110
+ # @param method_name [String] the method name
111
+ # @param arity [Integer, nil] the method's arity
112
+ # @param pattern [String] the full pattern like "initialize/0"
113
+ # @return [Boolean] true if matches
114
+ def match_arity_pattern(method_name, arity, pattern)
115
+ pattern_name, pattern_arity_str = pattern.split('/')
116
+
117
+ # Validate arity is numeric
118
+ return false unless pattern_arity_str.match?(/^\d+$/)
119
+
120
+ pattern_arity = pattern_arity_str.to_i
121
+
122
+ method_name == pattern_name && arity == pattern_arity
123
+ end
34
124
  end
35
125
  end
36
126
  end