Created
May 7, 2018 14:38
-
-
Save AndrewDryga/0022bb2d2b42a99ca48ec4fb3978892a to your computer and use it in GitHub Desktop.
Find unused code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Mix.Tasks.Xref2 do | |
use Mix.Task | |
import Mix.Compilers.Elixir, | |
only: [read_manifest: 2, source: 0, source: 1, source: 2, module: 1] | |
@shortdoc "Performs cross reference checks" | |
@recursive true | |
@manifest "compile.elixir" | |
@moduledoc """ | |
Performs cross reference checks between modules. | |
The unreachable and deprecated checks below happen every time | |
your project is compiled via `mix compile.xref`. See | |
`Mix.Tasks.Compile.Xref` for more information | |
## Xref modes | |
The `xref` task expects a mode as first argument: | |
mix xref MODE | |
All available modes are discussed below. | |
### unreachable | |
Prints all unreachable "file:line: module.function/arity" entries: | |
mix xref unreachable | |
The "file:line" represents the file and line a call to an unknown | |
"module.function/arity" is made. | |
The flag `--abort-if-any` can be used for the command to fail if | |
unreachable calls exist. | |
## deprecated | |
Prints all deprecated "file:line: module.function/arity" entries: | |
mix xref deprecated | |
The "file:line" represents the file and line a call to a deprecated | |
"module.function/arity" is made. This operation does not show deprecated | |
local calls (a call to a deprecated function or macro in the same module) | |
nor calls to deprecated functionality in Elixir itself. | |
The flag `--abort-if-any` can be used for the command to fail if | |
deprecated calls exist. | |
### callers CALLEE | |
Prints all callers of the given `CALLEE`, which can be one of: `Module`, | |
`Module.function`, or `Module.function/arity`. Examples: | |
mix xref callers MyMod | |
mix xref callers MyMod.fun | |
mix xref callers MyMod.fun/3 | |
### graph | |
Prints a file dependency graph where an edge from `A` to `B` indicates | |
that `A` depends on `B`. | |
mix xref graph --format stats | |
The following options are accepted: | |
* `--exclude` - paths to exclude | |
* `--label` - only shows relationships with the given label | |
The labels are "compile", "struct" and "runtime" (runtime is now shown on the graph) | |
* `--only-nodes` - only show the node names (no edges) | |
* `--source` - displays all files that the given source file references (directly or indirectly) | |
* `--sink` - displays all files that reference the given file (directly or indirectly) | |
* `--format` - can be set to one of: | |
* `pretty` - prints the graph to the terminal using Unicode characters. | |
Each prints each file followed by the files it depends on. This is the | |
default except on Windows; | |
* `plain` - the same as pretty except ASCII characters is used instead of | |
Unicode characters. This is the default on Windows; | |
* `stats` - prints general statistics about the graph; | |
* `dot` - produces a DOT graph description in `xref_graph.dot` in the | |
current directory. Warning: this will override any previously generated file | |
The `--source` and `--sink` options are particularly useful when trying to understand | |
how the modules in a particular file interact with the whole system. You can combine | |
those options with `--label` and `--only-nodes` to get all files that exhibit a certain | |
property, for example: | |
# To get all files that depend on lib/foo.ex | |
mix xref graph --sink lib/foo.ex --only-nodes | |
# To get all files that depend on lib/foo.ex at compile time | |
mix xref graph --label compile --sink lib/foo.ex --only-nodes | |
# To show general statistics about the graph | |
mix xref graph --format stats | |
# To limit statistics only to certain labels | |
mix xref graph --format stats --label compile | |
## Shared options | |
Those options are shared across all modes: | |
* `--include-siblings` - include dependencies that have `:in_umbrella` set | |
to true in the current project in the reports. This can be used to find | |
callers or analyze graphs between projects | |
* `--no-compile` - does not compile even if files require compilation | |
* `--no-deps-check` - does not check dependencies | |
* `--no-archives-check` - does not check archives | |
* `--no-elixir-version-check` - does not check the Elixir version from mix.exs | |
## Configuration | |
All configuration for Xref should be placed under the key `:xref`. | |
* `:exclude` - a list of modules and `{module, function, arity}` tuples to ignore when checking | |
cross references. For example: `[MissingModule, {MissingModule2, :missing_func, 2}]` | |
""" | |
@switches [ | |
abort_if_any: :boolean, | |
archives_check: :boolean, | |
compile: :boolean, | |
deps_check: :boolean, | |
elixir_version_check: :boolean, | |
exclude: :keep, | |
format: :string, | |
include_siblings: :boolean, | |
label: :string, | |
only_nodes: :boolean, | |
sink: :string, | |
source: :string | |
] | |
def run(args) do | |
{opts, args} = OptionParser.parse!(args, strict: @switches) | |
Mix.Task.run("loadpaths") | |
if Keyword.get(opts, :compile, true) do | |
Mix.Task.run("compile") | |
end | |
Mix.Task.reenable("xref") | |
case args do | |
["deprecated"] -> | |
deprecated(opts) | |
["unreachable"] -> | |
unreachable(opts) | |
["callers", callee] -> | |
callers(callee, opts) | |
["graph"] -> | |
graph(opts) | |
["unused"] -> | |
unused(MapSet.new([Mix.Tasks.Xref2]), opts) | |
_ -> | |
Mix.raise("xref doesn't support this command. For more information run \"mix help xref\"") | |
end | |
end | |
@doc false | |
def warnings(opts \\ []) do | |
source_warnings(excludes(), opts) | |
|> merge_entries(:all) | |
|> sort_entries() | |
|> print_warnings() | |
end | |
@doc """ | |
Returns a list of information of all the function calls in the project. | |
Each item in the list is a map with the following keys: | |
* `:callee` - a tuple containing the module, function, and arity of the call | |
* `:line` - an integer representing the line where the function is called | |
* `:file` - a binary representing the file where the function is called | |
* `:caller_module` - the module where the function is called | |
This function returns an empty list when used at the root of an umbrella | |
project because there is no compile manifest to extract the function call | |
information from. To get the function calls of each child in an umbrella, | |
execute the function at the root of each individual application. | |
""" | |
@spec calls(keyword()) :: [ | |
%{ | |
callee: {module(), atom(), arity()}, | |
caller_module: module(), | |
line: integer(), | |
file: String.t() | |
} | |
] | |
def calls(opts \\ []) do | |
module_sources = | |
for manifest <- manifests(opts), | |
manifest_data = read_manifest(manifest, ""), | |
module(module: module, sources: sources) <- manifest_data, | |
source <- sources, | |
source = Enum.find(manifest_data, &match?(source(source: ^source), &1)), | |
do: {module, source} | |
Enum.flat_map(module_sources, fn {caller_module, source} -> | |
source( | |
runtime_dispatches: runtime_nested, | |
compile_dispatches: compile_nested, | |
source: rel_file | |
) = source | |
runtime_function_calls = | |
dispatches_to_function_calls(caller_module, rel_file, runtime_nested) | |
compile_function_calls = | |
dispatches_to_function_calls(caller_module, rel_file, compile_nested) | |
runtime_function_calls ++ compile_function_calls | |
end) | |
end | |
defp dispatches_to_function_calls(caller_module, file, dispatches) do | |
for {module, function_calls} <- dispatches, | |
{{function, arity}, lines} <- function_calls, | |
line <- lines do | |
%{ | |
callee: {module, function, arity}, | |
file: file, | |
line: line, | |
caller_module: caller_module | |
} | |
end | |
end | |
## Modes | |
defp unreachable(opts) do | |
source_warnings(excludes(), opts) | |
|> merge_entries(&(&1 in [:unknown_module, :unknown_function])) | |
|> sort_entries() | |
|> print_mfas() | |
|> return_ok_error_or_abort(:unreachable, opts) | |
end | |
defp deprecated(opts) do | |
source_warnings(excludes(), opts) | |
|> merge_entries(&(&1 == :deprecated)) | |
|> sort_entries() | |
|> print_mfas() | |
|> return_ok_error_or_abort(:deprecated, opts) | |
end | |
defp callers(callee, opts) do | |
callee | |
|> filter_for_callee() | |
|> source_callers(opts) | |
|> merge_entries(:all) | |
|> sort_entries() | |
|> print_calls() | |
:ok | |
end | |
defp graph(opts) do | |
write_graph(file_references(opts), excluded(opts), opts) | |
:ok | |
end | |
## Warnings (unreacheable + deprecated) | |
defp source_warnings(excludes, opts) do | |
sources = sources(opts) | |
exports_and_deprecated = | |
Enum.reduce(sources, %{}, fn source, acc -> | |
runtime = source(source, :runtime_dispatches) | |
compile = source(source, :compile_dispatches) | |
acc = Enum.reduce(runtime, acc, &load_exports_and_deprecated_into_acc/2) | |
acc = Enum.reduce(compile, acc, &load_exports_and_deprecated_into_acc/2) | |
acc | |
end) | |
runtime_warnings = | |
for source <- sources, | |
{module, func_arity_locations} <- source(source, :runtime_dispatches), | |
{exports, deprecated} = exports_and_deprecated[module], | |
{{func, arity}, locations} <- func_arity_locations, | |
not excluded?(excludes, module, func, arity), | |
warning = | |
unreachable_mfa(exports, module, func, arity) || | |
deprecated_mfa(deprecated, module, func, arity), | |
do: {warning, absolute_locations(locations, source(source, :source))} | |
compile_warnings = | |
for source <- sources, | |
{module, func_arity_locations} <- source(source, :compile_dispatches), | |
{_, deprecated} = exports_and_deprecated[module], | |
{{func, arity}, locations} <- func_arity_locations, | |
not excluded?(excludes, module, func, arity), | |
warning = deprecated_mfa(deprecated, module, func, arity), | |
do: {warning, absolute_locations(locations, source(source, :source))} | |
runtime_warnings ++ compile_warnings | |
end | |
defp load_exports_and_deprecated_into_acc({module, _}, acc) do | |
case acc do | |
%{^module => _} -> acc | |
_ -> Map.put(acc, module, load_exports_and_deprecated(module)) | |
end | |
end | |
defp load_exports_and_deprecated(module) do | |
if :code.is_loaded(module) do | |
# If the module is loaded, we will use the faster function_exported?/3 | |
# check for exports and __info__/1 for deprecated | |
if function_exported?(module, :__info__, 1) do | |
{module, load_deprecated_from_module(module)} | |
else | |
{module, []} | |
end | |
else | |
# Otherwise we get the information from :beam_lib to avoid loading modules | |
with [_ | _] = file <- :code.which(module), | |
{:ok, {^module, [{:exports, exports}, {'ExDp', deprecated}]}} <- | |
:beam_lib.chunks(file, [:exports, 'ExDp'], [:allow_missing_chunks]) do | |
{exports, load_deprecated_from_chunk(deprecated)} | |
else | |
_ -> {:unknown_module, []} | |
end | |
end | |
end | |
defp load_deprecated_from_chunk(chunk) do | |
if is_binary(chunk) do | |
{:elixir_deprecated_v1, deprecated} = :erlang.binary_to_term(chunk) | |
deprecated | |
else | |
[] | |
end | |
end | |
defp load_deprecated_from_module(module) do | |
try do | |
module.__info__(:deprecated) | |
rescue | |
_ -> [] | |
end | |
end | |
defp unreachable_mfa(exports, module, func, arity) do | |
cond do | |
skip_unreachable?(module, func, arity) -> | |
nil | |
exports == :unknown_module -> | |
{:unknown_module, module, func, arity, nil} | |
is_atom(exports) and not function_exported?(module, func, arity) -> | |
{:unknown_function, module, func, arity, nil} | |
is_list(exports) and {func, arity} not in exports -> | |
{:unknown_function, module, func, arity, exports} | |
true -> | |
nil | |
end | |
end | |
defp deprecated_mfa(deprecated, module, func, arity) do | |
case List.keyfind(deprecated, {func, arity}, 0) do | |
{_, reason} -> | |
{:deprecated, module, func, arity, reason} | |
nil -> | |
nil | |
end | |
end | |
@protocol_builtins for {_, type} <- Protocol.__builtin__(), do: type | |
defp skip_unreachable?(:erlang, func, 2) when func in [:andalso, :orelse] do | |
true | |
end | |
defp skip_unreachable?(module, :__impl__, 1) do | |
{maybe_protocol, maybe_builtin} = module |> Module.split() |> Enum.split(-1) | |
maybe_protocol = Module.concat(maybe_protocol) | |
maybe_builtin = Module.concat(maybe_builtin) | |
maybe_builtin in @protocol_builtins and Code.ensure_loaded?(maybe_protocol) and | |
function_exported?(maybe_protocol, :__protocol__, 1) | |
end | |
defp skip_unreachable?(_, _, _) do | |
false | |
end | |
defp excludes() do | |
Mix.Project.config() | |
|> Keyword.get(:xref, []) | |
|> Keyword.get(:exclude, []) | |
|> MapSet.new() | |
end | |
defp excluded?(excludes, module, func, arity) do | |
MapSet.member?(excludes, module) or MapSet.member?(excludes, {module, func, arity}) | |
end | |
## Print mfas (unreachable / deprecated) | |
defp print_mfas(entries) do | |
Enum.each(entries, &print_mfa/1) | |
entries | |
end | |
defp print_mfa({{_, module, function, arity, _}, locations}) do | |
shell = Mix.shell() | |
for {file, line} <- locations do | |
shell.info([ | |
Exception.format_file_line(file, line, " "), | |
Exception.format_mfa(module, function, arity) | |
]) | |
end | |
end | |
## Print warnings | |
defp print_warnings(entries) do | |
prefix = IO.ANSI.format([:yellow, "warning: "]) | |
Enum.map(entries, fn {type, locations} -> | |
message = warning_message(type) | |
IO.write(:stderr, [prefix, message, ?\n, format_locations(locations), ?\n]) | |
{message, locations} | |
end) | |
end | |
defp warning_message({:unknown_function, module, function, arity, exports}) do | |
UndefinedFunctionError.function_not_exported(module, function, arity, exports) | |
end | |
defp warning_message({:unknown_module, module, function, arity, _}) do | |
[ | |
"function ", | |
Exception.format_mfa(module, function, arity), | |
" is undefined (module #{inspect(module)} is not available)" | |
] | |
end | |
defp warning_message({:deprecated, module, function, arity, reason}) do | |
[ | |
Exception.format_mfa(module, function, arity), | |
" is deprecated. ", | |
reason, | |
"." | |
] | |
end | |
defp format_locations([location]) do | |
format_location(location) | |
end | |
defp format_locations(locations) do | |
[ | |
"Found at #{length(locations)} locations:\n", | |
Enum.map(locations, &format_location/1) | |
] | |
end | |
defp format_location({file, line}) do | |
[" ", file, ?:, Integer.to_string(line), ?\n] | |
end | |
## Callers | |
defp source_callers(filter, opts) do | |
for source <- sources(opts), | |
file = source(source, :source), | |
{module, func_arity_locations} <- dispatches(source), | |
{{func, arity}, locations} <- func_arity_locations, | |
filter.({module, func, arity}), | |
do: {{module, func, arity}, absolute_locations(locations, file)} | |
end | |
defp print_calls(calls) do | |
Enum.each(calls, &print_call/1) | |
calls | |
end | |
defp print_call({{module, func, arity}, locations}) do | |
shell = Mix.shell() | |
for {file, line} <- locations do | |
shell.info([ | |
Exception.format_file_line(file, line, " "), | |
Exception.format_mfa(module, func, arity) | |
]) | |
end | |
end | |
defp filter_for_callee(callee) do | |
case Mix.Utils.parse_mfa(callee) do | |
{:ok, mfa_list} -> | |
mfa_list_length = length(mfa_list) | |
fn {module, function, arity} -> | |
mfa_list == Enum.take([module, function, arity], mfa_list_length) | |
end | |
:error -> | |
Mix.raise( | |
"xref callers CALLEE expects Module, Module.function, or Module.function/arity, " <> | |
"got: " <> callee | |
) | |
end | |
end | |
## Graph | |
defp excluded(opts) do | |
opts | |
|> Keyword.get_values(:exclude) | |
|> Enum.flat_map(&[{&1, nil}, {&1, :compile}, {&1, :struct}]) | |
end | |
defp label_filter(nil), do: :all | |
defp label_filter("compile"), do: :compile | |
defp label_filter("struct"), do: :struct | |
defp label_filter("runtime"), do: nil | |
defp label_filter(other), do: Mix.raise("unknown --label #{other}") | |
defp file_references(opts) do | |
filter = label_filter(opts[:label]) | |
module_sources = | |
for manifest_path <- manifests(opts), | |
manifest_data = read_manifest(manifest_path, ""), | |
module(module: module, sources: sources) <- manifest_data, | |
source <- sources, | |
source = Enum.find(manifest_data, &match?(source(source: ^source), &1)), | |
do: {module, source} | |
all_modules = MapSet.new(module_sources, &elem(&1, 0)) | |
Map.new(module_sources, fn {current, source} -> | |
source( | |
runtime_references: runtime, | |
struct_references: structs, | |
compile_references: compile, | |
source: file | |
) = source | |
compile_references = | |
modules_to_nodes(compile, :compile, current, source, module_sources, all_modules, filter) | |
struct_references = | |
modules_to_nodes(structs, :struct, current, source, module_sources, all_modules, filter) | |
runtime_references = | |
modules_to_nodes(runtime, nil, current, source, module_sources, all_modules, filter) | |
references = | |
runtime_references | |
|> Map.merge(struct_references) | |
|> Map.merge(compile_references) | |
|> Enum.to_list() | |
{file, references} | |
end) | |
end | |
defp modules_to_nodes(_, label, _, _, _, _, filter) when filter != :all and label != filter do | |
%{} | |
end | |
defp modules_to_nodes(modules, label, current, source, module_sources, all_modules, _filter) do | |
for module <- modules, | |
module != current, | |
module in all_modules, | |
module_sources[module] != source, | |
do: {source(module_sources[module], :source), label}, | |
into: %{} | |
end | |
defp write_graph(file_references, excluded, opts) do | |
{root, file_references} = | |
case {opts[:source], opts[:sink]} do | |
{nil, nil} -> | |
{Enum.map(file_references, &{elem(&1, 0), nil}) -- excluded, file_references} | |
{source, nil} -> | |
if file_references[source] do | |
{Map.get(file_references, source, []), file_references} | |
else | |
Mix.raise("Source could not be found: #{source}") | |
end | |
{nil, sink} -> | |
if file_references[sink] do | |
file_references = filter_for_sink(file_references, sink) | |
roots = | |
file_references | |
|> Map.delete(sink) | |
|> Enum.map(&{elem(&1, 0), nil}) | |
{roots -- excluded, file_references} | |
else | |
Mix.raise("Sink could not be found: #{sink}") | |
end | |
{_, _} -> | |
Mix.raise("mix xref graph expects only one of --source and --sink") | |
end | |
callback = fn {file, type} -> | |
children = if opts[:only_nodes], do: [], else: Map.get(file_references, file, []) | |
type = type && "(#{type})" | |
{{file, type}, children -- excluded} | |
end | |
case opts[:format] do | |
"dot" -> | |
Mix.Utils.write_dot_graph!("xref_graph.dot", "xref graph", root, callback, opts) | |
""" | |
Generated "xref_graph.dot" in the current directory. To generate a PNG: | |
dot -Tpng xref_graph.dot -o xref_graph.png | |
For more options see http://www.graphviz.org/. | |
""" | |
|> String.trim_trailing() | |
|> Mix.shell().info() | |
"stats" -> | |
stats(file_references) | |
_ -> | |
Mix.Utils.print_tree(root, callback, opts) | |
end | |
end | |
defp filter_for_sink(file_references, sink) do | |
file_references | |
|> invert_references() | |
|> apply_filter_for_sink([{sink, nil}], %{}) | |
|> invert_references() | |
end | |
defp apply_filter_for_sink(file_references, new_nodes, acc) do | |
Enum.reduce(new_nodes, acc, fn {new_node_name, _type}, acc -> | |
new_nodes = file_references[new_node_name] | |
if acc[new_node_name] || !new_nodes do | |
acc | |
else | |
apply_filter_for_sink(file_references, new_nodes, Map.put(acc, new_node_name, new_nodes)) | |
end | |
end) | |
end | |
defp invert_references(file_references) do | |
Enum.reduce(file_references, %{}, fn {file, references}, acc -> | |
Enum.reduce(references, acc, fn {reference, type}, acc -> | |
Map.update(acc, reference, [{file, type}], &[{file, type} | &1]) | |
end) | |
end) | |
end | |
defp stats(references) do | |
shell = Mix.shell() | |
counters = | |
Enum.reduce(references, %{compile: 0, struct: 0, nil: 0}, fn {_, deps}, acc -> | |
Enum.reduce(deps, acc, fn {_, value}, acc -> | |
Map.update!(acc, value, &(&1 + 1)) | |
end) | |
end) | |
shell.info("Tracked files: #{map_size(references)} (nodes)") | |
shell.info("Compile dependencies: #{counters.compile} (edges)") | |
shell.info("Structs dependencies: #{counters.struct} (edges)") | |
shell.info("Runtime dependencies: #{counters.nil} (edges)") | |
outgoing = | |
references | |
|> Enum.map(fn {file, deps} -> {length(deps), file} end) | |
|> Enum.sort() | |
|> Enum.take(-10) | |
|> Enum.reverse() | |
shell.info("\nTop #{length(outgoing)} files with most outgoing dependencies:") | |
for {count, file} <- outgoing, do: shell.info(" * #{file} (#{count})") | |
incoming = | |
references | |
|> Enum.reduce(%{}, fn {_, deps}, acc -> | |
Enum.reduce(deps, acc, fn {file, _}, acc -> | |
Map.update(acc, file, 1, &(&1 + 1)) | |
end) | |
end) | |
|> Enum.map(fn {file, count} -> {count, file} end) | |
|> Enum.sort() | |
|> Enum.take(-10) | |
|> Enum.reverse() | |
shell.info("\nTop #{length(incoming)} files with most incoming dependencies:") | |
for {count, file} <- incoming, do: shell.info(" * #{file} (#{count})") | |
end | |
## Helpers | |
defp sources(opts) do | |
for manifest <- manifests(opts), | |
source() = source <- read_manifest(manifest, ""), | |
do: source | |
end | |
defp manifests(opts) do | |
siblings = | |
if opts[:include_siblings] do | |
for %{scm: Mix.SCM.Path, opts: opts} <- Mix.Dep.cached(), | |
opts[:in_umbrella], | |
do: Path.join([opts[:build], ".mix", @manifest]) | |
else | |
[] | |
end | |
[Path.join(Mix.Project.manifest_path(), @manifest) | siblings] | |
end | |
defp merge_entries(entries, filter) do | |
Enum.reduce(entries, %{}, fn {type, locations}, merged_entries -> | |
if filter == :all or filter.(elem(type, 0)) do | |
locations = MapSet.new(locations) | |
Map.update(merged_entries, type, locations, &MapSet.union(&1, locations)) | |
else | |
merged_entries | |
end | |
end) | |
end | |
defp sort_entries(entries) do | |
entries | |
|> Enum.map(fn {type, locations} -> {type, Enum.sort(locations)} end) | |
|> Enum.sort() | |
end | |
defp absolute_locations(locations, base) do | |
Enum.map(locations, &absolute_location(&1, base)) | |
end | |
defp absolute_location({_, _} = location, _), do: location | |
defp absolute_location(line, base), do: {base, line} | |
defp dispatches(source) do | |
source(source, :runtime_dispatches) ++ source(source, :compile_dispatches) | |
end | |
defp return_ok_error_or_abort(entries, kind, opts) do | |
cond do | |
entries == [] -> :ok | |
opts[:abort_if_any] -> Mix.raise("mix xref #{kind} failed: #{kind} calls were found") | |
true -> :error | |
end | |
end | |
## Unused | |
defp unused(excludes, opts) do | |
dispatches = | |
for source <- sources(opts), | |
{module, func_arity_locations} <- dispatches(source), | |
{{func, arity}, _locations} <- func_arity_locations, | |
do: {module, func, arity}, | |
into: MapSet.new() | |
mfas = | |
for manifest <- manifests(opts), | |
module(module: module) <- read_manifest(manifest, ""), | |
definitions = definitions(module), | |
locals = locals(definitions), | |
callbacks = behaviour_callbacks(module), | |
{{func, arity}, kind, meta, _clauses} <- definitions, | |
kind in [:def, :defmacro] and | |
not Keyword.has_key?(meta, :file) and | |
not MapSet.member?(dispatches, {module, func, arity}) and | |
not MapSet.member?(locals, {func, arity}) and | |
not MapSet.member?(callbacks, {func, arity}) and | |
not excluded?(excludes, module, func, arity) and | |
not underscored?(func), | |
do: IO.inspect({module, func, arity}) | |
mfas | |
end | |
defp definitions(module) do | |
with [_ | _] = path <- :code.which(module), | |
{:ok, {_, [debug_info: debug_info]}} <- :beam_lib.chunks(path, [:debug_info]), | |
{:debug_info_v1, backend, data} <- debug_info, | |
{:ok, %{definitions: definitions}} <- backend.debug_info(:elixir_v1, module, data, []) do | |
definitions | |
else | |
_ -> nil | |
end | |
end | |
defp locals(definitions) do | |
locals = | |
Enum.reduce(definitions, [], fn {_, _, _, clauses}, acc -> | |
Enum.reduce(clauses, acc, fn clause, acc -> | |
{_, acc} = | |
Macro.prewalk(clause, acc, fn | |
{local, meta, args} = node, acc when is_atom(local) and is_list(args) -> | |
{node, [{local, length(args)} | acc]} | |
node, acc -> | |
{node, acc} | |
end) | |
acc | |
end) | |
end) | |
MapSet.new(locals) | |
end | |
defp behaviour_callbacks(module) do | |
for {:behaviour, behaviours} <- module.__info__(:attributes), | |
behaviour <- behaviours, | |
{fa, _} <- Kernel.Typespec.beam_callbacks(behaviour) || [], | |
do: fa, | |
into: MapSet.new() | |
end | |
defp underscored?(func) do | |
match?("_" <> _, Atom.to_string(func)) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment