-
-
Notifications
You must be signed in to change notification settings - Fork 51
feat: Add svelte/valid-context-access
rule
#480
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
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