From 4e88bbc3a9bfeec22531dc0eba349b6bdf2c0e40 Mon Sep 17 00:00:00 2001 From: Andrew Jakubowicz Date: Thu, 25 Jan 2024 16:39:25 -0800 Subject: [PATCH 1/8] detect duplicate attributes in DEV_MODE --- packages/lit-html/src/lit-html.ts | 23 +++++++++++++++++++++ packages/lit-html/src/test/lit-html_test.ts | 6 ++++++ 2 files changed, 29 insertions(+) diff --git a/packages/lit-html/src/lit-html.ts b/packages/lit-html/src/lit-html.ts index a504d067b8..5a6430cd04 100644 --- a/packages/lit-html/src/lit-html.ts +++ b/packages/lit-html/src/lit-html.ts @@ -925,6 +925,29 @@ class Template { // Create template element const [html, attrNames] = getTemplateHtml(strings, type); + if (DEV_MODE) { + let attrSet; + if ((attrSet = new Set(attrNames)).size !== attrNames.length) { + // Report all duplicated attributes. This is linear time, but is only + // done in DEV_MODE when an error is expected. + const duplicatedAttributes = new Set(); + for (const attrName of attrNames) { + if (!attrSet.has(attrName)) { + continue; + } + duplicatedAttributes.add(attrName); + } + throw new Error( + `Detected duplicate attribute ` + + `bindings of attribute${ + duplicatedAttributes.size > 1 ? 's' : '' + }: '${[...duplicatedAttributes].join(', ')}', in the template: ` + + '`' + + strings.join('${...}') + + '`' + ); + } + } this.el = Template.createElement(html, options); walker.currentNode = this.el.content; diff --git a/packages/lit-html/src/test/lit-html_test.ts b/packages/lit-html/src/test/lit-html_test.ts index 705f74291e..276c6c2a9a 100644 --- a/packages/lit-html/src/test/lit-html_test.ts +++ b/packages/lit-html/src/test/lit-html_test.ts @@ -2193,6 +2193,12 @@ suite('lit-html', () => { ); }); + test('Duplicate attributes throw', () => { + assert.throws(() => { + render(html``, container); + }, `Detected duplicate attribute bindings of attribute: '?disabled', in the template: \`\``); + }); + test('Expressions inside nested templates throw in dev mode', () => { // top level assert.throws(() => { From 9ddc292ba5a88d6d4b2a68290109c81e792b9b36 Mon Sep 17 00:00:00 2001 From: Andrew Jakubowicz Date: Thu, 25 Jan 2024 16:40:55 -0800 Subject: [PATCH 2/8] Add changeset --- .changeset/forty-schools-flash.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/forty-schools-flash.md diff --git a/.changeset/forty-schools-flash.md b/.changeset/forty-schools-flash.md new file mode 100644 index 0000000000..67015e022b --- /dev/null +++ b/.changeset/forty-schools-flash.md @@ -0,0 +1,5 @@ +--- +'lit-html': patch +--- + +Add a DEV_MODE error to catch duplicate attribute bindings that otherwise create silent errors. From e2097e25b28deea9bd0f40b56b1e80a8b809814c Mon Sep 17 00:00:00 2001 From: Andrew Jakubowicz Date: Fri, 26 Jan 2024 10:40:30 -0800 Subject: [PATCH 3/8] fix the mismatched attributes error --- packages/lit-html/src/lit-html.ts | 43 ++++++++++----------- packages/lit-html/src/test/lit-html_test.ts | 16 +++++++- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/packages/lit-html/src/lit-html.ts b/packages/lit-html/src/lit-html.ts index 5a6430cd04..7d1f65b496 100644 --- a/packages/lit-html/src/lit-html.ts +++ b/packages/lit-html/src/lit-html.ts @@ -925,29 +925,6 @@ class Template { // Create template element const [html, attrNames] = getTemplateHtml(strings, type); - if (DEV_MODE) { - let attrSet; - if ((attrSet = new Set(attrNames)).size !== attrNames.length) { - // Report all duplicated attributes. This is linear time, but is only - // done in DEV_MODE when an error is expected. - const duplicatedAttributes = new Set(); - for (const attrName of attrNames) { - if (!attrSet.has(attrName)) { - continue; - } - duplicatedAttributes.add(attrName); - } - throw new Error( - `Detected duplicate attribute ` + - `bindings of attribute${ - duplicatedAttributes.size > 1 ? 's' : '' - }: '${[...duplicatedAttributes].join(', ')}', in the template: ` + - '`' + - strings.join('${...}') + - '`' - ); - } - } this.el = Template.createElement(html, options); walker.currentNode = this.el.content; @@ -1059,6 +1036,26 @@ class Template { } nodeIndex++; } + + if (DEV_MODE) { + // If there was a duplicate attribute on a tag, then when the tag is + // parsed into an element the attribute gets de-duplicated. We can detect + // this mismatch if we haven't precisely consumed every attribute name + // when preparing the template. + if (attrNames.length !== attrNameIndex) { + throw new Error( + `Detected duplicate attribute bindings. This occurs if your template ` + + `has the dupliate attributes on an element tag. For example ` + + `"" contains a ` + + `duplicate "disabled" attribute. The error was detected in ` + + `the following template: \n` + + '`' + + strings.join('${...}') + + '`' + ); + } + } + // We could set walker.currentNode to another node here to prevent a memory // leak, but every time we prepare a template, we immediately render it // and re-use the walker in new TemplateInstance._clone(). diff --git a/packages/lit-html/src/test/lit-html_test.ts b/packages/lit-html/src/test/lit-html_test.ts index 276c6c2a9a..d4b24eebf6 100644 --- a/packages/lit-html/src/test/lit-html_test.ts +++ b/packages/lit-html/src/test/lit-html_test.ts @@ -2195,8 +2195,20 @@ suite('lit-html', () => { test('Duplicate attributes throw', () => { assert.throws(() => { - render(html``, container); - }, `Detected duplicate attribute bindings of attribute: '?disabled', in the template: \`\``); + render( + html``, + container + ); + }, `Detected duplicate attribute bindings. This occurs if your template has the dupliate attributes on an element tag. For example "" contains a duplicate "disabled" attribute. The error was detected in the following template: \n\`\``); + }); + + test('Matching attribute bindings across elements should not throw', () => { + assert.doesNotThrow(() => { + render( + html``, + container + ); + }); }); test('Expressions inside nested templates throw in dev mode', () => { From 44750fb3c771524fd37cf977c8765f3af1790423 Mon Sep 17 00:00:00 2001 From: Andrew Jakubowicz Date: Fri, 26 Jan 2024 11:00:14 -0800 Subject: [PATCH 4/8] fix typo --- packages/lit-html/src/lit-html.ts | 2 +- packages/lit-html/src/test/lit-html_test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/lit-html/src/lit-html.ts b/packages/lit-html/src/lit-html.ts index 7d1f65b496..584c034d09 100644 --- a/packages/lit-html/src/lit-html.ts +++ b/packages/lit-html/src/lit-html.ts @@ -1045,7 +1045,7 @@ class Template { if (attrNames.length !== attrNameIndex) { throw new Error( `Detected duplicate attribute bindings. This occurs if your template ` + - `has the dupliate attributes on an element tag. For example ` + + `has duplicate attributes on an element tag. For example ` + `"" contains a ` + `duplicate "disabled" attribute. The error was detected in ` + `the following template: \n` + diff --git a/packages/lit-html/src/test/lit-html_test.ts b/packages/lit-html/src/test/lit-html_test.ts index d4b24eebf6..e6b859e265 100644 --- a/packages/lit-html/src/test/lit-html_test.ts +++ b/packages/lit-html/src/test/lit-html_test.ts @@ -2199,7 +2199,7 @@ suite('lit-html', () => { html``, container ); - }, `Detected duplicate attribute bindings. This occurs if your template has the dupliate attributes on an element tag. For example "" contains a duplicate "disabled" attribute. The error was detected in the following template: \n\`\``); + }, `Detected duplicate attribute bindings. This occurs if your template has duplicate attributes on an element tag. For example "" contains a duplicate "disabled" attribute. The error was detected in the following template: \n\`\``); }); test('Matching attribute bindings across elements should not throw', () => { From 0a105cb8cd3b24f5a8aa6d8da6babad21b50f3bb Mon Sep 17 00:00:00 2001 From: Andrew Jakubowicz Date: Fri, 26 Jan 2024 11:02:28 -0800 Subject: [PATCH 5/8] update changeset to include 'lit' package --- .changeset/forty-schools-flash.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/forty-schools-flash.md b/.changeset/forty-schools-flash.md index 67015e022b..ba935ccc18 100644 --- a/.changeset/forty-schools-flash.md +++ b/.changeset/forty-schools-flash.md @@ -1,5 +1,6 @@ --- 'lit-html': patch +'lit': patch --- Add a DEV_MODE error to catch duplicate attribute bindings that otherwise create silent errors. From b5ad48671771bf76154ee91bb1728fd8ad85b196 Mon Sep 17 00:00:00 2001 From: Andrew Jakubowicz Date: Fri, 26 Jan 2024 11:27:06 -0800 Subject: [PATCH 6/8] skip compiled test --- packages/lit-html/src/test/lit-html_test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lit-html/src/test/lit-html_test.ts b/packages/lit-html/src/test/lit-html_test.ts index e6b859e265..67778cdb50 100644 --- a/packages/lit-html/src/test/lit-html_test.ts +++ b/packages/lit-html/src/test/lit-html_test.ts @@ -2193,7 +2193,7 @@ suite('lit-html', () => { ); }); - test('Duplicate attributes throw', () => { + skipTestIfCompiled('Duplicate attributes throw', () => { assert.throws(() => { render( html``, From 12f37ca4108fe557a52851d5b1bd6de787fc687c Mon Sep 17 00:00:00 2001 From: Andrew Jakubowicz Date: Mon, 29 Jan 2024 10:32:11 -0800 Subject: [PATCH 7/8] Update packages/lit-html/src/lit-html.ts Co-authored-by: Steve Orvell --- packages/lit-html/src/lit-html.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lit-html/src/lit-html.ts b/packages/lit-html/src/lit-html.ts index 584c034d09..f0d8c923ab 100644 --- a/packages/lit-html/src/lit-html.ts +++ b/packages/lit-html/src/lit-html.ts @@ -1041,7 +1041,7 @@ class Template { // If there was a duplicate attribute on a tag, then when the tag is // parsed into an element the attribute gets de-duplicated. We can detect // this mismatch if we haven't precisely consumed every attribute name - // when preparing the template. + // when preparing the template. This works because `attrNames` is built from the template string and `attrNameIndex` comes from processing the resulting DOM. if (attrNames.length !== attrNameIndex) { throw new Error( `Detected duplicate attribute bindings. This occurs if your template ` + From d3d93a70185b78656ea2aa81596d05878a7ed0d9 Mon Sep 17 00:00:00 2001 From: Andrew Jakubowicz Date: Mon, 29 Jan 2024 10:34:23 -0800 Subject: [PATCH 8/8] rewrap comment --- packages/lit-html/src/lit-html.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/lit-html/src/lit-html.ts b/packages/lit-html/src/lit-html.ts index f0d8c923ab..9876287579 100644 --- a/packages/lit-html/src/lit-html.ts +++ b/packages/lit-html/src/lit-html.ts @@ -1041,7 +1041,9 @@ class Template { // If there was a duplicate attribute on a tag, then when the tag is // parsed into an element the attribute gets de-duplicated. We can detect // this mismatch if we haven't precisely consumed every attribute name - // when preparing the template. This works because `attrNames` is built from the template string and `attrNameIndex` comes from processing the resulting DOM. + // when preparing the template. This works because `attrNames` is built + // from the template string and `attrNameIndex` comes from processing the + // resulting DOM. if (attrNames.length !== attrNameIndex) { throw new Error( `Detected duplicate attribute bindings. This occurs if your template ` +