rubocop-rbs_inline 1.6.1 → 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/CHANGELOG.md +8 -0
- data/README.md +70 -0
- data/config/default.yml +15 -0
- data/lib/rubocop/cop/rbs_inline_cops.rb +6 -0
- data/lib/rubocop/cop/style/rbs_inline/class_comment_alignment.rb +66 -0
- data/lib/rubocop/cop/style/rbs_inline/data_class_comment_alignment.rb +5 -86
- data/lib/rubocop/cop/style/rbs_inline/data_define_with_block.rb +3 -4
- data/lib/rubocop/cop/style/rbs_inline/data_struct_helper.rb +97 -0
- 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 +5 -135
- 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 +1 -1
- 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/rbs_inline/version.rb +1 -1
- data/rbs_collection.lock.yaml +5 -1
- 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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz: '
|
|
3
|
+
metadata.gz: 5f1bf0f9d17e73042a1c33ae69ffe0cf079bedb1423119b36a524d085cfe6f6a
|
|
4
|
+
data.tar.gz: '0797210c998bb891bcd07b68f3d4cc9e779cf775ea051e6529c8b052ed58642a'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bb484322f4605db7c86dddf047de1ad1dbe477faf11b7d736edaa544132b87c1b7c0481cd13a6154662a0e318daeaf4880102882288cf02b84ba88eb8d3372f5
|
|
7
|
+
data.tar.gz: 141d659c28c9c9b2bef362b7f5b57bccf68aaec436b553e19785d97d162a0293c94d0d79ad7d29b7300f0b2583cce353c3152503ec965ab521eeff6fe3bb1127
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.7.0 (2026-07-20)
|
|
4
|
+
|
|
5
|
+
### New Features
|
|
6
|
+
|
|
7
|
+
- **Style/RbsInline/MissingStructClassAnnotation**: New cop that checks each attribute passed to `Struct.new` has a trailing `#:` inline type annotation. Supports autocorrect.
|
|
8
|
+
- **Style/RbsInline/StructClassCommentAlignment**: New cop that checks `#:` inline type annotations in a multiline `Struct.new` call are aligned. Supports autocorrect.
|
|
9
|
+
- **Style/RbsInline/StructNewWithBlock**: New cop that checks for `Struct.new` calls with a block, which RBS::Inline does not parse.
|
|
10
|
+
|
|
3
11
|
## 1.6.1 (2026-07-10)
|
|
4
12
|
|
|
5
13
|
### Bug Fixes
|
data/README.md
CHANGED
|
@@ -198,6 +198,32 @@ MethodEntry = Data.define(
|
|
|
198
198
|
)
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
+
### Style/RbsInline/MissingStructClassAnnotation
|
|
202
|
+
|
|
203
|
+
Checks that each attribute passed to `Struct.new` has a trailing `#:` inline type annotation on the same line.
|
|
204
|
+
|
|
205
|
+
For folded `Struct.new` calls (where multiple attributes share a line), the cop will suggest rewriting as a multiline call so each attribute can be annotated individually. A leading string argument (the struct name) and the `keyword_init:` keyword argument are not attributes and are ignored.
|
|
206
|
+
|
|
207
|
+
Supports autocorrect.
|
|
208
|
+
|
|
209
|
+
**Examples:**
|
|
210
|
+
```ruby
|
|
211
|
+
# bad
|
|
212
|
+
Point = Struct.new(:x, :y)
|
|
213
|
+
|
|
214
|
+
# bad - missing annotation for :y
|
|
215
|
+
Point = Struct.new(
|
|
216
|
+
:x, #: Integer
|
|
217
|
+
:y
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# good
|
|
221
|
+
Point = Struct.new(
|
|
222
|
+
:x, #: Integer
|
|
223
|
+
:y #: Integer
|
|
224
|
+
)
|
|
225
|
+
```
|
|
226
|
+
|
|
201
227
|
### Style/RbsInline/MissingTypeAnnotation
|
|
202
228
|
|
|
203
229
|
Enforces that method definitions and `attr_*` declarations have RBS inline type annotations.
|
|
@@ -458,6 +484,50 @@ class Foo
|
|
|
458
484
|
end
|
|
459
485
|
```
|
|
460
486
|
|
|
487
|
+
### Style/RbsInline/StructClassCommentAlignment
|
|
488
|
+
|
|
489
|
+
Checks that `#:` inline type annotations in a multiline `Struct.new` call are aligned to the same column. The expected column is determined by the longest attribute name (plus its trailing comma). Folded `Struct.new` calls (where multiple attributes share a line) are excluded.
|
|
490
|
+
|
|
491
|
+
Supports autocorrect.
|
|
492
|
+
|
|
493
|
+
**Examples:**
|
|
494
|
+
```ruby
|
|
495
|
+
# bad
|
|
496
|
+
Point = Struct.new(
|
|
497
|
+
:x, #: Integer
|
|
498
|
+
:long_attr #: Integer
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
# good
|
|
502
|
+
Point = Struct.new(
|
|
503
|
+
:x, #: Integer
|
|
504
|
+
:long_attr #: Integer
|
|
505
|
+
)
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Style/RbsInline/StructNewWithBlock
|
|
509
|
+
|
|
510
|
+
Checks for `Struct.new` calls with a block. RBS::Inline does not parse block contents, so any methods defined inside the block will not be recognized for type checking. Instead, call `Struct.new` without a block and reopen the class separately to add methods.
|
|
511
|
+
|
|
512
|
+
**Examples:**
|
|
513
|
+
```ruby
|
|
514
|
+
# bad
|
|
515
|
+
User = Struct.new(:name, :role) do
|
|
516
|
+
def admin?
|
|
517
|
+
role == :admin
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
# good
|
|
522
|
+
User = Struct.new(:name, :role)
|
|
523
|
+
|
|
524
|
+
class User
|
|
525
|
+
def admin? #: bool
|
|
526
|
+
role == :admin
|
|
527
|
+
end
|
|
528
|
+
end
|
|
529
|
+
```
|
|
530
|
+
|
|
461
531
|
### Style/RbsInline/UnmatchedAnnotations
|
|
462
532
|
|
|
463
533
|
Verifies that annotation parameters match the actual method parameters.
|
data/config/default.yml
CHANGED
|
@@ -46,6 +46,11 @@ Style/RbsInline/MissingDataClassAnnotation:
|
|
|
46
46
|
Enabled: true
|
|
47
47
|
VersionAdded: '1.5.0'
|
|
48
48
|
|
|
49
|
+
Style/RbsInline/MissingStructClassAnnotation:
|
|
50
|
+
Description: "Checks that `Struct.new` attributes have inline type annotations."
|
|
51
|
+
Enabled: true
|
|
52
|
+
VersionAdded: '1.7.0'
|
|
53
|
+
|
|
49
54
|
Style/RbsInline/MissingTypeAnnotation:
|
|
50
55
|
Description: "Checks that method definitions and attr_* declarations have RBS inline type annotations."
|
|
51
56
|
Enabled: true
|
|
@@ -94,6 +99,16 @@ Style/RbsInline/RequireRbsInlineComment:
|
|
|
94
99
|
- always
|
|
95
100
|
- never
|
|
96
101
|
|
|
102
|
+
Style/RbsInline/StructClassCommentAlignment:
|
|
103
|
+
Description: "Checks that `#:` inline type annotations in `Struct.new` blocks are aligned."
|
|
104
|
+
Enabled: true
|
|
105
|
+
VersionAdded: '1.7.0'
|
|
106
|
+
|
|
107
|
+
Style/RbsInline/StructNewWithBlock:
|
|
108
|
+
Description: "Checks for `Struct.new` calls with a block, which RBS::Inline does not parse."
|
|
109
|
+
Enabled: true
|
|
110
|
+
VersionAdded: '1.7.0'
|
|
111
|
+
|
|
97
112
|
Style/RbsInline/UnmatchedAnnotations:
|
|
98
113
|
Description: "Checks the rbs-inline annotation comment is surely matched."
|
|
99
114
|
Enabled: true
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
require_relative "style/rbs_inline/ast_utils"
|
|
4
4
|
require_relative "style/rbs_inline/source_code_helper"
|
|
5
5
|
require_relative "style/rbs_inline/comment_parser"
|
|
6
|
+
require_relative "style/rbs_inline/data_struct_helper"
|
|
7
|
+
require_relative "style/rbs_inline/missing_class_annotation"
|
|
8
|
+
require_relative "style/rbs_inline/class_comment_alignment"
|
|
6
9
|
require_relative "style/rbs_inline/data_class_comment_alignment"
|
|
7
10
|
require_relative "style/rbs_inline/data_define_with_block"
|
|
8
11
|
require_relative "style/rbs_inline/embedded_rbs_spacing"
|
|
@@ -17,6 +20,9 @@ require_relative "style/rbs_inline/redundant_annotation_with_skip"
|
|
|
17
20
|
require_relative "style/rbs_inline/redundant_instance_variable_annotation"
|
|
18
21
|
require_relative "style/rbs_inline/redundant_type_annotation"
|
|
19
22
|
require_relative "style/rbs_inline/require_rbs_inline_comment"
|
|
23
|
+
require_relative "style/rbs_inline/struct_class_comment_alignment"
|
|
24
|
+
require_relative "style/rbs_inline/struct_new_with_block"
|
|
25
|
+
require_relative "style/rbs_inline/missing_struct_class_annotation"
|
|
20
26
|
require_relative "style/rbs_inline/unmatched_annotations"
|
|
21
27
|
require_relative "style/rbs_inline/untyped_instance_variable"
|
|
22
28
|
require_relative "style/rbs_inline/variable_comment_spacing"
|
|
@@ -0,0 +1,66 @@
|
|
|
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 `#:` inline type annotations on
|
|
8
|
+
# struct-like class attributes (`Data.define` / `Struct.new`) are aligned.
|
|
9
|
+
#
|
|
10
|
+
# The including cop matches the definition node (e.g. via `struct_like_class?`)
|
|
11
|
+
# and calls {#check_comment_alignment}.
|
|
12
|
+
#
|
|
13
|
+
# @rbs module-self RuboCop::Cop::Base
|
|
14
|
+
module ClassCommentAlignment
|
|
15
|
+
include DataStructHelper
|
|
16
|
+
|
|
17
|
+
# @rbs node: RuboCop::AST::SendNode
|
|
18
|
+
def check_comment_alignment(node) #: void
|
|
19
|
+
return if folded?(node)
|
|
20
|
+
|
|
21
|
+
check_annotation_alignment(node)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
# @rbs node: RuboCop::AST::SendNode
|
|
27
|
+
def check_annotation_alignment(node) #: void
|
|
28
|
+
annotated = attr_arguments(node).filter_map do |arg|
|
|
29
|
+
comment = inline_type_comment(arg.location.line)
|
|
30
|
+
[arg, comment] if comment #: [RuboCop::AST::Node, Parser::Source::Comment]
|
|
31
|
+
end
|
|
32
|
+
return if annotated.size < 2
|
|
33
|
+
|
|
34
|
+
expected_col = annotation_column(node)
|
|
35
|
+
annotated.each do |arg, comment|
|
|
36
|
+
actual_col = comment.location.column
|
|
37
|
+
next if actual_col == expected_col
|
|
38
|
+
|
|
39
|
+
add_offense(comment.source_range) do |corrector|
|
|
40
|
+
correct_alignment(corrector, arg, comment, expected_col)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @rbs corrector: RuboCop::Cop::Corrector
|
|
46
|
+
# @rbs arg: RuboCop::AST::Node
|
|
47
|
+
# @rbs comment: Parser::Source::Comment
|
|
48
|
+
# @rbs expected_col: Integer
|
|
49
|
+
def correct_alignment(corrector, arg, comment, expected_col) #: void # rubocop:disable Metrics/AbcSize
|
|
50
|
+
line = arg.location.line
|
|
51
|
+
line_source = source_code_at(line)
|
|
52
|
+
source = line_source[...comment.location.column] || raise
|
|
53
|
+
content_end_col = source.rstrip.length
|
|
54
|
+
padding = [expected_col - content_end_col, 1].max || raise
|
|
55
|
+
|
|
56
|
+
line_begin = processed_source.buffer.line_range(line).begin_pos
|
|
57
|
+
replace_start = line_begin + content_end_col
|
|
58
|
+
replace_end = line_begin + comment.location.column
|
|
59
|
+
|
|
60
|
+
corrector.replace(range_between(replace_start, replace_end), " " * padding)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -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
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
|
|
@@ -34,7 +34,7 @@ module RuboCop
|
|
|
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
|
|
@@ -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
|