Skip to content

Commit 905fbda

Browse files
committed
Extract rule: template-modifier-name-case
1 parent ca9741c commit 905fbda

4 files changed

Lines changed: 216 additions & 7 deletions

File tree

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -322,13 +322,14 @@ rules in templates can be disabled with eslint directives with mustache or html
322322

323323
### Stylistic Issues
324324

325-
| Name                     | Description | 💼 | 🔧 | 💡 |
326-
| :----------------------------------------------------------------- | :------------------------------------------------------------- | :- | :- | :- |
327-
| [order-in-components](docs/rules/order-in-components.md) | enforce proper order of properties in components | | 🔧 | |
328-
| [order-in-controllers](docs/rules/order-in-controllers.md) | enforce proper order of properties in controllers | | 🔧 | |
329-
| [order-in-models](docs/rules/order-in-models.md) | enforce proper order of properties in models | | 🔧 | |
330-
| [order-in-routes](docs/rules/order-in-routes.md) | enforce proper order of properties in routes | | 🔧 | |
331-
| [template-attribute-order](docs/rules/template-attribute-order.md) | enforce consistent ordering of attributes in template elements | | | |
325+
| Name                        | Description | 💼 | 🔧 | 💡 |
326+
| :----------------------------------------------------------------------- | :------------------------------------------------------------- | :- | :- | :- |
327+
| [order-in-components](docs/rules/order-in-components.md) | enforce proper order of properties in components | | 🔧 | |
328+
| [order-in-controllers](docs/rules/order-in-controllers.md) | enforce proper order of properties in controllers | | 🔧 | |
329+
| [order-in-models](docs/rules/order-in-models.md) | enforce proper order of properties in models | | 🔧 | |
330+
| [order-in-routes](docs/rules/order-in-routes.md) | enforce proper order of properties in routes | | 🔧 | |
331+
| [template-attribute-order](docs/rules/template-attribute-order.md) | enforce consistent ordering of attributes in template elements | | | |
332+
| [template-modifier-name-case](docs/rules/template-modifier-name-case.md) | require dasherized names for modifiers | | 🔧 | |
332333

