rubocop 1.41.0 → 1.43.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +2 -2
  4. data/config/default.yml +34 -2
  5. data/lib/rubocop/cli.rb +1 -1
  6. data/lib/rubocop/config.rb +7 -7
  7. data/lib/rubocop/config_loader_resolver.rb +5 -1
  8. data/lib/rubocop/cop/base.rb +62 -61
  9. data/lib/rubocop/cop/cop.rb +28 -28
  10. data/lib/rubocop/cop/corrector.rb +23 -11
  11. data/lib/rubocop/cop/gemspec/dependency_version.rb +16 -18
  12. data/lib/rubocop/cop/layout/class_structure.rb +32 -11
  13. data/lib/rubocop/cop/layout/comment_indentation.rb +3 -1
  14. data/lib/rubocop/cop/layout/indentation_style.rb +4 -1
  15. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +6 -6
  16. data/lib/rubocop/cop/layout/multiline_block_layout.rb +1 -1
  17. data/lib/rubocop/cop/layout/space_around_keyword.rb +1 -1
  18. data/lib/rubocop/cop/layout/trailing_whitespace.rb +5 -2
  19. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +19 -0
  20. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +3 -1
  21. data/lib/rubocop/cop/lint/regexp_as_condition.rb +6 -0
  22. data/lib/rubocop/cop/lint/require_parentheses.rb +3 -1
  23. data/lib/rubocop/cop/lint/unused_method_argument.rb +2 -1
  24. data/lib/rubocop/cop/lint/useless_rescue.rb +71 -0
  25. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +5 -3
  26. data/lib/rubocop/cop/metrics/parameter_lists.rb +27 -0
  27. data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -1
  28. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +4 -4
  29. data/lib/rubocop/cop/mixin/annotation_comment.rb +1 -1
  30. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +9 -1
  31. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +1 -1
  32. data/lib/rubocop/cop/mixin/statement_modifier.rb +1 -0
  33. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  34. data/lib/rubocop/cop/registry.rb +22 -22
  35. data/lib/rubocop/cop/security/compound_hash.rb +2 -1
  36. data/lib/rubocop/cop/style/alias.rb +9 -1
  37. data/lib/rubocop/cop/style/block_comments.rb +1 -1
  38. data/lib/rubocop/cop/style/concat_array_literals.rb +22 -2
  39. data/lib/rubocop/cop/style/documentation.rb +10 -4
  40. data/lib/rubocop/cop/style/guard_clause.rb +12 -8
  41. data/lib/rubocop/cop/style/hash_each_methods.rb +13 -1
  42. data/lib/rubocop/cop/style/hash_syntax.rb +11 -7
  43. data/lib/rubocop/cop/style/identical_conditional_branches.rb +15 -0
  44. data/lib/rubocop/cop/style/map_to_set.rb +61 -0
  45. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +12 -9
  46. data/lib/rubocop/cop/style/method_def_parentheses.rb +11 -4
  47. data/lib/rubocop/cop/style/min_max_comparison.rb +73 -0
  48. data/lib/rubocop/cop/style/missing_else.rb +13 -1
  49. data/lib/rubocop/cop/style/operator_method_call.rb +15 -1
  50. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +1 -1
  51. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +2 -1
  52. data/lib/rubocop/cop/style/redundant_string_escape.rb +6 -3
  53. data/lib/rubocop/cop/style/require_order.rb +4 -2
  54. data/lib/rubocop/cop/style/select_by_regexp.rb +6 -2
  55. data/lib/rubocop/cop/style/signal_exception.rb +8 -6
  56. data/lib/rubocop/cop/style/string_hash_keys.rb +4 -1
  57. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +4 -4
  58. data/lib/rubocop/cop/style/word_array.rb +41 -0
  59. data/lib/rubocop/cop/style/yoda_expression.rb +81 -0
  60. data/lib/rubocop/cop/style/zero_length_predicate.rb +31 -14
  61. data/lib/rubocop/cop/team.rb +29 -29
  62. data/lib/rubocop/cop/variable_force.rb +0 -3
  63. data/lib/rubocop/cops_documentation_generator.rb +11 -8
  64. data/lib/rubocop/formatter.rb +2 -0
  65. data/lib/rubocop/path_util.rb +17 -7
  66. data/lib/rubocop/result_cache.rb +1 -1
  67. data/lib/rubocop/runner.rb +10 -3
  68. data/lib/rubocop/target_ruby.rb +0 -1
  69. data/lib/rubocop/version.rb +1 -1
  70. data/lib/rubocop.rb +4 -0
  71. metadata +13 -9
