├── .formatter.exs ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── config └── config.exs ├── lib ├── mix │ └── tasks │ │ └── surface │ │ └── format.ex └── surface │ ├── formatter.ex │ └── formatter │ ├── node_translator.ex │ ├── phase.ex │ ├── phases │ ├── block_exceptions.ex │ ├── final_newline.ex │ ├── indent.ex │ ├── newlines.ex │ ├── render.ex │ ├── spaces_to_newlines.ex │ └── tag_whitespace.ex │ └── plugin.ex ├── mix.exs ├── mix.lock └── test ├── mix └── tasks │ └── surface │ └── format_test.exs ├── surface ├── formatter │ └── plugin_test.exs └── formatter_test.exs └── test_helper.exs /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | ci: 11 | name: Build and test 12 | runs-on: ubuntu-latest 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | include: 17 | - elixir: 1.11.1 18 | otp: 21.3.8.17 19 | - elixir: 1.11.4 20 | otp: 23.2 21 | - elixir: 1.12.3 22 | otp: 24.1 23 | - elixir: 1.13.4 24 | otp: 24.1 25 | - elixir: 1.13.4 26 | otp: 25.0 27 | check_formatted: true 28 | run_plugin_tests: true 29 | warnings_as_errors: true 30 | 31 | env: 32 | MIX_ENV: test 33 | steps: 34 | - uses: actions/checkout@v2 35 | - uses: erlef/setup-beam@v1 36 | with: 37 | otp-version: ${{matrix.otp}} 38 | elixir-version: ${{matrix.elixir}} 39 | experimental-otp: true 40 | - name: Restore dependencies cache 41 | uses: actions/cache@v2 42 | with: 43 | path: deps 44 | key: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-mix-${{ hashFiles('**/mix.lock') }} 45 | restore-keys: ${{ runner.os }}-mix- 46 | 47 | - run: mix format --check-formatted 48 | if: matrix.check_formatted 49 | - name: Install Dependencies 50 | run: | 51 | mix local.hex --force 52 | mix local.rebar --force 53 | mix deps.get --only test 54 | - run: mix compile --warnings-as-errors 55 | if: matrix.warnings_as_errors 56 | - run: mix test 57 | if: matrix.run_plugin_tests 58 | - run: mix test --exclude plugin 59 | if: ${{ !matrix.run_plugin_tests }} 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | surface_formatter-*.tar 24 | 25 | 26 | # Temporary files for e.g. tests 27 | /tmp 28 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.7.5 (2022-03-14) 4 | 5 | * Keep single-line and empty ~F sigils intact (#57) 6 | * Prevent extra indentation on strings with newlines inside expressions (#61) 7 | 8 | ## v0.7.4 (2021-12-20) 9 | 10 | * Handle arbitrary expressions when checking for newlines in strings 11 | 12 | ## v0.7.3 (2021-12-18) 13 | 14 | * Stop endlessly indenting strings with newlines in expression attributes (#54) 15 | 16 | ## v0.7.2 (2021-12-03) 17 | 18 | * Fix crash with Elixir expression `{...}` on Elixir 1.13 without `:line_length` or `:surface_line_length` (#53) 19 | 20 | ## v0.7.1 (2021-11-29) 21 | 22 | * Stop adding spaces to indent blank lines in expressions (#51) 23 | 24 | ## v0.7.0 (2021-11-22) 25 | 26 | * Add `Surface.Formatter.Plugin` for Elixir 1.13 Formatter Plugin support. 27 | 28 | ## v0.6.0 (2021-10-21) 29 | 30 | * Support tagged expression attributes referencing variables (#47) 31 | * Require Surface `~> 0.5` instead of `~> 0.5.0` to expand compatibility to `0.6` 32 | 33 | ## v0.5.4 (2021-08-31) 34 | 35 | * Stop endlessly indenting attribute strings with interpolation whenever node is indented (#43) 36 | 37 | ## v0.5.3 (2021-08-30) 38 | 39 | * Stop endlessly indenting attribute strings with interpolation (#41) 40 | 41 | ## v0.5.2 (2021-08-27) 42 | 43 | * Enable reading from stdin (#37) 44 | * Stop turning `:if={true}` into `:if` (#39) 45 | * Stop endlessly indenting strings with newlines in list attributes (#40) 46 | 47 | ## v0.5.1 (2021-07-06) 48 | 49 | * Fix crash with `{#match "string"}` scenario (#32) 50 | * Stop removing `{/unless}` (#33) 51 | * Stop formatting contents of ` 282 | """, 283 | """ 284 |
 285 |         {@data}
 286 |               
 287 |         
