rubocop 1.48.1 → 1.50.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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +19 -7
  4. data/lib/rubocop/cli/command/execute_runner.rb +7 -2
  5. data/lib/rubocop/cli.rb +6 -6
  6. data/lib/rubocop/config.rb +1 -1
  7. data/lib/rubocop/config_loader.rb +8 -8
  8. data/lib/rubocop/cop/autocorrect_logic.rb +28 -12
  9. data/lib/rubocop/cop/cop.rb +2 -2
  10. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +1 -1
  11. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +2 -2
  12. data/lib/rubocop/cop/internal_affairs/cop_description.rb +1 -1
  13. data/lib/rubocop/cop/internal_affairs/example_heredoc_delimiter.rb +2 -2
  14. data/lib/rubocop/cop/internal_affairs/inherit_deprecated_cop_class.rb +1 -1
  15. data/lib/rubocop/cop/internal_affairs/redundant_source_range.rb +29 -2
  16. data/lib/rubocop/cop/layout/class_structure.rb +1 -0
  17. data/lib/rubocop/cop/layout/empty_comment.rb +1 -1
  18. data/lib/rubocop/cop/layout/empty_lines.rb +1 -1
  19. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +2 -0
  20. data/lib/rubocop/cop/layout/end_alignment.rb +5 -1
  21. data/lib/rubocop/cop/layout/extra_spacing.rb +6 -1
  22. data/lib/rubocop/cop/layout/first_argument_indentation.rb +6 -1
  23. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +2 -2
  24. data/lib/rubocop/cop/layout/initial_indentation.rb +1 -1
  25. data/lib/rubocop/cop/layout/redundant_line_break.rb +6 -7
  26. data/lib/rubocop/cop/layout/space_before_first_arg.rb +1 -1
  27. data/lib/rubocop/cop/layout/space_inside_parens.rb +2 -2
  28. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +3 -3
  29. data/lib/rubocop/cop/lint/duplicate_match_pattern.rb +102 -0
  30. data/lib/rubocop/cop/lint/empty_interpolation.rb +1 -1
  31. data/lib/rubocop/cop/lint/nested_method_definition.rb +2 -2
  32. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +1 -1
  33. data/lib/rubocop/cop/lint/redundant_string_coercion.rb +35 -15
  34. data/lib/rubocop/cop/lint/to_enum_arguments.rb +7 -1
  35. data/lib/rubocop/cop/lint/unreachable_loop.rb +3 -3
  36. data/lib/rubocop/cop/lint/useless_method_definition.rb +10 -2
  37. data/lib/rubocop/cop/lint/void.rb +7 -3
  38. data/lib/rubocop/cop/metrics/block_nesting.rb +1 -1
  39. data/lib/rubocop/cop/metrics/class_length.rb +1 -0
  40. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  41. data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
  42. data/lib/rubocop/cop/mixin/hash_transform_method.rb +1 -1
  43. data/lib/rubocop/cop/mixin/statement_modifier.rb +1 -1
  44. data/lib/rubocop/cop/naming/ascii_identifiers.rb +1 -1
  45. data/lib/rubocop/cop/naming/inclusive_language.rb +23 -4
  46. data/lib/rubocop/cop/style/class_and_module_children.rb +1 -1
  47. data/lib/rubocop/cop/style/class_equality_comparison.rb +42 -9
  48. data/lib/rubocop/cop/style/copyright.rb +1 -1
  49. data/lib/rubocop/cop/style/data_inheritance.rb +75 -0
  50. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +2 -2
  51. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +1 -1
  52. data/lib/rubocop/cop/style/double_negation.rb +2 -2
  53. data/lib/rubocop/cop/style/file_empty.rb +3 -3
  54. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +1 -1
  55. data/lib/rubocop/cop/style/hash_except.rb +4 -4
  56. data/lib/rubocop/cop/style/hash_syntax.rb +4 -1
  57. data/lib/rubocop/cop/style/if_unless_modifier.rb +38 -12
  58. data/lib/rubocop/cop/style/map_to_hash.rb +4 -1
  59. data/lib/rubocop/cop/style/map_to_set.rb +4 -1
  60. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +3 -7
  61. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +43 -36
  62. data/lib/rubocop/cop/style/multiline_method_signature.rb +6 -3
  63. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -3
  64. data/lib/rubocop/cop/style/percent_q_literals.rb +1 -1
  65. data/lib/rubocop/cop/style/redundant_line_continuation.rb +167 -0
  66. data/lib/rubocop/cop/style/redundant_parentheses.rb +1 -1
  67. data/lib/rubocop/cop/style/redundant_percent_q.rb +1 -1
  68. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +2 -2
  69. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +1 -1
  70. data/lib/rubocop/cop/style/redundant_string_escape.rb +2 -3
  71. data/lib/rubocop/cop/style/sole_nested_conditional.rb +2 -2
  72. data/lib/rubocop/cop/style/struct_inheritance.rb +1 -1
  73. data/lib/rubocop/cop/style/trailing_body_on_class.rb +1 -0
  74. data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
  75. data/lib/rubocop/cop/style/unless_logical_operators.rb +1 -0
  76. data/lib/rubocop/cops_documentation_generator.rb +10 -3
  77. data/lib/rubocop/ext/regexp_node.rb +1 -1
  78. data/lib/rubocop/ext/regexp_parser.rb +1 -1
  79. data/lib/rubocop/formatter/simple_text_formatter.rb +1 -1
  80. data/lib/rubocop/options.rb +4 -1
  81. data/lib/rubocop/result_cache.rb +1 -1
  82. data/lib/rubocop/server/cache.rb +1 -1
  83. data/lib/rubocop/server/helper.rb +1 -1
  84. data/lib/rubocop/server/server_command/exec.rb +1 -1
  85. data/lib/rubocop/version.rb +1 -1
  86. data/lib/rubocop.rb +3 -0
  87. metadata +9 -6
