aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ide/src/runnables.rs108
-rw-r--r--crates/rust-analyzer/src/markdown.rs81
2 files changed, 170 insertions, 19 deletions
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index dd59d9e70..989a63c09 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -211,12 +211,29 @@ fn has_test_related_attribute(fn_def: &ast::Fn) -> bool {
211 .any(|attribute_text| attribute_text.contains("test")) 211 .any(|attribute_text| attribute_text.contains("test"))
212} 212}
213 213
214const RUSTDOC_FENCE: &str = "```";
215const RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE: &[&str] =
216 &["", "rust", "should_panic", "edition2015", "edition2018"];
217
214fn has_runnable_doc_test(fn_def: &ast::Fn) -> bool { 218fn has_runnable_doc_test(fn_def: &ast::Fn) -> bool {
215 fn_def.doc_comment_text().map_or(false, |comments_text| { 219 fn_def.doc_comment_text().map_or(false, |comments_text| {
216 comments_text.contains("```") 220 let mut in_code_block = false;
217 && !comments_text.contains("```ignore") 221
218 && !comments_text.contains("```no_run") 222 for line in comments_text.lines() {
219 && !comments_text.contains("```compile_fail") 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
220 }) 237 })
221} 238}
222 239
@@ -421,7 +438,21 @@ fn main() {}
421/// ``` 438/// ```
422/// let x = 5; 439/// let x = 5;
423/// ``` 440/// ```
424fn foo() {} 441fn should_have_runnable() {}
442
443/// ```edition2018
444/// let x = 5;
445/// ```
446fn should_have_runnable_1() {}
447
448/// ```
449/// let z = 55;
450/// ```
451///
452/// ```ignore
453/// let z = 56;
454/// ```
455fn should_have_runnable_2() {}
425 456
426/// ```no_run 457/// ```no_run
427/// let z = 55; 458/// let z = 55;
@@ -437,8 +468,27 @@ fn should_have_no_runnable_2() {}
437/// let z = 55; 468/// let z = 55;
438/// ``` 469/// ```
439fn should_have_no_runnable_3() {} 470fn should_have_no_runnable_3() {}
471
472/// ```text
473/// arbitrary plain text
474/// ```
475fn should_have_no_runnable_4() {}
476
477/// ```text
478/// arbitrary plain text
479/// ```
480///
481/// ```sh
482/// $ shell code
483/// ```
484fn should_have_no_runnable_5() {}
485
486/// ```rust,no_run
487/// let z = 55;
488/// ```
489fn should_have_no_runnable_6() {}
440"#, 490"#,
441 &[&BIN, &DOCTEST], 491 &[&BIN, &DOCTEST, &DOCTEST, &DOCTEST],
442 expect![[r#" 492 expect![[r#"
443 [ 493 [
444 Runnable { 494 Runnable {
@@ -464,9 +514,49 @@ fn should_have_no_runnable_3() {}
464 file_id: FileId( 514 file_id: FileId(
465 1, 515 1,
466 ), 516 ),
467 full_range: 15..57, 517 full_range: 15..74,
468 focus_range: None, 518 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",
470 kind: FN, 560 kind: FN,
471 container_name: None, 561 container_name: None,
472 description: None, 562 description: None,
@@ -474,7 +564,7 @@ fn should_have_no_runnable_3() {}
474 }, 564 },
475 kind: DocTest { 565 kind: DocTest {
476 test_id: Path( 566 test_id: Path(
477 "foo", 567 "should_have_runnable_2",
478 ), 568 ),
479 }, 569 },
480 cfg_exprs: [], 570 cfg_exprs: [],
diff --git a/crates/rust-analyzer/src/markdown.rs b/crates/rust-analyzer/src/markdown.rs
index 76bef45cc..968ea55f0 100644
--- a/crates/rust-analyzer/src/markdown.rs
+++ b/crates/rust-analyzer/src/markdown.rs
@@ -1,22 +1,32 @@
1//! Transforms markdown 1//! Transforms markdown
2 2
3const RUSTDOC_FENCE: &str = "```";
4const RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUST_SPECIFIC: &[&str] =
5 &["", "rust", "should_panic", "ignore", "no_run", "compile_fail", "edition2015", "edition2018"];
6
3pub(crate) fn format_docs(src: &str) -> String { 7pub(crate) fn format_docs(src: &str) -> String {
4 let mut processed_lines = Vec::new(); 8 let mut processed_lines = Vec::new();
5 let mut in_code_block = false; 9 let mut in_code_block = false;
6 for line in src.lines() { 10 let mut is_rust = false;
7 if in_code_block && code_line_ignored_by_rustdoc(line) { 11
12 for mut line in src.lines() {
13 if in_code_block && is_rust && code_line_ignored_by_rustdoc(line) {
8 continue; 14 continue;
9 } 15 }
10 16
11 if line.starts_with("```") { 17 if let Some(header) = line.strip_prefix(RUSTDOC_FENCE) {
12 in_code_block ^= true 18 in_code_block ^= true;
13 }
14 19
15 let line = if in_code_block && line.starts_with("```") && !line.contains("rust") { 20 if in_code_block {
16 "```rust" 21 is_rust = header
17 } else { 22 .split(',')
18 line 23 .all(|sub| RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUST_SPECIFIC.contains(&sub.trim()));
19 }; 24
25 if is_rust {
26 line = "```rust";
27 }
28 }
29 }
20 30
21 processed_lines.push(line); 31 processed_lines.push(line);
22 } 32 }
@@ -39,6 +49,30 @@ mod tests {
39 } 49 }
40 50
41 #[test] 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
75 #[test]
42 fn test_format_docs_skips_comments_in_rust_block() { 76 fn test_format_docs_skips_comments_in_rust_block() {
43 let comment = 77 let comment =
44 "```rust\n # skip1\n# skip2\n#stay1\nstay2\n#\n #\n # \n #\tskip3\n\t#\t\n```"; 78 "```rust\n # skip1\n# skip2\n#stay1\nstay2\n#\n #\n # \n #\tskip3\n\t#\t\n```";
@@ -46,6 +80,16 @@ mod tests {
46 } 80 }
47 81
48 #[test] 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
92 #[test]
49 fn test_format_docs_keeps_comments_outside_of_rust_block() { 93 fn test_format_docs_keeps_comments_outside_of_rust_block() {
50 let comment = " # stay1\n# stay2\n#stay3\nstay4\n#\n #\n # \n #\tstay5\n\t#\t"; 94 let comment = " # stay1\n# stay2\n#stay3\nstay4\n#\n #\n # \n #\tstay5\n\t#\t";
51 assert_eq!(format_docs(comment), comment); 95 assert_eq!(format_docs(comment), comment);
@@ -72,4 +116,21 @@ let a = 1;
72 "```rust\nfn main(){}\n```\nSome comment.\n```rust\nlet a = 1;\n```" 116 "```rust\nfn main(){}\n```\nSome comment.\n```rust\nlet a = 1;\n```"
73 ); 117 );
74 } 118 }
119
120 #[test]
121 fn test_code_blocks_in_comments_marked_as_text() {
122 let comment = r#"```text
123filler
124text
125```
126Some comment.
127```
128let 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 }
75} 136}