yard-lint 1.0.0 → 1.2.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +77 -0
  3. data/README.md +160 -268
  4. data/bin/yard-lint +100 -8
  5. data/lib/yard/lint/command_cache.rb +17 -1
  6. data/lib/yard/lint/config.rb +20 -2
  7. data/lib/yard/lint/config_generator.rb +200 -0
  8. data/lib/yard/lint/ext/irb_notifier_shim.rb +95 -0
  9. data/lib/yard/lint/git.rb +125 -0
  10. data/lib/yard/lint/results/aggregate.rb +22 -2
  11. data/lib/yard/lint/runner.rb +4 -3
  12. data/lib/yard/lint/stats_calculator.rb +157 -0
  13. data/lib/yard/lint/validators/base.rb +36 -0
  14. data/lib/yard/lint/validators/documentation/markdown_syntax/config.rb +20 -0
  15. data/lib/yard/lint/validators/documentation/markdown_syntax/messages_builder.rb +44 -0
  16. data/lib/yard/lint/validators/documentation/markdown_syntax/parser.rb +53 -0
  17. data/lib/yard/lint/validators/documentation/markdown_syntax/result.rb +25 -0
  18. data/lib/yard/lint/validators/documentation/markdown_syntax/validator.rb +38 -0
  19. data/lib/yard/lint/validators/documentation/markdown_syntax.rb +37 -0
  20. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods.rb +26 -1
  21. data/lib/yard/lint/validators/documentation/undocumented_method_arguments.rb +26 -1
  22. data/lib/yard/lint/validators/documentation/undocumented_objects.rb +131 -2
  23. data/lib/yard/lint/validators/documentation/undocumented_options/config.rb +20 -0
  24. data/lib/yard/lint/validators/documentation/undocumented_options/parser.rb +53 -0
  25. data/lib/yard/lint/validators/documentation/undocumented_options/result.rb +29 -0
  26. data/lib/yard/lint/validators/documentation/undocumented_options/validator.rb +38 -0
  27. data/lib/yard/lint/validators/documentation/undocumented_options.rb +40 -0
  28. data/lib/yard/lint/validators/semantic/abstract_methods.rb +31 -1
  29. data/lib/yard/lint/validators/tags/api_tags.rb +34 -1
  30. data/lib/yard/lint/validators/tags/collection_type/config.rb +2 -1
  31. data/lib/yard/lint/validators/tags/collection_type/messages_builder.rb +40 -11
  32. data/lib/yard/lint/validators/tags/collection_type/parser.rb +6 -5
  33. data/lib/yard/lint/validators/tags/collection_type/validator.rb +26 -7
  34. data/lib/yard/lint/validators/tags/collection_type.rb +38 -2
  35. data/lib/yard/lint/validators/tags/example_syntax/config.rb +20 -0
  36. data/lib/yard/lint/validators/tags/example_syntax/messages_builder.rb +28 -0
  37. data/lib/yard/lint/validators/tags/example_syntax/parser.rb +79 -0
  38. data/lib/yard/lint/validators/tags/example_syntax/result.rb +42 -0
  39. data/lib/yard/lint/validators/tags/example_syntax/validator.rb +88 -0
  40. data/lib/yard/lint/validators/tags/example_syntax.rb +42 -0
  41. data/lib/yard/lint/validators/tags/invalid_types/validator.rb +2 -2
  42. data/lib/yard/lint/validators/tags/invalid_types.rb +25 -1
  43. data/lib/yard/lint/validators/tags/meaningless_tag/validator.rb +2 -4
  44. data/lib/yard/lint/validators/tags/meaningless_tag.rb +31 -3
  45. data/lib/yard/lint/validators/tags/option_tags/validator.rb +7 -1
  46. data/lib/yard/lint/validators/tags/option_tags.rb +26 -1
  47. data/lib/yard/lint/validators/tags/order.rb +25 -1
  48. data/lib/yard/lint/validators/tags/redundant_param_description/config.rb +33 -0
  49. data/lib/yard/lint/validators/tags/redundant_param_description/messages_builder.rb +61 -0
  50. data/lib/yard/lint/validators/tags/redundant_param_description/parser.rb +67 -0
  51. data/lib/yard/lint/validators/tags/redundant_param_description/result.rb +25 -0
  52. data/lib/yard/lint/validators/tags/redundant_param_description/validator.rb +148 -0
  53. data/lib/yard/lint/validators/tags/redundant_param_description.rb +168 -0
  54. data/lib/yard/lint/validators/tags/tag_type_position/validator.rb +2 -4
  55. data/lib/yard/lint/validators/tags/tag_type_position.rb +39 -2
  56. data/lib/yard/lint/validators/tags/type_syntax.rb +26 -2
  57. data/lib/yard/lint/validators/warnings/duplicated_parameter_name.rb +26 -1
  58. data/lib/yard/lint/validators/warnings/invalid_directive_format.rb +26 -1
  59. data/lib/yard/lint/validators/warnings/invalid_tag_format.rb +25 -1
  60. data/lib/yard/lint/validators/warnings/unknown_directive.rb +26 -1
  61. data/lib/yard/lint/validators/warnings/unknown_parameter_name.rb +23 -1
  62. data/lib/yard/lint/validators/warnings/unknown_tag.rb +26 -1
  63. data/lib/yard/lint/version.rb +1 -1
  64. data/lib/yard/lint.rb +38 -2
  65. data/lib/yard-lint.rb +5 -0
  66. metadata +28 -1
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Documentation
7
+ module UndocumentedOptions
8
+ # Parses YARD output for undocumented options 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
+ line_number = location_match[2].to_i
27
+ object_name = location_match[3]
28
+
29
+ # Next line contains parameter list
30
+ i += 1
31
+ next unless i < lines.size
32
+
33
+ params = lines[i]
34
+
35
+ violations << {
36
+ location: file_path,
37
+ line: line_number,
38
+ object_name: object_name,
39
+ params: params
40
+ }
41
+ end
42
+
43
+ i += 1
44
+ end
45
+
46
+ violations
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Documentation
7
+ module UndocumentedOptions
8
+ # Result object for undocumented options validation
9
+ class Result < Results::Base
10
+ self.default_severity = 'warning'
11
+ self.offense_type = 'line'
12
+ self.offense_name = 'UndocumentedOptions'
13
+
14
+ # Build human-readable message for undocumented options offense
15
+ # @param offense [Hash] offense data with :object_name and :params
16
+ # @return [String] formatted message
17
+ def build_message(offense)
18
+ object_name = offense[:object_name]
19
+ params = offense[:params]
20
+
21
+ "Method '#{object_name}' has options parameter (#{params}) " \
22
+ 'but no @option tags in documentation.'
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Documentation
7
+ module UndocumentedOptions
8
+ # Validates that methods with options hash parameters have @option tags
9
+ class Validator < Validators::Base
10
+ # YARD query to detect methods with options parameters but no @option tags
11
+ # @return [String] YARD Ruby query code
12
+ def query
13
+ <<~QUERY.strip
14
+ 'if object.is_a?(YARD::CodeObjects::MethodObject); params = object.parameters || []; has_options_param = params.any? { |p| p[0] =~ /^(options?|opts?|kwargs)$/ || p[0] =~ /^\\*\\*/ || (p[0] =~ /^(options?|opts?|kwargs)$/ && p[1] =~ /^\\{\\}/) }; if has_options_param; option_tags = object.tags(:option); if option_tags.empty?; puts object.file + ":" + object.line.to_s + ": " + object.title; puts params.map { |p| p.join(" ") }.join(", "); end; end; end; false'
15
+ QUERY
16
+ end
17
+
18
+ # Builds and executes the YARD command to detect undocumented options
19
+ # @param dir [String] the directory containing the .yardoc database
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)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Documentation
7
+ # UndocumentedOptions validator
8
+ #
9
+ # Checks that options hashes have detailed documentation about their keys.
10
+ # When a method accepts an options hash parameter, the individual option
11
+ # keys should be documented using `@option` tags. This validator is enabled
12
+ # by default.
13
+ #
14
+ # @example Bad - Options parameter without @option tags
15
+ # # Configures the service
16
+ # # @param options [Hash] configuration options
17
+ # def configure(options)
18
+ # end
19
+ #
20
+ # @example Good - Options keys documented with @option tags
21
+ # # Configures the service
22
+ # # @param options [Hash] configuration options
23
+ # # @option options [Boolean] :enabled Whether to enable the feature
24
+ # # @option options [Integer] :timeout Connection timeout in seconds
25
+ # def configure(options)
26
+ # end
27
+ #
28
+ # ## Configuration
29
+ #
30
+ # To disable this validator:
31
+ #
32
+ # Documentation/UndocumentedOptions:
33
+ # Enabled: false
34
+ #
35
+ module UndocumentedOptions
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -4,7 +4,37 @@ module Yard
4
4
  module Lint
