1 use crate::{
2     context::{create_indent_trivia, create_newline_trivia, Context},
3     fmt_symbol,
4     formatters::{
5         expression::{format_expression, hang_expression},
6         general::{format_contained_span, format_end_token, format_token_reference, EndTokenType},
7         trivia::{strip_trivia, FormatTriviaType, UpdateLeadingTrivia, UpdateTrailingTrivia},
8         trivia_util,
9     },
10     shape::Shape,
11 };
12 use full_moon::ast::{
13     punctuated::{Pair, Punctuated},
14     span::ContainedSpan,
15     Expression, Field, TableConstructor, Value,
16 };
17 use full_moon::tokenizer::{Token, TokenReference};
18 
19 /// Used to provide information about the table
20 #[derive(Debug, Clone, Copy)]
21 pub enum TableType {
22     /// The table will have multline fields
23     MultiLine,
24     /// The table will be on a single line
25     SingleLine,
26     /// The table has no fields
27     Empty,
28 }
29 
format_field( ctx: &Context, field: &Field, table_type: TableType, shape: Shape, ) -> (Field, Vec<Token>)30 fn format_field(
31     ctx: &Context,
32     field: &Field,
33     table_type: TableType,
34     shape: Shape,
35 ) -> (Field, Vec<Token>) {
36     let leading_trivia = match table_type {
37         TableType::MultiLine => FormatTriviaType::Append(vec![create_indent_trivia(ctx, shape)]),
38         _ => FormatTriviaType::NoChange,
39     };
40 
41     let trailing_trivia;
42     let field = match field {
43         Field::ExpressionKey {
44             brackets,
45             key,
46             equal,
47             value,
48         } => {
49             trailing_trivia = trivia_util::get_expression_trailing_trivia(value);
50             let brackets =
51                 format_contained_span(ctx, brackets, shape).update_leading_trivia(leading_trivia);
52             let key = format_expression(ctx, key, shape + 1); // 1 = opening bracket
53             let equal = fmt_symbol!(ctx, equal, " = ", shape);
54             let shape = shape.take_last_line(&key) + (2 + 3); // 2 = brackets, 3 = " = "
55 
56             let singleline_value = format_expression(ctx, value, shape)
57                 .update_trailing_trivia(FormatTriviaType::Replace(vec![])); // We will remove all the trivia from this value, and place it after the comma
58 
59             let value = if trivia_util::can_hang_expression(value)
60                 && shape.take_first_line(&singleline_value).over_budget()
61             {
62                 hang_expression(ctx, value, shape, Some(1))
63                     .update_trailing_trivia(FormatTriviaType::Replace(vec![]))
64             } else {
65                 singleline_value
66             };
67 
68             Field::ExpressionKey {
69                 brackets,
70                 key,
71                 equal,
72                 value,
73             }
74         }
75         Field::NameKey { key, equal, value } => {
76             trailing_trivia = trivia_util::get_expression_trailing_trivia(value);
77             let key = format_token_reference(ctx, key, shape).update_leading_trivia(leading_trivia);
78             let equal = fmt_symbol!(ctx, equal, " = ", shape);
79             let shape = shape + (strip_trivia(&key).to_string().len() + 3); // 3 = " = "
80 
81             let singleline_value = format_expression(ctx, value, shape)
82                 .update_trailing_trivia(FormatTriviaType::Replace(vec![])); // We will remove all the trivia from this value, and place it after the comma
83 
84             let value = if trivia_util::can_hang_expression(value)
85                 && shape.take_first_line(&singleline_value).over_budget()
86             {
87                 hang_expression(ctx, value, shape, Some(1))
88                     .update_trailing_trivia(FormatTriviaType::Replace(vec![]))
89             } else {
90                 singleline_value
91             };
92 
93             Field::NameKey { key, equal, value }
94         }
95         Field::NoKey(expression) => {
96             trailing_trivia = trivia_util::get_expression_trailing_trivia(expression);
97             let formatted_expression = format_expression(ctx, expression, shape);
98 
99             if let TableType::MultiLine = table_type {
100                 // If still over budget, hang the expression
101                 let formatted_expression = if trivia_util::can_hang_expression(expression)
102                     && shape.take_first_line(&formatted_expression).over_budget()
103                 {
104                     hang_expression(ctx, expression, shape, Some(1))
105                 } else {
106                     formatted_expression
107                 };
108 
109                 Field::NoKey(
110                     formatted_expression
111                         .update_leading_trivia(leading_trivia)
112                         .update_trailing_trivia(FormatTriviaType::Replace(vec![])),
113                 )
114             } else {
115                 Field::NoKey(formatted_expression)
116             }
117         }
118 
119         other => panic!("unknown node {:?}", other),
120     };
121 
122     (field, trailing_trivia)
123 }
124 
create_table_braces( ctx: &Context, start_brace: &TokenReference, end_brace: &TokenReference, table_type: TableType, shape: Shape, ) -> ContainedSpan125 pub fn create_table_braces(
126     ctx: &Context,
127     start_brace: &TokenReference,
128     end_brace: &TokenReference,
129     table_type: TableType,
130     shape: Shape,
131 ) -> ContainedSpan {
132     match table_type {
133         TableType::MultiLine => {
134             // Format start and end brace properly with correct trivia
135             let end_brace_leading_trivia = vec![create_indent_trivia(ctx, shape)];
136 
137             // Add new_line trivia to start_brace
138             let start_brace_token = fmt_symbol!(ctx, start_brace, "{", shape)
139                 .update_trailing_trivia(FormatTriviaType::Append(vec![create_newline_trivia(ctx)]));
140 
141             let end_brace_token =
142                 format_end_token(ctx, end_brace, EndTokenType::ClosingBrace, shape)
143                     .update_leading_trivia(FormatTriviaType::Append(end_brace_leading_trivia));
144 
145             ContainedSpan::new(start_brace_token, end_brace_token)
146         }
147 
148         TableType::SingleLine => ContainedSpan::new(
149             fmt_symbol!(ctx, start_brace, "{ ", shape),
150             fmt_symbol!(ctx, end_brace, " }", shape),
151         ),
152 
153         TableType::Empty => {
154             let start_brace = fmt_symbol!(ctx, start_brace, "{", shape);
155             let end_brace = fmt_symbol!(ctx, end_brace, "}", shape);
156             // Remove any newline trivia trailing the start brace, as it shouldn't be present
157             let start_brace_trailing_trivia = start_brace
158                 .trailing_trivia()
159                 .filter(|t| !trivia_util::trivia_is_newline(t))
160                 .map(|x| x.to_owned())
161                 .collect();
162             // Remove any newline trivia leading the end brace, as it shouldn't be present
163             let end_brace_leading_trivia = end_brace
164                 .leading_trivia()
165                 .filter(|t| !trivia_util::trivia_is_newline(t))
166                 .map(|x| x.to_owned())
167                 .collect();
168 
169             ContainedSpan::new(
170                 start_brace
171                     .update_trailing_trivia(FormatTriviaType::Replace(start_brace_trailing_trivia)),
172                 end_brace
173                     .update_leading_trivia(FormatTriviaType::Replace(end_brace_leading_trivia)),
174             )
175         }
176     }
177 }
178 
179 /// Formats a table onto a single line.
180 /// Takes in a [`ContainedSpan`] representing the braces, and the fields within the table.
181 /// This function is generic to support [`TableConstructor`] and [`TypeInfo::Table`] in Luau.
182 /// This function does not perform any length checking, or checking whether comments are present.
format_singleline_table<T, U>( ctx: &Context, braces: &ContainedSpan, fields: &Punctuated<T>, formatter: U, shape: Shape, ) -> (ContainedSpan, Punctuated<T>) where T: std::fmt::Display, U: Fn(&Context, &T, TableType, Shape) -> (T, Vec<Token>),183 pub fn format_singleline_table<T, U>(
184     ctx: &Context,
185     braces: &ContainedSpan,
186     fields: &Punctuated<T>,
187     formatter: U,
188     shape: Shape,
189 ) -> (ContainedSpan, Punctuated<T>)
190 where
191     T: std::fmt::Display,
192     U: Fn(&Context, &T, TableType, Shape) -> (T, Vec<Token>),
193 {
194     let table_type = TableType::SingleLine;
195 
196     let (start_brace, end_brace) = braces.tokens();
197     let braces = create_table_braces(ctx, start_brace, end_brace, table_type, shape);
198     let mut shape = shape + 2; // 2 = "{ "
199 
200     let mut current_fields = fields.pairs().peekable();
201     let mut fields = Punctuated::new();
202 
203     while let Some(pair) = current_fields.next() {
204         let (field, punctuation) = (pair.value(), pair.punctuation());
205 
206         // Format the field. We will ignore the taken trailing trivia, as we do not need it.
207         // (If there were any comments present, this function should never have been called)
208         let formatted_field = formatter(ctx, field, table_type, shape).0;
209 
210         let formatted_punctuation = match current_fields.peek() {
211             Some(_) => {
212                 // Have more elements still to go
213                 shape = shape + (formatted_field.to_string().len() + 2); // 2 = ", "
214                 match punctuation {
215                     Some(punctuation) => Some(fmt_symbol!(ctx, punctuation, ", ", shape)),
216                     None => Some(TokenReference::symbol(", ").unwrap()),
217                 }
218             }
219             None => None,
220         };
221 
222         fields.push(Pair::new(formatted_field, formatted_punctuation))
223     }
224 
225     (braces, fields)
226 }
227 
228 /// Expands a table's fields to format it onto multiple lines
229 /// Takes in a [`ContainedSpan`] representing the braces, and the fields within the table.
230 /// This function is generic to support [`TableConstructor`] and [`TypeInfo::Table`] in Luau.
231 /// This function does not perform any length checking.
format_multiline_table<T, U>( ctx: &Context, braces: &ContainedSpan, fields: &Punctuated<T>, formatter: U, shape: Shape, ) -> (ContainedSpan, Punctuated<T>) where T: std::fmt::Display, U: Fn(&Context, &T, TableType, Shape) -> (T, Vec<Token>),232 pub fn format_multiline_table<T, U>(
233     ctx: &Context,
234     braces: &ContainedSpan,
235     fields: &Punctuated<T>,
236     formatter: U,
237     shape: Shape,
238 ) -> (ContainedSpan, Punctuated<T>)
239 where
240     T: std::fmt::Display,
241     U: Fn(&Context, &T, TableType, Shape) -> (T, Vec<Token>),
242 {
243     let table_type = TableType::MultiLine;
244 
245     let (start_brace, end_brace) = braces.tokens();
246     let braces = create_table_braces(ctx, start_brace, end_brace, table_type, shape);
247     let mut shape = shape.reset().increment_additional_indent(); // Will take new line, and additional indentation
248 
249     let current_fields = fields.pairs();
250     let mut fields = Punctuated::new();
251 
252     for pair in current_fields {
253         let (field, punctuation) = (pair.value(), pair.punctuation());
254 
255         // Reset the shape onto a new line, as we are a new field
256         shape = shape.reset();
257 
258         // Format the field
259         let (formatted_field, mut trailing_trivia) = formatter(ctx, field, table_type, shape);
260 
261         // If trivia is just whitespace, ignore it completely
262         if trailing_trivia
263             .iter()
264             .all(trivia_util::trivia_is_whitespace)
265         {
266             trailing_trivia = Vec::new();
267         } else {
268             // Filter trailing trivia for any newlines
269             trailing_trivia = trailing_trivia
270                 .iter()
271                 .filter(|x| !trivia_util::trivia_is_newline(x))
272                 .map(|x| x.to_owned())
273                 .collect();
274         }
275 
276         // Continue adding a comma and a new line for multiline tables
277         // Add newline trivia to the end of the symbol
278         trailing_trivia.push(create_newline_trivia(ctx));
279 
280         let symbol = match punctuation {
281             Some(punctuation) => fmt_symbol!(ctx, punctuation, ",", shape),
282             None => TokenReference::symbol(",").unwrap(),
283         }
284         .update_trailing_trivia(FormatTriviaType::Append(trailing_trivia));
285         let formatted_punctuation = Some(symbol);
286 
287         fields.push(Pair::new(formatted_field, formatted_punctuation))
288     }
289 
290     (braces, fields)
291 }
292 
expression_is_multiline_function(expression: &Expression) -> bool293 fn expression_is_multiline_function(expression: &Expression) -> bool {
294     if let Expression::Value { value, .. } = expression {
295         if let Value::Function((_, function_body)) = &**value {
296             return !trivia_util::is_function_empty(function_body);
297         }
298     }
299     false
300 }
301 
302 /// Examines the fields of a table constructor to see if we should force the table constructor multiline.
303 /// This will only happen if either:
304 ///  1) There are comments within the table
305 ///  2) There are anonymous functions defined within the table [As these will expand multiline]
should_expand(table_constructor: &TableConstructor) -> bool306 fn should_expand(table_constructor: &TableConstructor) -> bool {
307     let (start_brace, end_brace) = table_constructor.braces().tokens();
308     let contains_comments = start_brace
309         .trailing_trivia()
310         .any(trivia_util::trivia_is_comment)
311         || end_brace
312             .leading_trivia()
313             .any(trivia_util::trivia_is_comment)
314         || trivia_util::table_fields_contains_comments(table_constructor);
315 
316     if contains_comments {
317         true
318     } else {
319         for field in table_constructor.fields() {
320             let should_expand = match field {
321                 Field::ExpressionKey { key, value, .. } => {
322                     expression_is_multiline_function(key) || expression_is_multiline_function(value)
323                 }
324                 Field::NameKey { value, .. } => expression_is_multiline_function(value),
325                 Field::NoKey(expression) => expression_is_multiline_function(expression),
326                 other => panic!("unknown node {:?}", other),
327             };
328 
329             if should_expand {
330                 return true;
331             }
332         }
333         false
334     }
335 }
336 
format_table_constructor( ctx: &Context, table_constructor: &TableConstructor, shape: Shape, ) -> TableConstructor337 pub fn format_table_constructor(
338     ctx: &Context,
339     table_constructor: &TableConstructor,
340     shape: Shape,
341 ) -> TableConstructor {
342     let (start_brace, end_brace) = table_constructor.braces().tokens();
343 
344     // Determine if we need to force the table multiline
345     let should_expand = should_expand(table_constructor);
346 
347     let table_type = match (should_expand, table_constructor.fields().iter().next()) {
348         // We should expand, so force multiline
349         (true, _) => TableType::MultiLine,
350 
351         (false, Some(_)) => {
352             // Compare the difference between the position of the start brace and the end brace to
353             // guess how long the table is. This heuristic is very naiive, since it relies on the input.
354             // If the input is badly formatted (e.g. lots of spaces in the table), then it would flag this over width.
355             // However, this is currently our best solution: attempting to format the input onto a single line to
356             // see if we are over width (both completely and in a fail-fast shape.over_budget() check) leads to
357             // exponential time complexity with respect to how deep the table is.
358             // TODO: find an improved heuristic whilst comparing against benchmarks
359             let braces_range = (
360                 start_brace.token().end_position().bytes(),
361                 end_brace.token().start_position().bytes(),
362             );
363             let singleline_shape = shape + (braces_range.1 - braces_range.0) + 4; // 4 = two braces + single space before/after them
364 
365             match singleline_shape.over_budget() {
366                 true => TableType::MultiLine,
367                 false => {
368                     // Determine if there was a new line at the end of the start brace
369                     // If so, then we should always be multiline
370                     if start_brace
371                         .trailing_trivia()
372                         .any(trivia_util::trivia_is_newline)
373                     {
374                         TableType::MultiLine
375                     } else {
376                         TableType::SingleLine
377                     }
378                 }
379             }
380         }
381 
382         (false, None) => TableType::Empty,
383     };
384 
385     let (braces, fields) = match table_type {
386         TableType::Empty => {
387             let braces = create_table_braces(ctx, start_brace, end_brace, table_type, shape);
388             (braces, Punctuated::new())
389         }
390         TableType::SingleLine => format_singleline_table(
391             ctx,
392             table_constructor.braces(),
393             table_constructor.fields(),
394             format_field,
395             shape,
396         ),
397         TableType::MultiLine => format_multiline_table(
398             ctx,
399             table_constructor.braces(),
400             table_constructor.fields(),
401             format_field,
402             shape,
403         ),
404     };
405 
406     TableConstructor::new()
407         .with_braces(braces)
408         .with_fields(fields)
409 }
410