1 use full_moon::{
2 ast::{
3 span::ContainedSpan, BinOp, Call, Expression, Index, Prefix, Suffix, UnOp, Value, Var,
4 VarExpression,
5 },
6 node::Node,
7 tokenizer::{Symbol, Token, TokenReference, TokenType},
8 };
9 use std::boxed::Box;
10
11 #[cfg(feature = "luau")]
12 use crate::formatters::luau::format_type_assertion;
13 use crate::{
14 context::{create_indent_trivia, create_newline_trivia, Context},
15 fmt_symbol,
16 formatters::{
17 functions::{
18 format_anonymous_function, format_call, format_function_call, FunctionCallNextNode,
19 },
20 general::{format_contained_span, format_token_reference},
21 table::format_table_constructor,
22 trivia::{
23 strip_leading_trivia, strip_trivia, FormatTriviaType, UpdateLeadingTrivia,
24 UpdateTrailingTrivia, UpdateTrivia,
25 },
26 trivia_util::{
27 self, contains_comments, expression_leading_comments, get_expression_trailing_trivia,
28 trivia_is_newline,
29 },
30 },
31 shape::Shape,
32 };
33
34 #[macro_export]
35 macro_rules! fmt_op {
36 ($ctx:expr, $enum:ident, $value:ident, $shape:expr, { $($operator:ident = $output:expr,)+ }) => {
37 match $value {
38 $(
39 $enum::$operator(token) => $enum::$operator(fmt_symbol!($ctx, token, $output, $shape)),
40 )+
41 other => panic!("unknown node {:?}", other),
42 }
43 };
44 }
45
46 enum ExpressionContext {
47 /// Standard expression, with no special context
48 Standard,
49 /// The expression originates from a [`Prefix`] node. The special context here is that the expression will
50 /// always be wrapped in parentheses.
51 Prefix,
52 }
53
format_binop(ctx: &Context, binop: &BinOp, shape: Shape) -> BinOp54 pub fn format_binop(ctx: &Context, binop: &BinOp, shape: Shape) -> BinOp {
55 fmt_op!(ctx, BinOp, binop, shape, {
56 And = " and ",
57 Caret = " ^ ",
58 GreaterThan = " > ",
59 GreaterThanEqual = " >= ",
60 LessThan = " < ",
61 LessThanEqual = " <= ",
62 Minus = " - ",
63 Or = " or ",
64 Percent = " % ",
65 Plus = " + ",
66 Slash = " / ",
67 Star = " * ",
68 TildeEqual = " ~= ",
69 TwoDots = " .. ",
70 TwoEqual = " == ",
71 })
72 }
73
74 /// Check to determine whether expression parentheses are required, depending on the provided
75 /// internal expression contained within the parentheses
check_excess_parentheses(internal_expression: &Expression) -> bool76 fn check_excess_parentheses(internal_expression: &Expression) -> bool {
77 match internal_expression {
78 // Parentheses inside parentheses, not necessary
79 Expression::Parentheses { .. } => true,
80 // Check whether the expression relating to the UnOp is safe
81 Expression::UnaryOperator { expression, .. } => check_excess_parentheses(expression),
82 // Don't bother removing them if there is a binop, as they may be needed. TODO: can we be more intelligent here?
83 Expression::BinaryOperator { .. } => false,
84 Expression::Value {
85 value,
86 #[cfg(feature = "luau")]
87 type_assertion,
88 } => {
89 // If we have a type assertion, we should always keep parentheses
90 #[cfg(feature = "luau")]
91 if type_assertion.is_some() {
92 return false;
93 }
94
95 match &**value {
96 // Internal expression is a function call
97 // We could potentially be culling values, so we should not remove parentheses
98 Value::FunctionCall(_) => false,
99 Value::Symbol(token_ref) => {
100 match token_ref.token_type() {
101 // If we have an ellipse inside of parentheses, we may also be culling values
102 // Therefore, we don't remove parentheses
103 TokenType::Symbol { symbol } => !matches!(symbol, Symbol::Ellipse),
104 _ => true,
105 }
106 }
107 _ => true,
108 }
109 }
110 other => panic!("unknown node {:?}", other),
111 }
112 }
113
114 /// Formats an Expression node
format_expression(ctx: &Context, expression: &Expression, shape: Shape) -> Expression115 pub fn format_expression(ctx: &Context, expression: &Expression, shape: Shape) -> Expression {
116 format_expression_internal(ctx, expression, ExpressionContext::Standard, shape)
117 }
118
119 /// Internal expression formatter, with access to expression context
format_expression_internal( ctx: &Context, expression: &Expression, context: ExpressionContext, shape: Shape, ) -> Expression120 fn format_expression_internal(
121 ctx: &Context,
122 expression: &Expression,
123 context: ExpressionContext,
124 shape: Shape,
125 ) -> Expression {
126 match expression {
127 Expression::Value {
128 value,
129 #[cfg(feature = "luau")]
130 type_assertion,
131 } => Expression::Value {
132 value: Box::new(format_value(ctx, value, shape)),
133 #[cfg(feature = "luau")]
134 type_assertion: type_assertion
135 .as_ref()
136 .map(|assertion| format_type_assertion(ctx, assertion, shape)),
137 },
138 Expression::Parentheses {
139 contained,
140 expression,
141 } => {
142 // Examine whether the internal expression requires parentheses
143 // If not, just format and return the internal expression. Otherwise, format the parentheses
144 let use_internal_expression = check_excess_parentheses(expression);
145
146 // If the context is for a prefix, we should always keep the parentheses, as they are always required
147 if use_internal_expression && !matches!(context, ExpressionContext::Prefix) {
148 // Get the trailing comments from contained span and append them onto the expression
149 let trailing_comments = contained
150 .tokens()
151 .1
152 .trailing_trivia()
153 .filter(|token| trivia_util::trivia_is_comment(token))
154 .flat_map(|x| {
155 // Prepend a single space beforehand
156 vec![Token::new(TokenType::spaces(1)), x.to_owned()]
157 })
158 .collect();
159 format_expression(ctx, expression, shape)
160 .update_trailing_trivia(FormatTriviaType::Append(trailing_comments))
161 } else {
162 Expression::Parentheses {
163 contained: format_contained_span(ctx, contained, shape),
164 expression: Box::new(format_expression(ctx, expression, shape + 1)), // 1 = opening parentheses
165 }
166 }
167 }
168 Expression::UnaryOperator { unop, expression } => {
169 let unop = format_unop(ctx, unop, shape);
170 let shape = shape + strip_leading_trivia(&unop).to_string().len();
171 let mut expression = format_expression(ctx, expression, shape);
172
173 // Special case: if we have `- -foo`, or `-(-foo)` where we have already removed the parentheses, then
174 // it will lead to `--foo`, which is invalid syntax. We must explicitly add/keep the parentheses `-(-foo)`.
175 if let UnOp::Minus(_) = unop {
176 let require_parentheses = match expression {
177 Expression::UnaryOperator {
178 unop: UnOp::Minus(_),
179 ..
180 } => true,
181
182 Expression::Value { ref value, .. } => matches!(
183 **value,
184 Value::ParenthesesExpression(Expression::UnaryOperator {
185 unop: UnOp::Minus(_),
186 ..
187 })
188 ),
189
190 _ => false,
191 };
192
193 if require_parentheses {
194 let (new_expression, trailing_comments) =
195 trivia_util::take_expression_trailing_comments(&expression);
196 expression = Expression::Parentheses {
197 contained: ContainedSpan::new(
198 TokenReference::symbol("(").unwrap(),
199 TokenReference::symbol(")").unwrap(),
200 )
201 .update_trailing_trivia(FormatTriviaType::Append(trailing_comments)),
202 expression: Box::new(new_expression),
203 }
204 }
205 }
206
207 Expression::UnaryOperator {
208 unop,
209 expression: Box::new(expression),
210 }
211 }
212 Expression::BinaryOperator { lhs, binop, rhs } => {
213 let lhs = format_expression(ctx, lhs, shape);
214 let binop = format_binop(ctx, binop, shape);
215 let shape = shape.take_last_line(&lhs) + binop.to_string().len();
216 Expression::BinaryOperator {
217 lhs: Box::new(lhs),
218 binop,
219 rhs: Box::new(format_expression(ctx, rhs, shape)),
220 }
221 }
222 other => panic!("unknown node {:?}", other),
223 }
224 }
225
226 /// Formats an Index Node
format_index(ctx: &Context, index: &Index, shape: Shape) -> Index227 pub fn format_index(ctx: &Context, index: &Index, shape: Shape) -> Index {
228 match index {
229 Index::Brackets {
230 brackets,
231 expression,
232 } => Index::Brackets {
233 brackets: format_contained_span(ctx, brackets, shape),
234 expression: format_expression(ctx, expression, shape + 1), // 1 = opening bracket
235 },
236
237 Index::Dot { dot, name } => Index::Dot {
238 dot: format_token_reference(ctx, dot, shape),
239 name: format_token_reference(ctx, name, shape),
240 },
241 other => panic!("unknown node {:?}", other),
242 }
243 }
244
245 /// Formats a Prefix Node
format_prefix(ctx: &Context, prefix: &Prefix, shape: Shape) -> Prefix246 pub fn format_prefix(ctx: &Context, prefix: &Prefix, shape: Shape) -> Prefix {
247 match prefix {
248 Prefix::Expression(expression) => {
249 let singleline_format =
250 format_expression_internal(ctx, expression, ExpressionContext::Prefix, shape);
251 let singeline_shape = shape.take_first_line(&strip_trivia(&singleline_format));
252
253 if singeline_shape.over_budget() {
254 Prefix::Expression(format_hanging_expression_(
255 ctx,
256 expression,
257 shape,
258 ExpressionContext::Prefix,
259 None,
260 ))
261 } else {
262 Prefix::Expression(singleline_format)
263 }
264 }
265 Prefix::Name(token_reference) => {
266 Prefix::Name(format_token_reference(ctx, token_reference, shape))
267 }
268 other => panic!("unknown node {:?}", other),
269 }
270 }
271
272 /// Formats a Suffix Node
format_suffix( ctx: &Context, suffix: &Suffix, shape: Shape, call_next_node: FunctionCallNextNode, ) -> Suffix273 pub fn format_suffix(
274 ctx: &Context,
275 suffix: &Suffix,
276 shape: Shape,
277 call_next_node: FunctionCallNextNode,
278 ) -> Suffix {
279 match suffix {
280 Suffix::Call(call) => Suffix::Call(format_call(ctx, call, shape, call_next_node)),
281 Suffix::Index(index) => Suffix::Index(format_index(ctx, index, shape)),
282 other => panic!("unknown node {:?}", other),
283 }
284 }
285
286 /// Formats a Value Node
format_value(ctx: &Context, value: &Value, shape: Shape) -> Value287 pub fn format_value(ctx: &Context, value: &Value, shape: Shape) -> Value {
288 match value {
289 Value::Function((token_reference, function_body)) => Value::Function(
290 format_anonymous_function(ctx, token_reference, function_body, shape),
291 ),
292 Value::FunctionCall(function_call) => {
293 Value::FunctionCall(format_function_call(ctx, function_call, shape))
294 }
295 Value::Number(token_reference) => {
296 Value::Number(format_token_reference(ctx, token_reference, shape))
297 }
298 Value::ParenthesesExpression(expression) => {
299 Value::ParenthesesExpression(format_expression(ctx, expression, shape))
300 }
301 Value::String(token_reference) => {
302 Value::String(format_token_reference(ctx, token_reference, shape))
303 }
304 Value::Symbol(token_reference) => {
305 Value::Symbol(format_token_reference(ctx, token_reference, shape))
306 }
307 Value::TableConstructor(table_constructor) => {
308 Value::TableConstructor(format_table_constructor(ctx, table_constructor, shape))
309 }
310 Value::Var(var) => Value::Var(format_var(ctx, var, shape)),
311 other => panic!("unknown node {:?}", other),
312 }
313 }
314
315 /// Formats a Var Node
format_var(ctx: &Context, var: &Var, shape: Shape) -> Var316 pub fn format_var(ctx: &Context, var: &Var, shape: Shape) -> Var {
317 match var {
318 Var::Name(token_reference) => {
319 Var::Name(format_token_reference(ctx, token_reference, shape))
320 }
321 Var::Expression(var_expression) => {
322 Var::Expression(format_var_expression(ctx, var_expression, shape))
323 }
324 other => panic!("unknown node {:?}", other),
325 }
326 }
327
format_var_expression( ctx: &Context, var_expression: &VarExpression, shape: Shape, ) -> VarExpression328 pub fn format_var_expression(
329 ctx: &Context,
330 var_expression: &VarExpression,
331 shape: Shape,
332 ) -> VarExpression {
333 let formatted_prefix = format_prefix(ctx, var_expression.prefix(), shape);
334 let mut shape = shape + strip_leading_trivia(&formatted_prefix).to_string().len();
335
336 let mut formatted_suffixes = Vec::new();
337 let mut suffixes = var_expression.suffixes().peekable();
338
339 while let Some(suffix) = suffixes.next() {
340 // If the suffix after this one is something like `.foo` or `:foo` - this affects removing parentheses
341 let ambiguous_next_suffix = if matches!(
342 suffixes.peek(),
343 Some(Suffix::Index(_)) | Some(Suffix::Call(Call::MethodCall(_)))
344 ) {
345 FunctionCallNextNode::ObscureWithoutParens
346 } else {
347 FunctionCallNextNode::None
348 };
349
350 let suffix = format_suffix(ctx, suffix, shape, ambiguous_next_suffix);
351 shape = shape + suffix.to_string().len();
352 formatted_suffixes.push(suffix);
353 }
354
355 VarExpression::new(formatted_prefix).with_suffixes(formatted_suffixes)
356 }
357
358 /// Formats an UnOp Node
format_unop(ctx: &Context, unop: &UnOp, shape: Shape) -> UnOp359 pub fn format_unop(ctx: &Context, unop: &UnOp, shape: Shape) -> UnOp {
360 fmt_op!(ctx, UnOp, unop, shape, {
361 Minus = "-",
362 Not = "not ",
363 Hash = "#",
364 })
365 }
366
367 /// Pushes a [`BinOp`] onto a newline, and indent its depending on indent_level.
368 /// Preserves any leading comments, and moves trailing comments to before the BinOp.
369 /// Also takes in the [`Expression`] present on the RHS of the BinOp - this is needed so that we can take any
370 /// leading comments from the expression, and place them before the BinOp.
hang_binop(ctx: &Context, binop: BinOp, shape: Shape, rhs: &Expression) -> BinOp371 fn hang_binop(ctx: &Context, binop: BinOp, shape: Shape, rhs: &Expression) -> BinOp {
372 // Get the leading comments of a binop, as we need to preserve them
373 // Intersperse a newline and indent trivia between them
374 // iter_intersperse is currently not available, so we need to do something different. Tracking issue: https://github.com/rust-lang/rust/issues/79524
375 let mut leading_comments = trivia_util::binop_leading_comments(&binop)
376 .iter()
377 .flat_map(|x| {
378 vec![
379 create_newline_trivia(ctx),
380 create_indent_trivia(ctx, shape),
381 x.to_owned(),
382 ]
383 })
384 .collect::<Vec<_>>();
385
386 // If there are any comments trailing the BinOp, we need to move them to before the BinOp
387 let mut trailing_comments = trivia_util::binop_trailing_comments(&binop);
388 leading_comments.append(&mut trailing_comments);
389
390 // If there are any leading comments to the RHS expression, we need to move them to before the BinOp
391 let mut expression_leading_comments = trivia_util::expression_leading_comments(rhs)
392 .iter()
393 .flat_map(|x| {
394 vec![
395 create_newline_trivia(ctx),
396 create_indent_trivia(ctx, shape),
397 x.to_owned(),
398 ]
399 })
400 .collect::<Vec<_>>();
401 leading_comments.append(&mut expression_leading_comments);
402
403 // Create a newline just before the BinOp, and preserve the indentation
404 leading_comments.push(create_newline_trivia(ctx));
405 leading_comments.push(create_indent_trivia(ctx, shape));
406
407 binop.update_trivia(
408 FormatTriviaType::Replace(leading_comments),
409 FormatTriviaType::Replace(vec![Token::new(TokenType::spaces(1))]),
410 )
411 }
412
413 /// Finds the length of the expression which matches the precedence level of the provided binop
binop_expression_length(expression: &Expression, top_binop: &BinOp) -> usize414 fn binop_expression_length(expression: &Expression, top_binop: &BinOp) -> usize {
415 match expression {
416 Expression::BinaryOperator { lhs, binop, rhs } => {
417 if binop.precedence() >= top_binop.precedence()
418 && binop.is_right_associative() == top_binop.is_right_associative()
419 {
420 if binop.is_right_associative() {
421 binop_expression_length(rhs, top_binop)
422 + strip_trivia(binop).to_string().len() + 2 // 2 = space before and after binop
423 + lhs.to_string().len()
424 } else {
425 binop_expression_length(lhs, top_binop)
426 + strip_trivia(binop).to_string().len() + 2 // 2 = space before and after binop
427 + rhs.to_string().len()
428 }
429 } else {
430 0
431 }
432 }
433 _ => expression.to_string().len(),
434 }
435 }
436
binop_expression_contains_comments(expression: &Expression, top_binop: &BinOp) -> bool437 fn binop_expression_contains_comments(expression: &Expression, top_binop: &BinOp) -> bool {
438 match expression {
439 Expression::BinaryOperator { lhs, binop, rhs } => {
440 if binop.precedence() == top_binop.precedence() {
441 contains_comments(binop)
442 || !expression_leading_comments(rhs).is_empty()
443 || get_expression_trailing_trivia(lhs)
444 .iter()
445 .any(trivia_util::trivia_is_comment)
446 || binop_expression_contains_comments(lhs, top_binop)
447 || binop_expression_contains_comments(rhs, top_binop)
448 } else {
449 false
450 }
451 }
452 _ => false,
453 }
454 }
455
456 /// Converts an item to a range
457 trait ToRange {
to_range(&self) -> (usize, usize)458 fn to_range(&self) -> (usize, usize);
459 }
460
461 impl ToRange for (usize, usize) {
to_range(&self) -> (usize, usize)462 fn to_range(&self) -> (usize, usize) {
463 *self
464 }
465 }
466
467 impl ToRange for Expression {
to_range(&self) -> (usize, usize)468 fn to_range(&self) -> (usize, usize) {
469 let (start, end) = self.range().unwrap();
470 (start.bytes(), end.bytes())
471 }
472 }
473
474 /// This struct encompasses information about the leftmost-expression in a BinaryExpression tree.
475 /// It holds the range of the leftmost binary expression, and the original additional indent level of this range.
476 /// This struct is only used when the hanging binary expression involves a hang level, for example:
477 /// ```lua
478 /// foooo
479 /// + bar
480 /// + baz
481 /// ```
482 /// or in a larger context:
483 /// ```lua
484 /// local someVariable = foooo
485 /// + bar
486 /// + baz
487 /// ```
488 /// As seen, the first item (`foooo`) is inlined, and has an indent level one lower than the rest of the binary
489 /// expressions. We want to ensure that whenever we have `foooo` in our expression, we use the original indentation level
490 /// because the expression is (at this current point in time) inlined - otherwise, it will be over-indented.
491 /// We hold the original indentation level incase we are deep down in the recursivecalls:
492 /// ```lua
493 /// local ratio = (minAxis - minAxisSize) / delta * (self.props.maxScaleRatio - self.props.minScaleRatio)
494 /// + self.props.minScaleRatio
495 /// ```
496 /// Since the first line contains binary operators at a different precedence level to the `+`, then the indentation
497 /// level has been increased even further. But we want to use the original indentation level, because as it stands,
498 /// the expression is currently inlined on the original line.
499 #[derive(Clone, Copy, Debug)]
500 struct LeftmostRangeHang {
501 range: (usize, usize),
502 original_additional_indent_level: usize,
503 }
504
505 impl LeftmostRangeHang {
506 /// Finds the leftmost expression from the given (full) expression, and then creates a [`LeftmostRangeHang`]
507 /// to represent it
find(expression: &Expression, original_additional_indent_level: usize) -> Self508 fn find(expression: &Expression, original_additional_indent_level: usize) -> Self {
509 match expression {
510 Expression::BinaryOperator { lhs, .. } => {
511 Self::find(lhs, original_additional_indent_level)
512 }
513 _ => Self {
514 range: expression.to_range(),
515 original_additional_indent_level,
516 },
517 }
518 }
519
520 /// Given an [`Expression`], returns the [`Shape`] to use for this expression.
521 /// This function checks the provided expression to see if the LeftmostRange falls inside of it.
522 /// If so, then we need to use the original indentation level shape, as (so far) the expression is inlined.
required_shape<T: ToRange>(&self, shape: Shape, item: &T) -> Shape523 fn required_shape<T: ToRange>(&self, shape: Shape, item: &T) -> Shape {
524 let (expression_start, expression_end) = item.to_range();
525 let (lhs_start, lhs_end) = self.range;
526
527 if lhs_start >= expression_start && lhs_end <= expression_end {
528 shape.with_indent(
529 shape
530 .indent()
531 .with_additional_indent(self.original_additional_indent_level),
532 )
533 } else {
534 shape
535 }
536 }
537 }
538
is_hang_binop_over_width( shape: Shape, expression: &Expression, top_binop: &BinOp, lhs_range: Option<LeftmostRangeHang>, ) -> bool539 fn is_hang_binop_over_width(
540 shape: Shape,
541 expression: &Expression,
542 top_binop: &BinOp,
543 lhs_range: Option<LeftmostRangeHang>,
544 ) -> bool {
545 let shape = if let Some(lhs_hang) = lhs_range {
546 lhs_hang.required_shape(shape, expression)
547 } else {
548 shape
549 };
550
551 shape
552 .add_width(binop_expression_length(expression, top_binop))
553 .over_budget()
554 }
555
556 /// If present, finds the precedence level of the provided binop in the BinOp expression. Otherwise, returns 0
binop_precedence_level(expression: &Expression) -> u8557 fn binop_precedence_level(expression: &Expression) -> u8 {
558 match expression {
559 Expression::BinaryOperator { binop, .. } => binop.precedence(),
560 _ => 0,
561 }
562 }
563
did_hang_expression(expression: &Expression) -> bool564 fn did_hang_expression(expression: &Expression) -> bool {
565 if let Expression::BinaryOperator { binop, .. } = expression {
566 // Examine the binop's leading trivia for a newline
567 // TODO: this works..., but is it the right solution?
568 binop
569 .surrounding_trivia()
570 .0
571 .iter()
572 .any(|x| trivia_is_newline(x))
573 } else {
574 false
575 }
576 }
577
578 #[derive(Debug)]
579 enum ExpressionSide {
580 Left,
581 Right,
582 }
583
hang_binop_expression( ctx: &Context, expression: Expression, top_binop: BinOp, shape: Shape, lhs_range: Option<LeftmostRangeHang>, ) -> Expression584 fn hang_binop_expression(
585 ctx: &Context,
586 expression: Expression,
587 top_binop: BinOp,
588 shape: Shape,
589 lhs_range: Option<LeftmostRangeHang>,
590 ) -> Expression {
591 let full_expression = expression.to_owned();
592
593 match expression {
594 Expression::BinaryOperator { lhs, binop, rhs } => {
595 // Keep grouping together all operators with the same precedence level as the main BinOp
596 // They should also have the same associativity
597 let same_op_level = binop.precedence() == top_binop.precedence()
598 && binop.is_right_associative() == top_binop.is_right_associative();
599 let is_right_associative = binop.is_right_associative();
600
601 let test_shape = if same_op_level {
602 shape
603 } else {
604 shape.increment_additional_indent()
605 };
606
607 let side_to_hang = if is_right_associative {
608 ExpressionSide::Right
609 } else {
610 ExpressionSide::Left
611 };
612
613 // TODO/FIXME: using test_shape here leads to too high of an indent level, causing the expression to hang unnecessarily
614 let over_column_width =
615 is_hang_binop_over_width(test_shape, &full_expression, &binop, lhs_range);
616 let should_hang = same_op_level
617 || over_column_width
618 || binop_expression_contains_comments(&full_expression, &binop);
619
620 // Only use the indented shape if we are planning to hang
621 let shape = if should_hang { test_shape } else { shape };
622
623 let mut new_binop = format_binop(ctx, &binop, shape);
624 if should_hang {
625 new_binop = hang_binop(ctx, binop.to_owned(), shape, &rhs);
626 }
627
628 let (lhs, rhs) = match should_hang {
629 true => {
630 let lhs_shape = shape;
631 let rhs_shape = shape + strip_trivia(&new_binop).to_string().len() + 1;
632
633 let (lhs, rhs) = match side_to_hang {
634 ExpressionSide::Left => (
635 hang_binop_expression(
636 ctx,
637 *lhs,
638 if same_op_level { top_binop } else { binop },
639 lhs_shape,
640 lhs_range,
641 ),
642 format_expression(ctx, &*rhs, rhs_shape),
643 ),
644 ExpressionSide::Right => (
645 format_expression(ctx, &*lhs, lhs_shape),
646 hang_binop_expression(
647 ctx,
648 *rhs,
649 if same_op_level { top_binop } else { binop },
650 rhs_shape,
651 lhs_range,
652 ),
653 ),
654 };
655 (
656 lhs,
657 rhs.update_leading_trivia(FormatTriviaType::Replace(Vec::new())),
658 )
659 }
660 false => {
661 // Check if the chain still has comments deeper inside of it.
662 // If it does, we need to hang that part of the chain still, otherwise the comments will mess it up
663 let lhs = if contains_comments(&*lhs) {
664 hang_binop_expression(ctx, *lhs, binop.to_owned(), shape, lhs_range)
665 } else {
666 format_expression(ctx, &*lhs, shape)
667 };
668
669 let rhs = if contains_comments(&*rhs) {
670 hang_binop_expression(ctx, *rhs, binop, shape, lhs_range)
671 } else {
672 format_expression(ctx, &*rhs, shape)
673 };
674
675 (lhs, rhs)
676 }
677 };
678
679 Expression::BinaryOperator {
680 lhs: Box::new(lhs),
681 binop: new_binop,
682 rhs: Box::new(rhs),
683 }
684 }
685 // Base case: no more binary operators - just return to normal splitting
686 _ => format_hanging_expression_(
687 ctx,
688 &expression,
689 shape,
690 ExpressionContext::Standard,
691 lhs_range,
692 ),
693 }
694 }
695
696 /// Internal expression formatter, where the binop is also hung
format_hanging_expression_( ctx: &Context, expression: &Expression, shape: Shape, expression_context: ExpressionContext, lhs_range: Option<LeftmostRangeHang>, ) -> Expression697 fn format_hanging_expression_(
698 ctx: &Context,
699 expression: &Expression,
700 shape: Shape,
701 expression_context: ExpressionContext,
702 lhs_range: Option<LeftmostRangeHang>,
703 ) -> Expression {
704 let expression_range = expression.to_range();
705
706 match expression {
707 Expression::Value {
708 value,
709 #[cfg(feature = "luau")]
710 type_assertion,
711 } => {
712 let value = Box::new(match &**value {
713 Value::ParenthesesExpression(expression) => {
714 Value::ParenthesesExpression(format_hanging_expression_(
715 ctx,
716 expression,
717 shape,
718 expression_context,
719 lhs_range,
720 ))
721 }
722 _ => {
723 let shape = if let Some(lhs_hang) = lhs_range {
724 lhs_hang.required_shape(shape, &expression_range)
725 } else {
726 shape
727 };
728 format_value(ctx, value, shape)
729 }
730 });
731 Expression::Value {
732 value,
733 #[cfg(feature = "luau")]
734 type_assertion: type_assertion
735 .as_ref()
736 .map(|assertion| format_type_assertion(ctx, assertion, shape)),
737 }
738 }
739 Expression::Parentheses {
740 contained,
741 expression,
742 } => {
743 let lhs_shape = if let Some(lhs_hang) = lhs_range {
744 lhs_hang.required_shape(shape, &expression_range)
745 } else {
746 shape
747 };
748
749 // Examine whether the internal expression requires parentheses
750 // If not, just format and return the internal expression. Otherwise, format the parentheses
751 let use_internal_expression = check_excess_parentheses(expression);
752
753 // If the context is for a prefix, we should always keep the parentheses, as they are always required
754 if use_internal_expression && !matches!(expression_context, ExpressionContext::Prefix) {
755 format_hanging_expression_(
756 ctx,
757 expression,
758 lhs_shape,
759 expression_context,
760 lhs_range,
761 )
762 } else {
763 let contained = format_contained_span(ctx, contained, lhs_shape);
764
765 // Provide a sample formatting to see how large it is
766 // Examine the expression itself to see if needs to be split onto multiple lines
767 let formatted_expression = format_expression(ctx, expression, lhs_shape + 1); // 1 = opening parentheses
768
769 let expression_str = formatted_expression.to_string();
770 if !lhs_shape.add_width(2 + expression_str.len()).over_budget() {
771 // The expression inside the parentheses is small, we do not need to break it down further
772 return Expression::Parentheses {
773 contained,
774 expression: Box::new(formatted_expression),
775 };
776 }
777
778 // Update the expression shape to be used inside the parentheses, applying the indent increase
779 // Use the original `shape` rather than the LeftmostRangeHang-determined shape, because we are now
780 // indenting the internal expression, which is not part of the hang
781 let expression_shape = shape.reset().increment_additional_indent();
782
783 // Modify the parentheses to hang the expression
784 let (start_token, end_token) = contained.tokens();
785
786 // Create a newline after the start brace and before the end brace
787 // Also, indent enough for the first expression in the start brace
788 let contained = ContainedSpan::new(
789 start_token.update_trailing_trivia(FormatTriviaType::Append(vec![
790 create_newline_trivia(ctx),
791 create_indent_trivia(ctx, expression_shape),
792 ])),
793 end_token.update_leading_trivia(FormatTriviaType::Append(vec![
794 create_newline_trivia(ctx),
795 create_indent_trivia(ctx, shape),
796 ])),
797 );
798
799 Expression::Parentheses {
800 contained,
801 expression: Box::new(format_hanging_expression_(
802 ctx,
803 expression,
804 expression_shape,
805 ExpressionContext::Standard,
806 None,
807 )),
808 }
809 }
810 }
811 Expression::UnaryOperator { unop, expression } => {
812 let unop = format_unop(ctx, unop, shape);
813 let shape = shape + strip_leading_trivia(&unop).to_string().len();
814 let expression =
815 format_hanging_expression_(ctx, expression, shape, expression_context, lhs_range);
816
817 Expression::UnaryOperator {
818 unop,
819 expression: Box::new(expression),
820 }
821 }
822 Expression::BinaryOperator { lhs, binop, rhs } => {
823 // Don't format the lhs and rhs here, because it will be handled later when hang_binop_expression calls back for a Value
824 let lhs =
825 hang_binop_expression(ctx, *lhs.to_owned(), binop.to_owned(), shape, lhs_range);
826
827 let current_shape = shape.take_last_line(&lhs) + 1; // 1 = space before binop
828 let mut new_binop = format_binop(ctx, binop, current_shape);
829
830 let singleline_shape = current_shape + strip_trivia(binop).to_string().len() + 1; // 1 = space after binop
831
832 let mut new_rhs = hang_binop_expression(
833 ctx,
834 *rhs.to_owned(),
835 binop.to_owned(),
836 singleline_shape,
837 None,
838 );
839
840 // Examine the last line to see if we need to hang this binop, or if the precedence levels match
841 if (did_hang_expression(&lhs) && binop_precedence_level(&lhs) >= binop.precedence())
842 || (did_hang_expression(&new_rhs)
843 && binop_precedence_level(&new_rhs) >= binop.precedence())
844 || contains_comments(binop)
845 || get_expression_trailing_trivia(&lhs)
846 .iter()
847 .any(trivia_util::trivia_is_comment)
848 || (shape.take_last_line(&lhs) + format!("{}{}", binop, rhs).len()).over_budget()
849 {
850 let hanging_shape = shape.reset() + strip_trivia(binop).to_string().len() + 1;
851 new_binop = hang_binop(ctx, binop.to_owned(), shape, rhs);
852 new_rhs = hang_binop_expression(
853 ctx,
854 *rhs.to_owned(),
855 binop.to_owned(),
856 hanging_shape,
857 None,
858 )
859 .update_leading_trivia(FormatTriviaType::Replace(Vec::new()));
860 }
861
862 Expression::BinaryOperator {
863 lhs: Box::new(lhs),
864 binop: new_binop,
865 rhs: Box::new(new_rhs),
866 }
867 }
868 other => panic!("unknown node {:?}", other),
869 }
870 }
871
hang_expression( ctx: &Context, expression: &Expression, shape: Shape, hang_level: Option<usize>, ) -> Expression872 pub fn hang_expression(
873 ctx: &Context,
874 expression: &Expression,
875 shape: Shape,
876 hang_level: Option<usize>,
877 ) -> Expression {
878 let original_additional_indent_level = shape.indent().additional_indent();
879 let shape = match hang_level {
880 Some(hang_level) => shape.with_indent(shape.indent().add_indent_level(hang_level)),
881 None => shape,
882 };
883
884 let lhs_range =
885 hang_level.map(|_| LeftmostRangeHang::find(expression, original_additional_indent_level));
886
887 format_hanging_expression_(
888 ctx,
889 expression,
890 shape,
891 ExpressionContext::Standard,
892 lhs_range,
893 )
894 }
895
hang_expression_trailing_newline( ctx: &Context, expression: &Expression, shape: Shape, hang_level: Option<usize>, ) -> Expression896 pub fn hang_expression_trailing_newline(
897 ctx: &Context,
898 expression: &Expression,
899 shape: Shape,
900 hang_level: Option<usize>,
901 ) -> Expression {
902 hang_expression(ctx, expression, shape, hang_level)
903 .update_trailing_trivia(FormatTriviaType::Append(vec![create_newline_trivia(ctx)]))
904 }
905