@@ -118,11 +118,23 @@ module RuboCop
118
118
  end
119
119
 
120
120
  def allowed_receiver?(receiver)
121
- receiver_name = receiver.send_type? ? receiver.method_name.to_s : receiver.source
121
+ receiver_name = receiver_name(receiver)
122
122
 
123
123
  allowed_receivers.include?(receiver_name)
124
124
  end
125
125
 
126
+ def receiver_name(receiver)
127
+ if receiver.send_type?
128
+ if receiver.receiver
129
+ "#{receiver_name(receiver.receiver)}.#{receiver.method_name}"
130
+ else
131
+ receiver.method_name.to_s
132
+ end
133
+ else
134
+ receiver.source
135
+ end
136
+ end
137
+
126
138
  def allowed_receivers
127
139
  cop_config.fetch('AllowedReceivers', [])
128
140
  end
@@ -28,7 +28,7 @@ module RuboCop
28
28
  # * always - forces use of the 3.1 syntax (e.g. {foo:})
29
29
  # * never - forces use of explicit hash literal value
30
30
  # * either - accepts both shorthand and explicit use of hash literal value
31
- # * consistent - like "either", but will avoid mixing styles in a single hash
31
+ # * consistent - forces use of the 3.1 syntax only if all values can be omitted in the hash
32
32
  #
33
33
  # @example EnforcedStyle: ruby19 (default)
34
34
  # # bad
@@ -92,16 +92,19 @@ module RuboCop
92
92
  #
93
93
  # @example EnforcedShorthandSyntax: consistent
94
94
  #
95
- # # bad
96
- # {foo: , bar: bar}
95
+ # # bad - `foo` and `bar` values can be omitted
96
+ # {foo: foo, bar: bar}
97
97
  #
98
- # # good
99
- # {foo:, bar:}
98
+ # # bad - `bar` value can be omitted
99
+ # {foo:, bar: bar}
100
100
  #
101
- # # bad
102
- # {foo: , bar: baz}
101
+ # # bad - mixed syntaxes
102
+ # {foo:, bar: baz}
103
103
  #
104
104
  # # good
105
+ # {foo:, bar:}
106
+ #
107
+ # # good - can't omit `baz`
105
108
  # {foo: foo, bar: baz}
106
109
  #
107
110
  class HashSyntax < Base
@@ -251,6 +254,7 @@ module RuboCop
251
254
  op = pair_node.loc.operator
252
255
 
253
256
  key_with_hash_rocket = ":#{pair_node.key.source}#{pair_node.inverse_delimiter(true)}"
257
+ key_with_hash_rocket += pair_node.key.source if pair_node.value_omission?
254
258
  corrector.replace(pair_node.key, key_with_hash_rocket)
255
259
  corrector.remove(range_with_surrounding_space(op))
256
260
  end
@@ -136,6 +136,7 @@ module RuboCop
136
136
 
137
137
  private
138
138
 
139
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
139
140
  def check_branches(node, branches)
140
141
  # return if any branch is empty. An empty branch can be an `if`
141
142
  # without an `else` or a branch that contains only comments.
@@ -144,9 +145,13 @@ module RuboCop
144
145
  tails = branches.map { |branch| tail(branch) }
145
146
  check_expressions(node, tails, :after_condition) if duplicated_expressions?(node, tails)
146
147
 
148
+ return if last_child_of_parent?(node) &&
149
+ branches.any? { |branch| single_child_branch?(branch) }
150
+
147
151
  heads = branches.map { |branch| head(branch) }
148
152
  check_expressions(node, heads, :before_condition) if duplicated_expressions?(node, heads)
149
153
  end
154
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
150
155
 
151
156
  def duplicated_expressions?(node, expressions)
152
157
  unique_expressions = expressions.uniq
@@ -180,6 +185,16 @@ module RuboCop
180
185
  end
