UNPKG

ember-source

Version:

A JavaScript framework for creating ambitious web applications

1,722 lines (1,638 loc) 347 kB
import { i as isPresentArray, a as getFirst, g as getLast, b as asPresentArray, m as mapPresentArray } from './present-B1rrjAVM.js'; import { a as assert } from './assert-Zqc4wiAV.js'; import { assertNever } from '../@glimmer/util/index.js'; import { s as setLocalDebugType } from './debug-brand-B1TWjOCH.js'; import { a as assign } from './object-utils-AijlD-JH.js'; import { i as isVoidTag, b as build, v as voidMap, P as Printer } from './transform-resolutions-h1ik8gqW.js'; import { u as unwrap, e as expect, d as dict, b as exhausted } from './collections-DPkjqeA3.js'; import { o as opcodes } from './opcodes-CplRyHl_.js'; import { C as CURRIED_MODIFIER, b as CURRIED_HELPER, a as CURRIED_COMPONENT } from './curried-BZnYakIg.js'; import { a as WellKnownTagNames, W as WellKnownAttrNames } from './well-known-_EVO9RaV.js'; const UNKNOWN_POSITION = Object.freeze({ line: 1, column: 0 }); const SYNTHETIC_LOCATION = Object.freeze({ source: '(synthetic)', start: UNKNOWN_POSITION, end: UNKNOWN_POSITION }); const NON_EXISTENT_LOCATION = Object.freeze({ source: '(nonexistent)', start: UNKNOWN_POSITION, end: UNKNOWN_POSITION }); const BROKEN_LOCATION = Object.freeze({ source: '(broken)', start: UNKNOWN_POSITION, end: UNKNOWN_POSITION }); /** * We have already computed the character position of this offset or span. */ const CHAR_OFFSET_KIND = 'CharPosition'; /** * This offset or span was instantiated with a Handlebars SourcePosition or SourceLocation. Its * character position will be computed on demand. */ const HBS_POSITION_KIND = 'HbsPosition'; /** * for (rare) situations where a node is created but there was no source location (e.g. the name * "default" in default blocks when the word "default" never appeared in source). This is used * by the internals when there is a legitimate reason for the internals to synthesize a node * with no location. */ const INTERNAL_SYNTHETIC_KIND = 'InternalsSynthetic'; /** * For situations where a node represents zero parts of the source (for example, empty arguments). * In general, we attempt to assign these nodes *some* position (empty arguments can be * positioned immediately after the callee), but it's not always possible */ const NON_EXISTENT_KIND = 'NonExistent'; /** * For situations where a source location was expected, but it didn't correspond to the node in * the source. This happens if a plugin creates broken locations. */ const BROKEN_KIND = 'Broken'; /** * These kinds describe spans that don't have a concrete location in the original source. */ function isInvisible(kind) { return kind !== CHAR_OFFSET_KIND && kind !== HBS_POSITION_KIND; } /** * This file implements the DSL used by span and offset in places where they need to exhaustively * consider all combinations of states (Handlebars offsets, character offsets and invisible/broken * offsets). * * It's probably overkill, but it makes the code that uses it clear. It could be refactored or * removed. */ const MatchAny = 'MATCH_ANY'; const IsInvisible = 'IS_INVISIBLE'; class WhenList { _whens; constructor(whens) { this._whens = whens; } first(kind) { for (const when of this._whens) { const value = when.match(kind); if (isPresentArray(value)) { return value[0]; } } return null; } } class When { _map = new Map(); get(pattern, or) { let value = this._map.get(pattern); if (value) { return value; } value = or(); this._map.set(pattern, value); return value; } add(pattern, out) { this._map.set(pattern, out); } match(kind) { const pattern = patternFor(kind); const out = []; const exact = this._map.get(pattern); const fallback = this._map.get(MatchAny); if (exact) { out.push(exact); } if (fallback) { out.push(fallback); } return out; } } function match(callback) { return callback(new Matcher()).validate(); } class Matcher { _whens = new When(); /** * You didn't exhaustively match all possibilities. */ validate() { return (left, right) => this.matchFor(left.kind, right.kind)(left, right); } matchFor(left, right) { const nesteds = this._whens.match(left); assert(isPresentArray(nesteds)); const callback = new WhenList(nesteds).first(right); return callback; } // This big block is the bulk of the heavy lifting in this file. It facilitates exhaustiveness // checking so that matchers can ensure they've actually covered all the cases (and TypeScript // will treat it as an exhaustive match). when(left, right, // eslint-disable-next-line @typescript-eslint/no-explicit-any callback) { this._whens.get(left, () => new When()).add(right, callback); return this; } } function patternFor(kind) { switch (kind) { case BROKEN_KIND: case INTERNAL_SYNTHETIC_KIND: case NON_EXISTENT_KIND: return IsInvisible; default: return kind; } } class SourceSlice { static synthetic(chars) { let offsets = SourceSpan.synthetic(chars); return new SourceSlice({ loc: offsets, chars: chars }); } static load(source, slice) { return new SourceSlice({ loc: SourceSpan.load(source, slice[1]), chars: slice[0] }); } chars; loc; constructor(options) { this.loc = options.loc; this.chars = options.chars; } getString() { return this.chars; } serialize() { return [this.chars, this.loc.serialize()]; } } /** * All spans have these details in common. */ /** * A `SourceSpan` object represents a span of characters inside of a template source. * * There are three kinds of `SourceSpan` objects: * * - `ConcreteSourceSpan`, which contains byte offsets * - `LazySourceSpan`, which contains `SourceLocation`s from the Handlebars AST, which can be * converted to byte offsets on demand. * - `InvisibleSourceSpan`, which represent source strings that aren't present in the source, * because: * - they were created synthetically * - their location is nonsensical (the span is broken) * - they represent nothing in the source (this currently happens only when a bug in the * upstream Handlebars parser fails to assign a location to empty blocks) * * At a high level, all `SourceSpan` objects provide: * * - byte offsets * - source in column and line format * * And you can do these operations on `SourceSpan`s: * * - collapse it to a `SourceSpan` representing its starting or ending position * - slice out some characters, optionally skipping some characters at the beginning or end * - create a new `SourceSpan` with a different starting or ending offset * * All SourceSpan objects implement `SourceLocation`, for compatibility. All SourceSpan * objects have a `toJSON` that emits `SourceLocation`, also for compatibility. * * For compatibility, subclasses of `AbstractSourceSpan` must implement `locDidUpdate`, which * happens when an AST plugin attempts to modify the `start` or `end` of a span directly. * * The goal is to avoid creating any problems for use-cases like AST Explorer. */ class SourceSpan { static get NON_EXISTENT() { return new InvisibleSpan(NON_EXISTENT_KIND, NON_EXISTENT_LOCATION).wrap(); } static load(source, serialized) { if (typeof serialized === 'number') { return SourceSpan.forCharPositions(source, serialized, serialized); } else if (typeof serialized === 'string') { return SourceSpan.synthetic(serialized); } else if (Array.isArray(serialized)) { return SourceSpan.forCharPositions(source, serialized[0], serialized[1]); } else if (serialized === NON_EXISTENT_KIND) { return SourceSpan.NON_EXISTENT; } else if (serialized === BROKEN_KIND) { return SourceSpan.broken(BROKEN_LOCATION); } assertNever(serialized); } static forHbsLoc(source, loc) { const start = new HbsPosition(source, loc.start); const end = new HbsPosition(source, loc.end); return new HbsSpan(source, { start, end }, loc).wrap(); } static forCharPositions(source, startPos, endPos) { const start = new CharPosition(source, startPos); const end = new CharPosition(source, endPos); return new CharPositionSpan(source, { start, end }).wrap(); } static synthetic(chars) { return new InvisibleSpan(INTERNAL_SYNTHETIC_KIND, NON_EXISTENT_LOCATION, chars).wrap(); } static broken(pos = BROKEN_LOCATION) { return new InvisibleSpan(BROKEN_KIND, pos).wrap(); } isInvisible; constructor(data) { this.data = data; this.isInvisible = isInvisible(data.kind); } getStart() { return this.data.getStart().wrap(); } getEnd() { return this.data.getEnd().wrap(); } get loc() { const span = this.data.toHbsSpan(); return span === null ? BROKEN_LOCATION : span.toHbsLoc(); } get module() { return this.data.getModule(); } /** * Get the starting `SourcePosition` for this `SourceSpan`, lazily computing it if needed. */ get startPosition() { return this.loc.start; } /** * Get the ending `SourcePosition` for this `SourceSpan`, lazily computing it if needed. */ get endPosition() { return this.loc.end; } /** * Support converting ASTv1 nodes into a serialized format using JSON.stringify. */ toJSON() { return this.loc; } /** * Create a new span with the current span's end and a new beginning. */ withStart(other) { return span(other.data, this.data.getEnd()); } /** * Create a new span with the current span's beginning and a new ending. */ withEnd(other) { return span(this.data.getStart(), other.data); } asString() { return this.data.asString(); } /** * Convert this `SourceSpan` into a `SourceSlice`. */ toSlice(expected) { const chars = this.data.asString(); assert(expected === undefined || expected === chars, `unexpectedly found ${JSON.stringify(chars)} when slicing source, ` + `but expected ${JSON.stringify(expected)}`); return new SourceSlice({ loc: this, chars: expected || chars }); } /** * For compatibility with SourceLocation in AST plugins * * @deprecated use startPosition instead */ get start() { return this.loc.start; } /** * For compatibility with SourceLocation in AST plugins * * @deprecated use withStart instead */ set start(position) { this.data.locDidUpdate({ start: position }); } /** * For compatibility with SourceLocation in AST plugins * * @deprecated use endPosition instead */ get end() { return this.loc.end; } /** * For compatibility with SourceLocation in AST plugins * * @deprecated use withEnd instead */ set end(position) { this.data.locDidUpdate({ end: position }); } /** * For compatibility with SourceLocation in AST plugins * * @deprecated use module instead */ get source() { return this.module; } collapse(where) { switch (where) { case 'start': return this.getStart().collapsed(); case 'end': return this.getEnd().collapsed(); } } extend(other) { return span(this.data.getStart(), other.data.getEnd()); } serialize() { return this.data.serialize(); } slice({ skipStart = 0, skipEnd = 0 }) { return span(this.getStart().move(skipStart).data, this.getEnd().move(-skipEnd).data); } sliceStartChars({ skipStart = 0, chars }) { return span(this.getStart().move(skipStart).data, this.getStart().move(skipStart + chars).data); } sliceEndChars({ skipEnd = 0, chars }) { return span(this.getEnd().move(skipEnd - chars).data, this.getStart().move(-skipEnd).data); } } class CharPositionSpan { kind = CHAR_OFFSET_KIND; #locPosSpan = null; constructor(source, charPositions) { this.source = source; this.charPositions = charPositions; } wrap() { return new SourceSpan(this); } asString() { return this.source.slice(this.charPositions.start.charPos, this.charPositions.end.charPos); } getModule() { return this.source.module; } getStart() { return this.charPositions.start; } getEnd() { return this.charPositions.end; } locDidUpdate() { } toHbsSpan() { let locPosSpan = this.#locPosSpan; if (locPosSpan === null) { const start = this.charPositions.start.toHbsPos(); const end = this.charPositions.end.toHbsPos(); if (start === null || end === null) { locPosSpan = this.#locPosSpan = BROKEN; } else { locPosSpan = this.#locPosSpan = new HbsSpan(this.source, { start, end }); } } return locPosSpan === BROKEN ? null : locPosSpan; } serialize() { const { start: { charPos: start }, end: { charPos: end } } = this.charPositions; if (start === end) { return start; } else { return [start, end]; } } toCharPosSpan() { return this; } } class HbsSpan { kind = HBS_POSITION_KIND; #charPosSpan = null; // the source location from Handlebars + AST Plugins -- could be wrong #providedHbsLoc; constructor(source, hbsPositions, providedHbsLoc = null) { this.source = source; this.hbsPositions = hbsPositions; this.#providedHbsLoc = providedHbsLoc; } serialize() { const charPos = this.toCharPosSpan(); return charPos === null ? BROKEN_KIND : charPos.wrap().serialize(); } wrap() { return new SourceSpan(this); } updateProvided(pos, edge) { if (this.#providedHbsLoc) { this.#providedHbsLoc[edge] = pos; } // invalidate computed character offsets this.#charPosSpan = null; this.#providedHbsLoc = { start: pos, end: pos }; } locDidUpdate({ start, end }) { if (start !== undefined) { this.updateProvided(start, 'start'); this.hbsPositions.start = new HbsPosition(this.source, start, null); } if (end !== undefined) { this.updateProvided(end, 'end'); this.hbsPositions.end = new HbsPosition(this.source, end, null); } } asString() { const span = this.toCharPosSpan(); return span === null ? '' : span.asString(); } getModule() { return this.source.module; } getStart() { return this.hbsPositions.start; } getEnd() { return this.hbsPositions.end; } toHbsLoc() { return { start: this.hbsPositions.start.hbsPos, end: this.hbsPositions.end.hbsPos }; } toHbsSpan() { return this; } toCharPosSpan() { let charPosSpan = this.#charPosSpan; if (charPosSpan === null) { const start = this.hbsPositions.start.toCharPos(); const end = this.hbsPositions.end.toCharPos(); if (start && end) { charPosSpan = this.#charPosSpan = new CharPositionSpan(this.source, { start, end }); } else { charPosSpan = this.#charPosSpan = BROKEN; return null; } } return charPosSpan === BROKEN ? null : charPosSpan; } } class InvisibleSpan { constructor(kind, // whatever was provided, possibly broken loc, // if the span represents a synthetic string string = null) { this.kind = kind; this.loc = loc; this.string = string; } serialize() { switch (this.kind) { case BROKEN_KIND: case NON_EXISTENT_KIND: return this.kind; case INTERNAL_SYNTHETIC_KIND: return this.string || ''; } } wrap() { return new SourceSpan(this); } asString() { return this.string || ''; } locDidUpdate({ start, end }) { if (start !== undefined) { this.loc.start = start; } if (end !== undefined) { this.loc.end = end; } } getModule() { // TODO: Make this reflect the actual module this span originated from return 'an unknown module'; } getStart() { return new InvisiblePosition(this.kind, this.loc.start); } getEnd() { return new InvisiblePosition(this.kind, this.loc.end); } toCharPosSpan() { return this; } toHbsSpan() { return null; } toHbsLoc() { return BROKEN_LOCATION; } } const span = match(m => m.when(HBS_POSITION_KIND, HBS_POSITION_KIND, (left, right) => new HbsSpan(left.source, { start: left, end: right }).wrap()).when(CHAR_OFFSET_KIND, CHAR_OFFSET_KIND, (left, right) => new CharPositionSpan(left.source, { start: left, end: right }).wrap()).when(CHAR_OFFSET_KIND, HBS_POSITION_KIND, (left, right) => { const rightCharPos = right.toCharPos(); if (rightCharPos === null) { return new InvisibleSpan(BROKEN_KIND, BROKEN_LOCATION).wrap(); } else { return span(left, rightCharPos); } }).when(HBS_POSITION_KIND, CHAR_OFFSET_KIND, (left, right) => { const leftCharPos = left.toCharPos(); if (leftCharPos === null) { return new InvisibleSpan(BROKEN_KIND, BROKEN_LOCATION).wrap(); } else { return span(leftCharPos, right); } }).when(IsInvisible, MatchAny, left => new InvisibleSpan(left.kind, BROKEN_LOCATION).wrap()).when(MatchAny, IsInvisible, (_, right) => new InvisibleSpan(right.kind, BROKEN_LOCATION).wrap())); // `string` includes NON_EXISTENT_KIND and BROKEN_KIND /** * All positions have these details in common. Most notably, all three kinds of positions can * must be able to attempt to convert themselves into {@see CharPosition}. */ /** * Used to indicate that an attempt to convert a `SourcePosition` to a character offset failed. It * is separate from `null` so that `null` can be used to indicate that the computation wasn't yet * attempted (and therefore to cache the failure) */ const BROKEN = 'BROKEN'; /** * A `SourceOffset` represents a single position in the source. * * There are three kinds of backing data for `SourceOffset` objects: * * - `CharPosition`, which contains a character offset into the raw source string * - `HbsPosition`, which contains a `SourcePosition` from the Handlebars AST, which can be * converted to a `CharPosition` on demand. * - `InvisiblePosition`, which represents a position not in source (@see {InvisiblePosition}) */ class SourceOffset { /** * Create a `SourceOffset` from a Handlebars `SourcePosition`. It's stored as-is, and converted * into a character offset on demand, which avoids unnecessarily computing the offset of every * `SourceLocation`, but also means that broken `SourcePosition`s are not always detected. */ static forHbsPos(source, pos) { return new HbsPosition(source, pos, null).wrap(); } /** * Create a `SourceOffset` that corresponds to a broken `SourcePosition`. This means that the * calling code determined (or knows) that the `SourceLocation` doesn't correspond correctly to * any part of the source. */ static broken(pos = UNKNOWN_POSITION) { return new InvisiblePosition(BROKEN_KIND, pos).wrap(); } constructor(data) { this.data = data; } /** * Get the character offset for this `SourceOffset`, if possible. */ get offset() { const charPos = this.data.toCharPos(); return charPos === null ? null : charPos.offset; } /** * Compare this offset with another one. * * If both offsets are `HbsPosition`s, they're equivalent as long as their lines and columns are * the same. This avoids computing offsets unnecessarily. * * Otherwise, two `SourceOffset`s are equivalent if their successfully computed character offsets * are the same. */ eql(right) { return eql(this.data, right.data); } /** * Create a span that starts from this source offset and ends with another source offset. Avoid * computing character offsets if both `SourceOffset`s are still lazy. */ until(other) { return span(this.data, other.data); } /** * Create a `SourceOffset` by moving the character position represented by this source offset * forward or backward (if `by` is negative), if possible. * * If this `SourceOffset` can't compute a valid character offset, `move` returns a broken offset. * * If the resulting character offset is less than 0 or greater than the size of the source, `move` * returns a broken offset. */ move(by) { const charPos = this.data.toCharPos(); if (charPos === null) { return SourceOffset.broken(); } else { const result = charPos.offset + by; if (charPos.source.validate(result)) { return new CharPosition(charPos.source, result).wrap(); } else { return SourceOffset.broken(); } } } /** * Create a new `SourceSpan` that represents a collapsed range at this source offset. Avoid * computing the character offset if it has not already been computed. */ collapsed() { return span(this.data, this.data); } /** * Convert this `SourceOffset` into a Handlebars {@see SourcePosition} for compatibility with * existing plugins. */ toJSON() { return this.data.toJSON(); } } class CharPosition { kind = CHAR_OFFSET_KIND; /** Computed from char offset */ _locPos = null; constructor(source, charPos) { this.source = source; this.charPos = charPos; } /** * This is already a `CharPosition`. * * {@see HbsPosition} for the alternative. */ toCharPos() { return this; } /** * Produce a Handlebars {@see SourcePosition} for this `CharPosition`. If this `CharPosition` was * computed using {@see SourceOffset#move}, this will compute the `SourcePosition` for the offset. */ toJSON() { const hbs = this.toHbsPos(); return hbs === null ? UNKNOWN_POSITION : hbs.toJSON(); } wrap() { return new SourceOffset(this); } /** * A `CharPosition` always has an offset it can produce without any additional computation. */ get offset() { return this.charPos; } /** * Convert the current character offset to an `HbsPosition`, if it was not already computed. Once * a `CharPosition` has computed its `HbsPosition`, it will not need to do compute it again, and * the same `CharPosition` is retained when used as one of the ends of a `SourceSpan`, so * computing the `HbsPosition` should be a one-time operation. */ toHbsPos() { let locPos = this._locPos; if (locPos === null) { const hbsPos = this.source.hbsPosFor(this.charPos); if (hbsPos === null) { this._locPos = locPos = BROKEN; } else { this._locPos = locPos = new HbsPosition(this.source, hbsPos, this.charPos); } } return locPos === BROKEN ? null : locPos; } } class HbsPosition { kind = HBS_POSITION_KIND; _charPos; constructor(source, hbsPos, charPos = null) { this.source = source; this.hbsPos = hbsPos; this._charPos = charPos === null ? null : new CharPosition(source, charPos); } /** * Lazily compute the character offset from the {@see SourcePosition}. Once an `HbsPosition` has * computed its `CharPosition`, it will not need to do compute it again, and the same * `HbsPosition` is retained when used as one of the ends of a `SourceSpan`, so computing the * `CharPosition` should be a one-time operation. */ toCharPos() { let charPos = this._charPos; if (charPos === null) { const charPosNumber = this.source.charPosFor(this.hbsPos); if (charPosNumber === null) { this._charPos = charPos = BROKEN; } else { this._charPos = charPos = new CharPosition(this.source, charPosNumber); } } return charPos === BROKEN ? null : charPos; } /** * Return the {@see SourcePosition} that this `HbsPosition` was instantiated with. This operation * does not need to compute anything. */ toJSON() { return this.hbsPos; } wrap() { return new SourceOffset(this); } /** * This is already an `HbsPosition`. * * {@see CharPosition} for the alternative. */ toHbsPos() { return this; } } class InvisiblePosition { constructor(kind, // whatever was provided, possibly broken pos) { this.kind = kind; this.pos = pos; } /** * A broken position cannot be turned into a {@see CharacterPosition}. */ toCharPos() { return null; } /** * The serialization of an `InvisiblePosition is whatever Handlebars {@see SourcePosition} was * originally identified as broken, non-existent or synthetic. * * If an `InvisiblePosition` never had an source offset at all, this method returns * {@see UNKNOWN_POSITION} for compatibility. */ toJSON() { return this.pos; } wrap() { return new SourceOffset(this); } get offset() { return null; } } /** * Compare two {@see AnyPosition} and determine whether they are equal. * * @see {SourceOffset#eql} */ const eql = match(m => m.when(HBS_POSITION_KIND, HBS_POSITION_KIND, ({ hbsPos: left }, { hbsPos: right }) => left.column === right.column && left.line === right.line).when(CHAR_OFFSET_KIND, CHAR_OFFSET_KIND, ({ charPos: left }, { charPos: right }) => left === right).when(CHAR_OFFSET_KIND, HBS_POSITION_KIND, ({ offset: left }, right) => left === right.toCharPos()?.offset).when(HBS_POSITION_KIND, CHAR_OFFSET_KIND, (left, { offset: right }) => left.toCharPos()?.offset === right).when(MatchAny, MatchAny, () => false)); class Source { static from(source, options = {}) { return new Source(source, options.meta?.moduleName); } /** Char offset of each `\n` in the source. */ #newlineOffsets; constructor(source, module = 'an unknown module') { this.source = source; this.module = module; setLocalDebugType('syntax:source', this); this.#newlineOffsets = computeNewlineOffsets(source); } /** * Validate that the character offset represents a position in the source string. */ validate(offset) { return offset >= 0 && offset <= this.source.length; } slice(start, end) { return this.source.slice(start, end); } offsetFor(line, column) { return SourceOffset.forHbsPos(this, { line, column }); } spanFor({ start, end }) { return SourceSpan.forHbsLoc(this, { start: { line: start.line, column: start.column }, end: { line: end.line, column: end.column } }); } hbsPosFor(offset) { if (offset < 0 || offset > this.source.length) return null; const lineIdx = lowerBound(this.#newlineOffsets, offset); return { line: lineIdx + 1, column: offset - this.#lineStartFor(lineIdx) }; } charPosFor({ line, column }) { const lineIdx = line - 1; // Valid lines are [0, newlineOffsets.length]. Anything else has no offset. if (lineIdx < 0 || lineIdx > this.#newlineOffsets.length || column < 0) return null; const lineStart = lineIdx === 0 ? 0 : this.#newlineOffsets[lineIdx - 1] + 1; const lineEnd = this.#newlineOffsets[lineIdx] ?? this.source.length; const target = lineStart + column; if (target <= lineEnd) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition { const roundTrip = this.hbsPosFor(target); assert(roundTrip.line === line); assert(roundTrip.column === column); } return target; } return lineEnd; } #lineStartFor(lineIdx) { if (lineIdx === 0) return 0; const prevNl = this.#newlineOffsets[lineIdx - 1]; return prevNl === undefined ? 0 : prevNl + 1; } } function computeNewlineOffsets(source) { const offsets = []; for (let i = source.indexOf('\n'); i !== -1; i = source.indexOf('\n', i + 1)) { offsets.push(i); } return offsets; } /** Lower-bound binary search: smallest i with arr[i] >= target, else arr.length. */ function lowerBound(arr, target) { let lo = 0; let hi = arr.length; while (lo < hi) { const mid = lo + hi >>> 1; // mid is in [lo, hi) so always a valid index. if (arr[mid] < target) lo = mid + 1;else hi = mid; } return lo; } class SpanList { static range(span, fallback = SourceSpan.NON_EXISTENT) { return new SpanList(span.map(loc)).getRangeOffset(fallback); } _span; constructor(span = []) { this._span = span; } add(offset) { this._span.push(offset); } getRangeOffset(fallback) { if (isPresentArray(this._span)) { let first = getFirst(this._span); let last = getLast(this._span); return first.extend(last); } else { return fallback; } } } function loc(span) { if (Array.isArray(span)) { let first = getFirst(span); let last = getLast(span); return loc(first).extend(loc(last)); } else if (span instanceof SourceSpan) { return span; } else { return span.loc; } } function hasSpan(span) { if (Array.isArray(span) && span.length === 0) { return false; } return true; } function maybeLoc(location, fallback) { if (hasSpan(location)) { return loc(location); } else { return fallback; } } const errorProps = ['description', 'fileName', 'lineNumber', 'endLineNumber', 'message', 'name', 'number', 'stack']; function Exception(message, node) { let loc = node && node.loc, line, endLineNumber, column, endColumn; if (loc) { line = loc.start.line; endLineNumber = loc.end.line; column = loc.start.column; endColumn = loc.end.column; message += ' - ' + line + ':' + column; } let tmp = Error.prototype.constructor.call(this, message); // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. for (let idx = 0; idx < errorProps.length; idx++) { this[errorProps[idx]] = tmp[errorProps[idx]]; } /* istanbul ignore else */ if (Error.captureStackTrace) { Error.captureStackTrace(this, Exception); } try { if (loc) { this.lineNumber = line; this.endLineNumber = endLineNumber; // Work around issue under safari where we can't directly set the column value /* istanbul ignore next */ if (Object.defineProperty) { Object.defineProperty(this, 'column', { value: column, enumerable: true }); Object.defineProperty(this, 'endColumn', { value: endColumn, enumerable: true }); } else { this.column = column; this.endColumn = endColumn; } } } catch (nop) { /* Ignore if the browser is very particular */ } } Exception.prototype = new Error(); function Visitor() { this.parents = []; } Visitor.prototype = { constructor: Visitor, mutating: false, // Visits a given value. If mutating, will replace the value if necessary. acceptKey: function (node, name) { let value = this.accept(node[name]); if (this.mutating) { // Hacky sanity check: This may have a few false positives for type for the helper // methods but will generally do the right thing without a lot of overhead. if (value && !Visitor.prototype[value.type]) { throw new Exception('Unexpected node type "' + value.type + '" found when accepting ' + name + ' on ' + node.type); } node[name] = value; } }, // Performs an accept operation with added sanity check to ensure // required keys are not removed. acceptRequired: function (node, name) { this.acceptKey(node, name); if (!node[name]) { throw new Exception(node.type + ' requires ' + name); } }, // Traverses a given array. If mutating, empty responses will be removed // for child elements. acceptArray: function (array) { for (let i = 0, l = array.length; i < l; i++) { this.acceptKey(array, i); if (!array[i]) { array.splice(i, 1); i--; l--; } } }, accept: function (object) { if (!object) { return; } /* istanbul ignore next: Sanity code */ if (!this[object.type]) { throw new Exception('Unknown type: ' + object.type, object); } if (this.current) { this.parents.unshift(this.current); } this.current = object; let ret = this[object.type](object); this.current = this.parents.shift(); if (!this.mutating || ret) { return ret; } else if (ret !== false) { return object; } }, Program: function (program) { this.acceptArray(program.body); }, MustacheStatement: visitSubExpression, Decorator: visitSubExpression, BlockStatement: visitBlock, DecoratorBlock: visitBlock, PartialStatement: visitPartial, PartialBlockStatement: function (partial) { visitPartial.call(this, partial); this.acceptKey(partial, 'program'); }, ContentStatement: function /* content */ () {}, CommentStatement: function /* comment */ () {}, SubExpression: visitSubExpression, PathExpression: function /* path */ () {}, StringLiteral: function /* string */ () {}, NumberLiteral: function /* number */ () {}, BooleanLiteral: function /* bool */ () {}, UndefinedLiteral: function /* literal */ () {}, NullLiteral: function /* literal */ () {}, Hash: function (hash) { this.acceptArray(hash.pairs); }, HashPair: function (pair) { this.acceptRequired(pair, 'value'); } }; function visitSubExpression(mustache) { this.acceptRequired(mustache, 'path'); this.acceptArray(mustache.params); this.acceptKey(mustache, 'hash'); } function visitBlock(block) { visitSubExpression.call(this, block); this.acceptKey(block, 'program'); this.acceptKey(block, 'inverse'); } function visitPartial(partial) { this.acceptRequired(partial, 'name'); this.acceptArray(partial.params); this.acceptKey(partial, 'hash'); } function WhitespaceControl(options = {}) { this.options = options; } WhitespaceControl.prototype = new Visitor(); WhitespaceControl.prototype.Program = function (program) { const doStandalone = !this.options.ignoreStandalone; let isRoot = !this.isRootSeen; this.isRootSeen = true; let body = program.body; for (let i = 0, l = body.length; i < l; i++) { let current = body[i], strip = this.accept(current); if (!strip) { continue; } let _isPrevWhitespace = isPrevWhitespace(body, i, isRoot), _isNextWhitespace = isNextWhitespace(body, i, isRoot), openStandalone = strip.openStandalone && _isPrevWhitespace, closeStandalone = strip.closeStandalone && _isNextWhitespace, inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace; if (strip.close) { omitRight(body, i, true); } if (strip.open) { omitLeft(body, i, true); } if (doStandalone && inlineStandalone) { omitRight(body, i); if (omitLeft(body, i)) { // If we are on a standalone node, save the indent info for partials if (current.type === 'PartialStatement') { // Pull out the whitespace from the final line current.indent = /([ \t]+$)/.exec(body[i - 1].original)[1]; } } } if (doStandalone && openStandalone) { omitRight((current.program || current.inverse).body); // Strip out the previous content node if it's whitespace only omitLeft(body, i); } if (doStandalone && closeStandalone) { // Always strip the next node omitRight(body, i); omitLeft((current.inverse || current.program).body); } } return program; }; WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.DecoratorBlock = WhitespaceControl.prototype.PartialBlockStatement = function (block) { this.accept(block.program); this.accept(block.inverse); // Find the inverse program that is involved with whitespace stripping. let program = block.program || block.inverse, inverse = block.program && block.inverse, firstInverse = inverse, lastInverse = inverse; if (inverse && inverse.chained) { firstInverse = inverse.body[0].program; // Walk the inverse chain to find the last inverse that is actually in the chain. while (lastInverse.chained) { lastInverse = lastInverse.body[lastInverse.body.length - 1].program; } } let strip = { open: block.openStrip.open, close: block.closeStrip.close, // Determine the standalone candidacy. Basically flag our content as being possibly standalone // so our parent can determine if we actually are standalone openStandalone: isNextWhitespace(program.body), closeStandalone: isPrevWhitespace((firstInverse || program).body) }; if (block.openStrip.close) { omitRight(program.body, null, true); } if (inverse) { let inverseStrip = block.inverseStrip; if (inverseStrip.open) { omitLeft(program.body, null, true); } if (inverseStrip.close) { omitRight(firstInverse.body, null, true); } if (block.closeStrip.open) { omitLeft(lastInverse.body, null, true); } // Find standalone else statements if (!this.options.ignoreStandalone && isPrevWhitespace(program.body) && isNextWhitespace(firstInverse.body)) { omitLeft(program.body); omitRight(firstInverse.body); } } else if (block.closeStrip.open) { omitLeft(program.body, null, true); } return strip; }; WhitespaceControl.prototype.Decorator = WhitespaceControl.prototype.MustacheStatement = function (mustache) { return mustache.strip; }; WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.CommentStatement = function (node) { /* istanbul ignore next */ let strip = node.strip || {}; return { inlineStandalone: true, open: strip.open, close: strip.close }; }; function isPrevWhitespace(body, i, isRoot) { if (i === undefined) { i = body.length; } // Nodes that end with newlines are considered whitespace (but are special // cased for strip operations) let prev = body[i - 1], sibling = body[i - 2]; if (!prev) { return isRoot; } if (prev.type === 'ContentStatement') { return (sibling || !isRoot ? /\r?\n\s*?$/ : /(^|\r?\n)\s*?$/).test(prev.original); } } function isNextWhitespace(body, i, isRoot) { if (i === undefined) { i = -1; } let next = body[i + 1], sibling = body[i + 2]; if (!next) { return isRoot; } if (next.type === 'ContentStatement') { return (sibling || !isRoot ? /^\s*?\r?\n/ : /^\s*?(\r?\n|$)/).test(next.original); } } // Marks the node to the right of the position as omitted. // I.e. {{foo}}' ' will mark the ' ' node as omitted. // // If i is undefined, then the first child will be marked as such. // // If multiple is truthy then all whitespace will be stripped out until non-whitespace // content is met. function omitRight(body, i, multiple) { let current = body[i == null ? 0 : i + 1]; if (!current || current.type !== 'ContentStatement' || !multiple && current.rightStripped) { return; } let original = current.value; current.value = current.value.replace(multiple ? /^\s+/ : /^[ \t]*\r?\n?/, ''); current.rightStripped = current.value !== original; } // Marks the node to the left of the position as omitted. // I.e. ' '{{foo}} will mark the ' ' node as omitted. // // If i is undefined then the last child will be marked as such. // // If multiple is truthy then all whitespace will be stripped out until non-whitespace // content is met. function omitLeft(body, i, multiple) { let current = body[i == null ? body.length - 1 : i - 1]; if (!current || current.type !== 'ContentStatement' || !multiple && current.leftStripped) { return; } // We omit the last node if it's whitespace only and not preceded by a non-content node. let original = current.value; current.value = current.value.replace(multiple ? /\s+$/ : /[ \t]+$/, ''); current.leftStripped = current.value !== original; return current.leftStripped; } // @ts-nocheck /* parser generated by jison 0.4.18 */ /* Returns a Parser object of the following structure: Parser: { yy: {} } Parser.prototype: { yy: {}, trace: function(), symbols_: {associative list: name ==> number}, terminals_: {associative list: number ==> name}, productions_: [...], performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$), table: [...], defaultActions: {...}, parseError: function(str, hash), parse: function(input), lexer: { EOF: 1, parseError: function(str, hash), setInput: function(input), input: function(), unput: function(str), more: function(), less: function(n), pastInput: function(), upcomingInput: function(), showPosition: function(), test_match: function(regex_match_array, rule_index), next: function(), lex: function(), begin: function(condition), popState: function(), _currentRules: function(), topState: function(), pushState: function(condition), options: { ranges: boolean (optional: true ==> token location info will include a .range[] member) flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match) backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code) }, performAction: function(yy, yy_, $avoiding_name_collisions, YY_START), rules: [...], conditions: {associative list: name ==> set}, } } token location info (@$, _$, etc.): { first_line: n, last_line: n, first_column: n, last_column: n, range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based) } the parseError function receives a 'hash' object with these members for lexer and parser errors: { text: (matched text) token: (the produced terminal token, if any) line: (yylineno) } while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: { loc: (yylloc) expected: (string describing the set of expected tokens) recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error) } */ var parser = function () { var o = function (k, v, o, l) { for (o = o || {}, l = k.length; l--; o[k[l]] = v); return o; }, $V0 = [2, 52], $V1 = [1, 20], $V2 = [5, 14, 15, 19, 29, 34, 39, 44, 47, 48, 53, 57, 61], $V3 = [1, 44], $V4 = [1, 40], $V5 = [1, 43], $V6 = [1, 33], $V7 = [1, 34], $V8 = [1, 35], $V9 = [1, 36], $Va = [1, 37], $Vb = [1, 42], $Vc = [1, 46], $Vd = [14, 15, 19, 29, 34, 39, 44, 47, 48, 53, 57, 61], $Ve = [14, 15, 19, 29, 34, 44, 47, 48, 53, 57, 61], $Vf = [15, 18], $Vg = [14, 15, 19, 29, 34, 47, 48, 53, 57, 61], $Vh = [33, 67, 73, 75, 84, 85, 86, 87, 88, 89], $Vi = [23, 33, 56, 67, 68, 73, 75, 77, 79, 84, 85, 86, 87, 88, 89], $Vj = [1, 62], $Vk = [1, 63], $Vl = [23, 33, 56, 68, 73, 79], $Vm = [23, 33, 56, 67, 68, 73, 75, 77, 79, 84, 85, 86, 87, 88, 89, 92, 93], $Vn = [2, 51], $Vo = [1, 64], $Vp = [67, 73, 75, 77, 84, 85, 86, 87, 88, 89], $Vq = [56, 67, 73, 75, 84, 85, 86, 87, 88, 89], $Vr = [1, 75], $Vs = [1, 76], $Vt = [1, 83], $Vu = [33, 67, 73, 75, 79, 84, 85, 86, 87, 88, 89], $Vv = [23, 67, 73, 75, 84, 85, 86, 87, 88, 89], $Vw = [67, 68, 73, 75, 84, 85, 86, 87, 88, 89], $Vx = [33, 79], $Vy = [1, 134], $Vz = [73, 81]; var parser = { trace: function trace() {}, yy: {}, symbols_: { error: 2, root: 3, program: 4, EOF: 5, program_repetition0: 6, statement: 7, mustache: 8, block: 9, rawBlock: 10, partial: 11, partialBlock: 12, content: 13, COMMENT: 14, CONTENT: 15, openRawBlock: 16, rawBlock_repetition0: 17, END_RAW_BLOCK: 18, OPEN_RAW_BLOCK: 19, helperName: 20, openRawBlock_repetition0: 21, openRawBlock_option0: 22, CLOSE_RAW_BLOCK: 23, openBlock: 24, block_option0: 25, closeBlock: 26, openInverse: 27, block_option1: 28, OPEN_BLOCK: 29, openBlock_repetition0: 30, openBlock_option0: 31, openBlock_option1: 32, CLOSE: 33, OPEN_INVERSE: 34, openInverse_repetition0: 35, openInverse_option0: 36, openInverse_option1: 37, openInverseChain: 38, OPEN_INVERSE_CHAIN: 39, openInverseChain_repetition0: 40, openInverseChain_option0: 41, openInverseChain_option1: 42, inverseAndProgram: 43, INVERSE: 44, inverseChain: 45, inverseChain_option0: 46, OPEN_ENDBLOCK: 47, OPEN: 48, hash: 49, expr: 50, mustache_repetition0: 51, mustache_option0: 52, OPEN_UNESCAPED: 53, mustache_repetition1: 54, mustache_option1: 55, CLOSE_UNESCAPED: 56, OPEN_PARTIAL: 57, partial_repetition0: 58, partial_option0: 59, openPartialBlock: 60, OPEN_PARTIAL_BLOCK: 61, openPartialBlock_repetition0: 62, openPartialBlock_option0: 63, exprHead: 64, arrayLiteral: 65, sexpr: 66, OPEN_SEXPR: 67, CLOSE_SEXPR: 68, sexpr_repetition0: 69, sexpr_option0: 70, hash_repetition_plus0: 71, hashSegment: 72, ID: 73, EQUALS: 74, OPEN_ARRAY: 75, arrayLiteral_repetition0: 76, CLOSE_ARRAY: 77, blockParams: 78, OPEN_BLOCK_PARAMS: 79, blockParams_repetition_plus0: 80, CLOSE_BLOCK_PARAMS: 81, path: 82, dataName: 83, STRING: 84, NUMBER: 85, BOOLEAN: 86, UNDEFINED: 87, NULL: 88, DATA: 89, pathSegments: 90, sep: 91, SEP: 92, PRIVATE_SEP: 93, $accept: 0, $end: 1 }, terminals_: { 2: 'error', 5: 'EOF', 14: 'COMMENT', 15: 'CONTENT', 18: 'END_RAW_BLOCK', 19: 'OPEN_RAW_BLOCK', 23: 'CLOSE_RAW_BLOCK', 29: 'OPEN_BLOCK', 33: 'CLOSE', 34: 'OPEN_INVERSE', 39: 'OPEN_INVERSE_CHAIN', 44: 'INVERSE', 47: 'OPEN_ENDBLOCK', 48: 'OPEN', 53: 'OPEN_UNESCAPED', 56: 'CLOSE_UNESCAPED', 57: 'OPEN_PARTIAL', 61: 'OPEN_PARTIAL_BLOCK', 67: 'OPEN_SEXPR', 68: 'CLOSE_SEXPR', 73: 'ID', 74: 'EQUALS', 75: 'OPEN_ARRAY', 77: 'CLOSE_ARRAY', 79: 'OPEN_BLOCK_PARAMS', 81: 'CLOSE_BLOCK_PARAMS', 84: 'STRING', 85: 'NUMBER', 86: 'BOOLEAN', 87: 'UNDEFINED', 88: 'NULL', 89: 'DATA', 92: 'SEP', 93: 'PRIVATE_SEP' }, productions_: [0, [3, 2], [4, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [13, 1], [10, 3], [16, 5], [9, 4], [9, 4], [24, 6], [27, 6], [38, 6], [43, 2], [45, 3], [45, 1], [26, 3], [8, 3], [8, 5], [8, 5], [11, 5], [12, 3], [60, 5], [50, 1], [50, 1], [64, 1], [64, 1], [66, 3], [66, 5], [49, 1], [72, 3], [65, 3], [78, 3], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [83, 2], [91, 1], [91, 1], [82, 3], [82, 1], [90, 3], [90, 1], [6, 0], [6, 2], [17, 0], [17, 2], [21, 0], [21, 2], [22, 0], [22, 1], [25, 0], [25, 1], [28, 0], [28, 1], [30, 0], [30, 2], [31, 0], [31, 1], [32, 0], [32, 1], [35, 0], [35, 2], [36, 0], [36, 1], [37, 0], [37, 1], [40, 0], [40, 2], [41, 0], [41, 1], [42, 0], [42, 1], [46, 0], [46, 1], [51, 0], [51, 2], [52, 0], [52, 1], [54, 0], [54, 2], [55, 0], [55, 1], [58, 0], [58, 2], [59, 0], [59, 1], [62, 0], [62, 2], [63, 0], [63, 1], [69, 0], [69, 2], [70, 0], [70, 1], [71, 1], [71, 2], [76, 0], [76, 2], [80, 1], [80, 2]], performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) { /* this == yyval */ var $0 = $$.length - 1; switch (yystate) { case 1: return $$[$0 - 1]; case 2: this.$ = yy.prepareProgram($$[$0]); break; case 3: case 4: case 5: case 6: case 7: case 8: case 20: case 28: case 29: case 30: case 31: case 38: case 39: case 46: case 47: this.$ = $$[$0]; break; case 9: this.$ = { type: 'CommentStatement', value: yy.stripComment($$[$0]), strip: yy.stripFlags($$[$0], $$[$0]), loc: yy.locInfo(this._$) }; break; case 10: this.$ = { type: 'ContentStatement', original: $$[$0], value: $$[$0], loc: yy.locInfo(this._$) }; break; case 11: this.$ = yy.prepareRawBlock($$[$0 - 2], $$[$0 - 1], $$[$0], this._$); break; case 12: this.$ = { path: $$[$0 - 3], params: $$[$0 - 2], hash: $$[$0 - 1] }; break; case 13: this.$ = yy.prepareBlock($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0], false, this._$); break; case 14: this.$ = yy.prepareBlock($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0], true, this._$); break; case 15: this.$ = { open: $$[$0 - 5], path: $$[$0 - 4], params: $$[$0 - 3], hash: $$[$0 - 2], blockParams: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 5], $$[$0]) }; break; case 16: case 17: this.$ = { path: $$[$0 - 4], params: $$[$0 - 3], hash: $$[$0 - 2], blockParams: $$[$0 - 1], strip: y