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
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
# Generates .yard-lint-todo.yml file with exclusions for current violations
|
|
6
|
+
class TodoGenerator
|
|
7
|
+
# Default grouping threshold (15+ files triggers pattern grouping)
|
|
8
|
+
DEFAULT_EXCLUDE_LIMIT = 15
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
# Generate .yard-lint-todo.yml file with exclusions for current violations
|
|
12
|
+
# @param path [String] directory or file path containing Ruby files to analyze for violations
|
|
13
|
+
# @param config [Config] yard-lint configuration object with validator settings
|
|
14
|
+
# @param force [Boolean] whether to overwrite existing todo file if present
|
|
15
|
+
# @param exclude_limit [Integer] minimum files in directory before grouping into wildcard patterns
|
|
16
|
+
# @return [Hash] result with :message, :offense_count, :validator_count
|
|
17
|
+
def generate(path:, config:, force: false, exclude_limit: DEFAULT_EXCLUDE_LIMIT)
|
|
18
|
+
new(path: path, config: config, force: force, exclude_limit: exclude_limit).generate
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Initialize a new TodoGenerator instance
|
|
23
|
+
# @param path [String] directory or file path containing Ruby files to analyze
|
|
24
|
+
# @param config [Config] yard-lint configuration object
|
|
25
|
+
# @param force [Boolean] whether to overwrite existing todo file
|
|
26
|
+
# @param exclude_limit [Integer] minimum files before grouping into patterns
|
|
27
|
+
def initialize(path:, config:, force:, exclude_limit:)
|
|
28
|
+
@path = path
|
|
29
|
+
@config = config
|
|
30
|
+
@force = force
|
|
31
|
+
@exclude_limit = exclude_limit
|
|
32
|
+
@todo_path = File.join(Dir.pwd, '.yard-lint-todo.yml')
|
|
33
|
+
@config_path = File.join(Dir.pwd, Config::DEFAULT_CONFIG_FILE)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Generate the .yard-lint-todo.yml file with exclusions for current violations
|
|
37
|
+
# @return [Hash] result hash with :message, :offense_count, :validator_count keys
|
|
38
|
+
def generate
|
|
39
|
+
# Step 1: Check if todo file exists
|
|
40
|
+
validate_todo_file_not_exists! unless @force
|
|
41
|
+
|
|
42
|
+
# Step 2: Run linting to collect violations
|
|
43
|
+
lint_result = run_linting
|
|
44
|
+
|
|
45
|
+
# Step 3: Handle clean codebase
|
|
46
|
+
return no_violations_result if lint_result[:violations_by_validator].empty?
|
|
47
|
+
|
|
48
|
+
# Step 4: Group violations by validator
|
|
49
|
+
violations_by_validator = group_violations_by_validator(lint_result)
|
|
50
|
+
|
|
51
|
+
# Step 5: Generate todo YAML content
|
|
52
|
+
todo_content = build_todo_yaml(violations_by_validator)
|
|
53
|
+
|
|
54
|
+
# Step 6: Write todo file
|
|
55
|
+
File.write(@todo_path, todo_content)
|
|
56
|
+
|
|
57
|
+
# Step 7: Update main config to inherit todo file
|
|
58
|
+
update_main_config
|
|
59
|
+
|
|
60
|
+
# Step 8: Build success message
|
|
61
|
+
build_success_result(violations_by_validator, lint_result[:total_offenses])
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
# Validate that the todo file doesn't already exist
|
|
67
|
+
# @return [void]
|
|
68
|
+
# @raise [Errors::TodoFileExistsError] if todo file exists and force is false
|
|
69
|
+
def validate_todo_file_not_exists!
|
|
70
|
+
return unless File.exist?(@todo_path)
|
|
71
|
+
|
|
72
|
+
raise Errors::TodoFileExistsError,
|
|
73
|
+
'.yard-lint-todo.yml already exists. Use --regenerate-todo to overwrite.'
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Run linting and collect violations per validator
|
|
77
|
+
# @return [Hash] hash with :violations_by_validator and :total_offenses keys
|
|
78
|
+
def run_linting
|
|
79
|
+
# Run each validator individually to track violations per validator
|
|
80
|
+
# This is more reliable than trying to infer validator from offense name
|
|
81
|
+
files = Yard::Lint.send(:expand_path, @path, @config)
|
|
82
|
+
runner = Runner.new(files, @config)
|
|
83
|
+
|
|
84
|
+
# Run validators and collect raw results
|
|
85
|
+
raw_results = runner.send(:run_validators)
|
|
86
|
+
result_builder = ResultBuilder.new(@config)
|
|
87
|
+
|
|
88
|
+
violations_by_validator = {}
|
|
89
|
+
total_offenses = 0
|
|
90
|
+
|
|
91
|
+
# Process each validator's results
|
|
92
|
+
ConfigLoader::ALL_VALIDATORS.each do |validator_name|
|
|
93
|
+
next unless @config.validator_enabled?(validator_name)
|
|
94
|
+
|
|
95
|
+
validator_result = result_builder.build(validator_name, raw_results)
|
|
96
|
+
next unless validator_result && validator_result.offenses.any?
|
|
97
|
+
|
|
98
|
+
# Extract file paths from offenses
|
|
99
|
+
file_paths = validator_result.offenses.map do |offense|
|
|
100
|
+
make_relative_path(offense[:location])
|
|
101
|
+
end.uniq.sort
|
|
102
|
+
|
|
103
|
+
violations_by_validator[validator_name] = file_paths
|
|
104
|
+
total_offenses += validator_result.count
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
{ violations_by_validator: violations_by_validator, total_offenses: total_offenses }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Build result hash for when no violations are found
|
|
111
|
+
# @return [Hash] result indicating clean codebase
|
|
112
|
+
def no_violations_result
|
|
113
|
+
{
|
|
114
|
+
message: "No offenses found. No .yard-lint-todo.yml needed.\nYour codebase is already compliant!",
|
|
115
|
+
offense_count: 0,
|
|
116
|
+
validator_count: 0
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Apply path grouping to each validator's file list
|
|
121
|
+
# @param lint_result [Hash] hash containing violations_by_validator data
|
|
122
|
+
# @return [Hash] hash of validator names to grouped file patterns
|
|
123
|
+
def group_violations_by_validator(lint_result)
|
|
124
|
+
# Apply path grouping to each validator's file list
|
|
125
|
+
lint_result[:violations_by_validator].transform_values do |files|
|
|
126
|
+
PathGrouper.group(files, limit: @exclude_limit)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Convert absolute path to relative path from current directory
|
|
131
|
+
# @param path [String] absolute or relative file path
|
|
132
|
+
# @return [String] relative path from current directory
|
|
133
|
+
def make_relative_path(path)
|
|
134
|
+
pwd = Dir.pwd
|
|
135
|
+
path.start_with?(pwd) ? path.sub("#{pwd}/", '') : path
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Build YAML content for the todo file
|
|
139
|
+
# @param violations_by_validator [Hash] hash of validator names to file patterns
|
|
140
|
+
# @return [String] formatted YAML content
|
|
141
|
+
def build_todo_yaml(violations_by_validator)
|
|
142
|
+
lines = []
|
|
143
|
+
|
|
144
|
+
# Header
|
|
145
|
+
lines << "# This file was auto-generated by yard-lint --auto-gen-config on #{Time.now.utc.strftime('%Y-%m-%d %H:%M:%S UTC')}"
|
|
146
|
+
lines << '# It contains exclusions for all current violations to establish a baseline.'
|
|
147
|
+
lines << '#'
|
|
148
|
+
lines << '# To gradually fix violations:'
|
|
149
|
+
lines << '# 1. Remove files/patterns from the Exclude list below'
|
|
150
|
+
lines << '# 2. Run yard-lint to see the violations for those files'
|
|
151
|
+
lines << '# 3. Fix the violations'
|
|
152
|
+
lines << '# 4. Commit the changes'
|
|
153
|
+
lines << '#'
|
|
154
|
+
lines << '# To regenerate this file, run: yard-lint --regenerate-todo'
|
|
155
|
+
lines << ''
|
|
156
|
+
|
|
157
|
+
# Group validators by category
|
|
158
|
+
categories = group_by_category(violations_by_validator)
|
|
159
|
+
|
|
160
|
+
# Write each category
|
|
161
|
+
ConfigUpdater::CATEGORY_ORDER.each do |category|
|
|
162
|
+
validators = categories[category]
|
|
163
|
+
next unless validators&.any?
|
|
164
|
+
|
|
165
|
+
lines << ConfigUpdater::CATEGORY_COMMENTS[category] if ConfigUpdater::CATEGORY_COMMENTS[category]
|
|
166
|
+
|
|
167
|
+
validators.each do |validator_name, file_patterns|
|
|
168
|
+
lines << "#{validator_name}:"
|
|
169
|
+
lines << ' Exclude:'
|
|
170
|
+
file_patterns.each do |pattern|
|
|
171
|
+
lines << " - '#{pattern}'"
|
|
172
|
+
end
|
|
173
|
+
lines << ''
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
lines.join("\n")
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Group validators by their category (Documentation, Tags, etc.)
|
|
181
|
+
# @param violations_by_validator [Hash] hash of validator names to patterns
|
|
182
|
+
# @return [Hash] validators grouped by category
|
|
183
|
+
def group_by_category(violations_by_validator)
|
|
184
|
+
categories = Hash.new { |h, k| h[k] = {} }
|
|
185
|
+
|
|
186
|
+
violations_by_validator.each do |validator_name, patterns|
|
|
187
|
+
category = validator_name.split('/').first
|
|
188
|
+
categories[category][validator_name] = patterns
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
categories
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Update or create main config file to inherit from todo file
|
|
195
|
+
# @return [void]
|
|
196
|
+
def update_main_config
|
|
197
|
+
if File.exist?(@config_path)
|
|
198
|
+
update_existing_config
|
|
199
|
+
else
|
|
200
|
+
create_minimal_config
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Update existing config file to add inherit_from todo file
|
|
205
|
+
# @return [void]
|
|
206
|
+
def update_existing_config
|
|
207
|
+
config_yaml = YAML.load_file(@config_path) || {}
|
|
208
|
+
inherit_from = Array(config_yaml['inherit_from'] || [])
|
|
209
|
+
|
|
210
|
+
# Add todo file to inherit_from if not already present
|
|
211
|
+
unless inherit_from.include?('.yard-lint-todo.yml')
|
|
212
|
+
inherit_from.unshift('.yard-lint-todo.yml')
|
|
213
|
+
config_yaml['inherit_from'] = inherit_from
|
|
214
|
+
|
|
215
|
+
# Write updated config
|
|
216
|
+
File.write(@config_path, config_yaml.to_yaml)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Create a minimal config file that inherits from todo file
|
|
221
|
+
# @return [void]
|
|
222
|
+
def create_minimal_config
|
|
223
|
+
content = <<~YAML
|
|
224
|
+
# YARD-Lint Configuration
|
|
225
|
+
# See https://github.com/mensfeld/yard-lint for documentation
|
|
226
|
+
|
|
227
|
+
inherit_from:
|
|
228
|
+
- .yard-lint-todo.yml
|
|
229
|
+
YAML
|
|
230
|
+
|
|
231
|
+
File.write(@config_path, content)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Build success result hash with summary message
|
|
235
|
+
# @param violations_by_validator [Hash] hash of validator names to file patterns
|
|
236
|
+
# @param total_offenses [Integer] total number of offenses found
|
|
237
|
+
# @return [Hash] result with :message, :offense_count, :validator_count keys
|
|
238
|
+
def build_success_result(violations_by_validator, total_offenses)
|
|
239
|
+
lines = []
|
|
240
|
+
lines << 'Created .yard-lint-todo.yml'
|
|
241
|
+
lines << "Silenced #{total_offenses} offense(s) across #{violations_by_validator.size} validator(s):"
|
|
242
|
+
|
|
243
|
+
violations_by_validator.each do |validator_name, file_patterns|
|
|
244
|
+
lines << " #{validator_name}: #{file_patterns.size} pattern(s)"
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
lines << ''
|
|
248
|
+
lines << 'Updated .yard-lint.yml to inherit from .yard-lint-todo.yml' if File.exist?(@config_path)
|
|
249
|
+
lines << ''
|
|
250
|
+
lines << 'Run yard-lint again to confirm - you should see no offenses.'
|
|
251
|
+
lines << 'To fix violations incrementally, remove entries from .yard-lint-todo.yml'
|
|
252
|
+
|
|
253
|
+
{
|
|
254
|
+
message: lines.join("\n"),
|
|
255
|
+
offense_count: total_offenses,
|
|
256
|
+
validator_count: violations_by_validator.size
|
|
257
|
+
}
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
@@ -85,7 +85,7 @@ module Yard
|
|
|
85
85
|
# @return [Object] the configured value or default value from the validator's Config.defaults
|
|
86
86
|
# @example Usage in a validator (e.g., Tags::RedundantParamDescription)
|
|
87
87
|
# def config_articles
|
|
88
|
-
# config_or_default(
|
|
88
|
+
# config_or_default("Articles")
|
|
89
89
|
# end
|
|
90
90
|
# @note The validator name is automatically extracted from the class namespace.
|
|
91
91
|
# For example, Yard::Lint::Validators::Tags::RedundantParamDescription::Validator
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module MissingReturn
|
|
8
|
+
# Configuration for MissingReturn validator
|
|
9
|
+
class Config < ::Yard::Lint::Validators::Config
|
|
10
|
+
self.id = :missing_return
|
|
11
|
+
self.defaults = {
|
|
12
|
+
'Enabled' => false, # Disabled by default (opt-in validator)
|
|
13
|
+
'Severity' => 'warning',
|
|
14
|
+
'ExcludedMethods' => [
|
|
15
|
+
'initialize' # Exclude all initialize methods by default
|
|
16
|
+
]
|
|
17
|
+
}.freeze
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module MissingReturn
|
|
8
|
+
# Builds messages for missing return tag offenses
|
|
9
|
+
class MessagesBuilder
|
|
10
|
+
class << self
|
|
11
|
+
# Build message for a method missing @return tag
|
|
12
|
+
# @param offense [Hash] offense data with :element key
|
|
13
|
+
# @return [String] formatted message
|
|
14
|
+
def call(offense)
|
|
15
|
+
"Missing @return tag for `#{offense[:element]}`"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module MissingReturn
|
|
8
|
+
# Class used to extract details about methods missing @return tags from validator output
|
|
9
|
+
# @example Output format (skip-lint)
|
|
10
|
+
# /path/to/file.rb:3: ClassName#method_name|2
|
|
11
|
+
# /path/to/file.rb:10: ModuleName.class_method|0
|
|
12
|
+
class Parser < ::Yard::Lint::Parsers::Base
|
|
13
|
+
# Regex used to parse validator output format
|
|
14
|
+
# Format: file.rb:LINE: ObjectName|ARITY
|
|
15
|
+
LINE_REGEX = /^(.+):(\d+): (.+?)\|(\d+)$/
|
|
16
|
+
|
|
17
|
+
# @param validator_output [String] raw validator results string
|
|
18
|
+
# @param config [Yard::Lint::Config, nil] configuration object (optional)
|
|
19
|
+
# @return [Array<Hash>] Array with methods missing @return tags
|
|
20
|
+
def call(validator_output, config: nil)
|
|
21
|
+
excluded_methods = config&.validator_config(
|
|
22
|
+
'Documentation/MissingReturn',
|
|
23
|
+
'ExcludedMethods'
|
|
24
|
+
) || []
|
|
25
|
+
|
|
26
|
+
# Ensure excluded_methods is an Array
|
|
27
|
+
excluded_methods = Array(excluded_methods)
|
|
28
|
+
|
|
29
|
+
# Sanitize patterns: remove nil, empty, whitespace-only, and normalize
|
|
30
|
+
excluded_methods = sanitize_patterns(excluded_methods)
|
|
31
|
+
|
|
32
|
+
validator_output
|
|
33
|
+
.split("\n")
|
|
34
|
+
.map(&:strip)
|
|
35
|
+
.reject(&:empty?)
|
|
36
|
+
.filter_map do |line|
|
|
37
|
+
match = line.match(LINE_REGEX)
|
|
38
|
+
next unless match
|
|
39
|
+
|
|
40
|
+
element = match[3]
|
|
41
|
+
arity = match[4].to_i
|
|
42
|
+
|
|
43
|
+
# Skip if method is in excluded list
|
|
44
|
+
next if method_excluded?(element, arity, excluded_methods)
|
|
45
|
+
|
|
46
|
+
{
|
|
47
|
+
location: match[1],
|
|
48
|
+
line: match[2].to_i,
|
|
49
|
+
element: element
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
# Checks if a method should be excluded based on ExcludedMethods config
|
|
57
|
+
# Supports: simple names, arity notation, and regex patterns
|
|
58
|
+
# @param element [String] the element name (e.g., "Class#method")
|
|
59
|
+
# @param arity [Integer] number of parameters (required + optional,
|
|
60
|
+
# excluding splat and block)
|
|
61
|
+
# @param excluded_methods [Array<String>] list of exclusion patterns
|
|
62
|
+
# @return [Boolean] true if method should be excluded
|
|
63
|
+
def method_excluded?(element, arity, excluded_methods)
|
|
64
|
+
# Extract method name from element (e.g., "Foo::Bar#baz" -> "baz")
|
|
65
|
+
method_name = element.split(/[#.]/).last
|
|
66
|
+
return false unless method_name
|
|
67
|
+
|
|
68
|
+
excluded_methods.any? do |pattern|
|
|
69
|
+
case pattern
|
|
70
|
+
when %r{^/(.+)/$}
|
|
71
|
+
# Regex pattern: '/^_/' matches methods starting with _
|
|
72
|
+
match_regex_pattern(method_name, Regexp.last_match(1))
|
|
73
|
+
when %r{/\d+$}
|
|
74
|
+
# Arity pattern: 'initialize/0' checks method name and parameter count
|
|
75
|
+
match_arity_pattern(method_name, arity, pattern)
|
|
76
|
+
else
|
|
77
|
+
# Simple name match: 'initialize'
|
|
78
|
+
# Simple names match any arity (use arity notation for specific arity)
|
|
79
|
+
method_name == pattern
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Sanitize exclusion patterns
|
|
85
|
+
# @param patterns [Array] raw patterns from config
|
|
86
|
+
# @return [Array<String>] cleaned and validated patterns
|
|
87
|
+
def sanitize_patterns(patterns)
|
|
88
|
+
patterns
|
|
89
|
+
.compact # Remove nil values
|
|
90
|
+
.map { |p| p.to_s.strip } # Convert to strings and trim whitespace
|
|
91
|
+
.reject(&:empty?) # Remove empty strings
|
|
92
|
+
.reject { |p| p == '//' } # Reject empty regex (matches everything)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Match a regex pattern against method name with error handling
|
|
96
|
+
# @param method_name [String] the method name to match
|
|
97
|
+
# @param regex_pattern [String] the regex pattern (without delimiters)
|
|
98
|
+
# @return [Boolean] true if matches, false if invalid regex or no match
|
|
99
|
+
def match_regex_pattern(method_name, regex_pattern)
|
|
100
|
+
return false if regex_pattern.empty? # Empty regex would match everything
|
|
101
|
+
|
|
102
|
+
Regexp.new(regex_pattern).match?(method_name)
|
|
103
|
+
rescue RegexpError
|
|
104
|
+
# Invalid regex - skip this pattern
|
|
105
|
+
false
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Match an arity pattern like "initialize/0"
|
|
109
|
+
# @param method_name [String] the method name
|
|
110
|
+
# @param arity [Integer] number of parameters the method accepts
|
|
111
|
+
# @param pattern [String] the full pattern like "initialize/0"
|
|
112
|
+
# @return [Boolean] true if matches
|
|
113
|
+
def match_arity_pattern(method_name, arity, pattern)
|
|
114
|
+
pattern_name, pattern_arity_str = pattern.split('/')
|
|
115
|
+
|
|
116
|
+
# Validate arity is numeric
|
|
117
|
+
return false unless pattern_arity_str.match?(/^\d+$/)
|
|
118
|
+
|
|
119
|
+
pattern_arity = pattern_arity_str.to_i
|
|
120
|
+
|
|
121
|
+
method_name == pattern_name && arity == pattern_arity
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module MissingReturn
|
|
8
|
+
# Result object for missing return tag validation
|
|
9
|
+
class Result < Results::Base
|
|
10
|
+
self.default_severity = 'warning'
|
|
11
|
+
self.offense_type = 'line'
|
|
12
|
+
self.offense_name = 'MissingReturnTag'
|
|
13
|
+
|
|
14
|
+
# Build human-readable message for missing return tag offense
|
|
15
|
+
# @param offense [Hash] offense data
|
|
16
|
+
# @return [String] formatted message
|
|
17
|
+
def build_message(offense)
|
|
18
|
+
MessagesBuilder.call(offense)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module MissingReturn
|
|
8
|
+
# Runs a query that will pick all methods that do not have a @return tag documented
|
|
9
|
+
class Validator < Base
|
|
10
|
+
# Enable in-process execution for this validator
|
|
11
|
+
in_process visibility: :public
|
|
12
|
+
|
|
13
|
+
# Execute query for a single object during in-process execution.
|
|
14
|
+
# Finds methods without @return tag.
|
|
15
|
+
# @param object [YARD::CodeObjects::Base] the code object to query
|
|
16
|
+
# @param collector [Executor::ResultCollector] collector for output
|
|
17
|
+
# @return [void]
|
|
18
|
+
def in_process_query(object, collector)
|
|
19
|
+
# Only check methods
|
|
20
|
+
return unless object.type == :method
|
|
21
|
+
# Skip aliases and implicit methods
|
|
22
|
+
return if object.is_alias?
|
|
23
|
+
return unless object.is_explicit?
|
|
24
|
+
|
|
25
|
+
# Check if @return tag is missing
|
|
26
|
+
return_tag = object.tag(:return)
|
|
27
|
+
return unless return_tag.nil?
|
|
28
|
+
|
|
29
|
+
# Calculate arity (exclude splat and block parameters)
|
|
30
|
+
arity = object.parameters.reject { |p| p[0].to_s.start_with?('*', '&') }.size
|
|
31
|
+
|
|
32
|
+
# Output method with arity for parser filtering
|
|
33
|
+
collector.puts "#{object.file}:#{object.line}: #{object.title}|#{arity}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
# MissingReturn validator
|
|
8
|
+
#
|
|
9
|
+
# Ensures that all methods have an explicit `@return` tag documented.
|
|
10
|
+
# This validator helps maintain consistent API documentation by catching
|
|
11
|
+
# methods that are missing return value documentation. This validator is
|
|
12
|
+
# disabled by default and must be explicitly enabled in configuration.
|
|
13
|
+
#
|
|
14
|
+
# @example Bad - Missing @return tag
|
|
15
|
+
# # Calculates the total price
|
|
16
|
+
# def calculate_total
|
|
17
|
+
# @items.sum(&:price)
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# @example Good - Return value documented
|
|
21
|
+
# # Calculates the total price
|
|
22
|
+
# # @return [Float] the total price of all items
|
|
23
|
+
# def calculate_total
|
|
24
|
+
# @items.sum(&:price)
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# ## Configuration
|
|
28
|
+
#
|
|
29
|
+
# This validator is disabled by default. To enable it:
|
|
30
|
+
#
|
|
31
|
+
# Documentation/MissingReturn:
|
|
32
|
+
# Enabled: true
|
|
33
|
+
# Severity: warning
|
|
34
|
+
# ExcludedMethods:
|
|
35
|
+
# - initialize
|
|
36
|
+
# - '/^_/' # Exclude private methods starting with underscore
|
|
37
|
+
#
|
|
38
|
+
# ### ExcludedMethods
|
|
39
|
+
#
|
|
40
|
+
# Supports three pattern types:
|
|
41
|
+
# - Simple name: 'initialize' - matches all methods with that name
|
|
42
|
+
# - Arity notation: 'initialize/0' - matches only methods with specific parameter count
|
|
43
|
+
# - Regex pattern: '/^_/' - matches methods using regular expressions
|
|
44
|
+
module MissingReturn
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -7,7 +7,7 @@ module Yard
|
|
|
7
7
|
module Documentation
|
|
8
8
|
module UndocumentedBooleanMethods
|
|
9
9
|
# Class used to extract details about undocumented boolean methods
|
|
10
|
-
# @example
|
|
10
|
+
# @example Output format (skip-lint)
|
|
11
11
|
# Platform::Analysis::Authors#valid?
|
|
12
12
|
class Parser < Parsers::Base
|
|
13
13
|
# Regex to extract location and method name from yard list output
|
|
@@ -6,7 +6,7 @@ module Yard
|
|
|
6
6
|
module Documentation
|
|
7
7
|
module UndocumentedMethodArguments
|
|
8
8
|
# Class used to extract details about methods with undocumented arguments
|
|
9
|
-
# @example
|
|
9
|
+
# @example Output format (skip-lint)
|
|
10
10
|
# /path/to/file.rb:10: Platform::Analysis::Authors#initialize
|
|
11
11
|
class Parser < Parsers::Base
|
|
12
12
|
# Regex to extract file, line, and method name from yard list output
|
|
@@ -21,6 +21,9 @@ module Yard
|
|
|
21
21
|
# Skip aliases and implicit methods
|
|
22
22
|
return if object.is_alias?
|
|
23
23
|
return unless object.is_explicit?
|
|
24
|
+
# Skip attribute methods (@!attribute directive) — their setter parameter
|
|
25
|
+
# doesn't need explicit @param documentation, matching attr_accessor behavior
|
|
26
|
+
return if object.is_attribute?
|
|
24
27
|
|
|
25
28
|
# Check if parameters count exceeds @param tags count
|
|
26
29
|
param_count = object.parameters.size
|
|
@@ -6,7 +6,7 @@ module Yard
|
|
|
6
6
|
module Documentation
|
|
7
7
|
module UndocumentedObjects
|
|
8
8
|
# Class used to extract details about undocumented objects from raw yard list output
|
|
9
|
-
# @example
|
|
9
|
+
# @example Output format (skip-lint)
|
|
10
10
|
# /path/to/file.rb:3: UndocumentedClass
|
|
11
11
|
# /path/to/file.rb:4: UndocumentedClass#method_one|2
|
|
12
12
|
class Parser < ::Yard::Lint::Parsers::Base
|
|
@@ -47,6 +47,12 @@ module Yard
|
|
|
47
47
|
if type_string.start_with?('{')
|
|
48
48
|
# {K => V} -> Hash{K => V}
|
|
49
49
|
"Hash#{type_string}"
|
|
50
|
+
elsif type_string.start_with?('<')
|
|
51
|
+
# <String> -> Array<String>
|
|
52
|
+
"Array#{type_string}"
|
|
53
|
+
elsif type_string.start_with?('(')
|
|
54
|
+
# (String, Integer) -> Array(String, Integer)
|
|
55
|
+
"Array#{type_string}"
|
|
50
56
|
else
|
|
51
57
|
# Hash<K, V> -> Hash{K => V}
|
|
52
58
|
type_string.gsub(/Hash<(.+?)>/) do
|
|
@@ -61,8 +67,8 @@ module Yard
|
|
|
61
67
|
# @param type_string [String] the type string
|
|
62
68
|
# @return [String] the converted type string
|
|
63
69
|
def convert_to_short(type_string)
|
|
64
|
-
# Hash{K => V} -> {K => V}
|
|
65
|
-
type_string.sub(/^Hash/, '')
|
|
70
|
+
# Hash{K => V} -> {K => V} or Array<String> -> <String> or Array(S, I) -> (S, I)
|
|
71
|
+
type_string.sub(/^(Hash|Array)/, '')
|
|
66
72
|
end
|
|
67
73
|
end
|
|
68
74
|
end
|