@@ -111,7 +111,7 @@ module RuboCop
111
111
  return if comments.none?
112
112
 
113
113
  regexp = comment_regexp(arg_node)
114
- comments.any? { |comment| regexp.match?(comment) } || regexp.match?(comments.join)
114
+ comments.any?(regexp) || regexp.match?(comments.join)
115
115
  end
116
116
 
117
117
  def preceding_comment_blocks(node)
@@ -103,12 +103,12 @@ module RuboCop
103
103
  def find_def_node_from_ascendant(node)
104
104
  return unless (parent = node.parent)
105
105
  return parent if parent.def_type? || parent.defs_type?
106
- return node.parent.child_nodes.first if define_mehod?(parent)
106
+ return node.parent.child_nodes.first if define_method?(parent)
107
107
 
108
108
  find_def_node_from_ascendant(node.parent)
109
109
  end
110
110
 
111
- def define_mehod?(node)
111
+ def define_method?(node)
112
112
  return false unless node.block_type?
113
113
 
114
114
  child = node.child_nodes.first
@@ -6,9 +6,9 @@ module RuboCop
6
6
  # Prefer to use `File.empty?('path/to/file')` when checking if a file is empty.
7
7
  #
8
8
  # @safety
9
- # This cop's autocorrection is unsafe it because `File.size`, `File.read`,
10
- # and `File.binread` raise `ENOENT` exception when there is no file
11
- # corresponding to the path, while `File.empty?` does not raise an exception.
9
+ # This cop is unsafe, because `File.size`, `File.read`, and `File.binread`
10
+ # raise `ENOENT` exception when there is no file corresponding to the path,
11
+ # while `File.empty?` does not raise an exception.
12
12
  #
13
13
  # @example
14
14
  # # bad
@@ -148,7 +148,7 @@ module RuboCop
148
148
  end
149
149
 
150
150
  def frozen_string_literal_comment(processed_source)
151
- processed_source.find_token do |token|
151
+ processed_source.tokens.find do |token|
152
152
  token.text.start_with?(FROZEN_STRING_LITERAL)
153
153
  end
154
154
  end
@@ -122,8 +122,8 @@ module RuboCop
122
122
  end
