diff --git a/crates/rspack_plugin_rstest/src/mock_method_dependency.rs b/crates/rspack_plugin_rstest/src/mock_method_dependency.rs index 638b239a4a18..ea8402e03f29 100644 --- a/crates/rspack_plugin_rstest/src/mock_method_dependency.rs +++ b/crates/rspack_plugin_rstest/src/mock_method_dependency.rs @@ -1,19 +1,11 @@ use rspack_cacheable::{cacheable, cacheable_dyn, with::Skip}; use rspack_core::{ - AsContextDependency, AsModuleDependency, ConditionalInitFragment, DependencyCodeGeneration, - DependencyId, DependencyRange, DependencyTemplate, DependencyTemplateType, DependencyType, - InitFragmentExt, InitFragmentKey, InitFragmentStage, NormalInitFragment, RuntimeCondition, - TemplateContext, TemplateReplaceSource, import_statement, + AsContextDependency, AsModuleDependency, DependencyCodeGeneration, DependencyRange, + DependencyTemplate, DependencyTemplateType, DependencyType, InitFragmentExt, InitFragmentKey, + InitFragmentStage, NormalInitFragment, TemplateContext, TemplateReplaceSource, }; use swc_core::common::Span; -#[cacheable] -#[derive(Debug, Clone)] -pub enum Position { - Before, - After, -} - #[cacheable] #[derive(Debug, Clone)] pub struct MockMethodDependency { @@ -24,16 +16,18 @@ pub struct MockMethodDependency { request: String, hoist: bool, method: MockMethod, - module_dep_id: Option, - position: Position, } #[cacheable] #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(dead_code)] pub enum MockMethod { Mock, DoMock, + MockRequire, + DoMockRequire, Unmock, + DoUnmock, Hoisted, } @@ -44,8 +38,6 @@ impl MockMethodDependency { request: String, hoist: bool, method: MockMethod, - module_dep_id: Option, - position: Position, ) -> Self { Self { call_expr_span, @@ -53,8 +45,6 @@ impl MockMethodDependency { request, hoist, method, - module_dep_id, - position, } } } @@ -86,14 +76,7 @@ impl DependencyTemplate for MockMethodDependencyTemplate { source: &mut TemplateReplaceSource, code_generatable_context: &mut TemplateContext, ) { - let TemplateContext { - module, - runtime_requirements, - compilation, - init_fragments, - runtime, - .. - } = code_generatable_context; + let TemplateContext { init_fragments, .. } = code_generatable_context; let dep = dep .as_any() .downcast_ref::() @@ -103,52 +86,33 @@ impl DependencyTemplate for MockMethodDependencyTemplate { let hoist_flag = match dep.method { MockMethod::Mock => "MOCK", MockMethod::DoMock => "", // won't be used. + MockMethod::MockRequire => "MOCKREQUIRE", + MockMethod::DoMockRequire => "", // won't be used. MockMethod::Unmock => "UNMOCK", MockMethod::Hoisted => "HOISTED", + MockMethod::DoUnmock => "", // won't be used. }; let mock_method = match dep.method { MockMethod::Mock => "rstest_mock", MockMethod::DoMock => "rstest_do_mock", + MockMethod::MockRequire => "rstest_mock_require", + MockMethod::DoMockRequire => "rstest_do_mock_require", MockMethod::Unmock => "rstest_unmock", MockMethod::Hoisted => "rstest_hoisted", + MockMethod::DoUnmock => "rstest_do_unmock", }; // Hoist placeholder init fragment. - let init = NormalInitFragment::new( - format!("/* RSTEST:{hoist_flag}_PLACEHOLDER:{request} */;"), - InitFragmentStage::StageESMImports, - match dep.position { - Position::Before => 0, - Position::After => i32::MAX - 1, - }, - InitFragmentKey::Const(format!("rstest mock_hoist {request}")), - None, - ); - init_fragments.push(init.boxed()); - - if dep.method == MockMethod::Mock - && let Some(module_dep_id) = dep.module_dep_id - { - let content: (String, String) = import_statement( - *module, - *runtime, - compilation, - runtime_requirements, - &module_dep_id, - request, - false, - ); - - // Redeclaration init fragment. - init_fragments.push(Box::new(ConditionalInitFragment::new( - format!("{}{}", content.0, content.1), - InitFragmentStage::StageAsyncESMImports, - i32::MAX, - InitFragmentKey::ESMImport(format!("{} {}", request, "mock")), + if !hoist_flag.is_empty() { + let init = NormalInitFragment::new( + format!("/* RSTEST:{hoist_flag}_PLACEHOLDER:{request} */;"), + InitFragmentStage::StageESMImports, + 0, + InitFragmentKey::Const(format!("rstest mock_hoist {request}")), None, - RuntimeCondition::Boolean(true), - ))); + ); + init_fragments.push(init.boxed()); } // Start before hoist. diff --git a/crates/rspack_plugin_rstest/src/mock_module_id_dependency.rs b/crates/rspack_plugin_rstest/src/mock_module_id_dependency.rs index 384c9d09c53d..a7b35e84926d 100644 --- a/crates/rspack_plugin_rstest/src/mock_module_id_dependency.rs +++ b/crates/rspack_plugin_rstest/src/mock_module_id_dependency.rs @@ -1,9 +1,8 @@ use rspack_cacheable::{cacheable, cacheable_dyn}; use rspack_core::{ - AsContextDependency, ConditionalInitFragment, Dependency, DependencyCategory, - DependencyCodeGeneration, DependencyId, DependencyRange, DependencyTemplate, - DependencyTemplateType, DependencyType, ExtendedReferencedExport, FactorizeInfo, InitFragmentKey, - InitFragmentStage, ModuleDependency, ModuleGraph, ModuleGraphCacheArtifact, RuntimeCondition, + AsContextDependency, Dependency, DependencyCategory, DependencyCodeGeneration, DependencyId, + DependencyRange, DependencyTemplate, DependencyTemplateType, DependencyType, + ExtendedReferencedExport, FactorizeInfo, ModuleDependency, ModuleGraph, ModuleGraphCacheArtifact, RuntimeSpec, TemplateContext, TemplateReplaceSource, }; @@ -20,8 +19,6 @@ pub struct MockModuleIdDependency { factorize_info: FactorizeInfo, category: DependencyCategory, pub suffix: Option, - hoist: bool, - await_factory: bool, } #[allow(clippy::too_many_arguments)] @@ -33,8 +30,6 @@ impl MockModuleIdDependency { optional: bool, category: DependencyCategory, suffix: Option, - hoist: bool, - async_factory: bool, ) -> Self { Self { range, @@ -45,8 +40,6 @@ impl MockModuleIdDependency { factorize_info: Default::default(), category, suffix, - hoist, - await_factory: async_factory, } } } @@ -136,8 +129,6 @@ impl DependencyTemplate for MockModuleIdDependencyTemplate { source: &mut TemplateReplaceSource, code_generatable_context: &mut TemplateContext, ) { - let TemplateContext { init_fragments, .. } = code_generatable_context; - let dep = dep .as_any() .downcast_ref::() @@ -150,18 +141,6 @@ impl DependencyTemplate for MockModuleIdDependencyTemplate { dep.weak, ); - if dep.hoist && dep.await_factory { - // Await exec init fragment. - init_fragments.push(Box::new(ConditionalInitFragment::new( - format!("await __webpack_require__.rstest_exec({module_id})\n"), - InitFragmentStage::StageAsyncESMImports, - i32::MAX - 1, - InitFragmentKey::ESMImport(format!("{}_{}", module_id, "mock")), - None, - RuntimeCondition::Boolean(true), - ))); - } - source.replace( dep.range.start, dep.range.end, diff --git a/crates/rspack_plugin_rstest/src/parser_plugin.rs b/crates/rspack_plugin_rstest/src/parser_plugin.rs index f510e82e5566..4de0c5b102e1 100644 --- a/crates/rspack_plugin_rstest/src/parser_plugin.rs +++ b/crates/rspack_plugin_rstest/src/parser_plugin.rs @@ -1,7 +1,6 @@ use camino::Utf8PathBuf; use rspack_core::{ - AsyncDependenciesBlock, ConstDependency, Dependency, DependencyRange, ImportAttributes, - SharedSourceMap, + AsyncDependenciesBlock, ConstDependency, DependencyRange, ImportAttributes, SharedSourceMap, }; use rspack_plugin_javascript::{ JavascriptParserPlugin, @@ -10,7 +9,7 @@ use rspack_plugin_javascript::{ self, eval::{self}, }, - visitors::JavascriptParser, + visitors::{JavascriptParser, Statement, VariableDeclaration}, }; use rspack_util::{SpanExt, atom::Atom, json_stringify, swc::get_swc_comments}; use swc_core::{ @@ -21,7 +20,7 @@ use swc_core::{ static RSTEST_MOCK_FIRST_ARG_TAG: &str = "strip the import call from the first arg of mock series"; use crate::{ - mock_method_dependency::{MockMethod, MockMethodDependency, Position}, + mock_method_dependency::{MockMethod, MockMethodDependency}, mock_module_id_dependency::MockModuleIdDependency, module_path_name_dependency::{ModulePathNameDependency, NameType}, }; @@ -242,7 +241,6 @@ impl RstestParserPlugin { is_esm: bool, method: MockMethod, has_b: bool, - async_factory: bool, ) { match call_expr.args.len() { 1 => { @@ -266,10 +264,7 @@ impl RstestParserPlugin { rspack_core::DependencyCategory::CommonJS }, if has_b { Some(", ".to_string()) } else { None }, - hoist, - async_factory, ); - let id = *dep.id(); parser.dependencies.push(Box::new(dep)); parser @@ -280,8 +275,6 @@ impl RstestParserPlugin { lit_str, hoist, method, - Some(id), - Position::Before, ))); if has_b { @@ -302,8 +295,6 @@ impl RstestParserPlugin { rspack_core::DependencyCategory::CommonJS }, None, - hoist, - false, ))); } } @@ -336,8 +327,6 @@ impl RstestParserPlugin { rspack_core::DependencyCategory::CommonJS }, None, - hoist, - true, ); parser @@ -348,8 +337,6 @@ impl RstestParserPlugin { lit_str, hoist, method, - Some(*module_dep.id()), - Position::After, ))); parser.dependencies.push(Box::new(module_dep)); } else { @@ -373,8 +360,6 @@ impl RstestParserPlugin { call_expr.span().real_lo().to_string(), true, MockMethod::Hoisted, - None, - Position::Before, ))); } _ => { @@ -445,12 +430,9 @@ impl RstestParserPlugin { return Some(true); } else { - // add CommonJsRequireDependency - let mut range_expr: DependencyRange = first_arg.span().into(); - range_expr.end += 1; // TODO: let dep: CommonJsRequireDependency = CommonJsRequireDependency::new( mocked_target.to_string(), - range_expr, + first_arg.span().into(), Some(call_expr.span.into()), parser.in_try, Some(parser.source_map.clone()), @@ -507,9 +489,134 @@ impl RstestParserPlugin { call_expr.span.real_hi(), ) } + + fn handle_rstest_method_call( + &self, + parser: &mut JavascriptParser, + call_expr: &CallExpr, + ident: &Ident, + prop: &swc_core::ecma::ast::IdentName, + ) -> Option { + match (ident.sym.as_str(), prop.sym.as_str()) { + // rs.mock + ("rs", "mock") | ("rstest", "mock") => { + self.process_mock(parser, call_expr, true, true, MockMethod::Mock, true); + Some(false) + } + // rs.mockRequire + ("rs", "mockRequire") | ("rstest", "mockRequire") => { + self.process_mock(parser, call_expr, true, false, MockMethod::Mock, true); + Some(false) + } + // rs.doMock + ("rs", "doMock") | ("rstest", "doMock") => { + self.process_mock(parser, call_expr, false, true, MockMethod::DoMock, true); + Some(false) + } + // rs.doMockRequire + ("rs", "doMockRequire") | ("rstest", "doMockRequire") => { + self.process_mock( + parser, + call_expr, + false, + false, + MockMethod::DoMockRequire, + true, + ); + Some(false) + } + // rs.importActual + ("rs", "importActual") | ("rstest", "importActual") => { + self.process_import_actual(parser, call_expr) + } + // rs.requireActual + ("rs", "requireActual") | ("rstest", "requireActual") => { + self.process_require_actual(parser, call_expr); + Some(false) + } + // rs.importMock + ("rs", "importMock") | ("rstest", "importMock") => self.load_mock(parser, call_expr, true), + // rs.requireMock + ("rs", "requireMock") | ("rstest", "requireMock") => self.load_mock(parser, call_expr, false), + // rs.unmock + ("rs", "unmock") | ("rstest", "unmock") => { + self.process_mock(parser, call_expr, true, true, MockMethod::Unmock, false); + Some(true) + } + // rs.doUnmock + ("rs", "doUnmock") | ("rstest", "doUnmock") => { + self.process_mock(parser, call_expr, false, true, MockMethod::Unmock, false); + Some(true) + } + // rs.resetModules + ("rs", "resetModules") | ("rstest", "resetModules") => self.reset_modules(parser, call_expr), + // rs.hoisted + ("rs", "hoisted") | ("rstest", "hoisted") => { + self.hoisted(parser, call_expr); + Some(true) + } + _ => { + // Not a mock module, continue. + None + } + } + } } impl JavascriptParserPlugin for RstestParserPlugin { + fn declarator( + &self, + parser: &mut JavascriptParser, + _expr: &swc_core::ecma::ast::VarDeclarator, + stmt: VariableDeclaration<'_>, + ) -> Option { + for decl in stmt.declarators() { + if let Some(init) = &decl.init { + let call_expr = match init.as_call() { + Some(call) => Some(call), + None => init + .as_await_expr() + .and_then(|await_expr| await_expr.arg.as_call()), + }; + + if let Some(call_expr) = call_expr + && let Some(callee_expr) = call_expr.callee.as_expr() + && let Some(member_expr) = callee_expr.as_member() + && let Some(obj_ident) = member_expr.obj.as_ident() + && let Some(prop_ident) = member_expr.prop.as_ident() + { + return self.handle_rstest_method_call(parser, call_expr, obj_ident, prop_ident); + } + } + } + + None + } + + fn statement(&self, parser: &mut JavascriptParser, stmt: Statement) -> Option { + let call_expr = match stmt { + Statement::Expr(expr_stmt) if expr_stmt.expr.as_call().is_some() => expr_stmt + .expr + .as_call() + .expect("call expression should exist after checking with is_some()"), + _ => return None, + }; + + if !self.hoist_mock_module { + return None; + } + + if let Some(callee_expr) = call_expr.callee.as_expr() + && let Some(member_expr) = callee_expr.as_member() + && let Some(obj_ident) = member_expr.obj.as_ident() + && let Some(prop_ident) = member_expr.prop.as_ident() + { + return self.handle_rstest_method_call(parser, call_expr, obj_ident, prop_ident); + } + + None + } + fn import_call(&self, parser: &mut JavascriptParser, call_expr: &CallExpr) -> Option { let first_arg = self.handle_mock_first_arg(parser, call_expr); if first_arg.is_some() { @@ -526,134 +633,6 @@ impl JavascriptParserPlugin for RstestParserPlugin { None } - fn call_member_chain( - &self, - parser: &mut JavascriptParser, - call_expr: &CallExpr, - _for_name: &str, - _members: &[Atom], - _members_optionals: &[bool], - _member_ranges: &[Span], - ) -> Option { - if self.hoist_mock_module { - let expr = call_expr.callee.as_expr(); - if let Some(expr) = expr { - let q = expr.as_member(); - if let Some(q) = q { - if let Some(ident) = q.obj.as_ident() - && let Some(prop) = q.prop.as_ident() - { - match (ident.sym.as_str(), prop.sym.as_str()) { - // rs.mock - ("rs", "mock") | ("rstest", "mock") => { - self.process_mock(parser, call_expr, true, true, MockMethod::Mock, true, true); - return Some(false); - } - // rs.mockRequire - ("rs", "mockRequire") | ("rstest", "mockRequire") => { - self.process_mock( - parser, - call_expr, - true, - false, - MockMethod::Mock, - true, - false, - ); - return Some(false); - } - // rs.doMock - ("rs", "doMock") | ("rstest", "doMock") => { - self.process_mock( - parser, - call_expr, - false, - true, - MockMethod::DoMock, - true, - false, - ); - return Some(false); - } - // rs.doMockRequire - ("rs", "doMockRequire") | ("rstest", "doMockRequire") => { - self.process_mock( - parser, - call_expr, - false, - false, - MockMethod::Mock, - true, - false, - ); - return Some(false); - } - // rs.importActual - ("rs", "importActual") | ("rstest", "importActual") => { - return self.process_import_actual(parser, call_expr); - } - // rs.requireActual - ("rs", "requireActual") | ("rstest", "requireActual") => { - return self.process_require_actual(parser, call_expr); - } - // rs.importMock - ("rs", "importMock") | ("rstest", "importMock") => { - return self.load_mock(parser, call_expr, true); - } - // rs.requireMock - ("rs", "requireMock") | ("rstest", "requireMock") => { - return self.load_mock(parser, call_expr, false); - } - // rs.unmock - ("rs", "unmock") | ("rstest", "unmock") => { - self.process_mock( - parser, - call_expr, - true, - true, - MockMethod::Unmock, - false, - false, - ); - return Some(true); - } - // rs.doUnmock - ("rs", "doUnmock") | ("rstest", "doUnmock") => { - // return self.unmock_method(parser, call_expr, true); - self.process_mock( - parser, - call_expr, - false, - true, - MockMethod::Unmock, - false, - false, - ); - return Some(true); - } - // rs.resetModules - ("rs", "resetModules") | ("rstest", "resetModules") => { - return self.reset_modules(parser, call_expr); - } - // rs.hoisted - ("rs", "hoisted") | ("rstest", "hoisted") => { - self.hoisted(parser, call_expr); - return Some(true); - } - _ => { - // Not a mock module, continue. - return None; - } - } - } - } else { - return None; - } - } - } - None - } - fn identifier( &self, parser: &mut rspack_plugin_javascript::visitors::JavascriptParser, diff --git a/crates/rspack_plugin_rstest/src/plugin.rs b/crates/rspack_plugin_rstest/src/plugin.rs index e00cb68e951e..703ade266b7c 100644 --- a/crates/rspack_plugin_rstest/src/plugin.rs +++ b/crates/rspack_plugin_rstest/src/plugin.rs @@ -170,7 +170,7 @@ async fn mock_hoist_process_assets(&self, compilation: &mut Compilation) -> Resu files.push(file.clone()); } } - let regex = regex::Regex::new(r"\/\* RSTEST:(MOCK|UNMOCK)_(.*?):(.*?) \*\/") + let regex = regex::Regex::new(r"\/\* RSTEST:(MOCK|UNMOCK|MOCKREQUIRE)_(.*?):(.*?) \*\/") .expect("should initialize `Regex`"); for file in files {