rubocop 1.88.0 → 1.88.2

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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +5 -1
  3. data/lib/rubocop/cop/base.rb +13 -8
  4. data/lib/rubocop/cop/bundler/gem_comment.rb +3 -1
  5. data/lib/rubocop/cop/commissioner.rb +13 -11
  6. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +1 -1
  7. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +7 -1
  8. data/lib/rubocop/cop/correctors/ordered_gem_corrector.rb +8 -1
  9. data/lib/rubocop/cop/gemspec/development_dependencies.rb +1 -1
  10. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +12 -2
  11. data/lib/rubocop/cop/gemspec/require_mfa.rb +4 -1
  12. data/lib/rubocop/cop/layout/block_alignment.rb +17 -0
  13. data/lib/rubocop/cop/layout/class_structure.rb +7 -3
  14. data/lib/rubocop/cop/layout/comment_indentation.rb +9 -2
  15. data/lib/rubocop/cop/layout/condition_position.rb +13 -3
  16. data/lib/rubocop/cop/layout/else_alignment.rb +1 -14
  17. data/lib/rubocop/cop/layout/empty_comment.rb +8 -10
  18. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +14 -1
  19. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +13 -14
  20. data/lib/rubocop/cop/layout/indentation_width.rb +28 -0
  21. data/lib/rubocop/cop/layout/line_length.rb +10 -5
  22. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +6 -1
  23. data/lib/rubocop/cop/layout/space_around_operators.rb +6 -2
  24. data/lib/rubocop/cop/lint/assignment_in_condition.rb +13 -1
  25. data/lib/rubocop/cop/lint/debugger.rb +13 -1
  26. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +7 -0
  27. data/lib/rubocop/cop/lint/to_enum_arguments.rb +7 -1
  28. data/lib/rubocop/cop/lint/to_json.rb +8 -1
  29. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +32 -8
  30. data/lib/rubocop/cop/lint/unreachable_code.rb +9 -4
  31. data/lib/rubocop/cop/lint/useless_numeric_operation.rb +10 -10
  32. data/lib/rubocop/cop/lint/void.rb +2 -2
  33. data/lib/rubocop/cop/metrics/method_length.rb +1 -1
  34. data/lib/rubocop/cop/metrics/perceived_complexity.rb +38 -7
  35. data/lib/rubocop/cop/mixin/allowed_methods.rb +5 -5
  36. data/lib/rubocop/cop/mixin/allowed_pattern.rb +5 -1
  37. data/lib/rubocop/cop/mixin/forbidden_pattern.rb +5 -1
  38. data/lib/rubocop/cop/mixin/hash_subset.rb +8 -0
  39. data/lib/rubocop/cop/mixin/hash_transform_method.rb +4 -0
  40. data/lib/rubocop/cop/mixin/statement_modifier.rb +1 -1
  41. data/lib/rubocop/cop/naming/file_name.rb +4 -3
  42. data/lib/rubocop/cop/naming/inclusive_language.rb +8 -2
  43. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +9 -0
  44. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +9 -3
  45. data/lib/rubocop/cop/security/io_methods.rb +1 -1
  46. data/lib/rubocop/cop/security/marshal_load.rb +1 -1
  47. data/lib/rubocop/cop/style/accessor_grouping.rb +11 -1
  48. data/lib/rubocop/cop/style/arguments_forwarding.rb +1 -1
  49. data/lib/rubocop/cop/style/array_intersect.rb +4 -4
  50. data/lib/rubocop/cop/style/collection_compact.rb +1 -1
  51. data/lib/rubocop/cop/style/data_inheritance.rb +4 -0
  52. data/lib/rubocop/cop/style/def_with_parentheses.rb +6 -2
  53. data/lib/rubocop/cop/style/dir_empty.rb +4 -0
  54. data/lib/rubocop/cop/style/documentation_method.rb +5 -1
  55. data/lib/rubocop/cop/style/empty_case_condition.rb +12 -2
  56. data/lib/rubocop/cop/style/empty_class_definition.rb +8 -1
  57. data/lib/rubocop/cop/style/empty_heredoc.rb +4 -0
  58. data/lib/rubocop/cop/style/empty_literal.rb +7 -2
  59. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +30 -20
  60. data/lib/rubocop/cop/style/env_home.rb +4 -0
  61. data/lib/rubocop/cop/style/even_odd.rb +11 -1
  62. data/lib/rubocop/cop/style/exact_regexp_match.rb +8 -1
  63. data/lib/rubocop/cop/style/file_null.rb +4 -2
  64. data/lib/rubocop/cop/style/format_string.rb +13 -1
  65. data/lib/rubocop/cop/style/hash_conversion.rb +14 -6
  66. data/lib/rubocop/cop/style/hash_lookup_method.rb +2 -6
  67. data/lib/rubocop/cop/style/hash_syntax.rb +2 -0
  68. data/lib/rubocop/cop/style/identical_conditional_branches.rb +15 -4
  69. data/lib/rubocop/cop/style/if_unless_modifier.rb +4 -4
  70. data/lib/rubocop/cop/style/if_with_semicolon.rb +9 -1
  71. data/lib/rubocop/cop/style/inline_comment.rb +1 -1
  72. data/lib/rubocop/cop/style/invertible_unless_condition.rb +25 -5
  73. data/lib/rubocop/cop/style/keyword_arguments_merging.rb +4 -0
  74. data/lib/rubocop/cop/style/keyword_parameters_order.rb +7 -3
  75. data/lib/rubocop/cop/style/lambda.rb +7 -1
  76. data/lib/rubocop/cop/style/lambda_call.rb +11 -0
  77. data/lib/rubocop/cop/style/map_compact_with_conditional_block.rb +11 -0
  78. data/lib/rubocop/cop/style/map_into_array.rb +1 -1
  79. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +11 -5
  80. data/lib/rubocop/cop/style/method_def_parentheses.rb +5 -1
  81. data/lib/rubocop/cop/style/min_max_comparison.rb +3 -0
  82. data/lib/rubocop/cop/style/missing_respond_to_missing.rb +10 -7
  83. data/lib/rubocop/cop/style/mixin_usage.rb +1 -1
  84. data/lib/rubocop/cop/style/module_function.rb +4 -2
  85. data/lib/rubocop/cop/style/multiline_if_then.rb +1 -1
  86. data/lib/rubocop/cop/style/multiline_memoization.rb +7 -1
  87. data/lib/rubocop/cop/style/multiline_method_signature.rb +11 -4
  88. data/lib/rubocop/cop/style/nil_lambda.rb +8 -0
  89. data/lib/rubocop/cop/style/numeric_predicate.rb +1 -1
  90. data/lib/rubocop/cop/style/open_struct_use.rb +1 -1
  91. data/lib/rubocop/cop/style/option_hash.rb +1 -1
  92. data/lib/rubocop/cop/style/optional_arguments.rb +1 -0
  93. data/lib/rubocop/cop/style/parallel_assignment.rb +11 -2
  94. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -0
  95. data/lib/rubocop/cop/style/perl_backrefs.rb +5 -3
  96. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +13 -1
  97. data/lib/rubocop/cop/style/redundant_exception.rb +6 -0
  98. data/lib/rubocop/cop/style/redundant_filter_chain.rb +1 -1
  99. data/lib/rubocop/cop/style/redundant_format.rb +28 -0
  100. data/lib/rubocop/cop/style/redundant_heredoc_delimiter_quotes.rb +6 -1
  101. data/lib/rubocop/cop/style/redundant_line_continuation.rb +11 -3
  102. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -4
  103. data/lib/rubocop/cop/style/redundant_self.rb +9 -0
  104. data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +23 -4
  105. data/lib/rubocop/cop/style/semicolon.rb +4 -4
  106. data/lib/rubocop/cop/style/single_line_do_end_block.rb +17 -4
  107. data/lib/rubocop/cop/style/string_hash_keys.rb +1 -0
  108. data/lib/rubocop/cop/style/struct_inheritance.rb +3 -1
  109. data/lib/rubocop/cop/style/ternary_parentheses.rb +11 -0
  110. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +7 -8
  111. data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
  112. data/lib/rubocop/cop/team.rb +18 -4
  113. data/lib/rubocop/runner.rb +55 -13
  114. data/lib/rubocop/version.rb +1 -1
  115. metadata +2 -2
@@ -75,9 +75,10 @@ module RuboCop
75
75
  MSG = 'Remove debugger entry point `%<source>s`.'
76
76
 
77
77
  def on_send(node)
78
+ return unless debugger_method?(node) || debugger_require?(node)
78
79
  return if assumed_usage_context?(node)
79
80
 
80
- add_offense(node) if debugger_method?(node) || debugger_require?(node)
81
+ add_offense(node)
81
82
  end
82
83
 
83
84
  private
@@ -101,9 +102,20 @@ module RuboCop
101
102
  end
102
103
 
103
104
  def debugger_method?(send_node)
105
+ return false unless debugger_method_names.include?(send_node.method_name)
106
+
104
107
  debugger_methods.include?(chained_method_name(send_node))
105
108
  end
106
109
 
110
+ # The last segment of each configured debugger method, used to cheaply
111
+ # rule out the vast majority of `send` nodes before building the
112
+ # chained method name.
113
+ def debugger_method_names
114
+ @debugger_method_names ||= debugger_methods.to_set do |method|
115
+ method.to_s.split('.').last&.to_sym
116
+ end
117
+ end
118
+
107
119
  def debugger_require?(send_node)
108
120
  return false unless send_node.method?(:require) && send_node.arguments.one?
109
121
  return false unless (argument = send_node.first_argument).str_type?
@@ -15,6 +15,13 @@ module RuboCop
15
15
  # can't determine the type of `x`. If `x` is an `Array` or `String`, it doesn't perform
16
16
  # a numeric operation.
17
17
  #
18
+ # @safety
19
+ # This cop is unsafe because the autocorrection drops the operands, which
20
+ # discards any side effects of evaluating them and can change behavior when
21
+ # the result is not actually constant. For example, `x / x` raises
22
+ # `ZeroDivisionError` when `x` is `0`, and returns `Float::NAN` (not `1`)
23
+ # when `x` is `0.0`; replacing it with `1` silences that.
24
+ #
18
25
  # @example
19
26
  #
20
27
  # # bad
@@ -109,9 +109,11 @@ module RuboCop
109
109
  when :optarg
110
110
  send_arg.source == def_arg_name.to_s
111
111
  when :kwoptarg, :kwarg
112
- send_arg.hash_type? &&
112
+ keyword_hash_argument?(send_arg) &&
113
113
  send_arg.pairs.any? { |pair| passing_keyword_arg?(pair, def_arg_name) }
114
114
  when :kwrestarg
115
+ return false unless keyword_hash_argument?(send_arg)
116
+
115
117
  send_arg.each_child_node(:kwsplat, :forwarded_kwrestarg).any? do |child|
116
118
  child.source == def_arg.source
117
119
  end
@@ -120,6 +122,10 @@ module RuboCop
120
122
  end
121
123
  end
122
124
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
125
+
126
+ def keyword_hash_argument?(send_arg)
127
+ send_arg.hash_type? && !send_arg.braces?
128
+ end
123
129
  end
124
130
  end
125
131
  end
@@ -36,9 +36,16 @@ module RuboCop
36
36
  # The following used `*_args` because `to_json(*args)` has
37
37
  # an offense of `Lint/UnusedMethodArgument` cop if `*args`
38
38
  # is not used.
39
- corrector.insert_after(node.loc.name, '(*_args)')
39
+ if (opening_parenthesis = node.each_child_node(:args).first.loc.begin)
40
+ # Explicit empty parentheses (`def to_json()`) are already present,
41
+ # so insert the argument inside them to avoid producing `to_json(*_args)()`.
42
+ corrector.insert_after(opening_parenthesis, '*_args')
43
+ else
44
+ corrector.insert_after(node.loc.name, '(*_args)')
45
+ end
40
46
  end
41
47
  end
48
+ alias on_defs on_def
42
49
  end
43
50
  end
44
51
  end
@@ -44,9 +44,7 @@ module RuboCop
44
44
 
45
45
  def on_regexp(node)
46
46
  RuboCop::Util.silence_warnings do
47
- node.parsed_tree&.each_expression do |expr|
48
- detect_offenses(node, expr)
49
- end
47
+ detect_offenses_in_tree(node, node.parsed_tree)
50
48
  end
51
49
  end
52
50
 
@@ -55,19 +53,43 @@ module RuboCop
55
53
  return if node.each_descendant(:dstr).any?
56
54
 
57
55
  regexp_constructor(node) do |text|
58
- parse_regexp(text.value)&.each_expression do |expr|
59
- detect_offenses(text, expr)
60
- end
56
+ detect_offenses_in_tree(text, parse_regexp(text.value))
61
57
  end
62
58
  end
63
59
 
64
60
  private
65
61
 
66
- def detect_offenses(node, expr)
67
- return unless expr.type?(:literal)
62
+ # When a character class opens with a bare `]` (e.g. `[^]]`), `regexp_parser` parses
63
+ # `[^]` / `[]` as an empty set and reports the closing `]` as a separate literal.
64
+ # Ruby treats that `]` as the end of the class, not as an unescaped bracket,
65
+ # so the first `]` following an empty set must be skipped.
66
+ def detect_offenses_in_tree(node, tree)
67
+ return unless tree
68
+
69
+ skip_class_closer = false
70
+ tree.each_expression do |expr|
71
+ if empty_character_set?(expr)
72
+ skip_class_closer = true
73
+ elsif expr.type?(:literal)
74
+ skip_class_closer = detect_offenses(node, expr, skip_class_closer)
75
+ end
76
+ end
77
+ end
78
+
79
+ def empty_character_set?(expr)
80
+ expr.type?(:set) && expr.expressions.empty?
81
+ end
68
82
 
