yard-lint 0.2.1 → 1.1.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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +73 -1
  3. data/README.md +121 -89
  4. data/bin/yard-lint +44 -0
  5. data/lib/yard/lint/config.rb +35 -3
  6. data/lib/yard/lint/config_generator.rb +191 -0
  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 +77 -12
  10. data/lib/yard/lint/validators/documentation/markdown_syntax/config.rb +20 -0
  11. data/lib/yard/lint/validators/documentation/markdown_syntax/messages_builder.rb +44 -0
  12. data/lib/yard/lint/validators/documentation/markdown_syntax/parser.rb +53 -0
  13. data/lib/yard/lint/validators/documentation/markdown_syntax/result.rb +25 -0
  14. data/lib/yard/lint/validators/documentation/markdown_syntax/validator.rb +38 -0
  15. data/lib/yard/lint/validators/documentation/markdown_syntax.rb +37 -0
  16. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/validator.rb +7 -6
  17. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods.rb +26 -1
  18. data/lib/yard/lint/validators/documentation/undocumented_method_arguments/validator.rb +4 -5
  19. data/lib/yard/lint/validators/documentation/undocumented_method_arguments.rb +26 -1
  20. data/lib/yard/lint/validators/documentation/undocumented_objects/config.rb +2 -1
  21. data/lib/yard/lint/validators/documentation/undocumented_objects/parser.rb +95 -5
  22. data/lib/yard/lint/validators/documentation/undocumented_objects/validator.rb +15 -11
  23. data/lib/yard/lint/validators/documentation/undocumented_objects.rb +131 -2
  24. data/lib/yard/lint/validators/documentation/undocumented_options/config.rb +20 -0
  25. data/lib/yard/lint/validators/documentation/undocumented_options/parser.rb +53 -0
  26. data/lib/yard/lint/validators/documentation/undocumented_options/result.rb +29 -0
  27. data/lib/yard/lint/validators/documentation/undocumented_options/validator.rb +38 -0
  28. data/lib/yard/lint/validators/documentation/undocumented_options.rb +40 -0
  29. data/lib/yard/lint/validators/semantic/abstract_methods/validator.rb +4 -5
  30. data/lib/yard/lint/validators/semantic/abstract_methods.rb +31 -1
  31. data/lib/yard/lint/validators/tags/api_tags/validator.rb +4 -5
  32. data/lib/yard/lint/validators/tags/api_tags.rb +34 -1
  33. data/lib/yard/lint/validators/tags/collection_type/config.rb +22 -0
  34. data/lib/yard/lint/validators/tags/collection_type/messages_builder.rb +73 -0
  35. data/lib/yard/lint/validators/tags/collection_type/parser.rb +50 -0
  36. data/lib/yard/lint/validators/tags/collection_type/result.rb +25 -0
  37. data/lib/yard/lint/validators/tags/collection_type/validator.rb +92 -0
  38. data/lib/yard/lint/validators/tags/collection_type.rb +50 -0
  39. data/lib/yard/lint/validators/tags/example_syntax/config.rb +20 -0
  40. data/lib/yard/lint/validators/tags/example_syntax/messages_builder.rb +28 -0
  41. data/lib/yard/lint/validators/tags/example_syntax/parser.rb +79 -0
  42. data/lib/yard/lint/validators/tags/example_syntax/result.rb +42 -0
  43. data/lib/yard/lint/validators/tags/example_syntax/validator.rb +88 -0
  44. data/lib/yard/lint/validators/tags/example_syntax.rb +42 -0
  45. data/lib/yard/lint/validators/tags/invalid_types/validator.rb +8 -8
  46. data/lib/yard/lint/validators/tags/invalid_types.rb +25 -1
  47. data/lib/yard/lint/validators/tags/meaningless_tag/config.rb +22 -0
  48. data/lib/yard/lint/validators/tags/meaningless_tag/messages_builder.rb +28 -0
  49. data/lib/yard/lint/validators/tags/meaningless_tag/parser.rb +53 -0
  50. data/lib/yard/lint/validators/tags/meaningless_tag/result.rb +26 -0
  51. data/lib/yard/lint/validators/tags/meaningless_tag/validator.rb +82 -0
  52. data/lib/yard/lint/validators/tags/meaningless_tag.rb +43 -0
  53. data/lib/yard/lint/validators/tags/option_tags/validator.rb +11 -6
  54. data/lib/yard/lint/validators/tags/option_tags.rb +26 -1
  55. data/lib/yard/lint/validators/tags/order/validator.rb +4 -5
  56. data/lib/yard/lint/validators/tags/order.rb +25 -1
  57. data/lib/yard/lint/validators/tags/redundant_param_description/config.rb +33 -0
  58. data/lib/yard/lint/validators/tags/redundant_param_description/messages_builder.rb +61 -0
  59. data/lib/yard/lint/validators/tags/redundant_param_description/parser.rb +67 -0
  60. data/lib/yard/lint/validators/tags/redundant_param_description/result.rb +25 -0
  61. data/lib/yard/lint/validators/tags/redundant_param_description/validator.rb +148 -0
  62. data/lib/yard/lint/validators/tags/redundant_param_description.rb +168 -0
  63. data/lib/yard/lint/validators/tags/tag_type_position/config.rb +22 -0
  64. data/lib/yard/lint/validators/tags/tag_type_position/messages_builder.rb +38 -0
  65. data/lib/yard/lint/validators/tags/tag_type_position/parser.rb +51 -0
  66. data/lib/yard/lint/validators/tags/tag_type_position/result.rb +25 -0
  67. data/lib/yard/lint/validators/tags/tag_type_position/validator.rb +111 -0
  68. data/lib/yard/lint/validators/tags/tag_type_position.rb +51 -0
  69. data/lib/yard/lint/validators/tags/type_syntax/config.rb +21 -0
  70. data/lib/yard/lint/validators/tags/type_syntax/messages_builder.rb +27 -0
  71. data/lib/yard/lint/validators/tags/type_syntax/parser.rb +54 -0
  72. data/lib/yard/lint/validators/tags/type_syntax/result.rb +25 -0
  73. data/lib/yard/lint/validators/tags/type_syntax/validator.rb +76 -0
  74. data/lib/yard/lint/validators/tags/type_syntax.rb +38 -0
  75. data/lib/yard/lint/validators/warnings/duplicated_parameter_name/validator.rb +4 -5
  76. data/lib/yard/lint/validators/warnings/duplicated_parameter_name.rb +26 -1
  77. data/lib/yard/lint/validators/warnings/invalid_directive_format/validator.rb +4 -5
  78. data/lib/yard/lint/validators/warnings/invalid_directive_format.rb +26 -1
  79. data/lib/yard/lint/validators/warnings/invalid_tag_format/validator.rb +4 -5
  80. data/lib/yard/lint/validators/warnings/invalid_tag_format.rb +25 -1
  81. data/lib/yard/lint/validators/warnings/unknown_directive/validator.rb +4 -5
  82. data/lib/yard/lint/validators/warnings/unknown_directive.rb +26 -1
  83. data/lib/yard/lint/validators/warnings/unknown_parameter_name/parser.rb +4 -1
  84. data/lib/yard/lint/validators/warnings/unknown_parameter_name/validator.rb +4 -5
  85. data/lib/yard/lint/validators/warnings/unknown_parameter_name.rb +23 -1
  86. data/lib/yard/lint/validators/warnings/unknown_tag/validator.rb +4 -5
  87. data/lib/yard/lint/validators/warnings/unknown_tag.rb +26 -1
  88. data/lib/yard/lint/version.rb +1 -1
  89. data/lib/yard/lint.rb +1 -0
  90. data/misc/logo.png +0 -0
  91. metadata +64 -1
