rubocop 1.18.3 → 1.19.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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +22 -5
  4. data/lib/rubocop.rb +4 -0
  5. data/lib/rubocop/cli.rb +18 -0
  6. data/lib/rubocop/config_loader_resolver.rb +21 -6
  7. data/lib/rubocop/config_validator.rb +18 -5
  8. data/lib/rubocop/cop/bundler/ordered_gems.rb +1 -1
  9. data/lib/rubocop/cop/correctors/require_library_corrector.rb +23 -0
  10. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -1
  11. data/lib/rubocop/cop/internal_affairs.rb +2 -0
  12. data/lib/rubocop/cop/internal_affairs/inherit_deprecated_cop_class.rb +34 -0
  13. data/lib/rubocop/cop/internal_affairs/undefined_config.rb +71 -0
  14. data/lib/rubocop/cop/layout/class_structure.rb +5 -1
  15. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +9 -0
  16. data/lib/rubocop/cop/layout/end_alignment.rb +10 -2
  17. data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -1
  18. data/lib/rubocop/cop/layout/hash_alignment.rb +22 -18
  19. data/lib/rubocop/cop/layout/heredoc_indentation.rb +0 -7
  20. data/lib/rubocop/cop/layout/indentation_style.rb +2 -2
  21. data/lib/rubocop/cop/layout/leading_comment_space.rb +1 -1
  22. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +33 -14
  23. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +21 -9
  24. data/lib/rubocop/cop/layout/space_around_operators.rb +8 -1
  25. data/lib/rubocop/cop/layout/space_before_comment.rb +1 -1
  26. data/lib/rubocop/cop/layout/space_inside_parens.rb +5 -5
  27. data/lib/rubocop/cop/layout/trailing_whitespace.rb +24 -1
  28. data/lib/rubocop/cop/lint/ambiguous_range.rb +105 -0
  29. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +5 -2
  30. data/lib/rubocop/cop/lint/duplicate_branch.rb +2 -1
  31. data/lib/rubocop/cop/lint/duplicate_methods.rb +8 -5
  32. data/lib/rubocop/cop/lint/shadowed_argument.rb +1 -1
  33. data/lib/rubocop/cop/mixin/check_line_breakable.rb +2 -2
  34. data/lib/rubocop/cop/mixin/hash_transform_method.rb +6 -1
  35. data/lib/rubocop/cop/mixin/heredoc.rb +7 -0
  36. data/lib/rubocop/cop/mixin/percent_array.rb +10 -7
  37. data/lib/rubocop/cop/mixin/require_library.rb +59 -0
  38. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +1 -1
  39. data/lib/rubocop/cop/naming/inclusive_language.rb +18 -1
  40. data/lib/rubocop/cop/style/block_delimiters.rb +16 -0
  41. data/lib/rubocop/cop/style/commented_keyword.rb +2 -1
  42. data/lib/rubocop/cop/style/conditional_assignment.rb +19 -5
  43. data/lib/rubocop/cop/style/double_cop_disable_directive.rb +1 -7
  44. data/lib/rubocop/cop/style/eval_with_location.rb +1 -1
  45. data/lib/rubocop/cop/style/explicit_block_argument.rb +32 -7
  46. data/lib/rubocop/cop/style/hash_transform_keys.rb +0 -3
  47. data/lib/rubocop/cop/style/identical_conditional_branches.rb +30 -5
  48. data/lib/rubocop/cop/style/method_def_parentheses.rb +10 -1
  49. data/lib/rubocop/cop/style/missing_else.rb +7 -0
  50. data/lib/rubocop/cop/style/mutable_constant.rb +6 -8
  51. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +88 -0
  52. data/lib/rubocop/cop/style/redundant_sort.rb +2 -2
  53. data/lib/rubocop/cop/style/semicolon.rb +32 -24
  54. data/lib/rubocop/cop/style/single_line_block_params.rb +3 -1
  55. data/lib/rubocop/cop/style/single_line_methods.rb +14 -9
  56. data/lib/rubocop/cop/style/special_global_vars.rb +21 -0
  57. data/lib/rubocop/cop/style/word_array.rb +20 -2
  58. data/lib/rubocop/cop/util.rb +7 -2
  59. data/lib/rubocop/formatter/git_hub_actions_formatter.rb +1 -1
  60. data/lib/rubocop/options.rb +1 -1
  61. data/lib/rubocop/version.rb +1 -1
  62. metadata +11 -5
