rubocop 1.18.2 → 1.19.1

Sign up to get free protection for your applications and to get access to all the features.
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,