parlour 4.0.1 → 5.0.0.beta.1

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