mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:37 +00:00
[red-knot] Error out when an mdtest code block is unterminated (#14965)
## Summary Resolves #14934. ## Test Plan Added a unit test.
This commit is contained in:
parent
224c8438bd
commit
a80e934838
1 changed files with 51 additions and 3 deletions
|
@ -7,7 +7,8 @@ use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
|
||||||
use ruff_index::{newtype_index, IndexVec};
|
use ruff_index::{newtype_index, IndexVec};
|
||||||
use ruff_python_trivia::Cursor;
|
use ruff_python_trivia::Cursor;
|
||||||
use ruff_text_size::{TextLen, TextSize};
|
use ruff_source_file::LineRanges;
|
||||||
|
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||||
|
|
||||||
use crate::config::MarkdownTestConfig;
|
use crate::config::MarkdownTestConfig;
|
||||||
|
|
||||||
|
@ -156,7 +157,13 @@ static HEADER_RE: LazyLock<Regex> =
|
||||||
/// Matches a code block fenced by triple backticks, possibly with language and `key=val`
|
/// Matches a code block fenced by triple backticks, possibly with language and `key=val`
|
||||||
/// configuration items following the opening backticks (in the "tag string" of the code block).
|
/// configuration items following the opening backticks (in the "tag string" of the code block).
|
||||||
static CODE_RE: LazyLock<Regex> = LazyLock::new(|| {
|
static CODE_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||||
Regex::new(r"^```(?<lang>(?-u:\w)+)?(?<config>(?: +\S+)*)\s*\n(?<code>(?:.|\n)*?)\n?```\s*\n?")
|
Regex::new(
|
||||||
|
r"(?x)
|
||||||
|
^```(?<lang>(?-u:\w)+)?(?<config>(?:\x20+\S+)*)\s*\n
|
||||||
|
(?<code>(?:.|\n)*?)\n?
|
||||||
|
(?<end>```|\z)
|
||||||
|
",
|
||||||
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -202,6 +209,7 @@ struct Parser<'s> {
|
||||||
/// The unparsed remainder of the Markdown source.
|
/// The unparsed remainder of the Markdown source.
|
||||||
cursor: Cursor<'s>,
|
cursor: Cursor<'s>,
|
||||||
|
|
||||||
|
source: &'s str,
|
||||||
source_len: TextSize,
|
source_len: TextSize,
|
||||||
|
|
||||||
/// Stack of ancestor sections.
|
/// Stack of ancestor sections.
|
||||||
|
@ -225,6 +233,7 @@ impl<'s> Parser<'s> {
|
||||||
});
|
});
|
||||||
Self {
|
Self {
|
||||||
sections,
|
sections,
|
||||||
|
source,
|
||||||
files: IndexVec::default(),
|
files: IndexVec::default(),
|
||||||
cursor: Cursor::new(source),
|
cursor: Cursor::new(source),
|
||||||
source_len: source.text_len(),
|
source_len: source.text_len(),
|
||||||
|
@ -328,6 +337,13 @@ impl<'s> Parser<'s> {
|
||||||
// We never pop the implicit root section.
|
// We never pop the implicit root section.
|
||||||
let section = self.stack.top();
|
let section = self.stack.top();
|
||||||
|
|
||||||
|
if captures.name("end").unwrap().is_empty() {
|
||||||
|
let code_block_start = self.cursor.token_len();
|
||||||
|
let line = self.source.count_lines(TextRange::up_to(code_block_start)) + 1;
|
||||||
|
|
||||||
|
return Err(anyhow::anyhow!("Unterminated code block at line {line}."));
|
||||||
|
}
|
||||||
|
|
||||||
let mut config: FxHashMap<&'s str, &'s str> = FxHashMap::default();
|
let mut config: FxHashMap<&'s str, &'s str> = FxHashMap::default();
|
||||||
|
|
||||||
if let Some(config_match) = captures.name("config") {
|
if let Some(config_match) = captures.name("config") {
|
||||||
|
@ -664,6 +680,38 @@ mod tests {
|
||||||
assert_eq!(file.code, "x = 10");
|
assert_eq!(file.code, "x = 10");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unterminated_code_block_1() {
|
||||||
|
let source = dedent(
|
||||||
|
"
|
||||||
|
```
|
||||||
|
x = 1
|
||||||
|
",
|
||||||
|
);
|
||||||
|
let err = super::parse("file.md", &source).expect_err("Should fail to parse");
|
||||||
|
assert_eq!(err.to_string(), "Unterminated code block at line 2.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unterminated_code_block_2() {
|
||||||
|
let source = dedent(
|
||||||
|
"
|
||||||
|
## A well-fenced block
|
||||||
|
|
||||||
|
```
|
||||||
|
y = 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## A not-so-well-fenced block
|
||||||
|
|
||||||
|
```
|
||||||
|
x = 1
|
||||||
|
",
|
||||||
|
);
|
||||||
|
let err = super::parse("file.md", &source).expect_err("Should fail to parse");
|
||||||
|
assert_eq!(err.to_string(), "Unterminated code block at line 10.");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn no_header_inside_test() {
|
fn no_header_inside_test() {
|
||||||
let source = dedent(
|
let source = dedent(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue