rubocop 0.87.1 → 0.88.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -75,11 +75,11 @@ module RuboCop
75
75
 
76
76
  # rubocop:disable Style/FormatStringToken
77
77
  def message_text(style)
78
- case style
79
- when :annotated then 'annotated tokens (like `%<foo>s`)'
80
- when :template then 'template tokens (like `%{foo}`)'
81
- when :unannotated then 'unannotated tokens (like `%s`)'
82
- end
78
+ {
79
+ annotated: 'annotated tokens (like `%<foo>s`)',
80
+ template: 'template tokens (like `%{foo}`)',
81
+ unannotated: 'unannotated tokens (like `%s`)'
82
+ }[style]
83
83
  end
84
84
  # rubocop:enable Style/FormatStringToken
85
85
 
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for presence or absence of braces around hash literal as a last
7
+ # array item depending on configuration.
8
+ #
9
+ # @example EnforcedStyle: braces (default)
10
+ # # bad
11
+ # [1, 2, one: 1, two: 2]
12
+ #
13
+ # # good
14
+ # [1, 2, { one: 1, two: 2 }]
15
+ #
16
+ # @example EnforcedStyle: no_braces
17
+ # # bad
18
+ # [1, 2, { one: 1, two: 2 }]
19
+ #
20
+ # # good
21
+ # [1, 2, one: 1, two: 2]
22
+ #
23
+ class HashAsLastArrayItem < Base
24
+ include ConfigurableEnforcedStyle
25
+ extend AutoCorrector
26
+
27
+ def on_hash(node)
28
+ return unless node.parent&.array_type?
29
+
30
+ if braces_style?
31
+ check_braces(node)
32
+ else
33
+ check_no_braces(node)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def check_braces(node)
40
+ return if node.braces?
41
+
42
+ add_offense(node, message: 'Wrap hash in `{` and `}`.') do |corrector|
43
+ corrector.wrap(node, '{', '}')
44
+ end
45
+ end
46
+
47
+ def check_no_braces(node)
48
+ return unless node.braces?
49
+
50
+ add_offense(node, message: 'Omit the braces around the hash.') do |corrector|
51
+ corrector.remove(node.loc.begin)
52
+ corrector.remove(node.loc.end)
53
+ end
54
+ end
55
+
56
+ def braces_style?
57
+ style == :braces
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for places where `case-when` represents a simple 1:1
7
+ # mapping and can be replaced with a hash lookup.
8
+ #
9
+ # @example MinBranchesCount: 3 (default)
10
+ # # bad
11
+ # case country
12
+ # when 'europe'
13
+ # 'http://eu.example.com'
14
+ # when 'america'
15
+ # 'http://us.example.com'
16
+ # when 'australia'
17
+ # 'http://au.example.com'
18
+ # end
19
+ #
20
+ # # good
21
+ # SITES = {
22
+ # 'europe' => 'http://eu.example.com',
23
+ # 'america' => 'http://us.example.com',
24
+ # 'australia' => 'http://au.example.com'
25
+ # }
26
+ # SITES[country]
27
+ #
28
+ # @example MinBranchesCount: 4
29
+ # # good
30
+ # case country
31
+ # when 'europe'
32
+ # 'http://eu.example.com'
33
+ # when 'america'
34
+ # 'http://us.example.com'
35
+ # when 'australia'
36
+ # 'http://au.example.com'
37
+ # end
38
+ #
39
+ class HashLikeCase < Base
40
+ MSG = 'Consider replacing `case-when` with a hash lookup.'
41
+
42
+ def_node_matcher :hash_like_case?, <<~PATTERN
43
+ (case
44
+ _
45
+ (when
46
+ ${str_type? sym_type?}
47
+ $[!nil? recursive_basic_literal?])+ nil?)
48
+ PATTERN
49
+
50
+ def on_case(node)
51
+ return if node.when_branches.size < min_branches_count
52
+
53
+ hash_like_case?(node) do |condition_nodes, body_nodes|
54
+ if nodes_of_same_type?(condition_nodes) &&
55
+ nodes_of_same_type?(body_nodes)
56
+ add_offense(node)
57
+ end
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def nodes_of_same_type?(nodes)
64
+ nodes.all? { |node| node.type == nodes.first.type }
65
+ end
66
+
67
+ def min_branches_count
68
+ length = cop_config['MinBranchesCount'] || 3
69
+ return length if length.is_a?(Integer) && length.positive?
70
+
71
+ raise 'MinBranchesCount needs to be a positive integer!'
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -41,11 +41,8 @@ module RuboCop
41
41
  MSG_USE_NORMAL =
