rubocop 1.15.0 → 1.16.0

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.
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: []