rubocop 1.52.1 → 1.53.1

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +38 -1
  4. data/lib/rubocop/cli/command/lsp.rb +19 -0
  5. data/lib/rubocop/cli.rb +3 -0
  6. data/lib/rubocop/config_loader_resolver.rb +4 -3
  7. data/lib/rubocop/cop/bundler/gem_comment.rb +1 -1
  8. data/lib/rubocop/cop/bundler/gem_version.rb +2 -2
  9. data/lib/rubocop/cop/gemspec/dependency_version.rb +2 -2
  10. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +3 -3
  11. data/lib/rubocop/cop/layout/class_structure.rb +7 -0
  12. data/lib/rubocop/cop/layout/closing_heredoc_indentation.rb +1 -1
  13. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +1 -1
  14. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +2 -0
  15. data/lib/rubocop/cop/layout/indentation_style.rb +1 -1
  16. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  17. data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -1
  18. data/lib/rubocop/cop/layout/space_inside_range_literal.rb +1 -1
  19. data/lib/rubocop/cop/lint/debugger.rb +1 -1
  20. data/lib/rubocop/cop/lint/duplicate_hash_key.rb +2 -1
  21. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +46 -19
  22. data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +1 -1
  23. data/lib/rubocop/cop/lint/missing_super.rb +31 -5
  24. data/lib/rubocop/cop/lint/mixed_case_range.rb +109 -0
  25. data/lib/rubocop/cop/lint/number_conversion.rb +5 -0
  26. data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +120 -0
  27. data/lib/rubocop/cop/lint/redundant_require_statement.rb +8 -3
  28. data/lib/rubocop/cop/lint/suppressed_exception.rb +1 -1
  29. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -1
  30. data/lib/rubocop/cop/lint/void.rb +1 -1
  31. data/lib/rubocop/cop/migration/department_name.rb +2 -2
  32. data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
  33. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -1
  34. data/lib/rubocop/cop/mixin/percent_literal.rb +1 -1
  35. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  36. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +3 -3
  37. data/lib/rubocop/cop/style/block_comments.rb +1 -1
  38. data/lib/rubocop/cop/style/block_delimiters.rb +3 -3
  39. data/lib/rubocop/cop/style/conditional_assignment.rb +3 -1
  40. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +1 -1
  41. data/lib/rubocop/cop/style/identical_conditional_branches.rb +6 -2
  42. data/lib/rubocop/cop/style/invertible_unless_condition.rb +1 -1
  43. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -2
  44. data/lib/rubocop/cop/style/preferred_hash_methods.rb +1 -1
  45. data/lib/rubocop/cop/style/redundant_begin.rb +1 -1
  46. data/lib/rubocop/cop/style/redundant_conditional.rb +1 -1
  47. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +38 -0
  48. data/lib/rubocop/cop/style/redundant_line_continuation.rb +2 -2
  49. data/lib/rubocop/cop/style/redundant_parentheses.rb +1 -1
  50. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +97 -0
  51. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +2 -1
  52. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +3 -1
  53. data/lib/rubocop/cop/style/redundant_sort.rb +1 -1
  54. data/lib/rubocop/cop/style/redundant_string_escape.rb +2 -0
  55. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +81 -0
  56. data/lib/rubocop/cop/style/signal_exception.rb +1 -1
  57. data/lib/rubocop/cop/style/yaml_file_read.rb +66 -0
  58. data/lib/rubocop/cop/util.rb +1 -1
  59. data/lib/rubocop/cop/utils/regexp_ranges.rb +100 -0
  60. data/lib/rubocop/cops_documentation_generator.rb +1 -1
  61. data/lib/rubocop/ext/regexp_parser.rb +4 -1
  62. data/lib/rubocop/lsp/logger.rb +22 -0
  63. data/lib/rubocop/lsp/routes.rb +223 -0
  64. data/lib/rubocop/lsp/runtime.rb +79 -0
  65. data/lib/rubocop/lsp/server.rb +62 -0
  66. data/lib/rubocop/lsp/severity.rb +27 -0
  67. data/lib/rubocop/options.rb +11 -1
  68. data/lib/rubocop/version.rb +1 -1
  69. data/lib/rubocop.rb +8 -0
  70. metadata +30 -3
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks for redundant quantifiers inside Regexp literals.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # /(?:x+)+/
11
+ #
12
+ # # good
13
+ # /(?:x)+/
14
+ #
15
+ # # good
16
+ # /(?:x+)/
17
+ #
18
+ # # bad
19
+ # /(?:x+)?/
20
+ #
21
+ # # good
22
+ # /(?:x)*/
23
+ #
24
+ # # good
25
+ # /(?:x*)/
26
+ class RedundantRegexpQuantifiers < Base
27
+ include RangeHelp
28
+ extend AutoCorrector
29
+
30
+ MSG_REDUNDANT_QUANTIFIER = 'Replace redundant quantifiers ' \
31
+ '`%<inner_quantifier>s` and `%<outer_quantifier>s` ' \
32
+ 'with a single `%<replacement>s`.'
33
+
34
+ def on_regexp(node)
35
+ each_redundantly_quantified_pair(node) do |group, child|
36
+ replacement = merged_quantifier(group, child)
37
+ add_offense(
38
+ quantifier_range(group, child),
39
+ message: message(group, child, replacement)
40
+ ) do |corrector|
41
+ # drop outer quantifier
42
+ corrector.replace(group.loc.quantifier, '')
43
+ # replace inner quantifier
44
+ corrector.replace(child.loc.quantifier, replacement)
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def each_redundantly_quantified_pair(node)
52
+ seen = Set.new
53
+ node.parsed_tree&.each_expression do |(expr)|
54
+ next if seen.include?(expr) || !redundant_group?(expr) || !mergeable_quantifier(expr)
55
+
56
+ expr.each_expression do |(subexp)|
57
+ seen << subexp
58
+ break unless redundantly_quantifiable?(subexp)
59
+
60
+ yield(expr, subexp) if mergeable_quantifier(subexp)
61
+ end
62
+ end
63
+ end
64
+
65
+ def redundant_group?(expr)
66
+ expr.is?(:passive, :group) && expr.count { |child| child.type != :free_space } == 1
67
+ end
68
+
69
+ def redundantly_quantifiable?(node)
70
+ redundant_group?(node) || character_set?(node) || node.terminal?
71
+ end
72
+
73
+ def character_set?(expr)
74
+ expr.is?(:character, :set)
75
+ end
76
+
77
+ def mergeable_quantifier(expr)
78
+ # Merging reluctant or possessive quantifiers would be more complex,
79
+ # and Ruby does not emit warnings for these cases.
80
+ return unless expr.quantifier&.greedy?
81
+
82
+ # normalize quantifiers, e.g. "{1,}" => "+"
83
+ case expr.quantity
84
+ when [0, -1]
85
+ '*'
86
+ when [0, 1]
87
+ '?'
88
+ when [1, -1]
89
+ '+'
90
+ end
91
+ end
92
+
93
+ def merged_quantifier(exp1, exp2)
94
+ quantifier1 = mergeable_quantifier(exp1)
95
+ quantifier2 = mergeable_quantifier(exp2)
96
+ if quantifier1 == quantifier2
97
+ # (?:a+)+ equals (?:a+) ; (?:a*)* equals (?:a*) ; # (?:a?)? equals (?:a?)
98
+ quantifier1
99
+ else
100
+ # (?:a+)*, (?:a+)?, (?:a*)+, (?:a*)?, (?:a?)+, (?:a?)* - all equal (?:a*)
101
+ '*'
102
+ end
103
+ end
104
+
105
+ def quantifier_range(group, child)
106
+ range_between(child.loc.quantifier.begin_pos, group.loc.quantifier.end_pos)
107
+ end
108
+
109
+ def message(group, child, replacement)
110
+ format(
111
+ MSG_REDUNDANT_QUANTIFIER,
112
+ inner_quantifier: child.quantifier.to_s,
113
+ outer_quantifier: group.quantifier.to_s,
114
+ replacement: replacement
115
+ )
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -49,6 +49,11 @@ module RuboCop
49
49
  (str #redundant_feature?))
50
50
  PATTERN
51
51
 
52
+ # @!method pp_const?(node)
53
+ def_node_matcher :pp_const?, <<~PATTERN
54
+ (const {nil? cbase} :PP)
55
+ PATTERN
56
+
52
57
  def on_send(node)
53
58
  return unless redundant_require_statement?(node)
54
59
 
@@ -72,16 +77,16 @@ module RuboCop
72
77
  feature_name == 'enumerator' ||
73
78
  (target_ruby_version >= 2.1 && feature_name == 'thread') ||
74
79
  (target_ruby_version >= 2.2 && RUBY_22_LOADED_FEATURES.include?(feature_name)) ||
75
- (target_ruby_version >= 2.5 && feature_name == 'pp' && !use_pretty_print_method?) ||
80
+ (target_ruby_version >= 2.5 && feature_name == 'pp' && !need_to_require_pp?) ||
76
81
  (target_ruby_version >= 2.7 && feature_name == 'ruby2_keywords') ||
77
82
  (target_ruby_version >= 3.1 && feature_name == 'fiber') ||
78
83
  (target_ruby_version >= 3.2 && feature_name == 'set')
79
84
  end
80
85
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
81
86
 
82
- def use_pretty_print_method?
87
+ def need_to_require_pp?
83
88
  processed_source.ast.each_descendant(:send).any? do |node|
84
- PRETTY_PRINT_METHODS.include?(node.method_name)
89
+ pp_const?(node.receiver) || PRETTY_PRINT_METHODS.include?(node.method_name)
85
90
  end
86
91
  end
87
92
  end
@@ -117,7 +117,7 @@ module RuboCop
117
117
 
118
118
  def comment_between_rescue_and_end?(node)
119
119
  ancestor = node.each_ancestor(:kwbegin, :def, :defs, :block, :numblock).first
120
- return unless ancestor
120
+ return false unless ancestor
121
121
 
122
122
  end_line = ancestor.loc.end.line
123
123
  processed_source[node.first_line...end_line].any? { |line| comment_line?(line) }
@@ -124,7 +124,7 @@ module RuboCop
124
124
  source == value ||
125
125
  # `Symbol#inspect` uses double quotes, but allow single-quoted
126
126
  # symbols to work as well.
127
- source.tr("'", '"') == value
127
+ source.gsub('"', '\"').tr("'", '"') == value
128
128
  end
129
129
 
130
130
  def requires_quotes?(sym_node)
@@ -144,7 +144,7 @@ module RuboCop
144
144
  end
145
145
 
146
146
  def check_nonmutating(node)
147
- return unless node.respond_to?(:method_name)
147
+ return if !node.send_type? && !node.block_type? && !node.numblock_type?
148
148
 
149
149
  method_name = node.method_name
150
150
  return unless NONMUTATING_METHODS.include?(method_name)
@@ -16,7 +16,7 @@ module RuboCop
16
16
  # The token that makes up a disable comment.
17
17
  # The allowed specification for comments after `# rubocop: disable` is
18
18
  # `DepartmentName/CopName` or` all`.
19
- DISABLING_COPS_CONTENT_TOKEN = %r{[A-z]+/[A-z]+|all}.freeze
19
+ DISABLING_COPS_CONTENT_TOKEN = %r{[A-Za-z]+/[A-Za-z]+|all}.freeze
20
20
 
21
21
  def on_new_investigation
22
22
  processed_source.comments.each do |comment|
@@ -67,7 +67,7 @@ module RuboCop
67
67
  end
68
68
 
69
69
  def contain_unexpected_character_for_department_name?(name)
70
- name.match?(%r{[^A-z/, ]})
70
+ name.match?(%r{[^A-Za-z/, ]})
71
71
  end
72
72
 
73
73
  def qualified_legacy_cop_name(cop_name)
@@ -25,7 +25,7 @@ module RuboCop
25
25
  def comments_contain_disables?(node, cop_name)
26
26
  disabled_ranges = processed_source.disabled_line_ranges[cop_name]
27
27
 
28
- return unless disabled_ranges
28
+ return false unless disabled_ranges
29
29
 
30
30
  node_range = node.source_range.line...find_end_line(node)
31
31
 
@@ -67,7 +67,7 @@ module RuboCop
67
67
  end
68
68
 
69
69
  def variable_alignment?(whole_expression, rhs, end_alignment_style)
70
- return if end_alignment_style == :keyword
70
+ return false if end_alignment_style == :keyword
71
71
 
72
72
  !line_break_before_keyword?(whole_expression, rhs)
73
73
  end
@@ -9,7 +9,7 @@ module RuboCop
9
9
  private
10
10
 
11
11
  def percent_literal?(node)
12
- return unless (begin_source = begin_source(node))
12
+ return false unless (begin_source = begin_source(node))
13
13
 
14
14
  begin_source.start_with?('%')
15
15
  end
@@ -109,7 +109,7 @@ module RuboCop
109
109
  end
110
110
 
111
111
  def use_block_argument_as_local_variable?(node, last_argument)
112
- return if node.body.nil?
112
+ return false if node.body.nil?
113
113
 
114
114
  node.body.each_descendant(:lvar, :lvasgn).any? do |lvar|
115
115
  !lvar.parent.block_pass_type? && lvar.node_parts[0].to_s == last_argument
@@ -204,14 +204,14 @@ module RuboCop
204
204
  # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
205
205
  def on_defined?(node)
206
206
  arg = node.arguments.first
207
- return unless arg.ivar_type?
207
+ return false unless arg.ivar_type?
208
208
 
209
209
  method_node, method_name = find_definition(node)
210
- return unless method_node
210
+ return false unless method_node
211
211
 
212
212
  var_name = arg.children.first
213
213
  defined_memoized?(method_node.body, var_name) do |defined_ivar, return_ivar, ivar_assign|
214
- return if matches?(method_name, ivar_assign)
214
+ return false if matches?(method_name, ivar_assign)
215
215
 
216
216
  suggested_var = suggested_var(method_name)
217
217
  msg = format(
@@ -35,7 +35,7 @@ module RuboCop
35
35
  unless contents.empty?
36
36
  corrector.replace(
37
37
  contents,
38
- contents.source.gsub(/\A/, '# ').gsub(/\n\n/, "\n#\n").gsub(/\n(?=[^#])/, "\n# ")
38
+ contents.source.gsub(/\A/, '# ').gsub("\n\n", "\n#\n").gsub(/\n(?=[^#])/, "\n# ")
39
39
  )
40
40
  end
41
41
  corrector.remove(eq_end)
@@ -411,7 +411,7 @@ module RuboCop
411
411
  end
412
412
 
413
413
  def correction_would_break_code?(node)
414
- return unless node.keywords?
414
+ return false unless node.keywords?
415
415
 
416
416
  node.send_node.arguments? && !node.send_node.parenthesized?
417
417
  end
@@ -433,7 +433,7 @@ module RuboCop
433
433
  end
434
434
 
435
435
  def return_value_used?(node)
436
- return unless node.parent
436
+ return false unless node.parent
437
437
 
438
438
  # If there are parentheses around the block, check if that
439
439
  # is being used.
@@ -445,7 +445,7 @@ module RuboCop
445
445
  end
446
446
 
447
447
  def return_value_of_scope?(node)
448
- return unless node.parent
448
+ return false unless node.parent
449
449
 
450
450
  conditional?(node.parent) || array_or_range?(node.parent) ||
451
451
  node.parent.children.last == node
@@ -361,7 +361,7 @@ module RuboCop
361
361
  end
362
362
 
363
363
  def assignment_types_match?(*nodes)
364
- return unless assignment_type?(nodes.first)
364
+ return false unless assignment_type?(nodes.first)
365
365
 
366
366
  nodes.map(&:type).uniq.one?
367
367
  end
@@ -440,6 +440,8 @@ module RuboCop
440
440
  module ConditionalCorrectorHelper
441
441
  def remove_whitespace_in_branches(corrector, branch, condition, column)
442
442
  branch.each_node do |child|
443
+ next if child.source_range.nil?
444
+
443
445
  white_space = white_space_range(child, column)
444
446
  corrector.remove(white_space) if white_space.source.strip.empty?
445
447
  end
@@ -108,7 +108,7 @@ module RuboCop
108
108
  comments = heredoc_comment_blocks(arg_node.loc.heredoc_body.line_span)
109
109
  .concat(preceding_comment_blocks(arg_node.parent))
110
110
 
111
- return if comments.none?
111
+ return false if comments.none?
112
112
 
113
113
  regexp = comment_regexp(arg_node)
114
114
  comments.any?(regexp) || regexp.match?(comments.join)
@@ -158,13 +158,16 @@ module RuboCop
158
158
  return false unless expressions.size >= 1 && unique_expressions.one?
159
159
 
160
160
  unique_expression = unique_expressions.first
161
- return true unless unique_expression.assignment?
161
+ return true unless unique_expression&.assignment?
162
162
 
163
163
  lhs = unique_expression.child_nodes.first
164
164
  node.condition.child_nodes.none? { |n| n.source == lhs.source if n.variable? }
165
165
  end
166
166
 
167
- def check_expressions(node, expressions, insert_position) # rubocop:disable Metrics/MethodLength
167
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
168
+ def check_expressions(node, expressions, insert_position)
169
+ return if expressions.any?(&:nil?)
170
+
168
171
  inserted_expression = false
169
172
 
170
173
  expressions.each do |expression|
@@ -184,6 +187,7 @@ module RuboCop
184
187
  end
185
188
  end
186
189
  end
190
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
187
191
 
188
192
  def last_child_of_parent?(node)
189
193
  return true unless (parent = node.parent)
@@ -72,7 +72,7 @@ module RuboCop
72
72
  when :begin
73
73
  invertible?(node.children.first)
74
74
  when :send
75
- return if inheritance_check?(node)
75
+ return false if inheritance_check?(node)
76
76
 
77
77
  node.method?(:!) || inverse_methods.key?(node.method_name)
78
78
  when :or, :and
@@ -98,7 +98,7 @@ module RuboCop
98
98
 
99
99
  def call_in_literals?(node)
100
100
  parent = node.parent&.block_type? ? node.parent.parent : node.parent
101
- return unless parent
101
+ return false unless parent
102
102
 
103
103
  parent.pair_type? ||
104
104
  parent.array_type? ||
@@ -109,7 +109,7 @@ module RuboCop
109
109
 
110
110
  def call_in_logical_operators?(node)
111
111
  parent = node.parent&.block_type? ? node.parent.parent : node.parent
112
- return unless parent
112
+ return false unless parent
113
113
 
114
114
  logical_operator?(parent) ||
115
115
  (parent.send_type? &&
@@ -61,7 +61,7 @@ module RuboCop
61
61
  if style == :verbose
62
62
  "has_#{method_name}"
63
63
  else
64
- method_name.to_s.sub(/has_/, '')
64
+ method_name.to_s.delete_prefix('has_')
65
65
  end
66
66
  end
67
67
 
@@ -146,7 +146,7 @@ module RuboCop
146
146
  end
147
147
 
148
148
  def use_modifier_form_after_multiline_begin_block?(node)
149
- return unless (parent = node.parent)
149
+ return false unless (parent = node.parent)
150
150
 
151
151
  node.multiline? && parent.if_type? && parent.modifier_form?
152
152
  end
@@ -63,7 +63,7 @@ module RuboCop
63
63
  RUBY
64
64
 
65
65
  def offense?(node)
66
- return if node.modifier_form?
66
+ return false if node.modifier_form?
67
67
 
68
68
  redundant_condition?(node) || redundant_condition_inverted?(node)
69
69
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for uses a redundant current directory in path.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # require_relative './path/to/feature'
12
+ #
13
+ # # good
14
+ # require_relative 'path/to/feature'
15
+ #
16
+ class RedundantCurrentDirectoryInPath < Base
17
+ include RangeHelp
18
+ extend AutoCorrector
19
+
20
+ MSG = 'Remove the redundant current directory path.'
21
+ CURRENT_DIRECTORY_PATH = './'
22
+
23
+ def on_send(node)
24
+ return unless node.method?(:require_relative)
25
+ return unless node.first_argument.str_content&.start_with?(CURRENT_DIRECTORY_PATH)
26
+ return unless (index = node.first_argument.source.index(CURRENT_DIRECTORY_PATH))
27
+
28
+ begin_pos = node.first_argument.source_range.begin.begin_pos + index
29
+ range = range_between(begin_pos, begin_pos + 2)
30
+
31
+ add_offense(range) do |corrector|
32
+ corrector.remove(range)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -116,7 +116,7 @@ module RuboCop
116
116
  return false if argument_newline?(node)
117
117
 
118
118
  source = node.parent ? node.parent.source : node.source
119
- parse(source.gsub(/\\\n/, "\n")).valid_syntax?
119
+ parse(source.gsub("\\\n", "\n")).valid_syntax?
120
120
  end
121
121
 
122
122
  def inside_string_literal?(range, token)
@@ -150,7 +150,7 @@ module RuboCop
150
150
  end
151
151
 
152
152
  def same_line?(node, line)
153
- return unless (source_range = node.source_range)
153
+ return false unless (source_range = node.source_range)
154
154
 
155
155
  if node.is_a?(AST::StrNode)
156
156
  if node.heredoc?
@@ -85,7 +85,7 @@ module RuboCop
85
85
  end
86
86
 
87
87
  def allowed_ternary?(node)
88
- return unless node&.parent&.if_type?
88
+ return false unless node&.parent&.if_type?
89
89
 
90
90
  node.parent.ternary? && ternary_parentheses_required?
91
91
  end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Identifies places where argument can be replaced from
7
+ # a deterministic regexp to a string.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # 'foo'.byteindex(/f/)
12
+ # 'foo'.byterindex(/f/)
13
+ # 'foo'.gsub(/f/, 'x')
14
+ # 'foo'.gsub!(/f/, 'x')
15
+ # 'foo'.partition(/f/)
16
+ # 'foo'.rpartition(/f/)
17
+ # 'foo'.scan(/f/)
18
+ # 'foo'.split(/f/)
19
+ # 'foo'.start_with?(/f/)
20
+ # 'foo'.sub(/f/, 'x')
21
+ # 'foo'.sub!(/f/, 'x')
22
+ #
23
+ # # good
24
+ # 'foo'.byteindex('f')
25
+ # 'foo'.byterindex('f')
26
+ # 'foo'.gsub('f', 'x')
27
+ # 'foo'.gsub!('f', 'x')
28
+ # 'foo'.partition('f')
29
+ # 'foo'.rpartition('f')
30
+ # 'foo'.scan('f')
31
+ # 'foo'.split('f')
32
+ # 'foo'.start_with?('f')
33
+ # 'foo'.sub('f', 'x')
34
+ # 'foo'.sub!('f', 'x')
35
+ class RedundantRegexpArgument < Base
36
+ extend AutoCorrector
37
+
38
+ MSG = 'Use string `%<prefer>s` as argument instead of regexp `%<current>s`.'
39
+ RESTRICT_ON_SEND = %i[
40
+ byteindex byterindex gsub gsub! partition rpartition scan split start_with? sub sub!
41
+ ].freeze
42
+ DETERMINISTIC_REGEX = /\A(?:#{LITERAL_REGEX})+\Z/.freeze
43
+ STR_SPECIAL_CHARS = %w[\n \" \' \\\\ \t \b \f \r].freeze
44
+
45
+ def on_send(node)
46
+ return unless (regexp_node = node.first_argument)
47
+ return unless regexp_node.regexp_type?
48
+ return if !regexp_node.regopt.children.empty? || regexp_node.content == ' '
49
+ return unless determinist_regexp?(regexp_node)
50
+
51
+ prefer = preferred_argument(regexp_node)
52
+ message = format(MSG, prefer: prefer, current: regexp_node.source)
53
+
54
+ add_offense(regexp_node, message: message) do |corrector|
55
+ corrector.replace(regexp_node, prefer)
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def determinist_regexp?(regexp_node)
62
+ DETERMINISTIC_REGEX.match?(regexp_node.source)
63
+ end
64
+
65
+ def preferred_argument(regexp_node)
66
+ new_argument = replacement(regexp_node)
67
+
68
+ if new_argument.include?('"')
69
+ new_argument.gsub!("'", "\\\\'")
70
+ quote = "'"
71
+ else
72
+ quote = '"'
73
+ end
74
+
75
+ "#{quote}#{new_argument}#{quote}"
76
+ end
77
+
78
+ def replacement(regexp_node)
79
+ regexp_content = regexp_node.content
80
+ stack = []
81
+ chars = regexp_content.chars.each_with_object([]) do |char, strings|
82
+ if stack.empty? && char == '\\'
83
+ stack.push(char)
84
+ else
85
+ strings << "#{stack.pop}#{char}"
86
+ end
87
+ end
88
+ chars.map do |char|
89
+ char = char.dup
90
+ char.delete!('\\') unless STR_SPECIAL_CHARS.include?(char)
91
+ char
92
+ end.join
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -44,7 +44,8 @@ module RuboCop
44
44
 
45
45
  def on_regexp(node)
46
46
  each_escape(node) do |char, index, within_character_class|
47
- next if allowed_escape?(node, char, index, within_character_class)
47
+ next if char.valid_encoding? && allowed_escape?(node, char, index,
48
+ within_character_class)
48
49
 
49
50
  location = escape_range_at_index(node, index)
50
51
 
@@ -62,7 +62,9 @@ module RuboCop
62
62
  end
63
63
 
64
64
  def multiple_statements?(branch)
65
- branch && branch.children.compact.count > 1
65
+ return false unless branch&.begin_type?
66
+
67
+ !branch.children.empty?
66
68
  end
67
69
 
68
70
  def self_assign?(variable, branch)
@@ -198,7 +198,7 @@ module RuboCop
198
198
  end
199
199
 
200
200
  def with_logical_operator?(node)
201
- return unless (parent = node.parent)
201
+ return false unless (parent = node.parent)
202
202
 
203
203
  parent.or_type? || parent.and_type?
204
204
  end
@@ -157,6 +157,8 @@ module RuboCop
157
157
  return delimiter?(node.parent, char)
158
158
  end
159
159
 
160
+ return true unless node.loc.begin
161
+
160
162
  delimiters = [node.loc.begin.source[-1], node.loc.end.source[0]]
161
163
 
162
164
  delimiters.include?(char)