parlour 2.0.0 → 5.0.0.beta.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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
  3. data/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
  4. data/.gitignore +1 -1
  5. data/.parlour +5 -0
  6. data/.rspec +0 -0
  7. data/.travis.yml +3 -3
  8. data/CHANGELOG.md +64 -0
  9. data/CODE_OF_CONDUCT.md +0 -0
  10. data/Gemfile +0 -0
  11. data/LICENSE.txt +0 -0
  12. data/README.md +233 -19
  13. data/Rakefile +0 -0
  14. data/exe/parlour +109 -4
  15. data/lib/parlour.rb +29 -1
  16. data/lib/parlour/conflict_resolver.rb +75 -27
  17. data/lib/parlour/conversion/converter.rb +34 -0
  18. data/lib/parlour/conversion/rbi_to_rbs.rb +223 -0
  19. data/lib/parlour/debugging.rb +0 -0
  20. data/lib/parlour/detached_rbi_generator.rb +1 -6
  21. data/lib/parlour/detached_rbs_generator.rb +25 -0
  22. data/lib/parlour/generator.rb +34 -0
  23. data/lib/parlour/kernel_hack.rb +0 -0
  24. data/lib/parlour/options.rb +71 -0
  25. data/lib/parlour/parse_error.rb +0 -0
  26. data/lib/parlour/plugin.rb +1 -1
  27. data/lib/parlour/rbi_generator.rb +24 -37
  28. data/lib/parlour/rbi_generator/arbitrary.rb +5 -2
  29. data/lib/parlour/rbi_generator/attribute.rb +14 -5
  30. data/lib/parlour/rbi_generator/class_namespace.rb +8 -3
  31. data/lib/parlour/rbi_generator/constant.rb +28 -8
  32. data/lib/parlour/rbi_generator/enum_class_namespace.rb +32 -5
  33. data/lib/parlour/rbi_generator/extend.rb +5 -2
  34. data/lib/parlour/rbi_generator/include.rb +5 -2
  35. data/lib/parlour/rbi_generator/method.rb +15 -10
  36. data/lib/parlour/rbi_generator/module_namespace.rb +7 -2
  37. data/lib/parlour/rbi_generator/namespace.rb +115 -27
  38. data/lib/parlour/rbi_generator/parameter.rb +13 -7
  39. data/lib/parlour/rbi_generator/rbi_object.rb +19 -78
  40. data/lib/parlour/rbi_generator/struct_class_namespace.rb +110 -0
  41. data/lib/parlour/rbi_generator/struct_prop.rb +139 -0
  42. data/lib/parlour/rbi_generator/type_alias.rb +101 -0
  43. data/lib/parlour/rbs_generator.rb +24 -0
  44. data/lib/parlour/rbs_generator/arbitrary.rb +92 -0
  45. data/lib/parlour/rbs_generator/attribute.rb +82 -0
  46. data/lib/parlour/rbs_generator/block.rb +49 -0
  47. data/lib/parlour/rbs_generator/class_namespace.rb +106 -0
  48. data/lib/parlour/rbs_generator/constant.rb +95 -0
  49. data/lib/parlour/rbs_generator/extend.rb +92 -0
  50. data/lib/parlour/rbs_generator/include.rb +92 -0
  51. data/lib/parlour/rbs_generator/interface_namespace.rb +34 -0
  52. data/lib/parlour/rbs_generator/method.rb +146 -0
  53. data/lib/parlour/rbs_generator/method_signature.rb +104 -0
  54. data/lib/parlour/rbs_generator/module_namespace.rb +35 -0
  55. data/lib/parlour/rbs_generator/namespace.rb +627 -0
  56. data/lib/parlour/rbs_generator/parameter.rb +145 -0
  57. data/lib/parlour/rbs_generator/rbs_object.rb +78 -0
  58. data/lib/parlour/rbs_generator/type_alias.rb +96 -0
  59. data/lib/parlour/type_loader.rb +30 -10
  60. data/lib/parlour/type_parser.rb +440 -43
  61. data/lib/parlour/typed_object.rb +87 -0
  62. data/lib/parlour/types.rb +445 -0
  63. data/lib/parlour/version.rb +1 -1
  64. data/parlour.gemspec +2 -2
  65. data/plugin_examples/foobar_plugin.rb +0 -0
  66. data/rbi/parlour.rbi +1799 -0
  67. metadata +42 -15
  68. data/lib/parlour/rbi_generator/options.rb +0 -74
