171 lines
5.2 KiB
Ruby
171 lines
5.2 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require_relative '../../code_reuse_helpers'
|
|
|
|
module RuboCop
|
|
module Cop
|
|
module CodeReuse
|
|
# Cop that blacklists the use of ActiveRecord methods outside of models.
|
|
class ActiveRecord < RuboCop::Cop::Cop
|
|
include CodeReuseHelpers
|
|
|
|
MSG = 'This method can only be used inside an ActiveRecord model: ' \
|
|
'https://gitlab.com/gitlab-org/gitlab-ce/issues/49653'
|
|
|
|
# Various methods from ActiveRecord::Querying that are blacklisted. We
|
|
# exclude some generic ones such as `any?` and `first`, as these may
|
|
# lead to too many false positives, since `Array` also supports these
|
|
# methods.
|
|
#
|
|
# The keys of this Hash are the blacklisted method names. The values are
|
|
# booleans that indicate if the method should only be blacklisted if any
|
|
# arguments are provided.
|
|
NOT_ALLOWED = {
|
|
average: true,
|
|
calculate: true,
|
|
count_by_sql: true,
|
|
create_with: true,
|
|
distinct: false,
|
|
eager_load: true,
|
|
except: true,
|
|
exists?: true,
|
|
find_by: true,
|
|
find_by!: true,
|
|
find_by_sql: true,
|
|
find_each: true,
|
|
find_in_batches: true,
|
|
find_or_create_by: true,
|
|
find_or_create_by!: true,
|
|
find_or_initialize_by: true,
|
|
first!: false,
|
|
first_or_create: true,
|
|
first_or_create!: true,
|
|
first_or_initialize: true,
|
|
from: true,
|
|
group: true,
|
|
having: true,
|
|
ids: false,
|
|
includes: true,
|
|
joins: true,
|
|
limit: true,
|
|
lock: false,
|
|
many?: false,
|
|
offset: true,
|
|
order: true,
|
|
pluck: true,
|
|
preload: true,
|
|
readonly: false,
|
|
references: true,
|
|
reorder: true,
|
|
rewhere: true,
|
|
sum: false,
|
|
take: false,
|
|
take!: false,
|
|
unscope: false,
|
|
where: false,
|
|
with: true
|
|
}.freeze
|
|
|
|
# Directories that allow the use of the blacklisted methods. These
|
|
# directories are checked relative to both . and ee/
|
|
WHITELISTED_DIRECTORIES = %w[
|
|
app/models
|
|
config
|
|
danger
|
|
db
|
|
lib/backup
|
|
lib/banzai
|
|
lib/gitlab/background_migration
|
|
lib/gitlab/cycle_analytics
|
|
lib/gitlab/database
|
|
lib/gitlab/import_export
|
|
lib/gitlab/project_authorizations
|
|
lib/gitlab/sql
|
|
lib/system_check
|
|
lib/tasks
|
|
qa
|
|
rubocop
|
|
spec
|
|
].freeze
|
|
|
|
def on_send(node)
|
|
return if in_whitelisted_directory?(node)
|
|
|
|
receiver = node.children[0]
|
|
send_name = node.children[1]
|
|
first_arg = node.children[2]
|
|
|
|
if receiver && NOT_ALLOWED.key?(send_name)
|
|
# If the rule requires an argument to be given, but none are
|
|
# provided, we won't register an offense. This prevents us from
|
|
# adding offenses for `project.group`, while still covering
|
|
# `Project.group(:name)`.
|
|
return if NOT_ALLOWED[send_name] && !first_arg
|
|
|
|
add_offense(node, location: :selector)
|
|
end
|
|
end
|
|
|
|
# Returns true if the node resides in one of the whitelisted
|
|
# directories.
|
|
def in_whitelisted_directory?(node)
|
|
path = file_path_for_node(node)
|
|
|
|
WHITELISTED_DIRECTORIES.any? do |directory|
|
|
path.start_with?(
|
|
File.join(rails_root, directory),
|
|
File.join(rails_root, 'ee', directory)
|
|
)
|
|
end
|
|
end
|
|
|
|
# We can not auto correct code like this, as it requires manual
|
|
# refactoring. Instead, we'll just whitelist the surrounding scope.
|
|
#
|
|
# Despite this method's presence, you should not use it. This method
|
|
# exists to make it possible to whitelist large chunks of offenses we
|
|
# can't fix in the short term. If you are writing new code, follow the
|
|
# code reuse guidelines, instead of whitelisting any new offenses.
|
|
def autocorrect(node)
|
|
scope = surrounding_scope_of(node)
|
|
indent = indentation_of(scope)
|
|
|
|
lambda do |corrector|
|
|
# This prevents us from inserting the same enable/disable comment
|
|
# for a method or block that has multiple offenses.
|
|
next if whitelisted_scopes.include?(scope)
|
|
|
|
corrector.insert_before(
|
|
scope.source_range,
|
|
"# rubocop: disable #{cop_name}\n#{indent}"
|
|
)
|
|
|
|
corrector.insert_after(
|
|
scope.source_range,
|
|
"\n#{indent}# rubocop: enable #{cop_name}"
|
|
)
|
|
|
|
whitelisted_scopes << scope
|
|
end
|
|
end
|
|
|
|
def indentation_of(node)
|
|
' ' * node.loc.expression.source_line[/\A */].length
|
|
end
|
|
|
|
def surrounding_scope_of(node)
|
|
%i[def defs block begin].each do |type|
|
|
if (found = node.each_ancestor(type).first)
|
|
return found
|
|
end
|
|
end
|
|
end
|
|
|
|
def whitelisted_scopes
|
|
@whitelisted_scopes ||= Set.new
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|