rubocop 1.18.2 → 1.19.1

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +23 -6
  4. data/lib/rubocop.rb +4 -0
  5. data/lib/rubocop/cli.rb +18 -0
  6. data/lib/rubocop/config_loader.rb +1 -1
  7. data/lib/rubocop/config_loader_resolver.rb +22 -7
  8. data/lib/rubocop/config_validator.rb +18 -5
  9. data/lib/rubocop/cop/bundler/ordered_gems.rb +1 -1
  10. data/lib/rubocop/cop/correctors/require_library_corrector.rb +23 -0
  11. data/lib/rubocop/cop/documentation.rb +1 -1
  12. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -1
  13. data/lib/rubocop/cop/internal_affairs.rb +2 -0
  14. data/lib/rubocop/cop/internal_affairs/inherit_deprecated_cop_class.rb +34 -0
  15. data/lib/rubocop/cop/internal_affairs/undefined_config.rb +71 -0
  16. data/lib/rubocop/cop/layout/class_structure.rb +5 -1
  17. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +9 -0
  18. data/lib/rubocop/cop/layout/end_alignment.rb +10 -2
  19. data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -1
  20. data/lib/rubocop/cop/layout/hash_alignment.rb +22 -18
  21. data/lib/rubocop/cop/layout/heredoc_indentation.rb +0 -7
  22. data/lib/rubocop/cop/layout/indentation_style.rb +2 -2
  23. data/lib/rubocop/cop/layout/leading_comment_space.rb +1 -1
  24. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +36 -22
  25. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +22 -9
  26. data/lib/rubocop/cop/layout/space_around_operators.rb +12 -1
  27. data/lib/rubocop/cop/layout/space_before_comment.rb +1 -1
  28. data/lib/rubocop/cop/layout/space_inside_parens.rb +5 -5
  29. data/lib/rubocop/cop/layout/trailing_whitespace.rb +24 -1
  30. data/lib/rubocop/cop/lint/ambiguous_range.rb +105 -0
  31. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +5 -2
  32. data/lib/rubocop/cop/lint/duplicate_branch.rb +2 -1
  33. data/lib/rubocop/cop/lint/duplicate_methods.rb +8 -5
  34. data/lib/rubocop/cop/lint/shadowed_argument.rb +1 -1
  35. data/lib/rubocop/cop/lint/useless_times.rb +1 -1
  36. data/lib/rubocop/cop/mixin/check_line_breakable.rb +2 -2
  37. data/lib/rubocop/cop/mixin/hash_transform_method.rb +6 -1
  38. data/lib/rubocop/cop/mixin/heredoc.rb +7 -0
  39. data/lib/rubocop/cop/mixin/percent_array.rb +13 -7
  40. data/lib/rubocop/cop/mixin/require_library.rb +59 -0
  41. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +1 -1
  42. data/lib/rubocop/cop/naming/inclusive_language.rb +18 -1
  43. data/lib/rubocop/cop/style/block_delimiters.rb +31 -0
  44. data/lib/rubocop/cop/style/commented_keyword.rb +2 -1
  45. data/lib/rubocop/cop/style/conditional_assignment.rb +19 -5
  46. data/lib/rubocop/cop/style/double_cop_disable_directive.rb +1 -7
  47. data/lib/rubocop/cop/style/double_negation.rb +12 -1
  48. data/lib/rubocop/cop/style/encoding.rb +26 -15
  49. data/lib/rubocop/cop/style/eval_with_location.rb +1 -1
  50. data/lib/rubocop/cop/style/explicit_block_argument.rb +32 -7
  51. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +8 -2
  52. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +11 -0
  53. data/lib/rubocop/cop/style/hash_syntax.rb +1 -1
  54. data/lib/rubocop/cop/style/hash_transform_keys.rb +0 -3
  55. data/lib/rubocop/cop/style/identical_conditional_branches.rb +30 -5
  56. data/lib/rubocop/cop/style/method_def_parentheses.rb +10 -1
  57. data/lib/rubocop/cop/style/missing_else.rb +7 -0
  58. data/lib/rubocop/cop/style/mutable_constant.rb +6 -8
  59. data/lib/rubocop/cop/style/redundant_begin.rb +25 -0
  60. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +83 -0
  61. data/lib/rubocop/cop/style/redundant_sort.rb +2 -2
  62. data/lib/rubocop/cop/style/semicolon.rb +32 -24
  63. data/lib/rubocop/cop/style/single_line_block_params.rb +3 -1
  64. data/lib/rubocop/cop/style/single_line_methods.rb +25 -15
  65. data/lib/rubocop/cop/style/sole_nested_conditional.rb +4 -0
  66. data/lib/rubocop/cop/style/special_global_vars.rb +21 -0
  67. data/lib/rubocop/cop/style/symbol_array.rb +3 -3
  68. data/lib/rubocop/cop/style/word_array.rb +23 -5
  69. data/lib/rubocop/cop/util.rb +7 -2
  70. data/lib/rubocop/formatter/git_hub_actions_formatter.rb +1 -1
  71. data/lib/rubocop/magic_comment.rb +44 -15
  72. data/lib/rubocop/options.rb +1 -1
  73. data/lib/rubocop/version.rb +1 -1
  74. metadata +15 -9
