From 990b3ecb374b820cbabd65574008af8c04799346 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Fri, 20 Jun 2025 09:51:51 +0900 Subject: [PATCH 1/3] fix(no-top-level-browser-globals): false positive for `{#if browser}` --- .../src/rules/no-top-level-browser-globals.ts | 26 ++++++++++++++++--- .../invalid/in-template01-errors.yaml | 16 ++++++++++++ .../invalid/in-template01-input.svelte | 25 ++++++++++++++++++ .../valid/in-template01-input.svelte | 7 +++++ .../valid/in-template02-input.svelte | 3 +++ .../valid/in-template03-input.svelte | 9 +++++++ 6 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/invalid/in-template01-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/invalid/in-template01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/valid/in-template01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/valid/in-template02-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/valid/in-template03-input.svelte diff --git a/packages/eslint-plugin-svelte/src/rules/no-top-level-browser-globals.ts b/packages/eslint-plugin-svelte/src/rules/no-top-level-browser-globals.ts index 1a997eca3..ca92bfdaf 100644 --- a/packages/eslint-plugin-svelte/src/rules/no-top-level-browser-globals.ts +++ b/packages/eslint-plugin-svelte/src/rules/no-top-level-browser-globals.ts @@ -4,6 +4,7 @@ import { createRule } from '../utils/index.js'; import globals from 'globals'; import type { TSESTree } from '@typescript-eslint/types'; import { findVariable, getScope } from '../utils/ast-utils.js'; +import type { AST } from 'svelte-eslint-parser'; export default createRule('no-top-level-browser-globals', { meta: { @@ -36,10 +37,10 @@ export default createRule('no-top-level-browser-globals', { }; const maybeGuards: MaybeGuard[] = []; - const functions: TSESTree.FunctionLike[] = []; + const functions: (TSESTree.FunctionLike | AST.SvelteSnippetBlock)[] = []; const typeAnnotations: (TSESTree.TypeNode | TSESTree.TSTypeAnnotation)[] = []; - function enterFunction(node: TSESTree.FunctionLike) { + function enterFunction(node: TSESTree.FunctionLike | AST.SvelteSnippetBlock) { if (isTopLevelLocation(node)) { functions.push(node); } @@ -120,6 +121,7 @@ export default createRule('no-top-level-browser-globals', { return { ':function': enterFunction, + SvelteSnippetBlock: enterFunction, '*.typeAnnotation': enterTypeAnnotation, MetaProperty: enterMetaProperty, 'Program:exit': verifyGlobalReferences @@ -144,7 +146,7 @@ export default createRule('no-top-level-browser-globals', { * Checks whether the node is in a top-level location. * @returns `true` if the node is in a top-level location. */ - function isTopLevelLocation(node: TSESTree.Node) { + function isTopLevelLocation(node: TSESTree.Node | AST.SvelteSnippetBlock) { for (const func of functions) { if (func.range[0] <= node.range[0] && node.range[1] <= func.range[1]) { return false; @@ -321,7 +323,7 @@ export default createRule('no-top-level-browser-globals', { node: TSESTree.Expression; not?: boolean; }): ((node: TSESTree.Node) => boolean) | null { - const parent = guardInfo.node.parent; + const parent = guardInfo.node.parent as TSESTree.Node | AST.SvelteNode; if (!parent) return null; if (parent.type === 'ConditionalExpression') { @@ -331,6 +333,22 @@ export default createRule('no-top-level-browser-globals', { if (parent.type === 'UnaryExpression' && parent.operator === '!') { return getGuardChecker({ not: !guardInfo.not, node: parent }); } + if (parent.type === 'SvelteIfBlock' && parent.expression === guardInfo.node) { + if (!guardInfo.not) { + if (parent.children.length === 0) { + return null; // No block to check + } + const first = parent.children[0]; + const last = parent.children.at(-1)!; + return (n) => first.range[0] <= n.range[0] && n.range[1] <= last.range[1]; + } + // not + if (parent.else) { + const block = parent.else; + return (n) => block.range[0] <= n.range[0] && n.range[1] <= block.range[1]; + } + return null; + } if (parent.type === 'IfStatement' && parent.test === guardInfo.node) { if (!guardInfo.not) { const block = parent.consequent; diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/invalid/in-template01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/invalid/in-template01-errors.yaml new file mode 100644 index 000000000..b6758a8be --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/invalid/in-template01-errors.yaml @@ -0,0 +1,16 @@ +- message: Unexpected top-level browser global variable "location". + line: 5 + column: 2 + suggestions: null +- message: Unexpected top-level browser global variable "location". + line: 12 + column: 3 + suggestions: null +- message: Unexpected top-level browser global variable "location". + line: 18 + column: 3 + suggestions: null +- message: Unexpected top-level browser global variable "location". + line: 22 + column: 3 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/invalid/in-template01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/invalid/in-template01-input.svelte new file mode 100644 index 000000000..a546411a0 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/invalid/in-template01-input.svelte @@ -0,0 +1,25 @@ + + +{location.href} + +{#if browser} + {location.href} +{/if} + +{#if !browser} + {location.href} +{/if} + +{#if browser} + {location.href} +{:else} + {location.href} +{/if} + +{#if !browser} + {location.href} +{:else} + {location.href} +{/if} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/valid/in-template01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/valid/in-template01-input.svelte new file mode 100644 index 000000000..a3c05f995 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/valid/in-template01-input.svelte @@ -0,0 +1,7 @@ + + +{#if browser} + {location.href} +{/if} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/valid/in-template02-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/valid/in-template02-input.svelte new file mode 100644 index 000000000..b36a0bdab --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/valid/in-template02-input.svelte @@ -0,0 +1,3 @@ +{#snippet f()} + {location.href} +{/snippet} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/valid/in-template03-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/valid/in-template03-input.svelte new file mode 100644 index 000000000..71960e387 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/valid/in-template03-input.svelte @@ -0,0 +1,9 @@ + + +{#if !browser} + Server-side. +{:else} + {location.href} +{/if} From 5ee87e1e6ebaa67e7f67424b3a5c0d56b82a51c1 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Fri, 20 Jun 2025 09:52:40 +0900 Subject: [PATCH 2/3] Create popular-donkeys-cross.md --- .changeset/popular-donkeys-cross.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/popular-donkeys-cross.md diff --git a/.changeset/popular-donkeys-cross.md b/.changeset/popular-donkeys-cross.md new file mode 100644 index 000000000..a7164cc26 --- /dev/null +++ b/.changeset/popular-donkeys-cross.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-svelte": patch +--- + +fix(no-top-level-browser-globals): false positive for `{#if browser}` From 2c840d8d0aa05898f71312125a752ff7ba5104ab Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Fri, 20 Jun 2025 10:08:40 +0900 Subject: [PATCH 3/3] fix: error in svelte v4 --- .../valid/in-template02-requirements.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/valid/in-template02-requirements.json diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/valid/in-template02-requirements.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/valid/in-template02-requirements.json new file mode 100644 index 000000000..0192b1098 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-top-level-browser-globals/valid/in-template02-requirements.json @@ -0,0 +1,3 @@ +{ + "svelte": ">=5.0.0-0" +}