JobyKit.Lint (JobyKit v0.3.2)

Copy Markdown View Source

Lint engine for JobyKit-installed apps. Verifies wrapper-contract invariants by introspecting a manifest module and scanning the host's component source files.

Rules

  • :manifest_drift (error) — a manifest entry points at a function that is not actually a Phoenix function component. Caught first because every other check depends on the entry being live.
  • :missing_data_component (error) — a registered wrapper does not carry data-component="Module.function" in its source.
  • :missing_rest_global (warning) — a registered wrapper does not declare attr :rest, :global, blocking pass-through of phx- and aria- attributes from callers.
  • :unregistered_wrapper (warning) — a function carries a data-component="..." attribute but is not registered in the manifest, making it invisible to /design.json and to agents.
  • :raw_html_primitive (warning) — a .heex template (or ~H block in a .ex) contains a raw <button>, <input>, <textarea>, or <select> outside of a wrapper definition. The kit ships wrappers for all four; reaching past them drops the contract. Exempted per enclosing def: a function whose body carries data-component= is a wrapper, and raw primitives inside it are its own body. Commented-out markup is ignored. Per-line opt-out via <%!-- jobykit:allow-raw-html --%>.
  • :forked_wrapper (warning) — the app excludes a kit component from import JobyKit.CoreComponents and substitutes its own, so upstream fixes to that component never reach it.
  • :duplicated_class_string (warning) — the same long class="..." string appears three or more times, which the kit's own guidance names as the signal that markup wants lifting into a wrapper.
  • :unmarked_component (warning) — a public function component renders markup but carries no data-component at all, so :unregistered_wrapper (which keys off that attribute) cannot see it. The most common way to skip the contract entirely.
  • :assign_new_default (error) — assign_new(assigns, :x, ...) on an attr declared with a default:. The key is always present, so the fallback is dead code. This shipped once as the flash nil-id bug.

Each violation is a map with :rule, :severity, :message, :file, :line, :module, :function. The engine is pure data — formatting and exit codes live in Mix.Tasks.JobyKit.Lint.

Summary

Functions

Run the lint engine. Returns a list of violations in deterministic order (drift first, then per-entry checks in manifest declaration order, then unregistered wrappers sorted by data-component string).

Types

severity()

@type severity() :: :error | :warning | :info

violation()

@type violation() :: %{
  rule: atom(),
  severity: severity(),
  message: String.t(),
  file: String.t() | nil,
  line: non_neg_integer() | nil,
  module: module() | nil,
  function: atom() | nil
}

Functions

run(opts)

@spec run(keyword()) :: [violation()]

Run the lint engine. Returns a list of violations in deterministic order (drift first, then per-entry checks in manifest declaration order, then unregistered wrappers sorted by data-component string).

Options

  • :manifest — required. The host's manifest module (use JobyKit.Manifest).
  • :paths — list of glob patterns to scan for the unregistered- wrapper rule. Defaults to ["lib/**/*.ex"]. Patterns are resolved relative to cwd.