module RuboCop::Cop::FactoryBotUsage

Helpers for detecting FactoryBot usage. rubocop:disable Metrics/ModuleLength

Constants

FACTORY_BOT_METHODS
Factory

Public Class Methods

factories_cache() click to toggle source
# File lib/rubocop/cop/mixin/factory_bot_usage.rb, line 37
def self.factories_cache
  @@factories
end
factories_cache=(factories) click to toggle source
# File lib/rubocop/cop/mixin/factory_bot_usage.rb, line 41
def self.factories_cache=(factories)
  @@factories = factories
end

Public Instance Methods

engines_path() click to toggle source
# File lib/rubocop/cop/mixin/factory_bot_usage.rb, line 73
def engines_path
  raise NotImplementedError
end
factory_files() click to toggle source
# File lib/rubocop/cop/mixin/factory_bot_usage.rb, line 69
def factory_files
  @factory_files ||= Dir['spec/factories/**/*.rb'] + Dir["#{engines_path}*/spec/factories/**/*.rb"]
end
find_factories() click to toggle source

Parses factory definition files, returning a hash mapping factory names to model class names for each file.

# File lib/rubocop/cop/mixin/factory_bot_usage.rb, line 52
def find_factories
  RuboCop::Cop::FactoryBotUsage.factories_cache ||= begin
    # We'll add factories here as we parse the factory files.
    @factories = {}

    # We'll add factories that specify a parent here, so we can resolve the
    # reference to the parent after we have finished parsing all the files.
    @parents = {}

    # Parse the factory files, then resolve any parent references.
    traverse_factory_files
    resolve_parents

    @factories
  end
end
spec_file?() click to toggle source

rubocop:enable Style/ClassVars

# File lib/rubocop/cop/mixin/factory_bot_usage.rb, line 46
def spec_file?
  processed_source&.path&.match?(/_spec\.rb$/) || false
end

Private Instance Methods

determine_model_class_name(factory, model_class_name_from_surrounding_block) click to toggle source
# File lib/rubocop/cop/mixin/factory_bot_usage.rb, line 200
def determine_model_class_name(factory, model_class_name_from_surrounding_block)
  factory.model_class_name ||
    model_class_name_from_surrounding_block ||
    ActiveSupport::Inflector.camelize(factory.name)
end
determine_parent(factory, parent_from_surrounding_block) click to toggle source
# File lib/rubocop/cop/mixin/factory_bot_usage.rb, line 192
def determine_parent(factory, parent_from_surrounding_block)
  # If the factory specifies an explicit model class name, we don't need
  # to resolve the parent to determine the model class name.
  return nil if factory.model_class_name

  factory.parent || parent_from_surrounding_block
end
extract_aliases(factory_config_hash_node) click to toggle source
# File lib/rubocop/cop/mixin/factory_bot_usage.rb, line 157
def extract_aliases(factory_config_hash_node)
  aliases_array = extract_hash_value(factory_config_hash_node, :aliases)
  return [] if aliases_array&.type != :array

  aliases_array.children.map(&:value)
end
extract_factory_node(node) click to toggle source
# File lib/rubocop/cop/mixin/factory_bot_usage.rb, line 121
def extract_factory_node(node)
  return node.children[0] if factory_block?(node)
  return node if factory_node?(node)
end
extract_hash_value(node, hash_key) click to toggle source
# File lib/rubocop/cop/mixin/factory_bot_usage.rb, line 180
def extract_hash_value(node, hash_key)
  return nil if node&.type != :hash

  pairs = node.children.select { |child| child.type == :pair }
  pairs.each do |pair|
    key, value = pair.children
    return value if key.value == hash_key
  end

  nil
end
extract_model_class_name(factory_config_hash_node) click to toggle source
# File lib/rubocop/cop/mixin/factory_bot_usage.rb, line 169
def extract_model_class_name(factory_config_hash_node)
  model_class_name_node = extract_hash_value(factory_config_hash_node, :class)

  case model_class_name_node&.type
  when :const
    model_class_name_node.source.sub(/^::/, '')
  when :str
    model_class_name_node.value.sub(/^::/, '')
  end
end
extract_parent(factory_config_hash_node) click to toggle source
# File lib/rubocop/cop/mixin/factory_bot_usage.rb, line 164
def extract_parent(factory_config_hash_node)
  parent_node = extract_hash_value(factory_config_hash_node, :parent)
  parent_node&.value
end
factory_block?(node) click to toggle source
# File lib/rubocop/cop/mixin/factory_bot_usage.rb, line 136
def factory_block?(node)
  return false if node&.type != :block

  factory_node?(node.children[0])
end
factory_node?(node) click to toggle source
# File lib/rubocop/cop/mixin/factory_bot_usage.rb, line 142
def factory_node?(node)
  node&.type == :send && node.children[1] == :factory
end
parse_factory_node(node) click to toggle source
# File lib/rubocop/cop/mixin/factory_bot_usage.rb, line 146
def parse_factory_node(node)
  factory_name_node, factory_config_node = node.children[2..3]

  name = factory_name_node.children[0]
  aliases = extract_aliases(factory_config_node)
  parent = extract_parent(factory_config_node)
  model_class_name = extract_model_class_name(factory_config_node)

  Factory.new(name, aliases, parent, model_class_name)
end
register_factory(path, factory_name, aliases, parent, model_class_name) click to toggle source
# File lib/rubocop/cop/mixin/factory_bot_usage.rb, line 126
def register_factory(path, factory_name, aliases, parent, model_class_name)
  ([factory_name] + aliases).each do |name|
    if parent
      @parents[path][name] = parent
    else
      @factories[path][name] = model_class_name
    end
  end
end
resolve_parents() click to toggle source
# File lib/rubocop/cop/mixin/factory_bot_usage.rb, line 90
def resolve_parents
  all_factories = @factories.values.reduce(:merge)
  all_parents = @parents.values.reduce(:merge)
  @parents.each do |path, parents|
    parents.each do |factory, parent|
      parent = all_parents[parent] while all_parents[parent]
      model_class_name = all_factories[parent]
      next unless model_class_name

      @factories[path][factory] = model_class_name
    end
  end
end
traverse_factory_files() click to toggle source
# File lib/rubocop/cop/mixin/factory_bot_usage.rb, line 79
def traverse_factory_files
  factory_files.each do |path|
    @factories[path] = {}
    @parents[path] = {}

    source_code = File.read(path)
    source = RuboCop::ProcessedSource.new(source_code, RUBY_VERSION.to_f)
    traverse_node(source.ast, path)
  end
end
traverse_node(node, path, parent = nil, model_class_name = nil) click to toggle source
# File lib/rubocop/cop/mixin/factory_bot_usage.rb, line 104
def traverse_node(node, path, parent = nil, model_class_name = nil)
  return unless node.is_a?(Parser::AST::Node)

  factory_node = extract_factory_node(node)
  if factory_node
    factory = parse_factory_node(factory_node)
    parent = determine_parent(factory, parent)
    model_class_name = determine_model_class_name(factory, model_class_name)
    if factory_node?(node)
      register_factory(path, factory.name, factory.aliases, parent, model_class_name)
      return
    end
  end

  node.children.each { |child| traverse_node(child, path, parent, model_class_name) }
end