5
5
  module Validators
6
6
  module Semantic
7
- # AbstractMethods validator module
7
+ # AbstractMethods validator
8
+ #
9
+ # Ensures that methods marked with `@abstract` are actually abstract (not
10
+ # implemented). Abstract methods should either raise NotImplementedError or
11
+ # be empty stubs, not contain actual implementation logic. This validator
12
+ # is enabled by default.
13
+ #
14
+ # @example Bad - @abstract tag on implemented method
15
+ # # @abstract
16
+ # def process
17
+ # puts "This is actually implemented!"
18
+ # end
19
+ #
20
+ # @example Good - @abstract method raises NotImplementedError
21
+ # # @abstract
22
+ # def process
23
+ # raise NotImplementedError
24
+ # end
25
+ #
26
+ # @example Good - @abstract method is empty
27
+ # # @abstract
28
+ # def process
29
+ # end
30
+ #
31
+ # ## Configuration
32
+ #
33
+ # To disable this validator:
34
+ #
35
+ # Semantic/AbstractMethods:
36
+ # Enabled: false
37
+ #
8
38
  module AbstractMethods
9
39
  end
10
40
  end
@@ -4,7 +4,40 @@ module Yard
4
4
  module Lint
5
5
  module Validators
6
6
  module Tags
7
- # ApiTags validator module
7
+ # ApiTags validator
8
+ #
9
+ # Enforces that all public classes, modules, and methods have an `@api` tag
10
+ # to explicitly document their API visibility level. This validator is disabled
11
+ # by default and must be explicitly enabled.
12
+ #
13
+ # ## Configuration
14
+ #
15
+ # To enable this validator (it's disabled by default):
16
+ #
17
+ # Tags/ApiTags:
18
+ # Enabled: true
19
+ # AllowedApis:
20
+ # - public
21
+ # - private
22
+ #
23
+ # @example Good - Methods and classes have @api tags
24
+ # # @api public
25
+ # class MyClass
26
+ # # @api public
27
+ # def public_method
28
+ # end
29
+ #
30
+ # # @api private
31
+ # def internal_helper
32
+ # end
33
+ # end
34
+ #
35
+ # @example Bad - Missing @api tags
36
+ # class AnotherClass
37
+ # def some_method
38
+ # end
39
+ # end
40
+ #
8
41
  module ApiTags
