Skip to content
Merged
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
2 changes: 2 additions & 0 deletions generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -1472,6 +1472,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
20 changes: 20 additions & 0 deletions generator/test-attribute-comments/expected.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<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>
<a
href="https://example.com/path"
title="// Title with comment-like text"
class="external-link"
>External Link</a
><link rel="stylesheet" href="https://cdn.example.com/styles.css" /><a
href="https://dynamic.example.com/path"
target="_blank"
>Dynamic Link</a
>
<img
src="https://images.example.com/photo.jpg"
alt="Photo//photo"
data-url="https://api.example.com/endpoint"
/>
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)
}
}
69 changes: 69 additions & 0 deletions generator/test-attribute-comments/template.templ
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
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>
<a
// Link with URL containing //
href="https://example.com/path"
title="// Title with comment-like text"
class="external-link"
>
External Link
</a>
<link
rel="stylesheet"
// CDN link with // in value
href="https://cdn.example.com/styles.css"
/>
<a
// Expression with URL
href={ "https://dynamic.example.com/path" }
// Comment after expression
target="_blank"
>
Dynamic Link
</a>
<img
src="https://images.example.com/photo.jpg"
// Alt text comment with //
alt="Photo//photo"
// Comment before data attribute with //
data-url="https://api.example.com/endpoint"
/>
}
78 changes: 78 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>
}
25 changes: 25 additions & 0 deletions parser/v2/elementparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,31 @@ 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,
Range: goNode.Range,
})
}
continue
}

in.Seek(commentStart)
break
}

var attr Attribute
attr, ok, err = attribute.Parse(in)
if err != nil {
Expand Down
89 changes: 89 additions & 0 deletions parser/v2/elementparser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2516,6 +2516,95 @@ func TestElementParser(t *testing.T) {
},
},
},
{
name: "element: with single-line comment in attributes",
input: `<div // comment` + "\n" + `class="test"></div>`,
expected: &Element{
Name: "div",
NameRange: Range{
From: Position{Index: 1, Line: 0, Col: 1},
To: Position{Index: 4, Line: 0, Col: 4},
},
IndentAttrs: true,
Attributes: []Attribute{
&AttributeComment{
Comment: " comment",
Multiline: false,
Range: Range{
From: Position{Index: 5, Line: 0, Col: 5},
To: Position{Index: 15, Line: 0, Col: 15},
},
},
&ConstantAttribute{
Value: "test",
Key: ConstantAttributeKey{
Name: "class",
NameRange: Range{
From: Position{Index: 16, Line: 1, Col: 0},
To: Position{Index: 21, Line: 1, Col: 5},
},
},
ValueRange: Range{
From: Position{Index: 23, Line: 1, Col: 7},
To: Position{Index: 27, Line: 1, Col: 11},
},
Range: Range{
From: Position{Index: 16, Line: 1, Col: 0},
To: Position{Index: 28, Line: 1, Col: 12},
},
},
},
Children: nil,
Range: Range{
From: Position{Index: 0, Line: 0, Col: 0},
To: Position{Index: 35, Line: 1, Col: 19},
},
},
},
{
name: "element: with multi-line comment in attributes",
input: `<div /* multi-line comment */ class="test"></div>`,
expected: &Element{
Name: "div",
NameRange: Range{
From: Position{Index: 1, Line: 0, Col: 1},
To: Position{Index: 4, Line: 0, Col: 4},
},
Attributes: []Attribute{
&AttributeComment{
Comment: " multi-line comment ",
Multiline: true,
Range: Range{
From: Position{Index: 5, Line: 0, Col: 5},
To: Position{Index: 29, Line: 0, Col: 29},
},
},
&ConstantAttribute{
Value: "test",
Key: ConstantAttributeKey{
Name: "class",
NameRange: Range{
From: Position{Index: 30, Line: 0, Col: 30},
To: Position{Index: 35, Line: 0, Col: 35},
},
},
ValueRange: Range{
From: Position{Index: 37, Line: 0, Col: 37},
To: Position{Index: 41, Line: 0, Col: 41},
},
Range: Range{
From: Position{Index: 30, Line: 0, Col: 30},
To: Position{Index: 42, Line: 0, Col: 42},
},
},
},
Children: nil,
Range: Range{
From: Position{Index: 0, Line: 0, Col: 0},
To: Position{Index: 49, Line: 0, Col: 49},
},
},
},
{
name: "element: non-self-closing with unquoted attribute",
input: `<div id=main></div>`,
Expand Down
Loading
Loading