rubocop 1.62.1 → 1.63.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +22 -3
  4. data/lib/rubocop/cached_data.rb +11 -3
  5. data/lib/rubocop/config.rb +33 -10
  6. data/lib/rubocop/config_obsoletion.rb +1 -1
  7. data/lib/rubocop/cop/base.rb +40 -1
  8. data/lib/rubocop/cop/internal_affairs/example_description.rb +2 -1
  9. data/lib/rubocop/cop/lint/assignment_in_condition.rb +2 -2
  10. data/lib/rubocop/cop/lint/debugger.rb +27 -2
  11. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +1 -1
  12. data/lib/rubocop/cop/lint/empty_conditional_body.rb +1 -1
  13. data/lib/rubocop/cop/lint/mixed_case_range.rb +9 -4
  14. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +1 -1
  15. data/lib/rubocop/cop/lint/redundant_with_index.rb +3 -0
  16. data/lib/rubocop/cop/lint/unreachable_code.rb +4 -2
  17. data/lib/rubocop/cop/lint/unreachable_loop.rb +8 -2
  18. data/lib/rubocop/cop/mixin/code_length.rb +12 -1
  19. data/lib/rubocop/cop/mixin/method_complexity.rb +15 -6
  20. data/lib/rubocop/cop/mixin/safe_assignment.rb +1 -1
  21. data/lib/rubocop/cop/naming/block_forwarding.rb +31 -12
  22. data/lib/rubocop/cop/naming/file_name.rb +2 -2
  23. data/lib/rubocop/cop/naming/inclusive_language.rb +1 -2
  24. data/lib/rubocop/cop/style/alias.rb +1 -0
  25. data/lib/rubocop/cop/style/arguments_forwarding.rb +2 -1
  26. data/lib/rubocop/cop/style/collection_compact.rb +3 -3
  27. data/lib/rubocop/cop/style/copyright.rb +16 -11
  28. data/lib/rubocop/cop/style/eval_with_location.rb +3 -1
  29. data/lib/rubocop/cop/style/exact_regexp_match.rb +2 -1
  30. data/lib/rubocop/cop/style/format_string.rb +9 -9
  31. data/lib/rubocop/cop/style/map_into_array.rb +175 -0
  32. data/lib/rubocop/cop/style/map_to_hash.rb +1 -1
  33. data/lib/rubocop/cop/style/map_to_set.rb +1 -1
  34. data/lib/rubocop/cop/style/redundant_argument.rb +24 -1
  35. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +1 -1
  36. data/lib/rubocop/cop/style/redundant_each.rb +1 -1
  37. data/lib/rubocop/cop/style/redundant_filter_chain.rb +1 -1
  38. data/lib/rubocop/cop/style/redundant_line_continuation.rb +8 -14
  39. data/lib/rubocop/cop/style/redundant_percent_q.rb +1 -1
  40. data/lib/rubocop/cop/style/require_order.rb +1 -1
  41. data/lib/rubocop/cop/style/send.rb +4 -4
  42. data/lib/rubocop/cop/style/top_level_method_definition.rb +1 -1
  43. data/lib/rubocop/cop/team.rb +3 -0
  44. data/lib/rubocop/formatter/clang_style_formatter.rb +3 -7
  45. data/lib/rubocop/formatter/tap_formatter.rb +3 -7
  46. data/lib/rubocop/lockfile.rb +44 -7
  47. data/lib/rubocop/lsp/server.rb +2 -0
  48. data/lib/rubocop/rspec/expect_offense.rb +8 -0
  49. data/lib/rubocop/rspec/shared_contexts.rb +13 -1
  50. data/lib/rubocop/runner.rb +3 -0
  51. data/lib/rubocop/version.rb +2 -2
  52. data/lib/rubocop.rb +1 -0
  53. metadata +5 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8daa4ef7dd74b8608039d7dfda5f1e3fe739738949390a6f1347acaccf7bd1c4
4
- data.tar.gz: 271a978db956def596a5b06a7fd69d189d807b27db0acdda03fd6200b95ef541
3
+ metadata.gz: 280ae6904ade12bb288a774c260e7a51c2e3152446d82415f3839f41b15d83a1
4
+ data.tar.gz: 48b8fce72325a97238428955dde6d3dbcafacbb2b5ea5952808d088003b2a6b8
5
5
  SHA512:
