rubocop 0.92.0 → 1.2.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.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +21 -7
  3. data/config/default.yml +169 -59
  4. data/exe/rubocop +1 -1
  5. data/lib/rubocop.rb +15 -3
  6. data/lib/rubocop/cached_data.rb +2 -1
  7. data/lib/rubocop/cli/command/auto_genenerate_config.rb +1 -1
  8. data/lib/rubocop/cli/command/version.rb +1 -1
  9. data/lib/rubocop/comment_config.rb +1 -1
  10. data/lib/rubocop/config.rb +4 -0
  11. data/lib/rubocop/config_loader.rb +19 -2
  12. data/lib/rubocop/config_loader_resolver.rb +7 -5
  13. data/lib/rubocop/config_validator.rb +7 -6
  14. data/lib/rubocop/cop/badge.rb +9 -24
  15. data/lib/rubocop/cop/base.rb +16 -1
  16. data/lib/rubocop/cop/bundler/duplicated_gem.rb +23 -3
  17. data/lib/rubocop/cop/commissioner.rb +36 -22
  18. data/lib/rubocop/cop/corrector.rb +3 -1
  19. data/lib/rubocop/cop/correctors/line_break_corrector.rb +2 -2
  20. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +1 -1
  21. data/lib/rubocop/cop/force.rb +1 -1
  22. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +10 -10
  23. data/lib/rubocop/cop/layout/class_structure.rb +7 -0
  24. data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
  25. data/lib/rubocop/cop/layout/dot_position.rb +6 -9
  26. data/lib/rubocop/cop/layout/else_alignment.rb +15 -2
  27. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +7 -7
  28. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -1
  29. data/lib/rubocop/cop/layout/end_alignment.rb +3 -3
  30. data/lib/rubocop/cop/layout/extra_spacing.rb +1 -2
  31. data/lib/rubocop/cop/layout/hash_alignment.rb +4 -4
  32. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +24 -18
  33. data/lib/rubocop/cop/layout/space_around_equals_in_parameter_default.rb +2 -11
  34. data/lib/rubocop/cop/layout/space_around_operators.rb +4 -1
  35. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +0 -4
  36. data/lib/rubocop/cop/layout/space_inside_parens.rb +35 -13
  37. data/lib/rubocop/cop/layout/trailing_whitespace.rb +37 -13
  38. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +2 -0
  39. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +18 -1
  40. data/lib/rubocop/cop/lint/boolean_symbol.rb +3 -0
  41. data/lib/rubocop/cop/lint/debugger.rb +2 -3
  42. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +77 -0
  43. data/lib/rubocop/cop/lint/else_layout.rb +29 -3
  44. data/lib/rubocop/cop/lint/empty_block.rb +59 -0
  45. data/lib/rubocop/cop/lint/flip_flop.rb +8 -2
  46. data/lib/rubocop/cop/lint/hash_compare_by_identity.rb +37 -0
  47. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +17 -3
  48. data/lib/rubocop/cop/lint/loop.rb +0 -4
  49. data/lib/rubocop/cop/lint/mixed_regexp_capture_types.rb +1 -0
  50. data/lib/rubocop/cop/lint/nested_percent_literal.rb +14 -0
  51. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +58 -0
  52. data/lib/rubocop/cop/lint/number_conversion.rb +46 -13
  53. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +27 -8
  54. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +1 -1
  55. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +78 -0
  56. data/lib/rubocop/cop/lint/to_enum_arguments.rb +95 -0
  57. data/lib/rubocop/cop/lint/to_json.rb +1 -1
  58. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +185 -0
  59. data/lib/rubocop/cop/lint/useless_access_modifier.rb +2 -2
  60. data/lib/rubocop/cop/lint/useless_setter_call.rb +6 -1
  61. data/lib/rubocop/cop/metrics/block_length.rb +3 -1
  62. data/lib/rubocop/cop/metrics/class_length.rb +14 -6
  63. data/lib/rubocop/cop/metrics/parameter_lists.rb +4 -1
  64. data/lib/rubocop/cop/mixin/configurable_numbering.rb +3 -3
  65. data/lib/rubocop/cop/mixin/hash_transform_method.rb +1 -1
  66. data/lib/rubocop/cop/mixin/line_length_help.rb +1 -1
  67. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +12 -2
  68. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +11 -5
  69. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +67 -18
  70. data/lib/rubocop/cop/naming/predicate_name.rb +2 -1
  71. data/lib/rubocop/cop/naming/variable_number.rb +82 -8
  72. data/lib/rubocop/cop/offense.rb +18 -5
  73. data/lib/rubocop/cop/security/open.rb +12 -10
  74. data/lib/rubocop/cop/style/access_modifier_declarations.rb +6 -2
  75. data/lib/rubocop/cop/style/accessor_grouping.rb +3 -0
  76. data/lib/rubocop/cop/style/arguments_forwarding.rb +142 -0
  77. data/lib/rubocop/cop/style/bisected_attr_accessor.rb +0 -4
  78. data/lib/rubocop/cop/style/case_like_if.rb +18 -6
  79. data/lib/rubocop/cop/style/class_equality_comparison.rb +64 -0
  80. data/lib/rubocop/cop/style/collection_compact.rb +85 -0
  81. data/lib/rubocop/cop/style/combinable_loops.rb +8 -1
  82. data/lib/rubocop/cop/style/comment_annotation.rb +6 -0
  83. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +67 -0
  84. data/lib/rubocop/cop/style/double_negation.rb +6 -1
  85. data/lib/rubocop/cop/style/explicit_block_argument.rb +6 -2
  86. data/lib/rubocop/cop/style/for.rb +0 -4
  87. data/lib/rubocop/cop/style/format_string_token.rb +48 -3
  88. data/lib/rubocop/cop/style/hash_syntax.rb +3 -3
  89. data/lib/rubocop/cop/style/keyword_parameters_order.rb +12 -0
  90. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +10 -13
  91. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +6 -11
  92. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +7 -11
  93. data/lib/rubocop/cop/style/method_def_parentheses.rb +0 -4
  94. data/lib/rubocop/cop/style/mixin_grouping.rb +0 -4
  95. data/lib/rubocop/cop/style/multiple_comparison.rb +54 -7
  96. data/lib/rubocop/cop/style/negated_if_else_condition.rb +99 -0
  97. data/lib/rubocop/cop/style/nested_ternary_operator.rb +2 -0
  98. data/lib/rubocop/cop/style/raise_args.rb +21 -9
  99. data/lib/rubocop/cop/style/redundant_begin.rb +36 -8
  100. data/lib/rubocop/cop/style/redundant_condition.rb +5 -1
  101. data/lib/rubocop/cop/style/redundant_interpolation.rb +6 -1
  102. data/lib/rubocop/cop/style/redundant_parentheses.rb +4 -0
  103. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +45 -24
  104. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -15
  105. data/lib/rubocop/cop/style/redundant_self.rb +3 -0
  106. data/lib/rubocop/cop/style/safe_navigation.rb +16 -4
  107. data/lib/rubocop/cop/style/semicolon.rb +3 -0
  108. data/lib/rubocop/cop/style/string_concatenation.rb +14 -2
  109. data/lib/rubocop/cop/style/swap_values.rb +108 -0
  110. data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
  111. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +3 -1
  112. data/lib/rubocop/cop/team.rb +6 -1
  113. data/lib/rubocop/cop/util.rb +5 -1
  114. data/lib/rubocop/cop/variable_force/branch.rb +0 -4
  115. data/lib/rubocop/ext/regexp_node.rb +35 -11
  116. data/lib/rubocop/ext/regexp_parser.rb +84 -0
  117. data/lib/rubocop/formatter/formatter_set.rb +2 -1
  118. data/lib/rubocop/formatter/git_hub_actions_formatter.rb +47 -0
  119. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -1
  120. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
  121. data/lib/rubocop/magic_comment.rb +2 -2
  122. data/lib/rubocop/options.rb +6 -1
  123. data/lib/rubocop/result_cache.rb +8 -2
  124. data/lib/rubocop/rspec/cop_helper.rb +1 -1
  125. data/lib/rubocop/rspec/shared_contexts.rb +4 -0
  126. data/lib/rubocop/runner.rb +4 -4
  127. data/lib/rubocop/target_finder.rb +23 -25
  128. data/lib/rubocop/version.rb +56 -6
  129. metadata +22 -8
  130. data/lib/rubocop/cop/mixin/regexp_literal_help.rb +0 -43
