rubocop 1.58.0 → 1.60.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +3 -3
  4. data/config/default.yml +9 -1
  5. data/lib/rubocop/config.rb +0 -2
  6. data/lib/rubocop/config_loader.rb +0 -1
  7. data/lib/rubocop/config_validator.rb +0 -2
  8. data/lib/rubocop/cop/base.rb +6 -0
  9. data/lib/rubocop/cop/exclude_limit.rb +1 -1
  10. data/lib/rubocop/cop/layout/end_alignment.rb +5 -1
  11. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +16 -1
  12. data/lib/rubocop/cop/layout/heredoc_indentation.rb +1 -1
  13. data/lib/rubocop/cop/layout/line_continuation_leading_space.rb +1 -1
  14. data/lib/rubocop/cop/layout/redundant_line_break.rb +2 -1
  15. data/lib/rubocop/cop/layout/single_line_block_chain.rb +1 -0
  16. data/lib/rubocop/cop/lint/binary_operator_with_identical_operands.rb +2 -2
  17. data/lib/rubocop/cop/lint/constant_overwritten_in_rescue.rb +1 -1
  18. data/lib/rubocop/cop/lint/hash_compare_by_identity.rb +2 -1
  19. data/lib/rubocop/cop/lint/it_without_arguments_in_block.rb +56 -0
  20. data/lib/rubocop/cop/lint/literal_assignment_in_condition.rb +23 -2
  21. data/lib/rubocop/cop/lint/next_without_accumulator.rb +6 -21
  22. data/lib/rubocop/cop/lint/number_conversion.rb +9 -4
  23. data/lib/rubocop/cop/lint/redundant_with_index.rb +2 -2
  24. data/lib/rubocop/cop/lint/redundant_with_object.rb +2 -2
  25. data/lib/rubocop/cop/lint/self_assignment.rb +1 -0
  26. data/lib/rubocop/cop/lint/shadowed_argument.rb +1 -0
  27. data/lib/rubocop/cop/lint/syntax.rb +6 -3
  28. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +2 -2
  29. data/lib/rubocop/cop/lint/useless_times.rb +1 -1
  30. data/lib/rubocop/cop/metrics/abc_size.rb +3 -3
  31. data/lib/rubocop/cop/mixin/configurable_formatting.rb +1 -0
  32. data/lib/rubocop/cop/naming/block_forwarding.rb +10 -2
  33. data/lib/rubocop/cop/naming/constant_name.rb +1 -2
  34. data/lib/rubocop/cop/registry.rb +4 -4
  35. data/lib/rubocop/cop/security/open.rb +2 -2
  36. data/lib/rubocop/cop/style/arguments_forwarding.rb +16 -3
  37. data/lib/rubocop/cop/style/array_first_last.rb +1 -1
  38. data/lib/rubocop/cop/style/class_check.rb +1 -0
  39. data/lib/rubocop/cop/style/collection_compact.rb +7 -6
  40. data/lib/rubocop/cop/style/combinable_loops.rb +11 -0
  41. data/lib/rubocop/cop/style/concat_array_literals.rb +1 -0
  42. data/lib/rubocop/cop/style/conditional_assignment.rb +1 -1
  43. data/lib/rubocop/cop/style/date_time.rb +5 -4
  44. data/lib/rubocop/cop/style/each_for_simple_loop.rb +7 -7
  45. data/lib/rubocop/cop/style/each_with_object.rb +2 -2
  46. data/lib/rubocop/cop/style/eval_with_location.rb +0 -11
  47. data/lib/rubocop/cop/style/exact_regexp_match.rb +2 -1
  48. data/lib/rubocop/cop/style/hash_each_methods.rb +29 -4
  49. data/lib/rubocop/cop/style/hash_except.rb +2 -1
  50. data/lib/rubocop/cop/style/identical_conditional_branches.rb +4 -1
  51. data/lib/rubocop/cop/style/invertible_unless_condition.rb +39 -2
  52. data/lib/rubocop/cop/style/map_compact_with_conditional_block.rb +3 -2
  53. data/lib/rubocop/cop/style/map_to_hash.rb +9 -4
  54. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +14 -5
  55. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +1 -3
  56. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +20 -0
  57. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +1 -3
  58. data/lib/rubocop/cop/style/next.rb +1 -1
  59. data/lib/rubocop/cop/style/numeric_literal_prefix.rb +1 -1
  60. data/lib/rubocop/cop/style/operator_method_call.rb +2 -2
  61. data/lib/rubocop/cop/style/parallel_assignment.rb +2 -2
  62. data/lib/rubocop/cop/style/parentheses_around_condition.rb +8 -0
  63. data/lib/rubocop/cop/style/redundant_argument.rb +1 -0
  64. data/lib/rubocop/cop/style/redundant_each.rb +7 -4
  65. data/lib/rubocop/cop/style/redundant_fetch_block.rb +3 -3
  66. data/lib/rubocop/cop/style/redundant_line_continuation.rb +8 -1
  67. data/lib/rubocop/cop/style/redundant_parentheses.rb +15 -7
  68. data/lib/rubocop/cop/style/redundant_self.rb +17 -2
  69. data/lib/rubocop/cop/style/redundant_sort.rb +8 -7
  70. data/lib/rubocop/cop/style/redundant_sort_by.rb +2 -2
  71. data/lib/rubocop/cop/style/sample.rb +2 -1
  72. data/lib/rubocop/cop/style/select_by_regexp.rb +6 -5
  73. data/lib/rubocop/cop/style/single_argument_dig.rb +5 -2
  74. data/lib/rubocop/cop/style/slicing_with_range.rb +76 -10
  75. data/lib/rubocop/cop/style/string_chars.rb +1 -0
  76. data/lib/rubocop/cop/style/strip.rb +7 -4
  77. data/lib/rubocop/cop/style/symbol_proc.rb +36 -0
  78. data/lib/rubocop/cop/style/unpack_first.rb +11 -14
  79. data/lib/rubocop/cops_documentation_generator.rb +11 -1
  80. data/lib/rubocop/ext/regexp_node.rb +9 -4
  81. data/lib/rubocop/formatter/disabled_config_formatter.rb +17 -6
  82. data/lib/rubocop/formatter/json_formatter.rb +0 -1
  83. data/lib/rubocop/formatter.rb +1 -1
  84. data/lib/rubocop/lsp/routes.rb +1 -1
  85. data/lib/rubocop/options.rb +0 -8
  86. data/lib/rubocop/rspec/shared_contexts.rb +6 -0
  87. data/lib/rubocop/rspec/support.rb +1 -0
  88. data/lib/rubocop/server/cache.rb +0 -1
  89. data/lib/rubocop/version.rb +1 -1
  90. data/lib/rubocop.rb +1 -0
  91. metadata +8 -7
  92. /data/lib/rubocop/formatter/{git_hub_actions_formatter.rb → github_actions_formatter.rb} +0 -0
