yard-lint 1.2.3 → 1.3.0.rc1
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/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/renovate.json +1 -8
- metadata +38 -2
- data/lib/yard/lint/command_cache.rb +0 -93
|
@@ -7,112 +7,136 @@ module Yard
|
|
|
7
7
|
module RedundantParamDescription
|
|
8
8
|
# Validates that parameter descriptions are not redundant/meaningless
|
|
9
9
|
class Validator < Validators::Base
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
# Enable in-process execution
|
|
11
|
+
in_process visibility: :public
|
|
12
|
+
|
|
13
|
+
# Execute query for a single object during in-process execution.
|
|
14
|
+
# Checks for redundant/meaningless parameter descriptions.
|
|
15
|
+
# @param object [YARD::CodeObjects::Base] the code object to query
|
|
16
|
+
# @param collector [Executor::ResultCollector] collector for output
|
|
17
|
+
# @return [void]
|
|
18
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
|
|
19
|
+
def in_process_query(object, collector)
|
|
20
|
+
return unless object.is_a?(YARD::CodeObjects::MethodObject)
|
|
21
|
+
|
|
22
|
+
articles = config_articles
|
|
23
|
+
generic_terms = config_generic_terms.map(&:downcase)
|
|
24
|
+
connectors = config_low_value_connectors.map(&:downcase)
|
|
25
|
+
low_value_verbs = config_low_value_verbs.map(&:downcase)
|
|
15
26
|
max_words = config_max_redundant_words
|
|
16
|
-
|
|
27
|
+
tags_to_check = config_checked_tags
|
|
17
28
|
patterns = config_enabled_patterns
|
|
18
29
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
'if desc_parts[0].match?(articles_re) && desc_parts[1].end_with?("s") && desc_parts[1].include?(39.chr) && desc_parts[2].downcase == param_name.downcase; ' \
|
|
41
|
-
'pattern_type = "possessive_param"; ' \
|
|
42
|
-
'end; ' \
|
|
43
|
-
'end; ' \
|
|
44
|
-
'end; ' \
|
|
45
|
-
"if pattern_type.nil? && #{patterns['TypeRestatement']} && type_name && word_count <= 2; " \
|
|
46
|
-
"generic_terms_arr = [\"#{generic_terms.gsub('|', '\", \"')}\"].map(&:downcase); " \
|
|
47
|
-
'if description.downcase == type_name.downcase; ' \
|
|
48
|
-
'pattern_type = "type_restatement"; ' \
|
|
49
|
-
'elsif word_count == 2; ' \
|
|
50
|
-
'parts = description.split; ' \
|
|
51
|
-
'if parts[0].downcase == type_name.downcase && generic_terms_arr.include?(parts[1].downcase); ' \
|
|
52
|
-
'pattern_type = "type_restatement"; ' \
|
|
53
|
-
'end; ' \
|
|
54
|
-
'end; ' \
|
|
55
|
-
'end; ' \
|
|
56
|
-
"if pattern_type.nil? && #{patterns['ParamToVerb']} && word_count <= 4; " \
|
|
57
|
-
'parts = description.split; ' \
|
|
58
|
-
'if parts.length == 3 && parts[0].downcase == param_name.downcase && parts[1].downcase == "to"; ' \
|
|
59
|
-
'pattern_type = "param_to_verb"; ' \
|
|
60
|
-
'end; ' \
|
|
61
|
-
'end; ' \
|
|
62
|
-
"if pattern_type.nil? && #{patterns['IdPattern']} && word_count <= 6; " \
|
|
63
|
-
'if param_name =~ /_id$|_uuid$|_identifier$/; ' \
|
|
64
|
-
'if description =~ /^(ID|Unique identifier|Identifier)\\s+(of|for)\\s+/i; ' \
|
|
65
|
-
'pattern_type = "id_pattern"; ' \
|
|
66
|
-
'end; ' \
|
|
67
|
-
'end; ' \
|
|
68
|
-
'end; ' \
|
|
69
|
-
"if pattern_type.nil? && #{patterns['DirectionalDate']} && word_count <= 4; " \
|
|
70
|
-
'if param_name =~ /^(from|to|till|until)$/; ' \
|
|
71
|
-
'parts = description.split; ' \
|
|
72
|
-
'if parts.length == 3 && parts[0].downcase == param_name.downcase && parts[1].downcase == "this"; ' \
|
|
73
|
-
'pattern_type = "directional_date"; ' \
|
|
74
|
-
'end; ' \
|
|
75
|
-
'end; ' \
|
|
76
|
-
'end; ' \
|
|
77
|
-
"if pattern_type.nil? && #{patterns['TypeGeneric']} && type_name && word_count <= 5; " \
|
|
78
|
-
"generic_terms_arr = [\"#{generic_terms.gsub('|', '\", \"')}\"].map(&:downcase); " \
|
|
79
|
-
'parts = description.split; ' \
|
|
80
|
-
'if parts.length >= 2 && parts[0].downcase == type_name.downcase && generic_terms_arr.include?(parts[1].downcase); ' \
|
|
81
|
-
'pattern_type = "type_generic"; ' \
|
|
82
|
-
'end; ' \
|
|
83
|
-
'end; ' \
|
|
84
|
-
'if pattern_type; ' \
|
|
85
|
-
'puts object.file + ":" + object.line.to_s + ": " + object.title; ' \
|
|
86
|
-
'puts tag.tag_name + "|" + param_name + "|" + tag.text.strip + "|" + (type_name || "") + "|" + pattern_type + "|" + word_count.to_s; ' \
|
|
87
|
-
'end; ' \
|
|
88
|
-
'end; ' \
|
|
89
|
-
'end; ' \
|
|
90
|
-
'false'
|
|
91
|
-
|
|
92
|
-
# Wrap in single quotes like other validators do
|
|
93
|
-
"'#{query_body}'"
|
|
30
|
+
object.docstring.tags.each do |tag|
|
|
31
|
+
next unless tags_to_check.include?(tag.tag_name)
|
|
32
|
+
next unless tag.name && tag.text && !tag.text.strip.empty?
|
|
33
|
+
|
|
34
|
+
param_name = tag.name
|
|
35
|
+
description = tag.text.strip.gsub(/\.$/, '')
|
|
36
|
+
word_count = description.split.length
|
|
37
|
+
type_name = tag.types&.first&.gsub(/[<>{}\[\],]/, '')&.strip
|
|
38
|
+
|
|
39
|
+
next if word_count > max_words
|
|
40
|
+
|
|
41
|
+
pattern_type = detect_pattern(
|
|
42
|
+
param_name, description, type_name, word_count,
|
|
43
|
+
articles, generic_terms, connectors, low_value_verbs, patterns
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
next unless pattern_type
|
|
47
|
+
|
|
48
|
+
collector.puts "#{object.file}:#{object.line}: #{object.title}"
|
|
49
|
+
collector.puts "#{tag.tag_name}|#{param_name}|#{tag.text.strip}|#{type_name || ''}|#{pattern_type}|#{word_count}"
|
|
50
|
+
end
|
|
94
51
|
end
|
|
52
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
# Detect the type of redundant pattern
|
|
57
|
+
# @param param_name [String] parameter name
|
|
58
|
+
# @param description [String] description text
|
|
59
|
+
# @param type_name [String, nil] type annotation
|
|
60
|
+
# @param word_count [Integer] number of words in description
|
|
61
|
+
# @param articles [Array<String>] article words
|
|
62
|
+
# @param generic_terms [Array<String>] generic terms
|
|
63
|
+
# @param connectors [Array<String>] low-value connectors
|
|
64
|
+
# @param low_value_verbs [Array<String>] low-value verbs
|
|
65
|
+
# @param patterns [Hash] enabled pattern flags
|
|
66
|
+
# @return [String, nil] pattern type or nil
|
|
67
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize, Metrics/ParameterLists
|
|
68
|
+
def detect_pattern(param_name, description, type_name, word_count, articles, generic_terms, connectors, low_value_verbs, patterns)
|
|
69
|
+
desc_parts = description.split
|
|
70
|
+
articles_re = /^(#{articles.join('|')})/i
|
|
71
|
+
|
|
72
|
+
# ArticleParam pattern
|
|
73
|
+
if patterns['ArticleParam'] && word_count <= 3 && desc_parts.length == 2
|
|
74
|
+
if desc_parts[0].match?(articles_re) && desc_parts[1].downcase == param_name.downcase
|
|
75
|
+
return 'article_param'
|
|
76
|
+
end
|
|
77
|
+
end
|
|
95
78
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
79
|
+
# PossessiveParam pattern
|
|
80
|
+
if patterns['PossessiveParam'] && word_count <= 4 && desc_parts.length >= 3
|
|
81
|
+
if desc_parts[0].match?(articles_re) && desc_parts[1].end_with?('s') &&
|
|
82
|
+
desc_parts[1].include?("'") && desc_parts[2].downcase == param_name.downcase
|
|
83
|
+
return 'possessive_param'
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# TypeRestatement pattern
|
|
88
|
+
if patterns['TypeRestatement'] && type_name && word_count <= 2
|
|
89
|
+
if description.downcase == type_name.downcase
|
|
90
|
+
return 'type_restatement'
|
|
91
|
+
elsif word_count == 2
|
|
92
|
+
parts = description.split
|
|
93
|
+
if parts[0].downcase == type_name.downcase && generic_terms.include?(parts[1].downcase)
|
|
94
|
+
return 'type_restatement'
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# ParamToVerb pattern
|
|
100
|
+
if patterns['ParamToVerb'] && word_count <= 4 && desc_parts.length == 3
|
|
101
|
+
if desc_parts[0].downcase == param_name.downcase && desc_parts[1].downcase == 'to'
|
|
102
|
+
return 'param_to_verb'
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# IdPattern
|
|
107
|
+
if patterns['IdPattern'] && word_count <= 6 && param_name =~ /_id$|_uuid$|_identifier$/
|
|
108
|
+
if description =~ /^(ID|Unique identifier|Identifier)\s+(of|for)\s+/i
|
|
109
|
+
return 'id_pattern'
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# DirectionalDate pattern
|
|
114
|
+
if patterns['DirectionalDate'] && word_count <= 4 && param_name =~ /^(from|to|till|until)$/
|
|
115
|
+
if desc_parts.length == 3 && desc_parts[0].downcase == param_name.downcase && desc_parts[1].downcase == 'this'
|
|
116
|
+
return 'directional_date'
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# TypeGeneric pattern
|
|
121
|
+
if patterns['TypeGeneric'] && type_name && word_count <= 5 && desc_parts.length >= 2
|
|
122
|
+
if desc_parts[0].downcase == type_name.downcase && generic_terms.include?(desc_parts[1].downcase)
|
|
123
|
+
return 'type_generic'
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# ArticleParamPhrase pattern: "The action being performed"
|
|
128
|
+
if patterns['ArticleParamPhrase'] && word_count >= 3 && desc_parts.length >= 3 &&
|
|
129
|
+
desc_parts[0].match?(articles_re) &&
|
|
130
|
+
desc_parts[1].downcase == param_name.downcase &&
|
|
131
|
+
connectors.include?(desc_parts[2].downcase) &&
|
|
132
|
+
(desc_parts.length == 3 ||
|
|
133
|
+
(desc_parts.length == 4 && low_value_verbs.include?(desc_parts[3].downcase)))
|
|
134
|
+
return 'article_param_phrase'
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
nil
|
|
115
138
|
end
|
|
139
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize, Metrics/ParameterLists
|
|
116
140
|
|
|
117
141
|
private
|
|
118
142
|
|
|
@@ -126,6 +150,16 @@ module Yard
|
|
|
126
150
|
config_or_default('GenericTerms')
|
|
127
151
|
end
|
|
128
152
|
|
|
153
|
+
# @return [Array<String>] configured low-value connectors
|
|
154
|
+
def config_low_value_connectors
|
|
155
|
+
config_or_default('LowValueConnectors')
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# @return [Array<String>] configured low-value verbs
|
|
159
|
+
def config_low_value_verbs
|
|
160
|
+
config_or_default('LowValueVerbs')
|
|
161
|
+
end
|
|
162
|
+
|
|
129
163
|
# @return [Integer] maximum word count for redundant descriptions
|
|
130
164
|
def config_max_redundant_words
|
|
131
165
|
config_or_default('MaxRedundantWords')
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module TagGroupSeparator
|
|
8
|
+
# Configuration for TagGroupSeparator validator
|
|
9
|
+
class Config < ::Yard::Lint::Validators::Config
|
|
10
|
+
self.id = :tag_group_separator
|
|
11
|
+
self.defaults = {
|
|
12
|
+
'Enabled' => false,
|
|
13
|
+
'Severity' => 'convention',
|
|
14
|
+
'TagGroups' => {
|
|
15
|
+
'param' => %w[param option],
|
|
16
|
+
'return' => %w[return],
|
|
17
|
+
'error' => %w[raise throws],
|
|
18
|
+
'example' => %w[example],
|
|
19
|
+
'meta' => %w[see note todo deprecated since version api],
|
|
20
|
+
'yield' => %w[yield yieldparam yieldreturn]
|
|
21
|
+
},
|
|
22
|
+
'RequireAfterDescription' => false
|
|
23
|
+
}.freeze
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
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 TagGroupSeparator
|
|
8
|
+
# Builds messages for missing tag group separator offenses.
|
|
9
|
+
class MessagesBuilder
|
|
10
|
+
class << self
|
|
11
|
+
# Build message for missing tag group separator.
|
|
12
|
+
#
|
|
13
|
+
# @param offense [Hash] offense data with :method_name and :separators keys
|
|
14
|
+
#
|
|
15
|
+
# @return [String] formatted message
|
|
16
|
+
def call(offense)
|
|
17
|
+
transitions = parse_transitions(offense[:separators])
|
|
18
|
+
|
|
19
|
+
if transitions.size == 1
|
|
20
|
+
from, to = transitions.first
|
|
21
|
+
"The `#{offense[:method_name]}` is missing a blank line between " \
|
|
22
|
+
"`#{from}` and `#{to}` tag groups."
|
|
23
|
+
else
|
|
24
|
+
formatted = transitions.map { |from, to| "`#{from}` -> `#{to}`" }.join(', ')
|
|
25
|
+
"The `#{offense[:method_name]}` is missing blank lines between tag groups: " \
|
|
26
|
+
"#{formatted}."
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
# Parses transition string into array of [from, to] pairs.
|
|
33
|
+
#
|
|
34
|
+
# @param separators [String] string in format "from->to,from->to"
|
|
35
|
+
#
|
|
36
|
+
# @return [Array<Array<String>>] array of [from, to] pairs
|
|
37
|
+
def parse_transitions(separators)
|
|
38
|
+
separators
|
|
39
|
+
.to_s
|
|
40
|
+
.split(',')
|
|
41
|
+
.map { |transition| transition.split('->') }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module TagGroupSeparator
|
|
8
|
+
# Parser for extracting tag group separator violations from raw validator output.
|
|
9
|
+
#
|
|
10
|
+
# @example Output format
|
|
11
|
+
# /path/to/file.rb:10: ClassName#method_name
|
|
12
|
+
# param->return,return->error
|
|
13
|
+
class Parser < Parsers::Base
|
|
14
|
+
# Regexp to extract only word and numeric parts of the location line
|
|
15
|
+
NORMALIZATION_REGEXP = /\w+/
|
|
16
|
+
|
|
17
|
+
private_constant :NORMALIZATION_REGEXP
|
|
18
|
+
|
|
19
|
+
# Parses raw validator output into structured offense data.
|
|
20
|
+
#
|
|
21
|
+
# @param yard_list [String] raw validator output string
|
|
22
|
+
#
|
|
23
|
+
# @return [Array<Hash>] array of hashes with offense details
|
|
24
|
+
def call(yard_list)
|
|
25
|
+
return [] if yard_list.nil? || yard_list.empty?
|
|
26
|
+
|
|
27
|
+
base_hash = {}
|
|
28
|
+
|
|
29
|
+
yard_list.split("\n").each_slice(2).each do |location, separators|
|
|
30
|
+
next if location.nil? || separators.nil?
|
|
31
|
+
|
|
32
|
+
key = normalize(location)
|
|
33
|
+
|
|
34
|
+
if separators == 'valid'
|
|
35
|
+
base_hash[key] = 'valid'
|
|
36
|
+
else
|
|
37
|
+
base_hash[key] ||= [location, separators]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
base_hash.delete_if { |_key, value| value == 'valid' }
|
|
42
|
+
separator_data = base_hash.values.map(&:last)
|
|
43
|
+
|
|
44
|
+
Validators::Documentation::UndocumentedMethodArguments::Parser
|
|
45
|
+
.new
|
|
46
|
+
.call(base_hash.values.map(&:first).join("\n"))
|
|
47
|
+
.each.with_index { |element, index| element[:separators] = separator_data[index] }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
# Normalizes a location line by extracting only word and numeric characters.
|
|
53
|
+
#
|
|
54
|
+
# @param location_line [String] full line with the location
|
|
55
|
+
#
|
|
56
|
+
# @return [String] normalized line without special characters
|
|
57
|
+
def normalize(location_line)
|
|
58
|
+
location_line
|
|
59
|
+
.scan(NORMALIZATION_REGEXP)
|
|
60
|
+
.join
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module TagGroupSeparator
|
|
8
|
+
# Result object for tag group separator validation.
|
|
9
|
+
# Transforms parsed separator violations into offense objects.
|
|
10
|
+
class Result < Results::Base
|
|
11
|
+
self.default_severity = 'convention'
|
|
12
|
+
self.offense_type = 'method'
|
|
13
|
+
self.offense_name = 'MissingTagGroupSeparator'
|
|
14
|
+
|
|
15
|
+
# Build human-readable message for tag group separator offense.
|
|
16
|
+
#
|
|
17
|
+
# @param offense [Hash] offense data with :method_name and :separators keys
|
|
18
|
+
#
|
|
19
|
+
# @return [String] formatted message
|
|
20
|
+
def build_message(offense)
|
|
21
|
+
MessagesBuilder.call(offense)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module TagGroupSeparator
|
|
8
|
+
# Validates that blank lines separate different groups of YARD documentation tags.
|
|
9
|
+
#
|
|
10
|
+
# This validator enforces visual separation between semantically different tag
|
|
11
|
+
# groups (e.g., @param tags should be separated from @return tags by a blank line).
|
|
12
|
+
class Validator < Base
|
|
13
|
+
# Enable in-process execution with all visibility
|
|
14
|
+
in_process visibility: :all
|
|
15
|
+
|
|
16
|
+
# Execute query for a single object during in-process execution.
|
|
17
|
+
# Checks if different tag groups are separated by blank lines.
|
|
18
|
+
#
|
|
19
|
+
# @param object [YARD::CodeObjects::Base] the code object to query
|
|
20
|
+
# @param collector [Executor::ResultCollector] collector for output
|
|
21
|
+
#
|
|
22
|
+
# @return [void]
|
|
23
|
+
def in_process_query(object, collector)
|
|
24
|
+
return if object.is_alias?
|
|
25
|
+
|
|
26
|
+
docstring = object.docstring.all
|
|
27
|
+
return if docstring.nil? || docstring.empty?
|
|
28
|
+
|
|
29
|
+
missing_separators = find_missing_separators(docstring)
|
|
30
|
+
|
|
31
|
+
collector.puts "#{object.file}:#{object.line}: #{object.title}"
|
|
32
|
+
|
|
33
|
+
if missing_separators.empty?
|
|
34
|
+
collector.puts 'valid'
|
|
35
|
+
else
|
|
36
|
+
collector.puts missing_separators.map { |s| "#{s[:from]}->#{s[:to]}" }.join(',')
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
# Find all locations where a blank line separator is missing between tag groups.
|
|
43
|
+
#
|
|
44
|
+
# @param docstring [String] the raw docstring content
|
|
45
|
+
#
|
|
46
|
+
# @return [Array<Hash>] array of hashes with :from and :to group names
|
|
47
|
+
def find_missing_separators(docstring)
|
|
48
|
+
lines = docstring.split("\n")
|
|
49
|
+
missing = []
|
|
50
|
+
|
|
51
|
+
previous_group = require_after_description? ? 'description' : nil
|
|
52
|
+
had_blank_line = true
|
|
53
|
+
|
|
54
|
+
lines.each do |line|
|
|
55
|
+
stripped = line.strip
|
|
56
|
+
|
|
57
|
+
if stripped.empty?
|
|
58
|
+
had_blank_line = true
|
|
59
|
+
next
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
if stripped.start_with?('@')
|
|
63
|
+
tag_name = stripped.match(/^@(\S+)/)&.captures&.first
|
|
64
|
+
next unless tag_name
|
|
65
|
+
|
|
66
|
+
current_group = group_for_tag(tag_name)
|
|
67
|
+
|
|
68
|
+
if previous_group && current_group != previous_group && !had_blank_line
|
|
69
|
+
missing << { from: previous_group, to: current_group }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
previous_group = current_group
|
|
73
|
+
elsif previous_group.nil? && require_after_description?
|
|
74
|
+
# Non-tag, non-blank line (continuation or description)
|
|
75
|
+
# Only set description group if we haven't seen tags yet
|
|
76
|
+
previous_group = 'description'
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
had_blank_line = false
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
missing
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Determine which group a tag belongs to.
|
|
86
|
+
#
|
|
87
|
+
# @param tag_name [String] the tag name without the @ prefix
|
|
88
|
+
#
|
|
89
|
+
# @return [String] the group name, or the tag name itself if not in any group
|
|
90
|
+
def group_for_tag(tag_name)
|
|
91
|
+
tag_groups.each do |group_name, tags|
|
|
92
|
+
return group_name if tags.include?(tag_name)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Tags not in any configured group are their own group
|
|
96
|
+
tag_name
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# @return [Hash] configured tag groups
|
|
100
|
+
def tag_groups
|
|
101
|
+
@tag_groups ||= config.validator_config('Tags/TagGroupSeparator', 'TagGroups') ||
|
|
102
|
+
Config.defaults['TagGroups']
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @return [Boolean] whether to require separator after description
|
|
106
|
+
def require_after_description?
|
|
107
|
+
@require_after_description ||= config.validator_config(
|
|
108
|
+
'Tags/TagGroupSeparator',
|
|
109
|
+
'RequireAfterDescription'
|
|
110
|
+
) || false
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
# TagGroupSeparator validator
|
|
8
|
+
#
|
|
9
|
+
# Enforces blank line separators between different groups of YARD documentation
|
|
10
|
+
# tags. This improves readability by visually grouping semantically related tags.
|
|
11
|
+
# This validator is disabled by default.
|
|
12
|
+
#
|
|
13
|
+
# @example Bad - No separator between @param and @return
|
|
14
|
+
# # @param organization_id [String] the organization ID
|
|
15
|
+
# # @param id [String] the pet ID
|
|
16
|
+
# # @return [Pet] the pet object
|
|
17
|
+
# def call(organization_id, id)
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# @example Good - Blank line separates tag groups
|
|
21
|
+
# # @param organization_id [String] the organization ID
|
|
22
|
+
# # @param id [String] the pet ID
|
|
23
|
+
# #
|
|
24
|
+
# # @return [Pet] the pet object
|
|
25
|
+
# def call(organization_id, id)
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# ## Configuration
|
|
29
|
+
#
|
|
30
|
+
# To enable this validator:
|
|
31
|
+
#
|
|
32
|
+
# Tags/TagGroupSeparator:
|
|
33
|
+
# Enabled: true
|
|
34
|
+
#
|
|
35
|
+
# To customize tag groups:
|
|
36
|
+
#
|
|
37
|
+
# Tags/TagGroupSeparator:
|
|
38
|
+
# Enabled: true
|
|
39
|
+
# TagGroups:
|
|
40
|
+
# param: [param, option]
|
|
41
|
+
# return: [return]
|
|
42
|
+
# error: [raise]
|
|
43
|
+
# yield: [yield, yieldparam, yieldreturn]
|
|
44
|
+
module TagGroupSeparator
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|