rubocop 1.15.0 → 1.16.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1009c9da04982d8684219d15bcaaf8a09f5aa3523d8fb668fb47b885281e11ef
4
- data.tar.gz: fac46c18283c960c698d18f5fd6206cef3d309b2d74bc5046c0c6ba30ca60431
3
+ metadata.gz: 87761564ea69f6968eb595204b979c7accaee729c8c27005d8f4d22fe7d7c588
4
+ data.tar.gz: f1b08e98a6bfed772381c159a5bc1c22e697b965acb48154c7fb4b2c58566077
5
5
  SHA512:
6
- metadata.gz: 292b418ad2da13f7357a6b3189128ed35a84fa662506de26d4973797e430751b158e9cb0c7a0753c0cded518ba0299cf5bcf1394abb9793aa728b4dedf36863a
7
- data.tar.gz: cfca1c661ace6abafa34d5506c27ef48afe3ba2abc7187d3fabf41db0abce0c93d59d2f5411671d22fbadea6a09987cda71f2600cc85b5b88b18fca7f526f451
6
+ metadata.gz: '0709e83e22cd4b7ffd86e2144ce9da499a8aa6ded608337c6e88f0418feb313c9d46d2c0a46245297a8e2ff5e072b45af7c7797d30f0a172fb0ab0e377c70264'
7
+ data.tar.gz: 19bf9b1b665b3421665f29196702aaedcedd7599b7ba22c547ac2b4871241ddcab1103c017aa8c447d2a4b882a09a835a3ac8e101f8db06f91cd8957fad077c0
data/README.md CHANGED
@@ -54,7 +54,7 @@ To prevent an unwanted RuboCop update you might want to use a conservative versi
54
54
  in your `Gemfile`:
55
55
 
56
56
  ```rb
57
- gem 'rubocop', '~> 1.15', require: false
57
+ gem 'rubocop', '~> 1.16', require: false
58
58
  ```
59
59
 
