From 535464b9d83c3ff9a6e7cf162829cba271d5e23a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Wed, 6 Aug 2025 00:11:38 +0200 Subject: [PATCH 1/8] test(prefer-svelte-reactivity): added tests for encapsulated local variables --- .../encapsulated-local-variables/_config.json | 3 +++ .../_requirements.json | 3 +++ .../encapsulated-arrow-function01-errors.yaml | 4 ++++ ...encapsulated-arrow-function01-input.svelte | 9 +++++++++ .../encapsulated-class01-errors.yaml | 4 ++++ .../encapsulated-class01-input.svelte | 14 ++++++++++++++ .../encapsulated-class02-errors.yaml | 4 ++++ .../encapsulated-class02-input.svelte | 19 +++++++++++++++++++ .../encapsulated-class03-errors.yaml | 4 ++++ .../encapsulated-class03-input.svelte | 14 ++++++++++++++ .../encapsulated-class04-errors.yaml | 4 ++++ .../encapsulated-class04-input.svelte | 19 +++++++++++++++++++ .../encapsulated-function01-errors.yaml | 4 ++++ .../encapsulated-function01-input.svelte | 9 +++++++++ .../_requirements.json | 3 +++ ...nencapsulated-arrow-function01-errors.yaml | 4 ++++ ...encapsulated-arrow-function01-input.svelte | 8 ++++++++ ...nencapsulated-arrow-function02-errors.yaml | 4 ++++ ...encapsulated-arrow-function02-input.svelte | 7 +++++++ .../unencapsulated-class01-errors.yaml | 4 ++++ .../unencapsulated-class01-input.svelte | 13 +++++++++++++ .../unencapsulated-class02-errors.yaml | 4 ++++ .../unencapsulated-class02-input.svelte | 17 +++++++++++++++++ .../unencapsulated-class03-errors.yaml | 4 ++++ .../unencapsulated-class03-input.svelte | 13 +++++++++++++ .../unencapsulated-class04-errors.yaml | 4 ++++ .../unencapsulated-class04-input.svelte | 17 +++++++++++++++++ .../unencapsulated-class05-errors.yaml | 4 ++++ .../unencapsulated-class05-input.svelte | 13 +++++++++++++ .../unencapsulated-class06-errors.yaml | 4 ++++ .../unencapsulated-class06-input.svelte | 13 +++++++++++++ .../unencapsulated-class07-errors.yaml | 4 ++++ .../unencapsulated-class07-input.svelte | 12 ++++++++++++ .../unencapsulated-class08-errors.yaml | 4 ++++ .../unencapsulated-class08-input.svelte | 11 +++++++++++ .../unencapsulated-function01-errors.yaml | 4 ++++ .../unencapsulated-function01-input.svelte | 8 ++++++++ .../unencapsulated-function02-errors.yaml | 4 ++++ .../unencapsulated-function02-input.svelte | 7 +++++++ ...encapsulated-arrow-function01-input.svelte | 9 +++++++++ .../valid/encapsulated-class01-input.svelte | 14 ++++++++++++++ .../valid/encapsulated-class02-input.svelte | 19 +++++++++++++++++++ .../valid/encapsulated-class03-input.svelte | 14 ++++++++++++++ .../valid/encapsulated-class04-input.svelte | 19 +++++++++++++++++++ .../encapsulated-function01-input.svelte | 9 +++++++++ 45 files changed, 388 insertions(+) create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/_config.json create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/_requirements.json create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-arrow-function01-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-arrow-function01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class01-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class02-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class02-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class03-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class03-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class04-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class04-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-function01-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-function01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/_requirements.json create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-arrow-function01-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-arrow-function01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-arrow-function02-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-arrow-function02-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class01-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class02-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class02-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class03-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class03-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class04-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class04-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class05-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class05-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class06-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class06-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class07-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class07-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class08-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class08-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-function01-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-function01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-function02-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-function02-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-arrow-function01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-class01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-class02-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-class03-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-class04-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-function01-input.svelte diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/_config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/_config.json new file mode 100644 index 000000000..1f4c02173 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/_config.json @@ -0,0 +1,3 @@ +{ + "options": [{ "ignoreEncapsulatedLocalVariables": false }] +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/_requirements.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/_requirements.json new file mode 100644 index 000000000..498661308 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/_requirements.json @@ -0,0 +1,3 @@ +{ + "svelte": ">=5.0.0" +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-arrow-function01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-arrow-function01-errors.yaml new file mode 100644 index 000000000..261288a54 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-arrow-function01-errors.yaml @@ -0,0 +1,4 @@ +- message: Found a mutable instance of the built-in Set class. Use SvelteSet instead. + line: 3 + column: 20 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-arrow-function01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-arrow-function01-input.svelte new file mode 100644 index 000000000..b1fddc706 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-arrow-function01-input.svelte @@ -0,0 +1,9 @@ + + +{fn()} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class01-errors.yaml new file mode 100644 index 000000000..4356fa0a3 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class01-errors.yaml @@ -0,0 +1,4 @@ +- message: Found a mutable instance of the built-in Set class. Use SvelteSet instead. + line: 3 + column: 15 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class01-input.svelte new file mode 100644 index 000000000..a773d09dc --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class01-input.svelte @@ -0,0 +1,14 @@ + + +{a.fn()} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class02-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class02-errors.yaml new file mode 100644 index 000000000..4356fa0a3 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class02-errors.yaml @@ -0,0 +1,4 @@ +- message: Found a mutable instance of the built-in Set class. Use SvelteSet instead. + line: 3 + column: 15 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class02-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class02-input.svelte new file mode 100644 index 000000000..6c220ac86 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class02-input.svelte @@ -0,0 +1,19 @@ + + +{a.fn2()} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class03-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class03-errors.yaml new file mode 100644 index 000000000..eafa9d96c --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class03-errors.yaml @@ -0,0 +1,4 @@ +- message: Found a mutable instance of the built-in Set class. Use SvelteSet instead. + line: 3 + column: 22 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class03-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class03-input.svelte new file mode 100644 index 000000000..20109b36e --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class03-input.svelte @@ -0,0 +1,14 @@ + + +{a.fn()} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class04-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class04-errors.yaml new file mode 100644 index 000000000..eafa9d96c --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class04-errors.yaml @@ -0,0 +1,4 @@ +- message: Found a mutable instance of the built-in Set class. Use SvelteSet instead. + line: 3 + column: 22 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class04-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class04-input.svelte new file mode 100644 index 000000000..285546187 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-class04-input.svelte @@ -0,0 +1,19 @@ + + +{a.fn2()} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-function01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-function01-errors.yaml new file mode 100644 index 000000000..261288a54 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-function01-errors.yaml @@ -0,0 +1,4 @@ +- message: Found a mutable instance of the built-in Set class. Use SvelteSet instead. + line: 3 + column: 20 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-function01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-function01-input.svelte new file mode 100644 index 000000000..1925b4252 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/encapsulated-local-variables/encapsulated-function01-input.svelte @@ -0,0 +1,9 @@ + + +{fn()} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/_requirements.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/_requirements.json new file mode 100644 index 000000000..498661308 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/_requirements.json @@ -0,0 +1,3 @@ +{ + "svelte": ">=5.0.0" +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-arrow-function01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-arrow-function01-errors.yaml new file mode 100644 index 000000000..261288a54 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-arrow-function01-errors.yaml @@ -0,0 +1,4 @@ +- message: Found a mutable instance of the built-in Set class. Use SvelteSet instead. + line: 3 + column: 20 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-arrow-function01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-arrow-function01-input.svelte new file mode 100644 index 000000000..0dafc5973 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-arrow-function01-input.svelte @@ -0,0 +1,8 @@ + + +{fn().has(42)} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-arrow-function02-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-arrow-function02-errors.yaml new file mode 100644 index 000000000..83a23dffc --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-arrow-function02-errors.yaml @@ -0,0 +1,4 @@ +- message: Found a mutable instance of the built-in Set class. Use SvelteSet instead. + line: 3 + column: 10 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-arrow-function02-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-arrow-function02-input.svelte new file mode 100644 index 000000000..9d43ca045 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-arrow-function02-input.svelte @@ -0,0 +1,7 @@ + + +{fn().has(42)} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class01-errors.yaml new file mode 100644 index 000000000..4356fa0a3 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class01-errors.yaml @@ -0,0 +1,4 @@ +- message: Found a mutable instance of the built-in Set class. Use SvelteSet instead. + line: 3 + column: 15 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class01-input.svelte new file mode 100644 index 000000000..f6541953a --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class01-input.svelte @@ -0,0 +1,13 @@ + + +{a.fn().has(42)} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class02-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class02-errors.yaml new file mode 100644 index 000000000..4356fa0a3 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class02-errors.yaml @@ -0,0 +1,4 @@ +- message: Found a mutable instance of the built-in Set class. Use SvelteSet instead. + line: 3 + column: 15 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class02-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class02-input.svelte new file mode 100644 index 000000000..3875bd39d --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class02-input.svelte @@ -0,0 +1,17 @@ + + +{a.fn2().has(42)} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class03-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class03-errors.yaml new file mode 100644 index 000000000..eafa9d96c --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class03-errors.yaml @@ -0,0 +1,4 @@ +- message: Found a mutable instance of the built-in Set class. Use SvelteSet instead. + line: 3 + column: 22 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class03-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class03-input.svelte new file mode 100644 index 000000000..1ae19334b --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class03-input.svelte @@ -0,0 +1,13 @@ + + +{a.fn().has(42)} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class04-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class04-errors.yaml new file mode 100644 index 000000000..eafa9d96c --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class04-errors.yaml @@ -0,0 +1,4 @@ +- message: Found a mutable instance of the built-in Set class. Use SvelteSet instead. + line: 3 + column: 22 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class04-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class04-input.svelte new file mode 100644 index 000000000..cf0c955d9 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class04-input.svelte @@ -0,0 +1,17 @@ + + +{a.fn2().has(42)} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class05-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class05-errors.yaml new file mode 100644 index 000000000..3123a888a --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class05-errors.yaml @@ -0,0 +1,4 @@ +- message: Found a mutable instance of the built-in Set class. Use SvelteSet instead. + line: 3 + column: 14 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class05-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class05-input.svelte new file mode 100644 index 000000000..f781683fb --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class05-input.svelte @@ -0,0 +1,13 @@ + + +{a.fn()} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class06-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class06-errors.yaml new file mode 100644 index 000000000..5e8895329 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class06-errors.yaml @@ -0,0 +1,4 @@ +- message: Found a mutable instance of the built-in Set class. Use SvelteSet instead. + line: 3 + column: 21 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class06-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class06-input.svelte new file mode 100644 index 000000000..2cc27ea56 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class06-input.svelte @@ -0,0 +1,13 @@ + + +{a.fn()} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class07-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class07-errors.yaml new file mode 100644 index 000000000..0fd05822b --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class07-errors.yaml @@ -0,0 +1,4 @@ +- message: Found a mutable instance of the built-in Set class. Use SvelteSet instead. + line: 4 + column: 18 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class07-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class07-input.svelte new file mode 100644 index 000000000..51437ede6 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class07-input.svelte @@ -0,0 +1,12 @@ + + +{a.fn().has(42)} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class08-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class08-errors.yaml new file mode 100644 index 000000000..f01de6b75 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class08-errors.yaml @@ -0,0 +1,4 @@ +- message: Found a mutable instance of the built-in Set class. Use SvelteSet instead. + line: 4 + column: 11 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class08-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class08-input.svelte new file mode 100644 index 000000000..90186aa3a --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-class08-input.svelte @@ -0,0 +1,11 @@ + + +{a.fn().has(42)} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-function01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-function01-errors.yaml new file mode 100644 index 000000000..261288a54 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-function01-errors.yaml @@ -0,0 +1,4 @@ +- message: Found a mutable instance of the built-in Set class. Use SvelteSet instead. + line: 3 + column: 20 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-function01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-function01-input.svelte new file mode 100644 index 000000000..2bb763a44 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-function01-input.svelte @@ -0,0 +1,8 @@ + + +{fn().has(42)} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-function02-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-function02-errors.yaml new file mode 100644 index 000000000..83a23dffc --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-function02-errors.yaml @@ -0,0 +1,4 @@ +- message: Found a mutable instance of the built-in Set class. Use SvelteSet instead. + line: 3 + column: 10 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-function02-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-function02-input.svelte new file mode 100644 index 000000000..5601de190 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/invalid/unencapsulated-local-variables/unencapsulated-function02-input.svelte @@ -0,0 +1,7 @@ + + +{fn().has(42)} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-arrow-function01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-arrow-function01-input.svelte new file mode 100644 index 000000000..b1fddc706 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-arrow-function01-input.svelte @@ -0,0 +1,9 @@ + + +{fn()} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-class01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-class01-input.svelte new file mode 100644 index 000000000..a773d09dc --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-class01-input.svelte @@ -0,0 +1,14 @@ + + +{a.fn()} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-class02-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-class02-input.svelte new file mode 100644 index 000000000..6c220ac86 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-class02-input.svelte @@ -0,0 +1,19 @@ + + +{a.fn2()} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-class03-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-class03-input.svelte new file mode 100644 index 000000000..20109b36e --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-class03-input.svelte @@ -0,0 +1,14 @@ + + +{a.fn()} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-class04-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-class04-input.svelte new file mode 100644 index 000000000..285546187 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-class04-input.svelte @@ -0,0 +1,19 @@ + + +{a.fn2()} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-function01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-function01-input.svelte new file mode 100644 index 000000000..1925b4252 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-svelte-reactivity/valid/encapsulated-function01-input.svelte @@ -0,0 +1,9 @@ + + +{fn()} From 04c85adee941b620a52c5aaf903aed99d0c81115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Wed, 6 Aug 2025 15:23:29 +0200 Subject: [PATCH 2/8] feat(prefer-svelte-reactivity): reporting returned variables --- .changeset/lovely-moments-kneel.md | 5 ++ .../src/rules/prefer-svelte-reactivity.ts | 63 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 .changeset/lovely-moments-kneel.md diff --git a/.changeset/lovely-moments-kneel.md b/.changeset/lovely-moments-kneel.md new file mode 100644 index 000000000..b05d01385 --- /dev/null +++ b/.changeset/lovely-moments-kneel.md @@ -0,0 +1,5 @@ +--- +'eslint-plugin-svelte': minor +--- + +feat(prefer-svelte-reactivity): reporting returned variables diff --git a/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts b/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts index 7118b054d..0506e1264 100644 --- a/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts +++ b/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts @@ -31,6 +31,10 @@ export default createRule('prefer-svelte-reactivity', { ] }, create(context) { + const returnedVariables: Map< + TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration, + TSESTree.VariableDeclarator[] + > = new Map(); const exportedVars: TSESTree.Node[] = []; return { ...(getSvelteContext(context)?.svelteFileType === '.svelte.[js|ts]' && { @@ -59,6 +63,28 @@ export default createRule('prefer-svelte-reactivity', { } } }), + Identifier(node) { + const enclosingReturn = findEnclosingReturn(node); + if (enclosingReturn === null) { + return; + } + const enclosingFunction = findEnclosingFunction(enclosingReturn); + if (enclosingFunction === null) { + return; + } + const variable = findVariable(context, node); + if ( + variable === null || + variable.identifiers.length < 1 || + variable.identifiers[0].parent.type !== 'VariableDeclarator' + ) { + return; + } + if (!returnedVariables.has(enclosingFunction)) { + returnedVariables.set(enclosingFunction, []); + } + returnedVariables.get(enclosingFunction)?.push(variable.identifiers[0].parent); + }, 'Program:exit'() { const referenceTracker = new ReferenceTracker(context.sourceCode.scopeManager.globalScope!); for (const { node, path } of referenceTracker.iterateGlobalReferences({ @@ -96,6 +122,20 @@ export default createRule('prefer-svelte-reactivity', { }); } } + for (const returnedVar of Array.from(returnedVariables.values()).flat()) { + if (isIn(node, returnedVar)) { + context.report({ + messageId, + node + }); + } + } + if (findEnclosingReturn(node) !== null) { + context.report({ + messageId, + node + }); + } if (path[0] === 'Date' && isDateMutable(referenceTracker, node as TSESTree.Expression)) { context.report({ messageId: 'mutableDateUsed', @@ -135,6 +175,29 @@ export default createRule('prefer-svelte-reactivity', { } }); +function findAncestorOfTypes( + node: TSESTree.Node, + types: string[] +): (TSESTree.Node & { type: T }) | null { + if (types.includes(node.type)) { + return node as TSESTree.Node & { type: T }; + } + if (node.parent === undefined || node.parent === null) { + return null; + } + return findAncestorOfTypes(node.parent, types); +} + +function findEnclosingReturn(node: TSESTree.Node): TSESTree.ReturnStatement | null { + return findAncestorOfTypes(node, ['ReturnStatement']); +} + +function findEnclosingFunction( + node: TSESTree.Node +): TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration | null { + return findAncestorOfTypes(node, ['ArrowFunctionExpression', 'FunctionDeclaration']); +} + function isDateMutable(referenceTracker: ReferenceTracker, ctorNode: TSESTree.Expression): boolean { return !referenceTracker .iteratePropertyReferences(ctorNode, { From 9538681838b064c1a7c364c0eb41de64b68bc79e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Wed, 6 Aug 2025 18:31:48 +0200 Subject: [PATCH 3/8] feat(prefer-svelte-reactivity): reporting public properties --- .changeset/lovely-carpets-clean.md | 5 +++++ .../src/rules/prefer-svelte-reactivity.ts | 17 ++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 .changeset/lovely-carpets-clean.md diff --git a/.changeset/lovely-carpets-clean.md b/.changeset/lovely-carpets-clean.md new file mode 100644 index 000000000..1eea7e559 --- /dev/null +++ b/.changeset/lovely-carpets-clean.md @@ -0,0 +1,5 @@ +--- +'eslint-plugin-svelte': minor +--- + +feat(prefer-svelte-reactivity): reporting public properties diff --git a/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts b/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts index 0506e1264..73e743a13 100644 --- a/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts +++ b/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts @@ -130,7 +130,10 @@ export default createRule('prefer-svelte-reactivity', { }); } } - if (findEnclosingReturn(node) !== null) { + if ( + findEnclosingReturn(node) !== null || + findEnclosingPropertyDefinition(node)?.accessibility === 'public' + ) { context.report({ messageId, node @@ -188,16 +191,20 @@ function findAncestorOfTypes( return findAncestorOfTypes(node.parent, types); } -function findEnclosingReturn(node: TSESTree.Node): TSESTree.ReturnStatement | null { - return findAncestorOfTypes(node, ['ReturnStatement']); -} - function findEnclosingFunction( node: TSESTree.Node ): TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration | null { return findAncestorOfTypes(node, ['ArrowFunctionExpression', 'FunctionDeclaration']); } +function findEnclosingPropertyDefinition(node: TSESTree.Node): TSESTree.PropertyDefinition | null { + return findAncestorOfTypes(node, ['PropertyDefinition']); +} + +function findEnclosingReturn(node: TSESTree.Node): TSESTree.ReturnStatement | null { + return findAncestorOfTypes(node, ['ReturnStatement']); +} + function isDateMutable(referenceTracker: ReferenceTracker, ctorNode: TSESTree.Expression): boolean { return !referenceTracker .iteratePropertyReferences(ctorNode, { From f5bcfe2ba03d06c870f7aae20bd36e7b5ccf47d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Wed, 6 Aug 2025 16:16:19 +0200 Subject: [PATCH 4/8] feat(prefer-svelte-reactivity): ignoring variables encapsulated in functions --- .changeset/long-ghosts-prove.md | 5 +++ docs/rules/prefer-svelte-reactivity.md | 13 ++++++- .../eslint-plugin-svelte/src/rule-types.ts | 6 +++- .../src/rules/prefer-svelte-reactivity.ts | 34 ++++++++++++++++++- 4 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 .changeset/long-ghosts-prove.md diff --git a/.changeset/long-ghosts-prove.md b/.changeset/long-ghosts-prove.md new file mode 100644 index 000000000..da44ed049 --- /dev/null +++ b/.changeset/long-ghosts-prove.md @@ -0,0 +1,5 @@ +--- +'eslint-plugin-svelte': minor +--- + +feat(prefer-svelte-reactivity): ignoring variables encapsulated in functions diff --git a/docs/rules/prefer-svelte-reactivity.md b/docs/rules/prefer-svelte-reactivity.md index 4a5edf986..22b191ba7 100644 --- a/docs/rules/prefer-svelte-reactivity.md +++ b/docs/rules/prefer-svelte-reactivity.md @@ -105,7 +105,18 @@ export default e; ## :wrench: Options -Nothing. +```json +{ + "svelte/prefer-svelte-reactivity": [ + "error", + { + "ignoreEncapsulatedLocalVariables": true + } + ] +} +``` + +- `ignoreEncapsulatedLocalVariables` ... Whether to ignore variables that are defined inside a function and aren't returned, thus being encapsulated in the function. Default `true`. ## :books: Further Reading diff --git a/packages/eslint-plugin-svelte/src/rule-types.ts b/packages/eslint-plugin-svelte/src/rule-types.ts index f58b636f9..e635232dd 100644 --- a/packages/eslint-plugin-svelte/src/rule-types.ts +++ b/packages/eslint-plugin-svelte/src/rule-types.ts @@ -326,7 +326,7 @@ export interface RuleOptions { * disallow using mutable instances of built-in classes where a reactive alternative is provided by svelte/reactivity * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-svelte-reactivity/ */ - 'svelte/prefer-svelte-reactivity'?: Linter.RuleEntry<[]> + 'svelte/prefer-svelte-reactivity'?: Linter.RuleEntry /** * Prefer using writable $derived instead of $state and $effect * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-writable-derived/ @@ -593,6 +593,10 @@ type SveltePreferConst = []|[{ excludedRunes?: string[] [k: string]: unknown | undefined }] +// ----- svelte/prefer-svelte-reactivity ----- +type SveltePreferSvelteReactivity = []|[{ + ignoreEncapsulatedLocalVariables?: boolean +}] // ----- svelte/require-event-prefix ----- type SvelteRequireEventPrefix = []|[{ checkAsyncFunctions?: boolean diff --git a/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts b/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts index 73e743a13..62f83d66a 100644 --- a/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts +++ b/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts @@ -12,7 +12,18 @@ export default createRule('prefer-svelte-reactivity', { category: 'Possible Errors', recommended: true }, - schema: [], + schema: [ + { + type: 'object', + properties: { + ignoreEncapsulatedLocalVariables: { + type: 'boolean', + default: true + } + }, + additionalProperties: false + } + ], messages: { mutableDateUsed: 'Found a mutable instance of the built-in Date class. Use SvelteDate instead.', @@ -31,6 +42,8 @@ export default createRule('prefer-svelte-reactivity', { ] }, create(context) { + const ignoreEncapsulatedLocalVariables = + context.options[0]?.ignoreEncapsulatedLocalVariables ?? true; const returnedVariables: Map< TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration, TSESTree.VariableDeclarator[] @@ -139,6 +152,9 @@ export default createRule('prefer-svelte-reactivity', { node }); } + if (ignoreEncapsulatedLocalVariables && isLocalVarEncapsulated(returnedVariables, node)) { + continue; + } if (path[0] === 'Date' && isDateMutable(referenceTracker, node as TSESTree.Expression)) { context.report({ messageId: 'mutableDateUsed', @@ -205,6 +221,22 @@ function findEnclosingReturn(node: TSESTree.Node): TSESTree.ReturnStatement | nu return findAncestorOfTypes(node, ['ReturnStatement']); } +function isLocalVarEncapsulated( + returnedVariables: Map< + TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration, + TSESTree.VariableDeclarator[] + >, + node: TSESTree.Node +): boolean { + const enclosingFunction = findEnclosingFunction(node); + if (enclosingFunction === null) { + return false; + } + return ( + returnedVariables.get(enclosingFunction)?.some((variable) => isIn(node, variable)) !== true + ); +} + function isDateMutable(referenceTracker: ReferenceTracker, ctorNode: TSESTree.Expression): boolean { return !referenceTracker .iteratePropertyReferences(ctorNode, { From 6241c489778b2e978042bafd7d93f6fca4f08f41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Wed, 6 Aug 2025 19:31:33 +0200 Subject: [PATCH 5/8] feat(prefer-svelte-reactivity): ignoring variables encapsulated in classes --- .../src/rules/prefer-svelte-reactivity.ts | 107 ++++++++++++++---- 1 file changed, 84 insertions(+), 23 deletions(-) diff --git a/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts b/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts index 62f83d66a..5d53a9587 100644 --- a/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts +++ b/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts @@ -4,6 +4,13 @@ import type { TSESTree } from '@typescript-eslint/types'; import { findVariable, isIn } from '../utils/ast-utils.js'; import { getSvelteContext } from '../utils/svelte-context.js'; +type FunctionLike = + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionDeclaration + | TSESTree.MethodDefinition; + +type VariableLike = TSESTree.VariableDeclarator | TSESTree.PropertyDefinition; + export default createRule('prefer-svelte-reactivity', { meta: { docs: { @@ -44,10 +51,7 @@ export default createRule('prefer-svelte-reactivity', { create(context) { const ignoreEncapsulatedLocalVariables = context.options[0]?.ignoreEncapsulatedLocalVariables ?? true; - const returnedVariables: Map< - TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration, - TSESTree.VariableDeclarator[] - > = new Map(); + const returnedVariables: Map = new Map(); const exportedVars: TSESTree.Node[] = []; return { ...(getSvelteContext(context)?.svelteFileType === '.svelte.[js|ts]' && { @@ -85,18 +89,35 @@ export default createRule('prefer-svelte-reactivity', { if (enclosingFunction === null) { return; } - const variable = findVariable(context, node); - if ( - variable === null || - variable.identifiers.length < 1 || - variable.identifiers[0].parent.type !== 'VariableDeclarator' - ) { + let variableDeclaration = null; + if (node.parent.type === 'MemberExpression') { + const enclosingClassBody = findEnclosingClassBody(node); + for (const classElement of enclosingClassBody?.body ?? []) { + if ( + classElement.type === 'PropertyDefinition' && + classElement.key.type === 'Identifier' && + node.name === classElement.key.name + ) { + variableDeclaration = classElement; + } + } + } else { + const variable = findVariable(context, node); + if ( + variable !== null && + variable.identifiers.length > 0 && + variable.identifiers[0].parent.type === 'VariableDeclarator' + ) { + variableDeclaration = variable.identifiers[0].parent; + } + } + if (variableDeclaration === null) { return; } if (!returnedVariables.has(enclosingFunction)) { returnedVariables.set(enclosingFunction, []); } - returnedVariables.get(enclosingFunction)?.push(variable.identifiers[0].parent); + returnedVariables.get(enclosingFunction)?.push(variableDeclaration); }, 'Program:exit'() { const referenceTracker = new ReferenceTracker(context.sourceCode.scopeManager.globalScope!); @@ -135,17 +156,25 @@ export default createRule('prefer-svelte-reactivity', { }); } } - for (const returnedVar of Array.from(returnedVariables.values()).flat()) { - if (isIn(node, returnedVar)) { - context.report({ - messageId, - node - }); + for (const [fn, fnReturnVars] of returnedVariables.entries()) { + for (const returnedVar of fnReturnVars) { + if (fn.type === 'MethodDefinition' && returnedVar.type === 'PropertyDefinition') { + continue; + } + if (isIn(node, returnedVar)) { + context.report({ + messageId, + node + }); + } } } + const enclosingPropertyDefinition = findEnclosingPropertyDefinition(node); if ( findEnclosingReturn(node) !== null || - findEnclosingPropertyDefinition(node)?.accessibility === 'public' + (enclosingPropertyDefinition !== null && + (!ignoreEncapsulatedLocalVariables || + !isPropertyEncapsulated(enclosingPropertyDefinition, returnedVariables))) ) { context.report({ messageId, @@ -207,10 +236,18 @@ function findAncestorOfTypes( return findAncestorOfTypes(node.parent, types); } +function findEnclosingClassBody(node: TSESTree.Node): TSESTree.ClassBody | null { + return findAncestorOfTypes(node, ['ClassBody']); +} + function findEnclosingFunction( node: TSESTree.Node ): TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration | null { - return findAncestorOfTypes(node, ['ArrowFunctionExpression', 'FunctionDeclaration']); + return findAncestorOfTypes(node, [ + 'ArrowFunctionExpression', + 'FunctionDeclaration', + 'MethodDefinition' + ]); } function findEnclosingPropertyDefinition(node: TSESTree.Node): TSESTree.PropertyDefinition | null { @@ -222,10 +259,7 @@ function findEnclosingReturn(node: TSESTree.Node): TSESTree.ReturnStatement | nu } function isLocalVarEncapsulated( - returnedVariables: Map< - TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration, - TSESTree.VariableDeclarator[] - >, + returnedVariables: Map, node: TSESTree.Node ): boolean { const enclosingFunction = findEnclosingFunction(node); @@ -237,6 +271,33 @@ function isLocalVarEncapsulated( ); } +function methodReturnsProperty( + method: TSESTree.MethodDefinition, + property: TSESTree.PropertyDefinition, + returnedVariables: Map +): boolean { + return returnedVariables.get(method)?.includes(property) ?? false; +} + +function isPropertyEncapsulated( + node: TSESTree.PropertyDefinition, + returnedVariables: Map +): boolean { + if (node.accessibility === 'public') { + return false; + } + for (const classElement of node.parent.body) { + if ( + classElement.type === 'MethodDefinition' && + classElement.accessibility === 'public' && + methodReturnsProperty(classElement, node, returnedVariables) + ) { + return false; + } + } + return true; +} + function isDateMutable(referenceTracker: ReferenceTracker, ctorNode: TSESTree.Expression): boolean { return !referenceTracker .iteratePropertyReferences(ctorNode, { From 328e2b59b0affb31fec509d23fb594308bcde2d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Wed, 6 Aug 2025 21:18:10 +0200 Subject: [PATCH 6/8] feat(prefer-svelte-reactivity): tracking variables through function calls --- .../src/rules/prefer-svelte-reactivity.ts | 63 +++++++++++++++---- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts b/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts index 5d53a9587..10da4af7e 100644 --- a/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts +++ b/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts @@ -51,6 +51,7 @@ export default createRule('prefer-svelte-reactivity', { create(context) { const ignoreEncapsulatedLocalVariables = context.options[0]?.ignoreEncapsulatedLocalVariables ?? true; + const returnedFunctionCalls: Map = new Map(); const returnedVariables: Map = new Map(); const exportedVars: TSESTree.Node[] = []; return { @@ -81,6 +82,29 @@ export default createRule('prefer-svelte-reactivity', { } }), Identifier(node) { + function recordVariable(enclosingFunction: FunctionLike, variable: VariableLike): void { + if (variable === null) { + return; + } + if (!returnedVariables.has(enclosingFunction)) { + returnedVariables.set(enclosingFunction, []); + } + returnedVariables.get(enclosingFunction)?.push(variable); + } + + function recordFunctionCall( + enclosingFunction: FunctionLike, + functionCall: TSESTree.MethodDefinition + ): void { + if (functionCall === null) { + return; + } + if (!returnedFunctionCalls.has(enclosingFunction)) { + returnedFunctionCalls.set(enclosingFunction, []); + } + returnedFunctionCalls.get(enclosingFunction)?.push(functionCall); + } + const enclosingReturn = findEnclosingReturn(node); if (enclosingReturn === null) { return; @@ -89,7 +113,6 @@ export default createRule('prefer-svelte-reactivity', { if (enclosingFunction === null) { return; } - let variableDeclaration = null; if (node.parent.type === 'MemberExpression') { const enclosingClassBody = findEnclosingClassBody(node); for (const classElement of enclosingClassBody?.body ?? []) { @@ -98,7 +121,14 @@ export default createRule('prefer-svelte-reactivity', { classElement.key.type === 'Identifier' && node.name === classElement.key.name ) { - variableDeclaration = classElement; + recordVariable(enclosingFunction, classElement); + } + if ( + classElement.type === 'MethodDefinition' && + classElement.key.type === 'Identifier' && + node.name === classElement.key.name + ) { + recordFunctionCall(enclosingFunction, classElement); } } } else { @@ -108,16 +138,9 @@ export default createRule('prefer-svelte-reactivity', { variable.identifiers.length > 0 && variable.identifiers[0].parent.type === 'VariableDeclarator' ) { - variableDeclaration = variable.identifiers[0].parent; + recordVariable(enclosingFunction, variable.identifiers[0].parent); } } - if (variableDeclaration === null) { - return; - } - if (!returnedVariables.has(enclosingFunction)) { - returnedVariables.set(enclosingFunction, []); - } - returnedVariables.get(enclosingFunction)?.push(variableDeclaration); }, 'Program:exit'() { const referenceTracker = new ReferenceTracker(context.sourceCode.scopeManager.globalScope!); @@ -174,7 +197,11 @@ export default createRule('prefer-svelte-reactivity', { findEnclosingReturn(node) !== null || (enclosingPropertyDefinition !== null && (!ignoreEncapsulatedLocalVariables || - !isPropertyEncapsulated(enclosingPropertyDefinition, returnedVariables))) + !isPropertyEncapsulated( + enclosingPropertyDefinition, + returnedFunctionCalls, + returnedVariables + ))) ) { context.report({ messageId, @@ -274,13 +301,23 @@ function isLocalVarEncapsulated( function methodReturnsProperty( method: TSESTree.MethodDefinition, property: TSESTree.PropertyDefinition, + returnedFunctionCalls: Map, returnedVariables: Map ): boolean { - return returnedVariables.get(method)?.includes(property) ?? false; + return ( + (returnedVariables.get(method)?.includes(property) ?? false) || + (returnedFunctionCalls + .get(method) + ?.some((calledFn) => + methodReturnsProperty(calledFn, property, returnedFunctionCalls, returnedVariables) + ) ?? + false) + ); } function isPropertyEncapsulated( node: TSESTree.PropertyDefinition, + returnedFunctionCalls: Map, returnedVariables: Map ): boolean { if (node.accessibility === 'public') { @@ -290,7 +327,7 @@ function isPropertyEncapsulated( if ( classElement.type === 'MethodDefinition' && classElement.accessibility === 'public' && - methodReturnsProperty(classElement, node, returnedVariables) + methodReturnsProperty(classElement, node, returnedFunctionCalls, returnedVariables) ) { return false; } From 71a3e96a615daae27155b2d1399c6dfd59765196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Fri, 8 Aug 2025 10:13:26 +0200 Subject: [PATCH 7/8] feat(prefer-svelte-reactivity): added support for ES class element accessibility --- .../src/rules/prefer-svelte-reactivity.ts | 139 ++++++++++-------- 1 file changed, 76 insertions(+), 63 deletions(-) diff --git a/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts b/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts index 10da4af7e..9712e3bc8 100644 --- a/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts +++ b/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts @@ -54,6 +54,71 @@ export default createRule('prefer-svelte-reactivity', { const returnedFunctionCalls: Map = new Map(); const returnedVariables: Map = new Map(); const exportedVars: TSESTree.Node[] = []; + + function recordReturnedIdentifiers(node: TSESTree.Identifier | TSESTree.PrivateIdentifier) { + function recordVariable(enclosingFunction: FunctionLike, variable: VariableLike): void { + if (variable === null) { + return; + } + if (!returnedVariables.has(enclosingFunction)) { + returnedVariables.set(enclosingFunction, []); + } + returnedVariables.get(enclosingFunction)?.push(variable); + } + + function recordFunctionCall( + enclosingFunction: FunctionLike, + functionCall: TSESTree.MethodDefinition + ): void { + if (functionCall === null) { + return; + } + if (!returnedFunctionCalls.has(enclosingFunction)) { + returnedFunctionCalls.set(enclosingFunction, []); + } + returnedFunctionCalls.get(enclosingFunction)?.push(functionCall); + } + + const enclosingReturn = findEnclosingReturn(node); + if (enclosingReturn === null) { + return; + } + const enclosingFunction = findEnclosingFunction(enclosingReturn); + if (enclosingFunction === null) { + return; + } + if (node.parent.type === 'MemberExpression') { + const enclosingClassBody = findEnclosingClassBody(node); + for (const classElement of enclosingClassBody?.body ?? []) { + if ( + classElement.type === 'PropertyDefinition' && + (classElement.key.type === 'Identifier' || + classElement.key.type === 'PrivateIdentifier') && + node.name === classElement.key.name + ) { + recordVariable(enclosingFunction, classElement); + } + if ( + classElement.type === 'MethodDefinition' && + (classElement.key.type === 'Identifier' || + classElement.key.type === 'PrivateIdentifier') && + node.name === classElement.key.name + ) { + recordFunctionCall(enclosingFunction, classElement); + } + } + } else if (node.type === 'Identifier') { + const variable = findVariable(context, node); + if ( + variable !== null && + variable.identifiers.length > 0 && + variable.identifiers[0].parent.type === 'VariableDeclarator' + ) { + recordVariable(enclosingFunction, variable.identifiers[0].parent); + } + } + } + return { ...(getSvelteContext(context)?.svelteFileType === '.svelte.[js|ts]' && { ExportNamedDeclaration(node) { @@ -81,67 +146,8 @@ export default createRule('prefer-svelte-reactivity', { } } }), - Identifier(node) { - function recordVariable(enclosingFunction: FunctionLike, variable: VariableLike): void { - if (variable === null) { - return; - } - if (!returnedVariables.has(enclosingFunction)) { - returnedVariables.set(enclosingFunction, []); - } - returnedVariables.get(enclosingFunction)?.push(variable); - } - - function recordFunctionCall( - enclosingFunction: FunctionLike, - functionCall: TSESTree.MethodDefinition - ): void { - if (functionCall === null) { - return; - } - if (!returnedFunctionCalls.has(enclosingFunction)) { - returnedFunctionCalls.set(enclosingFunction, []); - } - returnedFunctionCalls.get(enclosingFunction)?.push(functionCall); - } - - const enclosingReturn = findEnclosingReturn(node); - if (enclosingReturn === null) { - return; - } - const enclosingFunction = findEnclosingFunction(enclosingReturn); - if (enclosingFunction === null) { - return; - } - if (node.parent.type === 'MemberExpression') { - const enclosingClassBody = findEnclosingClassBody(node); - for (const classElement of enclosingClassBody?.body ?? []) { - if ( - classElement.type === 'PropertyDefinition' && - classElement.key.type === 'Identifier' && - node.name === classElement.key.name - ) { - recordVariable(enclosingFunction, classElement); - } - if ( - classElement.type === 'MethodDefinition' && - classElement.key.type === 'Identifier' && - node.name === classElement.key.name - ) { - recordFunctionCall(enclosingFunction, classElement); - } - } - } else { - const variable = findVariable(context, node); - if ( - variable !== null && - variable.identifiers.length > 0 && - variable.identifiers[0].parent.type === 'VariableDeclarator' - ) { - recordVariable(enclosingFunction, variable.identifiers[0].parent); - } - } - }, + Identifier: recordReturnedIdentifiers, + PrivateIdentifier: recordReturnedIdentifiers, 'Program:exit'() { const referenceTracker = new ReferenceTracker(context.sourceCode.scopeManager.globalScope!); for (const { node, path } of referenceTracker.iterateGlobalReferences({ @@ -320,13 +326,13 @@ function isPropertyEncapsulated( returnedFunctionCalls: Map, returnedVariables: Map ): boolean { - if (node.accessibility === 'public') { + if (isPublic(node)) { return false; } for (const classElement of node.parent.body) { if ( classElement.type === 'MethodDefinition' && - classElement.accessibility === 'public' && + isPublic(classElement) && methodReturnsProperty(classElement, node, returnedFunctionCalls, returnedVariables) ) { return false; @@ -335,6 +341,13 @@ function isPropertyEncapsulated( return true; } +function isPublic(node: TSESTree.MethodDefinition | TSESTree.PropertyDefinition): boolean { + return ( + (node.accessibility === undefined && node.key.type !== 'PrivateIdentifier') || + node.accessibility === 'public' + ); +} + function isDateMutable(referenceTracker: ReferenceTracker, ctorNode: TSESTree.Expression): boolean { return !referenceTracker .iteratePropertyReferences(ctorNode, { From cab6f5953bea0ba1446bef196fb0b99c5a9c5353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Fri, 8 Aug 2025 12:05:15 +0200 Subject: [PATCH 8/8] chore(prefer-svelte-reactivity): cleaned-up code --- .../src/rules/prefer-svelte-reactivity.ts | 174 +++++++++--------- 1 file changed, 91 insertions(+), 83 deletions(-) diff --git a/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts b/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts index 9712e3bc8..8a192eb08 100644 --- a/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts +++ b/packages/eslint-plugin-svelte/src/rules/prefer-svelte-reactivity.ts @@ -119,6 +119,92 @@ export default createRule('prefer-svelte-reactivity', { } } + function checkNonReactiveUsage( + node: TSESTree.Node, + objectType: 'Date' | 'Map' | 'Set' | 'URL' | 'URLSearchParams', + referenceTracker: ReferenceTracker + ) { + const messageId = `mutable${objectType}Used`; + + function report() { + context.report({ + messageId, + node + }); + } + + // Report all values directly returned from functions + if (findEnclosingReturn(node) !== null) { + report(); + return; + } + + // Report all exported variables + for (const exportedVar of exportedVars) { + if (isIn(node, exportedVar)) { + report(); + return; + } + } + + // Report all returned variables + for (const [fn, fnReturnVars] of returnedVariables.entries()) { + for (const returnedVar of fnReturnVars) { + if (fn.type === 'MethodDefinition' && returnedVar.type === 'PropertyDefinition') { + continue; + } + if (isIn(node, returnedVar)) { + report(); + return; + } + } + } + + // Report all encapsulated class properties + const enclosingPropertyDefinition = findEnclosingPropertyDefinition(node); + if ( + enclosingPropertyDefinition !== null && + (!ignoreEncapsulatedLocalVariables || + !isPropertyEncapsulated( + enclosingPropertyDefinition, + returnedFunctionCalls, + returnedVariables + )) + ) { + report(); + return; + } + + // Ignore all variables encapsulated in functions + if (ignoreEncapsulatedLocalVariables && isLocalVarEncapsulated(returnedVariables, node)) { + return; + } + + // Report all other mutable variables + if (objectType === 'Date' && isDateMutable(referenceTracker, node as TSESTree.Expression)) { + report(); + return; + } + if (objectType === 'Map' && isMapMutable(referenceTracker, node as TSESTree.Expression)) { + report(); + return; + } + if (objectType === 'Set' && isSetMutable(referenceTracker, node as TSESTree.Expression)) { + report(); + return; + } + if (objectType === 'URL' && isURLMutable(referenceTracker, node as TSESTree.Expression)) { + report(); + return; + } + if ( + objectType === 'URLSearchParams' && + isURLSearchParamsMutable(referenceTracker, node as TSESTree.Expression) + ) { + report(); + } + } + return { ...(getSvelteContext(context)?.svelteFileType === '.svelte.[js|ts]' && { ExportNamedDeclaration(node) { @@ -167,89 +253,11 @@ export default createRule('prefer-svelte-reactivity', { [ReferenceTracker.CONSTRUCT]: true } })) { - const messageId = - path[0] === 'Date' - ? 'mutableDateUsed' - : path[0] === 'Map' - ? 'mutableMapUsed' - : path[0] === 'Set' - ? 'mutableSetUsed' - : path[0] === 'URL' - ? 'mutableURLUsed' - : 'mutableURLSearchParamsUsed'; - for (const exportedVar of exportedVars) { - if (isIn(node, exportedVar)) { - context.report({ - messageId, - node - }); - } - } - for (const [fn, fnReturnVars] of returnedVariables.entries()) { - for (const returnedVar of fnReturnVars) { - if (fn.type === 'MethodDefinition' && returnedVar.type === 'PropertyDefinition') { - continue; - } - if (isIn(node, returnedVar)) { - context.report({ - messageId, - node - }); - } - } - } - const enclosingPropertyDefinition = findEnclosingPropertyDefinition(node); - if ( - findEnclosingReturn(node) !== null || - (enclosingPropertyDefinition !== null && - (!ignoreEncapsulatedLocalVariables || - !isPropertyEncapsulated( - enclosingPropertyDefinition, - returnedFunctionCalls, - returnedVariables - ))) - ) { - context.report({ - messageId, - node - }); - } - if (ignoreEncapsulatedLocalVariables && isLocalVarEncapsulated(returnedVariables, node)) { - continue; - } - if (path[0] === 'Date' && isDateMutable(referenceTracker, node as TSESTree.Expression)) { - context.report({ - messageId: 'mutableDateUsed', - node - }); - } - if (path[0] === 'Map' && isMapMutable(referenceTracker, node as TSESTree.Expression)) { - context.report({ - messageId: 'mutableMapUsed', - node - }); - } - if (path[0] === 'Set' && isSetMutable(referenceTracker, node as TSESTree.Expression)) { - context.report({ - messageId: 'mutableSetUsed', - node - }); - } - if (path[0] === 'URL' && isURLMutable(referenceTracker, node as TSESTree.Expression)) { - context.report({ - messageId: 'mutableURLUsed', - node - }); - } - if ( - path[0] === 'URLSearchParams' && - isURLSearchParamsMutable(referenceTracker, node as TSESTree.Expression) - ) { - context.report({ - messageId: 'mutableURLSearchParamsUsed', - node - }); - } + checkNonReactiveUsage( + node, + path[0] as 'Date' | 'Map' | 'Set' | 'URL' | 'URLSearchParams', + referenceTracker + ); } } };