pretty-print-ast
Version:
Formats ASTs as nice readable strings, with colors
155 lines (154 loc) • 5.49 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.formatAst = void 0;
const traverse_1 = __importDefault(require("@suchipi/traverse"));
let isNode = false;
try {
// @ts-ignore process not defined
isNode = process.argv0 != null;
}
catch (err) {
// ignored
}
const identityStringFn = (value) => value;
const fakeKleur = {
red: identityStringFn,
dim: identityStringFn,
magenta: identityStringFn,
blue: identityStringFn,
yellow: identityStringFn,
cyan: identityStringFn,
green: identityStringFn,
bold: identityStringFn,
italic: identityStringFn,
underline: identityStringFn,
};
let autoDetectedKleur = fakeKleur;
if (isNode) {
// I use eval("require") here instead of just require,
// so that webpack et. al don't try to bundle kleur into
// browser stuff, since it won't be used even if it's
// bundled.
autoDetectedKleur = eval("require")("kleur");
}
const primitiveTypes = new Set(["number", "string", "boolean", "undefined"]);
const defaultKeysToIgnore = new Set([
"tokens",
"comments",
"loc",
"type",
"start",
"end",
]);
function isASTNode(value) {
return (typeof value === "object" &&
value != null &&
// @ts-ignore accessing unknown property type
typeof value.type === "string");
}
/**
* Formats an AST into a nice readable format.
*
* @param ast The AST object(s) to format.
* @param options Options that affect the format of the returned string.
* @returns A formatted string containing information about the AST.
*/
function formatAst(ast, { color = true, propertyFilter = (key) => !defaultKeysToIgnore.has(key), } = {}) {
if (typeof ast !== "object" || ast == null) {
throw new Error("Received an unexpected argument type in formatAst: " + ast === null
? "null"
: typeof ast);
}
const kleur = color ? autoDetectedKleur : fakeKleur;
const outputLines = [];
const ids = new WeakMap();
const counts = {};
function assignId(node) {
if (counts[node.type] == null) {
counts[node.type] = 1;
}
const id = counts[node.type]++;
ids.set(node, id);
return id;
}
const inputs = [];
(0, traverse_1.default)(ast, {
before: (value, path) => {
const lastPart = path[path.length - 1];
if (lastPart != null && !propertyFilter(String(lastPart))) {
return traverse_1.default.stop;
}
if (isASTNode(value)) {
assignId(value);
inputs.push({ node: value, path });
}
return;
},
});
function prettifyValue(value) {
if (typeof value === "string") {
return kleur.yellow(JSON.stringify(value));
}
else if (typeof value === "number") {
return kleur.magenta(String(value));
}
else if (Array.isArray(value)) {
return (kleur.dim("[") +
value.map((child) => prettifyValue(child)).join(", ") +
kleur.dim("]"));
}
else if (value == null || typeof value === "boolean") {
return kleur.red(String(value));
}
else if (isASTNode(value)) {
return (kleur.bold(kleur.cyan(value.type)) + kleur.green("#" + ids.get(value)));
}
}
while (inputs.length > 0) {
const { node, path } = inputs.shift();
const pathString = path
.map((part) => (typeof part === "number" ? `[${part}]` : "." + part))
.join("");
const nodeProperties = Object.keys(node).filter((key) => {
if (!propertyFilter(key))
return false;
const value = node[key];
return (value === null ||
primitiveTypes.has(typeof value) ||
(Array.isArray(value) &&
value.every((child) => primitiveTypes.has(typeof child))));
});
const propertiesWithChildren = Object.keys(node).filter((key) => {
if (!propertyFilter(key))
return false;
const value = node[key];
return (isASTNode(value) ||
(Array.isArray(value) && value.length > 0 && value.every(isASTNode)));
});
outputLines.push([
prettifyValue(node) + kleur.italic(kleur.dim(" at root" + pathString)),
...nodeProperties.map((key) => {
const value = node[key];
const prettyValue = prettifyValue(value);
return (" " +
`${kleur.magenta(key)}${kleur.dim(":")} ${prettyValue}`.trim());
}),
...propertiesWithChildren.map((key) => {
const value = node[key];
const prettyValue = prettifyValue(value);
return (" " +
`${kleur.magenta(key)}${Array.isArray(value)
? " (" +
value.length +
` ${value.length === 1 ? "child" : "children"})`
: ""}${kleur.dim(":")} ${prettyValue}`.trim());
}),
...(inputs.length === 0 ? [] : [""]),
].join("\n"));
}
return outputLines.join("\n");
}
exports.formatAst = formatAst;