rubocop-yard 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 35f55b03315d73095819182351a19278528a8d91f5fb71f9644e39734ef0aa89
4
- data.tar.gz: 731f21ea908307e87b59e36dae70fc9da5ced99157cdc53d0084c077673fd496
3
+ metadata.gz: d4dd27dab805f9450c5043686ad6a530235d31e2a148e1913db76288fa51a39e
4
+ data.tar.gz: 2820aa0509d5ff6ef0adecbc772168cd8f2b64650109feb4bfb38e810625812a
5
5
  SHA512:
6
- metadata.gz: 378e7d10ff7f50c807a1fe0fe6266e7163ebcece3bc6a4cf340cf98baade320a4785d2520c42aa9319bc20e20642dc46b39311088b58e521f57ee0ce15fab48b
7
- data.tar.gz: a33bc93f4cb7712b0c73c259d3ecb7f94409973ec73c897494e723c33f5879b7686deba726e7a1f871da20aae3abd46554641c8d1ea29d5c58124cc66646213e
6
+ metadata.gz: be2c582e52297d345a8d09f413d98e15c7724b4adbd288fa5bfa38b280e9b5c752dd549885e6ec521bcd4c9dd335a71a9210475ef585d472332c2dd30066350c
7
+ data.tar.gz: c239f115e44518f79edb4da03cbf54c4c3e6d66659e1ff51ede40047fb5d7af6944dd4a0e08cc8e9bad53c88ae0d7869a4071655105c40704cab44a7eda10620
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
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
+
3
11
  ## [0.4.0] - 2023-09-19
4
12
 
5
13
  - Add new cop `YARD/MeaninglessTag`
data/README.md CHANGED
@@ -6,15 +6,63 @@ You can check YARD format in Ruby code comment by RuboCop.
6
6
 
7
7
  ## Features
8
8
 
9
- ### `YARD/TagType`
9
+ ### `YARD/TagTypeSyntax`
10
10
 
11
11
  Check tag type syntax error.
12
12
 
13
13
  ```
14
14
  # @param [Symbol|String]
15
- ^^^^^^^^^^^^^ SyntaxError as YARD tag type
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}`
@@ -25,6 +73,12 @@ 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]
data/config/default.yml CHANGED
@@ -3,10 +3,28 @@ YARD/MeaninglessTag:
3
3
  Enabled: true
4
4
  VersionAdded: '0.4.0'
5
5
 
6
- YARD/TagType:
6
+ YARD/TagTypeSyntax:
7
7
  Description: 'Check syntax for yard tag type'
8
8
  Enabled: true
9
- VersionAdded: '0.2.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
10
28
 
11
29
  YARD/MismatchName:
12
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
@@ -22,6 +22,7 @@ module RuboCop
22
22
  class MeaninglessTag < Base
23
23
  include RangeHelp
24
24
  include DocumentationComment
25
+ extend AutoCorrector
25
26
 
26
27
  def on_class(node)
27
28
  check(node)
@@ -41,7 +42,9 @@ module RuboCop
41
42
  comment = preceding_lines.find { |line| line.text.include?("@#{tag.tag_name}") }
42
43
  next unless comment
43
44
 
44
- add_offense(comment, message: "`@#{tag.tag_name}` is meaningless tag on #{node.type}")
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
45
48
  end
46
49
  end
47
50
  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.each do |tag|
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
- tag_name_regexp = Regexp.new("\\b#{Regexp.escape(tag.name)}\\b")
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
- start_column = comment.source.index(tag_name_regexp)
40
- offense_start = comment.location.column + start_column
41
- offense_end = offense_start + tag.name.length - 1
42
- range = source_range(processed_source.buffer, comment.location.line, offense_start..offense_end)
43
- add_offense(range, message: "`#{tag.name}` is not found in method arguments")
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
- private
54
+ private
50
55
 
51
- # @param [void] aaa
52
- # @option opts bbb [void]
53
- def dummy(aaa, opts = {}, *)
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,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'yard'
4
+ require_relative 'yard/collection_helper'
5
+ require_relative 'yard/collection_style'
6
+ require_relative 'yard/collection_type'
4
7
  require_relative 'yard/meaningless_tag'
5
- require_relative 'yard/tag_type'
6
8
  require_relative 'yard/mismatch_name'
9
+ require_relative 'yard/tag_type_syntax'
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module YARD
5
- VERSION = "0.4.0"
5
+ VERSION = "0.5.0"
6
6
  end
7
7
  end
data/sig/rubocop/yard.rbs CHANGED
@@ -5,7 +5,7 @@ module RuboCop
5
5
  end
