rubocop-sorbet 0.7.3 → 0.7.4
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/Gemfile.lock +1 -1
- data/config/default.yml +24 -0
- data/lib/rubocop/cop/sorbet/buggy_obsolete_strict_memoization.rb +83 -0
- data/lib/rubocop/cop/sorbet/forbid_t_struct.rb +223 -0
- data/lib/rubocop/cop/sorbet/mixin/target_sorbet_version.rb +7 -3
- data/lib/rubocop/cop/sorbet/obsolete_strict_memoization.rb +1 -1
- data/lib/rubocop/cop/sorbet/redundant_extend_t_sig.rb +2 -1
- data/lib/rubocop/cop/sorbet_cops.rb +2 -0
- data/lib/rubocop/sorbet/version.rb +1 -1
- data/manual/cops.md +2 -0
- data/manual/cops_sorbet.md +77 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d771dbc78b04c867bb517a024dacaf6d1c280539ebbd6950374a19fe9d89e439
|
4
|
+
data.tar.gz: 5310766358cdbfec15d154733f32769ab7320cf987798710a2c0f1dcd2fddceb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2b0a1b42b4a1a368565d86ef69a335bc5345a562a892dcc930bd4786e1049c5d4cc018a762df22ffac6189aa68628d3c5f74cee147232f6eb9de62d21350d19b
|
7
|
+
data.tar.gz: 21d0d71d76975bb9204622b40a026fdc3c5c62105506ddfa293df0cf117e52d05e541a430290d045792208019a60ee824c9e8535d36a5d8f70f455abdbc42f89
|
data/Gemfile.lock
CHANGED
data/config/default.yml
CHANGED
@@ -97,6 +97,13 @@ Sorbet/ForbidSuperclassConstLiteral:
|
|
97
97
|
Exclude:
|
98
98
|
- db/migrate/*.rb
|
99
99
|
|
100
|
+
Sorbet/ForbidTStruct:
|
101
|
+
Description: 'Forbid usage of T::Struct.'
|
102
|
+
Enabled: false
|
103
|
+
VersionAdded: <<next>>
|
104
|
+
VersionChanged: <<next>>
|
105
|
+
Safe: false
|
106
|
+
|
100
107
|
Sorbet/ForbidTUnsafe:
|
101
108
|
Description: 'Forbid usage of T.unsafe.'
|
102
109
|
Enabled: false
|
@@ -171,6 +178,23 @@ Sorbet/ObsoleteStrictMemoization:
|
|
171
178
|
Safe: true
|
172
179
|
SafeAutoCorrect: true
|
173
180
|
|
181
|
+
Sorbet/BuggyObsoleteStrictMemoization:
|
182
|
+
Description: >-
|
183
|
+
Checks for the a mistaken variant of the "obsolete memoization pattern" that used to be required
|
184
|
+
for older Sorbet versions in `#typed: strict` files. The mistaken variant would overwrite the ivar with `nil`
|
185
|
+
on every call, causing the memoized value to be discarded and recomputed on every call.
|
186
|
+
|
187
|
+
This cop will correct it to read from the ivar instead of `nil`, which will memoize it correctly.
|
188
|
+
|
189
|
+
The result of this correction will be the "obsolete memoization pattern", which can further be corrected by
|
190
|
+
the `Sorbet/ObsoleteStrictMemoization` cop.
|
191
|
+
|
192
|
+
See `Sorbet/ObsoleteStrictMemoization` for more details.
|
193
|
+
Enabled: true
|
194
|
+
VersionAdded: '0.7.3'
|
195
|
+
Safe: true
|
196
|
+
SafeAutoCorrect: false
|
197
|
+
|
174
198
|
Sorbet/OneAncestorPerLine:
|
175
199
|
Description: 'Enforces one ancestor per call to requires_ancestor'
|
176
200
|
Enabled: false
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rubocop"
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Cop
|
7
|
+
module Sorbet
|
8
|
+
# Checks for the a mistaken variant of the "obsolete memoization pattern" that used to be required
|
9
|
+
# for older Sorbet versions in `#typed: strict` files. The mistaken variant would overwrite the ivar with `nil`
|
10
|
+
# on every call, causing the memoized value to be discarded and recomputed on every call.
|
11
|
+
#
|
12
|
+
# This cop will correct it to read from the ivar instead of `nil`, which will memoize it correctly.
|
13
|
+
#
|
14
|
+
# The result of this correction will be the "obsolete memoization pattern", which can further be corrected by
|
15
|
+
# the `Sorbet/ObsoleteStrictMemoization` cop.
|
16
|
+
#
|
17
|
+
# See `Sorbet/ObsoleteStrictMemoization` for more details.
|
18
|
+
#
|
19
|
+
# @safety
|
20
|
+
# If the computation being memoized had side effects, calling it only once (instead of once on every call
|
21
|
+
# to the affected method) can be observed, and might be a breaking change.
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# # bad
|
25
|
+
# sig { returns(Foo) }
|
26
|
+
# def foo
|
27
|
+
# # This `nil` is likely a mistake, causing the memoized value to be discarded and recomputed on every call.
|
28
|
+
# @foo = T.let(nil, T.nilable(Foo))
|
29
|
+
# @foo ||= some_computation
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# # good
|
33
|
+
# sig { returns(Foo) }
|
34
|
+
# def foo
|
35
|
+
# # This will now memoize the value as was likely intended, so `some_computation` is only ever called once.
|
36
|
+
# # ⚠️If `some_computation` has side effects, this might be a breaking change!
|
37
|
+
# @foo = T.let(@foo, T.nilable(Foo))
|
38
|
+
# @foo ||= some_computation
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# @see Sorbet/ObsoleteStrictMemoization
|
42
|
+
class BuggyObsoleteStrictMemoization < RuboCop::Cop::Base
|
43
|
+
include RuboCop::Cop::MatchRange
|
44
|
+
include RuboCop::Cop::Alignment
|
45
|
+
include RuboCop::Cop::LineLengthHelp
|
46
|
+
include RuboCop::Cop::RangeHelp
|
47
|
+
extend AutoCorrector
|
48
|
+
|
49
|
+
include TargetSorbetVersion
|
50
|
+
|
51
|
+
MSG = "This might be a mistaken variant of the two-stage workaround that used to be needed for memoization in "\
|
52
|
+
"`#typed: strict` files. See https://sorbet.org/docs/type-assertions#put-type-assertions-behind-memoization."
|
53
|
+
|
54
|
+
# @!method buggy_legacy_memoization_pattern?(node)
|
55
|
+
def_node_matcher :buggy_legacy_memoization_pattern?, <<~PATTERN
|
56
|
+
(begin
|
57
|
+
... # Ignore any other lines that come first.
|
58
|
+
(ivasgn $_ivar # First line: @_ivar = ...
|
59
|
+
(send # T.let(_ivar, T.nilable(_ivar_type))
|
60
|
+
(const {nil? cbase} :T) :let
|
61
|
+
$nil
|
62
|
+
(send (const {nil? cbase} :T) :nilable _ivar_type))) # T.nilable(_ivar_type)
|
63
|
+
(or-asgn (ivasgn _ivar) _initialization_expr)) # Second line: @_ivar ||= _initialization_expr
|
64
|
+
PATTERN
|
65
|
+
|
66
|
+
def on_begin(node)
|
67
|
+
buggy_legacy_memoization_pattern?(node) do |ivar, nil_node|
|
68
|
+
add_offense(nil_node) do |corrector|
|
69
|
+
corrector.replace(
|
70
|
+
range_between(nil_node.source_range.begin_pos, nil_node.source_range.end_pos),
|
71
|
+
ivar,
|
72
|
+
)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def relevant_file?(file)
|
78
|
+
super && sorbet_enabled?
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rubocop"
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Cop
|
7
|
+
module Sorbet
|
8
|
+
# Disallow using `T::Struct` and `T::Props`.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
#
|
12
|
+
# # bad
|
13
|
+
# class MyStruct < T::Struct
|
14
|
+
# const :foo, String
|
15
|
+
# prop :bar, Integer, default: 0
|
16
|
+
#
|
17
|
+
# def some_method; end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# class MyStruct
|
22
|
+
# extend T::Sig
|
23
|
+
#
|
24
|
+
# sig { returns(String) }
|
25
|
+
# attr_reader :foo
|
26
|
+
#
|
27
|
+
# sig { returns(Integer) }
|
28
|
+
# attr_accessor :bar
|
29
|
+
#
|
30
|
+
# sig { params(foo: String, bar: Integer) }
|
31
|
+
# def initialize(foo:, bar: 0)
|
32
|
+
# @foo = foo
|
33
|
+
# @bar = bar
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# def some_method; end
|
37
|
+
# end
|
38
|
+
class ForbidTStruct < RuboCop::Cop::Base
|
39
|
+
include Alignment
|
40
|
+
include RangeHelp
|
41
|
+
include CommentsHelp
|
42
|
+
extend AutoCorrector
|
43
|
+
|
44
|
+
RESTRICT_ON_SEND = [:include, :prepend, :extend].freeze
|
45
|
+
|
46
|
+
MSG_STRUCT = "Using `T::Struct` or its variants is deprecated."
|
47
|
+
MSG_PROPS = "Using `T::Props` or its variants is deprecated."
|
48
|
+
|
49
|
+
# This class walks down the class body of a T::Struct and collects all the properties that will need to be
|
50
|
+
# translated into `attr_reader` and `attr_accessor` methods.
|
51
|
+
class TStructWalker
|
52
|
+
include AST::Traversal
|
53
|
+
extend AST::NodePattern::Macros
|
54
|
+
|
55
|
+
attr_reader :props, :has_extend_t_sig
|
56
|
+
|
57
|
+
def initialize
|
58
|
+
@props = []
|
59
|
+
@has_extend_t_sig = false
|
60
|
+
end
|
61
|
+
|
62
|
+
# @!method extend_t_sig?(node)
|
63
|
+
def_node_matcher :extend_t_sig?, <<~PATTERN
|
64
|
+
(send _ :extend (const (const {nil? | cbase} :T) :Sig))
|
65
|
+
PATTERN
|
66
|
+
|
67
|
+
# @!method t_struct_prop?(node)
|
68
|
+
def_node_matcher(:t_struct_prop?, <<~PATTERN)
|
69
|
+
(send nil? {:const :prop} ...)
|
70
|
+
PATTERN
|
71
|
+
|
72
|
+
def on_send(node)
|
73
|
+
if extend_t_sig?(node)
|
74
|
+
# So we know we won't need to generate again a `extend T::Sig` line in the new class body
|
75
|
+
@has_extend_t_sig = true
|
76
|
+
return
|
77
|
+
end
|
78
|
+
|
79
|
+
return unless t_struct_prop?(node)
|
80
|
+
|
81
|
+
kind = node.method?(:const) ? :attr_reader : :attr_accessor
|
82
|
+
name = node.arguments[0].source.delete_prefix(":")
|
83
|
+
type = node.arguments[1].source
|
84
|
+
default = nil
|
85
|
+
factory = nil
|
86
|
+
|
87
|
+
node.arguments[2..-1].each do |arg|
|
88
|
+
next unless arg.hash_type?
|
89
|
+
|
90
|
+
arg.each_pair do |key, value|
|
91
|
+
case key.source
|
92
|
+
when "default"
|
93
|
+
default = value.source
|
94
|
+
when "factory"
|
95
|
+
factory = value.source
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
@props << Property.new(node, kind, name, type, default: default, factory: factory)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class Property
|
105
|
+
attr_reader :node, :kind, :name, :type, :default, :factory
|
106
|
+
|
107
|
+
def initialize(node, kind, name, type, default:, factory:)
|
108
|
+
@node = node
|
109
|
+
@kind = kind
|
110
|
+
@name = name
|
111
|
+
@type = type
|
112
|
+
@default = default
|
113
|
+
@factory = factory
|
114
|
+
|
115
|
+
# A T::Struct should have both a default and a factory, if we find one let's raise an error
|
116
|
+
raise if @default && @factory
|
117
|
+
end
|
118
|
+
|
119
|
+
def attr_sig
|
120
|
+
"sig { returns(#{type}) }"
|
121
|
+
end
|
122
|
+
|
123
|
+
def attr_accessor
|
124
|
+
"#{kind} :#{name}"
|
125
|
+
end
|
126
|
+
|
127
|
+
def initialize_sig_param
|
128
|
+
"#{name}: #{type}"
|
129
|
+
end
|
130
|
+
|
131
|
+
def initialize_param
|
132
|
+
rb = String.new
|
133
|
+
rb << "#{name}:"
|
134
|
+
rb << " #{default}" if default
|
135
|
+
rb << " #{factory}" if factory
|
136
|
+
rb
|
137
|
+
end
|
138
|
+
|
139
|
+
def initialize_assign
|
140
|
+
rb = String.new
|
141
|
+
rb << "@#{name} = #{name}"
|
142
|
+
rb << ".call" if factory
|
143
|
+
rb
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# @!method t_struct?(node)
|
148
|
+
def_node_matcher(:t_struct?, <<~PATTERN)
|
149
|
+
(const (const {nil? cbase} :T) {:Struct :ImmutableStruct :InexactStruct})
|
150
|
+
PATTERN
|
151
|
+
|
152
|
+
# @!method t_props?(node)
|
153
|
+
def_node_matcher(:t_props?, "(send nil? {:include :prepend :extend} `(const (const {nil? cbase} :T) :Props))")
|
154
|
+
|
155
|
+
def on_class(node)
|
156
|
+
return unless t_struct?(node.parent_class)
|
157
|
+
|
158
|
+
add_offense(node, message: MSG_STRUCT) do |corrector|
|
159
|
+
walker = TStructWalker.new
|
160
|
+
walker.walk(node.body)
|
161
|
+
|
162
|
+
range = range_between(node.identifier.source_range.end_pos, node.parent_class.source_range.end_pos)
|
163
|
+
corrector.remove(range)
|
164
|
+
next if node.single_line?
|
165
|
+
|
166
|
+
unless walker.has_extend_t_sig
|
167
|
+
indent = offset(node)
|
168
|
+
corrector.insert_after(node.identifier, "\n#{indent} extend T::Sig\n")
|
169
|
+
end
|
170
|
+
|
171
|
+
first_prop = walker.props.first
|
172
|
+
walker.props.each do |prop|
|
173
|
+
node = prop.node
|
174
|
+
indent = offset(node)
|
175
|
+
line_range = range_by_whole_lines(prop.node.source_range)
|
176
|
+
new_line = prop != first_prop && !previous_line_blank?(node)
|
177
|
+
trailing_comments = processed_source.each_comment_in_lines(line_range.line..line_range.line)
|
178
|
+
|
179
|
+
corrector.replace(
|
180
|
+
line_range,
|
181
|
+
"#{new_line ? "\n" : ""}" \
|
182
|
+
"#{trailing_comments.map { |comment| "#{indent}#{comment.text}\n" }.join}" \
|
183
|
+
"#{indent}#{prop.attr_sig}\n#{indent}#{prop.attr_accessor}",
|
184
|
+
)
|
185
|
+
end
|
186
|
+
|
187
|
+
last_prop = walker.props.last
|
188
|
+
if last_prop
|
189
|
+
indent = offset(last_prop.node)
|
190
|
+
line_range = range_by_whole_lines(last_prop.node.source_range, include_final_newline: true)
|
191
|
+
corrector.insert_after(line_range, initialize_method(indent, walker.props))
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def on_send(node)
|
197
|
+
return unless t_props?(node)
|
198
|
+
|
199
|
+
add_offense(node, message: MSG_PROPS)
|
200
|
+
end
|
201
|
+
|
202
|
+
private
|
203
|
+
|
204
|
+
def initialize_method(indent, props)
|
205
|
+
# We sort optional keyword arguments after required ones
|
206
|
+
props = props.sort_by { |prop| prop.default || prop.factory ? 1 : 0 }
|
207
|
+
|
208
|
+
string = +"\n"
|
209
|
+
string << "#{indent}sig { params(#{props.map(&:initialize_sig_param).join(", ")}).void }\n"
|
210
|
+
string << "#{indent}def initialize(#{props.map(&:initialize_param).join(", ")})\n"
|
211
|
+
props.each do |prop|
|
212
|
+
string << "#{indent} #{prop.initialize_assign}\n"
|
213
|
+
end
|
214
|
+
string << "#{indent}end\n"
|
215
|
+
end
|
216
|
+
|
217
|
+
def previous_line_blank?(node)
|
218
|
+
processed_source.buffer.source_line(node.source_range.line - 1).blank?
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
@@ -11,21 +11,25 @@ module RuboCop
|
|
11
11
|
end
|
12
12
|
|
13
13
|
module ClassMethods
|
14
|
-
#
|
14
|
+
# Sets the version of the Sorbet static type checker required by this cop
|
15
15
|
def minimum_target_sorbet_static_version(version)
|
16
16
|
@minimum_target_sorbet_static_version = Gem::Version.new(version)
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
19
|
+
def supports_target_sorbet_static_version?(version)
|
20
20
|
@minimum_target_sorbet_static_version <= Gem::Version.new(version)
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
+
def sorbet_enabled?
|
25
|
+
!target_sorbet_static_version_from_bundler_lock_file.nil?
|
26
|
+
end
|
27
|
+
|
24
28
|
def enabled_for_sorbet_static_version?
|
25
29
|
sorbet_static_version = target_sorbet_static_version_from_bundler_lock_file
|
26
30
|
return false unless sorbet_static_version
|
27
31
|
|
28
|
-
self.class.
|
32
|
+
self.class.supports_target_sorbet_static_version?(sorbet_static_version)
|
29
33
|
end
|
30
34
|
|
31
35
|
def target_sorbet_static_version_from_bundler_lock_file
|
@@ -54,7 +54,7 @@ module RuboCop
|
|
54
54
|
$(ivasgn $_ivar # First line: @_ivar = ...
|
55
55
|
(send # T.let(_ivar, T.nilable(_ivar_type))
|
56
56
|
$(const {nil? cbase} :T) :let
|
57
|
-
|
57
|
+
(ivar _ivar)
|
58
58
|
(send (const {nil? cbase} :T) :nilable $_ivar_type))) # T.nilable(_ivar_type)
|
59
59
|
$(or-asgn (ivasgn _ivar) $_initialization_expr)) # Second line: @_ivar ||= _initialization_expr
|
60
60
|
PATTERN
|
@@ -26,6 +26,7 @@ module RuboCop
|
|
26
26
|
# end
|
27
27
|
#
|
28
28
|
class RedundantExtendTSig < RuboCop::Cop::Base
|
29
|
+
include RangeHelp
|
29
30
|
extend AutoCorrector
|
30
31
|
|
31
32
|
MSG = "Do not redundantly `extend T::Sig` when it is already included in all modules."
|
@@ -40,7 +41,7 @@ module RuboCop
|
|
40
41
|
return unless extend_t_sig?(node)
|
41
42
|
|
42
43
|
add_offense(node) do |corrector|
|
43
|
-
corrector.remove(node)
|
44
|
+
corrector.remove(range_by_whole_lines(node.source_range, include_final_newline: true))
|
44
45
|
end
|
45
46
|
end
|
46
47
|
end
|
@@ -10,11 +10,13 @@ require_relative "sorbet/forbid_untyped_struct_props"
|
|
10
10
|
require_relative "sorbet/implicit_conversion_method"
|
11
11
|
require_relative "sorbet/one_ancestor_per_line"
|
12
12
|
require_relative "sorbet/callback_conditionals_binding"
|
13
|
+
require_relative "sorbet/forbid_t_struct"
|
13
14
|
require_relative "sorbet/forbid_t_unsafe"
|
14
15
|
require_relative "sorbet/forbid_t_untyped"
|
15
16
|
require_relative "sorbet/redundant_extend_t_sig"
|
16
17
|
require_relative "sorbet/type_alias_name"
|
17
18
|
require_relative "sorbet/obsolete_strict_memoization"
|
19
|
+
require_relative "sorbet/buggy_obsolete_strict_memoization"
|
18
20
|
|
19
21
|
require_relative "sorbet/rbi/forbid_extend_t_sig_helpers_in_shims"
|
20
22
|
require_relative "sorbet/rbi/forbid_rbi_outside_of_allowed_paths"
|
data/manual/cops.md
CHANGED
@@ -7,6 +7,7 @@ In the following section you find all available cops:
|
|
7
7
|
|
8
8
|
* [Sorbet/AllowIncompatibleOverride](cops_sorbet.md#sorbetallowincompatibleoverride)
|
9
9
|
* [Sorbet/BindingConstantWithoutTypeAlias](cops_sorbet.md#sorbetbindingconstantwithouttypealias)
|
10
|
+
* [Sorbet/BuggyObsoleteStrictMemoization](cops_sorbet.md#sorbetbuggyobsoletestrictmemoization)
|
10
11
|
* [Sorbet/CallbackConditionalsBinding](cops_sorbet.md#sorbetcallbackconditionalsbinding)
|
11
12
|
* [Sorbet/CheckedTrueInSignature](cops_sorbet.md#sorbetcheckedtrueinsignature)
|
12
13
|
* [Sorbet/ConstantsFromStrings](cops_sorbet.md#sorbetconstantsfromstrings)
|
@@ -19,6 +20,7 @@ In the following section you find all available cops:
|
|
19
20
|
* [Sorbet/ForbidIncludeConstLiteral](cops_sorbet.md#sorbetforbidincludeconstliteral)
|
20
21
|
* [Sorbet/ForbidRBIOutsideOfAllowedPaths](cops_sorbet.md#sorbetforbidrbioutsideofallowedpaths)
|
21
22
|
* [Sorbet/ForbidSuperclassConstLiteral](cops_sorbet.md#sorbetforbidsuperclassconstliteral)
|
23
|
+
* [Sorbet/ForbidTStruct](cops_sorbet.md#sorbetforbidtstruct)
|
22
24
|
* [Sorbet/ForbidTUnsafe](cops_sorbet.md#sorbetforbidtunsafe)
|
23
25
|
* [Sorbet/ForbidTUntyped](cops_sorbet.md#sorbetforbidtuntyped)
|
24
26
|
* [Sorbet/ForbidUntypedStructProps](cops_sorbet.md#sorbetforbiduntypedstructprops)
|
data/manual/cops_sorbet.md
CHANGED
@@ -41,6 +41,44 @@ FooOrBar = T.any(Foo, Bar)
|
|
41
41
|
FooOrBar = T.type_alias { T.any(Foo, Bar) }
|
42
42
|
```
|
43
43
|
|
44
|
+
## Sorbet/BuggyObsoleteStrictMemoization
|
45
|
+
|
46
|
+
Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
|
47
|
+
--- | --- | --- | --- | ---
|
48
|
+
Enabled | Yes | Yes (Unsafe) | 0.7.3 | -
|
49
|
+
|
50
|
+
Checks for the a mistaken variant of the "obsolete memoization pattern" that used to be required
|
51
|
+
for older Sorbet versions in `#typed: strict` files. The mistaken variant would overwrite the ivar with `nil`
|
52
|
+
on every call, causing the memoized value to be discarded and recomputed on every call.
|
53
|
+
|
54
|
+
This cop will correct it to read from the ivar instead of `nil`, which will memoize it correctly.
|
55
|
+
|
56
|
+
The result of this correction will be the "obsolete memoization pattern", which can further be corrected by
|
57
|
+
the `Sorbet/ObsoleteStrictMemoization` cop.
|
58
|
+
|
59
|
+
See `Sorbet/ObsoleteStrictMemoization` for more details.
|
60
|
+
|
61
|
+
### Examples
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
# bad
|
65
|
+
sig { returns(Foo) }
|
66
|
+
def foo
|
67
|
+
# This `nil` is likely a mistake, causing the memoized value to be discarded and recomputed on every call.
|
68
|
+
@foo = T.let(nil, T.nilable(Foo))
|
69
|
+
@foo ||= some_computation
|
70
|
+
end
|
71
|
+
|
72
|
+
# good
|
73
|
+
sig { returns(Foo) }
|
74
|
+
def foo
|
75
|
+
# This will now memoize the value as was likely intended, so `some_computation` is only ever called once.
|
76
|
+
# ⚠️If `some_computation` has side effects, this might be a breaking change!
|
77
|
+
@foo = T.let(@foo, T.nilable(Foo))
|
78
|
+
@foo ||= some_computation
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
44
82
|
## Sorbet/CallbackConditionalsBinding
|
45
83
|
|
46
84
|
Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
|
@@ -383,6 +421,45 @@ Name | Default value | Configurable values
|
|
383
421
|
--- | --- | ---
|
384
422
|
Exclude | `db/migrate/*.rb` | Array
|
385
423
|
|
424
|
+
## Sorbet/ForbidTStruct
|
425
|
+
|
426
|
+
Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
|
427
|
+
--- | --- | --- | --- | ---
|
428
|
+
Disabled | No | Yes | <<next>> | <<next>>
|
429
|
+
|
430
|
+
Disallow using `T::Struct` and `T::Props`.
|
431
|
+
|
432
|
+
### Examples
|
433
|
+
|
434
|
+
```ruby
|
435
|
+
# bad
|
436
|
+
class MyStruct < T::Struct
|
437
|
+
const :foo, String
|
438
|
+
prop :bar, Integer, default: 0
|
439
|
+
|
440
|
+
def some_method; end
|
441
|
+
end
|
442
|
+
|
443
|
+
# good
|
444
|
+
class MyStruct
|
445
|
+
extend T::Sig
|
446
|
+
|
447
|
+
sig { returns(String) }
|
448
|
+
attr_reader :foo
|
449
|
+
|
450
|
+
sig { returns(Integer) }
|
451
|
+
attr_accessor :bar
|
452
|
+
|
453
|
+
sig { params(foo: String, bar: Integer) }
|
454
|
+
def initialize(foo:, bar: 0)
|
455
|
+
@foo = foo
|
456
|
+
@bar = bar
|
457
|
+
end
|
458
|
+
|
459
|
+
def some_method; end
|
460
|
+
end
|
461
|
+
```
|
462
|
+
|
386
463
|
## Sorbet/ForbidTUnsafe
|
387
464
|
|
388
465
|
Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
|
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.7.
|
4
|
+
version: 0.7.4
|
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: 2023-
|
14
|
+
date: 2023-09-25 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: rspec
|
@@ -87,10 +87,12 @@ files:
|
|
87
87
|
- dev.yml
|
88
88
|
- lib/rubocop-sorbet.rb
|
89
89
|
- lib/rubocop/cop/sorbet/binding_constant_without_type_alias.rb
|
90
|
+
- lib/rubocop/cop/sorbet/buggy_obsolete_strict_memoization.rb
|
90
91
|
- lib/rubocop/cop/sorbet/callback_conditionals_binding.rb
|
91
92
|
- lib/rubocop/cop/sorbet/constants_from_strings.rb
|
92
93
|
- lib/rubocop/cop/sorbet/forbid_include_const_literal.rb
|
93
94
|
- lib/rubocop/cop/sorbet/forbid_superclass_const_literal.rb
|
95
|
+
- lib/rubocop/cop/sorbet/forbid_t_struct.rb
|
94
96
|
- lib/rubocop/cop/sorbet/forbid_t_unsafe.rb
|
95
97
|
- lib/rubocop/cop/sorbet/forbid_t_untyped.rb
|
96
98
|
- lib/rubocop/cop/sorbet/forbid_untyped_struct_props.rb
|
@@ -152,7 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
152
154
|
- !ruby/object:Gem::Version
|
153
155
|
version: '0'
|
154
156
|
requirements: []
|
155
|
-
rubygems_version: 3.4.
|
157
|
+
rubygems_version: 3.4.19
|
156
158
|
signing_key:
|
157
159
|
specification_version: 4
|
158
160
|
summary: Automatic Sorbet code style checking tool.
|