Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/new_lint_unreliable_subclasses_cop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#14618](https://github.com/rubocop/rubocop/issues/14618): Add new `Lint/UnreliableSubclasses` cop to warn against using `Class#subclasses` due to autoloading and garbage collection reliability issues. ([@alexanderadam][])
6 changes: 6 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2574,6 +2574,12 @@ Lint/UnreachableLoop:
# eg. `exactly(2).times`
- !ruby/regexp /(exactly|at_least|at_most)\(\d+\)\.times/

Lint/UnreliableSubclasses:
Description: 'Checks for usage of `Class#subclasses` which is unreliable with autoloading and garbage collection.'
Enabled: pending
VersionAdded: '<<next>>'
Safe: false

Lint/UnusedBlockArgument:
Description: 'Checks for unused block arguments.'
StyleGuide: '#underscore-unused-vars'
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@
require_relative 'rubocop/cop/lint/unmodified_reduce_accumulator'
require_relative 'rubocop/cop/lint/unreachable_code'
require_relative 'rubocop/cop/lint/unreachable_loop'
require_relative 'rubocop/cop/lint/unreliable_subclasses'
require_relative 'rubocop/cop/lint/unused_block_argument'
require_relative 'rubocop/cop/lint/unused_method_argument'
require_relative 'rubocop/cop/lint/uri_escape_unescape'
Expand Down
42 changes: 42 additions & 0 deletions lib/rubocop/cop/lint/unreliable_subclasses.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Lint
# Checks for usage of `Class#subclasses`.
#
# This method is unreliable for two main reasons:
# 1. It doesn't know about classes that have yet to be autoloaded
# 2. It is non-deterministic with regards to garbage collection of dynamically created classes
#
# @safety
# This cop is unsafe because it may flag code that intentionally uses this method
# with full awareness of its limitations.
#
# @example
#
# # bad
# MyBaseClass.subclasses.map(&:name)
#
class UnreliableSubclasses < Base
MSG = 'Avoid using `%<method>s` as it is unreliable with autoloading and ' \
'non-deterministic with garbage collection.'

RESTRICT_ON_SEND = %i[subclasses].freeze

# @!method class_introspection_method?(node)
def_node_matcher :class_introspection_method?, <<~PATTERN
(send _ :subclasses)
PATTERN

def on_send(node)
return unless class_introspection_method?(node)

message = format(MSG, method: node.method_name)
add_offense(node.loc.selector, message: message)
end
alias on_csend on_send
end
end
end
end
10 changes: 10 additions & 0 deletions spec/rubocop/cop/lint/unreliable_subclasses_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Lint::UnreliableSubclasses, :config do
it 'registers an offense when using `subclasses`' do
expect_offense(<<~RUBY)
MyBaseClass.subclasses
^^^^^^^^^^ Avoid using `subclasses` as it is unreliable with autoloading and non-deterministic with garbage collection.
RUBY
end
end