rbs_rails 0.4.1 → 0.8.1

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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +29 -0
  3. data/.gitignore +4 -1
  4. data/.gitmodules +0 -0
  5. data/CHANGELOG.md +17 -0
  6. data/Gemfile +2 -2
  7. data/README.md +28 -44
  8. data/Rakefile +2 -1
  9. data/Steepfile +12 -1
  10. data/bin/add-type-params.rb +2 -1
  11. data/bin/gem_rbs +94 -0
  12. data/bin/postprocess.rb +1 -1
  13. data/bin/rbs +29 -2
  14. data/bin/rbs-prototype-rb.rb +59 -6
  15. data/bin/setup +1 -0
  16. data/lib/rbs_rails.rb +4 -0
  17. data/lib/rbs_rails/active_record.rb +80 -46
  18. data/lib/rbs_rails/dependency_builder.rb +43 -0
  19. data/lib/rbs_rails/rake_task.rb +72 -0
  20. data/lib/rbs_rails/util.rb +25 -0
  21. data/lib/rbs_rails/version.rb +1 -1
  22. data/rbs_rails.gemspec +2 -2
  23. data/sig/fileutils.rbs +1 -0
  24. data/sig/rake.rbs +6 -0
  25. data/sig/rbs_rails/active_record.rbs +9 -2
  26. data/sig/rbs_rails/dependency_builder.rbs +9 -0
  27. data/sig/rbs_rails/rake_task.rbs +26 -0
  28. data/sig/rbs_rails/util.rbs +11 -0
  29. metadata +17 -48
  30. data/.travis.yml +0 -11
  31. data/assets/sig/action_controller.rbs +0 -49
  32. data/assets/sig/action_mailer.rbs +0 -8
  33. data/assets/sig/active_record.rbs +0 -137
  34. data/assets/sig/builtin.rbs +0 -7
  35. data/assets/sig/capybara.rbs +0 -14
  36. data/assets/sig/concurrent.rbs +0 -4
  37. data/assets/sig/erb.rbs +0 -4
  38. data/assets/sig/erubi.rbs +0 -4
  39. data/assets/sig/generated/actionpack.rbs +0 -11831
  40. data/assets/sig/generated/actionview.rbs +0 -10591
  41. data/assets/sig/generated/activejob.rbs +0 -1920
  42. data/assets/sig/generated/activemodel.rbs +0 -4214
  43. data/assets/sig/generated/activerecord-meta-programming.rbs +0 -98
  44. data/assets/sig/generated/activerecord.rbs +0 -24602
  45. data/assets/sig/generated/activesupport.rbs +0 -12613
  46. data/assets/sig/generated/railties.rbs +0 -4687
  47. data/assets/sig/i18n.rbs +0 -4
  48. data/assets/sig/libxml.rbs +0 -10
  49. data/assets/sig/minitest.rbs +0 -13
  50. data/assets/sig/nokogiri.rbs +0 -8
  51. data/assets/sig/patches/README.md +0 -4
  52. data/assets/sig/patches/for_actionpack.rbs +0 -74
  53. data/assets/sig/patches/for_actionview.rbs +0 -19
  54. data/assets/sig/patches/for_activemodel.rbs +0 -11
  55. data/assets/sig/patches/for_activerecord.rbs +0 -84
  56. data/assets/sig/patches/for_activesupport.rbs +0 -48
  57. data/assets/sig/patches/for_railties.rbs +0 -30
  58. data/assets/sig/pg.rbs +0 -5
  59. data/assets/sig/que.rbs +0 -4
  60. data/assets/sig/queue_classic.rbs +0 -4
  61. data/assets/sig/racc.rbs +0 -4
  62. data/assets/sig/rack-test.rbs +0 -6
  63. data/assets/sig/rack.rbs +0 -47
  64. data/assets/sig/rails.rbs +0 -14
  65. data/assets/sig/rdoc.rbs +0 -9
  66. data/assets/sig/sidekiq.rbs +0 -4
  67. data/assets/sig/sneakers.rbs +0 -4
  68. data/assets/sig/stdlib.rbs +0 -24
  69. data/assets/sig/sucker_punch.rbs +0 -4
  70. data/assets/sig/thor.rbs +0 -12
  71. data/assets/sig/tzinfo.rbs +0 -4
  72. data/bin/generate_rbs_from_rails_source_code.rb +0 -201
data/bin/setup CHANGED
@@ -4,5 +4,6 @@ IFS=$'\n\t'
4
4
  set -vx
5
5
 
6
6
  bundle install
7
+ bin/gem_rbs
7
8
 
