rubocop-performance 1.7.0 → 1.9.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/README.md +8 -0
- data/config/default.yml +49 -7
- data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +4 -4
- data/lib/rubocop/cop/performance/ancestors_include.rb +16 -12
- data/lib/rubocop/cop/performance/array_semi_infinite_range_slice.rb +77 -0
- data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +17 -14
- data/lib/rubocop/cop/performance/bind_call.rb +9 -18
- data/lib/rubocop/cop/performance/block_given_with_explicit_block.rb +52 -0
- data/lib/rubocop/cop/performance/caller.rb +14 -15
- data/lib/rubocop/cop/performance/case_when_splat.rb +18 -11
- data/lib/rubocop/cop/performance/casecmp.rb +13 -20
- data/lib/rubocop/cop/performance/chain_array_allocation.rb +24 -28
- data/lib/rubocop/cop/performance/collection_literal_in_loop.rb +140 -0
- data/lib/rubocop/cop/performance/compare_with_block.rb +10 -21
- data/lib/rubocop/cop/performance/constant_regexp.rb +68 -0
- data/lib/rubocop/cop/performance/count.rb +14 -16
- data/lib/rubocop/cop/performance/delete_prefix.rb +14 -22
- data/lib/rubocop/cop/performance/delete_suffix.rb +14 -22
- data/lib/rubocop/cop/performance/detect.rb +65 -32
- data/lib/rubocop/cop/performance/double_start_end_with.rb +16 -24
- data/lib/rubocop/cop/performance/end_with.rb +9 -13
- data/lib/rubocop/cop/performance/fixed_size.rb +2 -1
- data/lib/rubocop/cop/performance/flat_map.rb +21 -22
- data/lib/rubocop/cop/performance/inefficient_hash_search.rb +15 -14
- data/lib/rubocop/cop/performance/io_readlines.rb +27 -42
- data/lib/rubocop/cop/performance/method_object_as_block.rb +32 -0
- data/lib/rubocop/cop/performance/open_struct.rb +3 -2
- data/lib/rubocop/cop/performance/range_include.rb +8 -6
- data/lib/rubocop/cop/performance/redundant_block_call.rb +15 -10
- data/lib/rubocop/cop/performance/redundant_match.rb +12 -6
- data/lib/rubocop/cop/performance/redundant_merge.rb +19 -17
- data/lib/rubocop/cop/performance/redundant_sort_block.rb +6 -16
- data/lib/rubocop/cop/performance/redundant_string_chars.rb +10 -18
- data/lib/rubocop/cop/performance/regexp_match.rb +20 -20
- data/lib/rubocop/cop/performance/reverse_each.rb +10 -5
- data/lib/rubocop/cop/performance/reverse_first.rb +5 -10
- data/lib/rubocop/cop/performance/size.rb +7 -6
- data/lib/rubocop/cop/performance/sort_reverse.rb +6 -15
- data/lib/rubocop/cop/performance/squeeze.rb +8 -11
- data/lib/rubocop/cop/performance/start_with.rb +9 -13
- data/lib/rubocop/cop/performance/string_include.rb +13 -14
- data/lib/rubocop/cop/performance/string_replacement.rb +24 -27
- data/lib/rubocop/cop/performance/sum.rb +236 -0
- data/lib/rubocop/cop/performance/times_map.rb +12 -18
- data/lib/rubocop/cop/performance/unfreeze_string.rb +20 -2
- data/lib/rubocop/cop/performance/uri_default_parser.rb +7 -12
- data/lib/rubocop/cop/performance_cops.rb +6 -0
- data/lib/rubocop/performance/version.rb +6 -1
- metadata +25 -13
@@ -4,7 +4,7 @@ module RuboCop
|
|
4
4
|
module Cop
|
5
5
|
module Performance
|
6
6
|
# This cop is used to identify usages of `count` on an `Enumerable` that
|
7
|
-
# follow calls to `select` or `reject`. Querying logic can instead be
|
7
|
+
# follow calls to `select`, `find_all`, `filter` or `reject`. Querying logic can instead be
|
8
8
|
# passed to the `count` call.
|
9
9
|
#
|
10
10
|
# @example
|
@@ -37,15 +37,17 @@ module RuboCop
|
|
37
37
|
# becomes:
|
38
38
|
#
|
39
39
|
# `Model.where(id: [1, 2, 3]).to_a.count { |m| m.method == true }`
|
40
|
-
class Count <
|
40
|
+
class Count < Base
|
41
41
|
include RangeHelp
|
42
|
+
extend AutoCorrector
|
42
43
|
|
43
44
|
MSG = 'Use `count` instead of `%<selector>s...%<counter>s`.'
|
45
|
+
RESTRICT_ON_SEND = %i[count length size].freeze
|
44
46
|
|
45
47
|
def_node_matcher :count_candidate?, <<~PATTERN
|
46
48
|
{
|
47
|
-
(send (block $(send _ ${:select :reject}) ...) ${:count :length :size})
|
48
|
-
(send $(send _ ${:select :reject} (:block_pass _)) ${:count :length :size})
|
49
|
+
(send (block $(send _ ${:select :filter :find_all :reject}) ...) ${:count :length :size})
|
50
|
+
(send $(send _ ${:select :filter :find_all :reject} (:block_pass _)) ${:count :length :size})
|
49
51
|
}
|
50
52
|
PATTERN
|
51
53
|
|
@@ -57,29 +59,25 @@ module RuboCop
|
|
57
59
|
selector_node.loc.selector.begin_pos
|
58
60
|
end
|
59
61
|
|
60
|
-
add_offense(
|
61
|
-
|
62
|
-
|
63
|
-
counter: counter))
|
62
|
+
add_offense(range, message: format(MSG, selector: selector, counter: counter)) do |corrector|
|
63
|
+
autocorrect(corrector, node, selector_node, selector)
|
64
|
+
end
|
64
65
|
end
|
65
66
|
end
|
66
67
|
|
67
|
-
|
68
|
-
|
68
|
+
private
|
69
|
+
|
70
|
+
def autocorrect(corrector, node, selector_node, selector)
|
69
71
|
selector_loc = selector_node.loc.selector
|
70
72
|
|
71
73
|
return if selector == :reject
|
72
74
|
|
73
75
|
range = source_starting_at(node) { |n| n.loc.dot.begin_pos }
|
74
76
|
|
75
|
-
|
76
|
-
|
77
|
-
corrector.replace(selector_loc, 'count')
|
78
|
-
end
|
77
|
+
corrector.remove(range)
|
78
|
+
corrector.replace(selector_loc, 'count')
|
79
79
|
end
|
80
80
|
|
81
|
-
private
|
82
|
-
|
83
81
|
def eligible_node?(node)
|
84
82
|
!(node.parent && node.parent.block_type?)
|
85
83
|
end
|
@@ -43,13 +43,15 @@ module RuboCop
|
|
43
43
|
# str.sub(/^prefix/, '')
|
44
44
|
# str.sub!(/^prefix/, '')
|
45
45
|
#
|
46
|
-
class DeletePrefix <
|
47
|
-
extend TargetRubyVersion
|
46
|
+
class DeletePrefix < Base
|
48
47
|
include RegexpMetacharacter
|
48
|
+
extend AutoCorrector
|
49
|
+
extend TargetRubyVersion
|
49
50
|
|
50
51
|
minimum_target_ruby_version 2.5
|
51
52
|
|
52
53
|
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
|
54
|
+
RESTRICT_ON_SEND = %i[gsub gsub! sub sub!].freeze
|
53
55
|
|
54
56
|
PREFERRED_METHODS = {
|
55
57
|
gsub: :delete_prefix,
|
@@ -63,31 +65,21 @@ module RuboCop
|
|
63
65
|
PATTERN
|
64
66
|
|
65
67
|
def on_send(node)
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
good_method = PREFERRED_METHODS[bad_method]
|
68
|
+
return unless (receiver, bad_method, regexp_str, replace_string = delete_prefix_candidate?(node))
|
69
|
+
return unless replace_string.blank?
|
70
70
|
|
71
|
-
|
71
|
+
good_method = PREFERRED_METHODS[bad_method]
|
72
72
|
|
73
|
-
|
74
|
-
end
|
75
|
-
end
|
73
|
+
message = format(MSG, current: bad_method, prefer: good_method)
|
76
74
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
regexp_str = drop_start_metacharacter(regexp_str)
|
82
|
-
regexp_str = interpret_string_escapes(regexp_str)
|
83
|
-
string_literal = to_string_literal(regexp_str)
|
75
|
+
add_offense(node.loc.selector, message: message) do |corrector|
|
76
|
+
regexp_str = drop_start_metacharacter(regexp_str)
|
77
|
+
regexp_str = interpret_string_escapes(regexp_str)
|
78
|
+
string_literal = to_string_literal(regexp_str)
|
84
79
|
|
85
|
-
|
80
|
+
new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
|
86
81
|
|
87
|
-
|
88
|
-
# https://github.com/rubocop-hq/rubocop/commit/82eb350d2cba16
|
89
|
-
corrector.replace(node.source_range, new_code)
|
90
|
-
end
|
82
|
+
corrector.replace(node, new_code)
|
91
83
|
end
|
92
84
|
end
|
93
85
|
end
|
@@ -43,13 +43,15 @@ module RuboCop
|
|
43
43
|
# str.sub(/suffix$/, '')
|
44
44
|
# str.sub!(/suffix$/, '')
|
45
45
|
#
|
46
|
-
class DeleteSuffix <
|
47
|
-
extend TargetRubyVersion
|
46
|
+
class DeleteSuffix < Base
|
48
47
|
include RegexpMetacharacter
|
48
|
+
extend AutoCorrector
|
49
|
+
extend TargetRubyVersion
|
49
50
|
|
50
51
|
minimum_target_ruby_version 2.5
|
51
52
|
|
52
53
|
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
|
54
|
+
RESTRICT_ON_SEND = %i[gsub gsub! sub sub!].freeze
|
53
55
|
|
54
56
|
PREFERRED_METHODS = {
|
55
57
|
gsub: :delete_suffix,
|
@@ -63,31 +65,21 @@ module RuboCop
|
|
63
65
|
PATTERN
|
64
66
|
|
65
67
|
def on_send(node)
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
good_method = PREFERRED_METHODS[bad_method]
|
68
|
+
return unless (receiver, bad_method, regexp_str, replace_string = delete_suffix_candidate?(node))
|
69
|
+
return unless replace_string.blank?
|
70
70
|
|
71
|
-
|
71
|
+
good_method = PREFERRED_METHODS[bad_method]
|
72
72
|
|
73
|
-
|
74
|
-
end
|
75
|
-
end
|
73
|
+
message = format(MSG, current: bad_method, prefer: good_method)
|
76
74
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
regexp_str = drop_end_metacharacter(regexp_str)
|
82
|
-
regexp_str = interpret_string_escapes(regexp_str)
|
83
|
-
string_literal = to_string_literal(regexp_str)
|
75
|
+
add_offense(node.loc.selector, message: message) do |corrector|
|
76
|
+
regexp_str = drop_end_metacharacter(regexp_str)
|
77
|
+
regexp_str = interpret_string_escapes(regexp_str)
|
78
|
+
string_literal = to_string_literal(regexp_str)
|
84
79
|
|
85
|
-
|
80
|
+
new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
|
86
81
|
|
87
|
-
|
88
|
-
# https://github.com/rubocop-hq/rubocop/commit/82eb350d2cba16
|
89
|
-
corrector.replace(node.source_range, new_code)
|
90
|
-
end
|
82
|
+
corrector.replace(node, new_code)
|
91
83
|
end
|
92
84
|
end
|
93
85
|
end
|
@@ -3,9 +3,9 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Performance
|
6
|
-
# This cop is used to identify usages of
|
7
|
-
#
|
8
|
-
#
|
6
|
+
# This cop is used to identify usages of `first`, `last`, `[0]` or `[-1]`
|
7
|
+
# chained to `select`, `find_all` or `filter` and change them to use
|
8
|
+
# `detect` instead.
|
9
9
|
#
|
10
10
|
# @example
|
11
11
|
# # bad
|
@@ -13,6 +13,10 @@ module RuboCop
|
|
13
13
|
# [].select { |item| true }.last
|
14
14
|
# [].find_all { |item| true }.first
|
15
15
|
# [].find_all { |item| true }.last
|
16
|
+
# [].filter { |item| true }.first
|
17
|
+
# [].filter { |item| true }.last
|
18
|
+
# [].filter { |item| true }[0]
|
19
|
+
# [].filter { |item| true }[-1]
|
16
20
|
#
|
17
21
|
# # good
|
18
22
|
# [].detect { |item| true }
|
@@ -22,47 +26,44 @@ module RuboCop
|
|
22
26
|
# `ActiveRecord` does not implement a `detect` method and `find` has its
|
23
27
|
# own meaning. Correcting ActiveRecord methods with this cop should be
|
24
28
|
# considered unsafe.
|
25
|
-
class Detect <
|
29
|
+
class Detect < Base
|
30
|
+
extend AutoCorrector
|
31
|
+
|
32
|
+
CANDIDATE_METHODS = Set[:select, :find_all, :filter].freeze
|
33
|
+
|
26
34
|
MSG = 'Use `%<prefer>s` instead of ' \
|
27
35
|
'`%<first_method>s.%<second_method>s`.'
|
28
36
|
REVERSE_MSG = 'Use `reverse.%<prefer>s` instead of ' \
|
29
37
|
'`%<first_method>s.%<second_method>s`.'
|
38
|
+
INDEX_MSG = 'Use `%<prefer>s` instead of ' \
|
39
|
+
'`%<first_method>s[%<index>i]`.'
|
40
|
+
INDEX_REVERSE_MSG = 'Use `reverse.%<prefer>s` instead of ' \
|
41
|
+
'`%<first_method>s[%<index>i]`.'
|
42
|
+
RESTRICT_ON_SEND = %i[first last []].freeze
|
30
43
|
|
31
44
|
def_node_matcher :detect_candidate?, <<~PATTERN
|
32
45
|
{
|
33
|
-
(send $(block (send _
|
34
|
-
(send $(send _
|
46
|
+
(send $(block (send _ %CANDIDATE_METHODS) ...) ${:first :last} $...)
|
47
|
+
(send $(block (send _ %CANDIDATE_METHODS) ...) $:[] (int ${0 -1}))
|
48
|
+
(send $(send _ %CANDIDATE_METHODS ...) ${:first :last} $...)
|
49
|
+
(send $(send _ %CANDIDATE_METHODS ...) $:[] (int ${0 -1}))
|
35
50
|
}
|
36
51
|
PATTERN
|
37
52
|
|
38
53
|
def on_send(node)
|
39
54
|
detect_candidate?(node) do |receiver, second_method, args|
|
55
|
+
if second_method == :[]
|
56
|
+
index = args
|
57
|
+
args = {}
|
58
|
+
end
|
59
|
+
|
40
60
|
return unless args.empty?
|
41
61
|
return unless receiver
|
42
62
|
|
43
63
|
receiver, _args, body = *receiver if receiver.block_type?
|
44
64
|
return if accept_first_call?(receiver, body)
|
45
65
|
|
46
|
-
register_offense(node, receiver, second_method)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def autocorrect(node)
|
51
|
-
receiver, first_method = *node
|
52
|
-
|
53
|
-
replacement = if first_method == :last
|
54
|
-
"reverse.#{preferred_method}"
|
55
|
-
else
|
56
|
-
preferred_method
|
57
|
-
end
|
58
|
-
|
59
|
-
first_range = receiver.source_range.end.join(node.loc.selector)
|
60
|
-
|
61
|
-
receiver, _args, _body = *receiver if receiver.block_type?
|
62
|
-
|
63
|
-
lambda do |corrector|
|
64
|
-
corrector.remove(first_range)
|
65
|
-
corrector.replace(receiver.loc.selector, replacement)
|
66
|
+
register_offense(node, receiver, second_method, index)
|
66
67
|
end
|
67
68
|
end
|
68
69
|
|
@@ -77,21 +78,53 @@ module RuboCop
|
|
77
78
|
lazy?(caller)
|
78
79
|
end
|
79
80
|
|
80
|
-
def register_offense(node, receiver, second_method)
|
81
|
+
def register_offense(node, receiver, second_method, index)
|
81
82
|
_caller, first_method, _args = *receiver
|
82
83
|
range = receiver.loc.selector.join(node.loc.selector)
|
83
84
|
|
84
|
-
message = second_method
|
85
|
+
message = message_for_method(second_method, index)
|
85
86
|
formatted_message = format(message, prefer: preferred_method,
|
86
87
|
first_method: first_method,
|
87
|
-
second_method: second_method
|
88
|
+
second_method: second_method,
|
89
|
+
index: index)
|
90
|
+
|
91
|
+
add_offense(range, message: formatted_message) do |corrector|
|
92
|
+
autocorrect(corrector, node, replacement(second_method, index))
|
93
|
+
end
|
94
|
+
end
|
88
95
|
|
89
|
-
|
96
|
+
def replacement(method, index)
|
97
|
+
if method == :last || method == :[] && index == -1
|
98
|
+
"reverse.#{preferred_method}"
|
99
|
+
else
|
100
|
+
preferred_method
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def autocorrect(corrector, node, replacement)
|
105
|
+
receiver, _first_method = *node
|
106
|
+
|
107
|
+
first_range = receiver.source_range.end.join(node.loc.selector)
|
108
|
+
|
109
|
+
receiver, _args, _body = *receiver if receiver.block_type?
|
110
|
+
|
111
|
+
corrector.remove(first_range)
|
112
|
+
corrector.replace(receiver.loc.selector, replacement)
|
113
|
+
end
|
114
|
+
|
115
|
+
def message_for_method(method, index)
|
116
|
+
case method
|
117
|
+
when :[]
|
118
|
+
index == -1 ? INDEX_REVERSE_MSG : INDEX_MSG
|
119
|
+
when :last
|
120
|
+
REVERSE_MSG
|
121
|
+
else
|
122
|
+
MSG
|
123
|
+
end
|
90
124
|
end
|
91
125
|
|
92
126
|
def preferred_method
|
93
|
-
config.for_cop('Style/CollectionMethods')
|
94
|
-
['PreferredMethods']['detect'] || 'detect'
|
127
|
+
config.for_cop('Style/CollectionMethods')['PreferredMethods']['detect'] || 'detect'
|
95
128
|
end
|
96
129
|
|
97
130
|
def lazy?(node)
|
@@ -17,39 +17,34 @@ module RuboCop
|
|
17
17
|
# str.start_with?("a", Some::CONST)
|
18
18
|
# str.start_with?("a", "b", "c")
|
19
19
|
# str.end_with?(var1, var2)
|
20
|
-
class DoubleStartEndWith <
|
20
|
+
class DoubleStartEndWith < Base
|
21
|
+
extend AutoCorrector
|
22
|
+
|
21
23
|
MSG = 'Use `%<receiver>s.%<method>s(%<combined_args>s)` ' \
|
22
24
|
'instead of `%<original_code>s`.'
|
23
25
|
|
24
26
|
def on_or(node)
|
25
|
-
receiver,
|
26
|
-
method,
|
27
|
-
first_call_args,
|
28
|
-
second_call_args = process_source(node)
|
27
|
+
receiver, method, first_call_args, second_call_args = process_source(node)
|
29
28
|
|
30
29
|
return unless receiver && second_call_args.all?(&:pure?)
|
31
30
|
|
32
31
|
combined_args = combine_args(first_call_args, second_call_args)
|
33
32
|
|
34
|
-
|
33
|
+
add_offense(node, message: message(node, receiver, method, combined_args)) do |corrector|
|
34
|
+
autocorrect(corrector, first_call_args, second_call_args, combined_args)
|
35
|
+
end
|
35
36
|
end
|
36
37
|
|
37
|
-
|
38
|
-
_receiver, _method,
|
39
|
-
first_call_args, second_call_args = process_source(node)
|
38
|
+
private
|
40
39
|
|
41
|
-
|
40
|
+
def autocorrect(corrector, first_call_args, second_call_args, combined_args)
|
42
41
|
first_argument = first_call_args.first.loc.expression
|
43
42
|
last_argument = second_call_args.last.loc.expression
|
44
43
|
range = first_argument.join(last_argument)
|
45
44
|
|
46
|
-
|
47
|
-
corrector.replace(range, combined_args)
|
48
|
-
end
|
45
|
+
corrector.replace(range, combined_args)
|
49
46
|
end
|
50
47
|
|
51
|
-
private
|
52
|
-
|
53
48
|
def process_source(node)
|
54
49
|
if check_for_active_support_aliases?
|
55
50
|
check_with_active_support_aliases(node)
|
@@ -58,17 +53,14 @@ module RuboCop
|
|
58
53
|
end
|
59
54
|
end
|
60
55
|
|
61
|
-
def
|
62
|
-
(
|
56
|
+
def message(node, receiver, method, combined_args)
|
57
|
+
format(
|
58
|
+
MSG, receiver: receiver.source, method: method, combined_args: combined_args, original_code: node.source
|
59
|
+
)
|
63
60
|
end
|
64
61
|
|
65
|
-
def
|
66
|
-
|
67
|
-
method: method,
|
68
|
-
combined_args: combined_args,
|
69
|
-
original_code: node.source)
|
70
|
-
|
71
|
-
add_offense(node, message: msg)
|
62
|
+
def combine_args(first_call_args, second_call_args)
|
63
|
+
(first_call_args + second_call_args).map(&:source).join(', ')
|
72
64
|
end
|
73
65
|
|
74
66
|
def check_for_active_support_aliases?
|
@@ -41,11 +41,13 @@ module RuboCop
|
|
41
41
|
# 'abc'.match(/bc$/)
|
42
42
|
# /bc$/.match('abc')
|
43
43
|
#
|
44
|
-
class EndWith <
|
44
|
+
class EndWith < Base
|
45
45
|
include RegexpMetacharacter
|
46
|
+
extend AutoCorrector
|
46
47
|
|
47
48
|
MSG = 'Use `String#end_with?` instead of a regex match anchored to ' \
|
48
49
|
'the end of the string.'
|
50
|
+
RESTRICT_ON_SEND = %i[match =~ match?].freeze
|
49
51
|
|
50
52
|
def_node_matcher :redundant_regex?, <<~PATTERN
|
51
53
|
{(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_end?) (regopt)))
|
@@ -54,25 +56,19 @@ module RuboCop
|
|
54
56
|
PATTERN
|
55
57
|
|
56
58
|
def on_send(node)
|
57
|
-
return unless redundant_regex?(node)
|
59
|
+
return unless (receiver, regex_str = redundant_regex?(node))
|
58
60
|
|
59
|
-
add_offense(node)
|
60
|
-
end
|
61
|
-
alias on_match_with_lvasgn on_send
|
62
|
-
|
63
|
-
def autocorrect(node)
|
64
|
-
redundant_regex?(node) do |receiver, regex_str|
|
61
|
+
add_offense(node) do |corrector|
|
65
62
|
receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
|
66
63
|
regex_str = drop_end_metacharacter(regex_str)
|
67
64
|
regex_str = interpret_string_escapes(regex_str)
|
68
65
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
corrector.replace(node.source_range, new_source)
|
73
|
-
end
|
66
|
+
new_source = "#{receiver.source}.end_with?(#{to_string_literal(regex_str)})"
|
67
|
+
|
68
|
+
corrector.replace(node.source_range, new_source)
|
74
69
|
end
|
75
70
|
end
|
71
|
+
alias on_match_with_lvasgn on_send
|
76
72
|
end
|
77
73
|
end
|
78
74
|
end
|