rubocop 1.67.0 → 1.68.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +40 -0
  4. data/lib/rubocop/cached_data.rb +12 -4
  5. data/lib/rubocop/cli/command/execute_runner.rb +1 -1
  6. data/lib/rubocop/cli/command/version.rb +2 -2
  7. data/lib/rubocop/cop/autocorrect_logic.rb +22 -2
  8. data/lib/rubocop/cop/correctors/alignment_corrector.rb +1 -12
  9. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +10 -0
  10. data/lib/rubocop/cop/layout/leading_comment_space.rb +29 -1
  11. data/lib/rubocop/cop/layout/space_before_brackets.rb +5 -5
  12. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +4 -0
  13. data/lib/rubocop/cop/lint/duplicate_branch.rb +39 -4
  14. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +7 -0
  15. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +9 -0
  16. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +3 -1
  17. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +88 -0
  18. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +4 -1
  19. data/lib/rubocop/cop/mixin/check_line_breakable.rb +10 -0
  20. data/lib/rubocop/cop/mixin/endless_method_rewriter.rb +24 -0
  21. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +3 -1
  22. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  23. data/lib/rubocop/cop/offense.rb +2 -3
  24. data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +79 -0
  25. data/lib/rubocop/cop/style/bitwise_predicate.rb +100 -0
  26. data/lib/rubocop/cop/style/block_delimiters.rb +17 -2
  27. data/lib/rubocop/cop/style/combinable_defined.rb +115 -0
  28. data/lib/rubocop/cop/style/endless_method.rb +1 -14
  29. data/lib/rubocop/cop/style/guard_clause.rb +14 -1
  30. data/lib/rubocop/cop/style/keyword_arguments_merging.rb +67 -0
  31. data/lib/rubocop/cop/style/map_into_array.rb +6 -1
  32. data/lib/rubocop/cop/style/multiple_comparison.rb +28 -39
  33. data/lib/rubocop/cop/style/redundant_line_continuation.rb +20 -2
  34. data/lib/rubocop/cop/style/redundant_parentheses.rb +8 -10
  35. data/lib/rubocop/cop/style/safe_navigation.rb +12 -0
  36. data/lib/rubocop/cop/style/safe_navigation_chain_length.rb +52 -0
  37. data/lib/rubocop/cop/style/ternary_parentheses.rb +25 -4
  38. data/lib/rubocop/cop/variable_force/assignment.rb +18 -3
  39. data/lib/rubocop/cop/variable_force/branch.rb +1 -1
  40. data/lib/rubocop/cop/variable_force/variable.rb +5 -1
  41. data/lib/rubocop/cop/variable_force/variable_table.rb +2 -2
  42. data/lib/rubocop/cops_documentation_generator.rb +11 -9
  43. data/lib/rubocop/formatter/disabled_config_formatter.rb +1 -1
  44. data/lib/rubocop/runner.rb +16 -8
  45. data/lib/rubocop/target_ruby.rb +1 -1
  46. data/lib/rubocop/version.rb +27 -8
  47. data/lib/rubocop.rb +8 -0
  48. metadata +15 -8
@@ -43,11 +43,10 @@ module RuboCop
43
43
  # @!attribute [r] cop_name
44
44
  #
45
45
  # @return [String]
46
- # a cop class name without department.
47
- # i.e. type of the violation.
46
+ # the cop name as a String for which this offense is for.
48
47
  #
49
48
  # @example
50
- # 'LineLength'
49
+ # 'Layout/LineLength'
51
50
  attr_reader :cop_name
52
51
 
