rubocop 0.87.1 → 0.88.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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/bin/rubocop-profile +31 -0
  4. data/config/default.yml +57 -6
  5. data/lib/rubocop.rb +6 -0
  6. data/lib/rubocop/cli.rb +2 -2
  7. data/lib/rubocop/cli/command/auto_genenerate_config.rb +2 -2
  8. data/lib/rubocop/config_loader.rb +20 -7
  9. data/lib/rubocop/config_store.rb +4 -0
  10. data/lib/rubocop/cop/autocorrect_logic.rb +1 -1
  11. data/lib/rubocop/cop/badge.rb +1 -1
  12. data/lib/rubocop/cop/base.rb +12 -4
  13. data/lib/rubocop/cop/cop.rb +1 -1
  14. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +26 -0
  15. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +6 -1
  16. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -0
  17. data/lib/rubocop/cop/layout/end_alignment.rb +3 -2
  18. data/lib/rubocop/cop/layout/multiline_block_layout.rb +16 -5
  19. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +3 -2
  20. data/lib/rubocop/cop/layout/space_around_method_call_operator.rb +27 -68
  21. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +3 -2
  22. data/lib/rubocop/cop/lint/disjunctive_assignment_in_constructor.rb +8 -2
  23. data/lib/rubocop/cop/lint/duplicate_elsif_condition.rb +39 -0
  24. data/lib/rubocop/cop/lint/duplicate_methods.rb +2 -2
  25. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +3 -2
  26. data/lib/rubocop/cop/lint/literal_as_condition.rb +11 -1
  27. data/lib/rubocop/cop/lint/nested_method_definition.rb +13 -19
  28. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +67 -0
  29. data/lib/rubocop/cop/mixin/statement_modifier.rb +3 -3
  30. data/lib/rubocop/cop/style/accessor_grouping.rb +8 -1
  31. data/lib/rubocop/cop/style/array_coercion.rb +63 -0
  32. data/lib/rubocop/cop/style/auto_resource_cleanup.rb +3 -2
  33. data/lib/rubocop/cop/style/bisected_attr_accessor.rb +5 -4
  34. data/lib/rubocop/cop/style/case_like_if.rb +217 -0
  35. data/lib/rubocop/cop/style/commented_keyword.rb +5 -2
  36. data/lib/rubocop/cop/style/conditional_assignment.rb +1 -1
  37. data/lib/rubocop/cop/style/exponential_notation.rb +6 -8
  38. data/lib/rubocop/cop/style/float_division.rb +7 -10
  39. data/lib/rubocop/cop/style/format_string_token.rb +5 -5
  40. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +62 -0
  41. data/lib/rubocop/cop/style/hash_like_case.rb +76 -0
  42. data/lib/rubocop/cop/style/if_unless_modifier.rb +11 -11
  43. data/lib/rubocop/cop/style/missing_else.rb +1 -11
  44. data/lib/rubocop/cop/style/numeric_predicate.rb +3 -4
  45. data/lib/rubocop/cop/style/parallel_assignment.rb +3 -3
  46. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +1 -1
  47. data/lib/rubocop/cop/style/redundant_file_extension_in_require.rb +50 -0
  48. data/lib/rubocop/cop/style/redundant_sort.rb +3 -2
  49. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +3 -2
  50. data/lib/rubocop/cop/style/trailing_method_end_statement.rb +9 -32
  51. data/lib/rubocop/cop/variable_force/variable.rb +5 -3
  52. data/lib/rubocop/file_finder.rb +12 -12
  53. data/lib/rubocop/path_util.rb +2 -17
  54. data/lib/rubocop/result_cache.rb +12 -8
  55. data/lib/rubocop/rspec/expect_offense.rb +31 -5
  56. data/lib/rubocop/rspec/shared_contexts.rb +12 -9
  57. data/lib/rubocop/runner.rb +5 -6
  58. data/lib/rubocop/target_finder.rb +2 -2
  59. data/lib/rubocop/version.rb +1 -1
  60. metadata +9 -2
