# `MishkaChelekom.CmsBundle.Heex`
[🔗](https://github.com/mishka-group/mishka_chelekom/blob/v0.0.9/lib/mishka_chelekom/cms_bundle/heex.ex#L1)

HEEx parsing for the v3 bundle exporter. One public surface for
three jobs:

  * `tokenize/2` — return a unified token stream for raw `.heex`
    source by composing `EEx.tokenize/1` with
    `Phoenix.LiveView.TagEngine.Tokenizer.tokenize/5`. This is the same recipe
    `Phoenix.LiveView.HTMLFormatter` (the official `mix format`
    plugin for HEEx) uses internally — neither tokenizer alone
    handles raw HEEx (`EEx.tokenize` leaves HTML opaque inside
    `:text` tokens; `Phoenix.LiveView.TagEngine.Tokenizer` raises on `<%= … %>`
    mid-content).

  * `extract/2` — find every top-level chelekom-component invocation
    in a HEEx demo file and return its **verbatim source** plus
    line. Used by `mix mishka.ui.export --cms` to populate
    `extra.demo_examples` per component.

  * `rewrite/3` — rewrite sibling `<.X>` references to
    `<.component component_name="<kit>-<X-slug>" site={…} …/>`. Used
    by the bundle exporter to make cross-component calls go through
    the runtime helper.

Source slicing (line/column → byte range) is private to this
module — only `extract/2` and `rewrite/3` need it.

## Token shapes

    {:tag, name, attrs, meta}              — `<div>`, `<input/>`
    {:local_component, name, attrs, meta}  — `<.button>`, `<.icon/>`
    {:remote_component, "Mod.X", attrs, meta}
    {:slot, name, attrs, meta}             — `<:inner_block>`
    {:close, kind, name, meta}             — matching close tag
    {:text, raw, meta}                     — text between tags
    {:eex, type, expr, meta}               — `<%= … %>` etc.
    {:eex_comment, body, meta}             — `<%!-- … --%>`

## Failure mode

All three public fns return *something safe* on parse failure:

  * `tokenize/2` → `{:error, message}`
  * `extract/2`  → `[]`
  * `rewrite/3`  → input unchanged

Strict callers (e.g. `mix mishka.ui.verify`) can compare against
`tokenize/2` directly to surface errors at author time.

# `extraction`

```elixir
@type extraction() :: %{
  component: String.t(),
  source: String.t(),
  line: pos_integer()
}
```

# `sibling_set`

```elixir
@type sibling_set() :: MapSet.t(String.t())
```

# `token`

```elixir
@type token() :: tuple()
```

# `extract`

```elixir
@spec extract(String.t() | nil, sibling_set()) :: [extraction()]
```

Walk `text`, extract every top-level invocation whose tag name is in
`kit_components`. Returns extractions in source order. Returns `[]`
on parse failure.

# `rewrite`

```elixir
@spec rewrite(String.t() | nil, sibling_set(), String.t()) :: String.t() | nil
```

Rewrite a template body. Every `<.NAME …>` whose `NAME` is in
`siblings` becomes `<.component component_name="<kit>-<slug>"
site={assigns[:site]} …>`; matching `</.NAME>` becomes
`</.component>`. Other tags, EEx blocks, and string literals are
preserved verbatim.

Returns the input unchanged on parse failure.

# `tokenize`

```elixir
@spec tokenize(
  String.t(),
  keyword()
) :: {:ok, [token()]} | {:error, String.t()}
```

Tokenize raw HEEx source. Returns `{:ok, tokens}` in source order or
`{:error, message}` on parse failure.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
