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
@@ -41,6 +41,7 @@ module RuboCop
41
41
  #
42
42
  class TrailingWhitespace < Base
43
43
  include RangeHelp
44
+ include Heredoc
44
45
  extend AutoCorrector
45
46
 
46
47
  MSG = 'Trailing whitespace detected.'
@@ -54,6 +55,8 @@ module RuboCop
54
55
  end
55
56
  end
56
57
 
58
+ def on_heredoc(_node); end
59
+
57
60
  private
58
61
 
59
62
  def process_line(line, lineno)
@@ -63,13 +66,33 @@ module RuboCop
63
66
  range = offense_range(lineno, line)
64
67
  add_offense(range) do |corrector|
65
68
  if heredoc
66
- corrector.wrap(range, "\#{'", "'}") unless static?(heredoc)
69
+ process_line_in_heredoc(corrector, range, heredoc)
67
70
  else
68
71
  corrector.remove(range)
69
72
  end
70
73
  end
71
74
  end
72
75
 
76
+ def process_line_in_heredoc(corrector, range, heredoc)
77
+ indent_level = indent_level(find_heredoc(range.line).loc.heredoc_body.source)
78
+ whitespace_only = whitespace_only?(range)
79
+ if whitespace_only && whitespace_is_indentation?(range, indent_level)
80
+ corrector.remove(range)
81
+ elsif !static?(heredoc)
82
+ range = range_between(range.begin_pos + indent_level, range.end_pos) if whitespace_only
83
+ corrector.wrap(range, "\#{'", "'}")
84
+ end
85
+ end
86
+
87
+ def whitespace_is_indentation?(range, level)
88
+ range.source[/ +/].length <= level
89
+ end
90
+
91
+ def whitespace_only?(range)
92
+ source = range_with_surrounding_space(range: range).source
93
+ source.start_with?("\n") && source.end_with?("\n")
94
+ end
95
+
73
96
  def static?(heredoc)
74
97
  heredoc.loc.expression.source.end_with? "'"
75
98
  end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # This cop checks for ambiguous ranges.
7
+ #
8
+ # Ranges have quite low precedence, which leads to unexpected behaviour when
9
+ # using a range with other operators. This cop avoids that by making ranges
10
+ # explicit by requiring parenthesis around complex range boundaries (anything
11
+ # that is not a basic literal: numerics, strings, symbols, etc.).
12
+ #
13
+ # NOTE: The cop auto-corrects by wrapping the entire boundary in parentheses, which
14
+ # makes the outcome more explicit but is possible to not be the intention of the
15
+ # programmer. For this reason, this cop's auto-correct is marked as unsafe (it
16
+ # will not change the behaviour of the code, but will not necessarily match the
17
+ # intent of the program).
18
+ #
19
+ # This cop can be configured with `RequireParenthesesForMethodChains` in order to
20
+ # specify whether method chains (including `self.foo`) should be wrapped in parens
21
+ # by this cop.
22
+ #
23
+ # NOTE: Regardless of this configuration, if a method receiver is a basic literal
24
+ # value, it will be wrapped in order to prevent the ambiguity of `1..2.to_a`.
25
+ #
26
+ # @example
27
+ # # bad
28
+ # x || 1..2
29
+ # (x || 1..2)
30
+ # 1..2.to_a
31
+ #
32
+ # # good, unambiguous
33
+ # 1..2
34
+ # 'a'..'z'
35
+ # :bar..:baz
36
+ # MyClass::MIN..MyClass::MAX
37
+ # @min..@max
38
+ # a..b
39
+ # -a..b
40
+ #
41
+ # # good, ambiguity removed
42
+ # x || (1..2)
43
+ # (x || 1)..2
44
+ # (x || 1)..(y || 2)
45
+ # (1..2).to_a
46
+ #
47
+ # @example RequireParenthesesForMethodChains: false (default)
48
+ # # good
49
+ # a.foo..b.bar
50
+ # (a.foo)..(b.bar)
51
+ #
52
+ # @example RequireParenthesesForMethodChains: true
53
+ # # bad
54
+ # a.foo..b.bar
55
+ #
56
+ # # good
57
+ # (a.foo)..(b.bar)
58
+ #
59
+ class AmbiguousRange < Base
60
+ extend AutoCorrector
61
+
62
+ MSG = 'Wrap complex range boundaries with parentheses to avoid ambiguity.'
63
+
64
+ def on_irange(node)
65
+ each_boundary(node) do |boundary|
66
+ next if acceptable?(boundary)
67
+
68
+ add_offense(boundary) do |corrector|
69
+ corrector.wrap(boundary, '(', ')')
70
+ end
71
+ end
72
+ end
73
+ alias on_erange on_irange
74
+
75
+ private
76
+
77
+ def each_boundary(range)
78
+ yield range.begin if range.begin
79
+ yield range.end if range.end
80
+ end
81
+
82
+ def acceptable?(node)
83
+ node.begin_type? ||
84
+ node.basic_literal? ||
85
+ node.variable? || node.const_type? ||
86
+ node.call_type? && acceptable_call?(node)
87
+ end
88
+
89
+ def acceptable_call?(node)
90
+ return true if node.unary_operation?
91
+
92
+ # Require parentheses when making a method call on a literal
93
+ # to avoid the ambiguity of `1..2.to_a`.
94
+ return false if node.receiver&.basic_literal?
95
+
96
+ require_parentheses_for_method_chain? || node.receiver.nil?
97
+ end
98
+
99
+ def require_parentheses_for_method_chain?
100
+ !cop_config['RequireParenthesesForMethodChains']
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -46,12 +46,11 @@ module RuboCop
46
46
  node = processed_source.ast.each_node(:regexp).find do |regexp_node|
