@@ -3,6 +3,7 @@ use oxc_ast::ast::*;
33use oxc_ast_visit:: Visit ;
44use oxc_ecmascript:: { constant_evaluation:: ConstantEvaluation , side_effects:: MayHaveSideEffects } ;
55use oxc_span:: GetSpan ;
6+ use oxc_syntax:: symbol:: SymbolId ;
67use oxc_traverse:: Ancestor ;
78
89use crate :: { ctx:: Ctx , keep_var:: KeepVar } ;
@@ -53,6 +54,8 @@ impl<'a> PeepholeOptimizations {
5354 self . remove_unused_assignment_expression ( expr, state, ctx) ;
5455 None
5556 }
57+ Expression :: CallExpression ( call_expr) => self . remove_call_expression ( call_expr, ctx) ,
58+
5659 _ => None ,
5760 } {
5861 * expr = folded_expr;
@@ -494,6 +497,70 @@ impl<'a> PeepholeOptimizations {
494497 None
495498 }
496499
500+ pub fn keep_track_of_empty_functions ( stmt : & mut Statement < ' a > , ctx : & mut Ctx < ' a , ' _ > ) {
501+ match stmt {
502+ Statement :: FunctionDeclaration ( func) => {
503+ if let Some ( body) = & func. body {
504+ if body. is_empty ( ) {
505+ let symbol_id = func. id . as_ref ( ) . and_then ( |id| id. symbol_id . get ( ) ) ;
506+ Self :: save_empty_function ( symbol_id, ctx) ;
507+ }
508+ }
509+ }
510+ Statement :: VariableDeclaration ( decl) => {
511+ for d in & decl. declarations {
512+ if d. init . as_ref ( ) . is_some_and ( |e|matches ! ( e, Expression :: ArrowFunctionExpression ( arrow) if arrow. body. is_empty( ) ) ) {
513+ if let BindingPatternKind :: BindingIdentifier ( id) = & d. id . kind {
514+ let symbol_id = id. symbol_id . get ( ) ;
515+ Self :: save_empty_function ( symbol_id, ctx) ;
516+ }
517+ }
518+ }
519+ }
520+ _ => { }
521+ }
522+ }
523+
524+ fn save_empty_function ( symbol_id : Option < SymbolId > , ctx : & mut Ctx < ' a , ' _ > ) {
525+ if let Some ( symbol_id) = symbol_id {
526+ if ctx. scoping ( ) . get_resolved_references ( symbol_id) . all ( |r| r. flags ( ) . is_read_only ( ) ) {
527+ ctx. state . empty_functions . insert ( symbol_id) ;
528+ }
529+ }
530+ }
531+
532+ fn remove_call_expression (
533+ & self ,
534+ call_expr : & mut CallExpression < ' a > ,
535+ ctx : & mut Ctx < ' a , ' _ > ,
536+ ) -> Option < Expression < ' a > > {
537+ if let Expression :: Identifier ( ident) = & call_expr. callee {
538+ if let Some ( reference_id) = ident. reference_id . get ( ) {
539+ if let Some ( symbol_id) = ctx. scoping ( ) . get_reference ( reference_id) . symbol_id ( ) {
540+ if ctx. state . empty_functions . contains ( & symbol_id) {
541+ if call_expr. arguments . is_empty ( ) {
542+ return Some ( ctx. ast . void_0 ( call_expr. span ) ) ;
543+ }
544+ let mut exprs = ctx. ast . vec ( ) ;
545+ for arg in call_expr. arguments . drain ( ..) {
546+ match arg {
547+ Argument :: SpreadElement ( e) => {
548+ exprs. push ( e. unbox ( ) . argument ) ;
549+ }
550+ match_expression ! ( Argument ) => {
551+ exprs. push ( arg. into_expression ( ) ) ;
552+ }
553+ }
554+ }
555+ exprs. push ( ctx. ast . void_0 ( call_expr. span ) ) ;
556+ return Some ( ctx. ast . expression_sequence ( call_expr. span , exprs) ) ;
557+ }
558+ }
559+ }
560+ }
561+ None
562+ }
563+
497564 /// Whether the indirect access should be kept.
498565 /// For example, `(0, foo.bar)()` should not be transformed to `foo.bar()`.
499566 /// Example case: `let o = { f() { assert.ok(this !== o); } }; (true && o.f)(); (true && o.f)``;`
@@ -563,7 +630,10 @@ impl<'a> LatePeepholeOptimizations {
563630/// <https://github.com/google/closure-compiler/blob/v20240609/test/com/google/javascript/jscomp/PeepholeRemoveDeadCodeTest.java>
564631#[ cfg( test) ]
565632mod test {
566- use crate :: tester:: { test, test_same} ;
633+ use crate :: {
634+ CompressOptions ,
635+ tester:: { test, test_options, test_same} ,
636+ } ;
567637
568638 #[ test]
569639 fn test_fold_block ( ) {
@@ -770,4 +840,18 @@ mod test {
770840 fn remove_constant_value ( ) {
771841 test ( "const foo = false; if (foo) { console.log('foo') }" , "const foo = !1;" ) ;
772842 }
843+
844+ #[ test]
845+ fn remove_empty_function ( ) {
846+ let options = CompressOptions :: smallest ( ) ;
847+ test_options ( "function foo() {} foo()" , "" , & options) ;
848+ test_options ( "function foo() {} foo(); foo()" , "" , & options) ;
849+ test_options ( "var foo = () => {}; foo()" , "" , & options) ;
850+ test_options ( "var foo = () => {}; foo(a)" , "a" , & options) ;
851+ test_options ( "var foo = () => {}; foo(a, b)" , "a, b" , & options) ;
852+ test_options ( "var foo = () => {}; foo(...a, b)" , "a, b" , & options) ;
853+ test_options ( "var foo = () => {}; foo(...a, ...b)" , "a, b" , & options) ;
854+ test_options ( "var foo = () => {}; x = foo()" , "x = void 0" , & options) ;
855+ test_options ( "var foo = () => {}; x = foo(a(), b())" , "x = (a(), b(), void 0)" , & options) ;
856+ }
773857}
0 commit comments