toptal/crystalball

View on GitHub
lib/crystalball/map_generator/parser_strategy.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

require 'crystalball/map_generator/parser_strategy/processor'
require 'crystalball/map_generator/helpers/path_filter'

module Crystalball
  class MapGenerator
    # Map generator strategy based on parsing source files to detect constant definition
    # and tracing method calls on those constants.
    class ParserStrategy
      include BaseStrategy
      include Helpers::PathFilter

      attr_reader :const_definition_paths

      def initialize(root = Dir.pwd, pattern:)
        @root_path = Pathname.new(root).realpath.to_s
        @processor = Processor.new
        @const_definition_paths = {}
        @pattern = pattern
      end

      def after_register
        files_to_inspect.each do |path|
          processor.consts_defined_in(path).each do |const|
            (const_definition_paths[const] ||= []) << path
          end
        end
      end

      # Parses the current example group map seeking calls to class methods and adds
      #   the classes to the map.
      # @param [Crystalball::ExampleGroupMap] example_map - object holding example metadata and used files
      # @param [RSpec::Core::Example] example - a RSpec example
      def call(example_map, example)
        paths = []
        yield example_map, example
        example_map.each do |path|
          next unless path.end_with?('.rb')

          used_consts = processor.consts_interacted_with_in(path)
          paths.push(*used_files(used_consts))
        end
        example_map.push(*filter(paths))
      end

      private

      attr_reader :processor, :pattern, :root_path

      def used_files(used_consts)
        const_definition_paths.select { |k, _| Array(used_consts).include?(k) }.values.flatten
      end

      def files_to_inspect
        Dir.glob(File.join(root_path, '**/*.rb')).grep(pattern)
      end
    end
  end
end