@glint/core
Version:
A CLI for performing typechecking on Glimmer templates
115 lines • 4.68 kB
JavaScript
/**
* In cases where we're unable to parse a template, we still want to
* be able to hold a placeholder mapping so that we can respond sensibly
* to offset transformation queries. This class acts as a standin for
* the proper AST node we were unable to obtain in such cases.
*/
export class ParseError {
constructor() {
this.type = 'ParseError';
}
}
/**
* The Glimmer AST uses `TextNode` for both string arg values on elements
* and for top-level text content floating in the DOM itself. Since we
* want to treat the two differently (namely, string args may have useful
* completion suggestions but plain text doesn't), we use a stand-in
* node for the latter.
*/
export class TextContent {
constructor() {
this.type = 'TextContent';
}
}
/**
* This node represents the root of an embedded template, including any
* boilerplate like tagged template syntax or `<template>` that designates
* the surrounded contents as a template.
*/
export class TemplateEmbedding {
constructor() {
this.type = 'TemplateEmbedding';
}
}
/**
* A `MappingTree` maintains a hierarchy of mappings between ranges of
* locations in original and transformed source strings. These mappings
* are naturally hierarchical due to the tree structure of the underlying
* code.
*
* For instance, given an expression like `{{foo.bar}}` in a template, a
* corresponding expression in TypeScript might be `foo?.bar`. The individual
* identifiers `foo` and `bar` map directly to one another, but the full
* expressions do as well. By maintaining a full hierarchy of these mappings,
* we can always report diagnostics in the template at roughly the same
* level of granularity as TS itself uses when reporting on the transformed
* output.
*/
export default class MappingTree {
constructor(transformedRange, originalRange, children = [], sourceNode) {
this.transformedRange = transformedRange;
this.originalRange = originalRange;
this.children = children;
this.sourceNode = sourceNode;
this.parent = null;
children.forEach((child) => (child.parent = this));
}
/**
* Returns the mapping corresponding to the smallest span in the transformed source
* that contains the given range, or `null` if that range doesn't fall within
* this mapping tree.
*/
narrowestMappingForTransformedRange(range) {
if (range.start < this.transformedRange.start || range.end > this.transformedRange.end) {
return null;
}
for (let child of this.children) {
let mapping = child.narrowestMappingForTransformedRange(range);
if (mapping) {
return mapping;
}
}
return this;
}
/**
* Returns the mapping corresponding to the smallest span in the original source
* that contains the given range, or `null` if that range doesn't fall within
* this mapping tree.
*/
narrowestMappingForOriginalRange(range) {
if (range.start < this.originalRange.start || range.end > this.originalRange.end) {
return null;
}
for (let child of this.children) {
let mapping = child.narrowestMappingForOriginalRange(range);
if (mapping) {
return mapping;
}
}
return this;
}
toDebugString(options) {
let { originalSource, transformedSource, indent = '| ' } = options;
let { sourceNode, originalRange, transformedRange, children } = this;
let hbsStart = options.originalStart + originalRange.start;
let hbsEnd = options.originalStart + originalRange.end;
let tsStart = options.transformedStart + transformedRange.start;
let tsEnd = options.transformedStart + transformedRange.end;
let lines = [];
lines.push(`${indent}Mapping: ${sourceNode.type}`);
lines.push(`${indent}${` hbs(${hbsStart}:${hbsEnd}):`.padEnd(15)}${this.getSourceRange(originalSource, originalRange)}`);
lines.push(`${indent}${` ts(${tsStart}:${tsEnd}):`.padEnd(15)}${this.getSourceRange(transformedSource, transformedRange)}`);
lines.push(indent);
for (let child of children) {
lines.push(child.toDebugString({ ...options, indent: indent + '| ' }));
}
if (children.length) {
lines.push(indent);
}
return lines.map((line) => line.trimEnd()).join('\n');
}
getSourceRange(source, range) {
return source.slice(range.start, range.end).trim().replace(/\n/g, '\\n').replace(/\r/g, '\\r');
}
}
//# sourceMappingURL=mapping-tree.js.map