rubocop-sorbet 0.4.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +26 -0
- data/.gitignore +1 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +5 -3
- data/README.md +7 -0
- data/bin/rubocop +29 -0
- data/config/default.yml +53 -3
- data/dev.yml +1 -1
- data/lib/rubocop/cop/sorbet/binding_constants_without_type_alias.rb +14 -1
- data/lib/rubocop/cop/sorbet/callback_conditionals_binding.rb +138 -0
- data/lib/rubocop/cop/sorbet/forbid_include_const_literal.rb +0 -40
- data/lib/rubocop/cop/sorbet/forbid_superclass_const_literal.rb +0 -17
- data/lib/rubocop/cop/sorbet/forbid_t_unsafe.rb +26 -0
- data/lib/rubocop/cop/sorbet/one_ancestor_per_line.rb +75 -0
- data/lib/rubocop/cop/sorbet/rbi/forbid_extend_t_sig_helpers_in_shims.rb +53 -0
- data/lib/rubocop/cop/sorbet/rbi/forbid_rbi_outside_of_sorbet_dir.rb +31 -0
- data/lib/rubocop/cop/sorbet/rbi/single_line_rbi_class_module_definitions.rb +46 -0
- data/lib/rubocop/cop/sorbet/sigils/enforce_sigil_order.rb +10 -0
- data/lib/rubocop/cop/sorbet/sigils/valid_sigil.rb +4 -2
- data/lib/rubocop/cop/sorbet/signatures/enforce_signatures.rb +19 -10
- data/lib/rubocop/cop/sorbet/signatures/signature_build_order.rb +18 -9
- data/lib/rubocop/cop/sorbet_cops.rb +7 -0
- data/lib/rubocop/sorbet/version.rb +1 -1
- data/manual/cops.md +6 -0
- data/manual/cops_sorbet.md +208 -4
- data/service.yml +1 -4
- metadata +11 -5
- data/.shopify-build/VERSION +0 -1
- data/.shopify-build/rubocop-sorbet.yml +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 309687789d8de94ff0cc8c0d64361cd227ab332ffdd16ab0248d588639dfcd80
|
4
|
+
data.tar.gz: ca39d50843f2e577ed0a0328c9707e21af175daacd8c92b9b4403bc2195c4cff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f774385eec35af24755af1cc5e7f5de554bedf0f6a475e3235879aee61a2c68a818ea87ca4663daa17bb807c862d911264284d1f3270e852166c3e84cde32e7d
|
7
|
+
data.tar.gz: d6d91114e9c70bc64fa5adf6679a689c12263caa352f7f38b6d96d36609a81e90bc18f369e42b1605259262626114aac6fd5d50f7c91bced3e2f24e2f606fbc4
|
@@ -0,0 +1,26 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
env:
|
6
|
+
SRB_SKIP_GEM_RBIS: true
|
7
|
+
|
8
|
+
jobs:
|
9
|
+
build:
|
10
|
+
runs-on: ubuntu-latest
|
11
|
+
strategy:
|
12
|
+
fail-fast: false
|
13
|
+
matrix:
|
14
|
+
ruby: [ 2.5, 2.6, 2.7, 3.0 ]
|
15
|
+
name: Test Ruby ${{ matrix.ruby }}
|
16
|
+
steps:
|
17
|
+
- uses: actions/checkout@v2
|
18
|
+
- name: Set up Ruby
|
19
|
+
uses: ruby/setup-ruby@v1
|
20
|
+
with:
|
21
|
+
ruby-version: ${{ matrix.ruby }}
|
22
|
+
bundler-cache: true
|
23
|
+
- name: Lint Ruby files
|
24
|
+
run: bin/rubocop
|
25
|
+
- name: Run tests
|
26
|
+
run: bin/rspec
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rubocop-sorbet (0.
|
4
|
+
rubocop-sorbet (0.6.2)
|
5
5
|
rubocop
|
6
6
|
|
7
7
|
GEM
|
@@ -12,6 +12,7 @@ GEM
|
|
12
12
|
ice_nine (~> 0.11.0)
|
13
13
|
memoizable (~> 0.4.0)
|
14
14
|
ast (2.4.0)
|
15
|
+
byebug (11.1.3)
|
15
16
|
concord (0.1.5)
|
16
17
|
adamantium (~> 0.2.0)
|
17
18
|
equalizer (~> 0.0.9)
|
@@ -27,7 +28,7 @@ GEM
|
|
27
28
|
rainbow (3.0.0)
|
28
29
|
rake (13.0.1)
|
29
30
|
regexp_parser (1.7.0)
|
30
|
-
rexml (3.2.
|
31
|
+
rexml (3.2.5)
|
31
32
|
rspec (3.8.0)
|
32
33
|
rspec-core (~> 3.8.0)
|
33
34
|
rspec-expectations (~> 3.8.0)
|
@@ -71,6 +72,7 @@ PLATFORMS
|
|
71
72
|
ruby
|
72
73
|
|
73
74
|
DEPENDENCIES
|
75
|
+
byebug
|
74
76
|
rake (>= 12.3.3)
|
75
77
|
rspec
|
76
78
|
rubocop-shopify
|
@@ -79,4 +81,4 @@ DEPENDENCIES
|
|
79
81
|
yard (~> 0.9)
|
80
82
|
|
81
83
|
BUNDLED WITH
|
82
|
-
|
84
|
+
2.2.16
|
data/README.md
CHANGED
@@ -17,6 +17,13 @@ or, if you use `Bundler`, add this line your application's `Gemfile`:
|
|
17
17
|
gem 'rubocop-sorbet', require: false
|
18
18
|
```
|
19
19
|
|
20
|
+
Note: in order to use the [Sorbet/SignatureBuildOrder](https://github.com/Shopify/rubocop-sorbet/blob/master/manual/cops_sorbet.md#sorbetsignaturebuildorder) cop autocorrect feature, it is necessary
|
21
|
+
to install `unparser` in addition to `rubocop-sorbet`.
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
gem "unparser", require: false
|
25
|
+
```
|
26
|
+
|
20
27
|
## Usage
|
21
28
|
|
22
29
|
You need to tell RuboCop to load the Sorbet extension. There are three ways to do this:
|
data/bin/rubocop
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rubocop' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load(Gem.bin_path("rubocop", "rubocop"))
|
data/config/default.yml
CHANGED
@@ -14,6 +14,11 @@ Sorbet/BindingConstantWithoutTypeAlias:
|
|
14
14
|
Enabled: true
|
15
15
|
VersionAdded: 0.2.0
|
16
16
|
|
17
|
+
Sorbet/CallbackConditionalsBinding:
|
18
|
+
Description: 'Ensures callback conditionals are bound to the right type.'
|
19
|
+
Enabled: true
|
20
|
+
VersionAdded: 0.7.0
|
21
|
+
|
17
22
|
Sorbet/CheckedTrueInSignature:
|
18
23
|
Description: 'Disallows the usage of `checked(true)` in signatures.'
|
19
24
|
Enabled: true
|
@@ -42,7 +47,7 @@ Sorbet/FalseSigil:
|
|
42
47
|
Description: 'All files must be at least at strictness `false`.'
|
43
48
|
Enabled: true
|
44
49
|
VersionAdded: 0.3.3
|
45
|
-
SuggestedStrictness:
|
50
|
+
SuggestedStrictness: "false"
|
46
51
|
Include:
|
47
52
|
- "**/*.rb"
|
48
53
|
- "**/*.rbi"
|
@@ -53,15 +58,39 @@ Sorbet/FalseSigil:
|
|
53
58
|
- db/**/*.rb
|
54
59
|
- script/**/*
|
55
60
|
|
61
|
+
Sorbet/ForbidExtendTSigHelpersInShims:
|
62
|
+
Description: 'Forbid the use of `extend T::Sig` and `extend T::Helpers` in RBI shims'
|
63
|
+
Enabled: true
|
64
|
+
VersionAdded: '0.6.0'
|
65
|
+
Include:
|
66
|
+
- "**/*.rbi"
|
67
|
+
|
68
|
+
Sorbet/ForbidRBIOutsideOfSorbetDir:
|
69
|
+
Description: 'Forbids RBI files outside of the sorbet/ directory.'
|
70
|
+
Enabled: true
|
71
|
+
VersionAdded: 0.6.1
|
72
|
+
Include:
|
73
|
+
- "**/*.rbi"
|
74
|
+
|
56
75
|
Sorbet/ForbidIncludeConstLiteral:
|
57
76
|
Description: 'Forbids include of non-literal constants.'
|
58
|
-
Enabled:
|
77
|
+
Enabled: false
|
59
78
|
VersionAdded: 0.2.0
|
79
|
+
VersionChanged: 0.5.0
|
60
80
|
|
61
81
|
Sorbet/ForbidSuperclassConstLiteral:
|
62
82
|
Description: 'Forbid superclasses which are non-literal constants.'
|
63
|
-
Enabled:
|
83
|
+
Enabled: false
|
64
84
|
VersionAdded: 0.2.0
|
85
|
+
VersionChanged: 0.6.1
|
86
|
+
Exclude:
|
87
|
+
- db/migrate/*.rb
|
88
|
+
|
89
|
+
Sorbet/ForbidTUnsafe:
|
90
|
+
Description: 'Forbid usage of T.unsafe.'
|
91
|
+
Enabled: false
|
92
|
+
VersionAdded: 0.7.0
|
93
|
+
VersionChanged: 0.7.0
|
65
94
|
|
66
95
|
Sorbet/ForbidUntypedStructProps:
|
67
96
|
Description: >-
|
@@ -73,11 +102,14 @@ Sorbet/ForbidUntypedStructProps:
|
|
73
102
|
Sorbet/HasSigil:
|
74
103
|
Description: 'Makes the Sorbet typed sigil mandatory in all files.'
|
75
104
|
Enabled: false
|
105
|
+
SuggestedStrictness: "false"
|
106
|
+
MinimumStrictness: "false"
|
76
107
|
VersionAdded: 0.3.3
|
77
108
|
|
78
109
|
Sorbet/IgnoreSigil:
|
79
110
|
Description: 'All files must be at least at strictness `ignore`.'
|
80
111
|
Enabled: false
|
112
|
+
SuggestedStrictness: "ignore"
|
81
113
|
VersionAdded: 0.3.3
|
82
114
|
|
83
115
|
Sorbet/KeywordArgumentOrdering:
|
@@ -90,6 +122,11 @@ Sorbet/KeywordArgumentOrdering:
|
|
90
122
|
Enabled: true
|
91
123
|
VersionAdded: 0.2.0
|
92
124
|
|
125
|
+
Sorbet/OneAncestorPerLine:
|
126
|
+
Description: 'Enforces one ancestor per call to requires_ancestor'
|
127
|
+
Enabled: false
|
128
|
+
VersionAdded: '0.6.0'
|
129
|
+
|
93
130
|
Sorbet/ParametersOrderingInSignature:
|
94
131
|
Description: 'Enforces same parameter order between a method and its signature.'
|
95
132
|
Enabled: true
|
@@ -105,22 +142,35 @@ Sorbet/SignatureBuildOrder:
|
|
105
142
|
Enabled: true
|
106
143
|
VersionAdded: 0.3.0
|
107
144
|
|
145
|
+
Sorbet/SingleLineRbiClassModuleDefinitions:
|
146
|
+
Description: 'Empty class and module definitions in RBI must be on a single line.'
|
147
|
+
Enabled: false
|
148
|
+
VersionAdded: '0.6.0'
|
149
|
+
Include:
|
150
|
+
- "**/*.rbi"
|
151
|
+
|
108
152
|
Sorbet/StrictSigil:
|
109
153
|
Description: 'All files must be at least at strictness `strict`.'
|
110
154
|
Enabled: false
|
155
|
+
SuggestedStrictness: "strict"
|
111
156
|
VersionAdded: 0.3.3
|
112
157
|
|
113
158
|
Sorbet/StrongSigil:
|
114
159
|
Description: 'All files must be at least at strictness `strong`.'
|
115
160
|
Enabled: false
|
161
|
+
SuggestedStrictness: "strong"
|
116
162
|
VersionAdded: 0.3.3
|
117
163
|
|
118
164
|
Sorbet/TrueSigil:
|
119
165
|
Description: 'All files must be at least at strictness `true`.'
|
120
166
|
Enabled: false
|
167
|
+
SuggestedStrictness: "true"
|
121
168
|
VersionAdded: 0.3.3
|
122
169
|
|
123
170
|
Sorbet/ValidSigil:
|
124
171
|
Description: 'All files must have a valid sigil.'
|
125
172
|
Enabled: true
|
173
|
+
RequireSigilOnAllFiles: false
|
174
|
+
SuggestedStrictness: "false"
|
175
|
+
MinimumStrictness: "false"
|
126
176
|
VersionAdded: 0.3.3
|
data/dev.yml
CHANGED
@@ -17,7 +17,7 @@ module RuboCop
|
|
17
17
|
# FooOrBar = T.type_alias { T.any(Foo, Bar) }
|
18
18
|
class BindingConstantWithoutTypeAlias < RuboCop::Cop::Cop
|
19
19
|
def_node_matcher(:binding_unaliased_type?, <<-PATTERN)
|
20
|
-
(casgn _ _ [#not_nil? #not_t_let? #not_generic_parameter_decl? #method_needing_aliasing_on_t?])
|
20
|
+
(casgn _ _ [#not_nil? #not_t_let? #not_dynamic_type_creation_with_block? #not_generic_parameter_decl? #method_needing_aliasing_on_t?])
|
21
21
|
PATTERN
|
22
22
|
|
23
23
|
def_node_matcher(:using_type_alias?, <<-PATTERN)
|
@@ -48,6 +48,15 @@ module RuboCop
|
|
48
48
|
)
|
49
49
|
PATTERN
|
50
50
|
|
51
|
+
def_node_matcher(:dynamic_type_creation_with_block?, <<-PATTERN)
|
52
|
+
(block
|
53
|
+
(send
|
54
|
+
const :new ...)
|
55
|
+
_
|
56
|
+
_
|
57
|
+
)
|
58
|
+
PATTERN
|
59
|
+
|
51
60
|
def_node_matcher(:generic_parameter_decl?, <<-PATTERN)
|
52
61
|
(
|
53
62
|
send nil? {:type_template :type_member} ...
|
@@ -67,6 +76,10 @@ module RuboCop
|
|
67
76
|
!t_let?(node)
|
68
77
|
end
|
69
78
|
|
79
|
+
def not_dynamic_type_creation_with_block?(node)
|
80
|
+
!dynamic_type_creation_with_block?(node)
|
81
|
+
end
|
82
|
+
|
70
83
|
def not_generic_parameter_decl?(node)
|
71
84
|
!generic_parameter_decl?(node)
|
72
85
|
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Sorbet
|
6
|
+
# This cop ensures that callback conditionals are bound to the right type
|
7
|
+
# so that they are type checked properly.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
#
|
11
|
+
# # bad
|
12
|
+
# class Post < ApplicationRecord
|
13
|
+
# before_create :do_it, if: -> { should_do_it? }
|
14
|
+
#
|
15
|
+
# def should_do_it?
|
16
|
+
# true
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# class Post < ApplicationRecord
|
22
|
+
# before_create :do_it, if: -> { T.bind(self, Post).should_do_it? }
|
23
|
+
#
|
24
|
+
# def should_do_it?
|
25
|
+
# true
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
class CallbackConditionalsBinding < RuboCop::Cop::Cop
|
29
|
+
CALLBACKS = %i(
|
30
|
+
validate
|
31
|
+
validates
|
32
|
+
validates_with
|
33
|
+
before_validation
|
34
|
+
around_validation
|
35
|
+
|
36
|
+
before_create
|
37
|
+
before_save
|
38
|
+
before_destroy
|
39
|
+
before_update
|
40
|
+
|
41
|
+
after_create
|
42
|
+
after_save
|
43
|
+
after_destroy
|
44
|
+
after_update
|
45
|
+
after_touch
|
46
|
+
after_initialize
|
47
|
+
after_find
|
48
|
+
|
49
|
+
around_create
|
50
|
+
around_save
|
51
|
+
around_destroy
|
52
|
+
around_update
|
53
|
+
|
54
|
+
before_commit
|
55
|
+
|
56
|
+
after_commit
|
57
|
+
after_create_commit
|
58
|
+
after_destroy_commit
|
59
|
+
after_rollback
|
60
|
+
after_save_commit
|
61
|
+
after_update_commit
|
62
|
+
|
63
|
+
before_action
|
64
|
+
prepend_before_action
|
65
|
+
append_before_action
|
66
|
+
|
67
|
+
around_action
|
68
|
+
prepend_around_action
|
69
|
+
append_around_action
|
70
|
+
|
71
|
+
after_action
|
72
|
+
prepend_after_action
|
73
|
+
append_after_action
|
74
|
+
).freeze
|
75
|
+
|
76
|
+
def autocorrect(node)
|
77
|
+
lambda do |corrector|
|
78
|
+
options = node.each_child_node.find(&:hash_type?)
|
79
|
+
|
80
|
+
conditional = nil
|
81
|
+
options.each_pair do |keyword, block|
|
82
|
+
if keyword.value == :if || keyword.value == :unless
|
83
|
+
conditional = block
|
84
|
+
break
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
_, _, block = conditional.child_nodes
|
89
|
+
expected_class = node.parent_module_name
|
90
|
+
|
91
|
+
bind = if block.begin_type?
|
92
|
+
indentation = " " * block.child_nodes.first.loc.column
|
93
|
+
"T.bind(self, #{expected_class})\n#{indentation}"
|
94
|
+
elsif block.child_nodes.empty? && !block.ivar_type?
|
95
|
+
"T.bind(self, #{expected_class})."
|
96
|
+
else
|
97
|
+
"T.bind(self, #{expected_class}); "
|
98
|
+
end
|
99
|
+
|
100
|
+
corrector.insert_before(block, bind)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def on_send(node)
|
105
|
+
return unless CALLBACKS.include?(node.method_name)
|
106
|
+
|
107
|
+
options = node.each_child_node.find(&:hash_type?)
|
108
|
+
return if options.nil?
|
109
|
+
|
110
|
+
conditional = nil
|
111
|
+
options.each_pair do |keyword, block|
|
112
|
+
next unless keyword.sym_type?
|
113
|
+
|
114
|
+
if keyword.value == :if || keyword.value == :unless
|
115
|
+
conditional = block
|
116
|
+
break
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
return if conditional.nil? || conditional.child_nodes.empty?
|
121
|
+
|
122
|
+
type, _, block = conditional.child_nodes
|
123
|
+
return unless type.lambda_or_proc?
|
124
|
+
|
125
|
+
expected_class = node.parent_module_name
|
126
|
+
return if expected_class.nil?
|
127
|
+
|
128
|
+
unless block.source.include?("T.bind(self, #{expected_class})")
|
129
|
+
add_offense(
|
130
|
+
node,
|
131
|
+
message: "Callback conditionals should be bound to the right type. Use T.bind(self, #{expected_class})"
|
132
|
+
)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -14,15 +14,6 @@ require 'rubocop'
|
|
14
14
|
# end
|
15
15
|
# ```
|
16
16
|
#
|
17
|
-
# This cop replaces them by:
|
18
|
-
#
|
19
|
-
# ```ruby
|
20
|
-
# class MyClass
|
21
|
-
# MyClassInclude = send_expr
|
22
|
-
# include MyClassInclude
|
23
|
-
# end
|
24
|
-
# ```
|
25
|
-
#
|
26
17
|
# Multiple occurences of this can be found in Shopify's code base like:
|
27
18
|
#
|
28
19
|
# ```ruby
|
@@ -61,37 +52,6 @@ module RuboCop
|
|
61
52
|
return unless [:module, :class, :sclass].include?(parent.type)
|
62
53
|
add_offense(node)
|
63
54
|
end
|
64
|
-
|
65
|
-
def autocorrect(node)
|
66
|
-
lambda do |corrector|
|
67
|
-
# Find parent class node
|
68
|
-
parent = node.parent
|
69
|
-
parent = parent.parent if parent.type == :begin
|
70
|
-
|
71
|
-
# Build include variable name
|
72
|
-
class_name = (parent.child_nodes.first.const_name || 'Anon').split('::').last
|
73
|
-
include_name = find_free_name("#{class_name}Include")
|
74
|
-
used_names << include_name
|
75
|
-
|
76
|
-
# Apply fix
|
77
|
-
indent = ' ' * node.loc.column
|
78
|
-
fix = "#{include_name} = #{node.child_nodes.first.source}\n#{indent}"
|
79
|
-
corrector.insert_before(node.loc.expression, fix)
|
80
|
-
corrector.replace(node.child_nodes.first.loc.expression, include_name)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
# Find a free local variable name
|
85
|
-
#
|
86
|
-
# Since each include uses its own local variable to store the send result,
|
87
|
-
# we need to ensure that we don't use the same name twice in the same
|
88
|
-
# module.
|
89
|
-
def find_free_name(base_name)
|
90
|
-
return base_name unless used_names.include?(base_name)
|
91
|
-
i = 2
|
92
|
-
i += 1 while used_names.include?("#{base_name}#{i}")
|
93
|
-
"#{base_name}#{i}"
|
94
|
-
end
|
95
55
|
end
|
96
56
|
end
|
97
57
|
end
|