yard-lint 1.2.3 → 1.3.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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +150 -1
  3. data/README.md +98 -4
  4. data/Rakefile +20 -0
  5. data/bin/yard-lint +71 -38
  6. data/lib/yard/lint/config.rb +5 -0
  7. data/lib/yard/lint/config_updater.rb +222 -0
  8. data/lib/yard/lint/errors.rb +6 -0
  9. data/lib/yard/lint/executor/in_process_registry.rb +130 -0
  10. data/lib/yard/lint/executor/query_executor.rb +109 -0
  11. data/lib/yard/lint/executor/result_collector.rb +55 -0
  12. data/lib/yard/lint/executor/warning_dispatcher.rb +79 -0
  13. data/lib/yard/lint/ext/irb_notifier_shim.rb +19 -6
  14. data/lib/yard/lint/results/base.rb +2 -1
  15. data/lib/yard/lint/runner.rb +50 -38
  16. data/lib/yard/lint/templates/default_config.yml +105 -0
  17. data/lib/yard/lint/templates/strict_config.yml +105 -0
  18. data/lib/yard/lint/validators/base.rb +52 -118
  19. data/lib/yard/lint/validators/documentation/blank_line_before_definition/config.rb +25 -0
  20. data/lib/yard/lint/validators/documentation/blank_line_before_definition/messages_builder.rb +39 -0
  21. data/lib/yard/lint/validators/documentation/blank_line_before_definition/parser.rb +59 -0
  22. data/lib/yard/lint/validators/documentation/blank_line_before_definition/result.rb +61 -0
  23. data/lib/yard/lint/validators/documentation/blank_line_before_definition/validator.rb +94 -0
  24. data/lib/yard/lint/validators/documentation/blank_line_before_definition.rb +63 -0
  25. data/lib/yard/lint/validators/documentation/empty_comment_line/config.rb +24 -0
  26. data/lib/yard/lint/validators/documentation/empty_comment_line/messages_builder.rb +34 -0
  27. data/lib/yard/lint/validators/documentation/empty_comment_line/parser.rb +60 -0
  28. data/lib/yard/lint/validators/documentation/empty_comment_line/result.rb +25 -0
  29. data/lib/yard/lint/validators/documentation/empty_comment_line/validator.rb +109 -0
  30. data/lib/yard/lint/validators/documentation/empty_comment_line.rb +58 -0
  31. data/lib/yard/lint/validators/documentation/markdown_syntax/validator.rb +36 -21
  32. data/lib/yard/lint/validators/documentation/markdown_syntax.rb +0 -1
  33. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/validator.rb +19 -29
  34. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods.rb +0 -1
  35. data/lib/yard/lint/validators/documentation/undocumented_method_arguments/validator.rb +18 -34
  36. data/lib/yard/lint/validators/documentation/undocumented_method_arguments.rb +0 -1
  37. data/lib/yard/lint/validators/documentation/undocumented_objects/validator.rb +17 -25
  38. data/lib/yard/lint/validators/documentation/undocumented_objects.rb +4 -5
  39. data/lib/yard/lint/validators/documentation/undocumented_options/validator.rb +30 -21
  40. data/lib/yard/lint/validators/documentation/undocumented_options.rb +0 -1
  41. data/lib/yard/lint/validators/semantic/abstract_methods/result.rb +2 -2
  42. data/lib/yard/lint/validators/semantic/abstract_methods/validator.rb +31 -43
  43. data/lib/yard/lint/validators/semantic/abstract_methods.rb +0 -1
  44. data/lib/yard/lint/validators/tags/api_tags/validator.rb +24 -39
  45. data/lib/yard/lint/validators/tags/api_tags.rb +0 -1
  46. data/lib/yard/lint/validators/tags/collection_type/validator.rb +37 -66
  47. data/lib/yard/lint/validators/tags/collection_type.rb +0 -1
  48. data/lib/yard/lint/validators/tags/example_syntax/validator.rb +51 -64
  49. data/lib/yard/lint/validators/tags/example_syntax.rb +0 -1
  50. data/lib/yard/lint/validators/tags/informal_notation/config.rb +40 -0
  51. data/lib/yard/lint/validators/tags/informal_notation/messages_builder.rb +35 -0
  52. data/lib/yard/lint/validators/tags/informal_notation/parser.rb +55 -0
  53. data/lib/yard/lint/validators/tags/informal_notation/result.rb +26 -0
  54. data/lib/yard/lint/validators/tags/informal_notation/validator.rb +133 -0
  55. data/lib/yard/lint/validators/tags/informal_notation.rb +45 -0
  56. data/lib/yard/lint/validators/tags/invalid_types/validator.rb +57 -70
  57. data/lib/yard/lint/validators/tags/invalid_types.rb +0 -1
  58. data/lib/yard/lint/validators/tags/meaningless_tag/validator.rb +22 -54
  59. data/lib/yard/lint/validators/tags/meaningless_tag.rb +0 -1
  60. data/lib/yard/lint/validators/tags/non_ascii_type/config.rb +21 -0
  61. data/lib/yard/lint/validators/tags/non_ascii_type/messages_builder.rb +29 -0
  62. data/lib/yard/lint/validators/tags/non_ascii_type/parser.rb +59 -0
  63. data/lib/yard/lint/validators/tags/non_ascii_type/result.rb +25 -0
  64. data/lib/yard/lint/validators/tags/non_ascii_type/validator.rb +50 -0
  65. data/lib/yard/lint/validators/tags/non_ascii_type.rb +39 -0
  66. data/lib/yard/lint/validators/tags/option_tags/result.rb +2 -2
  67. data/lib/yard/lint/validators/tags/option_tags/validator.rb +25 -40
  68. data/lib/yard/lint/validators/tags/option_tags.rb +0 -1
  69. data/lib/yard/lint/validators/tags/order/validator.rb +28 -55
  70. data/lib/yard/lint/validators/tags/order.rb +0 -1
  71. data/lib/yard/lint/validators/tags/redundant_param_description/config.rb +15 -1
  72. data/lib/yard/lint/validators/tags/redundant_param_description/messages_builder.rb +5 -0
  73. data/lib/yard/lint/validators/tags/redundant_param_description/validator.rb +134 -100
  74. data/lib/yard/lint/validators/tags/redundant_param_description.rb +0 -1
  75. data/lib/yard/lint/validators/tags/tag_group_separator/config.rb +29 -0
  76. data/lib/yard/lint/validators/tags/tag_group_separator/messages_builder.rb +49 -0
  77. data/lib/yard/lint/validators/tags/tag_group_separator/parser.rb +67 -0
  78. data/lib/yard/lint/validators/tags/tag_group_separator/result.rb +28 -0
  79. data/lib/yard/lint/validators/tags/tag_group_separator/validator.rb +117 -0
  80. data/lib/yard/lint/validators/tags/tag_group_separator.rb +49 -0
  81. data/lib/yard/lint/validators/tags/tag_type_position/validator.rb +53 -84
  82. data/lib/yard/lint/validators/tags/tag_type_position.rb +0 -1
  83. data/lib/yard/lint/validators/tags/type_syntax/parser.rb +7 -2
  84. data/lib/yard/lint/validators/tags/type_syntax/validator.rb +29 -59
  85. data/lib/yard/lint/validators/tags/type_syntax.rb +0 -1
  86. data/lib/yard/lint/validators/warnings/duplicated_parameter_name/validator.rb +1 -18
  87. data/lib/yard/lint/validators/warnings/invalid_directive_format/validator.rb +1 -18
  88. data/lib/yard/lint/validators/warnings/invalid_tag_format/validator.rb +1 -18
  89. data/lib/yard/lint/validators/warnings/unknown_directive/validator.rb +1 -18
  90. data/lib/yard/lint/validators/warnings/unknown_parameter_name/messages_builder.rb +243 -0
  91. data/lib/yard/lint/validators/warnings/unknown_parameter_name/result.rb +4 -3
  92. data/lib/yard/lint/validators/warnings/unknown_parameter_name/validator.rb +1 -18
  93. data/lib/yard/lint/validators/warnings/unknown_tag/messages_builder.rb +144 -0
  94. data/lib/yard/lint/validators/warnings/unknown_tag/result.rb +4 -3
  95. data/lib/yard/lint/validators/warnings/unknown_tag/validator.rb +1 -18
  96. data/lib/yard/lint/validators/warnings/unknown_tag.rb +10 -0
  97. data/lib/yard/lint/version.rb +1 -1
  98. data/lib/yard/lint.rb +81 -13
  99. data/lib/yard-lint.rb +1 -1
  100. data/renovate.json +1 -8
  101. metadata +38 -2
  102. data/lib/yard/lint/command_cache.rb +0 -93