@@ -14,14 +14,25 @@ module RuboCop
14
14
  # # good
15
15
  # x = 0
16
16
  #
17
- # @example AllowInHeredoc: false
17
+ # @example AllowInHeredoc: false (default)
18
18
  # # The line in this example contains spaces after the 0.
19
19
  # # bad
20
20
  # code = <<~RUBY
21
21
  # x = 0
22
22
  # RUBY
23
23
  #
24
- # @example AllowInHeredoc: true (default)
24
+ # # ok
25
+ # code = <<~RUBY
26
+ # x = 0 #{}
27
+ # RUBY
28
+ #
29
+ # # good
30
+ # trailing_whitespace = ' '
31
+ # code = <<~RUBY
32
+ # x = 0#{trailing_whitespace}
33
+ # RUBY
34
+ #
35
+ # @example AllowInHeredoc: true
25
36
  # # The line in this example contains spaces after the 0.
26
37
  # # good
27
38
  # code = <<~RUBY
@@ -35,36 +46,49 @@ module RuboCop
35
46
  MSG = 'Trailing whitespace detected.'
36
47
 
37
48
  def on_new_investigation
38
- heredoc_ranges = extract_heredoc_ranges(processed_source.ast)
49
+ @heredocs = extract_heredocs(processed_source.ast)
39
50
  processed_source.lines.each_with_index do |line, index|