42
42
  'Modifier form of `%<keyword>s` makes the line too long.'
43
43
 
44
- ASSIGNMENT_TYPES = %i[lvasgn casgn cvasgn
45
- gvasgn ivasgn masgn].freeze
46
-
47
44
  def on_if(node)
48
- msg = if eligible_node?(node)
45
+ msg = if single_line_as_modifier?(node)
49
46
  MSG_USE_MODIFIER unless named_capture_in_condition?(node)
50
47
  elsif too_long_due_to_modifier?(node)
51
48
  MSG_USE_NORMAL
@@ -125,13 +122,15 @@ module RuboCop
125
122
  node.condition.match_with_lvasgn_type?
126
123
  end
127
124
 
128
- def eligible_node?(node)
129
- !non_eligible_if?(node) && !node.chained? &&
130
- !node.nested_conditional? && single_line_as_modifier?(node)
125
+ def non_eligible_node?(node)
126
+ non_simple_if_unless?(node) ||
127
+ node.chained? ||
128
+ node.nested_conditional? ||
129
+ super
131
130
  end
132
131
 
133
- def non_eligible_if?(node)
134
- node.ternary? || node.modifier_form? || node.elsif? || node.else?
132
+ def non_simple_if_unless?(node)
133
+ node.ternary? || node.elsif? || node.else?
135
134
  end
136
135
 
137
136
  def another_statement_on_same_line?(node)
@@ -153,8 +152,9 @@ module RuboCop
153
152
  # Parenthesize corrected expression if changing to modifier-if form
154
153
  # would change the meaning of the parent expression
155
154
  # (due to the low operator precedence of modifier-if)
156
- return false if node.parent.nil?
157
- return true if ASSIGNMENT_TYPES.include?(node.parent.type)
155
+ parent = node.parent
156
+ return false if parent.nil?
157
+ return true if parent.assignment? || parent.operator_keyword?
158
158
 
159
159
  node.parent.send_type? && !node.parent.parenthesized?
160
160
  end
@@ -119,17 +119,7 @@ module RuboCop
119
119
  private
120
120
 
121
121
  def check(node)
122
- return if node.else?
123
-
124
- if empty_else_cop_enabled?
125
- if empty_else_style == :empty
126
- add_offense(node)
127
- elsif empty_else_style == :nil
128
- add_offense(node)
129
- end
130
- end
131
-
132
- add_offense(node)
122
+ add_offense(node) unless node.else?
133
123
  end
134
124
 
135
125
  def message(node)
@@ -54,15 +54,14 @@ module RuboCop
54
54
  }.freeze
55
55
 
56
56
  def on_send(node)
57
+ numeric, replacement = check(node)
58
+ return unless numeric
59
+
57
60
  return if ignored_method?(node.method_name) ||
58
61
  node.each_ancestor(:send, :block).any? do |ancestor|
59
62
  ignored_method?(ancestor.method_name)
60
63
  end
61
64
 