60
60
  See [our versioning policy](https://docs.rubocop.org/rubocop/versioning.html) for further details.
data/config/default.yml CHANGED
@@ -377,10 +377,11 @@ Layout/BlockEndNewline:
377
377
  VersionAdded: '0.49'
378
378
 
379
379
  Layout/CaseIndentation:
380
- Description: 'Indentation of when in a case/when/[else/]end.'
380
+ Description: 'Indentation of when in a case/(when|in)/[else/]end.'
381
381
  StyleGuide: '#indent-when-to-case'
382
382
  Enabled: true
383
383
  VersionAdded: '0.49'
384
+ VersionChanged: '1.16'
384
385
  EnforcedStyle: case
385
386
  SupportedStyles:
386
387
  - case
@@ -771,7 +772,7 @@ Layout/HashAlignment:
771
772
  Enabled: true
772
773
  AllowMultipleStyles: true
773
774
  VersionAdded: '0.49'
774
- VersionChanged: '0.77'
775
+ VersionChanged: '1.16'
775
776
  # Alignment of entries using hash rocket as separator. Valid values are:
776
777
  #
777
778
  # key - left alignment of keys
@@ -1645,6 +1646,11 @@ Lint/EmptyFile:
1645
1646
  AllowComments: true
1646
1647
  VersionAdded: '0.90'
1647
1648
 
1649
+ Lint/EmptyInPattern:
1650
+ Description: 'Checks for the presence of `in` pattern branches without a body.'
1651
+ Enabled: pending
1652
+ VersionAdded: '1.16'
1653
+
1648
1654
  Lint/EmptyInterpolation:
1649
1655
  Description: 'Checks for empty string interpolation.'
1650
1656
  Enabled: true
@@ -2092,6 +2098,7 @@ Lint/SymbolConversion:
2092
2098
  Description: 'Checks for unnecessary symbol conversions.'
2093
2099
  Enabled: pending
2094
2100
  VersionAdded: '1.9'
2101
+ VersionChanged: '1.16'
2095
2102
  EnforcedStyle: strict
2096
2103
  SupportedStyles:
2097
2104
  - strict
@@ -3490,8 +3497,10 @@ Style/HashEachMethods:
3490
3497
  Description: 'Use Hash#each_key and Hash#each_value.'
3491
3498
  StyleGuide: '#hash-each'
3492
3499
  Enabled: true
3493
- VersionAdded: '0.80'
3494
3500
  Safe: false
3501
+ VersionAdded: '0.80'
3502
+ VersionChanged: '1.16'
3503
+ AllowedReceivers: []
3495
3504
 
3496
3505
  Style/HashExcept:
3497
3506
  Description: >-
@@ -3554,6 +3563,7 @@ Style/IdenticalConditionalBranches:
3554
3563
  out of the conditional.
3555
3564
  Enabled: true
3556
3565
  VersionAdded: '0.36'
3566
+ VersionChanged: '1.16'
3557
3567
 
3558
3568
  Style/IfInsideElse:
3559
3569
  Description: 'Finds if nodes inside else, which can be converted to elsif.'
@@ -3600,6 +3610,12 @@ Style/ImplicitRuntimeError:
3600
3610
  Enabled: false
3601
3611
  VersionAdded: '0.41'
3602
3612
 
3613
+ Style/InPatternThen:
3614
+ Description: 'Checks for `in;` uses in `case` expressions.'
3615
+ StyleGuide: '#no-in-pattern-semicolons'
3616
+ Enabled: pending
3617
+ VersionAdded: '1.16'
3618
+
3603
3619
  Style/InfiniteLoop:
3604
3620
  Description: >-
3605
3621
  Use Kernel#loop for infinite loops.
@@ -3828,6 +3844,12 @@ Style/MultilineIfThen:
3828
3844
  VersionAdded: '0.9'
3829
3845
  VersionChanged: '0.26'
3830
3846
 
3847
+ Style/MultilineInPatternThen:
3848
+ Description: 'Do not use `then` for multi-line `in` statement.'
3849
+ StyleGuide: '#no-then'
3850
+ Enabled: pending
3851
+ VersionAdded: '1.16'
3852
+
3831
3853
  Style/MultilineMemoization:
3832
3854
  Description: 'Wrap multiline memoizations in a `begin` and `end` block.'
3833
3855
  Enabled: true
@@ -4186,6 +4208,16 @@ Style/Proc:
4186
4208
  VersionAdded: '0.9'
4187
4209
  VersionChanged: '0.18'
4188
4210
 
4211
+ Style/QuotedSymbols:
4212
+ Description: 'Use a consistent style for quoted symbols.'
4213
+ Enabled: pending
4214
+ VersionAdded: '1.16'
4215
+ EnforcedStyle: same_as_string_literals
4216
+ SupportedStyles:
4217
+ - same_as_string_literals
4218
+ - single_quotes
4219
+ - double_quotes
4220
+
4189
4221
  Style/RaiseArgs:
4190
4222
  Description: 'Checks the arguments passed to raise/fail.'
4191
4223
  StyleGuide: '#exception-class-messages'
data/lib/rubocop.rb CHANGED
@@ -119,6 +119,7 @@ require_relative 'rubocop/cop/mixin/surrounding_space'
119
119
  require_relative 'rubocop/cop/mixin/statement_modifier'
120
120
  require_relative 'rubocop/cop/mixin/string_help'
121
121
  require_relative 'rubocop/cop/mixin/string_literals_help'
122
+ require_relative 'rubocop/cop/mixin/symbol_help'
122
123
  require_relative 'rubocop/cop/mixin/target_ruby_version'
123
124
  require_relative 'rubocop/cop/mixin/trailing_body'
124
125
  require_relative 'rubocop/cop/mixin/trailing_comma'
@@ -287,6 +288,7 @@ require_relative 'rubocop/cop/lint/empty_conditional_body'
287
288
  require_relative 'rubocop/cop/lint/empty_ensure'
288
289
  require_relative 'rubocop/cop/lint/empty_expression'
289
290
  require_relative 'rubocop/cop/lint/empty_file'
291
+ require_relative 'rubocop/cop/lint/empty_in_pattern'
290
292
  require_relative 'rubocop/cop/lint/empty_interpolation'
291
293
  require_relative 'rubocop/cop/lint/empty_when'
292
294
  require_relative 'rubocop/cop/lint/ensure_return'
@@ -490,6 +492,7 @@ require_relative 'rubocop/cop/style/if_unless_modifier_of_if_unless'
490
492
  require_relative 'rubocop/cop/style/if_with_boolean_literal_branches'
491
493
  require_relative 'rubocop/cop/style/if_with_semicolon'
492
494
  require_relative 'rubocop/cop/style/implicit_runtime_error'
495
+ require_relative 'rubocop/cop/style/in_pattern_then'
493
496
  require_relative 'rubocop/cop/style/infinite_loop'
494
497
  require_relative 'rubocop/cop/style/inverse_methods'
495
498
  require_relative 'rubocop/cop/style/inline_comment'
@@ -500,6 +503,7 @@ require_relative 'rubocop/cop/style/lambda_call'
500
503
  require_relative 'rubocop/cop/style/line_end_concatenation'
501
504
  require_relative 'rubocop/cop/style/method_call_without_args_parentheses'
502
505
  require_relative 'rubocop/cop/style/method_call_with_args_parentheses'
506
+ require_relative 'rubocop/cop/style/multiline_in_pattern_then'
503
507
  require_relative 'rubocop/cop/style/redundant_assignment'
504
508
  require_relative 'rubocop/cop/style/redundant_fetch_block'
505
509
  require_relative 'rubocop/cop/style/redundant_file_extension_in_require'
@@ -550,6 +554,7 @@ require_relative 'rubocop/cop/style/percent_q_literals'
550
554
  require_relative 'rubocop/cop/style/perl_backrefs'
551
555
  require_relative 'rubocop/cop/style/preferred_hash_methods'
552
556
  require_relative 'rubocop/cop/style/proc'
557
+ require_relative 'rubocop/cop/style/quoted_symbols'
553
558
  require_relative 'rubocop/cop/style/raise_args'
554
559
  require_relative 'rubocop/cop/style/random_with_offset'
555
560
  require_relative 'rubocop/cop/style/redundant_argument'
@@ -56,8 +56,9 @@ module RuboCop
56
56
  first_arg = node.first_argument
57
57
  return if !multiple_arguments?(node, first_arg) || node.send_type? && node.method?(:[]=)
58
58
 
59
- if first_arg.hash_type?
60
- check_alignment(first_arg.pairs, base_column(node, first_arg.pairs.first))
59
+ if first_arg.hash_type? && !first_arg.braces?
60
+ pairs = first_arg.pairs
61
+ check_alignment(pairs, base_column(node, pairs.first))
61
62
  else
62
63
  check_alignment(node.arguments, base_column(node, first_arg))
63
64
  end
@@ -3,10 +3,10 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Layout
6
- # This cop checks how the ``when``s of a `case` expression
6
+ # This cop checks how the `when` and `in`s of a `case` expression
7
7
  # are indented in relation to its `case` or `end` keyword.
8
8
  #
9
- # It will register a separate offense for each misaligned `when`.
9
+ # It will register a separate offense for each misaligned `when` and `in`.
10
10
  #
11
11
  # @example
12
12
  # # If Layout/EndAlignment is set to keyword style (default)
@@ -22,6 +22,13 @@ module RuboCop
22
22
  # y / 3
23
23
  # end
24
24
  #
25
+ # case n
26
+ # in pattern
27
+ # x * 2
28
+ # else
29
+ # y / 3
30
+ # end
31
+ #
25
32
  # # good for all styles
26
33
  # case n
27
34
  # when 0
@@ -30,6 +37,13 @@ module RuboCop
30
37
  # y / 3
31
38
  # end
32
39
  #
40
+ # case n
41
+ # in pattern
42
+ # x * 2
43
+ # else
44
+ # y / 3
45
+ # end
46
+ #
33
47
  # @example EnforcedStyle: case (default)
34
48
  # # if EndAlignment is set to other style such as
35
49
  # # start_of_line (as shown below), then *when* alignment
@@ -43,6 +57,13 @@ module RuboCop
43
57
  # y / 3
44
58
  # end
45
59
  #
60
+ # a = case n
61
+ # in pattern
62
+ # x * 2
63
+ # else
64
+ # y / 3
65
+ # end
66
+ #
46
67
  # # good
47
68
  # a = case n
48
69
  # when 0
@@ -51,6 +72,13 @@ module RuboCop
51
72
  # y / 3
52
73
  # end
53
74
  #
75
+ # a = case n
76
+ # in pattern
77
+ # x * 2
78
+ # else
79
+ # y / 3
80
+ # end
81
+ #
54
82
  # @example EnforcedStyle: end
55
83
  # # bad
56
84
  # a = case n
@@ -60,6 +88,13 @@ module RuboCop
60
88
  # y / 3
61
89
  # end
62
90
  #
91
+ # a = case n
92
+ # in pattern
93
+ # x * 2
94
+ # else
95
+ # y / 3
96
+ # end
97
+ #
63
98
  # # good
64
99
  # a = case n
65
100
  # when 0
@@ -67,30 +102,43 @@ module RuboCop
67
102
  # else
68
103
  # y / 3
69
104
  # end
105
+ #
106
+ # a = case n
107
+ # in pattern
108
+ # x * 2
109
+ # else
110
+ # y / 3
111
+ # end
70
112
  class CaseIndentation < Base
71
113
  include Alignment
72
114
  include ConfigurableEnforcedStyle
73
115
  include RangeHelp
74
116
  extend AutoCorrector
75
117
 
76
- MSG = 'Indent `when` %<depth>s `%<base>s`.'
118
+ MSG = 'Indent `%<branch_type>s` %<depth>s `%<base>s`.'
77
119
 
78
120
  def on_case(case_node)
79
121
  return if case_node.single_line?
80
122
 
81
- case_node.each_when { |when_node| check_when(when_node) }
123
+ case_node.each_when { |when_node| check_when(when_node, 'when') }
124
+ end
125
+
126
+ def on_case_match(case_match_node)
127
+ return if case_match_node.single_line?
128
+
129
+ case_match_node.each_in_pattern { |in_pattern_node| check_when(in_pattern_node, 'in') }
82
130
  end
83
131
 
84
132
  private
85
133
 
86
- def check_when(when_node)
134
+ def check_when(when_node, branch_type)
87
135
  when_column = when_node.loc.keyword.column
88
136
  base_column = base_column(when_node.parent, style)
89
137
 
90
138
  if when_column == base_column + indentation_width
91
139
  correct_style_detected
92
140
  else
93
- incorrect_style(when_node)
141
+ incorrect_style(when_node, branch_type)
94
142
  end
95
143
  end
96
144
 
@@ -102,9 +150,9 @@ module RuboCop
102
150
  indent_one_step? ? configured_indentation_width : 0
103
151
  end
104
152
 
105
- def incorrect_style(when_node)
153
+ def incorrect_style(when_node, branch_type)
106
154
  depth = indent_one_step? ? 'one step more than' : 'as deep as'
107
- message = format(MSG, depth: depth, base: style)
155
+ message = format(MSG, branch_type: branch_type, depth: depth, base: style)
108
156
 
109
157
  add_offense(when_node.loc.keyword, message: message) do |corrector|
110
158
  detect_incorrect_style(when_node)
@@ -141,7 +189,7 @@ module RuboCop
141
189
  end
142
190
 
143
191
  def replacement(node)
144
- case_node = node.each_ancestor(:case).first
192
+ case_node = node.each_ancestor(:case, :case_match).first
145
193
  base_type = cop_config[style_parameter_name] == 'end' ? :end : :case
146
194
 
147
195
  column = base_column(case_node, base_type)
@@ -185,7 +185,9 @@ module RuboCop
185
185
  SeparatorAlignment => 'Align the separators of a hash ' \
186
186
  'literal if they span more than one line.',
187
187
  TableAlignment => 'Align the keys and values of a hash ' \
188
- 'literal if they span more than one line.' }.freeze
188
+ 'literal if they span more than one line.',
189
+ KeywordSplatAlignment => 'Align keyword splats with the ' \
190
+ 'rest of the hash if it spans more than one line.' }.freeze
189
191
 