40
- lineno = index + 1
41
-
42
51
  next unless line.end_with?(' ', "\t")
43
- next if skip_heredoc? && inside_heredoc?(heredoc_ranges, lineno)
44
52
 
45
- range = offense_range(lineno, line)
46
- add_offense(range) do |corrector|
53
+ process_line(line, index + 1)
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def process_line(line, lineno)
60
+ heredoc = find_heredoc(lineno)
61
+ return if skip_heredoc? && heredoc
62
+
63
+ range = offense_range(lineno, line)
64
+ add_offense(range) do |corrector|
65
+ if heredoc
66
+ corrector.wrap(range, "\#{'", "'}") unless static?(heredoc)
67
+ else
47
68
  corrector.remove(range)
48
69
  end
49
70
  end
50
71
  end
51
72
 
52
- private
73
+ def static?(heredoc)
74
+ heredoc.loc.expression.source.end_with? "'"
75
+ end
53
76
 
54
77
  def skip_heredoc?
55
78
  cop_config.fetch('AllowInHeredoc', false)
56
79
  end
57
80
 
58
- def inside_heredoc?(heredoc_ranges, line_number)
59
- heredoc_ranges.any? { |r| r.include?(line_number) }
81
+ def find_heredoc(line_number)
82
+ @heredocs.each { |node, r| return node if r.include?(line_number) }
83
+ nil
60
84
  end
61
85
 
62
- def extract_heredoc_ranges(ast)
86
+ def extract_heredocs(ast)
63
87
  return [] unless ast
64
88
 
65
89
  ast.each_node(:str, :dstr, :xstr).select(&:heredoc?).map do |node|
66
90
  body = node.location.heredoc_body
67
- (body.first_line...body.last_line)
91
+ [node, body.first_line...body.last_line]
68
92
  end
69
93
  end
70
94
 
@@ -15,6 +15,8 @@ module RuboCop
15
15
  #
