Skip to content

Commit 152939b

Browse files
committed
Extract rule: template-no-splattributes-with-class
1 parent ec2712e commit 152939b

4 files changed

Lines changed: 198 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ rules in templates can be disabled with eslint directives with mustache or html
256256
| [template-no-obsolete-elements](docs/rules/template-no-obsolete-elements.md) | disallow obsolete HTML elements | | | |
257257
| [template-no-outlet-outside-routes](docs/rules/template-no-outlet-outside-routes.md) | disallow {{outlet}} outside of route templates | | | |
258258
| [template-no-page-title-component](docs/rules/template-no-page-title-component.md) | disallow usage of ember-page-title component | | | |
259+
| [template-no-splattributes-with-class](docs/rules/template-no-splattributes-with-class.md) | disallow splattributes with class attribute | | | |
259260
| [template-no-trailing-spaces](docs/rules/template-no-trailing-spaces.md) | disallow trailing whitespace at the end of lines in templates | | 🔧 | |
260261
| [template-no-unavailable-this](docs/rules/template-no-unavailable-this.md) | disallow `this` in templates that are not inside a class or function | | | |
261262
| [template-no-unnecessary-component-helper](docs/rules/template-no-unnecessary-component-helper.md) | disallow unnecessary component helper | | 🔧 | |
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# ember/template-no-splattributes-with-class
2+
3+
<!-- end auto-generated rule header -->
4+
5+
Disallow using `...attributes` with `class` attribute.
6+
7+
When using `...attributes` (splattributes), any classes passed from the parent component will be automatically merged with the component's own classes. Adding a `class` attribute alongside `...attributes` can lead to confusion about which classes take precedence.
8+
9+
## Examples
10+
11+
This rule **forbids** the following:
12+
13+
```gjs
14+
<template>
15+
<div ...attributes class='foo'>
16+
content
17+
</div>
18+
</template>
19+
```
20+
21+
```gjs
22+
<template>
23+
<div class='foo' ...attributes>
24+
content
25+
</div>
26+
</template>
27+
```
28+
29+
This rule **allows** the following:
30+
31+
```gjs
32+
<template>
33+
<div ...attributes>
34+
content
35+
</div>
36+
</template>
37+
```
38+
39+
```gjs
40+
<template>
41+
<div class='foo'>
42+
content
43+
</div>
44+
</template>
45+
```
46+
47+
## Why?
48+
49+
When using `...attributes`, classes are automatically merged from parent components. Using a `class` attribute alongside it creates confusion about which classes take precedence and can lead to unexpected styling behavior.
50+
51+
## References
52+
53+
- [Ember.js Guides - Splattributes](https://guides.emberjs.com/release/components/component-arguments-and-html-attributes/#toc_html-attributes)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const ERROR_MESSAGE =
2+
'Using `...attributes` with `class` attribute is not allowed. Use `...attributes` alone to allow class merging.';
3+
4+
/** @type {import('eslint').Rule.RuleModule} */
5+
module.exports = {
6+
meta: {
7+
type: 'suggestion',
8+
docs: {
9+
description: 'disallow splattributes with class attribute',
10+
category: 'Best Practices',
11+
recommended: false,
12+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-splattributes-with-class.md',
13+
templateMode: 'both',
14+
},
15+
fixable: null,
16+
schema: [],
17+
messages: {},
18+
originallyFrom: {
19+
name: 'ember-template-lint',
20+
rule: 'lib/rules/no-splattributes-with-class.js',
21+
docs: 'docs/rule/no-splattributes-with-class.md',
22+
tests: 'test/unit/rules/no-splattributes-with-class-test.js',
23+
},
24+
},
25+
26+
create(context) {
27+
return {
28+
GlimmerElementNode(node) {
29+
const hasSplattributes = node.attributes.some((attr) => attr.name === '...attributes');
30+
const classAttribute = node.attributes.find((attr) => attr.name === 'class');
31+
32+
if (hasSplattributes && classAttribute) {
33+
context.report({
34+
node: classAttribute,
35+
message: ERROR_MESSAGE,
36+
});
37+
}
38+
},
39+
};
40+
},
41+
};
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
const rule = require('../../../lib/rules/template-no-splattributes-with-class');
2+
const RuleTester = require('eslint').RuleTester;
3+
4+
const ERROR_MESSAGE =
5+
'Using `...attributes` with `class` attribute is not allowed. Use `...attributes` alone to allow class merging.';
6+
7+
const ruleTester = new RuleTester({
8+
parser: require.resolve('ember-eslint-parser'),
9+
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
10+
});
11+
12+
ruleTester.run('template-no-splattributes-with-class', rule, {
13+
valid: [
14+
'<template><div ...attributes>content</div></template>',
15+
'<template><div class="foo">content</div></template>',
16+
'<template><div class="foo bar">content</div></template>',
17+
'<template><div class={{foo}}>content</div></template>',
18+
'<template><div class="foo {{bar}}">content</div></template>',
19+
],
20+
invalid: [
21+
{
22+
code: '<template><div ...attributes class="foo">content</div></template>',
23+
output: null,
24+
errors: [{ message: ERROR_MESSAGE }],
25+
},
26+
{
27+
code: '<template><div class="foo" ...attributes>content</div></template>',
28+
output: null,
29+
errors: [{ message: ERROR_MESSAGE }],
30+
},
31+
{
32+
code: '<template><div ...attributes class={{foo}}>content</div></template>',
33+
output: null,
34+
errors: [{ message: ERROR_MESSAGE }],
35+
},
36+
37+
{
38+
code: '<template><div class="foo" ...attributes class="bar">content</div></template>',
39+
output: null,
40+
errors: [{ message: ERROR_MESSAGE }],
41+
},
42+
],
43+
});
44+
45+
const hbsRuleTester = new RuleTester({
46+
parser: require.resolve('ember-eslint-parser/hbs'),
47+
parserOptions: {
48+
ecmaVersion: 2022,
49+
sourceType: 'module',
50+
},
51+
});
52+
53+
hbsRuleTester.run('template-no-splattributes-with-class', rule, {
54+
valid: [
55+
'<div ...attributes>content</div>',
56+
'<div class="foo">content</div>',
57+
'<div class="foo bar">content</div>',
58+
'<div class={{foo}}>content</div>',
59+
'<div class="foo {{bar}}">content</div>',
60+
],
61+
invalid: [
62+
{
63+
code: '<div ...attributes class="foo">content</div>',
64+
output: null,
65+
errors: [
66+
{
67+
message:
68+
'Using `...attributes` with `class` attribute is not allowed. Use `...attributes` alone to allow class merging.',
69+
},
70+
],
71+
},
72+
{
73+
code: '<div class="foo" ...attributes>content</div>',
74+
output: null,
75+
errors: [
76+
{
77+
message:
78+
'Using `...attributes` with `class` attribute is not allowed. Use `...attributes` alone to allow class merging.',
79+
},
80+
],
81+
},
82+
{
83+
code: '<div ...attributes class={{foo}}>content</div>',
84+
output: null,
85+
errors: [
86+
{
87+
message:
88+
'Using `...attributes` with `class` attribute is not allowed. Use `...attributes` alone to allow class merging.',
89+
},
90+
],
91+
},
92+
{
93+
code: '<div class="foo" ...attributes class="bar">content</div>',
94+
output: null,
95+
errors: [
96+
{
97+
message:
98+
'Using `...attributes` with `class` attribute is not allowed. Use `...attributes` alone to allow class merging.',
99+
},
100+
],
101+
},
102+
],
103+
});

0 commit comments

Comments
 (0)