123
123
 
124
124
  def safe_to_register_offense?(block, except_key)
125
- extracted = extract_body_if_nagated(block.body)
126
- if extracted.method?('in?') || extracted.method?('include?') || \
125
+ extracted = extract_body_if_negated(block.body)
126
+ if extracted.method?('in?') || extracted.method?('include?') ||
127
127
  extracted.method?('exclude?')
128
128
  return true
129
129
  end
@@ -132,7 +132,7 @@ module RuboCop
132
132
  except_key.sym_type? || except_key.str_type?
133
133
  end
134
134
 
135
- def extract_body_if_nagated(body)
135
+ def extract_body_if_negated(body)
136
136
  return body unless body.method?('!')
137
137
 
138
138
  body.receiver
@@ -161,7 +161,7 @@ module RuboCop
161
161
 
162
162
  def except_key(node)
163
163
  key_argument = node.argument_list.first.source
164
- body = extract_body_if_nagated(node.body)
164
+ body = extract_body_if_negated(node.body)
165
165
  lhs, _method_name, rhs = *body
166
166
  return if [lhs, rhs].map(&:source).none?(key_argument)
167
167
 
@@ -88,6 +88,9 @@ module RuboCop
88
88
  # {foo: foo, bar: bar}
89
89
  #
90
90
  # # good
91
+ # {foo: foo, bar:}
92
+ #
93
+ # # good
91
94
  # {foo:, bar:}
92
95
  #
93
96
  # @example EnforcedShorthandSyntax: consistent
@@ -207,7 +210,7 @@ module RuboCop
207
210
  return true if /\A[_a-z]\w*[?!]?\z/i.match?(sym_name)
208
211
 
209
212
  # For more complicated hash keys, let the parser validate the syntax.
210
- ProcessedSource.new("{ #{sym_name}: :foo }", target_ruby_version).valid_syntax?
213
+ parse("{ #{sym_name}: :foo }").valid_syntax?
211
214
  end
212
215
 
213
216
  def check(pairs, delim, msg)
@@ -11,6 +11,18 @@ module RuboCop
11
11
  # cop. The tab size is configured in the `IndentationWidth` of the
12
12
  # `Layout/IndentationStyle` cop.
13
13
  #
14
+ # One-line pattern matching is always allowed. To ensure that there are few cases
15
+ # where the match variable is not used, and to prevent oversights. The variable `x`
16
+ # becomes undefined and raises `NameError` when the following example is changed to
17
+ # the modifier form:
18
+ #
19
+ # [source,ruby]
20
+ # ----
21
+ # if [42] in [x]
22
+ # x # `x` is undefined when using modifier form.
23
+ # end
24
+ # ----
25
+ #
14
26
  # NOTE: It is allowed when `defined?` argument has an undefined value,
15
27
  # because using the modifier form causes the following incompatibility:
16
28
  #
@@ -66,14 +78,10 @@ module RuboCop
66
78
  end
67
79
 
68
80
  def on_if(node)
69
- return if defined_nodes(node).any? { |n| defined_argument_is_undefined?(node, n) }
70
-
71
- msg = if single_line_as_modifier?(node) && !named_capture_in_condition?(node)
72
- MSG_USE_MODIFIER
73
- elsif too_long_due_to_modifier?(node)
74
- MSG_USE_NORMAL
75
- end
76
- return unless msg
81
+ condition = node.condition
82
+ return if defined_nodes(condition).any? { |n| defined_argument_is_undefined?(node, n) } ||
83
+ pattern_matching_nodes(condition).any?
84
+ return unless (msg = message(node))
77
85
 
78
86
  add_offense(node.loc.keyword, message: format(msg, keyword: node.keyword)) do |corrector|
79
87
  autocorrect(corrector, node)
@@ -82,11 +90,11 @@ module RuboCop
82
90
 
83
91
  private
84
92
 
85
- def defined_nodes(node)
86
- if node.condition.defined_type?
87
- [node.condition]
93
+ def defined_nodes(condition)
94
+ if condition.defined_type?
95
+ [condition]
88
96
  else