47
47
  regexp_node.source_range.begin_pos == diagnostic.location.begin_pos
48
48
  end
49
-
50
49
  find_offense_node(node.parent, node)
51
50
  end
52
51
 
53
52
  def find_offense_node(node, regexp_receiver)
54
- return node unless node.parent
53
+ return node if first_argument_is_regexp?(node) || !node.parent
55
54
 
56
55
  if (node.parent.send_type? && node.receiver) ||
57
56
  method_chain_to_regexp_receiver?(node, regexp_receiver)
@@ -61,6 +60,10 @@ module RuboCop
61
60
  node
62
61
  end
63
62
 
63
+ def first_argument_is_regexp?(node)
64
+ node.send_type? && node.first_argument&.regexp_type?
65
+ end
66
+
64
67
  def method_chain_to_regexp_receiver?(node, regexp_receiver)
65
68
  return false unless (parent = node.parent)
66
69
  return false unless (parent_receiver = parent.receiver)
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module Cop
5
5
  module Lint
6
6
  # This cop checks that there are no repeated bodies
7
- # within `if/unless`, `case-when` and `rescue` constructs.
7
+ # within `if/unless`, `case-when`, `case-in` and `rescue` constructs.
8
8
  #
9
9
  # With `IgnoreLiteralBranches: true`, branches are not registered
10
10
  # as offenses if they return a basic literal value (string, symbol,
@@ -97,6 +97,7 @@ module RuboCop
97
97
  end
98
98
  alias on_if on_branching_statement
99
99
  alias on_case on_branching_statement
100
+ alias on_case_match on_branching_statement
100
101
  alias on_rescue on_branching_statement
101
102
 
102
103
  private
@@ -135,11 +135,14 @@ module RuboCop
135
135
  def found_instance_method(node, name)
136
136
  return unless (scope = node.parent_module_name)
137
137
 
138
- if scope =~ /\A#<Class:(.*)>\Z/
139
- found_method(node, "#{Regexp.last_match(1)}.#{name}")
140
- else
141
- found_method(node, "#{scope}##{name}")
142
- end
138
+ # Humanize the scope
139
+ scope = scope.sub(
140
+ /(?:(?<name>.*)::)#<Class:\k<name>>|#<Class:(?<name>.*)>(?:::)?/,
141
+ '\k<name>.'
142
+ )
143
+ scope << '#' unless scope.end_with?('.')
144
+
145
+ found_method(node, "#{scope}#{name}")
143
146
  end
144
147
 
145
148
  def found_method(node, method_name)
@@ -141,7 +141,7 @@ module RuboCop
141
141
  end
142
142
 
143
143
  def reference_pos(node)
144
- node = node.parent.masgn_type? ? node.parent : node
144
+ node = node.parent if node.parent.masgn_type?
145
145
 
146
146
  node.source_range.begin_pos
147
147
  end
@@ -81,7 +81,7 @@ module RuboCop
81
81
  return if block_reassigns_arg?(node, block_arg)
82
82
 
83
83
  source = node.body.source
84
- source.gsub!(/\b#{block_arg}\b/, '1') if block_arg
84
+ source.gsub!(/\b#{block_arg}\b/, '0') if block_arg
85
85
 
86
86
  corrector.replace(node, fix_indentation(source, node.loc.column...node.body.loc.column))
87
87
  end
@@ -97,9 +97,9 @@ module RuboCop
97
97
  # If a send node contains a heredoc argument, splitting cannot happen
98
98
  # after the heredoc or else it will cause a syntax error.
99
99
  def shift_elements_for_heredoc_arg(node, elements, index)
100
- return index unless node.send_type?
100
+ return index unless node.send_type? || node.array_type?
101
101
 
102
- heredoc_index = elements.index { |arg| (arg.str_type? || arg.dstr_type?) && arg.heredoc? }
102
+ heredoc_index = elements.index { |arg| arg.respond_to?(:heredoc?) && arg.heredoc? }
103
103
  return index unless heredoc_index
104
104
  return nil if heredoc_index.zero?
105
105
 
@@ -175,7 +175,12 @@ module RuboCop
175
175
  end
176
176
 
177
177
  def set_new_body_expression(transforming_body_expr, corrector)
178
- corrector.replace(block_node.body, transforming_body_expr.loc.expression.source)
178
+ body_source = transforming_body_expr.loc.expression.source
179
+ if transforming_body_expr.hash_type? && !transforming_body_expr.braces?
180
+ body_source = "{ #{body_source} }"
181
+ end
182
+
183
+ corrector.replace(block_node.body, body_source)
179
184
  end
180
185
  end
181
186
  end
@@ -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,14 @@ 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)
40
+
41
+ bracketed_array = build_bracketed_array(node)
42
+ message = format(self.class::ARRAY_MSG, prefer: bracketed_array)
39
43
 
40
- add_offense(node) { |corrector| correct_bracketed(corrector, node) }
44
+ add_offense(node, message: message) do |corrector|
45
+ corrector.replace(node, bracketed_array)
46
+ end
41
47
  end
42
48
 
43
49
  def check_bracketed_array(node, literal_prefix)
@@ -47,7 +53,7 @@ module RuboCop
47
53
 
48
54
  return unless style == :percent
49
55
 
50
- add_offense(node) do |corrector|
56
+ add_offense(node, message: self.class::PERCENT_MSG) do |corrector|
51
57
  percent_literal_corrector = PercentLiteralCorrector.new(@config, @preferred_delimiters)
52
58
  percent_literal_corrector.correct(corrector, node, literal_prefix)
53
59
  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,