parlour 1.0.0 → 2.0.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f603ddca16331f43a36b4120b7d12aa5a4b7c83f753e8527b91bc1391109e9fe
4
- data.tar.gz: f550cdf4cc358dd6ac0c21853ad26756d4729c7f28f3695af53d25775b3f45f0
3
+ metadata.gz: 382331994bebd14df33b96b084d725b0375fa84e0a3aa263e944bad22bb5ef3f
4
+ data.tar.gz: 33b9014173a85bc6dddec5aa3ad38cc7e23d869636694fbbe96d4e07c7e78886
5
5
  SHA512:
6
- metadata.gz: a962191c42becbe6dbe213eb63be142df100643786006093e5663af51f80049890c26ff8974b718cfd89198387cf0ec801b8d3e10cbf032aaf3aa86f746fda97
7
- data.tar.gz: 1bfafd7afbcf81105379d7b747e0c204e47fdd593b0b1a67e659275b0ebd8d236753e48b5bc05896e1e1759ac6495ad3e8132f85a497f28b35c436ddb3cb1585
6
+ metadata.gz: 6ecd35a473c689909a7ef3b84af799cfa718b1901929743676c557d95f6dd717b3c9cd60dcec1c4274ce1ecc4e69310aa3213505c6f3ef479cfa580ccb39f187
7
+ data.tar.gz: 7dddda7cb3a9d123e74f0ed218d62b17697f63451b7389d6d5c92bb4712d41b1cc920380eb8ee3b84c7916be5ad8c32c5d2bb0f3b46c7828acefa45fa0581683
data/.gitignore CHANGED
@@ -8,6 +8,7 @@
8
8
  /tmp/
9
9
  Gemfile.lock
10
10
  /.vscode
11
+ /sorbte/rbi/hidden-definitions/errors.txt
11
12
 
12
13
  # rspec failure tracking
13
14
  .rspec_status
@@ -8,6 +8,7 @@ rvm:
8
8
  - 2.4
9
9
  - 2.5
10
10
  - 2.6
11
+ - 2.7
11
12
  - ruby-head
12
13
  matrix:
13
14
  allow_failures:
@@ -3,6 +3,31 @@ 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
+ ## [2.0.0] - 2020-02-10
7
+ ### Added
8
+ - Parlour can now load types back out of RBI files or Ruby source files by
9
+ parsing them, using the `TypeLoader` module.
10
+ - The `sort_namespaces` option has been added to `RbiGenerator` to
11
+ alphabetically sort all namespace children.
12
+ - Added `DetachedRbiGenerator`, which can be used to create instances of
13
+ `RbiObject` which are not bound to a particular set of options. This is
14
+ used internally for `TypeLoader`.
15
+ - Parlour will now create a polyfill for `then` on `Kernel`.
16
+ - Added `NodePath#sibling`.
17
+
18
+ ### Changed
19
+ - Version restrictions on _rainbow_ and _commander_ have been slightly relaxed.
20
+ - The version of _sorbet-runtime_ is now restricted to `>= 0.5` after previously
21
+ being unrestricted.
22
+ - Instances of `Namespace` can now be merged with instances of `ClassNamespace`
23
+ or `MethodNamespace`.
24
+ - A method and a namespace can now have the same name without causing a merge
25
+ conflict.
26
+
27
+ ### Fixed
28
+ - Parameter names are no longer nilable.
29
+ **Potentially breaking if you were doing something cursed with Parameter names.**
30
+
6
31
  ## [1.0.0] - 2019-11-22
7
32
  ### Added
8
33
  - `T::Enum` classes have been implemented, and can be generated using