@@ -36,13 +36,7 @@ module RuboCop
36
36
  next unless comment.text.scan(/# rubocop:(?:disable|todo)/).size > 1
37
37
 
38
38
  add_offense(comment) do |corrector|
39
- prefix = if comment.text.start_with?('# rubocop:disable')
40
- '# rubocop:disable'
41
- else
42
- '# rubocop:todo'
43
- end
44
-
45
- corrector.replace(comment, comment.text[/#{prefix} \S+/])
39
+ corrector.replace(comment, comment.text.gsub(%r{ # rubocop:(disable|todo)}, ','))
46
40
  end
47
41
  end
48
42
  end
@@ -43,7 +43,7 @@ module RuboCop
43
43
  # RUBY
44
44
  #
45
45
  # This cop works only when a string literal is given as a code string.
46
- # No offence is reported if a string variable is given as below:
46
+ # No offense is reported if a string variable is given as below:
47
47
  #
48
48
  # @example
49
49
  # # not checked
@@ -93,18 +93,43 @@ module RuboCop
93
93
 
94
94
  def add_block_argument(node, corrector)
95
95
  if node.arguments?
96
- last_arg = node.arguments.last
97
- arg_range = range_with_surrounding_comma(last_arg.source_range, :right)
98
- replacement = ' &block'
99
- replacement = ",#{replacement}" unless arg_range.source.end_with?(',')
100
- corrector.insert_after(arg_range, replacement) unless last_arg.blockarg_type?
101
- elsif node.call_type? || node.zsuper_type?
102
- corrector.insert_after(node, '(&block)')
96
+ insert_argument(node, corrector)
97
+ elsif empty_arguments?(node)
98
+ corrector.replace(node.arguments, '(&block)')
99
+ elsif call_like?(node)
100
+ correct_call_node(node, corrector)
103
101
  else
104
102
  corrector.insert_after(node.loc.name, '(&block)')
105
103
  end
106
104
  end
107
105
 
106
+ def empty_arguments?(node)
107
+ # Is there an arguments node with only parentheses?
108
+ node.arguments.is_a?(RuboCop::AST::Node) && node.arguments.loc.begin
109
+ end
110
+
111
+ def call_like?(node)
112
+ node.call_type? || node.zsuper_type? || node.super_type?
113
+ end
114
+
115
+ def insert_argument(node, corrector)
116
+ last_arg = node.arguments.last
117
+ arg_range = range_with_surrounding_comma(last_arg.source_range, :right)
118
+ replacement = ' &block'
119
+ replacement = ",#{replacement}" unless arg_range.source.end_with?(',')
120
+ corrector.insert_after(arg_range, replacement) unless last_arg.blockarg_type?
121
+ end
122
+
123
+ def correct_call_node(node, corrector)
124
+ corrector.insert_after(node, '(&block)')
125
+ return unless node.parenthesized?
126
+
127
+ args_begin = Util.args_begin(node)
128
+ args_end = Util.args_end(node)
129
+ range = range_between(args_begin.begin_pos, args_end.end_pos)
130
+ corrector.remove(range)
131
+ end
132
+
108
133
  def block_body_range(block_node, send_node)
109
134
  range_between(send_node.loc.expression.end_pos, block_node.loc.end.end_pos)
110
135
  end
@@ -27,11 +27,8 @@ module RuboCop
27
27
  # {a: 1, b: 2}.transform_keys { |k| k.to_s }
28
28
  class HashTransformKeys < Base
29
29
  include HashTransformMethod
30
- extend TargetRubyVersion
31
30
  extend AutoCorrector
32
31
 
33
- minimum_target_ruby_version 2.5
34
-
35
32
  # @!method on_bad_each_with_object(node)
36
33
  def_node_matcher :on_bad_each_with_object, <<~PATTERN
37
34
  (block
@@ -7,6 +7,22 @@ module RuboCop
7
7
  # each branch of a conditional expression. Such expressions should normally
8
8
  # be placed outside the conditional expression - before or after it.
9
9
  #
10
+ # This cop is marked unsafe auto-correction as the order of method invocations
11
+ # must be guaranteed in the following case:
12
+ #
13
+ # [source,ruby]
14
+ # ----
15
+ # if method_that_modifies_global_state # 1
16
+ # method_that_relies_on_global_state # 2
17
+ # foo # 3
18
+ # else
19
+ # method_that_relies_on_global_state # 2
20
+ # bar # 3
21
+ # end
22
+ # ----
23
+ #
24
+ # In such a case, auto-correction may change the invocation order.
25
+ #
10
26
  # NOTE: The cop is poorly named and some people might think that it actually
11
27
  # checks for duplicated conditional branches. The name will probably be changed
12
28
  # in a future major RuboCop release.
@@ -124,21 +140,30 @@ module RuboCop
124
140
  return if branches.any?(&:nil?)
125
141
 
126
142
  tails = branches.map { |branch| tail(branch) }
127
- check_expressions(node, tails, :after_condition) if duplicated_expressions?(tails)
143
+ check_expressions(node, tails, :after_condition) if duplicated_expressions?(node, tails)
128
144
 
129
145
  heads = branches.map { |branch| head(branch) }
130
- check_expressions(node, heads, :before_condition) if duplicated_expressions?(heads)
146
+ check_expressions(node, heads, :before_condition) if duplicated_expressions?(node, heads)
131
147
  end
132
148
 
133
- def duplicated_expressions?(expressions)
134
- expressions.size > 1 && expressions.uniq.one?
149
+ def duplicated_expressions?(node, expressions)
150
+ unique_expressions = expressions.uniq
151
+ return false unless expressions.size >= 1 && unique_expressions.one?
152
+
153
+ unique_expression = unique_expressions.first
154
+ return true unless unique_expression.assignment?
155
+
156
+ lhs = unique_expression.child_nodes.first
157
+ node.condition.child_nodes.none? { |n| n.source == lhs.source if n.variable? }
135
158
  end
136
159
 
137
- def check_expressions(node, expressions, insert_position)
160
+ def check_expressions(node, expressions, insert_position) # rubocop:disable Metrics/MethodLength
138
161
  inserted_expression = false
139
162
 
140
163
  expressions.each do |expression|
141
164
  add_offense(expression) do |corrector|
165
+ next if node.if_type? && node.ternary?
166
+
142
167
  range = range_by_whole_lines(expression.source_range, include_final_newline: true)
143
168
  corrector.remove(range)
144
169
  next if inserted_expression
@@ -96,7 +96,7 @@ module RuboCop
96
96
  MSG_MISSING = 'Use def with parentheses when there are parameters.'
97
97
 
98
98
  def on_def(node)
99
- return if node.endless?
99
+ return if forced_parentheses?(node)
100
100
 
101
101
  args = node.arguments
102
102
 
@@ -129,6 +129,15 @@ module RuboCop
129
129
  corrector.insert_after(arguments_range, ')')
130
130
  end
131
131
 
132
+ def forced_parentheses?(node)
133
+ # Regardless of style, parentheses are necessary for:
134
+ # 1. Endless methods
135
+ # 2. Argument lists containing a `forward-arg` (`...`)
136
+ # Removing the parens would be a syntax error here.
137
+
138
+ node.endless? || node.arguments.any?(&:forward_arg_type?)
139
+ end
140
+
132
141
  def require_parentheses?(args)
133
142
  style == :require_parentheses ||
134
143
  (style == :require_no_parentheses_except_multiline && args.multiline?)
@@ -5,6 +5,9 @@ module RuboCop
5
5
  module Style
6
6
  # Checks for `if` expressions that do not have an `else` branch.
7
7
  #
8
+ # NOTE: Pattern matching is allowed to have no `else` branch because unlike `if` and `case`,
9
+ # it raises `NoMatchingPatternError` if the pattern doesn't match and without having `else`.
10
+ #
8
11
  # Supported styles are: if, case, both.
9
12
  #
10
13
  # @example EnforcedStyle: if
@@ -114,6 +117,10 @@ module RuboCop
114
117
  check(node)
115
118
  end
116
119
 
120
+ def on_case_match(node)
121
+ # do nothing.
122
+ end
123
+
117
124
  private
118
125
 
119
126
  def check(node)
@@ -61,13 +61,12 @@ module RuboCop
61
61
 
62
62
  def on_casgn(node)
63
63
  _scope, _const_name, value = *node
64
- on_assignment(value)
65
- end
64
+ if value.nil? # This is only the case for `CONST += ...` or similarg66
65
+ parent = node.parent
66
+ return unless parent.or_asgn_type? # We only care about `CONST ||= ...`
66
67
 
67
- def on_or_asgn(node)
68
- lhs, value = *node
69
-
70
- return unless lhs&.casgn_type?
68
+ value = parent.children.last
69
+ end
71
70
 
72
71
  on_assignment(value)
73
72
  end
@@ -118,14 +117,13 @@ module RuboCop
118
117
  end
119
118
 
120
119
  def mutable_literal?(value)
121
- return false if value.nil?
122
120
  return false if frozen_regexp_or_range_literals?(value)
123
121
 
124
122
  value.mutable_literal?
125
123
  end
126
124
 
127
125
  def immutable_literal?(node)
128
- node.nil? || frozen_regexp_or_range_literals?(node) || node.immutable_literal?
126
+ frozen_regexp_or_range_literals?(node) || node.immutable_literal?
129
127
  end
130
128
 
131
129
  def frozen_string_literal?(node)
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for places where conditional branch makes redundant self-assignment.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # foo = condition ? bar : foo
12
+ #
13
+ # # good
14
+ # foo = bar if condition
15
+ #
16
+ # # bad
17
+ # foo = condition ? foo : bar
18
+ #
19
+ # # good
20
+ # foo = bar unless condition
21
+ #
22
+ class RedundantSelfAssignmentBranch < Base
23
+ include RangeHelp
24
+ extend AutoCorrector
25
+
26
+ MSG = 'Remove the self-assignment branch.'
27
+
28
+ # @!method bad_method?(node)
29
+ def_node_matcher :bad_method?, <<~PATTERN
30
+ (send nil? :bad_method ...)
31
+ PATTERN
32
+
33
+ def on_lvasgn(node)
34
+ variable, expression = *node
35
+ return unless expression&.if_type?
36
+ return unless expression.ternary? || expression.else?
37
+
38
+ if_branch = expression.if_branch
39
+ else_branch = expression.else_branch
40
+
41
+ if self_assign?(variable, if_branch)
42
+ register_offense(expression, if_branch, else_branch, 'unless')
43
+ elsif self_assign?(variable, else_branch)
44
+ register_offense(expression, else_branch, if_branch, 'if')
45
+ end
46
+ end
47
+
48
+ alias on_ivasgn on_lvasgn
49
+ alias on_cvasgn on_lvasgn
50
+ alias on_gvasgn on_lvasgn
51
+
52
+ private
53
+
54
+ def self_assign?(variable, branch)
55
+ variable.to_s == branch&.source
56
+ end
57
+
58
+ def register_offense(if_node, offense_branch, opposite_branch, keyword)
59
+ add_offense(offense_branch) do |corrector|
60
+ if if_node.ternary?
61
+ replacement = "#{opposite_branch.source} #{keyword} #{if_node.condition.source}"
62
+ corrector.replace(if_node, replacement)
63
+ else
64
+ if_node_loc = if_node.loc
65
+
66
+ range = range_by_whole_lines(offense_branch.source_range, include_final_newline: true)
67
+ corrector.remove(range)
68
+ range = range_by_whole_lines(if_node_loc.else, include_final_newline: true)
69
+ corrector.remove(range)
70
+
71
+ autocorrect_if_condition(corrector, if_node, if_node_loc, keyword)
72
+ end
73
+ end
74
+ end
75
+
76
+ def autocorrect_if_condition(corrector, if_node, if_node_loc, keyword)
77
+ else_branch = if_node.else_branch
78
+
79
+ if else_branch.respond_to?(:elsif?) && else_branch.elsif?
80
+ corrector.replace(if_node.condition, else_branch.condition.source)
81
+ else
82
+ corrector.replace(if_node_loc.keyword, keyword)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -60,8 +60,8 @@ module RuboCop
60
60
  # @!method redundant_sort?(node)
61
61
  def_node_matcher :redundant_sort?, <<~MATCHER
62
62
  {
63
- (send $(send _ $:sort ...) ${:last :first})
64
- (send $(send _ $:sort ...) ${:[] :at :slice} {(int 0) (int -1)})
63
+ (send $(send _ $:sort) ${:last :first})
64
+ (send $(send _ $:sort) ${:[] :at :slice} {(int 0) (int -1)})
65
65
 
66
66
  (send $(send _ $:sort_by _) ${:last :first})
67
67
  (send $(send _ $:sort_by _) ${:[] :at :slice} {(int 0) (int -1)})
@@ -32,38 +32,29 @@ module RuboCop
32
32
 
33
33
  MSG = 'Do not use semicolons to terminate expressions.'
34
34
 
35
+ def self.autocorrect_incompatible_with
36
+ [Style::SingleLineMethods]
37
+ end
38
+
35
39
  def on_new_investigation
36
40
  return if processed_source.blank?
37
41
 
38
- @processed_source = processed_source
39
-
40
42
  check_for_line_terminator_or_opener
41
43
  end
42
44
 
43
- def on_begin(node) # rubocop:todo Metrics/CyclomaticComplexity
45
+ def on_begin(node)
44
46
  return if cop_config['AllowAsExpressionSeparator']
45
47
 
46
48
  exprs = node.children
47
49
 
48
50
  return if exprs.size < 2
49
51
 
50
- # create a map matching lines to the number of expressions on them
51
- exprs_lines = exprs.map(&:first_line)
52
- lines = exprs_lines.group_by(&:itself)
53
-
54
- lines.each do |line, expr_on_line|
52
+ expressions_per_line(exprs).each do |line, expr_on_line|
55
53
  # Every line with more than one expression on it is a
56
54
  # potential offense
57
55
  next unless expr_on_line.size > 1
58
56
 
59
- # TODO: Find the correct position of the semicolon. We don't know
60
- # if the first semicolon on the line is a separator of
61
- # expressions. It's just a guess.
62
- column = @processed_source[line - 1].index(';')
63
-
64
- next unless column
65
-
66
- convention_on(line, column, false)
57
+ find_semicolon_positions(line) { |pos| register_semicolon(line, pos, true) }
67
58
  end
68
59
  end
69
60
 
@@ -71,9 +62,9 @@ module RuboCop
71
62
 
72
63
  def check_for_line_terminator_or_opener
73
64
  # Make the obvious check first
74
- return unless @processed_source.raw_source.include?(';')
65
+ return unless processed_source.raw_source.include?(';')
75
66
 
76
- each_semicolon { |line, column| convention_on(line, column, true) }
67
+ each_semicolon { |line, column| register_semicolon(line, column, false) }
77
68
  end
78
69
 
79
70
  def each_semicolon
@@ -84,15 +75,32 @@ module RuboCop
84
75
  end
85
76
 
86
77
  def tokens_for_lines
87
- @processed_source.tokens.group_by(&:line)
78
+ processed_source.tokens.group_by(&:line)
88
79
  end
89
80
 
90
- def convention_on(line, column, autocorrect)
91
- range = source_range(@processed_source.buffer, line, column)
92
- # Don't attempt to autocorrect if semicolon is separating statements
93
- # on the same line
81
+ def register_semicolon(line, column, after_expression)
82
+ range = source_range(processed_source.buffer, line, column)
83
+
94
84
  add_offense(range) do |corrector|
95
- corrector.remove(range) if autocorrect
85
+ if after_expression
86
+ corrector.replace(range, "\n")
87
+ else
88
+ corrector.remove(range)
89
+ end
90
+ end
91
+ end
92
+
93
+ def expressions_per_line(exprs)
94
+ # create a map matching lines to the number of expressions on them
95
+ exprs_lines = exprs.map(&:first_line)
96
+ exprs_lines.group_by(&:itself)
97
+ end
98
+
99
+ def find_semicolon_positions(line)
100
+ # Scan for all the semicolons on the line
101
+ semicolons = processed_source[line - 1].enum_for(:scan, ';')
102
+ semicolons.each do
103
+ yield Regexp.last_match.begin(0)
96
104
  end
97
105
  end
98
106
  end
@@ -109,7 +109,9 @@ module RuboCop
109
109
  # we remove any leading underscores before comparing.
110
110
  actual_args_no_underscores = actual_args.map { |arg| arg.to_s.sub(/^_+/, '') }
111
111
 
112
- actual_args_no_underscores == target_args(method_name)
112
+ # Allow the arguments if the names match but not all are given
113
+ expected_args = target_args(method_name).first(actual_args_no_underscores.size)
114
+ actual_args_no_underscores == expected_args
113
115
  end
114
116
  end
115
117
  end
@@ -72,17 +72,15 @@ module RuboCop
72
72
  end
73
73
 
74
74
  def correct_to_multiline(corrector, node)
75
- each_part(node.body) do |part|
76
- LineBreakCorrector.break_line_before(
77
- range: part, node: node, corrector: corrector,
78
- configured_width: configured_indentation_width
79
- )
75
+ if (body = node.body) && body.begin_type? && body.parenthesized_call?
76
+ break_line_before(corrector, node, body)
77
+ else
78
+ each_part(body) do |part|
79
+ break_line_before(corrector, node, part)
80
+ end
80
81
  end
81
82
 
82
- LineBreakCorrector.break_line_before(
83
- range: node.loc.end, node: node, corrector: corrector,
84
- indent_steps: 0, configured_width: configured_indentation_width
85
- )
83
+ break_line_before(corrector, node, node.loc.end, indent_steps: 0)
86
84
 
87
85
  move_comment(node, corrector)
88
86
  end
@@ -96,6 +94,13 @@ module RuboCop
96
94
  corrector.replace(node, replacement)
97
95
  end
98
96
 
97
+ def break_line_before(corrector, node, range, indent_steps: 1)
98
+ LineBreakCorrector.break_line_before(
99
+ range: range, node: node, corrector: corrector,
100
+ configured_width: configured_indentation_width, indent_steps: indent_steps
101
+ )
102
+ end
103
+
99
104
  def each_part(body)
100
105
  return unless body
101
106