@@ -16,15 +16,67 @@ command :run do |c|
16
16
  c.description = 'Generates an RBI file from your .parlour file'
17
17
 
18
18
  c.action do |args, options|
19
- configuration = keys_to_symbols(YAML.load_file(File.join(Dir.pwd, '.parlour')))
19
+ working_dir = Dir.pwd
20
+ config_filename = File.join(working_dir, '.parlour')
20
21
 
21
- raise 'you must specify output_file in your .parlour file' unless configuration[:output_file]
22
+ if File.exists?(config_filename)
23
+ configuration = keys_to_symbols(YAML.load_file(config_filename))
24
+ else
25
+ configuration = {}
26
+ end
27
+
28
+ # Output file
29
+ if configuration[:output_file].is_a?(String)
30
+ assumed_format = \
31
+ if configuration[:output_file].end_with?('.rbi')
32
+ :rbi
33
+ elsif configuration[:output_file].end_with?('.rbs')
34
+ :rbs
35
+ else
36
+ raise 'invalid output file; please specify an RBI or RBS file'
37
+ end
38
+
39
+ unless $VERBOSE.nil?
40
+ print Rainbow("Parlour warning: ").yellow.dark.bold
41
+ print Rainbow("CLI: ").magenta.bright.bold
42
+ puts "Specifying output_file in .parlour as a string is deprecated."
43
+ puts "For now, generating an #{assumed_format.to_s.upcase} file based on the file extension."
44
+ puts "Please update your .parlour to use the new form:"
45
+ puts " output_file:"
46
+ puts " #{assumed_format}: #{configuration[:output_file]}"
47
+ end
48
+ configuration[:output_file] = {
49
+ assumed_format => configuration[:output_file]
50
+ }
51
+ end
52
+ configuration[:output_file] ||= {
53
+ rbi: "rbi/#{File.basename(working_dir)}.rbi"
54
+ }
22
55
 
23
56
  # Style defaults
24
57
  configuration[:style] ||= {}
25
58
  configuration[:style][:tab_size] ||= 2
26
59
  configuration[:style][:break_params] ||= 4
27
60
 
61
+ # Parser defaults, set explicitly to false to not run parser
62
+ if configuration[:parser] != false
63
+ configuration[:parser] ||= {}
64
+
65
+ # Input/Output defaults
66
+ configuration[:parser][:root] ||= '.'
67
+
68
+ # Included/Excluded path defaults
69
+ configuration[:parser][:included_paths] ||= ['lib']
70
+ configuration[:parser][:excluded_paths] ||= ['sorbet', 'spec']
71
+
72
+ # Defaults can be overridden but we always want to exclude the output file
73
+ configuration[:parser][:excluded_paths] << configuration[:output_file][:rbi]
74
+ end
75
+
76
+ # Included/Excluded module defaults
77
+ configuration[:included_modules] ||= []
78
+ configuration[:excluded_modules] ||= []
79
+
28
80
  # Require defaults
29
81
  configuration[:requires] ||= []
30
82
  configuration[:relative_requires] ||= []
@@ -53,6 +105,15 @@ command :run do |c|
53
105
  break_params: configuration[:style][:break_params],
54
106
  tab_size: configuration[:style][:tab_size]
55
107
  )
108
+
109
+ if configuration[:parser]
110
+ Parlour::TypeLoader.load_project(
111
+ configuration[:parser][:root],
112
+ inclusions: configuration[:parser][:included_paths],
113
+ exclusions: configuration[:parser][:excluded_paths],
114
+ generator: gen,
115
+ )
116
+ end
56
117
  Parlour::Plugin.run_plugins(plugin_instances, gen)
