rubocop-rbs_inline 1.6.0 → 1.7.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/.rubocop.yml +48 -19
- data/CHANGELOG.md +14 -0
- data/README.md +70 -0
- data/Rakefile +19 -19
- data/Steepfile +2 -2
- data/config/default.yml +23 -51
- data/lib/rubocop/cop/rbs_inline_cops.rb +26 -20
- data/lib/rubocop/cop/style/rbs_inline/class_comment_alignment.rb +66 -0
- data/lib/rubocop/cop/style/rbs_inline/comment_parser.rb +5 -5
- data/lib/rubocop/cop/style/rbs_inline/data_class_comment_alignment.rb +6 -87
- data/lib/rubocop/cop/style/rbs_inline/data_define_with_block.rb +6 -7
- data/lib/rubocop/cop/style/rbs_inline/data_struct_helper.rb +97 -0
- data/lib/rubocop/cop/style/rbs_inline/embedded_rbs_spacing.rb +3 -3
- data/lib/rubocop/cop/style/rbs_inline/invalid_comment.rb +7 -7
- data/lib/rubocop/cop/style/rbs_inline/invalid_types.rb +2 -2
- data/lib/rubocop/cop/style/rbs_inline/keyword_separator.rb +3 -3
- data/lib/rubocop/cop/style/rbs_inline/method_comment_spacing.rb +6 -6
- data/lib/rubocop/cop/style/rbs_inline/missing_class_annotation.rb +85 -0
- data/lib/rubocop/cop/style/rbs_inline/missing_data_class_annotation.rb +7 -137
- data/lib/rubocop/cop/style/rbs_inline/missing_struct_class_annotation.rb +56 -0
- data/lib/rubocop/cop/style/rbs_inline/missing_type_annotation.rb +16 -16
- data/lib/rubocop/cop/style/rbs_inline/parameters_separator.rb +5 -5
- data/lib/rubocop/cop/style/rbs_inline/redundant_annotation_with_skip.rb +11 -11
- data/lib/rubocop/cop/style/rbs_inline/redundant_instance_variable_annotation.rb +2 -2
- data/lib/rubocop/cop/style/rbs_inline/redundant_type_annotation.rb +5 -5
- data/lib/rubocop/cop/style/rbs_inline/require_rbs_inline_comment.rb +3 -3
- data/lib/rubocop/cop/style/rbs_inline/source_code_helper.rb +2 -2
- data/lib/rubocop/cop/style/rbs_inline/struct_class_comment_alignment.rb +58 -0
- data/lib/rubocop/cop/style/rbs_inline/struct_new_with_block.rb +54 -0
- data/lib/rubocop/cop/style/rbs_inline/unmatched_annotations.rb +13 -13
- data/lib/rubocop/cop/style/rbs_inline/untyped_instance_variable.rb +10 -10
- data/lib/rubocop/cop/style/rbs_inline/variable_comment_spacing.rb +4 -4
- data/lib/rubocop/rbs_inline/plugin.rb +7 -7
- data/lib/rubocop/rbs_inline/version.rb +1 -1
- data/lib/rubocop/rbs_inline.rb +1 -1
- data/lib/rubocop-rbs_inline.rb +5 -5
- data/rbs_collection.lock.yaml +19 -11
- data/sig/rubocop/cop/style/rbs_inline/class_comment_alignment.rbs +34 -0
- data/sig/rubocop/cop/style/rbs_inline/data_class_comment_alignment.rbs +2 -30
- data/sig/rubocop/cop/style/rbs_inline/data_define_with_block.rbs +1 -1
- data/sig/rubocop/cop/style/rbs_inline/data_struct_helper.rbs +55 -0
- data/sig/rubocop/cop/style/rbs_inline/missing_class_annotation.rbs +41 -0
- data/sig/rubocop/cop/style/rbs_inline/missing_data_class_annotation.rbs +2 -46
- data/sig/rubocop/cop/style/rbs_inline/missing_struct_class_annotation.rbs +46 -0
- data/sig/rubocop/cop/style/rbs_inline/struct_class_comment_alignment.rbs +48 -0
- data/sig/rubocop/cop/style/rbs_inline/struct_new_with_block.rbs +40 -0
- metadata +14 -2
|
@@ -26,106 +26,25 @@ module RuboCop
|
|
|
26
26
|
# )
|
|
27
27
|
#
|
|
28
28
|
class DataClassCommentAlignment < Base
|
|
29
|
-
include
|
|
30
|
-
include RangeHelp
|
|
31
|
-
include SourceCodeHelper
|
|
29
|
+
include ClassCommentAlignment
|
|
32
30
|
extend AutoCorrector
|
|
33
31
|
|
|
34
|
-
MSG =
|
|
32
|
+
MSG = "Misaligned inline type annotation for Data attribute."
|
|
35
33
|
|
|
36
34
|
# @rbs node: RuboCop::AST::SendNode
|
|
37
35
|
def on_send(node) #: void
|
|
38
|
-
return unless
|
|
39
|
-
return if folded_data_class?(node)
|
|
36
|
+
return unless struct_like_class?(node)
|
|
40
37
|
|
|
41
|
-
|
|
38
|
+
check_comment_alignment(node)
|
|
42
39
|
end
|
|
43
40
|
|
|
44
41
|
private
|
|
45
42
|
|
|
46
43
|
# @rbs node: RuboCop::AST::SendNode
|
|
47
|
-
def
|
|
44
|
+
def struct_like_class?(node) #: bool
|
|
48
45
|
return false unless node.method_name == :define
|
|
49
46
|
|
|
50
|
-
|
|
51
|
-
when RuboCop::AST::ConstNode
|
|
52
|
-
r.short_name == :Data
|
|
53
|
-
else
|
|
54
|
-
false
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# @rbs arg: RuboCop::AST::Node
|
|
59
|
-
def data_attribute?(arg) #: bool
|
|
60
|
-
arg.sym_type? || arg.str_type?
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# @rbs node: RuboCop::AST::SendNode
|
|
64
|
-
def data_attributes(node) #: Array[RuboCop::AST::Node]
|
|
65
|
-
node.arguments.select { data_attribute?(_1) }
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# @rbs node: RuboCop::AST::SendNode
|
|
69
|
-
def folded_data_class?(node) #: bool
|
|
70
|
-
args = node.arguments
|
|
71
|
-
return false if args.empty?
|
|
72
|
-
|
|
73
|
-
lines = args.map { _1.location.line }
|
|
74
|
-
lines.uniq.length < args.length || lines.include?(node.location.line)
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# @rbs node: RuboCop::AST::SendNode
|
|
78
|
-
def check_annotation_alignment(node) #: void
|
|
79
|
-
annotated = data_attributes(node).filter_map do |arg|
|
|
80
|
-
comment = inline_type_comment(arg.location.line)
|
|
81
|
-
[arg, comment] if comment #: [RuboCop::AST::Node, Parser::Source::Comment]
|
|
82
|
-
end
|
|
83
|
-
return if annotated.size < 2
|
|
84
|
-
|
|
85
|
-
expected_col = annotation_column(node)
|
|
86
|
-
annotated.each do |arg, comment|
|
|
87
|
-
actual_col = comment.location.column
|
|
88
|
-
next if actual_col == expected_col
|
|
89
|
-
|
|
90
|
-
add_offense(comment.source_range) do |corrector|
|
|
91
|
-
correct_alignment(corrector, arg, comment, expected_col)
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
# @rbs line: Integer
|
|
97
|
-
def inline_type_comment(line) #: Parser::Source::Comment?
|
|
98
|
-
comment = comment_at(line)
|
|
99
|
-
comment if comment&.text&.match?(/\A#:/)
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
# @rbs node: RuboCop::AST::SendNode
|
|
103
|
-
def annotation_column(node) #: Integer
|
|
104
|
-
last_arg = node.arguments.last
|
|
105
|
-
max_end_col = node.arguments.map do |arg|
|
|
106
|
-
comma_length = arg.equal?(last_arg) ? 0 : 1
|
|
107
|
-
arg.location.column + source!(arg).length + comma_length
|
|
108
|
-
end.max || 0
|
|
109
|
-
|
|
110
|
-
max_end_col + 2
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# @rbs corrector: RuboCop::Cop::Corrector
|
|
114
|
-
# @rbs arg: RuboCop::AST::Node
|
|
115
|
-
# @rbs comment: Parser::Source::Comment
|
|
116
|
-
# @rbs expected_col: Integer
|
|
117
|
-
def correct_alignment(corrector, arg, comment, expected_col) #: void # rubocop:disable Metrics/AbcSize
|
|
118
|
-
line = arg.location.line
|
|
119
|
-
line_source = source_code_at(line)
|
|
120
|
-
source = line_source[...comment.location.column] || raise
|
|
121
|
-
content_end_col = source.rstrip.length
|
|
122
|
-
padding = [expected_col - content_end_col, 1].max || raise
|
|
123
|
-
|
|
124
|
-
line_begin = processed_source.buffer.line_range(line).begin_pos
|
|
125
|
-
replace_start = line_begin + content_end_col
|
|
126
|
-
replace_end = line_begin + comment.location.column
|
|
127
|
-
|
|
128
|
-
corrector.replace(range_between(replace_start, replace_end), ' ' * padding)
|
|
47
|
+
(r = node.receiver).is_a?(RuboCop::AST::ConstNode) && r.short_name == :Data
|
|
129
48
|
end
|
|
130
49
|
end
|
|
131
50
|
end
|
|
@@ -28,13 +28,13 @@ module RuboCop
|
|
|
28
28
|
# end
|
|
29
29
|
#
|
|
30
30
|
class DataDefineWithBlock < Base
|
|
31
|
-
MSG =
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
MSG = "Do not use `Data.define` with a block. RBS::Inline does not parse block contents, " \
|
|
32
|
+
"so methods defined in the block will not be recognized. " \
|
|
33
|
+
"Use a separate class definition instead."
|
|
34
34
|
|
|
35
35
|
# @rbs node: RuboCop::AST::SendNode
|
|
36
36
|
def on_send(node) #: void
|
|
37
|
-
return unless
|
|
37
|
+
return unless struct_like_class?(node)
|
|
38
38
|
|
|
39
39
|
block_node = node.parent
|
|
40
40
|
return unless block_node&.block_type?
|
|
@@ -45,11 +45,10 @@ module RuboCop
|
|
|
45
45
|
private
|
|
46
46
|
|
|
47
47
|
# @rbs node: RuboCop::AST::SendNode
|
|
48
|
-
def
|
|
48
|
+
def struct_like_class?(node) #: bool
|
|
49
49
|
return false unless node.method_name == :define
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
receiver.is_a?(RuboCop::AST::ConstNode) && receiver.short_name == :Data
|
|
51
|
+
(r = node.receiver).is_a?(RuboCop::AST::ConstNode) && r.short_name == :Data
|
|
53
52
|
end
|
|
54
53
|
end
|
|
55
54
|
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Style
|
|
6
|
+
module RbsInline
|
|
7
|
+
# Shared logic for cops that check inline type annotations on struct-like
|
|
8
|
+
# class definitions such as `Data.define` and `Struct.new`.
|
|
9
|
+
#
|
|
10
|
+
# Including cops decide which nodes to check by defining their own matcher
|
|
11
|
+
# (e.g. `struct_like_class?`); this module only provides helpers that operate
|
|
12
|
+
# on an already-matched definition node. Cops may override {#attr_argument?}
|
|
13
|
+
# when the set of attribute arguments differs from the default (e.g.
|
|
14
|
+
# `Struct.new` treats a leading string argument as the struct name rather than
|
|
15
|
+
# an attribute).
|
|
16
|
+
#
|
|
17
|
+
# @rbs module-self RuboCop::Cop::Base
|
|
18
|
+
module DataStructHelper
|
|
19
|
+
include ASTUtils
|
|
20
|
+
include SourceCodeHelper
|
|
21
|
+
include RangeHelp
|
|
22
|
+
|
|
23
|
+
# @rbs arg: RuboCop::AST::Node
|
|
24
|
+
def attr_argument?(arg) #: bool
|
|
25
|
+
arg.sym_type? || arg.str_type?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @rbs node: RuboCop::AST::SendNode
|
|
29
|
+
def attr_arguments(node) #: Array[RuboCop::AST::Node]
|
|
30
|
+
node.arguments.select { attr_argument?(_1) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @rbs node: RuboCop::AST::SendNode
|
|
34
|
+
def folded?(node) #: bool
|
|
35
|
+
attrs = attr_arguments(node)
|
|
36
|
+
return false if attrs.empty?
|
|
37
|
+
|
|
38
|
+
lines = attrs.map { _1.location.line }
|
|
39
|
+
lines.uniq.length < attrs.length || lines.include?(node.location.line)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @rbs line: Integer
|
|
43
|
+
def inline_type_annotation?(line) #: boolish
|
|
44
|
+
comment = comment_at(line)
|
|
45
|
+
comment&.text&.match?(/\A#:/)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @rbs line: Integer
|
|
49
|
+
def inline_type_comment(line) #: Parser::Source::Comment?
|
|
50
|
+
comment = comment_at(line)
|
|
51
|
+
comment if comment&.text&.match?(/\A#:/)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @rbs line: Integer
|
|
55
|
+
def find_regular_comment(line) #: Parser::Source::Comment?
|
|
56
|
+
comment = comment_at(line)
|
|
57
|
+
comment unless comment&.text&.match?(/\A#:/)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @rbs node: RuboCop::AST::SendNode
|
|
61
|
+
def annotation_column(node) #: Integer
|
|
62
|
+
last_arg = node.arguments.last
|
|
63
|
+
max_end_col = node.arguments.map do |arg|
|
|
64
|
+
comma_length = arg.equal?(last_arg) ? 0 : 1
|
|
65
|
+
arg.location.column + source!(arg).length + comma_length
|
|
66
|
+
end.max || 0
|
|
67
|
+
|
|
68
|
+
max_end_col + 2
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @rbs node: RuboCop::AST::SendNode
|
|
72
|
+
def longest_argname(node) #: String
|
|
73
|
+
last_index = node.arguments.size - 1
|
|
74
|
+
args = node.arguments.each_with_index.map { |a, i| i < last_index ? "#{a.source}," : a.source.to_s }
|
|
75
|
+
args.max_by(&:length) || ""
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @rbs node: RuboCop::AST::SendNode
|
|
79
|
+
def build_multiline_replacement(node) #: String # rubocop:disable Metrics/AbcSize
|
|
80
|
+
base_indent = source_code_at(node.location.line)[/\A\s*/]
|
|
81
|
+
last_index = node.arguments.size - 1
|
|
82
|
+
longest = longest_argname(node)
|
|
83
|
+
|
|
84
|
+
args_source = node.arguments.each_with_index.map do |arg, i|
|
|
85
|
+
comma = i < last_index ? "," : ""
|
|
86
|
+
prefix = "#{base_indent} #{arg.source}#{comma}"
|
|
87
|
+
padding = " " * (longest.length - source!(arg).length - comma.length + 2)
|
|
88
|
+
attr_argument?(arg) ? "#{prefix}#{padding}#: untyped" : prefix
|
|
89
|
+
end.join("\n")
|
|
90
|
+
|
|
91
|
+
"(\n#{args_source}\n#{base_indent})"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
4
|
-
require_relative
|
|
3
|
+
require_relative "source_code_helper"
|
|
4
|
+
require_relative "comment_parser"
|
|
5
5
|
|
|
6
6
|
module RuboCop
|
|
7
7
|
module Cop
|
|
@@ -30,7 +30,7 @@ module RuboCop
|
|
|
30
30
|
include SourceCodeHelper
|
|
31
31
|
include CommentParser
|
|
32
32
|
|
|
33
|
-
MSG =
|
|
33
|
+
MSG = "`@rbs!` comment must be followed by a blank line."
|
|
34
34
|
|
|
35
35
|
def on_new_investigation #: void
|
|
36
36
|
check_embedded_rbs_spacing
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "rbs/inline/annotation_parser/tokenizer"
|
|
4
4
|
|
|
5
5
|
module RuboCop
|
|
6
6
|
module Cop
|
|
@@ -22,11 +22,11 @@ module RuboCop
|
|
|
22
22
|
class InvalidComment < Base
|
|
23
23
|
extend AutoCorrector
|
|
24
24
|
|
|
25
|
-
MSG =
|
|
25
|
+
MSG = "Invalid RBS annotation comment found."
|
|
26
26
|
|
|
27
27
|
# refs: https://github.com/soutaro/rbs-inline/blob/main/lib/rbs/inline/annotation_parser/tokenizer.rb
|
|
28
28
|
RBS_INLINE_KEYWORDS = %w[inherits override use module-self generic skip module class].freeze #: Array[String]
|
|
29
|
-
RBS_INLINE_KEYWORD_PATTERN = RBS_INLINE_KEYWORDS.join(
|
|
29
|
+
RBS_INLINE_KEYWORD_PATTERN = RBS_INLINE_KEYWORDS.join("|") #: String
|
|
30
30
|
|
|
31
31
|
SIGNATURE_PATTERN = '\(.*\)\s*(\??\s*{.*?}\s*)?->\s*.*'
|
|
32
32
|
|
|
@@ -56,10 +56,10 @@ module RuboCop
|
|
|
56
56
|
# @rbs text: String
|
|
57
57
|
def corrected_text(text) #: String?
|
|
58
58
|
case text
|
|
59
|
-
when MISSING_HASH_COLON then text.sub(/\A#\s+/,
|
|
60
|
-
when SPACE_BEFORE_COLON then text.sub(/\A#\s+:\s*/,
|
|
61
|
-
when MIXED_ANNOTATION then text.sub(/\A#:\s+/,
|
|
62
|
-
when MISSING_AT_SIGN then text.sub(/\A#\s*rbs\s+/,
|
|
59
|
+
when MISSING_HASH_COLON then text.sub(/\A#\s+/, "#: ")
|
|
60
|
+
when SPACE_BEFORE_COLON then text.sub(/\A#\s+:\s*/, "#: ")
|
|
61
|
+
when MIXED_ANNOTATION then text.sub(/\A#:\s+/, "# ")
|
|
62
|
+
when MISSING_AT_SIGN then text.sub(/\A#\s*rbs\s+/, "# @rbs ")
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "rbs/inline"
|
|
4
4
|
|
|
5
5
|
module RuboCop
|
|
6
6
|
module Cop
|
|
@@ -27,7 +27,7 @@ module RuboCop
|
|
|
27
27
|
include RBS::Inline::AST::Annotations
|
|
28
28
|
include RBS::Inline::AST::Members
|
|
29
29
|
|
|
30
|
-
MSG =
|
|
30
|
+
MSG = "Invalid annotation found."
|
|
31
31
|
|
|
32
32
|
def on_new_investigation #: void
|
|
33
33
|
parse_comments.each do |result|
|
|
@@ -19,7 +19,7 @@ module RuboCop
|
|
|
19
19
|
include CommentParser
|
|
20
20
|
include RangeHelp
|
|
21
21
|
|
|
22
|
-
MSG =
|
|
22
|
+
MSG = "Do not use `:` after the keyword."
|
|
23
23
|
|
|
24
24
|
# refs: https://github.com/soutaro/rbs-inline/blob/main/lib/rbs/inline/annotation_parser/tokenizer.rb
|
|
25
25
|
RBS_INLINE_KEYWORDS = %w[inherits override use module-self generic skip module class].freeze #: Array[String]
|
|
@@ -49,7 +49,7 @@ module RuboCop
|
|
|
49
49
|
|
|
50
50
|
def on_investigation_end #: void
|
|
51
51
|
processed_source.comments.each do |comment|
|
|
52
|
-
matched = comment.text.match(/\A#\s+@rbs\s+(#{RBS_INLINE_KEYWORDS.join(
|
|
52
|
+
matched = comment.text.match(/\A#\s+@rbs\s+(#{RBS_INLINE_KEYWORDS.join("|")}):/)
|
|
53
53
|
next unless matched
|
|
54
54
|
next if valid_method_annotation?(comment)
|
|
55
55
|
|
|
@@ -86,7 +86,7 @@ module RuboCop
|
|
|
86
86
|
# followed by `:` but no type.
|
|
87
87
|
# @rbs comment: Parser::Source::Comment
|
|
88
88
|
def no_argument_keyword_without_type?(comment) #: bool
|
|
89
|
-
comment.text.match?(/\A#\s+@rbs\s+(#{NO_ARGUMENT_KEYWORDS.join(
|
|
89
|
+
comment.text.match?(/\A#\s+@rbs\s+(#{NO_ARGUMENT_KEYWORDS.join("|")}):\s*\z/)
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
# @rbs comment: Parser::Source::Comment
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
4
|
-
require_relative
|
|
3
|
+
require_relative "source_code_helper"
|
|
4
|
+
require_relative "comment_parser"
|
|
5
5
|
|
|
6
6
|
module RuboCop
|
|
7
7
|
module Cop
|
|
@@ -61,8 +61,8 @@ module RuboCop
|
|
|
61
61
|
include SourceCodeHelper
|
|
62
62
|
include CommentParser
|
|
63
63
|
|
|
64
|
-
MSG =
|
|
65
|
-
MSG_WITH_BLANK_LINE =
|
|
64
|
+
MSG = "Method-related `@rbs` annotation must be immediately before a method definition."
|
|
65
|
+
MSG_WITH_BLANK_LINE = "Remove blank line between method annotation and method definition."
|
|
66
66
|
METHOD_DEFINITION_PATTERN =
|
|
67
67
|
/\A(?:(?:private|protected|public|private_class_method|module_function)\s+)?def\s/ #: Regexp
|
|
68
68
|
|
|
@@ -115,7 +115,7 @@ module RuboCop
|
|
|
115
115
|
when RBS::Inline::AST::Annotations::VarType
|
|
116
116
|
# VarType includes both parameter types and variable types (@ivar, @@cvar)
|
|
117
117
|
# Only consider it method-related if it's a parameter annotation (name doesn't start with @)
|
|
118
|
-
return true unless annotation.name.start_with?(
|
|
118
|
+
return true unless annotation.name.start_with?("@")
|
|
119
119
|
when RBS::Inline::AST::Annotations::ReturnType,
|
|
120
120
|
RBS::Inline::AST::Annotations::BlockType,
|
|
121
121
|
RBS::Inline::AST::Annotations::Override,
|
|
@@ -166,7 +166,7 @@ module RuboCop
|
|
|
166
166
|
# @rbs comment: RBS::Inline::AnnotationParser::ParsingResult
|
|
167
167
|
def skip_only_annotation?(comment) #: bool
|
|
168
168
|
annotations = [] #: Array[RBS::Inline::AST::Annotations::t]
|
|
169
|
-
comment.each_annotation {
|
|
169
|
+
comment.each_annotation { annotations << _1 }
|
|
170
170
|
annotations.any? && annotations.all?(RBS::Inline::AST::Annotations::Skip)
|
|
171
171
|
end
|
|
172
172
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Style
|
|
6
|
+
module RbsInline
|
|
7
|
+
# Shared behavior for cops that ensure struct-like class attributes have
|
|
8
|
+
# inline type annotations (`Data.define` / `Struct.new`).
|
|
9
|
+
#
|
|
10
|
+
# The including cop matches the definition node (e.g. via `struct_like_class?`)
|
|
11
|
+
# and calls {#check_missing_annotations}.
|
|
12
|
+
#
|
|
13
|
+
# @rbs module-self RuboCop::Cop::Base
|
|
14
|
+
module MissingClassAnnotation
|
|
15
|
+
include DataStructHelper
|
|
16
|
+
|
|
17
|
+
# @rbs node: RuboCop::AST::SendNode
|
|
18
|
+
def check_missing_annotations(node) #: void
|
|
19
|
+
if folded?(node)
|
|
20
|
+
add_offenses_for_folded(node)
|
|
21
|
+
else
|
|
22
|
+
check_multiline(node)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
# @rbs node: RuboCop::AST::SendNode
|
|
29
|
+
def add_offenses_for_folded(node) #: void
|
|
30
|
+
corrected = false
|
|
31
|
+
attr_arguments(node).each do |arg|
|
|
32
|
+
if corrected
|
|
33
|
+
add_offense(arg)
|
|
34
|
+
else
|
|
35
|
+
replacement = build_multiline_replacement(node)
|
|
36
|
+
loc = node.loc
|
|
37
|
+
if loc.begin && loc.end
|
|
38
|
+
add_offense(arg) { _1.replace(loc.begin.join(loc.end), replacement) }
|
|
39
|
+
corrected = true
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @rbs node: RuboCop::AST::SendNode
|
|
46
|
+
def check_multiline(node) #: void
|
|
47
|
+
attr_arguments(node).each do |arg|
|
|
48
|
+
next if inline_type_annotation?(arg.location.line)
|
|
49
|
+
|
|
50
|
+
add_offense(arg) { correct_multiline(_1, node, arg) }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @rbs corrector: RuboCop::Cop::Corrector
|
|
55
|
+
# @rbs node: RuboCop::AST::SendNode
|
|
56
|
+
# @rbs arg: RuboCop::AST::Node
|
|
57
|
+
def correct_multiline(corrector, node, arg) #: void
|
|
58
|
+
existing_comment = find_regular_comment(arg.location.line)
|
|
59
|
+
|
|
60
|
+
if existing_comment
|
|
61
|
+
comment_text = existing_comment.text.sub(/\A#\s*/, "")
|
|
62
|
+
corrector.replace(existing_comment.source_range, "#: untyped -- #{comment_text}")
|
|
63
|
+
else
|
|
64
|
+
insert_annotation(corrector, node, arg)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @rbs corrector: RuboCop::Cop::Corrector
|
|
69
|
+
# @rbs node: RuboCop::AST::SendNode
|
|
70
|
+
# @rbs arg: RuboCop::AST::Node
|
|
71
|
+
def insert_annotation(corrector, node, arg) #: void
|
|
72
|
+
line = arg.location.line
|
|
73
|
+
line_source = source_code_at(line)
|
|
74
|
+
content_end_col = line_source.rstrip.length
|
|
75
|
+
padding = [annotation_column(node) - content_end_col, 1].max || raise
|
|
76
|
+
line_begin = processed_source.buffer.line_range(line).begin_pos
|
|
77
|
+
insert_pos = line_begin + content_end_col
|
|
78
|
+
|
|
79
|
+
corrector.insert_before(range_between(insert_pos, insert_pos), "#{" " * padding}#: untyped")
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -20,156 +20,26 @@ module RuboCop
|
|
|
20
20
|
# :visibility #: Symbol
|
|
21
21
|
# )
|
|
22
22
|
#
|
|
23
|
-
class MissingDataClassAnnotation < Base
|
|
24
|
-
include
|
|
25
|
-
include RangeHelp
|
|
26
|
-
include SourceCodeHelper
|
|
23
|
+
class MissingDataClassAnnotation < Base
|
|
24
|
+
include MissingClassAnnotation
|
|
27
25
|
extend AutoCorrector
|
|
28
26
|
|
|
29
|
-
MSG =
|
|
27
|
+
MSG = "Missing inline type annotation for Data attribute (e.g., `#: Type`)."
|
|
30
28
|
|
|
31
29
|
# @rbs node: RuboCop::AST::SendNode
|
|
32
30
|
def on_send(node) #: void
|
|
33
|
-
return unless
|
|
31
|
+
return unless struct_like_class?(node)
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
add_offenses_for_folded_data_class(node)
|
|
37
|
-
else
|
|
38
|
-
check_multiline_data_class(node)
|
|
39
|
-
end
|
|
33
|
+
check_missing_annotations(node)
|
|
40
34
|
end
|
|
41
35
|
|
|
42
36
|
private
|
|
43
37
|
|
|
44
38
|
# @rbs node: RuboCop::AST::SendNode
|
|
45
|
-
def
|
|
39
|
+
def struct_like_class?(node) #: bool
|
|
46
40
|
return false unless node.method_name == :define
|
|
47
41
|
|
|
48
|
-
|
|
49
|
-
when RuboCop::AST::ConstNode
|
|
50
|
-
r.short_name == :Data
|
|
51
|
-
else
|
|
52
|
-
false
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# @rbs arg: RuboCop::AST::Node
|
|
57
|
-
def data_attribute?(arg) #: bool
|
|
58
|
-
arg.sym_type? || arg.str_type?
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# @rbs node: RuboCop::AST::SendNode
|
|
62
|
-
def data_attributes(node) #: Array[RuboCop::AST::Node]
|
|
63
|
-
node.arguments.select { data_attribute?(_1) }
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# @rbs line: Integer
|
|
67
|
-
def inline_type_annotation?(line) #: boolish
|
|
68
|
-
comment = comment_at(line)
|
|
69
|
-
comment&.text&.match?(/\A#:/)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# @rbs node: RuboCop::AST::SendNode
|
|
73
|
-
def folded_data_class?(node) #: bool
|
|
74
|
-
attrs = data_attributes(node)
|
|
75
|
-
return false if attrs.empty?
|
|
76
|
-
|
|
77
|
-
lines = attrs.map { _1.location.line }
|
|
78
|
-
lines.uniq.length < attrs.length || lines.include?(node.location.line)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# @rbs node: RuboCop::AST::SendNode
|
|
82
|
-
def add_offenses_for_folded_data_class(node) #: void
|
|
83
|
-
corrected = false
|
|
84
|
-
data_attributes(node).each do |arg|
|
|
85
|
-
if corrected
|
|
86
|
-
add_offense(arg)
|
|
87
|
-
else
|
|
88
|
-
replacement = build_multiline_replacement(node)
|
|
89
|
-
loc = node.loc
|
|
90
|
-
if loc.begin && loc.end
|
|
91
|
-
add_offense(arg) { _1.replace(loc.begin.join(loc.end), replacement) }
|
|
92
|
-
corrected = true
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# @rbs node: RuboCop::AST::SendNode
|
|
99
|
-
def longest_argname(node) #: String
|
|
100
|
-
last_index = node.arguments.size - 1
|
|
101
|
-
args = node.arguments.each_with_index.map { |a, i| i < last_index ? "#{a.source}," : a.source.to_s }
|
|
102
|
-
args.max_by(&:length) || ''
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# @rbs node: RuboCop::AST::SendNode
|
|
106
|
-
def build_multiline_replacement(node) #: String # rubocop:disable Metrics/AbcSize
|
|
107
|
-
base_indent = source_code_at(node.location.line)[/\A\s*/]
|
|
108
|
-
last_index = node.arguments.size - 1
|
|
109
|
-
longest = longest_argname(node)
|
|
110
|
-
|
|
111
|
-
args_source = node.arguments.each_with_index.map do |arg, i|
|
|
112
|
-
comma = i < last_index ? ',' : ''
|
|
113
|
-
prefix = "#{base_indent} #{arg.source}#{comma}"
|
|
114
|
-
padding = ' ' * (longest.length - source!(arg).length - comma.length + 2)
|
|
115
|
-
data_attribute?(arg) ? "#{prefix}#{padding}#: untyped" : prefix
|
|
116
|
-
end.join("\n")
|
|
117
|
-
|
|
118
|
-
"(\n#{args_source}\n#{base_indent})"
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# @rbs node: RuboCop::AST::SendNode
|
|
122
|
-
def check_multiline_data_class(node) #: void
|
|
123
|
-
data_attributes(node).each do |arg|
|
|
124
|
-
next if inline_type_annotation?(arg.location.line)
|
|
125
|
-
|
|
126
|
-
add_offense(arg) { |corrector| correct_multiline(corrector, node, arg) }
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
# @rbs corrector: RuboCop::Cop::Corrector
|
|
131
|
-
# @rbs node: RuboCop::AST::SendNode
|
|
132
|
-
# @rbs arg: RuboCop::AST::Node
|
|
133
|
-
def correct_multiline(corrector, node, arg) #: void
|
|
134
|
-
existing_comment = find_regular_comment(arg.location.line)
|
|
135
|
-
|
|
136
|
-
if existing_comment
|
|
137
|
-
comment_text = existing_comment.text.sub(/\A#\s*/, '')
|
|
138
|
-
corrector.replace(existing_comment.source_range, "#: untyped -- #{comment_text}")
|
|
139
|
-
else
|
|
140
|
-
insert_annotation(corrector, node, arg)
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
# @rbs corrector: RuboCop::Cop::Corrector
|
|
145
|
-
# @rbs node: RuboCop::AST::SendNode
|
|
146
|
-
# @rbs arg: RuboCop::AST::Node
|
|
147
|
-
def insert_annotation(corrector, node, arg) #: void
|
|
148
|
-
line = arg.location.line
|
|
149
|
-
line_source = source_code_at(line)
|
|
150
|
-
content_end_col = line_source.rstrip.length
|
|
151
|
-
padding = [annotation_column(node) - content_end_col, 1].max || raise
|
|
152
|
-
line_begin = processed_source.buffer.line_range(line).begin_pos
|
|
153
|
-
insert_pos = line_begin + content_end_col
|
|
154
|
-
|
|
155
|
-
corrector.insert_before(range_between(insert_pos, insert_pos), "#{' ' * padding}#: untyped")
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
# @rbs node: RuboCop::AST::SendNode
|
|
159
|
-
def annotation_column(node) #: Integer
|
|
160
|
-
last_arg = node.arguments.last
|
|
161
|
-
max_end_col = node.arguments.map do |arg|
|
|
162
|
-
comma_length = arg.equal?(last_arg) ? 0 : 1
|
|
163
|
-
arg.location.column + source!(arg).length + comma_length
|
|
164
|
-
end.max || 0
|
|
165
|
-
|
|
166
|
-
max_end_col + 2
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
# @rbs line: Integer
|
|
170
|
-
def find_regular_comment(line) #: Parser::Source::Comment?
|
|
171
|
-
comment = comment_at(line)
|
|
172
|
-
comment unless comment&.text&.match?(/\A#:/)
|
|
42
|
+
(r = node.receiver).is_a?(RuboCop::AST::ConstNode) && r.short_name == :Data
|
|
173
43
|
end
|
|
174
44
|
end
|
|
175
45
|
end
|