rubocop 1.86.1 → 1.87.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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +15 -1
  3. data/lib/rubocop/cli/command/auto_generate_config.rb +27 -1
  4. data/lib/rubocop/cli/command/list_enabled_cops_for.rb +40 -0
  5. data/lib/rubocop/cli/command/show_docs_url.rb +3 -7
  6. data/lib/rubocop/cli/command/suggest_extensions.rb +1 -1
  7. data/lib/rubocop/cli.rb +6 -7
  8. data/lib/rubocop/comment_config.rb +12 -15
  9. data/lib/rubocop/config_loader.rb +17 -2
  10. data/lib/rubocop/config_loader_resolver.rb +11 -3
  11. data/lib/rubocop/config_store.rb +1 -1
  12. data/lib/rubocop/cop/autocorrect_logic.rb +2 -1
  13. data/lib/rubocop/cop/base.rb +8 -2
  14. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +1 -5
  15. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +33 -2
  16. data/lib/rubocop/cop/correctors.rb +28 -0
  17. data/lib/rubocop/cop/exclude_limit.rb +31 -5
  18. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +2 -2
  19. data/lib/rubocop/cop/gemspec/require_mfa.rb +4 -4
  20. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +3 -3
  21. data/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb +1 -0
  22. data/lib/rubocop/cop/layout/begin_end_alignment.rb +1 -1
  23. data/lib/rubocop/cop/layout/class_structure.rb +1 -1
  24. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +14 -5
  25. data/lib/rubocop/cop/layout/end_alignment.rb +2 -2
  26. data/lib/rubocop/cop/layout/indentation_width.rb +13 -1
  27. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +1 -1
  28. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +26 -1
  29. data/lib/rubocop/cop/layout/redundant_line_break.rb +3 -1
  30. data/lib/rubocop/cop/layout/space_before_brackets.rb +1 -1
  31. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +1 -1
  32. data/lib/rubocop/cop/lint/constant_overwritten_in_rescue.rb +1 -1
  33. data/lib/rubocop/cop/lint/constant_reassignment.rb +36 -4
  34. data/lib/rubocop/cop/lint/constant_resolution.rb +5 -5
  35. data/lib/rubocop/cop/lint/deprecated_constants.rb +1 -1
  36. data/lib/rubocop/cop/lint/erb_new_arguments.rb +1 -1
  37. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +1 -1
  38. data/lib/rubocop/cop/lint/multiple_comparison.rb +2 -2
  39. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +1 -1
  40. data/lib/rubocop/cop/lint/number_conversion.rb +5 -5
  41. data/lib/rubocop/cop/lint/numbered_parameter_assignment.rb +1 -1
  42. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +3 -13
  43. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +2 -2
  44. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +3 -3
  45. data/lib/rubocop/cop/lint/require_relative_self_path.rb +3 -1
  46. data/lib/rubocop/cop/lint/shadowed_exception.rb +1 -1
  47. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +1 -1
  48. data/lib/rubocop/cop/lint/unreachable_code.rb +2 -2
  49. data/lib/rubocop/cop/lint/useless_assignment.rb +3 -8
  50. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +1 -1
  51. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +18 -7
  52. data/lib/rubocop/cop/metrics/block_length.rb +1 -1
  53. data/lib/rubocop/cop/metrics/method_length.rb +1 -1
  54. data/lib/rubocop/cop/metrics/utils/iterating_block.rb +1 -1
  55. data/lib/rubocop/cop/mixin/configurable_max.rb +6 -5
  56. data/lib/rubocop/cop/mixin/project_index_help.rb +48 -0
  57. data/lib/rubocop/cop/mixin.rb +86 -0
  58. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +1 -1
  59. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +1 -1
  60. data/lib/rubocop/cop/naming/predicate_method.rb +2 -2
  61. data/lib/rubocop/cop/naming/predicate_prefix.rb +1 -1
  62. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -1
  63. data/lib/rubocop/cop/offense.rb +8 -0
  64. data/lib/rubocop/cop/registry.rb +42 -25
  65. data/lib/rubocop/cop/security/io_methods.rb +1 -1
  66. data/lib/rubocop/cop/style/alias.rb +10 -1
  67. data/lib/rubocop/cop/style/character_literal.rb +2 -2
  68. data/lib/rubocop/cop/style/class_and_module_children.rb +8 -0
  69. data/lib/rubocop/cop/style/copyright.rb +21 -10
  70. data/lib/rubocop/cop/style/date_time.rb +2 -2
  71. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +1 -1
  72. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +6 -1
  73. data/lib/rubocop/cop/style/file_write.rb +18 -16
  74. data/lib/rubocop/cop/style/format_string.rb +4 -3
  75. data/lib/rubocop/cop/style/hash_conversion.rb +1 -1
  76. data/lib/rubocop/cop/style/hash_lookup_method.rb +12 -7
  77. data/lib/rubocop/cop/style/if_inside_else.rb +15 -2
  78. data/lib/rubocop/cop/style/magic_comment_format.rb +1 -1
  79. data/lib/rubocop/cop/style/min_max_comparison.rb +1 -1
  80. data/lib/rubocop/cop/style/module_member_existence_check.rb +6 -3
  81. data/lib/rubocop/cop/style/reduce_to_hash.rb +16 -0
  82. data/lib/rubocop/cop/style/redundant_array_constructor.rb +2 -2
  83. data/lib/rubocop/cop/style/redundant_constant_base.rb +5 -5
  84. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +2 -2
  85. data/lib/rubocop/cop/style/redundant_self.rb +2 -2
  86. data/lib/rubocop/cop/style/regexp_literal.rb +31 -2
  87. data/lib/rubocop/cop/style/rescue_modifier.rb +3 -3
  88. data/lib/rubocop/cop/style/self_assignment.rb +1 -1
  89. data/lib/rubocop/cop/style/sole_nested_conditional.rb +4 -2
  90. data/lib/rubocop/cop/style/struct_inheritance.rb +13 -0
  91. data/lib/rubocop/cop/style/symbol_proc.rb +3 -3
  92. data/lib/rubocop/cop/style/top_level_method_definition.rb +2 -2
  93. data/lib/rubocop/cop/style/unless_logical_operators.rb +3 -3
  94. data/lib/rubocop/cop/style/while_until_modifier.rb +16 -0
  95. data/lib/rubocop/cop/style/yoda_condition.rb +1 -1
  96. data/lib/rubocop/cop/team.rb +86 -35
  97. data/lib/rubocop/file_patterns.rb +9 -1
  98. data/lib/rubocop/formatter/disabled_config_formatter.rb +4 -1
  99. data/lib/rubocop/lsp/runtime.rb +1 -2
  100. data/lib/rubocop/options.rb +26 -4
  101. data/lib/rubocop/project_index_loader.rb +66 -0
  102. data/lib/rubocop/rspec/shared_contexts.rb +21 -0
  103. data/lib/rubocop/runner.rb +123 -57
  104. data/lib/rubocop/target_finder.rb +13 -6
  105. data/lib/rubocop/version.rb +20 -2
  106. data/lib/rubocop.rb +8 -96
  107. metadata +8 -3
