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,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
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module TagTypePosition
|
|
8
|
+
# Configuration for TagTypePosition validator
|
|
9
|
+
class Config < ::Yard::Lint::Validators::Config
|
|
10
|
+
self.id = :tag_type_position
|
|
11
|
+
self.defaults = {
|
|
12
|
+
'Enabled' => true,
|
|
13
|
+
'Severity' => 'convention',
|
|
14
|
+
'CheckedTags' => %w[param option],
|
|
15
|
+
'EnforcedStyle' => 'type_after_name' # 'type_after_name' (standard) or 'type_first'
|
|
16
|
+
}.freeze
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module TagTypePosition
|
|
8
|
+
# Builds human-readable messages for TagTypePosition violations
|
|
9
|
+
class MessagesBuilder
|
|
10
|
+
class << self
|
|
11
|
+
# Formats a violation message
|
|
12
|
+
# @param offense [Hash] the offense details
|
|
13
|
+
# @return [String] formatted message
|
|
14
|
+
def call(offense)
|
|
15
|
+
tag_name = offense[:tag_name]
|
|
16
|
+
param_name = offense[:param_name]
|
|
17
|
+
type_info = offense[:type_info]
|
|
18
|
+
detected_style = offense[:detected_style]
|
|
19
|
+
|
|
20
|
+
if detected_style == 'type_after_name'
|
|
21
|
+
# Enforcing type_first, but found type_after_name
|
|
22
|
+
"Type should appear before parameter name in @#{tag_name} tag. " \
|
|
23
|
+
"Use '@#{tag_name} [#{type_info}] #{param_name}' instead of " \
|
|
24
|
+
"'@#{tag_name} #{param_name} [#{type_info}]'."
|
|
25
|
+
else
|
|
26
|
+
# Enforcing type_after_name, but found type_first
|
|
27
|
+
"Type should appear after parameter name in @#{tag_name} tag. " \
|
|
28
|
+
"Use '@#{tag_name} #{param_name} [#{type_info}]' instead of " \
|
|
29
|
+
"'@#{tag_name} [#{type_info}] #{param_name}'."
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module TagTypePosition
|
|
8
|
+
# Parses YARD output for TagTypePosition violations
|
|
9
|
+
class Parser < ::Yard::Lint::Parsers::Base
|
|
10
|
+
# Parses YARD query output into structured violation data
|
|
11
|
+
# @param yard_output [String] raw output from YARD query
|
|
12
|
+
# @param _kwargs [Hash] additional keyword arguments (unused)
|
|
13
|
+
# @return [Array<Hash>] array of violation hashes
|
|
14
|
+
def call(yard_output, **_kwargs)
|
|
15
|
+
return [] if yard_output.nil? || yard_output.strip.empty?
|
|
16
|
+
|
|
17
|
+
lines = yard_output.split("\n").map(&:strip).reject(&:empty?)
|
|
18
|
+
violations = []
|
|
19
|
+
|
|
20
|
+
lines.each_slice(2) do |location_line, details_line|
|
|
21
|
+
next unless location_line && details_line
|
|
22
|
+
|
|
23
|
+
# Parse location: "file.rb:10: ClassName#method_name"
|
|
24
|
+
location_match = location_line.match(/^(.+):(\d+): (.+)$/)
|
|
25
|
+
next unless location_match
|
|
26
|
+
|
|
27
|
+
# Parse details: "tag_name|param_name|type_info|detected_style"
|
|
28
|
+
details = details_line.split('|', 4)
|
|
29
|
+
next unless details.size >= 3
|
|
30
|
+
|
|
31
|
+
tag_name, param_name, type_info, detected_style = details
|
|
32
|
+
|
|
33
|
+
violations << {
|
|
34
|
+
location: location_match[1],
|
|
35
|
+
line: location_match[2].to_i,
|
|
36
|
+
object_name: location_match[3],
|
|
37
|
+
tag_name: tag_name,
|
|
38
|
+
param_name: param_name,
|
|
39
|
+
type_info: type_info,
|
|
40
|
+
detected_style: detected_style
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
violations
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
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 TagTypePosition
|
|
8
|
+
# Result wrapper for TagTypePosition violations
|
|
9
|
+
class Result < Results::Base
|
|
10
|
+
self.default_severity = 'convention'
|
|
11
|
+
self.offense_type = 'style'
|
|
12
|
+
self.offense_name = 'TagTypePosition'
|
|
13
|
+
|
|
14
|
+
# Builds a human-readable message for a violation
|
|
15
|
+
# @param offense [Hash] the offense 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,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module TagTypePosition
|
|
8
|
+
# Validates type annotation position in @param and @option tags
|
|
9
|
+
# YARD standard (type_after_name): @param name [String] description
|
|
10
|
+
# Alternative (type_first): @param name [String] description
|
|
11
|
+
#
|
|
12
|
+
# Note: @return tags are not checked as they don't have parameter names
|
|
13
|
+
class Validator < Base
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
# Runs YARD query to check type position in tags
|
|
17
|
+
# @param dir [String] directory where YARD database is stored
|
|
18
|
+
# @param file_list_path [String] path to temp file containing file paths (one per line)
|
|
19
|
+
# @return [Hash] shell command execution results
|
|
20
|
+
def yard_cmd(dir, file_list_path)
|
|
21
|
+
# Write query to a temporary file to avoid shell escaping issues
|
|
22
|
+
cmd = "cat #{Shellwords.escape(file_list_path)} | xargs yard list --query #{query} "
|
|
23
|
+
|
|
24
|
+
Tempfile.create(['yard_query', '.sh']) do |f|
|
|
25
|
+
f.write("#!/bin/bash\n")
|
|
26
|
+
f.write(cmd)
|
|
27
|
+
f.write("#{shell_arguments} -b #{Shellwords.escape(dir)}\n")
|
|
28
|
+
f.flush
|
|
29
|
+
f.chmod(0o755)
|
|
30
|
+
|
|
31
|
+
shell("bash #{Shellwords.escape(f.path)}")
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# YARD query that checks source code directly instead of docstring.all
|
|
36
|
+
# Detects patterns based on configured style
|
|
37
|
+
# @return [String] YARD query string
|
|
38
|
+
def query
|
|
39
|
+
<<~QUERY.strip
|
|
40
|
+
'
|
|
41
|
+
require "ripper"
|
|
42
|
+
|
|
43
|
+
checked_tags = #{checked_tags_array}
|
|
44
|
+
enforced_style = "#{enforced_style}"
|
|
45
|
+
|
|
46
|
+
# Read the source file and find comment lines for this object
|
|
47
|
+
return false unless object.file && File.exist?(object.file)
|
|
48
|
+
|
|
49
|
+
source_lines = File.readlines(object.file)
|
|
50
|
+
start_line = [object.line - 50, 0].max
|
|
51
|
+
end_line = [object.line, source_lines.length - 1].min
|
|
52
|
+
|
|
53
|
+
# Look for comments before the object definition
|
|
54
|
+
# Start just before the object line and scan backward
|
|
55
|
+
(start_line...(end_line - 1)).reverse_each do |line_num|
|
|
56
|
+
line = source_lines[line_num].to_s.strip
|
|
57
|
+
|
|
58
|
+
# Skip empty lines
|
|
59
|
+
next if line.empty?
|
|
60
|
+
|
|
61
|
+
# Stop if we hit code (non-comment line)
|
|
62
|
+
break unless line.start_with?("#")
|
|
63
|
+
|
|
64
|
+
# Skip comment-only lines without tags
|
|
65
|
+
next unless line.include?("@")
|
|
66
|
+
|
|
67
|
+
checked_tags.each do |tag_name|
|
|
68
|
+
if enforced_style == "type_first"
|
|
69
|
+
# Detect: @tag_name word [Type] (violation when type_first is enforced)
|
|
70
|
+
pattern = /@\#{tag_name}\\s+(\\w+)\\s+\\[([^\\]]+)\\]/
|
|
71
|
+
if line =~ pattern
|
|
72
|
+
param_name = $1
|
|
73
|
+
type_info = $2
|
|
74
|
+
puts object.file + ":" + (line_num + 1).to_s + ": " + object.title
|
|
75
|
+
puts tag_name + "|" + param_name + "|" + type_info + "|type_after_name"
|
|
76
|
+
end
|
|
77
|
+
else
|
|
78
|
+
# Detect: @tag_name [Type] word (violation when type_after_name is enforced)
|
|
79
|
+
pattern = /@\#{tag_name}\\s+\\[([^\\]]+)\\]\\s+(\\w+)/
|
|
80
|
+
if line =~ pattern
|
|
81
|
+
type_info = $1
|
|
82
|
+
param_name = $2
|
|
83
|
+
puts object.file + ":" + (line_num + 1).to_s + ": " + object.title
|
|
84
|
+
puts tag_name + "|" + param_name + "|" + type_info + "|type_first"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
false
|
|
91
|
+
'
|
|
92
|
+
QUERY
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @return [String] the enforced style ('type_after_name' (standard) or 'type_first')
|
|
96
|
+
def enforced_style
|
|
97
|
+
config_or_default('EnforcedStyle')
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Array of tag names to check, formatted for YARD query
|
|
101
|
+
# @return [String] Ruby array literal string
|
|
102
|
+
def checked_tags_array
|
|
103
|
+
tags = config_or_default('CheckedTags')
|
|
104
|
+
"[#{tags.map { |t| "\"#{t}\"" }.join(',')}]"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
# TagTypePosition validator
|
|
8
|
+
#
|
|
9
|
+
# Ensures consistent type annotation positioning in YARD documentation tags.
|
|
10
|
+
# By default, it enforces YARD standard style where the type appears after
|
|
11
|
+
# the parameter name (`@param name [Type]`), but you can configure it to
|
|
12
|
+
# enforce `type_first` style if your team prefers that convention.
|
|
13
|
+
# This validator is enabled by default.
|
|
14
|
+
#
|
|
15
|
+
# @example Good - Type after parameter name (YARD standard)
|
|
16
|
+
# # @param name [String] the user's name
|
|
17
|
+
# # @param age [Integer] the user's age
|
|
18
|
+
# # @param options [Hash{Symbol => Object}] configuration options
|
|
19
|
+
# def create_user(name, age, options = {})
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# @example Bad - Type before parameter name (when using default style)
|
|
23
|
+
# # @param [String] name the user's name
|
|
24
|
+
# # @param [Integer] age the user's age
|
|
25
|
+
# def create_user(name, age)
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# ## Configuration
|
|
29
|
+
#
|
|
30
|
+
# To use type_first style instead:
|
|
31
|
+
#
|
|
32
|
+
# Tags/TagTypePosition:
|
|
33
|
+
# EnforcedStyle: type_first
|
|
34
|
+
#
|
|
35
|
+
# @example Good - When EnforcedStyle is type_first
|
|
36
|
+
# # @param [String] name the user's name
|
|
37
|
+
# # @param [Integer] age the user's age
|
|
38
|
+
# def create_user(name, age)
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# To disable this validator:
|
|
42
|
+
#
|
|
43
|
+
# Tags/TagTypePosition:
|
|
44
|
+
# Enabled: false
|
|
45
|
+
#
|
|
46
|
+
module TagTypePosition
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module TypeSyntax
|
|
8
|
+
# Configuration for TypeSyntax validator
|
|
9
|
+
class Config < ::Yard::Lint::Validators::Config
|
|
10
|
+
self.id = :type_syntax
|
|
11
|
+
self.defaults = {
|
|
12
|
+
'Enabled' => true,
|
|
13
|
+
'Severity' => 'warning',
|
|
14
|
+
'ValidatedTags' => %w[param option return yieldreturn]
|
|
15
|
+
}.freeze
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module TypeSyntax
|
|
8
|
+
# Builds human-readable messages for type syntax violations
|
|
9
|
+
class MessagesBuilder
|
|
10
|
+
class << self
|
|
11
|
+
# Formats a type syntax violation message
|
|
12
|
+
# @param offense [Hash] offense details with tag_name, type_string, error_message
|
|
13
|
+
# @return [String] formatted message
|
|
14
|
+
def call(offense)
|
|
15
|
+
tag = offense[:tag_name]
|
|
16
|
+
type = offense[:type_string]
|
|
17
|
+
error = offense[:error_message]
|
|
18
|
+
|
|
19
|
+
"Invalid type syntax in @#{tag} tag: '#{type}' (#{error})"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module TypeSyntax
|
|
8
|
+
# Parser for TypeSyntax validator output
|
|
9
|
+
# Parses YARD query output that reports type syntax errors
|
|
10
|
+
class Parser < ::Yard::Lint::Parsers::Base
|
|
11
|
+
# Parses YARD output and extracts type syntax violations
|
|
12
|
+
# Expected format (two lines per violation):
|
|
13
|
+
# file.rb:LINE: ClassName#method_name
|
|
14
|
+
# tag_name|type_string|error_message
|
|
15
|
+
# @param yard_output [String] raw YARD query results
|
|
16
|
+
# @param _kwargs [Hash] unused keyword arguments (for compatibility)
|
|
17
|
+
# @return [Array<Hash>] array with violation details
|
|
18
|
+
def call(yard_output, **_kwargs)
|
|
19
|
+
return [] if yard_output.nil? || yard_output.strip.empty?
|
|
20
|
+
|
|
21
|
+
lines = yard_output.split("\n").map(&:strip).reject(&:empty?)
|
|
22
|
+
violations = []
|
|
23
|
+
|
|
24
|
+
lines.each_slice(2) do |location_line, details_line|
|
|
25
|
+
next unless location_line && details_line
|
|
26
|
+
|
|
27
|
+
# Parse location: "file.rb:10: ClassName#method_name"
|
|
28
|
+
location_match = location_line.match(/^(.+):(\d+): (.+)$/)
|
|
29
|
+
next unless location_match
|
|
30
|
+
|
|
31
|
+
# Parse details: "tag_name|type_string|error_message"
|
|
32
|
+
details = details_line.split('|', 3)
|
|
33
|
+
next unless details.size == 3
|
|
34
|
+
|
|
35
|
+
tag_name, type_string, error_message = details
|
|
36
|
+
|
|
37
|
+
violations << {
|
|
38
|
+
location: location_match[1],
|
|
39
|
+
line: location_match[2].to_i,
|
|
40
|
+
method_name: location_match[3],
|
|
41
|
+
tag_name: tag_name,
|
|
42
|
+
type_string: type_string,
|
|
43
|
+
error_message: error_message
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
violations
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
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 TypeSyntax
|
|
8
|
+
# Result wrapper for TypeSyntax validator
|
|
9
|
+
class Result < Results::Base
|
|
10
|
+
self.default_severity = 'warning'
|
|
11
|
+
self.offense_type = 'method'
|
|
12
|
+
self.offense_name = 'InvalidTypeSyntax'
|
|
13
|
+
|
|
14
|
+
# Builds a human-readable message for the offense
|
|
15
|
+
# @param offense [Hash] offense 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,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yard
|
|
4
|
+
module Lint
|
|
5
|
+
module Validators
|
|
6
|
+
module Tags
|
|
7
|
+
module TypeSyntax
|
|
8
|
+
# Runs YARD to validate type syntax using TypesExplainer::Parser
|
|
9
|
+
class Validator < Base
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# Runs YARD query to validate type syntax on given files
|
|
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("#{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 validates type syntax for each tag
|
|
32
|
+
# Format output as two lines per violation:
|
|
33
|
+
# Line 1: file.rb:LINE: ClassName#method_name
|
|
34
|
+
# Line 2: tag_name|type_string|error_message
|
|
35
|
+
# @return [String] YARD query string
|
|
36
|
+
def query
|
|
37
|
+
<<~QUERY.strip
|
|
38
|
+
'
|
|
39
|
+
require "yard"
|
|
40
|
+
|
|
41
|
+
docstring
|
|
42
|
+
.tags
|
|
43
|
+
.select { |tag| #{validated_tags_array}.include?(tag.tag_name) }
|
|
44
|
+
.each do |tag|
|
|
45
|
+
next unless tag.types
|
|
46
|
+
|
|
47
|
+
tag.types.each do |type_str|
|
|
48
|
+
begin
|
|
49
|
+
YARD::Tags::TypesExplainer::Parser.parse(type_str)
|
|
50
|
+
rescue SyntaxError => e
|
|
51
|
+
puts object.file + ":" + object.line.to_s + ": " + object.title
|
|
52
|
+
puts tag.tag_name + "|" + type_str + "|" + e.message
|
|
53
|
+
break
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
false
|
|
59
|
+
'
|
|
60
|
+
QUERY
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Array of tag names to validate, formatted for YARD query
|
|
64
|
+
# @return [String] Ruby array literal string
|
|
65
|
+
def validated_tags_array
|
|
66
|
+
tags = config.validator_config('Tags/TypeSyntax', 'ValidatedTags') || %w[
|
|
67
|
+
param option return yieldreturn
|
|
68
|
+
]
|
|
69
|
+
"[#{tags.map { |t| "\"#{t}\"" }.join(',')}]"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|