rubocop 1.41.1 → 1.45.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +2 -2
  4. data/config/default.yml +97 -31
  5. data/lib/rubocop/cli.rb +55 -9
  6. data/lib/rubocop/config.rb +7 -7
  7. data/lib/rubocop/config_loader.rb +12 -15
  8. data/lib/rubocop/config_loader_resolver.rb +8 -5
  9. data/lib/rubocop/cop/base.rb +89 -70
  10. data/lib/rubocop/cop/commissioner.rb +8 -2
  11. data/lib/rubocop/cop/cop.rb +51 -31
  12. data/lib/rubocop/cop/corrector.rb +30 -10
  13. data/lib/rubocop/cop/correctors/ordered_gem_corrector.rb +1 -6
  14. data/lib/rubocop/cop/gemspec/dependency_version.rb +16 -18
  15. data/lib/rubocop/cop/gemspec/development_dependencies.rb +107 -0
  16. data/lib/rubocop/cop/internal_affairs/redundant_let_rubocop_config_new.rb +11 -3
  17. data/lib/rubocop/cop/layout/array_alignment.rb +1 -1
  18. data/lib/rubocop/cop/layout/block_end_newline.rb +7 -1
  19. data/lib/rubocop/cop/layout/class_structure.rb +31 -23
  20. data/lib/rubocop/cop/layout/closing_parenthesis_indentation.rb +2 -6
  21. data/lib/rubocop/cop/layout/comment_indentation.rb +3 -1
  22. data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -1
  23. data/lib/rubocop/cop/layout/heredoc_indentation.rb +6 -9
  24. data/lib/rubocop/cop/layout/indentation_style.rb +4 -1
  25. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +6 -6
  26. data/lib/rubocop/cop/layout/multiline_block_layout.rb +1 -1
  27. data/lib/rubocop/cop/layout/space_around_keyword.rb +2 -2
  28. data/lib/rubocop/cop/layout/space_around_operators.rb +1 -1
  29. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +11 -13
  30. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +4 -4
  31. data/lib/rubocop/cop/layout/space_inside_string_interpolation.rb +5 -4
  32. data/lib/rubocop/cop/layout/trailing_whitespace.rb +5 -2
  33. data/lib/rubocop/cop/lint/ambiguous_operator.rb +4 -0
  34. data/lib/rubocop/cop/lint/debugger.rb +8 -27
  35. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +62 -112
  36. data/lib/rubocop/cop/lint/else_layout.rb +2 -6
  37. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +14 -7
  38. data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +15 -17
  39. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +1 -1
  40. data/lib/rubocop/cop/lint/mixed_regexp_capture_types.rb +1 -0
  41. data/lib/rubocop/cop/lint/nested_method_definition.rb +8 -5
  42. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +19 -0
  43. data/lib/rubocop/cop/lint/redundant_require_statement.rb +11 -1
  44. data/lib/rubocop/cop/lint/regexp_as_condition.rb +6 -0
  45. data/lib/rubocop/cop/lint/require_parentheses.rb +3 -1
  46. data/lib/rubocop/cop/lint/unused_method_argument.rb +2 -1
  47. data/lib/rubocop/cop/lint/useless_access_modifier.rb +7 -4
  48. data/lib/rubocop/cop/lint/useless_method_definition.rb +3 -3
  49. data/lib/rubocop/cop/lint/useless_rescue.rb +85 -0
  50. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +14 -4
  51. data/lib/rubocop/cop/lint/void.rb +19 -10
  52. data/lib/rubocop/cop/metrics/block_length.rb +1 -1
  53. data/lib/rubocop/cop/metrics/block_nesting.rb +1 -1
  54. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +1 -1
  55. data/lib/rubocop/cop/metrics/parameter_lists.rb +27 -0
  56. data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -1
  57. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +3 -6
  58. data/lib/rubocop/cop/mixin/alignment.rb +1 -1
  59. data/lib/rubocop/cop/mixin/allowed_methods.rb +3 -1
  60. data/lib/rubocop/cop/mixin/annotation_comment.rb +1 -1
  61. data/lib/rubocop/cop/mixin/comments_help.rb +5 -3
  62. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +57 -23
  63. data/lib/rubocop/cop/mixin/line_length_help.rb +3 -1
  64. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +1 -1
  65. data/lib/rubocop/cop/mixin/statement_modifier.rb +1 -0
  66. data/lib/rubocop/cop/mixin/surrounding_space.rb +3 -3
  67. data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
  68. data/lib/rubocop/cop/naming/block_forwarding.rb +4 -0
  69. data/lib/rubocop/cop/naming/class_and_module_camel_case.rb +1 -1
  70. data/lib/rubocop/cop/registry.rb +34 -29
  71. data/lib/rubocop/cop/security/compound_hash.rb +2 -1
  72. data/lib/rubocop/cop/style/access_modifier_declarations.rb +26 -11
  73. data/lib/rubocop/cop/style/arguments_forwarding.rb +1 -0
  74. data/lib/rubocop/cop/style/block_comments.rb +1 -1
  75. data/lib/rubocop/cop/style/block_delimiters.rb +8 -2
  76. data/lib/rubocop/cop/style/class_and_module_children.rb +3 -10
  77. data/lib/rubocop/cop/style/command_literal.rb +1 -1
  78. data/lib/rubocop/cop/style/comparable_clamp.rb +125 -0
  79. data/lib/rubocop/cop/style/concat_array_literals.rb +22 -2
  80. data/lib/rubocop/cop/style/conditional_assignment.rb +0 -6
  81. data/lib/rubocop/cop/style/documentation.rb +1 -1
  82. data/lib/rubocop/cop/style/documentation_method.rb +6 -0
  83. data/lib/rubocop/cop/style/guard_clause.rb +11 -7
  84. data/lib/rubocop/cop/style/hash_each_methods.rb +13 -1
  85. data/lib/rubocop/cop/style/hash_syntax.rb +11 -7
  86. data/lib/rubocop/cop/style/identical_conditional_branches.rb +15 -0
  87. data/lib/rubocop/cop/style/infinite_loop.rb +2 -5
  88. data/lib/rubocop/cop/style/invertible_unless_condition.rb +114 -0
  89. data/lib/rubocop/cop/style/map_to_set.rb +61 -0
  90. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +23 -14
  91. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +2 -0
  92. data/lib/rubocop/cop/style/method_def_parentheses.rb +11 -4
  93. data/lib/rubocop/cop/style/min_max_comparison.rb +83 -0
  94. data/lib/rubocop/cop/style/missing_else.rb +13 -1
  95. data/lib/rubocop/cop/style/multiline_if_modifier.rb +0 -4
  96. data/lib/rubocop/cop/style/multiline_memoization.rb +2 -2
  97. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +18 -3
  98. data/lib/rubocop/cop/style/negated_if_else_condition.rb +1 -5
  99. data/lib/rubocop/cop/style/numbered_parameters_limit.rb +11 -3
  100. data/lib/rubocop/cop/style/one_line_conditional.rb +3 -6
  101. data/lib/rubocop/cop/style/operator_method_call.rb +16 -2
  102. data/lib/rubocop/cop/style/parallel_assignment.rb +3 -1
  103. data/lib/rubocop/cop/style/redundant_condition.rb +16 -1
  104. data/lib/rubocop/cop/style/redundant_conditional.rb +0 -4
  105. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +16 -10
  106. data/lib/rubocop/cop/style/redundant_heredoc_delimiter_quotes.rb +58 -0
  107. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +2 -1
  108. data/lib/rubocop/cop/style/redundant_string_escape.rb +4 -2
  109. data/lib/rubocop/cop/style/require_order.rb +6 -11
  110. data/lib/rubocop/cop/style/select_by_regexp.rb +6 -2
  111. data/lib/rubocop/cop/style/self_assignment.rb +2 -2
  112. data/lib/rubocop/cop/style/semicolon.rb +24 -2
  113. data/lib/rubocop/cop/style/signal_exception.rb +8 -6
  114. data/lib/rubocop/cop/style/string_hash_keys.rb +4 -1
  115. data/lib/rubocop/cop/style/symbol_array.rb +1 -1
  116. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +4 -4
  117. data/lib/rubocop/cop/style/word_array.rb +42 -1
  118. data/lib/rubocop/cop/style/yoda_condition.rb +12 -5
  119. data/lib/rubocop/cop/style/yoda_expression.rb +90 -0
  120. data/lib/rubocop/cop/style/zero_length_predicate.rb +31 -14
  121. data/lib/rubocop/cop/team.rb +48 -43
  122. data/lib/rubocop/cop/variable_force/scope.rb +3 -3
  123. data/lib/rubocop/cop/variable_force/variable_table.rb +3 -1
  124. data/lib/rubocop/cop/variable_force.rb +1 -4
  125. data/lib/rubocop/formatter.rb +0 -1
  126. data/lib/rubocop/options.rb +22 -1
  127. data/lib/rubocop/path_util.rb +17 -7
  128. data/lib/rubocop/result_cache.rb +1 -1
  129. data/lib/rubocop/rspec/expect_offense.rb +6 -4
  130. data/lib/rubocop/runner.rb +50 -7
  131. data/lib/rubocop/server/cache.rb +10 -3
  132. data/lib/rubocop/server/cli.rb +37 -18
  133. data/lib/rubocop/server/client_command/exec.rb +1 -1
  134. data/lib/rubocop/server/client_command/start.rb +6 -1
  135. data/lib/rubocop/server/core.rb +23 -8
  136. data/lib/rubocop/target_ruby.rb +0 -1
  137. data/lib/rubocop/version.rb +1 -1
  138. data/lib/rubocop.rb +8 -0
  139. metadata +21 -33
