octane 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/runtime.cjs +338 -111
- package/dist/cjs/runtime.server.cjs +307 -66
- package/dist/cjs/version.cjs +1 -1
- package/dist/compiler/compile.js +171 -13
- package/dist/compiler/context-use.js +154 -0
- package/dist/compiler/hook-deps.js +17 -1
- package/dist/compiler/native-read-diagnostics.js +11 -32
- package/dist/compiler/slot-hooks.js +6 -0
- package/dist/compiler/strong-mode.js +716 -39
- package/dist/compiler/volar.LICENSES.txt +1 -1
- package/dist/compiler/volar.js +3485 -3476
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -0
- package/dist/runtime.d.ts +8 -3
- package/dist/runtime.js +337 -111
- package/dist/runtime.server.d.ts +6 -5
- package/dist/runtime.server.js +307 -66
- package/dist/universal-core.d.ts +15 -0
- package/dist/universal-core.js +516 -32
- package/dist/version.js +1 -1
- package/package.json +3 -3
|
@@ -53,9 +53,98 @@ const NO_RETURN_VALUE = Symbol('no return value');
|
|
|
53
53
|
const OTHER_BINDING = { kind: 'other' };
|
|
54
54
|
const SNAPSHOT_BINDING = { kind: 'snapshot' };
|
|
55
55
|
const ARRAY_SNAPSHOT_BINDING = { kind: 'snapshot', array: true };
|
|
56
|
-
const
|
|
57
|
-
const
|
|
56
|
+
const STATE_GETTER_BINDING = { kind: 'getter' };
|
|
57
|
+
const STATE_TUPLE_BINDING = {
|
|
58
|
+
kind: 'state-tuple',
|
|
59
|
+
snapshot: SNAPSHOT_BINDING,
|
|
60
|
+
getter: STATE_GETTER_BINDING,
|
|
61
|
+
};
|
|
62
|
+
const ARRAY_STATE_TUPLE_BINDING = {
|
|
63
|
+
kind: 'state-tuple',
|
|
64
|
+
snapshot: ARRAY_SNAPSHOT_BINDING,
|
|
65
|
+
getter: STATE_GETTER_BINDING,
|
|
66
|
+
};
|
|
58
67
|
const UNDEFINED_BINDING = { kind: 'constant', value: UNDEFINED_VALUE, primitive: undefined };
|
|
68
|
+
const GLOBAL_OBJECT_BINDING = { kind: 'global-object' };
|
|
69
|
+
const AMBIENT_GLOBAL_BINDINGS = new Map(
|
|
70
|
+
[
|
|
71
|
+
'window',
|
|
72
|
+
'document',
|
|
73
|
+
'localStorage',
|
|
74
|
+
'sessionStorage',
|
|
75
|
+
'navigator',
|
|
76
|
+
'location',
|
|
77
|
+
'matchMedia',
|
|
78
|
+
].map((name) => [name, { kind: 'ambient', name }]),
|
|
79
|
+
);
|
|
80
|
+
// Reading a standard language builtin is permitted here. Its calls still
|
|
81
|
+
// follow the separate clock/randomness rules; this is not a purity whitelist.
|
|
82
|
+
const LANGUAGE_GLOBALS = new Set([
|
|
83
|
+
'undefined',
|
|
84
|
+
'NaN',
|
|
85
|
+
'Infinity',
|
|
86
|
+
'globalThis',
|
|
87
|
+
'Object',
|
|
88
|
+
'Function',
|
|
89
|
+
'Boolean',
|
|
90
|
+
'Symbol',
|
|
91
|
+
'Error',
|
|
92
|
+
'AggregateError',
|
|
93
|
+
'EvalError',
|
|
94
|
+
'RangeError',
|
|
95
|
+
'ReferenceError',
|
|
96
|
+
'SyntaxError',
|
|
97
|
+
'TypeError',
|
|
98
|
+
'URIError',
|
|
99
|
+
'Number',
|
|
100
|
+
'BigInt',
|
|
101
|
+
'Math',
|
|
102
|
+
'Date',
|
|
103
|
+
'String',
|
|
104
|
+
'RegExp',
|
|
105
|
+
'Array',
|
|
106
|
+
'Int8Array',
|
|
107
|
+
'Uint8Array',
|
|
108
|
+
'Uint8ClampedArray',
|
|
109
|
+
'Int16Array',
|
|
110
|
+
'Uint16Array',
|
|
111
|
+
'Int32Array',
|
|
112
|
+
'Uint32Array',
|
|
113
|
+
'BigInt64Array',
|
|
114
|
+
'BigUint64Array',
|
|
115
|
+
'Float16Array',
|
|
116
|
+
'Float32Array',
|
|
117
|
+
'Float64Array',
|
|
118
|
+
'Map',
|
|
119
|
+
'Set',
|
|
120
|
+
'WeakMap',
|
|
121
|
+
'WeakSet',
|
|
122
|
+
'ArrayBuffer',
|
|
123
|
+
'SharedArrayBuffer',
|
|
124
|
+
'DataView',
|
|
125
|
+
'Atomics',
|
|
126
|
+
'JSON',
|
|
127
|
+
'Promise',
|
|
128
|
+
'Reflect',
|
|
129
|
+
'Proxy',
|
|
130
|
+
'WeakRef',
|
|
131
|
+
'FinalizationRegistry',
|
|
132
|
+
'Intl',
|
|
133
|
+
'Iterator',
|
|
134
|
+
'AsyncIterator',
|
|
135
|
+
'DisposableStack',
|
|
136
|
+
'AsyncDisposableStack',
|
|
137
|
+
'isFinite',
|
|
138
|
+
'isNaN',
|
|
139
|
+
'parseFloat',
|
|
140
|
+
'parseInt',
|
|
141
|
+
'decodeURI',
|
|
142
|
+
'decodeURIComponent',
|
|
143
|
+
'encodeURI',
|
|
144
|
+
'encodeURIComponent',
|
|
145
|
+
'escape',
|
|
146
|
+
'unescape',
|
|
147
|
+
]);
|
|
59
148
|
const SKIP_KEYS = new Set([
|
|
60
149
|
'type',
|
|
61
150
|
'start',
|
|
@@ -72,8 +161,12 @@ const SKIP_KEYS = new Set([
|
|
|
72
161
|
]);
|
|
73
162
|
|
|
74
163
|
export const STRONG_RENDER_STATE_UPDATE = 'OCTANE_STRONG_RENDER_STATE_UPDATE';
|
|
164
|
+
export const STRONG_RENDER_STATE_GETTER_CALL = 'OCTANE_STRONG_RENDER_STATE_GETTER_CALL';
|
|
165
|
+
export const STRONG_RENDER_MODULE_STATE_READ = 'OCTANE_STRONG_RENDER_MODULE_STATE_READ';
|
|
166
|
+
export const STRONG_RENDER_AMBIENT_READ = 'OCTANE_STRONG_RENDER_AMBIENT_READ';
|
|
75
167
|
export const STRONG_EFFECT_STATE_UPDATE = 'OCTANE_STRONG_EFFECT_STATE_UPDATE';
|
|
76
168
|
export const STRONG_RENDER_REF_WRITE = 'OCTANE_STRONG_RENDER_REF_WRITE';
|
|
169
|
+
export const STRONG_RENDER_REF_READ = 'OCTANE_STRONG_RENDER_REF_READ';
|
|
77
170
|
export const STRONG_RENDER_SNAPSHOT_MUTATION = 'OCTANE_STRONG_RENDER_SNAPSHOT_MUTATION';
|
|
78
171
|
export const STRONG_RETAINED_ROW_MUTATION = 'OCTANE_STRONG_RETAINED_ROW_MUTATION';
|
|
79
172
|
export const STRONG_RENDER_IMPURE_CALL = 'OCTANE_STRONG_RENDER_IMPURE_CALL';
|
|
@@ -1200,6 +1293,40 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
1200
1293
|
return reassignedBindings.has(identifier);
|
|
1201
1294
|
}
|
|
1202
1295
|
const moduleScope = createScope(null, 'module', ast.body ?? [], [], isReassigned);
|
|
1296
|
+
const mutableModuleDeclarations = new Map();
|
|
1297
|
+
const reassignedModuleNames = new Map();
|
|
1298
|
+
const moduleAmbientAliases = new Map();
|
|
1299
|
+
const activeAmbientAliases = new Set();
|
|
1300
|
+
function recordModuleAmbientAliases(pattern, initializer, properties = []) {
|
|
1301
|
+
if (pattern?.type === 'Identifier') {
|
|
1302
|
+
moduleAmbientAliases.set(pattern.name, { initializer, properties });
|
|
1303
|
+
} else if (pattern?.type === 'AssignmentPattern') {
|
|
1304
|
+
recordModuleAmbientAliases(pattern.left, initializer, properties);
|
|
1305
|
+
} else if (pattern?.type === 'ObjectPattern') {
|
|
1306
|
+
for (const property of pattern.properties ?? []) {
|
|
1307
|
+
if (property.type === 'Property') {
|
|
1308
|
+
recordModuleAmbientAliases(property.value, initializer, [...properties, property]);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
function recordMutableModulePattern(pattern) {
|
|
1314
|
+
if (pattern?.type === 'Identifier') {
|
|
1315
|
+
let declarations = mutableModuleDeclarations.get(pattern.name);
|
|
1316
|
+
if (declarations === undefined) {
|
|
1317
|
+
mutableModuleDeclarations.set(pattern.name, (declarations = []));
|
|
1318
|
+
}
|
|
1319
|
+
declarations.push(pattern);
|
|
1320
|
+
} else if (pattern?.type === 'AssignmentPattern' || pattern?.type === 'RestElement') {
|
|
1321
|
+
recordMutableModulePattern(pattern.left ?? pattern.argument);
|
|
1322
|
+
} else if (pattern?.type === 'ArrayPattern') {
|
|
1323
|
+
for (const element of pattern.elements ?? []) recordMutableModulePattern(element);
|
|
1324
|
+
} else if (pattern?.type === 'ObjectPattern') {
|
|
1325
|
+
for (const property of pattern.properties ?? []) {
|
|
1326
|
+
recordMutableModulePattern(property.argument ?? property.value);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1203
1330
|
const activeCallbacks = new Set();
|
|
1204
1331
|
const activeReturnCallbacks = new Set();
|
|
1205
1332
|
const callResults = new WeakMap();
|
|
@@ -1251,7 +1378,23 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
1251
1378
|
for (const statement of ast.body ?? []) {
|
|
1252
1379
|
const declaration = declarationOf(statement);
|
|
1253
1380
|
if (declaration?.type === 'VariableDeclaration') {
|
|
1381
|
+
if (declaration.kind === 'let' && declaration.declare !== true) {
|
|
1382
|
+
for (const binding of declaration.declarations ?? []) {
|
|
1383
|
+
recordMutableModulePattern(binding.id);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1254
1386
|
for (const binding of declaration.declarations ?? []) {
|
|
1387
|
+
const initial = unwrap(binding.init);
|
|
1388
|
+
if (
|
|
1389
|
+
declaration.kind === 'const' &&
|
|
1390
|
+
(initial?.type === 'Identifier' ||
|
|
1391
|
+
initial?.type === 'MemberExpression' ||
|
|
1392
|
+
initial?.type === 'Literal' ||
|
|
1393
|
+
initial?.type === 'TemplateLiteral' ||
|
|
1394
|
+
initial?.type === 'BinaryExpression')
|
|
1395
|
+
) {
|
|
1396
|
+
recordModuleAmbientAliases(binding.id, initial);
|
|
1397
|
+
}
|
|
1255
1398
|
if (binding.id?.type === 'Identifier') {
|
|
1256
1399
|
addNamedRenderRoot(unwrap(binding.init), binding.id.name);
|
|
1257
1400
|
}
|
|
@@ -1266,6 +1409,7 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
1266
1409
|
let returnCycles = 0;
|
|
1267
1410
|
let currentFunctionIsAsync = false;
|
|
1268
1411
|
let currentFunctionChecksImpureCalls = false;
|
|
1412
|
+
let currentFunctionChecksRenderReads = false;
|
|
1269
1413
|
let currentRetainedRowScope = null;
|
|
1270
1414
|
|
|
1271
1415
|
function predeclareHoistedVars(node, scope) {
|
|
@@ -1296,6 +1440,9 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
1296
1440
|
if (value.type === 'VariableDeclaration' && value.kind === 'var') {
|
|
1297
1441
|
for (const declaration of value.declarations ?? []) {
|
|
1298
1442
|
addPatternNames(declaration.id, names, OTHER_BINDING);
|
|
1443
|
+
if (scope === moduleScope && value.declare !== true) {
|
|
1444
|
+
recordMutableModulePattern(declaration.id);
|
|
1445
|
+
}
|
|
1299
1446
|
}
|
|
1300
1447
|
}
|
|
1301
1448
|
for (const key in value) {
|
|
@@ -1328,6 +1475,170 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
1328
1475
|
report(code, node, message, [{ hook: 'useLinkedState' }]);
|
|
1329
1476
|
}
|
|
1330
1477
|
|
|
1478
|
+
function reportStateGetterCall(node) {
|
|
1479
|
+
report(
|
|
1480
|
+
STRONG_RENDER_STATE_GETTER_CALL,
|
|
1481
|
+
node,
|
|
1482
|
+
'Strong mode does not allow calling a state getter during render. Render from the state tuple snapshot; call the getter in an event, effect, or deferred callback for the latest scheduled state.',
|
|
1483
|
+
);
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
function reportModuleStateRead(node) {
|
|
1487
|
+
report(
|
|
1488
|
+
STRONG_RENDER_MODULE_STATE_READ,
|
|
1489
|
+
node,
|
|
1490
|
+
'Strong mode does not allow reading a reassigned module variable during render. Move the value into state or context, or pass an immutable snapshot as a prop.',
|
|
1491
|
+
);
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
function reportAmbientRead(node) {
|
|
1495
|
+
report(
|
|
1496
|
+
STRONG_RENDER_AMBIENT_READ,
|
|
1497
|
+
node,
|
|
1498
|
+
'Strong mode does not allow reading browser globals during render. Use useSyncExternalStore with a server snapshot for live browser state, or read it in an effect or a lazy state initializer. Lazy initializers still need to handle server rendering.',
|
|
1499
|
+
);
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
function ambientMemberBinding(object, key) {
|
|
1503
|
+
if (object === GLOBAL_OBJECT_BINDING || object === AMBIENT_GLOBAL_BINDINGS.get('window')) {
|
|
1504
|
+
if (key === 'globalThis') return GLOBAL_OBJECT_BINDING;
|
|
1505
|
+
return AMBIENT_GLOBAL_BINDINGS.get(key) ?? null;
|
|
1506
|
+
}
|
|
1507
|
+
return null;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
function ambientKeyValue(expression, scope) {
|
|
1511
|
+
const node = unwrap(expression);
|
|
1512
|
+
if (node?.type !== 'Identifier' || resolveScope(scope, node.name) !== moduleScope)
|
|
1513
|
+
return UNKNOWN_PRIMITIVE;
|
|
1514
|
+
const alias = moduleAmbientAliases.get(node.name);
|
|
1515
|
+
if (
|
|
1516
|
+
alias === undefined ||
|
|
1517
|
+
alias.properties.length !== 0 ||
|
|
1518
|
+
activeAmbientAliases.has(node.name)
|
|
1519
|
+
) {
|
|
1520
|
+
return UNKNOWN_PRIMITIVE;
|
|
1521
|
+
}
|
|
1522
|
+
// A render root can appear before the module constant used as its key.
|
|
1523
|
+
activeAmbientAliases.add(node.name);
|
|
1524
|
+
try {
|
|
1525
|
+
return staticPrimitiveValue(alias.initializer, moduleScope, true);
|
|
1526
|
+
} finally {
|
|
1527
|
+
activeAmbientAliases.delete(node.name);
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
function ambientPropertyKey(property, scope) {
|
|
1532
|
+
const key = property.type === 'Property' ? property.key : property.property;
|
|
1533
|
+
return property.computed ? staticPrimitiveValue(key, scope, true) : (key?.name ?? key?.value);
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
function ambientReference(expression, scope) {
|
|
1537
|
+
const node = unwrap(expression);
|
|
1538
|
+
if (node?.type === 'Identifier' || node?.type === 'JSXIdentifier') {
|
|
1539
|
+
const binding = resolve(scope, node.name);
|
|
1540
|
+
if (binding?.kind === 'ambient' || binding === GLOBAL_OBJECT_BINDING) return binding;
|
|
1541
|
+
if (binding === null) {
|
|
1542
|
+
return node.name === 'globalThis'
|
|
1543
|
+
? GLOBAL_OBJECT_BINDING
|
|
1544
|
+
: (AMBIENT_GLOBAL_BINDINGS.get(node.name) ?? null);
|
|
1545
|
+
}
|
|
1546
|
+
const alias = moduleAmbientAliases.get(node.name);
|
|
1547
|
+
if (
|
|
1548
|
+
alias !== undefined &&
|
|
1549
|
+
resolveScope(scope, node.name) === moduleScope &&
|
|
1550
|
+
!activeAmbientAliases.has(node.name)
|
|
1551
|
+
) {
|
|
1552
|
+
// Module functions can precede their const aliases. Resolve only
|
|
1553
|
+
// browser handles, never taint a copied scalar module snapshot.
|
|
1554
|
+
activeAmbientAliases.add(node.name);
|
|
1555
|
+
try {
|
|
1556
|
+
let value = ambientReference(alias.initializer, moduleScope);
|
|
1557
|
+
for (const property of alias.properties) {
|
|
1558
|
+
value = ambientMemberBinding(value, ambientPropertyKey(property, moduleScope));
|
|
1559
|
+
}
|
|
1560
|
+
return value;
|
|
1561
|
+
} finally {
|
|
1562
|
+
activeAmbientAliases.delete(node.name);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
return null;
|
|
1566
|
+
}
|
|
1567
|
+
if (node?.type === 'MemberExpression' || node?.type === 'JSXMemberExpression') {
|
|
1568
|
+
const object = ambientReference(node.object, scope);
|
|
1569
|
+
return object === GLOBAL_OBJECT_BINDING || object === AMBIENT_GLOBAL_BINDINGS.get('window')
|
|
1570
|
+
? ambientMemberBinding(object, ambientPropertyKey(node, scope))
|
|
1571
|
+
: null;
|
|
1572
|
+
}
|
|
1573
|
+
return null;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
function isAmbientMemberRead(node, scope) {
|
|
1577
|
+
const value = unwrap(node);
|
|
1578
|
+
if (value?.type !== 'MemberExpression' && value?.type !== 'JSXMemberExpression') return false;
|
|
1579
|
+
return (
|
|
1580
|
+
ambientReference(value.object, scope) === GLOBAL_OBJECT_BINDING &&
|
|
1581
|
+
!LANGUAGE_GLOBALS.has(ambientPropertyKey(value, scope))
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
function bindAmbientPattern(pattern, value, scope, bind) {
|
|
1586
|
+
if (value !== GLOBAL_OBJECT_BINDING && value?.kind !== 'ambient') return;
|
|
1587
|
+
if (pattern?.type === 'Identifier') {
|
|
1588
|
+
bind(pattern, value);
|
|
1589
|
+
} else if (pattern?.type === 'AssignmentPattern') {
|
|
1590
|
+
bindAmbientPattern(pattern.left, value, scope, bind);
|
|
1591
|
+
} else if (pattern?.type === 'ObjectPattern') {
|
|
1592
|
+
for (const property of pattern.properties ?? []) {
|
|
1593
|
+
if (property.type !== 'Property') continue;
|
|
1594
|
+
bindAmbientPattern(
|
|
1595
|
+
property.value,
|
|
1596
|
+
ambientMemberBinding(value, ambientPropertyKey(property, scope)),
|
|
1597
|
+
scope,
|
|
1598
|
+
bind,
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
function mutableModuleRead(name, scope) {
|
|
1605
|
+
const declarations = mutableModuleDeclarations.get(name);
|
|
1606
|
+
if (declarations === undefined || resolveScope(scope, name) !== moduleScope) {
|
|
1607
|
+
return false;
|
|
1608
|
+
}
|
|
1609
|
+
let reassigned = reassignedModuleNames.get(name);
|
|
1610
|
+
if (reassigned === undefined) {
|
|
1611
|
+
reassigned = declarations.some(isReassigned);
|
|
1612
|
+
reassignedModuleNames.set(name, reassigned);
|
|
1613
|
+
}
|
|
1614
|
+
return reassigned;
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
function definitelySkippedOptionalContinuation(expression, scope) {
|
|
1618
|
+
let node = expression;
|
|
1619
|
+
while (node != null) {
|
|
1620
|
+
// Grouping ends the chain; the caller decides whether a grouped
|
|
1621
|
+
// undefined value is itself behind an optional access.
|
|
1622
|
+
if (node.type === 'ChainExpression') return false;
|
|
1623
|
+
if (TRANSPARENT_EXPRESSIONS.has(node.type)) {
|
|
1624
|
+
node = node.expression;
|
|
1625
|
+
continue;
|
|
1626
|
+
}
|
|
1627
|
+
if (node.type !== 'CallExpression' && node.type !== 'MemberExpression') return false;
|
|
1628
|
+
const target = node.type === 'CallExpression' ? node.callee : node.object;
|
|
1629
|
+
if (node.optional === true && definitelyNullishOptionalValue(target, scope)) return true;
|
|
1630
|
+
node = target;
|
|
1631
|
+
}
|
|
1632
|
+
return false;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
function definitelyNullishOptionalValue(expression, scope) {
|
|
1636
|
+
return (
|
|
1637
|
+
(staticExpressionValue(expression, scope) & ~NULLISH_VALUE) === 0 ||
|
|
1638
|
+
definitelySkippedOptionalContinuation(unwrap(expression), scope)
|
|
1639
|
+
);
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1331
1642
|
function reportRef(node) {
|
|
1332
1643
|
report(
|
|
1333
1644
|
STRONG_RENDER_REF_WRITE,
|
|
@@ -1336,6 +1647,32 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
1336
1647
|
);
|
|
1337
1648
|
}
|
|
1338
1649
|
|
|
1650
|
+
function reportRefRead(node) {
|
|
1651
|
+
report(
|
|
1652
|
+
STRONG_RENDER_REF_READ,
|
|
1653
|
+
node,
|
|
1654
|
+
'Strong mode does not allow reading useRef.current during render. Read the ref in an event or effect, or use state or useLinkedState for values that drive render output.',
|
|
1655
|
+
);
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
function isRefObject(value, scope) {
|
|
1659
|
+
const object = unwrap(value);
|
|
1660
|
+
if (object?.type === 'Identifier') return resolve(scope, object.name)?.kind === 'ref';
|
|
1661
|
+
if (object?.type === 'SequenceExpression') {
|
|
1662
|
+
return isRefObject(object.expressions?.[object.expressions.length - 1], scope);
|
|
1663
|
+
}
|
|
1664
|
+
return object?.type === 'CallExpression' && importedHook(object.callee, scope) === 'useRef';
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
function readCurrentRef(member, scope) {
|
|
1668
|
+
const value = unwrap(member);
|
|
1669
|
+
if (value?.type !== 'MemberExpression') return false;
|
|
1670
|
+
const isCurrent = value.computed
|
|
1671
|
+
? staticPrimitiveValue(value.property, scope) === 'current'
|
|
1672
|
+
: value.property?.type === 'Identifier' && value.property.name === 'current';
|
|
1673
|
+
return isCurrent && isRefObject(value.object, scope);
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1339
1676
|
function reportSnapshotMutation(node) {
|
|
1340
1677
|
report(
|
|
1341
1678
|
STRONG_RENDER_SNAPSHOT_MUTATION,
|
|
@@ -1770,6 +2107,7 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
1770
2107
|
}
|
|
1771
2108
|
|
|
1772
2109
|
function definitelyDefined(value) {
|
|
2110
|
+
if (value === GLOBAL_OBJECT_BINDING) return true;
|
|
1773
2111
|
if (value?.kind === 'constant' && value.primitive !== UNKNOWN_PRIMITIVE) {
|
|
1774
2112
|
return value.primitive !== undefined;
|
|
1775
2113
|
}
|
|
@@ -1784,6 +2122,11 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
1784
2122
|
function visitParameters(node, parentScope, phase, args) {
|
|
1785
2123
|
const parameters = node.params ?? [];
|
|
1786
2124
|
const parameterScope = createScope(parentScope, 'function', [], parameters);
|
|
2125
|
+
function bindGetter(identifier, binding) {
|
|
2126
|
+
if (identifier?.type === 'Identifier' && !isReassigned(identifier)) {
|
|
2127
|
+
parameterScope.bindings.set(identifier.name, binding);
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
1787
2130
|
if (
|
|
1788
2131
|
node.type === 'FunctionExpression' &&
|
|
1789
2132
|
node.id?.name &&
|
|
@@ -1796,17 +2139,38 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
1796
2139
|
if (parameter.type === 'TSParameterProperty') parameter = parameter.parameter;
|
|
1797
2140
|
let value = args === null ? OTHER_BINDING : (args[index] ?? UNDEFINED_BINDING);
|
|
1798
2141
|
if (parameter.type === 'AssignmentPattern') {
|
|
1799
|
-
|
|
1800
|
-
if (
|
|
2142
|
+
const usesDefault = !definitelyDefined(value);
|
|
2143
|
+
if (usesDefault) visit(parameter.right, parameterScope, phase);
|
|
2144
|
+
const defaultValue = usesDefault
|
|
2145
|
+
? expressionBinding(parameter.right, parameterScope)
|
|
2146
|
+
: null;
|
|
1801
2147
|
value =
|
|
1802
2148
|
value.kind === 'constant' && value.primitive === undefined
|
|
1803
|
-
?
|
|
2149
|
+
? defaultValue
|
|
1804
2150
|
: definitelyDefined(value)
|
|
1805
2151
|
? value
|
|
1806
|
-
:
|
|
2152
|
+
: defaultValue?.kind === 'getter' ||
|
|
2153
|
+
defaultValue?.kind === 'state-tuple' ||
|
|
2154
|
+
defaultValue === GLOBAL_OBJECT_BINDING ||
|
|
2155
|
+
defaultValue?.kind === 'ambient'
|
|
2156
|
+
? defaultValue
|
|
2157
|
+
: OTHER_BINDING;
|
|
2158
|
+
visitPatternExpressions(
|
|
2159
|
+
parameter.left,
|
|
2160
|
+
parameterScope,
|
|
2161
|
+
usesDefault ? phaseAfter(parameter.right, phase) : phase,
|
|
2162
|
+
parameter.left.type === 'ObjectPattern' &&
|
|
2163
|
+
(value?.kind === 'ref' ||
|
|
2164
|
+
(usesDefault && isRefObject(parameter.right, parameterScope))),
|
|
2165
|
+
value,
|
|
2166
|
+
);
|
|
2167
|
+
bindStateGetterPattern(parameter.left, value, parameterScope, bindGetter);
|
|
2168
|
+
bindAmbientPattern(parameter.left, value, parameterScope, bindGetter);
|
|
1807
2169
|
parameter = parameter.left;
|
|
1808
2170
|
} else {
|
|
1809
|
-
visitPatternExpressions(parameter, parameterScope, phase);
|
|
2171
|
+
visitPatternExpressions(parameter, parameterScope, phase, value?.kind === 'ref', value);
|
|
2172
|
+
bindStateGetterPattern(parameter, value, parameterScope, bindGetter);
|
|
2173
|
+
bindAmbientPattern(parameter, value, parameterScope, bindGetter);
|
|
1810
2174
|
}
|
|
1811
2175
|
if (parameter.type === 'Identifier' && !isReassigned(parameter)) {
|
|
1812
2176
|
parameterScope.bindings.set(parameter.name, value);
|
|
@@ -1818,11 +2182,12 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
1818
2182
|
function visitFunction(node, parentScope, phase, args = null) {
|
|
1819
2183
|
const enclosingFunctionIsAsync = currentFunctionIsAsync;
|
|
1820
2184
|
const enclosingFunctionChecksImpureCalls = currentFunctionChecksImpureCalls;
|
|
2185
|
+
const enclosingFunctionChecksRenderReads = currentFunctionChecksRenderReads;
|
|
1821
2186
|
currentFunctionIsAsync = node.async === true;
|
|
1822
2187
|
if (phase === 'render' && !activeCallbacks.has(node)) {
|
|
1823
2188
|
// Ordinary module helpers may be used only by events. Check their
|
|
1824
2189
|
// standard calls when a known render root invokes them synchronously.
|
|
1825
|
-
currentFunctionChecksImpureCalls =
|
|
2190
|
+
currentFunctionChecksImpureCalls = currentFunctionChecksRenderReads =
|
|
1826
2191
|
renderRoots.has(node) || node.body?.type === 'JSXCodeBlock';
|
|
1827
2192
|
}
|
|
1828
2193
|
try {
|
|
@@ -1854,38 +2219,74 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
1854
2219
|
} finally {
|
|
1855
2220
|
currentFunctionIsAsync = enclosingFunctionIsAsync;
|
|
1856
2221
|
currentFunctionChecksImpureCalls = enclosingFunctionChecksImpureCalls;
|
|
2222
|
+
currentFunctionChecksRenderReads = enclosingFunctionChecksRenderReads;
|
|
1857
2223
|
}
|
|
1858
2224
|
}
|
|
1859
2225
|
|
|
1860
|
-
function visitPatternExpressions(pattern, scope, phase) {
|
|
2226
|
+
function visitPatternExpressions(pattern, scope, phase, refSource = false, ambientSource = null) {
|
|
1861
2227
|
let executionPhase = phase;
|
|
1862
2228
|
if (pattern?.type === 'TSParameterProperty') {
|
|
1863
|
-
return visitPatternExpressions(pattern.parameter, scope, phase);
|
|
2229
|
+
return visitPatternExpressions(pattern.parameter, scope, phase, refSource, ambientSource);
|
|
1864
2230
|
} else if (pattern?.type === 'Identifier') {
|
|
1865
2231
|
if (executionPhase === 'render') reportRetainedRowMutation(pattern, scope);
|
|
1866
2232
|
} else if (pattern?.type === 'AssignmentPattern') {
|
|
1867
|
-
executionPhase = visitPatternExpressions(
|
|
2233
|
+
executionPhase = visitPatternExpressions(
|
|
2234
|
+
pattern.left,
|
|
2235
|
+
scope,
|
|
2236
|
+
executionPhase,
|
|
2237
|
+
refSource,
|
|
2238
|
+
ambientSource,
|
|
2239
|
+
);
|
|
1868
2240
|
visit(pattern.right, scope, executionPhase);
|
|
1869
2241
|
} else if (pattern?.type === 'ArrayPattern') {
|
|
1870
2242
|
for (const element of pattern.elements ?? []) {
|
|
1871
2243
|
executionPhase = visitPatternExpressions(element, scope, executionPhase);
|
|
1872
2244
|
}
|
|
1873
2245
|
} else if (pattern?.type === 'ObjectPattern') {
|
|
2246
|
+
let currentExcluded = false;
|
|
1874
2247
|
for (const property of pattern.properties ?? []) {
|
|
1875
2248
|
if (property.computed) {
|
|
1876
2249
|
visit(property.key, scope, executionPhase);
|
|
1877
2250
|
executionPhase = phaseAfter(property.key, executionPhase);
|
|
1878
2251
|
}
|
|
2252
|
+
if (
|
|
2253
|
+
ambientSource === GLOBAL_OBJECT_BINDING &&
|
|
2254
|
+
executionPhase === 'render' &&
|
|
2255
|
+
currentFunctionChecksImpureCalls &&
|
|
2256
|
+
(property.type === 'RestElement' ||
|
|
2257
|
+
!LANGUAGE_GLOBALS.has(ambientPropertyKey(property, scope)))
|
|
2258
|
+
) {
|
|
2259
|
+
reportAmbientRead(property.type === 'RestElement' ? property : property.key);
|
|
2260
|
+
}
|
|
2261
|
+
const currentProperty =
|
|
2262
|
+
refSource &&
|
|
2263
|
+
property.type === 'Property' &&
|
|
2264
|
+
(property.computed
|
|
2265
|
+
? staticPrimitiveValue(property.key, scope)
|
|
2266
|
+
: (property.key?.name ?? property.key?.value)) === 'current';
|
|
2267
|
+
if (
|
|
2268
|
+
refSource &&
|
|
2269
|
+
executionPhase === 'render' &&
|
|
2270
|
+
currentFunctionChecksRenderReads &&
|
|
2271
|
+
(currentProperty || (property.type === 'RestElement' && !currentExcluded))
|
|
2272
|
+
) {
|
|
2273
|
+
reportRefRead(currentProperty ? property.key : property);
|
|
2274
|
+
}
|
|
2275
|
+
if (currentProperty) currentExcluded = true;
|
|
1879
2276
|
executionPhase = visitPatternExpressions(
|
|
1880
2277
|
property.argument ?? property.value,
|
|
1881
2278
|
scope,
|
|
1882
2279
|
executionPhase,
|
|
2280
|
+
false,
|
|
2281
|
+
ambientSource === null
|
|
2282
|
+
? null
|
|
2283
|
+
: ambientMemberBinding(ambientSource, ambientPropertyKey(property, scope)),
|
|
1883
2284
|
);
|
|
1884
2285
|
}
|
|
1885
2286
|
} else if (pattern?.type === 'RestElement') {
|
|
1886
2287
|
executionPhase = visitPatternExpressions(pattern.argument, scope, executionPhase);
|
|
1887
2288
|
} else if (pattern?.type === 'MemberExpression') {
|
|
1888
|
-
visit(pattern, scope, executionPhase);
|
|
2289
|
+
visit(pattern, scope, executionPhase, false);
|
|
1889
2290
|
executionPhase = phaseAfter(pattern, executionPhase, true);
|
|
1890
2291
|
if (executionPhase === 'render') {
|
|
1891
2292
|
reportSnapshotWrite(pattern, scope);
|
|
@@ -1895,6 +2296,26 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
1895
2296
|
return executionPhase;
|
|
1896
2297
|
}
|
|
1897
2298
|
|
|
2299
|
+
function bindStateGetterPattern(pattern, tuple, scope, bind) {
|
|
2300
|
+
if (tuple?.kind !== 'state-tuple') return;
|
|
2301
|
+
if (pattern?.type === 'ArrayPattern') {
|
|
2302
|
+
const element = pattern.elements?.[2];
|
|
2303
|
+
bind(element?.type === 'AssignmentPattern' ? element.left : element, tuple.getter);
|
|
2304
|
+
} else if (pattern?.type === 'ObjectPattern') {
|
|
2305
|
+
for (const property of pattern.properties ?? []) {
|
|
2306
|
+
if (property.type !== 'Property') continue;
|
|
2307
|
+
const key = property.computed
|
|
2308
|
+
? staticPrimitiveValue(property.key, scope)
|
|
2309
|
+
: property.key?.type === 'Identifier'
|
|
2310
|
+
? property.key.name
|
|
2311
|
+
: property.key?.value;
|
|
2312
|
+
if (key !== 2 && key !== '2') continue;
|
|
2313
|
+
const value = property.value;
|
|
2314
|
+
bind(value?.type === 'AssignmentPattern' ? value.left : value, tuple.getter);
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
|
|
1898
2319
|
function bindDeclarationValue(declaration, declarationKind, scope) {
|
|
1899
2320
|
const target = declarationScope(scope, declarationKind);
|
|
1900
2321
|
const initial = unwrap(declaration.init);
|
|
@@ -1905,6 +2326,11 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
1905
2326
|
declarationKind === 'const' || !isReassigned(identifier) ? value : OTHER_BINDING,
|
|
1906
2327
|
);
|
|
1907
2328
|
}
|
|
2329
|
+
const ambient = ambientReference(initial, scope);
|
|
2330
|
+
if (ambient !== null) {
|
|
2331
|
+
bindAmbientPattern(declaration.id, ambient, scope, bind);
|
|
2332
|
+
return;
|
|
2333
|
+
}
|
|
1908
2334
|
const stateTuple = stateTupleBinding(initial, scope);
|
|
1909
2335
|
const snapshot =
|
|
1910
2336
|
stateTuple === null && declarationKind === 'const' ? snapshotBinding(initial, scope) : null;
|
|
@@ -1913,6 +2339,7 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
1913
2339
|
const element = declaration.id.elements?.[1];
|
|
1914
2340
|
const setter = element?.type === 'AssignmentPattern' ? element.left : element;
|
|
1915
2341
|
bind(setter, { kind: 'setter' });
|
|
2342
|
+
bindStateGetterPattern(declaration.id, stateTuple, scope, bind);
|
|
1916
2343
|
} else if (declaration.id?.type === 'ObjectPattern' && stateTuple) {
|
|
1917
2344
|
for (const property of declaration.id.properties ?? []) {
|
|
1918
2345
|
if (property.type !== 'Property') continue;
|
|
@@ -1929,6 +2356,7 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
1929
2356
|
bindSnapshotPattern(value, stateTuple.snapshot, bind);
|
|
1930
2357
|
}
|
|
1931
2358
|
}
|
|
2359
|
+
bindStateGetterPattern(declaration.id, stateTuple, scope, bind);
|
|
1932
2360
|
} else if (declaration.id?.type === 'Identifier') {
|
|
1933
2361
|
if (stateTuple) {
|
|
1934
2362
|
bind(declaration.id, stateTuple);
|
|
@@ -1939,12 +2367,15 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
1939
2367
|
bind(declaration.id, { kind: 'ref' });
|
|
1940
2368
|
} else if (declarationKind === 'const' && stateTupleUpdater(initial, scope)) {
|
|
1941
2369
|
target.bindings.set(declaration.id.name, { kind: 'setter' });
|
|
2370
|
+
} else if (declarationKind === 'const' && stateTupleGetter(initial, scope)) {
|
|
2371
|
+
target.bindings.set(declaration.id.name, STATE_GETTER_BINDING);
|
|
1942
2372
|
} else if (snapshot !== null) {
|
|
1943
2373
|
target.bindings.set(declaration.id.name, snapshot);
|
|
1944
2374
|
} else if (declarationKind === 'const' && initial?.type === 'Identifier') {
|
|
1945
2375
|
const value = resolve(scope, initial.name);
|
|
1946
2376
|
if (
|
|
1947
2377
|
value?.kind === 'setter' ||
|
|
2378
|
+
value?.kind === 'getter' ||
|
|
1948
2379
|
value?.kind === 'ref' ||
|
|
1949
2380
|
value?.kind === 'callback' ||
|
|
1950
2381
|
value?.kind === 'callback-choice' ||
|
|
@@ -2038,7 +2469,13 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
2038
2469
|
function bindDeclaration(declaration, declarationKind, scope, phase) {
|
|
2039
2470
|
bindDeclarationValue(declaration, declarationKind, scope);
|
|
2040
2471
|
visit(declaration.init, scope, phase);
|
|
2041
|
-
return visitPatternExpressions(
|
|
2472
|
+
return visitPatternExpressions(
|
|
2473
|
+
declaration.id,
|
|
2474
|
+
scope,
|
|
2475
|
+
phaseAfter(declaration.init, phase),
|
|
2476
|
+
declaration.id?.type === 'ObjectPattern' && isRefObject(declaration.init, scope),
|
|
2477
|
+
declaration.id?.type === 'ObjectPattern' ? ambientReference(declaration.init, scope) : null,
|
|
2478
|
+
);
|
|
2042
2479
|
}
|
|
2043
2480
|
|
|
2044
2481
|
function visitCallback(node, parentScope, phase, args = null) {
|
|
@@ -2063,7 +2500,17 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
2063
2500
|
);
|
|
2064
2501
|
}
|
|
2065
2502
|
|
|
2066
|
-
function
|
|
2503
|
+
function stateTupleGetter(value, scope) {
|
|
2504
|
+
const member = unwrap(value);
|
|
2505
|
+
if (member?.type !== 'MemberExpression' || member.computed !== true) return false;
|
|
2506
|
+
const key = staticPrimitiveValue(member.property, scope);
|
|
2507
|
+
return (
|
|
2508
|
+
(key === 2 || key === '2') &&
|
|
2509
|
+
stateTupleBinding(member.object, scope)?.getter === STATE_GETTER_BINDING
|
|
2510
|
+
);
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
function staticPrimitiveValue(value, scope, moduleConstantKeys = false) {
|
|
2067
2514
|
const expression = unwrap(value);
|
|
2068
2515
|
if (expression?.type === 'CallExpression') {
|
|
2069
2516
|
const result = callResult(expression, scope);
|
|
@@ -2075,7 +2522,7 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
2075
2522
|
if (binding?.kind === 'linked-key') return binding.value;
|
|
2076
2523
|
if (binding?.kind === 'constant') return binding.primitive;
|
|
2077
2524
|
if (binding == null && expression.name === 'undefined') return undefined;
|
|
2078
|
-
return UNKNOWN_PRIMITIVE;
|
|
2525
|
+
return moduleConstantKeys ? ambientKeyValue(expression, scope) : UNKNOWN_PRIMITIVE;
|
|
2079
2526
|
}
|
|
2080
2527
|
if (expression?.type === 'TemplateLiteral') {
|
|
2081
2528
|
let result = '';
|
|
@@ -2084,7 +2531,11 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
2084
2531
|
if (text == null) return UNKNOWN_PRIMITIVE;
|
|
2085
2532
|
result += text;
|
|
2086
2533
|
if (index < (expression.expressions?.length ?? 0)) {
|
|
2087
|
-
const part = staticPrimitiveValue(
|
|
2534
|
+
const part = staticPrimitiveValue(
|
|
2535
|
+
expression.expressions[index],
|
|
2536
|
+
scope,
|
|
2537
|
+
moduleConstantKeys,
|
|
2538
|
+
);
|
|
2088
2539
|
if (
|
|
2089
2540
|
part === UNKNOWN_PRIMITIVE ||
|
|
2090
2541
|
(part !== null && typeof part === 'object') ||
|
|
@@ -2099,8 +2550,8 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
2099
2550
|
return result;
|
|
2100
2551
|
}
|
|
2101
2552
|
if (expression?.type === 'BinaryExpression' && expression.operator === '+') {
|
|
2102
|
-
const left = staticPrimitiveValue(expression.left, scope);
|
|
2103
|
-
const right = staticPrimitiveValue(expression.right, scope);
|
|
2553
|
+
const left = staticPrimitiveValue(expression.left, scope, moduleConstantKeys);
|
|
2554
|
+
const right = staticPrimitiveValue(expression.right, scope, moduleConstantKeys);
|
|
2104
2555
|
if (
|
|
2105
2556
|
left === UNKNOWN_PRIMITIVE ||
|
|
2106
2557
|
right === UNKNOWN_PRIMITIVE ||
|
|
@@ -2125,7 +2576,7 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
2125
2576
|
}
|
|
2126
2577
|
if (expression?.type === 'UnaryExpression') {
|
|
2127
2578
|
if (expression.operator === 'void') return undefined;
|
|
2128
|
-
const argument = staticPrimitiveValue(expression.argument, scope);
|
|
2579
|
+
const argument = staticPrimitiveValue(expression.argument, scope, moduleConstantKeys);
|
|
2129
2580
|
if (expression.operator === '!') {
|
|
2130
2581
|
if (argument !== UNKNOWN_PRIMITIVE) return !argument;
|
|
2131
2582
|
const value = staticExpressionValue(expression.argument, scope);
|
|
@@ -2151,22 +2602,22 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
2151
2602
|
}
|
|
2152
2603
|
if (expression?.type === 'SequenceExpression') {
|
|
2153
2604
|
const expressions = expression.expressions ?? [];
|
|
2154
|
-
return staticPrimitiveValue(expressions[expressions.length - 1], scope);
|
|
2605
|
+
return staticPrimitiveValue(expressions[expressions.length - 1], scope, moduleConstantKeys);
|
|
2155
2606
|
}
|
|
2156
2607
|
if (expression?.type === 'ConditionalExpression') {
|
|
2157
2608
|
const branches = conditionalExpressionBranches(expression, scope);
|
|
2158
2609
|
return branches === 1
|
|
2159
|
-
? staticPrimitiveValue(expression.consequent, scope)
|
|
2610
|
+
? staticPrimitiveValue(expression.consequent, scope, moduleConstantKeys)
|
|
2160
2611
|
: branches === 2
|
|
2161
|
-
? staticPrimitiveValue(expression.alternate, scope)
|
|
2612
|
+
? staticPrimitiveValue(expression.alternate, scope, moduleConstantKeys)
|
|
2162
2613
|
: UNKNOWN_PRIMITIVE;
|
|
2163
2614
|
}
|
|
2164
2615
|
if (expression?.type === 'LogicalExpression') {
|
|
2165
2616
|
const branches = logicalExpressionBranches(expression, scope);
|
|
2166
2617
|
return branches === 1
|
|
2167
|
-
? staticPrimitiveValue(expression.left, scope)
|
|
2618
|
+
? staticPrimitiveValue(expression.left, scope, moduleConstantKeys)
|
|
2168
2619
|
: branches === 2
|
|
2169
|
-
? staticPrimitiveValue(expression.right, scope)
|
|
2620
|
+
? staticPrimitiveValue(expression.right, scope, moduleConstantKeys)
|
|
2170
2621
|
: UNKNOWN_PRIMITIVE;
|
|
2171
2622
|
}
|
|
2172
2623
|
return UNKNOWN_PRIMITIVE;
|
|
@@ -2188,7 +2639,8 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
2188
2639
|
FUNCTION_TYPES.has(expression?.type) ||
|
|
2189
2640
|
expression?.type === 'ObjectExpression' ||
|
|
2190
2641
|
expression?.type === 'ArrayExpression' ||
|
|
2191
|
-
stateTupleUpdater(expression, scope)
|
|
2642
|
+
stateTupleUpdater(expression, scope) ||
|
|
2643
|
+
stateTupleGetter(expression, scope)
|
|
2192
2644
|
) {
|
|
2193
2645
|
return TRUTHY_VALUE;
|
|
2194
2646
|
} else if (
|
|
@@ -2207,6 +2659,7 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
2207
2659
|
binding?.kind === 'callback' ||
|
|
2208
2660
|
binding?.kind === 'effect-event' ||
|
|
2209
2661
|
binding?.kind === 'setter' ||
|
|
2662
|
+
binding?.kind === 'getter' ||
|
|
2210
2663
|
binding?.kind === 'ref' ||
|
|
2211
2664
|
binding?.kind === 'state-tuple' ||
|
|
2212
2665
|
binding?.kind === 'linked-key' ||
|
|
@@ -2305,7 +2758,8 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
2305
2758
|
value?.kind === 'callback' ||
|
|
2306
2759
|
value?.kind === 'callback-choice' ||
|
|
2307
2760
|
value?.kind === 'effect-event' ||
|
|
2308
|
-
value?.kind === 'setter'
|
|
2761
|
+
value?.kind === 'setter' ||
|
|
2762
|
+
value?.kind === 'getter'
|
|
2309
2763
|
);
|
|
2310
2764
|
}
|
|
2311
2765
|
|
|
@@ -2332,7 +2786,8 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
2332
2786
|
if (scopes === undefined) functions.set(callback.node, (scopes = new Set()));
|
|
2333
2787
|
scopes.add(callback.scope);
|
|
2334
2788
|
} else {
|
|
2335
|
-
const identity =
|
|
2789
|
+
const identity =
|
|
2790
|
+
callback.kind === 'setter' || callback.kind === 'getter' ? callback.kind : callback;
|
|
2336
2791
|
if (seen.has(identity)) return;
|
|
2337
2792
|
seen.add(identity);
|
|
2338
2793
|
}
|
|
@@ -2396,6 +2851,7 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
2396
2851
|
return isCallableValue(binding) ? binding : null;
|
|
2397
2852
|
}
|
|
2398
2853
|
if (stateTupleUpdater(node, scope)) return { kind: 'setter' };
|
|
2854
|
+
if (stateTupleGetter(node, scope)) return STATE_GETTER_BINDING;
|
|
2399
2855
|
if (node?.type === 'SequenceExpression') {
|
|
2400
2856
|
return callableValue(node.expressions?.[node.expressions.length - 1], scope);
|
|
2401
2857
|
}
|
|
@@ -2498,6 +2954,8 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
2498
2954
|
|
|
2499
2955
|
function expressionBinding(expression, scope) {
|
|
2500
2956
|
const node = unwrap(expression);
|
|
2957
|
+
const ambient = ambientReference(node, scope);
|
|
2958
|
+
if (ambient !== null) return ambient;
|
|
2501
2959
|
if (node?.type === 'Identifier') {
|
|
2502
2960
|
const binding = resolve(scope, node.name);
|
|
2503
2961
|
if (
|
|
@@ -2681,12 +3139,14 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
2681
3139
|
else if (phase === 'effect') visitCallable(value.callback, origin, phase, args);
|
|
2682
3140
|
} else if (value?.kind === 'setter' && (phase === 'render' || phase === 'effect')) {
|
|
2683
3141
|
reportSetter(origin, phase);
|
|
3142
|
+
} else if (value?.kind === 'getter' && phase === 'render' && currentFunctionChecksRenderReads) {
|
|
3143
|
+
reportStateGetterCall(origin);
|
|
2684
3144
|
}
|
|
2685
3145
|
}
|
|
2686
3146
|
|
|
2687
3147
|
function visitSynchronousHookCallback(value, scope, phase, stateInitializer = false) {
|
|
2688
3148
|
const checkImpureCalls = currentFunctionChecksImpureCalls;
|
|
2689
|
-
// Lazy state initialization may read a clock or
|
|
3149
|
+
// Lazy state initialization may read a clock, randomness, or browser state. Keep its
|
|
2690
3150
|
// existing state/ref/Effect Event checks at the synchronous render phase.
|
|
2691
3151
|
if (stateInitializer) currentFunctionChecksImpureCalls = false;
|
|
2692
3152
|
try {
|
|
@@ -2853,26 +3313,92 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
2853
3313
|
}
|
|
2854
3314
|
}
|
|
2855
3315
|
|
|
2856
|
-
function visit(node, scope, phase) {
|
|
3316
|
+
function visit(node, scope, phase, readAccess = true) {
|
|
2857
3317
|
if (node == null || typeof node !== 'object') return;
|
|
2858
3318
|
if (Array.isArray(node)) {
|
|
2859
3319
|
for (const child of node) visit(child, scope, phase);
|
|
2860
3320
|
return;
|
|
2861
3321
|
}
|
|
2862
3322
|
if (TRANSPARENT_EXPRESSIONS.has(node.type)) {
|
|
2863
|
-
visit(node.expression, scope, phase);
|
|
3323
|
+
visit(node.expression, scope, phase, readAccess);
|
|
2864
3324
|
return;
|
|
2865
3325
|
}
|
|
2866
3326
|
if (node.type?.startsWith('TS')) return;
|
|
2867
3327
|
|
|
2868
3328
|
switch (node.type) {
|
|
2869
3329
|
case 'ImportDeclaration':
|
|
2870
|
-
case '
|
|
3330
|
+
case 'MetaProperty':
|
|
3331
|
+
case 'BreakStatement':
|
|
3332
|
+
case 'ContinueStatement':
|
|
2871
3333
|
case 'JSXIdentifier':
|
|
2872
3334
|
case 'Literal':
|
|
2873
3335
|
case 'ThisExpression':
|
|
2874
3336
|
case 'Super':
|
|
2875
3337
|
return;
|
|
3338
|
+
case 'Identifier':
|
|
3339
|
+
if (
|
|
3340
|
+
readAccess &&
|
|
3341
|
+
phase === 'render' &&
|
|
3342
|
+
currentFunctionChecksImpureCalls &&
|
|
3343
|
+
ambientReference(node, scope)?.kind === 'ambient'
|
|
3344
|
+
) {
|
|
3345
|
+
reportAmbientRead(node);
|
|
3346
|
+
}
|
|
3347
|
+
if (
|
|
3348
|
+
readAccess &&
|
|
3349
|
+
phase === 'render' &&
|
|
3350
|
+
currentFunctionChecksRenderReads &&
|
|
3351
|
+
mutableModuleRead(node.name, scope)
|
|
3352
|
+
) {
|
|
3353
|
+
reportModuleStateRead(node);
|
|
3354
|
+
}
|
|
3355
|
+
return;
|
|
3356
|
+
case 'JSXOpeningElement': {
|
|
3357
|
+
let name = node.name;
|
|
3358
|
+
const member = name?.type === 'JSXMemberExpression';
|
|
3359
|
+
let ambientMember;
|
|
3360
|
+
while (name?.type === 'JSXMemberExpression') {
|
|
3361
|
+
if (
|
|
3362
|
+
phase === 'render' &&
|
|
3363
|
+
currentFunctionChecksImpureCalls &&
|
|
3364
|
+
isAmbientMemberRead(name, scope)
|
|
3365
|
+
) {
|
|
3366
|
+
ambientMember = name;
|
|
3367
|
+
}
|
|
3368
|
+
name = name.object;
|
|
3369
|
+
}
|
|
3370
|
+
if (
|
|
3371
|
+
name?.type === 'JSXIdentifier' &&
|
|
3372
|
+
(member || (!/^[a-z]/.test(name.name) && !name.name.includes('-'))) &&
|
|
3373
|
+
phase === 'render' &&
|
|
3374
|
+
currentFunctionChecksImpureCalls
|
|
3375
|
+
) {
|
|
3376
|
+
const reference = ambientReference(name, scope);
|
|
3377
|
+
if (reference?.kind === 'ambient' || ambientMember) {
|
|
3378
|
+
reportAmbientRead(ambientMember ?? name);
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
if (
|
|
3382
|
+
name?.type === 'JSXIdentifier' &&
|
|
3383
|
+
(member || (!/^[a-z]/.test(name.name) && !name.name.includes('-'))) &&
|
|
3384
|
+
phase === 'render' &&
|
|
3385
|
+
currentFunctionChecksRenderReads &&
|
|
3386
|
+
mutableModuleRead(name.name, scope)
|
|
3387
|
+
) {
|
|
3388
|
+
reportModuleStateRead(name);
|
|
3389
|
+
}
|
|
3390
|
+
break;
|
|
3391
|
+
}
|
|
3392
|
+
case 'JSXSpreadAttribute':
|
|
3393
|
+
visit(node.argument, scope, phase);
|
|
3394
|
+
if (
|
|
3395
|
+
currentFunctionChecksImpureCalls &&
|
|
3396
|
+
phaseAfter(node.argument, phase) === 'render' &&
|
|
3397
|
+
ambientReference(node.argument, scope) === GLOBAL_OBJECT_BINDING
|
|
3398
|
+
) {
|
|
3399
|
+
reportAmbientRead(node);
|
|
3400
|
+
}
|
|
3401
|
+
return;
|
|
2876
3402
|
case 'ExportNamedDeclaration':
|
|
2877
3403
|
case 'ExportDefaultDeclaration':
|
|
2878
3404
|
visit(node.declaration, scope, phase);
|
|
@@ -2882,6 +3408,25 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
2882
3408
|
case 'ArrowFunctionExpression':
|
|
2883
3409
|
visitFunction(node, scope, phase === 'module' ? 'render' : 'deferred');
|
|
2884
3410
|
return;
|
|
3411
|
+
case 'ClassDeclaration':
|
|
3412
|
+
case 'ClassExpression': {
|
|
3413
|
+
const classScope = createScope(scope, 'block', [], node.id ? [node.id] : []);
|
|
3414
|
+
visit(node.decorators, scope, phase);
|
|
3415
|
+
visit(node.superClass, classScope, phase);
|
|
3416
|
+
visit(node.body, classScope, phase);
|
|
3417
|
+
return;
|
|
3418
|
+
}
|
|
3419
|
+
case 'MethodDefinition':
|
|
3420
|
+
case 'PropertyDefinition':
|
|
3421
|
+
case 'AccessorProperty':
|
|
3422
|
+
visit(node.decorators, scope, phase);
|
|
3423
|
+
if (node.computed) visit(node.key, scope, phase);
|
|
3424
|
+
visit(
|
|
3425
|
+
node.value,
|
|
3426
|
+
scope,
|
|
3427
|
+
node.type === 'MethodDefinition' || node.static ? phase : 'deferred',
|
|
3428
|
+
);
|
|
3429
|
+
return;
|
|
2885
3430
|
case 'BlockStatement':
|
|
2886
3431
|
case 'JSXCodeBlock': {
|
|
2887
3432
|
if (node.type === 'JSXCodeBlock') hasNestedCodeBlock = true;
|
|
@@ -2930,7 +3475,22 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
2930
3475
|
for (const property of node.properties ?? []) {
|
|
2931
3476
|
if (property.type === 'SpreadElement') {
|
|
2932
3477
|
visit(property.argument, scope, executionPhase);
|
|
2933
|
-
|
|
3478
|
+
const spreadPhase = phaseAfter(property.argument, executionPhase);
|
|
3479
|
+
if (
|
|
3480
|
+
spreadPhase === 'render' &&
|
|
3481
|
+
currentFunctionChecksImpureCalls &&
|
|
3482
|
+
ambientReference(property.argument, scope) === GLOBAL_OBJECT_BINDING
|
|
3483
|
+
) {
|
|
3484
|
+
reportAmbientRead(property);
|
|
3485
|
+
}
|
|
3486
|
+
if (
|
|
3487
|
+
spreadPhase === 'render' &&
|
|
3488
|
+
currentFunctionChecksRenderReads &&
|
|
3489
|
+
isRefObject(property.argument, scope)
|
|
3490
|
+
) {
|
|
3491
|
+
reportRefRead(property);
|
|
3492
|
+
}
|
|
3493
|
+
executionPhase = spreadPhase;
|
|
2934
3494
|
continue;
|
|
2935
3495
|
}
|
|
2936
3496
|
if (property.computed === true) {
|
|
@@ -2946,14 +3506,62 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
2946
3506
|
}
|
|
2947
3507
|
case 'MemberExpression': {
|
|
2948
3508
|
visit(node.object, scope, phase);
|
|
3509
|
+
if (
|
|
3510
|
+
node.optional === true
|
|
3511
|
+
? definitelyNullishOptionalValue(node.object, scope)
|
|
3512
|
+
: definitelySkippedOptionalContinuation(node.object, scope)
|
|
3513
|
+
) {
|
|
3514
|
+
return;
|
|
3515
|
+
}
|
|
2949
3516
|
if (node.computed === true) {
|
|
2950
|
-
|
|
3517
|
+
const propertyPhase = phaseAfter(node.object, phase, true, node.optional === true);
|
|
3518
|
+
visit(node.property, scope, propertyPhase);
|
|
3519
|
+
if (
|
|
3520
|
+
readAccess &&
|
|
3521
|
+
propertyPhase === 'render' &&
|
|
3522
|
+
currentFunctionChecksRenderReads &&
|
|
3523
|
+
readCurrentRef(node, scope) &&
|
|
3524
|
+
phaseAfter(node.property, propertyPhase) === 'render'
|
|
3525
|
+
) {
|
|
3526
|
+
reportRefRead(node);
|
|
3527
|
+
}
|
|
3528
|
+
} else if (
|
|
3529
|
+
readAccess &&
|
|
3530
|
+
phase === 'render' &&
|
|
3531
|
+
currentFunctionChecksRenderReads &&
|
|
3532
|
+
readCurrentRef(node, scope) &&
|
|
3533
|
+
phaseAfter(node.object, phase, true, node.optional === true) === 'render'
|
|
3534
|
+
) {
|
|
3535
|
+
reportRefRead(node);
|
|
3536
|
+
}
|
|
3537
|
+
if (
|
|
3538
|
+
readAccess &&
|
|
3539
|
+
phase === 'render' &&
|
|
3540
|
+
currentFunctionChecksImpureCalls &&
|
|
3541
|
+
isAmbientMemberRead(node, scope)
|
|
3542
|
+
) {
|
|
3543
|
+
const propertyPhase = phaseAfter(node.object, phase, true, node.optional === true);
|
|
3544
|
+
if (phaseAfter(node.computed ? node.property : null, propertyPhase) === 'render') {
|
|
3545
|
+
reportAmbientRead(node);
|
|
3546
|
+
}
|
|
2951
3547
|
}
|
|
2952
3548
|
return;
|
|
2953
3549
|
}
|
|
2954
3550
|
case 'LabeledStatement':
|
|
2955
3551
|
visit(node.body, scope, phase);
|
|
2956
3552
|
return;
|
|
3553
|
+
case 'JSXSwitchExpression': {
|
|
3554
|
+
visit(node.discriminant, scope, phase);
|
|
3555
|
+
let searchPhase = phaseAfter(node.discriminant, phase);
|
|
3556
|
+
for (const branch of node.cases ?? []) {
|
|
3557
|
+
visit(branch.test, scope, searchPhase);
|
|
3558
|
+
searchPhase = phaseAfter(branch.test, searchPhase);
|
|
3559
|
+
const caseScope = createScope(scope, 'function', branch.consequent ?? []);
|
|
3560
|
+
predeclareHoistedVars(branch.consequent, caseScope);
|
|
3561
|
+
visitStatements(branch.consequent, caseScope, searchPhase);
|
|
3562
|
+
}
|
|
3563
|
+
return;
|
|
3564
|
+
}
|
|
2957
3565
|
case 'SwitchStatement': {
|
|
2958
3566
|
visit(node.discriminant, scope, phase);
|
|
2959
3567
|
const initialPhase =
|
|
@@ -3016,6 +3624,13 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
3016
3624
|
if (!FUNCTION_TYPES.has(callee?.type)) {
|
|
3017
3625
|
visit(node.callee, scope, phase);
|
|
3018
3626
|
}
|
|
3627
|
+
if (
|
|
3628
|
+
node.optional === true
|
|
3629
|
+
? definitelyNullishOptionalValue(node.callee, scope)
|
|
3630
|
+
: definitelySkippedOptionalContinuation(node.callee, scope)
|
|
3631
|
+
) {
|
|
3632
|
+
return;
|
|
3633
|
+
}
|
|
3019
3634
|
let executionPhase = phaseAfter(node.callee, phase, true, node.optional === true);
|
|
3020
3635
|
const synchronousCallbackIndex =
|
|
3021
3636
|
hook === 'useLinkedState'
|
|
@@ -3075,11 +3690,14 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
3075
3690
|
// memo owns a component callback. lazy also accepts a module
|
|
3076
3691
|
// loader, so require JSX evidence before treating it as a render.
|
|
3077
3692
|
const checkImpureCalls = currentFunctionChecksImpureCalls;
|
|
3693
|
+
const checkRenderReads = currentFunctionChecksRenderReads;
|
|
3078
3694
|
currentFunctionChecksImpureCalls = true;
|
|
3695
|
+
currentFunctionChecksRenderReads = true;
|
|
3079
3696
|
try {
|
|
3080
3697
|
visitCallback(component.node, component.scope, 'render');
|
|
3081
3698
|
} finally {
|
|
3082
3699
|
currentFunctionChecksImpureCalls = checkImpureCalls;
|
|
3700
|
+
currentFunctionChecksRenderReads = checkRenderReads;
|
|
3083
3701
|
}
|
|
3084
3702
|
return;
|
|
3085
3703
|
}
|
|
@@ -3110,7 +3728,9 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
3110
3728
|
callback,
|
|
3111
3729
|
callee,
|
|
3112
3730
|
executionPhase,
|
|
3113
|
-
callback.kind === 'setter'
|
|
3731
|
+
callback.kind === 'setter' || callback.kind === 'getter'
|
|
3732
|
+
? null
|
|
3733
|
+
: argumentValues(node.arguments, scope),
|
|
3114
3734
|
);
|
|
3115
3735
|
}
|
|
3116
3736
|
}
|
|
@@ -3185,11 +3805,27 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
3185
3805
|
case 'AssignmentExpression': {
|
|
3186
3806
|
if (node.left?.type === 'ArrayPattern' || node.left?.type === 'ObjectPattern') {
|
|
3187
3807
|
visit(node.right, scope, phase);
|
|
3188
|
-
visitPatternExpressions(
|
|
3808
|
+
visitPatternExpressions(
|
|
3809
|
+
node.left,
|
|
3810
|
+
scope,
|
|
3811
|
+
phaseAfter(node.right, phase),
|
|
3812
|
+
node.left.type === 'ObjectPattern' && isRefObject(node.right, scope),
|
|
3813
|
+
node.left.type === 'ObjectPattern' ? ambientReference(node.right, scope) : null,
|
|
3814
|
+
);
|
|
3189
3815
|
return;
|
|
3190
3816
|
}
|
|
3191
|
-
visit(node.left, scope, phase);
|
|
3817
|
+
visit(node.left, scope, phase, false);
|
|
3192
3818
|
const rightPhase = phaseAfter(node.left, phase, true);
|
|
3819
|
+
if (
|
|
3820
|
+
node.operator !== '=' &&
|
|
3821
|
+
rightPhase === 'render' &&
|
|
3822
|
+
currentFunctionChecksImpureCalls &&
|
|
3823
|
+
(isAmbientMemberRead(node.left, scope) ||
|
|
3824
|
+
(unwrap(node.left)?.type === 'Identifier' &&
|
|
3825
|
+
ambientReference(node.left, scope)?.kind === 'ambient'))
|
|
3826
|
+
) {
|
|
3827
|
+
reportAmbientRead(unwrap(node.left));
|
|
3828
|
+
}
|
|
3193
3829
|
visit(node.right, scope, rightPhase);
|
|
3194
3830
|
const executionPhase = phaseAfter(node.right, rightPhase);
|
|
3195
3831
|
if (executionPhase === 'render') {
|
|
@@ -3197,18 +3833,55 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
3197
3833
|
reportSnapshotWrite(node.left, scope);
|
|
3198
3834
|
reportRetainedRowMutation(node.left, scope);
|
|
3199
3835
|
}
|
|
3836
|
+
// Compound and logical assignments read the old value before the
|
|
3837
|
+
// right side, even when an await defers the eventual write.
|
|
3838
|
+
if (
|
|
3839
|
+
node.operator !== '=' &&
|
|
3840
|
+
rightPhase === 'render' &&
|
|
3841
|
+
currentFunctionChecksRenderReads &&
|
|
3842
|
+
(executionPhase !== 'render' || !currentRef(node.left, scope)) &&
|
|
3843
|
+
readCurrentRef(node.left, scope)
|
|
3844
|
+
) {
|
|
3845
|
+
reportRefRead(node.left);
|
|
3846
|
+
}
|
|
3847
|
+
if (
|
|
3848
|
+
node.operator !== '=' &&
|
|
3849
|
+
phase === 'render' &&
|
|
3850
|
+
currentFunctionChecksRenderReads &&
|
|
3851
|
+
unwrap(node.left)?.type === 'Identifier' &&
|
|
3852
|
+
mutableModuleRead(unwrap(node.left).name, scope)
|
|
3853
|
+
) {
|
|
3854
|
+
reportModuleStateRead(unwrap(node.left));
|
|
3855
|
+
}
|
|
3200
3856
|
return;
|
|
3201
3857
|
}
|
|
3202
3858
|
case 'UpdateExpression':
|
|
3859
|
+
if (
|
|
3860
|
+
phaseAfter(node.argument, phase) === 'render' &&
|
|
3861
|
+
currentFunctionChecksImpureCalls &&
|
|
3862
|
+
(isAmbientMemberRead(node.argument, scope) ||
|
|
3863
|
+
(unwrap(node.argument)?.type === 'Identifier' &&
|
|
3864
|
+
ambientReference(node.argument, scope)?.kind === 'ambient'))
|
|
3865
|
+
) {
|
|
3866
|
+
reportAmbientRead(unwrap(node.argument));
|
|
3867
|
+
}
|
|
3868
|
+
if (
|
|
3869
|
+
phase === 'render' &&
|
|
3870
|
+
currentFunctionChecksRenderReads &&
|
|
3871
|
+
unwrap(node.argument)?.type === 'Identifier' &&
|
|
3872
|
+
mutableModuleRead(unwrap(node.argument).name, scope)
|
|
3873
|
+
) {
|
|
3874
|
+
reportModuleStateRead(unwrap(node.argument));
|
|
3875
|
+
}
|
|
3203
3876
|
if (phase === 'render' && currentRef(node.argument, scope)) reportRef(node.argument);
|
|
3204
|
-
visit(node.argument, scope, phase);
|
|
3877
|
+
visit(node.argument, scope, phase, false);
|
|
3205
3878
|
if (phaseAfter(node.argument, phase, true) === 'render') {
|
|
3206
3879
|
reportSnapshotWrite(node.argument, scope);
|
|
3207
3880
|
reportRetainedRowMutation(node.argument, scope);
|
|
3208
3881
|
}
|
|
3209
3882
|
return;
|
|
3210
3883
|
case 'UnaryExpression':
|
|
3211
|
-
visit(node.argument, scope, phase);
|
|
3884
|
+
visit(node.argument, scope, phase, node.operator !== 'delete');
|
|
3212
3885
|
if (node.operator === 'delete' && phaseAfter(node.argument, phase, true) === 'render') {
|
|
3213
3886
|
reportSnapshotWrite(node.argument, scope);
|
|
3214
3887
|
reportRetainedRowMutation(node.argument, scope);
|
|
@@ -3291,7 +3964,11 @@ export function analyzeStrongMode(ast, source, filename, options = {}) {
|
|
|
3291
3964
|
(alwaysAwaits(node.right) || (node.type === 'ForOfStatement' && node.await === true))
|
|
3292
3965
|
? 'deferred'
|
|
3293
3966
|
: phase;
|
|
3294
|
-
|
|
3967
|
+
if (node.left?.type === 'ArrayPattern' || node.left?.type === 'ObjectPattern') {
|
|
3968
|
+
visitPatternExpressions(node.left, loop, executionPhase);
|
|
3969
|
+
} else {
|
|
3970
|
+
visit(node.left, loop, executionPhase, false);
|
|
3971
|
+
}
|
|
3295
3972
|
visit(node.body, loop, executionPhase);
|
|
3296
3973
|
return;
|
|
3297
3974
|
}
|