@@ -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
@@ -4,8 +4,137 @@ module Yard
4
4
  module Lint
5
5
  module Validators
6
6
  module Documentation
7
- # UndocumentedObjects validator module
8
- # This validator checks for missing documentation on objects
7
+ # UndocumentedObjects validator
8
+ #
9
+ # Checks for missing documentation on classes, modules, and methods.
10
+ # This validator supports flexible method exclusions through the `ExcludedMethods`
11
+ # configuration option.
12
+ #
13
+ # ## Pattern Types
14
+ #
15
+ # The `ExcludedMethods` feature supports three pattern types for maximum flexibility:
16
+ #
17
+ # ### 1. Exact Name Matching
18
+ #
19
+ # Excludes methods with the specified name, regardless of arity:
20
+ #
21
+ # ExcludedMethods:
22
+ # - 'to_s' # Excludes ALL to_s methods regardless of parameters
23
+ # - 'inspect' # Excludes ALL inspect methods
24
+ #
25
+ # Note: Exact name matching excludes the method with **any arity**. If you need
26
+ # arity-specific exclusions, use arity notation instead.
27
+ #
28
+ # ### 2. Arity Notation (method_name/N)
29
+ #
30
+ # Excludes methods with specific parameter counts:
31
+ #
32
+ # ExcludedMethods:
33
+ # - 'initialize/0' # Only excludes initialize with NO parameters (default)
34
+ # - 'call/1' # Only excludes call methods with exactly 1 parameter
35
+ # - 'initialize/2' # Only excludes initialize with exactly 2 parameters
36
+ #
37
+ # Note: Arity counts total parameters (required + optional) excluding splat (*)
38
+ # and block (&) parameters.
39
+ #
40
+ # ### 3. Regex Patterns
41
+ #
42
+ # Excludes methods matching a regular expression:
43
+ #
44
+ # ExcludedMethods:
45
+ # - '/^_/' # Excludes all methods starting with underscore (private convention)
46
+ # - '/^test_/' # Excludes all test methods
47
+ # - '/_(helper|util)$/' # Excludes methods ending with _helper or _util
48
+ #
49
+ # ## Configuration Examples
50
+ #
51
+ # ### Minimal setup - Only exclude parameter-less initialize
52
+ #
53
+ # Documentation/UndocumentedObjects:
54
+ # ExcludedMethods:
55
+ # - 'initialize/0'
56
+ #
57
+ # ### Common Rails/Ruby patterns
58
+ #
59
+ # Documentation/UndocumentedObjects:
60
+ # ExcludedMethods:
61
+ # - 'initialize/0' # Parameter-less constructors
62
+ # - '/^_/' # Private methods (by convention)
63
+ # - 'to_s' # String conversion
64
+ # - 'inspect' # Object inspection
65
+ # - 'hash' # Hash code generation
66
+ # - 'eql?' # Equality comparison
67
+ # - '==' # Binary equality operator
68
+ # - '<=>' # Spaceship operator (comparison)
69
+ # - '+' # Addition operator
70
+ # - '-' # Subtraction operator
71
+ # - '+@' # Unary plus operator
72
+ # - '-@' # Unary minus operator
73
+ #
74
+ # ### Test framework exclusions
75
+ #
76
+ # Documentation/UndocumentedObjects:
77
+ # ExcludedMethods:
78
+ # - '/^test_/' # Minitest methods
79
+ # - '/^should_/' # Shoulda methods
80
+ # - 'setup/0' # Setup with no params
81
+ # - 'teardown/0' # Teardown with no params
82
+ #
83
+ # ## Pattern Validation & Edge Cases
84
+ #
85
+ # The `ExcludedMethods` feature includes robust validation and error handling:
86
+ #
87
+ # **Automatic Pattern Sanitization:**
88
+ # - **Nil values** are automatically removed
89
+ # - **Empty strings** and whitespace-only patterns are filtered out
90
+ # - **Whitespace trimming** is applied to all patterns
91
+ # - **Empty regex patterns** (`//`) are rejected (would match everything)
92
+ # - **Non-array values** are automatically converted to arrays
93
+ #
94
+ # **Invalid Pattern Handling:**
95
+ # - **Invalid regex patterns** (e.g., `/[/`, `/(unclosed`) are silently skipped without crashing
96
+ # - **Invalid arity notation** (e.g., `method/abc`, `method/`) is silently skipped
97
+ # - **Pattern matching is case-sensitive** for both exact names and regex
98
+ #
99
+ # **Operator Method Support:**
100
+ # YARD-Lint fully supports Ruby operator methods including:
101
+ # - Binary operators: `+`, `-`, `*`, `/`, `%`, `**`, `==`, `!=`, `===`, `<`, `>`,
102
+ # `<=`, `>=`, `<=>`, `&`, `|`, `^`, `<<`, `>>`
103
+ # - Unary operators: `+@`, `-@`, `!`, `~`
104
+ # - Other special methods: `[]`, `[]=`, `=~`
105
+ #
106
+ # **Pattern Matching Behavior:**
107
+ # - **Any match excludes**: If a method matches any pattern, it is excluded from validation
108
+ # - **Patterns are evaluated in order** as defined in the configuration
109
+ # - **Exact names have no arity restriction**: `'initialize'` excludes all initialize
110
+ # methods, regardless of parameters
111
+ # - **Arity notation is strict**: `'initialize/0'` only excludes initialize with
112
+ # exactly 0 parameters
113
+ #
114
+ # ## Troubleshooting
115
+ #
116
+ # ### Methods still showing as undocumented
117
+ #
118
+ # 1. Verify the method name matches exactly (case-sensitive)
119
+ # 2. Check if you're using arity notation - ensure the arity count is correct
120
+ # 3. For regex patterns, test your regex independently to ensure it matches
121
+ # 4. Remember: Arity counts `required + optional` parameters, **excluding**
122
+ # splat (`*args`) and block (`&block`)
123
+ #
124
+ # ### Regex patterns not working
125
+ #
126
+ # 1. Ensure you're using `/pattern/` format with forward slashes
127
+ # 2. Test the regex in Ruby: `Regexp.new('your_pattern').match?('method_name')`
128
+ # 3. Escape special regex characters: `\.`, `\(`, `\)`, `\[`, `\]`, etc.
129
+ # 4. Invalid regex patterns are silently skipped - check for syntax errors
130
+ #
131
+ # ### Arity not matching
132
+ #
133
+ # 1. Count parameters correctly: `def method(a, b = 1)` has arity 2 (required + optional)
134
+ # 2. Splat parameters don't count: `def method(a, *rest)` has arity 1
135
+ # 3. Block parameters don't count: `def method(a, &block)` has arity 1
136
+ # 4. Keyword arguments count as individual parameters: `def method(a:, b:)` has arity 2
137
+ #
9
138
  module UndocumentedObjects