@@ -299,8 +299,8 @@ module RuboCop
299
299
 
300
300
  def move_comment_before_block(corrector, comment, block_node, closing_brace)
301
301
  range = block_node.chained? ? end_of_chain(block_node.parent).source_range : closing_brace
302
- comment_range = range_between(range.end_pos, comment.loc.expression.end_pos)
303
- corrector.remove(range_with_surrounding_space(comment_range, side: :right))
302
+ corrector.remove(range_with_surrounding_space(comment.loc.expression, side: :right))
303
+ remove_trailing_whitespace(corrector, range, comment)
304
304
  corrector.insert_after(range, "\n")
305
305
 
306
306
  corrector.insert_before(block_node, "#{comment.text}\n")
@@ -313,6 +313,12 @@ module RuboCop
313
313
  end_of_chain(node.parent)
314
314
  end
315
315
 
316
+ def remove_trailing_whitespace(corrector, range, comment)
317
+ range_of_trailing = range.end.join(comment.loc.expression.begin)
318
+
319
+ corrector.remove(range_of_trailing) if range_of_trailing.source.match?(/\A\s+\z/)
320
+ end
321
+
316
322
  def with_block?(node)
317
323
  node.respond_to?(:block_node) && node.block_node
318
324
  end
@@ -31,6 +31,7 @@ module RuboCop
31
31
  #