9
42
  end
10
43
  end
@@ -11,7 +11,8 @@ module Yard
11
11
  self.defaults = {
12
12
  'Enabled' => true,
13
13
  'Severity' => 'convention',
14
- 'ValidatedTags' => %w[param option return yieldreturn]
14
+ 'ValidatedTags' => %w[param option return yieldreturn],
15
+ 'EnforcedStyle' => 'long' # 'long' (Hash{K => V}) or 'short' ({K => V})
15
16
  }.freeze
16
17
  end
17
18
  end
@@ -14,27 +14,56 @@ module Yard
14
14
  def call(offense)
15
15
  type_string = offense[:type_string]
16
16
  tag_name = offense[:tag_name]
17
+ detected_style = offense[:detected_style]
17
18
 
18
- # Extract the corrected version
19
- corrected = suggest_correction(type_string)
19
+ # Extract the corrected version based on detected style
20
+ corrected = suggest_correction(type_string, detected_style)
21
+ style_description = detected_style == 'short' ? 'long' : 'short'
20
22
 
21
- "Use YARD collection syntax #{corrected} instead of #{type_string} " \
22
- "in @#{tag_name} tag. YARD uses Hash{K => V} syntax for hashes."
23
+ "Use #{style_description} collection syntax #{corrected} instead of " \
24
+ "#{type_string} in @#{tag_name} tag."
23
25
  end