333334
### Testing
334335

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# ember/template-modifier-name-case
2+
3+
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
4+
5+
<!-- end auto-generated rule header -->
6+
7+
Requires dasherized names for modifiers.
8+
9+
Modifiers should use dasherized names when being invoked, not camelCase. This is a stylistic rule that will prevent you from using camelCase modifiers, requiring you to use dasherized modifier names instead.
10+
11+
## Examples
12+
13+
This rule **forbids** the following:
14+
15+
```hbs
16+
<div {{didInsert}}></div>
17+
```
18+
19+
```hbs
20+
<div {{onFocus}}></div>
21+
```
22+
23+
```hbs
24+
<div {{modifier 'didInsert'}}></div>
25+
```
26+
27+
This rule **allows** the following:
28+
29+
```hbs
30+
<div {{did-insert}}></div>
31+
```
32+
33+
```hbs
34+
<div {{on-focus}}></div>
35+
```
36+
37+
```hbs
38+
<div {{modifier 'did-insert'}}></div>
39+
```
40+
41+
## See Also
42+
43+
- [named-functions-in-promises](named-functions-in-promises.md)
44+
45+
## References
46+
47+
- [Template syntax guide - Modifiers](https://guides.emberjs.com/release/components/template-syntax/#toc_modifiers)
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/* eslint-disable unicorn/consistent-function-scoping, eslint-plugin/no-unused-message-ids */
2+
3+
function dasherize(str) {
4+
return str.replaceAll(/([A-Z])/g, '-$1').toLowerCase();
5+
}
6+
7+
/** @type {import('eslint').Rule.RuleModule} */
8+
module.exports = {
9+
meta: {
10+
type: 'suggestion',
11+
docs: {
12+
description: 'require dasherized names for modifiers',
13+
category: 'Stylistic Issues',
14+
strictGjs: true,
15+
strictGts: true,
16+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-modifier-name-case.md',
17+
},
18+
fixable: 'code',
19+
schema: [],
20+
messages: {
21+
dasherized:
22+
'Use dasherized names for modifier invocation. Please replace `{{dasherizeModifierName}}` with `{{dasherizeModifierName}}`.',
23+
},
24+
},
25+
26+
create(context) {
27+
function generateErrorMessage(modifierName) {
28+
const dasherizedName = dasherize(modifierName);
29+
return `Use dasherized names for modifier invocation. Please replace \`${modifierName}\` with \`${dasherizedName}\`.`;
30+
}
31+
32+
function isModifierHelper(node) {
33+
return (
34+
node.path && node.path.type === 'GlimmerPathExpression' && node.path.original === 'modifier'
35+
);
36+
}
37+
38+
return {
39+
GlimmerElementModifierStatement(node) {
40+
const modifierName = node.path?.original;
41+
42+
if (typeof modifierName === 'string' && modifierName !== dasherize(modifierName)) {
43+
context.report({
44+
node,
45+
message: generateErrorMessage(modifierName),
46+
fix(fixer) {
47+
const dasherizedName = dasherize(modifierName);
48+
return fixer.replaceTextRange(node.path.range, dasherizedName);
49+
},
50+
});
51+
}
52+
},
53+
54+
GlimmerSubExpression(node) {
55+
if (!isModifierHelper(node)) {
56+
return;
57+
}
58+
59+
const nameParam = node.params?.[0];
60+
61+
if (nameParam && nameParam.type === 'GlimmerStringLiteral') {
62+
const modifierName = nameParam.value;
63+
64+
if (typeof modifierName === 'string' && modifierName !== dasherize(modifierName)) {
65+
context.report({
66+
node: nameParam,
67+
message: generateErrorMessage(modifierName),
68+
fix(fixer) {
69+
const dasherizedName = dasherize(modifierName);
70+
return fixer.replaceTextRange(nameParam.range, `"${dasherizedName}"`);
71+
},
72+
});
73+
}
74+
}
75+
},
76+
77+
GlimmerMustacheStatement(node) {
78+
if (!isModifierHelper(node)) {
79+
return;
80+
}
81+
82+
const nameParam = node.params?.[0];
83+
84+
if (nameParam && nameParam.type === 'GlimmerStringLiteral') {
85+
const modifierName = nameParam.value;
86+
87+
if (typeof modifierName === 'string' && modifierName !== dasherize(modifierName)) {
88+
context.report({
89+
node: nameParam,
90+
message: generateErrorMessage(modifierName),
91+
fix(fixer) {
92+
const dasherizedName = dasherize(modifierName);
93+
return fixer.replaceTextRange(nameParam.range, `"${dasherizedName}"`);
94+
},
95+
});
96+
}
97+
}
98+
},
99+
};
100+
},
101+
};
102+
/* eslint-enable unicorn/consistent-function-scoping, eslint-plugin/no-unused-message-ids */
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const rule = require('../../../lib/rules/template-modifier-name-case');
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-modifier-name-case', rule, {
10+
valid: [
11+
'<template><div {{did-insert}}></div></template>',
12+
'<template><div {{did-update}}></div></template>',
13+
'<template><div {{on-click}}></div></template>',
14+
'<template><div {{(modifier "did-insert")}}></div></template>',
15+
'<template><div {{(modifier "on-click")}}></div></template>',
16+
],
17+
invalid: [
18+
{
19+
code: '<template><div {{didInsert}}></div></template>',
20+
output: '<template><div {{did-insert}}></div></template>',
21+
errors: [
22+
{
23+
message:
24+
'Use dasherized names for modifier invocation. Please replace `didInsert` with `did-insert`.',
25+
},
26+
],
27+
},
28+
{
29+
code: '<template><div {{doSomething}}></div></template>',
30+
output: '<template><div {{do-something}}></div></template>',
31+
errors: [
32+
{
33+
message:
34+
'Use dasherized names for modifier invocation. Please replace `doSomething` with `do-something`.',
35+
},
36+
],
37+
},
38+
{
39+
code: '<template><div {{fooBar}}></div></template>',
40+
output: '<template><div {{foo-bar}}></div></template>',
41+
errors: [
42+
{
43+
message:
44+
'Use dasherized names for modifier invocation. Please replace `fooBar` with `foo-bar`.',
45+
},
46+
],
47+
},
48+
{
49+
code: '<template><div {{(modifier "didInsert")}}></div></template>',
50+
output: '<template><div {{(modifier "did-insert")}}></div></template>',
51+
errors: [
52+
{
53+
message:
54+
'Use dasherized names for modifier invocation. Please replace `didInsert` with `did-insert`.',
55+
},
56+
],
57+
},
58+
],
59+
});

0 commit comments

Comments
 (0)