Skip to content

Commit

Permalink
feat: support destructuring of import.meta
Browse files Browse the repository at this point in the history
  • Loading branch information
LingyuCoder committed Jul 19, 2024
1 parent a5898ea commit e7e9d7b
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 41 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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>) -> String {
format!(
r#"/* unsupported import.meta.{} */ undefined{}"#,
members.join("."),
property_access(members, 1)
)
}
}

impl JavascriptParserPlugin for ImportMetaPlugin {
fn evaluate_typeof(
&self,
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -112,29 +131,60 @@ impl JavascriptParserPlugin for ImportMetaPlugin {
span: Span,
) -> Option<bool> {
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
}
Expand All @@ -148,13 +198,12 @@ impl JavascriptParserPlugin for ImportMetaPlugin {
) -> Option<bool> {
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)
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Atom, FxHashSet<Atom>>,
pub(crate) destructuring_assignment_properties: Option<FxHashMap<Span, FxHashSet<String>>>,
pub(crate) semicolons: &'parser mut FxHashSet<BytePos>,
pub(crate) statement_path: Vec<StatementPath>,
pub(crate) prev_statement: Option<StatementPath>,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand All @@ -874,6 +877,7 @@ impl<'parser> JavascriptParser<'parser> {
self.walk_statements(&s.body);
}
};
self.destructuring_assignment_properties = None;
}
self.plugin_drive.clone().finish(self);
}
Expand Down Expand Up @@ -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<FxHashSet<String>> {
self
.destructuring_assignment_properties
.as_ref()
.and_then(|x| x.get(span).cloned())
}
}

impl<'parser> JavascriptParser<'parser> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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) => {
Expand All @@ -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)
}
Expand Down
14 changes: 7 additions & 7 deletions tests/webpack-test/cases/esm/import-meta/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});

0 comments on commit e7e9d7b

Please sign in to comment.