Skip to content

Commit

Permalink
implement
Browse files Browse the repository at this point in the history
  • Loading branch information
baseballyama committed May 27, 2023
1 parent 89c4b89 commit 766cd04
Show file tree
Hide file tree
Showing 24 changed files with 441 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
| [svelte/require-store-callbacks-use-set-param](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-store-callbacks-use-set-param/) | store callbacks must use `set` param | |
| [svelte/require-store-reactive-access](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-store-reactive-access/) | disallow to use of the store itself as an operand. Need to use $ prefix or get function. | :wrench: |
| [svelte/valid-compile](https://sveltejs.github.io/eslint-plugin-svelte/rules/valid-compile/) | disallow warnings when compiling. | :star: |
| [svelte/valid-context-access](https://sveltejs.github.io/eslint-plugin-svelte/rules/valid-context-access/) | context functions must be called during component initialization. | |
| [svelte/valid-prop-names-in-kit-pages](https://sveltejs.github.io/eslint-plugin-svelte/rules/valid-prop-names-in-kit-pages/) | disallow props other than data or errors in Svelte Kit page components. | |

## Security Vulnerability
Expand Down
1 change: 1 addition & 0 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
| [svelte/require-store-callbacks-use-set-param](./rules/require-store-callbacks-use-set-param.md) | store callbacks must use `set` param | |
| [svelte/require-store-reactive-access](./rules/require-store-reactive-access.md) | disallow to use of the store itself as an operand. Need to use $ prefix or get function. | :wrench: |
| [svelte/valid-compile](./rules/valid-compile.md) | disallow warnings when compiling. | :star: |
| [svelte/valid-context-access](./rules/valid-context-access.md) | context functions must be called during component initialization. | |
| [svelte/valid-prop-names-in-kit-pages](./rules/valid-prop-names-in-kit-pages.md) | disallow props other than data or errors in Svelte Kit page components. | |

## Security Vulnerability
Expand Down
67 changes: 67 additions & 0 deletions docs/rules/valid-context-access.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
pageClass: "rule-details"
sidebarDepth: 0
title: "svelte/valid-context-access"
description: "context functions must be called during component initialization."
---

# svelte/valid-context-access

> context functions must be called during component initialization.
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> **_This rule has not been released yet._** </badge>

## :book: Rule Details

This rule reports where context API is called except during component initialization.

<ESLintCodeBlock>

<!--eslint-skip-->

```svelte
<script>
/* eslint svelte/valid-context-access: "error" */
import { setContext, onMount } from "svelte"
/** ✓ GOOD */
setContext("answer", 42)
;(() => {
setContext("answer", 42)
})()
const init = () => {
setContext("answer", 42)
}
init()
/** ✗ BAD */
const update = () => {
setContext("answer", 42)
}
onMount(() => {
update()
setContext("answer", 42)
})
</script>
```

</ESLintCodeBlock>

## :wrench: Options

Nothing.

## :books: Further Reading

- [Svelte - Docs > RUN TIME > svelte > setContext](https://svelte.dev/docs#run-time-svelte-setcontext)
- [Svelte - Docs > RUN TIME > svelte > getContext](https://svelte.dev/docs#run-time-svelte-getContext)
- [Svelte - Docs > RUN TIME > svelte > hasContext](https://svelte.dev/docs#run-time-svelte-hasContext)
- [Svelte - Docs > RUN TIME > svelte > getAllContexts](https://svelte.dev/docs#run-time-svelte-getAllContexts)

## :mag: Implementation

- [Rule source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/src/rules/valid-context-access.ts)
- [Test source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/tests/src/rules/valid-context-access.ts)
42 changes: 42 additions & 0 deletions src/rules/reference-helpers/svelte-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { TSESTree } from "@typescript-eslint/types"
import { ReferenceTracker } from "@eslint-community/eslint-utils"
import type { RuleContext } from "../../types"

type ContextName = "setContext" | "getContext" | "hasContext" | "getAllContexts"

/** Extract svelte's context API references */
export function* extractContextReferences(
context: RuleContext,
contextNames: ContextName[] = [
"setContext",
"getContext",
"hasContext",
"getAllContexts",
],
): Generator<{ node: TSESTree.CallExpression; name: string }, void> {
const referenceTracker = new ReferenceTracker(
context.getSourceCode().scopeManager.globalScope!,
)
for (const { node, path } of referenceTracker.iterateEsmReferences({
svelte: {
[ReferenceTracker.ESM]: true,
setContext: {
[ReferenceTracker.CALL]: contextNames.includes("setContext"),
},
getContext: {
[ReferenceTracker.CALL]: contextNames.includes("getContext"),
},
hasContext: {
[ReferenceTracker.CALL]: contextNames.includes("hasContext"),
},
getAllContexts: {
[ReferenceTracker.CALL]: contextNames.includes("getAllContexts"),
},
},
})) {
yield {
node: node as TSESTree.CallExpression,
name: path[path.length - 1],
}
}
}
113 changes: 113 additions & 0 deletions src/rules/valid-context-access.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { createRule } from "../utils"
import { extractContextReferences } from "./reference-helpers/svelte-context"
import type { TSESTree } from "@typescript-eslint/types"

export default createRule("valid-context-access", {
meta: {
docs: {
description:
"context functions must be called during component initialization.",
category: "Possible Errors",
// TODO Switch to recommended in the major version.
recommended: false,
},
schema: [],
messages: {
unexpected:
"Do not call {{function}} except during component initialization.",
},
type: "problem",
},
create(context) {
const scopeManager = context.getSourceCode().scopeManager
const toplevelScope =
scopeManager.globalScope?.childScopes.find(
(scope) => scope.type === "module",
) || scopeManager.globalScope

/** report ESLint error */
function report(node: TSESTree.CallExpression) {
context.report({
loc: node.loc,
messageId: "unexpected",
data: {
function:
node.callee.type === "Identifier"
? node.callee.name
: "context function",
},
})
}

/** Get nodes where the variable is used */
function getReferences(id: TSESTree.Identifier | TSESTree.BindingName) {
const variable = toplevelScope?.variables.find((v) => {
if (id.type === "Identifier") {
return v.identifiers.includes(id)
}
return false
})
if (variable) {
return variable.references.filter((r) => r.identifier !== id)
}
return []
}

/** Let's lint! */
function doLint(
visitedCallExpressions: TSESTree.CallExpression[],
contextCallExpression: TSESTree.CallExpression,
currentNode: TSESTree.CallExpression,
) {
let { parent } = currentNode
if (parent?.type !== "ExpressionStatement") {
report(contextCallExpression)
return
}
while (parent) {
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 (parent.expression.callee.type === "Identifier") {
report(contextCallExpression)
}
}
}
}

return {
Program() {
for (const { node } of extractContextReferences(context)) {
const visitedCallExpressions: TSESTree.CallExpression[] = []
doLint(visitedCallExpressions, node, node)
}
},
}
},
})
2 changes: 2 additions & 0 deletions src/utils/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import sortAttributes from "../rules/sort-attributes"
import spacedHtmlComment from "../rules/spaced-html-comment"
import system from "../rules/system"
import validCompile from "../rules/valid-compile"
import validContextAccess from "../rules/valid-context-access"
import validEachKey from "../rules/valid-each-key"
import validPropNamesInKitPages from "../rules/valid-prop-names-in-kit-pages"

Expand Down Expand Up @@ -116,6 +117,7 @@ export const rules = [
spacedHtmlComment,
system,
validCompile,
validContextAccess,
validEachKey,
validPropNamesInKitPages,
] as RuleModule[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
- message: Do not call setContext except during component initialization.
line: 11
column: 5
- message: Do not call getContext except during component initialization.
line: 12
column: 5
- message: Do not call hasContext except during component initialization.
line: 13
column: 5
- message: Do not call getAllContexts except during component initialization.
line: 14
column: 5
- message: Do not call setContext except during component initialization.
line: 18
column: 5
- message: Do not call getContext except during component initialization.
line: 19
column: 5
- message: Do not call hasContext except during component initialization.
line: 20
column: 5
- message: Do not call getAllContexts except during component initialization.
line: 21
column: 5
- message: Do not call setContext except during component initialization.
line: 25
column: 5
- message: Do not call getContext except during component initialization.
line: 26
column: 5
- message: Do not call hasContext except during component initialization.
line: 27
column: 5
- message: Do not call getAllContexts except during component initialization.
line: 28
column: 5
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<script>
import {
setContext,
getContext,
hasContext,
getAllContexts,
onMount,
} from "svelte"
const update1 = () => {
setContext("answer", 42)
getContext("answer")
hasContext("answer")
getAllContexts()
}
const update2 = function () {
setContext("answer", 42)
getContext("answer")
hasContext("answer")
getAllContexts()
}
function update3() {
setContext("answer", 42)
getContext("answer")
hasContext("answer")
getAllContexts()
}
onMount(() => {
update1()
update2()
update3()
})
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- message: Do not call setContext except during component initialization.
line: 5
column: 5
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script>
import { setContext, onMount } from "svelte"
onMount(() => {
setContext("answer", 42)
})
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- message: Do not call setContext except during component initialization.
line: 4
column: 5
suggestions: null
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script>
import { setContext, onMount } from "svelte"
const something = () => {
setContext("answer", 42)
}
something()
</script>

<button on:click={() => something()}>Click Me</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- message: Do not call setContext except during component initialization.
line: 4
column: 5
suggestions: null
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<script>
import { setContext, onMount } from "svelte"
const something = () => {
setContext("answer", 42)
}
something()
</script>

{#if true}
{@const foo = something()}
<button>Click Me</button>
{/if}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- message: Do not call setContext except during component initialization.
line: 4
column: 5
suggestions: null
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script>
import { setContext, onMount } from "svelte"
const something = () => {
setContext("answer", 42)
}
something()
</script>

{#if something()}
<button>Click Me</button>
{/if}
Loading

0 comments on commit 766cd04

Please sign in to comment.