rubocop 1.41.1 → 1.43.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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +2 -2
  4. data/config/default.yml +33 -2
  5. data/lib/rubocop/cli.rb +1 -1
  6. data/lib/rubocop/config.rb +7 -7
  7. data/lib/rubocop/config_loader_resolver.rb +5 -1
  8. data/lib/rubocop/cop/base.rb +62 -61
  9. data/lib/rubocop/cop/cop.rb +28 -28
  10. data/lib/rubocop/cop/corrector.rb +23 -11
  11. data/lib/rubocop/cop/gemspec/dependency_version.rb +16 -18
  12. data/lib/rubocop/cop/layout/class_structure.rb +32 -10
  13. data/lib/rubocop/cop/layout/comment_indentation.rb +3 -1
  14. data/lib/rubocop/cop/layout/indentation_style.rb +4 -1
  15. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +6 -6
  16. data/lib/rubocop/cop/layout/multiline_block_layout.rb +1 -1
  17. data/lib/rubocop/cop/layout/space_around_keyword.rb +1 -1
  18. data/lib/rubocop/cop/layout/trailing_whitespace.rb +5 -2
  19. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +19 -0
  20. data/lib/rubocop/cop/lint/regexp_as_condition.rb +6 -0
  21. data/lib/rubocop/cop/lint/require_parentheses.rb +3 -1
  22. data/lib/rubocop/cop/lint/unused_method_argument.rb +2 -1
  23. data/lib/rubocop/cop/lint/useless_rescue.rb +71 -0
  24. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +5 -3
  25. data/lib/rubocop/cop/metrics/parameter_lists.rb +27 -0
  26. data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -1
  27. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +4 -4
  28. data/lib/rubocop/cop/mixin/annotation_comment.rb +1 -1
  29. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +9 -1
  30. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +1 -1
  31. data/lib/rubocop/cop/mixin/statement_modifier.rb +1 -0
  32. data/lib/rubocop/cop/registry.rb +22 -22
  33. data/lib/rubocop/cop/security/compound_hash.rb +2 -1
  34. data/lib/rubocop/cop/style/block_comments.rb +1 -1
  35. data/lib/rubocop/cop/style/concat_array_literals.rb +22 -2
  36. data/lib/rubocop/cop/style/guard_clause.rb +11 -7
  37. data/lib/rubocop/cop/style/hash_each_methods.rb +13 -1
  38. data/lib/rubocop/cop/style/hash_syntax.rb +11 -7
  39. data/lib/rubocop/cop/style/identical_conditional_branches.rb +15 -0
  40. data/lib/rubocop/cop/style/map_to_set.rb +61 -0
  41. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +12 -9
  42. data/lib/rubocop/cop/style/method_def_parentheses.rb +11 -4
  43. data/lib/rubocop/cop/style/min_max_comparison.rb +73 -0
  44. data/lib/rubocop/cop/style/missing_else.rb +13 -1
  45. data/lib/rubocop/cop/style/operator_method_call.rb +15 -1
  46. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +1 -1
  47. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +2 -1
  48. data/lib/rubocop/cop/style/redundant_string_escape.rb +4 -2
  49. data/lib/rubocop/cop/style/require_order.rb +4 -2
  50. data/lib/rubocop/cop/style/select_by_regexp.rb +6 -2
  51. data/lib/rubocop/cop/style/signal_exception.rb +8 -6
  52. data/lib/rubocop/cop/style/string_hash_keys.rb +4 -1
  53. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +4 -4
  54. data/lib/rubocop/cop/style/word_array.rb +41 -0
  55. data/lib/rubocop/cop/style/yoda_expression.rb +81 -0
  56. data/lib/rubocop/cop/style/zero_length_predicate.rb +31 -14
  57. data/lib/rubocop/cop/team.rb +29 -29
  58. data/lib/rubocop/cop/variable_force.rb +0 -3
  59. data/lib/rubocop/path_util.rb +17 -7
  60. data/lib/rubocop/result_cache.rb +1 -1
  61. data/lib/rubocop/runner.rb +10 -3
  62. data/lib/rubocop/target_ruby.rb +0 -1
  63. data/lib/rubocop/version.rb +1 -1
  64. data/lib/rubocop.rb +4 -0
  65. metadata +13 -9