16
16
  # # good
17
17
  # # With parentheses, there's no ambiguity.
18
+ # some_method(a { |val| puts val })
19
+ # # or (different meaning)
18
20
  # some_method(a) { |val| puts val }
19
21
  #
20
22
  # # good
@@ -47,7 +47,24 @@ module RuboCop
47
47
  regexp_node.source_range.begin_pos == diagnostic.location.begin_pos
48
48
  end
49
49
 
50
- node.parent
50
+ find_offense_node(node.parent, node)
51
+ end
52
+
53
+ def find_offense_node(node, regexp_receiver)
54
+ return node unless node.parent
55
+
56
+ if node.parent.send_type? || method_chain_to_regexp_receiver?(node, regexp_receiver)
57
+ node = find_offense_node(node.parent, regexp_receiver)
58
+ end
59
+
60
+ node
61
+ end
62
+
63
+ def method_chain_to_regexp_receiver?(node, regexp_receiver)
64
+ return false unless (parent = node.parent)
65
+ return false unless (parent_receiver = parent.receiver)
66
+
67
+ parent.parent && parent_receiver.receiver == regexp_receiver
51
68
  end
52
69
  end
53
70
  end
@@ -32,6 +32,9 @@ module RuboCop
32
32
  def on_sym(node)
33
33
  return unless boolean_symbol?(node)
34
34
 
35
+ parent = node.parent
36
+ return if parent&.array_type? && parent&.percent_literal?(:symbol)
37
+
35
38
  add_offense(node, message: format(MSG, boolean: node.value)) do |corrector|
36
39
  autocorrect(corrector, node)
37
40
  end
@@ -37,7 +37,7 @@ module RuboCop
37
37
 
38
38
  RESTRICT_ON_SEND = %i[
39
39
  debugger byebug remote_byebug pry remote_pry pry_remote console rescue
40
- save_and_open_page save_and_open_screenshot save_screenshot irb
40
+ save_and_open_page save_and_open_screenshot irb
41
41
  ].freeze
42
42
 
43
43
  def_node_matcher :kernel?, <<~PATTERN
@@ -53,8 +53,7 @@ module RuboCop
53
53
  {:pry :remote_pry :pry_remote :console} ...)
54
54
  (send (const {nil? (cbase)} :Pry) :rescue ...)
55
55
  (send nil? {:save_and_open_page
56
- :save_and_open_screenshot
57
- :save_screenshot} ...)}
56
+ :save_and_open_screenshot} ...)}
58
57
  PATTERN
59
58
 
60
59
  def_node_matcher :binding_irb_call?, <<~PATTERN
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # This cop checks for duplicate elements in Regexp character classes.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # r = /[xyx]/
12
+ #
13
+ # # bad
14
+ # r = /[0-9x0-9]/
15
+ #
16
+ # # good
17
+ # r = /[xy]/
18
+ #
19
+ # # good
20
+ # r = /[0-9x]/
21
+ class DuplicateRegexpCharacterClassElement < Base
22
+ include RangeHelp
23
+ extend AutoCorrector
24
+
25
+ MSG_REPEATED_ELEMENT = 'Duplicate element inside regexp character class'
26
+
27
+ def on_regexp(node)
28
+ each_repeated_character_class_element_loc(node) do |loc|
29
+ add_offense(loc, message: MSG_REPEATED_ELEMENT) do |corrector|
30
+ corrector.remove(loc)
31
+ end
32
+ end
33
+ end
34
+
35
+ def each_repeated_character_class_element_loc(node)
36
+ node.parsed_tree&.each_expression do |expr|
37
+ next if expr.type != :set || expr.token == :intersection
38
+
39
+ seen = Set.new
40
+
41
+ expr.expressions.each do |child|
42
+ next if within_interpolation?(node, child)
43
+
44
+ child_source = child.to_s
45
+
46
+ yield child.expression if seen.include?(child_source)
47
+
48
+ seen << child_source
49
+ end
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ # Since we blank interpolations with a space for every char of the interpolation, we would
56
+ # mark every space (except the first) as duplicate if we do not skip regexp_parser nodes
57
+ # that are within an interpolation.
58
+ def within_interpolation?(node, child)
59
+ parse_tree_child_loc = child.expression
60
+
61
+ interpolation_locs(node).any? { |il| il.overlaps?(parse_tree_child_loc) }
62
+ end
63
+
64
+ def interpolation_locs(node)
65
+ @interpolation_locs ||= {}
66
+
67
+ # Cache by loc, not by regexp content, as content can be repeated in multiple patterns
68
+ key = node.loc
69
+
70
+ @interpolation_locs[key] ||= node.children.select(&:begin_type?).map do |interpolation|
71
+ interpolation.loc.expression
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -3,10 +3,14 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- # This cop checks for odd else block layout - like
7
- # having an expression on the same line as the else keyword,
6
+ # This cop checks for odd `else` block layout - like
7
+ # having an expression on the same line as the `else` keyword,
8
8
  # which is usually a mistake.