57
118
 
58
119
  # Run a pass of the conflict resolver
@@ -74,6 +135,14 @@ command :run do |c|
74
135
  choice == 0 ? nil : candidates[choice - 1]
75
136
  end
76
137
 
138
+ if !configuration[:included_modules].empty? || !configuration[:excluded_modules].empty?
139
+ remove_unwanted_modules(
140
+ gen.root,
141
+ included_modules: configuration[:included_modules],
142
+ excluded_modules: configuration[:excluded_modules],
143
+ )
144
+ end
145
+
77
146
  # Figure out strictness levels
78
147
  requested_strictness_levels = plugin_instances.map do |plugin|
79
148
  s = plugin.strictness&.to_s
@@ -97,8 +166,23 @@ command :run do |c|
97
166
  end
98
167
  end
99
168
 
100
- # Write the final RBI
101
- File.write(configuration[:output_file], gen.rbi(strictness))
169
+ # Write the final files
170
+ if configuration[:output_file][:rbi]
171
+ FileUtils.mkdir_p(File.dirname(configuration[:output_file][:rbi]))
172
+ File.write(configuration[:output_file][:rbi], gen.rbi(strictness))
173
+ end
174
+ if configuration[:output_file][:rbs]
175
+ gen.root.generalize_from_rbi!
176
+ rbs_gen = Parlour::RbsGenerator.new
177
+
178
+ converter = Parlour::Conversion::RbiToRbs.new(rbs_gen)
179
+ gen.root.children.each do |child|
180
+ converter.convert_object(child, rbs_gen.root)
181
+ end
182
+
183
+ FileUtils.mkdir_p(File.dirname(configuration[:output_file][:rbs]))
184
+ File.write(configuration[:output_file][:rbs], rbs_gen.rbs)
185
+ end
102
186
  end
103
187
  end
104
188
 
@@ -122,3 +206,24 @@ def keys_to_symbols(hash)
122
206
  ]
123
207
  end.to_h
124
208
  end
209
+
210
+ def remove_unwanted_modules(root, included_modules:, excluded_modules:, prefix: nil)
211
+ root.children.select! do |child|
212
+ module_name = "#{prefix}#{child.name}"
213
+
214
+ if child.respond_to?(:children)
215
+ remove_unwanted_modules(
216
+ child,
217
+ included_modules: included_modules,
218
+ excluded_modules: excluded_modules,
219
+ prefix: "#{module_name}::",
220
+ )
221
+ has_included_children = !child.children.empty?
222
+ end
223
+
224
+ included = included_modules.empty? ? true : included_modules.any? { |m| module_name.start_with?(m) }
225
+ excluded = excluded_modules.empty? ? false : excluded_modules.any? { |m| module_name.start_with?(m) }
226
+
227
+ (included || has_included_children) && !excluded
228
+ end
229
+ end
@@ -9,12 +9,17 @@ require 'parlour/kernel_hack'
9
9
 
10
10
  require 'parlour/plugin'
11
11
 
12
+ require 'parlour/types'
13
+
14
+ require 'parlour/options'
15
+ require 'parlour/typed_object'
16
+ require 'parlour/generator'
12
17
  require 'parlour/rbi_generator/parameter'
13
18
  require 'parlour/rbi_generator/rbi_object'
19
+ require 'parlour/rbi_generator/type_alias'
14
20
  require 'parlour/rbi_generator/method'
15
21
  require 'parlour/rbi_generator/attribute'
16
22
  require 'parlour/rbi_generator/arbitrary'
17
- require 'parlour/rbi_generator/options'
18
23
  require 'parlour/rbi_generator/include'
19
24
  require 'parlour/rbi_generator/extend'
20
25
  require 'parlour/rbi_generator/constant'
@@ -22,9 +27,32 @@ require 'parlour/rbi_generator/namespace'
22
27
  require 'parlour/rbi_generator/module_namespace'
23
28
  require 'parlour/rbi_generator/class_namespace'
24
29
  require 'parlour/rbi_generator/enum_class_namespace'
