-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathstringify.js
73 lines (66 loc) · 2.5 KB
/
stringify.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
export default function stringify(root) {
let lines = []
function view(e) {
// This local function will never be called with an object. We can
// usually use JSON.stringify, but not always. There are a few special
// cases to handle.
if (["function", "symbol"].includes(typeof e)) {
throw new Error("ASTs should not contain functions or symbols")
}
if (typeof e == "bigint") return e
return JSON.stringify(e).replaceAll(/&/g, "&").replaceAll(/</g, "<")
}
function ast(node, indent = 0, prefix = "") {
if (node === null || typeof node !== "object") return
// Esprima's type field is much nicer than the node's constructor name.
let type = node?.type ?? node.constructor.name
let simpleProps = {}
let objectProps = {}
for (let [k, v] of Object.entries(node)) {
if (typeof v === "object" && v !== null) objectProps[k] = v
else simpleProps[k] = v
}
// We want to print the simple properties first, then the object properties.
// Let's keep the display rather light, eliding the display of many of the
// falses and nulls that are basically default cases. Also we don't want to
// show the type field since we're using that at the beginning of a line.
simpleProps = Object.entries(simpleProps)
.filter(([k, v]) => k !== "type" || v !== type)
.filter(
([k, v]) =>
![
"async",
"generator",
"optional",
"computed",
"delegate",
"expression",
"shorthand",
"method",
"static",
].includes(k) || v !== false
)
.filter(
([k, v]) => !["superClass", "decorators"].includes(k) || v !== null
)
.map(([k, v]) => `<span class='key'>${k}</span>=${view(v)}`)
// Show the line for the current object with its simple properties...
let line = `${" ".repeat(indent)}<span class='key'>${prefix}</span>`
if (prefix) line += ": "
line += `<strong>${type}</strong> ${simpleProps.join(" ")}`
lines.push(`<div style='margin-left:${indent * 20}px'>${line}</div>`)
// ...then for each object property, show indented on following lines.
for (let [k, v] of Object.entries(objectProps)) {
if (Array.isArray(v)) {
for (let [i, e] of v.entries()) {
// Manufacture property names for array elements.
ast(e, indent + 1, `${k}[${i}]`)
}
} else {
ast(v, indent + 1, k)
}
}
}
ast(root)
return lines.join("\n")
}