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.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +150 -1
  3. data/README.md +98 -4
  4. data/Rakefile +20 -0
  5. data/bin/yard-lint +71 -38
  6. data/lib/yard/lint/config.rb +5 -0
  7. data/lib/yard/lint/config_updater.rb +222 -0
  8. data/lib/yard/lint/errors.rb +6 -0
  9. data/lib/yard/lint/executor/in_process_registry.rb +130 -0
  10. data/lib/yard/lint/executor/query_executor.rb +109 -0
  11. data/lib/yard/lint/executor/result_collector.rb +55 -0
  12. data/lib/yard/lint/executor/warning_dispatcher.rb +79 -0
  13. data/lib/yard/lint/results/base.rb +2 -1
  14. data/lib/yard/lint/runner.rb +50 -38
  15. data/lib/yard/lint/templates/default_config.yml +105 -0
  16. data/lib/yard/lint/templates/strict_config.yml +105 -0
  17. data/lib/yard/lint/validators/base.rb +52 -118
  18. data/lib/yard/lint/validators/documentation/blank_line_before_definition/config.rb +25 -0
  19. data/lib/yard/lint/validators/documentation/blank_line_before_definition/messages_builder.rb +39 -0
  20. data/lib/yard/lint/validators/documentation/blank_line_before_definition/parser.rb +59 -0
  21. data/lib/yard/lint/validators/documentation/blank_line_before_definition/result.rb +61 -0
  22. data/lib/yard/lint/validators/documentation/blank_line_before_definition/validator.rb +94 -0
  23. data/lib/yard/lint/validators/documentation/blank_line_before_definition.rb +63 -0
  24. data/lib/yard/lint/validators/documentation/empty_comment_line/config.rb +24 -0
  25. data/lib/yard/lint/validators/documentation/empty_comment_line/messages_builder.rb +34 -0
  26. data/lib/yard/lint/validators/documentation/empty_comment_line/parser.rb +60 -0
  27. data/lib/yard/lint/validators/documentation/empty_comment_line/result.rb +25 -0
  28. data/lib/yard/lint/validators/documentation/empty_comment_line/validator.rb +109 -0
  29. data/lib/yard/lint/validators/documentation/empty_comment_line.rb +58 -0
  30. data/lib/yard/lint/validators/documentation/markdown_syntax/validator.rb +36 -21
  31. data/lib/yard/lint/validators/documentation/markdown_syntax.rb +0 -1
  32. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/validator.rb +19 -29
  33. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods.rb +0 -1
  34. data/lib/yard/lint/validators/documentation/undocumented_method_arguments/validator.rb +18 -34
  35. data/lib/yard/lint/validators/documentation/undocumented_method_arguments.rb +0 -1
  36. data/lib/yard/lint/validators/documentation/undocumented_objects/validator.rb +17 -25
  37. data/lib/yard/lint/validators/documentation/undocumented_objects.rb +4 -5
  38. data/lib/yard/lint/validators/documentation/undocumented_options/validator.rb +30 -21
  39. data/lib/yard/lint/validators/documentation/undocumented_options.rb +0 -1
  40. data/lib/yard/lint/validators/semantic/abstract_methods/result.rb +2 -2
  41. data/lib/yard/lint/validators/semantic/abstract_methods/validator.rb +31 -43
  42. data/lib/yard/lint/validators/semantic/abstract_methods.rb +0 -1
  43. data/lib/yard/lint/validators/tags/api_tags/validator.rb +24 -39
  44. data/lib/yard/lint/validators/tags/api_tags.rb +0 -1
  45. data/lib/yard/lint/validators/tags/collection_type/validator.rb +37 -66
  46. data/lib/yard/lint/validators/tags/collection_type.rb +0 -1
  47. data/lib/yard/lint/validators/tags/example_syntax/validator.rb +51 -64
  48. data/lib/yard/lint/validators/tags/example_syntax.rb +0 -1
  49. data/lib/yard/lint/validators/tags/informal_notation/config.rb +40 -0
  50. data/lib/yard/lint/validators/tags/informal_notation/messages_builder.rb +35 -0
  51. data/lib/yard/lint/validators/tags/informal_notation/parser.rb +55 -0
  52. data/lib/yard/lint/validators/tags/informal_notation/result.rb +26 -0
  53. data/lib/yard/lint/validators/tags/informal_notation/validator.rb +133 -0
  54. data/lib/yard/lint/validators/tags/informal_notation.rb +45 -0
  55. data/lib/yard/lint/validators/tags/invalid_types/validator.rb +57 -70
  56. data/lib/yard/lint/validators/tags/invalid_types.rb +0 -1
  57. data/lib/yard/lint/validators/tags/meaningless_tag/validator.rb +22 -54
  58. data/lib/yard/lint/validators/tags/meaningless_tag.rb +0 -1
  59. data/lib/yard/lint/validators/tags/non_ascii_type/config.rb +21 -0
  60. data/lib/yard/lint/validators/tags/non_ascii_type/messages_builder.rb +29 -0
  61. data/lib/yard/lint/validators/tags/non_ascii_type/parser.rb +59 -0
  62. data/lib/yard/lint/validators/tags/non_ascii_type/result.rb +25 -0
  63. data/lib/yard/lint/validators/tags/non_ascii_type/validator.rb +50 -0
  64. data/lib/yard/lint/validators/tags/non_ascii_type.rb +39 -0
  65. data/lib/yard/lint/validators/tags/option_tags/result.rb +2 -2
  66. data/lib/yard/lint/validators/tags/option_tags/validator.rb +25 -40
  67. data/lib/yard/lint/validators/tags/option_tags.rb +0 -1
  68. data/lib/yard/lint/validators/tags/order/validator.rb +28 -55
  69. data/lib/yard/lint/validators/tags/order.rb +0 -1
  70. data/lib/yard/lint/validators/tags/redundant_param_description/config.rb +15 -1
  71. data/lib/yard/lint/validators/tags/redundant_param_description/messages_builder.rb +5 -0
  72. data/lib/yard/lint/validators/tags/redundant_param_description/validator.rb +134 -100
  73. data/lib/yard/lint/validators/tags/redundant_param_description.rb +0 -1
  74. data/lib/yard/lint/validators/tags/tag_group_separator/config.rb +29 -0
  75. data/lib/yard/lint/validators/tags/tag_group_separator/messages_builder.rb +49 -0
  76. data/lib/yard/lint/validators/tags/tag_group_separator/parser.rb +67 -0
  77. data/lib/yard/lint/validators/tags/tag_group_separator/result.rb +28 -0
  78. data/lib/yard/lint/validators/tags/tag_group_separator/validator.rb +117 -0
  79. data/lib/yard/lint/validators/tags/tag_group_separator.rb +49 -0
  80. data/lib/yard/lint/validators/tags/tag_type_position/validator.rb +53 -84
  81. data/lib/yard/lint/validators/tags/tag_type_position.rb +0 -1
  82. data/lib/yard/lint/validators/tags/type_syntax/parser.rb +7 -2
  83. data/lib/yard/lint/validators/tags/type_syntax/validator.rb +29 -59
  84. data/lib/yard/lint/validators/tags/type_syntax.rb +0 -1
  85. data/lib/yard/lint/validators/warnings/duplicated_parameter_name/validator.rb +1 -18
  86. data/lib/yard/lint/validators/warnings/invalid_directive_format/validator.rb +1 -18
  87. data/lib/yard/lint/validators/warnings/invalid_tag_format/validator.rb +1 -18
  88. data/lib/yard/lint/validators/warnings/unknown_directive/validator.rb +1 -18
  89. data/lib/yard/lint/validators/warnings/unknown_parameter_name/messages_builder.rb +243 -0
  90. data/lib/yard/lint/validators/warnings/unknown_parameter_name/result.rb +4 -3
  91. data/lib/yard/lint/validators/warnings/unknown_parameter_name/validator.rb +1 -18
  92. data/lib/yard/lint/validators/warnings/unknown_tag/messages_builder.rb +144 -0
  93. data/lib/yard/lint/validators/warnings/unknown_tag/result.rb +4 -3
  94. data/lib/yard/lint/validators/warnings/unknown_tag/validator.rb +1 -18
  95. data/lib/yard/lint/validators/warnings/unknown_tag.rb +10 -0
  96. data/lib/yard/lint/version.rb +1 -1
  97. data/lib/yard/lint.rb +81 -13
  98. data/renovate.json +1 -8
  99. metadata +38 -2
  100. data/lib/yard/lint/command_cache.rb +0 -93
