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
@@ -4,194 +4,23 @@ module Yard
4
4
  module Lint
5
5
  # Generates default .yard-lint.yml configuration file
6
6
  class ConfigGenerator
7
- # Default configuration template
8
- DEFAULT_CONFIG = <<~YAML
9
- # YARD-Lint Configuration
10
- # See https://github.com/mensfeld/yard-lint for documentation
11
-
12
- # Global settings for all validators
13
- AllValidators:
14
- # YARD command-line options (applied to all validators by default)
15
- YardOptions:
16
- - --private
17
- - --protected
18
-
19
- # Global file exclusion patterns
20
- Exclude:
21
- - '\\.git'
22
- - 'vendor/**/*'
23
- - 'node_modules/**/*'
24
- - 'spec/**/*'
25
- - 'test/**/*'
26
-
27
- # Exit code behavior (error, warning, convention, never)
28
- FailOnSeverity: warning
29
-
30
- # Minimum documentation coverage percentage (0-100)
31
- # Fails if coverage is below this threshold
32
- # MinCoverage: 80.0
33
-
34
- # Diff mode settings
35
- DiffMode:
36
- # Default base ref for --diff (auto-detects main/master if not specified)
37
- DefaultBaseRef: ~
38
-
39
- # Documentation validators
40
- Documentation/UndocumentedObjects:
41
- Description: 'Checks for classes, modules, and methods without documentation.'
42
- Enabled: true
43
- Severity: warning
44
- ExcludedMethods:
45
- - 'initialize/0' # Exclude parameter-less initialize
46
- - '/^_/' # Exclude private methods (by convention)
47
-
48
- Documentation/UndocumentedMethodArguments:
49
- Description: 'Checks for method parameters without @param tags.'
50
- Enabled: true
51
- Severity: warning
52
-
53
- Documentation/UndocumentedBooleanMethods:
54
- Description: 'Checks that question mark methods document their boolean return.'
55
- Enabled: true
56
- Severity: warning
57
-
58
- Documentation/UndocumentedOptions:
59
- Description: 'Detects methods with options hash parameters but no @option tags.'
60
- Enabled: true
61
- Severity: warning
62
-
63
- Documentation/MarkdownSyntax:
64
- Description: 'Detects common markdown syntax errors in documentation.'
65
- Enabled: true
66
- Severity: warning
67
-
68
- # Tags validators
69
- Tags/Order:
70
- Description: 'Enforces consistent ordering of YARD tags.'
71
- Enabled: true
72
- Severity: convention
73
- EnforcedOrder:
74
- - param
75
- - option
76
- - return
77
- - raise
78
- - example
79
-
80
- Tags/InvalidTypes:
81
- Description: 'Validates type definitions in @param, @return, @option tags.'
82
- Enabled: true
83
- Severity: warning
84
- ValidatedTags:
85
- - param
86
- - option
87
- - return
88
-
89
- Tags/TypeSyntax:
90
- Description: 'Validates YARD type syntax using YARD parser.'
91
- Enabled: true
92
- Severity: warning
93
- ValidatedTags:
94
- - param
95
- - option
96
- - return
97
- - yieldreturn
98
-
99
- Tags/MeaninglessTag:
100
- Description: 'Detects @param/@option tags on classes, modules, or constants.'
101
- Enabled: true
102
- Severity: warning
103
- CheckedTags:
104
- - param
105
- - option
106
- InvalidObjectTypes:
107
- - class
108
- - module
109
- - constant
110
-
111
- Tags/CollectionType:
112
- Description: 'Validates Hash collection syntax consistency.'
113
- Enabled: true
114
- Severity: convention
115
- EnforcedStyle: long # 'long' for Hash{K => V} (YARD standard), 'short' for {K => V}
116
- ValidatedTags:
117
- - param
118
- - option
119
- - return
120
- - yieldreturn
121
-
122
- Tags/TagTypePosition:
123
- Description: 'Validates type annotation position in tags.'
124
- Enabled: true
125
- Severity: convention
126
- CheckedTags:
127
- - param
128
- - option
129
- # EnforcedStyle: 'type_after_name' (YARD standard: @param name [Type])
130
- # or 'type_first' (@param [Type] name)
131
- EnforcedStyle: type_after_name
132
-
133
- Tags/ApiTags:
134
- Description: 'Enforces @api tags on public objects.'
135
- Enabled: false # Opt-in validator
136
- Severity: warning
137
- AllowedApis:
138
- - public
139
- - private
140
- - internal
141
-
142
- Tags/OptionTags:
143
- Description: 'Requires @option tags for methods with options parameters.'
144
- Enabled: true
145
- Severity: warning
146
-
147
- # Warnings validators - catches YARD parser errors
148
- Warnings/UnknownTag:
149
- Description: 'Detects unknown YARD tags.'
150
- Enabled: true
151
- Severity: error
152
-
153
- Warnings/UnknownDirective:
154
- Description: 'Detects unknown YARD directives.'
155
- Enabled: true
156
- Severity: error
157
-
158
- Warnings/InvalidTagFormat:
159
- Description: 'Detects malformed tag syntax.'
160
- Enabled: true
161
- Severity: error
162
-
163
- Warnings/InvalidDirectiveFormat:
164
- Description: 'Detects malformed directive syntax.'
165
- Enabled: true
166
- Severity: error
167
-
168
- Warnings/DuplicatedParameterName:
169
- Description: 'Detects duplicate @param tags.'
170
- Enabled: true
171
- Severity: error
172
-
173
- Warnings/UnknownParameterName:
174
- Description: 'Detects @param tags for non-existent parameters.'
175
- Enabled: true
176
- Severity: error
177
-
178
- # Semantic validators
179
- Semantic/AbstractMethods:
180
- Description: 'Ensures @abstract methods do not have real implementations.'
181
- Enabled: true
182
- Severity: warning
183
- YAML
7
+ # Path to templates directory
8
+ TEMPLATES_DIR = File.expand_path('templates', __dir__)
184
9
 
185
10
  # Generate .yard-lint.yml file in current directory
186
11
  # @param force [Boolean] overwrite existing file if true
12
+ # @param strict [Boolean] generate strict configuration (all errors, 100% coverage)
187
13
  # @return [Boolean] true if file was created, false if already exists
188
- def self.generate(force: false)
14
+ def self.generate(force: false, strict: false)
189
15
  config_path = File.join(Dir.pwd, Config::DEFAULT_CONFIG_FILE)
190
16
 
191
17
  if File.exist?(config_path) && !force
192
18
  false
193
19
  else
194
- File.write(config_path, DEFAULT_CONFIG)
20
+ template_name = strict ? 'strict_config.yml' : 'default_config.yml'
21
+ template_path = File.join(TEMPLATES_DIR, template_name)
22
+ content = File.read(template_path)
23
+ File.write(config_path, content)
195
24
  true
196
25
  end
197
26
  end
@@ -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