181
186
  end
182
187
 
188
+ def last_child_of_parent?(node)
189
+ return true unless (parent = node.parent)
190
+
191
+ parent.child_nodes.last == node
192
+ end
193
+
194
+ def single_child_branch?(branch_node)
195
+ !branch_node.begin_type? || branch_node.children.size == 1
196
+ end
197
+
183
198
  def message(node)
184
199
  format(MSG, source: node.source)
185
200
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Looks for uses of `map.to_set` or `collect.to_set` that could be
7
+ # written with just `to_set`.
8
+ #
9
+ # @safety
10
+ # This cop is unsafe, as it can produce false positives if the receiver
11
+ # is not an `Enumerable`.
12
+ #
13
+ # @example
14
+ # # bad
15
+ # something.map { |i| i * 2 }.to_set
16
+ #
17
+ # # good
18
+ # something.to_set { |i| i * 2 }
19
+ #
20
+ # # bad
21
+ # [1, 2, 3].collect { |i| i.to_s }.to_set
22
+ #
23
+ # # good
24
+ # [1, 2, 3].to_set { |i| i.to_s }
25
+ #
26
+ class MapToSet < Base
27
+ extend AutoCorrector
28
+ include RangeHelp
29
+
30
+ MSG = 'Pass a block to `to_set` instead of calling `%<method>s.to_set`.'
31
+ RESTRICT_ON_SEND = %i[to_set].freeze
32
+
33
+ # @!method map_to_set?(node)
34
+ def_node_matcher :map_to_set?, <<~PATTERN
35
+ $(send (block $(send _ {:map :collect}) ...) :to_set)
36
+ PATTERN
37
+
38
+ def on_send(node)
39
+ return unless (to_set_node, map_node = map_to_set?(node))
40
+
41
+ message = format(MSG, method: map_node.loc.selector.source)
42
+ add_offense(map_node.loc.selector, message: message) do |corrector|
43
+ # If the `to_set` call already has a block, do not autocorrect.
44
+ next if to_set_node.block_node
45
+
46
+ autocorrect(corrector, to_set_node, map_node)
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def autocorrect(corrector, to_set, map)
53
+ removal_range = range_between(to_set.loc.dot.begin_pos, to_set.loc.selector.end_pos)
54
+
55
+ corrector.remove(range_with_surrounding_space(removal_range, side: :left))
56
+ corrector.replace(map.loc.selector, 'to_set')
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -102,20 +102,23 @@ module RuboCop
102
102
  end
103
103
 
104
104
  def call_in_literals?(node)
105
- node.parent &&
106
- (node.parent.pair_type? ||
107
- node.parent.array_type? ||
108
- node.parent.range_type? ||
109
- splat?(node.parent) ||
110
- ternary_if?(node.parent))
105
+ parent = node.parent&.block_type? ? node.parent.parent : node.parent
106
+ return unless parent
107
+
108
+ parent.pair_type? ||
109
+ parent.array_type? ||
110
+ parent.range_type? ||
111
+ splat?(parent) ||
112
+ ternary_if?(parent)
111
113
  end
112
114
 
113
115
  def call_in_logical_operators?(node)
114
116
  parent = node.parent&.block_type? ? node.parent.parent : node.parent
115
- parent &&
116
- (logical_operator?(parent) ||
117
+ return unless parent
118
+
119
+ logical_operator?(parent) ||
117
120
  (parent.send_type? &&
118
- parent.arguments.any? { |argument| logical_operator?(argument) }))
121
+ parent.arguments.any? { |argument| logical_operator?(argument) })
119
122
  end
120
123
 
121
124
  def call_in_optional_arguments?(node)
@@ -10,7 +10,9 @@ module RuboCop
10
10
  #
11
11
  # 1. Endless methods
12
12
  # 2. Argument lists containing a `forward-arg` (`...`)
13
- # 3. Argument lists containing an anonymous block forwarding (`&`)
13
+ # 3. Argument lists containing an anonymous rest arguments forwarding (`*`)
14
+ # 4. Argument lists containing an anonymous keyword rest arguments forwarding (`**`)
15
+ # 5. Argument lists containing an anonymous block forwarding (`&`)
14
16
  #
