parlour 2.1.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 889d6730ffefc764887706e5dadc4441e66cdf32e4567e809f7e634f511b7dfc
4
- data.tar.gz: 9e9ab14b6618c209c8d194bf6e5557398689f0656247cac9561064dbcd541a6a
3
+ metadata.gz: 1e2b9021c22592632f30a0a51c17cfbf3838c18061638297e9437c1dd34b5c9f
4
+ data.tar.gz: 337e2ea6d8212e6c2d16d2f8f5577b16bfd1c8ffa41197fad70f9dc7c51620b5
5
5
  SHA512:
6
- metadata.gz: ea57b34b827ced92fdd1d28387217c0a5bc7027d94319cac6ffb238847a2820e9c164df86c9b2c43e60826cb1b362000c7c2bfc7f1f63b9b998959c70a3be377
7
- data.tar.gz: fb10c8c783aea69e412d1a6eb67dd736f3db377168a21f8025e28fdb76acfc94ed20f1f7b9a17dafbac39ebcc2d36846b350d04c3e59882b546143df374b9f41
6
+ metadata.gz: 22e0196918f8214827f9904e45fff8689f4609ec71683ad4aca36c8eb75593e19f5d0ec4e5a5e034afdca1d3bdf7f0d9482bfb24b0e3e667553c13b5cb5b65a6
7
+ data.tar.gz: 2b322d3dcdce74985b2104a8d8beb8ac0a7b6b4de1bcc8af7288d5334d715765285cba032017c7ab6e5dccde045b284dd8056e57515e13f32f319de26fdc0666
@@ -10,10 +10,6 @@ rvm:
10
10
  - 2.6
11
11
  - 2.7
12
12
  - ruby-head
13
- matrix:
14
- allow_failures:
15
- - rvm: 2.3
16
- - rvm: ruby-head
17
13
 
18
14
  jobs:
19
15
  include:
@@ -27,3 +23,6 @@ jobs:
27
23
  keep_history: true
28
24
  on:
29
25
  branch: master
26
+ allow_failures:
27
+ - rvm: 2.3
28
+ - rvm: ruby-head
@@ -3,6 +3,19 @@ All notable changes to this project will be documented in this file.
3
3
 
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
5
5
 
6
+ ## [3.0.0] - 2020-05-15
7
+ ### Added
8
+ - `T::Struct` classes can now be generated and parsed.
9
+ - `T::Enum` classes can now be parsed.
10
+ - Constants are now parsed.
11
+ - `TypeParser` now detects and parses methods which do not have a `sig`.
12
+ **Potentially breaking if there is a strict set of methods you are expecting Parlour to detect.**
13
+
14
+ ### Fixed
15
+ - "Specialized" classes, such as enums and now structs, have had many erroneous
16
+ conflicts with standard classes or namespaces fixed.
17
+ - Attributes writers and methods with the same name no longer conflict incorrectly.
18
+
6
19
  ## [2.1.0] - 2020-03-22
7
20
  ### Added
8
21
  - Files can now be excluded from the `TypeLoader`.
@@ -22,6 +22,8 @@ require 'parlour/rbi_generator/namespace'
22
22
  require 'parlour/rbi_generator/module_namespace'
23
23
  require 'parlour/rbi_generator/class_namespace'
24
24
  require 'parlour/rbi_generator/enum_class_namespace'
25
+ require 'parlour/rbi_generator/struct_prop'
26
+ require 'parlour/rbi_generator/struct_class_namespace'
25
27
  require 'parlour/rbi_generator'
26
28
  require 'parlour/detached_rbi_generator'
27
29
 
@@ -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.
@@ -42,8 +44,15 @@ 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)
46
-
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
55
+
47
56
  grouped_by_name_children.each do |name, children|
48
57
  Debugging.debug_puts(self, Debugging::Tree.begin("Checking children named #{name}..."))
49
58
 
@@ -60,6 +69,20 @@ module Parlour
60
69
  next
61
70
  end
62
71
 
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
+
82
+ Debugging.debug_puts(self, Debugging::Tree.end("Includes/extends do not conflict with namespaces; no resolution required"))
83
+ next
84
+ end
85
+
63
86
  # Special case: do we have two attributes, one of which is a class
64
87
  # attribute and the other isn't? If so, do nothing - this is fine
