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