rubocop 1.40.0 → 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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +2 -2
  4. data/config/default.yml +52 -2
  5. data/lib/rubocop/cli.rb +1 -1
  6. data/lib/rubocop/config.rb +34 -11
  7. data/lib/rubocop/config_loader.rb +9 -0
  8. data/lib/rubocop/config_loader_resolver.rb +5 -1
  9. data/lib/rubocop/config_validator.rb +1 -1
  10. data/lib/rubocop/cop/badge.rb +9 -4
  11. data/lib/rubocop/cop/base.rb +83 -66
  12. data/lib/rubocop/cop/commissioner.rb +8 -3
  13. data/lib/rubocop/cop/cop.rb +29 -29
  14. data/lib/rubocop/cop/corrector.rb +23 -11
  15. data/lib/rubocop/cop/gemspec/dependency_version.rb +16 -18
  16. data/lib/rubocop/cop/internal_affairs/cop_description.rb +3 -1
  17. data/lib/rubocop/cop/layout/class_structure.rb +32 -11
  18. data/lib/rubocop/cop/layout/comment_indentation.rb +3 -1
  19. data/lib/rubocop/cop/layout/empty_lines.rb +2 -0
  20. data/lib/rubocop/cop/layout/extra_spacing.rb +10 -6
  21. data/lib/rubocop/cop/layout/first_array_element_line_break.rb +38 -2
  22. data/lib/rubocop/cop/layout/first_hash_element_line_break.rb +49 -2
  23. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +61 -2
  24. data/lib/rubocop/cop/layout/first_method_parameter_line_break.rb +52 -2
  25. data/lib/rubocop/cop/layout/indentation_style.rb +7 -2
  26. data/lib/rubocop/cop/layout/line_continuation_leading_space.rb +5 -0
  27. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +11 -5
  28. data/lib/rubocop/cop/layout/line_length.rb +2 -0
  29. data/lib/rubocop/cop/layout/multiline_array_line_breaks.rb +51 -2
  30. data/lib/rubocop/cop/layout/multiline_block_layout.rb +1 -1
  31. data/lib/rubocop/cop/layout/multiline_hash_key_line_breaks.rb +49 -2
  32. data/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb +53 -2
  33. data/lib/rubocop/cop/layout/multiline_method_parameter_line_breaks.rb +58 -2
  34. data/lib/rubocop/cop/layout/redundant_line_break.rb +2 -2
  35. data/lib/rubocop/cop/layout/space_around_keyword.rb +1 -1
  36. data/lib/rubocop/cop/layout/trailing_empty_lines.rb +1 -1
  37. data/lib/rubocop/cop/layout/trailing_whitespace.rb +11 -4
  38. data/lib/rubocop/cop/lint/constant_resolution.rb +4 -0
  39. data/lib/rubocop/cop/lint/debugger.rb +3 -1
  40. data/lib/rubocop/cop/lint/duplicate_branch.rb +0 -2
  41. data/lib/rubocop/cop/lint/duplicate_methods.rb +19 -8
  42. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +10 -5
  43. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +19 -0
  44. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +3 -1
  45. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +1 -1
  46. data/lib/rubocop/cop/lint/regexp_as_condition.rb +6 -0
  47. data/lib/rubocop/cop/lint/require_parentheses.rb +3 -1
  48. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +8 -19
  49. data/lib/rubocop/cop/lint/unused_method_argument.rb +2 -1
  50. data/lib/rubocop/cop/lint/useless_rescue.rb +71 -0
  51. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +5 -3
  52. data/lib/rubocop/cop/metrics/class_length.rb +1 -1
  53. data/lib/rubocop/cop/metrics/module_length.rb +1 -1
  54. data/lib/rubocop/cop/metrics/parameter_lists.rb +27 -0
  55. data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -1
  56. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +4 -4
  57. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +2 -2
  58. data/lib/rubocop/cop/mixin/alignment.rb +1 -1
  59. data/lib/rubocop/cop/mixin/allowed_identifiers.rb +2 -2
  60. data/lib/rubocop/cop/mixin/annotation_comment.rb +13 -6
  61. data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +21 -9
  62. data/lib/rubocop/cop/mixin/first_element_line_break.rb +11 -7
  63. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +14 -2
  64. data/lib/rubocop/cop/mixin/line_length_help.rb +8 -1
  65. data/lib/rubocop/cop/mixin/method_complexity.rb +5 -3
  66. data/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb +5 -3
  67. data/lib/rubocop/cop/mixin/percent_array.rb +3 -5
  68. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +1 -1
  69. data/lib/rubocop/cop/mixin/require_library.rb +2 -0
  70. data/lib/rubocop/cop/mixin/rescue_node.rb +3 -3
  71. data/lib/rubocop/cop/mixin/statement_modifier.rb +2 -1
  72. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  73. data/lib/rubocop/cop/naming/class_and_module_camel_case.rb +2 -0
  74. data/lib/rubocop/cop/naming/inclusive_language.rb +4 -1
  75. data/lib/rubocop/cop/registry.rb +28 -25
  76. data/lib/rubocop/cop/security/compound_hash.rb +2 -1
  77. data/lib/rubocop/cop/style/alias.rb +9 -1
  78. data/lib/rubocop/cop/style/block_comments.rb +1 -1
  79. data/lib/rubocop/cop/style/concat_array_literals.rb +86 -0
  80. data/lib/rubocop/cop/style/documentation.rb +11 -5
  81. data/lib/rubocop/cop/style/guard_clause.rb +17 -9
  82. data/lib/rubocop/cop/style/hash_each_methods.rb +13 -1
  83. data/lib/rubocop/cop/style/hash_syntax.rb +11 -7
  84. data/lib/rubocop/cop/style/identical_conditional_branches.rb +15 -0
  85. data/lib/rubocop/cop/style/if_with_semicolon.rb +2 -3
  86. data/lib/rubocop/cop/style/inverse_methods.rb +2 -0
  87. data/lib/rubocop/cop/style/line_end_concatenation.rb +4 -1
  88. data/lib/rubocop/cop/style/map_to_set.rb +61 -0
  89. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +12 -9
  90. data/lib/rubocop/cop/style/method_def_parentheses.rb +11 -4
  91. data/lib/rubocop/cop/style/min_max_comparison.rb +73 -0
  92. data/lib/rubocop/cop/style/missing_else.rb +13 -1
  93. data/lib/rubocop/cop/style/operator_method_call.rb +15 -1
  94. data/lib/rubocop/cop/style/redundant_constant_base.rb +13 -0
  95. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +39 -0
  96. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +2 -1
  97. data/lib/rubocop/cop/style/redundant_string_escape.rb +6 -3
  98. data/lib/rubocop/cop/style/require_order.rb +63 -9
  99. data/lib/rubocop/cop/style/select_by_regexp.rb +6 -2
  100. data/lib/rubocop/cop/style/semicolon.rb +2 -1
  101. data/lib/rubocop/cop/style/signal_exception.rb +8 -6
  102. data/lib/rubocop/cop/style/string_hash_keys.rb +4 -1
  103. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +4 -4
  104. data/lib/rubocop/cop/style/word_array.rb +41 -0
  105. data/lib/rubocop/cop/style/yoda_expression.rb +81 -0
  106. data/lib/rubocop/cop/style/zero_length_predicate.rb +31 -14
  107. data/lib/rubocop/cop/team.rb +29 -29
  108. data/lib/rubocop/cop/util.rb +31 -4
  109. data/lib/rubocop/cop/variable_force.rb +0 -3
  110. data/lib/rubocop/cops_documentation_generator.rb +33 -11
  111. data/lib/rubocop/directive_comment.rb +1 -1
  112. data/lib/rubocop/file_patterns.rb +43 -0
  113. data/lib/rubocop/formatter.rb +2 -0
  114. data/lib/rubocop/options.rb +1 -1
  115. data/lib/rubocop/path_util.rb +38 -22
  116. data/lib/rubocop/result_cache.rb +1 -1
  117. data/lib/rubocop/runner.rb +10 -3
  118. data/lib/rubocop/target_finder.rb +1 -1
  119. data/lib/rubocop/target_ruby.rb +0 -1
  120. data/lib/rubocop/version.rb +1 -1
  121. data/lib/rubocop.rb +7 -1
  122. metadata +16 -10
  123. data/lib/rubocop/optimized_patterns.rb +0 -38