@@ -4,8 +4,8 @@ module RuboCop
4
4
  module Cop
5
5
  module Style
6
6
  # Checks for redundant dot before operator method call.
7
- # The target operator methods are `|`, `^`, `&`, `<=>`, `==`, `===`, `=~`, `>`, `>=`, `<`,
8
- # `<=`, `<<`, `>>`, `+`, `-`, `*`, `/`, `%`, `**`, `~`, `!`, `!=`, and `!~`.
7
+ # The target operator methods are `|`, `^`, `&`, ``<=>``, `==`, `===`, `=~`, `>`, `>=`, `<`,
8
+ # ``<=``, `<<`, `>>`, `+`, `-`, `*`, `/`, `%`, `**`, `~`, `!`, `!=`, and `!~`.
9
9
  #
10
10
  # @example
11
11
  #
@@ -142,8 +142,8 @@ module RuboCop
142
142
  @assignments = assignments
143
143
  end
144
144
 
145
- def tsort_each_node(&block)
146
- @assignments.each(&block)
145
+ def tsort_each_node(...)
146
+ @assignments.each(...)
147
147
  end
148
148
 
149
149
  def tsort_each_child(assignment)
@@ -81,6 +81,7 @@ module RuboCop
81
81
  cond = node.condition
82
82
 
83
83
  control_op_condition(cond) do |first_child, rest_children|
84
+ return if require_parentheses?(node, first_child)
84
85
  return if semicolon_separated_expressions?(first_child, rest_children)
85
86
  return if modifier_op?(first_child)
86
87
  return if parens_allowed?(cond)
@@ -92,6 +93,13 @@ module RuboCop
92
93
  end
93
94
  end
94
95
 
96
+ def require_parentheses?(node, condition_body)
97
+ return false if !node.while_type? && !node.until_type?
98
+ return false if !condition_body.block_type? && !condition_body.numblock_type?
99
+
100
+ condition_body.send_node.block_literal? && condition_body.keywords?
101
+ end
102
+
95
103
  def semicolon_separated_expressions?(first_exp, rest_exps)
