yard-lint 1.11.0 → 1.13.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 +201 -498
- data/README.md +103 -1
- data/bin/yard-lint +100 -40
- data/lib/yard/lint/config.rb +81 -5
- data/lib/yard/lint/config_validator.rb +7 -191
- data/lib/yard/lint/contractable/contract.rb +219 -0
- data/lib/yard/lint/contractable/result.rb +93 -0
- data/lib/yard/lint/contractable/rule.rb +10 -0
- data/lib/yard/lint/contracts/base.rb +57 -0
- data/lib/yard/lint/contracts/category.rb +71 -0
- data/lib/yard/lint/contracts/config.rb +81 -0
- data/lib/yard/lint/contracts/validator.rb +72 -0
- data/lib/yard/lint/errors.rb +3 -0
- data/lib/yard/lint/executor/in_process_registry.rb +1 -1
- data/lib/yard/lint/explainer.rb +205 -0
- data/lib/yard/lint/formatters/sarif.rb +218 -0
- data/lib/yard/lint/git.rb +5 -3
- data/lib/yard/lint/result_builder.rb +7 -6
- data/lib/yard/lint/runner.rb +2 -21
- data/lib/yard/lint/templates/default_config.yml +10 -0
- data/lib/yard/lint/templates/strict_config.yml +10 -0
- data/lib/yard/lint/todo_generator.rb +143 -12
- data/lib/yard/lint/validators/base.rb +63 -0
- data/lib/yard/lint/validators/documentation/reference_typo/config.rb +20 -0
- data/lib/yard/lint/validators/documentation/reference_typo/messages_builder.rb +24 -0
- data/lib/yard/lint/validators/documentation/reference_typo/parser.rb +44 -0
- data/lib/yard/lint/validators/documentation/reference_typo/result.rb +27 -0
- data/lib/yard/lint/validators/documentation/reference_typo/validator.rb +214 -0
- data/lib/yard/lint/validators/documentation/reference_typo.rb +39 -0
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/parser.rb +13 -12
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/validator.rb +84 -5
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods.rb +16 -3
- data/lib/yard/lint/validators/documentation/undocumented_options/validator.rb +3 -2
- data/lib/yard/lint/validators/tags/example_style/validator.rb +4 -4
- data/lib/yard/lint/validators/tags/example_syntax/validator.rb +4 -2
- data/lib/yard/lint/validators/tags/invalid_types/messages_builder.rb +15 -4
- data/lib/yard/lint/validators/tags/invalid_types/parser.rb +18 -7
- data/lib/yard/lint/validators/tags/invalid_types/validator.rb +22 -12
- data/lib/yard/lint/validators/tags/overload_consistency/config.rb +20 -0
- data/lib/yard/lint/validators/tags/overload_consistency/messages_builder.rb +32 -0
- data/lib/yard/lint/validators/tags/overload_consistency/parser.rb +49 -0
- data/lib/yard/lint/validators/tags/overload_consistency/result.rb +27 -0
- data/lib/yard/lint/validators/tags/overload_consistency/validator.rb +71 -0
- data/lib/yard/lint/validators/tags/overload_consistency.rb +53 -0
- data/lib/yard/lint/validators/tags/redundant_param_description/validator.rb +3 -3
- data/lib/yard/lint/validators/tags/tag_group_separator/validator.rb +8 -0
- data/lib/yard/lint/validators/tags/tag_separator/validator.rb +8 -0
- data/lib/yard/lint/validators/tags/type_syntax/parser.rb +13 -2
- data/lib/yard/lint/validators/tags/type_syntax/validator.rb +11 -1
- data/lib/yard/lint/version.rb +1 -1
- data/lib/yard/lint.rb +3 -57
- metadata +23 -2
data/lib/yard/lint/runner.rb
CHANGED
|
@@ -102,11 +102,7 @@ module Yard
|
|
|
102
102
|
validator_excludes = config.validator_exclude(validator_name)
|
|
103
103
|
return files if validator_excludes.empty?
|
|
104
104
|
|
|
105
|
-
files.reject
|
|
106
|
-
validator_excludes.any? do |pattern|
|
|
107
|
-
File.fnmatch(pattern, file, File::FNM_PATHNAME | File::FNM_EXTGLOB)
|
|
108
|
-
end
|
|
109
|
-
end
|
|
105
|
+
files.reject { |file| config.excluded_path?(file, validator_excludes) }
|
|
110
106
|
end
|
|
111
107
|
|
|
112
108
|
# Filter result offenses based on per-validator exclusions
|
|
@@ -124,23 +120,8 @@ module Yard
|
|
|
124
120
|
return nil if result.offenses.empty?
|
|
125
121
|
return result if validator_excludes.empty?
|
|
126
122
|
|
|
127
|
-
working_dir = Dir.pwd
|
|
128
|
-
|
|
129
123
|
filtered_offenses = result.offenses.reject do |offense|
|
|
130
|
-
|
|
131
|
-
next true unless file_path
|
|
132
|
-
|
|
133
|
-
# Convert to relative path for pattern matching
|
|
134
|
-
relative_path = if file_path.start_with?(working_dir)
|
|
135
|
-
file_path.sub(%r{^#{Regexp.escape(working_dir)}/}, '')
|
|
136
|
-
else
|
|
137
|
-
file_path
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
# Check if file matches any exclusion pattern
|
|
141
|
-
validator_excludes.any? do |pattern|
|
|
142
|
-
File.fnmatch(pattern, relative_path, File::FNM_PATHNAME | File::FNM_EXTGLOB)
|
|
143
|
-
end
|
|
124
|
+
config.excluded_path?(offense[:location] || offense[:file], validator_excludes)
|
|
144
125
|
end
|
|
145
126
|
|
|
146
127
|
# Return nil if no offenses remain after filtering
|
|
@@ -156,6 +156,11 @@ Documentation/DuplicateNamespaceComment:
|
|
|
156
156
|
Enabled: true
|
|
157
157
|
Severity: warning
|
|
158
158
|
|
|
159
|
+
Documentation/ReferenceTypo:
|
|
160
|
+
Description: 'Detects probable typos in method and constant references.'
|
|
161
|
+
Enabled: true
|
|
162
|
+
Severity: warning
|
|
163
|
+
|
|
159
164
|
# Tags validators
|
|
160
165
|
Tags/Order:
|
|
161
166
|
Description: 'Enforces consistent ordering of YARD tags.'
|
|
@@ -381,6 +386,11 @@ Tags/ForbiddenTags:
|
|
|
381
386
|
# - Object
|
|
382
387
|
# - Tag: api # Forbids @api tag entirely (no Types = any occurrence)
|
|
383
388
|
|
|
389
|
+
Tags/OverloadConsistency:
|
|
390
|
+
Description: 'Checks the tags of each @overload against its own signature.'
|
|
391
|
+
Enabled: true
|
|
392
|
+
Severity: warning
|
|
393
|
+
|
|
384
394
|
# Warnings validators - catches YARD parser errors
|
|
385
395
|
Warnings/SyntaxError:
|
|
386
396
|
Description: 'Detects Ruby files YARD cannot parse (syntax errors).'
|
|
@@ -154,6 +154,11 @@ Documentation/DuplicateNamespaceComment:
|
|
|
154
154
|
Enabled: true
|
|
155
155
|
Severity: error
|
|
156
156
|
|
|
157
|
+
Documentation/ReferenceTypo:
|
|
158
|
+
Description: 'Detects probable typos in method and constant references.'
|
|
159
|
+
Enabled: true
|
|
160
|
+
Severity: error
|
|
161
|
+
|
|
157
162
|
# Tags validators
|
|
158
163
|
Tags/Order:
|
|
159
164
|
Description: 'Enforces consistent ordering of YARD tags.'
|
|
@@ -379,6 +384,11 @@ Tags/ForbiddenTags:
|
|
|
379
384
|
# - Object
|
|
380
385
|
# - Tag: api # Forbids @api tag entirely (no Types = any occurrence)
|
|
381
386
|
|
|
387
|
+
Tags/OverloadConsistency:
|
|
388
|
+
Description: 'Checks the tags of each @overload against its own signature.'
|
|
389
|
+
Enabled: true
|
|
390
|
+
Severity: error
|
|
391
|
+
|
|
382
392
|
# Warnings validators - catches YARD parser errors
|
|
383
393
|
Warnings/SyntaxError:
|
|
384
394
|
Description: 'Detects Ruby files YARD cannot parse (syntax errors).'
|
|
@@ -14,12 +14,20 @@ module Yard
|
|
|
14
14
|
# @param force [Boolean] whether to overwrite existing todo file if present
|
|
15
15
|
# @param exclude_limit [Integer] minimum files in directory before grouping into wildcard patterns
|
|
16
16
|
# @param config_path [String, nil] config file to link the todo into
|
|
17
|
-
# (defaults to .yard-lint.yml in the
|
|
17
|
+
# (defaults to .yard-lint.yml in the config directory)
|
|
18
18
|
# @return [Hash] result with :message, :offense_count, :validator_count
|
|
19
19
|
def generate(path:, config:, force: false, exclude_limit: DEFAULT_EXCLUDE_LIMIT, config_path: nil)
|
|
20
20
|
new(path: path, config: config, force: force, exclude_limit: exclude_limit,
|
|
21
21
|
config_path: config_path).generate
|
|
22
22
|
end
|
|
23
|
+
|
|
24
|
+
# Report .yard-lint-todo.yml Exclude entries that no longer match any offense
|
|
25
|
+
# @param path [String] directory or file path containing Ruby files to analyze for violations
|
|
26
|
+
# @param config [Config] yard-lint configuration object with validator settings
|
|
27
|
+
# @return [Hash] result with :message and :unused_entries (validator name => unused patterns)
|
|
28
|
+
def check(path:, config:)
|
|
29
|
+
new(path: path, config: config, force: false, exclude_limit: DEFAULT_EXCLUDE_LIMIT).check
|
|
30
|
+
end
|
|
23
31
|
end
|
|
24
32
|
|
|
25
33
|
# Initialize a new TodoGenerator instance
|
|
@@ -33,8 +41,8 @@ module Yard
|
|
|
33
41
|
@config = config
|
|
34
42
|
@force = force
|
|
35
43
|
@exclude_limit = exclude_limit
|
|
36
|
-
@todo_path = File.join(
|
|
37
|
-
@config_path = config_path || File.join(
|
|
44
|
+
@todo_path = File.join(config.base_dir, '.yard-lint-todo.yml')
|
|
45
|
+
@config_path = config_path || File.join(config.base_dir, Config::DEFAULT_CONFIG_FILE)
|
|
38
46
|
end
|
|
39
47
|
|
|
40
48
|
# Generate the .yard-lint-todo.yml file with exclusions for current violations
|
|
@@ -65,8 +73,130 @@ module Yard
|
|
|
65
73
|
build_success_result(violations_by_validator, lint_result[:total_offenses])
|
|
66
74
|
end
|
|
67
75
|
|
|
76
|
+
# Re-lint without the todo file exclusions and find entries that match no offense
|
|
77
|
+
# Offenses can depend on the other linted files and on their order, so an entry counts as
|
|
78
|
+
# used when it matches an offense of a whole-project run or of a run on the path only.
|
|
79
|
+
# The path only limits which entries are judged: those that cover a file under it,
|
|
80
|
+
# or no project file at all. The given config is not modified.
|
|
81
|
+
# @return [Hash] result with :message and :unused_entries (validator name => unused patterns)
|
|
82
|
+
# @raise [Errors::TodoFileNotFoundError] if the todo file does not exist
|
|
83
|
+
def check
|
|
84
|
+
todo_entries = load_todo_entries
|
|
85
|
+
audit_config = build_audit_config(todo_entries)
|
|
86
|
+
path_files = Yard::Lint.send(:expand_path, @path, audit_config)
|
|
87
|
+
files = Yard::Lint.send(:expand_path, '.', audit_config) | path_files
|
|
88
|
+
|
|
89
|
+
violations_by_validator = audit_violations([files, path_files], audit_config)
|
|
90
|
+
files = files.map { |file| make_relative_path(file) }
|
|
91
|
+
path_files = path_files.map { |file| make_relative_path(file) }
|
|
92
|
+
|
|
93
|
+
unused_entries = todo_entries.each_with_object({}) do |(validator_name, patterns), unused|
|
|
94
|
+
next unless judged?(validator_name, audit_config)
|
|
95
|
+
|
|
96
|
+
violating_files = violations_by_validator.fetch(validator_name, [])
|
|
97
|
+
stale = patterns.select do |pattern|
|
|
98
|
+
in_scope?(pattern, files, path_files) && violating_files.none? { |file| todo_match?(pattern, file) }
|
|
99
|
+
end
|
|
100
|
+
unused[validator_name] = stale if stale.any?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
{ message: build_check_message(unused_entries), unused_entries: unused_entries }
|
|
104
|
+
end
|
|
105
|
+
|
|
68
106
|
private
|
|
69
107
|
|
|
108
|
+
# Load per-validator Exclude patterns from the todo file
|
|
109
|
+
# @return [Hash{String => Array<String>}] validator names to their todo Exclude patterns
|
|
110
|
+
# @raise [Errors::TodoFileNotFoundError] if the todo file does not exist
|
|
111
|
+
def load_todo_entries
|
|
112
|
+
unless File.exist?(@todo_path)
|
|
113
|
+
raise Errors::TodoFileNotFoundError,
|
|
114
|
+
'.yard-lint-todo.yml not found. Use --auto-gen-config to generate it.'
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
ConfigLoader.load_yaml_file(@todo_path).each_with_object({}) do |(validator_name, settings), entries|
|
|
118
|
+
next unless settings.is_a?(Hash) && settings['Exclude'].is_a?(Array)
|
|
119
|
+
|
|
120
|
+
entries[validator_name] = settings['Exclude'].map(&:to_s)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Build a copy of the config without the todo file's Exclude lists
|
|
125
|
+
# A per-validator Exclude replaces an inherited one, so a validator's todo list is in effect
|
|
126
|
+
# only when it is that validator's whole Exclude. Otherwise the user's Exclude stays as is.
|
|
127
|
+
# @param todo_entries [Hash{String => Array<String>}] validator names to todo Exclude patterns
|
|
128
|
+
# @return [Config] configuration for the audit run
|
|
129
|
+
def build_audit_config(todo_entries)
|
|
130
|
+
audit_config = Config.new(Marshal.load(Marshal.dump(@config.raw_config)))
|
|
131
|
+
audit_config.base_dir = @config.base_dir
|
|
132
|
+
audit_config.only_validators = @config.only_validators.dup
|
|
133
|
+
|
|
134
|
+
todo_entries.each do |validator_name, patterns|
|
|
135
|
+
next unless ConfigLoader::ALL_VALIDATORS.include?(validator_name)
|
|
136
|
+
next unless audit_config.validator_config(validator_name, 'Exclude') == patterns
|
|
137
|
+
|
|
138
|
+
audit_config.set_validator_config(validator_name, 'Exclude', [])
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
audit_config
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Lint each file set and merge the violating files per validator
|
|
145
|
+
# @param file_sets [Array<Array<String>>] file sets to lint separately
|
|
146
|
+
# @param config [Config] configuration for the audit run
|
|
147
|
+
# @return [Hash{String => Array<String>}] validator names to files with offenses in any run
|
|
148
|
+
def audit_violations(file_sets, config)
|
|
149
|
+
file_sets.uniq(&:sort).each_with_object({}) do |files, merged|
|
|
150
|
+
run_linting(files: files, config: config)[:violations_by_validator].each do |validator_name, paths|
|
|
151
|
+
merged[validator_name] = merged.fetch(validator_name, []) | paths
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Whether the audit can judge a validator's todo entries
|
|
157
|
+
# Entries of validators left out by only_validators are not judged, as they were not run.
|
|
158
|
+
# @param validator_name [String] validator name from the todo file
|
|
159
|
+
# @param config [Config] configuration for the audit run
|
|
160
|
+
# @return [Boolean] true unless only_validators leaves the validator out
|
|
161
|
+
def judged?(validator_name, config)
|
|
162
|
+
config.only_validators.empty? || config.only_validators.include?(validator_name)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Whether a todo pattern belongs to the checked path
|
|
166
|
+
# @param pattern [String] todo Exclude pattern
|
|
167
|
+
# @param files [Array<String>] relative paths of all linted files
|
|
168
|
+
# @param path_files [Array<String>] relative paths of the files under the checked path
|
|
169
|
+
# @return [Boolean] true if the pattern covers a file under the path, or no linted file at all
|
|
170
|
+
def in_scope?(pattern, files, path_files)
|
|
171
|
+
path_files.any? { |file| todo_match?(pattern, file) } ||
|
|
172
|
+
files.none? { |file| todo_match?(pattern, file) }
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Whether a todo Exclude pattern matches a file
|
|
176
|
+
# @param pattern [String] todo Exclude pattern
|
|
177
|
+
# @param file [String] relative file path
|
|
178
|
+
# @return [Boolean] true if the pattern matches the file
|
|
179
|
+
def todo_match?(pattern, file)
|
|
180
|
+
File.fnmatch(pattern, file, Config::EXCLUDE_FLAGS)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Build the summary message for a todo check
|
|
184
|
+
# @param unused_entries [Hash{String => Array<String>}] validator names to unused patterns
|
|
185
|
+
# @return [String] human-readable summary
|
|
186
|
+
def build_check_message(unused_entries)
|
|
187
|
+
return 'No unused entries found in .yard-lint-todo.yml' if unused_entries.empty?
|
|
188
|
+
|
|
189
|
+
count = unused_entries.values.sum(&:size)
|
|
190
|
+
lines = ["Found #{count} unused entr#{count == 1 ? 'y' : 'ies'} in .yard-lint-todo.yml:"]
|
|
191
|
+
unused_entries.each do |validator_name, patterns|
|
|
192
|
+
lines << " #{validator_name}:"
|
|
193
|
+
patterns.each { |pattern| lines << " - '#{pattern}'" }
|
|
194
|
+
end
|
|
195
|
+
lines << ''
|
|
196
|
+
lines << 'Remove these entries, or run yard-lint --regenerate-todo to rebuild the file.'
|
|
197
|
+
lines.join("\n")
|
|
198
|
+
end
|
|
199
|
+
|
|
70
200
|
# Validate that the todo file doesn't already exist
|
|
71
201
|
# @return [void]
|
|
72
202
|
# @raise [Errors::TodoFileExistsError] if todo file exists and force is false
|
|
@@ -78,23 +208,25 @@ module Yard
|
|
|
78
208
|
end
|
|
79
209
|
|
|
80
210
|
# Run linting and collect violations per validator
|
|
211
|
+
# @param files [Array<String>, nil] files to lint (defaults to the files under the path)
|
|
212
|
+
# @param config [Config] configuration to lint with
|
|
81
213
|
# @return [Hash] hash with :violations_by_validator and :total_offenses keys
|
|
82
|
-
def run_linting
|
|
214
|
+
def run_linting(files: nil, config: @config)
|
|
83
215
|
# Run each validator individually to track violations per validator
|
|
84
216
|
# This is more reliable than trying to infer validator from offense name
|
|
85
|
-
files
|
|
86
|
-
runner = Runner.new(files,
|
|
217
|
+
files ||= Yard::Lint.send(:expand_path, @path, config)
|
|
218
|
+
runner = Runner.new(files, config)
|
|
87
219
|
|
|
88
220
|
# Run validators and collect raw results
|
|
89
221
|
raw_results = runner.send(:run_validators)
|
|
90
|
-
result_builder = ResultBuilder.new(
|
|
222
|
+
result_builder = ResultBuilder.new(config)
|
|
91
223
|
|
|
92
224
|
violations_by_validator = {}
|
|
93
225
|
total_offenses = 0
|
|
94
226
|
|
|
95
227
|
# Process each validator's results
|
|
96
228
|
ConfigLoader::ALL_VALIDATORS.each do |validator_name|
|
|
97
|
-
next unless
|
|
229
|
+
next unless config.validator_enabled?(validator_name)
|
|
98
230
|
|
|
99
231
|
validator_result = result_builder.build(validator_name, raw_results)
|
|
100
232
|
next unless validator_result
|
|
@@ -137,14 +269,13 @@ module Yard
|
|
|
137
269
|
end
|
|
138
270
|
end
|
|
139
271
|
|
|
140
|
-
# Convert
|
|
272
|
+
# Convert a path to the form Exclude patterns match (relative to the config directory)
|
|
141
273
|
# @param path [String, nil] absolute or relative file path
|
|
142
|
-
# @return [String, nil] relative
|
|
274
|
+
# @return [String, nil] path relative to the config directory, or nil if path is nil
|
|
143
275
|
def make_relative_path(path)
|
|
144
276
|
return path if path.nil?
|
|
145
277
|
|
|
146
|
-
|
|
147
|
-
path.start_with?(pwd) ? path.sub("#{pwd}/", '') : path
|
|
278
|
+
@config.relative_path(path)
|
|
148
279
|
end
|
|
149
280
|
|
|
150
281
|
# Build YAML content for the todo file
|
|
@@ -110,6 +110,69 @@ module Yard
|
|
|
110
110
|
!@scanned_docstrings.add?(key)
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
+
# Whether the object is a synthesized copy whose docstring has been
|
|
114
|
+
# normalized and should not be linted.
|
|
115
|
+
#
|
|
116
|
+
# `module_function` makes YARD register one definition twice: as a public
|
|
117
|
+
# class method and as a private instance method.
|
|
118
|
+
#
|
|
119
|
+
# One of the two is built with `CodeObjects::Base#copy_to` which assigns a
|
|
120
|
+
# docstring which is a normalized copy of the original, authored docstring
|
|
121
|
+
# that preserves content but not layout.
|
|
122
|
+
#
|
|
123
|
+
# The normalized docstring drops the blank lines between tags, moves param
|
|
124
|
+
# types ahead of param names, and folds in tags inherited from the enclosing
|
|
125
|
+
# namespace (such as an api tag). Linting the normalized docstring reports
|
|
126
|
+
# layout offenses against text no one wrote.
|
|
127
|
+
#
|
|
128
|
+
# The authored docstring is the one YARD tracks back to a range of source
|
|
129
|
+
# lines.
|
|
130
|
+
#
|
|
131
|
+
# For `module_function def name` neither the original object nor its copy
|
|
132
|
+
# carries a line range. For this form, the instance method holds the original
|
|
133
|
+
# docstring (YARD::Handlers::Ruby::ModuleFunctionHandler copies it to the
|
|
134
|
+
# class method).
|
|
135
|
+
#
|
|
136
|
+
# @param object [YARD::CodeObjects::Base] the code object to check
|
|
137
|
+
# @return [Boolean] true if the object's docstring is a normalized copy
|
|
138
|
+
def module_function_copy?(object)
|
|
139
|
+
return false unless object.type == :method
|
|
140
|
+
# Checked before the twin lookup, which scans the namespace's children:
|
|
141
|
+
# an object YARD tracked back to source lines is authored, whatever it
|
|
142
|
+
# is paired with, and most objects are
|
|
143
|
+
return false if object.docstring.line_range
|
|
144
|
+
|
|
145
|
+
twin = module_function_twin(object)
|
|
146
|
+
return false unless twin
|
|
147
|
+
return true if twin.docstring.line_range
|
|
148
|
+
|
|
149
|
+
object.scope == :class
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Finds the other object registered for a module_function definition.
|
|
153
|
+
# @param object [YARD::CodeObjects::Base] one half of the pair
|
|
154
|
+
# @return [YARD::CodeObjects::MethodObject, nil] the other half, or nil
|
|
155
|
+
# when the object does not come from module_function
|
|
156
|
+
def module_function_twin(object)
|
|
157
|
+
return nil unless object.respond_to?(:scope)
|
|
158
|
+
# `def CONST.name` leaves a Proxy or ConstantObject namespace with no
|
|
159
|
+
# children; Proxy#is_a? answers for the resolved object
|
|
160
|
+
return nil unless object.namespace.is_a?(YARD::CodeObjects::NamespaceObject)
|
|
161
|
+
|
|
162
|
+
twin = object.namespace.child(
|
|
163
|
+
name: object.name,
|
|
164
|
+
scope: (object.scope == :class) ? :instance : :class
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return nil unless twin
|
|
168
|
+
return nil unless twin.file == object.file && twin.line == object.line
|
|
169
|
+
# Both halves point at the same definition, but only the class method
|
|
170
|
+
# is flagged as a module function by YARD.
|
|
171
|
+
return nil unless [object, twin].any? { |o| o.respond_to?(:module_function?) && o.module_function? }
|
|
172
|
+
|
|
173
|
+
twin
|
|
174
|
+
end
|
|
175
|
+
|
|
113
176
|
# Converts a zero-based line offset within a docstring's text into an
|
|
114
177
|
# absolute line number in the source file, so offenses can point at
|
|
115
178
|
# the offending documentation line instead of the definition line.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module ReferenceTypo
|
|
8
|
+
# Configuration for ReferenceTypo validator
|
|
9
|
+
class Config < ::Yard::Lint::Validators::Config
|
|
10
|
+
self.id = :reference_typo
|
|
11
|
+
self.defaults = {
|
|
12
|
+
'Enabled' => true,
|
|
13
|
+
'Severity' => 'warning'
|
|
14
|
+
}.freeze
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module ReferenceTypo
|
|
8
|
+
# Builds human-readable messages for ReferenceTypo violations
|
|
9
|
+
class MessagesBuilder
|
|
10
|
+
class << self
|
|
11
|
+
# Formats a reference typo violation message
|
|
12
|
+
# @param offense [Hash] offense details with :reference and :suggestion keys
|
|
13
|
+
# @return [String] formatted message
|
|
14
|
+
def call(offense)
|
|
15
|
+
"Reference '#{offense[:reference]}' does not resolve " \
|
|
16
|
+
"(did you mean '#{offense[:suggestion]}'?)"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module ReferenceTypo
|
|
8
|
+
# Parser for ReferenceTypo validator output
|
|
9
|
+
class Parser < ::Yard::Lint::Parsers::Base
|
|
10
|
+
# Parses validator output and extracts reference typo violations
|
|
11
|
+
# Expected format (two lines per violation):
|
|
12
|
+
# file.rb:LINE: ObjectName
|
|
13
|
+
# reference|suggestion
|
|
14
|
+
# @param yard_output [String] raw validator output
|
|
15
|
+
# @return [Array<Hash>] array with violation details
|
|
16
|
+
def call(yard_output, **)
|
|
17
|
+
return [] if yard_output.nil? || yard_output.strip.empty?
|
|
18
|
+
|
|
19
|
+
lines = yard_output.split("\n").map(&:strip).reject(&:empty?)
|
|
20
|
+
|
|
21
|
+
lines.each_slice(2).filter_map do |location_line, details_line|
|
|
22
|
+
next unless location_line && details_line
|
|
23
|
+
|
|
24
|
+
location_match = location_line.match(/^(.+):(\d+): (.+)$/)
|
|
25
|
+
next unless location_match
|
|
26
|
+
|
|
27
|
+
reference, suggestion = details_line.split('|', 2)
|
|
28
|
+
next if reference.to_s.empty? || suggestion.to_s.empty?
|
|
29
|
+
|
|
30
|
+
{
|
|
31
|
+
location: location_match[1],
|
|
32
|
+
line: location_match[2].to_i,
|
|
33
|
+
object_name: location_match[3],
|
|
34
|
+
reference: reference,
|
|
35
|
+
suggestion: suggestion
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module ReferenceTypo
|
|
8
|
+
# Result wrapper for ReferenceTypo validator
|
|
9
|
+
class Result < Results::Base
|
|
10
|
+
self.default_severity = 'warning'
|
|
11
|
+
self.offense_type = 'line'
|
|
12
|
+
self.offense_name = 'ReferenceTypo'
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
# Builds a human-readable message for the offense
|
|
17
|
+
# @param offense [Hash] offense details
|
|
18
|
+
# @return [String] formatted message
|
|
19
|
+
def build_message(offense)
|
|
20
|
+
MessagesBuilder.call(offense)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|