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
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module MeaninglessTag
|
|
8
|
+
# Validates that @param/@option tags only appear on methods
|
|
9
|
+
class Validator < Base
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# Runs YARD query to find @param/@option tags on non-methods
|
|
13
|
+
# @param dir [String] directory where YARD database is stored
|
|
14
|
+
# @param file_list_path [String] path to temp file containing file paths (one per line)
|
|
15
|
+
# @return [Hash] shell command execution results
|
|
16
|
+
def yard_cmd(dir, file_list_path)
|
|
17
|
+
# Write query to a temporary file to avoid shell escaping issues
|
|
18
|
+
cmd = "cat #{Shellwords.escape(file_list_path)} | xargs yard list --query #{query} "
|
|
19
|
+
|
|
20
|
+
Tempfile.create(['yard_query', '.sh']) do |f|
|
|
21
|
+
f.write("#!/bin/bash\n")
|
|
22
|
+
f.write(cmd)
|
|
23
|
+
f.write("--private --protected #{shell_arguments} -b #{Shellwords.escape(dir)}\n")
|
|
24
|
+
f.flush
|
|
25
|
+
f.chmod(0o755)
|
|
26
|
+
|
|
27
|
+
shell("bash #{Shellwords.escape(f.path)}")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# YARD query that finds method-only tags on non-method objects
|
|
32
|
+
# Format output as two lines per violation:
|
|
33
|
+
# Line 1: file.rb:LINE: ClassName
|
|
34
|
+
# Line 2: object_type|tag_name
|
|
35
|
+
# @return [String] YARD query string
|
|
36
|
+
def query
|
|
37
|
+
<<~QUERY.strip
|
|
38
|
+
'
|
|
39
|
+
object_type = object.type.to_s
|
|
40
|
+
|
|
41
|
+
if #{invalid_object_types_array}.include?(object_type)
|
|
42
|
+
docstring.tags.each do |tag|
|
|
43
|
+
if #{checked_tags_array}.include?(tag.tag_name)
|
|
44
|
+
puts object.file + ":" + object.line.to_s + ": " + object.title
|
|
45
|
+
puts object_type + "|" + tag.tag_name
|
|
46
|
+
break
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
false
|
|
52
|
+
'
|
|
53
|
+
QUERY
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Array of tag names to check, formatted for YARD query
|
|
57
|
+
# @return [String] Ruby array literal string
|
|
58
|
+
def checked_tags_array
|
|
59
|
+
"[#{checked_tags.map { |t| "\"#{t}\"" }.join(',')}]"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Array of invalid object types, formatted for YARD query
|
|
63
|
+
# @return [String] Ruby array literal string
|
|
64
|
+
def invalid_object_types_array
|
|
65
|
+
"[#{invalid_object_types.map { |t| "\"#{t}\"" }.join(',')}]"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @return [Array<String>] tags that should only appear on methods
|
|
69
|
+
def checked_tags
|
|
70
|
+
config_or_default('CheckedTags')
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @return [Array<String>] object types that shouldn't have method-only tags
|
|
74
|
+
def invalid_object_types
|
|
75
|
+
config_or_default('InvalidObjectTypes')
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
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
|
+
#
|
|
38
|
+
module MeaninglessTag
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -11,15 +11,14 @@ module Yard
|
|
|
11
11
|
|
|
12
12
|
# Runs yard list query to find methods with options parameter but missing @option tags
|
|
13
13
|
# @param dir [String] dir where the yard db is (or where it should be generated)
|
|
14
|
-
# @param
|
|
14
|
+
# @param file_list_path [String] path to temp file containing file paths (one per line)
|
|
15
15
|
# @return [Hash] shell command execution hash results
|
|
16
|
-
def yard_cmd(dir,
|
|
16
|
+
def yard_cmd(dir, file_list_path)
|
|
17
17
|
cmd = <<~CMD
|
|
18
|
-
yard list \
|
|
18
|
+
cat #{Shellwords.escape(file_list_path)} | xargs yard list \
|
|
19
19
|
--private \
|
|
20
20
|
--protected \
|
|
21
|
-
-b #{Shellwords.escape(dir)}
|
|
22
|
-
#{escaped_file_names}
|
|
21
|
+
-b #{Shellwords.escape(dir)}
|
|
23
22
|
CMD
|
|
24
23
|
cmd = cmd.tr("\n", ' ')
|
|
25
24
|
cmd = cmd.gsub('yard list', "yard list --query #{query}")
|
|
@@ -30,13 +29,14 @@ module Yard
|
|
|
30
29
|
# @return [String] yard query to find methods with options parameter
|
|
31
30
|
# but no @option tags
|
|
32
31
|
def query
|
|
32
|
+
parameter_names = config_parameter_names
|
|
33
33
|
<<~QUERY
|
|
34
34
|
'
|
|
35
35
|
if object.is_a?(YARD::CodeObjects::MethodObject)
|
|
36
36
|
# Check if method has a parameter named "options" or "opts"
|
|
37
37
|
has_options_param = object.parameters.any? do |param|
|
|
38
38
|
param_name = param[0].to_s.gsub(/[*:]/, '')
|
|
39
|
-
|
|
39
|
+
#{parameter_names.inspect}.include?(param_name)
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
if has_options_param
|
|
@@ -53,6 +53,11 @@ module Yard
|
|
|
53
53
|
'
|
|
54
54
|
QUERY
|
|
55
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
|
|
56
61
|
end
|
|
57
62
|
end
|
|
58
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
|
|
@@ -12,16 +12,15 @@ module Yard
|
|
|
12
12
|
|
|
13
13
|
# Runs yard list query with proper settings on a given dir and files
|
|
14
14
|
# @param dir [String] dir where the yard db is (or where it should be generated)
|
|
15
|
-
# @param
|
|
15
|
+
# @param file_list_path [String] path to temp file containing file paths (one per line)
|
|
16
16
|
# @return [Hash] shell command execution hash results
|
|
17
|
-
def yard_cmd(dir,
|
|
17
|
+
def yard_cmd(dir, file_list_path)
|
|
18
18
|
cmd = <<~CMD
|
|
19
|
-
yard list \
|
|
19
|
+
cat #{Shellwords.escape(file_list_path)} | xargs yard list \
|
|
20
20
|
--private \
|
|
21
21
|
--protected \
|
|
22
22
|
--query #{query} \
|
|
23
|
-
-b #{Shellwords.escape(dir)}
|
|
24
|
-
#{escaped_file_names}
|
|
23
|
+
-b #{Shellwords.escape(dir)}
|
|
25
24
|
CMD
|
|
26
25
|
|
|
27
26
|
result = shell(cmd)
|
|
@@ -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
|