24
26
 
25
27
  private
26
28
 
27
- # Suggests the corrected YARD syntax
29
+ # Suggests the corrected YARD syntax based on detected style
28
30
  # @param type_string [String] the incorrect type string
31
+ # @param detected_style [String] the detected style ('short' or 'long')
29
32
  # @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*/, ' => ')}}"
33
+ def suggest_correction(type_string, detected_style)
34
+ if detected_style == 'short'
35
+ # Convert short to long: Hash<K, V> -> Hash{K => V} or {K => V} -> Hash{K => V}
36
+ convert_to_long(type_string)
37
+ else
38
+ # Convert long to short: Hash{K => V} -> {K => V}
39
+ convert_to_short(type_string)
36
40
  end
37
41
  end
42
+
43
+ # Converts short syntax to long syntax
44
+ # @param type_string [String] the type string
45
+ # @return [String] the converted type string
46
+ def convert_to_long(type_string)
47
+ if type_string.start_with?('{')
48
+ # {K => V} -> Hash{K => V}
49
+ "Hash#{type_string}"
50
+ else
51
+ # Hash<K, V> -> Hash{K => V}
52
+ type_string.gsub(/Hash<(.+?)>/) do
53
+ types = ::Regexp.last_match(1)
54
+ # Split on comma, handle nested types
55
+ "Hash{#{types.sub(/,\s*/, ' => ')}}"
56
+ end
57
+ end
58
+ end
59
+
60
+ # Converts long syntax to short syntax
61
+ # @param type_string [String] the type string
62
+ # @return [String] the converted type string
63
+ def convert_to_short(type_string)
64
+ # Hash{K => V} -> {K => V}
65
+ type_string.sub(/^Hash/, '')
66
+ end
38
67
  end
39
68
  end
40
69
  end
@@ -24,18 +24,19 @@ module Yard
24
24
  location_match = location_line.match(/^(.+):(\d+): (.+)$/)
25
25
  next unless location_match
26
26
 
27
- # Parse details: "tag_name|type_string"
28
- details = details_line.split('|', 2)
29
- next unless details.size == 2
27
+ # Parse details: "tag_name|type_string|detected_style"
28
+ details = details_line.split('|', 3)
29
+ next unless details.size == 3
30
30
 
31
- tag_name, type_string = details
31
+ tag_name, type_string, detected_style = details
32
32
 
33
33
  violations << {
34
34
  location: location_match[1],
35
35
  line: location_match[2].to_i,
36
36
  object_name: location_match[3],
37
37
  tag_name: tag_name,
38
- type_string: type_string
38
+ type_string: type_string,
39
+ detected_style: detected_style
39
40
  }
40
41
  end
41
42
 
@@ -28,12 +28,14 @@ module Yard
28
28
  end
29
29
  end
30
30
 
31
- # YARD query that finds Hash<K, V> syntax in type annotations
31
+ # YARD query that finds incorrect collection syntax based on EnforcedStyle
32
32
  # Format output as two lines per violation:
33
33
  # Line 1: file.rb:LINE: ClassName#method_name
34
- # Line 2: tag_name|type_string
34
+ # Line 2: tag_name|type_string|detected_style
35
35
  # @return [String] YARD query string
36
36
  def query
37
+ style = enforced_style
38
+
37
39
  <<~QUERY.strip
38
40
  '
39
41
  docstring
