parlour 1.0.0 → 4.0.1
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/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
- data/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
- data/.gitignore +1 -0
- data/.parlour +5 -0
- data/.rspec +0 -0
- data/.travis.yml +4 -3
- data/CHANGELOG.md +65 -0
- data/CODE_OF_CONDUCT.md +0 -0
- data/Gemfile +0 -0
- data/LICENSE.txt +0 -0
- data/README.md +54 -1
- data/Rakefile +0 -0
- data/exe/parlour +68 -2
- data/lib/parlour.rb +7 -0
- data/lib/parlour/conflict_resolver.rb +129 -19
- data/lib/parlour/debugging.rb +0 -0
- data/lib/parlour/detached_rbi_generator.rb +25 -0
- data/lib/parlour/kernel_hack.rb +2 -0
- data/lib/parlour/parse_error.rb +19 -0
- data/lib/parlour/plugin.rb +1 -1
- data/lib/parlour/rbi_generator.rb +13 -7
- data/lib/parlour/rbi_generator/arbitrary.rb +0 -0
- data/lib/parlour/rbi_generator/attribute.rb +0 -0
- data/lib/parlour/rbi_generator/class_namespace.rb +8 -5
- data/lib/parlour/rbi_generator/constant.rb +11 -2
- data/lib/parlour/rbi_generator/enum_class_namespace.rb +24 -2
- data/lib/parlour/rbi_generator/extend.rb +0 -0
- data/lib/parlour/rbi_generator/include.rb +0 -0
- data/lib/parlour/rbi_generator/method.rb +0 -0
- data/lib/parlour/rbi_generator/module_namespace.rb +6 -4
- data/lib/parlour/rbi_generator/namespace.rb +81 -15
- data/lib/parlour/rbi_generator/options.rb +15 -2
- data/lib/parlour/rbi_generator/parameter.rb +5 -5
- data/lib/parlour/rbi_generator/rbi_object.rb +0 -0
- data/lib/parlour/rbi_generator/struct_class_namespace.rb +103 -0
- data/lib/parlour/rbi_generator/struct_prop.rb +136 -0
- data/lib/parlour/type_loader.rb +104 -0
- data/lib/parlour/type_parser.rb +854 -0
- data/lib/parlour/version.rb +1 -1
- data/parlour.gemspec +6 -5
- data/plugin_examples/foobar_plugin.rb +0 -0
- data/rbi/parlour.rbi +893 -0
- metadata +40 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 707934b89fd83fbb876582bd7e66679bc2f8cf2179961eed62358ba6266bf524
|
4
|
+
data.tar.gz: 3d0a0c8be95e09275a9b02232ce9b9b062a9f00eb3216a0e74ce63a036025d76
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ae723cbe6bebbba12ee3d802b223fa91fcda085f6b1fce4ec107efa3f9e3cc5df53ee0269a6e9d1391ff32c903d15b72153c67fae61392b7e5495688b69b7b0a
|
7
|
+
data.tar.gz: 98626719eb9d2c08f7e87c1ae48933d8b1afb9badab000bb408a8df26c57716846b71278654193c821ce59a552c83456d1f61523ce5bd9ff0e52d296ab34e274
|
File without changes
|
File without changes
|
data/.gitignore
CHANGED
data/.parlour
ADDED
data/.rspec
CHANGED
File without changes
|
data/.travis.yml
CHANGED
@@ -8,10 +8,8 @@ rvm:
|
|
8
8
|
- 2.4
|
9
9
|
- 2.5
|
10
10
|
- 2.6
|
11
|
+
- 2.7
|
11
12
|
- ruby-head
|
12
|
-
matrix:
|
13
|
-
allow_failures:
|
14
|
-
- rvm: ruby-head
|
15
13
|
|
16
14
|
jobs:
|
17
15
|
include:
|
@@ -25,3 +23,6 @@ jobs:
|
|
25
23
|
keep_history: true
|
26
24
|
on:
|
27
25
|
branch: master
|
26
|
+
allow_failures:
|
27
|
+
- rvm: 2.3
|
28
|
+
- rvm: ruby-head
|
data/CHANGELOG.md
CHANGED
@@ -3,6 +3,71 @@ 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
|
+
## [4.0.1] - 2020-08-05
|
7
|
+
### Fixed
|
8
|
+
- Fixed duplicate includes and extends.
|
9
|
+
- Fixed the block return type for `#resolve_conflicts` not being nilable.
|
10
|
+
|
11
|
+
## [4.0.0] - 2020-05-23
|
12
|
+
### Added
|
13
|
+
- Parlour now defaults to loading the current project when running its command
|
14
|
+
line tool, allowing it to be used as a "`sig` extractor" when run without
|
15
|
+
plugins! **Breaking if you invoke Parlour from its command line tool** - to
|
16
|
+
revert to the old behaviour of having nothing loaded into the root namespace
|
17
|
+
initially, add `parser: false` to your `.parlour` file.
|
18
|
+
- Generating constants in an eigenclass context (`class << self`) is now
|
19
|
+
supported.
|
20
|
+
|
21
|
+
## [3.0.0] - 2020-05-15
|
22
|
+
### Added
|
23
|
+
- `T::Struct` classes can now be generated and parsed.
|
24
|
+
- `T::Enum` classes can now be parsed.
|
25
|
+
- Constants are now parsed.
|
26
|
+
- `TypeParser` now detects and parses methods which do not have a `sig`.
|
27
|
+
**Potentially breaking if there is a strict set of methods you are expecting Parlour to detect.**
|
28
|
+
|
29
|
+
### Fixed
|
30
|
+
- "Specialized" classes, such as enums and now structs, have had many erroneous
|
31
|
+
conflicts with standard classes or namespaces fixed.
|
32
|
+
- Attributes writers and methods with the same name no longer conflict incorrectly.
|
33
|
+
|
34
|
+
## [2.1.0] - 2020-03-22
|
35
|
+
### Added
|
36
|
+
- Files can now be excluded from the `TypeLoader`.
|
37
|
+
|
38
|
+
### Changed
|
39
|
+
- A block argument in the definition but not in the signature no longer causes
|
40
|
+
an error in the `TypeParser`.
|
41
|
+
- Sorting of namespace children is now a stable sort.
|
42
|
+
|
43
|
+
### Fixed
|
44
|
+
- Type parameters are now parsed by the `TypeParser`.
|
45
|
+
|
46
|
+
## [2.0.0] - 2020-02-10
|
47
|
+
### Added
|
48
|
+
- Parlour can now load types back out of RBI files or Ruby source files by
|
49
|
+
parsing them, using the `TypeLoader` module.
|
50
|
+
- The `sort_namespaces` option has been added to `RbiGenerator` to
|
51
|
+
alphabetically sort all namespace children.
|
52
|
+
- Added `DetachedRbiGenerator`, which can be used to create instances of
|
53
|
+
`RbiObject` which are not bound to a particular set of options. This is
|
54
|
+
used internally for `TypeLoader`.
|
55
|
+
- Parlour will now create a polyfill for `then` on `Kernel`.
|
56
|
+
- Added `NodePath#sibling`.
|
57
|
+
|
58
|
+
### Changed
|
59
|
+
- Version restrictions on _rainbow_ and _commander_ have been slightly relaxed.
|
60
|
+
- The version of _sorbet-runtime_ is now restricted to `>= 0.5` after previously
|
61
|
+
being unrestricted.
|
62
|
+
- Instances of `Namespace` can now be merged with instances of `ClassNamespace`
|
63
|
+
or `MethodNamespace`.
|
64
|
+
- A method and a namespace can now have the same name without causing a merge
|
65
|
+
conflict.
|
66
|
+
|
67
|
+
### Fixed
|
68
|
+
- Parameter names are no longer nilable.
|
69
|
+
**Potentially breaking if you were doing something cursed with Parameter names.**
|
70
|
+
|
6
71
|
## [1.0.0] - 2019-11-22
|
7
72
|
### Added
|
8
73
|
- `T::Enum` classes have been implemented, and can be generated using
|
data/CODE_OF_CONDUCT.md
CHANGED
File without changes
|
data/Gemfile
CHANGED
File without changes
|
data/LICENSE.txt
CHANGED
File without changes
|
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,11 @@ 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!
|
30
|
+
|
31
|
+
- You can **generate RBI to ship with your gem** for consuming projects to use
|
32
|
+
([see "RBIs within gems" in Sorbet's docs](https://sorbet.org/docs/rbi#rbis-within-gems)).
|
24
33
|
|
25
34
|
Please [**read the wiki**](https://github.com/AaronC81/parlour/wiki) to get
|
26
35
|
started!
|
@@ -147,6 +156,50 @@ plugins:
|
|
147
156
|
Gem3::Plugin: {}
|
148
157
|
```
|
149
158
|
|
159
|
+
## Parsing RBIs
|
160
|
+
|
161
|
+
You can either parse individual RBI files, or point Parlour to the root of a
|
162
|
+
project and it will locate, parse and merge all RBI files.
|
163
|
+
|
164
|
+
Note that Parlour isn't limited to just RBIs; it can parse inline `sigs` out
|
165
|
+
of your Ruby source too!
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
require 'parlour'
|
169
|
+
|
170
|
+
# Return the object tree of a particular file
|
171
|
+
Parlour::TypeLoader.load_file('path/to/your/file.rbis')
|
172
|
+
|
173
|
+
# Return the object tree for an entire Sorbet project - slow but thorough!
|
174
|
+
Parlour::TypeLoader.load_project('root/of/the/project')
|
175
|
+
```
|
176
|
+
|
177
|
+
The structure of the returned object trees is identical to those you would
|
178
|
+
create when generating an RBI, built of instances of `RbiObject` subclasses.
|
179
|
+
|
180
|
+
## Generating RBI for a Gem
|
181
|
+
|
182
|
+
Include `parlour` as a development_dependency in your `.gemspec`:
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
spec.add_development_dependency 'parlour'
|
186
|
+
```
|
187
|
+
|
188
|
+
Run Parlour from the command line:
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
bundle exec parlour
|
192
|
+
```
|
193
|
+
|
194
|
+
Parlour is configured to use sane defaults assuming a standard gem structure
|
195
|
+
to generate an RBI that Sorbet will automatically find when your gem is included
|
196
|
+
as a dependency. If you require more advanced configuration you can add a
|
197
|
+
`.parlour` YAML file in the root of your project (see this project's `.parlour`
|
198
|
+
file as an example).
|
199
|
+
|
200
|
+
To disable the parsing step entire and just run plugins you can set `parser: false`
|
201
|
+
in your `.parlour` file.
|
202
|
+
|
150
203
|
## Parlour Plugins
|
151
204
|
|
152
205
|
_Have you written an awesome Parlour plugin? Please submit a PR to add it to this list!_
|
data/Rakefile
CHANGED
File without changes
|
data/exe/parlour
CHANGED
@@ -16,15 +16,42 @@ command :run do |c|
|
|
16
16
|
c.description = 'Generates an RBI file from your .parlour file'
|
17
17
|
|
18
18
|
c.action do |args, options|
|
19
|
-
|
19
|
+
working_dir = Dir.pwd
|
20
|
+
config_filename = File.join(working_dir, '.parlour')
|
20
21
|
|
21
|
-
|
22
|
+
if File.exists?(config_filename)
|
23
|
+
configuration = keys_to_symbols(YAML.load_file(config_filename))
|
24
|
+
else
|
25
|
+
configuration = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
# Output default
|
29
|
+
configuration[:output_file] ||= "rbi/#{File.basename(working_dir)}.rbi"
|
22
30
|
|
23
31
|
# Style defaults
|
24
32
|
configuration[:style] ||= {}
|
25
33
|
configuration[:style][:tab_size] ||= 2
|
26
34
|
configuration[:style][:break_params] ||= 4
|
27
35
|
|
36
|
+
# Parser defaults, set explicitly to false to not run parser
|
37
|
+
if configuration[:parser] != false
|
38
|
+
configuration[:parser] ||= {}
|
39
|
+
|
40
|
+
# Input/Output defaults
|
41
|
+
configuration[:parser][:root] ||= '.'
|
42
|
+
|
43
|
+
# Included/Excluded path defaults
|
44
|
+
configuration[:parser][:included_paths] ||= ['lib']
|
45
|
+
configuration[:parser][:excluded_paths] ||= ['sorbet', 'spec']
|
46
|
+
|
47
|
+
# Defaults can be overridden but we always want to exclude the output file
|
48
|
+
configuration[:parser][:excluded_paths] << configuration[:output_file]
|
49
|
+
end
|
50
|
+
|
51
|
+
# Included/Excluded module defaults
|
52
|
+
configuration[:included_modules] ||= []
|
53
|
+
configuration[:excluded_modules] ||= []
|
54
|
+
|
28
55
|
# Require defaults
|
29
56
|
configuration[:requires] ||= []
|
30
57
|
configuration[:relative_requires] ||= []
|
@@ -53,6 +80,15 @@ command :run do |c|
|
|
53
80
|
break_params: configuration[:style][:break_params],
|
54
81
|
tab_size: configuration[:style][:tab_size]
|
55
82
|
)
|
83
|
+
|
84
|
+
if configuration[:parser]
|
85
|
+
Parlour::TypeLoader.load_project(
|
86
|
+
configuration[:parser][:root],
|
87
|
+
inclusions: configuration[:parser][:included_paths],
|
88
|
+
exclusions: configuration[:parser][:excluded_paths],
|
89
|
+
generator: gen,
|
90
|
+
)
|
91
|
+
end
|
56
92
|
Parlour::Plugin.run_plugins(plugin_instances, gen)
|
57
93
|
|
58
94
|
# Run a pass of the conflict resolver
|
@@ -74,6 +110,14 @@ command :run do |c|
|
|
74
110
|
choice == 0 ? nil : candidates[choice - 1]
|
75
111
|
end
|
76
112
|
|
113
|
+
if !configuration[:included_modules].empty? || !configuration[:excluded_modules].empty?
|
114
|
+
remove_unwanted_modules(
|
115
|
+
gen.root,
|
116
|
+
included_modules: configuration[:included_modules],
|
117
|
+
excluded_modules: configuration[:excluded_modules],
|
118
|
+
)
|
119
|
+
end
|
120
|
+
|
77
121
|
# Figure out strictness levels
|
78
122
|
requested_strictness_levels = plugin_instances.map do |plugin|
|
79
123
|
s = plugin.strictness&.to_s
|
@@ -98,6 +142,7 @@ command :run do |c|
|
|
98
142
|
end
|
99
143
|
|
100
144
|
# Write the final RBI
|
145
|
+
FileUtils.mkdir_p(File.dirname(configuration[:output_file]))
|
101
146
|
File.write(configuration[:output_file], gen.rbi(strictness))
|
102
147
|
end
|
103
148
|
end
|
@@ -122,3 +167,24 @@ def keys_to_symbols(hash)
|
|
122
167
|
]
|
123
168
|
end.to_h
|
124
169
|
end
|
170
|
+
|
171
|
+
def remove_unwanted_modules(root, included_modules:, excluded_modules:, prefix: nil)
|
172
|
+
root.children.select! do |child|
|
173
|
+
module_name = "#{prefix}#{child.name}"
|
174
|
+
|
175
|
+
if child.respond_to?(:children)
|
176
|
+
remove_unwanted_modules(
|
177
|
+
child,
|
178
|
+
included_modules: included_modules,
|
179
|
+
excluded_modules: excluded_modules,
|
180
|
+
prefix: "#{module_name}::",
|
181
|
+
)
|
182
|
+
has_included_children = !child.children.empty?
|
183
|
+
end
|
184
|
+
|
185
|
+
included = included_modules.empty? ? true : included_modules.any? { |m| module_name.start_with?(m) }
|
186
|
+
excluded = excluded_modules.empty? ? false : excluded_modules.any? { |m| module_name.start_with?(m) }
|
187
|
+
|
188
|
+
(included || has_included_children) && !excluded
|
189
|
+
end
|
190
|
+
end
|
data/lib/parlour.rb
CHANGED
@@ -22,6 +22,13 @@ 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'
|
28
|
+
require 'parlour/detached_rbi_generator'
|
26
29
|
|
27
30
|
require 'parlour/conflict_resolver'
|
31
|
+
|
32
|
+
require 'parlour/parse_error'
|
33
|
+
require 'parlour/type_parser'
|
34
|
+
require 'parlour/type_loader'
|
@@ -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.
|
@@ -11,7 +13,7 @@ module Parlour
|
|
11
13
|
resolver: T.proc.params(
|
12
14
|
desc: String,
|
13
15
|
choices: T::Array[RbiGenerator::RbiObject]
|
14
|
-
).returns(RbiGenerator::RbiObject)
|
16
|
+
).returns(T.nilable(RbiGenerator::RbiObject))
|
15
17
|
).void
|
16
18
|
end
|
17
19
|
# Given a namespace, attempts to automatically resolve conflicts in the
|
@@ -21,13 +23,13 @@ module Parlour
|
|
21
23
|
# All children of the given namespace which are also namespaces are
|
22
24
|
# processed recursively, so passing {RbiGenerator#root} will eliminate all
|
23
25
|
# conflicts in the entire object tree.
|
24
|
-
#
|
26
|
+
#
|
25
27
|
# If automatic resolution is not possible, the block passed to this method
|
26
28
|
# is invoked and passed two arguments: a message on what the conflict is,
|
27
29
|
# and an array of candidate objects. The block should return one of these
|
28
30
|
# candidate objects, which will be kept, and all other definitions are
|
29
31
|
# deleted. Alternatively, the block may return nil, which will delete all
|
30
|
-
# definitions. The block may be invoked many times from one call to
|
32
|
+
# definitions. The block may be invoked many times from one call to
|
31
33
|
# {resolve_conflicts}, one for each unresolvable conflict.
|
32
34
|
#
|
33
35
|
# @param namespace [RbiGenerator::Namespace] The starting namespace to
|
@@ -42,7 +44,14 @@ 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
|
-
|
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
|
46
55
|
|
47
56
|
grouped_by_name_children.each do |name, children|
|
48
57
|
Debugging.debug_puts(self, Debugging::Tree.begin("Checking children named #{name}..."))
|
@@ -50,7 +59,7 @@ module Parlour
|
|
50
59
|
if children.length > 1
|
51
60
|
Debugging.debug_puts(self, Debugging::Tree.here("Possible conflict between #{children.length} objects"))
|
52
61
|
|
53
|
-
# Special case: do we have two methods, one of which is a class method
|
62
|
+
# Special case: do we have two methods, one of which is a class method
|
54
63
|
# and the other isn't? If so, do nothing - this is fine
|
55
64
|
if children.length == 2 &&
|
56
65
|
children.all? { |c| c.is_a?(RbiGenerator::Method) } &&
|
@@ -60,7 +69,22 @@ module Parlour
|
|
60
69
|
next
|
61
70
|
end
|
62
71
|
|
63
|
-
# Special case:
|
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
|
+
deduplicate_mixins_of_name(namespace, name)
|
82
|
+
|
83
|
+
Debugging.debug_puts(self, Debugging::Tree.end("Includes/extends do not conflict with namespaces; no resolution required"))
|
84
|
+
next
|
85
|
+
end
|
86
|
+
|
87
|
+
# Special case: do we have two attributes, one of which is a class
|
64
88
|
# attribute and the other isn't? If so, do nothing - this is fine
|
65
89
|
if children.length == 2 &&
|
66
90
|
children.all? { |c| c.is_a?(RbiGenerator::Attribute) } &&
|
@@ -70,13 +94,13 @@ module Parlour
|
|
70
94
|
next
|
71
95
|
end
|
72
96
|
|
73
|
-
# Special case: are they all clearly equal? If so, remove all but one
|
97
|
+
# Optimization for Special case: are they all clearly equal? If so, remove all but one
|
74
98
|
if all_eql?(children)
|
75
99
|
Debugging.debug_puts(self, Debugging::Tree.end("All children are identical"))
|
76
100
|
|
77
101
|
# All of the children are the same, so this deletes all of them
|
78
102
|
namespace.children.delete(T.must(children.first))
|
79
|
-
|
103
|
+
|
80
104
|
# Re-add one child
|
81
105
|
namespace.children << T.must(children.first)
|
82
106
|
next
|
@@ -88,11 +112,11 @@ module Parlour
|
|
88
112
|
namespace.children.delete(c)
|
89
113
|
end
|
90
114
|
|
91
|
-
#
|
92
|
-
#
|
93
|
-
|
94
|
-
unless
|
95
|
-
Debugging.debug_puts(self, Debugging::Tree.end("Children are
|
115
|
+
# Check that the types of the given objects allow them to be merged,
|
116
|
+
# and get the strategy to use
|
117
|
+
strategy = merge_strategy(children)
|
118
|
+
unless strategy
|
119
|
+
Debugging.debug_puts(self, Debugging::Tree.end("Children are unmergeable types; requesting manual resolution"))
|
96
120
|
# The types aren't the same, so ask the resolver what to do, and
|
97
121
|
# insert that (if not nil)
|
98
122
|
choice = resolver.call("Different kinds of definition for the same name", children)
|
@@ -100,8 +124,47 @@ module Parlour
|
|
100
124
|
next
|
101
125
|
end
|
102
126
|
|
127
|
+
case strategy
|
128
|
+
when :normal
|
129
|
+
first, *rest = children
|
130
|
+
when :differing_namespaces
|
131
|
+
# Let the namespaces be merged normally, but handle the method here
|
132
|
+
namespaces, non_namespaces = children.partition { |x| RbiGenerator::Namespace === x }
|
133
|
+
|
134
|
+
# If there is any non-namespace item in this conflict, it should be
|
135
|
+
# a single method
|
136
|
+
if non_namespaces.length != 0
|
137
|
+
unless non_namespaces.length == 1 && RbiGenerator::Method === non_namespaces.first
|
138
|
+
Debugging.debug_puts(self, Debugging::Tree.end("Non-namespace item in a differing namespace conflict is not a single method; requesting manual resolution"))
|
139
|
+
# The types aren't the same, so ask the resolver what to do, and
|
140
|
+
# insert that (if not nil)
|
141
|
+
choice = resolver.call("Non-namespace item in a differing namespace conflict is not a single method", non_namespaces)
|
142
|
+
non_namespaces = []
|
143
|
+
non_namespaces << choice if choice
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
non_namespaces.each do |x|
|
148
|
+
namespace.children << x
|
149
|
+
end
|
150
|
+
|
151
|
+
# For certain namespace types the order matters. For example, if there's
|
152
|
+
# both a `Namespace` and `ModuleNamespace` then merging the two would
|
153
|
+
# produce different results depending on which is first.
|
154
|
+
first_index = (
|
155
|
+
namespaces.find_index { |x| RbiGenerator::EnumClassNamespace === x || RbiGenerator::StructClassNamespace === x } ||
|
156
|
+
namespaces.find_index { |x| RbiGenerator::ClassNamespace === x } ||
|
157
|
+
namespaces.find_index { |x| RbiGenerator::ModuleNamespace === x } ||
|
158
|
+
0
|
159
|
+
)
|
160
|
+
|
161
|
+
first = namespaces.delete_at(first_index)
|
162
|
+
rest = namespaces
|
163
|
+
else
|
164
|
+
raise 'unknown merge strategy; this is a Parlour bug'
|
165
|
+
end
|
166
|
+
|
103
167
|
# Can the children merge themselves automatically? If so, let them
|
104
|
-
first, *rest = children
|
105
168
|
first, rest = T.must(first), T.must(rest)
|
106
169
|
if T.must(first).mergeable?(T.must(rest))
|
107
170
|
Debugging.debug_puts(self, Debugging::Tree.end("Children are all mergeable; resolving automatically"))
|
@@ -131,15 +194,41 @@ module Parlour
|
|
131
194
|
|
132
195
|
private
|
133
196
|
|
134
|
-
sig { params(arr: T::Array[T.untyped]).returns(T.nilable(
|
197
|
+
sig { params(arr: T::Array[T.untyped]).returns(T.nilable(Symbol)) }
|
135
198
|
# Given an array, if all elements in the array are instances of the exact
|
136
|
-
# same class
|
199
|
+
# same class or are otherwise mergeable (for example Namespace and
|
200
|
+
# ClassNamespace), returns the kind of merge which needs to be made. A
|
201
|
+
# return value of nil indicates that the values cannot be merged.
|
202
|
+
#
|
203
|
+
# The following kinds are available:
|
204
|
+
# - They are all the same. (:normal)
|
205
|
+
# - There are exactly two types, one of which is Namespace and other is a
|
206
|
+
# subclass of it. (:differing_namespaces)
|
207
|
+
# - One of them is Namespace or a subclass (or both, as described above),
|
208
|
+
# and the only other is Method. (also :differing_namespaces)
|
137
209
|
#
|
138
210
|
# @param arr [Array] The array.
|
139
|
-
# @return [
|
140
|
-
|
211
|
+
# @return [Symbol] The merge strategy to use, or nil if they can't be
|
212
|
+
# merged.
|
213
|
+
def merge_strategy(arr)
|
214
|
+
# If they're all the same type, they can be merged easily
|
141
215
|
array_types = arr.map { |c| c.class }.uniq
|
142
|
-
array_types.length == 1
|
216
|
+
return :normal if array_types.length == 1
|
217
|
+
|
218
|
+
# Find all the namespaces and non-namespaces
|
219
|
+
namespace_types, non_namespace_types = array_types.partition { |x| x <= RbiGenerator::Namespace }
|
220
|
+
exactly_namespace, namespace_subclasses = namespace_types.partition { |x| x == RbiGenerator::Namespace }
|
221
|
+
|
222
|
+
return nil unless namespace_subclasses.empty? \
|
223
|
+
|| (namespace_subclasses.length == 1 && namespace_subclasses.first < RbiGenerator::Namespace) \
|
224
|
+
|| namespace_subclasses.to_set == Set[RbiGenerator::ClassNamespace, RbiGenerator::StructClassNamespace] \
|
225
|
+
|| namespace_subclasses.to_set == Set[RbiGenerator::ClassNamespace, RbiGenerator::EnumClassNamespace]
|
226
|
+
|
227
|
+
# It's OK, albeit cursed, for there to be a method with the same name as
|
228
|
+
# a namespace (Rainbow does this)
|
229
|
+
return nil if non_namespace_types.length != 0 && non_namespace_types != [RbiGenerator::Method]
|
230
|
+
|
231
|
+
:differing_namespaces
|
143
232
|
end
|
144
233
|
|
145
234
|
sig { params(arr: T::Array[T.untyped]).returns(T::Boolean) }
|
@@ -151,5 +240,26 @@ module Parlour
|
|
151
240
|
def all_eql?(arr)
|
152
241
|
arr.each_cons(2).all? { |x, y| x == y }
|
153
242
|
end
|
243
|
+
|
244
|
+
sig { params(namespace: RbiGenerator::Namespace, name: T.nilable(String)).void }
|
245
|
+
# Given a namespace and a child name, removes all duplicate children that are mixins
|
246
|
+
# and that have the given name, except the first found instance.
|
247
|
+
#
|
248
|
+
# @param namespace [RbiGenerator::Namespace] The namespace to deduplicate mixins in.
|
249
|
+
# @param name [String] The name of the mixin modules to deduplicate.
|
250
|
+
# @return [void]
|
251
|
+
def deduplicate_mixins_of_name(namespace, name)
|
252
|
+
found_map = {}
|
253
|
+
namespace.children.delete_if do |x|
|
254
|
+
# ignore children whose names don't match
|
255
|
+
next unless x.name == name
|
256
|
+
# ignore children that are not mixins
|
257
|
+
next unless x.is_a?(RbiGenerator::Include) || x.is_a?(RbiGenerator::Extend)
|
258
|
+
|
259
|
+
delete = found_map.key?(x.class)
|
260
|
+
found_map[x.class] = true
|
261
|
+
delete
|
262
|
+
end
|
263
|
+
end
|
154
264
|
end
|
155
265
|
end
|