yard-lint 1.12.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +199 -501
  3. data/README.md +94 -1
  4. data/bin/yard-lint +68 -32
  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 +42 -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 +3 -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/type_syntax/parser.rb +13 -2
  48. data/lib/yard/lint/validators/tags/type_syntax/validator.rb +11 -1
  49. data/lib/yard/lint/version.rb +1 -1
  50. data/lib/yard/lint.rb +3 -57
  51. metadata +21 -1
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ module OverloadConsistency
8
+ # Configuration for OverloadConsistency validator
9
+ class Config < ::Yard::Lint::Validators::Config
10
+ self.id = :overload_consistency
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,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ module OverloadConsistency
8
+ # Builds human-readable messages for OverloadConsistency violations
9
+ class MessagesBuilder
10
+ class << self
11
+ # Formats an overload consistency violation message
12
+ # @param offense [Hash] offense details with :kind, :param and :signature keys
13
+ # @return [String] formatted message
14
+ def call(offense)
15
+ overload = "Overload '#{offense[:signature]}'"
16
+
17
+ case offense[:kind]
18
+ when 'unknown_param'
19
+ "#{overload} documents @param '#{offense[:param]}', which is not in its signature"
20
+ when 'undocumented_param'
21
+ "#{overload} has no @param for '#{offense[:param]}'"
22
+ else
23
+ "#{overload} has no @return, while other overloads of this method have one"
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ module OverloadConsistency
8
+ # Parser for OverloadConsistency validator output
9
+ class Parser < ::Yard::Lint::Parsers::Base
10
+ # Offense kinds the validator emits
11
+ KINDS = %w[unknown_param undocumented_param missing_return].freeze
12
+
13
+ # Parses validator output and extracts overload violations
14
+ # Expected format (two lines per violation):
15
+ # file.rb:LINE: ObjectName
16
+ # kind|param|signature
17
+ # @param yard_output [String] raw validator output
18
+ # @return [Array<Hash>] array with violation details
19
+ def call(yard_output, **)
20
+ return [] if yard_output.nil? || yard_output.strip.empty?
21
+
22
+ lines = yard_output.split("\n").map(&:strip).reject(&:empty?)
23
+
24
+ lines.each_slice(2).filter_map do |location_line, details_line|
25
+ next unless location_line && details_line
26
+
27
+ location_match = location_line.match(/^(.+):(\d+): (.+)$/)
28
+ next unless location_match
29
+
30
+ kind, param, signature = details_line.split('|', 3)
31
+ next unless KINDS.include?(kind)
32
+ next if signature.to_s.empty?
33
+
34
+ {
35
+ location: location_match[1],
36
+ line: location_match[2].to_i,
37
+ object_name: location_match[3],
38
+ kind: kind,
39
+ param: param,
40
+ signature: signature
41
+ }
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ module OverloadConsistency
8
+ # Result wrapper for OverloadConsistency validator
9
+ class Result < Results::Base
10
+ self.default_severity = 'warning'
11
+ self.offense_type = 'method'
12
+ self.offense_name = 'OverloadConsistency'
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
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ module OverloadConsistency
8
+ # Validates each @overload's tags against that overload's own signature
9
+ class Validator < Base
10
+ in_process visibility: :all
11
+
12
+ # Execute query for a single object during in-process execution.
13
+ # @param object [YARD::CodeObjects::Base] the code object to query
14
+ # @param collector [Executor::ResultCollector] collector for output
15
+ # @return [void]
16
+ def in_process_query(object, collector)
17
+ return unless object.type == :method
18
+ return if object.is_alias?
19
+ return if module_function_copy?(object)
20
+
21
+ overloads = object.tags(:overload)
22
+ return if overloads.empty?
23
+
24
+ shared = object.docstring.tags(:param).map { |tag| tag.name.to_s }
25
+ offenses = overloads.flat_map { |overload| param_offenses(overload, shared) }
26
+ offenses.concat(return_offenses(object, overloads))
27
+
28
+ offenses.each do |kind, param, overload|
29
+ collector.puts "#{object.file}:#{object.line}: #{object.title}"
30
+ collector.puts "#{kind}|#{param}|#{overload.signature}"
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ # @param overload [YARD::Tags::OverloadTag] overload to check
37
+ # @param shared [Array<String>] @param names on the method docstring, common to all overloads
38
+ # @return [Array<Array>] [kind, param name, overload] offenses
39
+ def param_offenses(overload, shared)
40
+ documented = overload.docstring.tags(:param).map { |tag| tag.name.to_s }
41
+ return [] if documented.empty?
42
+
43
+ names = overload.parameters.map { |param| param[0].to_s }
44
+ plain = names.reject { |name| name.start_with?('*', '&', '...') }.map { |name| name.sub(/:\z/, '') }
45
+
46
+ offenses = (plain - documented - shared).map { |name| ['undocumented_param', name, overload] }
47
+
48
+ # `...`, `*`, `**` and `&` take arguments no @param can name
49
+ return offenses if names.any? { |name| %w[... * ** &].include?(name) }
50
+
51
+ known = names.map { |name| name.delete_prefix('**').delete_prefix('*').delete_prefix('&').sub(/:\z/, '') }
52
+ (documented.uniq - known).map { |name| ['unknown_param', name, overload] } + offenses
53
+ end
54
+
55
+ # @param object [YARD::CodeObjects::MethodObject] the method
56
+ # @param overloads [Array<YARD::Tags::OverloadTag>] its overloads
57
+ # @return [Array<Array>] [kind, param name, overload] offenses
58
+ def return_offenses(object, overloads)
59
+ return [] if object.docstring.has_tag?(:return)
60
+
61
+ missing = overloads.reject { |overload| overload.docstring.has_tag?(:return) }
62
+ return [] if missing.size == overloads.size
63
+
64
+ missing.map { |overload| ['missing_return', nil, overload] }
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ # OverloadConsistency validator
8
+ #
9
+ # Checks each `@overload` against its own signature. YARD checks `@param`
10
+ # names only on the method's primary docstring, so tags inside an
11
+ # overload are never compared with the overload's parameters. This
12
+ # validator reports:
13
+ #
14
+ # - a `@param` that names no parameter of its overload's signature
15
+ # - a parameter with no `@param` in an overload that documents at least
16
+ # one other parameter (splat and block parameters are not required)
17
+ # - an overload with no `@return` when another overload of the same
18
+ # method has one and the method docstring itself has none
19
+ #
20
+ # Overloads that use argument forwarding (`...`) or anonymous splat or
21
+ # block parameters are not checked for unknown names. This validator is
22
+ # enabled by default.
23
+ #
24
+ # @example Bad - @param does not match the overload's signature
25
+ # # @overload fetch(key)
26
+ # # @param name [String] the key
27
+ # # @return [Object]
28
+ # def fetch(*args)
29
+ # end
30
+ #
31
+ # @example Good - each overload documents its own parameters
32
+ # # @overload fetch(key)
33
+ # # @param key [String] the key
34
+ # # @return [Object]
35
+ # # @overload fetch(key, default)
36
+ # # @param key [String] the key
37
+ # # @param default [Object] value when the key is missing
38
+ # # @return [Object]
39
+ # def fetch(*args)
40
+ # end
41
+ #
42
+ # ## Configuration
43
+ #
44
+ # To disable this validator:
45
+ #
46
+ # Tags/OverloadConsistency:
47
+ # Enabled: false
48
+ module OverloadConsistency
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -27,9 +27,9 @@ module Yard
27
27
  tags_to_check = config_checked_tags
