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
5 changes: 5 additions & 0 deletions .changeset/html-dynamic-tag-raw-text-attribute.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@solidjs/html": patch
---

Fix the tokenizer treating the first attribute of a dynamic tag as its tag name. `<${Comp} title="...">` or `<${Comp} style=${...}>` entered raw text mode, so the children were read as text and parsing failed with "Unclosed tag".
4 changes: 3 additions & 1 deletion packages/html/src/tokenize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,9 @@ export const tokenize = (
const start = cursor;
while (cursor < len && isIdentifierChar(str.charCodeAt(cursor))) cursor++;
const value = str.slice(start, cursor);
if (lastTagName === "") {
// Only a tag name directly after "<" can open a raw text element, not an
// attribute name on a dynamic tag such as <${Comp} title="...">.
if (tokens[tokens.length - 1]?.type === OPEN_TAG_TOKEN) {
lastTagName = value;
}
tokens.push({ type: IDENTIFIER_TOKEN, value });
Expand Down
23 changes: 23 additions & 0 deletions packages/html/test/parse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,29 @@ describe("Dynamic Component Tags", () => {
]
});
});

it("parses children of a dynamic component whose first attribute is named like a raw text element", () => {
const Comp = "Comp";
const ast = jsx`<${Comp} style=${{}}><span>content</span></${Comp}>`;
expect(ast).toEqual({
type: ROOT_NODE,
children: [
{
type: COMPONENT_NODE,
name: 0,
props: [{ name: "style", type: EXPRESSION_PROP, value: 1 }],
children: [
{
type: ELEMENT_NODE,
name: "span",
props: [],
children: [{ type: TEXT_NODE, value: "content" }]
}
]
}
]
});
});
});

describe("Edge Cases", () => {
Expand Down
26 changes: 26 additions & 0 deletions packages/html/test/tokenize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,32 @@ describe("dynamic component tags", () => {
{ type: CLOSE_TAG_TOKEN }
]);
});

it("should not treat an attribute named like a raw text element as the tag name", () => {
const Comp = "Comp";
const tokens = tokenizeTemplate`<${Comp} title="Home"><b>Hi</b></${Comp}>`;

expect(tokens).toEqual([
{ type: OPEN_TAG_TOKEN },
{ type: EXPRESSION_TOKEN, value: 0 },
{ type: IDENTIFIER_TOKEN, value: "title" },
{ type: EQUALS_TOKEN },
{ type: STRING_TOKEN, value: "Home", quote: '"' },
{ type: CLOSE_TAG_TOKEN },
{ type: OPEN_TAG_TOKEN },
{ type: IDENTIFIER_TOKEN, value: "b" },
{ type: CLOSE_TAG_TOKEN },
{ type: TEXT_TOKEN, value: "Hi" },
{ type: OPEN_TAG_TOKEN },
{ type: SLASH_TOKEN },
{ type: IDENTIFIER_TOKEN, value: "b" },
{ type: CLOSE_TAG_TOKEN },
{ type: OPEN_TAG_TOKEN },
{ type: SLASH_TOKEN },
{ type: EXPRESSION_TOKEN, value: 1 },
{ type: CLOSE_TAG_TOKEN }
]);
});
});

describe("comments handling", () => {
Expand Down