190
192
  def on_send(node)
191
193
  return if double_splat?(node)
@@ -201,7 +203,7 @@ module RuboCop
201
203
  alias on_yield on_send
202
204
 
203
205
  def on_hash(node)
204
- return if enforce_first_argument_with_fixed_indentation? || ignored_node?(node) ||
206
+ return if autocorrect_incompatible_with_other_cops?(node) || ignored_node?(node) ||
205
207
  node.pairs.empty? || node.single_line?
206
208
 
207
209
  proc = ->(a) { a.checkable_layout?(node) }
@@ -214,6 +216,10 @@ module RuboCop
214
216
 
215
217
  private
216
218
 
219
+ def autocorrect_incompatible_with_other_cops?(node)
220
+ enforce_first_argument_with_fixed_indentation? && !node.braces? && node.parent&.call_type?
221
+ end
222
+
217
223
  def reset!
218
224
  self.offences_by = {}
219
225
  self.column_deltas = Hash.new { |hash, key| hash[key] = {} }
@@ -243,7 +249,14 @@ module RuboCop
243
249
  end
244
250
 
245
251
  def add_offences
252
+ kwsplat_offences = offences_by.delete(KeywordSplatAlignment)
253
+ register_offences_with_format(kwsplat_offences, KeywordSplatAlignment)
254
+
246
255
  format, offences = offences_by.min_by { |_, v| v.length }
