修复脚本分支与回调参数快照
在条件测试完成后分别冻结两侧 callable identity。 按回调参数自身起点解析身份,保留前置参数副作用。 新增两类求值顺序回归并同步技术方案与决策记录。
This commit is contained in:
+44
-3
@@ -1476,8 +1476,14 @@ impl JavascriptMethodReceiverOwnerCollector<'_> {
|
||||
self.callable_values(&parenthesized.expression, captured_at)
|
||||
}
|
||||
JavascriptExpression::ConditionalExpression(conditional) => {
|
||||
let mut values = self.callable_values(&conditional.consequent, captured_at);
|
||||
values.extend(self.callable_values(&conditional.alternate, captured_at));
|
||||
let mut values = self.callable_values(
|
||||
&conditional.consequent,
|
||||
conditional.consequent.span().start as usize,
|
||||
);
|
||||
values.extend(self.callable_values(
|
||||
&conditional.alternate,
|
||||
conditional.alternate.span().start as usize,
|
||||
));
|
||||
values
|
||||
}
|
||||
JavascriptExpression::CallExpression(call) => call
|
||||
@@ -2277,7 +2283,7 @@ impl<'a> VisitJavascript<'a> for JavascriptFunctionInvocationCollector<'a, '_> {
|
||||
.get(*index)
|
||||
.and_then(|argument| argument.as_expression())
|
||||
{
|
||||
self.record_expression(expression, resolve_at, invoke_at);
|
||||
self.record_expression(expression, expression.span().start as usize, invoke_at);
|
||||
}
|
||||
}
|
||||
oxc_ast_visit::walk::walk_call_expression(self, call);
|
||||
@@ -10430,6 +10436,20 @@ mod javascript_projection_reachability_tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conditional_callable_captures_each_branch_after_the_test() {
|
||||
let content = "function original() { return import('./before-test.mjs'); } function replacement() { return import('./after-test.mjs'); } let source = original; function swap() { source = replacement; return true; } const alias = swap() ? source : original; alias();";
|
||||
let ranges = named_javascript_function_ranges(content);
|
||||
let replacement = content
|
||||
.find("./after-test.mjs")
|
||||
.expect("find callable installed by the conditional test");
|
||||
|
||||
assert!(
|
||||
javascript_position_is_reachable(content, &ranges, replacement),
|
||||
"a conditional branch must capture callable state after its test side effects",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_function_alias_preserves_the_call_edge() {
|
||||
let content = "function start() { return import('./aliased-function.mjs'); } const alias = start; alias();";
|
||||
@@ -10642,6 +10662,27 @@ mod javascript_projection_reachability_tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn controlled_callback_captures_after_earlier_arguments() {
|
||||
let content = "function original() { return import('./listener-before-argument.mjs'); } function replacement() { return import('./listener-after-argument.mjs'); } let listener = original; function swap() { listener = replacement; return 'click'; } document.addEventListener(swap(), listener);";
|
||||
let ranges = named_javascript_function_ranges(content);
|
||||
let original = content
|
||||
.find("./listener-before-argument.mjs")
|
||||
.expect("find callback before argument side effect");
|
||||
let replacement = content
|
||||
.find("./listener-after-argument.mjs")
|
||||
.expect("find callback after argument side effect");
|
||||
|
||||
assert!(
|
||||
!javascript_position_is_reachable(content, &ranges, original),
|
||||
"a callback must not retain the identity replaced by an earlier argument",
|
||||
);
|
||||
assert!(
|
||||
javascript_position_is_reachable(content, &ranges, replacement),
|
||||
"a callback must capture the identity installed by an earlier argument",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn controlled_callback_apis_unwrap_parenthesized_callees_and_receivers() {
|
||||
assert_callback_reachability(
|
||||
|
||||
Reference in New Issue
Block a user