Watch
0
0
Fork
You've already forked hyperhive
0

review classifier: escape $ in verdict cells so the forge doesn't render them as math

Forgejo's markdown renders $...$ and $$...$$ as KaTeX; a reason quoting
nix `${...}` put two $ in one cell and the span between them became a KaTeX
parse error. The cell escaper now backslash-escapes $ like its other markup.

Rule source: the math inline parser triggers on '$' and only starts a span
there (modules/markup/markdown/math/inline_parser.go, Trigger/Parse in
go-gitea/gitea, which Forgejo carries); goldmark's CommonMark backslash
escape consumes '\$' first, so the parser never sees it. Escaping each $
also covers '$$'. Backslashes are already escaped, so '\\$' stays literal.
This commit is contained in:
atlas 2026-10-13 16:13:50 +02:00 • committed by mara
commit c42321ff0e

View file

@ -736,7 +736,7 @@ fn table(rows: &[Row]) -> String {
}
/// `text` as one table cell's inline text: on one line, with every character
/// that could end the cell, start markup or raw HTML escaped, and a zero-width
/// that could end the cell, start markup, math (`$`) or raw HTML escaped, and a zero-width
/// space after every `@`, `#`, `!`, `://` and `www.`, so it carries no live
/// mention, issue or PR reference, or link. The forge links issue and PR
/// numbers and bare URLs itself, so the escapes alone don't stop those.
@ -748,7 +748,8 @@ fn cell(text: &str) -> String {
}
for c in word.chars() {
match c {
'\\' | '|' | '`' | '*' | '_' | '~' | '[' | ']' | '(' | ')' | '<' | '>' | '&' => {
'\\' | '|' | '`' | '*' | '_' | '~' | '[' | ']' | '(' | ')' | '<' | '>' | '&'
| '$' => {
out.push('\\');
out.push(c);
}
@ -1484,6 +1485,29 @@ mod tests {
);
}
#[test]
fn a_reason_carries_no_live_math_delimiter() {
let mut r = row(None, true, true);
r.reason =
"uses https://${hyperhiveCfg.swarm.forge.domain}/metrics and $$x$$ | y".to_owned();
let body = table(&[r]);
let line = body.lines().nth(2).expect("a data row");
assert!(
line.contains("\\${hyperhiveCfg") && line.contains("\\$\\$x\\$\\$"),
"{line}"
);
let bare = line
.char_indices()
.filter(|(i, c)| *c == '$' && (*i == 0 || line.as_bytes()[i - 1] != b'\\'))
.count();
assert_eq!(bare, 0, "{line}");
let unescaped = line
.char_indices()
.filter(|(i, c)| *c == '|' && (*i == 0 || line.as_bytes()[i - 1] != b'\\'))
.count();
assert_eq!(unescaped, 8, "{line}");
}
#[test]
fn a_reason_carries_no_link_or_reference() {
let text = cell("see https://evil.example/x, WWW.evil.example, #1, o/r#3 and !4");