rubocop 1.86.0 → 1.86.2

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +14 -3
  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 +4 -7
  8. data/lib/rubocop/comment_config.rb +12 -15
  9. data/lib/rubocop/config.rb +13 -9
  10. data/lib/rubocop/config_loader_resolver.rb +2 -1
  11. data/lib/rubocop/cop/autocorrect_logic.rb +2 -1
  12. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +1 -5
  13. data/lib/rubocop/cop/correctors.rb +28 -0
  14. data/lib/rubocop/cop/documentation.rb +2 -3
  15. data/lib/rubocop/cop/exclude_limit.rb +31 -5
  16. data/lib/rubocop/cop/gemspec/require_mfa.rb +3 -3
  17. data/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb +1 -0
  18. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -1
  19. data/lib/rubocop/cop/layout/end_alignment.rb +4 -2
  20. data/lib/rubocop/cop/layout/line_length.rb +5 -3
  21. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +1 -1
  22. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +26 -1
  23. data/lib/rubocop/cop/lint/duplicate_methods.rb +10 -5
  24. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +3 -13
  25. data/lib/rubocop/cop/lint/require_relative_self_path.rb +2 -0
  26. data/lib/rubocop/cop/lint/syntax.rb +25 -1
  27. data/lib/rubocop/cop/lint/unused_method_argument.rb +10 -0
  28. data/lib/rubocop/cop/lint/useless_assignment.rb +3 -8
  29. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +33 -8
  30. data/lib/rubocop/cop/mixin/configurable_max.rb +6 -5
  31. data/lib/rubocop/cop/mixin.rb +85 -0
  32. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +1 -1
  33. data/lib/rubocop/cop/offense.rb +8 -0
  34. data/lib/rubocop/cop/registry.rb +19 -24
  35. data/lib/rubocop/cop/style/access_modifier_declarations.rb +14 -2
  36. data/lib/rubocop/cop/style/copyright.rb +21 -10
  37. data/lib/rubocop/cop/style/date_time.rb +2 -2
  38. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +6 -1
  39. data/lib/rubocop/cop/style/guard_clause.rb +9 -6
  40. data/lib/rubocop/cop/style/hash_lookup_method.rb +12 -7
  41. data/lib/rubocop/cop/style/if_inside_else.rb +16 -7
  42. data/lib/rubocop/cop/style/module_member_existence_check.rb +7 -14
  43. data/lib/rubocop/cop/style/one_class_per_file.rb +24 -4
  44. data/lib/rubocop/cop/style/reduce_to_hash.rb +16 -0
  45. data/lib/rubocop/cop/style/redundant_line_continuation.rb +16 -0
  46. data/lib/rubocop/cop/style/redundant_parentheses.rb +4 -1
  47. data/lib/rubocop/cop/style/redundant_self.rb +2 -2
  48. data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +10 -0
  49. data/lib/rubocop/cop/style/regexp_literal.rb +29 -0
  50. data/lib/rubocop/cop/style/sole_nested_conditional.rb +4 -2
  51. data/lib/rubocop/cop/style/symbol_proc.rb +3 -3
  52. data/lib/rubocop/cop/style/while_until_modifier.rb +16 -0
  53. data/lib/rubocop/cop/team.rb +86 -35
  54. data/lib/rubocop/formatter/disabled_config_formatter.rb +4 -1
  55. data/lib/rubocop/lsp/runtime.rb +1 -2
  56. data/lib/rubocop/mcp/server.rb +2 -0
  57. data/lib/rubocop/options.rb +8 -4
  58. data/lib/rubocop/rspec/cop_helper.rb +8 -0
  59. data/lib/rubocop/rspec/shared_contexts.rb +21 -0
  60. data/lib/rubocop/runner.rb +77 -55
  61. data/lib/rubocop/target_finder.rb +13 -6
  62. data/lib/rubocop/version.rb +1 -1
  63. data/lib/rubocop.rb +7 -96
  64. metadata +7 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a8991e66e8594a8ba30b4122a2990df1d7f610e3e51515b016e68432bcd94855
