rspec-support 3.0.4 → 3.12.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data/Changelog.md +322 -0
- data/{LICENSE.txt → LICENSE.md} +3 -2
- data/README.md +29 -6
- data/lib/rspec/support/caller_filter.rb +35 -16
- data/lib/rspec/support/comparable_version.rb +46 -0
- data/lib/rspec/support/differ.rb +51 -41
- data/lib/rspec/support/directory_maker.rb +63 -0
- data/lib/rspec/support/encoded_string.rb +110 -15
- data/lib/rspec/support/fuzzy_matcher.rb +5 -6
- data/lib/rspec/support/hunk_generator.rb +0 -1
- data/lib/rspec/support/matcher_definition.rb +42 -0
- data/lib/rspec/support/method_signature_verifier.rb +287 -54
- data/lib/rspec/support/mutex.rb +73 -0
- data/lib/rspec/support/object_formatter.rb +275 -0
- data/lib/rspec/support/recursive_const_methods.rb +76 -0
- data/lib/rspec/support/reentrant_mutex.rb +78 -0
- data/lib/rspec/support/ruby_features.rb +177 -14
- data/lib/rspec/support/source/location.rb +21 -0
- data/lib/rspec/support/source/node.rb +110 -0
- data/lib/rspec/support/source/token.rb +94 -0
- data/lib/rspec/support/source.rb +85 -0
- data/lib/rspec/support/spec/deprecation_helpers.rb +19 -32
- data/lib/rspec/support/spec/diff_helpers.rb +31 -0
- data/lib/rspec/support/spec/in_sub_process.rb +43 -16
- data/lib/rspec/support/spec/library_wide_checks.rb +150 -0
- data/lib/rspec/support/spec/shell_out.rb +108 -0
- data/lib/rspec/support/spec/stderr_splitter.rb +31 -9
- data/lib/rspec/support/spec/string_matcher.rb +45 -0
- data/lib/rspec/support/spec/with_isolated_directory.rb +13 -0
- data/lib/rspec/support/spec/with_isolated_stderr.rb +0 -2
- data/lib/rspec/support/spec.rb +46 -26
- data/lib/rspec/support/version.rb +1 -1
- data/lib/rspec/support/warnings.rb +6 -6
- data/lib/rspec/support/with_keywords_when_needed.rb +33 -0
- data/lib/rspec/support.rb +87 -3
- data.tar.gz.sig +0 -0
- metadata +70 -52
- metadata.gz.sig +0 -0
- data/lib/rspec/support/version_checker.rb +0 -53
data/lib/rspec/support/differ.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
1
|
RSpec::Support.require_rspec_support 'encoded_string'
|
2
2
|
RSpec::Support.require_rspec_support 'hunk_generator'
|
3
|
+
RSpec::Support.require_rspec_support "object_formatter"
|
3
4
|
|
4
5
|
require 'pp'
|
5
6
|
|
6
7
|
module RSpec
|
7
8
|
module Support
|
9
|
+
# rubocop:disable Metrics/ClassLength
|
8
10
|
class Differ
|
9
11
|
def diff(actual, expected)
|
10
12
|
diff = ""
|
11
13
|
|
12
|
-
|
14
|
+
unless actual.nil? || expected.nil?
|
13
15
|
if all_strings?(actual, expected)
|
14
16
|
if any_multiline_strings?(actual, expected)
|
15
17
|
diff = diff_as_string(coerce_to_string(actual), coerce_to_string(expected))
|
@@ -22,34 +24,35 @@ module RSpec
|
|
22
24
|
diff.to_s
|
23
25
|
end
|
24
26
|
|
27
|
+
# rubocop:disable Metrics/MethodLength
|
25
28
|
def diff_as_string(actual, expected)
|
26
|
-
|
29
|
+
encoding = EncodedString.pick_encoding(actual, expected)
|
27
30
|
|
28
|
-
|
29
|
-
|
31
|
+
actual = EncodedString.new(actual, encoding)
|
32
|
+
expected = EncodedString.new(expected, encoding)
|
30
33
|
|
31
|
-
output = EncodedString.new("\n",
|
34
|
+
output = EncodedString.new("\n", encoding)
|
35
|
+
hunks = build_hunks(actual, expected)
|
32
36
|
|
33
37
|
hunks.each_cons(2) do |prev_hunk, current_hunk|
|
34
38
|
begin
|
35
39
|
if current_hunk.overlaps?(prev_hunk)
|
36
40
|
add_old_hunk_to_hunk(current_hunk, prev_hunk)
|
37
41
|
else
|
38
|
-
add_to_output(output, prev_hunk.diff(
|
42
|
+
add_to_output(output, prev_hunk.diff(format_type).to_s)
|
39
43
|
end
|
40
44
|
ensure
|
41
45
|
add_to_output(output, "\n")
|
42
46
|
end
|
43
47
|
end
|
44
48
|
|
45
|
-
if hunks.last
|
46
|
-
finalize_output(output, hunks.last.diff(format).to_s)
|
47
|
-
end
|
49
|
+
finalize_output(output, hunks.last.diff(format_type).to_s) if hunks.last
|
48
50
|
|
49
51
|
color_diff output
|
50
52
|
rescue Encoding::CompatibilityError
|
51
|
-
handle_encoding_errors
|
53
|
+
handle_encoding_errors(actual, expected)
|
52
54
|
end
|
55
|
+
# rubocop:enable Metrics/MethodLength
|
53
56
|
|
54
57
|
def diff_as_object(actual, expected)
|
55
58
|
actual_as_string = object_to_string(actual)
|
@@ -57,8 +60,9 @@ module RSpec
|
|
57
60
|
diff_as_string(actual_as_string, expected_as_string)
|
58
61
|
end
|
59
62
|
|
60
|
-
|
61
|
-
|
63
|
+
def color?
|
64
|
+
@color
|
65
|
+
end
|
62
66
|
|
63
67
|
def initialize(opts={})
|
64
68
|
@color = opts.fetch(:color, false)
|
@@ -68,19 +72,19 @@ module RSpec
|
|
68
72
|
private
|
69
73
|
|
70
74
|
def no_procs?(*args)
|
71
|
-
args.
|
75
|
+
safely_flatten(args).none? { |a| Proc === a }
|
72
76
|
end
|
73
77
|
|
74
78
|
def all_strings?(*args)
|
75
|
-
args.
|
79
|
+
safely_flatten(args).all? { |a| String === a }
|
76
80
|
end
|
77
81
|
|
78
82
|
def any_multiline_strings?(*args)
|
79
|
-
all_strings?(*args) && args.
|
83
|
+
all_strings?(*args) && safely_flatten(args).any? { |a| multiline?(a) }
|
80
84
|
end
|
81
85
|
|
82
86
|
def no_numbers?(*args)
|
83
|
-
args.
|
87
|
+
safely_flatten(args).none? { |a| Numeric === a }
|
84
88
|
end
|
85
89
|
|
86
90
|
def coerce_to_string(string_or_array)
|
@@ -93,7 +97,7 @@ module RSpec
|
|
93
97
|
if Array === entry
|
94
98
|
entry.inspect
|
95
99
|
else
|
96
|
-
entry.to_s.gsub("\n", "\\n")
|
100
|
+
entry.to_s.gsub("\n", "\\n").gsub("\r", "\\r")
|
97
101
|
end
|
98
102
|
end
|
99
103
|
end
|
@@ -108,8 +112,8 @@ module RSpec
|
|
108
112
|
end
|
109
113
|
end
|
110
114
|
|
111
|
-
def
|
112
|
-
|
115
|
+
def build_hunks(actual, expected)
|
116
|
+
HunkGenerator.new(actual, expected).hunks
|
113
117
|
end
|
114
118
|
|
115
119
|
def finalize_output(output, final_line)
|
@@ -125,7 +129,12 @@ module RSpec
|
|
125
129
|
hunk.merge(oldhunk)
|
126
130
|
end
|
127
131
|
|
128
|
-
def
|
132
|
+
def safely_flatten(array)
|
133
|
+
array = array.flatten(1) until (array == array.flatten(1))
|
134
|
+
array
|
135
|
+
end
|
136
|
+
|
137
|
+
def format_type
|
129
138
|
:unified
|
130
139
|
end
|
131
140
|
|
@@ -152,7 +161,7 @@ module RSpec
|
|
152
161
|
def color_diff(diff)
|
153
162
|
return diff unless color?
|
154
163
|
|
155
|
-
diff.lines.map
|
164
|
+
diff.lines.map do |line|
|
156
165
|
case line[0].chr
|
157
166
|
when "+"
|
158
167
|
green line
|
@@ -163,43 +172,44 @@ module RSpec
|
|
163
172
|
else
|
164
173
|
normal(line)
|
165
174
|
end
|
166
|
-
|
175
|
+
end.join
|
167
176
|
end
|
168
177
|
|
169
178
|
def object_to_string(object)
|
170
179
|
object = @object_preparer.call(object)
|
171
180
|
case object
|
172
181
|
when Hash
|
173
|
-
object
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
"#{pp_key} => #{pp_value},"
|
178
|
-
end.join("\n")
|
182
|
+
hash_to_string(object)
|
183
|
+
when Array
|
184
|
+
PP.pp(ObjectFormatter.prepare_for_inspection(object), "".dup)
|
179
185
|
when String
|
180
186
|
object =~ /\n/ ? object : object.inspect
|
181
187
|
else
|
182
|
-
PP.pp(object,"")
|
188
|
+
PP.pp(object, "".dup)
|
183
189
|
end
|
184
190
|
end
|
185
191
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
192
|
+
def hash_to_string(hash)
|
193
|
+
formatted_hash = ObjectFormatter.prepare_for_inspection(hash)
|
194
|
+
formatted_hash.keys.sort_by { |k| k.to_s }.map do |key|
|
195
|
+
pp_key = PP.singleline_pp(key, "".dup)
|
196
|
+
pp_value = PP.singleline_pp(formatted_hash[key], "".dup)
|
197
|
+
|
198
|
+
"#{pp_key} => #{pp_value},"
|
199
|
+
end.join("\n")
|
193
200
|
end
|
194
201
|
|
195
|
-
def handle_encoding_errors
|
196
|
-
if
|
197
|
-
"Could not produce a diff because the encoding of the actual string
|
198
|
-
|
202
|
+
def handle_encoding_errors(actual, expected)
|
203
|
+
if actual.source_encoding != expected.source_encoding
|
204
|
+
"Could not produce a diff because the encoding of the actual string " \
|
205
|
+
"(#{actual.source_encoding}) differs from the encoding of the expected " \
|
206
|
+
"string (#{expected.source_encoding})"
|
199
207
|
else
|
200
|
-
"Could not produce a diff because of the encoding of the string
|
208
|
+
"Could not produce a diff because of the encoding of the string " \
|
209
|
+
"(#{expected.source_encoding})"
|
201
210
|
end
|
202
211
|
end
|
203
212
|
end
|
213
|
+
# rubocop:enable Metrics/ClassLength
|
204
214
|
end
|
205
215
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
RSpec::Support.require_rspec_support 'ruby_features'
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module Support
|
5
|
+
# @api private
|
6
|
+
#
|
7
|
+
# Replacement for fileutils#mkdir_p because we don't want to require parts
|
8
|
+
# of stdlib in RSpec.
|
9
|
+
class DirectoryMaker
|
10
|
+
# @api private
|
11
|
+
#
|
12
|
+
# Implements nested directory construction
|
13
|
+
def self.mkdir_p(path)
|
14
|
+
stack = generate_stack(path)
|
15
|
+
path.split(File::SEPARATOR).each do |part|
|
16
|
+
stack = generate_path(stack, part)
|
17
|
+
begin
|
18
|
+
Dir.mkdir(stack) unless directory_exists?(stack)
|
19
|
+
rescue Errno::EEXIST => e
|
20
|
+
raise e unless directory_exists?(stack)
|
21
|
+
rescue Errno::ENOTDIR => e
|
22
|
+
raise Errno::EEXIST, e.message
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if OS.windows_file_path?
|
28
|
+
def self.generate_stack(path)
|
29
|
+
if path.start_with?(File::SEPARATOR)
|
30
|
+
File::SEPARATOR
|
31
|
+
elsif path[1] == ':'
|
32
|
+
''
|
33
|
+
else
|
34
|
+
'.'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
def self.generate_path(stack, part)
|
38
|
+
if stack == ''
|
39
|
+
part
|
40
|
+
elsif stack == File::SEPARATOR
|
41
|
+
File.join('', part)
|
42
|
+
else
|
43
|
+
File.join(stack, part)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
else
|
47
|
+
def self.generate_stack(path)
|
48
|
+
path.start_with?(File::SEPARATOR) ? File::SEPARATOR : "."
|
49
|
+
end
|
50
|
+
def self.generate_path(stack, part)
|
51
|
+
File.join(stack, part)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.directory_exists?(dirname)
|
56
|
+
File.exist?(dirname) && File.directory?(dirname)
|
57
|
+
end
|
58
|
+
private_class_method :directory_exists?
|
59
|
+
private_class_method :generate_stack
|
60
|
+
private_class_method :generate_path
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -2,10 +2,16 @@ module RSpec
|
|
2
2
|
module Support
|
3
3
|
# @private
|
4
4
|
class EncodedString
|
5
|
+
# Reduce allocations by storing constants.
|
6
|
+
UTF_8 = "UTF-8"
|
7
|
+
US_ASCII = "US-ASCII"
|
5
8
|
|
6
|
-
|
9
|
+
# Ruby's default replacement string is:
|
10
|
+
# U+FFFD ("\xEF\xBF\xBD"), for Unicode encoding forms, else
|
11
|
+
# ? ("\x3F")
|
12
|
+
REPLACE = "?"
|
7
13
|
|
8
|
-
def initialize(string, encoding
|
14
|
+
def initialize(string, encoding=nil)
|
9
15
|
@encoding = encoding
|
10
16
|
@source_encoding = detect_source_encoding(string)
|
11
17
|
@string = matching_encoding(string)
|
@@ -21,8 +27,18 @@ module RSpec
|
|
21
27
|
@string << matching_encoding(string)
|
22
28
|
end
|
23
29
|
|
24
|
-
|
25
|
-
|
30
|
+
if Ruby.jruby?
|
31
|
+
def split(regex_or_string)
|
32
|
+
@string.split(matching_encoding(regex_or_string))
|
33
|
+
rescue ArgumentError
|
34
|
+
# JRuby raises an ArgumentError when splitting a source string that
|
35
|
+
# contains invalid bytes.
|
36
|
+
remove_invalid_bytes(@string).split regex_or_string
|
37
|
+
end
|
38
|
+
else
|
39
|
+
def split(regex_or_string)
|
40
|
+
@string.split(matching_encoding(regex_or_string))
|
41
|
+
end
|
26
42
|
end
|
27
43
|
|
28
44
|
def to_s
|
@@ -30,35 +46,114 @@ module RSpec
|
|
30
46
|
end
|
31
47
|
alias :to_str :to_s
|
32
48
|
|
33
|
-
private
|
34
|
-
|
35
49
|
if String.method_defined?(:encoding)
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Encoding Exceptions:
|
54
|
+
#
|
55
|
+
# Raised by Encoding and String methods:
|
56
|
+
# Encoding::UndefinedConversionError:
|
57
|
+
# when a transcoding operation fails
|
58
|
+
# if the String contains characters invalid for the target encoding
|
59
|
+
# e.g. "\x80".encode('UTF-8','ASCII-8BIT')
|
60
|
+
# vs "\x80".encode('UTF-8','ASCII-8BIT', undef: :replace, replace: '<undef>')
|
61
|
+
# # => '<undef>'
|
62
|
+
# Encoding::CompatibilityError
|
63
|
+
# when Encoding.compatible?(str1, str2) is nil
|
64
|
+
# e.g. utf_16le_emoji_string.split("\n")
|
65
|
+
# e.g. valid_unicode_string.encode(utf8_encoding) << ascii_string
|
66
|
+
# Encoding::InvalidByteSequenceError:
|
67
|
+
# when the string being transcoded contains a byte invalid for
|
68
|
+
# either the source or target encoding
|
69
|
+
# e.g. "\x80".encode('UTF-8','US-ASCII')
|
70
|
+
# vs "\x80".encode('UTF-8','US-ASCII', invalid: :replace, replace: '<byte>')
|
71
|
+
# # => '<byte>'
|
72
|
+
# ArgumentError
|
73
|
+
# when operating on a string with invalid bytes
|
74
|
+
# e.g."\x80".split("\n")
|
75
|
+
# TypeError
|
76
|
+
# when a symbol is passed as an encoding
|
77
|
+
# Encoding.find(:"UTF-8")
|
78
|
+
# when calling force_encoding on an object
|
79
|
+
# that doesn't respond to #to_str
|
80
|
+
#
|
81
|
+
# Raised by transcoding methods:
|
82
|
+
# Encoding::ConverterNotFoundError:
|
83
|
+
# when a named encoding does not correspond with a known converter
|
84
|
+
# e.g. 'abc'.force_encoding('UTF-8').encode('foo')
|
85
|
+
# or a converter path cannot be found
|
86
|
+
# e.g. "\x80".force_encoding('ASCII-8BIT').encode('Emacs-Mule')
|
87
|
+
#
|
88
|
+
# Raised by byte <-> char conversions
|
89
|
+
# RangeError: out of char range
|
90
|
+
# e.g. the UTF-16LE emoji: 128169.chr
|
36
91
|
def matching_encoding(string)
|
92
|
+
string = remove_invalid_bytes(string)
|
37
93
|
string.encode(@encoding)
|
38
94
|
rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError
|
39
|
-
|
95
|
+
# Originally defined as a constant to avoid unneeded allocations, this hash must
|
96
|
+
# be defined inline (without {}) to avoid warnings on Ruby 2.7
|
97
|
+
#
|
98
|
+
# In MRI 2.1 'invalid: :replace' changed to also replace an invalid byte sequence
|
99
|
+
# see https://github.com/ruby/ruby/blob/v2_1_0/NEWS#L176
|
100
|
+
# https://www.ruby-forum.com/topic/6861247
|
101
|
+
# https://twitter.com/nalsh/status/553413844685438976
|
102
|
+
#
|
103
|
+
# For example, given:
|
104
|
+
# "\x80".force_encoding("Emacs-Mule").encode(:invalid => :replace).bytes.to_a
|
105
|
+
#
|
106
|
+
# On MRI 2.1 or above: 63 # '?'
|
107
|
+
# else : 128 # "\x80"
|
108
|
+
#
|
109
|
+
string.encode(@encoding, :invalid => :replace, :undef => :replace, :replace => REPLACE)
|
40
110
|
rescue Encoding::ConverterNotFoundError
|
41
|
-
|
111
|
+
# Originally defined as a constant to avoid unneeded allocations, this hash must
|
112
|
+
# be defined inline (without {}) to avoid warnings on Ruby 2.7
|
113
|
+
string.dup.force_encoding(@encoding).encode(:invalid => :replace, :replace => REPLACE)
|
42
114
|
end
|
43
115
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
116
|
+
# Prevents raising ArgumentError
|
117
|
+
if String.method_defined?(:scrub)
|
118
|
+
# https://github.com/ruby/ruby/blob/eeb05e8c11/doc/NEWS-2.1.0#L120-L123
|
119
|
+
# https://github.com/ruby/ruby/blob/v2_1_0/string.c#L8242
|
120
|
+
# https://github.com/hsbt/string-scrub
|
121
|
+
# https://github.com/rubinius/rubinius/blob/v2.5.2/kernel/common/string.rb#L1913-L1972
|
122
|
+
def remove_invalid_bytes(string)
|
123
|
+
string.scrub(REPLACE)
|
124
|
+
end
|
125
|
+
else
|
126
|
+
# http://stackoverflow.com/a/8711118/879854
|
127
|
+
# Loop over chars in a string replacing chars
|
128
|
+
# with invalid encoding, which is a pretty good proxy
|
129
|
+
# for the invalid byte sequence that causes an ArgumentError
|
130
|
+
def remove_invalid_bytes(string)
|
131
|
+
string.chars.map do |char|
|
132
|
+
char.valid_encoding? ? char : REPLACE
|
133
|
+
end.join
|
49
134
|
end
|
50
135
|
end
|
51
136
|
|
52
137
|
def detect_source_encoding(string)
|
53
138
|
string.encoding
|
54
139
|
end
|
140
|
+
|
141
|
+
def self.pick_encoding(source_a, source_b)
|
142
|
+
Encoding.compatible?(source_a, source_b) || Encoding.default_external
|
143
|
+
end
|
55
144
|
else
|
145
|
+
|
146
|
+
def self.pick_encoding(_source_a, _source_b)
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
56
151
|
def matching_encoding(string)
|
57
152
|
string
|
58
153
|
end
|
59
154
|
|
60
|
-
def detect_source_encoding(
|
61
|
-
|
155
|
+
def detect_source_encoding(_string)
|
156
|
+
US_ASCII
|
62
157
|
end
|
63
158
|
end
|
64
159
|
end
|
@@ -6,14 +6,14 @@ module RSpec
|
|
6
6
|
module FuzzyMatcher
|
7
7
|
# @api private
|
8
8
|
def self.values_match?(expected, actual)
|
9
|
-
if
|
9
|
+
if Hash === actual
|
10
|
+
return hashes_match?(expected, actual) if Hash === expected
|
11
|
+
elsif Array === expected && Enumerable === actual && !(Struct === actual)
|
10
12
|
return arrays_match?(expected, actual.to_a)
|
11
|
-
elsif Hash === expected && Hash === actual
|
12
|
-
return hashes_match?(expected, actual)
|
13
|
-
elsif actual == expected
|
14
|
-
return true
|
15
13
|
end
|
16
14
|
|
15
|
+
return true if expected == actual
|
16
|
+
|
17
17
|
begin
|
18
18
|
expected === actual
|
19
19
|
rescue ArgumentError
|
@@ -46,4 +46,3 @@ module RSpec
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
end
|
49
|
-
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Support
|
3
|
+
# @private
|
4
|
+
def self.matcher_definitions
|
5
|
+
@matcher_definitions ||= []
|
6
|
+
end
|
7
|
+
|
8
|
+
# Used internally to break cyclic dependency between mocks, expectations,
|
9
|
+
# and support. We don't currently have a consistent implementation of our
|
10
|
+
# matchers, though we are considering changing that:
|
11
|
+
# https://github.com/rspec/rspec-mocks/issues/513
|
12
|
+
#
|
13
|
+
# @private
|
14
|
+
def self.register_matcher_definition(&block)
|
15
|
+
matcher_definitions << block
|
16
|
+
end
|
17
|
+
|
18
|
+
# Remove a previously registered matcher. Useful for cleaning up after
|
19
|
+
# yourself in specs.
|
20
|
+
#
|
21
|
+
# @private
|
22
|
+
def self.deregister_matcher_definition(&block)
|
23
|
+
matcher_definitions.delete(block)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @private
|
27
|
+
def self.is_a_matcher?(object)
|
28
|
+
matcher_definitions.any? { |md| md.call(object) }
|
29
|
+
end
|
30
|
+
|
31
|
+
# @api private
|
32
|
+
#
|
33
|
+
# gives a string representation of an object for use in RSpec descriptions
|
34
|
+
def self.rspec_description_for_object(object)
|
35
|
+
if RSpec::Support.is_a_matcher?(object) && object.respond_to?(:description)
|
36
|
+
object.description
|
37
|
+
else
|
38
|
+
object
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|