From 3000fb36ee08618c22b3c850a24e91932221ee6f Mon Sep 17 00:00:00 2001 From: furedea <132188853+furedea@users.noreply.github.com> Date: Wed, 21 Oct 2026 16:20:00 +0900 Subject: [PATCH] refactor: name real argument groups instead of dodging max-params Read caption parts from named regex groups via matchAll instead of a rest parameter, and group the unit symbol and formatter into named SECONDS and MILLISECONDS units instead of an anonymous median/samples object. Refs furedea/dotfiles#247 Co-Authored-By: Claude Opus 5.5 --- src/data/article.test.ts | 14 ++++++++ src/data/article.ts | 31 ++++++++++------- tests/performance/performance_report.test.ts | 11 ++++++ tests/performance/performance_report.ts | 36 +++++++++++--------- 4 files changed, 64 insertions(+), 28 deletions(-) diff --git a/src/data/article.test.ts b/src/data/article.test.ts index 81483fb..534a8da 100644 --- a/src/data/article.test.ts +++ b/src/data/article.test.ts @@ -156,6 +156,20 @@ _System overview._`; `); }); +test("converts asterisk-emphasized image captions to esa figures", () => { + const markdown = `![Flow](https://example.com/flow.png) +*Request flow.*`; + + expect( + toEsaMarkdown(markdown, { + canonicalUrl: "https://furedea.com/ja/blog/article-publishing/", + }), + ).toContain(`
+Flow +
Request flow.
+
`); +}); + test("preserves Zenn extension examples inside fenced code blocks", () => { const markdown = `\`\`\`markdown :::details Example diff --git a/src/data/article.ts b/src/data/article.ts index 98e9b5e..e0f5890 100644 --- a/src/data/article.ts +++ b/src/data/article.ts @@ -183,18 +183,25 @@ function restoreFencedBlocks(markdown: string, fencedBlocks: string[]): string { } function convertCaptions(markdown: string): string { - return markdown.replace( - /^!\[([^\]]*)\]\(([^)\s]+)\)\n([_*])(.+)\3$/gmu, - (_match, ...captures: string[]) => { - const [alt, url, , caption] = captures; - return [ - "
", - `${escapeHtml(alt)}`, - `
${escapeHtml(caption)}
`, - "
", - ].join("\n"); - }, - ); + const captionedImages = + /^!\[(?[^\]]*)\]\((?[^)\s]+)\)\n(?[_*])(?.+)\k$/gmu; + let converted = ""; + let copiedUntil = 0; + for (const match of markdown.matchAll(captionedImages)) { + const { alt = "", url = "", caption = "" } = match.groups ?? {}; + converted += markdown.slice(copiedUntil, match.index) + captionFigure(alt, url, caption); + copiedUntil = match.index + match[0].length; + } + return converted + markdown.slice(copiedUntil); +} + +function captionFigure(alt: string, url: string, caption: string): string { + return [ + "
", + `${escapeHtml(alt)}`, + `
${escapeHtml(caption)}
`, + "
", + ].join("\n"); } function replaceImageUrls(markdown: string, origin: string): string { diff --git a/tests/performance/performance_report.test.ts b/tests/performance/performance_report.test.ts index 0cf2585..bbbb375 100644 --- a/tests/performance/performance_report.test.ts +++ b/tests/performance/performance_report.test.ts @@ -79,6 +79,17 @@ describe("performance report", () => { expect(summary).toContain("1.23 s (1.00–1.50)"); expect(summary).toContain("| 3 |"); }); + + test("shows the blocking time range in rounded milliseconds", () => { + const entry = reportEntry(); + entry.samples = [ + { ...entry.metrics, blockingTime: 50.4 }, + entry.metrics, + { ...entry.metrics, blockingTime: 120.6 }, + ]; + + expect(renderPerformanceSummary([entry])).toContain("88 ms (50–121)"); + }); }); function reportEntry(): PerformanceReportEntry { diff --git a/tests/performance/performance_report.ts b/tests/performance/performance_report.ts index e881a09..07cc449 100644 --- a/tests/performance/performance_report.ts +++ b/tests/performance/performance_report.ts @@ -21,6 +21,18 @@ export type PerformanceReportEntry = { export type PerformanceMeasurement = Omit; +type Unit = { + format: (milliseconds: number) => string | number; + symbol: string; +}; + +const SECONDS: Unit = { + format: (milliseconds) => (milliseconds / 1_000).toFixed(2), + symbol: "s", +}; + +const MILLISECONDS: Unit = { format: Math.round, symbol: "ms" }; + export function renderPerformanceSummary(entries: PerformanceReportEntry[]): string { const rows = entries.toSorted(byRoute).map(renderRow); return [ @@ -68,9 +80,9 @@ function renderRow(entry: PerformanceReportEntry): string { (value) => value.toFixed(3), ), unitWithRange( - { median: metrics.blockingTime, samples: samples.map(({ blockingTime }) => blockingTime) }, - "ms", - Math.round, + metrics.blockingTime, + samples.map(({ blockingTime }) => blockingTime), + MILLISECONDS, ), `${samples.length} |`, ].join(" | "); @@ -83,24 +95,16 @@ function statusSymbol(entry: PerformanceReportEntry): string { return "❌"; } -function seconds(milliseconds: number): string { - return (milliseconds / 1_000).toFixed(2); -} - function secondsWithRange(median: number, samples: number[]): string { - return unitWithRange({ median, samples }, "s", seconds); + return unitWithRange(median, samples, SECONDS); } -function unitWithRange( - { median, samples }: { median: number; samples: number[] }, - unit: string, - format: (value: number) => string | number, -): string { +function unitWithRange(median: number, samples: number[], unit: Unit): string { const minimum = Math.min(...samples); const maximum = Math.max(...samples); - const formattedMedian = format(median); - if (minimum === maximum) return `${formattedMedian} ${unit}`; - return `${formattedMedian} ${unit} (${format(minimum)}–${format(maximum)})`; + const formattedMedian = unit.format(median); + if (minimum === maximum) return `${formattedMedian} ${unit.symbol}`; + return `${formattedMedian} ${unit.symbol} (${unit.format(minimum)}–${unit.format(maximum)})`; } function valueWithRange(