96
104
  return false unless (second_exp = rest_exps.first)
97
105
 
@@ -73,6 +73,7 @@ module RuboCop
73
73
  corrector.remove(offense_range)
74
74
  end
75
75
  end
76
+ alias on_csend on_send
76
77
 
77
78
  private
78
79
 
@@ -56,6 +56,7 @@ module RuboCop
56
56
  end
57
57
  end
58
58
  end
59
+ alias on_csend on_send
59
60
 
60
61
  private
61
62
 
@@ -64,7 +65,7 @@ module RuboCop
64
65
  return if node.last_argument&.block_pass_type?
65
66
 
66
67
  if node.method?(:each) && !node.parent&.block_type?
67
- ancestor_node = node.each_ancestor(:send).detect do |ancestor|
68
+ ancestor_node = node.each_ancestor(:send, :csend).detect do |ancestor|
68
69
  ancestor.receiver == node &&
69
70
  (RESTRICT_ON_SEND.include?(ancestor.method_name) || ancestor.method?(:reverse_each))
70
71
  end
@@ -83,10 +84,12 @@ module RuboCop
83
84
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
84
85
 
85
86
  def range(node)
86
- if node.method?(:each)
87
- node.loc.dot.join(node.loc.selector)
87
+ return node.selector unless node.method?(:each)
88
+
89
+ if node.parent.call_type?
90
+ node.selector.join(node.parent.loc.dot)
88
91
  else
89
- node.loc.selector
92
+ node.loc.dot.join(node.selector)
90
93
  end
91
94
  end
92
95
 
@@ -47,7 +47,7 @@ module RuboCop
47
47
  # @!method redundant_fetch_block_candidate?(node)
48
48
  def_node_matcher :redundant_fetch_block_candidate?, <<~PATTERN
