1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
use std::io::{self, Write};
use std::path::PathBuf;

use crate::checker::Checker;
use crate::node::Node;

use crate::tools::*;
use crate::traits::*;

const CR: [u8; 8192] = [b'\n'; 8192];

/// Removes comments from a code.
pub fn rm_comments<T: ParserTrait>(parser: &T) -> Option<Vec<u8>> {
    let node = parser.get_root();
    let mut stack = Vec::new();
    let mut cursor = node.object().walk();
    let mut spans = Vec::new();

    stack.push(node);

    while let Some(node) = stack.pop() {
        if T::Checker::is_comment(&node) && !T::Checker::is_useful_comment(&node, parser.get_code())
        {
            let lines = node.object().end_position().row - node.object().start_position().row;
            spans.push((node.object().start_byte(), node.object().end_byte(), lines));
        } else {
            cursor.reset(node.object());
            if cursor.goto_first_child() {
                loop {
                    stack.push(Node::new(cursor.node()));
                    if !cursor.goto_next_sibling() {
                        break;
                    }
                }
            }
        }
    }
    if !spans.is_empty() {
        Some(remove_from_code(parser.get_code(), spans))
    } else {
        None
    }
}

fn remove_from_code(code: &[u8], mut spans: Vec<(usize, usize, usize)>) -> Vec<u8> {
    let mut new_code = Vec::with_capacity(code.len());
    let mut code_start = 0;
    for (start, end, lines) in spans.drain(..).rev() {
        new_code.extend(&code[code_start..start]);
        if lines != 0 {
            if lines <= CR.len() {
                new_code.extend(&CR[..lines]);
            } else {
                new_code.resize_with(new_code.len() + lines, || b'\n');
            }
        }
        code_start = end;
    }
    if code_start < code.len() {
        new_code.extend(&code[code_start..]);
    }
    new_code
}

/// Configuration options for removing comments from a code.
pub struct CommentRmCfg {
    /// If `true`, the modified code is saved on a file
    pub in_place: bool,
    /// Path to output file
    pub path: PathBuf,
}

pub struct CommentRm {
    _guard: (),
}

impl Callback for CommentRm {
    type Res = std::io::Result<()>;
    type Cfg = CommentRmCfg;

    fn call<T: ParserTrait>(cfg: Self::Cfg, parser: &T) -> Self::Res {
        if let Some(new_source) = rm_comments(parser) {
            if cfg.in_place {
                write_file(&cfg.path, &new_source)?;
            } else if let Ok(new_source) = std::str::from_utf8(&new_source) {
                println!("{}", new_source);
            } else {
                io::stdout().write_all(&new_source)?;
            }
        }
        Ok(())
    }
}