32
32
  # The compact style is only forced for classes/modules with one child.
33
33
  class ClassAndModuleChildren < Base
34
+ include Alignment
34
35
  include ConfigurableEnforcedStyle
35
36
  include RangeHelp
36
37
  extend AutoCorrector
@@ -59,7 +60,7 @@ module RuboCop
59
60
  end
60
61
 
61
62
  def nest_definition(corrector, node)
62
- padding = ((' ' * indent_width) + leading_spaces(node)).to_s
63
+ padding = indentation(node) + leading_spaces(node)
63
64
  padding_for_trailing_end = padding.sub(' ' * node.loc.end.column, '')
64
65
 
65
66
  replace_namespace_keyword(corrector, node)
@@ -124,10 +125,6 @@ module RuboCop
124
125
  corrector.remove(range)
125
126
  end
126
127
 
127
- def configured_indentation_width
128
- config.for_badge(Layout::IndentationWidth.badge).fetch('Width', 2)
129
- end
130
-
131
128
  def unindent(corrector, node)
132
129
  return if node.body.children.last.nil?
133
130
 
@@ -141,10 +138,6 @@ module RuboCop
141
138
  node.source_range.source_line[/\A\s*/]
142
139
  end
143
140
 
144
- def indent_width
145
- @config.for_cop('Layout/IndentationWidth')['Width'] || 2
146
- end
147
-
148
141
  def check_style(node, body)