288 | 289 | {@data} 290 | 291 | 292 | 293 | <#MacroComponent> Foo {@bar} baz 294 | 295 | 299 | """ 300 | ) 301 | end 302 | 303 | test "HTML elements rendered in
//<#MacroComponent> tags are left in their original state" do
 304 |       # Note that the  and <#Macro> components (which are too indented)
 305 |       # are brought all the way to the left side, but all of the whitespace
 306 |       # characters therein are left alone.
 307 |       assert_formatter_outputs(
 308 |         """
 309 |         
 310 |             

Hello world

311 |
312 | 313 | 314 |

Hello world

315 |
316 | 317 | <#Macro> 318 |

Hello world

319 | 320 | """, 321 | """ 322 |
 323 |             

Hello world

324 |
325 | 326 | 327 |

Hello world

328 |
329 | 330 | <#Macro> 331 |

Hello world

332 | 333 | """ 334 | ) 335 | end 336 | 337 | test "Attributes are lines up properly when split onto newlines with a multi-line attribute" do 338 | assert_formatter_outputs( 339 | """ 340 | 341 | 348 | 349 | """, 350 | """ 351 | 352 | 359 | 360 | """ 361 | ) 362 | end 363 | 364 | test "If any attribute is formatted with a newline, attributes are split onto separate lines" do 365 | # This is because multiple of them may have newlines, and it could result in odd formatting such as: 366 | # 367 | # 372 | # 373 | # The attributes aren't the easiest to read in that case, and we're making the choice not 374 | # to open the can of worms of potentially re-ordering attributes, because that introduces 375 | # plenty of complexity and might not be desired by users. 376 | assert_formatter_outputs( 377 | """ 378 | 379 | 386 | 387 | """, 388 | """ 389 | 390 | 397 | 398 | """ 399 | ) 400 | 401 | assert_formatter_outputs( 402 | """ 403 | 404 | 407 | 408 | """, 409 | """ 410 | 411 | 418 | 419 | """ 420 | ) 421 | end 422 | 423 | test "tags without children are collapsed if there is no whitespace between them" do 424 | assert_formatter_outputs( 425 | """ 426 | 427 | """, 428 | """ 429 | 430 | """ 431 | ) 432 | 433 | # Should these be collapsed? 434 | assert_formatter_doesnt_change(""" 435 | 436 | """) 437 | end 438 | 439 | test "lists with invisible brackets in attribute expressions are formatted" do 440 | assert_formatter_outputs( 441 | ~S""" 442 | @another_extremely_long_name_to_make_the_elixir_formatter_wrap_this_expression } /> 443 | """, 444 | ~S""" 445 | 449 | @another_extremely_long_name_to_make_the_elixir_formatter_wrap_this_expression 450 | } /> 451 | """ 452 | ) 453 | end 454 | 455 | test "existing whitespace in string attributes is not altered when there are multiple attributes" do 456 | # The output may not look "clean", but it didn't look "clean" to begin with, and it's the only 457 | # way to ensure the formatter doesn't accidentally change the behavior of the resulting code. 458 | # 459 | # As with the Elixir formatter, it's important that the semantics of the code remain the same. 460 | assert_formatter_outputs( 461 | """ 462 | 466 | """, 467 | """ 468 | 474 | """ 475 | ) 476 | end 477 | 478 | test "existing whitespace in string attributes is not altered when there is only one attribute" do 479 | assert_formatter_doesnt_change(""" 480 | 481 | 482 | 484 | 485 | 486 | """) 487 | end 488 | 489 | test "a single extra newline between children is retained" do 490 | assert_formatter_doesnt_change(""" 491 | 492 | foo 493 | 494 | bar 495 | 496 | """) 497 | end 498 | 499 | test "multiple extra newlines between children are collapsed to one" do 500 | assert_formatter_outputs( 501 | """ 502 | 503 | foo 504 | 505 | 506 | 507 | bar 508 | 509 | """, 510 | """ 511 | 512 | foo 513 | 514 | bar 515 | 516 | """ 517 | ) 518 | end 519 | 520 | test "at most one blank newline is retained when an HTML comment exists" do 521 | assert_formatter_outputs( 522 | ~S""" 523 |
524 | 525 | 526 | 527 | 528 |
529 | """, 530 | ~S""" 531 |
532 | 533 | 534 | 535 | 536 |
537 | """ 538 | ) 539 | end 540 | 541 | test "inline elements mixed with text are left on the same line by default" do 542 | assert_formatter_doesnt_change(""" 543 | The Dialog is a stateless component. All event handlers 544 | had to be defined in the parent LiveView. 545 | """) 546 | 547 | assert_formatter_doesnt_change(""" 548 | Surface v{surface_version()} - 549 | github.com/msaraiva/surface. 550 | """) 551 | 552 | assert_formatter_doesnt_change(""" 553 | This Dialog is a stateful component. Cool! 554 | """) 555 | 556 | assert_formatter_doesnt_change(""" 557 | 558 |
559 | A simple card component 560 |
561 | 562 | This is the same Card component but now we're using 563 | typed slotables instead of templates. 564 | 565 | 569 |
570 | """) 571 | end 572 | 573 | test "when element content and tags aren't left on the same line, the next sibling is pushed to its own line" do 574 | assert_formatter_outputs( 575 | """ 576 |
Hello
{ 1 + 1 }