@@ -7,78 +7,65 @@ module Yard
7
7
  module ExampleSyntax
8
8
  # Validator to check syntax of code in @example tags
9
9
  class Validator < Base
10
- private
10
+ # Enable in-process execution with all visibility
11
+ in_process visibility: :all
11
12
 
12
- # Runs yard list query to find objects with invalid syntax in @example tags
13
- # @param dir [String] dir where the yard db is (or where it should be generated)
14
- # @param file_list_path [String] path to temp file containing file paths (one per line)
15
- # @return [Hash] shell command execution hash results
16
- def yard_cmd(dir, file_list_path)
17
- cmd = <<~CMD
18
- cat #{Shellwords.escape(file_list_path)} | xargs yard list \
19
- --private \
20
- --protected \
21
- -b #{Shellwords.escape(dir)}
22
- CMD
23
- cmd = cmd.tr("\n", ' ')
24
- cmd = cmd.gsub('yard list', "yard list --query #{query}")
13
+ # Execute query for a single object during in-process execution.
14
+ # Checks syntax of code in @example tags.
15
+ # @param object [YARD::CodeObjects::Base] the code object to query
16
+ # @param collector [Executor::ResultCollector] collector for output
17
+ # @return [void]
18
+ def in_process_query(object, collector)
19
+ return unless object.has_tag?(:example)
25
20
 
