rubocop-sorbet 0.4.1 → 0.6.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: b73993245df905cf40d9e0dbc440d613315e5b32d89e9b61ee123994fee53e73
4
- data.tar.gz: '019d4212c2c204492aa38f928ddfc31cceac5d79fe6ee09927c889e3b245f999'
3
+ metadata.gz: 309687789d8de94ff0cc8c0d64361cd227ab332ffdd16ab0248d588639dfcd80
4
+ data.tar.gz: ca39d50843f2e577ed0a0328c9707e21af175daacd8c92b9b4403bc2195c4cff
5
5
  SHA512:
6
- metadata.gz: d491ed5a48d75c2f5151329fc0018ca56ed57504d6d1e338befe0c9395bf85824b65f68164538667b4720eca216eeb15c29c6ed379524ae9ed1a8215cf6daf84
7
- data.tar.gz: 7944aa4817f48ca0bb43c47dabcf197ced656674ab59ce261705b946045a2e3c0de813200187829cf9936a2144a5203ed625c8cdd89a33b7e80876229f7f2748
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
@@ -6,3 +6,4 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ .byebug_history
data/Gemfile CHANGED
@@ -4,6 +4,7 @@ source "https://rubygems.org"
4
4
  # Specify your gem's dependencies in rubocop-sorbet.gemspec
5
5
  gemspec
6
6
 
7
+ gem "byebug"
7
8
  gem "rake", ">= 12.3.3"
8
9
  gem "rspec"
9
10
  gem "rubocop-shopify", require: false
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rubocop-sorbet (0.4.1)
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.4)
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
- 1.17.3
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: true
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: true
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: true
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
@@ -8,4 +8,4 @@ up:
8
8
 
9
9
  commands:
10
10
  test: "bin/rspec"
11
- style: "bundle exec rubocop"
11
+ style: "bin/rubocop"
@@ -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