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