rubocop 0.67.2 → 0.68.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +86 -233
  4. data/exe/rubocop +0 -12
  5. data/lib/rubocop.rb +13 -30
  6. data/lib/rubocop/ast/builder.rb +4 -0
  7. data/lib/rubocop/ast/node/alias_node.rb +24 -0
  8. data/lib/rubocop/ast/node/class_node.rb +31 -0
  9. data/lib/rubocop/ast/node/module_node.rb +24 -0
  10. data/lib/rubocop/ast/node/range_node.rb +7 -0
  11. data/lib/rubocop/ast/node/resbody_node.rb +12 -0
  12. data/lib/rubocop/ast/node/self_class_node.rb +24 -0
  13. data/lib/rubocop/cli.rb +40 -4
  14. data/lib/rubocop/config.rb +9 -7
  15. data/lib/rubocop/config_loader.rb +48 -7
  16. data/lib/rubocop/config_loader_resolver.rb +5 -4
  17. data/lib/rubocop/cop/commissioner.rb +24 -0
  18. data/lib/rubocop/cop/correctors/unused_arg_corrector.rb +18 -6
  19. data/lib/rubocop/cop/internal_affairs/node_destructuring.rb +12 -14
  20. data/lib/rubocop/cop/layout/access_modifier_indentation.rb +9 -20
  21. data/lib/rubocop/cop/layout/align_arguments.rb +93 -0
  22. data/lib/rubocop/cop/layout/align_parameters.rb +57 -33
  23. data/lib/rubocop/cop/layout/class_structure.rb +5 -5
  24. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +6 -8
  25. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +3 -6
  26. data/lib/rubocop/cop/layout/empty_lines_around_module_body.rb +1 -2
  27. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +1 -0
  28. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +292 -0
  29. data/lib/rubocop/cop/layout/{first_parameter_indentation.rb → indent_first_argument.rb} +11 -12
  30. data/lib/rubocop/cop/layout/{indent_array.rb → indent_first_array_element.rb} +2 -2
  31. data/lib/rubocop/cop/layout/{indent_hash.rb → indent_first_hash_element.rb} +2 -2
  32. data/lib/rubocop/cop/layout/indent_first_parameter.rb +96 -0
  33. data/lib/rubocop/cop/layout/indentation_width.rb +4 -16
  34. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +2 -4
  35. data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +1 -16
  36. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +1 -2
  37. data/lib/rubocop/cop/lint/duplicate_methods.rb +6 -8
  38. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +4 -8
  39. data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +157 -0
  40. data/lib/rubocop/cop/lint/inherit_exception.rb +3 -4
  41. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +18 -1
  42. data/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +3 -5
  43. data/lib/rubocop/cop/lint/underscore_prefixed_variable_name.rb +25 -5
  44. data/lib/rubocop/cop/lint/useless_assignment.rb +2 -6
  45. data/lib/rubocop/cop/lint/useless_setter_call.rb +1 -2
  46. data/lib/rubocop/cop/message_annotator.rb +1 -0
  47. data/lib/rubocop/cop/metrics/line_length.rb +139 -28
  48. data/lib/rubocop/cop/metrics/perceived_complexity.rb +3 -4
  49. data/lib/rubocop/cop/mixin/check_line_breakable.rb +190 -0
  50. data/lib/rubocop/cop/mixin/{array_hash_indentation.rb → multiline_element_indentation.rb} +3 -2
  51. data/lib/rubocop/cop/mixin/too_many_lines.rb +3 -7
  52. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +33 -4
  53. data/lib/rubocop/cop/rails/active_record_override.rb +23 -8
  54. data/lib/rubocop/cop/rails/delegate.rb +5 -8
  55. data/lib/rubocop/cop/rails/environment_comparison.rb +5 -3
  56. data/lib/rubocop/cop/rails/find_each.rb +1 -1
  57. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +3 -3
  58. data/lib/rubocop/cop/rails/reflection_class_name.rb +1 -1
  59. data/lib/rubocop/cop/rails/skips_model_validations.rb +6 -7
  60. data/lib/rubocop/cop/rails/time_zone.rb +3 -10
  61. data/lib/rubocop/cop/rails/validation.rb +3 -0
  62. data/lib/rubocop/cop/registry.rb +3 -3
  63. data/lib/rubocop/cop/style/alias.rb +13 -7
  64. data/lib/rubocop/cop/style/block_delimiters.rb +20 -0
  65. data/lib/rubocop/cop/style/class_and_module_children.rb +19 -21
  66. data/lib/rubocop/cop/style/class_methods.rb +16 -24
  67. data/lib/rubocop/cop/style/conditional_assignment.rb +20 -49
  68. data/lib/rubocop/cop/style/documentation.rb +3 -7
  69. data/lib/rubocop/cop/style/format_string.rb +18 -21
  70. data/lib/rubocop/cop/style/hash_syntax.rb +1 -1
  71. data/lib/rubocop/cop/style/inverse_methods.rb +4 -0
  72. data/lib/rubocop/cop/style/lambda.rb +12 -8
  73. data/lib/rubocop/cop/style/mixin_grouping.rb +8 -10
  74. data/lib/rubocop/cop/style/module_function.rb +2 -3
  75. data/lib/rubocop/cop/style/next.rb +10 -14
  76. data/lib/rubocop/cop/style/one_line_conditional.rb +5 -3
  77. data/lib/rubocop/cop/style/optional_arguments.rb +1 -4
  78. data/lib/rubocop/cop/style/random_with_offset.rb +44 -47
  79. data/lib/rubocop/cop/style/redundant_return.rb +6 -14
  80. data/lib/rubocop/cop/style/redundant_sort_by.rb +1 -1
  81. data/lib/rubocop/cop/style/safe_navigation.rb +3 -0
  82. data/lib/rubocop/cop/style/struct_inheritance.rb +2 -3
  83. data/lib/rubocop/cop/style/symbol_proc.rb +20 -40
  84. data/lib/rubocop/cop/style/unless_else.rb +1 -2
  85. data/lib/rubocop/cop/style/yoda_condition.rb +8 -7
  86. data/lib/rubocop/cop/util.rb +2 -4
  87. data/lib/rubocop/file_finder.rb +5 -10
  88. data/lib/rubocop/formatter/disabled_config_formatter.rb +5 -0
  89. data/lib/rubocop/node_pattern.rb +304 -170
  90. data/lib/rubocop/options.rb +4 -1
  91. data/lib/rubocop/rspec/shared_contexts.rb +3 -0
  92. data/lib/rubocop/version.rb +1 -1
  93. data/lib/rubocop/yaml_duplication_checker.rb +1 -1
  94. metadata +26 -50
  95. data/lib/rubocop/cop/performance/caller.rb +0 -69
  96. data/lib/rubocop/cop/performance/case_when_splat.rb +0 -177
  97. data/lib/rubocop/cop/performance/casecmp.rb +0 -108
  98. data/lib/rubocop/cop/performance/chain_array_allocation.rb +0 -78
  99. data/lib/rubocop/cop/performance/compare_with_block.rb +0 -122
  100. data/lib/rubocop/cop/performance/count.rb +0 -102
  101. data/lib/rubocop/cop/performance/detect.rb +0 -110
  102. data/lib/rubocop/cop/performance/double_start_end_with.rb +0 -94
  103. data/lib/rubocop/cop/performance/end_with.rb +0 -56
  104. data/lib/rubocop/cop/performance/fixed_size.rb +0 -97
  105. data/lib/rubocop/cop/performance/flat_map.rb +0 -78
  106. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +0 -99
  107. data/lib/rubocop/cop/performance/open_struct.rb +0 -46
  108. data/lib/rubocop/cop/performance/range_include.rb +0 -50
  109. data/lib/rubocop/cop/performance/redundant_block_call.rb +0 -93
  110. data/lib/rubocop/cop/performance/redundant_match.rb +0 -56
  111. data/lib/rubocop/cop/performance/redundant_merge.rb +0 -183
  112. data/lib/rubocop/cop/performance/regexp_match.rb +0 -265
  113. data/lib/rubocop/cop/performance/reverse_each.rb +0 -42
  114. data/lib/rubocop/cop/performance/size.rb +0 -77
  115. data/lib/rubocop/cop/performance/start_with.rb +0 -59
  116. data/lib/rubocop/cop/performance/string_replacement.rb +0 -173
  117. data/lib/rubocop/cop/performance/times_map.rb +0 -71
  118. data/lib/rubocop/cop/performance/unfreeze_string.rb +0 -50
  119. data/lib/rubocop/cop/performance/uri_default_parser.rb +0 -47
