sorbet-rails 0.5.5 → 0.5.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +7 -1
- data/lib/sorbet-rails.rb +2 -0
- data/lib/sorbet-rails/custom_types/boolean_string.rb +32 -0
- data/lib/sorbet-rails/custom_types/integer_string.rb +35 -0
- data/lib/sorbet-rails/model_plugins/active_record_enum.rb +5 -1
- data/lib/sorbet-rails/rails_mixins/active_record_overrides.rb +18 -4
- data/lib/sorbet-rails/tasks/rails_rbi.rake +1 -2
- data/lib/sorbet-rails/type_assert/type_assert.rb +0 -1
- data/lib/sorbet-rails/type_assert/type_assert_impl.rb +16 -16
- data/sorbet-rails.gemspec +1 -1
- data/spec/boolean_string_spec.rb +59 -0
- data/spec/generators/rails-template.rb +16 -0
- data/spec/integer_string_spec.rb +46 -0
- data/spec/rake_rails_rbi_models_spec.rb +1 -0
- data/spec/support/v4.2/Gemfile.lock +4 -4
- data/spec/support/v4.2/app/models/squib.rb +6 -0
- data/spec/support/v4.2/app/models/wizard.rb +1 -2
- data/spec/support/v4.2/db/migrate/20190620000007_add_type_to_wizard.rb +6 -0
- data/spec/support/v4.2/db/schema.rb +2 -1
- data/spec/support/v5.0/Gemfile.lock +4 -4
- data/spec/support/v5.0/app/models/squib.rb +6 -0
- data/spec/support/v5.0/app/models/wizard.rb +1 -1
- data/spec/support/v5.0/db/migrate/20190620000007_add_type_to_wizard.rb +6 -0
- data/spec/support/v5.0/db/schema.rb +4 -3
- data/spec/support/v5.1/Gemfile.lock +4 -4
- data/spec/support/v5.1/app/models/squib.rb +6 -0
- data/spec/support/v5.1/app/models/wizard.rb +1 -1
- data/spec/support/v5.1/db/migrate/20190620000007_add_type_to_wizard.rb +6 -0
- data/spec/support/v5.1/db/schema.rb +2 -1
- data/spec/support/v5.2/Gemfile.lock +4 -4
- data/spec/support/v5.2/app/models/squib.rb +6 -0
- data/spec/support/v5.2/app/models/wizard.rb +1 -1
- data/spec/support/v5.2/db/migrate/20190620000007_add_type_to_wizard.rb +6 -0
- data/spec/support/v5.2/db/schema.rb +2 -1
- data/spec/support/v6.0/Gemfile.lock +5 -5
- data/spec/support/v6.0/app/models/squib.rb +6 -0
- data/spec/support/v6.0/app/models/wizard.rb +1 -1
- data/spec/support/v6.0/db/migrate/20190620000007_add_type_to_wizard.rb +6 -0
- data/spec/support/v6.0/db/schema.rb +2 -1
- data/spec/test_data/v4.2/expected_squib.rbi +903 -0
- data/spec/test_data/v4.2/expected_wizard.rbi +9 -0
- data/spec/test_data/v4.2/expected_wizard_wo_spellbook.rbi +9 -0
- data/spec/test_data/v5.0/expected_squib.rbi +1164 -0
- data/spec/test_data/v5.0/expected_wizard.rbi +9 -0
- data/spec/test_data/v5.0/expected_wizard_wo_spellbook.rbi +9 -0
- data/spec/test_data/v5.1/expected_squib.rbi +1188 -0
- data/spec/test_data/v5.1/expected_wizard.rbi +9 -0
- data/spec/test_data/v5.1/expected_wizard_wo_spellbook.rbi +9 -0
- data/spec/test_data/v5.2/expected_squib.rbi +1236 -0
- data/spec/test_data/v5.2/expected_wizard.rbi +9 -0
- data/spec/test_data/v5.2/expected_wizard_wo_spellbook.rbi +9 -0
- data/spec/test_data/v6.0/expected_squib.rbi +1500 -0
- data/spec/test_data/v6.0/expected_wizard.rbi +9 -0
- data/spec/test_data/v6.0/expected_wizard_wo_spellbook.rbi +9 -0
- metadata +37 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f4aa01ecf2a262e021765ef53cdd6d5dbaaa93776c0d45ce991f9e70943eae84
|
4
|
+
data.tar.gz: 3957eb0b707a6615466d102eb7fafc25ab21ec35a6a7b3413629ac2924cda36d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cf331590decda2ee306cecda5bd89b1c603b5cbbfb1000f8dfe13e2ca6277d13269e5916cf9337c855be390da9a09eb0b6dd5e36867425421adeb563f1ffafe4
|
7
|
+
data.tar.gz: 61f5427e41cbee6b74311596b8aa54aa981ddf522692c151ca6db95b0cb5fc409e0ee476aab307b6baf0eb0576f38352c2e6c65be1d1d7bd7ccd28753bc3e5a8
|
data/README.md
CHANGED
@@ -87,6 +87,12 @@ For example:
|
|
87
87
|
key = params.require_typed(:key, TA[String].new)
|
88
88
|
T.reveal_type(key) # String
|
89
89
|
|
90
|
+
# nested params
|
91
|
+
nested_params = params.require_typed(:nested, TA[ActionController::Parameters].new)
|
92
|
+
T.reveal_type(nested_params) # ActionController::Parameters
|
93
|
+
key = nested_params.require_typed(:key, TA[String.new])
|
94
|
+
T.reveal_type(key) # String
|
95
|
+
|
90
96
|
# fetch_typed
|
91
97
|
key = params.fetch_typed(:key, TA[T.nilable(String)].new) # raises error if params doesn't have :key
|
92
98
|
T.reveal_type(key) # T.nilable(String)
|
@@ -96,7 +102,7 @@ T.reveal_type(key) # T.nilable(String)
|
|
96
102
|
```
|
97
103
|
The parameters are type-checked both statically and at runtime.
|
98
104
|
|
99
|
-
Note: The API `TA[...].new` may
|
105
|
+
Note: The API `TA[...].new` may seem verbose, but necessary to support this feature. Ideally, the API can be simply `require_typed(:key, Type)`. However, `sorbet` [doesn't support](http://github.com/sorbet/sorbet/issues/62) defining a method that accept a type and return an instance of the type. The library provides a wrapper `TA` (which stands for `TypeAssert`) in order to achieve the behavior. If this feature is supported by `sorbet` in the future, it will be easy to codemod to remove the `TA[...].new` part from your code.
|
100
106
|
|
101
107
|
### Routes
|
102
108
|
|
data/lib/sorbet-rails.rb
CHANGED
@@ -0,0 +1,32 @@
|
|
1
|
+
# typed: false
|
2
|
+
module BooleanStringImpl
|
3
|
+
def is_a?(type)
|
4
|
+
return super unless type == BooleanString
|
5
|
+
_is_a_boolean_string?
|
6
|
+
end
|
7
|
+
|
8
|
+
def kind_of?(type)
|
9
|
+
return super unless type == BooleanString
|
10
|
+
_is_a_boolean_string?
|
11
|
+
end
|
12
|
+
|
13
|
+
def instance_of?(type)
|
14
|
+
return super unless type == BooleanString
|
15
|
+
_is_a_boolean_string?
|
16
|
+
end
|
17
|
+
|
18
|
+
def _is_a_boolean_string?
|
19
|
+
return @cached_is_a unless @cached_is_a.nil?
|
20
|
+
@cached_is_a = (self =~ /^(true|false)$/i) == 0
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class String
|
25
|
+
include BooleanStringImpl
|
26
|
+
end
|
27
|
+
|
28
|
+
class BooleanString < String
|
29
|
+
def self.===(other)
|
30
|
+
other.is_a?(BooleanString)
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# typed: false
|
2
|
+
module IntegerStringImpl
|
3
|
+
def is_a?(type)
|
4
|
+
return super unless type == IntegerString
|
5
|
+
_is_a_integer_string?
|
6
|
+
end
|
7
|
+
|
8
|
+
def kind_of?(type)
|
9
|
+
return super unless type == IntegerString
|
10
|
+
_is_a_integer_string?
|
11
|
+
end
|
12
|
+
|
13
|
+
def instance_of?(type)
|
14
|
+
return super unless type == IntegerString
|
15
|
+
_is_a_integer_string?
|
16
|
+
end
|
17
|
+
|
18
|
+
def _is_a_integer_string?
|
19
|
+
return @cached_is_a unless @cached_is_a.nil?
|
20
|
+
Integer(self, 10)
|
21
|
+
@cached_is_a = true
|
22
|
+
rescue ArgumentError => err
|
23
|
+
@cached_is_a = false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class String
|
28
|
+
include IntegerStringImpl
|
29
|
+
end
|
30
|
+
|
31
|
+
class IntegerString < String
|
32
|
+
def self.===(other)
|
33
|
+
other.is_a?(IntegerString)
|
34
|
+
end
|
35
|
+
end
|
@@ -32,7 +32,11 @@ class SorbetRails::ModelPlugins::ActiveRecordEnum < SorbetRails::ModelPlugins::B
|
|
32
32
|
class_method: true,
|
33
33
|
)
|
34
34
|
|
35
|
-
enum_call =
|
35
|
+
enum_call = ActiveRecordOverrides.instance.get_enum_call(@model_class, enum_name.to_sym)
|
36
|
+
if enum_call.nil?
|
37
|
+
puts "Error: unable to find enum call for enum #{enum_name}, model #{self.model_class_name}"
|
38
|
+
next
|
39
|
+
end
|
36
40
|
|
37
41
|
enum_prefix = enum_call[:_prefix]
|
38
42
|
prefix =
|
@@ -11,9 +11,23 @@ class ActiveRecordOverrides
|
|
11
11
|
@enum_calls = {}
|
12
12
|
end
|
13
13
|
|
14
|
-
def store_enum_call(
|
15
|
-
|
16
|
-
@enum_calls[class_name]
|
14
|
+
def store_enum_call(klass, kwargs)
|
15
|
+
class_name = klass.name
|
16
|
+
@enum_calls[class_name] ||= {}
|
17
|
+
# modeling the logic in
|
18
|
+
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/enum.rb#L152
|
19
|
+
kwargs.each do |name, values|
|
20
|
+
next if [:_prefix, :_suffix, :_scopes].include?(name)
|
21
|
+
@enum_calls[class_name][name] = kwargs
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_enum_call(klass, enum_sym)
|
26
|
+
return nil if klass == Object
|
27
|
+
class_name = klass.name
|
28
|
+
class_enum_calls = @enum_calls[klass.name]
|
29
|
+
return class_enum_calls[enum_sym] if class_enum_calls && class_enum_calls.has_key?(enum_sym)
|
30
|
+
return get_enum_call(klass.superclass, enum_sym)
|
17
31
|
end
|
18
32
|
end
|
19
33
|
|
@@ -21,7 +35,7 @@ module ::ActiveRecord::Enum
|
|
21
35
|
alias old_enum enum
|
22
36
|
|
23
37
|
def enum(*args, **kwargs)
|
24
|
-
ActiveRecordOverrides.instance.store_enum_call(
|
38
|
+
ActiveRecordOverrides.instance.store_enum_call(self, kwargs)
|
25
39
|
old_enum(*args, **kwargs)
|
26
40
|
end
|
27
41
|
end
|
@@ -134,11 +134,10 @@ namespace :rails_rbi do
|
|
134
134
|
end
|
135
135
|
|
136
136
|
def copy_bundled_rbi(filename)
|
137
|
-
puts "
|
137
|
+
puts "Copy bundled file #{filename}"
|
138
138
|
bundled_rbi_file_path = File.join(RAILS_RBI_RAKE_DIR, "..", "..", "bundled_rbi", filename)
|
139
139
|
copy_to_path = Rails.root.join("sorbet", "rails-rbi", filename)
|
140
140
|
FileUtils.mkdir_p(File.dirname(copy_to_path))
|
141
|
-
puts "file path to copy #{bundled_rbi_file_path}"
|
142
141
|
FileUtils.cp(bundled_rbi_file_path, copy_to_path)
|
143
142
|
end
|
144
143
|
end
|
@@ -1,26 +1,26 @@
|
|
1
1
|
# typed: ignore
|
2
2
|
require 'sorbet-runtime'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
type = types.first
|
11
|
-
return Class.new do
|
12
|
-
include ITypeAssert
|
4
|
+
if !$PROGRAM_NAME.include?('sorbet')
|
5
|
+
module TypeAssertImpl
|
6
|
+
def self.included(klass)
|
7
|
+
klass.define_singleton_method(:[]) do |type|
|
8
|
+
return Class.new do
|
9
|
+
include ITypeAssert
|
13
10
|
|
14
|
-
|
11
|
+
define_method(:to_s) { "TA[#{type.to_s}]" }
|
15
12
|
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
define_method(:assert) do |val|
|
14
|
+
T.let(val, type)
|
15
|
+
end
|
19
16
|
|
20
|
-
|
17
|
+
define_method(:get_type) { type }
|
18
|
+
end
|
21
19
|
end
|
22
20
|
end
|
23
21
|
end
|
24
22
|
|
25
|
-
class
|
26
|
-
|
23
|
+
class TA
|
24
|
+
include TypeAssertImpl
|
25
|
+
end
|
26
|
+
end
|
data/sorbet-rails.gemspec
CHANGED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
require 'sorbet-rails/custom_types/boolean_string'
|
3
|
+
require 'sorbet-runtime'
|
4
|
+
|
5
|
+
RSpec.describe BooleanString do
|
6
|
+
shared_examples 'boolean string' do |str|
|
7
|
+
it 'acts as BooleanString perfectly' do
|
8
|
+
expect(str.is_a?(BooleanString)).to be(true)
|
9
|
+
expect(str.kind_of?(BooleanString)).to be(true)
|
10
|
+
expect(str.instance_of?(BooleanString)).to be(true)
|
11
|
+
expect(BooleanString === str).to be(true)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
include_examples 'boolean string', 'true'
|
16
|
+
include_examples 'boolean string', 'false'
|
17
|
+
include_examples 'boolean string', 'True'
|
18
|
+
include_examples 'boolean string', 'False'
|
19
|
+
|
20
|
+
shared_examples 'non boolean string' do |str|
|
21
|
+
it 'does not acts as BooleanString' do
|
22
|
+
expect(str.is_a?(BooleanString)).to be(false)
|
23
|
+
expect(str.kind_of?(BooleanString)).to be(false)
|
24
|
+
expect(str.instance_of?(BooleanString)).to be(false)
|
25
|
+
expect(BooleanString === str).to be(false)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
include_examples 'non boolean string', 'yes'
|
30
|
+
include_examples 'non boolean string', 'no'
|
31
|
+
include_examples 'non boolean string', 'alala'
|
32
|
+
|
33
|
+
context 'sorbet recognizes it at runtime' do
|
34
|
+
it 'lets boolean string pass runtime check' do
|
35
|
+
expect(T.let('true', BooleanString)).to eql('true')
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'doesnt let normal string pass runtime typecheck' do
|
39
|
+
expect {
|
40
|
+
T.let('yes', BooleanString)
|
41
|
+
}.to raise_error(TypeError)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'using with TypeAssert' do
|
46
|
+
let!(:ta) { TA[BooleanString].new }
|
47
|
+
|
48
|
+
it 'lets boolean string pass runtime typecheck' do
|
49
|
+
expect(ta.assert('true')).to eql('true')
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'doesnt let normal string pass runtime typecheck' do
|
53
|
+
expect {
|
54
|
+
ta.assert('yes')
|
55
|
+
}.to raise_error(TypeError)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
@@ -216,6 +216,14 @@ def create_models
|
|
216
216
|
end
|
217
217
|
end
|
218
218
|
RUBY
|
219
|
+
|
220
|
+
file "app/models/squib.rb", <<~RUBY
|
221
|
+
class Squib < Wizard
|
222
|
+
def is_magical
|
223
|
+
false
|
224
|
+
end
|
225
|
+
end
|
226
|
+
RUBY
|
219
227
|
end
|
220
228
|
|
221
229
|
def create_migrations
|
@@ -304,6 +312,14 @@ def create_migrations
|
|
304
312
|
end
|
305
313
|
RUBY
|
306
314
|
end
|
315
|
+
|
316
|
+
file "db/migrate/20190620000007_add_type_to_wizard.rb", <<~RUBY
|
317
|
+
class AddTypeToWizard < #{migration_superclass}
|
318
|
+
def change
|
319
|
+
add_column :wizards, :type, :string, null: false, default: 'Wizard'
|
320
|
+
end
|
321
|
+
end
|
322
|
+
RUBY
|
307
323
|
end
|
308
324
|
|
309
325
|
def create_mailers
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
require 'sorbet-rails/custom_types/integer_string'
|
3
|
+
require 'sorbet-runtime'
|
4
|
+
|
5
|
+
RSpec.describe IntegerString do
|
6
|
+
it 'lets integer string acts as IntegerString perfectly' do
|
7
|
+
expect('1'.is_a?(IntegerString)).to be(true)
|
8
|
+
expect('12'.kind_of?(IntegerString)).to be(true)
|
9
|
+
expect('123'.instance_of?(IntegerString)).to be(true)
|
10
|
+
expect(IntegerString === '1234').to be(true)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'does not let other string acts as IntegerString' do
|
14
|
+
expect('a1'.is_a?(IntegerString)).to be(false)
|
15
|
+
expect('a12'.kind_of?(IntegerString)).to be(false)
|
16
|
+
expect('a123'.instance_of?(IntegerString)).to be(false)
|
17
|
+
expect(IntegerString === 'a1234').to be(false)
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'sorbet recognizes it at runtime' do
|
21
|
+
it 'lets integer string pass runtime check' do
|
22
|
+
expect(T.let('123', IntegerString)).to eql('123')
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'doesnt let normal string pass runtime typecheck' do
|
26
|
+
expect {
|
27
|
+
T.let('a123', IntegerString)
|
28
|
+
}.to raise_error(TypeError)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'using with TypeAssert' do
|
33
|
+
let!(:ta) { TA[IntegerString].new }
|
34
|
+
|
35
|
+
it 'lets integer string pass runtime typecheck' do
|
36
|
+
expect(ta.assert('123')).to eql('123')
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'doesnt let normal string pass runtime typecheck' do
|
40
|
+
expect {
|
41
|
+
ta.assert('a123')
|
42
|
+
}.to raise_error(TypeError)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
@@ -95,10 +95,10 @@ GEM
|
|
95
95
|
thor (>= 0.18.1, < 2.0)
|
96
96
|
rainbow (3.0.0)
|
97
97
|
rake (12.3.3)
|
98
|
-
sorbet (0.4.
|
99
|
-
sorbet-static (= 0.4.
|
100
|
-
sorbet-runtime (0.4.
|
101
|
-
sorbet-static (0.4.
|
98
|
+
sorbet (0.4.4755)
|
99
|
+
sorbet-static (= 0.4.4755)
|
100
|
+
sorbet-runtime (0.4.4755)
|
101
|
+
sorbet-static (0.4.4755-x86_64-linux)
|
102
102
|
sprockets (3.7.2)
|
103
103
|
concurrent-ruby (~> 1.0)
|
104
104
|
rack (> 1, < 3)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: ignore
|
2
2
|
class Wizard < ApplicationRecord
|
3
3
|
validates :name, length: { minimum: 5 }, presence: true
|
4
4
|
|
@@ -26,5 +26,4 @@ class Wizard < ApplicationRecord
|
|
26
26
|
has_many :spell_books
|
27
27
|
|
28
28
|
scope :recent, -> { where('created_at > ?', 1.month.ago) }
|
29
|
-
|
30
29
|
end
|
@@ -12,7 +12,7 @@
|
|
12
12
|
#
|
13
13
|
# It's strongly recommended that you check this file into your version control system.
|
14
14
|
|
15
|
-
ActiveRecord::Schema.define(version:
|
15
|
+
ActiveRecord::Schema.define(version: 20190620000007) do
|
16
16
|
|
17
17
|
create_table "spell_books", force: :cascade do |t|
|
18
18
|
t.string "name"
|
@@ -42,6 +42,7 @@ ActiveRecord::Schema.define(version: 20190620000005) do
|
|
42
42
|
t.datetime "created_at"
|
43
43
|
t.datetime "updated_at"
|
44
44
|
t.string "broom"
|
45
|
+
t.string "type", default: "Wizard", null: false
|
45
46
|
end
|
46
47
|
|
47
48
|
end
|