1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
3
4use super::writer::TokenWriter;
5use i_slint_compiler::parser::{syntax_nodes, NodeOrToken, SyntaxKind, SyntaxNode};
6
7pub fn format_document(
8 doc: syntax_nodes::Document,
9 writer: &mut impl TokenWriter,
10) -> Result<(), std::io::Error> {
11 let mut state: FormatState = FormatState::default();
12 format_node(&doc, writer, &mut state)
13}
14
15#[derive(Default)]
16struct FormatState {
17 /// The whitespace have been written, all further whitespace can be skipped
18 skip_all_whitespace: bool,
19 /// The whitespace to add before the next token
20 whitespace_to_add: Option<String>,
21 /// The level of indentation
22 indentation_level: u32,
23
24 /// A counter that is incremented when something is inserted
25 insertion_count: usize,
26
27 /// a comment has been written followed maybe by some spacing
28 after_comment: bool,
29}
30
31impl FormatState {
32 fn new_line(&mut self) {
33 if self.after_comment {
34 return;
35 }
36 self.skip_all_whitespace = true;
37 if let Some(x) = &mut self.whitespace_to_add {
38 x.insert(0, '\n');
39 return;
40 }
41 let mut new_line = String::from("\n");
42 for _ in 0..self.indentation_level {
43 new_line += " ";
44 }
45 self.whitespace_to_add = Some(new_line);
46 }
47
48 fn insert_whitespace(&mut self, arg: &str) {
49 if self.after_comment {
50 return;
51 }
52 self.skip_all_whitespace = true;
53 if !arg.is_empty() {
54 if let Some(ws) = &mut self.whitespace_to_add {
55 *ws += arg;
56 } else {
57 self.whitespace_to_add = Some(arg.into());
58 }
59 }
60 }
61}
62
63fn format_node(
64 node: &SyntaxNode,
65 writer: &mut impl TokenWriter,
66 state: &mut FormatState,
67) -> Result<(), std::io::Error> {
68 match node.kind() {
69 SyntaxKind::Component => {
70 return format_component(node, writer, state);
71 }
72 SyntaxKind::Element => {
73 return format_element(node, writer, state);
74 }
75 SyntaxKind::SubElement => {
76 return format_sub_element(node, writer, state);
77 }
78 SyntaxKind::PropertyDeclaration => {
79 return format_property_declaration(node, writer, state);
80 }
81 SyntaxKind::Binding => {
82 return format_binding(node, writer, state);
83 }
84 SyntaxKind::TwoWayBinding => {
85 return format_two_way_binding(node, writer, state);
86 }
87 SyntaxKind::CallbackConnection => {
88 return format_callback_connection(node, writer, state);
89 }
90 SyntaxKind::CallbackDeclaration => {
91 return format_callback_declaration(node, writer, state);
92 }
93 SyntaxKind::Function => {
94 return format_function(node, writer, state);
95 }
96 SyntaxKind::QualifiedName => {
97 return format_qualified_name(node, writer, state);
98 }
99 SyntaxKind::SelfAssignment | SyntaxKind::BinaryExpression => {
100 return format_binary_expression(node, writer, state);
101 }
102 SyntaxKind::ConditionalExpression => {
103 return format_conditional_expression(node, writer, state);
104 }
105 SyntaxKind::Expression => {
106 return format_expression(node, writer, state);
107 }
108 SyntaxKind::CodeBlock => {
109 return format_codeblock(node, writer, state);
110 }
111 SyntaxKind::ReturnStatement => {
112 return format_return_statement(node, writer, state);
113 }
114 SyntaxKind::AtGradient => {
115 return format_at_gradient(node, writer, state);
116 }
117 SyntaxKind::ChildrenPlaceholder => {
118 return format_children_placeholder(node, writer, state);
119 }
120 SyntaxKind::RepeatedElement => {
121 return format_repeated_element(node, writer, state);
122 }
123 SyntaxKind::RepeatedIndex => {
124 return format_repeated_index(node, writer, state);
125 }
126 SyntaxKind::Array => {
127 return format_array(node, writer, state);
128 }
129 SyntaxKind::State => {
130 return format_state(node, writer, state);
131 }
132 SyntaxKind::States => {
133 return format_states(node, writer, state);
134 }
135 SyntaxKind::StatePropertyChange => {
136 return format_state_prop_change(node, writer, state);
137 }
138 SyntaxKind::Transition => {
139 return format_transition(node, writer, state);
140 }
141 SyntaxKind::PropertyAnimation => {
142 return format_property_animation(node, writer, state);
143 }
144 SyntaxKind::ObjectLiteral => {
145 return format_object_literal(node, writer, state);
146 }
147 _ => (),
148 }
149
150 for n in node.children_with_tokens() {
151 fold(n, writer, state)?;
152 }
153 Ok(())
154}
155
156fn fold(
157 n: NodeOrToken,
158 writer: &mut impl TokenWriter,
159 state: &mut FormatState,
160) -> std::io::Result<()> {
161 match n {
162 NodeOrToken::Node(n) => format_node(&n, writer, state),
163 NodeOrToken::Token(t) => {
164 if t.kind() == SyntaxKind::Eof {
165 if state.skip_all_whitespace {
166 writer.with_new_content(t, "\n")?;
167 }
168 return Ok(());
169 } else if t.kind() == SyntaxKind::Whitespace {
170 if state.skip_all_whitespace && !state.after_comment {
171 writer.with_new_content(t, "")?;
172 return Ok(());
173 }
174 } else {
175 state.after_comment = t.kind() == SyntaxKind::Comment;
176 state.skip_all_whitespace = false;
177 if let Some(x) = state.whitespace_to_add.take() {
178 state.insertion_count += 1;
179 writer.insert_before(t, x.as_ref())?;
180 return Ok(());
181 }
182 }
183 state.insertion_count += 1;
184 writer.no_change(t)
185 }
186 }
187}
188
189#[derive(Clone, Copy, PartialEq, Eq)]
190enum SyntaxMatch {
191 NotFound,
192 Found(SyntaxKind),
193}
194
195impl SyntaxMatch {
196 fn is_found(self) -> bool {
197 matches!(self, SyntaxMatch::Found(..))
198 }
199}
200
201fn whitespace_to(
202 sub: &mut impl Iterator<Item = NodeOrToken>,
203 element: SyntaxKind,
204 writer: &mut impl TokenWriter,
205 state: &mut FormatState,
206 prefix_whitespace: &str,
207) -> Result<bool, std::io::Error> {
208 whitespace_to_one_of(sub, &[element], writer, state, prefix_whitespace)
209 .map(op:SyntaxMatch::is_found)
210}
211
212fn whitespace_to_one_of(
213 sub: &mut impl Iterator<Item = NodeOrToken>,
214 elements: &[SyntaxKind],
215 writer: &mut impl TokenWriter,
216 state: &mut FormatState,
217 prefix_whitespace: &str,
218) -> Result<SyntaxMatch, std::io::Error> {
219 state.insert_whitespace(arg:prefix_whitespace);
220 for n: NodeOrToken in sub {
221 match n.kind() {
222 SyntaxKind::Whitespace | SyntaxKind::Comment => (),
223 expected_kind: SyntaxKind if elements.contains(&expected_kind) => {
224 fold(n, writer, state)?;
225 return Ok(SyntaxMatch::Found(expected_kind));
226 }
227 _ => {
228 eprintln!("Inconsistency: expected {:?}, found {:?}", elements, n);
229 fold(n, writer, state)?;
230 return Ok(SyntaxMatch::NotFound);
231 }
232 }
233 fold(n, writer, state)?;
234 }
235 eprintln!("Inconsistency: expected {:?}, not found", elements);
236 Ok(SyntaxMatch::NotFound)
237}
238
239fn finish_node(
240 sub: impl Iterator<Item = NodeOrToken>,
241 writer: &mut impl TokenWriter,
242 state: &mut FormatState,
243) -> Result<bool, std::io::Error> {
244 // FIXME: We should check that there are only comments or whitespace in sub
245 for n: NodeOrToken in sub {
246 fold(n, writer, state)?;
247 }
248 Ok(true)
249}
250
251fn format_component(
252 node: &SyntaxNode,
253 writer: &mut impl TokenWriter,
254 state: &mut FormatState,
255) -> Result<(), std::io::Error> {
256 if node.child_token(SyntaxKind::ColonEqual).is_some() {
257 // Legacy syntax
258 let mut sub = node.children_with_tokens();
259 let _ok = whitespace_to(&mut sub, SyntaxKind::DeclaredIdentifier, writer, state, "")?
260 && whitespace_to(&mut sub, SyntaxKind::ColonEqual, writer, state, " ")?
261 && whitespace_to(&mut sub, SyntaxKind::Element, writer, state, " ")?;
262
263 finish_node(sub, writer, state)?;
264 state.new_line();
265 } else {
266 let mut sub = node.children_with_tokens();
267 let _ok = whitespace_to(&mut sub, SyntaxKind::Identifier, writer, state, "")?
268 && whitespace_to(&mut sub, SyntaxKind::DeclaredIdentifier, writer, state, " ")?;
269 let r = whitespace_to_one_of(
270 &mut sub,
271 &[SyntaxKind::Identifier, SyntaxKind::Element],
272 writer,
273 state,
274 " ",
275 )?;
276 if r == SyntaxMatch::Found(SyntaxKind::Identifier) {
277 whitespace_to(&mut sub, SyntaxKind::Element, writer, state, " ")?;
278 }
279
280 finish_node(sub, writer, state)?;
281 state.new_line();
282 }
283
284 Ok(())
285}
286
287fn format_element(
288 node: &SyntaxNode,
289 writer: &mut impl TokenWriter,
290 state: &mut FormatState,
291) -> Result<(), std::io::Error> {
292 let mut sub = node.children_with_tokens();
293
294 let ok = if node.child_node(SyntaxKind::QualifiedName).is_some() {
295 whitespace_to(&mut sub, SyntaxKind::QualifiedName, writer, state, "")?
296 && whitespace_to(&mut sub, SyntaxKind::LBrace, writer, state, " ")?
297 } else {
298 whitespace_to(&mut sub, SyntaxKind::LBrace, writer, state, "")?
299 };
300
301 if !ok {
302 finish_node(sub, writer, state)?;
303 return Ok(());
304 }
305
306 state.indentation_level += 1;
307 state.new_line();
308 let ins_ctn = state.insertion_count;
309 let mut inserted_newline = false;
310
311 for n in sub {
312 if n.kind() == SyntaxKind::Whitespace {
313 let is_empty_line = n.as_token().map(|n| n.text().contains("\n\n")).unwrap_or(false);
314 if is_empty_line && !inserted_newline {
315 state.new_line();
316 }
317 }
318 inserted_newline = false;
319
320 if n.kind() == SyntaxKind::RBrace {
321 state.indentation_level -= 1;
322 state.whitespace_to_add = None;
323 if ins_ctn == state.insertion_count {
324 state.insert_whitespace(" ");
325 } else {
326 state.new_line();
327 }
328 fold(n, writer, state)?;
329 state.new_line();
330 } else {
331 let put_newline_after = n.kind() == SyntaxKind::SubElement;
332
333 fold(n, writer, state)?;
334
335 if put_newline_after {
336 state.new_line();
337 inserted_newline = true;
338 }
339 }
340 }
341 Ok(())
342}
343
344fn format_sub_element(
345 node: &SyntaxNode,
346 writer: &mut impl TokenWriter,
347 state: &mut FormatState,
348) -> Result<(), std::io::Error> {
349 let mut sub = node.children_with_tokens().peekable();
350
351 // Let's decide based on the first child
352 match sub.peek() {
353 Some(first_node_or_token) => {
354 if first_node_or_token.kind() == SyntaxKind::Identifier {
355 // In this branch the sub element starts with an identifier, eg.
356 // Something := Text {...}
357 if !(whitespace_to(&mut sub, SyntaxKind::Identifier, writer, state, "")?
358 && whitespace_to(&mut sub, SyntaxKind::ColonEqual, writer, state, " ")?)
359 {
360 // There is an error finding the QualifiedName and LBrace when we do this branch.
361 finish_node(sub, writer, state)?;
362 return Ok(());
363 }
364 state.insert_whitespace(" ");
365 }
366 // If the first child was not an identifier, we just fold it
367 // (it might be an element, eg. Text {...})
368 for s in sub {
369 fold(s, writer, state)?;
370 }
371 Ok(())
372 }
373 // No children found -> we ignore this node
374 None => Ok(()),
375 }
376}
377
378fn format_property_declaration(
379 node: &SyntaxNode,
380 writer: &mut impl TokenWriter,
381 state: &mut FormatState,
382) -> Result<(), std::io::Error> {
383 let mut sub = node.children_with_tokens();
384 whitespace_to(&mut sub, SyntaxKind::Identifier, writer, state, "")?;
385 while let SyntaxMatch::Found(x) = whitespace_to_one_of(
386 &mut sub,
387 &[SyntaxKind::Identifier, SyntaxKind::LAngle, SyntaxKind::DeclaredIdentifier],
388 writer,
389 state,
390 " ",
391 )? {
392 match x {
393 SyntaxKind::DeclaredIdentifier => break,
394 SyntaxKind::LAngle => {
395 let _ok = whitespace_to(&mut sub, SyntaxKind::Type, writer, state, "")?
396 && whitespace_to(&mut sub, SyntaxKind::RAngle, writer, state, "")?
397 && whitespace_to(&mut sub, SyntaxKind::DeclaredIdentifier, writer, state, " ")?;
398 break;
399 }
400 _ => continue,
401 }
402 }
403 let need_newline = node.child_node(SyntaxKind::TwoWayBinding).is_none();
404
405 state.skip_all_whitespace = true;
406 for s in sub {
407 fold(s, writer, state)?;
408 }
409 if need_newline {
410 state.new_line();
411 }
412 Ok(())
413}
414
415fn format_binding(
416 node: &SyntaxNode,
417 writer: &mut impl TokenWriter,
418 state: &mut FormatState,
419) -> Result<(), std::io::Error> {
420 let mut sub: impl Iterator = node.children_with_tokens();
421 let _ok: bool = whitespace_to(&mut sub, element:SyntaxKind::Identifier, writer, state, prefix_whitespace:"")?
422 && whitespace_to(&mut sub, element:SyntaxKind::Colon, writer, state, prefix_whitespace:"")?
423 && whitespace_to(&mut sub, element:SyntaxKind::BindingExpression, writer, state, prefix_whitespace:" ")?;
424 // FIXME: more formatting
425 for s: NodeOrToken in sub {
426 fold(n:s, writer, state)?;
427 }
428 state.new_line();
429 Ok(())
430}
431
432fn format_two_way_binding(
433 node: &SyntaxNode,
434 writer: &mut impl TokenWriter,
435 state: &mut FormatState,
436) -> Result<(), std::io::Error> {
437 let mut sub: impl Iterator = node.children_with_tokens();
438 if node.child_token(kind:SyntaxKind::Identifier).is_some() {
439 whitespace_to(&mut sub, element:SyntaxKind::Identifier, writer, state, prefix_whitespace:"")?;
440 }
441 let _ok: bool = whitespace_to(&mut sub, element:SyntaxKind::DoubleArrow, writer, state, prefix_whitespace:" ")?
442 && whitespace_to(&mut sub, element:SyntaxKind::Expression, writer, state, prefix_whitespace:" ")?;
443 if node.child_token(kind:SyntaxKind::Semicolon).is_some() {
444 whitespace_to(&mut sub, element:SyntaxKind::Semicolon, writer, state, prefix_whitespace:"")?;
445 state.new_line();
446 }
447 for s: NodeOrToken in sub {
448 fold(n:s, writer, state)?;
449 }
450 Ok(())
451}
452
453fn format_callback_declaration(
454 node: &SyntaxNode,
455 writer: &mut impl TokenWriter,
456 state: &mut FormatState,
457) -> Result<(), std::io::Error> {
458 let mut sub = node.children_with_tokens();
459 whitespace_to(&mut sub, SyntaxKind::Identifier, writer, state, "")?;
460 while whitespace_to_one_of(
461 &mut sub,
462 &[SyntaxKind::Identifier, SyntaxKind::DeclaredIdentifier],
463 writer,
464 state,
465 " ",
466 )? == SyntaxMatch::Found(SyntaxKind::Identifier)
467 {}
468
469 while let Some(n) = sub.next() {
470 state.skip_all_whitespace = true;
471 match n.kind() {
472 SyntaxKind::Comma => {
473 fold(n, writer, state)?;
474 state.insert_whitespace(" ");
475 }
476 SyntaxKind::Arrow => {
477 state.insert_whitespace(" ");
478 fold(n, writer, state)?;
479 whitespace_to(&mut sub, SyntaxKind::ReturnType, writer, state, " ")?;
480 }
481 SyntaxKind::TwoWayBinding => {
482 fold(n, writer, state)?;
483 }
484 _ => {
485 fold(n, writer, state)?;
486 }
487 }
488 }
489 state.new_line();
490 Ok(())
491}
492
493fn format_function(
494 node: &SyntaxNode,
495 writer: &mut impl TokenWriter,
496 state: &mut FormatState,
497) -> Result<(), std::io::Error> {
498 let mut sub = node.children_with_tokens();
499 whitespace_to(&mut sub, SyntaxKind::Identifier, writer, state, "")?;
500 while whitespace_to_one_of(
501 &mut sub,
502 &[SyntaxKind::Identifier, SyntaxKind::DeclaredIdentifier],
503 writer,
504 state,
505 " ",
506 )? == SyntaxMatch::Found(SyntaxKind::Identifier)
507 {}
508
509 while let Some(n) = sub.next() {
510 state.skip_all_whitespace = true;
511 match n.kind() {
512 SyntaxKind::Comma => {
513 fold(n, writer, state)?;
514 state.insert_whitespace(" ");
515 }
516 SyntaxKind::Arrow => {
517 state.insert_whitespace(" ");
518 fold(n, writer, state)?;
519 whitespace_to(&mut sub, SyntaxKind::ReturnType, writer, state, " ")?;
520 }
521 _ => {
522 fold(n, writer, state)?;
523 }
524 }
525 }
526 state.new_line();
527 Ok(())
528}
529
530fn format_callback_connection(
531 node: &SyntaxNode,
532 writer: &mut impl TokenWriter,
533 state: &mut FormatState,
534) -> Result<(), std::io::Error> {
535 let mut sub: impl Iterator = node.children_with_tokens();
536 whitespace_to(&mut sub, element:SyntaxKind::Identifier, writer, state, prefix_whitespace:"")?;
537
538 for s: NodeOrToken in sub {
539 state.skip_all_whitespace = true;
540 match s.kind() {
541 SyntaxKind::FatArrow => {
542 state.insert_whitespace(arg:" ");
543 fold(n:s, writer, state)?;
544 state.insert_whitespace(arg:" ");
545 }
546 SyntaxKind::Comma => {
547 fold(n:s, writer, state)?;
548 state.insert_whitespace(arg:" ");
549 }
550 _ => fold(n:s, writer, state)?,
551 }
552 }
553 state.new_line();
554 Ok(())
555}
556
557fn format_qualified_name(
558 node: &SyntaxNode,
559 writer: &mut impl TokenWriter,
560 state: &mut FormatState,
561) -> Result<(), std::io::Error> {
562 for n in node.children_with_tokens() {
563 state.skip_all_whitespace = true;
564 fold(n, writer, state)?;
565 }
566 /*if !node
567 .last_token()
568 .and_then(|x| x.next_token())
569 .map(|x| {
570 matches!(
571 x.kind(),
572 SyntaxKind::LParent
573 | SyntaxKind::RParent
574 | SyntaxKind::Semicolon
575 | SyntaxKind::Comma
576 )
577 })
578 .unwrap_or(false)
579 {
580 state.insert_whitespace(" ");
581 } else {
582 state.skip_all_whitespace = true;
583 }*/
584 Ok(())
585}
586
587// Called both for BinaryExpression and SelfAssignment
588fn format_binary_expression(
589 node: &SyntaxNode,
590 writer: &mut impl TokenWriter,
591 state: &mut FormatState,
592) -> Result<(), std::io::Error> {
593 let mut sub = node.children_with_tokens();
594
595 let _ok = whitespace_to(&mut sub, SyntaxKind::Expression, writer, state, "")?
596 && whitespace_to_one_of(
597 &mut sub,
598 &[
599 SyntaxKind::Plus,
600 SyntaxKind::Minus,
601 SyntaxKind::Star,
602 SyntaxKind::Div,
603 SyntaxKind::AndAnd,
604 SyntaxKind::OrOr,
605 SyntaxKind::EqualEqual,
606 SyntaxKind::NotEqual,
607 SyntaxKind::LAngle,
608 SyntaxKind::LessEqual,
609 SyntaxKind::RAngle,
610 SyntaxKind::GreaterEqual,
611 SyntaxKind::Equal,
612 SyntaxKind::PlusEqual,
613 SyntaxKind::MinusEqual,
614 SyntaxKind::StarEqual,
615 SyntaxKind::DivEqual,
616 ],
617 writer,
618 state,
619 " ",
620 )?
621 .is_found()
622 && whitespace_to(&mut sub, SyntaxKind::Expression, writer, state, " ")?;
623
624 Ok(())
625}
626
627fn format_conditional_expression(
628 node: &SyntaxNode,
629 writer: &mut impl TokenWriter,
630 state: &mut FormatState,
631) -> Result<(), std::io::Error> {
632 let has_if = node.child_text(SyntaxKind::Identifier).map_or(false, |x| x == "if");
633
634 let mut sub = node.children_with_tokens();
635 if has_if {
636 let ok = whitespace_to(&mut sub, SyntaxKind::Identifier, writer, state, "")?
637 && whitespace_to(&mut sub, SyntaxKind::Expression, writer, state, " ")?
638 && whitespace_to(&mut sub, SyntaxKind::Expression, writer, state, " ")?;
639 if !ok {
640 finish_node(sub, writer, state)?;
641 return Ok(());
642 }
643 while let Some(n) = sub.next() {
644 state.skip_all_whitespace = true;
645 // `else`
646 if n.kind() == SyntaxKind::Identifier {
647 state.insert_whitespace(" ");
648 fold(n, writer, state)?;
649 whitespace_to(&mut sub, SyntaxKind::Expression, writer, state, " ")?;
650 continue;
651 }
652 fold(n, writer, state)?;
653 }
654 state.whitespace_to_add = None;
655 state.new_line();
656 } else {
657 let _ok = whitespace_to(&mut sub, SyntaxKind::Expression, writer, state, "")?
658 && whitespace_to(&mut sub, SyntaxKind::Question, writer, state, " ")?
659 && whitespace_to(&mut sub, SyntaxKind::Expression, writer, state, " ")?
660 && whitespace_to(&mut sub, SyntaxKind::Colon, writer, state, " ")?
661 && whitespace_to(&mut sub, SyntaxKind::Expression, writer, state, " ")?;
662 finish_node(sub, writer, state)?;
663 }
664 Ok(())
665}
666
667fn format_expression(
668 node: &SyntaxNode,
669 writer: &mut impl TokenWriter,
670 state: &mut FormatState,
671) -> Result<(), std::io::Error> {
672 // For expressions, we skip whitespace.
673 // Since state.skip_all_whitespace is reset every time we find something that is not a whitespace,
674 // we need to set it all the time.
675
676 for n: NodeOrToken in node.children_with_tokens() {
677 state.skip_all_whitespace = true;
678 fold(n, writer, state)?;
679 }
680 Ok(())
681}
682
683fn format_codeblock(
684 node: &SyntaxNode,
685 writer: &mut impl TokenWriter,
686 state: &mut FormatState,
687) -> Result<(), std::io::Error> {
688 if node.first_child_or_token().is_none() {
689 // empty CodeBlock happens when there is no `else` for example
690 return Ok(());
691 }
692
693 let prev_is_return_type =
694 node.prev_sibling().map(|s| s.kind() == SyntaxKind::ReturnType).unwrap_or(false);
695
696 let mut sub = node.children_with_tokens();
697 let prefix_whitespace = if prev_is_return_type { " " } else { "" };
698 if !whitespace_to(&mut sub, SyntaxKind::LBrace, writer, state, prefix_whitespace)? {
699 finish_node(sub, writer, state)?;
700 return Ok(());
701 }
702 state.indentation_level += 1;
703 state.new_line();
704 for n in sub {
705 state.skip_all_whitespace = true;
706 if n.kind() == SyntaxKind::Whitespace {
707 let is_empty_line = n.as_token().map(|n| n.text().contains("\n\n")).unwrap_or(false);
708 if is_empty_line {
709 state.new_line();
710 }
711 } else if n.kind() == SyntaxKind::RBrace {
712 state.indentation_level -= 1;
713 state.whitespace_to_add = None;
714 state.new_line();
715 }
716
717 let is_semicolon = n.kind() == SyntaxKind::Semicolon;
718
719 fold(n, writer, state)?;
720
721 if is_semicolon {
722 state.whitespace_to_add = None;
723 state.new_line();
724 }
725 }
726 state.skip_all_whitespace = true;
727 Ok(())
728}
729
730fn format_return_statement(
731 node: &SyntaxNode,
732 writer: &mut impl TokenWriter,
733 state: &mut FormatState,
734) -> Result<(), std::io::Error> {
735 let mut sub: impl Iterator = node.children_with_tokens();
736 whitespace_to(&mut sub, element:SyntaxKind::Identifier, writer, state, prefix_whitespace:"")?;
737 if node.child_node(kind:SyntaxKind::Expression).is_some() {
738 whitespace_to(&mut sub, element:SyntaxKind::Identifier, writer, state, prefix_whitespace:" ")?;
739 }
740 whitespace_to(&mut sub, element:SyntaxKind::Semicolon, writer, state, prefix_whitespace:"")?;
741 state.new_line();
742 finish_node(sub, writer, state)?;
743 Ok(())
744}
745
746fn format_at_gradient(
747 node: &SyntaxNode,
748 writer: &mut impl TokenWriter,
749 state: &mut FormatState,
750) -> Result<(), std::io::Error> {
751 // ensure that two consecutive expression are separated with space
752 let mut seen_expression: bool = false;
753 for n: NodeOrToken in node.children_with_tokens() {
754 state.skip_all_whitespace = true;
755 if n.kind() == SyntaxKind::Expression {
756 if seen_expression {
757 state.insert_whitespace(arg:" ");
758 }
759 seen_expression = true;
760 }
761 fold(n, writer, state)?;
762 }
763 Ok(())
764}
765
766fn format_children_placeholder(
767 node: &SyntaxNode,
768 writer: &mut impl TokenWriter,
769 state: &mut FormatState,
770) -> Result<(), std::io::Error> {
771 for n: NodeOrToken in node.children_with_tokens() {
772 fold(n, writer, state)?;
773 }
774 state.new_line();
775 Ok(())
776}
777
778fn format_repeated_element(
779 node: &SyntaxNode,
780 writer: &mut impl TokenWriter,
781 state: &mut FormatState,
782) -> Result<(), std::io::Error> {
783 let mut sub = node.children_with_tokens();
784 whitespace_to(&mut sub, SyntaxKind::Identifier, writer, state, "")?;
785 whitespace_to(&mut sub, SyntaxKind::DeclaredIdentifier, writer, state, " ")?;
786
787 let (kind, prefix_whitespace) = if node.child_node(SyntaxKind::RepeatedIndex).is_some() {
788 (SyntaxKind::RepeatedIndex, "")
789 } else {
790 (SyntaxKind::Identifier, " ")
791 };
792 whitespace_to(&mut sub, kind, writer, state, prefix_whitespace)?;
793
794 if kind == SyntaxKind::RepeatedIndex {
795 whitespace_to(&mut sub, SyntaxKind::Identifier, writer, state, " ")?;
796 }
797
798 whitespace_to(&mut sub, SyntaxKind::Expression, writer, state, " ")?;
799 whitespace_to(&mut sub, SyntaxKind::Colon, writer, state, "")?;
800 state.insert_whitespace(" ");
801 state.skip_all_whitespace = true;
802
803 for s in sub {
804 fold(s, writer, state)?;
805 }
806 Ok(())
807}
808
809fn format_repeated_index(
810 node: &SyntaxNode,
811 writer: &mut impl TokenWriter,
812 state: &mut FormatState,
813) -> Result<(), std::io::Error> {
814 let mut sub: impl Iterator = node.children_with_tokens();
815 whitespace_to(&mut sub, element:SyntaxKind::LBracket, writer, state, prefix_whitespace:"")?;
816 whitespace_to(&mut sub, element:SyntaxKind::Identifier, writer, state, prefix_whitespace:"")?;
817 whitespace_to(&mut sub, element:SyntaxKind::RBracket, writer, state, prefix_whitespace:"")?;
818 Ok(())
819}
820
821fn format_array(
822 node: &SyntaxNode,
823 writer: &mut impl TokenWriter,
824 state: &mut FormatState,
825) -> Result<(), std::io::Error> {
826 let has_trailing_comma = node
827 .last_token()
828 .and_then(|last| last.prev_token())
829 .map(|second_last| {
830 if second_last.kind() == SyntaxKind::Whitespace {
831 second_last.prev_token().map(|n| n.kind() == SyntaxKind::Comma).unwrap_or(false)
832 } else {
833 second_last.kind() == SyntaxKind::Comma
834 }
835 })
836 .unwrap_or(false);
837 // len of all children
838 let len = node.children().fold(0, |acc, e| {
839 let mut len = 0;
840 e.text().for_each_chunk(|s| len += s.trim().len());
841 acc + len
842 });
843 // add , and 1-space len for each
844 // not really accurate, e.g., [1] should have len 1, but due to this
845 // it will be 3, but it doesn't matter
846 let len = len + (2 * node.children().count());
847 let is_large_array = len >= 80;
848 let mut sub = node.children_with_tokens().peekable();
849 whitespace_to(&mut sub, SyntaxKind::LBracket, writer, state, "")?;
850
851 let is_empty_array = node.children().count() == 0;
852 if is_empty_array {
853 whitespace_to(&mut sub, SyntaxKind::RBracket, writer, state, "")?;
854 return Ok(());
855 }
856
857 if is_large_array || has_trailing_comma {
858 state.indentation_level += 1;
859 state.new_line();
860 }
861
862 loop {
863 whitespace_to(&mut sub, SyntaxKind::Expression, writer, state, "")?;
864 let at_end = sub.peek().map(|next| next.kind() == SyntaxKind::RBracket).unwrap_or(false);
865 if at_end && is_large_array {
866 state.indentation_level -= 1;
867 state.new_line();
868 }
869
870 let el = whitespace_to_one_of(
871 &mut sub,
872 &[SyntaxKind::RBracket, SyntaxKind::Comma],
873 writer,
874 state,
875 "",
876 )?;
877
878 match el {
879 SyntaxMatch::Found(SyntaxKind::RBracket) => break,
880 SyntaxMatch::Found(SyntaxKind::Comma) => {
881 let is_trailing_comma = sub
882 .peek()
883 .map(|next| {
884 if next.kind() == SyntaxKind::Whitespace {
885 next.as_token()
886 .and_then(|ws| ws.next_token())
887 .map(|n| n.kind() == SyntaxKind::RBracket)
888 .unwrap_or(false)
889 } else {
890 next.kind() == SyntaxKind::RBracket
891 }
892 })
893 .unwrap_or(false);
894
895 if is_trailing_comma {
896 state.indentation_level -= 1;
897 state.new_line();
898 whitespace_to(&mut sub, SyntaxKind::RBracket, writer, state, "")?;
899 break;
900 }
901 if is_large_array || has_trailing_comma {
902 state.new_line();
903 } else {
904 state.insert_whitespace(" ");
905 }
906 }
907 SyntaxMatch::NotFound | SyntaxMatch::Found(_) => {
908 eprintln!("Inconsistency: unexpected syntax in array.");
909 break;
910 }
911 }
912 }
913
914 Ok(())
915}
916
917fn format_state(
918 node: &SyntaxNode,
919 writer: &mut impl TokenWriter,
920 state: &mut FormatState,
921) -> Result<(), std::io::Error> {
922 let has_when = node.child_text(SyntaxKind::Identifier).map_or(false, |x| x == "when");
923 let mut sub = node.children_with_tokens();
924 let ok = if has_when {
925 whitespace_to(&mut sub, SyntaxKind::DeclaredIdentifier, writer, state, "")?
926 && whitespace_to(&mut sub, SyntaxKind::Identifier, writer, state, " ")?
927 && whitespace_to(&mut sub, SyntaxKind::Expression, writer, state, " ")?
928 && whitespace_to(&mut sub, SyntaxKind::Colon, writer, state, "")?
929 && whitespace_to(&mut sub, SyntaxKind::LBrace, writer, state, " ")?
930 } else {
931 whitespace_to(&mut sub, SyntaxKind::DeclaredIdentifier, writer, state, "")?
932 && whitespace_to(&mut sub, SyntaxKind::Colon, writer, state, "")?
933 && whitespace_to(&mut sub, SyntaxKind::LBrace, writer, state, " ")?
934 };
935 if !ok {
936 finish_node(sub, writer, state)?;
937 return Ok(());
938 }
939
940 state.new_line();
941 let ins_ctn = state.insertion_count;
942 let mut first = true;
943
944 for n in sub {
945 if matches!(n.kind(), SyntaxKind::StatePropertyChange | SyntaxKind::Transition) {
946 if first {
947 // add new line after brace + increase indent
948 state.indentation_level += 1;
949 state.whitespace_to_add = None;
950 state.new_line();
951 }
952 first = false;
953 fold(n, writer, state)?;
954 } else if n.kind() == SyntaxKind::RBrace {
955 if !first {
956 state.indentation_level -= 1;
957 }
958 state.whitespace_to_add = None;
959 if ins_ctn == state.insertion_count {
960 state.insert_whitespace("");
961 } else {
962 state.new_line();
963 }
964 fold(n, writer, state)?;
965 state.new_line();
966 } else {
967 fold(n, writer, state)?;
968 }
969 }
970
971 Ok(())
972}
973
974fn format_states(
975 node: &SyntaxNode,
976 writer: &mut impl TokenWriter,
977 state: &mut FormatState,
978) -> Result<(), std::io::Error> {
979 let mut sub: impl Iterator = node.children_with_tokens();
980 let ok: bool = whitespace_to(&mut sub, element:SyntaxKind::Identifier, writer, state, prefix_whitespace:"")?
981 && whitespace_to(&mut sub, element:SyntaxKind::LBracket, writer, state, prefix_whitespace:" ")?;
982
983 if !ok {
984 eprintln!("Inconsistency: Expect states and ']'");
985 return Ok(());
986 }
987
988 state.indentation_level += 1;
989 state.new_line();
990
991 for n: NodeOrToken in sub {
992 if n.kind() == SyntaxKind::RBracket {
993 state.whitespace_to_add = None;
994 state.indentation_level -= 1;
995 state.new_line();
996 }
997 fold(n, writer, state)?;
998 }
999 state.new_line();
1000 Ok(())
1001}
1002
1003fn format_state_prop_change(
1004 node: &SyntaxNode,
1005 writer: &mut impl TokenWriter,
1006 state: &mut FormatState,
1007) -> Result<(), std::io::Error> {
1008 let mut sub: impl Iterator = node.children_with_tokens();
1009 let _ok: bool = whitespace_to(&mut sub, element:SyntaxKind::QualifiedName, writer, state, prefix_whitespace:"")?
1010 && whitespace_to(&mut sub, element:SyntaxKind::Colon, writer, state, prefix_whitespace:"")?
1011 && whitespace_to(&mut sub, element:SyntaxKind::BindingExpression, writer, state, prefix_whitespace:" ")?;
1012
1013 for n: NodeOrToken in sub {
1014 state.skip_all_whitespace = true;
1015 fold(n, writer, state)?;
1016 }
1017 state.new_line();
1018 Ok(())
1019}
1020
1021fn format_transition(
1022 node: &SyntaxNode,
1023 writer: &mut impl TokenWriter,
1024 state: &mut FormatState,
1025) -> Result<(), std::io::Error> {
1026 let mut sub: impl Iterator = node.children_with_tokens();
1027 let ok: bool = whitespace_to(&mut sub, element:SyntaxKind::Identifier, writer, state, prefix_whitespace:"")?
1028 && whitespace_to(&mut sub, element:SyntaxKind::LBrace, writer, state, prefix_whitespace:" ")?;
1029
1030 if !ok {
1031 finish_node(sub, writer, state)?;
1032 return Ok(());
1033 }
1034 state.indentation_level += 1;
1035 state.new_line();
1036 for n: NodeOrToken in sub {
1037 if n.kind() == SyntaxKind::RBrace {
1038 state.indentation_level -= 1;
1039 state.whitespace_to_add = None;
1040 state.new_line();
1041 fold(n, writer, state)?;
1042 state.new_line();
1043 } else {
1044 fold(n, writer, state)?;
1045 }
1046 }
1047 Ok(())
1048}
1049
1050fn format_property_animation(
1051 node: &SyntaxNode,
1052 writer: &mut impl TokenWriter,
1053 state: &mut FormatState,
1054) -> Result<(), std::io::Error> {
1055 let mut sub = node.children_with_tokens().peekable();
1056 let _ok = whitespace_to(&mut sub, SyntaxKind::Identifier, writer, state, "")?
1057 && whitespace_to(&mut sub, SyntaxKind::QualifiedName, writer, state, " ")?;
1058
1059 loop {
1060 let next_kind = sub.peek().map(|n| n.kind()).unwrap_or(SyntaxKind::Error);
1061 match next_kind {
1062 SyntaxKind::Whitespace | SyntaxKind::Comment => {
1063 let n = sub.next().unwrap();
1064 state.skip_all_whitespace = true;
1065 fold(n, writer, state)?;
1066 continue;
1067 }
1068 SyntaxKind::Comma => {
1069 whitespace_to(&mut sub, SyntaxKind::Comma, writer, state, "")?;
1070 continue;
1071 }
1072 SyntaxKind::QualifiedName => {
1073 whitespace_to(&mut sub, SyntaxKind::QualifiedName, writer, state, " ")?;
1074 continue;
1075 }
1076 SyntaxKind::LBrace => {
1077 whitespace_to(&mut sub, SyntaxKind::LBrace, writer, state, " ")?;
1078 break;
1079 }
1080 _ => break,
1081 }
1082 }
1083
1084 let bindings = node.children().fold(0, |acc, e| {
1085 if e.kind() == SyntaxKind::Binding {
1086 return acc + 1;
1087 }
1088 acc
1089 });
1090
1091 if bindings > 1 {
1092 state.indentation_level += 1;
1093 state.new_line();
1094 } else {
1095 state.insert_whitespace(" ");
1096 }
1097
1098 for n in sub {
1099 if n.kind() == SyntaxKind::RBrace {
1100 state.whitespace_to_add = None;
1101 if bindings > 1 {
1102 state.indentation_level -= 1;
1103 state.new_line();
1104 } else {
1105 state.insert_whitespace(" ");
1106 }
1107 fold(n, writer, state)?;
1108 state.new_line();
1109 } else {
1110 fold(n, writer, state)?;
1111 }
1112 }
1113 Ok(())
1114}
1115
1116fn format_object_literal(
1117 node: &SyntaxNode,
1118 writer: &mut impl TokenWriter,
1119 state: &mut FormatState,
1120) -> Result<(), std::io::Error> {
1121 let has_trailing_comma = node
1122 .last_token()
1123 .and_then(|last| last.prev_token())
1124 .map(|second_last| {
1125 if second_last.kind() == SyntaxKind::Whitespace {
1126 second_last.prev_token().map(|n| n.kind() == SyntaxKind::Comma).unwrap_or(false)
1127 } else {
1128 second_last.kind() == SyntaxKind::Comma
1129 }
1130 })
1131 .unwrap_or(false);
1132 // len of all children
1133 let len = node.children().fold(0, |acc, e| {
1134 let mut len = 0;
1135 e.text().for_each_chunk(|s| len += s.trim().len());
1136 acc + len
1137 });
1138 let is_large_literal = len >= 80;
1139
1140 let mut sub = node.children_with_tokens().peekable();
1141 whitespace_to(&mut sub, SyntaxKind::LBrace, writer, state, "")?;
1142 let indent_with_new_line = is_large_literal || has_trailing_comma;
1143
1144 if indent_with_new_line {
1145 state.indentation_level += 1;
1146 state.new_line();
1147 } else {
1148 state.insert_whitespace(" ");
1149 }
1150
1151 loop {
1152 let el = whitespace_to_one_of(
1153 &mut sub,
1154 &[SyntaxKind::ObjectMember, SyntaxKind::RBrace],
1155 writer,
1156 state,
1157 "",
1158 )?;
1159
1160 if let SyntaxMatch::Found(SyntaxKind::ObjectMember) = el {
1161 if indent_with_new_line {
1162 state.new_line();
1163 } else {
1164 state.insert_whitespace(" ");
1165 }
1166
1167 // are we at the end of literal?
1168 let at_end = sub
1169 .peek()
1170 .map(|next| {
1171 if next.kind() == SyntaxKind::Whitespace {
1172 next.as_token()
1173 .and_then(|ws| ws.next_token())
1174 .map(|n| n.kind() == SyntaxKind::RBrace)
1175 .unwrap_or(false)
1176 } else {
1177 next.kind() == SyntaxKind::RBrace
1178 }
1179 })
1180 .unwrap_or(false);
1181
1182 if at_end && indent_with_new_line {
1183 state.indentation_level -= 1;
1184 state.whitespace_to_add = None;
1185 state.new_line();
1186 }
1187
1188 continue;
1189 } else if let SyntaxMatch::Found(SyntaxKind::RBrace) = el {
1190 break;
1191 } else {
1192 eprintln!("Inconsistency: unexpected syntax in object literal.");
1193 break;
1194 }
1195 }
1196 Ok(())
1197}
1198
1199#[cfg(test)]
1200mod tests {
1201 use super::*;
1202 use crate::fmt::writer::FileWriter;
1203 use i_slint_compiler::diagnostics::BuildDiagnostics;
1204
1205 // FIXME more descriptive errors when an assertion fails
1206 #[track_caller]
1207 fn assert_formatting(unformatted: &str, formatted: &str) {
1208 // Parse the unformatted string
1209 let syntax_node = i_slint_compiler::parser::parse(
1210 String::from(unformatted),
1211 None,
1212 None,
1213 &mut BuildDiagnostics::default(),
1214 );
1215 // Turn the syntax node into a document
1216 let doc = syntax_nodes::Document::new(syntax_node).unwrap();
1217 let mut file = Vec::new();
1218 format_document(doc, &mut FileWriter { file: &mut file }).unwrap();
1219 assert_eq!(String::from_utf8(file).unwrap(), formatted);
1220 }
1221
1222 #[test]
1223 fn basic_formatting() {
1224 assert_formatting("A:=Text{}", "A := Text { }\n");
1225 }
1226
1227 #[test]
1228 fn components() {
1229 assert_formatting(
1230 "component A {} export component B inherits Text { }",
1231 "component A { }\n\nexport component B inherits Text { }\n",
1232 );
1233 }
1234
1235 #[test]
1236 fn with_comments() {
1237 assert_formatting(
1238 r#"component /* */ Foo // aaa
1239 inherits /* x */ // bbb
1240 Window // ccc
1241 /*y*/ { // c
1242 Foo /*aa*/ /*bb*/ {}
1243 }
1244 "#,
1245 r#"component /* */ Foo // aaa
1246 inherits /* x */ // bbb
1247 Window// ccc
1248 /*y*/ {
1249 // c
1250 Foo/*aa*/ /*bb*/ { }
1251}
1252"#,
1253 );
1254 }
1255
1256 #[test]
1257 fn complex_formatting() {
1258 assert_formatting(
1259 r#"
1260Main :=Window{callback some-fn(string,string)->bool;some-fn(a, b)=>{a<=b} property<bool>prop-x;
1261 VerticalBox { combo:=ComboBox{} }
1262 pure callback some-fn ({x: int},string); in property < int > foo: 42; }
1263 "#,
1264 r#"
1265Main := Window {
1266 callback some-fn(string, string) -> bool;
1267 some-fn(a, b) => {
1268 a <= b
1269 }
1270 property <bool> prop-x;
1271 VerticalBox {
1272 combo := ComboBox { }
1273 }
1274
1275 pure callback some-fn({x: int}, string);
1276 in property <int> foo: 42;
1277}
1278"#,
1279 );
1280 }
1281
1282 #[test]
1283 fn parent_access() {
1284 assert_formatting(
1285 r#"
1286Main := Parent{ Child{
1287 some-prop: parent.foo - 60px;
1288}}"#,
1289 r#"
1290Main := Parent {
1291 Child {
1292 some-prop: parent.foo - 60px;
1293 }
1294}
1295"#,
1296 );
1297 }
1298
1299 #[test]
1300 fn space_with_braces() {
1301 assert_formatting("Main := Window{}", "Main := Window { }\n");
1302 // Also in a child
1303 assert_formatting(
1304 r#"
1305Main := Window{Child{}}"#,
1306 r#"
1307Main := Window {
1308 Child { }
1309}
1310"#,
1311 );
1312 assert_formatting(
1313 r#"
1314Main := VerticalLayout{HorizontalLayout{prop:3;}}"#,
1315 r#"
1316Main := VerticalLayout {
1317 HorizontalLayout {
1318 prop: 3;
1319 }
1320}
1321"#,
1322 );
1323 }
1324
1325 #[test]
1326 fn binary_expressions() {
1327 assert_formatting(
1328 r#"
1329Main := Some{
1330 a:3+2; b:4-7; c:3*7; d:3/9;
1331 e:3==4; f:3!=4; g:3<4; h:3<=4;
1332 i:3>4; j:3>=4; k:3&&4; l:3||4;
1333}"#,
1334 r#"
1335Main := Some {
1336 a: 3 + 2;
1337 b: 4 - 7;
1338 c: 3 * 7;
1339 d: 3 / 9;
1340 e: 3 == 4;
1341 f: 3 != 4;
1342 g: 3 < 4;
1343 h: 3 <= 4;
1344 i: 3 > 4;
1345 j: 3 >= 4;
1346 k: 3 && 4;
1347 l: 3 || 4;
1348}
1349"#,
1350 );
1351
1352 assert_formatting(
1353 r#"
1354Main := Some{
1355 m: 3 + 8;
1356 m:3 + 8;
1357 m: 3+ 8;
1358 m:3+ 8;
1359 m: 3 +8;
1360 m:3 +8;
1361 m: 3+8;
1362 m:3+8;
1363}"#,
1364 r#"
1365Main := Some {
1366 m: 3 + 8;
1367 m: 3 + 8;
1368 m: 3 + 8;
1369 m: 3 + 8;
1370 m: 3 + 8;
1371 m: 3 + 8;
1372 m: 3 + 8;
1373 m: 3 + 8;
1374}
1375"#,
1376 );
1377 }
1378
1379 #[test]
1380 fn file_with_an_import() {
1381 assert_formatting(
1382 r#"
1383import { Some } from "./here.slint";
1384
1385A := Some{ padding-left: 10px; Text{ x: 3px; }}"#,
1386 r#"
1387import { Some } from "./here.slint";
1388
1389A := Some {
1390 padding-left: 10px;
1391 Text {
1392 x: 3px;
1393 }
1394}
1395"#,
1396 );
1397 }
1398
1399 #[test]
1400 fn children() {
1401 // Regression test - children was causing additional newlines
1402 assert_formatting(
1403 r#"
1404A := B {
1405 C {
1406 @children
1407 }
1408}"#,
1409 r#"
1410A := B {
1411 C {
1412 @children
1413 }
1414}
1415"#,
1416 );
1417 }
1418
1419 #[test]
1420 fn for_in() {
1421 assert_formatting(
1422 r#"
1423A := B { for c in root.d: T { e: c.attr; } }
1424 "#,
1425 r#"
1426A := B {
1427 for c in root.d: T {
1428 e: c.attr;
1429 }
1430}
1431"#,
1432 );
1433 }
1434
1435 #[test]
1436 fn for_in_index() {
1437 // FIXME: there should not be a whitespace before [index]
1438 assert_formatting(
1439 r#"
1440A := B {
1441 for number [ index
1442 ] in [1,2,3]: C { d:number*index; }
1443}
1444 "#,
1445 r#"
1446A := B {
1447 for number[index] in [1, 2, 3]: C {
1448 d: number * index;
1449 }
1450}
1451"#,
1452 );
1453 }
1454
1455 #[test]
1456 fn if_element() {
1457 assert_formatting(
1458 r#"
1459component A { if condition : Text { } }
1460 "#,
1461 r#"
1462component A {
1463 if condition: Text { }
1464}
1465"#,
1466 );
1467 }
1468
1469 #[test]
1470 fn array() {
1471 assert_formatting(
1472 r#"
1473A := B { c: [1,2,3]; }
1474"#,
1475 r#"
1476A := B {
1477 c: [1, 2, 3];
1478}
1479"#,
1480 );
1481 assert_formatting(
1482 r#"
1483A := B { c: [ 1 ]; }
1484"#,
1485 r#"
1486A := B {
1487 c: [1];
1488}
1489"#,
1490 );
1491 assert_formatting(
1492 r#"
1493A := B { c: [ ] ; }
1494"#,
1495 r#"
1496A := B {
1497 c: [];
1498}
1499"#,
1500 );
1501 assert_formatting(
1502 r#"
1503A := B { c: [
1504
15051,
1506
15072
1508
1509 ] ; }
1510"#,
1511 r#"
1512A := B {
1513 c: [1, 2];
1514}
1515"#,
1516 );
1517 }
1518
1519 #[test]
1520 fn states() {
1521 assert_formatting(
1522 r#"
1523component FooBar {
1524 states [
1525 dummy1 when a == true :{
1526
1527 }
1528 ]
1529}
1530"#,
1531 r#"
1532component FooBar {
1533 states [
1534 dummy1 when a == true: {}
1535 ]
1536}
1537"#,
1538 );
1539
1540 assert_formatting(
1541 r#"
1542component ABC {
1543 in-out property <bool> b: false;
1544 in-out property <int> a: 1;
1545 states[
1546 is-selected when root.b == root.b: {
1547 b:false;
1548 root.a:1;
1549 }
1550 is-not-selected when root.b!=root.b: {
1551 root.a: 1;
1552 }
1553 ] foo := Rectangle { }
1554}
1555"#,
1556 r#"
1557component ABC {
1558 in-out property <bool> b: false;
1559 in-out property <int> a: 1;
1560 states [
1561 is-selected when root.b == root.b: {
1562 b: false;
1563 root.a: 1;
1564 }
1565 is-not-selected when root.b != root.b: {
1566 root.a: 1;
1567 }
1568 ]
1569 foo := Rectangle { }
1570}
1571"#,
1572 );
1573 }
1574
1575 #[test]
1576 fn state_issue_4850() {
1577 // #4850
1578 assert_formatting(
1579 "export component LspCrashMvp { states [ active: { } inactive: { } ] }",
1580 r#"export component LspCrashMvp {
1581 states [
1582 active: {}
1583 inactive: {}
1584 ]
1585}
1586"#,
1587 );
1588 }
1589
1590 #[test]
1591 fn states_transitions() {
1592 assert_formatting(
1593 r#"
1594component FooBar {
1595 states [
1596 //comment
1597 s1 when true: {
1598 vv: 0; in { animate vv { duration: 400ms; }} out { animate /*...*/ vv { duration: 400ms; } animate dd { duration: 100ms+400ms ; easing: ease-out; } } }
1599 ]
1600}
1601"#,
1602 r#"
1603component FooBar {
1604 states [
1605 //comment
1606 s1 when true: {
1607 vv: 0;
1608 in {
1609 animate vv { duration: 400ms; }
1610 }
1611 out {
1612 animate /*...*/ vv { duration: 400ms; }
1613 animate dd {
1614 duration: 100ms + 400ms;
1615 easing: ease-out;
1616 }
1617 }
1618 }
1619 ]
1620}
1621"#,
1622 );
1623
1624 assert_formatting(
1625 "component FooBar {states[foo:{in{animate x{duration:1ms;}}x:0;}]}",
1626 r"component FooBar {
1627 states [
1628 foo: {
1629 in {
1630 animate x { duration: 1ms; }
1631 }
1632 x: 0;
1633 }
1634 ]
1635}
1636",
1637 );
1638 }
1639
1640 #[test]
1641 fn if_else() {
1642 assert_formatting(
1643 r#"
1644A := B { c: true ||false? 45 + 6:34+ 1; }
1645"#,
1646 r#"
1647A := B {
1648 c: true || false ? 45 + 6 : 34 + 1;
1649}
1650"#,
1651 );
1652 assert_formatting(
1653 r#"A := B { c => { if(!abc){nothing}else if (true){if (0== 8) {} } else{ } } } "#,
1654 r#"A := B {
1655 c => {
1656 if (!abc) {
1657 nothing
1658 } else if (true) {
1659 if (0 == 8) {
1660 }
1661 } else {
1662 }
1663 }
1664}
1665"#,
1666 );
1667 assert_formatting(
1668 r#"A := B { c => { if !abc{nothing}else if true{if 0== 8 {} } else{ } } } "#,
1669 r#"A := B {
1670 c => {
1671 if !abc {
1672 nothing
1673 } else if true {
1674 if 0 == 8 {
1675 }
1676 } else {
1677 }
1678 }
1679}
1680"#,
1681 );
1682
1683 assert_formatting(
1684 "component A { c => { if( a == 1 ){b+=1;} else if (a==2)\n{b+=2;} else if a==3{\nb+=3;\n} else\n if(a==4){ a+=4} return 0; } }",
1685 r"component A {
1686 c => {
1687 if (a == 1) {
1688 b += 1;
1689 } else if (a == 2) {
1690 b += 2;
1691 } else if a == 3 {
1692 b += 3;
1693 } else if (a == 4) {
1694 a += 4
1695 }
1696 return 0;
1697 }
1698}
1699");
1700 }
1701
1702 #[test]
1703 fn code_block() {
1704 assert_formatting(
1705 r#"
1706component ABC {
1707 in-out property <bool> logged_in: false;
1708 function clicked() -> bool {
1709 if (logged_in) { foo();
1710 logged_in = false;
1711 return
1712 true;
1713 } else {
1714 logged_in = false; return false;
1715 }
1716 }
1717}
1718"#,
1719 r#"
1720component ABC {
1721 in-out property <bool> logged_in: false;
1722 function clicked() -> bool {
1723 if (logged_in) {
1724 foo();
1725 logged_in = false;
1726 return true;
1727 } else {
1728 logged_in = false;
1729 return false;
1730 }
1731 }
1732}
1733"#,
1734 );
1735 }
1736
1737 #[test]
1738 fn trailing_comma_array() {
1739 assert_formatting(
1740 r#"
1741component ABC {
1742 in-out property <[int]> ar: [1, ];
1743 in-out property <[int]> ar: [1, 2, 3, 4, 5,];
1744 in-out property <[int]> ar2: [1, 2, 3, 4, 5];
1745}
1746"#,
1747 r#"
1748component ABC {
1749 in-out property <[int]> ar: [
1750 1,
1751 ];
1752 in-out property <[int]> ar: [
1753 1,
1754 2,
1755 3,
1756 4,
1757 5,
1758 ];
1759 in-out property <[int]> ar2: [1, 2, 3, 4, 5];
1760}
1761"#,
1762 );
1763 }
1764
1765 #[test]
1766 fn large_array() {
1767 assert_formatting(
1768 r#"
1769component ABC {
1770 in-out property <[string]> large: ["first string", "second string", "third string", "fourth string", "fifth string"];
1771 in property <[int]> model: [
1772 1,
1773 2
1774 ];
1775}
1776"#,
1777 r#"
1778component ABC {
1779 in-out property <[string]> large: [
1780 "first string",
1781 "second string",
1782 "third string",
1783 "fourth string",
1784 "fifth string"
1785 ];
1786 in property <[int]> model: [1, 2];
1787}
1788"#,
1789 );
1790 }
1791
1792 #[test]
1793 fn property_animation() {
1794 assert_formatting(
1795 r#"
1796export component MainWindow inherits Window {
1797 animate background { duration: 800ms;}
1798 animate x { duration: 100ms; easing: ease-out-bounce; }
1799 Rectangle {}
1800}
1801"#,
1802 r#"
1803export component MainWindow inherits Window {
1804 animate background { duration: 800ms; }
1805 animate x {
1806 duration: 100ms;
1807 easing: ease-out-bounce;
1808 }
1809 Rectangle { }
1810}
1811"#,
1812 );
1813 }
1814
1815 #[test]
1816 fn object_literal() {
1817 assert_formatting(
1818 r#"
1819export component MainWindow inherits Window {
1820 in property <[TileData]> memory-tiles : [
1821 { image: @image-url("icons/at.png"), image-visible: false, solved: false, },
1822 { image: @image-url("icons/at.png"), image-visible: false, solved: false,},
1823 { image: @image-url("icons/at.png"), image-visible: false, solved: false, some_other_property: 12345 },
1824 { image: @image-url("icons/at.png"), image-visible: false, solved: false, some_other_property: 12345},
1825 { image: @image-url("icons/balance-scale.png") },
1826 ];
1827}
1828"#,
1829 r#"
1830export component MainWindow inherits Window {
1831 in property <[TileData]> memory-tiles: [
1832 {
1833 image: @image-url("icons/at.png"),
1834 image-visible: false,
1835 solved: false,
1836 },
1837 {
1838 image: @image-url("icons/at.png"),
1839 image-visible: false,
1840 solved: false,
1841 },
1842 {
1843 image: @image-url("icons/at.png"),
1844 image-visible: false,
1845 solved: false,
1846 some_other_property: 12345
1847 },
1848 {
1849 image: @image-url("icons/at.png"),
1850 image-visible: false,
1851 solved: false,
1852 some_other_property: 12345
1853 },
1854 { image: @image-url("icons/balance-scale.png") },
1855 ];
1856}
1857"#,
1858 );
1859 }
1860
1861 #[test]
1862 fn preserve_empty_lines() {
1863 assert_formatting(
1864 r#"
1865export component MainWindow inherits Rectangle {
1866 in property <bool> open-curtain;
1867 callback clicked;
1868
1869 border-radius: 8px;
1870
1871
1872 animate background { duration: 800ms; }
1873
1874 Image {
1875 y: 8px;
1876 }
1877
1878
1879 Image {
1880 y: 8px;
1881 }
1882}
1883"#,
1884 r#"
1885export component MainWindow inherits Rectangle {
1886 in property <bool> open-curtain;
1887 callback clicked;
1888
1889 border-radius: 8px;
1890
1891 animate background { duration: 800ms; }
1892
1893 Image {
1894 y: 8px;
1895 }
1896
1897 Image {
1898 y: 8px;
1899 }
1900}
1901"#,
1902 );
1903 }
1904
1905 #[test]
1906 fn multiple_property_animation() {
1907 assert_formatting(
1908 r#"
1909export component MainWindow inherits Rectangle {
1910 animate x , y { duration: 170ms; easing: cubic-bezier(0.17,0.76,0.4,1.75); }
1911 animate x , y { duration: 170ms;}
1912}
1913"#,
1914 r#"
1915export component MainWindow inherits Rectangle {
1916 animate x, y {
1917 duration: 170ms;
1918 easing: cubic-bezier(0.17,0.76,0.4,1.75);
1919 }
1920 animate x, y { duration: 170ms; }
1921}
1922"#,
1923 );
1924 }
1925
1926 #[test]
1927 fn empty_array() {
1928 assert_formatting(
1929 r#"
1930export component MainWindow2 inherits Rectangle {
1931 in property <[string]> model: [ ];
1932}
1933"#,
1934 r#"
1935export component MainWindow2 inherits Rectangle {
1936 in property <[string]> model: [];
1937}
1938"#,
1939 );
1940 }
1941
1942 #[test]
1943 fn two_way_binding() {
1944 assert_formatting(
1945 "export component Foobar{foo<=>xx.bar ; property<int\n>xx <=> ff . mm ; callback doo <=> moo\n;\nproperty e-e<=>f-f; }",
1946 r#"export component Foobar {
1947 foo <=> xx.bar;
1948 property <int> xx <=> ff.mm;
1949 callback doo <=> moo;
1950 property e-e <=> f-f;
1951}
1952"#,
1953 );
1954 }
1955
1956 #[test]
1957 fn function() {
1958 assert_formatting(
1959 "export component Foo-bar{ pure\nfunction\n(x : int,y:string)->int{ self.y=0;\n\nif(true){return(45); a=0;} return x; } function a(){/* ddd */}}",
1960 r#"export component Foo-bar {
1961 pure function (x : int,y:string) -> int {
1962 self.y = 0;
1963
1964 if (true) {
1965 return (45);
1966 a = 0;
1967 }
1968 return x;
1969 }
1970 function a(){
1971 /* ddd */}
1972}
1973"#,
1974 );
1975 }
1976}
1977