rubocop 0.36.0 → 0.37.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rubocop might be problematic. Click here for more details.

Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +62 -2
  3. data/README.md +10 -1
  4. data/assets/output.html.erb +55 -1
  5. data/config/default.yml +9 -3
  6. data/config/disabled.yml +21 -0
  7. data/config/enabled.yml +11 -10
  8. data/lib/rubocop.rb +9 -2
  9. data/lib/rubocop/ast_node.rb +67 -19
  10. data/lib/rubocop/ast_node/builder.rb +1 -0
  11. data/lib/rubocop/ast_node/sexp.rb +1 -0
  12. data/lib/rubocop/ast_node/traversal.rb +171 -0
  13. data/lib/rubocop/cached_data.rb +4 -1
  14. data/lib/rubocop/cli.rb +1 -1
  15. data/lib/rubocop/config.rb +36 -20
  16. data/lib/rubocop/config_loader.rb +6 -5
  17. data/lib/rubocop/cop/commissioner.rb +27 -18
  18. data/lib/rubocop/cop/cop.rb +7 -6
  19. data/lib/rubocop/cop/lint/duplicated_key.rb +1 -8
  20. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +1 -1
  21. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +18 -1
  22. data/lib/rubocop/cop/lint/nested_method_definition.rb +5 -1
  23. data/lib/rubocop/cop/lint/unneeded_disable.rb +1 -1
  24. data/lib/rubocop/cop/mixin/array_hash_indentation.rb +1 -0
  25. data/lib/rubocop/cop/mixin/autocorrect_alignment.rb +9 -2
  26. data/lib/rubocop/cop/mixin/check_assignment.rb +1 -1
  27. data/lib/rubocop/cop/mixin/classish_length.rb +3 -4
  28. data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +8 -4
  29. data/lib/rubocop/cop/mixin/configurable_naming.rb +1 -1
  30. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +2 -1
  31. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +35 -0
  32. data/lib/rubocop/cop/mixin/method_complexity.rb +1 -1
  33. data/lib/rubocop/cop/mixin/min_body_length.rb +1 -1
  34. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +58 -0
  35. data/lib/rubocop/cop/mixin/space_after_punctuation.rb +1 -1
  36. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +1 -1
  37. data/lib/rubocop/cop/mixin/trailing_comma.rb +3 -2
  38. data/lib/rubocop/cop/performance/case_when_splat.rb +7 -0
  39. data/lib/rubocop/cop/performance/casecmp.rb +56 -17
  40. data/lib/rubocop/cop/performance/redundant_block_call.rb +17 -3
  41. data/lib/rubocop/cop/performance/redundant_merge.rb +7 -1
  42. data/lib/rubocop/cop/performance/times_map.rb +3 -4
  43. data/lib/rubocop/cop/severity.rb +1 -1
  44. data/lib/rubocop/cop/style/align_hash.rb +1 -1
  45. data/lib/rubocop/cop/style/align_parameters.rb +1 -1
  46. data/lib/rubocop/cop/style/and_or.rb +1 -1
  47. data/lib/rubocop/cop/style/block_comments.rb +2 -0
  48. data/lib/rubocop/cop/style/conditional_assignment.rb +2 -0
  49. data/lib/rubocop/cop/style/copyright.rb +2 -2
  50. data/lib/rubocop/cop/style/documentation.rb +19 -29
  51. data/lib/rubocop/cop/style/each_with_object.rb +1 -1
  52. data/lib/rubocop/cop/style/else_alignment.rb +2 -2
  53. data/lib/rubocop/cop/style/encoding.rb +1 -1
  54. data/lib/rubocop/cop/style/first_parameter_indentation.rb +1 -1
  55. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +2 -12
  56. data/lib/rubocop/cop/style/guard_clause.rb +2 -1
  57. data/lib/rubocop/cop/style/if_unless_modifier.rb +1 -8
  58. data/lib/rubocop/cop/style/indent_assignment.rb +1 -1
  59. data/lib/rubocop/cop/style/indentation_width.rb +3 -7
  60. data/lib/rubocop/cop/style/method_call_parentheses.rb +2 -1
  61. data/lib/rubocop/cop/style/method_def_parentheses.rb +1 -1
  62. data/lib/rubocop/cop/style/multiline_array_brace_layout.rb +3 -41
  63. data/lib/rubocop/cop/style/multiline_hash_brace_layout.rb +57 -0
  64. data/lib/rubocop/cop/style/multiline_method_call_brace_layout.rb +65 -0
  65. data/lib/rubocop/cop/style/multiline_method_call_indentation.rb +5 -4
  66. data/lib/rubocop/cop/style/multiline_method_definition_brace_layout.rb +62 -0
  67. data/lib/rubocop/cop/style/multiline_operation_indentation.rb +9 -3
  68. data/lib/rubocop/cop/style/mutable_constant.rb +18 -3
  69. data/lib/rubocop/cop/style/nested_modifier.rb +5 -2
  70. data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +2 -1
  71. data/lib/rubocop/cop/style/next.rb +32 -11
  72. data/lib/rubocop/cop/style/option_hash.rb +1 -1
  73. data/lib/rubocop/cop/style/redundant_freeze.rb +13 -3
  74. data/lib/rubocop/cop/style/redundant_parentheses.rb +33 -7
  75. data/lib/rubocop/cop/style/send.rb +1 -1
  76. data/lib/rubocop/cop/style/space_around_keyword.rb +198 -0
  77. data/lib/rubocop/cop/style/space_around_operators.rb +2 -12
  78. data/lib/rubocop/cop/style/space_inside_block_braces.rb +1 -1
  79. data/lib/rubocop/cop/style/special_global_vars.rb +4 -4
  80. data/lib/rubocop/cop/style/symbol_array.rb +1 -1
  81. data/lib/rubocop/cop/style/trailing_blank_lines.rb +1 -1
  82. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +1 -0
  83. data/lib/rubocop/cop/style/trailing_comma_in_literal.rb +1 -0
  84. data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
  85. data/lib/rubocop/cop/style/zero_length_predicate.rb +55 -0
  86. data/lib/rubocop/cop/team.rb +30 -5
  87. data/lib/rubocop/cop/util.rb +16 -1
  88. data/lib/rubocop/cop/variable_force.rb +3 -12
  89. data/lib/rubocop/cop/variable_force/assignment.rb +3 -3
  90. data/lib/rubocop/cop/variable_force/locatable.rb +25 -6
  91. data/lib/rubocop/cop/variable_force/reference.rb +3 -3
  92. data/lib/rubocop/cop/variable_force/scope.rb +8 -7
  93. data/lib/rubocop/cop/variable_force/variable.rb +3 -3
  94. data/lib/rubocop/cop/variable_force/variable_table.rb +1 -1
  95. data/lib/rubocop/formatter/formatter_set.rb +2 -2
  96. data/lib/rubocop/node_pattern.rb +1 -1
  97. data/lib/rubocop/options.rb +10 -10
  98. data/lib/rubocop/path_util.rb +5 -0
  99. data/lib/rubocop/processed_source.rb +8 -2
  100. data/lib/rubocop/result_cache.rb +1 -1
  101. data/lib/rubocop/runner.rb +1 -1
  102. data/lib/rubocop/token.rb +2 -2
  103. data/lib/rubocop/version.rb +1 -1
  104. data/relnotes/v0.30.1.md +1 -0
  105. data/relnotes/v0.33.0.md +1 -1
  106. data/relnotes/v0.36.0.md +2 -1
  107. data/relnotes/v0.37.0.md +200 -0
  108. data/rubocop.gemspec +2 -1
  109. metadata +28 -7
  110. data/lib/rubocop/cop/style/space_after_control_keyword.rb +0 -35
  111. data/lib/rubocop/cop/style/space_before_modifier_keyword.rb +0 -38
