rubocop 1.18.4 → 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 (50) 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/cop/bundler/ordered_gems.rb +1 -1
  8. data/lib/rubocop/cop/correctors/require_library_corrector.rb +23 -0
  9. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -1
  10. data/lib/rubocop/cop/internal_affairs.rb +2 -0
  11. data/lib/rubocop/cop/internal_affairs/inherit_deprecated_cop_class.rb +34 -0
  12. data/lib/rubocop/cop/internal_affairs/undefined_config.rb +71 -0
  13. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +9 -0
  14. data/lib/rubocop/cop/layout/end_alignment.rb +2 -1
  15. data/lib/rubocop/cop/layout/hash_alignment.rb +7 -3
  16. data/lib/rubocop/cop/layout/heredoc_indentation.rb +0 -7
  17. data/lib/rubocop/cop/layout/leading_comment_space.rb +1 -1
  18. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +33 -14
  19. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +21 -9
  20. data/lib/rubocop/cop/layout/space_around_operators.rb +8 -1
  21. data/lib/rubocop/cop/layout/space_before_comment.rb +1 -1
  22. data/lib/rubocop/cop/layout/space_inside_parens.rb +5 -5
  23. data/lib/rubocop/cop/layout/trailing_whitespace.rb +24 -1
  24. data/lib/rubocop/cop/lint/ambiguous_range.rb +105 -0
  25. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +5 -2
  26. data/lib/rubocop/cop/lint/duplicate_methods.rb +8 -5
  27. data/lib/rubocop/cop/lint/shadowed_argument.rb +1 -1
  28. data/lib/rubocop/cop/mixin/heredoc.rb +7 -0
  29. data/lib/rubocop/cop/mixin/percent_array.rb +10 -7
  30. data/lib/rubocop/cop/mixin/require_library.rb +59 -0
  31. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +1 -1
  32. data/lib/rubocop/cop/naming/inclusive_language.rb +18 -1
  33. data/lib/rubocop/cop/style/block_delimiters.rb +16 -0
  34. data/lib/rubocop/cop/style/commented_keyword.rb +2 -1
  35. data/lib/rubocop/cop/style/conditional_assignment.rb +19 -5
  36. data/lib/rubocop/cop/style/explicit_block_argument.rb +32 -7
  37. data/lib/rubocop/cop/style/hash_transform_keys.rb +0 -3
  38. data/lib/rubocop/cop/style/identical_conditional_branches.rb +30 -5
  39. data/lib/rubocop/cop/style/method_def_parentheses.rb +10 -1
  40. data/lib/rubocop/cop/style/missing_else.rb +7 -0
  41. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +88 -0
  42. data/lib/rubocop/cop/style/redundant_sort.rb +2 -2
  43. data/lib/rubocop/cop/style/semicolon.rb +32 -24
  44. data/lib/rubocop/cop/style/single_line_block_params.rb +3 -1
  45. data/lib/rubocop/cop/style/special_global_vars.rb +21 -0
  46. data/lib/rubocop/cop/style/word_array.rb +20 -2
  47. data/lib/rubocop/cop/util.rb +7 -2
  48. data/lib/rubocop/options.rb +1 -1
  49. data/lib/rubocop/version.rb +1 -1
  50. metadata +11 -5
@@ -20,6 +20,13 @@ module RuboCop
20
20
 
21
21
  private
22
22
 
23
+ def indent_level(str)
24
+ indentations = str.lines
25
+ .map { |line| line[/^\s*/] }
26
+ .reject { |line| line.end_with?("\n") }
27
+ indentations.empty? ? 0 : indentations.min_by(&:size).size
28
+ end
29
+
23
30
  def delimiter_string(node)
24
31
  node.source.match(OPENING_DELIMITER).captures[1]
25
32
  end
@@ -18,15 +18,16 @@ module RuboCop
18
18
  !parent.parenthesized? && parent&.block_literal?
19
19
  end
20
20
 
21
+ # Override to determine values that are invalid in a percent array
22
+ def invalid_percent_array_contents?(_node)
23
+ false
24
+ end
25
+
21
26
  def allowed_bracket_array?(node)
22
27
  comments_in_array?(node) || below_array_length?(node) ||
23
28
  invalid_percent_array_context?(node)
24
29
  end
25
30
 
26
- def message(_node)
27
- style == :percent ? self.class::PERCENT_MSG : self.class::ARRAY_MSG
28
- end
29
-
30
31
  def comments_in_array?(node)
31
32
  line_span = node.source_range.first_line...node.source_range.last_line
32
33
  processed_source.each_comment_in_lines(line_span).any?
@@ -35,9 +36,11 @@ module RuboCop
35
36
  def check_percent_array(node)
36
37
  array_style_detected(:percent, node.values.size)
37
38
 
38
- return unless style == :brackets
39
+ return unless style == :brackets || invalid_percent_array_contents?(node)
39
40
 
40
- add_offense(node) { |corrector| correct_bracketed(corrector, node) }
41
+ add_offense(node, message: self.class::ARRAY_MSG) do |corrector|
42
+ correct_bracketed(corrector, node)
43
+ end
41
44
  end
