parlour 4.0.1 → 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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/README.md +208 -20
  4. data/exe/parlour +45 -6
  5. data/lib/parlour.rb +27 -1
  6. data/lib/parlour/conversion/converter.rb +34 -0
  7. data/lib/parlour/conversion/rbi_to_rbs.rb +223 -0
  8. data/lib/parlour/detached_rbs_generator.rb +25 -0
  9. data/lib/parlour/generator.rb +34 -0
  10. data/lib/parlour/options.rb +71 -0
  11. data/lib/parlour/rbi_generator.rb +24 -37
  12. data/lib/parlour/rbi_generator/arbitrary.rb +5 -2
  13. data/lib/parlour/rbi_generator/attribute.rb +14 -5
  14. data/lib/parlour/rbi_generator/class_namespace.rb +8 -3
  15. data/lib/parlour/rbi_generator/constant.rb +17 -6
  16. data/lib/parlour/rbi_generator/enum_class_namespace.rb +8 -3
  17. data/lib/parlour/rbi_generator/extend.rb +5 -2
  18. data/lib/parlour/rbi_generator/include.rb +5 -2
  19. data/lib/parlour/rbi_generator/method.rb +15 -10
  20. data/lib/parlour/rbi_generator/module_namespace.rb +7 -2
  21. data/lib/parlour/rbi_generator/namespace.rb +39 -12
  22. data/lib/parlour/rbi_generator/parameter.rb +11 -5
  23. data/lib/parlour/rbi_generator/rbi_object.rb +19 -78
  24. data/lib/parlour/rbi_generator/struct_class_namespace.rb +9 -2
  25. data/lib/parlour/rbi_generator/struct_prop.rb +12 -9
  26. data/lib/parlour/rbi_generator/type_alias.rb +101 -0
  27. data/lib/parlour/rbs_generator.rb +24 -0
  28. data/lib/parlour/rbs_generator/arbitrary.rb +92 -0
  29. data/lib/parlour/rbs_generator/attribute.rb +82 -0
  30. data/lib/parlour/rbs_generator/block.rb +49 -0
  31. data/lib/parlour/rbs_generator/class_namespace.rb +106 -0
  32. data/lib/parlour/rbs_generator/constant.rb +95 -0
  33. data/lib/parlour/rbs_generator/extend.rb +92 -0
  34. data/lib/parlour/rbs_generator/include.rb +92 -0
  35. data/lib/parlour/rbs_generator/interface_namespace.rb +34 -0
  36. data/lib/parlour/rbs_generator/method.rb +146 -0
  37. data/lib/parlour/rbs_generator/method_signature.rb +104 -0
  38. data/lib/parlour/rbs_generator/module_namespace.rb +35 -0
  39. data/lib/parlour/rbs_generator/namespace.rb +627 -0
  40. data/lib/parlour/rbs_generator/parameter.rb +145 -0
  41. data/lib/parlour/rbs_generator/rbs_object.rb +78 -0
  42. data/lib/parlour/rbs_generator/type_alias.rb +96 -0
  43. data/lib/parlour/type_parser.rb +152 -0
  44. data/lib/parlour/typed_object.rb +87 -0
  45. data/lib/parlour/types.rb +445 -0
  46. data/lib/parlour/version.rb +1 -1
  47. data/parlour.gemspec +1 -1
  48. data/rbi/parlour.rbi +982 -76
  49. metadata +30 -7
  50. data/lib/parlour/rbi_generator/options.rb +0 -74