83
+ def detect_offenses(node, expr, skip_class_closer)
69
84
  expr.text.scan(/(?<!\\)\]/) do
70
85
  pos = Regexp.last_match.begin(0)
86
+
87
+ # The first `]` following an empty `[^]` / `[]` set closes the character class.
88
+ if skip_class_closer
89
+ skip_class_closer = false
90
+ next
91
+ end
92
+
71
93
  # If the unescaped bracket is the first character of the regexp, Ruby does not warn.
72
94
  # `pos` is relative to the sub-expression, so add its start offset (`expr.ts`).
73
95
  next if (expr.ts + pos).zero?
@@ -78,6 +100,8 @@ module RuboCop
78
100
  corrector.replace(location, '\]')
79
101
  end
80
102
  end
103
+
104
+ skip_class_closer
81
105
  end
82
106
 
83
107
  def range_at_index(node, index, offset)
@@ -52,10 +52,15 @@ module RuboCop
52
52
  def on_begin(node)
53
53
  expressions = *node
54
54
 
55
- expressions.each_cons(2) do |expression1, expression2|
56
- next unless flow_expression?(expression1)
57
-
58
- add_offense(expression2)
55
+ # Once a flow-of-control statement is reached, every following statement
56
+ # in the block is unreachable, not just the one immediately after it.
57
+ flow_reached = false
58
+ expressions.each_with_index do |expression, index|
59
+ if flow_reached
60
+ add_offense(expression)
61
+ elsif index < expressions.size - 1 && flow_expression?(expression)
62
+ flow_reached = true
63
+ end
59
64
  end
60
65
  end
61
66
 
@@ -36,31 +36,31 @@ module RuboCop
36
36
  RESTRICT_ON_SEND = %i[+ - * / **].freeze
37
37
 
38
38
  # @!method useless_operation?(node)
39
- def_node_matcher :useless_operation?, '(call (call nil? $_) $_ (int $_))'
39
+ def_node_matcher :useless_operation?, <<~PATTERN
40
+ (call ${lvar ivar cvar gvar const (send nil? _)} $_ (int $_))
41
+ PATTERN
40
42
 
41
43
  # @!method useless_abbreviated_assignment?(node)
42
- def_node_matcher :useless_abbreviated_assignment?, '(op-asgn (lvasgn $_) $_ (int $_))'
44
+ def_node_matcher :useless_abbreviated_assignment?, <<~PATTERN
45
+ (op-asgn ${lvasgn ivasgn cvasgn gvasgn casgn} $_ (int $_))
46
+ PATTERN
43
47
 
44
48
  def on_send(node)
45
- return unless useless_operation?(node)
46
-
47
- variable, operation, number = useless_operation?(node)
49
+ return unless (receiver, operation, number = useless_operation?(node))
48
50
  return unless useless?(operation, number)
49
51
 
50
52
  add_offense(node) do |corrector|
51
- corrector.replace(node, variable)
53
+ corrector.replace(node, receiver.source)
52
54
  end
53
55
  end
54
56
  alias on_csend on_send
55
57
 
56
58
  def on_op_asgn(node)
57
- return unless useless_abbreviated_assignment?(node)
58
-
59
- variable, operation, number = useless_abbreviated_assignment?(node)
59
+ return unless (variable, operation, number = useless_abbreviated_assignment?(node))
60
60
  return unless useless?(operation, number)
61
61
 
62
62
  add_offense(node) do |corrector|
63
- corrector.replace(node, "#{variable} = #{variable}")
63
+ corrector.replace(node, "#{variable.source} = #{variable.source}")
64
64
  end
65
65
  end
66
66
 
@@ -212,7 +212,7 @@ module RuboCop
212
212
  end
213
213
 
214
214
  def check_nonmutating(node)
215
- return unless node.type?(:send, :any_block)
215
+ return unless node.type?(:call, :any_block)
216
216
 
217
217
  method_name = node.method_name
218
218
  return unless NONMUTATING_METHODS.include?(method_name)
@@ -265,7 +265,7 @@ module RuboCop
265
265
  end
266
266
 
267
267
  def autocorrect_nonmutating_send(corrector, node, suggestion)
268
- send_node = if node.send_type?
268
+ send_node = if node.call_type?
269
269
  node
