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, { '', '', '', + // Custom elements aren't in the html-tags/svg-tags allowlists, so they're + // not flagged. Accepted false negative — web component namespace is open. + '', + // Namespaced/path component invocations aren't in the allowlists either. + '', ], invalid: [ @@ -50,5 +55,27 @@ ruleTester.run('template-no-block-params-for-html-elements', rule, { }, ], }, + { + // SVG element — in svg-tags allowlist. + code: '', + 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: '', + output: null, + errors: [ + { + message: 'Block params can only be used with components, not HTML elements.', + type: 'GlimmerElementNode', + }, + ], + }, ], });