@@ -13,20 +13,13 @@ module RuboCop
13
13
  class DuplicatedKey < Cop
14
14
  MSG = 'Duplicated key in hash literal.'.freeze
15
15
 
16
- LITERALS = [:sym, :str, :float, :int].freeze
17
-
18
16
  def on_hash(node)
19
17
  keys = []
20
18
 
21
19
  hash_pairs = *node
22
20
  hash_pairs.each do |pair|
23
21
  key, _value = *pair
24
- if keys.include?(key) && LITERALS.include?(key.type)
25
- add_offense(key, :expression)
26
- elsif keys.include?(key) && key.type == :array
27
- key.children.each do |child|
28
- return false unless LITERALS.include?(child.type)
29
- end
22
+ if keys.include?(key) && key.recursive_basic_literal?
30
23
  add_offense(key, :expression)
31
24
  end
32
25
  keys << key
@@ -68,7 +68,7 @@ module RuboCop
68
68
  receiver_node
69
69
  end
70
70
 
71
- relevant_node.source.scan(NAMED_FIELD_REGEX).size > 0
71
+ !relevant_node.source.scan(NAMED_FIELD_REGEX).empty?
72
72
  end
73
73
 
74
74
  def node_with_splat_args?(node)
@@ -17,6 +17,7 @@ module RuboCop
17
17
  final_node = begin_node.children.last
