rubocop 1.40.0 → 1.43.0

Sign up to get free protection for your applications and to get access to all the features.
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?