Skip to content

Commit 84acd56

Browse files
bors[bot]fmease
andauthored
Merge #5887
5887: Improve fenced code block support for doctests r=fmease a=fmease Fixes #5783 Co-authored-by: León Orell Valerian Liehr <liehr.exchange@gmx.net>
2 parents a0324a5 + 63caef3 commit 84acd56

File tree

2 files changed

+170
-19
lines changed

2 files changed

+170
-19
lines changed

crates/ide/src/runnables.rs

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -211,12 +211,29 @@ fn has_test_related_attribute(fn_def: &ast::Fn) -> bool {
211211
.any(|attribute_text| attribute_text.contains("test"))
212212
}
213213

214+
const RUSTDOC_FENCE: &str = "```";
215+
const RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE: &[&str] =
216+
&["", "rust", "should_panic", "edition2015", "edition2018"];
217+
214218
fn has_runnable_doc_test(fn_def: &ast::Fn) -> bool {
215219
fn_def.doc_comment_text().map_or(false, |comments_text| {
216-
comments_text.contains("```")
217-
&& !comments_text.contains("```ignore")
218-
&& !comments_text.contains("```no_run")
219-
&& !comments_text.contains("```compile_fail")
220+
let mut in_code_block = false;
221+
222+
for line in comments_text.lines() {
223+
if let Some(header) = line.strip_prefix(RUSTDOC_FENCE) {
224+
in_code_block = !in_code_block;
225+
226+
if in_code_block
227+
&& header
228+
.split(',')
229+
.all(|sub| RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE.contains(&sub.trim()))
230+
{
231+
return true;
232+
}
233+
}
234+
}
235+
236+
false
220237
})
221238
}
222239