89
- node.condition.each_descendant.select(&:defined_type?)
97
+ condition.each_descendant.select(&:defined_type?)
90
98
  end
91
99
  end
92
100
 
@@ -100,6 +108,24 @@ module RuboCop
100
108
  end
101
109
  end
102
110
 
111
+ def pattern_matching_nodes(condition)
112
+ if condition.match_pattern_type? || condition.match_pattern_p_type?
113
+ [condition]
114
+ else
115
+ condition.each_descendant.select do |node|
116
+ node.match_pattern_type? || node.match_pattern_p_type?
117
+ end
118
+ end
119
+ end
120
+
121
+ def message(node)
122
+ if single_line_as_modifier?(node) && !named_capture_in_condition?(node)
123
+ MSG_USE_MODIFIER
124
+ elsif too_long_due_to_modifier?(node)
125
+ MSG_USE_NORMAL
126
+ end
127
+ end
128
+
103
129
  def autocorrect(corrector, node)
104
130
  replacement = if node.modifier_form?
105
131
  replacement_for_modifier_form(corrector, node)
@@ -39,7 +39,10 @@ module RuboCop
39
39
 
40
40
  # @!method map_to_h?(node)
41
41
  def_node_matcher :map_to_h?, <<~PATTERN
42
- $(send (block $(send _ {:map :collect}) ...) :to_h)
42
+ {
43
+ $(send ({block numblock} $(send _ {:map :collect}) ...) :to_h)
44
+ $(send $(send _ {:map :collect} (block_pass sym)) :to_h)
45
+ }
43
46
  PATTERN
44
47
 
45
48
  def on_send(node)
@@ -32,7 +32,10 @@ module RuboCop
32
32
 
33
33
  # @!method map_to_set?(node)
34
34
  def_node_matcher :map_to_set?, <<~PATTERN
35
- $(send (block $(send _ {:map :collect}) ...) :to_set)
35
+ {
36
+ $(send ({block numblock} $(send _ {:map :collect}) ...) :to_set)
37
+ $(send $(send _ {:map :collect} (block_pass sym)) :to_set)
38
+ }
36
39
  PATTERN
37
40
 
38
41
  def on_send(node)
@@ -50,17 +50,13 @@ module RuboCop
50
50
  return false unless (last_argument = node.last_argument)
51
51
  return false if !last_argument.hash_type? || !last_argument.pairs.last&.value_omission?
52
52
 
53
- modifier_form?(node) || exist_next_line_expression?(node)
54
- end
55
-
56
- def modifier_form?(node)
57
- node.parent.respond_to?(:modifier_form?) && node.parent.modifier_form?
53
+ node.parent&.conditional? || !last_expression?(node)
58
54
  end
59
55
 
60
56
  # Require hash value omission be enclosed in parentheses to prevent the following issue:
61
57
  # https://bugs.ruby-lang.org/issues/18396.
62
- def exist_next_line_expression?(node)
63
- node.parent&.assignment? ? node.parent.right_sibling : node.right_sibling
58
+ def last_expression?(node)
59
+ !(node.parent&.assignment? ? node.parent.right_sibling : node.right_sibling)
64
60
  end
65
61
 
66
62
  def syntax_like_method_call?(node)
@@ -7,21 +7,19 @@ module RuboCop
7
7
  # method calls containing parameters.
8
8
  #
9
9
  # In the default style (require_parentheses), macro methods are allowed.
10
- # Additional methods can be added to the `AllowedMethods`
11
- # or `AllowedPatterns` list. These options are
12
- # valid only in the default style. Macros can be included by
13
- # either setting `IgnoreMacros` to false or adding specific macros to
14
- # the `IncludedMacros` list.
10
+ # Additional methods can be added to the `AllowedMethods` or
11
+ # `AllowedPatterns` list. These options are valid only in the default
12
+ # style. Macros can be included by either setting `IgnoreMacros` to false
13
+ # or adding specific macros to the `IncludedMacros` list.
15
14
  #
16
- # Precedence of options is all follows:
15
+ # Precedence of options is as follows:
17
16
  #
