Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 2 additions & 0 deletions generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -1458,6 +1458,8 @@ func (g *generator) writeElementAttributes(indentLevel int, name string, attrs [
err = g.writeSpreadAttributes(indentLevel, attr)
case *parser.ConditionalAttribute:
err = g.writeConditionalAttribute(indentLevel, name, attr)
case *parser.AttributeComment:
continue
default:
err = fmt.Errorf("unknown attribute type %T", attr)
}
Expand Down
6 changes: 6 additions & 0 deletions generator/test-attribute-comments/expected.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<form class="flex" action="javascript:void(0)" method="post" id="test-form">
<input type="text" name="field1" />
</form>
<div id="main" class="container" data-value="dynamic" hidden>Content</div>
<button type="button" onclick="handleClick()" disabled="false">Click me</button>

23 changes: 23 additions & 0 deletions generator/test-attribute-comments/render_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package testattributecomments

import (
_ "embed"
"testing"

"github.com/a-h/templ/generator/htmldiff"
)

//go:embed expected.html
var expected string

func Test(t *testing.T) {
component := TestAttributeComments()

_, diff, err := htmldiff.Diff(component, expected)
if err != nil {
t.Fatal(err)
}
if diff != "" {
t.Error(diff)
}
}
41 changes: 41 additions & 0 deletions generator/test-attribute-comments/template.templ
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package testattributecomments

templ TestAttributeComments() {
<form
class="flex"
// This comment here explains why the following attribute is necessary,
// namely to prevent submit when hitting the enter key.
action="javascript:void(0)"
/* This is a multi-line comment
that spans multiple lines
within the attributes */
method="post"
// Another single line comment
id="test-form"
>
<input type="text" name="field1"/>
</form>
<div
// Comment at the start
id="main"
/* Multi-line comment */
class="container"
// Comment before expression
data-value={ "dynamic" }
// Comment before boolean
hidden
// Final comment
>
Content
</div>
<button
type="button"
// This explains the onclick handler
onclick="handleClick()"
/* This is a safety measure
to prevent accidental submits */
disabled={ false }
>
Click me
</button>
}
65 changes: 65 additions & 0 deletions generator/test-attribute-comments/template_templ.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 58 additions & 0 deletions internal/format/testdata/comments_in_attributes_are_preserved.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
-- in --
package main

templ test() {
<form
class="flex"
// This comment explains why the action is necessary
action="javascript:void(0)"
/* Multi-line comment
for detailed explanations */
method="post"
// Comment before id
id="test-form"
>
<input type="text" />
</form>
<div
// Comment at start
id="main"
/* Multi-line comment */
class="container"
// Comment before expression
data-value={ "dynamic" }
// Comment before boolean
hidden
>
Content
</div>
}
-- out --
package main

templ test() {
<form
class="flex"
// This comment explains why the action is necessary
action="javascript:void(0)"
/* Multi-line comment
for detailed explanations */
method="post"
// Comment before id
id="test-form"
>
<input type="text"/>
</form>
<div
// Comment at start
id="main"
/* Multi-line comment */
class="container"
// Comment before expression
data-value={ "dynamic" }
// Comment before boolean
hidden
>
Content
</div>
}
24 changes: 24 additions & 0 deletions parser/v2/elementparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,30 @@ type attributesParser struct{}

func (attributesParser) Parse(in *parse.Input) (attributes []Attribute, ok bool, err error) {
for {
// Try to parse and handle any comments first.
for {
if _, _, err = parse.OptionalWhitespace.Parse(in); err != nil {
return
}

commentStart := in.Index()
if node, commentOk, commentErr := goComment.Parse(in); commentErr != nil {
return attributes, false, commentErr
} else if commentOk {
// Found a Go comment, add it as an attribute comment
if goNode, ok := node.(*GoComment); ok {
attributes = append(attributes, &AttributeComment{
Comment: goNode.Contents,
Multiline: goNode.Multiline,
})
}
continue
}

in.Seek(commentStart)
break
}

var attr Attribute
attr, ok, err = attribute.Parse(in)
if err != nil {
Expand Down
29 changes: 29 additions & 0 deletions parser/v2/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1108,6 +1108,35 @@ func (ca *ConditionalAttribute) Copy() Attribute {
}
}

// AttributeComment represents a comment within element attributes.
type AttributeComment struct {
Comment string
Multiline bool
Comment thread
jackielii marked this conversation as resolved.
Resolved
}

func (ac *AttributeComment) String() string {
if ac.Multiline {
return "/*" + ac.Comment + "*/"
}
return "//" + ac.Comment
}

func (ac *AttributeComment) Write(w io.Writer, indent int) error {
return writeIndent(w, indent, ac.String())
}

func (ac *AttributeComment) Visit(v Visitor) error {
// Comments don't need to be visited.
return nil
}

func (ac *AttributeComment) Copy() Attribute {
return &AttributeComment{
Comment: ac.Comment,
Multiline: ac.Multiline,
}
}

func CopyAttributes(attrs []Attribute) (copies []Attribute) {
copies = make([]Attribute, len(attrs))
for i, a := range attrs {
Expand Down