15
17
  # Removing the parens would be a syntax error here.
16
18
  #
@@ -130,9 +132,11 @@ module RuboCop
130
132
  # Regardless of style, parentheses are necessary for:
131
133
  # 1. Endless methods
132
134
  # 2. Argument lists containing a `forward-arg` (`...`)
133
- # 3. Argument lists containing an anonymous block forwarding (`&`)
135
+ # 3. Argument lists containing an anonymous rest arguments forwarding (`*`)
136
+ # 4. Argument lists containing an anonymous keyword rest arguments forwarding (`**`)
137
+ # 5. Argument lists containing an anonymous block forwarding (`&`)
134
138
  # Removing the parens would be a syntax error here.
135
- node.endless? || node.arguments.any?(&:forward_arg_type?) || anonymous_block_arg?(node)
139
+ node.endless? || anonymous_arguments?(node)
136
140
  end
137
141
 
138
142
  def require_parentheses?(args)
@@ -162,7 +166,10 @@ module RuboCop
162
166
  end
163
167
  end
164
168
 
165
- def anonymous_block_arg?(node)
169
+ def anonymous_arguments?(node)
170
+ return true if node.arguments.any? do |arg|
171
+ arg.forward_arg_type? || arg.restarg_type? || arg.kwrestarg_type?
172
+ end
166
173
  return false unless (last_argument = node.arguments.last)
167
174
 
168
175
  last_argument.blockarg_type? && last_argument.name.nil?
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Enforces the use of `max` or `min` instead of comparison for greater or less.
7
+ #
8
+ # NOTE: It can be used if you want to present limit or threshold in Ruby 2.7+.
9
+ # That it is slow though. So autocorrection will apply generic `max` or `min`:
10
+ #
11
+ # [source,ruby]
12
+ # ----
13
+ # a.clamp(b..) # Same as `[a, b].max`
14
+ # a.clamp(..b) # Same as `[a, b].min`
15
+ # ----
16
+ #
17
+ # @safety
18
+ # This cop is unsafe because even if a value has `<` or `>` method,
19
+ # it is not necessarily `Comparable`.
20
+ #
21
+ # @example
22
+ #
23
+ # # bad
24
+ # a > b ? a : b
25
+ # a >= b ? a : b
26
+ #
27
+ # # good
28
+ # [a, b].max
29
+ #
30
+ # # bad
31
+ # a < b ? a : b
32
+ # a <= b ? a : b
33
+ #
34
+ # # good
35
+ # [a, b].min
36
+ #
37
+ class MinMaxComparison < Base
38
+ extend AutoCorrector
39
+
40
+ MSG = 'Use `%<prefer>s` instead.'
41
+ GRATER_OPERATORS = %i[> >=].freeze
42
+ LESS_OPERATORS = %i[< <=].freeze
43
+ COMPARISON_OPERATORS = GRATER_OPERATORS + LESS_OPERATORS
44
+
45
+ def on_if(node)
46
+ lhs, operator, rhs = *node.condition
47
+ return unless COMPARISON_OPERATORS.include?(operator)
48
+
49
+ if_branch = node.if_branch
50
+ else_branch = node.else_branch
51
+ preferred_method = preferred_method(operator, lhs, rhs, if_branch, else_branch)
52
+ return unless preferred_method
53
+
54
+ replacement = "[#{lhs.source}, #{rhs.source}].#{preferred_method}"
55
+
56
+ add_offense(node, message: format(MSG, prefer: replacement)) do |corrector|
57
+ corrector.replace(node, replacement)
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def preferred_method(operator, lhs, rhs, if_branch, else_branch)
64
+ if lhs == if_branch && rhs == else_branch
65
+ GRATER_OPERATORS.include?(operator) ? 'max' : 'min'
66
+ elsif lhs == else_branch && rhs == if_branch
67
+ LESS_OPERATORS.include?(operator) ? 'max' : 'min'
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -99,6 +99,7 @@ module RuboCop
99
99
  class MissingElse < Base
100
100
  include OnNormalIfUnless
101
101
  include ConfigurableEnforcedStyle
102
+ extend AutoCorrector
102
103
 
103
104
  MSG = '`%<type>s` condition requires an `else`-clause.'
