rubocop-sorbet 0.8.0 → 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +10 -0
- data/.github/workflows/ci.yml +3 -3
- data/.github/workflows/stale.yml +1 -1
- data/.rubocop.yml +0 -1
- data/.ruby-version +1 -0
- data/Gemfile +2 -2
- data/Gemfile.lock +42 -29
- data/config/default.yml +10 -5
- data/config/obsoletion.yml +8 -0
- data/config/rbi.yml +0 -1
- data/dev.yml +1 -1
- data/lib/rubocop/cop/sorbet/buggy_obsolete_strict_memoization.rb +2 -2
- data/lib/rubocop/cop/sorbet/callback_conditionals_binding.rb +50 -86
- data/lib/rubocop/cop/sorbet/constants_from_strings.rb +10 -13
- data/lib/rubocop/cop/sorbet/forbid_t_struct.rb +1 -1
- data/lib/rubocop/cop/sorbet/mixin/t_enum.rb +35 -0
- data/lib/rubocop/cop/sorbet/sigils/valid_sigil.rb +2 -1
- data/lib/rubocop/cop/sorbet/t_enum/forbid_comparable_t_enum.rb +44 -0
- data/lib/rubocop/cop/sorbet/t_enum/multiple_t_enum_values.rb +59 -0
- data/lib/rubocop/cop/sorbet_cops.rb +6 -3
- data/lib/rubocop/sorbet/version.rb +1 -1
- data/lib/rubocop/sorbet.rb +2 -0
- data/manual/cops.md +2 -1
- data/manual/cops_sorbet.md +54 -24
- data/rubocop-sorbet.gemspec +2 -0
- metadata +10 -5
- data/lib/rubocop/cop/sorbet/one_ancestor_per_line.rb +0 -80
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1cf8cfdff3aedba93731774d5f47fd8c4f10ef8d615ea2cb9f4cdfd2f20d1522
|
4
|
+
data.tar.gz: 69b6974d9a6de86eeed4b29dcfcae25648a3880036b49ceb57a426821da4535c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 98a957ed012dcfefb642d54ee332d1435c0931bafe6fa07e2d36d280b7a1c4391bab2f05d2b77b1f44da850196a7edb322886b0d50bbe154e0e6d9d80a9f8f62
|
7
|
+
data.tar.gz: 25634e48e2daf8db8b41211d64f49560f9ddcd7236cc181364e7ab6ecc67cbdcd5d169454825a92ffebb2e6337ed81c1847d54483f0a073447034333c2b4b150
|
data/.github/workflows/ci.yml
CHANGED
@@ -11,10 +11,10 @@ jobs:
|
|
11
11
|
strategy:
|
12
12
|
fail-fast: false
|
13
13
|
matrix:
|
14
|
-
ruby: ["
|
14
|
+
ruby: ["3.0", "3.1", "3.2", "3.3"]
|
15
15
|
name: Test Ruby ${{ matrix.ruby }}
|
16
16
|
steps:
|
17
|
-
- uses: actions/checkout@
|
17
|
+
- uses: actions/checkout@v4
|
18
18
|
- name: Set up Ruby
|
19
19
|
uses: ruby/setup-ruby@v1
|
20
20
|
with:
|
@@ -29,7 +29,7 @@ jobs:
|
|
29
29
|
fail-fast: false
|
30
30
|
name: Lint & Docs
|
31
31
|
steps:
|
32
|
-
- uses: actions/checkout@
|
32
|
+
- uses: actions/checkout@v4
|
33
33
|
- name: Set up Ruby
|
34
34
|
uses: ruby/setup-ruby@v1
|
35
35
|
with:
|
data/.github/workflows/stale.yml
CHANGED
data/.rubocop.yml
CHANGED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.2.0
|
data/Gemfile
CHANGED
@@ -5,8 +5,8 @@ source "https://rubygems.org"
|
|
5
5
|
# Specify your gem's dependencies in rubocop-sorbet.gemspec
|
6
6
|
gemspec
|
7
7
|
|
8
|
-
gem "
|
8
|
+
gem "debug"
|
9
9
|
gem "rake", ">= 12.3.3"
|
10
|
-
gem "rspec", "~> 3.
|
10
|
+
gem "rspec", "~> 3.13"
|
11
11
|
gem "rubocop-shopify", require: false
|
12
12
|
gem "yard", "~> 0.9"
|
data/Gemfile.lock
CHANGED
@@ -1,65 +1,78 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rubocop-sorbet (0.8.
|
4
|
+
rubocop-sorbet (0.8.2)
|
5
5
|
rubocop (>= 0.90.0)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
10
|
ast (2.4.2)
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
debug (1.9.2)
|
12
|
+
irb (~> 1.10)
|
13
|
+
reline (>= 0.3.8)
|
14
|
+
diff-lcs (1.5.1)
|
15
|
+
io-console (0.7.2)
|
16
|
+
irb (1.12.0)
|
17
|
+
rdoc
|
18
|
+
reline (>= 0.4.2)
|
19
|
+
json (2.7.2)
|
14
20
|
language_server-protocol (3.17.0.3)
|
15
|
-
parallel (1.
|
16
|
-
parser (3.
|
21
|
+
parallel (1.24.0)
|
22
|
+
parser (3.3.0.5)
|
17
23
|
ast (~> 2.4.1)
|
18
24
|
racc
|
19
|
-
|
25
|
+
psych (5.1.2)
|
26
|
+
stringio
|
27
|
+
racc (1.7.3)
|
20
28
|
rainbow (3.1.1)
|
21
|
-
rake (13.
|
22
|
-
|
29
|
+
rake (13.2.1)
|
30
|
+
rdoc (6.6.3.1)
|
31
|
+
psych (>= 4.0.0)
|
32
|
+
regexp_parser (2.9.0)
|
33
|
+
reline (0.5.1)
|
34
|
+
io-console (~> 0.5)
|
23
35
|
rexml (3.2.6)
|
24
|
-
rspec (3.
|
25
|
-
rspec-core (~> 3.
|
26
|
-
rspec-expectations (~> 3.
|
27
|
-
rspec-mocks (~> 3.
|
28
|
-
rspec-core (3.
|
29
|
-
rspec-support (~> 3.
|
30
|
-
rspec-expectations (3.
|
36
|
+
rspec (3.13.0)
|
37
|
+
rspec-core (~> 3.13.0)
|
38
|
+
rspec-expectations (~> 3.13.0)
|
39
|
+
rspec-mocks (~> 3.13.0)
|
40
|
+
rspec-core (3.13.0)
|
41
|
+
rspec-support (~> 3.13.0)
|
42
|
+
rspec-expectations (3.13.0)
|
31
43
|
diff-lcs (>= 1.2.0, < 2.0)
|
32
|
-
rspec-support (~> 3.
|
33
|
-
rspec-mocks (3.
|
44
|
+
rspec-support (~> 3.13.0)
|
45
|
+
rspec-mocks (3.13.0)
|
34
46
|
diff-lcs (>= 1.2.0, < 2.0)
|
35
|
-
rspec-support (~> 3.
|
36
|
-
rspec-support (3.
|
37
|
-
rubocop (1.
|
47
|
+
rspec-support (~> 3.13.0)
|
48
|
+
rspec-support (3.13.1)
|
49
|
+
rubocop (1.63.2)
|
38
50
|
json (~> 2.3)
|
39
51
|
language_server-protocol (>= 3.17.0)
|
40
52
|
parallel (~> 1.10)
|
41
|
-
parser (>= 3.
|
53
|
+
parser (>= 3.3.0.2)
|
42
54
|
rainbow (>= 2.2.2, < 4.0)
|
43
55
|
regexp_parser (>= 1.8, < 3.0)
|
44
56
|
rexml (>= 3.2.5, < 4.0)
|
45
|
-
rubocop-ast (>= 1.
|
57
|
+
rubocop-ast (>= 1.31.1, < 2.0)
|
46
58
|
ruby-progressbar (~> 1.7)
|
47
59
|
unicode-display_width (>= 2.4.0, < 3.0)
|
48
|
-
rubocop-ast (1.
|
49
|
-
parser (>= 3.
|
50
|
-
rubocop-shopify (2.
|
60
|
+
rubocop-ast (1.31.2)
|
61
|
+
parser (>= 3.3.0.4)
|
62
|
+
rubocop-shopify (2.15.1)
|
51
63
|
rubocop (~> 1.51)
|
52
64
|
ruby-progressbar (1.13.0)
|
53
|
-
|
65
|
+
stringio (3.1.0)
|
66
|
+
unicode-display_width (2.5.0)
|
54
67
|
yard (0.9.36)
|
55
68
|
|
56
69
|
PLATFORMS
|
57
70
|
ruby
|
58
71
|
|
59
72
|
DEPENDENCIES
|
60
|
-
|
73
|
+
debug
|
61
74
|
rake (>= 12.3.3)
|
62
|
-
rspec (~> 3.
|
75
|
+
rspec (~> 3.13)
|
63
76
|
rubocop-shopify
|
64
77
|
rubocop-sorbet!
|
65
78
|
yard (~> 0.9)
|
data/config/default.yml
CHANGED
@@ -200,11 +200,6 @@ Sorbet/BuggyObsoleteStrictMemoization:
|
|
200
200
|
Safe: true
|
201
201
|
SafeAutoCorrect: false
|
202
202
|
|
203
|
-
Sorbet/OneAncestorPerLine:
|
204
|
-
Description: 'Enforces one ancestor per call to requires_ancestor'
|
205
|
-
Enabled: false
|
206
|
-
VersionAdded: '0.6.0'
|
207
|
-
|
208
203
|
Sorbet/RedundantExtendTSig:
|
209
204
|
Description: >-
|
210
205
|
Forbid the usage of redundant `extend T::Sig`.
|
@@ -312,3 +307,13 @@ Sorbet/VoidCheckedTests:
|
|
312
307
|
Description: 'Forbid `.void.checked(:tests)`'
|
313
308
|
Enabled: true
|
314
309
|
VersionAdded: 0.7.7
|
310
|
+
|
311
|
+
Sorbet/MultipleTEnumValues:
|
312
|
+
Description: 'Ensures that all `T::Enum`s have multiple values.'
|
313
|
+
Enabled: true
|
314
|
+
VersionAdded: 0.8.2
|
315
|
+
|
316
|
+
Sorbet/ForbidComparableTEnum:
|
317
|
+
Description: 'Disallows including the `Comparable` module in a `T::Enum`.'
|
318
|
+
Enabled: true
|
319
|
+
VersionAdded: 0.8.2
|
data/config/rbi.yml
CHANGED
data/dev.yml
CHANGED
@@ -48,8 +48,8 @@ module RuboCop
|
|
48
48
|
|
49
49
|
include TargetSorbetVersion
|
50
50
|
|
51
|
-
MSG = "This might be a mistaken variant of the two-stage workaround that used to be needed for memoization
|
52
|
-
"`#typed: strict` files. See https://sorbet.org/docs/type-assertions#put-type-assertions-behind-memoization."
|
51
|
+
MSG = "This might be a mistaken variant of the two-stage workaround that used to be needed for memoization " \
|
52
|
+
"in `#typed: strict` files. See https://sorbet.org/docs/type-assertions#put-type-assertions-behind-memoization."
|
53
53
|
|
54
54
|
# @!method buggy_legacy_memoization_pattern?(node)
|
55
55
|
def_node_matcher :buggy_legacy_memoization_pattern?, <<~PATTERN
|
@@ -32,8 +32,13 @@ module RuboCop
|
|
32
32
|
# true
|
33
33
|
# end
|
34
34
|
# end
|
35
|
-
class CallbackConditionalsBinding < RuboCop::Cop::
|
36
|
-
|
35
|
+
class CallbackConditionalsBinding < RuboCop::Cop::Base
|
36
|
+
extend AutoCorrector
|
37
|
+
include Alignment
|
38
|
+
|
39
|
+
MSG = "Callback conditionals should be bound to the right type. Use T.bind(self, %{type})"
|
40
|
+
|
41
|
+
RESTRICT_ON_SEND = [
|
37
42
|
:validate,
|
38
43
|
:validates,
|
39
44
|
:validates_with,
|
@@ -72,97 +77,56 @@ module RuboCop
|
|
72
77
|
:append_after_action,
|
73
78
|
].freeze
|
74
79
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
end
|
86
|
-
|
87
|
-
_, _, block = conditional.child_nodes
|
88
|
-
|
89
|
-
# Find the class node and check if it includes a namespace on the
|
90
|
-
# same line e.g.: Namespace::Class, which will require the fully
|
91
|
-
# qualified name
|
92
|
-
|
93
|
-
klass = node.ancestors.find(&:class_type?)
|
94
|
-
|
95
|
-
expected_class = if klass.children.first.children.first.nil?
|
96
|
-
node.parent_module_name.split("::").last
|
97
|
-
else
|
98
|
-
klass.identifier.source
|
99
|
-
end
|
100
|
-
|
101
|
-
do_end_lambda = conditional.source.include?("do") && conditional.source.include?("end")
|
80
|
+
# @!method argumentless_unbound_callable_callback_conditional?(node)
|
81
|
+
def_node_matcher :argumentless_unbound_callable_callback_conditional?, <<~PATTERN
|
82
|
+
(pair (sym {:if :unless}) # callback conditional
|
83
|
+
$(block
|
84
|
+
(send nil? {:lambda :proc}) # callable
|
85
|
+
(args) # argumentless
|
86
|
+
!`(send(const {cbase nil?} :T) :bind self $_ ) # unbound
|
87
|
+
)
|
88
|
+
)
|
89
|
+
PATTERN
|
102
90
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
91
|
+
def on_send(node)
|
92
|
+
type = immediately_enclosing_module_name(node)
|
93
|
+
return unless type
|
94
|
+
|
95
|
+
node.arguments.each do |arg|
|
96
|
+
next unless arg.hash_type? # Skip non-keyword arguments
|
97
|
+
|
98
|
+
arg.each_child_node do |pair_node|
|
99
|
+
argumentless_unbound_callable_callback_conditional?(pair_node) do |block|
|
100
|
+
add_offense(pair_node, message: format(MSG, type: type)) do |corrector|
|
101
|
+
block_opening_indentation = block.source_range.source_line[/\A */]
|
102
|
+
block_body_indentation = block_opening_indentation + SPACE * configured_indentation_width
|
103
|
+
|
104
|
+
if block.single_line? # then convert to multi-line block first
|
105
|
+
# 1. Replace whitespace (if any) between the opening delimiter and the block body,
|
106
|
+
# with newline and the correct indentation for the block body.
|
107
|
+
preceeding_whitespace_range = block.loc.begin.end.join(block.body.source_range.begin)
|
108
|
+
corrector.replace(preceeding_whitespace_range, "\n#{block_body_indentation}")
|
109
|
+
|
110
|
+
# 2. Replace whitespace (if any) between the block body and the closing delimiter,
|
111
|
+
# with newline and the same indentation as the block opening.
|
112
|
+
trailing_whitespace_range = block.body.source_range.end.join(block.loc.end.begin)
|
113
|
+
corrector.replace(trailing_whitespace_range, "\n#{block_opening_indentation}")
|
114
|
+
end
|
115
|
+
|
116
|
+
# Prepend the binding to the block body
|
117
|
+
corrector.insert_before(block.body, "T.bind(self, #{type})\n#{block_body_indentation}")
|
118
|
+
end
|
108
119
|
end
|
109
|
-
|
110
|
-
# Remove the last space and `}` and re-add it with a line break
|
111
|
-
# and the correct indentation
|
112
|
-
base_indentation = " " * node.loc.column
|
113
|
-
chars_to_remove = /\s}/.match?(conditional.source) ? 2 : 1
|
114
|
-
corrector.remove_trailing(conditional, chars_to_remove)
|
115
|
-
corrector.insert_after(block, "\n#{base_indentation}}")
|
116
120
|
end
|
117
|
-
|
118
|
-
# Add the T.bind
|
119
|
-
indentation = " " * (node.loc.column + 2)
|
120
|
-
line_start = do_end_lambda ? "" : "\n#{indentation}"
|
121
|
-
bind = "#{line_start}T.bind(self, #{expected_class})\n#{indentation}"
|
122
|
-
|
123
|
-
corrector.insert_before(block, bind)
|
124
121
|
end
|
125
122
|
end
|
126
123
|
|
127
|
-
|
128
|
-
return unless CALLBACKS.include?(node.method_name)
|
129
|
-
|
130
|
-
options = node.each_child_node.find(&:hash_type?)
|
131
|
-
return if options.nil?
|
132
|
-
|
133
|
-
conditional = nil
|
134
|
-
options.each_pair do |keyword, block|
|
135
|
-
next unless keyword.sym_type?
|
136
|
-
|
137
|
-
if keyword.value == :if || keyword.value == :unless
|
138
|
-
conditional = block
|
139
|
-
break
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
return if conditional.nil? || conditional.array_type? || conditional.child_nodes.empty?
|
144
|
-
|
145
|
-
return unless conditional.arguments.empty?
|
124
|
+
private
|
146
125
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
expected_class = if klass&.children&.first&.children&.first.nil?
|
153
|
-
node.parent_module_name&.split("::")&.last
|
154
|
-
else
|
155
|
-
klass.identifier.source
|
156
|
-
end
|
157
|
-
|
158
|
-
return if expected_class.nil?
|
159
|
-
|
160
|
-
unless block.source.include?("T.bind(self")
|
161
|
-
add_offense(
|
162
|
-
node,
|
163
|
-
message: "Callback conditionals should be bound to the right type. Use T.bind(self, #{expected_class})",
|
164
|
-
)
|
165
|
-
end
|
126
|
+
# Find the immediately enclosing class or module name.
|
127
|
+
# Returns `nil`` if the immediate parent (skipping begin if present) is not a class or module.
|
128
|
+
def immediately_enclosing_module_name(node)
|
129
|
+
(node.parent&.begin_type? ? node.parent.parent : node.parent)&.defined_module_name
|
166
130
|
end
|
167
131
|
end
|
168
132
|
end
|
@@ -33,21 +33,18 @@ module RuboCop
|
|
33
33
|
#
|
34
34
|
# # good
|
35
35
|
# { "User" => User }.fetch(class_name)
|
36
|
-
class ConstantsFromStrings < ::RuboCop::Cop::
|
37
|
-
|
38
|
-
|
39
|
-
(send _ {:constantize :constants :const_get} ...)
|
40
|
-
PATTERN
|
36
|
+
class ConstantsFromStrings < ::RuboCop::Cop::Base
|
37
|
+
MSG = "Don't use `%<method_name>s`, it makes the code harder to understand, less editor-friendly, " \
|
38
|
+
"and impossible to analyze. Replace `%<method_name>s` with a case/when or a hash."
|
41
39
|
|
42
|
-
|
43
|
-
|
40
|
+
RESTRICT_ON_SEND = [
|
41
|
+
:constantize,
|
42
|
+
:constants,
|
43
|
+
:const_get,
|
44
|
+
].freeze
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
location: :selector,
|
48
|
-
message: "Don't use `#{node.method_name}`, it makes the code harder to understand, less editor-friendly, " \
|
49
|
-
"and impossible to analyze. Replace `#{node.method_name}` with a case/when or a hash.",
|
50
|
-
)
|
46
|
+
def on_send(node)
|
47
|
+
add_offense(node.selector, message: format(MSG, method_name: node.method_name))
|
51
48
|
end
|
52
49
|
end
|
53
50
|
end
|
@@ -79,7 +79,7 @@ module RuboCop
|
|
79
79
|
return unless t_struct_prop?(node)
|
80
80
|
|
81
81
|
kind = node.method?(:const) ? :attr_reader : :attr_accessor
|
82
|
-
name = node.
|
82
|
+
name = node.first_argument.source.delete_prefix(":")
|
83
83
|
type = node.arguments[1].source
|
84
84
|
default = nil
|
85
85
|
factory = nil
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Sorbet
|
6
|
+
# Mixing for writing cops that deal with `T::Enum`s
|
7
|
+
module TEnum
|
8
|
+
extend RuboCop::NodePattern::Macros
|
9
|
+
def initialize(*)
|
10
|
+
@scopes = []
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
# @!method t_enum?(node)
|
15
|
+
def_node_matcher :t_enum?, <<~PATTERN
|
16
|
+
(class (const...) (const (const nil? :T) :Enum) ...)
|
17
|
+
PATTERN
|
18
|
+
|
19
|
+
def on_class(node)
|
20
|
+
@scopes.push(node)
|
21
|
+
end
|
22
|
+
|
23
|
+
def after_class(node)
|
24
|
+
@scopes.pop
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def in_t_enum_class?
|
30
|
+
t_enum?(@scopes&.last)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -29,7 +29,8 @@ module RuboCop
|
|
29
29
|
strictness = extract_strictness(sigil)
|
30
30
|
return unless check_strictness_not_empty(sigil, strictness)
|
31
31
|
return unless check_strictness_valid(sigil, strictness)
|
32
|
-
|
32
|
+
|
33
|
+
nil unless check_strictness_level(sigil, strictness)
|
33
34
|
end
|
34
35
|
|
35
36
|
def autocorrect(_node)
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Sorbet
|
6
|
+
# Disallow including the `Comparable` module in `T::Enum`.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# # bad
|
11
|
+
# class Priority < T::Enum
|
12
|
+
# include Comparable
|
13
|
+
#
|
14
|
+
# enums do
|
15
|
+
# High = new(3)
|
16
|
+
# Medium = new(2)
|
17
|
+
# Low = new(1)
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# def <=>(other)
|
21
|
+
# serialize <=> other.serialize
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
class ForbidComparableTEnum < Base
|
25
|
+
include TEnum
|
26
|
+
|
27
|
+
MSG = "Do not use `T::Enum` as a comparable object because of significant performance overhead."
|
28
|
+
|
29
|
+
RESTRICT_ON_SEND = [:include, :prepend].freeze
|
30
|
+
|
31
|
+
# @!method mix_in_comparable?(node)
|
32
|
+
def_node_matcher :mix_in_comparable?, <<~PATTERN
|
33
|
+
(send nil? {:include | :prepend} (const nil? :Comparable))
|
34
|
+
PATTERN
|
35
|
+
|
36
|
+
def on_send(node)
|
37
|
+
return unless in_t_enum_class? && mix_in_comparable?(node)
|
38
|
+
|
39
|
+
add_offense(node)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Sorbet
|
6
|
+
# Disallow creating a `T::Enum` with less than two values.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# # bad
|
11
|
+
# class ErrorMessages < T::Enum
|
12
|
+
# enums do
|
13
|
+
# ServerError = new("There was a server error.")
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# class ErrorMessages < T::Enum
|
19
|
+
# enums do
|
20
|
+
# ServerError = new("There was a server error.")
|
21
|
+
# NotFound = new("The resource was not found.")
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
class MultipleTEnumValues < Base
|
25
|
+
include TEnum
|
26
|
+
|
27
|
+
MSG = "`T::Enum` should have at least two values."
|
28
|
+
|
29
|
+
# @!method enums_block?(node)
|
30
|
+
def_node_matcher :enums_block?, <<~PATTERN
|
31
|
+
(block (send nil? :enums) ...)
|
32
|
+
PATTERN
|
33
|
+
|
34
|
+
def on_class(node)
|
35
|
+
super
|
36
|
+
|
37
|
+
add_offense(node) if t_enum?(node) && node.body.nil?
|
38
|
+
end
|
39
|
+
|
40
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
41
|
+
return unless in_t_enum_class?
|
42
|
+
return unless enums_block?(node)
|
43
|
+
|
44
|
+
scope = @scopes.last
|
45
|
+
|
46
|
+
if node.body.nil?
|
47
|
+
add_offense(scope)
|
48
|
+
return
|
49
|
+
end
|
50
|
+
|
51
|
+
begin_node = node.children.find(&:begin_type?)
|
52
|
+
|
53
|
+
num_casgn_nodes = (begin_node || node).children.count(&:casgn_type?)
|
54
|
+
add_offense(scope) if num_casgn_nodes < 2
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -1,7 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "sorbet/mixin/target_sorbet_version
|
4
|
-
require_relative "sorbet/mixin/
|
3
|
+
require_relative "sorbet/mixin/target_sorbet_version"
|
4
|
+
require_relative "sorbet/mixin/t_enum"
|
5
|
+
require_relative "sorbet/mixin/signature_help"
|
5
6
|
|
6
7
|
require_relative "sorbet/binding_constant_without_type_alias"
|
7
8
|
require_relative "sorbet/constants_from_strings"
|
@@ -10,7 +11,6 @@ require_relative "sorbet/forbid_include_const_literal"
|
|
10
11
|
require_relative "sorbet/forbid_type_aliased_shapes"
|
11
12
|
require_relative "sorbet/forbid_untyped_struct_props"
|
12
13
|
require_relative "sorbet/implicit_conversion_method"
|
13
|
-
require_relative "sorbet/one_ancestor_per_line"
|
14
14
|
require_relative "sorbet/callback_conditionals_binding"
|
15
15
|
require_relative "sorbet/forbid_t_struct"
|
16
16
|
require_relative "sorbet/forbid_t_unsafe"
|
@@ -42,4 +42,7 @@ require_relative "sorbet/sigils/strong_sigil"
|
|
42
42
|
require_relative "sorbet/sigils/enforce_sigil_order"
|
43
43
|
require_relative "sorbet/sigils/enforce_single_sigil"
|
44
44
|
|
45
|
+
require_relative "sorbet/t_enum/forbid_comparable_t_enum"
|
46
|
+
require_relative "sorbet/t_enum/multiple_t_enum_values"
|
47
|
+
|
45
48
|
require_relative "sorbet/mutable_constant_sorbet_aware_behaviour"
|
data/lib/rubocop/sorbet.rb
CHANGED
data/manual/cops.md
CHANGED
@@ -16,6 +16,7 @@ In the following section you find all available cops:
|
|
16
16
|
* [Sorbet/EnforceSignatures](cops_sorbet.md#sorbetenforcesignatures)
|
17
17
|
* [Sorbet/EnforceSingleSigil](cops_sorbet.md#sorbetenforcesinglesigil)
|
18
18
|
* [Sorbet/FalseSigil](cops_sorbet.md#sorbetfalsesigil)
|
19
|
+
* [Sorbet/ForbidComparableTEnum](cops_sorbet.md#sorbetforbidcomparabletenum)
|
19
20
|
* [Sorbet/ForbidExtendTSigHelpersInShims](cops_sorbet.md#sorbetforbidextendtsighelpersinshims)
|
20
21
|
* [Sorbet/ForbidIncludeConstLiteral](cops_sorbet.md#sorbetforbidincludeconstliteral)
|
21
22
|
* [Sorbet/ForbidRBIOutsideOfAllowedPaths](cops_sorbet.md#sorbetforbidrbioutsideofallowedpaths)
|
@@ -29,8 +30,8 @@ In the following section you find all available cops:
|
|
29
30
|
* [Sorbet/IgnoreSigil](cops_sorbet.md#sorbetignoresigil)
|
30
31
|
* [Sorbet/ImplicitConversionMethod](cops_sorbet.md#sorbetimplicitconversionmethod)
|
31
32
|
* [Sorbet/KeywordArgumentOrdering](cops_sorbet.md#sorbetkeywordargumentordering)
|
33
|
+
* [Sorbet/MultipleTEnumValues](cops_sorbet.md#sorbetmultipletenumvalues)
|
32
34
|
* [Sorbet/ObsoleteStrictMemoization](cops_sorbet.md#sorbetobsoletestrictmemoization)
|
33
|
-
* [Sorbet/OneAncestorPerLine](cops_sorbet.md#sorbetoneancestorperline)
|
34
35
|
* [Sorbet/RedundantExtendTSig](cops_sorbet.md#sorbetredundantextendtsig)
|
35
36
|
* [Sorbet/SignatureBuildOrder](cops_sorbet.md#sorbetsignaturebuildorder)
|
36
37
|
* [Sorbet/SingleLineRbiClassModuleDefinitions](cops_sorbet.md#sorbetsinglelinerbiclassmoduledefinitions)
|
data/manual/cops_sorbet.md
CHANGED
@@ -295,6 +295,33 @@ SuggestedStrictness | `false` | String
|
|
295
295
|
Include | `**/*.{rb,rbi,rake,ru}` | Array
|
296
296
|
Exclude | `bin/**/*`, `db/**/*.rb`, `script/**/*` | Array
|
297
297
|
|
298
|
+
## Sorbet/ForbidComparableTEnum
|
299
|
+
|
300
|
+
Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
|
301
|
+
--- | --- | --- | --- | ---
|
302
|
+
Enabled | Yes | No | 0.8.2 | -
|
303
|
+
|
304
|
+
Disallow including the `Comparable` module in `T::Enum`.
|
305
|
+
|
306
|
+
### Examples
|
307
|
+
|
308
|
+
```ruby
|
309
|
+
# bad
|
310
|
+
class Priority < T::Enum
|
311
|
+
include Comparable
|
312
|
+
|
313
|
+
enums do
|
314
|
+
High = new(3)
|
315
|
+
Medium = new(2)
|
316
|
+
Low = new(1)
|
317
|
+
end
|
318
|
+
|
319
|
+
def <=>(other)
|
320
|
+
serialize <=> other.serialize
|
321
|
+
end
|
322
|
+
end
|
323
|
+
```
|
324
|
+
|
298
325
|
## Sorbet/ForbidExtendTSigHelpersInShims
|
299
326
|
|
300
327
|
Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
|
@@ -639,6 +666,33 @@ sig { params(b: String, a: Integer).void }
|
|
639
666
|
def foo(b:, a: 1); end
|
640
667
|
```
|
641
668
|
|
669
|
+
## Sorbet/MultipleTEnumValues
|
670
|
+
|
671
|
+
Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
|
672
|
+
--- | --- | --- | --- | ---
|
673
|
+
Enabled | Yes | No | 0.8.2 | -
|
674
|
+
|
675
|
+
Disallow creating a `T::Enum` with less than two values.
|
676
|
+
|
677
|
+
### Examples
|
678
|
+
|
679
|
+
```ruby
|
680
|
+
# bad
|
681
|
+
class ErrorMessages < T::Enum
|
682
|
+
enums do
|
683
|
+
ServerError = new("There was a server error.")
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
# good
|
688
|
+
class ErrorMessages < T::Enum
|
689
|
+
enums do
|
690
|
+
ServerError = new("There was a server error.")
|
691
|
+
NotFound = new("The resource was not found.")
|
692
|
+
end
|
693
|
+
end
|
694
|
+
```
|
695
|
+
|
642
696
|
## Sorbet/ObsoleteStrictMemoization
|
643
697
|
|
644
698
|
Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
|
@@ -676,30 +730,6 @@ def foo
|
|
676
730
|
end
|
677
731
|
```
|
678
732
|
|
679
|
-
## Sorbet/OneAncestorPerLine
|
680
|
-
|
681
|
-
Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
|
682
|
-
--- | --- | --- | --- | ---
|
683
|
-
Disabled | Yes | Yes | 0.6.0 | -
|
684
|
-
|
685
|
-
Ensures one ancestor per requires_ancestor line
|
686
|
-
rather than chaining them as a comma-separated list.
|
687
|
-
|
688
|
-
### Examples
|
689
|
-
|
690
|
-
```ruby
|
691
|
-
# bad
|
692
|
-
module SomeModule
|
693
|
-
requires_ancestor Kernel, Minitest::Assertions
|
694
|
-
end
|
695
|
-
|
696
|
-
# good
|
697
|
-
module SomeModule
|
698
|
-
requires_ancestor Kernel
|
699
|
-
requires_ancestor Minitest::Assertions
|
700
|
-
end
|
701
|
-
```
|
702
|
-
|
703
733
|
## Sorbet/RedundantExtendTSig
|
704
734
|
|
705
735
|
Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
|
data/rubocop-sorbet.gemspec
CHANGED
@@ -12,6 +12,8 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.homepage = "https://github.com/shopify/rubocop-sorbet"
|
13
13
|
spec.license = "MIT"
|
14
14
|
|
15
|
+
spec.required_ruby_version = ">= 3.0"
|
16
|
+
|
15
17
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
16
18
|
spec.metadata["homepage_uri"] = spec.homepage
|
17
19
|
spec.metadata["source_code_uri"] = "https://github.com/shopify/rubocop-sorbet"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubocop-sorbet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ufuk Kayserilioglu
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: exe
|
13
13
|
cert_chain: []
|
14
|
-
date: 2024-
|
14
|
+
date: 2024-04-23 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: rubocop
|
@@ -35,6 +35,7 @@ extensions: []
|
|
35
35
|
extra_rdoc_files: []
|
36
36
|
files:
|
37
37
|
- ".github/CODEOWNERS"
|
38
|
+
- ".github/dependabot.yml"
|
38
39
|
- ".github/release.yml"
|
39
40
|
- ".github/workflows/ci.yml"
|
40
41
|
- ".github/workflows/cla.yml"
|
@@ -42,6 +43,7 @@ files:
|
|
42
43
|
- ".gitignore"
|
43
44
|
- ".rspec"
|
44
45
|
- ".rubocop.yml"
|
46
|
+
- ".ruby-version"
|
45
47
|
- ".travis.yml"
|
46
48
|
- ".yardopts"
|
47
49
|
- CODE_OF_CONDUCT.md
|
@@ -55,6 +57,7 @@ files:
|
|
55
57
|
- bin/rubocop
|
56
58
|
- bin/setup
|
57
59
|
- config/default.yml
|
60
|
+
- config/obsoletion.yml
|
58
61
|
- config/rbi.yml
|
59
62
|
- dev.yml
|
60
63
|
- lib/rubocop-sorbet.rb
|
@@ -71,10 +74,10 @@ files:
|
|
71
74
|
- lib/rubocop/cop/sorbet/forbid_untyped_struct_props.rb
|
72
75
|
- lib/rubocop/cop/sorbet/implicit_conversion_method.rb
|
73
76
|
- lib/rubocop/cop/sorbet/mixin/signature_help.rb
|
77
|
+
- lib/rubocop/cop/sorbet/mixin/t_enum.rb
|
74
78
|
- lib/rubocop/cop/sorbet/mixin/target_sorbet_version.rb
|
75
79
|
- lib/rubocop/cop/sorbet/mutable_constant_sorbet_aware_behaviour.rb
|
76
80
|
- lib/rubocop/cop/sorbet/obsolete_strict_memoization.rb
|
77
|
-
- lib/rubocop/cop/sorbet/one_ancestor_per_line.rb
|
78
81
|
- lib/rubocop/cop/sorbet/rbi/forbid_extend_t_sig_helpers_in_shims.rb
|
79
82
|
- lib/rubocop/cop/sorbet/rbi/forbid_rbi_outside_of_allowed_paths.rb
|
80
83
|
- lib/rubocop/cop/sorbet/rbi/single_line_rbi_class_module_definitions.rb
|
@@ -95,6 +98,8 @@ files:
|
|
95
98
|
- lib/rubocop/cop/sorbet/signatures/keyword_argument_ordering.rb
|
96
99
|
- lib/rubocop/cop/sorbet/signatures/signature_build_order.rb
|
97
100
|
- lib/rubocop/cop/sorbet/signatures/void_checked_tests.rb
|
101
|
+
- lib/rubocop/cop/sorbet/t_enum/forbid_comparable_t_enum.rb
|
102
|
+
- lib/rubocop/cop/sorbet/t_enum/multiple_t_enum_values.rb
|
98
103
|
- lib/rubocop/cop/sorbet/type_alias_name.rb
|
99
104
|
- lib/rubocop/cop/sorbet_cops.rb
|
100
105
|
- lib/rubocop/sorbet.rb
|
@@ -121,14 +126,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
121
126
|
requirements:
|
122
127
|
- - ">="
|
123
128
|
- !ruby/object:Gem::Version
|
124
|
-
version: '0'
|
129
|
+
version: '3.0'
|
125
130
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
131
|
requirements:
|
127
132
|
- - ">="
|
128
133
|
- !ruby/object:Gem::Version
|
129
134
|
version: '0'
|
130
135
|
requirements: []
|
131
|
-
rubygems_version: 3.5.
|
136
|
+
rubygems_version: 3.5.9
|
132
137
|
signing_key:
|
133
138
|
specification_version: 4
|
134
139
|
summary: Automatic Sorbet code style checking tool.
|
@@ -1,80 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require "rubocop"
|
5
|
-
|
6
|
-
module RuboCop
|
7
|
-
module Cop
|
8
|
-
module Sorbet
|
9
|
-
# Ensures one ancestor per requires_ancestor line
|
10
|
-
# rather than chaining them as a comma-separated list.
|
11
|
-
#
|
12
|
-
# @example
|
13
|
-
#
|
14
|
-
# # bad
|
15
|
-
# module SomeModule
|
16
|
-
# requires_ancestor Kernel, Minitest::Assertions
|
17
|
-
# end
|
18
|
-
#
|
19
|
-
# # good
|
20
|
-
# module SomeModule
|
21
|
-
# requires_ancestor Kernel
|
22
|
-
# requires_ancestor Minitest::Assertions
|
23
|
-
# end
|
24
|
-
class OneAncestorPerLine < RuboCop::Cop::Cop # rubocop:todo InternalAffairs/InheritDeprecatedCopClass
|
25
|
-
MSG = "Cannot require more than one ancestor per line"
|
26
|
-
|
27
|
-
# @!method requires_ancestors(node)
|
28
|
-
def_node_search :requires_ancestors, <<~PATTERN
|
29
|
-
(send nil? :requires_ancestor ...)
|
30
|
-
PATTERN
|
31
|
-
|
32
|
-
# @!method more_than_one_ancestor(node)
|
33
|
-
def_node_matcher :more_than_one_ancestor, <<~PATTERN
|
34
|
-
(send nil? :requires_ancestor const const+)
|
35
|
-
PATTERN
|
36
|
-
|
37
|
-
# @!method abstract?(node)
|
38
|
-
def_node_search :abstract?, <<~PATTERN
|
39
|
-
(send nil? :abstract!)
|
40
|
-
PATTERN
|
41
|
-
|
42
|
-
def on_module(node)
|
43
|
-
return unless node.body
|
44
|
-
return unless requires_ancestors(node)
|
45
|
-
|
46
|
-
process_node(node)
|
47
|
-
end
|
48
|
-
|
49
|
-
def on_class(node)
|
50
|
-
return unless abstract?(node)
|
51
|
-
return unless requires_ancestors(node)
|
52
|
-
|
53
|
-
process_node(node)
|
54
|
-
end
|
55
|
-
|
56
|
-
def autocorrect(node)
|
57
|
-
->(corrector) do
|
58
|
-
ra_call = node.parent
|
59
|
-
split_ra_calls = ra_call.source.gsub(/,\s+/, new_ra_line(ra_call.loc.column))
|
60
|
-
corrector.replace(ra_call, split_ra_calls)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
private
|
65
|
-
|
66
|
-
def process_node(node)
|
67
|
-
requires_ancestors(node).each do |ra|
|
68
|
-
add_offense(ra.child_nodes[1]) if more_than_one_ancestor(ra)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def new_ra_line(indent_count)
|
73
|
-
indents = " " * indent_count
|
74
|
-
indented_ra_call = "#{indents}requires_ancestor "
|
75
|
-
"\n#{indented_ra_call}"
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|