@@ -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
@@ -0,0 +1,25 @@
1
+ # typed: true
2
+
3
+ module Parlour
4
+ class DetachedRbsGenerator < RbsGenerator
5
+ sig { returns(T.untyped) }
6
+ def detached!
7
+ raise "cannot call methods on a detached RBS generator"
8
+ end
9
+
10
+ sig { override.returns(Options) }
11
+ def options
12
+ detached!
13
+ end
14
+
15
+ sig { override.returns(T.nilable(Plugin)) }
16
+ def current_plugin
17
+ nil
18
+ end
19
+
20
+ sig { override.returns(String) }
21
+ def rbs
22
+ detached!
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,34 @@
1
+ # typed: true
2
+ module Parlour
3
+ class Generator
4
+ extend T::Sig
5
+
6
+ sig { params(break_params: Integer, tab_size: Integer, sort_namespaces: T::Boolean).void }
7
+ # Creates a new generator.
8
+ #
9
+ # @param break_params [Integer] If there are at least this many parameters in a
10
+ # signature, then it is broken onto separate lines.
11
+ # @param tab_size [Integer] The number of spaces to use per indent.
12
+ # @param sort_namespaces [Boolean] Whether to sort all items within a
13
+ # namespace alphabetically.
14
+ # @return [void]
15
+ def initialize(break_params: 4, tab_size: 2, sort_namespaces: false)
16
+ @options = Options.new(
17
+ break_params: break_params,
18
+ tab_size: tab_size,
19
+ sort_namespaces: sort_namespaces
20
+ )
21
+ end
22
+
23
+ sig { overridable.returns(Options) }
24
+ # The formatting options for this generator. Currently ignored.
25
+ # @return [Options]
26
+ attr_reader :options
27
+
28
+ sig { overridable.returns(T.nilable(Plugin)) }
29
+ # The plugin which is currently generating new definitions.
30
+ # {Plugin#run_plugins} controls this value.
31
+ # @return [Plugin, nil]
32
+ attr_accessor :current_plugin
33
+ end
34
+ end
@@ -0,0 +1,71 @@
1
+ # typed: true
2
+ module Parlour
3
+ # A set of immutable formatting options.
4
+ class Options
5
+ extend T::Sig
6
+
7
+ sig { params(break_params: Integer, tab_size: Integer, sort_namespaces: T::Boolean).void }
8
+ # Creates a new set of formatting options.
9
+ #
10
+ # @example Create Options with +break_params+ of +4+ and +tab_size+ of +2+.
11
+ # Parlour::Options.new(break_params: 4, tab_size: 2)
12
+ #
13
+ # @param break_params [Integer] If there are at least this many parameters in a
14
+ # signature, then it is broken onto separate lines.
15
+ # @param tab_size [Integer] The number of spaces to use per indent.
16
+ # @param sort_namespaces [Boolean] Whether to sort all items within a
17
+ # namespace alphabetically.
18
+ # @return [void]
19
+ def initialize(break_params:, tab_size:, sort_namespaces:)
20
+ @break_params = break_params
21
+ @tab_size = tab_size
22
+ @sort_namespaces = sort_namespaces
23
+ end
24
+
25
+ sig { returns(Integer) }
26
+ # If there are at least this many parameters in a signature, then it
27
+ # is broken onto separate lines.
28
+ #
29
+ # # With break_params: 5
30
+ # sig { params(name: String, age: Integer, hobbies: T::Array(String), country: Symbol).void }
31
+ #
32
+ # # With break_params: 4
33
+ # sig do
34
+ # params(
35
+ # name: String,
36
+ # age: Integer,
37
+ # hobbies: T::Array(String),
38
+ # country: Symbol
39
+ # ).void
40
+ # end
41
+ #
42
+ # @return [Integer]
43
+ attr_reader :break_params
44
+
45
+ sig { returns(Integer) }
46
+ # The number of spaces to use per indent.
47
+ # @return [Integer]
48
+ attr_reader :tab_size
49
+
50
+ sig { returns(T::Boolean) }
51
+ # Whether to sort all items within a namespace alphabetically.
52
+ # Items which are typically grouped together, such as "include" or
53
+ # "extend" calls, will remain grouped together when sorted.
54
+ # If true, items are sorted by their name when the RBI is generated.
55
+ # If false, items are generated in the order they are added to the
56
+ # namespace.
57
+ # @return [Boolean]
58
+ attr_reader :sort_namespaces
59
+
60
+ sig { params(level: Integer, str: String).returns(String) }
61
+ # Returns a string indented to the given indent level, according to the
62
+ # set {tab_size}.
63
+ #
64
+ # @param level [Integer] The indent level, as an integer. 0 is totally unindented.
65
+ # @param str [String] The string to indent.
66
+ # @return [String] The indented string.
67
+ def indented(level, str)
68
+ " " * (level * tab_size) + str
69
+ end
70
+ end
71
+ end
@@ -1,55 +1,42 @@
1
1
  # typed: true
