yard-lint 0.2.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +73 -1
- data/README.md +121 -89
- data/bin/yard-lint +44 -0
- data/lib/yard/lint/config.rb +35 -3
- data/lib/yard/lint/config_generator.rb +191 -0
- data/lib/yard/lint/config_loader.rb +1 -1
- data/lib/yard/lint/result_builder.rb +10 -1
- data/lib/yard/lint/validators/base.rb +77 -12
- 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/validator.rb +7 -6
- data/lib/yard/lint/validators/documentation/undocumented_boolean_methods.rb +26 -1
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments/validator.rb +4 -5
- data/lib/yard/lint/validators/documentation/undocumented_method_arguments.rb +26 -1
- data/lib/yard/lint/validators/documentation/undocumented_objects/config.rb +2 -1
- data/lib/yard/lint/validators/documentation/undocumented_objects/parser.rb +95 -5
- data/lib/yard/lint/validators/documentation/undocumented_objects/validator.rb +15 -11
- 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/validator.rb +4 -5
- data/lib/yard/lint/validators/semantic/abstract_methods.rb +31 -1
- data/lib/yard/lint/validators/tags/api_tags/validator.rb +4 -5
- data/lib/yard/lint/validators/tags/api_tags.rb +34 -1
- data/lib/yard/lint/validators/tags/collection_type/config.rb +22 -0
- data/lib/yard/lint/validators/tags/collection_type/messages_builder.rb +73 -0
- data/lib/yard/lint/validators/tags/collection_type/parser.rb +50 -0
- data/lib/yard/lint/validators/tags/collection_type/result.rb +25 -0
- data/lib/yard/lint/validators/tags/collection_type/validator.rb +92 -0
- data/lib/yard/lint/validators/tags/collection_type.rb +50 -0
- 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 +8 -8
- data/lib/yard/lint/validators/tags/invalid_types.rb +25 -1
- data/lib/yard/lint/validators/tags/meaningless_tag/config.rb +22 -0
- data/lib/yard/lint/validators/tags/meaningless_tag/messages_builder.rb +28 -0
- data/lib/yard/lint/validators/tags/meaningless_tag/parser.rb +53 -0
- data/lib/yard/lint/validators/tags/meaningless_tag/result.rb +26 -0
- data/lib/yard/lint/validators/tags/meaningless_tag/validator.rb +82 -0
- data/lib/yard/lint/validators/tags/meaningless_tag.rb +43 -0
- data/lib/yard/lint/validators/tags/option_tags/validator.rb +11 -6
- data/lib/yard/lint/validators/tags/option_tags.rb +26 -1
- data/lib/yard/lint/validators/tags/order/validator.rb +4 -5
- 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/config.rb +22 -0
- data/lib/yard/lint/validators/tags/tag_type_position/messages_builder.rb +38 -0
- data/lib/yard/lint/validators/tags/tag_type_position/parser.rb +51 -0
- data/lib/yard/lint/validators/tags/tag_type_position/result.rb +25 -0
- data/lib/yard/lint/validators/tags/tag_type_position/validator.rb +111 -0
- data/lib/yard/lint/validators/tags/tag_type_position.rb +51 -0
- data/lib/yard/lint/validators/tags/type_syntax/config.rb +21 -0
- data/lib/yard/lint/validators/tags/type_syntax/messages_builder.rb +27 -0
- data/lib/yard/lint/validators/tags/type_syntax/parser.rb +54 -0
- data/lib/yard/lint/validators/tags/type_syntax/result.rb +25 -0
- data/lib/yard/lint/validators/tags/type_syntax/validator.rb +76 -0
- data/lib/yard/lint/validators/tags/type_syntax.rb +38 -0
- data/lib/yard/lint/validators/warnings/duplicated_parameter_name/validator.rb +4 -5
- data/lib/yard/lint/validators/warnings/duplicated_parameter_name.rb +26 -1
- data/lib/yard/lint/validators/warnings/invalid_directive_format/validator.rb +4 -5
- data/lib/yard/lint/validators/warnings/invalid_directive_format.rb +26 -1
- data/lib/yard/lint/validators/warnings/invalid_tag_format/validator.rb +4 -5
- data/lib/yard/lint/validators/warnings/invalid_tag_format.rb +25 -1
- data/lib/yard/lint/validators/warnings/unknown_directive/validator.rb +4 -5
- data/lib/yard/lint/validators/warnings/unknown_directive.rb +26 -1
- data/lib/yard/lint/validators/warnings/unknown_parameter_name/parser.rb +4 -1
- data/lib/yard/lint/validators/warnings/unknown_parameter_name/validator.rb +4 -5
- data/lib/yard/lint/validators/warnings/unknown_parameter_name.rb +23 -1
- data/lib/yard/lint/validators/warnings/unknown_tag/validator.rb +4 -5
- data/lib/yard/lint/validators/warnings/unknown_tag.rb +26 -1
- data/lib/yard/lint/version.rb +1 -1
- data/lib/yard/lint.rb +1 -0
- data/misc/logo.png +0 -0
- metadata +64 -1
|
@@ -109,7 +109,16 @@ module Yard
|
|
|
109
109
|
return [] unless stdout
|
|
110
110
|
|
|
111
111
|
parsers = discover_parsers(validator_module)
|
|
112
|
-
parsers.flat_map
|
|
112
|
+
parsers.flat_map do |parser|
|
|
113
|
+
parser_instance = parser.new
|
|
114
|
+
# Try passing config to parser if it accepts it (for filtering)
|
|
115
|
+
# Otherwise, call without config for backwards compatibility
|
|
116
|
+
begin
|
|
117
|
+
parser_instance.call(stdout, config: config)
|
|
118
|
+
rescue ArgumentError
|
|
119
|
+
parser_instance.call(stdout)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
113
122
|
end
|
|
114
123
|
|
|
115
124
|
# Auto-discover parser classes in a validator module
|
|
@@ -17,12 +17,11 @@ module Yard
|
|
|
17
17
|
'--no-progress'
|
|
18
18
|
].freeze
|
|
19
19
|
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
|
|
23
|
-
YARDOC_TEMP_DIR = Dir.mktmpdir.freeze
|
|
20
|
+
# Base temp directory for YARD databases
|
|
21
|
+
# Each unique set of arguments gets its own subdirectory to prevent contamination
|
|
22
|
+
YARDOC_BASE_TEMP_DIR = Dir.mktmpdir.freeze
|
|
24
23
|
|
|
25
|
-
private_constant :
|
|
24
|
+
private_constant :YARDOC_BASE_TEMP_DIR
|
|
26
25
|
|
|
27
26
|
attr_reader :config, :selection
|
|
28
27
|
|
|
@@ -42,12 +41,12 @@ module Yard
|
|
|
42
41
|
Base.instance_variable_set(:@shared_command_cache, nil)
|
|
43
42
|
end
|
|
44
43
|
|
|
45
|
-
# Clear
|
|
44
|
+
# Clear all YARD databases (primarily for testing)
|
|
46
45
|
# @return [void]
|
|
47
46
|
def clear_yard_database!
|
|
48
|
-
return unless defined?(
|
|
47
|
+
return unless defined?(YARDOC_BASE_TEMP_DIR)
|
|
49
48
|
|
|
50
|
-
FileUtils.rm_rf(Dir.glob(File.join(
|
|
49
|
+
FileUtils.rm_rf(Dir.glob(File.join(YARDOC_BASE_TEMP_DIR, '*')))
|
|
51
50
|
end
|
|
52
51
|
end
|
|
53
52
|
|
|
@@ -66,21 +65,51 @@ module Yard
|
|
|
66
65
|
return raw if selection.nil? || selection.empty?
|
|
67
66
|
|
|
68
67
|
# Anything that goes to shell needs to be escaped
|
|
69
|
-
escaped_file_names = escape(selection)
|
|
68
|
+
escaped_file_names = escape(selection)
|
|
70
69
|
|
|
71
|
-
|
|
70
|
+
# Use a unique YARD database per set of arguments to prevent contamination
|
|
71
|
+
# between validators with different file selections or options
|
|
72
|
+
yardoc_dir = yardoc_temp_dir_for_arguments(escaped_file_names.join(' '))
|
|
73
|
+
|
|
74
|
+
# For large file lists, use a temporary file to avoid ARG_MAX limits
|
|
75
|
+
# Write file paths to temp file, one per line
|
|
76
|
+
Tempfile.create(['yard_files', '.txt']) do |f|
|
|
77
|
+
escaped_file_names.each { |file| f.puts(file) }
|
|
78
|
+
f.flush
|
|
79
|
+
|
|
80
|
+
yard_cmd(yardoc_dir, f.path)
|
|
81
|
+
end
|
|
72
82
|
end
|
|
73
83
|
|
|
74
84
|
private
|
|
75
85
|
|
|
86
|
+
# Returns a unique YARD database directory for the given arguments
|
|
87
|
+
# Uses SHA256 hash of the normalized arguments to ensure different file sets
|
|
88
|
+
# get separate databases, preventing contamination
|
|
89
|
+
# @param escaped_file_names [String] escaped file names to process
|
|
90
|
+
# @return [String] path to the YARD database directory
|
|
91
|
+
def yardoc_temp_dir_for_arguments(escaped_file_names)
|
|
92
|
+
# Combine all arguments that affect YARD output
|
|
93
|
+
all_args = "#{shell_arguments} #{escaped_file_names}"
|
|
94
|
+
|
|
95
|
+
# Create a hash of the arguments for a unique directory name
|
|
96
|
+
args_hash = Digest::SHA256.hexdigest(all_args)
|
|
97
|
+
|
|
98
|
+
# Create subdirectory under base temp dir
|
|
99
|
+
dir = File.join(YARDOC_BASE_TEMP_DIR, args_hash)
|
|
100
|
+
FileUtils.mkdir_p(dir) unless File.directory?(dir)
|
|
101
|
+
|
|
102
|
+
dir
|
|
103
|
+
end
|
|
104
|
+
|
|
76
105
|
# @return [String] all arguments with which YARD command should be executed
|
|
77
106
|
def shell_arguments
|
|
78
|
-
validator_name = self.class.name
|
|
107
|
+
validator_name = self.class.name&.split('::')&.then do |parts|
|
|
79
108
|
idx = parts.index('Validators')
|
|
80
109
|
next config.options unless idx && parts[idx + 1] && parts[idx + 2]
|
|
81
110
|
|
|
82
111
|
"#{parts[idx + 1]}/#{parts[idx + 2]}"
|
|
83
|
-
end
|
|
112
|
+
end || config.options
|
|
84
113
|
|
|
85
114
|
yard_options = config.validator_yard_options(validator_name)
|
|
86
115
|
args = escape(yard_options).join(' ')
|
|
@@ -114,6 +143,42 @@ module Yard
|
|
|
114
143
|
def shell(cmd)
|
|
115
144
|
self.class.command_cache.execute(cmd)
|
|
116
145
|
end
|
|
146
|
+
|
|
147
|
+
# Retrieves configuration value with fallback to default
|
|
148
|
+
# Automatically determines the validator name from the class namespace
|
|
149
|
+
#
|
|
150
|
+
# @param key [String] the configuration key to retrieve
|
|
151
|
+
# @return [Object] the configured value or default value from the validator's Config.defaults
|
|
152
|
+
# @note The validator name is automatically extracted from the class namespace.
|
|
153
|
+
# For example, Yard::Lint::Validators::Tags::RedundantParamDescription::Validator
|
|
154
|
+
# becomes 'Tags/RedundantParamDescription'
|
|
155
|
+
# @example Usage in a validator (e.g., Tags::RedundantParamDescription)
|
|
156
|
+
# def config_articles
|
|
157
|
+
# config_or_default('Articles')
|
|
158
|
+
# end
|
|
159
|
+
def config_or_default(key)
|
|
160
|
+
validator_name = self.class.name&.split('::')&.then do |parts|
|
|
161
|
+
idx = parts.index('Validators')
|
|
162
|
+
next nil unless idx && parts[idx + 1] && parts[idx + 2]
|
|
163
|
+
|
|
164
|
+
"#{parts[idx + 1]}/#{parts[idx + 2]}"
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Get the validator module's Config class
|
|
168
|
+
validator_config_class = begin
|
|
169
|
+
# Get parent module (e.g., Yard::Lint::Validators::Tags::RedundantParamDescription)
|
|
170
|
+
parent_module = self.class.name.split('::')[0..-2].join('::')
|
|
171
|
+
Object.const_get("#{parent_module}::Config")
|
|
172
|
+
rescue NameError
|
|
173
|
+
nil
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
defaults = validator_config_class&.defaults || {}
|
|
177
|
+
|
|
178
|
+
return defaults[key] unless validator_name
|
|
179
|
+
|
|
180
|
+
config.validator_config(validator_name, key) || defaults[key]
|
|
181
|
+
end
|
|
117
182
|
end
|
|
118
183
|
end
|
|
119
184
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module MarkdownSyntax
|
|
8
|
+
# Configuration for MarkdownSyntax validator
|
|
9
|
+
class Config < ::Yard::Lint::Validators::Config
|
|
10
|
+
self.id = :markdown_syntax
|
|
11
|
+
self.defaults = {
|
|
12
|
+
'Enabled' => true,
|
|
13
|
+
'Severity' => 'warning'
|
|
14
|
+
}.freeze
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module MarkdownSyntax
|
|
8
|
+
# Builds human-readable messages for MarkdownSyntax violations
|
|
9
|
+
class MessagesBuilder
|
|
10
|
+
# Maps markdown syntax error types to human-readable descriptions
|
|
11
|
+
ERROR_DESCRIPTIONS = {
|
|
12
|
+
'unclosed_backtick' => 'Unclosed backtick in documentation',
|
|
13
|
+
'unclosed_code_block' => 'Unclosed code block (```) in documentation',
|
|
14
|
+
'unclosed_bold' => 'Unclosed bold formatting (**) in documentation',
|
|
15
|
+
'invalid_list_marker' => 'Invalid list marker (use - or * instead)'
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
# Formats a violation message
|
|
20
|
+
# @param offense [Hash] the offense details
|
|
21
|
+
# @return [String] formatted message
|
|
22
|
+
def call(offense)
|
|
23
|
+
object_name = offense[:object_name]
|
|
24
|
+
errors = offense[:errors]
|
|
25
|
+
|
|
26
|
+
error_messages = errors.map do |error|
|
|
27
|
+
if error.start_with?('invalid_list_marker:')
|
|
28
|
+
line_num = error.split(':').last
|
|
29
|
+
"#{ERROR_DESCRIPTIONS['invalid_list_marker']} at line #{line_num}"
|
|
30
|
+
else
|
|
31
|
+
ERROR_DESCRIPTIONS[error] || error
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
"Markdown syntax errors in '#{object_name}': " \
|
|
36
|
+
"#{error_messages.join(', ')}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module MarkdownSyntax
|
|
8
|
+
# Parses YARD output for markdown syntax violations
|
|
9
|
+
class Parser < Parsers::Base
|
|
10
|
+
# Parse YARD output into structured violations
|
|
11
|
+
# @param output [String] raw YARD output
|
|
12
|
+
# @return [Array<Hash>] array of violation hashes
|
|
13
|
+
def call(output)
|
|
14
|
+
return [] if output.nil? || output.empty?
|
|
15
|
+
|
|
16
|
+
violations = []
|
|
17
|
+
lines = output.lines.map(&:chomp)
|
|
18
|
+
|
|
19
|
+
i = 0
|
|
20
|
+
while i < lines.size
|
|
21
|
+
line = lines[i]
|
|
22
|
+
|
|
23
|
+
# Match location line: "file:line: object_name"
|
|
24
|
+
if (location_match = line.match(/^(.+):(\d+): (.+)$/))
|
|
25
|
+
file_path = location_match[1]
|
|
26
|
+
line_number = location_match[2].to_i
|
|
27
|
+
object_name = location_match[3]
|
|
28
|
+
|
|
29
|
+
# Next line contains error types
|
|
30
|
+
i += 1
|
|
31
|
+
next unless i < lines.size
|
|
32
|
+
|
|
33
|
+
errors = lines[i].split('|')
|
|
34
|
+
|
|
35
|
+
violations << {
|
|
36
|
+
location: file_path,
|
|
37
|
+
line: line_number,
|
|
38
|
+
object_name: object_name,
|
|
39
|
+
errors: errors
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
i += 1
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
violations
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module MarkdownSyntax
|
|
8
|
+
# Result object for markdown syntax validation
|
|
9
|
+
class Result < Results::Base
|
|
10
|
+
self.default_severity = 'warning'
|
|
11
|
+
self.offense_type = 'line'
|
|
12
|
+
self.offense_name = 'MarkdownSyntax'
|
|
13
|
+
|
|
14
|
+
# Build human-readable message for markdown syntax offense
|
|
15
|
+
# @param offense [Hash] offense data with :object_name and :errors
|
|
16
|
+
# @return [String] formatted message
|
|
17
|
+
def build_message(offense)
|
|
18
|
+
MessagesBuilder.call(offense)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module MarkdownSyntax
|
|
8
|
+
# Validates markdown syntax in documentation
|
|
9
|
+
class Validator < Validators::Base
|
|
10
|
+
# YARD query to extract docstrings and check for markdown errors
|
|
11
|
+
# @return [String] YARD Ruby query code
|
|
12
|
+
def query
|
|
13
|
+
<<~QUERY.strip
|
|
14
|
+
'docstring_text = object.docstring.to_s; unless docstring_text.empty?; errors = []; backtick_count = docstring_text.scan(/\\x60/).count; errors << "unclosed_backtick" if backtick_count.odd?; code_block_count = docstring_text.scan(/^```/).count; errors << "unclosed_code_block" if code_block_count.odd?; non_code_text = docstring_text.gsub(/\\x60[^\\x60]*\\x60/, ""); bold_count = non_code_text.scan(/\\*\\*/).count; errors << "unclosed_bold" if bold_count.odd?; lines = docstring_text.lines; lines.each_with_index do |line, line_idx|; stripped = line.strip; errors << "invalid_list_marker:" + (line_idx + 1).to_s if stripped =~ /^[•·]/; end; unless errors.empty?; puts object.file + ":" + object.line.to_s + ": " + object.title; puts errors.join("|"); end; end; false'
|
|
15
|
+
QUERY
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Builds and executes the YARD command to detect markdown syntax errors
|
|
19
|
+
# @param dir [String] the directory containing the .yardoc database
|
|
20
|
+
# @param file_list_path [String] path to file containing list of files to analyze
|
|
21
|
+
# @return [String] command output
|
|
22
|
+
def yard_cmd(dir, file_list_path)
|
|
23
|
+
cmd = <<~CMD
|
|
24
|
+
cat #{Shellwords.escape(file_list_path)} | xargs yard list \
|
|
25
|
+
#{shell_arguments} \
|
|
26
|
+
--query #{query} \
|
|
27
|
+
-q \
|
|
28
|
+
-b #{Shellwords.escape(dir)}
|
|
29
|
+
CMD
|
|
30
|
+
cmd = cmd.tr("\n", ' ')
|
|
31
|
+
shell(cmd)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
# MarkdownSyntax validator
|
|
8
|
+
#
|
|
9
|
+
# Validates markdown syntax in documentation comments. This validator checks
|
|
10
|
+
# for common markdown errors and formatting issues in YARD documentation
|
|
11
|
+
# strings. This validator is enabled by default.
|
|
12
|
+
#
|
|
13
|
+
# @example Bad - Invalid markdown syntax
|
|
14
|
+
# # This is [broken markdown
|
|
15
|
+
# # Another line with `unclosed code
|
|
16
|
+
# def process
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# @example Good - Valid markdown syntax
|
|
20
|
+
# # This is [valid markdown](https://example.com)
|
|
21
|
+
# # Another line with `closed code`
|
|
22
|
+
# def process
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# ## Configuration
|
|
26
|
+
#
|
|
27
|
+
# To disable this validator:
|
|
28
|
+
#
|
|
29
|
+
# Documentation/MarkdownSyntax:
|
|
30
|
+
# Enabled: false
|
|
31
|
+
#
|
|
32
|
+
module MarkdownSyntax
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -9,13 +9,15 @@ module Yard
|
|
|
9
9
|
# do not have a return type or return description documented
|
|
10
10
|
class Validator < Base
|
|
11
11
|
# Query to find all the boolean methods without proper return documentation
|
|
12
|
+
# Requires either: no @return tag, OR @return tag with no types specified
|
|
13
|
+
# Accepts @return [Boolean] without description text as valid documentation
|
|
12
14
|
QUERY = <<~QUERY.tr("\n", ' ')
|
|
13
15
|
'
|
|
14
16
|
type == :method &&
|
|
15
17
|
!is_alias? &&
|
|
16
18
|
is_explicit? &&
|
|
17
19
|
name.to_s.end_with?("?") &&
|
|
18
|
-
(tag("return").nil? || tag("return").
|
|
20
|
+
(tag("return").nil? || tag("return").types.to_a.empty?)
|
|
19
21
|
'
|
|
20
22
|
QUERY
|
|
21
23
|
|
|
@@ -25,16 +27,15 @@ module Yard
|
|
|
25
27
|
|
|
26
28
|
# Runs yard list query with proper settings on a given dir and files
|
|
27
29
|
# @param dir [String] dir where we should generate the temp docs
|
|
28
|
-
# @param
|
|
30
|
+
# @param file_list_path [String] path to temp file containing file paths (one per line)
|
|
29
31
|
# @return [Hash] shell command execution hash results
|
|
30
|
-
def yard_cmd(dir,
|
|
32
|
+
def yard_cmd(dir, file_list_path)
|
|
31
33
|
cmd = <<~CMD
|
|
32
|
-
yard list \
|
|
34
|
+
cat #{Shellwords.escape(file_list_path)} | xargs yard list \
|
|
33
35
|
#{shell_arguments} \
|
|
34
36
|
--query #{QUERY} \
|
|
35
37
|
-q \
|
|
36
|
-
-b #{Shellwords.escape(dir)}
|
|
37
|
-
#{escaped_file_names}
|
|
38
|
+
-b #{Shellwords.escape(dir)}
|
|
38
39
|
CMD
|
|
39
40
|
cmd = cmd.tr("\n", ' ')
|
|
40
41
|
|
|
@@ -4,7 +4,32 @@ module Yard
|
|
|
4
4
|
module Lint
|
|
5
5
|
module Validators
|
|
6
6
|
module Documentation
|
|
7
|
-
# UndocumentedBooleanMethods validator
|
|
7
|
+
# UndocumentedBooleanMethods validator
|
|
8
|
+
#
|
|
9
|
+
# Ensures that boolean methods (methods ending with `?`) have an explicit
|
|
10
|
+
# `@return [Boolean]` tag. Boolean methods should clearly document that they
|
|
11
|
+
# return true or false values. This validator is enabled by default.
|
|
12
|
+
#
|
|
13
|
+
# @example Bad - Missing @return tag on boolean method
|
|
14
|
+
# # Checks if the user is active
|
|
15
|
+
# def active?
|
|
16
|
+
# @active
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# @example Good - Boolean return documented
|
|
20
|
+
# # Checks if the user is active
|
|
21
|
+
# # @return [Boolean] true if the user is active
|
|
22
|
+
# def active?
|
|
23
|
+
# @active
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# ## Configuration
|
|
27
|
+
#
|
|
28
|
+
# To disable this validator:
|
|
29
|
+
#
|
|
30
|
+
# Documentation/UndocumentedBooleanMethods:
|
|
31
|
+
# Enabled: false
|
|
32
|
+
#
|
|
8
33
|
module UndocumentedBooleanMethods
|
|
9
34
|
end
|
|
10
35
|
end
|
|
@@ -29,19 +29,18 @@ module Yard
|
|
|
29
29
|
|
|
30
30
|
# Runs yard list query with proper settings on a given dir and files
|
|
31
31
|
# @param dir [String] dir where we should generate the temp docs
|
|
32
|
-
# @param
|
|
32
|
+
# @param file_list_path [String] path to temp file containing file paths (one per line)
|
|
33
33
|
# @return [Hash] shell command execution hash results
|
|
34
|
-
def yard_cmd(dir,
|
|
34
|
+
def yard_cmd(dir, file_list_path)
|
|
35
35
|
shell_args = shell_arguments
|
|
36
36
|
UNWANTED_OPTIONS.each { |opt| shell_args.gsub!(opt, '') }
|
|
37
37
|
|
|
38
38
|
cmd = <<~CMD
|
|
39
|
-
yard list \
|
|
39
|
+
cat #{Shellwords.escape(file_list_path)} | xargs yard list \
|
|
40
40
|
#{shell_args} \
|
|
41
41
|
--query #{QUERY} \
|
|
42
42
|
-q \
|
|
43
|
-
-b #{Shellwords.escape(dir)}
|
|
44
|
-
#{escaped_file_names}
|
|
43
|
+
-b #{Shellwords.escape(dir)}
|
|
45
44
|
CMD
|
|
46
45
|
cmd = cmd.tr("\n", ' ')
|
|
47
46
|
|
|
@@ -4,7 +4,32 @@ module Yard
|
|
|
4
4
|
module Lint
|
|
5
5
|
module Validators
|
|
6
6
|
module Documentation
|
|
7
|
-
# UndocumentedMethodArguments validator
|
|
7
|
+
# UndocumentedMethodArguments validator
|
|
8
|
+
#
|
|
9
|
+
# Ensures that all method parameters are documented with `@param` tags.
|
|
10
|
+
# This validator checks that every parameter in a method signature has
|
|
11
|
+
# a corresponding `@param` documentation tag. This validator is enabled
|
|
12
|
+
# by default.
|
|
13
|
+
#
|
|
14
|
+
# @example Bad - Missing @param tags
|
|
15
|
+
# # Does something with data
|
|
16
|
+
# def process(name, options)
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# @example Good - All parameters documented
|
|
20
|
+
# # Does something with data
|
|
21
|
+
# # @param name [String] the name to process
|
|
22
|
+
# # @param options [Hash] configuration options
|
|
23
|
+
# def process(name, options)
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# ## Configuration
|
|
27
|
+
#
|
|
28
|
+
# To disable this validator:
|
|
29
|
+
#
|
|
30
|
+
# Documentation/UndocumentedMethodArguments:
|
|
31
|
+
# Enabled: false
|
|
32
|
+
#
|
|
8
33
|
module UndocumentedMethodArguments
|
|
9
34
|
end
|
|
10
35
|
end
|
|
@@ -10,7 +10,8 @@ module Yard
|
|
|
10
10
|
self.id = :undocumented_objects
|
|
11
11
|
self.defaults = {
|
|
12
12
|
'Enabled' => true,
|
|
13
|
-
'Severity' => 'warning'
|
|
13
|
+
'Severity' => 'warning',
|
|
14
|
+
'ExcludedMethods' => ['initialize/0']
|
|
14
15
|
}.freeze
|
|
15
16
|
self.combines_with = ['Documentation/UndocumentedBooleanMethods'].freeze
|
|
16
17
|
end
|
|
@@ -8,14 +8,28 @@ module Yard
|
|
|
8
8
|
# Class used to extract details about undocumented objects from raw yard list output
|
|
9
9
|
# @example
|
|
10
10
|
# /path/to/file.rb:3: UndocumentedClass
|
|
11
|
-
# /path/to/file.rb:4: UndocumentedClass#method_one
|
|
11
|
+
# /path/to/file.rb:4: UndocumentedClass#method_one|2
|
|
12
12
|
class Parser < ::Yard::Lint::Parsers::Base
|
|
13
|
-
# Regex used to parse yard list output format
|
|
14
|
-
|
|
13
|
+
# Regex used to parse yard list output format
|
|
14
|
+
# Format: file.rb:LINE: ObjectName or ObjectName|ARITY
|
|
15
|
+
LINE_REGEX = /^(.+):(\d+): (.+?)(?:\|(\d+))?$/
|
|
15
16
|
|
|
16
17
|
# @param yard_list_output [String] raw yard list results string
|
|
18
|
+
# @param config [Yard::Lint::Config, nil] configuration object (optional)
|
|
19
|
+
# @param _kwargs [Hash] unused keyword arguments (for compatibility)
|
|
17
20
|
# @return [Array<Hash>] Array with undocumented objects details
|
|
18
|
-
def call(yard_list_output)
|
|
21
|
+
def call(yard_list_output, config: nil, **_kwargs)
|
|
22
|
+
excluded_methods = config&.validator_config(
|
|
23
|
+
'Documentation/UndocumentedObjects',
|
|
24
|
+
'ExcludedMethods'
|
|
25
|
+
) || []
|
|
26
|
+
|
|
27
|
+
# Ensure excluded_methods is an Array
|
|
28
|
+
excluded_methods = Array(excluded_methods)
|
|
29
|
+
|
|
30
|
+
# Sanitize patterns: remove nil, empty, whitespace-only, and normalize
|
|
31
|
+
excluded_methods = sanitize_patterns(excluded_methods)
|
|
32
|
+
|
|
19
33
|
yard_list_output
|
|
20
34
|
.split("\n")
|
|
21
35
|
.map(&:strip)
|
|
@@ -24,13 +38,89 @@ module Yard
|
|
|
24
38
|
match = line.match(LINE_REGEX)
|
|
25
39
|
next unless match
|
|
26
40
|
|
|
41
|
+
element = match[3]
|
|
42
|
+
arity = match[4]&.to_i
|
|
43
|
+
|
|
44
|
+
# Skip if method is in excluded list
|
|
45
|
+
next if method_excluded?(element, arity, excluded_methods)
|
|
46
|
+
|
|
27
47
|
{
|
|
28
48
|
location: match[1],
|
|
29
49
|
line: match[2].to_i,
|
|
30
|
-
element:
|
|
50
|
+
element: element
|
|
31
51
|
}
|
|
32
52
|
end
|
|
33
53
|
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# Checks if a method should be excluded based on ExcludedMethods config
|
|
58
|
+
# Supports: simple names, arity notation, and regex patterns
|
|
59
|
+
# @param element [String] the element name (e.g., "Class#method")
|
|
60
|
+
# @param arity [Integer, nil] number of parameters (required + optional,
|
|
61
|
+
# excluding splat and block)
|
|
62
|
+
# @param excluded_methods [Array<String>] list of exclusion patterns
|
|
63
|
+
# @return [Boolean] true if method should be excluded
|
|
64
|
+
def method_excluded?(element, arity, excluded_methods)
|
|
65
|
+
# Extract method name from element (e.g., "Foo::Bar#baz" -> "baz")
|
|
66
|
+
method_name = element.split(/[#.]/).last
|
|
67
|
+
return false unless method_name
|
|
68
|
+
|
|
69
|
+
excluded_methods.any? do |pattern|
|
|
70
|
+
case pattern
|
|
71
|
+
when %r{^/(.+)/$}
|
|
72
|
+
# Regex pattern: '/^_/' matches methods starting with _
|
|
73
|
+
match_regex_pattern(method_name, Regexp.last_match(1))
|
|
74
|
+
when %r{/\d+$}
|
|
75
|
+
# Arity pattern: 'initialize/0' checks method name and parameter count
|
|
76
|
+
match_arity_pattern(method_name, arity, pattern)
|
|
77
|
+
else
|
|
78
|
+
# Simple name match: 'initialize'
|
|
79
|
+
# Simple names match any arity (use arity notation for specific arity)
|
|
80
|
+
method_name == pattern
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Sanitize exclusion patterns
|
|
86
|
+
# @param patterns [Array] raw patterns from config
|
|
87
|
+
# @return [Array<String>] cleaned and validated patterns
|
|
88
|
+
def sanitize_patterns(patterns)
|
|
89
|
+
patterns
|
|
90
|
+
.compact # Remove nil values
|
|
91
|
+
.map { |p| p.to_s.strip } # Convert to strings and trim whitespace
|
|
92
|
+
.reject(&:empty?) # Remove empty strings
|
|
93
|
+
.reject { |p| p == '//' } # Reject empty regex (matches everything)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Match a regex pattern against method name with error handling
|
|
97
|
+
# @param method_name [String] the method name to match
|
|
98
|
+
# @param regex_pattern [String] the regex pattern (without delimiters)
|
|
99
|
+
# @return [Boolean] true if matches, false if invalid regex or no match
|
|
100
|
+
def match_regex_pattern(method_name, regex_pattern)
|
|
101
|
+
return false if regex_pattern.empty? # Empty regex would match everything
|
|
102
|
+
|
|
103
|
+
Regexp.new(regex_pattern).match?(method_name)
|
|
104
|
+
rescue RegexpError
|
|
105
|
+
# Invalid regex - skip this pattern
|
|
106
|
+
false
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Match an arity pattern like "initialize/0"
|
|
110
|
+
# @param method_name [String] the method name
|
|
111
|
+
# @param arity [Integer, nil] the method's arity
|
|
112
|
+
# @param pattern [String] the full pattern like "initialize/0"
|
|
113
|
+
# @return [Boolean] true if matches
|
|
114
|
+
def match_arity_pattern(method_name, arity, pattern)
|
|
115
|
+
pattern_name, pattern_arity_str = pattern.split('/')
|
|
116
|
+
|
|
117
|
+
# Validate arity is numeric
|
|
118
|
+
return false unless pattern_arity_str.match?(/^\d+$/)
|
|
119
|
+
|
|
120
|
+
pattern_arity = pattern_arity_str.to_i
|
|
121
|
+
|
|
122
|
+
method_name == pattern_name && arity == pattern_arity
|
|
123
|
+
end
|
|
34
124
|
end
|
|
35
125
|
end
|
|
36
126
|
end
|