rbs_rails 0.3.0 → 0.7.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +18 -0
  3. data/.gitignore +3 -0
  4. data/.gitmodules +0 -0
  5. data/CHANGELOG.md +8 -0
  6. data/Gemfile +3 -2
  7. data/README.md +23 -43
  8. data/Rakefile +11 -2
  9. data/Steepfile +10 -1
  10. data/assets/sig/pg.rbs +5 -0
  11. data/assets/sig/que.rbs +4 -0
  12. data/assets/sig/queue_classic.rbs +4 -0
  13. data/assets/sig/rack.rbs +1 -0
  14. data/assets/sig/rails.rbs +1 -5
  15. data/assets/sig/sidekiq.rbs +4 -0
  16. data/assets/sig/sneakers.rbs +4 -0
  17. data/assets/sig/sucker_punch.rbs +4 -0
  18. data/bin/add-type-params.rb +7 -0
  19. data/bin/gem_rbs +94 -0
  20. data/bin/postprocess.rb +15 -6
  21. data/bin/rbs +29 -2
  22. data/bin/rbs-prototype-rb.rb +59 -6
  23. data/lib/rbs_rails.rb +4 -0
  24. data/lib/rbs_rails/active_record.rb +100 -50
  25. data/lib/rbs_rails/dependency_builder.rb +43 -0
  26. data/lib/rbs_rails/rake_task.rb +83 -0
  27. data/lib/rbs_rails/util.rb +25 -0
  28. data/lib/rbs_rails/version.rb +1 -1
  29. data/rbs_rails.gemspec +2 -1
  30. data/sig/fileutils.rbs +1 -0
  31. data/sig/rake.rbs +6 -0
  32. data/sig/rbs_rails/active_record.rbs +8 -2
  33. data/sig/rbs_rails/dependency_builder.rbs +9 -0
  34. data/sig/rbs_rails/rake_task.rbs +26 -0
  35. data/sig/rbs_rails/util.rbs +11 -0
  36. metadata +34 -13
  37. data/.travis.yml +0 -8
  38. data/assets/sig/action_controller.rbs +0 -49
  39. data/assets/sig/active_record.rbs +0 -137
  40. data/assets/sig/generated/actionpack.rbs +0 -11677
  41. data/assets/sig/generated/actionview.rbs +0 -10491
  42. data/assets/sig/generated/activemodel.rbs +0 -4139
  43. data/assets/sig/generated/activerecord-meta-programming.rbs +0 -98
  44. data/assets/sig/generated/activerecord.rbs +0 -24023
  45. data/assets/sig/generated/activesupport.rbs +0 -12207
  46. data/assets/sig/generated/railties.rbs +0 -4647
