-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathderivation-nodes.ts
More file actions
133 lines (114 loc) · 5.47 KB
/
derivation-nodes.ts
File metadata and controls
133 lines (114 loc) · 5.47 KB
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import type { LJError, RefinementError } from "../../../types/diagnostics";
import type { DerivationNode, ValDerivationNode } from "../../../types/derivation-nodes";
// Handles rendering and interaction of derivation nodes in refinement errors
const expansionsMap = new Map<string, Set<string>>();
function getExpansions(errorId: string): Set<string> {
if (!expansionsMap.has(errorId)) {
expansionsMap.set(errorId, new Set());
}
return expansionsMap.get(errorId)!;
}
function renderJsonTree(
error: RefinementError,
node: DerivationNode | undefined,
errorId: string,
path: string,
expandedPaths: Set<string>
): string {
if (!node)
return '<span class="node-value">undefined</span>';
const hasOrigin = Boolean("origin" in node && node.origin);
const isExpanded = expandedPaths.has(path);
if (hasOrigin && isExpanded && "origin" in node) {
return renderJsonTree(error, node.origin, errorId, path, expandedPaths);
}
// VarDerivationNode
if ("var" in node) {
const placement = error.translationTable?.[node.var];
if (!placement) return `<span class="node-var">${node.var}</span>`;
const filePath = (placement as any)?.file ?? error.file;
const filename = filePath.split("/").pop() ?? "";
const tooltipData = `${filename}:${(placement.position?.lineStart ?? 0) + 1}`;
const classes = `node-var tooltip clickable ${hasOrigin ? "derivable-node" : ""}`.trim();
const attrs = hasOrigin ? ` data-node-path="${path}" data-error-id="${errorId}"` : "";
const fileAttr = ` data-file="${filePath}" data-line="${placement.position?.lineStart ?? 0}" data-column="${placement.position?.colStart ?? 0}"`;
return `<span class="${classes}" data-tooltip="${tooltipData}"${fileAttr}${attrs}>${node.var}</span>`;
}
// ValDerivationNode
if ("value" in node) {
const valueNode = node as ValDerivationNode;
const valClass = typeof valueNode.value === "number" ? "node-number" : typeof valueNode.value === "boolean" ? "node-boolean" : "node-value";
const clickableClass = hasOrigin ? "derivable-node clickable" : "";
const pathAttr = hasOrigin ? `data-node-path="${path}"` : "";
const idAttr = hasOrigin ? `data-error-id="${errorId}"` : "";
return `<span class="${valClass} ${clickableClass}" ${pathAttr} ${idAttr}>${valueNode.value}</span>`;
}
// BinaryDerivationNode
if ("left" in node && "right" in node) {
const leftHtml = renderJsonTree(error, node.left, errorId, `${path}.left`, expandedPaths);
const rightHtml = renderJsonTree(error, node.right, errorId, `${path}.right`, expandedPaths);
return `${leftHtml} ${node.op} ${rightHtml}`;
}
// UnaryDerivationNode
if ("operand" in node) {
const operandHtml = renderJsonTree(error, node.operand, errorId, `${path}.operand`, expandedPaths);
return node.op === "-" ? `${node.op}(${operandHtml})` : `${node.op}${operandHtml}`;
}
// IteDerivationNode
if ("condition" in node && "thenBranch" in node && "elseBranch" in node) {
const conditionHtml = renderJsonTree(error, node.condition, errorId, `${path}.condition`, expandedPaths);
const thenBranchHtml = renderJsonTree(error, node.thenBranch, errorId, `${path}.thenBranch`, expandedPaths);
const elseBranchHtml = renderJsonTree(error, node.elseBranch, errorId, `${path}.elseBranch`, expandedPaths);
return `${conditionHtml} ? ${thenBranchHtml} : ${elseBranchHtml}`;
}
// fallback
return `<span class="node-value">${JSON.stringify(node)}</span>`;
}
function hashError(error: LJError): string {
const content = `${error.title}|${error.message}|${error.file}|${error.position?.lineStart ?? 0}`;
let hash = 0;
for (let i = 0; i < content.length; i++) {
const char = content.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}
return `error_${Math.abs(hash)}`;
}
export function handleDerivableNodeClick(target?: any): boolean {
if (!target) return false;
const nodePath = target.getAttribute("data-node-path");
const errorId = target.getAttribute("data-error-id");
if (nodePath && errorId !== null) {
const paths = getExpansions(errorId);
if (!paths.has(nodePath)) {
paths.add(nodePath);
}
return true;
}
return false;
}
export function handleDerivationResetClick(target?: any): boolean {
if (!target) return false;
const errorId = target.getAttribute("data-error-id");
if (errorId !== null) {
expansionsMap.delete(errorId);
return true;
}
return false;
}
export function renderDerivationNode(error: RefinementError, node: ValDerivationNode): string {
if (!node.origin) return `<pre>${node.value}</pre>`; // no derivation available
const errorId = hashError(error);
const expansions = getExpansions(errorId);
return /*html*/ `
<div class="container derivation-container" data-error-id="${errorId}">
<div style="flex: 1;">
${renderJsonTree(error, node, errorId, "root", expansions)}
${expansions.size === 0 ? '<span class="node-expand-indicator"> (click to expand)</span>' : ''}
</div>
<button class="reset-btn derivation-reset-btn" data-error-id="${errorId}" ${expansions.size === 0 ? "disabled" : ""}>
↻
</button>
</div>
`;
}