270
270
  else
271
271
  node.send_node
@@ -58,7 +58,7 @@ module RuboCop
58
58
  return unless node.method?(:define_method)
59
59
 
60
60
  method_name = node.send_node.first_argument
61
- return if method_name.basic_literal? && allowed?(method_name.value)
61
+ return if method_name&.basic_literal? && allowed?(method_name.value)
62
62
 
63
63
  check_code_length(node)
64
64
  end
@@ -12,9 +12,15 @@ module RuboCop
12
12
  # nodes count. In contrast to the CyclomaticComplexity cop, this cop
13
13
  # considers `else` nodes as adding complexity.
14
14
  #
15
+ # A `case`/`in` branch whose pattern is a simple literal (e.g. `in 1`, `in "red"`, `in 1..10`)
16
+ # or a constant/type (e.g. `in Integer`) and has no guard is just as easy to read as a `when`
17
+ # branch, so it is discounted the same way. Branches with structural patterns (e.g. array,
18
+ # hash, or find patterns), bindings, alternatives, or a guard add the full complexity of
19
+ # a decision point.
20
+ #
15
21
  # @example
16
22
  #
17
- # def my_method # 1
23
+ # def example_1 # 1
18
24
  # if cond # 1
19
25
  # case var # 2 (0.8 + 4 * 0.2, rounded)
20
26
  # when 1 then func_one
@@ -26,33 +32,58 @@ module RuboCop
26
32
  # do_something until a && b # 2
27
33
  # end # ===
28
34
  # end # 7 complexity points
35
+ #
36
+ # def example_2 # 1
37
+ # case color # 1 (3 * 0.2, rounded)
38
+ # in "red" then func_red
39
+ # in "blue" then func_blue
40
+ # in "green" then func_green
41
+ # end # ===
42
+ # end # 2 complexity points
29
43
  class PerceivedComplexity < CyclomaticComplexity
30
44
  MSG = 'Perceived complexity for `%<method>s` is too high. [%<complexity>d/%<max>d]'
31
45
 
32
- COUNTED_NODES = (CyclomaticComplexity::COUNTED_NODES - [:when] + [:case]).freeze
46
+ COUNTED_NODES = (
47
+ CyclomaticComplexity::COUNTED_NODES - %i[when in_pattern] + %i[case case_match]
48
+ ).freeze
33
49
 
34
50
  private
35
51
 
52
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
36
53
  def complexity_score_for(node)
37
54
  case node.type
38
55
  when :case
39
- # If cond is nil, that means each when has an expression that
40
- # evaluates to true or false. It's just an alternative to
41
- # if/elsif/elsif... so the when nodes count.
56
+ # If cond is nil, that means each when has an expression that evaluates to true or
57
+ # false. It's just an alternative to if/elsif/elsif... so the when nodes count.
42
58
  nb_branches = node.when_branches.length + (node.else_branch ? 1 : 0)
43
59
  if node.condition.nil?
44
60
  nb_branches
45
61
  else
46
- # Otherwise, the case node gets 0.8 complexity points and each
47
- # when gets 0.2.
62
+ # Otherwise, the case node gets 0.8 complexity points and each when gets 0.2.
48
63
  ((nb_branches * 0.2) + 0.8).round
49
64
  end
65
+ when :case_match
66
+ # Simple `in` branches are discounted like `when`, while structural patterns keep
67
+ # the full complexity of a decision point.
68
+ score = node.in_pattern_branches.sum { |branch| simple_in_pattern?(branch) ? 0.2 : 1 }
69
+ score += 0.2 if node.else_branch
70
+ score.round
50
71
  when :if
51
72
  node.else? && !node.elsif? ? 2 : 1
52
73
  else
53
74
  super
54
75
  end
55
76
  end
77
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
78
+
79
+ def simple_in_pattern?(in_pattern_node)
80
+ # `in_pattern_node.children[1]` is the guard (`if`/`unless`), or `nil`.
81
+ return false unless in_pattern_node.children[1].nil?
82
+
83
+ # A scalar literal, a literal range, or a constant/type is as easy to read as a `when`.
84
+ pattern = in_pattern_node.pattern
85
+ pattern.literal? || pattern.const_type?
86
+ end
56
87
  end
57
88
  end
58
89
  end
@@ -25,11 +25,11 @@ module RuboCop
25
25
 
26
26
  # @api public
27
27
  def allowed_methods
