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}`
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-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"
+}
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}