9
9
  #
10
+ # Its auto-correction tweaks layout to keep the syntax. So, this auto-correction
11
+ # is compatible correction for bad case syntax, but if your code makes a mistake
12
+ # with `elsif` and `else`, you will have to correct it manually.
13
+ #
10
14
  # @example
11
15
  #
12
16
  # # bad
@@ -21,13 +25,25 @@ module RuboCop
21
25
  #
22
26
  # # good
23
27
  #
28
+ # # This code is compatible with the bad case. It will be auto-corrected like this.
24
29
  # if something
25
30
  # # ...
26
31
  # else
27
32
  # do_this
28
33
  # do_that
29
34
  # end
35
+ #
36
+ # # This code is incompatible with the bad case.
37
+ # # If `do_this` is a condition, `elsif` should be used instead of `else`.
38
+ # if something
39
+ # # ...
40
+ # elsif do_this
41
+ # do_that
42
+ # end
30
43
  class ElseLayout < Base
44
+ include RangeHelp
45
+ extend AutoCorrector
46
+
31
47
  MSG = 'Odd `else` layout detected. Did you mean to use `elsif`?'
32
48
 
33
49
  def on_if(node)
@@ -58,7 +74,17 @@ module RuboCop
58
74
  return unless first_else
59
75
  return unless first_else.source_range.line == node.loc.else.line
60
76
 
61
- add_offense(first_else)
77
+ add_offense(first_else) do |corrector|
78
+ autocorrect(corrector, node, first_else)
79
+ end
80
+ end
81
+
82
+ def autocorrect(corrector, node, first_else)
83
+ corrector.insert_after(node.loc.else, "\n")
84
+
85
+ blank_range = range_between(node.loc.else.end_pos, first_else.loc.expression.begin_pos)
86
+ indentation = indent(node.else_branch.children[1])
87
+ corrector.replace(blank_range, indentation)
62
88
  end
63
89
  end
64
90
  end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # This cop checks for blocks without a body.
