Skip to content

Commit 7914121

Browse files
committed
feat: add rule no-invalid-html-elements
1 parent ca37fbb commit 7914121

File tree

11 files changed

+176
-0
lines changed

11 files changed

+176
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
387387
| [svelte/no-dupe-use-directives](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-dupe-use-directives/) | disallow duplicate `use:` directives | |
388388
| [svelte/no-dynamic-slot-name](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-dynamic-slot-name/) | disallow dynamic slot name | :star::wrench: |
389389
| [svelte/no-export-load-in-svelte-module-in-kit-pages](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-export-load-in-svelte-module-in-kit-pages/) | disallow exporting load functions in `*.svelte` module in SvelteKit page components. | |
390+
| [svelte/no-invalid-html-elements](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-invalid-html-elements/) | Disallows valid Svelte 4 tags, that no are no longer valid in Svelte 5 | |
390391
| [svelte/no-not-function-handler](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-not-function-handler/) | disallow use of not function in event handler | :star: |
391392
| [svelte/no-object-in-text-mustaches](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-object-in-text-mustaches/) | disallow objects in text mustache interpolation | :star: |
392393
| [svelte/no-reactive-reassign](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-reactive-reassign/) | disallow reassigning reactive values | |

docs/rules.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
2424
| [svelte/no-dupe-use-directives](./rules/no-dupe-use-directives.md) | disallow duplicate `use:` directives | |
2525
| [svelte/no-dynamic-slot-name](./rules/no-dynamic-slot-name.md) | disallow dynamic slot name | :star::wrench: |
2626
| [svelte/no-export-load-in-svelte-module-in-kit-pages](./rules/no-export-load-in-svelte-module-in-kit-pages.md) | disallow exporting load functions in `*.svelte` module in SvelteKit page components. | |
27+
| [svelte/no-invalid-html-elements](./rules/no-invalid-html-elements.md) | Disallows valid Svelte 4 tags, that no are no longer valid in Svelte 5 | |
2728
| [svelte/no-not-function-handler](./rules/no-not-function-handler.md) | disallow use of not function in event handler | :star: |
2829
| [svelte/no-object-in-text-mustaches](./rules/no-object-in-text-mustaches.md) | disallow objects in text mustache interpolation | :star: |
2930
| [svelte/no-reactive-reassign](./rules/no-reactive-reassign.md) | disallow reassigning reactive values | |
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
pageClass: 'rule-details'
3+
sidebarDepth: 0
4+
title: 'svelte/no-invalid-html-elements'
5+
description: 'Disallows valid Svelte 4 tags, that no are no longer valid in Svelte 5'
6+
---
7+
8+
# svelte/no-invalid-html-elements
9+
10+
> Disallows valid Svelte 4 tags, that no are no longer valid in Svelte 5
11+
12+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> **_This rule has not been released yet._** </badge>
13+
14+
## :book: Rule Details
15+
16+
This rule reports the invalid usage of `head`, `body`, `window`, `document`, `element` and `options` HTML elements, **in Svelte 5**. These elements were valid in Svelte 4, but since Svelte 5 they must be used with `svelte:`.
17+
18+
<ESLintCodeBlock>
19+
20+
<!--eslint-skip-->
21+
22+
```svelte
23+
<script>
24+
/* eslint svelte/no-invalid-html-elements: "error" */
25+
</script>
26+
27+
<!-- ✓ GOOD -->
28+
<svelte:head>
29+
<title>Invalid HTML Elements</title>
30+
</svelte:head>
31+
32+
<!-- ✗ BAD -->
33+
<head>
34+
<title>Invalid HTML Elements</title>
35+
</head>
36+
```
37+
38+
</ESLintCodeBlock>
39+
40+
## :wrench: Options
41+
42+
```json
43+
{
44+
"svelte/no-invalid-html-elements": ["error", {}]
45+
}
46+
```
47+
48+
-
49+
50+
## :books: Further Reading
51+
52+
- See special elements section in [Svelte docs](https://svelte.dev/docs/svelte/svelte-window)
53+
54+
## :mag: Implementation
55+
56+
- [Rule source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/packages/eslint-plugin-svelte/src/rules/no-invalid-html-elements.ts)
57+
- [Test source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/packages/eslint-plugin-svelte/tests/src/rules/no-invalid-html-elements.ts)

packages/eslint-plugin-svelte/src/rule-types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ export interface RuleOptions {
174174
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-inspect/
175175
*/
176176
'svelte/no-inspect'?: Linter.RuleEntry<[]>
177+
/**
178+
* Disallows valid Svelte 4 tags, that no are no longer valid in Svelte 5
179+
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-invalid-html-elements/
180+
*/
181+
'svelte/no-invalid-html-elements'?: Linter.RuleEntry<[]>
177182
/**
178183
* disallow use of not function in event handler
179184
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-not-function-handler/
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import type { AST } from 'svelte-eslint-parser';
2+
import { createRule } from '../utils';
3+
import { getSourceCode } from '../utils/compat';
4+
5+
const INVALID_HTML_ELEMENTS = ['head', 'body', 'window', 'document', 'element', 'options'];
6+
const VALID_PREFIX = 'svelte:';
7+
8+
export default createRule('no-invalid-html-elements', {
9+
meta: {
10+
docs: {
11+
description: 'Disallows valid Svelte 4 tags, that no are no longer valid in Svelte 5',
12+
category: 'Possible Errors',
13+
// TODO: Switch to recommended in the major version
14+
recommended: false
15+
},
16+
schema: [],
17+
messages: {
18+
invalidElement: 'Invalid {{name}} element, use svelte:{{name}} instead.'
19+
},
20+
type: 'problem', // 'problem', or 'layout',
21+
fixable: 'code'
22+
},
23+
create(context) {
24+
const sourceCode = getSourceCode(context);
25+
const ctx = sourceCode.parserServices.svelteParseContext;
26+
if (!(ctx?.compilerVersion ?? '').startsWith('5')) {
27+
// Only applies to Svelte 5
28+
return {};
29+
}
30+
31+
return {
32+
'SvelteElement[kind="html"]'(node: AST.SvelteHTMLElement) {
33+
const { name } = node.name;
34+
if (INVALID_HTML_ELEMENTS.includes(name)) {
35+
context.report({
36+
node,
37+
messageId: 'invalidElement',
38+
data: { name },
39+
*fix(fixer) {
40+
const { endTag } = node;
41+
yield fixer.insertTextBeforeRange([node.range[0] + 1, node.range[1]], VALID_PREFIX);
42+
if (endTag) {
43+
yield fixer.insertTextBeforeRange(
44+
[endTag.range[0] + 2, endTag.range[1]],
45+
VALID_PREFIX
46+
);
47+
}
48+
}
49+
});
50+
}
51+
}
52+
};
53+
}
54+
});

packages/eslint-plugin-svelte/src/utils/rules.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import noImmutableReactiveStatements from '../rules/no-immutable-reactive-statem
3434
import noInlineStyles from '../rules/no-inline-styles';
3535
import noInnerDeclarations from '../rules/no-inner-declarations';
3636
import noInspect from '../rules/no-inspect';
37+
import noInvalidHtmlElements from '../rules/no-invalid-html-elements';
3738
import noNotFunctionHandler from '../rules/no-not-function-handler';
3839
import noObjectInTextMustaches from '../rules/no-object-in-text-mustaches';
3940
import noReactiveFunctions from '../rules/no-reactive-functions';
@@ -101,6 +102,7 @@ export const rules = [
101102
noInlineStyles,
102103
noInnerDeclarations,
103104
noInspect,
105+
noInvalidHtmlElements,
104106
noNotFunctionHandler,
105107
noObjectInTextMustaches,
106108
noReactiveFunctions,
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
- message: Invalid head element, use svelte:head instead.
2+
line: 1
3+
column: 1
4+
suggestions: null
5+
- message: Invalid body element, use svelte:body instead.
6+
line: 2
7+
column: 1
8+
suggestions: null
9+
- message: Invalid window element, use svelte:window instead.
10+
line: 3
11+
column: 1
12+
suggestions: null
13+
- message: Invalid document element, use svelte:document instead.
14+
line: 4
15+
column: 1
16+
suggestions: null
17+
- message: Invalid element element, use svelte:element instead.
18+
line: 5
19+
column: 1
20+
suggestions: null
21+
- message: Invalid options element, use svelte:options instead.
22+
line: 6
23+
column: 1
24+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<head></head>
2+
<body></body>
3+
<window></window>
4+
<document></document>
5+
<element this={{}}></element>
6+
<options></options>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<svelte:head></svelte:head>
2+
<svelte:body></svelte:body>
3+
<svelte:window></svelte:window>
4+
<svelte:document></svelte:document>
5+
<svelte:element this={{}}></svelte:element>
6+
<svelte:options></svelte:options>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<svelte:options />
2+
3+
<svelte:body />
4+
<svelte:document />
5+
<svelte:element this={{}}></svelte:element>
6+
<svelte:head></svelte:head>
7+
8+
<svelte:window />
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { RuleTester } from '../../utils/eslint-compat';
2+
import rule from '../../../src/rules/no-invalid-html-elements';
3+
import { loadTestCases } from '../../utils/utils';
4+
5+
const tester = new RuleTester({
6+
languageOptions: {
7+
ecmaVersion: 2020,
8+
sourceType: 'module'
9+
}
10+
});
11+
12+
tester.run('no-invalid-html-elements', rule as any, loadTestCases('no-invalid-html-elements'));

0 commit comments

Comments
 (0)