rbs_rails 0.5.0 → 0.8.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/.dependabot/config.yml +8 -0
- data/.github/workflows/ci.yml +29 -0
- data/.gitignore +4 -2
- data/.gitmodules +0 -3
- data/CHANGELOG.md +22 -0
- data/Gemfile +2 -2
- data/Gemfile.lock +61 -0
- data/README.md +16 -44
- data/Steepfile +3 -0
- data/bin/add-type-params.rb +2 -1
- data/bin/gem_rbs +94 -0
- data/bin/postprocess.rb +1 -1
- data/bin/rbs +1 -1
- data/bin/rbs-prototype-rb.rb +5 -1
- data/bin/setup +1 -0
- data/lib/rbs_rails.rb +4 -0
- data/lib/rbs_rails/active_record.rb +91 -46
- data/lib/rbs_rails/dependency_builder.rb +43 -0
- data/lib/rbs_rails/rake_task.rb +72 -0
- data/lib/rbs_rails/util.rb +25 -0
- data/lib/rbs_rails/version.rb +1 -1
- data/rbs_rails.gemspec +2 -2
- data/sig/activerecord.rbs +4 -0
- data/sig/fileutils.rbs +1 -0
- data/sig/rake.rbs +6 -0
- data/sig/rbs_rails/active_record.rbs +11 -2
- data/sig/rbs_rails/dependency_builder.rbs +9 -0
- data/sig/rbs_rails/rake_task.rbs +26 -0
- data/sig/rbs_rails/util.rbs +11 -0
- metadata +19 -30
- data/.travis.yml +0 -11
- data/assets/sig/action_mailer.rbs +0 -8
- data/assets/sig/builtin.rbs +0 -7
- data/assets/sig/capybara.rbs +0 -14
- data/assets/sig/concurrent.rbs +0 -4
- data/assets/sig/erb.rbs +0 -4
- data/assets/sig/erubi.rbs +0 -4
- data/assets/sig/i18n.rbs +0 -4
- data/assets/sig/libxml.rbs +0 -10
- data/assets/sig/minitest.rbs +0 -13
- data/assets/sig/nokogiri.rbs +0 -8
- data/assets/sig/pg.rbs +0 -5
- data/assets/sig/que.rbs +0 -4
- data/assets/sig/queue_classic.rbs +0 -4
- data/assets/sig/racc.rbs +0 -4
- data/assets/sig/rack-test.rbs +0 -6
- data/assets/sig/rack.rbs +0 -47
- data/assets/sig/rails.rbs +0 -14
- data/assets/sig/rdoc.rbs +0 -9
- data/assets/sig/sidekiq.rbs +0 -4
- data/assets/sig/sneakers.rbs +0 -4
- data/assets/sig/stdlib.rbs +0 -24
- data/assets/sig/sucker_punch.rbs +0 -4
- data/assets/sig/thor.rbs +0 -12
- data/assets/sig/tzinfo.rbs +0 -4
data/bin/setup
CHANGED
data/lib/rbs_rails.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
require 'parser/current'
|
2
|
+
require 'rbs'
|
3
|
+
require 'stringio'
|
2
4
|
|
3
5
|
require_relative "rbs_rails/version"
|
6
|
+
require_relative "rbs_rails/util"
|
4
7
|
require_relative 'rbs_rails/active_record'
|
5
8
|
require_relative 'rbs_rails/path_helpers'
|
9
|
+
require_relative 'rbs_rails/dependency_builder'
|
6
10
|
|
7
11
|
module RbsRails
|
8
12
|
class Error < StandardError; end
|
@@ -1,59 +1,97 @@
|
|
1
1
|
module RbsRails
|
2
2
|
module ActiveRecord
|
3
|
-
|
4
|
-
|
3
|
+
|
4
|
+
def self.generatable?(klass)
|
5
|
+
return false if klass.abstract_class?
|
6
|
+
|
7
|
+
klass.connection.table_exists?(klass.table_name)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.class_to_rbs(klass, dependencies: [])
|
11
|
+
Generator.new(klass, dependencies: dependencies).generate
|
5
12
|
end
|
6
13
|
|
7
14
|
class Generator
|
8
|
-
def initialize(klass)
|
15
|
+
def initialize(klass, dependencies:)
|
9
16
|
@klass = klass
|
17
|
+
@dependencies = dependencies
|
18
|
+
@klass_name = Util.module_name(klass)
|
19
|
+
|
20
|
+
namespaces = klass_name.split('::').tap{ |names| names.pop }
|
21
|
+
@dependencies << namespaces.join('::') unless namespaces.empty?
|
10
22
|
end
|
11
23
|
|
12
24
|
def generate
|
13
|
-
|
14
|
-
klass_decl,
|
15
|
-
relation_decl,
|
16
|
-
collection_proxy_decl,
|
17
|
-
].join("\n")
|
25
|
+
Util.format_rbs klass_decl
|
18
26
|
end
|
19
27
|
|
20
28
|
private def klass_decl
|
21
29
|
<<~RBS
|
22
30
|
#{header}
|
23
|
-
extend _ActiveRecord_Relation_ClassMethods[#{
|
31
|
+
extend _ActiveRecord_Relation_ClassMethods[#{klass_name}, #{relation_class_name}, #{pk_type}]
|
24
32
|
|
25
|
-
#{columns
|
26
|
-
#{associations
|
27
|
-
#{enum_instance_methods
|
28
|
-
#{enum_scope_methods(singleton: true)
|
29
|
-
#{scopes(singleton: true)
|
30
|
-
|
33
|
+
#{columns}
|
34
|
+
#{associations}
|
35
|
+
#{enum_instance_methods}
|
36
|
+
#{enum_scope_methods(singleton: true)}
|
37
|
+
#{scopes(singleton: true)}
|
38
|
+
|
39
|
+
#{relation_decl}
|
40
|
+
|
41
|
+
#{collection_proxy_decl}
|
42
|
+
|
43
|
+
#{footer}
|
31
44
|
RBS
|
32
45
|
end
|
33
46
|
|
47
|
+
private def pk_type
|
48
|
+
pk = klass.primary_key
|
49
|
+
col = klass.columns.find {|col| col.name == pk }
|
50
|
+
sql_type_to_class(col.type)
|
51
|
+
end
|
52
|
+
|
34
53
|
private def relation_decl
|
35
54
|
<<~RBS
|
36
55
|
class #{relation_class_name} < ActiveRecord::Relation
|
37
|
-
include _ActiveRecord_Relation[#{
|
38
|
-
include Enumerable[#{
|
39
|
-
|
40
|
-
#{
|
56
|
+
include _ActiveRecord_Relation[#{klass_name}, #{pk_type}]
|
57
|
+
include Enumerable[#{klass_name}]
|
58
|
+
|
59
|
+
#{enum_scope_methods(singleton: false)}
|
60
|
+
#{scopes(singleton: false)}
|
41
61
|
end
|
42
62
|
RBS
|
43
63
|
end
|
44
64
|
|
45
65
|
private def collection_proxy_decl
|
46
66
|
<<~RBS
|
47
|
-
class
|
67
|
+
class ActiveRecord_Associations_CollectionProxy < ActiveRecord::Associations::CollectionProxy
|
48
68
|
end
|
49
69
|
RBS
|
50
70
|
end
|
51
71
|
|
52
|
-
|
53
72
|
private def header
|
54
|
-
|
55
|
-
|
56
|
-
|
73
|
+
namespace = +''
|
74
|
+
klass_name.split('::').map do |mod_name|
|
75
|
+
namespace += "::#{mod_name}"
|
76
|
+
mod_object = Object.const_get(namespace)
|
77
|
+
case mod_object
|
78
|
+
when Class
|
79
|
+
# @type var superclass: Class
|
80
|
+
superclass = _ = mod_object.superclass
|
81
|
+
superclass_name = Util.module_name(superclass)
|
82
|
+
@dependencies << superclass_name
|
83
|
+
|
84
|
+
"class #{mod_name} < #{superclass_name}"
|
85
|
+
when Module
|
86
|
+
"module #{mod_name}"
|
87
|
+
else
|
88
|
+
raise 'unreachable'
|
89
|
+
end
|
90
|
+
end.join("\n")
|
91
|
+
end
|
92
|
+
|
93
|
+
private def footer
|
94
|
+
"end\n" * klass_name.split('::').size
|
57
95
|
end
|
58
96
|
|
59
97
|
private def associations
|
@@ -67,7 +105,7 @@ module RbsRails
|
|
67
105
|
private def has_many
|
68
106
|
klass.reflect_on_all_associations(:has_many).map do |a|
|
69
107
|
singular_name = a.name.to_s.singularize
|
70
|
-
type = a.klass
|
108
|
+
type = Util.module_name(a.klass)
|
71
109
|
collection_type = "#{type}::ActiveRecord_Associations_CollectionProxy"
|
72
110
|
<<~RUBY.chomp
|
73
111
|
def #{a.name}: () -> #{collection_type}
|
@@ -80,7 +118,7 @@ module RbsRails
|
|
80
118
|
|
81
119
|
private def has_one
|
82
120
|
klass.reflect_on_all_associations(:has_one).map do |a|
|
83
|
-
type = a.polymorphic? ? 'untyped' : a.klass
|
121
|
+
type = a.polymorphic? ? 'untyped' : Util.module_name(a.klass)
|
84
122
|
type_optional = optional(type)
|
85
123
|
<<~RUBY.chomp
|
86
124
|
def #{a.name}: () -> #{type}
|
@@ -95,7 +133,7 @@ module RbsRails
|
|
95
133
|
|
96
134
|
private def belongs_to
|
97
135
|
klass.reflect_on_all_associations(:belongs_to).map do |a|
|
98
|
-
type = a.polymorphic? ? 'untyped' : a.klass
|
136
|
+
type = a.polymorphic? ? 'untyped' : Util.module_name(a.klass)
|
99
137
|
type_optional = optional(type)
|
100
138
|
<<~RUBY.chomp
|
101
139
|
def #{a.name}: () -> #{type}
|
@@ -208,37 +246,42 @@ module RbsRails
|
|
208
246
|
next unless body_node.type == :block
|
209
247
|
|
210
248
|
args = args_to_type(body_node.children[1])
|
211
|
-
"def #{singleton ? 'self.' : ''}#{name}:
|
249
|
+
"def #{singleton ? 'self.' : ''}#{name}: #{args} -> #{relation_class_name}"
|
212
250
|
end.compact.join("\n")
|
213
251
|
end
|
214
252
|
|
215
253
|
private def args_to_type(args_node)
|
216
254
|
# @type var res: Array[String]
|
217
255
|
res = []
|
256
|
+
# @type var block: String?
|
257
|
+
block = nil
|
218
258
|
args_node.children.each do |node|
|
219
259
|
case node.type
|
220
260
|
when :arg
|
221
|
-
res << "untyped"
|
261
|
+
res << "untyped #{node.children[0]}"
|
222
262
|
when :optarg
|
223
|
-
res << "?untyped"
|
263
|
+
res << "?untyped #{node.children[0]}"
|
224
264
|
when :kwarg
|
225
265
|
res << "#{node.children[0]}: untyped"
|
226
266
|
when :kwoptarg
|
227
267
|
res << "?#{node.children[0]}: untyped"
|
268
|
+
when :restarg
|
269
|
+
res << "*untyped #{node.children[0]}"
|
270
|
+
when :kwrestarg
|
271
|
+
res << "**untyped #{node.children[0]}"
|
272
|
+
when :blockarg
|
273
|
+
block = " { (*untyped) -> untyped }"
|
228
274
|
else
|
229
275
|
raise "unexpected: #{node}"
|
230
276
|
end
|
231
277
|
end
|
232
|
-
res.join(", ")
|
278
|
+
"(#{res.join(", ")})#{block}"
|
233
279
|
end
|
234
280
|
|
235
281
|
private def parse_model_file
|
236
282
|
return @parse_model_file if defined?(@parse_model_file)
|
237
283
|
|
238
|
-
|
239
|
-
# @type var class_name: String
|
240
|
-
class_name = _ = klass.name
|
241
|
-
path = Rails.root.join('app/models/', class_name.underscore + '.rb')
|
284
|
+
path = Rails.root.join('app/models/', klass_name.underscore + '.rb')
|
242
285
|
return @parse_model_file = nil unless path.exist?
|
243
286
|
return [] unless path.exist?
|
244
287
|
|
@@ -259,7 +302,7 @@ module RbsRails
|
|
259
302
|
end
|
260
303
|
|
261
304
|
private def relation_class_name
|
262
|
-
"
|
305
|
+
"ActiveRecord_Relation"
|
263
306
|
end
|
264
307
|
|
265
308
|
private def columns
|
@@ -280,6 +323,12 @@ module RbsRails
|
|
280
323
|
def #{col.name}_previously_changed?: () -> bool
|
281
324
|
def #{col.name}_previous_change: () -> Array[#{class_name_opt}]?
|
282
325
|
def #{col.name}_previously_was: () -> #{class_name_opt}
|
326
|
+
def #{col.name}_before_last_save: () -> #{class_name_opt}
|
327
|
+
def #{col.name}_change_to_be_saved: () -> Array[#{class_name_opt}]?
|
328
|
+
def #{col.name}_in_database: () -> #{class_name_opt}
|
329
|
+
def saved_change_to_#{col.name}: () -> Array[#{class_name_opt}]?
|
330
|
+
def saved_change_to_#{col.name}?: () -> bool
|
331
|
+
def will_save_change_to_#{col.name}?: () -> bool
|
283
332
|
def restore_#{col.name}!: () -> void
|
284
333
|
def clear_#{col.name}_change: () -> void
|
285
334
|
EOS
|
@@ -303,29 +352,25 @@ module RbsRails
|
|
303
352
|
when :string, :text, :citext, :uuid, :binary
|
304
353
|
'String'
|
305
354
|
when :datetime
|
306
|
-
|
307
|
-
# ActiveSupport::TimeWithZone.name
|
308
|
-
'Time'
|
355
|
+
'ActiveSupport::TimeWithZone'
|
309
356
|
when :boolean
|
310
|
-
"
|
357
|
+
"bool"
|
311
358
|
when :jsonb, :json
|
312
359
|
"untyped"
|
313
360
|
when :date
|
314
|
-
|
315
|
-
# Date.name
|
316
|
-
'untyped'
|
361
|
+
'Date'
|
317
362
|
when :time
|
318
363
|
'Time'
|
319
364
|
when :inet
|
320
365
|
"IPAddr"
|
321
366
|
else
|
322
|
-
|
367
|
+
# Unknown column type, give up
|
368
|
+
'untyped'
|
323
369
|
end
|
324
370
|
end
|
325
371
|
|
326
372
|
private
|
327
|
-
|
328
|
-
attr_reader :klass
|
373
|
+
attr_reader :klass, :klass_name
|
329
374
|
end
|
330
375
|
end
|
331
376
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module RbsRails
|
2
|
+
class DependencyBuilder
|
3
|
+
attr_reader :deps
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@deps = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def build
|
10
|
+
dep_rbs = +""
|
11
|
+
done = Set.new(['ActiveRecord::Base', 'ActiveRecord', 'Object'])
|
12
|
+
deps.uniq!
|
13
|
+
while dep = deps.shift
|
14
|
+
next unless done.add?(dep)
|
15
|
+
|
16
|
+
case dep_object = Object.const_get(dep)
|
17
|
+
when Class
|
18
|
+
superclass = dep_object.superclass or raise
|
19
|
+
super_name = Util.module_name(superclass)
|
20
|
+
deps << super_name
|
21
|
+
dep_rbs << "class #{dep} < #{super_name} end\n"
|
22
|
+
when Module
|
23
|
+
dep_rbs << "module #{dep} end\n"
|
24
|
+
else
|
25
|
+
raise
|
26
|
+
end
|
27
|
+
|
28
|
+
# push namespaces
|
29
|
+
namespaces = dep.split('::')
|
30
|
+
namespaces.pop
|
31
|
+
namespaces.inject('') do |base, name|
|
32
|
+
full_name = base.empty? ? name : [base, name].join('::')
|
33
|
+
deps << full_name
|
34
|
+
full_name
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
unless dep_rbs.empty?
|
39
|
+
Util.format_rbs(dep_rbs)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/tasklib'
|
3
|
+
|
4
|
+
module RbsRails
|
5
|
+
class RakeTask < Rake::TaskLib
|
6
|
+
attr_accessor :ignore_model_if, :name, :signature_root_dir
|
7
|
+
|
8
|
+
def initialize(name = :rbs_rails, &block)
|
9
|
+
super()
|
10
|
+
|
11
|
+
@name = name
|
12
|
+
|
13
|
+
block.call(self) if block
|
14
|
+
|
15
|
+
setup_signature_root_dir!
|
16
|
+
|
17
|
+
def_generate_rbs_for_models
|
18
|
+
def_generate_rbs_for_path_helpers
|
19
|
+
def_all
|
20
|
+
end
|
21
|
+
|
22
|
+
def def_all
|
23
|
+
desc 'Run all tasks of rbs_rails'
|
24
|
+
|
25
|
+
deps = [:"#{name}:generate_rbs_for_models", :"#{name}:generate_rbs_for_path_helpers"]
|
26
|
+
task("#{name}:all": deps)
|
27
|
+
end
|
28
|
+
|
29
|
+
def def_generate_rbs_for_models
|
30
|
+
desc 'Generate RBS files for Active Record models'
|
31
|
+
task("#{name}:generate_rbs_for_models": :environment) do
|
32
|
+
require 'rbs_rails'
|
33
|
+
|
34
|
+
Rails.application.eager_load!
|
35
|
+
|
36
|
+
dep_builder = DependencyBuilder.new
|
37
|
+
|
38
|
+
::ActiveRecord::Base.descendants.each do |klass|
|
39
|
+
next unless RbsRails::ActiveRecord.generatable?(klass)
|
40
|
+
next if ignore_model_if&.call(klass)
|
41
|
+
|
42
|
+
path = signature_root_dir / "app/models/#{klass.name.underscore}.rbs"
|
43
|
+
path.dirname.mkpath
|
44
|
+
|
45
|
+
sig = RbsRails::ActiveRecord.class_to_rbs(klass, dependencies: dep_builder.deps)
|
46
|
+
path.write sig
|
47
|
+
end
|
48
|
+
|
49
|
+
if dep_rbs = dep_builder.build
|
50
|
+
signature_root_dir.join('model_dependencies.rbs').write(dep_rbs)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def def_generate_rbs_for_path_helpers
|
56
|
+
desc 'Generate RBS files for path helpers'
|
57
|
+
task("#{name}:generate_rbs_for_path_helpers": :environment) do
|
58
|
+
require 'rbs_rails'
|
59
|
+
|
60
|
+
out_path = signature_root_dir.join 'path_helpers.rbs'
|
61
|
+
rbs = RbsRails::PathHelpers.generate
|
62
|
+
out_path.write rbs
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private def setup_signature_root_dir!
|
67
|
+
@signature_root_dir ||= Rails.root / 'sig/rbs_rails'
|
68
|
+
@signature_root_dir = Pathname(@signature_root_dir)
|
69
|
+
@signature_root_dir.mkpath
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module RbsRails
|
2
|
+
module Util
|
3
|
+
MODULE_NAME = Module.instance_method(:name)
|
4
|
+
|
5
|
+
extend self
|
6
|
+
|
7
|
+
if '2.7' <= RUBY_VERSION
|
8
|
+
def module_name(mod)
|
9
|
+
# HACK: RBS doesn't have UnboundMethod#bind_call
|
10
|
+
(_ = MODULE_NAME).bind_call(mod)
|
11
|
+
end
|
12
|
+
else
|
13
|
+
def module_name(mod)
|
14
|
+
MODULE_NAME.bind(mod).call
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def format_rbs(rbs)
|
19
|
+
decls = RBS::Parser.parse_signature(rbs)
|
20
|
+
StringIO.new.tap do |io|
|
21
|
+
RBS::Writer.new(out: io).write(decls)
|
22
|
+
end.string
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/rbs_rails/version.rb
CHANGED
data/rbs_rails.gemspec
CHANGED
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
|
|
14
14
|
|
15
15
|
spec.metadata["homepage_uri"] = spec.homepage
|
16
16
|
spec.metadata["source_code_uri"] = spec.homepage
|
17
|
-
|
17
|
+
spec.metadata["changelog_uri"] = "https://github.com/pocke/rbs_rails/blob/master/CHANGELOG.md"
|
18
18
|
|
19
19
|
# Specify which files should be added to the gem when it is released.
|
20
20
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
@@ -26,5 +26,5 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.require_paths = ["lib"]
|
27
27
|
|
28
28
|
spec.add_runtime_dependency 'parser'
|
29
|
-
spec.add_runtime_dependency 'rbs', '>=
|
29
|
+
spec.add_runtime_dependency 'rbs', '>= 1'
|
30
30
|
end
|