4
- data.tar.gz: dadc1936594395ec71c4b628867d1ed2e2f585fcafedb886e5493d0b81c4cb2f
3
+ metadata.gz: dfb5d2d113d00eb380202a0e76f462318033838aa240febe88bd2861b1bf6609
4
+ data.tar.gz: 9463407310a9babcc5869f9b1db0ca273ed036a3b440b5c27438cba855bee4ec
5
5
  SHA512:
6
- metadata.gz: f95a7d048724e297f77b42444bd2eae15889bfa1b9b815fabbde131af75e99e114ab6ba2b4c0d078c937be9abd899ec7d9fb0650354b4bfd3cfb5a554f4bcfc3
7
- data.tar.gz: ed088266152e384f4c2c82dead8363ced3c990b6492a4b4652f98a548ec16b0c57da73e1295dbbc302b00c5affd3fe2e540d9c012221068449d733d1c812139c
6
+ metadata.gz: 5084465b6aa455c8ecd75e8b8f1e52f92e966d64117d2d88ad343ca535eb6fdc02f05056e6f21ac86e2344ec52cc1db2d69f8bf14a76b76bdee86a2bd5971520
7
+ data.tar.gz: 2101bc8bb5b392fa971f9a881e96b03321442b5067b4952413d806363bb89389cb2631671bfa6af0e30203800cff947d587ac0c68ef1651fdec455cc453faa18
data/config/default.yml CHANGED
@@ -529,6 +529,10 @@ Layout/ClosingParenthesisIndentation:
529
529
  Description: 'Checks the indentation of hanging closing parentheses.'
530
530
  Enabled: true
531
531
  VersionAdded: '0.49'
532
+ VersionChanged: '1.86'
533
+ # By default the indentation width from `Layout/IndentationWidth` is used,
534
+ # but it can be overridden by setting this parameter.
535
+ IndentationWidth: ~
532
536
 
533
537
  Layout/CommentIndentation:
534
538
  Description: 'Indentation of comments.'
@@ -537,7 +541,10 @@ Layout/CommentIndentation:
537
541
  # with a comment on the preceding line.
538
542
  AllowForAlignment: false
539
543
  VersionAdded: '0.49'
540
- VersionChanged: '1.24'
544
+ VersionChanged: '1.86'
545
+ # By default the indentation width from `Layout/IndentationWidth` is used,
546
+ # but it can be overridden by setting this parameter.
547
+ IndentationWidth: ~
541
548
 
542
549
  Layout/ConditionPosition:
543
550
  Description: >-
@@ -4796,7 +4803,6 @@ Style/ModuleMemberExistenceCheck:
4796
4803
  Description: 'Checks for usage of `Module` methods returning arrays that can be replaced with equivalent predicates.'
4797
4804
  Enabled: pending
4798
4805
  VersionAdded: '1.82'
4799
- AllowedMethods: []
4800
4806
 
4801
4807
  Style/MultilineBlockChain:
4802
4808
  Description: 'Avoid multi-line chains of blocks.'
@@ -5112,7 +5118,11 @@ Style/OneClassPerFile:
5112
5118
  Description: 'Checks that each source file defines at most one top-level class or module.'
5113
5119
  Enabled: pending
5114
5120
  VersionAdded: '1.85'
5121
+ VersionChanged: '1.86'
5115
5122
  AllowedClasses: []
5123
+ Exclude:
5124
+ - 'spec/**/*'
5125
+ - 'test/**/*'
5116
5126
 
5117
5127
  Style/OneLineConditional:
5118
5128
  Description: >-
@@ -5557,9 +5567,10 @@ Style/RedundantStringEscape:
5557
5567
 
5558
5568
  Style/RedundantStructKeywordInit:
5559
5569
  Description: 'Checks for redundant `keyword_init` option for `Struct.new`.'
5560
- Enabled: pending
5570
+ Enabled: false
5561
5571
  SafeAutoCorrect: false
5562
5572
  VersionAdded: '1.85'
5573
+ VersionChanged: '1.86'
5563
5574
 
5564
5575
  Style/RegexpLiteral:
5565
5576
  Description: 'Use / or %r around regular expressions.'
@@ -24,13 +24,39 @@ module RuboCop
24
24
 
25
25
  def run
26
26
  add_formatter
27
+ use_temporary_cache
28
+ reset_auto_gen_tmp_dir
27
29
  reset_config_and_auto_gen_file
28
30
  line_length_contents = maybe_run_line_length_cop
