# frozen_string_literal: true module Gitlab module Styles module Rubocop module Cop module CodeReuse # Cop that denies the use of ActiveRecord methods outside of models. class ActiveRecord < RuboCop::Cop::Cop MSG = 'This method can only be used inside an ActiveRecord model: ' \ 'https://gitlab.com/gitlab-org/gitlab-foss/issues/49653' # Various methods from ActiveRecord::Querying that are denied. 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 denied method names. The values are # booleans that indicate if the method should only be denied if any # arguments are provided. NOT_ALLOWED = { average: true, calculate: true, count_by_sql: true, create_with: true, distinct: false, eager_load: 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, take: false, take!: false, unscope: false, where: false, with: true }.freeze def on_send(node) receiver = node.children[0] send_name = node.children[1] first_arg = node.children[2] return unless 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 # We can not auto correct code like this, as it requires manual # refactoring. Instead, we'll just allow the surrounding scope. # # Despite this method's presence, you should not use it. This method # exists to make it possible to allow 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 allowing 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 allowed_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}" ) allowed_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 allowed_scopes @allowed_scopes ||= Set.new end end end end end end end