yard-lint 1.3.0 → 1.5.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/.ruby-version +1 -0
- data/CHANGELOG.md +73 -3
- data/README.md +154 -527
- data/Rakefile +11 -8
- data/bin/yard-lint +64 -5
- data/lib/yard/lint/config.rb +4 -0
- data/lib/yard/lint/config_validator.rb +230 -0
- data/lib/yard/lint/errors.rb +6 -0
- data/lib/yard/lint/executor/in_process_registry.rb +9 -0
- data/lib/yard/lint/path_grouper.rb +70 -0
- data/lib/yard/lint/result_builder.rb +19 -5
- data/lib/yard/lint/results/base.rb +3 -3
- data/lib/yard/lint/templates/default_config.yml +31 -0
- data/lib/yard/lint/templates/strict_config.yml +31 -0
- data/lib/yard/lint/todo_generator.rb +261 -0
- data/lib/yard/lint/validators/base.rb +1 -1
- data/lib/yard/lint/validators/documentation/missing_return/config.rb +23 -0
- data/lib/yard/lint/validators/documentation/missing_return/messages_builder.rb +23 -0
- data/lib/yard/lint/validators/documentation/missing_return/parser.rb +128 -0
- data/lib/yard/lint/validators/documentation/missing_return/result.rb +25 -0
- data/lib/yard/lint/validators/documentation/missing_return/validator.rb +40 -0
- data/lib/yard/lint/validators/documentation/missing_return.rb +49 -0
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/parser.rb +1 -1
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/parser.rb +1 -1
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/validator.rb +3 -0
- data/lib/yard/lint/validators/documentation/undocumented_objects/parser.rb +1 -1
- data/lib/yard/lint/validators/tags/collection_type/messages_builder.rb +8 -2
- data/lib/yard/lint/validators/tags/collection_type/validator.rb +33 -14
- data/lib/yard/lint/validators/tags/example_style/config.rb +33 -0
- data/lib/yard/lint/validators/tags/example_style/linter_detector.rb +71 -0
- data/lib/yard/lint/validators/tags/example_style/messages_builder.rb +29 -0
- data/lib/yard/lint/validators/tags/example_style/parser.rb +88 -0
- data/lib/yard/lint/validators/tags/example_style/result.rb +42 -0
- data/lib/yard/lint/validators/tags/example_style/rubocop_runner.rb +210 -0
- data/lib/yard/lint/validators/tags/example_style/validator.rb +87 -0
- data/lib/yard/lint/validators/tags/example_style.rb +61 -0
- data/lib/yard/lint/validators/tags/forbidden_tags/config.rb +21 -0
- data/lib/yard/lint/validators/tags/forbidden_tags/messages_builder.rb +34 -0
- data/lib/yard/lint/validators/tags/forbidden_tags/parser.rb +51 -0
- data/lib/yard/lint/validators/tags/forbidden_tags/result.rb +28 -0
- data/lib/yard/lint/validators/tags/forbidden_tags/validator.rb +66 -0
- data/lib/yard/lint/validators/tags/forbidden_tags.rb +68 -0
- data/lib/yard/lint/validators/tags/invalid_types/validator.rb +1 -1
- data/lib/yard/lint/validators/tags/tag_group_separator/parser.rb +1 -1
- data/lib/yard/lint/validators/tags/type_syntax/validator.rb +19 -0
- data/lib/yard/lint/validators/warnings/unknown_tag/parser.rb +1 -1
- data/lib/yard/lint/version.rb +1 -1
- data/mise.toml +2 -0
- data/package-lock.json +329 -0
- data/package.json +7 -0
- data/proxy_types +0 -0
- data/renovate.json +18 -1
- metadata +30 -3
- data/.coditsu/ci.yml +0 -3
data/Rakefile
CHANGED
|
@@ -3,8 +3,15 @@
|
|
|
3
3
|
require 'bundler/setup'
|
|
4
4
|
require 'bundler/gem_tasks'
|
|
5
5
|
require 'etc'
|
|
6
|
+
require 'rake/testtask'
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
Rake::TestTask.new(:test) do |t|
|
|
9
|
+
t.libs << 'test'
|
|
10
|
+
t.ruby_opts << '-r test_helper'
|
|
11
|
+
t.test_files = FileList['test/**/*_test.rb']
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
namespace :test do
|
|
8
15
|
# Determine optimal number of parallel processes
|
|
9
16
|
# Use all CPUs if less than 8, otherwise cap at 8
|
|
10
17
|
def parallel_process_count
|
|
@@ -12,13 +19,9 @@ namespace :spec do
|
|
|
12
19
|
[cpus, 8].min
|
|
13
20
|
end
|
|
14
21
|
|
|
15
|
-
desc 'Run
|
|
16
|
-
task :integrations do
|
|
17
|
-
sh "bundle exec parallel_rspec -n #{parallel_process_count} spec/integrations/"
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
desc 'Run all specs in parallel'
|
|
22
|
+
desc 'Run all tests in parallel'
|
|
21
23
|
task :parallel do
|
|
22
|
-
|
|
24
|
+
executable = 'ruby -Itest -r test_helper'
|
|
25
|
+
sh "PARALLEL_TESTS_EXECUTABLE='#{executable}' bundle exec parallel_test -n #{parallel_process_count} test/"
|
|
23
26
|
end
|
|
24
27
|
end
|
data/bin/yard-lint
CHANGED
|
@@ -78,6 +78,22 @@ OptionParser.new do |opts|
|
|
|
78
78
|
options[:force] = true
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
+
opts.separator ''
|
|
82
|
+
opts.separator 'Todo file generation:'
|
|
83
|
+
|
|
84
|
+
opts.on('--auto-gen-config', 'Generate .yard-lint-todo.yml to silence existing violations') do
|
|
85
|
+
options[:auto_gen_config] = true
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
opts.on('--regenerate-todo', 'Regenerate .yard-lint-todo.yml (overwrites existing)') do
|
|
89
|
+
options[:auto_gen_config] = true
|
|
90
|
+
options[:force] = true
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
opts.on('--exclude-limit COUNT', Integer, 'Min files before grouping into pattern (default: 15)') do |count|
|
|
94
|
+
options[:exclude_limit] = count
|
|
95
|
+
end
|
|
96
|
+
|
|
81
97
|
opts.on('-v', '--version', 'Show version') do
|
|
82
98
|
puts "yard-lint #{Yard::Lint::VERSION}"
|
|
83
99
|
exit
|
|
@@ -98,6 +114,9 @@ OptionParser.new do |opts|
|
|
|
98
114
|
puts ' yard-lint --init # Generate default config'
|
|
99
115
|
puts ' yard-lint --init --strict # Generate strict config'
|
|
100
116
|
puts ' yard-lint --update # Update config with new validators'
|
|
117
|
+
puts ' yard-lint --auto-gen-config # Generate todo file for existing violations'
|
|
118
|
+
puts ' yard-lint --regenerate-todo # Regenerate todo file'
|
|
119
|
+
puts ' yard-lint --auto-gen-config --exclude-limit 10 # Custom grouping threshold'
|
|
101
120
|
exit
|
|
102
121
|
end
|
|
103
122
|
end.parse!
|
|
@@ -142,6 +161,41 @@ if options[:update]
|
|
|
142
161
|
end
|
|
143
162
|
end
|
|
144
163
|
|
|
164
|
+
# Handle --auto-gen-config flag
|
|
165
|
+
if options[:auto_gen_config]
|
|
166
|
+
begin
|
|
167
|
+
# Load config
|
|
168
|
+
config = if config_file
|
|
169
|
+
Yard::Lint::Config.from_file(config_file)
|
|
170
|
+
else
|
|
171
|
+
Yard::Lint::Config.load || Yard::Lint::Config.new
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Get path argument
|
|
175
|
+
path = ARGV[0] || '.'
|
|
176
|
+
|
|
177
|
+
# Generate todo file
|
|
178
|
+
result = Yard::Lint::TodoGenerator.generate(
|
|
179
|
+
path: path,
|
|
180
|
+
config: config,
|
|
181
|
+
force: options[:force] || false,
|
|
182
|
+
exclude_limit: options[:exclude_limit] || 15
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
puts result[:message]
|
|
186
|
+
exit 0
|
|
187
|
+
rescue Yard::Lint::Errors::InvalidConfigError => e
|
|
188
|
+
puts "Configuration Error:\n#{e.message}"
|
|
189
|
+
exit 1
|
|
190
|
+
rescue Yard::Lint::Errors::TodoFileExistsError => e
|
|
191
|
+
puts "Error: #{e.message}"
|
|
192
|
+
exit 1
|
|
193
|
+
rescue Yard::Lint::Git::Error, Yard::Lint::Errors::FileNotFoundError => e
|
|
194
|
+
puts "Error: #{e.message}"
|
|
195
|
+
exit 1
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
145
199
|
# Get path argument (defaults to current directory)
|
|
146
200
|
path = ARGV[0] || '.'
|
|
147
201
|
|
|
@@ -149,11 +203,16 @@ path = ARGV[0] || '.'
|
|
|
149
203
|
YARD::Registry.clear
|
|
150
204
|
|
|
151
205
|
# Load config and apply CLI overrides
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
206
|
+
begin
|
|
207
|
+
config = if config_file
|
|
208
|
+
Yard::Lint::Config.from_file(config_file)
|
|
209
|
+
else
|
|
210
|
+
Yard::Lint::Config.load || Yard::Lint::Config.new
|
|
211
|
+
end
|
|
212
|
+
rescue Yard::Lint::Errors::InvalidConfigError => e
|
|
213
|
+
puts "Configuration Error:\n#{e.message}"
|
|
214
|
+
exit 1
|
|
215
|
+
end
|
|
157
216
|
|
|
158
217
|
# Apply CLI min_coverage override if provided
|
|
159
218
|
config.min_coverage = options[:min_coverage] if options[:min_coverage]
|
data/lib/yard/lint/config.rb
CHANGED
|
@@ -20,6 +20,10 @@ module Yard
|
|
|
20
20
|
# @param raw_config [Hash] raw configuration hash (new hierarchical format)
|
|
21
21
|
def initialize(raw_config = {})
|
|
22
22
|
@raw_config = raw_config
|
|
23
|
+
|
|
24
|
+
# Validate configuration structure and values
|
|
25
|
+
ConfigValidator.validate!(@raw_config) unless raw_config.empty?
|
|
26
|
+
|
|
23
27
|
@validators = build_validators_config
|
|
24
28
|
@only_validators = []
|
|
25
29
|
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# YARD Lint - comprehensive linter for YARD documentation
|
|
4
|
+
module Yard
|
|
5
|
+
# YARD Lint module providing linting functionality for YARD documentation
|
|
6
|
+
module Lint
|
|
7
|
+
# Validates configuration structure and values to catch typos and invalid settings
|
|
8
|
+
class ConfigValidator
|
|
9
|
+
# Valid category names (from ConfigUpdater::CATEGORY_ORDER)
|
|
10
|
+
VALID_CATEGORIES = %w[Documentation Tags Warnings Semantic].freeze
|
|
11
|
+
|
|
12
|
+
# Keys that are valid at the root level but are not validators
|
|
13
|
+
SPECIAL_KEYS = %w[
|
|
14
|
+
AllValidators
|
|
15
|
+
inherit_from
|
|
16
|
+
inherit_gem
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
# Valid boolean values
|
|
20
|
+
BOOLEAN_VALUES = [true, false].freeze
|
|
21
|
+
|
|
22
|
+
# @param raw_config [Hash] raw configuration hash to validate
|
|
23
|
+
def initialize(raw_config)
|
|
24
|
+
@raw_config = raw_config
|
|
25
|
+
@errors = []
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Validate configuration and raise error if invalid
|
|
29
|
+
# @param raw_config [Hash] raw configuration hash to validate
|
|
30
|
+
# @return [void]
|
|
31
|
+
# @raise [Errors::InvalidConfigError] if configuration is invalid
|
|
32
|
+
def self.validate!(raw_config)
|
|
33
|
+
validator = new(raw_config)
|
|
34
|
+
validator.validate!
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Perform validation
|
|
38
|
+
# @return [void]
|
|
39
|
+
# @raise [Errors::InvalidConfigError] if configuration is invalid
|
|
40
|
+
def validate!
|
|
41
|
+
validate_root_keys!
|
|
42
|
+
validate_global_settings!
|
|
43
|
+
validate_validators!
|
|
44
|
+
|
|
45
|
+
return if @errors.empty?
|
|
46
|
+
|
|
47
|
+
raise Errors::InvalidConfigError, build_error_message
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
# Validate root-level keys in configuration
|
|
53
|
+
def validate_root_keys!
|
|
54
|
+
@raw_config.each_key do |key|
|
|
55
|
+
# Skip special keys
|
|
56
|
+
next if SPECIAL_KEYS.include?(key)
|
|
57
|
+
|
|
58
|
+
# Skip category-level configs (e.g., 'Documentation', 'Tags')
|
|
59
|
+
next if VALID_CATEGORIES.include?(key)
|
|
60
|
+
|
|
61
|
+
# Validator names must contain a '/' (category/name format)
|
|
62
|
+
next if key.include?('/')
|
|
63
|
+
|
|
64
|
+
# If we get here, it's an unknown validator (missing category prefix)
|
|
65
|
+
@errors << "Unknown validator: '#{key}'"
|
|
66
|
+
suggest_validator_name(key)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Validate AllValidators section
|
|
71
|
+
def validate_global_settings!
|
|
72
|
+
all_validators = @raw_config['AllValidators']
|
|
73
|
+
return unless all_validators
|
|
74
|
+
|
|
75
|
+
unless all_validators.is_a?(Hash)
|
|
76
|
+
@errors << "Invalid AllValidators: must be a Hash, got #{all_validators.class}"
|
|
77
|
+
return
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Only validate specific known settings, allow unknown keys to pass through
|
|
81
|
+
# This allows users to put custom data in AllValidators
|
|
82
|
+
|
|
83
|
+
# Validate FailOnSeverity value
|
|
84
|
+
fail_on = all_validators['FailOnSeverity']
|
|
85
|
+
if fail_on && !Config::VALID_SEVERITIES.include?(fail_on.to_s)
|
|
86
|
+
@errors << "Invalid FailOnSeverity: '#{fail_on}'"
|
|
87
|
+
@errors << " Valid values: #{Config::VALID_SEVERITIES.join(', ')}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Validate MinCoverage value
|
|
91
|
+
min_cov = all_validators['MinCoverage']
|
|
92
|
+
if min_cov && (!min_cov.is_a?(Numeric) || min_cov < 0 || min_cov > 100)
|
|
93
|
+
@errors << "Invalid MinCoverage: '#{min_cov}'"
|
|
94
|
+
@errors << ' Must be a number between 0 and 100'
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Validate Exclude is an array
|
|
98
|
+
exclude = all_validators['Exclude']
|
|
99
|
+
if exclude && !exclude.is_a?(Array)
|
|
100
|
+
@errors << "Invalid Exclude in AllValidators: must be an array, got #{exclude.class}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Validate YardOptions is an array
|
|
104
|
+
yard_options = all_validators['YardOptions']
|
|
105
|
+
if yard_options && !yard_options.is_a?(Array)
|
|
106
|
+
@errors << "Invalid YardOptions in AllValidators: must be an array, got #{yard_options.class}"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Validate validator-specific configurations
|
|
111
|
+
def validate_validators!
|
|
112
|
+
@raw_config.each do |key, value|
|
|
113
|
+
# Only process validator keys (containing '/')
|
|
114
|
+
next unless key.include?('/')
|
|
115
|
+
|
|
116
|
+
unless value.is_a?(Hash)
|
|
117
|
+
@errors << "Invalid configuration for validator '#{key}': expected a Hash, got #{value.class}"
|
|
118
|
+
next
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
validate_validator_exists!(key)
|
|
122
|
+
validate_validator_config!(key, value)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Check if validator name exists
|
|
127
|
+
# @param validator_name [String] validator name to check
|
|
128
|
+
# @return [void]
|
|
129
|
+
def validate_validator_exists!(validator_name)
|
|
130
|
+
return if ConfigLoader::ALL_VALIDATORS.include?(validator_name)
|
|
131
|
+
|
|
132
|
+
@errors << "Unknown validator: '#{validator_name}'"
|
|
133
|
+
suggest_validator_name(validator_name)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Validate individual validator configuration
|
|
137
|
+
# @param validator_name [String] validator name
|
|
138
|
+
# @param config [Hash] validator configuration hash
|
|
139
|
+
# @return [void]
|
|
140
|
+
def validate_validator_config!(validator_name, config)
|
|
141
|
+
# Validate Enabled value
|
|
142
|
+
enabled = config['Enabled']
|
|
143
|
+
if enabled && !BOOLEAN_VALUES.include?(enabled)
|
|
144
|
+
@errors << "Invalid Enabled value for #{validator_name}: '#{enabled}'"
|
|
145
|
+
@errors << ' Must be true or false'
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Validate Severity value
|
|
149
|
+
severity = config['Severity']
|
|
150
|
+
if severity && !Config::VALID_SEVERITIES.include?(severity.to_s)
|
|
151
|
+
@errors << "Invalid Severity for #{validator_name}: '#{severity}'"
|
|
152
|
+
@errors << " Valid values: #{Config::VALID_SEVERITIES.join(', ')}"
|
|
153
|
+
suggest_similar_severity(severity.to_s)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Validate Exclude is an array
|
|
157
|
+
exclude = config['Exclude']
|
|
158
|
+
if exclude && !exclude.is_a?(Array)
|
|
159
|
+
@errors << "Invalid Exclude for #{validator_name}: must be an array, got #{exclude.class}"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Validate YardOptions is an array
|
|
163
|
+
yard_options = config['YardOptions']
|
|
164
|
+
if yard_options && !yard_options.is_a?(Array)
|
|
165
|
+
@errors << "Invalid YardOptions for #{validator_name}: must be an array, got #{yard_options.class}"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Check for unknown validator-specific keys
|
|
169
|
+
validate_validator_specific_keys!(validator_name, config)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Validate validator-specific configuration keys
|
|
173
|
+
# @param validator_name [String] validator name
|
|
174
|
+
# @param config [Hash] validator configuration hash
|
|
175
|
+
# @return [void]
|
|
176
|
+
def validate_validator_specific_keys!(validator_name, config)
|
|
177
|
+
# Skip if validator doesn't exist (already reported)
|
|
178
|
+
return unless ConfigLoader::ALL_VALIDATORS.include?(validator_name)
|
|
179
|
+
|
|
180
|
+
validator_config_class = ConfigLoader.validator_config(validator_name)
|
|
181
|
+
return unless validator_config_class
|
|
182
|
+
|
|
183
|
+
# Base config keys that are valid for all validators
|
|
184
|
+
base_keys = %w[Enabled Severity Exclude YardOptions]
|
|
185
|
+
valid_keys = validator_config_class.defaults.keys + Config::METADATA_KEYS + base_keys
|
|
186
|
+
|
|
187
|
+
config.each_key do |key|
|
|
188
|
+
next if valid_keys.include?(key)
|
|
189
|
+
|
|
190
|
+
@errors << "Unknown configuration key for #{validator_name}: '#{key}'"
|
|
191
|
+
@errors << " Valid keys: #{valid_keys.uniq.sort.join(', ')}"
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Suggest similar validator names using did_you_mean
|
|
196
|
+
# @param invalid_name [String] invalid validator name
|
|
197
|
+
# @return [void]
|
|
198
|
+
def suggest_validator_name(invalid_name)
|
|
199
|
+
checker = DidYouMean::SpellChecker.new(dictionary: ConfigLoader::ALL_VALIDATORS)
|
|
200
|
+
suggestions = checker.correct(invalid_name)
|
|
201
|
+
|
|
202
|
+
if suggestions.any?
|
|
203
|
+
@errors << " Did you mean: #{suggestions.first}?"
|
|
204
|
+
else
|
|
205
|
+
@errors << ' Run `yard-lint --list-validators` to see all available validators'
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Suggest similar severity values
|
|
210
|
+
# @param invalid_severity [String] invalid severity value
|
|
211
|
+
# @return [void]
|
|
212
|
+
def suggest_similar_severity(invalid_severity)
|
|
213
|
+
checker = DidYouMean::SpellChecker.new(dictionary: Config::VALID_SEVERITIES)
|
|
214
|
+
suggestions = checker.correct(invalid_severity)
|
|
215
|
+
|
|
216
|
+
if suggestions.any?
|
|
217
|
+
@errors << " Did you mean: #{suggestions.first}?"
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Build comprehensive error message
|
|
222
|
+
def build_error_message
|
|
223
|
+
header = 'Invalid configuration detected:'
|
|
224
|
+
errors_text = @errors.map { |e| " #{e}" }.join("\n")
|
|
225
|
+
|
|
226
|
+
"#{header}\n#{errors_text}"
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
data/lib/yard/lint/errors.rb
CHANGED
|
@@ -18,6 +18,12 @@ module Yard
|
|
|
18
18
|
|
|
19
19
|
# Raised when a specified file or directory does not exist
|
|
20
20
|
class FileNotFoundError < BaseError; end
|
|
21
|
+
|
|
22
|
+
# Raised when .yard-lint-todo.yml already exists without force flag
|
|
23
|
+
class TodoFileExistsError < BaseError; end
|
|
24
|
+
|
|
25
|
+
# Raised when configuration is invalid
|
|
26
|
+
class InvalidConfigError < BaseError; end
|
|
21
27
|
end
|
|
22
28
|
end
|
|
23
29
|
end
|
|
@@ -33,6 +33,15 @@ module Yard
|
|
|
33
33
|
original_level = YARD::Logger.instance.level
|
|
34
34
|
YARD::Logger.instance.level = 4 # Only show fatal errors
|
|
35
35
|
|
|
36
|
+
# First pass: parse all files to process directive definitions
|
|
37
|
+
YARD.parse(files)
|
|
38
|
+
|
|
39
|
+
# Clear checksums to force reparsing without clearing the registry.
|
|
40
|
+
# This allows macro definitions from the first pass to be available
|
|
41
|
+
# during the second pass, enabling proper directive expansion regardless of parse order.
|
|
42
|
+
YARD::Registry.checksums.clear
|
|
43
|
+
|
|
44
|
+
# Second pass: reparse files now that all directive definitions are available
|
|
36
45
|
@warnings = capture_warnings { YARD.parse(files) }
|
|
37
46
|
@parsed = true
|
|
38
47
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
# Groups file paths into patterns when beneficial
|
|
6
|
+
class PathGrouper
|
|
7
|
+
# Coverage threshold for directory-level grouping (80%)
|
|
8
|
+
DIRECTORY_COVERAGE_THRESHOLD = 0.8
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
# Group file paths into patterns
|
|
12
|
+
# @param files [Array<String>] array of file paths
|
|
13
|
+
# @param limit [Integer] minimum files to trigger grouping (default: 15)
|
|
14
|
+
# @return [Array<String>] array of paths or patterns
|
|
15
|
+
def group(files, limit: 15)
|
|
16
|
+
return files.uniq.sort if files.size < limit
|
|
17
|
+
|
|
18
|
+
grouped = find_common_directories(files, limit)
|
|
19
|
+
grouped.sort
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
# Find directories where files can be grouped into patterns
|
|
25
|
+
# @param files [Array<String>] unique file paths to analyze
|
|
26
|
+
# @param limit [Integer] minimum file count threshold for grouping
|
|
27
|
+
# @return [Array<String>] array of file paths or directory patterns
|
|
28
|
+
def find_common_directories(files, limit)
|
|
29
|
+
# Deduplicate files first
|
|
30
|
+
unique_files = files.uniq
|
|
31
|
+
by_dir = unique_files.group_by { |f| File.dirname(f) }
|
|
32
|
+
result = []
|
|
33
|
+
|
|
34
|
+
by_dir.each do |dir, dir_files|
|
|
35
|
+
if should_group_directory?(dir, dir_files.uniq, limit)
|
|
36
|
+
result << "#{dir}/**/*"
|
|
37
|
+
else
|
|
38
|
+
result.concat(dir_files.uniq)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
result.uniq
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Determine if a directory should be grouped
|
|
46
|
+
# @param dir [String] directory path to evaluate
|
|
47
|
+
# @param dir_files [Array<String>] files in this directory
|
|
48
|
+
# @param limit [Integer] minimum file count threshold
|
|
49
|
+
# @return [Boolean] true if directory should be grouped into pattern
|
|
50
|
+
def should_group_directory?(dir, dir_files, limit)
|
|
51
|
+
# Must have enough files and more than just one file
|
|
52
|
+
return false if dir_files.size < limit || dir_files.size == 1
|
|
53
|
+
|
|
54
|
+
# Check coverage: do we have most Ruby files in this directory?
|
|
55
|
+
begin
|
|
56
|
+
all_ruby_files = Dir.glob("#{dir}/**/*.rb")
|
|
57
|
+
# Don't group if directory doesn't exist or is empty
|
|
58
|
+
return false if all_ruby_files.empty?
|
|
59
|
+
|
|
60
|
+
coverage = dir_files.size.to_f / all_ruby_files.size
|
|
61
|
+
coverage >= DIRECTORY_COVERAGE_THRESHOLD
|
|
62
|
+
rescue StandardError
|
|
63
|
+
# If glob fails (directory doesn't exist), don't group
|
|
64
|
+
false
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -91,7 +91,15 @@ module Yard
|
|
|
91
91
|
return nil if parsers.empty?
|
|
92
92
|
|
|
93
93
|
# Parse output with all parsers (single or multiple)
|
|
94
|
-
|
|
94
|
+
# Check if parser accepts config keyword argument and pass it if supported
|
|
95
|
+
parsed = parsers.flat_map do |parser|
|
|
96
|
+
parser_instance = parser.new
|
|
97
|
+
if parser_accepts_config?(parser_instance)
|
|
98
|
+
parser_instance.call(stdout, config: config)
|
|
99
|
+
else
|
|
100
|
+
parser_instance.call(stdout)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
95
103
|
return nil if parsed.nil? || parsed.empty?
|
|
96
104
|
|
|
97
105
|
validator_module::Result.new(parsed, config)
|
|
@@ -111,16 +119,22 @@ module Yard
|
|
|
111
119
|
parsers = discover_parsers(validator_module)
|
|
112
120
|
parsers.flat_map do |parser|
|
|
113
121
|
parser_instance = parser.new
|
|
114
|
-
|
|
115
|
-
# Otherwise, call without config for backwards compatibility
|
|
116
|
-
begin
|
|
122
|
+
if parser_accepts_config?(parser_instance)
|
|
117
123
|
parser_instance.call(stdout, config: config)
|
|
118
|
-
|
|
124
|
+
else
|
|
119
125
|
parser_instance.call(stdout)
|
|
120
126
|
end
|
|
121
127
|
end
|
|
122
128
|
end
|
|
123
129
|
|
|
130
|
+
# Check if a parser instance accepts a config keyword argument
|
|
131
|
+
# @param parser_instance [Object] parser instance to check
|
|
132
|
+
# @return [Boolean] true if parser accepts config keyword
|
|
133
|
+
def parser_accepts_config?(parser_instance)
|
|
134
|
+
params = parser_instance.method(:call).parameters
|
|
135
|
+
params.any? { |type, name| [:key, :keyreq].include?(type) && name == :config }
|
|
136
|
+
end
|
|
137
|
+
|
|
124
138
|
# Auto-discover parser classes in a validator module
|
|
125
139
|
# Finds all classes that inherit from Parsers::Base
|
|
126
140
|
# @param validator_module [Module] validator module to search
|
|
@@ -8,9 +8,9 @@ module Yard
|
|
|
8
8
|
#
|
|
9
9
|
# @example Creating a validator result class
|
|
10
10
|
# class MyValidator::Result < Results::Base
|
|
11
|
-
# self.default_severity =
|
|
12
|
-
# self.offense_type =
|
|
13
|
-
# self.offense_name =
|
|
11
|
+
# self.default_severity = "warning"
|
|
12
|
+
# self.offense_type = "method"
|
|
13
|
+
# self.offense_name = "MyOffense"
|
|
14
14
|
#
|
|
15
15
|
# def build_message(offense)
|
|
16
16
|
# "Found issue in #{offense[:location]}"
|
|
@@ -52,6 +52,14 @@ Documentation/UndocumentedOptions:
|
|
|
52
52
|
Enabled: true
|
|
53
53
|
Severity: warning
|
|
54
54
|
|
|
55
|
+
Documentation/MissingReturn:
|
|
56
|
+
Description: 'Requires @return tags on all methods (opt-in for strict documentation).'
|
|
57
|
+
Enabled: false # Opt-in validator
|
|
58
|
+
Severity: warning
|
|
59
|
+
ExcludedMethods:
|
|
60
|
+
- 'initialize' # Exclude all initialize methods
|
|
61
|
+
# - '/^_/' # Uncomment to exclude private methods (by convention)
|
|
62
|
+
|
|
55
63
|
Documentation/MarkdownSyntax:
|
|
56
64
|
Description: 'Detects common markdown syntax errors in documentation.'
|
|
57
65
|
Enabled: true
|
|
@@ -164,6 +172,15 @@ Tags/ExampleSyntax:
|
|
|
164
172
|
Enabled: true
|
|
165
173
|
Severity: warning
|
|
166
174
|
|
|
175
|
+
Tags/ExampleStyle:
|
|
176
|
+
Description: 'Validates code style in @example tags using RuboCop/StandardRB.'
|
|
177
|
+
Enabled: false # Opt-in validator (requires RuboCop or StandardRB)
|
|
178
|
+
Severity: convention
|
|
179
|
+
# Linter: auto # Uncomment to explicitly configure: 'auto', 'rubocop', 'standard', 'none'
|
|
180
|
+
# SkipPatterns: # Uncomment to skip examples matching patterns
|
|
181
|
+
# - '/skip-lint/i'
|
|
182
|
+
# - '/bad code/i'
|
|
183
|
+
|
|
167
184
|
Tags/RedundantParamDescription:
|
|
168
185
|
Description: 'Detects meaningless parameter descriptions that add no value.'
|
|
169
186
|
Enabled: true
|
|
@@ -241,6 +258,20 @@ Tags/TagGroupSeparator:
|
|
|
241
258
|
yield: [yield, yieldparam, yieldreturn]
|
|
242
259
|
RequireAfterDescription: false
|
|
243
260
|
|
|
261
|
+
Tags/ForbiddenTags:
|
|
262
|
+
Description: 'Detects forbidden tag and type combinations.'
|
|
263
|
+
Enabled: false # Opt-in validator
|
|
264
|
+
Severity: convention
|
|
265
|
+
ForbiddenPatterns: []
|
|
266
|
+
# Example patterns:
|
|
267
|
+
# - Tag: return
|
|
268
|
+
# Types:
|
|
269
|
+
# - void
|
|
270
|
+
# - Tag: param
|
|
271
|
+
# Types:
|
|
272
|
+
# - Object
|
|
273
|
+
# - Tag: api # Forbids @api tag entirely (no Types = any occurrence)
|
|
274
|
+
|
|
244
275
|
# Warnings validators - catches YARD parser errors
|
|
245
276
|
Warnings/UnknownTag:
|
|
246
277
|
Description: 'Detects unknown YARD tags.'
|
|
@@ -56,6 +56,14 @@ Documentation/UndocumentedOptions:
|
|
|
56
56
|
Enabled: true
|
|
57
57
|
Severity: error
|
|
58
58
|
|
|
59
|
+
Documentation/MissingReturn:
|
|
60
|
+
Description: 'Requires @return tags on all methods (opt-in for strict documentation).'
|
|
61
|
+
Enabled: true # Enabled in strict mode
|
|
62
|
+
Severity: error
|
|
63
|
+
ExcludedMethods:
|
|
64
|
+
- 'initialize' # Exclude all initialize methods
|
|
65
|
+
# - '/^_/' # Uncomment to exclude private methods (by convention)
|
|
66
|
+
|
|
59
67
|
Documentation/MarkdownSyntax:
|
|
60
68
|
Description: 'Detects common markdown syntax errors in documentation.'
|
|
61
69
|
Enabled: true
|
|
@@ -168,6 +176,15 @@ Tags/ExampleSyntax:
|
|
|
168
176
|
Enabled: true
|
|
169
177
|
Severity: error
|
|
170
178
|
|
|
179
|
+
Tags/ExampleStyle:
|
|
180
|
+
Description: 'Validates code style in @example tags using RuboCop/StandardRB.'
|
|
181
|
+
Enabled: false # Opt-in validator (requires RuboCop or StandardRB)
|
|
182
|
+
Severity: convention
|
|
183
|
+
# Linter: auto # Uncomment to explicitly configure: 'auto', 'rubocop', 'standard', 'none'
|
|
184
|
+
# SkipPatterns: # Uncomment to skip examples matching patterns
|
|
185
|
+
# - '/skip-lint/i'
|
|
186
|
+
# - '/bad code/i'
|
|
187
|
+
|
|
171
188
|
Tags/RedundantParamDescription:
|
|
172
189
|
Description: 'Detects meaningless parameter descriptions that add no value.'
|
|
173
190
|
Enabled: true
|
|
@@ -245,6 +262,20 @@ Tags/TagGroupSeparator:
|
|
|
245
262
|
yield: [yield, yieldparam, yieldreturn]
|
|
246
263
|
RequireAfterDescription: false
|
|
247
264
|
|
|
265
|
+
Tags/ForbiddenTags:
|
|
266
|
+
Description: 'Detects forbidden tag and type combinations.'
|
|
267
|
+
Enabled: false # Opt-in validator
|
|
268
|
+
Severity: error
|
|
269
|
+
ForbiddenPatterns: []
|
|
270
|
+
# Example patterns:
|
|
271
|
+
# - Tag: return
|
|
272
|
+
# Types:
|
|
273
|
+
# - void
|
|
274
|
+
# - Tag: param
|
|
275
|
+
# Types:
|
|
276
|
+
# - Object
|
|
277
|
+
# - Tag: api # Forbids @api tag entirely (no Types = any occurrence)
|
|
278
|
+
|
|
248
279
|
# Warnings validators - catches YARD parser errors
|
|
249
280
|
Warnings/UnknownTag:
|
|
250
281
|
Description: 'Detects unknown YARD tags.'
|