yard-lint 1.0.0 → 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 +40 -0
- data/README.md +42 -265
- data/bin/yard-lint +20 -0
- data/lib/yard/lint/config.rb +0 -2
- data/lib/yard/lint/config_generator.rb +191 -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
- metadata +39 -1
|
@@ -143,6 +143,42 @@ module Yard
|
|
|
143
143
|
def shell(cmd)
|
|
144
144
|
self.class.command_cache.execute(cmd)
|
|
145
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
|
|
146
182
|
end
|
|
147
183
|
end
|
|
148
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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -4,8 +4,137 @@ module Yard
|
|
|
4
4
|
module Lint
|
|
5
5
|
module Validators
|
|
6
6
|
module Documentation
|
|
7
|
-
# UndocumentedObjects validator
|
|
8
|
-
#
|
|
7
|
+
# UndocumentedObjects validator
|
|
8
|
+
#
|
|
9
|
+
# Checks for missing documentation on classes, modules, and methods.
|
|
10
|
+
# This validator supports flexible method exclusions through the `ExcludedMethods`
|
|
11
|
+
# configuration option.
|
|
12
|
+
#
|
|
13
|
+
# ## Pattern Types
|
|
14
|
+
#
|
|
15
|
+
# The `ExcludedMethods` feature supports three pattern types for maximum flexibility:
|
|
16
|
+
#
|
|
17
|
+
# ### 1. Exact Name Matching
|
|
18
|
+
#
|
|
19
|
+
# Excludes methods with the specified name, regardless of arity:
|
|
20
|
+
#
|
|
21
|
+
# ExcludedMethods:
|
|
22
|
+
# - 'to_s' # Excludes ALL to_s methods regardless of parameters
|
|
23
|
+
# - 'inspect' # Excludes ALL inspect methods
|
|
24
|
+
#
|
|
25
|
+
# Note: Exact name matching excludes the method with **any arity**. If you need
|
|
26
|
+
# arity-specific exclusions, use arity notation instead.
|
|
27
|
+
#
|
|
28
|
+
# ### 2. Arity Notation (method_name/N)
|
|
29
|
+
#
|
|
30
|
+
# Excludes methods with specific parameter counts:
|
|
31
|
+
#
|
|
32
|
+
# ExcludedMethods:
|
|
33
|
+
# - 'initialize/0' # Only excludes initialize with NO parameters (default)
|
|
34
|
+
# - 'call/1' # Only excludes call methods with exactly 1 parameter
|
|
35
|
+
# - 'initialize/2' # Only excludes initialize with exactly 2 parameters
|
|
36
|
+
#
|
|
37
|
+
# Note: Arity counts total parameters (required + optional) excluding splat (*)
|
|
38
|
+
# and block (&) parameters.
|
|
39
|
+
#
|
|
40
|
+
# ### 3. Regex Patterns
|
|
41
|
+
#
|
|
42
|
+
# Excludes methods matching a regular expression:
|
|
43
|
+
#
|
|
44
|
+
# ExcludedMethods:
|
|
45
|
+
# - '/^_/' # Excludes all methods starting with underscore (private convention)
|
|
46
|
+
# - '/^test_/' # Excludes all test methods
|
|
47
|
+
# - '/_(helper|util)$/' # Excludes methods ending with _helper or _util
|
|
48
|
+
#
|
|
49
|
+
# ## Configuration Examples
|
|
50
|
+
#
|
|
51
|
+
# ### Minimal setup - Only exclude parameter-less initialize
|
|
52
|
+
#
|
|
53
|
+
# Documentation/UndocumentedObjects:
|
|
54
|
+
# ExcludedMethods:
|
|
55
|
+
# - 'initialize/0'
|
|
56
|
+
#
|
|
57
|
+
# ### Common Rails/Ruby patterns
|
|
58
|
+
#
|
|
59
|
+
# Documentation/UndocumentedObjects:
|
|
60
|
+
# ExcludedMethods:
|
|
61
|
+
# - 'initialize/0' # Parameter-less constructors
|
|
62
|
+
# - '/^_/' # Private methods (by convention)
|
|
63
|
+
# - 'to_s' # String conversion
|
|
64
|
+
# - 'inspect' # Object inspection
|
|
65
|
+
# - 'hash' # Hash code generation
|
|
66
|
+
# - 'eql?' # Equality comparison
|
|
67
|
+
# - '==' # Binary equality operator
|
|
68
|
+
# - '<=>' # Spaceship operator (comparison)
|
|
69
|
+
# - '+' # Addition operator
|
|
70
|
+
# - '-' # Subtraction operator
|
|
71
|
+
# - '+@' # Unary plus operator
|
|
72
|
+
# - '-@' # Unary minus operator
|
|
73
|
+
#
|
|
74
|
+
# ### Test framework exclusions
|
|
75
|
+
#
|
|
76
|
+
# Documentation/UndocumentedObjects:
|
|
77
|
+
# ExcludedMethods:
|
|
78
|
+
# - '/^test_/' # Minitest methods
|
|
79
|
+
# - '/^should_/' # Shoulda methods
|
|
80
|
+
# - 'setup/0' # Setup with no params
|
|
81
|
+
# - 'teardown/0' # Teardown with no params
|
|
82
|
+
#
|
|
83
|
+
# ## Pattern Validation & Edge Cases
|
|
84
|
+
#
|
|
85
|
+
# The `ExcludedMethods` feature includes robust validation and error handling:
|
|
86
|
+
#
|
|
87
|
+
# **Automatic Pattern Sanitization:**
|
|
88
|
+
# - **Nil values** are automatically removed
|
|
89
|
+
# - **Empty strings** and whitespace-only patterns are filtered out
|
|
90
|
+
# - **Whitespace trimming** is applied to all patterns
|
|
91
|
+
# - **Empty regex patterns** (`//`) are rejected (would match everything)
|
|
92
|
+
# - **Non-array values** are automatically converted to arrays
|
|
93
|
+
#
|
|
94
|
+
# **Invalid Pattern Handling:**
|
|
95
|
+
# - **Invalid regex patterns** (e.g., `/[/`, `/(unclosed`) are silently skipped without crashing
|
|
96
|
+
# - **Invalid arity notation** (e.g., `method/abc`, `method/`) is silently skipped
|
|
97
|
+
# - **Pattern matching is case-sensitive** for both exact names and regex
|
|
98
|
+
#
|
|
99
|
+
# **Operator Method Support:**
|
|
100
|
+
# YARD-Lint fully supports Ruby operator methods including:
|
|
101
|
+
# - Binary operators: `+`, `-`, `*`, `/`, `%`, `**`, `==`, `!=`, `===`, `<`, `>`,
|
|
102
|
+
# `<=`, `>=`, `<=>`, `&`, `|`, `^`, `<<`, `>>`
|
|
103
|
+
# - Unary operators: `+@`, `-@`, `!`, `~`
|
|
104
|
+
# - Other special methods: `[]`, `[]=`, `=~`
|
|
105
|
+
#
|
|
106
|
+
# **Pattern Matching Behavior:**
|
|
107
|
+
# - **Any match excludes**: If a method matches any pattern, it is excluded from validation
|
|
108
|
+
# - **Patterns are evaluated in order** as defined in the configuration
|
|
109
|
+
# - **Exact names have no arity restriction**: `'initialize'` excludes all initialize
|
|
110
|
+
# methods, regardless of parameters
|
|
111
|
+
# - **Arity notation is strict**: `'initialize/0'` only excludes initialize with
|
|
112
|
+
# exactly 0 parameters
|
|
113
|
+
#
|
|
114
|
+
# ## Troubleshooting
|
|
115
|
+
#
|
|
116
|
+
# ### Methods still showing as undocumented
|
|
117
|
+
#
|
|
118
|
+
# 1. Verify the method name matches exactly (case-sensitive)
|
|
119
|
+
# 2. Check if you're using arity notation - ensure the arity count is correct
|
|
120
|
+
# 3. For regex patterns, test your regex independently to ensure it matches
|
|
121
|
+
# 4. Remember: Arity counts `required + optional` parameters, **excluding**
|
|
122
|
+
# splat (`*args`) and block (`&block`)
|
|
123
|
+
#
|
|
124
|
+
# ### Regex patterns not working
|
|
125
|
+
#
|
|
126
|
+
# 1. Ensure you're using `/pattern/` format with forward slashes
|
|
127
|
+
# 2. Test the regex in Ruby: `Regexp.new('your_pattern').match?('method_name')`
|
|
128
|
+
# 3. Escape special regex characters: `\.`, `\(`, `\)`, `\[`, `\]`, etc.
|
|
129
|
+
# 4. Invalid regex patterns are silently skipped - check for syntax errors
|
|
130
|
+
#
|
|
131
|
+
# ### Arity not matching
|
|
132
|
+
#
|
|
133
|
+
# 1. Count parameters correctly: `def method(a, b = 1)` has arity 2 (required + optional)
|
|
134
|
+
# 2. Splat parameters don't count: `def method(a, *rest)` has arity 1
|
|
135
|
+
# 3. Block parameters don't count: `def method(a, &block)` has arity 1
|
|
136
|
+
# 4. Keyword arguments count as individual parameters: `def method(a:, b:)` has arity 2
|
|
137
|
+
#
|
|
9
138
|
module UndocumentedObjects
|
|
10
139
|
end
|
|
11
140
|
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 UndocumentedOptions
|
|
8
|
+
# Configuration for UndocumentedOptions validator
|
|
9
|
+
class Config < ::Yard::Lint::Validators::Config
|
|
10
|
+
self.id = :undocumented_options
|
|
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,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module UndocumentedOptions
|
|
8
|
+
# Parses YARD output for undocumented options 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 parameter list
|
|
30
|
+
i += 1
|
|
31
|
+
next unless i < lines.size
|
|
32
|
+
|
|
33
|
+
params = lines[i]
|
|
34
|
+
|
|
35
|
+
violations << {
|
|
36
|
+
location: file_path,
|
|
37
|
+
line: line_number,
|
|
38
|
+
object_name: object_name,
|
|
39
|
+
params: params
|
|
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,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
module UndocumentedOptions
|
|
8
|
+
# Result object for undocumented options validation
|
|
9
|
+
class Result < Results::Base
|
|
10
|
+
self.default_severity = 'warning'
|
|
11
|
+
self.offense_type = 'line'
|
|
12
|
+
self.offense_name = 'UndocumentedOptions'
|
|
13
|
+
|
|
14
|
+
# Build human-readable message for undocumented options offense
|
|
15
|
+
# @param offense [Hash] offense data with :object_name and :params
|
|
16
|
+
# @return [String] formatted message
|
|
17
|
+
def build_message(offense)
|
|
18
|
+
object_name = offense[:object_name]
|
|
19
|
+
params = offense[:params]
|
|
20
|
+
|
|
21
|
+
"Method '#{object_name}' has options parameter (#{params}) " \
|
|
22
|
+
'but no @option tags in documentation.'
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
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 UndocumentedOptions
|
|
8
|
+
# Validates that methods with options hash parameters have @option tags
|
|
9
|
+
class Validator < Validators::Base
|
|
10
|
+
# YARD query to detect methods with options parameters but no @option tags
|
|
11
|
+
# @return [String] YARD Ruby query code
|
|
12
|
+
def query
|
|
13
|
+
<<~QUERY.strip
|
|
14
|
+
'if object.is_a?(YARD::CodeObjects::MethodObject); params = object.parameters || []; has_options_param = params.any? { |p| p[0] =~ /^(options?|opts?|kwargs)$/ || p[0] =~ /^\\*\\*/ || (p[0] =~ /^(options?|opts?|kwargs)$/ && p[1] =~ /^\\{\\}/) }; if has_options_param; option_tags = object.tags(:option); if option_tags.empty?; puts object.file + ":" + object.line.to_s + ": " + object.title; puts params.map { |p| p.join(" ") }.join(", "); end; end; end; false'
|
|
15
|
+
QUERY
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Builds and executes the YARD command to detect undocumented options
|
|
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,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Documentation
|
|
7
|
+
# UndocumentedOptions validator
|
|
8
|
+
#
|
|
9
|
+
# Checks that options hashes have detailed documentation about their keys.
|
|
10
|
+
# When a method accepts an options hash parameter, the individual option
|
|
11
|
+
# keys should be documented using `@option` tags. This validator is enabled
|
|
12
|
+
# by default.
|
|
13
|
+
#
|
|
14
|
+
# @example Bad - Options parameter without @option tags
|
|
15
|
+
# # Configures the service
|
|
16
|
+
# # @param options [Hash] configuration options
|
|
17
|
+
# def configure(options)
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# @example Good - Options keys documented with @option tags
|
|
21
|
+
# # Configures the service
|
|
22
|
+
# # @param options [Hash] configuration options
|
|
23
|
+
# # @option options [Boolean] :enabled Whether to enable the feature
|
|
24
|
+
# # @option options [Integer] :timeout Connection timeout in seconds
|
|
25
|
+
# def configure(options)
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# ## Configuration
|
|
29
|
+
#
|
|
30
|
+
# To disable this validator:
|
|
31
|
+
#
|
|
32
|
+
# Documentation/UndocumentedOptions:
|
|
33
|
+
# Enabled: false
|
|
34
|
+
#
|
|
35
|
+
module UndocumentedOptions
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|