@@ -17,7 +17,7 @@ using Module.new {
17
17
  end
18
18
  end
19
19
 
20
- def process_class_methods(node, decls:, comments:, singleton:)
20
+ def process_class_methods(node, decls:, comments:, context:)
21
21
  return false unless node.type == :ITER
22
22
 
23
23
  fcall = node.children[0]
@@ -37,13 +37,13 @@ using Module.new {
37
37
  decls.push mod
38
38
 
39
39
  each_node [node.children[1]] do |child|
40
- process child, decls: mod.members, comments: comments, singleton: false
40
+ process child, decls: mod.members, comments: comments, context: RBS::Prototype::RB::Context.initial
41
41
  end
42
42
 
43
43
  true
44
44
  end
45
45
 
46
- def process_struct_new(node, decls:, comments:, singleton:)
46
+ def process_struct_new(node, decls:, comments:, context:)
47
47
  return unless node.type == :CDECL
48
48
 
49
49
  name, *_, rhs = node.children
@@ -68,6 +68,7 @@ using Module.new {
68
68
  kls.members << RBS::AST::Members::AttrAccessor.new(
69
69
  name: f.children.first,
70
70
  type: untyped,
71
+ kind: :instance,
71
72
  ivar_name: false,
72
73
  annotations: [],
73
74
  location: nil,
@@ -78,13 +79,65 @@ using Module.new {
78
79
 
79
80
  if body
80
81
  each_node [body] do |child|
81
- process child, decls: kls.members, comments: comments, singleton: false
82
+ process child, decls: kls.members, comments: comments, context: RBS::Prototype::RB::Context.initial
82
83
  end
83
84
  end
84
85
 
85
86
  true
86
87
  end
87
88
 
89
+ def process_attr_internal(node, decls:, comments:, context:)
90
+ case node.type
91
+ when :FCALL, :VCALL
92
+ args = node.children[1]&.children || []
93
+
94
+ case node.children[0]
95
+ when :attr_internal_reader
96
+ args.each do |arg|
97
+ if arg && (name = literal_to_symbol(arg))
98
+ decls << RBS::AST::Members::AttrReader.new(
99
+ name: name,
100
+ ivar_name: :"@_#{name}",
101
+ type: RBS::Types::Bases::Any.new(location: nil),
102
+ kind: context.attribute_kind,
103
+ location: nil,
104
+ comment: comments[node.first_lineno - 1],
105
+ annotations: []
106
+ )
107
+ end
108
+ end
109
+ when :attr_internal_writer
110
+ args.each do |arg|
111
+ if arg && (name = literal_to_symbol(arg))
112
+ decls << RBS::AST::Members::AttrWriter.new(
113
+ name: name,
114
+ ivar_name: :"@_#{name}",
115
+ type: RBS::Types::Bases::Any.new(location: nil),
116
+ kind: context.attribute_kind,
117
+ location: nil,
118
+ comment: comments[node.first_lineno - 1],
119
+ annotations: []
120
+ )
121
+ end
122
+ end
123
+ when :attr_internal_accessor, :attr_internal
124
+ args.each do |arg|
125
+ if arg && (name = literal_to_symbol(arg))
126
+ decls << RBS::AST::Members::AttrAccessor.new(
127
+ name: name,
128
+ ivar_name: :"@_#{name}",
129
+ type: RBS::Types::Bases::Any.new(location: nil),
130
+ kind: context.attribute_kind,
131
+ location: nil,
132
+ comment: comments[node.first_lineno - 1],
133
+ annotations: []
134
+ )
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+
88
141
  def class_new_method_to_type(node)
89
142
  case node.type
90
143
  when :CALL
@@ -122,14 +175,14 @@ using Module.new {
122
175
 
123
176
  def struct_as_superclass
124
177
  name = RBS::TypeName.new(name: 'Struct', namespace: RBS::Namespace.root)
125
- RBS::AST::Declarations::Class::Super.new(name: name, args: ['untyped'])
178
+ RBS::AST::Declarations::Class::Super.new(name: name, args: ['untyped'], location: nil)
126
179
  end
127
180
  end
128
181
  }
129
182
 
130
183
  module PrototypeExt
131
184
  def process(...)
132
- process_class_methods(...) || process_struct_new(...) || super
185
+ process_class_methods(...) || process_struct_new(...) || process_attr_internal(...) || super
133
186
  end
134
187
 
135
188
  def literal_to_type(node)
@@ -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
@@ -66,21 +91,45 @@ module RbsRails
66
91
 
67
92
  private def has_many
68
93
  klass.reflect_on_all_associations(:has_many).map do |a|
69
- "def #{a.name}: () -> #{a.klass.name}::ActiveRecord_Associations_CollectionProxy"
94
+ singular_name = a.name.to_s.singularize
95
+ type = Util.module_name(a.klass)
96
+ collection_type = "#{type}::ActiveRecord_Associations_CollectionProxy"
97
+ <<~RUBY.chomp
98
+ def #{a.name}: () -> #{collection_type}
99
+ def #{a.name}=: (#{collection_type} | Array[#{type}]) -> (#{collection_type} | Array[#{type}])
100
+ def #{singular_name}_ids: () -> Array[Integer]
101
+ def #{singular_name}_ids=: (Array[Integer]) -> Array[Integer]
102
+ RUBY
70
103
  end.join("\n")
71
104
  end
72
105
 
73
106
  private def has_one
74
107
  klass.reflect_on_all_associations(:has_one).map do |a|
75
- type = a.polymorphic? ? 'untyped' : a.klass.name
76
- "def #{a.name}: () -> #{type}"
108
+ type = a.polymorphic? ? 'untyped' : Util.module_name(a.klass)
109
+ type_optional = optional(type)
110
+ <<~RUBY.chomp
111
+ def #{a.name}: () -> #{type}
112
+ def #{a.name}=: (#{type_optional}) -> #{type_optional}
113
+ def build_#{a.name}: (untyped) -> #{type}
114
+ def create_#{a.name}: (untyped) -> #{type}
115
+ def create_#{a.name}!: (untyped) -> #{type}
116
+ def reload_#{a.name}: () -> #{type_optional}
117
+ RUBY
77
118
  end.join("\n")
78
119
  end
79
120
 
80
121
  private def belongs_to
81
122
  klass.reflect_on_all_associations(:belongs_to).map do |a|
82
- type = a.polymorphic? ? 'untyped' : a.klass.name
83
- "def #{a.name}: () -> #{type}"
123
+ type = a.polymorphic? ? 'untyped' : Util.module_name(a.klass)
124
+ type_optional = optional(type)
125
+ <<~RUBY.chomp
126
+ def #{a.name}: () -> #{type}
127
+ def #{a.name}=: (#{type_optional}) -> #{type_optional}
128
+ def build_#{a.name}: (untyped) -> #{type}
129
+ def create_#{a.name}: (untyped) -> #{type}
130
+ def create_#{a.name}!: (untyped) -> #{type}
131
+ def reload_#{a.name}: () -> #{type_optional}
132
+ RUBY
84
133
  end.join("\n")
85
134
  end
86
135
 
@@ -130,6 +179,7 @@ module RbsRails
130
179
  return [] unless ast
131
180
 
132
181
  traverse(ast).map do |node|
182
+ # @type block: nil | Hash[untyped, untyped]
133
183
  next unless node.type == :send
134
184
  next unless node.children[0].nil?
135
185
  next unless node.children[1] == :enum
@@ -168,6 +218,7 @@ module RbsRails
168
218
  return '' unless ast
169
219
 
170
220
  traverse(ast).map do |node|
221
+ # @type block: nil | String
171
222
  next unless node.type == :send
172
223
  next unless node.children[0].nil?
173
224
  next unless node.children[1] == :scope
@@ -209,10 +260,7 @@ module RbsRails
209
260
  private def parse_model_file
210
261
  return @parse_model_file if defined?(@parse_model_file)
211
262
 
212
-
213
- # @type var class_name: String
214
- class_name = _ = klass.name
215
- path = Rails.root.join('app/models/', class_name.underscore + '.rb')
263
+ path = Rails.root.join('app/models/', klass_name.underscore + '.rb')
216
264
  return @parse_model_file = nil unless path.exist?
217
265
  return [] unless path.exist?
218
266
 
@@ -233,12 +281,16 @@ module RbsRails
233
281
  end
234
282
 
235
283
  private def relation_class_name
236
- "#{klass.name}::ActiveRecord_Relation"
284
+ "ActiveRecord_Relation"
237
285
  end
238
286
 
239
287
  private def columns
240
288
  klass.columns.map do |col|
241
- class_name = sql_type_to_class(col.type)
289
+ class_name = if enum_definitions.any? { |hash| hash.key?(col.name) || hash.key?(col.name.to_sym) }
290
+ 'String'
291
+ else
292
+ sql_type_to_class(col.type)
293
+ end
242
294
  class_name_opt = optional(class_name)
243
295
  column_type = col.null ? class_name_opt : class_name
244
296
  sig = <<~EOS
@@ -265,35 +317,33 @@ module RbsRails
265
317
  private def sql_type_to_class(t)
266
318
  case t
267
319
  when :integer
268
- Integer.name
320
+ 'Integer'
269
321
  when :float
270
- Float.name
271
- when :string, :text, :uuid, :binary
272
- String.name
322
+ 'Float'
323
+ when :decimal
324
+ 'BigDecimal'
325
+ when :string, :text, :citext, :uuid, :binary
326
+ 'String'
273
327
  when :datetime
274
- # TODO
275
- # ActiveSupport::TimeWithZone.name
276
- Time.name
328
+ 'ActiveSupport::TimeWithZone'
277
329
  when :boolean
278
- "TrueClass | FalseClass"
330
+ "bool"
279
331
  when :jsonb, :json
280
332
  "untyped"
281
333
  when :date
282
- # TODO
283
- # Date.name
284
- 'untyped'
334
+ 'Date'
285
335
  when :time
286
- Time.name
336
+ 'Time'
287
337
  when :inet
288
338
  "IPAddr"
289
339
  else
290
- raise "unexpected: #{t.inspect}"
340
+ # Unknown column type, give up
341
+ 'untyped'
291
342
  end
292
343
  end
293
344
 
294
345
  private
295
- # @dynamic klass
296
- attr_reader :klass
346
+ attr_reader :klass, :klass_name
297
347
  end
298
348
  end
299
349
  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,83 @@
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_copy_signature_files
18
+ def_generate_rbs_for_models
19
+ def_generate_rbs_for_path_helpers
20
+ def_all
21
+ end
22
+
23
+ def def_all
24
+ desc 'Run all tasks of rbs_rails'
25
+
26
+ deps = [:"#{name}:copy_signature_files", :"#{name}:generate_rbs_for_models", :"#{name}:generate_rbs_for_path_helpers"]
27
+ task("#{name}:all": deps)
28
+ end
29
+
30
+ def def_copy_signature_files
31
+ desc 'Copy RBS files for rbs_rails'
32
+ task("#{name}:copy_signature_files": :environment) do
33
+ require 'rbs_rails'
34
+
35
+ RbsRails.copy_signatures(to: signature_root_dir)
36
+ end
37
+ end
38
+
39
+ def def_generate_rbs_for_models
40
+ desc 'Generate RBS files for Active Record models'
41
+ task("#{name}:generate_rbs_for_models": :environment) do
42
+ require 'rbs_rails'
43
+
44
+ Rails.application.eager_load!
45
+
46
+ dep_builder = DependencyBuilder.new
47
+
48
+ # HACK: for steep
49
+ (_ = ::ActiveRecord::Base).descendants.each do |klass|
50
+ next if klass.abstract_class?
51
+ next if ignore_model_if&.call(klass)
52
+
53
+ path = signature_root_dir / "app/models/#{klass.name.underscore}.rbs"
54
+ path.dirname.mkpath
55
+
56
+ sig = RbsRails::ActiveRecord.class_to_rbs(klass, dependencies: dep_builder.deps)
57
+ path.write sig
58
+ end
59
+
60
+ if dep_rbs = dep_builder.build
61
+ signature_root_dir.join('model_dependencies.rbs').write(dep_rbs)
62
+ end
63
+ end
64
+ end
65
+
66
+ def def_generate_rbs_for_path_helpers
67
+ desc 'Generate RBS files for path helpers'
68
+ task("#{name}:generate_rbs_for_path_helpers": :environment) do
69
+ require 'rbs_rails'
70
+
71
+ out_path = signature_root_dir.join 'path_helpers.rbs'
72
+ rbs = RbsRails::PathHelpers.generate
73
+ out_path.write rbs
74
+ end
75
+ end
76
+
77
+ private def setup_signature_root_dir!
78
+ @signature_root_dir ||= Rails.root / 'sig/rbs_rails'
79
+ @signature_root_dir = Pathname(@signature_root_dir)
80
+ @signature_root_dir.mkpath
81
+ end
82
+ end
83
+ end