65
88
  if children.length == 2 &&
@@ -117,14 +140,25 @@ module Parlour
117
140
  choice = resolver.call("Non-namespace item in a differing namespace conflict is not a single method", non_namespaces)
118
141
  non_namespaces = []
119
142
  non_namespaces << choice if choice
120
- end
143
+ end
121
144
  end
122
145
 
123
146
  non_namespaces.each do |x|
124
147
  namespace.children << x
125
148
  end
126
149
 
127
- first, *rest = namespaces
150
+ # For certain namespace types the order matters. For example, if there's
151
+ # both a `Namespace` and `ModuleNamespace` then merging the two would
152
+ # produce different results depending on which is first.
153
+ first_index = (
154
+ namespaces.find_index { |x| RbiGenerator::EnumClassNamespace === x || RbiGenerator::StructClassNamespace === x } ||
155
+ namespaces.find_index { |x| RbiGenerator::ClassNamespace === x } ||
156
+ namespaces.find_index { |x| RbiGenerator::ModuleNamespace === x } ||
157
+ 0
158
+ )
159
+
160
+ first = namespaces.delete_at(first_index)
161
+ rest = namespaces
128
162
  else
129
163
  raise 'unknown merge strategy; this is a Parlour bug'
130
164
  end
@@ -161,16 +195,16 @@ module Parlour
161
195
 
162
196
  sig { params(arr: T::Array[T.untyped]).returns(T.nilable(Symbol)) }
163
197
  # 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
198
+ # same class or are otherwise mergeable (for example Namespace and
165
199
  # ClassNamespace), returns the kind of merge which needs to be made. A
166
200
  # return value of nil indicates that the values cannot be merged.
167
201
  #
168
202
  # The following kinds are available:
169
203
  # - They are all the same. (:normal)
170
- # - There are exactly two types, one of which is Namespace and other is a
204
+ # - There are exactly two types, one of which is Namespace and other is a
171
205
  # subclass of it. (:differing_namespaces)
172
206
  # - One of them is Namespace or a subclass (or both, as described above),
173
- # and the only other is Method. (also :differing_namespaces)
207
+ # and the only other is Method. (also :differing_namespaces)
174
208
  #
175
209
  # @param arr [Array] The array.
176
210
  # @return [Symbol] The merge strategy to use, or nil if they can't be
@@ -182,21 +216,13 @@ module Parlour
182
216
 
183
217
  # Find all the namespaces and non-namespaces
184
218
  namespace_types, non_namespace_types = array_types.partition { |x| x <= RbiGenerator::Namespace }
219
+ exactly_namespace, namespace_subclasses = namespace_types.partition { |x| x == RbiGenerator::Namespace }
220
+
221
+ return nil unless namespace_subclasses.empty? \
222
+ || (namespace_subclasses.length == 1 && namespace_subclasses.first < RbiGenerator::Namespace) \
223
+ || namespace_subclasses.to_set == Set[RbiGenerator::ClassNamespace, RbiGenerator::StructClassNamespace] \
224
+ || namespace_subclasses.to_set == Set[RbiGenerator::ClassNamespace, RbiGenerator::EnumClassNamespace]
185
225
 
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
226
  # It's OK, albeit cursed, for there to be a method with the same name as
201
227
  # a namespace (Rainbow does this)
202
228
  return nil if non_namespace_types.length != 0 && non_namespace_types != [RbiGenerator::Method]
@@ -80,10 +80,32 @@ module Parlour
80
80
  # @param others [Array<RbiGenerator::RbiObject>] An array of other {EnumClassNamespace} instances.
81
81
  # @return [Boolean] Whether this instance may be merged with them.
82
82
  def mergeable?(others)
83
- others = T.cast(others, T::Array[EnumClassNamespace]) rescue (return false)
83
+ others = T.cast(others, T::Array[Namespace]) rescue (return false)
84
84
  all = others + [self]
85
+ all_enums = T.cast(all.select { |x| EnumClassNamespace === x }, T::Array[EnumClassNamespace])
85
86
 
