rubocop-performance 1.6.1 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -0
  3. data/config/default.yml +95 -8
  4. data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +4 -4
  5. data/lib/rubocop/cop/mixin/sort_block.rb +28 -0
  6. data/lib/rubocop/cop/performance/ancestors_include.rb +49 -0
  7. data/lib/rubocop/cop/performance/array_semi_infinite_range_slice.rb +74 -0
  8. data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +46 -0
  9. data/lib/rubocop/cop/performance/bind_call.rb +9 -18
  10. data/lib/rubocop/cop/performance/block_given_with_explicit_block.rb +52 -0
  11. data/lib/rubocop/cop/performance/caller.rb +14 -15
  12. data/lib/rubocop/cop/performance/case_when_splat.rb +18 -11
  13. data/lib/rubocop/cop/performance/casecmp.rb +13 -20
  14. data/lib/rubocop/cop/performance/chain_array_allocation.rb +4 -10
  15. data/lib/rubocop/cop/performance/collection_literal_in_loop.rb +140 -0
  16. data/lib/rubocop/cop/performance/compare_with_block.rb +10 -21
  17. data/lib/rubocop/cop/performance/constant_regexp.rb +68 -0
  18. data/lib/rubocop/cop/performance/count.rb +14 -16
  19. data/lib/rubocop/cop/performance/delete_prefix.rb +14 -22
  20. data/lib/rubocop/cop/performance/delete_suffix.rb +14 -22
  21. data/lib/rubocop/cop/performance/detect.rb +65 -32
  22. data/lib/rubocop/cop/performance/double_start_end_with.rb +16 -24
  23. data/lib/rubocop/cop/performance/end_with.rb +9 -13
  24. data/lib/rubocop/cop/performance/fixed_size.rb +2 -1
  25. data/lib/rubocop/cop/performance/flat_map.rb +21 -22
  26. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +15 -14
  27. data/lib/rubocop/cop/performance/io_readlines.rb +112 -0
  28. data/lib/rubocop/cop/performance/method_object_as_block.rb +32 -0
  29. data/lib/rubocop/cop/performance/open_struct.rb +3 -2
  30. data/lib/rubocop/cop/performance/range_include.rb +15 -11
  31. data/lib/rubocop/cop/performance/redundant_block_call.rb +15 -10
  32. data/lib/rubocop/cop/performance/redundant_match.rb +12 -6
  33. data/lib/rubocop/cop/performance/redundant_merge.rb +19 -17
  34. data/lib/rubocop/cop/performance/redundant_sort_block.rb +43 -0
  35. data/lib/rubocop/cop/performance/redundant_string_chars.rb +129 -0
  36. data/lib/rubocop/cop/performance/regexp_match.rb +20 -20
  37. data/lib/rubocop/cop/performance/reverse_each.rb +10 -5
  38. data/lib/rubocop/cop/performance/reverse_first.rb +73 -0
  39. data/lib/rubocop/cop/performance/size.rb +42 -43
  40. data/lib/rubocop/cop/performance/sort_reverse.rb +45 -0
  41. data/lib/rubocop/cop/performance/squeeze.rb +67 -0
  42. data/lib/rubocop/cop/performance/start_with.rb +9 -13
  43. data/lib/rubocop/cop/performance/string_include.rb +56 -0
  44. data/lib/rubocop/cop/performance/string_replacement.rb +24 -27
  45. data/lib/rubocop/cop/performance/sum.rb +236 -0
  46. data/lib/rubocop/cop/performance/times_map.rb +12 -18
  47. data/lib/rubocop/cop/performance/unfreeze_string.rb +20 -2
  48. data/lib/rubocop/cop/performance/uri_default_parser.rb +7 -12
  49. data/lib/rubocop/cop/performance_cops.rb +16 -0
  50. data/lib/rubocop/performance/version.rb +6 -1
  51. metadata +35 -13
@@ -9,67 +9,66 @@ module RuboCop
9
9
  # @example
10
10
  # # bad
11
11
  # [1, 2, 3].count
12
+ # (1..3).to_a.count
13
+ # Array[*1..3].count
14
+ # Array(1..3).count
12
15
  #
13
16
  # # bad
14
17
  # {a: 1, b: 2, c: 3}.count