30
+ require 'parlour/rbi_generator/struct_prop'
31
+ require 'parlour/rbi_generator/struct_class_namespace'
25
32
  require 'parlour/rbi_generator'
26
33
  require 'parlour/detached_rbi_generator'
27
34
 
35
+ require 'parlour/rbs_generator/rbs_object'
36
+ require 'parlour/rbs_generator/type_alias'
37
+ require 'parlour/rbs_generator/namespace'
38
+ require 'parlour/rbs_generator/method'
39
+ require 'parlour/rbs_generator/arbitrary'
40
+ require 'parlour/rbs_generator/attribute'
41
+ require 'parlour/rbs_generator/block'
42
+ require 'parlour/rbs_generator/class_namespace'
43
+ require 'parlour/rbs_generator/constant'
44
+ require 'parlour/rbs_generator/extend'
45
+ require 'parlour/rbs_generator/include'
46
+ require 'parlour/rbs_generator/method_signature'
47
+ require 'parlour/rbs_generator/module_namespace'
48
+ require 'parlour/rbs_generator/interface_namespace'
49
+ require 'parlour/rbs_generator/parameter'
50
+ require 'parlour/rbs_generator'
51
+ require 'parlour/detached_rbs_generator'
52
+
53
+ require 'parlour/conversion/converter'
54
+ require 'parlour/conversion/rbi_to_rbs'
55
+
28
56
  require 'parlour/conflict_resolver'
29
57
 
30
58
  require 'parlour/parse_error'
@@ -1,4 +1,6 @@
1
1
  # typed: true
2
+ require 'set'
3
+
2
4
  module Parlour
3
5
  # Responsible for resolving conflicts (that is, multiple definitions with the
4
6
  # same name) between objects defined in the same namespace.
@@ -11,7 +13,7 @@ module Parlour
11
13
  resolver: T.proc.params(
12
14
  desc: String,
13
15
  choices: T::Array[RbiGenerator::RbiObject]
14
- ).returns(RbiGenerator::RbiObject)
16
+ ).returns(T.nilable(RbiGenerator::RbiObject))
15
17
  ).void
16
18
  end
17
19
  # Given a namespace, attempts to automatically resolve conflicts in the
@@ -21,13 +23,13 @@ module Parlour
21
23
  # All children of the given namespace which are also namespaces are
22
24
  # processed recursively, so passing {RbiGenerator#root} will eliminate all
23
25
  # conflicts in the entire object tree.
24
- #
26
+ #
25
27
  # If automatic resolution is not possible, the block passed to this method
26
28
  # is invoked and passed two arguments: a message on what the conflict is,
27
29
  # and an array of candidate objects. The block should return one of these
28
30
  # candidate objects, which will be kept, and all other definitions are
29
31
  # deleted. Alternatively, the block may return nil, which will delete all
30
- # definitions. The block may be invoked many times from one call to
32
+ # definitions. The block may be invoked many times from one call to
31
33
  # {resolve_conflicts}, one for each unresolvable conflict.
32
34
  #
33
35
  # @param namespace [RbiGenerator::Namespace] The starting namespace to
@@ -42,7 +44,14 @@ module Parlour
42
44
  Debugging.debug_puts(self, Debugging::Tree.begin("Resolving conflicts for #{namespace.name}..."))
43
45
 
44
46
  # Check for multiple definitions with the same name
45
- grouped_by_name_children = namespace.children.group_by(&:name)
47
+ # (Special case here: writer attributes get an "=" appended to their name)
48
+ grouped_by_name_children = namespace.children.group_by do |child|
49
+ if RbiGenerator::Attribute === child && child.kind == :writer
50
+ "#{child.name}=" unless child.name.end_with?('=')
51
+ else
52
+ child.name
53
+ end
54
+ end
46
55
 
47
56
  grouped_by_name_children.each do |name, children|
48
57
  Debugging.debug_puts(self, Debugging::Tree.begin("Checking children named #{name}..."))
@@ -50,7 +59,7 @@ module Parlour
50
59
  if children.length > 1