@@ -30,6 +30,8 @@ module RuboCop
30
30
  MSG = 'Use CamelCase for classes and modules.'
31
31
 
32
32
  def on_class(node)
33
+ return unless node.loc.name.source.include?('_')
34
+
33
35
  allowed = /#{cop_config['AllowedNames'].join('|')}/
34
36
  name = node.loc.name.source.gsub(allowed, '')
35
37
  return unless /_/.match?(name)
@@ -207,7 +207,10 @@ module RuboCop
207
207
  end
208
208
 
209
209
  def scan_for_words(input)
210
- mask_input(input).enum_for(:scan, @flagged_terms_regex).map do
210
+ masked_input = mask_input(input)
211
+ return EMPTY_ARRAY unless masked_input.match?(@flagged_terms_regex)
212
+
213
+ masked_input.enum_for(:scan, @flagged_terms_regex).map do
211
214
  match = Regexp.last_match
212
215
  WordLocation.new(match.to_s, match.offset(0).first)
213
216
  end
@@ -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 = {})
@@ -28,6 +50,9 @@ module RuboCop
28
50
 
29
51
  @enrollment_queue = cops
30
52
  @options = options
53
+
54
+ @enabled_cache = {}.compare_by_identity
55
+ @disabled_cache = {}.compare_by_identity
31
56
  end
32
57
 
33
58
  def enlist(cop)
@@ -61,7 +86,7 @@ module RuboCop
61
86
 
62
87
  # @return [Boolean] Checks if given name is department
63
88
  def department?(name)
64
- departments.include? name.to_sym
89
+ departments.include?(name.to_sym)
65
90
  end
66
91
 
67
92
  def contains_cop_matching?(names)
@@ -150,11 +175,11 @@ module RuboCop
150
175
  end
151
176
 
152
177
  def enabled(config)
153
- select { |cop| enabled?(cop, config) }
178
+ @enabled_cache[config] ||= select { |cop| enabled?(cop, config) }
154
179
  end
155
180
 
156
181
  def disabled(config)
157
- reject { |cop| enabled?(cop, config) }
182
+ @disabled_cache[config] ||= reject { |cop| enabled?(cop, config) }
158
183
  end
159
184
 
160
185
  def enabled?(cop, config)
@@ -235,28 +260,6 @@ module RuboCop
235
260
  attr_reader :global
236
261
  end
237
262
 
238
- def self.all
239
- global.without_department(:Test).cops
240
- end
241
-
242
- def self.qualified_cop_name(name, origin)
243
- global.qualified_cop_name(name, origin)
244
- end
245
-
246
- # Changes momentarily the global registry
247
- # Intended for testing purposes
248
- def self.with_temporary_global(temp_global = global.dup)
249
- previous = @global
250
- @global = temp_global
251
- yield
252
- ensure
253
- @global = previous
254
- end
255
-
256
- def self.reset!
257
- @global = new
258
- end
259
-
260
263
  private
261
264
 
262
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
@@ -7,6 +7,11 @@ module RuboCop
7
7
  # depending on configuration.
8
8
  # It also flags uses of `alias :symbol` rather than `alias bareword`.
9
9
  #
10
+ # However, it will always enforce `method_alias` when used `alias`
11
+ # in an instance method definition and in a singleton method definition.
12
+ # If used in a block, always enforce `alias_method`
13
+ # unless it is an `instance_eval` block.
14
+ #
10
15
  # @example EnforcedStyle: prefer_alias (default)
11
16
  # # bad
12
17
  # alias_method :bar, :foo
@@ -22,6 +27,7 @@ module RuboCop
22
27
  #
23
28
  # # good
24
29
  # alias_method :bar, :foo
30
+ #
25
31
  class Alias < Base
26
32
  include ConfigurableEnforcedStyle
27
33
  extend AutoCorrector
@@ -71,7 +77,9 @@ module RuboCop
71
77
  end
72
78
 
73
79
  def alias_method_possible?(node)
74
- scope_type(node) != :instance_eval && node.children.none?(&:gvar_type?)
80
+ scope_type(node) != :instance_eval &&
81
+ node.children.none?(&:gvar_type?) &&
82
+ node&.parent&.type != :def
75
83
  end
76
84
 
77
85
  def add_offense_for_args(node, &block)