@@ -64,7 +64,7 @@ module RuboCop
64
64
 
65
65
  MSG = 'Convert `if` nested inside `else` to `elsif`.'
66
66
 
67
- # rubocop:disable Metrics/CyclomaticComplexity
67
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
68
68
  def on_if(node)
69
69
  return if node.ternary? || node.unless?
70
70
 
@@ -72,6 +72,7 @@ module RuboCop
72
72
 
73
73
  return unless else_branch&.if_type? && else_branch.if?
74
74
  return if allow_if_modifier_in_else_branch?(else_branch)
75
+ return if comments_between_else_and_if?(node, else_branch)
75
76
 
76
77
  add_offense(else_branch.loc.keyword) do |corrector|
77
78
  next if part_of_ignored_node?(node)
@@ -80,7 +81,7 @@ module RuboCop
80
81
  ignore_node(node)
81
82
  end
82
83
  end
83
- # rubocop:enable Metrics/CyclomaticComplexity
84
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
84
85
 
85
86
  private
86
87
 
@@ -135,6 +136,18 @@ module RuboCop
135
136
  range_between(node.loc.keyword.begin_pos, condition.source_range.end_pos)
136
137
  end
137
138
 
139
+ def comments_between_else_and_if?(node, else_branch)
140
+ return false if else_branch.modifier_form?
141
+
142
+ else_end = node.loc.else.end_pos
143
+ if_begin = else_branch.loc.keyword.begin_pos
144
+
145
+ processed_source.comments.any? do |comment|
146
+ comment_pos = comment.source_range.begin_pos
147
+ comment_pos > else_end && comment_pos < if_begin
148
+ end
149
+ end
150
+
138
151
  def allow_if_modifier_in_else_branch?(else_branch)