@@ -43,10 +45,23 @@ module Yard
43
45
  next unless tag.types
44
46
 
45
47
  tag.types.each do |type_str|
46
- # Check for Hash<...> syntax (should be Hash{...})
48
+ detected_style = nil
49
+
50
+ # Check for Hash<...> syntax (angle brackets)
47
51
  if type_str =~ /Hash<.*>/
52
+ detected_style = "short"
53
+ # Check for Hash{...} syntax (curly braces)
54
+ elsif type_str =~ /Hash\\{.*\\}/
55
+ detected_style = "long"
56
+ # Check for {...} syntax without Hash prefix
57
+ elsif type_str =~ /^\\{.*\\}$/
58
+ detected_style = "short"
59
+ end
60
+
61
+ # Report violations based on enforced style
62
+ if detected_style && detected_style != "#{style}"
48
63
  puts object.file + ":" + object.line.to_s + ": " + object.title
49
- puts tag.tag_name + "|" + type_str
64
+ puts tag.tag_name + "|" + type_str + "|" + detected_style
50
65
  break
51
66
  end
52
67
  end
@@ -57,12 +72,16 @@ module Yard
57
72
  QUERY
58
73
  end
59
74
 
75
+ # Gets the enforced collection style from configuration
76
+ # @return [String] 'long' or 'short'
77
+ def enforced_style
78
+ config_or_default('EnforcedStyle')
79
+ end
80
+
60
81
  # Array of tag names to validate, formatted for YARD query
61
82
  # @return [String] Ruby array literal string
62
83
  def validated_tags_array
63
- tags = config.validator_config('Tags/CollectionType', 'ValidatedTags') || %w[
64
- param option return yieldreturn
65
- ]
84
+ tags = config_or_default('ValidatedTags')
66
85
  "[#{tags.map { |t| "\"#{t}\"" }.join(',')}]"
67
86
  end
68
87
  end
@@ -4,8 +4,44 @@ module Yard
4
4
  module Lint
5
5
  module Validators
6
6
  module Tags
7
- # CollectionType validator module
8
- # Detects incorrect Hash collection syntax (Hash<K, V> instead of Hash{K => V})
7
+ # CollectionType validator
8
+ #
9
+ # Enforces correct YARD collection type syntax for Hash types. YARD uses
10
+ # `Hash{K => V}` syntax with curly braces and hash rockets, not the generic
11
+ # `Hash<K, V>` syntax used in other languages and documentation tools.
12
+ # This validator is enabled by default.
13
+ #
14
+ # ## Why This Matters
15
+ #
16
+ # YARD has specific syntax conventions that differ from other documentation tools.
17
+ # Using the correct syntax ensures that YARD can properly parse and display your
18
+ # type annotations in generated documentation.
19
+ #
20
+ # @example Bad - Generic syntax with angle brackets
21
+ # # @param options [Hash<Symbol, String>] configuration options
22
+ # # @param mapping [Hash<String, Integer>] key to value mapping
23
+ # def configure(options, mapping)
24
+ # end
25
+ #
26
+ # @example Good - YARD syntax with curly braces
27
+ # # @param options [Hash{Symbol => String}] configuration options
28
+ # # @param mapping [Hash{String => Integer}] key to value mapping
29
+ # def configure(options, mapping)
30
+ # end
31
+ #
32
+ # @example Good - Arrays use angle brackets (correct for YARD)
33
+ # # @param items [Array<String>] list of items
34
+ # # @param numbers [Array<Integer>] list of numbers
35
+ # def process(items, numbers)
36
+ # end
37
+ #
38
+ # ## Configuration
39
+ #
40
+ # To disable this validator:
41
+ #
42
+ # Tags/CollectionType:
43
+ # Enabled: false
44
+ #
9
45
  module CollectionType
10
46
  end