@@ -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# ")
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Enforces the use of `Array#push(item)` instead of `Array#concat([item])`
7
+ # to avoid redundant array literals.
8
+ #
9
+ # @safety
10
+ # This cop is unsafe, as it can produce false positives if the receiver
11
+ # is not an `Array` object.
12
+ #
13
+ # @example
14
+ #
15
+ # # bad
16
+ # list.concat([foo])
17
+ # list.concat([bar, baz])
18
+ # list.concat([qux, quux], [corge])
19
+ #
20
+ # # good
21
+ # list.push(foo)
22
+ # list.push(bar, baz)
23
+ # list.push(qux, quux, corge)
24
+ #
25
+ class ConcatArrayLiterals < Base
26
+ extend AutoCorrector
27
+
28
+ MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
29
+ MSG_FOR_PERCENT_LITERALS =
30
+ 'Use `push` with elements as arguments without array brackets instead of `%<current>s`.'
31
+ RESTRICT_ON_SEND = %i[concat].freeze
32
+
33
+ # rubocop:disable Metrics
34
+ def on_send(node)
35
+ return if node.arguments.empty?
36
+ return unless node.arguments.all?(&:array_type?)
37
+
38
+ offense = offense_range(node)
39
+ current = offense.source
40
+
41
+ if node.arguments.any?(&:percent_literal?)
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
48
+ else
49
+ prefer = preferred_method(node)
50
+ message = format(MSG, prefer: prefer, current: current)
51
+ end
52
+
53
+ add_offense(offense, message: message) do |corrector|
54
+ corrector.replace(offense, prefer)
55
+ end
56
+ end
57
+ # rubocop:enable Metrics
58
+
59
+ private
60
+
61
+ def offense_range(node)
62
+ node.loc.selector.join(node.source_range.end)
63
+ end
64
+
65
+ def preferred_method(node)
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(', ')
74
+
75
+ "push(#{new_arguments})"
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
83
+ end
84
+ end
85
+ end
86
+ end
@@ -86,6 +86,11 @@ module RuboCop
86
86
  (send nil? {:public_constant :private_constant} ({sym str} _))
87
87
  PATTERN
88
88
 
89
+ # @!method include_statement?(node)
90
+ def_node_matcher :include_statement?, <<~PATTERN
91
+ (send nil? {:include :extend :prepend} const)
92
+ PATTERN
93
+
89
94
  def on_class(node)
90
95
  return unless node.body
91
96
 
@@ -103,7 +108,7 @@ module RuboCop
103
108
  return if documentation_comment?(node)
104
109
  return if constant_allowed?(node)
105
110
  return if nodoc_self_or_outer_module?(node)
106
- return if macro_only?(body)
111
+ return if include_statement_only?(body)
107
112
 
108
113
  range = range_between(node.loc.expression.begin_pos, node.loc.name.end_pos)
109
114
  message = format(MSG, type: node.type, identifier: identifier(node))
@@ -115,9 +120,10 @@ module RuboCop
115
120
  (compact_namespace?(node) && nodoc_comment?(outer_module(node).first))
116
121
  end
117
122
 
118
- def macro_only?(body)
119
- (body.respond_to?(:macro?) && body.macro?) ||
120
- (body.respond_to?(:children) && body.children&.all? { |child| macro_only?(child) })
123
+ def include_statement_only?(body)
124
+ return true if include_statement?(body)
125
+
126
+ body.respond_to?(:children) && body.children.all? { |node| include_statement_only?(node) }
121
127
  end
122
128
 
123
129
  def namespace?(node)
@@ -176,7 +182,7 @@ module RuboCop
176
182
  end
177
183
 
178
184
  def qualify_const(node)
179
- return if node.nil? || node.cbase_type?
185
+ return if node.nil? || node.cbase_type? || node.self_type? || node.send_type?
180
186
 
181
187
  [qualify_const(node.namespace), node.short_name].compact
182
188
  end
@@ -187,35 +187,43 @@ module RuboCop
187
187
  if_branch = node.if_branch
188
188
  else_branch = node.else_branch
189
189
 
190
- if if_branch&.send_type? && if_branch.last_argument&.heredoc?
190
+ if if_branch&.send_type? && heredoc?(if_branch.last_argument)
191
191
  autocorrect_heredoc_argument(corrector, node, if_branch, else_branch, guard)
192
- elsif else_branch&.send_type? && else_branch.last_argument&.heredoc?
192
+ elsif else_branch&.send_type? && heredoc?(else_branch.last_argument)
193
193
  autocorrect_heredoc_argument(corrector, node, else_branch, if_branch, guard)
