rbs_rails 0.4.0 → 0.8.0

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/.gitattributes +1 -0
  3. data/.github/workflows/ci.yml +18 -0
  4. data/.gitignore +3 -1
  5. data/.gitmodules +0 -0
  6. data/CHANGELOG.md +13 -0
  7. data/Gemfile +2 -2
  8. data/README.md +24 -43
  9. data/Rakefile +2 -1
  10. data/Steepfile +12 -1
  11. data/bin/add-type-params.rb +2 -1
  12. data/bin/gem_rbs +94 -0
  13. data/bin/postprocess.rb +1 -1
  14. data/bin/rbs +29 -2
  15. data/bin/rbs-prototype-rb.rb +59 -6
  16. data/lib/rbs_rails.rb +4 -0
  17. data/lib/rbs_rails/active_record.rb +74 -46
  18. data/lib/rbs_rails/dependency_builder.rb +43 -0
  19. data/lib/rbs_rails/rake_task.rb +73 -0
  20. data/lib/rbs_rails/util.rb +25 -0
  21. data/lib/rbs_rails/version.rb +1 -1
  22. data/rbs_rails.gemspec +3 -2
  23. data/sig/fileutils.rbs +1 -0
  24. data/sig/rake.rbs +6 -0
  25. data/sig/rbs_rails/active_record.rbs +8 -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 +30 -46
  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 -195
@@ -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,84 @@
1
1
  module RbsRails
2
2
  module ActiveRecord
3
- def self.class_to_rbs(klass)
4
- Generator.new(klass).generate
3
+
4
+ def self.class_to_rbs(klass, dependencies: [])
5
+ Generator.new(klass, dependencies: dependencies).generate
5
6
  end
6
7
 
7
8
  class Generator
8
- def initialize(klass)
9
+ def initialize(klass, dependencies:)
9
10
  @klass = klass
11
+ @dependencies = dependencies
12
+ @klass_name = Util.module_name(klass)
13
+
14
+ namespaces = klass_name.split('::').tap{ |names| names.pop }
15
+ @dependencies << namespaces.join('::') unless namespaces.empty?
10
16
  end
11
17
 
12
18
  def generate
13
- [
14
- klass_decl,
15
- relation_decl,
16
- collection_proxy_decl,
17
- ].join("\n")
19
+ Util.format_rbs klass_decl
18
20
  end
19
21
 
20
22
  private def klass_decl
21
23
  <<~RBS
22
24
  #{header}
