UNPKG

@glint/core

Version:

A CLI for performing typechecking on Glimmer templates

115 lines 4.68 kB
/** * 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