feat: parser 支持 ES module(parse_module)+ test262 放行 module 用例

- oxide_parser 新增 parse_module(SourceType::mjs),parse 重构为 parse_with_source_type
- oxide_test262 不再跳过 flags:[module]/features:module,module 用例走 parse_module
- 首轮实测:language/import 12 pass/35 fail(原全跳过,10/10 负向 parse 全过),
  language/module-code 248 pass/274 fail(parse-err 103/0,early-* 26/0)
- 顺带修复 oxide_test262 两个 clippy lint(single-match、unnecessary unwrap)
This commit is contained in:
爱特每羊队
2026-08-12 15:15:27 +08:00
parent ad547d5cd8
commit d1b5c533fc
2 changed files with 31 additions and 12 deletions
+20 -3
View File
@@ -17,11 +17,11 @@ pub use oxc_span::Span;
/// 使用 oxc 默认 `SourceType`(严格模式由语法自身决定)。返回 `Ok(program)` /// 使用 oxc 默认 `SourceType`(严格模式由语法自身决定)。返回 `Ok(program)`
/// 或 `Err`(收集全部解析错误,按源码顺序排列);若 oxc 内部 panic(不可恢复 /// 或 `Err`(收集全部解析错误,按源码顺序排列);若 oxc 内部 panic(不可恢复
/// 语法错误)则返回单个通用错误。 /// 语法错误)则返回单个通用错误。
pub fn parse<'a>(allocator: &'a Allocator, source: &'a str) -> Result<Program<'a>, Vec<OxideError>> { fn parse_with_source_type<'a>(
allocator: &'a Allocator, source: &'a str, source_type: oxc_span::SourceType,
) -> Result<Program<'a>, Vec<OxideError>> {
use oxc_parser::Parser; use oxc_parser::Parser;
use oxc_span::SourceType;
let source_type = SourceType::unambiguous();
let ret = Parser::new(allocator, source, source_type).parse(); let ret = Parser::new(allocator, source, source_type).parse();
if ret.panicked { if ret.panicked {
@@ -46,3 +46,20 @@ pub fn parse<'a>(allocator: &'a Allocator, source: &'a str) -> Result<Program<'a
Ok(ret.program) Ok(ret.program)
} }
/// 将 `source` 解析为 AST 根节点 `Program`(脚本/模块由语法内容自动判定)。
///
/// 使用 oxc 的 `unambiguous` SourceType:代码含顶层 import/export 时按模块解析,
/// 否则按脚本解析。返回 `Ok(program)` 或 `Err`(收集全部解析错误)。
pub fn parse<'a>(allocator: &'a Allocator, source: &'a str) -> Result<Program<'a>, Vec<OxideError>> {
parse_with_source_type(allocator, source, oxc_span::SourceType::unambiguous())
}
/// 将 `source` 强制按 ES module 解析为 AST 根节点 `Program`。
///
/// 与 [`parse`] 的唯一区别是 SourceType 固定为模块(`SourceType::mjs`):
/// 即使源码没有 import/export 也会按模块文法解析(如 `await` 保留字、
/// module-only early errors 等),用于 test262 `flags: [module]` 用例。
pub fn parse_module<'a>(allocator: &'a Allocator, source: &'a str) -> Result<Program<'a>, Vec<OxideError>> {
parse_with_source_type(allocator, source, oxc_span::SourceType::mjs())
}
+11 -9
View File
@@ -387,12 +387,10 @@ impl RunConfig {
/// 按测试元数据的 flags/features 判断是否应跳过,返回跳过原因(None 表示不跳过)。 /// 按测试元数据的 flags/features 判断是否应跳过,返回跳过原因(None 表示不跳过)。
fn is_skipped(meta: &TestMeta) -> Option<String> { fn is_skipped(meta: &TestMeta) -> Option<String> {
for flag in &meta.flags { for flag in &meta.flags {
match flag.as_str() { if flag.as_str() == "raw" {
"module" => return Some("module tests excluded".into()), return Some("raw tests excluded".into());
"raw" => return Some("raw tests excluded".into()),
// noStrict 测试放行——很多在严格模式下仍可通过;运行时跳过逻辑会捕获失败。
_ => {}
} }
// noStrict 测试放行——很多在严格模式下仍可通过;运行时跳过逻辑会捕获失败。
} }
// 保持大范围已实现 feature tag 可运行;只排除真正未实现的子特性。 // 保持大范围已实现 feature tag 可运行;只排除真正未实现的子特性。
@@ -401,7 +399,6 @@ fn is_skipped(meta: &TestMeta) -> Option<String> {
let excluded_features = [ let excluded_features = [
"Proxy", "Proxy",
"Intl", "Intl",
"module",
"Atomics", "Atomics",
"SharedArrayBuffer", "SharedArrayBuffer",
"cross-realm", "cross-realm",
@@ -449,6 +446,7 @@ fn run_test_inner(
let start = std::time::Instant::now(); let start = std::time::Instant::now();
let is_async = meta.flags.iter().any(|f| f == "async"); let is_async = meta.flags.iter().any(|f| f == "async");
let is_module = meta.flags.iter().any(|f| f == "module");
let code = match get_harness_prefix(meta, harness, harness_cache) { let code = match get_harness_prefix(meta, harness, harness_cache) {
Ok(prefix) => { Ok(prefix) => {
@@ -480,7 +478,11 @@ fn run_test_inner(
}; };
let alloc = oxide_parser::Allocator::default(); let alloc = oxide_parser::Allocator::default();
let program = match oxide_parser::parse(&alloc, &code) { let program = match if is_module {
oxide_parser::parse_module(&alloc, &code)
} else {
oxide_parser::parse(&alloc, &code)
} {
Ok(p) => p, Ok(p) => p,
Err(errs) => { Err(errs) => {
let dur = start.elapsed().as_millis() as u64; let dur = start.elapsed().as_millis() as u64;
@@ -572,13 +574,13 @@ fn judge_async_result(
} }
if output.contains("Test262:AsyncTestComplete") { if output.contains("Test262:AsyncTestComplete") {
if meta.negative.is_some() { if let Some(neg) = meta.negative.as_ref() {
return TestResult::fail( return TestResult::fail(
path.to_path_buf(), path.to_path_buf(),
dur, dur,
format!( format!(
"expected runtime error ({}), got: async complete", "expected runtime error ({}), got: async complete",
meta.negative.as_ref().unwrap().error_type neg.error_type
), ),
); );
} }