@@ -256,7 +256,7 @@ module RuboCop
256
256
  # regular dotted method calls bind more tightly than operators
257
257
  # so we need to climb up the AST past them
258
258
  node.each_ancestor do |ancestor|
259
- return true if ancestor.and_type? || ancestor.or_type?
259
+ return true if ancestor.and_type? || ancestor.or_type? || ancestor.range_type?
260
260
  return false unless ancestor.send_type?
261
261
  return true if ancestor.operator_method?
262
262
  end
@@ -47,7 +47,6 @@ module RuboCop
47
47
  MSG = 'Trailing whitespace detected.'
48
48
 
49
49
  def on_new_investigation
50
- @heredocs = extract_heredocs(processed_source.ast)
51
50
  processed_source.lines.each_with_index do |line, index|
52
51
  next unless line.end_with?(' ', "\t")
53
52
 
@@ -102,10 +101,14 @@ module RuboCop
102
101
  end
103
102
 
104
103
  def find_heredoc(line_number)
105
- @heredocs.each { |node, r| return node if r.include?(line_number) }
104
+ heredocs.each { |node, r| return node if r.include?(line_number) }
106
105
  nil
107
106
  end
108
107
 
108
+ def heredocs
109
+ @heredocs ||= extract_heredocs(processed_source.ast)
110
+ end
111
+
109
112
  def extract_heredocs(ast)
110
113
  return [] unless ast
111
114
 
@@ -68,6 +68,12 @@ module RuboCop
68
68
  @valid_ref = regexp_conditions.map { |condition| check_regexp(condition) }.compact.max
69
69
  end
70
70
 
71
+ def on_in_pattern(node)
72
+ regexp_patterns = patterns(node).select(&:regexp_type?)
73
+
74
+ @valid_ref = regexp_patterns.map { |pattern| check_regexp(pattern) }.compact.max
75
+ end
76
+
71
77
  def on_nth_ref(node)
72
78
  backref, = *node
73
79
  return if @valid_ref.nil? || backref <= @valid_ref
@@ -84,6 +90,19 @@ module RuboCop
84
90
 
85
91
  private
86
92
 
93
+ def patterns(pattern_node)
94
+ pattern = pattern_node.node_parts[0]
95
+
96
+ case pattern.type
97
+ when :array_pattern, :match_alt
98
+ pattern.children
99
+ when :match_as
100
+ patterns(pattern)
101
+ else
102
+ [pattern]
103
+ end
104
+ end
105
+
87
106
  def check_regexp(node)
88
107
  return if node.interpolation?
89
108
 
@@ -17,13 +17,19 @@ module RuboCop
17
17
  # do_something
18
18
  # end
19
19
  class RegexpAsCondition < Base
20
+ include IgnoredNode
20
21
  extend AutoCorrector
21
22
 
22
23
  MSG = 'Do not use regexp literal as a condition. ' \
23
24
  'The regexp literal matches `$_` implicitly.'
24
25
 
25
26
  def on_match_current_line(node)
27
+ return if node.ancestors.none?(&:conditional?)
28
+ return if part_of_ignored_node?(node)
29
+
26
30
  add_offense(node) { |corrector| corrector.replace(node, "#{node.source} =~ $_") }
31
+
32
+ ignore_node(node)
27
33
  end
28
34
  end
29
35
  end
@@ -46,7 +46,9 @@ module RuboCop
46
46
  private
47
47
 
48
48
  def check_ternary(ternary, node)
49
- return if node.method?(:[]) || !ternary.condition.operator_keyword?
49
+ if node.method?(:[]) || node.assignment_method? || !ternary.condition.operator_keyword?
50
+ return
51
+ end
50
52
 
