diff --git a/.changeset/tidy-pugs-draw.md b/.changeset/tidy-pugs-draw.md
new file mode 100644
index 000000000..dcbe6bd68
--- /dev/null
+++ b/.changeset/tidy-pugs-draw.md
@@ -0,0 +1,5 @@
+---
+"eslint-plugin-svelte": minor
+---
+
+feat: add `svelte/no-reactive-reassign` rule
diff --git a/README.md b/README.md
index 0a9910f12..51735373b 100644
--- a/README.md
+++ b/README.md
@@ -319,6 +319,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
| [svelte/no-export-load-in-svelte-module-in-kit-pages](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-export-load-in-svelte-module-in-kit-pages/) | disallow exporting load functions in `*.svelte` module in Svelte Kit page components. | |
| [svelte/no-not-function-handler](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-not-function-handler/) | disallow use of not function in event handler | :star: |
| [svelte/no-object-in-text-mustaches](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-object-in-text-mustaches/) | disallow objects in text mustache interpolation | :star: |
+| [svelte/no-reactive-reassign](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-reactive-reassign/) | disallow reassigning reactive values | |
| [svelte/no-shorthand-style-property-overrides](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-shorthand-style-property-overrides/) | disallow shorthand style properties that override related longhand properties | :star: |
| [svelte/no-store-async](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-store-async/) | disallow using async/await inside svelte stores because it causes issues with the auto-unsubscribing features | |
| [svelte/no-unknown-style-directive-property](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-unknown-style-directive-property/) | disallow unknown `style:property` | :star: |
diff --git a/docs/rules.md b/docs/rules.md
index b4543b474..ecef4f018 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -26,6 +26,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
| [svelte/no-export-load-in-svelte-module-in-kit-pages](./rules/no-export-load-in-svelte-module-in-kit-pages.md) | disallow exporting load functions in `*.svelte` module in Svelte Kit page components. | |
| [svelte/no-not-function-handler](./rules/no-not-function-handler.md) | disallow use of not function in event handler | :star: |
| [svelte/no-object-in-text-mustaches](./rules/no-object-in-text-mustaches.md) | disallow objects in text mustache interpolation | :star: |
+| [svelte/no-reactive-reassign](./rules/no-reactive-reassign.md) | disallow reassigning reactive values | |
| [svelte/no-shorthand-style-property-overrides](./rules/no-shorthand-style-property-overrides.md) | disallow shorthand style properties that override related longhand properties | :star: |
| [svelte/no-store-async](./rules/no-store-async.md) | disallow using async/await inside svelte stores because it causes issues with the auto-unsubscribing features | |
| [svelte/no-unknown-style-directive-property](./rules/no-unknown-style-directive-property.md) | disallow unknown `style:property` | :star: |
diff --git a/docs/rules/no-reactive-reassign.md b/docs/rules/no-reactive-reassign.md
new file mode 100644
index 000000000..194a21a85
--- /dev/null
+++ b/docs/rules/no-reactive-reassign.md
@@ -0,0 +1,122 @@
+---
+pageClass: "rule-details"
+sidebarDepth: 0
+title: "svelte/no-reactive-reassign"
+description: "disallow reassigning reactive values"
+---
+
+# svelte/no-reactive-reassign
+
+> disallow reassigning reactive values
+
+- :exclamation: **_This rule has not been released yet._**
+
+## :book: Rule Details
+
+This rule aims to prevent unintended behavior caused by modification or reassignment of reactive values.
+
+
+
+
+
+```svelte
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "svelte/no-reactive-reassign": ["error", {
+ "props": true
+ }]
+}
+```
+
+- `props` ... If set to `true`, this rule warns against the modification of reactive value properties. Default is `true`.
+
+### `{ "props": true }`
+
+
+
+
+
+```svelte
+
+
+
+
+
+
+
+```
+
+
+
+### `{ "props": false }`
+
+
+
+
+
+```svelte
+
+
+
+
+
+
+
+
+```
+
+
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/src/rules/no-reactive-reassign.ts)
+- [Test source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/tests/src/rules/no-reactive-reassign.ts)
diff --git a/src/rules/no-reactive-reassign.ts b/src/rules/no-reactive-reassign.ts
new file mode 100644
index 000000000..214a6e60f
--- /dev/null
+++ b/src/rules/no-reactive-reassign.ts
@@ -0,0 +1,296 @@
+import { TSESTree } from "@typescript-eslint/types"
+import type { AST } from "svelte-eslint-parser"
+import { createRule } from "../utils"
+import { getPropertyName } from "@eslint-community/eslint-utils"
+
+export default createRule("no-reactive-reassign", {
+ meta: {
+ docs: {
+ description: "disallow reassigning reactive values",
+ category: "Possible Errors",
+ // TODO Switch to recommended in the major version.
+ recommended: false,
+ },
+ schema: [
+ {
+ type: "object",
+ properties: {
+ props: {
+ type: "boolean",
+ },
+ },
+ additionalProperties: false,
+ },
+ ],
+ messages: {
+ assignmentToReactiveValue: "Assignment to reactive value '{{name}}'.",
+ assignmentToReactiveValueProp:
+ "Assignment to property of reactive value '{{name}}'.",
+ },
+ type: "problem",
+ },
+ create(context) {
+ const props = context.options[0]?.props !== false // default true
+ const sourceCode = context.getSourceCode()
+ const scopeManager = sourceCode.scopeManager
+ const globalScope = scopeManager.globalScope
+ const toplevelScope =
+ globalScope?.childScopes.find((scope) => scope.type === "module") ||
+ globalScope
+ if (!globalScope || !toplevelScope) {
+ return {}
+ }
+
+ type CheckContext
= {
+ node: TSESTree.Expression
+ parent: P
+ pathNodes: TSESTree.MemberExpression[]
+ }
+ const CHECK_REASSIGN: {
+ [key in TSESTree.Node["type"] | "SvelteDirective"]?: (
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Ignore
+ ctx: CheckContext,
+ ) =>
+ | null // The given expression does not reassign.
+ // The given expression will reassign.
+ | {
+ type: "reassign"
+ node: TSESTree.Node | AST.SvelteNode
+ pathNodes?: TSESTree.MemberExpression[]
+ }
+ // The context to check next.
+ | {
+ type: "check"
+ node: TSESTree.Expression
+ pathNodes?: TSESTree.MemberExpression[]
+ }
+ } = {
+ [TSESTree.AST_NODE_TYPES.UpdateExpression]:
+ // e.g. foo ++, foo --
+ ({ parent }) => ({ type: "reassign", node: parent }),
+ [TSESTree.AST_NODE_TYPES.UnaryExpression]: ({
+ parent,
+ }: CheckContext) => {
+ if (parent.operator === "delete") {
+ // e.g. delete foo.prop
+ return { type: "reassign", node: parent }
+ }
+ return null
+ },
+ [TSESTree.AST_NODE_TYPES.AssignmentExpression]: ({
+ node,
+ parent,
+ }: CheckContext) => {
+ if (parent.left === node) {
+ // e.g. foo = 42, foo += 42, foo -= 42
+ return { type: "reassign", node: parent }
+ }
+ return null
+ },
+ [TSESTree.AST_NODE_TYPES.ForInStatement]: ({
+ node,
+ parent,
+ }: CheckContext) => {
+ if (parent.left === node) {
+ // e.g. for (foo in itr)
+ return { type: "reassign", node: parent }
+ }
+ return null
+ },
+ [TSESTree.AST_NODE_TYPES.ForOfStatement]: ({
+ node,
+ parent,
+ }: CheckContext) => {
+ if (parent.left === node) {
+ // e.g. for (foo of itr)
+ return { type: "reassign", node: parent }
+ }
+ return null
+ },
+ [TSESTree.AST_NODE_TYPES.CallExpression]: ({
+ node,
+ parent,
+ pathNodes,
+ }: CheckContext) => {
+ if (pathNodes.length > 0 && parent.callee === node) {
+ const mem = pathNodes[pathNodes.length - 1]
+ const callName = getPropertyName(mem)
+ if (
+ callName &&
+ /^(?:push|pop|shift|unshift|reverse|splice|sort|copyWithin|fill)$/u.test(
+ callName,
+ )
+ ) {
+ // e.g. foo.push()
+ return {
+ type: "reassign",
+ node: parent,
+ pathNodes: pathNodes.slice(0, -1),
+ }
+ }
+ }
+ return null
+ },
+ [TSESTree.AST_NODE_TYPES.MemberExpression]: ({
+ node,
+ parent,
+ pathNodes,
+ }: CheckContext) => {
+ if (parent.object === node) {
+ // The context to check next.
+ return {
+ type: "check",
+ node: parent,
+ pathNodes: [...pathNodes, parent],
+ }
+ }
+ return null
+ },
+ [TSESTree.AST_NODE_TYPES.ChainExpression]: ({
+ parent,
+ }: CheckContext) => {
+ // e.g. `foo?.prop`
+ // The context to check next.
+ return { type: "check", node: parent }
+ },
+ [TSESTree.AST_NODE_TYPES.ConditionalExpression]: ({
+ node,
+ parent,
+ }: CheckContext) => {
+ if (parent.test === node) {
+ return null
+ }
+ // The context to check next for `(test ? foo : bar).prop`.
+ return { type: "check", node: parent }
+ },
+ [TSESTree.AST_NODE_TYPES.Property]: ({
+ node,
+ parent,
+ }: CheckContext) => {
+ if (
+ parent.value === node &&
+ parent.parent &&
+ parent.parent.type === "ObjectPattern"
+ ) {
+ // The context to check next for `({a: foo} = obj)`.
+ return { type: "check", node: parent.parent }
+ }
+ return null
+ },
+ [TSESTree.AST_NODE_TYPES.ArrayPattern]: ({
+ node,
+ parent,
+ }: CheckContext) => {
+ if (parent.elements.includes(node as TSESTree.DestructuringPattern)) {
+ // The context to check next for `([foo] = obj)`.
+ return { type: "check", node: parent }
+ }
+ return null
+ },
+ [TSESTree.AST_NODE_TYPES.RestElement]: ({
+ node,
+ parent,
+ }: CheckContext) => {
+ if (parent.argument === node && parent.parent) {
+ // The context to check next for `({...foo} = obj)`.
+ return {
+ type: "check",
+ node: parent.parent as
+ | TSESTree.ArrayPattern
+ | TSESTree.ObjectPattern,
+ }
+ }
+ return null
+ },
+ SvelteDirective: ({
+ node,
+ parent,
+ }: CheckContext) => {
+ if (parent.kind !== "Binding") {
+ return null
+ }
+ if (parent.shorthand || parent.expression === node) {
+ return {
+ type: "reassign",
+ node: parent,
+ }
+ }
+ return null
+ },
+ }
+
+ /**
+ * Returns the reassign information for the given expression node if it has a reassign.
+ */
+ function getReassignData(expr: TSESTree.Expression) {
+ let pathNodes: TSESTree.MemberExpression[] = []
+ let node: TSESTree.Expression = expr
+ let parent
+ while ((parent = node.parent)) {
+ const check = CHECK_REASSIGN[parent.type]
+ if (!check) {
+ return null
+ }
+ const result = check({ node, parent, pathNodes })
+ if (!result) {
+ return null
+ }
+ pathNodes = result.pathNodes || pathNodes
+ if (result.type === "reassign") {
+ return {
+ node: result.node,
+ pathNodes,
+ }
+ }
+ node = result.node
+ }
+ return null
+ }
+
+ return {
+ SvelteReactiveStatement(node: AST.SvelteReactiveStatement) {
+ if (
+ node.body.type !== "ExpressionStatement" ||
+ node.body.expression.type !== "AssignmentExpression" ||
+ node.body.expression.operator !== "="
+ ) {
+ return
+ }
+ const assignment = node.body.expression
+ for (const variable of toplevelScope.variables) {
+ if (!variable.defs.some((def) => def.node === assignment)) {
+ continue
+ }
+ for (const reference of variable.references) {
+ const id = reference.identifier
+ if (
+ (assignment.left.range[0] <= id.range[0] &&
+ id.range[1] <= assignment.left.range[1]) ||
+ id.type === "JSXIdentifier"
+ ) {
+ continue
+ }
+ const reassign = getReassignData(id)
+ if (!reassign) {
+ continue
+ }
+
+ // Suppresses reporting if the props option is set to `false` and reassigned to properties.
+ if (!props && reassign.pathNodes.length > 0) continue
+
+ context.report({
+ node: reassign.node,
+ messageId:
+ reassign.pathNodes.length === 0
+ ? "assignmentToReactiveValue"
+ : "assignmentToReactiveValueProp",
+ data: {
+ name: id.name,
+ },
+ })
+ }
+ }
+ },
+ }
+ },
+})
diff --git a/src/utils/rules.ts b/src/utils/rules.ts
index b720d98b7..0f560ad86 100644
--- a/src/utils/rules.ts
+++ b/src/utils/rules.ts
@@ -29,6 +29,7 @@ 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 noReactiveReassign from "../rules/no-reactive-reassign"
import noShorthandStylePropertyOverrides from "../rules/no-shorthand-style-property-overrides"
import noSpacesAroundEqualSignsInAttribute from "../rules/no-spaces-around-equal-signs-in-attribute"
import noStoreAsync from "../rules/no-store-async"
@@ -84,6 +85,7 @@ export const rules = [
noObjectInTextMustaches,
noReactiveFunctions,
noReactiveLiterals,
+ noReactiveReassign,
noShorthandStylePropertyOverrides,
noSpacesAroundEqualSignsInAttribute,
noStoreAsync,
diff --git a/tests/fixtures/rules/no-reactive-reassign/invalid/array01-errors.yaml b/tests/fixtures/rules/no-reactive-reassign/invalid/array01-errors.yaml
new file mode 100644
index 000000000..87c0b9b91
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/invalid/array01-errors.yaml
@@ -0,0 +1,36 @@
+- message: Assignment to reactive value 'array1'.
+ line: 15
+ column: 5
+ suggestions: null
+- message: Assignment to reactive value 'array2'.
+ line: 16
+ column: 5
+ suggestions: null
+- message: Assignment to reactive value 'array3'.
+ line: 17
+ column: 5
+ suggestions: null
+- message: Assignment to reactive value 'array4'.
+ line: 18
+ column: 5
+ suggestions: null
+- message: Assignment to reactive value 'array5'.
+ line: 19
+ column: 5
+ suggestions: null
+- message: Assignment to reactive value 'array6'.
+ line: 20
+ column: 5
+ suggestions: null
+- message: Assignment to reactive value 'array7'.
+ line: 21
+ column: 5
+ suggestions: null
+- message: Assignment to reactive value 'array8'.
+ line: 22
+ column: 5
+ suggestions: null
+- message: Assignment to reactive value 'array9'.
+ line: 23
+ column: 5
+ suggestions: null
diff --git a/tests/fixtures/rules/no-reactive-reassign/invalid/array01-input.svelte b/tests/fixtures/rules/no-reactive-reassign/invalid/array01-input.svelte
new file mode 100644
index 000000000..c50246253
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/invalid/array01-input.svelte
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+{JSON.stringify(array1) +
+ JSON.stringify(array2) +
+ JSON.stringify(array3) +
+ JSON.stringify(array4) +
+ JSON.stringify(array5) +
+ JSON.stringify(array6) +
+ JSON.stringify(array7) +
+ JSON.stringify(array8) +
+ JSON.stringify(array9) +
+ rerender}
diff --git a/tests/fixtures/rules/no-reactive-reassign/invalid/bind-dir01-errors.yaml b/tests/fixtures/rules/no-reactive-reassign/invalid/bind-dir01-errors.yaml
new file mode 100644
index 000000000..97bf6731b
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/invalid/bind-dir01-errors.yaml
@@ -0,0 +1,4 @@
+- message: Assignment to reactive value 'reactiveValue'.
+ line: 6
+ column: 22
+ suggestions: null
diff --git a/tests/fixtures/rules/no-reactive-reassign/invalid/bind-dir01-input.svelte b/tests/fixtures/rules/no-reactive-reassign/invalid/bind-dir01-input.svelte
new file mode 100644
index 000000000..ebcd157a8
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/invalid/bind-dir01-input.svelte
@@ -0,0 +1,6 @@
+
+
+
diff --git a/tests/fixtures/rules/no-reactive-reassign/invalid/conditional01-errors.yaml b/tests/fixtures/rules/no-reactive-reassign/invalid/conditional01-errors.yaml
new file mode 100644
index 000000000..55c536ee2
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/invalid/conditional01-errors.yaml
@@ -0,0 +1,4 @@
+- message: Assignment to property of reactive value 'reactiveValue'.
+ line: 7
+ column: 6
+ suggestions: null
diff --git a/tests/fixtures/rules/no-reactive-reassign/invalid/conditional01-input.svelte b/tests/fixtures/rules/no-reactive-reassign/invalid/conditional01-input.svelte
new file mode 100644
index 000000000..38df90d69
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/invalid/conditional01-input.svelte
@@ -0,0 +1,13 @@
+
+
+{reactiveValue}
diff --git a/tests/fixtures/rules/no-reactive-reassign/invalid/delete01-errors.yaml b/tests/fixtures/rules/no-reactive-reassign/invalid/delete01-errors.yaml
new file mode 100644
index 000000000..ec9c1100a
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/invalid/delete01-errors.yaml
@@ -0,0 +1,4 @@
+- message: Assignment to property of reactive value 'reactiveValue'.
+ line: 7
+ column: 5
+ suggestions: null
diff --git a/tests/fixtures/rules/no-reactive-reassign/invalid/delete01-input.svelte b/tests/fixtures/rules/no-reactive-reassign/invalid/delete01-input.svelte
new file mode 100644
index 000000000..25838e82b
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/invalid/delete01-input.svelte
@@ -0,0 +1,11 @@
+
+
+{reactiveValue}
diff --git a/tests/fixtures/rules/no-reactive-reassign/invalid/destructure01-errors.yaml b/tests/fixtures/rules/no-reactive-reassign/invalid/destructure01-errors.yaml
new file mode 100644
index 000000000..362330ace
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/invalid/destructure01-errors.yaml
@@ -0,0 +1,16 @@
+- message: Assignment to reactive value 'reactiveValue'.
+ line: 9
+ column: 7
+ suggestions: null
+- message: Assignment to reactive value 'reactiveObject'.
+ line: 10
+ column: 7
+ suggestions: null
+- message: Assignment to reactive value 'reactiveValue'.
+ line: 12
+ column: 6
+ suggestions: null
+- message: Assignment to reactive value 'reactiveArray'.
+ line: 13
+ column: 6
+ suggestions: null
diff --git a/tests/fixtures/rules/no-reactive-reassign/invalid/destructure01-input.svelte b/tests/fixtures/rules/no-reactive-reassign/invalid/destructure01-input.svelte
new file mode 100644
index 000000000..58e060cff
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/invalid/destructure01-input.svelte
@@ -0,0 +1,19 @@
+
+
+{reactiveValue}
+{JSON.stringify(reactiveObject)}
+{JSON.stringify(reactiveArray)}
diff --git a/tests/fixtures/rules/no-reactive-reassign/invalid/for-in01-errors.yaml b/tests/fixtures/rules/no-reactive-reassign/invalid/for-in01-errors.yaml
new file mode 100644
index 000000000..eff4f0b2e
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/invalid/for-in01-errors.yaml
@@ -0,0 +1,4 @@
+- message: Assignment to property of reactive value 'reactiveValue'.
+ line: 8
+ column: 5
+ suggestions: null
diff --git a/tests/fixtures/rules/no-reactive-reassign/invalid/for-in01-input.svelte b/tests/fixtures/rules/no-reactive-reassign/invalid/for-in01-input.svelte
new file mode 100644
index 000000000..a0bc9962a
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/invalid/for-in01-input.svelte
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+{reactiveValue.key + rerender}
diff --git a/tests/fixtures/rules/no-reactive-reassign/invalid/for-of01-errors.yaml b/tests/fixtures/rules/no-reactive-reassign/invalid/for-of01-errors.yaml
new file mode 100644
index 000000000..eff4f0b2e
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/invalid/for-of01-errors.yaml
@@ -0,0 +1,4 @@
+- message: Assignment to property of reactive value 'reactiveValue'.
+ line: 8
+ column: 5
+ suggestions: null
diff --git a/tests/fixtures/rules/no-reactive-reassign/invalid/for-of01-input.svelte b/tests/fixtures/rules/no-reactive-reassign/invalid/for-of01-input.svelte
new file mode 100644
index 000000000..ba9c313b3
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/invalid/for-of01-input.svelte
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+{reactiveValue.key + rerender}
diff --git a/tests/fixtures/rules/no-reactive-reassign/invalid/member01-errors.yaml b/tests/fixtures/rules/no-reactive-reassign/invalid/member01-errors.yaml
new file mode 100644
index 000000000..af20c5021
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/invalid/member01-errors.yaml
@@ -0,0 +1,8 @@
+- message: Assignment to property of reactive value 'reactiveValue'.
+ line: 6
+ column: 5
+ suggestions: null
+- message: Assignment to property of reactive value 'reactiveValue'.
+ line: 8
+ column: 6
+ suggestions: null
diff --git a/tests/fixtures/rules/no-reactive-reassign/invalid/member01-input.svelte b/tests/fixtures/rules/no-reactive-reassign/invalid/member01-input.svelte
new file mode 100644
index 000000000..3861dfba5
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/invalid/member01-input.svelte
@@ -0,0 +1,12 @@
+
+
+{reactiveValue}
diff --git a/tests/fixtures/rules/no-reactive-reassign/invalid/props-false/_config.json b/tests/fixtures/rules/no-reactive-reassign/invalid/props-false/_config.json
new file mode 100644
index 000000000..cda6d1be9
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/invalid/props-false/_config.json
@@ -0,0 +1,7 @@
+{
+ "options": [
+ {
+ "props": false
+ }
+ ]
+}
diff --git a/tests/fixtures/rules/no-reactive-reassign/invalid/props-false/test01-errors.yaml b/tests/fixtures/rules/no-reactive-reassign/invalid/props-false/test01-errors.yaml
new file mode 100644
index 000000000..62f301cdf
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/invalid/props-false/test01-errors.yaml
@@ -0,0 +1,8 @@
+- message: Assignment to reactive value 'reactiveValue'.
+ line: 7
+ column: 5
+ suggestions: null
+- message: Assignment to reactive value 'reactiveValue'.
+ line: 15
+ column: 22
+ suggestions: null
diff --git a/tests/fixtures/rules/no-reactive-reassign/invalid/props-false/test01-input.svelte b/tests/fixtures/rules/no-reactive-reassign/invalid/props-false/test01-input.svelte
new file mode 100644
index 000000000..ec3fbaf0c
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/invalid/props-false/test01-input.svelte
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+{reactiveValue}
diff --git a/tests/fixtures/rules/no-reactive-reassign/invalid/props-true01-errors.yaml b/tests/fixtures/rules/no-reactive-reassign/invalid/props-true01-errors.yaml
new file mode 100644
index 000000000..659cac015
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/invalid/props-true01-errors.yaml
@@ -0,0 +1,8 @@
+- message: Assignment to property of reactive value 'reactiveValue'.
+ line: 9
+ column: 5
+ suggestions: null
+- message: Assignment to property of reactive value 'reactiveValue'.
+ line: 16
+ column: 22
+ suggestions: null
diff --git a/tests/fixtures/rules/no-reactive-reassign/invalid/props-true01-input.svelte b/tests/fixtures/rules/no-reactive-reassign/invalid/props-true01-input.svelte
new file mode 100644
index 000000000..53d38b183
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/invalid/props-true01-input.svelte
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/tests/fixtures/rules/no-reactive-reassign/invalid/test01-errors.yaml b/tests/fixtures/rules/no-reactive-reassign/invalid/test01-errors.yaml
new file mode 100644
index 000000000..8889ad39a
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/invalid/test01-errors.yaml
@@ -0,0 +1,12 @@
+- message: Assignment to reactive value 'reactiveValue'.
+ line: 7
+ column: 5
+ suggestions: null
+- message: Assignment to reactive value 'reactiveValue'.
+ line: 8
+ column: 5
+ suggestions: null
+- message: Assignment to reactive value 'reactiveValue'.
+ line: 16
+ column: 22
+ suggestions: null
diff --git a/tests/fixtures/rules/no-reactive-reassign/invalid/test01-input.svelte b/tests/fixtures/rules/no-reactive-reassign/invalid/test01-input.svelte
new file mode 100644
index 000000000..9e4d6240b
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/invalid/test01-input.svelte
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+{reactiveValue}
diff --git a/tests/fixtures/rules/no-reactive-reassign/valid/array01-input.svelte b/tests/fixtures/rules/no-reactive-reassign/valid/array01-input.svelte
new file mode 100644
index 000000000..215eb285c
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/valid/array01-input.svelte
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+{JSON.stringify(array1) +
+ JSON.stringify(array2) +
+ JSON.stringify(array3) +
+ JSON.stringify(array4) +
+ JSON.stringify(array5) +
+ JSON.stringify(array6) +
+ JSON.stringify(array7) +
+ JSON.stringify(array8) +
+ JSON.stringify(array9) +
+ rerender}
diff --git a/tests/fixtures/rules/no-reactive-reassign/valid/assign-right01-input.svelte b/tests/fixtures/rules/no-reactive-reassign/valid/assign-right01-input.svelte
new file mode 100644
index 000000000..ba41cb86a
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/valid/assign-right01-input.svelte
@@ -0,0 +1,14 @@
+
+
+{reactiveValue}
diff --git a/tests/fixtures/rules/no-reactive-reassign/valid/destructure01-input.svelte b/tests/fixtures/rules/no-reactive-reassign/valid/destructure01-input.svelte
new file mode 100644
index 000000000..a253277c8
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/valid/destructure01-input.svelte
@@ -0,0 +1,12 @@
+
+
+{reactiveValue}
diff --git a/tests/fixtures/rules/no-reactive-reassign/valid/for-in01-input.svelte b/tests/fixtures/rules/no-reactive-reassign/valid/for-in01-input.svelte
new file mode 100644
index 000000000..a4caf704e
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/valid/for-in01-input.svelte
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+{reactiveValue.key + rerender}
diff --git a/tests/fixtures/rules/no-reactive-reassign/valid/for-of01-input.svelte b/tests/fixtures/rules/no-reactive-reassign/valid/for-of01-input.svelte
new file mode 100644
index 000000000..b6c07b2d3
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/valid/for-of01-input.svelte
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+{reactiveValue[0] + rerender}
diff --git a/tests/fixtures/rules/no-reactive-reassign/valid/member-key01-input.svelte b/tests/fixtures/rules/no-reactive-reassign/valid/member-key01-input.svelte
new file mode 100644
index 000000000..fe8d886e4
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/valid/member-key01-input.svelte
@@ -0,0 +1,10 @@
+
+
+{reactiveValue}
diff --git a/tests/fixtures/rules/no-reactive-reassign/valid/on-dir01-input.svelte b/tests/fixtures/rules/no-reactive-reassign/valid/on-dir01-input.svelte
new file mode 100644
index 000000000..2e0a7a1ed
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/valid/on-dir01-input.svelte
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/tests/fixtures/rules/no-reactive-reassign/valid/props-false/_config.json b/tests/fixtures/rules/no-reactive-reassign/valid/props-false/_config.json
new file mode 100644
index 000000000..cda6d1be9
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/valid/props-false/_config.json
@@ -0,0 +1,7 @@
+{
+ "options": [
+ {
+ "props": false
+ }
+ ]
+}
diff --git a/tests/fixtures/rules/no-reactive-reassign/valid/props-false/props-false01-input.svelte b/tests/fixtures/rules/no-reactive-reassign/valid/props-false/props-false01-input.svelte
new file mode 100644
index 000000000..53d38b183
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/valid/props-false/props-false01-input.svelte
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/tests/fixtures/rules/no-reactive-reassign/valid/reactive-like01-input.svelte b/tests/fixtures/rules/no-reactive-reassign/valid/reactive-like01-input.svelte
new file mode 100644
index 000000000..c6cf245ab
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/valid/reactive-like01-input.svelte
@@ -0,0 +1,14 @@
+
+
+
+
+
+{reactiveLikeValue}
diff --git a/tests/fixtures/rules/no-reactive-reassign/valid/reactive-like02-input.svelte b/tests/fixtures/rules/no-reactive-reassign/valid/reactive-like02-input.svelte
new file mode 100644
index 000000000..3e73fb527
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/valid/reactive-like02-input.svelte
@@ -0,0 +1,16 @@
+
+
+
+
+
+{reactiveLikeValue}
diff --git a/tests/fixtures/rules/no-reactive-reassign/valid/test01-input.svelte b/tests/fixtures/rules/no-reactive-reassign/valid/test01-input.svelte
new file mode 100644
index 000000000..1098b14f3
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/valid/test01-input.svelte
@@ -0,0 +1,14 @@
+
+
+
+
+
+{reactiveValue}
diff --git a/tests/fixtures/rules/no-reactive-reassign/valid/typeof01-input.svelte b/tests/fixtures/rules/no-reactive-reassign/valid/typeof01-input.svelte
new file mode 100644
index 000000000..91c0be5ea
--- /dev/null
+++ b/tests/fixtures/rules/no-reactive-reassign/valid/typeof01-input.svelte
@@ -0,0 +1,10 @@
+
+
+{reactiveValue}
diff --git a/tests/src/rules/no-reactive-reassign.ts b/tests/src/rules/no-reactive-reassign.ts
new file mode 100644
index 000000000..ebf34c6bf
--- /dev/null
+++ b/tests/src/rules/no-reactive-reassign.ts
@@ -0,0 +1,16 @@
+import { RuleTester } from "eslint"
+import rule from "../../../src/rules/no-reactive-reassign"
+import { loadTestCases } from "../../utils/utils"
+
+const tester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 2020,
+ sourceType: "module",
+ },
+})
+
+tester.run(
+ "no-reactive-reassign",
+ rule as any,
+ loadTestCases("no-reactive-reassign"),
+)