7
+ # Such empty blocks are typically an oversight or we should provide a comment
8
+ # be clearer what we're aiming for.
9
+ #
10
+ # @example
11
+ # # bad
12
+ # items.each { |item| }
13
+ #
14
+ # # good
15
+ # items.each { |item| puts item }
16
+ #
17
+ # @example AllowComments: true (default)
18
+ # # good
19
+ # items.each do |item|
20
+ # # TODO: implement later (inner comment)
21
+ # end
22
+ #
23
+ # items.each { |item| } # TODO: implement later (inline comment)
24
+ #
25
+ # @example AllowComments: false
26
+ # # bad
27
+ # items.each do |item|
28
+ # # TODO: implement later (inner comment)
29
+ # end
30
+ #
31
+ # items.each { |item| } # TODO: implement later (inline comment)
32
+ #
33
+ class EmptyBlock < Base
34
+ MSG = 'Empty block detected.'
35
+
36
+ def on_block(node)
37
+ return if node.body
38
+ return if cop_config['AllowComments'] && allow_comment?(node)
39
+
40
+ add_offense(node)
41
+ end
42
+
43
+ private
44
+
45
+ def allow_comment?(node)
46
+ return false unless processed_source.contains_comment?(node.source_range)
47
+
48
+ line_comment = processed_source.comment_at_line(node.source_range.line)
49
+ !line_comment || !comment_disables_cop?(line_comment.loc.expression.source)
50
+ end
51
+
52
+ def comment_disables_cop?(comment)
53
+ regexp_pattern = "# rubocop : (disable|todo) ([^,],)* (all|#{cop_name})"
54
+ Regexp.new(regexp_pattern.gsub(' ', '\s*')).match?(comment)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -3,8 +3,14 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- # This cop looks for uses of flip-flop operator.
7
- # flip-flop operator is deprecated since Ruby 2.6.0.
6
+ # This cop looks for uses of flip-flop operator
7
+ # based on the Ruby Style Guide.
8
+ #
9
+ # Here is the history of flip-flops in Ruby.
10
+ # flip-flop operator is deprecated in Ruby 2.6.0 and
11
+ # the deprecation has been reverted by Ruby 2.7.0 and
12
+ # backported to Ruby 2.6.
13
+ # See: https://bugs.ruby-lang.org/issues/5400
8
14
  #
9
15
  # @example
10
16
  # # bad
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Prefer using `Hash#compare_by_identity` than using `object_id` for hash keys.
7
+ #
8
+ # This cop is marked as unsafe as a hash possibly can contain other keys
9
+ # besides `object_id`s.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # hash = {}
14
+ # hash[foo.object_id] = :bar
15
+ # hash.key?(baz.object_id)
16
+ #
17
+ # # good
18
+ # hash = {}.compare_by_identity
19
+ # hash[foo] = :bar
20
+ # hash.key?(baz)
21
+ #
22
+ class HashCompareByIdentity < Base
23
+ RESTRICT_ON_SEND = %i[key? has_key? fetch [] []=].freeze
24
+
25
+ MSG = 'Use `Hash#compare_by_identity` instead of using `object_id` for keys.'
26
+
27
+ def_node_matcher :id_as_hash_key?, <<~PATTERN
28
+ (send _ {:key? :has_key? :fetch :[] :[]=} (send _ :object_id) ...)
29
+ PATTERN
30
+
31
+ def on_send(node)
32
+ add_offense(node) if id_as_hash_key?(node)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -31,12 +31,18 @@ module RuboCop
31
31
  return if special_keyword?(final_node)
32
32
  return unless prints_as_self?(final_node)
33
33
 
34
+ # %W and %I split the content into words before expansion
35
+ # treating each interpolation as a word component, so
36
+ # interpolation should not be removed if the expanded value
37
+ # contains a space character.
38
+ expanded_value = autocorrected_value(final_node)
39
+ return if in_array_percent_literal?(begin_node) &&
40
+ /\s/.match?(expanded_value)
41
+
34
42
  add_offense(final_node) do |corrector|
35
43
  return if final_node.dstr_type? # nested, fixed in next iteration
36
44
 
37
- value = autocorrected_value(final_node)
38
-
39
- corrector.replace(final_node.parent, value)
45
+ corrector.replace(final_node.parent, expanded_value)
40
46
  end
41
47
  end
42
48
 
@@ -92,6 +98,14 @@ module RuboCop
92
98
  (COMPOSITE.include?(node.type) &&
93
99
  node.children.all? { |child| prints_as_self?(child) })
94
100
  end
101
+
102
+ def in_array_percent_literal?(node)
103
+ parent = node.parent
104
+ return false unless parent.dstr_type? || parent.dsym_type?
105
+
106
+ grandparent = parent.parent
107
+ grandparent&.array_type? && grandparent&.percent_literal?
108
+ end
95
109
  end
96
110
  end
97
111
  end