28
28
  patterns = config_enabled_patterns
29
29
 
30
- object.docstring.tags.each do |tag|
31
- next unless tags_to_check.include?(tag.tag_name)
32
-
30
+ # Tags nested inside overload blocks live on the overload's own
31
+ # docstring, so check those too
32
+ all_typed_tags(object.docstring, tags_to_check).each do |tag|
33
33
  # Option tags carry their description on the nested pair tag;
34
34
  # the documented name is the option key (e.g. ":mode"), not the
35
35
  # hash parameter name
@@ -12,6 +12,8 @@ module Yard
12
12
  # Expected format (two lines per violation):
13
13
  # file.rb:LINE: ClassName#method_name
14
14
  # tag_name|type_string|error_message
15
+ # '|', '%' and newlines inside type_string and error_message are escaped
16
+ # as %7C, %25 and %0A
15
17
  # @param yard_output [String] raw YARD query results
16
18
  # @option _kwargs [Object] :unused this parameter accepts no options (reserved for future use)
17
19
  # @return [Array<Hash>] array with violation details
@@ -44,13 +46,22 @@ module Yard
44
46
  line: location_match[2].to_i,
45
47
  method_name: location_match[3],
46
48
  tag_name: tag_name,
47
- type_string: type_string,
48
- error_message: error_message
49
+ type_string: unescape_field(type_string),
50
+ error_message: unescape_field(error_message)
49
51
  }
50
52
  end
51
53
 
52
54
  violations
53
55
  end
