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