86
- T.must(super && all.map(&:enums).uniq.length <= 1)
87
+ T.must(super && all_enums.map { |e| e.enums.sort }.reject(&:empty?).uniq.length <= 1)
88
+ end
89
+
90
+ sig do
91
+ override.params(
92
+ others: T::Array[RbiGenerator::RbiObject]
93
+ ).void
94
+ end
95
+ # Given an array of {EnumClassNamespace} instances, merges them into this one.
96
+ # You MUST ensure that {mergeable?} is true for those instances.
97
+ #
98
+ # @param others [Array<RbiGenerator::RbiObject>] An array of other {EnumClassNamespace} instances.
99
+ # @return [void]
100
+ def merge_into_self(others)
101
+ super
102
+
103
+ others.each do |other|
104
+ next unless EnumClassNamespace === other
105
+ other = T.cast(other, EnumClassNamespace)
106
+
107
+ @enums = other.enums if enums.empty?
108
+ end
87
109
  end
88
110
  end
89
111
  end
@@ -196,6 +196,35 @@ module Parlour
196
196
  new_enum_class
197
197
  end
198
198
 
199
+ sig do
200
+ params(
201
+ name: String,
202
+ final: T::Boolean,
203
+ props: T.nilable(T::Array[StructProp]),
204
+ abstract: T::Boolean,
205
+ block: T.nilable(T.proc.params(x: StructClassNamespace).void)
206
+ ).returns(StructClassNamespace)
207
+ end
208
+ # Creates a new struct class definition as a child of this namespace.
209
+ #
210
+ # @example Create a person struct.
211
+ # namespace.create_class('Person', props: [
212
+ # Parlour::RbiGenerator::StructProp.new('name', 'String')
213
+ # ])
214
+ #
215
+ # @param name [String] The name of this class.
216
+ # @param final [Boolean] Whether this namespace is final.
217
+ # @param props [Array<StructProp>] The props of the struct.
218
+ # @param abstract [Boolean] A boolean indicating whether this class is abstract.
219
+ # @param block A block which the new instance yields itself to.
220
+ # @return [EnumClassNamespace]
221
+ def create_struct_class(name, final: false, props: nil, abstract: false, &block)
222
+ new_struct_class = StructClassNamespace.new(generator, name, final, props || [], abstract, &block)
223
+ move_next_comments(new_struct_class)
224
+ children << new_struct_class
225
+ new_struct_class
226
+ end
227
+
199
228
  sig do
