1 use crate::{
2     context::{create_indent_trivia, create_newline_trivia, Context},
3     fmt_op, fmt_symbol,
4     formatters::{
5         expression::{format_expression, format_var},
6         general::{
7             format_contained_span, format_end_token, format_punctuated,
8             format_punctuated_multiline, format_symbol, format_token_reference,
9             try_format_punctuated, EndTokenType,
10         },
11         table::{create_table_braces, format_multiline_table, format_singleline_table, TableType},
12         trivia::{
13             strip_leading_trivia, strip_trailing_trivia, strip_trivia, FormatTriviaType,
14             UpdateLeadingTrivia, UpdateTrailingTrivia,
15         },
16         trivia_util::{
17             contains_comments, token_trivia_contains_comments, trivia_is_comment,
18             trivia_is_newline, type_info_trailing_trivia,
19         },
20     },
21     shape::Shape,
22 };
23 use full_moon::ast::types::{
24     CompoundAssignment, CompoundOp, ExportedTypeDeclaration, GenericDeclaration, IndexedTypeInfo,
25     TypeArgument, TypeAssertion, TypeDeclaration, TypeField, TypeFieldKey, TypeInfo, TypeSpecifier,
26 };
27 use full_moon::ast::{punctuated::Punctuated, span::ContainedSpan};
28 use full_moon::tokenizer::{Token, TokenReference, TokenType};
29 use std::boxed::Box;
30 
format_compound_op(ctx: &Context, compound_op: &CompoundOp, shape: Shape) -> CompoundOp31 pub fn format_compound_op(ctx: &Context, compound_op: &CompoundOp, shape: Shape) -> CompoundOp {
32     fmt_op!(ctx, CompoundOp, compound_op, shape, {
33         PlusEqual = " += ",
34         MinusEqual = " -= ",
35         StarEqual = " *= ",
36         SlashEqual = " /= ",
37         PercentEqual = " %= ",
38         CaretEqual = " ^= ",
39         TwoDotsEqual = " ..= ",
40     })
41 }
42 
format_compound_assignment( ctx: &Context, compound_assignment: &CompoundAssignment, shape: Shape, ) -> CompoundAssignment43 pub fn format_compound_assignment(
44     ctx: &Context,
45     compound_assignment: &CompoundAssignment,
46     shape: Shape,
47 ) -> CompoundAssignment {
48     // Calculate trivia
49     let leading_trivia = vec![create_indent_trivia(ctx, shape)];
50     let trailing_trivia = vec![create_newline_trivia(ctx)];
51 
52     let lhs = format_var(ctx, compound_assignment.lhs(), shape)
53         .update_leading_trivia(FormatTriviaType::Append(leading_trivia));
54     let compound_operator = format_compound_op(ctx, compound_assignment.compound_operator(), shape);
55     let shape = shape
56         + (strip_leading_trivia(&lhs).to_string().len() + compound_operator.to_string().len());
57 
58     let rhs = format_expression(ctx, compound_assignment.rhs(), shape)
59         .update_trailing_trivia(FormatTriviaType::Append(trailing_trivia));
60 
61     CompoundAssignment::new(lhs, compound_operator, rhs)
62 }
63 
format_type_info(ctx: &Context, type_info: &TypeInfo, shape: Shape) -> TypeInfo64 pub fn format_type_info(ctx: &Context, type_info: &TypeInfo, shape: Shape) -> TypeInfo {
65     match type_info {
66         TypeInfo::Array { braces, type_info } => {
67             let (start_brace, end_brace) = braces.tokens().to_owned();
68             let braces = ContainedSpan::new(
69                 fmt_symbol!(ctx, start_brace, "{ ", shape),
70                 fmt_symbol!(ctx, end_brace, " }", shape),
71             );
72             let type_info = Box::new(format_type_info(ctx, type_info, shape + 2)); // 2 = "{ "
73 
74             TypeInfo::Array { braces, type_info }
75         }
76 
77         TypeInfo::Basic(token_reference) => {
78             let token_reference = format_token_reference(ctx, token_reference, shape);
79             TypeInfo::Basic(token_reference)
80         }
81 
82         TypeInfo::Callback {
83             parentheses,
84             arguments,
85             arrow,
86             return_type,
87         } => {
88             let (start_parens, end_parens) = parentheses.tokens();
89 
90             let force_multiline = token_trivia_contains_comments(start_parens.trailing_trivia())
91                 || token_trivia_contains_comments(end_parens.leading_trivia())
92                 || contains_comments(arguments)
93                 || shape
94                     .add_width(
95                         2 + 4
96                             + arguments.to_string().len()
97                             + strip_trailing_trivia(&**return_type).to_string().len(),
98                     )
99                     .over_budget(); // 2 = opening/closing parens, 4 = " -> "
100 
101             let (parentheses, arguments, shape) = if force_multiline {
102                 let start_parens = fmt_symbol!(ctx, start_parens, "(", shape)
103                     .update_trailing_trivia(FormatTriviaType::Append(vec![
104                         create_newline_trivia(ctx),
105                         create_indent_trivia(ctx, shape.increment_additional_indent()),
106                     ]));
107                 let end_parens =
108                     format_end_token(ctx, end_parens, EndTokenType::ClosingParens, shape)
109                         .update_leading_trivia(FormatTriviaType::Append(vec![
110                             create_newline_trivia(ctx),
111                             create_indent_trivia(ctx, shape),
112                         ]));
113 
114                 let parentheses = ContainedSpan::new(start_parens, end_parens);
115 
116                 let arguments = format_punctuated_multiline(
117                     ctx,
118                     arguments,
119                     shape.reset(),
120                     format_type_argument,
121                     Some(1),
122                 );
123 
124                 let shape = shape.reset() + 1; // 1 = ")"
125 
126                 (parentheses, arguments, shape)
127             } else {
128                 let parentheses = format_contained_span(ctx, parentheses, shape);
129                 let arguments = format_punctuated(ctx, arguments, shape + 1, format_type_argument);
130                 let shape = shape + (2 + arguments.to_string().len()); // 2 = opening and closing parens
131 
132                 (parentheses, arguments, shape)
133             };
134 
135             let arrow = fmt_symbol!(ctx, arrow, " -> ", shape);
136             let return_type = Box::new(format_type_info(ctx, return_type, shape));
137 
138             TypeInfo::Callback {
139                 parentheses,
140                 arguments,
141                 arrow,
142                 return_type,
143             }
144         }
145 
146         TypeInfo::Generic {
147             base,
148             arrows,
149             generics,
150         } => {
151             let base = format_token_reference(ctx, base, shape);
152             let arrows = format_contained_span(ctx, arrows, shape);
153             let generics = try_format_punctuated(
154                 ctx,
155                 generics,
156                 shape + (strip_trivia(&base).to_string().len() + 1), // 1 = "<"
157                 format_type_info,
158                 None,
159             );
160             TypeInfo::Generic {
161                 base,
162                 arrows,
163                 generics,
164             }
165         }
166 
167         TypeInfo::Intersection {
168             left,
169             ampersand,
170             right,
171         } => {
172             let left = Box::new(format_type_info(ctx, left, shape));
173             let ampersand = fmt_symbol!(ctx, ampersand, " & ", shape);
174             let right = Box::new(format_type_info(ctx, right, shape + 3)); // 3 = " & "
175             TypeInfo::Intersection {
176                 left,
177                 ampersand,
178                 right,
179             }
180         }
181 
182         TypeInfo::Module {
183             module,
184             punctuation,
185             type_info,
186         } => {
187             let module = format_token_reference(ctx, module, shape);
188             let punctuation = fmt_symbol!(ctx, punctuation, ".", shape);
189             let type_info = Box::new(format_indexed_type_info(
190                 ctx,
191                 type_info,
192                 shape + (strip_trivia(&module).to_string().len() + 1), // 1 = "."
193             ));
194             TypeInfo::Module {
195                 module,
196                 punctuation,
197                 type_info,
198             }
199         }
200 
201         TypeInfo::Optional {
202             base,
203             question_mark,
204         } => {
205             let base = Box::new(format_type_info(ctx, base, shape));
206             let question_mark = fmt_symbol!(ctx, question_mark, "?", shape);
207             TypeInfo::Optional {
208                 base,
209                 question_mark,
210             }
211         }
212 
213         TypeInfo::Table { braces, fields } => {
214             let (start_brace, end_brace) = braces.tokens().to_owned();
215             let contains_comments = start_brace.trailing_trivia().any(trivia_is_comment)
216                 || end_brace.leading_trivia().any(trivia_is_comment)
217                 || fields.pairs().any(|field| {
218                     contains_comments(field.punctuation()) || contains_comments(field.value())
219                 });
220 
221             let table_type = match (contains_comments, fields.iter().next()) {
222                 // Table contains comments, so force multiline
223                 (true, _) => TableType::MultiLine,
224 
225                 (false, Some(_)) => {
226                     let braces_range = (
227                         start_brace.token().end_position().bytes(),
228                         end_brace.token().start_position().bytes(),
229                     );
230 
231                     let singleline_shape = shape + (braces_range.1 - braces_range.0);
232 
233                     match singleline_shape.over_budget() {
234                         true => TableType::MultiLine,
235                         false => {
236                             // Determine if there was a new line at the end of the start brace
237                             // If so, then we should always be multiline
238                             if start_brace.trailing_trivia().any(trivia_is_newline) {
239                                 TableType::MultiLine
240                             } else {
241                                 TableType::SingleLine
242                             }
243                         }
244                     }
245                 }
246 
247                 (false, None) => TableType::Empty,
248             };
249 
250             let (braces, fields) = match table_type {
251                 TableType::Empty => {
252                     let braces =
253                         create_table_braces(ctx, start_brace, end_brace, table_type, shape);
254                     (braces, Punctuated::new())
255                 }
256                 TableType::SingleLine => {
257                     format_singleline_table(ctx, braces, fields, format_type_field, shape)
258                 }
259                 TableType::MultiLine => {
260                     format_multiline_table(ctx, braces, fields, format_type_field, shape)
261                 }
262             };
263 
264             TypeInfo::Table { braces, fields }
265         }
266 
267         TypeInfo::Typeof {
268             typeof_token,
269             parentheses,
270             inner,
271         } => {
272             let typeof_token = format_symbol(
273                 ctx,
274                 typeof_token,
275                 &TokenReference::new(
276                     vec![],
277                     Token::new(TokenType::Identifier {
278                         identifier: "typeof".into(),
279                     }),
280                     vec![],
281                 ),
282                 shape,
283             );
284             let shape = shape + 6; // 6 = "typeof"
285             let parentheses = format_contained_span(ctx, parentheses, shape);
286             let inner = Box::new(format_expression(ctx, inner, shape + 1)); // 1 = "("
287             TypeInfo::Typeof {
288                 typeof_token,
289                 parentheses,
290                 inner,
291             }
292         }
293 
294         TypeInfo::Tuple { parentheses, types } => {
295             let parentheses = format_contained_span(ctx, parentheses, shape);
296             let types = try_format_punctuated(ctx, types, shape + 1, format_type_info, None); // 1 = "("
297 
298             TypeInfo::Tuple { parentheses, types }
299         }
300 
301         TypeInfo::Union { left, pipe, right } => {
302             let left = Box::new(format_type_info(ctx, left, shape));
303             let pipe = fmt_symbol!(ctx, pipe, " | ", shape);
304             let right = Box::new(format_type_info(ctx, right, shape + 3)); // 3 = " | "
305 
306             TypeInfo::Union { left, pipe, right }
307         }
308 
309         TypeInfo::Variadic { ellipse, type_info } => {
310             let ellipse = fmt_symbol!(ctx, ellipse, "...", shape);
311             let type_info = Box::new(format_type_info(ctx, type_info, shape + 3)); // 3 = "..."
312 
313             TypeInfo::Variadic { ellipse, type_info }
314         }
315 
316         other => panic!("unknown node {:?}", other),
317     }
318 }
319 
hang_type_info(ctx: &Context, type_info: TypeInfo, shape: Shape) -> TypeInfo320 pub fn hang_type_info(ctx: &Context, type_info: TypeInfo, shape: Shape) -> TypeInfo {
321     match type_info {
322         TypeInfo::Union { left, pipe, right } => TypeInfo::Union {
323             left,
324             pipe: pipe.update_leading_trivia(FormatTriviaType::Replace(vec![
325                 create_newline_trivia(ctx),
326                 create_indent_trivia(ctx, shape),
327             ])),
328             right: Box::new(hang_type_info(ctx, *right, shape)),
329         },
330         _ => type_info,
331     }
332 }
333 
format_indexed_type_info( ctx: &Context, indexed_type_info: &IndexedTypeInfo, shape: Shape, ) -> IndexedTypeInfo334 pub fn format_indexed_type_info(
335     ctx: &Context,
336     indexed_type_info: &IndexedTypeInfo,
337     shape: Shape,
338 ) -> IndexedTypeInfo {
339     match indexed_type_info {
340         IndexedTypeInfo::Basic(token_reference) => {
341             IndexedTypeInfo::Basic(format_token_reference(ctx, token_reference, shape))
342         }
343 
344         IndexedTypeInfo::Generic {
345             base,
346             arrows,
347             generics,
348         } => {
349             let base = format_token_reference(ctx, base, shape);
350             let arrows = format_contained_span(ctx, arrows, shape);
351             let generics = try_format_punctuated(
352                 ctx,
353                 generics,
354                 shape + (strip_trivia(&base).to_string().len() + 1), // 1 = "<"
355                 format_type_info,
356                 None,
357             );
358 
359             IndexedTypeInfo::Generic {
360                 base,
361                 arrows,
362                 generics,
363             }
364         }
365 
366         other => panic!("unknown node {:?}", other),
367     }
368 }
369 
format_type_argument(ctx: &Context, type_argument: &TypeArgument, shape: Shape) -> TypeArgument370 fn format_type_argument(ctx: &Context, type_argument: &TypeArgument, shape: Shape) -> TypeArgument {
371     let name = match type_argument.name() {
372         Some((name, colon_token)) => {
373             let name = format_token_reference(ctx, name, shape);
374             let colon_token = fmt_symbol!(ctx, colon_token, ": ", shape);
375 
376             Some((name, colon_token))
377         }
378         None => None,
379     };
380 
381     let type_info = format_type_info(
382         ctx,
383         type_argument.type_info(),
384         shape
385             + name
386                 .as_ref()
387                 .map_or(0, |(name, _)| strip_trivia(name).to_string().len() + 2), // 2 = ": "
388     );
389 
390     type_argument
391         .to_owned()
392         .with_name(name)
393         .with_type_info(type_info)
394 }
395 
396 /// Formats a [`TypeField`] present inside of a [`TypeInfo::Table`]
397 /// Returns the new [`TypeField`] and any trailing trivia associated with its value (as this may need to later be moved).
398 /// If the [`TableType`] provided is [`TableType::MultiLine`] then the trailing trivia from the value will be removed.
format_type_field( ctx: &Context, type_field: &TypeField, table_type: TableType, shape: Shape, ) -> (TypeField, Vec<Token>)399 pub fn format_type_field(
400     ctx: &Context,
401     type_field: &TypeField,
402     table_type: TableType,
403     shape: Shape,
404 ) -> (TypeField, Vec<Token>) {
405     let leading_trivia = match table_type {
406         TableType::MultiLine => FormatTriviaType::Append(vec![create_indent_trivia(ctx, shape)]),
407         _ => FormatTriviaType::NoChange,
408     };
409 
410     let key = format_type_field_key(ctx, type_field.key(), leading_trivia, shape);
411     let colon_token = fmt_symbol!(ctx, type_field.colon_token(), ": ", shape);
412     let shape = shape + (strip_leading_trivia(&key).to_string().len() + 2);
413     let mut value = format_type_info(ctx, type_field.value(), shape);
414 
415     let trailing_trivia = type_info_trailing_trivia(&value);
416 
417     if let TableType::MultiLine = table_type {
418         value = value.update_trailing_trivia(FormatTriviaType::Replace(vec![]))
419     }
420 
421     (
422         type_field
423             .to_owned()
424             .with_key(key)
425             .with_colon_token(colon_token)
426             .with_value(value),
427         trailing_trivia,
428     )
429 }
430 
format_type_field_key( ctx: &Context, type_field_key: &TypeFieldKey, leading_trivia: FormatTriviaType, shape: Shape, ) -> TypeFieldKey431 pub fn format_type_field_key(
432     ctx: &Context,
433     type_field_key: &TypeFieldKey,
434     leading_trivia: FormatTriviaType,
435     shape: Shape,
436 ) -> TypeFieldKey {
437     match type_field_key {
438         TypeFieldKey::Name(token) => TypeFieldKey::Name(
439             format_token_reference(ctx, token, shape).update_leading_trivia(leading_trivia),
440         ),
441         TypeFieldKey::IndexSignature { brackets, inner } => TypeFieldKey::IndexSignature {
442             brackets: format_contained_span(ctx, brackets, shape)
443                 .update_leading_trivia(leading_trivia),
444             inner: format_type_info(ctx, inner, shape + 1), // 1 = "["
445         },
446         other => panic!("unknown node {:?}", other),
447     }
448 }
449 
format_type_assertion( ctx: &Context, type_assertion: &TypeAssertion, shape: Shape, ) -> TypeAssertion450 pub fn format_type_assertion(
451     ctx: &Context,
452     type_assertion: &TypeAssertion,
453     shape: Shape,
454 ) -> TypeAssertion {
455     let assertion_op = fmt_symbol!(ctx, type_assertion.assertion_op(), " :: ", shape);
456     let cast_to = format_type_info(ctx, type_assertion.cast_to(), shape + 4); // 4 = " :: "
457 
458     TypeAssertion::new(cast_to).with_assertion_op(assertion_op)
459 }
460 
format_type_declaration( ctx: &Context, type_declaration: &TypeDeclaration, add_leading_trivia: bool, shape: Shape, ) -> TypeDeclaration461 fn format_type_declaration(
462     ctx: &Context,
463     type_declaration: &TypeDeclaration,
464     add_leading_trivia: bool,
465     shape: Shape,
466 ) -> TypeDeclaration {
467     // Calculate trivia
468     let trailing_trivia = vec![create_newline_trivia(ctx)];
469 
470     let mut type_token = format_symbol(
471         ctx,
472         type_declaration.type_token(),
473         &TokenReference::new(
474             vec![],
475             Token::new(TokenType::Identifier {
476                 identifier: "type".into(),
477             }),
478             vec![Token::new(TokenType::spaces(1))],
479         ),
480         shape,
481     );
482 
483     if add_leading_trivia {
484         let leading_trivia = vec![create_indent_trivia(ctx, shape)];
485         type_token = type_token.update_leading_trivia(FormatTriviaType::Append(leading_trivia))
486     }
487 
488     let shape = shape + 5; // 5 = "type "
489     let type_name = format_token_reference(ctx, type_declaration.type_name(), shape);
490     let shape = shape + type_name.to_string().len();
491 
492     let generics = type_declaration
493         .generics()
494         .map(|generics| format_generic_declaration(ctx, generics, shape));
495 
496     let shape = match generics {
497         Some(ref generics) => shape.take_last_line(&generics),
498         None => shape,
499     };
500 
501     let mut equal_token = fmt_symbol!(ctx, type_declaration.equal_token(), " = ", shape);
502     let mut type_definition =
503         format_type_info(ctx, type_declaration.type_definition(), shape + 3) // 3 = " = "
504             .update_trailing_trivia(FormatTriviaType::Append(trailing_trivia));
505 
506     let shape = shape.take_last_line(&strip_trailing_trivia(&type_definition));
507 
508     if shape.over_budget() {
509         let shape = shape.increment_additional_indent();
510         equal_token = equal_token.update_trailing_trivia(FormatTriviaType::Replace(vec![
511             create_newline_trivia(ctx),
512             create_indent_trivia(ctx, shape),
513         ]));
514         type_definition = hang_type_info(ctx, type_definition, shape);
515     }
516 
517     type_declaration
518         .to_owned()
519         .with_type_token(type_token)
520         .with_type_name(type_name)
521         .with_generics(generics)
522         .with_equal_token(equal_token)
523         .with_type_definition(type_definition)
524 }
525 
526 /// Wrapper around `format_type_declaration` for statements
527 /// This is required as `format_type_declaration` is also used for ExportedTypeDeclaration, and we don't want leading trivia there
format_type_declaration_stmt( ctx: &Context, type_declaration: &TypeDeclaration, shape: Shape, ) -> TypeDeclaration528 pub fn format_type_declaration_stmt(
529     ctx: &Context,
530     type_declaration: &TypeDeclaration,
531     shape: Shape,
532 ) -> TypeDeclaration {
533     format_type_declaration(ctx, type_declaration, true, shape)
534 }
535 
format_generic_declaration( ctx: &Context, generic_declaration: &GenericDeclaration, shape: Shape, ) -> GenericDeclaration536 pub fn format_generic_declaration(
537     ctx: &Context,
538     generic_declaration: &GenericDeclaration,
539     shape: Shape,
540 ) -> GenericDeclaration {
541     // If the generics contains comments, then format multiline
542     let (arrows, generics) = if contains_comments(generic_declaration.generics()) {
543         let (start_arrow, end_arrow) = generic_declaration.arrows().tokens();
544 
545         // Format start and end arrows properly with correct trivia
546         let end_arrow_leading_trivia =
547             vec![create_newline_trivia(ctx), create_indent_trivia(ctx, shape)];
548 
549         // Add new_line trivia to start arrow
550         let start_arrow_token = fmt_symbol!(ctx, start_arrow, "<", shape)
551             .update_trailing_trivia(FormatTriviaType::Append(vec![create_newline_trivia(ctx)]));
552 
553         let end_arrow_token = format_end_token(ctx, end_arrow, EndTokenType::ClosingBrace, shape)
554             .update_leading_trivia(FormatTriviaType::Append(end_arrow_leading_trivia));
555 
556         let arrows = ContainedSpan::new(start_arrow_token, end_arrow_token);
557 
558         let shape = shape.reset().increment_additional_indent();
559         let generics = try_format_punctuated(
560             ctx,
561             generic_declaration.generics(),
562             shape,
563             format_token_reference,
564             None,
565         )
566         .update_leading_trivia(FormatTriviaType::Append(vec![create_indent_trivia(
567             ctx, shape,
568         )]));
569 
570         (arrows, generics)
571     } else {
572         (
573             format_contained_span(ctx, generic_declaration.arrows(), shape),
574             try_format_punctuated(
575                 ctx,
576                 generic_declaration.generics(),
577                 shape,
578                 format_token_reference,
579                 None,
580             ),
581         )
582     };
583 
584     generic_declaration
585         .to_owned()
586         .with_arrows(arrows)
587         .with_generics(generics)
588 }
589 
format_type_specifier( ctx: &Context, type_specifier: &TypeSpecifier, shape: Shape, ) -> TypeSpecifier590 pub fn format_type_specifier(
591     ctx: &Context,
592     type_specifier: &TypeSpecifier,
593     shape: Shape,
594 ) -> TypeSpecifier {
595     let punctuation = fmt_symbol!(ctx, type_specifier.punctuation(), ": ", shape);
596     let type_info = format_type_info(ctx, type_specifier.type_info(), shape + 2); // 2 = ": "
597 
598     type_specifier
599         .to_owned()
600         .with_punctuation(punctuation)
601         .with_type_info(type_info)
602 }
603 
format_exported_type_declaration( ctx: &Context, exported_type_declaration: &ExportedTypeDeclaration, shape: Shape, ) -> ExportedTypeDeclaration604 pub fn format_exported_type_declaration(
605     ctx: &Context,
606     exported_type_declaration: &ExportedTypeDeclaration,
607     shape: Shape,
608 ) -> ExportedTypeDeclaration {
609     // Calculate trivia
610     let shape = shape.reset();
611     let leading_trivia = vec![create_indent_trivia(ctx, shape)];
612 
613     let export_token = format_symbol(
614         ctx,
615         exported_type_declaration.export_token(),
616         &TokenReference::new(
617             vec![],
618             Token::new(TokenType::Identifier {
619                 identifier: "export".into(),
620             }),
621             vec![Token::new(TokenType::spaces(1))],
622         ),
623         shape,
624     )
625     .update_leading_trivia(FormatTriviaType::Append(leading_trivia));
626     let type_declaration = format_type_declaration(
627         ctx,
628         exported_type_declaration.type_declaration(),
629         false,
630         shape + 7, // 7 = "export "
631     );
632 
633     exported_type_declaration
634         .to_owned()
635         .with_export_token(export_token)
636         .with_type_declaration(type_declaration)
637 }
638