Skip to content

Commit 4d27b16

Browse files
committed
Extract rule: template-no-class-bindings
1 parent 3e00ff7 commit 4d27b16

4 files changed

Lines changed: 241 additions & 30 deletions

File tree

README.md

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -197,36 +197,37 @@ rules in templates can be disabled with eslint directives with mustache or html
197197

198198
### Best Practices
199199

200-
| Name                                          | Description | 💼 | 🔧 | 💡 |
201-
| :----------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------- | :- | :- | :- |
202-
| [template-builtin-component-arguments](docs/rules/template-builtin-component-arguments.md) | disallow setting certain attributes on builtin components | | | |
203-
| [template-no-action-modifiers](docs/rules/template-no-action-modifiers.md) | disallow usage of {{action}} modifiers | | | |
204-
| [template-no-arguments-for-html-elements](docs/rules/template-no-arguments-for-html-elements.md) | disallow @arguments on HTML elements | | | |
205-
| [template-no-array-prototype-extensions](docs/rules/template-no-array-prototype-extensions.md) | disallow usage of Ember Array prototype extensions | | | |
206-
| [template-no-block-params-for-html-elements](docs/rules/template-no-block-params-for-html-elements.md) | disallow block params on HTML elements | | | |
207-
| [template-no-capital-arguments](docs/rules/template-no-capital-arguments.md) | disallow capital arguments (use lowercase @arg instead of @Arg) | | | |
208-
| [template-no-chained-this](docs/rules/template-no-chained-this.md) | disallow redundant `this.this` in templates | | 🔧 | |
209-
| [template-no-debugger](docs/rules/template-no-debugger.md) | disallow {{debugger}} in templates | | | |
210-
| [template-no-dynamic-subexpression-invocations](docs/rules/template-no-dynamic-subexpression-invocations.md) | disallow dynamic subexpression invocations | | | |
211-
| [template-no-element-event-actions](docs/rules/template-no-element-event-actions.md) | disallow element event actions (use {{on}} modifier instead) | | | |
212-
| [template-no-implicit-this](docs/rules/template-no-implicit-this.md) | require explicit `this` in property access | | | |
213-
| [template-no-inline-event-handlers](docs/rules/template-no-inline-event-handlers.md) | disallow DOM event handler attributes | | | |
214-
| [template-no-inline-styles](docs/rules/template-no-inline-styles.md) | disallow inline styles | | | |
215-
| [template-no-input-block](docs/rules/template-no-input-block.md) | disallow block usage of {{input}} helper | | | |
216-
| [template-no-input-placeholder](docs/rules/template-no-input-placeholder.md) | disallow placeholder attribute on input elements | | | |
217-
| [template-no-input-tagname](docs/rules/template-no-input-tagname.md) | disallow tagName attribute on {{input}} helper | | | |
218-
| [template-no-invalid-meta](docs/rules/template-no-invalid-meta.md) | disallow invalid meta tags | | | |
219-
| [template-no-log](docs/rules/template-no-log.md) | disallow {{log}} in templates | | | |
220-
| [template-no-model-argument-in-route-templates](docs/rules/template-no-model-argument-in-route-templates.md) | disallow @model argument in route templates | | 🔧 | |
221-
| [template-no-multiple-empty-lines](docs/rules/template-no-multiple-empty-lines.md) | disallow multiple consecutive empty lines in templates | | | |
222-
| [template-no-mut-helper](docs/rules/template-no-mut-helper.md) | disallow usage of (mut) helper | | | |
223-
| [template-no-negated-comparison](docs/rules/template-no-negated-comparison.md) | disallow negated comparisons in templates | | | |
224-
| [template-no-negated-condition](docs/rules/template-no-negated-condition.md) | disallow negated conditions in if/unless | | | |
225-
| [template-no-nested-splattributes](docs/rules/template-no-nested-splattributes.md) | disallow nested ...attributes usage | | | |
226-
| [template-no-obscure-array-access](docs/rules/template-no-obscure-array-access.md) | disallow obscure array access patterns like `objectPath.@each.property` | | | |
227-
| [template-no-obsolete-elements](docs/rules/template-no-obsolete-elements.md) | disallow obsolete HTML elements | | | |
228-
| [template-no-outlet-outside-routes](docs/rules/template-no-outlet-outside-routes.md) | disallow {{outlet}} outside of route templates | | | |
229-
| [template-no-page-title-component](docs/rules/template-no-page-title-component.md) | disallow usage of ember-page-title component | | | |
200+
| Name                                          | Description | 💼 | 🔧 | 💡 |
201+
| :----------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------- | :- | :- | :- |
202+
| [template-builtin-component-arguments](docs/rules/template-builtin-component-arguments.md) | disallow setting certain attributes on builtin components | | | |
203+
| [template-no-action-modifiers](docs/rules/template-no-action-modifiers.md) | disallow usage of {{action}} modifiers | | | |
204+
| [template-no-arguments-for-html-elements](docs/rules/template-no-arguments-for-html-elements.md) | disallow @arguments on HTML elements | | | |
205+
| [template-no-array-prototype-extensions](docs/rules/template-no-array-prototype-extensions.md) | disallow usage of Ember Array prototype extensions | | | |
206+
| [template-no-block-params-for-html-elements](docs/rules/template-no-block-params-for-html-elements.md) | disallow block params on HTML elements | | | |
207+
| [template-no-capital-arguments](docs/rules/template-no-capital-arguments.md) | disallow capital arguments (use lowercase @arg instead of @Arg) | | | |
208+
| [template-no-chained-this](docs/rules/template-no-chained-this.md) | disallow redundant `this.this` in templates | | 🔧 | |
209+
| [template-no-class-bindings](docs/rules/template-no-class-bindings.md) | disallow passing classBinding or classNameBindings as arguments in templates | | | |
210+
| [template-no-debugger](docs/rules/template-no-debugger.md) | disallow {{debugger}} in templates | | | |
211+
| [template-no-dynamic-subexpression-invocations](docs/rules/template-no-dynamic-subexpression-invocations.md) | disallow dynamic subexpression invocations | | | |
212+
| [template-no-element-event-actions](docs/rules/template-no-element-event-actions.md) | disallow element event actions (use {{on}} modifier instead) | | | |
213+
| [template-no-implicit-this](docs/rules/template-no-implicit-this.md) | require explicit `this` in property access | | | |
214+
| [template-no-inline-event-handlers](docs/rules/template-no-inline-event-handlers.md) | disallow DOM event handler attributes | | | |
215+
| [template-no-inline-styles](docs/rules/template-no-inline-styles.md) | disallow inline styles | | | |
216+
| [template-no-input-block](docs/rules/template-no-input-block.md) | disallow block usage of {{input}} helper | | | |
217+
| [template-no-input-placeholder](docs/rules/template-no-input-placeholder.md) | disallow placeholder attribute on input elements | | | |
218+
| [template-no-input-tagname](docs/rules/template-no-input-tagname.md) | disallow tagName attribute on {{input}} helper | | | |
219+
| [template-no-invalid-meta](docs/rules/template-no-invalid-meta.md) | disallow invalid meta tags | | | |
220+
| [template-no-log](docs/rules/template-no-log.md) | disallow {{log}} in templates | | | |
221+
| [template-no-model-argument-in-route-templates](docs/rules/template-no-model-argument-in-route-templates.md) | disallow @model argument in route templates | | 🔧 | |
222+
| [template-no-multiple-empty-lines](docs/rules/template-no-multiple-empty-lines.md) | disallow multiple consecutive empty lines in templates | | | |
223+
| [template-no-mut-helper](docs/rules/template-no-mut-helper.md) | disallow usage of (mut) helper | | | |
224+
| [template-no-negated-comparison](docs/rules/template-no-negated-comparison.md) | disallow negated comparisons in templates | | | |
225+
| [template-no-negated-condition](docs/rules/template-no-negated-condition.md) | disallow negated conditions in if/unless | | | |
226+
| [template-no-nested-splattributes](docs/rules/template-no-nested-splattributes.md) | disallow nested ...attributes usage | | | |
227+
| [template-no-obscure-array-access](docs/rules/template-no-obscure-array-access.md) | disallow obscure array access patterns like `objectPath.@each.property` | | | |
228+
| [template-no-obsolete-elements](docs/rules/template-no-obsolete-elements.md) | disallow obsolete HTML elements | | | |
229+
| [template-no-outlet-outside-routes](docs/rules/template-no-outlet-outside-routes.md) | disallow {{outlet}} outside of route templates | | | |
230+
| [template-no-page-title-component](docs/rules/template-no-page-title-component.md) | disallow usage of ember-page-title component | | | |
230231

