Skip to content

Commit 5db956e

Browse files
authored
fix(no-top-level-browser-globals): false positive for {#if browser} (#1252)
1 parent fac2e64 commit 5db956e

File tree

8 files changed

+90
-4
lines changed

8 files changed

+90
-4
lines changed

.changeset/popular-donkeys-cross.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-svelte": patch
3+
---
4+
5+
fix(no-top-level-browser-globals): false positive for `{#if browser}`

packages/eslint-plugin-svelte/src/rules/no-top-level-browser-globals.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { createRule } from '../utils/index.js';
44
import globals from 'globals';
55
import type { TSESTree } from '@typescript-eslint/types';
66
import { findVariable, getScope } from '../utils/ast-utils.js';
7+
import type { AST } from 'svelte-eslint-parser';
78

89
export default createRule('no-top-level-browser-globals', {
910
meta: {
@@ -36,10 +37,10 @@ export default createRule('no-top-level-browser-globals', {
3637
};
3738
const maybeGuards: MaybeGuard[] = [];
3839

39-
const functions: TSESTree.FunctionLike[] = [];
40+
const functions: (TSESTree.FunctionLike | AST.SvelteSnippetBlock)[] = [];
4041
const typeAnnotations: (TSESTree.TypeNode | TSESTree.TSTypeAnnotation)[] = [];
4142

42-
function enterFunction(node: TSESTree.FunctionLike) {
43+
function enterFunction(node: TSESTree.FunctionLike | AST.SvelteSnippetBlock) {
4344
if (isTopLevelLocation(node)) {
4445
functions.push(node);
4546
}
@@ -120,6 +121,7 @@ export default createRule('no-top-level-browser-globals', {
120121

121122
return {
122123
':function': enterFunction,
124+
SvelteSnippetBlock: enterFunction,
123125
'*.typeAnnotation': enterTypeAnnotation,
124126
MetaProperty: enterMetaProperty,
125127
'Program:exit': verifyGlobalReferences
@@ -144,7 +146,7 @@ export default createRule('no-top-level-browser-globals', {
144146
* Checks whether the node is in a top-level location.
145147
* @returns `true` if the node is in a top-level location.
146148
*/
147-
function isTopLevelLocation(node: TSESTree.Node) {
149+
function isTopLevelLocation(node: TSESTree.Node | AST.SvelteSnippetBlock) {
148150
for (const func of functions) {
149151
if (func.range[0] <= node.range[0] && node.range[1] <= func.range[1]) {
150152
return false;
@@ -321,7 +323,7 @@ export default createRule('no-top-level-browser-globals', {
321323
node: TSESTree.Expression;
322324
not?: boolean;
323325
}): ((node: TSESTree.Node) => boolean) | null {
324-
const parent = guardInfo.node.parent;
326+
const parent = guardInfo.node.parent as TSESTree.Node | AST.SvelteNode;
325327
if (!parent) return null;
326328

327329
if (parent.type === 'ConditionalExpression') {
@@ -331,6 +333,22 @@ export default createRule('no-top-level-browser-globals', {
331333
if (parent.type === 'UnaryExpression' && parent.operator === '!') {
332334
return getGuardChecker({ not: !guardInfo.not, node: parent });
333335
}
336+
if (parent.type === 'SvelteIfBlock' && parent.expression === guardInfo.node) {
337+
if (!guardInfo.not) {
338+
if (parent.children.length === 0) {
339+
return null; // No block to check
340+
}
341+
const first = parent.children[0];
342+
const last = parent.children.at(-1)!;
343+
return (n) => first.range[0] <= n.range[0] && n.range[1] <= last.range[1];
344+
}
345+
// not
346+
if (parent.else) {
347+
const block = parent.else;
348+
return (n) => block.range[0] <= n.range[0] && n.range[1] <= block.range[1];
349+
}
350+
return null;
351+
}
334352
if (parent.type === 'IfStatement' && parent.test === guardInfo.node) {
335353
if (!guardInfo.not) {
336354
const block = parent.consequent;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
- message: Unexpected top-level browser global variable "location".
2+
line: 5
3+
column: 2
4+
suggestions: null
5+
- message: Unexpected top-level browser global variable "location".
6+
line: 12
7+
column: 3
8+
suggestions: null
9+
- message: Unexpected top-level browser global variable "location".
10+
line: 18
11+
column: 3
12+
suggestions: null
13+
- message: Unexpected top-level browser global variable "location".
14+
line: 22
15+
column: 3
16+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<script>
2+
import { browser } from '$app/environment';
3+
</script>
4+
5+
{location.href}
6+
7+
{#if browser}
8+
{location.href} <!-- Client-side -->
9+
{/if}
10+
11+
{#if !browser}
12+
{location.href} <!-- Server-side -->
13+
{/if}
14+
15+
{#if browser}
16+
{location.href} <!-- Client-side -->
17+
{:else}
18+
{location.href} <!-- Server-side -->
19+
{/if}
20+
21+
{#if !browser}
22+
{location.href} <!-- Server-side -->
23+
{:else}
24+
{location.href} <!-- Client-side -->
25+
{/if}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
import { browser } from '$app/environment';
3+
</script>
4+
5+
{#if browser}
6+
{location.href}
7+
{/if}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{#snippet f()}
2+
{location.href} <!-- This is valid because usage cannot be tracked. -->
3+
{/snippet}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"svelte": ">=5.0.0-0"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
import { browser } from '$app/environment';
3+
</script>
4+
5+
{#if !browser}
6+
Server-side.
7+
{:else}
8+
{location.href}
9+
{/if}

0 commit comments

Comments
 (0)