149
142
  return if node.identifier.children[0]&.cbase_type?
150
143
 
@@ -185,7 +178,7 @@ module RuboCop
185
178
  end
186
179
 
187
180
  def compact_node_name?(node)
188
- /::/.match?(node.identifier.source)
181
+ node.identifier.source.include?('::')
189
182
  end
190
183
  end
191
184
  end
@@ -148,7 +148,7 @@ module RuboCop
148
148
  end
149
149
 
150
150
  def contains_backtick?(node)
151
- /`/.match?(node_body(node))
151
+ node_body(node).include?('`')
152
152
  end
153
153
 
154
154
  def node_body(node)
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Enforces the use of `Comparable#clamp` instead of comparison by minimum and maximum.
7
+ #
8
+ # This cop supports autocorrection for `if/elsif/else` bad style only.
9
+ # Because `ArgumentError` occurs if the minimum and maximum of `clamp` arguments are reversed.
10
+ # When these are variables, it is not possible to determine which is the minimum and maximum:
11
+ #
12
+ # [source,ruby]
13
+ # ----
14
+ # [1, [2, 3].max].min # => 1
15
+ # 1.clamp(3, 1) # => min argument must be smaller than max argument (ArgumentError)
16
+ # ----
17
+ #
18
+ # @example
19
+ #
20
+ # # bad
21
+ # [[x, low].max, high].min
22
+ #
23
+ # # bad
24
+ # if x < low
25
+ # low
26
+ # elsif high < x
27
+ # high
28
+ # else
29
+ # x
30
+ # end
31
+ #
32
+ # # good
33
+ # x.clamp(low, high)
34
+ #
35
+ class ComparableClamp < Base
36
+ include Alignment
37
+ extend AutoCorrector
38
+ extend TargetRubyVersion
39
+
40
+ minimum_target_ruby_version 2.4
41
+
42
+ MSG = 'Use `%<prefer>s` instead of `if/elsif/else`.'
43
+ MSG_MIN_MAX = 'Use `Comparable#clamp` instead.'
44
+ RESTRICT_ON_SEND = %i[min max].freeze
45
+
46
+ # @!method if_elsif_else_condition?(node)
47
+ def_node_matcher :if_elsif_else_condition?, <<~PATTERN
48
+ {
49
+ (if (send _x :< _min) _min (if (send _max :< _x) _max _x))
50
+ (if (send _min :> _x) _min (if (send _max :< _x) _max _x))
51
+ (if (send _x :< _min) _min (if (send _x :> _max) _max _x))
52
+ (if (send _min :> _x) _min (if (send _x :> _max) _max _x))
53
+ (if (send _max :< _x) _max (if (send _x :< _min) _min _x))
54
+ (if (send _x :> _max) _max (if (send _x :< _min) _min _x))
55
+ (if (send _max :< _x) _max (if (send _min :> _x) _min _x))
56
+ (if (send _x :> _max) _max (if (send _min :> _x) _min _x))
57
+ }
58
+ PATTERN
59
+
60
+ # @!method array_min_max?(node)
61
+ def_node_matcher :array_min_max?, <<~PATTERN
62
+ {
63
+ (send
64
+ (array
65
+ (send (array _ _) :max) _) :min)
66
+ (send
67
+ (array
68
+ _ (send (array _ _) :max)) :min)
69
+ (send
70
+ (array
71
+ (send (array _ _) :min) _) :max)
72
+ (send
73
+ (array
74
+ _ (send (array _ _) :min)) :max)
75
+ }
76
+ PATTERN
77
+
78
+ def on_if(node)
79
+ return unless if_elsif_else_condition?(node)
80
+
81
+ if_body, elsif_body, else_body = *node.branches
82
+
83
+ else_body_source = else_body.source
84
+
85
+ if min_condition?(node.condition, else_body_source)
86
+ min = if_body.source
87
+ max = elsif_body.source
88
+ else
89
+ min = elsif_body.source
90
+ max = if_body.source
91
+ end
92
+
93
+ prefer = "#{else_body_source}.clamp(#{min}, #{max})"
94
+
95
+ add_offense(node, message: format(MSG, prefer: prefer)) do |corrector|
96
+ autocorrect(corrector, node, prefer)
97
+ end
98
+ end
99
+
100
+ def on_send(node)
101
+ return unless array_min_max?(node)
102
+
103
+ add_offense(node, message: MSG_MIN_MAX)
104
+ end
105
+
106
+ private
107
+
108
+ def autocorrect(corrector, node, prefer)
109
+ if node.elsif?
110
+ corrector.insert_before(node, "else\n")
111
+ corrector.replace(node, "#{indentation(node)}#{prefer}")
112
+ else
113
+ corrector.replace(node, prefer)
114
+ end
115
+ end
116
+
117
+ def min_condition?(if_condition, else_body)
118
+ lhs, op, rhs = *if_condition
119
+
120
+ (lhs.source == else_body && op == :<) || (rhs.source == else_body && op == :>)
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -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
@@ -218,11 +218,9 @@ module RuboCop
218
218
  VARIABLE_ASSIGNMENT_TYPES = %i[casgn cvasgn gvasgn ivasgn lvasgn].freeze