29
- run_all_cops(line_length_contents)
31
+ result = run_all_cops(line_length_contents)
32
+ reset_auto_gen_tmp_dir
33
+ result
30
34
  end
31
35
 
32
36
  private
33
37
 
38
+ def use_temporary_cache
39
+ # Use a separate cache directory to ensure MinDigits and Max values are calculated.
40
+ # This allows parallel execution (which requires cache) while ensuring MinDigits
41
+ # and Max values are computed, since the cache starts empty.
42
+ @options[:cache_root] = auto_gen_tmp_dir.join('cache').to_s
43
+ end
44
+
45
+ def reset_auto_gen_tmp_dir
46
+ auto_gen_tmp_dir.rmtree if auto_gen_tmp_dir.exist?
47
+ auto_gen_tmp_dir.mkpath
48
+ end
49
+
50
+ def auto_gen_tmp_dir
51
+ @auto_gen_tmp_dir ||= Pathname.new(
52
+ RuboCop::CacheConfig.root_dir_from_toplevel_config
53
+ ).join('auto-gen-tmp').tap do |path|
54
+ path.mkpath
55
+ # Set the temp directory path for ExcludeLimit to use
56
+ RuboCop::ExcludeLimit.tmp_dir = path
57
+ end
58
+ end
59
+
34
60
  def maybe_run_line_length_cop
35
61
  if only_exclude?
36
62
  skip_line_length_cop(PHASE_1_SKIPPED_ONLY_EXCLUDE)
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ class CLI
5
+ module Command
6
+ # Lists the cops that will inspect the given file or directory.
7
+ # @api private
8
+ class ListEnabledCopsFor < Base
9
+ self.command_name = :list_enabled_cops_for
10
+
11
+ def initialize(env)
12
+ super
13
+
14
+ # Load the configs so the require()s are done for custom cops
15
+ @config = @config_store.for(@options[:list_enabled_cops_for])
16
+ end
17
+
18
+ def run
19
+ print_available_cops
20
+ end
21
+
22
+ private
23
+
24
+ def print_available_cops
25
+ registry = Cop::Registry.global
26
+
27
+ registry.departments.sort.each do |department|
28
+ puts cops_of_department(registry, department).sort
29
+ end
30
+ end
31
+
32
+ def cops_of_department(registry, department)
33
+ registry.with_department(department)
34
+ .map(&:cop_name)
35
+ .select { |cop_name| @config.cop_enabled?(cop_name) }
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -25,10 +25,10 @@ module RuboCop
25
25
  puts Cop::Documentation.default_base_url if cops_array.empty?
26
26
 
27
27
  cops_array.each do |cop_name|
28
- cop = registry_hash[cop_name]
29
- next if cop.empty?
28
+ cop = Cop::Registry.global.find_by_cop_name(cop_name)
29
+ next unless cop
30
30
 
31
- url = Cop::Documentation.url_for(cop.first, @config)
31
+ url = Cop::Documentation.url_for(cop, @config)
32
32
  puts url if url
33
33
  end
34
34
 
@@ -38,10 +38,6 @@ module RuboCop
38
38
  def cops_array
39
39
  @cops_array ||= @options[:show_docs_url]
40
40
  end
41
-
42
- def registry_hash
43
- @registry_hash ||= Cop::Registry.global.to_h
44
- end
45
41
  end
46
42
  end
47
43
  end
@@ -60,7 +60,7 @@ module RuboCop
60
60
  def print_opt_out_instruction
61
61
  puts
62
62
  puts 'You can opt out of this message by adding the following to your config ' \
63
- '(see https://docs.rubocop.org/rubocop/extensions.html#extension-suggestions ' \
63
+ '(see https://docs.rubocop.org/rubocop/plugins.html#plugin-suggestions ' \
64
64
  'for more options):'
65
65
  puts ' AllCops:'
66
66
  puts ' SuggestExtensions: false'
data/lib/rubocop/cli.rb CHANGED
@@ -13,7 +13,7 @@ module RuboCop
13
13
  DEFAULT_PARALLEL_OPTIONS = %i[
14
14
  color config debug display_style_guide display_time display_only_fail_level_offenses
15
15
  display_only_failed editor_mode except extra_details fail_level fix_layout format formatters
16
- ignore_disable_comments lint only only_guide_cops require safe
16
+ ignore_disable_comments lint only only_guide_cops out require safe
17
17
  autocorrect safe_autocorrect autocorrect_all
18
18
  ].freeze