62
- numeric, replacement = check(node)
63
-
64
- return unless numeric
65
-
66
65
  add_offense(node,
67
66
  message: format(MSG,
68
67
  prefer: replacement,
@@ -30,7 +30,7 @@ module RuboCop
30
30
  def on_masgn(node)
31
31
  lhs, rhs = *node
32
32
  lhs_elements = *lhs
33
- rhs_elements = [*rhs].compact # edge case for one constant
33
+ rhs_elements = Array(rhs).compact # edge case for one constant
34
34
 
35
35
  return if allowed_lhs?(lhs) || allowed_rhs?(rhs) ||
36
36
  allowed_masign?(lhs_elements, rhs_elements)
@@ -42,7 +42,7 @@ module RuboCop
42
42
  lambda do |corrector|
43
43
  left, right = *node
44
44
  left_elements = *left
45
- right_elements = [*right].compact
45
+ right_elements = Array(right).compact
46
46
  order = find_valid_order(left_elements, right_elements)
47
47
  correction = assignment_corrector(node, order)
48
48
 
@@ -69,7 +69,7 @@ module RuboCop
69
69
 
70
70
  def allowed_rhs?(node)
71
71
  # Edge case for one constant
72
- elements = [*node].compact
72
+ elements = Array(node).compact
73
73
 
74
74
  # Account for edge case of `Constant::CONSTANT`
75
75
  !node.array_type? ||
@@ -102,7 +102,7 @@ module RuboCop
102
102
  delimiters_regexp = Regexp.union(delimiters)
103
103
  node
104
104
  .children.map { |n| string_source(n) }.compact
105
- .any? { |s| delimiters_regexp.match?(s) }
105
+ .any? { |s| delimiters_regexp.match?(s.scrub) }
106
106
  end
107
107
 
108
108
  def string_source(node)
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for the presence of superfluous `.rb` extension in
7
+ # the filename provided to `require` and `require_relative`.
8
+ #
9
+ # Note: If the extension is omitted, Ruby tries adding '.rb', '.so',
10
+ # and so on to the name until found. If the file named cannot be found,
11
+ # a `LoadError` will be raised.
12
+ # There is an edge case where `foo.so` file is loaded instead of a `LoadError`
13
+ # if `foo.so` file exists when `require 'foo.rb'` will be changed to `require 'foo'`,
14
+ # but that seems harmless.
15
+ #
16
+ # @example
17
+ # # bad
18
+ # require 'foo.rb'
19
+ # require_relative '../foo.rb'
20
+ #
21
+ # # good
22
+ # require 'foo'
23
+ # require 'foo.so'
24
+ # require_relative '../foo'
25
+ # require_relative '../foo.so'
26
+ #
27
+ class RedundantFileExtensionInRequire < Cop
28
+ MSG = 'Redundant `.rb` file extension detected.'
29
+
30
+ def_node_matcher :require_call?, <<~PATTERN
31
+ (send nil? {:require :require_relative} $str_type?)
32
+ PATTERN
33
+
34
+ def on_send(node)
35
+ require_call?(node) do |name_node|
36
+ add_offense(name_node) if name_node.value.end_with?('.rb')
37
+ end
38
+ end
39
+
40
+ def autocorrect(node)
41
+ correction = node.value.sub(/\.rb\z/, '')
42
+
43
+ lambda do |corrector|
44
+ corrector.replace(node, "'#{correction}'")
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -135,9 +135,10 @@ module RuboCop
135
135
  end
136
136
 
137
137
  def suffix(sorter)
138
- if sorter == :sort
138
+ case sorter
139
+ when :sort
139
140
  ''
140
- elsif sorter == :sort_by
141
+ when :sort_by
141
142
  '_by'
142
143
  end
143
144
  end
@@ -34,9 +34,10 @@ module RuboCop
34
34
  end
35
35
 
36
36
  def autocorrect(node)
37
- if style == :require_parentheses
37
+ case style
38
+ when :require_parentheses
38
39
  missing_parentheses_corrector(node)
39
- elsif style == :require_no_parentheses
40
+ when :require_no_parentheses
40
41
  unwanted_parentheses_corrector(node)
41
42
  end
42
43
  end
@@ -33,8 +33,8 @@ module RuboCop
33
33
  # end
34
34
  # end
35
35
  #
36
- class TrailingMethodEndStatement < Cop
37
- include Alignment
36
+ class TrailingMethodEndStatement < Base
37
+ extend AutoCorrector
38
38
 
39
39
  MSG = 'Place the end statement of a multi-line method on ' \
40
40
  'its own line.'
@@ -42,13 +42,11 @@ module RuboCop
42
42
  def on_def(node)
43
43
  return unless trailing_end?(node)
44
44
 
45
- add_offense(node, location: end_token(node).pos)
46
- end
47
-
48
- def autocorrect(node)
49
- lambda do |corrector|
50
- break_line_before_end(node, corrector)
51
- remove_semicolon(node, corrector)
45
+ add_offense(node.loc.end) do |corrector|
46
+ corrector.insert_before(
47
+ node.loc.end,
48
+ "\n" + ' ' * node.loc.keyword.column
49
+ )
52
50
  end
53
51
  end
54
52
 
@@ -60,30 +58,9 @@ module RuboCop
60
58
  body_and_end_on_same_line?(node)
61
59
  end
62
60
 
63
- def end_token(node)
64
- tokens(node).reverse.find(&:end?)
65
- end
66
-
67
61
  def body_and_end_on_same_line?(node)
68
- end_token(node).line == token_before_end(node).line
69
- end
70
-
71
- def token_before_end(node)
72
- i = tokens(node).index(end_token(node))
73
- tokens(node)[i - 1]
74
- end
75
-
76
- def break_line_before_end(node, corrector)
77
- corrector.insert_before(
78
- end_token(node).pos,
79
- "\n" + ' ' * configured_indentation_width
80
- )
81
- end
82
-
83
- def remove_semicolon(node, corrector)
84
- return unless token_before_end(node).semicolon?
85
-
86
- corrector.remove(token_before_end(node).pos)
62
+ last_child = node.children.last
63
+ last_child.loc.last_line == node.loc.end.last_line
87
64
  end
88
65
  end
89
66
  end
@@ -42,10 +42,10 @@ module RuboCop
42
42
  def reference!(node)
43
43
  reference = Reference.new(node, @scope)
44
44
  @references << reference
45
- consumed_branches = Set.new
45
+ consumed_branches = nil
46
46
 
47
47
  @assignments.reverse_each do |assignment|
48
- next if consumed_branches.include?(assignment.branch)
48
+ next if consumed_branches&.include?(assignment.branch)
49
49
 
50
50
  assignment.reference!(node) unless assignment.run_exclusively_with?(reference)
51
51
 
@@ -58,7 +58,9 @@ module RuboCop
58
58
 
59
59
  break if !assignment.branch || assignment.branch == reference.branch
60
60
 
61
- consumed_branches << assignment.branch unless assignment.branch.may_run_incompletely?
61
+ unless assignment.branch.may_run_incompletely?
62
+ (consumed_branches ||= Set.new) << assignment.branch
63
+ end
62
64
  end
63
65
  end
64
66
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
@@ -9,33 +9,33 @@ module RuboCop
9
9
  @root_level = level
10
10
  end
11
11
 
12
- def self.root_level?(path)
13
- @root_level == path.to_s
12
+ def self.root_level?(path, stop_dir)
13
+ (@root_level || stop_dir) == path.to_s
14
14
  end
15
15
 
16
- def find_file_upwards(filename, start_dir)
17
- traverse_files_upwards(filename, start_dir) do |file|
16
+ def find_file_upwards(filename, start_dir, stop_dir = nil)
17
+ traverse_files_upwards(filename, start_dir, stop_dir) do |file|
18
18
  # minimize iteration for performance
19
19
  return file if file
20
20
  end
21
21
  end
22
22
 
23
- def find_files_upwards(filename, start_dir)
24
- files = []
25
- traverse_files_upwards(filename, start_dir) do |file|
26
- files << file
23
+ def find_last_file_upwards(filename, start_dir, stop_dir = nil)
24
+ last_file = nil
25
+ traverse_files_upwards(filename, start_dir, stop_dir) do |file|
26
+ last_file = file
27
27
  end
28
- files
28
+ last_file
29
29
  end
30
30
 
31
31
  private
32
32
 
33
- def traverse_files_upwards(filename, start_dir)
33
+ def traverse_files_upwards(filename, start_dir, stop_dir)
34
34
  Pathname.new(start_dir).expand_path.ascend do |dir|
35
- break if FileFinder.root_level?(dir)
36
-
37
35
  file = dir + filename
38
36
  yield(file.to_s) if file.exist?
37
+
38
+ break if FileFinder.root_level?(dir, stop_dir)
39
39
  end
40
40
  end
41
41
  end