53
52
  # @api private
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Looks for endless methods inside operations of lower precedence (`and`, `or`, and
7
+ # modifier forms of `if`, `unless`, `while`, `until`) that are ambiguous due to
8
+ # lack of parentheses. This may lead to unexpected behavior as the code may appear
9
+ # to use these keywords as part of the method but in fact they modify
10
+ # the method definition itself.
11
+ #
12
+ # In these cases, using a normal method definition is more clear.
13
+ #
14
+ # @example
15
+ #
16
+ # # bad
17
+ # def foo = true if bar
18
+ #
19
+ # # good - using a non-endless method is more explicit
20
+ # def foo
21
+ # true
22
+ # end if bar
23
+ #
24
+ # # ok - method body is explicit
25
+ # def foo = (true if bar)
26
+ #
27
+ # # ok - method definition is explicit
28
+ # (def foo = true) if bar
29
+ class AmbiguousEndlessMethodDefinition < Base
30
+ extend TargetRubyVersion
31
+ extend AutoCorrector
32
+ include EndlessMethodRewriter
33
+ include RangeHelp
34
+
35
+ minimum_target_ruby_version 3.0
36
+
37
+ MSG = 'Avoid using `%<keyword>s` statements with endless methods.'
38
+
39
+ # @!method ambiguous_endless_method_body(node)
40
+ def_node_matcher :ambiguous_endless_method_body, <<~PATTERN
41
+ ^${
42
+ (if _ <def _>)
43
+ ({and or} def _)
44
+ ({while until} _ def)
45
+ }
46
+ PATTERN
47
+
48
+ def on_def(node)
49
+ return unless node.endless?
50
+
51
+ operation = ambiguous_endless_method_body(node)
52
+ return unless operation
53
+
54
+ return unless modifier_form?(operation)
55
+
56
+ add_offense(operation, message: format(MSG, keyword: keyword(operation))) do |corrector|
57
+ correct_to_multiline(corrector, node)
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def modifier_form?(operation)
64
+ return true if operation.and_type? || operation.or_type?
65
+
66
+ operation.modifier_form?
67
+ end
68
+
69
+ def keyword(operation)
70
+ if operation.respond_to?(:keyword)
71
+ operation.keyword
72
+ else
73
+ operation.operator
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Prefer bitwise predicate methods over direct comparison operations.
7
+ #
8
+ # @safety
9
+ # This cop is unsafe, as it can produce false positives if the receiver
10
+ # is not an `Integer` object.
11
+ #
12
+ # @example
13
+ #
14
+ # # bad - checks any set bits
15
+ # (variable & flags).positive?
16
+ #
17
+ # # good
18
+ # variable.anybits?(flags)
19
+ #
20
+ # # bad - checks all set bits
21
+ # (variable & flags) == flags
22
+ #
23
+ # # good
24
+ # variable.allbits?(flags)
25
+ #
26
+ # # bad - checks no set bits
27
+ # (variable & flags).zero?
28
+ #
29
+ # # good
30
+ # variable.nobits?(flags)
31
+ #
32
+ class BitwisePredicate < Base
33
+ extend AutoCorrector
34
+ extend TargetRubyVersion
35
+
36
+ MSG = 'Replace with `%<preferred>s` for comparison with bit flags.'
37
+ RESTRICT_ON_SEND = %i[!= == > >= positive? zero?].freeze
38
+
39
+ minimum_target_ruby_version 2.5
40
+
41
+ # @!method anybits?(node)
42
+ def_node_matcher :anybits?, <<~PATTERN
43
+ {
44
+ (send #bit_operation? :positive?)
45
+ (send #bit_operation? :> (int 0))
46
+ (send #bit_operation? :>= (int 1))
47
+ (send #bit_operation? :!= (int 0))
48
+ }
49
+ PATTERN
50
+
51
+ # @!method allbits?(node)
52
+ def_node_matcher :allbits?, <<~PATTERN
53
+ {
54
+ (send (begin (send _ :& _flags)) :== _flags)
55
+ (send (begin (send _flags :& _)) :== _flags)
56
+ }
57
+ PATTERN
58
+
59
+ # @!method nobits?(node)
60
+ def_node_matcher :nobits?, <<~PATTERN
61
+ {
62
+ (send #bit_operation? :zero?)
63
+ (send #bit_operation? :== (int 0))
64
+ }
65
+ PATTERN
66
+
67
+ # @!method bit_operation?(node)
68
+ def_node_matcher :bit_operation?, <<~PATTERN
69
+ (begin
70
+ (send _ :& _))
71
+ PATTERN
72
+
73
+ def on_send(node)
74
+ return unless node.receiver.begin_type?
75
+ return unless (preferred_method = preferred_method(node))
76
+
77
+ bit_operation = node.receiver.children.first
78
+ lhs, _operator, rhs = *bit_operation
79
+ preferred = "#{lhs.source}.#{preferred_method}(#{rhs.source})"
80
+
81
+ add_offense(node, message: format(MSG, preferred: preferred)) do |corrector|
82
+ corrector.replace(node, preferred)
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ def preferred_method(node)
89
+ if anybits?(node)
90
+ 'anybits?'
91
+ elsif allbits?(node)
92
+ 'allbits?'
93
+ elsif nobits?(node)
94
+ 'nobits?'
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -303,13 +303,28 @@ module RuboCop
303
303
 
304
304
  def move_comment_before_block(corrector, comment, block_node, closing_brace)
305
305
  range = block_node.chained? ? end_of_chain(block_node.parent).source_range : closing_brace
306
+
307
+ # It is possible that there is code between the block and the comment
308
+ # which needs to be preserved and trimmed.
309
+ pre_comment_range = source_range_before_comment(range, comment)
310
+
306
311
  corrector.remove(range_with_surrounding_space(comment.source_range, side: :right))
307
- remove_trailing_whitespace(corrector, range, comment)
308
- corrector.insert_after(range, "\n")
312
+ remove_trailing_whitespace(corrector, pre_comment_range, comment)
313
+ corrector.insert_after(pre_comment_range, "\n")
309
314
 
310
315
  corrector.insert_before(block_node, "#{comment.text}\n")
311
316
  end
312
317
 
318
+ def source_range_before_comment(range, comment)
319
+ range = range.end.join(comment.source_range.begin)
320
+
321
+ # End the range before any whitespace that precedes the comment
322
+ trailing_whitespace_count = range.source[/\s+\z/]&.length
323
+ range = range.adjust(end_pos: -trailing_whitespace_count) if trailing_whitespace_count
324
+
325
+ range
326
+ end
327
+
313
328
  def end_of_chain(node)
314
329
  return end_of_chain(node.block_node) if with_block?(node)
315
330
  return node unless node.chained?
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for multiple `defined?` calls joined by `&&` that can be combined
7
+ # into a single `defined?`.
8
+ #
9
+ # When checking that a nested constant or chained method is defined, it is
10
+ # not necessary to check each ancestor or component of the chain.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # defined?(Foo) && defined?(Foo::Bar) && defined?(Foo::Bar::Baz)
15
+ #
16
+ # # good
17
+ # defined?(Foo::Bar::Baz)
18
+ #
19
+ # # bad
20
+ # defined?(foo) && defined?(foo.bar) && defined?(foo.bar.baz)
21
+ #
22
+ # # good
23
+ # defined?(foo.bar.baz)
24
+ class CombinableDefined < Base
25
+ extend AutoCorrector
26
+ include RangeHelp
27
+
28
+ MSG = 'Combine nested `defined?` calls.'
29
+ OPERATORS = %w[&& and].freeze
30
+
31
+ def on_and(node)
32
+ # Only register an offense if all `&&` terms are `defined?` calls
33
+ return unless (terms = terms(node)).all?(&:defined_type?)
34
+
35
+ calls = defined_calls(terms)
36
+ namespaces = namespaces(calls)
37
+
38
+ calls.each do |call|
39
+ next unless namespaces.any?(call)
40
+
41
+ add_offense(node) do |corrector|
42
+ remove_term(corrector, call)
43
+ end
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def terms(node)
50
+ node.each_descendant.select do |descendant|
51
+ descendant.parent.and_type? && !descendant.and_type?
52
+ end
53
+ end
54
+
55
+ def defined_calls(nodes)
56
+ nodes.filter_map do |defined_node|
57
+ subject = defined_node.first_argument
58
+ subject if subject.const_type? || subject.call_type?
59
+ end
60
+ end
61
+
62
+ def namespaces(nodes)
63
+ nodes.filter_map do |node|
64
+ if node.respond_to?(:namespace)
65
+ node.namespace
66
+ elsif node.respond_to?(:receiver)
67
+ node.receiver
68
+ end
69
+ end
70
+ end
71
+
72
+ def remove_term(corrector, term)
73
+ term = term.parent until term.parent.and_type?
74
+ range = if term == term.parent.children.last
75
+ rhs_range_to_remove(term)
76
+ else
77
+ lhs_range_to_remove(term)
78
+ end
79
+
80
+ corrector.remove(range)
81
+ end
82
+
83
+ # If the redundant `defined?` node is the LHS of an `and` node,
84
+ # the term as well as the subsequent `&&`/`and` operator will be removed.
85
+ def lhs_range_to_remove(term)
86
+ source = @processed_source.buffer.source
87
+
88
+ pos = term.source_range.end_pos
89
+ pos += 1 until source[..pos].end_with?(*OPERATORS)
90
+
91
+ range_with_surrounding_space(
92
+ range: term.source_range.with(end_pos: pos + 1),
93
+ side: :right,
94
+ newlines: false
95
+ )
96
+ end
97
+
98
+ # If the redundant `defined?` node is the RHS of an `and` node,
99
+ # the term as well as the preceding `&&`/`and` operator will be removed.
100
+ def rhs_range_to_remove(term)
101
+ source = @processed_source.buffer.source
102
+
103
+ pos = term.source_range.begin_pos
104
+ pos -= 1 until source[pos, 3].start_with?(*OPERATORS)
105
+
106
+ range_with_surrounding_space(
107
+ range: term.source_range.with(begin_pos: pos - 1),
108
+ side: :right,
109
+ newlines: false
110
+ )
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -48,6 +48,7 @@ module RuboCop
48
48
  #
49
49
  class EndlessMethod < Base
50
50
  include ConfigurableEnforcedStyle
51
+ include EndlessMethodRewriter
51
52
  extend TargetRubyVersion
52
53
  extend AutoCorrector
53
54
 
@@ -81,20 +82,6 @@ module RuboCop
81
82
 
82
83
  add_offense(node) { |corrector| correct_to_multiline(corrector, node) }
83
84
  end
84
-
85
- def correct_to_multiline(corrector, node)
86
- replacement = <<~RUBY.strip
87
- def #{node.method_name}#{arguments(node)}
88
- #{node.body.source}
89
- end
90
- RUBY
91
-
92
- corrector.replace(node, replacement)
93
- end
94
-
95
- def arguments(node, missing = '')
96
- node.arguments.any? ? node.arguments.source : missing
97
- end
98
85
  end
99
86
  end
100
87
  end
@@ -283,7 +283,8 @@ module RuboCop
283
283
  end
284
284
 
285
285
  def accepted_if?(node, ending)
286
- return true if node.modifier_form? || node.ternary? || node.elsif_conditional?
286
+ return true if node.modifier_form? || node.ternary? || node.elsif_conditional? ||
287
+ assigned_lvar_used_in_if_branch?(node)
287
288
 
288
289
  if ending
289
290
  node.else?
@@ -292,6 +293,18 @@ module RuboCop
292
293
  end
293
294
  end
294
295
 
296
+ def assigned_lvar_used_in_if_branch?(node)
297
+ return false unless (if_branch = node.if_branch)
298
+
299
+ assigned_lvars_in_condition = node.condition.each_descendant(:lvasgn).map do |lvasgn|
300
+ lvar_name, = *lvasgn
301
+ lvar_name.to_s
302
+ end
303
+ used_lvars_in_branch = if_branch.each_descendant(:lvar).map(&:source) || []
304
+
305
+ (assigned_lvars_in_condition & used_lvars_in_branch).any?
306
+ end
307
+
295
308
  def remove_whole_lines(corrector, range)
296
309
  corrector.remove(range_by_whole_lines(range, include_final_newline: true))
297
310
  end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # When passing an existing hash as keyword arguments, provide additional arguments
7
+ # directly rather than using `merge`.
8
+ #
9
+ # Providing arguments directly is more performant, than using `merge`, and
10
+ # also leads to a shorter and simpler code.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # some_method(**opts.merge(foo: true))
15
+ # some_method(**opts.merge(other_opts))
16
+ #
17
+ # # good
18
+ # some_method(**opts, foo: true)
19
+ # some_method(**opts, **other_opts)
20
+ #
21
+ class KeywordArgumentsMerging < Base
22
+ extend AutoCorrector
23
+
24
+ MSG = 'Provide additional arguments directly rather than using `merge`.'
25
+
26
+ # @!method merge_kwargs?(node)
27
+ def_node_matcher :merge_kwargs?, <<~PATTERN
28
+ (send _ _
29
+ ...
30
+ (hash
31
+ (kwsplat
32
+ $(send $_ :merge $...))
33
+ ...))
34
+ PATTERN
35
+
36
+ def on_kwsplat(node)
37
+ return unless (ancestor = node.parent&.parent)
38
+
39
+ merge_kwargs?(ancestor) do |merge_node, hash_node, other_hash_node|
40
+ add_offense(merge_node) do |corrector|
41
+ autocorrect(corrector, node, hash_node, other_hash_node)
42
+ end
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def autocorrect(corrector, kwsplat_node, hash_node, other_hash_node)
49
+ other_hash_node_replacement =
50
+ other_hash_node.map do |node|
51
+ if node.hash_type?
52
+ if node.braces?
53
+ node.source[1...-1]
54
+ else
55
+ node.source
56
+ end
57
+ else
58
+ "**#{node.source}"
59
+ end
60
+ end.join(', ')
61
+
62
+ corrector.replace(kwsplat_node, "**#{hash_node.source}, #{other_hash_node_replacement}")
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -63,12 +63,17 @@ module RuboCop
63
63
 
64
64
  MSG = 'Use `%<new_method_name>s` instead of `each` to map elements into an array.'
65
65
 
66
+ # @!method suitable_argument_node?(node)
67
+ def_node_matcher :suitable_argument_node?, <<-PATTERN
68
+ !{splat forwarded-restarg forwarded-args (hash (forwarded-kwrestarg)) (block-pass nil?)}
69
+ PATTERN
70
+
66
71
  # @!method each_block_with_push?(node)
67
72
  def_node_matcher :each_block_with_push?, <<-PATTERN
68
73
  [
69
74
  ^({begin kwbegin block} ...)
70
75
  ({block numblock} (send !{nil? self} :each) _
71
- (send (lvar _) {:<< :push :append} {send lvar begin}))
76
+ (send (lvar _) {:<< :push :append} #suitable_argument_node?))
72
77
  ]
73
78
  PATTERN
74
79
 
@@ -55,25 +55,19 @@ module RuboCop
55
55
  MSG = 'Avoid comparing a variable with multiple items ' \
56
56
  'in a conditional, use `Array#include?` instead.'
57
57
 
58
- def on_new_investigation
59
- reset_comparison
60
- end
61
-
62
58
  def on_or(node)
63
59
  root_of_or_node = root_of_or_node(node)
64
-
65
60
  return unless node == root_of_or_node
66
- return unless nested_variable_comparison?(root_of_or_node)
67
- return if @allowed_method_comparison
68
- return if @compared_elements.size < comparisons_threshold
61
+ return unless nested_comparison?(node)
62
+
63
+ return unless (variable, values = find_offending_var(node))
64
+ return if values.size < comparisons_threshold
69
65
 
70
66
  add_offense(node) do |corrector|
71
- elements = @compared_elements.join(', ')
72
- prefer_method = "[#{elements}].include?(#{variables_in_node(node).first})"
67
+ elements = values.map(&:source).join(', ')
68
+ prefer_method = "[#{elements}].include?(#{variable_name(variable)})"
73
69
 
74
70
  corrector.replace(node, prefer_method)
75
-
76
- reset_comparison
77
71
  end
78
72
  end
79
73
 
@@ -92,32 +86,25 @@ module RuboCop
92
86
  (send $_ :== $lvar)
93
87
  PATTERN
94
88
 
95
- def nested_variable_comparison?(node)
96
- return false unless nested_comparison?(node)
97
-
98
- variables_in_node(node).count == 1
99
- end
100
-
101
- def variables_in_node(node)
89
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
90
+ def find_offending_var(node, variables = Set.new, values = [])
102
91
  if node.or_type?
103
- node.node_parts.flat_map { |node_part| variables_in_node(node_part) }.uniq
104
- else
105
- variables_in_simple_node(node)
106
- end
107
- end
92
+ find_offending_var(node.lhs, variables, values)
93
+ find_offending_var(node.rhs, variables, values)
94
+ elsif simple_double_comparison?(node)
95
+ return
96
+ elsif (var, obj = simple_comparison?(node))
97
+ return if allow_method_comparison? && obj.send_type?
108
98
 
109
- def variables_in_simple_node(node)
110
- simple_double_comparison?(node) do |var1, var2|
111
- return [variable_name(var1), variable_name(var2)]
112
- end
113
- if (var, obj = simple_comparison_lhs?(node)) || (obj, var = simple_comparison_rhs?(node))
114
- @allowed_method_comparison = true if allow_method_comparison? && obj.send_type?
115
- @compared_elements << obj.source
116
- return [variable_name(var)]
99
+ variables << var
100
+ return if variables.size > 1
101
+
102
+ values << obj
117
103
  end
118
104
 
119
- []
105
+ [variables.first, values] if variables.any?
120
106
  end
107
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
121
108
 
122
109
  def variable_name(node)
123
110
  node.children[0]
@@ -132,7 +119,14 @@ module RuboCop
132
119
  end
133
120
 
134
121
  def comparison?(node)
135
- simple_comparison_lhs?(node) || simple_comparison_rhs?(node) || nested_comparison?(node)
122
+ simple_comparison?(node) || nested_comparison?(node)
123
+ end
124
+
125
+ def simple_comparison?(node)
126
+ if (var, obj = simple_comparison_lhs?(node)) ||
127
+ (obj, var = simple_comparison_rhs?(node))
128
+ [var, obj]
129
+ end
136
130
  end
137
131
 
138
132
  def root_of_or_node(or_node)
@@ -145,11 +139,6 @@ module RuboCop
145
139
  end
146
140
  end
147
141
 
148
- def reset_comparison
149
- @compared_elements = []
150
- @allowed_method_comparison = false
151
- end
152
-
153
142
  def allow_method_comparison?
154
143
  cop_config.fetch('AllowMethodComparison', true)
155
144
  end
@@ -69,6 +69,8 @@ module RuboCop
69
69
  extend AutoCorrector
70
70
 
71
71
  MSG = 'Redundant line continuation.'
72
+ LINE_CONTINUATION = "\\\n"
73
+ LINE_CONTINUATION_PATTERN = /(\\\n)/.freeze
72
74
  ALLOWED_STRING_TOKENS = %i[tSTRING tSTRING_CONTENT].freeze
73
75
  ARGUMENT_TYPES = %i[
74
76
  kFALSE kNIL kSELF kTRUE tCONSTANT tCVAR tFLOAT tGVAR tIDENTIFIER tINTEGER tIVAR
@@ -79,7 +81,7 @@ module RuboCop
79
81
  def on_new_investigation
80
82
  return unless processed_source.ast
81
83
 
82
- each_match_range(processed_source.ast.source_range, /(\\\n)/) do |range|
84
+ each_match_range(processed_source.ast.source_range, LINE_CONTINUATION_PATTERN) do |range|
83
85
  next if require_line_continuation?(range)
84
86
  next unless redundant_line_continuation?(range)
85
87
 
@@ -87,6 +89,8 @@ module RuboCop
87
89
  corrector.remove_leading(range, 1)
88
90
  end
89
91
  end
92
+
93
+ inspect_eof_line_continuation
90
94
  end
91
95
 
92
96
  private
@@ -125,10 +129,24 @@ module RuboCop
125
129
  return true unless (node = find_node_for_line(range.last_line))
126
130
  return false if argument_newline?(node)
127
131
 
128
- source = node.parent ? node.parent.source : node.source
132
+ source = node.source
133
+ while (node = node.parent)
134
+ source = node.source
135
+ end
129
136
  parse(source.gsub("\\\n", "\n")).valid_syntax?
130
137
  end
131
138
 
139
+ def inspect_eof_line_continuation
140
+ return unless processed_source.raw_source.end_with?(LINE_CONTINUATION)
141
+
142
+ rindex = processed_source.raw_source.rindex(LINE_CONTINUATION)
143
+ line_continuation_range = range_between(rindex, rindex + 1)
144
+
145
+ add_offense(line_continuation_range) do |corrector|
146
+ corrector.remove_trailing(line_continuation_range, 1)
147
+ end
148
+ end
149
+
132
150
  def inside_string_literal?(range, token)
133
151
  ALLOWED_STRING_TOKENS.include?(token.type) && token.pos.overlaps?(range)
134
152
  end