23
- extend _ActiveRecord_Relation_ClassMethods[#{klass.name}, #{relation_class_name}]
25
+ extend _ActiveRecord_Relation_ClassMethods[#{klass_name}, #{relation_class_name}]
24
26
 
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
27
+ #{columns}
28
+ #{associations}
29
+ #{enum_instance_methods}
30
+ #{enum_scope_methods(singleton: true)}
31
+ #{scopes(singleton: true)}
32
+
33
+ #{relation_decl}
34
+
35
+ #{collection_proxy_decl}
36
+
37
+ #{footer}
31
38
  RBS
32
39
  end
33
40
 
34
41
  private def relation_decl
35
42
  <<~RBS
36
43
  class #{relation_class_name} < ActiveRecord::Relation
37
- include _ActiveRecord_Relation[#{klass.name}]
38
- include Enumerable[#{klass.name}, self]
39
- #{enum_scope_methods(singleton: false).indent(2)}
40
- #{scopes(singleton: false).indent(2)}
44
+ include _ActiveRecord_Relation[#{klass_name}]
45
+ include Enumerable[#{klass_name}]
46
+ #{enum_scope_methods(singleton: false)}
47
+ #{scopes(singleton: false)}
41
48
  end
42
49
  RBS
43
50
  end
44
51
 
45
52
  private def collection_proxy_decl
46
53
  <<~RBS
47
- class #{klass.name}::ActiveRecord_Associations_CollectionProxy < ActiveRecord::Associations::CollectionProxy
54
+ class ActiveRecord_Associations_CollectionProxy < ActiveRecord::Associations::CollectionProxy
48
55
  end
49
56
  RBS
50
57
  end
51
58
 
52
-
53
59
  private def header
54
- # @type var superclass: Class
55
- superclass = _ = klass.superclass
56
- "class #{klass.name} < #{superclass.name}"
60
+ namespace = +''
61
+ klass_name.split('::').map do |mod_name|
62
+ namespace += "::#{mod_name}"
63
+ mod_object = Object.const_get(namespace)
64
+ case mod_object
65
+ when Class
66
+ # @type var superclass: Class
67
+ superclass = _ = mod_object.superclass
68
+ superclass_name = Util.module_name(superclass)
69
+ @dependencies << superclass_name
70
+
71
+ "class #{mod_name} < #{superclass_name}"
72
+ when Module
73
+ "module #{mod_name}"
74
+ else
75
+ raise 'unreachable'
76
+ end
77
+ end.join("\n")
78
+ end
79
+
80
+ private def footer
81
+ "end\n" * klass_name.split('::').size
57
82
  end
58
83
 
59
84
  private def associations
@@ -67,7 +92,7 @@ module RbsRails
67
92
  private def has_many
68
93
  klass.reflect_on_all_associations(:has_many).map do |a|
69
94
  singular_name = a.name.to_s.singularize
70
- type = a.klass.name
95
+ type = Util.module_name(a.klass)
71
96
  collection_type = "#{type}::ActiveRecord_Associations_CollectionProxy"
72
97
  <<~RUBY.chomp
73
98
  def #{a.name}: () -> #{collection_type}
@@ -80,7 +105,7 @@ module RbsRails
80
105
 
81
106
  private def has_one
82
107
  klass.reflect_on_all_associations(:has_one).map do |a|
83
- type = a.polymorphic? ? 'untyped' : a.klass.name
108
+ type = a.polymorphic? ? 'untyped' : Util.module_name(a.klass)
84
109
  type_optional = optional(type)
85
110
  <<~RUBY.chomp
86
111
  def #{a.name}: () -> #{type}
@@ -95,7 +120,7 @@ module RbsRails
95
120
 
96
121
  private def belongs_to
97
122
  klass.reflect_on_all_associations(:belongs_to).map do |a|
98
- type = a.polymorphic? ? 'untyped' : a.klass.name
123
+ type = a.polymorphic? ? 'untyped' : Util.module_name(a.klass)
99
124
  type_optional = optional(type)
100
125
  <<~RUBY.chomp
101
126
  def #{a.name}: () -> #{type}
@@ -154,6 +179,7 @@ module RbsRails
154
179
  return [] unless ast
155
180
 
156
181
  traverse(ast).map do |node|
182
+ # @type block: nil | Hash[untyped, untyped]
157
183
  next unless node.type == :send
158
184
  next unless node.children[0].nil?
159
185
  next unless node.children[1] == :enum
@@ -192,6 +218,7 @@ module RbsRails
192
218
  return '' unless ast
193
219
 
194
220
  traverse(ast).map do |node|
221
+ # @type block: nil | String
195
222
  next unless node.type == :send
196
223
  next unless node.children[0].nil?
197
224
  next unless node.children[1] == :scope
@@ -206,37 +233,42 @@ module RbsRails
206
233
  next unless body_node.type == :block
207
234
 
208
235
  args = args_to_type(body_node.children[1])
209
- "def #{singleton ? 'self.' : ''}#{name}: (#{args}) -> #{relation_class_name}"
236
+ "def #{singleton ? 'self.' : ''}#{name}: #{args} -> #{relation_class_name}"
210
237
  end.compact.join("\n")
211
238
  end
212
239
 
213
240
  private def args_to_type(args_node)
214
241
  # @type var res: Array[String]
215
242
  res = []
243
+ # @type var block: String?
244
+ block = nil
216
245
  args_node.children.each do |node|
217
246
  case node.type
218
247
  when :arg
219
- res << "untyped"
248
+ res << "untyped #{node.children[0]}"
220
249
  when :optarg
221
- res << "?untyped"
250
+ res << "?untyped #{node.children[0]}"
222
251
  when :kwarg
223
252
  res << "#{node.children[0]}: untyped"
224
253
  when :kwoptarg
225
254
  res << "?#{node.children[0]}: untyped"
255
+ when :restarg
256
+ res << "*untyped #{node.children[0]}"
257
+ when :kwrestarg
258
+ res << "**untyped #{node.children[0]}"
259
+ when :blockarg
260
+ block = " { (*untyped) -> untyped }"
226
261
  else
227
262
  raise "unexpected: #{node}"
228
263
  end
229
264
  end
230
- res.join(", ")
265
+ "(#{res.join(", ")})#{block}"
231
266
  end
232
267
 
233
268
  private def parse_model_file
234
269
  return @parse_model_file if defined?(@parse_model_file)
235
270
 
236
-
237
- # @type var class_name: String
238
- class_name = _ = klass.name
239
- path = Rails.root.join('app/models/', class_name.underscore + '.rb')
271
+ path = Rails.root.join('app/models/', klass_name.underscore + '.rb')
240
272
  return @parse_model_file = nil unless path.exist?
241
273
  return [] unless path.exist?
242
274
 
@@ -257,7 +289,7 @@ module RbsRails
257
289
  end
258
290
 
259
291
  private def relation_class_name
260
- "#{klass.name}::ActiveRecord_Relation"
292
+ "ActiveRecord_Relation"
261
293
  end
262
294
 
263
295
  private def columns
@@ -301,29 +333,25 @@ module RbsRails
301
333
  when :string, :text, :citext, :uuid, :binary
302
334
  'String'
303
335
  when :datetime
304
- # TODO
305
- # ActiveSupport::TimeWithZone.name
306
- 'Time'
336
+ 'ActiveSupport::TimeWithZone'
307
337
  when :boolean
308
- "TrueClass | FalseClass"
338
+ "bool"
309
339
  when :jsonb, :json
310
340
  "untyped"
311
341
  when :date
312
- # TODO
313
- # Date.name
314
- 'untyped'
342
+ 'Date'
315
343
  when :time
316
344
  'Time'
317
345
  when :inet
318
346
  "IPAddr"
319
347
  else
320
- raise "unexpected: #{t.inspect}"
348
+ # Unknown column type, give up
349
+ 'untyped'
321
350
  end
322
351
  end
323
352
 
324
353
  private
325
- # @dynamic klass
326
- attr_reader :klass
354
+ attr_reader :klass, :klass_name
327
355
  end
328
356
  end
329
357
  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,73 @@
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
+ # HACK: for steep
39
+ (_ = ::ActiveRecord::Base).descendants.each do |klass|
40
+ next if klass.abstract_class?
41
+ next if ignore_model_if&.call(klass)
42
+
43
+ path = signature_root_dir / "app/models/#{klass.name.underscore}.rbs"
44
+ path.dirname.mkpath
45
+
46
+ sig = RbsRails::ActiveRecord.class_to_rbs(klass, dependencies: dep_builder.deps)
47
+ path.write sig
48
+ end
49
+
50
+ if dep_rbs = dep_builder.build
51
+ signature_root_dir.join('model_dependencies.rbs').write(dep_rbs)
52
+ end
53
+ end
54
+ end
55
+
56
+ def def_generate_rbs_for_path_helpers
57
+ desc 'Generate RBS files for path helpers'
58
+ task("#{name}:generate_rbs_for_path_helpers": :environment) do
59
+ require 'rbs_rails'
60
+
61
+ out_path = signature_root_dir.join 'path_helpers.rbs'
62
+ rbs = RbsRails::PathHelpers.generate
63
+ out_path.write rbs
64
+ end
65
+ end
66
+
67
+ private def setup_signature_root_dir!
68
+ @signature_root_dir ||= Rails.root / 'sig/rbs_rails'
69
+ @signature_root_dir = Pathname(@signature_root_dir)
70
+ @signature_root_dir.mkpath
71
+ end
72
+ end
73
+ 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.0"
5
+ VERSION = "0.8.0"
6
6
  end
@@ -14,16 +14,17 @@ 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.
21
21
  spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
22
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|gem_rbs)/}) }
23
23
  end
24
24
  spec.bindir = "exe"
25
25
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
26
  spec.require_paths = ["lib"]
27
27
 
28
28
  spec.add_runtime_dependency 'parser'
29
+ spec.add_runtime_dependency 'rbs', '>= 1'
29
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
@@ -1,11 +1,11 @@
1
1
  module RbsRails::ActiveRecord
2
- def self.class_to_rbs: (untyped klass) -> untyped
2
+ def self.class_to_rbs: (untyped klass, ?dependencies: Array[String]) -> untyped
3
3
  end
4
4
 
5
5
  class RbsRails::ActiveRecord::Generator
6
6
  @parse_model_file: nil | Parser::AST::Node
7
7
 
8
- def initialize: (singleton(ActiveRecord::Base) klass) -> untyped
8
+ def initialize: (singleton(ActiveRecord::Base) klass, dependencies: Array[String]) -> untyped
9
9
 
10
10
  def generate: () -> String
11
11
 
@@ -17,6 +17,8 @@ class RbsRails::ActiveRecord::Generator
17
17
 
18
18
  def header: () -> String
19
19
 
20
+ def footer: () -> String
21
+
20
22
  def associations: () -> String
21
23
 
22
24
  def has_many: () -> String
@@ -55,5 +57,9 @@ class RbsRails::ActiveRecord::Generator
55
57
 
56
58
  def optional: (String) -> String
57
59
 
60
+ private
61
+
58
62
  attr_reader klass: singleton(ActiveRecord::Base)
63
+
64
+ attr_reader klass_name: String
59
65
  end