200
229
  params(
201
230
  name: String,
@@ -0,0 +1,103 @@
1
+ # typed: true
2
+ module Parlour
3
+ class RbiGenerator
4
+ # Represents an struct definition; that is, a class which subclasses
5
+ # +T::Struct+ and declares `prop` members.
6
+ class StructClassNamespace < ClassNamespace
7
+ extend T::Sig
8
+
9
+ sig do
10
+ params(
11
+ generator: RbiGenerator,
12
+ name: String,
13
+ final: T::Boolean,
14
+ props: T::Array[StructProp],
15
+ abstract: T::Boolean,
16
+ block: T.nilable(T.proc.params(x: StructClassNamespace).void)
17
+ ).void
18
+ end
19
+ # Creates a new struct class definition.
20
+ # @note You should use {Namespace#create_struct_class} rather than this directly.
21
+ #
22
+ # @param generator [RbiGenerator] The current RbiGenerator.
23
+ # @param name [String] The name of this class.
24
+ # @param final [Boolean] Whether this namespace is final.
25
+ # @param props [Array<StructProp>] The props of the struct.
26
+ # @param abstract [Boolean] A boolean indicating whether this class is abstract.
27
+ # @param block A block which the new instance yields itself to.
28
+ # @return [void]
29
+ def initialize(generator, name, final, props, abstract, &block)
30
+ super(generator, name, final, 'T::Struct', abstract, &block)
31
+ @props = props
32
+ end
33
+
34
+ sig { returns(T::Array[StructProp]) }
35
+ # The props of the struct.
36
+ # @return [Array<StructProp>]
37
+ attr_reader :props
38
+
39
+ sig do
40
+ override.params(
41
+ indent_level: Integer,
42
+ options: Options
43
+ ).returns(T::Array[String])
44
+ end
45
+ # Generates the RBI lines for the body of this struct. This consists of
46
+ # {props}, {includes}, {extends} and {children}.
47
+ #
48
+ # @param indent_level [Integer] The indentation level to generate the lines at.
49
+ # @param options [Options] The formatting options to use.
50
+ # @return [Array<String>] The RBI lines for the body, formatted as specified.
51
+ def generate_body(indent_level, options)
52
+ result = []
53
+ props.each do |prop|
54
+ result << options.indented(indent_level, prop.to_prop_call)
55
+ end
56
+ result << ''
57
+
58
+ result + super
59
+ end
60
+
61
+ sig do
62
+ override.params(
63
+ others: T::Array[RbiGenerator::RbiObject]
64
+ ).returns(T::Boolean)
65
+ end
66
+ # Given an array of {StructClassNamespace} instances, returns true if they may
67
+ # be merged into this instance using {merge_into_self}. For instances to
68
+ # be mergeable, they must either all be abstract or all not be abstract,
69
+ # and they must define the same superclass (or none at all).
70
+ #
71
+ # @param others [Array<RbiGenerator::RbiObject>] An array of other {StructClassNamespace} instances.
72
+ # @return [Boolean] Whether this instance may be merged with them.
73
+ def mergeable?(others)
74
+ others = T.cast(others, T::Array[Namespace]) rescue (return false)
75
+ all = others + [self]
76
+ all_structs = T.cast(all.select { |x| StructClassNamespace === x }, T::Array[StructClassNamespace])
77
+
78
+ T.must(super && all_structs.map { |s| s.props.map(&:to_prop_call).sort }.reject(&:empty?).uniq.length <= 1)
79
+ end
80
+
81
+ sig do
82
+ override.params(
83
+ others: T::Array[RbiGenerator::RbiObject]
84
+ ).void
85
+ end
86
+ # Given an array of {StructClassNamespace} instances, merges them into this one.
87
+ # You MUST ensure that {mergeable?} is true for those instances.
88
+ #
89
+ # @param others [Array<RbiGenerator::RbiObject>] An array of other {StructClassNamespace} instances.
90
+ # @return [void]
91
+ def merge_into_self(others)
92
+ super
93
+
94
+ others.each do |other|
95
+ next unless StructClassNamespace === other
96
+ other = T.cast(other, StructClassNamespace)
97
+
98
+ @props = other.props if props.empty?
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,136 @@
1
+ # typed: true
2
+ module Parlour
3
+ class RbiGenerator
4
+ # Represents a +T::Struct+ property.
5
+ class StructProp
6
+ extend T::Sig
7
+
8
+ sig do
9
+ params(
10
+ name: String,
11
+ type: String,
12
+ optional: T.nilable(T.any(T::Boolean, Symbol)),
13
+ enum: T.nilable(String),
14
+ dont_store: T.nilable(T::Boolean),
15
+ foreign: T.nilable(String),
16
+ default: T.nilable(String),
17
+ factory: T.nilable(String),
18
+ immutable: T.nilable(T::Boolean),
19
+ array: T.nilable(String),
20
+ override: T.nilable(T::Boolean),
21
+ redaction: T.nilable(String),
22
+ ).void
23
+ end
24
+ # Create a new struct property.
25
+ #
26
+ # For documentation on all optional properties, please refer to the
27
+ # documentation for T::Struct within the sorbet-runtime gem:
28
+ # https://github.com/sorbet/sorbet/blob/master/gems/sorbet-runtime/lib/types/props/_props.rb#L31-L106
29
+ #
30
+ # @param name [String] The name of this property.
31
+ # @param type [String] A Sorbet string of this property's type, such as
32
+ # +"String"+.
33
+ # @return [void]
34
+ def initialize(name, type, optional: nil, enum: nil, dont_store: nil,
35
+ foreign: nil, default: nil, factory: nil, immutable: nil, array: nil,
36
+ override: nil, redaction: nil)
37
+
38
+ @name = name
39
+ @type = type
40
+ @optional = optional
41
+ @enum = enum
42
+ @dont_store = dont_store
43
+ @foreign = foreign
44
+ @default = default
45
+ @factory = factory
46
+ @immutable = immutable
47
+ @array = array
48
+ @override = override
49
+ @redaction = redaction
50
+ end
51
+
52
+ sig { params(other: Object).returns(T::Boolean) }
53
+ # Returns true if this instance is equal to another instance.
54
+ #
55
+ # @param other [Object] The other instance. If this is not a {StructProp} (or a
56
+ # subclass of it), this will always return false.
57
+ # @return [Boolean]
58
+ def ==(other)
59
+ StructProp === other &&
60
+ name == other.name &&
61
+ type == other.type &&
62
+ optional == other.optional &&
63
+ enum == other.enum &&
64
+ dont_store == other.dont_store &&
65
+ foreign == other.foreign &&
66
+ default == other.default &&
67
+ factory == other.factory &&
68
+ immutable == other.immutable &&
69
+ array == other.array &&
70
+ override == other.override &&
71
+ redaction == other.redaction
72
+ end
73
+
74
+ sig { returns(String) }
75
+ # The name of this parameter, including any prefixes or suffixes such as
76
+ # +*+.
77
+ # @return [String]
78
+ attr_reader :name
79
+
80
+ sig { returns(T.nilable(String)) }
81
+ # A Sorbet string of this parameter's type, such as +"String"+ or
82
+ # +"T.untyped"+.
83
+ # @return [String, nil]
84
+ attr_reader :type
85
+
86
+ sig { returns(T.nilable(T.any(T::Boolean, Symbol))) }
87
+ attr_reader :optional
88
+
89
+ sig { returns(T.nilable(String)) }
90
+ attr_reader :enum
91
+
92
+ sig { returns(T.nilable(T::Boolean)) }
93
+ attr_reader :dont_store
94
+
95
+ sig { returns(T.nilable(String)) }
96
+ attr_reader :foreign
97
+
98
+ sig { returns(T.nilable(String)) }
99
+ attr_reader :default
100
+
101
+ sig { returns(T.nilable(String)) }
102
+ attr_reader :factory
103
+
104
+ sig { returns(T.nilable(T::Boolean)) }
105
+ attr_reader :immutable
106
+
107
+ sig { returns(T.nilable(String)) }
108
+ attr_reader :array
109
+
110
+ sig { returns(T.nilable(T::Boolean)) }
111
+ attr_reader :override
112
+
113
+ sig { returns(T.nilable(String)) }
114
+ attr_reader :redaction
115
+
116
+ # The optional properties available on instances of this class.
117
+ EXTRA_PROPERTIES = %i{
118
+ optional enum dont_store foreign default factory immutable array override redaction
119
+ }
120
+
121
+ sig { returns(String) }
122
+ # Returns the +prop+ call required to create this property.
123
+ # @return [String]
124
+ def to_prop_call
125
+ call = "prop :#{name}, #{type}"
126
+
127
+ EXTRA_PROPERTIES.each do |extra_property|
128
+ value = send extra_property
129
+ call += ", #{extra_property}: #{value}" unless value.nil?
130
+ end
131
+
132
+ call
133
+ end
134
+ end
135
+ end
136
+ end
@@ -108,7 +108,9 @@ module Parlour
108
108
  buffer = Parser::Source::Buffer.new(filename)
109
109
  buffer.source = source
110
110
 
111
- TypeParser.new(Parser::CurrentRuby.new.parse(buffer))
111
+ # || special case handles parser returning nil on an empty file
112
+ parsed = Parser::CurrentRuby.new.parse(buffer) || Parser::AST::Node.new(:body)
113
+ TypeParser.new(parsed)
112
114
  end
113
115
 
114
116
  sig { returns(Parser::AST::Node) }
@@ -169,18 +171,103 @@ module Parlour
169
171
  top_level ||= new_obj
170
172
  end if parent_names
171
173
 
172
- final_obj = RbiGenerator::ClassNamespace.new(
173
- DetachedRbiGenerator.new,
174
- this_name.to_s,
175
- final,
176
- node_to_s(superclass),
177
- abstract,
178
- ) do |c|
179
- c.children.concat(parse_path_to_object(path.child(2))) if body
180
- c.create_includes(includes)
181
- c.create_extends(extends)
174
+ # Instantiate the correct kind of class
175
+ if ['T::Struct', '::T::Struct'].include?(node_to_s(superclass))
176
+ # Find all of this struct's props and consts
177
+ # The body is typically a `begin` element but when there's only
178
+ # one node there's no wrapping block and instead it would directly
179
+ # be the node.
180
+ prop_nodes = body.nil? ? [] :
181
+ (body.type == :begin ? body.to_a : [body]).select { |x| x.type == :send && [:prop, :const].include?(x.to_a[1]) }
182
+
183
+ props = prop_nodes.map do |prop_node|
184
+ _, prop_type, name_node, type_node, extras_hash_node = *prop_node
185
+
186
+ # "const" is just "prop ..., immutable: true"
187
+ extras_hash = extras_hash_node.to_a.map do |pair_node|
188
+ key_node, value_node = *pair_node
189
+ parse_err 'prop/const key must be a symbol', prop_node unless key_node.type == :sym
190
+ key = key_node.to_a.first
191
+
192
+ value =
193
+ if key == :default
194
+ T.must(node_to_s(value_node))
195
+ else
196
+ case value_node.type
197
+ when :true
198
+ true
199
+ when :false
200
+ false
201
+ when :sym
202
+ value_node.to_a.first
203
+ else
204
+ T.must(node_to_s(value_node))
205
+ end
206
+ end
207
+
208
+ [key, value]
209
+ end.to_h
210
+
211
+ if prop_type == :const
212
+ parse_err 'const cannot use immutable key', prop_node unless extras_hash[:immutable].nil?
213
+ extras_hash[:immutable] = true
214
+ end
215
+
216
+ # Get prop/const name
217
+ parse_err 'prop/const name must be a symbol or string', prop_node unless [:sym, :str].include?(name_node.type)
218
+ name = name_node.to_a.first.to_s
219
+
220
+ RbiGenerator::StructProp.new(
221
+ name,
222
+ T.must(node_to_s(type_node)),
223
+ **T.unsafe(extras_hash)
224
+ )
225
+ end
226
+
227
+ final_obj = RbiGenerator::StructClassNamespace.new(
228
+ DetachedRbiGenerator.new,
229
+ this_name.to_s,
230
+ final,
231
+ props,
232
+ abstract,
233
+ )
234
+ elsif ['T::Enum', '::T::Enum'].include?(node_to_s(superclass))
235
+ # Look for (block (send nil :enums) ...) structure
236
+ enums_node = body.nil? ? nil :
237
+ (body.type == :begin ? body.to_a : [body]).find { |x| x.type == :block && x.to_a[0].type == :send && x.to_a[0].to_a[1] == :enums }
238
+
239
+ # Find the constant assigments within this block
240
+ constant_nodes = enums_node.to_a[2].to_a
241
+
242
+ # Convert this to an array to enums as EnumClassNamespace expects
243
+ enums = constant_nodes.map do |constant_node|
244
+ _, name, new_node = *constant_node
245
+ serialize_value = node_to_s(new_node.to_a[2])
246
+
247
+ serialize_value ? [name.to_s, serialize_value] : name.to_s
248
+ end
249
+
250
+ final_obj = RbiGenerator::EnumClassNamespace.new(
251
+ DetachedRbiGenerator.new,
252
+ this_name.to_s,
253
+ final,
254
+ enums,
255
+ abstract,
256
+ )
257
+ else
258
+ final_obj = RbiGenerator::ClassNamespace.new(
259
+ DetachedRbiGenerator.new,
260
+ this_name.to_s,
261
+ final,
262
+ node_to_s(superclass),
263
+ abstract,
264
+ )
182
265
  end
183
266
 
267
+ final_obj.children.concat(parse_path_to_object(path.child(2))) if body
268
+ final_obj.create_includes(includes)
269
+ final_obj.create_extends(extends)
270
+
184
271
  if target
185
272
  target.children << final_obj
186
273
  [top_level]
@@ -230,16 +317,19 @@ module Parlour
230
317
  when :send, :block
231
318
  if sig_node?(node)
232
319
  parse_sig_into_methods(path, is_within_eigenclass: is_within_eigenclass)
320
+ elsif node.type == :send &&
321
+ [:attr_reader, :attr_writer, :attr_accessor].include?(node.to_a[1]) &&
322
+ !previous_sibling_sig_node?(path)
323
+ parse_method_into_methods(path, is_within_eigenclass: is_within_eigenclass)
233
324
  else
234
325
  []
235
326
  end
236
327
  when :def, :defs
237
- # TODO: Support for defs without sigs
238
- # If so, we need some kind of state machine to determine whether
239
- # they've already been dealt with by the "when :send" clause and
240
- # #parse_sig_into_methods.
241
- # If not, just ignore this.
242
- []
328
+ if previous_sibling_sig_node?(path)
329
+ []
330
+ else
331
+ parse_method_into_methods(path, is_within_eigenclass: is_within_eigenclass)
332
+ end
243
333
  when :sclass
244
334
  parse_err 'cannot access eigen of non-self object', node unless node.to_a[0].type == :self
245
335
  parse_path_to_object(path.child(1), is_within_eigenclass: true)
@@ -248,6 +338,13 @@ module Parlour
248
338
  node.to_a.length.times.map do |c|
249
339
  parse_path_to_object(path.child(c), is_within_eigenclass: is_within_eigenclass)
250
340
  end.flatten
341
+ when :casgn
342
+ _, name, body = *node
343
+ [Parlour::RbiGenerator::Constant.new(
344
+ DetachedRbiGenerator.new,
345
+ name: T.must(name).to_s,
346
+ value: T.must(node_to_s(body)),
347
+ )]
251
348
  else
252
349
  if unknown_node_errors
253
350
  parse_err "don't understand node type #{node.type}", node
@@ -490,6 +587,110 @@ module Parlour
490
587
  end
491
588
  end
492
589
 
590
+ sig { params(path: NodePath, is_within_eigenclass: T::Boolean).returns(T::Array[RbiGenerator::Method]) }
591
+ # Given a path to a method in the AST, finds the associated definition and
592
+ # parses them into methods.
593
+ # Usually this will return one method; the only exception currently is for
594
+ # attributes, where multiple can be declared in one call, e.g.
595
+ # +attr_reader :x, :y, :z+.
596
+ #
597
+ # @param [NodePath] path The sig to parse.
598
+ # @param [Boolean] is_within_eigenclass Whether the method definition this sig is
599
+ # associated with appears inside an eigenclass definition. If true, the
600
+ # returned method is made a class method. If the method definition
601
+ # is already a class method, an exception is thrown as the method will be
602
+ # a class method of the eigenclass, which Parlour can't represent.
603
+ # @return [<RbiGenerator::Method>] The parsed methods.
604
+ def parse_method_into_methods(path, is_within_eigenclass: false)
605
+ # A :def node represents a definition like "def x; end"
606
+ # A :defs node represents a definition like "def self.x; end"
607
+ def_node = path.traverse(ast)
608
+ case def_node.type
609
+ when :def
610
+ class_method = false
611
+ def_names = [def_node.to_a[0].to_s]
612
+ def_params = def_node.to_a[1].to_a
613
+ kind = :def
614
+ when :defs
615
+ parse_err 'targeted definitions on a non-self target are not supported', def_node \
616
+ unless def_node.to_a[0].type == :self
617
+ class_method = true
618
+ def_names = [def_node.to_a[1].to_s]
619
+ def_params = def_node.to_a[2].to_a
620
+ kind = :def
621
+ when :send
622
+ target, method_name, *parameters = *def_node
623
+
624
+ parse_err 'node after a sig must be a method definition', def_node \
625
+ unless [:attr_reader, :attr_writer, :attr_accessor].include?(method_name) \
626
+ || target != nil
627
+
628
+ parse_err 'typed attribute should have at least one name', def_node if parameters&.length == 0
629
+
630
+ kind = :attr
631
+ attr_direction = method_name.to_s.gsub('attr_', '').to_sym
632
+ def_names = T.must(parameters).map { |param| param.to_a[0].to_s }
633
+ class_method = false
634
+ else
635
+ parse_err 'node after a sig must be a method definition', def_node
636
+ end
637
+
638
+ if is_within_eigenclass
639
+ parse_err 'cannot represent multiple levels of eigenclassing', def_node if class_method
640
+ class_method = true
641
+ end
642
+
643
+ return_type = "T.untyped"
644
+
645
+ if kind == :def
646
+ parameters = def_params.map do |def_param|
647
+ arg_name = def_param.to_a[0]
648
+
649
+ # TODO: anonymous restarg
650
+ full_name = arg_name.to_s
651
+ full_name = "*#{arg_name}" if def_param.type == :restarg
652
+ full_name = "**#{arg_name}" if def_param.type == :kwrestarg
653
+ full_name = "#{arg_name}:" if def_param.type == :kwarg || def_param.type == :kwoptarg
654
+ full_name = "&#{arg_name}" if def_param.type == :blockarg
655
+
656
+ default = def_param.to_a[1] ? node_to_s(def_param.to_a[1]) : nil
657
+ type = nil
658
+
659
+ RbiGenerator::Parameter.new(full_name, type: type, default: default)
660
+ end
661
+
662
+ # There should only be one ever here, but future-proofing anyway
663
+ def_names.map do |def_name|
664
+ RbiGenerator::Method.new(
665
+ DetachedRbiGenerator.new,
666
+ def_name,
667
+ parameters,
668
+ return_type,
669
+ class_method: class_method
670
+ )
671
+ end
672
+ elsif kind == :attr
673
+ case attr_direction
674
+ when :reader, :accessor, :writer
675
+ attr_type = return_type
676
+ else
677
+ raise "unknown attribute direction #{attr_direction}"
678
+ end
679
+
680
+ def_names.map do |def_name|
681
+ RbiGenerator::Attribute.new(
682
+ DetachedRbiGenerator.new,
683
+ def_name,
684
+ attr_direction,
685
+ attr_type,
686
+ class_attribute: class_method
687
+ )
688
+ end
689
+ else
690
+ raise "unknown definition kind #{kind}"
691
+ end
692
+ end
693
+
493
694
  protected
494
695
 
495
696
  sig { params(node: T.nilable(Parser::AST::Node)).returns(T::Array[Symbol]) }
@@ -518,6 +719,24 @@ module Parlour
518
719
  node.to_a[0].to_a[1] == :sig
519
720
  end
520
721
 
722
+ sig { params(path: NodePath).returns(T::Boolean) }
723
+ # Given a path, returns a boolean indicating whether the previous sibling
724
+ # represents a call to "sig" with a block.
725
+ #
726
+ # @param [NodePath] path The path to the namespace definition.
727
+ # @return [Boolean] True if that node represents a "sig" call, false
728
+ # otherwise.
729
+ def previous_sibling_sig_node?(path)
730
+ previous_sibling = path.sibling(-1)
731
+ previous_node = previous_sibling.traverse(ast)
732
+ sig_node?(previous_node)
733
+ rescue IndexError, ArgumentError, TypeError
734
+ # `sibling` call could raise IndexError or ArgumentError if reaching into negative indices
735
+ # `traverse` call could raise TypeError if path doesn't return Parser::AST::Node
736
+
737
+ false
738
+ end
739
+
521
740
  sig { params(node: T.nilable(Parser::AST::Node)).returns(T.nilable(String)) }
522
741
  # Given an AST node, returns the source code from which it was constructed.
523
742
  # If the given AST node is nil, this returns nil.
@@ -1,5 +1,5 @@
1
1
  # typed: strong
2
2
  module Parlour
3
3
  # The library version.
4
- VERSION = '2.1.0'
4
+ VERSION = '3.0.0'
5
5
  end
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
28
28
  spec.add_dependency "parser"
29
29
 
30
30
  spec.add_development_dependency "bundler", "~> 2.0"
31
- spec.add_development_dependency "rake", "~> 10.0"
31
+ spec.add_development_dependency "rake", ">= 12.3.3"
32
32
  spec.add_development_dependency "rspec", "~> 3.0"
33
33
  spec.add_development_dependency "sorbet"
34
34
  spec.add_development_dependency "simplecov"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parlour
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Christiansen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-03-22 00:00:00.000000000 Z
11
+ date: 2020-05-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sorbet-runtime
@@ -84,16 +84,16 @@ dependencies:
84
84
  name: rake
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "~>"
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
- version: '10.0'
89
+ version: 12.3.3
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - "~>"
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
- version: '10.0'
96
+ version: 12.3.3
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: rspec
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -177,6 +177,8 @@ files:
177
177
  - lib/parlour/rbi_generator/options.rb
178
178
  - lib/parlour/rbi_generator/parameter.rb
179
179
  - lib/parlour/rbi_generator/rbi_object.rb
180
+ - lib/parlour/rbi_generator/struct_class_namespace.rb
181
+ - lib/parlour/rbi_generator/struct_prop.rb
180
182
  - lib/parlour/type_loader.rb
181
183
  - lib/parlour/type_parser.rb
182
184
  - lib/parlour/version.rb