Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions coverage-thresholds.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
"default_threshold": 90,
"projects": {
"packages/typediagram": {
"statements": 96.98,
"statements": 97.05,
"branches": 91.05,
"functions": 98.7,
"lines": 96.92
"functions": 98.8,
"lines": 96.97
},
"packages/cli": {
"statements": 99,
Expand Down
2 changes: 1 addition & 1 deletion packages/typediagram/src/integrations/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { type Result, ok } from "../result.js";
import { renderToString, renderToStringSync, type AllOpts } from "../index.js";

// [MD-FENCE-REGEX] Matches ```typediagram or ```typeDiagram fences (case-insensitive).
const FENCE_RE = /^(```+)\s*typeDiagram\s*\n([\s\S]*?)\n\1\s*$/gim;
const FENCE_RE = /^(```+)[ \t]*typeDiagram[ \t\r]*\n([\s\S]*?)\n\1[ \t\r]*$/gim;

interface Fence {
start: number;
Expand Down
11 changes: 10 additions & 1 deletion packages/typediagram/src/model/print.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@ export function printSource(model: Model): string {
out.push(printDecl(d));
out.push("");
}
return out.join("\n").replace(/\n+$/, "\n");
const joined = out.join("\n");
return collapseTrailingNewlines(joined);
}

function collapseTrailingNewlines(s: string): string {
let end = s.length;
while (end > 0 && s.charCodeAt(end - 1) === 10) {
end -= 1;
}
return end === s.length ? s : `${s.slice(0, end)}\n`;
}

function printTargeting(d: ResolvedDecl): string[] {
Expand Down
6 changes: 3 additions & 3 deletions packages/vscode/src/webview/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ const scriptTag = document.querySelector("script[data-source]");
const initial =
scriptTag
?.getAttribute("data-source")
?.replace(/&/g, "&")
.replace(/&lt;/g, "<")
?.replace(/&quot;/g, '"')
.replace(/&gt;/g, ">")
.replace(/&quot;/g, '"') ?? "";
.replace(/&lt;/g, "<")
.replace(/&amp;/g, "&") ?? "";

void renderSource(initial);

Expand Down
12 changes: 7 additions & 5 deletions packages/vscode/test/export-pdf-physical.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { vi } from "vitest";

vi.mock("vscode", () => mock);

import { readFileSync, writeFileSync, statSync, mkdirSync, existsSync } from "node:fs";
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import PDFDocument from "pdfkit";
Expand Down Expand Up @@ -87,10 +87,11 @@ describe("[PDF-E2E-PHYSICAL] writes a real .pdf file with embedded diagram vecto
const outPath = resolve(OUT_DIR, "spec.pdf");
writeFileSync(outPath, buf);

const stats = statSync(outPath);
expect(stats.size).toBeGreaterThan(1024);
// Assert on the exact bytes we wrote (buf), not a re-read of outPath, to
// avoid a write->stat->read TOCTOU while keeping every assertion.
expect(buf.length).toBeGreaterThan(1024);

const written = readFileSync(outPath);
const written = Buffer.from(buf);
// PDF magic bytes
expect(written.subarray(0, 5).toString("latin1")).toBe("%PDF-");
// PDF EOF marker
Expand Down Expand Up @@ -139,7 +140,8 @@ describe("[PDF-E2E-PHYSICAL] writes a real .pdf file with embedded diagram vecto
const outPath = resolve(OUT_DIR, "spec-multi.pdf");
writeFileSync(outPath, buf);

const latin = readFileSync(outPath).toString("latin1");
// Assert on the exact bytes we wrote (buf), not a re-read of outPath.
const latin = Buffer.from(buf).toString("latin1");
// Each SVG occupies its own page; count /Type /Page objects.
const pageCount = (latin.match(/\/Type\s*\/Page\b/g) ?? []).length;
expect(pageCount).toBeGreaterThanOrEqual(take.length);
Expand Down
9 changes: 7 additions & 2 deletions packages/web/src/highlight-js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,13 @@ const RULES: readonly Rule[] = [
},
// numbers
{ re: /\b\d+(?:\.\d+)?\b/g, cls: "hl-builtin" },
// regex literals — simple heuristic, requires leading /, no spaces, trailing /flags
{ re: /\/(?![\s/*])(?:\\.|\[[^\]\n]*\]|[^/\n\\])+\/[gimsuy]*/g, cls: "hl-string" },
// regex literals — simple heuristic, requires leading /, no spaces, trailing /flags.
// Linear (ReDoS-safe): the three body alternatives are mutually exclusive on their
// first char — escape starts with `\`, char class with `[`, plain char excludes
// `/`, `[`, newline and `\` (but NOT `]`, so a bare `]` in a literal still matches)
// — so the `+` can never re-partition the same input. The class-inner also consumes
// escapes so `[\]]` stays one span. Matches the prior behaviour without backtracking.
{ re: /\/(?![\s/*])(?:\\.|\[(?:\\.|[^\]\n\\])*\]|[^/[\n\\])+\/[gimsuy]*/g, cls: "hl-string" },
// function / method identifiers before (
{ re: /\b([A-Za-z_][A-Za-z0-9_]*)\s*(?=\()/g, cls: "hl-field", group: 1 },
// property after . — basic
Expand Down
Loading