diff --git a/Cargo.lock b/Cargo.lock index 6092220ad973a5..b6d458b1395186 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1560,9 +1560,13 @@ dependencies = [ name = "oxc_mangler" version = "0.21.0" dependencies = [ + "insta", "itertools 0.13.0", + "oxc_allocator", "oxc_ast", + "oxc_codegen", "oxc_index", + "oxc_parser", "oxc_semantic", "oxc_span", ] diff --git a/crates/oxc_codegen/src/gen.rs b/crates/oxc_codegen/src/gen.rs index 85af160bce68ca..e05b47b03546bc 100644 --- a/crates/oxc_codegen/src/gen.rs +++ b/crates/oxc_codegen/src/gen.rs @@ -1079,18 +1079,10 @@ impl<'a, const MINIFY: bool> GenExpr for ParenthesizedExpression<'a> { impl<'a, const MINIFY: bool> Gen for IdentifierReference<'a> { fn gen(&self, p: &mut Codegen<{ MINIFY }>, _ctx: Context) { - if let Some(mangler) = &p.mangler { - if let Some(reference_id) = self.reference_id.get() { - if let Some(name) = mangler.get_reference_name(reference_id) { - let name = CompactStr::new(name); - p.add_source_mapping_for_name(self.span, &name); - p.print_str(&name); - return; - } - } - } - p.add_source_mapping_for_name(self.span, &self.name); - p.print_str(&self.name); + let name = p.get_identifier_reference_name(self); + let name = CompactStr::new(name); + p.add_source_mapping_for_name(self.span, &name); + p.print_str(&name); } } @@ -1103,7 +1095,10 @@ impl<'a, const MINIFY: bool> Gen for IdentifierName<'a> { impl<'a, const MINIFY: bool> Gen for BindingIdentifier<'a> { fn gen(&self, p: &mut Codegen<{ MINIFY }>, _ctx: Context) { - p.print_symbol(self.span, self.symbol_id.get(), self.name.as_str()); + let name = p.get_binding_identifier_name(self); + let name = CompactStr::new(name); + p.add_source_mapping_for_name(self.span, &name); + p.print_str(&name); } } @@ -1514,7 +1509,6 @@ impl<'a, const MINIFY: bool> Gen for ObjectPropertyKind<'a> { } } -// TODO: only print shorthand if key value are the same. impl<'a, const MINIFY: bool> Gen for ObjectProperty<'a> { fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) { if let Expression::FunctionExpression(func) = &self.value { @@ -1559,15 +1553,29 @@ impl<'a, const MINIFY: bool> Gen for ObjectProperty<'a> { return; } } + + let mut shorthand = false; + if let PropertyKey::StaticIdentifier(key) = &self.key { + if let Expression::Identifier(ident) = &self.value { + if key.name == p.get_identifier_reference_name(ident) { + shorthand = true; + } + } + } + if self.computed { p.print_char(b'['); } - self.key.gen(p, ctx); + if !shorthand { + self.key.gen(p, ctx); + } if self.computed { p.print_char(b']'); } - p.print_colon(); - p.print_soft_space(); + if !shorthand { + p.print_colon(); + p.print_soft_space(); + } self.value.gen_expr(p, Precedence::Assign, Context::default()); } } @@ -1925,7 +1933,17 @@ impl<'a, const MINIFY: bool> Gen for AssignmentTargetProperty<'a> { impl<'a, const MINIFY: bool> Gen for AssignmentTargetPropertyIdentifier<'a> { fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) { - self.binding.gen(p, ctx); + let ident_name = p.get_identifier_reference_name(&self.binding); + if ident_name == self.binding.name.as_str() { + self.binding.gen(p, ctx); + } else { + // `({x: a} = y);` + let ident_name = CompactStr::new(ident_name); + p.print_str(self.binding.name.as_str()); + p.print_colon(); + p.print_soft_space(); + p.print_str(ident_name.as_str()); + } if let Some(expr) = &self.init { p.print_soft_space(); p.print_equal(); @@ -2535,19 +2553,32 @@ impl<'a, const MINIFY: bool> Gen for ObjectPattern<'a> { } } -// TODO: only print shorthand if key value are the same. impl<'a, const MINIFY: bool> Gen for BindingProperty<'a> { fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) { p.add_source_mapping(self.span.start); if self.computed { p.print_char(b'['); } - self.key.gen(p, ctx); + + let mut shorthand = false; + if let PropertyKey::StaticIdentifier(key) = &self.key { + if let BindingPatternKind::BindingIdentifier(ident) = &self.value.kind { + if key.name == p.get_binding_identifier_name(ident) { + shorthand = true; + } + } + } + + if !shorthand { + self.key.gen(p, ctx); + } if self.computed { p.print_char(b']'); } - p.print_colon(); - p.print_soft_space(); + if !shorthand { + p.print_colon(); + p.print_soft_space(); + } self.value.gen(p, ctx); } } diff --git a/crates/oxc_codegen/src/lib.rs b/crates/oxc_codegen/src/lib.rs index e6fdeefce4aef7..dc7ac422d67728 100644 --- a/crates/oxc_codegen/src/lib.rs +++ b/crates/oxc_codegen/src/lib.rs @@ -14,16 +14,18 @@ use std::{borrow::Cow, ops::Range}; use rustc_hash::FxHashMap; use oxc_ast::{ - ast::{BlockStatement, Directive, Expression, Program, Statement}, + ast::{ + BindingIdentifier, BlockStatement, Directive, Expression, IdentifierReference, Program, + Statement, + }, Comment, Trivias, }; use oxc_mangler::Mangler; -use oxc_span::{CompactStr, Span}; +use oxc_span::Span; use oxc_syntax::{ identifier::is_identifier_part, operator::{BinaryOperator, UnaryOperator, UpdateOperator}, precedence::Precedence, - symbol::SymbolId, }; pub use crate::{ @@ -425,19 +427,25 @@ impl<'a, const MINIFY: bool> Codegen<'a, MINIFY> { } } - #[allow(clippy::needless_pass_by_value)] - fn print_symbol(&mut self, span: Span, symbol_id: Option, fallback: &str) { + fn get_identifier_reference_name(&self, reference: &IdentifierReference<'a>) -> &str { if let Some(mangler) = &self.mangler { - if let Some(symbol_id) = symbol_id { + if let Some(reference_id) = reference.reference_id.get() { + if let Some(name) = mangler.get_reference_name(reference_id) { + return name; + } + } + } + reference.name.as_str() + } + + fn get_binding_identifier_name(&self, ident: &BindingIdentifier<'a>) -> &str { + if let Some(mangler) = &self.mangler { + if let Some(symbol_id) = ident.symbol_id.get() { let name = mangler.get_symbol_name(symbol_id); - let name = CompactStr::new(name); - self.add_source_mapping_for_name(span, &name); - self.print_str(&name); - return; + return name; } } - self.add_source_mapping_for_name(span, fallback); - self.print_str(fallback); + ident.name.as_str() } fn print_space_before_operator(&mut self, next: Operator) { diff --git a/crates/oxc_codegen/tests/integration/unit.rs b/crates/oxc_codegen/tests/integration/unit.rs index 7ad988a7a96906..670ac475d434cf 100644 --- a/crates/oxc_codegen/tests/integration/unit.rs +++ b/crates/oxc_codegen/tests/integration/unit.rs @@ -126,6 +126,13 @@ fn for_stmt() { // ); } +#[test] +fn shorthand() { + test("let _ = { x }", "let _ = {x};\n"); + test("let { x } = y", "let { x } = y;\n"); + test("({ x } = y)", "({x} = y);\n"); +} + #[test] fn unicode_escape() { test("console.log('你好');", "console.log('你好');\n"); diff --git a/crates/oxc_isolated_declarations/tests/snapshots/function-parameters.snap b/crates/oxc_isolated_declarations/tests/snapshots/function-parameters.snap index ab84d4ebf11283..c006d77d4c6139 100644 --- a/crates/oxc_isolated_declarations/tests/snapshots/function-parameters.snap +++ b/crates/oxc_isolated_declarations/tests/snapshots/function-parameters.snap @@ -7,8 +7,8 @@ input_file: crates/oxc_isolated_declarations/tests/fixtures/function-parameters. export declare function fnDeclGood(p?: T, rParam?: string): void; export declare function fnDeclGood2(p?: T, rParam?: number): void; export declare function fooGood([a, b]?: any[]): number; -export declare const fooGood2: ({ a: a, b: b }?: object) => number; -export declare function fooGood3({ a: a, b: [{ c: c }] }: object): void; +export declare const fooGood2: ({ a, b }?: object) => number; +export declare function fooGood3({ a, b: [{ c }] }: object): void; export declare function fnDeclBad(p: T, rParam: T, r2: T): void; export declare function fnDeclBad2(p: T, r2: T): void; export declare function fnDeclBad3(p: T, rParam?: T, r2: T): void; diff --git a/crates/oxc_mangler/Cargo.toml b/crates/oxc_mangler/Cargo.toml index 3a8d3819cc8c27..e5717b71d7068c 100644 --- a/crates/oxc_mangler/Cargo.toml +++ b/crates/oxc_mangler/Cargo.toml @@ -26,3 +26,10 @@ oxc_ast = { workspace = true } oxc_semantic = { workspace = true } oxc_index = { workspace = true } itertools = { workspace = true } + +[dev-dependencies] +oxc_codegen = { workspace = true } +oxc_parser = { workspace = true } +oxc_span = { workspace = true } +oxc_allocator = { workspace = true } +insta = { workspace = true } diff --git a/crates/oxc_mangler/tests/integration/main.rs b/crates/oxc_mangler/tests/integration/main.rs new file mode 100644 index 00000000000000..c746940bf172bc --- /dev/null +++ b/crates/oxc_mangler/tests/integration/main.rs @@ -0,0 +1,24 @@ +mod tester; + +use std::fmt::Write; + +use tester::mangle; + +#[test] +fn mangler() { + let cases = [ + "function foo(a) {a}", + "function foo(a) { let _ = { x } }", + "function foo(a) { let { x } = y }", + "var x; function foo(a) { ({ x } = y) }", + ]; + + let snapshot = cases.into_iter().fold(String::new(), |mut w, case| { + write!(w, "{case}\n{}\n", mangle(case)).unwrap(); + w + }); + + insta::with_settings!({ prepend_module_to_snapshot => false, omit_expression => true }, { + insta::assert_snapshot!("mangler", snapshot); + }); +} diff --git a/crates/oxc_mangler/tests/integration/snapshots/mangler.snap b/crates/oxc_mangler/tests/integration/snapshots/mangler.snap new file mode 100644 index 00000000000000..acef0c9a68e021 --- /dev/null +++ b/crates/oxc_mangler/tests/integration/snapshots/mangler.snap @@ -0,0 +1,23 @@ +--- +source: crates/oxc_mangler/tests/integration/main.rs +--- +function foo(a) {a} +function a(b) { + b; +} + +function foo(a) { let _ = { x } } +function a(b) { + let c = {x}; +} + +function foo(a) { let { x } = y } +function a(b) { + let { x: c } = y; +} + +var x; function foo(a) { ({ x } = y) } +var a; +function b(c) { + ({x: a} = y); +} diff --git a/crates/oxc_mangler/tests/integration/tester.rs b/crates/oxc_mangler/tests/integration/tester.rs new file mode 100644 index 00000000000000..cdfdcc82cabcee --- /dev/null +++ b/crates/oxc_mangler/tests/integration/tester.rs @@ -0,0 +1,14 @@ +use oxc_allocator::Allocator; +use oxc_codegen::CodeGenerator; +use oxc_mangler::ManglerBuilder; +use oxc_parser::Parser; +use oxc_span::SourceType; + +pub fn mangle(source_text: &str) -> String { + let allocator = Allocator::default(); + let source_type = SourceType::default().with_module(true); + let ret = Parser::new(&allocator, source_text, source_type).parse(); + let program = ret.program; + let mangler = ManglerBuilder::default().build(&program); + CodeGenerator::new().with_mangler(Some(mangler)).build(&program).source_text +}