yard-lint 1.5.1 → 1.6.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 +51 -0
- data/README.md +207 -8
- data/bin/yard-lint +45 -11
- data/lib/yard/lint/executor/in_process_registry.rb +38 -12
- data/lib/yard/lint/results/base.rb +3 -0
- data/lib/yard/lint/runner.rb +11 -3
- data/lib/yard/lint/templates/default_config.yml +40 -0
- data/lib/yard/lint/templates/strict_config.yml +39 -0
- data/lib/yard/lint/todo_generator.rb +6 -4
- data/lib/yard/lint/validators/base.rb +125 -0
- data/lib/yard/lint/validators/documentation/line_length/config.rb +21 -0
- data/lib/yard/lint/validators/documentation/line_length/messages_builder.rb +26 -0
- data/lib/yard/lint/validators/documentation/line_length/parser.rb +65 -0
- data/lib/yard/lint/validators/documentation/line_length/result.rb +26 -0
- data/lib/yard/lint/validators/documentation/line_length/validator.rb +59 -0
- data/lib/yard/lint/validators/documentation/line_length.rb +43 -0
- data/lib/yard/lint/validators/documentation/missing_return/config.rb +2 -1
- data/lib/yard/lint/validators/documentation/missing_return/parser.rb +0 -1
- data/lib/yard/lint/validators/documentation/missing_return/validator.rb +1 -0
- data/lib/yard/lint/validators/documentation/orphaned_doc_comment/config.rb +20 -0
- data/lib/yard/lint/validators/documentation/orphaned_doc_comment/messages_builder.rb +23 -0
- data/lib/yard/lint/validators/documentation/orphaned_doc_comment/parser.rb +38 -0
- data/lib/yard/lint/validators/documentation/orphaned_doc_comment/result.rb +24 -0
- data/lib/yard/lint/validators/documentation/orphaned_doc_comment/validator.rb +121 -0
- data/lib/yard/lint/validators/documentation/orphaned_doc_comment.rb +53 -0
- data/lib/yard/lint/validators/documentation/text_substitution/config.rb +24 -0
- data/lib/yard/lint/validators/documentation/text_substitution/messages_builder.rb +33 -0
- data/lib/yard/lint/validators/documentation/text_substitution/parser.rb +57 -0
- data/lib/yard/lint/validators/documentation/text_substitution/result.rb +24 -0
- data/lib/yard/lint/validators/documentation/text_substitution/validator.rb +72 -0
- data/lib/yard/lint/validators/documentation/text_substitution.rb +55 -0
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/config.rb +2 -1
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/validator.rb +1 -0
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/config.rb +3 -1
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/validator.rb +3 -1
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments.rb +1 -2
- data/lib/yard/lint/validators/documentation/undocumented_objects/config.rb +2 -1
- data/lib/yard/lint/validators/documentation/undocumented_objects/validator.rb +1 -0
- data/lib/yard/lint/validators/documentation/undocumented_options/config.rb +2 -1
- data/lib/yard/lint/validators/documentation/undocumented_options/validator.rb +1 -0
- data/lib/yard/lint/validators/documentation/undocumented_options.rb +1 -2
- data/lib/yard/lint/validators/tags/api_tags/result.rb +1 -0
- data/lib/yard/lint/validators/tags/api_tags/validator.rb +12 -0
- data/lib/yard/lint/validators/tags/collection_type/config.rb +1 -1
- data/lib/yard/lint/validators/tags/collection_type/validator.rb +1 -3
- data/lib/yard/lint/validators/tags/example_style/result.rb +1 -0
- data/lib/yard/lint/validators/tags/example_syntax/result.rb +1 -0
- data/lib/yard/lint/validators/tags/invalid_types/config.rb +1 -1
- data/lib/yard/lint/validators/tags/invalid_types/messages_builder.rb +14 -3
- data/lib/yard/lint/validators/tags/invalid_types/parser.rb +63 -3
- data/lib/yard/lint/validators/tags/invalid_types/validator.rb +31 -16
- data/lib/yard/lint/validators/tags/meaningless_tag/validator.rb +16 -1
- data/lib/yard/lint/validators/tags/missing_yield/config.rb +20 -0
- data/lib/yard/lint/validators/tags/missing_yield/messages_builder.rb +22 -0
- data/lib/yard/lint/validators/tags/missing_yield/parser.rb +39 -0
- data/lib/yard/lint/validators/tags/missing_yield/result.rb +24 -0
- data/lib/yard/lint/validators/tags/missing_yield/validator.rb +76 -0
- data/lib/yard/lint/validators/tags/missing_yield.rb +56 -0
- data/lib/yard/lint/validators/tags/non_ascii_type/config.rb +1 -1
- data/lib/yard/lint/validators/tags/non_ascii_type/validator.rb +1 -3
- data/lib/yard/lint/validators/tags/redundant_param_description/validator.rb +4 -2
- data/lib/yard/lint/validators/tags/redundant_param_description.rb +2 -1
- data/lib/yard/lint/validators/tags/type_syntax/config.rb +1 -1
- data/lib/yard/lint/validators/tags/type_syntax/validator.rb +1 -3
- data/lib/yard/lint/validators/tags/type_syntax.rb +1 -2
- data/lib/yard/lint/validators/warnings/duplicated_parameter_name/parser.rb +4 -4
- data/lib/yard/lint/validators/warnings/duplicated_parameter_name.rb +1 -2
- data/lib/yard/lint/validators/warnings/invalid_directive_format.rb +1 -2
- data/lib/yard/lint/version.rb +1 -1
- data/lib/yard/lint.rb +14 -4
- metadata +26 -2
|
@@ -28,6 +28,7 @@ module Yard
|
|
|
28
28
|
severity: configured_severity,
|
|
29
29
|
type: self.class.offense_type,
|
|
30
30
|
name: offense_data[:name] || self.class.offense_name,
|
|
31
|
+
validator: validator_name,
|
|
31
32
|
message: build_message(offense_data),
|
|
32
33
|
location: offense_data[:location] || offense_data[:file],
|
|
33
34
|
location_line: offense_data[:line] || offense_data[:location_line] || 0
|
|
@@ -28,6 +28,7 @@ module Yard
|
|
|
28
28
|
severity: configured_severity,
|
|
29
29
|
type: self.class.offense_type,
|
|
30
30
|
name: offense_data[:name] || self.class.offense_name,
|
|
31
|
+
validator: validator_name,
|
|
31
32
|
message: build_message(offense_data),
|
|
32
33
|
location: offense_data[:location] || offense_data[:file],
|
|
33
34
|
location_line: offense_data[:line] || offense_data[:location_line] || 0
|
|
@@ -9,11 +9,22 @@ module Yard
|
|
|
9
9
|
class MessagesBuilder
|
|
10
10
|
class << self
|
|
11
11
|
# Build message for invalid tag types
|
|
12
|
-
# @param offense [Hash] offense data with :method_name
|
|
12
|
+
# @param offense [Hash] offense data with :method_name and :tag_violations keys
|
|
13
13
|
# @return [String] formatted message
|
|
14
14
|
def call(offense)
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
violations = offense[:tag_violations]
|
|
16
|
+
|
|
17
|
+
if violations&.any?
|
|
18
|
+
details = violations.map do |v|
|
|
19
|
+
label = v[:param] ? "#{v[:tag]} #{v[:param]}" : v[:tag]
|
|
20
|
+
types = v[:types].map { |t| "`#{t}`" }.join(', ')
|
|
21
|
+
"#{label}: #{types}"
|
|
22
|
+
end.join('; ')
|
|
23
|
+
|
|
24
|
+
"The `#{offense[:method_name]}` has invalid type(s): #{details}"
|
|
25
|
+
else
|
|
26
|
+
"The `#{offense[:method_name]}` has at least one tag with an invalid type definition."
|
|
27
|
+
end
|
|
17
28
|
end
|
|
18
29
|
end
|
|
19
30
|
end
|
|
@@ -5,9 +5,69 @@ module Yard
|
|
|
5
5
|
module Validators
|
|
6
6
|
module Tags
|
|
7
7
|
module InvalidTypes
|
|
8
|
-
# Parser for invalid
|
|
9
|
-
#
|
|
10
|
-
|
|
8
|
+
# Parser for invalid tag types output.
|
|
9
|
+
# The validator emits two lines per offense:
|
|
10
|
+
# /path/to/file.rb:10: ClassName#method_name
|
|
11
|
+
# @tagname param_name:BadType1,BadType2|@tagname2:BadType3
|
|
12
|
+
class Parser < Parsers::Base
|
|
13
|
+
# @return [Regexp] matches "file:line: ClassName#method"
|
|
14
|
+
LOCATION_REGEX = /^(.+):(\d+):\s+(.+)[#.](.+)$/
|
|
15
|
+
# @return [Regexp] matches the tag violations line (starts with @)
|
|
16
|
+
TAG_VIOLATIONS_REGEX = /^@/
|
|
17
|
+
|
|
18
|
+
# @param yard_output [String] raw validator output
|
|
19
|
+
# @return [Array<Hash>] parsed offenses
|
|
20
|
+
def call(yard_output)
|
|
21
|
+
lines = yard_output.split("\n").reject(&:empty?)
|
|
22
|
+
results = []
|
|
23
|
+
i = 0
|
|
24
|
+
|
|
25
|
+
while i < lines.size
|
|
26
|
+
match = lines[i].match(LOCATION_REGEX)
|
|
27
|
+
|
|
28
|
+
unless match
|
|
29
|
+
i += 1
|
|
30
|
+
next
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
offense = {
|
|
34
|
+
location: match[1],
|
|
35
|
+
line: match[2].to_i,
|
|
36
|
+
class_name: match[3],
|
|
37
|
+
method_name: match[4],
|
|
38
|
+
tag_violations: []
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
next_line = lines[i + 1]
|
|
42
|
+
if next_line&.match?(TAG_VIOLATIONS_REGEX)
|
|
43
|
+
offense[:tag_violations] = parse_tag_violations(next_line)
|
|
44
|
+
i += 2
|
|
45
|
+
else
|
|
46
|
+
i += 1
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
results << offense
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
results
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# Parse "tagname param:Type1,Type2|tagname2:Type3" into structured data
|
|
58
|
+
# @param line [String] the violations line
|
|
59
|
+
# @return [Array<Hash>] each entry has :tag, :param (may be nil), :types
|
|
60
|
+
def parse_tag_violations(line)
|
|
61
|
+
line.split('|').map do |entry|
|
|
62
|
+
label, types_str = entry.split(':', 2)
|
|
63
|
+
parts = label.split(' ', 2)
|
|
64
|
+
{
|
|
65
|
+
tag: parts[0],
|
|
66
|
+
param: parts[1],
|
|
67
|
+
types: (types_str || '').split(',').reject(&:empty?)
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
end
|
|
11
71
|
end
|
|
12
72
|
end
|
|
13
73
|
end
|
|
@@ -19,6 +19,9 @@ module Yard
|
|
|
19
19
|
nil
|
|
20
20
|
self
|
|
21
21
|
void
|
|
22
|
+
undefined
|
|
23
|
+
unspecified
|
|
24
|
+
unknown
|
|
22
25
|
].freeze
|
|
23
26
|
|
|
24
27
|
private_constant :ALLOWED_DEFAULTS
|
|
@@ -33,35 +36,47 @@ module Yard
|
|
|
33
36
|
extra_types = config_or_default('ExtraTypes')
|
|
34
37
|
allowed_types = ALLOWED_DEFAULTS + extra_types
|
|
35
38
|
|
|
36
|
-
#
|
|
37
|
-
|
|
39
|
+
# Collect per-tag violations to surface in the offense message.
|
|
40
|
+
# Each entry is "tagname param_name:Type1,Type2" (param_name omitted when nil).
|
|
41
|
+
tag_violations = all_typed_tags(object.docstring, checked_tags).filter_map do |tag|
|
|
42
|
+
bad = (tag.types || [])
|
|
43
|
+
.compact
|
|
44
|
+
.flat_map { |type| extract_type_names(type) }
|
|
45
|
+
.uniq
|
|
46
|
+
.reject { |type| allowed_types.include?(type) }
|
|
47
|
+
.reject { |type| type_defined?(type) }
|
|
48
|
+
.reject { |type| type.include?('#') }
|
|
49
|
+
next if bad.empty?
|
|
38
50
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
.flat_map(&:types)
|
|
43
|
-
.compact
|
|
44
|
-
.uniq
|
|
45
|
-
.map(&sanitize)
|
|
46
|
-
.reject { |type| allowed_types.include?(type) }
|
|
47
|
-
.reject { |type| type_defined?(type) }
|
|
48
|
-
.reject { |type| type.include?('#') }
|
|
51
|
+
label = tag.name ? "@#{tag.tag_name} #{tag.name}" : "@#{tag.tag_name}"
|
|
52
|
+
"#{label}:#{bad.join(',')}"
|
|
53
|
+
end
|
|
49
54
|
|
|
50
|
-
return if
|
|
55
|
+
return if tag_violations.empty?
|
|
51
56
|
|
|
52
57
|
collector.puts "#{object.file}:#{object.line}: #{object.title}"
|
|
58
|
+
collector.puts tag_violations.join('|')
|
|
53
59
|
end
|
|
54
60
|
|
|
55
61
|
private
|
|
56
62
|
|
|
63
|
+
# Extract individual type names from a compound type string.
|
|
64
|
+
# Instead of stripping all syntax characters and concatenating (which
|
|
65
|
+
# turns "Array<self>" into "Arrayself"), this splits on syntax boundaries
|
|
66
|
+
# and returns each type name individually.
|
|
67
|
+
# @param type_str [String] the raw type string (e.g., "Array<self>", "Hash{Symbol => String}")
|
|
68
|
+
# @return [Array<String>] individual type names (e.g., ["Array", "self"], ["Hash", "Symbol", "String"])
|
|
69
|
+
def extract_type_names(type_str)
|
|
70
|
+
type_str.split(/[=><,{}\s();]+/).reject(&:empty?)
|
|
71
|
+
end
|
|
72
|
+
|
|
57
73
|
# Check if a type is defined in Ruby runtime or YARD registry
|
|
58
74
|
# In in-process mode, parsed classes are in YARD registry but not loaded into Ruby
|
|
59
75
|
# @param type [String] type name to check
|
|
60
76
|
# @return [Boolean] true if type is defined (or at least recognized as a valid type)
|
|
61
77
|
def type_defined?(type)
|
|
62
|
-
# Symbol types
|
|
63
|
-
|
|
64
|
-
return true if type.start_with?(':')
|
|
78
|
+
# Symbol and string literal types (:foo, "bar") are valid hash key notations
|
|
79
|
+
return true if type.start_with?(':', '"', "'")
|
|
65
80
|
|
|
66
81
|
# Check Ruby runtime first
|
|
67
82
|
# The shell query uses: !(Kernel.const_defined?(type) rescue nil).nil?
|
|
@@ -22,8 +22,13 @@ module Yard
|
|
|
22
22
|
|
|
23
23
|
return unless invalid_types.include?(object_type)
|
|
24
24
|
|
|
25
|
+
# The `@param` tag is meaningful on Struct.new / Data.define constants because
|
|
26
|
+
# Solargraph uses those annotations to type the synthesized accessors.
|
|
27
|
+
effective_tags = struct_or_data_class?(object) ? tags_to_check - ['param'] : tags_to_check
|
|
28
|
+
return if effective_tags.empty?
|
|
29
|
+
|
|
25
30
|
object.docstring.tags.each do |tag|
|
|
26
|
-
next unless
|
|
31
|
+
next unless effective_tags.include?(tag.tag_name)
|
|
27
32
|
|
|
28
33
|
collector.puts "#{object.file}:#{object.line}: #{object.title}"
|
|
29
34
|
collector.puts "#{object_type}|#{tag.tag_name}"
|
|
@@ -42,6 +47,16 @@ module Yard
|
|
|
42
47
|
def invalid_object_types
|
|
43
48
|
config_or_default('InvalidObjectTypes')
|
|
44
49
|
end
|
|
50
|
+
|
|
51
|
+
# @param object [YARD::CodeObjects::Base] the code object to inspect
|
|
52
|
+
# @return [Boolean] true when the object is a class synthesised by Struct.new
|
|
53
|
+
# or Data.define, where @param documents the generated accessors
|
|
54
|
+
def struct_or_data_class?(object)
|
|
55
|
+
return false unless object.type == :class
|
|
56
|
+
|
|
57
|
+
sc_path = object.superclass&.path
|
|
58
|
+
sc_path == 'Struct' || sc_path == 'Data'
|
|
59
|
+
end
|
|
45
60
|
end
|
|
46
61
|
end
|
|
47
62
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module MissingYield
|
|
8
|
+
# Configuration for MissingYield validator
|
|
9
|
+
class Config < ::Yard::Lint::Validators::Config
|
|
10
|
+
self.id = :missing_yield
|
|
11
|
+
self.defaults = {
|
|
12
|
+
'Enabled' => false,
|
|
13
|
+
'Severity' => 'warning'
|
|
14
|
+
}.freeze
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module MissingYield
|
|
8
|
+
# Builds messages for missing yield tag offenses
|
|
9
|
+
class MessagesBuilder
|
|
10
|
+
class << self
|
|
11
|
+
# @param offense [Hash] offense data with :element key
|
|
12
|
+
# @return [String] formatted message
|
|
13
|
+
def call(offense)
|
|
14
|
+
"Method `#{offense[:element]}` yields to a block but is missing a @yield, @yieldparam, or @yieldreturn tag"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module MissingYield
|
|
8
|
+
# Parses validator output into structured offense hashes.
|
|
9
|
+
# @example Output format (one offense per line)
|
|
10
|
+
# # "path/to/file.rb:10: ClassName#method_name"
|
|
11
|
+
class Parser < ::Yard::Lint::Parsers::Base
|
|
12
|
+
# @return [Regexp] parses "file:line: element" collector output lines
|
|
13
|
+
LINE_REGEX = /^(.+):(\d+): (.+)$/.freeze
|
|
14
|
+
|
|
15
|
+
# @param validator_output [String] raw collector output
|
|
16
|
+
# @param config [Yard::Lint::Config, nil] configuration (unused)
|
|
17
|
+
# @return [Array<Hash>] parsed offense hashes
|
|
18
|
+
def call(validator_output, config: nil)
|
|
19
|
+
validator_output
|
|
20
|
+
.split("\n")
|
|
21
|
+
.map(&:strip)
|
|
22
|
+
.reject(&:empty?)
|
|
23
|
+
.filter_map do |line|
|
|
24
|
+
match = line.match(LINE_REGEX)
|
|
25
|
+
next unless match
|
|
26
|
+
|
|
27
|
+
{
|
|
28
|
+
location: match[1],
|
|
29
|
+
line: match[2].to_i,
|
|
30
|
+
element: match[3]
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module MissingYield
|
|
8
|
+
# Result object for missing yield tag validation
|
|
9
|
+
class Result < Results::Base
|
|
10
|
+
self.default_severity = 'warning'
|
|
11
|
+
self.offense_type = 'line'
|
|
12
|
+
self.offense_name = 'MissingYield'
|
|
13
|
+
|
|
14
|
+
# @param offense [Hash] offense data
|
|
15
|
+
# @return [String] formatted message
|
|
16
|
+
def build_message(offense)
|
|
17
|
+
MessagesBuilder.call(offense)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module MissingYield
|
|
8
|
+
# Detects methods that call `yield` but lack @yield/@yieldparam/@yieldreturn documentation
|
|
9
|
+
class Validator < Base
|
|
10
|
+
# Enable in-process execution with all visibility (private methods can yield too)
|
|
11
|
+
in_process visibility: :all
|
|
12
|
+
|
|
13
|
+
# Matches the `yield` keyword. The negative lookbehind `(?<![:.])`
|
|
14
|
+
# prevents matching method calls like `Fiber.yield` or `yielder.yield`
|
|
15
|
+
# and symbol literals like `:yield`. Word boundaries ensure `yield_self`
|
|
16
|
+
# and similar identifiers are not matched.
|
|
17
|
+
# Known limitation: `yield` inside regex literals (e.g. /yield/) is
|
|
18
|
+
# not stripped before scanning; it is rare enough to be acceptable.
|
|
19
|
+
YIELD_PATTERN = /(?<![:.])\byield\b/.freeze
|
|
20
|
+
|
|
21
|
+
# @return [Regexp] matches full-line Ruby comments
|
|
22
|
+
COMMENT_LINE_PATTERN = /\A\s*#/.freeze
|
|
23
|
+
|
|
24
|
+
# Matches simple single- and double-quoted string literals to strip before
|
|
25
|
+
# scanning for the yield keyword, reducing false positives from strings
|
|
26
|
+
# that contain the word "yield".
|
|
27
|
+
STRING_LITERAL_PATTERN = /("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/.freeze
|
|
28
|
+
|
|
29
|
+
# Matches user-written @yield, @yieldparam, or @yieldreturn tags in raw
|
|
30
|
+
# docstring text. YARD infers @yield automatically for bare `yield expr`
|
|
31
|
+
# statements and adds it to object.tags(), so we check docstring.all (the
|
|
32
|
+
# raw source text) rather than the parsed tag list to avoid counting
|
|
33
|
+
# YARD-inferred tags as explicit documentation.
|
|
34
|
+
YIELD_TAG_PATTERN = /^\s*@yield(?:param|return)?(?:\s|$)/.freeze
|
|
35
|
+
|
|
36
|
+
# Execute query for a single object during in-process execution.
|
|
37
|
+
# Flags methods that yield without a @yield, @yieldparam, or @yieldreturn tag.
|
|
38
|
+
# @param object [YARD::CodeObjects::Base] the code object to query
|
|
39
|
+
# @param collector [Executor::ResultCollector] collector for output
|
|
40
|
+
# @return [void]
|
|
41
|
+
def in_process_query(object, collector)
|
|
42
|
+
return unless object.type == :method
|
|
43
|
+
return if object.is_alias?
|
|
44
|
+
return unless object.is_explicit?
|
|
45
|
+
return unless object.source
|
|
46
|
+
return if yield_documented?(object)
|
|
47
|
+
return unless source_contains_yield?(object.source)
|
|
48
|
+
|
|
49
|
+
collector.puts "#{object.file}:#{object.line}: #{object.title}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
# @param object [YARD::CodeObjects::MethodObject] the method to check
|
|
55
|
+
# @return [Boolean] true if the user explicitly wrote a yield-related tag
|
|
56
|
+
def yield_documented?(object)
|
|
57
|
+
object.docstring.all.match?(YIELD_TAG_PATTERN)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @param source [String] raw method source code
|
|
61
|
+
# @return [Boolean] true if the source contains the `yield` keyword
|
|
62
|
+
def source_contains_yield?(source)
|
|
63
|
+
source.each_line.any? do |line|
|
|
64
|
+
next false if line.match?(COMMENT_LINE_PATTERN)
|
|
65
|
+
|
|
66
|
+
sanitized = line.gsub(STRING_LITERAL_PATTERN, '""')
|
|
67
|
+
sanitized = sanitized.sub(/#.*$/, '')
|
|
68
|
+
sanitized.match?(YIELD_PATTERN)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
# MissingYield validator
|
|
8
|
+
#
|
|
9
|
+
# Detects methods that call `yield` in their body but do not document
|
|
10
|
+
# the block with a `@yield`, `@yieldparam`, or `@yieldreturn` tag.
|
|
11
|
+
# Callers need to know a method yields so they can pass a block;
|
|
12
|
+
# undocumented yield is a silent API contract that only source readers discover.
|
|
13
|
+
#
|
|
14
|
+
# This validator is disabled by default (opt-in).
|
|
15
|
+
#
|
|
16
|
+
# @note Does not flag the inverse case (has `@yield` tag but no actual
|
|
17
|
+
# `yield` in source) - that is intentional for abstract/overridable methods.
|
|
18
|
+
#
|
|
19
|
+
# @note Known limitation: `yield` appearing inside heredoc bodies or
|
|
20
|
+
# multi-line string literals may produce false positives. These cases
|
|
21
|
+
# are rare enough in practice that the validator does not attempt to
|
|
22
|
+
# handle them.
|
|
23
|
+
#
|
|
24
|
+
# @example Bad - method yields but block is undocumented
|
|
25
|
+
# # Iterates over items
|
|
26
|
+
# # @param items [Array] the items
|
|
27
|
+
# def each(items)
|
|
28
|
+
# items.each { |item| yield item }
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# @example Good - block documented with @yield
|
|
32
|
+
# # Iterates over items
|
|
33
|
+
# # @param items [Array] the items
|
|
34
|
+
# # @yield [item] each item in the collection
|
|
35
|
+
# def each(items)
|
|
36
|
+
# items.each { |item| yield item }
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# @example Good - block documented with @yieldparam
|
|
40
|
+
# # @param items [Array] the items
|
|
41
|
+
# # @yieldparam item [Object] each item
|
|
42
|
+
# def each(items)
|
|
43
|
+
# items.each { |item| yield item }
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# ## Configuration
|
|
47
|
+
#
|
|
48
|
+
# Tags/MissingYield:
|
|
49
|
+
# Enabled: true
|
|
50
|
+
# Severity: warning
|
|
51
|
+
module MissingYield
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -23,9 +23,7 @@ module Yard
|
|
|
23
23
|
validated_tags = config.validator_config('Tags/NonAsciiType', 'ValidatedTags') ||
|
|
24
24
|
%w[param option return yieldreturn yieldparam]
|
|
25
25
|
|
|
26
|
-
object.docstring.
|
|
27
|
-
.select { |tag| validated_tags.include?(tag.tag_name) }
|
|
28
|
-
.each do |tag|
|
|
26
|
+
all_typed_tags(object.docstring, validated_tags).each do |tag|
|
|
29
27
|
next unless tag.types
|
|
30
28
|
|
|
31
29
|
tag.types.each do |type_str|
|
|
@@ -64,7 +64,8 @@ module Yard
|
|
|
64
64
|
# @param low_value_verbs [Array<String>] low-value verbs
|
|
65
65
|
# @param patterns [Hash] enabled pattern flags
|
|
66
66
|
# @return [String, nil] pattern type or nil
|
|
67
|
-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
67
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
68
|
+
# rubocop:disable Metrics/AbcSize, Metrics/ParameterLists
|
|
68
69
|
def detect_pattern(param_name, description, type_name, word_count, articles, generic_terms, connectors, low_value_verbs, patterns)
|
|
69
70
|
desc_parts = description.split
|
|
70
71
|
articles_re = /^(#{articles.join('|')})/i
|
|
@@ -136,7 +137,8 @@ module Yard
|
|
|
136
137
|
|
|
137
138
|
nil
|
|
138
139
|
end
|
|
139
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
140
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
141
|
+
# rubocop:enable Metrics/AbcSize, Metrics/ParameterLists
|
|
140
142
|
|
|
141
143
|
private
|
|
142
144
|
|
|
@@ -68,7 +68,8 @@ module Yard
|
|
|
68
68
|
# end
|
|
69
69
|
#
|
|
70
70
|
# @example Good - Long, meaningful descriptions with context
|
|
71
|
-
# # @param date [Date, nil] the date
|
|
71
|
+
# # @param date [Date, nil] the date describing when the event starts,
|
|
72
|
+
# # or nil if the event has not yet started
|
|
72
73
|
# # @param user [User] the user who initiated the request and will receive notifications
|
|
73
74
|
# # @param data [Hash] configuration options for the API endpoint including timeout and retry settings
|
|
74
75
|
# def configure(date, user, data)
|
|
@@ -31,9 +31,7 @@ module Yard
|
|
|
31
31
|
validated_tags = config.validator_config('Tags/TypeSyntax', 'ValidatedTags') ||
|
|
32
32
|
%w[param option return yieldreturn]
|
|
33
33
|
|
|
34
|
-
object.docstring.
|
|
35
|
-
.select { |tag| validated_tags.include?(tag.tag_name) }
|
|
36
|
-
.each do |tag|
|
|
34
|
+
all_typed_tags(object.docstring, validated_tags).each do |tag|
|
|
37
35
|
next unless tag.types
|
|
38
36
|
|
|
39
37
|
tag.types.each do |type_str|
|
|
@@ -8,8 +8,7 @@ module Yard
|
|
|
8
8
|
#
|
|
9
9
|
# Validates YARD type syntax using YARD's TypesExplainer::Parser. This
|
|
10
10
|
# validator ensures that type annotations can be properly parsed by YARD
|
|
11
|
-
# and follow YARD's type specification format. This validator is enabled
|
|
12
|
-
# by default.
|
|
11
|
+
# and follow YARD's type specification format. This validator is enabled by default.
|
|
13
12
|
#
|
|
14
13
|
# @example Bad - Invalid type syntax that YARD cannot parse
|
|
15
14
|
# # @param data [{String, Integer}] invalid hash syntax
|
|
@@ -6,13 +6,13 @@ module Yard
|
|
|
6
6
|
module Warnings
|
|
7
7
|
module DuplicatedParameterName
|
|
8
8
|
# Parser for DuplicatedParameterName warnings
|
|
9
|
-
class Parser < ::Yard::Lint::Parsers::
|
|
9
|
+
class Parser < ::Yard::Lint::Parsers::TwoLineBase
|
|
10
10
|
# Set of regexps for detecting warnings reported by YARD stats
|
|
11
11
|
self.regexps = {
|
|
12
12
|
general: /^\[warn\]: @param tag has duplicate parameter name/,
|
|
13
|
-
message: /\[warn\]: (.*)
|
|
14
|
-
location: /in file `(
|
|
15
|
-
line: /line (\d
|
|
13
|
+
message: /\[warn\]: (.*)$/,
|
|
14
|
+
location: /in file `(.*?)'\s*near/,
|
|
15
|
+
line: /near line (\d+)/
|
|
16
16
|
}.freeze
|
|
17
17
|
end
|
|
18
18
|
end
|
|
@@ -9,8 +9,7 @@ module Yard
|
|
|
9
9
|
#
|
|
10
10
|
# Detects duplicate `@param` tags for the same parameter name. If a parameter
|
|
11
11
|
# is documented multiple times, it's unclear which documentation is correct
|
|
12
|
-
# and can confuse both readers and YARD's parser. This validator is enabled
|
|
13
|
-
# by default.
|
|
12
|
+
# and can confuse both readers and YARD's parser. This validator is enabled by default.
|
|
14
13
|
#
|
|
15
14
|
# @example Bad - Duplicate @param tags
|
|
16
15
|
# # @param name [String] the name
|
|
@@ -9,8 +9,7 @@ module Yard
|
|
|
9
9
|
#
|
|
10
10
|
# Detects malformed YARD directive syntax. Directives have specific format
|
|
11
11
|
# requirements (like `@!attribute [r] name` for read-only attributes), and
|
|
12
|
-
# this validator ensures those requirements are met. This validator is
|
|
13
|
-
# enabled by default.
|
|
12
|
+
# this validator ensures those requirements are met. This validator is enabled by default.
|
|
14
13
|
#
|
|
15
14
|
# @example Bad - Malformed directive syntax
|
|
16
15
|
# # @!attribute name missing brackets
|
data/lib/yard/lint/version.rb
CHANGED
data/lib/yard/lint.rb
CHANGED
|
@@ -23,18 +23,28 @@ module Yard
|
|
|
23
23
|
# @param diff [Hash, nil] diff mode options
|
|
24
24
|
# - :mode [Symbol] one of :ref, :staged, :changed
|
|
25
25
|
# - :base_ref [String, nil] base ref for :ref mode (auto-detects main/master if nil)
|
|
26
|
+
# @param source [String, nil] in-memory source content; when given, the file is not
|
|
27
|
+
# read from disk — `path` must be a single .rb file path (used for config/exclusion
|
|
28
|
+
# resolution and offense location reporting only)
|
|
26
29
|
# @return [Yard::Lint::Result] result object with offenses
|
|
27
|
-
def run(path:, config: nil, config_file: nil, progress: nil, diff: nil)
|
|
30
|
+
def run(path:, config: nil, config_file: nil, progress: nil, diff: nil, source: nil)
|
|
31
|
+
if source
|
|
32
|
+
raise ArgumentError, '`source:` requires `path:` to be a single .rb file, not a directory or glob' \
|
|
33
|
+
if path.is_a?(Array) || path.to_s.include?('*') || File.directory?(path.to_s)
|
|
34
|
+
end
|
|
35
|
+
|
|
28
36
|
config ||= load_config(config_file)
|
|
29
37
|
|
|
30
|
-
# Determine files to lint based on diff mode or normal path expansion
|
|
31
|
-
files = if
|
|
38
|
+
# Determine files to lint based on source, diff mode, or normal path expansion
|
|
39
|
+
files = if source
|
|
40
|
+
[File.expand_path(path)]
|
|
41
|
+
elsif diff
|
|
32
42
|
get_diff_files(diff, path, config)
|
|
33
43
|
else
|
|
34
44
|
expand_path(path, config)
|
|
35
45
|
end
|
|
36
46
|
|
|
37
|
-
runner = Runner.new(files, config)
|
|
47
|
+
runner = Runner.new(files, config, source: source)
|
|
38
48
|
|
|
39
49
|
# Enable progress by default if output is a TTY
|
|
40
50
|
show_progress = progress.nil? ? $stdout.tty? : progress
|