@@ -1,108 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module Cop
5
- module Performance
6
- # This cop identifies places where a case-insensitive string comparison
7
- # can better be implemented using `casecmp`.
8
- #
9
- # @example
10
- # # bad
11
- # str.downcase == 'abc'
12
- # str.upcase.eql? 'ABC'
13
- # 'abc' == str.downcase
14
- # 'ABC'.eql? str.upcase
15
- # str.downcase == str.downcase
16
- #
17
- # # good
18
- # str.casecmp('ABC').zero?
19
- # 'abc'.casecmp(str).zero?
20
- class Casecmp < Cop
21
- MSG = 'Use `%<good>s` instead of `%<bad>s`.'.freeze
22
- CASE_METHODS = %i[downcase upcase].freeze
23
-
24
- def_node_matcher :downcase_eq, <<-PATTERN
25
- (send
26
- $(send _ ${:downcase :upcase})
27
- ${:== :eql? :!=}
28
- ${str (send _ {:downcase :upcase} ...) (begin str)})
29
- PATTERN
30
-
31
- def_node_matcher :eq_downcase, <<-PATTERN
32
- (send
33
- {str (send _ {:downcase :upcase} ...) (begin str)}
34
- ${:== :eql? :!=}
35
- $(send _ ${:downcase :upcase}))
36
- PATTERN
37
-
38
- def_node_matcher :downcase_downcase, <<-PATTERN
39
- (send
40
- $(send _ ${:downcase :upcase})
41
- ${:== :eql? :!=}
42
- $(send _ ${:downcase :upcase}))
43
- PATTERN
44
-
45
- def on_send(node)
46
- return unless downcase_eq(node) || eq_downcase(node)
47
- return unless (parts = take_method_apart(node))
48
-
49
- _, _, arg, variable = parts
50
- good_method = build_good_method(arg, variable)
51
-
52
- add_offense(
53
- node,
54
- message: format(MSG, good: good_method, bad: node.source)
55
- )
56
- end
57
-
58
- def autocorrect(node)
59
- return unless (parts = take_method_apart(node))
60
-
61
- receiver, method, arg, variable = parts
62
-
63
- correction(node, receiver, method, arg, variable)
64
- end
65
-
66
- private
67
-
68
- def take_method_apart(node)
69
- if downcase_downcase(node)
70
- receiver, method, rhs = *node
71
- arg, = *rhs
72
- elsif downcase_eq(node)
73
- receiver, method, arg = *node
74
- elsif eq_downcase(node)
75
- arg, method, receiver = *node
76
- else
77
- return
78
- end
79
-
80
- variable, = *receiver
81
-
82
- [receiver, method, arg, variable]
83
- end
84
-
85
- def correction(node, _receiver, method, arg, variable)
86
- lambda do |corrector|
87
- corrector.insert_before(node.loc.expression, '!') if method == :!=
88
-
89
- replacement = build_good_method(arg, variable)
90
-
91
- corrector.replace(node.loc.expression, replacement)
92
- end
93
- end
94
-
95
- def build_good_method(arg, variable)
96
- # We want resulting call to be parenthesized
97
- # if arg already includes one or more sets of parens, don't add more
98
- # or if method call already used parens, again, don't add more
99
- if arg.send_type? || !parentheses?(arg)
100
- "#{variable.source}.casecmp(#{arg.source}).zero?"
101
- else
102
- "#{variable.source}.casecmp#{arg.source}.zero?"
103
- end
104
- end
105
- end
106
- end
107
- end
108
- end
@@ -1,78 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module Cop
5
- module Performance
6
- # This cop is used to identify usages of
7
- # @example
8
- # # bad
9
- # array = ["a", "b", "c"]
10
- # array.compact.flatten.map { |x| x.downcase }
11
- #
12
- # Each of these methods (`compact`, `flatten`, `map`) will generate a
13
- # new intermediate array that is promptly thrown away. Instead it is
14
- # faster to mutate when we know it's safe.
15
- #
16
- # @example
17
- # # good.
18
- # array = ["a", "b", "c"]
19
- # array.compact!
20
- # array.flatten!
21
- # array.map! { |x| x.downcase }
22
- # array
23
- class ChainArrayAllocation < Cop
24
- include RangeHelp
25
-
26
- # These methods return a new array but only sometimes. They must be
27
- # called with an argument. For example:
28
- #
29
- # [1,2].first # => 1
30
- # [1,2].first(1) # => [1]
31
- #
32
- RETURN_NEW_ARRAY_WHEN_ARGS = ':first :last :pop :sample :shift '.freeze
33
-
34
- # These methods return a new array only when called without a block.
35
- RETURNS_NEW_ARRAY_WHEN_NO_BLOCK = ':zip :product '.freeze
36
-
37
- # These methods ALWAYS return a new array
38
- # after they're called it's safe to mutate the the resulting array
39
- ALWAYS_RETURNS_NEW_ARRAY = ':* :+ :- :collect :compact :drop '\
40
- ':drop_while :flatten :map :reject ' \
41
- ':reverse :rotate :select :shuffle :sort ' \
42
- ':take :take_while :transpose :uniq ' \
43
- ':values_at :| '.freeze
44
-
45
- # These methods have a mutation alternative. For example :collect
46
- # can be called as :collect!
47
- HAS_MUTATION_ALTERNATIVE = ':collect :compact :flatten :map :reject '\
48
- ':reverse :rotate :select :shuffle :sort '\
49
- ':uniq '.freeze
50
- MSG = 'Use unchained `%<method>s!` and `%<second_method>s!` '\
51
- '(followed by `return array` if required) instead of chaining '\
52
- '`%<method>s...%<second_method>s`.'.freeze
53
-
54
- def_node_matcher :flat_map_candidate?, <<-PATTERN
55
- {
56
- (send (send _ ${#{RETURN_NEW_ARRAY_WHEN_ARGS}} {int lvar ivar cvar gvar}) ${#{HAS_MUTATION_ALTERNATIVE}} $...)
57
- (send (block (send _ ${#{ALWAYS_RETURNS_NEW_ARRAY} }) ...) ${#{HAS_MUTATION_ALTERNATIVE}} $...)
58
- (send (send _ ${#{ALWAYS_RETURNS_NEW_ARRAY + RETURNS_NEW_ARRAY_WHEN_NO_BLOCK}} ...) ${#{HAS_MUTATION_ALTERNATIVE}} $...)
59
- }
60
- PATTERN
61
-
62
- def on_send(node)
63
- flat_map_candidate?(node) do |fm, sm, _|
64
- range = range_between(
65
- node.loc.dot.begin_pos,
66
- node.source_range.end_pos
67
- )
68
- add_offense(
69
- node,
70
- location: range,
71
- message: format(MSG, method: fm, second_method: sm)
72
- )
73
- end
74
- end
75
- end
76
- end
77
- end
78
- end
@@ -1,122 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module Cop
5
- module Performance
6
- # This cop identifies places where `sort { |a, b| a.foo <=> b.foo }`
7
- # can be replaced by `sort_by(&:foo)`.
8
- # This cop also checks `max` and `min` methods.
9
- #
10
- # @example
11
- # # bad
12
- # array.sort { |a, b| a.foo <=> b.foo }
13
- # array.max { |a, b| a.foo <=> b.foo }
14
- # array.min { |a, b| a.foo <=> b.foo }
15
- # array.sort { |a, b| a[:foo] <=> b[:foo] }
16
- #
17
- # # good
18
- # array.sort_by(&:foo)
19
- # array.sort_by { |v| v.foo }
20
- # array.sort_by do |var|
21
- # var.foo
22
- # end
23
- # array.max_by(&:foo)
24
- # array.min_by(&:foo)
25
- # array.sort_by { |a| a[:foo] }
26
- class CompareWithBlock < Cop
27
- include RangeHelp
28
-
29
- MSG = 'Use `%<compare_method>s_by%<instead>s` instead of ' \
30
- '`%<compare_method>s { |%<var_a>s, %<var_b>s| %<str_a>s ' \
31
- '<=> %<str_b>s }`.'.freeze
32
-
33
- def_node_matcher :compare?, <<-PATTERN
34
- (block
35
- $(send _ {:sort :min :max})
36
- (args (arg $_a) (arg $_b))
37
- $send)
38
- PATTERN
39
-
40
- def_node_matcher :replaceable_body?, <<-PATTERN
41
- (send
42
- (send (lvar %1) $_method $...)
43
- :<=>
44
- (send (lvar %2) _method $...))
45
- PATTERN
46
-
47
- def on_block(node)
48
- compare?(node) do |send, var_a, var_b, body|
49
- replaceable_body?(body, var_a, var_b) do |method, args_a, args_b|
50
- return unless slow_compare?(method, args_a, args_b)
51
-
52
- range = compare_range(send, node)
53
-
54
- add_offense(
55
- node,
56
- location: range,
57
- message: message(send, method, var_a, var_b, args_a)
58
- )
59
- end
60
- end
61
- end
62
-
63
- def autocorrect(node)
64
- lambda do |corrector|
65
- send, var_a, var_b, body = compare?(node)
66
- method, arg, = replaceable_body?(body, var_a, var_b)
67
- replacement =
68
- if method == :[]
69
- "#{send.method_name}_by { |a| a[#{arg.first.source}] }"
70
- else
71
- "#{send.method_name}_by(&:#{method})"
72
- end
73
- corrector.replace(compare_range(send, node),
74
- replacement)
75
- end
76
- end
77
-
78
- private
79
-
80
- def slow_compare?(method, args_a, args_b)
81
- return false unless args_a == args_b
82
-
83
- if method == :[]
84
- return false unless args_a.size == 1
85
-
86
- key = args_a.first
87
- return false unless %i[sym str int].include?(key.type)
88
- else
89
- return false unless args_a.empty?
90
- end
91
- true
92
- end
93
-
94
- # rubocop:disable Metrics/MethodLength
95
- def message(send, method, var_a, var_b, args)
96
- compare_method = send.method_name
97
- if method == :[]
98
- key = args.first
99
- instead = " { |a| a[#{key.source}] }"
100
- str_a = "#{var_a}[#{key.source}]"
101
- str_b = "#{var_b}[#{key.source}]"
102
- else
103
- instead = "(&:#{method})"
104
- str_a = "#{var_a}.#{method}"
105
- str_b = "#{var_b}.#{method}"
106
- end
107
- format(MSG, compare_method: compare_method,
108
- instead: instead,
109
- var_a: var_a,
110
- var_b: var_b,
111
- str_a: str_a,
112
- str_b: str_b)
113
- end
114
- # rubocop:enable Metrics/MethodLength
115
-
116
- def compare_range(send, node)
117
- range_between(send.loc.selector.begin_pos, node.loc.end.end_pos)
118
- end
119
- end
120
- end
121
- end
122
- end
@@ -1,102 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module Cop
5
- module Performance
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
8
- # passed to the `count` call.
9
- #
10
- # @example
11
- # # bad
12
- # [1, 2, 3].select { |e| e > 2 }.size
13
- # [1, 2, 3].reject { |e| e > 2 }.size
14
- # [1, 2, 3].select { |e| e > 2 }.length
15
- # [1, 2, 3].reject { |e| e > 2 }.length
16
- # [1, 2, 3].select { |e| e > 2 }.count { |e| e.odd? }
17
- # [1, 2, 3].reject { |e| e > 2 }.count { |e| e.even? }
18
- # array.select(&:value).count
19
- #
20
- # # good
21
- # [1, 2, 3].count { |e| e > 2 }
22
- # [1, 2, 3].count { |e| e < 2 }
23
- # [1, 2, 3].count { |e| e > 2 && e.odd? }
24
- # [1, 2, 3].count { |e| e < 2 && e.even? }
25
- # Model.select('field AS field_one').count
26
- # Model.select(:value).count
27
- #
28
- # `ActiveRecord` compatibility:
29
- # `ActiveRecord` will ignore the block that is passed to `count`.
30
- # Other methods, such as `select`, will convert the association to an
31
- # array and then run the block on the array. A simple work around to
32
- # make `count` work with a block is to call `to_a.count {...}`.
33
- #
34
- # Example:
35
- # Model.where(id: [1, 2, 3].select { |m| m.method == true }.size
36
- #
37
- # becomes:
38
- #
39
- # Model.where(id: [1, 2, 3]).to_a.count { |m| m.method == true }
40
- class Count < Cop
41
- include SafeMode
42
- include RangeHelp
43
-
44
- MSG = 'Use `count` instead of `%<selector>s...%<counter>s`.'.freeze
45
-
46
- def_node_matcher :count_candidate?, <<-PATTERN
47
- {
48
- (send (block $(send _ ${:select :reject}) ...) ${:count :length :size})
49
- (send $(send _ ${:select :reject} (:block_pass _)) ${:count :length :size})
50
- }
51
- PATTERN
52
-
53
- def on_send(node)
54
- return if rails_safe_mode?
55
-
56
- count_candidate?(node) do |selector_node, selector, counter|
57
- return unless eligible_node?(node)
58
-
59
- range = source_starting_at(node) do
60
- selector_node.loc.selector.begin_pos
61
- end
62
-
63
- add_offense(node,
64
- location: range,
65
- message: format(MSG, selector: selector,
66
- counter: counter))
67
- end
68
- end
69
-
70
- def autocorrect(node)
71
- selector_node, selector, _counter = count_candidate?(node)
72
- selector_loc = selector_node.loc.selector
73
-
74
- return if selector == :reject
75
-
76
- range = source_starting_at(node) { |n| n.loc.dot.begin_pos }
77
-
78
- lambda do |corrector|
79
- corrector.remove(range)
80
- corrector.replace(selector_loc, 'count')
81
- end
82
- end
83
-
84
- private
85
-
86
- def eligible_node?(node)
87
- !(node.parent && node.parent.block_type?)
88
- end
89
-
90
- def source_starting_at(node)
91
- begin_pos = if block_given?
92
- yield node
93
- else
94
- node.source_range.begin_pos
95
- end
96
-
97
- range_between(begin_pos, node.source_range.end_pos)
98
- end
99
- end
100
- end
101
- end
102
- end
@@ -1,110 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module Cop
5
- module Performance
6
- # This cop is used to identify usages of
7
- # `select.first`, `select.last`, `find_all.first`, and `find_all.last`
8
- # and change them to use `detect` instead.
9
- #
10
- # @example
11
- # # bad
12
- # [].select { |item| true }.first
13
- # [].select { |item| true }.last
14
- # [].find_all { |item| true }.first
15
- # [].find_all { |item| true }.last
16
- #
17
- # # good
18
- # [].detect { |item| true }
19
- # [].reverse.detect { |item| true }
20
- #
21
- # `ActiveRecord` compatibility:
22
- # `ActiveRecord` does not implement a `detect` method and `find` has its
23
- # own meaning. Correcting ActiveRecord methods with this cop should be
24
- # considered unsafe.
25
- class Detect < Cop
26
- include SafeMode
27
-
28
- MSG = 'Use `%<prefer>s` instead of ' \
29
- '`%<first_method>s.%<second_method>s`.'.freeze
30
- REVERSE_MSG = 'Use `reverse.%<prefer>s` instead of ' \
31
- '`%<first_method>s.%<second_method>s`.'.freeze
32
-
33
- def_node_matcher :detect_candidate?, <<-PATTERN
34
- {
35
- (send $(block (send _ {:select :find_all}) ...) ${:first :last} $...)
36
- (send $(send _ {:select :find_all} ...) ${:first :last} $...)
37
- }
38
- PATTERN
39
-
40
- def on_send(node)
41
- return if rails_safe_mode?
42
-
43
- detect_candidate?(node) do |receiver, second_method, args|
44
- return unless args.empty?
45
- return unless receiver
46
-
47
- receiver, _args, body = *receiver if receiver.block_type?
48
- return if accept_first_call?(receiver, body)
49
-
50
- register_offense(node, receiver, second_method)
51
- end
52
- end
53
-
54
- def autocorrect(node)
55
- receiver, first_method = *node
56
-
57
- replacement = if first_method == :last
58
- "reverse.#{preferred_method}"
59
- else
60
- preferred_method
61
- end
62
-
63
- first_range = receiver.source_range.end.join(node.loc.selector)
64
-
65
- receiver, _args, _body = *receiver if receiver.block_type?
66
-
67
- lambda do |corrector|
68
- corrector.remove(first_range)
69
- corrector.replace(receiver.loc.selector, replacement)
70
- end
71
- end
72
-
73
- private
74
-
75
- def accept_first_call?(receiver, body)
76
- caller, _first_method, args = *receiver
77
-
78
- # check that we have usual block or block pass
79
- return true if body.nil? && (args.nil? || !args.block_pass_type?)
80
-
81
- lazy?(caller)
82
- end
83
-
84
- def register_offense(node, receiver, second_method)
85
- _caller, first_method, _args = *receiver
86
- range = receiver.loc.selector.join(node.loc.selector)
87
-
88
- message = second_method == :last ? REVERSE_MSG : MSG
89
- formatted_message = format(message, prefer: preferred_method,
90
- first_method: first_method,
91
- second_method: second_method)
92
-
93
- add_offense(node, location: range, message: formatted_message)
94
- end
95
-
96
- def preferred_method
97
- config.for_cop('Style/CollectionMethods') \
98
- ['PreferredMethods']['detect'] || 'detect'
99
- end
100
-
101
- def lazy?(node)
102
- return false unless node
103
-
104
- receiver, method, _args = *node
105
- method == :lazy && !receiver.nil?
106
- end
107
- end
108
- end
109
- end
110
- end