256
+ register_offences_with_format(offences, format)
257
+ end
258
+
259
+ def register_offences_with_format(offences, format)
247
260
  (offences || []).each do |offence|
248
261
  add_offense(offence, message: MESSAGES[format]) do |corrector|
249
262
  delta = column_deltas[alignment_for(offence).first.class][offence]
@@ -271,7 +284,9 @@ module RuboCop
271
284
  end
272
285
 
273
286
  def alignment_for(pair)
274
- if pair.hash_rocket?
287
+ if pair.kwsplat_type?
288
+ [KeywordSplatAlignment.new]
289
+ elsif pair.hash_rocket?
275
290
  alignment_for_hash_rockets
276
291
  else
277
292
  alignment_for_colons
@@ -53,6 +53,10 @@ module RuboCop
53
53
  check(node, %i[keyword else].freeze)
54
54
  end
55
55
 
56
+ def on_case_match(node)
57
+ check(node, %i[keyword else].freeze)
58
+ end
59
+
56
60
  def on_ensure(node)
57
61
  check(node, [:keyword].freeze)
58
62
  end
@@ -65,6 +69,14 @@ module RuboCop
65
69
  check(node, %i[keyword else begin end].freeze, 'then')
66
70
  end
67
71
 
72
+ def on_if_guard(node)
73
+ check(node, [:keyword].freeze)
74
+ end
75
+
76
+ def on_in_pattern(node)
77
+ check(node, [:keyword].freeze)
78
+ end
79
+
68
80
  def on_kwbegin(node)
69
81
  check(node, %i[begin end].freeze, nil)
70
82
  end
@@ -109,6 +121,10 @@ module RuboCop
109
121
  check(node, [:keyword].freeze)
110
122
  end
111
123
 
124
+ def on_unless_guard(node)
125
+ check(node, [:keyword].freeze)
126
+ end
127
+
112
128
  def on_until(node)
113
129
  check(node, %i[begin end keyword].freeze)
114
130
  end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # This cop checks for the presence of `in` pattern branches without a body.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # case condition
12
+ # in [a]
13
+ # do_something
14
+ # in [a, b]
15
+ # end
16
+ #
17
+ # # good
18
+ # case condition
19
+ # in [a]
20
+ # do_something
21
+ # in [a, b]
22
+ # nil
23
+ # end
24
+ #
25
+ # @example AllowComments: true (default)
26
+ #
27
+ # # good
28
+ # case condition
29
+ # in [a]
30
+ # do_something
31
+ # in [a, b]
32
+ # # noop
33
+ # end
34
+ #
35
+ # @example AllowComments: false
36
+ #
37
+ # # bad
38
+ # case condition
39
+ # in [a]
40
+ # do_something
41
+ # in [a, b]
42
+ # # noop
43
+ # end
44
+ #
45
+ class EmptyInPattern < Base
46
+ extend TargetRubyVersion
47
+
48
+ MSG = 'Avoid `in` branches without a body.'
49
+
50
+ minimum_target_ruby_version 2.7
51
+
52
+ def on_case_match(node)
53
+ node.in_pattern_branches.each do |branch|
54
+ next if branch.body || cop_config['AllowComments'] && comment_lines?(node)
55
+
56
+ add_offense(branch)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -60,16 +60,19 @@ module RuboCop
60
60
  processed_source.buffer.source_line(range.line - 1).blank?
61
61
  end
62
62
 
63
- def comment_range_with_surrounding_space(range)
64
- if previous_line_blank?(range) &&
65
- processed_source.comment_config.comment_only_line?(range.line)
63
+ def comment_range_with_surrounding_space(directive_comment_range, line_comment_range)
64
+ if previous_line_blank?(directive_comment_range) &&
65
+ processed_source.comment_config.comment_only_line?(directive_comment_range.line) &&
66
+ directive_comment_range.begin_pos == line_comment_range.begin_pos
66
67
  # When the previous line is blank, it should be retained
67
- range_with_surrounding_space(range: range, side: :right)
68
+ range_with_surrounding_space(range: directive_comment_range, side: :right)
68
69
  else
69
70
  # Eat the entire comment, the preceding space, and the preceding
70
71
  # newline if there is one.
71
- original_begin = range.begin_pos
72
- range = range_with_surrounding_space(range: range, side: :left, newlines: true)
72
+ original_begin = directive_comment_range.begin_pos
73
+ range = range_with_surrounding_space(
74
+ range: directive_comment_range, side: :left, newlines: true
75
+ )
73
76
 
