Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
71 changes: 71 additions & 0 deletions docs/rules/template-attribute-order.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# ember/template-attribute-order

<!-- end auto-generated rule header -->

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
<template>
<div id="main" class="container"></div>
</template>
```

```gjs
<template>
<button aria-label="Submit" role="button">Send</button>
</template>
```

Examples of **correct** code for this rule:

```gjs
<template>
<div class="container" id="main"></div>
</template>
```

```gjs
<template>
<button class="btn" role="button" aria-label="Submit">Send</button>
</template>
```

## 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)
103 changes: 103 additions & 0 deletions lib/rules/template-attribute-order.js
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
},
};
},
};
34 changes: 34 additions & 0 deletions tests/lib/rules/template-attribute-order.js
Original file line number Diff line number Diff line change
@@ -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: [
'<template><div class="foo" id="bar"></div></template>',
'<template><button class="btn" role="button" aria-label="Submit"></button></template>',
'<template><input type="text" name="username" value=""></template>',
'<template><div data-test-id="foo"></div></template>',
],

invalid: [
{
code: '<template><div id="bar" class="foo"></div></template>',
output: null,
errors: [{ messageId: 'wrongOrder' }],
},
{
code: '<template><button aria-label="Submit" role="button"></button></template>',
output: null,
errors: [{ messageId: 'wrongOrder' }],
},
{
code: '<template><input name="username" type="text"></template>',
output: null,
errors: [{ messageId: 'wrongOrder' }],
},
],
});
Loading