diff --git a/crates/oxc_mangler/src/lib.rs b/crates/oxc_mangler/src/lib.rs index 66e059c8dcab3..3efb4f39eaf49 100644 --- a/crates/oxc_mangler/src/lib.rs +++ b/crates/oxc_mangler/src/lib.rs @@ -564,11 +564,40 @@ impl<'t> Mangler<'t> { semantic: &Semantic<'_>, ) -> IndexVec> { let classes = semantic.classes(); - classes + + let private_member_count: IndexVec = classes .elements .iter() .map(|class_elements| { - assert!(u32::try_from(class_elements.len()).is_ok(), "too many class elements"); + class_elements + .iter() + .filter_map(|element| { + if element.is_private { Some(element.name.to_string()) } else { None } + }) + .count() + }) + .collect(); + let parent_private_member_count: IndexVec = classes + .declarations + .iter_enumerated() + .map(|(class_id, _)| { + classes + .ancestors(class_id) + .skip(1) + .map(|id| private_member_count[id]) + .sum::() + }) + .collect(); + + classes + .elements + .iter_enumerated() + .map(|(class_id, class_elements)| { + let parent_private_member_count = parent_private_member_count[class_id]; + assert!( + u32::try_from(class_elements.len() + parent_private_member_count).is_ok(), + "too many class elements" + ); class_elements .iter() .filter_map(|element| { @@ -580,7 +609,13 @@ impl<'t> Mangler<'t> { clippy::cast_possible_truncation, reason = "checked above with assert" )] - let mangled = CompactStr::new(base54(i as u32).as_str()); + let mangled = CompactStr::new( + // Avoid reusing the same mangled name in parent classes. + // We can improve this by reusing names that are not used in child classes, + // but nesting a class inside another class is not common + // and that would require liveness analysis. + base54((parent_private_member_count + i) as u32).as_str(), + ); (name, mangled) }) .collect::>() diff --git a/crates/oxc_minifier/tests/mangler/mod.rs b/crates/oxc_minifier/tests/mangler/mod.rs index 91ff0bf848fa6..62d2f6b9bcb2f 100644 --- a/crates/oxc_minifier/tests/mangler/mod.rs +++ b/crates/oxc_minifier/tests/mangler/mod.rs @@ -125,6 +125,7 @@ fn private_member_mangling() { // Test same names across different classes should reuse mangled names "class A { #field = 1; #method() { return this.#field; } } class B { #field = 2; #method() { return this.#field; } }", "class A { #field = 1; #method() { return this.#field; } } class B { #field2 = 2; #method2() { return this.#field2; } }", + "class Outer { #shared = 1; #getInner() { return class { #method() { return this.#shared; } }; } }", ]; let mut snapshot = String::new(); diff --git a/crates/oxc_minifier/tests/mangler/snapshots/private_member_mangling.snap b/crates/oxc_minifier/tests/mangler/snapshots/private_member_mangling.snap index 02e6a36075da5..3568146d496f8 100644 --- a/crates/oxc_minifier/tests/mangler/snapshots/private_member_mangling.snap +++ b/crates/oxc_minifier/tests/mangler/snapshots/private_member_mangling.snap @@ -52,9 +52,9 @@ class Outer { #e = 1; inner() { return class e { - #e = 2; + #t = 2; get() { - return this.#e; + return this.#t; } }; } @@ -78,9 +78,9 @@ class Outer { #e = 1; getInner() { return class { - #e = 2; + #t = 2; method() { - return this.#e; + return this.#t; } }; } @@ -122,3 +122,15 @@ class B { return this.#e; } } + +class Outer { #shared = 1; #getInner() { return class { #method() { return this.#shared; } }; } } +class Outer { + #e = 1; + #t() { + return class { + #n() { + return this.#e; + } + }; + } +}