56
+
57
+ private
58
+
59
+ # Reverts the validator's escaping of '|', '%' and newlines
60
+ # @param text [String] escaped field text
61
+ # @return [String] raw field text
62
+ def unescape_field(text)
63
+ text.gsub(/%(?:25|7C|0A)/, '%25' => '%', '%7C' => '|', '%0A' => "\n")
64
+ end
54
65
  end
55
66
  end
56
67
  end
@@ -49,12 +49,22 @@ module Yard
49
49
  # YARD's parser may generate malformed error messages for non-ASCII input
50
50
  error_msg = e.message.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
51
51
  collector.puts "#{object.file}:#{object.line}: #{object.title}"
52
- collector.puts "#{tag.tag_name}|#{type_str}|#{error_msg}"
52
+ collector.puts "#{tag.tag_name}|#{escape_field(type_str)}|#{escape_field(error_msg)}"
53
53
  break
54
54
  end
55
55
  end
56
56
  end
57
57
  end
58
+
59
+ private
60
+
61
+ # Escapes '|' (the field separator), newlines (YARD keeps them in
62
+ # multi-line types) and '%' (the escape character)
63
+ # @param text [String] raw field text
64
+ # @return [String] escaped field text
65
+ def escape_field(text)
66
+ text.gsub(/[%|\n]/, '%' => '%25', '|' => '%7C', "\n" => '%0A')
67
+ end
58
68
  end
59
69
  end
60
70
  end
@@ -3,6 +3,6 @@
3
3
  module Yard
4
4
  module Lint
5
5
  # @return [String] version of the YARD Lint gem
6
- VERSION = '1.12.0'
6
+ VERSION = '1.13.0'
7
7
  end
8
8
  end
data/lib/yard/lint.rb CHANGED
@@ -5,6 +5,7 @@ require 'shellwords'
5
5
  require 'open3'
6
6
  require 'tempfile'
7
7
  require 'tmpdir'
8
+ require 'pathname'
8
9
  require 'digest'
9
10
  require 'did_you_mean'
10
11
  require 'yard'
@@ -73,9 +74,6 @@ module Yard
73
74
  # @param config [Yard::Lint::Config] configuration object
74
75
  # @return [Array<String>] array of absolute file paths
75
76
  def get_diff_files(diff, path, config)
76
- # Determine the base directory for relative path calculations
77
- base_dir = determine_base_dir(path)
78
-
79
77
  # Get changed files from git based on mode
80
78
  git_files = case diff[:mode]
81
79
  when :ref
@@ -92,7 +90,7 @@ module Yard
92
90
  end
93
91
 
94
92
  # Apply exclusion patterns
95
- git_files.reject { |file| excluded_file?(file, config.exclude, base_dir) }
93
+ git_files.reject { |file| config.excluded_path?(file, config.exclude) }
96
94
  end
97
95
 
98
96
  # Expand path/glob patterns into an array of files
@@ -100,16 +98,13 @@ module Yard
100
98
  # @param config [Yard::Lint::Config] configuration object
101
99
  # @return [Array<String>] array of absolute file paths
102
100
  def expand_path(path, config)
103
- # Determine the base directory for relative path calculations
104
- base_dir = determine_base_dir(path)
105
-
106
101
  files = discover_ruby_files(path)
107
102
 
108
103
  # Convert to absolute paths for YARD
109
104
  files = files.map { |file| File.expand_path(file) }
110
105
 
111
106
  # Filter out excluded files
112
- files.reject { |file| excluded_file?(file, config.exclude, base_dir) }
107
+ files.reject { |file| config.excluded_path?(file, config.exclude) }
113
108
  end
114
109
 
115
110
  # Discover Ruby files from path/glob patterns
@@ -139,55 +134,6 @@ module Yard
139
134
 
140
135
  raise Errors::FileNotFoundError, "No such file or directory: #{path}"
141
136
  end
