diff --git a/.changeset/heavy-cycles-explain.md b/.changeset/heavy-cycles-explain.md
new file mode 100644
index 000000000..246e36911
--- /dev/null
+++ b/.changeset/heavy-cycles-explain.md
@@ -0,0 +1,5 @@
+---
+"eslint-plugin-svelte": patch
+---
+
+fix: false positive for containing element in `svelte/no-unused-svelte-ignore`
diff --git a/src/shared/svelte-compile-warns/extract-leading-comments.ts b/src/shared/svelte-compile-warns/extract-leading-comments.ts
index 83ed3c7cf..cedb4b691 100644
--- a/src/shared/svelte-compile-warns/extract-leading-comments.ts
+++ b/src/shared/svelte-compile-warns/extract-leading-comments.ts
@@ -17,7 +17,7 @@ export function extractLeadingComments(
}
const astToken = token as AST.Token
if (astToken.type === "HTMLText") {
- return Boolean(astToken.value.trim())
+ return false
}
return astToken.type !== "HTMLComment"
},
diff --git a/src/shared/svelte-compile-warns/index.ts b/src/shared/svelte-compile-warns/index.ts
index 85c8dbe53..ac6f037cb 100644
--- a/src/shared/svelte-compile-warns/index.ts
+++ b/src/shared/svelte-compile-warns/index.ts
@@ -2,7 +2,7 @@ import type { AST } from "svelte-eslint-parser"
import * as compiler from "svelte/compiler"
import type { SourceMapMappings } from "@jridgewell/sourcemap-codec"
import { decode } from "@jridgewell/sourcemap-codec"
-import type { RuleContext } from "../../types"
+import type { ASTNodeWithParent, RuleContext } from "../../types"
import { LinesAndColumns } from "../../utils/lines-and-columns"
import type { TransformResult } from "./transform/types"
import {
@@ -21,6 +21,18 @@ import { getLangValue } from "../../utils/ast-utils"
import path from "path"
import fs from "fs"
+type WarningTargetNode =
+ | (AST.SvelteProgram & ASTNodeWithParent)
+ | (AST.SvelteElement & ASTNodeWithParent)
+ | (AST.SvelteStyleElement & ASTNodeWithParent)
+ | (AST.SvelteScriptElement["body"][number] & ASTNodeWithParent)
+type IgnoreTargetNode =
+ | WarningTargetNode
+ | (AST.SvelteIfBlock & ASTNodeWithParent)
+ | (AST.SvelteKeyBlock & ASTNodeWithParent)
+ | (AST.SvelteEachBlock & ASTNodeWithParent)
+ | (AST.SvelteAwaitBlock & ASTNodeWithParent)
+
const STYLE_TRANSFORMS: Record<
string,
typeof transformWithPostCSS | undefined
@@ -477,21 +489,22 @@ function processIgnore(
if (!warning.code) {
continue
}
- const node = getWarningNode(warning)
- if (!node) {
- continue
- }
- for (const comment of extractLeadingComments(context, node).reverse()) {
- const ignoreItem = ignoreComments.find(
- (item) => item.token === comment && item.code === warning.code,
- )
- if (ignoreItem) {
- unusedIgnores.delete(ignoreItem)
- remainingWarning.delete(warning)
- break
+ let node: IgnoreTargetNode | null = getWarningNode(warning)
+ while (node) {
+ for (const comment of extractLeadingComments(context, node).reverse()) {
+ const ignoreItem = ignoreComments.find(
+ (item) => item.token === comment && item.code === warning.code,
+ )
+ if (ignoreItem) {
+ unusedIgnores.delete(ignoreItem)
+ remainingWarning.delete(warning)
+ break
+ }
}
+ node = getIgnoreParent(node)
}
}
+
// Stripped styles are ignored from compilation and cannot determine css errors.
for (const node of stripStyleElements) {
for (const comment of extractLeadingComments(context, node).reverse()) {
@@ -509,8 +522,42 @@ function processIgnore(
unusedIgnores: [...unusedIgnores],
}
+ /** Get ignore target parent node */
+ function getIgnoreParent(node: IgnoreTargetNode): IgnoreTargetNode | null {
+ if (
+ node.type !== "SvelteElement" &&
+ node.type !== "SvelteIfBlock" &&
+ node.type !== "SvelteKeyBlock" &&
+ node.type !== "SvelteEachBlock" &&
+ node.type !== "SvelteAwaitBlock"
+ ) {
+ return null
+ }
+ const parent = node.parent
+ if (parent.type === "SvelteElseBlock") {
+ return parent.parent // SvelteIfBlock or SvelteEachBlock
+ }
+ if (
+ parent.type === "SvelteAwaitPendingBlock" ||
+ parent.type === "SvelteAwaitThenBlock" ||
+ parent.type === "SvelteAwaitCatchBlock"
+ ) {
+ return parent.parent // SvelteAwaitBlock
+ }
+ if (
+ parent.type !== "SvelteElement" &&
+ parent.type !== "SvelteIfBlock" &&
+ parent.type !== "SvelteKeyBlock" &&
+ parent.type !== "SvelteEachBlock"
+ // && parent.type !== "SvelteAwaitBlock"
+ ) {
+ return null
+ }
+ return parent
+ }
+
/** Get warning node */
- function getWarningNode(warning: Warning) {
+ function getWarningNode(warning: Warning): WarningTargetNode | null {
const indexes = getWarningIndexes(warning)
if (indexes.start != null) {
const node = getWarningTargetNodeFromIndex(indexes.start)
@@ -534,7 +581,9 @@ function processIgnore(
/**
* Get warning target node from the given index
*/
- function getWarningTargetNodeFromIndex(index: number) {
+ function getWarningTargetNodeFromIndex(
+ index: number,
+ ): WarningTargetNode | null {
let targetNode = sourceCode.getNodeByRangeIndex(index)
while (targetNode) {
if (
@@ -548,7 +597,7 @@ function processIgnore(
targetNode.parent.type === "Program" ||
targetNode.parent.type === "SvelteScriptElement"
) {
- return targetNode
+ return targetNode as WarningTargetNode
}
} else {
return null
diff --git a/tests/fixtures/rules/no-unused-svelte-ignore/invalid/invalid-svelte-ignore01-errors.yaml b/tests/fixtures/rules/no-unused-svelte-ignore/invalid/invalid-svelte-ignore01-errors.yaml
new file mode 100644
index 000000000..be0f7052d
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-svelte-ignore/invalid/invalid-svelte-ignore01-errors.yaml
@@ -0,0 +1,8 @@
+- message: svelte-ignore comment is used, but not warned
+ line: 4
+ column: 24
+ suggestions: null
+- message: svelte-ignore comment is used, but not warned
+ line: 4
+ column: 58
+ suggestions: null
diff --git a/tests/fixtures/rules/no-unused-svelte-ignore/invalid/invalid-svelte-ignore01-input.svelte b/tests/fixtures/rules/no-unused-svelte-ignore/invalid/invalid-svelte-ignore01-input.svelte
new file mode 100644
index 000000000..56a9e02da
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-svelte-ignore/invalid/invalid-svelte-ignore01-input.svelte
@@ -0,0 +1,9 @@
+
+ {#if true}
+ A
+
+ {:else}
+
+
+ {/if}
+
diff --git a/tests/fixtures/rules/no-unused-svelte-ignore/invalid/invalid-svelte-ignore02-errors.yaml b/tests/fixtures/rules/no-unused-svelte-ignore/invalid/invalid-svelte-ignore02-errors.yaml
new file mode 100644
index 000000000..be0f7052d
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-svelte-ignore/invalid/invalid-svelte-ignore02-errors.yaml
@@ -0,0 +1,8 @@
+- message: svelte-ignore comment is used, but not warned
+ line: 4
+ column: 24
+ suggestions: null
+- message: svelte-ignore comment is used, but not warned
+ line: 4
+ column: 58
+ suggestions: null
diff --git a/tests/fixtures/rules/no-unused-svelte-ignore/invalid/invalid-svelte-ignore02-input.svelte b/tests/fixtures/rules/no-unused-svelte-ignore/invalid/invalid-svelte-ignore02-input.svelte
new file mode 100644
index 000000000..d05928121
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-svelte-ignore/invalid/invalid-svelte-ignore02-input.svelte
@@ -0,0 +1,9 @@
+
+ {#each [] as e}
+ A
+
+ {:else}
+
+
+ {/each}
+
diff --git a/tests/fixtures/rules/no-unused-svelte-ignore/invalid/invalid-svelte-ignore03-errors.yaml b/tests/fixtures/rules/no-unused-svelte-ignore/invalid/invalid-svelte-ignore03-errors.yaml
new file mode 100644
index 000000000..68aa84582
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-svelte-ignore/invalid/invalid-svelte-ignore03-errors.yaml
@@ -0,0 +1,24 @@
+- message: svelte-ignore comment is used, but not warned
+ line: 3
+ column: 24
+ suggestions: null
+- message: svelte-ignore comment is used, but not warned
+ line: 3
+ column: 58
+ suggestions: null
+- message: svelte-ignore comment is used, but not warned
+ line: 7
+ column: 24
+ suggestions: null
+- message: svelte-ignore comment is used, but not warned
+ line: 7
+ column: 58
+ suggestions: null
+- message: svelte-ignore comment is used, but not warned
+ line: 15
+ column: 24
+ suggestions: null
+- message: svelte-ignore comment is used, but not warned
+ line: 15
+ column: 58
+ suggestions: null
diff --git a/tests/fixtures/rules/no-unused-svelte-ignore/invalid/invalid-svelte-ignore03-input.svelte b/tests/fixtures/rules/no-unused-svelte-ignore/invalid/invalid-svelte-ignore03-input.svelte
new file mode 100644
index 000000000..62e33f59c
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-svelte-ignore/invalid/invalid-svelte-ignore03-input.svelte
@@ -0,0 +1,20 @@
+
+ {#await Promise.resolve(42)}
+
+ {:then name}
+
+
+
+ {:catch name}
+
+
+ {/await}
+
+
+ {#await Promise.resolve(42)}
+
+ {:then name}
+
+
+ {/await}
+
diff --git a/tests/fixtures/rules/no-unused-svelte-ignore/valid/svelte-ignore01-input.svelte b/tests/fixtures/rules/no-unused-svelte-ignore/valid/svelte-ignore01-input.svelte
new file mode 100644
index 000000000..bef114449
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-svelte-ignore/valid/svelte-ignore01-input.svelte
@@ -0,0 +1,5 @@
+
+
diff --git a/tests/fixtures/rules/no-unused-svelte-ignore/valid/svelte-ignore02-input.svelte b/tests/fixtures/rules/no-unused-svelte-ignore/valid/svelte-ignore02-input.svelte
new file mode 100644
index 000000000..ec4c607d3
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-svelte-ignore/valid/svelte-ignore02-input.svelte
@@ -0,0 +1,6 @@
+
+TEXT
+
diff --git a/tests/fixtures/rules/no-unused-svelte-ignore/valid/svelte-ignore03-input.svelte b/tests/fixtures/rules/no-unused-svelte-ignore/valid/svelte-ignore03-input.svelte
new file mode 100644
index 000000000..46ca36867
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-svelte-ignore/valid/svelte-ignore03-input.svelte
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/tests/fixtures/rules/no-unused-svelte-ignore/valid/svelte-ignore04-input.svelte b/tests/fixtures/rules/no-unused-svelte-ignore/valid/svelte-ignore04-input.svelte
new file mode 100644
index 000000000..701dde5d0
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-svelte-ignore/valid/svelte-ignore04-input.svelte
@@ -0,0 +1,17 @@
+
+
+ {#if true}
+
+
+ {/if}
+
+
+
+ {#if true}
+ A
+ {:else}
+
+
+
+ {/if}
+
diff --git a/tests/fixtures/rules/no-unused-svelte-ignore/valid/svelte-ignore05-input.svelte b/tests/fixtures/rules/no-unused-svelte-ignore/valid/svelte-ignore05-input.svelte
new file mode 100644
index 000000000..7d1ec600a
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-svelte-ignore/valid/svelte-ignore05-input.svelte
@@ -0,0 +1,7 @@
+
+
+ {#key 42}
+
+
+ {/key}
+
diff --git a/tests/fixtures/rules/no-unused-svelte-ignore/valid/svelte-ignore06-input.svelte b/tests/fixtures/rules/no-unused-svelte-ignore/valid/svelte-ignore06-input.svelte
new file mode 100644
index 000000000..ab95cbf26
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-svelte-ignore/valid/svelte-ignore06-input.svelte
@@ -0,0 +1,17 @@
+
+
+ {#each [] as e}
+
+
+ {/each}
+
+
+
+ {#each [] as e}
+ A
+ {:else}
+
+
+
+ {/each}
+
diff --git a/tests/fixtures/rules/no-unused-svelte-ignore/valid/svelte-ignore07-input.svelte b/tests/fixtures/rules/no-unused-svelte-ignore/valid/svelte-ignore07-input.svelte
new file mode 100644
index 000000000..cd96ffcd4
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-svelte-ignore/valid/svelte-ignore07-input.svelte
@@ -0,0 +1,44 @@
+
+
+ {#await Promise.resolve(42)}
+
+
+ {/await}
+
+
+
+ {#await Promise.resolve(42)}
+
+
+ {:then name}
+
+
+ {:catch name}
+
+
+ {/await}
+
+
+
+ {#await Promise.resolve(42)}
+
+
+ {:then name}
+
+
+ {/await}
+
+
+
+ {#await Promise.resolve(42) then n}
+
+
+ {/await}
+
+
+
+ {#await Promise.resolve(42) catch n}
+
+
+ {/await}
+
diff --git a/tests/fixtures/rules/valid-compile/invalid/invalid-svelte-ignore01-errors.yaml b/tests/fixtures/rules/valid-compile/invalid/invalid-svelte-ignore01-errors.yaml
new file mode 100644
index 000000000..fd95963aa
--- /dev/null
+++ b/tests/fixtures/rules/valid-compile/invalid/invalid-svelte-ignore01-errors.yaml
@@ -0,0 +1,15 @@
+- message: "A11y: noninteractive element cannot have nonnegative tabIndex
+ value(a11y-no-noninteractive-tabindex)"
+ line: 6
+ column: 5
+ suggestions: null
+- message: "A11y: A form label must be associated with a
+ control.(a11y-label-has-associated-control)"
+ line: 6
+ column: 5
+ suggestions: null
+- message: "A11y: noninteractive element cannot have nonnegative tabIndex
+ value(a11y-no-noninteractive-tabindex)"
+ line: 7
+ column: 5
+ suggestions: null
diff --git a/tests/fixtures/rules/valid-compile/invalid/invalid-svelte-ignore01-input.svelte b/tests/fixtures/rules/valid-compile/invalid/invalid-svelte-ignore01-input.svelte
new file mode 100644
index 000000000..56a9e02da
--- /dev/null
+++ b/tests/fixtures/rules/valid-compile/invalid/invalid-svelte-ignore01-input.svelte
@@ -0,0 +1,9 @@
+
+ {#if true}
+ A
+
+ {:else}
+
+
+ {/if}
+
diff --git a/tests/fixtures/rules/valid-compile/invalid/invalid-svelte-ignore02-errors.yaml b/tests/fixtures/rules/valid-compile/invalid/invalid-svelte-ignore02-errors.yaml
new file mode 100644
index 000000000..fd95963aa
--- /dev/null
+++ b/tests/fixtures/rules/valid-compile/invalid/invalid-svelte-ignore02-errors.yaml
@@ -0,0 +1,15 @@
+- message: "A11y: noninteractive element cannot have nonnegative tabIndex
+ value(a11y-no-noninteractive-tabindex)"
+ line: 6
+ column: 5
+ suggestions: null
+- message: "A11y: A form label must be associated with a
+ control.(a11y-label-has-associated-control)"
+ line: 6
+ column: 5
+ suggestions: null
+- message: "A11y: noninteractive element cannot have nonnegative tabIndex
+ value(a11y-no-noninteractive-tabindex)"
+ line: 7
+ column: 5
+ suggestions: null
diff --git a/tests/fixtures/rules/valid-compile/invalid/invalid-svelte-ignore02-input.svelte b/tests/fixtures/rules/valid-compile/invalid/invalid-svelte-ignore02-input.svelte
new file mode 100644
index 000000000..d05928121
--- /dev/null
+++ b/tests/fixtures/rules/valid-compile/invalid/invalid-svelte-ignore02-input.svelte
@@ -0,0 +1,9 @@
+
+ {#each [] as e}
+ A
+
+ {:else}
+
+
+ {/each}
+
diff --git a/tests/fixtures/rules/valid-compile/invalid/invalid-svelte-ignore03-errors.yaml b/tests/fixtures/rules/valid-compile/invalid/invalid-svelte-ignore03-errors.yaml
new file mode 100644
index 000000000..7ed6236ce
--- /dev/null
+++ b/tests/fixtures/rules/valid-compile/invalid/invalid-svelte-ignore03-errors.yaml
@@ -0,0 +1,53 @@
+- message: Empty block(empty-block)
+ line: 2
+ column: 31
+ suggestions: null
+- message: "A11y: noninteractive element cannot have nonnegative tabIndex
+ value(a11y-no-noninteractive-tabindex)"
+ line: 5
+ column: 5
+ suggestions: null
+- message: "A11y: A form label must be associated with a
+ control.(a11y-label-has-associated-control)"
+ line: 5
+ column: 5
+ suggestions: null
+- message: "A11y: noninteractive element cannot have nonnegative tabIndex
+ value(a11y-no-noninteractive-tabindex)"
+ line: 6
+ column: 5
+ suggestions: null
+- message: "A11y: noninteractive element cannot have nonnegative tabIndex
+ value(a11y-no-noninteractive-tabindex)"
+ line: 9
+ column: 5
+ suggestions: null
+- message: "A11y: A form label must be associated with a
+ control.(a11y-label-has-associated-control)"
+ line: 9
+ column: 5
+ suggestions: null
+- message: "A11y: noninteractive element cannot have nonnegative tabIndex
+ value(a11y-no-noninteractive-tabindex)"
+ line: 10
+ column: 5
+ suggestions: null
+- message: Empty block(empty-block)
+ line: 14
+ column: 31
+ suggestions: null
+- message: "A11y: noninteractive element cannot have nonnegative tabIndex
+ value(a11y-no-noninteractive-tabindex)"
+ line: 17
+ column: 5
+ suggestions: null
+- message: "A11y: A form label must be associated with a
+ control.(a11y-label-has-associated-control)"
+ line: 17
+ column: 5
+ suggestions: null
+- message: "A11y: noninteractive element cannot have nonnegative tabIndex
+ value(a11y-no-noninteractive-tabindex)"
+ line: 18
+ column: 5
+ suggestions: null
diff --git a/tests/fixtures/rules/valid-compile/invalid/invalid-svelte-ignore03-input.svelte b/tests/fixtures/rules/valid-compile/invalid/invalid-svelte-ignore03-input.svelte
new file mode 100644
index 000000000..62e33f59c
--- /dev/null
+++ b/tests/fixtures/rules/valid-compile/invalid/invalid-svelte-ignore03-input.svelte
@@ -0,0 +1,20 @@
+
+ {#await Promise.resolve(42)}
+
+ {:then name}
+
+
+
+ {:catch name}
+
+
+ {/await}
+
+
+ {#await Promise.resolve(42)}
+
+ {:then name}
+
+
+ {/await}
+
diff --git a/tests/fixtures/rules/valid-compile/valid/svelte-ignore01-input.svelte b/tests/fixtures/rules/valid-compile/valid/svelte-ignore01-input.svelte
new file mode 100644
index 000000000..bef114449
--- /dev/null
+++ b/tests/fixtures/rules/valid-compile/valid/svelte-ignore01-input.svelte
@@ -0,0 +1,5 @@
+
+
diff --git a/tests/fixtures/rules/valid-compile/valid/svelte-ignore02-input.svelte b/tests/fixtures/rules/valid-compile/valid/svelte-ignore02-input.svelte
new file mode 100644
index 000000000..ec4c607d3
--- /dev/null
+++ b/tests/fixtures/rules/valid-compile/valid/svelte-ignore02-input.svelte
@@ -0,0 +1,6 @@
+
+TEXT
+
diff --git a/tests/fixtures/rules/valid-compile/valid/svelte-ignore03-input.svelte b/tests/fixtures/rules/valid-compile/valid/svelte-ignore03-input.svelte
new file mode 100644
index 000000000..46ca36867
--- /dev/null
+++ b/tests/fixtures/rules/valid-compile/valid/svelte-ignore03-input.svelte
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/tests/fixtures/rules/valid-compile/valid/svelte-ignore04-input.svelte b/tests/fixtures/rules/valid-compile/valid/svelte-ignore04-input.svelte
new file mode 100644
index 000000000..701dde5d0
--- /dev/null
+++ b/tests/fixtures/rules/valid-compile/valid/svelte-ignore04-input.svelte
@@ -0,0 +1,17 @@
+
+
+ {#if true}
+
+
+ {/if}
+
+
+
+ {#if true}
+ A
+ {:else}
+
+
+
+ {/if}
+
diff --git a/tests/fixtures/rules/valid-compile/valid/svelte-ignore05-input.svelte b/tests/fixtures/rules/valid-compile/valid/svelte-ignore05-input.svelte
new file mode 100644
index 000000000..7d1ec600a
--- /dev/null
+++ b/tests/fixtures/rules/valid-compile/valid/svelte-ignore05-input.svelte
@@ -0,0 +1,7 @@
+
+
+ {#key 42}
+
+
+ {/key}
+
diff --git a/tests/fixtures/rules/valid-compile/valid/svelte-ignore06-input.svelte b/tests/fixtures/rules/valid-compile/valid/svelte-ignore06-input.svelte
new file mode 100644
index 000000000..ab95cbf26
--- /dev/null
+++ b/tests/fixtures/rules/valid-compile/valid/svelte-ignore06-input.svelte
@@ -0,0 +1,17 @@
+
+
+ {#each [] as e}
+
+
+ {/each}
+
+
+
+ {#each [] as e}
+ A
+ {:else}
+
+
+
+ {/each}
+
diff --git a/tests/fixtures/rules/valid-compile/valid/svelte-ignore07-input.svelte b/tests/fixtures/rules/valid-compile/valid/svelte-ignore07-input.svelte
new file mode 100644
index 000000000..cd96ffcd4
--- /dev/null
+++ b/tests/fixtures/rules/valid-compile/valid/svelte-ignore07-input.svelte
@@ -0,0 +1,44 @@
+
+
+ {#await Promise.resolve(42)}
+
+
+ {/await}
+
+
+
+ {#await Promise.resolve(42)}
+
+
+ {:then name}
+
+
+ {:catch name}
+
+
+ {/await}
+
+
+
+ {#await Promise.resolve(42)}
+
+
+ {:then name}
+
+
+ {/await}
+
+
+
+ {#await Promise.resolve(42) then n}
+
+
+ {/await}
+
+
+
+ {#await Promise.resolve(42) catch n}
+
+
+ {/await}
+
diff --git a/typings/estree/index.d.ts b/typings/estree/index.d.ts
index 56df768b7..306dd6e31 100644
--- a/typings/estree/index.d.ts
+++ b/typings/estree/index.d.ts
@@ -5,6 +5,7 @@
import type { TSESTree } from "@typescript-eslint/types"
export type Node = TSESTree.Node
+export type Program = TSESTree.Program
export type Expression = TSESTree.Expression
export type Statement = TSESTree.Statement
export type Pattern = TSESTree.Pattern