28
- if cop_config_deprecated_values.any?(Regexp)
29
- cop_config_allowed_methods
30
- else
31
- cop_config_allowed_methods + cop_config_deprecated_values
32
- end
28
+ @allowed_methods ||= if cop_config_deprecated_values.any?(Regexp)
29
+ cop_config_allowed_methods
30
+ else
31
+ cop_config_allowed_methods + cop_config_deprecated_values
32
+ end
33
33
  end
34
34
 
35
35
  def cop_config_allowed_methods
@@ -27,7 +27,7 @@ module RuboCop
27
27
  end
28
28
 
29
29
  def matches_allowed_pattern?(line)
30
- allowed_patterns.any? { |pattern| Regexp.new(pattern).match?(line) }
30
+ allowed_pattern_regexps.any? { |pattern| pattern.match?(line) }
31
31
  end
32
32
 
33
33
  # @deprecated Use matches_allowed_pattern? instead
@@ -49,6 +49,10 @@ module RuboCop
49
49
  end
50
50
  end
51
51
 
52
+ def allowed_pattern_regexps
53
+ @allowed_pattern_regexps ||= allowed_patterns.map { |pattern| Regexp.new(pattern) }
54
+ end
55
+
52
56
  def cop_config_patterns_values
53
57
  @cop_config_patterns_values ||=
54
58
  Array(cop_config.fetch('AllowedPatterns', [])) +
@@ -5,12 +5,16 @@ module RuboCop
5
5
  # This module encapsulates the ability to forbid certain patterns in a cop.
6
6
  module ForbiddenPattern
7
7
  def forbidden_pattern?(name)
8
- forbidden_patterns.any? { |pattern| Regexp.new(pattern).match?(name) }
8
+ forbidden_pattern_regexps.any? { |pattern| pattern.match?(name) }
9
9
  end
10
10
 
11
11
  def forbidden_patterns
12
12
  cop_config.fetch('ForbiddenPatterns', [])
13
13
  end
14
+
15
+ def forbidden_pattern_regexps
16
+ @forbidden_pattern_regexps ||= forbidden_patterns.map { |pattern| Regexp.new(pattern) }
17
+ end
14
18
  end
15
19
  end
16
20
  end
@@ -182,10 +182,18 @@ module RuboCop
182
182
  return ":\"#{value.source}\"" if value.dsym_type?
183
183
  return "\"#{value.source}\"" if value.dstr_type?
184
184
  return ":#{value.source}" if value.sym_type?
185
+ # The element of a `%w` array can contain characters that are special
186
+ # inside a single-quoted string (e.g. a `'`), so escape them rather than
187
+ # wrapping the raw source.
188
+ return to_single_quoted(value.value) if value.str_type?
185
189
 
186
190
  "'#{value.source}'"
187
191
  end
188
192
 
193
+ def to_single_quoted(string)
194
+ "'#{string.gsub(/['\\]/) { |character| "\\#{character}" }}'"
195
+ end
196
+
189
197
  def except_key(node)
190
198
  key_arg = node.argument_list.first.source
191
199
  body, = extract_body_if_negated(node.body)
@@ -94,6 +94,10 @@ module RuboCop
94
94
 
95
95
  return unless captures.use_transformed_argname?
96
96
 
97
+ # A splat transforming expression (e.g. `[k, *v]`) can't be used as a
98
+ # standalone block return value, so the rewrite would produce invalid Ruby.
99
+ return if captures.transforming_body_expr.splat_type?
100
+
97
101
  message = "Prefer `#{new_method_name}` over `#{match_desc}`."
98
102
  add_offense(node, message: message) do |corrector|
99
103
  correction = prepare_correction(node)
@@ -75,7 +75,7 @@ module RuboCop
75
75
  end
76
76
 
77
77
  def first_line_comment(node)
78
- comment = processed_source.comments.find { |c| same_line?(c, node) }
78
+ comment = processed_source.comment_at_line(node.first_line)
79
79
  return unless comment
80
80
 
81
81
  comment_source = comment.source
@@ -203,9 +203,10 @@ module RuboCop
203
203
 
204
204
  def match_acronym?(expected, name)
205
205
  expected = expected.to_s
206
- name = name.to_s
207
-
208
- allowed_acronyms.any? { |acronym| expected.gsub(acronym.capitalize, acronym) == name }
206
+ name = allowed_acronyms.reduce(name.to_s) do |result, acronym|
207
+ result.gsub(acronym, acronym.capitalize)
208
+ end
209
+ expected == name
209
210
  end
210
211
 
211
212
  def to_namespace(path) # rubocop:disable Metrics/AbcSize
@@ -91,6 +91,8 @@ module RuboCop
91
91
  end
92
92
 
93
93
  def on_new_investigation
94
+ return unless @flagged_terms_regex
95
+
94
96
  investigate_filepath if cop_config['CheckFilepaths']
95
97
  investigate_tokens
96
98
  end
@@ -144,7 +146,7 @@ module RuboCop
144
146
  def preprocess_flagged_terms
145
147
  allowed_strings = []
146
148
  flagged_term_strings = []
147
- cop_config['FlaggedTerms'].each do |term, term_definition|
149
+ (cop_config['FlaggedTerms'] || {}).each do |term, term_definition|
148
150
  next if term_definition.nil?
149
151
 
150
152
  allowed_strings.concat(process_allowed_regex(term_definition['AllowedRegex']))
@@ -181,7 +183,11 @@ module RuboCop
181
183
  end
182
184
 
183
185
  def set_regexes(flagged_term_strings, allowed_strings)
184
- @flagged_terms_regex = array_to_ignorecase_regex(flagged_term_strings)
186
+ # With no flagged terms an empty regex would match everything, so leave the
187
+ # regex nil and let `on_new_investigation` treat the cop as a no-op.
188
+ unless flagged_term_strings.empty?
189
+ @flagged_terms_regex = array_to_ignorecase_regex(flagged_term_strings)
190
+ end
185
191
  @allowed_regex = array_to_ignorecase_regex(allowed_strings) unless allowed_strings.empty?
186
192
  end
187
193
 
@@ -174,6 +174,7 @@ module RuboCop
174
174
 
175
175
  method_node, method_name = find_definition(node)
176
176
  return unless method_node
177
+ return unless nameable_method?(method_name)
177
178
 
178
179
  body = method_node.body
179
180
  return unless body == node || body.children.last == node
@@ -209,6 +210,7 @@ module RuboCop
209
210
 
210
211
  method_node, method_name = find_definition(node)
211
212
  return false unless method_node
213
+ return false unless nameable_method?(method_name)
212
214
 
213
215
  defined_memoized?(method_node.body, arg.name) do |defined_ivar, return_ivar, ivar_assign|
214
216
  return false if matches?(method_name, ivar_assign)
@@ -250,6 +252,13 @@ module RuboCop
250
252
  nil
251
253
  end
252
254
 
255
+ # Operator and other non-word method names (e.g. `[]`, `+`, `<=>`) cannot form a
256
+ # valid instance variable name, so there is no matching ivar to enforce and a
257
+ # suggested correction like `@[]` would be invalid Ruby.
258
+ def nameable_method?(method_name)
259
+ /\A[a-zA-Z_]\w*\z/.match?(method_name.to_s.delete('!?='))
260
+ end
261
+
253
262
  def matches?(method_name, ivar_assign)
254
263
  return true if ivar_assign.nil? || INITIALIZE_METHODS.include?(method_name)
255
264
 
@@ -95,11 +95,14 @@ module RuboCop
95
95
 
96
96
  def autocorrect(corrector, node, range, offending_name, preferred_name)
97
97
  corrector.replace(range, preferred_name)
98
- correct_node(corrector, node.body, offending_name, preferred_name)
98
+ # Once the exception variable is reassigned, later references point to a
99
+ # different value, so stop correcting after the reassignment - both in the
100
+ # body and in the code following the `begin`/`rescue`.
101
+ return if correct_node(corrector, node.body, offending_name, preferred_name)
99
102
  return unless (kwbegin_node = node.parent.each_ancestor(:kwbegin).first)
100
103
 
101
104
  kwbegin_node.right_siblings.each do |child_node|
102
- correct_node(corrector, child_node, offending_name, preferred_name)
105
+ break if correct_node(corrector, child_node, offending_name, preferred_name)
103
106
  end
104
107
  end
105
108
 
@@ -113,6 +116,8 @@ module RuboCop
113
116
  end
114
117
  end
115
118
 
119
+ # Returns the reassignment node once the exception variable is reassigned (a truthy
120
+ # signal to stop correcting later references), or `nil` when no reassignment is found.
116
121
  # rubocop:disable Metrics/MethodLength
117
122
  def correct_node(corrector, node, offending_name, preferred_name)
118
123
  return unless node
@@ -131,9 +136,10 @@ module RuboCop
131
136
 
132
137
  if child_node.type?(:masgn, :lvasgn)
133
138
  correct_reassignment(corrector, child_node, offending_name, preferred_name)
134
- break
139
+ return child_node
135
140
  end
136
141
  end
142
+ nil
137
143
  end
138
144
  # rubocop:enable Metrics/MethodLength
139
145
 
@@ -37,7 +37,7 @@ module RuboCop
37
37
  return unless (receiver = node.receiver) && receiver.source == 'IO'
