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,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Parsers
6
+ # Base class for all two line warnings parsers
7
+ class TwoLineBase < Base
8
+ # @param yard_stats [String] raw yard stats results string
9
+ # @return [Array<Hash>] array with all warnings informations from yard stats analysis
10
+ def call(yard_stats)
11
+ # Not all the lines from the yard_stats output are valuable, that's why we filter
12
+ # them out, preprocess and leave only those against which we should match
13
+ rows = classify(yard_stats.split("\n"))
14
+
15
+ rows.map do |warning|
16
+ {
17
+ name: self.class.to_s.split('::').last,
18
+ message: match(warning[0], :message).last,
19
+ location: match(warning[1], :location).last,
20
+ line: match(warning[1], :line).last.to_i
21
+ }
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ # @param rows [Array<String>] array with lines of output from yard stats
28
+ # @return [Array<Array<String>>] Array with always two elements - row that was
29
+ # classified and the next one because yard returns valuable chunks of informations
30
+ # in two lines that are one after another
31
+ def classify(rows)
32
+ buffor = []
33
+
34
+ rows.each.with_index do |row, index|
35
+ next unless row.match? self.class.regexps[:general]
36
+
37
+ buffor << [rows[index].to_s, rows[index + 1].to_s]
38
+ end
39
+
40
+ buffor
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ # Builds result objects from raw validator output
6
+ # Handles standard validators, multi-parser validators, and composite validators
7
+ class ResultBuilder
8
+ attr_reader :config
9
+
10
+ # @param config [Config] configuration object
11
+ def initialize(config)
12
+ @config = config
13
+ end
14
+
15
+ # Build result for a single validator
16
+ # Uses convention-based discovery to handle all validator types uniformly
17
+ # @param validator_name [String] validator name (e.g., 'Tags/Order')
18
+ # @param raw [Hash] raw results from all validators
19
+ # @return [Results::Base, nil] result object or nil if no offenses
20
+ def build(validator_name, raw)
21
+ validator_module = ConfigLoader.validator_module(validator_name)
22
+ validator_cfg = ConfigLoader.validator_config(validator_name)
23
+ return nil unless validator_module && validator_cfg
24
+
25
+ # Skip if this validator is a child of a composite
26
+ return nil if composite_child?(validator_name)
27
+
28
+ # Handle composite validators (those that combine multiple validators)
29
+ unless validator_cfg.combines_with.empty?
30
+ return build_composite_result(validator_module, validator_cfg, raw)
31
+ end
32
+
33
+ # Handle standard validators (single or multi-parser)
34
+ build_standard_result(validator_module, validator_cfg, raw)
35
+ end
36
+
37
+ private
38
+
39
+ # Check if a validator is a child of a composite validator
40
+ # @param validator_name [String] validator name to check
41
+ # @return [Boolean] true if this is a composite child
42
+ def composite_child?(validator_name)
43
+ ConfigLoader::ALL_VALIDATORS.any? do |parent_name|
44
+ parent_cfg = ConfigLoader.validator_config(parent_name)
45
+ next unless parent_cfg
46
+
47
+ parent_cfg.combines_with.include?(validator_name)
48
+ end
49
+ end
50
+
51
+ # Build result for a composite validator (combines multiple validators)
52
+ # @param validator_module [Module] validator namespace module
53
+ # @param validator_cfg [Class] validator config class
54
+ # @param raw [Hash] raw results
55
+ # @return [Results::Base, nil] composite result or nil
56
+ def build_composite_result(validator_module, validator_cfg, raw)
57
+ # Collect all child validators (modules + configs)
58
+ children = validator_cfg.combines_with.filter_map do |child_name|
59
+ child_mod = ConfigLoader.validator_module(child_name)
60
+ child_cfg = ConfigLoader.validator_config(child_name)
61
+ [child_mod, child_cfg] if child_mod && child_cfg
62
+ end
63
+
64
+ # All validators (parent + children)
65
+ all_validators = [[validator_module, validator_cfg]] + children
66
+
67
+ # Parse output from all validators
68
+ combined = all_validators.flat_map do |mod, cfg|
69
+ parse_validator_output(mod, cfg, raw)
70
+ end
71
+
72
+ return nil if combined.empty?
73
+
74
+ validator_module::Result.new(combined, config)
75
+ end
76
+
77
+ # Build result for a standard validator (single or multi-parser)
78
+ # Auto-detects multi-parser validators by discovering parser classes
79
+ # @param validator_module [Module] validator namespace module
80
+ # @param validator_cfg [Class] validator config class
81
+ # @param raw [Hash] raw results
82
+ # @return [Results::Base, nil] result or nil
83
+ def build_standard_result(validator_module, validator_cfg, raw)
84
+ return nil unless raw[validator_cfg.id]
85
+
86
+ stdout = raw.dig(validator_cfg.id, :stdout)
87
+ return nil unless stdout
88
+
89
+ # Discover all parser classes in the validator module
90
+ parsers = discover_parsers(validator_module)
91
+ return nil if parsers.empty?
92
+
93
+ # Parse output with all parsers (single or multiple)
94
+ parsed = parsers.flat_map { |parser| parser.new.call(stdout) }
95
+ return nil if parsed.nil? || parsed.empty?
96
+
97
+ validator_module::Result.new(parsed, config)
98
+ end
99
+
100
+ # Parse output from a validator module
101
+ # @param validator_module [Module] validator namespace module
102
+ # @param validator_cfg [Class] validator config class
103
+ # @param raw [Hash] raw results
104
+ # @return [Array<Hash>] parsed offenses
105
+ def parse_validator_output(validator_module, validator_cfg, raw)
106
+ return [] unless raw[validator_cfg.id]
107
+
108
+ stdout = raw.dig(validator_cfg.id, :stdout)
109
+ return [] unless stdout
110
+
111
+ parsers = discover_parsers(validator_module)
112
+ parsers.flat_map { |parser| parser.new.call(stdout) }
113
+ end
114
+
115
+ # Auto-discover parser classes in a validator module
116
+ # Finds all classes that inherit from Parsers::Base
117
+ # @param validator_module [Module] validator module to search
118
+ # @return [Array<Class>] array of parser classes
119
+ def discover_parsers(validator_module)
120
+ validator_module.constants
121
+ .map { |const_name| validator_module.const_get(const_name) }
122
+ .select { |const| const.is_a?(Class) }
123
+ .select { |klass| klass < Parsers::Base }
124
+ .reject do |klass|
125
+ [validator_module::Validator, validator_module::Result].include?(klass)
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ # Result objects for validators
6
+ module Results
7
+ # Aggregates multiple validator results into a single result object
8
+ class Aggregate
9
+ # Error severity level constant
10
+ SEVERITY_ERROR = 'error'
11
+ # Warning severity level constant
12
+ SEVERITY_WARNING = 'warning'
13
+ # Convention severity level constant
14
+ SEVERITY_CONVENTION = 'convention'
15
+
16
+ attr_reader :config
17
+
18
+ # Initialize aggregate result with array of validator results
19
+ # @param results [Array<Results::Base>] array of validator result objects
20
+ # @param config [Config, nil] configuration object
21
+ def initialize(results, config = nil)
22
+ @results = Array(results)
23
+ @config = config
24
+ end
25
+
26
+ # Get all offenses from all validators
27
+ # @return [Array<Hash>] flattened array of all offenses
28
+ def offenses
29
+ @results.flat_map(&:offenses)
30
+ end
31
+
32
+ # Total number of offenses
33
+ # @return [Integer] offense count
34
+ def count
35
+ offenses.count
36
+ end
37
+
38
+ # Check if there are no offenses
39
+ # @return [Boolean] true if no offenses found
40
+ def clean?
41
+ offenses.empty?
42
+ end
43
+
44
+ # Get offense statistics by severity
45
+ # @return [Hash] hash with severity counts (using symbol keys)
46
+ def statistics
47
+ stats = {
48
+ error: 0,
49
+ warning: 0,
50
+ convention: 0,
51
+ total: 0
52
+ }
53
+
54
+ offenses.each do |offense|
55
+ severity = offense[:severity].to_sym
56
+ stats[severity] += 1 if stats.key?(severity)
57
+ stats[:total] += 1
58
+ end
59
+
60
+ stats
61
+ end
62
+
63
+ # Determine exit code based on configured fail_on_severity
64
+ # Uses the config object stored during initialization
65
+ # @return [Integer] 0 for success, 1 for failure
66
+ def exit_code
67
+ return 0 if offenses.empty?
68
+ return 0 unless @config # No config means don't fail
69
+
70
+ fail_on = @config.fail_on_severity
71
+
72
+ case fail_on
73
+ when SEVERITY_ERROR
74
+ statistics[:error].positive? ? 1 : 0
75
+ when SEVERITY_WARNING
76
+ (statistics[:error] + statistics[:warning]).positive? ? 1 : 0
77
+ when SEVERITY_CONVENTION
78
+ offenses.any? ? 1 : 0
79
+ else
80
+ 0
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Results
6
+ # Base class for validator-specific result objects
7
+ # Each validator should subclass this and set class attributes
8
+ #
9
+ # @example Creating a validator result class
10
+ # class MyValidator::Result < Results::Base
11
+ # self.default_severity = 'warning'
12
+ # self.offense_type = 'method'
13
+ # self.offense_name = 'MyOffense'
14
+ #
15
+ # def build_message(offense)
16
+ # "Found issue in #{offense[:location]}"
17
+ # end
18
+ # end
19
+ class Base
20
+ class << self
21
+ # Default severity level for this validator's offenses
22
+ # @return [String] 'error', 'warning', or 'convention'
23
+ attr_writer :default_severity
24
+
25
+ # Get the default severity level for this validator
26
+ # @return [String] 'error', 'warning', or 'convention'
27
+ def default_severity
28
+ @default_severity ||
29
+ (superclass.respond_to?(:default_severity) ? superclass.default_severity : nil)
30
+ end
31
+
32
+ # Type of offense for display purposes
33
+ # @return [String] 'line' or 'method'
34
+ attr_writer :offense_type
35
+
36
+ # Get the offense type for this validator
37
+ # @return [String] 'line' or 'method'
38
+ def offense_type
39
+ @offense_type ||
40
+ (superclass.respond_to?(:offense_type) ? superclass.offense_type : nil)
41
+ end
42
+
43
+ # Name of the offense for identification
44
+ # @return [String] offense name
45
+ attr_writer :offense_name
46
+
47
+ # Get the offense name for this validator
48
+ # @return [String] offense name
49
+ def offense_name
50
+ @offense_name ||
51
+ (superclass.respond_to?(:offense_name) ? superclass.offense_name : nil)
52
+ end
53
+ end
54
+
55
+ # Set default values for base class
56
+ self.offense_type = 'line'
57
+
58
+ attr_reader :offenses, :config
59
+
60
+ # Initialize a result object with parsed validator data
61
+ # @param parsed_data [Array<Hash>] Array of offense hashes from validator parser
62
+ # @param config [Config, nil] Configuration object for severity lookup
63
+ def initialize(parsed_data, config = nil)
64
+ @parsed_data = Array(parsed_data)
65
+ @config = config
66
+ @offenses = build_offenses
67
+ end
68
+
69
+ # Count of offenses
70
+ # @return [Integer] number of offenses
71
+ def count
72
+ @offenses.count
73
+ end
74
+
75
+ # Check if there are no offenses
76
+ # @return [Boolean] true if no offenses
77
+ def empty?
78
+ @offenses.empty?
79
+ end
80
+
81
+ # Delegate array methods to offenses for convenience
82
+ # @return [Array] mapped offenses
83
+ def map(&)
84
+ @offenses.map(&)
85
+ end
86
+
87
+ # Delegate each to offenses
88
+ # @return [Array] offenses
89
+ def each(&)
90
+ @offenses.each(&)
91
+ end
92
+
93
+ # Full validator name in format 'Category/ValidatorName'
94
+ # Extracted from the class path
95
+ # @return [String] validator name for config lookup
96
+ def validator_name
97
+ # Extract from class path: Validators::Tags::Order::Result => 'Tags/Order'
98
+ parts = self.class.name.split('::')
99
+ validators_index = parts.index('Validators')
100
+ return '' unless validators_index
101
+
102
+ category = parts[validators_index + 1]
103
+ name = parts[validators_index + 2]
104
+ "#{category}/#{name}"
105
+ end
106
+
107
+ private
108
+
109
+ # Build a human-readable message for an offense
110
+ # Subclasses must override this method
111
+ # @param offense [Hash] offense data from parser
112
+ # @return [String] formatted message
113
+ def build_message(offense)
114
+ raise NotImplementedError, "#{self.class} must implement #build_message"
115
+ end
116
+
117
+ # Build array of offense hashes in unified format
118
+ # Merges original parsed data with standard offense fields
119
+ # @return [Array<Hash>] array of offense hashes
120
+ def build_offenses
121
+ @parsed_data.map do |offense_data|
122
+ # Start with original parsed data to preserve all fields
123
+ offense_data.merge(
124
+ severity: configured_severity,
125
+ type: self.class.offense_type,
126
+ name: computed_offense_name,
127
+ message: build_message(offense_data),
128
+ location: offense_data[:location] || offense_data[:file],
129
+ location_line: offense_data[:line] || offense_data[:location_line] || 0
130
+ )
131
+ end
132
+ end
133
+
134
+ # Get configured severity or fall back to default
135
+ # @return [String] severity level
136
+ def configured_severity
137
+ default = self.class.default_severity
138
+ raise NotImplementedError, "#{self.class} must set self.default_severity" unless default
139
+
140
+ return default unless config
141
+
142
+ config.validator_severity(validator_name) || default
143
+ end
144
+
145
+ # Compute offense name from class attribute or derive from class name
146
+ # @return [String] offense name
147
+ def computed_offense_name
148
+ return self.class.offense_name if self.class.offense_name
149
+ return 'Unknown' unless self.class.name
150
+
151
+ self.class.name.split('::').last.sub(/Result$/, '')
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ # Main runner class that orchestrates the YARD validation process
6
+ class Runner
7
+ attr_reader :config, :selection
8
+ attr_accessor :progress_formatter
9
+
10
+ # @param selection [Array<String>] array with ruby files to check
11
+ # @param config [Yard::Lint::Config] configuration object
12
+ def initialize(selection, config = Config.new)
13
+ @selection = Array(selection).flatten
14
+ @config = config
15
+ @result_builder = ResultBuilder.new(config)
16
+ @progress_formatter = nil
17
+ end
18
+
19
+ # Runs all validators and returns a Result object
20
+ # @return [Yard::Lint::Result] result object with all offenses
21
+ def run
22
+ raw_results = run_validators
23
+ parsed_results = parse_results(raw_results)
24
+ build_result(parsed_results)
25
+ end
26
+
27
+ private
28
+
29
+ # Run all validators
30
+ # Automatically runs all validators from ConfigLoader::ALL_VALIDATORS if enabled
31
+ # @return [Hash] hash with raw results from all validators
32
+ def run_validators
33
+ results = {}
34
+ enabled_validators = ConfigLoader::ALL_VALIDATORS.select do |name|
35
+ config.validator_enabled?(name)
36
+ end
37
+
38
+ @progress_formatter&.start(enabled_validators.size)
39
+
40
+ # Iterate through all registered validators
41
+ enabled_validators.each_with_index do |validator_name, index|
42
+ # Get the validator namespace and config
43
+ validator_namespace = ConfigLoader.validator_module(validator_name)
44
+ validator_cfg = ConfigLoader.validator_config(validator_name)
45
+
46
+ # Show progress before running validator
47
+ @progress_formatter&.update(index + 1, validator_name)
48
+
49
+ # Run the validator if it has a module
50
+ # (validators with modules have Validator classes)
51
+ if validator_namespace
52
+ run_and_store_validator(validator_namespace, validator_cfg, results, validator_name)
53
+ end
54
+ end
55
+
56
+ @progress_formatter&.finish
57
+
58
+ results
59
+ end
60
+
61
+ # Run a validator and store its result using the module's ID
62
+ # @param validator_namespace [Module] validator namespace module (e.g., Validators::Tags::Order)
63
+ # @param validator_config [Class] validator config class
64
+ # @param results [Hash] hash to store results in
65
+ # @param validator_name [String] full validator name for per-validator exclusions
66
+ def run_and_store_validator(
67
+ validator_namespace, validator_config, results, validator_name
68
+ )
69
+ results[validator_config.id] = run_validator(
70
+ validator_namespace::Validator,
71
+ validator_name
72
+ )
73
+ end
74
+
75
+ # Run a single validator with per-validator file filtering
76
+ # @param validator_class [Class] validator class to instantiate and run
77
+ # @param validator_name [String] full validator name for exclusions
78
+ # @return [Hash] hash with stdout, stderr and exit_code keys
79
+ def run_validator(validator_class, validator_name)
80
+ validator_selection = filter_files_for_validator(validator_name, selection)
81
+ validator_class.new(config, validator_selection).call
82
+ end
83
+
84
+ # Filter files for a specific validator based on per-validator exclusions
85
+ # @param validator_name [String] full validator name
86
+ # @param files [Array<String>] array of file paths
87
+ # @return [Array<String>] filtered array of file paths
88
+ def filter_files_for_validator(validator_name, files)
89
+ validator_excludes = config.validator_exclude(validator_name)
90
+ return files if validator_excludes.empty?
91
+
92
+ files.reject do |file|
93
+ validator_excludes.any? do |pattern|
94
+ File.fnmatch(pattern, file, File::FNM_PATHNAME | File::FNM_EXTGLOB)
95
+ end
96
+ end
97
+ end
98
+
99
+ # Parse raw results from validators and create Result objects
100
+ # Delegates result building to ResultBuilder
101
+ # @param raw [Hash] hash with raw results from all validators
102
+ # @return [Array<Results::Base>] array of Result objects
103
+ def parse_results(raw)
104
+ results = []
105
+
106
+ # Iterate through all registered validators and build results
107
+ ConfigLoader::ALL_VALIDATORS.each do |validator_name|
108
+ next unless config.validator_enabled?(validator_name)
109
+
110
+ result = @result_builder.build(validator_name, raw)
111
+ results << result if result
112
+ end
113
+
114
+ results
115
+ end
116
+
117
+ # Build final result object
118
+ # @param results [Array<Results::Base>] array of validator result objects
119
+ # @return [Results::Aggregate] aggregate result object
120
+ def build_result(results)
121
+ Results::Aggregate.new(results, config)
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ # Validators for checking different aspects of YARD documentation
6
+ module Validators
7
+ # Base YARD validator class
8
+ class Base
9
+ # Class-level cache shared across ALL validator classes
10
+ # Must be stored on Base itself, not on subclasses
11
+ @shared_command_cache = nil
12
+
13
+ # Default YARD command options that we need to use
14
+ DEFAULT_OPTIONS = [
15
+ '--charset utf-8',
16
+ '--markup markdown',
17
+ '--no-progress'
18
+ ].freeze
19
+
20
+ # String with a temp dir to store the YARD database
21
+ # @note We run YARD multiple times and in order not to rebuild db over and over
22
+ # again but reuse the same one, we have a single tmp dir for it
23
+ YARDOC_TEMP_DIR = Dir.mktmpdir.freeze
24
+
25
+ private_constant :YARDOC_TEMP_DIR
26
+
27
+ attr_reader :config, :selection
28
+
29
+ class << self
30
+ # Lazy-initialized command cache shared across all validator instances
31
+ # This allows different validators to reuse results from identical YARD commands
32
+ # @return [CommandCache] the command cache instance
33
+ def command_cache
34
+ # Use Base's cache, not subclass's cache
35
+ Base.instance_variable_get(:@shared_command_cache) ||
36
+ Base.instance_variable_set(:@shared_command_cache, CommandCache.new)
37
+ end
38
+
39
+ # Reset the command cache (primarily for testing)
40
+ # @return [void]
41
+ def reset_command_cache!
42
+ Base.instance_variable_set(:@shared_command_cache, nil)
43
+ end
44
+
45
+ # Clear the YARD database (primarily for testing)
46
+ # @return [void]
47
+ def clear_yard_database!
48
+ return unless defined?(YARDOC_TEMP_DIR)
49
+
50
+ FileUtils.rm_rf(Dir.glob(File.join(YARDOC_TEMP_DIR, '*')))
51
+ end
52
+ end
53
+
54
+ # @param config [Yard::Lint::Config] configuration object
55
+ # @param selection [Array<String>] array with ruby files we want to check
56
+ def initialize(config, selection)
57
+ @config = config
58
+ @selection = selection
59
+ end
60
+
61
+ # Performs the validation and returns raw results
62
+ # @return [Hash] hash with stdout, stderr and exit_code keys
63
+ def call
64
+ # There might be a case when there were no files because someone ignored all
65
+ # then we need to return empty result
66
+ return raw if selection.nil? || selection.empty?
67
+
68
+ # Anything that goes to shell needs to be escaped
69
+ escaped_file_names = escape(selection).join(' ')
70
+
71
+ yard_cmd(YARDOC_TEMP_DIR, escaped_file_names)
72
+ end
73
+
74
+ private
75
+
76
+ # @return [String] all arguments with which YARD command should be executed
77
+ def shell_arguments
78
+ validator_name = self.class.name.split('::').then do |parts|
79
+ idx = parts.index('Validators')
80
+ next config.options unless idx && parts[idx + 1] && parts[idx + 2]
81
+
82
+ "#{parts[idx + 1]}/#{parts[idx + 2]}"
83
+ end
84
+
85
+ yard_options = config.validator_yard_options(validator_name)
86
+ args = escape(yard_options).join(' ')
87
+ "#{args} #{DEFAULT_OPTIONS.join(' ')}"
88
+ end
89
+
90
+ # @param array [Array] escape all elements in an array
91
+ # @return [Array] array with escaped elements
92
+ def escape(array)
93
+ array.map { |cmd| Shellwords.escape(cmd) }
94
+ end
95
+
96
+ # Builds a raw hash that can be used for further processing
97
+ # @param stdout [String, Hash, Array] anything that we want to return as stdout
98
+ # @param stderr [String, Hash, Array] any errors that occurred
99
+ # @param exit_code [Integer, false] result exit code or false if we want to decide it based
100
+ # on the stderr content
101
+ # @return [Hash] hash with stdout, stderr and exit_code keys
102
+ def raw(stdout = '', stderr = '', exit_code = false)
103
+ {
104
+ stdout: stdout,
105
+ stderr: stderr,
106
+ exit_code: exit_code || (stderr.empty? ? 0 : 1)
107
+ }
108
+ end
109
+
110
+ # Executes a shell command and returns the result
111
+ # Routes through command cache to avoid duplicate executions
112
+ # @param cmd [String] shell command to execute
113
+ # @return [Hash] hash with stdout, stderr and exit_code keys
114
+ def shell(cmd)
115
+ self.class.command_cache.execute(cmd)
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yard
4
+ module Lint
5
+ module Validators
6
+ # Base configuration class for validators
7
+ # Provides common class attributes for all validators
8
+ class Config
9
+ class << self
10
+ # Unique identifier for this validator
11
+ # @return [Symbol] validator identifier
12
+ attr_accessor :id
13
+
14
+ # Default configuration for this validator
15
+ # @return [Hash] default configuration hash
16
+ attr_accessor :defaults
17
+
18
+ # Validators to combine with this one
19
+ # @return [Array<String>] validator names to combine, empty array for standalone
20
+ def combines_with
21
+ @combines_with ||= []
22
+ end
23
+
24
+ # Set validators to combine with
25
+ attr_writer :combines_with
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end