6
- metadata.gz: 484294f43781f114ba01470b22331ef17648219e26dc4ba2f28073f609c909af2997c47e1f1aad62860124f6f4594a2e536cd793f05352a78d588f979732a290
7
- data.tar.gz: 6a8313e61422bcd568eea747c0f9a88f61f50620b4dd4f478da0bdd3e067c1aa8bcf70e15744e02118d4c525eb0632fae3cc19caf73db6db1f63ea76dbb84b45
6
+ metadata.gz: e9cc268aefd0aefd13db5ca31865aaf1298639da77fa3c36fba425d980c36b209550cdd1dddfd68d644c50665a33cb6908512434a25639319c41ea266015409c
7
+ data.tar.gz: f3b64a7ed84536a42546ef6bd8a4f6ebcb8b9402fbe01a24421ac5f3aee9efa2f4e66ce1563caf0ca2504f265fecf9c4b27e5444c20e5a2bd6803980996d39b8
data/README.md CHANGED
@@ -53,7 +53,7 @@ To prevent an unwanted RuboCop update you might want to use a conservative versi
53
53
  in your `Gemfile`:
54
54
 
55
55
  ```rb
56
- gem 'rubocop', '~> 1.62', require: false
56
+ gem 'rubocop', '~> 1.63', require: false
57
57
  ```
58
58
 