51
60
  Debugging.debug_puts(self, Debugging::Tree.here("Possible conflict between #{children.length} objects"))
52
61
 
53
- # Special case: do we have two methods, one of which is a class method
62
+ # Special case: do we have two methods, one of which is a class method
54
63
  # and the other isn't? If so, do nothing - this is fine
55
64
  if children.length == 2 &&
56
65
  children.all? { |c| c.is_a?(RbiGenerator::Method) } &&
@@ -60,7 +69,22 @@ module Parlour
60
69
  next
61
70
  end
62
71
 
63
- # Special case: do we have two attributes, one of which is a class
72
+ # Special case: if we remove the namespaces, is everything either an
73
+ # include or an extend? If so, do nothing - this is fine
74
+ if children \
75
+ .reject { |c| c.is_a?(RbiGenerator::Namespace) }
76
+ .then do |x|
77
+ !x.empty? && x.all? do |c|
78
+ c.is_a?(RbiGenerator::Include) || c.is_a?(RbiGenerator::Extend)
79
+ end
80
+ end
81
+ deduplicate_mixins_of_name(namespace, name)
82
+
83
+ Debugging.debug_puts(self, Debugging::Tree.end("Includes/extends do not conflict with namespaces; no resolution required"))
84
+ next
85
+ end
86
+
87
+ # Special case: do we have two attributes, one of which is a class
64
88
  # attribute and the other isn't? If so, do nothing - this is fine
65
89
  if children.length == 2 &&
66
90
  children.all? { |c| c.is_a?(RbiGenerator::Attribute) } &&
@@ -70,13 +94,13 @@ module Parlour
70
94
  next
71
95
  end
72
96
 
73
- # Special case: are they all clearly equal? If so, remove all but one
97
+ # Optimization for Special case: are they all clearly equal? If so, remove all but one
74
98
  if all_eql?(children)
75
99
  Debugging.debug_puts(self, Debugging::Tree.end("All children are identical"))
76
100
 
77
101
  # All of the children are the same, so this deletes all of them
78
102
  namespace.children.delete(T.must(children.first))
79
-
103
+
80
104
  # Re-add one child
81
105
  namespace.children << T.must(children.first)
82
106
  next
@@ -117,14 +141,25 @@ module Parlour
117
141
  choice = resolver.call("Non-namespace item in a differing namespace conflict is not a single method", non_namespaces)
118
142
  non_namespaces = []
119
143
  non_namespaces << choice if choice
120
- end
144
+ end
121
145
  end
122
146
 
123
147
  non_namespaces.each do |x|
124
148
  namespace.children << x
125
149
  end
126
150
 
127
- first, *rest = namespaces
151
+ # For certain namespace types the order matters. For example, if there's
152
+ # both a `Namespace` and `ModuleNamespace` then merging the two would
153
+ # produce different results depending on which is first.
154
+ first_index = (
155
+ namespaces.find_index { |x| RbiGenerator::EnumClassNamespace === x || RbiGenerator::StructClassNamespace === x } ||
156
+ namespaces.find_index { |x| RbiGenerator::ClassNamespace === x } ||
157
+ namespaces.find_index { |x| RbiGenerator::ModuleNamespace === x } ||
158
+ 0
159
+ )
160
+
161
+ first = namespaces.delete_at(first_index)
162
+ rest = namespaces
128
163
  else
129
164
  raise 'unknown merge strategy; this is a Parlour bug'
130
165
  end
@@ -161,16 +196,16 @@ module Parlour
161
196
 
162
197
  sig { params(arr: T::Array[T.untyped]).returns(T.nilable(Symbol)) }
163
198
  # Given an array, if all elements in the array are instances of the exact
164
- # same class or are otherwise mergeable (for example Namespace and
199
+ # same class or are otherwise mergeable (for example Namespace and
165
200
  # ClassNamespace), returns the kind of merge which needs to be made. A
166
201
  # return value of nil indicates that the values cannot be merged.
167
202
  #
168
203
  # The following kinds are available:
