From e7e9d7b970d96bb3f7fcfd9ddf585e710315b7e8 Mon Sep 17 00:00:00 2001 From: LingyuCoder Date: Fri, 19 Jul 2024 14:43:06 +0800 Subject: [PATCH 1/2] feat: support destructuring of import.meta --- .../src/parser_plugin/import_meta_plugin.rs | 103 +++++++++++++----- .../src/visitors/dependency/parser/mod.rs | 11 ++ .../dependency/parser/walk_block_pre.rs | 5 + .../visitors/dependency/parser/walk_pre.rs | 58 ++++++++-- .../cases/esm/import-meta/index.js | 14 +-- 5 files changed, 150 insertions(+), 41 deletions(-) diff --git a/crates/rspack_plugin_javascript/src/parser_plugin/import_meta_plugin.rs b/crates/rspack_plugin_javascript/src/parser_plugin/import_meta_plugin.rs index 0eb4f356af6..697bf5e2d8e 100644 --- a/crates/rspack_plugin_javascript/src/parser_plugin/import_meta_plugin.rs +++ b/crates/rspack_plugin_javascript/src/parser_plugin/import_meta_plugin.rs @@ -1,4 +1,4 @@ -use rspack_core::{ConstDependency, SpanExt}; +use rspack_core::{property_access, ConstDependency, SpanExt}; use rspack_error::miette::Severity; use swc_core::common::{Span, Spanned}; use swc_core::ecma::ast::MemberProp; @@ -11,12 +11,28 @@ use crate::visitors::ExportedVariableInfo; use crate::visitors::JavascriptParser; use crate::visitors::{create_traceable_error, RootName}; -// Port from https://github.com/webpack/webpack/blob/main/lib/dependencies/ImportMetaPlugin.js -// TODO: -// - scan `import.meta.url.indexOf("index.js")` -// - evaluate expression. eg `import.meta.env && import.meta.env.xx` should be `false` pub struct ImportMetaPlugin; +impl ImportMetaPlugin { + fn import_meta_url(&self, parser: &JavascriptParser) -> String { + Url::from_file_path(&parser.resource_data.resource) + .expect("should be a path") + .to_string() + } + + fn import_meta_webpack_version(&self) -> String { + "5".to_string() + } + + fn import_meta_unknown_property(&self, members: &Vec) -> String { + format!( + r#"/* unsupported import.meta.{} */ undefined{}"#, + members.join("."), + property_access(members, 1) + ) + } +} + impl JavascriptParserPlugin for ImportMetaPlugin { fn evaluate_typeof( &self, @@ -57,8 +73,11 @@ impl JavascriptParserPlugin for ImportMetaPlugin { if ident == expr_name::IMPORT_META_WEBPACK { Some(eval::evaluate_to_number(5_f64, start, end)) } else if ident == expr_name::IMPORT_META_URL { - let url = Url::from_file_path(&parser.resource_data.resource).expect("should be a path"); - Some(eval::evaluate_to_string(url.to_string(), start, end)) + Some(eval::evaluate_to_string( + self.import_meta_url(parser), + start, + end, + )) } else { None } @@ -112,29 +131,60 @@ impl JavascriptParserPlugin for ImportMetaPlugin { span: Span, ) -> Option { if root_name == expr_name::IMPORT_META { - // import.meta - // warn when access import.meta directly - parser.warning_diagnostics.push(Box::new(create_traceable_error( + if let Some(referenced_properties_in_destructuring) = + parser.destructuring_assignment_properties_for(&span) + { + let mut content = vec![]; + for prop in referenced_properties_in_destructuring { + if prop == "url" { + content.push(format!(r#"url: "{}""#, self.import_meta_url(parser))) + } else if prop == "webpack" { + content.push(format!( + r#"webpack: {}"#, + self.import_meta_webpack_version() + )); + } else { + content.push(format!( + r#"[{}]: {}"#, + serde_json::to_string(&prop).expect("json stringify failed"), + self.import_meta_unknown_property(&vec![prop]) + )); + } + } + parser + .presentational_dependencies + .push(Box::new(ConstDependency::new( + span.real_lo(), + span.real_hi(), + format!("({{{}}})", content.join(",")).into(), + None, + ))); + Some(true) + } else { + // import.meta + // warn when access import.meta directly + parser.warning_diagnostics.push(Box::new(create_traceable_error( "Critical dependency".into(), "Accessing import.meta directly is unsupported (only property access or destructuring is supported)".into(), parser.source_file, span.into() ).with_severity(Severity::Warning))); - let content = if parser.is_asi_position(span.lo()) { - ";({})" - } else { - "({})" - }; - parser - .presentational_dependencies - .push(Box::new(ConstDependency::new( - span.real_lo(), - span.real_hi(), - content.into(), - None, - ))); - Some(true) + let content = if parser.is_asi_position(span.lo()) { + ";({})" + } else { + "({})" + }; + parser + .presentational_dependencies + .push(Box::new(ConstDependency::new( + span.real_lo(), + span.real_hi(), + content.into(), + None, + ))); + Some(true) + } } else { None } @@ -148,13 +198,12 @@ impl JavascriptParserPlugin for ImportMetaPlugin { ) -> Option { if for_name == expr_name::IMPORT_META_URL { // import.meta.url - let url = Url::from_file_path(&parser.resource_data.resource).expect("should be a path"); parser .presentational_dependencies .push(Box::new(ConstDependency::new( member_expr.span().real_lo(), member_expr.span().real_hi(), - format!("'{url}'").into(), + format!("'{}'", self.import_meta_url(parser)).into(), None, ))); Some(true) @@ -165,7 +214,7 @@ impl JavascriptParserPlugin for ImportMetaPlugin { .push(Box::new(ConstDependency::new( member_expr.span().real_lo(), member_expr.span().real_hi(), - "5".to_string().into(), + self.import_meta_webpack_version().into(), None, ))); Some(true) diff --git a/crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs b/crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs index 37455dac52d..6ddb1d17c0c 100644 --- a/crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs +++ b/crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs @@ -230,6 +230,7 @@ pub struct JavascriptParser<'parser> { pub(crate) member_expr_in_optional_chain: bool, // TODO: delete `properties_in_destructuring` pub(crate) properties_in_destructuring: FxHashMap>, + pub(crate) destructuring_assignment_properties: Option>>, pub(crate) semicolons: &'parser mut FxHashSet, pub(crate) statement_path: Vec, pub(crate) prev_statement: Option, @@ -379,6 +380,7 @@ impl<'parser> JavascriptParser<'parser> { module_identifier, member_expr_in_optional_chain: false, properties_in_destructuring: Default::default(), + destructuring_assignment_properties: None, semicolons, statement_path: Default::default(), current_tag_info: None, @@ -854,6 +856,7 @@ impl<'parser> JavascriptParser<'parser> { pub fn walk_program(&mut self, ast: &Program) { if self.plugin_drive.clone().program(self, ast).is_none() { + self.destructuring_assignment_properties = Some(FxHashMap::default()); match ast { Program::Module(m) => { self.set_strict(true); @@ -874,6 +877,7 @@ impl<'parser> JavascriptParser<'parser> { self.walk_statements(&s.body); } }; + self.destructuring_assignment_properties = None; } self.plugin_drive.clone().finish(self); } @@ -906,6 +910,13 @@ impl<'parser> JavascriptParser<'parser> { pub fn is_unresolved_ident(&mut self, str: &str) -> bool { self.definitions_db.get(self.definitions, str).is_none() } + + pub fn destructuring_assignment_properties_for(&self, span: &Span) -> Option> { + self + .destructuring_assignment_properties + .as_ref() + .and_then(|x| x.get(span).cloned()) + } } impl<'parser> JavascriptParser<'parser> { diff --git a/crates/rspack_plugin_javascript/src/visitors/dependency/parser/walk_block_pre.rs b/crates/rspack_plugin_javascript/src/visitors/dependency/parser/walk_block_pre.rs index a9490445b49..1f11e1ac125 100644 --- a/crates/rspack_plugin_javascript/src/visitors/dependency/parser/walk_block_pre.rs +++ b/crates/rspack_plugin_javascript/src/visitors/dependency/parser/walk_block_pre.rs @@ -93,6 +93,11 @@ impl<'parser> JavascriptParser<'parser> { if let Some(assign) = stmt.expr.as_assign() { self.pre_walk_assignment_expression(assign) } + if let Some(paren) = stmt.expr.as_paren() + && let Some(assign) = paren.expr.as_assign() + { + self.pre_walk_assignment_expression(assign) + } } pub(super) fn block_pre_walk_variable_declaration(&mut self, decl: &VarDecl) { diff --git a/crates/rspack_plugin_javascript/src/visitors/dependency/parser/walk_pre.rs b/crates/rspack_plugin_javascript/src/visitors/dependency/parser/walk_pre.rs index 7735e2cbfda..fc3f4d3e780 100644 --- a/crates/rspack_plugin_javascript/src/visitors/dependency/parser/walk_pre.rs +++ b/crates/rspack_plugin_javascript/src/visitors/dependency/parser/walk_pre.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; use rustc_hash::FxHashSet; +use swc_core::common::Spanned; use swc_core::ecma::ast::{AssignExpr, BlockStmt, CatchClause, Decl, DoWhileStmt}; use swc_core::ecma::ast::{ForInStmt, ForOfStmt, ForStmt, IfStmt, LabeledStmt, WithStmt}; use swc_core::ecma::ast::{ModuleDecl, ModuleItem, ObjectPat, ObjectPatProp, Stmt, WhileStmt}; @@ -193,9 +194,15 @@ impl<'parser> JavascriptParser<'parser> { for prop in &obj_pat.props { match prop { ObjectPatProp::KeyValue(prop) => { - let name = eval::eval_prop_name(&prop.key); - if let Some(id) = name.and_then(|id| id.as_string()) { - keys.insert(id); + if let Some(ident_key) = prop.key.as_ident() { + keys.insert(ident_key.sym.to_string()); + } else { + let name = eval::eval_prop_name(&prop.key); + if let Some(id) = name.and_then(|id| id.as_string()) { + keys.insert(id); + } else { + return None; + } } } ObjectPatProp::Assign(prop) => { @@ -208,29 +215,66 @@ impl<'parser> JavascriptParser<'parser> { } fn pre_walk_variable_declarator(&mut self, declarator: &VarDeclarator) { + if self.destructuring_assignment_properties.is_none() { + return; + } + let Some(init) = declarator.init.as_ref() else { return; }; let Some(obj_pat) = declarator.name.as_object() else { return; }; - let keys = self._pre_walk_object_pattern(obj_pat); - if keys.is_none() { + let Some(keys) = self._pre_walk_object_pattern(obj_pat) else { return; + }; + + let destructuring_assignment_properties = self + .destructuring_assignment_properties + .as_mut() + .unwrap_or_else(|| unreachable!()); + + if let Some(await_expr) = declarator + .init + .as_ref() + .and_then(|decl| decl.as_await_expr()) + { + destructuring_assignment_properties.insert(await_expr.arg.span(), keys); + } else { + destructuring_assignment_properties.insert(declarator.init.span(), keys); } + if let Some(assign) = init.as_assign() { self.pre_walk_assignment_expression(assign); } } pub(super) fn pre_walk_assignment_expression(&mut self, assign: &AssignExpr) { + if self.destructuring_assignment_properties.is_none() { + return; + } let Some(left) = assign.left.as_pat().and_then(|pat| pat.as_object()) else { return; }; - let keys = self._pre_walk_object_pattern(left); - if keys.is_none() { + let Some(mut keys) = self._pre_walk_object_pattern(left) else { return; + }; + + let destructuring_assignment_properties = self + .destructuring_assignment_properties + .as_mut() + .unwrap_or_else(|| unreachable!()); + + if let Some(set) = destructuring_assignment_properties.remove(&assign.span()) { + keys.extend(set); } + + if let Some(await_expr) = assign.right.as_await_expr() { + destructuring_assignment_properties.insert(await_expr.arg.span(), keys); + } else { + destructuring_assignment_properties.insert(assign.right.span(), keys); + } + if let Some(right) = assign.right.as_assign() { self.pre_walk_assignment_expression(right) } diff --git a/tests/webpack-test/cases/esm/import-meta/index.js b/tests/webpack-test/cases/esm/import-meta/index.js index 950f1556dc6..a8de2202166 100644 --- a/tests/webpack-test/cases/esm/import-meta/index.js +++ b/tests/webpack-test/cases/esm/import-meta/index.js @@ -52,10 +52,10 @@ it("should add warning on direct import.meta usage", () => { expect(Object.keys(import.meta)).toHaveLength(0); }); -// it("should support destructuring assignment", () => { -// let version, url2, c; -// ({ webpack: version } = { url: url2 } = { c } = import.meta); -// expect(version).toBeTypeOf("number"); -// expect(url2).toBe(url); -// expect(c).toBe(undefined); -// }); +it("should support destructuring assignment", () => { + let version, url2, c; + ({ webpack: version } = { url: url2 } = { c } = import.meta); + expect(version).toBeTypeOf("number"); + expect(url2).toBe(url); + expect(c).toBe(undefined); +}); From 9e8af95915e7ed3ac50b43a45cef9b1f7fa8c749 Mon Sep 17 00:00:00 2001 From: LingyuCoder Date: Fri, 19 Jul 2024 15:11:17 +0800 Subject: [PATCH 2/2] feat: support destructuring of import.meta --- .../src/parser_plugin/import_meta_plugin.rs | 20 ++++++++++++++++--- .../cases/esm/import-meta/index.js | 2 +- .../cases/esm/import-meta/test.filter.js | 17 ---------------- 3 files changed, 18 insertions(+), 21 deletions(-) delete mode 100644 tests/webpack-test/cases/esm/import-meta/test.filter.js diff --git a/crates/rspack_plugin_javascript/src/parser_plugin/import_meta_plugin.rs b/crates/rspack_plugin_javascript/src/parser_plugin/import_meta_plugin.rs index 697bf5e2d8e..12bf89bca5a 100644 --- a/crates/rspack_plugin_javascript/src/parser_plugin/import_meta_plugin.rs +++ b/crates/rspack_plugin_javascript/src/parser_plugin/import_meta_plugin.rs @@ -1,3 +1,4 @@ +use itertools::Itertools; use rspack_core::{property_access, ConstDependency, SpanExt}; use rspack_error::miette::Severity; use swc_core::common::{Span, Spanned}; @@ -6,10 +7,10 @@ use url::Url; use super::JavascriptParserPlugin; use crate::utils::eval; -use crate::visitors::expr_name; -use crate::visitors::ExportedVariableInfo; use crate::visitors::JavascriptParser; use crate::visitors::{create_traceable_error, RootName}; +use crate::visitors::{expr_name, AllowedMemberTypes}; +use crate::visitors::{ExportedVariableInfo, MemberExpressionInfo}; pub struct ImportMetaPlugin; @@ -232,12 +233,25 @@ impl JavascriptParserPlugin for ImportMetaPlugin { match root_info { ExportedVariableInfo::Name(root) => { if root == expr_name::IMPORT_META { + let members = parser + .get_member_expression_info(expr, AllowedMemberTypes::Expression) + .and_then(|info| match info { + MemberExpressionInfo::Expression(res) => Some(res.members), + _ => None, + }); parser .presentational_dependencies .push(Box::new(ConstDependency::new( expr.span().real_lo(), expr.span().real_hi(), - "undefined".into(), + members + .map(|members| { + self.import_meta_unknown_property( + &members.iter().map(|x| x.to_string()).collect_vec(), + ) + }) + .unwrap_or("undefined".to_string()) + .into(), None, ))); return Some(true); diff --git a/tests/webpack-test/cases/esm/import-meta/index.js b/tests/webpack-test/cases/esm/import-meta/index.js index a8de2202166..2aad84967ba 100644 --- a/tests/webpack-test/cases/esm/import-meta/index.js +++ b/tests/webpack-test/cases/esm/import-meta/index.js @@ -45,7 +45,7 @@ it("should return correct import.meta.webpack", () => { it("should return undefined for unknown property", () => { expect(import.meta.other).toBe(undefined); if (typeof import.meta.other !== "undefined") require("fail"); - // expect(() => import.meta.other.other.other).toThrowError(); + expect(() => import.meta.other.other.other).toThrowError(); }); it("should add warning on direct import.meta usage", () => { diff --git a/tests/webpack-test/cases/esm/import-meta/test.filter.js b/tests/webpack-test/cases/esm/import-meta/test.filter.js deleted file mode 100644 index ba138d5e5d3..00000000000 --- a/tests/webpack-test/cases/esm/import-meta/test.filter.js +++ /dev/null @@ -1,17 +0,0 @@ -const { FilteredStatus } = require("../../../lib/util/filterUtil"); - -/* -const supportsRequireInModule = require("../../../helpers/supportsRequireInModule"); - -module.exports = config => { - return !config.module || supportsRequireInModule(); -}; - -*/ -module.exports = () => { - return [ - FilteredStatus.PARTIAL_PASS, - "compile type evaluation typeof", - "https://github.com/web-infra-dev/rspack/issues/4350" - ]; -};