parlour 2.0.0 → 5.0.0.beta.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 -1
- data/.parlour +5 -0
- data/.rspec +0 -0
- data/.travis.yml +3 -3
- data/CHANGELOG.md +64 -0
- data/CODE_OF_CONDUCT.md +0 -0
- data/Gemfile +0 -0
- data/LICENSE.txt +0 -0
- data/README.md +233 -19
- data/Rakefile +0 -0
- data/exe/parlour +109 -4
- data/lib/parlour.rb +29 -1
- data/lib/parlour/conflict_resolver.rb +75 -27
- data/lib/parlour/conversion/converter.rb +34 -0
- data/lib/parlour/conversion/rbi_to_rbs.rb +223 -0
- data/lib/parlour/debugging.rb +0 -0
- data/lib/parlour/detached_rbi_generator.rb +1 -6
- data/lib/parlour/detached_rbs_generator.rb +25 -0
- data/lib/parlour/generator.rb +34 -0
- data/lib/parlour/kernel_hack.rb +0 -0
- data/lib/parlour/options.rb +71 -0
- data/lib/parlour/parse_error.rb +0 -0
- data/lib/parlour/plugin.rb +1 -1
- data/lib/parlour/rbi_generator.rb +24 -37
- data/lib/parlour/rbi_generator/arbitrary.rb +5 -2
- data/lib/parlour/rbi_generator/attribute.rb +14 -5
- data/lib/parlour/rbi_generator/class_namespace.rb +8 -3
- data/lib/parlour/rbi_generator/constant.rb +28 -8
- data/lib/parlour/rbi_generator/enum_class_namespace.rb +32 -5
- data/lib/parlour/rbi_generator/extend.rb +5 -2
- data/lib/parlour/rbi_generator/include.rb +5 -2
- data/lib/parlour/rbi_generator/method.rb +15 -10
- data/lib/parlour/rbi_generator/module_namespace.rb +7 -2
- data/lib/parlour/rbi_generator/namespace.rb +115 -27
- data/lib/parlour/rbi_generator/parameter.rb +13 -7
- data/lib/parlour/rbi_generator/rbi_object.rb +19 -78
- data/lib/parlour/rbi_generator/struct_class_namespace.rb +110 -0
- data/lib/parlour/rbi_generator/struct_prop.rb +139 -0
- data/lib/parlour/rbi_generator/type_alias.rb +101 -0
- data/lib/parlour/rbs_generator.rb +24 -0
- data/lib/parlour/rbs_generator/arbitrary.rb +92 -0
- data/lib/parlour/rbs_generator/attribute.rb +82 -0
- data/lib/parlour/rbs_generator/block.rb +49 -0
- data/lib/parlour/rbs_generator/class_namespace.rb +106 -0
- data/lib/parlour/rbs_generator/constant.rb +95 -0
- data/lib/parlour/rbs_generator/extend.rb +92 -0
- data/lib/parlour/rbs_generator/include.rb +92 -0
- data/lib/parlour/rbs_generator/interface_namespace.rb +34 -0
- data/lib/parlour/rbs_generator/method.rb +146 -0
- data/lib/parlour/rbs_generator/method_signature.rb +104 -0
- data/lib/parlour/rbs_generator/module_namespace.rb +35 -0
- data/lib/parlour/rbs_generator/namespace.rb +627 -0
- data/lib/parlour/rbs_generator/parameter.rb +145 -0
- data/lib/parlour/rbs_generator/rbs_object.rb +78 -0
- data/lib/parlour/rbs_generator/type_alias.rb +96 -0
- data/lib/parlour/type_loader.rb +30 -10
- data/lib/parlour/type_parser.rb +440 -43
- data/lib/parlour/typed_object.rb +87 -0
- data/lib/parlour/types.rb +445 -0
- data/lib/parlour/version.rb +1 -1
- data/parlour.gemspec +2 -2
- data/plugin_examples/foobar_plugin.rb +0 -0
- data/rbi/parlour.rbi +1799 -0
- metadata +42 -15
- data/lib/parlour/rbi_generator/options.rb +0 -74
@@ -0,0 +1,24 @@
|
|
1
|
+
# typed: true
|
2
|
+
module Parlour
|
3
|
+
# The RBS generator.
|
4
|
+
class RbsGenerator < Generator
|
5
|
+
def initialize(**hash)
|
6
|
+
super
|
7
|
+
@root = RbsGenerator::Namespace.new(self)
|
8
|
+
end
|
9
|
+
|
10
|
+
sig { overridable.returns(RbsGenerator::Namespace) }
|
11
|
+
# The root {Namespace} of this generator.
|
12
|
+
# @return [Namespace]
|
13
|
+
attr_reader :root
|
14
|
+
|
15
|
+
sig { overridable.returns(String) }
|
16
|
+
# Returns the complete contents of the generated RBS file as a string.
|
17
|
+
#
|
18
|
+
# @return [String] The generated RBS file
|
19
|
+
def rbs
|
20
|
+
root.generate_rbs(0, options).join("\n")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# typed: true
|
2
|
+
module Parlour
|
3
|
+
class RbsGenerator < Generator
|
4
|
+
# Represents miscellaneous Ruby code.
|
5
|
+
class Arbitrary < RbsObject
|
6
|
+
sig do
|
7
|
+
params(
|
8
|
+
generator: Generator,
|
9
|
+
code: String,
|
10
|
+
block: T.nilable(T.proc.params(x: Arbitrary).void)
|
11
|
+
).void
|
12
|
+
end
|
13
|
+
# Creates new arbitrary code.
|
14
|
+
#
|
15
|
+
# @param code [String] The arbitrary code string. Indentation is added to
|
16
|
+
# the beginning of each line.
|
17
|
+
def initialize(generator, code: '', &block)
|
18
|
+
super(generator, '')
|
19
|
+
@code = code
|
20
|
+
yield_self(&block) if block
|
21
|
+
end
|
22
|
+
|
23
|
+
sig { returns(String) }
|
24
|
+
# Returns arbitrary code string.
|
25
|
+
attr_accessor :code
|
26
|
+
|
27
|
+
sig { params(other: Object).returns(T::Boolean) }
|
28
|
+
# Returns true if this instance is equal to another arbitrary code line.
|
29
|
+
#
|
30
|
+
# @param other [Object] The other instance. If this is not a {Arbitrary} (or a
|
31
|
+
# subclass of it), this will always return false.
|
32
|
+
# @return [Boolean]
|
33
|
+
def ==(other)
|
34
|
+
Arbitrary === other && code == other.code
|
35
|
+
end
|
36
|
+
|
37
|
+
sig do
|
38
|
+
override.params(
|
39
|
+
indent_level: Integer,
|
40
|
+
options: Options
|
41
|
+
).returns(T::Array[String])
|
42
|
+
end
|
43
|
+
# Generates the RBS lines for this arbitrary code.
|
44
|
+
#
|
45
|
+
# @param indent_level [Integer] The indentation level to generate the lines at.
|
46
|
+
# @param options [Options] The formatting options to use.
|
47
|
+
# @return [Array<String>] The RBS lines, formatted as specified.
|
48
|
+
def generate_rbs(indent_level, options)
|
49
|
+
code.split("\n").map { |l| options.indented(indent_level, l) }
|
50
|
+
end
|
51
|
+
|
52
|
+
sig do
|
53
|
+
override.params(
|
54
|
+
others: T::Array[RbsGenerator::RbsObject]
|
55
|
+
).returns(T::Boolean)
|
56
|
+
end
|
57
|
+
# Given an array of {Arbitrary} instances, returns true if they may be
|
58
|
+
# merged into this instance using {merge_into_self}. This is always false.
|
59
|
+
#
|
60
|
+
# @param others [Array<RbsGenerator::RbsObject>] An array of other
|
61
|
+
# {Arbitrary} instances.
|
62
|
+
# @return [Boolean] Whether this instance may be merged with them.
|
63
|
+
def mergeable?(others)
|
64
|
+
false
|
65
|
+
end
|
66
|
+
|
67
|
+
sig do
|
68
|
+
override.params(
|
69
|
+
others: T::Array[RbsGenerator::RbsObject]
|
70
|
+
).void
|
71
|
+
end
|
72
|
+
# Given an array of {Arbitrary} instances, merges them into this one.
|
73
|
+
# This particular implementation always throws an exception, because
|
74
|
+
# {mergeable?} is always false.
|
75
|
+
#
|
76
|
+
# @param others [Array<RbsGenerator::RbsObject>] An array of other
|
77
|
+
# {Arbitrary} instances.
|
78
|
+
# @return [void]
|
79
|
+
def merge_into_self(others)
|
80
|
+
raise 'arbitrary code is never mergeable'
|
81
|
+
end
|
82
|
+
|
83
|
+
sig { override.returns(String) }
|
84
|
+
# Returns a human-readable brief string description of this code.
|
85
|
+
#
|
86
|
+
# @return [String]
|
87
|
+
def describe
|
88
|
+
"Arbitrary code (#{code})"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# typed: true
|
2
|
+
module Parlour
|
3
|
+
class RbsGenerator < Generator
|
4
|
+
# Represents an attribute reader, writer or accessor.
|
5
|
+
class Attribute < RbsGenerator::Method
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig do
|
9
|
+
params(
|
10
|
+
generator: Generator,
|
11
|
+
name: String,
|
12
|
+
kind: Symbol,
|
13
|
+
type: Types::TypeLike,
|
14
|
+
block: T.nilable(T.proc.params(x: Attribute).void)
|
15
|
+
).void
|
16
|
+
end
|
17
|
+
# Creates a new attribute.
|
18
|
+
# @note You should use {Namespace#create_attribute} rather than this directly.
|
19
|
+
#
|
20
|
+
# @param generator [RbsGenerator] The current RbsGenerator.
|
21
|
+
# @param name [String] The name of this attribute.
|
22
|
+
# @param kind [Symbol] The kind of attribute this is; one of :writer, :reader or
|
23
|
+
# :accessor.
|
24
|
+
# @param type [String, Types::Type] This attribute's type.
|
25
|
+
# @param block A block which the new instance yields itself to.
|
26
|
+
# @return [void]
|
27
|
+
def initialize(generator, name, kind, type, &block)
|
28
|
+
@type = type
|
29
|
+
@kind = kind
|
30
|
+
case kind
|
31
|
+
when :accessor, :reader
|
32
|
+
super(generator, name, [MethodSignature.new([], type)], &block)
|
33
|
+
when :writer
|
34
|
+
super(generator, name, [MethodSignature.new([
|
35
|
+
Parameter.new(name, type: type)
|
36
|
+
], type)], &block)
|
37
|
+
else
|
38
|
+
raise 'unknown kind'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
sig { returns(Symbol) }
|
43
|
+
# The kind of attribute this is; one of +:writer+, +:reader+, or +:accessor+.
|
44
|
+
# @return [Symbol]
|
45
|
+
attr_reader :kind
|
46
|
+
|
47
|
+
sig { returns(Types::TypeLike) }
|
48
|
+
# The type of this attribute.
|
49
|
+
attr_reader :type
|
50
|
+
|
51
|
+
sig do
|
52
|
+
override.params(
|
53
|
+
indent_level: Integer,
|
54
|
+
options: Options
|
55
|
+
).returns(T::Array[String])
|
56
|
+
end
|
57
|
+
# Generates the RBS lines for this arbstrary code.
|
58
|
+
#
|
59
|
+
# @param indent_level [Integer] The indentation level to generate the lines at.
|
60
|
+
# @param options [Options] The formatting options to use.
|
61
|
+
# @return [Array<String>] The RBS lines, formatted as specified.
|
62
|
+
def generate_rbs(indent_level, options)
|
63
|
+
[options.indented(
|
64
|
+
indent_level,
|
65
|
+
"attr_#{kind} #{name}: #{String === @type ? @type : @type.generate_rbs}"
|
66
|
+
)]
|
67
|
+
end
|
68
|
+
|
69
|
+
sig { override.params(other: Object).returns(T::Boolean) }
|
70
|
+
# Returns true if this instance is equal to another attribute.
|
71
|
+
#
|
72
|
+
# @param other [Object] The other instance. If this is not a {Attribute}
|
73
|
+
# (or a subclass of it), this will always return false.
|
74
|
+
# @return [Boolean]
|
75
|
+
def ==(other)
|
76
|
+
T.must(
|
77
|
+
super(other) && Attribute === other && kind == other.kind
|
78
|
+
)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# typed: true
|
2
|
+
module Parlour
|
3
|
+
class RbsGenerator < Generator
|
4
|
+
# Represents a block in a method signature.
|
5
|
+
# (This is not an RbsObject because it doesn't generate a full line.)
|
6
|
+
class Block
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig { params(type: Types::Proc, required: T::Boolean).void }
|
10
|
+
# Creates a new block for a method signature.
|
11
|
+
#
|
12
|
+
# @param type [Types::Proc] The type of this block.
|
13
|
+
# @param required [T::Boolean] Whether this block is required.
|
14
|
+
def initialize(type, required)
|
15
|
+
@type = type
|
16
|
+
@required = required
|
17
|
+
end
|
18
|
+
|
19
|
+
sig { overridable.params(other: Object).returns(T::Boolean).checked(:never) }
|
20
|
+
# Returns true if this instance is equal to another method signature.
|
21
|
+
#
|
22
|
+
# @param other [Object] The other instance. If this is not a {MethodSignature} (or a
|
23
|
+
# subclass of it), this will always return false.
|
24
|
+
# @return [Boolean]
|
25
|
+
def ==(other)
|
26
|
+
Block === other && type == other.type && required == other.required
|
27
|
+
end
|
28
|
+
|
29
|
+
sig { returns(Types::Proc) }
|
30
|
+
# The type of this block.
|
31
|
+
# @return [Types::Proc]
|
32
|
+
attr_reader :type
|
33
|
+
|
34
|
+
sig { returns(T::Boolean) }
|
35
|
+
# Whether this block is required.
|
36
|
+
# @return [Boolean]
|
37
|
+
attr_reader :required
|
38
|
+
|
39
|
+
sig { params(options: Options).returns(T::Array[String]) }
|
40
|
+
# Generates the RBS string for this signature.
|
41
|
+
#
|
42
|
+
# @param options [Options] The formatting options to use.
|
43
|
+
# @return [Array<String>] The RBS string, formatted as specified.
|
44
|
+
def generate_rbs(options)
|
45
|
+
["#{required ? '' : '?'}{ #{type.generate_rbs} }"]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# typed: true
|
2
|
+
module Parlour
|
3
|
+
class RbsGenerator < Generator
|
4
|
+
# Represents a class definition.
|
5
|
+
class ClassNamespace < Namespace
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig do
|
9
|
+
params(
|
10
|
+
generator: Generator,
|
11
|
+
name: String,
|
12
|
+
superclass: T.nilable(Types::TypeLike),
|
13
|
+
block: T.nilable(T.proc.params(x: ClassNamespace).void)
|
14
|
+
).void
|
15
|
+
end
|
16
|
+
# Creates a new class definition.
|
17
|
+
# @note You should use {Namespace#create_class} rather than this directly.
|
18
|
+
#
|
19
|
+
# @param generator [RbsGenerator] The current RbsGenerator.
|
20
|
+
# @param name [String] The name of this class.
|
21
|
+
# @param final [Boolean] Whether this namespace is final.
|
22
|
+
# @param superclass [String, nil] The superclass of this class, or nil if it doesn't
|
23
|
+
# have one.
|
24
|
+
# @param abstract [Boolean] A boolean indicating whether this class is abstract.
|
25
|
+
# @param block A block which the new instance yields itself to.
|
26
|
+
# @return [void]
|
27
|
+
def initialize(generator, name, superclass, &block)
|
28
|
+
super(generator, name, &block)
|
29
|
+
@superclass = superclass
|
30
|
+
end
|
31
|
+
|
32
|
+
sig do
|
33
|
+
override.params(
|
34
|
+
indent_level: Integer,
|
35
|
+
options: Options
|
36
|
+
).returns(T::Array[String])
|
37
|
+
end
|
38
|
+
def generate_rbs(indent_level, options)
|
39
|
+
class_definition = @superclass.nil? \
|
40
|
+
? "class #{name}"
|
41
|
+
: "class #{name} < #{String === @superclass ? @superclass : @superclass.generate_rbs}"
|
42
|
+
|
43
|
+
lines = generate_comments(indent_level, options)
|
44
|
+
lines << options.indented(indent_level, class_definition)
|
45
|
+
lines += generate_body(indent_level + 1, options)
|
46
|
+
lines << options.indented(indent_level, "end")
|
47
|
+
end
|
48
|
+
|
49
|
+
sig { returns(T.nilable(Types::TypeLike)) }
|
50
|
+
# The superclass of this class, or nil if it doesn't have one.
|
51
|
+
# @return [Types::TypeLike, nil]
|
52
|
+
attr_reader :superclass
|
53
|
+
|
54
|
+
sig do
|
55
|
+
override.params(
|
56
|
+
others: T::Array[RbsGenerator::RbsObject]
|
57
|
+
).returns(T::Boolean)
|
58
|
+
end
|
59
|
+
# Given an array of {Namespace} instances, returns true if they may
|
60
|
+
# be merged into this instance using {merge_into_self}. For instances to
|
61
|
+
# be mergeable, they must either all be abstract or all not be abstract,
|
62
|
+
# and they must define the same superclass (or none at all).
|
63
|
+
#
|
64
|
+
# @param others [Array<RbsGenerator::RbsObject>] An array of other {Namespace} instances.
|
65
|
+
# @return [Boolean] Whether this instance may be merged with them.
|
66
|
+
def mergeable?(others)
|
67
|
+
others = T.cast(others, T::Array[Namespace]) rescue (return false)
|
68
|
+
all = others + [self]
|
69
|
+
|
70
|
+
all_classes = T.cast(all.select { |x| ClassNamespace === x }, T::Array[ClassNamespace])
|
71
|
+
|
72
|
+
all_classes.map(&:superclass).compact.uniq.length <= 1
|
73
|
+
end
|
74
|
+
|
75
|
+
sig do
|
76
|
+
override.params(
|
77
|
+
others: T::Array[RbsGenerator::RbsObject]
|
78
|
+
).void
|
79
|
+
end
|
80
|
+
# Given an array of {ClassNamespace} instances, merges them into this one.
|
81
|
+
# You MUST ensure that {mergeable?} is true for those instances.
|
82
|
+
#
|
83
|
+
# @param others [Array<RbsGenerator::RbsObject>] An array of other {ClassNamespace} instances.
|
84
|
+
# @return [void]
|
85
|
+
def merge_into_self(others)
|
86
|
+
super
|
87
|
+
|
88
|
+
others.each do |other|
|
89
|
+
next unless ClassNamespace === other
|
90
|
+
other = T.cast(other, ClassNamespace)
|
91
|
+
|
92
|
+
@superclass = other.superclass unless superclass
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
sig { override.returns(String) }
|
97
|
+
# Returns a human-readable brief string description of this class.
|
98
|
+
# @return [String]
|
99
|
+
def describe
|
100
|
+
"Class #{name} - #{"superclass #{superclass}, " if superclass}" +
|
101
|
+
"#{children.length} children, " +
|
102
|
+
"#{includes.length} includes, #{extends.length} extends"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# typed: true
|
2
|
+
module Parlour
|
3
|
+
class RbsGenerator < Generator
|
4
|
+
# Represents a constant definition.
|
5
|
+
class Constant < RbsObject
|
6
|
+
sig do
|
7
|
+
params(
|
8
|
+
generator: Generator,
|
9
|
+
name: String,
|
10
|
+
type: Types::TypeLike,
|
11
|
+
block: T.nilable(T.proc.params(x: Constant).void)
|
12
|
+
).void
|
13
|
+
end
|
14
|
+
# Creates a new constant definition.
|
15
|
+
#
|
16
|
+
# @param name [String] The name of the constant.
|
17
|
+
# @param value [String] The value of the constant, as a Ruby code string.
|
18
|
+
# eigenclass of the current namespace.
|
19
|
+
def initialize(generator, name, type:, &block)
|
20
|
+
super(generator, name)
|
21
|
+
@type = type
|
22
|
+
yield_self(&block) if block
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Types::TypeLike] The type of the constant.
|
26
|
+
sig { returns(Types::TypeLike) }
|
27
|
+
attr_reader :type
|
28
|
+
|
29
|
+
sig { params(other: Object).returns(T::Boolean) }
|
30
|
+
# Returns true if this instance is equal to another extend.
|
31
|
+
#
|
32
|
+
# @param other [Object] The other instance. If this is not a {Extend} (or a
|
33
|
+
# subclass of it), this will always return false.
|
34
|
+
# @return [Boolean]
|
35
|
+
def ==(other)
|
36
|
+
Constant === other && name == other.name && type == other.type
|
37
|
+
end
|
38
|
+
|
39
|
+
sig do
|
40
|
+
override.params(
|
41
|
+
indent_level: Integer,
|
42
|
+
options: Options
|
43
|
+
).returns(T::Array[String])
|
44
|
+
end
|
45
|
+
# Generates the RBS lines for this constant.
|
46
|
+
#
|
47
|
+
# @param indent_level [Integer] The indentation level to generate the lines at.
|
48
|
+
# @param options [Options] The formatting options to use.
|
49
|
+
# @return [Array<String>] The RBS lines, formatted as specified.
|
50
|
+
def generate_rbs(indent_level, options)
|
51
|
+
[options.indented(indent_level, "#{name}: #{String === @type ? @type : @type.generate_rbs}")]
|
52
|
+
end
|
53
|
+
|
54
|
+
sig do
|
55
|
+
override.params(
|
56
|
+
others: T::Array[RbsGenerator::RbsObject]
|
57
|
+
).returns(T::Boolean)
|
58
|
+
end
|
59
|
+
# Given an array of {Constant} instances, returns true if they may be
|
60
|
+
# merged into this instance using {merge_into_self}. This is always false.
|
61
|
+
#
|
62
|
+
# @param others [Array<RbsGenerator::RbsObject>] An array of other
|
63
|
+
# {Constant} instances.
|
64
|
+
# @return [Boolean] Whether this instance may be merged with them.
|
65
|
+
def mergeable?(others)
|
66
|
+
others.all? { |other| self == other }
|
67
|
+
end
|
68
|
+
|
69
|
+
sig do
|
70
|
+
override.params(
|
71
|
+
others: T::Array[RbsGenerator::RbsObject]
|
72
|
+
).void
|
73
|
+
end
|
74
|
+
# Given an array of {Constant} instances, merges them into this one.
|
75
|
+
# This particular implementation will simply do nothing, as instances
|
76
|
+
# are only mergeable if they are indentical.
|
77
|
+
# You MUST ensure that {mergeable?} is true for those instances.
|
78
|
+
#
|
79
|
+
# @param others [Array<RbsGenerator::RbsObject>] An array of other
|
80
|
+
# {Extend} instances.
|
81
|
+
# @return [void]
|
82
|
+
def merge_into_self(others)
|
83
|
+
# We don't need to change anything! We only merge identical constants
|
84
|
+
end
|
85
|
+
|
86
|
+
sig { override.returns(String) }
|
87
|
+
# Returns a human-readable brief string description of this code.
|
88
|
+
#
|
89
|
+
# @return [String]
|
90
|
+
def describe
|
91
|
+
"Constant (#{name} = #{type})"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|