sf_afmt/
formatter.rs

1use crate::context::CommentMap;
2use crate::data_model::*;
3use crate::doc::{pretty_print, PrettyConfig};
4use crate::doc_builder::DocBuilder;
5use crate::message_helper::{red, yellow};
6use crate::utility::{
7    assert_no_missing_comments, collect_comments, enrich, set_thread_comment_map,
8    set_thread_source_code, truncate_snippet,
9};
10use serde::Deserialize;
11use std::sync::mpsc;
12use std::thread;
13use std::{fs, path::Path};
14use tree_sitter::{Node, Parser, Tree};
15
16#[allow(unused_imports)]
17use crate::utility::print_comment_map;
18
19#[derive(Clone, Debug, Deserialize)]
20pub struct Config {
21    #[serde(default = "default_max_width")]
22    pub max_width: u32,
23
24    #[serde(default = "default_indent_size")]
25    pub indent_size: u32,
26}
27
28fn default_max_width() -> u32 {
29    80
30}
31
32fn default_indent_size() -> u32 {
33    2
34}
35
36impl Default for Config {
37    fn default() -> Self {
38        Self {
39            max_width: default_max_width(),
40            indent_size: default_indent_size(),
41        }
42    }
43}
44
45impl Config {
46    pub fn new(max_width: u32) -> Self {
47        Self {
48            max_width,
49            indent_size: 2,
50        }
51    }
52
53    pub fn from_file(path: &str) -> Result<Self, String> {
54        let content =
55            fs::read_to_string(path).map_err(|e| format!("Failed to read config file: {}", e))?;
56        let config: Config =
57            toml::from_str(&content).map_err(|e| format!("Failed to parse config file: {}", e))?;
58        Ok(config)
59    }
60
61    pub fn max_width(&self) -> u32 {
62        self.max_width
63    }
64
65    pub fn indent_size(&self) -> u32 {
66        self.indent_size
67    }
68}
69
70#[derive(Clone, Debug)]
71pub struct Formatter {
72    config: Config,
73    source_files: Vec<String>,
74    //pub errors: ReportedErrors,
75}
76
77impl Formatter {
78    pub fn new(config: Config, source_files: Vec<String>) -> Self {
79        Self {
80            config,
81            source_files,
82            //errors: ReportedErrors::default(),
83        }
84    }
85
86    pub fn config(&self) -> &Config {
87        &self.config
88    }
89
90    pub fn create_from_config(
91        config_path: Option<&str>,
92        source_files: Vec<String>,
93    ) -> Result<Formatter, String> {
94        let config = match config_path {
95            Some(path) => Config::from_file(path)
96                .map_err(|e| format!("{}: {}", yellow(&e.to_string()), path))?,
97            None => Config::default(),
98        };
99        Ok(Formatter::new(config, source_files))
100    }
101
102    pub fn format(&self) -> Vec<Result<String, String>> {
103        let (tx, rx) = mpsc::channel();
104        let config = self.config.clone();
105
106        for file in &self.source_files {
107            let tx = tx.clone();
108            let config = config.clone();
109            let file = file.clone();
110
111            thread::spawn(move || {
112                let result = std::panic::catch_unwind(|| {
113                    let source_code = fs::read_to_string(Path::new(&file))
114                        .map_err(|e| {
115                            format!(
116                                "Failed to read file: {} {}",
117                                red(&file),
118                                yellow(e.to_string().as_str())
119                            )
120                        })
121                        .unwrap();
122
123                    Formatter::format_one(&source_code, config)
124                });
125                match result {
126                    Ok(result) => {
127                        tx.send(Ok(result)).expect("failed to send result in tx");
128                    }
129                    Err(_) => tx
130                        .send(Err("Thread panicked".to_string()))
131                        .expect("failed to send error in tx"),
132                }
133            });
134        }
135
136        drop(tx);
137
138        rx.into_iter().collect()
139    }
140
141    pub fn format_one(source_code: &str, config: Config) -> String {
142        let ast_tree = Formatter::parse(source_code);
143        set_thread_source_code(source_code.to_string()); // important to set thread level source code now;
144
145        let mut cursor = ast_tree.walk();
146        let mut comment_map = CommentMap::new();
147        collect_comments(&mut cursor, &mut comment_map);
148        set_thread_comment_map(comment_map); // important to set thread level comment map;
149
150        // traverse the tree to build enriched data
151        let root: Root = enrich(&ast_tree);
152
153        // traverse enriched data and create pretty print combinators
154        let c = PrettyConfig::new(config.indent_size);
155        let b = DocBuilder::new(c);
156        let doc_ref = root.build(&b);
157
158        let result = pretty_print(doc_ref, config.max_width);
159
160        // debugging tool: use this to print named node value + comments in bucket
161        // print_comment_map(&ast_tree);
162
163        assert_no_missing_comments();
164
165        result
166    }
167
168    pub fn parse(source_code: &str) -> Tree {
169        let mut parser = Parser::new();
170        let language_fn = tree_sitter_sfapex::apex::LANGUAGE;
171        parser
172            .set_language(&language_fn.into())
173            .expect("Error loading Apex parser");
174
175        let ast_tree = parser.parse(source_code, None).unwrap();
176        let root_node = &ast_tree.root_node();
177
178        if root_node.has_error() {
179            if let Some(error_node) = Self::find_last_error_node(root_node) {
180                let error_snippet =
181                    truncate_snippet(&source_code[error_node.start_byte()..error_node.end_byte()]);
182                println!(
183                    "Error in node kind: {}, at byte range: {}-{}, snippet: {}",
184                    yellow(error_node.kind()),
185                    error_node.start_byte(),
186                    error_node.end_byte(),
187                    error_snippet,
188                );
189                if let Some(p) = error_node.parent() {
190                    let parent_snippet =
191                        truncate_snippet(&source_code[p.start_byte()..p.end_byte()]);
192                    println!(
193                        "Parent node kind: {}, at byte range: {}-{}, snippet: {}",
194                        yellow(p.kind()),
195                        p.start_byte(),
196                        p.end_byte(),
197                        parent_snippet,
198                    );
199                }
200            }
201            panic!("{}", red("Parser encounters an error node in the tree."));
202        }
203
204        ast_tree
205    }
206
207    fn find_last_error_node<'tree>(node: &Node<'tree>) -> Option<Node<'tree>> {
208        if !node.has_error() {
209            return None; // If the current node has no error, return None
210        }
211
212        let mut last_error_node = Some(*node);
213
214        for i in 0..node.child_count() {
215            if let Some(child) = node.child(i) {
216                if child.has_error() {
217                    last_error_node = Self::find_last_error_node(&child);
218                }
219            }
220        }
221
222        last_error_node // Return the last (deepest) error node
223    }
224}