18
17
  # 1. `AllowedMethods`
19
18
  # 2. `AllowedPatterns`
20
19
  # 3. `IncludedMacros`
21
20
  #
22
- # eg. If a method is listed in both
23
- # `IncludedMacros` and `AllowedMethods`, then the latter takes
24
- # precedence (that is, the method is allowed).
21
+ # If a method is listed in both `IncludedMacros` and `AllowedMethods`,
22
+ # then the latter takes precedence (that is, the method is allowed).
25
23
  #
26
24
  # In the alternative style (omit_parentheses), there are three additional
27
25
  # options.
@@ -40,14 +38,29 @@ module RuboCop
40
38
  # to `true` allows the presence of parentheses in such a method call
41
39
  # even with arguments.
42
40
  #
43
- # NOTE: Parentheses are still allowed in cases where omitting them
44
- # results in ambiguous or syntactically incorrect code. For example,
45
- # parentheses are required around a method with arguments when inside an
46
- # endless method definition introduced in Ruby 3.0. Parentheses are also
47
- # allowed when forwarding arguments with the triple-dot syntax introduced
48
- # in Ruby 2.7 as omitting them starts an endless range.
49
- # And Ruby 3.1's hash omission syntax has a case that requires parentheses
50
- # because of the following issue: https://bugs.ruby-lang.org/issues/18396.
41
+ # NOTE: The style of `omit_parentheses` allows parentheses in cases where
42
+ # omitting them results in ambiguous or syntactically incorrect code.
43
+ #
44
+ # Non-exhaustive list of examples:
45
+ #
46
+ # - Parentheses are required allowed in method calls with arguments inside
47
+ # literals, logical operators, setting default values in position and
48
+ # keyword arguments, chaining and more.
49
+ # - Parentheses are allowed in method calls with arguments inside
50
+ # operators to avoid ambiguity.
51
+ # triple-dot syntax introduced in Ruby 2.7 as omitting them starts an
52
+ # endless range.
53
+ # - Parentheses are allowed when forwarding arguments with the
54
+ # triple-dot syntax introduced in Ruby 2.7 as omitting them starts an
55
+ # endless range.
56
+ # - Parentheses are required in calls with arguments when inside an
57
+ # endless method definition introduced in Ruby 3.0.
58
+ # - Ruby 3.1's hash omission syntax allows parentheses if the method call
59
+ # is in conditionals and requires parentheses if the call
60
+ # is not the value-returning expression. See
61
+ # https://bugs.ruby-lang.org/issues/18396.
62
+ # - Parentheses are required in anonymous arguments, keyword arguments
63
+ # and block passing in Ruby 3.2.
51
64
  #
52
65
  # @example EnforcedStyle: require_parentheses (default)
53
66
  #
@@ -80,34 +93,28 @@ module RuboCop
80
93
  # array.delete e
81
94
  #
82
95
  # # bad
83
- # foo.enforce(strict: true)
96
+ # action.enforce(strict: true)
84
97
  #
85
98
  # # good
86
- # foo.enforce strict: true
99
+ # action.enforce strict: true
87
100
  #
88
101
  # # good
89
- # # Allows parens for calls that won't produce valid Ruby or be ambiguous.
90
- # model.validate strict(true)
102
+ # # Parentheses are allowed for code that can be ambiguous without
103
+ # # them.
104
+ # action.enforce(condition) || other_condition
91
105
  #
92
106
  # # good
93
- # # Allows parens for calls that won't produce valid Ruby or be ambiguous.
107
+ # # Parentheses are allowed for calls that won't produce valid Ruby
108
+ # # without them.
94
109
  # yield path, File.basename(path)
95
110
  #
96
111
  # # good
