cargo_rdme/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::transform::utils::rust_code_block_iterator;
7use crate::transform::DocTransform;
8use crate::utils::ItemOrOther;
9use crate::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.markdown).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!(doc_str.starts_with("    println!"), "Ensure file starts correctly");
421
422        let expected_str = indoc! { r#"
423                println!("Hi");
424                println!("There");
425
426            That's all!  Have a nice day!
427            "#
428        };
429
430        let doc = Doc::from_str(doc_str);
431        let expected = Doc::from_str(expected_str);
432
433        let transform = DocTransformRustRemoveComments::new();
434
435        assert_eq!(transform.transform(&doc).unwrap(), expected);
436    }
437
438    #[test]
439    fn test_remove_comments_identify_comment() {
440        let doc_str = indoc! { "
441            # The crate
442
443            Look a this code:
444
445            ```
446            # This is a comment
447            #This is not.
448            println!(\"There\");
449            #
450            # ↑ That line is a comment
451            #\t
452            # ↑ And so is that one.
453            ```
454            "
455        };
456
457        let expected_str = indoc! { r#"
458            # The crate
459
460            Look a this code:
461
462            ```
463            #This is not.
464            println!("There");
465            ```
466            "#
467        };
468
469        let doc = Doc::from_str(doc_str);
470        let expected = Doc::from_str(expected_str);
471
472        let transform = DocTransformRustRemoveComments::new();
473
474        assert_eq!(transform.transform(&doc).unwrap(), expected);
475    }
476
477    #[test]
478    fn test_remove_comments_double_hash_escape_comment() {
479        let doc_str = indoc! { r#"
480            ```
481            if true {
482                ## This is not a comment.
483                ##And neither is this.
484                println!("There");
485            }
486            ```
487            "#
488        };
489
490        let expected_str = indoc! { r#"
491            ```
492            if true {
493                # This is not a comment.
494                #And neither is this.
495                println!("There");
496            }
497            ```
498            "#
499        };
500
501        let doc = Doc::from_str(doc_str);
502        let expected = Doc::from_str(expected_str);
503
504        let transform = DocTransformRustRemoveComments::new();
505
506        assert_eq!(transform.transform(&doc).unwrap(), expected);
507    }
508
509    #[test]
510    fn test_remove_comments_for_known_code_block_tags() {
511        let tags = [
512            "should_panic",
513            "no_run",
514            "ignore",
515            "allow_fail",
516            "rust",
517            "test_harness",
518            "compile_fail",
519            "edition2018",
520            "ignore-foo",
521        ];
522
523        for tag in tags {
524            let doc_str = format!("```{tag}\n# This is a comment.\nprintln!(\"#There\");\n```\n");
525
526            let expected_str = format!("```{tag}\nprintln!(\"#There\");\n```\n");
527
528            let doc = Doc::from_str(doc_str);
529            let expected = Doc::from_str(expected_str);
530
531            let transform = DocTransformRustRemoveComments::new();
532
533            assert_eq!(transform.transform(&doc).unwrap(), expected);
534        }
535    }
536
537    #[test]
538    fn test_remove_comments_for_unknown_code_block_tags_no_change() {
539        let tags = ["text", "bash"];
540
541        for tag in tags {
542            let doc_str = format!("```{tag}\n# This is a comment.\nprintln!(\"#There\");\n```\n");
543            let doc = Doc::from_str(doc_str);
544
545            let transform = DocTransformRustRemoveComments::new();
546
547            assert_eq!(transform.transform(&doc).unwrap(), doc);
548        }
549    }
550
551    #[test]
552    fn test_remove_comments_nested_fenced_block() {
553        let doc_str = indoc! { r#"
554            ````
555            # Comment 1
556            let s = "
557            ```
558            ";
559            # Comment 2
560            println!("Hi");
561            let s = "
562            ```
563            ";
564            ````
565            "#
566        };
567
568        let expected_str = indoc! { r#"
569            ````
570            let s = "
571            ```
572            ";
573            println!("Hi");
574            let s = "
575            ```
576            ";
577            ````
578            "#
579        };
580
581        let doc = Doc::from_str(doc_str);
582        let expected = Doc::from_str(expected_str);
583
584        let transform = DocTransformRustRemoveComments::new();
585
586        assert_eq!(transform.transform(&doc).unwrap(), expected);
587    }
588}