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
|
@@ -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
|
|
@@ -4,7 +4,37 @@ module Yard
|
|
|
4
4
|
module Lint
|
|
5
5
|
module Validators
|
|
6
6
|
module Semantic
|
|
7
|
-
# AbstractMethods validator
|
|
7
|
+
# AbstractMethods validator
|
|
8
|
+
#
|
|
9
|
+
# Ensures that methods marked with `@abstract` are actually abstract (not
|
|
10
|
+
# implemented). Abstract methods should either raise NotImplementedError or
|
|
11
|
+
# be empty stubs, not contain actual implementation logic. This validator
|
|
12
|
+
# is enabled by default.
|
|
13
|
+
#
|
|
14
|
+
# @example Bad - @abstract tag on implemented method
|
|
15
|
+
# # @abstract
|
|
16
|
+
# def process
|
|
17
|
+
# puts "This is actually implemented!"
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# @example Good - @abstract method raises NotImplementedError
|
|
21
|
+
# # @abstract
|
|
22
|
+
# def process
|
|
23
|
+
# raise NotImplementedError
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# @example Good - @abstract method is empty
|
|
27
|
+
# # @abstract
|
|
28
|
+
# def process
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# ## Configuration
|
|
32
|
+
#
|
|
33
|
+
# To disable this validator:
|
|
34
|
+
#
|
|
35
|
+
# Semantic/AbstractMethods:
|
|
36
|
+
# Enabled: false
|
|
37
|
+
#
|
|
8
38
|
module AbstractMethods
|
|
9
39
|
end
|
|
10
40
|
end
|
|
@@ -4,7 +4,40 @@ module Yard
|
|
|
4
4
|
module Lint
|
|
5
5
|
module Validators
|
|
6
6
|
module Tags
|
|
7
|
-
# ApiTags validator
|
|
7
|
+
# ApiTags validator
|
|
8
|
+
#
|
|
9
|
+
# Enforces that all public classes, modules, and methods have an `@api` tag
|
|
10
|
+
# to explicitly document their API visibility level. This validator is disabled
|
|
11
|
+
# by default and must be explicitly enabled.
|
|
12
|
+
#
|
|
13
|
+
# ## Configuration
|
|
14
|
+
#
|
|
15
|
+
# To enable this validator (it's disabled by default):
|
|
16
|
+
#
|
|
17
|
+
# Tags/ApiTags:
|
|
18
|
+
# Enabled: true
|
|
19
|
+
# AllowedApis:
|
|
20
|
+
# - public
|
|
21
|
+
# - private
|
|
22
|
+
#
|
|
23
|
+
# @example Good - Methods and classes have @api tags
|
|
24
|
+
# # @api public
|
|
25
|
+
# class MyClass
|
|
26
|
+
# # @api public
|
|
27
|
+
# def public_method
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# # @api private
|
|
31
|
+
# def internal_helper
|
|
32
|
+
# end
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# @example Bad - Missing @api tags
|
|
36
|
+
# class AnotherClass
|
|
37
|
+
# def some_method
|
|
38
|
+
# end
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
8
41
|
module ApiTags
|
|
9
42
|
end
|
|
10
43
|
end
|
|
@@ -11,7 +11,8 @@ module Yard
|
|
|
11
11
|
self.defaults = {
|
|
12
12
|
'Enabled' => true,
|
|
13
13
|
'Severity' => 'convention',
|
|
14
|
-
'ValidatedTags' => %w[param option return yieldreturn]
|
|
14
|
+
'ValidatedTags' => %w[param option return yieldreturn],
|
|
15
|
+
'EnforcedStyle' => 'long' # 'long' (Hash{K => V}) or 'short' ({K => V})
|
|
15
16
|
}.freeze
|
|
16
17
|
end
|
|
17
18
|
end
|
|
@@ -14,27 +14,56 @@ module Yard
|
|
|
14
14
|
def call(offense)
|
|
15
15
|
type_string = offense[:type_string]
|
|
16
16
|
tag_name = offense[:tag_name]
|
|
17
|
+
detected_style = offense[:detected_style]
|
|
17
18
|
|
|
18
|
-
# Extract the corrected version
|
|
19
|
-
corrected = suggest_correction(type_string)
|
|
19
|
+
# Extract the corrected version based on detected style
|
|
20
|
+
corrected = suggest_correction(type_string, detected_style)
|
|
21
|
+
style_description = detected_style == 'short' ? 'long' : 'short'
|
|
20
22
|
|
|
21
|
-
"Use
|
|
22
|
-
"in @#{tag_name} tag.
|
|
23
|
+
"Use #{style_description} collection syntax #{corrected} instead of " \
|
|
24
|
+
"#{type_string} in @#{tag_name} tag."
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
private
|
|
26
28
|
|
|
27
|
-
# Suggests the corrected YARD syntax
|
|
29
|
+
# Suggests the corrected YARD syntax based on detected style
|
|
28
30
|
# @param type_string [String] the incorrect type string
|
|
31
|
+
# @param detected_style [String] the detected style ('short' or 'long')
|
|
29
32
|
# @return [String] the suggested correction
|
|
30
|
-
def suggest_correction(type_string)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
def suggest_correction(type_string, detected_style)
|
|
34
|
+
if detected_style == 'short'
|
|
35
|
+
# Convert short to long: Hash<K, V> -> Hash{K => V} or {K => V} -> Hash{K => V}
|
|
36
|
+
convert_to_long(type_string)
|
|
37
|
+
else
|
|
38
|
+
# Convert long to short: Hash{K => V} -> {K => V}
|
|
39
|
+
convert_to_short(type_string)
|
|
36
40
|
end
|
|
37
41
|
end
|
|
42
|
+
|
|
43
|
+
# Converts short syntax to long syntax
|
|
44
|
+
# @param type_string [String] the type string
|
|
45
|
+
# @return [String] the converted type string
|
|
46
|
+
def convert_to_long(type_string)
|
|
47
|
+
if type_string.start_with?('{')
|
|
48
|
+
# {K => V} -> Hash{K => V}
|
|
49
|
+
"Hash#{type_string}"
|
|
50
|
+
else
|
|
51
|
+
# Hash<K, V> -> Hash{K => V}
|
|
52
|
+
type_string.gsub(/Hash<(.+?)>/) do
|
|
53
|
+
types = ::Regexp.last_match(1)
|
|
54
|
+
# Split on comma, handle nested types
|
|
55
|
+
"Hash{#{types.sub(/,\s*/, ' => ')}}"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Converts long syntax to short syntax
|
|
61
|
+
# @param type_string [String] the type string
|
|
62
|
+
# @return [String] the converted type string
|
|
63
|
+
def convert_to_short(type_string)
|
|
64
|
+
# Hash{K => V} -> {K => V}
|
|
65
|
+
type_string.sub(/^Hash/, '')
|
|
66
|
+
end
|
|
38
67
|
end
|
|
39
68
|
end
|
|
40
69
|
end
|
|
@@ -24,18 +24,19 @@ module Yard
|
|
|
24
24
|
location_match = location_line.match(/^(.+):(\d+): (.+)$/)
|
|
25
25
|
next unless location_match
|
|
26
26
|
|
|
27
|
-
# Parse details: "tag_name|type_string"
|
|
28
|
-
details = details_line.split('|',
|
|
29
|
-
next unless details.size ==
|
|
27
|
+
# Parse details: "tag_name|type_string|detected_style"
|
|
28
|
+
details = details_line.split('|', 3)
|
|
29
|
+
next unless details.size == 3
|
|
30
30
|
|
|
31
|
-
tag_name, type_string = details
|
|
31
|
+
tag_name, type_string, detected_style = details
|
|
32
32
|
|
|
33
33
|
violations << {
|
|
34
34
|
location: location_match[1],
|
|
35
35
|
line: location_match[2].to_i,
|
|
36
36
|
object_name: location_match[3],
|
|
37
37
|
tag_name: tag_name,
|
|
38
|
-
type_string: type_string
|
|
38
|
+
type_string: type_string,
|
|
39
|
+
detected_style: detected_style
|
|
39
40
|
}
|
|
40
41
|
end
|
|
41
42
|
|
|
@@ -28,12 +28,14 @@ module Yard
|
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
# YARD query that finds
|
|
31
|
+
# YARD query that finds incorrect collection syntax based on EnforcedStyle
|
|
32
32
|
# Format output as two lines per violation:
|
|
33
33
|
# Line 1: file.rb:LINE: ClassName#method_name
|
|
34
|
-
# Line 2: tag_name|type_string
|
|
34
|
+
# Line 2: tag_name|type_string|detected_style
|
|
35
35
|
# @return [String] YARD query string
|
|
36
36
|
def query
|
|
37
|
+
style = enforced_style
|
|
38
|
+
|
|
37
39
|
<<~QUERY.strip
|
|
38
40
|
'
|
|
39
41
|
docstring
|
|
@@ -43,10 +45,23 @@ module Yard
|
|
|
43
45
|
next unless tag.types
|
|
44
46
|
|
|
45
47
|
tag.types.each do |type_str|
|
|
46
|
-
|
|
48
|
+
detected_style = nil
|
|
49
|
+
|
|
50
|
+
# Check for Hash<...> syntax (angle brackets)
|
|
47
51
|
if type_str =~ /Hash<.*>/
|
|
52
|
+
detected_style = "short"
|
|
53
|
+
# Check for Hash{...} syntax (curly braces)
|
|
54
|
+
elsif type_str =~ /Hash\\{.*\\}/
|
|
55
|
+
detected_style = "long"
|
|
56
|
+
# Check for {...} syntax without Hash prefix
|
|
57
|
+
elsif type_str =~ /^\\{.*\\}$/
|
|
58
|
+
detected_style = "short"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Report violations based on enforced style
|
|
62
|
+
if detected_style && detected_style != "#{style}"
|
|
48
63
|
puts object.file + ":" + object.line.to_s + ": " + object.title
|
|
49
|
-
puts tag.tag_name + "|" + type_str
|
|
64
|
+
puts tag.tag_name + "|" + type_str + "|" + detected_style
|
|
50
65
|
break
|
|
51
66
|
end
|
|
52
67
|
end
|
|
@@ -57,12 +72,16 @@ module Yard
|
|
|
57
72
|
QUERY
|
|
58
73
|
end
|
|
59
74
|
|
|
75
|
+
# Gets the enforced collection style from configuration
|
|
76
|
+
# @return [String] 'long' or 'short'
|
|
77
|
+
def enforced_style
|
|
78
|
+
config_or_default('EnforcedStyle')
|
|
79
|
+
end
|
|
80
|
+
|
|
60
81
|
# Array of tag names to validate, formatted for YARD query
|
|
61
82
|
# @return [String] Ruby array literal string
|
|
62
83
|
def validated_tags_array
|
|
63
|
-
tags =
|
|
64
|
-
param option return yieldreturn
|
|
65
|
-
]
|
|
84
|
+
tags = config_or_default('ValidatedTags')
|
|
66
85
|
"[#{tags.map { |t| "\"#{t}\"" }.join(',')}]"
|
|
67
86
|
end
|
|
68
87
|
end
|
|
@@ -4,8 +4,44 @@ module Yard
|
|
|
4
4
|
module Lint
|
|
5
5
|
module Validators
|
|
6
6
|
module Tags
|
|
7
|
-
# CollectionType validator
|
|
8
|
-
#
|
|
7
|
+
# CollectionType validator
|
|
8
|
+
#
|
|
9
|
+
# Enforces correct YARD collection type syntax for Hash types. YARD uses
|
|
10
|
+
# `Hash{K => V}` syntax with curly braces and hash rockets, not the generic
|
|
11
|
+
# `Hash<K, V>` syntax used in other languages and documentation tools.
|
|
12
|
+
# This validator is enabled by default.
|
|
13
|
+
#
|
|
14
|
+
# ## Why This Matters
|
|
15
|
+
#
|
|
16
|
+
# YARD has specific syntax conventions that differ from other documentation tools.
|
|
17
|
+
# Using the correct syntax ensures that YARD can properly parse and display your
|
|
18
|
+
# type annotations in generated documentation.
|
|
19
|
+
#
|
|
20
|
+
# @example Bad - Generic syntax with angle brackets
|
|
21
|
+
# # @param options [Hash<Symbol, String>] configuration options
|
|
22
|
+
# # @param mapping [Hash<String, Integer>] key to value mapping
|
|
23
|
+
# def configure(options, mapping)
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# @example Good - YARD syntax with curly braces
|
|
27
|
+
# # @param options [Hash{Symbol => String}] configuration options
|
|
28
|
+
# # @param mapping [Hash{String => Integer}] key to value mapping
|
|
29
|
+
# def configure(options, mapping)
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# @example Good - Arrays use angle brackets (correct for YARD)
|
|
33
|
+
# # @param items [Array<String>] list of items
|
|
34
|
+
# # @param numbers [Array<Integer>] list of numbers
|
|
35
|
+
# def process(items, numbers)
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# ## Configuration
|
|
39
|
+
#
|
|
40
|
+
# To disable this validator:
|
|
41
|
+
#
|
|
42
|
+
# Tags/CollectionType:
|
|
43
|
+
# Enabled: false
|
|
44
|
+
#
|
|
9
45
|
module CollectionType
|
|
10
46
|
end
|
|
11
47
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module ExampleSyntax
|
|
8
|
+
# Configuration for ExampleSyntax validator
|
|
9
|
+
class Config < ::Yard::Lint::Validators::Config
|
|
10
|
+
self.id = :example_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,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module ExampleSyntax
|
|
8
|
+
# Builds messages for example syntax offenses
|
|
9
|
+
class MessagesBuilder
|
|
10
|
+
class << self
|
|
11
|
+
# Build message for example syntax offense
|
|
12
|
+
# @param offense [Hash] offense data with :example_name and :error_message keys
|
|
13
|
+
# @return [String] formatted message
|
|
14
|
+
def call(offense)
|
|
15
|
+
example_name = offense[:example_name]
|
|
16
|
+
error_msg = offense[:error_message]
|
|
17
|
+
object_name = offense[:object_name]
|
|
18
|
+
|
|
19
|
+
"Object `#{object_name}` has syntax error in @example " \
|
|
20
|
+
"'#{example_name}': #{error_msg}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module ExampleSyntax
|
|
8
|
+
# Parser for @example syntax validation results
|
|
9
|
+
class Parser < Parsers::Base
|
|
10
|
+
# @param yard_output [String] raw yard output with example syntax issues
|
|
11
|
+
# @return [Array<Hash>] array with example syntax violation details
|
|
12
|
+
def call(yard_output)
|
|
13
|
+
return [] if yard_output.nil? || yard_output.empty?
|
|
14
|
+
|
|
15
|
+
lines = yard_output.split("\n").reject(&:empty?)
|
|
16
|
+
results = []
|
|
17
|
+
|
|
18
|
+
# Output format is variable lines per error:
|
|
19
|
+
# 1. file.rb:10: ClassName#method_name
|
|
20
|
+
# 2. syntax_error
|
|
21
|
+
# 3. Example name
|
|
22
|
+
# 4+. Error message (can be multiple lines)
|
|
23
|
+
# Next error starts with another file.rb:line: pattern
|
|
24
|
+
|
|
25
|
+
i = 0
|
|
26
|
+
while i < lines.length
|
|
27
|
+
location_line = lines[i]
|
|
28
|
+
|
|
29
|
+
# Parse location line: "file.rb:10: ClassName#method_name"
|
|
30
|
+
# File paths typically start with a letter or . or / or ~
|
|
31
|
+
match = location_line.match(%r{^([a-zA-Z./~].+):(\d+): (.+)$})
|
|
32
|
+
unless match
|
|
33
|
+
i += 1
|
|
34
|
+
next
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
file = match[1]
|
|
38
|
+
line = match[2].to_i
|
|
39
|
+
object_name = match[3]
|
|
40
|
+
|
|
41
|
+
# Next line should be status
|
|
42
|
+
i += 1
|
|
43
|
+
status_line = lines[i]
|
|
44
|
+
next unless status_line == 'syntax_error'
|
|
45
|
+
|
|
46
|
+
# Next line is example name
|
|
47
|
+
i += 1
|
|
48
|
+
example_name = lines[i]
|
|
49
|
+
|
|
50
|
+
# Collect all remaining lines until we hit the next location line or end
|
|
51
|
+
error_message_lines = []
|
|
52
|
+
i += 1
|
|
53
|
+
while i < lines.length
|
|
54
|
+
# Check if this line starts a new error (matches file:line: pattern)
|
|
55
|
+
# File paths typically start with a letter or . or / or ~
|
|
56
|
+
break if lines[i].match?(%r{^[a-zA-Z./~].+:\d+: .+$})
|
|
57
|
+
|
|
58
|
+
error_message_lines << lines[i]
|
|
59
|
+
i += 1
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
results << {
|
|
63
|
+
name: 'ExampleSyntax',
|
|
64
|
+
object_name: object_name,
|
|
65
|
+
example_name: example_name,
|
|
66
|
+
error_message: error_message_lines.join("\n"),
|
|
67
|
+
location: file,
|
|
68
|
+
line: line
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
results
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module ExampleSyntax
|
|
8
|
+
# Result object for example syntax validation
|
|
9
|
+
class Result < Results::Base
|
|
10
|
+
self.default_severity = 'warning'
|
|
11
|
+
self.offense_type = 'line'
|
|
12
|
+
self.offense_name = 'ExampleSyntaxError'
|
|
13
|
+
|
|
14
|
+
# Build human-readable message for example syntax offense
|
|
15
|
+
# @param offense [Hash] offense data with :example_name and :error_message keys
|
|
16
|
+
# @return [String] formatted message
|
|
17
|
+
def build_message(offense)
|
|
18
|
+
MessagesBuilder.call(offense)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
# Override to build offenses with dynamic names from parsed data
|
|
24
|
+
# @return [Array<Hash>] array of offense hashes
|
|
25
|
+
def build_offenses
|
|
26
|
+
@parsed_data.map do |offense_data|
|
|
27
|
+
{
|
|
28
|
+
severity: configured_severity,
|
|
29
|
+
type: self.class.offense_type,
|
|
30
|
+
name: offense_data[:name] || self.class.offense_name,
|
|
31
|
+
message: build_message(offense_data),
|
|
32
|
+
location: offense_data[:location] || offense_data[:file],
|
|
33
|
+
location_line: offense_data[:line] || offense_data[:location_line] || 0
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|