231232
### Components
232233

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# ember/template-no-class-bindings
2+
3+
> **HBS Only**: This rule applies to classic `.hbs` template files only (loose mode). It is not relevant for `gjs`/`gts` files (strict mode), where these patterns cannot occur.
4+
5+
<!-- end auto-generated rule header -->
6+
7+
Disallow passing `classBinding` or `classNameBindings` as arguments within templates. These are legacy Ember Classic patterns that should be replaced with modern approaches.
8+
9+
## Examples
10+
11+
This rule **forbids** the following:
12+
13+
```hbs
14+
<SomeThing @classBinding='isActive:active' />
15+
```
16+
17+
```hbs
18+
{{some-thing classNameBindings='isActive:active:inactive'}}
19+
```
20+
21+
```hbs
22+
<SomeThing @classNameBindings='isActive:active:inactive' />
23+
```
24+
25+
This rule **allows** the following:
26+
27+
```hbs
28+
<SomeThing class={{if this.isActive 'active'}} />
29+
```
30+
31+
```hbs
32+
<SomeThing />
33+
```
34+
35+
## Migration
36+
37+
- find in templates and remove `classBinding` and/or `classNameBindings`.
38+
39+
## References
40+
41+
- [ember-template-lint no-class-bindings](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-class-bindings.md)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/** @type {import('eslint').Rule.RuleModule} */
2+
module.exports = {
3+
meta: {
4+
type: 'problem',
5+
docs: {
6+
description: 'disallow passing classBinding or classNameBindings as arguments in templates',
7+
category: 'Best Practices',
8+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-class-bindings.md',
9+
templateMode: 'loose',
10+
},
11+
fixable: null,
12+
schema: [],
13+
messages: {
14+
noClassBindings:
15+
'Passing the `{{name}}` property as an argument within templates is not allowed.',
16+
},
17+
originallyFrom: {
18+
name: 'ember-template-lint',
19+
rule: 'lib/rules/no-class-bindings.js',
20+
docs: 'docs/rule/no-class-bindings.md',
21+
tests: 'test/unit/rules/no-class-bindings-test.js',
22+
},
23+
},
24+
25+
create(context) {
26+
const FORBIDDEN_ATTR_NAMES = new Set([
27+
'classBinding',
28+
'@classBinding',
29+
'classNameBindings',
30+
'@classNameBindings',
31+
]);
32+
33+
const FORBIDDEN_HASH_KEYS = new Set(['classBinding', 'classNameBindings']);
34+
35+
return {
36+
'GlimmerElementNode > GlimmerAttrNode'(node) {
37+
if (FORBIDDEN_ATTR_NAMES.has(node.name)) {
38+
context.report({
39+
node,
40+
messageId: 'noClassBindings',
41+
data: { name: node.name },
42+
});
43+
}
44+
},
45+
GlimmerHashPair(node) {
46+
if (FORBIDDEN_HASH_KEYS.has(node.key)) {
47+
context.report({
48+
node,
49+
messageId: 'noClassBindings',
50+
data: { name: node.key },
51+
});
52+
}
53+
},
54+
};
55+
},
56+
};
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
const rule = require('../../../lib/rules/template-no-class-bindings');
2+
const RuleTester = require('eslint').RuleTester;
3+
4+
const ruleTester = new RuleTester({
5+
parser: require.resolve('ember-eslint-parser'),
6+
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
7+
});
8+
9+
ruleTester.run('template-no-class-bindings', rule, {
10+
valid: [
11+
'<template><SomeThing /></template>',
12+
'<template>{{lol-wat}}</template>',
13+
'<template>{{true}}</template>',
14+
'<template>{{"hehe"}}</template>',
15+
'<template><div class="foo"></div></template>',
16+
],
17+
invalid: [
18+
{
19+
code: '<template>{{some-thing classBinding="lol:wat"}}</template>',
20+
output: null,
21+
errors: [
22+
{
23+
messageId: 'noClassBindings',
24+
data: { name: 'classBinding' },
25+
},
26+
],
27+
},
28+
{
29+
code: '<template><SomeThing @classBinding="lol:wat" /></template>',
30+
output: null,
31+
errors: [
32+
{
33+
messageId: 'noClassBindings',
34+
data: { name: '@classBinding' },
35+
},
36+
],
37+
},
38+
{
39+
code: '<template>{{some-thing classNameBindings="lol:foo:bar"}}</template>',
40+
output: null,
41+
errors: [
42+
{
43+
messageId: 'noClassBindings',
44+
data: { name: 'classNameBindings' },
45+
},
46+
],
47+
},
48+
{
49+
code: '<template><SomeThing @classNameBindings="lol:foo:bar" /></template>',
50+
output: null,
51+
errors: [
52+
{
53+
messageId: 'noClassBindings',
54+
data: { name: '@classNameBindings' },
55+
},
56+
],
57+
},
58+
],
59+
});
60+
61+
const hbsRuleTester = new RuleTester({
62+
parser: require.resolve('ember-eslint-parser/hbs'),
63+
parserOptions: {
64+
ecmaVersion: 2022,
65+
sourceType: 'module',
66+
},
67+
});
68+
69+
hbsRuleTester.run('template-no-class-bindings', rule, {
70+
valid: ['<SomeThing />', '{{lol-wat}}', '{{true}}', '{{"hehe"}}'],
71+
invalid: [
72+
{
73+
code: '{{some-thing classBinding="lol:wat"}}',
74+
output: null,
75+
errors: [
76+
{
77+
message:
78+
'Passing the `classBinding` property as an argument within templates is not allowed.',
79+
},
80+
],
81+
},
82+
{
83+
code: '<SomeThing @classBinding="lol:wat" />',
84+
output: null,
85+
errors: [
86+
{
87+
message:
88+
'Passing the `@classBinding` property as an argument within templates is not allowed.',
89+
},
90+
],
91+
},
92+
{
93+
code: '{{some-thing classNameBindings="lol:foo:bar"}}',
94+
output: null,
95+
errors: [
96+
{
97+
message:
98+
'Passing the `classNameBindings` property as an argument within templates is not allowed.',
99+
},
100+
],
101+
},
102+
{
103+
code: '<SomeThing @classNameBindings="lol:foo:bar" />',
104+
output: null,
105+
errors: [
106+
{
107+
message:
108+
'Passing the `@classNameBindings` property as an argument within templates is not allowed.',
109+
},
110+
],
111+
},
112+
],
113+
});

0 commit comments

Comments
 (0)