19
19
 
@@ -200,18 +200,15 @@ module RuboCop
200
200
  RuboCop::LSP.enable if @options[:editor_mode]
201
201
  end
202
202
 
203
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
204
203
  def handle_exiting_options
205
204
  return unless Options::EXITING_OPTIONS.any? { |o| @options.key? o }
206
205
 
207
206
  run_command(:version) if @options[:version] || @options[:verbose_version]
208
- run_command(:show_cops) if @options[:show_cops]
209
- run_command(:show_docs_url) if @options[:show_docs_url]
210
- run_command(:lsp) if @options[:lsp]
211
- run_command(:mcp) if @options[:mcp]
207
+ %i[show_cops list_enabled_cops_for show_docs_url lsp mcp].each do |name|
208
+ run_command(name) if @options[name]
209
+ end
212
210
  raise Finished
213
211
  end
214
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
215
212
 
216
213
  def apply_default_formatter
217
214
  # This must be done after the options have already been processed,
@@ -95,7 +95,8 @@ module RuboCop
95
95
  end
96
96
  end
97
97
 
98
- def analyze # rubocop:todo Metrics/AbcSize, Metrics/MethodLength
98
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
99
+ def analyze
99
100
  return {} if @no_directives
100
101
 
101
102
  analyses = Hash.new { |hash, key| hash[key] = CopAnalysis.new([], nil) }
@@ -103,9 +104,9 @@ module RuboCop
103
104
 
104
105
  each_directive do |directive|
105
106
  if directive.push?
106
- resolved = resolve_push_cops(directive)
107
- @stack.push(snapshot_cops(analyses, resolved.values.flatten))
108
- apply_push(analyses, resolved, directive.line_number)
107
+ restore_point = analyses.transform_values(&:dup)
108
+ @stack.push(restore_point)
109
+ apply_push(analyses, resolve_push_cops(directive), directive.line_number)
109
110
  elsif directive.pop?
110
111
  pop_state(analyses, directive.line_number) if @stack.any?
111
112
  else
@@ -121,10 +122,7 @@ module RuboCop
121
122
  hash[cop_name] = cop_line_ranges(analysis)
122
123
  end
123
124
  end
124
-
125
- def snapshot_cops(analyses, cop_names)
126
- cop_names.to_h { |name| [name, analyses[name].dup] }
127
- end
125
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
128
126
 
129
127
  def resolve_push_cops(directive)
130
128
  directive.push_args.transform_values do |names|
@@ -155,13 +153,12 @@ module RuboCop
155
153
  end
156
154
 
157
155
  def pop_state(analyses, line)
158
- saved = @stack.pop
159
- saved.each do |cop, old|
160
- cur = analyses[cop]
161
- new_range = cur.start_line_number ? [cur.start_line_number..(line - 1)] : []
162
- ranges = cur.line_ranges + new_range
163
- new_start = old.start_line_number ? line : nil
164
- analyses[cop] = CopAnalysis.new(ranges, new_start)
156
+ restore_point = @stack.pop
157
+ (restore_point.keys | analyses.keys).each do |cop|
158
+ current = analyses[cop]
159
+ new_range = current.start_line_number ? [current.start_line_number..(line - 1)] : []
160
+ new_start = restore_point[cop]&.start_line_number ? line : nil
161
+ analyses[cop] = CopAnalysis.new(current.line_ranges + new_range, new_start)
165
162
  end
166
163
  end
167
164
 
@@ -217,6 +217,9 @@ module RuboCop
217
217
  for_all_cops['StringLiteralsFrozenByDefault']
218
218
  end
219
219
 
220
+ # Returns true if the file matches any include pattern. If a block is given, the block is called
221
+ # to determine if the pattern is relevant (true returned by the block) or should be skipped
222
+ # (false returned).
220
223
  def file_to_include?(file)
221
224
  relative_file_path = path_relative_to_config(file)
222
225
 
@@ -228,11 +231,9 @@ module RuboCop
228
231
  absolute_file_path = File.expand_path(file)
