Skip to content

add no-reactive-functions rule #206

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

Merged
merged 3 commits into from
Aug 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ These rules relate to better ways of doing things to help you avoid problems:
|:--------|:------------|:---|
| [svelte/button-has-type](https://ota-meshi.github.io/eslint-plugin-svelte/rules/button-has-type/) | disallow usage of button without an explicit type attribute | |
| [svelte/no-at-debug-tags](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-at-debug-tags/) | disallow the use of `{@debug}` | :star: |
| [svelte/no-reactive-functions](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-reactive-functions/) | It's not necessary to define functions in reactive statements | :bulb: |
| [svelte/no-reactive-literals](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-reactive-literals/) | Don't assign literal values in reactive statements | :bulb: |
| [svelte/no-unused-svelte-ignore](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-unused-svelte-ignore/) | disallow unused svelte-ignore comments | :star: |
| [svelte/no-useless-mustaches](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-useless-mustaches/) | disallow unnecessary mustache interpolations | :wrench: |
Expand Down
1 change: 1 addition & 0 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ These rules relate to better ways of doing things to help you avoid problems:
|:--------|:------------|:---|
| [svelte/button-has-type](./rules/button-has-type.md) | disallow usage of button without an explicit type attribute | |
| [svelte/no-at-debug-tags](./rules/no-at-debug-tags.md) | disallow the use of `{@debug}` | :star: |
| [svelte/no-reactive-functions](./rules/no-reactive-functions.md) | It's not necessary to define functions in reactive statements | :bulb: |
| [svelte/no-reactive-literals](./rules/no-reactive-literals.md) | Don't assign literal values in reactive statements | :bulb: |
| [svelte/no-unused-svelte-ignore](./rules/no-unused-svelte-ignore.md) | disallow unused svelte-ignore comments | :star: |
| [svelte/no-useless-mustaches](./rules/no-useless-mustaches.md) | disallow unnecessary mustache interpolations | :wrench: |
Expand Down
52 changes: 52 additions & 0 deletions docs/rules/no-reactive-functions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
pageClass: "rule-details"
sidebarDepth: 0
title: "svelte/no-reactive-functions"
description: "It's not necessary to define functions in reactive statements"
---

# svelte/no-reactive-functions

> It's not necessary to define functions in reactive statements

- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> **_This rule has not been released yet._** </badge>
- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).

## :book: Rule Details

This rule reports whenever a function is defined in a reactive statement. This isn't necessary, as each time the function is executed it'll already have access to the latest values necessary. Redefining the function in the reactive statement is just a waste of CPU cycles.

<ESLintCodeBlock>

<!--eslint-skip-->

```svelte
<script>
/* eslint svelte/no-reactive-functions: "error" */

/* ✓ GOOD */
const arrowFn = () => { /* ... */ }
const func = function() { /* ... */ }

/* ✗ BAD */
$: arrowFn = () => { /* ... */ }
$: func = function() { /* ... */ }
</script>
```

</ESLintCodeBlock>

## :wrench: Options

Nothing

## :heart: Compatibility

This rule was taken from [@tivac/eslint-plugin-svelte].
This rule is compatible with `@tivac/svelte/reactive-functions` rule.

[@tivac/eslint-plugin-svelte]: https://github.com/tivac/eslint-plugin-svelte/
## :mag: Implementation

- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/no-reactive-functions.ts)
- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/no-reactive-functions.ts)
66 changes: 66 additions & 0 deletions src/rules/no-reactive-functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { TSESTree } from "@typescript-eslint/types"
import type { AST } from "svelte-eslint-parser"
import { createRule } from "../utils"

