rubocop-sorbet 0.9.0 → 0.10.1

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.
data/dev.yml CHANGED
@@ -7,5 +7,5 @@ up:
7
7
  - "bundler"
8
8
 
9
9
  commands:
10
- test: "bin/rspec"
10
+ test: "bin/rake test"
11
11
  style: "bin/rubocop"
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sorbet
6
+ # Disallow defining methods in blocks, to prevent running into issues
7
+ # caused by https://github.com/sorbet/sorbet/issues/3609.
8
+ #
9
+ # As a workaround, use `define_method` instead.
10
+ #
11
+ # The one exception is for `Class.new` blocks, as long as the result is
12
+ # assigned to a constant (i.e. as long as it is not an anonymous class).
13
+
14
+ # @example
15
+ # # bad
16
+ # yielding_method do
17
+ # def bad(args)
18
+ # # ...
19
+ # end
20
+ # end
21
+ #
22
+ # # bad
23
+ # Class.new do
24
+ # def bad(args)
25
+ # # ...
26
+ # end
27
+ # end
28
+ #
29
+ # # good
30
+ # yielding_method do
31
+ # define_method(:good) do |args|
32
+ # # ...
33
+ # end
34
+ # end
35
+ #
36
+ # # good
37
+ # MyClass = Class.new do
38
+ # def good(args)
39
+ # # ...
40
+ # end
41
+ # end
42
+ #
43
+ class BlockMethodDefinition < Base
44
+ include RuboCop::Cop::Alignment
45
+ extend AutoCorrector
46
+
47
+ MSG = "Do not define methods in blocks (use `define_method` as a workaround)."
48
+
49
+ def on_block(node)
50
+ if (parent = node.parent)
51
+ return if parent.casgn_type?
52
+ end
53
+
54
+ node.each_descendant(:any_def) do |def_node|
55
+ add_offense(def_node) do |corrector|
56
+ autocorrect_method_in_block(corrector, def_node)
57
+ end
58
+ end
59
+ end
60
+ alias_method :on_numblock, :on_block
61
+
62
+ private
63
+
64
+ def autocorrect_method_in_block(corrector, node)
65
+ indent = offset(node)
66
+
67
+ method_name = node.method_name
68
+ args = node.arguments.map(&:source).join(", ")
69
+ body = node.body&.source&.prepend("\n#{indent} ")
70
+
71
+ if node.def_type?
72
+ replacement = "define_method(:#{method_name}) do |#{args}|#{body}\n#{indent}end"
73
+ elsif node.defs_type?
74
+ receiver = node.receiver.source
75
+ replacement = "#{receiver}.define_singleton_method(:#{method_name}) do |#{args}|#{body}\n#{indent}end"
76
+ end
77
+
78
+ corrector.replace(node, replacement)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sorbet
6
+ # Check that code does not call `mixes_in_class_methods` from Sorbet `T::Helpers`.
7
+ #
8
+ # Good:
9
+ #
10
+ # ```
11
+ # module M
12
+ # extend ActiveSupport::Concern
13
+ #
14
+ # class_methods do
15
+ # ...
16
+ # end
17
+ # end
18
+ # ```
19
+ #
20
+ # Bad:
21
+ #
22
+ # ```
23
+ # module M
24
+ # extend T::Helpers
25
+ #
26
+ # module ClassMethods
27
+ # ...
28
+ # end
29
+ #
30
+ # mixes_in_class_methods(ClassMethods)
31
+ # end
32
+ # ```
33
+ class ForbidMixesInClassMethods < ::RuboCop::Cop::Base
34
+ MSG = "Do not use `mixes_in_class_methods`, use `extend ActiveSupport::Concern` instead."
35
+ RESTRICT_ON_SEND = [:mixes_in_class_methods].freeze
36
+
37
+ # @!method mixes_in_class_methods?(node)
38
+ def_node_matcher(:mixes_in_class_methods?, <<~PATTERN)
39
+ (send {self | nil? | (const (const {cbase | nil?} :T) :Helpers)} :mixes_in_class_methods ...)
40
+ PATTERN
41
+
42
+ def on_send(node)
43
+ add_offense(node) if mixes_in_class_methods?(node)
44
+ end
45
+ alias_method :on_csend, :on_send
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubocop"
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module Sorbet
8
+ # Suggests using `grep` over `select` when using it only for type narrowing.
9
+ #
10
+ # @example
11
+ #
12
+ # # bad
13
+ # strings_or_integers.select { |e| e.is_a?(String) }
14
+ # strings_or_integers.filter { |e| e.is_a?(String) }
15
+ # strings_or_integers.select { |e| e.kind_of?(String) }
16
+ #
17
+ # # good
18
+ # strings_or_integers.grep(String)
19
+ class SelectByIsA < RuboCop::Cop::Base
20
+ extend AutoCorrector
21
+
22
+ MSG = "Use `grep` instead of `select` when using it only for type narrowing."
23
+ RESTRICT_ON_SEND = [:select, :filter].freeze
24
+
25
+ # @!method type_narrowing_select?(node)
26
+ def_node_matcher :type_narrowing_select?, <<~PATTERN
27
+ {
28
+ (block
29
+ (call _ {:select :filter})
30
+ (args (arg _))
31
+ (send (lvar _) { :is_a? :kind_of? } (const nil? _)))
32
+ (numblock
33
+ (call _ {:select :filter})
34
+ _
35
+ (send (lvar _) { :is_a? :kind_of? } (const nil? _)))
36
+ (itblock
37
+ (call _ {:select :filter})
38
+ _
39
+ (send (lvar _) { :is_a? :kind_of? } (const nil? _)))
40
+ }
41
+ PATTERN
42
+
43
+ def on_send(node)
44
+ block_node = node.block_node
45
+
46
+ return unless block_node
47
+ return unless type_narrowing_select?(block_node)
48
+
49
+ add_offense(block_node) do |corrector|
50
+ receiver = node.receiver
51
+ type_class = block_node.body.children[2]
52
+ navigation = node.csend_type? ? "&." : "."
53
+ replacement = "#{receiver.source}#{navigation}grep(#{type_class.source})"
54
+
55
+ corrector.replace(block_node, replacement)
56
+ end
57
+ end
58
+ alias_method :on_csend, :on_send
59
+ end
60
+ end
61
+ end
62
+ end
@@ -54,7 +54,7 @@ module RuboCop
54
54
  # The first sigil encountered represents the "real" strictness so remove any below