219
219
  ASSIGNMENT_TYPES = VARIABLE_ASSIGNMENT_TYPES + %i[and_asgn or_asgn op_asgn masgn].freeze
220
220
  LINE_LENGTH = 'Layout/LineLength'
221
- INDENTATION_WIDTH = 'Layout/IndentationWidth'
222
221
  ENABLED = 'Enabled'
223
222
  MAX = 'Max'
224
223
  SINGLE_LINE_CONDITIONS_ONLY = 'SingleLineConditionsOnly'
225
- WIDTH = 'Width'
226
224
 
227
225
  # The shovel operator `<<` does not have its own type. It is a `send`
228
226
  # type.
@@ -428,10 +426,6 @@ module RuboCop
428
426
  config.for_cop(LINE_LENGTH)[MAX]
429
427
  end
430
428
 
431
- def indentation_width
432
- config.for_cop(INDENTATION_WIDTH)[WIDTH] || 2
433
- end
434
-
435
429
  def single_line_conditions_only?
436
430
  cop_config[SINGLE_LINE_CONDITIONS_ONLY]
437
431
  end
@@ -145,7 +145,7 @@ module RuboCop
145
145
  end
146
146
 
147
147
  def compact_namespace?(node)
148
- /::/.match?(node.loc.name.source)
148
+ node.loc.name.source.include?('::')
149
149
  end
150
150
 
151
151
  # First checks if the :nodoc: comment is associated with the
@@ -7,6 +7,10 @@ module RuboCop
7
7
  # It can optionally be configured to also require documentation for
8
8
  # non-public methods.
9
9
  #
10
+ # NOTE: This cop allows `initialize` method because `initialize` is
11
+ # a special method called from `new`. In some programming languages
12
+ # they are called constructor to distinguish it from method.
13
+ #
10
14
  # @example
11
15
  #
12
16
  # # bad
@@ -103,6 +107,8 @@ module RuboCop
103
107
  PATTERN
104
108
 
105
109
  def on_def(node)
110
+ return if node.method?(:initialize)
111
+
106
112
  parent = node.parent
107
113
  module_function_node?(parent) ? check(parent) : check(node)
108
114
  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
@@ -21,6 +21,7 @@ module RuboCop
21
21
  # work
22
22
  # end
23
23
  class InfiniteLoop < Base
24
+ include Alignment
24
25
  extend AutoCorrector
25
26
 
26
27
  LEADING_SPACE = /\A(\s*)/.freeze
@@ -106,7 +107,7 @@ module RuboCop
106
107
  else
107
108
  indentation = body.source_range.source_line[LEADING_SPACE]
