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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ddc9a9caf478aaa31d5b985064aadc77e9e2d199927b7db01eb5823ea2190c28
4
- data.tar.gz: d440f179cff018d9ff54d68c01fafa9265d4020ca738f5311fe552b44c7d2074
3
+ metadata.gz: 1cf8cfdff3aedba93731774d5f47fd8c4f10ef8d615ea2cb9f4cdfd2f20d1522
4
+ data.tar.gz: 69b6974d9a6de86eeed4b29dcfcae25648a3880036b49ceb57a426821da4535c
5
5
  SHA512:
6
- metadata.gz: b046329f103125db498e57acb613d387b780c6ec00c3d433cdb88e90ed1110091f5c6a1f59f0d3907abc98e1755ef332046b545b8dd54665939d997716ecf057
7
- data.tar.gz: 68348b3e0ad4a8a6141526fb6c5406b42e7ea12661256f83258551b2a11c3a6c04ded7843b7398a4903fa9877cbd16c1acb4bf7f3ccce69c600fca5bc51b18bc
6
+ metadata.gz: 98a957ed012dcfefb642d54ee332d1435c0931bafe6fa07e2d36d280b7a1c4391bab2f05d2b77b1f44da850196a7edb322886b0d50bbe154e0e6d9d80a9f8f62
7
+ data.tar.gz: 25634e48e2daf8db8b41211d64f49560f9ddcd7236cc181364e7ab6ecc67cbdcd5d169454825a92ffebb2e6337ed81c1847d54483f0a073447034333c2b4b150
@@ -0,0 +1,10 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "bundler"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ - package-ecosystem: "github-actions"
8
+ directory: "/"
9
+ schedule:
10
+ interval: "weekly"
@@ -11,10 +11,10 @@ jobs:
11
11
  strategy:
12
12
  fail-fast: false
13
13
  matrix:
14
- ruby: ["2.7", "3.0", "3.1", "3.2"]
14
+ ruby: ["3.0", "3.1", "3.2", "3.3"]
15
15
  name: Test Ruby ${{ matrix.ruby }}
16
16
  steps:
17
- - uses: actions/checkout@v2
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@v2
32
+ - uses: actions/checkout@v4
33
33
  - name: Set up Ruby
34
34
  uses: ruby/setup-ruby@v1
35
35
  with:
@@ -12,7 +12,7 @@ jobs:
12
12
  stale:
13
13
  runs-on: ubuntu-latest
14
14
  steps:
15
- - uses: actions/stale@v5
15
+ - uses: actions/stale@v9
16
16
  with:
17
17
  stale-pr-message: >
18
18
  This PR has been automatically marked as stale because it has not had
data/.rubocop.yml CHANGED
@@ -7,7 +7,6 @@ require:
7
7
  - rubocop/cop/internal_affairs
8
8
 
9
9
  AllCops:
10
- TargetRubyVersion: 2.7
11
10
  Exclude:
12
11
  - vendor/**/*
13
12
  NewCops: disable
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 "byebug"
8
+ gem "debug"
9
9
  gem "rake", ">= 12.3.3"
10
- gem "rspec", "~> 3.7"
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.0)
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
- byebug (11.1.3)
12
- diff-lcs (1.3)
13
- json (2.6.3)
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.23.0)
16
- parser (3.2.2.3)
21
+ parallel (1.24.0)
22
+ parser (3.3.0.5)
17
23
  ast (~> 2.4.1)
18
24
  racc
19
- racc (1.7.1)
25
+ psych (5.1.2)
26
+ stringio
27
+ racc (1.7.3)
20
28
  rainbow (3.1.1)
21
- rake (13.0.1)
22
- regexp_parser (2.8.1)
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.8.0)
25
- rspec-core (~> 3.8.0)
26
- rspec-expectations (~> 3.8.0)
27
- rspec-mocks (~> 3.8.0)
28
- rspec-core (3.8.2)
29
- rspec-support (~> 3.8.0)
30
- rspec-expectations (3.8.4)
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.8.0)
33
- rspec-mocks (3.8.1)
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.8.0)
36
- rspec-support (3.8.2)
37
- rubocop (1.55.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.2.2.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.28.1, < 2.0)
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.29.0)
49
- parser (>= 3.2.1.0)
50
- rubocop-shopify (2.14.0)
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
- unicode-display_width (2.4.2)
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
- byebug
73
+ debug
61
74
  rake (>= 12.3.3)
62
- rspec (~> 3.7)
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
@@ -0,0 +1,8 @@
1
+ #
2
+ # Configuration for obsoletion.
3
+ #
4
+ # See: https://docs.rubocop.org/rubocop/extensions.html#config-obsoletions
5
+ #
6
+ removed:
7
+ Sorbet/OneAncestorPerLine:
8
+ reason: '`require_ancestor` now takes a block instead of arguments'
data/config/rbi.yml CHANGED
@@ -2,7 +2,6 @@ require:
2
2
  - rubocop-sorbet
3
3
 
4
4
  AllCops:
5
- TargetRubyVersion: 3.0
6
5
  DisabledByDefault: true
7
6
  Include:
8
7
  - '**/*.rbi'
data/dev.yml CHANGED
@@ -3,7 +3,7 @@ name: "rubocop-sorbet"
3
3
  type: "ruby"
4
4
 
5
5
  up:
6
- - ruby: "3.2.0"
6
+ - ruby
7
7
  - "bundler"
8
8
 
9
9
  commands:
@@ -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 in "\
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::Cop # rubocop:todo InternalAffairs/InheritDeprecatedCopClass
36
- CALLBACKS = [
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
- def autocorrect(node)
76
- lambda do |corrector|
77
- options = node.each_child_node.find(&:hash_type?)
78
-
79
- conditional = nil
80
- options.each_pair do |keyword, block|
81
- if keyword.value == :if || keyword.value == :unless
82
- conditional = block
83
- break
84
- end
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
- unless do_end_lambda
104
- # We are converting a one line lambda into a multiline
105
- # Remove the space after the `{`
106
- if /{\s/.match?(conditional.source)
107
- corrector.remove_preceding(block, 1)
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
- def on_send(node)
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
- type, _, block = conditional.child_nodes
148
- return unless type.lambda_or_proc? || type.block_literal?
149
-
150
- klass = node.ancestors.find(&:class_type?)
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::Cop # rubocop:todo InternalAffairs/InheritDeprecatedCopClass
37
- # @!method constant_from_string?(node)
38
- def_node_matcher(:constant_from_string?, <<-PATTERN)
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
- def on_send(node)
43
- return unless constant_from_string?(node)
40
+ RESTRICT_ON_SEND = [
41
+ :constantize,
42
+ :constants,
43
+ :const_get,
44
+ ].freeze
44
45
 
45
- add_offense(
46
- node,
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.arguments[0].source.delete_prefix(":")
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
- return unless check_strictness_level(sigil, strictness)
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.rb"
4
- require_relative "sorbet/mixin/signature_help.rb"
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"
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Sorbet
5
- VERSION = "0.8.0"
5
+ VERSION = "0.8.2"
6
6
  end
7
7
  end
@@ -12,5 +12,7 @@ module RuboCop
12
12
  CONFIG = YAML.safe_load(CONFIG_DEFAULT.read).freeze
13
13
 
14
14
  private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT)
15
+
16
+ ::RuboCop::ConfigObsoletion.files << PROJECT_ROOT.join("config", "obsoletion.yml")
15
17
  end
16
18
  end
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)
@@ -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
@@ -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.0
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-03-18 00:00:00.000000000 Z
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.6
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