yard-lint 0.2.0 → 1.0.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 +36 -1
- data/README.md +256 -1
- data/Rakefile +4 -0
- data/bin/yard-lint +24 -0
- data/lib/yard/lint/config.rb +35 -1
- data/lib/yard/lint/config_loader.rb +1 -1
- data/lib/yard/lint/result_builder.rb +10 -1
- data/lib/yard/lint/validators/base.rb +41 -12
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/validator.rb +7 -6
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/validator.rb +4 -5
- data/lib/yard/lint/validators/documentation/undocumented_objects/config.rb +2 -1
- data/lib/yard/lint/validators/documentation/undocumented_objects/parser.rb +95 -5
- data/lib/yard/lint/validators/documentation/undocumented_objects/validator.rb +15 -11
- data/lib/yard/lint/validators/semantic/abstract_methods/validator.rb +4 -5
- data/lib/yard/lint/validators/tags/api_tags/validator.rb +4 -5
- data/lib/yard/lint/validators/tags/collection_type/config.rb +21 -0
- data/lib/yard/lint/validators/tags/collection_type/messages_builder.rb +44 -0
- data/lib/yard/lint/validators/tags/collection_type/parser.rb +49 -0
- data/lib/yard/lint/validators/tags/collection_type/result.rb +25 -0
- data/lib/yard/lint/validators/tags/collection_type/validator.rb +73 -0
- data/lib/yard/lint/validators/tags/collection_type.rb +14 -0
- data/lib/yard/lint/validators/tags/invalid_types/validator.rb +6 -6
- data/lib/yard/lint/validators/tags/meaningless_tag/config.rb +22 -0
- data/lib/yard/lint/validators/tags/meaningless_tag/messages_builder.rb +28 -0
- data/lib/yard/lint/validators/tags/meaningless_tag/parser.rb +53 -0
- data/lib/yard/lint/validators/tags/meaningless_tag/result.rb +26 -0
- data/lib/yard/lint/validators/tags/meaningless_tag/validator.rb +84 -0
- data/lib/yard/lint/validators/tags/meaningless_tag.rb +15 -0
- data/lib/yard/lint/validators/tags/option_tags/validator.rb +4 -5
- data/lib/yard/lint/validators/tags/order/validator.rb +4 -5
- data/lib/yard/lint/validators/tags/tag_type_position/config.rb +22 -0
- data/lib/yard/lint/validators/tags/tag_type_position/messages_builder.rb +38 -0
- data/lib/yard/lint/validators/tags/tag_type_position/parser.rb +51 -0
- data/lib/yard/lint/validators/tags/tag_type_position/result.rb +25 -0
- data/lib/yard/lint/validators/tags/tag_type_position/validator.rb +113 -0
- data/lib/yard/lint/validators/tags/tag_type_position.rb +14 -0
- data/lib/yard/lint/validators/tags/type_syntax/config.rb +21 -0
- data/lib/yard/lint/validators/tags/type_syntax/messages_builder.rb +27 -0
- data/lib/yard/lint/validators/tags/type_syntax/parser.rb +54 -0
- data/lib/yard/lint/validators/tags/type_syntax/result.rb +25 -0
- data/lib/yard/lint/validators/tags/type_syntax/validator.rb +76 -0
- data/lib/yard/lint/validators/tags/type_syntax.rb +14 -0
- data/lib/yard/lint/validators/warnings/duplicated_parameter_name/validator.rb +4 -5
- data/lib/yard/lint/validators/warnings/invalid_directive_format/validator.rb +4 -5
- data/lib/yard/lint/validators/warnings/invalid_tag_format/validator.rb +4 -5
- data/lib/yard/lint/validators/warnings/unknown_directive/validator.rb +4 -5
- data/lib/yard/lint/validators/warnings/unknown_parameter_name/parser.rb +4 -1
- data/lib/yard/lint/validators/warnings/unknown_parameter_name/validator.rb +4 -5
- data/lib/yard/lint/validators/warnings/unknown_tag/validator.rb +4 -5
- data/lib/yard/lint/version.rb +1 -1
- data/lib/yard/lint.rb +1 -0
- data/misc/logo.png +0 -0
- metadata +27 -1
|
@@ -8,14 +8,28 @@ module Yard
|
|
|
8
8
|
# Class used to extract details about undocumented objects from raw yard list output
|
|
9
9
|
# @example
|
|
10
10
|
# /path/to/file.rb:3: UndocumentedClass
|
|
11
|
-
# /path/to/file.rb:4: UndocumentedClass#method_one
|
|
11
|
+
# /path/to/file.rb:4: UndocumentedClass#method_one|2
|
|
12
12
|
class Parser < ::Yard::Lint::Parsers::Base
|
|
13
|
-
# Regex used to parse yard list output format
|
|
14
|
-
|
|
13
|
+
# Regex used to parse yard list output format
|
|
14
|
+
# Format: file.rb:LINE: ObjectName or ObjectName|ARITY
|
|
15
|
+
LINE_REGEX = /^(.+):(\d+): (.+?)(?:\|(\d+))?$/
|
|
15
16
|
|
|
16
17
|
# @param yard_list_output [String] raw yard list results string
|
|
18
|
+
# @param config [Yard::Lint::Config, nil] configuration object (optional)
|
|
19
|
+
# @param _kwargs [Hash] unused keyword arguments (for compatibility)
|
|
17
20
|
# @return [Array<Hash>] Array with undocumented objects details
|
|
18
|
-
def call(yard_list_output)
|
|
21
|
+
def call(yard_list_output, config: nil, **_kwargs)
|
|
22
|
+
excluded_methods = config&.validator_config(
|
|
23
|
+
'Documentation/UndocumentedObjects',
|
|
24
|
+
'ExcludedMethods'
|
|
25
|
+
) || []
|
|
26
|
+
|
|
27
|
+
# Ensure excluded_methods is an Array
|
|
28
|
+
excluded_methods = Array(excluded_methods)
|
|
29
|
+
|
|
30
|
+
# Sanitize patterns: remove nil, empty, whitespace-only, and normalize
|
|
31
|
+
excluded_methods = sanitize_patterns(excluded_methods)
|
|
32
|
+
|
|
19
33
|
yard_list_output
|
|
20
34
|
.split("\n")
|
|
21
35
|
.map(&:strip)
|
|
@@ -24,13 +38,89 @@ module Yard
|
|
|
24
38
|
match = line.match(LINE_REGEX)
|
|
25
39
|
next unless match
|
|
26
40
|
|
|
41
|
+
element = match[3]
|
|
42
|
+
arity = match[4]&.to_i
|
|
43
|
+
|
|
44
|
+
# Skip if method is in excluded list
|
|
45
|
+
next if method_excluded?(element, arity, excluded_methods)
|
|
46
|
+
|
|
27
47
|
{
|
|
28
48
|
location: match[1],
|
|
29
49
|
line: match[2].to_i,
|
|
30
|
-
element:
|
|
50
|
+
element: element
|
|
31
51
|
}
|
|
32
52
|
end
|
|
33
53
|
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# Checks if a method should be excluded based on ExcludedMethods config
|
|
58
|
+
# Supports: simple names, arity notation, and regex patterns
|
|
59
|
+
# @param element [String] the element name (e.g., "Class#method")
|
|
60
|
+
# @param arity [Integer, nil] number of parameters (required + optional,
|
|
61
|
+
# excluding splat and block)
|
|
62
|
+
# @param excluded_methods [Array<String>] list of exclusion patterns
|
|
63
|
+
# @return [Boolean] true if method should be excluded
|
|
64
|
+
def method_excluded?(element, arity, excluded_methods)
|
|
65
|
+
# Extract method name from element (e.g., "Foo::Bar#baz" -> "baz")
|
|
66
|
+
method_name = element.split(/[#.]/).last
|
|
67
|
+
return false unless method_name
|
|
68
|
+
|
|
69
|
+
excluded_methods.any? do |pattern|
|
|
70
|
+
case pattern
|
|
71
|
+
when %r{^/(.+)/$}
|
|
72
|
+
# Regex pattern: '/^_/' matches methods starting with _
|
|
73
|
+
match_regex_pattern(method_name, Regexp.last_match(1))
|
|
74
|
+
when %r{/\d+$}
|
|
75
|
+
# Arity pattern: 'initialize/0' checks method name and parameter count
|
|
76
|
+
match_arity_pattern(method_name, arity, pattern)
|
|
77
|
+
else
|
|
78
|
+
# Simple name match: 'initialize'
|
|
79
|
+
# Simple names match any arity (use arity notation for specific arity)
|
|
80
|
+
method_name == pattern
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Sanitize exclusion patterns
|
|
86
|
+
# @param patterns [Array] raw patterns from config
|
|
87
|
+
# @return [Array<String>] cleaned and validated patterns
|
|
88
|
+
def sanitize_patterns(patterns)
|
|
89
|
+
patterns
|
|
90
|
+
.compact # Remove nil values
|
|
91
|
+
.map { |p| p.to_s.strip } # Convert to strings and trim whitespace
|
|
92
|
+
.reject(&:empty?) # Remove empty strings
|
|
93
|
+
.reject { |p| p == '//' } # Reject empty regex (matches everything)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Match a regex pattern against method name with error handling
|
|
97
|
+
# @param method_name [String] the method name to match
|
|
98
|
+
# @param regex_pattern [String] the regex pattern (without delimiters)
|
|
99
|
+
# @return [Boolean] true if matches, false if invalid regex or no match
|
|
100
|
+
def match_regex_pattern(method_name, regex_pattern)
|
|
101
|
+
return false if regex_pattern.empty? # Empty regex would match everything
|
|
102
|
+
|
|
103
|
+
Regexp.new(regex_pattern).match?(method_name)
|
|
104
|
+
rescue RegexpError
|
|
105
|
+
# Invalid regex - skip this pattern
|
|
106
|
+
false
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Match an arity pattern like "initialize/0"
|
|
110
|
+
# @param method_name [String] the method name
|
|
111
|
+
# @param arity [Integer, nil] the method's arity
|
|
112
|
+
# @param pattern [String] the full pattern like "initialize/0"
|
|
113
|
+
# @return [Boolean] true if matches
|
|
114
|
+
def match_arity_pattern(method_name, arity, pattern)
|
|
115
|
+
pattern_name, pattern_arity_str = pattern.split('/')
|
|
116
|
+
|
|
117
|
+
# Validate arity is numeric
|
|
118
|
+
return false unless pattern_arity_str.match?(/^\d+$/)
|
|
119
|
+
|
|
120
|
+
pattern_arity = pattern_arity_str.to_i
|
|
121
|
+
|
|
122
|
+
method_name == pattern_name && arity == pattern_arity
|
|
123
|
+
end
|
|
34
124
|
end
|
|
35
125
|
end
|
|
36
126
|
end
|
|
@@ -7,30 +7,34 @@ module Yard
|
|
|
7
7
|
module UndocumentedObjects
|
|
8
8
|
# Runs yard list to check for undocumented objects
|
|
9
9
|
class Validator < Base
|
|
10
|
-
# Query to find all objects without documentation
|
|
11
|
-
QUERY = "'docstring.blank?'"
|
|
12
|
-
|
|
13
|
-
private_constant :QUERY
|
|
14
|
-
|
|
15
10
|
private
|
|
16
11
|
|
|
17
12
|
# Runs yard list query with proper settings on a given dir and files
|
|
18
13
|
# @param dir [String] dir where we should generate the temp docs
|
|
19
|
-
# @param
|
|
14
|
+
# @param file_list_path [String] path to temp file containing file paths (one per line)
|
|
20
15
|
# @return [Hash] shell command execution hash results
|
|
21
|
-
def yard_cmd(dir,
|
|
16
|
+
def yard_cmd(dir, file_list_path)
|
|
22
17
|
cmd = <<~CMD
|
|
23
|
-
yard list \
|
|
18
|
+
cat #{Shellwords.escape(file_list_path)} | xargs yard list \
|
|
24
19
|
#{shell_arguments} \
|
|
25
|
-
--query #{
|
|
20
|
+
--query #{query} \
|
|
26
21
|
-q \
|
|
27
|
-
-b #{Shellwords.escape(dir)}
|
|
28
|
-
#{escaped_file_names}
|
|
22
|
+
-b #{Shellwords.escape(dir)}
|
|
29
23
|
CMD
|
|
30
24
|
cmd = cmd.tr("\n", ' ')
|
|
31
25
|
|
|
32
26
|
shell(cmd)
|
|
33
27
|
end
|
|
28
|
+
|
|
29
|
+
# Custom query that outputs parameter count for methods
|
|
30
|
+
# Format: file.rb:LINE: ElementName|ARITY
|
|
31
|
+
# Arity counts all parameters (required + optional) excluding splat and block
|
|
32
|
+
# @return [String] YARD query string
|
|
33
|
+
def query
|
|
34
|
+
<<~QUERY.chomp
|
|
35
|
+
"if docstring.all.empty? then if object.is_a?(YARD::CodeObjects::MethodObject) then arity = object.parameters.reject { |p| p[0].start_with?('*', '&') }.size; puts object.file + ':' + object.line.to_s + ': ' + object.title + '|' + arity.to_s; else puts object.file + ':' + object.line.to_s + ': ' + object.title; end; false; end"
|
|
36
|
+
QUERY
|
|
37
|
+
end
|
|
34
38
|
end
|
|
35
39
|
end
|
|
36
40
|
end
|
|
@@ -11,15 +11,14 @@ module Yard
|
|
|
11
11
|
|
|
12
12
|
# Runs YARD list query to find abstract methods with implementation
|
|
13
13
|
# @param dir [String] dir where the YARD db is (or where it should be generated)
|
|
14
|
-
# @param
|
|
14
|
+
# @param file_list_path [String] path to temp file containing file paths (one per line)
|
|
15
15
|
# @return [Hash] shell command execution hash results
|
|
16
|
-
def yard_cmd(dir,
|
|
16
|
+
def yard_cmd(dir, file_list_path)
|
|
17
17
|
cmd = <<~CMD
|
|
18
|
-
yard list \
|
|
18
|
+
cat #{Shellwords.escape(file_list_path)} | xargs yard list \
|
|
19
19
|
--private \
|
|
20
20
|
--protected \
|
|
21
|
-
-b #{Shellwords.escape(dir)}
|
|
22
|
-
#{escaped_file_names}
|
|
21
|
+
-b #{Shellwords.escape(dir)}
|
|
23
22
|
CMD
|
|
24
23
|
cmd = cmd.tr("\n", ' ')
|
|
25
24
|
cmd = cmd.gsub('yard list', "yard list --query #{query}")
|
|
@@ -11,15 +11,14 @@ module Yard
|
|
|
11
11
|
|
|
12
12
|
# Runs yard list query to find objects missing or with invalid @api tags
|
|
13
13
|
# @param dir [String] dir where the yard db is (or where it should be generated)
|
|
14
|
-
# @param
|
|
14
|
+
# @param file_list_path [String] path to temp file containing file paths (one per line)
|
|
15
15
|
# @return [Hash] shell command execution hash results
|
|
16
|
-
def yard_cmd(dir,
|
|
16
|
+
def yard_cmd(dir, file_list_path)
|
|
17
17
|
cmd = <<~CMD
|
|
18
|
-
yard list \
|
|
18
|
+
cat #{Shellwords.escape(file_list_path)} | xargs yard list \
|
|
19
19
|
--private \
|
|
20
20
|
--protected \
|
|
21
|
-
-b #{Shellwords.escape(dir)}
|
|
22
|
-
#{escaped_file_names}
|
|
21
|
+
-b #{Shellwords.escape(dir)}
|
|
23
22
|
CMD
|
|
24
23
|
cmd = cmd.tr("\n", ' ')
|
|
25
24
|
cmd = cmd.gsub('yard list', "yard list --query #{query}")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module CollectionType
|
|
8
|
+
# Configuration for CollectionType validator
|
|
9
|
+
class Config < ::Yard::Lint::Validators::Config
|
|
10
|
+
self.id = :collection_type
|
|
11
|
+
self.defaults = {
|
|
12
|
+
'Enabled' => true,
|
|
13
|
+
'Severity' => 'convention',
|
|
14
|
+
'ValidatedTags' => %w[param option return yieldreturn]
|
|
15
|
+
}.freeze
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module CollectionType
|
|
8
|
+
# Builds human-readable messages for CollectionType violations
|
|
9
|
+
class MessagesBuilder
|
|
10
|
+
class << self
|
|
11
|
+
# Formats a violation message
|
|
12
|
+
# @param offense [Hash] the offense details
|
|
13
|
+
# @return [String] formatted message
|
|
14
|
+
def call(offense)
|
|
15
|
+
type_string = offense[:type_string]
|
|
16
|
+
tag_name = offense[:tag_name]
|
|
17
|
+
|
|
18
|
+
# Extract the corrected version
|
|
19
|
+
corrected = suggest_correction(type_string)
|
|
20
|
+
|
|
21
|
+
"Use YARD collection syntax #{corrected} instead of #{type_string} " \
|
|
22
|
+
"in @#{tag_name} tag. YARD uses Hash{K => V} syntax for hashes."
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
# Suggests the corrected YARD syntax
|
|
28
|
+
# @param type_string [String] the incorrect type string
|
|
29
|
+
# @return [String] the suggested correction
|
|
30
|
+
def suggest_correction(type_string)
|
|
31
|
+
# Convert Hash<K, V> to Hash{K => V}
|
|
32
|
+
type_string.gsub(/Hash<(.+?)>/) do
|
|
33
|
+
types = ::Regexp.last_match(1)
|
|
34
|
+
# Split on comma, handle nested types
|
|
35
|
+
"Hash{#{types.sub(/,\s*/, ' => ')}}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
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 CollectionType
|
|
8
|
+
# Parses YARD output for CollectionType violations
|
|
9
|
+
class Parser < ::Yard::Lint::Parsers::Base
|
|
10
|
+
# Parses YARD query output into structured violation data
|
|
11
|
+
# @param yard_output [String] raw output from YARD query
|
|
12
|
+
# @param _kwargs [Hash] additional keyword arguments (unused)
|
|
13
|
+
# @return [Array<Hash>] array of violation hashes
|
|
14
|
+
def call(yard_output, **_kwargs)
|
|
15
|
+
return [] if yard_output.nil? || yard_output.strip.empty?
|
|
16
|
+
|
|
17
|
+
lines = yard_output.split("\n").map(&:strip).reject(&:empty?)
|
|
18
|
+
violations = []
|
|
19
|
+
|
|
20
|
+
lines.each_slice(2) do |location_line, details_line|
|
|
21
|
+
next unless location_line && details_line
|
|
22
|
+
|
|
23
|
+
# Parse location: "file.rb:10: ClassName#method_name"
|
|
24
|
+
location_match = location_line.match(/^(.+):(\d+): (.+)$/)
|
|
25
|
+
next unless location_match
|
|
26
|
+
|
|
27
|
+
# Parse details: "tag_name|type_string"
|
|
28
|
+
details = details_line.split('|', 2)
|
|
29
|
+
next unless details.size == 2
|
|
30
|
+
|
|
31
|
+
tag_name, type_string = details
|
|
32
|
+
|
|
33
|
+
violations << {
|
|
34
|
+
location: location_match[1],
|
|
35
|
+
line: location_match[2].to_i,
|
|
36
|
+
object_name: location_match[3],
|
|
37
|
+
tag_name: tag_name,
|
|
38
|
+
type_string: type_string
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
violations
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module CollectionType
|
|
8
|
+
# Result wrapper for CollectionType violations
|
|
9
|
+
class Result < Results::Base
|
|
10
|
+
self.default_severity = 'convention'
|
|
11
|
+
self.offense_type = 'style'
|
|
12
|
+
self.offense_name = 'CollectionType'
|
|
13
|
+
|
|
14
|
+
# Builds a human-readable message for a violation
|
|
15
|
+
# @param offense [Hash] the offense 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,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module CollectionType
|
|
8
|
+
# Validates Hash collection type syntax in YARD tags
|
|
9
|
+
class Validator < Base
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# Runs YARD query to find incorrect Hash<K, V> syntax
|
|
13
|
+
# @param dir [String] directory where YARD database is stored
|
|
14
|
+
# @param file_list_path [String] path to temp file containing file paths (one per line)
|
|
15
|
+
# @return [Hash] shell command execution results
|
|
16
|
+
def yard_cmd(dir, file_list_path)
|
|
17
|
+
# Write query to a temporary file to avoid shell escaping issues
|
|
18
|
+
cmd = "cat #{Shellwords.escape(file_list_path)} | xargs yard list --query #{query} "
|
|
19
|
+
|
|
20
|
+
Tempfile.create(['yard_query', '.sh']) do |f|
|
|
21
|
+
f.write("#!/bin/bash\n")
|
|
22
|
+
f.write(cmd)
|
|
23
|
+
f.write("#{shell_arguments} -b #{Shellwords.escape(dir)}\n")
|
|
24
|
+
f.flush
|
|
25
|
+
f.chmod(0o755)
|
|
26
|
+
|
|
27
|
+
shell("bash #{Shellwords.escape(f.path)}")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# YARD query that finds Hash<K, V> syntax in type annotations
|
|
32
|
+
# Format output as two lines per violation:
|
|
33
|
+
# Line 1: file.rb:LINE: ClassName#method_name
|
|
34
|
+
# Line 2: tag_name|type_string
|
|
35
|
+
# @return [String] YARD query string
|
|
36
|
+
def query
|
|
37
|
+
<<~QUERY.strip
|
|
38
|
+
'
|
|
39
|
+
docstring
|
|
40
|
+
.tags
|
|
41
|
+
.select { |tag| #{validated_tags_array}.include?(tag.tag_name) }
|
|
42
|
+
.each do |tag|
|
|
43
|
+
next unless tag.types
|
|
44
|
+
|
|
45
|
+
tag.types.each do |type_str|
|
|
46
|
+
# Check for Hash<...> syntax (should be Hash{...})
|
|
47
|
+
if type_str =~ /Hash<.*>/
|
|
48
|
+
puts object.file + ":" + object.line.to_s + ": " + object.title
|
|
49
|
+
puts tag.tag_name + "|" + type_str
|
|
50
|
+
break
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
false
|
|
56
|
+
'
|
|
57
|
+
QUERY
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Array of tag names to validate, formatted for YARD query
|
|
61
|
+
# @return [String] Ruby array literal string
|
|
62
|
+
def validated_tags_array
|
|
63
|
+
tags = config.validator_config('Tags/CollectionType', 'ValidatedTags') || %w[
|
|
64
|
+
param option return yieldreturn
|
|
65
|
+
]
|
|
66
|
+
"[#{tags.map { |t| "\"#{t}\"" }.join(',')}]"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
# CollectionType validator module
|
|
8
|
+
# Detects incorrect Hash collection syntax (Hash<K, V> instead of Hash{K => V})
|
|
9
|
+
module CollectionType
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -24,17 +24,17 @@ module Yard
|
|
|
24
24
|
|
|
25
25
|
# Runs yard list query with proper settings on a given dir and files
|
|
26
26
|
# @param dir [String] dir where the yard db is (or where it should be generated)
|
|
27
|
-
# @param
|
|
27
|
+
# @param file_list_path [String] path to temp file containing file paths (one per line)
|
|
28
28
|
# @return [Hash] shell command execution hash results
|
|
29
|
-
def yard_cmd(dir,
|
|
29
|
+
def yard_cmd(dir, file_list_path)
|
|
30
30
|
# Write query to a temporary file to avoid shell escaping issues
|
|
31
|
-
|
|
31
|
+
squery = Shellwords.escape(query)
|
|
32
|
+
cmd = "cat #{Shellwords.escape(file_list_path)} | xargs yard list --query #{squery} "
|
|
32
33
|
|
|
33
34
|
Tempfile.create(['yard_query', '.sh']) do |f|
|
|
34
35
|
f.write("#!/bin/bash\n")
|
|
35
|
-
f.write(
|
|
36
|
-
f.write("--private --protected -b #{Shellwords.escape(dir)}
|
|
37
|
-
f.write("#{escaped_file_names}\n")
|
|
36
|
+
f.write(cmd)
|
|
37
|
+
f.write("--private --protected -b #{Shellwords.escape(dir)}\n")
|
|
38
38
|
f.flush
|
|
39
39
|
f.chmod(0o755)
|
|
40
40
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module MeaninglessTag
|
|
8
|
+
# Configuration for MeaninglessTag validator
|
|
9
|
+
class Config < ::Yard::Lint::Validators::Config
|
|
10
|
+
self.id = :meaningless_tag
|
|
11
|
+
self.defaults = {
|
|
12
|
+
'Enabled' => true,
|
|
13
|
+
'Severity' => 'warning',
|
|
14
|
+
'CheckedTags' => %w[param option],
|
|
15
|
+
'InvalidObjectTypes' => %w[class module constant]
|
|
16
|
+
}.freeze
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
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 MeaninglessTag
|
|
8
|
+
# Builds human-readable messages for MeaninglessTag violations
|
|
9
|
+
class MessagesBuilder
|
|
10
|
+
class << self
|
|
11
|
+
# Formats a meaningless tag violation message
|
|
12
|
+
# @param offense [Hash] offense details with :object_type, :tag_name, :object_name
|
|
13
|
+
# @return [String] formatted message
|
|
14
|
+
def call(offense)
|
|
15
|
+
object_type = offense[:object_type]
|
|
16
|
+
tag_name = offense[:tag_name]
|
|
17
|
+
object_name = offense[:object_name]
|
|
18
|
+
|
|
19
|
+
"The @#{tag_name} tag is meaningless on a #{object_type} " \
|
|
20
|
+
"(#{object_name}). This tag only makes sense on methods."
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module MeaninglessTag
|
|
8
|
+
# Parser for MeaninglessTag validator output
|
|
9
|
+
# Parses YARD query output that reports meaningless tags on non-methods
|
|
10
|
+
class Parser < ::Yard::Lint::Parsers::Base
|
|
11
|
+
# Parses YARD output and extracts meaningless tag violations
|
|
12
|
+
# Expected format (two lines per violation):
|
|
13
|
+
# file.rb:LINE: ClassName
|
|
14
|
+
# object_type|tag_name
|
|
15
|
+
# @param yard_output [String] raw YARD query results
|
|
16
|
+
# @param _kwargs [Hash] unused keyword arguments (for compatibility)
|
|
17
|
+
# @return [Array<Hash>] array with violation details
|
|
18
|
+
def call(yard_output, **_kwargs)
|
|
19
|
+
return [] if yard_output.nil? || yard_output.strip.empty?
|
|
20
|
+
|
|
21
|
+
lines = yard_output.split("\n").map(&:strip).reject(&:empty?)
|
|
22
|
+
violations = []
|
|
23
|
+
|
|
24
|
+
lines.each_slice(2) do |location_line, details_line|
|
|
25
|
+
next unless location_line && details_line
|
|
26
|
+
|
|
27
|
+
# Parse location: "file.rb:10: ClassName"
|
|
28
|
+
location_match = location_line.match(/^(.+):(\d+): (.+)$/)
|
|
29
|
+
next unless location_match
|
|
30
|
+
|
|
31
|
+
# Parse details: "object_type|tag_name"
|
|
32
|
+
details = details_line.split('|', 2)
|
|
33
|
+
next unless details.size == 2
|
|
34
|
+
|
|
35
|
+
object_type, tag_name = details
|
|
36
|
+
|
|
37
|
+
violations << {
|
|
38
|
+
location: location_match[1],
|
|
39
|
+
line: location_match[2].to_i,
|
|
40
|
+
object_name: location_match[3],
|
|
41
|
+
object_type: object_type,
|
|
42
|
+
tag_name: tag_name
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
violations
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module MeaninglessTag
|
|
8
|
+
# Result wrapper for MeaninglessTag validator
|
|
9
|
+
# Formats parsed violations into offense objects
|
|
10
|
+
class Result < Results::Base
|
|
11
|
+
self.default_severity = 'warning'
|
|
12
|
+
self.offense_type = 'class'
|
|
13
|
+
self.offense_name = 'MeaninglessTag'
|
|
14
|
+
|
|
15
|
+
# Builds a human-readable message for the offense
|
|
16
|
+
# @param offense [Hash] offense details
|
|
17
|
+
# @return [String] formatted message
|
|
18
|
+
def build_message(offense)
|
|
19
|
+
MessagesBuilder.call(offense)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|