8
9
  # Do any other automated setup that you need to do here
@@ -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,90 @@
1
1
  module RbsRails
2
2
  module ActiveRecord
3
- def self.class_to_rbs(klass)
4
- Generator.new(klass).generate
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[#{klass.name}, #{relation_class_name}]
31
+ extend _ActiveRecord_Relation_ClassMethods[#{klass_name}, #{relation_class_name}]
24
32
 
25
- #{columns.indent(2)}
26
- #{associations.indent(2)}
27
- #{enum_instance_methods.indent(2)}
28
- #{enum_scope_methods(singleton: true).indent(2)}
29
- #{scopes(singleton: true).indent(2)}
30
- end
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
 
34
47
  private def relation_decl
35
48
  <<~RBS
36
49
  class #{relation_class_name} < ActiveRecord::Relation
37
- include _ActiveRecord_Relation[#{klass.name}]
38
- include Enumerable[#{klass.name}]
39
- #{enum_scope_methods(singleton: false).indent(2)}
40
- #{scopes(singleton: false).indent(2)}
50
+ include _ActiveRecord_Relation[#{klass_name}]
51
+ include Enumerable[#{klass_name}]
52
+ #{enum_scope_methods(singleton: false)}
53
+ #{scopes(singleton: false)}
41
54
  end
42
55
  RBS
43
56
  end
44
57
 
45
58
  private def collection_proxy_decl
46
59
  <<~RBS
47
- class #{klass.name}::ActiveRecord_Associations_CollectionProxy < ActiveRecord::Associations::CollectionProxy
60
+ class ActiveRecord_Associations_CollectionProxy < ActiveRecord::Associations::CollectionProxy
48
61
  end
49
62
  RBS
50
63
  end
51
64
 
52
-
53
65
  private def header
54
- # @type var superclass: Class
55
- superclass = _ = klass.superclass
56
- "class #{klass.name} < #{superclass.name}"
66
+ namespace = +''
67
+ klass_name.split('::').map do |mod_name|
68
+ namespace += "::#{mod_name}"
69
+ mod_object = Object.const_get(namespace)
70
+ case mod_object
71
+ when Class
72
+ # @type var superclass: Class
73
+ superclass = _ = mod_object.superclass
74
+ superclass_name = Util.module_name(superclass)
75
+ @dependencies << superclass_name
76
+
77
+ "class #{mod_name} < #{superclass_name}"
78
+ when Module
79
+ "module #{mod_name}"
80
+ else
81
+ raise 'unreachable'
82
+ end
83
+ end.join("\n")
84
+ end
85
+
86
+ private def footer
87
+ "end\n" * klass_name.split('::').size
57
88
  end
58
89
 
59
90
  private def associations
@@ -67,7 +98,7 @@ module RbsRails
67
98
  private def has_many
68
99
  klass.reflect_on_all_associations(:has_many).map do |a|
69
100
  singular_name = a.name.to_s.singularize
70
- type = a.klass.name
101
+ type = Util.module_name(a.klass)
71
102
  collection_type = "#{type}::ActiveRecord_Associations_CollectionProxy"
72
103
  <<~RUBY.chomp
73
104
  def #{a.name}: () -> #{collection_type}
@@ -80,7 +111,7 @@ module RbsRails
80
111
 
81
112
  private def has_one
82
113
  klass.reflect_on_all_associations(:has_one).map do |a|
83
- type = a.polymorphic? ? 'untyped' : a.klass.name
114
+ type = a.polymorphic? ? 'untyped' : Util.module_name(a.klass)
84
115
  type_optional = optional(type)
85
116
  <<~RUBY.chomp
86
117
  def #{a.name}: () -> #{type}
@@ -95,7 +126,7 @@ module RbsRails
95
126
 
96
127
  private def belongs_to
97
128
  klass.reflect_on_all_associations(:belongs_to).map do |a|
98
- type = a.polymorphic? ? 'untyped' : a.klass.name
129
+ type = a.polymorphic? ? 'untyped' : Util.module_name(a.klass)
99
130
  type_optional = optional(type)
100
131
  <<~RUBY.chomp
101
132
  def #{a.name}: () -> #{type}
@@ -154,6 +185,7 @@ module RbsRails
154
185
  return [] unless ast
155
186
 
156
187
  traverse(ast).map do |node|
188
+ # @type block: nil | Hash[untyped, untyped]
157
189
  next unless node.type == :send
158
190
  next unless node.children[0].nil?
159
191
  next unless node.children[1] == :enum
@@ -192,6 +224,7 @@ module RbsRails
192
224
  return '' unless ast
193
225
 
194
226
  traverse(ast).map do |node|
227
+ # @type block: nil | String
195
228
  next unless node.type == :send
196
229
  next unless node.children[0].nil?
197
230
  next unless node.children[1] == :scope
@@ -206,37 +239,42 @@ module RbsRails
206
239
  next unless body_node.type == :block
207
240
 
208
241
  args = args_to_type(body_node.children[1])
209
- "def #{singleton ? 'self.' : ''}#{name}: (#{args}) -> #{relation_class_name}"
242
+ "def #{singleton ? 'self.' : ''}#{name}: #{args} -> #{relation_class_name}"
210
243
  end.compact.join("\n")
211
244
  end
212
245
 
213
246
  private def args_to_type(args_node)
214
247
  # @type var res: Array[String]
215
248
  res = []
249
+ # @type var block: String?
250
+ block = nil
216
251
  args_node.children.each do |node|
217
252
  case node.type
218
253
  when :arg
219
- res << "untyped"
254
+ res << "untyped #{node.children[0]}"
220
255
  when :optarg
221
- res << "?untyped"
256
+ res << "?untyped #{node.children[0]}"
222
257
  when :kwarg
223
258
  res << "#{node.children[0]}: untyped"
224
259
  when :kwoptarg
225
260
  res << "?#{node.children[0]}: untyped"
261
+ when :restarg
262
+ res << "*untyped #{node.children[0]}"
263
+ when :kwrestarg
264
+ res << "**untyped #{node.children[0]}"
265
+ when :blockarg
266
+ block = " { (*untyped) -> untyped }"
226
267
  else
227
268
  raise "unexpected: #{node}"
228
269
  end
229
270
  end
230
- res.join(", ")
271
+ "(#{res.join(", ")})#{block}"
231
272
  end
232
273
 
233
274
  private def parse_model_file
234
275
  return @parse_model_file if defined?(@parse_model_file)
235
276
 
236
-
237
- # @type var class_name: String
238
- class_name = _ = klass.name
239
- path = Rails.root.join('app/models/', class_name.underscore + '.rb')
277
+ path = Rails.root.join('app/models/', klass_name.underscore + '.rb')
240
278
  return @parse_model_file = nil unless path.exist?
241
279
  return [] unless path.exist?
242
280
 
@@ -257,7 +295,7 @@ module RbsRails
257
295
  end
258
296
 
259
297
  private def relation_class_name
260
- "#{klass.name}::ActiveRecord_Relation"
298
+ "ActiveRecord_Relation"
261
299
  end
262
300
 
263
301
  private def columns
@@ -301,29 +339,25 @@ module RbsRails
301
339
  when :string, :text, :citext, :uuid, :binary
302
340
  'String'
303
341
  when :datetime
304
- # TODO
305
- # ActiveSupport::TimeWithZone.name
306
- 'Time'
342
+ 'ActiveSupport::TimeWithZone'
307
343
  when :boolean
308
- "TrueClass | FalseClass"
344
+ "bool"
309
345
  when :jsonb, :json
310
346
  "untyped"
311
347
  when :date
312
- # TODO
313
- # Date.name
314
- 'untyped'
348
+ 'Date'
315
349
  when :time
316
350
  'Time'
317
351
  when :inet
318
352
  "IPAddr"
319
353
  else
320
- raise "unexpected: #{t.inspect}"
354
+ # Unknown column type, give up
355
+ 'untyped'
321
356
  end
322
357
  end
323
358
 
324
359
  private
325
- # @dynamic klass
326
- attr_reader :klass
360
+ attr_reader :klass, :klass_name
327
361
  end
328
362
  end
329
363
  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
@@ -2,5 +2,5 @@ module RbsRails
2
2
  # Because of copy_signatures is defined by lib/rbs_rails.rb
3
3
  # @dynamic self.copy_signatures
4
4
 
5
- VERSION = "0.4.1"
5
+ VERSION = "0.8.1"
6
6
  end
@@ -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
- # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
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', '>= 0.17'
29
+ spec.add_runtime_dependency 'rbs', '>= 1'
30
30
  end
@@ -1,3 +1,4 @@
1
1
  module FileUtils
2
2
  def self.cp_r: (Pathname, Pathname) -> untyped
3
+ def self.mkdir_p: (String | Array[String], ?untyped) -> Array[String]
3
4
  end
@@ -0,0 +1,6 @@
1
+ module Rake
2
+ class TaskLib
3
+ def desc: (String) -> void
4
+ def task: (Hash[Symbol, Symbol | Array[Symbol]]) ?{ () -> void } -> void
5
+ end
6
+ end