@@ -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)
@@ -84,6 +84,7 @@ module RuboCop
84
84
 
85
85
  def on_kwbegin(node)
86
86
  return if empty_begin?(node) ||
87
+ begin_block_has_multiline_statements?(node) ||
87
88
  contain_rescue_or_ensure?(node) ||
88
89
  valid_context_using_only_begin?(node)
89
90
 
@@ -102,6 +103,9 @@ module RuboCop
102
103
  corrector.remove(offense_range)
103
104
  end
104
105
 
106
+ if use_modifier_form_after_multiline_begin_block?(node)
107
+ correct_modifier_form_after_multiline_begin_block(corrector, node)
108
+ end
105
109
  corrector.remove(node.loc.end)
106
110
  end
107
111
  end
@@ -127,10 +131,31 @@ module RuboCop
127
131
  corrector.insert_before(node.parent, comments) unless comments.blank?
128
132
  end
129
133
 
134
+ def use_modifier_form_after_multiline_begin_block?(node)
135
+ return unless (parent = node.parent)
136
+
137
+ node.multiline? && parent.if_type? && parent.modifier_form?
138
+ end
139
+
140
+ def correct_modifier_form_after_multiline_begin_block(corrector, node)
141
+ condition_range = condition_range(node.parent)
142
+
143
+ corrector.insert_after(node.children.first, " #{condition_range.source}")
144
+ corrector.remove(range_by_whole_lines(condition_range, include_final_newline: true))
145
+ end
146
+
147
+ def condition_range(node)
148
+ range_between(node.loc.keyword.begin_pos, node.condition.source_range.end_pos)
149
+ end
150
+
130
151
  def empty_begin?(node)
131
152
  node.children.empty?
132
153
  end
133
154
 
155
+ def begin_block_has_multiline_statements?(node)
156
+ node.children.count >= 2
157
+ end
158
+
134
159
  def contain_rescue_or_ensure?(node)
135
160
  first_child = node.children.first
136
161
 
@@ -0,0 +1,83 @@
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
+ # It only detects local variable because it may replace state of instance variable,
9
+ # class variable, and global variable that have state across methods with `nil`.
10
+ #
11
+ # @example
12
+ #
13
+ # # bad
14
+ # foo = condition ? bar : foo
15
+ #
16
+ # # good
17
+ # foo = bar if condition
18
+ #
19
+ # # bad
20
+ # foo = condition ? foo : bar
21
+ #
22
+ # # good
23
+ # foo = bar unless condition
24
+ #
25
+ class RedundantSelfAssignmentBranch < Base
26
+ include RangeHelp
27
+ extend AutoCorrector
28
+
29
+ MSG = 'Remove the self-assignment branch.'
30
+
31
+ # @!method bad_method?(node)
32
+ def_node_matcher :bad_method?, <<~PATTERN
33
+ (send nil? :bad_method ...)
34
+ PATTERN
35
+
36
+ def on_lvasgn(node)
37
+ variable, expression = *node
38
+ return unless use_if_and_else_branch?(expression)
39
+
40
+ if_branch = expression.if_branch
41
+ else_branch = expression.else_branch
42
+ return if inconvertible_to_modifier?(if_branch, else_branch)
43
+
44
+ if self_assign?(variable, if_branch)
45
+ register_offense(expression, if_branch, else_branch, 'unless')
46
+ elsif self_assign?(variable, else_branch)
47
+ register_offense(expression, else_branch, if_branch, 'if')
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def use_if_and_else_branch?(expression)
54
+ return false unless expression&.if_type?
55
+
56
+ !expression.ternary? || !expression.else?
57
+ end
58
+
59
+ def inconvertible_to_modifier?(if_branch, else_branch)
60
+ multiple_statements?(if_branch) || multiple_statements?(else_branch) ||
61
+ else_branch.respond_to?(:elsif?) && else_branch.elsif?
62
+ end
63
+
64
+ def multiple_statements?(branch)
65
+ branch && branch.children.compact.count > 1
66
+ end
67
+
68
+ def self_assign?(variable, branch)
69
+ variable.to_s == branch&.source
70
+ end
71
+
72
+ def register_offense(if_node, offense_branch, opposite_branch, keyword)
73
+ add_offense(offense_branch) do |corrector|
74
+ assignment_value = opposite_branch ? opposite_branch.source : 'nil'
75
+ replacement = "#{assignment_value} #{keyword} #{if_node.condition.source}"
76
+
77
+ corrector.replace(if_node, replacement)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ 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
@@ -36,6 +36,7 @@ module RuboCop
36
36
  extend AutoCorrector
37
37
 
38
38
  MSG = 'Avoid single-line method definitions.'
39
+ NOT_SUPPORTED_ENDLESS_METHOD_BODY_TYPES = %i[return break next].freeze
39
40
 
40
41
  def on_def(node)
41
42
  return unless node.single_line?