229
232
 
230
233
  patterns_to_include.any? do |pattern|
231
- if block_given?
232
- yield pattern, relative_file_path, absolute_file_path
233
- else
234
- match_path?(pattern, relative_file_path) || match_path?(pattern, absolute_file_path)
235
- end
234
+ next if block_given? && !yield(pattern)
235
+
236
+ match_relative_or_absolute_path?(pattern, relative_file_path, absolute_file_path)
236
237
  end
237
238
  end
238
239
 
@@ -242,10 +243,7 @@ module RuboCop
242
243
  # `bundler-console` conveys `Bundler::Console`).
243
244
  return true if File.extname(file) == '.gemspec'
244
245
 
245
- file_to_include?(file) do |pattern, relative_path, absolute_path|
246
- /[A-Z]/.match?(pattern.to_s) &&
247
- (match_path?(pattern, relative_path) || match_path?(pattern, absolute_path))
248
- end
246
+ file_to_include?(file) { |pattern| /[A-Z]/.match?(pattern.to_s) }
249
247
  end
250
248
 
251
249
  # Returns true if there's a chance that an Include pattern matches hidden
@@ -345,6 +343,12 @@ module RuboCop
345
343
 
346
344
  private
347
345
 
346
+ def match_relative_or_absolute_path?(pattern, relative_file_path, absolute_file_path)
347
+ should_use_absolute_path = absolute?(pattern.to_s) || pattern.to_s.start_with?('..') ||
348
+ relative_file_path.start_with?('..')
349
+ match_path?(pattern, should_use_absolute_path ? absolute_file_path : relative_file_path)
350
+ end
351
+
348
352
  # @return [Float, nil] The Rails version as a `major.minor` Float.
349
353
  def target_rails_version_from_bundler_lock_file
350
354
  @target_rails_version_from_bundler_lock_file ||= read_rails_version_from_bundler_lock_file
@@ -45,6 +45,7 @@ module RuboCop
45
45
  base_config.each do |k, v|
46
46
  next unless v.is_a?(Hash)
47
47
 
48
+ only_base_has_include = v.key?('Include') && !hash.dig(k, 'Include')
48
49
  if hash.key?(k)
49
50
  v = merge(v, hash[k],
50
51
  cop_name: k, file: file, debug: debug,
@@ -52,7 +53,7 @@ module RuboCop
52
53
  inherit_mode: determine_inherit_mode(hash, k))
53
54
  end
54
55
  hash[k] = v
55
- fix_include_paths(base_config.loaded_path, hash, path, k, v) if v.key?('Include')
56
+ fix_include_paths(base_config.loaded_path, hash, path, k, v) if only_base_has_include
56
57
  end
57
58
  end
58
59
  end
@@ -50,7 +50,8 @@ module RuboCop
50
50
 
51
51
  def disable_offense(offense_range)
52
52
  unbreakable_range = multiline_ranges(offense_range)&.find do |range|
53
- eol_comment_would_be_inside_literal?(offense_range, range)
53
+ offense_range.overlaps?(range) &&
54
+ eol_comment_would_be_inside_literal?(offense_range, range)
54
55
  end
55
56
 
56
57
  if unbreakable_range
@@ -59,11 +59,7 @@ module RuboCop
59
59
  end
60
60
 
61
61
  def content_if_comment_present(corrector, node)
62
- range = range_with_surrounding_space(
63
- children(node).last.source_range,
64
- side: :right
65
- ).end.resize(1)
66
- if range.source == '#'
62
+ if processed_source.comment_at_line(children(node).last.last_line)
67
63
  select_content_to_be_inserted_after_last_element(corrector, node)
68
64
  else
