rubocop-sorbet 0.10.4 → 0.11.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 79bcbc5d5d2a20065dae6bd132ce89a78f6d55d913253f582d57b26dbc90e3fe
4
- data.tar.gz: fc68fab55a25f15a2689564842768d87b2268ed99e8bdcf503284e47dd82deb2
3
+ metadata.gz: bf3fcf2d81ddb556a7a499288fefc3cdd30f4c1a6a47047ae5111ba510b51d00
4
+ data.tar.gz: cfeddb88f154b226f0321ed78b6373725985daf068355659763eb9913a40fbcc
5
5
  SHA512:
6
- metadata.gz: 6b67f844675e5ca0c5f229cf8dd55e091f12c068dcaeb0cf289eded28a92367d32ead0f315ca4776bd0acd04e743c3c0420c345ac1ac6a7041f84dc61cc1fa78
7
- data.tar.gz: ca77c4f1a29809ce05da20319309c760dff38fcf4adae09b60e4a588a24ca234a32c6a48226df8baa9eaa169b388953e4f65cb079a47d22f062924efdf3b9c3b
6
+ metadata.gz: 51c844a3c3dc0254a09e7c31fe3c5794873e9757c5f2ebb59f842d7ccf8786b66788d98acbac922e064395baefeb9f44df2a1f128720a9c6b5ffc56d4dabfe51
7
+ data.tar.gz: d42d29fbbdaa9ffd32ff3f01e56443677e439ac04444859df3fa2f87f5273a891bd6e9bfdf7f46454e604e6a5c0388c554633f39d11e3932556b7e1dd80c1ed3
@@ -0,0 +1,16 @@
1
+ name: Check Version Placeholders
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ jobs:
8
+ check-placeholders:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
12
+ - uses: ruby/setup-ruby@v1
13
+ with:
14
+ bundler-cache: true
15
+ - name: Check for stale version placeholders
16
+ run: bundle exec rake check_version_placeholders
@@ -14,7 +14,7 @@ jobs:
14
14
  ruby: ["3.1", "3.2", "3.3", "3.4"]
15
15
  name: Test Ruby ${{ matrix.ruby }}
16
16
  steps:
17
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
17
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
18
18
  - name: Set up Ruby
19
19
  uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 # v1.245.0
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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
32
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
33
33
  - name: Set up Ruby
34
34
  uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 # v1.245.0
35
35
  with:
@@ -17,7 +17,7 @@ jobs:
17
17
  github-token: "${{ secrets.GITHUB_TOKEN }}"
18
18
  - name: Enable auto-merge for Dependabot PRs
19
19
  if: ${{ steps.metadata.outputs.update-type != 'version-update:semver-major' }}
20
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
20
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
21
21
  with:
22
22
  github-token: "${{ secrets.GITHUB_TOKEN }}"
23
23
  script: |
@@ -12,7 +12,7 @@ jobs:
12
12
  stale:
13
13
  runs-on: ubuntu-latest
14
14
  steps:
15
- - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
15
+ - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
16
16
  with:
17
17
  stale-pr-message: >
18
18
  This PR has been automatically marked as stale because it has not had
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rubocop-sorbet (0.10.4)
4
+ rubocop-sorbet (0.11.0)
5
5
  lint_roller
6
6
  rubocop (>= 1.75.2)
7
7
 
@@ -9,12 +9,16 @@ GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
11
  ast (2.4.3)
12
+ cgi (0.5.0)
12
13
  date (3.4.1)
13
- debug (1.10.0)
14
+ debug (1.11.0)
14
15
  irb (~> 1.10)
15
16
  reline (>= 0.3.8)
17
+ erb (4.0.4)
18
+ cgi (>= 0.3.3)
16
19
  io-console (0.8.0)
17
- irb (1.14.3)
20
+ irb (1.15.2)
21
+ pp (>= 0.6.0)
18
22
  rdoc (>= 4.0.0)
19
23
  reline (>= 0.4.2)
20
24
  json (2.12.2)
@@ -27,19 +31,23 @@ GEM
27
31
  parser (3.3.8.0)
28
32
  ast (~> 2.4.1)
29
33
  racc
34
+ pp (0.6.2)
35
+ prettyprint
36
+ prettyprint (0.2.0)
30
37
  prism (1.4.0)
31
- psych (5.2.2)
38
+ psych (5.2.6)
32
39
  date
33
40
  stringio
34
41
  racc (1.8.1)
35
42
  rainbow (3.1.1)
36
43
  rake (13.3.0)
37
- rdoc (6.10.0)
44
+ rdoc (6.14.1)
45
+ erb
38
46
  psych (>= 4.0.0)
39
47
  regexp_parser (2.10.0)
40
- reline (0.6.0)
48
+ reline (0.6.1)
41
49
  io-console (~> 0.5)
42
- rubocop (1.76.1)
50
+ rubocop (1.77.0)
43
51
  json (~> 2.3)
44
52
  language_server-protocol (~> 3.17.0.2)
45
53
  lint_roller (~> 1.1.0)
@@ -47,7 +55,7 @@ GEM
47
55
  parser (>= 3.3.0.2)
48
56
  rainbow (>= 2.2.2, < 4.0)
