mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 模块顶层路径归一绝对口径,自导入不再误判为外部依赖
This commit is contained in:
@@ -128,11 +128,20 @@ fn is_hoisted_function_decl(stmt: &Statement) -> bool {
|
||||
|
||||
impl Emitter {
|
||||
/// 编译 ES module:顶层 body + 递归依赖。
|
||||
/// `module_path` 为模块文件的规范路径(依赖解析基准 = 其父目录)。
|
||||
/// `module_path` 为模块文件路径;入口即归一为规范绝对路径(依赖解析基准 =
|
||||
/// 其父目录),与加载器 resolve 的绝对规范口径对齐,使自导入身份比较稳定。
|
||||
pub fn emit_program_module(
|
||||
&self, program: &oxide_parser::Program, module_path: &str, loader: &mut dyn ModuleSourceLoader,
|
||||
) -> Result<IRFunction, String> {
|
||||
crate::emit_debug!("emit_program_module: {} stmts", program.body.len());
|
||||
// 顶层模块路径归一为规范绝对路径:自导入身份比较(resolved.path ==
|
||||
// module_path)与 base_dir 需与加载器 resolve 的 canonicalize 绝对口径
|
||||
// 一致——若保留相对发现路径,自导入比较恒 false、自导入被当外部依赖
|
||||
// 重编译。文件不存在(虚拟/内存模块)时 canonicalize 失败,保留原路径。
|
||||
let canonical = std::fs::canonicalize(module_path)
|
||||
.map(|p| p.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|_| module_path.to_string());
|
||||
let module_path = canonical.as_str();
|
||||
let mut ctx = CompileCtx::new();
|
||||
// 模块顶层 var/function 不写全局对象(模块作用域绑定)。
|
||||
ctx.is_global_scope = false;
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
//! 自导入路径口径:顶层模块相对发现路径 vs 加载器 resolve 的 canonicalize 绝对
|
||||
//! 路径,二者须同口径,自导入才按自身命名空间绑定、整模块只求值一次。
|
||||
//! 误判为外部依赖会把自导入重新编译求值一遍,模块被求值两次。
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_emit::module::{ModuleKind, ModuleSourceLoader, ResolvedModule};
|
||||
use oxide_types::value::JsValue;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
/// 自导入模块源:`import * as self from './selfm.js'` 导入自身,每次求值给
|
||||
/// global 计数加一,导出 self 命名空间引用以实化自导入绑定。
|
||||
const MODULE_SRC: &str = "import * as self from './selfm.js';\n \
|
||||
globalThis.__evals = (globalThis.__evals || 0) + 1;\n \
|
||||
export const self_ref = self;";
|
||||
|
||||
/// 复刻 test262 加载器:base_dir/specifier join 后 canonicalize 为绝对规范路径
|
||||
/// 并读源——与顶层相对发现路径构成「绝对 vs 相对」口径差,正是本面复现条件。
|
||||
struct AbsLoader;
|
||||
|
||||
impl ModuleSourceLoader for AbsLoader {
|
||||
fn resolve(
|
||||
&mut self, base_dir: &str, specifier: &str, _attributes: &[(&str, &str)],
|
||||
) -> Result<ResolvedModule, String> {
|
||||
let full = std::path::Path::new(base_dir).join(specifier);
|
||||
let canonical = full.canonicalize().map_err(|e| format!("cannot resolve {specifier}: {e}"))?;
|
||||
let source = std::fs::read_to_string(&canonical).map_err(|e| format!("cannot read {specifier}: {e}"))?;
|
||||
Ok(ResolvedModule {
|
||||
source,
|
||||
path: canonical.to_string_lossy().into_owned(),
|
||||
kind: ModuleKind::Js,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// 测试结束删除临时目录,避免在 crate 树残留文件。
|
||||
struct Cleanup(std::path::PathBuf);
|
||||
impl Drop for Cleanup {
|
||||
fn drop(&mut self) {
|
||||
let _ = std::fs::remove_dir_all(&self.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// 同一 VM 续跑脚本并返回完成值(读 global 计数用)。
|
||||
fn probe(vm: &mut Vm, source: &str) -> JsValue {
|
||||
let allocator = oxide_parser::Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, source).expect("parse");
|
||||
let module = Compiler::new().compile(&program).expect("compile");
|
||||
vm.run(&Arc::new(module)).expect("probe run")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn self_import_module_evaluated_once() {
|
||||
// 临时模块落在 CWD 相对路径下,使顶层发现路径可为相对形式、canonicalize
|
||||
// 又可达——与 runner「相对发现路径 + 绝对 resolve」的口径差同构。
|
||||
let cwd = std::env::current_dir().expect("cwd");
|
||||
let dir = cwd.join("__selfimport_repro__");
|
||||
let file = dir.join("selfm.js");
|
||||
std::fs::create_dir_all(&dir).expect("mkdir");
|
||||
std::fs::write(&file, MODULE_SRC).expect("write module");
|
||||
let _cleanup = Cleanup(dir.clone());
|
||||
|
||||
let allocator = oxide_parser::Allocator::default();
|
||||
let source = std::fs::read_to_string(&file).expect("read module");
|
||||
let program = oxide_parser::parse_module(&allocator, &source).expect("parse module");
|
||||
// 顶层模块路径用相对发现路径(不 canonicalize),复刻 runner 侧口径。
|
||||
let module = Compiler::new()
|
||||
.compile_module(&program, "__selfimport_repro__/selfm.js", &mut AbsLoader)
|
||||
.expect("compile module");
|
||||
|
||||
let mut vm = Vm::new();
|
||||
vm.run(&Arc::new(module)).expect("module run");
|
||||
// 自导入须绑定自身命名空间,整模块只求值一次;误判为外部依赖会二次求值。
|
||||
let r = probe(&mut vm, "globalThis.__evals");
|
||||
assert!(r.is_int() && r.as_int() == 1, "自导入模块应只求值一次,实际 __evals={r:?}");
|
||||
}
|
||||
Reference in New Issue
Block a user