diff --git a/lib/rules/template-no-arguments-for-html-elements.js b/lib/rules/template-no-arguments-for-html-elements.js index b9f4750139..4ea227f994 100644 --- a/lib/rules/template-no-arguments-for-html-elements.js +++ b/lib/rules/template-no-arguments-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', @@ -16,27 +20,31 @@ module.exports = { noArgumentsForHtmlElements: '@arguments can only be used on components, not HTML elements. Use regular attributes instead.', }, + originallyFrom: { + name: 'ember-template-lint', + rule: 'lib/rules/no-arguments-for-html-elements.js', + docs: 'docs/rule/no-arguments-for-html-elements.md', + tests: 'test/unit/rules/no-arguments-for-html-elements-test.js', + }, }, 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 @arguments for (const attr of node.attributes) { if (attr.type === 'GlimmerAttrNode' && attr.name.startsWith('@')) { context.report({ diff --git a/package.json b/package.json index de20f9800e..488579b1d4 100644 --- a/package.json +++ b/package.json @@ -73,8 +73,10 @@ "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..301983850e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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 diff --git a/tests/lib/rules/template-no-arguments-for-html-elements.js b/tests/lib/rules/template-no-arguments-for-html-elements.js index 330ba683e6..d3bd2714e2 100644 --- a/tests/lib/rules/template-no-arguments-for-html-elements.js +++ b/tests/lib/rules/template-no-arguments-for-html-elements.js @@ -13,6 +13,13 @@ ruleTester.run('template-no-arguments-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. + '', + // Named blocks (colon-prefixed) aren't in the allowlists either. + '', `let div =