Goodbye

577 | """, 578 | """ 579 |
580 |
581 | Hello 582 |
583 | {1 + 1}

Goodbye

584 |
585 | """ 586 | ) 587 | 588 | assert_formatter_outputs( 589 | """ 590 |

Hello

{ 1 + 1 }

Goodbye

591 | """, 592 | """ 593 |
594 |

595 | Hello 596 |

597 | {1 + 1}

Goodbye

598 |
599 | """ 600 | ) 601 | end 602 | 603 | test "(bugfix) newlines aren't removed for no reason" do 604 | assert_formatter_doesnt_change(""" 605 | 606 | 607 | Example 1 608 | 609 | 610 | Example 2 611 | 612 | 613 | """) 614 | end 615 | 616 | test "whitespace padding in code comments is normalized" do 617 | assert_formatter_outputs( 618 | """ 619 | {!-- testing --} 620 | 621 | """, 622 | """ 623 | {!-- testing --} 624 | 625 | """ 626 | ) 627 | end 628 | 629 | test "multiline code comments are rendered as-is (except aligning indentation) to avoid false assumptions about how developers want to format comments" do 630 | # The ending state of these comments is a bit quirky. 631 | # The formatter refuses to make assumptions about the whitespace in 632 | # multiline comments, so it renders them verbatim. However, it renders 633 | # the beginning "tag" indented "properly" as a child of its parent. 634 | # Developers can respond to this by adjusting the contents in relation 635 | # to the opening "tag". 636 | # 637 | # This is identical to how whitespace is handled for
//<#MacroComponent>
 638 |       assert_formatter_outputs(
 639 |         """
 640 |         
641 | {!-- 642 | 643 | 644 | testing 645 | 646 | 647 | --} 648 | 654 |
655 | """, 656 | """ 657 |
658 | {!-- 659 | 660 | 661 | testing 662 | 663 | 664 | --} 665 | 671 |
672 | """ 673 | ) 674 | end 675 | end 676 | 677 | describe "[expressions]" do 678 | test "Elixir expressions retain the original code snippet" do 679 | assert_formatter_outputs( 680 | """ 681 |
682 | {"hello "<>"dolly"} 683 |
684 | 685 | 686 | 687 | 688 | """, 689 | """ 690 |
691 | {"hello " <> "dolly"} 692 |
693 | """ 694 | ) 695 | end 696 | 697 | test "shorthand surface syntax (invisible []) is formatted by Elixir code formatter" do 698 | assert_formatter_outputs( 699 | "
", 700 | "
" 701 | ) 702 | end 703 | 704 | test "expressions in attributes" do 705 | assert_formatter_outputs( 706 | """ 707 |
708 | """, 709 | """ 710 |
711 | """ 712 | ) 713 | 714 | assert_formatter_outputs( 715 | """ 716 |
717 | """, 718 | """ 719 |
724 | """ 725 | ) 726 | end 727 | 728 | test "expressions in attributes of deeply nested elements" do 729 | assert_formatter_outputs( 730 | """ 731 |
732 |
733 |

734 |

735 |
736 | """, 737 | """ 738 |
739 |
740 |

