-
-
Notifications
You must be signed in to change notification settings - Fork 38
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
feat: Add svelte/valid-context-access
rule
#480
base: main
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: ffc0bce The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
49ff328
to
43d6041
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for this PR!
I haven't checked all yet, but I have some comments.
src/rules/valid-context-access.ts
Outdated
currentNode: TSESTree.CallExpression, | ||
) { | ||
let { parent } = currentNode | ||
if (parent?.type !== "ExpressionStatement") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect an if statement like the following will not work:
if (hasContext("answer")) {
}
Could you add this test case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a basic test for if statement. I think it works fine.
Is this same understanding with you?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry. I should have explained more.
I would like to add a test for if statements are used at the top level.
<script>
import { hasContext } from "svelte"
if (hasContext("answer")) {
console.log("The answer exist")
}
</script>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh OK! I will add a test and fix logic.
}, | ||
type: "problem", | ||
}, | ||
create(context) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It probably doesn't work well other then *.svelte
files, so I think the following guard is needed.
create(context) { | |
create(context) { | |
if (!context.parserServices.isSvelte) { | |
return {} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to add more test but even JS file can use onMount
so I think we need to check JS files also.
import { setContext, onMount } from "svelte"
const something = () => {
setContext("answer", 42)
}
onMount(() => something())
But at the same time, I found this pattern.
It should be work but now ESLint error occurs.
So I need to handle this.
import { setContext } from "svelte"
const something = () => {
setContext("answer", 42)
}
const wrapper = (fn) => {
fn()
}
wrapper(() => something())
In addition, I forgot to handle async/await.😇
const something = async () => {
await Promise.resolve()
setContext("answer", 42)
}
something()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this logic and I wrote this limitation on the docs.
I realized that it’s impossible to check async/await things perfectly. So my opinion is that we will mention this limitation on the docs and we don't check async/await things. <script>
import { setContext } from "svelte"
import someAsyncProcess from './outside'
someAsyncProcess(() => {
setContext("answer", 42)
}); // outside.js
export async function someAsyncProcess(fn) {
await Promise.resolve();
fn();
} |
Hmm. You're right, there are false negatives, but I don't think users will use setContext that way, so I don't think it matters much if it's not reported. I think code like the following is a common mistake, so I think there's value in having it reported by a rule. const something = async () => {
await Promise.resolve()
setContext("answer", 42)
} eslint-plugin-vue has some similar checking rules. |
import { extractSvelteLifeCycleReferences } from "./reference-helpers/svelte-lifecycle" | ||
import { extractTaskReferences } from "./reference-helpers/microtask" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved few functions to utils dir because I want to use these in valid-context-access
rule.
const tickCallExpressions = Array.from( | ||
extractSvelteLifeCycleReferences(context, ["tick"]), | ||
) | ||
const taskReferences = Array.from(extractTaskReferences(context)) | ||
const reactiveVariableReferences = getReactiveVariableReferences(context) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just I moved there lines for performance improvement.
}, | ||
type: "problem", | ||
}, | ||
create(context) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this logic and I wrote this limitation on the docs.
const awaitExpressions: { | ||
belongingFunction: | ||
| TSESTree.FunctionDeclaration | ||
| TSESTree.VariableDeclaration |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the belongingFunction
should be assigned a VariableDeclaration
.
Should we have checked FunctionExpression
instead?
function isAfterAwait(node: TSESTree.CallExpression) { | ||
for (const awaitExpression of awaitExpressions) { | ||
const { belongingFunction, node: awaitNode } = awaitExpression | ||
if (isInsideOf(node, belongingFunction)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There seems to be false positives in complex cases such as:
<script>
import { getContext } from "svelte"
async function fn() {
async function foo() {
await p
}
getContext("a")
await foo()
}
fn()
</script>
I don't think we can check the correct function scope by just checking the range.
parent = parent.parent | ||
if ( | ||
parent?.type === "VariableDeclaration" || | ||
parent?.type === "FunctionDeclaration" | ||
) { | ||
const references = | ||
parent.type === "VariableDeclaration" | ||
? getReferences(parent.declarations[0].id) | ||
: parent.id | ||
? getReferences(parent.id) | ||
: [] | ||
|
||
for (const reference of references) { | ||
if (reference.identifier?.parent?.type === "CallExpression") { | ||
if ( | ||
!visitedCallExpressions.includes(reference.identifier.parent) | ||
) { | ||
visitedCallExpressions.push(reference.identifier.parent) | ||
doLint( | ||
visitedCallExpressions, | ||
contextCallExpression, | ||
reference.identifier?.parent, | ||
) | ||
} | ||
} | ||
} | ||
} else if (parent?.type === "ExpressionStatement") { | ||
if (parent.expression.type !== "CallExpression") { | ||
report(contextCallExpression) | ||
} else if (lifeCycleReferences.includes(parent.expression)) { | ||
report(contextCallExpression) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's safer to check the VariableDeclarator. Also, I think we should check if the right hand side operand is a function expression.
Otherwise, the following cases will result in false positives.
<script>
import { getContext, onMount } from "svelte"
const foo = getContext("foo")
onMount(() => {
foo()
})
</script>
parent = parent.parent | |
if ( | |
parent?.type === "VariableDeclaration" || | |
parent?.type === "FunctionDeclaration" | |
) { | |
const references = | |
parent.type === "VariableDeclaration" | |
? getReferences(parent.declarations[0].id) | |
: parent.id | |
? getReferences(parent.id) | |
: [] | |
for (const reference of references) { | |
if (reference.identifier?.parent?.type === "CallExpression") { | |
if ( | |
!visitedCallExpressions.includes(reference.identifier.parent) | |
) { | |
visitedCallExpressions.push(reference.identifier.parent) | |
doLint( | |
visitedCallExpressions, | |
contextCallExpression, | |
reference.identifier?.parent, | |
) | |
} | |
} | |
} | |
} else if (parent?.type === "ExpressionStatement") { | |
if (parent.expression.type !== "CallExpression") { | |
report(contextCallExpression) | |
} else if (lifeCycleReferences.includes(parent.expression)) { | |
report(contextCallExpression) | |
} | |
} | |
if ( | |
(parent?.type === "VariableDeclarator" && | |
parent.init && | |
(parent.init.type === "FunctionExpression" || | |
parent.init.type === "ArrowFunctionExpression") && | |
isInsideOf(parent.init, currentNode)) || | |
parent?.type === "FunctionDeclaration" | |
) { | |
const references = parent.id ? getReferences(parent.id) : [] | |
for (const reference of references) { | |
if (reference.identifier?.parent?.type === "CallExpression") { | |
if ( | |
!visitedCallExpressions.includes(reference.identifier.parent) | |
) { | |
visitedCallExpressions.push(reference.identifier.parent) | |
doLint( | |
visitedCallExpressions, | |
contextCallExpression, | |
reference.identifier?.parent, | |
) | |
} | |
} | |
} | |
} else if (parent?.type === "ExpressionStatement") { | |
if (parent.expression.type !== "CallExpression") { | |
report(contextCallExpression) | |
} else if (lifeCycleReferences.includes(parent.expression)) { | |
report(contextCallExpression) | |
} | |
} | |
parent = parent.parent |
The repository has been migrated to a monorepo. |
close: #448