55
55
  sigils[1..sigils.size].each do |token|
56
56
  corrector.remove(
57
- source_range(processed_source.buffer, token.line, (0..token.pos.last_column)),
57
+ source_range(processed_source.buffer, token.line, 0..token.pos.last_column),
58
58
  )
59
59
  end
60
60
  end
@@ -28,6 +28,8 @@ module RuboCop
28
28
  return unless check_sigil_present(sigil)
29
29
 
30
30
  strictness = extract_strictness(sigil)
31
+ check_double_commented_sigil(sigil, strictness)
32
+
31
33
  return unless check_strictness_not_empty(sigil, strictness)
32
34
  return unless check_strictness_valid(sigil, strictness)
33
35
 
@@ -37,7 +39,8 @@ module RuboCop
37
39
  protected
38
40
 
39
41
  STRICTNESS_LEVELS = ["ignore", "false", "true", "strict", "strong"]
40
- SIGIL_REGEX = /^[[:blank:]]*#[[:blank:]]+typed:(?:[[:blank:]]+([\S]+))?/
42
+ SIGIL_REGEX = /^[[:blank:]]*(?:#[[:blank:]]*)?#[[:blank:]]+typed:(?:[[:blank:]]+([\S]+))?/
43
+ INVALID_SIGIL_MSG = "Invalid Sorbet sigil `%<sigil>s`."
41
44
 
42
45
  # extraction
43
46
 
@@ -104,12 +107,27 @@ module RuboCop
104
107
  false
105
108
  end
106
109
 
110
+ def check_double_commented_sigil(sigil, strictness)
111
+ return unless sigil.text.start_with?(/[[:blank:]]*#[[:blank:]]*#/)
112
+
113
+ add_offense(
114
+ sigil.pos,
115
+ message: format(INVALID_SIGIL_MSG, sigil: sigil.text),
116
+ ) do |corrector|
117
+ # Remove the extra comment and normalize spaces
118
+ corrector.replace(
119
+ sigil.pos,
120
+ "# typed: #{strictness}",
121
+ )
122
+ end
123
+ end
124
+
107
125
  def check_strictness_valid(sigil, strictness)
108
126
  return true if STRICTNESS_LEVELS.include?(strictness)
109
127
 
110
128
  add_offense(
111
129
  sigil.pos,
112
- message: "Invalid Sorbet sigil `#{strictness}`.",
130
+ message: format(INVALID_SIGIL_MSG, sigil: strictness),
113
131
  ) do |corrector|
114
132
  autocorrect(corrector)
115
133
  end
@@ -24,9 +24,8 @@ module RuboCop
24
24
  # @!method sig_or_signable_method_definition?(node)
25
25
  def_node_matcher :sig_or_signable_method_definition?, <<~PATTERN
26
26
  ${
27
- def
28
- defs
29
- (send nil? {:attr_reader :attr_writer :attr_accessor} ...)
27
+ any_def
28
+ (send nil? {:attr :attr_reader :attr_writer :attr_accessor} ...)
30
29
  #signature?
31
30
  }
32
31
  PATTERN
@@ -34,16 +33,22 @@ module RuboCop
34
33
  def on_signature(sig)
35
34
  sig_or_signable_method_definition?(next_sibling(sig)) do |definition|
36
35
  range = lines_between(sig, definition)
37
- next if range.empty? || range.single_line?
36
+ next if range.empty? || range.single_line? || contains_only_rubocop_directives?(range)
38
37
 
39
38
  add_offense(range) do |corrector|
40
- corrector.insert_before(
41
- range_by_whole_lines(sig.source_range),
42
- range.source
43
- .sub(/\A\n+/, "") # remove initial newline(s)
44
- .gsub(/\n{2,}/, "\n"), # remove empty line(s)
45
- )
46
- corrector.remove(range)
39
+ lines = range.source.lines
40
+ rubocop_lines, other_lines = lines.partition { |line| line.strip.start_with?("# rubocop:") }
41
+
42
+ unless other_lines.empty?
43
+ corrector.insert_before(
44
+ range_by_whole_lines(sig.source_range),
45
+ other_lines.join
46
+ .sub(/\A\n+/, "") # remove initial newline(s)
47
+ .gsub(/\n{2,}/, "\n"), # remove empty line(s)
48
+ )
49
+ end
50
+
51
+ corrector.replace(range, rubocop_lines.empty? ? "" : rubocop_lines.join)
47
52
  end
48
53
  end
49
54
  end
@@ -54,6 +59,10 @@ module RuboCop
54
59
  node.parent&.children&.at(node.sibling_index + 1)
55
60
  end
56
61
 
62
+ def contains_only_rubocop_directives?(range)
63
+ range.source.lines.all? { |line| line.strip.start_with?("# rubocop:") }
64
+ end
65
+
57
66
  def lines_between(node1, node2, buffer: processed_source.buffer)
58
67
  end_of_node1_pos = node1.source_range.end_pos
59
68
  start_of_node2_pos = node2.source_range.begin_pos
@@ -5,9 +5,11 @@ require_relative "sorbet/mixin/t_enum"
5
5
  require_relative "sorbet/mixin/signature_help"
6
6
 
7
7
  require_relative "sorbet/binding_constant_without_type_alias"
8
+ require_relative "sorbet/block_method_definition"
8
9
  require_relative "sorbet/constants_from_strings"
9
10
  require_relative "sorbet/forbid_superclass_const_literal"
10
11
  require_relative "sorbet/forbid_include_const_literal"
12
+ require_relative "sorbet/forbid_mixes_in_class_methods"
11
13
  require_relative "sorbet/forbid_type_aliased_shapes"
12
14
  require_relative "sorbet/forbid_untyped_struct_props"
13
15
  require_relative "sorbet/implicit_conversion_method"
@@ -18,6 +20,7 @@ require_relative "sorbet/forbid_t_unsafe"
18
20
  require_relative "sorbet/forbid_t_untyped"
19
21
  require_relative "sorbet/redundant_extend_t_sig"
20
22
  require_relative "sorbet/refinement"
23
+ require_relative "sorbet/select_by_is_a"
21
24
  require_relative "sorbet/type_alias_name"
22
25
  require_relative "sorbet/obsolete_strict_memoization"
23
26
  require_relative "sorbet/buggy_obsolete_strict_memoization"
@@ -1,5 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ rubocop_min_version = Gem::Version.create("1.72.1")
4
+ rubocop_version = Gem::Version.create(RuboCop::Version.version)
5
+
6
+ return if rubocop_version < rubocop_min_version
7
+
3
8
  require "rubocop"
4
9
  require "lint_roller"
5
10
  require "pathname"
@@ -8,9 +13,6 @@ module RuboCop
8
13
  module Sorbet
9
14
  # A plugin that integrates RuboCop Sorbet with RuboCop's plugin system.
10
15
  class Plugin < LintRoller::Plugin
11
- RUBOCOP_MIN_VERSION = "1.72.1"
12
- SUPPORTED = Gem::Version.create(RuboCop::Version.version) < RUBOCOP_MIN_VERSION
13
-
14
16
  def about
15
17
  LintRoller::About.new(
16
18
  name: "rubocop-sorbet",
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Sorbet
5
- VERSION = "0.9.0"
5
+ VERSION = "0.10.1"
6
6
  end
7
7
  end
@@ -10,7 +10,7 @@ module RuboCop
10
10
  module Sorbet
11
11
  class Error < StandardError; end
12
12
 
13
- unless Plugin::SUPPORTED
13
+ unless defined?(::RuboCop::Sorbet::Plugin)
14
14
  PROJECT_ROOT = Pathname.new(__dir__).parent.parent.expand_path.freeze
15
15
  CONFIG_DEFAULT = PROJECT_ROOT.join("config", "default.yml").freeze
16
16
  CONFIG = YAML.safe_load(CONFIG_DEFAULT.read).freeze
@@ -6,7 +6,7 @@ require_relative "rubocop/sorbet"
6
6
  require_relative "rubocop/sorbet/version"
7
7
  require_relative "rubocop/sorbet/plugin"
8
8
 
9
- unless RuboCop::Sorbet::Plugin::SUPPORTED
9
+ unless defined?(RuboCop::Sorbet::Plugin)
10
10
  require_relative "rubocop/sorbet/inject"
11
11
  RuboCop::Sorbet::Inject.defaults!
12
12
  end
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/BlockMethodDefinition](cops_sorbet.md#sorbetblockmethoddefinition)
10
11
  * [Sorbet/BuggyObsoleteStrictMemoization](cops_sorbet.md#sorbetbuggyobsoletestrictmemoization)
11
12
  * [Sorbet/CallbackConditionalsBinding](cops_sorbet.md#sorbetcallbackconditionalsbinding)
12
13
  * [Sorbet/CheckedTrueInSignature](cops_sorbet.md#sorbetcheckedtrueinsignature)
@@ -19,6 +20,7 @@ In the following section you find all available cops:
19
20
  * [Sorbet/ForbidComparableTEnum](cops_sorbet.md#sorbetforbidcomparabletenum)
20
21
  * [Sorbet/ForbidExtendTSigHelpersInShims](cops_sorbet.md#sorbetforbidextendtsighelpersinshims)
21
22
  * [Sorbet/ForbidIncludeConstLiteral](cops_sorbet.md#sorbetforbidincludeconstliteral)
23
+ * [Sorbet/ForbidMixesInClassMethods](cops_sorbet.md#sorbetforbidmixesinclassmethods)
22
24
  * [Sorbet/ForbidRBIOutsideOfAllowedPaths](cops_sorbet.md#sorbetforbidrbioutsideofallowedpaths)
23
25
  * [Sorbet/ForbidSig](cops_sorbet.md#sorbetforbidsig)
24
26
  * [Sorbet/ForbidSigWithRuntime](cops_sorbet.md#sorbetforbidsigwithruntime)
@@ -38,6 +40,7 @@ In the following section you find all available cops:
38
40
  * [Sorbet/ObsoleteStrictMemoization](cops_sorbet.md#sorbetobsoletestrictmemoization)
39
41
  * [Sorbet/RedundantExtendTSig](cops_sorbet.md#sorbetredundantextendtsig)
40
42
  * [Sorbet/Refinement](cops_sorbet.md#sorbetrefinement)
43
+ * [Sorbet/SelectByIsA](cops_sorbet.md#sorbetselectbyisa)
41
44
  * [Sorbet/SignatureBuildOrder](cops_sorbet.md#sorbetsignaturebuildorder)
42
45
  * [Sorbet/SingleLineRbiClassModuleDefinitions](cops_sorbet.md#sorbetsinglelinerbiclassmoduledefinitions)
43
46
  * [Sorbet/StrictSigil](cops_sorbet.md#sorbetstrictsigil)
@@ -41,6 +41,46 @@ FooOrBar = T.any(Foo, Bar)
41
41
  FooOrBar = T.type_alias { T.any(Foo, Bar) }
42
42
  ```
43
43
 
44
+ ## Sorbet/BlockMethodDefinition
45
+
46
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
47
+ --- | --- | --- | --- | ---
48
+ Enabled | Yes | Yes (Unsafe) | 0.10.1 | -
49
+
50
+
51
+
52
+ ### Examples
53
+
54
+ ```ruby
55
+ # bad
56
+ yielding_method do
57
+ def bad(args)
58
+ # ...
59
+ end
60
+ end
61
+
62
+ # bad
63
+ Class.new do
64
+ def bad(args)
65
+ # ...
66
+ end
67
+ end
68
+
69
+ # good
70
+ yielding_method do
71
+ define_method(:good) do |args|
72
+ # ...
73
+ end
74
+ end
75
+
76
+ # good
77
+ MyClass = Class.new do
78
+ def good(args)
79
+ # ...
80
+ end
81
+ end
82
+ ```
83
+
44
84
  ## Sorbet/BuggyObsoleteStrictMemoization
45
85
 
46
86
  Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
@@ -180,7 +220,7 @@ end
180
220
 
181
221
  Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
182
222
  --- | --- | --- | --- | ---
183
- Enabled | Yes | Yes | 0.7.0 | -
223
+ Enabled | Yes | Yes | 0.7.0 | 0.10.1
184
224
 
185
225
  Checks for blank lines after signatures.
186
226
 
@@ -254,6 +294,12 @@ You can configure the placeholders used by changing the following options:
254
294
  * `ParameterTypePlaceholder`: placeholders used for parameter types (default: 'T.untyped')
255
295
  * `ReturnTypePlaceholder`: placeholders used for return types (default: 'T.untyped')
256
296
 
297
+ ### Configurable attributes
298
+
299
+ Name | Default value | Configurable values
300
+ --- | --- | ---
301
+ AllowRBS | `false` | Boolean
302
+
257
303
  ## Sorbet/EnforceSingleSigil
258
304
 
259
305
  Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
@@ -385,6 +431,40 @@ or
385
431
  include Polaris::Engine.helpers
386
432
  ```
387
433
 
434
+ ## Sorbet/ForbidMixesInClassMethods
435
+
436
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
437
+ --- | --- | --- | --- | ---
438
+ Disabled | Yes | No | 0.10.1 | -
439
+
440
+ Check that code does not call `mixes_in_class_methods` from Sorbet `T::Helpers`.
441
+
442
+ Good:
443
+
444
+ ```
445
+ module M
446
+ extend ActiveSupport::Concern
447
+
448
+ class_methods do
449
+ ...
450
+ end
451
+ end
452
+ ```
453
+
454
+ Bad:
455
+
456
+ ```
457
+ module M
458
+ extend T::Helpers
459
+
460
+ module ClassMethods
461
+ ...
462
+ end
463
+
464
+ mixes_in_class_methods(ClassMethods)
465
+ end
466
+ ```
467
+
388
468
  ## Sorbet/ForbidRBIOutsideOfAllowedPaths
389
469
 
390
470
  Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
@@ -421,7 +501,7 @@ Include | `**/*.rbi` | Array
421
501
 
422
502
  Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
423
503
  --- | --- | --- | --- | ---
424
- Disabled | Yes | No | <<next>> | -
504
+ Disabled | Yes | No | 0.9.0 | -
425
505
 
426
506
  Check that definitions do not use a `sig` block.
427
507
 
@@ -443,7 +523,7 @@ def foo; end
443
523
 
444
524
  Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
445
525
  --- | --- | --- | --- | ---
446
- Disabled | Yes | No | <<next>> | -
526
+ Disabled | Yes | No | 0.9.0 | -
447
527
 
448
528
  Check that definitions do not use a `sig` block.
449
529
 
@@ -465,7 +545,7 @@ def foo; end
465
545
 
466
546
  Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
467
547
  --- | --- | --- | --- | ---
468
- Disabled | Yes | Yes | <<next>> | -
548
+ Disabled | Yes | Yes | 0.9.0 | -
469
549
 
470
550
  Check that `sig` is used instead of `T::Sig::WithoutRuntime.sig`.
471
551
 
@@ -518,7 +598,7 @@ Exclude | `db/migrate/*.rb` | Array
518
598
 
519
599
  Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
520
600
  --- | --- | --- | --- | ---
521
- Disabled | No | No | <<next>> | <<next>>
601
+ Disabled | No | No | 0.8.9 | -
522
602
 
523
603
  Disallow using `T::Enum`.
524
604
 
@@ -545,7 +625,7 @@ end
545
625
 
546
626
  Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
547
627
  --- | --- | --- | --- | ---
548
- Disabled | No | Yes | <<next>> | <<next>>
628
+ Disabled | No | Yes | 0.7.4 | -
549
629
 
550
630
  Disallow using `T::Struct` and `T::Props`.
551
631
 
@@ -714,7 +794,7 @@ Exclude | `bin/**/*`, `db/**/*.rb`, `script/**/*` | Array
714
794
 
715
795
  Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
716
796
  --- | --- | --- | --- | ---
717
- Disabled | Yes | No | <<next>> | -
797
+ Disabled | Yes | No | 0.7.1 | -
718
798
 
719
799
  Disallows declaring implicit conversion methods.
720
800
  Since Sorbet is a nominal (not structural) type system,
@@ -857,7 +937,7 @@ end
857
937
 
858
938
  Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
859
939
  --- | --- | --- | --- | ---
860
- Enabled | Yes | No | <<next>> | -
940
+ Enabled | Yes | No | 0.8.6 | -
861
941
 
862
942
  Checks for the use of Ruby Refinements library. Refinements add
863
943
  complexity and incur a performance penalty that can be significant
@@ -890,6 +970,26 @@ module Foo
890
970
  end
891
971
  ```
892
972
 
973
+ ## Sorbet/SelectByIsA
974
+
975
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
976
+ --- | --- | --- | --- | ---
977
+ Enabled | Yes | Yes | 0.10.1 | -
978
+
979
+ Suggests using `grep` over `select` when using it only for type narrowing.
980
+
981
+ ### Examples
982
+
983
+ ```ruby
984
+ # bad
985
+ strings_or_integers.select { |e| e.is_a?(String) }
986
+ strings_or_integers.filter { |e| e.is_a?(String) }
987
+ strings_or_integers.select { |e| e.kind_of?(String) }
988
+
989
+ # good
990
+ strings_or_integers.grep(String)
991
+ ```
992
+
893
993
  ## Sorbet/SignatureBuildOrder
894
994
 
895
995
  Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
@@ -12,7 +12,7 @@ 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"
15
+ spec.required_ruby_version = ">= 3.1"
16
16
 
17
17
  spec.metadata["allowed_push_host"] = "https://rubygems.org"
18
18
  spec.metadata["homepage_uri"] = spec.homepage
@@ -28,6 +28,6 @@ Gem::Specification.new do |spec|
28
28
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
29
  spec.require_paths = ["lib"]
30
30
 
31
- spec.add_runtime_dependency("lint_roller", "~> 1.1")
31
+ spec.add_runtime_dependency("lint_roller")
32
32
  spec.add_runtime_dependency("rubocop", ">= 1")
33
33
  end