49
49
  (block
50
- $(send _ :fetch _)
50
+ $(call _ :fetch _)
51
51
  (args)
52
52
  ${nil? #basic_literal? #const_type?})
53
53
  PATTERN
@@ -61,10 +61,10 @@ module RuboCop
61
61
  bad = build_bad_method(send, body)
62
62
 
63
63
  add_offense(range, message: format(MSG, good: good, bad: bad)) do |corrector|
64
- receiver, _, key = send.children
64
+ _, _, key = send.children
65
65
  default_value = body ? body.source : 'nil'
66
66
 
67
- corrector.replace(node, "#{receiver.source}.fetch(#{key.source}, #{default_value})")
67
+ corrector.replace(range, "fetch(#{key.source}, #{default_value})")
68
68
  end
69
69
  end
70
70
  end
@@ -94,7 +94,8 @@ module RuboCop
94
94
  !ends_with_backslash_without_comment?(range.source_line) ||
95
95
  string_concatenation?(range.source_line) ||
96
96
  start_with_arithmetic_operator?(processed_source[range.line]) ||
97
- inside_string_literal_or_method_with_argument?(range)
97
+ inside_string_literal_or_method_with_argument?(range) ||
98
+ leading_dot_method_chain_with_blank_line?(range)
98
99
  end
99
100
 
100
101
  def ends_with_backslash_without_comment?(source_line)
@@ -113,6 +114,12 @@ module RuboCop
113
114
  end
114
115
  end
115
116
 
117
+ def leading_dot_method_chain_with_blank_line?(range)
118
+ return false unless range.source_line.strip.start_with?('.', '&.')
119
+
120
+ processed_source[range.line].strip.empty?
121
+ end
122
+
116
123
  def redundant_line_continuation?(range)
117
124
  return true unless (node = find_node_for_line(range.line))
118
125
  return false if argument_newline?(node)
@@ -22,9 +22,6 @@ module RuboCop
22
22
  # @!method square_brackets?(node)
23
23
  def_node_matcher :square_brackets?, '(send {(send _recv _msg) str array hash} :[] ...)'
24
24
 
25
- # @!method range_end?(node)
26
- def_node_matcher :range_end?, '^^{irange erange}'
27
-
28
25
  # @!method method_node_and_args(node)
29
26
  def_node_matcher :method_node_and_args, '$(call _recv _msg $...)'
30
27
 
@@ -56,7 +53,7 @@ module RuboCop
56
53
  def ignore_syntax?(node)
57
54
  return false unless (parent = node.parent)
58
55
 
59
- parent.while_post_type? || parent.until_post_type? ||
56
+ parent.while_post_type? || parent.until_post_type? || parent.match_with_lvasgn_type? ||
60
57
  like_method_argument_parentheses?(parent)
61
58
  end
62
59
 
@@ -64,7 +61,8 @@ module RuboCop
64
61
  allowed_ancestor?(node) ||
65
62
  allowed_method_call?(node) ||
66
63
  allowed_multiple_expression?(node) ||
67
- allowed_ternary?(node)
64
+ allowed_ternary?(node) ||
65
+ node.parent&.range_type?
68
66
  end
69
67
 
70
68
  def allowed_ancestor?(node)
@@ -144,12 +142,16 @@ module RuboCop
144
142
  return 'a literal' if disallowed_literal?(begin_node, node)
145
143
  return 'a variable' if node.variable?
146
144
  return 'a constant' if node.const_type?
147
- return 'an expression' if node.lambda_or_proc?
145
+ if node.lambda_or_proc? && (node.braces? || node.send_node.lambda_literal?)
146
+ return 'an expression'
147
+ end
148
148
  return 'an interpolated expression' if interpolation?(begin_node)
149
149
 
150
150
  return if begin_node.chained?
151
151
 
152
152
  if node.and_type? || node.or_type?
153
+ return if node.semantic_operator? && begin_node.parent
154
+ return if node.multiline? && allow_in_multiline_conditions?
153
155
  return if ALLOWED_NODE_TYPES.include?(begin_node.parent&.type)
154
156
  return if begin_node.parent&.if_type? && begin_node.parent&.ternary?
155
157
 
@@ -165,6 +167,13 @@ module RuboCop
165
167
  # @!method interpolation?(node)
166
168
  def_node_matcher :interpolation?, '[^begin ^^dstr]'
167
169
 
170
+ def allow_in_multiline_conditions?
171
+ parentheses_around_condition_config = config.for_cop('Style/ParenthesesAroundCondition')
172
+ return false unless parentheses_around_condition_config['Enabled']
173
+
174
+ !!parentheses_around_condition_config['AllowInMultilineConditions']
175
+ end
176
+
168
177
  def check_send(begin_node, node)
169
178
  return check_unary(begin_node, node) if node.unary_operation?
170
179
 
@@ -229,7 +238,6 @@ module RuboCop
229
238
  def method_call_with_redundant_parentheses?(node)
230
239
  return false unless node.call_type?
231
240
  return false if node.prefix_not?
232
- return false if range_end?(node)
233
241
 
234
242
  send_node, args = method_node_and_args(node)
235
243
 
@@ -17,7 +17,8 @@ module RuboCop
17
17
  # protected scope, you cannot send private messages this way.
18
18
  #
19
19
  # Note we allow uses of `self` with operators because it would be awkward
20
- # otherwise.
20
+ # otherwise. Also allows the use of `self.it` without arguments in blocks,
21
+ # as in `0.times { self.it }`, following `Lint/ItWithoutArgumentsInBlock` cop.
21
22
  #
22
23
  # @example
23
24
  #
@@ -107,8 +108,8 @@ module RuboCop
107
108
  def on_send(node)
108
109
  return unless node.self_receiver? && regular_method_call?(node)
109
110
  return if node.parent&.mlhs_type?
110
-
111
111
  return if allowed_send_node?(node)
112
+ return if it_method_in_block?(node)
112
113
 
113
114
  add_offense(node.receiver) do |corrector|
114
115
  corrector.remove(node.receiver)
@@ -155,6 +156,20 @@ module RuboCop
155
156
  KERNEL_METHODS.include?(node.method_name)
156
157
  end
157
158
 
159
+ # Respects `Lint/ItWithoutArgumentsInBlock` cop and the following Ruby 3.3's warning:
160
+ #
161
+ # $ ruby -e '0.times { begin; it; end }'
162
+ # -e:1: warning: `it` calls without arguments will refer to the first block param in
163
+ # Ruby 3.4; use it() or self.it
164
+ #
165
+ def it_method_in_block?(node)
166
+ return false unless node.method?(:it)
167
+ return false unless (block_node = node.each_ancestor(:block).first)
168
+ return false unless block_node.arguments.empty_and_without_delimiters?
169
+
170
+ node.arguments.empty? && !node.block_literal?
171
+ end
172
+
158
173
  def regular_method_call?(node)
159
174
  !(node.operator_method? ||
160
175
  KEYWORDS.include?(node.method_name) ||
@@ -16,7 +16,7 @@ module RuboCop
16
16
  # This cop is unsafe, because `sort...last` and `max` may not return the
17
17
  # same element in all cases.
18
18
  #
19
- # In an enumerable where there are multiple elements where `a <=> b == 0`,
19
+ # In an enumerable where there are multiple elements where ``a <=> b == 0``,
20
20
  # or where the transformation done by the `sort_by` block has the
21
21
  # same result, `sort.last` and `max` (or `sort_by.last` and `max_by`)
22
22
  # will return different elements. `sort.last` will return the last
@@ -87,15 +87,15 @@ module RuboCop
87
87
  # @!method redundant_sort?(node)
88
88
  def_node_matcher :redundant_sort?, <<~MATCHER
89
89
  {
90
- (send $(send _ $:sort) ${:last :first})
91
- (send $(send _ $:sort) ${:[] :at :slice} {(int 0) (int -1)})
90
+ (call $(call _ $:sort) ${:last :first})
91
+ (call $(call _ $:sort) ${:[] :at :slice} {(int 0) (int -1)})
92
92
 
93
- (send $(send _ $:sort_by _) ${:last :first})
93
+ (call $(call _ $:sort_by _) ${:last :first})
94
94
  (send $(send _ $:sort_by _) ${:[] :at :slice} {(int 0) (int -1)})
95
95
 
96
- (send ({block numblock} $(send _ ${:sort_by :sort}) ...) ${:last :first})
97
- (send
98
- ({block numblock} $(send _ ${:sort_by :sort}) ...)
96
+ (call ({block numblock} $(call _ ${:sort_by :sort}) ...) ${:last :first})
97
+ (call
98
+ ({block numblock} $(call _ ${:sort_by :sort}) ...)
99
99
  ${:[] :at :slice} {(int 0) (int -1)}
100
100
  )
101
101
  }
@@ -108,6 +108,7 @@ module RuboCop
108
108
 
109
109
  register_offense(ancestor, sort_node, sorter, accessor)
110
110
  end
111
+ alias on_csend on_send
111
112
 
112
113
  private
113
114
 
@@ -46,12 +46,12 @@ module RuboCop
46
46
 
47
47
  # @!method redundant_sort_by_block(node)
48
48
  def_node_matcher :redundant_sort_by_block, <<~PATTERN
49
- (block $(send _ :sort_by) (args (arg $_x)) (lvar _x))
49
+ (block $(call _ :sort_by) (args (arg $_x)) (lvar _x))
50
50
  PATTERN
51
51
 
52
52
  # @!method redundant_sort_by_numblock(node)
53
53
  def_node_matcher :redundant_sort_by_numblock, <<~PATTERN
54
- (numblock $(send _ :sort_by) 1 (lvar :_1))
54
+ (numblock $(call _ :sort_by) 1 (lvar :_1))
55
55
  PATTERN
56
56
 
57
57
  def sort_by_range(send, node)
@@ -35,7 +35,7 @@ module RuboCop
35
35
 
36
36
  # @!method sample_candidate?(node)
37
37
  def_node_matcher :sample_candidate?, <<~PATTERN
38
- (send $(send _ :shuffle $...) ${:#{RESTRICT_ON_SEND.join(' :')}} $...)
38
+ (call $(call _ :shuffle $...) ${:#{RESTRICT_ON_SEND.join(' :')}} $...)
39
39
  PATTERN
40
40
 
41
41
  def on_send(node)
@@ -52,6 +52,7 @@ module RuboCop
52
52
  end
53
53
  end
54
54
  end
55
+ alias on_csend on_send
55
56
 
56
57
  private
57
58
 
@@ -55,8 +55,8 @@ module RuboCop
55
55
  # @!method regexp_match?(node)
56
56
  def_node_matcher :regexp_match?, <<~PATTERN
57
57
  {
58
- (block send (args (arg $_)) ${(send _ %REGEXP_METHODS _) match-with-lvasgn})
59
- (numblock send $1 ${(send _ %REGEXP_METHODS _) match-with-lvasgn})
58
+ (block call (args (arg $_)) ${(send _ %REGEXP_METHODS _) match-with-lvasgn})
59
+ (numblock call $1 ${(send _ %REGEXP_METHODS _) match-with-lvasgn})
60
60
  }
61
61
  PATTERN
62
62
 
@@ -64,9 +64,9 @@ module RuboCop
64
64
  # @!method creates_hash?(node)
65
65
  def_node_matcher :creates_hash?, <<~PATTERN
66
66
  {
67
- (send (const _ :Hash) {:new :[]} ...)
68
- (block (send (const _ :Hash) :new ...) ...)
69
- (send _ { :to_h :to_hash } ...)
67
+ (call (const _ :Hash) {:new :[]} ...)
68
+ (block (call (const _ :Hash) :new ...) ...)
69
+ (call _ { :to_h :to_hash } ...)
70
70
  }
71
71
  PATTERN
72
72
 
@@ -100,6 +100,7 @@ module RuboCop
100
100
  register_offense(node, block_node, regexp, replacement)
101
101
  end
102
102
  # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
103
+ alias on_csend on_send
103
104
 
104
105
  private
105
106
 
@@ -3,8 +3,11 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Sometimes using dig method ends up with just a single
7
- # argument. In such cases, dig should be replaced with [].
6
+ # Sometimes using `dig` method ends up with just a single
7
+ # argument. In such cases, dig should be replaced with `[]`.
8
+ #
9
+ # Since replacing `hash&.dig(:key)` with `hash[:key]` could potentially lead to error,
10
+ # calls to the `dig` method using safe navigation will be ignored.
8
11
  #
9
12
  # @safety
10
13
  # This cop is unsafe because it cannot be guaranteed that the receiver
@@ -3,8 +3,9 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks that arrays are sliced with endless ranges instead of
7
- # `ary[start..-1]` on Ruby 2.6+.
6
+ # Checks that arrays are not sliced with the redundant `ary[0..-1]`, replacing it with `ary`,
7
+ # and ensures arrays are sliced with endless ranges instead of `ary[start..-1]` on Ruby 2.6+,
8
+ # and with beginless ranges instead of `ary[nil..end]` on Ruby 2.7+.
8
9
  #
9
10
  # @safety
10
11
  # This cop is unsafe because `x..-1` and `x..` are only guaranteed to
@@ -21,29 +22,94 @@ module RuboCop
21
22
  #
22
23
  # @example
23
24
  # # bad
24
- # items[1..-1]
25
+ # items[0..-1]
26
+ # items[0..nil]
27
+ # items[0...nil]
25
28
  #
26
29
  # # good
27
- # items[1..]
30
+ # items
31
+ #
32
+ # # bad
33
+ # items[1..-1] # Ruby 2.6+
34
+ # items[1..nil] # Ruby 2.6+
35
+ #
36
+ # # good
37
+ # items[1..] # Ruby 2.6+
38
+ #
39
+ # # bad
40
+ # items[nil..42] # Ruby 2.7+
41
+ #
42
+ # # good
43
+ # items[..42] # Ruby 2.7+
44
+ # items[0..42] # Ruby 2.7+
45
+ #
28
46
  class SlicingWithRange < Base
29
47
  extend AutoCorrector
30
48
  extend TargetRubyVersion
31
49
 
32
50
  minimum_target_ruby_version 2.6
33
51
 
34
- MSG = 'Prefer ary[n..] over ary[n..-1].'
52
+ MSG = 'Prefer `%<prefer>s` over `%<current>s`.'
53
+ MSG_USELESS_RANGE = 'Remove the useless `%<prefer>s`.'
35
54
  RESTRICT_ON_SEND = %i[[]].freeze
36
55
 
56
+ # @!method range_from_zero_till_minus_one?(node)
57
+ def_node_matcher :range_from_zero_till_minus_one?, <<~PATTERN
58
+ {
59
+ (irange (int 0) {(int -1) nil})
60
+ (erange (int 0) nil)
61
+ }
62
+ PATTERN
63
+
37
64
  # @!method range_till_minus_one?(node)
38
- def_node_matcher :range_till_minus_one?, '(irange !nil? (int -1))'
65
+ def_node_matcher :range_till_minus_one?, <<~PATTERN
66
+ {
67
+ (irange !nil? {(int -1) nil})
68
+ (erange !nil? nil)
69
+ }
70
+ PATTERN
71
+
72
+ # @!method range_from_zero?(node)
73
+ def_node_matcher :range_from_zero?, <<~PATTERN
74
+ (irange nil !nil?)
75
+ PATTERN
39
76
 
40
77
  def on_send(node)
41
- return unless node.arguments.count == 1
42
- return unless range_till_minus_one?(node.first_argument)
78
+ return unless node.arguments.one?
43
79
 
44
- add_offense(node.first_argument) do |corrector|
45
- corrector.remove(node.first_argument.end)
80
+ range_node = node.first_argument
81
+ selector = node.loc.selector
82
+ unless (message, removal_range = offense_message_with_removal_range(range_node, selector))
83
+ return
46
84
  end
85
+
86
+ add_offense(selector, message: message) do |corrector|
87
+ corrector.remove(removal_range)
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def offense_message_with_removal_range(range_node, selector)
94
+ if range_from_zero_till_minus_one?(range_node)
95
+ [format(MSG_USELESS_RANGE, prefer: selector.source), selector]
96
+ elsif range_till_minus_one?(range_node)
97
+ [
98
+ format(MSG, prefer: endless(range_node), current: selector.source), range_node.end
99
+ ]
100
+ elsif range_from_zero?(range_node) && target_ruby_version >= 2.7
101
+ [
102
+ format(MSG, prefer: beginless(range_node), current: selector.source), range_node.begin
103
+ ]
104
+ end
105
+ end
106
+
107
+ def endless(range_node)
108
+ "[#{range_node.begin.source}#{range_node.loc.operator.source}]"
109
+ end
110
+
111
+ def beginless(range_node)
112
+ "[#{range_node.loc.operator.source}#{range_node.end.source}]"
47
113
  end
48
114
  end
49
115
  end
@@ -35,6 +35,7 @@ module RuboCop
35
35
  corrector.replace(range, 'chars')
36
36
  end
37
37
  end
38
+ alias on_csend on_send
38
39
  end
39
40
  end
40
41
  end
@@ -22,20 +22,23 @@ module RuboCop
22
22
 
23
23
  # @!method lstrip_rstrip(node)
24
24
  def_node_matcher :lstrip_rstrip, <<~PATTERN
25
- {(send $(send _ $:rstrip) $:lstrip)
26
- (send $(send _ $:lstrip) $:rstrip)}
25
+ {
26
+ (call $(call _ :rstrip) :lstrip)
27
+ (call $(call _ :lstrip) :rstrip)
28
+ }
27
29
  PATTERN
28
30
 
29
31
  def on_send(node)
30
- lstrip_rstrip(node) do |first_send, method_one, method_two|
32
+ lstrip_rstrip(node) do |first_send|
31
33
  range = range_between(first_send.loc.selector.begin_pos, node.source_range.end_pos)
32
- message = format(MSG, methods: "#{method_one}.#{method_two}")
34
+ message = format(MSG, methods: range.source)
33
35
 
34
36
  add_offense(range, message: message) do |corrector|
35
37
  corrector.replace(range, 'strip')
36
38
  end
37
39
  end
38
40
  end
41
+ alias on_csend on_send
39
42
  end
40
43
  end
41
44
  end
@@ -37,6 +37,42 @@ module RuboCop
37
37
  # # ArgumentError: wrong number of arguments (given 1, expected 0)
38
38
  # ----
39
39
  #
40
+ # It is also unsafe because `Symbol#to_proc` does not work with
41
+ # `protected` methods which would otherwise be accessible.
42
+ #
43
+ # For example:
44
+ #
45
+ # [source,ruby]
46
+ # ----
47
+ # class Box
48
+ # def initialize
49
+ # @secret = rand
50
+ # end
51
+ #
52
+ # def normal_matches?(*others)
53
+ # others.map { |other| other.secret }.any?(secret)
54
+ # end
55
+ #
56
+ # def symbol_to_proc_matches?(*others)
57
+ # others.map(&:secret).any?(secret)
58
+ # end
59
+ #
60
+ # protected
61
+ #
62
+ # attr_reader :secret
63
+ # end
64
+ #
65
+ # boxes = [Box.new, Box.new]
66
+ # Box.new.normal_matches?(*boxes)
67
+ # # => false
68
+ # boxes.first.normal_matches?(*boxes)
69
+ # # => true
70
+ # Box.new.symbol_to_proc_matches?(*boxes)
71
+ # # => NoMethodError: protected method `secret' called for #<Box...>
72
+ # boxes.first.symbol_to_proc_matches?(*boxes)
73
+ # # => NoMethodError: protected method `secret' called for #<Box...>
74
+ # ----
75
+ #
40
76
  # @example
41
77
  # # bad
42
78
  # something.map { |s| s.upcase }
@@ -23,38 +23,35 @@ module RuboCop
23
23
 
24
24
  minimum_target_ruby_version 2.4
25
25
 
26
- MSG = 'Use `%<receiver>s.unpack1(%<format>s)` instead of ' \
27
- '`%<receiver>s.unpack(%<format>s)%<method>s`.'
26
+ MSG = 'Use `unpack1(%<format>s)` instead of `%<current>s`.'
28
27
  RESTRICT_ON_SEND = %i[first [] slice at].freeze
29
28
 
30
29
  # @!method unpack_and_first_element?(node)
31
30
  def_node_matcher :unpack_and_first_element?, <<~PATTERN
32
31
  {
33
- (send $(send (...) :unpack $(...)) :first)
34
- (send $(send (...) :unpack $(...)) {:[] :slice :at} (int 0))
32
+ (call $(call (...) :unpack $(...)) :first)
33
+ (call $(call (...) :unpack $(...)) {:[] :slice :at} (int 0))
35
34
  }
36
35
  PATTERN
37
36
 
38
37
  def on_send(node)
39
38
  unpack_and_first_element?(node) do |unpack_call, unpack_arg|
40
- range = first_element_range(node, unpack_call)
41
- message = format(MSG,
42
- receiver: unpack_call.receiver.source,
43
- format: unpack_arg.source,
44
- method: range.source)
45
- add_offense(node, message: message) do |corrector|
46
- corrector.remove(first_element_range(node, unpack_call))
39
+ first_element_range = first_element_range(node, unpack_call)
40
+ offense_range = unpack_call.loc.selector.join(node.source_range.end)
41
+ message = format(MSG, format: unpack_arg.source, current: offense_range.source)
42
+
43
+ add_offense(offense_range, message: message) do |corrector|
44
+ corrector.remove(first_element_range)
47
45
  corrector.replace(unpack_call.loc.selector, 'unpack1')
48
46
  end
49
47
  end
50
48
  end
49
+ alias on_csend on_send
51
50
 
52
51
  private
53
52
 
54
53
  def first_element_range(node, unpack_call)
55
- Parser::Source::Range.new(node.source_range.source_buffer,
56
- unpack_call.source_range.end_pos,
57
- node.source_range.end_pos)
54
+ unpack_call.source_range.end.join(node.source_range.end)
58
55
  end
59
56
  end
60
57
  end
@@ -251,9 +251,18 @@ class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength
251
251
  "\ninclude::../partials/#{filename}[]\n"
252
252
  end
253
253
 
254
+ # rubocop:disable Metrics/MethodLength
254
255
  def print_cops_of_department(department)
255
256
  selected_cops = cops_of_department(department)
256
- content = +"= #{department}\n"
257
+ content = +<<~HEADER
258
+ ////
259
+ Do NOT edit this file by hand directly, as it is automatically generated.
260
+
261
+ Please make any necessary changes to the cop documentation within the source files themselves.
262
+ ////
263
+
264
+ = #{department}
265
+ HEADER
257
266
  selected_cops.each { |cop| content << print_cop_with_doc(cop) }
258
267
  content << footer_for_department(department)
259
268
  file_name = "#{docs_path}/#{department_to_basename(department)}.adoc"
@@ -262,6 +271,7 @@ class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength
262
271
  file.write("#{content.strip}\n")
263
272
  end
264
273
  end
274
+ # rubocop:enable Metrics/MethodLength
265
275
 
266
276
  def print_cop_with_doc(cop) # rubocop:todo Metrics/AbcSize, Metrics/MethodLength
267
277
  cop_config = config.for_cop(cop)
@@ -54,10 +54,7 @@ module RuboCop
54
54
  return enum_for(__method__, named: named) unless block_given?
55
55
 
56
56
  parsed_tree&.traverse do |event, exp, _index|
57
- yield(exp) if event == :enter &&
58
- named == exp.respond_to?(:name) &&
59
- exp.respond_to?(:capturing?) &&
60
- exp.capturing?
57
+ yield(exp) if named_capturing?(exp, event, named)
61
58
  end
62
59
 
63
60
  self
@@ -65,6 +62,14 @@ module RuboCop
65
62
 
66
63
  private
67
64
 
65
+ def named_capturing?(exp, event, named)
66
+ event == :enter &&
67
+ named == exp.respond_to?(:name) &&
68
+ !exp.text.start_with?('(?<=') &&
69
+ exp.respond_to?(:capturing?) &&
70
+ exp.capturing?
71
+ end
72
+
68
73
  def with_interpolations_blanked
69
74
  # Ignore the trailing regopt node
70
75
  children[0...-1].map do |child|