rubocop 0.85.1 → 0.86.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/README.md +16 -4
- data/config/default.yml +29 -1
- data/lib/rubocop.rb +3 -0
- data/lib/rubocop/cli/command/init_dotfile.rb +1 -1
- data/lib/rubocop/config.rb +1 -1
- data/lib/rubocop/config_loader_resolver.rb +1 -1
- data/lib/rubocop/cop/gemspec/required_ruby_version.rb +1 -1
- data/lib/rubocop/cop/generator/configuration_injector.rb +1 -1
- data/lib/rubocop/cop/layout/comment_indentation.rb +3 -3
- data/lib/rubocop/cop/layout/empty_comment.rb +1 -1
- data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +2 -0
- data/lib/rubocop/cop/layout/end_of_line.rb +1 -1
- data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -1
- data/lib/rubocop/cop/layout/first_array_element_line_break.rb +1 -1
- data/lib/rubocop/cop/layout/hash_alignment.rb +1 -1
- data/lib/rubocop/cop/layout/multiline_block_layout.rb +1 -1
- data/lib/rubocop/cop/layout/space_after_colon.rb +1 -1
- data/lib/rubocop/cop/layout/space_around_keyword.rb +2 -2
- data/lib/rubocop/cop/layout/space_around_operators.rb +1 -1
- data/lib/rubocop/cop/layout/space_before_block_braces.rb +14 -0
- data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +1 -1
- data/lib/rubocop/cop/layout/space_inside_block_braces.rb +1 -1
- data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +2 -2
- data/lib/rubocop/cop/lint/constant_resolution.rb +89 -0
- data/lib/rubocop/cop/lint/float_out_of_range.rb +1 -1
- data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +6 -1
- data/lib/rubocop/cop/lint/percent_string_array.rb +1 -1
- data/lib/rubocop/cop/lint/percent_symbol_array.rb +1 -1
- data/lib/rubocop/cop/lint/raise_exception.rb +12 -4
- data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +4 -2
- data/lib/rubocop/cop/lint/regexp_as_condition.rb +6 -0
- data/lib/rubocop/cop/lint/safe_navigation_with_empty.rb +1 -1
- data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +35 -3
- data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +1 -1
- data/lib/rubocop/cop/metrics/utils/iterating_block.rb +61 -0
- data/lib/rubocop/cop/mixin/configurable_naming.rb +1 -1
- data/lib/rubocop/cop/mixin/documentation_comment.rb +2 -2
- data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -1
- data/lib/rubocop/cop/mixin/first_element_line_break.rb +1 -1
- data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +1 -1
- data/lib/rubocop/cop/mixin/parentheses.rb +1 -2
- data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +1 -1
- data/lib/rubocop/cop/mixin/range_help.rb +1 -1
- data/lib/rubocop/cop/mixin/regexp_literal_help.rb +27 -0
- data/lib/rubocop/cop/mixin/string_literals_help.rb +1 -1
- data/lib/rubocop/cop/mixin/surrounding_space.rb +3 -3
- data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
- data/lib/rubocop/cop/mixin/uncommunicative_name.rb +2 -2
- data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +1 -1
- data/lib/rubocop/cop/naming/file_name.rb +1 -3
- data/lib/rubocop/cop/naming/heredoc_delimiter_naming.rb +1 -1
- data/lib/rubocop/cop/style/bare_percent_literals.rb +1 -1
- data/lib/rubocop/cop/style/block_delimiters.rb +2 -4
- data/lib/rubocop/cop/style/class_and_module_children.rb +1 -1
- data/lib/rubocop/cop/style/command_literal.rb +1 -1
- data/lib/rubocop/cop/style/commented_keyword.rb +2 -2
- data/lib/rubocop/cop/style/conditional_assignment.rb +1 -1
- data/lib/rubocop/cop/style/copyright.rb +3 -3
- data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +1 -1
- data/lib/rubocop/cop/style/documentation.rb +2 -2
- data/lib/rubocop/cop/style/empty_case_condition.rb +8 -6
- data/lib/rubocop/cop/style/encoding.rb +1 -1
- data/lib/rubocop/cop/style/exponential_notation.rb +2 -2
- data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +1 -1
- data/lib/rubocop/cop/style/identical_conditional_branches.rb +1 -1
- data/lib/rubocop/cop/style/if_inside_else.rb +1 -1
- data/lib/rubocop/cop/style/inverse_methods.rb +1 -1
- data/lib/rubocop/cop/style/ip_addresses.rb +1 -1
- data/lib/rubocop/cop/style/multiline_if_then.rb +1 -1
- data/lib/rubocop/cop/style/multiline_ternary_operator.rb +17 -6
- data/lib/rubocop/cop/style/nested_ternary_operator.rb +27 -0
- data/lib/rubocop/cop/style/next.rb +2 -2
- data/lib/rubocop/cop/style/numeric_literal_prefix.rb +2 -2
- data/lib/rubocop/cop/style/percent_literal_delimiters.rb +1 -1
- data/lib/rubocop/cop/style/redundant_fetch_block.rb +103 -0
- data/lib/rubocop/cop/style/redundant_parentheses.rb +1 -1
- data/lib/rubocop/cop/style/redundant_percent_q.rb +2 -2
- data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +2 -2
- data/lib/rubocop/cop/style/redundant_regexp_escape.rb +14 -23
- data/lib/rubocop/cop/style/redundant_self.rb +6 -9
- data/lib/rubocop/cop/style/sample.rb +1 -1
- data/lib/rubocop/cop/style/semicolon.rb +1 -1
- data/lib/rubocop/cop/style/struct_inheritance.rb +21 -0
- data/lib/rubocop/cop/style/symbol_array.rb +5 -5
- data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
- data/lib/rubocop/cop/style/word_array.rb +1 -1
- data/lib/rubocop/cop/style/yoda_condition.rb +18 -1
- data/lib/rubocop/cop/util.rb +2 -2
- data/lib/rubocop/cop/utils/format_string.rb +1 -1
- data/lib/rubocop/formatter/disabled_config_formatter.rb +1 -1
- data/lib/rubocop/formatter/formatter_set.rb +1 -1
- data/lib/rubocop/name_similarity.rb +6 -0
- data/lib/rubocop/path_util.rb +2 -2
- data/lib/rubocop/platform.rb +1 -1
- data/lib/rubocop/rspec/expect_offense.rb +12 -2
- data/lib/rubocop/target_finder.rb +1 -1
- data/lib/rubocop/target_ruby.rb +1 -1
- data/lib/rubocop/version.rb +1 -1
- metadata +11 -2
@@ -123,10 +123,9 @@ module RuboCop
|
|
123
123
|
# special handling for Action Pack Variants file names like
|
124
124
|
# some_file.xlsx+mobile.axlsx
|
125
125
|
basename = basename.sub('+', '_')
|
126
|
-
basename
|
126
|
+
basename.match?(regex || SNAKE_CASE)
|
127
127
|
end
|
128
128
|
|
129
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
130
129
|
def find_class_or_module(node, namespace)
|
131
130
|
return nil unless node
|
132
131
|
|
@@ -145,7 +144,6 @@ module RuboCop
|
|
145
144
|
|
146
145
|
nil
|
147
146
|
end
|
148
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
149
147
|
|
150
148
|
def match_namespace(node, namespace, expected)
|
151
149
|
match_partial = partial_matcher!(expected)
|
@@ -245,14 +245,13 @@ module RuboCop
|
|
245
245
|
end
|
246
246
|
|
247
247
|
def whitespace_before?(range)
|
248
|
-
range.source_buffer.source[range.begin_pos - 1, 1]
|
248
|
+
/\s/.match?(range.source_buffer.source[range.begin_pos - 1, 1])
|
249
249
|
end
|
250
250
|
|
251
251
|
def whitespace_after?(range, length = 1)
|
252
|
-
range.source_buffer.source[range.begin_pos + length, 1]
|
252
|
+
/\s/.match?(range.source_buffer.source[range.begin_pos + length, 1])
|
253
253
|
end
|
254
254
|
|
255
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
256
255
|
def get_blocks(node, &block)
|
257
256
|
case node.type
|
258
257
|
when :block
|
@@ -270,7 +269,6 @@ module RuboCop
|
|
270
269
|
node.each_child_node { |child| get_blocks(child, &block) }
|
271
270
|
end
|
272
271
|
end
|
273
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
274
272
|
|
275
273
|
def proper_block_style?(node)
|
276
274
|
return special_method_proper_block_style?(node) if special_method?(node.method_name)
|
@@ -55,8 +55,8 @@ module RuboCop
|
|
55
55
|
|
56
56
|
def offensive?(comment)
|
57
57
|
line = line(comment)
|
58
|
-
KEYWORDS.any? { |word|
|
59
|
-
ALLOWED_COMMENTS.none? { |c|
|
58
|
+
KEYWORDS.any? { |word| /^\s*#{word}\s/.match?(line) } &&
|
59
|
+
ALLOWED_COMMENTS.none? { |c| /#\s*#{c}/.match?(line) }
|
60
60
|
end
|
61
61
|
|
62
62
|
def message(comment)
|
@@ -67,7 +67,7 @@ module RuboCop
|
|
67
67
|
|
68
68
|
private
|
69
69
|
|
70
|
-
def expand_elsif(node, elsif_branches = [])
|
70
|
+
def expand_elsif(node, elsif_branches = []) # rubocop:todo Metrics/CyclomaticComplexity
|
71
71
|
return [] if node.nil? || !node.if_type? || !node.elsif?
|
72
72
|
|
73
73
|
elsif_branches << node.if_branch
|
@@ -72,14 +72,14 @@ module RuboCop
|
|
72
72
|
return false if token_index >= processed_source.tokens.size
|
73
73
|
|
74
74
|
token = processed_source.tokens[token_index]
|
75
|
-
token.comment? && token.text
|
75
|
+
token.comment? && /^#!.*$/.match?(token.text)
|
76
76
|
end
|
77
77
|
|
78
78
|
def encoding_token?(processed_source, token_index)
|
79
79
|
return false if token_index >= processed_source.tokens.size
|
80
80
|
|
81
81
|
token = processed_source.tokens[token_index]
|
82
|
-
token.comment? &&
|
82
|
+
token.comment? && /^#.*coding\s?[:=]\s?(?:UTF|utf)-8/.match?(token.text)
|
83
83
|
end
|
84
84
|
|
85
85
|
def notice_found?(processed_source)
|
@@ -88,7 +88,7 @@ module RuboCop
|
|
88
88
|
processed_source.each_token do |token|
|
89
89
|
break unless token.comment?
|
90
90
|
|
91
|
-
notice_found =
|
91
|
+
notice_found = notice_regexp.match?(token.text)
|
92
92
|
break if notice_found
|
93
93
|
end
|
94
94
|
notice_found
|
@@ -104,7 +104,7 @@ module RuboCop
|
|
104
104
|
end
|
105
105
|
|
106
106
|
def compact_namespace?(node)
|
107
|
-
node.loc.name.source
|
107
|
+
/::/.match?(node.loc.name.source)
|
108
108
|
end
|
109
109
|
|
110
110
|
# First checks if the :nodoc: comment is associated with the
|
@@ -123,7 +123,7 @@ module RuboCop
|
|
123
123
|
end
|
124
124
|
|
125
125
|
def nodoc?(comment, require_all = false)
|
126
|
-
|
126
|
+
/^#\s*:nodoc:#{"\s+all\s*$" if require_all}/.match?(comment.text)
|
127
127
|
end
|
128
128
|
|
129
129
|
def nodoc(node)
|
@@ -43,13 +43,15 @@ module RuboCop
|
|
43
43
|
|
44
44
|
def on_case(case_node)
|
45
45
|
return if case_node.condition
|
46
|
-
return if case_node.when_branches.any? do |when_branch|
|
47
|
-
when_branch.each_descendant.any?(&:return_type?)
|
48
|
-
end
|
49
46
|
|
50
|
-
|
51
|
-
|
52
|
-
|
47
|
+
branch_bodies = [
|
48
|
+
*case_node.when_branches.map(&:body),
|
49
|
+
case_node.else_branch
|
50
|
+
].compact
|
51
|
+
|
52
|
+
return if branch_bodies.any? do |body|
|
53
|
+
body.return_type? ||
|
54
|
+
body.each_descendant.any?(&:return_type?)
|
53
55
|
end
|
54
56
|
|
55
57
|
add_offense(case_node, location: :keyword)
|
@@ -69,7 +69,7 @@ module RuboCop
|
|
69
69
|
|
70
70
|
def scientific?(node)
|
71
71
|
mantissa, = node.source.split('e')
|
72
|
-
|
72
|
+
/^-?[1-9](\.\d*[0-9])?$/.match?(mantissa)
|
73
73
|
end
|
74
74
|
|
75
75
|
def engineering?(node)
|
@@ -85,7 +85,7 @@ module RuboCop
|
|
85
85
|
|
86
86
|
def integral(node)
|
87
87
|
mantissa, = node.source.split('e')
|
88
|
-
|
88
|
+
/^-?[1-9](\d*[1-9])?$/.match?(mantissa)
|
89
89
|
end
|
90
90
|
|
91
91
|
def offense?(node)
|
@@ -148,7 +148,7 @@ module RuboCop
|
|
148
148
|
end
|
149
149
|
|
150
150
|
next_token = processed_source.tokens[token_number]
|
151
|
-
token = next_token if
|
151
|
+
token = next_token if Encoding::ENCODING_PATTERN.match?(next_token&.text)
|
152
152
|
|
153
153
|
token
|
154
154
|
end
|
@@ -81,7 +81,7 @@ module RuboCop
|
|
81
81
|
|
82
82
|
private
|
83
83
|
|
84
|
-
def check_branches(branches)
|
84
|
+
def check_branches(branches) # rubocop:todo Metrics/CyclomaticComplexity
|
85
85
|
# return if any branch is empty. An empty branch can be an `if`
|
86
86
|
# without an `else` or a branch that contains only comments.
|
87
87
|
return if branches.any?(&:nil?)
|
@@ -61,7 +61,7 @@ module RuboCop
|
|
61
61
|
class IfInsideElse < Cop
|
62
62
|
MSG = 'Convert `if` nested inside `else` to `elsif`.'
|
63
63
|
|
64
|
-
def on_if(node)
|
64
|
+
def on_if(node) # rubocop:todo Metrics/CyclomaticComplexity
|
65
65
|
return if node.ternary? || node.unless?
|
66
66
|
|
67
67
|
else_branch = node.else_branch
|
@@ -34,7 +34,7 @@ module RuboCop
|
|
34
34
|
# shortcut out if the string does not look like an IP address
|
35
35
|
return false unless could_be_ip?(contents)
|
36
36
|
|
37
|
-
|
37
|
+
::Resolv::IPv4::Regex.match?(contents) || ::Resolv::IPv6::Regex.match?(contents)
|
38
38
|
end
|
39
39
|
|
40
40
|
# Dummy implementation of method in ConfigurableEnforcedStyle that is
|
@@ -17,12 +17,11 @@ module RuboCop
|
|
17
17
|
#
|
18
18
|
# # good
|
19
19
|
# a = cond ? b : c
|
20
|
-
# a =
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
# end
|
20
|
+
# a = if cond
|
21
|
+
# b
|
22
|
+
# else
|
23
|
+
# c
|
24
|
+
# end
|
26
25
|
class MultilineTernaryOperator < Cop
|
27
26
|
MSG = 'Avoid multi-line ternary operators, ' \
|
28
27
|
'use `if` or `unless` instead.'
|
@@ -32,6 +31,18 @@ module RuboCop
|
|
32
31
|
|
33
32
|
add_offense(node)
|
34
33
|
end
|
34
|
+
|
35
|
+
def autocorrect(node)
|
36
|
+
lambda do |corrector|
|
37
|
+
corrector.replace(node, <<~RUBY.chop)
|
38
|
+
if #{node.condition.source}
|
39
|
+
#{node.if_branch.source}
|
40
|
+
else
|
41
|
+
#{node.else_branch.source}
|
42
|
+
end
|
43
|
+
RUBY
|
44
|
+
end
|
45
|
+
end
|
35
46
|
end
|
36
47
|
end
|
37
48
|
end
|
@@ -26,6 +26,33 @@ module RuboCop
|
|
26
26
|
add_offense(nested_ternary)
|
27
27
|
end
|
28
28
|
end
|
29
|
+
|
30
|
+
def autocorrect(node)
|
31
|
+
if_node = if_node(node)
|
32
|
+
|
33
|
+
lambda do |corrector|
|
34
|
+
corrector.replace(if_node, <<~RUBY.chop)
|
35
|
+
if #{if_node.condition.source}
|
36
|
+
#{remove_parentheses(if_node.if_branch.source)}
|
37
|
+
else
|
38
|
+
#{if_node.else_branch.source}
|
39
|
+
end
|
40
|
+
RUBY
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def if_node(node)
|
47
|
+
node = node.parent
|
48
|
+
return node if node.if_type?
|
49
|
+
|
50
|
+
if_node(node)
|
51
|
+
end
|
52
|
+
|
53
|
+
def remove_parentheses(source)
|
54
|
+
source.gsub(/\A\(/, '').gsub(/\)\z/, '')
|
55
|
+
end
|
29
56
|
end
|
30
57
|
end
|
31
58
|
end
|
@@ -191,7 +191,7 @@ module RuboCop
|
|
191
191
|
end
|
192
192
|
|
193
193
|
def end_followed_by_whitespace_only?(source_buffer, end_pos)
|
194
|
-
source_buffer.source[end_pos..-1]
|
194
|
+
/\A\s*$/.match?(source_buffer.source[end_pos..-1])
|
195
195
|
end
|
196
196
|
|
197
197
|
def reindentable_lines(node)
|
@@ -201,7 +201,7 @@ module RuboCop
|
|
201
201
|
lines = (node.source_range.line + 1)...node.loc.end.line
|
202
202
|
lines = lines.to_a - heredoc_lines(node)
|
203
203
|
# Skip blank lines
|
204
|
-
lines.reject { |lineno| buffer.source_line(lineno)
|
204
|
+
lines.reject { |lineno| /\A\s*\z/.match?(buffer.source_line(lineno)) }
|
205
205
|
end
|
206
206
|
|
207
207
|
# Adjust indentation of `lines` to match `node`
|
@@ -77,9 +77,9 @@ module RuboCop
|
|
77
77
|
end
|
78
78
|
|
79
79
|
def octal_literal_type(literal)
|
80
|
-
if literal
|
80
|
+
if OCTAL_ZERO_ONLY_REGEX.match?(literal) && octal_zero_only?
|
81
81
|
:octal_zero_only
|
82
|
-
elsif literal
|
82
|
+
elsif OCTAL_REGEX.match?(literal) && !octal_zero_only?
|
83
83
|
:octal
|
84
84
|
end
|
85
85
|
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# This cop identifies places where `fetch(key) { value }`
|
7
|
+
# can be replaced by `fetch(key, value)`.
|
8
|
+
#
|
9
|
+
# In such cases `fetch(key, value)` method is faster
|
10
|
+
# than `fetch(key) { value }`.
|
11
|
+
#
|
12
|
+
# @example SafeForConstants: false (default)
|
13
|
+
# # bad
|
14
|
+
# hash.fetch(:key) { 5 }
|
15
|
+
# hash.fetch(:key) { true }
|
16
|
+
# hash.fetch(:key) { nil }
|
17
|
+
# array.fetch(5) { :value }
|
18
|
+
# ENV.fetch(:key) { 'value' }
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# hash.fetch(:key, 5)
|
22
|
+
# hash.fetch(:key, true)
|
23
|
+
# hash.fetch(:key, nil)
|
24
|
+
# array.fetch(5, :value)
|
25
|
+
# ENV.fetch(:key, 'value')
|
26
|
+
#
|
27
|
+
# @example SafeForConstants: true
|
28
|
+
# # bad
|
29
|
+
# ENV.fetch(:key) { VALUE }
|
30
|
+
#
|
31
|
+
# # good
|
32
|
+
# ENV.fetch(:key, VALUE)
|
33
|
+
#
|
34
|
+
class RedundantFetchBlock < Cop
|
35
|
+
include FrozenStringLiteral
|
36
|
+
include RangeHelp
|
37
|
+
|
38
|
+
MSG = 'Use `%<good>s` instead of `%<bad>s`.'
|
39
|
+
|
40
|
+
def_node_matcher :redundant_fetch_block_candidate?, <<~PATTERN
|
41
|
+
(block
|
42
|
+
$(send _ :fetch _)
|
43
|
+
(args)
|
44
|
+
${#basic_literal? const_type?})
|
45
|
+
PATTERN
|
46
|
+
|
47
|
+
def on_block(node)
|
48
|
+
redundant_fetch_block_candidate?(node) do |send, body|
|
49
|
+
return if body.const_type? && !check_for_constant?
|
50
|
+
return if body.str_type? && !check_for_string?
|
51
|
+
|
52
|
+
range = fetch_range(send, node)
|
53
|
+
good = build_good_method(send, body)
|
54
|
+
bad = build_bad_method(send, body)
|
55
|
+
|
56
|
+
add_offense(
|
57
|
+
node,
|
58
|
+
location: range,
|
59
|
+
message: format(MSG, good: good, bad: bad)
|
60
|
+
)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def autocorrect(node)
|
65
|
+
redundant_fetch_block_candidate?(node) do |send, body|
|
66
|
+
lambda do |corrector|
|
67
|
+
receiver, _, key = send.children
|
68
|
+
corrector.replace(node, "#{receiver.source}.fetch(#{key.source}, #{body.source})")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def basic_literal?(node)
|
76
|
+
node.basic_literal?
|
77
|
+
end
|
78
|
+
|
79
|
+
def fetch_range(send, node)
|
80
|
+
range_between(send.loc.selector.begin_pos, node.loc.end.end_pos)
|
81
|
+
end
|
82
|
+
|
83
|
+
def build_good_method(send, body)
|
84
|
+
key = send.children[2].source
|
85
|
+
"fetch(#{key}, #{body.source})"
|
86
|
+
end
|
87
|
+
|
88
|
+
def build_bad_method(send, body)
|
89
|
+
key = send.children[2].source
|
90
|
+
"fetch(#{key}) { #{body.source} }"
|
91
|
+
end
|
92
|
+
|
93
|
+
def check_for_constant?
|
94
|
+
cop_config['SafeForConstants']
|
95
|
+
end
|
96
|
+
|
97
|
+
def check_for_string?
|
98
|
+
frozen_string_literals_enabled?
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|