51
53
  range = range_between(node.source_range.begin_pos, ternary.condition.source_range.end_pos)
52
54
 
@@ -100,7 +100,8 @@ module RuboCop
100
100
 
101
101
  unless variable.keyword_argument?
102
102
  message << " If it's necessary, use `_` or `_#{variable.name}` " \
103
- "as an argument name to indicate that it won't be used."
103
+ "as an argument name to indicate that it won't be used. " \
104
+ "If it's unnecessary, remove it."
104
105
  end
105
106
 
106
107
  scope = variable.scope
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks for useless `rescue`s, which only reraise rescued exceptions.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # def foo
11
+ # do_something
12
+ # rescue
13
+ # raise
14
+ # end
15
+ #
16
+ # # bad
17
+ # def foo
18
+ # do_something
19
+ # rescue => e
20
+ # raise # or 'raise e', or 'raise $!', or 'raise $ERROR_INFO'
21
+ # end
22
+ #
23
+ # # good
24
+ # def foo
25
+ # do_something
26
+ # rescue
27
+ # do_cleanup
28
+ # raise
29
+ # end
30
+ #
31
+ # # bad (latest rescue)
32
+ # def foo
33
+ # do_something
34
+ # rescue ArgumentError
35
+ # # noop
36
+ # rescue
37
+ # raise
38
+ # end
39
+ #
40
+ # # good (not the latest rescue)
41
+ # def foo
42
+ # do_something
43
+ # rescue ArgumentError
44
+ # raise
45
+ # rescue
46
+ # # noop
47
+ # end
48
+ #
49
+ class UselessRescue < Base
50
+ MSG = 'Useless `rescue` detected.'
51
+
52
+ def on_rescue(node)
53
+ resbody_node = node.resbody_branches.last
54
+ add_offense(resbody_node) if only_reraising?(resbody_node)
55
+ end
56
+
57
+ private
58
+
59
+ def only_reraising?(resbody_node)
60
+ body = resbody_node.body
61
+ return false if body.nil? || !body.send_type? || !body.method?(:raise)
62
+ return true unless body.arguments?
63
+ return false if body.arguments.size > 1
64
+
65
+ exception_name = body.first_argument.source
66
+ [resbody_node.exception_variable&.source, '$!', '$ERROR_INFO'].include?(exception_name)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -77,10 +77,12 @@ module RuboCop
77
77
  PATTERN
78
78
 
79
79
  def on_send(node)
80
- if node.first_argument.def_type?
81
- inspect_def(node, node.first_argument)
80
+ return unless (first_argument = node.first_argument)
81
+
82
+ if first_argument.def_type?
83
+ inspect_def(node, first_argument)
82
84
  elsif node.first_argument.sym_type?
83
- inspect_sym(node, node.first_argument)
85
+ inspect_sym(node, first_argument)
84
86
  end
85
87
  end
86
88
 
@@ -9,6 +9,20 @@ module RuboCop
9
9
  # Keyword arguments can optionally be excluded from the total count,
10
10
  # as they add less complexity than positional or optional parameters.
11
11
  #
12
+ # Any number of arguments for `initialize` method inside a block of
13
+ # `Struct.new` and `Data.define` like this is always allowed:
14
+ #
15
+ # [source,ruby]
16
+ # ----
17
+ # Struct.new(:one, :two, :three, :four, :five, keyword_init: true) do
18
+ # def initialize(one:, two:, three:, four:, five:)
19
+ # end
20
+ # end
21
+ # ----
22
+ #
23
+ # This is because checking the number of arguments of the `initialize` method
24
+ # does not make sense.
25
+ #
12
26
  # NOTE: Explicit block argument `&block` is not counted to prevent
13
27
  # erroneous change that is avoided by making block argument implicit.
14
28
  #
@@ -63,6 +77,16 @@ module RuboCop
63
77
  NAMED_KEYWORD_TYPES = %i[kwoptarg kwarg].freeze
64
78
  private_constant :NAMED_KEYWORD_TYPES
