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,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module ExampleSyntax
|
|
8
|
+
# Validator to check syntax of code in @example tags
|
|
9
|
+
class Validator < Base
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# Runs yard list query to find objects with invalid syntax in @example tags
|
|
13
|
+
# @param dir [String] dir where the yard db is (or where it should be generated)
|
|
14
|
+
# @param file_list_path [String] path to temp file containing file paths (one per line)
|
|
15
|
+
# @return [Hash] shell command execution hash results
|
|
16
|
+
def yard_cmd(dir, file_list_path)
|
|
17
|
+
cmd = <<~CMD
|
|
18
|
+
cat #{Shellwords.escape(file_list_path)} | xargs yard list \
|
|
19
|
+
--private \
|
|
20
|
+
--protected \
|
|
21
|
+
-b #{Shellwords.escape(dir)}
|
|
22
|
+
CMD
|
|
23
|
+
cmd = cmd.tr("\n", ' ')
|
|
24
|
+
cmd = cmd.gsub('yard list', "yard list --query #{query}")
|
|
25
|
+
|
|
26
|
+
shell(cmd)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @return [String] yard query to find @example tags with syntax errors
|
|
30
|
+
def query
|
|
31
|
+
<<~QUERY
|
|
32
|
+
'
|
|
33
|
+
if object.has_tag?(:example)
|
|
34
|
+
example_tags = object.tags(:example)
|
|
35
|
+
|
|
36
|
+
example_tags.each_with_index do |example, index|
|
|
37
|
+
code = example.text
|
|
38
|
+
next if code.nil? || code.empty?
|
|
39
|
+
|
|
40
|
+
# Clean the code: strip output indicators (#=>) and everything after it
|
|
41
|
+
code_lines = code.split("\\n").map do |line|
|
|
42
|
+
line.sub(/\\s*#\\s*=>.*$/, "")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
cleaned_code = code_lines.join("\\n").strip
|
|
46
|
+
next if cleaned_code.empty?
|
|
47
|
+
|
|
48
|
+
# Check if code looks incomplete (single expression without context)
|
|
49
|
+
# Skip validation for these cases
|
|
50
|
+
lines = cleaned_code.split("\\n").reject { |l| l.strip.empty? || l.strip.start_with?("#") }
|
|
51
|
+
|
|
52
|
+
# Skip if it is a single line that looks like an incomplete expression
|
|
53
|
+
if lines.size == 1
|
|
54
|
+
line = lines.first.strip
|
|
55
|
+
# Skip method calls, variable references, or simple expressions
|
|
56
|
+
# These are likely incomplete snippets showing usage
|
|
57
|
+
if line.match?(/^[a-z_][a-z0-9_]*(\\.| |$)/) || line.match?(/^[A-Z]/) && !line.match?(/^(class|module|def)\\s/)
|
|
58
|
+
next
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Try to parse the code
|
|
63
|
+
begin
|
|
64
|
+
RubyVM::InstructionSequence.compile(cleaned_code)
|
|
65
|
+
rescue SyntaxError => e
|
|
66
|
+
# Report syntax errors
|
|
67
|
+
example_name = example.name || "Example " + (index + 1).to_s
|
|
68
|
+
puts object.file + ":" + object.line.to_s + ": " + object.title
|
|
69
|
+
puts "syntax_error"
|
|
70
|
+
puts example_name
|
|
71
|
+
puts e.message
|
|
72
|
+
rescue => e
|
|
73
|
+
# Other errors (like NameError, ArgumentError) are fine
|
|
74
|
+
# We only check syntax, not semantics
|
|
75
|
+
next
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
false
|
|
80
|
+
'
|
|
81
|
+
QUERY
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
# Tags validators - validate YARD tag quality and consistency
|
|
7
|
+
module Tags
|
|
8
|
+
# ExampleSyntax validator
|
|
9
|
+
#
|
|
10
|
+
# Validates Ruby syntax in `@example` tags using RubyVM::InstructionSequence.compile().
|
|
11
|
+
# This validator ensures that code examples in documentation are syntactically valid.
|
|
12
|
+
# It automatically strips output indicators (`#=>`) and skips incomplete single-line
|
|
13
|
+
# snippets. This validator is enabled by default.
|
|
14
|
+
#
|
|
15
|
+
# @example Bad - Invalid Ruby syntax in example
|
|
16
|
+
# # @example
|
|
17
|
+
# # def broken(
|
|
18
|
+
# # # missing closing
|
|
19
|
+
# def my_method
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# @example Good - Valid Ruby syntax in example
|
|
23
|
+
# # @example
|
|
24
|
+
# # def my_method(name)
|
|
25
|
+
# # puts name
|
|
26
|
+
# # end
|
|
27
|
+
# def my_method(name)
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# ## Configuration
|
|
31
|
+
#
|
|
32
|
+
# To disable this validator:
|
|
33
|
+
#
|
|
34
|
+
# Tags/ExampleSyntax:
|
|
35
|
+
# Enabled: false
|
|
36
|
+
#
|
|
37
|
+
module ExampleSyntax
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -77,14 +77,14 @@ module Yard
|
|
|
77
77
|
# @return [String] tags names for which we want to check the invalid tags
|
|
78
78
|
# types definitions
|
|
79
79
|
def checked_tags_names
|
|
80
|
-
validated_tags =
|
|
80
|
+
validated_tags = config_or_default('ValidatedTags')
|
|
81
81
|
query_array(validated_tags)
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
# @return [String] extra names that we allow for types definitions in a yard
|
|
85
85
|
# query acceptable form
|
|
86
86
|
def allowed_types_code
|
|
87
|
-
extra_types =
|
|
87
|
+
extra_types = config_or_default('ExtraTypes')
|
|
88
88
|
query_array(ALLOWED_DEFAULTS + extra_types)
|
|
89
89
|
end
|
|
90
90
|
|
|
@@ -4,7 +4,31 @@ module Yard
|
|
|
4
4
|
module Lint
|
|
5
5
|
module Validators
|
|
6
6
|
module Tags
|
|
7
|
-
# InvalidTypes validator
|
|
7
|
+
# InvalidTypes validator
|
|
8
|
+
#
|
|
9
|
+
# Detects invalid or malformed type annotations in YARD tags. This validator
|
|
10
|
+
# checks that type specifications follow YARD's type syntax rules and that
|
|
11
|
+
# type names are valid. This validator is enabled by default.
|
|
12
|
+
#
|
|
13
|
+
# @example Bad - Invalid type syntax
|
|
14
|
+
# # @param name [String | Integer] the name (wrong pipe syntax)
|
|
15
|
+
# # @return [Array[String]] invalid nested syntax
|
|
16
|
+
# def process(name)
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# @example Good - Valid type syntax
|
|
20
|
+
# # @param name [String, Integer] the name (comma-separated union)
|
|
21
|
+
# # @return [Array<String>] valid nested syntax
|
|
22
|
+
# def process(name)
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# ## Configuration
|
|
26
|
+
#
|
|
27
|
+
# To disable this validator:
|
|
28
|
+
#
|
|
29
|
+
# Tags/InvalidTypes:
|
|
30
|
+
# Enabled: false
|
|
31
|
+
#
|
|
8
32
|
module InvalidTypes
|
|
9
33
|
end
|
|
10
34
|
end
|
|
@@ -67,14 +67,12 @@ module Yard
|
|
|
67
67
|
|
|
68
68
|
# @return [Array<String>] tags that should only appear on methods
|
|
69
69
|
def checked_tags
|
|
70
|
-
|
|
70
|
+
config_or_default('CheckedTags')
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
# @return [Array<String>] object types that shouldn't have method-only tags
|
|
74
74
|
def invalid_object_types
|
|
75
|
-
|
|
76
|
-
class module constant
|
|
77
|
-
]
|
|
75
|
+
config_or_default('InvalidObjectTypes')
|
|
78
76
|
end
|
|
79
77
|
end
|
|
80
78
|
end
|
|
@@ -4,9 +4,37 @@ module Yard
|
|
|
4
4
|
module Lint
|
|
5
5
|
module Validators
|
|
6
6
|
module Tags
|
|
7
|
-
# MeaninglessTag validator
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# MeaninglessTag validator
|
|
8
|
+
#
|
|
9
|
+
# Prevents `@param` and `@option` tags from being used on classes, modules,
|
|
10
|
+
# or constants, where they make no sense. These tags are only valid on methods.
|
|
11
|
+
# This validator is enabled by default.
|
|
12
|
+
#
|
|
13
|
+
# ## Configuration
|
|
14
|
+
#
|
|
15
|
+
# To disable this validator:
|
|
16
|
+
#
|
|
17
|
+
# Tags/MeaninglessTag:
|
|
18
|
+
# Enabled: false
|
|
19
|
+
#
|
|
20
|
+
# @example Bad - @param on a class
|
|
21
|
+
# # @param name [String] this makes no sense on a class
|
|
22
|
+
# class User
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# @example Bad - @option on a module
|
|
26
|
+
# # @option config [Boolean] :enabled modules don't have parameters
|
|
27
|
+
# module Authentication
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# @example Good - @param on a method
|
|
31
|
+
# class User
|
|
32
|
+
# # @param name [String] the user's name
|
|
33
|
+
# def initialize(name)
|
|
34
|
+
# @name = name
|
|
35
|
+
# end
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
10
38
|
module MeaninglessTag
|
|
11
39
|
end
|
|
12
40
|
end
|
|
@@ -29,13 +29,14 @@ module Yard
|
|
|
29
29
|
# @return [String] yard query to find methods with options parameter
|
|
30
30
|
# but no @option tags
|
|
31
31
|
def query
|
|
32
|
+
parameter_names = config_parameter_names
|
|
32
33
|
<<~QUERY
|
|
33
34
|
'
|
|
34
35
|
if object.is_a?(YARD::CodeObjects::MethodObject)
|
|
35
36
|
# Check if method has a parameter named "options" or "opts"
|
|
36
37
|
has_options_param = object.parameters.any? do |param|
|
|
37
38
|
param_name = param[0].to_s.gsub(/[*:]/, '')
|
|
38
|
-
|
|
39
|
+
#{parameter_names.inspect}.include?(param_name)
|
|
39
40
|
end
|
|
40
41
|
|
|
41
42
|
if has_options_param
|
|
@@ -52,6 +53,11 @@ module Yard
|
|
|
52
53
|
'
|
|
53
54
|
QUERY
|
|
54
55
|
end
|
|
56
|
+
|
|
57
|
+
# @return [Array<String>] parameter names that should have @option tags
|
|
58
|
+
def config_parameter_names
|
|
59
|
+
config.validator_config('Tags/OptionTags', 'ParameterNames') || Config.defaults['ParameterNames']
|
|
60
|
+
end
|
|
55
61
|
end
|
|
56
62
|
end
|
|
57
63
|
end
|
|
@@ -4,7 +4,32 @@ module Yard
|
|
|
4
4
|
module Lint
|
|
5
5
|
module Validators
|
|
6
6
|
module Tags
|
|
7
|
-
# OptionTags validator
|
|
7
|
+
# OptionTags validator
|
|
8
|
+
#
|
|
9
|
+
# Ensures that methods with options parameters document them using `@option`
|
|
10
|
+
# tags. This validator is enabled by default.
|
|
11
|
+
#
|
|
12
|
+
# ## Configuration
|
|
13
|
+
#
|
|
14
|
+
# To disable this validator:
|
|
15
|
+
#
|
|
16
|
+
# Tags/OptionTags:
|
|
17
|
+
# Enabled: false
|
|
18
|
+
#
|
|
19
|
+
# @example Good - Options hash is documented
|
|
20
|
+
# # @param name [String] the name
|
|
21
|
+
# # @param options [Hash] configuration options
|
|
22
|
+
# # @option options [Boolean] :enabled Whether to enable the feature
|
|
23
|
+
# # @option options [Integer] :timeout Timeout in seconds
|
|
24
|
+
# def configure(name, options = {})
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# @example Bad - Missing @option tags
|
|
28
|
+
# # @param name [String] the name
|
|
29
|
+
# # @param options [Hash] configuration options
|
|
30
|
+
# def configure(name, options = {})
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
8
33
|
module OptionTags
|
|
9
34
|
end
|
|
10
35
|
end
|
|
@@ -4,7 +4,31 @@ module Yard
|
|
|
4
4
|
module Lint
|
|
5
5
|
module Validators
|
|
6
6
|
module Tags
|
|
7
|
-
# Order validator
|
|
7
|
+
# Order validator
|
|
8
|
+
#
|
|
9
|
+
# Enforces a consistent order for YARD documentation tags. This validator
|
|
10
|
+
# checks that tags appear in a logical sequence (e.g., `@param` before
|
|
11
|
+
# `@return`, `@option` after `@param`). This validator is enabled by default.
|
|
12
|
+
#
|
|
13
|
+
# @example Bad - Tags in wrong order
|
|
14
|
+
# # @return [String] the result
|
|
15
|
+
# # @param name [String] the name
|
|
16
|
+
# def process(name)
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# @example Good - Tags in correct order
|
|
20
|
+
# # @param name [String] the name
|
|
21
|
+
# # @return [String] the result
|
|
22
|
+
# def process(name)
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# ## Configuration
|
|
26
|
+
#
|
|
27
|
+
# To disable this validator:
|
|
28
|
+
#
|
|
29
|
+
# Tags/Order:
|
|
30
|
+
# Enabled: false
|
|
31
|
+
#
|
|
8
32
|
module Order
|
|
9
33
|
end
|
|
10
34
|
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module RedundantParamDescription
|
|
8
|
+
# Configuration for RedundantParamDescription validator
|
|
9
|
+
class Config < ::Yard::Lint::Validators::Config
|
|
10
|
+
self.id = :redundant_param_description
|
|
11
|
+
self.defaults = {
|
|
12
|
+
'Enabled' => true,
|
|
13
|
+
'Severity' => 'convention',
|
|
14
|
+
'CheckedTags' => %w[param option],
|
|
15
|
+
'Articles' => %w[The the A a An an],
|
|
16
|
+
'MaxRedundantWords' => 6,
|
|
17
|
+
'GenericTerms' => %w[object instance value data item element],
|
|
18
|
+
'EnabledPatterns' => {
|
|
19
|
+
'ArticleParam' => true,
|
|
20
|
+
'PossessiveParam' => true,
|
|
21
|
+
'TypeRestatement' => true,
|
|
22
|
+
'ParamToVerb' => true,
|
|
23
|
+
'IdPattern' => true,
|
|
24
|
+
'DirectionalDate' => true,
|
|
25
|
+
'TypeGeneric' => true
|
|
26
|
+
}
|
|
27
|
+
}.freeze
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module RedundantParamDescription
|
|
8
|
+
# Builds human-readable messages for redundant parameter description violations
|
|
9
|
+
class MessagesBuilder
|
|
10
|
+
# Build message for a violation
|
|
11
|
+
# @param offense [Hash] the offense data
|
|
12
|
+
# @return [String] formatted message
|
|
13
|
+
def self.call(offense)
|
|
14
|
+
tag_name = offense[:tag_name]
|
|
15
|
+
param_name = offense[:param_name]
|
|
16
|
+
description = offense[:description]
|
|
17
|
+
pattern_type = offense[:pattern_type]
|
|
18
|
+
|
|
19
|
+
case pattern_type
|
|
20
|
+
when 'article_param'
|
|
21
|
+
'The @' + tag_name + ' description \'' + description + '\' is redundant - ' \
|
|
22
|
+
'it just restates the parameter name. ' \
|
|
23
|
+
'Consider removing the description: @' + tag_name + ' ' + param_name + ' [Type]'
|
|
24
|
+
|
|
25
|
+
when 'possessive_param'
|
|
26
|
+
'The @' + tag_name + ' description \'' + description + '\' adds no meaningful information ' \
|
|
27
|
+
'beyond the parameter name. ' \
|
|
28
|
+
'Consider removing it or explaining the parameter\'s specific purpose.'
|
|
29
|
+
|
|
30
|
+
when 'type_restatement'
|
|
31
|
+
'The @' + tag_name + ' description \'' + description + '\' just repeats the type name. ' \
|
|
32
|
+
'Consider removing the description or explaining what makes this ' + param_name + ' significant.'
|
|
33
|
+
|
|
34
|
+
when 'param_to_verb'
|
|
35
|
+
'The @' + tag_name + ' description \'' + description + '\' is too generic. ' \
|
|
36
|
+
'Consider removing it or explaining what the ' + param_name + ' is used for in detail.'
|
|
37
|
+
|
|
38
|
+
when 'id_pattern'
|
|
39
|
+
'The @' + tag_name + ' description \'' + description + '\' is self-explanatory from the parameter name. ' \
|
|
40
|
+
'Consider removing the description: @' + tag_name + ' ' + param_name + ' [Type]'
|
|
41
|
+
|
|
42
|
+
when 'directional_date'
|
|
43
|
+
'The @' + tag_name + ' description \'' + description + '\' is redundant - ' \
|
|
44
|
+
'the parameter name already indicates this. ' \
|
|
45
|
+
'Consider removing the description or explaining the date\'s specific meaning.'
|
|
46
|
+
|
|
47
|
+
when 'type_generic'
|
|
48
|
+
'The @' + tag_name + ' description \'' + description + '\' just combines type and generic terms. ' \
|
|
49
|
+
'Consider removing it or providing specific details about this ' + param_name + '.'
|
|
50
|
+
|
|
51
|
+
else
|
|
52
|
+
'The @' + tag_name + ' description \'' + description + '\' appears redundant. ' \
|
|
53
|
+
'Consider providing a meaningful description or omitting it entirely.'
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module RedundantParamDescription
|
|
8
|
+
# Parses YARD output for redundant parameter description violations
|
|
9
|
+
class Parser < Parsers::Base
|
|
10
|
+
# Parse YARD output into structured violations
|
|
11
|
+
# @param yard_output [String] raw YARD output
|
|
12
|
+
# @return [Array<Hash>] array of violation hashes
|
|
13
|
+
def call(yard_output)
|
|
14
|
+
return [] if yard_output.nil? || yard_output.empty?
|
|
15
|
+
|
|
16
|
+
violations = []
|
|
17
|
+
lines = yard_output.lines.map(&:chomp)
|
|
18
|
+
|
|
19
|
+
i = 0
|
|
20
|
+
while i < lines.length
|
|
21
|
+
line = lines[i]
|
|
22
|
+
|
|
23
|
+
# Match location line: "file:line: object_name"
|
|
24
|
+
location_match = line.match(/^(.+):(\d+): (.+)$/)
|
|
25
|
+
unless location_match
|
|
26
|
+
i += 1
|
|
27
|
+
next
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
file_path = location_match[1]
|
|
31
|
+
line_number = location_match[2].to_i
|
|
32
|
+
object_name = location_match[3]
|
|
33
|
+
|
|
34
|
+
# Next line contains violation data
|
|
35
|
+
i += 1
|
|
36
|
+
next unless i < lines.length
|
|
37
|
+
|
|
38
|
+
data_line = lines[i]
|
|
39
|
+
parts = data_line.split('|')
|
|
40
|
+
next unless parts.length == 6
|
|
41
|
+
|
|
42
|
+
tag_name, param_name, description, type_name, pattern_type, word_count = parts
|
|
43
|
+
|
|
44
|
+
violations << {
|
|
45
|
+
name: 'RedundantParamDescription',
|
|
46
|
+
tag_name: tag_name,
|
|
47
|
+
param_name: param_name,
|
|
48
|
+
description: description,
|
|
49
|
+
type_name: type_name.empty? ? nil : type_name,
|
|
50
|
+
pattern_type: pattern_type,
|
|
51
|
+
word_count: word_count.to_i,
|
|
52
|
+
location: file_path,
|
|
53
|
+
line: line_number,
|
|
54
|
+
object_name: object_name
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
i += 1
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
violations
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module RedundantParamDescription
|
|
8
|
+
# Result builder for redundant parameter description violations
|
|
9
|
+
class Result < Results::Base
|
|
10
|
+
self.default_severity = 'convention'
|
|
11
|
+
self.offense_type = 'tag'
|
|
12
|
+
self.offense_name = 'RedundantParamDescription'
|
|
13
|
+
|
|
14
|
+
# Build human-readable message for redundant param offense
|
|
15
|
+
# @param offense [Hash] offense data with redundancy details
|
|
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,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module RedundantParamDescription
|
|
8
|
+
# Validates that parameter descriptions are not redundant/meaningless
|
|
9
|
+
class Validator < Validators::Base
|
|
10
|
+
# YARD query to detect redundant parameter descriptions
|
|
11
|
+
# @return [String] YARD Ruby query code
|
|
12
|
+
def query
|
|
13
|
+
articles = config_articles.join('|')
|
|
14
|
+
generic_terms = config_generic_terms.join('|')
|
|
15
|
+
max_words = config_max_redundant_words
|
|
16
|
+
checked_tags = config_checked_tags
|
|
17
|
+
patterns = config_enabled_patterns
|
|
18
|
+
|
|
19
|
+
# Build query as single line for shell compatibility
|
|
20
|
+
query_body = 'if object.is_a?(YARD::CodeObjects::MethodObject); ' \
|
|
21
|
+
"object.docstring.tags.select { |tag| #{checked_tags.inspect}.include?(tag.tag_name) }.each do |tag|; " \
|
|
22
|
+
'next unless tag.name && tag.text && !tag.text.strip.empty?; ' \
|
|
23
|
+
'param_name = tag.name; ' \
|
|
24
|
+
'description = tag.text.strip.gsub(/\\.$/, ""); ' \
|
|
25
|
+
'word_count = description.split.length; ' \
|
|
26
|
+
'type_name = tag.types&.first&.gsub(/[<>{}\\[\\],]/, "")&.strip; ' \
|
|
27
|
+
"next if word_count > #{max_words}; " \
|
|
28
|
+
'pattern_type = nil; ' \
|
|
29
|
+
"if #{patterns['ArticleParam']} && word_count <= 3; " \
|
|
30
|
+
"articles_re = /^(#{articles})/i; " \
|
|
31
|
+
'desc_parts = description.split; ' \
|
|
32
|
+
'if desc_parts.length == 2 && desc_parts[0].match?(articles_re) && desc_parts[1].downcase == param_name.downcase; ' \
|
|
33
|
+
'pattern_type = "article_param"; ' \
|
|
34
|
+
'end; ' \
|
|
35
|
+
'end; ' \
|
|
36
|
+
"if pattern_type.nil? && #{patterns['PossessiveParam']} && word_count <= 4; " \
|
|
37
|
+
'desc_parts = description.split; ' \
|
|
38
|
+
'if desc_parts.length >= 3; ' \
|
|
39
|
+
"articles_re = /^(#{articles})/i; " \
|
|
40
|
+
'if desc_parts[0].match?(articles_re) && desc_parts[1].end_with?("s") && desc_parts[1].include?(39.chr) && desc_parts[2].downcase == param_name.downcase; ' \
|
|
41
|
+
'pattern_type = "possessive_param"; ' \
|
|
42
|
+
'end; ' \
|
|
43
|
+
'end; ' \
|
|
44
|
+
'end; ' \
|
|
45
|
+
"if pattern_type.nil? && #{patterns['TypeRestatement']} && type_name && word_count <= 2; " \
|
|
46
|
+
"generic_terms_arr = [\"#{generic_terms.gsub('|', '\", \"')}\"].map(&:downcase); " \
|
|
47
|
+
'if description.downcase == type_name.downcase; ' \
|
|
48
|
+
'pattern_type = "type_restatement"; ' \
|
|
49
|
+
'elsif word_count == 2; ' \
|
|
50
|
+
'parts = description.split; ' \
|
|
51
|
+
'if parts[0].downcase == type_name.downcase && generic_terms_arr.include?(parts[1].downcase); ' \
|
|
52
|
+
'pattern_type = "type_restatement"; ' \
|
|
53
|
+
'end; ' \
|
|
54
|
+
'end; ' \
|
|
55
|
+
'end; ' \
|
|
56
|
+
"if pattern_type.nil? && #{patterns['ParamToVerb']} && word_count <= 4; " \
|
|
57
|
+
'parts = description.split; ' \
|
|
58
|
+
'if parts.length == 3 && parts[0].downcase == param_name.downcase && parts[1].downcase == "to"; ' \
|
|
59
|
+
'pattern_type = "param_to_verb"; ' \
|
|
60
|
+
'end; ' \
|
|
61
|
+
'end; ' \
|
|
62
|
+
"if pattern_type.nil? && #{patterns['IdPattern']} && word_count <= 6; " \
|
|
63
|
+
'if param_name =~ /_id$|_uuid$|_identifier$/; ' \
|
|
64
|
+
'if description =~ /^(ID|Unique identifier|Identifier)\\s+(of|for)\\s+/i; ' \
|
|
65
|
+
'pattern_type = "id_pattern"; ' \
|
|
66
|
+
'end; ' \
|
|
67
|
+
'end; ' \
|
|
68
|
+
'end; ' \
|
|
69
|
+
"if pattern_type.nil? && #{patterns['DirectionalDate']} && word_count <= 4; " \
|
|
70
|
+
'if param_name =~ /^(from|to|till|until)$/; ' \
|
|
71
|
+
'parts = description.split; ' \
|
|
72
|
+
'if parts.length == 3 && parts[0].downcase == param_name.downcase && parts[1].downcase == "this"; ' \
|
|
73
|
+
'pattern_type = "directional_date"; ' \
|
|
74
|
+
'end; ' \
|
|
75
|
+
'end; ' \
|
|
76
|
+
'end; ' \
|
|
77
|
+
"if pattern_type.nil? && #{patterns['TypeGeneric']} && type_name && word_count <= 5; " \
|
|
78
|
+
"generic_terms_arr = [\"#{generic_terms.gsub('|', '\", \"')}\"].map(&:downcase); " \
|
|
79
|
+
'parts = description.split; ' \
|
|
80
|
+
'if parts.length >= 2 && parts[0].downcase == type_name.downcase && generic_terms_arr.include?(parts[1].downcase); ' \
|
|
81
|
+
'pattern_type = "type_generic"; ' \
|
|
82
|
+
'end; ' \
|
|
83
|
+
'end; ' \
|
|
84
|
+
'if pattern_type; ' \
|
|
85
|
+
'puts object.file + ":" + object.line.to_s + ": " + object.title; ' \
|
|
86
|
+
'puts tag.tag_name + "|" + param_name + "|" + tag.text.strip + "|" + (type_name || "") + "|" + pattern_type + "|" + word_count.to_s; ' \
|
|
87
|
+
'end; ' \
|
|
88
|
+
'end; ' \
|
|
89
|
+
'end; ' \
|
|
90
|
+
'false'
|
|
91
|
+
|
|
92
|
+
# Wrap in single quotes like other validators do
|
|
93
|
+
"'#{query_body}'"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Builds and executes the YARD command
|
|
97
|
+
# @param dir [String] the directory containing the .yardoc database
|
|
98
|
+
# @param file_list_path [String] path to file containing list of files to analyze
|
|
99
|
+
# @return [Hash] command output with stdout, stderr, exit_code
|
|
100
|
+
def yard_cmd(dir, file_list_path)
|
|
101
|
+
# Create a temporary script file to avoid shell escaping issues
|
|
102
|
+
require 'tempfile'
|
|
103
|
+
|
|
104
|
+
script = Tempfile.new(['yard_lint_query', '.sh'])
|
|
105
|
+
script.write("#!/bin/bash\n")
|
|
106
|
+
# Write query to a variable - since query already has outer single quotes, just assign it
|
|
107
|
+
script.write("QUERY=#{query}\n")
|
|
108
|
+
script.write("cat #{Shellwords.escape(file_list_path)} | xargs yard list #{shell_arguments} --query \"$QUERY\" -q -b #{Shellwords.escape(dir)}\n")
|
|
109
|
+
script.chmod(0o755)
|
|
110
|
+
script.close
|
|
111
|
+
|
|
112
|
+
result = shell(script.path)
|
|
113
|
+
script.unlink
|
|
114
|
+
result
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
# @return [Array<String>] configured articles to check
|
|
120
|
+
def config_articles
|
|
121
|
+
config_or_default('Articles')
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# @return [Array<String>] configured generic terms to check
|
|
125
|
+
def config_generic_terms
|
|
126
|
+
config_or_default('GenericTerms')
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @return [Integer] maximum word count for redundant descriptions
|
|
130
|
+
def config_max_redundant_words
|
|
131
|
+
config_or_default('MaxRedundantWords')
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# @return [Array<String>] tags to check for redundant descriptions
|
|
135
|
+
def config_checked_tags
|
|
136
|
+
config_or_default('CheckedTags')
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# @return [Hash] enabled pattern detection flags
|
|
140
|
+
def config_enabled_patterns
|
|
141
|
+
config_or_default('EnabledPatterns')
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|