74
77
  range_with_surrounding_space(range: range,
75
78
  side: :right,
@@ -179,14 +182,14 @@ module RuboCop
179
182
  end
180
183
 
181
184
  def add_offense_for_entire_comment(comment, cops)
182
- location = comment.loc.expression
185
+ location = DirectiveComment.new(comment).range
183
186
  cop_list = cops.sort.map { |c| describe(c) }
184
187
 
185
188
  add_offense(
186
189
  location,
187
190
  message: "Unnecessary disabling of #{cop_list.join(', ')}."
188
191
  ) do |corrector|
189
- range = comment_range_with_surrounding_space(location)
192
+ range = comment_range_with_surrounding_space(location, comment.loc.expression)
190
193
  corrector.remove(range)
191
194
  end
192
195
  end
@@ -66,6 +66,7 @@ module RuboCop
66
66
  class SymbolConversion < Base
67
67
  extend AutoCorrector
68
68
  include ConfigurableEnforcedStyle
69
+ include SymbolHelp
69
70
 
70
71
  MSG = 'Unnecessary symbol conversion; use `%<correction>s` instead.'
71
72
  MSG_CONSISTENCY = 'Symbol hash key should be quoted for consistency; ' \
@@ -138,10 +139,6 @@ module RuboCop
138
139
  node.parent&.array_type? && node.parent&.percent_literal?
139
140
  end
140
141
 
141
- def hash_key?(node)
142
- node.parent&.pair_type? && node == node.parent.child_nodes.first
143
- end
144
-
145
142
  def correct_hash_key(node)
146
143
  # Although some operators can be converted to symbols normally
147
144
  # (ie. `:==`), these are not accepted as hash keys and will
@@ -167,7 +164,7 @@ module RuboCop
167
164
  next if requires_quotes?(key)
168
165
  next if properly_quoted?(key.source, %("#{key.value}"))
169
166
 
170
- correction = "#{quote_type}#{key.value}#{quote_type}"
167
+ correction = %("#{key.value}")
171
168
  register_offense(
172
169
  key,
173
170
  correction: correction,
@@ -175,13 +172,6 @@ module RuboCop
175
172
  )
176
173
  end
177
174
  end
178
-
179
- def quote_type
180
- # Use the `Style/StringLiterals` configuration for quoting symbols
181
- return '"' unless config.for_cop('Style/StringLiterals')['Enabled']
182
-
183
- config.for_cop('Style/StringLiterals')['EnforcedStyle'] == 'single_quotes' ? "'" : '"'
184
- end
185
175
  end
186
176
  end
187
177
  end
@@ -43,7 +43,7 @@ module RuboCop
43
43
  end
44
44
 
45
45
  def value_delta(pair)
46
- return 0 if pair.kwsplat_type? || pair.value_on_new_line?
46
+ return 0 if pair.value_on_new_line?
47
47
 
48
48
  correct_value_column = pair.loc.operator.end.column + 1
49
49
  actual_value_column = pair.value.loc.column
@@ -108,8 +108,6 @@ module RuboCop
108
108
  end
109
109
 
110
110
  def value_delta(first_pair, current_pair)
111
- return 0 if current_pair.kwsplat_type?
112
-
113
111
  correct_value_column = first_pair.key.loc.column +
114
112
  current_pair.delimiter(true).length +
115
113
  max_key_width
@@ -139,6 +137,19 @@ module RuboCop
139
137
  first_pair.value_delta(current_pair)
140
138
  end
141
139
  end
140
+
141
+ # Handles calculation of deltas for `kwsplat` nodes.
142
+ # This is a special case that just ensures the kwsplat is aligned with the rest of the hash
143
+ # since a `kwsplat` does not have a key, separator or value.
144
+ class KeywordSplatAlignment
145
+ def deltas(first_pair, current_pair)
146
+ if Util.begins_its_line?(current_pair.source_range)
147
+ { key: first_pair.key_delta(current_pair) }
148
+ else
149
+ {}
150
+ end
151
+ end
152
+ end
142
153
  end
143
154
  end
144
155
  end
@@ -4,12 +4,10 @@ module RuboCop
4
4
  module Cop
5
5
  # Common functionality for cops checking single/double quotes.
6
6
  module StringLiteralsHelp
7
- include StringHelp
8
-
9
7
  private
10
8
 
11
- def wrong_quotes?(node)
12
- src = node.source
9
+ def wrong_quotes?(src_or_node)
10
+ src = src_or_node.is_a?(RuboCop::AST::Node) ? src_or_node.source : src_or_node
13
11
  return false if src.start_with?('%', '?')
14
12
 
15
13
  if style == :single_quotes
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Classes that include this module just implement functions for working
6
+ # with symbol nodes.
7
+ module SymbolHelp
8
+ def hash_key?(node)
9
+ node.parent&.pair_type? && node == node.parent.child_nodes.first
10
+ end
11
+ end
12
+ end
13
+ end
@@ -17,6 +17,11 @@ module RuboCop
17
17
  # # good
18
18
  # hash.each_key { |k| p k }
19
19
  # hash.each_value { |v| p v }
20
+ #
21
+ # @example AllowedReceivers: ['execute']
22
+ # # good
23
+ # execute(sql).keys.each { |v| p v }
24
+ # execute(sql).values.each { |v| p v }
20
25
  class HashEachMethods < Base
21
26
  include Lint::UnusedArgument
22
27
  extend AutoCorrector
@@ -36,7 +41,9 @@ module RuboCop
36
41
 
37
42
  def register_kv_offense(node)
38
43
  kv_each(node) do |target, method|
39
- return unless target.receiver.receiver
44
+ parent_receiver = target.receiver.receiver
45
+ return unless parent_receiver
46
+ return if allowed_receiver?(parent_receiver)
40
47
 
41
48
  msg = format(message, prefer: "each_#{method[0..-2]}", current: "#{method}.each")
42
49
 
@@ -80,6 +87,16 @@ module RuboCop
80
87
  def kv_range(outer_node)
81
88
  outer_node.receiver.loc.selector.join(outer_node.loc.selector)
82
89
  end
90
+
91
+ def allowed_receiver?(receiver)
92
+ receiver_name = receiver.send_type? ? receiver.method_name.to_s : receiver.source
93
+
94
+ allowed_receivers.include?(receiver_name)
95
+ end
96
+
97
+ def allowed_receivers
98
+ cop_config.fetch('AllowedReceivers', [])
99
+ end
83
100
  end
84
101
  end
85
102
  end
@@ -68,39 +68,60 @@ module RuboCop
68
68
  # do_z
69
69
  # end
70
70
  class IdenticalConditionalBranches < Base
71
+ include RangeHelp
72
+ extend AutoCorrector
73
+
71
74
  MSG = 'Move `%<source>s` out of the conditional.'
72
75
 
73
76
  def on_if(node)
74
77
  return if node.elsif?
75
78
 
76
79
  branches = expand_elses(node.else_branch).unshift(node.if_branch)
77
- check_branches(branches)
80
+ check_branches(node, branches)
78
81
  end
79
82
 
80
83
  def on_case(node)
81
84
  return unless node.else? && node.else_branch
82
85
 
83
86
  branches = node.when_branches.map(&:body).push(node.else_branch)
84
- check_branches(branches)
87
+ check_branches(node, branches)
85
88
  end
86
89
 
87
90
  private
88
91
 
89
- def check_branches(branches) # rubocop:todo Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
92
+ def check_branches(node, branches)
90
93
  # return if any branch is empty. An empty branch can be an `if`
91
94
  # without an `else` or a branch that contains only comments.
92
95
  return if branches.any?(&:nil?)
93
96
 
94
97
  tails = branches.map { |branch| tail(branch) }
95
- check_expressions(tails) if tails.none?(&:nil?)
98
+ check_expressions(node, tails, :after_condition) if duplicated_expressions?(tails)
99
+
96
100
  heads = branches.map { |branch| head(branch) }
97
- check_expressions(heads) if tails.none?(&:nil?)
101
+ check_expressions(node, heads, :before_condition) if duplicated_expressions?(heads)
102
+ end
103
+
104
+ def duplicated_expressions?(expressions)
105
+ expressions.size > 1 && expressions.uniq.one?
98
106
  end
99
107
 
100
- def check_expressions(expressions)
101
- return unless expressions.size > 1 && expressions.uniq.one?
108
+ def check_expressions(node, expressions, insert_position)
109
+ inserted_expression = false
110
+
111
+ expressions.each do |expression|
112
+ add_offense(expression) do |corrector|
113
+ range = range_by_whole_lines(expression.source_range, include_final_newline: true)
114
+ corrector.remove(range)
115
+ next if inserted_expression
102
116
 
103
- expressions.each { |expression| add_offense(expression) }
117
+ if insert_position == :after_condition
118
+ corrector.insert_after(node, "\n#{expression.source}")
119
+ else
120
+ corrector.insert_before(node, "#{expression.source}\n")
121
+ end
122
+ inserted_expression = true
123
+ end
124
+ end
104
125
  end
105
126
 
106
127
  def message(node)
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for `in;` uses in `case` expressions.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # case expression
11
+ # in pattern_a; foo
12
+ # in pattern_b; bar
13
+ # end
14
+ #
15
+ # # good
16
+ # case expression
17
+ # in pattern_a then foo
18
+ # in pattern_b then bar
19
+ # end
20
+ #
21
+ class InPatternThen < Base
22
+ extend AutoCorrector
23
+ extend TargetRubyVersion
24
+
25
+ minimum_target_ruby_version 2.7
26
+
27
+ MSG = 'Do not use `in %<pattern>s;`. Use `in %<pattern>s then` instead.'
28
+
29
+ def on_in_pattern(node)
30
+ return if node.multiline? || node.then? || !node.body
31
+
32
+ pattern = node.pattern
33
+ pattern_source = if pattern.match_alt_type?
34
+ alternative_pattern_source(pattern)
35
+ else
36
+ pattern.source
37
+ end
38
+
39
+ add_offense(node.loc.begin, message: format(MSG, pattern: pattern_source)) do |corrector|
40
+ corrector.replace(node.loc.begin, ' then')
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def alternative_pattern_source(pattern)
47
+ return pattern.children.map(&:source) unless pattern.children.first.match_alt_type?
48
+
49
+ pattern_sources = alternative_pattern_source(pattern.children.first)
50
+
51
+ (pattern_sources << pattern.children[1].source).join(' | ')
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks uses of the `then` keyword in multi-line `in` statement.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # case expression
11
+ # in pattern then
12
+ # end
13
+ #
14
+ # # good
15
+ # case expression
16
+ # in pattern
17
+ # end
18
+ #
19
+ # # good
20
+ # case expression
21
+ # in pattern then do_something
22
+ # end
23
+ #
24
+ # # good
25
+ # case expression
26
+ # in pattern then do_something(arg1,
27
+ # arg2)
28
+ # end
29
+ #
30
+ class MultilineInPatternThen < Base
31
+ include RangeHelp
32
+ extend AutoCorrector
33
+ extend TargetRubyVersion
34
+
35
+ minimum_target_ruby_version 2.7
36
+
37
+ MSG = 'Do not use `then` for multiline `in` statement.'
38
+
39
+ def on_in_pattern(node)
40
+ return if !node.then? || require_then?(node)
41
+
42
+ range = node.loc.begin
43
+ add_offense(range) do |corrector|
44
+ corrector.remove(
45
+ range_with_surrounding_space(range: range, side: :left, newlines: false)
46
+ )
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ # Requires `then` for write `in` and its body on the same line.
53
+ def require_then?(in_pattern_node)
54
+ return true if in_pattern_node.pattern.first_line != in_pattern_node.pattern.last_line
55
+ return false unless in_pattern_node.body
56
+
57
+ in_pattern_node.loc.line == in_pattern_node.body.loc.line
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks if the quotes used for quoted symbols match the configured defaults.
7
+ # By default uses the same configuration as `Style/StringLiterals`.
8
+ #
9
+ # String interpolation is always kept in double quotes.
10
+ #
11
+ # Note: `Lint/SymbolConversion` can be used in parallel to ensure that symbols
12
+ # are not quoted that don't need to be. This cop is for configuring the quoting
13
+ # style to use for symbols that require quotes.
14
+ #
15
+ # @example EnforcedStyle: same_as_string_literals (default) / single_quotes
16
+ # # bad
17
+ # :"abc-def"
18
+ #
19
+ # # good
20
+ # :'abc-def'
21
+ # :"#{str}"
22
+ # :"a\'b"
23
+ #
24
+ # @example EnforcedStyle: double_quotes
25
+ # # bad
26
+ # :'abc-def'
27
+ #
28
+ # # good
29
+ # :"abc-def"
30
+ # :"#{str}"
31
+ # :"a\'b"
32
+ class QuotedSymbols < Base
33
+ include ConfigurableEnforcedStyle
34
+ include SymbolHelp
35
+ include StringLiteralsHelp
36
+ extend AutoCorrector
37
+
38
+ MSG_SINGLE = "Prefer single-quoted symbols when you don't need string interpolation " \
39
+ 'or special symbols.'
40
+ MSG_DOUBLE = 'Prefer double-quoted symbols unless you need single quotes to ' \
41
+ 'avoid extra backslashes for escaping.'
42
+
43
+ def on_sym(node)
44
+ return unless quoted?(node)
45
+
46
+ message = style == :single_quotes ? MSG_SINGLE : MSG_DOUBLE
47
+
48
+ if wrong_quotes?(node)
49
+ add_offense(node, message: message) do |corrector|
50
+ opposite_style_detected
51
+ autocorrect(corrector, node)
52
+ end
53
+ else
54
+ correct_style_detected
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def autocorrect(corrector, node)
61
+ str = if hash_key?(node)
62
+ # strip quotes
63
+ correct_quotes(node.source[1..-2])
64
+ else
65
+ # strip leading `:` and quotes
66
+ ":#{correct_quotes(node.source[2..-2])}"
67
+ end
68
+
69
+ corrector.replace(node, str)
70
+ end
71
+
72
+ def correct_quotes(str)
73
+ if style == :single_quotes
74
+ to_string_literal(str)
75
+ else
76
+ str.inspect
77
+ end
78
+ end
79
+
80
+ def style
81
+ return super unless super == :same_as_string_literals
82
+
83
+ string_literals_config = config.for_cop('Style/StringLiterals')
84
+ return :single_quotes unless string_literals_config['Enabled']
85
+
86
+ string_literals_config['EnforcedStyle'].to_sym
87
+ end
88
+
89
+ def alternative_style
90
+ (supported_styles - [style, :same_as_string_literals]).first
91
+ end
92
+
93
+ def quoted?(sym_node)
94
+ sym_node.source.match?(/\A:?(['"]).*?\1\z/m)
95
+ end
96
+
97
+ def wrong_quotes?(node)
98
+ return super if hash_key?(node)
99
+
100
+ super(node.source[1..-1])
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -29,6 +29,7 @@ module RuboCop
29
29
  class StringLiterals < Base
30
30
  include ConfigurableEnforcedStyle
31
31
  include StringLiteralsHelp
32
+ include StringHelp
32
33
  extend AutoCorrector
33
34
 
34
35
  MSG_INCONSISTENT = 'Inconsistent quote style.'
@@ -22,6 +22,7 @@ module RuboCop
22
22
  class StringLiteralsInInterpolation < Base
23
23
  include ConfigurableEnforcedStyle
24
24
  include StringLiteralsHelp
25
+ include StringHelp
25
26
  extend AutoCorrector
26
27
 
27
28
  def autocorrect(corrector, node)
@@ -50,7 +50,7 @@ module RuboCop
50
50
  RESTRICT_ON_SEND = %i[define_method].freeze
51
51
 
52
52
  def on_def(node)
53
- return unless node.root?
53
+ return unless top_level_method_definition?(node)
54
54
 
55
55
  add_offense(node)
56
56
  end
@@ -58,13 +58,21 @@ module RuboCop
58
58
  alias on_send on_def
59
59
 
60
60
  def on_block(node)
61
- return unless define_method_block?(node) && node.root?
61
+ return unless define_method_block?(node) && top_level_method_definition?(node)
62
62
 
63
63
  add_offense(node)
64
64
  end
65
65
 
66
66
  private
67
67
 
68
+ def top_level_method_definition?(node)
69
+ if node.parent&.begin_type?
70
+ node.parent.root?
71
+ else
72
+ node.root?
73
+ end
74
+ end
75
+
68
76
  # @!method define_method_block?(node)
69
77
  def_node_matcher :define_method_block?, <<~PATTERN
70
78
  (block (send _ {:define_method} _) ...)
@@ -20,12 +20,16 @@ module RuboCop
20
20
  class WhenThen < Base
21
21
  extend AutoCorrector
22
22
 
23
- MSG = 'Do not use `when x;`. Use `when x then` instead.'
23
+ MSG = 'Do not use `when %<expression>s;`. Use `when %<expression>s then` instead.'
24
24
 
25
25
  def on_when(node)
26
26
  return if node.multiline? || node.then? || !node.body
27
27
 
28
- add_offense(node.loc.begin) { |corrector| corrector.replace(node.loc.begin, ' then') }
28
+ message = format(MSG, expression: node.conditions.map(&:source).join(', '))
29
+
30
+ add_offense(node.loc.begin, message: message) do |corrector|
31
+ corrector.replace(node.loc.begin, ' then')
32
+ end
29
33
  end
30
34
  end
31
35
  end
@@ -41,7 +41,11 @@ module RuboCop
41
41
  end
42
42
 
43
43
  def range
44
- comment.location.expression
44
+ match = comment.text.match(DIRECTIVE_COMMENT_REGEXP)
45
+ begin_pos = comment.loc.expression.begin_pos
46
+ Parser::Source::Range.new(
47
+ comment.loc.expression.source_buffer, begin_pos + match.begin(0), begin_pos + match.end(0)
48
+ )
45
49
  end
46
50
 
47
51
  # Returns match captures to directive comment pattern
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  # This module holds the RuboCop version information.
5
5
  module Version
6
- STRING = '1.15.0'
6
+ STRING = '1.16.0'
7
7
 
8
8
  MSG = '%<version>s (using Parser %<parser_version>s, '\
9
9
  'rubocop-ast %<rubocop_ast_version>s, ' \
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.15.0
4
+ version: 1.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2021-05-17 00:00:00.000000000 Z
13
+ date: 2021-06-01 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: parallel
@@ -100,7 +100,7 @@ dependencies:
100
100
  requirements:
101
101
  - - ">="
102
102
  - !ruby/object:Gem::Version
103
- version: 1.5.0
103
+ version: 1.7.0
104
104
  - - "<"
105
105
  - !ruby/object:Gem::Version
106
106
  version: '2.0'
@@ -110,7 +110,7 @@ dependencies:
110
110
  requirements:
111
111
  - - ">="
112
112
  - !ruby/object:Gem::Version
113
- version: 1.5.0
113
+ version: 1.7.0
114
114
  - - "<"
115
115
  - !ruby/object:Gem::Version
116
116
  version: '2.0'
@@ -397,6 +397,7 @@ files:
397
397
  - lib/rubocop/cop/lint/empty_ensure.rb
398
398
  - lib/rubocop/cop/lint/empty_expression.rb
399
399
  - lib/rubocop/cop/lint/empty_file.rb
400
+ - lib/rubocop/cop/lint/empty_in_pattern.rb
400
401
  - lib/rubocop/cop/lint/empty_interpolation.rb
401
402
  - lib/rubocop/cop/lint/empty_when.rb
402
403
  - lib/rubocop/cop/lint/ensure_return.rb
@@ -562,6 +563,7 @@ files:
562
563
  - lib/rubocop/cop/mixin/string_help.rb
563
564
  - lib/rubocop/cop/mixin/string_literals_help.rb
564
565
  - lib/rubocop/cop/mixin/surrounding_space.rb
566
+ - lib/rubocop/cop/mixin/symbol_help.rb
565
567
  - lib/rubocop/cop/mixin/target_ruby_version.rb
566
568
  - lib/rubocop/cop/mixin/trailing_body.rb
567
569
  - lib/rubocop/cop/mixin/trailing_comma.rb
@@ -676,6 +678,7 @@ files:
676
678
  - lib/rubocop/cop/style/if_with_boolean_literal_branches.rb
677
679
  - lib/rubocop/cop/style/if_with_semicolon.rb
678
680
  - lib/rubocop/cop/style/implicit_runtime_error.rb
681
+ - lib/rubocop/cop/style/in_pattern_then.rb
679
682
  - lib/rubocop/cop/style/infinite_loop.rb
680
683
  - lib/rubocop/cop/style/inline_comment.rb
681
684
  - lib/rubocop/cop/style/inverse_methods.rb
@@ -699,6 +702,7 @@ files:
699
702
  - lib/rubocop/cop/style/multiline_block_chain.rb
700
703
  - lib/rubocop/cop/style/multiline_if_modifier.rb
701
704
  - lib/rubocop/cop/style/multiline_if_then.rb
705
+ - lib/rubocop/cop/style/multiline_in_pattern_then.rb
702
706
  - lib/rubocop/cop/style/multiline_memoization.rb
703
707
  - lib/rubocop/cop/style/multiline_method_signature.rb
704
708
  - lib/rubocop/cop/style/multiline_ternary_operator.rb
@@ -732,6 +736,7 @@ files:
732
736
  - lib/rubocop/cop/style/perl_backrefs.rb
733
737
  - lib/rubocop/cop/style/preferred_hash_methods.rb
734
738
  - lib/rubocop/cop/style/proc.rb
739
+ - lib/rubocop/cop/style/quoted_symbols.rb
735
740
  - lib/rubocop/cop/style/raise_args.rb
736
741
  - lib/rubocop/cop/style/random_with_offset.rb
737
742
  - lib/rubocop/cop/style/redundant_argument.rb
@@ -876,7 +881,7 @@ metadata:
876
881
  homepage_uri: https://rubocop.org/
877
882
  changelog_uri: https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md
878
883
  source_code_uri: https://github.com/rubocop/rubocop/
879
- documentation_uri: https://docs.rubocop.org/rubocop/1.15/
884
+ documentation_uri: https://docs.rubocop.org/rubocop/1.16/
880
885
  bug_tracker_uri: https://github.com/rubocop/rubocop/issues
881
886
  post_install_message:
882
887
  rdoc_options: []