Skip to content

Commit 948f1ab

Browse files
Merge pull request #2282 from wagenet/no-built-in-form-components
Add no-builtin-form-components rule
2 parents 19947f4 + 3bee52f commit 948f1ab

File tree

4 files changed

+228
-0
lines changed

4 files changed

+228
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ rules in templates can be disabled with eslint directives with mustache or html
180180
| :------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | :- | :- | :- |
181181
| [no-attrs-in-components](docs/rules/no-attrs-in-components.md) | disallow usage of `this.attrs` in components || | |
182182
| [no-attrs-snapshot](docs/rules/no-attrs-snapshot.md) | disallow use of attrs snapshot in the `didReceiveAttrs` and `didUpdateAttrs` component hooks || | |
183+
| [no-builtin-form-components](docs/rules/no-builtin-form-components.md) | disallow usage of built-in form components | | | |
183184
| [no-classic-components](docs/rules/no-classic-components.md) | enforce using Glimmer components || | |
184185
| [no-component-lifecycle-hooks](docs/rules/no-component-lifecycle-hooks.md) | disallow usage of "classic" ember component lifecycle hooks. Render modifiers or custom functional modifiers should be used instead. || | |
185186
| [no-on-calls-in-components](docs/rules/no-on-calls-in-components.md) | disallow usage of `on` to call lifecycle hooks in components || | |
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# ember/no-builtin-form-components
2+
3+
<!-- end auto-generated rule header -->
4+
5+
This rule disallows the use of Ember's built-in form components (`Input` and `Textarea`) from `@ember/component` and encourages using native HTML elements instead.
6+
7+
## Rule Details
8+
9+
Ember's built-in form components (`Input` and `Textarea`) were designed to bridge the gap between classic HTML form elements and Ember's component system. However, as Ember has evolved, using native HTML elements with modifiers has become the preferred approach for several reasons:
10+
11+
- Native HTML elements have better accessibility support
12+
- They provide a more consistent developer experience with standard web development
13+
- They have better performance characteristics
14+
- They avoid the extra abstraction layer that the built-in components provide
15+
16+
This rule helps identify where these built-in form components are being used so they can be replaced with native HTML elements.
17+
18+
## Examples
19+
20+
Examples of **incorrect** code for this rule:
21+
22+
```js
23+
import { Input } from '@ember/component';
24+
```
25+
26+
```js
27+
import { Textarea } from '@ember/component';
28+
```
29+
30+
```js
31+
import { Input as EmberInput, Textarea as EmberTextarea } from '@ember/component';
32+
```
33+
34+
Examples of **correct** code for this rule:
35+
36+
```hbs
37+
<!-- Instead of using the Input component -->
38+
<input
39+
value={{this.value}}
40+
{{on "input" this.updateValue}}
41+
/>
42+
43+
<!-- Instead of using the Textarea component -->
44+
<textarea
45+
value={{this.value}}
46+
{{on "input" this.updateValue}}
47+
/>
48+
```
49+
50+
## Migration
51+
52+
### Input Component
53+
54+
Replace:
55+
56+
```hbs
57+
<Input @value={{this.value}} @type="text" @placeholder="Enter text" {{on "input" this.handleInput}} />
58+
```
59+
60+
With:
61+
62+
```hbs
63+
<input
64+
value={{this.value}}
65+
type="text"
66+
placeholder="Enter text"
67+
{{on "input" this.handleInput}}
68+
/>
69+
```
70+
71+
### Textarea Component
72+
73+
Replace:
74+
75+
```hbs
76+
<Textarea @value={{this.value}} @placeholder="Enter text" {{on "input" this.handleInput}} />
77+
```
78+
79+
With:
80+
81+
```hbs
82+
<textarea
83+
value={{this.value}}
84+
placeholder="Enter text"
85+
{{on "input" this.handleInput}}
86+
/>
87+
```
88+
89+
## References
90+
91+
- [Ember Input Component API](https://api.emberjs.com/ember/release/classes/Input)
92+
- [Ember Textarea Component API](https://api.emberjs.com/ember/release/classes/Textarea)
93+
- [Ember Octane Modifier RFC](https://emberjs.github.io/rfcs/0373-element-modifiers.html)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use strict';
2+
3+
const { getImportIdentifier } = require('../utils/import');
4+
5+
const ERROR_MESSAGE = 'Do not use built-in form components. Use native HTML elements instead.';
6+
const DISALLOWED_IMPORTS = new Set(['Input', 'Textarea']);
7+
8+
/** @type {import('eslint').Rule.RuleModule} */
9+
module.exports = {
10+
meta: {
11+
type: 'suggestion',
12+
docs: {
13+
description: 'disallow usage of built-in form components',
14+
category: 'Components',
15+
recommended: false,
16+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-builtin-form-components.md',
17+
},
18+
fixable: null,
19+
schema: [],
20+
},
21+
22+
ERROR_MESSAGE,
23+
24+
create(context) {
25+
const report = function (node) {
26+
context.report({ node, message: ERROR_MESSAGE });
27+
};
28+
29+
return {
30+
ImportDeclaration(node) {
31+
if (node.source.value === '@ember/component') {
32+
// Check for named imports like: import { Input } from '@ember/component';
33+
const namedImports = node.specifiers.filter(
34+
(specifier) =>
35+
specifier.type === 'ImportSpecifier' &&
36+
DISALLOWED_IMPORTS.has(specifier.imported.name)
37+
);
38+
39+
if (namedImports.length > 0) {
40+
for (const specifier of namedImports) {
41+
report(specifier);
42+
}
43+
}
44+
}
45+
},
46+
};
47+
},
48+
};
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
'use strict';
2+
3+
//------------------------------------------------------------------------------
4+
// Requirements
5+
//------------------------------------------------------------------------------
6+
7+
const rule = require('../../../lib/rules/no-builtin-form-components');
8+
const RuleTester = require('eslint').RuleTester;
9+
10+
//------------------------------------------------------------------------------
11+
// Tests
12+
//------------------------------------------------------------------------------
13+
14+
const { ERROR_MESSAGE } = rule;
15+
const parserOptions = {
16+
ecmaVersion: 2022,
17+
sourceType: 'module',
18+
};
19+
const ruleTester = new RuleTester({ parserOptions });
20+
21+
ruleTester.run('no-builtin-form-components', rule, {
22+
valid: [
23+
"import Component from '@ember/component';",
24+
"import { setComponentTemplate } from '@ember/component';",
25+
"import { helper } from '@ember/component/helper';",
26+
"import { Input } from 'custom-component';",
27+
"import { Textarea } from 'custom-component';",
28+
],
29+
30+
invalid: [
31+
{
32+
code: "import { Input } from '@ember/component';",
33+
output: null,
34+
errors: [
35+
{
36+
message: ERROR_MESSAGE,
37+
type: 'ImportSpecifier',
38+
},
39+
],
40+
},
41+
{
42+
code: "import { Textarea } from '@ember/component';",
43+
output: null,
44+
errors: [
45+
{
46+
message: ERROR_MESSAGE,
47+
type: 'ImportSpecifier',
48+
},
49+
],
50+
},
51+
{
52+
code: "import { Input, Textarea } from '@ember/component';",
53+
output: null,
54+
errors: [
55+
{
56+
message: ERROR_MESSAGE,
57+
type: 'ImportSpecifier',
58+
},
59+
{
60+
message: ERROR_MESSAGE,
61+
type: 'ImportSpecifier',
62+
},
63+
],
64+
},
65+
{
66+
code: "import { Input as EmberInput } from '@ember/component';",
67+
output: null,
68+
errors: [
69+
{
70+
message: ERROR_MESSAGE,
71+
type: 'ImportSpecifier',
72+
},
73+
],
74+
},
75+
{
76+
code: "import { Textarea as EmberTextarea } from '@ember/component';",
77+
output: null,
78+
errors: [
79+
{
80+
message: ERROR_MESSAGE,
81+
type: 'ImportSpecifier',
82+
},
83+
],
84+
},
85+
],
86+
});

0 commit comments

Comments
 (0)