@@ -0,0 +1,222 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ # Updates existing .yard-lint.yml configuration files
6
+ # Adds new validators, removes obsolete ones, preserves user settings
7
+ class ConfigUpdater
8
+ # Path to templates directory
9
+ TEMPLATES_DIR = File.expand_path('templates', __dir__)
10
+
11
+ # Category order for output
12
+ CATEGORY_ORDER = %w[Documentation Tags Warnings Semantic].freeze
13
+
14
+ # Category comments for output
15
+ CATEGORY_COMMENTS = {
16
+ 'Documentation' => '# Documentation validators',
17
+ 'Tags' => '# Tags validators',
18
+ 'Warnings' => '# Warnings validators - catches YARD parser errors',
19
+ 'Semantic' => '# Semantic validators'
20
+ }.freeze
21
+
22
+ class << self
23
+ # Update an existing config file
24
+ # @param path [String] path to config file (default: .yard-lint.yml in current dir)
25
+ # @param strict [Boolean] use strict template as base for new validators
26
+ # @return [Hash] result hash with :added, :removed, :preserved arrays
27
+ def update(path: nil, strict: false)
28
+ new(path: path, strict: strict).update
29
+ end
30
+ end
31
+
32
+ # @param path [String, nil] path to config file
33
+ # @param strict [Boolean] use strict template
34
+ def initialize(path: nil, strict: false)
35
+ @path = path || File.join(Dir.pwd, Config::DEFAULT_CONFIG_FILE)
36
+ @strict = strict
37
+ end
38
+
39
+ # Perform the config update
40
+ # @return [Hash] result with :added, :removed, :preserved arrays
41
+ def update
42
+ validate_file_exists!
43
+
44
+ existing_config = load_existing_config
45
+ template_config = load_template_config
46
+
47
+ result = merge_configs(existing_config, template_config)
48
+
49
+ write_updated_config(result[:config])
50
+
51
+ {
52
+ added: result[:added],
53
+ removed: result[:removed],
54
+ preserved: result[:preserved]
55
+ }
56
+ end
57
+
58
+ private
59
+
60
+ # Validate that the config file exists
61
+ # @raise [Errors::ConfigFileNotFoundError] if file doesn't exist
62
+ def validate_file_exists!
63
+ return if File.exist?(@path)
64
+
65
+ raise Errors::ConfigFileNotFoundError,
66
+ "Config file not found: #{@path}. Use --init to create one."
67
+ end
68
+
69
+ # Load the existing user config
70
+ # @return [Hash] parsed YAML config
71
+ def load_existing_config
72
+ YAML.load_file(@path) || {}
73
+ end
74
+
75
+ # Load the template config
76
+ # @return [Hash] parsed template YAML
77
+ def load_template_config
78
+ template_name = @strict ? 'strict_config.yml' : 'default_config.yml'
79
+ template_path = File.join(TEMPLATES_DIR, template_name)
80
+ YAML.load_file(template_path)
81
+ end
82
+
83
+ # Merge existing config with template to add new and remove obsolete validators
84
+ # @param existing [Hash] existing user config
85
+ # @param template [Hash] template config
86
+ # @return [Hash] result with :config, :added, :removed, :preserved
87
+ def merge_configs(existing, template)
88
+ current_validators = ConfigLoader::ALL_VALIDATORS
89
+ existing_validators = extract_validator_keys(existing)
90
+
91
+ # Calculate changes
92
+ added = (current_validators - existing_validators).sort
93
+ removed = (existing_validators - current_validators).sort
94
+ preserved = (existing_validators & current_validators).sort
95
+
96
+ # Build merged config
97
+ merged = {}
98
+
99
+ # Copy AllValidators from existing, falling back to template
100
+ merged['AllValidators'] = existing['AllValidators'] || template['AllValidators']
101
+
102
+ # Process validators in template order (which has proper category grouping)
103
+ template.each_key do |key|
104
+ next if key == 'AllValidators'
105
+ next unless key.include?('/')
106
+ next unless current_validators.include?(key)
107
+
108
+ merged[key] = if existing.key?(key)
109
+ # Preserve existing user config, but merge with template defaults
110
+ merge_validator_config(template[key], existing[key])
111
+ else
112
+ # New validator - use template defaults
113
+ template[key].dup
114
+ end
115
+ end
116
+
117
+ {
118
+ config: merged,
119
+ added: added,
120
+ removed: removed,
121
+ preserved: preserved
122
+ }
123
+ end
124
+
125
+ # Extract validator keys (keys containing '/') from config
126
+ # @param config [Hash] configuration hash
127
+ # @return [Array<String>] validator keys
128
+ def extract_validator_keys(config)
129
+ config.keys.select { |k| k.include?('/') }
130
+ end
131
+
132
+ # Merge template defaults with user config
133
+ # User config takes precedence
134
+ # @param template_config [Hash] template validator config
135
+ # @param user_config [Hash] user validator config
136
+ # @return [Hash] merged config
137
+ def merge_validator_config(template_config, user_config)
138
+ result = template_config.dup
139
+ user_config.each do |key, value|
140
+ result[key] = value
141
+ end
142
+ result
143
+ end
144
+
145
+ # Write the updated config to file
146
+ # @param config [Hash] merged config to write
147
+ def write_updated_config(config)
148
+ content = generate_yaml_content(config)
149
+ File.write(@path, content)
150
+ end
151
+
152
+ # Generate YAML content with proper formatting and section comments
153
+ # @param config [Hash] merged configuration with AllValidators and validator sections
154
+ # @return [String] formatted YAML content
155
+ def generate_yaml_content(config)
156
+ lines = []
157
+ lines << '# YARD-Lint Configuration'
158
+ lines << '# See https://github.com/mensfeld/yard-lint for documentation'
159
+ lines << ''
160
+
161
+ # Write AllValidators section
162
+ if config['AllValidators']
163
+ lines << '# Global settings for all validators'
164
+ lines << 'AllValidators:'
165
+ lines.concat(yaml_hash_lines(config['AllValidators'], indent: 2))
166
+ lines << ''
167
+ end
168
+
169
+ # Group validators by category
170
+ categories = group_by_category(config)
171
+
172
+ # Write each category in order
173
+ CATEGORY_ORDER.each do |category|
174
+ validators = categories[category]
175
+ next unless validators&.any?
176
+
177
+ lines << CATEGORY_COMMENTS[category] if CATEGORY_COMMENTS[category]
178
+
179
+ validators.each do |validator_name, validator_config|
180
+ lines << "#{validator_name}:"
181
+ lines.concat(yaml_hash_lines(validator_config, indent: 2))
182
+ lines << ''
183
+ end
184
+ end
185
+
186
+ lines.join("\n")
187
+ end
188
+
189
+ # Group validators by their category
190
+ # @param config [Hash] config with validator keys
191
+ # @return [Hash{String => Hash}] validators grouped by category
192
+ def group_by_category(config)
193
+ categories = Hash.new { |h, k| h[k] = {} }
194
+
195
+ config.each do |key, value|
196
+ next unless key.include?('/')
197
+
198
+ category = key.split('/').first
199
+ categories[category][key] = value
200
+ end
201
+
202
+ categories
203
+ end
204
+
205
+ # Convert a hash to indented YAML lines
206
+ # @param hash [Hash] validator or AllValidators configuration to format as YAML
207
+ # @param indent [Integer] number of spaces to indent
208
+ # @return [Array<String>] indented YAML lines
209
+ def yaml_hash_lines(hash, indent:)
210
+ yaml_str = hash.to_yaml
211
+ # Remove the "---\n" header and convert to indented lines
212
+ yaml_str.lines[1..].map do |line|
213
+ if line.strip.empty?
214
+ ''
215
+ else
216
+ "#{' ' * indent}#{line.rstrip}"
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
@@ -12,6 +12,12 @@ module Yard
12
12
 