139
152
  allow_if_modifier? && else_branch&.modifier_form?
140
153
  end
@@ -10,7 +10,7 @@ module RuboCop
10
10
  # Required capitalization can be set with the `DirectiveCapitalization` and
11
11
  # `ValueCapitalization` configuration keys.
12
12
  #
13
- # NOTE: If one of these configuration is set to nil, any capitalization is allowed.
13
+ # NOTE: If one of these configurations is set to nil, any capitalization is allowed.
14
14
  #
15
15
  # @example EnforcedStyle: snake_case (default)
16
16
  # # The `snake_case` style will enforce that the frozen string literal
@@ -6,7 +6,7 @@ module RuboCop
6
6
  # Enforces the use of `max` or `min` instead of comparison for greater or less.
7
7
  #
8
8
  # NOTE: It can be used if you want to present limit or threshold in Ruby 2.7+.
9
- # That it is slow though. So autocorrection will apply generic `max` or `min`:
9
+ # It is slow though. So autocorrection will apply generic `max` or `min`:
10
10
  #
11
11
  # [source,ruby]
12
12
  # ----
@@ -13,6 +13,12 @@ module RuboCop
13
13
  # array, while `method_defined?` will do direct method lookup, which is much
14
14
  # faster and consumes less memory.
15
15
  #
16
+ # NOTE: `constants.include?` is not handled by this cop because
17
+ # `Module#const_defined?` has different lookup behavior than
18
+ # `Module#constants` - `const_defined?` searches up to `Object`
19
+ # (top-level constants like `String`, `Integer`, etc.) while
20
+ # `constants` does not, which can cause behavior changes after autocorrection.
21
+ #
16
22
  # @example
17
23
  # # bad
18
24
  # Array.instance_methods.include?(:size)
@@ -28,14 +34,12 @@ module RuboCop
28
34
  #
29
35
  # # bad
30
36
  # Array.class_variables.include?(:foo)
31
- # Array.constants.include?(:foo)
32
37
  # Array.private_instance_methods.include?(:foo)
33
38
  # Array.protected_instance_methods.include?(:foo)
34
39
  # Array.public_instance_methods.include?(:foo)
35
40
  #
36
41
  # # good
37
42
  # Array.class_variable_defined?(:foo)
38
- # Array.const_defined?(:foo)
39
43
  # Array.private_method_defined?(:foo)
40
44
  # Array.protected_method_defined?(:foo)
41
45
  # Array.public_method_defined?(:foo)
@@ -55,7 +59,6 @@ module RuboCop
55
59
 
