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' }], + }, + ], +});