yard-lint 1.2.3 → 1.3.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 +150 -1
- data/README.md +98 -4
- data/Rakefile +20 -0
- data/bin/yard-lint +71 -38
- data/lib/yard/lint/config.rb +5 -0
- data/lib/yard/lint/config_updater.rb +222 -0
- data/lib/yard/lint/errors.rb +6 -0
- data/lib/yard/lint/executor/in_process_registry.rb +130 -0
- data/lib/yard/lint/executor/query_executor.rb +109 -0
- data/lib/yard/lint/executor/result_collector.rb +55 -0
- data/lib/yard/lint/executor/warning_dispatcher.rb +79 -0
- data/lib/yard/lint/ext/irb_notifier_shim.rb +19 -6
- data/lib/yard/lint/results/base.rb +2 -1
- data/lib/yard/lint/runner.rb +50 -38
- data/lib/yard/lint/templates/default_config.yml +105 -0
- data/lib/yard/lint/templates/strict_config.yml +105 -0
- data/lib/yard/lint/validators/base.rb +52 -118
- data/lib/yard/lint/validators/documentation/blank_line_before_definition/config.rb +25 -0
- data/lib/yard/lint/validators/documentation/blank_line_before_definition/messages_builder.rb +39 -0
- data/lib/yard/lint/validators/documentation/blank_line_before_definition/parser.rb +59 -0
- data/lib/yard/lint/validators/documentation/blank_line_before_definition/result.rb +61 -0
- data/lib/yard/lint/validators/documentation/blank_line_before_definition/validator.rb +94 -0
- data/lib/yard/lint/validators/documentation/blank_line_before_definition.rb +63 -0
- data/lib/yard/lint/validators/documentation/empty_comment_line/config.rb +24 -0
- data/lib/yard/lint/validators/documentation/empty_comment_line/messages_builder.rb +34 -0
- data/lib/yard/lint/validators/documentation/empty_comment_line/parser.rb +60 -0
- data/lib/yard/lint/validators/documentation/empty_comment_line/result.rb +25 -0
- data/lib/yard/lint/validators/documentation/empty_comment_line/validator.rb +109 -0
- data/lib/yard/lint/validators/documentation/empty_comment_line.rb +58 -0
- data/lib/yard/lint/validators/documentation/markdown_syntax/validator.rb +36 -21
- data/lib/yard/lint/validators/documentation/markdown_syntax.rb +0 -1
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/validator.rb +19 -29
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods.rb +0 -1
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/validator.rb +18 -34
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments.rb +0 -1
- data/lib/yard/lint/validators/documentation/undocumented_objects/validator.rb +17 -25
- data/lib/yard/lint/validators/documentation/undocumented_objects.rb +4 -5
- data/lib/yard/lint/validators/documentation/undocumented_options/validator.rb +30 -21
- data/lib/yard/lint/validators/documentation/undocumented_options.rb +0 -1
- data/lib/yard/lint/validators/semantic/abstract_methods/result.rb +2 -2
- data/lib/yard/lint/validators/semantic/abstract_methods/validator.rb +31 -43
- data/lib/yard/lint/validators/semantic/abstract_methods.rb +0 -1
- data/lib/yard/lint/validators/tags/api_tags/validator.rb +24 -39
- data/lib/yard/lint/validators/tags/api_tags.rb +0 -1
- data/lib/yard/lint/validators/tags/collection_type/validator.rb +37 -66
- data/lib/yard/lint/validators/tags/collection_type.rb +0 -1
- data/lib/yard/lint/validators/tags/example_syntax/validator.rb +51 -64
- data/lib/yard/lint/validators/tags/example_syntax.rb +0 -1
- data/lib/yard/lint/validators/tags/informal_notation/config.rb +40 -0
- data/lib/yard/lint/validators/tags/informal_notation/messages_builder.rb +35 -0
- data/lib/yard/lint/validators/tags/informal_notation/parser.rb +55 -0
- data/lib/yard/lint/validators/tags/informal_notation/result.rb +26 -0
- data/lib/yard/lint/validators/tags/informal_notation/validator.rb +133 -0
- data/lib/yard/lint/validators/tags/informal_notation.rb +45 -0
- data/lib/yard/lint/validators/tags/invalid_types/validator.rb +57 -70
- data/lib/yard/lint/validators/tags/invalid_types.rb +0 -1
- data/lib/yard/lint/validators/tags/meaningless_tag/validator.rb +22 -54
- data/lib/yard/lint/validators/tags/meaningless_tag.rb +0 -1
- data/lib/yard/lint/validators/tags/non_ascii_type/config.rb +21 -0
- data/lib/yard/lint/validators/tags/non_ascii_type/messages_builder.rb +29 -0
- data/lib/yard/lint/validators/tags/non_ascii_type/parser.rb +59 -0
- data/lib/yard/lint/validators/tags/non_ascii_type/result.rb +25 -0
- data/lib/yard/lint/validators/tags/non_ascii_type/validator.rb +50 -0
- data/lib/yard/lint/validators/tags/non_ascii_type.rb +39 -0
- data/lib/yard/lint/validators/tags/option_tags/result.rb +2 -2
- data/lib/yard/lint/validators/tags/option_tags/validator.rb +25 -40
- data/lib/yard/lint/validators/tags/option_tags.rb +0 -1
- data/lib/yard/lint/validators/tags/order/validator.rb +28 -55
- data/lib/yard/lint/validators/tags/order.rb +0 -1
- data/lib/yard/lint/validators/tags/redundant_param_description/config.rb +15 -1
- data/lib/yard/lint/validators/tags/redundant_param_description/messages_builder.rb +5 -0
- data/lib/yard/lint/validators/tags/redundant_param_description/validator.rb +134 -100
- data/lib/yard/lint/validators/tags/redundant_param_description.rb +0 -1
- data/lib/yard/lint/validators/tags/tag_group_separator/config.rb +29 -0
- data/lib/yard/lint/validators/tags/tag_group_separator/messages_builder.rb +49 -0
- data/lib/yard/lint/validators/tags/tag_group_separator/parser.rb +67 -0
- data/lib/yard/lint/validators/tags/tag_group_separator/result.rb +28 -0
- data/lib/yard/lint/validators/tags/tag_group_separator/validator.rb +117 -0
- data/lib/yard/lint/validators/tags/tag_group_separator.rb +49 -0
- data/lib/yard/lint/validators/tags/tag_type_position/validator.rb +53 -84
- data/lib/yard/lint/validators/tags/tag_type_position.rb +0 -1
- data/lib/yard/lint/validators/tags/type_syntax/parser.rb +7 -2
- data/lib/yard/lint/validators/tags/type_syntax/validator.rb +29 -59
- data/lib/yard/lint/validators/tags/type_syntax.rb +0 -1
- data/lib/yard/lint/validators/warnings/duplicated_parameter_name/validator.rb +1 -18
- data/lib/yard/lint/validators/warnings/invalid_directive_format/validator.rb +1 -18
- data/lib/yard/lint/validators/warnings/invalid_tag_format/validator.rb +1 -18
- data/lib/yard/lint/validators/warnings/unknown_directive/validator.rb +1 -18
- data/lib/yard/lint/validators/warnings/unknown_parameter_name/messages_builder.rb +243 -0
- data/lib/yard/lint/validators/warnings/unknown_parameter_name/result.rb +4 -3
- data/lib/yard/lint/validators/warnings/unknown_parameter_name/validator.rb +1 -18
- data/lib/yard/lint/validators/warnings/unknown_tag/messages_builder.rb +144 -0
- data/lib/yard/lint/validators/warnings/unknown_tag/result.rb +4 -3
- data/lib/yard/lint/validators/warnings/unknown_tag/validator.rb +1 -18
- data/lib/yard/lint/validators/warnings/unknown_tag.rb +10 -0
- data/lib/yard/lint/version.rb +1 -1
- data/lib/yard/lint.rb +81 -13
- data/lib/yard-lint.rb +1 -1
- data/renovate.json +1 -8
- metadata +38 -2
- data/lib/yard/lint/command_cache.rb +0 -93
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module BlankLineBeforeDefinition
|
|
8
|
+
# Parses YARD output for blank line before definition violations
|
|
9
|
+
class Parser < Parsers::Base
|
|
10
|
+
# Parse YARD output into structured violations
|
|
11
|
+
# @param output [String] raw YARD output
|
|
12
|
+
# @return [Array<Hash>] array of violation hashes
|
|
13
|
+
def call(output)
|
|
14
|
+
return [] if output.nil? || output.empty?
|
|
15
|
+
|
|
16
|
+
violations = []
|
|
17
|
+
lines = output.lines.map(&:chomp)
|
|
18
|
+
|
|
19
|
+
i = 0
|
|
20
|
+
while i < lines.size
|
|
21
|
+
line = lines[i]
|
|
22
|
+
|
|
23
|
+
# Match location line: "file:line: object_name"
|
|
24
|
+
if (location_match = line.match(/^(.+):(\d+): (.+)$/))
|
|
25
|
+
file_path = location_match[1]
|
|
26
|
+
object_line = location_match[2].to_i
|
|
27
|
+
object_name = location_match[3]
|
|
28
|
+
|
|
29
|
+
# Next line contains violation details
|
|
30
|
+
i += 1
|
|
31
|
+
next unless i < lines.size
|
|
32
|
+
|
|
33
|
+
# Parse violation: "single:1" or "orphaned:3"
|
|
34
|
+
detail_parts = lines[i].split(':', 2)
|
|
35
|
+
next unless detail_parts.size == 2
|
|
36
|
+
|
|
37
|
+
violation_type = detail_parts[0]
|
|
38
|
+
blank_count = detail_parts[1].to_i
|
|
39
|
+
|
|
40
|
+
violations << {
|
|
41
|
+
location: file_path,
|
|
42
|
+
line: object_line,
|
|
43
|
+
object_name: object_name,
|
|
44
|
+
violation_type: violation_type,
|
|
45
|
+
blank_count: blank_count
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
i += 1
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
violations
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module BlankLineBeforeDefinition
|
|
8
|
+
# Result builder for blank line before definition violations
|
|
9
|
+
class Result < Results::Base
|
|
10
|
+
self.default_severity = 'convention'
|
|
11
|
+
self.offense_type = 'line'
|
|
12
|
+
self.offense_name = 'BlankLineBeforeDefinition'
|
|
13
|
+
|
|
14
|
+
# Build human-readable message for blank line violation
|
|
15
|
+
# @param offense [Hash] offense data with violation details
|
|
16
|
+
# @return [String] formatted message
|
|
17
|
+
def build_message(offense)
|
|
18
|
+
MessagesBuilder.call(offense)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
# Override to handle per-violation severity based on violation type
|
|
24
|
+
# @return [Array<Hash>] array of offense hashes
|
|
25
|
+
def build_offenses
|
|
26
|
+
@parsed_data.map do |offense_data|
|
|
27
|
+
severity = severity_for_violation(offense_data[:violation_type])
|
|
28
|
+
|
|
29
|
+
offense_data.merge(
|
|
30
|
+
severity: severity,
|
|
31
|
+
type: self.class.offense_type,
|
|
32
|
+
name: computed_offense_name,
|
|
33
|
+
message: build_message(offense_data),
|
|
34
|
+
location: offense_data[:location] || offense_data[:file],
|
|
35
|
+
location_line: offense_data[:line] || offense_data[:location_line] || 0
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Get severity for a specific violation type
|
|
41
|
+
# @param violation_type [String] 'single' or 'orphaned'
|
|
42
|
+
# @return [String] severity level
|
|
43
|
+
def severity_for_violation(violation_type)
|
|
44
|
+
default = self.class.default_severity
|
|
45
|
+
return default unless config
|
|
46
|
+
|
|
47
|
+
case violation_type
|
|
48
|
+
when 'orphaned'
|
|
49
|
+
config.validator_config(validator_name, 'OrphanedSeverity') ||
|
|
50
|
+
config.validator_severity(validator_name) ||
|
|
51
|
+
default
|
|
52
|
+
else
|
|
53
|
+
config.validator_severity(validator_name) || default
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module BlankLineBeforeDefinition
|
|
8
|
+
# Validates blank lines between documentation and definitions
|
|
9
|
+
class Validator < Validators::Base
|
|
10
|
+
# Enable in-process execution for this validator
|
|
11
|
+
in_process visibility: :public
|
|
12
|
+
|
|
13
|
+
# Execute query for a single object during in-process execution.
|
|
14
|
+
# Checks for blank lines between documentation blocks and definitions.
|
|
15
|
+
# @param object [YARD::CodeObjects::Base] the code object to query
|
|
16
|
+
# @param collector [Executor::ResultCollector] collector for output
|
|
17
|
+
# @return [void]
|
|
18
|
+
def in_process_query(object, collector)
|
|
19
|
+
return unless object.file && File.exist?(object.file) && object.line.to_i > 1
|
|
20
|
+
|
|
21
|
+
source_lines = File.readlines(object.file)
|
|
22
|
+
definition_line = object.line - 1
|
|
23
|
+
|
|
24
|
+
blank_count, has_doc_block = analyze_spacing(source_lines, definition_line)
|
|
25
|
+
|
|
26
|
+
return if blank_count.zero? || !has_doc_block
|
|
27
|
+
|
|
28
|
+
violation_type = blank_count >= 2 ? 'orphaned' : 'single'
|
|
29
|
+
|
|
30
|
+
return unless pattern_enabled?(violation_type)
|
|
31
|
+
|
|
32
|
+
collector.puts "#{object.file}:#{object.line}: #{object.title}"
|
|
33
|
+
collector.puts "#{violation_type}:#{blank_count}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
# Analyze spacing between documentation and definition
|
|
39
|
+
# @param source_lines [Array<String>] lines of source file
|
|
40
|
+
# @param definition_line [Integer] 0-indexed line of definition
|
|
41
|
+
# @return [Array<Integer, Boolean>] blank count and whether doc block exists
|
|
42
|
+
def analyze_spacing(source_lines, definition_line)
|
|
43
|
+
blank_count = 0
|
|
44
|
+
has_doc_block = false
|
|
45
|
+
|
|
46
|
+
(definition_line - 1).downto(0) do |i|
|
|
47
|
+
line = source_lines[i].to_s.rstrip
|
|
48
|
+
stripped = line.strip
|
|
49
|
+
|
|
50
|
+
if stripped.empty?
|
|
51
|
+
blank_count += 1
|
|
52
|
+
elsif stripped.start_with?('#')
|
|
53
|
+
# Skip Ruby magic comments - they're not YARD documentation
|
|
54
|
+
next if magic_comment?(stripped)
|
|
55
|
+
|
|
56
|
+
has_doc_block = true
|
|
57
|
+
break
|
|
58
|
+
else
|
|
59
|
+
# Non-comment, non-blank line - no documentation above
|
|
60
|
+
break
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
[blank_count, has_doc_block]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Check if a comment line is a Ruby magic comment
|
|
68
|
+
# @param line [String] stripped comment line
|
|
69
|
+
# @return [Boolean] true if line is a magic comment
|
|
70
|
+
def magic_comment?(line)
|
|
71
|
+
# Ruby magic comments: frozen_string_literal, encoding, warn_indent, shareable_constant_value
|
|
72
|
+
line.match?(/^#\s*(frozen[_-]string[_-]literal|encoding|warn[_-]indent|shareable[_-]constant[_-]value)\s*:/i)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Check if the given pattern is enabled in configuration
|
|
76
|
+
# @param violation_type [String] 'single' or 'orphaned'
|
|
77
|
+
# @return [Boolean] whether the pattern is enabled
|
|
78
|
+
def pattern_enabled?(violation_type)
|
|
79
|
+
patterns = config_or_default('EnabledPatterns')
|
|
80
|
+
case violation_type
|
|
81
|
+
when 'single'
|
|
82
|
+
patterns['SingleBlankLine']
|
|
83
|
+
when 'orphaned'
|
|
84
|
+
patterns['OrphanedDocs']
|
|
85
|
+
else
|
|
86
|
+
true
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
# BlankLineBeforeDefinition validator
|
|
8
|
+
#
|
|
9
|
+
# Detects blank lines between YARD documentation and method/class/module definitions.
|
|
10
|
+
# YARD requires documentation to be immediately adjacent to the definition it documents.
|
|
11
|
+
#
|
|
12
|
+
# @example Bad - Single blank line (convention violation)
|
|
13
|
+
# # Description of the method
|
|
14
|
+
# # @param value [String] the value
|
|
15
|
+
#
|
|
16
|
+
# def process(value)
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# @example Bad - Multiple blank lines (orphaned documentation)
|
|
20
|
+
# # Description of the method
|
|
21
|
+
# # @param value [String] the value
|
|
22
|
+
#
|
|
23
|
+
#
|
|
24
|
+
# def process(value)
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# @example Good - No blank lines
|
|
28
|
+
# # Description of the method
|
|
29
|
+
# # @param value [String] the value
|
|
30
|
+
# def process(value)
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# ## Severity Levels
|
|
34
|
+
#
|
|
35
|
+
# - **1 blank line**: Convention violation - YARD associates the doc but this
|
|
36
|
+
# violates formatting conventions
|
|
37
|
+
# - **2+ blank lines**: Orphaned documentation - YARD ignores the documentation entirely
|
|
38
|
+
#
|
|
39
|
+
# ## Configuration
|
|
40
|
+
#
|
|
41
|
+
# To customize severity levels:
|
|
42
|
+
#
|
|
43
|
+
# Documentation/BlankLineBeforeDefinition:
|
|
44
|
+
# Severity: warning # For single blank line
|
|
45
|
+
# OrphanedSeverity: error # For 2+ blank lines
|
|
46
|
+
#
|
|
47
|
+
# To check only single blank lines:
|
|
48
|
+
#
|
|
49
|
+
# Documentation/BlankLineBeforeDefinition:
|
|
50
|
+
# EnabledPatterns:
|
|
51
|
+
# SingleBlankLine: true
|
|
52
|
+
# OrphanedDocs: false
|
|
53
|
+
#
|
|
54
|
+
# To disable this validator:
|
|
55
|
+
#
|
|
56
|
+
# Documentation/BlankLineBeforeDefinition:
|
|
57
|
+
# Enabled: false
|
|
58
|
+
module BlankLineBeforeDefinition
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
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 EmptyCommentLine
|
|
8
|
+
# Configuration for EmptyCommentLine validator
|
|
9
|
+
class Config < ::Yard::Lint::Validators::Config
|
|
10
|
+
self.id = :empty_comment_line
|
|
11
|
+
self.defaults = {
|
|
12
|
+
'Enabled' => true,
|
|
13
|
+
'Severity' => 'convention',
|
|
14
|
+
'EnabledPatterns' => {
|
|
15
|
+
'Leading' => true,
|
|
16
|
+
'Trailing' => true
|
|
17
|
+
}
|
|
18
|
+
}.freeze
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module EmptyCommentLine
|
|
8
|
+
# Builds human-readable messages for empty comment line violations
|
|
9
|
+
class MessagesBuilder
|
|
10
|
+
# Maps violation types to human-readable descriptions
|
|
11
|
+
ERROR_DESCRIPTIONS = {
|
|
12
|
+
'leading' => 'Empty leading comment line in documentation',
|
|
13
|
+
'trailing' => 'Empty trailing comment line in documentation'
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
# Formats a violation message
|
|
18
|
+
# @param offense [Hash] the offense details
|
|
19
|
+
# @return [String] formatted message
|
|
20
|
+
def call(offense)
|
|
21
|
+
type = offense[:violation_type]
|
|
22
|
+
object_name = offense[:object_name]
|
|
23
|
+
|
|
24
|
+
description = ERROR_DESCRIPTIONS[type] || 'Empty comment line in documentation'
|
|
25
|
+
|
|
26
|
+
"#{description} for '#{object_name}'"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module EmptyCommentLine
|
|
8
|
+
# Parses YARD output for empty comment line violations
|
|
9
|
+
class Parser < Parsers::Base
|
|
10
|
+
# Parse YARD output into structured violations
|
|
11
|
+
# @param output [String] raw YARD output
|
|
12
|
+
# @return [Array<Hash>] array of violation hashes
|
|
13
|
+
def call(output)
|
|
14
|
+
return [] if output.nil? || output.empty?
|
|
15
|
+
|
|
16
|
+
violations = []
|
|
17
|
+
lines = output.lines.map(&:chomp)
|
|
18
|
+
|
|
19
|
+
i = 0
|
|
20
|
+
while i < lines.size
|
|
21
|
+
line = lines[i]
|
|
22
|
+
|
|
23
|
+
# Match location line: "file:line: object_name"
|
|
24
|
+
if (location_match = line.match(/^(.+):(\d+): (.+)$/))
|
|
25
|
+
file_path = location_match[1]
|
|
26
|
+
object_line = location_match[2].to_i
|
|
27
|
+
object_name = location_match[3]
|
|
28
|
+
|
|
29
|
+
# Next line contains violation details
|
|
30
|
+
i += 1
|
|
31
|
+
next unless i < lines.size
|
|
32
|
+
|
|
33
|
+
# Parse violations: "leading:5|trailing:10"
|
|
34
|
+
violation_parts = lines[i].split('|')
|
|
35
|
+
|
|
36
|
+
violation_parts.each do |part|
|
|
37
|
+
type, line_num = part.split(':', 2)
|
|
38
|
+
next unless type && line_num
|
|
39
|
+
|
|
40
|
+
violations << {
|
|
41
|
+
location: file_path,
|
|
42
|
+
line: line_num.to_i,
|
|
43
|
+
object_line: object_line,
|
|
44
|
+
object_name: object_name,
|
|
45
|
+
violation_type: type
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
i += 1
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
violations
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module EmptyCommentLine
|
|
8
|
+
# Result builder for empty comment line violations
|
|
9
|
+
class Result < Results::Base
|
|
10
|
+
self.default_severity = 'convention'
|
|
11
|
+
self.offense_type = 'line'
|
|
12
|
+
self.offense_name = 'EmptyCommentLine'
|
|
13
|
+
|
|
14
|
+
# Build human-readable message for empty comment line offense
|
|
15
|
+
# @param offense [Hash] offense data with violation details
|
|
16
|
+
# @return [String] formatted message
|
|
17
|
+
def build_message(offense)
|
|
18
|
+
MessagesBuilder.call(offense)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module EmptyCommentLine
|
|
8
|
+
# Validates empty comment lines at the start/end of documentation blocks
|
|
9
|
+
class Validator < Validators::Base
|
|
10
|
+
# Enable in-process execution for this validator
|
|
11
|
+
in_process visibility: :public
|
|
12
|
+
|
|
13
|
+
# Execute query for a single object during in-process execution.
|
|
14
|
+
# Checks for empty leading/trailing comment lines in documentation blocks.
|
|
15
|
+
# @param object [YARD::CodeObjects::Base] the code object to query
|
|
16
|
+
# @param collector [Executor::ResultCollector] collector for output
|
|
17
|
+
# @return [void]
|
|
18
|
+
def in_process_query(object, collector)
|
|
19
|
+
return unless object.file && File.exist?(object.file) && object.line.to_i > 1
|
|
20
|
+
|
|
21
|
+
check_leading = check_leading?
|
|
22
|
+
check_trailing = check_trailing?
|
|
23
|
+
|
|
24
|
+
source_lines = File.readlines(object.file)
|
|
25
|
+
definition_line = object.line - 1
|
|
26
|
+
|
|
27
|
+
# Find comment block boundaries
|
|
28
|
+
comment_end = nil
|
|
29
|
+
comment_start = nil
|
|
30
|
+
|
|
31
|
+
(definition_line - 1).downto(0) do |i|
|
|
32
|
+
line = source_lines[i].to_s.rstrip
|
|
33
|
+
stripped = line.strip
|
|
34
|
+
|
|
35
|
+
if stripped.empty? && comment_end.nil?
|
|
36
|
+
# Skip empty lines before finding comment block
|
|
37
|
+
next
|
|
38
|
+
elsif stripped.start_with?('#')
|
|
39
|
+
comment_end ||= i
|
|
40
|
+
comment_start = i
|
|
41
|
+
else
|
|
42
|
+
break
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
return unless comment_start && comment_end
|
|
47
|
+
|
|
48
|
+
comment_block = source_lines[comment_start..comment_end]
|
|
49
|
+
|
|
50
|
+
# Find first and last content lines
|
|
51
|
+
first_content_idx = nil
|
|
52
|
+
last_content_idx = nil
|
|
53
|
+
|
|
54
|
+
comment_block.each_with_index do |line, idx|
|
|
55
|
+
stripped = line.strip
|
|
56
|
+
has_content = stripped.match?(/^#.+\S/)
|
|
57
|
+
if has_content
|
|
58
|
+
first_content_idx ||= idx
|
|
59
|
+
last_content_idx = idx
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
return unless first_content_idx && last_content_idx
|
|
64
|
+
|
|
65
|
+
violations = []
|
|
66
|
+
|
|
67
|
+
# Check for leading empty comment lines
|
|
68
|
+
if check_leading
|
|
69
|
+
(0...first_content_idx).each do |i|
|
|
70
|
+
if comment_block[i].strip.match?(/^#\s*$/)
|
|
71
|
+
violations << "leading:#{comment_start + i + 1}"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Check for trailing empty comment lines
|
|
77
|
+
if check_trailing
|
|
78
|
+
((last_content_idx + 1)...comment_block.length).each do |i|
|
|
79
|
+
if comment_block[i].strip.match?(/^#\s*$/)
|
|
80
|
+
violations << "trailing:#{comment_start + i + 1}"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
return if violations.empty?
|
|
86
|
+
|
|
87
|
+
collector.puts "#{object.file}:#{object.line}: #{object.title}"
|
|
88
|
+
collector.puts violations.join('|')
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
# @return [Boolean] whether to check for leading empty lines
|
|
94
|
+
def check_leading?
|
|
95
|
+
patterns = config_or_default('EnabledPatterns')
|
|
96
|
+
patterns['Leading']
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# @return [Boolean] whether to check for trailing empty lines
|
|
100
|
+
def check_trailing?
|
|
101
|
+
patterns = config_or_default('EnabledPatterns')
|
|
102
|
+
patterns['Trailing']
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
# EmptyCommentLine validator
|
|
8
|
+
#
|
|
9
|
+
# Detects empty comment lines at the start or end of YARD documentation blocks.
|
|
10
|
+
# Empty lines BETWEEN tag groups are allowed for readability.
|
|
11
|
+
#
|
|
12
|
+
# @example Bad - Empty leading comment line
|
|
13
|
+
# #
|
|
14
|
+
# # Description of the method
|
|
15
|
+
# # @param value [String] the value
|
|
16
|
+
# def process(value)
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# @example Bad - Empty trailing comment line
|
|
20
|
+
# # Description of the method
|
|
21
|
+
# # @param value [String] the value
|
|
22
|
+
# #
|
|
23
|
+
# def process(value)
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# @example Good - No leading or trailing empty lines
|
|
27
|
+
# # Description of the method
|
|
28
|
+
# # @param value [String] the value
|
|
29
|
+
# def process(value)
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# @example Good - Empty line between sections (allowed)
|
|
33
|
+
# # Description of the method
|
|
34
|
+
# #
|
|
35
|
+
# # @param value [String] the value
|
|
36
|
+
# # @return [Boolean] success
|
|
37
|
+
# def process(value)
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# ## Configuration
|
|
41
|
+
#
|
|
42
|
+
# To check only leading empty lines:
|
|
43
|
+
#
|
|
44
|
+
# Documentation/EmptyCommentLine:
|
|
45
|
+
# EnabledPatterns:
|
|
46
|
+
# Leading: true
|
|
47
|
+
# Trailing: false
|
|
48
|
+
#
|
|
49
|
+
# To disable this validator:
|
|
50
|
+
#
|
|
51
|
+
# Documentation/EmptyCommentLine:
|
|
52
|
+
# Enabled: false
|
|
53
|
+
module EmptyCommentLine
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -7,28 +7,43 @@ module Yard
|
|
|
7
7
|
module MarkdownSyntax
|
|
8
8
|
# Validates markdown syntax in documentation
|
|
9
9
|
class Validator < Validators::Base
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
# Enable in-process execution for this validator
|
|
11
|
+
in_process visibility: :public
|
|
12
|
+
|
|
13
|
+
# Execute query for a single object during in-process execution.
|
|
14
|
+
# Checks for markdown syntax errors in docstrings.
|
|
15
|
+
# @param object [YARD::CodeObjects::Base] the code object to query
|
|
16
|
+
# @param collector [Executor::ResultCollector] collector for output
|
|
17
|
+
# @return [void]
|
|
18
|
+
def in_process_query(object, collector)
|
|
19
|
+
docstring_text = object.docstring.to_s
|
|
20
|
+
return if docstring_text.empty?
|
|
21
|
+
|
|
22
|
+
errors = []
|
|
23
|
+
|
|
24
|
+
# Check for unclosed backticks
|
|
25
|
+
backtick_count = docstring_text.scan(/`/).count
|
|
26
|
+
errors << 'unclosed_backtick' if backtick_count.odd?
|
|
27
|
+
|
|
28
|
+
# Check for unclosed code blocks
|
|
29
|
+
code_block_count = docstring_text.scan(/^```/).count
|
|
30
|
+
errors << 'unclosed_code_block' if code_block_count.odd?
|
|
31
|
+
|
|
32
|
+
# Check for unclosed bold markers (excluding code sections)
|
|
33
|
+
non_code_text = docstring_text.gsub(/`[^`]*`/, '')
|
|
34
|
+
bold_count = non_code_text.scan(/\*\*/).count
|
|
35
|
+
errors << 'unclosed_bold' if bold_count.odd?
|
|
36
|
+
|
|
37
|
+
# Check for invalid list markers
|
|
38
|
+
docstring_text.lines.each_with_index do |line, line_idx|
|
|
39
|
+
stripped = line.strip
|
|
40
|
+
errors << "invalid_list_marker:#{line_idx + 1}" if stripped.match?(/^[•·]/)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
return if errors.empty?
|
|
17
44
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
# @param file_list_path [String] path to file containing list of files to analyze
|
|
21
|
-
# @return [String] command output
|
|
22
|
-
def yard_cmd(dir, file_list_path)
|
|
23
|
-
cmd = <<~CMD
|
|
24
|
-
cat #{Shellwords.escape(file_list_path)} | xargs yard list \
|
|
25
|
-
#{shell_arguments} \
|
|
26
|
-
--query #{query} \
|
|
27
|
-
-q \
|
|
28
|
-
-b #{Shellwords.escape(dir)}
|
|
29
|
-
CMD
|
|
30
|
-
cmd = cmd.tr("\n", ' ')
|
|
31
|
-
shell(cmd)
|
|
45
|
+
collector.puts "#{object.file}:#{object.line}: #{object.title}"
|
|
46
|
+
collector.puts errors.join('|')
|
|
32
47
|
end
|
|
33
48
|
end
|
|
34
49
|
end
|