@@ -17,9 +17,9 @@ module RuboCop
17
17
  end
18
18
 
19
19
  def non_eligible_node?(node)
20
- node.nonempty_line_count > 3 ||
21
- !node.modifier_form? &&
22
- processed_source.commented?(node.loc.end)
20
+ node.modifier_form? ||
21
+ node.nonempty_line_count > 3 ||
22
+ processed_source.commented?(node.loc.end)
23
23
  end
24
24
 
25
25
  def non_eligible_body?(body)
@@ -58,6 +58,10 @@ module RuboCop
58
58
 
59
59
  private
60
60
 
61
+ def previous_line_comment?(node)
62
+ comment_line?(processed_source[node.first_line - 2])
63
+ end
64
+
61
65
  def class_send_elements(class_node)
62
66
  class_def = class_node.body
63
67
 
@@ -75,6 +79,8 @@ module RuboCop
75
79
  end
76
80
 
77
81
  def check(send_node)
82
+ return if previous_line_comment?(send_node)
83
+
78
84
  if grouped_style? && sibling_accessors(send_node).size > 1
79
85
  add_offense(send_node)
80
86
  elsif separated_style? && send_node.arguments.size > 1
@@ -94,7 +100,8 @@ module RuboCop
94
100
  send_node.parent.each_child_node(:send).select do |sibling|
95
101
  accessor?(sibling) &&
96
102
  sibling.method?(send_node.method_name) &&
97
- node_visibility(sibling) == node_visibility(send_node)
103
+ node_visibility(sibling) == node_visibility(send_node) &&
104
+ !previous_line_comment?(sibling)
98
105
  end
99
106
  end
100
107
 
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop enforces the use of `Array()` instead of explicit `Array` check or `[*var]`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # paths = [paths] unless paths.is_a?(Array)
11
+ # paths.each { |path| do_something(path) }
12
+ #
13
+ # # bad (always creates a new Array instance)
14
+ # [*paths].each { |path| do_something(path) }
15
+ #
16
+ # # good (and a bit more readable)
17
+ # Array(paths).each { |path| do_something(path) }
18
+ #
19
+ class ArrayCoercion < Base
20
+ extend AutoCorrector
21
+
22
+ SPLAT_MSG = 'Use `Array(%<arg>s)` instead of `[*%<arg>s]`.'
23
+ CHECK_MSG = 'Use `Array(%<arg>s)` instead of explicit `Array` check.'
24
+
25
+ def_node_matcher :array_splat?, <<~PATTERN
26
+ (array (splat $_))
27
+ PATTERN
28
+
29
+ def_node_matcher :unless_array?, <<~PATTERN
30
+ (if
31
+ (send
32
+ (lvar $_) :is_a?
33
+ (const nil? :Array)) nil?
34
+ (lvasgn $_
35
+ (array
36
+ (lvar $_))))
37
+ PATTERN
38
+
39
+ def on_array(node)
40
+ return unless node.square_brackets?
41
+
42
+ array_splat?(node) do |arg_node|
43
+ message = format(SPLAT_MSG, arg: arg_node.source)
44
+ add_offense(node, message: message) do |corrector|
45
+ corrector.replace(node, "Array(#{arg_node.source})")
46
+ end
47
+ end
48
+ end
49
+
50
+ def on_if(node)
51
+ unless_array?(node) do |var_a, var_b, var_c|
52
+ if var_a == var_b && var_c == var_b
53
+ message = format(CHECK_MSG, arg: var_a)
54
+ add_offense(node, message: message) do |corrector|
55
+ corrector.replace(node, "#{var_a} = Array(#{var_a})")
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -25,10 +25,11 @@ module RuboCop
25
25
 
26
26
  def on_send(node)
27
27
  TARGET_METHODS.each do |target_class, target_method|
