Spans and mappings
When the core lowers structural tags (e.g. <if>, <for>) to a host’s representation (like ternary chains or .map calls), the emitted code must still map back to the original source positions in the .mx or .solid.mx file. This is crucial for TypeScript type checking (the TS plugin) and editor diagnostics.
Position Mapping
The core consumes Marko’s AST through @marko/compiler and records source spans for elements, tags, attributes, and expressions. When resolving that AST into the IR and emitting through a host emitter, those spans are preserved and turned into mappings.
- Expressions: an expression is sliced from the original source text rather than printed back from a parsed node. That is deliberate, and it is what keeps TypeScript syntax intact: Marko’s parser drops type arguments from the AST, so a regenerated node would be missing them, while the original text retains exactly what the author wrote.
${pick<string>("lo")}emitspick<string>("lo"), type argument and all. - Component tags and attributes: the spans for a component’s tag name, each attribute name, and each
<@name>attribute tag are recorded, so passing a wrong prop to an imported.mxcomponent reports the error at that attribute rather than at the opening tag or the whole call. - Hoisted blocks:
staticblocks,imports,exports andexport interface Inputmap as one span covering the statement’s own source range, so a diagnostic inside one lands within the author’s own line.
A mapping is emitted only when the code is found in both texts — the source span must contain it, and the generated text must still contain it. When either lookup fails, no mapping is emitted at all, deliberately: mapping to a plausible-but-wrong column is worse than not mapping. A diagnostic falling outside every mapping is not surfaced against the MX file.
Known gaps
Two, both narrow:
- A bare generic call in tag or attribute position. Inside
${}a generic call is fine. Written bare — directly as an attribute value, outside any${}— the<is read as the start of a tag, sotitle=pick<string>("lo")misparses rather than losing its types in the map. Wrap it in${}. - Attribute-method bodies. A method written directly on an attribute (
onClick() { … }) reaches the emitter with a synthetic body, so column mapping inside that body can drift.