38
38
 
39
39
  argument = node.first_argument
40
- return if argument.respond_to?(:value) && argument.value.strip.start_with?('|')
40
+ return if argument&.str_type? && argument.value.strip.start_with?('|')
41
41
 
42
42
  add_offense(node, message: format(MSG, method_name: node.method_name)) do |corrector|
43
43
  corrector.replace(receiver, 'File')
@@ -25,7 +25,7 @@ module RuboCop
25
25
  # @!method marshal_load(node)
26
26
  def_node_matcher :marshal_load, <<~PATTERN
27
27
  (send (const {nil? cbase} :Marshal) ${:load :restore}
28
- !(send (const {nil? cbase} :Marshal) :dump ...))
28
+ !(send (const {nil? cbase} :Marshal) :dump ...) _?)
29
29
  PATTERN
30
30
 
31
31
  def on_send(node)
@@ -203,13 +203,23 @@ module RuboCop
203
203
  end
204
204
 
205
205
  def range_with_trailing_argument_comment(node)
206
- comment = processed_source.ast_with_comments[node.last_argument].last
206
+ comment = trailing_argument_comment(node)
207
207
  if comment
208
208
  add_range(node.source_range, comment.source_range)
209
209
  else
210
210
  node
211
211
  end
212
212
  end
213
+
214
+ # For a single-line declaration the parser associates the trailing
215
+ # comment with the first argument, not `last_argument`, so look through
216
+ # all arguments for a comment that trails the whole node.
217
+ def trailing_argument_comment(node)
218
+ comments = node.arguments.filter_map do |argument|
219
+ processed_source.ast_with_comments[argument].last
220
+ end
221
+ comments.find { |comment| comment.source_range.begin_pos >= node.source_range.end_pos }
222
+ end
213
223
  end
214
224
  end
215
225
  end
@@ -153,7 +153,7 @@ module RuboCop
153
153
  BLOCK_MSG = 'Use anonymous block arguments forwarding (`&`).'
154
154
 
155
155
  def self.autocorrect_incompatible_with
156
- [Naming::BlockForwarding]
156
+ [Naming::BlockForwarding, Style::MethodDefParentheses]
157
157
  end
158
158
 
159
159
  def on_def(node)
@@ -47,7 +47,7 @@ module RuboCop
47
47
  #
48
48
  # # bad
49
49
  # array1.any? { |elem| array2.member?(elem) }
50
- # array1.none? { |elem| array2.member?(elem) }
50
+ # array1.none? { |elem| array2.include?(elem) }
51
51
  #
52
52
  # # good
53
53
  # array1.intersect?(array2)
@@ -121,15 +121,15 @@ module RuboCop
121
121
  (block
122
122
  (call $_receiver ${:any? :none?})
123
123
  (args (arg _key))
124
- (send $_argument :member? (lvar _key))
124
+ (send $_argument {:member? :include?} (lvar _key))
125
125
  )
126
126
  (numblock
127
127
  (call $_receiver ${:any? :none?}) 1
128
- (send $_argument :member? (lvar :_1))
128
+ (send $_argument {:member? :include?} (lvar :_1))
129
129
  )
130
130
  (itblock
131
131
  (call $_receiver ${:any? :none?}) :it
132
- (send $_argument :member? (lvar :it))
132
+ (send $_argument {:member? :include?} (lvar :it))
133
133
  )
134
134
  }
135
135
  PATTERN
@@ -96,7 +96,7 @@ module RuboCop
96
96
 
97
97
  # @!method grep_v_with_nil?(node)
98
98
  def_node_matcher :grep_v_with_nil?, <<~PATTERN
99
- (send _ :grep_v {(nil) (const {nil? cbase} :NilClass)})
99
+ (call _ :grep_v {(nil) (const {nil? cbase} :NilClass)})
100
100
  PATTERN
101
101
 
102
102
  def on_send(node)
@@ -61,6 +61,10 @@ module RuboCop
61
61
 
62
62
  def correct_parent(parent, corrector)
63
63
  if parent.block_type?
64
+ # Convert a brace block to `do`, so the class's own `end` closes it
65
+ # once the closing delimiter is removed; otherwise a dangling `{` is
66
+ # left behind, producing invalid Ruby.
67
+ corrector.replace(parent.loc.begin, 'do') if parent.braces?
64
68
  corrector.remove(range_with_surrounding_space(parent.loc.end, newlines: false))
65
69
  elsif (class_node = parent.parent).body.nil?
66
70
  corrector.remove(range_for_empty_class_body(class_node, parent))