6
6
  module Cop
7
7
  module YARD
8
- class TagType
8
+ class CollectionType
9
9
  type t = YARD::Tags::TypesExplainer::Type
10
10
  | ::YARD::Tags::TypesExplainer::CollectionType
11
11
  | ::YARD::Tags::TypesExplainer::FixedCollectionType
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.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-09-19 00:00:00.000000000 Z
11
+ date: 2023-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -51,9 +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
54
57
  - lib/rubocop/cop/yard/meaningless_tag.rb
55
58
  - lib/rubocop/cop/yard/mismatch_name.rb
56
- - lib/rubocop/cop/yard/tag_type.rb
59
+ - lib/rubocop/cop/yard/tag_type_syntax.rb
57
60
  - lib/rubocop/cop/yard_cops.rb
58
61
  - lib/rubocop/yard.rb
59
62
  - lib/rubocop/yard/inject.rb
@@ -1,126 +0,0 @@
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
- # # 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
- include RangeHelp # @return [void,]
37
-
38
- def on_new_investigation
39
- processed_source.comments.each do |comment|
40
- next if inline_comment?(comment)
41
- next unless include_yard_tag?(comment)
42
-
43
- check(comment)
44
- end
45
- end
46
-
47
- private
48
-
49
- def check(comment)
50
- docstring = comment.text.gsub(/\A#\s*/, '')
51
- ::YARD::DocstringParser.new.parse(docstring).tags.each do |tag|
52
- types = extract_tag_type(tag)
53
-
54
- check_syntax_error(comment) do
55
- types_explainers = ::YARD::Tags::TypesExplainer::Parser.parse(types.join(', '))
56
- types_explainers.each do |types_explainer|
57
- check_mismatch_collection_type(comment, types_explainer)
58
- end
59
- end
60
- end
61
- end
62
-
63
- def check_syntax_error(comment)
64
- begin
65
- yield
66
- rescue SyntaxError => e
67
- add_offense(tag_range_for_comment(comment), message: "(#{e.class}) #{e.message}")
68
- end
69
- end
70
-
71
- def check_mismatch_collection_type(comment, types_explainer)
72
- case types_explainer
73
- when ::YARD::Tags::TypesExplainer::HashCollectionType
74
- if types_explainer.name == 'Hash'
75
- types_explainer.key_types.each { |t| check_mismatch_collection_type(comment, t) }
76
- types_explainer.value_types.each { |t| check_mismatch_collection_type(comment, t) }
77
- else
78
- did_you_mean = types_explainer.name == 'Array' ? 'Did you mean `<Type>` or `Array<Type>`' : ''
79
- message = "`{KeyType => ValueType}` is the hash collection type syntax. #{did_you_mean}"
80
- add_offense(tag_range_for_comment(comment), message: message)
81
- end
82
- when ::YARD::Tags::TypesExplainer::FixedCollectionType
83
- if types_explainer.name == 'Hash'
84
- message = "`(Type)` is the fixed collection type syntax. Did you mean `{KeyType => ValueType}` or `Hash{KeyType => ValueType}`"
85
- add_offense(tag_range_for_comment(comment), message: message)
86
- else
87
- types_explainer.types.each { |t| check_mismatch_collection_type(comment, t) }
88
- end
89
- when ::YARD::Tags::TypesExplainer::CollectionType
90
- if types_explainer.name == 'Hash'
91
- message = "`<Type>` is the collection type syntax. `{KeyType => ValueType}` or `Hash{KeyType => ValueType}` is more good"
92
- add_offense(tag_range_for_comment(comment), message: message)
93
- else
94
- types_explainer.types.each { |t| check_mismatch_collection_type(comment, t) }
95
- end
96
- end
97
- end
98
-
99
- def extract_tag_type(tag)
100
- case tag
101
- when ::YARD::Tags::OptionTag
102
- tag.pair.types
103
- else
104
- tag.types
105
- end
106
- end
107
-
108
- def inline_comment?(comment)
109
- !comment_line?(comment.source_range.source_line)
110
- end
111
-
112
- def include_yard_tag?(comment)
113
- comment.source.match?(/@(?:param|return|option|raise|yieldparam|yieldreturn)\s+.*\[.*\]/)
114
- end
115
-
116
- def tag_range_for_comment(comment)
117
- start_column = comment.source.index(/\[/) + 1
118
- end_column = comment.source.index(/\]/)
119
- offense_start = comment.location.column + start_column
120
- offense_end = comment.location.column + end_column
121
- source_range(processed_source.buffer, comment.location.line, offense_start..offense_end)
122
- end
123
- end
124
- end
125
- end
126
- end