Skip to content

Commit 08adc02

Browse files
committed
distinguish between identifiers (reassignments) and member expressions (mutations)
1 parent a8854e0 commit 08adc02

File tree

3 files changed

+39
-26
lines changed

3 files changed

+39
-26
lines changed

packages/svelte/src/compiler/phases/3-transform/server/transform-server.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2024,8 +2024,7 @@ export function server_component(analysis, options) {
20242024

20252025
if (
20262026
node.body.type === 'ExpressionStatement' &&
2027-
node.body.expression.type === 'AssignmentExpression' &&
2028-
node.body.expression.left.type !== 'MemberExpression'
2027+
node.body.expression.type === 'AssignmentExpression'
20292028
) {
20302029
for (const id of extract_identifiers(node.body.expression.left)) {
20312030
const binding = analysis.instance.scope.get(id.name);

packages/svelte/src/compiler/phases/scope.js

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ import { walk } from 'zimmerframe';
33
import { is_element_node } from './nodes.js';
44
import * as b from '../utils/builders.js';
55
import * as e from '../errors.js';
6-
import { extract_identifiers, extract_identifiers_from_destructuring } from '../utils/ast.js';
6+
import {
7+
extract_identifiers,
8+
extract_identifiers_from_destructuring,
9+
object,
10+
unwrap_pattern
11+
} from '../utils/ast.js';
712
import { JsKeywords, Runes } from './constants.js';
813

914
export class Scope {
@@ -361,8 +366,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
361366

362367
if (
363368
node.body.type === 'ExpressionStatement' &&
364-
node.body.expression.type === 'AssignmentExpression' &&
365-
node.body.expression.left.type !== 'MemberExpression'
369+
node.body.expression.type === 'AssignmentExpression'
366370
) {
367371
for (const id of extract_identifiers(node.body.expression.left)) {
368372
if (!id.name.startsWith('$')) {
@@ -714,11 +718,14 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
714718
const binding = scope.get(/** @type {import('estree').Identifier} */ (object).name);
715719
if (binding) binding.mutated = true;
716720
} else {
717-
extract_identifiers(node, [], true).forEach((identifier) => {
718-
const binding = scope.get(identifier.name);
719-
if (binding && identifier !== binding.node) {
721+
unwrap_pattern(node).forEach((node) => {
722+
let id = node.type === 'Identifier' ? node : object(node);
723+
if (id === null) return;
724+
725+
const binding = scope.get(id.name);
726+
if (binding && id !== binding.node) {
720727
binding.mutated = true;
721-
binding.reassigned = true;
728+
binding.reassigned = node.type === 'Identifier';
722729
}
723730
});
724731
}

packages/svelte/src/compiler/utils/ast.js

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -54,53 +54,60 @@ export function is_event_attribute(attribute) {
5454
}
5555

5656
/**
57-
* Extracts all identifiers from a pattern.
58-
* @param {ESTree.Pattern} param
59-
* @param {ESTree.Identifier[]} [nodes]
60-
* @returns {ESTree.Identifier[]}
57+
* Extracts all identifiers and member expressions from a pattern.
58+
* @param {ESTree.Pattern} pattern
59+
* @param {Array<ESTree.Identifier | ESTree.MemberExpression>} [nodes]
60+
* @returns {Array<ESTree.Identifier | ESTree.MemberExpression>}
6161
*/
62-
export function extract_identifiers(param, nodes = []) {
63-
switch (param.type) {
62+
export function unwrap_pattern(pattern, nodes = []) {
63+
switch (pattern.type) {
6464
case 'Identifier':
65-
nodes.push(param);
65+
nodes.push(pattern);
6666
break;
6767

6868
case 'MemberExpression':
69-
// Only the `a` from `[a.b[c].d]` is of interest to us here
70-
const id = object(param);
71-
if (id) nodes.push(id);
69+
nodes.push(pattern);
7270
break;
7371

7472
case 'ObjectPattern':
75-
for (const prop of param.properties) {
73+
for (const prop of pattern.properties) {
7674
if (prop.type === 'RestElement') {
77-
extract_identifiers(prop.argument, nodes);
75+
unwrap_pattern(prop.argument, nodes);
7876
} else {
79-
extract_identifiers(prop.value, nodes);
77+
unwrap_pattern(prop.value, nodes);
8078
}
8179
}
8280

8381
break;
8482

8583
case 'ArrayPattern':
86-
for (const element of param.elements) {
87-
if (element) extract_identifiers(element, nodes);
84+
for (const element of pattern.elements) {
85+
if (element) unwrap_pattern(element, nodes);
8886
}
8987

9088
break;
9189

9290
case 'RestElement':
93-
extract_identifiers(param.argument, nodes);
91+
unwrap_pattern(pattern.argument, nodes);
9492
break;
9593

9694
case 'AssignmentPattern':
97-
extract_identifiers(param.left, nodes);
95+
unwrap_pattern(pattern.left, nodes);
9896
break;
9997
}
10098

10199
return nodes;
102100
}
103101

102+
/**
103+
* Extracts all identifiers from a pattern.
104+
* @param {ESTree.Pattern} pattern
105+
* @returns {ESTree.Identifier[]}
106+
*/
107+
export function extract_identifiers(pattern) {
108+
return unwrap_pattern(pattern, []).filter((node) => node.type === 'Identifier');
109+
}
110+
104111
/**
105112
* Extracts all identifiers and a stringified keypath from an expression.
106113
* @param {ESTree.Expression} expr

0 commit comments

Comments
 (0)