-
-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add
svelte/no-dom-manipulating
rule
- Loading branch information
Showing
30 changed files
with
615 additions
and
11 deletions.
There are no files selected for viewing
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 |
---|---|---|
|
@@ -14,3 +14,4 @@ LICENSE | |
*.md | ||
/docs-svelte-kit/ | ||
/coverage | ||
/build |
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
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,54 @@ | ||
--- | ||
pageClass: "rule-details" | ||
sidebarDepth: 0 | ||
title: "svelte/no-dom-manipulating" | ||
description: "disallow DOM manipulating" | ||
--- | ||
|
||
# svelte/no-dom-manipulating | ||
|
||
> disallow DOM manipulating | ||
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> **_This rule has not been released yet._** </badge> | ||
|
||
## :book: Rule Details | ||
|
||
In general, DOM manipulating should delegate to Svelte runtime. If you manipulate the DOM directly, the Svelte runtime may confuse because there is a difference between the actual DOM and the Svelte runtime's expected DOM. | ||
Therefore this rule reports where you use DOM manipulating function. | ||
We don't recommend but If you intentionally manipulate the DOM, simply you can ignore this ESLint report. | ||
|
||
<ESLintCodeBlock> | ||
|
||
<!--eslint-skip--> | ||
|
||
```svelte | ||
<script> | ||
/* eslint svelte/no-dom-manipulating: "error" */ | ||
let div | ||
let show | ||
/* ✓ GOOD */ | ||
const toggle = () => (show = !show) | ||
/* ✗ BAD */ | ||
const remove = () => div.remove() | ||
</script> | ||
{#if show} | ||
<div bind:this={div}>div</div> | ||
{/if} | ||
<button on:click={() => toggle()}>Click Me (Good)</button> | ||
<button on:click={() => remove()}>Click Me (Bad)</button> | ||
``` | ||
|
||
</ESLintCodeBlock> | ||
|
||
## :wrench: Options | ||
|
||
Nothing. | ||
|
||
## :mag: Implementation | ||
|
||
- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/no-dom-manipulating.ts) | ||
- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/no-dom-manipulating.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,131 @@ | ||
import type { AST } from "svelte-eslint-parser" | ||
import type { TSESTree } from "@typescript-eslint/types" | ||
import { createRule } from "../utils" | ||
import { findVariable, getNodeName } from "../utils/ast-utils" | ||
import type { Variable } from "@typescript-eslint/scope-manager" | ||
import { getPropertyName } from "eslint-utils" | ||
|
||
const DOM_MANIPULATING_METHODS = new Set([ | ||
"appendChild", // https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild | ||
"insertBefore", // https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore | ||
"normalize", // https://developer.mozilla.org/en-US/docs/Web/API/Node/normalize | ||
"removeChild", // https://developer.mozilla.org/en-US/docs/Web/API/Node/removeChild | ||
"replaceChild", // https://developer.mozilla.org/en-US/docs/Web/API/Node/replaceChild | ||
"after", // https://developer.mozilla.org/en-US/docs/Web/API/Element/after | ||
"append", // https://developer.mozilla.org/en-US/docs/Web/API/Element/append | ||
"before", // https://developer.mozilla.org/en-US/docs/Web/API/Element/before | ||
"insertAdjacentElement", // https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentElement | ||
"insertAdjacentHTML", // https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML | ||
"insertAdjacentText", // https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentText | ||
"prepend", // https://developer.mozilla.org/en-US/docs/Web/API/Element/prepend | ||
"remove", // https://developer.mozilla.org/en-US/docs/Web/API/Element/remove | ||
"replaceChildren", // https://developer.mozilla.org/en-US/docs/Web/API/Element/replaceChildren | ||
"replaceWith", // https://developer.mozilla.org/en-US/docs/Web/API/Element/replaceWith | ||
]) | ||
const DOM_MANIPULATING_PROPERTIES = new Set([ | ||
"textContent", // https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent | ||
"innerHTML", // https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML | ||
"outerHTML", // https://developer.mozilla.org/en-US/docs/Web/API/Element/outerHTML | ||
"innerText", // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText | ||
"outerText", // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/outerText | ||
]) | ||
|
||
export default createRule("no-dom-manipulating", { | ||
meta: { | ||
docs: { | ||
description: "disallow DOM manipulating", | ||
category: "Possible Errors", | ||
recommended: false, | ||
}, | ||
schema: [], | ||
messages: { | ||
disallowManipulateDOM: | ||
"Don't manipulate the DOM directly. The Svelte runtime can get confused if there is a difference between the actual DOM and the DOM expected by the Svelte runtime.", | ||
}, | ||
type: "problem", | ||
}, | ||
create(context) { | ||
const domVariables = new Set<Variable>() | ||
|
||
/** | ||
* Verify DOM variable identifier node | ||
*/ | ||
function verifyIdentifier( | ||
node: TSESTree.Identifier | TSESTree.JSXIdentifier, | ||
) { | ||
const member = node.parent | ||
if (member?.type !== "MemberExpression" || member.object !== node) { | ||
return | ||
} | ||
const name = getPropertyName(member) | ||
if (!name) { | ||
return | ||
} | ||
let target: TSESTree.Expression = member | ||
let parent = target.parent | ||
while (parent?.type === "ChainExpression") { | ||
target = parent | ||
parent = parent.parent | ||
} | ||
if (!parent) { | ||
return | ||
} | ||
if (parent.type === "CallExpression") { | ||
if (parent.callee !== target || !DOM_MANIPULATING_METHODS.has(name)) { | ||
return | ||
} | ||
} else if (parent.type === "AssignmentExpression") { | ||
if (parent.left !== target || !DOM_MANIPULATING_PROPERTIES.has(name)) { | ||
return | ||
} | ||
} | ||
context.report({ | ||
node: member, | ||
messageId: "disallowManipulateDOM", | ||
}) | ||
} | ||
|
||
return { | ||
"SvelteDirective[kind='Binding']"(node: AST.SvelteBindingDirective) { | ||
if ( | ||
node.key.name.name !== "this" || | ||
!node.expression || | ||
node.expression.type !== "Identifier" | ||
) { | ||
// not bind:this={id} | ||
return | ||
} | ||
const element = node.parent.parent | ||
if (element.type !== "SvelteElement" || !isHTMLElement(element)) { | ||
// not HTML element | ||
return | ||
} | ||
const variable = findVariable(context, node.expression) | ||
if ( | ||
!variable || | ||
(variable.scope.type !== "module" && variable.scope.type !== "global") | ||
) { | ||
return | ||
} | ||
domVariables.add(variable) | ||
}, | ||
"Program:exit"() { | ||
for (const variable of domVariables) { | ||
for (const reference of variable.references) { | ||
verifyIdentifier(reference.identifier) | ||
} | ||
} | ||
}, | ||
} | ||
|
||
/** | ||
* Checks whether the given node is a HTML element or not. | ||
*/ | ||
function isHTMLElement(node: AST.SvelteElement) { | ||
return ( | ||
node.kind === "html" || | ||
(node.kind === "special" && getNodeName(node) === "svelte:element") | ||
) | ||
} | ||
}, | ||
}) |
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
14 changes: 14 additions & 0 deletions
14
tests/fixtures/rules/no-dom-manipulating/invalid/chain01-errors.yaml
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,14 @@ | ||
- message: | ||
Don't manipulate the DOM directly. The Svelte runtime can get confused | ||
if there is a difference between the actual DOM and the DOM expected by the | ||
Svelte runtime. | ||
line: 5 | ||
column: 5 | ||
suggestions: null | ||
- message: | ||
Don't manipulate the DOM directly. The Svelte runtime can get confused | ||
if there is a difference between the actual DOM and the DOM expected by the | ||
Svelte runtime. | ||
line: 9 | ||
column: 7 | ||
suggestions: null |
16 changes: 16 additions & 0 deletions
16
tests/fixtures/rules/no-dom-manipulating/invalid/chain01-input.svelte
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,16 @@ | ||
<script> | ||
let foo | ||
const remove1 = () => { | ||
foo?.remove() | ||
} | ||
const remove2 = () => { | ||
// eslint-disable-next-line no-unsafe-optional-chaining -- ignore | ||
;(foo?.remove)() | ||
} | ||
</script> | ||
<p bind:this={foo}>div</p> | ||
<button on:click={() => remove1()}>Click Me</button> | ||
<button on:click={() => remove2()}>Click Me</button> |
7 changes: 7 additions & 0 deletions
7
tests/fixtures/rules/no-dom-manipulating/invalid/remove-text01-errors.yaml
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,7 @@ | ||
- message: | ||
Don't manipulate the DOM directly. The Svelte runtime can get confused | ||
if there is a difference between the actual DOM and the DOM expected by the | ||
Svelte runtime. | ||
line: 9 | ||
column: 25 | ||
suggestions: null |
19 changes: 19 additions & 0 deletions
19
tests/fixtures/rules/no-dom-manipulating/invalid/remove-text01-input.svelte
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,19 @@ | ||
<script> | ||
let div | ||
let show | ||
// ✓ GOOD | ||
const toggle = () => (show = !show) | ||
// ✗ BAD | ||
const update = () => (div.textContent = "foo") | ||
</script> | ||
|
||
<div bind:this={div}> | ||
{#if show} | ||
div | ||
{/if} | ||
</div> | ||
|
||
<button on:click={() => toggle()}>Click Me (Good)</button> | ||
<button on:click={() => update()}>Click Me (Bad)</button> |
7 changes: 7 additions & 0 deletions
7
tests/fixtures/rules/no-dom-manipulating/invalid/remove01-errors.yaml
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,7 @@ | ||
- message: | ||
Don't manipulate the DOM directly. The Svelte runtime can get confused | ||
if there is a difference between the actual DOM and the DOM expected by the | ||
Svelte runtime. | ||
line: 9 | ||
column: 24 | ||
suggestions: null |
17 changes: 17 additions & 0 deletions
17
tests/fixtures/rules/no-dom-manipulating/invalid/remove01-input.svelte
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,17 @@ | ||
<script> | ||
let div | ||
let show | ||
// ✓ GOOD | ||
const toggle = () => (show = !show) | ||
// ✗ BAD | ||
const remove = () => div.remove() | ||
</script> | ||
|
||
{#if show} | ||
<div bind:this={div}>div</div> | ||
{/if} | ||
|
||
<button on:click={() => toggle()}>Click Me (Good)</button> | ||
<button on:click={() => remove()}>Click Me (Bad)</button> |
14 changes: 14 additions & 0 deletions
14
tests/fixtures/rules/no-dom-manipulating/invalid/svelte-element01-errors.yaml
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,14 @@ | ||
- message: | ||
Don't manipulate the DOM directly. The Svelte runtime can get confused | ||
if there is a difference between the actual DOM and the DOM expected by the | ||
Svelte runtime. | ||
line: 7 | ||
column: 5 | ||
suggestions: null | ||
- message: | ||
Don't manipulate the DOM directly. The Svelte runtime can get confused | ||
if there is a difference between the actual DOM and the DOM expected by the | ||
Svelte runtime. | ||
line: 8 | ||
column: 5 | ||
suggestions: null |
15 changes: 15 additions & 0 deletions
15
tests/fixtures/rules/no-dom-manipulating/invalid/svelte-element01-input.svelte
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,15 @@ | ||
<script> | ||
export let tag = "div" | ||
let foo | ||
let bar | ||
const remove = () => { | ||
foo.remove() | ||
bar.remove() | ||
} | ||
</script> | ||
|
||
<p bind:this={foo}>div</p> | ||
<svelte:element this={tag} bind:this={bar}>div</svelte:element> | ||
|
||
<button on:click={() => remove()}>Click Me</button> |
Oops, something went wrong.