13
13
  # Raised when a circular dependency is detected in configuration inheritance
14
14
  class CircularDependencyError < BaseError; end
15
+
16
+ # Raised when an unknown validator name is specified via --only
17
+ class UnknownValidatorError < BaseError; end
18
+
19
+ # Raised when a specified file or directory does not exist
20
+ class FileNotFoundError < BaseError; end
15
21
  end
16
22
  end
17
23
  end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ # In-process execution components for YARD validation.
6
+ # Provides registry management, query execution, and result collection
7
+ # for running validators within the same Ruby process.
8
+ module Executor
9
+ # Manages shared YARD::Registry for in-process execution.
10
+ # Ensures files are parsed once and shared across all validators.
11
+ class InProcessRegistry
12
+ # @return [Array<String>] warnings captured during parsing
13
+ attr_reader :warnings
14
+
15
+ def initialize
16
+ @parsed = false
17
+ @warnings = []
18
+ @mutex = Mutex.new
19
+ end
20
+
21
+ # Parse Ruby source files and populate the YARD registry.
22
+ # Captures any warnings emitted by YARD during parsing for later dispatch.
23
+ # @param files [Array<String>] absolute or relative paths to Ruby source files
24
+ # @return [void]
25
+ def parse(files)
26
+ @mutex.synchronize do
27
+ return if @parsed
28
+
29
+ YARD::Registry.clear
30
+
31
+ # Suppress YARD's default output by setting log level high
32
+ # YARD uses its own logging levels, 4 is ERROR/FATAL level
33
+ original_level = YARD::Logger.instance.level
34
+ YARD::Logger.instance.level = 4 # Only show fatal errors
35
+
36
+ @warnings = capture_warnings { YARD.parse(files) }
37
+ @parsed = true
38
+
39
+ YARD::Logger.instance.level = original_level
40
+ end
41
+ end
42
+
43
+ # Check if registry has been parsed
44
+ # @return [Boolean]
45
+ def parsed?
46
+ @parsed
47
+ end
48
+
49
+ # Get all objects from the registry
50
+ # @return [Array<YARD::CodeObjects::Base>]
51
+ def all_objects
52
+ YARD::Registry.all
53
+ end
54
+
55
+ # Get objects filtered for a specific validator
56
+ # @param visibility [Symbol] visibility filter, either :all or :public
57
+ # @param file_excludes [Array<String>] glob patterns for files to exclude
58
+ # @param file_selection [Array<String>, nil] specific files to include (nil = all files)
59
+ # @return [Array<YARD::CodeObjects::Base>]
60
+ def objects_for_validator(visibility:, file_excludes: [], file_selection: nil)
61
+ objects = all_objects
62
+
63
+ # Filter by visibility
64
+ unless visibility == :all
65
+ objects = objects.select do |obj|
66
+ !obj.respond_to?(:visibility) || obj.visibility == :public
67
+ end
68
+ end
69
+
70
+ # Filter by file selection (if provided)
71
+ if file_selection && !file_selection.empty?
72
+ expanded_selection = file_selection.to_set { |f| File.expand_path(f) }
73
+ objects = objects.select do |obj|
74
+ obj.file && expanded_selection.include?(File.expand_path(obj.file))
75
+ end
76
+ end
77
+
78
+ # Filter by file excludes
79
+ unless file_excludes.empty?
80
+ objects = objects.reject do |obj|
81
+ next false unless obj.file
82
+
83
+ file_excludes.any? do |pattern|
84
+ File.fnmatch(pattern, obj.file, File::FNM_PATHNAME | File::FNM_EXTGLOB)
85
+ end
86
+ end
87
+ end
88
+
89
+ objects
90
+ end
91
+
92
+ # Clear the registry and reset state
93
+ # @return [void]
94
+ def clear!
95
+ @mutex.synchronize do
96
+ YARD::Registry.clear
97
+ @parsed = false
98
+ @warnings = []
99
+ end
100
+ end
101
+
102
+ private
103
+
104
+ # Capture warnings during a block execution
105
+ # @yield Block to execute while capturing warnings
106
+ # @return [Array<String>] captured warnings
107
+ def capture_warnings
108
+ captured = []
109
+
110
+ # Store original warn method
111
+ original_warn = YARD::Logger.instance.method(:warn)
112
+
113
+ # Override warn to capture warnings
114
+ YARD::Logger.instance.define_singleton_method(:warn) do |*args|
115
+ message = args.first.to_s
116
+ captured << message
117
+ original_warn.call(*args)
118
+ end
119
+
120
+ yield
121
+
122
+ captured
123
+ ensure
124
+ # Restore original warn method
125
+ YARD::Logger.instance.define_singleton_method(:warn, original_warn) if original_warn
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Executor
6
+ # Executes validator queries against the shared registry.
7
+ # Handles visibility filtering and file exclusions per validator.
8
+ class QueryExecutor
9
+ # @param registry [InProcessRegistry] the shared registry instance
10
+ def initialize(registry)
11
+ @registry = registry
12
+ end
13
+
14
+ # Execute a validator's query against the registry
15
+ # @param validator [Validators::Base] validator instance with in_process_query method
16
+ # @param file_selection [Array<String>] files to include in the query
17
+ # @return [Hash] result hash with :stdout, :stderr, :exit_code keys
18
+ def execute(validator, file_selection: nil)
19
+ validator_name = validator.class.validator_name
20
+
21
+ # Determine visibility: if config has --private/--protected, use :all
22
+ visibility = determine_visibility(validator)
23
+
24
+ # Get file excludes from config
25
+ excludes = if validator_name && validator.config
26
+ validator.config.validator_exclude(validator_name)
27
+ else
28
+ []
29
+ end
30
+
31
+ objects = @registry.objects_for_validator(
32
+ visibility: visibility,
33
+ file_excludes: excludes,
34
+ file_selection: file_selection
35
+ )
36
+
37
+ collector = ResultCollector.new
38
+
39
+ objects.each do |object|
40
+ # Skip objects without file/line info
41
+ next unless object.file && object.line
42
+
43
+ execute_query_for_object(validator, object, collector)
44
+ end
45
+
46
+ build_result(collector)
47
+ end
48
+
49
+ private
50
+
51
+ # Execute query for a single object, handling errors gracefully
52
+ # @param validator [Validators::Base] validator instance
53
+ # @param object [YARD::CodeObjects::Base] code object to query
54
+ # @param collector [ResultCollector] output collector
55
+ # @return [void]
56
+ def execute_query_for_object(validator, object, collector)
57
+ validator.in_process_query(object, collector)
58
+ rescue NotImplementedError, NoMethodError
59
+ # These indicate bugs in validator implementation - re-raise them
60
+ raise
61
+ rescue StandardError => e
62
+ # Skip objects that cause data-related errors (mirrors YARD CLI behavior).
63
+ # Some code objects may have malformed data that causes errors during validation.
64
+ # We log these in debug mode but don't fail the entire validator run.
65
+ return unless ENV['DEBUG']
66
+
67
+ warn "[YARD::Lint] Validator #{validator.class} error on #{object.path}: #{e.class}: #{e.message}"
68
+ end
69
+
70
+ # Build the result hash matching shell command output format
71
+ # @param collector [ResultCollector] output collector
72
+ # @return [Hash] result hash with :stdout, :stderr, :exit_code keys
73
+ def build_result(collector)
74
+ {
75
+ stdout: collector.to_stdout,
76
+ stderr: '',
77
+ exit_code: 0
78
+ }
79
+ end
80
+
81
+ # Determine visibility setting based on validator and config
82
+ # If config has --private or --protected in YardOptions, use :all
83
+ # If config explicitly sets empty YardOptions, use :public (override validator default)
84
+ # Otherwise use the validator's declared visibility
85
+ # @param validator [Validators::Base] validator instance
86
+ # @return [Symbol] visibility setting (:public or :all)
87
+ def determine_visibility(validator)
88
+ return validator.class.in_process_visibility || :public unless validator.config
89
+
90
+ validator_name = validator.class.validator_name
91
+ yard_options = validator.config.validator_yard_options(validator_name)
92
+
93
+ # If YardOptions contains --private or --protected, use :all visibility
94
+ if yard_options.any? { |opt| opt.include?('--private') || opt.include?('--protected') }
95
+ return :all
96
+ end
97
+
98
+ # Check if validator has explicit YardOptions set in config
99
+ # If explicitly set (even to empty), respect that choice and use :public
100
+ validator_cfg = validator.config.validators[validator_name] || {}
101
+ return :public if validator_cfg.key?('YardOptions')
102
+
103
+ # No explicit YardOptions - fall back to validator's declared visibility
104
+ validator.class.in_process_visibility || :public
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Executor
6
+ # Collects query output in a format matching shell stdout.
7
+ # Used by validators to write their results during in-process execution.
8
+ class ResultCollector
9
+ def initialize
10
+ @lines = []
11
+ @mutex = Mutex.new
12
+ end
13
+
14
+ # Add a single line to the output
15
+ # @param line [String, Object] content to add (will be converted to string)
16
+ # @return [void]
17
+ def puts(line)
18
+ @mutex.synchronize { @lines << line.to_s }
19
+ end
20
+
21
+ # Add multiple lines by splitting content on newlines.
22
+ # Each line is added separately to the output buffer.
23
+ # @param content [String] multiline string to split and add
24
+ # @return [void]
25
+ def print(content)
26
+ content.to_s.each_line { |line| puts(line.chomp) }
27
+ end
28
+
29
+ # Get the collected output as a single string
30
+ # @return [String] all lines joined with newlines
31
+ def to_stdout
32
+ @lines.join("\n")
33
+ end
34
+
35
+ # Check if any output has been collected
36
+ # @return [Boolean]
37
+ def empty?
38
+ @lines.empty?
39
+ end
40
+
41
+ # Get the number of lines collected
42
+ # @return [Integer]
43
+ def size
44
+ @lines.size
45
+ end
46
+
47
+ # Clear all collected output
48
+ # @return [void]
49
+ def clear!
50
+ @mutex.synchronize { @lines.clear }
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Executor
6
+ # Routes captured YARD warnings to appropriate warning validators.
7
+ # Uses the same regex patterns as the existing parsers to ensure consistency.
8
+ class WarningDispatcher
9
+ # Patterns matching the 'general' regexps from each warning validator's parser.
10
+ # These patterns identify which validator should handle each warning.
11
+ PATTERNS = {
12
+ 'Warnings/UnknownTag' => /^\[warn\]: Unknown tag.*@.*near line/,
13
+ 'Warnings/UnknownParameterName' => /^\[warn\]: @param tag has unknown parameter name/,
14
+ 'Warnings/DuplicatedParameterName' => /^\[warn\]: @param tag has duplicate parameter name/,
15
+ 'Warnings/UnknownDirective' => /^\[warn\]: Unknown directive.*@!.*near line/,
16
+ 'Warnings/InvalidTagFormat' => /^\[warn\]: Invalid tag format/,
17
+ 'Warnings/InvalidDirectiveFormat' => /^\[warn\]: Invalid directive format/
18
+ }.freeze
19
+
20
+ # Dispatch warnings to appropriate validators
21
+ # @param warnings [Array<String>] raw warning messages from YARD
22
+ # @return [Hash{String => Array<String>}] warnings grouped by validator name
23
+ def dispatch(warnings)
24
+ grouped = Hash.new { |h, k| h[k] = [] }
25
+
26
+ warnings.each do |warning|
27
+ # Format the warning as YARD outputs it
28
+ formatted = format_warning(warning)
29
+
30
+ PATTERNS.each do |validator_name, pattern|
31
+ if formatted.match?(pattern)
32
+ grouped[validator_name] << formatted
33
+ break
34
+ end
35
+ end
36
+ end
37
+
38
+ grouped
39
+ end
40
+
41
+ # Format a raw warning to match YARD's stderr output format
42
+ # @param warning [String] raw warning message
43
+ # @return [String] formatted warning
44
+ def format_warning(warning)
45
+ # If the warning already has [warn]: prefix, return as-is
46
+ return warning if warning.start_with?('[warn]:')
47
+
48
+ "[warn]: #{warning}"
49
+ end
50
+
51
+ # Build result hash for a validator from its dispatched warnings
52
+ # The warnings are put in stdout because the ResultBuilder reads from stdout
53
+ # (in shell mode, YARD outputs warnings to stderr but they get combined)
54
+ # @param warnings [Array<String>] warnings for this validator
55
+ # @return [Hash] result hash with :stdout, :stderr, :exit_code keys
56
+ def format_for_validator(warnings)
57
+ {
58
+ stdout: warnings.join("\n"),
59
+ stderr: '',
60
+ exit_code: 0
61
+ }
62
+ end
63
+
64
+ # Check if a validator is a warning validator (handled by dispatcher)
65
+ # @param validator_name [String] full validator name
66
+ # @return [Boolean]
67
+ def warning_validator?(validator_name)
68
+ PATTERNS.key?(validator_name)
69
+ end
70
+
71
+ # Get all warning validator names
72
+ # @return [Array<String>]
73
+ def warning_validator_names
74
+ PATTERNS.keys
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -1,16 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Shim for IRB::Notifier to avoid IRB dependency in Ruby 3.5+
3
+ # Shim for IRB::Notifier to avoid IRB dependency in Ruby 3.5+/4.0+
4
4
  #
