markdown_includes/rustdoc_parse/transform/
rust_remove_comments.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 */
5
6use crate::rustdoc_parse::transform::utils::rust_code_block_iterator;
7use crate::rustdoc_parse::transform::DocTransform;
8use crate::rustdoc_parse::utils::ItemOrOther;
9use crate::rustdoc_parse::Doc;
10use std::convert::Infallible;
11
12pub struct DocTransformRustRemoveComments;
13
14impl DocTransformRustRemoveComments {
15    #[must_use]
16    pub fn new() -> DocTransformRustRemoveComments {
17        DocTransformRustRemoveComments
18    }
19}
20
21fn is_line_commented(line: &str) -> bool {
22    line.trim_start().starts_with("# ") || line.trim() == "#"
23}
24
25fn process_code_block(new_doc_str: &mut String, code_block: &str) {
26    let mut first = true;
27
28    for (i, line) in code_block.split('\n').enumerate() {
29        // If we have an indent code block and we start with a comment we need to
30        // drop any indent whitespace that started this indent block, since
31        // pulldown-cmark doesn't consider it part of the code block.
32        if i == 0 && is_line_commented(line) {
33            while !new_doc_str.ends_with('\n') && !new_doc_str.is_empty() {
34                new_doc_str.pop();
35            }
36        }
37
38        if !is_line_commented(line) {
39            if !first {
40                new_doc_str.push('\n');
41            }
42
43            // Lines starting with `##` are not comments, that is a way to intentionally start a
44            // line with `#`.  See https://github.com/rust-lang/rust/pull/41785.
45            match line.trim_start().starts_with("##") {
46                true => new_doc_str.push_str(&line.replacen('#', "", 1)),
47                false => new_doc_str.push_str(line),
48            }
49
50            first = false;
51        }
52    }
53}
54
55impl DocTransform for DocTransformRustRemoveComments {
56    type E = Infallible;
57
58    fn transform(&self, doc: &Doc) -> Result<Doc, Infallible> {
59        let mut new_doc_str = String::new();
60
61        for item_or_other in rust_code_block_iterator(&doc.content).complete() {
62            match item_or_other {
63                ItemOrOther::Item(code_block) => {
64                    process_code_block(&mut new_doc_str, code_block);
65                }
66                ItemOrOther::Other(other) => {
67                    new_doc_str.push_str(other);
68                }
69            }
70        }
71
72        Ok(Doc::from_str(new_doc_str))
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use indoc::indoc;
80    use pretty_assertions::assert_eq;
81
82    #[test]
83    fn test_remove_comments_no_code_block() {
84        let doc_str = indoc! { r#"
85            # The crate
86
87            Look a this code:
88
89            That's all!  Have a nice day!
90            "#
91        };
92
93        let expected_str = indoc! { r#"
94            # The crate
95
96            Look a this code:
97
98            That's all!  Have a nice day!
99            "#
100        };
101
102        let doc = Doc::from_str(doc_str);
103        let expected = Doc::from_str(expected_str);
104
105        let transform = DocTransformRustRemoveComments::new();
106
107        assert_eq!(transform.transform(&doc).unwrap(), expected);
108    }
109
110    #[test]
111    fn test_remove_comments_fenced_code_block() {
112        let doc_str = indoc! { r#"
113            # The crate
114
115            Look a this code:
116
117            ```
118            println!("Hi");
119            println!("There");
120            ```
121
122            A second one:
123
124            ```
125            # A comment.
126            println!("Hi");
127            println!("There");
128            ```
129
130            And so one:
131
132            ```
133            println!("Hi");
134            # A comment.
135            println!("There");
136            ```
137
138            And so forth:
139
140            ```
141            println!("Hi");
142            println!("There");
143            # A comment.
144            ```
145
146            That's all!  Have a nice day!
147            "#
148        };
149
150        let expected_str = indoc! { r#"
151            # The crate
152
153            Look a this code:
154
155            ```
156            println!("Hi");
157            println!("There");
158            ```
159
160            A second one:
161
162            ```
163            println!("Hi");
164            println!("There");
165            ```
166
167            And so one:
168
169            ```
170            println!("Hi");
171            println!("There");
172            ```
173
174            And so forth:
175
176            ```
177            println!("Hi");
178            println!("There");
179            ```
180
181            That's all!  Have a nice day!
182            "#
183        };
184
185        let doc = Doc::from_str(doc_str);
186        let expected = Doc::from_str(expected_str);
187
188        let transform = DocTransformRustRemoveComments::new();
189
190        assert_eq!(transform.transform(&doc).unwrap(), expected);
191    }
192
193    #[test]
194    fn test_remove_comments_fenced_code_block_starting_with_whitespace() {
195        let doc_str = indoc! { r#"
196            # The crate
197
198            Look a this code:
199
200              ```
201            println!("Hi");
202            println!("There");
203            ```
204
205            A second one:
206
207              ```
208            # A comment.
209            println!("Hi");
210            println!("There");
211            ```
212
213            And so one:
214
215              ```
216            println!("Hi");
217            # A comment.
218            println!("There");
219            ```
220
221            And so forth:
222
223              ```
224            println!("Hi");
225            println!("There");
226            # A comment.
227            ```
228
229            That's all!  Have a nice day!
230            "#
231        };
232
233        let expected_str = indoc! { r#"
234            # The crate
235
236            Look a this code:
237
238              ```
239            println!("Hi");
240            println!("There");
241            ```
242
243            A second one:
244
245              ```
246            println!("Hi");
247            println!("There");
248            ```
249
250            And so one:
251
252              ```
253            println!("Hi");
254            println!("There");
255            ```
256
257            And so forth:
258
259              ```
260            println!("Hi");
261            println!("There");
262            ```
263
264            That's all!  Have a nice day!
265            "#
266        };
267
268        let doc = Doc::from_str(doc_str);
269        let expected = Doc::from_str(expected_str);
270
271        let transform = DocTransformRustRemoveComments::new();
272
273        assert_eq!(transform.transform(&doc).unwrap(), expected);
274    }
275
276    #[test]
277    fn test_remove_comments_indent_code_block() {
278        let doc_str = indoc! { r#"
279            # The crate
280
281            Look a this code:
282
283                println!("Hi");
284                println!("There");
285
286            A second one:
287
288                # A comment.
289                println!("Hi");
290                println!("There");
291
292            And so one:
293
294                println!("Hi");
295                # A comment.
296                println!("There");
297
298            And so forth:
299
300                println!("Hi");
301                println!("There");
302                # A comment.
303
304            That's all!  Have a nice day!
305            "#
306        };
307
308        let expected_str = indoc! { r#"
309            # The crate
310
311            Look a this code:
312
313                println!("Hi");
314                println!("There");
315
316            A second one:
317
318                println!("Hi");
319                println!("There");
320
321            And so one:
322
323                println!("Hi");
324                println!("There");
325
326            And so forth:
327
328                println!("Hi");
329                println!("There");
330
331            That's all!  Have a nice day!
332            "#
333        };
334
335        let doc = Doc::from_str(doc_str);
336        let expected = Doc::from_str(expected_str);
337
338        let transform = DocTransformRustRemoveComments::new();
339
340        assert_eq!(transform.transform(&doc).unwrap(), expected);
341    }
342
343    #[test]
344    fn test_remove_comments_indent_code_block_empty_lines() {
345        let doc_str = indoc! { r#"
346            # The crate
347
348            Look a this code:
349
350                println!("Hi");
351
352                println!("There");
353
354            That's all!  Have a nice day!
355            "#
356        };
357
358        let expected_str = indoc! { r#"
359            # The crate
360
361            Look a this code:
362
363                println!("Hi");
364
365                println!("There");
366
367            That's all!  Have a nice day!
368            "#
369        };
370
371        let doc = Doc::from_str(doc_str);
372        let expected = Doc::from_str(expected_str);
373
374        let transform = DocTransformRustRemoveComments::new();
375
376        assert_eq!(transform.transform(&doc).unwrap(), expected);
377    }
378
379    #[test]
380    fn test_remove_comments_indent_code_beginning_file_with_comment() {
381        let doc_str = indoc! { r#"
382                # Comment
383                println!("Hi");
384                # x
385                println!("There");
386
387            That's all!  Have a nice day!
388            "#
389        };
390
391        assert!(doc_str.starts_with("    #"), "Ensure file starts correctly");
392
393        let expected_str = indoc! { r#"
394                println!("Hi");
395                println!("There");
396
397            That's all!  Have a nice day!
398            "#
399        };
400
401        let doc = Doc::from_str(doc_str);
402        let expected = Doc::from_str(expected_str);
403
404        let transform = DocTransformRustRemoveComments::new();
405
406        assert_eq!(transform.transform(&doc).unwrap(), expected);
407    }
408
409    #[test]
410    fn test_remove_comments_indent_code_beginning_file_no_comment() {
411        let doc_str = indoc! { r#"
412                println!("Hi");
413                # x
414                println!("There");
415
416            That's all!  Have a nice day!
417            "#
418        };
419
420        assert!(
421            doc_str.starts_with("    println!"),
422            "Ensure file starts correctly"
423        );
424
425        let expected_str = indoc! { r#"
426                println!("Hi");
427                println!("There");
428
429            That's all!  Have a nice day!
430            "#
431        };
432
433        let doc = Doc::from_str(doc_str);
434        let expected = Doc::from_str(expected_str);
435
436        let transform = DocTransformRustRemoveComments::new();
437
438        assert_eq!(transform.transform(&doc).unwrap(), expected);
439    }
440
441    #[test]
442    fn test_remove_comments_identify_comment() {
443        let doc_str = indoc! { "
444            # The crate
445
446            Look a this code:
447
448            ```
449            # This is a comment
450            #This is not.
451            println!(\"There\");
452            #
453            # ↑ That line is a comment
454            #\t
455            # ↑ And so is that one.
456            ```
457            "
458        };
459
460        let expected_str = indoc! { r#"
461            # The crate
462
463            Look a this code:
464
465            ```
466            #This is not.
467            println!("There");
468            ```
469            "#
470        };
471
472        let doc = Doc::from_str(doc_str);
473        let expected = Doc::from_str(expected_str);
474
475        let transform = DocTransformRustRemoveComments::new();
476
477        assert_eq!(transform.transform(&doc).unwrap(), expected);
478    }
479
480    #[test]
481    fn test_remove_comments_double_hash_escape_comment() {
482        let doc_str = indoc! { r#"
483            ```
484            if true {
485                ## This is not a comment.
486                ##And neither is this.
487                println!("There");
488            }
489            ```
490            "#
491        };
492
493        let expected_str = indoc! { r#"
494            ```
495            if true {
496                # This is not a comment.
497                #And neither is this.
498                println!("There");
499            }
500            ```
501            "#
502        };
503
504        let doc = Doc::from_str(doc_str);
505        let expected = Doc::from_str(expected_str);
506
507        let transform = DocTransformRustRemoveComments::new();
508
509        assert_eq!(transform.transform(&doc).unwrap(), expected);
510    }
511
512    #[test]
513    fn test_remove_comments_for_known_code_block_tags() {
514        let tags = [
515            "should_panic",
516            "no_run",
517            "ignore",
518            "allow_fail",
519            "rust",
520            "test_harness",
521            "compile_fail",
522            "edition2018",
523            "ignore-foo",
524        ];
525
526        for tag in tags {
527            let doc_str = format!(
528                "```{}\n# This is a comment.\nprintln!(\"#There\");\n```\n",
529                tag
530            );
531
532            let expected_str = format!("```{}\nprintln!(\"#There\");\n```\n", tag);
533
534            let doc = Doc::from_str(doc_str);
535            let expected = Doc::from_str(expected_str);
536
537            let transform = DocTransformRustRemoveComments::new();
538
539            assert_eq!(transform.transform(&doc).unwrap(), expected);
540        }
541    }
542
543    #[test]
544    fn test_remove_comments_for_unknown_code_block_tags_no_change() {
545        let tags = ["text", "bash"];
546
547        for tag in tags {
548            let doc_str = format!(
549                "```{}\n# This is a comment.\nprintln!(\"#There\");\n```\n",
550                tag
551            );
552            let doc = Doc::from_str(doc_str);
553
554            let transform = DocTransformRustRemoveComments::new();
555
556            assert_eq!(transform.transform(&doc).unwrap(), doc);
557        }
558    }
559
560    #[test]
561    fn test_remove_comments_nested_fenced_block() {
562        let doc_str = indoc! { r#"
563            ````
564            # Comment 1
565            let s = "
566            ```
567            ";
568            # Comment 2
569            println!("Hi");
570            let s = "
571            ```
572            ";
573            ````
574            "#
575        };
576
577        let expected_str = indoc! { r#"
578            ````
579            let s = "
580            ```
581            ";
582            println!("Hi");
583            let s = "
584            ```
585            ";
586            ````
587            "#
588        };
589
590        let doc = Doc::from_str(doc_str);
591        let expected = Doc::from_str(expected_str);
592
593        let transform = DocTransformRustRemoveComments::new();
594
595        assert_eq!(transform.transform(&doc).unwrap(), expected);
596    }
597}