Current section

Files

Jump to
dala lib mix tasks dala.verify.ex
Raw

lib/mix/tasks/dala.verify.ex

defmodule Mix.Tasks.Dala.Verify do
@shortdoc "Verify Dala DSL definitions and project configuration"
@moduledoc """
Verifies Dala DSL definitions in the current project and reports any issues.
## Usage
mix dala.verify # Run all verifications
mix dala.verify --dsl # Verify DSL definitions only
mix dala.verify --components # List all available components
mix dala.verify --strict # Exit with error code on warnings
## What it checks
When `--dala.verify --dsl` is used, the following checks are performed:
- **Unknown component types** — component atoms not in the registry
- **Invalid prop names** — props not accepted by the component (with typo suggestions)
- **Event handler types** — event handler props that are not atoms or {pid, tag} tuples
- **Leaf with children** — children placed inside leaf components
- **Invalid attribute types** — attribute types not in the valid set
- **Missing handlers** — event handlers referenced in UI but no handle_event/3 clause
- **Invalid variants** — invalid variant values on text components
## Examples
mix dala.verify --dsl
mix dala.verify --dsl --strict
mix dala.verify --components
"""
use Mix.Task
@switches [
dsl: :boolean,
components: :boolean,
strict: :boolean
]
@impl Mix.Task
def run(argv) do
{opts, _rest, _invalid} = OptionParser.parse(argv, strict: @switches)
cond do
opts[:components] ->
list_components()
opts[:dsl] ->
verify_dsl(opts)
true ->
verify_all(opts)
end
end
# ── DSL verification ────────────────────────────────────────────────────────
defp verify_dsl(opts) do
Mix.shell().info("\n🔍 Verifying Dala DSL definitions...\n")
modules = find_dala_screens()
if modules == [] do
Mix.shell().info(" No Dala screen modules found in the current project.")
Mix.shell().info(" Make sure your screens use `Dala.Screen` or `Dala.Spark.Dsl`.")
else
all_warnings =
Enum.flat_map(modules, fn module ->
Mix.shell().info(" Checking #{inspect(module)}...")
Dala.Spark.DslVerifier.verify_module(module)
end)
report(all_warnings, opts)
end
end
# ── All verification ────────────────────────────────────────────────────────
defp verify_all(opts) do
Mix.shell().info("\n🔍 Running all Dala verifications...\n")
dsl_warnings =
case find_dala_screens() do
[] ->
Mix.shell().info(" No Dala screen modules found.")
[]
modules ->
Enum.flat_map(modules, &Dala.Spark.DslVerifier.verify_module/1)
end
report(dsl_warnings, opts)
end
# ── Report ───────────────────────────────────────────────────────────────────
defp report([], _opts) do
Mix.shell().info("\n ✓ All DSL definitions look correct. No issues found.\n")
end
defp report(warnings, opts) do
errors = Enum.filter(warnings, &(&1.type == :error))
warns = Enum.filter(warnings, &(&1.type == :warning))
Dala.Spark.DslVerifier.print_warnings(warnings)
Mix.shell().info("\n#{Dala.Spark.DslVerifier.format_report(warnings)}")
if opts[:strict] and (length(errors) > 0 or length(warns) > 0) do
Mix.raise("DSL verification failed with #{length(errors)} error(s) and #{length(warns)} warning(s)")
end
if length(errors) > 0 do
exit({:shutdown, 1})
end
end
# ── Component listing ───────────────────────────────────────────────────────
defp list_components do
components = Dala.Ui.Component.all()
leaf_count = Enum.count(components, fn {_, c} -> c.category == :leaf end)
container_count = Enum.count(components, fn {_, c} -> c.category == :container end)
Mix.shell().info("""
╔══════════════════════════════════════════════════════════════╗
║ Dala UI Component Registry ║
#{leaf_count} leaf + #{container_count} container = #{length(components)} total components ║
╚══════════════════════════════════════════════════════════════╝
""")
Mix.shell().info(" Container components:")
components
|> Enum.filter(fn {_, c} -> c.category == :container end)
|> Enum.sort_by(fn {name, _} -> name end)
|> Enum.each(fn {name, comp} ->
props = Enum.take(comp.props, 5) |> Enum.map_join(", ", &"#{&1}")
extra = if length(comp.props) > 5, do: "...", else: ""
Mix.shell().info(" :#{name}#{comp.doc} (#{props}#{extra})")
end)
Mix.shell().info("\n Leaf components:")
components
|> Enum.filter(fn {_, c} -> c.category == :leaf end)
|> Enum.sort_by(fn {name, _} -> name end)
|> Enum.each(fn {name, comp} ->
props = Enum.take(comp.props, 5) |> Enum.map_join(", ", &"#{&1}")
extra = if length(comp.props) > 5, do: "...", else: ""
Mix.shell().info(" :#{name}#{comp.doc} (#{props}#{extra})")
end)
Mix.shell().info("")
end
# ── Screen module discovery ──────────────────────────────────────────────────
defp find_dala_screens do
# Find all loaded modules that use Dala.Spark.Dsl
:code.all_loaded()
|> Enum.map(fn {mod, _} -> mod end)
|> Enum.filter(fn mod ->
Code.ensure_loaded?(mod) and
function_exported?(mod, :__spark_dsl__, 0)
end)
|> Enum.sort()
end
end