49
57
  regexp_parser (>= 2.9.3, < 3.0)
50
- rubocop-ast (>= 1.45.0, < 2.0)
58
+ rubocop-ast (>= 1.45.1, < 2.0)
51
59
  ruby-progressbar (~> 1.7)
52
60
  unicode-display_width (>= 2.4.0, < 4.0)
53
61
  rubocop-ast (1.45.1)
@@ -61,7 +69,7 @@ GEM
61
69
  rubocop (~> 1.62)
62
70
  ruby-progressbar (1.13.0)
63
71
  ruby2_keywords (0.0.5)
64
- stringio (3.1.2)
72
+ stringio (3.1.7)
65
73
  unicode-display_width (3.1.4)
66
74
  unicode-emoji (~> 4.0, >= 4.0.4)
67
75
  unicode-emoji (4.0.4)
data/Rakefile CHANGED
@@ -47,10 +47,6 @@ module Releaser
47
47
  version = File.read("VERSION").strip
48
48
  puts "Preparing release for version #{version}"
49
49
 
50
- update_file("lib/rubocop/sorbet/version.rb") do |version_file|
51
- version_file.sub(/VERSION = ".*"/, "VERSION = \"#{version}\"")
52
- end
53
-
54
50
  update_file("config/default.yml") do |default|
55
51
  default.gsub(/['"]?<<\s*next\s*>>['"]?/i, "'#{version}'")
56
52
  end
@@ -58,7 +54,7 @@ module Releaser
58
54
  sh "bundle install"
59
55
  sh "bundle exec rake generate_cops_documentation"
60
56
 
61
- sh "git add lib/rubocop/sorbet/version.rb config/default.yml Gemfile.lock VERSION manual"
57
+ sh "git add config/default.yml Gemfile.lock VERSION manual"
62
58
 
63
59
  sh "git commit -m 'Release v#{version}'"
64
60
  sh "git push origin main"
@@ -73,3 +69,25 @@ module Releaser
73
69
  File.write(path, yield(content))
74
70
  end
75
71
  end
72
+
73
+ desc "Check for stale <<next>> placeholders when VERSION is updated"
74
+ task :check_version_placeholders do
75
+ # Check if VERSION file was modified in the last commit
76
+ version_changed = %x(git diff HEAD~1 HEAD --name-only).split("\n").include?("VERSION")
77
+
78
+ if version_changed
79
+ puts "VERSION file was updated, checking for stale placeholders..."
80
+
81
+ # Check for <<next>> placeholders in config/default.yml
82
+ config_content = File.read("config/default.yml")
83
+ if config_content.match?(/['\"]?<<\s*next\s*>>['\"]?/i)
84
+ puts "\e[31mError: Found stale <<next>> placeholders in config/default.yml after VERSION update!\e[0m"
85
+ puts "\e[31mPlease run 'bundle exec rake prepare_release' to replace placeholders and commit the changes.\e[0m"
86
+ exit 1
87
+ else
88
+ puts "\e[32mNo stale placeholders found - all good!\e[0m"
89
+ end
90
+ else
91
+ puts "VERSION file was not updated in the last commit."
92
+ end
93
+ end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.10.3
1
+ 0.11.0
data/config/default.yml CHANGED
@@ -1,7 +1,21 @@
1
+ Sorbet/ForbidTHelpers:
2
+ Description: 'Forbid usage of T::Helpers via `extend` or `include`.'
3
+ Enabled: false
4
+ VersionAdded: '0.11.0'
5
+
6
+ Sorbet/ForbidTSig:
7
+ Description: 'Forbid usage of T::Sig via `extend` or `include`.'
8
+ Enabled: false
9
+ VersionAdded: '0.11.0'
10
+
1
11
  inherit_mode:
2
12
  merge:
3
13
  - Exclude
4
14
 
15
+ Sorbet:
16
+ DocumentationBaseURL: https://github.com/Shopify/rubocop-sorbet/blob/main/manual
17
+ DocumentationExtension: .md
18
+
5
19
  Sorbet/AllowIncompatibleOverride:
6
20
  Description: 'Disallows using `.override(allow_incompatible: true)`.'
7
21
  Enabled: true
@@ -82,7 +96,7 @@ Sorbet/EnforceSigilOrder:
82
96
  Sorbet/EnforceSignatures:
83
97
  Description: 'Ensures all methods have a valid signature.'
84
98
  Enabled: false
85
- AllowRBS: false
99
+ Style: sig
86
100
  VersionAdded: 0.3.4
87
101
 
88
102
  Sorbet/EnforceSingleSigil:
@@ -174,32 +188,39 @@ Sorbet/ForbidTStruct:
174
188
  Sorbet/ForbidTAbsurd:
175
189
  Description: 'Forbid usage of T.absurd.'
176
190
  Enabled: false
177
- VersionAdded: <<next>>
191
+ VersionAdded: 0.10.4
178
192
 
179
193
  Sorbet/ForbidTBind:
180
194
  Description: 'Forbid usage of T.bind.'
181
195
  Enabled: false
182
- VersionAdded: <<next>>
196
+ VersionAdded: 0.10.4
183
197
 
184
198
  Sorbet/ForbidTCast:
185
199
  Description: 'Forbid usage of T.cast.'
186
200
  Enabled: false
187
- VersionAdded: <<next>>
201
+ VersionAdded: 0.10.4
188
202
 
189
203
  Sorbet/ForbidTLet:
190
204
  Description: 'Forbid usage of T.let.'
191
205
  Enabled: false
192
- VersionAdded: <<next>>
206
+ VersionAdded: 0.10.4
193
207
 
194
208
  Sorbet/ForbidTMust:
195
209
  Description: 'Forbid usage of T.must.'
196
210
  Enabled: false
197
- VersionAdded: <<next>>
211
+ VersionAdded: 0.10.4
212
+
213
+ Sorbet/ForbidTAnyWithNil:
214
+ Description: 'Forbid usage of T.any(NilClass).'
215
+ Enabled: pending
216
+ VersionAdded: '0.11.0'
217
+ Safe: true
218
+ SafeAutoCorrect: true
198
219
 
199
220
  Sorbet/ForbidTTypeAlias:
200
221
  Description: 'Forbid usage of T.type_alias.'
201
222
  Enabled: false
202
- VersionAdded: <<next>>
223
+ VersionAdded: 0.10.4
203
224
 
204
225
  Sorbet/ForbidTUnsafe:
205
226
  Description: 'Forbid usage of T.unsafe.'
@@ -6,3 +6,8 @@
6
6
  removed:
7
7
  Sorbet/OneAncestorPerLine:
8
8
  reason: '`require_ancestor` now takes a block instead of arguments'
9
+
10
+ changed_parameters:
11
+ - cops: 'Sorbet/EnforceSignatures'
12
+ parameters: 'AllowRBS'
13
+ alternative: 'Style'
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sorbet
6
+ # Detect and autocorrect `T.any(..., NilClass, ...)` to `T.nilable(...)`
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # T.any(String, NilClass)
12
+ # T.any(NilClass, String)
13
+ # T.any(NilClass, Symbol, String)
14
+ #
15
+ # # good
16
+ # T.nilable(String)
17
+ # T.nilable(String)
18
+ # T.nilable(T.any(Symbol, String))
19
+ class ForbidTAnyWithNil < Base
20
+ extend AutoCorrector
21
+
22
+ MSG = "Use `T.nilable` instead of `T.any(..., NilClass, ...)`."
23
+ RESTRICT_ON_SEND = [:any].freeze
24
+
25
+ # @!method t_any_call?(node)
26
+ def_node_matcher :t_any_call?, <<~PATTERN
27
+ (send (const nil? :T) :any $...)
28
+ PATTERN
29
+
30
+ # @!method nil_const_node?(node)
31
+ def_node_matcher :nil_const_node?, <<~PATTERN
32
+ (const nil? :NilClass)
33
+ PATTERN
34
+
35
+ def on_send(node)
36
+ args = t_any_call?(node)
37
+ return unless args
38
+
39
+ nil_args, non_nil_args = args.partition { |a| nil_const_node?(a) }
40
+ return if nil_args.empty? || non_nil_args.empty?
41
+
42
+ add_offense(node) do |corrector|
43
+ replacement = build_replacement(non_nil_args)
44
+
45
+ corrector.replace(node, replacement)
46
+ end
47
+ end
48
+ alias_method :on_csend, :on_send
49
+
50
+ private
51
+
52
+ def build_replacement(non_nil_args)
53
+ sources = non_nil_args.map(&:source)
54
+ inner = sources.size == 1 ? sources.first : "T.any(#{sources.join(", ")})"
55
+ "T.nilable(#{inner})"
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sorbet
6
+ # Forbids `extend T::Helpers` and `include T::Helpers` in classes and modules.
7
+ #
8
+ # This is useful when using RBS or RBS-inline syntax for type signatures,
9
+ # where `T::Helpers` is not needed and including it is redundant.
10
+ #
11
+ # @example
12
+ #
13
+ # # bad
14
+ # class Example
15
+ # extend T::Helpers
16
+ # end
17
+ #
18
+ # # bad
19
+ # module Example
20
+ # include T::Helpers
21
+ # end
22
+ #
23
+ # # good
24
+ # class Example
25
+ # end
26
+ class ForbidTHelpers < RuboCop::Cop::Base
27
+ MSG = "Do not use `%<method>s T::Helpers`."
28
+ RESTRICT_ON_SEND = [:extend, :include].freeze
29
+
30
+ # @!method t_helpers?(node)
31
+ def_node_matcher :t_helpers?, <<~PATTERN
32
+ (send _ {:extend :include} (const (const {nil? | cbase} :T) :Helpers))
33
+ PATTERN
34
+
35
+ def on_send(node)
36
+ return unless t_helpers?(node)
37
+
38
+ add_offense(node, message: format(MSG, method: node.method_name))
39
+ end
40
+ alias_method :on_csend, :on_send
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sorbet
6
+ # Forbids `extend T::Sig` and `include T::Sig` in classes and modules.
7
+ #
8
+ # This is useful when using RBS or RBS-inline syntax for type signatures,
9
+ # where `T::Sig` is not needed and including it is redundant.
10
+ #
11
+ # @example
12
+ #
13
+ # # bad
14
+ # class Example
15
+ # extend T::Sig
16
+ # end
17
+ #
18
+ # # bad
19
+ # module Example
20
+ # include T::Sig
21
+ # end
22
+ #
23
+ # # good
24
+ # class Example
25
+ # end
26
+ class ForbidTSig < RuboCop::Cop::Base
27
+ MSG = "Do not use `%<method>s T::Sig`."
28
+ RESTRICT_ON_SEND = [:extend, :include].freeze
29
+
30
+ # @!method t_sig?(node)
31
+ def_node_matcher :t_sig?, <<~PATTERN
32
+ (send _ {:extend :include} (const (const {nil? | cbase} :T) :Sig))
33
+ PATTERN
34
+
35
+ def on_send(node)
36
+ return unless t_sig?(node)
37
+
38
+ add_offense(node, message: format(MSG, method: node.method_name))
39
+ end
40
+ alias_method :on_csend, :on_send
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "stringio"
4
-
5
3
  module RuboCop
6
4
  module Cop
7
5
  module Sorbet
@@ -24,16 +22,12 @@ module RuboCop
24
22
  #
25
23
  # * `ParameterTypePlaceholder`: placeholders used for parameter types (default: 'T.untyped')
26
24
  # * `ReturnTypePlaceholder`: placeholders used for return types (default: 'T.untyped')
25
+ # * `Style`: signature style to enforce - 'sig' for sig blocks, 'rbs' for RBS comments, 'both' to allow either (default: 'sig')
27
26
  class EnforceSignatures < ::RuboCop::Cop::Base
28
27
  extend AutoCorrector
29
28
  include SignatureHelp
30
29
 
31
- RBS_COMMENT_REGEX = /^#:.*$/
32
-
33
- def initialize(config = nil, options = nil)
34
- super(config, options)
35
- @last_sig_for_scope = {}
36
- end
30
+ VALID_STYLES = ["sig", "rbs", "both"].freeze
37
31
 
38
32
  # @!method accessor?(node)
39
33
  def_node_matcher(:accessor?, <<-PATTERN)
@@ -53,7 +47,13 @@ module RuboCop
53
47
  end
54
48
 
55
49
  def on_signature(node)
56
- @last_sig_for_scope[scope(node)] = node
50
+ sig_checker.on_signature(node, scope(node))
51
+ end
52
+
53
+ def on_new_investigation
54
+ super
55
+ @sig_checker = nil
56
+ @rbs_checker = nil
57
57
  end
58
58
 
59
59
  def scope(node)
@@ -67,48 +67,102 @@ module RuboCop
67
67
 
68
68
  def check_node(node)
69
69
  scope = self.scope(node)
70
- unless @last_sig_for_scope[scope] || has_rbs_comment?(node)
71
- add_offense(
72
- node,
73
- message: "Each method is required to have a signature.",
74
- ) do |corrector|
75
- autocorrect(corrector, node)
70
+ sig_node = sig_checker.signature_node(scope)
71
+ rbs_node = rbs_checker.signature_node(node)
72
+
73
+ case signature_style
74
+ when "rbs"
75
+ # RBS style - only RBS signatures allowed
76
+ if sig_node
77
+ add_offense(sig_node, message: "Use RBS signature comments rather than sig blocks.")
78
+ return
79
+ end
80
+
81
+ unless rbs_node
82
+ add_offense(node, message: "Each method is required to have an RBS signature.") do |corrector|
83
+ autocorrect_with_signature_type(corrector, node, "rbs")
84
+ end
85
+ end
86
+ when "both"
87
+ # Both styles allowed - require at least one
88
+ unless sig_node || rbs_node
89
+ add_offense(node, message: "Each method is required to have a signature.") do |corrector|
90
+ autocorrect_with_signature_type(corrector, node, "sig")
91
+ end
92
+ end
93
+ else # "sig" (default)
94
+ # Sig style - only sig signatures allowed
95
+ unless sig_node
96
+ add_offense(node, message: "Each method is required to have a sig block signature.") do |corrector|
97
+ autocorrect_with_signature_type(corrector, node, "sig")
98
+ end
76
99
  end
77
100
  end
78
- @last_sig_for_scope[scope] = nil
101
+ ensure
102
+ sig_checker.clear_signature(scope)
79
103
  end
80
104
 
81
- def has_rbs_comment?(node)
82
- return false unless cop_config["AllowRBS"] == true
83
-
84
- node = node.parent while RuboCop::AST::SendNode === node.parent
85
- return false if (comments = preceeding_comments(node)).empty?
105
+ def sig_checker
106
+ @sig_checker ||= SigSignatureChecker.new(processed_source)
107
+ end
86
108
 
87
- last_comment = comments.last
88
- return false if last_comment.loc.line + 1 < node.loc.line
109
+ def rbs_checker
110
+ @rbs_checker ||= RBSSignatureChecker.new(processed_source)
111
+ end
89
112
 
90
- comments.any? { |comment| RBS_COMMENT_REGEX.match?(comment.text) }
113
+ def autocorrect_with_signature_type(corrector, node, type)
114
+ suggest = create_signature_suggestion(node, type)
115
+ populate_signature_suggestion(suggest, node)
116
+ corrector.insert_before(node, suggest.to_autocorrect)
91
117
  end
92
118
 
93
- def preceeding_comments(node)
94
- processed_source.ast_with_comments[node].select { |comment| comment.loc.line < node.loc.line }
119
+ def create_signature_suggestion(node, type)
120
+ case type
121
+ when "rbs"
122
+ RBSSuggestion.new(node.loc.column)
123
+ else # "sig"
124
+ SigSuggestion.new(node.loc.column, param_type_placeholder, return_type_placeholder)
125
+ end
95
126
  end
96
127
 
97
- def autocorrect(corrector, node)
98
- suggest = SigSuggestion.new(node.loc.column, param_type_placeholder, return_type_placeholder)
128
+ def populate_signature_suggestion(suggest, node)
129
+ if node.any_def_type?
130
+ populate_method_definition_suggestion(suggest, node)
131
+ elsif accessor?(node)
132
+ populate_accessor_suggestion(suggest, node)
133
+ end
134
+ end
99
135
 
100
- if node.is_a?(RuboCop::AST::DefNode) # def something
101
- node.arguments.each do |arg|
136
+ def populate_method_definition_suggestion(suggest, node)
137
+ node.arguments.each do |arg|
138
+ if arg.blockarg_type? && suggest.respond_to?(:has_block=)
139
+ suggest.has_block = true
140
+ else
102
141
  suggest.params << arg.children.first
103
142
  end
104
- elsif accessor?(node) # attr reader, writer, accessor
105
- method = node.children[1]
106
- symbol = node.children[2]
107
- suggest.params << symbol.value if symbol && (method == :attr_writer || method == :attr_accessor)
108
- suggest.returns = "void" if method == :attr_writer
109
143
  end
144
+ end
110
145
 
111
- corrector.insert_before(node, suggest.to_autocorrect)
146
+ def populate_accessor_suggestion(suggest, node)
147
+ method = node.children[1]
148
+ symbol = node.children[2]
149
+
150
+ add_accessor_parameter_if_needed(suggest, symbol, method)
151
+ set_void_return_for_writer(suggest, method)
152
+ end
153
+
154
+ def add_accessor_parameter_if_needed(suggest, symbol, method)
155
+ return unless symbol && writer_or_accessor?(method)
156
+
157
+ suggest.params << symbol.value
158
+ end
159
+
160
+ def set_void_return_for_writer(suggest, method)
161
+ suggest.returns = "void" if method == :attr_writer
162
+ end
163
+
164
+ def writer_or_accessor?(method)
165
+ method == :attr_writer || method == :attr_accessor
112
166
  end
113
167
 
114
168
  def param_type_placeholder
@@ -119,6 +173,80 @@ module RuboCop
119
173
  cop_config["ReturnTypePlaceholder"] || "T.untyped"
120
174
  end
121
175
 
176
+ def allow_rbs?
177
+ cop_config["AllowRBS"] == true
178
+ end
179
+
180
+ def signature_style
181
+ config_value = cop_config["Style"]
182
+ if config_value
183
+ unless VALID_STYLES.include?(config_value)
184
+ raise ArgumentError, "Invalid Style option: '#{config_value}'. Valid options are: #{VALID_STYLES.join(", ")}"
185
+ end
186
+
187
+ return config_value
188
+ end
189
+
190
+ return "both" if allow_rbs?
191
+
192
+ "sig"
193
+ end
194
+
195
+ class SignatureChecker
196
+ def initialize(processed_source)
197
+ @processed_source = processed_source
198
+ end
199
+
200
+ protected
201
+
202
+ attr_reader :processed_source
203
+
204
+ def preceding_comments(node)
205
+ processed_source.ast_with_comments[node].select { |comment| comment.loc.line < node.loc.line }
206
+ end
207
+ end
208
+
209
+ class RBSSignatureChecker < SignatureChecker
210
+ RBS_COMMENT_REGEX = /^#\s*:.*$/
211
+
212
+ def signature_node(node)
213
+ node = find_non_send_ancestor(node)
214
+ comments = preceding_comments(node)
215
+ return if comments.empty?
216
+
217
+ last_comment = comments.last
218
+ return if last_comment.loc.line + 1 < node.loc.line
219
+
220
+ comments.find { |comment| RBS_COMMENT_REGEX.match?(comment.text) }
221
+ end
222
+
223
+ private
224
+
225
+ def find_non_send_ancestor(node)
226
+ node = node.parent while node.parent&.send_type?
227
+ node
228
+ end
229
+ end
230
+
231
+ class SigSignatureChecker < SignatureChecker
232
+ def initialize(processed_source)
233
+ super(processed_source)
234
+ @last_sig_for_scope = {}
235
+ end
236
+
237
+ def signature_node(scope)
238
+ @last_sig_for_scope[scope]
239
+ end
240
+
241
+ def on_signature(node, scope)
242
+ @last_sig_for_scope[scope] = node
243
+ end
244
+
245
+ def clear_signature(scope)
246
+ @last_sig_for_scope[scope] = nil
247
+ end
248
+ end
249
+
122
250
  class SigSuggestion
123
251
  attr_accessor :params, :returns
124
252
 
@@ -131,34 +259,59 @@ module RuboCop
131
259
  end
132
260
 
133
261
  def to_autocorrect
134
- out = StringIO.new
135
- out << "sig { "
136
- out << generate_params
137
- out << generate_return
138
- out << " }\n"
139
- out << " " * @indent # preserve indent for the next line
140
- out.string
262
+ "sig { #{generate_params}#{generate_return} }\n#{" " * @indent}"
141
263
  end
142
264
 
143
265
  private
144
266
 
145
267
  def generate_params
146
- return if @params.empty?
268
+ return "" if @params.empty?
147
269
 
148
- out = StringIO.new
149
- out << "params("
150
- out << @params.map do |param|
151
- "#{param}: #{@param_placeholder}"
152
- end.join(", ")
153
- out << ")."
154
- out.string
270
+ param_list = @params.map { |param| "#{param}: #{@param_placeholder}" }.join(", ")
271
+ "params(#{param_list})."
155
272
  end
156
273
 
157
274
  def generate_return
158
- return "returns(#{@return_placeholder})" if @returns.nil?
159
- return @returns if @returns == "void"
275
+ if @returns.nil?
276
+ "returns(#{@return_placeholder})"
277
+ elsif @returns == "void"
278
+ "void"
279
+ else
280
+ "returns(#{@returns})"
281
+ end
282
+ end
283
+ end
284
+
285
+ class RBSSuggestion
286
+ attr_accessor :params, :returns, :has_block
287
+
288
+ def initialize(indent)
289
+ @params = []
290
+ @returns = nil
291
+ @has_block = false
292
+ @indent = indent
293
+ end
294
+
295
+ def to_autocorrect
296
+ "#: #{generate_signature}\n#{" " * @indent}"
297
+ end
298
+
299
+ private
300
+
301
+ def generate_signature
302
+ param_types = @params.map { "untyped" }.join(", ")
303
+ return_type = @returns || "untyped"
304
+
305
+ signature = if @params.empty?
306
+ "()"
307
+ else
308
+ "(#{param_types})"
309
+ end
310
+
311
+ signature += " { (?) -> untyped }" if @has_block
312
+ signature += " -> #{return_type}"
160
313
 
161
- "returns(#{@returns})"
314
+ signature
162
315
  end
163
316
  end
164
317
  end
@@ -10,6 +10,7 @@ require_relative "sorbet/constants_from_strings"
10
10
  require_relative "sorbet/forbid_superclass_const_literal"
11
11
  require_relative "sorbet/forbid_include_const_literal"
12
12
  require_relative "sorbet/forbid_mixes_in_class_methods"
13
+ require_relative "sorbet/forbid_t_any_with_nil"
13
14
  require_relative "sorbet/forbid_type_aliased_shapes"
14
15
  require_relative "sorbet/forbid_untyped_struct_props"
15
16
  require_relative "sorbet/implicit_conversion_method"
@@ -18,8 +19,10 @@ require_relative "sorbet/forbid_t_absurd"
18
19
  require_relative "sorbet/forbid_t_bind"
19
20
  require_relative "sorbet/forbid_t_cast"
20
21
  require_relative "sorbet/forbid_t_enum"
22
+ require_relative "sorbet/forbid_t_helpers"
21
23
  require_relative "sorbet/forbid_t_let"
22
24
  require_relative "sorbet/forbid_t_must"
25
+ require_relative "sorbet/forbid_t_sig"
23
26
  require_relative "sorbet/forbid_t_struct"
24
27
  require_relative "sorbet/forbid_t_type_alias"
25
28
  require_relative "sorbet/forbid_t_unsafe"
@@ -1,10 +1,5 @@
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
-
8
3
  require "rubocop"
9
4
  require "lint_roller"
10
5
  require "pathname"
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Sorbet
5
- VERSION = "0.10.4"
5
+ VERSION = File.read(File.expand_path("../../../VERSION", __dir__)).strip
6
6
  end
7
7
  end
@@ -6,9 +6,4 @@ require_relative "rubocop/sorbet"
6
6
  require_relative "rubocop/sorbet/version"
7
7
  require_relative "rubocop/sorbet/plugin"
8
8
 
9
- unless defined?(RuboCop::Sorbet::Plugin)
10
- require_relative "rubocop/sorbet/inject"
11
- RuboCop::Sorbet::Inject.defaults!
12
- end
13
-
14
9
  require_relative "rubocop/cop/sorbet_cops"
data/manual/cops.md CHANGED
@@ -28,11 +28,14 @@ In the following section you find all available cops:
28
28
  * [Sorbet/ForbidSigWithoutRuntime](cops_sorbet.md#sorbetforbidsigwithoutruntime)
29
29
  * [Sorbet/ForbidSuperclassConstLiteral](cops_sorbet.md#sorbetforbidsuperclassconstliteral)
30
30
  * [Sorbet/ForbidTAbsurd](cops_sorbet.md#sorbetforbidtabsurd)
31
+ * [Sorbet/ForbidTAnyWithNil](cops_sorbet.md#sorbetforbidtanywithnil)
31
32
  * [Sorbet/ForbidTBind](cops_sorbet.md#sorbetforbidtbind)
32
33
  * [Sorbet/ForbidTCast](cops_sorbet.md#sorbetforbidtcast)
33
34
  * [Sorbet/ForbidTEnum](cops_sorbet.md#sorbetforbidtenum)
35
+ * [Sorbet/ForbidTHelpers](cops_sorbet.md#sorbetforbidthelpers)
34
36
  * [Sorbet/ForbidTLet](cops_sorbet.md#sorbetforbidtlet)
35
37
  * [Sorbet/ForbidTMust](cops_sorbet.md#sorbetforbidtmust)
38
+ * [Sorbet/ForbidTSig](cops_sorbet.md#sorbetforbidtsig)
36
39
  * [Sorbet/ForbidTStruct](cops_sorbet.md#sorbetforbidtstruct)
37
40
  * [Sorbet/ForbidTTypeAlias](cops_sorbet.md#sorbetforbidttypealias)
38
41
  * [Sorbet/ForbidTUnsafe](cops_sorbet.md#sorbetforbidtunsafe)
@@ -331,12 +331,13 @@ You can configure the placeholders used by changing the following options:
331
331
 
332
332
  * `ParameterTypePlaceholder`: placeholders used for parameter types (default: 'T.untyped')
333
333
  * `ReturnTypePlaceholder`: placeholders used for return types (default: 'T.untyped')
334
+ * `Style`: signature style to enforce - 'sig' for sig blocks, 'rbs' for RBS comments, 'both' to allow either (default: 'sig')
334
335
 
335
336
  ### Configurable attributes
336
337
 
337
338
  Name | Default value | Configurable values
338
339
  --- | --- | ---
339
- AllowRBS | `false` | Boolean
340
+ Style | `sig` | String
340
341
 
341
342
  ## Sorbet/EnforceSingleSigil
342
343
 
@@ -636,7 +637,7 @@ Exclude | `db/migrate/*.rb` | Array
636
637
 
637
638
  Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
638
639
  --- | --- | --- | --- | ---
639
- Disabled | Yes | No | <<next>> | -
640
+ Disabled | Yes | No | 0.10.4 | -
640
641
 
641
642
  Disallows using `T.absurd` anywhere.
642
643
 
@@ -650,11 +651,33 @@ T.absurd(foo)
650
651
  x #: absurd
651
652
  ```
652
653
 
654
+ ## Sorbet/ForbidTAnyWithNil
655
+
656
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
657
+ --- | --- | --- | --- | ---
658
+ Enabled | Yes | Yes | 0.11.0 | -
659
+
660
+ Detect and autocorrect `T.any(..., NilClass, ...)` to `T.nilable(...)`
661
+
662
+ ### Examples
663
+
664
+ ```ruby
665
+ # bad
666
+ T.any(String, NilClass)
667
+ T.any(NilClass, String)
668
+ T.any(NilClass, Symbol, String)
669
+
670
+ # good
671
+ T.nilable(String)
672
+ T.nilable(String)
673
+ T.nilable(T.any(Symbol, String))
674
+ ```
675
+
653
676
  ## Sorbet/ForbidTBind
654
677
 
655
678
  Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
656
679
  --- | --- | --- | --- | ---
657
- Disabled | Yes | No | <<next>> | -
680
+ Disabled | Yes | No | 0.10.4 | -
658
681
 
659
682
  Disallows using `T.bind` anywhere.
660
683
 
@@ -672,7 +695,7 @@ T.bind(self, Integer)
672
695
 
673
696
  Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
674
697
  --- | --- | --- | --- | ---
675
- Disabled | Yes | No | <<next>> | -
698
+ Disabled | Yes | No | 0.10.4 | -
676
699
 
677
700
  Disallows using `T.cast` anywhere.
678
701
 
@@ -713,11 +736,40 @@ class MyEnum
713
736
  end
714
737
  ```
715
738
 
739
+ ## Sorbet/ForbidTHelpers
740
+
741
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
742
+ --- | --- | --- | --- | ---
743
+ Disabled | Yes | No | 0.11.0 | -
744
+
745
+ Forbids `extend T::Helpers` and `include T::Helpers` in classes and modules.
746
+
747
+ This is useful when using RBS or RBS-inline syntax for type signatures,
748
+ where `T::Helpers` is not needed and including it is redundant.
749
+
750
+ ### Examples
751
+
752
+ ```ruby
753
+ # bad
754
+ class Example
755
+ extend T::Helpers
756
+ end
757
+
758
+ # bad
759
+ module Example
760
+ include T::Helpers
761
+ end
762
+
763
+ # good
764
+ class Example
765
+ end
766
+ ```
767
+
716
768
  ## Sorbet/ForbidTLet
717
769
 
718
770
  Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
719
771
  --- | --- | --- | --- | ---
720
- Disabled | Yes | No | <<next>> | -
772
+ Disabled | Yes | No | 0.10.4 | -
721
773
 
722
774
  Disallows using `T.let` anywhere.
723
775
 
@@ -735,7 +787,7 @@ foo #: Integer
735
787
 
736
788
  Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
737
789
  --- | --- | --- | --- | ---
738
- Disabled | Yes | No | <<next>> | -
790
+ Disabled | Yes | No | 0.10.4 | -
739
791
 
740
792
  Disallows using `T.must` anywhere.
741
793
 
@@ -749,6 +801,35 @@ T.must(foo)
749
801
  foo #: as !nil
750
802
  ```
751
803
 
804
+ ## Sorbet/ForbidTSig
805
+
806
+ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
807
+ --- | --- | --- | --- | ---
808
+ Disabled | Yes | No | 0.11.0 | -
809
+
810
+ Forbids `extend T::Sig` and `include T::Sig` in classes and modules.
811
+
812
+ This is useful when using RBS or RBS-inline syntax for type signatures,
813
+ where `T::Sig` is not needed and including it is redundant.
814
+
815
+ ### Examples
816
+
817
+ ```ruby
818
+ # bad
819
+ class Example
820
+ extend T::Sig
821
+ end
822
+
823
+ # bad
824
+ module Example
825
+ include T::Sig
826
+ end
827
+
828
+ # good
829
+ class Example
830
+ end
831
+ ```
832
+
752
833
  ## Sorbet/ForbidTStruct
753
834
 
754
835
  Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
@@ -792,7 +873,7 @@ end
792
873
 
793
874
  Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
794
875
  --- | --- | --- | --- | ---
795
- Disabled | Yes | No | <<next>> | -
876
+ Disabled | Yes | No | 0.10.4 | -
796
877
 
797
878
  Disallows using `T.type_alias` anywhere.
798
879
 
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.10.4
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ufuk Kayserilioglu
@@ -50,6 +50,7 @@ files:
50
50
  - ".github/CODEOWNERS"
51
51
  - ".github/dependabot.yml"
52
52
  - ".github/release.yml"
53
+ - ".github/workflows/check-version-placeholders.yml"
53
54
  - ".github/workflows/ci.yml"
54
55
  - ".github/workflows/cla.yml"
55
56
  - ".github/workflows/dependabot_automerge.yml"
@@ -85,11 +86,14 @@ files:
85
86
  - lib/rubocop/cop/sorbet/forbid_mixes_in_class_methods.rb
86
87
  - lib/rubocop/cop/sorbet/forbid_superclass_const_literal.rb
87
88
  - lib/rubocop/cop/sorbet/forbid_t_absurd.rb
89
+ - lib/rubocop/cop/sorbet/forbid_t_any_with_nil.rb
88
90
  - lib/rubocop/cop/sorbet/forbid_t_bind.rb
89
91
  - lib/rubocop/cop/sorbet/forbid_t_cast.rb
90
92
  - lib/rubocop/cop/sorbet/forbid_t_enum.rb
93
+ - lib/rubocop/cop/sorbet/forbid_t_helpers.rb
91
94
  - lib/rubocop/cop/sorbet/forbid_t_let.rb
92
95
  - lib/rubocop/cop/sorbet/forbid_t_must.rb
96
+ - lib/rubocop/cop/sorbet/forbid_t_sig.rb
93
97
  - lib/rubocop/cop/sorbet/forbid_t_struct.rb
94
98
  - lib/rubocop/cop/sorbet/forbid_t_type_alias.rb
95
99
  - lib/rubocop/cop/sorbet/forbid_t_unsafe.rb
@@ -135,7 +139,6 @@ files:
135
139
  - lib/rubocop/cop/sorbet/type_alias_name.rb
136
140
  - lib/rubocop/cop/sorbet_cops.rb
137
141
  - lib/rubocop/sorbet.rb
138
- - lib/rubocop/sorbet/inject.rb
139
142
  - lib/rubocop/sorbet/plugin.rb
140
143
  - lib/rubocop/sorbet/version.rb
141
144
  - manual/cops.md
@@ -166,7 +169,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
166
169
  - !ruby/object:Gem::Version
167
170
  version: '0'
168
171
  requirements: []
169
- rubygems_version: 3.6.9
172
+ rubygems_version: 3.7.2
170
173
  specification_version: 4
171
174
  summary: Automatic Sorbet code style checking tool.
172
175
  test_files: []
@@ -1,30 +0,0 @@
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
- class << self
11
- def defaults!
12
- path = CONFIG_DEFAULT.to_s
13
- hash = ConfigLoader.send(:load_yaml_configuration, path)
14
- if Gem::Version.new(RuboCop::Version::STRING) >= Gem::Version.new("1.66")
15
- # We use markdown for cop documentation. Unconditionally setting
16
- # the base url will produce incorrect urls on older RuboCop versions,
17
- # so we do that for fully supported version only here.
18
- hash["Sorbet"] ||= {}
19
- hash["Sorbet"]["DocumentationBaseURL"] = "https://github.com/Shopify/rubocop-sorbet/blob/main/manual"
20
- hash["Sorbet"]["DocumentationExtension"] = ".md"
21
- end
22
- config = Config.new(hash, path).tap(&:make_excludes_absolute)
23
- puts "configuration from #{path}" if ConfigLoader.debug?
24
- config = ConfigLoader.merge_with_default(config, path)
25
- ConfigLoader.instance_variable_set(:@default_configuration, config)
26
- end
27
- end
28
- end
29
- end
30
- end