28
- target_receiver = s(:const, nil, target_class)
28
+ next if node.method_name != target_method
29
29
 
30
+ target_receiver = s(:const, nil, target_class)
30
31
  next if node.receiver != target_receiver
31
- next if node.method_name != target_method
32
+
32
33
  next if cleanup?(node)
33
34
 
34
35
  add_offense(node,
@@ -28,6 +28,7 @@ module RuboCop
28
28
  def on_class(class_node)
29
29
  VISIBILITY_SCOPES.each do |visibility|
30
30
  reader_names, writer_names = accessor_names(class_node, visibility)
31
+ next unless reader_names && writer_names
31
32
 
32
33
  accessor_macroses(class_node, visibility).each do |macro|
33
34
  check(macro, reader_names, writer_names)
@@ -48,17 +49,17 @@ module RuboCop
48
49
  private
49
50
 
50
51
  def accessor_names(class_node, visibility)
51
- reader_names = Set.new
52
- writer_names = Set.new
52
+ reader_names = nil
53
+ writer_names = nil
53
54
 
54
55
  accessor_macroses(class_node, visibility).each do |macro|
55
56
  names = macro.arguments.map(&:source)
56
57
 
57
58
  names.each do |name|
58
59
  if attr_reader?(macro)
59
- reader_names.add(name)
60
+ (reader_names ||= Set.new).add(name)
60
61
  else
61
- writer_names.add(name)
62
+ (writer_names ||= Set.new).add(name)
62
63
  end
63
64
  end
64
65
  end
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop identifies places where `if-elsif` constructions
7
+ # can be replaced with `case-when`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # if status == :active
12
+ # perform_action
13
+ # elsif status == :inactive || status == :hibernating
14
+ # check_timeout
15
+ # else
16
+ # final_action
17
+ # end
18
+ #
19
+ # # good
20
+ # case status
21
+ # when :active
22
+ # perform_action
23
+ # when :inactive, :hibernating
24
+ # check_timeout
25
+ # else
26
+ # final_action
27
+ # end
28
+ #
29
+ class CaseLikeIf < Cop
30
+ include RangeHelp
31
+
32
+ MSG = 'Convert `if-elsif` to `case-when`.'
33
+
34
+ def on_if(node)
35
+ return unless should_check?(node)
36
+
37
+ target = find_target(node.condition)
38
+ return unless target
39
+
40
+ conditions = []
41
+ convertible = true
42
+
43
+ branch_conditions(node).each do |branch_condition|
44
+ conditions << []
45
+ convertible = collect_conditions(branch_condition, target, conditions.last)
46
+ break unless convertible
47
+ end
48
+
49
+ add_offense(node) if convertible
50
+ end
51
+
52
+ def autocorrect(node)
53
+ target = find_target(node.condition)
54
+
55
+ lambda do |corrector|
56
+ corrector.insert_before(node, "case #{target.source}\n#{indent(node)}")
57
+
58
+ branch_conditions(node).each do |branch_condition|
59
+ conditions = []
60
+ collect_conditions(branch_condition, target, conditions)
61
+
62
+ range = correction_range(branch_condition)
63
+ branch_replacement = "when #{conditions.map(&:source).join(', ')}"
64
+ corrector.replace(range, branch_replacement)
65
+ end
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def should_check?(node)
72
+ !node.unless? && !node.elsif? && !node.modifier_form? && !node.ternary? &&
73
+ node.elsif_conditional?
74
+ end
75
+
76
+ # rubocop:disable Metrics/MethodLength
77
+ def find_target(node)
78
+ case node.type
79
+ when :begin
80
+ find_target(node.children.first)
81
+ when :or
82
+ find_target(node.lhs)
83
+ when :match_with_lvasgn
84
+ lhs, rhs = *node
85
+ if lhs.regexp_type?
86
+ rhs
87
+ elsif rhs.regexp_type?
88
+ lhs
89
+ end
90
+ when :send
91
+ find_target_in_send_node(node)
92
+ end
93
+ end
94
+ # rubocop:enable Metrics/MethodLength
95
+
96
+ def find_target_in_send_node(node)
97
+ case node.method_name
98
+ when :is_a?
99
+ node.receiver
100
+ when :==, :eql?, :equal?
101
+ find_target_in_equality_node(node)
102
+ when :===
103
+ node.arguments.first
104
+ when :include?, :cover?
105
+ receiver = deparenthesize(node.receiver)
106
+ node.arguments.first if receiver.range_type?
107
+ when :match, :match?
108
+ find_target_in_match_node(node)
109
+ end
110
+ end
111
+
112
+ def find_target_in_equality_node(node)
113
+ argument = node.arguments.first
114
+ receiver = node.receiver
115
+
116
+ if argument.literal? || const_reference?(argument)
117
+ receiver
118
+ elsif receiver.literal? || const_reference?(receiver)
119
+ argument
120
+ end
121
+ end
122
+
123
+ def find_target_in_match_node(node)
124
+ argument = node.arguments.first
125
+ receiver = node.receiver
126
+
127
+ if receiver.regexp_type?
128
+ argument
129
+ elsif argument.regexp_type?
130
+ receiver
131
+ end
132
+ end
133
+
134
+ def collect_conditions(node, target, conditions)
135
+ condition =
136
+ case node.type
137
+ when :begin
138
+ return collect_conditions(node.children.first, target, conditions)
139
+ when :or
140
+ return collect_conditions(node.lhs, target, conditions) &&
141
+ collect_conditions(node.rhs, target, conditions)
142
+ when :match_with_lvasgn
143
+ lhs, rhs = *node
144
+ condition_from_binary_op(lhs, rhs, target)
145
+ when :send
146
+ condition_from_send_node(node, target)
147
+ end
148
+
149
+ conditions << condition if condition
150
+ end
151
+
152
+ # rubocop:disable Metrics/AbcSize
153
+ # rubocop:disable Metrics/CyclomaticComplexity
154
+ def condition_from_send_node(node, target)
155
+ case node.method_name
156
+ when :is_a?
157
+ node.arguments.first if node.receiver == target
158
+ when :==, :eql?, :equal?, :=~, :match, :match?
159
+ lhs, _method, rhs = *node
160
+ condition_from_binary_op(lhs, rhs, target)
161
+ when :===
162
+ lhs, _method, rhs = *node
163
+ lhs if rhs == target
164
+ when :include?, :cover?
165
+ receiver = deparenthesize(node.receiver)
166
+ receiver if receiver.range_type? && node.arguments.first == target
167
+ end
168
+ end
169
+ # rubocop:enable Metrics/CyclomaticComplexity
170
+ # rubocop:enable Metrics/AbcSize
171
+
172
+ def condition_from_binary_op(lhs, rhs, target)
173
+ lhs = deparenthesize(lhs)
174
+ rhs = deparenthesize(rhs)
175
+
176
+ if lhs == target
177
+ rhs
178
+ elsif rhs == target
179
+ lhs
180
+ end
181
+ end
182
+
183
+ def branch_conditions(node)
184
+ conditions = []
185
+ while node&.if_type?
186
+ conditions << node.condition
187
+ node = node.else_branch
188
+ end
189
+ conditions
190
+ end
191
+
192
+ def const_reference?(node)
193
+ return false unless node.const_type?
194
+
195
+ name = node.children[1].to_s
196
+
197
+ # We can no be sure if, e.g. `C`, represents a constant or a class reference
198
+ name.length > 1 &&
199
+ name == name.upcase
200
+ end
201
+
202
+ def deparenthesize(node)
203
+ node = node.children.last while node.begin_type?
204
+ node
205
+ end
206
+
207
+ def correction_range(node)
208
+ range_between(node.parent.loc.keyword.begin_pos, node.loc.expression.end_pos)
209
+ end
210
+
211
+ def indent(node)
212
+ ' ' * node.loc.column
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end
@@ -46,17 +46,20 @@ module RuboCop
46
46
  private
47
47
 
48
48
  KEYWORDS = %w[begin class def end module].freeze
49
+ KEYWORD_REGEXES = KEYWORDS.map { |w| /^\s*#{w}\s/ }.freeze
50
+
49
51
  ALLOWED_COMMENTS = %w[
50
52
  :nodoc:
51
53
  :yields:
52
54
  rubocop:disable
53
55
  rubocop:todo
54
56
  ].freeze
57
+ ALLOWED_COMMENT_REGEXES = ALLOWED_COMMENTS.map { |c| /#\s*#{c}/ }.freeze
55
58
 
56
59
  def offensive?(comment)
57
60
  line = line(comment)
58
- KEYWORDS.any? { |word| /^\s*#{word}\s/.match?(line) } &&
59
- ALLOWED_COMMENTS.none? { |c| /#\s*#{c}/.match?(line) }
61
+ KEYWORD_REGEXES.any? { |r| r.match?(line) } &&
62
+ ALLOWED_COMMENT_REGEXES.none? { |r| r.match?(line) }
60
63
  end
61
64
 
62
65
  def message(comment)
@@ -30,7 +30,7 @@ module RuboCop
30
30
  end
31
31
 
32
32
  def tail(branch)
33
- branch.begin_type? ? [*branch].last : branch
33
+ branch.begin_type? ? Array(branch).last : branch
34
34
  end
35
35
 
36
36
  # rubocop:disable Metrics/AbcSize
@@ -60,6 +60,11 @@ module RuboCop
60
60
  #
61
61
  class ExponentialNotation < Cop
62
62
  include ConfigurableEnforcedStyle
63
+ MESSAGES = {
64
+ scientific: 'Use a mantissa in [1, 10[.',
65
+ engineering: 'Use an exponent divisible by 3 and a mantissa in [0.1, 1000[.',
66
+ integral: 'Use an integer as mantissa, without trailing zero.'
67
+ }.freeze
63
68
 
64
69
  def on_float(node)
65
70
  add_offense(node) if offense?(node)
@@ -104,14 +109,7 @@ module RuboCop
104
109
  end
105
110
 
106
111
  def message(_node)
107
- case style
108
- when :scientific
109
- 'Use a mantissa in [1, 10[.'
110
- when :engineering
111
- 'Use an exponent divisible by 3 and a mantissa in [0.1, 1000[.'
112
- when :integral
113
- 'Use an integer as mantissa, without trailing zero.'
114
- end
112
+ MESSAGES[style]
115
113
  end
116
114
  end
117
115
  end
@@ -41,6 +41,12 @@ module RuboCop
41
41
  # a.fdiv(b)
42
42
  class FloatDivision < Cop
43
43
  include ConfigurableEnforcedStyle
44
+ MESSAGES = {
45
+ left_coerce: 'Prefer using `.to_f` on the left side.',
46
+ right_coerce: 'Prefer using `.to_f` on the right side.',
47
+ single_coerce: 'Prefer using `.to_f` on one side only.',
48
+ fdiv: 'Prefer using `fdiv` for float divisions.'
49
+ }.freeze
44
50
 
45
51
  def_node_matcher :right_coerce?, <<~PATTERN
46
52
  (send _ :/ (send _ :to_f))
@@ -77,16 +83,7 @@ module RuboCop
77
83
  end
78
84
 
79
85
  def message(_node)
80
- case style
81
- when :left_coerce
82
- 'Prefer using `.to_f` on the left side.'
83
- when :right_coerce
84
- 'Prefer using `.to_f` on the right side.'
85
- when :single_coerce
86
- 'Prefer using `.to_f` on one side only.'
87
- when :fdiv
88
- 'Prefer using `fdiv` for float divisions.'
89
- end
86
+ MESSAGES[style]
90
87
  end
91
88
  end
92
89
  end