rust_code_analysis/
comment_rm.rs

1use std::io::{self, Write};
2use std::path::PathBuf;
3
4use crate::checker::Checker;
5use crate::node::Node;
6
7use crate::tools::*;
8use crate::traits::*;
9
10const CR: [u8; 8192] = [b'\n'; 8192];
11
12/// Removes comments from a code.
13pub fn rm_comments<T: ParserTrait>(parser: &T) -> Option<Vec<u8>> {
14    let node = parser.get_root();
15    let mut stack = Vec::new();
16    let mut cursor = node.object().walk();
17    let mut spans = Vec::new();
18
19    stack.push(node);
20
21    while let Some(node) = stack.pop() {
22        if T::Checker::is_comment(&node) && !T::Checker::is_useful_comment(&node, parser.get_code())
23        {
24            let lines = node.object().end_position().row - node.object().start_position().row;
25            spans.push((node.object().start_byte(), node.object().end_byte(), lines));
26        } else {
27            cursor.reset(node.object());
28            if cursor.goto_first_child() {
29                loop {
30                    stack.push(Node::new(cursor.node()));
31                    if !cursor.goto_next_sibling() {
32                        break;
33                    }
34                }
35            }
36        }
37    }
38    if !spans.is_empty() {
39        Some(remove_from_code(parser.get_code(), spans))
40    } else {
41        None
42    }
43}
44
45fn remove_from_code(code: &[u8], mut spans: Vec<(usize, usize, usize)>) -> Vec<u8> {
46    let mut new_code = Vec::with_capacity(code.len());
47    let mut code_start = 0;
48    for (start, end, lines) in spans.drain(..).rev() {
49        new_code.extend(&code[code_start..start]);
50        if lines != 0 {
51            if lines <= CR.len() {
52                new_code.extend(&CR[..lines]);
53            } else {
54                new_code.resize_with(new_code.len() + lines, || b'\n');
55            }
56        }
57        code_start = end;
58    }
59    if code_start < code.len() {
60        new_code.extend(&code[code_start..]);
61    }
62    new_code
63}
64
65/// Configuration options for removing comments from a code.
66pub struct CommentRmCfg {
67    /// If `true`, the modified code is saved on a file
68    pub in_place: bool,
69    /// Path to output file
70    pub path: PathBuf,
71}
72
73pub struct CommentRm {
74    _guard: (),
75}
76
77impl Callback for CommentRm {
78    type Res = std::io::Result<()>;
79    type Cfg = CommentRmCfg;
80
81    fn call<T: ParserTrait>(cfg: Self::Cfg, parser: &T) -> Self::Res {
82        if let Some(new_source) = rm_comments(parser) {
83            if cfg.in_place {
84                write_file(&cfg.path, &new_source)?;
85            } else if let Ok(new_source) = std::str::from_utf8(&new_source) {
86                println!("{}", new_source);
87            } else {
88                io::stdout().write_all(&new_source)?;
89            }
90        }
91        Ok(())
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use std::path::PathBuf;
98
99    use crate::{CcommentParser, ParserTrait};
100
101    use super::rm_comments;
102
103    const SOURCE_CODE: &str = "/* Remove this code block */\n\
104                               int a = 42; // Remove this comment\n\
105                               // Remove this comment\n\
106                               int b = 42;\n\
107                               /* Remove\n\
108                                * this\n\
109                                * comment\n\
110                                */";
111
112    const SOURCE_CODE_NO_COMMENTS: &str = "\n\
113                                           int a = 42; \n\
114                                           \n\
115                                           int b = 42;\n\
116                                           \n\
117                                           \n\
118                                           \n\
119                                           \n";
120
121    #[test]
122    fn ccomment_remove_comments() {
123        let path = PathBuf::from("foo.c");
124        let mut trimmed_bytes = SOURCE_CODE.as_bytes().to_vec();
125        trimmed_bytes.push(b'\n');
126        let parser = CcommentParser::new(trimmed_bytes, &path, None);
127
128        let no_comments = rm_comments(&parser).unwrap();
129
130        assert_eq!(no_comments.as_slice(), SOURCE_CODE_NO_COMMENTS.as_bytes());
131    }
132}