18
+ # [[:foo, :bar], [1, 2]].to_h.count
19
+ # Hash[*('a'..'z')].count
20
+ # Hash(key: :value).count
15
21
  #
16
22
  # # good
17
23
  # [1, 2, 3].size
24
+ # (1..3).to_a.size
25
+ # Array[*1..3].size
26
+ # Array(1..3).size
18
27
  #
19
28
  # # good
20
29
  # {a: 1, b: 2, c: 3}.size
30
+ # [[:foo, :bar], [1, 2]].to_h.size
31
+ # Hash[*('a'..'z')].size
32
+ # Hash(key: :value).size
21
33
  #
22
34
  # # good
23
35
  # [1, 2, 3].count { |e| e > 2 }
24
36
  # TODO: Add advanced detection of variables that could
25
37
  # have been assigned to an array or a hash.
26
- class Size < Cop
38
+ class Size < Base
39
+ extend AutoCorrector
40
+
27
41
  MSG = 'Use `size` instead of `count`.'
42
+ RESTRICT_ON_SEND = %i[count].freeze
43
+
44
+ def_node_matcher :array?, <<~PATTERN
45
+ {
46
+ [!nil? array_type?]
47
+ (send _ :to_a)
48
+ (send (const nil? :Array) :[] _)
49
+ (send nil? :Array _)
50
+ }
51
+ PATTERN
52
+
53
+ def_node_matcher :hash?, <<~PATTERN
54
+ {
55
+ [!nil? hash_type?]
56
+ (send _ :to_h)
57
+ (send (const nil? :Hash) :[] _)
58
+ (send nil? :Hash _)
59
+ }
60
+ PATTERN
61
+
62
+ def_node_matcher :count?, <<~PATTERN
63
+ (send {#array? #hash?} :count)
64
+ PATTERN
28
65
 
29
66
  def on_send(node)
30
- return unless eligible_node?(node)
31
-
32
- add_offense(node, location: :selector)
33
- end
34
-
35
- def autocorrect(node)
36
- ->(corrector) { corrector.replace(node.loc.selector, 'size') }
37
- end
38
-
39
- private
40
-
41
- def eligible_node?(node)
42
- return false unless node.method?(:count) && !node.arguments?
43
-
44
- eligible_receiver?(node.receiver) && !allowed_parent?(node.parent)
45
- end
46
-
47
- def eligible_receiver?(node)
48
- return false unless node
49
-
50
- array?(node) || hash?(node)
51
- end
52
-
53
- def allowed_parent?(node)
54
- node&.block_type?
55
- end
56
-
57
- def array?(node)
58
- return true if node.array_type?
59
- return false unless node.send_type?
60
-
61
- _, constant = *node.receiver
62
-
63
- constant == :Array || node.method?(:to_a)
64
- end
65
-
66
- def hash?(node)
67
- return true if node.hash_type?
68
- return false unless node.send_type?
69
-
70
- _, constant = *node.receiver
67
+ return if node.parent&.block_type? || !count?(node)
71
68
 
72
- constant == :Hash || node.method?(:to_h)
69
+ add_offense(node.loc.selector) do |corrector|
70
+ corrector.replace(node.loc.selector, 'size')
71
+ end
73
72
  end
74
73
  end
75
74
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where `sort { |a, b| b <=> a }`
7
+ # can be replaced by a faster `sort.reverse`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # array.sort { |a, b| b <=> a }
12
+ #
13
+ # # good
14
+ # array.sort.reverse
15
+ #
16
+ class SortReverse < Base
17
+ include SortBlock
18
+ extend AutoCorrector
19
+
20
+ MSG = 'Use `sort.reverse` instead of `%<bad_method>s`.'
21
+
22
+ def on_block(node)
23
+ sort_with_block?(node) do |send, var_a, var_b, body|
24
+ replaceable_body?(body, var_b, var_a) do
25
+ range = sort_range(send, node)
26
+
27
+ add_offense(range, message: message(var_a, var_b)) do |corrector|
28
+ replacement = 'sort.reverse'
29
+
30
+ corrector.replace(range, replacement)
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def message(var_a, var_b)
39
+ bad_method = "sort { |#{var_a}, #{var_b}| #{var_b} <=> #{var_a} }"
40
+ format(MSG, bad_method: bad_method)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where `gsub(/a+/, 'a')` and `gsub!(/a+/, 'a')`
7
+ # can be replaced by `squeeze('a')` and `squeeze!('a')`.
8
+ #
9
+ # The `squeeze('a')` method is faster than `gsub(/a+/, 'a')`.
10
+ #
11
+ # @example
12
+ #
13
+ # # bad
14
+ # str.gsub(/a+/, 'a')
15
+ # str.gsub!(/a+/, 'a')
16
+ #
17
+ # # good
18
+ # str.squeeze('a')
19
+ # str.squeeze!('a')
20
+ #
21
+ class Squeeze < Base
22
+ extend AutoCorrector
23
+
24
+ MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
25
+ RESTRICT_ON_SEND = %i[gsub gsub!].freeze
26
+
27
+ PREFERRED_METHODS = {
28
+ gsub: :squeeze,
29
+ gsub!: :squeeze!
30
+ }.freeze
31
+
32
+ def_node_matcher :squeeze_candidate?, <<~PATTERN
33
+ (send
34
+ $!nil? ${:gsub :gsub!}
35
+ (regexp
36
+ (str $#repeating_literal?)
37
+ (regopt))
38
+ (str $_))
39
+ PATTERN
40
+
41
+ def on_send(node)
42
+ squeeze_candidate?(node) do |receiver, bad_method, regexp_str, replace_str|
43
+ regexp_str = regexp_str[0..-2] # delete '+' from the end
44
+ regexp_str = interpret_string_escapes(regexp_str)
45
+ return unless replace_str == regexp_str
46
+
47
+ good_method = PREFERRED_METHODS[bad_method]
48
+ message = format(MSG, current: bad_method, prefer: good_method)
49
+
50
+ add_offense(node.loc.selector, message: message) do |corrector|
51
+ string_literal = to_string_literal(replace_str)
52
+ new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
53
+
54
+ corrector.replace(node.source_range, new_code)
55
+ end
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def repeating_literal?(regex_str)
62
+ regex_str.match?(/\A(?:#{Util::LITERAL_REGEX})\+\z/o)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -41,11 +41,13 @@ module RuboCop
41
41
  # 'abc'.match(/^ab/)
42
42
  # /^ab/.match('abc')
43
43
  #
44
- class StartWith < Cop
44
+ class StartWith < Base
45
45
  include RegexpMetacharacter
46
+ extend AutoCorrector
46
47
 
47
48
  MSG = 'Use `String#start_with?` instead of a regex match anchored to ' \
48
49
  'the beginning 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_start?) (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_start_metacharacter(regex_str)
67
64
  regex_str = interpret_string_escapes(regex_str)
68
65
 
69
- lambda do |corrector|
70
- new_source = receiver.source + '.start_with?(' +
71
- to_string_literal(regex_str) + ')'
72
- corrector.replace(node.source_range, new_source)
73
- end
66
+ new_source = "#{receiver.source}.start_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
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies unnecessary use of a regex where
7
+ # `String#include?` would suffice.
8
+ #
9
+ # This cop's offenses are not safe to auto-correct if a receiver is nil.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # 'abc'.match?(/ab/)
14
+ # /ab/.match?('abc')
15
+ # 'abc' =~ /ab/
16
+ # /ab/ =~ 'abc'
17
+ # 'abc'.match(/ab/)
18
+ # /ab/.match('abc')
19
+ #
20
+ # # good
21
+ # 'abc'.include?('ab')
22
+ class StringInclude < Base
23
+ extend AutoCorrector
24
+
25
+ MSG = 'Use `String#include?` instead of a regex match with literal-only pattern.'
26
+ RESTRICT_ON_SEND = %i[match =~ match?].freeze
27
+
28
+ def_node_matcher :redundant_regex?, <<~PATTERN
29
+ {(send $!nil? {:match :=~ :match?} (regexp (str $#literal?) (regopt)))
30
+ (send (regexp (str $#literal?) (regopt)) {:match :match?} $str)
31
+ (match-with-lvasgn (regexp (str $#literal?) (regopt)) $_)}
32
+ PATTERN
33
+
34
+ def on_send(node)
35
+ return unless (receiver, regex_str = redundant_regex?(node))
36
+
37
+ add_offense(node) do |corrector|
38
+ receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
39
+ regex_str = interpret_string_escapes(regex_str)
40
+
41
+ new_source = "#{receiver.source}.include?(#{to_string_literal(regex_str)})"
42
+
43
+ corrector.replace(node.source_range, new_source)
44
+ end
45
+ end
46
+ alias on_match_with_lvasgn on_send
47
+
48
+ private
49
+
50
+ def literal?(regex_str)
51
+ regex_str.match?(/\A#{Util::LITERAL_REGEX}+\z/o)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -18,10 +18,12 @@ module RuboCop
18
18
  # 'abc'.gsub(/a+/, 'd')
19
19
  # 'abc'.tr('b', 'd')
20
20
  # 'a b c'.delete(' ')
21
- class StringReplacement < Cop
21
+ class StringReplacement < Base
22
22
  include RangeHelp
23
+ extend AutoCorrector
23
24
 
24
25
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
26
+ RESTRICT_ON_SEND = %i[gsub gsub!].freeze
25
27
  DETERMINISTIC_REGEX = /\A(?:#{LITERAL_REGEX})+\Z/.freeze
26
28
  DELETE = 'delete'
27
29
  TR = 'tr'
@@ -42,33 +44,37 @@ module RuboCop
42
44
  end
43
45
  end
44
46
 
45
- def autocorrect(node)
47
+ private
48
+
49
+ def offense(node, first_param, second_param)
50
+ first_source, = first_source(first_param)
51
+ first_source = interpret_string_escapes(first_source) unless first_param.str_type?
52
+ second_source, = *second_param
53
+ message = message(node, first_source, second_source)
54
+
55
+ add_offense(range(node), message: message) do |corrector|
56
+ autocorrect(corrector, node)
57
+ end
58
+ end
59
+
60
+ def autocorrect(corrector, node)
46
61
  _string, _method, first_param, second_param = *node
47
62
  first_source, = first_source(first_param)
48
63
  second_source, = *second_param
49
64
 
50
65
  first_source = interpret_string_escapes(first_source) unless first_param.str_type?
51
66
 
52
- replacement_method =
53
- replacement_method(node, first_source, second_source)
54
-
55
- replace_method(node, first_source, second_source, first_param,
56
- replacement_method)
67
+ replace_method(corrector, node, first_source, second_source, first_param)
57
68
  end
58
69
 
59
- def replace_method(node, first, second, first_param, replacement)
60
- lambda do |corrector|
61
- corrector.replace(node.loc.selector, replacement)
62
- unless first_param.str_type?
63
- corrector.replace(first_param.source_range,
64
- to_string_literal(first))
65
- end
70
+ def replace_method(corrector, node, first_source, second_source, first_param)
71
+ replacement_method = replacement_method(node, first_source, second_source)
66
72
 
67
- remove_second_param(corrector, node, first_param) if second.empty? && first.length == 1
68
- end
69
- end
73
+ corrector.replace(node.loc.selector, replacement_method)
74
+ corrector.replace(first_param.source_range, to_string_literal(first_source)) unless first_param.str_type?
70
75
 
71
- private
76
+ remove_second_param(corrector, node, first_param) if second_source.empty? && first_source.length == 1
77
+ end
72
78
 
73
79
  def accept_second_param?(second_param)
74
80
  second_source, = *second_param
@@ -92,15 +98,6 @@ module RuboCop
92
98
  first_source.length != 1
93
99
  end
94
100
 
95
- def offense(node, first_param, second_param)
96
- first_source, = first_source(first_param)
97
- first_source = interpret_string_escapes(first_source) unless first_param.str_type?
98
- second_source, = *second_param
99
- message = message(node, first_source, second_source)
100
-
101
- add_offense(node, location: range(node), message: message)
102
- end
103
-
104
101
  def first_source(first_param)
105
102
  case first_param.type
106
103
  when :regexp
@@ -0,0 +1,236 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where custom code finding the sum of elements
7
+ # in some Enumerable object can be replaced by `Enumerable#sum` method.
8
+ #
9
+ # This cop can change auto-correction scope depending on the value of
10
+ # `SafeAutoCorrect`.
11
+ # Its auto-correction is marked as safe by default (`SafeAutoCorrect: true`)
12
+ # to prevent `TypeError` in auto-correced code when initial value is not
13
+ # specified as shown below:
14
+ #
15
+ # [source,ruby]
16
+ # ----
17
+ # ['a', 'b'].sum # => (String can't be coerced into Integer)
18
+ # ----
19
+ #
20
+ # Therefore if initial value is not specified, unsafe auto-corrected will not occur.
21
+ #
22
+ # If you always want to enable auto-correction, you can set `SafeAutoCorrect: false`.
23
+ #
24
+ # [source,yaml]
25
+ # ----
26
+ # Performance/Sum:
27
+ # SafeAutoCorrect: false
28
+ # ----
29
+ #
30
+ # Please note that the auto-correction command line option will be changed from
31
+ # `rubocop -a` to `rubocop -A`, which includes unsafe auto-correction.
32
+ #
33
+ # @example
34
+ # # bad
35
+ # [1, 2, 3].inject(:+) # These bad cases with no initial value are unsafe and
36
+ # [1, 2, 3].inject(&:+) # will not be auto-correced by default. If you want to
37
+ # [1, 2, 3].reduce { |acc, elem| acc + elem } # auto-corrected, you can set `SafeAutoCorrect: false`.
38
+ # [1, 2, 3].reduce(10, :+)
39
+ # [1, 2, 3].map { |elem| elem ** 2 }.sum
40
+ # [1, 2, 3].collect(&:count).sum(10)
41
+ #
42
+ # # good
43
+ # [1, 2, 3].sum
44
+ # [1, 2, 3].sum(10)
45
+ # [1, 2, 3].sum { |elem| elem ** 2 }
46
+ # [1, 2, 3].sum(10, &:count)
47
+ #
48
+ class Sum < Base
49
+ include RangeHelp
50
+ extend AutoCorrector
51
+
52
+ MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
53
+ MSG_IF_NO_INIT_VALUE =
54
+ 'Use `%<good_method>s` instead of `%<bad_method>s`, unless calling `%<bad_method>s` on an empty array.'
55
+ RESTRICT_ON_SEND = %i[inject reduce sum].freeze
56
+
57
+ def_node_matcher :sum_candidate?, <<~PATTERN
58
+ (send _ ${:inject :reduce} $_init ? ${(sym :+) (block_pass (sym :+))})
59
+ PATTERN
60
+
61
+ def_node_matcher :sum_map_candidate?, <<~PATTERN
62
+ (send
63
+ {
64
+ (block $(send _ {:map :collect}) ...)
65
+ $(send _ {:map :collect} (block_pass _))
66
+ }
67
+ :sum $_init ?)
68
+ PATTERN
69
+
70
+ def_node_matcher :sum_with_block_candidate?, <<~PATTERN
71
+ (block
72
+ $(send _ {:inject :reduce} $_init ?)
73
+ (args (arg $_acc) (arg $_elem))
74
+ $send)
75
+ PATTERN
76
+
77
+ def_node_matcher :acc_plus_elem?, <<~PATTERN
78
+ (send (lvar %1) :+ (lvar %2))
79
+ PATTERN
80
+ alias elem_plus_acc? acc_plus_elem?
81
+
82
+ def on_send(node)
83
+ return if empty_array_literal?(node)
84
+
85
+ handle_sum_candidate(node)
86
+ handle_sum_map_candidate(node)
87
+ end
88
+
89
+ def on_block(node)
90
+ sum_with_block_candidate?(node) do |send, init, var_acc, var_elem, body|
91
+ if acc_plus_elem?(body, var_acc, var_elem) || elem_plus_acc?(body, var_elem, var_acc)
92
+ range = sum_block_range(send, node)
93
+ message = build_block_message(send, init, var_acc, var_elem, body)
94
+
95
+ add_offense(range, message: message) do |corrector|
96
+ autocorrect(corrector, init, range)
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ private
103
+
104
+ def handle_sum_candidate(node)
105
+ sum_candidate?(node) do |method, init, operation|
106
+ range = sum_method_range(node)
107
+ message = build_method_message(node, method, init, operation)
108
+
109
+ add_offense(range, message: message) do |corrector|
110
+ autocorrect(corrector, init, range)
111
+ end
112
+ end
113
+ end
114
+
115
+ def handle_sum_map_candidate(node)
116
+ sum_map_candidate?(node) do |map, init|
117
+ next if node.block_literal? || node.block_argument?
118
+
119
+ message = build_sum_map_message(map.method_name, init)
120
+
121
+ add_offense(sum_map_range(map, node), message: message) do |corrector|
122
+ autocorrect_sum_map(corrector, node, map, init)
123
+ end
124
+ end
125
+ end
126
+
127
+ def empty_array_literal?(node)
128
+ receiver = node.children.first
129
+ array_literal?(node) && receiver && receiver.children.empty?
130
+ end
131
+
132
+ def array_literal?(node)
133
+ receiver = node.children.first
134
+ receiver&.literal? && receiver&.array_type?
135
+ end
136
+
137
+ def autocorrect(corrector, init, range)
138
+ return if init.empty? && safe_autocorrect?
139
+
140
+ replacement = build_good_method(init)
141
+
142
+ corrector.replace(range, replacement)
143
+ end
144
+
145
+ def autocorrect_sum_map(corrector, sum, map, init)
146
+ sum_range = method_call_with_args_range(sum)
147
+ map_range = method_call_with_args_range(map)
148
+
149
+ block_pass = map.last_argument if map.last_argument&.block_pass_type?
150
+ replacement = build_good_method(init, block_pass)
151
+
152
+ corrector.remove(sum_range)
153
+ corrector.replace(map_range, ".#{replacement}")
154
+ end
155
+
156
+ def sum_method_range(node)
157
+ range_between(node.loc.selector.begin_pos, node.loc.end.end_pos)
158
+ end
159
+
160
+ def sum_map_range(map, sum)
161
+ range_between(map.loc.selector.begin_pos, sum.source_range.end.end_pos)
162
+ end
163
+
164
+ def sum_block_range(send, node)
165
+ range_between(send.loc.selector.begin_pos, node.loc.end.end_pos)
166
+ end
167
+
168
+ def build_method_message(node, method, init, operation)
169
+ good_method = build_good_method(init)
170
+ bad_method = build_method_bad_method(init, method, operation)
171
+ msg = if init.empty? && !array_literal?(node)
172
+ MSG_IF_NO_INIT_VALUE
173
+ else
174
+ MSG
175
+ end
176
+ format(msg, good_method: good_method, bad_method: bad_method)
177
+ end
178
+
179
+ def build_sum_map_message(method, init)
180
+ sum_method = build_good_method(init)
181
+ good_method = "#{sum_method} { ... }"
182
+ bad_method = "#{method} { ... }.#{sum_method}"
183
+ format(MSG, good_method: good_method, bad_method: bad_method)
184
+ end
185
+
186
+ def build_block_message(send, init, var_acc, var_elem, body)
187
+ good_method = build_good_method(init)
188
+ bad_method = build_block_bad_method(send.method_name, init, var_acc, var_elem, body)
189
+ format(MSG, good_method: good_method, bad_method: bad_method)
190
+ end
191
+
192
+ def build_good_method(init, block_pass = nil)
193
+ good_method = 'sum'
194
+
195
+ args = []
196
+ unless init.empty?
197
+ init = init.first
198
+ args << init.source unless init.int_type? && init.value.zero?
199
+ end
200
+ args << block_pass.source if block_pass
201
+ good_method += "(#{args.join(', ')})" unless args.empty?
202
+ good_method
203
+ end
204
+
205
+ def build_method_bad_method(init, method, operation)
206
+ bad_method = "#{method}("
207
+ unless init.empty?
208
+ init = init.first
209
+ bad_method += "#{init.source}, "
210
+ end
211
+ bad_method += if operation.block_pass_type?
212
+ '&:+)'
213
+ else
214
+ ':+)'
215
+ end
216
+ bad_method
217
+ end
218
+
219
+ def build_block_bad_method(method, init, var_acc, var_elem, body)
220
+ bad_method = method.to_s
221
+
222
+ unless init.empty?
223
+ init = init.first
224
+ bad_method += "(#{init.source})"
225
+ end
226
+ bad_method += " { |#{var_acc}, #{var_elem}| #{body.source} }"
227
+ bad_method
228
+ end
229
+
230
+ def method_call_with_args_range(node)
231
+ node.receiver.source_range.end.join(node.source_range.end)
232
+ end
233
+ end
234
+ end
235
+ end
236
+ end