69
65
  node.loc.end.source
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop # rubocop:disable Style/Documentation
5
+ # Autoloads corrector classes used by cops. Classes are autoloaded to reduce the number of
6
+ # required classes because they're referenced only when autocorrection is performed.
7
+
8
+ # rubocop:disable Layout/LineLength
9
+ autoload :AlignmentCorrector, 'rubocop/cop/correctors/alignment_corrector'
10
+ autoload :ConditionCorrector, 'rubocop/cop/correctors/condition_corrector'
11
+ autoload :EachToForCorrector, 'rubocop/cop/correctors/each_to_for_corrector'
12
+ autoload :EmptyLineCorrector, 'rubocop/cop/correctors/empty_line_corrector'
13
+ autoload :ForToEachCorrector, 'rubocop/cop/correctors/for_to_each_corrector'
14
+ autoload :IfThenCorrector, 'rubocop/cop/correctors/if_then_corrector'
15
+ autoload :LambdaLiteralToMethodCorrector, 'rubocop/cop/correctors/lambda_literal_to_method_corrector'
16
+ autoload :LineBreakCorrector, 'rubocop/cop/correctors/line_break_corrector'
17
+ autoload :MultilineLiteralBraceCorrector, 'rubocop/cop/correctors/multiline_literal_brace_corrector'
18
+ autoload :OrderedGemCorrector, 'rubocop/cop/correctors/ordered_gem_corrector'
19
+ autoload :ParenthesesCorrector, 'rubocop/cop/correctors/parentheses_corrector'
20
+ autoload :PercentLiteralCorrector, 'rubocop/cop/correctors/percent_literal_corrector'
21
+ autoload :PunctuationCorrector, 'rubocop/cop/correctors/punctuation_corrector'
22
+ autoload :RequireLibraryCorrector, 'rubocop/cop/correctors/require_library_corrector'
23
+ autoload :SpaceCorrector, 'rubocop/cop/correctors/space_corrector'
24
+ autoload :StringLiteralCorrector, 'rubocop/cop/correctors/string_literal_corrector'
25
+ autoload :UnusedArgCorrector, 'rubocop/cop/correctors/unused_arg_corrector'
26
+ # rubocop:enable Layout/LineLength
27
+ end
28
+ end
@@ -55,10 +55,9 @@ module RuboCop
55
55
 
56
56
  # @api private
57
57
  def builtin?(cop_class)
58
- # any custom method will do
59
- return false unless (m = cop_class.instance_methods(false).first)
58
+ return false unless (name = cop_class.name)
60
59
 
61
- path, _line = cop_class.instance_method(m).source_location
60
+ path, _line = Module.const_source_location(name)
62
61
  path.start_with?(__dir__)
63
62
  end
64
63
  end
@@ -4,16 +4,42 @@ module RuboCop
4
4
  # Allows specified configuration options to have an exclude limit
5
5
  # ie. a maximum value tracked that it can be used by `--auto-gen-config`.
6
6
  module ExcludeLimit
7
+ class << self
8
+ attr_accessor :tmp_dir
9
+
10
+ # Reads the aggregated exclude limit values for a cop from tmp files.
11
+ # Returns a hash like { 'Max' => 81 } or an empty hash if no values were written.
12
+ def read_limits(cop_name)
13
+ cop_dir = cop_dir_for(cop_name)
14
+ return {} unless cop_dir&.directory?
15
+
16
+ limits = {}
17
+ cop_dir.children.each do |filepath|
18
+ next unless filepath.file?
19
+
20
+ values = filepath.readlines.map(&:to_i)
21
+ limits[filepath.basename.to_s] = values.max unless values.empty?
22
+ end
23
+ limits
24
+ end
25
+
26
+ # Returns the tmp directory path for a given cop, or nil if tmp_dir is not set.
27
+ def cop_dir_for(cop_name)
28
+ tmp_dir&.join(cop_name.tr('/', '-'))
29
+ end
30
+ end
31
+
7
32
  # Sets up a configuration option to have an exclude limit tracked.
8
33
  # The parameter name given is transformed into a method name (eg. `Max`
9
34
  # becomes `self.max=` and `MinDigits` becomes `self.min_digits=`).
10
35
  def exclude_limit(parameter_name, method_name: transform(parameter_name))
11
36
  define_method(:"#{method_name}=") do |value|
12
- cfg = config_to_allow_offenses
13
- cfg[:exclude_limit] ||= {}
14
- current_max = cfg[:exclude_limit][parameter_name]
15
- value = [current_max, value].max if current_max
16
- cfg[:exclude_limit][parameter_name] = value
37
+ cop_dir = RuboCop::ExcludeLimit.cop_dir_for(self.class.badge.to_s)
38
+ return unless cop_dir
39
+
40
+ cop_dir.mkpath
41
+ filepath = cop_dir.join(parameter_name)
42
+ filepath.write("#{value}\n", mode: 'a')
17
43
  end