18
18
  next unless final_node
19
19
  next if special_keyword?(final_node)
20
+ next if final_node.xstr_type?
20
21
  next unless final_node.literal?
21
22
 
22
23
  add_offense(final_node, :expression)
@@ -37,7 +38,23 @@ module RuboCop
37
38
  end
38
39
 
39
40
  def autocorrected_value(node)
40
- node.str_type? ? node.children.last : node.source
41
+ case node.type
42
+ when :str
43
+ node.children.last
44
+ when :sym
45
+ autocorrected_value_for_symbol(node)
46
+ else
47
+ node.source
48
+ end
49
+ end
50
+
51
+ def autocorrected_value_for_symbol(node)
52
+ end_pos =
53
+ node.loc.end ? node.loc.end.begin_pos : node.loc.expression.end_pos
54
+
55
+ Parser::Source::Range.new(node.source_range.source_buffer,
56
+ node.loc.begin.end_pos,
57
+ end_pos).source
41
58
  end
42
59
  end
43
60
  end
@@ -26,6 +26,10 @@ module RuboCop
26
26
  (block (send _ {:instance_eval :class_eval :module_eval} ...) ...)
27
27
  PATTERN
28
28
 
29
+ def_node_matcher :class_or_module_new_call?, <<-PATTERN
30
+ (block (send (const nil {:Class :Module}) :new) ...)
31
+ PATTERN
32
+
29
33
  def on_method_def(node, _method_name, _args, _body)
30
34
  find_nested_defs(node) do |nested_def_node|
31
35
  add_offense(nested_def_node, :expression)
@@ -36,7 +40,7 @@ module RuboCop
36
40
  node.each_child_node do |child|
37
41
  if child.def_type? || child.defs_type?
38
42
  yield child
39
- elsif !eval_call?(child)
43
+ elsif !(eval_call?(child) || class_or_module_new_call?(child))
40
44
  find_nested_defs(child, &block)
41
45
  end
42
46
  end
@@ -128,7 +128,7 @@ module RuboCop
128
128
  def cop_range(comment, cop)
129
129
  matching_range(comment.loc.expression, cop) ||
130
130
  matching_range(comment.loc.expression, cop.split('/').last) ||
131
- fail("Couldn't find #{cop} in comment: #{comment.text}")
131
+ raise("Couldn't find #{cop} in comment: #{comment.text}")
132
132
  end
133
133
 
134
134
  def matching_range(haystack, needle)
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  module RuboCop
4
5
  module Cop
@@ -22,13 +22,20 @@ module RuboCop
22
22
  SPACE * node.loc.column
23
23
  end
24
24
 
25
+ def display_column(range)
26
+ line = processed_source.lines[range.line - 1]
27
+ line[0, range.column].display_width
28
+ end
29
+
25
30
  def check_alignment(items, base_column = nil)
26
- base_column ||= items.first.loc.column unless items.empty?
31
+ unless items.empty?
32
+ base_column ||= display_column(items.first.source_range)
33
+ end
27
34
  prev_line = -1
28
35
  items.each do |current|
29
36
  if current.loc.line > prev_line &&
30
37
  begins_its_line?(current.source_range)
31
- @column_delta = base_column - current.loc.column
38
+ @column_delta = base_column - display_column(current.source_range)
32
39
  if @column_delta != 0
33
40
  expr = current.source_range
34
41
  if offenses.any? { |o| within?(expr, o.location) }
@@ -21,7 +21,7 @@ module RuboCop
21
21
  check_assignment(node, rhs) if rhs.is_a?(AST::Node)
22
22
  end
23
23
 
24
- private
24
+ module_function
25
25
 
26
26
  def extract_rhs(node)
27
27
  if node.casgn_type?
@@ -13,8 +13,7 @@ module RuboCop
13
13
  body_line_numbers = line_range(node).to_a[1...-1]
14
14
 
15
15
  target_line_numbers = body_line_numbers -