26
- shell(cmd)
27
- end
28
-
29
- # @return [String] yard query to find @example tags with syntax errors
30
- def query
31
- <<~QUERY
32
- '
33
- if object.has_tag?(:example)
34
- example_tags = object.tags(:example)
21
+ example_tags = object.tags(:example)
35
22
 
36
- example_tags.each_with_index do |example, index|
37
- code = example.text
38
- next if code.nil? || code.empty?
23
+ example_tags.each_with_index do |example, index|
24
+ code = example.text
25
+ next if code.nil? || code.empty?
39
26
 
40
- # Clean the code: strip output indicators (#=>) and everything after it
41
- code_lines = code.split("\\n").map do |line|
42
- line.sub(/\\s*#\\s*=>.*$/, "")
43
- end
27
+ # Clean the code: strip output indicators (#=>) and everything after it
28
+ code_lines = code.split("\n").map do |line|
29
+ line.sub(/\s*#\s*=>.*$/, '')
30
+ end
44
31
 
45
- cleaned_code = code_lines.join("\\n").strip
46
- next if cleaned_code.empty?
32
+ cleaned_code = code_lines.join("\n").strip
33
+ next if cleaned_code.empty?
47
34
 
48
- # Check if code looks incomplete (single expression without context)
49
- # Skip validation for these cases
50
- lines = cleaned_code.split("\\n").reject { |l| l.strip.empty? || l.strip.start_with?("#") }
35
+ # Check if code looks incomplete (single expression without context)
36
+ lines = cleaned_code.split("\n").reject { |l| l.strip.empty? || l.strip.start_with?('#') }
51
37
 
52
- # Skip if it is a single line that looks like an incomplete expression
53
- if lines.size == 1
54
- line = lines.first.strip
55
- # Skip method calls, variable references, or simple expressions
56
- # These are likely incomplete snippets showing usage
57
- if line.match?(/^[a-z_][a-z0-9_]*(\\.| |$)/) || line.match?(/^[A-Z]/) && !line.match?(/^(class|module|def)\\s/)
58
- next
59
- end
60
- end
38
+ # Skip if it is a single line that looks like an incomplete expression
39
+ if lines.size == 1
40
+ line = lines.first.strip
41
+ # Skip method calls, variable references, or simple expressions
42
+ next if line.match?(/^[a-z_][a-z0-9_]*(\.| |$)/) ||
43
+ (line.match?(/^[A-Z]/) && !line.match?(/^(class|module|def)\s/))
44
+ end
61
45
 
62
- # Try to parse the code
63
- begin
64
- RubyVM::InstructionSequence.compile(cleaned_code)
65
- rescue SyntaxError => e
66
- # Report syntax errors
67
- example_name = example.name || "Example " + (index + 1).to_s
68
- puts object.file + ":" + object.line.to_s + ": " + object.title
69
- puts "syntax_error"
70
- puts example_name
71
- puts e.message
72
- rescue => e
73
- # Other errors (like NameError, ArgumentError) are fine
74
- # We only check syntax, not semantics
75
- next
76
- end
77
- end
78
- end
79
- false
80
- '
81
- QUERY
46
+ # Try to parse the code
47
+ # Suppress Ruby parser warnings (like "... at EOL, should be parenthesized?")
48
+ # that can occur during compilation of valid but stylistically questionable code
49
+ original_verbose = $VERBOSE
50
+ begin
51
+ $VERBOSE = nil
52
+ RubyVM::InstructionSequence.compile(cleaned_code)
53
+ rescue SyntaxError => e
54
+ example_name = example.name || "Example #{index + 1}"
55
+ collector.puts "#{object.file}:#{object.line}: #{object.title}"
56
+ collector.puts 'syntax_error'
57
+ collector.puts example_name
58
+ collector.puts e.message
59
+ rescue ScriptError, EncodingError => e
60
+ # Non-syntax script errors (LoadError, NotImplementedError) and encoding
61
+ # issues should be logged but not reported as syntax errors.
62
+ # We only validate syntax, not runtime semantics or encoding validity.
63
+ warn "[YARD::Lint] Example code error in #{object.path}: #{e.class}: #{e.message}" if ENV['DEBUG']
64
+ next
65
+ ensure
66
+ $VERBOSE = original_verbose
67
+ end
68
+ end
82
69
  end
83
70
  end
84
71
  end
@@ -33,7 +33,6 @@ module Yard
33
33
  #
34
34
  # Tags/ExampleSyntax:
35
35
  # Enabled: false
36
- #
37
36
  module ExampleSyntax
38
37
  end
39
38
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ module InformalNotation
8
+ # Configuration for InformalNotation validator
9
+ class Config < ::Yard::Lint::Validators::Config
10
+ self.id = :informal_notation
11
+ self.defaults = {
12
+ 'Enabled' => true,
13
+ 'Severity' => 'warning',
14
+ 'CaseSensitive' => false,
15
+ 'RequireStartOfLine' => true,
16
+ 'Patterns' => {
17
+ 'Note' => '@note',
18
+ 'IMPORTANT' => '@note',
19
+ 'Important' => '@note',
20
+ 'Todo' => '@todo',
21
+ 'TODO' => '@todo',
22
+ 'FIXME' => '@todo',
23
+ 'See' => '@see',
24
+ 'See also' => '@see',
25
+ 'Warning' => '@deprecated',
26
+ 'Deprecated' => '@deprecated',
27
+ 'Author' => '@author',
28
+ 'Version' => '@version',
29
+ 'Since' => '@since',
30
+ 'Returns' => '@return',
31
+ 'Raises' => '@raise',
32
+ 'Example' => '@example'
33
+ }
34
+ }.freeze
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ module InformalNotation
8
+ # Builds human-readable messages for InformalNotation violations
9
+ class MessagesBuilder
10
+ class << self
11
+ # Formats an informal notation violation message
12
+ # @param offense [Hash] offense details with :pattern, :replacement, :line_text keys
13
+ # @return [String] formatted message
14
+ def call(offense)
15
+ pattern = offense[:pattern]
16
+ replacement = offense[:replacement]
17
+ line_text = offense[:line_text]
18
+
19
+ message = "Use #{replacement} tag instead of '#{pattern}:' notation"
20
+
21
+ if line_text && !line_text.empty?
22
+ # Truncate long lines for readability
23
+ truncated = line_text.length > 60 ? "#{line_text[0..57]}..." : line_text
24
+ message += ". Found: \"#{truncated}\""
25
+ end
26
+
27
+ message
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ module InformalNotation
8
+ # Parser for InformalNotation validator output
9
+ # Parses YARD query output that reports informal notation violations
10
+ class Parser < ::Yard::Lint::Parsers::Base
11
+ # Parses YARD output and extracts informal notation violations
12
+ # Expected format (two lines per violation):
13
+ # file.rb:LINE: ObjectName
14
+ # pattern|replacement|line_offset|line_text
15
+ # @param yard_output [String] raw YARD query results
16
+ # @option _kwargs [Object] :unused this parameter accepts no options (reserved for future use)
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: ObjectName"
28
+ location_match = location_line.match(/^(.+):(\d+): (.+)$/)
29
+ next unless location_match
30
+
31
+ # Parse details: "pattern|replacement|line_offset|line_text"
32
+ details = details_line.split('|', 4)
33
+ next unless details.size >= 3
34
+
35
+ pattern, replacement, line_offset_str, line_text = details
36
+
37
+ violations << {
38
+ location: location_match[1],
39
+ line: location_match[2].to_i,
40
+ object_name: location_match[3],
41
+ pattern: pattern,
42
+ replacement: replacement,
43
+ line_offset: line_offset_str.to_i,
44
+ line_text: line_text || ''
45
+ }
46
+ end
47
+
48
+ violations
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ 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 InformalNotation
8
+ # Result wrapper for InformalNotation validator
9
+ # Formats parsed violations into offense objects
10
+ class Result < Results::Base
11
+ self.default_severity = 'warning'
12
+ self.offense_type = 'line'
13
+ self.offense_name = 'InformalNotation'
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
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ module InformalNotation
8
+ # Validates that documentation uses proper YARD tags instead of
9
+ # informal notation patterns like "Note:", "TODO:", etc.
10
+ class Validator < Base
11
+ # Enable in-process execution with public visibility
12
+ in_process visibility: :public
13
+
14
+ # Execute query for a single object during in-process execution.
15
+ # Checks for informal notation patterns in docstrings.
16
+ # @param object [YARD::CodeObjects::Base] the code object to query
17
+ # @param collector [Executor::ResultCollector] collector for output
18
+ # @return [void]
19
+ def in_process_query(object, collector)
20
+ docstring_text = object.docstring.to_s
21
+ return if docstring_text.empty?
22
+
23
+ patterns = config_patterns
24
+ case_sensitive = config_case_sensitive
25
+ require_start = config_require_start_of_line
26
+
27
+ found_patterns = find_informal_patterns(
28
+ docstring_text,
29
+ patterns,
30
+ case_sensitive,
31
+ require_start
32
+ )
33
+
34
+ return if found_patterns.empty?
35
+
36
+ # Output format: two lines per violation
37
+ # Line 1: file:line: object_title
38
+ # Line 2: pattern|replacement|line_offset|line_text (pipe-separated)
39
+ found_patterns.each do |match|
40
+ collector.puts "#{object.file}:#{object.line}: #{object.title}"
41
+ collector.puts "#{match[:pattern]}|#{match[:replacement]}|" \
42
+ "#{match[:line_offset]}|#{match[:line_text]}"
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ # Find informal patterns in docstring text, skipping code blocks
49
+ # @param docstring_text [String] the docstring text to scan
50
+ # @param patterns [Hash] pattern => replacement mapping
51
+ # @param case_sensitive [Boolean] whether to match case-sensitively
52
+ # @param require_start [Boolean] whether pattern must be at start of line
53
+ # @return [Array<Hash>] array of matches with pattern, replacement, line_offset, line_text
54
+ def find_informal_patterns(docstring_text, patterns, case_sensitive, require_start)
55
+ found_patterns = []
56
+ matched_lines = Set.new
57
+ in_code_block = false
58
+
59
+ docstring_text.lines.each_with_index do |line, line_offset|
60
+ # Track fenced code blocks (``` markers)
61
+ if line.strip.start_with?('```')
62
+ in_code_block = !in_code_block
63
+ next
64
+ end
65
+
66
+ # Skip lines inside code blocks
67
+ next if in_code_block
68
+
69
+ # Skip lines already matched (avoids duplicate reports for similar patterns)
70
+ next if matched_lines.include?(line_offset)
71
+
72
+ patterns.each do |pattern, replacement|
73
+ next if replacement.nil? || replacement.empty?
74
+ next unless matches_pattern?(line, pattern, case_sensitive, require_start)
75
+
76
+ found_patterns << {
77
+ pattern: pattern,
78
+ replacement: replacement,
79
+ line_offset: line_offset,
80
+ line_text: line.strip
81
+ }
82
+ matched_lines.add(line_offset)
83
+ break
84
+ end
85
+ end
86
+
87
+ found_patterns
88
+ end
89
+
90
+ # Check if a line matches the informal pattern
91
+ # @param line [String] the line to check
92
+ # @param pattern [String] the pattern to match (without colon)
93
+ # @param case_sensitive [Boolean] whether to match case-sensitively
94
+ # @param require_start [Boolean] whether pattern must be at start of line
95
+ # @return [Boolean] true if the line matches
96
+ def matches_pattern?(line, pattern, case_sensitive, require_start)
97
+ # Build regex for pattern followed by colon
98
+ escaped_pattern = Regexp.escape(pattern)
99
+
100
+ regex_str = if require_start
101
+ # Match at start of line after optional whitespace
102
+ "^\\s*#{escaped_pattern}:"
103
+ else
104
+ # Match anywhere in line (after start or whitespace)
105
+ "(?:^|\\s)#{escaped_pattern}:"
106
+ end
107
+
108
+ flags = case_sensitive ? nil : Regexp::IGNORECASE
109
+ regex = Regexp.new(regex_str, flags)
110
+
111
+ line.match?(regex)
112
+ end
113
+
114
+ # @return [Hash] configured patterns mapping informal -> YARD tag
115
+ def config_patterns
116
+ config_or_default('Patterns')
117
+ end
118
+
119
+ # @return [Boolean] whether matching should be case-sensitive
120
+ def config_case_sensitive
121
+ config_or_default('CaseSensitive')
122
+ end
123
+
124
+ # @return [Boolean] whether patterns must appear at start of line
125
+ def config_require_start_of_line
126
+ config_or_default('RequireStartOfLine')
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ # InformalNotation validator
8
+ #
9
+ # Detects informal notation patterns in YARD documentation comments
10
+ # and suggests proper YARD tags. For example, "Note:" should use @note,
11
+ # "TODO:" should use @todo, etc.
12
+ #
13
+ # ## Configuration
14
+ #
15
+ # To customize which patterns are detected:
16
+ #
17
+ # ```yaml
18
+ # Tags/InformalNotation:
19
+ # Enabled: true
20
+ # CaseSensitive: false
21
+ # RequireStartOfLine: true
22
+ # Patterns:
23
+ # Note: '@note'
24
+ # TODO: '@todo'
25
+ # ```
26
+ #
27
+ # @example Good - Using proper YARD tags
28
+ # # Calculate the sum of values
29
+ # # @note This method is slow for large arrays
30
+ # # @todo Optimize for performance
31
+ # def sum(values)
32
+ # end
33
+ #
34
+ # @example Bad - Using informal notation
35
+ # # Calculate the sum of values
36
+ # # Note: This method is slow for large arrays
37
+ # # TODO: Optimize for performance
38
+ # def sum(values)
39
+ # end
40
+ module InformalNotation
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -9,94 +9,81 @@ module Yard
9
9
  # By invalid we mean, that they are not classes nor any of the allowed defaults or
10
10
  # exclusions.
11
11
  class Validator < Base
12
+ # Enable in-process execution with all visibility
13
+ in_process visibility: :all
14
+
12
15
  # All non-class yard types that are considered valid
13
16
  ALLOWED_DEFAULTS = %w[
14
17
  false
15
18
  true
16
19
  nil
17
20
  self
18
- vold
21
+ void
19
22
  ].freeze
20
23
 
21
24
  private_constant :ALLOWED_DEFAULTS
22
25
 
23
- private
26
+ # Execute query for a single object during in-process execution.
27
+ # Checks for invalid type definitions in tags.
28
+ # @param object [YARD::CodeObjects::Base] the code object to query
29
+ # @param collector [Executor::ResultCollector] collector for output
30
+ # @return [void]
31
+ def in_process_query(object, collector)
32
+ checked_tags = config_or_default('ValidatedTags')
33
+ extra_types = config_or_default('ExtraTypes')
34
+ allowed_types = ALLOWED_DEFAULTS + extra_types
24
35
 
25
- # Runs yard list query with proper settings on a given dir and files
26
- # @param dir [String] dir where the yard db is (or where it should be generated)
27
- # @param file_list_path [String] path to temp file containing file paths (one per line)
28
- # @return [Hash] shell command execution hash results
29
- def yard_cmd(dir, file_list_path)
30
- # Write query to a temporary file to avoid shell escaping issues
31
- squery = Shellwords.escape(query)
32
- cmd = "cat #{Shellwords.escape(file_list_path)} | xargs yard list --query #{squery} "
36
+ # Sanitize type string (remove type syntax characters)
37
+ sanitize = ->(type) { type.tr('=><>,{} ', '') }
33
38
 
34
- Tempfile.create(['yard_query', '.sh']) do |f|
35
- f.write("#!/bin/bash\n")
36
- f.write(cmd)
37
- f.write("--private --protected -b #{Shellwords.escape(dir)}\n")
38
- f.flush
39
- f.chmod(0o755)
39
+ # Check for invalid types
40
+ invalid_types = object.docstring.tags
41
+ .select { |tag| checked_tags.include?(tag.tag_name) }
42
+ .flat_map(&:types)
43
+ .compact
44
+ .uniq
45
+ .map(&sanitize)
46
+ .reject { |type| allowed_types.include?(type) }
47
+ .reject { |type| type_defined?(type) }
48
+ .reject { |type| type.include?('#') }
40
49
 
41
- shell("bash #{Shellwords.escape(f.path)}")
42
- end
43
- end
44
-
45
- # @return [String] multiline yard query that we use to find methods with
46
- # tags with invalid types definitions
47
- def query
48
- <<-QUERY
49
- '
50
- sanitize = ->(type) do
51
- type
52
- .tr('=>', '')
53
- .tr('<', '')
54
- .tr('>', '')
55
- .tr(' ', '')
56
- .tr(',', '')
57
- .tr('{', '')
58
- .tr('}', '')
59
- end
50
+ return if invalid_types.empty?
60
51
 
61
- docstring
62
- .tags
63
- .select { |tag| #{checked_tags_names}.include?(tag.tag_name) }
64
- .map(&:types)
65
- .flatten
66
- .uniq
67
- .compact
68
- .map(&sanitize)
69
- .reject { |type| #{allowed_types_code}.include?(type) }
70
- .reject { |type| !(Kernel.const_defined?(type) rescue nil).nil? }
71
- .reject { |type| type.include?('#') }
72
- .then { |types| !types.empty? }
73
- '
74
- QUERY
52
+ collector.puts "#{object.file}:#{object.line}: #{object.title}"
75
53
  end
76
54
 
77
- # @return [String] tags names for which we want to check the invalid tags
78
- # types definitions
79
- def checked_tags_names
80
- validated_tags = config_or_default('ValidatedTags')
81
- query_array(validated_tags)
82
- end
55
+ private
83
56
 
84
- # @return [String] extra names that we allow for types definitions in a yard
85
- # query acceptable form
86
- def allowed_types_code
87
- extra_types = config_or_default('ExtraTypes')
88
- query_array(ALLOWED_DEFAULTS + extra_types)
89
- end
57
+ # Check if a type is defined in Ruby runtime or YARD registry
58
+ # In in-process mode, parsed classes are in YARD registry but not loaded into Ruby
59
+ # @param type [String] type name to check
60
+ # @return [Boolean] true if type is defined (or at least recognized as a valid type)
61
+ def type_defined?(type)
62
+ # Symbol types like :foo are valid YARD documentation notations
63
+ # They document that a method accepts specific symbol values
64
+ return true if type.start_with?(':')
90
65
 
91
- # @param elements [Array<String>] array of elements that we want to convert into
92
- # a string ruby yard query array form
93
- # @return [String] array of elements for yard query converted into a string
94
- def query_array(elements)
95
- "
96
- [
97
- #{elements.map { |type| "'#{type}'" }.join(',')}
98
- ]
99
- "
66
+ # Check Ruby runtime first
67
+ # The shell query uses: !(Kernel.const_defined?(type) rescue nil).nil?
68
+ # This means: if const_defined? returns ANY value (true or false, not nil),
69
+ # the type is considered "recognized" and should not be flagged as invalid.
70
+ # This allows common types like "Boolean" which aren't actual Ruby classes
71
+ # but are still recognized by Ruby as valid constant names to check.
72
+ begin
73
+ const_result = Kernel.const_defined?(type)
74
+ rescue NameError
75
+ # Invalid constant name syntax (e.g., "foo<bar>" or names with special chars)
76
+ # These aren't valid Ruby constants, so we can't check them this way
77
+ const_result = nil
78
+ end
79
+ return true unless const_result.nil?
80
+
81
+ # Check YARD registry (for classes defined in parsed files)
82
+ # This may fail for malformed type strings or registry issues
83
+ !YARD::Registry.resolve(nil, type).nil?
84
+ rescue NameError
85
+ # Type couldn't be resolved - it's not defined
86
+ false
100
87
  end
101
88
  end
102
89
  end
@@ -28,7 +28,6 @@ module Yard
28
28
  #
29
29
  # Tags/InvalidTypes:
30
30
  # Enabled: false
31
- #
32
31
  module InvalidTypes
33
32
  end
34
33
  end