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 +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +1 -0
- data/CHANGELOG.md +25 -0
- data/README.md +28 -1
- data/lib/parlour.rb +5 -0
- data/lib/parlour/conflict_resolver.rb +73 -11
- data/lib/parlour/detached_rbi_generator.rb +30 -0
- data/lib/parlour/kernel_hack.rb +2 -0
- data/lib/parlour/parse_error.rb +19 -0
- data/lib/parlour/rbi_generator.rb +13 -7
- data/lib/parlour/rbi_generator/class_namespace.rb +8 -5
- data/lib/parlour/rbi_generator/module_namespace.rb +6 -4
- data/lib/parlour/rbi_generator/namespace.rb +11 -6
- data/lib/parlour/rbi_generator/options.rb +15 -2
- data/lib/parlour/rbi_generator/parameter.rb +1 -1
- data/lib/parlour/type_loader.rb +84 -0
- data/lib/parlour/type_parser.rb +609 -0
- data/lib/parlour/version.rb +1 -1
- data/parlour.gemspec +5 -4
- metadata +28 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 382331994bebd14df33b96b084d725b0375fa84e0a3aa263e944bad22bb5ef3f
|
4
|
+
data.tar.gz: 33b9014173a85bc6dddec5aa3ad38cc7e23d869636694fbbe96d4e07c7e78886
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6ecd35a473c689909a7ef3b84af799cfa718b1901929743676c557d95f6dd717b3c9cd60dcec1c4274ce1ecc4e69310aa3213505c6f3ef479cfa580ccb39f187
|
7
|
+
data.tar.gz: 7dddda7cb3a9d123e74f0ed218d62b17697f63451b7389d6d5c92bb4712d41b1cc920380eb8ee3b84c7916be5ad8c32c5d2bb0f3b46c7828acefa45fa0581683
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -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
|
[](https://travis-ci.org/AaronC81/parlour)
|
4
4
|

|
5
5
|
|
6
|
-
Parlour is an RBI generator and
|
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!_
|
data/lib/parlour.rb
CHANGED
@@ -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
|
-
#
|
92
|
-
#
|
93
|
-
|
94
|
-
unless
|
95
|
-
Debugging.debug_puts(self, Debugging::Tree.end("Children are
|
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(
|
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
|
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 [
|
140
|
-
|
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
|
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
|
data/lib/parlour/kernel_hack.rb
CHANGED
@@ -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(
|
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 {
|
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 {
|
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[
|
81
|
+
others = T.cast(others, T::Array[Namespace]) rescue (return false)
|
82
82
|
all = others + [self]
|
83
83
|
|
84
|
-
|
85
|
-
|
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 {
|
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 {
|
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[
|
70
|
+
others = T.cast(others, T::Array[Namespace]) rescue (return false)
|
71
71
|
all = others + [self]
|
72
72
|
|
73
|
-
|
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 =
|
616
|
-
|
617
|
-
|
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
|
|