169
204
  # - They are all the same. (:normal)
170
- # - There are exactly two types, one of which is Namespace and other is a
205
+ # - There are exactly two types, one of which is Namespace and other is a
171
206
  # subclass of it. (:differing_namespaces)
172
207
  # - One of them is Namespace or a subclass (or both, as described above),
173
- # and the only other is Method. (also :differing_namespaces)
208
+ # and the only other is Method. (also :differing_namespaces)
174
209
  #
175
210
  # @param arr [Array] The array.
176
211
  # @return [Symbol] The merge strategy to use, or nil if they can't be
@@ -182,21 +217,13 @@ module Parlour
182
217
 
183
218
  # Find all the namespaces and non-namespaces
184
219
  namespace_types, non_namespace_types = array_types.partition { |x| x <= RbiGenerator::Namespace }
220
+ exactly_namespace, namespace_subclasses = namespace_types.partition { |x| x == RbiGenerator::Namespace }
221
+
222
+ return nil unless namespace_subclasses.empty? \
223
+ || (namespace_subclasses.length == 1 && namespace_subclasses.first < RbiGenerator::Namespace) \
224
+ || namespace_subclasses.to_set == Set[RbiGenerator::ClassNamespace, RbiGenerator::StructClassNamespace] \
225
+ || namespace_subclasses.to_set == Set[RbiGenerator::ClassNamespace, RbiGenerator::EnumClassNamespace]
185
226
 
186
- # If there are two namespace types, one should be Namespace and the other
187
- # should be a subclass of it
188
- if namespace_types.length == 2
189
- exactly_namespace, exactly_one_subclass = namespace_types.partition { |x| x == RbiGenerator::Namespace }
190
-
191
- return nil unless exactly_namespace.length == 1 \
192
- && exactly_one_subclass.length == 1 \
193
- && exactly_one_subclass.first < RbiGenerator::Namespace
194
- elsif namespace_types.length != 1
195
- # The only other valid number of namespaces is 1, where we don't need to
196
- # check anything
197
- return nil
198
- end
199
-
200
227
  # It's OK, albeit cursed, for there to be a method with the same name as
201
228
  # a namespace (Rainbow does this)
202
229
  return nil if non_namespace_types.length != 0 && non_namespace_types != [RbiGenerator::Method]
@@ -213,5 +240,26 @@ module Parlour
213
240
  def all_eql?(arr)
214
241
  arr.each_cons(2).all? { |x, y| x == y }
215
242
  end
243
+
244
+ sig { params(namespace: RbiGenerator::Namespace, name: T.nilable(String)).void }
245
+ # Given a namespace and a child name, removes all duplicate children that are mixins
246
+ # and that have the given name, except the first found instance.
247
+ #
248
+ # @param namespace [RbiGenerator::Namespace] The namespace to deduplicate mixins in.
249
+ # @param name [String] The name of the mixin modules to deduplicate.
250
+ # @return [void]
251
+ def deduplicate_mixins_of_name(namespace, name)
252
+ found_map = {}
253
+ namespace.children.delete_if do |x|
254
+ # ignore children whose names don't match
255
+ next unless x.name == name
256
+ # ignore children that are not mixins
257
+ next unless x.is_a?(RbiGenerator::Include) || x.is_a?(RbiGenerator::Extend)
258
+
259
+ delete = found_map.key?(x.class)
260
+ found_map[x.class] = true
261
+ delete
262
+ end
263
+ end
216
264
  end
217
265
  end
