mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(minifier): drop console statements and exprs (#552)
This commit is contained in:
parent
707e717f0d
commit
2591194dba
4 changed files with 83 additions and 13 deletions
|
|
@ -1,6 +1,7 @@
|
|||
#![allow(clippy::unused_self)]
|
||||
|
||||
mod fold;
|
||||
mod util;
|
||||
|
||||
use oxc_allocator::{Allocator, Vec};
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
|
|
@ -16,30 +17,47 @@ use oxc_syntax::{
|
|||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CompressOptions {
|
||||
/// Various optimizations for boolean context, for example `!!a ? b : c` → `a ? b : c`
|
||||
/// Default true
|
||||
/// Various optimizations for boolean context, for example `!!a ? b : c` → `a ? b : c`.
|
||||
///
|
||||
/// Default `true`
|
||||
pub booleans: bool,
|
||||
|
||||
/// Remove `debugger;` statements
|
||||
/// Default true
|
||||
/// Remove `debugger;` statements.
|
||||
///
|
||||
/// Default `true`
|
||||
pub drop_debugger: bool,
|
||||
|
||||
/// Join consecutive var statements
|
||||
/// Default true
|
||||
/// Remove `console.*` statements.
|
||||
///
|
||||
/// Default `false`
|
||||
pub drop_console: bool,
|
||||
|
||||
/// Join consecutive var statements.
|
||||
///
|
||||
/// Default `true`
|
||||
pub join_vars: bool,
|
||||
|
||||
/// Optimizations for do, while and for loops when we can statically determine the condition
|
||||
/// Default: true
|
||||
///
|
||||
/// Default `true`
|
||||
pub loops: bool,
|
||||
|
||||
/// Transforms `typeof foo == "undefined" into `foo === void 0`
|
||||
/// Default true
|
||||
///
|
||||
/// Default `true`
|
||||
pub typeofs: bool,
|
||||
}
|
||||
|
||||
impl Default for CompressOptions {
|
||||
fn default() -> Self {
|
||||
Self { booleans: true, drop_debugger: true, join_vars: true, loops: true, typeofs: true }
|
||||
Self {
|
||||
booleans: true,
|
||||
drop_debugger: true,
|
||||
drop_console: false,
|
||||
join_vars: true,
|
||||
loops: true,
|
||||
typeofs: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -101,6 +119,22 @@ impl<'a> Compressor<'a> {
|
|||
matches!(stmt, Statement::DebuggerStatement(_)) && self.options.drop_debugger
|
||||
}
|
||||
|
||||
/// Drop `console.*` expressions.
|
||||
/// Enabled by `compress.drop_console
|
||||
fn drop_console<'b>(&mut self, stmt: &'b Statement<'a>) -> bool {
|
||||
self.options.drop_console
|
||||
&& matches!(stmt, Statement::ExpressionStatement(expr) if util::is_console(&expr.expression))
|
||||
}
|
||||
|
||||
fn compress_console<'b>(&mut self, expr: &'b mut Expression<'a>) -> bool {
|
||||
if self.options.drop_console && util::is_console(expr) {
|
||||
*expr = self.create_void_0();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Join consecutive var statements
|
||||
fn join_vars<'b>(&mut self, stmts: &'b mut Vec<'a, Statement<'a>>) {
|
||||
// Collect all the consecutive ranges that contain joinable vars.
|
||||
|
|
@ -265,7 +299,7 @@ impl<'a> Compressor<'a> {
|
|||
|
||||
impl<'a, 'b> VisitMut<'a, 'b> for Compressor<'a> {
|
||||
fn visit_statements(&mut self, stmts: &'b mut Vec<'a, Statement<'a>>) {
|
||||
stmts.retain(|stmt| !self.drop_debugger(stmt));
|
||||
stmts.retain(|stmt| !(self.drop_debugger(stmt) || self.drop_console(stmt)));
|
||||
|
||||
self.join_vars(stmts);
|
||||
|
||||
|
|
@ -290,13 +324,14 @@ impl<'a, 'b> VisitMut<'a, 'b> for Compressor<'a> {
|
|||
|
||||
fn visit_variable_declaration(&mut self, decl: &'b mut VariableDeclaration<'a>) {
|
||||
for declarator in decl.declarations.iter_mut() {
|
||||
self.compress_variable_declarator(declarator);
|
||||
self.visit_variable_declarator(declarator);
|
||||
self.compress_variable_declarator(declarator);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expression(&mut self, expr: &'b mut Expression<'a>) {
|
||||
self.visit_expression_match(expr);
|
||||
self.compress_console(expr);
|
||||
self.fold_expression(expr);
|
||||
if !self.compress_undefined(expr) {
|
||||
self.compress_boolean(expr);
|
||||
|
|
|
|||
10
crates/oxc_minifier/src/compressor/util.rs
Normal file
10
crates/oxc_minifier/src/compressor/util.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
use oxc_hir::hir::Expression;
|
||||
|
||||
pub(super) fn is_console(expr: &Expression<'_>) -> bool {
|
||||
// let Statement::ExpressionStatement(expr) = stmt else { return false };
|
||||
let Expression::CallExpression(call_expr) = &expr else { return false };
|
||||
let Expression::MemberExpression(member_expr) = &call_expr.callee else { return false };
|
||||
let obj = member_expr.object();
|
||||
let Some(ident) = obj.get_identifier_reference() else { return false };
|
||||
ident.name == "console"
|
||||
}
|
||||
|
|
@ -11,8 +11,12 @@ use oxc_minifier::{CompressOptions, Minifier, MinifierOptions, PrinterOptions};
|
|||
use oxc_span::SourceType;
|
||||
|
||||
pub(crate) fn test(source_text: &str, expected: &str) {
|
||||
let source_type = SourceType::default();
|
||||
let options = MinifierOptions { mangle: false, ..MinifierOptions::default() };
|
||||
test_with_options(source_text, expected, options);
|
||||
}
|
||||
|
||||
pub(crate) fn test_with_options(source_text: &str, expected: &str, options: MinifierOptions) {
|
||||
let source_type = SourceType::default();
|
||||
let minified = Minifier::new(source_text, source_type, options).build();
|
||||
assert_eq!(expected, minified, "for source {source_text}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::test;
|
||||
use crate::{test, test_with_options, CompressOptions, MinifierOptions};
|
||||
|
||||
#[test]
|
||||
fn undefined_assignment() {
|
||||
|
|
@ -17,3 +17,24 @@ fn undefined_return() {
|
|||
test("function f(){return void foo();}", "function f(){return void foo()}");
|
||||
test("function f(){if(a()){return undefined;}}", "function f(){if(a())return}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn console_removal() {
|
||||
let options = MinifierOptions {
|
||||
mangle: false,
|
||||
compress: CompressOptions { drop_console: true, ..CompressOptions::default() },
|
||||
..MinifierOptions::default()
|
||||
};
|
||||
test_with_options("console.log('hi')", "", options);
|
||||
test_with_options("let x = console.error('oops')", "let x", options);
|
||||
test_with_options(
|
||||
"function f() { return console.warn('problem') }",
|
||||
"function f(){return}",
|
||||
options,
|
||||
);
|
||||
|
||||
// console isn't removed when drop_console is `false`. This is also the
|
||||
// default value.
|
||||
let options = MinifierOptions { mangle: false, ..MinifierOptions::default() };
|
||||
test_with_options("console.log('hi')", "console.log('hi')", options);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue