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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +201 -498
  3. data/README.md +103 -1
  4. data/bin/yard-lint +100 -40
  5. data/lib/yard/lint/config.rb +81 -5
  6. data/lib/yard/lint/config_validator.rb +7 -191
  7. data/lib/yard/lint/contractable/contract.rb +219 -0
  8. data/lib/yard/lint/contractable/result.rb +93 -0
  9. data/lib/yard/lint/contractable/rule.rb +10 -0
  10. data/lib/yard/lint/contracts/base.rb +57 -0
  11. data/lib/yard/lint/contracts/category.rb +71 -0
  12. data/lib/yard/lint/contracts/config.rb +81 -0
  13. data/lib/yard/lint/contracts/validator.rb +72 -0
  14. data/lib/yard/lint/errors.rb +3 -0
  15. data/lib/yard/lint/executor/in_process_registry.rb +1 -1
  16. data/lib/yard/lint/explainer.rb +205 -0
  17. data/lib/yard/lint/formatters/sarif.rb +218 -0
  18. data/lib/yard/lint/git.rb +5 -3
  19. data/lib/yard/lint/result_builder.rb +7 -6
  20. data/lib/yard/lint/runner.rb +2 -21
  21. data/lib/yard/lint/templates/default_config.yml +10 -0
  22. data/lib/yard/lint/templates/strict_config.yml +10 -0
  23. data/lib/yard/lint/todo_generator.rb +143 -12
  24. data/lib/yard/lint/validators/base.rb +63 -0
  25. data/lib/yard/lint/validators/documentation/reference_typo/config.rb +20 -0
  26. data/lib/yard/lint/validators/documentation/reference_typo/messages_builder.rb +24 -0
  27. data/lib/yard/lint/validators/documentation/reference_typo/parser.rb +44 -0
  28. data/lib/yard/lint/validators/documentation/reference_typo/result.rb +27 -0
  29. data/lib/yard/lint/validators/documentation/reference_typo/validator.rb +214 -0
  30. data/lib/yard/lint/validators/documentation/reference_typo.rb +39 -0
  31. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/parser.rb +13 -12
  32. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/validator.rb +84 -5
  33. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods.rb +16 -3
  34. data/lib/yard/lint/validators/documentation/undocumented_options/validator.rb +3 -2
  35. data/lib/yard/lint/validators/tags/example_style/validator.rb +4 -4
  36. data/lib/yard/lint/validators/tags/example_syntax/validator.rb +4 -2
  37. data/lib/yard/lint/validators/tags/invalid_types/messages_builder.rb +15 -4
  38. data/lib/yard/lint/validators/tags/invalid_types/parser.rb +18 -7
  39. data/lib/yard/lint/validators/tags/invalid_types/validator.rb +22 -12
  40. data/lib/yard/lint/validators/tags/overload_consistency/config.rb +20 -0
  41. data/lib/yard/lint/validators/tags/overload_consistency/messages_builder.rb +32 -0
  42. data/lib/yard/lint/validators/tags/overload_consistency/parser.rb +49 -0
  43. data/lib/yard/lint/validators/tags/overload_consistency/result.rb +27 -0
  44. data/lib/yard/lint/validators/tags/overload_consistency/validator.rb +71 -0
  45. data/lib/yard/lint/validators/tags/overload_consistency.rb +53 -0
  46. data/lib/yard/lint/validators/tags/redundant_param_description/validator.rb +3 -3
  47. data/lib/yard/lint/validators/tags/tag_group_separator/validator.rb +8 -0
  48. data/lib/yard/lint/validators/tags/tag_separator/validator.rb +8 -0
  49. data/lib/yard/lint/validators/tags/type_syntax/parser.rb +13 -2
  50. data/lib/yard/lint/validators/tags/type_syntax/validator.rb +11 -1
  51. data/lib/yard/lint/version.rb +1 -1
  52. data/lib/yard/lint.rb +3 -57
  53. metadata +23 -2
@@ -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 do |file|
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
- file_path = offense[:location] || offense[:file]
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 current directory)
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(Dir.pwd, '.yard-lint-todo.yml')
37
- @config_path = config_path || File.join(Dir.pwd, Config::DEFAULT_CONFIG_FILE)
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 = Yard::Lint.send(:expand_path, @path, @config)
86
- runner = Runner.new(files, @config)
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(@config)
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 @config.validator_enabled?(validator_name)
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 absolute path to relative path from current directory
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 path from current directory, or nil if path is nil
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
- pwd = Dir.pwd
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