-
-
Notifications
You must be signed in to change notification settings - Fork 207
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(eslint-plugin-template): [self-closing-tags] add rule (#1322)
- Loading branch information
Showing
6 changed files
with
420 additions
and
0 deletions.
There are no files selected for viewing
263 changes: 263 additions & 0 deletions
263
packages/eslint-plugin-template/docs/rules/prefer-self-closing-tags.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,263 @@ | ||
<!-- | ||
DO NOT EDIT. | ||
This markdown file was autogenerated using a mixture of the following files as the source of truth for its data: | ||
- ../../src/rules/prefer-self-closing-tags.ts | ||
- ../../tests/rules/prefer-self-closing-tags/cases.ts | ||
In order to update this file, it is therefore those files which need to be updated, as well as potentially the generator script: | ||
- ../../../../tools/scripts/generate-rule-docs.ts | ||
--> | ||
|
||
<br> | ||
|
||
# `@angular-eslint/template/prefer-self-closing-tags` | ||
|
||
Ensures that self-closing tags are used for elements with a closing tag but no content. | ||
|
||
- Type: layout | ||
- 🔧 Supports autofix (`--fix`) | ||
|
||
<br> | ||
|
||
## Rule Options | ||
|
||
The rule does not have any configuration options. | ||
|
||
<br> | ||
|
||
## Usage Examples | ||
|
||
> The following examples are generated automatically from the actual unit tests within the plugin, so you can be assured that their behavior is accurate based on the current commit. | ||
<br> | ||
|
||
<details> | ||
<summary>❌ - Toggle examples of <strong>incorrect</strong> code for this rule</summary> | ||
|
||
<br> | ||
|
||
#### Default Config | ||
|
||
```json | ||
{ | ||
"rules": { | ||
"@angular-eslint/template/prefer-self-closing-tags": [ | ||
"error" | ||
] | ||
} | ||
} | ||
``` | ||
|
||
<br> | ||
|
||
#### ❌ Invalid Code | ||
|
||
```html | ||
<my-component></my-component> | ||
~~~~~~~~~~~~~~~ | ||
``` | ||
|
||
<br> | ||
|
||
--- | ||
|
||
<br> | ||
|
||
#### Default Config | ||
|
||
```json | ||
{ | ||
"rules": { | ||
"@angular-eslint/template/prefer-self-closing-tags": [ | ||
"error" | ||
] | ||
} | ||
} | ||
``` | ||
|
||
<br> | ||
|
||
#### ❌ Invalid Code | ||
|
||
```html | ||
<my-component type="text" [name]="foo"></my-component> | ||
~~~~~~~~~~~~~~~ | ||
``` | ||
|
||
<br> | ||
|
||
--- | ||
|
||
<br> | ||
|
||
#### Default Config | ||
|
||
```json | ||
{ | ||
"rules": { | ||
"@angular-eslint/template/prefer-self-closing-tags": [ | ||
"error" | ||
] | ||
} | ||
} | ||
``` | ||
|
||
<br> | ||
|
||
#### ❌ Invalid Code | ||
|
||
```html | ||
<my-component | ||
type="text" | ||
[name]="foo" | ||
[items]="items"> | ||
</my-component> | ||
~~~~~~~~~~~~~~~ | ||
``` | ||
|
||
</details> | ||
|
||
<br> | ||
|
||
--- | ||
|
||
<br> | ||
|
||
<details> | ||
<summary>✅ - Toggle examples of <strong>correct</strong> code for this rule</summary> | ||
|
||
<br> | ||
|
||
#### Default Config | ||
|
||
```json | ||
{ | ||
"rules": { | ||
"@angular-eslint/template/prefer-self-closing-tags": [ | ||
"error" | ||
] | ||
} | ||
} | ||
``` | ||
|
||
<br> | ||
|
||
#### ✅ Valid Code | ||
|
||
```html | ||
<my-component type="text" [name]="foo">With some content</my-component> | ||
``` | ||
|
||
<br> | ||
|
||
--- | ||
|
||
<br> | ||
|
||
#### Default Config | ||
|
||
```json | ||
{ | ||
"rules": { | ||
"@angular-eslint/template/prefer-self-closing-tags": [ | ||
"error" | ||
] | ||
} | ||
} | ||
``` | ||
|
||
<br> | ||
|
||
#### ✅ Valid Code | ||
|
||
```html | ||
<my-component /> | ||
``` | ||
|
||
<br> | ||
|
||
--- | ||
|
||
<br> | ||
|
||
#### Default Config | ||
|
||
```json | ||
{ | ||
"rules": { | ||
"@angular-eslint/template/prefer-self-closing-tags": [ | ||
"error" | ||
] | ||
} | ||
} | ||
``` | ||
|
||
<br> | ||
|
||
#### ✅ Valid Code | ||
|
||
```html | ||
<my-component | ||
type="text" | ||
[name]="foo" | ||
[items]="items" /> | ||
``` | ||
|
||
<br> | ||
|
||
--- | ||
|
||
<br> | ||
|
||
#### Default Config | ||
|
||
```json | ||
{ | ||
"rules": { | ||
"@angular-eslint/template/prefer-self-closing-tags": [ | ||
"error" | ||
] | ||
} | ||
} | ||
``` | ||
|
||
<br> | ||
|
||
#### ✅ Valid Code | ||
|
||
```html | ||
<img /> | ||
``` | ||
|
||
<br> | ||
|
||
--- | ||
|
||
<br> | ||
|
||
#### Default Config | ||
|
||
```json | ||
{ | ||
"rules": { | ||
"@angular-eslint/template/prefer-self-closing-tags": [ | ||
"error" | ||
] | ||
} | ||
} | ||
``` | ||
|
||
<br> | ||
|
||
#### ✅ Valid Code | ||
|
||
```html | ||
<div></div> | ||
``` | ||
|
||
</details> | ||
|
||
<br> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
76 changes: 76 additions & 0 deletions
76
packages/eslint-plugin-template/src/rules/prefer-self-closing-tags.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import type { | ||
TmplAstElement, | ||
TmplAstText, | ||
} from '@angular-eslint/bundled-angular-compiler'; | ||
import { getTemplateParserServices } from '@angular-eslint/utils'; | ||
import { createESLintRule } from '../utils/create-eslint-rule'; | ||
import { getDomElements } from '../utils/get-dom-elements'; | ||
|
||
export const MESSAGE_ID = 'preferSelfClosingTags'; | ||
export const RULE_NAME = 'prefer-self-closing-tags'; | ||
|
||
export default createESLintRule<[], typeof MESSAGE_ID>({ | ||
name: RULE_NAME, | ||
meta: { | ||
type: 'layout', | ||
docs: { | ||
description: | ||
'Ensures that self-closing tags are used for elements with a closing tag but no content.', | ||
recommended: false, | ||
}, | ||
fixable: 'code', | ||
schema: [], | ||
messages: { | ||
[MESSAGE_ID]: | ||
'Use self-closing tags for elements with a closing tag but no content.', | ||
}, | ||
}, | ||
defaultOptions: [], | ||
create(context) { | ||
const parserServices = getTemplateParserServices(context); | ||
|
||
return { | ||
Element$1({ | ||
children, | ||
name, | ||
startSourceSpan, | ||
endSourceSpan, | ||
}: TmplAstElement) { | ||
// Ignore native elements. | ||
if (getDomElements().has(name)) { | ||
return; | ||
} | ||
|
||
const noContent = | ||
!children.length || | ||
children.every((node) => { | ||
const text = (node as TmplAstText).value; | ||
|
||
// If the node has no value, or only whitespace, | ||
// we can consider it empty. | ||
return ( | ||
typeof text === 'string' && text.replace(/\n/g, '').trim() === '' | ||
); | ||
}); | ||
const noCloseTag = | ||
!endSourceSpan || | ||
(startSourceSpan.start.offset === endSourceSpan.start.offset && | ||
startSourceSpan.end.offset === endSourceSpan.end.offset); | ||
|
||
if (!noContent || noCloseTag) { | ||
return; | ||
} | ||
|
||
context.report({ | ||
loc: parserServices.convertNodeSourceSpanToLoc(endSourceSpan), | ||
messageId: MESSAGE_ID, | ||
fix: (fixer) => | ||
fixer.replaceTextRange( | ||
[startSourceSpan.end.offset - 1, endSourceSpan.end.offset], | ||
' />', | ||
), | ||
}); | ||
}, | ||
}; | ||
}, | ||
}); |
Oops, something went wrong.