@@ -0,0 +1,34 @@
1
+ # typed: true
2
+ require 'rainbow'
3
+
4
+ module Parlour
5
+ module Conversion
6
+ # An abstract class which converts between the node trees of two type
7
+ # systems.
8
+ class Converter
9
+ extend T::Sig
10
+ extend T::Helpers
11
+ abstract!
12
+
13
+ def initialize
14
+ @warnings = []
15
+ end
16
+
17
+ sig { returns(T::Array[[String, TypedObject]]) }
18
+ attr_reader :warnings
19
+
20
+ sig { params(msg: String, node: RbiGenerator::RbiObject).void }
21
+ def add_warning(msg, node)
22
+ warnings << [msg, node]
23
+
24
+ return if $VERBOSE.nil?
25
+ class_name = T.must(self.class.name).split('::').last
26
+ print Rainbow("Parlour warning: ").yellow.dark.bold
27
+ print Rainbow("#{class_name}: ").magenta.bright.bold
28
+ puts msg
29
+ print Rainbow(" └ at object: ").blue.bright.bold
30
+ puts node.describe
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,223 @@
1
+ # typed: true
2
+ module Parlour
3
+ module Conversion
4
+ # Converts RBI types to RBS types.
5
+ class RbiToRbs < Converter
6
+ extend T::Sig
7
+
8
+ sig { params(rbs_gen: RbsGenerator).void }
9
+ def initialize(rbs_gen)
10
+ super()
11
+ @rbs_gen = rbs_gen
12
+ end
13
+
14
+ sig { returns(RbsGenerator) }
15
+ attr_reader :rbs_gen
16
+
17
+ sig { params(from: RbiGenerator::Namespace, to: RbsGenerator::Namespace).void }
18
+ def convert_all(from, to)
19
+ from.children.each do |child|
20
+ convert_object(child, to)
21
+ end
22
+ end
23
+
24
+ sig do
25
+ params(
26
+ node: RbiGenerator::RbiObject,
27
+ new_parent: RbsGenerator::Namespace,
28
+ ).void
29
+ end
30
+ def convert_object(node, new_parent)
31
+ case node
32
+ when RbiGenerator::StructClassNamespace
33
+ add_warning 'performing a one-way conversion of an RBI struct to RBS', node
34
+
35
+ klass = new_parent.create_class(node.name)
36
+ klass.add_comments(node.comments)
37
+
38
+ # Create a constructor
39
+ klass.create_method('initialize', [
40
+ RbsGenerator::MethodSignature.new(
41
+ node.props.map do |prop|
42
+ RbsGenerator::Parameter.new(
43
+ "#{prop.name}:",
44
+ type: prop.type,
45
+ required: !prop.optional,
46
+ )
47
+ end,
48
+ nil,
49
+ )
50
+ ])
51
+
52
+ # Make each prop a getter (and setter, if not immutable) attribute
53
+ node.props.each do |prop|
54
+ klass.create_attribute(
55
+ prop.name,
56
+ kind: prop.immutable ? :reader : :accessor,
57
+ type: prop.type,
58
+ )
59
+ end
60
+
61
+ klass
62
+
63
+ when RbiGenerator::EnumClassNamespace
64
+ add_warning 'performing a one-way conversion of an RBI enum to RBS', node
65
+
66
+ klass = new_parent.create_class(node.name)
67
+ klass.add_comments(node.comments)
68
+
69
+ # Define .values
70
+ klass.create_method('values', [
71
+ RbsGenerator::MethodSignature.new([], Types::Array.new(node.name))
72
+ ], class_method: true)
73
+
74
+ # Define each enum variant
75
+ node.enums.each do |variant|
76
+ # We don't care about any extra value
77
+ variant = variant[0] if Array === variant
78
+
79
+ klass.create_constant(variant, type: node.name)
80
+ end
81
+
82
+ klass
83
+
84
+ when RbiGenerator::Arbitrary
85
+ add_warning 'converting type of Arbitrary is likely to cause syntax errors; doing it anyway', node
86
+ new_parent.create_arbitrary(
87
+ code: node.code,
88
+ ).add_comments(node.comments)
89
+
90
+ when RbiGenerator::Attribute
91
+ if node.class_attribute
92
+ add_warning 'RBS does not support class attributes; dropping', node
93
+ return
94
+ end
95
+ new_parent.create_attribute(
96
+ node.name,
97
+ kind: node.kind,
98
+ type: node.type,
99
+ ).add_comments(node.comments)
100
+
101
+ when RbiGenerator::ClassNamespace
102
+ if node.abstract
103
+ add_warning 'RBS does not support abstract classes', node
104
+ end
105
+ klass = new_parent.create_class(
106
+ node.name,
107
+ superclass: node.superclass
108
+ )
109
+ klass.add_comments(node.comments)
110
+ node.children.each do |child|
111
+ convert_object(child, klass)
112
+ end
113
+
114
+ when RbiGenerator::Constant
115
+ if node.eigen_constant
116
+ add_warning 'RBS does not support constants on eigenclasses; dropping', node
117
+ return
118
+ end
119
+ new_parent.create_constant(
120
+ node.name,
121
+ type: node.value,
122
+ ).add_comments(node.comments)
123
+
124
+ when RbiGenerator::Extend
125
+ new_parent.create_extend(node.name).add_comments(node.comments)
126
+
127
+ when RbiGenerator::Include
128
+ new_parent.create_include(node.name).add_comments(node.comments)
129
+
130
+ when RbiGenerator::Method
131
+ # Convert parameters
132
+ parameters = node.parameters
133
+ .reject { |param| param.kind == :block }
134
+ .map do |param|
135
+ RbsGenerator::Parameter.new(
136
+ param.name,
137
+ type: param.type,
138
+ required: param.default.nil?
139
+ )
140
+ end
141
+
142
+ # Find block if there is one
143
+ block_param = node.parameters.find { |param| param.kind == :block }
144
+ if block_param
145
+ if String === block_param.type
146
+ add_warning "block must have a Types::Type for conversion; dropping block", node
147
+ block = nil
148
+ else
149
+ # A nilable proc is an optional block
150
+ block_param_type = block_param.type
151
+ if Types::Nilable === block_param_type && Types::Proc === block_param_type.type
152
+ t = T.cast(block_param_type.type, Types::Proc)
153
+ required = false
154
+ block = RbsGenerator::Block.new(t, required)
155
+ elsif Types::Proc === block_param_type
156
+ t = block_param_type
157
+ required = true
158
+ block = RbsGenerator::Block.new(t, required)
159
+ elsif Types::Untyped === block_param_type
160
+ # Consider there to be a block of unknown types
161
+ block = RbsGenerator::Block.new(
162
+ Types::Proc.new(
163
+ [
164
+ Types::Proc::Parameter.new('*args', Types::Untyped.new),
165
+ Types::Proc::Parameter.new('**kwargs', Types::Untyped.new),
166
+ ],
167
+ Types::Untyped.new,
168
+ ),
169
+ false,
170
+ )
171
+ else
172
+ add_warning 'block type must be a Types::Proc (or nilable one); dropping block', node
173
+ end
174
+ end
175
+ else
176
+ block = nil
177
+ end
178
+
179
+ new_parent.create_method(
180
+ node.name,
181
+ [
182
+ RbsGenerator::MethodSignature.new(
183
+ parameters,
184
+ node.return_type,
185
+ block: block,
186
+ type_parameters: node.type_parameters,
187
+ )
188
+ ],
189
+ class_method: node.class_method,
190
+ ).add_comments(node.comments)
191
+
192
+ when RbiGenerator::ModuleNamespace
193
+ if node.interface
194
+ rbs_node = new_parent.create_interface(
195
+ node.name,
196
+ )
197
+ else
198
+ rbs_node = new_parent.create_module(
199
+ node.name,
200
+ )
201
+ end
202
+ rbs_node.add_comments(node.comments)
203
+ node.children.each do |child|
204
+ convert_object(child, rbs_node)
205
+ end
206
+
207
+ when RbiGenerator::Namespace
208
+ add_warning 'unspecialized namespaces are not supposed to be in the tree; you may run into issues', node
209
+ namespace = RbsGenerator::Namespace.new(rbs_gen)
210
+ namespace.add_comments(node.comments)
211
+ node.children.each do |child|
212
+ convert_object(child, namespace)
213
+ end
214
+ new_parent.children << namespace
215
+
216
+ else
217
+ raise "missing conversion for #{node.describe}"
218
+ # TODO: stick a T.absurd here
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end