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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +150 -1
- data/README.md +98 -4
- data/Rakefile +20 -0
- data/bin/yard-lint +71 -38
- data/lib/yard/lint/config.rb +5 -0
- data/lib/yard/lint/config_updater.rb +222 -0
- data/lib/yard/lint/errors.rb +6 -0
- data/lib/yard/lint/executor/in_process_registry.rb +130 -0
- data/lib/yard/lint/executor/query_executor.rb +109 -0
- data/lib/yard/lint/executor/result_collector.rb +55 -0
- data/lib/yard/lint/executor/warning_dispatcher.rb +79 -0
- data/lib/yard/lint/ext/irb_notifier_shim.rb +19 -6
- data/lib/yard/lint/results/base.rb +2 -1
- data/lib/yard/lint/runner.rb +50 -38
- data/lib/yard/lint/templates/default_config.yml +105 -0
- data/lib/yard/lint/templates/strict_config.yml +105 -0
- data/lib/yard/lint/validators/base.rb +52 -118
- data/lib/yard/lint/validators/documentation/blank_line_before_definition/config.rb +25 -0
- data/lib/yard/lint/validators/documentation/blank_line_before_definition/messages_builder.rb +39 -0
- data/lib/yard/lint/validators/documentation/blank_line_before_definition/parser.rb +59 -0
- data/lib/yard/lint/validators/documentation/blank_line_before_definition/result.rb +61 -0
- data/lib/yard/lint/validators/documentation/blank_line_before_definition/validator.rb +94 -0
- data/lib/yard/lint/validators/documentation/blank_line_before_definition.rb +63 -0
- data/lib/yard/lint/validators/documentation/empty_comment_line/config.rb +24 -0
- data/lib/yard/lint/validators/documentation/empty_comment_line/messages_builder.rb +34 -0
- data/lib/yard/lint/validators/documentation/empty_comment_line/parser.rb +60 -0
- data/lib/yard/lint/validators/documentation/empty_comment_line/result.rb +25 -0
- data/lib/yard/lint/validators/documentation/empty_comment_line/validator.rb +109 -0
- data/lib/yard/lint/validators/documentation/empty_comment_line.rb +58 -0
- data/lib/yard/lint/validators/documentation/markdown_syntax/validator.rb +36 -21
- data/lib/yard/lint/validators/documentation/markdown_syntax.rb +0 -1
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/validator.rb +19 -29
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods.rb +0 -1
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/validator.rb +18 -34
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments.rb +0 -1
- data/lib/yard/lint/validators/documentation/undocumented_objects/validator.rb +17 -25
- data/lib/yard/lint/validators/documentation/undocumented_objects.rb +4 -5
- data/lib/yard/lint/validators/documentation/undocumented_options/validator.rb +30 -21
- data/lib/yard/lint/validators/documentation/undocumented_options.rb +0 -1
- data/lib/yard/lint/validators/semantic/abstract_methods/result.rb +2 -2
- data/lib/yard/lint/validators/semantic/abstract_methods/validator.rb +31 -43
- data/lib/yard/lint/validators/semantic/abstract_methods.rb +0 -1
- data/lib/yard/lint/validators/tags/api_tags/validator.rb +24 -39
- data/lib/yard/lint/validators/tags/api_tags.rb +0 -1
- data/lib/yard/lint/validators/tags/collection_type/validator.rb +37 -66
- data/lib/yard/lint/validators/tags/collection_type.rb +0 -1
- data/lib/yard/lint/validators/tags/example_syntax/validator.rb +51 -64
- data/lib/yard/lint/validators/tags/example_syntax.rb +0 -1
- data/lib/yard/lint/validators/tags/informal_notation/config.rb +40 -0
- data/lib/yard/lint/validators/tags/informal_notation/messages_builder.rb +35 -0
- data/lib/yard/lint/validators/tags/informal_notation/parser.rb +55 -0
- data/lib/yard/lint/validators/tags/informal_notation/result.rb +26 -0
- data/lib/yard/lint/validators/tags/informal_notation/validator.rb +133 -0
- data/lib/yard/lint/validators/tags/informal_notation.rb +45 -0
- data/lib/yard/lint/validators/tags/invalid_types/validator.rb +57 -70
- data/lib/yard/lint/validators/tags/invalid_types.rb +0 -1
- data/lib/yard/lint/validators/tags/meaningless_tag/validator.rb +22 -54
- data/lib/yard/lint/validators/tags/meaningless_tag.rb +0 -1
- data/lib/yard/lint/validators/tags/non_ascii_type/config.rb +21 -0
- data/lib/yard/lint/validators/tags/non_ascii_type/messages_builder.rb +29 -0
- data/lib/yard/lint/validators/tags/non_ascii_type/parser.rb +59 -0
- data/lib/yard/lint/validators/tags/non_ascii_type/result.rb +25 -0
- data/lib/yard/lint/validators/tags/non_ascii_type/validator.rb +50 -0
- data/lib/yard/lint/validators/tags/non_ascii_type.rb +39 -0
- data/lib/yard/lint/validators/tags/option_tags/result.rb +2 -2
- data/lib/yard/lint/validators/tags/option_tags/validator.rb +25 -40
- data/lib/yard/lint/validators/tags/option_tags.rb +0 -1
- data/lib/yard/lint/validators/tags/order/validator.rb +28 -55
- data/lib/yard/lint/validators/tags/order.rb +0 -1
- data/lib/yard/lint/validators/tags/redundant_param_description/config.rb +15 -1
- data/lib/yard/lint/validators/tags/redundant_param_description/messages_builder.rb +5 -0
- data/lib/yard/lint/validators/tags/redundant_param_description/validator.rb +134 -100
- data/lib/yard/lint/validators/tags/redundant_param_description.rb +0 -1
- data/lib/yard/lint/validators/tags/tag_group_separator/config.rb +29 -0
- data/lib/yard/lint/validators/tags/tag_group_separator/messages_builder.rb +49 -0
- data/lib/yard/lint/validators/tags/tag_group_separator/parser.rb +67 -0
- data/lib/yard/lint/validators/tags/tag_group_separator/result.rb +28 -0
- data/lib/yard/lint/validators/tags/tag_group_separator/validator.rb +117 -0
- data/lib/yard/lint/validators/tags/tag_group_separator.rb +49 -0
- data/lib/yard/lint/validators/tags/tag_type_position/validator.rb +53 -84
- data/lib/yard/lint/validators/tags/tag_type_position.rb +0 -1
- data/lib/yard/lint/validators/tags/type_syntax/parser.rb +7 -2
- data/lib/yard/lint/validators/tags/type_syntax/validator.rb +29 -59
- data/lib/yard/lint/validators/tags/type_syntax.rb +0 -1
- data/lib/yard/lint/validators/warnings/duplicated_parameter_name/validator.rb +1 -18
- data/lib/yard/lint/validators/warnings/invalid_directive_format/validator.rb +1 -18
- data/lib/yard/lint/validators/warnings/invalid_tag_format/validator.rb +1 -18
- data/lib/yard/lint/validators/warnings/unknown_directive/validator.rb +1 -18
- data/lib/yard/lint/validators/warnings/unknown_parameter_name/messages_builder.rb +243 -0
- data/lib/yard/lint/validators/warnings/unknown_parameter_name/result.rb +4 -3
- data/lib/yard/lint/validators/warnings/unknown_parameter_name/validator.rb +1 -18
- data/lib/yard/lint/validators/warnings/unknown_tag/messages_builder.rb +144 -0
- data/lib/yard/lint/validators/warnings/unknown_tag/result.rb +4 -3
- data/lib/yard/lint/validators/warnings/unknown_tag/validator.rb +1 -18
- data/lib/yard/lint/validators/warnings/unknown_tag.rb +10 -0
- data/lib/yard/lint/version.rb +1 -1
- data/lib/yard/lint.rb +81 -13
- data/lib/yard-lint.rb +1 -1
- data/renovate.json +1 -8
- metadata +38 -2
- 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
|
data/lib/yard/lint/errors.rb
CHANGED
|
@@ -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
|
-
#
|
|
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
|
-
#
|
|
8
|
-
#
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|