2
2
  module Parlour
3
3
  # The RBI generator.
4
- class RbiGenerator
5
- extend T::Sig
4
+ class RbiGenerator < Generator
5
+ # For backwards compatibility.
6
+ # Before Parlour 5.0, Parlour::Options was Parlour::RbiGenerator::Options.
7
+ Options = Parlour::Options
6
8
 
7
- sig { params(break_params: Integer, tab_size: Integer, sort_namespaces: T::Boolean).void }
8
- # Creates a new RBI generator.
9
- #
10
- # @example Create a default generator.
11
- # generator = Parlour::RbiGenerator.new
12
- #
13
- # @example Create a generator with a custom +tab_size+ of 3.
14
- # generator = Parlour::RbiGenerator.new(tab_size: 3)
15
- #
16
- # @param break_params [Integer] If there are at least this many parameters in a
17
- # Sorbet +sig+, then it is broken onto separate lines.
18
- # @param tab_size [Integer] The number of spaces to use per indent.
19
- # @param sort_namespaces [Boolean] Whether to sort all items within a
20
- # namespace alphabetically.
21
- # @return [void]
22
- def initialize(break_params: 4, tab_size: 2, sort_namespaces: false)
23
- @options = Options.new(
24
- break_params: break_params,
25
- tab_size: tab_size,
26
- sort_namespaces: sort_namespaces
27
- )
28
- @root = Namespace.new(self)
9
+ def initialize(**hash)
10
+ super
11
+ @root = RbiGenerator::Namespace.new(self)
29
12
  end
30
13
 
31
- sig { overridable.returns(Options) }
32
- # The formatting options for this generator.
33
- # @return [Options]
34
- attr_reader :options
35
-
36
- sig { overridable.returns(Namespace) }
14
+ sig { overridable.returns(RbiGenerator::Namespace) }
37
15
  # The root {Namespace} of this generator.
38
16
  # @return [Namespace]
39
17
  attr_reader :root
40
18
 
41
- sig { overridable.returns(T.nilable(Plugin)) }
42
- # The plugin which is currently generating new definitions.
43
- # {Plugin#run_plugins} controls this value.
44
- # @return [Plugin, nil]
45
- attr_accessor :current_plugin
46
-
47
19
  sig { overridable.params(strictness: String).returns(String) }
48
20
  # Returns the complete contents of the generated RBI file as a string.
49
21
  #
50
22
  # @return [String] The generated RBI file
51
23
  def rbi(strictness = 'strong')
52
- "# typed: #{strictness}\n" + root.generate_rbi(0, options).join("\n") + "\n"
24
+ # TODO: Early test option - convert to RBS if requested
25
+ # Absolutely remove this later on
26
+ if ENV['PARLOUR_CONVERT_TO_RBS']
27
+ # Perform conversion
28
+ root.generalize_from_rbi!
29
+ rbs_gen = Parlour::RbsGenerator.new
30
+ converter = Parlour::Conversion::RbiToRbs.new(rbs_gen)
31
+ root.children.each do |child|
32
+ converter.convert_object(child, rbs_gen.root)
33
+ end
34
+
35
+ # Write the final RBS
36
+ rbs_gen.rbs
37
+ else
38
+ "# typed: #{strictness}\n" + root.generate_rbi(0, options).join("\n") + "\n"
39
+ end
53
40
  end
54
41
  end
55
42
  end