Repository navigation
build(scripts): guard the generated markdown tables with a round trip instead of a regex - #457
Merged
Merged
Conversation
Contributor
File size check0 over a hard cap (fails), 10 warning(s).
Split the file, wrap the line, shorten or exempt the comment, or list the path in 5 managed file(s) skipped; repo-platform owns them. |
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The guards consistently mirror their renderers and the tests cover the relevant malformed-table cases.
Review effort: Balanced
Findings: None
What changed in this PR
Replaces regex-based generated-table guards with round-trip validation to prevent malformed Markdown from being silently overwritten.
Changes:
- Adds reusable Markdown table parsing, rendering, and round-trip validation.
- Applies round-trip guards to the Sections and Defaults tables.
- Expands tests for malformed rows, duplicate keys, ordering, and renderer compatibility.
| File | Description |
|---|---|
.github/scripts/lib/markdown-table.ts |
Adds shared table rendering and round-trip guards. |
.github/scripts/lib/generated-regions.ts |
Extracts reusable rendered-body comparison. |
.github/scripts/gen-docs.ts |
Guards the Sections table through round-trip validation. |
.github/scripts/gen-action-docs.ts |
Guards Defaults and enforces nonempty policy prose literals. |
test/scripts/gen-docs.test.ts |
Tests Sections table acceptance and refusal cases. |
test/scripts/gen-action-docs.test.ts |
Tests Defaults table round trips and malformed bodies. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Vivswan
force-pushed
the
build/markdown-region-guards
branch
from
October 5, 2026 06:16
4d9cfbd to
5a71dfe
Compare
Vivswan
force-pushed
the
build/markdown-region-guards
branch
from
October 5, 2026 07:07
5a71dfe to
f950507
Compare
Vivswan
force-pushed
the
build/markdown-region-guards
branch
from
October 5, 2026 07:46
f950507 to
006cc61
Compare
… instead of a regex The sections-table and policy-defaults-table regions admitted authored text their renderers never write, so a hand edit inside the markers was erased on the next regeneration instead of refused. The sections-table shape used `[^\n]*` cells and admitted a pipe, a carriage return, a sixth cell, a blank cell, a repeated section with other prose, and an Undeclared default outside the three displays. The policy-defaults-table shape admitted an override naming the same policy as the default, a keep row above a delete row, and a repeated section. Both shapes are now one shared round trip, `tableRoundTrip` in lib/markdown-table.ts: the body splits into rows and cells by the inverse of `tableRow()`, the rows pass the table rule (the header's column count, the cell rule `tableCell()` writes by, a key span opening every row, no key repeated), a per-table parser turns the cells into the record the table renders from, and the records re-rendered must reproduce the body byte for byte. The refusal names the first differing line through `renderedMismatch`, now shared with the action.yml round trip. The Defaults table re-renders through its real renderer, so the delete-first order comes from the renderer, and its parser checks the opposite-policy binding through the same `OPPOSITE` table the renderer writes from. The Sections table re-renders through the table helper with the prose cells opaque, since its PAT cell paraphrases prose no parser inverts. The helper is the grammar on both sides. `tableFault()` is the one statement of the table rule: `renderTable()` throws its text and `tableRoundTrip()` refuses by the same rule, so a renderer cannot write a body its own guard would refuse, which would otherwise turn the next regeneration of the committed page into a throw. The Defaults statement `policyTableFault()` adds each row's prose by `proseFault`, the trimmed-blank rule `tableCell()` uses, which the parser consults too, since blank prose renders `delete ()` or a bare policy span that the cell rule admits. A compile-time proof of non-blank prose was tried and dropped: a structural type cannot keep a hidden property out of a parameter, so only the run-time check closes the boundary. Two property-style tests hold both sides to the statement over generated inputs (keys inside and outside the key span, both policies, prose with blanks, padding, pipes, carriage returns, and a Unicode separator, duplicate and reordered sections): the renderer throws exactly the statement's fault, the guard admits what the renderer wrote, and the guard refuses the same cells joined without the renderer whenever the statement faults, so a check added to one side only turns a generated input red. A rule test in each registry's suite refuses a RegExp body on any region whose rendering is a table. Refusal tables for both regions pin each input above: nine were admitted by the regexes on main, the rest were refused with the generic message and now name the cell or line.
Vivswan
force-pushed
the
build/markdown-region-guards
branch
from
October 5, 2026 10:58
006cc61 to
0ca98e2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Before
A pipe typed into the Sections table's
labelsNotes cell, thenbun run build:docs:After
How
tableRoundTripsplits by the inverse oftableRow(), applies the table rule, re-renders, byte-compares.tableFaultis thrown byrenderTableand refused by the guard;policyTableFaultadds the Defaults prose rule.Proof
tsc -p ., biome, knip,bun test test/scripts(415) green; both generators regenerate unchanged.Technical details
Line accounting (
git diff --numstatagainst the merge base):lib/markdown-table.ts,lib/generated-regions.ts,gen-docs.ts,gen-action-docs.ts)gen-docs.test.ts,gen-action-docs.test.ts)proseFault(trimmed-blank) andOPPOSITEwith it.untoucheddisplay each turn a property test red.\n|, and refuses a RegExp body without proving a function body istableRoundTrip.undeclaredDefaultgetter changing between reads) need a hostile accessor on data this repository authors itself, which no outside input can supply.renderedMismatchis the former inline tail of the action.ymlroundTrip, moved without a behavior change..github/scripts/lib/{markdown-table.ts,generated-regions.ts},.github/scripts/{gen-docs.ts,gen-action-docs.ts},test/scripts/{gen-docs.test.ts,gen-action-docs.test.ts}