Skip to content

Commit 040071b

Browse files
authored
[red-knot] Ignore surrounding whitespace when looking for <!-- snapshot-diagnostics --> directives in mdtests (#16380)
1 parent d56d241 commit 040071b

File tree

2 files changed

+54
-15
lines changed

2 files changed

+54
-15
lines changed

crates/red_knot_test/src/parser.rs

+38-14
Original file line numberDiff line numberDiff line change
@@ -420,27 +420,26 @@ impl<'s> Parser<'s> {
420420
}
421421

422422
fn parse_impl(&mut self) -> anyhow::Result<()> {
423-
const SECTION_CONFIG_SNAPSHOT: &str = "<!-- snapshot-diagnostics -->";
423+
const SECTION_CONFIG_SNAPSHOT: &str = "snapshot-diagnostics";
424424
const CODE_BLOCK_END: &[u8] = b"```";
425+
const HTML_COMMENT_END: &[u8] = b"-->";
425426

426427
while let Some(first) = self.cursor.bump() {
427428
match first {
428-
'<' => {
429-
self.explicit_path = None;
430-
self.preceding_blank_lines = 0;
431-
// If we want to support more comment directives, then we should
432-
// probably just parse the directive generically first. But it's
433-
// not clear if we'll want to add more, since comments are hidden
434-
// from GitHub Markdown rendering.
435-
if self
436-
.cursor
437-
.as_str()
438-
.starts_with(&SECTION_CONFIG_SNAPSHOT[1..])
429+
'<' if self.cursor.eat_char3('!', '-', '-') => {
430+
if let Some(position) =
431+
memchr::memmem::find(self.cursor.as_bytes(), HTML_COMMENT_END)
439432
{
440-
self.cursor.skip_bytes(SECTION_CONFIG_SNAPSHOT.len() - 1);
441-
self.process_snapshot_diagnostics()?;
433+
let html_comment = self.cursor.as_str()[..position].trim();
434+
if html_comment == SECTION_CONFIG_SNAPSHOT {
435+
self.process_snapshot_diagnostics()?;
436+
}
437+
self.cursor.skip_bytes(position + HTML_COMMENT_END.len());
438+
} else {
439+
bail!("Unterminated HTML comment.");
442440
}
443441
}
442+
444443
'#' => {
445444
self.explicit_path = None;
446445
self.preceding_blank_lines = 0;
@@ -1729,6 +1728,31 @@ mod tests {
17291728
);
17301729
}
17311730

1731+
#[test]
1732+
fn snapshot_diagnostic_directive_detection_ignores_whitespace() {
1733+
// A bit weird, but the fact that the parser rejects this indicates that
1734+
// we have correctly recognised both of these HTML comments as a directive to have
1735+
// snapshotting enabled for the file, which is what we want (even though they both
1736+
// contain different amounts of whitespace to the "standard" snapshot directive).
1737+
let source = dedent(
1738+
"
1739+
# Some header
1740+
1741+
<!-- snapshot-diagnostics -->
1742+
<!--snapshot-diagnostics-->
1743+
1744+
```py
1745+
x = 1
1746+
```
1747+
",
1748+
);
1749+
let err = super::parse("file.md", &source).expect_err("Should fail to parse");
1750+
assert_eq!(
1751+
err.to_string(),
1752+
"Section config to enable snapshotting diagnostics should appear at most once.",
1753+
);
1754+
}
1755+
17321756
#[test]
17331757
fn section_directive_must_appear_before_config() {
17341758
let source = dedent(

crates/ruff_python_trivia/src/cursor.rs

+16-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,22 @@ impl<'a> Cursor<'a> {
9595
/// Eats the next two characters if they are `c1` and `c2`. Does not
9696
/// consume any input otherwise, even if the first character matches.
9797
pub fn eat_char2(&mut self, c1: char, c2: char) -> bool {
98-
if self.first() == c1 && self.second() == c2 {
98+
let mut chars = self.chars.clone();
99+
if chars.next() == Some(c1) && chars.next() == Some(c2) {
100+
self.bump();
101+
self.bump();
102+
true
103+
} else {
104+
false
105+
}
106+
}
107+
108+
/// Eats the next three characters if they are `c1`, `c2` and `c3`
109+
/// Does not consume any input otherwise, even if the first character matches.
110+
pub fn eat_char3(&mut self, c1: char, c2: char, c3: char) -> bool {
111+
let mut chars = self.chars.clone();
112+
if chars.next() == Some(c1) && chars.next() == Some(c2) && chars.next() == Some(c3) {
113+
self.bump();
99114
self.bump();
100115
self.bump();
101116
true

0 commit comments

Comments
 (0)