16
- line_numbers_of_inner_thing(node, :module) -
17
- line_numbers_of_inner_thing(node, :class)
16
+ line_numbers_of_inner_nodes(node, :module, :class)
18
17
 
19
18
  target_line_numbers.reduce(0) do |length, line_number|
20
19
  source_line = processed_source[line_number]
@@ -23,10 +22,10 @@ module RuboCop
23
22
  end
24
23
  end
25
24
 
26
- def line_numbers_of_inner_thing(node, type)
25
+ def line_numbers_of_inner_nodes(node, *types)
27
26
  line_numbers = Set.new
28
27
 
29
- node.each_descendant(:module, type) do |inner_node|
28
+ node.each_descendant(*types) do |inner_node|
30
29
  line_range = line_range(inner_node)
31
30
  line_numbers.merge(line_range)
32
31
  end
@@ -77,14 +77,18 @@ module RuboCop
77
77
  alias unrecognized_style_detected no_acceptable_style!
78
78
 
79
79
  def style
80
- s = cop_config[parameter_name].to_sym
81
- return s if supported_styles.include?(s)
82
- fail "Unknown style #{s} selected!"
80
+ @enforced_style ||= begin
81
+ s = cop_config[parameter_name].to_sym
82
+ unless supported_styles.include?(s)
83
+ raise "Unknown style #{s} selected!"
84
+ end
85
+ s
86
+ end
83
87
  end
84
88
 
85
89
  def alternative_style
86
90
  if supported_styles.size != 2
87
- fail 'alternative_style can only be used when there are exactly ' \
91
+ raise 'alternative_style can only be used when there are exactly ' \
88
92
  '2 SupportedStyles'
89
93
  end
90
94
  (supported_styles - [style]).first
@@ -9,7 +9,7 @@ module RuboCop
9
9
  include ConfigurableEnforcedStyle
10
10
 
11
11
  SNAKE_CASE = /^@{0,2}[\da-z_]+[!?=]?$/
12
- CAMEL_CASE = /^@{0,2}[a-z][\da-zA-Z]+[!?=]?$/
12
+ CAMEL_CASE = /^@{0,2}_?[a-z][\da-zA-Z]+[!?=]?$/
13
13
 
14
14
  def check_name(node, name, name_range)
15
15
  return if operator?(name)
@@ -22,7 +22,8 @@ module RuboCop
22
22
  return unless end_loc # Discard modifier forms of if/while/until.
23
23
 
24
24
  matching = align_ranges.select do |_, range|
25
- range.line == end_loc.line || range.column == end_loc.column
25
+ range.line == end_loc.line ||
26
+ effective_column(range) == end_loc.column
26
27
  end
27
28
 
28
29
  if matching.key?(style)
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module RuboCop
5
+ module Cop
6
+ # Common functionality for dealing with frozen string literals.
7
+ module FrozenStringLiteral
8
+ module_function
9
+
10
+ FROZEN_STRING_LITERAL = '# frozen_string_literal:'.freeze
11
+ FROZEN_STRING_LITERAL_ENABLED = '# frozen_string_literal: true'.freeze
12
+ FROZEN_STRING_LITERAL_TYPES = [:str, :dstr].freeze
13
+
14
+ def frozen_string_literal_comment_exists?(processed_source,
15
+ comment = FROZEN_STRING_LITERAL)
16
+ first_three_lines =
17
+ [processed_source[0], processed_source[1], processed_source[2]]
18
+ first_three_lines.compact!
19
+ first_three_lines.any? do |line|
20
+ line.start_with?(comment)
21
+ end
22
+ end
23
+
24
+ def frozen_string_literals_enabled?(processed_source)
25
+ ruby_version = processed_source.ruby_version
26
+ return false unless ruby_version
27
+ return true if ruby_version >= 3.0
28
+ return false unless ruby_version >= 2.3
29
+ frozen_string_literal_comment_exists?(
30
+ processed_source, FROZEN_STRING_LITERAL_ENABLED
31
+ )
32
+ end
33
+ end
34
+ end
35
+ end
@@ -22,7 +22,7 @@ module RuboCop
22
22
  end
23
23
 
24
24
  def complexity(node)
25
- node.each_node(self.class::COUNTED_NODES).reduce(1) do |score, n|
25
+ node.each_node(*self.class::COUNTED_NODES).reduce(1) do |score, n|
26
26
  score + complexity_score_for(n)