5
- # YARD's legacy parser vendors old IRB code that depends on IRB::Notifier.
5
+ # WHY THIS SHIM IS NEEDED:
6
6
  # In Ruby 3.5+, IRB is no longer part of the default gems and must be explicitly installed.
7
- # This shim provides just enough functionality to keep YARD's legacy parser working
8
- # without requiring the full IRB gem as a dependency.
7
+ # YARD's codebase has a dependency chain that triggers `require "irb/notifier"` even when
8
+ # using the modern Ripper-based parser:
9
9
  #
10
- # The notifier is only used for debug output in YARD's legacy parser, which we don't need.
10
+ # @!attribute directive parsing
11
+ # → YARD::Tags::OverloadTag#parse_signature
12
+ # → YARD::Parser::Ruby::Legacy::TokenList
13
+ # → ruby_lex.rb
14
+ # → irb/slex.rb
15
+ # → require "irb/notifier" ← FAILS without IRB gem or this shim
16
+ #
17
+ # This happens because YARD uses its legacy TokenList for parsing attribute signatures,
18
+ # regardless of which main parser is selected. Until YARD removes this dependency,
19
+ # this shim is required for Ruby 3.5+/4.0+ compatibility.
20
+ #
21
+ # WHAT THIS SHIM DOES:
22
+ # Provides a minimal no-op implementation of IRB::Notifier that satisfies YARD's
23
+ # requirements. The notifier is only used for debug output which we don't need.
11
24
  #
12
25
  # IMPORTANT: This shim only loads if IRB::Notifier is not already defined.
13
- # If IRB gem is present, we use the real implementation instead.
26
+ # If the IRB gem is present, we use the real implementation instead.
14
27
 
15
28
  # Only load the shim if IRB::Notifier is not already defined
16
29
  unless defined?(IRB::Notifier)
@@ -55,7 +55,8 @@ module Yard
55
55
  # Set default values for base class
56
56
  self.offense_type = 'line'
57
57
 
58
- attr_reader :offenses, :config
58
+ attr_accessor :offenses
59
+ attr_reader :config
59
60
 
60
61
  # Initialize a result object with parsed validator data
61
62
  # @param parsed_data [Array<Hash>] Array of offense hashes from validator parser