65
79
 
80
+ # @!method struct_new_or_data_define_block?(node)
81
+ def_node_matcher :struct_new_or_data_define_block?, <<~PATTERN
82
+ (block
83
+ {
84
+ (send (const {nil? cbase} :Struct) :new ...)
85
+ (send (const {nil? cbase} :Data) :define ...)
86
+ }
87
+ (args) ...)
88
+ PATTERN
89
+
66
90
  def on_def(node)
67
91
  optargs = node.arguments.select(&:optarg_type?)
68
92
  return if optargs.count <= max_optional_parameters
@@ -78,6 +102,9 @@ module RuboCop
78
102
  alias on_defs on_def
79
103
 
80
104
  def on_args(node)
105
+ parent = node.parent
106
+ return if parent.method?(:initialize) && struct_new_or_data_define_block?(parent.parent)
107
+
81
108
  count = args_count(node)
82
109
  return unless count > max_params
83
110
 
@@ -45,7 +45,7 @@ module RuboCop
45
45
  else
46
46
  # Otherwise, the case node gets 0.8 complexity points and each
47
47
  # when gets 0.2.
48
- (0.8 + (0.2 * nb_branches)).round
48
+ ((nb_branches * 0.2) + 0.8).round
49
49
  end
50
50
  when :if
51
51
  node.else? && !node.elsif? ? 2 : 1
@@ -25,15 +25,15 @@ module RuboCop
25
25
  # > http://c2.com/cgi/wiki?AbcMetric
26
26
  CONDITION_NODES = CyclomaticComplexity::COUNTED_NODES.freeze
27
27
 
28
- def self.calculate(node, discount_repeated_attributes: false)
29
- new(node, discount_repeated_attributes: discount_repeated_attributes).calculate
30
- end
31
-
32
28
  # TODO: move to rubocop-ast
33
29
  ARGUMENT_TYPES = %i[arg optarg restarg kwarg kwoptarg kwrestarg blockarg].freeze
34
30
 
35
31
  private_constant :BRANCH_NODES, :CONDITION_NODES, :ARGUMENT_TYPES
36
32
 
33
+ def self.calculate(node, discount_repeated_attributes: false)
34
+ new(node, discount_repeated_attributes: discount_repeated_attributes).calculate
35
+ end
36
+
37
37
  def initialize(node)
38
38
  @assignment = 0
39
39
  @branch = 0
@@ -47,7 +47,7 @@ module RuboCop
47
47
  match.captures
48
48
  end
49
49
 
50
- KEYWORDS_REGEX_CACHE = {} # rubocop:disable Layout/ClassStructure, Style/MutableConstant
50
+ KEYWORDS_REGEX_CACHE = {} # rubocop:disable Style/MutableConstant
51
51
  private_constant :KEYWORDS_REGEX_CACHE
52
52
 
53
53
  def regex
@@ -3,6 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  # This module checks for Ruby 3.1's hash value omission syntax.
6
+ # rubocop:disable Metrics/ModuleLength
6
7
  module HashShorthandSyntax
7
8
  OMIT_HASH_VALUE_MSG = 'Omit the hash value.'
8
9
  EXPLICIT_HASH_VALUE_MSG = 'Include the hash value.'
@@ -93,6 +94,8 @@ module RuboCop
93
94
  end
94
95
 
95
96
  def def_node_that_require_parentheses(node)
97
+ last_pair = node.parent.pairs.last
98
+ return unless last_pair.key.source == last_pair.value.source
96
99
  return unless (send_node = find_ancestor_send_node(node))
97
100
  return unless without_parentheses_call_expr_follows?(send_node)
98
101
 
@@ -104,7 +107,11 @@ module RuboCop
104
107
  def find_ancestor_send_node(node)
105
108
  ancestor = node.parent.parent
106
109
 