194
194
  else
195
195
  corrector.remove(node.loc.end)
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
203
203
 
204
+ def heredoc?(argument)
205
+ argument.respond_to?(:heredoc?) && argument.heredoc?
206
+ end
207
+
204
208
  def autocorrect_heredoc_argument(corrector, node, heredoc_branch, leave_branch, guard)
209
+ return unless node.else?
210
+
205
211
  remove_whole_lines(corrector, leave_branch.source_range)
206
212
  remove_whole_lines(corrector, node.loc.else)
207
213
  remove_whole_lines(corrector, node.loc.end)
208
- remove_whole_lines(corrector, branch_to_remove(node, guard).source_range)
214
+ remove_whole_lines(corrector, range_of_branch_to_remove(node, guard))
209
215
  corrector.insert_after(
210
216
  heredoc_branch.last_argument.loc.heredoc_end, "\n#{leave_branch.source}"
211
217
  )
212
218
  end
213
219
 
214
- def branch_to_remove(node, guard)
215
- case guard
216
- when :if then node.if_branch
217
- when :else then node.else_branch
218
- 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
219
227
  end
220
228
 
221
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
@@ -21,13 +21,12 @@ module RuboCop
21
21
  MSG_TERNARY = 'Do not use `if %<expr>s;` - use a ternary operator instead.'
22
22
 
23
23
  def on_normal_if_unless(node)
24
- return unless node.else_branch
25
24
  return if node.parent&.if_type?
26
25
 
27
26
  beginning = node.loc.begin
28
27
  return unless beginning&.is?(';')
29
28
 
30
- message = node.else_branch.if_type? ? MSG_IF_ELSE : MSG_TERNARY
29
+ message = node.else_branch&.if_type? ? MSG_IF_ELSE : MSG_TERNARY
31
30
 
32
31
  add_offense(node, message: format(message, expr: node.condition.source)) do |corrector|
33
32
  corrector.replace(node, autocorrect(node))
@@ -37,7 +36,7 @@ module RuboCop
37
36
  private
38
37
 
39
38
  def autocorrect(node)
40
- return correct_elsif(node) if node.else_branch.if_type?
39
+ return correct_elsif(node) if node.else_branch&.if_type?
41
40
 
42
41
  then_code = node.if_branch ? node.if_branch.source : 'nil'
43
42
  else_code = node.else_branch ? node.else_branch.source : 'nil'
@@ -51,6 +51,8 @@ module RuboCop
51
51
  NEGATED_EQUALITY_METHODS = %i[!= !~].freeze
52
52
  CAMEL_CASE = /[A-Z]+[a-z]+/.freeze
53
53
 
54
+ RESTRICT_ON_SEND = [:!].freeze
55
+
54
56
  def self.autocorrect_incompatible_with
55
57
  [Style::Not, Style::SymbolProc]
56
58
  end
@@ -55,7 +55,10 @@ module RuboCop
55
55
  private
56
56
 
57
57
  def check_token_set(index)
58
- predecessor, operator, successor = processed_source.tokens[index, 3]
58
+ tokens = processed_source.tokens
59
+ predecessor = tokens[index]
60
+ operator = tokens[index + 1]
61
+ successor = tokens[index + 2]
59
62
 
60
63
  return unless eligible_token_set?(predecessor, operator, successor)
