├── .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 |288 |287 |
289 | {@data}
290 |
291 |
292 |
293 | <#MacroComponent> Foo {@bar} baz #MacroComponent>
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 | #Macro>
320 | """,
321 | """
322 |
323 | Hello world
324 |
325 |
326 |
327 | Hello world
328 |
329 |
330 | <#Macro>
331 | Hello world
332 | #Macro>
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 |
979 |
980 | """,
981 | """
982 |
983 |
984 | {^content_ast}
985 |
986 |
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 | #slot>
1382 |
1383 | <#slot />
1384 |
1385 | """,
1386 | """
1387 |
1388 | <#slot name="header">
1389 |
1393 | {@title}
1394 |
1395 | #slot>
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 #template>
1570 |
1571 | <#template> Foo #template>
1572 |
1573 | """,
1574 | """
1575 |
1576 | <:header :let={value: value}>
1577 | Foo
1578 |
1579 |
1580 | <#template>
1581 | Foo
1582 | #template>
1583 |
1584 | """
1585 | )
1586 | end
1587 |
1588 | test " tags are not rendered as <#template>" do
1589 | assert_formatter_outputs(
1590 | """
1591 |
1592 | Foo
1593 |
1594 | """,
1595 | """
1596 |
1597 |
1598 | Foo
1599 |
1600 |
1601 | """
1602 | )
1603 | end
1604 |
1605 | test "Multi-line strings in attributes aren't indented every time the formatter is ran" do
1606 | # multiple attributes
1607 | assert_formatter_doesnt_change(~S"""
1608 |
1615 | """)
1616 |
1617 | # single attribute
1618 | assert_formatter_doesnt_change(~S"""
1619 |
1623 | """)
1624 |
1625 | # nested multiline strings
1626 | assert_formatter_doesnt_change(~S"""
1627 |
1635 | """)
1636 |
1637 | # lists in attributes
1638 | assert_formatter_doesnt_change(~S"""
1639 |
1640 |
1641 | {!-- indenting this component allowed us to reproduce a bug --}
1642 |
1663 |
1664 |
1665 | """)
1666 |
1667 | assert_formatter_outputs(
1668 | ~S"""
1669 |
1680 | """,
1681 | ~S"""
1682 |
1691 | """
1692 | )
1693 |
1694 | assert_formatter_doesnt_change(~S"""
1695 |
1707 | """)
1708 |
1709 | assert_formatter_doesnt_change(~S"""
1710 |
1719 | """)
1720 | end
1721 |
1722 | test "newlines without trailing whitespace in formatted attribute expressions" do
1723 | # This demonstrates a bugfix where the empty newline in between case clauses
1724 | # would be indented with spaces even though there were no contents on the line.
1725 | assert_formatter_outputs(
1726 | ~S"""
1727 |
1729 | true
1730 | "baz" ->
1731 | false
1732 | end} />
1733 | """,
1734 | ~S"""
1735 |
1739 | true
1740 |
1741 | "baz" ->
1742 | false
1743 | end}
1744 | />
1745 | """
1746 | )
1747 | end
1748 |
1749 | test "newlines in strings passed to function calls" do
1750 | assert_formatter_doesnt_change(~S"""
1751 |
1752 | {foo("bar
1753 | baz
1754 | qux
1755 | ")}
1756 |
1757 | """)
1758 | end
1759 | end
1760 |
--------------------------------------------------------------------------------
/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
--------------------------------------------------------------------------------