From da16b64b014a3d0fb9ae5d07ded60efba221ead7 Mon Sep 17 00:00:00 2001
From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com>
Date: Tue, 17 Feb 2026 15:37:42 -0500
Subject: [PATCH] Extract rule: template-attribute-order
---
README.md | 13 +--
docs/rules/template-attribute-order.md | 71 ++++++++++++++
lib/rules/template-attribute-order.js | 103 ++++++++++++++++++++
tests/lib/rules/template-attribute-order.js | 34 +++++++
4 files changed, 215 insertions(+), 6 deletions(-)
create mode 100644 docs/rules/template-attribute-order.md
create mode 100644 lib/rules/template-attribute-order.js
create mode 100644 tests/lib/rules/template-attribute-order.js
diff --git a/README.md b/README.md
index 1997fcd7ea..a240290d77 100644
--- a/README.md
+++ b/README.md
@@ -315,12 +315,13 @@ rules in templates can be disabled with eslint directives with mustache or html
### Stylistic Issues
-| Name | Description | 💼 | 🔧 | 💡 |
-| :--------------------------------------------------------- | :------------------------------------------------ | :- | :- | :- |
-| [order-in-components](docs/rules/order-in-components.md) | enforce proper order of properties in components | | 🔧 | |
-| [order-in-controllers](docs/rules/order-in-controllers.md) | enforce proper order of properties in controllers | | 🔧 | |
-| [order-in-models](docs/rules/order-in-models.md) | enforce proper order of properties in models | | 🔧 | |
-| [order-in-routes](docs/rules/order-in-routes.md) | enforce proper order of properties in routes | | 🔧 | |
+| Name                    | Description | 💼 | 🔧 | 💡 |
+| :----------------------------------------------------------------- | :------------------------------------------------------------- | :- | :- | :- |
+| [order-in-components](docs/rules/order-in-components.md) | enforce proper order of properties in components | | 🔧 | |
+| [order-in-controllers](docs/rules/order-in-controllers.md) | enforce proper order of properties in controllers | | 🔧 | |
+| [order-in-models](docs/rules/order-in-models.md) | enforce proper order of properties in models | | 🔧 | |
+| [order-in-routes](docs/rules/order-in-routes.md) | enforce proper order of properties in routes | | 🔧 | |
+| [template-attribute-order](docs/rules/template-attribute-order.md) | enforce consistent ordering of attributes in template elements | | | |
### Testing
diff --git a/docs/rules/template-attribute-order.md b/docs/rules/template-attribute-order.md
new file mode 100644
index 0000000000..2e64946cc3
--- /dev/null
+++ b/docs/rules/template-attribute-order.md
@@ -0,0 +1,71 @@
+# ember/template-attribute-order
+
+
+
+Enforces a consistent ordering of attributes in template elements. This helps improve readability and maintainability of templates.
+
+## Rule Details
+
+This rule enforces a consistent order for attributes on template elements. By default, it follows this order:
+
+1. `class`
+2. `id`
+3. `role`
+4. `aria-*` attributes
+5. `data-test-*` attributes
+6. `type`
+7. `name`
+8. `value`
+9. `placeholder`
+10. `disabled`
+
+## Examples
+
+Examples of **incorrect** code for this rule:
+
+```gjs
+
+
+
+```
+
+```gjs
+
+
+
+```
+
+Examples of **correct** code for this rule:
+
+```gjs
+
+
+
+```
+
+```gjs
+
+
+
+```
+
+## Configuration
+
+You can customize the order by providing an `order` array:
+
+```js
+module.exports = {
+ rules: {
+ 'ember/template-attribute-order': [
+ 'error',
+ {
+ order: ['class', 'id', 'role', 'aria-', 'type'],
+ },
+ ],
+ },
+};
+```
+
+## References
+
+- [ember-template-lint attribute-order](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/attribute-order.md)
diff --git a/lib/rules/template-attribute-order.js b/lib/rules/template-attribute-order.js
new file mode 100644
index 0000000000..3b0f8a9baa
--- /dev/null
+++ b/lib/rules/template-attribute-order.js
@@ -0,0 +1,103 @@
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'enforce consistent ordering of attributes in template elements',
+ category: 'Stylistic Issues',
+ strictGjs: true,
+ strictGts: true,
+ url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-attribute-order.md',
+ },
+ fixable: null,
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ order: {
+ type: 'array',
+ items: {
+ type: 'string',
+ },
+ },
+ },
+ additionalProperties: false,
+ },
+ ],
+ messages: {
+ wrongOrder: 'Attribute "{{currentAttr}}" should come {{position}} "{{expectedAttr}}".',
+ },
+ },
+
+ create(context) {
+ const options = context.options[0] || {};
+ const order = options.order || [
+ 'class',
+ 'id',
+ 'role',
+ 'aria-',
+ 'data-test-',
+ 'type',
+ 'name',
+ 'value',
+ 'placeholder',
+ 'disabled',
+ ];
+
+ function getAttributeCategory(attrName) {
+ for (const category of order) {
+ if (category.endsWith('-')) {
+ if (attrName.startsWith(category)) {
+ return category;
+ }
+ } else if (attrName === category) {
+ return category;
+ }
+ }
+ return null;
+ }
+
+ function getExpectedIndex(attrName) {
+ const category = getAttributeCategory(attrName);
+ if (category === null) {
+ return order.length; // Unknown attributes go last
+ }
+ return order.indexOf(category);
+ }
+
+ return {
+ GlimmerElementNode(node) {
+ if (!node.attributes || node.attributes.length < 2) {
+ return;
+ }
+
+ const attributes = node.attributes.filter(
+ (attr) => attr.type === 'GlimmerAttrNode' && attr.name
+ );
+
+ for (let i = 1; i < attributes.length; i++) {
+ const current = attributes[i];
+ const currentIndex = getExpectedIndex(current.name);
+
+ for (let j = 0; j < i; j++) {
+ const previous = attributes[j];
+ const previousIndex = getExpectedIndex(previous.name);
+
+ if (currentIndex < previousIndex) {
+ context.report({
+ node: current,
+ messageId: 'wrongOrder',
+ data: {
+ currentAttr: current.name,
+ position: 'before',
+ expectedAttr: previous.name,
+ },
+ });
+ break;
+ }
+ }
+ }
+ },
+ };
+ },
+};
diff --git a/tests/lib/rules/template-attribute-order.js b/tests/lib/rules/template-attribute-order.js
new file mode 100644
index 0000000000..4d921c9e89
--- /dev/null
+++ b/tests/lib/rules/template-attribute-order.js
@@ -0,0 +1,34 @@
+const rule = require('../../../lib/rules/template-attribute-order');
+const RuleTester = require('eslint').RuleTester;
+
+const ruleTester = new RuleTester({
+ parser: require.resolve('ember-eslint-parser'),
+ parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
+});
+
+ruleTester.run('template-attribute-order', rule, {
+ valid: [
+ '',
+ '',
+ '',
+ '',
+ ],
+
+ invalid: [
+ {
+ code: '',
+ output: null,
+ errors: [{ messageId: 'wrongOrder' }],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [{ messageId: 'wrongOrder' }],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [{ messageId: 'wrongOrder' }],
+ },
+ ],
+});