104
105
  MSG_NIL = '`%<type>s` condition requires an `else`-clause with `nil` in it.'
@@ -126,7 +127,9 @@ module RuboCop
126
127
  def check(node)
127
128
  return if node.else?
128
129
 
129
- add_offense(node, message: format(message_template, type: node.type))
130
+ add_offense(node, message: format(message_template, type: node.type)) do |corrector|
131
+ autocorrect(corrector, node)
132
+ end
130
133
  end
131
134
 
132
135
  def message_template
@@ -140,6 +143,15 @@ module RuboCop
140
143
  end
141
144
  end
142
145
 
146
+ def autocorrect(corrector, node)
147
+ case empty_else_style
148
+ when :empty
149
+ corrector.insert_before(node.loc.end, 'else; nil; ')
150
+ when :nil
151
+ corrector.insert_before(node.loc.end, 'else; ')
152
+ end
153
+ end
154
+
143
155
  def if_style?
144
156
  style == :if
145
157
  end
@@ -28,7 +28,7 @@ module RuboCop
28
28
  return if node.receiver.const_type?
29
29
 
30
30
  _lhs, _op, rhs = *node
31
- return if rhs.nil? || rhs.children.first
31
+ return if !rhs || method_call_with_parenthesized_arg?(rhs) || anonymous_forwarding?(rhs)
32
32
 
33
33
  add_offense(dot) do |corrector|
34
34
  wrap_in_parentheses_if_chained(corrector, node)
@@ -38,6 +38,20 @@ module RuboCop
38
38
 
39
39
  private
40
40
 
41
+ # Checks for an acceptable case of `foo.+(bar).baz`.
42
+ def method_call_with_parenthesized_arg?(argument)
43
+ return false unless argument.parent.parent&.send_type?
44
+
45
+ argument.children.first && argument.parent.parenthesized?
46
+ end
47
+
48
+ def anonymous_forwarding?(argument)
49
+ return true if argument.forwarded_args_type? || argument.forwarded_restarg_type?
50
+ return true if argument.children.first&.forwarded_kwrestarg_type?
51
+
52
+ argument.block_pass_type? && argument.source == '&'
53
+ end
54
+
41
55
  def wrap_in_parentheses_if_chained(corrector, node)
42
56
  return unless node.parent&.call_type?
43
57
 
@@ -30,7 +30,7 @@ module RuboCop
30
30
  return unless double_splat_hash_braces?(grandparent)
31
31
 
32
32
  add_offense(grandparent) do |corrector|
33
- corrector.replace(grandparent, node.pairs.map(&:source).join(', '))
33
+ corrector.replace(grandparent, node.children.map(&:source).join(', '))
34
34
  end
35
35
  end
36
36
  end
@@ -77,7 +77,8 @@ module RuboCop
77
77
  # but it's not necessry to escape hyphen if it's the first or last character
78
78
  # within the character class. This method checks if that's the case.
79
79
  # e.g. "[0-9\\-]" or "[\\-0-9]" would return true
80
- node.source[index] == '[' || node.source[index + 3] == ']'
80
+ contents_range(node).source[index - 1] == '[' ||
81
+ contents_range(node).source[index + 2] == ']'
81
82
  end
82
83
 
83
84
  def delimiter?(node, char)
@@ -58,7 +58,7 @@ module RuboCop
58
58
  private
59
59
 
60
60
  def message(range)
61
- format(MSG, char: range.source.chars.last)
61
+ format(MSG, char: range.source[-1])
62
62
  end
63
63
 
64
64
  def str_contents_range(node)
@@ -76,6 +76,7 @@ module RuboCop
76
76
  node.loc.to_hash.key?(:begin) && !node.loc.begin.nil?
77
77
  end
78
78
 
79
+ # rubocop:disable Metrics/CyclomaticComplexity
79
80
  def allowed_escape?(node, range)
80
81
  escaped = range.source[(1..-1)]
81
82
 
@@ -88,13 +89,14 @@ module RuboCop
88
89
  # with different versions of Ruby so that e.g. /\d/ != /d/
89
90
  return true if /[\n\\[[:alnum:]]]/.match?(escaped[0])
90
91
 
