Shopify / constant_resolver

Resolve a partially qualified Ruby constant reference to the fully qualified name and the path of the file defining it.
MIT License
26 stars 10 forks source link

Eliminate dependency on zeitwerk #26

Open exterm opened 2 years ago

exterm commented 2 years ago

Currently this gem guesses the file that defines a constant by following zeitwerk's mapping conventions in reverse. In consequence, it only works reliably on code bases that use zeitwerk for loading.

If we could sever this dependency and use a different mechanism to resolve constants,

Possible alternative resolution methods to consider

Tracepoints

Tracepoints allow registering callbacks for certain events in the Ruby interpreter. While there is no tracepoint for defining constants, there is one for defining classes or modules. We could register this tracepoint, then load all the code that could possibly define the constant we're interested in, building up a lookup table from tracepoint invocations, and use that to resolve the constant.

Drawbacks:

Module.const_source_location

In Ruby 2.7, a new method was added that allows looking up the location a (loaded) constant is defined in. We can resolve constants in context by trying the enclosing namespaces first:

if "Sales::Entry".safe_constantize
  return Sales.const_source_location("Entry")
end
if "Entry".safe_constantize
  return Module.const_source_location("Entry")
end

Caveat: const_source_location only returns the file that defines the constant, no files that reopen classes or modules. If we have multiple files with a module Sales statement, which one is the definition depends on load order, which is usually unpredictable in large applications. We can work around this by making sure we've loaded all relevant code, then inspecting Entry for all the methods and constants it contains, ask for their source locations, and get the full list of files defining or reopening the module / class.

Advantages:

Disadvantages:

Sorbet

Sorbet already finds and resolves all constant references for its type analysis. We should be able to hook into it for constant resolution, and potentially, in packwerk, also for finding references.

We'd probably start up a language server, let it run an initial analysis of the codebase to build up context. Afterwards, asking the LS some questions (e.g. for constant resolution) should be relatively quick.

Advantages:

Disadvantages: