diff --git a/generator/generator.go b/generator/generator.go
index 943c695b5..f2f2ded27 100644
--- a/generator/generator.go
+++ b/generator/generator.go
@@ -528,6 +528,9 @@ func (g *generator) writeNodes(indentLevel int, nodes []parser.Node, next parser
}
func (g *generator) writeNode(indentLevel int, current parser.Node, next parser.Node) (err error) {
+ maybeWhitespace := true
+ forceWhitespace := false
+
switch n := current.(type) {
case parser.DocType:
err = g.writeDocType(indentLevel, n)
@@ -540,14 +543,25 @@ func (g *generator) writeNode(indentLevel int, current parser.Node, next parser.
case parser.RawElement:
err = g.writeRawElement(indentLevel, n)
case parser.ForExpression:
+ maybeWhitespace = false
err = g.writeForExpression(indentLevel, n, next)
case parser.CallTemplateExpression:
err = g.writeCallTemplateExpression(indentLevel, n)
case parser.TemplElementExpression:
err = g.writeTemplElementExpression(indentLevel, n)
+
+ // TemplElementExpression with block should always have whitespace if the next element is also
+ // a TemplElementExpression
+ if len(n.Children) > 0 {
+ if _, ok := next.(parser.TemplElementExpression); ok {
+ forceWhitespace = true
+ }
+ }
case parser.IfExpression:
+ maybeWhitespace = false
err = g.writeIfExpression(indentLevel, n, next)
case parser.SwitchExpression:
+ maybeWhitespace = false
err = g.writeSwitchExpression(indentLevel, n, next)
case parser.StringExpression:
err = g.writeStringExpression(indentLevel, n.Expression)
@@ -566,8 +580,10 @@ func (g *generator) writeNode(indentLevel int, current parser.Node, next parser.
// Write trailing whitespace, if there is a next node that might need the space.
// If the next node is inline or text, we might need it.
// If the current node is a block element, we don't need it.
- needed := (isInlineOrText(current) && isInlineOrText(next))
- if ws, ok := current.(parser.WhitespaceTrailer); ok && needed {
+ // If, switch and for as current node skip whitespace, but not always when next node.
+ neededWhitespace := forceWhitespace || (maybeWhitespace && isInlineOrText(current) && isInlineOrText(next))
+
+ if ws, ok := current.(parser.WhitespaceTrailer); ok && neededWhitespace {
if err := g.writeWhitespaceTrailer(indentLevel, ws.Trailing()); err != nil {
return err
}
@@ -604,7 +620,7 @@ func (g *generator) writeWhitespaceTrailer(indentLevel int, n parser.TrailingSpa
}
// Normalize whitespace for minified output. In HTML, a single space is equivalent to
// any number of spaces, tabs, or newlines.
- if n == parser.SpaceVertical {
+ if n == parser.SpaceVertical || n == parser.SpaceVerticalDouble {
n = parser.SpaceHorizontal
}
if _, err = g.w.WriteStringLiteral(indentLevel, string(n)); err != nil {
diff --git a/parser/v2/calltemplateparser.go b/parser/v2/calltemplateparser.go
index 3e82a2064..db4e698cc 100644
--- a/parser/v2/calltemplateparser.go
+++ b/parser/v2/calltemplateparser.go
@@ -29,5 +29,11 @@ func (p callTemplateExpressionParser) Parse(pi *parse.Input) (n Node, ok bool, e
return
}
+ // Parse trailing whitespace.
+ r.TrailingSpace, err = parseTrailingSpace(pi, true, false)
+ if err != nil {
+ return r, false, err
+ }
+
return r, true, nil
}
diff --git a/parser/v2/calltemplateparser_test.go b/parser/v2/calltemplateparser_test.go
index 765d91500..22319114e 100644
--- a/parser/v2/calltemplateparser_test.go
+++ b/parser/v2/calltemplateparser_test.go
@@ -76,6 +76,28 @@ func TestCallTemplateExpressionParser(t *testing.T) {
},
},
},
+ {
+ name: "call: can parse the initial expression and leave the text",
+ input: `{!Other(p.Test)} Home`,
+ expected: CallTemplateExpression{
+ Expression: Expression{
+ Value: "Other(p.Test)",
+ Range: Range{
+ From: Position{
+ Index: 2,
+ Line: 0,
+ Col: 2,
+ },
+ To: Position{
+ Index: 15,
+ Line: 0,
+ Col: 15,
+ },
+ },
+ },
+ TrailingSpace: SpaceHorizontal,
+ },
+ },
}
for _, tt := range tests {
tt := tt
diff --git a/parser/v2/elementparser.go b/parser/v2/elementparser.go
index 3c3e73d3f..d5796a112 100644
--- a/parser/v2/elementparser.go
+++ b/parser/v2/elementparser.go
@@ -467,11 +467,7 @@ func addTrailingSpaceAndValidate(start parse.Position, e Element, pi *parse.Inpu
return e, false, err
}
// Add trailing space.
- ws, _, err := parse.Whitespace.Parse(pi)
- if err != nil {
- return e, false, err
- }
- e.TrailingSpace, err = NewTrailingSpace(ws)
+ e.TrailingSpace, err = parseTrailingSpace(pi, true, false)
if err != nil {
return e, false, err
}
diff --git a/parser/v2/elementparser_test.go b/parser/v2/elementparser_test.go
index 0c7796dc7..065f89ab2 100644
--- a/parser/v2/elementparser_test.go
+++ b/parser/v2/elementparser_test.go
@@ -1538,6 +1538,43 @@ func TestElementParser(t *testing.T) {
},
},
},
+ {
+ name: "element: with multiple newlines, should collapse to two",
+ input: `
+
+
+
+
+
+
`,
+ expected: Element{
+ Name: "div",
+ NameRange: Range{
+ From: Position{Index: 1, Line: 0, Col: 1},
+ To: Position{Index: 4, Line: 0, Col: 4},
+ },
+ IndentChildren: true,
+ Children: []Node{
+ Whitespace{Value: "\n\t"},
+ Element{
+ Name: "span",
+ NameRange: Range{
+ From: Position{Index: 8, Line: 1, Col: 2},
+ To: Position{Index: 12, Line: 1, Col: 6},
+ },
+ TrailingSpace: SpaceVerticalDouble,
+ },
+ Element{
+ Name: "span",
+ NameRange: Range{
+ From: Position{Index: 26, Line: 5, Col: 2},
+ To: Position{Index: 30, Line: 5, Col: 6},
+ },
+ TrailingSpace: SpaceVertical,
+ },
+ },
+ },
+ },
{
name: "element: can contain text that starts with for",
input: `for which any
diff --git a/parser/v2/forexpressionparser.go b/parser/v2/forexpressionparser.go
index 3f8bc2ec1..2e498105c 100644
--- a/parser/v2/forexpressionparser.go
+++ b/parser/v2/forexpressionparser.go
@@ -10,7 +10,10 @@ var forExpression parse.Parser[Node] = forExpressionParser{}
type forExpressionParser struct{}
func (forExpressionParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
- var r ForExpression
+ r := ForExpression{
+ // Default behavior is always a trailing space
+ TrailingSpace: SpaceVertical,
+ }
start := pi.Index()
// Strip leading whitespace and look for `for `.
@@ -48,5 +51,11 @@ func (forExpressionParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
return
}
+ // Parse trailing whitespace.
+ r.TrailingSpace, err = parseTrailingSpace(pi, true, true)
+ if err != nil {
+ return r, false, err
+ }
+
return r, true, nil
}
diff --git a/parser/v2/forexpressionparser_test.go b/parser/v2/forexpressionparser_test.go
index 4a012e7b2..630dc17ad 100644
--- a/parser/v2/forexpressionparser_test.go
+++ b/parser/v2/forexpressionparser_test.go
@@ -64,6 +64,7 @@ func TestForExpressionParser(t *testing.T) {
TrailingSpace: SpaceVertical,
},
},
+ TrailingSpace: SpaceVertical,
},
},
{
@@ -117,6 +118,7 @@ func TestForExpressionParser(t *testing.T) {
TrailingSpace: SpaceVertical,
},
},
+ TrailingSpace: SpaceVertical,
},
},
}
diff --git a/parser/v2/formattestdata/calltemplate_newline_is_preserved.txt b/parser/v2/formattestdata/calltemplate_newline_is_preserved.txt
new file mode 100644
index 000000000..d04232fc4
--- /dev/null
+++ b/parser/v2/formattestdata/calltemplate_newline_is_preserved.txt
@@ -0,0 +1,34 @@
+-- in --
+package main
+
+templ test() {
+
+
+
+ {! Other(p.Test) }
+ {!Other(p.Test)} Home
+
+
Some standard templ
+
+{!Other(p.Test) }
+
+
+
+
+
+}
+-- out --
+package main
+
+templ test() {
+
+
+ @Other(p.Test)
+ @Other(p.Test) Home
+
Some standard templ
+
+ @Other(p.Test)
+
+
+
+}
diff --git a/parser/v2/formattestdata/comments_newline_is_preserved.txt b/parser/v2/formattestdata/comments_newline_is_preserved.txt
new file mode 100644
index 000000000..30f7ca4af
--- /dev/null
+++ b/parser/v2/formattestdata/comments_newline_is_preserved.txt
@@ -0,0 +1,31 @@
+-- in --
+package main
+
+templ test() {
+
+
+ // This is not included in the output.
+
Some standard templ
+
+
+ /* This is not included in the output too. */
+ /*
+ Leave this alone.
+ */
+
+
+}
+-- out --
+package main
+
+templ test() {
+
+
+ // This is not included in the output.
+
Some standard templ
+
+ /* This is not included in the output too. */
+ /*
+ Leave this alone.
+ */
+}
diff --git a/parser/v2/formattestdata/element_double_newline_after_component.txt b/parser/v2/formattestdata/element_double_newline_after_component.txt
new file mode 100644
index 000000000..5792f45fd
--- /dev/null
+++ b/parser/v2/formattestdata/element_double_newline_after_component.txt
@@ -0,0 +1,29 @@
+-- in --
+package main
+
+templ x() {
+
+ @Hero()
+
+
+
+ @Hero()
+
+
+
+
+
+}
+-- out --
+package main
+
+templ x() {
+
+ @Hero()
+
+
+ @Hero()
+
+
+
+}
diff --git a/parser/v2/formattestdata/element_double_newline_indent_issue.txt b/parser/v2/formattestdata/element_double_newline_indent_issue.txt
new file mode 100644
index 000000000..4732a5a3b
--- /dev/null
+++ b/parser/v2/formattestdata/element_double_newline_indent_issue.txt
@@ -0,0 +1,25 @@
+-- in --
+package main
+
+templ x() {
+
+
+
+ // This line is indented incorrectly
+
+
+
+}
+
+-- out --
+package main
+
+templ x() {
+
+
+
+ // This line is indented incorrectly
+
+
+
+}
diff --git a/parser/v2/formattestdata/element_double_newline_is_preserved.txt b/parser/v2/formattestdata/element_double_newline_is_preserved.txt
new file mode 100644
index 000000000..383dfc262
--- /dev/null
+++ b/parser/v2/formattestdata/element_double_newline_is_preserved.txt
@@ -0,0 +1,28 @@
+-- in --
+package main
+
+templ x() {
+
+ Hello
+
+ World
+
+
+
+
+ Foo Bar
+
+}
+-- out --
+package main
+
+templ x() {
+
+
+ Hello
+ World
+
+
+ Foo Bar
+
+}
diff --git a/parser/v2/formattestdata/element_double_newline_string_literal_termination_issue.txt b/parser/v2/formattestdata/element_double_newline_string_literal_termination_issue.txt
new file mode 100644
index 000000000..0962cc808
--- /dev/null
+++ b/parser/v2/formattestdata/element_double_newline_string_literal_termination_issue.txt
@@ -0,0 +1,25 @@
+-- in --
+package main
+
+// The below template caused the generated code to not strip newlines, causing error
+templ x() {
+
+}
+-- out --
+package main
+
+// The below template caused the generated code to not strip newlines, causing error
+templ x() {
+
+}
diff --git a/parser/v2/formattestdata/for_loops_are_placed_on_a_new_line.txt b/parser/v2/formattestdata/for_loops_are_placed_on_a_new_line.txt
index 97f7d586f..0ca1de359 100644
--- a/parser/v2/formattestdata/for_loops_are_placed_on_a_new_line.txt
+++ b/parser/v2/formattestdata/for_loops_are_placed_on_a_new_line.txt
@@ -4,7 +4,7 @@ package test
templ input(items []string) {
{ "the" }
{ "other" }
for _, item := range items {
{ item }
-}
+}
After closing bracket
}
-- out --
package test
@@ -16,5 +16,6 @@ templ input(items []string) {
for _, item := range items {
{ item }
}
+ After closing bracket
}
diff --git a/parser/v2/formattestdata/for_loops_newline_is_preserved.txt b/parser/v2/formattestdata/for_loops_newline_is_preserved.txt
new file mode 100644
index 000000000..a0c734b30
--- /dev/null
+++ b/parser/v2/formattestdata/for_loops_newline_is_preserved.txt
@@ -0,0 +1,33 @@
+-- in --
+package main
+
+templ x() {
+
+
+
+ for _, item := range items {
+
+
{ item }
+
+ }
+
+
+
+
Foo Bar
+
+}
+-- out --
+package main
+
+templ x() {
+
+
+
+ for _, item := range items {
+
{ item }
+
+ }
+
+
Foo Bar
+
+}
diff --git a/parser/v2/formattestdata/if_statement_newline_is_preserved.txt b/parser/v2/formattestdata/if_statement_newline_is_preserved.txt
new file mode 100644
index 000000000..fb77235a9
--- /dev/null
+++ b/parser/v2/formattestdata/if_statement_newline_is_preserved.txt
@@ -0,0 +1,37 @@
+-- in --
+package main
+
+templ x() {
+
+
+
+ if true {
+ Test
+
+ } else {
+
+ Test
+ }
+
+
+
+ Foo Bar
+
+}
+-- out --
+package main
+
+templ x() {
+
+
+
+ if true {
+ Test
+
+ } else {
+ Test
+ }
+
+ Foo Bar
+
+}
diff --git a/parser/v2/formattestdata/if_statements_are_placed_on_a_new_line.txt b/parser/v2/formattestdata/if_statements_are_placed_on_a_new_line.txt
index 77a40ba98..6418fd4cb 100644
--- a/parser/v2/formattestdata/if_statements_are_placed_on_a_new_line.txt
+++ b/parser/v2/formattestdata/if_statements_are_placed_on_a_new_line.txt
@@ -6,7 +6,7 @@ templ input(items []string) {
{ items[0] }
} else {
{ items[1] }
- }
+ } After closing brace
}
-- out --
@@ -21,5 +21,6 @@ templ input(items []string) {
} else {
{ items[1] }
}
+ After closing brace
}
diff --git a/parser/v2/formattestdata/switch_newline_is_preserved.txt b/parser/v2/formattestdata/switch_newline_is_preserved.txt
new file mode 100644
index 000000000..b9ebf5289
--- /dev/null
+++ b/parser/v2/formattestdata/switch_newline_is_preserved.txt
@@ -0,0 +1,43 @@
+-- in --
+package main
+
+templ x() {
+
+
+
+ switch items[0] {
+
+
+ case "a":
+
{ items[0] }
+
+
+ case "b":
+
{ items[1] }
+
+ }
+
+
+
+
Foo Bar
+
+}
+-- out --
+package main
+
+templ x() {
+
+
+
+ switch items[0] {
+ case "a":
+
{ items[0] }
+
+ case "b":
+
{ items[1] }
+
+ }
+
+
Foo Bar
+
+}
diff --git a/parser/v2/formattestdata/switch_statements_are_placed_on_a_new_line.txt b/parser/v2/formattestdata/switch_statements_are_placed_on_a_new_line.txt
index bcee5f9cf..2cf814a89 100644
--- a/parser/v2/formattestdata/switch_statements_are_placed_on_a_new_line.txt
+++ b/parser/v2/formattestdata/switch_statements_are_placed_on_a_new_line.txt
@@ -7,7 +7,7 @@ templ input(items []string) {
{ items[0] }
case "b":
{ items[1] }
-}
+}After closing bracket
}
-- out --
package test
@@ -22,5 +22,6 @@ templ input(items []string) {
case "b":
{ items[1] }
}
+ After closing bracket
}
diff --git a/parser/v2/gocodeparser.go b/parser/v2/gocodeparser.go
index 027bcc45d..42c58bd02 100644
--- a/parser/v2/gocodeparser.go
+++ b/parser/v2/gocodeparser.go
@@ -32,11 +32,7 @@ var goCode = parse.Func(func(pi *parse.Input) (n Node, ok bool, err error) {
}
// Parse trailing whitespace.
- ws, _, err := parse.Whitespace.Parse(pi)
- if err != nil {
- return r, false, err
- }
- r.TrailingSpace, err = NewTrailingSpace(ws)
+ r.TrailingSpace, err = parseTrailingSpace(pi, true, false)
if err != nil {
return r, false, err
}
diff --git a/parser/v2/htmlcommentparser.go b/parser/v2/htmlcommentparser.go
index 83853d90d..462fcde39 100644
--- a/parser/v2/htmlcommentparser.go
+++ b/parser/v2/htmlcommentparser.go
@@ -35,5 +35,11 @@ func (p htmlCommentParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
return
}
+ // Parse trailing whitespace.
+ c.TrailingSpace, err = parseTrailingSpace(pi, true, false)
+ if err != nil {
+ return c, false, err
+ }
+
return c, true, nil
}
diff --git a/parser/v2/ifexpressionparser.go b/parser/v2/ifexpressionparser.go
index f8cddb0df..7462a3757 100644
--- a/parser/v2/ifexpressionparser.go
+++ b/parser/v2/ifexpressionparser.go
@@ -12,7 +12,10 @@ var untilElseIfElseOrEnd = parse.Any(StripType(elseIfExpression), StripType(else
type ifExpressionParser struct{}
func (ifExpressionParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
- var r IfExpression
+ r := IfExpression{
+ // Default behavior is always a trailing space
+ TrailingSpace: SpaceVertical,
+ }
start := pi.Index()
if !peekPrefix(pi, "if ") {
@@ -60,6 +63,12 @@ func (ifExpressionParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
return
}
+ // Parse trailing whitespace.
+ r.TrailingSpace, err = parseTrailingSpace(pi, true, true)
+ if err != nil {
+ return r, false, err
+ }
+
return r, true, nil
}
diff --git a/parser/v2/ifexpressionparser_test.go b/parser/v2/ifexpressionparser_test.go
index d80300e41..f6712b4cd 100644
--- a/parser/v2/ifexpressionparser_test.go
+++ b/parser/v2/ifexpressionparser_test.go
@@ -70,6 +70,7 @@ func TestIfExpression(t *testing.T) {
TrailingSpace: SpaceVertical,
},
},
+ TrailingSpace: SpaceVertical,
},
},
{
@@ -136,6 +137,7 @@ func TestIfExpression(t *testing.T) {
TrailingSpace: SpaceVertical,
},
},
+ TrailingSpace: SpaceVertical,
},
},
{
@@ -171,6 +173,7 @@ func TestIfExpression(t *testing.T) {
TrailingSpace: SpaceVertical,
},
},
+ TrailingSpace: SpaceVertical,
},
},
{
@@ -230,6 +233,7 @@ func TestIfExpression(t *testing.T) {
TrailingSpace: SpaceVertical,
},
},
+ TrailingSpace: SpaceVertical,
},
},
{
@@ -296,6 +300,7 @@ func TestIfExpression(t *testing.T) {
TrailingSpace: SpaceVertical,
},
},
+ TrailingSpace: SpaceVertical,
},
},
{
@@ -370,9 +375,10 @@ func TestIfExpression(t *testing.T) {
TrailingSpace: SpaceVertical,
},
},
+ TrailingSpace: SpaceVertical,
},
- Whitespace{Value: "\n\t\t\t\t"},
},
+ TrailingSpace: SpaceVertical,
},
},
{
@@ -427,6 +433,7 @@ func TestIfExpression(t *testing.T) {
},
},
},
+ TrailingSpace: SpaceVertical,
},
},
{
@@ -505,6 +512,7 @@ func TestIfExpression(t *testing.T) {
},
},
},
+ TrailingSpace: SpaceVertical,
},
},
{
@@ -597,6 +605,7 @@ func TestIfExpression(t *testing.T) {
TrailingSpace: SpaceVertical,
},
},
+ TrailingSpace: SpaceVertical,
},
},
}
diff --git a/parser/v2/stringexpressionparser.go b/parser/v2/stringexpressionparser.go
index 0a4457163..190453944 100644
--- a/parser/v2/stringexpressionparser.go
+++ b/parser/v2/stringexpressionparser.go
@@ -26,11 +26,7 @@ var stringExpression = parse.Func(func(pi *parse.Input) (n Node, ok bool, err er
}
// Parse trailing whitespace.
- ws, _, err := parse.Whitespace.Parse(pi)
- if err != nil {
- return r, false, err
- }
- r.TrailingSpace, err = NewTrailingSpace(ws)
+ r.TrailingSpace, err = parseTrailingSpace(pi, false, false)
if err != nil {
return r, false, err
}
diff --git a/parser/v2/switchexpressionparser.go b/parser/v2/switchexpressionparser.go
index f21e4bf8d..539063a67 100644
--- a/parser/v2/switchexpressionparser.go
+++ b/parser/v2/switchexpressionparser.go
@@ -10,7 +10,10 @@ var switchExpression parse.Parser[Node] = switchExpressionParser{}
type switchExpressionParser struct{}
func (switchExpressionParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
- var r SwitchExpression
+ r := SwitchExpression{
+ // Default behavior is always a trailing space
+ TrailingSpace: SpaceVertical,
+ }
start := pi.Index()
// Check the prefix first.
@@ -51,6 +54,12 @@ func (switchExpressionParser) Parse(pi *parse.Input) (n Node, ok bool, err error
return
}
+ // Parse trailing whitespace.
+ r.TrailingSpace, err = parseTrailingSpace(pi, true, true)
+ if err != nil {
+ return r, false, err
+ }
+
return r, true, nil
}
diff --git a/parser/v2/switchexpressionparser_test.go b/parser/v2/switchexpressionparser_test.go
index e17cae717..b90633dd3 100644
--- a/parser/v2/switchexpressionparser_test.go
+++ b/parser/v2/switchexpressionparser_test.go
@@ -33,6 +33,7 @@ func TestSwitchExpressionParser(t *testing.T) {
},
},
},
+ TrailingSpace: SpaceVertical,
},
},
{
@@ -111,6 +112,7 @@ default:
},
},
},
+ TrailingSpace: SpaceVertical,
},
},
{
@@ -188,6 +190,7 @@ default:
},
},
},
+ TrailingSpace: SpaceVertical,
},
},
{
@@ -296,6 +299,7 @@ default:
},
},
},
+ TrailingSpace: SpaceVertical,
},
},
}
diff --git a/parser/v2/templateparser_test.go b/parser/v2/templateparser_test.go
index 6b0eff2db..4fa55846d 100644
--- a/parser/v2/templateparser_test.go
+++ b/parser/v2/templateparser_test.go
@@ -430,9 +430,7 @@ func TestTemplateParser(t *testing.T) {
TrailingSpace: SpaceVertical,
},
},
- },
- Whitespace{
- Value: "\n",
+ TrailingSpace: SpaceVertical,
},
},
},
@@ -626,8 +624,8 @@ func TestTemplateParser(t *testing.T) {
},
},
},
+ TrailingSpace: SpaceHorizontal,
},
- Whitespace{Value: " "},
Text{
Value: "Home",
Range: Range{
@@ -736,10 +734,8 @@ func TestTemplateParser(t *testing.T) {
},
Children: []Node{
Whitespace{Value: "\t"},
- HTMLComment{Contents: " Single line "},
- Whitespace{Value: "\n\t"},
- HTMLComment{Contents: " \n\t\tMultiline\n\t"},
- Whitespace{Value: "\n"},
+ HTMLComment{Contents: " Single line ", TrailingSpace: SpaceVertical},
+ HTMLComment{Contents: " \n\t\tMultiline\n\t", TrailingSpace: SpaceVertical},
},
},
},
diff --git a/parser/v2/templelementparser.go b/parser/v2/templelementparser.go
index 9a50b5b03..60adf1d12 100644
--- a/parser/v2/templelementparser.go
+++ b/parser/v2/templelementparser.go
@@ -13,7 +13,11 @@ func (p templElementExpressionParser) Parse(pi *parse.Input) (n Node, ok bool, e
return
}
- var r TemplElementExpression
+ r := TemplElementExpression{
+ // Default behavior is always a trailing space
+ TrailingSpace: SpaceVertical,
+ }
+
// Parse the Go expression.
if r.Expression, err = parseGo("templ element", pi, goexpression.TemplExpression); err != nil {
return r, false, err
@@ -26,6 +30,12 @@ func (p templElementExpressionParser) Parse(pi *parse.Input) (n Node, ok bool, e
return
}
if !hasOpenBrace {
+ // Parse trailing whitespace after expression.
+ r.TrailingSpace, err = parseTrailingSpace(pi, true, false)
+ if err != nil {
+ return r, false, err
+ }
+
return r, true, nil
}
@@ -46,6 +56,12 @@ func (p templElementExpressionParser) Parse(pi *parse.Input) (n Node, ok bool, e
return
}
+ // Parse trailing whitespace after closing brace.
+ r.TrailingSpace, err = parseTrailingSpace(pi, true, false)
+ if err != nil {
+ return r, false, err
+ }
+
return r, true, nil
}
diff --git a/parser/v2/templelementparser_test.go b/parser/v2/templelementparser_test.go
index 478620dc3..1ef8ffaf5 100644
--- a/parser/v2/templelementparser_test.go
+++ b/parser/v2/templelementparser_test.go
@@ -32,6 +32,7 @@ func TestTemplElementExpressionParser(t *testing.T) {
},
},
},
+ TrailingSpace: SpaceVertical,
},
},
{
@@ -53,6 +54,7 @@ func TestTemplElementExpressionParser(t *testing.T) {
},
},
},
+ TrailingSpace: SpaceVertical,
},
},
{
@@ -80,6 +82,7 @@ func TestTemplElementExpressionParser(t *testing.T) {
},
},
},
+ TrailingSpace: SpaceVertical,
},
},
{
@@ -190,8 +193,8 @@ func TestTemplElementExpressionParser(t *testing.T) {
To: Position{28, 1, 11},
},
},
+ TrailingSpace: SpaceVertical,
},
- Whitespace{Value: "\n\t\t\t"},
},
},
},
@@ -215,6 +218,7 @@ func TestTemplElementExpressionParser(t *testing.T) {
},
},
},
+ TrailingSpace: SpaceHorizontal,
},
},
{
diff --git a/parser/v2/textparser.go b/parser/v2/textparser.go
index d5353a9c4..50cefda44 100644
--- a/parser/v2/textparser.go
+++ b/parser/v2/textparser.go
@@ -31,11 +31,7 @@ var textParser = parse.Func(func(pi *parse.Input) (n Node, ok bool, err error) {
}
// Parse trailing whitespace.
- ws, _, err := parse.Whitespace.Parse(pi)
- if err != nil {
- return t, false, err
- }
- t.TrailingSpace, err = NewTrailingSpace(ws)
+ t.TrailingSpace, err = parseTrailingSpace(pi, false, false)
if err != nil {
return t, false, err
}
diff --git a/parser/v2/textparser_test.go b/parser/v2/textparser_test.go
index 2e4602f33..3118d0f29 100644
--- a/parser/v2/textparser_test.go
+++ b/parser/v2/textparser_test.go
@@ -80,7 +80,7 @@ func TestTextParser(t *testing.T) {
},
},
{
- name: "Multiline text is colected line by line",
+ name: "Multiline text is collected line by line",
input: "Line 1\nLine 2",
expected: Text{
Value: "Line 1",
@@ -92,7 +92,7 @@ func TestTextParser(t *testing.T) {
},
},
{
- name: "Multiline text is colected line by line (Windows)",
+ name: "Multiline text is collected line by line (Windows)",
input: "Line 1\r\nLine 2",
expected: Text{
Value: "Line 1",
@@ -103,6 +103,18 @@ func TestTextParser(t *testing.T) {
TrailingSpace: "\n",
},
},
+ {
+ name: "Multiline text with multiple newlines is collected line by line",
+ input: "Line 1\n\n\n\nLine 2",
+ expected: Text{
+ Value: "Line 1",
+ Range: Range{
+ From: Position{Index: 0, Line: 0, Col: 0},
+ To: Position{Index: 6, Line: 0, Col: 6},
+ },
+ TrailingSpace: "\n",
+ },
+ },
}
for _, tt := range tests {
tt := tt
diff --git a/parser/v2/types.go b/parser/v2/types.go
index 34186c438..6ef2f4379 100644
--- a/parser/v2/types.go
+++ b/parser/v2/types.go
@@ -375,17 +375,27 @@ func (t HTMLTemplate) Write(w io.Writer, indent int) error {
type TrailingSpace string
const (
- SpaceNone TrailingSpace = ""
- SpaceHorizontal TrailingSpace = " "
- SpaceVertical TrailingSpace = "\n"
+ SpaceNone TrailingSpace = ""
+ SpaceHorizontal TrailingSpace = " "
+ SpaceVertical TrailingSpace = "\n"
+ SpaceVerticalDouble TrailingSpace = "\n\n"
)
var ErrNonSpaceCharacter = errors.New("non space character found")
-func NewTrailingSpace(s string) (ts TrailingSpace, err error) {
+func NewTrailingSpace(s string, allowMulti bool) (ts TrailingSpace, err error) {
var hasHorizontalSpace bool
- for _, r := range s {
+
+ runes := []rune(s)
+
+ for i, r := range s {
if r == '\n' {
+ if allowMulti && i < len(runes)-1 {
+ next := runes[i+1]
+ if next == '\n' {
+ return SpaceVerticalDouble, nil
+ }
+ }
return SpaceVertical, nil
}
if unicode.IsSpace(r) {
@@ -424,6 +434,13 @@ var (
_ WhitespaceTrailer = Element{}
_ WhitespaceTrailer = Text{}
_ WhitespaceTrailer = StringExpression{}
+ _ WhitespaceTrailer = TemplElementExpression{}
+ _ WhitespaceTrailer = GoCode{}
+ _ WhitespaceTrailer = IfExpression{}
+ _ WhitespaceTrailer = ForExpression{}
+ _ WhitespaceTrailer = SwitchExpression{}
+ _ WhitespaceTrailer = HTMLComment{}
+ _ WhitespaceTrailer = CallTemplateExpression{}
)
// Text node within the document.
@@ -620,7 +637,7 @@ func writeNodes(w io.Writer, level int, nodes []Node, indent bool) error {
trailing = wst.Trailing()
}
// Put a newline after the last node in indentation mode.
- if indent && ((nextNodeIsBlock(nodes, i) || i == len(nodes)-1) || shouldAlwaysBreakAfter(nodes[i])) {
+ if indent && trailing != SpaceVerticalDouble && ((nextNodeIsBlock(nodes, i) || i == len(nodes)-1) || shouldAlwaysBreakAfter(nodes[i])) {
trailing = SpaceVertical
}
switch trailing {
@@ -630,6 +647,8 @@ func writeNodes(w io.Writer, level int, nodes []Node, indent bool) error {
level = 0
case SpaceVertical:
level = startLevel
+ case SpaceVerticalDouble:
+ level = startLevel
}
if _, err := w.Write([]byte(trailing)); err != nil {
return err
@@ -911,6 +930,12 @@ func (c GoComment) Write(w io.Writer, indent int) error {
// HTMLComment.
type HTMLComment struct {
Contents string
+ // TrailingSpace lists what happens after the element.
+ TrailingSpace TrailingSpace
+}
+
+func (c HTMLComment) Trailing() TrailingSpace {
+ return c.TrailingSpace
}
func (c HTMLComment) IsNode() bool { return true }
@@ -927,6 +952,12 @@ func (c HTMLComment) Write(w io.Writer, indent int) error {
type CallTemplateExpression struct {
// Expression returns a template to execute.
Expression Expression
+ // TrailingSpace lists what happens after the expression.
+ TrailingSpace TrailingSpace
+}
+
+func (cte CallTemplateExpression) Trailing() TrailingSpace {
+ return cte.TrailingSpace
}
func (cte CallTemplateExpression) IsNode() bool { return true }
@@ -944,6 +975,12 @@ type TemplElementExpression struct {
Expression Expression
// Children returns the elements in a block element.
Children []Node
+ // TrailingSpace lists what happens after the element.
+ TrailingSpace TrailingSpace
+}
+
+func (t TemplElementExpression) Trailing() TrailingSpace {
+ return t.TrailingSpace
}
func (tee TemplElementExpression) ChildNodes() []Node {
@@ -1016,6 +1053,8 @@ type IfExpression struct {
Then []Node
ElseIfs []ElseIfExpression
Else []Node
+ // TrailingSpace lists what happens after the expression.
+ TrailingSpace TrailingSpace
}
type ElseIfExpression struct {
@@ -1023,6 +1062,10 @@ type ElseIfExpression struct {
Then []Node
}
+func (n IfExpression) Trailing() TrailingSpace {
+ return n.TrailingSpace
+}
+
func (n IfExpression) ChildNodes() []Node {
var nodes []Node
nodes = append(nodes, n.Then...)
@@ -1071,7 +1114,13 @@ func (n IfExpression) Write(w io.Writer, indent int) error {
// }
type SwitchExpression struct {
Expression Expression
- Cases []CaseExpression
+ // TrailingSpace lists what happens after the expression.
+ TrailingSpace TrailingSpace
+ Cases []CaseExpression
+}
+
+func (se SwitchExpression) Trailing() TrailingSpace {
+ return se.TrailingSpace
}
func (se SwitchExpression) ChildNodes() []Node {
@@ -1114,7 +1163,13 @@ type CaseExpression struct {
// }
type ForExpression struct {
Expression Expression
- Children []Node
+ // TrailingSpace lists what happens after the expression.
+ TrailingSpace TrailingSpace
+ Children []Node
+}
+
+func (fe ForExpression) Trailing() TrailingSpace {
+ return fe.TrailingSpace
}
func (fe ForExpression) ChildNodes() []Node {
@@ -1177,7 +1232,8 @@ func (se StringExpression) Trailing() TrailingSpace {
return se.TrailingSpace
}
-func (se StringExpression) IsNode() bool { return true }
+func (se StringExpression) IsNode() bool { return true }
+
func (se StringExpression) IsStyleDeclarationValue() bool { return true }
func (se StringExpression) Write(w io.Writer, indent int) error {
if isWhitespace(se.Expression.Value) {
diff --git a/parser/v2/whitespaceparser.go b/parser/v2/whitespaceparser.go
index c57964349..064373b1e 100644
--- a/parser/v2/whitespaceparser.go
+++ b/parser/v2/whitespaceparser.go
@@ -2,6 +2,26 @@ package parser
import "github.com/a-h/parse"
+func parseTrailingSpace(pi *parse.Input, allowMulti bool, forceVertical bool) (space TrailingSpace, err error) {
+ // Add trailing space.
+ ws, _, err := parse.Whitespace.Parse(pi)
+ if err != nil {
+ return "", err
+ }
+
+ trailing, err := NewTrailingSpace(ws, allowMulti)
+ if err != nil {
+ return "", err
+ }
+
+ // If the trailing space is not vertical, set it to vertical.
+ if forceVertical && trailing != SpaceVertical && trailing != SpaceVerticalDouble {
+ return SpaceVertical, nil
+ }
+
+ return trailing, nil
+}
+
// Eat any whitespace.
var whitespaceExpression = parse.Func(func(pi *parse.Input) (n Node, ok bool, err error) {
var r Whitespace