cargo_rdme/transform/
rust_remove_comments.rs

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