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
|
@@ -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
|
|
@@ -0,0 +1,168 @@
|
|
|
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
|
+
# RedundantParamDescription validator
|
|
9
|
+
#
|
|
10
|
+
# Detects parameter descriptions that add no meaningful information beyond
|
|
11
|
+
# what's already obvious from the parameter name and type. This validator
|
|
12
|
+
# helps maintain high-quality documentation by flagging descriptions that
|
|
13
|
+
# should either be expanded with meaningful details or removed entirely.
|
|
14
|
+
#
|
|
15
|
+
# ## Why This Matters
|
|
16
|
+
#
|
|
17
|
+
# Documentation is most valuable when AI assistants and human developers
|
|
18
|
+
# can trust it. Redundant descriptions like `@param user [User] The user`
|
|
19
|
+
# create noise without adding value, training both humans and AI to ignore
|
|
20
|
+
# parameter documentation. Better to have no description (letting the type
|
|
21
|
+
# speak for itself) than a redundant one.
|
|
22
|
+
#
|
|
23
|
+
# @example Redundant - Article + param name (will be flagged)
|
|
24
|
+
# # @param appointment [Appointment] The appointment
|
|
25
|
+
# # @param user [User] the user
|
|
26
|
+
# def process(appointment, user)
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# @example Redundant - Possessive form (will be flagged)
|
|
30
|
+
# # @param appointment [Appointment] The event's appointment
|
|
31
|
+
# def schedule(appointment)
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# @example Redundant - Type restatement (will be flagged)
|
|
35
|
+
# # @param user [User] User object
|
|
36
|
+
# # @param value [Integer] Integer value
|
|
37
|
+
# def update(user, value)
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# @example Redundant - Parameter to verb pattern (will be flagged)
|
|
41
|
+
# # @param payments [Array] Payments to count
|
|
42
|
+
# # @param user [User] User to notify
|
|
43
|
+
# def process(payments, user)
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# @example Redundant - ID pattern (will be flagged)
|
|
47
|
+
# # @param treatment_id [String] ID of the treatment
|
|
48
|
+
# # @param uuid [String] Unique identifier for the list
|
|
49
|
+
# def find(treatment_id, uuid)
|
|
50
|
+
# end
|
|
51
|
+
#
|
|
52
|
+
# @example Redundant - Directional date (will be flagged)
|
|
53
|
+
# # @param from [Date] from this date
|
|
54
|
+
# # @param till [Date] till this date
|
|
55
|
+
# def filter(from, till)
|
|
56
|
+
# end
|
|
57
|
+
#
|
|
58
|
+
# @example Redundant - Type + generic term (will be flagged)
|
|
59
|
+
# # @param payment [Payment] Payment object
|
|
60
|
+
# # @param data [Hash] Hash data
|
|
61
|
+
# def process(payment, data)
|
|
62
|
+
# end
|
|
63
|
+
#
|
|
64
|
+
# @example Good - No description (type is self-explanatory)
|
|
65
|
+
# # @param appointment [Appointment]
|
|
66
|
+
# # @param user [User]
|
|
67
|
+
# def process(appointment, user)
|
|
68
|
+
# end
|
|
69
|
+
#
|
|
70
|
+
# @example Good - Long, meaningful descriptions with context
|
|
71
|
+
# # @param date [Date, nil] the date that can describe the event starting information or nil if event did not yet start
|
|
72
|
+
# # @param user [User] the user who initiated the request and will receive notifications
|
|
73
|
+
# # @param data [Hash] configuration options for the API endpoint including timeout and retry settings
|
|
74
|
+
# def configure(date, user, data)
|
|
75
|
+
# end
|
|
76
|
+
#
|
|
77
|
+
# @example Good - Short but adds value beyond param name
|
|
78
|
+
# # @param count [Integer] maximum number of retries before giving up
|
|
79
|
+
# # @param timeout [Integer] seconds to wait before timing out the connection
|
|
80
|
+
# # @param enabled [Boolean] whether the feature is enabled for this account
|
|
81
|
+
# def setup(count, timeout, enabled)
|
|
82
|
+
# end
|
|
83
|
+
#
|
|
84
|
+
# @example Good - Starts similar but continues with valuable info
|
|
85
|
+
# # @param user [User] the current user, or guest if not authenticated
|
|
86
|
+
# # @param data [Hash] the request payload containing user preferences
|
|
87
|
+
# # @param id [String] unique identifier used for tracking across systems
|
|
88
|
+
# def track(user, data, id)
|
|
89
|
+
# end
|
|
90
|
+
#
|
|
91
|
+
# ## Configuration
|
|
92
|
+
#
|
|
93
|
+
# The validator is highly configurable to match your project's needs:
|
|
94
|
+
#
|
|
95
|
+
# Tags/RedundantParamDescription:
|
|
96
|
+
# Description: 'Detects meaningless parameter descriptions.'
|
|
97
|
+
# Enabled: true
|
|
98
|
+
# Severity: convention
|
|
99
|
+
# CheckedTags:
|
|
100
|
+
# - param
|
|
101
|
+
# - option
|
|
102
|
+
# # Articles that trigger the article_param pattern
|
|
103
|
+
# Articles:
|
|
104
|
+
# - The
|
|
105
|
+
# - the
|
|
106
|
+
# - A
|
|
107
|
+
# - a
|
|
108
|
+
# - An
|
|
109
|
+
# - an
|
|
110
|
+
# # Maximum word count for redundant descriptions (longer descriptions are never flagged)
|
|
111
|
+
# MaxRedundantWords: 6
|
|
112
|
+
# # Generic terms that trigger the type_generic pattern
|
|
113
|
+
# GenericTerms:
|
|
114
|
+
# - object
|
|
115
|
+
# - instance
|
|
116
|
+
# - value
|
|
117
|
+
# - data
|
|
118
|
+
# - item
|
|
119
|
+
# - element
|
|
120
|
+
# # Pattern toggles (enable/disable specific detection patterns)
|
|
121
|
+
# EnabledPatterns:
|
|
122
|
+
# ArticleParam: true # "The user", "the appointment"
|
|
123
|
+
# PossessiveParam: true # "The event's appointment"
|
|
124
|
+
# TypeRestatement: true # "User object", "Appointment"
|
|
125
|
+
# ParamToVerb: true # "Payments to count"
|
|
126
|
+
# IdPattern: true # "ID of the treatment" for *_id params
|
|
127
|
+
# DirectionalDate: true # "from this date" for from/to/till
|
|
128
|
+
# TypeGeneric: true # "Payment object", "Hash data"
|
|
129
|
+
#
|
|
130
|
+
# ## Pattern Types
|
|
131
|
+
#
|
|
132
|
+
# The validator detects 7 different redundancy patterns:
|
|
133
|
+
#
|
|
134
|
+
# 1. **ArticleParam**: `"The user"`, `"the appointment"` - Article + parameter name
|
|
135
|
+
# 2. **PossessiveParam**: `"The event's appointment"` - Possessive form + parameter name
|
|
136
|
+
# 3. **TypeRestatement**: `"User object"`, `"Appointment"` - Just repeats the type
|
|
137
|
+
# 4. **ParamToVerb**: `"Payments to count"` - Parameter name + "to" + verb
|
|
138
|
+
# 5. **IdPattern**: `"ID of the treatment"` - For `_id` or `_uuid` suffixed parameters
|
|
139
|
+
# 6. **DirectionalDate**: `"from this date"` - For `from`, `to`, `till` parameters
|
|
140
|
+
# 7. **TypeGeneric**: `"Payment object"` - Type + generic term like "object", "instance"
|
|
141
|
+
#
|
|
142
|
+
# You can disable individual patterns while keeping others enabled.
|
|
143
|
+
#
|
|
144
|
+
# ## False Positive Prevention
|
|
145
|
+
#
|
|
146
|
+
# The validator uses multiple strategies to prevent false positives:
|
|
147
|
+
#
|
|
148
|
+
# 1. **Word count threshold**: Descriptions longer than `MaxRedundantWords` (default: 6)
|
|
149
|
+
# are never flagged, even if they start with a redundant pattern
|
|
150
|
+
# 2. **EXACT pattern matching**: Only flags complete matches, not partial/prefix matches
|
|
151
|
+
# 3. **Configurable patterns**: Disable patterns that don't work for your codebase
|
|
152
|
+
#
|
|
153
|
+
# This means `"the date that can describe the event starting information"` (9 words)
|
|
154
|
+
# will never be flagged, even though it starts with "the date".
|
|
155
|
+
#
|
|
156
|
+
# ## Disabling
|
|
157
|
+
#
|
|
158
|
+
# To disable this validator:
|
|
159
|
+
#
|
|
160
|
+
# Tags/RedundantParamDescription:
|
|
161
|
+
# Enabled: false
|
|
162
|
+
#
|
|
163
|
+
module RedundantParamDescription
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -94,15 +94,13 @@ module Yard
|
|
|
94
94
|
|
|
95
95
|
# @return [String] the enforced style ('type_after_name' (standard) or 'type_first')
|
|
96
96
|
def enforced_style
|
|
97
|
-
|
|
97
|
+
config_or_default('EnforcedStyle')
|
|
98
98
|
end
|
|
99
99
|
|
|
100
100
|
# Array of tag names to check, formatted for YARD query
|
|
101
101
|
# @return [String] Ruby array literal string
|
|
102
102
|
def checked_tags_array
|
|
103
|
-
tags =
|
|
104
|
-
'Tags/TagTypePosition', 'CheckedTags'
|
|
105
|
-
) || %w[param option]
|
|
103
|
+
tags = config_or_default('CheckedTags')
|
|
106
104
|
"[#{tags.map { |t| "\"#{t}\"" }.join(',')}]"
|
|
107
105
|
end
|
|
108
106
|
end
|