mirror of
https://git.sfja.dk/Mikkel/slige.git
synced 2025-01-19 04:46:30 +00:00
149 lines
4.3 KiB
TypeScript
149 lines
4.3 KiB
TypeScript
import * as data from "./data.ts";
|
|
|
|
type Color = { r: number; g: number; b: number };
|
|
|
|
function lerp2(ratio: number, start: number, end: number) {
|
|
return (1 - ratio) * start + ratio * end;
|
|
}
|
|
|
|
function lerp3(ratio: number, start: number, middle: number, end: number) {
|
|
return (1 - ratio) * lerp2(ratio, start, middle) +
|
|
ratio * lerp2(ratio, middle, end);
|
|
}
|
|
|
|
function colorLerp(
|
|
ratio: number,
|
|
start: Color,
|
|
middle: Color,
|
|
end: Color,
|
|
): Color {
|
|
return {
|
|
r: lerp3(ratio, start.r, middle.r, end.r),
|
|
g: lerp3(ratio, start.g, middle.g, end.g),
|
|
b: lerp3(ratio, start.b, middle.b, end.b),
|
|
};
|
|
}
|
|
|
|
function colorToString(color: Color): string {
|
|
return `rgb(${color.r}, ${color.g}, ${color.b})`;
|
|
}
|
|
|
|
const GREEN = { r: 42, g: 121, b: 82 };
|
|
const YELLOW = {
|
|
r: 247,
|
|
g: 203,
|
|
b: 21,
|
|
};
|
|
const RED = {
|
|
r: 167,
|
|
g: 29,
|
|
b: 49,
|
|
};
|
|
|
|
export type CodeCovRender = "performance" | "test-coverage";
|
|
|
|
export function loadCodeCoverage(
|
|
text: string,
|
|
input: data.CodeCovEntry[],
|
|
tooltip: HTMLElement,
|
|
mode: CodeCovRender,
|
|
): HTMLPreElement {
|
|
const container = document.createElement("pre");
|
|
container.classList.add("code-source");
|
|
if (input.length === 0) {
|
|
return container;
|
|
}
|
|
const entries = input.toSorted((
|
|
a: data.CodeCovEntry,
|
|
b: data.CodeCovEntry,
|
|
) => b.index - a.index);
|
|
const charEntries: { [key: string]: data.CodeCovEntry } = {};
|
|
const elements: HTMLElement[] = [];
|
|
let line = 1;
|
|
let col = 1;
|
|
const maxPerfCovers = entries.map((v) => v.covers).reduce((acc, v) =>
|
|
acc > Math.log10(v) ? acc : Math.log10(v)
|
|
);
|
|
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((entry) => index >= entry.index);
|
|
if (!entry) {
|
|
throw new Error("unreachable");
|
|
}
|
|
charEntries[`${line}-${col}`] = entry;
|
|
|
|
const perfColor = (ratio: number) => {
|
|
const clr = colorLerp(ratio, GREEN, YELLOW, RED);
|
|
return colorToString(clr);
|
|
};
|
|
|
|
const span = document.createElement("span");
|
|
span.style.backgroundColor = mode === "performance"
|
|
? perfColor(
|
|
Math.log10(entry.covers) / maxPerfCovers,
|
|
)
|
|
: entry.covers > 0
|
|
? colorToString(GREEN)
|
|
: colorToString(RED);
|
|
span.innerText = text[index];
|
|
span.dataset.covers = entry.covers.toString();
|
|
elements.push(span);
|
|
col += 1;
|
|
}
|
|
function positionInBox(
|
|
position: [number, number],
|
|
boundingRect: {
|
|
left: number;
|
|
top: number;
|
|
right: number;
|
|
bottom: number;
|
|
},
|
|
) {
|
|
const [x, y] = position;
|
|
const outside = x < boundingRect.left ||
|
|
x >= boundingRect.right || y < boundingRect.top ||
|
|
y >= boundingRect.bottom;
|
|
return !outside;
|
|
}
|
|
container.append(...elements);
|
|
document.addEventListener("mousemove", (event) => {
|
|
const [x, y] = [event.clientX, event.clientY];
|
|
const outerBox = container.getBoundingClientRect();
|
|
if (!positionInBox([x, y], outerBox)) {
|
|
tooltip.hidden = true;
|
|
return;
|
|
}
|
|
const element = elements.find((element) => {
|
|
if (typeof element === "string") {
|
|
return false;
|
|
}
|
|
if (!element.dataset.covers) {
|
|
return false;
|
|
}
|
|
const isIn = positionInBox([x, y], element.getBoundingClientRect());
|
|
return isIn;
|
|
});
|
|
if (!element) {
|
|
tooltip.hidden = true;
|
|
return;
|
|
}
|
|
const maybeCovers = element.dataset.covers;
|
|
if (!maybeCovers) {
|
|
throw new Error("unreachable");
|
|
}
|
|
const covers = parseInt(maybeCovers);
|
|
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" : ""}`;
|
|
});
|
|
return container;
|
|
}
|