UNPKG

svelte-ast-print

Version:

Serialize Svelte AST nodes into stringified syntax. A.k.a parse in reverse.

322 lines 8.27 kB
import * as char from "./char.js"; import { Options } from "./option.js"; export function isSvelteOnlyNode(n) { return new Set([ // Attribute-like "Attribute", "SpreadAttribute", /// Directives "AnimateDirective", "BindDirective", "ClassDirective", "LetDirective", "OnDirective", "StyleDirective", "TransitionDirective", "UseDirective", // Block "EachBlock", "IfBlock", "AwaitBlock", "KeyBlock", "SnippetBlock", // CSS "StyleSheet", "Rule", "Atrule", "SelectorList", "Block", "ComplexSelector", "RelativeSelector", "Combinator", "TypeSelector", "IdSelector", "ClassSelector", "AttributeSelector", "PseudoElementSelector", "PseudoClassSelector", "Percentage", "Nth", "NestingSelector", "Declaration", // Element-like "Component", "TitleElement", "SlotElement", "RegularElement", "SvelteBody", "SvelteBoundary", "SvelteComponent", "SvelteDocument", "SvelteElement", "SvelteFragment", "SvelteHead", "SvelteOptionsRaw", "SvelteSelf", "SvelteWindow", // "Fragment", // HTML-node "Comment", "Text", // "Root", "Script", // Tag "ExpressionTag", "HtmlTag", "ConstTag", "DebugTag", "RenderTag", ]).has(n.type); } /** * @internal * Storage to store our garbage collectible state for node that is being processed for printing. * Each function call should generate unique identifier to the passed user options - by default is `{}`. */ const STORE = new WeakMap(); /** * @internal */ export class State { /** * Stateful indentation depth level. */ static depth = 0; static get(n, opts) { if (!STORE.has(n)) STORE.set(n, new State(n, opts)); const state = STORE.get(n); if (!state) throw new Error("Unreachable state"); return state; } node; collector = new Collector(); /** * Transformed options. */ opts; constructor(n, opts) { if (new.target !== State) throw new Error("Unreachable, attempted to access private constructor."); this.opts = new Options(opts); this.node = n; this.collector = new Collector(); } break(depth) { this.collector.break(depth); } add(...pieces) { this.collector.append(...pieces); } get result() { return new Result(this.node, this.opts, this.collector); } } /** * @internal * An instance which can be pushed into collector, and give us a hint to break down the line. */ class Break { /** * Level of the depth for indentation purposes. * @type {number} */ depth; /** * @param {number} depth */ constructor(depth) { this.depth = depth; } } /** * @internal */ class Collector { /** * Collected pieces to be processed later. */ pieces = []; /** * Collect the total from pieces, for line "length" calculation. */ length = 0; [Symbol.iterator]() { return this.pieces.entries(); } append(...pieces) { for (const pc of pieces) { if (!pc) continue; if (Array.isArray(pc)) { this.append(...pc); continue; } if (pc instanceof Break) { // Do nothing? } if (pc instanceof Result) { this.length += pc.collector.length; } if (typeof pc === "string") { this.length += pc.length; } if (pc instanceof Wrapper) { this.length += pc.size; } this.pieces.push(pc); } } break(depth = 0) { State.depth += depth; this.append(new Break(State.depth)); } } /** * Instance with the result of printing _(serializing)_ AST {@link Node}. */ export class Result { node; /** * @internal */ opts; /** * @internal */ collector; /** * @internal */ constructor(node, opts, collector) { this.node = node; this.opts = opts; this.collector = collector; } #lines = []; #latest_line = new Line(0); #latest_depth = 0; #handle_collector(collector) { for (const [idx, pc] of collector) { if (pc instanceof Break) { this.#latest_depth = pc.depth; this.#handle_depth([idx, pc]); continue; } if (pc instanceof Result) { this.#handle_result([idx, pc]); continue; } if (pc instanceof Wrapper) { this.#handle_wrapper([idx, pc]); continue; } // TODO: Will probably have to handle string output from `esrap` - break into lines this.#latest_line.output.push(pc); } } #handle_depth([_idx, d]) { this.#lines.push(this.#latest_line); this.#latest_line = new Line(d.depth); } #handle_wrapper([_idx, w]) { const pcs = w.unwrap(this.#latest_depth); this.#handle_collector(pcs); } #handle_result([_idx, r]) { this.#handle_collector(r.collector); } #cached = false; /** * @internal */ get lines() { if (this.#cached) return this.#lines; this.#cached = true; this.#handle_collector(this.collector); this.#lines.push(this.#latest_line); return this.#lines; } /** * Get the stringified output. */ get code() { return this.lines.map((ln) => ln.toString(this.opts.indent)).join(char.NL); } } /** * @internal */ class Line { /** * Determines at which indentation level this line is supposed to be. */ depth; /** * Content of line. */ output = []; constructor(depth, ...output) { this.depth = depth; for (const o of output) this.output.push(...o); } toString(indent) { return indent.repeat(this.depth) + this.output.join(""); } } /** * @internal */ export class Wrapper { static START; static END; collector = new Collector(); type; constructor(type, ...pieces) { this.type = type; this.collector.append(...pieces); } get size() { // NOTE: Include brackets start/end - hence `+ 2` return this.collector.length + 2; } insert(...pieces) { this.collector.append(...pieces); } unwrap(latest_depth) { switch (this.type) { case "inline": { this.collector.pieces.unshift(this.constructor.START); this.collector.pieces.push(this.constructor.END); return this.collector; } case "mutliline": { this.collector.pieces = this.collector.pieces.flatMap((pc, idx, arr) => { return idx < arr.length - 1 ? [pc, new Break(latest_depth + 1)] : [pc]; }); this.collector.pieces.unshift( // this.constructor.START, new Break(latest_depth + 1)); this.collector.pieces.push( // new Break(latest_depth), this.constructor.END); return this.collector; } case "both": { // TODO: Handle when there's a feature of line breaking when line length is exceeded. this.collector.pieces.unshift(this.constructor.START); this.collector.pieces.push(this.constructor.END); return this.collector; } default: { throw new TypeError(`Unrecognized wrapper type: ${this.type}`); } } } } //# sourceMappingURL=shared.js.map