107
- ancestor if ancestor&.call_type? && !ancestor&.method?(:[])
110
+ ancestor if ancestor&.call_type? && !brackets?(ancestor)
111
+ end
112
+
113
+ def brackets?(send_node)
114
+ send_node.method?(:[]) || send_node.method?(:[]=)
108
115
  end
109
116
 
110
117
  def use_element_of_hash_literal_as_receiver?(ancestor, parent)
@@ -184,4 +191,5 @@ module RuboCop
184
191
  end
185
192
  end
186
193
  end
194
+ # rubocop:enable Metrics/ModuleLength
187
195
  end
@@ -175,7 +175,7 @@ module RuboCop
175
175
 
176
176
  def remove_optarg_equals(asgn_tokens, processed_source)
177
177
  optargs = processed_source.ast.each_node(:optarg)
178
- optarg_eql = optargs.map { |o| o.loc.operator.begin_pos }.to_set
178
+ optarg_eql = optargs.to_set { |o| o.loc.operator.begin_pos }
179
179
  asgn_tokens.reject { |t| optarg_eql.include?(t.begin_pos) }
180
180
  end
181
181
  end
@@ -5,6 +5,7 @@ module RuboCop
5
5
  # Common functionality for modifier cops.
6
6
  module StatementModifier
7
7
  include LineLengthHelp
8
+ include RangeHelp
8
9
 
9
10
  private
10
11
 
@@ -19,6 +19,28 @@ module RuboCop
19
19
  class Registry
20
20
  include Enumerable
21
21
 
22
+ def self.all
23
+ global.without_department(:Test).cops
24
+ end
25
+
26
+ def self.qualified_cop_name(name, origin)
27
+ global.qualified_cop_name(name, origin)
28
+ end
29
+
30
+ # Changes momentarily the global registry
31
+ # Intended for testing purposes
32
+ def self.with_temporary_global(temp_global = global.dup)
33
+ previous = @global
34
+ @global = temp_global
35
+ yield
36
+ ensure
37
+ @global = previous
38
+ end
39
+
40
+ def self.reset!
41
+ @global = new
42
+ end
43
+
22
44
  attr_reader :options
23
45
 
24
46
  def initialize(cops = [], options = {})
@@ -238,28 +260,6 @@ module RuboCop
238
260
  attr_reader :global
239
261
  end
240
262
 
241
- def self.all
242
- global.without_department(:Test).cops
243
- end
244
-
245
- def self.qualified_cop_name(name, origin)
246
- global.qualified_cop_name(name, origin)
247
- end
248
-
249
- # Changes momentarily the global registry
250
- # Intended for testing purposes
251
- def self.with_temporary_global(temp_global = global.dup)
252
- previous = @global
253
- @global = temp_global
254
- yield
255
- ensure
256
- @global = previous
257
- end
258
-
259
- def self.reset!
260
- @global = new
261
- end
262
-
263
263
  private
264
264
 
265
265
  def initialize_copy(reg)
@@ -9,7 +9,8 @@ module RuboCop
9
9
  # Manually combining hashes is error prone and hard to follow, especially
10
10
  # when there are many values. Poor implementations may also introduce
11
11
  # performance or security concerns if they are prone to collisions.
12
- # Delegating to `Array#hash` is clearer, faster, and safer.
12
+ # Delegating to `Array#hash` is clearer and safer, although it might be slower
13
+ # depending on the use case.
13
14
  #
14
15
  # @safety
15
16
  # This cop may be unsafe if the application logic depends on the hash
@@ -32,7 +32,7 @@ module RuboCop
32
32
  eq_begin, eq_end, contents = parts(comment)
33
33
 
34
34
  corrector.remove(eq_begin)
