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 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