-
Notifications
You must be signed in to change notification settings - Fork 6
feat: make module window check more robust #14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
de40373
558c4bd
98c1fd6
2996b70
9846500
fe5dec3
8a19819
5ea4aec
943176a
f0474fd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"eslint-plugin-react-server-components": patch | ||
--- | ||
|
||
Making window check more robust to not require "use client" when safely accessed behind a `typeof window !== undefined` check |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,6 +48,13 @@ describe("use client", () => { | |
return context; | ||
}`, | ||
}, | ||
{ | ||
code: `const HREF = typeof window === 'undefined' ? undefined : window.location.href;` | ||
}, | ||
{ | ||
code: `const HREF = typeof window !== 'undefined' ? window.location.href : '';` | ||
}, | ||
|
||
], | ||
invalid: [ | ||
// DOCUMENT | ||
|
@@ -99,6 +106,13 @@ function Bar() { | |
window.addEventListener('scroll', () => {}) | ||
return <div />; | ||
}`, | ||
}, | ||
{ | ||
code: `const HREF = typeof window === 'undefined' ? window.location.href : window.location.href.slice(0,10);`, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good edge case to catch! |
||
errors: [{ messageId: "addUseClientBrowserAPI" }], | ||
output: `'use client'; | ||
|
||
const HREF = typeof window === 'undefined' ? window.location.href : window.location.href.slice(0,10);`, | ||
}, | ||
// OBSERVERS | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -208,11 +208,40 @@ const create = Components.detect( | |
// @ts-expect-error | ||
const name = node.object.name; | ||
const scopeType = context.getScope().type; | ||
if ( | ||
|
||
// check if the window usage is behind a typeof window === 'undefined' check | ||
const conditionalExpressionNode = node.parent?.parent; | ||
const isWindowCheck = | ||
conditionalExpressionNode?.type === "ConditionalExpression" && | ||
conditionalExpressionNode.test?.type === "BinaryExpression" && | ||
conditionalExpressionNode.test.left?.type === "UnaryExpression" && | ||
conditionalExpressionNode.test.left.operator === "typeof" && | ||
conditionalExpressionNode.test.left.argument?.type === "Identifier" && | ||
conditionalExpressionNode.test.left.argument?.name === "window" && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this catch other places where we might make this check? Like in an if (typeof window !== 'undefined') {
} We could use I think for legibility I might recommend extracting this logic into a "checkIfSafeBrowserGlobalUse" function outside of this function (where it takes There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I'm not sure about the if statement or not, but I don't think this would cover that. I was mostly going for the simplest solution to the one edge case we were seeing. I can look into using the Agreed on the legibility part 👍 I can do that for sure. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh nvm, I think you're advocating for removing the MemberExpression entrypoint in favor of a starting at the Identifier level. I'll investigate that a bit 👍 |
||
conditionalExpressionNode.test.right?.type === "Literal" && | ||
conditionalExpressionNode.test.right.value === "undefined"; | ||
|
||
// checks to see if it's `typeof window !== 'undefined'` or `typeof window === 'undefined'` | ||
const isNegatedWindowCheck = | ||
isWindowCheck && | ||
conditionalExpressionNode.test?.type === "BinaryExpression" && | ||
conditionalExpressionNode.test.operator === "!=="; | ||
|
||
// checks to see if window is being accessed safely behind a window check | ||
const isSafelyBehindWindowCheck = | ||
(isWindowCheck && | ||
!isNegatedWindowCheck && | ||
conditionalExpressionNode.alternate === node?.parent) || | ||
(isNegatedWindowCheck && | ||
conditionalExpressionNode.consequent === node?.parent); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is basically to cover either |
||
|
||
if ( | ||
undeclaredReferences.has(name) && | ||
browserOnlyGlobals.has(name) && | ||
(scopeType === "module" || !!util.getParentComponent(node)) | ||
(scopeType === "module" || !!util.getParentComponent(node)) && | ||
!isSafelyBehindWindowCheck | ||
) { | ||
// console.log(name, node.object) | ||
instances.push(name); | ||
reportMissingDirective("addUseClientBrowserAPI", node.object); | ||
} | ||
|
Uh oh!
There was an error while loading. Please reload this page.