diff --git a/lib/rules/template-no-block-params-for-html-elements.js b/lib/rules/template-no-block-params-for-html-elements.js
index 37b96bad53..5912ee77a0 100644
--- a/lib/rules/template-no-block-params-for-html-elements.js
+++ b/lib/rules/template-no-block-params-for-html-elements.js
@@ -1,6 +1,10 @@
-/** @type {import('eslint').Rule.RuleModule} */
const htmlTags = require('html-tags');
+const svgTags = require('svg-tags');
+const { mathmlTagNames } = require('mathml-tag-names');
+
+const ELEMENT_TAGS = new Set([...htmlTags, ...svgTags, ...mathmlTagNames]);
+/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: 'problem',
@@ -20,23 +24,21 @@ module.exports = {
create(context) {
const sourceCode = context.sourceCode;
- const HTML_ELEMENTS = new Set(htmlTags);
return {
GlimmerElementNode(node) {
- // Check if this is an HTML element (lowercase)
- if (!HTML_ELEMENTS.has(node.tag)) {
+ if (!ELEMENT_TAGS.has(node.tag)) {
return;
}
- // If the tag name is a variable in scope, it's being used as a component, not an HTML element
+ // A known HTML/SVG tag can still be a component if it's bound in scope
+ // (block param, import, local).
const scope = sourceCode.getScope(node.parent);
const isVariable = scope.references.some((ref) => ref.identifier === node.parts[0]);
if (isVariable) {
return;
}
- // Check for block params
if (node.blockParams && node.blockParams.length > 0) {
context.report({
node,
diff --git a/package.json b/package.json
index de20f9800e..c2af296d2f 100644
--- a/package.json
+++ b/package.json
@@ -66,15 +66,17 @@
"aria-query": "^5.3.2",
"css-tree": "^3.0.1",
"editorconfig": "^3.0.2",
- "ember-eslint-parser": "^0.9.0",
+ "ember-eslint-parser": "^0.10.0",
"ember-rfc176-data": "^0.3.18",
"eslint-utils": "^3.0.0",
"estraverse": "^5.3.0",
"html-tags": "^3.3.1",
"lodash.camelcase": "^4.3.0",
"lodash.kebabcase": "^4.1.1",
+ "mathml-tag-names": "^4.0.0",
"requireindex": "^1.2.0",
- "snake-case": "^3.0.3"
+ "snake-case": "^3.0.3",
+ "svg-tags": "^1.0.0"
},
"devDependencies": {
"@babel/core": "^7.25.9",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ea58f19332..083c39a462 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -21,8 +21,8 @@ importers:
specifier: ^3.0.2
version: 3.0.2
ember-eslint-parser:
- specifier: ^0.9.0
- version: 0.9.0(@typescript-eslint/parser@8.58.1(eslint@8.57.1)(typescript@5.9.3))(typescript@5.9.3)
+ specifier: ^0.10.0
+ version: 0.10.0(@typescript-eslint/parser@8.58.1(eslint@8.57.1)(typescript@5.9.3))(typescript@5.9.3)
ember-rfc176-data:
specifier: ^0.3.18
version: 0.3.18
@@ -41,12 +41,18 @@ importers:
lodash.kebabcase:
specifier: ^4.1.1
version: 4.1.1
+ mathml-tag-names:
+ specifier: ^4.0.0
+ version: 4.0.0
requireindex:
specifier: ^1.2.0
version: 1.2.0
snake-case:
specifier: ^3.0.3
version: 3.0.4
+ svg-tags:
+ specifier: ^1.0.0
+ version: 1.0.0
devDependencies:
'@babel/core':
specifier: ^7.25.9
@@ -1775,8 +1781,8 @@ packages:
electron-to-chromium@1.5.335:
resolution: {integrity: sha512-q9n5T4BR4Xwa2cwbrwcsDJtHD/enpQ5S1xF1IAtdqf5AAgqDFmR/aakqH3ChFdqd/QXJhS3rnnXFtexU7rax6Q==}
- ember-eslint-parser@0.9.0:
- resolution: {integrity: sha512-/Z/eoBmkeVdS+UjrocxKuLP4HG9K7XxZgpVvFjDBZ+o3gXYpbnvWkL+wYjaxzySXm3cuuCvnGW5NppIh9Vl/ig==}
+ ember-eslint-parser@0.10.0:
+ resolution: {integrity: sha512-oq37TDYDBqR4fTGhJy/Yecw5VHlPJrSCd26KkwCKlhzAHMCzac+/HSln99COihKei2mMaaob17IrV3I9XL83YQ==}
engines: {node: '>=16.0.0'}
peerDependencies:
'@typescript-eslint/parser': '*'
@@ -5642,7 +5648,7 @@ snapshots:
electron-to-chromium@1.5.335: {}
- ember-eslint-parser@0.9.0(@typescript-eslint/parser@8.58.1(eslint@8.57.1)(typescript@5.9.3))(typescript@5.9.3):
+ ember-eslint-parser@0.10.0(@typescript-eslint/parser@8.58.1(eslint@8.57.1)(typescript@5.9.3))(typescript@5.9.3):
dependencies:
'@glimmer/syntax': 0.95.0
'@typescript-eslint/tsconfig-utils': 8.58.1(typescript@5.9.3)
diff --git a/tests/lib/rules/template-no-block-params-for-html-elements.js b/tests/lib/rules/template-no-block-params-for-html-elements.js
index c7a50ad9dc..62bbc97155 100644
--- a/tests/lib/rules/template-no-block-params-for-html-elements.js
+++ b/tests/lib/rules/template-no-block-params-for-html-elements.js
@@ -17,6 +17,11 @@ ruleTester.run('template-no-block-params-for-html-elements', rule, {
'{{item.name}}',
'{{#each this.items as |item|}}{{item}}{{/each}}',
'',
+ // Custom elements aren't in the html-tags/svg-tags allowlists, so they're
+ // not flagged. Accepted false negative — web component namespace is open.
+ '{{x}}',
+ // Namespaced/path component invocations aren't in the allowlists either.
+ '{{x}}',
],
invalid: [
@@ -50,5 +55,27 @@ ruleTester.run('template-no-block-params-for-html-elements', rule, {
},
],
},
+ {
+ // SVG element — in svg-tags allowlist.
+ code: '{{r}}',
+ output: null,
+ errors: [
+ {
+ message: 'Block params can only be used with components, not HTML elements.',
+ type: 'GlimmerElementNode',
+ },
+ ],
+ },
+ {
+ // MathML element — in mathml-tag-names allowlist.
+ code: '{{num}}',
+ output: null,
+ errors: [
+ {
+ message: 'Block params can only be used with components, not HTML elements.',
+ type: 'GlimmerElementNode',
+ },
+ ],
+ },
],
});