Noizu.Tessera.Spec (Noizu.Tessera v0.2.0)

Copy Markdown View Source

The compiled spec of a tessera component (CONTRACT.md is authoritative).

A spec is built at compile time by use Noizu.Tessera.Component from the attr/prop/event/slot/cy declarations of the component module, and is exposed on that module as __tessera_spec__/0. It is the single source the generated JS contract module (Noizu.Tessera.Codegen.contract_module/1), the Custom Elements Manifest (Noizu.Tessera.Codegen.cem/1) and the Hologram adapter all derive from.

Entry shapes (plain maps):

attrs:   %{name: :display_name, type: :string, required: true, default: nil, line: 3}
         optional: attribute: "custom-name" | false, reflect: true
props:   %{name: :tag_list, type: :list, json: true, default: [], line: 4}
events:  %{name: "persona-select", detail: %{persona_id: :string}, line: 5}
slots:   %{name: "default", line: 6}
styles:  %{kind: :css, value: ":host { … }", line: 7}
         %{kind: :shared, value: "./shared_styles.mjs", line: 8}
css:     %{name: "--persona-card-accent", default: "#6366f1", line: 7}
parts:   %{name: "select-button", line: 8}
lifecycle: %{will_update: %{js: "…", line: 9}, …}  (only declared hooks)

css/part entries are documentation metadata for the Custom Elements Manifest (and the gallery) — they do not ride the wire contract.

Summary

Functions

The first stable extension_api used when a spec declares none (§8.1).

Escapes a string for inclusion inside a JSON double-quoted literal. Every control character (< 0x20) is escaped: LF/CR/TAB as the short forms, all others (backspace, form feed, NUL, ESC, ...) as \u00XX — a raw control byte would be a SyntaxError in the generated .mjs and break JSON.parse.

The JSON-literal form of a spec default value, as it appears in the generated contract module (CONTRACT.md §2). nil becomes null.

Spec rendered to a plain JSON-encodable map (used by __tessera_manifest__/0).

Validates an option value against the contract and raises CompileError with a pointed message otherwise. Used at compile time by Noizu.Tessera.Component and by mix tessera.gen.component.

Validates the DSL kind + type carried in a raw entry tuple.

Types

action_entry()

@type action_entry() :: %{
  :name => atom(),
  :js => String.t(),
  optional(:fn_ast) => Macro.t(),
  optional(:inherited) => boolean(),
  optional(:internal) => boolean(),
  optional(:re_emit) => boolean(),
  optional(:line) => non_neg_integer()
}

attr_entry()

@type attr_entry() :: %{
  :name => Noizu.Tessera.Naming.snake(),
  :type => atom(),
  optional(:required) => boolean(),
  optional(:default) => any(),
  optional(:attribute) => String.t() | false,
  optional(:reflect) => boolean(),
  optional(:line) => non_neg_integer()
}

css_entry()

@type css_entry() :: %{
  :name => String.t(),
  optional(:default) => String.t() | nil,
  optional(:line) => non_neg_integer()
}

event_entry()

@type event_entry() :: %{
  :name => Noizu.Tessera.Naming.kebab(),
  optional(:detail) => %{required(Noizu.Tessera.Naming.snake()) => atom()},
  optional(:line) => non_neg_integer(),
  optional(:inherited) => boolean(),
  optional(:renames) => Noizu.Tessera.Naming.kebab(),
  optional(:renamed_from) => Noizu.Tessera.Naming.kebab()
}

lifecycle()

@type lifecycle() :: %{optional(lifecycle_hook()) => lifecycle_entry()}

lifecycle_entry()

@type lifecycle_entry() :: %{:js => String.t(), optional(:line) => non_neg_integer()}

lifecycle_hook()

@type lifecycle_hook() :: :should_update | :will_update | :first_updated | :updated

part_entry()

@type part_entry() :: %{:name => String.t(), optional(:line) => non_neg_integer()}

prop_entry()

@type prop_entry() :: %{
  :name => Noizu.Tessera.Naming.snake(),
  :type => atom(),
  optional(:json) => boolean(),
  optional(:default) => any(),
  optional(:attribute) => String.t() | false,
  optional(:reflect) => boolean(),
  optional(:line) => non_neg_integer()
}

region_entry()

@type region_entry() :: %{name: atom(), nested: [atom()]}

region_method()

@type region_method() :: %{name: atom(), js: String.t()}

slot_entry()

@type slot_entry() :: %{:name => String.t(), optional(:line) => non_neg_integer()}

state_entry()

@type state_entry() :: %{
  :name => Noizu.Tessera.Naming.snake(),
  :type => atom(),
  optional(:default) => any(),
  optional(:attribute) => String.t() | false,
  optional(:reflect) => boolean(),
  optional(:line) => non_neg_integer()
}

style_entry()

@type style_entry() :: %{
  :kind => :css | :shared,
  :value => String.t(),
  optional(:line) => non_neg_integer()
}

t()

@type t() :: %Noizu.Tessera.Spec{
  actions: [action_entry()],
  attrs: [attr_entry()],
  css_properties: [css_entry()],
  cy: String.t() | nil,
  derived_from: module() | nil,
  events: [event_entry()],
  extension_api: String.t() | nil,
  file: String.t() | nil,
  form_associated: boolean(),
  js: String.t() | nil,
  lifecycle: lifecycle(),
  module: module() | nil,
  parts: [part_entry()],
  priority: integer(),
  props: [prop_entry()],
  region_overrides: [atom()],
  regions: [region_entry()],
  slots: [slot_entry()],
  state: [state_entry()],
  styles: [style_entry()],
  tag: Noizu.Tessera.Naming.kebab() | nil,
  template: template_entry() | nil,
  version: pos_integer()
}

template_entry()

@type template_entry() :: %{
  :raw => String.t(),
  :lexer => :hologram | :minimal,
  :render_js => String.t(),
  optional(:line) => non_neg_integer(),
  optional(:regions) => [region_entry()],
  optional(:region_methods) => [region_method()],
  optional(:region_overrides) => [atom()]
}

Functions

default_extension_api()

@spec default_extension_api() :: String.t()

The first stable extension_api used when a spec declares none (§8.1).

escape_json(value)

@spec escape_json(String.t()) :: String.t()

Escapes a string for inclusion inside a JSON double-quoted literal. Every control character (< 0x20) is escaped: LF/CR/TAB as the short forms, all others (backspace, form feed, NUL, ESC, ...) as \u00XX — a raw control byte would be a SyntaxError in the generated .mjs and break JSON.parse.

json_literal(v)

@spec json_literal(any()) :: String.t()

The JSON-literal form of a spec default value, as it appears in the generated contract module (CONTRACT.md §2). nil becomes null.

to_manifest_map(spec)

@spec to_manifest_map(t()) :: map()

Spec rendered to a plain JSON-encodable map (used by __tessera_manifest__/0).

validate!(kind, name, detail_or_opts, file, line)

@spec validate!(
  atom(),
  String.t() | atom(),
  keyword() | %{required(atom()) => atom()},
  String.t(),
  non_neg_integer()
) :: :ok

Validates an option value against the contract and raises CompileError with a pointed message otherwise. Used at compile time by Noizu.Tessera.Component and by mix tessera.gen.component.

kind is the DSL entry being validated ("attr", "prop", "event", ...).

validate_type!(context, key, type, file, line)

@spec validate_type!(
  String.t() | atom(),
  atom(),
  atom(),
  String.t(),
  non_neg_integer()
) :: :ok

Validates the DSL kind + type carried in a raw entry tuple.