rubocop-yard 0.3.1 → 0.5.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 +12 -0
- data/README.md +72 -5
- data/config/default.yml +25 -2
- data/lib/rubocop/cop/yard/collection_helper.rb +71 -0
- data/lib/rubocop/cop/yard/collection_style.rb +91 -0
- data/lib/rubocop/cop/yard/collection_type.rb +134 -0
- data/lib/rubocop/cop/yard/meaningless_tag.rb +53 -0
- data/lib/rubocop/cop/yard/mismatch_name.rb +34 -16
- data/lib/rubocop/cop/yard/tag_type_syntax.rb +72 -0
- data/lib/rubocop/cop/yard_cops.rb +5 -1
- data/lib/rubocop/yard/version.rb +1 -1
- data/sig/rubocop/yard.rbs +1 -1
- metadata +7 -3
- data/lib/rubocop/cop/yard/tag_type.rb +0 -109
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d4dd27dab805f9450c5043686ad6a530235d31e2a148e1913db76288fa51a39e
|
4
|
+
data.tar.gz: 2820aa0509d5ff6ef0adecbc772168cd8f2b64650109feb4bfb38e810625812a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be2c582e52297d345a8d09f413d98e15c7724b4adbd288fa5bfa38b280e9b5c752dd549885e6ec521bcd4c9dd335a71a9210475ef585d472332c2dd30066350c
|
7
|
+
data.tar.gz: c239f115e44518f79edb4da03cbf54c4c3e6d66659e1ff51ede40047fb5d7af6944dd4a0e08cc8e9bad53c88ae0d7869a4071655105c40704cab44a7eda10620
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.5.0] - 2023-10-9
|
4
|
+
|
5
|
+
- Add new cop `YARD/CollectionStyle`
|
6
|
+
|
7
|
+
- Split cop from `YARD/TagType` to
|
8
|
+
- `YARD/TagTypeSyntax`
|
9
|
+
- `YARD/CollectionType`
|
10
|
+
|
11
|
+
## [0.4.0] - 2023-09-19
|
12
|
+
|
13
|
+
- Add new cop `YARD/MeaninglessTag`
|
14
|
+
|
3
15
|
## [0.3.1] - 2023-09-16
|
4
16
|
|
5
17
|
Fix config/default.yml
|
data/README.md
CHANGED
@@ -6,18 +6,66 @@ You can check YARD format in Ruby code comment by RuboCop.
|
|
6
6
|
|
7
7
|
## Features
|
8
8
|
|
9
|
-
### `YARD/
|
9
|
+
### `YARD/TagTypeSyntax`
|
10
10
|
|
11
11
|
Check tag type syntax error.
|
12
12
|
|
13
13
|
```
|
14
14
|
# @param [Symbol|String]
|
15
|
-
|
15
|
+
^^^^^^^^^^^^^ (SyntaxError) invalid character at |
|
16
16
|
```
|
17
17
|
|
18
|
+
### `YARD/CollectionStyle`
|
19
|
+
|
20
|
+
`EnforcedStyle long (default)`
|
21
|
+
|
22
|
+
```
|
23
|
+
# bad
|
24
|
+
# @param [{KeyType => ValueType}]
|
25
|
+
|
26
|
+
# bad
|
27
|
+
# @param [(String)]
|
28
|
+
|
29
|
+
# bad
|
30
|
+
# @param [<String>]
|
31
|
+
|
32
|
+
# good
|
33
|
+
# @param [Hash{KeyType => ValueType}]
|
34
|
+
|
35
|
+
# good
|
36
|
+
# @param [Array(String)]
|
37
|
+
|
38
|
+
# good
|
39
|
+
# @param [Array<String>]
|
40
|
+
```
|
41
|
+
|
42
|
+
`EnforcedStyle short`
|
43
|
+
|
44
|
+
```
|
45
|
+
# bad
|
46
|
+
# @param [Hash{KeyType => ValueType}]
|
47
|
+
|
48
|
+
# bad
|
49
|
+
# @param [Array(String)]
|
50
|
+
|
51
|
+
# bad
|
52
|
+
# @param [Array<String>]
|
53
|
+
|
54
|
+
# good
|
55
|
+
# @param [{KeyType => ValueType}]
|
56
|
+
|
57
|
+
# good
|
58
|
+
# @param [(String)]
|
59
|
+
|
60
|
+
# good
|
61
|
+
# @param [<String>]
|
62
|
+
```
|
63
|
+
|
64
|
+
### `YARD/CollectionType`
|
65
|
+
|
18
66
|
```
|
19
67
|
# @param [Hash<Symbol, String>]
|
20
|
-
|
68
|
+
^^^^^^^^^^^^^^^^^^^^ `<Type>` is the collection type syntax. Did you mean `{KeyType => ValueType}` or `Hash{KeyType => ValueType}`
|
21
69
|
```
|
22
70
|
|
23
71
|
### `YARD/MismatchName`
|
@@ -25,13 +73,32 @@ Check tag type syntax error.
|
|
25
73
|
Check `@param` and `@option` name with method definition.
|
26
74
|
|
27
75
|
```rb
|
76
|
+
# @param [String]
|
77
|
+
^^^^^^^^^^^^^^^^^ No tag name is supplied in `@param`
|
78
|
+
|
79
|
+
# @param string
|
80
|
+
^^^^^^^^^^^^^^^ No types are associated with the tag in `@param`
|
81
|
+
|
28
82
|
# @param [String] string
|
29
|
-
|
83
|
+
^^^^^^ `string` is not found in method arguments
|
30
84
|
# @option opt bar [String]
|
31
|
-
|
85
|
+
^^^ `opt` is not found in method arguments
|
32
86
|
def foo(strings, opts = {})
|
33
87
|
```
|
34
88
|
|
89
|
+
### `YARD/MeaninglessTag`
|
90
|
+
|
91
|
+
Check `@param` and `@option` with class/module or casgn
|
92
|
+
|
93
|
+
```rb
|
94
|
+
# @param [String] foo
|
95
|
+
^^^^^^^^^^^^^^^^^^^^^ `@param` is meaningless tag on module
|
96
|
+
module Foo
|
97
|
+
# @option foo bar [String]
|
98
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^ `@option` is meaningless tag on casgn
|
99
|
+
CONST = 1
|
100
|
+
```
|
101
|
+
|
35
102
|
## Installation
|
36
103
|
|
37
104
|
Install the gem and add to the application's Gemfile by executing:
|
data/config/default.yml
CHANGED
@@ -1,7 +1,30 @@
|
|
1
|
-
YARD/
|
1
|
+
YARD/MeaninglessTag:
|
2
|
+
Description: 'Check meaningless tag'
|
3
|
+
Enabled: true
|
4
|
+
VersionAdded: '0.4.0'
|
5
|
+
|
6
|
+
YARD/TagTypeSyntax:
|
2
7
|
Description: 'Check syntax for yard tag type'
|
3
8
|
Enabled: true
|
4
|
-
VersionAdded: '0.
|
9
|
+
VersionAdded: '0.5.0'
|
10
|
+
|
11
|
+
YARD/CollectionStyle:
|
12
|
+
Description: 'Check collection type style'
|
13
|
+
Enabled: true
|
14
|
+
VersionAdded: '0.5.0'
|
15
|
+
EnforcedStyle: long
|
16
|
+
SupportedStyles:
|
17
|
+
- long
|
18
|
+
- short
|
19
|
+
|
20
|
+
YARD/CollectionType:
|
21
|
+
Description: 'Check collection type syntax'
|
22
|
+
Enabled: true
|
23
|
+
VersionAdded: '0.5.0'
|
24
|
+
EnforcedStyle: long
|
25
|
+
SupportedStyles:
|
26
|
+
- long
|
27
|
+
- short
|
5
28
|
|
6
29
|
YARD/MismatchName:
|
7
30
|
Description: 'Check @param and @option name and method parameters'
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module YARD
|
6
|
+
module CollectionHelper
|
7
|
+
def extract_tag_types(tag)
|
8
|
+
case tag
|
9
|
+
when ::YARD::Tags::OptionTag
|
10
|
+
tag.pair.types
|
11
|
+
else
|
12
|
+
tag.types
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def each_types_explainer(docstring, &block)
|
17
|
+
docstring.tags.each do |tag|
|
18
|
+
types = extract_tag_types(tag)
|
19
|
+
|
20
|
+
begin
|
21
|
+
types_explainers = ::YARD::Tags::TypesExplainer::Parser.parse(types.join(', '))
|
22
|
+
types.zip(types_explainers).each do |type, types_explainer|
|
23
|
+
block.call(type, types_explainer)
|
24
|
+
end
|
25
|
+
rescue SyntaxError
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def styled_string(types_explainer)
|
31
|
+
case types_explainer
|
32
|
+
when ::YARD::Tags::TypesExplainer::HashCollectionType
|
33
|
+
tname = case [style, types_explainer.name]
|
34
|
+
when [:short, 'Hash']
|
35
|
+
''
|
36
|
+
when [:long, 'Hash']
|
37
|
+
'Hash'
|
38
|
+
else
|
39
|
+
types_explainer.name
|
40
|
+
end
|
41
|
+
"#{tname}{#{types_explainer.key_types.map { styled_string(_1) }.join(', ')} => #{types_explainer.value_types.map { styled_string(_1) }.join(', ')}}"
|
42
|
+
when ::YARD::Tags::TypesExplainer::FixedCollectionType
|
43
|
+
tname = case [style, types_explainer.name]
|
44
|
+
when [:short, 'Array']
|
45
|
+
''
|
46
|
+
when [:long, 'Array']
|
47
|
+
'Array'
|
48
|
+
else
|
49
|
+
types_explainer.name
|
50
|
+
end
|
51
|
+
"#{tname}(#{types_explainer.types.map { styled_string(_1) }.join(', ')})"
|
52
|
+
when ::YARD::Tags::TypesExplainer::CollectionType
|
53
|
+
tname = case [style, types_explainer.name]
|
54
|
+
when [:short, 'Array']
|
55
|
+
''
|
56
|
+
when [:long, 'Array']
|
57
|
+
'Array'
|
58
|
+
else
|
59
|
+
types_explainer.name
|
60
|
+
end
|
61
|
+
"#{tname}<#{types_explainer.types.map { styled_string(_1) }.join(', ')}>"
|
62
|
+
when ::YARD::Tags::TypesExplainer::Type
|
63
|
+
types_explainer.name
|
64
|
+
else
|
65
|
+
raise "#{types_explainer.class} is not supported"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module YARD
|
6
|
+
# @example EnforcedStyle short
|
7
|
+
#
|
8
|
+
# # bad
|
9
|
+
# # @param [Hash{KeyType => ValueType}]
|
10
|
+
#
|
11
|
+
# # bad
|
12
|
+
# # @param [Array(String)]
|
13
|
+
#
|
14
|
+
# # bad
|
15
|
+
# # @param [Array<String>]
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# # @param [{KeyType => ValueType}]
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# # @param [(String)]
|
22
|
+
#
|
23
|
+
# # good
|
24
|
+
# # @param [<String>]
|
25
|
+
#
|
26
|
+
# @example EnforcedStyle long (default)
|
27
|
+
# # bad
|
28
|
+
# # @param [{KeyType => ValueType}]
|
29
|
+
#
|
30
|
+
# # bad
|
31
|
+
# # @param [(String)]
|
32
|
+
#
|
33
|
+
# # bad
|
34
|
+
# # @param [<String>]
|
35
|
+
#
|
36
|
+
# # good
|
37
|
+
# # @param [Hash{KeyType => ValueType}]
|
38
|
+
#
|
39
|
+
# # good
|
40
|
+
# # @param [Array(String)]
|
41
|
+
#
|
42
|
+
# # good
|
43
|
+
# # @param [Array<String>]
|
44
|
+
class CollectionStyle < Base
|
45
|
+
include YARD::CollectionHelper
|
46
|
+
include RangeHelp
|
47
|
+
include ConfigurableEnforcedStyle
|
48
|
+
extend AutoCorrector
|
49
|
+
|
50
|
+
def on_new_investigation
|
51
|
+
processed_source.comments.each do |comment|
|
52
|
+
next if inline_comment?(comment)
|
53
|
+
next unless include_yard_tag?(comment)
|
54
|
+
|
55
|
+
check(comment)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def check(comment)
|
62
|
+
docstring = ::YARD::DocstringParser.new.parse(comment.text.gsub(/\A#\s*/, ''))
|
63
|
+
each_types_explainer(docstring) do |type, types_explainer|
|
64
|
+
correct_type = styled_string(types_explainer)
|
65
|
+
unless type == correct_type
|
66
|
+
add_offense(comment, message: "`#{type}` is using #{bad_style} style syntax") do |corrector|
|
67
|
+
corrector.replace(comment, comment.source.sub(/\[(.*)\]/) { "[#{correct_type}]" })
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def bad_style
|
74
|
+
if style == :long
|
75
|
+
:short
|
76
|
+
else
|
77
|
+
:long
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def inline_comment?(comment)
|
82
|
+
!comment_line?(comment.source_range.source_line)
|
83
|
+
end
|
84
|
+
|
85
|
+
def include_yard_tag?(comment)
|
86
|
+
comment.source.match?(/@(?:param|return|option|raise|yieldparam|yieldreturn)\s+.*\[.*\]/)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module YARD
|
6
|
+
# @example common
|
7
|
+
# # bad
|
8
|
+
# # @param [Hash<Symbol, String>]
|
9
|
+
#
|
10
|
+
# # bad
|
11
|
+
# # @param [Hash(String)]
|
12
|
+
#
|
13
|
+
# # bad
|
14
|
+
# # @param [Array{Symbol => String}]
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# # @param [Hash{Symbol => String}]
|
18
|
+
#
|
19
|
+
# # good
|
20
|
+
# # @param [Array(String)]
|
21
|
+
#
|
22
|
+
# # good
|
23
|
+
# # @param [Hash{Symbol => String}]
|
24
|
+
class CollectionType < Base
|
25
|
+
include YARD::CollectionHelper
|
26
|
+
include RangeHelp
|
27
|
+
include ConfigurableEnforcedStyle
|
28
|
+
extend AutoCorrector
|
29
|
+
|
30
|
+
def on_new_investigation
|
31
|
+
processed_source.comments.each do |comment|
|
32
|
+
next if inline_comment?(comment)
|
33
|
+
next unless include_yard_tag?(comment)
|
34
|
+
|
35
|
+
docstring = ::YARD::DocstringParser.new.parse(comment.text.gsub(/\A#\s*/, ''))
|
36
|
+
check_mismatch_collection_type(comment, docstring)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def check_mismatch_collection_type(comment, docstring)
|
43
|
+
each_types_explainer(docstring) do |_type, types_explainer|
|
44
|
+
check_mismatch_collection_type_one(comment, types_explainer)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def check_mismatch_collection_type_one(comment, types_explainer)
|
49
|
+
case types_explainer
|
50
|
+
when ::YARD::Tags::TypesExplainer::HashCollectionType
|
51
|
+
case types_explainer.name
|
52
|
+
when 'Hash'
|
53
|
+
types_explainer.key_types.each { |t| check_mismatch_collection_type_one(comment, t) }
|
54
|
+
types_explainer.value_types.each { |t| check_mismatch_collection_type_one(comment, t) }
|
55
|
+
when 'Array'
|
56
|
+
message = "`{KeyType => ValueType}` is the Hash collection type syntax."
|
57
|
+
add_offense(tag_range_for_comment(comment), message: message) do |corrector|
|
58
|
+
types_explainer.name = "Hash"
|
59
|
+
correct_tag_type(corrector, comment, types_explainer)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
when ::YARD::Tags::TypesExplainer::FixedCollectionType
|
63
|
+
case types_explainer.name
|
64
|
+
when 'Hash'
|
65
|
+
if types_explainer.types.length == 2
|
66
|
+
message = "`Hash(Key, Value)` is miswritten of `Hash<Key, Value>` in perhaps"
|
67
|
+
add_offense(tag_range_for_comment(comment), message: message) do |corrector|
|
68
|
+
hash_type = ::YARD::Tags::TypesExplainer::HashCollectionType.new(
|
69
|
+
'Hash',
|
70
|
+
[types_explainer.types[0]],
|
71
|
+
[types_explainer.types[1]]
|
72
|
+
)
|
73
|
+
correct_tag_type(corrector, comment, hash_type)
|
74
|
+
end
|
75
|
+
else
|
76
|
+
message = "`(Type)` is the fixed collection type syntax."
|
77
|
+
add_offense(tag_range_for_comment(comment), message: message) do |corrector|
|
78
|
+
types_explainer.name = "Array"
|
79
|
+
correct_tag_type(corrector, comment, types_explainer)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
when 'Array'
|
83
|
+
types_explainer.types.each { |t| check_mismatch_collection_type_one(comment, t) }
|
84
|
+
end
|
85
|
+
when ::YARD::Tags::TypesExplainer::CollectionType
|
86
|
+
case types_explainer.name
|
87
|
+
when 'Hash'
|
88
|
+
if types_explainer.types.length == 2
|
89
|
+
# `Hash<Key, Value>` pattern is the documented hash specific syntax.
|
90
|
+
message = "`Hash<Key, Value>` is the documented hash specific syntax"
|
91
|
+
add_offense(tag_range_for_comment(comment), message: message) do |corrector|
|
92
|
+
hash_type = ::YARD::Tags::TypesExplainer::HashCollectionType.new(
|
93
|
+
'Hash',
|
94
|
+
[types_explainer.types[0]],
|
95
|
+
[types_explainer.types[1]]
|
96
|
+
)
|
97
|
+
correct_tag_type(corrector, comment, hash_type)
|
98
|
+
end
|
99
|
+
else
|
100
|
+
message = "`<Type>` is the collection type syntax."
|
101
|
+
add_offense(tag_range_for_comment(comment), message: message) do |corrector|
|
102
|
+
types_explainer.name = "Array"
|
103
|
+
correct_tag_type(corrector, comment, types_explainer)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
when 'Array'
|
107
|
+
types_explainer.types.each { |t| check_mismatch_collection_type_one(comment, t) }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def correct_tag_type(corrector, comment, types_explainer)
|
113
|
+
corrector.replace(comment, comment.source.sub(/\[(.*)\]/) { "[#{styled_string(types_explainer)}]" })
|
114
|
+
end
|
115
|
+
|
116
|
+
def inline_comment?(comment)
|
117
|
+
!comment_line?(comment.source_range.source_line)
|
118
|
+
end
|
119
|
+
|
120
|
+
def include_yard_tag?(comment)
|
121
|
+
comment.source.match?(/@(?:param|return|option|raise|yieldparam|yieldreturn)\s+.*\[.*\]/)
|
122
|
+
end
|
123
|
+
|
124
|
+
def tag_range_for_comment(comment)
|
125
|
+
start_column = comment.source.index(/\[/) + 1
|
126
|
+
end_column = comment.source.index(/\]/)
|
127
|
+
offense_start = comment.location.column + start_column
|
128
|
+
offense_end = comment.location.column + end_column
|
129
|
+
source_range(processed_source.buffer, comment.location.line, offense_start..offense_end)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module YARD
|
6
|
+
# @example meaningless tag
|
7
|
+
# # bad
|
8
|
+
# # @param [String] foo
|
9
|
+
# # @option bar baz [String]
|
10
|
+
# class Foo
|
11
|
+
#
|
12
|
+
# # bad
|
13
|
+
# # @param [String] foo
|
14
|
+
# # @option bar baz [String]
|
15
|
+
# CONST = 1
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# class Foo
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# CONST = 1
|
22
|
+
class MeaninglessTag < Base
|
23
|
+
include RangeHelp
|
24
|
+
include DocumentationComment
|
25
|
+
extend AutoCorrector
|
26
|
+
|
27
|
+
def on_class(node)
|
28
|
+
check(node)
|
29
|
+
end
|
30
|
+
alias on_module on_class
|
31
|
+
alias on_casgn on_class
|
32
|
+
|
33
|
+
def check(node)
|
34
|
+
preceding_lines = preceding_lines(node)
|
35
|
+
return false unless preceding_comment?(node, preceding_lines.last)
|
36
|
+
|
37
|
+
yard_docstring = preceding_lines.map { |line| line.text.gsub(/\A#\s*/, '') }.join("\n")
|
38
|
+
docstring = ::YARD::DocstringParser.new.parse(yard_docstring)
|
39
|
+
docstring.tags.each do |tag|
|
40
|
+
next unless tag.tag_name == 'param' || tag.tag_name == 'option'
|
41
|
+
|
42
|
+
comment = preceding_lines.find { |line| line.text.include?("@#{tag.tag_name}") }
|
43
|
+
next unless comment
|
44
|
+
|
45
|
+
add_offense(comment, message: "`@#{tag.tag_name}` is meaningless tag on #{node.type}") do |corrector|
|
46
|
+
corrector.replace(comment, comment.text.gsub("@#{tag.tag_name}", tag.tag_name))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module YARD
|
6
|
-
# @example
|
6
|
+
# @example mismatch name
|
7
7
|
# # bad
|
8
8
|
# # @param [void] baz
|
9
9
|
# # @option opt aaa [void]
|
@@ -12,7 +12,7 @@ module RuboCop
|
|
12
12
|
#
|
13
13
|
# # good
|
14
14
|
# # @param [void] bar
|
15
|
-
# # @param [Array]
|
15
|
+
# # @param [Array] arg
|
16
16
|
# # @option opts aaa [void]
|
17
17
|
# def foo(bar, opts = {}, *arg)
|
18
18
|
# end
|
@@ -28,29 +28,47 @@ module RuboCop
|
|
28
28
|
|
29
29
|
yard_docstring = preceding_lines.map { |line| line.text.gsub(/\A#\s*/, '') }.join("\n")
|
30
30
|
docstring = ::YARD::DocstringParser.new.parse(yard_docstring)
|
31
|
-
docstring.tags.
|
31
|
+
docstring.tags.each_with_index do |tag, i|
|
32
32
|
next unless tag.tag_name == 'param' || tag.tag_name == 'option'
|
33
|
-
next unless node.arguments.none? { |arg_node| tag.name.to_sym == arg_node.name }
|
34
33
|
|
35
|
-
|
36
|
-
comment = preceding_lines.find { |line| line.text.match?(tag_name_regexp) && line.text.include?("@#{tag.tag_name}") }
|
34
|
+
comment = find_by_tag(preceding_lines, tag, i)
|
37
35
|
next unless comment
|
38
36
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
37
|
+
unless tag.name && tag.types
|
38
|
+
if tag.name.nil?
|
39
|
+
add_offense(comment, message: "No tag name is supplied in `@#{tag.tag_name}`")
|
40
|
+
elsif tag.types.nil?
|
41
|
+
add_offense(comment, message: "No types are associated with the tag in `@#{tag.tag_name}`")
|
42
|
+
end
|
43
|
+
|
44
|
+
next
|
45
|
+
end
|
46
|
+
|
47
|
+
next unless node.arguments.none? { |arg_node| tag.name.to_sym == arg_node.name }
|
48
|
+
|
49
|
+
add_offense_to_tag(comment, tag)
|
44
50
|
end
|
45
51
|
end
|
46
52
|
alias on_defs on_def
|
47
|
-
end
|
48
53
|
|
49
|
-
|
54
|
+
private
|
50
55
|
|
51
|
-
|
52
|
-
|
53
|
-
|
56
|
+
def find_by_tag(preceding_lines, tag, i)
|
57
|
+
count = -1
|
58
|
+
preceding_lines.find do |line|
|
59
|
+
count += 1 if line.text.include?("@#{tag.tag_name}")
|
60
|
+
count == i
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def add_offense_to_tag(comment, tag)
|
65
|
+
tag_name_regexp = Regexp.new("\\b#{Regexp.escape(tag.name)}\\b")
|
66
|
+
start_column = comment.source.index(tag_name_regexp)
|
67
|
+
offense_start = comment.location.column + start_column
|
68
|
+
offense_end = offense_start + tag.name.length - 1
|
69
|
+
range = source_range(processed_source.buffer, comment.location.line, offense_start..offense_end)
|
70
|
+
add_offense(range, message: "`#{tag.name}` is not found in method arguments")
|
71
|
+
end
|
54
72
|
end
|
55
73
|
end
|
56
74
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module YARD
|
6
|
+
# @example tag type
|
7
|
+
# # bad
|
8
|
+
# # @param [Integer String]
|
9
|
+
#
|
10
|
+
# # good
|
11
|
+
# # @param [Integer, String]
|
12
|
+
class TagTypeSyntax < Base
|
13
|
+
include RangeHelp
|
14
|
+
|
15
|
+
def on_new_investigation
|
16
|
+
processed_source.comments.each do |comment|
|
17
|
+
next if inline_comment?(comment)
|
18
|
+
next unless include_yard_tag?(comment)
|
19
|
+
|
20
|
+
check(comment)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def check(comment)
|
27
|
+
docstring = comment.text.gsub(/\A#\s*/, '')
|
28
|
+
::YARD::DocstringParser.new.parse(docstring).tags.each do |tag|
|
29
|
+
types = extract_tag_type(tag)
|
30
|
+
|
31
|
+
check_syntax_error(comment) do
|
32
|
+
::YARD::Tags::TypesExplainer::Parser.parse(types.join(', '))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def check_syntax_error(comment)
|
38
|
+
begin
|
39
|
+
yield
|
40
|
+
rescue SyntaxError => e
|
41
|
+
add_offense(tag_range_for_comment(comment), message: "(#{e.class}) #{e.message}")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def extract_tag_type(tag)
|
46
|
+
case tag
|
47
|
+
when ::YARD::Tags::OptionTag
|
48
|
+
tag.pair.types
|
49
|
+
else
|
50
|
+
tag.types
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def inline_comment?(comment)
|
55
|
+
!comment_line?(comment.source_range.source_line)
|
56
|
+
end
|
57
|
+
|
58
|
+
def include_yard_tag?(comment)
|
59
|
+
comment.source.match?(/@(?:param|return|option|raise|yieldparam|yieldreturn)\s+.*\[.*\]/)
|
60
|
+
end
|
61
|
+
|
62
|
+
def tag_range_for_comment(comment)
|
63
|
+
start_column = comment.source.index(/\[/) + 1
|
64
|
+
end_column = comment.source.index(/\]/)
|
65
|
+
offense_start = comment.location.column + start_column
|
66
|
+
offense_end = comment.location.column + end_column
|
67
|
+
source_range(processed_source.buffer, comment.location.line, offense_start..offense_end)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -1,5 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'yard'
|
4
|
-
require_relative 'yard/
|
4
|
+
require_relative 'yard/collection_helper'
|
5
|
+
require_relative 'yard/collection_style'
|
6
|
+
require_relative 'yard/collection_type'
|
7
|
+
require_relative 'yard/meaningless_tag'
|
5
8
|
require_relative 'yard/mismatch_name'
|
9
|
+
require_relative 'yard/tag_type_syntax'
|
data/lib/rubocop/yard/version.rb
CHANGED
data/sig/rubocop/yard.rbs
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubocop-yard
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ksss
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubocop
|
@@ -51,8 +51,12 @@ files:
|
|
51
51
|
- README.md
|
52
52
|
- config/default.yml
|
53
53
|
- lib/rubocop-yard.rb
|
54
|
+
- lib/rubocop/cop/yard/collection_helper.rb
|
55
|
+
- lib/rubocop/cop/yard/collection_style.rb
|
56
|
+
- lib/rubocop/cop/yard/collection_type.rb
|
57
|
+
- lib/rubocop/cop/yard/meaningless_tag.rb
|
54
58
|
- lib/rubocop/cop/yard/mismatch_name.rb
|
55
|
-
- lib/rubocop/cop/yard/
|
59
|
+
- lib/rubocop/cop/yard/tag_type_syntax.rb
|
56
60
|
- lib/rubocop/cop/yard_cops.rb
|
57
61
|
- lib/rubocop/yard.rb
|
58
62
|
- lib/rubocop/yard/inject.rb
|
@@ -1,109 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module RuboCop
|
4
|
-
module Cop
|
5
|
-
module YARD
|
6
|
-
# @example
|
7
|
-
# # bad
|
8
|
-
# @param [Integer String]
|
9
|
-
#
|
10
|
-
# # bad
|
11
|
-
# @param [Hash<Symbol, String>]
|
12
|
-
#
|
13
|
-
# # bad
|
14
|
-
# @param [Hash(String)]
|
15
|
-
#
|
16
|
-
# # bad
|
17
|
-
# @param [Array{Symbol => String}]
|
18
|
-
#
|
19
|
-
# # good
|
20
|
-
# @param [Integer, String]
|
21
|
-
#
|
22
|
-
# # good
|
23
|
-
# @param [<String>]
|
24
|
-
# @param [Array<String>]
|
25
|
-
# @param [List<String>]
|
26
|
-
# @param [Array<(String, Fixnum, Hash)>]
|
27
|
-
#
|
28
|
-
# # good
|
29
|
-
# @param [(String)]
|
30
|
-
# @param [Array(String)]
|
31
|
-
#
|
32
|
-
# # good
|
33
|
-
# @param [{KeyType => ValueType}]
|
34
|
-
# @param [Hash{KeyType => ValueType}]
|
35
|
-
class TagType < Base
|
36
|
-
MSG = ''
|
37
|
-
include RangeHelp # @return [void,]
|
38
|
-
|
39
|
-
def on_new_investigation
|
40
|
-
processed_source.comments.each do |comment|
|
41
|
-
next if inline_comment?(comment)
|
42
|
-
next unless include_yard_tag?(comment)
|
43
|
-
|
44
|
-
check(comment)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
private
|
49
|
-
|
50
|
-
def check(comment)
|
51
|
-
docstring = comment.text.gsub(/\A#\s*/, '')
|
52
|
-
::YARD::DocstringParser.new.parse(docstring).tags.each do |tag|
|
53
|
-
next unless tag.types
|
54
|
-
|
55
|
-
::YARD::Tags::TypesExplainer::Parser.parse(tag.types.join(', ')).each do |types_explainer|
|
56
|
-
check_mismatch_collection_type(comment, types_explainer)
|
57
|
-
end
|
58
|
-
rescue SyntaxError
|
59
|
-
add_offense(tag_range_for_comment(comment), message: 'SyntaxError as YARD tag type')
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def check_mismatch_collection_type(comment, types_explainer)
|
64
|
-
case types_explainer
|
65
|
-
when ::YARD::Tags::TypesExplainer::HashCollectionType
|
66
|
-
if types_explainer.name == 'Hash'
|
67
|
-
types_explainer.key_types.each { |t| check_mismatch_collection_type(comment, t) }
|
68
|
-
types_explainer.value_types.each { |t| check_mismatch_collection_type(comment, t) }
|
69
|
-
else
|
70
|
-
did_you_mean = types_explainer.name == 'Array' ? 'Did you mean `<Type>` or `Array<Type>`' : ''
|
71
|
-
message = "`{KeyType => ValueType}` is the hash collection type syntax. #{did_you_mean}"
|
72
|
-
add_offense(tag_range_for_comment(comment), message: message)
|
73
|
-
end
|
74
|
-
when ::YARD::Tags::TypesExplainer::FixedCollectionType
|
75
|
-
if types_explainer.name == 'Hash'
|
76
|
-
message = "`(Type)` is the fixed collection type syntax. Did you mean `{KeyType => ValueType}` or `Hash{KeyType => ValueType}`"
|
77
|
-
add_offense(tag_range_for_comment(comment), message: message)
|
78
|
-
else
|
79
|
-
types_explainer.types.each { |t| check_mismatch_collection_type(comment, t) }
|
80
|
-
end
|
81
|
-
when ::YARD::Tags::TypesExplainer::CollectionType
|
82
|
-
if types_explainer.name == 'Hash'
|
83
|
-
message = "`<Type>` is the collection type syntax. `{KeyType => ValueType}` or `Hash{KeyType => ValueType}` is more good"
|
84
|
-
add_offense(tag_range_for_comment(comment), message: message)
|
85
|
-
else
|
86
|
-
types_explainer.types.each { |t| check_mismatch_collection_type(comment, t) }
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
def inline_comment?(comment)
|
92
|
-
!comment_line?(comment.source_range.source_line)
|
93
|
-
end
|
94
|
-
|
95
|
-
def include_yard_tag?(comment)
|
96
|
-
comment.source.match?(/@(?:param|return|option|raise|yieldparam|yieldreturn)\s+\[.*\]/)
|
97
|
-
end
|
98
|
-
|
99
|
-
def tag_range_for_comment(comment)
|
100
|
-
start_column = comment.source.index(/\[/) + 1
|
101
|
-
end_column = comment.source.index(/\]/)
|
102
|
-
offense_start = comment.location.column + start_column
|
103
|
-
offense_end = comment.location.column + end_column
|
104
|
-
source_range(processed_source.buffer, comment.location.line, offense_start..offense_end)
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|