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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -1
  3. data/README.md +256 -1
  4. data/Rakefile +4 -0
  5. data/bin/yard-lint +24 -0
  6. data/lib/yard/lint/config.rb +35 -1
  7. data/lib/yard/lint/config_loader.rb +1 -1
  8. data/lib/yard/lint/result_builder.rb +10 -1
  9. data/lib/yard/lint/validators/base.rb +41 -12
  10. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/validator.rb +7 -6
  11. data/lib/yard/lint/validators/documentation/undocumented_method_arguments/validator.rb +4 -5
  12. data/lib/yard/lint/validators/documentation/undocumented_objects/config.rb +2 -1
  13. data/lib/yard/lint/validators/documentation/undocumented_objects/parser.rb +95 -5
  14. data/lib/yard/lint/validators/documentation/undocumented_objects/validator.rb +15 -11
  15. data/lib/yard/lint/validators/semantic/abstract_methods/validator.rb +4 -5
  16. data/lib/yard/lint/validators/tags/api_tags/validator.rb +4 -5
  17. data/lib/yard/lint/validators/tags/collection_type/config.rb +21 -0
  18. data/lib/yard/lint/validators/tags/collection_type/messages_builder.rb +44 -0
  19. data/lib/yard/lint/validators/tags/collection_type/parser.rb +49 -0
  20. data/lib/yard/lint/validators/tags/collection_type/result.rb +25 -0
  21. data/lib/yard/lint/validators/tags/collection_type/validator.rb +73 -0
  22. data/lib/yard/lint/validators/tags/collection_type.rb +14 -0
  23. data/lib/yard/lint/validators/tags/invalid_types/validator.rb +6 -6
  24. data/lib/yard/lint/validators/tags/meaningless_tag/config.rb +22 -0
  25. data/lib/yard/lint/validators/tags/meaningless_tag/messages_builder.rb +28 -0
  26. data/lib/yard/lint/validators/tags/meaningless_tag/parser.rb +53 -0
  27. data/lib/yard/lint/validators/tags/meaningless_tag/result.rb +26 -0
  28. data/lib/yard/lint/validators/tags/meaningless_tag/validator.rb +84 -0
  29. data/lib/yard/lint/validators/tags/meaningless_tag.rb +15 -0
  30. data/lib/yard/lint/validators/tags/option_tags/validator.rb +4 -5
  31. data/lib/yard/lint/validators/tags/order/validator.rb +4 -5
  32. data/lib/yard/lint/validators/tags/tag_type_position/config.rb +22 -0
  33. data/lib/yard/lint/validators/tags/tag_type_position/messages_builder.rb +38 -0
  34. data/lib/yard/lint/validators/tags/tag_type_position/parser.rb +51 -0
  35. data/lib/yard/lint/validators/tags/tag_type_position/result.rb +25 -0
  36. data/lib/yard/lint/validators/tags/tag_type_position/validator.rb +113 -0
  37. data/lib/yard/lint/validators/tags/tag_type_position.rb +14 -0
  38. data/lib/yard/lint/validators/tags/type_syntax/config.rb +21 -0
  39. data/lib/yard/lint/validators/tags/type_syntax/messages_builder.rb +27 -0
  40. data/lib/yard/lint/validators/tags/type_syntax/parser.rb +54 -0
  41. data/lib/yard/lint/validators/tags/type_syntax/result.rb +25 -0
  42. data/lib/yard/lint/validators/tags/type_syntax/validator.rb +76 -0
  43. data/lib/yard/lint/validators/tags/type_syntax.rb +14 -0
  44. data/lib/yard/lint/validators/warnings/duplicated_parameter_name/validator.rb +4 -5
  45. data/lib/yard/lint/validators/warnings/invalid_directive_format/validator.rb +4 -5
  46. data/lib/yard/lint/validators/warnings/invalid_tag_format/validator.rb +4 -5
  47. data/lib/yard/lint/validators/warnings/unknown_directive/validator.rb +4 -5
  48. data/lib/yard/lint/validators/warnings/unknown_parameter_name/parser.rb +4 -1
  49. data/lib/yard/lint/validators/warnings/unknown_parameter_name/validator.rb +4 -5
  50. data/lib/yard/lint/validators/warnings/unknown_tag/validator.rb +4 -5
  51. data/lib/yard/lint/version.rb +1 -1
  52. data/lib/yard/lint.rb +1 -0
  53. data/misc/logo.png +0 -0
  54. 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: file.rb:LINE: ObjectName
14
- LINE_REGEX = /^(.+):(\d+): (.+)$/
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: match[3]
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 escaped_file_names [String] files for which we want to get the stats
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, escaped_file_names)
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 #{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 escaped_file_names [String] files for which we want to run YARD
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, escaped_file_names)
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 escaped_file_names [String] files for which we want to get the stats
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, escaped_file_names)
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 escaped_file_names [String] files for which we want to get the stats
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, escaped_file_names)
29
+ def yard_cmd(dir, file_list_path)
30
30
  # Write query to a temporary file to avoid shell escaping issues
31
- require 'tempfile'
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("yard list --query #{Shellwords.escape(query)} ")
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