18
44
  end
19
45
 
@@ -70,7 +70,7 @@ module RuboCop
70
70
  def_node_matcher :metadata, <<~PATTERN
71
71
  `{
72
72
  (send _ :metadata= $_)
73
- (send (send _ :metadata) :[]= (str "rubygems_mfa_required") $_)
73
+ (send (send _ :metadata) :[]= {(str "rubygems_mfa_required") (sym :rubygems_mfa_required)} $_)
74
74
  }
75
75
  PATTERN
76
76
 
@@ -78,13 +78,13 @@ module RuboCop
78
78
  def_node_search :metadata_assignment, <<~PATTERN
79
79
  `{
80
80
  (send _ :metadata= _)
81
- (send (send _ :metadata) :[]= (str _) _)
81
+ (send (send _ :metadata) :[]= {str sym} _)
82
82
  }
83
83
  PATTERN
84
84
 
85
85
  # @!method rubygems_mfa_required(node)
86
86
  def_node_search :rubygems_mfa_required, <<~PATTERN
87
- (pair (str "rubygems_mfa_required") $_)
87
+ (pair {(str "rubygems_mfa_required") (sym :rubygems_mfa_required)} $_)
88
88
  PATTERN
89
89
 
90
90
  # @!method true_string?(node)
@@ -25,6 +25,7 @@ module RuboCop
25
25
  def_node_matcher :line_send, <<~PATTERN
26
26
  {
27
27
  (send (send _ {:loc :source_range}) {:line :first_line})
28
+ (send (send (send _ :loc) {:begin :end}) :line)
28
29
  (send _ :first_line)
29
30
  }
30
31
  PATTERN
@@ -111,7 +111,7 @@ module RuboCop
111
111
  def contains_guard_clause?(node)
112
112
  return false unless (branch = node.if_branch)
113
113
 
114
- guard_clause_branch?(branch)
114
+ branch.guard_clause? || guard_clause_branch?(branch)
115
115
  end
116
116
 
117
117
  def next_line_empty_or_allowed_directive_comment?(line)
@@ -123,6 +123,7 @@ module RuboCop
123
123
  AlignmentCorrector.align_end(corrector, processed_source, node, alignment_node(node))
124
124
  end
125
125
 
126
+ # rubocop:disable Metrics/CyclomaticComplexity
126
127
  def check_assignment(node, rhs)
127
128
  # If there are method calls chained to the right hand side of the
128
129
  # assignment, we let rhs be the receiver of those method calls before
@@ -131,13 +132,14 @@ module RuboCop
131
132
 
132
133
  # If `rhs` is a `begin` node or a logical operator,
133
134
  # unwrap to find the leading conditional.
134
- rhs = rhs.child_nodes.first while rhs.type?(:begin, :or, :and)
135
+ rhs = rhs.child_nodes.first while rhs&.type?(:begin, :or, :and)
135
136
 
136
- return unless rhs.conditional?
137
+ return unless rhs&.conditional?
137
138
  return if rhs.if_type? && rhs.ternary?
138
139
 
139
140
  check_asgn_alignment(node, rhs)
140
141
  end
142
+ # rubocop:enable Metrics/CyclomaticComplexity
141
143
 
142
144
  def check_asgn_alignment(outer_node, inner_node)