61
64
 
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Looks for uses of `map.to_set` or `collect.to_set` that could be
7
+ # written with just `to_set`.
8
+ #
9
+ # @safety
10
+ # This cop is unsafe, as it can produce false positives if the receiver
11
+ # is not an `Enumerable`.
12
+ #
13
+ # @example
14
+ # # bad
15
+ # something.map { |i| i * 2 }.to_set
16
+ #
17
+ # # good
18
+ # something.to_set { |i| i * 2 }
19
+ #
20
+ # # bad
21
+ # [1, 2, 3].collect { |i| i.to_s }.to_set
22
+ #
23
+ # # good
24
+ # [1, 2, 3].to_set { |i| i.to_s }
25
+ #
26
+ class MapToSet < Base
27
+ extend AutoCorrector
28
+ include RangeHelp
29
+
30
+ MSG = 'Pass a block to `to_set` instead of calling `%<method>s.to_set`.'
31
+ RESTRICT_ON_SEND = %i[to_set].freeze
32
+
33
+ # @!method map_to_set?(node)
34
+ def_node_matcher :map_to_set?, <<~PATTERN
35
+ $(send (block $(send _ {:map :collect}) ...) :to_set)
36
+ PATTERN
37
+
38
+ def on_send(node)
39
+ return unless (to_set_node, map_node = map_to_set?(node))
40
+
41
+ message = format(MSG, method: map_node.loc.selector.source)
42
+ add_offense(map_node.loc.selector, message: message) do |corrector|
43
+ # If the `to_set` call already has a block, do not autocorrect.
44
+ next if to_set_node.block_node
45
+
46
+ autocorrect(corrector, to_set_node, map_node)
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def autocorrect(corrector, to_set, map)
53
+ removal_range = range_between(to_set.loc.dot.begin_pos, to_set.loc.selector.end_pos)
54
+
55
+ corrector.remove(range_with_surrounding_space(removal_range, side: :left))
56
+ corrector.replace(map.loc.selector, 'to_set')
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -102,20 +102,23 @@ module RuboCop
102
102
  end
103
103
 
104
104
  def call_in_literals?(node)
105
- node.parent &&
106
- (node.parent.pair_type? ||
107
- node.parent.array_type? ||
108
- node.parent.range_type? ||
109
- splat?(node.parent) ||
110
- ternary_if?(node.parent))
105
+ parent = node.parent&.block_type? ? node.parent.parent : node.parent
106
+ return unless parent
107
+
108
+ parent.pair_type? ||
109
+ parent.array_type? ||
110
+ parent.range_type? ||
111
+ splat?(parent) ||
112
+ ternary_if?(parent)
111
113
  end
112
114
 
113
115
  def call_in_logical_operators?(node)
114
116
  parent = node.parent&.block_type? ? node.parent.parent : node.parent
115
- parent &&
116
- (logical_operator?(parent) ||
117
+ return unless parent
118
+
119
+ logical_operator?(parent) ||
117
120
  (parent.send_type? &&
118
- parent.arguments.any? { |argument| logical_operator?(argument) }))
121
+ parent.arguments.any? { |argument| logical_operator?(argument) })
119
122
  end
120
123
 
121
124
  def call_in_optional_arguments?(node)
@@ -10,7 +10,9 @@ module RuboCop
10
10
  #
11
11
  # 1. Endless methods
12
12
  # 2. Argument lists containing a `forward-arg` (`...`)
13
- # 3. Argument lists containing an anonymous block forwarding (`&`)
13
+ # 3. Argument lists containing an anonymous rest arguments forwarding (`*`)
14
+ # 4. Argument lists containing an anonymous keyword rest arguments forwarding (`**`)
15
+ # 5. Argument lists containing an anonymous block forwarding (`&`)
14
16
  #
15
17
  # Removing the parens would be a syntax error here.
16
18
  #
@@ -130,9 +132,11 @@ module RuboCop
130
132
  # Regardless of style, parentheses are necessary for:
131
133
  # 1. Endless methods
132
134
  # 2. Argument lists containing a `forward-arg` (`...`)
133
- # 3. Argument lists containing an anonymous block forwarding (`&`)
135
+ # 3. Argument lists containing an anonymous rest arguments forwarding (`*`)
136
+ # 4. Argument lists containing an anonymous keyword rest arguments forwarding (`**`)
137
+ # 5. Argument lists containing an anonymous block forwarding (`&`)
134
138
  # Removing the parens would be a syntax error here.
135
- node.endless? || node.arguments.any?(&:forward_arg_type?) || anonymous_block_arg?(node)
139
+ node.endless? || anonymous_arguments?(node)
136
140
  end
137
141
 
138
142
  def require_parentheses?(args)
@@ -162,7 +166,10 @@ module RuboCop
162
166
  end
163
167
  end
164
168
 
165
- def anonymous_block_arg?(node)
169
+ def anonymous_arguments?(node)
170
+ return true if node.arguments.any? do |arg|
171
+ arg.forward_arg_type? || arg.restarg_type? || arg.kwrestarg_type?
172
+ end
166
173
  return false unless (last_argument = node.arguments.last)
167
174
 
168
175
  last_argument.blockarg_type? && last_argument.name.nil?