Module: Spoom::Deadcode

Extended by:
T::Sig
Defined in:
lib/spoom/deadcode.rb,
lib/spoom/deadcode/erb.rb,
lib/spoom/deadcode/send.rb,
lib/spoom/deadcode/index.rb,
lib/spoom/deadcode/indexer.rb,
lib/spoom/deadcode/plugins.rb,
lib/spoom/deadcode/remover.rb,
lib/spoom/deadcode/visitor.rb,
lib/spoom/deadcode/location.rb,
lib/spoom/deadcode/reference.rb,
lib/spoom/deadcode/definition.rb,
lib/spoom/deadcode/plugins/base.rb,
lib/spoom/deadcode/plugins/rake.rb,
lib/spoom/deadcode/plugins/ruby.rb,
lib/spoom/deadcode/plugins/thor.rb,
lib/spoom/deadcode/plugins/rails.rb,
lib/spoom/deadcode/plugins/rspec.rb,
lib/spoom/deadcode/plugins/sorbet.rb,
lib/spoom/deadcode/plugins/graphql.rb,
lib/spoom/deadcode/plugins/rubocop.rb,
lib/spoom/deadcode/plugins/minitest.rb,
lib/spoom/deadcode/plugins/actionpack.rb,
lib/spoom/deadcode/plugins/active_job.rb,
lib/spoom/deadcode/plugins/namespaces.rb,
lib/spoom/deadcode/plugins/active_model.rb,
lib/spoom/deadcode/plugins/action_mailer.rb,
lib/spoom/deadcode/plugins/active_record.rb,
lib/spoom/deadcode/plugins/active_support.rb,
lib/spoom/deadcode/plugins/action_mailer_preview.rb

Defined Under Namespace

Modules: Plugins Classes: Definition, ERB, Error, Index, Indexer, IndexerError, Location, ParserError, Reference, Remover, Send, Visitor

Constant Summary collapse

DEFAULT_CUSTOM_PLUGINS_PATH =
".spoom/deadcode/plugins"
DEFAULT_PLUGINS =
T.let(
  Set.new([
    Spoom::Deadcode::Plugins::Namespaces,
    Spoom::Deadcode::Plugins::Ruby,
  ]).freeze,
  T::Set[T.class_of(Plugins::Base)],
)
PLUGINS_FOR_GEM =
T.let(
  {
    "actionmailer" => Spoom::Deadcode::Plugins::ActionMailer,
    "actionpack" => Spoom::Deadcode::Plugins::ActionPack,
    "activejob" => Spoom::Deadcode::Plugins::ActiveJob,
    "activemodel" => Spoom::Deadcode::Plugins::ActiveModel,
    "activerecord" => Spoom::Deadcode::Plugins::ActiveRecord,
    "activesupport" => Spoom::Deadcode::Plugins::ActiveSupport,
    "graphql" => Spoom::Deadcode::Plugins::GraphQL,
    "minitest" => Spoom::Deadcode::Plugins::Minitest,
    "rails" => Spoom::Deadcode::Plugins::Rails,
    "rake" => Spoom::Deadcode::Plugins::Rake,
    "rspec" => Spoom::Deadcode::Plugins::RSpec,
    "rubocop" => Spoom::Deadcode::Plugins::Rubocop,
    "sorbet-runtime" => Spoom::Deadcode::Plugins::Sorbet,
    "sorbet-static" => Spoom::Deadcode::Plugins::Sorbet,
    "thor" => Spoom::Deadcode::Plugins::Thor,
  }.freeze,
  T::Hash[String, T.class_of(Plugins::Base)],
)

Class Method Summary collapse

Class Method Details

.index_erb(index, erb, file:, plugins: []) ⇒ Object



81
82
83
84
# File 'lib/spoom/deadcode.rb', line 81

def index_erb(index, erb, file:, plugins: [])
  ruby = ERB.new(erb).src
  index_ruby(index, ruby, file: file, plugins: plugins)
end

.index_node(index, node, ruby, file:, plugins: []) ⇒ Object



67
68
69
70
71
72
# File 'lib/spoom/deadcode.rb', line 67

def index_node(index, node, ruby, file:, plugins: [])
  visitor = Spoom::Deadcode::Indexer.new(file, ruby, index, plugins: plugins)
  visitor.visit(node)
rescue => e
  raise IndexerError.new("Error while indexing #{file} (#{e.message})", parent: e)
end

.index_ruby(index, ruby, file:, plugins: []) ⇒ Object



75
76
77
78
# File 'lib/spoom/deadcode.rb', line 75

def index_ruby(index, ruby, file:, plugins: [])
  node = parse_ruby(ruby, file: file)
  index_node(index, node, ruby, file: file, plugins: plugins)
end

.load_custom_plugins(context) ⇒ Object



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/spoom/deadcode/plugins.rb', line 75

def load_custom_plugins(context)
  context.glob("#{DEFAULT_CUSTOM_PLUGINS_PATH}/*.rb").each do |path|
    require("#{context.absolute_path}/#{path}")
  end

  ObjectSpace
    .each_object(Class)
    .select do |klass|
      next unless T.unsafe(klass).name # skip anonymous classes, we only use them in tests
      next unless T.unsafe(klass) < Plugins::Base

      location = Object.const_source_location(T.unsafe(klass).to_s)&.first
      next unless location
      next unless location.start_with?("#{context.absolute_path}/#{DEFAULT_CUSTOM_PLUGINS_PATH}")

      true
    end
    .map { |klass| T.unsafe(klass).new }
end

.parse_ruby(ruby, file:) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/spoom/deadcode.rb', line 43

def parse_ruby(ruby, file:)
  result = Prism.parse(ruby)
  unless result.success?
    message = +"Error while parsing #{file}:\n"

    result.errors.each do |e|
      message << "- #{e.message} (at #{e.location.start_line}:#{e.location.start_column})\n"
    end

    raise ParserError, message
  end

  result.value
end

.plugins_from_gemfile_lock(context) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/spoom/deadcode/plugins.rb', line 61

def plugins_from_gemfile_lock(context)
  # These plugins are always loaded
  plugin_classes = DEFAULT_PLUGINS.dup

  # These plugins depends on the gems used by the project
  context.gemfile_lock_specs.keys.each do |name|
    plugin_class = PLUGINS_FOR_GEM[name]
    plugin_classes << plugin_class if plugin_class
  end

  plugin_classes.map(&:new)
end