Skip to content

Commit 1fe38d7

Browse files
moufmoufota-meshi
andauthored
feat: Adding a new no-ignored-unsubscribe rule. (#592)
Co-authored-by: Yosuke Ota <[email protected]>
1 parent 69e5c0e commit 1fe38d7

File tree

12 files changed

+138
-0
lines changed

12 files changed

+138
-0
lines changed

.changeset/hip-tips-smell.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-svelte": minor
3+
---
4+
5+
feat: add new `svelte/no-ignored-unsubscribe` rule.

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ These rules relate to better ways of doing things to help you avoid problems:
340340
| [svelte/block-lang](https://sveltejs.github.io/eslint-plugin-svelte/rules/block-lang/) | disallows the use of languages other than those specified in the configuration for the lang attribute of `<script>` and `<style>` blocks. | |
341341
| [svelte/button-has-type](https://sveltejs.github.io/eslint-plugin-svelte/rules/button-has-type/) | disallow usage of button without an explicit type attribute | |
342342
| [svelte/no-at-debug-tags](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-at-debug-tags/) | disallow the use of `{@debug}` | :star: |
343+
| [svelte/no-ignored-unsubscribe](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-ignored-unsubscribe/) | disallow ignoring the unsubscribe method returned by the `subscribe()` on Svelte stores. | |
343344
| [svelte/no-immutable-reactive-statements](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-immutable-reactive-statements/) | disallow reactive statements that don't reference reactive values. | |
344345
| [svelte/no-reactive-functions](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-reactive-functions/) | it's not necessary to define functions in reactive statements | :bulb: |
345346
| [svelte/no-reactive-literals](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-reactive-literals/) | don't assign literal values in reactive statements | :bulb: |

docs/rules.md

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ These rules relate to better ways of doing things to help you avoid problems:
5353
| [svelte/block-lang](./rules/block-lang.md) | disallows the use of languages other than those specified in the configuration for the lang attribute of `<script>` and `<style>` blocks. | |
5454
| [svelte/button-has-type](./rules/button-has-type.md) | disallow usage of button without an explicit type attribute | |
5555
| [svelte/no-at-debug-tags](./rules/no-at-debug-tags.md) | disallow the use of `{@debug}` | :star: |
56+
| [svelte/no-ignored-unsubscribe](./rules/no-ignored-unsubscribe.md) | disallow ignoring the unsubscribe method returned by the `subscribe()` on Svelte stores. | |
5657
| [svelte/no-immutable-reactive-statements](./rules/no-immutable-reactive-statements.md) | disallow reactive statements that don't reference reactive values. | |
5758
| [svelte/no-reactive-functions](./rules/no-reactive-functions.md) | it's not necessary to define functions in reactive statements | :bulb: |
5859
| [svelte/no-reactive-literals](./rules/no-reactive-literals.md) | don't assign literal values in reactive statements | :bulb: |

docs/rules/no-ignored-unsubscribe.md

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
pageClass: 'rule-details'
3+
sidebarDepth: 0
4+
title: 'svelte/no-ignored-unsubscribe'
5+
description: 'disallow ignoring the unsubscribe method returned by the `subscribe()` on Svelte stores.'
6+
---
7+
8+
# svelte/no-ignored-unsubscribe
9+
10+
> disallow ignoring the unsubscribe method returned by the `subscribe()` on Svelte stores.
11+
12+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> **_This rule has not been released yet._** </badge>
13+
14+
## :book: Rule Details
15+
16+
This rule fails if an "unsubscriber" returned by call to `subscribe()` is neither assigned to a variable or property or passed to a function.
17+
18+
One should always unsubscribe from a store when it is no longer needed. Otherwise, the subscription will remain active and constitute a **memory leak**.
19+
This rule helps to find such cases by ensuring that the unsubscriber (the return value from the store's `subscribe` method) is not ignored.
20+
21+
<ESLintCodeBlock>
22+
23+
<!--eslint-skip-->
24+
25+
```svelte
26+
<script>
27+
/* eslint svelte/no-ignored-unsubscribe: "error" */
28+
29+
import myStore from './my-stores';
30+
31+
/* ✓ GOOD */
32+
const unsubscribe = myStore.subscribe(() => {});
33+
34+
/* ✗ BAD */
35+
myStore.subscribe(() => {});
36+
</script>
37+
```
38+
39+
</ESLintCodeBlock>
40+
41+
## :wrench: Options
42+
43+
Nothing.
44+
45+
## :mag: Implementation
46+
47+
- [Rule source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/src/rules/no-ignored-unsubscribe.ts)
48+
- [Test source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/tests/src/rules/no-ignored-unsubscribe.ts)

src/rules/no-ignored-unsubscribe.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { TSESTree } from '@typescript-eslint/types';
2+
import { createRule } from '../utils';
3+
4+
export default createRule('no-ignored-unsubscribe', {
5+
meta: {
6+
docs: {
7+
description:
8+
'disallow ignoring the unsubscribe method returned by the `subscribe()` on Svelte stores.',
9+
category: 'Best Practices',
10+
recommended: false
11+
},
12+
fixable: undefined,
13+
hasSuggestions: false,
14+
messages: {
15+
forbidden: 'Ignoring returned value of the subscribe method is forbidden.'
16+
},
17+
schema: [],
18+
type: 'problem'
19+
},
20+
create: (context) => {
21+
return {
22+
"ExpressionStatement > CallExpression > MemberExpression.callee[property.name='subscribe']": (
23+
node: TSESTree.MemberExpression
24+
) => {
25+
context.report({
26+
messageId: 'forbidden',
27+
node: node.property
28+
});
29+
}
30+
};
31+
}
32+
});

src/utils/rules.ts

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import noDupeUseDirectives from '../rules/no-dupe-use-directives';
2727
import noDynamicSlotName from '../rules/no-dynamic-slot-name';
2828
import noExportLoadInSvelteModuleInKitPages from '../rules/no-export-load-in-svelte-module-in-kit-pages';
2929
import noExtraReactiveCurlies from '../rules/no-extra-reactive-curlies';
30+
import noIgnoredUnsubscribe from '../rules/no-ignored-unsubscribe';
3031
import noImmutableReactiveStatements from '../rules/no-immutable-reactive-statements';
3132
import noInnerDeclarations from '../rules/no-inner-declarations';
3233
import noNotFunctionHandler from '../rules/no-not-function-handler';
@@ -88,6 +89,7 @@ export const rules = [
8889
noDynamicSlotName,
8990
noExportLoadInSvelteModuleInKitPages,
9091
noExtraReactiveCurlies,
92+
noIgnoredUnsubscribe,
9193
noImmutableReactiveStatements,
9294
noInnerDeclarations,
9395
noNotFunctionHandler,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- message: Ignoring returned value of the subscribe method is forbidden.
2+
line: 6
3+
column: 6
4+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
import { writable } from 'svelte/store';
3+
4+
const foo = writable(0);
5+
6+
foo.subscribe(() => {
7+
console.log('foo changed');
8+
});
9+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
import { writable } from 'svelte/store';
3+
4+
const foo = writable(0);
5+
6+
const unsubscribe = foo.subscribe(() => {
7+
console.log('foo changed');
8+
});
9+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script>
2+
import { writable } from 'svelte/store';
3+
4+
const foo = writable(0);
5+
6+
const unsubscribers = [];
7+
unsubscribers.push(
8+
foo.subscribe(() => {
9+
console.log('foo changed');
10+
})
11+
);
12+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<script>
2+
a(foo.subscribe);
3+
</script>
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { RuleTester } from 'eslint';
2+
import rule from '../../../src/rules/no-ignored-unsubscribe';
3+
import { loadTestCases } from '../../utils/utils';
4+
5+
const tester = new RuleTester({
6+
parserOptions: {
7+
ecmaVersion: 2020,
8+
sourceType: 'module'
9+
}
10+
});
11+
12+
tester.run('no-ignored-unsubscribe', rule as any, loadTestCases('no-ignored-unsubscribe'));

0 commit comments

Comments
 (0)