56
60
  METHOD_REPLACEMENTS = {
57
61
  class_variables: :class_variable_defined?,
58
- constants: :const_defined?,
59
62
  instance_methods: :method_defined?,
60
63
  private_instance_methods: :private_method_defined?,
61
64
  protected_instance_methods: :protected_method_defined?,
@@ -99,6 +99,7 @@ module RuboCop
99
99
  end
100
100
  return unless key
101
101
  return if accumulator_used_in_expressions?(block_node, key, value)
102
+ return if nested_match?(key) || nested_match?(value)
102
103
 
103
104
  register_offense(node, block_node, key, value)
104
105
  end
@@ -108,6 +109,21 @@ module RuboCop
108
109
  references_variable?(key, acc_name) || references_variable?(value, acc_name)
109
110
  end
110
111
 
112
+ def nested_match?(node)
113
+ node.each_node(:call).any? do |send_node|
114
+ next false unless RESTRICT_ON_SEND.include?(send_node.method_name)
115
+
116
+ inner_block = send_node.block_node
117
+ next false unless inner_block
118
+
119
+ if send_node.method?(:each_with_object)
120
+ each_with_object_to_hash?(inner_block)
121
+ else
122
+ inject_to_hash?(inner_block)
123
+ end
124
+ end
125
+ end
126
+
111
127
  def accumulator_name(block_node)
112
128
  index = block_node.method?(:each_with_object) ? 1 : 0
113
129
  block_node.argument_list[index].name
@@ -3,8 +3,8 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for the instantiation of array using redundant `Array` constructor.
7
- # Autocorrect replaces to array literal which is the simplest and fastest.
6
+ # Checks for the instantiation of an array using a redundant `Array` constructor.
7
+ # Autocorrect replaces it with an array literal which is the simplest and fastest.
8
8
  #
9
9
  # @example
10
10
  #
@@ -3,16 +3,16 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Avoid redundant `::` prefix on constant.
6
+ # Avoid redundant `::` prefix on a constant.
7
7
  #
8
- # How Ruby searches constant is a bit complicated, and it can often be difficult to
8
+ # How Ruby searches constants is a bit complicated, and it can often be difficult to
9
9
  # understand from the code whether the `::` is intended or not. Where `Module.nesting`
10
10
  # is empty, there is no need to prepend `::`, so it would be nice to consistently
11
11
  # avoid such meaningless `::` prefix to avoid confusion.
12
12
  #
13
- # NOTE: This cop is disabled if `Lint/ConstantResolution` cop is enabled to prevent
14
- # conflicting rules. Because it respects user configurations that want to enable
15
- # `Lint/ConstantResolution` cop which is disabled by default.
13
+ # NOTE: This cop is disabled if `Lint/ConstantResolution` cop is enabled,
14
+ # to prevent conflicting rules. This is because it respects user configurations
15
+ # that want to enable `Lint/ConstantResolution` cop which is disabled by default.
16
16
  #
17
17
  # @example
18
18
  # # bad
@@ -3,8 +3,8 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for the instantiation of regexp using redundant `Regexp.new` or `Regexp.compile`.
7
- # Autocorrect replaces to regexp literal which is the simplest and fastest.
6
+ # Checks for the instantiation of a regexp using a redundant `Regexp.new` or `Regexp.compile`.
7
+ # Autocorrect replaces it with a regexp literal which is the simplest and fastest.
8
8
  #
9
9
  # @example
10
10
  #
@@ -59,7 +59,7 @@ module RuboCop
59
59
 
60
60
  def initialize(config = nil, options = nil)
61
61
  super
62
- @allowed_send_nodes = []
62
+ @allowed_send_nodes = Set.new.compare_by_identity
63
63
  @local_variables_scopes = Hash.new { |hash, key| hash[key] = [] }.compare_by_identity
64
64
  end
65
65
 
@@ -187,7 +187,7 @@ module RuboCop
187
187
  def allow_self(node)
188
188
  return unless node.send_type? && node.self_receiver?
189
189
 
190
- @allowed_send_nodes << node
190
+ @allowed_send_nodes.add(node)
191
191
  end
192
192
 
193
193
  def add_lhs_to_local_variables_scopes(rhs, lhs)
@@ -5,8 +5,8 @@ module RuboCop
5
5
  module Style
6
6
  # Enforces using `//` or `%r` around regular expressions.
7
7
  #
8
- # NOTE: The following `%r` cases using a regexp starts with a blank or `=`
9
- # as a method argument allowed to prevent syntax errors.
8
+ # NOTE: The following `%r` cases using a regexp that starts with a blank or `=`
9
+ # as a method argument are allowed to prevent syntax errors.
10
10
  #
11
11
  # [source,ruby]
12
12
  # ----
@@ -98,7 +98,16 @@ module RuboCop
98
98
  MSG_USE_SLASHES = 'Use `//` around regular expression.'
99
99
  MSG_USE_PERCENT_R = 'Use `%r` around regular expression.'
100
100
 
101
+ PAIR_DELIMITER_PATTERNS = {
102
+ ['(', ')'] => /\\.|[()]/,
103
+ ['[', ']'] => /\\.|[\[\]]/,
104
+ ['{', '}'] => /\\.|[{}]/,
105
+ ['<', '>'] => /\\.|[<>]/
106
+ }.freeze
107
+
101
108
  def on_regexp(node)
109
+ return if slash_literal?(node) && percent_r_delimiters_conflict?(node)
110
+
102
111
  message = if slash_literal?(node)
103
112
  MSG_USE_PERCENT_R unless allowed_slash_literal?(node)
104
113
  else
@@ -115,6 +124,26 @@ module RuboCop
115
124
 
116
125
  private
117
126
 
127
+ def percent_r_delimiters_conflict?(node)
128
+ opening, closing = preferred_delimiters
129
+ return false unless (pattern = PAIR_DELIMITER_PATTERNS[[opening, closing]])
130
+
131
+ !balanced_delimiters?(node_body(node), opening, closing, pattern)
132
+ end
133
+
134
+ def balanced_delimiters?(text, opening, closing, pattern)
135
+ depth = 0
136
+ text.scan(pattern) do |match|
137
+ if match == opening
138
+ depth += 1
139
+ elsif match == closing
140
+ depth -= 1
141
+ return false if depth.negative?
142
+ end
143
+ end
144
+ depth.zero?
145
+ end
146
+
118
147
  def allowed_slash_literal?(node)
119
148
  (style == :slashes && !contains_disallowed_slash?(node)) || allowed_mixed_slash?(node)
120
149
  end
@@ -3,17 +3,17 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for uses of `rescue` in its modifier form is added for following
6
+ # Checks for uses of `rescue` in its modifier form. It is added for the following
7
7
  # reasons:
8
8
  #
9
9
  # * The syntax of modifier form `rescue` can be misleading because it
10
10
  # might lead us to believe that `rescue` handles the given exception
11
- # but it actually rescue all exceptions to return the given rescue
11
+ # but it actually rescues all exceptions to return the given rescue
12
12
  # block. In this case, value returned by handle_error or
13
13
  # SomeException.
14
14
  #
15
15
  # * Modifier form `rescue` would rescue all the exceptions. It would
16
- # silently skip all exception or errors and handle the error.
16
+ # silently skip all exceptions or errors and handle the error.
17
17
  # Example: If `NoMethodError` is raised, modifier form rescue would
18
18
  # handle the exception.
19
19
  #
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Enforces the use the shorthand for self-assignment.
6
+ # Enforces the use of the shorthand for self-assignment.
7
7
  #
8
8
  # @example
9
9
  #
@@ -65,7 +65,10 @@ module RuboCop
65
65
 
66
66
  message = format(MSG, conditional_type: node.keyword)
67
67
  add_offense(if_branch.loc.keyword, message: message) do |corrector|
68
+ next if ignored_node?(node)
69
+
68
70
  autocorrect(corrector, node, if_branch)
71
+ ignore_node(if_branch)
69
72
  end
70
73
  end
71
74
 
@@ -115,9 +118,8 @@ module RuboCop
115
118
  end
116
119
 
117
120
  def correct_node(corrector, node)
118
- corrector.replace(node.loc.keyword, 'if') if node.unless? && !part_of_ignored_node?(node)
121
+ corrector.replace(node.loc.keyword, 'if') if node.unless?
119
122
  corrector.replace(node.condition, chainable_condition(node))
120
- ignore_node(node)
121
123
  end
122
124
 
123
125
  def correct_for_guard_condition_style(corrector, node, if_branch)
@@ -61,6 +61,8 @@ module RuboCop
61
61
  corrector.remove(range_with_surrounding_space(parent.loc.end, newlines: false))
62
62
  elsif (class_node = parent.parent).body.nil?
63
63
  corrector.remove(range_for_empty_class_body(class_node, parent))
64
+ elsif unparenthesized_struct_new?(parent)
65
+ wrap_unparenthesized_call_with_do(corrector, parent)
64
66
  else
65
67
  corrector.insert_after(parent, ' do')
66
68
  end
@@ -73,6 +75,17 @@ module RuboCop
73
75
  range_by_whole_lines(class_node.loc.end, include_final_newline: true)
74
76
  end
75
77
  end
78
+
79
+ def unparenthesized_struct_new?(parent)
80
+ parent.send_type? && parent.arguments.any? && !parent.parenthesized?
81
+ end
82
+
83
+ def wrap_unparenthesized_call_with_do(corrector, parent)
84
+ args_source = parent.arguments.map(&:source).join(', ')
85
+ range = parent.loc.selector.end.join(parent.source_range.end)
86
+
87
+ corrector.replace(range, "(#{args_source}) do")
88
+ end
76
89
  end
77
90
  end
78
91
  end
@@ -260,10 +260,10 @@ module RuboCop
260
260
  end
261
261
 
262
262
  def begin_pos_for_replacement(node)
263
- expr = node.send_node.source_range
263
+ send_node = node.send_node
264
264
 
265
- if (paren_pos = (expr.source =~ /\(\s*\)$/))
266
- expr.begin_pos + paren_pos
265
+ if send_node.parenthesized? && send_node.arguments.empty?
266
+ send_node.loc.begin.begin_pos
267
267
  else
268
268
  node.loc.begin.begin_pos
269
269
  end
@@ -3,11 +3,11 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Newcomers to ruby applications may write top-level methods,
6
+ # Newcomers to Ruby applications may write top-level methods,
7
7
  # when ideally they should be organized in appropriate classes or modules.
8
8
  # This cop looks for definitions of top-level methods and warns about them.
9
9
  #
10
- # However for ruby scripts it is perfectly fine to use top-level methods.
10
+ # However, for Ruby scripts it is perfectly fine to use top-level methods.
11
11
  # Hence this cop is disabled by default.
12
12
  #
13
13
  # @example
@@ -14,11 +14,11 @@ module RuboCop
14
14
  #
15
15
  # `forbid_mixed_logical_operators` style forbids the use of more than one type
16
16
  # of logical operators. This makes the `unless` condition easier to read
17
- # because either all conditions need to be met or any condition need to be met
17
+ # because either all conditions need to be met or any condition needs to be met
18
18
  # in order for the expression to be truthy or falsey.
19
19
  #
20
- # `forbid_logical_operators` style forbids any use of logical operator.
21
- # This makes it even more easy to read the `unless` condition as
20
+ # `forbid_logical_operators` style forbids any use of logical operators.
21
+ # This makes it even easier to read the `unless` condition as
22
22
  # there is only one condition in the expression.
23
23
  #
24
24
  # @example EnforcedStyle: forbid_mixed_logical_operators (default)
@@ -16,6 +16,11 @@ module RuboCop
16
16
  # # good
17
17
  # x += 1 while x < 10
18
18
  #
19
+ # # good
20
+ # while x < 10
21
+ # y += 1 if x.odd?
22
+ # end
23
+ #
19
24
  # # bad
20
25
  # until x > 10
21
26
  # x += 1
@@ -24,6 +29,11 @@ module RuboCop
24
29
  # # good
25
30
  # x += 1 until x > 10
26
31
  #
32
+ # # good
33
+ # until x > 10
34
+ # y += 1 unless x.even?
35
+ # end
36
+ #
27
37
  # # bad
28
38
  # x += 100 while x < 500 # a long comment that makes code too long if it were a single line
29
39
  #
@@ -45,6 +55,12 @@ module RuboCop
45
55
  end
46
56
  end
47
57
  alias on_until on_while
58
+
59
+ private
60
+
61
+ def non_eligible_body?(body)
62
+ body&.conditional? || super
63
+ end
48
64
  end
49
65
  end
50
66
  end
@@ -147,7 +147,7 @@ module RuboCop
147
147
  end
148
148
 
149
149
  def constant_portion?(node)
150
- node.literal? || node.const_type?
150
+ node.recursive_literal? || node.const_type?
151
151
  end
152
152
 
153
153
  def actual_code_range(node)
@@ -11,6 +11,9 @@ module RuboCop
11
11
  # (unless autocorrections happened).
12
12
  # rubocop:disable Metrics/ClassLength
13
13
  class Team
14
+ InvestigationResult = Struct.new(:report, :corrector)
15
+ private_constant :InvestigationResult
16
+
14
17
  # @return [Team]
15
18
  def self.new(cop_or_classes, config, options = {})
16
19
  # Support v0 api:
@@ -89,31 +92,25 @@ module RuboCop
89
92
 
90
93
  # @return [Commissioner::InvestigationReport]
91
94
  def investigate(processed_source, offset: 0, original: processed_source)
92
- be_ready
93
-
94
- # The autocorrection process may have to be repeated multiple times
95
- # until there are no corrections left to perform
96
- # To speed things up, run autocorrecting cops by themselves, and only
97
- # run the other cops when no corrections are left
98
- on_duty = roundup_relevant_cops(processed_source)
95
+ result = investigate_with_corrector(processed_source, offset: offset, original: original)
96
+ autocorrect(processed_source, result.corrector)
97
+ result.report
98
+ end
99
99
 
100
- autocorrect_cops, other_cops = on_duty.partition(&:autocorrect?)
101
- report = investigate_partial(autocorrect_cops, processed_source,
102
- offset: offset, original: original)
100
+ # @return [Array<Offense>]
101
+ def investigate_fragments(fragments, original:)
102
+ @updated_source_file = false
103
103
 
104
- unless autocorrect(processed_source, report, offset: offset, original: original)
105
- # If we corrected some errors, another round of inspection will be
106
- # done, and any other offenses will be caught then, so only need
107
- # to check other_cops if no correction was done
108
- report = report.merge(investigate_partial(other_cops, processed_source,
109
- offset: offset, original: original))
110
- end
104
+ offenses, errors, warnings, corrector =
105
+ fragments.each_with_object([[], [], [], nil]) do |fragment, data|
106
+ investigate_fragment(fragment, original, data)
107
+ end
111
108
 
112
- process_errors(processed_source.path, report.errors)
109
+ autocorrect(original, corrector)
110
+ @errors = errors
111
+ @warnings = warnings
113
112
 
114
- report
115
- ensure
116
- @ready = false
113
+ offenses
117
114
  end
118
115
 
119
116
  # @deprecated
@@ -136,14 +133,13 @@ module RuboCop
136
133
 
137
134
  private
138
135
 
139
- def autocorrect(processed_source, report, original:, offset:)
136
+ def autocorrect(processed_source, corrector)
140
137
  @updated_source_file = false
141
138
  return unless autocorrect?
142
- return if report.processed_source.parser_error
139
+ return unless corrector
140
+ return if corrector.empty?
143
141
 
144
- new_source = autocorrect_report(report, original: original, offset: offset)
145
-
146
- return unless new_source
142
+ new_source = corrector.rewrite
147
143
 
148
144
  if @options[:stdin]
149
145
  # holds source read in from stdin, when --stdin option is used
@@ -174,6 +170,54 @@ module RuboCop
174
170
  commissioner.investigate(processed_source, offset: offset, original: original)
175
171
  end
176
172
 
173
+ def investigate_with_corrector(processed_source, offset:, original:)
174
+ be_ready
175
+
176
+ # The autocorrection process may have to be repeated multiple times
177
+ # until there are no corrections left to perform
178
+ # To speed things up, run autocorrecting cops by themselves, and only
179
+ # run the other cops when no corrections are left
180
+ on_duty = roundup_relevant_cops(processed_source)
181
+
182
+ autocorrect_cops, other_cops = on_duty.partition(&:autocorrect?)
183
+ report = investigate_partial(autocorrect_cops, processed_source,
184
+ offset: offset, original: original)
185
+
186
+ corrector = collated_corrector(report, offset: offset, original: original)
187
+
188
+ unless corrector
189
+ # If we corrected some errors, another round of inspection will be
190
+ # done, and any other offenses will be caught then, so only need
191
+ # to check other_cops if no correction was done
192
+ report = report.merge(investigate_partial(other_cops, processed_source,
193
+ offset: offset, original: original))
194
+ end
195
+
196
+ process_errors(processed_source.path, report.errors)
197
+
198
+ InvestigationResult.new(report, corrector)
199
+ ensure
200
+ @ready = false
201
+ end
202
+
203
+ def investigate_fragment(fragment, original, data)
204
+ offenses, errors, warnings, corrector = data
205
+ result = investigate_with_corrector(
206
+ fragment[:processed_source],
207
+ offset: fragment[:offset],
208
+ original: original
209
+ )
210
+
211
+ offenses.concat(result.report.offenses)
212
+ if result.corrector
213
+ corrector ||= Corrector.new(original)
214
+ merge_corrector!(corrector, result.corrector, offset: 0)
215
+ data[3] = corrector
216
+ end
217
+ errors.concat(@errors)
218
+ warnings.concat(@warnings)
219
+ end
220
+
177
221
  # @return [Array<cop>]
178
222
  def roundup_relevant_cops(processed_source)
179
223
  cops.select do |cop|
@@ -200,28 +244,35 @@ module RuboCop
200
244
  cop.class.support_target_rails_version?(cop.target_rails_version)
201
245
  end
202
246
 
203
- def autocorrect_report(report, offset:, original:)
247
+ def collated_corrector(report, offset:, original:)
248
+ return unless autocorrect?
249
+ return if report.processed_source.parser_error
250
+
204
251
  corrector = collate_corrections(report, offset: offset, original: original)
205
252
 
206
- corrector.rewrite unless corrector.empty?
253
+ corrector unless corrector.empty?
207
254
  end
208
255
 
209
256
  def collate_corrections(report, offset:, original:)
210
257
  corrector = Corrector.new(original)
211
258
 
212
259
  each_corrector(report) do |to_merge|
213
- suppress_clobbering do
214
- if corrector.source_buffer == to_merge.source_buffer
215
- corrector.merge!(to_merge)
216
- else
217
- corrector.import!(to_merge, offset: offset)
218
- end
219
- end
260
+ merge_corrector!(corrector, to_merge, offset: offset)
220
261
  end
221
262
 
222
263
  corrector
223
264
  end
224
265
 
266
+ def merge_corrector!(corrector, to_merge, offset:)
267
+ suppress_clobbering do
268
+ if corrector.source_buffer == to_merge.source_buffer
269
+ corrector.merge!(to_merge)
270
+ else
271
+ corrector.import!(to_merge, offset: offset)
272
+ end
273
+ end
274
+ end
275
+
225
276
  def each_corrector(report)
226
277
  skips = Set.new
227
278
  report.cop_reports.each do |cop_report|
@@ -21,11 +21,19 @@ module RuboCop
21
21
  def initialize(patterns)
22
22
  @strings = Set.new
23
23
  @patterns = []
24
+ @match_cache = {}
24
25
  partition_patterns(patterns)
25
26
  end
26
27
 
27
28
  def match?(path)
28
- @strings.include?(path) || @patterns.any? { |pattern| PathUtil.match_path?(pattern, path) }
29
+ # `FilePatterns.from` memoizes one instance per pattern array (by identity),
30
+ # so this cache is shared across every cop using the same Include/Exclude
31
+ # list. Patterns are immutable within a run, so caching by path is safe.
32
+ cached = @match_cache[path]
33
+ return cached unless cached.nil?
34
+
35
+ @match_cache[path] =
36
+ @strings.include?(path) || @patterns.any? { |pattern| PathUtil.match_path?(pattern, path) }
29
37
  end
30
38
 
31
39
  private
@@ -131,6 +131,9 @@ module RuboCop
131
131
  end
132
132
 
133
133
  def set_max(cfg, cop_name)
134
+ exclude_limits = RuboCop::ExcludeLimit.read_limits(cop_name)
135
+ cfg[:exclude_limit] = exclude_limits unless exclude_limits.empty?
136
+
134
137
  return unless cfg[:exclude_limit]
135
138
 
136
139
  cfg.merge!(cfg[:exclude_limit]) if should_set_max?(cop_name)
@@ -192,7 +195,7 @@ module RuboCop
192
195
  next unless value.is_a?(Array)
193
196
  next if value.empty?
194
197
 
195
- value.map! { |v| v.nil? ? '~' : v } # Change nil back to ~ as in the YAML file.
198
+ value = value.map { |v| v.nil? ? '~' : v } # Change nil back to ~ as in the YAML file.
196
199
  output_buffer.puts "# #{param}: #{value.uniq.join(', ')}"
197
200
  end
198
201
  end
@@ -23,7 +23,6 @@ module RuboCop
23
23
  RuboCop::LSP.enable
24
24
 
25
25
  @runner = RuboCop::Lsp::StdinRunner.new(config_store)
26
- @cop_registry = RuboCop::Cop::Registry.global.to_h
27
26
 
28
27
  @safe_autocorrect = true
29
28
  @lint_mode = false
@@ -63,7 +62,7 @@ module RuboCop
63
62
  document_encoding,
64
63
  offense,
65
64
  path,
66
- @cop_registry[offense.cop_name]&.first,
65
+ RuboCop::Cop::Registry.global.find_by_cop_name(offense.cop_name),
67
66
  processed_source
68
67
  ).to_lsp_diagnostic(config)
69
68
  end