rbs_rails 0.3.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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