97
- # # Operators methods calls with parens
98
- # array&.[](index)
99
- #
100
- # # good
101
- # # Operators methods without parens, if you prefer
102
- # array.[] index
103
- #
104
- # # good
105
- # # Operators methods calls with parens
106
- # array&.[](index)
107
- #
108
- # # good
109
- # # Operators methods without parens, if you prefer
110
- # array.[] index
112
+ # # Omitting the parentheses in Ruby 3.1 hash omission syntax can lead
113
+ # # to ambiguous code. We allow them in conditionals and non-last
114
+ # # expressions. See https://bugs.ruby-lang.org/issues/18396
115
+ # if meets(criteria:, action:)
116
+ # safe_action(action) || dangerous_action(action)
117
+ # end
111
118
  #
112
119
  # @example IgnoreMacros: true (default)
113
120
  #
@@ -28,14 +28,17 @@ module RuboCop
28
28
  return unless node.arguments?
29
29
  return if opening_line(node) == closing_line(node)
30
30
  return if correction_exceeds_max_line_length?(node)
31
+ return unless (begin_of_arguments = node.arguments.loc.begin)
31
32
 
32
- add_offense(node) { |corrector| autocorrect(corrector, node) }
33
+ add_offense(node) do |corrector|
34
+ autocorrect(corrector, node, begin_of_arguments)
35
+ end
33
36
  end
34
37
  alias on_defs on_def
35
38
 
36
39
  private
37
40
 
38
- def autocorrect(corrector, node)
41
+ def autocorrect(corrector, node, begin_of_arguments)
39
42
  arguments = node.arguments
40
43
  joined_arguments = arguments.map(&:source).join(', ')
41
44
  last_line_source_of_arguments = last_line_source_of_arguments(arguments)
@@ -47,7 +50,7 @@ module RuboCop
47
50
  end
48
51
 
49
52
  corrector.remove(arguments_range(node))
50
- corrector.insert_after(arguments.loc.begin, joined_arguments)
53
+ corrector.insert_after(begin_of_arguments, joined_arguments)
51
54
  end
52
55
 
53
56
  def last_line_source_of_arguments(arguments)
@@ -92,9 +92,8 @@ module RuboCop
92
92
 
93
93
  def contains_delimiter?(node, delimiters)
94
94
  delimiters_regexp = Regexp.union(delimiters)
95
- node
96
- .children.map { |n| string_source(n) }.compact
97
- .any? { |s| delimiters_regexp.match?(s) }
95
+
96
+ node.children.map { |n| string_source(n) }.compact.any?(delimiters_regexp)
98
97
  end
99
98
 
100
99
  def string_source(node)
@@ -44,7 +44,7 @@ module RuboCop
44
44
 
45
45
  # Report offense only if changing case doesn't change semantics,
46
46
  # i.e., if the string would become dynamic or has special characters.
47
- ast = ProcessedSource.new(corrected(node.source), target_ruby_version).ast
47
+ ast = parse(corrected(node.source)).ast
48
48
  return if node.children != ast.children
49
49
 
