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} + +
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} + +
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} + +
+
+ {#await Promise.resolve(42)} + + {:then name} + +
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 true} + A + {:else} +
+ +
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} + +
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 [] 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