From ba82f5beb3a1e3df6b824ecea7867e24494b2e61 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 14 Jun 2026 15:17:28 +0000 Subject: [PATCH] Fix check() false-positive on empty-cell token MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit check() flagged every `\` immediately followed by LF as a dangling backslash. But a backslash alone on its line (`\`) is the empty-cell token — the canonical encoding of "" produced by correct writers (escape("") == "\") — and is valid, not a rejected string. A genuine dangling backslash has cell content before it on the same line (e.g. `text\`). Distinguish the two in the `b'\n'` escape arm by only warning when the backslash is not the first byte of its line (`i - 1 != line_start`); when it is the first byte, the line is exactly `\` and we emit nothing. The post-loop bare-`\`-at-EOF case still warns: a lone `\` with no terminator is not a valid empty cell (which requires the LF). Adds regression tests covering the empty-cell token, an unterminated empty-cell row, the genuine dangling backslash, and a row mixing both. --- src/lib.rs | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index ae3dce8..e09ad25 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -448,12 +448,13 @@ pub fn check(input: &[u8]) -> Vec { if escaped { match b { b'n' | b'\\' => {} - b'\n' => warnings.push(Warning { + b'\n' if i - 1 != line_start => warnings.push(Warning { kind: WarningKind::DanglingBackslash, pos: i - 1, line, col: i - line_start, }), + b'\n' => {} _ => warnings.push(Warning { kind: WarningKind::UnknownEscape(b), pos: i - 1, @@ -988,6 +989,50 @@ mod tests { ); } + #[test] + fn test_check_empty_cell_token_no_warning() { + assert_eq!(check(b"\\\n\n"), vec![]); + } + + #[test] + fn test_check_empty_cell_token_unterminated_row() { + let warnings = check(b"\\\n"); + assert!( + !warnings + .iter() + .any(|w| w.kind == WarningKind::DanglingBackslash), + "empty-cell token must not warn as dangling backslash: {warnings:?}" + ); + } + + #[test] + fn test_check_dangling_backslash_with_content_still_warns() { + let warnings = check(b"text\\\n\n"); + assert_eq!( + warnings, + vec![Warning { + kind: WarningKind::DanglingBackslash, + pos: 4, + line: 1, + col: 5, + }] + ); + } + + #[test] + fn test_check_empty_cell_and_dangling_mixed() { + let warnings = check(b"\\\nabc\\\n\n"); + assert_eq!( + warnings, + vec![Warning { + kind: WarningKind::DanglingBackslash, + pos: 5, + line: 2, + col: 4, + }] + ); + } + #[test] fn test_check_no_terminal_lf() { let warnings = check(b"hello");