108
109
 
109
- ['loop do', body.source.gsub(/^/, configured_indent), 'end'].join("\n#{indentation}")
110
+ ['loop do', body.source.gsub(/^/, indentation(node)), 'end'].join("\n#{indentation}")
110
111
  end
111
112
  end
112
113
 
@@ -120,10 +121,6 @@ module RuboCop
120
121
 
121
122
  start_range.join(end_range)
122
123
  end
123
-
124
- def configured_indent
125
- ' ' * config.for_cop('Layout/IndentationWidth')['Width']
126
- end
127
124
  end
128
125
  end
129
126
  end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for usages of `unless` which can be replaced by `if` with inverted condition.
7
+ # Code without `unless` is easier to read, but that is subjective, so this cop
8
+ # is disabled by default.
9
+ #
10
+ # Methods that can be inverted should be defined in `InverseMethods`. Note that
11
+ # the relationship of inverse methods needs to be defined in both directions.
12
+ # For example,
13
+ # InverseMethods:
14
+ # :!=: :==
15
+ # :even?: :odd?
16
+ # :odd?: :even?
17
+ #
18
+ # will suggest both `even?` and `odd?` to be inverted, but only `!=` (and not `==`).
19
+ #
20
+ # @safety
21
+ # This cop is unsafe because it cannot be guaranteed that the method
22
+ # and its inverse method are both defined on receiver, and also are
23
+ # actually inverse of each other.
24
+ #
25
+ # @example
26
+ # # bad (simple condition)
27
+ # foo unless !bar
28
+ # foo unless x != y
29
+ # foo unless x >= 10
30
+ # foo unless x.even?
31
+ #
32
+ # # good
33
+ # foo if bar
34
+ # foo if x == y
35
+ # foo if x < 10
36
+ # foo if x.odd?
37
+ #
38
+ # # bad (complex condition)
39
+ # foo unless x != y || x.even?
40
+ #
41
+ # # good
42
+ # foo if x == y && x.odd?
43
+ #
44
+ # # good (if)
45
+ # foo if !condition
46
+ #
47
+ class InvertibleUnlessCondition < Base
48
+ extend AutoCorrector
49
+
50
+ MSG = 'Favor `if` with inverted condition over `unless`.'
51
+
52
+ def on_if(node)
53
+ return unless node.unless?
54
+
55
+ condition = node.condition
56
+ return unless invertible?(condition)
57
+
58
+ add_offense(node) do |corrector|
59
+ corrector.replace(node.loc.keyword, node.inverse_keyword)
60
+ autocorrect(corrector, condition)
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def invertible?(node)
67
+ case node.type
68
+ when :begin
69
+ invertible?(node.children.first)
70
+ when :send
71
+ return if inheritance_check?(node)
72
+
73
+ node.method?(:!) || inverse_methods.key?(node.method_name)
74
+ when :or, :and
75
+ invertible?(node.lhs) && invertible?(node.rhs)
76
+ else
77
+ false
78
+ end
79
+ end
80
+
81
+ def inheritance_check?(node)
82
+ argument = node.first_argument
83
+ node.method?(:<) &&
84
+ (argument.const_type? && argument.short_name.to_s.upcase != argument.short_name.to_s)
85
+ end
86
+
87
+ def autocorrect(corrector, node)
88
+ case node.type
89
+ when :begin
90
+ autocorrect(corrector, node.children.first)
91
+ when :send
92
+ autocorrect_send_node(corrector, node)
93
+ when :or, :and
94
+ corrector.replace(node.loc.operator, node.inverse_operator)
95
+ autocorrect(corrector, node.lhs)
96
+ autocorrect(corrector, node.rhs)
97
+ end
98
+ end
99
+
100
+ def autocorrect_send_node(corrector, node)
101
+ if node.method?(:!)
102
+ corrector.remove(node.loc.selector)
103
+ else
104
+ corrector.replace(node.loc.selector, inverse_methods[node.method_name])
105
+ end
106
+ end
107
+
108
+ def inverse_methods
109
+ @inverse_methods ||= cop_config['InverseMethods']
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -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