svelte-ast-print
Version:
Serialize Svelte AST nodes into stringified syntax. A.k.a parse in reverse.
322 lines • 8.27 kB
JavaScript
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