export default createRule("no-reactive-functions", {
meta: {
docs: {
description:
"It's not necessary to define functions in reactive statements",
category: "Best Practices",
recommended: false,
},
hasSuggestions: true,
schema: [],
messages: {
noReactiveFns: `Do not create functions inside reactive statements unless absolutely necessary.`,
fixReactiveFns: `Move the function out of the reactive statement`,
},
type: "suggestion", // "problem", or "layout",
},
create(context) {
return {
// $: foo = () => { ... }
[`SvelteReactiveStatement > ExpressionStatement > AssignmentExpression > :function`](
node: TSESTree.ArrowFunctionExpression,
) {
// Move upwards to include the entire label
const parent = node.parent?.parent?.parent

if (!parent) {
return false
}

const source = context.getSourceCode()

return context.report({
node: parent,
loc: parent.loc,
messageId: "noReactiveFns",
suggest: [
{
messageId: "fixReactiveFns",
fix(fixer) {
const tokens = source.getFirstTokens(parent, {
includeComments: false,
count: 3,
})

const noExtraSpace = source.isSpaceBetweenTokens(
tokens[1] as AST.Token,
tokens[2] as AST.Token,
)

// Replace the entire reactive label with "const"
return fixer.replaceTextRange(
[tokens[0].range[0], tokens[1].range[1]],
noExtraSpace ? "const" : "const ",
)
},
},
],
})
},
}
},
})
2 changes: 2 additions & 0 deletions src/utils/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import noExtraReactiveCurlies from "../rules/no-extra-reactive-curlies"
import noInnerDeclarations from "../rules/no-inner-declarations"
import noNotFunctionHandler from "../rules/no-not-function-handler"
import noObjectInTextMustaches from "../rules/no-object-in-text-mustaches"
import noReactiveFunctions from "../rules/no-reactive-functions"
import noReactiveLiterals from "../rules/no-reactive-literals"
import noShorthandStylePropertyOverrides from "../rules/no-shorthand-style-property-overrides"
import noSpacesAroundEqualSignsInAttribute from "../rules/no-spaces-around-equal-signs-in-attribute"
Expand Down Expand Up @@ -51,6 +52,7 @@ export const rules = [
noInnerDeclarations,
noNotFunctionHandler,
noObjectInTextMustaches,
noReactiveFunctions,
noReactiveLiterals,
noShorthandStylePropertyOverrides,
noSpacesAroundEqualSignsInAttribute,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[
{
"message": "Do not create functions inside reactive statements unless absolutely necessary.",
"line": 3,
"column": 5,
"suggestions": [
{
"desc": "Move the function out of the reactive statement",
"messageId": "fixReactiveFns",
"output": "<!-- prettier-ignore -->\n<script>\n const arrow = () => {}\n $: fn = function() {}\n $:nospace = () => {}\n</script>\n"
}
]
},
{
"message": "Do not create functions inside reactive statements unless absolutely necessary.",
"line": 4,
"column": 5,
"suggestions": [
{
"desc": "Move the function out of the reactive statement",
"messageId": "fixReactiveFns",
"output": "<!-- prettier-ignore -->\n<script>\n $: arrow = () => {}\n const fn = function() {}\n $:nospace = () => {}\n</script>\n"
}
]
},
{
"message": "Do not create functions inside reactive statements unless absolutely necessary.",
"line": 5,
"column": 5,
"suggestions": [
{
"desc": "Move the function out of the reactive statement",
"messageId": "fixReactiveFns",
"output": "<!-- prettier-ignore -->\n<script>\n $: arrow = () => {}\n $: fn = function() {}\n const nospace = () => {}\n</script>\n"
}
]
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<!-- prettier-ignore -->
<script>
$: arrow = () => {}
$: fn = function() {}
$:nospace = () => {}
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<!-- prettier-ignore -->
<script>
const arrow = () => {}
const fn = function() { }
</script>
16 changes: 16 additions & 0 deletions tests/src/rules/no-reactive-functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { RuleTester } from "eslint"
import rule from "../../../src/rules/no-reactive-functions"
import { loadTestCases } from "../../utils/utils"

const tester = new RuleTester({
parserOptions: {
ecmaVersion: 2020,
sourceType: "module",
},
})

tester.run(
"no-reactive-functions",
rule as any,
loadTestCases("no-reactive-functions"),
)