yard-lint 0.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 (104) hide show
  1. checksums.yaml +7 -0
  2. data/.coditsu/ci.yml +3 -0
  3. data/CHANGELOG.md +28 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +454 -0
  6. data/bin/console +11 -0
  7. data/bin/setup +8 -0
  8. data/bin/yard-lint +109 -0
  9. data/lib/yard/lint/command_cache.rb +77 -0
  10. data/lib/yard/lint/config.rb +255 -0
  11. data/lib/yard/lint/config_loader.rb +198 -0
  12. data/lib/yard/lint/errors.rb +17 -0
  13. data/lib/yard/lint/formatters/progress.rb +50 -0
  14. data/lib/yard/lint/parsers/base.rb +23 -0
  15. data/lib/yard/lint/parsers/one_line_base.rb +35 -0
  16. data/lib/yard/lint/parsers/two_line_base.rb +45 -0
  17. data/lib/yard/lint/result_builder.rb +130 -0
  18. data/lib/yard/lint/results/aggregate.rb +86 -0
  19. data/lib/yard/lint/results/base.rb +156 -0
  20. data/lib/yard/lint/runner.rb +125 -0
  21. data/lib/yard/lint/validators/base.rb +120 -0
  22. data/lib/yard/lint/validators/config.rb +30 -0
  23. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/config.rb +20 -0
  24. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/parser.rb +43 -0
  25. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/result.rb +26 -0
  26. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/validator.rb +48 -0
  27. data/lib/yard/lint/validators/documentation/undocumented_boolean_methods.rb +13 -0
  28. data/lib/yard/lint/validators/documentation/undocumented_method_arguments/config.rb +20 -0
  29. data/lib/yard/lint/validators/documentation/undocumented_method_arguments/messages_builder.rb +24 -0
  30. data/lib/yard/lint/validators/documentation/undocumented_method_arguments/parser.rb +45 -0
  31. data/lib/yard/lint/validators/documentation/undocumented_method_arguments/result.rb +25 -0
  32. data/lib/yard/lint/validators/documentation/undocumented_method_arguments/validator.rb +55 -0
  33. data/lib/yard/lint/validators/documentation/undocumented_method_arguments.rb +13 -0
  34. data/lib/yard/lint/validators/documentation/undocumented_objects/config.rb +21 -0
  35. data/lib/yard/lint/validators/documentation/undocumented_objects/messages_builder.rb +23 -0
  36. data/lib/yard/lint/validators/documentation/undocumented_objects/parser.rb +39 -0
  37. data/lib/yard/lint/validators/documentation/undocumented_objects/result.rb +25 -0
  38. data/lib/yard/lint/validators/documentation/undocumented_objects/validator.rb +39 -0
  39. data/lib/yard/lint/validators/documentation/undocumented_objects.rb +14 -0
  40. data/lib/yard/lint/validators/semantic/abstract_methods/config.rb +24 -0
  41. data/lib/yard/lint/validators/semantic/abstract_methods/messages_builder.rb +25 -0
  42. data/lib/yard/lint/validators/semantic/abstract_methods/parser.rb +45 -0
  43. data/lib/yard/lint/validators/semantic/abstract_methods/result.rb +42 -0
  44. data/lib/yard/lint/validators/semantic/abstract_methods/validator.rb +65 -0
  45. data/lib/yard/lint/validators/semantic/abstract_methods.rb +13 -0
  46. data/lib/yard/lint/validators/tags/api_tags/config.rb +21 -0
  47. data/lib/yard/lint/validators/tags/api_tags/messages_builder.rb +29 -0
  48. data/lib/yard/lint/validators/tags/api_tags/parser.rb +50 -0
  49. data/lib/yard/lint/validators/tags/api_tags/result.rb +42 -0
  50. data/lib/yard/lint/validators/tags/api_tags/validator.rb +69 -0
  51. data/lib/yard/lint/validators/tags/api_tags.rb +13 -0
  52. data/lib/yard/lint/validators/tags/invalid_types/config.rb +22 -0
  53. data/lib/yard/lint/validators/tags/invalid_types/messages_builder.rb +24 -0
  54. data/lib/yard/lint/validators/tags/invalid_types/parser.rb +16 -0
  55. data/lib/yard/lint/validators/tags/invalid_types/result.rb +25 -0
  56. data/lib/yard/lint/validators/tags/invalid_types/validator.rb +106 -0
  57. data/lib/yard/lint/validators/tags/invalid_types.rb +13 -0
  58. data/lib/yard/lint/validators/tags/option_tags/config.rb +21 -0
  59. data/lib/yard/lint/validators/tags/option_tags/messages_builder.rb +24 -0
  60. data/lib/yard/lint/validators/tags/option_tags/parser.rb +45 -0
  61. data/lib/yard/lint/validators/tags/option_tags/result.rb +42 -0
  62. data/lib/yard/lint/validators/tags/option_tags/validator.rb +61 -0
  63. data/lib/yard/lint/validators/tags/option_tags.rb +13 -0
  64. data/lib/yard/lint/validators/tags/order/config.rb +33 -0
  65. data/lib/yard/lint/validators/tags/order/messages_builder.rb +30 -0
  66. data/lib/yard/lint/validators/tags/order/parser.rb +66 -0
  67. data/lib/yard/lint/validators/tags/order/result.rb +26 -0
  68. data/lib/yard/lint/validators/tags/order/validator.rb +89 -0
  69. data/lib/yard/lint/validators/tags/order.rb +13 -0
  70. data/lib/yard/lint/validators/warnings/duplicated_parameter_name/config.rb +22 -0
  71. data/lib/yard/lint/validators/warnings/duplicated_parameter_name/parser.rb +22 -0
  72. data/lib/yard/lint/validators/warnings/duplicated_parameter_name/result.rb +25 -0
  73. data/lib/yard/lint/validators/warnings/duplicated_parameter_name/validator.rb +33 -0
  74. data/lib/yard/lint/validators/warnings/duplicated_parameter_name.rb +14 -0
  75. data/lib/yard/lint/validators/warnings/invalid_directive_format/config.rb +22 -0
  76. data/lib/yard/lint/validators/warnings/invalid_directive_format/parser.rb +22 -0
  77. data/lib/yard/lint/validators/warnings/invalid_directive_format/result.rb +25 -0
  78. data/lib/yard/lint/validators/warnings/invalid_directive_format/validator.rb +33 -0
  79. data/lib/yard/lint/validators/warnings/invalid_directive_format.rb +14 -0
  80. data/lib/yard/lint/validators/warnings/invalid_tag_format/config.rb +22 -0
  81. data/lib/yard/lint/validators/warnings/invalid_tag_format/parser.rb +22 -0
  82. data/lib/yard/lint/validators/warnings/invalid_tag_format/result.rb +25 -0
  83. data/lib/yard/lint/validators/warnings/invalid_tag_format/validator.rb +33 -0
  84. data/lib/yard/lint/validators/warnings/invalid_tag_format.rb +14 -0
  85. data/lib/yard/lint/validators/warnings/unknown_directive/config.rb +22 -0
  86. data/lib/yard/lint/validators/warnings/unknown_directive/parser.rb +22 -0
  87. data/lib/yard/lint/validators/warnings/unknown_directive/result.rb +25 -0
  88. data/lib/yard/lint/validators/warnings/unknown_directive/validator.rb +33 -0
  89. data/lib/yard/lint/validators/warnings/unknown_directive.rb +14 -0
  90. data/lib/yard/lint/validators/warnings/unknown_parameter_name/config.rb +22 -0
  91. data/lib/yard/lint/validators/warnings/unknown_parameter_name/parser.rb +22 -0
  92. data/lib/yard/lint/validators/warnings/unknown_parameter_name/result.rb +25 -0
  93. data/lib/yard/lint/validators/warnings/unknown_parameter_name/validator.rb +33 -0
  94. data/lib/yard/lint/validators/warnings/unknown_parameter_name.rb +14 -0
  95. data/lib/yard/lint/validators/warnings/unknown_tag/config.rb +22 -0
  96. data/lib/yard/lint/validators/warnings/unknown_tag/parser.rb +24 -0
  97. data/lib/yard/lint/validators/warnings/unknown_tag/result.rb +25 -0
  98. data/lib/yard/lint/validators/warnings/unknown_tag/validator.rb +33 -0
  99. data/lib/yard/lint/validators/warnings/unknown_tag.rb +14 -0
  100. data/lib/yard/lint/version.rb +8 -0
  101. data/lib/yard/lint.rb +76 -0
  102. data/lib/yard-lint.rb +11 -0
  103. data/renovate.json +22 -0
  104. metadata +178 -0
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Semantic
7
+ module AbstractMethods
8
+ # Validator to check @abstract methods have proper implementation
9
+ class Validator < Base
10
+ private
11
+
12
+ # Runs YARD list query to find abstract methods with implementation
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
15
+ # @return [Hash] shell command execution hash results
16
+ def yard_cmd(dir, escaped_file_names)
17
+ cmd = <<~CMD
18
+ yard list \
19
+ --private \
20
+ --protected \
21
+ -b #{Shellwords.escape(dir)} \
22
+ #{escaped_file_names}
23
+ CMD
24
+ cmd = cmd.tr("\n", ' ')
25
+ cmd = cmd.gsub('yard list', "yard list --query #{query}")
26
+
27
+ shell(cmd)
28
+ end
29
+
30
+ # @return [String] yard query to find abstract methods with implementation
31
+ def query
32
+ <<~QUERY
33
+ '
34
+ if object.has_tag?(:abstract) && object.is_a?(YARD::CodeObjects::MethodObject)
35
+ # Check if method has actual implementation (not just NotImplementedError)
36
+ source = object.source rescue nil
37
+ if source && !source.empty?
38
+ # Simple heuristic: abstract methods should be empty or raise NotImplementedError
39
+ lines = source.split("\\n").map(&:strip).reject(&:empty?)
40
+ # Skip def line and end
41
+ body_lines = lines[1...-1] || []
42
+
43
+ has_real_implementation = body_lines.any? do |line|
44
+ !line.start_with?('#') &&
45
+ !line.include?('NotImplementedError') &&
46
+ !line.include?('raise') &&
47
+ line != 'end'
48
+ end
49
+
50
+ if has_real_implementation
51
+ puts object.file + ':' + object.line.to_s + ': ' + object.title
52
+ puts 'has_implementation'
53
+ end
54
+ end
55
+ end
56
+ false
57
+ '
58
+ QUERY
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Semantic
7
+ # AbstractMethods validator module
8
+ module AbstractMethods
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ module ApiTags
8
+ # Configuration for ApiTags validator
9
+ class Config < ::Yard::Lint::Validators::Config
10
+ self.id = :api_tags
11
+ self.defaults = {
12
+ 'Enabled' => false,
13
+ 'Severity' => 'warning',
14
+ 'AllowedApis' => %w[public private internal]
15
+ }.freeze
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ # Tags validators - validate YARD tag quality and consistency
7
+ module Tags
8
+ module ApiTags
9
+ # Builds messages for API tag offenses
10
+ class MessagesBuilder
11
+ class << self
12
+ # Build message for API tag offense
13
+ # @param offense [Hash] offense data with :status and :object_name keys
14
+ # @return [String] formatted message
15
+ def call(offense)
16
+ if offense[:status] == 'missing'
17
+ "Public object `#{offense[:object_name]}` is missing @api tag"
18
+ else
19
+ api_value = offense[:api_value] || offense[:status]&.sub('invalid:', '')
20
+ "Object `#{offense[:object_name]}` has invalid @api tag value: '#{api_value}'"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ 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 ApiTags
8
+ # Parser for @api tag validation results
9
+ class Parser < Parsers::Base
10
+ # @param yard_output [String] raw yard output with API tag issues
11
+ # @return [Array<Hash>] array with API tag 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
+ lines.each_slice(2) do |location_line, status_line|
19
+ next unless location_line && status_line
20
+
21
+ # Parse location line: "file.rb:10: ClassName#method_name"
22
+ match = location_line.match(/^(.+):(\d+): (.+)$/)
23
+ next unless match
24
+
25
+ file = match[1]
26
+ line = match[2].to_i
27
+ object_name = match[3]
28
+
29
+ api_value = if status_line.start_with?('invalid:')
30
+ status_line.sub('invalid:', '')
31
+ end
32
+
33
+ results << {
34
+ name: 'ApiTag',
35
+ object_name: object_name,
36
+ status: status_line,
37
+ api_value: api_value,
38
+ location: file,
39
+ line: line
40
+ }
41
+ end
42
+
43
+ results
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ 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 ApiTags
8
+ # Result object for API tag validation
9
+ class Result < Results::Base
10
+ self.default_severity = 'warning'
11
+ self.offense_type = 'line'
12
+ self.offense_name = 'ApiTagViolation'
13
+
14
+ # Build human-readable message for API tag offense
15
+ # @param offense [Hash] offense data with :name key
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
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ module ApiTags
8
+ # Validator to check for @api tag presence and validity
9
+ class Validator < Base
10
+ private
11
+
12
+ # Runs yard list query to find objects missing or with invalid @api tags
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
15
+ # @return [Hash] shell command execution hash results
16
+ def yard_cmd(dir, escaped_file_names)
17
+ cmd = <<~CMD
18
+ yard list \
19
+ --private \
20
+ --protected \
21
+ -b #{Shellwords.escape(dir)} \
22
+ #{escaped_file_names}
23
+ CMD
24
+ cmd = cmd.tr("\n", ' ')
25
+ cmd = cmd.gsub('yard list', "yard list --query #{query}")
26
+
27
+ shell(cmd)
28
+ end
29
+
30
+ # @return [String] yard query to find objects with missing or invalid @api tags
31
+ def query
32
+ allowed_list = allowed_apis.map { |api| "'#{api}'" }.join(', ')
33
+
34
+ <<~QUERY
35
+ '
36
+ if object.has_tag?(:api)
37
+ api_value = object.tag(:api).text
38
+ unless [#{allowed_list}].include?(api_value)
39
+ puts object.file + ':' + object.line.to_s + ': ' + object.title
40
+ puts 'invalid:' + api_value
41
+ end
42
+ elsif #{require_api_tags?}
43
+ # Only check public methods/classes if require_api_tags is enabled
44
+ visibility = object.visibility.to_s
45
+ if visibility == 'public' && !object.root?
46
+ puts object.file + ':' + object.line.to_s + ': ' + object.title
47
+ puts 'missing'
48
+ end
49
+ end
50
+ false
51
+ '
52
+ QUERY
53
+ end
54
+
55
+ # @return [Array<String>] list of allowed API values
56
+ def allowed_apis
57
+ config.validator_config('Tags/ApiTags', 'AllowedApis') || %w[public private internal]
58
+ end
59
+
60
+ # @return [Boolean] whether @api tags are required on public objects
61
+ def require_api_tags?
62
+ config.validator_enabled?('Tags/ApiTags')
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ # ApiTags validator module
8
+ module ApiTags
9
+ end
10
+ end
11
+ end
12
+ end
13
+ 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 InvalidTypes
8
+ # Configuration for InvalidTypes validator
9
+ class Config < ::Yard::Lint::Validators::Config
10
+ self.id = :invalid_types
11
+ self.defaults = {
12
+ 'Enabled' => true,
13
+ 'Severity' => 'warning',
14
+ 'ValidatedTags' => %w[param option return yieldreturn],
15
+ 'ExtraTypes' => []
16
+ }.freeze
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ module InvalidTypes
8
+ # Builds messages for invalid tag types offenses
9
+ class MessagesBuilder
10
+ class << self
11
+ # Build message for invalid tag types
12
+ # @param offense [Hash] offense data with :method_name key
13
+ # @return [String] formatted message
14
+ def call(offense)
15
+ "The `#{offense[:method_name]}` has at least one tag " \
16
+ 'with an invalid type definition.'
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ module InvalidTypes
8
+ # Parser for invalid tags types output
9
+ # Reuses location parsing logic from undocumented method arguments
10
+ class Parser < Validators::Documentation::UndocumentedMethodArguments::Parser
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ 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 InvalidTypes
8
+ # Result object for invalid tag types validation
9
+ class Result < Results::Base
10
+ self.default_severity = 'warning'
11
+ self.offense_type = 'method'
12
+ self.offense_name = 'InvalidTagType'
13
+
14
+ # Build human-readable message for invalid tag type offense
15
+ # @param offense [Hash] offense data
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,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ module InvalidTypes
8
+ # Runs a query that will pick all the objects that have invalid type definitions
9
+ # By invalid we mean, that they are not classes nor any of the allowed defaults or
10
+ # exclusions.
11
+ class Validator < Base
12
+ # All non-class yard types that are considered valid
13
+ ALLOWED_DEFAULTS = %w[
14
+ false
15
+ true
16
+ nil
17
+ self
18
+ vold
19
+ ].freeze
20
+
21
+ private_constant :ALLOWED_DEFAULTS
22
+
23
+ private
24
+
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 escaped_file_names [String] files for which we want to get the stats
28
+ # @return [Hash] shell command execution hash results
29
+ def yard_cmd(dir, escaped_file_names)
30
+ # Write query to a temporary file to avoid shell escaping issues
31
+ require 'tempfile'
32
+
33
+ Tempfile.create(['yard_query', '.sh']) do |f|
34
+ 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")
38
+ f.flush
39
+ f.chmod(0o755)
40
+
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
60
+
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
75
+ end
76
+
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.validator_config('Tags/InvalidTypes', 'ValidatedTags')
81
+ query_array(validated_tags)
82
+ end
83
+
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.validator_config('Tags/InvalidTypes', 'ExtraTypes') || []
88
+ query_array(ALLOWED_DEFAULTS + extra_types)
89
+ end
90
+
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
+ "
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ # InvalidTypes validator module
8
+ module InvalidTypes
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ module OptionTags
8
+ # Configuration for OptionTags validator
9
+ class Config < ::Yard::Lint::Validators::Config
10
+ self.id = :option_tags
11
+ self.defaults = {
12
+ 'Enabled' => true,
13
+ 'Severity' => 'warning',
14
+ 'ParameterNames' => %w[options opts kwargs]
15
+ }.freeze
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ module OptionTags
8
+ # Builds messages for option tag offenses
9
+ class MessagesBuilder
10
+ class << self
11
+ # Build message for option tag offense
12
+ # @param offense [Hash] offense data with :method_name key
13
+ # @return [String] formatted message
14
+ def call(offense)
15
+ "Method `#{offense[:method_name]}` has options parameter but no @option tags " \
16
+ 'documenting the available options'
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ module Tags
7
+ module OptionTags
8
+ # Parser for @option tag validation results
9
+ class Parser < Parsers::Base
10
+ # @param yard_output [String] raw yard output with option tag issues
11
+ # @return [Array<Hash>] array with option tag 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
+ lines.each_slice(2) do |location_line, status_line|
19
+ next unless location_line && status_line
20
+ next unless status_line == 'missing_option_tags'
21
+
22
+ # Parse location line: "file.rb:10: ClassName#method_name"
23
+ match = location_line.match(/^(.+):(\d+): (.+)$/)
24
+ next unless match
25
+
26
+ file = match[1]
27
+ line = match[2].to_i
28
+ method_name = match[3]
29
+
30
+ results << {
31
+ name: 'MissingOptionTags',
32
+ method_name: method_name,
33
+ location: file,
34
+ line: line
35
+ }
36
+ end
37
+
38
+ results
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ 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 OptionTags
8
+ # Result object for option tag validation
9
+ class Result < Results::Base
10
+ self.default_severity = 'warning'
11
+ self.offense_type = 'method'
12
+ self.offense_name = 'OptionTagViolation'
13
+
14
+ # Build human-readable message for option tag offense
15
+ # @param offense [Hash] offense data with :name key
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