yard-lint 1.0.0 → 1.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 +4 -4
- data/CHANGELOG.md +77 -0
- data/README.md +160 -268
- data/bin/yard-lint +100 -8
- data/lib/yard/lint/command_cache.rb +17 -1
- data/lib/yard/lint/config.rb +20 -2
- data/lib/yard/lint/config_generator.rb +200 -0
- data/lib/yard/lint/ext/irb_notifier_shim.rb +95 -0
- data/lib/yard/lint/git.rb +125 -0
- data/lib/yard/lint/results/aggregate.rb +22 -2
- data/lib/yard/lint/runner.rb +4 -3
- data/lib/yard/lint/stats_calculator.rb +157 -0
- data/lib/yard/lint/validators/base.rb +36 -0
- data/lib/yard/lint/validators/documentation/markdown_syntax/config.rb +20 -0
- data/lib/yard/lint/validators/documentation/markdown_syntax/messages_builder.rb +44 -0
- data/lib/yard/lint/validators/documentation/markdown_syntax/parser.rb +53 -0
- data/lib/yard/lint/validators/documentation/markdown_syntax/result.rb +25 -0
- data/lib/yard/lint/validators/documentation/markdown_syntax/validator.rb +38 -0
- data/lib/yard/lint/validators/documentation/markdown_syntax.rb +37 -0
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods.rb +26 -1
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments.rb +26 -1
- data/lib/yard/lint/validators/documentation/undocumented_objects.rb +131 -2
- data/lib/yard/lint/validators/documentation/undocumented_options/config.rb +20 -0
- data/lib/yard/lint/validators/documentation/undocumented_options/parser.rb +53 -0
- data/lib/yard/lint/validators/documentation/undocumented_options/result.rb +29 -0
- data/lib/yard/lint/validators/documentation/undocumented_options/validator.rb +38 -0
- data/lib/yard/lint/validators/documentation/undocumented_options.rb +40 -0
- data/lib/yard/lint/validators/semantic/abstract_methods.rb +31 -1
- data/lib/yard/lint/validators/tags/api_tags.rb +34 -1
- data/lib/yard/lint/validators/tags/collection_type/config.rb +2 -1
- data/lib/yard/lint/validators/tags/collection_type/messages_builder.rb +40 -11
- data/lib/yard/lint/validators/tags/collection_type/parser.rb +6 -5
- data/lib/yard/lint/validators/tags/collection_type/validator.rb +26 -7
- data/lib/yard/lint/validators/tags/collection_type.rb +38 -2
- data/lib/yard/lint/validators/tags/example_syntax/config.rb +20 -0
- data/lib/yard/lint/validators/tags/example_syntax/messages_builder.rb +28 -0
- data/lib/yard/lint/validators/tags/example_syntax/parser.rb +79 -0
- data/lib/yard/lint/validators/tags/example_syntax/result.rb +42 -0
- data/lib/yard/lint/validators/tags/example_syntax/validator.rb +88 -0
- data/lib/yard/lint/validators/tags/example_syntax.rb +42 -0
- data/lib/yard/lint/validators/tags/invalid_types/validator.rb +2 -2
- data/lib/yard/lint/validators/tags/invalid_types.rb +25 -1
- data/lib/yard/lint/validators/tags/meaningless_tag/validator.rb +2 -4
- data/lib/yard/lint/validators/tags/meaningless_tag.rb +31 -3
- data/lib/yard/lint/validators/tags/option_tags/validator.rb +7 -1
- data/lib/yard/lint/validators/tags/option_tags.rb +26 -1
- data/lib/yard/lint/validators/tags/order.rb +25 -1
- data/lib/yard/lint/validators/tags/redundant_param_description/config.rb +33 -0
- data/lib/yard/lint/validators/tags/redundant_param_description/messages_builder.rb +61 -0
- data/lib/yard/lint/validators/tags/redundant_param_description/parser.rb +67 -0
- data/lib/yard/lint/validators/tags/redundant_param_description/result.rb +25 -0
- data/lib/yard/lint/validators/tags/redundant_param_description/validator.rb +148 -0
- data/lib/yard/lint/validators/tags/redundant_param_description.rb +168 -0
- data/lib/yard/lint/validators/tags/tag_type_position/validator.rb +2 -4
- data/lib/yard/lint/validators/tags/tag_type_position.rb +39 -2
- data/lib/yard/lint/validators/tags/type_syntax.rb +26 -2
- data/lib/yard/lint/validators/warnings/duplicated_parameter_name.rb +26 -1
- data/lib/yard/lint/validators/warnings/invalid_directive_format.rb +26 -1
- data/lib/yard/lint/validators/warnings/invalid_tag_format.rb +25 -1
- data/lib/yard/lint/validators/warnings/unknown_directive.rb +26 -1
- data/lib/yard/lint/validators/warnings/unknown_parameter_name.rb +23 -1
- data/lib/yard/lint/validators/warnings/unknown_tag.rb +26 -1
- data/lib/yard/lint/version.rb +1 -1
- data/lib/yard/lint.rb +38 -2
- data/lib/yard-lint.rb +5 -0
- metadata +28 -1
data/bin/yard-lint
CHANGED
|
@@ -8,6 +8,7 @@ require 'yard-lint'
|
|
|
8
8
|
|
|
9
9
|
options = {}
|
|
10
10
|
config_file = nil
|
|
11
|
+
diff_mode = nil
|
|
11
12
|
|
|
12
13
|
OptionParser.new do |opts|
|
|
13
14
|
opts.banner = 'Usage: yard-lint [options] PATH'
|
|
@@ -28,10 +29,40 @@ OptionParser.new do |opts|
|
|
|
28
29
|
options[:stats] = true
|
|
29
30
|
end
|
|
30
31
|
|
|
32
|
+
opts.on('--min-coverage PERCENT', Float, 'Minimum documentation coverage required (0-100)') do |percent|
|
|
33
|
+
options[:min_coverage] = percent
|
|
34
|
+
end
|
|
35
|
+
|
|
31
36
|
opts.on('--[no-]progress', 'Show progress indicator (default: auto)') do |value|
|
|
32
37
|
options[:progress] = value
|
|
33
38
|
end
|
|
34
39
|
|
|
40
|
+
opts.separator ''
|
|
41
|
+
opts.separator 'Diff mode options (mutually exclusive):'
|
|
42
|
+
|
|
43
|
+
opts.on('--diff [REF]', 'Lint only files changed since REF (default: main/master auto-detected)') do |ref|
|
|
44
|
+
diff_mode = { mode: :ref, base_ref: ref }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
opts.on('--staged', 'Lint only staged files (git index)') do
|
|
48
|
+
diff_mode = { mode: :staged }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
opts.on('--changed', 'Lint only uncommitted files') do
|
|
52
|
+
diff_mode = { mode: :changed }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
opts.separator ''
|
|
56
|
+
opts.separator 'Other options:'
|
|
57
|
+
|
|
58
|
+
opts.on('--init', 'Generate .yard-lint.yml config file with defaults') do
|
|
59
|
+
options[:init] = true
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
opts.on('--force', 'Force overwrite when using --init') do
|
|
63
|
+
options[:force] = true
|
|
64
|
+
end
|
|
65
|
+
|
|
35
66
|
opts.on('-v', '--version', 'Show version') do
|
|
36
67
|
puts "yard-lint #{Yard::Lint::VERSION}"
|
|
37
68
|
exit
|
|
@@ -39,10 +70,29 @@ OptionParser.new do |opts|
|
|
|
39
70
|
|
|
40
71
|
opts.on('-h', '--help', 'Show this help') do
|
|
41
72
|
puts opts
|
|
73
|
+
puts
|
|
74
|
+
puts 'Examples:'
|
|
75
|
+
puts ' yard-lint lib/ # Lint all files in lib/'
|
|
76
|
+
puts ' yard-lint --diff main lib/ # Lint only files changed since main branch'
|
|
77
|
+
puts ' yard-lint --staged lib/ # Lint only staged files'
|
|
78
|
+
puts ' yard-lint --changed lib/ # Lint only uncommitted files'
|
|
79
|
+
puts ' yard-lint --format json lib/ # Output in JSON format'
|
|
42
80
|
exit
|
|
43
81
|
end
|
|
44
82
|
end.parse!
|
|
45
83
|
|
|
84
|
+
# Handle --init flag
|
|
85
|
+
if options[:init]
|
|
86
|
+
if Yard::Lint::ConfigGenerator.generate(force: options[:force])
|
|
87
|
+
puts 'Created .yard-lint.yml with default configuration'
|
|
88
|
+
exit 0
|
|
89
|
+
else
|
|
90
|
+
puts 'Error: .yard-lint.yml already exists'
|
|
91
|
+
puts 'Use --init --force to overwrite'
|
|
92
|
+
exit 1
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
46
96
|
# Get path argument
|
|
47
97
|
path = ARGV[0]
|
|
48
98
|
|
|
@@ -76,12 +126,28 @@ end
|
|
|
76
126
|
Yard::Lint::Validators::Base.reset_command_cache!
|
|
77
127
|
Yard::Lint::Validators::Base.clear_yard_database!
|
|
78
128
|
|
|
79
|
-
#
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
129
|
+
# Load config and apply CLI overrides
|
|
130
|
+
config = if config_file
|
|
131
|
+
Yard::Lint::Config.from_file(config_file)
|
|
132
|
+
else
|
|
133
|
+
Yard::Lint::Config.load || Yard::Lint::Config.new
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Apply CLI min_coverage override if provided
|
|
137
|
+
config.min_coverage = options[:min_coverage] if options[:min_coverage]
|
|
138
|
+
|
|
139
|
+
# Run the linter
|
|
140
|
+
begin
|
|
141
|
+
result = Yard::Lint.run(
|
|
142
|
+
path: path,
|
|
143
|
+
config: config,
|
|
144
|
+
progress: options[:progress],
|
|
145
|
+
diff: diff_mode
|
|
146
|
+
)
|
|
147
|
+
rescue Yard::Lint::Git::Error => e
|
|
148
|
+
puts "Git error: #{e.message}"
|
|
149
|
+
exit 1
|
|
150
|
+
end
|
|
85
151
|
|
|
86
152
|
# Format and display results
|
|
87
153
|
case options[:format]
|
|
@@ -93,14 +159,40 @@ when 'json'
|
|
|
93
159
|
})
|
|
94
160
|
exit result.exit_code
|
|
95
161
|
when 'text', nil
|
|
162
|
+
# Calculate coverage stats if requested or configured
|
|
163
|
+
coverage = result.documentation_coverage if options[:stats] || options[:min_coverage] || config.min_coverage
|
|
164
|
+
|
|
165
|
+
# Show coverage stats if available
|
|
166
|
+
if coverage && (options[:stats] || options[:quiet])
|
|
167
|
+
puts "\nDocumentation Coverage: #{coverage[:coverage].round(2)}%"
|
|
168
|
+
puts " Total objects: #{coverage[:total]}"
|
|
169
|
+
puts " Documented: #{coverage[:documented]}"
|
|
170
|
+
puts " Undocumented: #{coverage[:total] - coverage[:documented]}"
|
|
171
|
+
|
|
172
|
+
if config.min_coverage
|
|
173
|
+
if coverage[:coverage] >= config.min_coverage
|
|
174
|
+
puts " Status: ✓ Meets minimum (#{config.min_coverage}%)"
|
|
175
|
+
else
|
|
176
|
+
puts " Status: ✗ Below minimum (#{config.min_coverage}%)"
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
puts
|
|
180
|
+
end
|
|
181
|
+
|
|
96
182
|
if result.clean?
|
|
97
|
-
|
|
183
|
+
# Still check coverage requirement even if no offenses
|
|
184
|
+
if coverage && config.min_coverage && coverage[:coverage] < config.min_coverage
|
|
185
|
+
puts "Error: Documentation coverage #{coverage[:coverage].round(2)}% is below minimum #{config.min_coverage}%"
|
|
186
|
+
exit result.exit_code
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
puts 'No offenses found' unless options[:quiet]
|
|
98
190
|
exit 0
|
|
99
191
|
else
|
|
100
192
|
# Show statistics if requested or in quiet mode
|
|
101
193
|
if options[:stats] || options[:quiet]
|
|
102
194
|
stats = result.statistics
|
|
103
|
-
puts "
|
|
195
|
+
puts "#{result.count} offense(s) detected"
|
|
104
196
|
puts " Errors: #{stats[:error]}"
|
|
105
197
|
puts " Warnings: #{stats[:warning]}"
|
|
106
198
|
puts " Conventions: #{stats[:convention]}"
|
|
@@ -58,7 +58,10 @@ module Yard
|
|
|
58
58
|
# @param command_string [String] the command to execute
|
|
59
59
|
# @return [Hash] hash with stdout, stderr, exit_code keys
|
|
60
60
|
def execute_command(command_string)
|
|
61
|
-
|
|
61
|
+
# Set up environment to load IRB shim before YARD (Ruby 3.5+ compatibility)
|
|
62
|
+
env = build_environment_with_shim
|
|
63
|
+
|
|
64
|
+
stdout, stderr, status = Open3.capture3(env, command_string)
|
|
62
65
|
{
|
|
63
66
|
stdout: stdout,
|
|
64
67
|
stderr: stderr,
|
|
@@ -66,6 +69,19 @@ module Yard
|
|
|
66
69
|
}
|
|
67
70
|
end
|
|
68
71
|
|
|
72
|
+
# Build environment hash with RUBYOPT to load IRB shim
|
|
73
|
+
# This ensures the shim is loaded in subprocesses (like yard list commands)
|
|
74
|
+
# @return [Hash] environment variables for command execution
|
|
75
|
+
def build_environment_with_shim
|
|
76
|
+
shim_path = File.expand_path('ext/irb_notifier_shim.rb', __dir__)
|
|
77
|
+
rubyopt = "-r#{shim_path}"
|
|
78
|
+
|
|
79
|
+
# Preserve existing RUBYOPT if present
|
|
80
|
+
rubyopt = "#{ENV['RUBYOPT'].strip} #{rubyopt}" if ENV['RUBYOPT']
|
|
81
|
+
|
|
82
|
+
{ 'RUBYOPT' => rubyopt }
|
|
83
|
+
end
|
|
84
|
+
|
|
69
85
|
# Deep clone a hash to prevent modifications to cached data
|
|
70
86
|
# @param hash [Hash] the hash to clone
|
|
71
87
|
# @return [Hash] deep cloned hash
|
data/lib/yard/lint/config.rb
CHANGED
|
@@ -129,6 +129,19 @@ module Yard
|
|
|
129
129
|
all_validators['FailOnSeverity'] || 'warning'
|
|
130
130
|
end
|
|
131
131
|
|
|
132
|
+
# Diff mode default base ref (main or master)
|
|
133
|
+
# @return [String, nil] default base ref for diff mode
|
|
134
|
+
def diff_mode_default_base_ref
|
|
135
|
+
diff_config = all_validators['DiffMode'] || {}
|
|
136
|
+
diff_config['DefaultBaseRef']
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Minimum documentation coverage percentage required
|
|
140
|
+
# @return [Float, nil] minimum coverage percentage (0-100) or nil if not set
|
|
141
|
+
def min_coverage
|
|
142
|
+
all_validators['MinCoverage']
|
|
143
|
+
end
|
|
144
|
+
|
|
132
145
|
# Check if a validator is enabled
|
|
133
146
|
# @param validator_name [String] full validator name (e.g., 'Tags/Order')
|
|
134
147
|
# @return [Boolean] true if validator is enabled
|
|
@@ -192,6 +205,13 @@ module Yard
|
|
|
192
205
|
@raw_config['AllValidators']['FailOnSeverity'] = value
|
|
193
206
|
end
|
|
194
207
|
|
|
208
|
+
# Set minimum coverage percentage
|
|
209
|
+
# @param value [Float] minimum coverage percentage (0-100)
|
|
210
|
+
def min_coverage=(value)
|
|
211
|
+
@raw_config['AllValidators'] ||= {}
|
|
212
|
+
@raw_config['AllValidators']['MinCoverage'] = value
|
|
213
|
+
end
|
|
214
|
+
|
|
195
215
|
# Allow hash-like access for convenience
|
|
196
216
|
# @param key [Symbol, String] attribute name to access
|
|
197
217
|
# @return [Object, nil] attribute value or nil if not found
|
|
@@ -199,8 +219,6 @@ module Yard
|
|
|
199
219
|
respond_to?(key) ? send(key) : nil
|
|
200
220
|
end
|
|
201
221
|
|
|
202
|
-
private
|
|
203
|
-
|
|
204
222
|
# Generic helper to set validator configuration
|
|
205
223
|
# @param validator_name [String] full validator name (e.g., 'Tags/Order')
|
|
206
224
|
# @param key [String] configuration key
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
# Generates default .yard-lint.yml configuration file
|
|
6
|
+
class ConfigGenerator
|
|
7
|
+
# Default configuration template
|
|
8
|
+
DEFAULT_CONFIG = <<~YAML
|
|
9
|
+
# YARD-Lint Configuration
|
|
10
|
+
# See https://github.com/mensfeld/yard-lint for documentation
|
|
11
|
+
|
|
12
|
+
# Global settings for all validators
|
|
13
|
+
AllValidators:
|
|
14
|
+
# YARD command-line options (applied to all validators by default)
|
|
15
|
+
YardOptions:
|
|
16
|
+
- --private
|
|
17
|
+
- --protected
|
|
18
|
+
|
|
19
|
+
# Global file exclusion patterns
|
|
20
|
+
Exclude:
|
|
21
|
+
- '\\.git'
|
|
22
|
+
- 'vendor/**/*'
|
|
23
|
+
- 'node_modules/**/*'
|
|
24
|
+
- 'spec/**/*'
|
|
25
|
+
- 'test/**/*'
|
|
26
|
+
|
|
27
|
+
# Exit code behavior (error, warning, convention, never)
|
|
28
|
+
FailOnSeverity: warning
|
|
29
|
+
|
|
30
|
+
# Minimum documentation coverage percentage (0-100)
|
|
31
|
+
# Fails if coverage is below this threshold
|
|
32
|
+
# MinCoverage: 80.0
|
|
33
|
+
|
|
34
|
+
# Diff mode settings
|
|
35
|
+
DiffMode:
|
|
36
|
+
# Default base ref for --diff (auto-detects main/master if not specified)
|
|
37
|
+
DefaultBaseRef: ~
|
|
38
|
+
|
|
39
|
+
# Documentation validators
|
|
40
|
+
Documentation/UndocumentedObjects:
|
|
41
|
+
Description: 'Checks for classes, modules, and methods without documentation.'
|
|
42
|
+
Enabled: true
|
|
43
|
+
Severity: warning
|
|
44
|
+
ExcludedMethods:
|
|
45
|
+
- 'initialize/0' # Exclude parameter-less initialize
|
|
46
|
+
- '/^_/' # Exclude private methods (by convention)
|
|
47
|
+
|
|
48
|
+
Documentation/UndocumentedMethodArguments:
|
|
49
|
+
Description: 'Checks for method parameters without @param tags.'
|
|
50
|
+
Enabled: true
|
|
51
|
+
Severity: warning
|
|
52
|
+
|
|
53
|
+
Documentation/UndocumentedBooleanMethods:
|
|
54
|
+
Description: 'Checks that question mark methods document their boolean return.'
|
|
55
|
+
Enabled: true
|
|
56
|
+
Severity: warning
|
|
57
|
+
|
|
58
|
+
Documentation/UndocumentedOptions:
|
|
59
|
+
Description: 'Detects methods with options hash parameters but no @option tags.'
|
|
60
|
+
Enabled: true
|
|
61
|
+
Severity: warning
|
|
62
|
+
|
|
63
|
+
Documentation/MarkdownSyntax:
|
|
64
|
+
Description: 'Detects common markdown syntax errors in documentation.'
|
|
65
|
+
Enabled: true
|
|
66
|
+
Severity: warning
|
|
67
|
+
|
|
68
|
+
# Tags validators
|
|
69
|
+
Tags/Order:
|
|
70
|
+
Description: 'Enforces consistent ordering of YARD tags.'
|
|
71
|
+
Enabled: true
|
|
72
|
+
Severity: convention
|
|
73
|
+
EnforcedOrder:
|
|
74
|
+
- param
|
|
75
|
+
- option
|
|
76
|
+
- return
|
|
77
|
+
- raise
|
|
78
|
+
- example
|
|
79
|
+
|
|
80
|
+
Tags/InvalidTypes:
|
|
81
|
+
Description: 'Validates type definitions in @param, @return, @option tags.'
|
|
82
|
+
Enabled: true
|
|
83
|
+
Severity: warning
|
|
84
|
+
ValidatedTags:
|
|
85
|
+
- param
|
|
86
|
+
- option
|
|
87
|
+
- return
|
|
88
|
+
|
|
89
|
+
Tags/TypeSyntax:
|
|
90
|
+
Description: 'Validates YARD type syntax using YARD parser.'
|
|
91
|
+
Enabled: true
|
|
92
|
+
Severity: warning
|
|
93
|
+
ValidatedTags:
|
|
94
|
+
- param
|
|
95
|
+
- option
|
|
96
|
+
- return
|
|
97
|
+
- yieldreturn
|
|
98
|
+
|
|
99
|
+
Tags/MeaninglessTag:
|
|
100
|
+
Description: 'Detects @param/@option tags on classes, modules, or constants.'
|
|
101
|
+
Enabled: true
|
|
102
|
+
Severity: warning
|
|
103
|
+
CheckedTags:
|
|
104
|
+
- param
|
|
105
|
+
- option
|
|
106
|
+
InvalidObjectTypes:
|
|
107
|
+
- class
|
|
108
|
+
- module
|
|
109
|
+
- constant
|
|
110
|
+
|
|
111
|
+
Tags/CollectionType:
|
|
112
|
+
Description: 'Validates Hash collection syntax consistency.'
|
|
113
|
+
Enabled: true
|
|
114
|
+
Severity: convention
|
|
115
|
+
EnforcedStyle: long # 'long' for Hash{K => V} (YARD standard), 'short' for {K => V}
|
|
116
|
+
ValidatedTags:
|
|
117
|
+
- param
|
|
118
|
+
- option
|
|
119
|
+
- return
|
|
120
|
+
- yieldreturn
|
|
121
|
+
|
|
122
|
+
Tags/TagTypePosition:
|
|
123
|
+
Description: 'Validates type annotation position in tags.'
|
|
124
|
+
Enabled: true
|
|
125
|
+
Severity: convention
|
|
126
|
+
CheckedTags:
|
|
127
|
+
- param
|
|
128
|
+
- option
|
|
129
|
+
# EnforcedStyle: 'type_after_name' (YARD standard: @param name [Type])
|
|
130
|
+
# or 'type_first' (@param [Type] name)
|
|
131
|
+
EnforcedStyle: type_after_name
|
|
132
|
+
|
|
133
|
+
Tags/ApiTags:
|
|
134
|
+
Description: 'Enforces @api tags on public objects.'
|
|
135
|
+
Enabled: false # Opt-in validator
|
|
136
|
+
Severity: warning
|
|
137
|
+
AllowedApis:
|
|
138
|
+
- public
|
|
139
|
+
- private
|
|
140
|
+
- internal
|
|
141
|
+
|
|
142
|
+
Tags/OptionTags:
|
|
143
|
+
Description: 'Requires @option tags for methods with options parameters.'
|
|
144
|
+
Enabled: true
|
|
145
|
+
Severity: warning
|
|
146
|
+
|
|
147
|
+
# Warnings validators - catches YARD parser errors
|
|
148
|
+
Warnings/UnknownTag:
|
|
149
|
+
Description: 'Detects unknown YARD tags.'
|
|
150
|
+
Enabled: true
|
|
151
|
+
Severity: error
|
|
152
|
+
|
|
153
|
+
Warnings/UnknownDirective:
|
|
154
|
+
Description: 'Detects unknown YARD directives.'
|
|
155
|
+
Enabled: true
|
|
156
|
+
Severity: error
|
|
157
|
+
|
|
158
|
+
Warnings/InvalidTagFormat:
|
|
159
|
+
Description: 'Detects malformed tag syntax.'
|
|
160
|
+
Enabled: true
|
|
161
|
+
Severity: error
|
|
162
|
+
|
|
163
|
+
Warnings/InvalidDirectiveFormat:
|
|
164
|
+
Description: 'Detects malformed directive syntax.'
|
|
165
|
+
Enabled: true
|
|
166
|
+
Severity: error
|
|
167
|
+
|
|
168
|
+
Warnings/DuplicatedParameterName:
|
|
169
|
+
Description: 'Detects duplicate @param tags.'
|
|
170
|
+
Enabled: true
|
|
171
|
+
Severity: error
|
|
172
|
+
|
|
173
|
+
Warnings/UnknownParameterName:
|
|
174
|
+
Description: 'Detects @param tags for non-existent parameters.'
|
|
175
|
+
Enabled: true
|
|
176
|
+
Severity: error
|
|
177
|
+
|
|
178
|
+
# Semantic validators
|
|
179
|
+
Semantic/AbstractMethods:
|
|
180
|
+
Description: 'Ensures @abstract methods do not have real implementations.'
|
|
181
|
+
Enabled: true
|
|
182
|
+
Severity: warning
|
|
183
|
+
YAML
|
|
184
|
+
|
|
185
|
+
# Generate .yard-lint.yml file in current directory
|
|
186
|
+
# @param force [Boolean] overwrite existing file if true
|
|
187
|
+
# @return [Boolean] true if file was created, false if already exists
|
|
188
|
+
def self.generate(force: false)
|
|
189
|
+
config_path = File.join(Dir.pwd, Config::DEFAULT_CONFIG_FILE)
|
|
190
|
+
|
|
191
|
+
if File.exist?(config_path) && !force
|
|
192
|
+
false
|
|
193
|
+
else
|
|
194
|
+
File.write(config_path, DEFAULT_CONFIG)
|
|
195
|
+
true
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Shim for IRB::Notifier to avoid IRB dependency in Ruby 3.5+
|
|
4
|
+
#
|
|
5
|
+
# YARD's legacy parser vendors old IRB code that depends on IRB::Notifier.
|
|
6
|
+
# In Ruby 3.5+, IRB is no longer part of the default gems and must be explicitly installed.
|
|
7
|
+
# This shim provides just enough functionality to keep YARD's legacy parser working
|
|
8
|
+
# without requiring the full IRB gem as a dependency.
|
|
9
|
+
#
|
|
10
|
+
# The notifier is only used for debug output in YARD's legacy parser, which we don't need.
|
|
11
|
+
#
|
|
12
|
+
# IMPORTANT: This shim only loads if IRB::Notifier is not already defined.
|
|
13
|
+
# If IRB gem is present, we use the real implementation instead.
|
|
14
|
+
|
|
15
|
+
# Only load the shim if IRB::Notifier is not already defined
|
|
16
|
+
unless defined?(IRB::Notifier)
|
|
17
|
+
# Try to load the real IRB notifier first
|
|
18
|
+
# If it fails (IRB not installed), we'll provide our shim
|
|
19
|
+
begin
|
|
20
|
+
# Suppress warnings during require attempt (Ruby 3.5+ warns about missing default gems)
|
|
21
|
+
original_verbose = $VERBOSE
|
|
22
|
+
$VERBOSE = nil
|
|
23
|
+
require 'irb/notifier'
|
|
24
|
+
rescue LoadError
|
|
25
|
+
# IRB not available, use our shim
|
|
26
|
+
# Mark as loaded to prevent further require attempts
|
|
27
|
+
$LOADED_FEATURES << 'irb/notifier.rb'
|
|
28
|
+
|
|
29
|
+
module IRB
|
|
30
|
+
# Minimal Notifier implementation that does nothing
|
|
31
|
+
# YARD's legacy parser uses this for debug output which we can safely ignore
|
|
32
|
+
class Notifier
|
|
33
|
+
# No-op message level constant
|
|
34
|
+
D_NOMSG = 0
|
|
35
|
+
|
|
36
|
+
class << self
|
|
37
|
+
# Returns a no-op notifier
|
|
38
|
+
# @param _prefix [String] notification prefix (ignored)
|
|
39
|
+
# @return [NoOpNotifier] a notifier that does nothing
|
|
40
|
+
def def_notifier(_prefix)
|
|
41
|
+
NoOpNotifier.new
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# A notifier that silently discards all output
|
|
46
|
+
class NoOpNotifier
|
|
47
|
+
attr_accessor :level
|
|
48
|
+
|
|
49
|
+
def initialize
|
|
50
|
+
@level = Notifier::D_NOMSG
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Returns a no-op notifier for any sub-level
|
|
54
|
+
# @param _level [Integer] notification level (ignored)
|
|
55
|
+
# @param _prefix [String] notification prefix (ignored)
|
|
56
|
+
# @return [NoOpNotifier] a notifier that does nothing
|
|
57
|
+
def def_notifier(_level, _prefix)
|
|
58
|
+
NoOpNotifier.new
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Silently ignore pretty-print calls
|
|
62
|
+
# @param _obj [Object] object to pretty-print (ignored)
|
|
63
|
+
# @return [nil]
|
|
64
|
+
def pp(_obj)
|
|
65
|
+
nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Silently ignore print calls
|
|
69
|
+
# @param _args [Array] print arguments (ignored)
|
|
70
|
+
# @return [nil]
|
|
71
|
+
def print(*_args)
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Silently ignore puts calls
|
|
76
|
+
# @param _args [Array] puts arguments (ignored)
|
|
77
|
+
# @return [nil]
|
|
78
|
+
def puts(*_args)
|
|
79
|
+
nil
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Silently ignore printf calls
|
|
83
|
+
# @param _args [Array] printf arguments (ignored)
|
|
84
|
+
# @return [nil]
|
|
85
|
+
def printf(*_args)
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
ensure
|
|
92
|
+
# Restore original verbosity setting
|
|
93
|
+
$VERBOSE = original_verbose
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
# Git integration for diff mode functionality
|
|
6
|
+
module Git
|
|
7
|
+
# Custom error class for Git-related errors
|
|
8
|
+
class Error < StandardError; end
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
# Detect the default branch (main or master)
|
|
12
|
+
# @return [String] 'main', 'master', or nil if neither exists
|
|
13
|
+
def default_branch
|
|
14
|
+
# Try main first (modern default)
|
|
15
|
+
return 'main' if branch_exists?('main')
|
|
16
|
+
# Fall back to master (legacy default)
|
|
17
|
+
return 'master' if branch_exists?('master')
|
|
18
|
+
|
|
19
|
+
nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Check if a git ref exists
|
|
23
|
+
# @param ref [String] the git ref to check
|
|
24
|
+
# @return [Boolean] true if ref exists
|
|
25
|
+
def branch_exists?(ref)
|
|
26
|
+
_stdout, _stderr, status = Open3.capture3('git', 'rev-parse', '--verify', '--quiet', ref)
|
|
27
|
+
status.success?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Get files changed since a base ref
|
|
31
|
+
# @param base_ref [String, nil] the base ref to compare against (nil for auto-detect)
|
|
32
|
+
# @param path [String] the path to filter files within
|
|
33
|
+
# @return [Array<String>] absolute paths to changed Ruby files
|
|
34
|
+
def changed_files(base_ref, path)
|
|
35
|
+
base_ref ||= default_branch
|
|
36
|
+
raise Error, 'Could not detect default branch (main/master)' unless base_ref
|
|
37
|
+
|
|
38
|
+
ensure_git_repository!
|
|
39
|
+
|
|
40
|
+
# Use three-dot diff to compare against merge base
|
|
41
|
+
stdout, stderr, status = Open3.capture3('git', 'diff', '--name-only', "#{base_ref}...HEAD")
|
|
42
|
+
|
|
43
|
+
unless status.success?
|
|
44
|
+
raise Error, "Git diff failed: #{stderr.strip}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
filter_ruby_files(stdout.split("\n"), path)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Get staged files (files in git index)
|
|
51
|
+
# @param path [String] the path to filter files within
|
|
52
|
+
# @return [Array<String>] absolute paths to staged Ruby files
|
|
53
|
+
def staged_files(path)
|
|
54
|
+
ensure_git_repository!
|
|
55
|
+
|
|
56
|
+
# ACM filter: Added, Copied, Modified (exclude Deleted)
|
|
57
|
+
stdout, stderr, status = Open3.capture3(
|
|
58
|
+
'git', 'diff', '--cached', '--name-only', '--diff-filter=ACM'
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
unless status.success?
|
|
62
|
+
raise Error, "Git diff failed: #{stderr.strip}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
filter_ruby_files(stdout.split("\n"), path)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Get uncommitted files (all changes in working directory)
|
|
69
|
+
# @param path [String] the path to filter files within
|
|
70
|
+
# @return [Array<String>] absolute paths to uncommitted Ruby files
|
|
71
|
+
def uncommitted_files(path)
|
|
72
|
+
ensure_git_repository!
|
|
73
|
+
|
|
74
|
+
# Get both staged and unstaged changes
|
|
75
|
+
stdout, stderr, status = Open3.capture3('git', 'diff', '--name-only', 'HEAD')
|
|
76
|
+
|
|
77
|
+
unless status.success?
|
|
78
|
+
raise Error, "Git diff failed: #{stderr.strip}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
filter_ruby_files(stdout.split("\n"), path)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
# Ensure we're in a git repository
|
|
87
|
+
# @raise [Error] if not in a git repository
|
|
88
|
+
def ensure_git_repository!
|
|
89
|
+
_stdout, _stderr, status = Open3.capture3('git', 'rev-parse', '--git-dir')
|
|
90
|
+
|
|
91
|
+
return if status.success?
|
|
92
|
+
|
|
93
|
+
raise Error, 'Not a git repository'
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Filter for Ruby files within path and convert to absolute paths
|
|
97
|
+
# @param files [Array<String>] relative file paths from git
|
|
98
|
+
# @param path [String] the base path to filter within
|
|
99
|
+
# @return [Array<String>] absolute paths to Ruby files that exist
|
|
100
|
+
def filter_ruby_files(files, path)
|
|
101
|
+
base_path = File.expand_path(path)
|
|
102
|
+
|
|
103
|
+
files
|
|
104
|
+
.select { |f| f.end_with?('.rb') }
|
|
105
|
+
.map { |f| File.expand_path(f) }
|
|
106
|
+
.select { |f| File.exist?(f) } # Skip deleted files
|
|
107
|
+
.select { |f| file_within_path?(f, base_path) }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Check if file is within the specified path
|
|
111
|
+
# @param file [String] absolute file path
|
|
112
|
+
# @param base_path [String] absolute base path
|
|
113
|
+
# @return [Boolean] true if file is within base_path
|
|
114
|
+
def file_within_path?(file, base_path)
|
|
115
|
+
# Handle both directory and file base_path
|
|
116
|
+
if File.directory?(base_path)
|
|
117
|
+
file.start_with?(base_path + '/')
|
|
118
|
+
else
|
|
119
|
+
file == base_path
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -13,14 +13,16 @@ module Yard
|
|
|
13
13
|
# Convention severity level constant
|
|
14
14
|
SEVERITY_CONVENTION = 'convention'
|
|
15
15
|
|
|
16
|
-
attr_reader :config
|
|
16
|
+
attr_reader :config, :files
|
|
17
17
|
|
|
18
18
|
# Initialize aggregate result with array of validator results
|
|
19
19
|
# @param results [Array<Results::Base>] array of validator result objects
|
|
20
20
|
# @param config [Config, nil] configuration object
|
|
21
|
-
|
|
21
|
+
# @param files [Array<String>, nil] array of files that were analyzed
|
|
22
|
+
def initialize(results, config = nil, files = nil)
|
|
22
23
|
@results = Array(results)
|
|
23
24
|
@config = config
|
|
25
|
+
@files = Array(files)
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
# Get all offenses from all validators
|
|
@@ -60,10 +62,28 @@ module Yard
|
|
|
60
62
|
stats
|
|
61
63
|
end
|
|
62
64
|
|
|
65
|
+
# Calculate documentation coverage statistics
|
|
66
|
+
# @return [Hash] coverage statistics with :total, :documented, :coverage keys
|
|
67
|
+
def documentation_coverage
|
|
68
|
+
return @documentation_coverage if defined?(@documentation_coverage)
|
|
69
|
+
|
|
70
|
+
return nil unless @config && !@files.empty?
|
|
71
|
+
|
|
72
|
+
calculator = StatsCalculator.new(@config, @files)
|
|
73
|
+
@documentation_coverage = calculator.calculate
|
|
74
|
+
end
|
|
75
|
+
|
|
63
76
|
# Determine exit code based on configured fail_on_severity
|
|
64
77
|
# Uses the config object stored during initialization
|
|
65
78
|
# @return [Integer] 0 for success, 1 for failure
|
|
66
79
|
def exit_code
|
|
80
|
+
# Check minimum coverage requirement first
|
|
81
|
+
if @config&.min_coverage &&
|
|
82
|
+
documentation_coverage &&
|
|
83
|
+
documentation_coverage[:coverage] < @config.min_coverage
|
|
84
|
+
return 1
|
|
85
|
+
end
|
|
86
|
+
|
|
67
87
|
return 0 if offenses.empty?
|
|
68
88
|
return 0 unless @config # No config means don't fail
|
|
69
89
|
|