Bug: KCL formatter removes 'fn' from closures: (#4718)

# Problem

Before this PR, our formatter reformats
```
squares_out = reduce(arr, 0, fn (i, squares)  {
  return 1
})
```
to 
```
squares_out = reduce(arr, 0, (i, squares) {
  return 1
})
```
i.e. it removes the `fn` keyword from the closure. This keyword is required, so, our formatter turned working code into invalid code.

# Cause

When this closure parameter is formatted, the ExprContext is ::Decl, so `Expr::recast` skips adding the `fn` keyword. The reason it's ::Decl is because the `squares_out = ` declaration sets it, and no subsequent call sets the context to something else.

# Solution

When recasting a call expression, set the context for every argument to `ExprContext::Other`.
This commit is contained in:
Adam Chalmers
2024-12-09 19:13:49 -06:00
committed by GitHub
parent 59a6333aad
commit 6aa588f09f
4 changed files with 37 additions and 12 deletions

View File

@ -166,7 +166,14 @@ pub(crate) enum ExprContext {
}
impl Expr {
pub(crate) fn recast(&self, options: &FormatOptions, indentation_level: usize, ctxt: ExprContext) -> String {
pub(crate) fn recast(&self, options: &FormatOptions, indentation_level: usize, mut ctxt: ExprContext) -> String {
let is_decl = matches!(ctxt, ExprContext::Decl);
if is_decl {
// Just because this expression is being bound to a variable, doesn't mean that every child
// expression is being bound. So, reset the expression context if necessary.
// This will still preserve the "::Pipe" context though.
ctxt = ExprContext::Other;
}
match &self {
Expr::BinaryExpression(bin_exp) => bin_exp.recast(options),
Expr::ArrayExpression(array_exp) => array_exp.recast(options, indentation_level, ctxt),
@ -175,11 +182,7 @@ impl Expr {
Expr::MemberExpression(mem_exp) => mem_exp.recast(),
Expr::Literal(literal) => literal.recast(),
Expr::FunctionExpression(func_exp) => {
let mut result = if ctxt == ExprContext::Decl {
String::new()
} else {
"fn".to_owned()
};
let mut result = if is_decl { String::new() } else { "fn".to_owned() };
result += &func_exp.recast(options, indentation_level);
result
}
@ -2170,6 +2173,28 @@ sketch002 = startSketchOn({
assert_eq!(actual, expected);
}
#[test]
fn unparse_fn_unnamed() {
let input = r#"squares_out = reduce(arr, 0, fn(i, squares) {
return 1
})
"#;
let ast = crate::parsing::top_level_parse(input).unwrap();
let actual = ast.recast(&FormatOptions::new(), 0);
assert_eq!(actual, input);
}
#[test]
fn unparse_fn_named() {
let input = r#"fn f(x) {
return 1
}
"#;
let ast = crate::parsing::top_level_parse(input).unwrap();
let actual = ast.recast(&FormatOptions::new(), 0);
assert_eq!(actual, input);
}
#[test]
fn recast_objects_with_comments() {
use winnow::Parser;