--- name: position-encoding description: "Position encoding, span types, coordinate systems, path normalization tables for Verter's multi-layer architecture (OXC, Rust, LSP, FFI, VS Code)" --- # Position Encoding & Path Normalization Reference ## Typed Span Types (`verter_span` crate) All Rust span types are defined in `crates/verter_span/src/lib.rs`. Each type enforces a specific coordinate system at compile time: | Type | Meaning | Serde? | Used Where | | ---- | ------- | ------ | ---------- | | `Span` | SFC-absolute byte offsets `[start, end)` | **Yes** (`spanStart`/`spanEnd`) | Analysis types, diagnostics, CSS analysis, CodeTransform, Raw\* template data, CSS variable spans | | `RelativeSpan` | Byte offsets relative to a base stored elsewhere | **No** | CSS scanner internals, OXC binding extraction (`Binding.span`) | | `PartialGeneratedSpan` | Unresolved position in generated output (TSX) | **No** | TSGO response parsing before PositionMapper resolution | | `GeneratedSpan` | Resolved mapping: generated position + SFC origin | **No** | TSGO diagnostics after resolution, codegen error mapping | ### Typed LSP / generated-TSX coordinate wrappers Intra-process boundary newtypes (also in `verter_span`, **no serde**) used by the LSP `PositionMapper` and cross-file navigation stack. No `From` between source-side and generated-side types (no `From for SourceByteRange`); `LspPosition`/`TsPosition` are distinct so a TSX position can never be passed where a Vue LSP position is expected. | Type | Meaning | | ---- | ------- | | `SourceByteOffset` / `SourceByteRange` | byte offset / `[start,end)` range into the original `.vue` source | | `GeneratedByteOffset` / `GeneratedByteRange` | byte offset / `[start,end)` range into the generated TSX | | `GeneratedByteLen` | length of a generated-TSX content region (`content_offset` domain) | | `SourceUtf16Offset` / `GeneratedUtf16Offset` | UTF-16 code-unit offsets (source / generated) | | `LspPosition { line, character }` | 0-based Vue source position, LSP-negotiated encoding (Vue side) | | `TsPosition { line, character }` | 0-based generated-TSX position (TSX side) | ## Typed Span Rules 1. **All data crossing a serialization boundary (serde, MCP, LSP custom protocol, FFI) MUST use `Span` (SFC-absolute).** `RelativeSpan`, `PartialGeneratedSpan`, `GeneratedSpan` don't implement `Serialize`/`Deserialize` — putting them in a serializable struct is a compile error. Convert with `to_absolute(base)` before serialization. 2. **Inter-crate stored types prefer `Span`.** Analysis snapshots, host results, diagnostic structs crossing crate boundaries use `Span`. `RelativeSpan` is intra-crate only (CSS scanner, OXC binding extraction). 3. **`RelativeSpan` is 8 bytes, same as `Span`.** Base offset lives in context (field on parent struct, function parameter). Value is compile-time type safety, not runtime data. 4. **`PartialGeneratedSpan` → `GeneratedSpan` via resolution.** Use `PartialGeneratedSpan` for raw TSGO byte offsets. After PositionMapper resolves SFC origin, use `partial.resolve(origin_span)` to get `GeneratedSpan`. For display use `generated_span.origin`. 5. **`PositionMapper` is a STRICT in-run mapper** (`crates/verter_lsp/src/documents/position_map.rs`). `tsx_to_vue(TsPosition) -> Option` and `vue_to_tsx(LspPosition) -> Option` return `Some` **only** when the query lies strictly inside ONE mapped token's run (the next token on the line starts strictly after the query). No cross-token extrapolation, no snap-to-closest-preceding fallback — unmapped/synthetic content (`_ctx.`/`$setup.` prefixes), gaps, or bridging into the next token return `None`. Within-run character precision IS preserved (in-run offset added to the run's mapped start), but only inside a single mapped run. A range maps only when BOTH endpoints resolve inside the SAME compatibility component — runs are pre-labelled at construction with a `component_id`; two runs share an id only when linked by an unbroken chain of runs contiguous in BOTH generated AND source space (same-line adjacency, or the multiline line-wrap equivalent). Generated-side adjacency alone is NOT sufficient: generated output relocates/repeats source (`MoveOriginal`, repeated v-model emission), so two generated-adjacent but source-discontiguous runs are DIFFERENT components and do not compose — `runs_compatible` is then an O(1) `component_id` comparison. Endpoints are therefore coupled (same component), never mapped independently. Guard: `crates/verter_lsp/tests/cases/position_mapper_strict.rs` (behavioural — including the generated-adjacent-but-source-discontiguous discriminator — + static `ban_cross_token_extrapolation`). ## Key APIs - `Span::new(start, end)` / `RelativeSpan::new(start, end)` / `PartialGeneratedSpan::new(start, end)` - `RelativeSpan::to_absolute(base: u32) -> Span` — add base offset - `Span::to_relative(base: u32) -> RelativeSpan` — subtract base offset - `PartialGeneratedSpan::resolve(origin: Span) -> GeneratedSpan` — resolve with SFC origin - `GeneratedSpan::new(generated: Span, origin: Span)` — create resolved mapping directly - `slice(&self, source: &str) -> &str` — on `Span`, `RelativeSpan`, `PartialGeneratedSpan` - `From` for both `Span` and `RelativeSpan` - `LspPosition::new(line, character)` / `TsPosition::new(line, character)`; `SourceByteRange::new(..)` / `GeneratedByteRange::new(..)` (typed LSP/TSX coordinate wrappers) - **No `From` conversions between span types, nor between source-side and generated-side coordinate wrappers** — type safety enforced at compile time ## CSS Variable Analysis Spans All CSS variable span fields use SFC-absolute `Span`: | Type | Field | Meaning | | ---- | ----- | ------- | | `AnalyzedCustomProperty` | `name_span` | Span of `--name` in declaration | | `AnalyzedCustomProperty` | `value_span` | Span of the value text after `:` | | `CssVarReference` | `span` | Span of entire `var(...)` expression | | `CssVarReference` | `name_span` | Span of variable name within `var()` | | `CssVarFallback` | `span` | Span of fallback text within `var()` | | `CssVarManipulation` | `span` | Span of DOM API call expression (e.g., `setProperty(...)`) | Spans computed as `content_offset + local_offset` during CSS scanning, where `content_offset` is SFC-absolute byte offset of `