Reproduction
action.yml:
inputs:
mode:
description: 'Accepts a literal backslash-pipe \| in the text.'
default: 'a'
Generated inputs row:
| <b><code>mode</code></b> | Accepts a literal backslash-pipe \\ | in the text. | <code>a</code> | **false** |
That | is a live cell delimiter. The row now has six cells where the header has five, so every column after the description shifts by one. scripts/verify-readme-contract.mjs catches it:
::error::`mode` description in the inputs table does not match action.yml — want "Accepts a literal backslash-pipe | in the text.", got "Accepts a literal backslash-pipe \\\\"
::error::`mode` default in the inputs table does not match action.yml — want "a", got "in the text."
::error::`mode` required flag in the inputs table does not match action.yml — want "false", got "a"
Cause
src/markdowner/index.ts:26
export function markdownEscapeTableCell(text: string): string {
return text.replaceAll('\n', '<br />').replaceAll('|', '\\|');
}
The pipe is escaped; the backslash never is. A source \| becomes \\| — an escaped backslash, then an unescaped pipe. The correct output is \\\|: escaped backslash, escaped pipe.
The same omission affects a backslash before any Markdown punctuation. C:\*glob* renders with the * swallowed as an escape.
Fix direction
Escape backslashes before escaping pipes, in that order:
text.replaceAll('\n', '<br />').replaceAll('\\', '\\\\').replaceAll('|', '\\|')
markdownEscapeInlineCode runs afterwards (markdowner/index.ts:109-110) and inserts its own \< for ><!--, so ordering the backslash escape inside markdownEscapeTableCell leaves that escape alone.
Two things travel with the fix:
normalise in scripts/verify-readme-contract.mjs decodes \| and \<. It needs to decode \\ too, or the verifier fails on the newly-correct output. A single backslash-aware unescape pass over cell text is the way to keep the two consistent — but it must be applied to rendered cells only, never to the action.yml source it is compared against, since the source is not escaped.
- This changes generated output for any third-party README whose descriptions contain backslashes, so it wants its own integration-test run.
Why it is filed rather than fixed in #634
#634 already changes bundling, config precedence, redaction and the contract verifier. This changes cell escaping for every generated table in every consumer repository, which deserves its own diff and its own third-party run.
Found while verifying a CodeRabbit review comment on #634 that reported the symptom as a verifier normalisation asymmetry. The verifier is behaving correctly here — the row it rejects really is malformed.
Reproduction
action.yml:Generated inputs row:
That
|is a live cell delimiter. The row now has six cells where the header has five, so every column after the description shifts by one.scripts/verify-readme-contract.mjscatches it:Cause
src/markdowner/index.ts:26The pipe is escaped; the backslash never is. A source
\|becomes\\|— an escaped backslash, then an unescaped pipe. The correct output is\\\|: escaped backslash, escaped pipe.The same omission affects a backslash before any Markdown punctuation.
C:\*glob*renders with the*swallowed as an escape.Fix direction
Escape backslashes before escaping pipes, in that order:
markdownEscapeInlineCoderuns afterwards (markdowner/index.ts:109-110) and inserts its own\<for><!--, so ordering the backslash escape insidemarkdownEscapeTableCellleaves that escape alone.Two things travel with the fix:
normaliseinscripts/verify-readme-contract.mjsdecodes\|and\<. It needs to decode\\too, or the verifier fails on the newly-correct output. A single backslash-aware unescape pass over cell text is the way to keep the two consistent — but it must be applied to rendered cells only, never to theaction.ymlsource it is compared against, since the source is not escaped.Why it is filed rather than fixed in #634
#634 already changes bundling, config precedence, redaction and the contract verifier. This changes cell escaping for every generated table in every consumer repository, which deserves its own diff and its own third-party run.
Found while verifying a CodeRabbit review comment on #634 that reported the symptom as a verifier normalisation asymmetry. The verifier is behaving correctly here — the row it rejects really is malformed.