142
-
143
- # Determine base directory for relative path calculations
144
- # @param path [String, Array<String>] path or array of paths
145
- # @return [String] absolute base directory path
146
- def determine_base_dir(path)
147
- first_path = Array(path).first
148
- return Dir.pwd unless first_path
149
-
150
- absolute_path = File.expand_path(first_path)
151
- File.directory?(absolute_path) ? absolute_path : File.dirname(absolute_path)
152
- end
153
-
154
- # Check if a file matches any exclusion pattern
155
- # Patterns are matched against both absolute and relative paths
156
- # @param file [String] absolute file path
157
- # @param patterns [Array<String>] exclusion patterns
158
- # @param base_dir [String] base directory for relative path calculation
159
- # @return [Boolean] true if file should be excluded
160
- def excluded_file?(file, patterns, base_dir)
161
- relative_path = relative_path_from(file, base_dir)
162
-
163
- patterns.any? do |pattern|
164
- match_path?(pattern, file, relative_path)
165
- end
166
- end
167
-
168
- # Calculate relative path from base directory
169
- # @param file [String] absolute file path
170
- # @param base_dir [String] base directory
171
- # @return [String] relative path
172
- def relative_path_from(file, base_dir)
173
- if file.start_with?("#{base_dir}/")
174
- file.sub("#{base_dir}/", '')
175
- else
176
- file
177
- end
178
- end
179
-
180
- # Check if a pattern matches a file path
181
- # Tries matching against both relative and absolute paths
182
- # @param pattern [String] glob pattern
183
- # @param absolute_path [String] absolute file path
184
- # @param relative_path [String] relative file path
185
- # @return [Boolean] true if pattern matches
186
- def match_path?(pattern, absolute_path, relative_path)
187
- flags = File::FNM_PATHNAME | File::FNM_EXTGLOB
188
-
189
- File.fnmatch(pattern, relative_path, flags) || File.fnmatch(pattern, absolute_path, flags)
190
- end
191
137
  end
192
138
  end
193
139
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yard-lint
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.12.0
4
+ version: 1.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maciej Mensfeld
@@ -57,6 +57,13 @@ files:
57
57
  - lib/yard/lint/config_loader.rb
58
58
  - lib/yard/lint/config_updater.rb
59
59
  - lib/yard/lint/config_validator.rb
60
+ - lib/yard/lint/contractable/contract.rb
61
+ - lib/yard/lint/contractable/result.rb
62
+ - lib/yard/lint/contractable/rule.rb
63
+ - lib/yard/lint/contracts/base.rb
64
+ - lib/yard/lint/contracts/category.rb
65
+ - lib/yard/lint/contracts/config.rb
66
+ - lib/yard/lint/contracts/validator.rb
60
67
  - lib/yard/lint/errors.rb
61
68
  - lib/yard/lint/executor/in_process_registry.rb
62
69
  - lib/yard/lint/executor/query_executor.rb
@@ -65,6 +72,7 @@ files:
65
72
  - lib/yard/lint/explainer.rb
66
73
  - lib/yard/lint/ext/irb_notifier_shim.rb
67
74
  - lib/yard/lint/formatters/progress.rb
75
+ - lib/yard/lint/formatters/sarif.rb
68
76
  - lib/yard/lint/git.rb
69
77
  - lib/yard/lint/parsers/base.rb
70
78
  - lib/yard/lint/parsers/one_line_base.rb
@@ -122,6 +130,12 @@ files:
122
130
  - lib/yard/lint/validators/documentation/orphaned_doc_comment/parser.rb
123
131
  - lib/yard/lint/validators/documentation/orphaned_doc_comment/result.rb
124
132
  - lib/yard/lint/validators/documentation/orphaned_doc_comment/validator.rb
133
+ - lib/yard/lint/validators/documentation/reference_typo.rb
134
+ - lib/yard/lint/validators/documentation/reference_typo/config.rb
135
+ - lib/yard/lint/validators/documentation/reference_typo/messages_builder.rb
136
+ - lib/yard/lint/validators/documentation/reference_typo/parser.rb
137
+ - lib/yard/lint/validators/documentation/reference_typo/result.rb
138
+ - lib/yard/lint/validators/documentation/reference_typo/validator.rb
125
139
  - lib/yard/lint/validators/documentation/text_substitution.rb
126
140
  - lib/yard/lint/validators/documentation/text_substitution/config.rb
127
141
  - lib/yard/lint/validators/documentation/text_substitution/messages_builder.rb
@@ -236,6 +250,12 @@ files:
236
250
  - lib/yard/lint/validators/tags/order/parser.rb
237
251
  - lib/yard/lint/validators/tags/order/result.rb
238
252
  - lib/yard/lint/validators/tags/order/validator.rb
253
+ - lib/yard/lint/validators/tags/overload_consistency.rb
254
+ - lib/yard/lint/validators/tags/overload_consistency/config.rb
255
+ - lib/yard/lint/validators/tags/overload_consistency/messages_builder.rb
256
+ - lib/yard/lint/validators/tags/overload_consistency/parser.rb
257
+ - lib/yard/lint/validators/tags/overload_consistency/result.rb
258
+ - lib/yard/lint/validators/tags/overload_consistency/validator.rb
239
259
  - lib/yard/lint/validators/tags/redundant_param_description.rb
240
260
  - lib/yard/lint/validators/tags/redundant_param_description/config.rb
241
261
  - lib/yard/lint/validators/tags/redundant_param_description/messages_builder.rb