745 |

746 |
747 | """ 748 | ) 749 | end 750 | 751 | test "interpolation in string attributes" do 752 | # Note that the formatter does not remove the extra whitespace at the end of the string. 753 | # We have no context about whether the whitespace in the given attribute is significant, 754 | # so we might break code by modifying it. Therefore, the contents of string attributes 755 | # are left alone other than formatting interpolated expressions. 756 | assert_formatter_outputs( 757 | """ 758 | 759 | """, 760 | """ 761 | 762 | """ 763 | ) 764 | end 765 | 766 | test "numbers are formatted with underscores per the Elixir formatter" do 767 | assert_formatter_outputs( 768 | """ 769 | 770 | """, 771 | """ 772 | 773 | """ 774 | ) 775 | end 776 | 777 | test "attribute expressions that are a list merged with a keyword list" do 778 | assert_formatter_outputs( 779 | """ 780 | 781 | """, 782 | """ 783 | 784 | """ 785 | ) 786 | end 787 | 788 | test "attribute expressions with a function call that omits parentheses" do 789 | assert_formatter_outputs( 790 | """ 791 | 792 | """, 793 | """ 794 | 795 | """ 796 | ) 797 | end 798 | 799 | test "expressions that line-wrap are indented properly" do 800 | assert_formatter_outputs( 801 | """ 802 | 803 | { link "Log out", to: Routes.user_session_path(Endpoint, :delete), method: :delete, class: "container"} 804 | 805 | """, 806 | """ 807 | 808 | {link("Log out", 809 | to: Routes.user_session_path(Endpoint, :delete), 810 | method: :delete, 811 | class: "container" 812 | )} 813 | 814 | """ 815 | ) 816 | end 817 | 818 | test "(bugfix) attribute expressions that are keyword lists without brackets, with interpolated string keys" do 819 | assert_formatter_outputs( 820 | ~S""" 821 | 822 | """, 823 | ~S""" 824 | 825 | """ 826 | ) 827 | end 828 | 829 | test "string literals in attributes are not wrapped in expression brackets" do 830 | assert_formatter_outputs( 831 | """ 832 | 833 | """, 834 | """ 835 | 836 | """ 837 | ) 838 | end 839 | 840 | test "an expression with only a code comment is turned into a Surface code comment" do 841 | assert_formatter_outputs( 842 | """ 843 | { # Foo} 844 | """, 845 | """ 846 | {!-- Foo --} 847 | """ 848 | ) 849 | end 850 | 851 | test "dynamic attributes" do 852 | assert_formatter_outputs( 853 | """ 854 | 855 | """, 856 | """ 857 | 858 | """ 859 | ) 860 | 861 | assert_formatter_outputs( 862 | """ 863 |
864 | """, 865 | """ 866 |
867 | """ 868 | ) 869 | 870 | assert_formatter_outputs( 871 | """ 872 |
873 | """, 874 | """ 875 |
876 | """ 877 | ) 878 | 879 | assert_formatter_outputs( 880 | """ 881 | 882 | """, 883 | """ 884 | 885 | """ 886 | ) 887 | 888 | # keyword with implicit brackets 889 | assert_formatter_outputs( 890 | """ 891 | 892 | """, 893 | """ 894 | 895 | """ 896 | ) 897 | 898 | # keyword with explicit brackets 899 | assert_formatter_outputs( 900 | """ 901 | 902 | """, 903 | """ 904 | 905 | """ 906 | ) 907 | 908 | # demonstrate that <#slot :args={@foo} /> isn't collapsed 909 | assert_formatter_doesnt_change(""" 910 | <#slot :args={@foo} /> 911 | """) 912 | end 913 | 914 | test "shorthand assigns passthrough attributes" do 915 | assert_formatter_outputs( 916 | """ 917 | 918 | """, 919 | """ 920 | 921 | """ 922 | ) 923 | 924 | assert_formatter_outputs( 925 | """ 926 | 927 | """, 928 | """ 929 | 930 | """ 931 | ) 932 | 933 | # demonstrate that the formatter is unopinionated about short or longhand 934 | # in this scenario 935 | assert_formatter_outputs( 936 | """ 937 | 938 | """, 939 | """ 940 | 941 | """ 942 | ) 943 | end 944 | 945 | test "root prop" do 946 | assert_formatter_outputs( 947 | """ 948 | 10 } /> 949 | """, 950 | """ 951 | 10} /> 952 | """ 953 | ) 954 | end 955 | 956 | test "pin operator in expressions" do 957 | assert_formatter_outputs( 958 | """ 959 |
960 | {^ foo} 961 |
962 | """, 963 | """ 964 |
965 | {^foo} 966 |
967 | """ 968 | ) 969 | 970 | assert_formatter_outputs( 971 | """ 972 |
973 |
974 | {^content_ast} 975 |
976 |
977 | {^code_ast} 978 |
979 |
980 | """, 981 | """ 982 |
983 |
984 | {^content_ast} 985 |
986 |
987 | {^code_ast} 988 |
989 |
990 | """ 991 | ) 992 | 993 | assert_formatter_outputs( 994 | """ 995 |
{^code_content}
996 | """, 997 | """ 998 |
{^code_content}
999 | """ 1000 | ) 1001 | end 1002 | 1003 | test ":hook directive without any attribute" do 1004 | assert_formatter_doesnt_change(""" 1005 |
1006 | """) 1007 | end 1008 | 1009 | test "true boolean attribute in directive" do 1010 | assert_formatter_doesnt_change(""" 1011 |
1012 | """) 1013 | end 1014 | 1015 | test "strings in attribute expressions with keyword shorthand aren't modified" do 1016 | # By putting at least 2 attributes in the following examples, 1017 | # we make sure to hit `Surface.Formatter.Phases.Render.quoted_strings_with_newlines/1` 1018 | # which is only used with multiple attributes. 1019 | # 1020 | # Rendering a multi-attribute node involves extra specialized logic 1021 | # for dealing with newlines in strings properly. 1022 | 1023 | assert_formatter_doesnt_change(""" 1024 | 1030 | """) 1031 | 1032 | # deeply nested in blocks 1033 | assert_formatter_doesnt_change(""" 1034 | 1046 | """) 1047 | 1048 | assert_formatter_doesnt_change(""" 1049 | 1053 | with "qux" <- @baz, 1054 | :ok <- value?() do 1055 | cond do 1056 | @test != "newliney 1057 | " -> 1058 | "pass" 1059 | end 1060 | end 1061 | 1062 | :baz -> 1063 | nil 1064 | end} 1065 | /> 1066 | """) 1067 | end 1068 | end 1069 | 1070 | describe "[blocks]" do 1071 | test "if../if block expressions" do 1072 | assert_formatter_outputs( 1073 | """ 1074 |
1075 | {#if @greet} 1076 |

1077 | Hello 1078 |

1079 | {/if} 1080 |
1081 | """, 1082 | """ 1083 |
1084 |
1085 | {#if @greet} 1086 |

1087 | Hello 1088 |

1089 | {/if} 1090 |
1091 |
1092 | """ 1093 | ) 1094 | end 1095 | 1096 | test "if..elseif..else../if block expressions" do 1097 | assert_formatter_outputs( 1098 | """ 1099 | {#if @value == 0} 1100 | 1101 |
1102 | Value {@value} is 0 1103 |
1104 | 1105 | 1106 | 1107 | 1108 | {#elseif @value > 0 } 1109 |
1110 | 1111 | Value {@value} is greater than 0 1112 | 1113 |
1114 | {#else} 1115 | 1116 | 1117 |
1118 | 1119 | Value {@value} is lower than 0 1120 |
1121 | {/if} 1122 | """, 1123 | """ 1124 | {#if @value == 0} 1125 |
1126 | Value {@value} is 0 1127 |
1128 | {#elseif @value > 0} 1129 |
1130 | Value {@value} is greater than 0 1131 |
1132 | {#else} 1133 |
1134 | Value {@value} is lower than 0 1135 |
1136 | {/if} 1137 | """ 1138 | ) 1139 | end 1140 | 1141 | test "unless../unless block expressions" do 1142 | assert_formatter_outputs( 1143 | """ 1144 |
1145 | {#unless @new_user} 1146 |

1147 | Welcome back! 1148 |

1149 | {/unless} 1150 |
1151 | """, 1152 | """ 1153 |
1154 |
1155 | {#unless @new_user} 1156 |

1157 | Welcome back! 1158 |

1159 | {/unless} 1160 |
1161 |
1162 | """ 1163 | ) 1164 | end 1165 | 1166 | test "for..else../for block expressions" do 1167 | assert_formatter_outputs( 1168 | """ 1169 | {#for item <- @items} 1170 | 1171 | Item: {item} 1172 | {#else } 1173 | No items 1174 | {/for} 1175 | """, 1176 | """ 1177 | {#for item <- @items} 1178 | Item: {item} 1179 | {#else} 1180 | No items 1181 | {/for} 1182 | """ 1183 | ) 1184 | end 1185 | 1186 | test "for..else../for block expressions with multi-line generator" do 1187 | assert_formatter_outputs( 1188 | """ 1189 | {#for item <- @some_prop.items, 1190 | item.type == Some.Long.Complicated.Atom, 1191 | value = item.some_item_property} 1192 | 1193 | Item: {item} 1194 | {#else } 1195 | No items 1196 | {/for} 1197 | """, 1198 | """ 1199 | {#for item <- @some_prop.items, 1200 | item.type == Some.Long.Complicated.Atom, 1201 | value = item.some_item_property} 1202 | Item: {item} 1203 | {#else} 1204 | No items 1205 | {/for} 1206 | """ 1207 | ) 1208 | end 1209 | 1210 | test "case block expressions" do 1211 | assert_formatter_outputs( 1212 | """ 1213 | {#case @value } 1214 | 1215 | {#match [first|_]} 1216 |
1217 | First {first} 1218 |
1219 | 1220 | {#match []} 1221 | 1222 | 1223 |
1224 | Value is empty 1225 |
1226 | 1227 | {#match "string"} 1228 | 1229 |

String match

1230 | 1231 | {#match _} 1232 | 1233 | Value is something else 1234 | 1235 | 1236 | {/case} 1237 | """, 1238 | """ 1239 | {#case @value} 1240 | {#match [first | _]} 1241 |
1242 | First {first} 1243 |
1244 | {#match []} 1245 |
1246 | Value is empty 1247 |
1248 | {#match "string"} 1249 |

String match

1250 | {#match _} 1251 | Value is something else 1252 | {/case} 1253 | """ 1254 | ) 1255 | end 1256 | 1257 | test "nested blocks" do 1258 | assert_formatter_outputs( 1259 | """ 1260 | {#if @value == 0} 1261 | {#if @yell} 1262 |
1263 | VALUE {@value} IS 0 1264 |
1265 | {#else} 1266 | {#if @whisper} 1267 |
1268 | {@value}...0 1269 |
1270 | {#else} 1271 |
1272 | Value {@value} is 0 1273 |
1274 | {/if} 1275 | {/if} 1276 | {#else} 1277 |
1278 | Value {@value} is lower than 0 1279 |
1280 | {/if} 1281 | """, 1282 | """ 1283 | {#if @value == 0} 1284 | {#if @yell} 1285 |
1286 | VALUE {@value} IS 0 1287 |
1288 | {#else} 1289 | {#if @whisper} 1290 |
1291 | {@value}...0 1292 |
1293 | {#else} 1294 |
1295 | Value {@value} is 0 1296 |
1297 | {/if} 1298 | {/if} 1299 | {#else} 1300 |
1301 | Value {@value} is lower than 0 1302 |
1303 | {/if} 1304 | """ 1305 | ) 1306 | 1307 | assert_formatter_outputs( 1308 | """ 1309 | {#case @foo} 1310 | {#match 1} 1311 | {#case @bar} 1312 | {#match 2} 1313 |
1314 | foo is 1 and bar is 2 1315 |
1316 | {#match _} 1317 |
1318 | bar is not 2 1319 |
1320 | {/case} 1321 | {#match _} 1322 | foo is not 1 1323 | {/case} 1324 | """, 1325 | """ 1326 | {#case @foo} 1327 | {#match 1} 1328 | {#case @bar} 1329 | {#match 2} 1330 |
1331 | foo is 1 and bar is 2 1332 |
1333 | {#match _} 1334 |
1335 | bar is not 2 1336 |
1337 | {/case} 1338 | {#match _} 1339 | foo is not 1 1340 | {/case} 1341 | """ 1342 | ) 1343 | end 1344 | 1345 | test "line breaks in blocks" do 1346 | assert_formatter_outputs( 1347 | """ 1348 |
1349 |
1350 |

1351 | {#if not is_nil(@some_assign.foo) or not is_nil(@some_assign.bar) or not is_nil(@some_assign.bazzzzzzz.qux)} 1352 | Hello 1353 | {/if} 1354 |

1355 |
1356 |
1357 | """, 1358 | """ 1359 |
1360 |
1361 |

1362 | {#if not is_nil(@some_assign.foo) or not is_nil(@some_assign.bar) or 1363 | not is_nil(@some_assign.bazzzzzzz.qux)} 1364 | Hello 1365 | {/if} 1366 |

1367 |
1368 |
1369 | """ 1370 | ) 1371 | end 1372 | 1373 | test "slots" do 1374 | assert_formatter_outputs( 1375 | """ 1376 |
1377 | <#slot name="header"> 1378 |

1379 | {@title} 1380 |

1381 | 1382 | 1383 | <#slot /> 1384 |
1385 | """, 1386 | """ 1387 |
1388 | <#slot name="header"> 1389 |

1393 | {@title} 1394 |

1395 | 1396 | 1397 | <#slot /> 1398 |
1399 | """ 1400 | ) 1401 | end 1402 | end 1403 | 1404 | test "self closing macro components are preserved" do 1405 | assert_formatter_doesnt_change(""" 1406 | <#MacroComponent /> 1407 | """) 1408 | end 1409 | 1410 | test "indent option" do 1411 | assert_formatter_outputs( 1412 | """ 1413 |

1414 | 1415 | Indented 1416 | 1417 |

1418 | """, 1419 | """ 1420 |

1421 | 1422 | Indented 1423 | 1424 |

1425 | """, 1426 | indent: 3 1427 | ) 1428 | end 1429 | 1430 | test "for docs" do 1431 | assert_formatter_outputs( 1432 | """ 1433 | 1434 | 1435 | {!-- Surface private comment (does not hit the browser) --} 1436 | 1437 | 1438 | 1439 |
1441 |

Text inside paragraph

1442 | Text touching parent tags 1443 |
1444 | 1445 | 1446 | Default slot contents 1447 | 1448 |
1449 | """, 1450 | """ 1451 | 1456 | 1457 | {!-- Surface private comment (does not hit the browser) --} 1458 | 1459 |
1460 |

1461 | Text inside paragraph 1462 |

1463 | Text touching parent tags 1464 |
1465 | 1466 | 1472 | Default slot contents 1473 | 1474 |
1475 | """ 1476 | ) 1477 | 1478 | assert_formatter_outputs( 1479 | """ 1480 |

Hello

1481 | """, 1482 | """ 1483 |
1484 |

Hello

1485 |
1486 | """ 1487 | ) 1488 | 1489 | assert_formatter_outputs( 1490 | """ 1491 |

Hello

1492 | 1493 | 1494 | 1495 | 1496 | 1497 |

Goodbye

1498 | """, 1499 | """ 1500 |

Hello

1501 | 1502 |

Goodbye

1503 | """ 1504 | ) 1505 | 1506 | assert_formatter_outputs( 1507 | """ 1508 |
1509 |

Hello

1510 |

and

1511 | 1512 | 1513 | 1514 | 1515 | 1516 |

Goodbye

1517 |
1518 | """, 1519 | """ 1520 |
1521 |

Hello

1522 |

and

1523 | 1524 |

Goodbye

1525 |
1526 | """ 1527 | ) 1528 | end 1529 | 1530 | test "void elements do not have slash in single tag" do 1531 | assert_formatter_outputs( 1532 | """ 1533 | 1534 | 1535 |
1536 | 1537 |
1538 | 1539 | 1540 | 1541 | 1542 | 1543 | 1544 | 1545 | 1546 | """, 1547 | """ 1548 | 1549 | 1550 |
1551 | 1552 |
1553 | 1554 | 1555 | 1556 | 1557 | 1558 | 1559 | 1560 | 1561 | """ 1562 | ) 1563 | end 1564 | 1565 | test "<#template slot=\"...\"> is formatted in shorthand syntax" do 1566 | assert_formatter_outputs( 1567 | """ 1568 |
1569 | <#template slot="header" :let={value: value}> Foo 1570 | 1571 | <#template> Foo 1572 |
1573 | """, 1574 | """ 1575 |
1576 | <:header :let={value: value}> 1577 | Foo 1578 | 1579 | 1580 | <#template> 1581 | Foo 1582 | 1583 |
1584 | """ 1585 | ) 1586 | end 1587 | 1588 | test "