27
27
  end
28
28
  end
@@ -13,7 +13,7 @@ module RuboCop
13
13
  length = cop_config['MinBodyLength'] || 1
14
14
  return length if length.is_a?(Integer) && length > 0
15
15
 
16
- fail 'MinBodyLength needs to be a positive integer!'
16
+ raise 'MinBodyLength needs to be a positive integer!'
17
17
  end
18
18
  end
19
19
  end
@@ -0,0 +1,58 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module RuboCop
5
+ module Cop
6
+ # Common functionality for checking that the closing brace of a literal is
7
+ # symmetrical with respect to the opening brace and contained elements.
8
+ module MultilineLiteralBraceLayout
9
+ def check_brace_layout(node)
10
+ return unless node.loc.begin # Ignore implicit literals.
11
+ return if children(node).empty? # Ignore empty literals.
12
+
13
+ if opening_brace_on_same_line?(node)
14
+ return if closing_brace_on_same_line?(node)
15
+
16
+ add_offense(node, :expression, self.class::SAME_LINE_MESSAGE)
17
+ else
18
+ return unless closing_brace_on_same_line?(node)
19
+
20
+ add_offense(node, :expression, self.class::NEW_LINE_MESSAGE)
21
+ end
22
+ end
23
+
24
+ def autocorrect(node)
25
+ if closing_brace_on_same_line?(node)
26
+ lambda do |corrector|
27
+ corrector.insert_before(node.loc.end, "\n".freeze)
28
+ end
29
+ else
30
+ range = Parser::Source::Range.new(
31
+ node.source_range.source_buffer,
32
+ children(node).last.source_range.end_pos,
33
+ node.loc.end.begin_pos)
34
+
35
+ ->(corrector) { corrector.remove(range) }
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def children(node)
42
+ node.children
43
+ end
44
+
45
+ # This method depends on the fact that we have guarded
46
+ # against implicit and empty literals.
47
+ def opening_brace_on_same_line?(node)
48
+ node.loc.begin.line == children(node).first.loc.first_line
49
+ end
50
+
51
+ # This method depends on the fact that we have guarded
52
+ # against implicit and empty literals.
53
+ def closing_brace_on_same_line?(node)
54
+ node.loc.end.line == children(node).last.loc.last_line
55
+ end
56
+ end
57
+ end
58
+ end
@@ -21,7 +21,7 @@ module RuboCop
21
21
 
22
22
  def space_forbidden_before_rcurly?
23
23
  cfg = config.for_cop('Style/SpaceInsideBlockBraces')
24
- style = cfg['Enabled'] ? cfg['EnforcedStyle'] : 'space'
24
+ style = cfg['EnforcedStyle'] || 'space'
25
25
  style == 'no_space'
26
26
  end
27
27
 
@@ -26,7 +26,7 @@ module RuboCop
26
26
 
27
27
  def space_required_after_lcurly?
28
28
  cfg = config.for_cop('Style/SpaceInsideBlockBraces')
29
- style = cfg['Enabled'] ? cfg['EnforcedStyle'] : 'space'
29
+ style = cfg['EnforcedStyle'] || 'space'
30
30
  style == 'space'
31
31
  end
32
32
 
@@ -81,10 +81,11 @@ module RuboCop
81
81
  node.children
82
82
  end
83
83
 
84
- # Without this check, Foo.new({}) is considered multiline, which
84
+ # No need to process anything if the whole node is not multiline
85
+ # Without the 2nd check, Foo.new({}) is considered multiline, which
85
86
  # it should not be. Essentially, if there are no elements, the
86
87
  # expression can not be multiline.
87
- return if elements.empty?
88
+ return if !node.multiline? || elements.empty?
88
89
 
89
90
  items = elements.map(&:source_range)
90
91
  if style == :consistent_comma
@@ -63,6 +63,11 @@ module RuboCop
63
63
  PERCENT_I = '%i'.freeze
64
64
  PERCENT_CAPITAL_I = '%I'.freeze
65
65
 
66
+ def initialize(*)
67
+ super
68
+ @reordered_splat_condition = false
69
+ end
70
+
66
71
  def on_case(node)
67
72
  _case_branch, *when_branches, _else_branch = *node
68
73
  when_conditions =
@@ -85,6 +90,8 @@ module RuboCop
85
90
  if variable.array_type?
86
91
  correct_array_literal(condition, variable)