35
- unless contents.length.zero?
35
+ unless contents.empty?
36
36
  corrector.replace(
37
37
  contents,
38
38
  contents.source.gsub(/\A/, '# ').gsub(/\n\n/, "\n#\n").gsub(/\n(?=[^#])/, "\n# ")
@@ -30,6 +30,7 @@ module RuboCop
30
30
  'Use `push` with elements as arguments without array brackets instead of `%<current>s`.'
31
31
  RESTRICT_ON_SEND = %i[concat].freeze
32
32
 
33
+ # rubocop:disable Metrics
33
34
  def on_send(node)
34
35
  return if node.arguments.empty?
35
36
  return unless node.arguments.all?(&:array_type?)
@@ -38,7 +39,12 @@ module RuboCop
38
39
  current = offense.source
39
40
 
40
41
  if node.arguments.any?(&:percent_literal?)
41
- message = format(MSG_FOR_PERCENT_LITERALS, current: current)
42
+ if percent_literals_includes_only_basic_literals?(node)
43
+ prefer = preferred_method(node)
44
+ message = format(MSG, prefer: prefer, current: current)
45
+ else
46
+ message = format(MSG_FOR_PERCENT_LITERALS, current: current)
47
+ end
42
48
  else
43
49
  prefer = preferred_method(node)
44
50
  message = format(MSG, prefer: prefer, current: current)
@@ -48,6 +54,7 @@ module RuboCop
48
54
  corrector.replace(offense, prefer)
49
55
  end
50
56
  end
57
+ # rubocop:enable Metrics
51
58
 
52
59
  private
53
60
 
@@ -56,10 +63,23 @@ module RuboCop
56
63
  end
57
64
 
58
65
  def preferred_method(node)
59
- new_arguments = node.arguments.map { |arg| arg.children.map(&:source) }.join(', ')
66
+ new_arguments =
67
+ node.arguments.map do |arg|
68
+ if arg.percent_literal?
69
+ arg.children.map(&:value).map(&:inspect)
70
+ else
71
+ arg.children.map(&:source)
72
+ end
73
+ end.join(', ')
60
74
 
61
75
  "push(#{new_arguments})"
62
76
  end
77
+
78
+ def percent_literals_includes_only_basic_literals?(node)
79
+ node.arguments.select(&:percent_literal?).all? do |arg|
80
+ arg.children.all? { |child| child.str_type? || child.sym_type? }
81
+ end
82
+ end
63
83
  end
64
84
  end
65
85
  end
@@ -196,7 +196,7 @@ module RuboCop
196
196
  return unless node.else?
197
197
 
198
198
  corrector.remove(node.loc.else)
199
- corrector.remove(branch_to_remove(node, guard))
199
+ corrector.remove(range_of_branch_to_remove(node, guard))
200
200
  end
201
201
  end
202
202
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
@@ -206,20 +206,24 @@ module RuboCop
206
206
  end
207
207
 
208
208
  def autocorrect_heredoc_argument(corrector, node, heredoc_branch, leave_branch, guard)
209
+ return unless node.else?
210
+
209
211
  remove_whole_lines(corrector, leave_branch.source_range)
210
212
  remove_whole_lines(corrector, node.loc.else)
211
213
  remove_whole_lines(corrector, node.loc.end)
212
- remove_whole_lines(corrector, branch_to_remove(node, guard).source_range)
214
+ remove_whole_lines(corrector, range_of_branch_to_remove(node, guard))
213
215
  corrector.insert_after(
214
216
  heredoc_branch.last_argument.loc.heredoc_end, "\n#{leave_branch.source}"
215
217
  )
216
218
  end
217
219
 
218
- def branch_to_remove(node, guard)
219
- case guard
220
- when :if then node.if_branch
221
- when :else then node.else_branch
222
- end
220
+ def range_of_branch_to_remove(node, guard)
221
+ branch = case guard
222
+ when :if then node.if_branch
223
+ when :else then node.else_branch
224
+ end
225
+
226
+ branch.source_range
223
227
  end
224
228
 
225
229
  def guard_clause_source(guard_clause)
@@ -118,11 +118,23 @@ module RuboCop
118
118
  end
119
119
 
120
120
  def allowed_receiver?(receiver)
121
- receiver_name = receiver.send_type? ? receiver.method_name.to_s : receiver.source
121
+ receiver_name = receiver_name(receiver)
122
122
 
123
123
  allowed_receivers.include?(receiver_name)
124
124
  end
125
125
 
126
+ def receiver_name(receiver)
127
+ if receiver.send_type?
128
+ if receiver.receiver
129
+ "#{receiver_name(receiver.receiver)}.#{receiver.method_name}"
130
+ else
131
+ receiver.method_name.to_s
132
+ end
133
+ else
134
+ receiver.source
135
+ end
136
+ end
137
+
126
138
  def allowed_receivers
127
139
  cop_config.fetch('AllowedReceivers', [])
128
140
  end
@@ -28,7 +28,7 @@ module RuboCop
28
28
  # * always - forces use of the 3.1 syntax (e.g. {foo:})
29
29
  # * never - forces use of explicit hash literal value
30
30
  # * either - accepts both shorthand and explicit use of hash literal value
31
- # * consistent - like "either", but will avoid mixing styles in a single hash
31
+ # * consistent - forces use of the 3.1 syntax only if all values can be omitted in the hash
32
32
  #
33
33
  # @example EnforcedStyle: ruby19 (default)
34
34
  # # bad
@@ -92,16 +92,19 @@ module RuboCop
92
92
  #
93
93
  # @example EnforcedShorthandSyntax: consistent
94
94
  #
95
- # # bad
96
- # {foo: , bar: bar}
95
+ # # bad - `foo` and `bar` values can be omitted
96
+ # {foo: foo, bar: bar}
97
97
  #
98
- # # good
99
- # {foo:, bar:}
98
+ # # bad - `bar` value can be omitted
99
+ # {foo:, bar: bar}
100
100
  #
101
- # # bad
102
- # {foo: , bar: baz}
101
+ # # bad - mixed syntaxes
102
+ # {foo:, bar: baz}
103
103
  #
104
104
  # # good
105
+ # {foo:, bar:}
106
+ #
107
+ # # good - can't omit `baz`
105
108
  # {foo: foo, bar: baz}
106
109
  #
107
110
  class HashSyntax < Base
@@ -251,6 +254,7 @@ module RuboCop
251
254
  op = pair_node.loc.operator
252
255
 
253
256
  key_with_hash_rocket = ":#{pair_node.key.source}#{pair_node.inverse_delimiter(true)}"
257
+ key_with_hash_rocket += pair_node.key.source if pair_node.value_omission?
254
258
  corrector.replace(pair_node.key, key_with_hash_rocket)
255
259
  corrector.remove(range_with_surrounding_space(op))
256
260
  end
@@ -136,6 +136,7 @@ module RuboCop
136
136
 
137
137
  private
138
138
 
139
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
139
140
  def check_branches(node, branches)
140
141
  # return if any branch is empty. An empty branch can be an `if`
141
142
  # without an `else` or a branch that contains only comments.
@@ -144,9 +145,13 @@ module RuboCop
144
145
  tails = branches.map { |branch| tail(branch) }
145
146
  check_expressions(node, tails, :after_condition) if duplicated_expressions?(node, tails)
146
147
 
148
+ return if last_child_of_parent?(node) &&
149
+ branches.any? { |branch| single_child_branch?(branch) }
150
+
147
151
  heads = branches.map { |branch| head(branch) }
148
152
  check_expressions(node, heads, :before_condition) if duplicated_expressions?(node, heads)
149
153
  end
154
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
150
155
 
151
156
  def duplicated_expressions?(node, expressions)
152
157
  unique_expressions = expressions.uniq
@@ -180,6 +185,16 @@ module RuboCop
180
185
  end
181
186
  end
182
187
 
188
+ def last_child_of_parent?(node)
189
+ return true unless (parent = node.parent)
190
+
191
+ parent.child_nodes.last == node
192
+ end
193
+
194
+ def single_child_branch?(branch_node)
195
+ !branch_node.begin_type? || branch_node.children.size == 1
196
+ end
197
+
183
198
  def message(node)
184
199
  format(MSG, source: node.source)
185
200
  end