feat: publish Regex Tools 0.2.0

This commit is contained in:
2026-07-27 01:06:57 +02:00
parent 873b9b218d
commit 4951c966d4
180 changed files with 35344 additions and 503 deletions

View File

@@ -0,0 +1,617 @@
import { useEffect, useMemo, useRef, useState } from "react";
import type {
RegexEngineOptions,
RegexFlavourId,
} from "../regex/model/flavour";
import type { RegexSyntaxResult } from "../regex/model/syntax";
import type { RegexTestCase } from "../regex/tests/test-case.types";
import { WorkerRequestError } from "../regex/execution/WorkerSupervisor";
import { formatEcmaScriptPattern } from "../regex/formatting/ecmascript-format";
import { PatternFormatValidator } from "../regex/formatting/PatternFormatValidator";
import type {
PatternFormatPreview,
PatternFormatSnapshotCheck,
PatternFormatValidationInput,
PatternFormatValidationResult,
} from "../regex/formatting/formatting.types";
import "./PatternFormatterPanel.css";
const MAXIMUM_VISIBLE_CHANGES = 200;
const MAXIMUM_VISIBLE_CHECKS = 200;
export interface PatternFormatValidatorClient {
validate(
input: PatternFormatValidationInput,
onProgress?: (completed: number, total: number) => void,
): Promise<PatternFormatValidationResult>;
cancel(): void;
dispose(): void;
}
export interface PatternFormatterPanelProps {
readonly flavour: RegexFlavourId;
readonly flavourVersion?: string;
readonly pattern: string;
readonly flags: readonly string[];
readonly options: RegexEngineOptions;
readonly syntax?: RegexSyntaxResult;
readonly subject: string;
readonly replacement: string;
readonly scanAll: boolean;
readonly timeoutMs: number;
readonly tests: readonly RegexTestCase[];
readonly onApply: (formattedPattern: string) => void;
readonly onClose?: () => void;
readonly createValidator?: () => PatternFormatValidatorClient;
}
type PreviewState =
| { readonly status: "available"; readonly preview: PatternFormatPreview }
| { readonly status: "unavailable"; readonly reason: string };
interface ValidationConfiguration {
readonly flavourVersion?: string;
readonly preview: PatternFormatPreview;
readonly flags: readonly string[];
readonly options: RegexEngineOptions;
readonly subject: string;
readonly replacement: string;
readonly scanAll: boolean;
readonly timeoutMs: number;
readonly tests: readonly RegexTestCase[];
}
interface ValidationRecord {
readonly configuration: ValidationConfiguration;
readonly result: PatternFormatValidationResult;
}
interface ConfirmationRecord {
readonly configuration: ValidationConfiguration;
readonly checked: boolean;
}
function previewState(
flavour: RegexFlavourId,
pattern: string,
syntax: RegexSyntaxResult | undefined,
): PreviewState {
if (flavour !== "ecmascript") {
return {
status: "unavailable",
reason:
"Canonical formatting is currently available only for ECMAScript. PCRE2 and other flavours remain unchanged because no complete grammar-backed formatter is implemented for them.",
};
}
if (!syntax) {
return {
status: "unavailable",
reason:
"Wait for the ECMAScript syntax provider to finish parsing the current pattern.",
};
}
try {
return {
status: "available",
preview: formatEcmaScriptPattern(pattern, syntax),
};
} catch (cause) {
return {
status: "unavailable",
reason:
cause instanceof Error
? cause.message
: "The current syntax snapshot cannot be formatted.",
};
}
}
function formatDuration(milliseconds: number): string {
if (milliseconds < 1) return "<1 ms";
if (milliseconds < 1_000) return `${milliseconds.toFixed(0)} ms`;
return `${(milliseconds / 1_000).toFixed(2)} s`;
}
function observationIdentity(check: PatternFormatSnapshotCheck): string {
const before = check.before.engineIdentity ?? check.before.status;
const after = check.after.engineIdentity ?? check.after.status;
return before === after ? before : `${before}${after}`;
}
function ValidationCheck({
title,
check,
}: {
readonly title: string;
readonly check: PatternFormatSnapshotCheck;
}) {
return (
<article className={`formatter-check is-${check.comparison}`}>
<header>
<strong>{title}</strong>
<span>{check.comparison}</span>
</header>
<p>{check.summary}</p>
<small>{observationIdentity(check)}</small>
{check.mismatchKinds.length > 0 ? (
<code>{check.mismatchKinds.join(" · ")}</code>
) : null}
</article>
);
}
export function PatternFormatterPanel({
flavour,
flavourVersion,
pattern,
flags,
options,
syntax,
subject,
replacement,
scanAll,
timeoutMs,
tests,
onApply,
onClose,
createValidator = () => new PatternFormatValidator(),
}: PatternFormatterPanelProps) {
const preview = useMemo(
() => previewState(flavour, pattern, syntax),
[flavour, pattern, syntax],
);
const configuration = useMemo<ValidationConfiguration | undefined>(
() =>
preview.status === "available"
? {
flavourVersion,
preview: preview.preview,
flags,
options,
subject,
replacement,
scanAll,
timeoutMs,
tests,
}
: undefined,
[
flags,
flavourVersion,
options,
preview,
replacement,
scanAll,
subject,
tests,
timeoutMs,
],
);
const validator = useRef<PatternFormatValidatorClient | undefined>(undefined);
const revision = useRef(0);
const [running, setRunning] = useState(false);
const [progress, setProgress] = useState({ completed: 0, total: 0 });
const [message, setMessage] = useState(
"Review the exact preview before running bounded validation.",
);
const [error, setError] = useState<string>();
const [record, setRecord] = useState<ValidationRecord>();
const [confirmation, setConfirmation] = useState<ConfirmationRecord>();
const currentRecord =
record?.configuration === configuration ? record : undefined;
const confirmed =
confirmation !== undefined &&
confirmation.configuration === configuration &&
confirmation.checked;
useEffect(
() => () => {
revision.current += 1;
validator.current?.dispose();
validator.current = undefined;
},
[],
);
const cancel = () => {
revision.current += 1;
validator.current?.cancel();
setRunning(false);
setMessage("Validation cancelled; formatter workers were terminated.");
};
const runValidation = async () => {
if (
!configuration ||
!configuration.preview.changed ||
!configuration.preview.withinPatternLimit
) {
return;
}
const currentValidator =
validator.current ?? (validator.current = createValidator());
currentValidator.cancel();
revision.current += 1;
const runRevision = revision.current;
setRunning(true);
setConfirmation(undefined);
setError(undefined);
setRecord(undefined);
setProgress({ completed: 0, total: 0 });
setMessage(
"Reparsing, compiling and comparing exact source/candidate snapshots…",
);
try {
const result = await currentValidator.validate(
{
flavour: "ecmascript",
...(configuration.flavourVersion === undefined
? {}
: { flavourVersion: configuration.flavourVersion }),
sourcePattern: configuration.preview.sourcePattern,
formattedPattern: configuration.preview.formattedPattern,
flags: configuration.flags,
options: configuration.options,
subject: configuration.subject,
replacement: configuration.replacement,
scanAll: configuration.scanAll,
timeoutMs: configuration.timeoutMs,
tests: configuration.tests,
},
(completed, total) => {
if (revision.current !== runRevision) return;
setProgress({ completed, total });
setMessage(
total === 0
? "The current subject/replacement snapshot is being compared."
: `Validated ${completed.toLocaleString()} of ${total.toLocaleString()} applicable unit tests.`,
);
},
);
if (revision.current !== runRevision) return;
setRecord({ configuration, result });
setMessage(
result.status === "equivalent"
? "All completed safety gates retained the same observable behavior."
: result.status === "different"
? "Formatting changed at least one checked observation."
: "Validation was inconclusive; the candidate cannot be applied.",
);
} catch (cause) {
if (revision.current !== runRevision) return;
const cancelled =
cause instanceof WorkerRequestError && cause.kind === "cancelled";
setError(
cancelled
? "Validation was cancelled."
: cause instanceof Error
? cause.message
: "Formatter validation failed.",
);
setMessage(
cancelled
? "Validation cancelled; no candidate can be applied."
: "Validation failed; no candidate can be applied.",
);
} finally {
if (revision.current === runRevision) setRunning(false);
}
};
const close = () => {
if (running) cancel();
onClose?.();
};
const result = currentRecord?.result;
const canApply =
result?.canApply === true &&
confirmed &&
configuration?.preview.changed === true;
return (
<section
className="panel formatter-panel"
aria-labelledby="formatter-heading"
>
<header className="panel-heading">
<div>
<p className="eyebrow">
Grammar-backed preview + bounded proof gates
</p>
<h2 id="formatter-heading">Format pattern</h2>
</div>
<div className="panel-heading-actions">
<span className="provenance-badge">ECMAScript only</span>
{onClose ? (
<button
type="button"
className="icon-button"
aria-label="Close pattern formatter"
data-dialog-initial-focus
onClick={close}
>
×
</button>
) : null}
</div>
</header>
{preview.status === "unavailable" ? (
<p className="formatter-unavailable" role="status">
{preview.reason}
</p>
) : (
<div className="formatter-body">
<p className="formatter-advisory">
This deliberately narrow formatter escapes literal slashes and raw
control/line-separator characters. It does not insert insignificant
whitespaceECMAScript patterns generally have no such whitespace.
</p>
<section className="formatter-preview" aria-label="Format preview">
<header>
<div>
<p className="eyebrow">
{preview.preview.formatter.id} · v
{preview.preview.formatter.version}
</p>
<h3>Exact preview</h3>
</div>
<span>
{preview.preview.totalChanges.toLocaleString()} transformation
{preview.preview.totalChanges === 1 ? "" : "s"}
</span>
</header>
<div className="formatter-pattern-grid">
<article>
<strong>Source</strong>
<pre>
<code>{preview.preview.sourcePattern}</code>
</pre>
</article>
<article>
<strong>Formatted candidate</strong>
<pre data-testid="formatted-pattern-preview">
<code>{preview.preview.formattedPattern}</code>
</pre>
</article>
</div>
{preview.preview.changed ? (
<div className="formatter-table-scroll">
<table aria-label="Formatter transformations">
<thead>
<tr>
<th scope="col">UTF-16 range</th>
<th scope="col">Kind</th>
<th scope="col">Before</th>
<th scope="col">After</th>
</tr>
</thead>
<tbody>
{preview.preview.changes
.slice(0, MAXIMUM_VISIBLE_CHANGES)
.map((change) => (
<tr
key={`${change.startUtf16}:${change.endUtf16}:${change.kind}`}
title={change.explanation}
>
<td>
{change.startUtf16}{change.endUtf16}
</td>
<td>{change.kind.replaceAll("-", " ")}</td>
<td>
<code>{JSON.stringify(change.before)}</code>
</td>
<td>
<code>{JSON.stringify(change.after)}</code>
</td>
</tr>
))}
</tbody>
</table>
{preview.preview.totalChanges > MAXIMUM_VISIBLE_CHANGES ? (
<p>
The table shows the first{" "}
{MAXIMUM_VISIBLE_CHANGES.toLocaleString()} transformations.
</p>
) : null}
</div>
) : (
<p className="formatter-empty">
This pattern already uses the formatters canonical literal and
control escapes.
</p>
)}
<ul className="formatter-notices">
{preview.preview.warnings.map((warning) => (
<li key={warning}>{warning}</li>
))}
</ul>
</section>
{preview.preview.changed ? (
<section className="formatter-validation">
<header>
<div>
<p className="eyebrow">Required before apply</p>
<h3>Safety validation</h3>
</div>
<span>
{result?.status ?? (running ? "running" : "not run")}
</span>
</header>
<div
className={`formatter-status is-${result?.status ?? (error ? "different" : "idle")}`}
role="status"
aria-live="polite"
>
<strong>{message}</strong>
{error ? <p>{error}</p> : null}
</div>
{running ? (
<div className="formatter-progress">
<progress
max={Math.max(1, progress.total)}
value={Math.min(progress.completed, progress.total)}
/>
<span>
{progress.completed.toLocaleString()} /{" "}
{progress.total.toLocaleString()} tests
</span>
</div>
) : null}
<div className="formatter-actions">
<button
type="button"
className="primary-button"
disabled={
running ||
!preview.preview.withinPatternLimit ||
!preview.preview.changed
}
onClick={() => void runValidation()}
>
Validate exact snapshots
</button>
{running ? (
<button
type="button"
className="secondary-button"
onClick={cancel}
>
Cancel
</button>
) : null}
</div>
{result ? (
<div className="formatter-result">
<dl>
<div>
<dt>Syntax</dt>
<dd>
{result.sourceSyntaxAccepted &&
result.formattedSyntaxAccepted
? "both accepted"
: "rejected"}
</dd>
</div>
<div>
<dt>Capture shape</dt>
<dd>
{result.captureShapePreserved ? "preserved" : "changed"}
</dd>
</div>
<div>
<dt>Unit tests</dt>
<dd>
{result.completedTestCount.toLocaleString()} /{" "}
{result.applicableTestCount.toLocaleString()} applicable
</dd>
</div>
<div>
<dt>Unrelated / disabled</dt>
<dd>{result.skippedTestCount.toLocaleString()}</dd>
</div>
<div>
<dt>Elapsed</dt>
<dd>{formatDuration(result.elapsedMs)}</dd>
</div>
</dl>
<div className="formatter-checks">
<ValidationCheck
title="Current subject and replacement"
check={result.current}
/>
{result.tests
.slice(0, MAXIMUM_VISIBLE_CHECKS)
.map((test) => (
<ValidationCheck
key={test.testId}
title={test.name}
check={test}
/>
))}
</div>
{result.tests.length > MAXIMUM_VISIBLE_CHECKS ? (
<p className="formatter-result-note">
The panel shows the first{" "}
{MAXIMUM_VISIBLE_CHECKS.toLocaleString()} completed test
comparisons.
</p>
) : null}
{result.differences.length > 0 ? (
<section
className="formatter-differences"
aria-labelledby="formatter-differences-heading"
>
<h4 id="formatter-differences-heading">
Differences and inconclusive gates
</h4>
<ul>
{result.differences
.slice(0, MAXIMUM_VISIBLE_CHECKS)
.map((difference, index) => (
<li
key={`${difference.code}:${difference.testId ?? ""}:${index}`}
>
<strong>{difference.code}</strong>{" "}
{difference.summary}
</li>
))}
</ul>
</section>
) : null}
<ul className="formatter-notices">
{result.notices.map((notice) => (
<li key={notice}>{notice}</li>
))}
</ul>
<label className="check-control formatter-confirmation">
<input
type="checkbox"
checked={confirmed}
disabled={!result.canApply}
onChange={(event) => {
if (!configuration) return;
setConfirmation({
configuration,
checked: event.target.checked,
});
}}
/>
<span>
I understand that this bounded validation is not a proof
for every possible subject, and I want to apply the exact
candidate shown above.
</span>
</label>
<div className="formatter-actions">
<button
type="button"
className="primary-button"
disabled={!canApply}
onClick={() => {
if (!canApply || !configuration) return;
onApply(configuration.preview.formattedPattern);
}}
>
Apply formatted pattern
</button>
</div>
</div>
) : null}
</section>
) : null}
</div>
)}
</section>
);
}