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
+
+ {{input tagName='foo'}}
+ {{input tagName=X}}
+ {{component 'input' tagName='foo'}}
+ {{component 'input' tagName=X}}
+ {{yield (component 'input' tagName='foo')}}
+ {{yield (component 'input' tagName=X)}}
+
+```
+
+## 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: [
+ '{{input value=this.foo}}',
+ // Test cases ported from ember-template-lint
+ '{{input type="text"}}',
+ '{{component "input" type="text"}}',
+ '{{yield (component "input" type="text")}}',
+ ],
+ invalid: [
+ {
+ code: '{{input tagName="span"}}',
+ output: null,
+ errors: [{ messageId: 'unexpected' }],
+ },
+
+ // Test cases ported from ember-template-lint
+ {
+ code: '{{input tagName="foo"}}',
+ output: null,
+ errors: [{ messageId: 'unexpected' }],
+ },
+ {
+ code: '{{input tagName=bar}}',
+ output: null,
+ errors: [{ messageId: 'unexpected' }],
+ },
+ {
+ code: '{{component "input" tagName="foo"}}',
+ output: null,
+ errors: [{ messageId: 'unexpected' }],
+ },
+ {
+ code: '{{component "input" tagName=bar}}',
+ output: null,
+ errors: [{ messageId: 'unexpected' }],
+ },
+ {
+ code: '{{yield (component "input" tagName="foo")}}',
+ output: null,
+ errors: [{ messageId: 'unexpected' }],
+ },
+ {
+ code: '{{yield (component "input" tagName=bar)}}',
+ output: null,
+ errors: [{ messageId: 'unexpected' }],
+ },
+ ],
+});