From 52c8ff858fa4191ccb088a2a521cf71fc21479b6 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Mon, 23 Feb 2026 15:55:04 -0500 Subject: [PATCH] Extract rule: template-no-input-tagname --- README.md | 1 + docs/rules/template-no-input-tagname.md | 30 +++++++++++ lib/rules/template-no-input-tagname.js | 38 ++++++++++++++ tests/lib/rules/template-no-input-tagname.js | 55 ++++++++++++++++++++ 4 files changed, 124 insertions(+) create mode 100644 docs/rules/template-no-input-tagname.md create mode 100644 lib/rules/template-no-input-tagname.js create mode 100644 tests/lib/rules/template-no-input-tagname.js diff --git a/README.md b/README.md index ae2c7b3b6c..c6c0ce3c32 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,7 @@ rules in templates can be disabled with eslint directives with mustache or html | [template-no-capital-arguments](docs/rules/template-no-capital-arguments.md) | disallow capital arguments (use lowercase @arg instead of @Arg) | | | | | [template-no-debugger](docs/rules/template-no-debugger.md) | disallow {{debugger}} in templates | | | | | [template-no-element-event-actions](docs/rules/template-no-element-event-actions.md) | disallow element event actions (use {{on}} modifier instead) | | | | +| [template-no-input-tagname](docs/rules/template-no-input-tagname.md) | disallow tagName attribute on {{input}} helper | | | | | [template-no-log](docs/rules/template-no-log.md) | disallow {{log}} in templates | | | | ### Components diff --git a/docs/rules/template-no-input-tagname.md b/docs/rules/template-no-input-tagname.md new file mode 100644 index 0000000000..8a630390a2 --- /dev/null +++ b/docs/rules/template-no-input-tagname.md @@ -0,0 +1,30 @@ +# ember/template-no-input-tagname + + + +`{{input tagName=x}}` will result in obtuse errors. Typically, the input will simply fail to render, whether used in block form or inline. The only valid `tagName` for the input helper is `input`. For `textarea`, `button`, and other input-like elements, you should instead create a new component or better use the DOM! + +## Examples + +This rule **forbids** the following: + +```gjs + +``` + +## Related rules + +- [no-link-to-tagname](no-link-to-tagname.md) +- [no-unknown-arguments-for-builtin-components](no-unknown-arguments-for-builtin-components.md) + +## References + +- [Ember api/input component](https://api.emberjs.com/ember/release/classes/Ember.Templates.components/methods/Input?anchor=Input) +- [rfcs/built in components](https://emberjs.github.io/rfcs/0459-angle-bracket-built-in-components.html) diff --git a/lib/rules/template-no-input-tagname.js b/lib/rules/template-no-input-tagname.js new file mode 100644 index 0000000000..152f2c974c --- /dev/null +++ b/lib/rules/template-no-input-tagname.js @@ -0,0 +1,38 @@ +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'disallow tagName attribute on {{input}} helper', + category: 'Best Practices', + strictGjs: true, + strictGts: true, + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-input-tagname.md', + }, + schema: [], + messages: { unexpected: 'Unexpected tagName usage on input helper.' }, + }, + create(context) { + function check(node) { + if (!node.path) { + return; + } + const attrs = node.hash?.pairs || []; + const hasTagName = attrs.some((a) => a.key === 'tagName'); + + if (node.path.original === 'input' && hasTagName) { + context.report({ node, messageId: 'unexpected' }); + } else if ( + node.path.original === 'component' && + node.params?.[0]?.original === 'input' && + hasTagName + ) { + context.report({ node, messageId: 'unexpected' }); + } + } + return { + GlimmerMustacheStatement: check, + GlimmerSubExpression: check, + }; + }, +}; diff --git a/tests/lib/rules/template-no-input-tagname.js b/tests/lib/rules/template-no-input-tagname.js new file mode 100644 index 0000000000..efd4c26eb6 --- /dev/null +++ b/tests/lib/rules/template-no-input-tagname.js @@ -0,0 +1,55 @@ +const rule = require('../../../lib/rules/template-no-input-tagname'); +const RuleTester = require('eslint').RuleTester; + +const ruleTester = new RuleTester({ + parser: require.resolve('ember-eslint-parser'), + parserOptions: { ecmaVersion: 2022, sourceType: 'module' }, +}); +ruleTester.run('template-no-input-tagname', rule, { + valid: [ + '', + // Test cases ported from ember-template-lint + '', + '', + '', + ], + invalid: [ + { + code: '', + output: null, + errors: [{ messageId: 'unexpected' }], + }, + + // Test cases ported from ember-template-lint + { + code: '', + output: null, + errors: [{ messageId: 'unexpected' }], + }, + { + code: '', + output: null, + errors: [{ messageId: 'unexpected' }], + }, + { + code: '', + output: null, + errors: [{ messageId: 'unexpected' }], + }, + { + code: '', + output: null, + errors: [{ messageId: 'unexpected' }], + }, + { + code: '', + output: null, + errors: [{ messageId: 'unexpected' }], + }, + { + code: '', + output: null, + errors: [{ messageId: 'unexpected' }], + }, + ], +});