Skip to content

Commit cad8b9f

Browse files
committed
Add no-builtin-form-components rule
1 parent 19947f4 commit cad8b9f

File tree

5 files changed

+231
-0
lines changed

5 files changed

+231
-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: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# ember/no-builtin-form-components
2+
3+
💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).
4+
5+
<!-- end auto-generated rule header -->
6+
7+
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.
8+
9+
## Rule Details
10+
11+
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:
12+
13+
- Native HTML elements have better accessibility support
14+
- They provide a more consistent developer experience with standard web development
15+
- They have better performance characteristics
16+
- They avoid the extra abstraction layer that the built-in components provide
17+
18+
This rule helps identify where these built-in form components are being used so they can be replaced with native HTML elements.
19+
20+
## Examples
21+
22+
Examples of **incorrect** code for this rule:
23+
24+
```js
25+
import { Input } from '@ember/component';
26+
```
27+
28+
```js
29+
import { Textarea } from '@ember/component';
30+
```
31+
32+
```js
33+
import { Input as EmberInput, Textarea as EmberTextarea } from '@ember/component';
34+
```
35+
36+
Examples of **correct** code for this rule:
37+
38+
```hbs
39+
<!-- Instead of using the Input component -->
40+
<input
41+
value={{this.value}}
42+
{{on "input" this.updateValue}}
43+
/>
44+
45+
<!-- Instead of using the Textarea component -->
46+
<textarea
47+
value={{this.value}}
48+
{{on "input" this.updateValue}}
49+
/>
50+
```
51+
52+
## Migration
53+
54+
### Input Component
55+
56+
Replace:
57+
58+
```hbs
59+
<Input @value={{this.value}} @type="text" @placeholder="Enter text" {{on "input" this.handleInput}} />
60+
```
61+
62+
With:
63+
64+
```hbs
65+
<input
66+
value={{this.value}}
67+
type="text"
68+
placeholder="Enter text"
69+
{{on "input" this.handleInput}}
70+
/>
71+
```
72+
73+
### Textarea Component
74+
75+
Replace:
76+
77+
```hbs
78+
<Textarea @value={{this.value}} @placeholder="Enter text" {{on "input" this.handleInput}} />
79+
```
80+
81+
With:
82+
83+
```hbs
84+
<textarea
85+
value={{this.value}}
86+
placeholder="Enter text"
87+
{{on "input" this.handleInput}}
88+
/>
89+
```
90+
91+
## References
92+
93+
- [Ember Input Component API](https://api.emberjs.com/ember/release/classes/Input)
94+
- [Ember Textarea Component API](https://api.emberjs.com/ember/release/classes/Textarea)
95+
- [Ember Octane Modifier RFC](https://emberjs.github.io/rfcs/0373-element-modifiers.html)

lib/recommended-rules.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ module.exports = {
1818
"ember/no-at-ember-render-modifiers": "error",
1919
"ember/no-attrs-in-components": "error",
2020
"ember/no-attrs-snapshot": "error",
21+
"ember/no-builtin-form-components": "error",
2122
"ember/no-capital-letters-in-routes": "error",
2223
"ember/no-classic-classes": "error",
2324
"ember/no-classic-components": "error",
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)