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.
- checksums.yaml +7 -0
- data/.coditsu/ci.yml +3 -0
- data/CHANGELOG.md +28 -0
- data/LICENSE.txt +21 -0
- data/README.md +454 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/bin/yard-lint +109 -0
- data/lib/yard/lint/command_cache.rb +77 -0
- data/lib/yard/lint/config.rb +255 -0
- data/lib/yard/lint/config_loader.rb +198 -0
- data/lib/yard/lint/errors.rb +17 -0
- data/lib/yard/lint/formatters/progress.rb +50 -0
- data/lib/yard/lint/parsers/base.rb +23 -0
- data/lib/yard/lint/parsers/one_line_base.rb +35 -0
- data/lib/yard/lint/parsers/two_line_base.rb +45 -0
- data/lib/yard/lint/result_builder.rb +130 -0
- data/lib/yard/lint/results/aggregate.rb +86 -0
- data/lib/yard/lint/results/base.rb +156 -0
- data/lib/yard/lint/runner.rb +125 -0
- data/lib/yard/lint/validators/base.rb +120 -0
- data/lib/yard/lint/validators/config.rb +30 -0
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/config.rb +20 -0
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/parser.rb +43 -0
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/result.rb +26 -0
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods/validator.rb +48 -0
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods.rb +13 -0
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/config.rb +20 -0
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/messages_builder.rb +24 -0
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/parser.rb +45 -0
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/result.rb +25 -0
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/validator.rb +55 -0
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments.rb +13 -0
- data/lib/yard/lint/validators/documentation/undocumented_objects/config.rb +21 -0
- data/lib/yard/lint/validators/documentation/undocumented_objects/messages_builder.rb +23 -0
- data/lib/yard/lint/validators/documentation/undocumented_objects/parser.rb +39 -0
- data/lib/yard/lint/validators/documentation/undocumented_objects/result.rb +25 -0
- data/lib/yard/lint/validators/documentation/undocumented_objects/validator.rb +39 -0
- data/lib/yard/lint/validators/documentation/undocumented_objects.rb +14 -0
- data/lib/yard/lint/validators/semantic/abstract_methods/config.rb +24 -0
- data/lib/yard/lint/validators/semantic/abstract_methods/messages_builder.rb +25 -0
- data/lib/yard/lint/validators/semantic/abstract_methods/parser.rb +45 -0
- data/lib/yard/lint/validators/semantic/abstract_methods/result.rb +42 -0
- data/lib/yard/lint/validators/semantic/abstract_methods/validator.rb +65 -0
- data/lib/yard/lint/validators/semantic/abstract_methods.rb +13 -0
- data/lib/yard/lint/validators/tags/api_tags/config.rb +21 -0
- data/lib/yard/lint/validators/tags/api_tags/messages_builder.rb +29 -0
- data/lib/yard/lint/validators/tags/api_tags/parser.rb +50 -0
- data/lib/yard/lint/validators/tags/api_tags/result.rb +42 -0
- data/lib/yard/lint/validators/tags/api_tags/validator.rb +69 -0
- data/lib/yard/lint/validators/tags/api_tags.rb +13 -0
- data/lib/yard/lint/validators/tags/invalid_types/config.rb +22 -0
- data/lib/yard/lint/validators/tags/invalid_types/messages_builder.rb +24 -0
- data/lib/yard/lint/validators/tags/invalid_types/parser.rb +16 -0
- data/lib/yard/lint/validators/tags/invalid_types/result.rb +25 -0
- data/lib/yard/lint/validators/tags/invalid_types/validator.rb +106 -0
- data/lib/yard/lint/validators/tags/invalid_types.rb +13 -0
- data/lib/yard/lint/validators/tags/option_tags/config.rb +21 -0
- data/lib/yard/lint/validators/tags/option_tags/messages_builder.rb +24 -0
- data/lib/yard/lint/validators/tags/option_tags/parser.rb +45 -0
- data/lib/yard/lint/validators/tags/option_tags/result.rb +42 -0
- data/lib/yard/lint/validators/tags/option_tags/validator.rb +61 -0
- data/lib/yard/lint/validators/tags/option_tags.rb +13 -0
- data/lib/yard/lint/validators/tags/order/config.rb +33 -0
- data/lib/yard/lint/validators/tags/order/messages_builder.rb +30 -0
- data/lib/yard/lint/validators/tags/order/parser.rb +66 -0
- data/lib/yard/lint/validators/tags/order/result.rb +26 -0
- data/lib/yard/lint/validators/tags/order/validator.rb +89 -0
- data/lib/yard/lint/validators/tags/order.rb +13 -0
- data/lib/yard/lint/validators/warnings/duplicated_parameter_name/config.rb +22 -0
- data/lib/yard/lint/validators/warnings/duplicated_parameter_name/parser.rb +22 -0
- data/lib/yard/lint/validators/warnings/duplicated_parameter_name/result.rb +25 -0
- data/lib/yard/lint/validators/warnings/duplicated_parameter_name/validator.rb +33 -0
- data/lib/yard/lint/validators/warnings/duplicated_parameter_name.rb +14 -0
- data/lib/yard/lint/validators/warnings/invalid_directive_format/config.rb +22 -0
- data/lib/yard/lint/validators/warnings/invalid_directive_format/parser.rb +22 -0
- data/lib/yard/lint/validators/warnings/invalid_directive_format/result.rb +25 -0
- data/lib/yard/lint/validators/warnings/invalid_directive_format/validator.rb +33 -0
- data/lib/yard/lint/validators/warnings/invalid_directive_format.rb +14 -0
- data/lib/yard/lint/validators/warnings/invalid_tag_format/config.rb +22 -0
- data/lib/yard/lint/validators/warnings/invalid_tag_format/parser.rb +22 -0
- data/lib/yard/lint/validators/warnings/invalid_tag_format/result.rb +25 -0
- data/lib/yard/lint/validators/warnings/invalid_tag_format/validator.rb +33 -0
- data/lib/yard/lint/validators/warnings/invalid_tag_format.rb +14 -0
- data/lib/yard/lint/validators/warnings/unknown_directive/config.rb +22 -0
- data/lib/yard/lint/validators/warnings/unknown_directive/parser.rb +22 -0
- data/lib/yard/lint/validators/warnings/unknown_directive/result.rb +25 -0
- data/lib/yard/lint/validators/warnings/unknown_directive/validator.rb +33 -0
- data/lib/yard/lint/validators/warnings/unknown_directive.rb +14 -0
- data/lib/yard/lint/validators/warnings/unknown_parameter_name/config.rb +22 -0
- data/lib/yard/lint/validators/warnings/unknown_parameter_name/parser.rb +22 -0
- data/lib/yard/lint/validators/warnings/unknown_parameter_name/result.rb +25 -0
- data/lib/yard/lint/validators/warnings/unknown_parameter_name/validator.rb +33 -0
- data/lib/yard/lint/validators/warnings/unknown_parameter_name.rb +14 -0
- data/lib/yard/lint/validators/warnings/unknown_tag/config.rb +22 -0
- data/lib/yard/lint/validators/warnings/unknown_tag/parser.rb +24 -0
- data/lib/yard/lint/validators/warnings/unknown_tag/result.rb +25 -0
- data/lib/yard/lint/validators/warnings/unknown_tag/validator.rb +33 -0
- data/lib/yard/lint/validators/warnings/unknown_tag.rb +14 -0
- data/lib/yard/lint/version.rb +8 -0
- data/lib/yard/lint.rb +76 -0
- data/lib/yard-lint.rb +11 -0
- data/renovate.json +22 -0
- 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
|