Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(oxc_minifier): define plugin with postfix wildcard #4979

Merged
merged 1 commit into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 161 additions & 16 deletions crates/oxc_minifier/src/plugins/replace_global_defines.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::cmp::Ordering;
use std::sync::Arc;

use oxc_allocator::Allocator;
Expand All @@ -18,8 +19,9 @@ pub struct ReplaceGlobalDefinesConfig(Arc<ReplaceGlobalDefinesConfigImpl>);

#[derive(Debug)]
struct ReplaceGlobalDefinesConfigImpl {
identifier_defines: Vec<(/* key */ CompactStr, /* value */ CompactStr)>,
dot_defines: Vec<DotDefine>,
identifier: Vec<(/* key */ CompactStr, /* value */ CompactStr)>,
dot: Vec<DotDefine>,
meta_proeperty: Vec<MetaPropertyDefine>,
}

#[derive(Debug)]
Expand All @@ -29,6 +31,20 @@ pub struct DotDefine {
pub value: CompactStr,
}

#[derive(Debug)]
pub struct MetaPropertyDefine {
/// only store parts after `import.meta`
pub parts: Vec<CompactStr>,
pub value: CompactStr,
pub postfix_wildcard: bool,
}

impl MetaPropertyDefine {
pub fn new(parts: Vec<CompactStr>, value: CompactStr, postfix_wildcard: bool) -> Self {
Self { parts, value, postfix_wildcard }
}
}