87
92
  else
93
+ return if @reordered_splat_condition
94
+ @reordered_splat_condition = true
88
95
  reorder_splat_condition(node)
89
96
  end
90
97
  end
@@ -9,43 +9,82 @@ module RuboCop
9
9
  #
10
10
  # @example
11
11
  # @bad
12
- # 'abc'.downcase == 'ABC'
12
+ # 'aBc'.downcase == 'abc'
13
13
  # 'abc'.upcase.eql? 'ABC'
14
+ # 'abc' == 'ABC'.downcase
15
+ # 'ABC'.eql? 'abc'.upcase
16
+ # 'abc'.downcase == 'abc'.downcase
14
17
  #
15
18
  # @good
16
- # 'abc'.casecmp('ABC')
19
+ # 'aBc'.casecmp('ABC').zero?
20
+ # 'abc'.casecmp('abc').zero?
21
+ # 'abc'.casecmp('ABC'.downcase).zero?
17
22
  class Casecmp < Cop
23
+ include IgnoredNode
24
+
18
25
  MSG = 'Use `casecmp` instead of `%s %s`.'.freeze
26
+ CASE_METHODS = [:downcase, :upcase].freeze
19
27
 
20
28
  def_node_matcher :downcase_eq, <<-END
21
- (send $(send _ ${:downcase :upcase}) ${:== :eql?} _)
29
+ (send $(send _ ${:downcase :upcase}) ${:== :eql? :!=} $_)
30
+ END
31
+
32
+ def_node_matcher :eq_downcase, <<-END
33
+ (send _ ${:== :eql? :!=} $(send _ ${:downcase :upcase}))
22
34
  END
23
35
 
24
36
  def on_send(node)
25
- downcase_eq(node) do |send_downcase, case_method, eq_method|
26
- range = node.loc.selector.join(send_downcase.loc.selector)
37
+ return if part_of_ignored_node?(node)
38
+
39
+ downcase_eq(node) do |send_downcase, case_method, eq_method, other|
40
+ *_, method = *other
41
+ if CASE_METHODS.include?(method)
42
+ range = node.loc.expression
43
+ ignore_node(node)
44
+ else
45
+ range = node.loc.selector.join(send_downcase.loc.selector)
46
+ end
47
+
27
48
  add_offense(node, range, format(MSG, case_method, eq_method))
49
+ return
50
+ end
51
+
52
+ eq_downcase(node) do |eq_method, send_downcase, case_method|
53
+ range = node.loc.selector.join(send_downcase.loc.selector)
54
+ add_offense(node, range, format(MSG, eq_method, case_method))
28
55
  end
29
56
  end
30
57
 
31
58
  def autocorrect(node)
32
- receiver, _method, arg = *node
33
- range = Parser::Source::Range.new(node.source_range.source_buffer,
34
- receiver.loc.selector.begin_pos,
35
- arg.loc.begin.begin_pos)
59
+ downcase_eq(node) do
60
+ receiver, method, arg = *node
61
+ variable, = *receiver
62
+ return correction(node, receiver, method, arg, variable)
63
+ end
64
+
65
+ eq_downcase(node) do
66
+ arg, method, receiver = *node
67
+ variable, = *receiver
68
+ return correction(node, receiver, method, arg, variable)
69
+ end
70
+ end
36
71
 
72
+ private
73
+
74
+ def correction(node, _receiver, method, arg, variable)
37
75
  lambda do |corrector|
76
+ corrector.insert_before(node.loc.expression, '!') if method == :!=
77
+
38
78
  # we want resulting call to be parenthesized
39
79
  # if arg already includes one or more sets of parens, don't add more
40
80
  # or if method call already used parens, again, don't add more
41
- if arg.loc.begin.source == '('
42
- corrector.replace(range, 'casecmp')
43
- elsif range.source =~ /\(/
44
- corrector.replace(range, 'casecmp(')
45
- else
46
- corrector.replace(range, 'casecmp(')
47
- corrector.insert_after(arg.source_range, ')')
48
- end
81
+ replacement = if arg.send_type? || !parentheses?(arg)
82
+ "#{variable.source}.casecmp(#{arg.source}).zero?"
83
+ else
84
+ "#{variable.source}.casecmp#{arg.source}.zero?"
85
+ end
86
+
87
+ corrector.replace(node.loc.expression, replacement)
49
88
  end
50
89
  end
51
90
  end