data/README.md CHANGED
@@ -3,7 +3,8 @@
3
3
  [![Build Status](https://travis-ci.org/AaronC81/parlour.svg?branch=master)](https://travis-ci.org/AaronC81/parlour)
4
4
  ![Gem](https://img.shields.io/gem/v/parlour.svg)
5
5
 
6
- Parlour is an RBI generator and merger for Sorbet. It consists of two key parts:
6
+ Parlour is an RBI generator, merger and parser for Sorbet. It consists of three
7
+ key parts:
7
8
 
8
9
  - The generator, which outputs beautifully formatted RBI files, created using
9
10
  an intuitive DSL.
@@ -12,6 +13,9 @@ Parlour is an RBI generator and merger for Sorbet. It consists of two key parts:
12
13
  RBIs for the same codebase. These are combined automatically as much as
13
14
  possible, but any other conflicts can be resolved manually through prompts.
14
15
 
16
+ - The parser, which can read an RBI and convert it back into a tree of
17
+ generator objects.
18
+
15
19
  ## Why should I use this?
16
20
 
17
21
  - Parlour enables **much easier creation of RBI generators**, as formatting
@@ -21,6 +25,8 @@ Parlour is an RBI generator and merger for Sorbet. It consists of two key parts:
21
25
  single command and consolidating all of their definitions into a single
22
26
  RBI output file.
23
27
 
28
+ - You can **effortlessly build tools which need to access types within an RBI**;
29
+ no need to write your own parser!
24
30
 
25
31
  Please [**read the wiki**](https://github.com/AaronC81/parlour/wiki) to get
26
32
  started!
@@ -147,6 +153,27 @@ plugins:
147
153
  Gem3::Plugin: {}
148
154
  ```
149
155
 
156
+ ## Parsing RBIs
157
+
158
+ You can either parse individual RBI files, or point Parlour to the root of a
159
+ project and it will locate, parse and merge all RBI files.
160
+
161
+ Note that Parlour isn't limited to just RBIs; it can parse inline `sigs` out
162
+ of your Ruby source too!
163
+
164
+ ```ruby
165
+ require 'parlour'
166
+
167
+ # Return the object tree of a particular file
168
+ Parlour::TypeLoader.load_file('path/to/your/file.rbis')
169
+
170
+ # Return the object tree for an entire Sorbet project - slow but thorough!
171
+ Parlour::TypeLoader.load_project('root/of/the/project')
172
+ ```
173
+
174
+ The structure of the returned object trees is identical to those you would
175
+ create when generating an RBI, built of instances of `RbiObject` subclasses.
176
+
150
177
  ## Parlour Plugins
151
178
 
152
179
  _Have you written an awesome Parlour plugin? Please submit a PR to add it to this list!_
@@ -23,5 +23,10 @@ require 'parlour/rbi_generator/module_namespace'
23
23
  require 'parlour/rbi_generator/class_namespace'
24
24
  require 'parlour/rbi_generator/enum_class_namespace'
25
25
  require 'parlour/rbi_generator'
26
+ require 'parlour/detached_rbi_generator'
26
27
 
27
28
  require 'parlour/conflict_resolver'
29
+
30
+ require 'parlour/parse_error'
31
+ require 'parlour/type_parser'
32
+ require 'parlour/type_loader'
@@ -88,11 +88,11 @@ module Parlour
88
88
  namespace.children.delete(c)
89
89
  end
90
90
 
91
- # We can only try to resolve automatically if they're all the same
92
- # type of object, so check that first
93
- children_type = single_type_of_array(children)
94
- unless children_type
95
- Debugging.debug_puts(self, Debugging::Tree.end("Children are different types; requesting manual resolution"))
91
+ # Check that the types of the given objects allow them to be merged,
92
+ # and get the strategy to use
93
+ strategy = merge_strategy(children)
94
+ unless strategy
95
+ Debugging.debug_puts(self, Debugging::Tree.end("Children are unmergeable types; requesting manual resolution"))
96
96
  # The types aren't the same, so ask the resolver what to do, and
97
97
  # insert that (if not nil)
98
98
  choice = resolver.call("Different kinds of definition for the same name", children)
@@ -100,8 +100,36 @@ module Parlour
100
100
  next
101
101
  end
102
102
 
103
+ case strategy
104
+ when :normal
105
+ first, *rest = children
106
+ when :differing_namespaces
107
+ # Let the namespaces be merged normally, but handle the method here
108
+ namespaces, non_namespaces = children.partition { |x| RbiGenerator::Namespace === x }
109
+
110
+ # If there is any non-namespace item in this conflict, it should be
111
+ # a single method
112
+ if non_namespaces.length != 0
113
+ unless non_namespaces.length == 1 && RbiGenerator::Method === non_namespaces.first
114
+ Debugging.debug_puts(self, Debugging::Tree.end("Non-namespace item in a differing namespace conflict is not a single method; requesting manual resolution"))
115
+ # The types aren't the same, so ask the resolver what to do, and
116
+ # insert that (if not nil)
117
+ choice = resolver.call("Non-namespace item in a differing namespace conflict is not a single method", non_namespaces)
118
+ non_namespaces = []
119
+ non_namespaces << choice if choice
120
+ end
121
+ end
122
+
123
+ non_namespaces.each do |x|
124
+ namespace.children << x
125
+ end
126
+
127
+ first, *rest = namespaces
128
+ else
129
+ raise 'unknown merge strategy; this is a Parlour bug'
130
+ end
131
+
103
132
  # Can the children merge themselves automatically? If so, let them
104
- first, *rest = children
105
133
  first, rest = T.must(first), T.must(rest)
106
134
  if T.must(first).mergeable?(T.must(rest))
107
135
  Debugging.debug_puts(self, Debugging::Tree.end("Children are all mergeable; resolving automatically"))
@@ -131,15 +159,49 @@ module Parlour
131
159
 
132
160
  private
133
161
 
134
- sig { params(arr: T::Array[T.untyped]).returns(T.nilable(Class)) }
162
+ sig { params(arr: T::Array[T.untyped]).returns(T.nilable(Symbol)) }
135
163
  # Given an array, if all elements in the array are instances of the exact
136
- # same class, returns that class. If they are not, returns nil.
164
+ # same class or are otherwise mergeable (for example Namespace and
165
+ # ClassNamespace), returns the kind of merge which needs to be made. A
166
+ # return value of nil indicates that the values cannot be merged.
167
+ #
168
+ # The following kinds are available:
169
+ # - They are all the same. (:normal)
170
+ # - There are exactly two types, one of which is Namespace and other is a
171
+ # subclass of it. (:differing_namespaces)
172
+ # - One of them is Namespace or a subclass (or both, as described above),
173
+ # and the only other is Method. (also :differing_namespaces)
137
174
  #
138
175
  # @param arr [Array] The array.
139
- # @return [Class, nil] Either a class, or nil.
140
- def single_type_of_array(arr)
176
+ # @return [Symbol] The merge strategy to use, or nil if they can't be
177
+ # merged.
178
+ def merge_strategy(arr)
179
+ # If they're all the same type, they can be merged easily
141
180
  array_types = arr.map { |c| c.class }.uniq
142
- array_types.length == 1 ? array_types.first : nil
181
+ return :normal if array_types.length == 1
182
+
183
+ # Find all the namespaces and non-namespaces
184
+ namespace_types, non_namespace_types = array_types.partition { |x| x <= RbiGenerator::Namespace }
185
+
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
+ # It's OK, albeit cursed, for there to be a method with the same name as
201
+ # a namespace (Rainbow does this)
202
+ return nil if non_namespace_types.length != 0 && non_namespace_types != [RbiGenerator::Method]
203
+
204
+ :differing_namespaces
143
205
  end
144
206
 
145
207
  sig { params(arr: T::Array[T.untyped]).returns(T::Boolean) }
@@ -0,0 +1,30 @@
1
+ # typed: true
2
+
3
+ module Parlour
4
+ class DetachedRbiGenerator < RbiGenerator
5
+ sig { returns(T.untyped) }
6
+ def detached!
7
+ raise "cannot call methods on a detached RBI generator"
8
+ end
9
+
10
+ sig { override.returns(Options) }
11
+ def options
12
+ detached!
13
+ end
14
+
15
+ sig { override.returns(Namespace) }
16
+ def root
17
+ detached!
18
+ end
19
+
20
+ sig { override.returns(T.nilable(Plugin)) }
21
+ def current_plugin
22
+ nil
23
+ end
24
+
25
+ sig { override.params(strictness: String).returns(String) }
26
+ def rbi(strictness = 'strong')
27
+ detached!
28
+ end
29
+ end
30
+ end
@@ -3,4 +3,6 @@ module Kernel
3
3
  return to_enum(__method__) { 1 } unless block_given?
4
4
  yield self
5
5
  end unless method_defined? :yield_self
6
+
7
+ alias then yield_self
6
8
  end
@@ -0,0 +1,19 @@
1
+ # typed: true
2
+
3
+ module Parlour
4
+ class ParseError < StandardError
5
+ extend T::Sig
6
+
7
+ sig { returns(Parser::Source::Buffer) }
8
+ attr_reader :buffer
9
+
10
+ sig { returns(Parser::Source::Range) }
11
+ attr_reader :range
12
+
13
+ def initialize(buffer, range)
14
+ super()
15
+ @buffer = buffer
16
+ @range = range
17
+ end
18
+ end
19
+ end
@@ -4,7 +4,7 @@ module Parlour
4
4
  class RbiGenerator
5
5
  extend T::Sig
6
6
 
7
- sig { params(break_params: Integer, tab_size: Integer).void }
7
+ sig { params(break_params: Integer, tab_size: Integer, sort_namespaces: T::Boolean).void }
8
8
  # Creates a new RBI generator.
9
9
  #
10
10
  # @example Create a default generator.
@@ -16,29 +16,35 @@ module Parlour
16
16
  # @param break_params [Integer] If there are at least this many parameters in a
17
17
  # Sorbet +sig+, then it is broken onto separate lines.
18
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.
19
21
  # @return [void]
20
- def initialize(break_params: 4, tab_size: 2)
21
- @options = Options.new(break_params: break_params, tab_size: tab_size)
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
+ )
22
28
  @root = Namespace.new(self)
23
29
  end
24
30
 
25
- sig { returns(Options) }
31
+ sig { overridable.returns(Options) }
26
32
  # The formatting options for this generator.
27
33
  # @return [Options]
28
34
  attr_reader :options
29
35
 
30
- sig { returns(Namespace) }
36
+ sig { overridable.returns(Namespace) }
31
37
  # The root {Namespace} of this generator.
32
38
  # @return [Namespace]
33
39
  attr_reader :root
34
40
 
35
- sig { returns(T.nilable(Plugin)) }
41
+ sig { overridable.returns(T.nilable(Plugin)) }
36
42
  # The plugin which is currently generating new definitions.
37
43
  # {Plugin#run_plugins} controls this value.
38
44
  # @return [Plugin, nil]
39
45
  attr_accessor :current_plugin
40
46
 
41
- sig { params(strictness: String).returns(String) }
47
+ sig { overridable.params(strictness: String).returns(String) }
42
48
  # Returns the complete contents of the generated RBI file as a string.
43
49
  #
44
50
  # @return [String] The generated RBI file
@@ -70,19 +70,21 @@ module Parlour
70
70
  others: T::Array[RbiGenerator::RbiObject]
71
71
  ).returns(T::Boolean)
72
72
  end
73
- # Given an array of {ClassNamespace} instances, returns true if they may
73
+ # Given an array of {Namespace} instances, returns true if they may
74
74
  # be merged into this instance using {merge_into_self}. For instances to
75
75
  # be mergeable, they must either all be abstract or all not be abstract,
76
76
  # and they must define the same superclass (or none at all).
77
77
  #
78
- # @param others [Array<RbiGenerator::RbiObject>] An array of other {ClassNamespace} instances.
78
+ # @param others [Array<RbiGenerator::RbiObject>] An array of other {Namespace} instances.
79
79
  # @return [Boolean] Whether this instance may be merged with them.
80
80
  def mergeable?(others)
81
- others = T.cast(others, T::Array[ClassNamespace]) rescue (return false)
81
+ others = T.cast(others, T::Array[Namespace]) rescue (return false)
82
82
  all = others + [self]
83
83
 
84
- all.map(&:abstract).uniq.length == 1 &&
85
- all.map(&:superclass).compact.uniq.length <= 1
84
+ all_classes = T.cast(all.select { |x| ClassNamespace === x }, T::Array[ClassNamespace])
85
+
86
+ all_classes.map(&:abstract).uniq.length == 1 &&
87
+ all_classes.map(&:superclass).compact.uniq.length <= 1
86
88
  end
87
89
 
88
90
  sig do
@@ -99,6 +101,7 @@ module Parlour
99
101
  super
100
102
 
101
103
  others.each do |other|
104
+ next unless ClassNamespace === other
102
105
  other = T.cast(other, ClassNamespace)
103
106
 
104
107
  @superclass = other.superclass unless superclass
@@ -59,18 +59,20 @@ module Parlour
59
59
  others: T::Array[RbiGenerator::RbiObject]
60
60
  ).returns(T::Boolean)
61
61
  end
62
- # Given an array of {ModuleNamespace} instances, returns true if they may
62
+ # Given an array of {Namespace} instances, returns true if they may
63
63
  # be merged into this instance using {merge_into_self}. For instances to
64
64
  # be mergeable, they must either all be interfaces or all not be
65
65
  # interfaces.
66
66
  #
67
- # @param others [Array<RbiGenerator::RbiObject>] An array of other {ModuleNamespace} instances.
67
+ # @param others [Array<RbiGenerator::RbiObject>] An array of other {Namespace} instances.
68
68
  # @return [Boolean] Whether this instance may be merged with them.
69
69
  def mergeable?(others)
70
- others = T.cast(others, T::Array[RbiGenerator::ModuleNamespace]) rescue (return false)
70
+ others = T.cast(others, T::Array[Namespace]) rescue (return false)
71
71
  all = others + [self]
72
72
 
73
- all.map(&:interface).uniq.length == 1
73
+ all_modules = T.cast(all.select { |x| ModuleNamespace === x }, T::Array[ModuleNamespace])
74
+
75
+ all_modules.map(&:interface).uniq.length == 1
74
76
  end
75
77
 
76
78
  sig do
@@ -559,11 +559,15 @@ module Parlour
559
559
  # Given an array of {Namespace} instances, merges them into this one.
560
560
  # All children, constants, extends and includes are copied into this
561
561
  # instance.
562
+ #
563
+ # There may also be {RbiGenerator::Method} instances in the stream, which
564
+ # are ignored.
562
565
  #
563
566
  # @param others [Array<RbiGenerator::RbiObject>] An array of other {Namespace} instances.
564
567
  # @return [void]
565
568
  def merge_into_self(others)
566
569
  others.each do |other|
570
+ next if other.is_a?(RbiGenerator::Method)
567
571
  other = T.cast(other, Namespace)
568
572
 
569
573
  other.children.each { |c| children << c }
@@ -599,22 +603,23 @@ module Parlour
599
603
  result += [options.indented(indent_level, 'final!'), ''] if final
600
604
 
601
605
  if includes.any? || extends.any? || constants.any?
602
- result += includes
606
+ result += (options.sort_namespaces ? includes.sort_by(&:name) : includes)
603
607
  .flat_map { |x| x.generate_rbi(indent_level, options) }
604
608
  .reject { |x| x.strip == '' }
605
- result += extends
609
+ result += (options.sort_namespaces ? extends.sort_by(&:name) : extends)
606
610
  .flat_map { |x| x.generate_rbi(indent_level, options) }
607
611
  .reject { |x| x.strip == '' }
608
- result += constants
612
+ result += (options.sort_namespaces ? constants.sort_by(&:name) : constants)
609
613
  .flat_map { |x| x.generate_rbi(indent_level, options) }
610
614
  .reject { |x| x.strip == '' }
611
615
  result << ""
612
616
  end
613
617
 
614
618
  # Process singleton class attributes
615
- class_attributes, remaining_children = children.partition do |child|
616
- child.is_a?(Attribute) && child.class_attribute
617
- end
619
+ class_attributes, remaining_children = \
620
+ (options.sort_namespaces ? children.sort_by(&:name) : children)
621
+ .partition { |child| child.is_a?(Attribute) && child.class_attribute }
622
+
618
623
  if class_attributes.any?
619
624
  result << options.indented(indent_level, 'class << self')
620
625