// src/index.ts function loadCodeCoverage(text, codeCoverageData, codeCoverageDiv) { const tooltip = document.createElement("span"); tooltip.id = "covers-tooltip"; codeCoverageDiv.append(tooltip); const entries = codeCoverageData.toSorted((a, b) => b.index - a.index); const charEntries = {}; const elements = []; let line = 1; let col = 1; for (let index = 0; index < text.length; ++index) { if (text[index] === "\n") { col = 1; line += 1; const newlineSpan = document.createElement("span"); newlineSpan.innerText = "\n"; elements.push(newlineSpan); continue; } const entry = entries.find((entry2) => index >= entry2.index); if (!entry) { throw new Error("unreachable"); } charEntries[`${line}-${col}`] = entry; const color = (ratio) => `rgba(${255 - 255 * ratio}, ${255 * ratio}, 125, 0.5)`; const span = document.createElement("span"); span.style.backgroundColor = color(Math.min(entry.covers / 25, 1)); span.innerText = text[index]; span.dataset.covers = entry.covers.toString(); elements.push(span); col += 1; } function positionInBox(position, boundingRect) { const [x, y] = position; const outside = x < boundingRect.left || x >= boundingRect.right || y < boundingRect.top || y >= boundingRect.bottom; return !outside; } codeCoverageDiv.append(...elements); document.addEventListener("mousemove", (event) => { const [x, y] = [event.clientX, event.clientY]; const outerBox = codeCoverageDiv.getBoundingClientRect(); if (!positionInBox([x, y], outerBox)) { return; } const element = elements.find((element2) => { if (typeof element2 === "string") { return false; } if (!element2.dataset.covers) { return false; } const isIn = positionInBox([x, y], element2.getBoundingClientRect()); return isIn; }); if (!element) { tooltip.hidden = true; return; } const covers = parseInt(element.dataset.covers); tooltip.hidden = false; tooltip.style.left = `${event.clientX + 20}px`; tooltip.style.top = `${event.clientY + 20}px`; tooltip.innerText = `Ran ${covers} time${covers !== 1 ? "s" : ""}`; }); } function main() { const codeData = `fn add(a, b) { + a b } let result = 0; let i = 0; loop { if >= i 10 { break; } result = add(result, 5); i = + i 1; } `; const codeCoverageData = JSON.parse( `[{"index":0,"line":1,"col":1,"covers":2},{"index":28,"line":5,"col":1,"covers":1},{"index":44,"line":6,"col":1,"covers":1},{"index":55,"line":7,"col":1,"covers":1},{"index":66,"line":8,"col":5,"covers":11},{"index":104,"line":11,"col":5,"covers":10},{"index":19,"line":2,"col":5,"covers":10},{"index":133,"line":12,"col":5,"covers":10},{"index":87,"line":9,"col":9,"covers":1}]` ); const flameGraphData = JSON.parse( `{"fn":0,"acc":257,"parent":0,"children":[{"fn":18,"acc":251,"parent":0,"children":[{"fn":12,"acc":30,"parent":1,"children":[]}]}]}` ); const view = document.querySelector("#view"); const renderFunctions = { "source-code": () => { const code = document.createElement("pre"); code.innerHTML = codeData; view.replaceChildren(code); }, "code-coverage": () => { const codeCoverageElement = document.createElement("pre"); loadCodeCoverage(codeData, codeCoverageData, codeCoverageElement); const view2 = document.querySelector("#view"); view2.replaceChildren(codeCoverageElement); }, "flame-graph": () => { } }; const viewRadios = document.querySelectorAll( 'input[name="views"]' ); for (const input of viewRadios) { input.addEventListener("input", (ev) => { const target = ev.target; const value = target.value; renderFunctions[value](); }); if (input.checked) { const value = input.value; renderFunctions[value](); } } } main();