Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -1113,6 +1113,12 @@ func (g *generator) writeAttributesCSS(indentLevel int, attrs []parser.Attribute
if err != nil {
return err
}
for _, elseIf := range cattr.ElseIfs {
err = g.writeAttributesCSS(indentLevel, elseIf.Then)
if err != nil {
return err
}
}
err = g.writeAttributesCSS(indentLevel, cattr.Else)
if err != nil {
return err
Expand Down Expand Up @@ -1160,6 +1166,11 @@ func getAttributeScripts(attr parser.Attribute) (scripts []string) {
for _, attr := range attr.Then {
scripts = append(scripts, getAttributeScripts(attr)...)
}
for _, elseIf := range attr.ElseIfs {
for _, attr := range elseIf.Then {
scripts = append(scripts, getAttributeScripts(attr)...)
}
}
for _, attr := range attr.Else {
scripts = append(scripts, getAttributeScripts(attr)...)
}
Expand Down Expand Up @@ -1468,6 +1479,26 @@ func (g *generator) writeConditionalAttribute(indentLevel int, elementName strin
}
indentLevel--
}
for _, elseIf := range attr.ElseIfs {
// } else if x == y {
if _, err = g.w.WriteIndent(indentLevel, `} else if `); err != nil {
return err
}
if r, err = g.w.Write(elseIf.Expression.Value); err != nil {
return err
}
g.sourceMap.Add(elseIf.Expression, r)
if _, err = g.w.Write(` {` + "\n"); err != nil {
return err
}
{
indentLevel++
if err = g.writeElementAttributes(indentLevel, elementName, elseIf.Then); err != nil {
return err
}
indentLevel--
}
}
if len(attr.Else) > 0 {
// } else {
if _, err = g.w.WriteIndent(indentLevel, `} else {`+"\n"); err != nil {
Expand Down
53 changes: 53 additions & 0 deletions generator/generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package generator

import (
"bytes"
"strings"
"testing"

"github.com/a-h/templ/parser/v2"
Expand Down Expand Up @@ -71,6 +72,58 @@ templ Hello(name string) {
}
}

func TestWriteConditionalAttributeElseIf(t *testing.T) {
w := new(bytes.Buffer)
g := generator{
w: NewRangeWriter(w),
sourceMap: parser.NewSourceMap(),
}
attr := &parser.ConditionalAttribute{
Expression: parser.Expression{Value: "a"},
Then: []parser.Attribute{
&parser.ConstantAttribute{
Key: parser.ConstantAttributeKey{Name: "class"},
Value: "then",
},
},
ElseIfs: []parser.ConditionalElseIfAttribute{
{
Expression: parser.Expression{Value: "b"},
Then: []parser.Attribute{
&parser.ConstantAttribute{
Key: parser.ConstantAttributeKey{Name: "class"},
Value: "else-if",
},
},
},
},
Else: []parser.Attribute{
&parser.ConstantAttribute{
Key: parser.ConstantAttributeKey{Name: "class"},
Value: "else",
},
},
}

if err := g.writeConditionalAttribute(0, "div", attr); err != nil {
t.Fatalf("failed to write conditional attribute: %v", err)
}

output := w.String()
for _, expected := range []string{
"if a {\n",
"} else if b {\n",
"} else {\n",
`" class=\"then\""`,
`" class=\"else-if\""`,
`" class=\"else\""`,
} {
if !strings.Contains(output, expected) {
t.Errorf("expected generated output to contain %q, got:\n%s", expected, output)
}
}
}

func TestIsTrailingSpaceNeeded(t *testing.T) {
inlineText := &parser.Text{Value: "hello", TrailingSpace: parser.SpaceHorizontal}
newlineText := &parser.Text{Value: "hello", TrailingSpace: parser.SpaceVertical}
Expand Down
14 changes: 14 additions & 0 deletions internal/format/attributes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,16 @@ func TestAttributes(t *testing.T) {
Value: "then-class",
},
},
ElseIfs: []parser.ConditionalElseIfAttribute{
{
Then: []parser.Attribute{
&parser.ConstantAttribute{
Key: parser.ConstantAttributeKey{Name: "class"},
Value: "else-if-class",
},
},
},
},
Comment on lines +205 to +214
Else: []parser.Attribute{
&parser.ConstantAttribute{
Key: parser.ConstantAttributeKey{Name: "class"},
Expand All @@ -217,10 +227,14 @@ func TestAttributes(t *testing.T) {
t.Helper()
cond := children[0].(*parser.Element).Attributes[0].(*parser.ConditionalAttribute)
thenAttr := cond.Then[0].(*parser.ConstantAttribute)
elseIfAttr := cond.ElseIfs[0].Then[0].(*parser.ConstantAttribute)
elseAttr := cond.Else[0].(*parser.ConstantAttribute)
if thenAttr.Value != "then-class" {
t.Errorf("then branch: got %q, expected %q", thenAttr.Value, "then-class")
}
if elseIfAttr.Value != "else-if-class" {
t.Errorf("else if branch: got %q, expected %q", elseIfAttr.Value, "else-if-class")
}
if elseAttr.Value != "else-class" {
t.Errorf("else branch: got %q, expected %q", elseAttr.Value, "else-class")
}
Expand Down
51 changes: 50 additions & 1 deletion parser/v2/conditionalattributeparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,12 @@ func (conditionalAttributeParser) Parse(pi *parse.Input) (r *ConditionalAttribut
return
}

// Read the optional 'Else' Nodes.
// Read the optional 'ElseIf' attributes.
if r.ElseIfs, _, err = parse.ZeroOrMore(attributeElseIfExpression).Parse(pi); err != nil {
return
}

// Read the optional 'Else' attributes.
if r.Else, ok, err = attributeElseExpression.Parse(pi); err != nil {
return
}
Expand All @@ -71,6 +76,50 @@ func (conditionalAttributeParser) Parse(pi *parse.Input) (r *ConditionalAttribut
return r, true, nil
}

var attributeElseIfExpression parse.Parser[ConditionalElseIfAttribute] = attributeElseIfExpressionParser{}

type attributeElseIfExpressionParser struct{}

func (attributeElseIfExpressionParser) Parse(pi *parse.Input) (r ConditionalElseIfAttribute, matched bool, err error) {
start := pi.Index()

// Check the prefix first.
if _, matched, err = parse.All(parse.OptionalWhitespace, closeBrace, parse.OptionalWhitespace, parse.String("else if")).Parse(pi); err != nil || !matched {
pi.Seek(start)
return
}

// Rewind to the start of the `if` statement.
pi.Seek(pi.Index() - 2)
// Parse the Go if expression.
if r.Expression, err = parseGo("attribute else if", pi, goexpression.If); err != nil {
return
}
Comment on lines +87 to +97

// Eat " {".
if _, matched, err = openBraceWithOptionalPadding.Parse(pi); err != nil || !matched {
err = parse.Error("attribute else if: unterminated (missing closing '{\n')", pi.PositionAt(start))
return
}
Comment on lines +100 to +103
if _, _, err = parse.OptionalWhitespace.Parse(pi); err != nil {
return
}

// Read the 'Then' attributes.
if r.Then, matched, err = (attributesParser{}).Parse(pi); err != nil || !matched {
err = parse.Error("attribute if: expected attributes in else if block, but none were found", pi.Position())
return
}

if len(r.Then) == 0 {
err = parse.Error("attribute if: invalid content or no attributes were found in the else if block", pi.Position())
return
}
Comment on lines +109 to +117

r.Range = NewRange(pi.PositionAt(start), pi.Position())
return r, true, nil
}

var attributeElseExpression parse.Parser[[]Attribute] = attributeElseExpressionParser{}

type attributeElseExpressionParser struct{}
Expand Down
94 changes: 94 additions & 0 deletions parser/v2/elementparser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2845,6 +2845,52 @@ func TestElementFormatting(t *testing.T) {
input: `<div id=main></div>`,
expected: `<div id="main"></div>`,
},
{
name: "conditional attribute else if is formatted on one line",
input: `<div
if lint.Severity == 0 {
class="text-green-600"
} else if lint.Severity == 1 {
class="text-yellow-600"
} else {
class="text-red-600"
}
></div>`,
expected: `<div
if lint.Severity == 0 {
class="text-green-600"
} else if lint.Severity == 1 {
class="text-yellow-600"
} else {
class="text-red-600"
}
></div>`,
},
{
name: "conditional attribute multiple else if branches are formatted on one line",
input: `<div
if lint.Severity == 0 {
class="text-green-600"
} else if lint.Severity == 1 {
class="text-yellow-600"
} else if lint.Severity == 2 {
class="text-orange-600"
} else {
class="text-red-600"
}
></div>`,
expected: `<div
if lint.Severity == 0 {
class="text-green-600"
} else if lint.Severity == 1 {
class="text-yellow-600"
} else if lint.Severity == 2 {
class="text-orange-600"
} else {
class="text-red-600"
}
></div>`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand All @@ -2866,3 +2912,51 @@ func TestElementFormatting(t *testing.T) {
})
}
}

func TestConditionalAttributeElseIfsParseAsBranches(t *testing.T) {
input := `<div
if lint.Severity == 0 {
class="text-green-600"
} else if lint.Severity == 1 {
class="text-yellow-600"
} else if lint.Severity == 2 {
class="text-orange-600"
} else {
class="text-red-600"
}
></div>`

result, matched, err := element.Parse(parse.NewInput(input))
if err != nil {
t.Fatalf("parser error: %v", err)
}
if !matched {
t.Fatal("failed to parse element")
}
el, ok := result.(*Element)
if !ok {
t.Fatalf("expected Element, got %T", result)
}
if len(el.Attributes) != 1 {
t.Fatalf("expected 1 attribute, got %d", len(el.Attributes))
}
attr, ok := el.Attributes[0].(*ConditionalAttribute)
if !ok {
t.Fatalf("expected ConditionalAttribute, got %T", el.Attributes[0])
}
if got, want := attr.Expression.Value, "lint.Severity == 0"; got != want {
t.Fatalf("initial expression: got %q, want %q", got, want)
}
if got, want := len(attr.ElseIfs), 2; got != want {
t.Fatalf("else if count: got %d, want %d", got, want)
}
if got, want := attr.ElseIfs[0].Expression.Value, "lint.Severity == 1"; got != want {
t.Errorf("first else if expression: got %q, want %q", got, want)
}
if got, want := attr.ElseIfs[1].Expression.Value, "lint.Severity == 2"; got != want {
t.Errorf("second else if expression: got %q, want %q", got, want)
}
if got, want := len(attr.Else), 1; got != want {
t.Fatalf("else attribute count: got %d, want %d", got, want)
}
}
Loading