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 }
76
77impl Formatter {
78 pub fn new(config: Config, source_files: Vec<String>) -> Self {
79 Self {
80 config,
81 source_files,
82 }
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()); 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); let root: Root = enrich(&ast_tree);
152
153 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 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; }
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 }
224}