11
47
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ module ExampleSyntax
8
+ # Configuration for ExampleSyntax validator
9
+ class Config < ::Yard::Lint::Validators::Config
10
+ self.id = :example_syntax
11
+ self.defaults = {
12
+ 'Enabled' => true,
13
+ 'Severity' => 'warning'
14
+ }.freeze
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ 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 ExampleSyntax
8
+ # Builds messages for example syntax offenses
9
+ class MessagesBuilder
10
+ class << self
11
+ # Build message for example syntax offense
12
+ # @param offense [Hash] offense data with :example_name and :error_message keys
13
+ # @return [String] formatted message
14
+ def call(offense)
15
+ example_name = offense[:example_name]
16
+ error_msg = offense[:error_message]
17
+ object_name = offense[:object_name]
18
+
19
+ "Object `#{object_name}` has syntax error in @example " \
20
+ "'#{example_name}': #{error_msg}"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ module ExampleSyntax
8
+ # Parser for @example syntax validation results
9
+ class Parser < Parsers::Base
10
+ # @param yard_output [String] raw yard output with example syntax issues
11
+ # @return [Array<Hash>] array with example syntax violation details
12
+ def call(yard_output)
13
+ return [] if yard_output.nil? || yard_output.empty?
14
+
15
+ lines = yard_output.split("\n").reject(&:empty?)
16
+ results = []
17
+
18
+ # Output format is variable lines per error:
19
+ # 1. file.rb:10: ClassName#method_name
20
+ # 2. syntax_error
21
+ # 3. Example name
22
+ # 4+. Error message (can be multiple lines)
23
+ # Next error starts with another file.rb:line: pattern
24
+
25
+ i = 0
26
+ while i < lines.length
27
+ location_line = lines[i]
28
+
29
+ # Parse location line: "file.rb:10: ClassName#method_name"
30
+ # File paths typically start with a letter or . or / or ~
31
+ match = location_line.match(%r{^([a-zA-Z./~].+):(\d+): (.+)$})
32
+ unless match
33
+ i += 1
34
+ next
35
+ end
36
+
37
+ file = match[1]
38
+ line = match[2].to_i
39
+ object_name = match[3]
40
+
41
+ # Next line should be status
42
+ i += 1
43
+ status_line = lines[i]
44
+ next unless status_line == 'syntax_error'
45
+
46
+ # Next line is example name
47
+ i += 1
48
+ example_name = lines[i]
49
+
50
+ # Collect all remaining lines until we hit the next location line or end
51
+ error_message_lines = []
52
+ i += 1
53
+ while i < lines.length
54
+ # Check if this line starts a new error (matches file:line: pattern)
55
+ # File paths typically start with a letter or . or / or ~
56
+ break if lines[i].match?(%r{^[a-zA-Z./~].+:\d+: .+$})
57
+
58
+ error_message_lines << lines[i]
59
+ i += 1
60
+ end
61
+
62
+ results << {
63
+ name: 'ExampleSyntax',
64
+ object_name: object_name,
65
+ example_name: example_name,
66
+ error_message: error_message_lines.join("\n"),
67
+ location: file,
68
+ line: line
69
+ }
70
+ end
71
+
72
+ results
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ module ExampleSyntax
8
+ # Result object for example syntax validation
9
+ class Result < Results::Base
10
+ self.default_severity = 'warning'
11
+ self.offense_type = 'line'
12
+ self.offense_name = 'ExampleSyntaxError'
13
+
14
+ # Build human-readable message for example syntax offense
15
+ # @param offense [Hash] offense data with :example_name and :error_message keys
16
+ # @return [String] formatted message
17
+ def build_message(offense)
18
+ MessagesBuilder.call(offense)
19
+ end
20
+
21
+ private
22
+
23
+ # Override to build offenses with dynamic names from parsed data
24
+ # @return [Array<Hash>] array of offense hashes
25
+ def build_offenses
26
+ @parsed_data.map do |offense_data|
27
+ {
28
+ severity: configured_severity,
29
+ type: self.class.offense_type,
30
+ name: offense_data[:name] || self.class.offense_name,
31
+ message: build_message(offense_data),
32
+ location: offense_data[:location] || offense_data[:file],
33
+ location_line: offense_data[:line] || offense_data[:location_line] || 0
34
+ }
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end