yard-lint 1.5.2 → 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 +18 -2
- data/README.md +165 -7
- 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 +4 -2
- data/lib/yard/lint/templates/default_config.yml +40 -0
- data/lib/yard/lint/templates/strict_config.yml +39 -0
- data/lib/yard/lint/validators/base.rb +107 -1
- 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 +1 -1
- 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/validator.rb +1 -1
- data/lib/yard/lint/validators/tags/meaningless_tag/validator.rb +1 -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/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.rb +1 -2
- 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 +25 -1
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module OrphanedDocComment
|
|
8
|
+
# Scans source files for YARD-tagged comment blocks that YARD will silently drop.
|
|
9
|
+
# A comment block is orphaned when it has YARD tags but is immediately followed
|
|
10
|
+
# by a non-documentable statement or sits at end-of-file.
|
|
11
|
+
class Validator < Base
|
|
12
|
+
in_process visibility: :public
|
|
13
|
+
|
|
14
|
+
# Derive known tag names from YARD's own registry so we stay in sync with
|
|
15
|
+
# any tags added by YARD plugins. This avoids false positives from instance
|
|
16
|
+
# variable mentions in comments (e.g. `# @body.each` or `# @result is nil`).
|
|
17
|
+
YARD_TAG_PATTERN = begin
|
|
18
|
+
tags = ::YARD::Tags::Library.labels.keys.map(&:to_s).sort_by(&:length).reverse.join('|')
|
|
19
|
+
/\A\s*#\s*@(#{tags})\b/
|
|
20
|
+
end.freeze
|
|
21
|
+
# @return [Regexp] matches YARD directive lines (@!macro, @!method, etc.)
|
|
22
|
+
YARD_DIRECTIVE_PATTERN = /\A\s*#\s*@!/.freeze
|
|
23
|
+
# Matches method/class/module/attribute/alias definitions (with optional visibility prefix)
|
|
24
|
+
# and constant assignments (uppercase-leading identifier followed by =), both of which
|
|
25
|
+
# YARD tracks and attaches preceding doc comments to.
|
|
26
|
+
# Also matches define_method which YARD handles via a built-in dynamic handler.
|
|
27
|
+
DEFINITION_PATTERN = /
|
|
28
|
+
\A\s*(private\s+|protected\s+|public\s+)?
|
|
29
|
+
(def |class |module |attr_reader|attr_writer|attr_accessor|attr_internal|alias_method\b|alias\b|define_method\b)
|
|
30
|
+
|
|
|
31
|
+
\A\s*[A-Z][A-Za-z0-9_:]*\s*=
|
|
32
|
+
/x.freeze
|
|
33
|
+
|
|
34
|
+
# @param object [YARD::CodeObjects::Base] the code object to query
|
|
35
|
+
# @param collector [Executor::ResultCollector] collector for output
|
|
36
|
+
# @return [void]
|
|
37
|
+
def in_process_query(object, collector)
|
|
38
|
+
return unless object.file && File.exist?(object.file)
|
|
39
|
+
|
|
40
|
+
@scanned_files ||= {}
|
|
41
|
+
return if @scanned_files[object.file]
|
|
42
|
+
|
|
43
|
+
@scanned_files[object.file] = true
|
|
44
|
+
scan_file(object.file, collector)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
# @param file [String] absolute path to the source file to scan
|
|
50
|
+
# @param collector [Executor::ResultCollector] collector for output lines
|
|
51
|
+
# @return [void]
|
|
52
|
+
def scan_file(file, collector)
|
|
53
|
+
lines = File.readlines(file, chomp: true)
|
|
54
|
+
i = 0
|
|
55
|
+
|
|
56
|
+
while i < lines.length
|
|
57
|
+
if comment_line?(lines[i])
|
|
58
|
+
block_start = i
|
|
59
|
+
tags = []
|
|
60
|
+
|
|
61
|
+
has_directive = false
|
|
62
|
+
while i < lines.length && comment_line?(lines[i])
|
|
63
|
+
has_directive = true if directive_line?(lines[i])
|
|
64
|
+
tag = extract_yard_tag(lines[i])
|
|
65
|
+
tags << tag if tag
|
|
66
|
+
i += 1
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Skip blocks that contain @! directives - they are macro/method definitions,
|
|
70
|
+
# not orphaned doc comments.
|
|
71
|
+
next if has_directive || tags.empty?
|
|
72
|
+
|
|
73
|
+
# Skip trailing blank lines after the comment block
|
|
74
|
+
i += 1 while i < lines.length && lines[i].strip.empty?
|
|
75
|
+
|
|
76
|
+
if i >= lines.length || !definition_line?(lines[i])
|
|
77
|
+
collector.puts "#{file}:#{block_start + 1}: #{tags.uniq.join(',')}"
|
|
78
|
+
end
|
|
79
|
+
else
|
|
80
|
+
i += 1
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @param line [String] a raw source line
|
|
86
|
+
# @return [Boolean] true if the line is a Ruby comment (excluding magic comments)
|
|
87
|
+
def comment_line?(line)
|
|
88
|
+
stripped = line.strip
|
|
89
|
+
stripped.start_with?('#') && !magic_comment?(stripped)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @param stripped_line [String] a comment line with leading/trailing whitespace removed
|
|
93
|
+
# @return [Boolean] true if the line is a Ruby magic comment (frozen_string_literal, encoding, etc.)
|
|
94
|
+
def magic_comment?(stripped_line)
|
|
95
|
+
stripped_line.match?(/\A#\s*(frozen[_-]string[_-]literal|encoding|warn[_-]indent|shareable[_-]constant[_-]value)\s*:/i)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @param line [String] a raw source line
|
|
99
|
+
# @return [Boolean] true if the line contains a YARD directive (@!macro, @!method, etc.)
|
|
100
|
+
def directive_line?(line)
|
|
101
|
+
line.match?(YARD_DIRECTIVE_PATTERN)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# @param line [String] a raw source line
|
|
105
|
+
# @return [String, nil] the tag string (e.g. "@param") or nil if no known YARD tag found
|
|
106
|
+
def extract_yard_tag(line)
|
|
107
|
+
match = line.match(YARD_TAG_PATTERN)
|
|
108
|
+
"@#{match[1]}" if match
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# @param line [String] a raw source line
|
|
112
|
+
# @return [Boolean] true if the line starts a YARD-documentable definition
|
|
113
|
+
def definition_line?(line)
|
|
114
|
+
line.match?(DEFINITION_PATTERN)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
# OrphanedDocComment validator
|
|
8
|
+
#
|
|
9
|
+
# Detects YARD documentation comment blocks that contain tags (`@param`,
|
|
10
|
+
# `@return`, etc.) but are not attached to any documentable Ruby construct.
|
|
11
|
+
# YARD silently drops these comments - they never appear in the registry and
|
|
12
|
+
# the documentation is permanently lost.
|
|
13
|
+
#
|
|
14
|
+
# This happens when a YARD-tagged comment is immediately followed by a
|
|
15
|
+
# non-documentable statement (variable assignment, `require`, `include`, etc.)
|
|
16
|
+
# or sits at the end of a file.
|
|
17
|
+
#
|
|
18
|
+
# @note This validator is complementary to `Documentation/BlankLineBeforeDefinition`,
|
|
19
|
+
# which catches doc blocks separated from a `def` by blank lines.
|
|
20
|
+
# `OrphanedDocComment` catches doc blocks that lead to non-definition code entirely.
|
|
21
|
+
#
|
|
22
|
+
# @example Bad - comment before variable assignment
|
|
23
|
+
# # @param name [String] the name
|
|
24
|
+
# # @return [void]
|
|
25
|
+
# MY_CONSTANT = "value"
|
|
26
|
+
#
|
|
27
|
+
# @example Bad - comment before require
|
|
28
|
+
# # @param name [String] the name
|
|
29
|
+
# require "some_gem"
|
|
30
|
+
#
|
|
31
|
+
# @example Bad - comment at end of file
|
|
32
|
+
# # @param name [String] the name
|
|
33
|
+
# # @return [void]
|
|
34
|
+
# # (EOF)
|
|
35
|
+
#
|
|
36
|
+
# @example Good - comment before def
|
|
37
|
+
# # @param name [String] the name
|
|
38
|
+
# # @return [void]
|
|
39
|
+
# def process(name)
|
|
40
|
+
# end
|
|
41
|
+
#
|
|
42
|
+
# ## Configuration
|
|
43
|
+
#
|
|
44
|
+
# To disable this validator:
|
|
45
|
+
#
|
|
46
|
+
# Documentation/OrphanedDocComment:
|
|
47
|
+
# Enabled: false
|
|
48
|
+
module OrphanedDocComment
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
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 TextSubstitution
|
|
8
|
+
# Configuration for the TextSubstitution validator
|
|
9
|
+
class Config < ::Yard::Lint::Validators::Config
|
|
10
|
+
self.id = :text_substitution
|
|
11
|
+
self.defaults = {
|
|
12
|
+
'Enabled' => false,
|
|
13
|
+
'Severity' => 'warning',
|
|
14
|
+
'Substitutions' => {
|
|
15
|
+
"—" => '-', # em-dash (—)
|
|
16
|
+
"–" => '-' # en-dash (–)
|
|
17
|
+
}
|
|
18
|
+
}.freeze
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module TextSubstitution
|
|
8
|
+
# Builds human-readable messages for TextSubstitution violations.
|
|
9
|
+
class MessagesBuilder
|
|
10
|
+
class << self
|
|
11
|
+
# @param offense [Hash] offense details with :forbidden, :replacement, :line_text keys
|
|
12
|
+
# @return [String] formatted message
|
|
13
|
+
def call(offense)
|
|
14
|
+
forbidden = offense[:forbidden]
|
|
15
|
+
replacement = offense[:replacement]
|
|
16
|
+
line_text = offense[:line_text]
|
|
17
|
+
|
|
18
|
+
message = "Replace '#{forbidden}' with '#{replacement}' in documentation"
|
|
19
|
+
|
|
20
|
+
if line_text && !line_text.empty?
|
|
21
|
+
truncated = line_text.length > 60 ? "#{line_text[0, 57]}..." : line_text
|
|
22
|
+
message += ". Found: \"#{truncated}\""
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
message
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module TextSubstitution
|
|
8
|
+
# Parses TextSubstitution validator output into structured violation hashes.
|
|
9
|
+
#
|
|
10
|
+
# Wire format (four lines per violation):
|
|
11
|
+
# file.rb:LINE: ObjectTitle
|
|
12
|
+
# forbidden
|
|
13
|
+
# replacement
|
|
14
|
+
# line_offset|line_text
|
|
15
|
+
#
|
|
16
|
+
# forbidden and replacement occupy their own lines so they may contain
|
|
17
|
+
# any character, including the pipe delimiter used in the last field.
|
|
18
|
+
class Parser < ::Yard::Lint::Parsers::Base
|
|
19
|
+
# @param yard_output [String] raw collector output from the validator
|
|
20
|
+
# @option _kwargs [Object] :unused accepts no options (reserved for future use)
|
|
21
|
+
# @return [Array<Hash>] array with violation details
|
|
22
|
+
def call(yard_output, **_kwargs)
|
|
23
|
+
return [] if yard_output.nil? || yard_output.strip.empty?
|
|
24
|
+
|
|
25
|
+
# Do not strip lines — forbidden/replacement may have significant whitespace
|
|
26
|
+
lines = yard_output.split("\n")
|
|
27
|
+
violations = []
|
|
28
|
+
|
|
29
|
+
lines.each_slice(4) do |location_line, forbidden_line, replacement_line, details_line|
|
|
30
|
+
next unless location_line && forbidden_line && replacement_line && details_line
|
|
31
|
+
|
|
32
|
+
location_match = location_line.strip.match(/^(.+):(\d+): (.+)$/)
|
|
33
|
+
next unless location_match
|
|
34
|
+
|
|
35
|
+
# line_offset is always numeric; split on first pipe only so line_text may contain pipes
|
|
36
|
+
line_offset_str, line_text = details_line.split('|', 2)
|
|
37
|
+
next unless line_offset_str
|
|
38
|
+
|
|
39
|
+
violations << {
|
|
40
|
+
location: location_match[1],
|
|
41
|
+
line: location_match[2].to_i,
|
|
42
|
+
object_name: location_match[3],
|
|
43
|
+
forbidden: forbidden_line,
|
|
44
|
+
replacement: replacement_line,
|
|
45
|
+
line_offset: line_offset_str.to_i,
|
|
46
|
+
line_text: line_text || ''
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
violations
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
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 TextSubstitution
|
|
8
|
+
# Result wrapper for TextSubstitution violations
|
|
9
|
+
class Result < Results::Base
|
|
10
|
+
self.default_severity = 'warning'
|
|
11
|
+
self.offense_type = 'line'
|
|
12
|
+
self.offense_name = 'TextSubstitution'
|
|
13
|
+
|
|
14
|
+
# @param offense [Hash]
|
|
15
|
+
# @return [String]
|
|
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,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module TextSubstitution
|
|
8
|
+
# Validates that documentation does not contain forbidden strings.
|
|
9
|
+
# Reports every matching substitution rule on every line independently.
|
|
10
|
+
class Validator < Base
|
|
11
|
+
in_process visibility: :public
|
|
12
|
+
|
|
13
|
+
# @param object [YARD::CodeObjects::Base]
|
|
14
|
+
# @param collector [Executor::ResultCollector]
|
|
15
|
+
# @return [void]
|
|
16
|
+
def in_process_query(object, collector)
|
|
17
|
+
docstring_text = object.docstring.to_s
|
|
18
|
+
return if docstring_text.empty?
|
|
19
|
+
|
|
20
|
+
substitutions = config_or_default('Substitutions')
|
|
21
|
+
return if substitutions.nil? || substitutions.empty?
|
|
22
|
+
|
|
23
|
+
violations = find_violations(docstring_text, substitutions)
|
|
24
|
+
return if violations.empty?
|
|
25
|
+
|
|
26
|
+
violations.each do |violation|
|
|
27
|
+
collector.puts "#{object.file}:#{object.line}: #{object.title}"
|
|
28
|
+
collector.puts violation[:forbidden]
|
|
29
|
+
collector.puts violation[:replacement]
|
|
30
|
+
collector.puts "#{violation[:line_offset]}|#{violation[:line_text]}"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
# Scans each line of a docstring for forbidden strings, skipping fenced code blocks.
|
|
37
|
+
# @param docstring_text [String] full docstring text to scan
|
|
38
|
+
# @param substitutions [Hash{String => String}] map of forbidden string to replacement
|
|
39
|
+
# @return [Array<Hash>] list of violations with forbidden, replacement, line_offset, line_text
|
|
40
|
+
def find_violations(docstring_text, substitutions)
|
|
41
|
+
violations = []
|
|
42
|
+
in_code_block = false
|
|
43
|
+
|
|
44
|
+
docstring_text.lines.each_with_index do |line, line_offset|
|
|
45
|
+
if line.strip.start_with?('```')
|
|
46
|
+
in_code_block = !in_code_block
|
|
47
|
+
next
|
|
48
|
+
end
|
|
49
|
+
next if in_code_block
|
|
50
|
+
|
|
51
|
+
substitutions.each do |forbidden, replacement|
|
|
52
|
+
next if forbidden.nil? || forbidden.empty?
|
|
53
|
+
next if replacement.nil? || replacement.empty?
|
|
54
|
+
next unless line.include?(forbidden)
|
|
55
|
+
|
|
56
|
+
violations << {
|
|
57
|
+
forbidden: forbidden,
|
|
58
|
+
replacement: replacement,
|
|
59
|
+
line_offset: line_offset,
|
|
60
|
+
line_text: line.strip
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
violations
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
# TextSubstitution validator
|
|
8
|
+
#
|
|
9
|
+
# Detects forbidden characters or strings in YARD documentation comments
|
|
10
|
+
# and suggests replacements. The primary use case is detecting AI-generated
|
|
11
|
+
# em-dashes (—, U+2014) and en-dashes (–, U+2013) where a plain hyphen (-)
|
|
12
|
+
# is preferred, but any string-to-string substitution rule can be configured.
|
|
13
|
+
#
|
|
14
|
+
# All substitution rules are checked on every line — multiple violations can
|
|
15
|
+
# be reported for the same line when more than one forbidden string appears.
|
|
16
|
+
# Fenced code blocks (``` ... ```) are skipped.
|
|
17
|
+
#
|
|
18
|
+
# Disabled by default — enable it and configure Substitutions to taste.
|
|
19
|
+
#
|
|
20
|
+
# @example Bad - em-dash used in documentation
|
|
21
|
+
# # Connects the start — and end of the range.
|
|
22
|
+
# def connect(start, finish)
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# @example Good - plain hyphen
|
|
26
|
+
# # Connects the start - and end of the range.
|
|
27
|
+
# def connect(start, finish)
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# ## Configuration
|
|
31
|
+
#
|
|
32
|
+
# Enable with built-in defaults (em-dash and en-dash):
|
|
33
|
+
#
|
|
34
|
+
# Documentation/TextSubstitution:
|
|
35
|
+
# Enabled: true
|
|
36
|
+
#
|
|
37
|
+
# Enable with explicit substitution rules:
|
|
38
|
+
#
|
|
39
|
+
# Documentation/TextSubstitution:
|
|
40
|
+
# Enabled: true
|
|
41
|
+
# Substitutions:
|
|
42
|
+
# "—": "-" # em-dash (U+2014)
|
|
43
|
+
# "–": "-" # en-dash (U+2013)
|
|
44
|
+
# "…": "..." # ellipsis (U+2026)
|
|
45
|
+
#
|
|
46
|
+
# To disable:
|
|
47
|
+
#
|
|
48
|
+
# Documentation/TextSubstitution:
|
|
49
|
+
# Enabled: false
|
|
50
|
+
module TextSubstitution
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -21,7 +21,9 @@ module Yard
|
|
|
21
21
|
# Skip aliases and implicit methods
|
|
22
22
|
return if object.is_alias?
|
|
23
23
|
return unless object.is_explicit?
|
|
24
|
-
|
|
24
|
+
return if parent_class_allowed?(object)
|
|
25
|
+
return if method_allowed?(object)
|
|
26
|
+
# Skip attribute methods (@!attribute directive) - their setter parameter
|
|
25
27
|
# doesn't need explicit @param documentation, matching attr_accessor behavior
|
|
26
28
|
return if object.is_attribute?
|
|
27
29
|
|
|
@@ -8,8 +8,7 @@ module Yard
|
|
|
8
8
|
#
|
|
9
9
|
# Ensures that all method parameters are documented with `@param` tags.
|
|
10
10
|
# This validator checks that every parameter in a method signature has
|
|
11
|
-
# a corresponding `@param` documentation tag. This validator is enabled
|
|
12
|
-
# by default.
|
|
11
|
+
# a corresponding `@param` documentation tag. This validator is enabled by default.
|
|
13
12
|
#
|
|
14
13
|
# @example Bad - Missing @param tags
|
|
15
14
|
# # Does something with data
|
|
@@ -11,7 +11,8 @@ module Yard
|
|
|
11
11
|
self.defaults = {
|
|
12
12
|
'Enabled' => true,
|
|
13
13
|
'Severity' => 'warning',
|
|
14
|
-
'ExcludedMethods' => ['initialize/0']
|
|
14
|
+
'ExcludedMethods' => ['initialize/0'],
|
|
15
|
+
'AllowedParentClasses' => []
|
|
15
16
|
}.freeze
|
|
16
17
|
self.combines_with = ['Documentation/UndocumentedBooleanMethods'].freeze
|
|
17
18
|
end
|
|
@@ -16,6 +16,7 @@ module Yard
|
|
|
16
16
|
# @param collector [Executor::ResultCollector] collector for output
|
|
17
17
|
# @return [void]
|
|
18
18
|
def in_process_query(object, collector)
|
|
19
|
+
return if parent_class_allowed?(object)
|
|
19
20
|
# Check if docstring is empty
|
|
20
21
|
return unless object.docstring.all.empty?
|
|
21
22
|
|
|
@@ -8,8 +8,7 @@ module Yard
|
|
|
8
8
|
#
|
|
9
9
|
# Checks that options hashes have detailed documentation about their keys.
|
|
10
10
|
# When a method accepts an options hash parameter, the individual option
|
|
11
|
-
# keys should be documented using `@option` tags. This validator is enabled
|
|
12
|
-
# by default.
|
|
11
|
+
# keys should be documented using `@option` tags. This validator is enabled by default.
|
|
13
12
|
#
|
|
14
13
|
# @example Bad - Options parameter without @option tags
|
|
15
14
|
# # Configures the service
|
|
@@ -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
|
|
@@ -29,7 +29,7 @@ module Yard
|
|
|
29
29
|
# @!attribute directives, Struct.new and Data.define generated readers/writers).
|
|
30
30
|
# These are auto-generated accessors where per-method @api tags either flow
|
|
31
31
|
# through the declaration's docstring automatically or cannot be attached at
|
|
32
|
-
# all
|
|
32
|
+
# all - for example, YARD's Data.define handler creates getters with only a
|
|
33
33
|
# @return tag (hard-replacing their docstring and stripping any inherited @api
|
|
34
34
|
# from the enclosing class), and @!attribute directives written above a
|
|
35
35
|
# Data.define constant attach to the enclosing namespace, not the Data class
|
|
@@ -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
|
|
@@ -67,7 +67,7 @@ module Yard
|
|
|
67
67
|
# @param type_str [String] the raw type string (e.g., "Array<self>", "Hash{Symbol => String}")
|
|
68
68
|
# @return [Array<String>] individual type names (e.g., ["Array", "self"], ["Hash", "Symbol", "String"])
|
|
69
69
|
def extract_type_names(type_str)
|
|
70
|
-
type_str.split(/[=><,{}\s()]+/).reject(&:empty?)
|
|
70
|
+
type_str.split(/[=><,{}\s();]+/).reject(&:empty?)
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
# Check if a type is defined in Ruby runtime or YARD registry
|
|
@@ -22,7 +22,7 @@ module Yard
|
|
|
22
22
|
|
|
23
23
|
return unless invalid_types.include?(object_type)
|
|
24
24
|
|
|
25
|
-
#
|
|
25
|
+
# The `@param` tag is meaningful on Struct.new / Data.define constants because
|
|
26
26
|
# Solargraph uses those annotations to type the synthesized accessors.
|
|
27
27
|
effective_tags = struct_or_data_class?(object) ? tags_to_check - ['param'] : tags_to_check
|
|
28
28
|
return if effective_tags.empty?
|
|
@@ -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
|