1use std::fmt::Write;
2
3use crate::KclError;
4use crate::ModuleId;
5use crate::parsing::DeprecationKind;
6use crate::parsing::PIPE_OPERATOR;
7use crate::parsing::ast::types::Annotation;
8use crate::parsing::ast::types::ArrayExpression;
9use crate::parsing::ast::types::ArrayRangeExpression;
10use crate::parsing::ast::types::AscribedExpression;
11use crate::parsing::ast::types::Associativity;
12use crate::parsing::ast::types::BinaryExpression;
13use crate::parsing::ast::types::BinaryOperator;
14use crate::parsing::ast::types::BinaryPart;
15use crate::parsing::ast::types::Block;
16use crate::parsing::ast::types::BodyItem;
17use crate::parsing::ast::types::CallExpressionKw;
18use crate::parsing::ast::types::CommentStyle;
19use crate::parsing::ast::types::DefaultParamVal;
20use crate::parsing::ast::types::EnumDeclaration;
21use crate::parsing::ast::types::Expr;
22use crate::parsing::ast::types::FormatOptions;
23use crate::parsing::ast::types::FunctionExpression;
24use crate::parsing::ast::types::Identifier;
25use crate::parsing::ast::types::IfExpression;
26use crate::parsing::ast::types::ImportSelector;
27use crate::parsing::ast::types::ImportStatement;
28use crate::parsing::ast::types::ItemVisibility;
29use crate::parsing::ast::types::LabeledArg;
30use crate::parsing::ast::types::Literal;
31use crate::parsing::ast::types::LiteralValue;
32use crate::parsing::ast::types::MemberExpression;
33use crate::parsing::ast::types::Name;
34use crate::parsing::ast::types::Node;
35use crate::parsing::ast::types::NodeList;
36use crate::parsing::ast::types::NonCodeMeta;
37use crate::parsing::ast::types::NonCodeNode;
38use crate::parsing::ast::types::NonCodeValue;
39use crate::parsing::ast::types::NumericLiteral;
40use crate::parsing::ast::types::ObjectExpression;
41use crate::parsing::ast::types::Parameter;
42use crate::parsing::ast::types::PipeExpression;
43use crate::parsing::ast::types::Program;
44use crate::parsing::ast::types::SketchBlock;
45use crate::parsing::ast::types::SketchVar;
46use crate::parsing::ast::types::TagDeclarator;
47use crate::parsing::ast::types::TypeDeclaration;
48use crate::parsing::ast::types::TypeDeclarationDefinition;
49use crate::parsing::ast::types::UnaryExpression;
50use crate::parsing::ast::types::VariableDeclaration;
51use crate::parsing::ast::types::VariableKind;
52use crate::parsing::deprecation;
53
54#[allow(dead_code)]
55pub fn fmt(input: &str) -> Result<String, KclError> {
56 let program = crate::parsing::parse_str(input, ModuleId::default()).parse_errs_as_err()?;
57 Ok(program.ast.recast_top(&Default::default(), 0))
58}
59
60impl Program {
61 pub fn recast_top(&self, options: &FormatOptions, indentation_level: usize) -> String {
62 let mut buf = String::with_capacity(1024);
63 self.recast(&mut buf, options, indentation_level);
64 buf
65 }
66
67 pub fn recast(&self, buf: &mut String, options: &FormatOptions, indentation_level: usize) {
68 if let Some(sh) = self.shebang.as_ref() {
69 write!(buf, "{}\n\n", sh.inner.content).no_fail();
70 }
71
72 recast_body(
73 &self.body,
74 &self.non_code_meta,
75 &self.inner_attrs,
76 buf,
77 options,
78 indentation_level,
79 );
80 }
81}
82
83fn recast_body(
84 items: &[BodyItem],
85 non_code_meta: &NonCodeMeta,
86 inner_attrs: &NodeList<Annotation>,
87 buf: &mut String,
88 options: &FormatOptions,
89 indentation_level: usize,
90) {
91 let indentation = options.get_indentation(indentation_level);
92
93 let has_non_newline_start_node = non_code_meta
94 .start_nodes
95 .iter()
96 .any(|noncode| !matches!(noncode.value, NonCodeValue::NewLine));
97 if has_non_newline_start_node {
98 let mut pending_newline = false;
99 for start_node in &non_code_meta.start_nodes {
100 match start_node.value {
101 NonCodeValue::NewLine => pending_newline = true,
102 _ => {
103 if pending_newline {
104 if buf.ends_with('\n') {
106 buf.push('\n');
107 } else {
108 buf.push_str("\n\n");
109 }
110 pending_newline = false;
111 }
112 let noncode_recast = start_node.recast(options, indentation_level);
113 buf.push_str(&noncode_recast);
114 }
115 }
116 }
117 if pending_newline {
119 if buf.ends_with('\n') {
120 buf.push('\n');
121 } else {
122 buf.push_str("\n\n");
123 }
124 }
125 }
126
127 for attr in inner_attrs {
128 options.write_indentation(buf, indentation_level);
129 attr.recast(buf, options, indentation_level);
130 }
131 if !inner_attrs.is_empty() {
132 buf.push('\n');
133 }
134
135 let body_item_lines = items.iter().map(|body_item| {
136 let mut result = String::with_capacity(256);
137 for comment in body_item.get_comments() {
138 if !comment.is_empty() {
139 result.push_str(&indentation);
140 result.push_str(comment);
141 }
142 if comment.is_empty() && !result.ends_with("\n") {
143 result.push('\n');
144 }
145 if !result.ends_with("\n\n") && result != "\n" {
146 result.push('\n');
147 }
148 }
149 for attr in body_item.get_attrs() {
150 attr.recast(&mut result, options, indentation_level);
151 }
152 match body_item {
153 BodyItem::ImportStatement(stmt) => {
154 result.push_str(&stmt.recast(options, indentation_level));
155 }
156 BodyItem::ExpressionStatement(expression_statement) => {
157 let mut tmp_buf = String::new();
158 expression_statement
159 .expression
160 .recast(&mut tmp_buf, options, indentation_level, ExprContext::Other);
161 options.write_indentation(&mut result, indentation_level);
162 result.push_str(tmp_buf.trim_start());
163 }
164 BodyItem::VariableDeclaration(variable_declaration) => {
165 variable_declaration.recast(&mut result, options, indentation_level);
166 }
167 BodyItem::TypeDeclaration(ty_declaration) => ty_declaration.recast(&mut result, options, indentation_level),
168 BodyItem::ReturnStatement(return_statement) => {
169 write!(&mut result, "{indentation}return ").no_fail();
170 let mut tmp_buf = String::with_capacity(256);
171 return_statement
172 .argument
173 .recast(&mut tmp_buf, options, indentation_level, ExprContext::Other);
174 write!(&mut result, "{}", tmp_buf.trim_start()).no_fail();
175 }
176 };
177 result
178 });
179 for (index, recast_str) in body_item_lines.enumerate() {
180 write!(buf, "{recast_str}").no_fail();
181
182 let needs_line_break = !(index == items.len() - 1 && indentation_level == 0);
185
186 let custom_white_space_or_comment = non_code_meta.non_code_nodes.get(&index).map(|noncodes| {
187 noncodes.iter().enumerate().map(|(i, custom_white_space_or_comment)| {
188 let formatted = custom_white_space_or_comment.recast(options, indentation_level);
189 if i == 0 && !formatted.trim().is_empty() {
190 if let NonCodeValue::BlockComment { .. } = custom_white_space_or_comment.value {
191 format!("\n{formatted}")
192 } else {
193 formatted
194 }
195 } else {
196 formatted
197 }
198 })
199 });
200
201 if let Some(custom) = custom_white_space_or_comment {
202 for to_write in custom {
203 write!(buf, "{to_write}").no_fail();
204 }
205 } else if needs_line_break {
206 buf.push('\n')
207 }
208 }
209 trim_end(buf);
210
211 if options.insert_final_newline && !buf.is_empty() {
213 buf.push('\n');
214 }
215}
216
217impl NonCodeValue {
218 fn should_cause_array_newline(&self) -> bool {
219 match self {
220 Self::InlineComment { .. } => false,
221 Self::BlockComment { .. } | Self::NewLine => true,
222 }
223 }
224}
225
226impl Node<NonCodeNode> {
227 fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
228 let indentation = options.get_indentation(indentation_level);
229 match &self.value {
230 NonCodeValue::InlineComment {
231 value,
232 style: CommentStyle::Line,
233 } => format!(" // {value}\n"),
234 NonCodeValue::InlineComment {
235 value,
236 style: CommentStyle::Block,
237 } => format!(" /* {value} */"),
238 NonCodeValue::BlockComment { value, style } => match style {
239 CommentStyle::Block => format!("{indentation}/* {value} */"),
240 CommentStyle::Line => {
241 if value.trim().is_empty() {
242 format!("{indentation}//\n")
243 } else {
244 format!("{}// {}\n", indentation, value.trim())
245 }
246 }
247 },
248 NonCodeValue::NewLine => "\n\n".to_string(),
249 }
250 }
251}
252
253impl Node<Annotation> {
254 fn recast(&self, buf: &mut String, options: &FormatOptions, indentation_level: usize) {
255 let indentation = options.get_indentation(indentation_level);
256 let mut result = String::new();
257 for comment in &self.pre_comments {
258 if !comment.is_empty() {
259 result.push_str(&indentation);
260 result.push_str(comment);
261 }
262 if !result.ends_with("\n\n") && result != "\n" {
263 result.push('\n');
264 }
265 }
266 result.push('@');
267 if let Some(name) = &self.name {
268 result.push_str(&name.name);
269 }
270 if let Some(properties) = &self.properties {
271 result.push('(');
272 result.push_str(
273 &properties
274 .iter()
275 .map(|prop| {
276 let mut temp = format!("{} = ", prop.key.name);
277 prop.value
278 .recast(&mut temp, options, indentation_level + 1, ExprContext::Other);
279 temp.trim().to_owned()
280 })
281 .collect::<Vec<String>>()
282 .join(", "),
283 );
284 result.push(')');
285 result.push('\n');
286 }
287
288 buf.push_str(&result)
289 }
290}
291
292impl ImportStatement {
293 pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
294 let indentation = options.get_indentation(indentation_level);
295 let vis = if self.visibility == ItemVisibility::Export {
296 "export "
297 } else {
298 ""
299 };
300 let mut string = format!("{vis}{indentation}import ");
301 match &self.selector {
302 ImportSelector::List { items } => {
303 for (i, item) in items.iter().enumerate() {
304 if i > 0 {
305 string.push_str(", ");
306 }
307 string.push_str(&item.name.name);
308 if let Some(alias) = &item.alias {
309 if item.name.name != alias.name {
311 string.push_str(&format!(" as {}", alias.name));
312 }
313 }
314 }
315 string.push_str(" from ");
316 }
317 ImportSelector::Glob(_) => string.push_str("* from "),
318 ImportSelector::None { .. } => {}
319 }
320 string.push_str(&format!("\"{}\"", self.path));
321
322 if let ImportSelector::None { alias: Some(alias) } = &self.selector {
323 string.push_str(" as ");
324 string.push_str(&alias.name);
325 }
326 string
327 }
328}
329
330#[derive(Copy, Clone, Debug, Eq, PartialEq)]
331pub(crate) enum ExprContext {
332 Pipe,
333 PipeHead,
336 FnDecl,
337 PipeCallArg,
339 CallArg,
341 OperatorOperand {
343 in_pipe: bool,
344 },
345 Other,
346}
347
348impl ExprContext {
349 fn in_pipe(self) -> bool {
350 matches!(
351 self,
352 ExprContext::Pipe | ExprContext::PipeCallArg | ExprContext::OperatorOperand { in_pipe: true }
353 )
354 }
355
356 fn needs_leading_indent(self) -> bool {
357 !matches!(
358 self,
359 ExprContext::PipeHead
360 | ExprContext::CallArg
361 | ExprContext::PipeCallArg
362 | ExprContext::OperatorOperand { .. }
363 )
364 }
365
366 fn call_arg_context(self) -> ExprContext {
367 if self.in_pipe() {
368 ExprContext::PipeCallArg
369 } else {
370 ExprContext::CallArg
371 }
372 }
373}
374
375impl Expr {
376 pub(crate) fn recast(
377 &self,
378 buf: &mut String,
379 options: &FormatOptions,
380 indentation_level: usize,
381 mut ctxt: ExprContext,
382 ) {
383 let is_decl = matches!(ctxt, ExprContext::FnDecl);
384 if is_decl {
385 ctxt = ExprContext::Other;
389 }
390 match &self {
391 Expr::BinaryExpression(bin_exp) => bin_exp.recast(buf, options, indentation_level, ctxt),
392 Expr::ArrayExpression(array_exp) => array_exp.recast(buf, options, indentation_level, ctxt),
393 Expr::ArrayRangeExpression(range_exp) => range_exp.recast(buf, options, indentation_level, ctxt),
394 Expr::ObjectExpression(obj_exp) => obj_exp.recast(buf, options, indentation_level, ctxt),
395 Expr::MemberExpression(mem_exp) => mem_exp.recast(buf, options, indentation_level, ctxt),
396 Expr::Literal(literal) => {
397 literal.recast(buf);
398 }
399 Expr::FunctionExpression(func_exp) => {
400 if !is_decl {
401 buf.push_str("fn");
402 if let Some(name) = &func_exp.name {
403 buf.push(' ');
404 buf.push_str(&name.name);
405 }
406 }
407 func_exp.recast(buf, options, indentation_level);
408 }
409 Expr::CallExpressionKw(call_exp) => call_exp.recast(buf, options, indentation_level, ctxt),
410 Expr::Name(name) => {
411 let result = &name.inner.name.inner.name;
412 match deprecation(result, DeprecationKind::Const) {
413 Some(suggestion) => buf.push_str(suggestion),
414 None => {
415 for prefix in &name.path {
416 buf.push_str(&prefix.name);
417 buf.push(':');
418 buf.push(':');
419 }
420 buf.push_str(result);
421 }
422 }
423 }
424 Expr::TagDeclarator(tag) => tag.recast(buf),
425 Expr::PipeExpression(pipe_exp) => {
426 pipe_exp.recast(buf, options, indentation_level, !is_decl && ctxt.needs_leading_indent())
427 }
428 Expr::UnaryExpression(unary_exp) => unary_exp.recast(buf, options, indentation_level, ctxt),
429 Expr::IfExpression(e) => e.recast(buf, options, indentation_level, ctxt),
430 Expr::PipeSubstitution(_) => buf.push_str(crate::parsing::PIPE_SUBSTITUTION_OPERATOR),
431 Expr::LabelledExpression(e) => {
432 e.expr.recast(buf, options, indentation_level, ctxt);
433 buf.push_str(" as ");
434 buf.push_str(&e.label.name);
435 }
436 Expr::AscribedExpression(e) => e.recast(buf, options, indentation_level, ctxt),
437 Expr::SketchBlock(e) => e.recast(buf, options, indentation_level, ctxt),
438 Expr::SketchVar(e) => e.recast(buf),
439 Expr::None(_) => {
440 unimplemented!("there is no literal None, see https://github.com/KittyCAD/modeling-app/issues/1115")
441 }
442 }
443 }
444}
445
446impl AscribedExpression {
447 fn recast(&self, buf: &mut String, options: &FormatOptions, indentation_level: usize, ctxt: ExprContext) {
448 if matches!(
449 self.expr,
450 Expr::BinaryExpression(..) | Expr::PipeExpression(..) | Expr::UnaryExpression(..)
451 ) {
452 buf.push('(');
453 self.expr.recast(buf, options, indentation_level, ctxt);
454 buf.push(')');
455 } else {
456 self.expr.recast(buf, options, indentation_level, ctxt);
457 }
458 buf.push_str(": ");
459 write!(buf, "{}", self.ty).no_fail();
460 }
461}
462
463impl BinaryPart {
464 pub(crate) fn recast(
465 &self,
466 buf: &mut String,
467 options: &FormatOptions,
468 indentation_level: usize,
469 ctxt: ExprContext,
470 ) {
471 let operand_ctxt = match ctxt {
472 ExprContext::OperatorOperand { .. } => ctxt,
473 _ => ExprContext::Other,
474 };
475 match &self {
476 BinaryPart::Literal(literal) => {
477 literal.recast(buf);
478 }
479 BinaryPart::Name(name) => match deprecation(&name.inner.name.inner.name, DeprecationKind::Const) {
480 Some(suggestion) => write!(buf, "{suggestion}").no_fail(),
481 None => name.write_to(buf).no_fail(),
482 },
483 BinaryPart::BinaryExpression(binary_expression) => {
484 binary_expression.recast(buf, options, indentation_level, ctxt)
485 }
486 BinaryPart::CallExpressionKw(call_expression) => {
487 call_expression.recast(buf, options, indentation_level, operand_ctxt)
488 }
489 BinaryPart::UnaryExpression(unary_expression) => {
490 unary_expression.recast(buf, options, indentation_level, ctxt)
491 }
492 BinaryPart::MemberExpression(member_expression) => {
493 member_expression.recast(buf, options, indentation_level, ctxt)
494 }
495 BinaryPart::ArrayExpression(e) => e.recast(buf, options, indentation_level, ctxt),
496 BinaryPart::ArrayRangeExpression(e) => e.recast(buf, options, indentation_level, ctxt),
497 BinaryPart::ObjectExpression(e) => e.recast(buf, options, indentation_level, ctxt),
498 BinaryPart::IfExpression(e) => e.recast(buf, options, indentation_level, operand_ctxt),
499 BinaryPart::AscribedExpression(e) => e.recast(buf, options, indentation_level, operand_ctxt),
500 BinaryPart::SketchVar(e) => e.recast(buf),
501 }
502 }
503}
504
505impl CallExpressionKw {
506 fn recast(&self, buf: &mut String, options: &FormatOptions, indentation_level: usize, ctxt: ExprContext) {
507 recast_call(
508 &self.callee,
509 self.unlabeled.as_ref(),
510 &self.arguments,
511 &self.non_code_meta,
512 buf,
513 options,
514 indentation_level,
515 ctxt,
516 );
517 }
518}
519
520fn recast_args(
521 unlabeled: Option<&Expr>,
522 arguments: &[LabeledArg],
523 options: &FormatOptions,
524 indentation_level: usize,
525 ctxt: ExprContext,
526) -> Vec<String> {
527 let arg_ctxt = ctxt.call_arg_context();
528 let mut arg_list = if let Some(first_arg) = unlabeled {
529 let mut first = String::with_capacity(256);
530 first_arg.recast(&mut first, options, indentation_level, arg_ctxt);
531 vec![first.trim().to_owned()]
532 } else {
533 Vec::with_capacity(arguments.len())
534 };
535 arg_list.extend(arguments.iter().map(|arg| {
536 let mut buf = String::with_capacity(256);
537 arg.recast(&mut buf, options, indentation_level, arg_ctxt);
538 buf
539 }));
540 arg_list
541}
542
543#[allow(clippy::too_many_arguments)]
544fn recast_call(
545 callee: &Name,
546 unlabeled: Option<&Expr>,
547 arguments: &[LabeledArg],
548 non_code_meta: &NonCodeMeta,
549 buf: &mut String,
550 options: &FormatOptions,
551 indentation_level: usize,
552 ctxt: ExprContext,
553) {
554 let smart_indent_level = if ctxt.in_pipe() { 0 } else { indentation_level };
555 let name = callee;
556
557 if let Some(suggestion) = deprecation(&name.name.inner.name, DeprecationKind::Function) {
558 if ctxt.needs_leading_indent() {
559 options.write_indentation(buf, smart_indent_level);
560 }
561 return write!(buf, "{suggestion}").no_fail();
562 }
563
564 struct FormatItem {
567 text: String,
568 is_arg: bool,
569 }
570
571 let build_items = |arg_indent: usize| -> Vec<FormatItem> {
578 let arg_list = recast_args(unlabeled, arguments, options, arg_indent, ctxt);
579 let mut arg_iter = arg_list.into_iter();
580 let mut items = Vec::with_capacity(arguments.len() + non_code_meta.non_code_nodes_len() + 1);
581 if unlabeled.is_some()
582 && let Some(first_arg) = arg_iter.next()
583 {
584 items.push(FormatItem {
585 text: first_arg,
586 is_arg: true,
587 });
588 }
589 let num_items = arguments.len() + non_code_meta.non_code_nodes_len();
590 let num_slots = non_code_meta
591 .non_code_nodes
592 .keys()
593 .max()
594 .map_or(num_items, |max| num_items.max(max + 1));
595 let mut pending_block_comments = String::new();
598 for i in 0..num_slots {
599 if let Some(noncode) = non_code_meta.non_code_nodes.get(&i) {
600 for nc in noncode {
601 match &nc.value {
602 NonCodeValue::BlockComment {
603 style: CommentStyle::Block,
604 ..
605 }
606 | NonCodeValue::InlineComment {
607 style: CommentStyle::Block,
608 ..
609 } => {
610 pending_block_comments.push_str(nc.recast(options, 0).trim());
611 pending_block_comments.push(' ');
612 }
613 _ => {
614 let mut text = std::mem::take(&mut pending_block_comments);
617 text.push_str(nc.recast(options, 0).trim_end_matches('\n'));
618 items.push(FormatItem {
619 text: text.trim().to_owned(),
620 is_arg: false,
621 });
622 }
623 }
624 }
625 } else if let Some(arg) = arg_iter.next() {
626 let mut text = std::mem::take(&mut pending_block_comments);
627 text.push_str(&arg);
628 items.push(FormatItem { text, is_arg: true });
629 }
630 }
631 items.extend(arg_iter.map(|arg| FormatItem {
632 text: arg,
633 is_arg: true,
634 }));
635 if !pending_block_comments.is_empty() {
637 items.push(FormatItem {
638 text: pending_block_comments.trim_end().to_owned(),
639 is_arg: false,
640 });
641 }
642 items
643 };
644
645 let items = build_items(indentation_level);
646 let has_lots_of_args = items.iter().filter(|item| item.is_arg).count() >= 4;
647 let has_own_line_comment = items.iter().any(|item| !item.is_arg);
649 let some_arg_is_already_multiline = items.len() > 1 && items.iter().any(|item| item.text.contains('\n'));
650 let multiline = has_lots_of_args || some_arg_is_already_multiline || has_own_line_comment;
651 if multiline {
652 let next_indent = indentation_level + 1;
653 let inner_indentation = if ctxt.in_pipe() {
654 options.get_indentation_offset_pipe(next_indent)
655 } else {
656 options.get_indentation(next_indent)
657 };
658 let items = build_items(next_indent);
659 let end_indent = if ctxt.in_pipe() {
660 options.get_indentation_offset_pipe(indentation_level)
661 } else {
662 options.get_indentation(indentation_level)
663 };
664 if ctxt.needs_leading_indent() {
665 options.write_indentation(buf, smart_indent_level);
666 }
667 name.write_to(buf).no_fail();
668 buf.push('(');
669 buf.push('\n');
670 for item in items {
671 if item.is_arg {
672 writeln!(buf, "{inner_indentation}{},", item.text).no_fail();
673 } else if item.text.is_empty() {
674 buf.push('\n');
676 } else {
677 writeln!(buf, "{inner_indentation}{}", item.text).no_fail();
678 }
679 }
680 write!(buf, "{end_indent}").no_fail();
681 buf.push(')');
682 } else {
683 if ctxt.needs_leading_indent() {
684 options.write_indentation(buf, smart_indent_level);
685 }
686 name.write_to(buf).no_fail();
687 buf.push('(');
688 let args = items
689 .iter()
690 .map(|item| item.text.as_str())
691 .collect::<Vec<_>>()
692 .join(", ");
693 write!(buf, "{args}").no_fail();
694 buf.push(')');
695 }
696}
697
698impl LabeledArg {
699 fn recast(&self, buf: &mut String, options: &FormatOptions, indentation_level: usize, ctxt: ExprContext) {
700 if let Some(l) = &self.label {
701 buf.push_str(&l.name);
702 buf.push_str(" = ");
703 }
704 self.arg.recast(buf, options, indentation_level, ctxt);
705 }
706}
707
708impl VariableDeclaration {
709 pub fn recast(&self, buf: &mut String, options: &FormatOptions, indentation_level: usize) {
710 options.write_indentation(buf, indentation_level);
711 match self.visibility {
712 ItemVisibility::Default => {}
713 ItemVisibility::Export => buf.push_str("export "),
714 };
715
716 let (keyword, eq, ctxt) = match self.kind {
717 VariableKind::Fn => ("fn ", "", ExprContext::FnDecl),
718 VariableKind::Const => ("", " = ", ExprContext::Other),
719 };
720 buf.push_str(keyword);
721 buf.push_str(&self.declaration.id.name);
722 buf.push_str(eq);
723
724 let mut tmp_buf = String::new();
730 self.declaration
731 .init
732 .recast(&mut tmp_buf, options, indentation_level, ctxt);
733 buf.push_str(tmp_buf.trim_start());
734 }
735}
736
737impl TypeDeclaration {
738 pub fn recast(&self, buf: &mut String, options: &FormatOptions, indentation_level: usize) {
739 options.write_indentation(buf, indentation_level);
740 match self.visibility {
741 ItemVisibility::Default => {}
742 ItemVisibility::Export => buf.push_str("export "),
743 };
744 buf.push_str("type ");
745 buf.push_str(&self.name.name);
746
747 if let Some(args) = &self.args {
748 buf.push('(');
749 for (i, a) in args.iter().enumerate() {
750 buf.push_str(&a.name);
751 if i < args.len() - 1 {
752 buf.push_str(", ");
753 }
754 }
755 buf.push(')');
756 }
757 match &self.definition {
758 TypeDeclarationDefinition::Bare => {}
759 TypeDeclarationDefinition::Alias { ty } => {
760 buf.push_str(" = ");
761 write!(buf, "{ty}").no_fail();
762 }
763 TypeDeclarationDefinition::Enum(e) => e.recast(buf, options, indentation_level),
764 }
765 }
766}
767
768impl EnumDeclaration {
769 fn recast(&self, buf: &mut String, options: &FormatOptions, indentation_level: usize) {
770 let has_start_comment = self
775 .non_code_meta
776 .start_nodes
777 .iter()
778 .any(|noncode| !matches!(noncode.value, NonCodeValue::NewLine));
779
780 if self.variants.is_empty() && !has_start_comment {
783 buf.push_str(" { | }");
784 return;
785 }
786
787 let body_level = indentation_level + 1;
788 let body_indentation = options.get_indentation(body_level);
789 buf.push_str(" {\n");
790
791 if has_start_comment {
794 let mut pending_newline = false;
795 for start_node in &self.non_code_meta.start_nodes {
796 if matches!(start_node.value, NonCodeValue::NewLine) {
797 pending_newline = true;
798 continue;
799 }
800 if pending_newline {
801 buf.push('\n');
802 pending_newline = false;
803 }
804 buf.push_str(&start_node.recast(options, body_level));
805 if !buf.ends_with('\n') {
806 buf.push('\n');
807 }
808 }
809 if pending_newline {
810 buf.push('\n');
811 }
812 }
813
814 if self.variants.is_empty() {
815 buf.push_str(&body_indentation);
816 buf.push_str("|\n");
817 }
818
819 for (index, variant) in self.variants.iter().enumerate() {
820 let mut arm = String::new();
823 for comment in &variant.pre_comments {
824 if !comment.is_empty() {
825 arm.push_str(&body_indentation);
826 arm.push_str(comment);
827 }
828 if comment.is_empty() && !arm.ends_with('\n') {
829 arm.push('\n');
830 }
831 if !arm.ends_with("\n\n") && arm != "\n" {
832 arm.push('\n');
833 }
834 }
835 arm.push_str(&body_indentation);
836 arm.push_str("| ");
837 arm.push_str(&variant.name.name);
838 buf.push_str(&arm);
839
840 if let Some(noncodes) = self.non_code_meta.non_code_nodes.get(&index) {
843 for (i, noncode) in noncodes.iter().enumerate() {
844 let formatted = noncode.recast(options, body_level);
845 if i == 0
846 && !formatted.trim().is_empty()
847 && matches!(noncode.value, NonCodeValue::BlockComment { .. })
848 {
849 buf.push('\n');
850 }
851 buf.push_str(&formatted);
852 }
853 if !buf.ends_with('\n') {
854 buf.push('\n');
855 }
856 } else {
857 buf.push('\n');
858 }
859 }
860
861 options.write_indentation(buf, indentation_level);
862 buf.push('}');
863 }
864}
865
866fn write<W: std::fmt::Write>(f: &mut W, s: impl std::fmt::Display) {
867 f.write_fmt(format_args!("{s}"))
868 .expect("writing to a string should always succeed")
869}
870
871fn write_dbg<W: std::fmt::Write>(f: &mut W, s: impl std::fmt::Debug) {
872 f.write_fmt(format_args!("{s:?}"))
873 .expect("writing to a string should always succeed")
874}
875
876impl NumericLiteral {
877 fn recast(&self, buf: &mut String) {
878 if self.raw.contains('.') && self.value.fract() == 0.0 {
879 write_dbg(buf, self.value);
880 write(buf, self.suffix);
881 } else {
882 write(buf, &self.raw);
883 }
884 }
885}
886
887impl Literal {
888 fn recast(&self, buf: &mut String) {
889 match self.value {
890 LiteralValue::Number { value, suffix } => {
891 if self.raw.contains('.') && value.fract() == 0.0 {
892 write_dbg(buf, value);
893 write(buf, suffix);
894 } else {
895 write(buf, &self.raw);
896 }
897 }
898 LiteralValue::String(ref s) => {
899 if let Some(suggestion) = deprecation(s, DeprecationKind::String) {
900 return write!(buf, "{suggestion}").unwrap();
901 }
902 let quote = if self.raw.trim().starts_with('"') { '"' } else { '\'' };
903 write(buf, quote);
904 write(buf, s);
905 write(buf, quote);
906 }
907 LiteralValue::Bool(_) => {
908 write(buf, &self.raw);
909 }
910 }
911 }
912}
913
914impl TagDeclarator {
915 pub fn recast(&self, buf: &mut String) {
916 buf.push('$');
918 buf.push_str(&self.name);
919 }
920}
921
922impl ArrayExpression {
923 fn recast(&self, buf: &mut String, options: &FormatOptions, indentation_level: usize, ctxt: ExprContext) {
924 fn indent_multiline_item(item: &str, indent: &str) -> String {
925 if !item.contains('\n') {
926 return item.to_owned();
927 }
928 let mut out = String::with_capacity(item.len() + indent.len() * 2);
929 let mut first = true;
930 for segment in item.split_inclusive('\n') {
931 if first {
932 out.push_str(segment);
933 first = false;
934 continue;
935 }
936 out.push_str(indent);
937 out.push_str(segment);
938 }
939 out
940 }
941
942 let num_items = self.elements.len() + self.non_code_meta.non_code_nodes_len();
946 let mut elems = self.elements.iter();
947 let mut found_line_comment = false;
948 let mut format_items: Vec<_> = Vec::with_capacity(num_items);
949 for i in 0..num_items {
950 if let Some(noncode) = self.non_code_meta.non_code_nodes.get(&i) {
951 format_items.extend(noncode.iter().map(|nc| {
952 found_line_comment |= nc.value.should_cause_array_newline();
953 nc.recast(options, 0)
954 }));
955 } else {
956 let el = elems.next().unwrap();
957 let mut s = String::with_capacity(256);
958 el.recast(&mut s, options, 0, ExprContext::Other);
959 s.push_str(", ");
960 format_items.push(s);
961 }
962 }
963
964 if let Some(item) = format_items.last_mut()
966 && let Some(norm) = item.strip_suffix(", ")
967 {
968 *item = norm.to_owned();
969 }
970 let mut flat_recast = String::with_capacity(256);
971 flat_recast.push('[');
972 for fi in &format_items {
973 flat_recast.push_str(fi)
974 }
975 flat_recast.push(']');
976
977 let max_array_length = 40;
979 let multi_line = flat_recast.len() > max_array_length || found_line_comment;
980 if !multi_line {
981 buf.push_str(&flat_recast);
982 return;
983 }
984
985 buf.push_str("[\n");
987 let inner_indentation = if ctxt.in_pipe() {
988 options.get_indentation_offset_pipe(indentation_level + 1)
989 } else {
990 options.get_indentation(indentation_level + 1)
991 };
992 for format_item in format_items {
993 let item = if let Some(x) = format_item.strip_suffix(" ") {
994 x
995 } else {
996 &format_item
997 };
998 let item = indent_multiline_item(item, &inner_indentation);
999 buf.push_str(&inner_indentation);
1000 buf.push_str(&item);
1001 if !format_item.ends_with('\n') {
1002 buf.push('\n')
1003 }
1004 }
1005 let end_indent = if ctxt.in_pipe() {
1006 options.get_indentation_offset_pipe(indentation_level)
1007 } else {
1008 options.get_indentation(indentation_level)
1009 };
1010 buf.push_str(&end_indent);
1011 buf.push(']');
1012 }
1013}
1014
1015fn expr_is_trivial(expr: &Expr) -> bool {
1017 matches!(
1018 expr,
1019 Expr::Literal(_) | Expr::Name(_) | Expr::TagDeclarator(_) | Expr::PipeSubstitution(_) | Expr::None(_)
1020 )
1021}
1022
1023trait CannotActuallyFail {
1024 fn no_fail(self);
1025}
1026
1027impl CannotActuallyFail for std::fmt::Result {
1028 fn no_fail(self) {
1029 self.expect("writing to a string cannot fail, there's no IO happening")
1030 }
1031}
1032
1033impl ArrayRangeExpression {
1034 fn recast(&self, buf: &mut String, options: &FormatOptions, _: usize, _: ExprContext) {
1035 buf.push('[');
1036 self.start_element.recast(buf, options, 0, ExprContext::Other);
1037
1038 let range_op = if self.end_inclusive { ".." } else { "..<" };
1039 let no_spaces = expr_is_trivial(&self.start_element) && expr_is_trivial(&self.end_element);
1044 if no_spaces {
1045 write!(buf, "{range_op}").no_fail()
1046 } else {
1047 write!(buf, " {range_op} ").no_fail()
1048 }
1049 self.end_element.recast(buf, options, 0, ExprContext::Other);
1050 buf.push(']');
1051 }
1053}
1054
1055fn trim_end(buf: &mut String) {
1056 buf.truncate(buf.trim_end().len())
1057}
1058
1059impl ObjectExpression {
1060 fn recast(&self, buf: &mut String, options: &FormatOptions, indentation_level: usize, ctxt: ExprContext) {
1061 if self
1062 .non_code_meta
1063 .non_code_nodes
1064 .values()
1065 .any(|nc| nc.iter().any(|nc| nc.value.should_cause_array_newline()))
1066 {
1067 return self.recast_multi_line(buf, options, indentation_level, ctxt);
1068 }
1069 let mut flat_recast_buf = String::new();
1070 flat_recast_buf.push_str("{ ");
1071 for (i, prop) in self.properties.iter().enumerate() {
1072 let obj_key = &prop.key.name;
1073 write!(flat_recast_buf, "{obj_key} = ").no_fail();
1074 prop.value
1075 .recast(&mut flat_recast_buf, options, indentation_level, ctxt);
1076 if i < self.properties.len() - 1 {
1077 flat_recast_buf.push_str(", ");
1078 }
1079 }
1080 flat_recast_buf.push_str(" }");
1081 let max_array_length = 40;
1082 let needs_multiple_lines = flat_recast_buf.len() > max_array_length;
1083 if !needs_multiple_lines {
1084 buf.push_str(&flat_recast_buf);
1085 } else {
1086 self.recast_multi_line(buf, options, indentation_level, ctxt);
1087 }
1088 }
1089
1090 fn recast_multi_line(
1092 &self,
1093 buf: &mut String,
1094 options: &FormatOptions,
1095 indentation_level: usize,
1096 ctxt: ExprContext,
1097 ) {
1098 let inner_indentation = if ctxt.in_pipe() {
1099 options.get_indentation_offset_pipe(indentation_level + 1)
1100 } else {
1101 options.get_indentation(indentation_level + 1)
1102 };
1103 let num_items = self.properties.len() + self.non_code_meta.non_code_nodes_len();
1104 let mut props = self.properties.iter();
1105 let format_items: Vec<_> = (0..num_items)
1106 .flat_map(|i| {
1107 if let Some(noncode) = self.non_code_meta.non_code_nodes.get(&i) {
1108 noncode.iter().map(|nc| nc.recast(options, 0)).collect::<Vec<_>>()
1109 } else {
1110 let prop = props.next().unwrap();
1111 let comma = if i == num_items - 1 { "" } else { ",\n" };
1113 let mut s = String::new();
1114 prop.value.recast(&mut s, options, indentation_level + 1, ctxt);
1115 vec![format!("{} = {}{comma}", prop.key.name, s.trim())]
1117 }
1118 })
1119 .collect();
1120 let end_indent = if ctxt.in_pipe() {
1121 options.get_indentation_offset_pipe(indentation_level)
1122 } else {
1123 options.get_indentation(indentation_level)
1124 };
1125 write!(
1126 buf,
1127 "{{\n{inner_indentation}{}\n{end_indent}}}",
1128 format_items.join(&inner_indentation),
1129 )
1130 .no_fail();
1131 }
1132}
1133
1134impl MemberExpression {
1135 fn recast(&self, buf: &mut String, options: &FormatOptions, indentation_level: usize, ctxt: ExprContext) {
1136 self.object.recast(buf, options, indentation_level, ctxt);
1138 if self.computed {
1140 buf.push('[');
1141 self.property.recast(buf, options, indentation_level, ctxt);
1142 buf.push(']');
1143 } else {
1144 buf.push('.');
1145 self.property.recast(buf, options, indentation_level, ctxt);
1146 };
1147 }
1148}
1149
1150impl BinaryExpression {
1151 fn recast(&self, buf: &mut String, options: &FormatOptions, indentation_level: usize, ctxt: ExprContext) {
1152 let maybe_wrap_it = |a: String, doit: bool| -> String { if doit { format!("({a})") } else { a } };
1153
1154 let should_wrap_left = match &self.left {
1157 BinaryPart::BinaryExpression(bin_exp) => {
1158 self.precedence() > bin_exp.precedence()
1159 || ((self.precedence() == bin_exp.precedence())
1160 && (!(self.operator.associative() && self.operator == bin_exp.operator)
1161 && self.operator.associativity() == Associativity::Right))
1162 }
1163 _ => false,
1164 };
1165
1166 let should_wrap_right = match &self.right {
1167 BinaryPart::BinaryExpression(bin_exp) => {
1168 self.precedence() > bin_exp.precedence()
1169 || self.operator == BinaryOperator::Sub
1171 || self.operator == BinaryOperator::Div
1172 || ((self.precedence() == bin_exp.precedence())
1173 && (!(self.operator.associative() && self.operator == bin_exp.operator)
1174 && self.operator.associativity() == Associativity::Left))
1175 }
1176 _ => false,
1177 };
1178
1179 let operand_ctxt = ExprContext::OperatorOperand {
1180 in_pipe: ctxt.in_pipe(),
1181 };
1182 let mut left = String::new();
1183 self.left.recast(&mut left, options, indentation_level, operand_ctxt);
1184 let mut right = String::new();
1185 self.right.recast(&mut right, options, indentation_level, operand_ctxt);
1186 write!(
1187 buf,
1188 "{} {} {}",
1189 maybe_wrap_it(left, should_wrap_left),
1190 self.operator,
1191 maybe_wrap_it(right, should_wrap_right)
1192 )
1193 .no_fail();
1194 }
1195}
1196
1197impl UnaryExpression {
1198 fn recast(&self, buf: &mut String, options: &FormatOptions, indentation_level: usize, ctxt: ExprContext) {
1199 let operand_ctxt = ExprContext::OperatorOperand {
1200 in_pipe: ctxt.in_pipe(),
1201 };
1202 match self.argument {
1203 BinaryPart::Literal(_)
1204 | BinaryPart::Name(_)
1205 | BinaryPart::MemberExpression(_)
1206 | BinaryPart::ArrayExpression(_)
1207 | BinaryPart::ArrayRangeExpression(_)
1208 | BinaryPart::ObjectExpression(_)
1209 | BinaryPart::IfExpression(_)
1210 | BinaryPart::AscribedExpression(_)
1211 | BinaryPart::CallExpressionKw(_) => {
1212 write!(buf, "{}", self.operator).no_fail();
1213 self.argument.recast(buf, options, indentation_level, operand_ctxt)
1214 }
1215 BinaryPart::BinaryExpression(_) | BinaryPart::UnaryExpression(_) | BinaryPart::SketchVar(_) => {
1216 write!(buf, "{}", self.operator).no_fail();
1217 buf.push('(');
1218 self.argument.recast(buf, options, indentation_level, operand_ctxt);
1219 buf.push(')');
1220 }
1221 }
1222 }
1223}
1224
1225impl IfExpression {
1226 fn recast(&self, buf: &mut String, options: &FormatOptions, indentation_level: usize, ctxt: ExprContext) {
1227 let n = 2 + (self.else_ifs.len() * 2) + 3;
1230 let mut lines = Vec::with_capacity(n);
1231
1232 let cond = {
1233 let mut tmp_buf = String::new();
1234 self.cond.recast(&mut tmp_buf, options, indentation_level, ctxt);
1235 tmp_buf
1236 };
1237 lines.push((0, format!("if {cond} {{")));
1238 lines.push((1, {
1239 let mut tmp_buf = String::new();
1240 self.then_val.recast(&mut tmp_buf, options, indentation_level + 1);
1241 tmp_buf
1242 }));
1243 for else_if in &self.else_ifs {
1244 let cond = {
1245 let mut tmp_buf = String::new();
1246 else_if.cond.recast(&mut tmp_buf, options, indentation_level, ctxt);
1247 tmp_buf
1248 };
1249 lines.push((0, format!("}} else if {cond} {{")));
1250 lines.push((1, {
1251 let mut tmp_buf = String::new();
1252 else_if.then_val.recast(&mut tmp_buf, options, indentation_level + 1);
1253 tmp_buf
1254 }));
1255 }
1256 lines.push((0, "} else {".to_owned()));
1257 lines.push((1, {
1258 let mut tmp_buf = String::new();
1259 self.final_else.recast(&mut tmp_buf, options, indentation_level + 1);
1260 tmp_buf
1261 }));
1262 lines.push((0, "}".to_owned()));
1263 let out = lines
1264 .into_iter()
1265 .enumerate()
1266 .map(|(idx, (ind, line))| {
1267 let indentation = if idx == 0 && (ctxt.in_pipe() || matches!(ctxt, ExprContext::OperatorOperand { .. }))
1268 {
1269 String::new()
1270 } else {
1271 options.get_indentation(indentation_level + ind)
1272 };
1273 format!("{indentation}{}", line.trim())
1274 })
1275 .collect::<Vec<_>>()
1276 .join("\n");
1277 buf.push_str(&out);
1278 }
1279}
1280
1281impl Node<PipeExpression> {
1282 fn recast(&self, buf: &mut String, options: &FormatOptions, indentation_level: usize, preceding_indent: bool) {
1283 if preceding_indent {
1284 options.write_indentation(buf, indentation_level);
1285 }
1286 for (index, statement) in self.body.iter().enumerate() {
1287 let (statement_indentation, statement_ctxt) = if index == 0 {
1288 (indentation_level, ExprContext::PipeHead)
1289 } else {
1290 (indentation_level + 1, ExprContext::Pipe)
1291 };
1292 statement.recast(buf, options, statement_indentation, statement_ctxt);
1293 let non_code_meta = &self.non_code_meta;
1294 if let Some(non_code_meta_value) = non_code_meta.non_code_nodes.get(&index) {
1295 for val in non_code_meta_value {
1296 if let NonCodeValue::NewLine = val.value {
1297 buf.push('\n');
1298 continue;
1299 }
1300 let formatted = if val.end == self.end {
1302 val.recast(options, indentation_level)
1303 .trim_end_matches('\n')
1304 .to_string()
1305 } else {
1306 val.recast(options, indentation_level + 1)
1307 .trim_end_matches('\n')
1308 .to_string()
1309 };
1310 if let NonCodeValue::BlockComment { .. } = val.value
1311 && !buf.ends_with('\n')
1312 {
1313 buf.push('\n');
1314 }
1315 buf.push_str(&formatted);
1316 }
1317 }
1318
1319 if index != self.body.len() - 1 {
1320 buf.push('\n');
1321 options.write_indentation(buf, indentation_level + 1);
1322 buf.push_str(PIPE_OPERATOR);
1323 buf.push(' ');
1324 }
1325 }
1326 }
1327}
1328
1329impl FunctionExpression {
1330 pub fn recast(&self, buf: &mut String, options: &FormatOptions, indentation_level: usize) {
1331 let mut new_options = options.clone();
1333 new_options.insert_final_newline = false;
1334
1335 buf.push('(');
1336 for (i, param) in self.params.iter().enumerate() {
1337 param.recast(buf, options, indentation_level);
1338 if i < self.params.len() - 1 {
1339 buf.push_str(", ");
1340 }
1341 }
1342 buf.push(')');
1343 if let Some(return_type) = &self.return_type {
1344 write!(buf, ": {return_type}").no_fail();
1345 }
1346 writeln!(buf, " {{").no_fail();
1347 self.body.recast(buf, &new_options, indentation_level + 1);
1348 buf.push('\n');
1349 options.write_indentation(buf, indentation_level);
1350 buf.push('}');
1351 }
1352}
1353
1354impl Parameter {
1355 pub fn recast(&self, buf: &mut String, _options: &FormatOptions, _indentation_level: usize) {
1356 if !self.labeled {
1357 buf.push('@');
1358 }
1359 buf.push_str(&self.identifier.name);
1360 if self.default_value.is_some() {
1361 buf.push('?');
1362 };
1363 if let Some(ty) = &self.param_type {
1364 buf.push_str(": ");
1365 write!(buf, "{ty}").no_fail();
1366 }
1367 if let Some(DefaultParamVal::Literal(ref literal)) = self.default_value {
1368 buf.push_str(" = ");
1369 literal.recast(buf);
1370 };
1371 }
1372}
1373
1374impl SketchBlock {
1375 pub(crate) fn recast(
1376 &self,
1377 buf: &mut String,
1378 options: &FormatOptions,
1379 indentation_level: usize,
1380 ctxt: ExprContext,
1381 ) {
1382 let name = Name {
1383 name: Node {
1384 inner: Identifier {
1385 name: SketchBlock::CALLEE_NAME.to_owned(),
1386 digest: None,
1387 },
1388 start: Default::default(),
1389 end: Default::default(),
1390 module_id: Default::default(),
1391 node_path: None,
1392 outer_attrs: Default::default(),
1393 pre_comments: Default::default(),
1394 comment_start: Default::default(),
1395 },
1396 path: Vec::new(),
1397 abs_path: false,
1398 digest: None,
1399 };
1400 recast_call(
1401 &name,
1402 None,
1403 &self.arguments,
1404 &self.non_code_meta,
1405 buf,
1406 options,
1407 indentation_level,
1408 ctxt,
1409 );
1410
1411 let mut new_options = options.clone();
1413 new_options.insert_final_newline = false;
1414
1415 writeln!(buf, " {{").no_fail();
1416 self.body.recast(buf, &new_options, indentation_level + 1);
1417 buf.push('\n');
1418 options.write_indentation(buf, indentation_level);
1419 buf.push('}');
1420 }
1421}
1422
1423impl Block {
1424 pub fn recast(&self, buf: &mut String, options: &FormatOptions, indentation_level: usize) {
1425 recast_body(
1426 &self.items,
1427 &self.non_code_meta,
1428 &self.inner_attrs,
1429 buf,
1430 options,
1431 indentation_level,
1432 );
1433 }
1434}
1435
1436impl SketchVar {
1437 fn recast(&self, buf: &mut String) {
1438 if let Some(initial) = &self.initial {
1439 write!(buf, "var ").no_fail();
1440 initial.recast(buf);
1441 } else {
1442 write!(buf, "var").no_fail();
1443 }
1444 }
1445}
1446
1447#[cfg(not(target_arch = "wasm32"))]
1449#[async_recursion::async_recursion]
1450pub async fn walk_dir(dir: &std::path::PathBuf) -> Result<Vec<std::path::PathBuf>, anyhow::Error> {
1451 if !dir.is_dir() {
1453 anyhow::bail!("`{}` is not a directory", dir.display());
1454 }
1455
1456 let mut entries = tokio::fs::read_dir(dir).await?;
1457
1458 let mut files = Vec::new();
1459 while let Some(entry) = entries.next_entry().await? {
1460 let path = entry.path();
1461
1462 if path.is_dir() {
1463 files.extend(walk_dir(&path).await?);
1464 } else if path
1465 .extension()
1466 .is_some_and(|ext| crate::RELEVANT_FILE_EXTENSIONS.contains(&ext.to_string_lossy().to_lowercase()))
1467 {
1468 files.push(path);
1469 }
1470 }
1471
1472 Ok(files)
1473}
1474
1475#[cfg(not(target_arch = "wasm32"))]
1477pub async fn recast_dir(dir: &std::path::Path, options: &crate::FormatOptions) -> Result<(), anyhow::Error> {
1478 let files = walk_dir(&dir.to_path_buf()).await.map_err(|err| {
1479 crate::KclError::new_internal(crate::errors::KclErrorDetails::new(
1480 format!("Failed to walk directory `{}`: {:?}", dir.display(), err),
1481 vec![crate::SourceRange::default()],
1482 ))
1483 })?;
1484
1485 let futures = files
1486 .into_iter()
1487 .filter(|file| file.extension().is_some_and(|ext| ext == "kcl")) .map(|file| {
1490 let options = options.clone();
1491 tokio::spawn(async move {
1492 let contents = tokio::fs::read_to_string(&file)
1493 .await
1494 .map_err(|err| anyhow::anyhow!("Failed to read file `{}`: {:?}", file.display(), err))?;
1495 let (program, ces) = crate::Program::parse(&contents).map_err(|err| {
1496 let report = crate::Report {
1497 kcl_source: contents.to_string(),
1498 error: err,
1499 filename: file.to_string_lossy().to_string(),
1500 label: file.to_string_lossy().to_string(),
1501 };
1502 let report = miette::Report::new(report);
1503 anyhow::anyhow!("{:?}", report)
1504 })?;
1505 for ce in &ces {
1506 if ce.severity != crate::errors::Severity::Warning {
1507 let report = crate::Report {
1508 kcl_source: contents.to_string(),
1509 error: crate::KclError::new_semantic(ce.clone().into()),
1510 filename: file.to_string_lossy().to_string(),
1511 label: file.to_string_lossy().to_string(),
1512 };
1513 let report = miette::Report::new(report);
1514 anyhow::bail!("{:?}", report);
1515 }
1516 }
1517 let Some(program) = program else {
1518 anyhow::bail!("Failed to parse file `{}`", file.display());
1519 };
1520 let recast = program.recast_with_options(&options);
1521 tokio::fs::write(&file, recast)
1522 .await
1523 .map_err(|err| anyhow::anyhow!("Failed to write file `{}`: {:?}", file.display(), err))?;
1524
1525 Ok::<(), anyhow::Error>(())
1526 })
1527 })
1528 .collect::<Vec<_>>();
1529
1530 let results = futures::future::join_all(futures).await;
1532
1533 let mut errors = Vec::new();
1535 for result in results {
1536 if let Err(err) = result? {
1537 errors.push(err);
1538 }
1539 }
1540
1541 if !errors.is_empty() {
1542 anyhow::bail!("Failed to recast some files: {:?}", errors);
1543 }
1544
1545 Ok(())
1546}
1547
1548#[cfg(test)]
1549mod tests {
1550 use pretty_assertions::assert_eq;
1551
1552 use super::*;
1553 use crate::ModuleId;
1554 use crate::parsing::ast::types::FormatOptions;
1555
1556 #[test]
1557 fn test_recast_annotations_without_body_items() {
1558 let input = r#"@settings(defaultLengthUnit = in)
1559"#;
1560 let program = crate::parsing::top_level_parse(input).unwrap();
1561 let output = program.recast_top(&Default::default(), 0);
1562 assert_eq!(output, input);
1563 }
1564
1565 #[test]
1566 fn test_recast_annotations_in_function_body() {
1567 let input = r#"fn myFunc() {
1568 @meta(yes = true)
1569
1570 x = 2
1571}
1572"#;
1573 let program = crate::parsing::top_level_parse(input).unwrap();
1574 let output = program.recast_top(&Default::default(), 0);
1575 assert_eq!(output, input);
1576 }
1577
1578 #[test]
1579 fn test_recast_annotations_in_function_body_without_items() {
1580 let input = "\
1581fn myFunc() {
1582 @meta(yes = true)
1583}
1584";
1585 let program = crate::parsing::top_level_parse(input).unwrap();
1586 let output = program.recast_top(&Default::default(), 0);
1587 assert_eq!(output, input);
1588 }
1589
1590 #[test]
1591 fn recast_annotations_with_comments() {
1592 let input = r#"// Start comment
1593
1594// Comment on attr
1595@settings(defaultLengthUnit = in)
1596
1597// Comment on item
1598foo = 42
1599
1600// Comment on another item
1601@(impl = kcl)
1602bar = 0
1603"#;
1604 let program = crate::parsing::top_level_parse(input).unwrap();
1605 let output = program.recast_top(&Default::default(), 0);
1606 assert_eq!(output, input);
1607 }
1608
1609 #[test]
1610 fn recast_annotations_with_block_comment() {
1611 let input = r#"/* Start comment
1612
1613sdfsdfsdfs */
1614@settings(defaultLengthUnit = in)
1615
1616foo = 42
1617"#;
1618 let program = crate::parsing::top_level_parse(input).unwrap();
1619 let output = program.recast_top(&Default::default(), 0);
1620 assert_eq!(output, input);
1621 }
1622
1623 #[track_caller]
1626 fn assert_recast(input: &str, expected: &str) {
1627 let program = crate::parsing::top_level_parse(input).unwrap();
1628 let output = program.recast_top(&Default::default(), 0);
1629 assert_eq!(output, expected);
1630 let reparsed = crate::parsing::top_level_parse(&output).unwrap();
1631 assert_eq!(reparsed.recast_top(&Default::default(), 0), output);
1632 }
1633
1634 #[test]
1635 fn recast_enum_multi_line_is_stable() {
1636 let input = r#"@settings(experimentalFeatures = allow)
1637
1638type Color {
1639 | Red
1640 | Green
1641 | Blue
1642}
1643"#;
1644 assert_recast(input, input);
1645 }
1646
1647 #[test]
1648 fn recast_enum_expands_single_line() {
1649 let input = r#"@settings(experimentalFeatures = allow)
1650
1651type Color { | Red | Green | Blue }
1652"#;
1653 let expected = r#"@settings(experimentalFeatures = allow)
1654
1655type Color {
1656 | Red
1657 | Green
1658 | Blue
1659}
1660"#;
1661 assert_recast(input, expected);
1662 }
1663
1664 #[test]
1665 fn recast_enum_export() {
1666 let input = r#"@settings(experimentalFeatures = allow)
1667
1668export type Color {
1669 | Red
1670}
1671"#;
1672 assert_recast(input, input);
1673 }
1674
1675 #[test]
1676 fn recast_enum_no_variants() {
1677 let input = r#"@settings(experimentalFeatures = allow)
1678
1679type Empty { | }
1680"#;
1681 assert_recast(input, input);
1682 }
1683
1684 #[test]
1685 fn recast_enum_no_variants_collapses_blank_lines() {
1686 let input = r#"@settings(experimentalFeatures = allow)
1689
1690type Empty {
1691
1692 |
1693
1694}
1695"#;
1696 let expected = r#"@settings(experimentalFeatures = allow)
1697
1698type Empty { | }
1699"#;
1700 assert_recast(input, expected);
1701 }
1702
1703 #[test]
1704 fn recast_enum_no_variants_with_comments() {
1705 let input = r#"@settings(experimentalFeatures = allow)
1706
1707type Empty { /* a */ | /* b */ }
1708"#;
1709 let expected = r#"@settings(experimentalFeatures = allow)
1710
1711type Empty {
1712 /* a */
1713 /* b */
1714 |
1715}
1716"#;
1717 assert_recast(input, expected);
1718 }
1719
1720 #[test]
1721 fn recast_enum_with_comments() {
1722 let input = r#"@settings(experimentalFeatures = allow)
1723
1724type Color {
1725 // before red
1726 | Red // after red
1727 | /* inside green arm */ Green
1728
1729 | Blue
1730 // trailing
1731}
1732"#;
1733 let expected = r#"@settings(experimentalFeatures = allow)
1736
1737type Color {
1738 // before red
1739 | Red // after red
1740 /* inside green arm */
1741 | Green
1742
1743 | Blue
1744 // trailing
1745}
1746"#;
1747 assert_recast(input, expected);
1748 }
1749
1750 #[test]
1751 fn recast_enum_with_comment_above_declaration() {
1752 let input = r#"@settings(experimentalFeatures = allow)
1753
1754// palette
1755type Color {
1756 // before red
1757 | Red
1758}
1759"#;
1760 assert_recast(input, input);
1761 }
1762
1763 #[test]
1764 fn recast_enum_with_outer_annotation() {
1765 let input = r#"@settings(experimentalFeatures = allow)
1766
1767@(impl = kcl)
1768type Color {
1769 | Red
1770}
1771"#;
1772 assert_recast(input, input);
1773 }
1774
1775 #[test]
1776 fn recast_enum_export_annotation_and_comments() {
1777 let input = r#"@settings(experimentalFeatures = allow)
1778
1779// palette
1780@(impl = kcl)
1781export type Color {
1782 | Red // warm
1783}
1784"#;
1785 assert_recast(input, input);
1786 }
1787
1788 #[test]
1789 fn recast_enum_in_function_body() {
1790 let input = r#"@settings(experimentalFeatures = allow)
1791
1792fn palette() {
1793 type Color {
1794 | Red
1795 | Green
1796 }
1797 return 0
1798}
1799"#;
1800 assert_recast(input, input);
1801 }
1802
1803 #[test]
1804 fn test_recast_if_else_if_same() {
1805 let input = r#"b = if false {
1806 3
1807} else if true {
1808 4
1809} else {
1810 5
1811}
1812"#;
1813 let program = crate::parsing::top_level_parse(input).unwrap();
1814 let output = program.recast_top(&Default::default(), 0);
1815 assert_eq!(output, input);
1816 }
1817
1818 #[test]
1819 fn test_recast_if_same() {
1820 let input = r#"b = if false {
1821 3
1822} else {
1823 5
1824}
1825"#;
1826 let program = crate::parsing::top_level_parse(input).unwrap();
1827 let output = program.recast_top(&Default::default(), 0);
1828 assert_eq!(output, input);
1829 }
1830
1831 #[test]
1832 fn test_recast_import() {
1833 let input = r#"import a from "a.kcl"
1834import a as aaa from "a.kcl"
1835import a, b from "a.kcl"
1836import a as aaa, b from "a.kcl"
1837import a, b as bbb from "a.kcl"
1838import a as aaa, b as bbb from "a.kcl"
1839import "a_b.kcl"
1840import "a-b.kcl" as b
1841import * from "a.kcl"
1842export import a as aaa from "a.kcl"
1843export import a, b from "a.kcl"
1844export import a as aaa, b from "a.kcl"
1845export import a, b as bbb from "a.kcl"
1846"#;
1847 let program = crate::parsing::top_level_parse(input).unwrap();
1848 let output = program.recast_top(&Default::default(), 0);
1849 assert_eq!(output, input);
1850 }
1851
1852 #[test]
1853 fn test_recast_import_as_same_name() {
1854 let input = r#"import a as a from "a.kcl"
1855"#;
1856 let program = crate::parsing::top_level_parse(input).unwrap();
1857 let output = program.recast_top(&Default::default(), 0);
1858 let expected = r#"import a from "a.kcl"
1859"#;
1860 assert_eq!(output, expected);
1861 }
1862
1863 #[test]
1864 fn test_recast_export_fn() {
1865 let input = r#"export fn a() {
1866 return 0
1867}
1868"#;
1869 let program = crate::parsing::top_level_parse(input).unwrap();
1870 let output = program.recast_top(&Default::default(), 0);
1871 assert_eq!(output, input);
1872 }
1873
1874 #[test]
1875 fn test_recast_sketch_block_with_no_args() {
1876 let input = r#"sketch() {
1877 return 0
1878}
1879"#;
1880 let program = crate::parsing::top_level_parse(input).unwrap();
1881 let output = program.recast_top(&Default::default(), 0);
1882 assert_eq!(output, input);
1883 }
1884
1885 #[test]
1886 fn test_recast_sketch_block_with_labeled_args() {
1887 let input = r#"sketch(on = XY) {
1888 return 0
1889}
1890"#;
1891 let program = crate::parsing::top_level_parse(input).unwrap();
1892 let output = program.recast_top(&Default::default(), 0);
1893 assert_eq!(output, input);
1894 }
1895
1896 #[test]
1897 fn test_recast_sketch_block_with_arg_shorthand() {
1898 let input = r#"on = XY
1899sketch(on) {
1900 return 0
1901}
1902"#;
1903 let program = crate::parsing::top_level_parse(input).unwrap();
1904 let output = program.recast_top(&Default::default(), 0);
1905 assert_eq!(output, input);
1906 }
1907
1908 #[test]
1909 fn test_recast_sketch_block_with_arg_shorthand_and_comment() {
1910 let input = r#"on = XY
1914sketch(
1915 on,
1916 // plane
1917) {
1918 return 0
1919}
1920"#;
1921 let program = crate::parsing::top_level_parse(input).unwrap();
1922 let output = program.recast_top(&Default::default(), 0);
1923 assert_eq!(output, input);
1924
1925 let program = crate::parsing::top_level_parse(&output).unwrap();
1927 let output2 = program.recast_top(&Default::default(), 0);
1928 assert_eq!(output2, output);
1929 }
1930
1931 #[test]
1932 fn test_recast_sketch_block_with_statements_in_block() {
1933 let input = r#"sketch() {
1934 // Comments inside block.
1935 x = 5
1936 y = 2
1937}
1938"#;
1939 let program = crate::parsing::top_level_parse(input).unwrap();
1940 let output = program.recast_top(&Default::default(), 0);
1941 assert_eq!(output, input);
1942 }
1943
1944 #[test]
1945 fn test_recast_angle_dimension_constraint_indentation() {
1946 let input = r#"@settings(kclVersion = 2.0)
1947
1948sketch001 = sketch(on = XZ) {
1949 line1 = line(start = [var 0mm, var 127mm], end = [var 146.65mm, var 127mm])
1950 line2 = line(start = [var 146.65mm, var 127mm], end = [var 0mm, var -127mm])
1951 angleDimension(
1952 lines = [line1, line2],
1953 sector = 2,
1954 inverse = true,
1955 labelPosition = [137.6mm, 121.05mm],
1956) == 60deg
1957}
1958"#;
1959 let expected = r#"@settings(kclVersion = 2.0)
1960
1961sketch001 = sketch(on = XZ) {
1962 line1 = line(start = [var 0mm, var 127mm], end = [var 146.65mm, var 127mm])
1963 line2 = line(start = [var 146.65mm, var 127mm], end = [var 0mm, var -127mm])
1964 angleDimension(
1965 lines = [line1, line2],
1966 sector = 2,
1967 inverse = true,
1968 labelPosition = [137.6mm, 121.05mm],
1969 ) == 60deg
1970}
1971"#;
1972 assert_recast(input, expected);
1973 }
1974
1975 #[test]
1976 fn test_recast_bug_fn_in_fn() {
1977 let some_program_string = r#"// Start point (top left)
1978zoo_x = -20
1979zoo_y = 7
1980// Scale
1981s = 1 // s = 1 -> height of Z is 13.4mm
1982// Depth
1983d = 1
1984
1985fn rect(x, y, w, h) {
1986 startSketchOn(XY)
1987 |> startProfile(at = [x, y])
1988 |> xLine(length = w)
1989 |> yLine(length = h)
1990 |> xLine(length = -w)
1991 |> close()
1992 |> extrude(d)
1993}
1994
1995fn quad(x1, y1, x2, y2, x3, y3, x4, y4) {
1996 startSketchOn(XY)
1997 |> startProfile(at = [x1, y1])
1998 |> line(endAbsolute = [x2, y2])
1999 |> line(endAbsolute = [x3, y3])
2000 |> line(endAbsolute = [x4, y4])
2001 |> close()
2002 |> extrude(d)
2003}
2004
2005fn crosshair(x, y) {
2006 startSketchOn(XY)
2007 |> startProfile(at = [x, y])
2008 |> yLine(length = 1)
2009 |> yLine(length = -2)
2010 |> yLine(length = 1)
2011 |> xLine(length = 1)
2012 |> xLine(length = -2)
2013}
2014
2015fn z(z_x, z_y) {
2016 z_end_w = s * 8.4
2017 z_end_h = s * 3
2018 z_corner = s * 2
2019 z_w = z_end_w + 2 * z_corner
2020 z_h = z_w * 1.08130081300813
2021 rect(
2022 z_x,
2023 a = z_y,
2024 b = z_end_w,
2025 c = -z_end_h,
2026 )
2027 rect(
2028 z_x + z_w,
2029 a = z_y,
2030 b = -z_corner,
2031 c = -z_corner,
2032 )
2033 rect(
2034 z_x + z_w,
2035 a = z_y - z_h,
2036 b = -z_end_w,
2037 c = z_end_h,
2038 )
2039 rect(
2040 z_x,
2041 a = z_y - z_h,
2042 b = z_corner,
2043 c = z_corner,
2044 )
2045}
2046
2047fn o(c_x, c_y) {
2048 // Outer and inner radii
2049 o_r = s * 6.95
2050 i_r = 0.5652173913043478 * o_r
2051
2052 // Angle offset for diagonal break
2053 a = 7
2054
2055 // Start point for the top sketch
2056 o_x1 = c_x + o_r * cos((45 + a) / 360 * TAU)
2057 o_y1 = c_y + o_r * sin((45 + a) / 360 * TAU)
2058
2059 // Start point for the bottom sketch
2060 o_x2 = c_x + o_r * cos((225 + a) / 360 * TAU)
2061 o_y2 = c_y + o_r * sin((225 + a) / 360 * TAU)
2062
2063 // End point for the bottom startSketch
2064 o_x3 = c_x + o_r * cos((45 - a) / 360 * TAU)
2065 o_y3 = c_y + o_r * sin((45 - a) / 360 * TAU)
2066
2067 // Where is the center?
2068 // crosshair(c_x, c_y)
2069
2070
2071 startSketchOn(XY)
2072 |> startProfile(at = [o_x1, o_y1])
2073 |> arc(radius = o_r, angle_start = 45 + a, angle_end = 225 - a)
2074 |> angledLine(angle = 45, length = o_r - i_r)
2075 |> arc(radius = i_r, angle_start = 225 - a, angle_end = 45 + a)
2076 |> close()
2077 |> extrude(d)
2078
2079 startSketchOn(XY)
2080 |> startProfile(at = [o_x2, o_y2])
2081 |> arc(radius = o_r, angle_start = 225 + a, angle_end = 360 + 45 - a)
2082 |> angledLine(angle = 225, length = o_r - i_r)
2083 |> arc(radius = i_r, angle_start = 45 - a, angle_end = 225 + a - 360)
2084 |> close()
2085 |> extrude(d)
2086}
2087
2088fn zoo(x0, y0) {
2089 z(x = x0, y = y0)
2090 o(x = x0 + s * 20, y = y0 - (s * 6.7))
2091 o(x = x0 + s * 35, y = y0 - (s * 6.7))
2092}
2093
2094zoo(x = zoo_x, y = zoo_y)
2095"#;
2096 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2097
2098 let recasted = program.recast_top(&Default::default(), 0);
2099 assert_eq!(recasted, some_program_string);
2100 }
2101
2102 #[test]
2103 fn test_nested_fns_indent() {
2104 let some_program_string = "\
2105x = 1
2106fn rect(x, y, w, h) {
2107 y = 2
2108 z = 3
2109 startSketchOn(XY)
2110 |> startProfile(at = [x, y])
2111 |> xLine(length = w)
2112 |> yLine(length = h)
2113 |> xLine(length = -w)
2114 |> close()
2115 |> extrude(d)
2116}
2117";
2118 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2119
2120 let recasted = program.recast_top(&Default::default(), 0);
2121 assert_eq!(recasted, some_program_string);
2122 }
2123
2124 #[test]
2125 fn test_recast_bug_extra_parens() {
2126 let some_program_string = r#"// Ball Bearing
2127// A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads.
2128
2129// Define constants like ball diameter, inside diameter, overhange length, and thickness
2130sphereDia = 0.5
2131insideDia = 1
2132thickness = 0.25
2133overHangLength = .4
2134
2135// Sketch and revolve the inside bearing piece
2136insideRevolve = startSketchOn(XZ)
2137 |> startProfile(at = [insideDia / 2, 0])
2138 |> line(end = [0, thickness + sphereDia / 2])
2139 |> line(end = [overHangLength, 0])
2140 |> line(end = [0, -thickness])
2141 |> line(end = [-overHangLength + thickness, 0])
2142 |> line(end = [0, -sphereDia])
2143 |> line(end = [overHangLength - thickness, 0])
2144 |> line(end = [0, -thickness])
2145 |> line(end = [-overHangLength, 0])
2146 |> close()
2147 |> revolve(axis = Y)
2148
2149// Sketch and revolve one of the balls and duplicate it using a circular pattern. (This is currently a workaround, we have a bug with rotating on a sketch that touches the rotation axis)
2150sphere = startSketchOn(XZ)
2151 |> startProfile(at = [
2152 0.05 + insideDia / 2 + thickness,
2153 0 - 0.05
2154 ])
2155 |> line(end = [sphereDia - 0.1, 0])
2156 |> arc(
2157 angle_start = 0,
2158 angle_end = -180,
2159 radius = sphereDia / 2 - 0.05
2160 )
2161 |> close()
2162 |> revolve(axis = X)
2163 |> patternCircular3d(
2164 axis = [0, 0, 1],
2165 center = [0, 0, 0],
2166 repetitions = 10,
2167 arcDegrees = 360,
2168 rotateDuplicates = true
2169 )
2170
2171// Sketch and revolve the outside bearing
2172outsideRevolve = startSketchOn(XZ)
2173 |> startProfile(at = [
2174 insideDia / 2 + thickness + sphereDia,
2175 0
2176 ]
2177 )
2178 |> line(end = [0, sphereDia / 2])
2179 |> line(end = [-overHangLength + thickness, 0])
2180 |> line(end = [0, thickness])
2181 |> line(end = [overHangLength, 0])
2182 |> line(end = [0, -2 * thickness - sphereDia])
2183 |> line(end = [-overHangLength, 0])
2184 |> line(end = [0, thickness])
2185 |> line(end = [overHangLength - thickness, 0])
2186 |> close()
2187 |> revolve(axis = Y)"#;
2188 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2189
2190 let recasted = program.recast_top(&Default::default(), 0);
2191 assert_eq!(
2192 recasted,
2193 r#"// Ball Bearing
2194// A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads.
2195
2196// Define constants like ball diameter, inside diameter, overhange length, and thickness
2197sphereDia = 0.5
2198insideDia = 1
2199thickness = 0.25
2200overHangLength = .4
2201
2202// Sketch and revolve the inside bearing piece
2203insideRevolve = startSketchOn(XZ)
2204 |> startProfile(at = [insideDia / 2, 0])
2205 |> line(end = [0, thickness + sphereDia / 2])
2206 |> line(end = [overHangLength, 0])
2207 |> line(end = [0, -thickness])
2208 |> line(end = [-overHangLength + thickness, 0])
2209 |> line(end = [0, -sphereDia])
2210 |> line(end = [overHangLength - thickness, 0])
2211 |> line(end = [0, -thickness])
2212 |> line(end = [-overHangLength, 0])
2213 |> close()
2214 |> revolve(axis = Y)
2215
2216// Sketch and revolve one of the balls and duplicate it using a circular pattern. (This is currently a workaround, we have a bug with rotating on a sketch that touches the rotation axis)
2217sphere = startSketchOn(XZ)
2218 |> startProfile(at = [
2219 0.05 + insideDia / 2 + thickness,
2220 0 - 0.05
2221 ])
2222 |> line(end = [sphereDia - 0.1, 0])
2223 |> arc(angle_start = 0, angle_end = -180, radius = sphereDia / 2 - 0.05)
2224 |> close()
2225 |> revolve(axis = X)
2226 |> patternCircular3d(
2227 axis = [0, 0, 1],
2228 center = [0, 0, 0],
2229 repetitions = 10,
2230 arcDegrees = 360,
2231 rotateDuplicates = true,
2232 )
2233
2234// Sketch and revolve the outside bearing
2235outsideRevolve = startSketchOn(XZ)
2236 |> startProfile(at = [
2237 insideDia / 2 + thickness + sphereDia,
2238 0
2239 ])
2240 |> line(end = [0, sphereDia / 2])
2241 |> line(end = [-overHangLength + thickness, 0])
2242 |> line(end = [0, thickness])
2243 |> line(end = [overHangLength, 0])
2244 |> line(end = [0, -2 * thickness - sphereDia])
2245 |> line(end = [-overHangLength, 0])
2246 |> line(end = [0, thickness])
2247 |> line(end = [overHangLength - thickness, 0])
2248 |> close()
2249 |> revolve(axis = Y)
2250"#
2251 );
2252 }
2253
2254 #[test]
2255 fn test_recast_fn_in_object() {
2256 let some_program_string = r#"bing = { yo = 55 }
2257myNestedVar = [{ prop = callExp(bing.yo) }]
2258"#;
2259 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2260
2261 let recasted = program.recast_top(&Default::default(), 0);
2262 assert_eq!(recasted, some_program_string);
2263 }
2264
2265 #[test]
2266 fn test_recast_fn_in_array() {
2267 let some_program_string = r#"bing = { yo = 55 }
2268myNestedVar = [callExp(bing.yo)]
2269"#;
2270 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2271
2272 let recasted = program.recast_top(&Default::default(), 0);
2273 assert_eq!(recasted, some_program_string);
2274 }
2275
2276 #[test]
2277 fn test_recast_ranges() {
2278 let some_program_string = r#"foo = [0..10]
2279ten = 10
2280bar = [0 + 1 .. ten]
2281"#;
2282 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2283
2284 let recasted = program.recast_top(&Default::default(), 0);
2285 assert_eq!(recasted, some_program_string);
2286 }
2287
2288 #[test]
2289 fn test_recast_space_in_fn_call() {
2290 let some_program_string = r#"fn thing (x) {
2291 return x + 1
2292}
2293
2294thing ( 1 )
2295"#;
2296 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2297
2298 let recasted = program.recast_top(&Default::default(), 0);
2299 assert_eq!(
2300 recasted,
2301 r#"fn thing(x) {
2302 return x + 1
2303}
2304
2305thing(1)
2306"#
2307 );
2308 }
2309
2310 #[test]
2311 fn test_recast_typed_fn() {
2312 let some_program_string = r#"fn thing(x: string, y: [bool]): number {
2313 return x + 1
2314}
2315"#;
2316 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2317
2318 let recasted = program.recast_top(&Default::default(), 0);
2319 assert_eq!(recasted, some_program_string);
2320 }
2321
2322 #[test]
2323 fn test_recast_typed_consts() {
2324 let some_program_string = r#"a = 42: number
2325export b = 3.2: number(ft)
2326c = "dsfds": A | B | C
2327d = [1]: [number]
2328e = foo: [number; 3]
2329f = [1, 2, 3]: [number; 1+]
2330f = [1, 2, 3]: [number; 3+]
2331"#;
2332 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2333
2334 let recasted = program.recast_top(&Default::default(), 0);
2335 assert_eq!(recasted, some_program_string);
2336 }
2337
2338 #[test]
2339 fn test_recast_object_fn_in_array_weird_bracket() {
2340 let some_program_string = r#"bing = { yo = 55 }
2341myNestedVar = [
2342 {
2343 prop: line(a = [bing.yo, 21], b = sketch001)
2344}
2345]
2346"#;
2347 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2348
2349 let recasted = program.recast_top(&Default::default(), 0);
2350 let expected = r#"bing = { yo = 55 }
2351myNestedVar = [
2352 {
2353 prop = line(a = [bing.yo, 21], b = sketch001)
2354 }
2355]
2356"#;
2357 assert_eq!(recasted, expected,);
2358 }
2359
2360 #[test]
2361 fn test_recast_empty_file() {
2362 let some_program_string = r#""#;
2363 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2364
2365 let recasted = program.recast_top(&Default::default(), 0);
2366 assert_eq!(recasted, r#""#);
2368 }
2369
2370 #[test]
2371 fn test_recast_empty_file_new_line() {
2372 let some_program_string = r#"
2373"#;
2374 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2375
2376 let recasted = program.recast_top(&Default::default(), 0);
2377 assert_eq!(recasted, r#""#);
2379 }
2380
2381 #[test]
2382 fn test_recast_shebang() {
2383 let some_program_string = r#"#!/usr/local/env zoo kcl
2384part001 = startSketchOn(XY)
2385 |> startProfile(at = [-10, -10])
2386 |> line(end = [20, 0])
2387 |> line(end = [0, 20])
2388 |> line(end = [-20, 0])
2389 |> close()
2390"#;
2391
2392 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2393
2394 let recasted = program.recast_top(&Default::default(), 0);
2395 assert_eq!(
2396 recasted,
2397 r#"#!/usr/local/env zoo kcl
2398
2399part001 = startSketchOn(XY)
2400 |> startProfile(at = [-10, -10])
2401 |> line(end = [20, 0])
2402 |> line(end = [0, 20])
2403 |> line(end = [-20, 0])
2404 |> close()
2405"#
2406 );
2407 }
2408
2409 #[test]
2410 fn test_recast_shebang_new_lines() {
2411 let some_program_string = r#"#!/usr/local/env zoo kcl
2412
2413
2414
2415part001 = startSketchOn(XY)
2416 |> startProfile(at = [-10, -10])
2417 |> line(end = [20, 0])
2418 |> line(end = [0, 20])
2419 |> line(end = [-20, 0])
2420 |> close()
2421"#;
2422
2423 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2424
2425 let recasted = program.recast_top(&Default::default(), 0);
2426 assert_eq!(
2427 recasted,
2428 r#"#!/usr/local/env zoo kcl
2429
2430part001 = startSketchOn(XY)
2431 |> startProfile(at = [-10, -10])
2432 |> line(end = [20, 0])
2433 |> line(end = [0, 20])
2434 |> line(end = [-20, 0])
2435 |> close()
2436"#
2437 );
2438 }
2439
2440 #[test]
2441 fn test_recast_shebang_with_comments() {
2442 let some_program_string = r#"#!/usr/local/env zoo kcl
2443
2444// Yo yo my comments.
2445part001 = startSketchOn(XY)
2446 |> startProfile(at = [-10, -10])
2447 |> line(end = [20, 0])
2448 |> line(end = [0, 20])
2449 |> line(end = [-20, 0])
2450 |> close()
2451"#;
2452
2453 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2454
2455 let recasted = program.recast_top(&Default::default(), 0);
2456 assert_eq!(
2457 recasted,
2458 r#"#!/usr/local/env zoo kcl
2459
2460// Yo yo my comments.
2461part001 = startSketchOn(XY)
2462 |> startProfile(at = [-10, -10])
2463 |> line(end = [20, 0])
2464 |> line(end = [0, 20])
2465 |> line(end = [-20, 0])
2466 |> close()
2467"#
2468 );
2469 }
2470
2471 #[test]
2472 fn test_recast_empty_function_body_with_comments() {
2473 let input = r#"fn myFunc() {
2474 // Yo yo my comments.
2475}
2476"#;
2477
2478 let program = crate::parsing::top_level_parse(input).unwrap();
2479 let output = program.recast_top(&Default::default(), 0);
2480 assert_eq!(output, input);
2481 }
2482
2483 #[test]
2484 fn test_recast_large_file() {
2485 let some_program_string = r#"@settings(units=mm)
2486// define nts
2487radius = 6.0
2488width = 144.0
2489length = 83.0
2490depth = 45.0
2491thk = 5
2492hole_diam = 5
2493// define a rectangular shape func
2494fn rectShape(pos, w, l) {
2495 rr = startSketchOn(XY)
2496 |> startProfile(at = [pos[0] - (w / 2), pos[1] - (l / 2)])
2497 |> line(endAbsolute = [pos[0] + w / 2, pos[1] - (l / 2)], tag = $edge1)
2498 |> line(endAbsolute = [pos[0] + w / 2, pos[1] + l / 2], tag = $edge2)
2499 |> line(endAbsolute = [pos[0] - (w / 2), pos[1] + l / 2], tag = $edge3)
2500 |> close($edge4)
2501 return rr
2502}
2503// build the body of the focusrite scarlett solo gen 4
2504// only used for visualization
2505scarlett_body = rectShape(pos = [0, 0], w = width, l = length)
2506 |> extrude(depth)
2507 |> fillet(
2508 radius = radius,
2509 tags = [
2510 edge2,
2511 edge4,
2512 getOppositeEdge(edge2),
2513 getOppositeEdge(edge4)
2514]
2515 )
2516 // build the bracket sketch around the body
2517fn bracketSketch(w, d, t) {
2518 s = startSketchOn({
2519 plane = {
2520 origin = { x = 0, y = length / 2 + thk, z = 0 },
2521 x_axis = { x = 1, y = 0, z = 0 },
2522 y_axis = { x = 0, y = 0, z = 1 },
2523 z_axis = { x = 0, y = 1, z = 0 }
2524}
2525 })
2526 |> startProfile(at = [-w / 2 - t, d + t])
2527 |> line(endAbsolute = [-w / 2 - t, -t], tag = $edge1)
2528 |> line(endAbsolute = [w / 2 + t, -t], tag = $edge2)
2529 |> line(endAbsolute = [w / 2 + t, d + t], tag = $edge3)
2530 |> line(endAbsolute = [w / 2, d + t], tag = $edge4)
2531 |> line(endAbsolute = [w / 2, 0], tag = $edge5)
2532 |> line(endAbsolute = [-w / 2, 0], tag = $edge6)
2533 |> line(endAbsolute = [-w / 2, d + t], tag = $edge7)
2534 |> close($edge8)
2535 return s
2536}
2537// build the body of the bracket
2538bracket_body = bracketSketch(w = width, d = depth, t = thk)
2539 |> extrude(length + 10)
2540 |> fillet(
2541 radius = radius,
2542 tags = [
2543 getNextAdjacentEdge(edge7),
2544 getNextAdjacentEdge(edge2),
2545 getNextAdjacentEdge(edge3),
2546 getNextAdjacentEdge(edge6)
2547]
2548 )
2549 // build the tabs of the mounting bracket (right side)
2550tabs_r = startSketchOn({
2551 plane = {
2552 origin = { x = 0, y = 0, z = depth + thk },
2553 x_axis = { x = 1, y = 0, z = 0 },
2554 y_axis = { x = 0, y = 1, z = 0 },
2555 z_axis = { x = 0, y = 0, z = 1 }
2556}
2557 })
2558 |> startProfile(at = [width / 2 + thk, length / 2 + thk])
2559 |> line(end = [10, -5])
2560 |> line(end = [0, -10])
2561 |> line(end = [-10, -5])
2562 |> close()
2563 |> subtract2d(tool = circle(
2564 center = [
2565 width / 2 + thk + hole_diam,
2566 length / 2 - hole_diam
2567 ],
2568 radius = hole_diam / 2
2569 ))
2570 |> extrude(-thk)
2571 |> patternLinear3d(
2572 axis = [0, -1, 0],
2573 repetitions = 1,
2574 distance = length - 10
2575 )
2576 // build the tabs of the mounting bracket (left side)
2577tabs_l = startSketchOn({
2578 plane = {
2579 origin = { x = 0, y = 0, z = depth + thk },
2580 x_axis = { x = 1, y = 0, z = 0 },
2581 y_axis = { x = 0, y = 1, z = 0 },
2582 z_axis = { x = 0, y = 0, z = 1 }
2583}
2584 })
2585 |> startProfile(at = [-width / 2 - thk, length / 2 + thk])
2586 |> line(end = [-10, -5])
2587 |> line(end = [0, -10])
2588 |> line(end = [10, -5])
2589 |> close()
2590 |> subtract2d(tool = circle(
2591 center = [
2592 -width / 2 - thk - hole_diam,
2593 length / 2 - hole_diam
2594 ],
2595 radius = hole_diam / 2
2596 ))
2597 |> extrude(-thk)
2598 |> patternLinear3d(axis = [0, -1, 0], repetitions = 1, distance = length - 10ft)
2599"#;
2600 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2601
2602 let recasted = program.recast_top(&Default::default(), 0);
2603 assert_eq!(
2605 recasted,
2606 r#"@settings(units = mm)
2607
2608// define nts
2609radius = 6.0
2610width = 144.0
2611length = 83.0
2612depth = 45.0
2613thk = 5
2614hole_diam = 5
2615// define a rectangular shape func
2616fn rectShape(pos, w, l) {
2617 rr = startSketchOn(XY)
2618 |> startProfile(at = [pos[0] - (w / 2), pos[1] - (l / 2)])
2619 |> line(endAbsolute = [pos[0] + w / 2, pos[1] - (l / 2)], tag = $edge1)
2620 |> line(endAbsolute = [pos[0] + w / 2, pos[1] + l / 2], tag = $edge2)
2621 |> line(endAbsolute = [pos[0] - (w / 2), pos[1] + l / 2], tag = $edge3)
2622 |> close($edge4)
2623 return rr
2624}
2625// build the body of the focusrite scarlett solo gen 4
2626// only used for visualization
2627scarlett_body = rectShape(pos = [0, 0], w = width, l = length)
2628 |> extrude(depth)
2629 |> fillet(
2630 radius = radius,
2631 tags = [
2632 edge2,
2633 edge4,
2634 getOppositeEdge(edge2),
2635 getOppositeEdge(edge4)
2636 ],
2637 )
2638// build the bracket sketch around the body
2639fn bracketSketch(w, d, t) {
2640 s = startSketchOn({
2641 plane = {
2642 origin = { x = 0, y = length / 2 + thk, z = 0 },
2643 x_axis = { x = 1, y = 0, z = 0 },
2644 y_axis = { x = 0, y = 0, z = 1 },
2645 z_axis = { x = 0, y = 1, z = 0 }
2646 }
2647 })
2648 |> startProfile(at = [-w / 2 - t, d + t])
2649 |> line(endAbsolute = [-w / 2 - t, -t], tag = $edge1)
2650 |> line(endAbsolute = [w / 2 + t, -t], tag = $edge2)
2651 |> line(endAbsolute = [w / 2 + t, d + t], tag = $edge3)
2652 |> line(endAbsolute = [w / 2, d + t], tag = $edge4)
2653 |> line(endAbsolute = [w / 2, 0], tag = $edge5)
2654 |> line(endAbsolute = [-w / 2, 0], tag = $edge6)
2655 |> line(endAbsolute = [-w / 2, d + t], tag = $edge7)
2656 |> close($edge8)
2657 return s
2658}
2659// build the body of the bracket
2660bracket_body = bracketSketch(w = width, d = depth, t = thk)
2661 |> extrude(length + 10)
2662 |> fillet(
2663 radius = radius,
2664 tags = [
2665 getNextAdjacentEdge(edge7),
2666 getNextAdjacentEdge(edge2),
2667 getNextAdjacentEdge(edge3),
2668 getNextAdjacentEdge(edge6)
2669 ],
2670 )
2671// build the tabs of the mounting bracket (right side)
2672tabs_r = startSketchOn({
2673 plane = {
2674 origin = { x = 0, y = 0, z = depth + thk },
2675 x_axis = { x = 1, y = 0, z = 0 },
2676 y_axis = { x = 0, y = 1, z = 0 },
2677 z_axis = { x = 0, y = 0, z = 1 }
2678 }
2679})
2680 |> startProfile(at = [width / 2 + thk, length / 2 + thk])
2681 |> line(end = [10, -5])
2682 |> line(end = [0, -10])
2683 |> line(end = [-10, -5])
2684 |> close()
2685 |> subtract2d(tool = circle(
2686 center = [
2687 width / 2 + thk + hole_diam,
2688 length / 2 - hole_diam
2689 ],
2690 radius = hole_diam / 2,
2691 ))
2692 |> extrude(-thk)
2693 |> patternLinear3d(axis = [0, -1, 0], repetitions = 1, distance = length - 10)
2694// build the tabs of the mounting bracket (left side)
2695tabs_l = startSketchOn({
2696 plane = {
2697 origin = { x = 0, y = 0, z = depth + thk },
2698 x_axis = { x = 1, y = 0, z = 0 },
2699 y_axis = { x = 0, y = 1, z = 0 },
2700 z_axis = { x = 0, y = 0, z = 1 }
2701 }
2702})
2703 |> startProfile(at = [-width / 2 - thk, length / 2 + thk])
2704 |> line(end = [-10, -5])
2705 |> line(end = [0, -10])
2706 |> line(end = [10, -5])
2707 |> close()
2708 |> subtract2d(tool = circle(
2709 center = [
2710 -width / 2 - thk - hole_diam,
2711 length / 2 - hole_diam
2712 ],
2713 radius = hole_diam / 2,
2714 ))
2715 |> extrude(-thk)
2716 |> patternLinear3d(axis = [0, -1, 0], repetitions = 1, distance = length - 10ft)
2717"#
2718 );
2719 }
2720
2721 #[test]
2722 fn test_recast_nested_var_declaration_in_fn_body() {
2723 let some_program_string = r#"fn cube(pos, scale) {
2724 sg = startSketchOn(XY)
2725 |> startProfile(at = pos)
2726 |> line(end = [0, scale])
2727 |> line(end = [scale, 0])
2728 |> line(end = [0, -scale])
2729 |> close()
2730 |> extrude(scale)
2731}"#;
2732 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2733
2734 let recasted = program.recast_top(&Default::default(), 0);
2735 assert_eq!(
2736 recasted,
2737 r#"fn cube(pos, scale) {
2738 sg = startSketchOn(XY)
2739 |> startProfile(at = pos)
2740 |> line(end = [0, scale])
2741 |> line(end = [scale, 0])
2742 |> line(end = [0, -scale])
2743 |> close()
2744 |> extrude(scale)
2745}
2746"#
2747 );
2748 }
2749
2750 #[test]
2751 fn test_as() {
2752 let some_program_string = r#"fn cube(pos, scale) {
2753 x = dfsfs + dfsfsd as y
2754
2755 sg = startSketchOn(XY)
2756 |> startProfile(at = pos) as foo
2757 |> line([0, scale])
2758 |> line([scale, 0]) as bar
2759 |> line([0 as baz, -scale] as qux)
2760 |> close()
2761 |> extrude(length = scale)
2762}
2763
2764cube(pos = 0, scale = 0) as cub
2765"#;
2766 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2767
2768 let recasted = program.recast_top(&Default::default(), 0);
2769 assert_eq!(recasted, some_program_string,);
2770 }
2771
2772 #[test]
2773 fn test_recast_with_bad_indentation() {
2774 let some_program_string = r#"part001 = startSketchOn(XY)
2775 |> startProfile(at = [0.0, 5.0])
2776 |> line(end = [0.4900857016, -0.0240763666])
2777 |> line(end = [0.6804562304, 0.9087880491])"#;
2778 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2779
2780 let recasted = program.recast_top(&Default::default(), 0);
2781 assert_eq!(
2782 recasted,
2783 r#"part001 = startSketchOn(XY)
2784 |> startProfile(at = [0.0, 5.0])
2785 |> line(end = [0.4900857016, -0.0240763666])
2786 |> line(end = [0.6804562304, 0.9087880491])
2787"#
2788 );
2789 }
2790
2791 #[test]
2792 fn test_recast_with_bad_indentation_and_inline_comment() {
2793 let some_program_string = r#"part001 = startSketchOn(XY)
2794 |> startProfile(at = [0.0, 5.0])
2795 |> line(end = [0.4900857016, -0.0240763666]) // hello world
2796 |> line(end = [0.6804562304, 0.9087880491])"#;
2797 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2798
2799 let recasted = program.recast_top(&Default::default(), 0);
2800 assert_eq!(
2801 recasted,
2802 r#"part001 = startSketchOn(XY)
2803 |> startProfile(at = [0.0, 5.0])
2804 |> line(end = [0.4900857016, -0.0240763666]) // hello world
2805 |> line(end = [0.6804562304, 0.9087880491])
2806"#
2807 );
2808 }
2809 #[test]
2810 fn test_recast_with_bad_indentation_and_line_comment() {
2811 let some_program_string = r#"part001 = startSketchOn(XY)
2812 |> startProfile(at = [0.0, 5.0])
2813 |> line(end = [0.4900857016, -0.0240763666])
2814 // hello world
2815 |> line(end = [0.6804562304, 0.9087880491])"#;
2816 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2817
2818 let recasted = program.recast_top(&Default::default(), 0);
2819 assert_eq!(
2820 recasted,
2821 r#"part001 = startSketchOn(XY)
2822 |> startProfile(at = [0.0, 5.0])
2823 |> line(end = [0.4900857016, -0.0240763666])
2824 // hello world
2825 |> line(end = [0.6804562304, 0.9087880491])
2826"#
2827 );
2828 }
2829
2830 #[test]
2831 fn test_recast_comment_in_a_fn_block() {
2832 let some_program_string = r#"fn myFn() {
2833 // this is a comment
2834 yo = { a = { b = { c = '123' } } } /* block
2835 comment */
2836
2837 key = 'c'
2838 // this is also a comment
2839 return things
2840}"#;
2841 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2842
2843 let recasted = program.recast_top(&Default::default(), 0);
2844 assert_eq!(
2845 recasted,
2846 r#"fn myFn() {
2847 // this is a comment
2848 yo = { a = { b = { c = '123' } } } /* block
2849 comment */
2850
2851 key = 'c'
2852 // this is also a comment
2853 return things
2854}
2855"#
2856 );
2857 }
2858
2859 #[test]
2860 fn test_recast_comment_under_variable() {
2861 let some_program_string = r#"key = 'c'
2862// this is also a comment
2863thing = 'foo'
2864"#;
2865 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2866
2867 let recasted = program.recast_top(&Default::default(), 0);
2868 assert_eq!(
2869 recasted,
2870 r#"key = 'c'
2871// this is also a comment
2872thing = 'foo'
2873"#
2874 );
2875 }
2876
2877 #[test]
2878 fn test_recast_multiline_comment_start_file() {
2879 let some_program_string = r#"// hello world
2880// I am a comment
2881key = 'c'
2882// this is also a comment
2883// hello
2884thing = 'foo'
2885"#;
2886 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2887
2888 let recasted = program.recast_top(&Default::default(), 0);
2889 assert_eq!(
2890 recasted,
2891 r#"// hello world
2892// I am a comment
2893key = 'c'
2894// this is also a comment
2895// hello
2896thing = 'foo'
2897"#
2898 );
2899 }
2900
2901 #[test]
2902 fn test_recast_empty_comment() {
2903 let some_program_string = r#"// hello world
2904//
2905// I am a comment
2906key = 'c'
2907
2908//
2909// I am a comment
2910thing = 'c'
2911
2912foo = 'bar' //
2913"#;
2914 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2915
2916 let recasted = program.recast_top(&Default::default(), 0);
2917 assert_eq!(
2918 recasted,
2919 r#"// hello world
2920//
2921// I am a comment
2922key = 'c'
2923
2924//
2925// I am a comment
2926thing = 'c'
2927
2928foo = 'bar' //
2929"#
2930 );
2931 }
2932
2933 #[test]
2934 fn test_recast_multiline_comment_under_variable() {
2935 let some_program_string = r#"key = 'c'
2936// this is also a comment
2937// hello
2938thing = 'foo'
2939"#;
2940 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
2941
2942 let recasted = program.recast_top(&Default::default(), 0);
2943 assert_eq!(
2944 recasted,
2945 r#"key = 'c'
2946// this is also a comment
2947// hello
2948thing = 'foo'
2949"#
2950 );
2951 }
2952
2953 #[test]
2954 fn test_recast_only_line_comments() {
2955 let code = r#"// comment at start
2956"#;
2957 let program = crate::parsing::top_level_parse(code).unwrap();
2958
2959 assert_eq!(program.recast_top(&Default::default(), 0), code);
2960 }
2961
2962 #[test]
2963 fn test_recast_comment_at_start() {
2964 let test_program = r#"
2965/* comment at start */
2966
2967mySk1 = startSketchOn(XY)
2968 |> startProfile(at = [0, 0])"#;
2969 let program = crate::parsing::top_level_parse(test_program).unwrap();
2970
2971 let recasted = program.recast_top(&Default::default(), 0);
2972 assert_eq!(
2973 recasted,
2974 r#"/* comment at start */
2975
2976mySk1 = startSketchOn(XY)
2977 |> startProfile(at = [0, 0])
2978"#
2979 );
2980 }
2981
2982 #[test]
2983 fn test_recast_lots_of_comments() {
2984 let some_program_string = r#"// comment at start
2985mySk1 = startSketchOn(XY)
2986 |> startProfile(at = [0, 0])
2987 |> line(endAbsolute = [1, 1])
2988 // comment here
2989 |> line(endAbsolute = [0, 1], tag = $myTag)
2990 |> line(endAbsolute = [1, 1])
2991 /* and
2992 here
2993 */
2994 // a comment between pipe expression statements
2995 |> rx(90)
2996 // and another with just white space between others below
2997 |> ry(45)
2998 |> rx(45)
2999// one more for good measure"#;
3000 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
3001
3002 let recasted = program.recast_top(&Default::default(), 0);
3003 assert_eq!(
3004 recasted,
3005 r#"// comment at start
3006mySk1 = startSketchOn(XY)
3007 |> startProfile(at = [0, 0])
3008 |> line(endAbsolute = [1, 1])
3009 // comment here
3010 |> line(endAbsolute = [0, 1], tag = $myTag)
3011 |> line(endAbsolute = [1, 1])
3012 /* and
3013 here */
3014 // a comment between pipe expression statements
3015 |> rx(90)
3016 // and another with just white space between others below
3017 |> ry(45)
3018 |> rx(45)
3019// one more for good measure
3020"#
3021 );
3022 }
3023
3024 #[test]
3025 fn test_recast_multiline_object() {
3026 let some_program_string = r#"x = {
3027 a = 1000000000,
3028 b = 2000000000,
3029 c = 3000000000,
3030 d = 4000000000,
3031 e = 5000000000
3032}"#;
3033 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
3034
3035 let recasted = program.recast_top(&Default::default(), 0);
3036 assert_eq!(recasted.trim(), some_program_string);
3037 }
3038
3039 #[test]
3040 fn test_recast_first_level_object() {
3041 let some_program_string = r#"three = 3
3042
3043yo = {
3044 aStr = 'str',
3045 anum = 2,
3046 identifier = three,
3047 binExp = 4 + 5
3048}
3049yo = [
3050 1,
3051 " 2,",
3052 "three",
3053 4 + 5,
3054 " hey oooooo really long long long"
3055]
3056"#;
3057 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
3058
3059 let recasted = program.recast_top(&Default::default(), 0);
3060 assert_eq!(recasted, some_program_string);
3061 }
3062
3063 #[test]
3064 fn test_recast_new_line_before_comment() {
3065 let some_program_string = r#"
3066// this is a comment
3067yo = { a = { b = { c = '123' } } }
3068
3069key = 'c'
3070things = "things"
3071
3072// this is also a comment"#;
3073 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
3074
3075 let recasted = program.recast_top(&Default::default(), 0);
3076 let expected = some_program_string.trim();
3077 let actual = recasted.trim();
3079 assert_eq!(actual, expected);
3080 }
3081
3082 #[test]
3083 fn test_recast_comment_tokens_inside_strings() {
3084 let some_program_string = r#"b = {
3085 end = 141,
3086 start = 125,
3087 type_ = "NonCodeNode",
3088 value = "
3089 // a comment
3090 "
3091}"#;
3092 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
3093
3094 let recasted = program.recast_top(&Default::default(), 0);
3095 assert_eq!(recasted.trim(), some_program_string.trim());
3096 }
3097
3098 #[test]
3099 fn test_recast_array_new_line_in_pipe() {
3100 let some_program_string = r#"myVar = 3
3101myVar2 = 5
3102myVar3 = 6
3103myAng = 40
3104myAng2 = 134
3105part001 = startSketchOn(XY)
3106 |> startProfile(at = [0, 0])
3107 |> line(end = [1, 3.82], tag = $seg01) // ln-should-get-tag
3108 |> angledLine(angle = -foo(x = seg01, y = myVar, z = %), length = myVar) // ln-lineTo-xAbsolute should use angleToMatchLengthX helper
3109 |> angledLine(angle = -bar(x = seg01, y = myVar, z = %), length = myVar) // ln-lineTo-yAbsolute should use angleToMatchLengthY helper"#;
3110 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
3111
3112 let recasted = program.recast_top(&Default::default(), 0);
3113 assert_eq!(recasted.trim(), some_program_string);
3114 }
3115
3116 #[test]
3117 fn test_recast_array_new_line_in_pipe_custom() {
3118 let some_program_string = r#"myVar = 3
3119myVar2 = 5
3120myVar3 = 6
3121myAng = 40
3122myAng2 = 134
3123part001 = startSketchOn(XY)
3124 |> startProfile(at = [0, 0])
3125 |> line(end = [1, 3.82], tag = $seg01) // ln-should-get-tag
3126 |> angledLine(angle = -foo(x = seg01, y = myVar, z = %), length = myVar) // ln-lineTo-xAbsolute should use angleToMatchLengthX helper
3127 |> angledLine(angle = -bar(x = seg01, y = myVar, z = %), length = myVar) // ln-lineTo-yAbsolute should use angleToMatchLengthY helper
3128"#;
3129 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
3130
3131 let recasted = program.recast_top(
3132 &FormatOptions {
3133 tab_size: 3,
3134 use_tabs: false,
3135 insert_final_newline: true,
3136 },
3137 0,
3138 );
3139 assert_eq!(recasted, some_program_string);
3140 }
3141
3142 #[test]
3143 fn test_recast_after_rename_std() {
3144 let some_program_string = r#"part001 = startSketchOn(XY)
3145 |> startProfile(at = [0.0000000000, 5.0000000000])
3146 |> line(end = [0.4900857016, -0.0240763666])
3147
3148part002 = "part002"
3149things = [part001, 0.0]
3150blah = 1
3151foo = false
3152baz = {a: 1, part001: "thing"}
3153
3154fn ghi(part001) {
3155 return part001
3156}
3157"#;
3158 let mut program = crate::parsing::top_level_parse(some_program_string).unwrap();
3159 assert!(program.rename_symbol("mySuperCoolPart", 6));
3160
3161 let recasted = program.recast_top(&Default::default(), 0);
3162 assert_eq!(
3163 recasted,
3164 r#"mySuperCoolPart = startSketchOn(XY)
3165 |> startProfile(at = [0.0, 5.0])
3166 |> line(end = [0.4900857016, -0.0240763666])
3167
3168part002 = "part002"
3169things = [mySuperCoolPart, 0.0]
3170blah = 1
3171foo = false
3172baz = { a = 1, part001 = "thing" }
3173
3174fn ghi(part001) {
3175 return part001
3176}
3177"#
3178 );
3179 }
3180
3181 #[test]
3182 fn test_recast_after_rename_fn_args() {
3183 let some_program_string = r#"fn ghi(x, y, z) {
3184 return x
3185}"#;
3186 let mut program = crate::parsing::top_level_parse(some_program_string).unwrap();
3187 assert!(program.rename_symbol("newName", 7));
3188
3189 let recasted = program.recast_top(&Default::default(), 0);
3190 assert_eq!(
3191 recasted,
3192 r#"fn ghi(newName, y, z) {
3193 return newName
3194}
3195"#
3196 );
3197 }
3198
3199 #[test]
3200 fn test_recast_trailing_comma() {
3201 let some_program_string = r#"startSketchOn(XY)
3202 |> startProfile(at = [0, 0])
3203 |> arc({
3204 radius = 1,
3205 angle_start = 0,
3206 angle_end = 180,
3207 })"#;
3208 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
3209
3210 let recasted = program.recast_top(&Default::default(), 0);
3211 assert_eq!(
3212 recasted,
3213 r#"startSketchOn(XY)
3214 |> startProfile(at = [0, 0])
3215 |> arc({
3216 radius = 1,
3217 angle_start = 0,
3218 angle_end = 180
3219 })
3220"#
3221 );
3222 }
3223
3224 #[test]
3225 fn test_recast_array_no_trailing_comma_with_comments() {
3226 let some_program_string = r#"[
3227 1, // one
3228 2, // two
3229 3 // three
3230]"#;
3231 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
3232
3233 let recasted = program.recast_top(&Default::default(), 0);
3234 assert_eq!(
3235 recasted,
3236 r#"[
3237 1,
3238 // one
3239 2,
3240 // two
3241 3,
3242 // three
3243]
3244"#
3245 );
3246 }
3247
3248 #[test]
3249 fn test_recast_object_no_trailing_comma_with_comments() {
3250 let some_program_string = r#"{
3251 x=1, // one
3252 y=2, // two
3253 z=3 // three
3254}"#;
3255 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
3256
3257 let recasted = program.recast_top(&Default::default(), 0);
3258 assert_eq!(
3261 recasted,
3262 r#"{
3263 x = 1,
3264 // one
3265 y = 2,
3266 // two
3267 z = 3,
3268 // three
3269
3270}
3271"#
3272 );
3273 }
3274
3275 #[test]
3276 fn test_recast_comment_between_call_args() {
3277 let some_program_string = r#"rounded = fillet(
3278 body,
3279 radius = 1mm,
3280 // Keep this comment
3281 tags = [tag1, tag2],
3282)
3283"#;
3284 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
3285
3286 let recasted = program.recast_top(&Default::default(), 0);
3287 assert_eq!(recasted, some_program_string);
3288
3289 let program = crate::parsing::top_level_parse(&recasted).unwrap();
3291 let recasted2 = program.recast_top(&Default::default(), 0);
3292 assert_eq!(recasted2, recasted);
3293 }
3294
3295 #[test]
3296 fn test_recast_line_comments_in_call_args_force_multiline() {
3297 let some_program_string = r#"edged = chamfer(
3298 body,
3299 // leading
3300 length = 1mm,
3301 tags = [tag1],
3302 // trailing
3303)
3304"#;
3305 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
3306
3307 let recasted = program.recast_top(&Default::default(), 0);
3308 assert_eq!(recasted, some_program_string);
3309
3310 let program = crate::parsing::top_level_parse(&recasted).unwrap();
3312 let recasted2 = program.recast_top(&Default::default(), 0);
3313 assert_eq!(recasted2, recasted);
3314 }
3315
3316 #[test]
3317 fn test_recast_block_comment_in_call_args_stays_inline() {
3318 let some_program_string = r#"rounded = fillet(body, radius = 1mm, /* mid */ tags = [tag1])
3320"#;
3321 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
3322
3323 let recasted = program.recast_top(&Default::default(), 0);
3324 assert_eq!(recasted, some_program_string);
3325
3326 let program = crate::parsing::top_level_parse(&recasted).unwrap();
3328 let recasted2 = program.recast_top(&Default::default(), 0);
3329 assert_eq!(recasted2, recasted);
3330 }
3331
3332 #[test]
3333 fn test_recast_block_comment_in_multiline_call_args_stays_inline() {
3334 let some_program_string = r#"rounded = fillet(
3337 body,
3338 radius = 1mm,
3339 /* mid */ tags = [tag1],
3340 tag = $x,
3341)
3342"#;
3343 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
3344
3345 let recasted = program.recast_top(&Default::default(), 0);
3346 assert_eq!(recasted, some_program_string);
3347
3348 let program = crate::parsing::top_level_parse(&recasted).unwrap();
3350 let recasted2 = program.recast_top(&Default::default(), 0);
3351 assert_eq!(recasted2, recasted);
3352 }
3353
3354 #[test]
3355 fn test_recast_comment_in_call_args_in_pipe() {
3356 let some_program_string = r#"part = startSketchOn(XY)
3357 |> startProfile(at = [0, 0])
3358 |> fillet(
3359 radius = 1,
3360 // why
3361 tags = [a],
3362 )
3363"#;
3364 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
3365
3366 let recasted = program.recast_top(&Default::default(), 0);
3367 assert_eq!(recasted, some_program_string);
3368 }
3369
3370 #[test]
3371 fn test_recast_negative_var() {
3372 let some_program_string = r#"w = 20
3373l = 8
3374h = 10
3375
3376firstExtrude = startSketchOn(XY)
3377 |> startProfile(at = [0,0])
3378 |> line(end = [0, l])
3379 |> line(end = [w, 0])
3380 |> line(end = [0, -l])
3381 |> close()
3382 |> extrude(h)
3383"#;
3384 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
3385
3386 let recasted = program.recast_top(&Default::default(), 0);
3387 assert_eq!(
3388 recasted,
3389 r#"w = 20
3390l = 8
3391h = 10
3392
3393firstExtrude = startSketchOn(XY)
3394 |> startProfile(at = [0, 0])
3395 |> line(end = [0, l])
3396 |> line(end = [w, 0])
3397 |> line(end = [0, -l])
3398 |> close()
3399 |> extrude(h)
3400"#
3401 );
3402 }
3403
3404 #[test]
3405 fn test_recast_multiline_comment() {
3406 let some_program_string = r#"w = 20
3407l = 8
3408h = 10
3409
3410// This is my comment
3411// It has multiple lines
3412// And it's really long
3413firstExtrude = startSketchOn(XY)
3414 |> startProfile(at = [0,0])
3415 |> line(end = [0, l])
3416 |> line(end = [w, 0])
3417 |> line(end = [0, -l])
3418 |> close()
3419 |> extrude(h)
3420"#;
3421 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
3422
3423 let recasted = program.recast_top(&Default::default(), 0);
3424 assert_eq!(
3425 recasted,
3426 r#"w = 20
3427l = 8
3428h = 10
3429
3430// This is my comment
3431// It has multiple lines
3432// And it's really long
3433firstExtrude = startSketchOn(XY)
3434 |> startProfile(at = [0, 0])
3435 |> line(end = [0, l])
3436 |> line(end = [w, 0])
3437 |> line(end = [0, -l])
3438 |> close()
3439 |> extrude(h)
3440"#
3441 );
3442 }
3443
3444 #[test]
3445 fn test_recast_math_start_negative() {
3446 let some_program_string = r#"myVar = -5 + 6"#;
3447 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
3448
3449 let recasted = program.recast_top(&Default::default(), 0);
3450 assert_eq!(recasted.trim(), some_program_string);
3451 }
3452
3453 #[test]
3454 fn test_recast_math_negate_parens() {
3455 let some_program_string = r#"wallMountL = 3.82
3456thickness = 0.5
3457
3458startSketchOn(XY)
3459 |> startProfile(at = [0, 0])
3460 |> line(end = [0, -(wallMountL - thickness)])
3461 |> line(end = [0, -(5 - thickness)])
3462 |> line(end = [0, -(5 - 1)])
3463 |> line(end = [0, -(-5 - 1)])"#;
3464 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
3465
3466 let recasted = program.recast_top(&Default::default(), 0);
3467 assert_eq!(recasted.trim(), some_program_string);
3468 }
3469
3470 #[test]
3471 fn test_recast_math_nested_parens() {
3472 let some_program_string = r#"distance = 5
3473p = 3: Plane
3474FOS = { a = 3, b = 42 }: Sketch
3475sigmaAllow = 8: number(mm)
3476width = 20
3477thickness = sqrt(distance * p * FOS * 6 / (sigmaAllow * width))"#;
3478 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
3479
3480 let recasted = program.recast_top(&Default::default(), 0);
3481 assert_eq!(recasted.trim(), some_program_string);
3482 }
3483
3484 #[test]
3485 fn no_vardec_keyword() {
3486 let some_program_string = r#"distance = 5"#;
3487 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
3488
3489 let recasted = program.recast_top(&Default::default(), 0);
3490 assert_eq!(recasted.trim(), some_program_string);
3491 }
3492
3493 #[test]
3494 fn recast_types() {
3495 let some_program_string = r#"type foo
3496
3497// A comment
3498@(impl = primitive)
3499export type bar(unit, baz)
3500type baz = Foo | Bar
3501type UnionOfArrays = [Foo] | [Bar] | Foo | { a: T, b: Foo | Bar | [Baz] }
3502"#;
3503 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
3504 let recasted = program.recast_top(&Default::default(), 0);
3505 assert_eq!(recasted, some_program_string);
3506 }
3507
3508 #[test]
3509 fn recast_nested_fn() {
3510 let some_program_string = r#"fn f() {
3511 return fn() {
3512 return 1
3513}
3514}"#;
3515 let program = crate::parsing::top_level_parse(some_program_string).unwrap();
3516 let recasted = program.recast_top(&Default::default(), 0);
3517 let expected = "\
3518fn f() {
3519 return fn() {
3520 return 1
3521 }
3522}";
3523 assert_eq!(recasted.trim(), expected);
3524 }
3525
3526 #[test]
3527 fn recast_literal() {
3528 use winnow::Parser;
3529 for (i, (raw, expected, reason)) in [
3530 (
3531 "5.0",
3532 "5.0",
3533 "fractional numbers should stay fractional, i.e. don't reformat this to '5'",
3534 ),
3535 (
3536 "5",
3537 "5",
3538 "integers should stay integral, i.e. don't reformat this to '5.0'",
3539 ),
3540 (
3541 "5.0000000",
3542 "5.0",
3543 "if the number is f64 but not fractional, use its canonical format",
3544 ),
3545 ("5.1", "5.1", "straightforward case works"),
3546 ]
3547 .into_iter()
3548 .enumerate()
3549 {
3550 let tokens = crate::parsing::token::lex(raw, ModuleId::default()).unwrap();
3551 let literal = crate::parsing::parser::unsigned_number_literal
3552 .parse(tokens.as_slice())
3553 .unwrap();
3554 let mut actual = String::new();
3555 literal.recast(&mut actual);
3556 assert_eq!(actual, expected, "failed test {i}, which is testing that {reason}");
3557 }
3558 }
3559
3560 #[test]
3561 fn recast_objects_no_comments() {
3562 let input = r#"
3563sketch002 = startSketchOn({
3564 plane: {
3565 origin: { x = 1, y = 2, z = 3 },
3566 x_axis = { x = 4, y = 5, z = 6 },
3567 y_axis = { x = 7, y = 8, z = 9 },
3568 z_axis = { x = 10, y = 11, z = 12 }
3569 }
3570 })
3571"#;
3572 let expected = r#"sketch002 = startSketchOn({
3573 plane = {
3574 origin = { x = 1, y = 2, z = 3 },
3575 x_axis = { x = 4, y = 5, z = 6 },
3576 y_axis = { x = 7, y = 8, z = 9 },
3577 z_axis = { x = 10, y = 11, z = 12 }
3578 }
3579})
3580"#;
3581 let ast = crate::parsing::top_level_parse(input).unwrap();
3582 let actual = ast.recast_top(&FormatOptions::new(), 0);
3583 assert_eq!(actual, expected);
3584 }
3585
3586 #[test]
3587 fn unparse_fn_unnamed() {
3588 let input = "\
3589squares_out = reduce(
3590 arr,
3591 n = 0: number,
3592 f = fn(@i, accum) {
3593 return 1
3594 },
3595)
3596";
3597 let ast = crate::parsing::top_level_parse(input).unwrap();
3598 let actual = ast.recast_top(&FormatOptions::new(), 0);
3599 assert_eq!(actual, input);
3600 }
3601
3602 #[test]
3603 fn unparse_fn_named() {
3604 let input = r#"fn f(x) {
3605 return 1
3606}
3607"#;
3608 let ast = crate::parsing::top_level_parse(input).unwrap();
3609 let actual = ast.recast_top(&FormatOptions::new(), 0);
3610 assert_eq!(actual, input);
3611 }
3612
3613 #[test]
3614 fn unparse_call_inside_function_single_line() {
3615 let input = r#"fn foo() {
3616 toDegrees(atan(0.5), foo = 1)
3617 return 0
3618}
3619"#;
3620 let ast = crate::parsing::top_level_parse(input).unwrap();
3621 let actual = ast.recast_top(&FormatOptions::new(), 0);
3622 assert_eq!(actual, input);
3623 }
3624
3625 #[test]
3626 fn recast_function_types() {
3627 let input = r#"foo = x: fn
3628foo = x: fn(number)
3629fn foo(x: fn(): number): fn {
3630 return 0
3631}
3632fn foo(x: fn(a, b: number(mm), c: d): number(Angle)): fn {
3633 return 0
3634}
3635type fn
3636type foo = fn
3637type foo = fn(a: string, b: { f: fn(): any })
3638type foo = fn([fn])
3639type foo = fn(fn, f: fn(number(_))): [fn([any]): string]
3640"#;
3641 let ast = crate::parsing::top_level_parse(input).unwrap();
3642 let actual = ast.recast_top(&FormatOptions::new(), 0);
3643 assert_eq!(actual, input);
3644 }
3645
3646 #[test]
3647 fn unparse_call_inside_function_args_multiple_lines() {
3648 let input = r#"fn foo() {
3649 toDegrees(
3650 atan(0.5),
3651 foo = 1,
3652 bar = 2,
3653 baz = 3,
3654 qux = 4,
3655 )
3656 return 0
3657}
3658"#;
3659 let ast = crate::parsing::top_level_parse(input).unwrap();
3660 let actual = ast.recast_top(&FormatOptions::new(), 0);
3661 assert_eq!(actual, input);
3662 }
3663
3664 #[test]
3665 fn unparse_call_inside_function_single_arg_multiple_lines() {
3666 let input = r#"fn foo() {
3667 toDegrees(
3668 [
3669 profile0,
3670 profile1,
3671 profile2,
3672 profile3,
3673 profile4,
3674 profile5
3675 ],
3676 key = 1,
3677 )
3678 return 0
3679}
3680"#;
3681 let ast = crate::parsing::top_level_parse(input).unwrap();
3682 let actual = ast.recast_top(&FormatOptions::new(), 0);
3683 assert_eq!(actual, input);
3684 }
3685
3686 #[test]
3687 fn recast_objects_with_comments() {
3688 use winnow::Parser;
3689 for (i, (input, expected, reason)) in [(
3690 "\
3691{
3692 a = 1,
3693 // b = 2,
3694 c = 3
3695}",
3696 "\
3697{
3698 a = 1,
3699 // b = 2,
3700 c = 3
3701}",
3702 "preserves comments",
3703 )]
3704 .into_iter()
3705 .enumerate()
3706 {
3707 let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
3708 crate::parsing::parser::print_tokens(tokens.as_slice());
3709 let expr = crate::parsing::parser::object.parse(tokens.as_slice()).unwrap();
3710 let mut actual = String::new();
3711 expr.recast(&mut actual, &FormatOptions::new(), 0, ExprContext::Other);
3712 assert_eq!(
3713 actual, expected,
3714 "failed test {i}, which is testing that recasting {reason}"
3715 );
3716 }
3717 }
3718
3719 #[test]
3720 fn recast_array_with_comments() {
3721 use winnow::Parser;
3722 for (i, (input, expected, reason)) in [
3723 (
3724 "\
3725[
3726 1,
3727 2,
3728 3,
3729 4,
3730 5,
3731 6,
3732 7,
3733 8,
3734 9,
3735 10,
3736 11,
3737 12,
3738 13,
3739 14,
3740 15,
3741 16,
3742 17,
3743 18,
3744 19,
3745 20,
3746]",
3747 "\
3748[
3749 1,
3750 2,
3751 3,
3752 4,
3753 5,
3754 6,
3755 7,
3756 8,
3757 9,
3758 10,
3759 11,
3760 12,
3761 13,
3762 14,
3763 15,
3764 16,
3765 17,
3766 18,
3767 19,
3768 20
3769]",
3770 "preserves multi-line arrays",
3771 ),
3772 (
3773 "\
3774[
3775 1,
3776 // 2,
3777 3
3778]",
3779 "\
3780[
3781 1,
3782 // 2,
3783 3
3784]",
3785 "preserves comments",
3786 ),
3787 (
3788 "\
3789[
3790 1,
3791 2,
3792 // 3
3793]",
3794 "\
3795[
3796 1,
3797 2,
3798 // 3
3799]",
3800 "preserves comments at the end of the array",
3801 ),
3802 ]
3803 .into_iter()
3804 .enumerate()
3805 {
3806 let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
3807 let expr = crate::parsing::parser::array_elem_by_elem
3808 .parse(tokens.as_slice())
3809 .unwrap();
3810 let mut actual = String::new();
3811 expr.recast(&mut actual, &FormatOptions::new(), 0, ExprContext::Other);
3812 assert_eq!(
3813 actual, expected,
3814 "failed test {i}, which is testing that recasting {reason}"
3815 );
3816 }
3817 }
3818
3819 #[test]
3820 fn code_with_comment_and_extra_lines() {
3821 let code = r#"yo = 'c'
3822
3823/* this is
3824a
3825comment */
3826yo = 'bing'
3827"#;
3828 let ast = crate::parsing::top_level_parse(code).unwrap();
3829 let recasted = ast.recast_top(&FormatOptions::new(), 0);
3830 assert_eq!(recasted, code);
3831 }
3832
3833 #[test]
3834 fn comments_in_a_fn_block() {
3835 let code = r#"fn myFn() {
3836 // this is a comment
3837 yo = { a = { b = { c = '123' } } }
3838
3839 /* block
3840 comment */
3841 key = 'c'
3842 // this is also a comment
3843}
3844"#;
3845 let ast = crate::parsing::top_level_parse(code).unwrap();
3846 let recasted = ast.recast_top(&FormatOptions::new(), 0);
3847 assert_eq!(recasted, code);
3848 }
3849
3850 #[test]
3851 fn array_range_end_exclusive() {
3852 let code = "myArray = [0..<4]\n";
3853 let ast = crate::parsing::top_level_parse(code).unwrap();
3854 let recasted = ast.recast_top(&FormatOptions::new(), 0);
3855 assert_eq!(recasted, code);
3856 }
3857
3858 #[test]
3859 fn paren_precedence() {
3860 let code = r#"x = 1 - 2 - 3
3861x = (1 - 2) - 3
3862x = 1 - (2 - 3)
3863x = 1 + 2 + 3
3864x = (1 + 2) + 3
3865x = 1 + (2 + 3)
3866x = 2 * (y % 2)
3867x = (2 * y) % 2
3868x = 2 % (y * 2)
3869x = (2 % y) * 2
3870x = 2 * y % 2
3871"#;
3872
3873 let expected = r#"x = 1 - 2 - 3
3874x = 1 - 2 - 3
3875x = 1 - (2 - 3)
3876x = 1 + 2 + 3
3877x = 1 + 2 + 3
3878x = 1 + 2 + 3
3879x = 2 * (y % 2)
3880x = 2 * y % 2
3881x = 2 % (y * 2)
3882x = 2 % y * 2
3883x = 2 * y % 2
3884"#;
3885 let ast = crate::parsing::top_level_parse(code).unwrap();
3886 let recasted = ast.recast_top(&FormatOptions::new(), 0);
3887 assert_eq!(recasted, expected);
3888 }
3889
3890 #[test]
3891 fn gap_between_body_item_and_documented_fn() {
3892 let code = "\
3893x = 360
3894
3895// Watermelon
3896fn myFn() {
3897}
3898";
3899 let ast = crate::parsing::top_level_parse(code).unwrap();
3900 let recasted = ast.recast_top(&FormatOptions::new(), 0);
3901 let expected = code;
3902 assert_eq!(recasted, expected);
3903 }
3904
3905 #[test]
3906 fn simple_assignment_in_fn() {
3907 let code = "\
3908fn function001() {
3909 extrude002 = extrude()
3910}\n";
3911
3912 let ast = crate::parsing::top_level_parse(code).unwrap();
3913 let recasted = ast.recast_top(&FormatOptions::new(), 0);
3914 let expected = code;
3915 assert_eq!(recasted, expected);
3916 }
3917
3918 #[test]
3919 fn no_weird_extra_lines() {
3920 let code = "\
3923// Initial comment
3924
3925@settings(defaultLengthUnit = mm)
3926
3927x = 1
3928";
3929 let ast = crate::parsing::top_level_parse(code).unwrap();
3930 let recasted = ast.recast_top(&FormatOptions::new(), 0);
3931 let expected = code;
3932 assert_eq!(recasted, expected);
3933 }
3934
3935 #[test]
3936 fn settings_then_code_is_stable() {
3937 let code = "\
3938@settings(defaultLengthUnit = in)
3939
3940import \"cube-inches.kcl\" as cubeIn
3941import \"cube-mm.kcl\" as cubeMm
3942
3943cubeIn
3944cubeMm
3945";
3946 let formatted_once = crate::parsing::top_level_parse(code)
3947 .unwrap()
3948 .recast_top(&FormatOptions::new(), 0);
3949 assert_eq!(formatted_once, code);
3950
3951 let formatted_twice = crate::parsing::top_level_parse(&formatted_once)
3952 .unwrap()
3953 .recast_top(&FormatOptions::new(), 0);
3954 assert_eq!(formatted_twice, formatted_once);
3955 }
3956
3957 #[test]
3958 fn settings_then_standalone_comment_is_stable() {
3959 let code = "\
3960@settings(defaultLengthUnit = mm)
3961@settings(defaultAngleUnit = deg)
3962
3963// Cap for gimbal stick
3964
3965x = 1
3966";
3967 let formatted_once = crate::parsing::top_level_parse(code)
3968 .unwrap()
3969 .recast_top(&FormatOptions::new(), 0);
3970 assert_eq!(formatted_once, code);
3971
3972 let formatted_twice = crate::parsing::top_level_parse(&formatted_once)
3973 .unwrap()
3974 .recast_top(&FormatOptions::new(), 0);
3975 assert_eq!(formatted_twice, formatted_once);
3976 }
3977
3978 #[test]
3979 fn module_prefix() {
3980 let code = "x = std::sweep::SKETCH_PLANE\n";
3981 let ast = crate::parsing::top_level_parse(code).unwrap();
3982 let recasted = ast.recast_top(&FormatOptions::new(), 0);
3983 let expected = code;
3984 assert_eq!(recasted, expected);
3985 }
3986
3987 #[test]
3988 fn inline_ifs() {
3989 let code = "y = true
3990startSketchOn(XY)
3991 |> startProfile(at = [0, 0])
3992 |> if y {
3993 yLine(length = 1)
3994 } else {
3995 xLine(length = 1)
3996 }
3997";
3998 let ast = crate::parsing::top_level_parse(code).unwrap();
3999 let recasted = ast.recast_top(&FormatOptions::new(), 0);
4000 let expected = code;
4001 assert_eq!(recasted, expected);
4002 }
4003
4004 #[test]
4005 fn indented_binary_expressions() {
4006 let code = "\
4007fn foo() {
4008 1 == 2
4009}
4010";
4011 let ast = crate::parsing::top_level_parse(code).unwrap();
4012 let recasted = ast.recast_top(&FormatOptions::new(), 0);
4013 let expected = code;
4014 assert_eq!(recasted, expected);
4015 }
4016
4017 #[test]
4018 fn recast_binary_call_operands_across_operators_and_versions() {
4019 for version in ["1.0", "2.0", "\"3.0-preview\""] {
4020 for operator in ["+", "-", "*", "/", "%", "^", "==", "!=", ">", ">=", "<", "<=", "&", "|"] {
4021 let input = format!(
4022 "@settings(kclVersion = {version})\n\nfn example() {{\n x = f(a = 1, b = 2, c = 3, d = 4) {operator} g(a = 1, b = 2, c = 3, d = 4)\n}}\n"
4023 );
4024 let expected = format!(
4025 r#"@settings(kclVersion = {version})
4026
4027fn example() {{
4028 x = f(
4029 a = 1,
4030 b = 2,
4031 c = 3,
4032 d = 4,
4033 ) {operator} g(
4034 a = 1,
4035 b = 2,
4036 c = 3,
4037 d = 4,
4038 )
4039}}
4040"#
4041 );
4042 assert_recast(&input, &expected);
4043 }
4044 }
4045 }
4046
4047 #[test]
4048 fn recast_binary_operands_in_nested_contexts() {
4049 let code = r#"@settings(kclVersion = 2.0)
4050
4051fn example() {
4052 fn inner() {
4053 f(
4054 // argument
4055 a = 1,
4056 ) == 10
4057 x = outer(
4058 value = (f(
4059 a = 1,
4060 b = 2,
4061 c = 3,
4062 d = 4,
4063 ) + 10) * 20,
4064 other = [
4065 10 == f(
4066 a = 1,
4067 b = 2,
4068 c = 3,
4069 d = 4,
4070 )
4071 ],
4072 third = {
4073 value = f(
4074 a = 1,
4075 b = 2,
4076 c = 3,
4077 d = 4,
4078 ) == 10
4079 },
4080 )
4081 bounds = [base + 1 ..< end]
4082 return 10 == f(
4083 a = 1,
4084 b = 2,
4085 c = 3,
4086 d = 4,
4087 )
4088 }
4089}
4090"#;
4091 assert_recast(code, code);
4092 }
4093
4094 #[test]
4095 fn recast_binary_operands_with_member_and_type_wrappers() {
4096 let code = r#"@settings(kclVersion = 2.0)
4097
4098fn example() {
4099 member = f(
4100 // argument
4101 a = 1,
4102 ).result == 10
4103 ascribed = f(
4104 // argument
4105 a = 1,
4106 ): number == 10
4107 wrapped = (f(
4108 // argument
4109 a = 1,
4110 ) + 1): number == 10
4111}
4112"#;
4113 assert_recast(code, code);
4114 }
4115
4116 #[test]
4117 fn recast_deprecated_calls_as_binary_operands() {
4118 let input = "@settings(kclVersion = 2.0)\n\nfn example() {\n x = pi() + tau()\n y = f(a = pi() + 1)\n}\n";
4119 let expected = "@settings(kclVersion = 2.0)\n\nfn example() {\n x = PI + TAU\n y = f(a = PI + 1)\n}\n";
4120 assert_recast(input, expected);
4121 }
4122
4123 #[test]
4124 fn recast_binary_operands_in_conditionals_and_annotations() {
4125 let code = r#"@settings(kclVersion = 2.0)
4126
4127fn example() {
4128 @meta(value = f(
4129 // argument
4130 a = 1,
4131 ) == 10)
4132
4133 x = if f(
4134 // argument
4135 a = 1,
4136 ) == 10 {
4137 1
4138 } else {
4139 2
4140 } + if other {
4141 3
4142 } else {
4143 4
4144 }
4145}
4146"#;
4147 assert_recast(code, code);
4148 }
4149
4150 #[test]
4151 fn recast_binary_operands_in_pipelines() {
4152 let code = r#"@settings(kclVersion = 2.0)
4153
4154fn example() {
4155 x = f(
4156 a = 1,
4157 b = 2,
4158 c = 3,
4159 d = 4,
4160 ) == 10
4161 |> g(%)
4162 y = seed
4163 |> outer(
4164 value = 10 == f(
4165 a = 1,
4166 b = 2,
4167 c = 3,
4168 d = 4,
4169 ),
4170 other = 1,
4171 )
4172}
4173"#;
4174 assert_recast(code, code);
4175 }
4176
4177 #[test]
4178 fn recast_binary_operands_with_custom_indentation() {
4179 let input =
4180 "@settings(kclVersion = 2.0)\n\nfn example() {\n x = f(a = 1, b = 2, c = 3, d = 4) == g(a = 1)\n}\n";
4181 let program = crate::parsing::top_level_parse(input).unwrap();
4182 for (options, expected) in [
4183 (
4184 FormatOptions {
4185 tab_size: 4,
4186 ..Default::default()
4187 },
4188 "@settings(kclVersion = 2.0)\n\nfn example() {\n x = f(\n a = 1,\n b = 2,\n c = 3,\n d = 4,\n ) == g(a = 1)\n}\n",
4189 ),
4190 (
4191 FormatOptions {
4192 use_tabs: true,
4193 ..Default::default()
4194 },
4195 "@settings(kclVersion = 2.0)\n\nfn example() {\n\tx = f(\n\t\ta = 1,\n\t\tb = 2,\n\t\tc = 3,\n\t\td = 4,\n\t) == g(a = 1)\n}\n",
4196 ),
4197 ] {
4198 let output = program.recast_top(&options, 0);
4199 assert_eq!(output, expected);
4200 let reparsed = crate::parsing::top_level_parse(&output).unwrap();
4201 assert_eq!(reparsed.recast_top(&options, 0), output);
4202 }
4203 }
4204
4205 #[test]
4206 fn indented_assignment() {
4207 let code = "\
4208fn foo() {
4209 x = 1
4210}
4211";
4212 let ast = crate::parsing::top_level_parse(code).unwrap();
4213 let recasted = ast.recast_top(&FormatOptions::new(), 0);
4214 let expected = code;
4215 assert_eq!(recasted, expected);
4216 }
4217
4218 #[test]
4219 fn indented_unary_expression() {
4220 let code = "\
4221fn foo() {
4222 -x
4223}
4224";
4225 let ast = crate::parsing::top_level_parse(code).unwrap();
4226 let recasted = ast.recast_top(&FormatOptions::new(), 0);
4227 let expected = code;
4228 assert_eq!(recasted, expected);
4229 }
4230
4231 #[test]
4232 fn recast_unary_call_operands_across_operators_and_versions() {
4233 for version in ["1.0", "2.0", "\"3.0-preview\""] {
4234 for operator in ["-", "+", "!"] {
4235 let input = format!(
4236 "@settings(kclVersion = {version})\n\nfn example() {{\n x = {operator}f(a = 1, b = 2, c = 3, d = 4)\n y = {operator}g(a = 1)\n}}\n"
4237 );
4238 let expected = format!(
4239 r#"@settings(kclVersion = {version})
4240
4241fn example() {{
4242 x = {operator}f(
4243 a = 1,
4244 b = 2,
4245 c = 3,
4246 d = 4,
4247 )
4248 y = {operator}g(a = 1)
4249}}
4250"#
4251 );
4252 assert_recast(&input, &expected);
4253 }
4254 }
4255 }
4256
4257 #[test]
4258 fn recast_nested_unary_operands() {
4259 let code = r#"@settings(kclVersion = 2.0)
4260
4261fn example() {
4262 nested = -(-f(
4263 // This comment precedes the argument.
4264 a = 1,
4265 ))
4266 binary = -(f(
4267 // This comment precedes the argument.
4268 a = 1,
4269 ) + 2)
4270 member = -f(
4271 // This comment precedes the argument.
4272 a = 1,
4273 ).result
4274 ascribed = (-f(
4275 // This comment precedes the argument.
4276 a = 1,
4277 )): number
4278 constant = -PI
4279}
4280"#;
4281 assert_recast(code, code);
4282 assert_recast(
4283 "@settings(kclVersion = 2.0)\n\nfn example() {\n x = -pi()\n}\n",
4284 "@settings(kclVersion = 2.0)\n\nfn example() {\n x = -PI\n}\n",
4285 );
4286 }
4287
4288 #[test]
4289 fn recast_unary_operands_in_nested_contexts() {
4290 let code = r#"@settings(kclVersion = 2.0)
4291
4292fn example() {
4293 fn inner() {
4294 -f(
4295 // This comment precedes the argument.
4296 a = 1,
4297 )
4298 x = outer(
4299 value = 10 + -f(
4300 // This comment precedes the argument.
4301 a = 1,
4302 ),
4303 other = [
4304 -f(
4305 // This comment precedes the argument.
4306 a = 1,
4307 )
4308 ],
4309 third = {
4310 value = -f(
4311 // This comment precedes the argument.
4312 a = 1,
4313 )
4314 },
4315 )
4316 return -f(
4317 // This comment precedes the argument.
4318 a = 1,
4319 )
4320 }
4321}
4322"#;
4323 assert_recast(code, code);
4324 }
4325
4326 #[test]
4327 fn recast_unary_operands_in_conditionals() {
4328 let code = r#"@settings(kclVersion = 2.0)
4329
4330fn example() {
4331 x = -if !f(
4332 // This comment precedes the argument.
4333 a = 1,
4334 ) {
4335 1
4336 } else {
4337 2
4338 }
4339}
4340"#;
4341 assert_recast(code, code);
4342 }
4343
4344 #[test]
4345 fn recast_unary_operands_in_pipelines() {
4346 let code = r#"@settings(kclVersion = 2.0)
4347
4348fn example() {
4349 x = -f(
4350 // This comment precedes the argument.
4351 a = 1,
4352 )
4353 |> g(%)
4354 y = seed
4355 |> outer(
4356 value = -f(
4357 // This comment precedes the argument.
4358 a = 1,
4359 ),
4360 other = 1,
4361 )
4362}
4363"#;
4364 assert_recast(code, code);
4365 }
4366
4367 #[test]
4368 fn recast_unary_operands_with_custom_indentation() {
4369 let input = "@settings(kclVersion = 2.0)\n\nfn example() {\n x = -f(a = 1, b = 2, c = 3, d = 4)\n}\n";
4370 let program = crate::parsing::top_level_parse(input).unwrap();
4371 for (options, expected) in [
4372 (
4373 FormatOptions {
4374 tab_size: 4,
4375 ..Default::default()
4376 },
4377 "@settings(kclVersion = 2.0)\n\nfn example() {\n x = -f(\n a = 1,\n b = 2,\n c = 3,\n d = 4,\n )\n}\n",
4378 ),
4379 (
4380 FormatOptions {
4381 use_tabs: true,
4382 ..Default::default()
4383 },
4384 "@settings(kclVersion = 2.0)\n\nfn example() {\n\tx = -f(\n\t\ta = 1,\n\t\tb = 2,\n\t\tc = 3,\n\t\td = 4,\n\t)\n}\n",
4385 ),
4386 ] {
4387 let output = program.recast_top(&options, 0);
4388 assert_eq!(output, expected);
4389 let reparsed = crate::parsing::top_level_parse(&output).unwrap();
4390 assert_eq!(reparsed.recast_top(&options, 0), output);
4391 }
4392 }
4393
4394 #[test]
4395 fn indented_array_expression() {
4396 let code = "\
4397fn foo() {
4398 [1, 2]
4399}
4400";
4401 let ast = crate::parsing::top_level_parse(code).unwrap();
4402 let recasted = ast.recast_top(&FormatOptions::new(), 0);
4403 let expected = code;
4404 assert_eq!(recasted, expected);
4405 }
4406
4407 #[test]
4408 fn indented_name_expression() {
4409 let code = "\
4410fn foo() {
4411 x
4412}
4413";
4414 let ast = crate::parsing::top_level_parse(code).unwrap();
4415 let recasted = ast.recast_top(&FormatOptions::new(), 0);
4416 let expected = code;
4417 assert_eq!(recasted, expected);
4418 }
4419
4420 #[test]
4421 fn indented_member_assignment() {
4422 let code = "\
4423brakcetPlane = {
4424 origin = { x = length / 2 },
4425 origin = { x = length / 2 },
4426 origin = { x = length / 2 },
4427 origin = { x = length / 2 },
4428 origin = { x = length / 2 },
4429 origin = { x = length / 2 },
4430 origin = { x = length / 2 },
4431 origin = { x = length / 2 }
4432}
4433";
4434 let ast = crate::parsing::top_level_parse(code).unwrap();
4435 let recasted = ast.recast_top(&FormatOptions::new(), 0);
4436 let expected = code;
4437 assert_eq!(recasted, expected);
4438 }
4439
4440 #[test]
4441 fn badly_formatted_inline_calls() {
4442 let code = "\
4443return union([right, left])
4444 |> subtract(tools = [
4445 translate(axle(), y = pitchStabL + forkBaseL + wheelRGap + wheelR + addedLength),
4446 socket(rakeAngle = rearRake, xyTrans = [0, 12]),
4447 socket(
4448 rakeAngle = frontRake,
4449 xyTrans = [
4450 wheelW / 2 + wheelWGap + forkTineW / 2,
4451 40 + addedLength
4452 ],
4453 )
4454 ])
4455";
4456 let ast = crate::parsing::top_level_parse(code).unwrap();
4457 let recasted = ast.recast_top(&FormatOptions::new(), 0);
4458 let expected = code;
4459 assert_eq!(recasted, expected);
4460 }
4461
4462 #[test]
4463 fn fn_args_prefixed_with_spaces() {
4464 let code = "holeAt(
4465 [cube1, cube2],
4466 plane = XY,
4467 holeBottom = hole::flat(),
4468 holeBody = hole::blind(depth = 2, diameter = 1),
4469 holeType = hole::counterbore(diameter = 1.4, depth = 1),
4470 cutAt = [1, 1],
4471)";
4472 let expected = "holeAt(
4473 [cube1, cube2],
4474 plane = XY,
4475 holeBottom = hole::flat(),
4476 holeBody = hole::blind(depth = 2, diameter = 1),
4477 holeType = hole::counterbore(diameter = 1.4, depth = 1),
4478 cutAt = [1, 1],
4479)
4480";
4481 let ast = crate::parsing::top_level_parse(code).unwrap();
4482 let recasted = ast.recast_top(&FormatOptions::new(), 0);
4483 assert_eq!(recasted, expected);
4484 }
4485
4486 #[test]
4487 fn some_fn_args_still_prefixed() {
4488 let code = "a
4489 |> b()
4490 |> subtract(
4491 tools = startSketchOn(XY)
4492 |> circle(diameter = hubDiameter)
4493 |> extrude(length = hubThickness * 5, symmetric = true),
4494 tolerance,
4495 )
4496";
4497 let ast = crate::parsing::top_level_parse(code).unwrap();
4498 let actual_recasted = ast.recast_top(&FormatOptions::new(), 0);
4499 let expected_recasted = "a
4500 |> b()
4501 |> subtract(
4502 tools = startSketchOn(XY)
4503 |> circle(diameter = hubDiameter)
4504 |> extrude(length = hubThickness * 5, symmetric = true),
4505 tolerance,
4506 )
4507";
4508 assert_eq!(actual_recasted, expected_recasted);
4509 }
4510
4511 #[test]
4512 fn first_in_pipeline_indent() {
4513 let not_clone = "gear::helical(
4516 nTeeth = 12,
4517 module = 1.5,
4518 pressureAngle = 14deg,
4519 helixAngle = 25deg,
4520 gearHeight = 5,
4521)
4522";
4523 let yes_clone = "gear::helical(
4524 nTeeth = 12,
4525 module = 1.5,
4526 pressureAngle = 14deg,
4527 helixAngle = 25deg,
4528 gearHeight = 5,
4529)
4530|> clone()
4531";
4532 let not_clone_recasted = crate::parsing::top_level_parse(not_clone)
4534 .unwrap()
4535 .recast_top(&FormatOptions::new(), 0);
4536 let yes_clone_recasted = crate::parsing::top_level_parse(yes_clone)
4537 .unwrap()
4538 .recast_top(&FormatOptions::new(), 0);
4539 assert!(not_clone_recasted.contains("\n nTeeth"));
4540 assert!(!yes_clone_recasted.contains("\n nTeeth"));
4541 assert!(yes_clone_recasted.contains("\n nTeeth"));
4542 }
4543}