50
50
  add_offense(node.loc.begin) do |corrector|
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Check for redundant line continuation.
7
+ #
8
+ # This cop marks a line continuation as redundant if removing the backslash
9
+ # does not result in a syntax error.
10
+ # However, a backslash at the end of a comment or
11
+ # for string concatenation is not redundant and is not considered an offense.
12
+ #
13
+ # @example
14
+ # # bad
15
+ # foo. \
16
+ # bar
17
+ # foo \
18
+ # &.bar \
19
+ # .baz
20
+ #
21
+ # # good
22
+ # foo.
23
+ # bar
24
+ # foo
25
+ # &.bar
26
+ # .baz
27
+ #
28
+ # # bad
29
+ # [foo, \
30
+ # bar]
31
+ # {foo: \
32
+ # bar}
33
+ #
34
+ # # good
35
+ # [foo,
36
+ # bar]
37
+ # {foo:
38
+ # bar}
39
+ #
40
+ # # bad
41
+ # foo(bar, \
42
+ # baz)
43
+ #
44
+ # # good
45
+ # foo(bar,
46
+ # baz)
47
+ #
48
+ # # also good - backslash in string concatenation is not redundant
49
+ # foo('bar' \
50
+ # 'baz')
51
+ #
52
+ # # also good - backslash at the end of a comment is not redundant
53
+ # foo(bar, # \
54
+ # baz)
55
+ #
56
+ # # also good - backslash at the line following the newline begins with a + or -,
57
+ # # it is not redundant
58
+ # 1 \
59
+ # + 2 \
60
+ # - 3
61
+ #
62
+ # # also good - backslash with newline between the method name and its arguments,
63
+ # # it is not redundant.
64
+ # some_method \
65
+ # (argument)
66
+ #
67
+ class RedundantLineContinuation < Base
68
+ include MatchRange
69
+ extend AutoCorrector
70
+
71
+ MSG = 'Redundant line continuation.'
72
+ ALLOWED_STRING_TOKENS = %i[tSTRING tSTRING_CONTENT].freeze
73
+
74
+ def on_new_investigation
75
+ return unless processed_source.ast
76
+
77
+ each_match_range(processed_source.ast.source_range, /(\\\n)/) do |range|
78
+ next if require_line_continuation?(range)
79
+ next unless redundant_line_continuation?(range)
80
+
81
+ add_offense(range) do |corrector|
82
+ corrector.remove_leading(range, 1)
83
+ end
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def require_line_continuation?(range)
90
+ !ends_with_backslash_without_comment?(range.source_line) ||
91
+ string_concatenation?(range.source_line) ||
92
+ start_with_arithmetic_operator?(processed_source[range.line]) ||
93
+ inside_string_literal?(range)
94
+ end
95
+
96
+ def ends_with_backslash_without_comment?(source_line)
97
+ source_line.gsub(/#.+/, '').end_with?('\\')
98
+ end
99
+
100
+ def string_concatenation?(source_line)
101
+ /["']\s*\\\z/.match?(source_line)
102
+ end
103
+
104
+ def inside_string_literal?(range)
105
+ processed_source.tokens.each.any? do |token|
106
+ ALLOWED_STRING_TOKENS.include?(token.type) && token.pos.overlaps?(range)
107
+ end
108
+ end
109
+
110
+ def redundant_line_continuation?(range)
111
+ return true unless (node = find_node_for_line(range.line))
112
+ return false if argument_newline?(node)
113
+
114
+ source = node.parent ? node.parent.source : node.source
115
+ parse(source.gsub(/\\\n/, "\n")).valid_syntax?
116
+ end
117
+
118
+ def argument_newline?(node)
119
+ node = node.children.first if node.root? && node.begin_type?
120
+
121
+ if argument_is_method?(node)
122
+ argument_newline?(node.first_argument)
123
+ else
124
+ return false unless method_call_with_arguments?(node)
125
+
126
+ node.loc.selector.line != node.first_argument.loc.line
127
+ end
128
+ end
129
+
130
+ def find_node_for_line(line)
131
+ processed_source.ast.each_node do |node|
132
+ return node if same_line?(node, line)
133
+ end
134
+ end
135
+
136
+ def same_line?(node, line)
137
+ return unless (source_range = node.source_range)
138
+
139
+ if node.is_a?(AST::StrNode)
140
+ if node.heredoc?
141
+ (node.loc.heredoc_body.line..node.loc.heredoc_body.last_line).cover?(line)
142
+ else
143
+ (source_range.line..source_range.last_line).cover?(line)
144
+ end
145
+ else
146
+ source_range.line == line
147
+ end
148
+ end
149
+
150
+ def argument_is_method?(node)
151
+ return false unless node.send_type?
152
+ return false unless (first_argument = node.first_argument)
153
+
154
+ method_call_with_arguments?(first_argument)
155
+ end
156
+
157
+ def method_call_with_arguments?(node)
158
+ node.call_type? && !node.arguments.empty?
159
+ end
160
+
161
+ def start_with_arithmetic_operator?(source_line)
162
+ %r{\A\s*[+\-*/%]}.match?(source_line)
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
@@ -98,7 +98,7 @@ module RuboCop
98
98
  end
99
99
 
100
100
  def like_method_argument_parentheses?(node)
101
- node.send_type? && node.arguments.one? &&
101
+ node.send_type? && node.arguments.one? && !node.parenthesized? &&
102
102
  !node.arithmetic_operation? && node.first_argument.begin_type?
103
103
  end
104
104
 
@@ -93,7 +93,7 @@ module RuboCop
93
93
 
94
94
  return true if STRING_INTERPOLATION_REGEXP.match?(src)
95
95
 
96
- src.scan(/\\./).any? { |s| ESCAPED_NON_BACKSLASH.match?(s) }
96
+ src.scan(/\\./).any?(ESCAPED_NON_BACKSLASH)
97
97
  end
98
98
 
99
99
  def acceptable_capital_q?(node)
@@ -63,7 +63,7 @@ module RuboCop
63
63
  next if expr.type != :set || expr.expressions.size != 1
64
64
  next if expr.negative?
65
65
  next if %i[set posixclass nonposixclass].include?(expr.expressions.first.type)
66
- next if multiple_codepoins?(expr.expressions.first)
66
+ next if multiple_codepoints?(expr.expressions.first)
67
67
 
68
68
  yield expr
69
69
  end
@@ -80,7 +80,7 @@ module RuboCop
80
80
  !non_redundant
81
81
  end
82
82
 
83
- def multiple_codepoins?(expression)
83
+ def multiple_codepoints?(expression)
84
84
  expression.respond_to?(:codepoints) && expression.codepoints.count >= 2
85
85
  end
86
86
 
@@ -107,7 +107,7 @@ module RuboCop
107
107
  end
108
108
  end
109
109
  # Please remove this `else` branch when support for regexp_parser 1.8 will be dropped.
110
- # It's for compatibility with regexp_arser 1.8 and will never be maintained.
110
+ # It's for compatibility with regexp_parser 1.8 and will never be maintained.
111
111
  else
112
112
  def each_escape(node)
113
113
  node.parsed_tree&.traverse&.reduce(0) do |char_class_depth, (event, expr)|
@@ -36,7 +36,6 @@ module RuboCop
36
36
  # STR
37
37
  class RedundantStringEscape < Base
38
38
  include MatchRange
39
- include RangeHelp
40
39
  extend AutoCorrector
41
40
 
42
41
  MSG = 'Redundant escape of %<char>s inside string literal.'
@@ -64,10 +63,10 @@ module RuboCop
64
63
  def str_contents_range(node)
65
64
  if heredoc?(node)
66
65
  node.loc.heredoc_body
66
+ elsif node.str_type?
67
+ node.source_range
67
68
  elsif begin_loc_present?(node)
68
69
  contents_range(node)
69
- else
70
- node.source_range
71
70
  end
72
71
  end
73
72
 
@@ -167,7 +167,7 @@ module RuboCop
167
167
  corrector.insert_before(condition,
168
168
  "#{'!' if node.unless?}#{replace_condition(node.condition)} && ")
169
169
 
170
- corrector.remove(node.condition.source_range)
170
+ corrector.remove(node.condition)
171
171
  corrector.remove(range_with_surrounding_space(node.loc.keyword, newlines: false))
172
172
  corrector.replace(if_branch.loc.keyword, 'if')
173
173
  end
@@ -187,7 +187,7 @@ module RuboCop
187
187
  return if end_pos > begin_pos
188
188
 
189
189
  corrector.replace(range_between(end_pos, begin_pos), '(')
190
- corrector.insert_after(condition.last_argument.source_range, ')')
190
+ corrector.insert_after(condition.last_argument, ')')
191
191
  end
192
192
 
193
193
  def insert_bang(corrector, node, is_modify_form)
@@ -55,7 +55,7 @@ module RuboCop
55
55
  elsif (class_node = parent.parent).body.nil?
56
56
  corrector.remove(range_for_empty_class_body(class_node, parent))
57
57
  else
58
- corrector.insert_after(parent.source_range, ' do')
58
+ corrector.insert_after(parent, ' do')
59
59
  end
60
60
  end
61
61
 
@@ -34,6 +34,7 @@ module RuboCop
34
34
  )
35
35
  end
36
36
  end
37
+ alias on_sclass on_class
37
38
  end
38
39
  end
39
40
  end