rubocop-sorbet 0.3.7 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rubocop_sorbet'
3
+ require 'rubocop'
4
+
5
+ require_relative 'rubocop/sorbet'
6
+ require_relative 'rubocop/sorbet/version'
7
+ require_relative 'rubocop/sorbet/inject'
8
+
9
+ RuboCop::Sorbet::Inject.defaults!
10
+
11
+ require_relative 'rubocop/cop/sorbet_cops'
@@ -0,0 +1,58 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'rubocop'
5
+
6
+ module RuboCop
7
+ module Cop
8
+ module Sorbet
9
+ # This cop disallows use of `T.untyped` or `T.nilable(T.untyped)`
10
+ # as a prop type for `T::Struct`.
11
+ #
12
+ # @example
13
+ #
14
+ # # bad
15
+ # class SomeClass
16
+ # const :foo, T.untyped
17
+ # prop :bar, T.nilable(T.untyped)
18
+ # end
19
+ #
20
+ # # good
21
+ # class SomeClass
22
+ # const :foo, Integer
23
+ # prop :bar, T.nilable(String)
24
+ # end
25
+ class ForbidUntypedStructProps < RuboCop::Cop::Cop
26
+ MSG = 'Struct props cannot be T.untyped'
27
+
28
+ def_node_matcher :t_struct, <<~PATTERN
29
+ (const (const nil? :T) :Struct)
30
+ PATTERN
31
+
32
+ def_node_matcher :t_untyped, <<~PATTERN
33
+ (send (const nil? :T) :untyped)
34
+ PATTERN
35
+
36
+ def_node_matcher :t_nilable_untyped, <<~PATTERN
37
+ (send (const nil? :T) :nilable {#t_untyped #t_nilable_untyped})
38
+ PATTERN
39
+
40
+ def_node_matcher :subclass_of_t_struct?, <<~PATTERN
41
+ (class (const ...) #t_struct ...)
42
+ PATTERN
43
+
44
+ def_node_search :untyped_props, <<~PATTERN
45
+ (send nil? {:prop :const} _ {#t_untyped #t_nilable_untyped} ...)
46
+ PATTERN
47
+
48
+ def on_class(node)
49
+ return unless subclass_of_t_struct?(node)
50
+
51
+ untyped_props(node).each do |untyped_prop|
52
+ add_offense(untyped_prop.child_nodes[1])
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -59,8 +59,8 @@ module RuboCop
59
59
  FROZEN_REGEX = /#\s+frozen_string_literal:(?:\s+([\w]+))?/
60
60
 
61
61
  PREFERRED_ORDER = {
62
- SIGIL_REGEX => 'typed',
63
62
  CODING_REGEX => 'encoding',
63
+ SIGIL_REGEX => 'typed',
64
64
  INDENT_REGEX => 'warn_indent',
65
65
  FROZEN_REGEX => 'frozen_string_literal',
66
66
  }.freeze
@@ -26,13 +26,9 @@ module RuboCop
26
26
 
27
27
  def on_signature(node)
28
28
  sig_params = signature_params(node).first
29
- sig_params_order =
30
- if sig_params.nil?
31
- []
32
- else
33
- sig_params.arguments.first.keys.map(&:value)
34
- end
35
29
 
30
+ sig_params_order = extract_parameters(sig_params)
31
+ return if sig_params_order.nil?
36
32
  method_node = node.parent.children[node.sibling_index + 1]
37
33
  return if method_node.nil? || method_node.type != :def
38
34
  method_parameters = method_node.arguments
@@ -42,6 +38,18 @@ module RuboCop
42
38
 
43
39
  private
44
40
 
41
+ def extract_parameters(sig_params)
42
+ return [] if sig_params.nil?
43
+
44
+ arguments = sig_params.arguments.first
45
+ return arguments.keys.map(&:value) if RuboCop::AST::HashNode === arguments
46
+
47
+ add_offense(
48
+ sig_params,
49
+ message: "Invalid signature."
50
+ )
51
+ end
52
+
45
53
  def check_for_inconsistent_param_ordering(sig_params_order, parameters)
46
54
  parameters.each_with_index do |param, index|
47
55
  param_name = param.children[0]
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'sorbet/binding_constants_without_type_alias'
3
+ require_relative 'sorbet/constants_from_strings'
4
+ require_relative 'sorbet/forbid_superclass_const_literal'
5
+ require_relative 'sorbet/forbid_include_const_literal'
6
+ require_relative 'sorbet/forbid_untyped_struct_props'
7
+
8
+ require_relative 'sorbet/signatures/allow_incompatible_override'
9
+ require_relative 'sorbet/signatures/checked_true_in_signature'
10
+ require_relative 'sorbet/signatures/keyword_argument_ordering'
11
+ require_relative 'sorbet/signatures/parameters_ordering_in_signature'
12
+ require_relative 'sorbet/signatures/signature_build_order'
13
+ require_relative 'sorbet/signatures/enforce_signatures'
14
+
15
+ require_relative 'sorbet/sigils/valid_sigil'
16
+ require_relative 'sorbet/sigils/has_sigil'
17
+ require_relative 'sorbet/sigils/ignore_sigil'
18
+ require_relative 'sorbet/sigils/false_sigil'
19
+ require_relative 'sorbet/sigils/true_sigil'
20
+ require_relative 'sorbet/sigils/strict_sigil'
21
+ require_relative 'sorbet/sigils/strong_sigil'
22
+ require_relative 'sorbet/sigils/enforce_sigil_order'
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ require "rubocop/sorbet/version"
3
+ require "yaml"
4
+
5
+ module RuboCop
6
+ module Sorbet
7
+ class Error < StandardError; end
8
+
9
+ PROJECT_ROOT = Pathname.new(__dir__).parent.parent.expand_path.freeze
10
+ CONFIG_DEFAULT = PROJECT_ROOT.join('config', 'default.yml').freeze
11
+ CONFIG = YAML.safe_load(CONFIG_DEFAULT.read).freeze
12
+
13
+ private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT)
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The original code is from https://github.com/rubocop-hq/rubocop-rspec/blob/master/lib/rubocop/rspec/inject.rb
4
+ # See https://github.com/rubocop-hq/rubocop-rspec/blob/master/MIT-LICENSE.md
5
+ module RuboCop
6
+ module Sorbet
7
+ # Because RuboCop doesn't yet support plugins, we have to monkey patch in a
8
+ # bit of our configuration.
9
+ module Inject
10
+ def self.defaults!
11
+ path = CONFIG_DEFAULT.to_s
12
+ hash = ConfigLoader.send(:load_yaml_configuration, path)
13
+ config = Config.new(hash, path).tap(&:make_excludes_absolute)
14
+ puts "configuration from #{path}" if ConfigLoader.debug?
15
+ config = ConfigLoader.merge_with_default(config, path)
16
+ ConfigLoader.instance_variable_set(:@default_configuration, config)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+ module RuboCop
3
+ module Sorbet
4
+ VERSION = "0.4.0"
5
+ end
6
+ end
@@ -0,0 +1,29 @@
1
+ ## Available cops
2
+
3
+ In the following section you find all available cops:
4
+
5
+ <!-- START_COP_LIST -->
6
+ #### Department [Sorbet](cops_sorbet.md)
7
+
8
+ * [Sorbet/AllowIncompatibleOverride](cops_sorbet.md#sorbetallowincompatibleoverride)
9
+ * [Sorbet/BindingConstantWithoutTypeAlias](cops_sorbet.md#sorbetbindingconstantwithouttypealias)
10
+ * [Sorbet/CheckedTrueInSignature](cops_sorbet.md#sorbetcheckedtrueinsignature)
11
+ * [Sorbet/ConstantsFromStrings](cops_sorbet.md#sorbetconstantsfromstrings)
12
+ * [Sorbet/EnforceSigilOrder](cops_sorbet.md#sorbetenforcesigilorder)
13
+ * [Sorbet/EnforceSignatures](cops_sorbet.md#sorbetenforcesignatures)
14
+ * [Sorbet/FalseSigil](cops_sorbet.md#sorbetfalsesigil)
15
+ * [Sorbet/ForbidIncludeConstLiteral](cops_sorbet.md#sorbetforbidincludeconstliteral)
16
+ * [Sorbet/ForbidSuperclassConstLiteral](cops_sorbet.md#sorbetforbidsuperclassconstliteral)
17
+ * [Sorbet/ForbidUntypedStructProps](cops_sorbet.md#sorbetforbiduntypedstructprops)
18
+ * [Sorbet/HasSigil](cops_sorbet.md#sorbethassigil)
19
+ * [Sorbet/IgnoreSigil](cops_sorbet.md#sorbetignoresigil)
20
+ * [Sorbet/KeywordArgumentOrdering](cops_sorbet.md#sorbetkeywordargumentordering)
21
+ * [Sorbet/ParametersOrderingInSignature](cops_sorbet.md#sorbetparametersorderinginsignature)
22
+ * [Sorbet/SignatureBuildOrder](cops_sorbet.md#sorbetsignaturebuildorder)
23
+ * [Sorbet/SignatureCop](cops_sorbet.md#sorbetsignaturecop)
24
+ * [Sorbet/StrictSigil](cops_sorbet.md#sorbetstrictsigil)
25
+ * [Sorbet/StrongSigil](cops_sorbet.md#sorbetstrongsigil)
26
+ * [Sorbet/TrueSigil](cops_sorbet.md#sorbettruesigil)
27
+ * [Sorbet/ValidSigil](cops_sorbet.md#sorbetvalidsigil)
28
+
29
+ <!-- END_COP_LIST -->
@@ -0,0 +1,340 @@
1
+ # Sorbet
2
+
3
+ ## Sorbet/AllowIncompatibleOverride
4
+
5
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
6
+ --- | --- | --- | --- | ---
7
+ Enabled | Yes | No | 0.2.0 | -
8
+
9
+ This cop disallows using `.override(allow_incompatible: true)`.
10
+ Using `allow_incompatible` suggests a violation of the Liskov
11
+ Substitution Principle, meaning that a subclass is not a valid
12
+ subtype of it's superclass. This Cop prevents these design smells
13
+ from occurring.
14
+
15
+ ### Examples
16
+
17
+ ```ruby
18
+ # bad
19
+ sig.override(allow_incompatible: true)
20
+
21
+ # good
22
+ sig.override
23
+ ```
24
+
25
+ ## Sorbet/BindingConstantWithoutTypeAlias
26
+
27
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
28
+ --- | --- | --- | --- | ---
29
+ Enabled | Yes | Yes | 0.2.0 | -
30
+
31
+ This cop disallows binding the return value of `T.any`, `T.all`, `T.enum`
32
+ to a constant directly. To bind the value, one must use `T.type_alias`.
33
+
34
+ ### Examples
35
+
36
+ ```ruby
37
+ # bad
38
+ FooOrBar = T.any(Foo, Bar)
39
+
40
+ # good
41
+ FooOrBar = T.type_alias { T.any(Foo, Bar) }
42
+ ```
43
+
44
+ ## Sorbet/CheckedTrueInSignature
45
+
46
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
47
+ --- | --- | --- | --- | ---
48
+ Enabled | Yes | No | 0.2.0 | -
49
+
50
+ This cop disallows the usage of `checked(true)`. This usage could cause
51
+ confusion; it could lead some people to believe that a method would be checked
52
+ even if runtime checks have not been enabled on the class or globally.
53
+ Additionally, in the event where checks are enabled, `checked(true)` would
54
+ be redundant; only `checked(false)` or `soft` would change the behaviour.
55
+
56
+ ### Examples
57
+
58
+ ```ruby
59
+ # bad
60
+ sig { void.checked(true) }
61
+
62
+ # good
63
+ sig { void }
64
+ ```
65
+
66
+ ## Sorbet/ConstantsFromStrings
67
+
68
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
69
+ --- | --- | --- | --- | ---
70
+ Enabled | Yes | No | 0.2.0 | -
71
+
72
+ This cop disallows the calls that are used to get constants fom Strings
73
+ such as +constantize+, +const_get+, and +constants+.
74
+
75
+ The goal of this cop is to make the code easier to statically analyze,
76
+ more IDE-friendly, and more predictable. It leads to code that clearly
77
+ expresses which values the constant can have.
78
+
79
+ ### Examples
80
+
81
+ ```ruby
82
+ # bad
83
+ class_name.constantize
84
+
85
+ # bad
86
+ constants.detect { |c| c.name == "User" }
87
+
88
+ # bad
89
+ const_get(class_name)
90
+
91
+ # good
92
+ case class_name
93
+ when "User"
94
+ User
95
+ else
96
+ raise ArgumentError
97
+ end
98
+
99
+ # good
100
+ { "User" => User }.fetch(class_name)
101
+ ```
102
+
103
+ ## Sorbet/EnforceSigilOrder
104
+
105
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
106
+ --- | --- | --- | --- | ---
107
+ Enabled | Yes | Yes | 0.3.4 | -
108
+
109
+ This cop checks that the Sorbet sigil comes as the first magic comment in the file.
110
+
111
+ The expected order for magic comments is: typed, (en)?coding, warn_indent then frozen_string_literal.
112
+
113
+ For example, the following bad ordering:
114
+
115
+ ```ruby
116
+ # frozen_string_literal: true
117
+ # typed: true
118
+ class Foo; end
119
+ ```
120
+
121
+ Will be corrected as:
122
+
123
+ ```ruby
124
+ # typed: true
125
+ # frozen_string_literal: true
126
+ class Foo; end
127
+ ```
128
+
129
+ Only `typed`, `(en)?coding`, `warn_indent` and `frozen_string_literal` magic comments are considered,
130
+ other comments or magic comments are left in the same place.
131
+
132
+ ## Sorbet/EnforceSignatures
133
+
134
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
135
+ --- | --- | --- | --- | ---
136
+ Disabled | Yes | Yes | 0.3.4 | -
137
+
138
+ This cop checks that every method definition and attribute accessor has a Sorbet signature.
139
+
140
+ It also suggest an autocorrect with placeholders so the following code:
141
+
142
+ ```
143
+ def foo(a, b, c); end
144
+ ```
145
+
146
+ Will be corrected as:
147
+
148
+ ```
149
+ sig { params(a: T.untyped, b: T.untyped, c: T.untyped).returns(T.untyped)
150
+ def foo(a, b, c); end
151
+ ```
152
+
153
+ You can configure the placeholders used by changing the following options:
154
+
155
+ * `ParameterTypePlaceholder`: placeholders used for parameter types (default: 'T.untyped')
156
+ * `ReturnTypePlaceholder`: placeholders used for return types (default: 'T.untyped')
157
+
158
+ ## Sorbet/FalseSigil
159
+
160
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
161
+ --- | --- | --- | --- | ---
162
+ Enabled | Yes | Yes | 0.3.3 | -
163
+
164
+ This cop makes the Sorbet `false` sigil mandatory in all files.
165
+
166
+ ### Configurable attributes
167
+
168
+ Name | Default value | Configurable values
169
+ --- | --- | ---
170
+ SuggestedStrictness | `true` | Boolean
171
+ Include | `**/*.rb`, `**/*.rbi`, `**/*.rake`, `**/*.ru` | Array
172
+ Exclude | `bin/**/*`, `db/**/*.rb`, `script/**/*` | Array
173
+
174
+ ## Sorbet/ForbidIncludeConstLiteral
175
+
176
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
177
+ --- | --- | --- | --- | ---
178
+ Enabled | Yes | Yes | 0.2.0 | -
179
+
180
+ No documentation
181
+
182
+ ## Sorbet/ForbidSuperclassConstLiteral
183
+
184
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
185
+ --- | --- | --- | --- | ---
186
+ Enabled | Yes | Yes | 0.2.0 | -
187
+
188
+ No documentation
189
+
190
+ ## Sorbet/ForbidUntypedStructProps
191
+
192
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
193
+ --- | --- | --- | --- | ---
194
+ Enabled | Yes | No | 0.3.8 | -
195
+
196
+ This cop disallows use of `T.untyped` or `T.nilable(T.untyped)`
197
+ as a prop type for `T::Struct`.
198
+
199
+ ### Examples
200
+
201
+ ```ruby
202
+ # bad
203
+ class SomeClass
204
+ const :foo, T.untyped
205
+ prop :bar, T.nilable(T.untyped)
206
+ end
207
+
208
+ # good
209
+ class SomeClass
210
+ const :foo, Integer
211
+ prop :bar, T.nilable(String)
212
+ end
213
+ ```
214
+
215
+ ## Sorbet/HasSigil
216
+
217
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
218
+ --- | --- | --- | --- | ---
219
+ Disabled | Yes | Yes | 0.3.3 | -
220
+
221
+ This cop makes the Sorbet typed sigil mandatory in all files.
222
+
223
+ Options:
224
+
225
+ * `SuggestedStrictness`: Sorbet strictness level suggested in offense messages (default: 'false')
226
+ * `MinimumStrictness`: If set, make offense if the strictness level in the file is below this one
227
+
228
+ If a `MinimumStrictness` level is specified, it will be used in offense messages and autocorrect.
229
+
230
+ ## Sorbet/IgnoreSigil
231
+
232
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
233
+ --- | --- | --- | --- | ---
234
+ Disabled | Yes | Yes | 0.3.3 | -
235
+
236
+ This cop makes the Sorbet `ignore` sigil mandatory in all files.
237
+
238
+ ## Sorbet/KeywordArgumentOrdering
239
+
240
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
241
+ --- | --- | --- | --- | ---
242
+ Enabled | Yes | No | 0.2.0 | -
243
+
244
+ This cop checks for the ordering of keyword arguments required by
245
+ sorbet-runtime. The ordering requires that all keyword arguments
246
+ are at the end of the parameters list, and all keyword arguments
247
+ with a default value must be after those without default values.
248
+
249
+ ### Examples
250
+
251
+ ```ruby
252
+ # bad
253
+ sig { params(a: Integer, b: String).void }
254
+ def foo(a: 1, b:); end
255
+
256
+ # good
257
+ sig { params(b: String, a: Integer).void }
258
+ def foo(b:, a: 1); end
259
+ ```
260
+
261
+ ## Sorbet/ParametersOrderingInSignature
262
+
263
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
264
+ --- | --- | --- | --- | ---
265
+ Enabled | Yes | No | 0.2.0 | -
266
+
267
+ This cop checks for inconsistent ordering of parameters between the
268
+ signature and the method definition. The sorbet-runtime gem raises
269
+ when such inconsistency occurs.
270
+
271
+ ### Examples
272
+
273
+ ```ruby
274
+ # bad
275
+ sig { params(a: Integer, b: String).void }
276
+ def foo(b:, a:); end
277
+
278
+ # good
279
+ sig { params(a: Integer, b: String).void }
280
+ def foo(a:, b:); end
281
+ ```
282
+
283
+ ## Sorbet/SignatureBuildOrder
284
+
285
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
286
+ --- | --- | --- | --- | ---
287
+ Enabled | Yes | Yes | 0.3.0 | -
288
+
289
+ No documentation
290
+
291
+ ## Sorbet/SignatureCop
292
+
293
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
294
+ --- | --- | --- | --- | ---
295
+ Enabled | Yes | No | - | -
296
+
297
+ Abstract cop specific to Sorbet signatures
298
+
299
+ You can subclass it to use the `on_signature` trigger and the `signature?` node matcher.
300
+
301
+ ## Sorbet/StrictSigil
302
+
303
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
304
+ --- | --- | --- | --- | ---
305
+ Disabled | Yes | Yes | 0.3.3 | -
306
+
307
+ This cop makes the Sorbet `strict` sigil mandatory in all files.
308
+
309
+ ## Sorbet/StrongSigil
310
+
311
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
312
+ --- | --- | --- | --- | ---
313
+ Disabled | Yes | Yes | 0.3.3 | -
314
+
315
+ This cop makes the Sorbet `strong` sigil mandatory in all files.
316
+
317
+ ## Sorbet/TrueSigil
318
+
319
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
320
+ --- | --- | --- | --- | ---
321
+ Disabled | Yes | Yes | 0.3.3 | -
322
+
323
+ This cop makes the Sorbet `true` sigil mandatory in all files.
324
+
325
+ ## Sorbet/ValidSigil
326
+
327
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
328
+ --- | --- | --- | --- | ---
329
+ Enabled | Yes | Yes | 0.3.3 | -
330
+
331
+ This cop checks that every Ruby file contains a valid Sorbet sigil.
332
+ Adapted from: https://gist.github.com/clarkdave/85aca4e16f33fd52aceb6a0a29936e52
333
+
334
+ Options:
335
+
336
+ * `RequireSigilOnAllFiles`: make offense if the Sorbet typed is not found in the file (default: false)
337
+ * `SuggestedStrictness`: Sorbet strictness level suggested in offense messages (default: 'false')
338
+ * `MinimumStrictness`: If set, make offense if the strictness level in the file is below this one
339
+
340
+ If a `MinimumStrictness` level is specified, it will be used in offense messages and autocorrect.