10
139
  end
11
140
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Documentation
7
+ module UndocumentedOptions
8
+ # Configuration for UndocumentedOptions validator
9
+ class Config < ::Yard::Lint::Validators::Config
10
+ self.id = :undocumented_options
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,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
@@ -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}")
@@ -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
@@ -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}")
@@ -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
@@ -0,0 +1,22 @@
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
+ 'EnforcedStyle' => 'long' # 'long' (Hash{K => V}) or 'short' ({K => V})
16
+ }.freeze
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ 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
+ # 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
+ detected_style = offense[:detected_style]
18
+
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'
22
+
23
+ "Use #{style_description} collection syntax #{corrected} instead of " \
24
+ "#{type_string} in @#{tag_name} tag."
25
+ end
26
+
27
+ private
28
+
29
+ # Suggests the corrected YARD syntax based on detected style
30
+ # @param type_string [String] the incorrect type string
31
+ # @param detected_style [String] the detected style ('short' or 'long')
32
+ # @return [String] the suggested correction
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)
40
+ end
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
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,50 @@
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|detected_style"
28
+ details = details_line.split('|', 3)
29
+ next unless details.size == 3
30
+
31
+ tag_name, type_string, detected_style = 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
+ detected_style: detected_style
40
+ }
41
+ end
42
+
43
+ violations
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ 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