@@ -421,7 +438,21 @@ fn main() {}
421438
/// ```
422439
/// let x = 5;
423440
/// ```
424-
fn foo() {}
441+
fn should_have_runnable() {}
442+
443+
/// ```edition2018
444+
/// let x = 5;
445+
/// ```
446+
fn should_have_runnable_1() {}
447+
448+
/// ```
449+
/// let z = 55;
450+
/// ```
451+
///
452+
/// ```ignore
453+
/// let z = 56;
454+
/// ```
455+
fn should_have_runnable_2() {}
425456
426457
/// ```no_run
427458
/// let z = 55;
@@ -437,8 +468,27 @@ fn should_have_no_runnable_2() {}
437468
/// let z = 55;
438469
/// ```
439470
fn should_have_no_runnable_3() {}
471+
472+
/// ```text
473+
/// arbitrary plain text
474+
/// ```
475+
fn should_have_no_runnable_4() {}
476+
477+
/// ```text
478+
/// arbitrary plain text
479+
/// ```
480+
///
481+
/// ```sh
482+
/// $ shell code
483+
/// ```
484+
fn should_have_no_runnable_5() {}
485+
486+
/// ```rust,no_run
487+
/// let z = 55;
488+
/// ```
489+
fn should_have_no_runnable_6() {}
440490
"#,
441-
&[&BIN, &DOCTEST],
491+
&[&BIN, &DOCTEST, &DOCTEST, &DOCTEST],
442492
expect![[r#"
443493
[
444494
Runnable {
@@ -464,17 +514,57 @@ fn should_have_no_runnable_3() {}
464514
file_id: FileId(
465515
1,
466516
),
467-
full_range: 15..57,
517+
full_range: 15..74,
468518
focus_range: None,
469-
name: "foo",
519+
name: "should_have_runnable",
520+
kind: FN,
521+
container_name: None,
522+
description: None,
523+
docs: None,
524+
},
525+
kind: DocTest {
526+
test_id: Path(
527+
"should_have_runnable",
528+
),
529+
},
530+
cfg_exprs: [],
531+
},
532+
Runnable {
533+
nav: NavigationTarget {
534+
file_id: FileId(
535+
1,
536+
),
537+
full_range: 76..148,
538+
focus_range: None,
539+
name: "should_have_runnable_1",
540+
kind: FN,
541+
container_name: None,
542+
description: None,
543+
docs: None,
544+
},
545+
kind: DocTest {
546+
test_id: Path(
547+
"should_have_runnable_1",
548+
),
549+
},
550+
cfg_exprs: [],
551+
},
552+
Runnable {
553+
nav: NavigationTarget {
554+
file_id: FileId(
555+
1,
556+
),
557+
full_range: 150..254,
558+
focus_range: None,
559+
name: "should_have_runnable_2",
470560
kind: FN,
471561
container_name: None,
472562
description: None,
473563
docs: None,
474564
},
475565
kind: DocTest {
476566
test_id: Path(
477-
"foo",
567+
"should_have_runnable_2",
478568
),
479569
},
480570
cfg_exprs: [],

crates/rust-analyzer/src/markdown.rs

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,32 @@
11
//! Transforms markdown
22
3+
const RUSTDOC_FENCE: &str = "```";
4+
const RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUST_SPECIFIC: &[&str] =
5+
&["", "rust", "should_panic", "ignore", "no_run", "compile_fail", "edition2015", "edition2018"];
6+
37
pub(crate) fn format_docs(src: &str) -> String {
48
let mut processed_lines = Vec::new();
59
let mut in_code_block = false;
6-
for line in src.lines() {
7-
if in_code_block && code_line_ignored_by_rustdoc(line) {
10+
let mut is_rust = false;
11+
12+
for mut line in src.lines() {
13+
if in_code_block && is_rust && code_line_ignored_by_rustdoc(line) {
814
continue;
915
}
1016

11-
if line.starts_with("```") {
12-
in_code_block ^= true
13-
}
17+
if let Some(header) = line.strip_prefix(RUSTDOC_FENCE) {
18+
in_code_block ^= true;
1419

15-
let line = if in_code_block && line.starts_with("```") && !line.contains("rust") {
16-
"```rust"
17-
} else {
18-
line
19-
};
20+
if in_code_block {
21+
is_rust = header
22+
.split(',')
23+
.all(|sub| RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUST_SPECIFIC.contains(&sub.trim()));
24+
25+
if is_rust {
26+
line = "```rust";
27+
}
28+
}
29+
}
2030

2131
processed_lines.push(line);
2232
}
@@ -38,13 +48,47 @@ mod tests {
3848
assert_eq!(format_docs(comment), "```rust\nfn some_rust() {}\n```");
3949
}
4050

51+
#[test]
52+
fn test_format_docs_handles_plain_text() {
53+
let comment = "```text\nthis is plain text\n```";
54+
assert_eq!(format_docs(comment), "```text\nthis is plain text\n```");
55+
}
56+
57+
#[test]
58+
fn test_format_docs_handles_non_rust() {
59+
let comment = "```sh\nsupposedly shell code\n```";
60+
assert_eq!(format_docs(comment), "```sh\nsupposedly shell code\n```");
61+
}
62+
63+
#[test]
64+
fn test_format_docs_handles_rust_alias() {
65+
let comment = "```ignore\nlet z = 55;\n```";
66+
assert_eq!(format_docs(comment), "```rust\nlet z = 55;\n```");
67+
}
68+
69+
#[test]
70+
fn test_format_docs_handles_complex_code_block_attrs() {
71+
let comment = "```rust,no_run\nlet z = 55;\n```";
72+
assert_eq!(format_docs(comment), "```rust\nlet z = 55;\n```");
73+
}
74+
4175
#[test]
4276
fn test_format_docs_skips_comments_in_rust_block() {
4377
let comment =
4478
"```rust\n # skip1\n# skip2\n#stay1\nstay2\n#\n #\n # \n #\tskip3\n\t#\t\n```";
4579
assert_eq!(format_docs(comment), "```rust\n#stay1\nstay2\n```");
4680
}
4781

82+
#[test]
83+
fn test_format_docs_does_not_skip_lines_if_plain_text() {
84+
let comment =
85+
"```text\n # stay1\n# stay2\n#stay3\nstay4\n#\n #\n # \n #\tstay5\n\t#\t\n```";
86+
assert_eq!(
87+
format_docs(comment),
88+
"```text\n # stay1\n# stay2\n#stay3\nstay4\n#\n #\n # \n #\tstay5\n\t#\t\n```",
89+
);
90+
}
91+
4892
#[test]
4993
fn test_format_docs_keeps_comments_outside_of_rust_block() {
5094
let comment = " # stay1\n# stay2\n#stay3\nstay4\n#\n #\n # \n #\tstay5\n\t#\t";
@@ -72,4 +116,21 @@ let a = 1;
72116
"```rust\nfn main(){}\n```\nSome comment.\n```rust\nlet a = 1;\n```"
73117
);
74118
}
119+
120+
#[test]
121+
fn test_code_blocks_in_comments_marked_as_text() {
122+
let comment = r#"```text
123+
filler
124+
text
125+
```
126+
Some comment.
127+
```
128+
let a = 1;
129+
```"#;
130+
131+
assert_eq!(
132+
format_docs(comment),
133+
"```text\nfiller\ntext\n```\nSome comment.\n```rust\nlet a = 1;\n```"
134+
);
135+
}
75136
}

0 commit comments

Comments
 (0)