42
45
 
43
46
  def check_bracketed_array(node, literal_prefix)
@@ -47,7 +50,7 @@ module RuboCop
47
50
 
48
51
  return unless style == :percent
49
52
 
50
- add_offense(node) do |corrector|
53
+ add_offense(node, message: self.class::PERCENT_MSG) do |corrector|
51
54
  percent_literal_corrector = PercentLiteralCorrector.new(@config, @preferred_delimiters)
52
55
  percent_literal_corrector.correct(corrector, node, literal_prefix)
53
56
  end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Ensure a require statement is present for a standard library determined
6
+ # by variable library_name
7
+ module RequireLibrary
8
+ extend NodePattern::Macros
9
+
10
+ def ensure_required(corrector, node, library_name)
11
+ node = node.parent while node.parent&.parent?
12
+
13
+ if node.parent&.begin_type?
14
+ return if @required_libs.include?(library_name)
15
+
16
+ remove_subsequent_requires(corrector, node, library_name)
17
+ end
18
+
19
+ RequireLibraryCorrector.correct(corrector, node, library_name)
20
+ end
21
+
22
+ def remove_subsequent_requires(corrector, node, library_name)
23
+ node.right_siblings.each do |sibling|
24
+ next unless require_library_name?(sibling, library_name)
25
+
26
+ range = range_by_whole_lines(sibling.source_range, include_final_newline: true)
27
+ corrector.remove(range)
28
+ end
29
+ end
30
+
31
+ def on_send(node)
32
+ return if node.parent&.parent?
33
+
34
+ name = require_any_library?(node)
35
+ return if name.nil?
36
+
37
+ @required_libs.add(name)
38
+ end
39
+
40
+ private
41
+
42
+ def on_new_investigation
43
+ # Holds the required files at top-level
44
+ @required_libs = Set.new
45
+ super
46
+ end
47
+
48
+ # @!method require_any_library?(node)
49
+ def_node_matcher :require_any_library?, <<~PATTERN
50
+ (send {(const {nil? cbase} :Kernel) nil?} :require (str $_))
51
+ PATTERN
52
+
53
+ # @!method require_library_name?(node, library_name)
54
+ def_node_matcher :require_library_name?, <<~PATTERN
55
+ (send {(const {nil? cbase} :Kernel) nil?} :require (str %1))
56
+ PATTERN
57
+ end
58
+ end
59
+ end
@@ -10,7 +10,7 @@ module RuboCop
10
10
  MSG = 'Space found before %<token>s.'
11
11
 
12
12
  def on_new_investigation
13
- each_missing_space(processed_source.tokens) do |token, pos_before|
13
+ each_missing_space(processed_source.sorted_tokens) do |token, pos_before|
14
14
  add_offense(pos_before, message: format(MSG, token: kind(token))) do |corrector|
15
15
  PunctuationCorrector.remove_space(corrector, pos_before)
16
16
  end
@@ -19,6 +19,8 @@ module RuboCop
19
19
  # Regex can be specified to identify offenses. Suggestions for replacing a flagged term can
20
20
  # be configured and will be displayed as part of the offense message.
21
21
  # An AllowedRegex can be specified for a flagged term to exempt allowed uses of the term.
22
+ # `WholeWord: true` can be set on a flagged term to indicate the cop should only match when
23
+ # a term matches the whole word (partial matches will not be offenses).
22
24
  #
23
25
  # @example FlaggedTerms: { whitelist: { Suggestions: ['allowlist'] } }
24
26
  # # Suggest replacing identifier whitelist with allowlist
@@ -56,6 +58,14 @@ module RuboCop
56
58
  # # good
57
59
  # # They had a master's degree
58
60
  #
61
+ # @example FlaggedTerms: { slave: { WholeWord: true } }
62
+ # # Specify that only terms that are full matches will be flagged.
63
+ #
64
+ # # bad
65
+ # Slave
66
+ #
67
+ # # good (won't be flagged despite containing `slave`)
68
+ # TeslaVehicle
59
69
  class InclusiveLanguage < Base
60
70
  include RangeHelp
61
71
 
@@ -123,7 +133,7 @@ module RuboCop
123
133
  next if term_definition.nil?
124
134
 
125
135
  allowed_strings.concat(process_allowed_regex(term_definition['AllowedRegex']))
126
- regex_string = ensure_regex_string(term_definition['Regex'] || term)
136
+ regex_string = ensure_regex_string(extract_regexp(term, term_definition))
127
137
  flagged_term_strings << regex_string
128
138
 
129
139
  add_to_flagged_term_hash(regex_string, term, term_definition)
@@ -132,6 +142,13 @@ module RuboCop
132
142
  set_regexes(flagged_term_strings, allowed_strings)
133
143
  end
134
144
 
145
+ def extract_regexp(term, term_definition)
146
+ return term_definition['Regex'] if term_definition['Regex']
147
+ return /(?:\b|(?<=[\W_]))#{term}(?:\b|(?=[\W_]))/ if term_definition['WholeWord']
148
+
149
+ term
150
+ end
151
+
135
152
  def add_to_flagged_term_hash(regex_string, term, term_definition)
136
153
  @flagged_term_hash[Regexp.new(regex_string, Regexp::IGNORECASE)] =
137
154
  term_definition.merge('Term' => term,
@@ -6,6 +6,11 @@ module RuboCop
6
6
  # Check for uses of braces or do/end around single line or
7
7
  # multi-line blocks.
8
8
  #
9
+ # Methods that can be either procedural or functional and cannot be
10
+ # categorised from their usage alone is ignored.
11
+ # `lambda`, `proc`, and `it` are their defaults.
12
+ # Additional methods can be added to the `IgnoredMethods`.
13
+ #
9
14
  # @example EnforcedStyle: line_count_based (default)
10
15
  # # bad - single line block
11
16
  # items.each do |item| item / 5 end
@@ -132,6 +137,17 @@ module RuboCop
132
137
  # puts foo
133
138
  # end
134
139
  #
140
+ # @example IgnoredMethods: ['lambda', 'proc', 'it' ] (default)
141
+ #
142
+ # # good
143
+ # foo = lambda do |x|
144
+ # puts "Hello, #{x}"
145
+ # end
146
+ #
147
+ # foo = lambda do |x|
148
+ # x * 100
149
+ # end
150
+ #
135
151
  class BlockDelimiters < Base
136
152
  include ConfigurableEnforcedStyle
137
153
  include IgnoredMethods
@@ -12,6 +12,7 @@ module RuboCop
12
12
  #
13
13
  # Auto-correction removes comments from `end` keyword and keeps comments
14
14
  # for `class`, `module`, `def` and `begin` above the keyword.
15
+ # It is marked as unsafe auto-correction as it may remove meaningful comments.
15
16
  #
16
17
  # @example
17
18
  # # bad
@@ -50,7 +51,7 @@ module RuboCop
50
51
 
51
52
  def on_new_investigation
52
53
  processed_source.comments.each do |comment|
53
- next unless (match = line(comment).match(/(?<keyword>\S+).*#/)) && offensive?(comment)
54
+ next unless offensive?(comment) && (match = line(comment).match(/(?<keyword>\S+).*#/))
54
55
 
55
56
  register_offense(comment, match[:keyword])
56
57
  end
@@ -26,7 +26,7 @@ module RuboCop
26
26
  # `when` nodes contain the entire branch including the condition.
27
27
  # We only need the contents of the branch, not the condition.
28
28
  def expand_when_branches(when_branches)
29
- when_branches.map { |branch| branch.children[1] }
29
+ when_branches.map(&:body)
30
30
  end
31
31
 
32
32
  def tail(branch)
@@ -272,6 +272,16 @@ module RuboCop
272
272
  check_node(node, branches)
273
273
  end
274
274
 
275
+ def on_case_match(node)
276
+ return unless style == :assign_to_condition
277
+ return unless node.else_branch
278
+
279
+ in_pattern_branches = expand_when_branches(node.in_pattern_branches)
280
+ branches = [*in_pattern_branches, node.else_branch]
281
+
282
+ check_node(node, branches)
283
+ end
284
+
275
285
  private
276
286
 
277
287
  def check_assignment_to_condition(node)
@@ -297,7 +307,7 @@ module RuboCop
297
307
  end
298
308
 
299
309
  # @!method candidate_condition?(node)
300
- def_node_matcher :candidate_condition?, '[{if case} !#allowed_ternary?]'
310
+ def_node_matcher :candidate_condition?, '[{if case case_match} !#allowed_ternary?]'
301
311
 
302
312
  def allowed_ternary?(assignment)
303
313
  assignment.if_type? && assignment.ternary? && !include_ternary?
@@ -319,7 +329,7 @@ module RuboCop
319
329
  end
320
330
 
321
331
  def move_assignment_outside_condition(corrector, node)
322
- if node.case_type?
332
+ if node.case_type? || node.case_match_type?
323
333
  CaseCorrector.correct(corrector, self, node)
324
334
  elsif node.ternary?
325
335
  TernaryCorrector.correct(corrector, node)
@@ -333,7 +343,7 @@ module RuboCop
333
343
 
334
344
  if ternary_condition?(condition)
335
345
  TernaryCorrector.move_assignment_inside_condition(corrector, node)
336
- elsif condition.case_type?
346
+ elsif condition.case_type? || condition.case_match_type?
337
347
  CaseCorrector.move_assignment_inside_condition(corrector, node)
338
348
  elsif condition.if_type?
339
349
  IfCorrector.move_assignment_inside_condition(corrector, node)
@@ -631,7 +641,11 @@ module RuboCop
631
641
  end
632
642
 
633
643
  def extract_branches(case_node)
634
- when_branches = expand_when_branches(case_node.when_branches)
644
+ when_branches = if case_node.case_type?
645
+ expand_when_branches(case_node.when_branches)
646
+ else
647
+ expand_when_branches(case_node.in_pattern_branches)
648
+ end
635
649
 
636
650
  [when_branches, case_node.else_branch]
637
651
  end
@@ -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)