@@ -62,29 +63,24 @@ module RuboCop
62
63
 
63
64
  def correct_to_endless?(body_node)
64
65
  return false if target_ruby_version < 3.0
65
-
66
- endless_method_config = config.for_cop('Style/EndlessMethod')
67
-
68
- return false unless endless_method_config['Enabled']
69
- return false if endless_method_config['EnforcedStyle'] == 'disallow'
66
+ return false if disallow_endless_method_style?
70
67
  return false unless body_node
71
- return false if body_node.parent.assignment_method?
68
+ return false if body_node.parent.assignment_method? ||
69
+ NOT_SUPPORTED_ENDLESS_METHOD_BODY_TYPES.include?(body_node.type)
72
70
 
73
71
  !(body_node.begin_type? || body_node.kwbegin_type?)
74
72
  end
75
73
 
76
74
  def correct_to_multiline(corrector, node)
77
- each_part(node.body) do |part|
78
- LineBreakCorrector.break_line_before(
79
- range: part, node: node, corrector: corrector,
80
- configured_width: configured_indentation_width
81
- )
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
82
81
  end
83
82
 
84
- LineBreakCorrector.break_line_before(
85
- range: node.loc.end, node: node, corrector: corrector,
86
- indent_steps: 0, configured_width: configured_indentation_width
87
- )
83
+ break_line_before(corrector, node, node.loc.end, indent_steps: 0)
88
84
 
89
85
  move_comment(node, corrector)
90
86
  end
@@ -98,6 +94,13 @@ module RuboCop
98
94
  corrector.replace(node, replacement)
99
95
  end
100
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
+
101
104
  def each_part(body)
102
105
  return unless body
103
106
 
@@ -129,6 +132,13 @@ module RuboCop
129
132
  def require_parentheses?(method_body)
130
133
  method_body.send_type? && !method_body.arguments.empty? && !method_body.comparison_method?
131
134
  end
135
+
136
+ def disallow_endless_method_style?
137
+ endless_method_config = config.for_cop('Style/EndlessMethod')
138
+ return false unless endless_method_config['Enabled']
139
+
140
+ endless_method_config['EnforcedStyle'] == 'disallow'
141
+ end
132
142
  end
133
143
  end
134
144
  end
@@ -38,6 +38,10 @@ module RuboCop
38
38
 
39
39
  MSG = 'Consider merging nested conditions into outer `%<conditional_type>s` conditions.'
40
40
 
41
+ def self.autocorrect_incompatible_with
42
+ [Style::NegatedIf, Style::NegatedUnless]
43
+ end
44
+
41
45
  def on_if(node)
42
46
  return if node.ternary? || node.else? || node.elsif?
43
47
 
@@ -5,9 +5,14 @@ module RuboCop
5
5
  module Style
6
6
  #
7
7
  # This cop looks for uses of Perl-style global variables.
8
+ # Correcting to global variables in the 'English' library
9
+ # will add a require statement to the top of the file if
10
+ # enabled by RequireEnglish config.
8
11
  #
9
12
  # @example EnforcedStyle: use_english_names (default)
10
13
  # # good
14
+ # require 'English' # or this could be in another file.
15
+ #
11
16
  # puts $LOAD_PATH
12
17
  # puts $LOADED_FEATURES
13
18
  # puts $PROGRAM_NAME
@@ -50,6 +55,8 @@ module RuboCop
50
55
  #
51
56
  class SpecialGlobalVars < Base
52
57
  include ConfigurableEnforcedStyle
58
+ include RangeHelp
59
+ include RequireLibrary
53
60
  extend AutoCorrector
54
61
 
55
62
  MSG_BOTH = 'Prefer `%<prefer>s` from the stdlib \'English\' ' \
@@ -90,6 +97,8 @@ module RuboCop
90
97
  # Anything *not* in this set is provided by the English library.
91
98
  NON_ENGLISH_VARS = Set.new(%i[$LOAD_PATH $LOADED_FEATURES $PROGRAM_NAME ARGV]).freeze
92
99
 
100
+ LIBRARY_NAME = 'English'
101
+
93
102
  def on_gvar(node)
94
103
  global_var, = *node
95
104
 
@@ -117,6 +126,8 @@ module RuboCop
117
126
  def autocorrect(corrector, node, global_var)
118
127
  node = node.parent while node.parent&.begin_type? && node.parent.children.one?
119
128
 
129
+ ensure_required(corrector, node, LIBRARY_NAME) if should_require_english?(global_var)
130
+
120
131
  corrector.replace(node, replacement(node, global_var))
121
132
  end
122
133
 
@@ -172,6 +183,16 @@ module RuboCop
172
183
 
173
184
  "{#{preferred_name}}"
174
185
  end
186
+
187
+ def add_require_english?
188
+ cop_config['RequireEnglish']
189
+ end
190
+
191
+ def should_require_english?(global_var)
192
+ style == :use_english_names &&
193
+ add_require_english? &&
194
+ !NON_ENGLISH_VARS.include?(preferred_names(global_var).first)
195
+ end
175
196
  end
176
197
  end
177
198
  end