Current section
Files
Jump to
Current section
Files
lib/mix/tasks/firebird.target.check.ex
defmodule Mix.Tasks.Firebird.Target.Check do
@moduledoc """
Check if Elixir source files are compilable to WASM.
Analyzes source files for WASM compilability without compiling them.
Reports unsupported features, warnings, and optimization suggestions.
## Usage
# Check all @wasm annotated files
mix firebird.target.check
# Check specific files
mix firebird.target.check lib/math.ex lib/utils.ex
# Check specific directories
mix firebird.target.check --dirs lib/wasm_modules
# JSON output
mix firebird.target.check --json
## Exit Codes
- 0: All files are compilable
- 1: One or more files have errors
"""
use Mix.Task
@shortdoc "Check if Elixir files are compilable to WASM"
@switches [
dirs: :string,
json: :boolean,
verbose: :boolean
]
@aliases [
d: :dirs,
v: :verbose
]
@impl Mix.Task
def run(args) do
{opts, paths, _} = OptionParser.parse(args, switches: @switches, aliases: @aliases)
json_mode = Keyword.get(opts, :json, false)
verbose = Keyword.get(opts, :verbose, false)
files = resolve_files(opts, paths)
if Enum.empty?(files) do
unless json_mode do
Mix.shell().info("🔍 No files to check.")
Mix.shell().info(" Add @wasm true annotations or specify files.")
end
:ok
else
{:ok, reports} = Firebird.Target.Diagnostics.analyze_files(files)
if json_mode do
output_json(reports)
else
output_human(reports, verbose)
end
compilable = Enum.count(reports, & &1.compilable)
total = length(reports)
if compilable < total do
exit({:shutdown, 1})
end
:ok
end
end
defp resolve_files(opts, paths) do
explicit =
paths
|> Enum.filter(&File.regular?/1)
from_dirs =
case Keyword.get(opts, :dirs) do
nil ->
if Enum.empty?(explicit) do
# Default: scan lib/ for @wasm annotated files
find_wasm_files(["lib"])
else
[]
end
dirs_str ->
dirs = String.split(dirs_str, ",", trim: true)
find_wasm_files(dirs)
end
(explicit ++ from_dirs) |> Enum.sort() |> Enum.uniq()
end
defp find_wasm_files(dirs) do
dirs
|> Enum.flat_map(fn dir ->
if File.dir?(dir) do
Path.wildcard(Path.join(dir, "**/*.{ex,exs}"))
else
[]
end
end)
|> Enum.filter(fn path ->
case File.read(path) do
{:ok, content} ->
String.contains?(content, "@wasm true") or
String.contains?(path, "wasm_modules")
_ ->
false
end
end)
end
defp output_json(reports) do
data = %{
total: length(reports),
compilable: Enum.count(reports, & &1.compilable),
files:
Enum.map(reports, fn r ->
%{
file: r.file,
module: if(r.module, do: to_string(r.module)),
compilable: r.compilable,
function_count: r.function_count,
estimated_wasm_size: r.estimated_wasm_size,
diagnostics:
Enum.map(r.diagnostics, fn d ->
%{severity: d.severity, message: d.message, line: d.line}
end)
}
end)
}
Mix.shell().info(Jason.encode!(data, pretty: true))
end
defp output_human(reports, verbose) do
Mix.shell().info("🔍 Firebird WASM Compilability Check")
Mix.shell().info("")
Enum.each(reports, fn report ->
status = if report.compilable, do: "✅", else: "❌"
Mix.shell().info(" #{status} #{report.file}")
if report.module do
Mix.shell().info(" Module: #{report.module}")
end
Mix.shell().info(" Functions: #{report.function_count}")
if report.estimated_wasm_size do
Mix.shell().info(" Est. WASM size: ~#{report.estimated_wasm_size} bytes")
end
# Always show errors and warnings
errors = Enum.filter(report.diagnostics, &(&1.severity == :error))
warnings = Enum.filter(report.diagnostics, &(&1.severity == :warning))
hints = Enum.filter(report.diagnostics, &(&1.severity == :hint))
Enum.each(errors, fn d ->
loc = if d.line, do: " (line #{d.line})", else: ""
Mix.shell().error(" ❌ #{d.message}#{loc}")
end)
Enum.each(warnings, fn d ->
loc = if d.line, do: " (line #{d.line})", else: ""
Mix.shell().info(" ⚠️ #{d.message}#{loc}")
end)
if verbose do
Enum.each(hints, fn d ->
Mix.shell().info(" 💡 #{d.message}")
end)
end
Mix.shell().info("")
end)
compilable = Enum.count(reports, & &1.compilable)
total = length(reports)
total_funcs = Enum.map(reports, & &1.function_count) |> Enum.sum()
Mix.shell().info(
"📊 Summary: #{compilable}/#{total} files compilable, #{total_funcs} total functions"
)
end
end