1use std::fmt;
2
3use super::{
4 CompileSourceFileOptions, ParseError, SourceFlavor, frontends, parser, source_map::SourceMap,
5};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum FormatError {
9 Parse(ParseError),
10 UnsupportedFlavor(SourceFlavor),
11}
12
13impl fmt::Display for FormatError {
14 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
15 match self {
16 FormatError::Parse(err) => write!(f, "{err}"),
17 FormatError::UnsupportedFlavor(flavor) => {
18 write!(f, "formatting is unsupported for {flavor:?} source")
19 }
20 }
21 }
22}
23
24impl std::error::Error for FormatError {}
25
26pub fn format_source(source: &str) -> Result<String, FormatError> {
27 format_source_with_flavor(source, SourceFlavor::RustScript)
28}
29
30pub fn format_source_with_flavor(
31 source: &str,
32 flavor: SourceFlavor,
33) -> Result<String, FormatError> {
34 format_source_with_flavor_and_options(source, flavor, &CompileSourceFileOptions::default())
35}
36
37pub fn format_source_with_flavor_and_options(
38 source: &str,
39 flavor: SourceFlavor,
40 options: &CompileSourceFileOptions,
41) -> Result<String, FormatError> {
42 let Some(dialect) = frontends::parser_dialect_for_flavor(flavor, options) else {
43 return Err(FormatError::UnsupportedFlavor(flavor));
44 };
45
46 let mut source_map = SourceMap::new();
47 let source_id = source_map.add_source("<source>", source.to_string());
48 parser::format_source(source, dialect)
49 .map_err(|err| FormatError::Parse(err.with_line_span_from_source(&source_map, source_id)))
50}
51
52#[cfg(test)]
53mod tests {
54 use super::format_source_with_flavor;
55 use crate::compiler::SourceFlavor;
56
57 #[test]
58 fn keeps_tail_expression_addition_on_one_line() {
59 let input = "fn mix(seed) {\n v\n +\n seed\n}\n";
60 let formatted = format_source_with_flavor(input, SourceFlavor::RustScript)
61 .expect("formatting should succeed");
62
63 assert_eq!(formatted, "fn mix(seed) {\n v + seed\n}\n");
64 }
65
66 #[test]
67 fn adds_space_before_closure_literals_after_assignment() {
68 let input = "let base = 7;\nlet add =|value| value + base;\n";
69 let formatted = format_source_with_flavor(input, SourceFlavor::RustScript)
70 .expect("formatting should succeed");
71
72 assert_eq!(
73 formatted,
74 "let base = 7;\nlet add = |value| value + base;\n"
75 );
76 }
77
78 #[test]
79 fn adds_space_before_unary_bang_in_if_conditions() {
80 let input = concat!(
81 "use stdlib::rss::strings as string;\n\n",
82 "let total = if!string::non_empty(\"rustscript\") => {\n",
83 " 1\n",
84 "} else => {\n",
85 " 0\n",
86 "};\n"
87 );
88 let formatted = format_source_with_flavor(input, SourceFlavor::RustScript)
89 .expect("formatting should succeed");
90
91 assert_eq!(
92 formatted,
93 concat!(
94 "use stdlib::rss::strings as string;\n\n",
95 "let total = if !string::non_empty(\"rustscript\") => {\n",
96 " 1\n",
97 "} else => {\n",
98 " 0\n",
99 "};\n"
100 )
101 );
102 }
103
104 #[test]
105 fn formats_rust_style_for_in_loop_head_on_one_line() {
106 let input = "for i in 0..3{\nvalue=value+i;\n}\n";
107 let formatted = format_source_with_flavor(input, SourceFlavor::RustScript)
108 .expect("formatting should succeed");
109
110 assert_eq!(formatted, "for i in 0..3 {\n value = value + i;\n}\n");
111 }
112
113 #[test]
114 fn adds_space_before_array_literals_after_assignment() {
115 let input = "let a =[1, \"a\"];\n";
116 let formatted = format_source_with_flavor(input, SourceFlavor::RustScript)
117 .expect("formatting should succeed");
118
119 assert_eq!(formatted, "let a = [1, \"a\"];\n");
120 }
121
122 #[test]
123 fn adds_space_before_prefix_borrows_and_negation() {
124 let input = "let neg=-1;\nlet borrow=&mut value;\nconsume(a,&b,neg);\n";
125 let formatted = format_source_with_flavor(input, SourceFlavor::RustScript)
126 .expect("formatting should succeed");
127
128 assert_eq!(
129 formatted,
130 "let neg = -1;\nlet borrow = &mut value;\nconsume(a, &b, neg);\n"
131 );
132 }
133
134 #[test]
135 fn keeps_namespace_keyword_calls_tight_to_open_paren() {
136 let input = "let regex_ok = re::match (\"(?i)^rustscript$\", \"RUSTSCRIPT\");\n";
137 let formatted = format_source_with_flavor(input, SourceFlavor::RustScript)
138 .expect("formatting should succeed");
139
140 assert_eq!(
141 formatted,
142 "let regex_ok = re::match(\"(?i)^rustscript$\", \"RUSTSCRIPT\");\n"
143 );
144 }
145
146 #[test]
147 fn folds_long_comma_delimited_groups() {
148 let input = concat!(
149 "fn borrow_with_really_long_parameter_names(first_parameter, &second_parameter, third_parameter, fourth_parameter, fifth_parameter, sixth_parameter) {\n",
150 " call_with_really_long_parameter_names(first_parameter, &second_parameter, third_parameter, fourth_parameter, fifth_parameter, sixth_parameter)\n",
151 "}\n"
152 );
153 let formatted = format_source_with_flavor(input, SourceFlavor::RustScript)
154 .expect("formatting should succeed");
155
156 assert_eq!(
157 formatted,
158 concat!(
159 "fn borrow_with_really_long_parameter_names(\n",
160 " first_parameter,\n",
161 " &second_parameter,\n",
162 " third_parameter,\n",
163 " fourth_parameter,\n",
164 " fifth_parameter,\n",
165 " sixth_parameter\n",
166 ") {\n",
167 " call_with_really_long_parameter_names(\n",
168 " first_parameter,\n",
169 " &second_parameter,\n",
170 " third_parameter,\n",
171 " fourth_parameter,\n",
172 " fifth_parameter,\n",
173 " sixth_parameter\n",
174 " )\n",
175 "}\n"
176 )
177 );
178 }
179
180 #[test]
181 fn keeps_generic_type_params_and_turbofish_tight() {
182 let input = concat!(
183 "struct Box < T > {\n",
184 " value: T\n",
185 "}\n",
186 "fn wrap < T >(value: Box < T >) {\n",
187 " let decoded = json::decode:: < Box < T > >(payload);\n",
188 " decoded\n",
189 "}\n"
190 );
191 let formatted = format_source_with_flavor(input, SourceFlavor::RustScript)
192 .expect("formatting should succeed");
193
194 assert_eq!(
195 formatted,
196 concat!(
197 "struct Box<T> {\n",
198 " value: T\n",
199 "}\n",
200 "fn wrap<T>(value: Box<T>) {\n",
201 " let decoded = json::decode::<Box<T>>(payload);\n",
202 " decoded\n",
203 "}\n"
204 )
205 );
206 }
207
208 #[test]
209 fn keeps_nested_generic_type_annotations_tight() {
210 let input = concat!(
211 "struct LinkState < V > {\n",
212 " nodes: map < LruNode < V > >,\n",
213 " head: int,\n",
214 " tail: int\n",
215 "}\n",
216 "let cache: LruCacheState < FeedCacheItem > = new:: < FeedCacheItem >(3);\n",
217 "let relinked = append_existing_node:: < FeedCacheItem >(nodes, head, tail, id, node);\n"
218 );
219 let formatted = format_source_with_flavor(input, SourceFlavor::RustScript)
220 .expect("formatting should succeed");
221
222 assert_eq!(
223 formatted,
224 concat!(
225 "struct LinkState<V> {\n",
226 " nodes: map<LruNode<V>>,\n",
227 " head: int,\n",
228 " tail: int\n",
229 "}\n",
230 "let cache: LruCacheState<FeedCacheItem> = new::<FeedCacheItem>(3);\n",
231 "let relinked = append_existing_node::<FeedCacheItem>(nodes, head, tail, id, node);\n"
232 )
233 );
234 }
235
236 #[test]
237 fn keeps_multi_param_generic_fields_tight_inside_struct_bodies() {
238 let input = concat!(
239 "struct LruGetResult < K, V > {\n",
240 " cache: LruCacheState < K,\n",
241 " V >,\n",
242 " found: bool,\n",
243 " value: V\n",
244 "}\n"
245 );
246 let formatted = format_source_with_flavor(input, SourceFlavor::RustScript)
247 .expect("formatting should succeed");
248
249 assert_eq!(
250 formatted,
251 concat!(
252 "struct LruGetResult<K, V> {\n",
253 " cache: LruCacheState<K, V>,\n",
254 " found: bool,\n",
255 " value: V\n",
256 "}\n"
257 )
258 );
259 }
260
261 #[test]
262 fn keeps_multi_param_generic_calls_tight_inside_collection_literals() {
263 let input = concat!(
264 "let out = {\n",
265 " cache: make_cache:: < K,\n",
266 " V >(limit, size),\n",
267 " found: false,\n",
268 " value: null\n",
269 "};\n"
270 );
271 let formatted = format_source_with_flavor(input, SourceFlavor::RustScript)
272 .expect("formatting should succeed");
273
274 assert_eq!(
275 formatted,
276 concat!(
277 "let out = {\n",
278 " cache: make_cache::<K, V>(limit, size),\n",
279 " found: false,\n",
280 " value: null\n",
281 "};\n"
282 )
283 );
284 }
285
286 #[test]
287 fn keeps_space_before_grouped_expression_after_assignment() {
288 let input = "let mut next_node: LruNode<K, V> =(&next_nodes)[next_head];\n";
289 let formatted = format_source_with_flavor(input, SourceFlavor::RustScript)
290 .expect("formatting should succeed");
291
292 assert_eq!(
293 formatted,
294 "let mut next_node: LruNode<K, V> = (&next_nodes)[next_head];\n"
295 );
296 }
297}