diff --git a/spec/compiler/crystal/tools/expand_spec.cr b/spec/compiler/crystal/tools/expand_spec.cr index e8f9b770f3ec..4ebdfff6b836 100644 --- a/spec/compiler/crystal/tools/expand_spec.cr +++ b/spec/compiler/crystal/tools/expand_spec.cr @@ -157,7 +157,7 @@ describe "expand" do {% end %} CRYSTAL - assert_expand_simple code, "1\n2\n3\n" + assert_expand_simple code, "1\n\n2\n\n3\n" end it "expands macro control {% for %} with cursor inside it" do @@ -167,7 +167,7 @@ describe "expand" do {% end %} CRYSTAL - assert_expand_simple code, "1\n2\n3\n" + assert_expand_simple code, "1\n\n2\n\n3\n" end it "expands macro control {% for %} with cursor at end of it" do @@ -177,7 +177,7 @@ describe "expand" do ‸{% end %} CRYSTAL - assert_expand_simple code, "1\n2\n3\n" + assert_expand_simple code, "1\n\n2\n\n3\n" end it "expands macro control {% for %} with indent" do @@ -195,7 +195,7 @@ describe "expand" do {% end %} CRYSTAL - assert_expand_simple code, original: original, expanded: "1\n2\n3\n" + assert_expand_simple code, original: original, expanded: "1\n\n2\n\n3\n" end it "expands simple macro" do @@ -258,7 +258,7 @@ describe "expand" do ‸foo CRYSTAL - assert_expand_simple code, original: "foo", expanded: %("if true"\n"1"\n"2"\n"3"\n) + assert_expand_simple code, original: "foo", expanded: %("if true"\n\n\n"1"\n\n"2"\n\n"3"\n) end it "expands macros with 2 level" do @@ -615,6 +615,7 @@ describe "expand" do def hello_str "hello" end + # symbol of hello def hello_sym :hello diff --git a/spec/compiler/parser/to_s_spec.cr b/spec/compiler/parser/to_s_spec.cr index 86464e197267..d6d2ca79780d 100644 --- a/spec/compiler/parser/to_s_spec.cr +++ b/spec/compiler/parser/to_s_spec.cr @@ -1,7 +1,7 @@ require "../../support/syntax" -private def expect_to_s(original, expected = original, emit_doc = false, file = __FILE__, line = __LINE__) - it "does to_s of #{original.inspect}", file, line do +private def expect_to_s(original, expected = original, emit_doc = false, file = __FILE__, line = __LINE__, focus = false) + it "does to_s of #{original.inspect}", file, line, focus: focus do str = IO::Memory.new expected.bytesize source = original @@ -246,8 +246,137 @@ describe "ASTNode#to_s" do expect_to_s "1.+(&block)" expect_to_s "1.//(2, a: 3)" expect_to_s "1.//(2, &block)" - expect_to_s %({% verbatim do %}\n 1{{ 2 }}\n 3{{ 4 }}\n{% end %}) + expect_to_s <<-'CR' + {% verbatim do %} + 1{{ 2 }} + 3{{ 4 }} + {% end %} + CR + expect_to_s %({% for foo in bar %}\n {{ if true\n foo\n bar\nend }}\n{% end %}) + expect_to_s "{% a = 1 %}" + expect_to_s "{{ a = 1 }}" + expect_to_s "{%\n 1\n 2\n 3\n%}" + expect_to_s "{%\n 1\n%}" + expect_to_s "{%\n 2 + 2\n%}" + expect_to_s "{%\n a = 1 %}", "{%\n a = 1\n%}" + expect_to_s "{% a = 1\n%}", "{% a = 1 %}" + + expect_to_s <<-'CR', <<-'CR' + macro finished + {% verbatim do %} + {% + 10 + + # Foo + + 20 + %} + {% end %} + end + CR + macro finished + {% verbatim do %} + {% + 10 + + + + 20 + %} + {% end %} + end + CR + + expect_to_s <<-'CR', <<-'CR' + macro finished + {% verbatim do %} + {% + 10 + + # Foo + 20 + %} + {% end %} + end + CR + macro finished + {% verbatim do %} + {% + 10 + + + 20 + %} + {% end %} + end + CR + + expect_to_s <<-'CR', <<-'CR' + macro finished + {% verbatim do %} + {% + 10 + + # Foo + + 20 + 30 + + # Bar + + 40 + %} + {% + 50 + 60 + %} + {% end %} + end + CR + macro finished + {% verbatim do %} + {% + 10 + + + + 20 + 30 + + + + 40 + %} + {% + 50 + 60 + %} + {% end %} + end + CR + + expect_to_s <<-'CR' + macro finished + {% verbatim do %} + {% + 10 + 20 + %} + {% end %} + end + CR + + expect_to_s <<-'CR' + macro finished + {% verbatim do %} + {% + 10 + %} + {% end %} + end + CR + expect_to_s %(asm("nop" ::::)) expect_to_s %(asm("nop" : "a"(1), "b"(2) : "c"(3), "d"(4) : "e", "f" : "volatile", "alignstack", "intel")) expect_to_s %(asm("nop" :: "c"(3), "d"(4) ::)) diff --git a/spec/compiler/semantic/restrictions_augmenter_spec.cr b/spec/compiler/semantic/restrictions_augmenter_spec.cr index 2b7250658693..d77b61327f82 100644 --- a/spec/compiler/semantic/restrictions_augmenter_spec.cr +++ b/spec/compiler/semantic/restrictions_augmenter_spec.cr @@ -78,7 +78,9 @@ describe "Semantic: restrictions augmenter" do class Baz end end + @x : Bar::Baz + def initialize(value : ::Foo::Bar::Baz) @x = value end @@ -110,7 +112,9 @@ describe "Semantic: restrictions augmenter" do class Baz end end + @x : Bar::Baz + def initialize(value : Bar::Baz) @x = value end @@ -400,8 +404,10 @@ describe "Semantic: restrictions augmenter" do macro foo {{ yield }} end + class Foo end + class Bar @x : Foo def initialize(value : ::Foo) diff --git a/src/compiler/crystal/syntax/to_s.cr b/src/compiler/crystal/syntax/to_s.cr index 4ce9ca7efc43..ae3576051a87 100644 --- a/src/compiler/crystal/syntax/to_s.cr +++ b/src/compiler/crystal/syntax/to_s.cr @@ -55,6 +55,21 @@ module Crystal true end + private def write_extra_newlines(first_node_location : Location?, second_node_location : Location?, &) : Nil + # If any location information is missing, don't add any extra newlines. + if !first_node_location || !second_node_location + yield + return + end + + # Only write the "extra" newlines. I.e. If there are more than one. The first newline is handled directly via the Expressions visitor. + ((second_node_location.line_number - 1) - first_node_location.line_number).times do + newline + end + + yield + end + def visit(node : Nop) false end @@ -221,11 +236,17 @@ module Crystal if @inside_macro > 0 node.expressions.each &.accept self else + last_node = nil + node.expressions.each_with_index do |exp, i| unless exp.nop? - append_indent unless node.keyword.paren? && i == 0 - exp.accept self - newline unless node.keyword.paren? && i == node.expressions.size - 1 + self.write_extra_newlines((last_node || exp).end_location, exp.location) do + append_indent unless node.keyword.paren? && i == 0 + exp.accept self + newline unless node.keyword.paren? && i == node.expressions.size - 1 + end + + last_node = exp end end end @@ -717,8 +738,10 @@ module Crystal end newline - inside_macro do - accept node.body + with_indent do + inside_macro do + accept node.body + end end # newline @@ -728,13 +751,34 @@ module Crystal end def visit(node : MacroExpression) - @str << (node.output? ? "{{" : "{% ") - @str << ' ' if node.output? + # The node is considered multiline if its starting location is on a different line than its expression. + is_multiline = (start_loc = node.location) && (end_loc = node.exp.location) && end_loc.line_number > start_loc.line_number + + @str << (node.output? ? "{{ " : is_multiline ? "{%" : "{% ") + + if is_multiline + newline + @indent += 1 + end + outside_macro do + # If the MacroExpression consists of a single node we need to manually handle appending indent and trailing newline if *is_multiline* + # Otherwise, the Expressions logic handles that for us + if is_multiline && !node.exp.is_a?(Expressions) + append_indent + end + node.exp.accept self end - @str << ' ' if node.output? - @str << (node.output? ? "}}" : " %}") + + # If the opening tag has a newline after it, force trailing tag to have one as well + if is_multiline + @indent -= 1 + newline if !node.exp.is_a? Expressions + append_indent + end + + @str << (node.output? ? " }}" : is_multiline ? "%}" : " %}") false end @@ -790,9 +834,13 @@ module Crystal def visit(node : MacroVerbatim) @str << "{% verbatim do %}" - inside_macro do - node.exp.accept self + + with_indent do + inside_macro do + node.exp.accept self + end end + @str << "{% end %}" false end