impl DotDefine {
fn new(parts: Vec<CompactStr>, value: CompactStr) -> Self {
Self { parts, value }
Expand All @@ -37,7 +53,9 @@ impl DotDefine {

enum IdentifierType {
Identifier,
DotDefines(Vec<CompactStr>),
DotDefines { parts: Vec<CompactStr> },
// import.meta.a
ImportMeta { parts: Vec<CompactStr>, postfix_wildcard: bool },
}

impl ReplaceGlobalDefinesConfig {
Expand All @@ -49,6 +67,7 @@ impl ReplaceGlobalDefinesConfig {
let allocator = Allocator::default();
let mut identifier_defines = vec![];
let mut dot_defines = vec![];
let mut meta_proeperty_defines = vec![];
for (key, value) in defines {
let key = key.as_ref();

Expand All @@ -59,13 +78,35 @@ impl ReplaceGlobalDefinesConfig {
IdentifierType::Identifier => {
identifier_defines.push((CompactStr::new(key), CompactStr::new(value)));
}
IdentifierType::DotDefines(parts) => {
IdentifierType::DotDefines { parts } => {
dot_defines.push(DotDefine::new(parts, CompactStr::new(value)));
}
IdentifierType::ImportMeta { parts, postfix_wildcard } => {
meta_proeperty_defines.push(MetaPropertyDefine::new(
parts,
CompactStr::new(value),
postfix_wildcard,
));
}
}
}

Ok(Self(Arc::new(ReplaceGlobalDefinesConfigImpl { identifier_defines, dot_defines })))
// Always move specific meta define before wildcard dot define
// Keep other order unchanged
// see test case replace_global_definitions_dot_with_postfix_mixed as an example
meta_proeperty_defines.sort_by(|a, b| {
if !a.postfix_wildcard && b.postfix_wildcard {
Ordering::Less
} else if a.postfix_wildcard && b.postfix_wildcard {
Ordering::Greater
} else {
Ordering::Equal
}
});
Ok(Self(Arc::new(ReplaceGlobalDefinesConfigImpl {
identifier: identifier_defines,
dot: dot_defines,
meta_proeperty: meta_proeperty_defines,
})))
}

fn check_key(key: &str) -> Result<IdentifierType, Vec<OxcDiagnostic>> {
Expand All @@ -79,14 +120,40 @@ impl ReplaceGlobalDefinesConfig {
}
return Ok(IdentifierType::Identifier);
}
let normalized_parts_len =
if parts[parts.len() - 1] == "*" { parts.len() - 1 } else { parts.len() };
// We can ensure now the parts.len() >= 2
let is_import_meta = parts[0] == "import" && parts[1] == "meta";

for part in &parts {
for part in &parts[0..normalized_parts_len] {
if !is_identifier_name(part) {
return Err(vec![OxcDiagnostic::error(format!("`{key}` is not an identifier."))]);
}
}

Ok(IdentifierType::DotDefines(parts.iter().map(|s| CompactStr::new(s)).collect()))
if is_import_meta {
Ok(IdentifierType::ImportMeta {
parts: parts
.iter()
.skip(2)
.take(normalized_parts_len - 2)
.map(|s| CompactStr::new(s))
.collect(),
postfix_wildcard: normalized_parts_len != parts.len(),
})
// StaticMemberExpression with postfix wildcard
} else if normalized_parts_len != parts.len() {
Err(vec![OxcDiagnostic::error(
"postfix wildcard is only allowed for `import.meta`.".to_string(),
)])
} else {
Ok(IdentifierType::DotDefines {
parts: parts
.iter()
.take(normalized_parts_len)
.map(|s| CompactStr::new(s))
.collect(),
})
}
}

fn check_value(allocator: &Allocator, source_text: &str) -> Result<(), Vec<OxcDiagnostic>> {
Expand Down Expand Up @@ -136,7 +203,7 @@ impl<'a> ReplaceGlobalDefines<'a> {

fn replace_identifier_defines(&self, expr: &mut Expression<'a>) {
if let Expression::Identifier(ident) = expr {
for (key, value) in &self.config.0.identifier_defines {
for (key, value) in &self.config.0.identifier {
if ident.name.as_str() == key {
let value = self.parse_value(value);
*expr = value;
Expand All @@ -147,15 +214,93 @@ impl<'a> ReplaceGlobalDefines<'a> {
}

fn replace_dot_defines(&mut self, expr: &mut Expression<'a>) {
if let Expression::StaticMemberExpression(member) = expr {
for dot_define in &self.config.0.dot_defines {
if Self::is_dot_define(dot_define, member) {
let value = self.parse_value(&dot_define.value);
*expr = value;
break;
let Expression::StaticMemberExpression(member) = expr else {
return;
};
for dot_define in &self.config.0.dot {
if Self::is_dot_define(dot_define, member) {
let value = self.parse_value(&dot_define.value);
*expr = value;
return;
}
}
for meta_proeperty_define in &self.config.0.meta_proeperty {
let ret = Self::is_meta_property_define(meta_proeperty_define, member);
if ret {
let value = self.parse_value(&meta_proeperty_define.value);
*expr = value;
break;
}
}
}

pub fn is_meta_property_define(
meta_define: &MetaPropertyDefine,
member: &StaticMemberExpression<'a>,
) -> bool {
debug_assert!(!meta_define.parts.is_empty());

let mut current_part_member_expression = Some(member);
let mut cur_part_name = &member.property.name;
let mut is_full_match = true;
let mut i = meta_define.parts.len() - 1;
let mut has_matched_part = false;
loop {
let part = &meta_define.parts[i];
let matched = cur_part_name.as_str() == part;
if matched {
has_matched_part = true;
} else {
is_full_match = false;
// Considering import.meta.env.*
// ```js
// import.meta.env.test // should matched
// import.res.meta.env // should not matched
// ```
// So we use has_matched_part to track if any part has matched.

if !meta_define.postfix_wildcard || has_matched_part {
return false;
}
}

current_part_member_expression = if let Some(member) = current_part_member_expression {
match &member.object {
Expression::StaticMemberExpression(member) => {
cur_part_name = &member.property.name;
Some(member)
}
Expression::MetaProperty(_) => {
if meta_define.postfix_wildcard {
// `import.meta.env` should not match `import.meta.env.*`
return has_matched_part && !is_full_match;
}
return true;
}
Expression::Identifier(_) => {
return false;
}
_ => None,
}
} else {
return false;
};

// Config `import.meta.env.* -> 'undefined'`
// Considering try replace `import.meta.env` to `undefined`, for the first loop the i is already
// 0, if it did not match part name and still reach here, that means
// current_part_member_expression is still something, and possible to match in the
// further loop
if i == 0 && matched {
break;
}

if matched {
i -= 1;
}
}

false
}

pub fn is_dot_define(dot_define: &DotDefine, member: &StaticMemberExpression<'a>) -> bool {
Expand Down
26 changes: 26 additions & 0 deletions crates/oxc_minifier/tests/plugins/replace_global_defines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,29 @@ fn replace_global_definitions_dot() {
test("foo.process.NODE_ENV", "foo.process.NODE_ENV", config);
}
}

#[test]
fn replace_global_definitions_dot_with_postfix_wildcard() {
{
let config =
ReplaceGlobalDefinesConfig::new(&[("import.meta.env.*", "undefined")]).unwrap();
test("import.meta.env.result", "undefined", config.clone());
test("import.meta.env", "import.meta.env", config);
}
}

#[test]
fn replace_global_definitions_dot_with_postfix_mixed() {
{
let config = ReplaceGlobalDefinesConfig::new(&[
("import.meta.env.*", "undefined"),
("import.meta.env", "env"),
])
.unwrap();
test("import.meta.env.result", "undefined", config.clone());
test("import.meta.env.result.many.nested", "undefined", config.clone());
test("import.meta.env", "env", config.clone());
test("import.meta.somethingelse", "import.meta.somethingelse", config.clone());
test("import.meta", "import.meta", config);
}
}
Boshen marked this conversation as resolved.
Show resolved Hide resolved