59
59
  See [our versioning policy](https://docs.rubocop.org/rubocop/versioning.html) for further details.
data/config/default.yml CHANGED
@@ -156,13 +156,14 @@ AllCops:
156
156
  # included.
157
157
  SuggestExtensions:
158
158
  rubocop-rails: [rails]
159
- rubocop-rspec: [rspec, rspec-rails]
159
+ rubocop-rspec: [rspec]
160
160
  rubocop-minitest: [minitest]
161
161
  rubocop-sequel: [sequel]
162
162
  rubocop-rake: [rake]
163
163
  rubocop-graphql: [graphql]
164
164
  rubocop-capybara: [capybara]
165
165
  rubocop-factory_bot: [factory_bot, factory_bot_rails]
166
+ rubocop-rspec_rails: [rspec-rails]
166
167
  # Enable/Disable checking the methods extended by Active Support.
167
168
  ActiveSupportExtensionsEnabled: false
168
169
 
@@ -1657,7 +1658,7 @@ Lint/Debugger:
1657
1658
  Description: 'Check for debugger calls.'
1658
1659
  Enabled: true
1659
1660
  VersionAdded: '0.14'
1660
- VersionChanged: '1.46'
1661
+ VersionChanged: '1.63'
1661
1662
  DebuggerMethods:
1662
1663
  # Groups are available so that a specific group can be disabled in
1663
1664
  # a user's configuration, but are otherwise not significant.
@@ -1670,8 +1671,14 @@ Lint/Debugger:
1670
1671
  - Kernel.byebug
1671
1672
  - Kernel.remote_byebug
1672
1673
  Capybara:
1674
+ - page.save_and_open_page
1675
+ - page.save_and_open_screenshot
1676
+ - page.save_page
1677
+ - page.save_screenshot
1673
1678
  - save_and_open_page
1674
1679
  - save_and_open_screenshot
1680
+ - save_page
1681
+ - save_screenshot
1675
1682
  debug.rb:
1676
1683
  - binding.b
1677
1684
  - binding.break
@@ -1693,6 +1700,10 @@ Lint/Debugger:
1693
1700
  - jard
1694
1701
  WebConsole:
1695
1702
  - binding.console
1703
+ DebuggerRequires:
1704
+ debug.rb:
1705
+ - debug/open
1706
+ - debug/start
1696
1707
 
1697
1708
  Lint/DeprecatedClassMethods:
1698
1709
  Description: 'Check for deprecated class method calls.'
@@ -2762,7 +2773,8 @@ Naming/FileName:
2762
2773
  VersionChanged: '1.23'
2763
2774
  # Camel case file names listed in `AllCops:Include` and all file names listed
2764
2775
  # in `AllCops:Exclude` are excluded by default. Add extra excludes here.
2765
- Exclude: []
2776
+ Exclude:
2777
+ - Rakefile.rb
2766
2778
  # When `true`, requires that each source file should define a class or module
2767
2779
  # with a name which matches the file name (converted to ... case).
2768
2780
  # It further expects it to be nested inside modules which match the names
@@ -4276,6 +4288,13 @@ Style/MapCompactWithConditionalBlock:
4276
4288
  Enabled: pending
4277
4289
  VersionAdded: '1.30'
4278
4290
 
4291
+ Style/MapIntoArray:
4292
+ Description: 'Checks for usages of `each` with `<<`, `push`, or `append` which can be replaced by `map`.'
4293
+ StyleGuide: '#functional-code'
4294
+ Enabled: pending
4295
+ VersionAdded: '1.63'
4296
+ Safe: false
4297
+
4279
4298
  Style/MapToHash:
4280
4299
  Description: 'Prefer `to_h` with a block over `map.to_h`.'
4281
4300
  Enabled: pending
@@ -48,11 +48,19 @@ module RuboCop
48
48
  source_buffer = Parser::Source::Buffer.new(@filename)
49
49
  source_buffer.source = File.read(@filename, encoding: Encoding::UTF_8)
50
50
  offenses.map! do |o|
51
- location = Parser::Source::Range.new(source_buffer,
52
- o['location']['begin_pos'],
53
- o['location']['end_pos'])
51
+ location = location_from_source_buffer(o, source_buffer)
54
52
  Cop::Offense.new(o['severity'], location, o['message'], o['cop_name'], o['status'].to_sym)
55
53
  end
56
54
  end
55
+
56
+ def location_from_source_buffer(offense, source_buffer)
57
+ begin_pos = offense['location']['begin_pos']
58
+ end_pos = offense['location']['end_pos']
59
+ if begin_pos.zero? && end_pos.zero?
60
+ Cop::Offense::NO_LOCATION
61
+ else
62
+ Parser::Source::Range.new(source_buffer, begin_pos, end_pos)
63
+ end
64
+ end
57
65
  end
58
66
  end
@@ -263,6 +263,7 @@ module RuboCop
263
263
  PathUtil.smart_path(@loaded_path)
264
264
  end
265
265
 
266
+ # @return [String, nil]
266
267
  def bundler_lock_file_path
267
268
  return nil unless loaded_path
268
269
 
@@ -286,27 +287,49 @@ module RuboCop
286
287
  end
287
288
  end
288
289
 
290
+ # Returns target's locked gem versions (i.e. from Gemfile.lock or gems.locked)
291
+ # @returns [Hash{String => Gem::Version}] The locked gem versions, keyed by the gems' names.
292
+ def gem_versions_in_target
293
+ @gem_versions_in_target ||= read_gem_versions_from_target_lockfile
294
+ end
295
+
289
296
  def inspect # :nodoc:
290
297
  "#<#{self.class.name}:#{object_id} @loaded_path=#{loaded_path}>"
291
298
  end
292
299
 
293
300
  private
294
301
 
302
+ # @return [Float, nil] The Rails version as a `major.minor` Float.
295
303
  def target_rails_version_from_bundler_lock_file
296
304
  @target_rails_version_from_bundler_lock_file ||= read_rails_version_from_bundler_lock_file
297
305
  end
298
306
 
307
+ # @return [Float, nil] The Rails version as a `major.minor` Float.
299
308
  def read_rails_version_from_bundler_lock_file
300
- lock_file_path = bundler_lock_file_path
301
- return nil unless lock_file_path
302
-
303
- File.foreach(lock_file_path) do |line|
304
- # If Rails (or one of its frameworks) is in Gemfile.lock or gems.lock, there should be
305
- # a line like:
306
- # railties (X.X.X)
307
- result = line.match(/^\s+railties\s+\((\d+\.\d+)/)
308
- return result.captures.first.to_f if result
309
- end
309
+ return nil unless gem_versions_in_target
310
+
311
+ # Look for `railties` instead of `rails`, to support apps that only use a subset of `rails`
312
+ # See https://github.com/rubocop/rubocop/pull/11289
313
+ rails_version_in_target = gem_versions_in_target['railties']
314
+ return nil unless rails_version_in_target
315
+
316
+ gem_version_to_major_minor_float(rails_version_in_target)
317
+ end
318
+
319
+ # @param [Gem::Version] gem_version an object like `Gem::Version.new("7.1.2.3")`
320
+ # @return [Float] The major and minor version, like `7.1`
321
+ def gem_version_to_major_minor_float(gem_version)
322
+ segments = gem_version.canonical_segments
323
+ # segments.fetch(0).to_f + (segments.fetch(1, 0.0).to_f / 10)
324
+ Float("#{segments.fetch(0)}.#{segments.fetch(1, 0)}")
325
+ end
326
+
327
+ # @returns [Hash{String => Gem::Version}] The locked gem versions, keyed by the gems' names.
328
+ def read_gem_versions_from_target_lockfile
329
+ lockfile_path = bundler_lock_file_path
330
+ return nil unless lockfile_path
331
+
332
+ Lockfile.new(lockfile_path).gem_versions
310
333
  end
311
334
 
312
335
  def enable_cop?(qualified_cop_name, cop_options)
@@ -52,7 +52,7 @@ module RuboCop
52
52
  def load_rules # rubocop:disable Metrics/AbcSize
53
53
  rules = LOAD_RULES_CACHE[self.class.files] ||=
54
54
  self.class.files.each_with_object({}) do |filename, hash|
55
- hash.merge!(YAML.safe_load(File.read(filename))) do |_key, first, second|
55
+ hash.merge!(YAML.safe_load(File.read(filename)) || {}) do |_key, first, second|
56
56
  case first
57
57
  when Hash
58
58
  first.merge(second)
@@ -70,6 +70,7 @@ module RuboCop
70
70
 
71
71
  def self.inherited(subclass)
72
72
  super
73
+ subclass.instance_variable_set(:@gem_requirements, gem_requirements.dup)
73
74
  Registry.global.enlist(subclass)
74
75
  end
75
76
 
@@ -126,6 +127,29 @@ module RuboCop
126
127
  false
127
128
  end
128
129
 
130
+ ## Gem requirements
131
+
132
+ @gem_requirements = {}
133
+
134
+ class << self
135
+ attr_reader :gem_requirements
136
+
137
+ # Register a version requirement for the given gem name.
138
+ # This cop will be skipped unless the target satisfies *all* requirements.
139
+ # @param [String] gem_name
140
+ # @param [Array<String>] version_requirements The version requirements,
141
+ # using the same syntax as a Gemfile, e.g. ">= 1.2.3"
142
+ #
143
+ # If omitted, any version of the gem will be accepted.
144
+ #
145
+ # https://guides.rubygems.org/patterns/#declaring-dependencies
146
+ #
147
+ # @api public
148
+ def requires_gem(gem_name, *version_requirements)
149
+ @gem_requirements[gem_name] = Gem::Requirement.new(version_requirements)
150
+ end
151
+ end
152
+
129
153
  def initialize(config = nil, options = nil)
130
154
  @config = config || Config.new
131
155
  @options = options || { debug: false }
@@ -162,7 +186,9 @@ module RuboCop
162
186
  def add_global_offense(message = nil, severity: nil)
163
187
  severity = find_severity(nil, severity)
164
188
  message = find_message(nil, message)
165
- current_offenses << Offense.new(severity, Offense::NO_LOCATION, message, name, :unsupported)
189
+ range = Offense::NO_LOCATION
190
+ status = enabled_line?(range.line) ? :unsupported : :disabled
191
+ current_offenses << Offense.new(severity, range, message, name, status)
166
192
  end
167
193
 
168
194
  # Adds an offense on the specified range (or node with an expression)
@@ -245,6 +271,7 @@ module RuboCop
245
271
  end
246
272
 
247
273
  def relevant_file?(file)
274
+ return false unless target_satisfies_all_gem_version_requirements?
248
275
  return true unless @config.clusivity_config_for_badge?(self.class.badge)
249
276
 
250
277
  file == RuboCop::AST::ProcessedSource::STRING_SOURCE_NAME ||
@@ -496,6 +523,18 @@ module RuboCop
496
523
  range.end_pos + @current_offset
497
524
  )
498
525
  end
526
+
527
+ def target_satisfies_all_gem_version_requirements?
528
+ self.class.gem_requirements.all? do |gem_name, version_req|
529
+ all_gem_versions_in_target = @config.gem_versions_in_target
530
+ next false unless all_gem_versions_in_target
531
+
532
+ gem_version_in_target = all_gem_versions_in_target[gem_name]
533
+ next false unless gem_version_in_target
534
+
535
+ version_req.satisfied_by?(gem_version_in_target)
536
+ end
537
+ end
499
538
  end
500
539
  end
501
540
  end
@@ -45,7 +45,8 @@ module RuboCop
45
45
  EXPECT_OFFENSE_DESCRIPTION_MAPPING = {
46
46
  /\A(does not|doesn't) (register|find|flag|report)/ => 'registers',
47
47
  /\A(does not|doesn't) add (a|an|any )?offense/ => 'registers an offense',
48
- /\Aaccepts\b/ => 'registers'
48
+ /\Aregisters no offense/ => 'registers an offense',
49
+ /\A(accepts|register)\b/ => 'registers'
49
50
  }.freeze
50
51
 
51
52
  EXPECT_NO_CORRECTIONS_DESCRIPTION_MAPPING = {
@@ -50,7 +50,7 @@ module RuboCop
50
50
  MSG_WITHOUT_SAFE_ASSIGNMENT_ALLOWED =
51
51
  'Use `==` if you meant to do a comparison or move the assignment ' \
52
52
  'up out of the condition.'
53
- ASGN_TYPES = [:begin, *AST::Node::EQUALS_ASSIGNMENTS, :send].freeze
53
+ ASGN_TYPES = [:begin, *AST::Node::EQUALS_ASSIGNMENTS, :send, :csend].freeze
54
54
 
55
55
  def on_if(node)
56
56
  return if node.condition.block_type?
@@ -88,7 +88,7 @@ module RuboCop
88
88
  end
89
89
 
90
90
  def skip_children?(asgn_node)
91
- (asgn_node.send_type? && !asgn_node.assignment_method?) ||
91
+ (asgn_node.call_type? && !asgn_node.assignment_method?) ||
92
92
  empty_condition?(asgn_node) ||
93
93
  (safe_assignment_allowed? && safe_assignment?(asgn_node))
94
94
  end
@@ -29,6 +29,11 @@ module RuboCop
29
29
  # MyDebugger.debug_this
30
30
  # ----
31
31
  #
32
+ # Some gems also ship files that will start a debugging session when required,
33
+ # for example `require 'debug/start'` from `ruby/debug`. These requires can
34
+ # be configured through `DebuggerRequires`. It has the same structure as
35
+ # `DebuggerMethods`, which you can read about above.
36
+ #
32
37
  # @example
33
38
  #
34
39
  # # bad (ok during development)
@@ -64,14 +69,20 @@ module RuboCop
64
69
  # def some_method
65
70
  # my_debugger
66
71
  # end
72
+ #
73
+ # @example DebuggerRequires: [my_debugger/start]
74
+ #
75
+ # # bad (ok during development)
76
+ #
77
+ # require 'my_debugger/start'
67
78
  class Debugger < Base
68
79
  MSG = 'Remove debugger entry point `%<source>s`.'
69
80
  BLOCK_TYPES = %i[block numblock kwbegin].freeze
70
81
 
71
82
  def on_send(node)
72
- return if !debugger_method?(node) || assumed_usage_context?(node)
83
+ return if assumed_usage_context?(node)
73
84
 
74
- add_offense(node)
85
+ add_offense(node) if debugger_method?(node) || debugger_require?(node)
75
86
  end
76
87
 
77
88
  private
@@ -87,12 +98,26 @@ module RuboCop
87
98
  end
88
99
  end
89
100
 
101
+ def debugger_requires
102
+ @debugger_requires ||= begin
103
+ config = cop_config.fetch('DebuggerRequires', [])
104
+ config.is_a?(Array) ? config : config.values.flatten
105
+ end
106
+ end
107
+
90
108
  def debugger_method?(send_node)
91
109
  return false if send_node.parent&.send_type? && send_node.parent.receiver == send_node
92
110
 
93
111
  debugger_methods.include?(chained_method_name(send_node))
94
112
  end
95
113
 
114
+ def debugger_require?(send_node)
115
+ return false unless send_node.method?(:require) && send_node.arguments.one?
116
+ return false unless (argument = send_node.first_argument).str_type?
117
+
118
+ debugger_requires.include?(argument.value)
119
+ end
120
+
96
121
  def assumed_usage_context?(node)
97
122
  # Basically, debugger methods are not used as a method argument without arguments.
98
123
  return false unless node.arguments.empty? && node.each_ancestor(:send, :csend).any?
@@ -52,7 +52,7 @@ module RuboCop
52
52
  # @!method deprecated_class_method?(node)
53
53
  def_node_matcher :deprecated_class_method?, <<~PATTERN
54
54
  {
55
- (send (const {cbase nil?} {:ENV}) {:clone :dup :freeze})
55
+ (send (const {cbase nil?} :ENV) {:clone :dup :freeze})
56
56
  (send (const {cbase nil?} {:File :Dir}) :exists? _)
57
57
  (send (const {cbase nil?} :Socket) {:gethostbyaddr :gethostbyname} ...)
58
58
  (send nil? :attr _ boolean)
@@ -146,7 +146,7 @@ module RuboCop
146
146
  node.source_range.with(end_pos: node.condition.source_range.end_pos)
147
147
  elsif all_branches_body_missing?(node)
148
148
  if_node = node.ancestors.detect(&:if?)
149
- node.source_range.with(end_pos: if_node.loc.end.end_pos)
149
+ node.source_range.join(if_node.loc.end.end)
150
150
  else
151
151
  node.source_range
152
152
  end
@@ -47,8 +47,10 @@ module RuboCop
47
47
 
48
48
  def on_regexp(node)
49
49
  each_unsafe_regexp_range(node) do |loc|
50
+ next unless (replacement = regexp_range(loc.source))
51
+
50
52
  add_offense(loc) do |corrector|
51
- corrector.replace(loc, rewrite_regexp_range(loc.source))
53
+ corrector.replace(loc, replacement)
52
54
  end
53
55
  end
54
56
  end
@@ -99,10 +101,13 @@ module RuboCop
99
101
  end
100
102
  end
101
103
 
102
- def rewrite_regexp_range(source)
104
+ def regexp_range(source)
103
105
  open, close = source.split('-')
104
- first = [open, range_for(open).end]
105
- second = [range_for(close).begin, close]
106
+ return unless (open_range = range_for(open))
107
+ return unless (close_range = range_for(close))
108
+
109
+ first = [open, open_range.end]
110
+ second = [close_range.begin, close]
106
111
  "#{first.uniq.join('-')}#{second.uniq.join('-')}"
107
112
  end
108
113
  end
@@ -130,7 +130,7 @@ module RuboCop
130
130
  # @return [Parser::Source::Range]
131
131
  #
132
132
  def last_arg_range(node)
133
- node.last_argument.source_range.with(begin_pos: node.arguments[-2].source_range.end_pos)
133
+ node.last_argument.source_range.join(node.arguments[-2].source_range.end)
134
134
  end
135
135
 
136
136
  def unsorted_dir_loop?(node)
@@ -33,8 +33,10 @@ module RuboCop
33
33
  MSG_EACH_WITH_INDEX = 'Use `each` instead of `each_with_index`.'
34
34
  MSG_WITH_INDEX = 'Remove redundant `with_index`.'
35
35
 
36
+ # rubocop:disable Metrics/AbcSize
36
37
  def on_block(node)
37
38
  return unless node.receiver
39
+ return if node.method?(:with_index) && !node.receiver.receiver
38
40
  return unless (send = redundant_with_index?(node))
39
41
 
40
42
  range = with_index_range(send)
@@ -48,6 +50,7 @@ module RuboCop
48
50
  end
49
51
  end
50
52
  end
53
+ # rubocop:enable Metrics/AbcSize
51
54
 
52
55
  alias on_numblock on_block
53
56
 
@@ -71,7 +71,7 @@ module RuboCop
71
71
  expressions.any? { |expr| flow_expression?(expr) }
72
72
  when :if
73
73
  check_if(node)
74
- when :case
74
+ when :case, :case_match
75
75
  check_case(node)
76
76
  else
77
77
  false
@@ -89,7 +89,9 @@ module RuboCop
89
89
  return false unless else_branch
90
90
  return false unless flow_expression?(else_branch)
91
91
 
92
- node.when_branches.all? { |branch| branch.body && flow_expression?(branch.body) }
92
+ branches = node.case_type? ? node.when_branches : node.in_pattern_branches
93
+
94
+ branches.all? { |branch| branch.body && flow_expression?(branch.body) }
93
95
  end
94
96
  end
95
97
  end
@@ -160,7 +160,7 @@ module RuboCop
160
160
  break_statement && !preceded_by_continue_statement?(break_statement)
161
161
  when :if
162
162
  check_if(node)
163
- when :case
163
+ when :case, :case_match
164
164
  check_case(node)
165
165
  else
166
166
  false
@@ -178,7 +178,13 @@ module RuboCop
178
178
  return false unless else_branch
179
179
  return false unless break_statement?(else_branch)
180
180
 
181
- node.when_branches.all? { |branch| branch.body && break_statement?(branch.body) }
181
+ branches = if node.case_type?
182
+ node.when_branches
183
+ else
184
+ node.in_pattern_branches
185
+ end
186
+
187
+ branches.all? { |branch| branch.body && break_statement?(branch.body) }
182
188
  end
183
189
 
184
190
  def preceded_by_continue_statement?(break_statement)
@@ -36,7 +36,7 @@ module RuboCop
36
36
  length = calculator.calculate
37
37
  return if length <= max_length
38
38
 
39
- location = node.casgn_type? ? node.loc.name : node.source_range
39
+ location = location(node)
40
40
 
41
41
  add_offense(location, message: message(length, max_length)) { self.max = length }
42
42
  end
@@ -54,6 +54,17 @@ module RuboCop
54
54
  foldable_types: count_as_one
55
55
  )
56
56
  end
57
+
58
+ def location(node)
59
+ return node.loc.name if node.casgn_type?
60
+
61
+ if LSP.enabled?
62
+ end_range = node.loc.respond_to?(:name) ? node.loc.name : node.loc.begin
63
+ node.source_range.begin.join(end_range)
64
+ else
65
+ node.source_range
66
+ end
67
+ end
57
68
  end
58
69
  end
59
70
  end
@@ -49,13 +49,13 @@ module RuboCop
49
49
 
50
50
  return unless complexity > max
51
51
 
52
- msg = format(self.class::MSG,
53
- method: method_name,
54
- complexity: complexity,
55
- abc_vector: abc_vector,
56
- max: max)
52
+ msg = format(
53
+ self.class::MSG,
54
+ method: method_name, complexity: complexity, abc_vector: abc_vector, max: max
55
+ )
56
+ location = location(node)
57
57
 
58
- add_offense(node, message: msg) { self.max = complexity.ceil }
58
+ add_offense(location, message: msg) { self.max = complexity.ceil }
59
59
  end
60
60
 
61
61
  def complexity(body)
@@ -69,6 +69,15 @@ module RuboCop
69
69
  end
70
70
  score
71
71
  end
72
+
73
+ def location(node)
74
+ if LSP.enabled?
75
+ end_range = node.loc.respond_to?(:name) ? node.loc.name : node.loc.begin
76
+ node.source_range.begin.join(end_range)
77
+ else
78
+ node.source_range
79
+ end
80
+ end
72
81
  end
73
82
  end
74
83
  end
@@ -14,7 +14,7 @@ module RuboCop
14
14
  def_node_matcher :empty_condition?, '(begin)'
15
15
 
16
16
  # @!method setter_method?(node)
17
- def_node_matcher :setter_method?, '[(send ...) setter_method?]'
17
+ def_node_matcher :setter_method?, '[(call ...) setter_method?]'
18
18
 
19
19
  # @!method safe_assignment?(node)
20
20
  def_node_matcher :safe_assignment?, '(begin {equals_asgn? #setter_method?})'
@@ -51,29 +51,25 @@ module RuboCop
51
51
  [Lint::AmbiguousOperator, Style::ArgumentsForwarding]
52
52
  end
53
53
 
54
- # rubocop:disable Metrics/CyclomaticComplexity
55
54
  def on_def(node)
56
55
  return if node.arguments.empty?
57
56
 
58
57
  last_argument = node.last_argument
59
58
  return if expected_block_forwarding_style?(node, last_argument)
60
59
 
61
- invalid_syntax = false
62
- node.each_descendant(:block_pass) do |block_pass_node|
63
- next if block_pass_node.children.first&.sym_type? ||
64
- last_argument.source != block_pass_node.source
60
+ forwarded_args = node.each_descendant(:block_pass).with_object([]) do |block_pass, result|
61
+ return nil if invalidates_syntax?(block_pass)
62
+ next unless block_argument_name_matched?(block_pass, last_argument)
65
63
 
66
- if block_pass_node.each_ancestor(:block, :numblock).any?
67
- invalid_syntax = true
68
- next
69
- end
64
+ result << block_pass
65
+ end
70
66
 
71
- register_offense(block_pass_node, node)
67
+ forwarded_args.each do |forwarded_arg|
68
+ register_offense(forwarded_arg, node)
72
69
  end
73
70
 
74
- register_offense(last_argument, node) unless invalid_syntax
71
+ register_offense(last_argument, node)
75
72
  end
76
- # rubocop:enable Metrics/CyclomaticComplexity
77
73
  alias on_defs on_def
78
74
 
79
75
  private
@@ -88,6 +84,29 @@ module RuboCop
88
84
  end
89
85
  end
90
86
 
87
+ def block_argument_name_matched?(block_pass_node, last_argument)
88
+ return false if block_pass_node.children.first&.sym_type?
89
+
90
+ last_argument.source == block_pass_node.source
91
+ end
92
+
93
+ # Prevents the following syntax error:
94
+ #
95
+ # # foo.rb
96
+ # def foo(&)
97
+ # block_method do
98
+ # bar(&)
99
+ # end
100
+ # end
101
+ #
102
+ # $ ruby -vc foo.rb
103
+ # ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [x86_64-darwin22]
104
+ # foo.rb: foo.rb:4: anonymous block parameter is also used within block (SyntaxError)
105
+ #
106
+ def invalidates_syntax?(block_pass_node)
107
+ block_pass_node.each_ancestor(:block, :numblock).any?
108
+ end
109
+
91
110
  def use_kwarg_in_method_definition?(node)
92
111
  node.arguments.each_descendant(:kwarg, :kwoptarg).any?
93
112
  end
@@ -57,7 +57,7 @@ module RuboCop
57
57
  file_path = processed_source.file_path
58
58
  return if config.file_to_exclude?(file_path) || config.allowed_camel_case_file?(file_path)
59
59
 
60
- for_bad_filename(file_path) { |range, msg| add_offense(range, message: msg) }
60
+ for_bad_filename(file_path)
61
61
  end
62
62
 
63
63
  private
@@ -71,7 +71,7 @@ module RuboCop
71
71
  msg = other_message(basename) unless bad_filename_allowed?
72
72
  end
73
73
 
74
- yield source_range(processed_source.buffer, 1, 0), msg if msg
74
+ add_global_offense(msg) if msg
75
75
  end
76
76
 
77
77
  def perform_class_and_module_naming_checks(file_path, basename)
@@ -207,8 +207,7 @@ module RuboCop
207
207
  message = create_multiple_word_message_for_file(words)
208
208
  end
209
209
 
210
- range = source_range(processed_source.buffer, 1, 0)
211
- add_offense(range, message: message)
210
+ add_global_offense(message)
212
211
  end
213
212
 
214
213
  def create_single_word_message_for_file(word)