Skip to content

Commit af0a016

Browse files
committed
Extract rule: template-self-closing-void-elements
1 parent c98dafb commit af0a016

4 files changed

Lines changed: 489 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ rules in templates can be disabled with eslint directives with mustache or html
245245
| [template-no-obsolete-elements](docs/rules/template-no-obsolete-elements.md) | disallow obsolete HTML elements | | | |
246246
| [template-no-outlet-outside-routes](docs/rules/template-no-outlet-outside-routes.md) | disallow {{outlet}} outside of route templates | | | |
247247
| [template-no-page-title-component](docs/rules/template-no-page-title-component.md) | disallow usage of ember-page-title component | | | |
248+
| [template-self-closing-void-elements](docs/rules/template-self-closing-void-elements.md) | require self-closing on void elements | | 🔧 | |
248249
| [template-simple-modifiers](docs/rules/template-simple-modifiers.md) | require simple modifier syntax | | | |
249250
| [template-simple-unless](docs/rules/template-simple-unless.md) | require simple conditions in unless blocks | | | |
250251
| [template-splat-attributes-only](docs/rules/template-splat-attributes-only.md) | disallow ...spread other than ...attributes | | | |
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# ember/template-self-closing-void-elements
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+
Disallow or require self-closing void elements.
8+
9+
## Rule Details
10+
11+
Void elements (like `<img>`, `<br>`, `<input>`, etc.) cannot have child content. By default, this rule disallows redundant self-closing syntax (`/>`) on void elements since it's unnecessary in HTML.
12+
13+
## Config
14+
15+
This rule accepts a single option:
16+
17+
- `true` (default) — disallow self-closing void elements (e.g., `<br />``<br>`)
18+
- `"require"` — require self-closing void elements (e.g., `<br>``<br />`)
19+
20+
## Examples
21+
22+
Examples of **incorrect** code for this rule (with default config):
23+
24+
```gjs
25+
<template>
26+
<img src="foo.jpg" />
27+
</template>
28+
```
29+
30+
```gjs
31+
<template>
32+
<br />
33+
</template>
34+
```
35+
36+
```gjs
37+
<template>
38+
<input type="text" />
39+
</template>
40+
```
41+
42+
Examples of **correct** code for this rule (with default config):
43+
44+
```gjs
45+
<template>
46+
<img src="foo.jpg">
47+
</template>
48+
```
49+
50+
```gjs
51+
<template>
52+
<br>
53+
</template>
54+
```
55+
56+
```gjs
57+
<template>
58+
<input type="text">
59+
</template>
60+
```
61+
62+
## References
63+
64+
- [eslint-plugin-ember template-self-closing-void-elements](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-self-closing-void-elements.md)
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/** @type {import('eslint').Rule.RuleModule} */
2+
module.exports = {
3+
meta: {
4+
type: 'suggestion',
5+
docs: {
6+
description: 'require self-closing on void elements',
7+
category: 'Best Practices',
8+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-self-closing-void-elements.md',
9+
templateMode: 'both',
10+
},
11+
fixable: 'code',
12+
schema: [
13+
{
14+
oneOf: [{ type: 'boolean' }, { type: 'string', enum: ['require'] }],
15+
},
16+
],
17+
messages: {
18+
redundantSelfClosing: 'Self-closing a void element is redundant.',
19+
requireSelfClosing: 'Self-closing a void element is required.',
20+
},
21+
originallyFrom: {
22+
name: 'ember-template-lint',
23+
rule: 'lib/rules/self-closing-void-elements.js',
24+
docs: 'docs/rule/self-closing-void-elements.md',
25+
tests: 'test/unit/rules/self-closing-void-elements-test.js',
26+
},
27+
},
28+
29+
create(context) {
30+
const VOID_ELEMENTS = new Set([
31+
'area',
32+
'base',
33+
'br',
34+
'col',
35+
'command',
36+
'embed',
37+
'hr',
38+
'img',
39+
'input',
40+
'keygen',
41+
'link',
42+
'meta',
43+
'param',
44+
'source',
45+
'track',
46+
'wbr',
47+
]);
48+
49+
const config = context.options[0];
50+
const requireSelfClosing = config === 'require';
51+
52+
return {
53+
GlimmerElementNode(node) {
54+
if (!VOID_ELEMENTS.has(node.tag)) {
55+
return;
56+
}
57+
58+
if (requireSelfClosing) {
59+
if (!node.selfClosing) {
60+
context.report({
61+
node,
62+
messageId: 'requireSelfClosing',
63+
});
64+
}
65+
} else {
66+
if (node.selfClosing) {
67+
context.report({
68+
node,
69+
messageId: 'redundantSelfClosing',
70+
});
71+
}
72+
}
73+
},
74+
};
75+
},
76+
};

0 commit comments

Comments
 (0)