mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 语义重名诊断引擎侧复核,放行跨语句列的函数名合并
供应商语义检查器把"同语句列"近似为"声明域非 Top/Function 且前一项是函数声明",for-in/for-of 域用空 ScopeFlags 因而漏掉 Top|Function 守卫,误拒跨语句列的函数名与 var 头合法合并。 引擎侧在归一化前按 AST 复核:仅当函数非任一 block/case 列表 直接成员、或其外层链不含该列表时剔除该重名诊断,复核后无 剩余错误则落回解析成功。
This commit is contained in:
Generated
+1
@@ -1263,6 +1263,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"oxc_allocator",
|
||||
"oxc_ast",
|
||||
"oxc_ast_visit",
|
||||
"oxc_diagnostics",
|
||||
"oxc_parser",
|
||||
"oxc_semantic",
|
||||
|
||||
@@ -31,6 +31,7 @@ rust-version = "1.80"
|
||||
oxc_parser = "0.134.0"
|
||||
oxc_allocator = "0.134.0"
|
||||
oxc_ast = "0.134.0"
|
||||
oxc_ast_visit = "0.134.0"
|
||||
oxc_span = "0.134.0"
|
||||
oxc_diagnostics = "0.134.0"
|
||||
oxc_syntax = "0.134.0"
|
||||
|
||||
@@ -11,6 +11,7 @@ oxide_log = { path = "../oxide_log" }
|
||||
oxc_parser.workspace = true
|
||||
oxc_allocator.workspace = true
|
||||
oxc_ast.workspace = true
|
||||
oxc_ast_visit.workspace = true
|
||||
oxc_span.workspace = true
|
||||
oxc_diagnostics.workspace = true
|
||||
oxc_syntax.workspace = true
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
mod error;
|
||||
mod parser_log;
|
||||
mod redeclaration_filter;
|
||||
|
||||
/// 解析错误:消息 + 源文件字节区间(起始,结束)。
|
||||
pub use error::OxideError;
|
||||
@@ -46,7 +47,13 @@ fn parse_with_source_type<'a>(
|
||||
.with_check_syntax_error(true)
|
||||
.build(&ret.program);
|
||||
if !semantic_ret.errors.is_empty() {
|
||||
return Err(semantic_ret.errors.into_iter().map(OxideError::from).collect());
|
||||
// 重名诊断先经 AST 复核剔除跨语句列合法合并的误报(须在归一化前,
|
||||
// 归一化只留首标签会丢失复核所需的新声明 span);复核后无剩余
|
||||
// 错误时按解析成功落回。
|
||||
let filtered = redeclaration_filter::filter_semantic_diagnostics(semantic_ret.errors, &ret.program);
|
||||
if !filtered.is_empty() {
|
||||
return Err(filtered.into_iter().map(OxideError::from).collect());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ret.program)
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
//! 语义早期错误诊断的 AST 复核:剔除函数名与 var 声明合法合并的重名误报。
|
||||
//!
|
||||
//! "Identifier `x` has already been declared" 诊断按"声明子当前作用域 +
|
||||
//! 合并符号历史"粒度发射:var 声明子位于语句作用域(for-in/for-of 头、块、
|
||||
//! switch 等)且其 var 作用域内同名合并项的上一项为函数声明即判碰撞。
|
||||
//! 规范按"语句列"粒度裁定:只有作为**某个块/case 语句列直接成员**的函数
|
||||
//! 声明才计入该列的 lexically declared 名,其与该列闭包内 var 声明同名
|
||||
//! 才是 SyntaxError;脚本顶层/函数体语句列直接成员的函数声明与同 var
|
||||
//! 作用域的 var 声明属提升后合法合并(var 步复用既有绑定),不应拒。
|
||||
//!
|
||||
//! 关键不变式:只剔除可确认误报的既定消息形诊断;消息形不匹配、标签
|
||||
//! 缺失、模块模式、节点匹配失败一律原样保留,不吞任何真错。
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use oxc_ast::ast::{
|
||||
BlockStatement, Function, FunctionType, IfStatement, Program, SwitchStatement, VariableDeclaration,
|
||||
VariableDeclarationKind,
|
||||
};
|
||||
use oxc_ast_visit::walk::{
|
||||
walk_block_statement, walk_function_body, walk_if_statement, walk_program, walk_switch_statement,
|
||||
walk_variable_declaration,
|
||||
};
|
||||
use oxc_ast_visit::Visit;
|
||||
use oxc_diagnostics::OxcDiagnostic;
|
||||
use oxc_syntax::scope::ScopeFlags;
|
||||
|
||||
/// 重名诊断消息形:前缀 + 标识符名 + 后缀。
|
||||
///
|
||||
/// 上游改文案时匹配失败、过滤静默透传(行为回到全量拒绝,不误吞真错),
|
||||
/// 消息形由测试钉锁。
|
||||
const MSG_PREFIX: &str = "Identifier `";
|
||||
const MSG_SUFFIX: &str = "` has already been declared";
|
||||
|
||||
/// 对语义诊断列表做重名误报复核,返回剔除误报后的列表。
|
||||
///
|
||||
/// 须在 `OxideError` 归一化之前调用:归一化只保留首个标签,会丢失
|
||||
/// "新声明"侧 span 而失去复核能力。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 诊断列表可能混有非重名形诊断:逐条独立裁定,未命中剔除条件者原样保留。
|
||||
/// - 模块模式(`program.source_type` 为模块)顶层函数名为 lexical 绑定,
|
||||
/// 一切 var 碰撞均为真错,整列保留。
|
||||
///
|
||||
/// # 注意事项
|
||||
/// - 无重名形诊断时直接返回原列表(常见路径零开销)。
|
||||
/// - AST 遍历只读,不改写 `program`。
|
||||
pub(crate) fn filter_semantic_diagnostics<'a>(errors: Vec<OxcDiagnostic>, program: &Program<'a>) -> Vec<OxcDiagnostic> {
|
||||
if !errors.iter().any(|e| redeclaration_name(&e.message).is_some()) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
// 单遍收集函数声明归属与 var 声明块链,供逐诊断复核。
|
||||
let mut collector = Collector::default();
|
||||
collector.visit_program(program);
|
||||
|
||||
let is_module = program.source_type.is_module();
|
||||
errors
|
||||
.into_iter()
|
||||
.filter(|e| verdict(e, &collector, is_module) != Some(true))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 从重名形消息提取标识符名;消息形不匹配返 `None`。
|
||||
fn redeclaration_name(message: &str) -> Option<&str> {
|
||||
let name = message.strip_prefix(MSG_PREFIX)?;
|
||||
name.strip_suffix(MSG_SUFFIX)
|
||||
}
|
||||
|
||||
/// 裁定单条重名形诊断。
|
||||
///
|
||||
/// 返回 `Some(true)` = 已确认误报(可剔除);`Some(false)` = 已确认真错
|
||||
/// (保留);`None` = 不在本过滤裁定范围(消息形/标签/节点匹配失败),
|
||||
/// 一律原样保留。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 消息形匹配取标识符名;标签取双标签(0 = 既有声明,1 = 新声明)。
|
||||
/// 2. 既有声明侧按 span 匹配同名函数声明,取其语句列归属。
|
||||
/// 3. 新声明侧在标签区间内匹配同名 var 绑定,取其块/case 链。
|
||||
/// 4. 函数不在任何块/case 语句列直接成员位 → 误报(合法合并形);
|
||||
/// 在 → 真错当且仅当 var 声明位于该语句列闭包内(块链含该列)。
|
||||
fn verdict(diag: &OxcDiagnostic, col: &Collector<'_>, is_module: bool) -> Option<bool> {
|
||||
let name = redeclaration_name(&diag.message)?;
|
||||
let labels = diag.labels.as_deref()?;
|
||||
let (existing, new) = match (labels.first(), labels.get(1)) {
|
||||
(Some(a), Some(b)) => (a, b),
|
||||
_ => return None,
|
||||
};
|
||||
// 模块顶层函数名为 lexical 绑定,一切 var 碰撞均为真错。
|
||||
if is_module {
|
||||
return Some(false);
|
||||
}
|
||||
|
||||
let (fn_name, owner) = col.fn_owners.get(&label_key(existing))?;
|
||||
if *fn_name != name {
|
||||
return None;
|
||||
}
|
||||
|
||||
// 新声明标签可覆盖整个声明子(含初始化器),绑定名 span 必为其子区间。
|
||||
let (start, end) = (new.offset() as u32, new.offset() as u32 + new.len() as u32);
|
||||
let chain = col
|
||||
.var_chains
|
||||
.iter()
|
||||
.find(|(span, ident, _)| *ident == name && span.0 >= start && span.1 <= end)?
|
||||
.2
|
||||
.clone();
|
||||
|
||||
Some(match owner {
|
||||
None => true,
|
||||
Some(list) => !chain.contains(list),
|
||||
})
|
||||
}
|
||||
|
||||
/// span 编码为单键:起点高 32 位、终点低 32 位。
|
||||
fn label_key(label: &oxc_diagnostics::LabeledSpan) -> u64 {
|
||||
let start = label.offset() as u64;
|
||||
let end = start + label.len() as u64;
|
||||
(start << 32) | end
|
||||
}
|
||||
|
||||
/// AST 收集器:记录复核所需的声明归属信息。
|
||||
///
|
||||
/// 归属性状语义(随遍历位置变化):
|
||||
/// - `block_chain`:当前位置外部的块/case 容器列(最外层在前);
|
||||
/// - `direct_owner`:当前语句所在语句列的块/switch 归属(程序体、函数体、
|
||||
/// static block、if/else 臂、标签语句等非块语句列为 `None`)。
|
||||
#[derive(Default)]
|
||||
struct Collector<'a> {
|
||||
/// 函数声明名 span → (名, 函数声明直接归属的语句列);后者 `None` 表示
|
||||
/// 直接位于程序体/函数体/static block 或非块语句(if/else 臂、标签语句),
|
||||
/// 这些位置的函数名不计入任何语句列的 lexically declared 名。
|
||||
fn_owners: HashMap<u64, (&'a str, Option<u64>)>,
|
||||
/// var 声明绑定名:(span, 名, 该声明处的块/case 链);覆盖解构的全部绑定。
|
||||
var_chains: Vec<((u32, u32), &'a str, Vec<u64>)>,
|
||||
/// 当前位置外部的块/switch 容器 span 列(最外层在前)。
|
||||
block_chain: Vec<u64>,
|
||||
/// 当前语句直接所在语句列的块/switch span;非块语句列为 `None`。
|
||||
direct_owner: Option<u64>,
|
||||
}
|
||||
|
||||
impl<'a> Visit<'a> for Collector<'a> {
|
||||
fn visit_program(&mut self, program: &Program<'a>) {
|
||||
// 程序语句列是脚本 var 作用域的顶层语句列:两项归属皆空。
|
||||
let saved = (std::mem::take(&mut self.block_chain), self.direct_owner);
|
||||
self.direct_owner = None;
|
||||
walk_program(self, program);
|
||||
self.block_chain = saved.0;
|
||||
self.direct_owner = saved.1;
|
||||
}
|
||||
|
||||
fn visit_function(&mut self, func: &Function<'a>, _flags: ScopeFlags) {
|
||||
// 函数声明按当前 direct_owner 归属记录(仅块/switch 语句列有归属)。
|
||||
if func.r#type == FunctionType::FunctionDeclaration {
|
||||
if let Some(id) = &func.id {
|
||||
let span = id.span;
|
||||
self.fn_owners
|
||||
.insert(((span.start as u64) << 32) | span.end as u64, (id.name.as_str(), self.direct_owner));
|
||||
}
|
||||
}
|
||||
|
||||
// 参数默认值为表达式上下文:不含语句列归属变化。
|
||||
self.visit_formal_parameters(&func.params);
|
||||
|
||||
// 函数体语句列是新 var 作用域的顶层语句列:两项归属复位。
|
||||
if let Some(body) = &func.body {
|
||||
let saved = (std::mem::take(&mut self.block_chain), self.direct_owner);
|
||||
self.direct_owner = None;
|
||||
walk_function_body(self, body);
|
||||
self.block_chain = saved.0;
|
||||
self.direct_owner = saved.1;
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_block_statement(&mut self, block: &BlockStatement<'a>) {
|
||||
// 块是重名裁定列:其直接成员的函数声明计入该列 lexically declared 名,
|
||||
// 闭包内(含嵌套块)的 var 声明计入该列 VarDeclaredNames。
|
||||
let key = (block.span.start as u64) << 32 | block.span.end as u64;
|
||||
let saved_len = self.block_chain.len();
|
||||
let saved_owner = self.direct_owner;
|
||||
self.block_chain.push(key);
|
||||
self.direct_owner = Some(key);
|
||||
walk_block_statement(self, block);
|
||||
self.block_chain.truncate(saved_len);
|
||||
self.direct_owner = saved_owner;
|
||||
}
|
||||
|
||||
fn visit_if_statement(&mut self, if_stmt: &IfStatement<'a>) {
|
||||
// if/else 臂非重名裁定语句列:臂内函数声明按外层 var 作用域绑定,
|
||||
// 其名不计入任何语句列的 lexically declared 名(标签语句不同:
|
||||
// 其内层语句仍为原语句列直接成员,归属继承不动)。
|
||||
let saved = self.direct_owner;
|
||||
self.direct_owner = None;
|
||||
walk_if_statement(self, if_stmt);
|
||||
self.direct_owner = saved;
|
||||
}
|
||||
|
||||
fn visit_switch_statement(&mut self, switch: &SwitchStatement<'a>) {
|
||||
// switch 的 CaseBlock 整体是重名裁定列:任一 case 的函数声明与
|
||||
// 任一 case 的 var 声明同列,等同同块碰撞。
|
||||
let key = (switch.span.start as u64) << 32 | switch.span.end as u64;
|
||||
let saved_len = self.block_chain.len();
|
||||
let saved_owner = self.direct_owner;
|
||||
self.block_chain.push(key);
|
||||
self.direct_owner = Some(key);
|
||||
walk_switch_statement(self, switch);
|
||||
self.block_chain.truncate(saved_len);
|
||||
self.direct_owner = saved_owner;
|
||||
}
|
||||
|
||||
fn visit_variable_declaration(&mut self, decl: &VariableDeclaration<'a>) {
|
||||
// 仅 var 声明是重名诊断的"新声明"侧(let/const/using 各有其裁定臂)。
|
||||
if decl.kind == VariableDeclarationKind::Var {
|
||||
for d in &decl.declarations {
|
||||
let chain = self.block_chain.clone();
|
||||
for ident in d.id.get_binding_identifiers() {
|
||||
self.var_chains
|
||||
.push(((ident.span.start, ident.span.end), ident.name.as_str(), chain.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
walk_variable_declaration(self, decl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
//! 重名误报过滤钉锁:函数名与 var 声明头跨语句列合法合并的形必须解析成功,
|
||||
//! 同语句列碰撞与模块 lexical 面必须继续拒绝。
|
||||
|
||||
use oxide_parser::Allocator;
|
||||
|
||||
fn parsed(source: &str) {
|
||||
let allocator = Allocator::default();
|
||||
let result = oxide_parser::parse(&allocator, source);
|
||||
assert!(result.is_ok(), "应解析成功:{source},实际错误:{:?}", result.err());
|
||||
}
|
||||
|
||||
fn parsed_module(source: &str) {
|
||||
let allocator = Allocator::default();
|
||||
let result = oxide_parser::parse_module(&allocator, source);
|
||||
assert!(result.is_ok(), "应按模块解析成功:{source},实际错误:{:?}", result.err());
|
||||
}
|
||||
|
||||
fn rejected(source: &str) {
|
||||
let allocator = Allocator::default();
|
||||
let result = oxide_parser::parse(&allocator, source);
|
||||
let errors = result.expect_err(&format!("应拒绝:{source}"));
|
||||
assert!(
|
||||
errors.iter().any(|e| e.message == "Identifier `x` has already been declared"),
|
||||
"拒绝原因应仍为重名诊断,实际:{errors:?}"
|
||||
);
|
||||
}
|
||||
|
||||
fn rejected_module(source: &str) {
|
||||
let allocator = Allocator::default();
|
||||
let result = oxide_parser::parse_module(&allocator, source);
|
||||
let errors = result.expect_err(&format!("应按模块拒绝:{source}"));
|
||||
assert!(
|
||||
errors.iter().any(|e| e.message == "Identifier `x` has already been declared"),
|
||||
"拒绝原因应仍为重名诊断,实际:{errors:?}"
|
||||
);
|
||||
}
|
||||
|
||||
// ── 合法合并形:必须解析成功 ──
|
||||
|
||||
#[test]
|
||||
fn script_top_function_for_in_var_header() {
|
||||
// 脚本顶层函数 + for-in var 头:var 提升后与函数名同 var 作用域合法合并。
|
||||
parsed("function x(){} var o={a:1,b:2}; for (var x in o) {} x+\"|\"+x");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_top_function_for_of_var_header() {
|
||||
// for-of 孪生形。
|
||||
parsed("function x(){} var o=[1,2]; for (var x of o) {} x+\"|\"+typeof x");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_body_function_for_in_var_header() {
|
||||
// 函数作用域面:外层函数体内的函数声明 + 内层 for-in var 头。
|
||||
parsed("function outer(){ function x(){} var o={a:1,b:2}; for (var x in o) {} return x+\"|\"+typeof x }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_top_function_c_for_var_header() {
|
||||
// C-for init 的 var 声明子同属外层 var 作用域。
|
||||
parsed("function x(){} var o,n; for (var x=0; n<2; n++,x++) {}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_top_function_var_in_if_block() {
|
||||
// if 块内 var 提升过 if 块到外层 var 作用域。
|
||||
parsed("function x(){} if (true) { var x; } typeof x");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_top_function_var_in_bare_block() {
|
||||
// 裸块面。
|
||||
parsed("function x(){} { var x; } typeof x");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_top_function_var_in_while_block() {
|
||||
// while 块面。
|
||||
parsed("function x(){} while (false) { var x; } typeof x");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_top_function_generator_for_in_var_header() {
|
||||
// 生成器函数声明同为 var 作用域函数名:合并形。
|
||||
parsed("function* x(){} var o={a:1}; for (var x in o) {} typeof x");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_arm_function_for_in_var_header() {
|
||||
// Annex B if/else 臂函数声明按 var 作用域绑定,其名不计入任何语句列
|
||||
// 的 lexically declared 名:与外层 var 头不碰撞。
|
||||
parsed("if(true) function x(){} var o={a:1,b:2}; for (var x in o) {} typeof x");
|
||||
parsed("if(false) {} else function x(){} var o={a:1}; for (var x in o) {} typeof x");
|
||||
// 块内 if 臂同形。
|
||||
parsed("{ if(true) function x(){} var o={a:1}; for (var x in o) {} } typeof x");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_function_sibling_of_var_header() {
|
||||
// 块内函数 + 块外 for-in 头:无同一语句列同时含两者,不碰撞。
|
||||
parsed("{ function x(){} } var o={a:1,b:2}; for (var x in o) {} typeof x");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inner_block_function_outer_var_header() {
|
||||
// 内嵌块函数 + 外层块 var 头:函数名不计入外层块的 lexically declared 名。
|
||||
parsed("{ { function x(){} } var o={a:1,b:2}; for (var x in o) {} } typeof x");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn labeled_statement_function_for_in_var_header() {
|
||||
// 标签语句包裹的函数声明非语句列直接成员。
|
||||
parsed("a: function x(){} var o={a:1,b:2}; for (var x in o) {} typeof x");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn switch_case_function_var_header_outside() {
|
||||
// case 内函数 + switch 外 for-in 头:var 声明不在 CaseBlock 列内。
|
||||
parsed("var o={a:1,b:2}; switch(1){ case 1: function x(){} } for (var x in o) {} typeof x");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_then_block_function_after_var_header() {
|
||||
// for-in 头在前、后块内函数声明:现行语义不碰撞(v8 一致)。
|
||||
parsed("{ var o={a:1,b:2}; for (var x in o) {} { function x(){} } } typeof x");
|
||||
}
|
||||
|
||||
// ── 真错面:必须继续拒绝 ──
|
||||
|
||||
#[test]
|
||||
fn same_block_function_and_var() {
|
||||
// 同块函数声明 + var:语句列重名真错。
|
||||
rejected("{ function x(){} var x; }");
|
||||
rejected("{ function x(){} var x; } \"ok\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn outer_block_function_inner_block_var() {
|
||||
// 外层块函数 + 嵌套块 var:函数名计入外层列 lexically declared 名,
|
||||
// var 计入该列 VarDeclaredNames(递归闭包):真错。
|
||||
rejected("{ function x(){} { var x; } }");
|
||||
rejected("{ function x(){} { for (var x in o) {} } }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_block_let_and_var() {
|
||||
// let 名与 var 同名同列:真错(标签侧非函数声明,过滤不动作)。
|
||||
rejected("{ let x; var x; }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_top_function_and_let() {
|
||||
// let 声明撞顶层函数名:声明期真错,非本过滤面。
|
||||
rejected("function x(){} let x;");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn class_name_with_for_in_var_header() {
|
||||
// class 名建声明式组绑定,var 撞之 = 真错(既有声明侧为 class 非函数)。
|
||||
rejected("class x{} var o={a:1,b:2}; for (var x in o) {}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_then_class_same_name() {
|
||||
// class 重名臂:既有声明侧为函数、新声明侧为 class,过滤不动作。
|
||||
rejected("function x(){} class x {}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_block_strict_function_and_var() {
|
||||
// 严格模式块内函数为 lexical 绑定,同列 var 碰撞同判真错。
|
||||
rejected("{ \"use strict\"; function x(){} var o={a:1}; for (var x in o) {} }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn labeled_function_in_block_with_var() {
|
||||
// 块内标签语句包裹的函数声明仍属该块语句列直接成员:同列 var 碰撞真错。
|
||||
rejected("{ a: function x(){} var o={a:1,b:2}; for (var x in o) {} }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn switch_cases_function_and_var() {
|
||||
// CaseBlock 列:任一 case 的函数声明与任一 case 的 var 声明同列碰撞。
|
||||
rejected("var o={a:1,b:2}; switch(1){ case 1: function x(){} case 2: { for (var x in o) {} } }");
|
||||
rejected("var o={a:1,b:2}; switch(1){ case 1: { function x(){} for (var x in o) {} } }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_var_and_function_top_level() {
|
||||
// 模块顶层函数为 lexical 绑定:var 撞之 = 真错。
|
||||
rejected_module("export {}; var x; function x(){}");
|
||||
rejected_module("export {}; function x(){} var x;");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_function_and_for_in_var_header() {
|
||||
// 模块面 for-in var 头同判真错。
|
||||
rejected_module("export {}; function x(){} var o={a:1,b:2}; for (var x in o){}");
|
||||
}
|
||||
|
||||
// ── 透传钉:同消息形但节点形不同的诊断必须原样保留 ──
|
||||
|
||||
#[test]
|
||||
fn same_message_let_let_still_rejected() {
|
||||
// let/let 同消息形重名:既有声明侧非函数声明,过滤须透传。
|
||||
rejected("{ let x; let x; }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_duplicate_params_still_rejected() {
|
||||
// 严格模式重复参数同消息形:新声明侧非 var 声明子,过滤须透传。
|
||||
rejected("\"use strict\"; function f(x, x) {}");
|
||||
}
|
||||
|
||||
// ── 无关面回归:普通解析不受过滤影响 ──
|
||||
|
||||
#[test]
|
||||
fn unrelated_shapes_unaffected() {
|
||||
parsed("var x; var o={a:1}; for (var x in o) {} x+\"|\"+x");
|
||||
parsed("function x(){} var x; typeof x");
|
||||
parsed("function x(){} for (x in o) {}");
|
||||
parsed_module("export {}; function x(){} var o={a:1}; for (let x in o) {} typeof x");
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
//! 函数名与 var 声明头跨语句列合法合并的求值行为钉:var 提升步复用函数
|
||||
//! 声明既有绑定(脚本顶层全局属性 / 函数 var 环境),迭代写入同一绑定。
|
||||
//!
|
||||
//! 完成值一律收敛为布尔(JsValue 的 Display 只暴露 number/bool,不暴露
|
||||
//! 字符串内容),字符串比较在引擎内完成。
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_parser::Allocator;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
fn eval(source: &str) -> String {
|
||||
let allocator = Allocator::default();
|
||||
let program = match oxide_parser::parse(&allocator, source) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return format!("parse error: {}", e[0].message),
|
||||
};
|
||||
let module = match Compiler::new().compile(&program) {
|
||||
Ok(m) => m,
|
||||
Err(e) => return format!("compile error: {e}"),
|
||||
};
|
||||
let mut vm = Vm::new();
|
||||
match vm.run(&Arc::new(module)) {
|
||||
Ok(result) => format!("{result}"),
|
||||
Err(e) => format!("vm error: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_top_function_for_in_var_header_reuses_global_property() {
|
||||
// 脚本顶层函数 + for-in var 头:迭代重写全局属性(末键),x 不再是函数。
|
||||
assert_eq!(
|
||||
eval(
|
||||
"function x(){} var o={a:1,b:2}; for (var x in o) {} \
|
||||
(x === \"b\") && (typeof x === \"string\")"
|
||||
),
|
||||
"true",
|
||||
"var head hoists into the global var environment and reuses the function property"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_top_function_for_of_var_header_reuses_global_property() {
|
||||
// for-of 孪生形:末元素 2 写入全局属性。
|
||||
assert_eq!(
|
||||
eval("function x(){} var o=[1,2]; for (var x of o) {} (x === 2) && (typeof x === \"number\")"),
|
||||
"true",
|
||||
"for-of var head merges with the top-level function name binding"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_body_function_for_in_var_header_reuses_fn_env_binding() {
|
||||
// 函数作用域面:函数 var 环境内同形合并。
|
||||
assert_eq!(
|
||||
eval(
|
||||
"function outer(){ function x(){} var o={a:1,b:2}; \
|
||||
for (var x in o) {} return (x === \"b\") && (typeof x === \"string\") } outer()"
|
||||
),
|
||||
"true",
|
||||
"var head in a for-in inside a function body reuses the function-scope binding"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_top_function_plain_var_keeps_function() {
|
||||
// 纯 var 重名(无迭代写入):var 步零动作,x 保持函数本体。
|
||||
assert_eq!(
|
||||
eval("function x(){} var x; typeof x === \"function\""),
|
||||
"true",
|
||||
"plain var redeclaration of a function name keeps the function binding"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user