parlour 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![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
|
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
|
|