Skip to content

Commit a120029

Browse files
Merge pull request #2391 from NullVoxPopuli/nvp/template-lint-extract-rule-template-builtin-component-arguments
Extract rule: template-builtin-component-arguments
2 parents 8bf87fd + fc1532a commit a120029

4 files changed

Lines changed: 175 additions & 4 deletions

File tree

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,11 @@ rules in templates can be disabled with eslint directives with mustache or html
176176

177177
### Best Practices
178178

179-
| Name | Description | 💼 | 🔧 | 💡 |
180-
| :--------------------------------------------------------- | :--------------------------------- | :- | :- | :- |
181-
| [template-no-debugger](docs/rules/template-no-debugger.md) | disallow {{debugger}} in templates | | | |
182-
| [template-no-log](docs/rules/template-no-log.md) | disallow {{log}} in templates | | | |
179+
| Name | Description | 💼 | 🔧 | 💡 |
180+
| :----------------------------------------------------------------------------------------- | :-------------------------------------------------------- | :- | :- | :- |
181+
| [template-builtin-component-arguments](docs/rules/template-builtin-component-arguments.md) | disallow setting certain attributes on builtin components | | | |
182+
| [template-no-debugger](docs/rules/template-no-debugger.md) | disallow {{debugger}} in templates | | | |
183+
| [template-no-log](docs/rules/template-no-log.md) | disallow {{log}} in templates | | | |
183184

184185
### Components
185186

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# ember/template-builtin-component-arguments
2+
3+
<!-- end auto-generated rule header -->
4+
5+
✅ The `extends: 'recommended'` property in a configuration file enables this rule.
6+
7+
The builtin `Input` component has several arguments that match attributes
8+
of the lower-case `input` HTML element. These arguments should be set via e.g.
9+
`@type`, instead of `type`, but it is easy to forget and can cause subtle
10+
issues.
11+
12+
This rule warns about `Input` component invocations that use the following attributes instead of arguments:
13+
14+
- `checked`
15+
- `type`
16+
- `value`
17+
18+
The builtin `Textarea` component has several arguments that match properties
19+
of the lower-case `textarea` HTML element. These arguments should be set via e.g.
20+
`@value`, instead of `value`, but it is easy to forget and can cause subtle
21+
issues.
22+
23+
This rule warns about `Textarea` component invocations that use the following attributes instead of arguments:
24+
25+
- `value`
26+
27+
Please note that this rule currently only warns about these three attributes on
28+
the `Input`, and one property on the `Textarea` components, but might be extended in the future to also warn about
29+
other attributes or builtin components.
30+
31+
## Examples
32+
33+
This rule **forbids** the following:
34+
35+
```hbs
36+
<Input type='text' size='10' />
37+
```
38+
39+
```hbs
40+
<Input @type='checkbox' checked />
41+
```
42+
43+
```hbs
44+
<Textarea value="Hello, Tom!" /></Textarea>
45+
```
46+
47+
This rule **allows** the following:
48+
49+
```hbs
50+
<input type='text' size='10' />
51+
```
52+
53+
```hbs
54+
<Input @type='text' size='10' />
55+
```
56+
57+
```hbs
58+
<Input @type='checkbox' @checked={{true}} />
59+
```
60+
61+
```hbs
62+
<Textarea @value="Hello, Tom!" /></Textarea>
63+
```
64+
65+
## Migration
66+
67+
- Add the `@` character in front of the relevant attributes to convert them
68+
into component argument
69+
70+
## Related Rules
71+
72+
- [template-no-unknown-arguments-for-builtin-components](template-no-unknown-arguments-for-builtin-components.md)
73+
74+
## References
75+
76+
- [`Input` component API documentation](https://api.emberjs.com/ember/release/classes/Ember.Templates.components/methods/Input?anchor=Input)
77+
- [`Textarea` component API documentation](https://api.emberjs.com/ember/release/classes/Ember.Templates.components/methods/Textarea?anchor=Textarea)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
const FORBIDDEN_ATTRIBUTES = {
2+
Input: new Set(['checked', 'type', 'value']),
3+
Textarea: new Set(['value']),
4+
};
5+
6+
function generateErrorMessage(component, attribute) {
7+
return `Setting the \`${attribute}\` attribute on the builtin <${component}> component is not allowed. Did you mean \`@${attribute}\`?`;
8+
}
9+
10+
/** @type {import('eslint').Rule.RuleModule} */
11+
module.exports = {
12+
meta: {
13+
type: 'suggestion',
14+
docs: {
15+
description: 'disallow setting certain attributes on builtin components',
16+
category: 'Best Practices',
17+
strictGjs: true,
18+
strictGts: true,
19+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-builtin-component-arguments.md',
20+
},
21+
fixable: null,
22+
schema: [],
23+
messages: {},
24+
},
25+
26+
create(context) {
27+
return {
28+
GlimmerElementNode(node) {
29+
const { tag, attributes } = node;
30+
const forbiddenAttributes = FORBIDDEN_ATTRIBUTES[tag];
31+
32+
if (forbiddenAttributes && attributes) {
33+
for (const attribute of attributes) {
34+
if (attribute.name && forbiddenAttributes.has(attribute.name)) {
35+
context.report({
36+
node: attribute,
37+
message: generateErrorMessage(tag, attribute.name),
38+
});
39+
}
40+
}
41+
}
42+
},
43+
};
44+
},
45+
};
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
const rule = require('../../../lib/rules/template-builtin-component-arguments');
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-builtin-component-arguments', rule, {
10+
valid: [
11+
'<template><input type="text" size="10" /></template>',
12+
'<template><Input @type="text" size="10" /></template>',
13+
'<template><Input @type="checkbox" @checked={{true}} /></template>',
14+
'<template><Textarea @value="Tomster" /></template>',
15+
],
16+
invalid: [
17+
{
18+
code: '<template><Input type="text" size="10" /></template>',
19+
output: null,
20+
errors: [
21+
{
22+
message:
23+
'Setting the `type` attribute on the builtin <Input> component is not allowed. Did you mean `@type`?',
24+
},
25+
],
26+
},
27+
{
28+
code: '<template><Input @type="checkbox" checked /></template>',
29+
output: null,
30+
errors: [
31+
{
32+
message:
33+
'Setting the `checked` attribute on the builtin <Input> component is not allowed. Did you mean `@checked`?',
34+
},
35+
],
36+
},
37+
{
38+
code: '<template><Textarea value="Tomster" /></template>',
39+
output: null,
40+
errors: [
41+
{
42+
message:
43+
'Setting the `value` attribute on the builtin <Textarea> component is not allowed. Did you mean `@value`?',
44+
},
45+
],
46+
},
47+
],
48+
});

0 commit comments

Comments
 (0)