Skip to content

Commit de40373

Browse files
committed
make window check more robust
1 parent d3c3cf6 commit de40373

File tree

2 files changed

+45
-2
lines changed

2 files changed

+45
-2
lines changed

src/rules/use-client.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ describe("use client", () => {
4848
return context;
4949
}`,
5050
},
51+
{
52+
code: `const HREF = typeof window === 'undefined' ? undefined : window.location.href;`
53+
},
54+
{
55+
code: `const HREF = typeof window !== 'undefined' ? window.location.href : '';`
56+
},
57+
5158
],
5259
invalid: [
5360
// DOCUMENT
@@ -99,6 +106,13 @@ function Bar() {
99106
window.addEventListener('scroll', () => {})
100107
return <div />;
101108
}`,
109+
},
110+
{
111+
code: `const HREF = typeof window === 'undefined' ? window.location.href : window.location.href.slice(0,10);`,
112+
errors: [{ messageId: "addUseClientBrowserAPI" }],
113+
output: `'use client';
114+
115+
const HREF = typeof window === 'undefined' ? window.location.href : window.location.href.slice(0,10);`,
102116
},
103117
// OBSERVERS
104118
{

src/rules/use-client.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,11 +208,40 @@ const create = Components.detect(
208208
// @ts-expect-error
209209
const name = node.object.name;
210210
const scopeType = context.getScope().type;
211-
if (
211+
212+
// check if the window usage is behind a typeof window === 'undefined' check
213+
const conditionalExpressionNode = node.parent?.parent;
214+
const isWindowCheck =
215+
conditionalExpressionNode?.type === "ConditionalExpression" &&
216+
conditionalExpressionNode.test?.type === "BinaryExpression" &&
217+
conditionalExpressionNode.test.left?.type === "UnaryExpression" &&
218+
conditionalExpressionNode.test.left.operator === "typeof" &&
219+
conditionalExpressionNode.test.left.argument?.type === "Identifier" &&
220+
conditionalExpressionNode.test.left.argument?.name === "window" &&
221+
conditionalExpressionNode.test.right?.type === "Literal" &&
222+
conditionalExpressionNode.test.right.value === "undefined";
223+
224+
// checks to see if it's `typeof window !== 'undefined'` or `typeof window === 'undefined'`
225+
const isNegatedWindowCheck =
226+
isWindowCheck &&
227+
conditionalExpressionNode.test?.type === "BinaryExpression" &&
228+
conditionalExpressionNode.test.operator === "!==";
229+
230+
// checks to see if window is being accessed safely behind a window check
231+
const isSafelyBehindWindowCheck =
232+
(isWindowCheck &&
233+
!isNegatedWindowCheck &&
234+
conditionalExpressionNode.alternate === node?.parent) ||
235+
(isNegatedWindowCheck &&
236+
conditionalExpressionNode.consequent === node?.parent);
237+
238+
if (
212239
undeclaredReferences.has(name) &&
213240
browserOnlyGlobals.has(name) &&
214-
(scopeType === "module" || !!util.getParentComponent(node))
241+
(scopeType === "module" || !!util.getParentComponent(node)) &&
242+
!isSafelyBehindWindowCheck
215243
) {
244+
// console.log(name, node.object)
216245
instances.push(name);
217246
reportMissingDirective("addUseClientBrowserAPI", node.object);
218247
}

0 commit comments

Comments
 (0)