91
- return true if escaped[0] == ' ' && percent_array_literal?(node)
92
+ return true if escaped[0] == ' ' && (percent_array_literal?(node) || node.heredoc?)
92
93
 
93
94
  return true if disabling_interpolation?(range)
94
95
  return true if delimiter?(node, escaped[0])
95
96
 
96
97
  false
97
98
  end
99
+ # rubocop:enable Metrics/CyclomaticComplexity
98
100
 
99
101
  def interpolation_not_enabled?(node)
100
102
  single_quoted?(node) ||
@@ -169,9 +171,10 @@ module RuboCop
169
171
  # Allow \#{foo}, \#$foo, \#@foo, and \#@@foo
170
172
  # for escaping local, global, instance and class variable interpolations
171
173
  return true if range.source.match?(/\A\\#[{$@]/)
172
-
173
174
  # Also allow #\{foo}, #\$foo, #\@foo and #\@@foo
174
175
  return true if range.adjust(begin_pos: -2).source.match?(/\A[^\\]#\\[{$@]/)
176
+ # For `\#\{foo} allow `\#` and warn `\{`
177
+ return true if range.adjust(end_pos: 1).source == '\\#\\{'
175
178
 
176
179
  false
177
180
  end
@@ -81,7 +81,7 @@ module RuboCop
81
81
  PATTERN
82
82
 
83
83
  def on_send(node)
84
- return unless node.arguments?
84
+ return unless node.parent && node.arguments?
85
85
  return if not_modifier_form?(node.parent)
86
86
 
87
87
  previous_older_sibling = find_previous_older_sibling(node)
@@ -102,8 +102,10 @@ module RuboCop
102
102
  node.if_type? && !node.modifier_form?
103
103
  end
104
104
 
105
- def find_previous_older_sibling(node) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity
105
+ def find_previous_older_sibling(node) # rubocop:disable Metrics
106
106
  search_node(node).left_siblings.reverse.find do |sibling|
107
+ next unless sibling.is_a?(AST::Node)
108
+
107
109
  sibling = sibling_node(sibling)
108
110
  break unless sibling&.send_type? && sibling&.method?(node.method_name)
109
111
  break unless sibling.arguments? && !sibling.receiver
@@ -86,12 +86,12 @@ module RuboCop
86
86
 
87
87
  def on_send(node)
88
88
  return unless (block_node = node.block_node)
89
- return if block_node.body.begin_type?
89
+ return if block_node.body&.begin_type?
90
90
  return if receiver_allowed?(block_node.receiver)
91
91
  return unless (regexp_method_send_node = extract_send_node(block_node))
92
92
  return if match_predicate_without_receiver?(regexp_method_send_node)
93
93
 
94
- opposite = regexp_method_send_node.send_type? && regexp_method_send_node.method?(:!~)
94
+ opposite = opposite?(regexp_method_send_node)
95
95
  regexp = find_regexp(regexp_method_send_node, block_node)
96
96
 
97
97
  register_offense(node, block_node, regexp, opposite)
@@ -128,6 +128,10 @@ module RuboCop
128
128
  regexp_method_send_node
129
129
  end
130
130
 
131
+ def opposite?(regexp_method_send_node)
132
+ regexp_method_send_node.send_type? && regexp_method_send_node.method?(:!~)
133
+ end
134
+
131
135
  def find_regexp(node, block)
132
136
  return node.child_nodes.first if node.match_with_lvasgn_type?
133
137
 
@@ -119,11 +119,6 @@ module RuboCop
119
119
  # @!method custom_fail_methods(node)
120
120
  def_node_search :custom_fail_methods, '{(def :fail ...) (defs _ :fail ...)}'
121
121
 
122
- def on_new_investigation
123
- ast = processed_source.ast
124
- @custom_fail_defined = ast && custom_fail_methods(ast).any?
125
- end
126
-
127
122
  def on_rescue(node)
128
123
  return unless style == :semantic
129
124
 
@@ -141,7 +136,7 @@ module RuboCop
141
136
  when :semantic
142
137
  check_send(:raise, node) unless ignored_node?(node)
143
138
  when :only_raise
144
- return if @custom_fail_defined
139
+ return if custom_fail_defined?
145
140
 
146
141
  check_send(:fail, node)
147
142
  when :only_fail
@@ -151,6 +146,13 @@ module RuboCop
151
146
 
152
147
  private
153
148
 
149
+ def custom_fail_defined?
150
+ return @custom_fail_defined if defined?(@custom_fail_defined)
151
+
152
+ ast = processed_source.ast
153
+ @custom_fail_defined = ast && custom_fail_methods(ast).any?
154
+ end
155
+
154
156
  def message(method_name)
155
157
  case style
156
158
  when :semantic
@@ -41,10 +41,13 @@ module RuboCop
41
41
 
42
42
  def on_pair(node)
43
43
  return unless string_hash_key?(node)
44
+
45
+ key_content = node.key.str_content
46
+ return unless key_content.valid_encoding?
44
47
  return if receive_environments_method?(node)
45
48
 
46
49
  add_offense(node.key) do |corrector|
47
- symbol_content = node.key.str_content.to_sym.inspect
50
+ symbol_content = key_content.to_sym.inspect
48
51
 
49
52
  corrector.replace(node.key, symbol_content)
50
53
  end
@@ -88,6 +88,10 @@ module RuboCop
88
88
  include TrailingComma
89
89
  extend AutoCorrector
90
90
 
91
+ def self.autocorrect_incompatible_with
92
+ [Layout::HeredocArgumentClosingParenthesis]
93
+ end
94
+
91
95
  def on_send(node)
92
96
  return unless node.arguments? && node.parenthesized?
93
97
 
@@ -96,10 +100,6 @@ module RuboCop
96
100
  node.source_range.end_pos)
97
101
  end
98
102
  alias on_csend on_send
99
-
100
- def self.autocorrect_incompatible_with
101
- [Layout::HeredocArgumentClosingParenthesis]
102
- end
103
103
  end
104
104
  end
105
105
  end
@@ -27,6 +27,25 @@ module RuboCop
27
27
  # # bad (contains spaces)
28
28
  # %w[foo\ bar baz\ quux]
29
29
  #
30
+ # # bad
31
+ # [
32
+ # ['one', 'One'],
33
+ # ['two', 'Two']
34
+ # ]
35
+ #
36
+ # # good
37
+ # [
38
+ # %w[one One],
39
+ # %w[two Two]
40
+ # ]
41
+ #
42
+ # # good (2d array containing spaces)
43
+ # [
44
+ # ['one', 'One'],
45
+ # ['two', 'Two'],
46
+ # ['forty two', 'Forty Two']
47
+ # ]
48
+ #
30
49
  # @example EnforcedStyle: brackets
31
50
  # # good
32
51
  # ['foo', 'bar', 'baz']
@@ -36,6 +55,19 @@ module RuboCop
36
55
  #
37
56
  # # good (contains spaces)
38
57
  # ['foo bar', 'baz quux']
58
+ #
59
+ # # good
60
+ # [
61
+ # ['one', 'One'],
62
+ # ['two', 'Two']
63
+ # ]
64
+ #
65
+ # # bad
66
+ # [
67
+ # %w[one One],
68
+ # %w[two Two]
69
+ # ]
70
+ #
39
71
  class WordArray < Base
40
72
  include ArrayMinSize
41
73
  include ArraySyntax
@@ -53,6 +85,7 @@ module RuboCop
53
85
  def on_array(node)
54
86
  if bracketed_array_of?(:str, node)
55
87
  return if complex_content?(node.values)
88
+ return if within_2d_array_of_complex_content?(node)
56
89
 
57
90
  check_bracketed_array(node, 'w')
58
91
  elsif node.percent_literal?(:string)
@@ -62,6 +95,14 @@ module RuboCop
62
95
 
63
96
  private
64
97
 
98
+ def within_2d_array_of_complex_content?(node)
99
+ return false unless (parent = node.parent)
100
+
101
+ parent.array_type? &&
102
+ parent.values.all?(&:array_type?) &&
103
+ parent.values.any? { |subarray| complex_content?(subarray.values) }
104
+ end
105
+
65
106
  def complex_content?(strings, complex_regex: word_regex)
66
107
  strings.any? do |s|
67
108
  next unless s.str_content