143
145
  align_with = {
@@ -406,9 +406,11 @@ module RuboCop
406
406
  end
407
407
 
408
408
  def string_delimiter(node)
409
- delimiter = node.loc.begin
410
- delimiter ||= node.parent.loc.begin if node.parent&.dstr_type? && node.parent.loc?(:begin)
411
- delimiter = delimiter&.source
409
+ delimiter = if node.loc?(:begin)
410
+ node.loc.begin
411
+ elsif node.parent&.dstr_type? && node.parent.loc?(:begin)
412
+ node.parent.loc.begin
413
+ end&.source
412
414
 
413
415
  delimiter if %w[' "].include?(delimiter)
414
416
  end
@@ -124,7 +124,7 @@ module RuboCop
124
124
  def single_line_ignoring_receiver?(node)
125
125
  return false unless node.loc.begin && node.loc.end
126
126
 
127
- node.loc.begin.line == node.loc.end.line
127
+ same_line?(node.loc.begin, node.loc.end)
128
128
  end
129
129
  end
130
130
  end
@@ -158,7 +158,7 @@ module RuboCop
158
158
  @base = find_hash_pair_alignment_base(node)
159
159
  return false if !@base && inside_multiline_chain_arg?(node)
160
160
 
161
- @base ||= lhs.source_range
161
+ @base ||= first_dot_alignment_base(node, rhs) || lhs.source_range
162
162
  return if aligned_with_first_line_dot?(node, rhs)
163
163
 
164
164
  calculate_column_delta_offense(rhs, @base.column)
@@ -172,6 +172,31 @@ module RuboCop
172
172
  first_call.loc.dot.join(first_call.loc.selector)
173
173
  end
174
174
 
175
+ def first_dot_alignment_base(node, rhs)
176
+ return unless rhs.source.start_with?('.', '&.')
177
+
178
+ first_call = first_call_has_a_dot(node)
179
+ dot = first_call.loc.dot
180
+ return unless dot
181
+ return if first_call == node
182
+
183
+ after_block_base = after_multiline_block_base(first_call, node)
184
+ return after_block_base if after_block_base
185
+
186
+ return unless same_line?(dot, first_call.receiver.source_range)
187
+
188
+ dot.join(first_call.loc.selector)
189
+ end
190
+
191
+ def after_multiline_block_base(first_call, node)
192
+ return unless first_call.block_node&.multiline?
193
+
194
+ after_block = first_call.block_node.parent
195
+ return unless after_block&.call_type? && after_block.loc?(:dot) && after_block != node
196
+
197
+ after_block.loc.dot.join(after_block.loc.selector)
198
+ end
199
+
175
200
  def inside_multiline_chain_arg?(node)
176
201
  enclosing_call = find_enclosing_chain_call(node)
177
202
  return false unless enclosing_call
@@ -308,7 +308,7 @@ module RuboCop
308
308
  def anonymous_class_block(node)
309
309
  first_block = node.each_ancestor(:block).first
310
310
  return unless class_or_module_new_block?(first_block)
311
- return if first_block.parent&.type?(:lvasgn, :block)
311
+ return if first_block.parent&.type?(:lvasgn)
312
312
  return if node.each_ancestor(:sclass).any? { |s| !s.children.first.self_type? }
313
313
 
314
314
  first_block
@@ -316,15 +316,20 @@ module RuboCop
316
316
 
317
317
  def anon_block_scope_id(anon_block)
318
318
  parent = anon_block.parent
319
- return unless parent&.call_type?
319
+ return unless parent&.type?(:any_block, :begin, :call, :casgn, :any_def)
320
320
 
321
- if parent.receiver
322
- "#{parent.receiver.source}.#{parent.method_name}"
323
- else
321
+ if (receiver = named_receiver(parent))
322
+ "#{receiver.source}.#{parent.method_name}"
323
+ elsif !parent.begin_type? || parent.parent&.any_block_type?
324
324
  source_location(anon_block)
325
325
  end
326
326
  end
327
327
 
328
+ def named_receiver(node)
329
+ receiver = node.receiver
330
+ receiver unless class_or_module_new_block?(receiver)
331
+ end
332
+
328
333
  def found_sclass_method(node, name)
329
334
  singleton_ancestor = node.each_ancestor.find(&:sclass_type?)
330
335
  return unless singleton_ancestor
@@ -63,19 +63,9 @@ module RuboCop
63
63
  end
64
64
 
65
65
  def spaces_before_left_parenthesis(node)
66
- receiver = node.receiver
67
- receiver_length = if receiver
68
- receiver.source.length
69
- else
70
- 0
71
- end
72
- without_receiver = node.source[receiver_length..]
73
-
74
- # Escape question mark if any.
75
- method_regexp = Regexp.escape(node.method_name)
76
-
77
- match = without_receiver.match(/^\s*&?\.?\s*#{method_regexp}(\s+)\(/)
78
- match ? match.captures[0].length : 0
66
+ return 0 if node.parenthesized?
67
+
68
+ node.first_argument.source_range.begin_pos - node.loc.selector.end_pos
79
69
  end
80
70
 
81
71
  def space_range(expr, space_length)