From 1c3a9227f1535c91988351d97f97d745f4a5d552 Mon Sep 17 00:00:00 2001 From: Boshen Date: Sat, 3 Aug 2024 14:09:37 +0800 Subject: [PATCH] refactor(minifier): ast passes infrastructure --- Cargo.lock | 1 + crates/oxc_ast_macros/Cargo.toml | 4 +- crates/oxc_codegen/src/gen.rs | 5 +- crates/oxc_minifier/examples/dce.rs | 34 - .../oxc_minifier/src/ast_passes/collapse.rs | 83 + .../mod.rs => ast_passes/fold_constants.rs} | 392 +- crates/oxc_minifier/src/ast_passes/mod.rs | 10 +- .../src/ast_passes/remove_dead_code.rs | 131 +- .../src/ast_passes/remove_parens.rs | 36 - .../src/ast_passes/remove_syntax.rs | 78 + .../ast_passes/substitute_alternate_syntax.rs | 212 + .../src/{compressor => }/ast_util.rs | 0 crates/oxc_minifier/src/compressor.rs | 47 + crates/oxc_minifier/src/compressor/mod.rs | 372 -- crates/oxc_minifier/src/compressor/util.rs | 10 - crates/oxc_minifier/src/folder/util.rs | 45 - crates/oxc_minifier/src/keep_var.rs | 51 + crates/oxc_minifier/src/lib.rs | 12 +- .../src/{compressor => }/options.rs | 17 + crates/oxc_minifier/src/{folder => }/tri.rs | 4 +- crates/oxc_minifier/src/{folder => }/ty.rs | 1 + .../tests/closure/fold_conditions.rs | 2 + .../tests/closure/fold_constants.rs | 761 +-- crates/oxc_minifier/tests/closure/mod.rs | 3 +- crates/oxc_minifier/tests/closure/printer.rs | 4282 ----------------- crates/oxc_minifier/tests/mangler/mod.rs | 3 +- crates/oxc_minifier/tests/mod.rs | 71 +- crates/oxc_minifier/tests/oxc/code_removal.rs | 9 +- crates/oxc_minifier/tests/oxc/folding.rs | 9 +- .../tests/oxc/remove_dead_code.rs | 4 +- crates/oxc_wasm/src/lib.rs | 1 + tasks/benchmark/Cargo.toml | 10 +- tasks/benchmark/benches/minifier.rs | 8 +- tasks/coverage/minifier_babel.snap | 8 +- tasks/coverage/minifier_test262.snap | 20 +- tasks/coverage/src/minifier.rs | 18 +- 36 files changed, 1124 insertions(+), 5630 deletions(-) delete mode 100644 crates/oxc_minifier/examples/dce.rs create mode 100644 crates/oxc_minifier/src/ast_passes/collapse.rs rename crates/oxc_minifier/src/{folder/mod.rs => ast_passes/fold_constants.rs} (66%) delete mode 100644 crates/oxc_minifier/src/ast_passes/remove_parens.rs create mode 100644 crates/oxc_minifier/src/ast_passes/remove_syntax.rs create mode 100644 crates/oxc_minifier/src/ast_passes/substitute_alternate_syntax.rs rename crates/oxc_minifier/src/{compressor => }/ast_util.rs (100%) create mode 100644 crates/oxc_minifier/src/compressor.rs delete mode 100644 crates/oxc_minifier/src/compressor/mod.rs delete mode 100644 crates/oxc_minifier/src/compressor/util.rs delete mode 100644 crates/oxc_minifier/src/folder/util.rs create mode 100644 crates/oxc_minifier/src/keep_var.rs rename crates/oxc_minifier/src/{compressor => }/options.rs (76%) rename crates/oxc_minifier/src/{folder => }/tri.rs (89%) rename crates/oxc_minifier/src/{folder => }/ty.rs (98%) delete mode 100644 crates/oxc_minifier/tests/closure/printer.rs diff --git a/Cargo.lock b/Cargo.lock index 74b20a859b9337..b740805a020884 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1367,6 +1367,7 @@ version = "0.0.0" dependencies = [ "criterion2", "oxc_allocator", + "oxc_ast", "oxc_codegen", "oxc_isolated_declarations", "oxc_linter", diff --git a/crates/oxc_ast_macros/Cargo.toml b/crates/oxc_ast_macros/Cargo.toml index 5286f5812c24a3..597d7c71f8c504 100644 --- a/crates/oxc_ast_macros/Cargo.toml +++ b/crates/oxc_ast_macros/Cargo.toml @@ -21,6 +21,6 @@ proc-macro = true doctest = false [dependencies] -quote = { workspace = true } -syn = { workspace = true, features = ["full"] } +quote = { workspace = true } +syn = { workspace = true, features = ["full"] } proc-macro2 = { workspace = true } diff --git a/crates/oxc_codegen/src/gen.rs b/crates/oxc_codegen/src/gen.rs index 4edf53841139a6..3c0fdc113e4621 100644 --- a/crates/oxc_codegen/src/gen.rs +++ b/crates/oxc_codegen/src/gen.rs @@ -1192,6 +1192,9 @@ impl<'a, const MINIFY: bool> Gen for NumericLiteral<'a> { let bytes = result.as_str(); p.print_str(bytes); need_space_before_dot(bytes, p); + } else if self.value == f64::INFINITY { + p.print_str("Infinity"); + need_space_before_dot("Infinity", p); } else { p.print_str(self.raw); need_space_before_dot(self.raw, p); @@ -1661,7 +1664,7 @@ impl<'a, const MINIFY: bool> GenExpr for ArrowFunctionExpression<'a> { p.print_str("=>"); p.print_soft_space(); if self.expression { - if let Statement::ExpressionStatement(stmt) = &self.body.statements[0] { + if let Some(Statement::ExpressionStatement(stmt)) = &self.body.statements.first() { p.start_of_arrow_expr = p.code_len(); stmt.expression.gen_expr(p, Precedence::Comma, ctx.and_forbid_in(true)); } diff --git a/crates/oxc_minifier/examples/dce.rs b/crates/oxc_minifier/examples/dce.rs deleted file mode 100644 index 7b5424628f77db..00000000000000 --- a/crates/oxc_minifier/examples/dce.rs +++ /dev/null @@ -1,34 +0,0 @@ -#![allow(clippy::print_stdout)] -use std::path::Path; - -use oxc_allocator::Allocator; -use oxc_codegen::CodeGenerator; -use oxc_minifier::RemoveDeadCode; -use oxc_parser::Parser; -use oxc_span::SourceType; -use pico_args::Arguments; - -// Instruction: -// create a `test.js`, -// run `cargo run -p oxc_minifier --example minifier` or `just example minifier` - -fn main() -> std::io::Result<()> { - let mut args = Arguments::from_env(); - - let name = args.subcommand().ok().flatten().unwrap_or_else(|| String::from("test.js")); - - let allocator = Allocator::default(); - let path = Path::new(&name); - let source_text = std::fs::read_to_string(path)?; - let source_type = SourceType::from_path(path).unwrap(); - - let ret = Parser::new(&allocator, &source_text, source_type).parse(); - let program = allocator.alloc(ret.program); - - RemoveDeadCode::new(&allocator).build(program); - - let printed = CodeGenerator::new().build(program).source_text; - println!("{printed}"); - - Ok(()) -} diff --git a/crates/oxc_minifier/src/ast_passes/collapse.rs b/crates/oxc_minifier/src/ast_passes/collapse.rs new file mode 100644 index 00000000000000..48f5fdb6a43a1b --- /dev/null +++ b/crates/oxc_minifier/src/ast_passes/collapse.rs @@ -0,0 +1,83 @@ +use oxc_allocator::Vec; +use oxc_ast::{ast::*, visit::walk_mut, AstBuilder, VisitMut}; + +use crate::CompressOptions; + +/// Collapse variable declarations (TODO: and assignments). +/// +/// `var a; var b = 1; var c = 2` => `var a, b = 1; c = 2` +/// TODO: `a = null; b = null;` => `a = b = null` +pub struct Collapse<'a> { + ast: AstBuilder<'a>, + options: CompressOptions, +} + +impl<'a> VisitMut<'a> for Collapse<'a> { + fn visit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>) { + if self.options.join_vars { + self.join_vars(stmts); + } + + walk_mut::walk_statements(self, stmts); + } +} + +impl<'a> Collapse<'a> { + pub fn new(ast: AstBuilder<'a>, options: CompressOptions) -> Self { + Self { ast, options } + } + + pub fn build(&mut self, program: &mut Program<'a>) { + self.visit_program(program); + } + + /// Join consecutive var statements + fn join_vars(&mut self, stmts: &mut Vec<'a, Statement<'a>>) { + // Collect all the consecutive ranges that contain joinable vars. + // This is required because Rust prevents in-place vec mutation. + let mut ranges = vec![]; + let mut range = 0..0; + let mut i = 1usize; + let mut capacity = 0usize; + for window in stmts.windows(2) { + let [prev, cur] = window else { unreachable!() }; + if let ( + Statement::VariableDeclaration(cur_decl), + Statement::VariableDeclaration(prev_decl), + ) = (cur, prev) + { + if cur_decl.kind == prev_decl.kind { + if i - 1 != range.end { + range.start = i - 1; + } + range.end = i + 1; + } + } + if (range.end != i || i == stmts.len() - 1) && range.start < range.end { + capacity += range.end - range.start - 1; + ranges.push(range.clone()); + range = 0..0; + } + i += 1; + } + + if ranges.is_empty() { + return; + } + + // Reconstruct the stmts array by joining consecutive ranges + let mut new_stmts = self.ast.vec_with_capacity(stmts.len() - capacity); + for (i, stmt) in stmts.drain(..).enumerate() { + if i > 0 && ranges.iter().any(|range| range.contains(&(i - 1)) && range.contains(&i)) { + if let Statement::VariableDeclaration(prev_decl) = new_stmts.last_mut().unwrap() { + if let Statement::VariableDeclaration(mut cur_decl) = stmt { + prev_decl.declarations.append(&mut cur_decl.declarations); + } + } + } else { + new_stmts.push(stmt); + } + } + *stmts = new_stmts; + } +} diff --git a/crates/oxc_minifier/src/folder/mod.rs b/crates/oxc_minifier/src/ast_passes/fold_constants.rs similarity index 66% rename from crates/oxc_minifier/src/folder/mod.rs rename to crates/oxc_minifier/src/ast_passes/fold_constants.rs index 50f9b5b71a8ff6..4c7f5e66198852 100644 --- a/crates/oxc_minifier/src/folder/mod.rs +++ b/crates/oxc_minifier/src/ast_passes/fold_constants.rs @@ -2,35 +2,47 @@ //! //! -mod tri; -mod ty; -mod util; - use std::{cmp::Ordering, mem}; -use oxc_ast::{ast::*, AstBuilder}; -use oxc_span::{GetSpan, Span}; +use num_bigint::BigInt; + +use oxc_ast::{ast::*, visit::walk_mut, AstBuilder, Visit, VisitMut}; +use oxc_span::{GetSpan, Span, SPAN}; use oxc_syntax::{ number::NumberBase, operator::{BinaryOperator, LogicalOperator, UnaryOperator}, }; -use crate::compressor::ast_util::{ - get_boolean_value, get_number_value, get_side_free_bigint_value, get_side_free_number_value, - get_side_free_string_value, get_string_value, is_exact_int64, IsLiteralValue, - MayHaveSideEffects, NumberValue, +use crate::{ + ast_util::{ + get_boolean_value, get_number_value, get_side_free_bigint_value, + get_side_free_number_value, get_side_free_string_value, get_string_value, is_exact_int64, + MayHaveSideEffects, NumberValue, + }, + keep_var::KeepVar, + tri::Tri, + ty::Ty, }; -use tri::Tri; -use ty::Ty; -use util::bigint_less_than_number; - -pub struct Folder<'a> { +pub struct FoldConstants<'a> { ast: AstBuilder<'a>, evaluate: bool, } -impl<'a> Folder<'a> { +impl<'a> VisitMut<'a> for FoldConstants<'a> { + fn visit_statement(&mut self, stmt: &mut Statement<'a>) { + walk_mut::walk_statement(self, stmt); + self.fold_condition(stmt); + } + + fn visit_expression(&mut self, expr: &mut Expression<'a>) { + walk_mut::walk_expression(self, expr); + self.fold_expression(expr); + self.fold_conditional_expression(expr); + } +} + +impl<'a> FoldConstants<'a> { pub fn new(ast: AstBuilder<'a>) -> Self { Self { ast, evaluate: false } } @@ -40,6 +52,61 @@ impl<'a> Folder<'a> { self } + pub fn build(&mut self, program: &mut Program<'a>) { + self.visit_program(program); + } + + fn fold_expression_and_get_boolean_value(&mut self, expr: &mut Expression<'a>) -> Option { + self.fold_expression(expr); + get_boolean_value(expr) + } + + fn fold_if_statement(&mut self, stmt: &mut Statement<'a>) { + let Statement::IfStatement(if_stmt) = stmt else { return }; + + // Descend and remove `else` blocks first. + if let Some(alternate) = &mut if_stmt.alternate { + self.fold_if_statement(alternate); + if matches!(alternate, Statement::EmptyStatement(_)) { + if_stmt.alternate = None; + } + } + + match self.fold_expression_and_get_boolean_value(&mut if_stmt.test) { + Some(true) => { + *stmt = self.ast.move_statement(&mut if_stmt.consequent); + } + Some(false) => { + *stmt = if let Some(alternate) = &mut if_stmt.alternate { + self.ast.move_statement(alternate) + } else { + // Keep hoisted `vars` from the consequent block. + let mut keep_var = KeepVar::new(self.ast); + keep_var.visit_statement(&if_stmt.consequent); + keep_var + .get_variable_declaration_statement() + .unwrap_or_else(|| self.ast.statement_empty(SPAN)) + }; + } + None => {} + } + } + + fn fold_conditional_expression(&mut self, expr: &mut Expression<'a>) { + let Expression::ConditionalExpression(conditional_expr) = expr else { + return; + }; + match self.fold_expression_and_get_boolean_value(&mut conditional_expr.test) { + Some(true) => { + *expr = self.ast.move_expression(&mut conditional_expr.consequent); + } + Some(false) => { + *expr = self.ast.move_expression(&mut conditional_expr.alternate); + } + _ => {} + } + } + pub fn fold_expression<'b>(&mut self, expr: &'b mut Expression<'a>) { let folded_expr = match expr { Expression::BinaryExpression(binary_expr) => match binary_expr.operator { @@ -75,21 +142,6 @@ impl<'a> Folder<'a> { } _ => None, }, - Expression::UnaryExpression(unary_expr) => match unary_expr.operator { - UnaryOperator::Typeof => { - self.try_fold_typeof(unary_expr.span, &unary_expr.argument) - } - UnaryOperator::UnaryPlus - | UnaryOperator::UnaryNegation - | UnaryOperator::LogicalNot - | UnaryOperator::BitwiseNot - if !unary_expr.may_have_side_effects() => - { - self.try_fold_unary_operator(unary_expr) - } - UnaryOperator::Void => self.try_reduce_void(unary_expr), - _ => None, - }, Expression::LogicalExpression(logic_expr) => { self.try_fold_logical_expression(logic_expr) } @@ -171,19 +223,21 @@ impl<'a> Folder<'a> { match op { BinaryOperator::Equality => self.try_abstract_equality_comparison(left, right), BinaryOperator::Inequality => self.try_abstract_equality_comparison(left, right).not(), - BinaryOperator::StrictEquality => self.try_strict_equality_comparison(left, right), + BinaryOperator::StrictEquality => Self::try_strict_equality_comparison(left, right), BinaryOperator::StrictInequality => { - self.try_strict_equality_comparison(left, right).not() + Self::try_strict_equality_comparison(left, right).not() + } + BinaryOperator::LessThan => { + Self::try_abstract_relational_comparison(left, right, false) } - BinaryOperator::LessThan => self.try_abstract_relational_comparison(left, right, false), BinaryOperator::GreaterThan => { - self.try_abstract_relational_comparison(right, left, false) + Self::try_abstract_relational_comparison(right, left, false) } BinaryOperator::LessEqualThan => { - self.try_abstract_relational_comparison(right, left, true).not() + Self::try_abstract_relational_comparison(right, left, true).not() } BinaryOperator::GreaterEqualThan => { - self.try_abstract_relational_comparison(left, right, true).not() + Self::try_abstract_relational_comparison(left, right, true).not() } _ => Tri::Unknown, } @@ -199,7 +253,7 @@ impl<'a> Folder<'a> { let right = Ty::from(right_expr); if left != Ty::Undetermined && right != Ty::Undetermined { if left == right { - return self.try_strict_equality_comparison(left_expr, right_expr); + return Self::try_strict_equality_comparison(left_expr, right_expr); } if matches!((left, right), (Ty::Null, Ty::Void) | (Ty::Void, Ty::Null)) { return Tri::True; @@ -263,7 +317,6 @@ impl<'a> Folder<'a> { /// fn try_abstract_relational_comparison<'b>( - &self, left_expr: &'b Expression<'a>, right_expr: &'b Expression<'a>, will_negative: bool, @@ -322,10 +375,10 @@ impl<'a> Folder<'a> { }, // Finally, try comparisons between BigInt and Number. (Some(l_big), _, _, Some(r_num)) => { - return bigint_less_than_number(&l_big, &r_num, Tri::False, will_negative); + return Self::bigint_less_than_number(&l_big, &r_num, Tri::False, will_negative); } (_, Some(r_big), Some(l_num), _) => { - return bigint_less_than_number(&r_big, &l_num, Tri::True, will_negative); + return Self::bigint_less_than_number(&r_big, &l_num, Tri::True, will_negative); } _ => {} } @@ -333,9 +386,46 @@ impl<'a> Folder<'a> { Tri::Unknown } + /// ported from [closure compiler](https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/PeepholeFoldConstants.java#L1250) + #[allow(clippy::cast_possible_truncation)] + pub fn bigint_less_than_number( + bigint_value: &BigInt, + number_value: &NumberValue, + invert: Tri, + will_negative: bool, + ) -> Tri { + // if invert is false, then the number is on the right in tryAbstractRelationalComparison + // if it's true, then the number is on the left + match number_value { + NumberValue::NaN => Tri::for_boolean(will_negative), + NumberValue::PositiveInfinity => Tri::True.xor(invert), + NumberValue::NegativeInfinity => Tri::False.xor(invert), + NumberValue::Number(num) => { + if let Some(Ordering::Equal | Ordering::Greater) = + num.abs().partial_cmp(&2_f64.powi(53)) + { + Tri::Unknown + } else { + let number_as_bigint = BigInt::from(*num as i64); + + match bigint_value.cmp(&number_as_bigint) { + Ordering::Less => Tri::True.xor(invert), + Ordering::Greater => Tri::False.xor(invert), + Ordering::Equal => { + if is_exact_int64(*num) { + Tri::False + } else { + Tri::for_boolean(num.is_sign_positive()).xor(invert) + } + } + } + } + } + } + } + /// fn try_strict_equality_comparison<'b>( - &self, left_expr: &'b Expression<'a>, right_expr: &'b Expression<'a>, ) -> Tri { @@ -406,217 +496,6 @@ impl<'a> Folder<'a> { Tri::Unknown } - /// Folds 'typeof(foo)' if foo is a literal, e.g. - /// typeof("bar") --> "string" - /// typeof(6) --> "number" - fn try_fold_typeof<'b>( - &mut self, - span: Span, - argument: &'b Expression<'a>, - ) -> Option> { - if argument.is_literal_value(true) { - let type_name = match argument { - Expression::FunctionExpression(_) | Expression::ArrowFunctionExpression(_) => { - Some("function") - } - Expression::StringLiteral(_) | Expression::TemplateLiteral(_) => Some("string"), - Expression::NumericLiteral(_) => Some("number"), - Expression::BooleanLiteral(_) => Some("boolean"), - Expression::NullLiteral(_) - | Expression::ObjectExpression(_) - | Expression::ArrayExpression(_) => Some("object"), - Expression::Identifier(_) if argument.is_undefined() => Some("undefined"), - Expression::UnaryExpression(unary_expr) => { - match unary_expr.operator { - UnaryOperator::Void => Some("undefined"), - // `unary_expr.argument` is literal value, so it's safe to fold - UnaryOperator::LogicalNot => Some("boolean"), - _ => None, - } - } - _ => None, - }; - - if let Some(type_name) = type_name { - return Some(self.ast.expression_string_literal(span, type_name)); - } - } - - None - } - - fn try_fold_unary_operator( - &mut self, - unary_expr: &UnaryExpression<'a>, - ) -> Option> { - if let Some(boolean) = get_boolean_value(&unary_expr.argument) { - match unary_expr.operator { - // !100 -> false - // !100n -> false - // after this, it will be compressed to !1 or !0 in `compress_boolean` - UnaryOperator::LogicalNot => match &unary_expr.argument { - Expression::NumericLiteral(number_literal) => { - let value = number_literal.value; - // Don't fold !0 and !1 back to false. - if value == 0_f64 || (value - 1_f64).abs() < f64::EPSILON { - return None; - } - return Some( - self.ast.expression_boolean_literal(unary_expr.span, !boolean), - ); - } - Expression::BigIntLiteral(_) => { - return Some( - self.ast.expression_boolean_literal(unary_expr.span, !boolean), - ); - } - _ => {} - }, - // +1 -> 1 - // NaN -> NaN - // +Infinity -> Infinity - UnaryOperator::UnaryPlus => match &unary_expr.argument { - Expression::NumericLiteral(number_literal) => { - return Some(self.ast.expression_numeric_literal( - unary_expr.span, - number_literal.value, - number_literal.raw, - number_literal.base, - )); - } - Expression::Identifier(ident) => { - if matches!(ident.name.as_str(), "NaN" | "Infinity") { - return self.try_detach_unary_op(unary_expr); - } - } - _ => { - // +true -> 1 - // +false -> 0 - // +null -> 0 - if let Some(NumberValue::Number(value)) = - get_number_value(&unary_expr.argument) - { - return Some(self.ast.expression_numeric_literal( - unary_expr.span, - value, - value.to_string(), - if value.fract() == 0.0 { - NumberBase::Decimal - } else { - NumberBase::Float - }, - )); - } - } - }, - // -4 -> -4, fold UnaryExpression -4 to NumericLiteral -4 - // -NaN -> NaN - UnaryOperator::UnaryNegation => match &unary_expr.argument { - Expression::NumericLiteral(number_literal) => { - let value = -number_literal.value; - return Some(self.ast.expression_numeric_literal( - unary_expr.span, - value, - value.to_string(), - number_literal.base, - )); - } - Expression::BigIntLiteral(_big_int_literal) => { - // let value = big_int_literal.value.clone().neg(); - // let literal = - // self.ast.bigint_literal(unary_expr.span, value, big_int_literal.base); - // return Some(self.ast.literal_bigint_expression(literal)); - return None; - } - Expression::Identifier(ident) => { - if ident.name == "NaN" { - return self.try_detach_unary_op(unary_expr); - } - } - _ => {} - }, - // ~10 -> -11 - // ~NaN -> -1 - UnaryOperator::BitwiseNot => match &unary_expr.argument { - Expression::NumericLiteral(number_literal) => { - if number_literal.value.fract() == 0.0 { - let int_value = - NumericLiteral::ecmascript_to_int32(number_literal.value); - return Some(self.ast.expression_numeric_literal( - unary_expr.span, - f64::from(!int_value), - number_literal.raw, - NumberBase::Decimal, // since it be converted to i32, it should always be decimal. - )); - } - } - Expression::BigIntLiteral(_big_int_literal) => { - // let value = big_int_literal.value.clone().not(); - // let leteral = - // self.ast.bigint_literal(unary_expr.span, value, big_int_literal.base); - // return Some(self.ast.literal_bigint_expression(leteral)); - return None; - } - Expression::Identifier(ident) => { - if ident.name == "NaN" { - let value = -1_f64; - return Some(self.ast.expression_numeric_literal( - unary_expr.span, - value, - "-1", - NumberBase::Decimal, - )); - } - } - _ => {} - }, - _ => {} - } - } - - None - } - - // +NaN -> NaN - // !Infinity -> Infinity - fn try_detach_unary_op(&mut self, unary_expr: &UnaryExpression<'a>) -> Option> { - if let Expression::Identifier(ident) = &unary_expr.argument { - if matches!(ident.name.as_str(), "NaN" | "Infinity") { - let ident = IdentifierReference { - span: unary_expr.span, - name: ident.name.clone(), - reference_id: ident.reference_id.clone(), - reference_flag: ident.reference_flag, - }; - return Some(self.ast.expression_from_identifier_reference(ident)); - } - } - - None - } - - /// port from [closure-compiler](https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/PeepholeFoldConstants.java#L195) - /// void 0 -> void 0 - /// void 1 -> void 0 - /// void x -> void 0 - fn try_reduce_void(&mut self, unary_expr: &UnaryExpression<'a>) -> Option> { - let can_replace = match &unary_expr.argument { - Expression::NumericLiteral(number_literal) => number_literal.value != 0_f64, - _ => !unary_expr.may_have_side_effects(), - }; - - if can_replace { - let argument = self.ast.expression_numeric_literal( - unary_expr.argument.span(), - 0_f64, - "0", - NumberBase::Decimal, - ); - return Some(self.ast.expression_unary(unary_expr.span, UnaryOperator::Void, argument)); - } - None - } - /// ported from [closure-compiler](https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/PeepholeFoldConstants.java#L1114-L1162) #[allow(clippy::cast_possible_truncation)] fn try_fold_shift<'b>( @@ -750,6 +629,9 @@ impl<'a> Folder<'a> { } } } + Statement::IfStatement(_) => { + self.fold_if_statement(stmt); + } _ => {} }; } @@ -761,7 +643,7 @@ impl<'a> Folder<'a> { let folded_expr = match expr { Expression::UnaryExpression(unary_expr) => match unary_expr.operator { UnaryOperator::LogicalNot => { - let should_fold = self.try_minimize_not(&mut unary_expr.argument); + let should_fold = Self::try_minimize_not(&mut unary_expr.argument); if should_fold { Some(self.move_out_expression(&mut unary_expr.argument)) @@ -783,7 +665,7 @@ impl<'a> Folder<'a> { } /// ported from [closure compiler](https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/PeepholeMinimizeConditions.java#L401-L435) - fn try_minimize_not(&mut self, expr: &mut Expression<'a>) -> bool { + fn try_minimize_not(expr: &mut Expression<'a>) -> bool { let span = &mut expr.span(); match expr { diff --git a/crates/oxc_minifier/src/ast_passes/mod.rs b/crates/oxc_minifier/src/ast_passes/mod.rs index 72767fdd3fd2f4..f094a3a6a2710b 100644 --- a/crates/oxc_minifier/src/ast_passes/mod.rs +++ b/crates/oxc_minifier/src/ast_passes/mod.rs @@ -1,9 +1,15 @@ #![allow(clippy::wildcard_imports)] +mod collapse; +mod fold_constants; mod remove_dead_code; -mod remove_parens; +mod remove_syntax; mod replace_global_defines; +mod substitute_alternate_syntax; +pub use collapse::Collapse; +pub use fold_constants::FoldConstants; pub use remove_dead_code::RemoveDeadCode; -pub use remove_parens::RemoveParens; +pub use remove_syntax::RemoveSyntax; pub use replace_global_defines::{ReplaceGlobalDefines, ReplaceGlobalDefinesConfig}; +pub use substitute_alternate_syntax::SubstituteAlternateSyntax; diff --git a/crates/oxc_minifier/src/ast_passes/remove_dead_code.rs b/crates/oxc_minifier/src/ast_passes/remove_dead_code.rs index 8d0e1be6fbbee4..2f544f48df368b 100644 --- a/crates/oxc_minifier/src/ast_passes/remove_dead_code.rs +++ b/crates/oxc_minifier/src/ast_passes/remove_dead_code.rs @@ -1,11 +1,7 @@ -use oxc_allocator::{Allocator, Vec}; -use oxc_ast::{ - ast::*, syntax_directed_operations::BoundNames, visit::walk_mut, AstBuilder, Visit, VisitMut, -}; -use oxc_span::{Atom, Span, SPAN}; -use oxc_syntax::scope::ScopeFlags; +use oxc_allocator::Vec; +use oxc_ast::{ast::*, visit::walk_mut, AstBuilder, Visit, VisitMut}; -use crate::{compressor::ast_util::get_boolean_value, folder::Folder}; +use crate::keep_var::KeepVar; /// Remove Dead Code from the AST. /// @@ -14,26 +10,23 @@ use crate::{compressor::ast_util::get_boolean_value, folder::Folder}; /// See `KeepVar` at the end of this file for `var` hoisting logic. pub struct RemoveDeadCode<'a> { ast: AstBuilder<'a>, - folder: Folder<'a>, } impl<'a> VisitMut<'a> for RemoveDeadCode<'a> { fn visit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>) { + stmts.retain(|stmt| !matches!(stmt, Statement::EmptyStatement(_))); self.dead_code_elimintation(stmts); walk_mut::walk_statements(self, stmts); } fn visit_expression(&mut self, expr: &mut Expression<'a>) { - self.fold_conditional_expression(expr); - self.fold_logical_expression(expr); walk_mut::walk_expression(self, expr); } } impl<'a> RemoveDeadCode<'a> { - pub fn new(allocator: &'a Allocator) -> Self { - let ast = AstBuilder::new(allocator); - Self { ast, folder: Folder::new(ast) } + pub fn new(ast: AstBuilder<'a>) -> Self { + Self { ast } } pub fn build(&mut self, program: &mut Program<'a>) { @@ -42,11 +35,6 @@ impl<'a> RemoveDeadCode<'a> { /// Removes dead code thats comes after `return` statements after inlining `if` statements fn dead_code_elimintation(&mut self, stmts: &mut Vec<'a, Statement<'a>>) { - // Fold if statements - for stmt in stmts.iter_mut() { - self.fold_if_statement(stmt); - } - // Remove code after `return` and `throw` statements let mut index = None; 'outer: for (i, stmt) in stmts.iter().enumerate() { @@ -79,111 +67,4 @@ impl<'a> RemoveDeadCode<'a> { stmts.push(stmt); } } - - fn fold_if_statement(&mut self, stmt: &mut Statement<'a>) { - let Statement::IfStatement(if_stmt) = stmt else { return }; - - // Descend and remove `else` blocks first. - if let Some(alternate) = &mut if_stmt.alternate { - self.fold_if_statement(alternate); - if matches!(alternate, Statement::EmptyStatement(_)) { - if_stmt.alternate = None; - } - } - - match self.fold_expression_and_get_boolean_value(&mut if_stmt.test) { - Some(true) => { - *stmt = self.ast.move_statement(&mut if_stmt.consequent); - } - Some(false) => { - *stmt = if let Some(alternate) = &mut if_stmt.alternate { - self.ast.move_statement(alternate) - } else { - // Keep hoisted `vars` from the consequent block. - let mut keep_var = KeepVar::new(self.ast); - keep_var.visit_statement(&if_stmt.consequent); - keep_var - .get_variable_declaration_statement() - .unwrap_or_else(|| self.ast.statement_empty(SPAN)) - }; - } - None => {} - } - } - - fn fold_expression_and_get_boolean_value(&mut self, expr: &mut Expression<'a>) -> Option { - self.folder.fold_expression(expr); - get_boolean_value(expr) - } - - fn fold_conditional_expression(&mut self, expr: &mut Expression<'a>) { - let Expression::ConditionalExpression(conditional_expr) = expr else { - return; - }; - match self.fold_expression_and_get_boolean_value(&mut conditional_expr.test) { - Some(true) => { - *expr = self.ast.move_expression(&mut conditional_expr.consequent); - } - Some(false) => { - *expr = self.ast.move_expression(&mut conditional_expr.alternate); - } - _ => {} - } - } - - fn fold_logical_expression(&mut self, expr: &mut Expression<'a>) { - let Expression::LogicalExpression(logical_expr) = expr else { - return; - }; - if let Some(e) = self.folder.try_fold_logical_expression(logical_expr) { - *expr = e; - } - } -} - -struct KeepVar<'a> { - ast: AstBuilder<'a>, - vars: std::vec::Vec<(Atom<'a>, Span)>, -} - -impl<'a> Visit<'a> for KeepVar<'a> { - fn visit_variable_declarator(&mut self, decl: &VariableDeclarator<'a>) { - if decl.kind.is_var() { - decl.id.bound_names(&mut |ident| { - self.vars.push((ident.name.clone(), ident.span)); - }); - } - } - - fn visit_function(&mut self, _it: &Function<'a>, _flags: ScopeFlags) { - /* skip functions */ - } - - fn visit_class(&mut self, _it: &Class<'a>) { - /* skip classes */ - } -} - -impl<'a> KeepVar<'a> { - fn new(ast: AstBuilder<'a>) -> Self { - Self { ast, vars: std::vec![] } - } - - fn get_variable_declaration_statement(self) -> Option> { - if self.vars.is_empty() { - return None; - } - - let kind = VariableDeclarationKind::Var; - let decls = self.ast.vec_from_iter(self.vars.into_iter().map(|(name, span)| { - let binding_kind = self.ast.binding_pattern_kind_binding_identifier(span, name); - let id = - self.ast.binding_pattern::>(binding_kind, None, false); - self.ast.variable_declarator(span, kind, id, None, false) - })); - - let decl = self.ast.variable_declaration(SPAN, kind, decls, false); - let stmt = self.ast.statement_declaration(self.ast.declaration_from_variable(decl)); - Some(stmt) - } } diff --git a/crates/oxc_minifier/src/ast_passes/remove_parens.rs b/crates/oxc_minifier/src/ast_passes/remove_parens.rs deleted file mode 100644 index 9b856718dbe957..00000000000000 --- a/crates/oxc_minifier/src/ast_passes/remove_parens.rs +++ /dev/null @@ -1,36 +0,0 @@ -use oxc_allocator::{Allocator, Vec}; -use oxc_ast::{ast::*, visit::walk_mut, AstBuilder, VisitMut}; - -/// Remove Parenthesized Expression from the AST. -pub struct RemoveParens<'a> { - ast: AstBuilder<'a>, -} - -impl<'a> RemoveParens<'a> { - pub fn new(allocator: &'a Allocator) -> Self { - Self { ast: AstBuilder::new(allocator) } - } - - pub fn build(&mut self, program: &mut Program<'a>) { - self.visit_program(program); - } - - fn strip_parenthesized_expression(&self, expr: &mut Expression<'a>) { - if let Expression::ParenthesizedExpression(paren_expr) = expr { - *expr = self.ast.move_expression(&mut paren_expr.expression); - self.strip_parenthesized_expression(expr); - } - } -} - -impl<'a> VisitMut<'a> for RemoveParens<'a> { - fn visit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>) { - stmts.retain(|stmt| !matches!(stmt, Statement::EmptyStatement(_))); - walk_mut::walk_statements(self, stmts); - } - - fn visit_expression(&mut self, expr: &mut Expression<'a>) { - self.strip_parenthesized_expression(expr); - walk_mut::walk_expression(self, expr); - } -} diff --git a/crates/oxc_minifier/src/ast_passes/remove_syntax.rs b/crates/oxc_minifier/src/ast_passes/remove_syntax.rs new file mode 100644 index 00000000000000..ed2cf03c727687 --- /dev/null +++ b/crates/oxc_minifier/src/ast_passes/remove_syntax.rs @@ -0,0 +1,78 @@ +use oxc_allocator::Vec; +use oxc_ast::{ast::*, visit::walk_mut, AstBuilder, VisitMut}; + +use crate::CompressOptions; + +/// Remove syntax from the AST. +/// +/// * Parenthesized Expression +/// * `debugger` +/// * `console.log` +pub struct RemoveSyntax<'a> { + ast: AstBuilder<'a>, + options: CompressOptions, +} + +impl<'a> VisitMut<'a> for RemoveSyntax<'a> { + fn visit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>) { + stmts.retain(|stmt| { + !(matches!(stmt, Statement::EmptyStatement(_)) + || self.drop_debugger(stmt) + || self.drop_console(stmt)) + }); + walk_mut::walk_statements(self, stmts); + } + + fn visit_expression(&mut self, expr: &mut Expression<'a>) { + self.strip_parenthesized_expression(expr); + self.compress_console(expr); + walk_mut::walk_expression(self, expr); + } +} + +impl<'a> RemoveSyntax<'a> { + pub fn new(ast: AstBuilder<'a>, options: CompressOptions) -> Self { + Self { ast, options } + } + + pub fn build(&mut self, program: &mut Program<'a>) { + self.visit_program(program); + } + + fn strip_parenthesized_expression(&self, expr: &mut Expression<'a>) { + if let Expression::ParenthesizedExpression(paren_expr) = expr { + *expr = self.ast.move_expression(&mut paren_expr.expression); + self.strip_parenthesized_expression(expr); + } + } + + /// Drop `drop_debugger` statement. + /// + /// Enabled by `compress.drop_debugger` + fn drop_debugger(&mut self, stmt: &Statement<'a>) -> bool { + matches!(stmt, Statement::DebuggerStatement(_)) && self.options.drop_debugger + } + + /// Drop `console.*` expressions. + /// + /// Enabled by `compress.drop_console + fn drop_console(&mut self, stmt: &Statement<'a>) -> bool { + self.options.drop_console + && matches!(stmt, Statement::ExpressionStatement(expr) if Self::is_console(&expr.expression)) + } + + fn compress_console(&mut self, expr: &mut Expression<'a>) { + if self.options.drop_console && Self::is_console(expr) { + *expr = self.ast.void_0(); + } + } + + fn is_console(expr: &Expression<'_>) -> bool { + // let Statement::ExpressionStatement(expr) = stmt else { return false }; + let Expression::CallExpression(call_expr) = &expr else { return false }; + let Some(member_expr) = call_expr.callee.as_member_expression() else { return false }; + let obj = member_expr.object(); + let Some(ident) = obj.get_identifier_reference() else { return false }; + ident.name == "console" + } +} diff --git a/crates/oxc_minifier/src/ast_passes/substitute_alternate_syntax.rs b/crates/oxc_minifier/src/ast_passes/substitute_alternate_syntax.rs new file mode 100644 index 00000000000000..9d1cb997429a95 --- /dev/null +++ b/crates/oxc_minifier/src/ast_passes/substitute_alternate_syntax.rs @@ -0,0 +1,212 @@ +use oxc_ast::{ast::*, visit::walk_mut, AstBuilder, VisitMut}; +use oxc_span::SPAN; +use oxc_syntax::{ + number::NumberBase, + operator::{BinaryOperator, UnaryOperator}, +}; + +use crate::CompressOptions; + +/// A peephole optimization that minimizes code by simplifying conditional +/// expressions, replacing IFs with HOOKs, replacing object constructors +/// with literals, and simplifying returns. +pub struct SubstituteAlternateSyntax<'a> { + ast: AstBuilder<'a>, + options: CompressOptions, +} + +impl<'a> VisitMut<'a> for SubstituteAlternateSyntax<'a> { + fn visit_statement(&mut self, stmt: &mut Statement<'a>) { + self.compress_block(stmt); + // self.compress_while(stmt); + walk_mut::walk_statement(self, stmt); + } + + fn visit_return_statement(&mut self, stmt: &mut ReturnStatement<'a>) { + walk_mut::walk_return_statement(self, stmt); + // We may fold `void 1` to `void 0`, so compress it after visiting + Self::compress_return_statement(stmt); + } + + fn visit_variable_declaration(&mut self, decl: &mut VariableDeclaration<'a>) { + for declarator in decl.declarations.iter_mut() { + self.visit_variable_declarator(declarator); + Self::compress_variable_declarator(declarator); + } + } + + fn visit_expression(&mut self, expr: &mut Expression<'a>) { + // Bail cjs `Object.defineProperty(exports, ...)` + if Self::is_object_define_property_exports(expr) { + return; + } + walk_mut::walk_expression(self, expr); + if !self.compress_undefined(expr) { + self.compress_boolean(expr); + } + } + + fn visit_binary_expression(&mut self, expr: &mut BinaryExpression<'a>) { + walk_mut::walk_binary_expression(self, expr); + self.compress_typeof_undefined(expr); + } +} + +impl<'a> SubstituteAlternateSyntax<'a> { + pub fn new(ast: AstBuilder<'a>, options: CompressOptions) -> Self { + Self { ast, options } + } + + pub fn build(&mut self, program: &mut Program<'a>) { + self.visit_program(program); + } + + /* Utilities */ + + /// Transforms `undefined` => `void 0` + fn compress_undefined(&self, expr: &mut Expression<'a>) -> bool { + let Expression::Identifier(ident) = expr else { return false }; + if ident.name == "undefined" { + // if let Some(reference_id) = ident.reference_id.get() { + // && self.semantic.symbols().is_global_reference(reference_id) + *expr = self.ast.void_0(); + return true; + // } + } + false + } + + /// Test `Object.defineProperty(exports, ...)` + fn is_object_define_property_exports(expr: &Expression<'a>) -> bool { + let Expression::CallExpression(call_expr) = expr else { return false }; + let Some(Argument::Identifier(ident)) = call_expr.arguments.first() else { return false }; + if ident.name != "exports" { + return false; + } + call_expr.callee.is_specific_member_access("Object", "defineProperty") + } + + /* Statements */ + + /// Remove block from single line blocks + /// `{ block } -> block` + #[allow(clippy::only_used_in_recursion)] // `&self` is only used in recursion + fn compress_block(&self, stmt: &mut Statement<'a>) { + if let Statement::BlockStatement(block) = stmt { + // Avoid compressing `if (x) { var x = 1 }` to `if (x) var x = 1` due to different + // semantics according to AnnexB, which lead to different semantics. + if block.body.len() == 1 && !block.body[0].is_declaration() { + *stmt = block.body.remove(0); + self.compress_block(stmt); + } + } + } + + // /// Transforms `while(expr)` to `for(;expr;)` + // fn compress_while(&mut self, stmt: &mut Statement<'a>) { + // let Statement::WhileStatement(while_stmt) = stmt else { return }; + // if self.options.loops { + // let dummy_test = self.ast.expression_this(SPAN); + // let test = std::mem::replace(&mut while_stmt.test, dummy_test); + // let body = self.ast.move_statement(&mut while_stmt.body); + // *stmt = self.ast.statement_for(SPAN, None, Some(test), None, body); + // } + // } + + /* Expressions */ + + /// Transforms boolean expression `true` => `!0` `false` => `!1` + /// Enabled by `compress.booleans` + fn compress_boolean(&mut self, expr: &mut Expression<'a>) -> bool { + let Expression::BooleanLiteral(lit) = expr else { return false }; + if self.options.booleans { + let num = self.ast.expression_numeric_literal( + SPAN, + if lit.value { 0.0 } else { 1.0 }, + if lit.value { "0" } else { "1" }, + NumberBase::Decimal, + ); + *expr = self.ast.expression_unary(SPAN, UnaryOperator::LogicalNot, num); + return true; + } + false + } + + /// Compress `typeof foo == "undefined"` into `typeof foo > "u"` + /// Enabled by `compress.typeofs` + fn compress_typeof_undefined(&self, expr: &mut BinaryExpression<'a>) { + if !self.options.typeofs { + return; + } + if !matches!(expr.operator, BinaryOperator::Equality | BinaryOperator::StrictEquality) { + return; + } + let pair = Self::commutative_pair( + (&expr.left, &expr.right), + |a| a.is_specific_string_literal("undefined").then_some(()), + |b| { + if let Expression::UnaryExpression(op) = b { + if op.operator == UnaryOperator::Typeof { + if let Expression::Identifier(id) = &op.argument { + return Some((*id).clone()); + } + } + } + None + }, + ); + let Some((_void_exp, id_ref)) = pair else { + return; + }; + let argument = self.ast.expression_from_identifier_reference(id_ref); + let left = self.ast.unary_expression(SPAN, UnaryOperator::Typeof, argument); + let right = self.ast.string_literal(SPAN, "u"); + let binary_expr = self.ast.binary_expression( + expr.span, + self.ast.expression_from_unary(left), + BinaryOperator::GreaterThan, + self.ast.expression_from_string_literal(right), + ); + *expr = binary_expr; + } + + fn commutative_pair( + pair: (&A, &A), + check_a: F, + check_b: G, + ) -> Option<(RetF, RetG)> + where + F: Fn(&A) -> Option, + G: Fn(&A) -> Option, + { + if let Some(a) = check_a(pair.0) { + if let Some(b) = check_b(pair.1) { + return Some((a, b)); + } + } else if let Some(a) = check_a(pair.1) { + if let Some(b) = check_b(pair.0) { + return Some((a, b)); + } + } + None + } + + /// Removes redundant argument of `ReturnStatement` + /// + /// `return undefined` -> `return` + /// `return void 0` -> `return` + fn compress_return_statement(stmt: &mut ReturnStatement<'a>) { + if stmt.argument.as_ref().is_some_and(|expr| expr.is_undefined() || expr.is_void_0()) { + stmt.argument = None; + } + } + + fn compress_variable_declarator(decl: &mut VariableDeclarator<'a>) { + if decl.kind.is_const() { + return; + } + if decl.init.as_ref().is_some_and(|init| init.is_undefined() || init.is_void_0()) { + decl.init = None; + } + } +} diff --git a/crates/oxc_minifier/src/compressor/ast_util.rs b/crates/oxc_minifier/src/ast_util.rs similarity index 100% rename from crates/oxc_minifier/src/compressor/ast_util.rs rename to crates/oxc_minifier/src/ast_util.rs diff --git a/crates/oxc_minifier/src/compressor.rs b/crates/oxc_minifier/src/compressor.rs new file mode 100644 index 00000000000000..6a2c013a0f4310 --- /dev/null +++ b/crates/oxc_minifier/src/compressor.rs @@ -0,0 +1,47 @@ +use oxc_allocator::Allocator; +#[allow(clippy::wildcard_imports)] +use oxc_ast::{ast::*, AstBuilder}; + +use crate::{ + ast_passes::{ + Collapse, FoldConstants, RemoveDeadCode, RemoveSyntax, SubstituteAlternateSyntax, + }, + CompressOptions, +}; + +pub struct Compressor<'a> { + allocator: &'a Allocator, + options: CompressOptions, +} + +impl<'a> Compressor<'a> { + pub fn new(allocator: &'a Allocator, options: CompressOptions) -> Self { + Self { allocator, options } + } + + pub fn build(self, program: &mut Program<'a>) { + let ast = AstBuilder::new(self.allocator); + if self.options.remove_syntax { + RemoveSyntax::new(ast, self.options).build(program); + } + if self.options.fold_constants { + FoldConstants::new(ast).with_evaluate(self.options.evaluate).build(program); + } + if self.options.substitute_alternate_syntax { + SubstituteAlternateSyntax::new(ast, self.options).build(program); + } + if self.options.remove_dead_code { + RemoveDeadCode::new(ast).build(program); + } + if self.options.collapse { + Collapse::new(ast, self.options).build(program); + } + } + + pub fn dce(self, program: &mut Program<'a>) { + let ast = AstBuilder::new(self.allocator); + RemoveSyntax::new(ast, self.options).build(program); + FoldConstants::new(ast).with_evaluate(self.options.evaluate).build(program); + RemoveDeadCode::new(ast).build(program); + } +} diff --git a/crates/oxc_minifier/src/compressor/mod.rs b/crates/oxc_minifier/src/compressor/mod.rs deleted file mode 100644 index ebb6f6d14652bf..00000000000000 --- a/crates/oxc_minifier/src/compressor/mod.rs +++ /dev/null @@ -1,372 +0,0 @@ -#![allow(clippy::unused_self)] - -pub(crate) mod ast_util; -mod options; -mod util; - -use oxc_allocator::{Allocator, Vec}; -use oxc_ast::visit::walk_mut; -#[allow(clippy::wildcard_imports)] -use oxc_ast::{ast::*, AstBuilder, VisitMut}; -use oxc_span::Span; -use oxc_syntax::{ - number::NumberBase, - operator::{BinaryOperator, UnaryOperator}, - precedence::GetPrecedence, -}; - -use crate::ast_passes::RemoveParens; -use crate::folder::Folder; - -pub use self::options::CompressOptions; - -pub struct Compressor<'a> { - ast: AstBuilder<'a>, - options: CompressOptions, - - prepass: RemoveParens<'a>, - folder: Folder<'a>, -} - -const SPAN: Span = Span::new(0, 0); - -impl<'a> Compressor<'a> { - pub fn new(allocator: &'a Allocator, options: CompressOptions) -> Self { - let ast = AstBuilder::new(allocator); - let folder = Folder::new(ast).with_evaluate(options.evaluate); - let prepass = RemoveParens::new(allocator); - Self { ast, options, prepass, folder } - } - - pub fn build(mut self, program: &mut Program<'a>) { - self.prepass.build(program); - self.visit_program(program); - } - - /* Utilities */ - - /// `1/0` - #[allow(unused)] - fn create_one_div_zero(&mut self) -> Expression<'a> { - let left = self.ast.expression_numeric_literal(SPAN, 1.0, "1", NumberBase::Decimal); - let right = self.ast.expression_numeric_literal(SPAN, 0.0, "0", NumberBase::Decimal); - self.ast.expression_binary(SPAN, left, BinaryOperator::Division, right) - } - - /// Test `Object.defineProperty(exports, ...)` - fn is_object_define_property_exports(expr: &Expression<'a>) -> bool { - let Expression::CallExpression(call_expr) = expr else { return false }; - let Some(Argument::Identifier(ident)) = call_expr.arguments.first() else { return false }; - if ident.name != "exports" { - return false; - } - call_expr.callee.is_specific_member_access("Object", "defineProperty") - } - - /* Statements */ - - /// Remove block from single line blocks - /// `{ block } -> block` - #[allow(clippy::only_used_in_recursion)] // `&self` is only used in recursion - fn compress_block(&self, stmt: &mut Statement<'a>) { - if let Statement::BlockStatement(block) = stmt { - // Avoid compressing `if (x) { var x = 1 }` to `if (x) var x = 1` due to different - // semantics according to AnnexB, which lead to different semantics. - if block.body.len() == 1 && !block.body[0].is_declaration() { - *stmt = block.body.remove(0); - self.compress_block(stmt); - } - } - } - - /// Drop `drop_debugger` statement. - /// Enabled by `compress.drop_debugger` - fn drop_debugger(&mut self, stmt: &Statement<'a>) -> bool { - matches!(stmt, Statement::DebuggerStatement(_)) && self.options.drop_debugger - } - - /// Drop `console.*` expressions. - /// Enabled by `compress.drop_console - fn drop_console(&mut self, stmt: &Statement<'a>) -> bool { - self.options.drop_console - && matches!(stmt, Statement::ExpressionStatement(expr) if util::is_console(&expr.expression)) - } - - fn compress_console(&mut self, expr: &mut Expression<'a>) -> bool { - if self.options.drop_console && util::is_console(expr) { - *expr = self.ast.void_0(); - true - } else { - false - } - } - - /// Join consecutive var statements - fn join_vars(&mut self, stmts: &mut Vec<'a, Statement<'a>>) { - // Collect all the consecutive ranges that contain joinable vars. - // This is required because Rust prevents in-place vec mutation. - let mut ranges = vec![]; - let mut range = 0..0; - let mut i = 1usize; - let mut capacity = 0usize; - for window in stmts.windows(2) { - let [prev, cur] = window else { unreachable!() }; - if let ( - Statement::VariableDeclaration(cur_decl), - Statement::VariableDeclaration(prev_decl), - ) = (cur, prev) - { - if cur_decl.kind == prev_decl.kind { - if i - 1 != range.end { - range.start = i - 1; - } - range.end = i + 1; - } - } - if (range.end != i || i == stmts.len() - 1) && range.start < range.end { - capacity += range.end - range.start - 1; - ranges.push(range.clone()); - range = 0..0; - } - i += 1; - } - - if ranges.is_empty() { - return; - } - - // Reconstruct the stmts array by joining consecutive ranges - let mut new_stmts = self.ast.vec_with_capacity(stmts.len() - capacity); - for (i, stmt) in stmts.drain(..).enumerate() { - if i > 0 && ranges.iter().any(|range| range.contains(&(i - 1)) && range.contains(&i)) { - if let Statement::VariableDeclaration(prev_decl) = new_stmts.last_mut().unwrap() { - if let Statement::VariableDeclaration(mut cur_decl) = stmt { - prev_decl.declarations.append(&mut cur_decl.declarations); - } - } - } else { - new_stmts.push(stmt); - } - } - *stmts = new_stmts; - } - - /// Transforms `while(expr)` to `for(;expr;)` - fn compress_while(&mut self, stmt: &mut Statement<'a>) { - let Statement::WhileStatement(while_stmt) = stmt else { return }; - if self.options.loops { - let dummy_test = self.ast.expression_this(SPAN); - let test = std::mem::replace(&mut while_stmt.test, dummy_test); - let body = self.ast.move_statement(&mut while_stmt.body); - *stmt = self.ast.statement_for(SPAN, None, Some(test), None, body); - } - } - - /* Expressions */ - - /// Transforms `undefined` => `void 0` - fn compress_undefined(&self, expr: &mut Expression<'a>) -> bool { - let Expression::Identifier(ident) = expr else { return false }; - if ident.name == "undefined" { - // if let Some(reference_id) = ident.reference_id.get() { - // && self.semantic.symbols().is_global_reference(reference_id) - *expr = self.ast.void_0(); - return true; - // } - } - false - } - - /// Transforms `Infinity` => `1/0` - #[allow(unused)] - fn compress_infinity(&mut self, expr: &mut Expression<'a>) -> bool { - let Expression::Identifier(ident) = expr else { return false }; - if ident.name == "Infinity" { - // if let Some(reference_id) = ident.reference_id.get() { - //&& self.semantic.symbols().is_global_reference(reference_id) - *expr = self.create_one_div_zero(); - return true; - // } - } - false - } - - /// Transforms boolean expression `true` => `!0` `false` => `!1` - /// Enabled by `compress.booleans` - fn compress_boolean(&mut self, expr: &mut Expression<'a>) -> bool { - let Expression::BooleanLiteral(lit) = expr else { return false }; - if self.options.booleans { - let num = self.ast.expression_numeric_literal( - SPAN, - if lit.value { 0.0 } else { 1.0 }, - if lit.value { "0" } else { "1" }, - NumberBase::Decimal, - ); - *expr = self.ast.expression_unary(SPAN, UnaryOperator::LogicalNot, num); - return true; - } - false - } - - /// Compress `typeof foo == "undefined"` into `typeof foo > "u"` - /// Enabled by `compress.typeofs` - fn compress_typeof_undefined(&self, expr: &mut BinaryExpression<'a>) { - if !self.options.typeofs { - return; - } - if !matches!(expr.operator, BinaryOperator::Equality | BinaryOperator::StrictEquality) { - return; - } - let pair = self.commutative_pair( - (&expr.left, &expr.right), - |a| a.is_specific_string_literal("undefined").then_some(()), - |b| { - if let Expression::UnaryExpression(op) = b { - if op.operator == UnaryOperator::Typeof { - if let Expression::Identifier(id) = &op.argument { - return Some((*id).clone()); - } - } - } - None - }, - ); - let Some((_void_exp, id_ref)) = pair else { - return; - }; - let argument = self.ast.expression_from_identifier_reference(id_ref); - let left = self.ast.unary_expression(SPAN, UnaryOperator::Typeof, argument); - let right = self.ast.string_literal(SPAN, "u"); - let binary_expr = self.ast.binary_expression( - expr.span, - self.ast.expression_from_unary(left), - BinaryOperator::GreaterThan, - self.ast.expression_from_string_literal(right), - ); - *expr = binary_expr; - } - - fn commutative_pair( - &self, - pair: (&A, &A), - check_a: F, - check_b: G, - ) -> Option<(RetF, RetG)> - where - F: Fn(&A) -> Option, - G: Fn(&A) -> Option, - { - if let Some(a) = check_a(pair.0) { - if let Some(b) = check_b(pair.1) { - return Some((a, b)); - } - } else if let Some(a) = check_a(pair.1) { - if let Some(b) = check_b(pair.0) { - return Some((a, b)); - } - } - None - } - - /// Removes redundant argument of `ReturnStatement` - /// - /// `return undefined` -> `return` - /// `return void 0` -> `return` - fn compress_return_statement(&mut self, stmt: &mut ReturnStatement<'a>) { - if stmt.argument.as_ref().is_some_and(|expr| expr.is_undefined() || expr.is_void_0()) { - stmt.argument = None; - } - } - - fn compress_variable_declarator(&mut self, decl: &mut VariableDeclarator<'a>) { - if decl.kind.is_const() { - return; - } - if decl.init.as_ref().is_some_and(|init| init.is_undefined() || init.is_void_0()) { - decl.init = None; - } - } - - /// [Peephole Reorder Constant Expression](https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/PeepholeReorderConstantExpression.java) - /// - /// Reorder constant expression hoping for a better compression. - /// ex. x === 0 -> 0 === x - /// After reordering, expressions like 0 === x and 0 === y may have higher - /// compression together than their original counterparts. - #[allow(unused)] - fn reorder_constant_expression(&self, expr: &mut BinaryExpression<'a>) { - let operator = expr.operator; - if operator.is_equality() - || operator.is_compare() - || operator == BinaryOperator::Multiplication - { - if expr.precedence() == expr.left.precedence() { - return; - } - if !expr.left.is_immutable_value() && expr.right.is_immutable_value() { - if let Some(inverse_operator) = operator.compare_inverse_operator() { - expr.operator = inverse_operator; - } - std::mem::swap(&mut expr.left, &mut expr.right); - } - } - } -} - -impl<'a> VisitMut<'a> for Compressor<'a> { - fn visit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>) { - stmts.retain(|stmt| { - if self.drop_debugger(stmt) { - return false; - } - if self.drop_console(stmt) { - return false; - } - true - }); - - if self.options.join_vars { - self.join_vars(stmts); - } - - walk_mut::walk_statements(self, stmts); - } - - fn visit_statement(&mut self, stmt: &mut Statement<'a>) { - self.compress_block(stmt); - self.compress_while(stmt); - self.folder.fold_condition(stmt); - walk_mut::walk_statement(self, stmt); - } - - fn visit_return_statement(&mut self, stmt: &mut ReturnStatement<'a>) { - walk_mut::walk_return_statement(self, stmt); - // We may fold `void 1` to `void 0`, so compress it after visiting - self.compress_return_statement(stmt); - } - - fn visit_variable_declaration(&mut self, decl: &mut VariableDeclaration<'a>) { - for declarator in decl.declarations.iter_mut() { - self.visit_variable_declarator(declarator); - self.compress_variable_declarator(declarator); - } - } - - fn visit_expression(&mut self, expr: &mut Expression<'a>) { - // Bail cjs `Object.defineProperty(exports, ...)` - if Self::is_object_define_property_exports(expr) { - return; - } - walk_mut::walk_expression(self, expr); - self.compress_console(expr); - self.folder.fold_expression(expr); - if !self.compress_undefined(expr) { - self.compress_boolean(expr); - } - } - - fn visit_binary_expression(&mut self, expr: &mut BinaryExpression<'a>) { - walk_mut::walk_binary_expression(self, expr); - self.compress_typeof_undefined(expr); - } -} diff --git a/crates/oxc_minifier/src/compressor/util.rs b/crates/oxc_minifier/src/compressor/util.rs deleted file mode 100644 index 602d98bf292988..00000000000000 --- a/crates/oxc_minifier/src/compressor/util.rs +++ /dev/null @@ -1,10 +0,0 @@ -use oxc_ast::ast::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 Some(member_expr) = call_expr.callee.as_member_expression() else { return false }; - let obj = member_expr.object(); - let Some(ident) = obj.get_identifier_reference() else { return false }; - ident.name == "console" -} diff --git a/crates/oxc_minifier/src/folder/util.rs b/crates/oxc_minifier/src/folder/util.rs deleted file mode 100644 index a99983b2a22e68..00000000000000 --- a/crates/oxc_minifier/src/folder/util.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::cmp::Ordering; - -use num_bigint::BigInt; - -use super::tri::Tri; - -use crate::compressor::ast_util::{is_exact_int64, NumberValue}; - -/// ported from [closure compiler](https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/PeepholeFoldConstants.java#L1250) -#[allow(clippy::cast_possible_truncation)] -pub fn bigint_less_than_number( - bigint_value: &BigInt, - number_value: &NumberValue, - invert: Tri, - will_negative: bool, -) -> Tri { - // if invert is false, then the number is on the right in tryAbstractRelationalComparison - // if it's true, then the number is on the left - match number_value { - NumberValue::NaN => Tri::for_boolean(will_negative), - NumberValue::PositiveInfinity => Tri::True.xor(invert), - NumberValue::NegativeInfinity => Tri::False.xor(invert), - NumberValue::Number(num) => { - if let Some(Ordering::Equal | Ordering::Greater) = - num.abs().partial_cmp(&2_f64.powi(53)) - { - Tri::Unknown - } else { - let number_as_bigint = BigInt::from(*num as i64); - - match bigint_value.cmp(&number_as_bigint) { - Ordering::Less => Tri::True.xor(invert), - Ordering::Greater => Tri::False.xor(invert), - Ordering::Equal => { - if is_exact_int64(*num) { - Tri::False - } else { - Tri::for_boolean(num.is_sign_positive()).xor(invert) - } - } - } - } - } - } -} diff --git a/crates/oxc_minifier/src/keep_var.rs b/crates/oxc_minifier/src/keep_var.rs new file mode 100644 index 00000000000000..3bbdcfd5dcd8e1 --- /dev/null +++ b/crates/oxc_minifier/src/keep_var.rs @@ -0,0 +1,51 @@ +#[allow(clippy::wildcard_imports)] +use oxc_ast::{ast::*, syntax_directed_operations::BoundNames, AstBuilder, Visit}; +use oxc_span::{Atom, Span, SPAN}; +use oxc_syntax::scope::ScopeFlags; + +pub struct KeepVar<'a> { + ast: AstBuilder<'a>, + vars: std::vec::Vec<(Atom<'a>, Span)>, +} + +impl<'a> Visit<'a> for KeepVar<'a> { + fn visit_variable_declarator(&mut self, decl: &VariableDeclarator<'a>) { + if decl.kind.is_var() { + decl.id.bound_names(&mut |ident| { + self.vars.push((ident.name.clone(), ident.span)); + }); + } + } + + fn visit_function(&mut self, _it: &Function<'a>, _flags: ScopeFlags) { + /* skip functions */ + } + + fn visit_class(&mut self, _it: &Class<'a>) { + /* skip classes */ + } +} + +impl<'a> KeepVar<'a> { + pub fn new(ast: AstBuilder<'a>) -> Self { + Self { ast, vars: std::vec![] } + } + + pub fn get_variable_declaration_statement(self) -> Option> { + if self.vars.is_empty() { + return None; + } + + let kind = VariableDeclarationKind::Var; + let decls = self.ast.vec_from_iter(self.vars.into_iter().map(|(name, span)| { + let binding_kind = self.ast.binding_pattern_kind_binding_identifier(span, name); + let id = + self.ast.binding_pattern::>(binding_kind, None, false); + self.ast.variable_declarator(span, kind, id, None, false) + })); + + let decl = self.ast.variable_declaration(SPAN, kind, decls, false); + let stmt = self.ast.statement_declaration(self.ast.declaration_from_variable(decl)); + Some(stmt) + } +} diff --git a/crates/oxc_minifier/src/lib.rs b/crates/oxc_minifier/src/lib.rs index 16012ace41bdb7..d69bd75a07f00f 100644 --- a/crates/oxc_minifier/src/lib.rs +++ b/crates/oxc_minifier/src/lib.rs @@ -1,17 +1,21 @@ -#![allow(clippy::wildcard_imports, clippy::unused_self)] //! ECMAScript Minifier mod ast_passes; +mod ast_util; mod compressor; -mod folder; +mod keep_var; +mod options; +mod tri; +mod ty; use oxc_allocator::Allocator; use oxc_ast::ast::Program; use oxc_mangler::{Mangler, ManglerBuilder}; pub use crate::{ - ast_passes::{RemoveDeadCode, RemoveParens, ReplaceGlobalDefines, ReplaceGlobalDefinesConfig}, - compressor::{CompressOptions, Compressor}, + ast_passes::{RemoveDeadCode, RemoveSyntax, ReplaceGlobalDefines, ReplaceGlobalDefinesConfig}, + compressor::Compressor, + options::CompressOptions, }; #[derive(Debug, Clone, Copy)] diff --git a/crates/oxc_minifier/src/compressor/options.rs b/crates/oxc_minifier/src/options.rs similarity index 76% rename from crates/oxc_minifier/src/compressor/options.rs rename to crates/oxc_minifier/src/options.rs index 957b2dadd99c74..4c9963d7907cee 100644 --- a/crates/oxc_minifier/src/compressor/options.rs +++ b/crates/oxc_minifier/src/options.rs @@ -1,5 +1,11 @@ #[derive(Debug, Clone, Copy)] pub struct CompressOptions { + pub remove_syntax: bool, + pub substitute_alternate_syntax: bool, + pub fold_constants: bool, + pub remove_dead_code: bool, + pub collapse: bool, + /// Various optimizations for boolean context, for example `!!a ? b : c` โ†’ `a ? b : c`. /// /// Default `true` @@ -39,6 +45,11 @@ pub struct CompressOptions { impl Default for CompressOptions { fn default() -> Self { Self { + remove_syntax: true, + substitute_alternate_syntax: true, + fold_constants: true, + remove_dead_code: true, + collapse: true, booleans: true, drop_debugger: true, drop_console: false, @@ -60,11 +71,17 @@ impl CompressOptions { join_vars: true, loops: true, typeofs: true, + ..Self::default() } } pub fn all_false() -> Self { Self { + remove_syntax: false, + substitute_alternate_syntax: false, + fold_constants: false, + remove_dead_code: false, + collapse: false, booleans: false, drop_debugger: false, drop_console: false, diff --git a/crates/oxc_minifier/src/folder/tri.rs b/crates/oxc_minifier/src/tri.rs similarity index 89% rename from crates/oxc_minifier/src/folder/tri.rs rename to crates/oxc_minifier/src/tri.rs index 9bc0f541c36c95..bfad8945c65178 100644 --- a/crates/oxc_minifier/src/folder/tri.rs +++ b/crates/oxc_minifier/src/tri.rs @@ -16,10 +16,10 @@ impl Tri { } pub fn xor(self, other: Self) -> Self { - self.for_int(-self.value() * other.value()) + Self::for_int(-self.value() * other.value()) } - pub fn for_int(self, int: i8) -> Self { + pub fn for_int(int: i8) -> Self { match int { -1 => Self::False, 1 => Self::True, diff --git a/crates/oxc_minifier/src/folder/ty.rs b/crates/oxc_minifier/src/ty.rs similarity index 98% rename from crates/oxc_minifier/src/folder/ty.rs rename to crates/oxc_minifier/src/ty.rs index 445a42e1e06ba5..933ca5bf59a659 100644 --- a/crates/oxc_minifier/src/folder/ty.rs +++ b/crates/oxc_minifier/src/ty.rs @@ -1,3 +1,4 @@ +#[allow(clippy::wildcard_imports)] use oxc_ast::ast::*; use oxc_syntax::operator::{BinaryOperator, UnaryOperator}; diff --git a/crates/oxc_minifier/tests/closure/fold_conditions.rs b/crates/oxc_minifier/tests/closure/fold_conditions.rs index a6b90799c25370..fb0b62cda2a27e 100644 --- a/crates/oxc_minifier/tests/closure/fold_conditions.rs +++ b/crates/oxc_minifier/tests/closure/fold_conditions.rs @@ -1,6 +1,8 @@ use crate::test; +// TODO: PeepholeMinimizeConditions.java #[test] +#[ignore] fn test_fold_not() { test("while(!(x==y)){a=b;}", "for(;x!=y;)a=b"); test("while(!(x!=y)){a=b;}", "for(;x==y;)a=b"); diff --git a/crates/oxc_minifier/tests/closure/fold_constants.rs b/crates/oxc_minifier/tests/closure/fold_constants.rs index 8e75085c0cc650..4b327c25574841 100644 --- a/crates/oxc_minifier/tests/closure/fold_constants.rs +++ b/crates/oxc_minifier/tests/closure/fold_constants.rs @@ -1,227 +1,232 @@ //! -use crate::{test, test_same, test_without_compress_booleans as test_wcb}; +use oxc_minifier::CompressOptions; + +use crate::test_with_options; + +fn test(source_text: &str, expected: &str) { + let options = CompressOptions { fold_constants: true, ..CompressOptions::all_false() }; + test_with_options(source_text, expected, options); +} + +fn test_same(source_text: &str) { + test(source_text, source_text); +} #[test] fn undefined_comparison1() { - test("undefined == undefined", "!0"); - test("undefined == null", "!0"); - test("undefined == void 0", "!0"); - - test("undefined == 0", "!1"); - test("undefined == 1", "!1"); - test("undefined == 'hi'", "!1"); - test("undefined == true", "!1"); - test("undefined == false", "!1"); - - test("undefined === undefined", "!0"); - test("undefined === null", "!1"); - test("undefined === void 0", "!0"); - - // origin was `test_same("undefined == this");` - test("undefined == this", "void 0==this"); - // origin was `test_same("undefined == x");` - test("undefined == x", "void 0==x"); - - test("undefined != undefined", "!1"); - test("undefined != null", "!1"); - test("undefined != void 0", "!1"); - - test("undefined != 0", "!0"); - test("undefined != 1", "!0"); - test("undefined != 'hi'", "!0"); - test("undefined != true", "!0"); - test("undefined != false", "!0"); - - test("undefined !== undefined", "!1"); - test("undefined !== void 0", "!1"); - test("undefined !== null", "!0"); - - // origin was `test_same("undefined != this");` - test("undefined != this", "void 0!=this"); - // origin was `test_same("undefined != x");` - test("undefined != x", "void 0!=x"); - - test("undefined < undefined", "!1"); - test("undefined > undefined", "!1"); - test("undefined >= undefined", "!1"); - test("undefined <= undefined", "!1"); - - test("0 < undefined", "!1"); - test("true > undefined", "!1"); - test("'hi' >= undefined", "!1"); - test("null <= undefined", "!1"); - - test("undefined < 0", "!1"); - test("undefined > true", "!1"); - test("undefined >= 'hi'", "!1"); - test("undefined <= null", "!1"); - - test("null == undefined", "!0"); - test("0 == undefined", "!1"); - test("1 == undefined", "!1"); - test("'hi' == undefined", "!1"); - test("true == undefined", "!1"); - test("false == undefined", "!1"); - test("null === undefined", "!1"); - test("void 0 === undefined", "!0"); - - test("undefined == NaN", "!1"); - test("NaN == undefined", "!1"); - test("undefined == Infinity", "!1"); - test("Infinity == undefined", "!1"); - test("undefined == -Infinity", "!1"); - test("-Infinity == undefined", "!1"); - test("({}) == undefined", "!1"); - test("undefined == ({})", "!1"); - test("([]) == undefined", "!1"); - test("undefined == ([])", "!1"); - test("(/a/g) == undefined", "!1"); - test("undefined == (/a/g)", "!1"); - test("(function(){}) == undefined", "!1"); - test("undefined == (function(){})", "!1"); - - test("undefined != NaN", "!0"); - test("NaN != undefined", "!0"); - test("undefined != Infinity", "!0"); - test("Infinity != undefined", "!0"); - test("undefined != -Infinity", "!0"); - test("-Infinity != undefined", "!0"); - test("({}) != undefined", "!0"); - test("undefined != ({})", "!0"); - test("([]) != undefined", "!0"); - test("undefined != ([])", "!0"); - test("(/a/g) != undefined", "!0"); - test("undefined != (/a/g)", "!0"); - test("(function(){}) != undefined", "!0"); - test("undefined != (function(){})", "!0"); - - // origin was `test_same("this == undefined");` - test("this == undefined", "this==void 0"); - // origin was `test_same("x == undefined");` - test("x == undefined", "x==void 0"); + test("undefined == undefined", "true"); + test("undefined == null", "true"); + test("undefined == void 0", "true"); + + test("undefined == 0", "false"); + test("undefined == 1", "false"); + test("undefined == 'hi'", "false"); + test("undefined == true", "false"); + test("undefined == false", "false"); + + test("undefined === undefined", "true"); + test("undefined === null", "false"); + test("undefined === void 0", "true"); + + test_same("undefined == this"); + test_same("undefined == x"); + + test("undefined != undefined", "false"); + test("undefined != null", "false"); + test("undefined != void 0", "false"); + + test("undefined != 0", "true"); + test("undefined != 1", "true"); + test("undefined != 'hi'", "true"); + test("undefined != true", "true"); + test("undefined != false", "true"); + + test("undefined !== undefined", "false"); + test("undefined !== void 0", "false"); + test("undefined !== null", "true"); + + test_same("undefined != this"); + test_same("undefined != x"); + + test("undefined < undefined", "false"); + test("undefined > undefined", "false"); + test("undefined >= undefined", "false"); + test("undefined <= undefined", "false"); + + test("0 < undefined", "false"); + test("true > undefined", "false"); + test("'hi' >= undefined", "false"); + test("null <= undefined", "false"); + + test("undefined < 0", "false"); + test("undefined > true", "false"); + test("undefined >= 'hi'", "false"); + test("undefined <= null", "false"); + + test("null == undefined", "true"); + test("0 == undefined", "false"); + test("1 == undefined", "false"); + test("'hi' == undefined", "false"); + test("true == undefined", "false"); + test("false == undefined", "false"); + test("null === undefined", "false"); + test("void 0 === undefined", "true"); + + test("undefined == NaN", "false"); + test("NaN == undefined", "false"); + test("undefined == Infinity", "false"); + test("Infinity == undefined", "false"); + test("undefined == -Infinity", "false"); + test("-Infinity == undefined", "false"); + test("({}) == undefined", "false"); + test("undefined == ({})", "false"); + test("([]) == undefined", "false"); + test("undefined == ([])", "false"); + test("(/a/g) == undefined", "false"); + test("undefined == (/a/g)", "false"); + test("(function(){}) == undefined", "false"); + test("undefined == (function(){})", "false"); + + test("undefined != NaN", "true"); + test("NaN != undefined", "true"); + test("undefined != Infinity", "true"); + test("Infinity != undefined", "true"); + test("undefined != -Infinity", "true"); + test("-Infinity != undefined", "true"); + test("({}) != undefined", "true"); + test("undefined != ({})", "true"); + test("([]) != undefined", "true"); + test("undefined != ([])", "true"); + test("(/a/g) != undefined", "true"); + test("undefined != (/a/g)", "true"); + test("(function(){}) != undefined", "true"); + test("undefined != (function(){})", "true"); + + test_same("this == undefined"); + test_same("x == undefined"); } #[test] fn test_undefined_comparison2() { - test("'123' !== void 0", "!0"); - test("'123' === void 0", "!1"); + test("\"123\" !== void 0", "true"); + test("\"123\" === void 0", "false"); - test("void 0 !== '123'", "!0"); - test("void 0 === '123'", "!1"); + test("void 0 !== \"123\"", "true"); + test("void 0 === \"123\"", "false"); } #[test] fn test_undefined_comparison3() { - test("'123' !== undefined", "!0"); - test("'123' === undefined", "!1"); + test("\"123\" !== undefined", "true"); + test("\"123\" === undefined", "false"); - test("undefined !== '123'", "!0"); - test("undefined === '123'", "!1"); + test("undefined !== \"123\"", "true"); + test("undefined === \"123\"", "false"); } #[test] fn test_null_comparison1() { - test_wcb("null == undefined", "true"); - test_wcb("null == null", "true"); - test_wcb("null == void 0", "true"); - - test_wcb("null == 0", "false"); - test_wcb("null == 1", "false"); - // test_wcb("null == 0n", "false"); - // test_wcb("null == 1n", "false"); - test_wcb("null == 'hi'", "false"); - test_wcb("null == true", "false"); - test_wcb("null == false", "false"); - - test_wcb("null === undefined", "false"); - test_wcb("null === null", "true"); - test_wcb("null === void 0", "false"); + test("null == undefined", "true"); + test("null == null", "true"); + test("null == void 0", "true"); + + test("null == 0", "false"); + test("null == 1", "false"); + // test("null == 0n", "false"); + // test("null == 1n", "false"); + test("null == 'hi'", "false"); + test("null == true", "false"); + test("null == false", "false"); + + test("null === undefined", "false"); + test("null === null", "true"); + test("null === void 0", "false"); test_same("null===x"); test_same("null==this"); test_same("null==x"); - test_wcb("null != undefined", "false"); - test_wcb("null != null", "false"); - test_wcb("null != void 0", "false"); + test("null != undefined", "false"); + test("null != null", "false"); + test("null != void 0", "false"); - test_wcb("null != 0", "true"); - test_wcb("null != 1", "true"); - // test_wcb("null != 0n", "true"); - // test_wcb("null != 1n", "true"); - test_wcb("null != 'hi'", "true"); - test_wcb("null != true", "true"); - test_wcb("null != false", "true"); + test("null != 0", "true"); + test("null != 1", "true"); + // test("null != 0n", "true"); + // test("null != 1n", "true"); + test("null != 'hi'", "true"); + test("null != true", "true"); + test("null != false", "true"); - test_wcb("null !== undefined", "true"); - test_wcb("null !== void 0", "true"); - test_wcb("null !== null", "false"); + test("null !== undefined", "true"); + test("null !== void 0", "true"); + test("null !== null", "false"); test_same("null!=this"); test_same("null!=x"); - test_wcb("null < null", "false"); - test_wcb("null > null", "false"); - test_wcb("null >= null", "true"); - test_wcb("null <= null", "true"); - - test_wcb("0 < null", "false"); - test_wcb("0 > null", "false"); - test_wcb("0 >= null", "true"); - // test_wcb("0n < null", "false"); - // test_wcb("0n > null", "false"); - // test_wcb("0n >= null", "true"); - test_wcb("true > null", "true"); - test_wcb("'hi' < null", "false"); - test_wcb("'hi' >= null", "false"); - test_wcb("null <= null", "true"); - - test_wcb("null < 0", "false"); - // test_wcb("null < 0n", "false"); - test_wcb("null > true", "false"); - test_wcb("null < 'hi'", "false"); - test_wcb("null >= 'hi'", "false"); - test_wcb("null <= null", "true"); - - test_wcb("null == null", "true"); - test_wcb("0 == null", "false"); - test_wcb("1 == null", "false"); - test_wcb("'hi' == null", "false"); - test_wcb("true == null", "false"); - test_wcb("false == null", "false"); - test_wcb("null === null", "true"); - test_wcb("void 0 === null", "false"); - - test_wcb("null == NaN", "false"); - test_wcb("NaN == null", "false"); - test_wcb("null == Infinity", "false"); - test_wcb("Infinity == null", "false"); - test_wcb("null == -Infinity", "false"); - test_wcb("-Infinity == null", "false"); - test_wcb("({}) == null", "false"); - test_wcb("null == ({})", "false"); - test_wcb("([]) == null", "false"); - test_wcb("null == ([])", "false"); - test_wcb("(/a/g) == null", "false"); - test_wcb("null == (/a/g)", "false"); - test_wcb("(function(){}) == null", "false"); - test_wcb("null == (function(){})", "false"); - - test_wcb("null != NaN", "true"); - test_wcb("NaN != null", "true"); - test_wcb("null != Infinity", "true"); - test_wcb("Infinity != null", "true"); - test_wcb("null != -Infinity", "true"); - test_wcb("-Infinity != null", "true"); - test_wcb("({}) != null", "true"); - test_wcb("null != ({})", "true"); - test_wcb("([]) != null", "true"); - test_wcb("null != ([])", "true"); - test_wcb("(/a/g) != null", "true"); - test_wcb("null != (/a/g)", "true"); - test_wcb("(function(){}) != null", "true"); - test_wcb("null != (function(){})", "true"); + test("null < null", "false"); + test("null > null", "false"); + test("null >= null", "true"); + test("null <= null", "true"); + + test("0 < null", "false"); + test("0 > null", "false"); + test("0 >= null", "true"); + // test("0n < null", "false"); + // test("0n > null", "false"); + // test("0n >= null", "true"); + test("true > null", "true"); + test("'hi' < null", "false"); + test("'hi' >= null", "false"); + test("null <= null", "true"); + + test("null < 0", "false"); + // test("null < 0n", "false"); + test("null > true", "false"); + test("null < 'hi'", "false"); + test("null >= 'hi'", "false"); + test("null <= null", "true"); + + test("null == null", "true"); + test("0 == null", "false"); + test("1 == null", "false"); + test("'hi' == null", "false"); + test("true == null", "false"); + test("false == null", "false"); + test("null === null", "true"); + test("void 0 === null", "false"); + + test("null == NaN", "false"); + test("NaN == null", "false"); + test("null == Infinity", "false"); + test("Infinity == null", "false"); + test("null == -Infinity", "false"); + test("-Infinity == null", "false"); + test("({}) == null", "false"); + test("null == ({})", "false"); + test("([]) == null", "false"); + test("null == ([])", "false"); + test("(/a/g) == null", "false"); + test("null == (/a/g)", "false"); + test("(function(){}) == null", "false"); + test("null == (function(){})", "false"); + + test("null != NaN", "true"); + test("NaN != null", "true"); + test("null != Infinity", "true"); + test("Infinity != null", "true"); + test("null != -Infinity", "true"); + test("-Infinity != null", "true"); + test("({}) != null", "true"); + test("null != ({})", "true"); + test("([]) != null", "true"); + test("null != ([])", "true"); + test("(/a/g) != null", "true"); + test("null != (/a/g)", "true"); + test("(function(){}) != null", "true"); + test("null != (function(){})", "true"); test_same("({a:f()})==null"); test_same("null=={a:f()}"); @@ -247,200 +252,200 @@ fn test_boolean_boolean_comparison() { fn test_boolean_number_comparison() { test_same("!x==+y"); test_same("!x<=+y"); - test_wcb("!x !== +y", "true"); + test("!x !== +y", "true"); } #[test] fn test_number_boolean_comparison() { test_same("+x==!y"); test_same("+x<=!y"); - test_wcb("+x === !y", "false"); + test("+x === !y", "false"); } #[test] fn test_boolean_string_comparison() { test_same("!x==''+y"); test_same("!x<=''+y"); - test_wcb("!x !== '' + y", "true"); + test("!x !== '' + y", "true"); } #[test] fn test_string_boolean_comparison() { test_same("''+x==!y"); test_same("''+x<=!y"); - test_wcb("'' + x === !y", "false"); + test("'' + x === !y", "false"); } #[test] fn test_string_string_comparison() { - test("'a' < 'b'", "!0"); - test("'a' <= 'b'", "!0"); - test("'a' > 'b'", "!1"); - test("'a' >= 'b'", "!1"); - test("+'a' < +'b'", "!1"); - test_same("typeof a<'a'"); - test_same("'a'>=typeof a"); - test("typeof a < typeof a", "!1"); - test("typeof a >= typeof a", "!0"); - test("typeof 3 > typeof 4", "!1"); - test("typeof function() {} < typeof function() {}", "!1"); - test("'a' == 'a'", "!0"); - test("'b' != 'a'", "!0"); - // test_same("'undefined'==typeof a"); // compresses to void 0 === a - test_same("typeof a!='number'"); - // test_same("'undefined'==typeof a"); // compresses to void 0 === a - // test_same("'undefined'==typeof a"); // compresses to void 0 === a - test("typeof a == typeof a", "!0"); - test("'a' === 'a'", "!0"); - test("'b' !== 'a'", "!0"); - test("typeof a === typeof a", "!0"); - test("typeof a !== typeof a", "!1"); - test_same("''+x<=''+y"); - test_same("''+x!=''+y"); - test_same("''+x===''+y"); - - test_same("''+x<=''+x"); // potentially foldable - test_same("''+x!=''+x"); // potentially foldable - test_same("''+x===''+x"); // potentially foldable + test("'a' < 'b'", "true"); + test("'a' <= 'b'", "true"); + test("'a' > 'b'", "false"); + test("'a' >= 'b'", "false"); + test("+'a' < +'b'", "false"); + test_same("typeof a < 'a'"); + test_same("'a' >= typeof a"); + test("typeof a < typeof a", "false"); + test("typeof a >= typeof a", "true"); + test("typeof 3 > typeof 4", "false"); + test("typeof function() {} < typeof function() {}", "false"); + test("'a' == 'a'", "true"); + test("'b' != 'a'", "true"); + test_same("'undefined' == typeof a"); + test_same("typeof a != 'number'"); + test_same("'undefined' == typeof a"); + test_same("'undefined' == typeof a"); + test("typeof a == typeof a", "true"); + test("'a' === 'a'", "true"); + test("'b' !== 'a'", "true"); + test("typeof a === typeof a", "true"); + test("typeof a !== typeof a", "false"); + test_same("'' + x <= '' + y"); + test_same("'' + x != '' + y"); + test_same("'' + x === '' + y"); + + test_same("'' + x <= '' + x"); // potentially foldable + test_same("'' + x != '' + x"); // potentially foldable + test_same("'' + x === '' + x"); // potentially foldable } #[test] fn test_number_string_comparison() { - test_wcb("1 < '2'", "true"); - test_wcb("2 > '1'", "true"); - test_wcb("123 > '34'", "true"); - test_wcb("NaN >= 'NaN'", "false"); - test_wcb("1 == '2'", "false"); - test_wcb("1 != '1'", "false"); - test_wcb("NaN == 'NaN'", "false"); - test_wcb("1 === '1'", "false"); - test_wcb("1 !== '1'", "true"); + test("1 < '2'", "true"); + test("2 > '1'", "true"); + test("123 > '34'", "true"); + test("NaN >= 'NaN'", "false"); + test("1 == '2'", "false"); + test("1 != '1'", "false"); + test("NaN == 'NaN'", "false"); + test("1 === '1'", "false"); + test("1 !== '1'", "true"); test_same("+x>''+y"); test_same("+x==''+y"); - test_wcb("+x !== '' + y", "true"); + test("+x !== '' + y", "true"); } #[test] fn test_string_number_comparison() { - test_wcb("'1' < 2", "true"); - test_wcb("'2' > 1", "true"); - test_wcb("'123' > 34", "true"); - test_wcb("'NaN' < NaN", "false"); - test_wcb("'1' == 2", "false"); - test_wcb("'1' != 1", "false"); - test_wcb("'NaN' == NaN", "false"); - test_wcb("'1' === 1", "false"); - test_wcb("'1' !== 1", "true"); + test("'1' < 2", "true"); + test("'2' > 1", "true"); + test("'123' > 34", "true"); + test("'NaN' < NaN", "false"); + test("'1' == 2", "false"); + test("'1' != 1", "false"); + test("'NaN' == NaN", "false"); + test("'1' === 1", "false"); + test("'1' !== 1", "true"); test_same("''+x<+y"); test_same("''+x==+y"); - test_wcb("'' + x === +y", "false"); + test("'' + x === +y", "false"); } #[test] #[ignore] fn test_bigint_number_comparison() { - test_wcb("1n < 2", "true"); - test_wcb("1n > 2", "false"); - test_wcb("1n == 1", "true"); - test_wcb("1n == 2", "false"); + test("1n < 2", "true"); + test("1n > 2", "false"); + test("1n == 1", "true"); + test("1n == 2", "false"); // comparing with decimals is allowed - test_wcb("1n < 1.1", "true"); - test_wcb("1n < 1.9", "true"); - test_wcb("1n < 0.9", "false"); - test_wcb("-1n < -1.1", "false"); - test_wcb("-1n < -1.9", "false"); - test_wcb("-1n < -0.9", "true"); - test_wcb("1n > 1.1", "false"); - test_wcb("1n > 0.9", "true"); - test_wcb("-1n > -1.1", "true"); - test_wcb("-1n > -0.9", "false"); + test("1n < 1.1", "true"); + test("1n < 1.9", "true"); + test("1n < 0.9", "false"); + test("-1n < -1.1", "false"); + test("-1n < -1.9", "false"); + test("-1n < -0.9", "true"); + test("1n > 1.1", "false"); + test("1n > 0.9", "true"); + test("-1n > -1.1", "true"); + test("-1n > -0.9", "false"); // Don't fold unsafely large numbers because there might be floating-point error let max_safe_int = 9_007_199_254_740_991_i64; let neg_max_safe_int = -9_007_199_254_740_991_i64; let max_safe_float = 9_007_199_254_740_991_f64; let neg_max_safe_float = -9_007_199_254_740_991_f64; - test_wcb(&format!("0n > {max_safe_int}"), "false"); - test_wcb(&format!("0n < {max_safe_int}"), "true"); - test_wcb(&format!("0n > {neg_max_safe_int}"), "true"); - test_wcb(&format!("0n < {neg_max_safe_int}"), "false"); - test_wcb(&format!("0n > {max_safe_float}"), "false"); - test_wcb(&format!("0n < {max_safe_float}"), "true"); - test_wcb(&format!("0n > {neg_max_safe_float}"), "true"); - test_wcb(&format!("0n < {neg_max_safe_float}"), "false"); + test(&format!("0n > {max_safe_int}"), "false"); + test(&format!("0n < {max_safe_int}"), "true"); + test(&format!("0n > {neg_max_safe_int}"), "true"); + test(&format!("0n < {neg_max_safe_int}"), "false"); + test(&format!("0n > {max_safe_float}"), "false"); + test(&format!("0n < {max_safe_float}"), "true"); + test(&format!("0n > {neg_max_safe_float}"), "true"); + test(&format!("0n < {neg_max_safe_float}"), "false"); // comparing with Infinity is allowed - test_wcb("1n < Infinity", "true"); - test_wcb("1n > Infinity", "false"); - test_wcb("1n < -Infinity", "false"); - test_wcb("1n > -Infinity", "true"); + test("1n < Infinity", "true"); + test("1n > Infinity", "false"); + test("1n < -Infinity", "false"); + test("1n > -Infinity", "true"); // null is interpreted as 0 when comparing with bigint - test_wcb("1n < null", "false"); - test_wcb("1n > null", "true"); + test("1n < null", "false"); + test("1n > null", "true"); } #[test] #[ignore] fn test_bigint_string_comparison() { - test_wcb("1n < '2'", "true"); - test_wcb("2n > '1'", "true"); - test_wcb("123n > '34'", "true"); - test_wcb("1n == '1'", "true"); - test_wcb("1n == '2'", "false"); - test_wcb("1n != '1'", "false"); - test_wcb("1n === '1'", "false"); - test_wcb("1n !== '1'", "true"); + test("1n < '2'", "true"); + test("2n > '1'", "true"); + test("123n > '34'", "true"); + test("1n == '1'", "true"); + test("1n == '2'", "false"); + test("1n != '1'", "false"); + test("1n === '1'", "false"); + test("1n !== '1'", "true"); } #[test] #[ignore] fn test_string_bigint_comparison() { - test_wcb("'1' < 2n", "true"); - test_wcb("'2' > 1n", "true"); - test_wcb("'123' > 34n", "true"); - test_wcb("'1' == 1n", "true"); - test_wcb("'1' == 2n", "false"); - test_wcb("'1' != 1n", "false"); - test_wcb("'1' === 1n", "false"); - test_wcb("'1' !== 1n", "true"); + test("'1' < 2n", "true"); + test("'2' > 1n", "true"); + test("'123' > 34n", "true"); + test("'1' == 1n", "true"); + test("'1' == 2n", "false"); + test("'1' != 1n", "false"); + test("'1' === 1n", "false"); + test("'1' !== 1n", "true"); } #[test] fn test_nan_comparison() { - test_wcb("NaN < 1", "false"); - test_wcb("NaN <= 1", "false"); - test_wcb("NaN > 1", "false"); - test_wcb("NaN >= 1", "false"); - // test_wcb("NaN < 1n", "false"); - // test_wcb("NaN <= 1n", "false"); - // test_wcb("NaN > 1n", "false"); - // test_wcb("NaN >= 1n", "false"); - - test_wcb("NaN < NaN", "false"); - test_wcb("NaN >= NaN", "false"); - test_wcb("NaN == NaN", "false"); - test_wcb("NaN === NaN", "false"); - - test_wcb("NaN < null", "false"); - test_wcb("null >= NaN", "false"); - test_wcb("NaN == null", "false"); - test_wcb("null != NaN", "true"); - test_wcb("null === NaN", "false"); - - test_wcb("NaN < undefined", "false"); - test_wcb("undefined >= NaN", "false"); - test_wcb("NaN == undefined", "false"); - test_wcb("undefined != NaN", "true"); - test_wcb("undefined === NaN", "false"); + test("NaN < 1", "false"); + test("NaN <= 1", "false"); + test("NaN > 1", "false"); + test("NaN >= 1", "false"); + // test("NaN < 1n", "false"); + // test("NaN <= 1n", "false"); + // test("NaN > 1n", "false"); + // test("NaN >= 1n", "false"); + + test("NaN < NaN", "false"); + test("NaN >= NaN", "false"); + test("NaN == NaN", "false"); + test("NaN === NaN", "false"); + + test("NaN < null", "false"); + test("null >= NaN", "false"); + test("NaN == null", "false"); + test("null != NaN", "true"); + test("null === NaN", "false"); + + test("NaN < undefined", "false"); + test("undefined >= NaN", "false"); + test("NaN == undefined", "false"); + test("undefined != NaN", "true"); + test("undefined === NaN", "false"); test_same("NaN=NaN"); test_same("NaN==x"); test_same("x!=NaN"); - test_wcb("NaN === x", "false"); - test_wcb("x !== NaN", "true"); + test("NaN === x", "false"); + test("x !== NaN", "true"); test_same("NaN==foo()"); } @@ -472,12 +477,12 @@ fn unary_ops() { // test_same("-foo()"); // These cases are handled here. - test("a=!true", "a=!!0"); - test("a=!10", "a=!1"); - test("a=!false", "a=!!1"); + test("a=!true", "a=false"); + test("a=!10", "a=false"); + test("a=!false", "a=true"); test_same("a=!foo()"); - test("a=-0", "a=-0"); - test("a=-(0)", "a=-0"); + test("a=-0", "a=-0.0"); + test("a=-(0)", "a=-0.0"); test_same("a=-Infinity"); test("a=-NaN", "a=NaN"); test_same("a=-foo()"); @@ -490,7 +495,7 @@ fn unary_ops() { test("a=+false", "a=0"); test_same("a=+foo()"); test_same("a=+f"); - // test("a=+(f?true:false)", "a=+(f?1:0)"); // TODO(johnlenz): foldable + test("a=+(f?true:false)", "a=+(f?1:0)"); test("a=+0", "a=0"); test("a=+Infinity", "a=Infinity"); test("a=+NaN", "a=NaN"); @@ -507,75 +512,75 @@ fn unary_ops() { fn unary_with_big_int() { test("-(1n)", "-1n"); test("- -1n", "1n"); - test_wcb("!1n", "false"); + test("!1n", "false"); test("~0n", "-1n"); } #[test] fn test_unary_ops_string_compare() { - test_same("a=-1"); - test("a = ~0", "a=-1"); - test("a = ~1", "a=-2"); - test("a = ~101", "a=-102"); + test_same("a = -1"); + test("a = ~0", "a = -1"); + test("a = ~1", "a = -2"); + test("a = ~101", "a = -102"); } #[test] fn test_fold_logical_op() { - test("x = true && x", "x=x"); - test("x = [foo()] && x", "x=([foo()],x)"); - - test("x = false && x", "x=!1"); - test("x = true || x", "x=!0"); - test("x = false || x", "x=x"); - test("x = 0 && x", "x=0"); - test("x = 3 || x", "x=3"); - test("x = 0n && x", "x=0n"); - test("x = 3n || x", "x=3n"); - test("x = false || 0", "x=0"); + test("x = true && x", "x = x"); + test("x = [foo()] && x", "x = ([foo()],x)"); + + test("x = false && x", "x = false"); + test("x = true || x", "x = true"); + test("x = false || x", "x = x"); + test("x = 0 && x", "x = 0"); + test("x = 3 || x", "x = 3"); + test("x = 0n && x", "x = 0n"); + test("x = 3n || x", "x = 3n"); + test("x = false || 0", "x = 0"); // unfoldable, because the right-side may be the result - test("a = x && true", "a=x&&!0"); - test("a = x && false", "a=x&&!1"); - test("a = x || 3", "a=x||3"); - test("a = x || false", "a=x||!1"); - test("a = b ? c : x || false", "a=b?c:x||!1"); - test("a = b ? x || false : c", "a=b?x||!1:c"); - test("a = b ? c : x && true", "a=b?c:x&&!0"); - test("a = b ? x && true : c", "a=b?x&&!0:c"); + test("a = x && true", "a=x && true"); + test("a = x && false", "a=x && false"); + test("a = x || 3", "a=x || 3"); + test("a = x || false", "a=x || false"); + test("a = b ? c : x || false", "a=b ? c:x || false"); + test("a = b ? x || false : c", "a=b ? x || false:c"); + test("a = b ? c : x && true", "a=b ? c:x && true"); + test("a = b ? x && true : c", "a=b ? x && true:c"); // folded, but not here. - test_wcb("a = x || false ? b : c", "a=x||false?b:c"); - test_wcb("a = x && true ? b : c", "a=x&&true?b:c"); - - test("x = foo() || true || bar()", "x=foo()||!0"); - test("x = foo() || true && bar()", "x=foo()||bar()"); - test("x = foo() || false && bar()", "x=foo()||!1"); - test("x = foo() && false && bar()", "x=foo()&&!1"); - test("x = foo() && false || bar()", "x=(foo()&&!1,bar())"); - test("x = foo() || false || bar()", "x=foo()||bar()"); - test("x = foo() && true && bar()", "x=foo()&&bar()"); - test("x = foo() || true || bar()", "x=foo()||!0"); - test("x = foo() && false && bar()", "x=foo()&&!1"); - test("x = foo() && 0 && bar()", "x=foo()&&0"); - test("x = foo() && 1 && bar()", "x=foo()&&bar()"); - test("x = foo() || 0 || bar()", "x=foo()||bar()"); - test("x = foo() || 1 || bar()", "x=foo()||1"); - test("x = foo() && 0n && bar()", "x=foo()&&0n"); - test("x = foo() && 1n && bar()", "x=foo()&&bar()"); - test("x = foo() || 0n || bar()", "x=foo()||bar()"); - test("x = foo() || 1n || bar()", "x=foo()||1n"); - test_same("x=foo()||bar()||baz()"); - test_same("x=foo()&&bar()&&baz()"); + test_same("a = x || false ? b : c"); + test_same("a = x && true ? b : c"); + + test("x = foo() || true || bar()", "x = foo() || true"); + test("x = foo() || true && bar()", "x = foo() || bar()"); + test("x = foo() || false && bar()", "x = foo() || false"); + test("x = foo() && false && bar()", "x = foo() && false"); + test("x = foo() && false || bar()", "x = (foo() && false,bar())"); + test("x = foo() || false || bar()", "x = foo() || bar()"); + test("x = foo() && true && bar()", "x = foo() && bar()"); + test("x = foo() || true || bar()", "x = foo() || true"); + test("x = foo() && false && bar()", "x = foo() && false"); + test("x = foo() && 0 && bar()", "x = foo() && 0"); + test("x = foo() && 1 && bar()", "x = foo() && bar()"); + test("x = foo() || 0 || bar()", "x = foo() || bar()"); + test("x = foo() || 1 || bar()", "x = foo() || 1"); + test("x = foo() && 0n && bar()", "x = foo() && 0n"); + test("x = foo() && 1n && bar()", "x = foo() && bar()"); + test("x = foo() || 0n || bar()", "x = foo() || bar()"); + test("x = foo() || 1n || bar()", "x = foo() || 1n"); + test_same("x = foo() || bar() || baz()"); + test_same("x = foo() && bar() && baz()"); test("0 || b()", "b()"); test("1 && b()", "b()"); - test("a() && (1 && b())", "a()&&b()"); - test("(a() && 1) && b()", "a()&&b()"); + test("a() && (1 && b())", "a() && b()"); + test("(a() && 1) && b()", "a() && b()"); - test("(x || '') || y", "x||y"); - test("false || (x || '')", "x||''"); - test("(x && 1) && y", "x&&y"); - test("true && (x && 1)", "x&&1"); + test("(x || '') || y;", "x || y"); + test("false || (x || '');", "x || ''"); + test("(x && 1) && y;", "x && y"); + test("true && (x && 1);", "x && 1"); // Really not foldable, because it would change the type of the // expression if foo() returns something truthy but not true. @@ -583,8 +588,8 @@ fn test_fold_logical_op() { // An example would be if foo() is 1 (truthy) and bar() is 0 (falsey): // (1 && true) || 0 == true // 1 || 0 == 1, but true =/= 1 - test_wcb("x=foo()&&true||bar()", "x=foo()&&true||bar()"); - test_wcb("foo()&&true||bar()", "foo()&&true||bar()"); + test_same("x = foo() && true || bar()"); + test_same("foo() && true || bar()"); } #[test] diff --git a/crates/oxc_minifier/tests/closure/mod.rs b/crates/oxc_minifier/tests/closure/mod.rs index 3649eaca9f9c2b..08f7ec62bec3ec 100644 --- a/crates/oxc_minifier/tests/closure/mod.rs +++ b/crates/oxc_minifier/tests/closure/mod.rs @@ -1,5 +1,4 @@ mod fold_conditions; -mod fold_constants; -mod printer; +// mod fold_constants; mod reorder_constant_expression; mod substitute_alternate_syntax; diff --git a/crates/oxc_minifier/tests/closure/printer.rs b/crates/oxc_minifier/tests/closure/printer.rs deleted file mode 100644 index 5e328bc2f4318b..00000000000000 --- a/crates/oxc_minifier/tests/closure/printer.rs +++ /dev/null @@ -1,4282 +0,0 @@ -//! - -use crate::{test, test_reparse, test_same}; - -macro_rules! lines { - ($base:expr, $($segment:expr),+) => {&{ - let mut s = String::new(); - $( - s.push_str($segment); - )* - s - }} -} - -#[test] -#[ignore] -fn test_big_int() { - test_same("1n"); - test("0b10n", "2n"); - test("0o3n", "3n"); - test("0x4n", "4n"); - test_same("-5n"); - test("-0b110n", "-6n"); - test("-0o7n", "-7n"); - test("-0x8n", "-8n"); -} - -#[test] -#[ignore] -fn test_trailing_comma_in_array_and_object_with_pretty_print() { - test_same("({a:1, b:2,});\n"); - test_same("[1, 2, 3,];\n"); - // An array starting with a hole is printed ideally but this is very rare. - test_same("[, ];\n"); -} - -#[test] -#[ignore] -fn test_trailing_comma_in_array_and_object_without_pretty_print() { - test("({a:1, b:2,})", "({a:1,b:2})"); - test("[1, 2, 3,]", "[1,2,3]"); - - // When the last element is empty, the trailing comma must be kept. - test_same("[,]"); // same as `[undefined]` - test_same("[a,,]"); // same as `[a, undefined]` -} - -#[test] -#[ignore] -fn test_no_trailing_comma_in_empty_array_literal() { - // In cases where we modify the AST we might wind up with an array literal that has no elements - // yet still has a trailing comma. This is meant to test for that. We need to build the tree - // manually because an array literal with no elements and a trailing comma has a different - // meaning: it represents a single undefined element. - // Node arrLit = IR.arraylit(); - // arrLit.setTrailingComma(true); - // expectNode("[]", arrLit); -} - -#[test] -#[ignore] -fn test_no_trailing_comma_in_empty_object_literal() { - // In cases where we modify the AST we might wind up with an object literal that has no elements - // yet still has a trailing comma. This is meant to test for that. We need to build the tree - // manually because an object literal with no elements and a trailing comma is a syntax error. - // Node objLit = IR.objectlit(); - // objLit.setTrailingComma(true); - // expectNode("{}", objLit); -} - -#[test] -#[ignore] -fn test_no_trailing_comma_in_empty_param_list() { - // In cases where we modify the AST we might wind up with a parameter list that has no elements - // yet still has a trailing comma. This is meant to test for that. We need to build the tree - // manually because a parameter list with no elements and a trailing comma is a syntax error. - // Node paramList = IR.paramList(); - // IR.function(IR.name("f"), paramList, IR.block()); - // paramList.setTrailingComma(true); - // expectNode("()", paramList); -} - -#[test] -#[ignore] -fn test_no_trailing_comma_in_empty_call() { - // In cases where we modify the AST we might wind up with a call node that has no elements - // yet still has a trailing comma. This is meant to test for that. We need to build the tree - // manually because a call node with no elements and a trailing comma is a syntax error. - // Node call = IR.call(IR.name("f")); - // call.setTrailingComma(true); - // expectNode("f()", call); -} - -#[test] -#[ignore] -fn test_no_trailing_comma_in_empty_opt_chain_call() { - // In cases where we modify the AST we might wind up with an optional chain call node that has - // no elements yet still has a trailing comma. This is meant to test for that. We need to build - // the tree manually because an optional chain call node with no elements and a trailing comma - // is a syntax error. - // Node optChainCall = IR.startOptChainCall(IR.name("f")); - // optChainCall.setTrailingComma(true); - // expectNode("f?.()", optChainCall); -} - -#[test] -#[ignore] -fn test_no_trailing_comma_in_empty_new() { - // In cases where we modify the AST we might wind up with a new node that has no elements - // yet still has a trailing comma. This is meant to test for that. We need to build the tree - // manually because a new node with no elements and a trailing comma is a syntax error. - // Node newNode = IR.newNode(IR.name("f")); - // newNode.setTrailingComma(true); - // expectNode("new f()", newNode); -} - -#[test] -#[ignore] -fn test_trailing_comma_in_parameter_list_with_pretty_print() { - test_same("function f(a, b,) {\n}\n"); - test_same("f(1, 2,);\n"); - test_same("f?.(1, 2,);\n"); - test_same("let x = new Number(1,);\n"); -} - -#[test] -#[ignore] -fn test_trailing_comma_in_parameter_list_without_pretty_print() { - test("function f(a, b,) {}", "function f(a,b){}"); - test("f(1, 2,);", "f(1,2)"); - test("f?.(1, 2,);", "f?.(1,2)"); - test("let x = new Number(1,);", "let x=new Number(1)"); -} - -#[test] -#[ignore] -fn opt_chain() { - test_same("a.b?.c"); - test_same("a.b?.[\"c\"]"); - test_same("a.b?.()"); - test_same("a?.b.c?.d"); - test_same("(a?.b).c"); - test_same("(a.b?.c.d).e"); - test_same("(a?.[b])[c]"); - test_same("(a.b?.())()"); -} - -#[test] -#[ignore] -fn test_unescaped_unicode_line_separator_2018() { - test_same("`\\u2028`"); - - test("'\\u2028'", "\"\\u2028\""); - test("\"\\u2028\"", "\"\\u2028\""); - - // printed as a unicode escape for ES_2018 output - test("'\\u2028'", "\"\\u2028\""); - test("\"\\u2028\"", "\"\\u2028\""); -} - -#[test] -#[ignore] -fn test_unescaped_unicode_line_separator_2019() { - test("'\\u2028'", "\"\\u2028\""); - test("\"\\u2028\"", "\"\\u2028\""); - - // left unescaped for ES_2019 out - test("'\\u2028'", "\"\\u2028\""); - test("\"\\u2028\"", "\"\\u2028\""); -} - -#[test] -#[ignore] -fn test_unescaped_unicode_paragraph_separator_2018() { - test_same("`\\u2029`"); - - test("'\\u2029'", "\"\\u2029\""); - test("\"\\u2029\"", "\"\\u2029\""); - - // printed as a unicode escape for ES_2018 output - test("'\\u2029'", "\"\\u2029\""); - test("\"\\u2029\"", "\"\\u2029\""); -} - -#[test] -#[ignore] -fn test_unescaped_unicode_paragraph_separator_2019() { - test("'\\u2029'", "\"\\u2029\""); - test("\"\\u2029\"", "\"\\u2029\""); - - // left unescaped for ES_2019 out - test("'\\u2029'", "\"\\u2029\""); - test("\"\\u2029\"", "\"\\u2029\""); -} - -#[test] -#[ignore] -fn test_optional_catch_block() { - test_same("try{}catch{}"); - test_same("try{}catch{}finally{}"); -} - -#[test] -#[ignore] -fn test_exponentiation_operator() { - test_same("x**y"); - // Exponentiation is right associative - test("x**(y**z)", "x**y**z"); - test_same("(x**y)**z"); - // parens are kept because ExponentiationExpression cannot expand to - // UnaryExpression ** ExponentiationExpression - test_same("(-x)**y"); - // parens are kept because unary operators are higher precedence than '**' - test_same("-(x**y)"); - // parens are not needed for a unary operator on the right operand - test("x**(-y)", "x**-y"); - // NOTE: "-x**y" is a syntax error tested in ParserTest - - // ** has a higher precedence than / - test("x/(y**z)", "x/y**z"); - test_same("(x/y)**z"); -} - -#[test] -#[ignore] -fn test_exponentiation_assignment_operator() { - test_same("x**=y"); -} - -#[test] -#[ignore] -fn test_nullish_coalesce_operator() { - test_same("x??y??z"); - // Nullish coalesce is left associative - test_same("x??(y??z)"); - test("(x??y)??z", "x??y??z"); - // // parens are kept because logical AND and logical OR must be separated from '??' - test_same("(x&&y)??z"); - test_same("(x??y)||z"); - test_same("x??(y||z)"); - // NOTE: "x&&y??z" is a syntax error tested in ParserTest -} - -#[test] -#[ignore] -fn test_nullish_coalesce_operator2() { - // | has higher precedence than ?? - test("(a|b)??c", "a|b??c"); - test_same("(a??b)|c"); - test_same("a|(b??c)"); - test("a??(b|c)", "a??b|c"); - // ?? has higher precedence than : ? (conditional) - test("(a??b)?(c??d):(e??f)", "a??b?c??d:e??f"); - test_same("a??(b?c:d)"); - test_same("(a?b:c)??d"); -} - -#[test] -#[ignore] -fn test_logical_assignment_operator() { - test_same("x||=y"); - test_same("x&&=y"); - test_same("x??=y"); -} - -#[test] -#[ignore] -fn test_object_literal_with_spread() { - test_same("({...{}})"); - test_same("({...x})"); - test_same("({...x,a:1})"); - test_same("({a:1,...x})"); - test_same("({a:1,...x,b:1})"); - test_same("({...x,...y})"); - test_same("({...x,...f()})"); - test_same("({...{...{}}})"); -} - -#[test] -#[ignore] -fn test_object_literal_with_comma() { - test_same("({[(a,b)]:c})"); - test_same("({a:(b,c)})"); - test_same("({[(a,b)]:(c,d)})"); - test_same("({[(a,b)]:c,[d]:(e,f)})"); -} - -#[test] -#[ignore] -fn test_print() { - test("10 + a + b", "10+a+b"); - test("10 + (30*50)", "10+30*50"); - test("with(x) { x + 3; }", "with(x)x+3"); - test("\"aa'a\"", "\"aa'a\""); - test("\"aa\\\"a\"", "'aa\"a'"); - test("function foo()\n{return 10;}", "function foo(){return 10}"); - test("a instanceof b", "a instanceof b"); - test("typeof(a)", "typeof a"); - test( - "var foo = x ? { a : 1 } : {a: 3, b:4, \"default\": 5, \"foo-bar\": 6}", - "var foo=x?{a:1}:{a:3,b:4,\"default\":5,\"foo-bar\":6}", - ); - - // Safari: needs ';' at the end of a throw statement - test("function foo(){throw 'error';}", "function foo(){throw\"error\";}"); - - // The code printer does not eliminate unnecessary blocks. - test("var x = 10; { var y = 20; }", "var x=10;{var y=20}"); - - test("while (x-- > 0);", "while(x-- >0);"); - test("x-- >> 1", "x-- >>1"); - - test("(function () {})(); ", "(function(){})()"); - - // Associativity - test("var a,b,c,d;a || (b&& c) && (a || d)", "var a,b,c,d;a||b&&c&&(a||d)"); - test( - "var a,b,c; a || (b || c); a * (b * c); a | (b | c)", - "var a,b,c;a||(b||c);a*(b*c);a|(b|c)", - ); - test("var a,b,c; a / b / c;a / (b / c); a - (b - c);", "var a,b,c;a/b/c;a/(b/c);a-(b-c)"); - - // Nested assignments - test("var a,b; a = b = 3;", "var a,b;a=b=3"); - test("var a,b,c,d; a = (b = c = (d = 3));", "var a,b,c,d;a=b=c=d=3"); - test("var a,b,c; a += (b = c += 3);", "var a,b,c;a+=b=c+=3"); - test("var a,b,c; a *= (b -= c);", "var a,b,c;a*=b-=c"); - - // Precedence - test("a ? delete b[0] : 3", "a?delete b[0]:3"); - test("(delete a[0])/10", "delete a[0]/10"); - - // optional '()' for new - - // simple new - test("new A", "new A"); - test("new A()", "new A"); - test("new A('x')", "new A(\"x\")"); - - // calling instance method directly after new - test("new A().a()", "(new A).a()"); - test("(new A).a()", "(new A).a()"); - - // this case should be fixed - test("new A('y').a()", "(new A(\"y\")).a()"); - - // internal class - test("new A.B", "new A.B"); - test("new A.B()", "new A.B"); - test("new A.B('z')", "new A.B(\"z\")"); - - // calling instance method directly after new internal class - test("(new A.B).a()", "(new A.B).a()"); - test("new A.B().a()", "(new A.B).a()"); - // this case should be fixed - test("new A.B('w').a()", "(new A.B(\"w\")).a()"); - - // calling new on the result of a call - test_same("new (a())"); - test("new (a())()", "new (a())"); - test_same("new (a.b())"); - test("new (a.b())()", "new (a.b())"); - - // Operators: make sure we don't convert binary + and unary + into ++ - test("x + +y", "x+ +y"); - test("x - (-y)", "x- -y"); - test("x++ +y", "x++ +y"); - test("x-- -y", "x-- -y"); - test("x++ -y", "x++-y"); - - // Label - test("foo:for(;;){break foo;}", "foo:for(;;)break foo"); - test("foo:while(1){continue foo;}", "foo:while(1)continue foo"); - test_same("foo:;"); - test("foo: {}", "foo:;"); - - // Object literals. - test("({})", "({})"); - test("var x = {};", "var x={}"); - test("({}).x", "({}).x"); - test("({})['x']", "({})[\"x\"]"); - test("({}) instanceof Object", "({})instanceof Object"); - test("({}) || 1", "({})||1"); - test("1 || ({})", "1||{}"); - test("({}) ? 1 : 2", "({})?1:2"); - test("0 ? ({}) : 2", "0?{}:2"); - test("0 ? 1 : ({})", "0?1:{}"); - test("typeof ({})", "typeof{}"); - test("f({})", "f({})"); - - // Anonymous function expressions. - test("(function(){})", "(function(){})"); - test("(function(){})()", "(function(){})()"); - test("(function(){})instanceof Object", "(function(){})instanceof Object"); - test("(function(){}).bind().call()", "(function(){}).bind().call()"); - test("var x = function() { };", "var x=function(){}"); - test("var x = function() { }();", "var x=function(){}()"); - test("(function() {}), 2", "(function(){}),2"); - - // Name functions expression. - test("(function f(){})", "(function f(){})"); - - // Function declaration. - test("function f(){}", "function f(){}"); - - // Make sure we don't treat non-Latin character escapes as raw strings. - test("({ 'a': 4, '\\u0100': 4 })", "({\"a\":4,\"\\u0100\":4})"); - test("({ a: 4, '\\u0100': 4 })", "({a:4,\"\\u0100\":4})"); - - // Test if statement and for statements with single statements in body. - test("if (true) { alert();}", "if(true)alert()"); - test("if (false) {} else {alert(\"a\");}", "if(false);else alert(\"a\")"); - test("for(;;) { alert();};", "for(;;)alert()"); - - test("do { alert(); } while(true);", "do alert();while(true)"); - test("myLabel: { alert();}", "myLabel:alert()"); - test("myLabel: for(;;) continue myLabel;", "myLabel:for(;;)continue myLabel"); - - // Test nested var statement - test("if (true) var x; x = 4;", "if(true)var x;x=4"); - - // Non-latin identifier. Make sure we keep them escaped. - test("\\u00fb", "\\u00fb"); - test("\\u00fa=1", "\\u00fa=1"); - test("function \\u00f9(){}", "function \\u00f9(){}"); - test("x.\\u00f8", "x.\\u00f8"); - test("x.\\u00f8", "x.\\u00f8"); - test("abc\\u4e00\\u4e01jkl", "abc\\u4e00\\u4e01jkl"); - - // Test the right-associative unary operators for spurious parens - test("! ! true", "!!true"); - test("!(!(true))", "!!true"); - test("typeof(void(0))", "typeof void 0"); - test("typeof(void(!0))", "typeof void!0"); - test("+ - + + - + 3", "+-+ +-+3"); // chained unary plus/minus - test("+(--x)", "+--x"); - test("-(++x)", "-++x"); - - // needs a space to prevent an ambiguous parse - test("-(--x)", "- --x"); - test("!(~~5)", "!~~5"); - test("~(a/b)", "~(a/b)"); - - // Preserve parens to overcome greedy binding of NEW - test("new (foo.bar()).factory(baz)", "new (foo.bar().factory)(baz)"); - test("new (bar()).factory(baz)", "new (bar().factory)(baz)"); - test("new (new foobar(x)).factory(baz)", "new (new foobar(x)).factory(baz)"); - - // Make sure that HOOK is right associative - test("a ? b : (c ? d : e)", "a?b:c?d:e"); - test("a ? (b ? c : d) : e", "a?b?c:d:e"); - test("(a ? b : c) ? d : e", "(a?b:c)?d:e"); - - // Test nested ifs - test("if (x) if (y); else;", "if(x)if(y);else;"); - - // Test comma. - test("a,b,c", "a,b,c"); - test("(a,b),c", "a,b,c"); - test("a,(b,c)", "a,b,c"); - test("x=a,b,c", "x=a,b,c"); - test("x=(a,b),c", "x=(a,b),c"); - test("x=a,(b,c)", "x=a,b,c"); - test("x=a,y=b,z=c", "x=a,y=b,z=c"); - test("x=(a,y=b,z=c)", "x=(a,y=b,z=c)"); - test("x=[a,b,c,d]", "x=[a,b,c,d]"); - test("x=[(a,b,c),d]", "x=[(a,b,c),d]"); - test("x=[(a,(b,c)),d]", "x=[(a,b,c),d]"); - test("x=[a,(b,c,d)]", "x=[a,(b,c,d)]"); - test("var x=(a,b)", "var x=(a,b)"); - test("var x=a,b,c", "var x=a,b,c"); - test("var x=(a,b),c", "var x=(a,b),c"); - test("var x=a,b=(c,d)", "var x=a,b=(c,d)"); - test("var x=(a,b)(c);", "var x=(a,b)(c)"); - test("var x=(a,b)`c`;", "var x=(a,b)`c`"); - test("foo(a,b,c,d)", "foo(a,b,c,d)"); - test("foo((a,b,c),d)", "foo((a,b,c),d)"); - test("foo((a,(b,c)),d)", "foo((a,b,c),d)"); - test("f(a+b,(c,d,(e,f,g)))", "f(a+b,(c,d,e,f,g))"); - test("({}) , 1 , 2", "({}),1,2"); - test("({}) , {} , {}", "({}),{},{}"); - - test_same("var a=(b=c,d)"); - test_same("var a=(b[c]=d,e)"); - test_same("var a=(b[c]=d,e[f]=g,h)"); - - test("var a = /** @type {?} */ (b=c,d)", "var a=(b=c,d)"); - test("var a = /** @type {?} */ (b[c]=d,e)", "var a=(b[c]=d,e)"); - test("var a = /** @type {?} */ (b[c]=d,e[f]=g,h)", "var a=(b[c]=d,e[f]=g,h)"); - - // EMPTY nodes - test("if (x){}", "if(x);"); - test("if(x);", "if(x);"); - test("if(x)if(y);", "if(x)if(y);"); - test("if(x){if(y);}", "if(x)if(y);"); - test("if(x){if(y){};;;}", "if(x)if(y);"); -} - -#[test] -#[ignore] -fn test_print_new_void() { - // Odd looking but valid. This, of course, will cause a runtime exception but - // should not cause a parse error as "new void 0" would. - test_same("new (void 0)"); -} - -#[test] -#[ignore] -fn test_print_comma1() { - // Node node = IR.var(IR.name("a"), IR.comma(IR.comma(IR.name("b"), IR.name("c")), IR.name("d"))); - // expectNode("var a=(b,c,d)", node); -} - -#[test] -#[ignore] -fn test_print_comma2() { - // Node node = IR.var(IR.name("a"), IR.comma(IR.name("b"), IR.comma(IR.name("c"), IR.name("d")))); - // expectNode("var a=(b,c,d)", node); -} - -#[test] -#[ignore] -fn test_pretty_print_js_doc() { - test_same("/** @type {number} */ \nvar x;\n"); -} - -#[test] -#[ignore] -fn test_print_cast1() { - test("var x = /** @type {number} */ (0);", "var x=0"); - test_same("var x = /** @type {number} */ (0);\n"); -} - -#[test] -#[ignore] -fn test_print_cast2() { - test("var x = (2+3) * 4;", "var x=(2+3)*4"); - test("var x = /** @type {number} */ (2+3) * 4;", "var x=(2+3)*4"); - test_same("var x = (/** @type {number} */ (2 + 3)) * 4;\n"); -} - -#[test] -#[ignore] -fn test_print_cast3() { - test("var x = (2*3) + 4;", "var x=2*3+4"); - test("var x = /** @type {number} */ (2*3) + 4;", "var x=2*3+4"); - test_same("var x = /** @type {number} */ (2 * 3) + 4;\n"); -} - -#[test] -#[ignore] -fn test_let_const_in_if() { - test("if (true) { let x; };", "if(true){let x}"); - test("if (true) { const x = 0; };", "if(true){const x=0}"); -} - -#[test] -#[ignore] -fn test_print_block_scoped_functions() { - // Safari 3 needs a "{" around a single function - test("if (true) function foo(){return}", "if(true){function foo(){return}}"); - test("if(x){;;function y(){};;}", "if(x){function y(){}}"); -} - -#[test] -#[ignore] -fn test_print_array_pattern_var() { - test_same("var []=[]"); - test_same("var [a]=[1]"); - test_same("var [a,b]=[1,2]"); - test_same("var [a,...b]=[1,2]"); - test_same("var [,b]=[1,2]"); - test_same("var [,,,,,,g]=[1,2,3,4,5,6,7]"); - test_same("var [a,,c]=[1,2,3]"); - test_same("var [a,,,d]=[1,2,3,4]"); - test_same("var [a,,c,,e]=[1,2,3,4,5]"); -} - -#[test] -#[ignore] -fn test_print_array_pattern_let() { - test_same("let []=[]"); - test_same("let [a]=[1]"); - test_same("let [a,b]=[1,2]"); - test_same("let [a,...b]=[1,2]"); - test_same("let [,b]=[1,2]"); - test_same("let [,,,,,,g]=[1,2,3,4,5,6,7]"); - test_same("let [a,,c]=[1,2,3]"); - test_same("let [a,,,d]=[1,2,3,4]"); - test_same("let [a,,c,,e]=[1,2,3,4,5]"); -} - -#[test] -#[ignore] -fn test_print_array_pattern_const() { - test_same("const []=[]"); - test_same("const [a]=[1]"); - test_same("const [a,b]=[1,2]"); - test_same("const [a,...b]=[1,2]"); - test_same("const [,b]=[1,2]"); - test_same("const [,,,,,,g]=[1,2,3,4,5,6,7]"); - test_same("const [a,,c]=[1,2,3]"); - test_same("const [a,,,d]=[1,2,3,4]"); - test_same("const [a,,c,,e]=[1,2,3,4,5]"); -} - -#[test] -#[ignore] -fn test_print_array_pattern_assign() { - test_same("[]=[]"); - test_same("[a]=[1]"); - test_same("[a,b]=[1,2]"); - test_same("[a,...b]=[1,2]"); - test_same("[,b]=[1,2]"); - test_same("[,,,,,,g]=[1,2,3,4,5,6,7]"); - test_same("[a,,c]=[1,2,3]"); - test_same("[a,,,d]=[1,2,3,4]"); - test_same("[a,,c,,e]=[1,2,3,4,5]"); -} - -#[test] -#[ignore] -fn test_print_array_pattern_with_initializer() { - test_same("[x=1]=[]"); - test_same("[a,,c=2,,e]=[1,2,3,4,5]"); - test_same("[a=1,b=2,c=3]=foo()"); - test_same("[a=(1,2),b]=foo()"); - test_same("[a=[b=(1,2)]=bar(),c]=foo()"); -} - -#[test] -#[ignore] -fn test_print_nested_array_pattern() { - test_same("var [a,[b,c],d]=[1,[2,3],4]"); - test_same("var [[[[a]]]]=[[[[1]]]]"); - - test_same("[a,[b,c],d]=[1,[2,3],4]"); - test_same("[[[[a]]]]=[[[[1]]]]"); -} - -#[test] -#[ignore] -fn test_pretty_print_array_pattern() { - test("let [a,b,c]=foo();", "let [a, b, c] = foo();\n"); -} - -#[test] -#[ignore] -fn test_print_object_pattern_var() { - test_same("var {a}=foo()"); - test_same("var {a,b}=foo()"); - test_same("var {a:a,b:b}=foo()"); -} - -#[test] -#[ignore] -fn test_print_object_pattern_let() { - test_same("let {a}=foo()"); - test_same("let {a,b}=foo()"); - test_same("let {a:a,b:b}=foo()"); -} - -#[test] -#[ignore] -fn test_print_object_pattern_const() { - test_same("const {a}=foo()"); - test_same("const {a,b}=foo()"); - test_same("const {a:a,b:b}=foo()"); -} - -#[test] -#[ignore] -fn test_print_object_pattern_assign() { - test_same("({a}=foo())"); - test_same("({a,b}=foo())"); - test_same("({a:a,b:b}=foo())"); -} - -#[test] -#[ignore] -fn test_print_nested_object_pattern() { - test_same("({a:{b,c}}=foo())"); - test_same("({a:{b:{c:{d}}}}=foo())"); -} - -#[test] -#[ignore] -fn test_print_object_pattern_initializer() { - test_same("({a=1}=foo())"); - test_same("({a:{b=2}}=foo())"); - test_same("({a:b=2}=foo())"); - test_same("({a,b:{c=2}}=foo())"); - test_same("({a:{b=2},c}=foo())"); - test_same("({a=(1,2),b}=foo())"); - test_same("({a:b=(1,2),c}=foo())"); -} - -#[test] -#[ignore] -fn test_print_object_pattern_with_rest() { - test_same("const {a,...rest}=foo()"); - test_same("var {a,...rest}=foo()"); - test_same("let {a,...rest}=foo()"); - test_same("({a,...rest}=foo())"); - test_same("({a=2,...rest}=foo())"); - test_same("({a:b=2,...rest}=foo())"); -} - -#[test] -#[ignore] -fn test_pretty_print_object_pattern() { - test("const {a,b,c}=foo();", "const {a, b, c} = foo();\n"); -} - -#[test] -#[ignore] -fn test_print_mixed_destructuring() { - test_same("({a:[b,c]}=foo())"); - test_same("[a,{b,c}]=foo()"); -} - -#[test] -#[ignore] -fn test_print_destructuring_in_param_list() { - test_same("function f([a]){}"); - test_same("function f([a,b]){}"); - test_same("function f([a,b]=c()){}"); - test_same("function f([a=(1,2),b=(3,4)]=c()){}"); - test_same("function f({a}){}"); - test_same("function f({a,b}){}"); - test_same("function f({a,b}=c()){}"); - test_same("function f([a,{b,c}]){}"); - test_same("function f({a,b:[c,d]}){}"); -} - -#[test] -#[ignore] -fn test_print_destructuring_in_rest_param() { - test_same("function f(...[a,b]){}"); - test_same("function f(...{length:num_params}){}"); -} - -#[test] -#[ignore] -fn test_destructuring_for_in_loops() { - test_same("for({a}in b)c"); - test_same("for(var {a}in b)c"); - test_same("for(let {a}in b)c"); - test_same("for(const {a}in b)c"); - - test_same("for({a:b}in c)d"); - test_same("for(var {a:b}in c)d"); - test_same("for(let {a:b}in c)d"); - test_same("for(const {a:b}in c)d"); - - test_same("for([a]in b)c"); - test_same("for(var [a]in b)c"); - test_same("for(let [a]in b)c"); - test_same("for(const [a]in b)c"); -} - -#[test] -#[ignore] -fn test_destructuring_for_of_loops1() { - test_same("for({a}of b)c"); - test_same("for(var {a}of b)c"); - test_same("for(let {a}of b)c"); - test_same("for(const {a}of b)c"); - - test_same("for({a:b}of c)d"); - test_same("for(var {a:b}of c)d"); - test_same("for(let {a:b}of c)d"); - test_same("for(const {a:b}of c)d"); - - test_same("for([a]of b)c"); - test_same("for(var [a]of b)c"); - test_same("for(let [a]of b)c"); - test_same("for(const [a]of b)c"); -} - -#[test] -#[ignore] -fn test_destructuring_for_of_loops2() { - // The destructuring 'var' statement is a child of the for-of loop, but - // not the first child. - test_same("for(a of b)var {x}=y"); -} - -#[test] -#[ignore] -fn test_break_trusted_strings() { - // Break scripts - test("''", "\"\\x3c/script>\""); - test("\" \"", "\"\\x3c/script> \\x3c/SCRIPT>\""); - - test("'-->'", "\"--\\x3e\""); - test("']]>'", "\"]]\\x3e\""); - test("' -->'", "\" --\\x3e\\x3c/script>\""); - - test("/--> <\\/script>/g", "/--\\x3e <\\/script>/g"); - - // Break HTML start comments. Certain versions of WebKit - // begin an HTML comment when they see this. - test("''", "\"\\x3c!-- I am a string --\\x3e\""); - - test("'<=&>'", "\"<=&>\""); -} - -#[test] -#[ignore] -fn test_break_untrusted_strings() { - // trustedStrings = false; - - // Break scripts - test("''", "\"\\x3c/script\\x3e\""); - test("\" \"", "\"\\x3c/script\\x3e \\x3c/SCRIPT\\x3e\""); - - test("'-->'", "\"--\\x3e\""); - test("']]>'", "\"]]\\x3e\""); - test("' -->'", "\" --\\x3e\\x3c/script\\x3e\""); - - test("/--> <\\/script>/g", "/--\\x3e <\\/script>/g"); - - // Break HTML start comments. Certain versions of WebKit - // begin an HTML comment when they see this. - test("''", "\"\\x3c!-- I am a string --\\x3e\""); - - test("'<=&>'", "\"\\x3c\\x3d\\x26\\x3e\""); - test("/(?=x)/", "/(?=x)/"); -} - -#[test] -#[ignore] -fn test_html_comments() { - test("3< !(--x)", "3< !--x"); - test("while (x-- > 0) {}", "while(x-- >0);"); -} - -#[test] -#[ignore] -fn test_print_array() { - test("[void 0, void 0]", "[void 0,void 0]"); - test("[undefined, undefined]", "[undefined,undefined]"); - test("[ , , , undefined]", "[,,,undefined]"); - test("[ , , , 0]", "[,,,0]"); -} - -#[test] -#[ignore] -fn test_hook() { - test("a ? b = 1 : c = 2", "a?b=1:c=2"); - test("x = a ? b = 1 : c = 2", "x=a?b=1:c=2"); - test("(x = a) ? b = 1 : c = 2", "(x=a)?b=1:c=2"); - - test("x, a ? b = 1 : c = 2", "x,a?b=1:c=2"); - test("x, (a ? b = 1 : c = 2)", "x,a?b=1:c=2"); - test("(x, a) ? b = 1 : c = 2", "(x,a)?b=1:c=2"); - - test("a ? (x, b) : c = 2", "a?(x,b):c=2"); - test("a ? b = 1 : (x,c)", "a?b=1:(x,c)"); - - test("a ? b = 1 : c = 2 + x", "a?b=1:c=2+x"); - test("(a ? b = 1 : c = 2) + x", "(a?b=1:c=2)+x"); - test("a ? b = 1 : (c = 2) + x", "a?b=1:(c=2)+x"); - - test("a ? (b?1:2) : 3", "a?b?1:2:3"); -} - -#[test] -#[ignore] -fn test_for_in() { - test_same("for(a in b)c"); - test_same("for(var a in b)c"); - test_same("for(var a in b=c)d"); - test_same("for(var a in b,c)d"); -} - -#[test] -#[ignore] -fn test_print_in_operator_in_for_loop() { - // Check for in expression in for's init expression. - // Check alone, with + (higher precedence), with ?: (lower precedence), - // and with conditional. - test( - "var a={}; for (var i = (\"length\" in a); i;) {}", - "var a={};for(var i=(\"length\"in a);i;);", - ); - test( - "var a={}; for (var i = (\"length\" in a) ? 0 : 1; i;) {}", - "var a={};for(var i=(\"length\"in a)?0:1;i;);", - ); - test( - "var a={}; for (var i = (\"length\" in a) + 1; i;) {}", - "var a={};for(var i=(\"length\"in a)+1;i;);", - ); - test( - "var a={};for (var i = (\"length\" in a|| \"size\" in a);;);", - "var a={};for(var i=(\"length\"in a)||(\"size\"in a);;);", - ); - test( - "var a={};for (var i = (a || a) || (\"size\" in a);;);", - "var a={};for(var i=a||a||(\"size\"in a);;);", - ); - - // Test works with unary operators and calls. - test( - "var a={}; for (var i = -(\"length\" in a); i;) {}", - "var a={};for(var i=-(\"length\"in a);i;);", - ); - // expect( - // "var a={};function b_(p){ return p;};" + "for(var i=1,j=b_(\"length\" in a);;) {}", - // "var a={};function b_(p){return p}" + "for(var i=1,j=b_(\"length\"in a);;);", - // ); - - // Test we correctly handle an in operator in the test clause. - test("var a={}; for (;(\"length\" in a);) {}", "var a={};for(;\"length\"in a;);"); - - // Test we correctly handle an in operator inside a comma. - test_same("for(x,(y in z);;)foo()"); - test_same("for(var x,w=(y in z);;)foo()"); - - // And in operator inside a hook. - test_same("for(a=c?0:(0 in d);;)foo()"); - - // And inside an arrow function body - test("var a={}; for(var i = () => (0 in a); i;) {}", "var a={};for(var i=()=>(0 in a);i;);"); - test("var a={}; for(var i = () => ({} in a); i;) {}", "var a={};for(var i=()=>({}in a);i;);"); - - // And inside a destructuring declaration - test( - "var a={}; for(var {noop} = (\"prop\" in a); noop;) {}", - "var a={};for(var {noop}=(\"prop\"in a);noop;);", - ); -} - -#[test] -#[ignore] -fn test_for_of() { - test_same("for(a of b)c"); - test_same("for(var a of b)c"); - test_same("for(var a of b=c)d"); - test_same("for(var a of(b,c))d"); -} - -// In pretty-print mode, make sure there is a space before and after the 'of' in a for/of loop. -#[test] -#[ignore] -fn test_for_of_pretty() { - test_same("for ([x, y] of b) {\n c;\n}\n"); - test_same("for (x of [[1, 2]]) {\n c;\n}\n"); - test_same("for ([x, y] of [[1, 2]]) {\n c;\n}\n"); -} - -#[test] -#[ignore] -fn test_for_await_of() { - test_same("async()=>{for await(a of b)c}"); - test_same("async()=>{for await(var a of b)c}"); - test_same("async()=>{for await(var a of b=c)d}"); - test_same("async()=>{for await(var a of(b,c))d}"); -} - -// In pretty-print mode, make sure there is a space before and after the 'of' in a for/of loop. -#[test] -#[ignore] -fn test_for_await_of_pretty() { - test_same("async() => {\n for await ([x, y] of b) {\n c;\n }\n};\n"); - test_same("async() => {\n for await (x of [[1, 2]]) {\n c;\n }\n};\n"); - test_same("async() => {\n for await ([x, y] of [[1, 2]]) {\n c;\n }\n};\n"); -} - -#[test] -#[ignore] -fn test_let_for() { - test_same("for(let a=0;a<5;a++)b"); - test_same("for(let a in b)c"); - test_same("for(let a of b)c"); - - test_same("async()=>{for await(let a of b)c}"); -} - -#[test] -#[ignore] -fn test_const_for() { - test_same("for(const a=5;b{for await(const a of b)c}"); -} - -#[test] -#[ignore] -fn test_literal_property() { - test("(64).toString()", "(64).toString()"); -} - -// Make sure that the code generator doesn't associate an -// else clause with the wrong if clause. -#[test] -#[ignore] -fn test_ambiguous_else_clauses() { - // expectNode( - // "if(x)if(y);else;", - // new Node( - // Token.IF, - // Node.newString(Token.NAME, "x"), - // new Node( - // Token.BLOCK, - // new Node( - // Token.IF, - // Node.newString(Token.NAME, "y"), - // new Node(Token.BLOCK), - - // // ELSE clause for the inner if - // new Node(Token.BLOCK))))); - - // expectNode( - // "if(x){if(y);}else;", - // new Node( - // Token.IF, - // Node.newString(Token.NAME, "x"), - // new Node( - // Token.BLOCK, - // new Node(Token.IF, Node.newString(Token.NAME, "y"), new Node(Token.BLOCK))), - - // // ELSE clause for the outer if - // new Node(Token.BLOCK))); - - // expectNode( - // "if(x)if(y);else{if(z);}else;", - // new Node( - // Token.IF, - // Node.newString(Token.NAME, "x"), - // new Node( - // Token.BLOCK, - // new Node( - // Token.IF, - // Node.newString(Token.NAME, "y"), - // new Node(Token.BLOCK), - // new Node( - // Token.BLOCK, - // new Node( - // Token.IF, Node.newString(Token.NAME, "z"), new Node(Token.BLOCK))))), - - // // ELSE clause for the outermost if - // new Node(Token.BLOCK))); -} - -#[test] -#[ignore] -fn test_line_break() { - // // line break after function if in a statement context - // assertLineBreak( - // "function a() {}\n" + "function b() {}", "function a(){}\n" + "function b(){}\n"); - - // // line break after ; after a function - // assertLineBreak( - // "var a = {};\n" + "a.foo = function () {}\n" + "function b() {}", - // "var a={};a.foo=function(){};\n" + "function b(){}\n"); - - // // break after comma after a function - // assertLineBreak( - // "var a = {\n" + " b: function() {},\n" + " c: function() {}\n" + "};\n" + "alert(a);", - // "var a={b:function(){},\n" + "c:function(){}};\n" + "alert(a)"); -} - -#[test] -#[ignore] -fn test_pretty_printer() { - // Ensure that the pretty printer inserts line breaks at appropriate - // places. - test("(function(){})();", "(function() {\n})();\n"); - test("var a = (function() {});alert(a);", "var a = function() {\n};\nalert(a);\n"); - - // Check we correctly handle putting brackets around all if clauses so - // we can put breakpoints inside statements. - // expect("if (1) {}", "if (1) {\n" + "}\n"); - // expect("if (1) {alert(\"\");}", "if (1) {\n" + " alert(\"\");\n" + "}\n"); - // expect("if (1)alert(\"\");", "if (1) {\n" + " alert(\"\");\n" + "}\n"); - // expect("if (1) {alert();alert();}", "if (1) {\n" + " alert();\n" + " alert();\n" + "}\n"); - - // Don't add blocks if they weren't there already. - test("label: alert();", "label: alert();\n"); - - // But if statements and loops get blocks automagically. - // expect("if (1) alert();", "if (1) {\n" + " alert();\n" + "}\n"); - // expect("for (;;) alert();", "for (;;) {\n" + " alert();\n" + "}\n"); - - // expect("while (1) alert();", "while (1) {\n" + " alert();\n" + "}\n"); - - // Do we put else clauses in blocks? - // expect("if (1) {} else {alert(a);}", "if (1) {\n" + "} else {\n alert(a);\n}\n"); - - // Do we add blocks to else clauses? - // expect( - // "if (1) alert(a); else alert(b);", - // "if (1) {\n" + " alert(a);\n" + "} else {\n" + " alert(b);\n" + "}\n", - // ); - - // Do we put for bodies in blocks? - // expect("for(;;) { alert();}", "for (;;) {\n" + " alert();\n" + "}\n"); - // expect("for(;;) {}", "for (;;) {\n" + "}\n"); - // expect( - // "for(;;) { alert(); alert(); }", - // "for (;;) {\n" + " alert();\n" + " alert();\n" + "}\n", - // ); - // expect( - // "for(var x=0;x<10;x++) { alert(); alert(); }", - // "for (var x = 0; x < 10; x++) {\n" + " alert();\n" + " alert();\n" + "}\n", - // ); - - // How about do loops? - // expect("do { alert(); } while(true);", "do {\n" + " alert();\n" + "} while (true);\n"); - - // label? - // expect("myLabel: { alert();}", "myLabel: {\n" + " alert();\n" + "}\n"); - test("myLabel: {}", "myLabel: {\n}\n"); - test("myLabel: ;", "myLabel: ;\n"); - - // Don't move the label on a loop, because then break {label} and - // continue {label} won't work. - // expect( - // "myLabel: for(;;) continue myLabel;", - // "myLabel: for (;;) {\n" + " continue myLabel;\n" + "}\n", - // ); - - test("var a;", "var a;\n"); - test("i--", "i--;\n"); - test("i++", "i++;\n"); - - // There must be a space before and after binary operators. - test("var foo = 3+5;", "var foo = 3 + 5;\n"); - - // There should be spaces between the ternary operator - test("var foo = bar ? 3 : null;", "var foo = bar ? 3 : null;\n"); - - // Ensure that string literals after return and throw have a space. - test("function foo() { return \"foo\"; }", "function foo() {\n return \"foo\";\n}\n"); - test("throw \"foo\";", "throw \"foo\";\n"); - - // Test that loops properly have spaces inserted. - test("do{ alert(); } while(true);", "do {\n alert();\n} while (true);\n"); - test("while(true) { alert(); }", "while (true) {\n alert();\n}\n"); -} - -#[test] -#[ignore] -fn test_pretty_printer2() { - // expect("if(true) f();", "if (true) {\n" + " f();\n" + "}\n"); - - // expect( - // "if (true) { f() } else { g() }", - // "if (true) {\n" + " f();\n" + "} else {\n" + " g();\n" + "}\n", - // ); - - // expect( - // "if(true) f(); for(;;) g();", - // "if (true) {\n" + " f();\n" + "}\n" + "for (;;) {\n" + " g();\n" + "}\n", - // ); -} - -#[test] -#[ignore] -fn test_pretty_printer3() { - // expect( - // "try {} catch(e) {}if (1) {alert();alert();}", - // "try {\n" - // + "} catch (e) {\n" - // + "}\n" - // + "if (1) {\n" - // + " alert();\n" - // + " alert();\n" - // + "}\n", - // ); - - // expect( - // "try {} finally {}if (1) {alert();alert();}", - // "try {\n" - // + "} finally {\n" - // + "}\n" - // + "if (1) {\n" - // + " alert();\n" - // + " alert();\n" - // + "}\n", - // ); - - // expect( - // "try {} catch(e) {} finally {} if (1) {alert();alert();}", - // "try {\n" - // + "} catch (e) {\n" - // + "} finally {\n" - // + "}\n" - // + "if (1) {\n" - // + " alert();\n" - // + " alert();\n" - // + "}\n", - // ); -} - -#[test] -#[ignore] -fn test_pretty_printer4() { - // expect( - // "function f() {}if (1) {alert();}", - // "function f() {\n" + "}\n" + "if (1) {\n" + " alert();\n" + "}\n", - // ); - - // expect( - // "var f = function() {};if (1) {alert();}", - // "var f = function() {\n" + "};\n" + "if (1) {\n" + " alert();\n" + "}\n", - // ); - - // expect( - // "(function() {})();if (1) {alert();}", - // "(function() {\n" + "})();\n" + "if (1) {\n" + " alert();\n" + "}\n", - // ); - - // expect( - // "(function() {alert();alert();})();if (1) {alert();}", - // "(function() {\n" - // + " alert();\n" - // + " alert();\n" - // + "})();\n" - // + "if (1) {\n" - // + " alert();\n" - // + "}\n", - // ); -} - -#[test] -#[ignore] -fn test_pretty_printer_arrow() { - test("(a)=>123;", "a => 123;\n"); -} - -#[test] -#[ignore] -fn test_pretty_printer_default_value() { - test("(a=1)=>123;", "(a = 1) => 123;\n"); - test("[a=(1,2)]=[];", "[a = (1, 2)] = [];\n"); -} - -// For https://github.com/google/closure-compiler/issues/782 -#[test] -#[ignore] -fn test_pretty_printer_space_before_single_quote() { - // expect( - // "var f = function() { return 'hello'; };", - // "var f = function() {\n" + " return 'hello';\n" + "};\n", - // new CompilerOptionBuilder() { - // @Override - // void setOptions(CompilerOptions options) { - // options.setPreferSingleQuotes(true); - // } - // }); -} - -// For https://github.com/google/closure-compiler/issues/782 -#[test] -#[ignore] -fn test_pretty_printer_space_before_unary_operators() { - // expect( - // "var f = function() { return !b; };", - // "var f = function() {\n" + " return !b;\n" + "};\n", - // ); - // expect("var f = function*(){yield -b}", "var f = function*() {\n" + " yield -b;\n" + "};\n"); - // expect( - // "var f = function() { return +b; };", - // "var f = function() {\n" + " return +b;\n" + "};\n", - // ); - // expect( - // "var f = function() { throw ~b; };", - // "var f = function() {\n" + " throw ~b;\n" + "};\n", - // ); - // expect( - // "var f = function() { return ++b; };", - // "var f = function() {\n" + " return ++b;\n" + "};\n", - // ); - // expect( - // "var f = function() { return --b; };", - // "var f = function() {\n" + " return --b;\n" + "};\n", - // ); -} - -#[test] -#[ignore] -fn test_pretty_printer_var_let_const() { - test("var x=0;", "var x = 0;\n"); - test("const x=0;", "const x = 0;\n"); - test("let x=0;", "let x = 0;\n"); -} - -#[test] -#[ignore] -fn test_pretty_printer_number() { - test_same("var x = 10;\n"); - test_same("var x = 1.;\n"); - test("var x = 0xFE;", "var x = 254;\n"); - // expect_same("var x = 1" + String.format("%0100d", 0) + ";\n"); // a googol - test_same("f(10000);\n"); - test("var x = -10000;\n", "var x = -10000;\n"); - test("var x = y - -10000;\n", "var x = y - -10000;\n"); - test("f(-10000);\n", "f(-10000);\n"); - test_same("x < 2592000;\n"); - test_same("x < 1000.000;\n"); - test_same("x < 1000.912;\n"); - test_same("var x = 1E20;\n"); - test_same("var x = 1E1;\n"); - test_same("var x = void 0;\n"); - test_same("foo(-0);\n"); - test("var x = 4-1000;", "var x = 4 - 1000;\n"); -} - -#[test] -#[ignore] -fn test_type_annotations() { - // assertTypeAnnotations( - // "/** @constructor */ function Foo(){}", - // "/**\n * @constructor\n */\nfunction Foo() {\n}\n", - // ); -} - -#[test] -#[ignore] -fn test_non_null_types() { - // assertTypeAnnotations( - // lines!( - // "/** @constructor */", - // "function Foo() {}", - // "/** @return {!Foo} */", - // "Foo.prototype.f = function() { return new Foo; };" - // ), - // lines!( - // "/**", - // " * @constructor", - // " */", - // "function Foo() {\n}", - // "/**", - // " * @return {!Foo}", - // " */", - // "Foo.prototype.f = function() {", - // " return new Foo();", - // "};\n" - // ), - // ); -} - -#[test] -#[ignore] -fn test_type_annotations_type_def() { - // TODO(johnlenz): It would be nice if there were some way to preserve - // typedefs but currently they are resolved into the basic types in the - // type registry. - // assertTypeAnnotations( - // lines!( - // "/** @const */ var goog = {};", - // "/** @const */ goog.java = {};", - // "/** @typedef {Array} */ goog.java.Long;", - // "/** @param {!goog.java.Long} a*/", - // "function f(a){};" - // ), - // lines!( - // "/** @const */ var goog = {};", - // "/** @const */ goog.java = {};", - // "goog.java.Long;", - // "/**", - // " * @param {!Array} a", - // " * @return {undefined}", - // " */", - // "function f(a) {\n}\n" - // ), - // ); -} - -#[test] -#[ignore] -fn test_type_annotations_assign() { - // assertTypeAnnotations( - // "/** @constructor */ var Foo = function(){}", - // lines!("/**\n * @constructor\n */", "var Foo = function() {\n};\n"), - // ); -} - -#[test] -#[ignore] -fn test_type_annotations_namespace_var_without_js_doc() { - // assertTypeAnnotations( - // lines!( - // "var a = {};", // - // "/** @constructor */ a.Foo = function(){}" - // ), - // lines!( - // "var a = {};", // - // "/**", - // " * @constructor", - // " */", - // "a.Foo = function() {", - // "};\n" - // ), - // ); -} - -#[test] -#[ignore] -fn test_type_annotations_namespace_var_with_const_js_doc() { - // assertTypeAnnotations( - // lines!( - // "/** @const */", // - // "var a = {};", - // "/** @constructor */ a.Foo = function(){}" - // ), - // lines!( - // "/** @const */ var a = {};", - // "/**", - // " * @constructor", - // " */", - // "a.Foo = function() {", - // "};\n" - // ), - // ); -} - -#[test] -#[ignore] -fn test_type_annotations_namespace_const_declaration_without_js_doc() { - // assertTypeAnnotations( - // lines!( - // "const a = {};", // - // "/** @constructor */ a.Foo = function(){}" - // ), - // lines!( - // "const a = {};", // - // "/**", - // " * @constructor", - // " */", - // "a.Foo = function() {", - // "};\n" - // ), - // ); -} - -#[test] -#[ignore] -fn test_type_annotations_namespace_const_declaration_with_js_doc() { - // assertTypeAnnotations( - // lines!( - // "/** @export */", - // "const a = {};", // - // "/** @constructor */ a.Foo = function(){}" - // ), - // lines!( - // "/** @export */ const a = {};", // - // "/**", - // " * @constructor", - // " */", - // "a.Foo = function() {", - // "};\n" - // ), - // ); -} - -#[test] -#[ignore] -fn test_type_annotations_namespace_qname_with_const_js_doc() { - // assertTypeAnnotations( - // lines!( - // "/** @const */", - // "var a = {};", - // "/** @const */", - // "a.b = {};", - // "/** @constructor */ a.b.Foo = function(){}" - // ), - // lines!( - // "/** @const */ var a = {};", - // "/** @const */ a.b = {};", - // "/**", - // " * @constructor", - // " */", - // "a.b.Foo = function() {", - // "};\n" - // ), - // ); -} - -#[test] -#[ignore] -fn test_type_annotations_member_subclass() { - // assertTypeAnnotations( - // lines!( - // "/** @const */ var a = {};", - // "/** @constructor */ a.Foo = function(){};", - // "/** @constructor \n @extends {a.Foo} */ a.Bar = function(){}" - // ), - // lines!( - // "/** @const */ var a = {};", - // "/**\n * @constructor\n */", - // "a.Foo = function() {\n};", - // "/**\n * @extends {a.Foo}", - // " * @constructor\n */", - // "a.Bar = function() {\n};\n" - // ), - // ); -} - -#[test] -#[ignore] -fn test_type_annotations_interface() { - // assertTypeAnnotations( - // lines!( - // "/** @const */ var a = {};", - // "/** @interface */ a.Foo = function(){};", - // "/** @interface \n @extends {a.Foo} */ a.Bar = function(){}" - // ), - // lines!( - // "/** @const */ var a = {};", - // "/**\n * @interface\n */", - // "a.Foo = function() {\n};", - // "/**\n * @extends {a.Foo}", - // " * @interface\n */", - // "a.Bar = function() {\n};\n" - // ), - // ); -} - -#[test] -#[ignore] -fn test_type_annotations_multiple_interface() { - // assertTypeAnnotations( - // lines!( - // "/** @const */ var a = {};", - // "/** @interface */ a.Foo1 = function(){};", - // "/** @interface */ a.Foo2 = function(){};", - // "/** @interface \n @extends {a.Foo1} \n @extends {a.Foo2} */", - // "a.Bar = function(){}" - // ), - // lines!( - // "/** @const */ var a = {};", - // "/**\n * @interface\n */", - // "a.Foo1 = function() {\n};", - // "/**\n * @interface\n */", - // "a.Foo2 = function() {\n};", - // "/**\n * @extends {a.Foo1}", - // " * @extends {a.Foo2}", - // " * @interface\n */", - // "a.Bar = function() {\n};\n" - // ), - // ); -} - -#[test] -#[ignore] -fn test_type_annotations_member() { - // assertTypeAnnotations( - // lines!( - // "var a = {};", - // "/** @constructor */ a.Foo = function(){}", - // "/** @param {string} foo", - // " * @return {number} */", - // "a.Foo.prototype.foo = function(foo) { return 3; };", - // "/** @type {!Array|undefined} */", - // "a.Foo.prototype.bar = [];" - // ), - // lines!( - // "var a = {};", - // "/**\n * @constructor\n */", - // "a.Foo = function() {\n};", - // "/**", - // " * @param {string} foo", - // " * @return {number}", - // " */", - // "a.Foo.prototype.foo = function(foo) {\n return 3;\n};", - // "/** @type {!Array} */", - // "a.Foo.prototype.bar = [];\n" - // ), - // ); -} - -#[test] -#[ignore] -fn test_type_annotations_member_stub() { - // TODO(blickly): Investigate why the method's type isn't preserved. - // assertTypeAnnotations( - // lines!( - // "/** @interface */ function I(){};", - // "/** @return {undefined} @param {number} x */ I.prototype.method;" - // ), - // "/**\n * @interface\n */\nfunction I() {\n}\nI.prototype.method;\n", - // ); -} - -#[test] -#[ignore] -fn test_type_annotations_implements() { - // assertTypeAnnotations( - // lines!( - // "/** @const */ var a = {};", - // "/** @constructor */ a.Foo = function(){};", - // "/** @interface */ a.I = function(){};", - // "/** @record */ a.I2 = function(){};", - // "/** @record @extends {a.I2} */ a.I3 = function(){};", - // "/** @constructor \n @extends {a.Foo}", - // " * @implements {a.I} \n @implements {a.I2}", - // " */ a.Bar = function(){}" - // ), - // lines!( - // "/** @const */ var a = {};", - // "/**\n * @constructor\n */", - // "a.Foo = function() {\n};", - // "/**\n * @interface\n */", - // "a.I = function() {\n};", - // "/**\n * @record\n */", - // "a.I2 = function() {\n};", - // "/**\n * @extends {a.I2}", - // " * @record\n */", - // "a.I3 = function() {\n};", - // "/**\n * @extends {a.Foo}", - // " * @implements {a.I}", - // " * @implements {a.I2}", - // " * @constructor\n */", - // "a.Bar = function() {\n};\n" - // ), - // ); -} - -#[test] -#[ignore] -fn test_type_annotation_class_implements() { - // assertTypeAnnotations( - // lines!( - // "/** @interface */ class Foo {}", // - // "/** @implements {Foo} */ class Bar {}" - // ), - // lines!( - // "/**\n * @interface\n */", - // "class Foo {\n}", - // "/**\n * @implements {Foo}\n */", - // "class Bar {\n}\n" - // ), - // ); -} - -#[test] -#[ignore] -fn test_type_annotation_class_member() { - // assertTypeAnnotations( - // lines!( - // "class Foo {", // - // " /** @return {number} */ method(/** string */ arg) {}", - // "}" - // ), - // lines!( - // "class Foo {", - // " /**", - // " * @param {string} arg", - // " * @return {number}", - // " */", - // " method(arg) {", - // " }", - // "}", - // "" - // ), - // ); -} - -#[test] -#[ignore] -fn test_type_annotation_class_constructor() { - // assertTypeAnnotations( - // lines!( - // "/**", - // " * @template T", - // " */", - // "class Foo {", // - // " /** @param {T} arg */", - // " constructor(arg) {}", - // "}" - // ), - // lines!( - // "/**", - // " * @template T", - // " */", - // "class Foo {", - // " /**", - // " * @param {T} arg", - // " */", - // " constructor(arg) {", - // " }", - // "}", - // "" - // ), - // ); -} - -#[test] -#[ignore] -fn test_rest_parameter() { - // assertTypeAnnotations( - // lines!( - // "/** @param {...string} args */", // - // "function f(...args) {}" - // ), - // lines!( - // "/**\n * @param {...string} args\n * @return {undefined}\n */", - // "function f(...args) {\n}\n" - // ), - // ); -} - -#[test] -#[ignore] -fn test_default_parameter() { - // assertTypeAnnotations( - // lines!( - // "/** @param {string=} msg */", // - // "function f(msg = 'hi') {}" - // ), - // lines!( - // "/**\n * @param {string=} msg\n * @return {undefined}\n */", - // "function f(msg = \"hi\") {\n}\n" - // ), - // ); -} - -#[test] -#[ignore] -fn test_object_destructuring_parameter() { - // assertTypeAnnotations( - // lines!( - // "/** @param {{a: number, b: number}} ignoredName */", // - // "function f({a, b}) {}" - // ), - // lines!( - // "/**", - // " * @param {{a: number, b: number}} p0", // old JSDoc name is ignored - // " * @return {undefined}", - // " */", - // "function f({a, b}) {", // whitespace in output must match - // "}", - // "" - // ), - // ); -} - -#[test] -#[ignore] -fn test_object_destructuring_parameter_with_default() { - // assertTypeAnnotations( - // lines!( - // "/** @param {{a: number, b: number}=} ignoredName */", // - // "function f({a, b} = {a: 1, b: 2}) {}" - // ), - // lines!( - // "/**", - // " * @param {{a: number, b: number}=} p0", // old JSDoc name is ignored - // " * @return {undefined}", - // " */", - // "function f({a, b} = {a:1, b:2}) {", // whitespace in output must match - // "}", - // "" - // ), - // ); -} - -#[test] -#[ignore] -fn test_array_destructuring_parameter() { - // assertTypeAnnotations( - // lines!( - // "/** @param {!Iterable} ignoredName */", // - // "function f([a, b]) {}" - // ), - // lines!( - // "/**", - // " * @param {!Iterable} p0", // old JSDoc name is ignored - // " * @return {undefined}", - // " */", - // "function f([a, b]) {", // whitespace in output must match - // "}", - // "" - // ), - // ); -} - -#[test] -#[ignore] -fn test_array_destructuring_parameter_with_default() { - // assertTypeAnnotations( - // lines!( - // "/** @param {!Iterable=} ignoredName */", // - // "function f([a, b] = [1, 2]) {}" - // ), - // lines!( - // "/**", - // " * @param {!Iterable=} p0", // old JSDoc name is ignored - // " * @return {undefined}", - // " */", - // "function f([a, b] = [1, 2]) {", // whitespace in output must match - // "}", - // "" - // ), - // ); -} - -#[test] -#[ignore] -fn test_u2_u_function_type_annotation1() { - // assertTypeAnnotations( - // "/** @type {!Function} */ var x = function() {}", - // "/** @type {!Function} */\nvar x = function() {\n};\n", - // ); -} - -#[test] -#[ignore] -fn test_u2_u_function_type_annotation2() { - // TODO(johnlenz): we currently report the type of the RHS which is not - // correct, we should export the type of the LHS. - // assertTypeAnnotations( - // "/** @type {Function} */ var x = function() {}", - // "/** @type {!Function} */\nvar x = function() {\n};\n", - // ); -} - -#[test] -#[ignore] -fn test_emit_unknown_param_types_as_all_type() { - // x is unused, so NTI infers that x can be omitted. - // assertTypeAnnotations( - // "var a = function(x) {}", - // lines!( - // "/**", - // " * @param {?} x", - // " * @return {undefined}", - // " */", - // "var a = function(x) {\n};\n" - // ), - // ); -} - -#[test] -#[ignore] -fn test_optional_types_annotation() { - // assertTypeAnnotations( - // "/** @param {string=} x */ var a = function(x) {}", - // lines!( - // "/**", - // " * @param {string=} x", - // " * @return {undefined}", - // " */", - // "var a = function(x) {\n};\n" - // ), - // ); -} - -#[test] -#[ignore] -fn test_optional_types_annotation2() { - // assertTypeAnnotations( - // "/** @param {undefined=} x */ var a = function(x) {}", - // lines!( - // "/**", - // " * @param {undefined=} x", - // " * @return {undefined}", - // " */", - // "var a = function(x) {\n};\n" - // ), - // ); -} - -#[test] -#[ignore] -fn test_variable_arguments_types_annotation() { - // assertTypeAnnotations( - // "/** @param {...string} x */ var a = function(x) {}", - // lines!( - // "/**", - // " * @param {...string} x", - // " * @return {undefined}", - // " */", - // "var a = function(x) {\n};\n" - // ), - // ); -} - -#[test] -#[ignore] -fn test_temp_constructor() { - // assertTypeAnnotations( - // lines!( - // "var x = function() {", - // " /** @constructor */ function t1() {}", - // " /** @constructor */ function t2() {}", - // " t1.prototype = t2.prototype", - // "}" - // ), - // lines!( - // "/**", - // " * @return {undefined}", - // " */", - // "var x = function() {", - // " /**", - // " * @constructor", - // " */", - // " function t1() {", - // " }", - // " /**", - // " * @constructor", - // " */", - // " function t2() {", - // " }", - // " t1.prototype = t2.prototype;", - // "};", - // "" - // ), - // ); -} - -#[test] -#[ignore] -fn test_enum_annotation1() { - // assertTypeAnnotations( - // "/** @enum {string} */ const Enum = {FOO: 'x', BAR: 'y'};", - // "/** @enum {string} */\nconst Enum = {FOO:\"x\", BAR:\"y\"};\n", - // ); -} - -#[test] -#[ignore] -fn test_enum_annotation2() { - // assertTypeAnnotations( - // lines!( - // "/** @const */ var goog = goog || {};", - // "/** @enum {string} */ goog.Enum = {FOO: 'x', BAR: 'y'};", - // "/** @const */ goog.Enum2 = goog.x ? {} : goog.Enum;" - // ), - // lines!( - // "/** @const */ var goog = goog || {};", - // "/** @enum {string} */\ngoog.Enum = {FOO:\"x\", BAR:\"y\"};", - // "/** @type {(!Object|{})} */\ngoog.Enum2 = goog.x ? {} : goog.Enum;\n" - // ), - // ); -} - -#[test] -#[ignore] -fn test_enum_annotation3() { - // assertTypeAnnotations( - // "/** @enum {!Object} */ var Enum = {FOO: {}};", - // "/** @enum {!Object} */\nvar Enum = {FOO:{}};\n", - // ); -} - -#[test] -#[ignore] -fn test_enum_annotation4() { - // assertTypeAnnotations( - // lines!( - // "/** @enum {number} */ var E = {A:1, B:2};", - // "function f(/** !E */ x) { return x; }" - // ), - // lines!( - // "/** @enum {number} */", - // "var E = {A:1, B:2};", - // "/**", - // " * @param {number} x", - // " * @return {?}", - // " */", - // "function f(x) {", - // " return x;", - // "}", - // "" - // ), - // ); -} - -#[test] -#[ignore] -fn test_closure_library_type_annotation_examples() { - // assertTypeAnnotations( - // lines!( - // "/** @const */ var goog = goog || {};", - // "/** @param {Object} obj */goog.removeUid = function(obj) {};", - // "/** @param {Object} obj The object to remove the field from. */", - // "goog.removeHashCode = goog.removeUid;" - // ), - // lines!( - // "/** @const */ var goog = goog || {};", - // "/**", - // " * @param {(Object|null)} obj", - // " * @return {undefined}", - // " */", - // "goog.removeUid = function(obj) {", - // "};", - // "/**", - // " * @param {(Object|null)} p0", - // " * @return {undefined}", - // " */", - // "goog.removeHashCode = goog.removeUid;\n" - // ), - // ); -} - -#[test] -#[ignore] -fn test_function_type_annotation() { - // assertTypeAnnotations( - // "/**\n * @param {{foo:number}} arg\n */\nfunction f(arg) {}", - // "/**\n * @param {{foo: number}} arg\n * @return {undefined}\n */\nfunction f(arg) {\n}\n", - // ); - // assertTypeAnnotations( - // "/**\n * @param {number} arg\n */\nfunction f(arg) {}", - // "/**\n * @param {number} arg\n * @return {undefined}\n */\nfunction f(arg) {\n}\n", - // ); - // assertTypeAnnotations( - // "/**\n * @param {!Array} arg\n */\nfunction f(arg) {}", - // "/**\n * @param {!Array} arg\n * @return {undefined}\n */\nfunction f(arg) {\n}\n", - // ); -} - -#[test] -#[ignore] -fn test_function_with_this_type_annotation() { - // assertTypeAnnotations( - // "/**\n * @this {{foo:number}}\n */\nfunction foo() {}", - // "/**\n * @return {undefined}\n * @this {{foo: number}}\n */\nfunction foo() {\n}\n", - // ); - // assertTypeAnnotations( - // "/**\n * @this {!Array}\n */\nfunction foo() {}", - // "/**\n * @return {undefined}\n * @this {!Array}\n */\nfunction foo() {\n}\n", - // ); -} - -#[test] -#[ignore] -fn test_return_with_type_annotation() { - // preserveTypeAnnotations = true; - test( - "function f() { return (/** @return {number} */ function() { return 42; }); }", - lines!( - "function f() {", - " return (/**", - " * @return {number}", - " */", - " function() {", - " return 42;", - " });", - "}", - "" - ), - ); -} - -#[test] -#[ignore] -fn test_deprecated_annotation_includes_newline() { - // String js = - // lines!( - // "/**", - // " * @type {number}", - // " * @deprecated See {@link replacementClass} for more details.", - // " */", - // "var x;", - // ""); - - // expect(js, js); -} - -#[test] -#[ignore] -fn test_non_js_doc_comments_printed_non_trailing_block_comment() { - // preserveNonJSDocComments = true; - test("/* test_comment */ function Foo(){}", "/* testComment */ function Foo() {\n}\n"); -} - -#[test] -#[ignore] -fn test_non_js_doc_comments_printed_end_of_file_line_comment() { - // preserveNonJSDocComments = true; - test( - lines!( - "function f1() {}", // - "if (true) {", - "// first", - "f1();", - "}", - "// second" - ), - lines!( - "function f1() {\n}", // - "if (true) {", - " // first", - " f1();", - "}", - " // second\n" - ), - ); -} - -#[test] -#[ignore] -fn test_non_js_doc_comments_printed_end_of_block_comment() { - // preserveNonJSDocComments = true; - test( - lines!( - "function f1() {}", // - "if (true) {", - "// first", - "f1();", - "/* second */", - "}" - ), - lines!( - "function f1() {\n}", // - "if (true) {", - " // first", - " f1(); ", - " /* second */", - "}\n" - ), - ); -} - -#[test] -#[ignore] -fn test_non_js_doc_comments_printed_end_of_block_many_mixed_comments() { - // preserveNonJSDocComments = true; - test( - lines!( - "function f1() {}", // - "if (true) {", - "// first", - "f1();", - "// second", - "/* third */", - "// fourth", - "}" - ), - lines!( - "function f1() {\n}", // - "if (true) {", - " // first", - " f1(); ", - " // second", - " /* third */", - " // fourth", - "}\n" - ), - ); -} - -#[test] -#[ignore] -fn test_non_js_doc_comments_printed_last_trailing() { - // preserveNonJSDocComments = true; - test( - lines!( - "function f1() {}", // - "if (true) {", - "// first", - "f1(); // second ", - "}" - ), - lines!( - "function f1() {\n}", // - "if (true) {", - " // first", - " f1(); // second", - "}\n" - ), - ); -} - -#[test] -#[ignore] -fn test_non_js_doc_comments_printed_non_trailing_line_comment() { - // preserveNonJSDocComments = true; - test("// test_comment\nfunction Foo(){}", "// testComment\nfunction Foo() {\n}\n"); -} - -#[test] -#[ignore] -fn test_non_js_doc_comments_printed_between_code_same_line() { - // preserveNonJSDocComments = true; - - test("function /* test_comment */ Foo(){}", "function/* testComment */ Foo() {\n}\n"); -} - -#[test] -#[ignore] -fn test_non_js_doc_comments_printed_between_code_differentlines() { - // preserveNonJSDocComments = true; - test("function /* test_comment */\nFoo(){}", "function/* testComment */\nFoo() {\n}\n"); -} - -#[test] -#[ignore] -fn test_non_js_doc_comments_printed_non_trailing_inline_comments() { - // preserveNonJSDocComments = true; - // tests inline comments in parameter lists are parsed and printed - test( - "function Foo(/*first*/ x, /* second*/ y) {}", - "function Foo(/*first*/ x, /* second*/ y) {\n}\n", - ); -} - -// Args on new line are condensed onto the same line by prettyPrint -#[test] -#[ignore] -fn test_args_no_comments_newlines() { - // expect( - // lines!(" var rpcid = new RpcId(a,\n b, \nc);"), - // lines!("var rpcid = new RpcId(a, b, c);\n"), - // ); -} - -// Comments are printed when args on new line are condensed onto the same line by prettyPrint -#[test] -#[ignore] -fn test_non_js_doc_comments_printed_non_trailing_inline_comments_newlines() { - // preserveNonJSDocComments = true; - // expect( - // lines!(" var rpcid = new RpcId(a,\n /* comment1 */ b, \n/* comment1 */ c);"), - // lines!("var rpcid = new RpcId(a, /* comment1 */ b, /* comment1 */ c);\n"), - // ); -} - -#[test] -#[ignore] -fn test_non_js_doc_comments_printed_trailing_and_non_trailing_inline_comments() { - // preserveNonJSDocComments = true; - // tests inline trailing comments in parameter lists are parsed and printed - test( - "function Foo(x //first\n, /* second*/ y) {}", - "function Foo(x //first\n, /* second*/ y) {\n}\n", - ); -} - -#[test] -#[ignore] -fn test_non_js_doc_comments_printed_trailing_inline_comments_param_list() { - // preserveNonJSDocComments = true; - test("function Foo(x) {}", "function Foo(x) {\n}\n"); - test("function Foo(x /*first*/) {}", "function Foo(x /*first*/) {\n}\n"); - test("function Foo(x //first\n) {}", "function Foo(x //first\n) {\n}\n"); -} - -#[test] -#[ignore] -fn test_class_extends_left_hand_side_expression() { - test("class A {} class B extends (0, A) {}", "class A {\n}\nclass B extends(0, A) {\n}\n"); -} - -// Same as above, but tests argList instead of Param list -#[test] -#[ignore] -fn test_non_js_doc_comments_printed_trailing_inline_comments_call_arg_list() { - test("foo(x);", "foo(x);\n"); - test("foo(x /*first*/);", "foo(x /*first*/);\n"); - test("foo(x //first\n);", "foo(x //first\n);\n"); -} - -#[test] -#[ignore] -fn test_subtraction() { - // Compiler compiler = new Compiler(); - // Node n = compiler.parse_test_code("x - -4"); - // assertThat(compiler.getErrorCount()).isEqualTo(0); - - // assertThat(printNode(n)).isEqualTo("x- -4"); -} - -#[test] -#[ignore] -fn test_function_with_call() { - // expect( - // "var user = new function() {" + "alert(\"foo\")}", - // "var user=new function(){" + "alert(\"foo\")}", - // ); - // expect( - // "var user = new function() {" - // + "this.name = \"foo\";" - // + "this.local = function(){alert(this.name)};}", - // "var user=new function(){" - // + "this.name=\"foo\";" - // + "this.local=function(){alert(this.name)}}", - // ); -} - -#[test] -#[ignore] -fn test_line_length() { - // list - // assertLineLength("var aba,bcb,cdc", "var aba,bcb," + "\ncdc"); - - // // operators, and two breaks - // assertLineLength( - // "\"foo\"+\"bar,baz,bomb\"+\"whee\"+\";long-string\"\n+\"aaa\"", - // "\"foo\"+\"bar,baz,bomb\"+" + "\n\"whee\"+\";long-string\"+" + "\n\"aaa\""); - - // // assignment - // assertLineLength("var abazaba=1234", "var abazaba=" + "\n1234"); - - // // statements - // assertLineLength("var abab=1;var bab=2", "var abab=1;" + "\nvar bab=2"); - - // // don't break regexes - // assertLineLength( - // "var a=/some[reg](ex),with.*we?rd|chars/i;var b=a", - // "var a=/some[reg](ex),with.*we?rd|chars/i;" + "\nvar b=a"); - - // // don't break strings - // assertLineLength("var a=\"foo,{bar};baz\";var b=a", "var a=\"foo,{bar};baz\";" + "\nvar b=a"); - - // // don't break before post inc/dec - // assertLineLength("var a=\"a\";a++;var b=\"bbb\";", "var a=\"a\";a++;\n" + "var b=\"bbb\""); -} - -#[test] -#[ignore] -fn test_parse_print_parse() { - test_reparse("3;"); - test_reparse("var a = b;"); - test_reparse("var x, y, z;"); - test_reparse("try { foo() } catch(e) { bar() }"); - test_reparse("try { foo() } catch(e) { bar() } finally { stuff() }"); - test_reparse("try { foo() } finally { stuff() }"); - test_reparse("throw 'me'"); - test_reparse("function foo(a) { return a + 4; }"); - test_reparse("function foo() { return; }"); - test_reparse("var a = function(a, b) { foo(); return a + b; }"); - test_reparse("b = [3, 4, 'paul', \"Buchhe it\",,5];"); - test_reparse("v = (5, 6, 7, 8)"); - test_reparse("d = 34.0; x = 0; y = .3; z = -22"); - test_reparse("d = -x; t = !x + ~y;"); - // expect_reparse( - // "'hi'; /* just a test */ stuff(a,b) \n" + " foo(); // and another \n" + " bar();", - // ); - test_reparse("a = b++ + ++c; a = b++-++c; a = - --b; a = - ++b;"); - test_reparse("a++; b= a++; b = ++a; b = a--; b = --a; a+=2; b-=5"); - test_reparse("a = (2 + 3) * 4;"); - test_reparse("a = 1 + (2 + 3) + 4;"); - test_reparse("x = a ? b : c; x = a ? (b,3,5) : (foo(),bar());"); - // expect_reparse("a = b | c || d ^ e " + "&& f & !g != h << i <= j < k >>> l > m * n % !o"); - // expect_reparse("a == b; a != b; a === b; a == b == a;" + " (a == b) == a; a == (b == a);"); - test_reparse("if (a > b) a = b; if (b < 3) a = 3; else c = 4;"); - test_reparse("if (a == b) { a++; } if (a == 0) { a++; } else { a --; }"); - test_reparse("for (var i in a) b += i;"); - // expect_reparse("for (var i = 0; i < 10; i++){ b /= 2;" + " if (b == 2)break;else continue;}"); - test_reparse("for (x = 0; x < 10; x++) a /= 2;"); - test_reparse("for (;;) a++;"); - test_reparse("while(true) { blah(); }while(true) blah();"); - test_reparse("do stuff(); while(a>b);"); - test_reparse("[0, null, , true, false, this];"); - test_reparse("s.replace(/absc/, 'X').replace(/ab/gi, 'Y');"); - test_reparse("new Foo; new Bar(a, b,c);"); - test_reparse("with(foo()) { x = z; y = t; } with(bar()) a = z;"); - test_reparse("delete foo['bar']; delete foo;"); - test_reparse("var x = { 'a':'paul', 1:'3', 2:(3,4) };"); - // expect_reparse( - // "switch(a) { case 2: case 3: stuff(); break;" - // + "case 4: morestuff(); break; default: done();}", - // ); - test_reparse("x = foo['bar'] + foo['my stuff'] + foo[bar] + f.stuff;"); - test_reparse("a.v = b.v; x['foo'] = y['zoo'];"); - test_reparse("'test' in x; 3 in x; a in x;"); - test_reparse("'foo\"bar' + \"foo'c\" + 'stuff\\n and \\\\more'"); - test_reparse("x.__proto__;"); -} - -#[test] -#[ignore] -fn test_do_loop_ie_compatibility() { - // Do loops within IFs cause syntax errors in IE6 and IE7. - test( - "function f(){if(e1){do foo();while(e2)}else foo()}", - "function f(){if(e1){do foo();while(e2)}else foo()}", - ); - - test( - "function f(){if(e1)do foo();while(e2)else foo()}", - "function f(){if(e1){do foo();while(e2)}else foo()}", - ); - - test("if(x){do{foo()}while(y)}else bar()", "if(x){do foo();while(y)}else bar()"); - - test("if(x)do{foo()}while(y);else bar()", "if(x){do foo();while(y)}else bar()"); - - test("if(x){do{foo()}while(y)}", "if(x){do foo();while(y)}"); - - test("if(x)do{foo()}while(y);", "if(x){do foo();while(y)}"); - - test("if(x)A:do{foo()}while(y);", "if(x){A:do foo();while(y)}"); - - test( - "var i = 0;a: do{b: do{i++;break b;} while(0);} while(0);", - "var i=0;a:do{b:do{i++;break b}while(0)}while(0)", - ); -} - -#[test] -#[ignore] -fn test_function_safari_compatibility() { - // Functions within IFs cause syntax errors on Safari. - test( - "function f(){if(e1){function goo(){return true}}else foo()}", - "function f(){if(e1){function goo(){return true}}else foo()}", - ); - - test( - "function f(){if(e1)function goo(){return true}else foo()}", - "function f(){if(e1){function goo(){return true}}else foo()}", - ); - - test("if(e1){function goo(){return true}}", "if(e1){function goo(){return true}}"); - - test("if(e1)function goo(){return true}", "if(e1){function goo(){return true}}"); -} - -#[test] -#[ignore] -fn test_exponents() { - // expectNumber("1", 1); - // expectNumber("10", 10); - // expectNumber("100", 100); - // expectNumber("1E3", 1000); - // expectNumber("1E4", 10000); - // expectNumber("1E5", 100000); - // expectNumber("1E18", 1000000000000000000d); - // expectNumber("1E5", 100000.0); - // expectNumber("100000.1", 100000.1); - - // expectNumber("1E-6", 0.000001); - // expectNumber("0x38d7ea4c68001", 0x38d7ea4c68001p0d); - // expectNumber("0x7fffffffffffffff", 0x7fffffffffffffffp0d); - - // expectNumber(".01", 0.01); - // expectNumber("1.01", 1.01); -} - -#[test] -#[ignore] -fn test_bigger_than_max_long_numeric_literals() { - // Since ECMAScript implements IEEE 754 "round to nearest, ties to even", - // any literal in the range [0x7ffffffffffffe00,0x8000000000000400] will - // round to the same value, namely 2^63. The fact that we print this as - // 2^63-1 doesn't matter, since it must be rounded back to 2^63 at runtime. - // See: - // http://www.ecma-international.org/ecma-262/5.1/#sec-8.5 - test("9223372036854775808", "0x7fffffffffffffff"); - test("0x8000000000000000", "0x7fffffffffffffff"); - test( - "0b1000000000000000000000000000000000000000000000000000000000000000", - "0x7fffffffffffffff", - ); - test("0o1000000000000000000000", "0x7fffffffffffffff"); -} - -#[test] -#[ignore] -fn test_direct_eval() { - test("eval('1');", "eval(\"1\")"); -} - -#[test] -#[ignore] -fn test_indirect_eval() { - // Node n = parse("eval('1');"); - // expectNode("eval(\"1\")", n); - // n.getFirstFirstChild().getFirstChild().putBooleanProp(Node.DIRECT_EVAL, false); - // expectNode("(0,eval)(\"1\")", n); -} - -#[test] -#[ignore] -fn free_call_tagged_template() { - // Node n = parse("a.b`xyz`"); - // Node call = n.getFirstFirstChild(); - // assertThat(call.isTaggedTemplateLit()).isTrue(); - // call.putBooleanProp(Node.FREE_CALL, true); - // expectNode("(0,a.b)`xyz`", n); -} - -#[test] -#[ignore] -fn free_call_opt_chain() { - // Node n = parse("(a?.b)()"); - // Node call = n.getFirstFirstChild(); - // assertThat(call.isCall()).isTrue(); - // call.putBooleanProp(Node.FREE_CALL, true); - // expectNode("(0,a?.b)()", n); -} - -#[test] -#[ignore] -fn free_call_opt_chain_opt_chain_call() { - // Node n = parse("(a?.b)?.()"); - // Node call = n.getFirstFirstChild(); - // assertThat(call.isOptChainCall()).isTrue(); - // call.putBooleanProp(Node.FREE_CALL, true); - // expectNode("(0,a?.b)?.()", n); -} - -#[test] -#[ignore] -fn opt_chain_callee_for_new_requires_parentheses() { - test_same("new (a?.b)"); -} - -#[test] -#[ignore] -fn test_free_call1() { - test("foo(a);", "foo(a)"); - test("x.foo(a);", "x.foo(a)"); -} - -#[test] -#[ignore] -fn test_free_call2() { - // Node n = parse("foo(a);"); - // expectNode("foo(a)", n); - // Node call = n.getFirstFirstChild(); - // assertThat(call.isCall()).isTrue(); - // call.putBooleanProp(Node.FREE_CALL, true); - // expectNode("foo(a)", n); -} - -#[test] -#[ignore] -fn test_free_call3() { - // Node n = parse("x.foo(a);"); - // expectNode("x.foo(a)", n); - // Node call = n.getFirstFirstChild(); - // assertThat(call.isCall()).isTrue(); - // call.putBooleanProp(Node.FREE_CALL, true); - // expectNode("(0,x.foo)(a)", n); -} - -#[test] -#[ignore] -fn test_print_script() { - // Verify that SCRIPT nodes not marked as synthetic are printed as - // blocks. - // Node ast = - // new Node( - // Token.SCRIPT, - // new Node(Token.EXPR_RESULT, Node.newString("f")), - // new Node(Token.EXPR_RESULT, Node.newString("g"))); - // String result = new CodePrinter.Builder(ast).setPrettyPrint(true).build(); - // assertThat(result).isEqualTo("\"f\";\n\"g\";\n"); -} - -#[test] -#[ignore] -fn test_object_lit() { - test("({x:1})", "({x:1})"); - test("var x=({x:1})", "var x={x:1}"); - test("var x={'x':1}", "var x={\"x\":1}"); - test("var x={1:1}", "var x={1:1}"); - test("({},42)+0", "({},42)+0"); -} - -#[test] -#[ignore] -fn test_object_lit2() { - test("var x={1:1}", "var x={1:1}"); - test("var x={'1':1}", "var x={1:1}"); - test("var x={'1.0':1}", "var x={\"1.0\":1}"); - test("var x={1.5:1}", "var x={\"1.5\":1}"); -} - -#[test] -#[ignore] -fn test_object_lit3() { - test("var x={3E9:1}", "var x={3E9:1}"); - test( - "var x={'3000000000':1}", // More than 31 bits - "var x={3E9:1}", - ); - test("var x={'3000000001':1}", "var x={3000000001:1}"); - test( - "var x={'6000000001':1}", // More than 32 bits - "var x={6000000001:1}", - ); - test( - "var x={\"12345678901234567\":1}", // More than 53 bits - "var x={\"12345678901234567\":1}", - ); -} - -#[test] -#[ignore] -fn test_object_lit4() { - // More than 128 bits. - test( - "var x={\"123456789012345671234567890123456712345678901234567\":1}", - "var x={\"123456789012345671234567890123456712345678901234567\":1}", - ); -} - -#[test] -#[ignore] -fn test_extended_object_lit() { - test_same("var a={b}"); - test_same("var a={b,c}"); - test_same("var a={b,c:d,e}"); - test_same("var a={b,c(){},d,e:f}"); -} - -#[test] -#[ignore] -fn test_computed_properties() { - test_same("var a={[b]:c}"); - test_same("var a={[b+3]:c}"); - - test_same("var a={[b](){}}"); - test_same("var a={[b](){alert(foo)}}"); - test_same("var a={*[b](){yield\"foo\"}}"); - test_same("var a={[b]:()=>c}"); - - test_same("var a={get [b](){return null}}"); - test_same("var a={set [b](val){window.b=val}}"); -} - -#[test] -#[ignore] -fn test_computed_properties_class_methods() { - test_same("class C{[m](){}}"); - - test_same("class C{[\"foo\"+bar](){alert(1)}}"); -} - -#[test] -#[ignore] -fn test_getter() { - test("var x = {}", "var x={}"); - test("var x = {get a() {return 1}}", "var x={get a(){return 1}}"); - test("var x = {get a() {}, get b(){}}", "var x={get a(){},get b(){}}"); - - test("var x = {get 'a'() {return 1}}", "var x={get \"a\"(){return 1}}"); - - test("var x = {get 1() {return 1}}", "var x={get 1(){return 1}}"); - - test("var x = {get \"()\"() {return 1}}", "var x={get \"()\"(){return 1}}"); - - test_same("var x={get function(){return 1}}"); -} - -#[test] -#[ignore] -fn test_getter_in_es3() { - // Getters and setters and not supported in ES3 but if someone sets the - // the ES3 output mode on an AST containing them we still produce them. - - // Node getter = Node.newString(Token.GETTER_DEF, "f"); - // getter.addChildToBack(NodeUtil.emptyFunction()); - // expectNode("({get f(){}})", IR.exprResult(IR.objectlit(getter))); -} - -#[test] -#[ignore] -fn test_setter() { - test("var x = {}", "var x={}"); - test("var x = {set a(y) {return 1}}", "var x={set a(y){return 1}}"); - - test("var x = {get 'a'() {return 1}}", "var x={get \"a\"(){return 1}}"); - - test("var x = {set 1(y) {return 1}}", "var x={set 1(y){return 1}}"); - - test("var x = {set \"(x)\"(y) {return 1}}", "var x={set \"(x)\"(y){return 1}}"); - - test_same("var x={set function(x){}}"); -} - -#[test] -#[ignore] -fn test_setter_in_es3() { - // Getters and setters and not supported in ES3 but if someone sets the - // the ES3 output mode on an AST containing them we still produce them. - - // Node getter = Node.newString(Token.SETTER_DEF, "f"); - // getter.addChildToBack(IR.function(IR.name(""), IR.paramList(IR.name("a")), IR.block())); - // expectNode("({set f(a){}})", IR.exprResult(IR.objectlit(getter))); -} - -#[test] -#[ignore] -fn test_neg_no_collapse() { - test("var x = - - 2;", "var x=- -2"); - test("var x = - (2);", "var x=-2"); -} - -#[test] -#[ignore] -fn test_strict() { - // String result = - // defaultBuilder(parse("var x", [> typeChecked= <] true)).setTagAsStrict(true).build(); - // assertThat(result).isEqualTo("'use strict';var x"); -} - -#[test] -#[ignore] -fn test_strict_pretty() { - // String result = - // defaultBuilder(parse("var x", [> typeChecked= <] true)) - // .setTagAsStrict(true) - // .setPrettyPrint(true) - // .build(); - // assertThat(result).isEqualTo("'use strict';\nvar x;\n"); -} - -#[test] -#[ignore] -fn test_ijs() { - // String result = - // defaultBuilder(parse("var x", [> typeChecked= <] true)).setTagAsTypeSummary(true).build(); - // assertThat(result).isEqualTo("/** @fileoverview @typeSummary */\nvar x"); -} - -#[test] -#[ignore] -fn test_ijs_with_provide_already_provided() { - test_same("/** @provideAlreadyProvided */ \ngoog.provide(\"a.b.c\");\n"); -} - -#[test] -#[ignore] -fn test_array_literal() { - test("var x = [,];", "var x=[,]"); - test("var x = [,,];", "var x=[,,]"); - test("var x = [,s,,];", "var x=[,s,,]"); - test("var x = [,s];", "var x=[,s]"); - test("var x = [s,];", "var x=[s]"); -} - -#[test] -#[ignore] -fn test_zero() { - test("var x ='\\0';", "var x=\"\\x00\""); - test("var x ='\\x00';", "var x=\"\\x00\""); - test("var x ='\\u0000';", "var x=\"\\x00\""); - test("var x ='\\u00003';", "var x=\"\\x003\""); -} - -#[test] -#[ignore] -fn test_octal_in_string() { - test("var x ='\\0';", "var x=\"\\x00\""); - test("var x ='\\07';", "var x=\"\\u0007\""); - - // Octal 12 = Hex 0A = \n - test("var x ='\\012';", "var x=\"\\n\""); - - // Octal 13 = Hex 0B = \v - test("var x ='\\013';", "var x=\"\\v\""); - - // Octal 34 = Hex 1C - test("var x ='\\034';", "var x=\"\\u001c\""); - - // 8 and 9 are not octal digits - test("var x ='\\08';", "var x=\"\\x008\""); - test("var x ='\\09';", "var x=\"\\x009\""); - - // Only the first two digits are part of the octal literal. - test("var x ='\\01234';", "var x=\"\\n34\""); -} - -#[test] -#[ignore] -fn test_octal_in_string_no_leading_zero() { - test("var x ='\\7';", "var x=\"\\u0007\""); - - // Octal 12 = Hex 0A = \n - test("var x ='\\12';", "var x=\"\\n\""); - - // Octal 13 = Hex 0B = \v. - test("var x ='\\13';", "var x=\"\\v\""); - - // Octal 34 = Hex 1C - test("var x ='\\34';", "var x=\"\\u001c\""); - - // Octal 240 = Hex A0 - test("var x ='\\240';", "var x=\"\\u00a0\""); - - // Only the first three digits are part of the octal literal. - test("var x ='\\2400';", "var x=\"\\u00a00\""); - - // Only the first two digits are part of the octal literal because '8' - // is not an octal digit. - // Octal 67 = Hex 37 = "7" - test("var x ='\\6789';", "var x=\"789\""); - - // 8 and 9 are not octal digits. '\' is ignored and the digit - // is just a regular character. - test("var x ='\\8';", "var x=\"8\""); - test("var x ='\\9';", "var x=\"9\""); - - // Only the first three digits are part of the octal literal. - // Octal 123 = Hex 53 = "S" - test("var x ='\\1234';", "var x=\"S4\""); -} - -#[test] -#[ignore] -fn test_unicode() { - test("var x ='\\x0f';", "var x=\"\\u000f\""); - test("var x ='\\x68';", "var x=\"h\""); - test("var x ='\\x7f';", "var x=\"\\u007f\""); -} - -// Separate from test_numeric_keys() so we can set allowWarnings. -#[test] -#[ignore] -fn test_octal_numeric_key() { - test("var x = {010: 1};", "var x={8:1}"); -} - -#[test] -#[ignore] -fn test_numeric_keys() { - test("var x = {'010': 1};", "var x={\"010\":1}"); - - test("var x = {0x10: 1};", "var x={16:1}"); - test("var x = {'0x10': 1};", "var x={\"0x10\":1}"); - - // I was surprised at this result too. - test("var x = {.2: 1};", "var x={\"0.2\":1}"); - test("var x = {'.2': 1};", "var x={\".2\":1}"); - - test("var x = {0.2: 1};", "var x={\"0.2\":1}"); - test("var x = {'0.2': 1};", "var x={\"0.2\":1}"); -} - -#[test] -#[ignore] -fn test_issue582() { - test("var x = -0.0;", "var x=-0"); -} - -#[test] -#[ignore] -fn test_issue942() { - test("var x = {0: 1};", "var x={0:1}"); -} - -#[test] -#[ignore] -fn test_issue601() { - test("'\\v' == 'v'", "\"\\v\"==\"v\""); - test("'\\u000B' == '\\v'", "\"\\v\"==\"\\v\""); - test("'\\x0B' == '\\v'", "\"\\v\"==\"\\v\""); -} - -#[test] -#[ignore] -fn test_issue620() { - test("alert(/ / / / /);", "alert(/ // / /)"); - test("alert(/ // / /);", "alert(/ // / /)"); -} - -#[test] -#[ignore] -fn test_issue5746867() { - test("var a = { '$\\\\' : 5 };", "var a={\"$\\\\\":5}"); -} - -#[test] -#[ignore] -fn test_comma_spacing() { - test("var a = (b = 5, c = 5);", "var a=(b=5,c=5)"); - test("var a = (b = 5, c = 5);", "var a = (b = 5, c = 5);\n"); -} - -#[test] -#[ignore] -fn test_many_commas() { - // int numCommas = 10000; - // List numbers = new ArrayList<>(); - // numbers.add("0"); - // numbers.add("1"); - // Node current = new Node(Token.COMMA, Node.newNumber(0), Node.newNumber(1)); - // for (int i = 2; i < numCommas; i++) { - // current = new Node(Token.COMMA, current); - - // // 1000 is printed as 1E3, and screws up our test. - // int num = i % 1000; - // numbers.add(String.valueOf(num)); - // current.addChildToBack(Node.newNumber(num)); - // } - - // String expected = Joiner.on(",").join(numbers); - // String actual = printNode(current).replace("\n", ""); - // assertThat(actual).isEqualTo(expected); -} - -#[test] -#[ignore] -fn test_many_adds() { - // int numAdds = 10000; - // List numbers = new ArrayList<>(); - // numbers.add("0"); - // numbers.add("1"); - // Node current = new Node(Token.ADD, Node.newNumber(0), Node.newNumber(1)); - // for (int i = 2; i < numAdds; i++) { - // current = new Node(Token.ADD, current); - - // // 1000 is printed as 1E3, and screws up our test. - // int num = i % 1000; - // numbers.add(String.valueOf(num)); - // current.addChildToBack(Node.newNumber(num)); - // } - - // String expected = Joiner.on("+").join(numbers); - // String actual = printNode(current).replace("\n", ""); - // assertThat(actual).isEqualTo(expected); -} - -#[test] -#[ignore] -fn test_minus_negative_zero() { - // Negative zero is weird, because we have to be able to distinguish - // it from positive zero (there are some subtle differences in behavior). - test("x- -0", "x- -0"); -} - -#[test] -#[ignore] -fn test_string_escape_sequences() { - // From the SingleEscapeCharacter grammar production. - test_same("var x=\"\\b\""); - test_same("var x=\"\\f\""); - test_same("var x=\"\\n\""); - test_same("var x=\"\\r\""); - test_same("var x=\"\\t\""); - test_same("var x=\"\\v\""); - test("var x=\"\\\"\"", "var x='\"'"); - test("var x=\"\\\'\"", "var x=\"'\""); - - // From the LineTerminator grammar - test("var x=\"\\u000A\"", "var x=\"\\n\""); - test("var x=\"\\u000D\"", "var x=\"\\r\""); - test_same("var x=\"\\u2028\""); - test_same("var x=\"\\u2029\""); - - // Now with regular expressions. - test_same("var x=/\\b/"); - test_same("var x=/\\f/"); - test_same("var x=/\\n/"); - test_same("var x=/\\r/"); - test_same("var x=/\\t/"); - test_same("var x=/\\v/"); - test_same("var x=/\\u000A/"); - test_same("var x=/\\u000D/"); - test_same("var x=/\\u2028/"); - test_same("var x=/\\u2029/"); -} - -#[test] -#[ignore] -fn test_regexp_escape() { - test_same("/\\bword\\b/"); - test_same("/Java\\BScript/"); - test_same("/\\ca/"); - test_same("/\\cb/"); - test_same("/\\cc/"); - test_same("/\\cA/"); - test_same("/\\cB/"); - test_same("/\\cC/"); - test_same("/\\d/"); - test_same("/\\D/"); - test_same("/\\0/"); - test_same("/\\\\/"); - test_same("/(.)\\1/"); - test_same("/\\x0B/"); // Don't print this as \v (as is done in strings) -} - -#[test] -#[ignore] -fn test_regexp_unnecessary_escape() { - test("/\\a/", "/a/"); - test("/\\e/", "/e/"); - test("/\\g/", "/g/"); - test("/\\h/", "/h/"); - test("/\\i/", "/i/"); - test("/\\ยก/", "/\\u00a1/"); -} - -#[test] -#[ignore] -fn test_keyword_properties1() { - test_same("x.foo=2"); - test_same("x.function=2"); - - test_same("x.foo=2"); -} - -#[test] -#[ignore] -fn test_keyword_properties1a() { - - // Node nodes = parse("x.function=2"); - - // expectNode("x[\"function\"]=2", nodes); -} - -#[test] -#[ignore] -fn test_keyword_properties2() { - test_same("x={foo:2}"); - test_same("x={function:2}"); - - test_same("x={foo:2}"); -} - -#[test] -#[ignore] -fn test_keyword_properties2a() { - - // Node nodes = parse("x={function:2}"); - - // expectNode("x={\"function\":2}", nodes); -} - -#[test] -#[ignore] -fn test_issue1062() { - test_same("3*(4%3*5)"); -} - -#[test] -#[ignore] -fn test_preserve_type_annotations() { - // preserveTypeAnnotations = true; - test_same("/** @type {foo} */ var bar"); - test_same("function/** void */ f(/** string */ s,/** number */ n){}"); - - // preserveTypeAnnotations = false; - test("/** @type {foo} */ var bar;", "var bar"); -} - -#[test] -#[ignore] -fn test_preserve_type_annotations2() { - // preserveTypeAnnotations = true; - - test_same("/** @const */ var ns={}"); - - test_same(lines!( - "/**", // - " * @const", - " * @suppress {const,duplicate}", - " */", - "var ns={}" - )); -} - -#[test] -#[ignore] -fn test_default_parameters() { - test_same("function f(a=0){}"); - test_same("function f(a,b=0){}"); - test_same("function f(a=(1,2),b){}"); - test_same("function f(a,b=(1,2)){}"); -} - -#[test] -#[ignore] -fn test_rest_parameters() { - test_same("function f(...args){}"); - test_same("function f(first,...rest){}"); -} - -#[test] -#[ignore] -fn test_default_parameters_with_rest_parameters() { - test_same("function f(first=0,...args){}"); - test_same("function f(first,second=0,...rest){}"); -} - -#[test] -#[ignore] -fn test_spread_expression() { - test_same("f(...args)"); - test_same("f(...arrayOfArrays[0])"); - test_same("f(...[1,2,3])"); - test_same("f(...([1],[2]))"); -} - -#[test] -#[ignore] -fn test_class() { - test_same("class C{}"); - test_same("(class C{})"); - test_same("class C extends D{}"); - test_same("class C{static member(){}}"); - test_same("class C{member(){}get f(){}}"); - test_same("var x=class C{}"); -} - -#[test] -#[ignore] -fn test_class_computed_properties() { - test_same("class C{[x](){}}"); - test_same("class C{get [x](){}}"); - test_same("class C{set [x](val){}}"); - - test_same("class C{static [x](){}}"); - test_same("class C{static get [x](){}}"); - test_same("class C{static set [x](val){}}"); -} - -#[test] -#[ignore] -fn test_class_pretty() { - test("class C{}", "class C {\n}\n"); - // expect( - // "class C{member(){}get f(){}}", - // "class C {\n" + " member() {\n" + " }\n" + " get f() {\n" + " }\n" + "}\n"); - test("var x=class C{}", "var x = class C {\n};\n"); -} - -#[test] -#[ignore] -fn test_class_field() { - test_same(lines!( - "class C {", // - " x;", - "}", - "" - )); - test_same(lines!( - "class C {", // - " x=2;", - "}", - "" - )); - test_same(lines!( - "class C {", // - " x=2;", - " y=3;", - "}", - "" - )); - test( - lines!( - "class C {", // - " x=2", - " y=3", - "}", - "" - ), - lines!( - "class C {", // - " x=2;", - " y=3;", - "}", - "" - ), - ); - test( - "class C {x=2;y=3}", - lines!( - "class C {", // - " x=2;", - " y=3;", - "}", - "" - ), - ); -} - -#[test] -#[ignore] -fn test_class_field_check_state() { - test_same(lines!( - "/** @interface */ ", // - "class C {", - " x;", - "}", - "" - )); - test_same(lines!( - "/** @record */ ", // - "class C {", - " x;", - "}", - "" - )); -} - -#[test] -#[ignore] -fn test_class_field_static() { - test_same(lines!( - "class C {", // - " static x;", - "}", - "" - )); - test_same(lines!( - "class C {", // - " static x=2;", - "}", - "" - )); - test_same(lines!( - "class C {", // - " static x=2;", - " static y=3;", - "}", - "" - )); - test_same(lines!( - "/** @interface */ ", // - "class C {", - " static x;", - "}", - "" - )); - test_same(lines!( - "/** @record */ ", // - "class C {", - " static x;", - "}", - "" - )); -} - -#[test] -#[ignore] -fn test_computed_class_field_literal_string_number() { - test( - "class C { 'str' = 2;}", - lines!( - "class C {", // - " [\"str\"]=2;", - "}", - "" - ), - ); - test( - "class C { 1 = 2;}", - lines!( - "class C {", // - " [1]=2;", - "}", - "" - ), - ); -} - -#[test] -#[ignore] -fn test_computed_class_field() { - test_same(lines!( - "class C {", // - " [x];", - "}", - "" - )); - test_same(lines!( - "class C {", // - " [x]=2;", - "}", - "" - )); - test_same(lines!( - "class C {", // - " [x]=2;", - " y=3;", - "}", - "" - )); - test_same(lines!( - "class C {", // - " [x]=2;", - " [y]=3;", - "}", - "" - )); -} - -#[test] -#[ignore] -fn test_computed_class_field_static() { - test_same(lines!( - "class C {", // - " static [x];", - "}", - "" - )); - test_same(lines!( - "class C {", // - " static [x]=2;", - "}", - "" - )); - test_same(lines!( - "class C {", // - " static [x]=2;", - " static y=3;", - "}", - "" - )); - test_same(lines!( - "class C {", // - " static [x]=2;", - " static [y]=3;", - "}", - "" - )); -} - -#[test] -#[ignore] -fn test_super() { - test_same("class C extends foo(){}"); - test_same("class C extends m.foo(){}"); - test_same("class C extends D{member(){super.foo()}}"); -} - -#[test] -#[ignore] -fn test_new_target() { - test_same("function f(){new.target}"); - test("function f() {\nnew\n.\ntarget;\n}", "function f(){new.target}"); -} - -#[test] -#[ignore] -fn test_import_meta() { - test_same("import.meta"); - test_same("import.meta.url"); - test_same("console.log(import.meta.url)"); -} - -#[test] -#[ignore] -fn test_generator_yield() { - test_same("function*f(){yield 1}"); - test_same("function*f(){yield}"); - test_same("function*f(){yield 1?0:2}"); - test_same("function*f(){yield 1,0}"); - test_same("function*f(){1,yield 0}"); - test_same("function*f(){yield(a=0)}"); - test_same("function*f(){a=yield 0}"); - test_same("function*f(){(yield 1)+(yield 1)}"); - // Parens required for evaluating arrow function expression i.e. `yield (() => expr)` - test_same("function*f(){yield(()=>({}))}"); -} - -#[test] -#[ignore] -fn test_generator_yield_pretty() { - test("function *f() {yield 1}", lines!("function* f() {", " yield 1;", "}", "")); - - test("function *f() {yield}", lines!("function* f() {", " yield;", "}", "")); -} - -#[test] -#[ignore] -fn test_member_generator_yield1() { - test_same("class C{*member(){(yield 1)+(yield 1)}}"); - test_same("class C{*[0](){(yield 1)+(yield 1)}}"); - test_same("var obj={*member(){(yield 1)+(yield 1)}}"); - test_same("var obj={*[0](){(yield 1)+(yield 1)}}"); - test_same("var obj={[0]:function*(){(yield 1)+(yield 1)}}"); -} - -#[test] -#[ignore] -fn test_arrow_function_zero_params() { - test_same("()=>1"); - test("(()=>1)", "()=>1"); - test_same("()=>{}"); - test("(()=>a),b", "()=>a,b"); - test("()=>(a=b)", "()=>a=b"); -} - -#[test] -#[ignore] -fn test_arrow_function_one_param() { - test_same("a=>b"); - test_same("([a])=>b"); - test_same("(...a)=>b"); - test_same("(a=0)=>b"); - test_same("(a=>b)(1)"); - test_same("fn?.(a=>a)"); - test("(a=>a)?.['length']", "(a=>a)?.[\"length\"]"); - test_same("(a=>a)?.(1)"); - test_same("(a=>a)?.length"); - test_same("a=>a?.length"); - test_same("var z={x:a=>1}"); - test_same("[1,2].forEach(x=>y)"); -} - -#[test] -#[ignore] -fn test_arrow_function_many_params() { - test_same("(a,b)=>b"); -} - -#[test] -#[ignore] -fn test_arrow_function_body_edge_cases() { - test_same("()=>(a,b)"); - test_same("()=>({a:1})"); - test_same("()=>{return 1}"); -} - -#[test] -#[ignore] -fn test_async_function() { - test_same("async function f(){}"); - test_same("let f=async function f(){}"); - test_same("let f=async function(){}"); - // implicit semicolon prevents async being treated as a keyword - test("async\nfunction f(){}", "async;function f(){}"); - test("let f=async\nfunction f(){}", "let f=async;function f(){}"); -} - -#[test] -#[ignore] -fn test_async_generator_function() { - test_same("async function*f(){}"); - test_same("let f=async function*f(){}"); - test_same("let f=async function*(){}"); - // implicit semicolon prevents async being treated as a keyword - test("async\nfunction*f(){}", "async;function*f(){}"); - test("let f=async\nfunction*f(){}", "let f=async;function*f(){}"); -} - -#[test] -#[ignore] -fn test_async_arrow_function() { - test_same("async()=>1"); - test("async (a) => 1", "async a=>1"); - - // implicit semicolon prevents async being treated as a keyword - test("f=async\n()=>1", "f=async;()=>1"); -} - -#[test] -#[ignore] -fn test_async_method() { - test_same("o={async m(){}}"); - test_same("o={async[a+b](){}}"); - test_same("o={[0]:async function(){}}"); // (not technically a method) - test_same("class C{async m(){}}"); - test_same("class C{async[a+b](){}}"); - test_same("class C{static async m(){}}"); - test_same("class C{static async[a+b](){}}"); -} - -#[test] -#[ignore] -fn test_async_generator_method() { - test_same("o={async *m(){}}"); - test_same("o={async*[a+b](){}}"); - test_same("o={[0]:async*function(){}}"); // (not technically a method) - test_same("class C{async *m(){}}"); - test_same("class C{async*[a+b](){}}"); - test_same("class C{static async *m(){}}"); - test_same("class C{static async*[a+b](){}}"); -} - -#[test] -#[ignore] -fn test_await_expression() { - test_same("async function f(promise){return await promise}"); - test_same("pwait=async function(promise){return await promise}"); - test_same("class C{async pwait(promise){await promise}}"); - test_same("o={async pwait(promise){await promise}}"); - test_same("pwait=async promise=>await promise"); -} - -/** Regression test for b/235871063 - necessary parens dropped around awaited arrow function. */ -#[test] -#[ignore] -fn test_parans_around_await_arrow_function() { - // Parens required for evaluating arrow function expression i.e. `await (() => expr)` - test( - "async function f(){return await (()=>new Promise((resolve)=>setTimeout(resolve,0)));}", - "async function f(){return await (()=>new Promise(resolve=>setTimeout(resolve,0)))}", - ); - // System.out.println("--------------"); - // Parens not required for evaluating new - test( - "async function f(){return await new Promise((resolve)=>setTimeout(resolve,0));}", - "async function f(){return await new Promise(resolve=>setTimeout(resolve,0))}", - ); -} - -/** - * Regression test for b/28633247 - necessary parens dropped around arrow functions. - * - *

Many of these cases use single param arrows because their PARAM_LIST parens should also be - * dropped, which can make this harder to parse for humans. - */ -#[test] -#[ignore] -fn test_parens_around_arrow() { - // Parens required for non-assignment binary operator - test("x||((_)=>true)", "x||(_=>true)"); - // Parens required for unary operator - test("void((e)=>e*5)", "void(e=>e*5)"); - // Parens not required for comma operator - test("((_) => true), ((_) => false)", "_=>true,_=>false"); - // Parens not required for right side of assignment operator - // NOTE: An arrow function on the left side would be a parse error. - test("x = ((_) => _ + 1)", "x=_=>_+1"); - // Parens required for template tag - test("((_)=>\"\")`template`", "(_=>\"\")`template`"); - // Parens required to reference a property - test_same("((a,b,c)=>a+b+c).length"); - test_same("((a,b,c)=>a+b+c)[\"length\"]"); - // Parens not required when evaluating property name. - // (It doesn't make much sense to do it, though.) - test("x[((_)=>0)]", "x[_=>0]"); - // Parens required to call the arrow function immediately - test("((x)=>x*5)(10)", "(x=>x*5)(10)"); - // Parens not required for function call arguments - test("x(((_) => true), ((_) => false))", "x(_=>true,_=>false)"); - // Parens required for first operand to a conditional, but not the rest. - test("((x)=>1)?a:b", "(x=>1)?a:b"); - test("x?((x)=>0):((x)=>1)", "x?x=>0:x=>1"); - test("new ((x)=>x)", "new (x=>x)"); - test_same("new C(x=>x)"); -} - -#[test] -#[ignore] -fn test_parens_around_arrow_fn_in_cast() { - // preserveTypeAnnotations = false; - test("x(/** @type {?} */ (()=>{x}))", "x(()=>{x})"); - test("x(/** @type {?} */ (()=>{x})())", "x((()=>{x})())"); - test("x(/** @type {string} */ (/** @type {?} */ (()=>{x}))())", "x((()=>{x})())"); - - // preserveTypeAnnotations = true; - test_same("x(/** @type {?} */ (()=>{x}))"); - test_same("x(/** @type {?} */ (()=>{x})())"); - test_same("x(/** @type {string} */ (/** @type {?} */ (()=>{x}))())"); -} - -#[test] -#[ignore] -fn test_parens_around_variable_declarator() { - test_same("var o=(test++,{one:1})"); - test_same("({one}=(test++,{one:1}))"); - test_same("[one]=(test++,[1])"); - - test_same("var {one}=(test++,{one:1})"); - test_same("var [one]=(test++,[1])"); - test("var {one}=/** @type {{one: number}} */(test++,{one:1})", "var {one}=(test++,{one:1})"); - test("var [one]=/** @type {!Array} */(test++,[1])", "var [one]=(test++,[1])"); -} - -#[test] -#[ignore] -fn test_parens_around_arrow_return_value() { - test_same("()=>({})"); - test_same("()=>({a:1})"); - test_same("()=>({a:1,b:2})"); - test("()=>/** @type {Object} */({})", "()=>({})"); - test("()=>/** @type {Object} */({a:1})", "()=>({a:1})"); - test("()=>/** @type {Object} */({a:1,b:2})", "()=>({a:1,b:2})"); - test("()=>/** @type {number} */(3)", "()=>3"); - test("()=>/** @type {Object} */ ({}={})", "()=>({}={})"); - - test_same("()=>(1,2)"); - test_same("()=>({},2)"); - test_same("()=>(1,{})"); - test("()=>/** @type {?} */(1,2)", "()=>(1,2)"); - test("()=>/** @type {?} */({},2)", "()=>({},2)"); - test("()=>/** @type {?} */(1,{})", "()=>(1,{})"); - - // Test object literals more deeply nested - test_same("fn=()=>({})||3"); - test_same("fn=()=>3||{}"); - test_same("fn=()=>({}={})"); - test_same("()=>function(){}"); // don't need parentheses around a function - test_same("for(var i=()=>({});;);"); - - // preserveTypeAnnotations = true; - test_same("()=>/** @type {Object} */ ({})"); -} - -#[test] -#[ignore] -fn test_pretty_arrow_function() { - test( - "if (x) {var f = ()=>{alert(1); alert(2)}}", - lines!("if (x) {", " var f = () => {", " alert(1);", " alert(2);", " };", "}", ""), - ); -} - -#[test] -#[ignore] -fn test_pretty_print_switch() { - test( - "switch(something){case 0:alert(0);break;case 1:alert(1);break}", - lines!( - "switch(something) {", - " case 0:", - " alert(0);", - " break;", - " case 1:", - " alert(1);", - " break;", - "}", - "" - ), - ); -} - -#[test] -#[ignore] -fn test_blocks_in_case_are_preserved() { - // String js = - // lines!( - // "switch(something) {", - // " case 0:", - // " {", - // " const x = 1;", - // " break;", - // " }", - // " case 1:", - // " break;", - // " case 2:", - // " console.log(`case 2!`);", - // " {", - // " const x = 2;", - // " break;", - // " }", - // "}", - // ""); - // expect(js, js); -} - -#[test] -#[ignore] -fn test_blocks_are_preserved() { - // String js = - // lines!( - // "console.log(0);", - // "{", - // " let x = 1;", - // " console.log(x);", - // "}", - // "console.log(x);", - // ""); - // expect(js, js); -} - -#[test] -#[ignore] -fn test_blocks_not_preserved() { - test("if (x) {};", "if(x);"); - test("while (x) {};", "while(x);"); -} - -#[test] -#[ignore] -fn test_empty_class_static_block() { - test_same("class C {\n static {\n }\n}\n"); - test("class C {\n static {\n }\n}\n", "class C{static{}}"); - test_same("let a = class {\n static {\n }\n};\n"); -} - -#[test] -#[ignore] -fn test_class_static_block() { - test_same(lines!( - "class C {", - " static field1=1;", - " static field2=2;", - " static {", - " let x = this.field1;", - " let y = this.field2;", - " }", - "}", - "" - )); - test_same(lines!( - "class C {", - " static {", - " this.field1 = 1;", - " this.field2 = 2;", - " }", - "}", - "" - )); - test_same(lines!( - "let a = class {", - " static field1=1;", - " static field2=2;", - " static {", - " let x = this.field1;", - " let y = this.field2;", - " }", - "};", - "" - )); - test_same(lines!( - "let a = class {", - " static {", - " this.field1 = 1;", - " this.field2 = 2;", - " }", - "};", - "" - )); -} - -#[test] -#[ignore] -fn test_multiple_class_static_blocks() { - // empty - test_same("class C {\n static {\n }\n static {\n }\n}\n"); - test_same("let a = class {\n static {\n }\n static {\n }\n};\n"); - // multiple fields - test_same(lines!( - "class C {", - " static field1=1;", - " static field2=2;", - " static {", - " let x = this.field1;", - " }", - " static {", - " let y = this.field2;", - " }", - "}", - "" - )); - test_same(lines!( - "class C {", - " static {", - " this.field1 = 1;", - " }", - " static {", - " this.field2 = 2;", - " }", - "}", - "" - )); - test_same(lines!( - "let a = class {", - " static field1=1;", - " static field2=2;", - " static {", - " let x = this.field1;", - " }", - " static {", - " let y = this.field2;", - " }", - "};", - "" - )); - test_same(lines!( - "let a = class {", - " static {", - " this.field1 = 1;", - " }", - " static {", - " this.field2 = 2;", - " }", - "};", - "" - )); -} - -#[test] -#[ignore] -fn test_declarations() { - test_same("let x"); - test_same("let x,y"); - test_same("let x=1"); - test_same("let x=1,y=2"); - test_same("if(a){let x}"); - - test_same("const x=1"); - test_same("const x=1,y=2"); - test_same("if(a){const x=1}"); - - test_same("function f(){}"); - test_same("if(a){function f(){}}"); - test_same("if(a)(function(){})"); - - test_same("class f{}"); - test_same("if(a){class f{}}"); - test_same("if(a)(class{})"); -} - -#[test] -#[ignore] -fn test_imports() { - // diagnosticsToIgnore = ImmutableList.of(MODULE_LOAD); // allow importing nonexistent modules - test_same("import x from\"./foo\""); - test_same("import\"./foo\""); - test_same("import x,{a as b}from\"./foo\""); - test_same("import{a as b,c as d}from\"./foo\""); - test_same("import x,{a}from\"./foo\""); - test_same("import{a,c}from\"./foo\""); - test_same("import x,*as f from\"./foo\""); - test_same("import*as f from\"./foo\""); -} - -#[test] -#[ignore] -fn test_exports() { - // export declarations - // diagnosticsToIgnore = ImmutableList.of(MODULE_LOAD); // allow importing nonexistent modules - test_same("export var x=1"); - test_same("export var x;export var y"); - test_same("export let x=1"); - test_same("export const x=1"); - test_same("export function f(){}"); - test_same("export class f{}"); - test_same("export class f{}export class b{}"); - - // export all from - test("export * from './a.b.c'", "export*from\"./a.b.c\""); - - // from - test_same("export{a}from\"./a.b.c\""); - test_same("export{a as x}from\"./a.b.c\""); - test_same("export{a,b}from\"./a.b.c\""); - test_same("export{a as x,b as y}from\"./a.b.c\""); - test_same("export{a}"); - test_same("export{a as x}"); - - test_same("export{a,b}"); - test_same("export{a as x,b as y}"); - - // export default - test_same("export default x"); - test_same("export default 1"); - test_same("export default class Foo{}export function f(){}"); - test_same("export function f(){}export default class Foo{}"); -} - -#[test] -#[ignore] -fn test_export_async_function() { - test_same("export async function f(){}"); -} - -#[test] -#[ignore] -fn test_template_literal() { - // We need to use the raw string instead of the normalized string in template literals - test_same("`hello`"); - test_same("`\\\\bhello`"); - test_same("`hell\rlo`"); - test_same("`hell\\rlo`"); - test_same("`hell\r\nlo`"); - test_same("`hell\\r\\nlo`"); - test("`hello`\n'world'", "`hello`;\"world\""); - test("`hello`\n`world`", "`hello``world`"); - test("var x=`TestA`\n`TemplateB`", "var x=`TestA``TemplateB`"); - test_same("`hello``world`"); - - test_same("`hello${world}!`"); - test_same("`hello${world} ${name}!`"); - - test_same("`hello${(function(){let x=3})()}`"); - test_same("(function(){})()`${(function(){})()}`"); - test_same("url`hello`"); - test_same("url(`hello`)"); - test_same("`\\u{2026}`"); - test_same("`start\\u{2026}end`"); - test_same("`\\u{1f42a}`"); - test_same("`start\\u{1f42a}end`"); - test_same("`\\u2026`"); - test_same("`start\\u2026end`"); - test_same("`\"`"); - test_same("`'`"); - test_same("`\\\"`"); - test_same("`\\'`"); - test_same("`\\``"); - - test_same("foo`\\unicode`"); - // b/114808380 - test_same("String.raw`a\\ b`"); - - // Nested substitutions. - test_same("`Hello ${x?`Alice`:`Bob`}?`"); - test_same("`Hello ${x?`Alice ${y(`Kitten`)}`:`Bob`}?`"); - - // Substitution without padding. - test_same("`Unbroken${x}string`"); - - // Template strings terminate statements if needed. - test_same("let a;`a`"); -} - -#[test] -#[ignore] -fn test_multi_line_template_literal_preserves_interval_new_and_blanklines() { - test_same(lines!( - "var y=`hello", // Line break (0 blank lines). - "world", - "", // Single blank line. - "foo", - "", // Multiple blank lines. - "", - "", - "bar`" - )); - - test_same(lines!( - "var y = `hello", // Line break (0 blank lines). - "world", - "", // Single blank line. - "foo", - "", // Multiple blank lines. - "", - "", - "bar`;", - "" - )); -} - -#[test] -#[ignore] -fn test_multi_line_template_literal_does_not_preserve_new_lines_in_substitutions() { - test( - lines!( - "var y=`Hello ${x", // - "+", - "z", // - "}`" - ), - "var y=`Hello ${x+z}`", - ); - - test( - lines!( - "var y=`Hello ${x", // - "+", - "z", // - "}`" - ), - lines!( - "var y = `Hello ${x + z}`;", // - "" - ), - ); -} - -#[test] -#[ignore] -fn test_multi_line_template_literal_not_indented_by_pretty_print() { - // We intentionally put all the delimiter characters on the start of their own line to check - // their indentation. - test( - lines!( - "function indentScope() {", // - " var y =", - "`hello", // Open backtick. - "world", - "foo", - "${", // Open substitution. - "bing", - "}", // Close substitution. - "bar", - "`;", // Close backtick. - "}" - ), - lines!( - "function indentScope() {", // - " var y = `hello", - "world", - "foo", - "${bing}", - "bar", - "`;", - "}", - "" - ), - ); -} - -#[test] -#[ignore] -fn test_multi_line_template_literal_broken_onto_last_line_is_not_collapsed() { - // related to b/117613188 - - // Given - // Configure these so that the printer would otherwise attempt to reuse an existing newline. - // CompilerOptions codePrinterOptions = new CompilerOptions(); - // codePrinterOptions.setLineLengthThreshold(30); // Must be big compared to the last line length. - - // String input = - // lines!( - // "`hello", // - // "world", // - // "foo", // - // "bar`;"); - - // // When - // String actual = - // new CodePrinter.Builder(parse(input)) - // .setCompilerOptions(codePrinterOptions) - // .setPrettyPrint(false) - // .build(); - - // // Then - // assertThat(actual) - // .isEqualTo( - // lines!( - // "`hello", // - // "world", // - // "foo", // - // "bar`")); -} - -#[test] -#[ignore] -fn test_es6_goog_module() { - // String code = - // lines!( - // "goog.module('foo.bar');", - // "const STR = '3';", - // "function fn() {", - // " alert(STR);", - // "}", - // "exports.fn = fn;"); - // String expectedCode = - // lines!( - // "goog.module('foo.bar');", - // "var module$exports$foo$bar = {};", - // "const STR = '3';", - // "function fn() {", - // " alert(STR);", - // "}", - // "exports.fn = fn;\n"); - - // CompilerOptions compilerOptions = new CompilerOptions(); - // compilerOptions.setClosurePass(true); - // compilerOptions.setPreserveDetailedSourceInfo(true); - // compilerOptions.setContinueAfterErrors(true); - // Compiler compiler = new Compiler(); - // compiler.disableThreads(); - // checkWithOriginalName(code, expectedCode, compilerOptions); -} - -#[test] -#[ignore] -fn test_es6_arrow_function_sets_original_name_for_this() { - // String code = "(x)=>{this.foo[0](3);}"; - // String expectedCode = - // "" - // + "var $jscomp$this = this;\n" // TODO(tomnguyen): Avoid printing this line. - // + "(function(x) {\n" // TODO(tomnguyen): This should print as an => function. - // + " this.foo[0](3);\n" - // + "});\n"; - // CompilerOptions compilerOptions = new CompilerOptions(); - // compilerOptions.skipAllCompilerPasses(); - // compilerOptions.set - // checkWithOriginalName(code, expectedCode, compilerOptions); -} - -#[test] -#[ignore] -fn test_es6_arrow_function_sets_original_name_for_arguments() { - // With original names in output set, the end result is not correct code, but the "this" is - // not rewritten. - // String code = "(x)=>{arguments[0]();}"; - // String expectedCode = - // "" - // + "var $jscomp$arguments = arguments;\n" - // + "(function(x) {\n" - // + " arguments[0]();\n" - // + "});\n"; - // CompilerOptions compilerOptions = new CompilerOptions(); - // compilerOptions.skipAllCompilerPasses(); - // compilerOptions.set - // checkWithOriginalName(code, expectedCode, compilerOptions); -} - -#[test] -#[ignore] -fn test_es6_new_target_bare() { - test_same("class C{constructor(){new.target.prototype}}"); -} - -#[test] -#[ignore] -fn test_es6_new_target_prototype() { - test_same( - "class C{constructor(){var callable=Object.setPrototypeOf(obj,new.target.prototype)}}", - ); -} - -#[test] -#[ignore] -fn test_es6_new_target_conditional() { - test( - lines!("function f() {", " if (!new.target) throw 'Must be called with new!';", "}"), - "function f(){if(!new.target)throw\"Must be called with new!\";}", - ); -} - -#[test] -#[ignore] -fn test_goog_scope() { - // TODO(mknichel): Function declarations need to be rewritten to match the original source - // instead of being assigned to a local variable with duplicate JS Doc. - // String code = - // "" - // + "goog.provide('foo.bar');\n" - // + "goog.require('baz.qux.Quux');\n" - // + "goog.require('foo.ScopedType');\n" - // + "\n" - // + "goog.scope(function() {\n" - // + "var Quux = baz.qux.Quux;\n" - // + "var ScopedType = foo.ScopedType;\n" - // + "\n" - // + "var STR = '3';\n" - // + "/** @param {ScopedType} obj */\n" - // + "function fn(obj) {\n" - // + " alert(STR);\n" - // + " alert(Quux.someProperty);\n" - // + "}\n" - // + "}); // goog.scope\n"; - // String expectedCode = - // "" - // + "goog.provide('foo.bar');\n" - // + "goog.require('baz.qux.Quux');\n" - // + "goog.require('foo.ScopedType');\n" - // + "/**\n" - // + " * @param {ScopedType} obj\n" - // + " */\n" - // + "var $jscomp$scope$3556498$1$fn = /**\n" - // + " * @param {ScopedType} obj\n" - // + " */\n" - // + "function(obj) {\n" - // + " alert(STR);\n" - // + " alert(Quux.someProperty);\n" - // + "};\n" - // + "var $jscomp$scope$3556498$0$STR = '3';\n"; - - // CompilerOptions compilerOptions = new CompilerOptions(); - // compilerOptions.setChecksOnly(true); - // compilerOptions.setClosurePass(true); - // compilerOptions.setPreserveDetailedSourceInfo(true); - // compilerOptions.setCheckTypes(true); - // compilerOptions.setContinueAfterErrors(true); - // compilerOptions.setPreserveClosurePrimitives(true); - // Compiler compiler = new Compiler(); - // compiler.disableThreads(); - // compiler.compile( - // ImmutableList.of(), // Externs - // ImmutableList.of(SourceFile.fromCode("test", code)), - // compilerOptions); - // Node node = compiler.getRoot().getLastChild().getFirstChild(); - - // CompilerOptions codePrinterOptions = new CompilerOptions(); - // codePrinterOptions.setPreferSingleQuotes(true); - // codePrinterOptions.setLineLengthThreshold(80); - // codePrinterOptions.setPreserveTypeAnnotations(true); - // codePrinterOptions.setUseOriginalNamesInOutput(true); - // assertThat( - // new CodePrinter.Builder(node) - // .setCompilerOptions(codePrinterOptions) - // .setPrettyPrint(true) - // .setLineBreak(true) - // .build()) - // .isEqualTo(expectedCode); -} - -#[test] -#[ignore] -fn test_escape_dollar_in_template_literal_in_output() { - // CompilerOptions compilerOptions = new CompilerOptions(); - // compilerOptions.skipAllCompilerPasses(); - // compilerOptions.set - - // checkWithOriginalName( - // "let Foo; const x = `${Foo}`;", "let Foo;\nconst x = `${Foo}`;\n", compilerOptions); - - // checkWithOriginalName("const x = `\\${Foo}`;", "const x = `\\${Foo}`;\n", compilerOptions); - - // checkWithOriginalName( - // "let Foo; const x = `${Foo}\\${Foo}`;", - // "let Foo;\nconst x = `${Foo}\\${Foo}`;\n", - // compilerOptions); - - // checkWithOriginalName( - // "let Foo; const x = `\\${Foo}${Foo}`;", - // "let Foo;\nconst x = `\\${Foo}${Foo}`;\n", - // compilerOptions); -} - -#[test] -#[ignore] -fn test_escape_dollar_in_template_literal_es5_output() { - // CompilerOptions compilerOptions = new CompilerOptions(); - // compilerOptions.skipAllCompilerPasses(); - // compilerOptions.set - - // checkWithOriginalName( - // "let Foo; const x = `${Foo}`;", "var Foo;\nvar x = '' + Foo;\n", compilerOptions); - - // checkWithOriginalName("const x = `\\${Foo}`;", "var x = '${Foo}';\n", compilerOptions); - - // checkWithOriginalName( - // "let Foo; const x = `${Foo}\\${Foo}`;", - // "var Foo;\nvar x = Foo + '${Foo}';\n", - // compilerOptions); - // checkWithOriginalName( - // "let Foo; const x = `\\${Foo}${Foo}`;", - // "var Foo;\nvar x = '${Foo}' + Foo;\n", - // compilerOptions); -} - -#[test] -#[ignore] -fn test_do_not_escape_dollar_in_regex() { - // CompilerOptions compilerOptions = new CompilerOptions(); - // compilerOptions.skipAllCompilerPasses(); - // compilerOptions.set - // checkWithOriginalName("var x = /\\$qux/;", "var x = /\\$qux/;\n", compilerOptions); - // checkWithOriginalName("var x = /$qux/;", "var x = /$qux/;\n", compilerOptions); -} - -#[test] -#[ignore] -fn test_do_not_escape_dollar_in_string_literal() { - // String code = "var x = '\\$qux';"; - // String expectedCode = "var x = '$qux';\n"; - // CompilerOptions compilerOptions = new CompilerOptions(); - // compilerOptions.skipAllCompilerPasses(); - // compilerOptions.set - // checkWithOriginalName(code, expectedCode, compilerOptions); - // checkWithOriginalName("var x = '\\$qux';", "var x = '$qux';\n", compilerOptions); - // checkWithOriginalName("var x = '$qux';", "var x = '$qux';\n", compilerOptions); -} - -#[test] -#[ignore] -fn test_pretty_printer_if_else_if_added_block() { - test_same(lines!( - "if (0) {", - " 0;", - "} else if (1) {", - " if (2) {", - " 2;", - " }", - "} else if (3) {", - " 3;", - "}", - "" - )); - - test( - "if(0)if(1)1;else 2;else 3;", - lines!( - "if (0) {", - " if (1) {", - " 1;", - " } else {", - " 2;", - " }", - "} else {", - " 3;", - "}", - "" - ), - ); -} - -#[test] -#[ignore] -fn test_non_js_doc_comments_printed_get_prop() { - // preserveNonJSDocComments = true; - // TODO(b/228156705): Fix comment printing properly for GETPROP. - test("a.// testComment\nb", "// testComment\na.b;\n"); -} diff --git a/crates/oxc_minifier/tests/mangler/mod.rs b/crates/oxc_minifier/tests/mangler/mod.rs index 55728f11774144..471c1b86daadf2 100644 --- a/crates/oxc_minifier/tests/mangler/mod.rs +++ b/crates/oxc_minifier/tests/mangler/mod.rs @@ -1,9 +1,8 @@ use std::fmt::Write; use oxc_allocator::Allocator; -use oxc_codegen::{CodeGenerator, CodegenOptions}; +use oxc_codegen::CodeGenerator; use oxc_mangler::ManglerBuilder; -use oxc_minifier::{CompressOptions, Minifier, MinifierOptions}; use oxc_parser::Parser; use oxc_span::SourceType; diff --git a/crates/oxc_minifier/tests/mod.rs b/crates/oxc_minifier/tests/mod.rs index b0415cd3d62bed..644eae4a0d1642 100644 --- a/crates/oxc_minifier/tests/mod.rs +++ b/crates/oxc_minifier/tests/mod.rs @@ -1,5 +1,4 @@ -#![allow(unused)] -// mod closure; +mod closure; mod mangler; mod oxc; // mod tdewolff; @@ -7,64 +6,40 @@ mod oxc; use oxc_allocator::Allocator; use oxc_codegen::{CodeGenerator, CodegenOptions}; -use oxc_minifier::{CompressOptions, Minifier, MinifierOptions}; +use oxc_minifier::{CompressOptions, Compressor}; use oxc_parser::Parser; use oxc_span::SourceType; -fn codegen(source_text: &str, source_type: SourceType) -> String { - let allocator = Allocator::default(); - let ret = Parser::new(&allocator, source_text, source_type).parse(); - CodeGenerator::new() - .with_options(CodegenOptions { single_quote: true }) - .build(&ret.program) - .source_text -} - -pub(crate) fn minify( - source_text: &str, - source_type: SourceType, - options: MinifierOptions, -) -> String { - let allocator = Allocator::default(); - let ret = Parser::new(&allocator, source_text, source_type).parse(); - let program = allocator.alloc(ret.program); - Minifier::new(options).build(&allocator, program); - CodeGenerator::new() - .with_options(CodegenOptions { single_quote: true }) - .build(program) - .source_text -} - pub(crate) fn test(source_text: &str, expected: &str) { - let options = MinifierOptions { mangle: false, ..MinifierOptions::default() }; + let options = CompressOptions::all_true(); 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 = minify(source_text, source_type, options); - let expected = codegen(expected, source_type); - assert_eq!(expected, minified, "for source {source_text}"); -} - pub(crate) fn test_same(source_text: &str) { test(source_text, source_text); } -pub(crate) fn test_reparse(source_text: &str) { +pub(crate) fn test_with_options(source_text: &str, expected: &str, options: CompressOptions) { let source_type = SourceType::default(); - let options = MinifierOptions { mangle: false, ..MinifierOptions::default() }; - let minified = minify(source_text, source_type, options); - let minified2 = minify(&minified, source_type, options); - assert_eq!(minified, minified2, "for source {source_text}"); + let result = run(source_text, source_type, Some(options)); + let expected = run(expected, source_type, None); + assert_eq!( + result, expected, + "\nfor source {source_text:?}\nexpect {expected:?}\ngot {result:?}" + ); } -pub(crate) fn test_without_compress_booleans(source_text: &str, expected: &str) { - let source_type = SourceType::default(); - let compress_options = CompressOptions { booleans: false, ..CompressOptions::default() }; - let options = MinifierOptions { mangle: false, compress: compress_options }; - let minified = minify(source_text, source_type, options); - assert_eq!(expected, minified, "for source {source_text}"); +fn run(source_text: &str, source_type: SourceType, options: Option) -> String { + let allocator = Allocator::default(); + let ret = Parser::new(&allocator, source_text, source_type).parse(); + let program = allocator.alloc(ret.program); + if let Some(options) = options { + Compressor::new(&allocator, options).build(program); + } + CodeGenerator::new() + .with_options(CodegenOptions { single_quote: true }) + .build(program) + .source_text } pub(crate) fn test_snapshot(name: &str, sources: S) @@ -72,11 +47,11 @@ where S: IntoIterator, { let source_type = SourceType::default(); - let options = MinifierOptions { mangle: false, ..MinifierOptions::default() }; + let options = CompressOptions::all_true(); let snapshot: String = sources .into_iter() .map(|source| { - let minified = minify(source, source_type, options); + let minified = run(source, source_type, Some(options)); format!( "==================================== SOURCE ==================================== {source} diff --git a/crates/oxc_minifier/tests/oxc/code_removal.rs b/crates/oxc_minifier/tests/oxc/code_removal.rs index dcd5fa9ba8a0a9..7f8b4fded35a47 100644 --- a/crates/oxc_minifier/tests/oxc/code_removal.rs +++ b/crates/oxc_minifier/tests/oxc/code_removal.rs @@ -1,4 +1,4 @@ -use crate::{test, test_with_options, CompressOptions, MinifierOptions}; +use crate::{test, test_with_options, CompressOptions}; #[test] fn undefined_assignment() { @@ -20,10 +20,7 @@ fn undefined_return() { #[test] fn console_removal() { - let options = MinifierOptions { - mangle: false, - compress: CompressOptions { drop_console: true, ..CompressOptions::default() }, - }; + let options = CompressOptions { drop_console: true, ..CompressOptions::default() }; test_with_options("console.log('hi')", "", options); test_with_options("let x = console.error('oops')", "let x", options); test_with_options( @@ -34,6 +31,6 @@ fn console_removal() { // console isn't removed when drop_console is `false`. This is also the // default value. - let options = MinifierOptions { mangle: false, ..MinifierOptions::default() }; + let options = CompressOptions::default(); test_with_options("console.log('hi')", "console.log('hi')", options); } diff --git a/crates/oxc_minifier/tests/oxc/folding.rs b/crates/oxc_minifier/tests/oxc/folding.rs index 67e03feb6673f9..a68e3be0811e03 100644 --- a/crates/oxc_minifier/tests/oxc/folding.rs +++ b/crates/oxc_minifier/tests/oxc/folding.rs @@ -1,4 +1,4 @@ -use oxc_minifier::{CompressOptions, MinifierOptions}; +use oxc_minifier::CompressOptions; use crate::{test, test_snapshot, test_with_options}; @@ -42,12 +42,9 @@ fn addition_folding_snapshots() { #[test] fn test_join_vars() { - let options = MinifierOptions { - mangle: false, - compress: CompressOptions { join_vars: false, ..CompressOptions::default() }, - }; + let options = CompressOptions { join_vars: false, ..CompressOptions::default() }; test_with_options("var foo = 1; var bar = 2", "var foo=1;var bar=2", options); // join_vars: true - let options = MinifierOptions::default(); + let options = CompressOptions::default(); test_with_options("var foo = 1; var bar = 2", "var foo=1,bar=2", options); } diff --git a/crates/oxc_minifier/tests/oxc/remove_dead_code.rs b/crates/oxc_minifier/tests/oxc/remove_dead_code.rs index 06b40f5667b62b..783359a4d9e664 100644 --- a/crates/oxc_minifier/tests/oxc/remove_dead_code.rs +++ b/crates/oxc_minifier/tests/oxc/remove_dead_code.rs @@ -1,6 +1,6 @@ use oxc_allocator::Allocator; use oxc_codegen::{CodeGenerator, CodegenOptions}; -use oxc_minifier::RemoveDeadCode; +use oxc_minifier::{CompressOptions, Compressor}; use oxc_parser::Parser; use oxc_span::SourceType; @@ -10,7 +10,7 @@ fn print(source_text: &str, remove_dead_code: bool) -> String { let ret = Parser::new(&allocator, source_text, source_type).parse(); let program = allocator.alloc(ret.program); if remove_dead_code { - RemoveDeadCode::new(&allocator).build(program); + Compressor::new(&allocator, CompressOptions::all_false()).dce(program); } CodeGenerator::new() .with_options(CodegenOptions { single_quote: true }) diff --git a/crates/oxc_wasm/src/lib.rs b/crates/oxc_wasm/src/lib.rs index 10de5c2c2f5abd..07cdf429230948 100644 --- a/crates/oxc_wasm/src/lib.rs +++ b/crates/oxc_wasm/src/lib.rs @@ -275,6 +275,7 @@ impl Oxc { join_vars: compress_options.join_vars(), loops: compress_options.loops(), typeofs: compress_options.typeofs(), + ..CompressOptions::default() } } else { CompressOptions::all_false() diff --git a/tasks/benchmark/Cargo.toml b/tasks/benchmark/Cargo.toml index 0ae885809f1658..f3939694747356 100644 --- a/tasks/benchmark/Cargo.toml +++ b/tasks/benchmark/Cargo.toml @@ -68,6 +68,7 @@ bench = false [dependencies] # All `oxc_*` dependencies optional as on CI we build each benchmark separately # with only the crates it needs, to speed up the builds +oxc_ast = { workspace = true, optional = true } oxc_allocator = { workspace = true, optional = true } oxc_linter = { workspace = true, optional = true } oxc_minifier = { workspace = true, optional = true } @@ -111,7 +112,14 @@ lexer = ["dep:oxc_allocator", "dep:oxc_parser", "dep:oxc_span", "dep:oxc_tasks_c parser = ["dep:oxc_allocator", "dep:oxc_parser", "dep:oxc_span", "dep:oxc_tasks_common"] transformer = ["dep:oxc_allocator", "dep:oxc_parser", "dep:oxc_span", "dep:oxc_tasks_common", "dep:oxc_transformer"] semantic = ["dep:oxc_allocator", "dep:oxc_parser", "dep:oxc_semantic", "dep:oxc_span", "dep:oxc_tasks_common"] -minifier = ["dep:oxc_allocator", "dep:oxc_minifier", "dep:oxc_parser", "dep:oxc_span", "dep:oxc_tasks_common"] +minifier = [ + "dep:oxc_allocator", + "dep:oxc_ast", + "dep:oxc_minifier", + "dep:oxc_parser", + "dep:oxc_span", + "dep:oxc_tasks_common", +] codegen = ["dep:oxc_allocator", "dep:oxc_codegen", "dep:oxc_parser", "dep:oxc_span", "dep:oxc_tasks_common"] sourcemap = [ "dep:oxc_allocator", diff --git a/tasks/benchmark/benches/minifier.rs b/tasks/benchmark/benches/minifier.rs index c9ee9354b3b11c..849910016e79fb 100644 --- a/tasks/benchmark/benches/minifier.rs +++ b/tasks/benchmark/benches/minifier.rs @@ -1,6 +1,7 @@ use oxc_allocator::Allocator; +use oxc_ast::AstBuilder; use oxc_benchmark::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use oxc_minifier::{Minifier, MinifierOptions, RemoveParens}; +use oxc_minifier::{CompressOptions, Minifier, MinifierOptions, RemoveSyntax}; use oxc_parser::Parser; use oxc_span::SourceType; use oxc_tasks_common::TestFiles; @@ -39,7 +40,10 @@ fn bench_passes(criterion: &mut Criterion) { let allocator = Allocator::default(); let program = Parser::new(&allocator, source_text, source_type).parse().program; let program = allocator.alloc(program); - b.iter(|| RemoveParens::new(&allocator).build(program)); + b.iter(|| { + RemoveSyntax::new(AstBuilder::new(&allocator), CompressOptions::all_true()) + .build(program); + }); }, ); } diff --git a/tasks/coverage/minifier_babel.snap b/tasks/coverage/minifier_babel.snap index f525664cad300e..426442c4a58d50 100644 --- a/tasks/coverage/minifier_babel.snap +++ b/tasks/coverage/minifier_babel.snap @@ -2,4 +2,10 @@ commit: 12619ffe minifier_babel Summary: AST Parsed : 1638/1638 (100.00%) -Positive Passed: 1638/1638 (100.00%) +Positive Passed: 1632/1638 (99.63%) +Expect to Parse: "core/uncategorised/313/input.js" +Expect to Parse: "core/uncategorised/314/input.js" +Expect to Parse: "core/uncategorised/315/input.js" +Expect to Parse: "esprima/automatic-semicolon-insertion/migrated_0013/input.js" +Expect to Parse: "esprima/automatic-semicolon-insertion/migrated_0014/input.js" +Expect to Parse: "esprima/automatic-semicolon-insertion/migrated_0015/input.js" diff --git a/tasks/coverage/minifier_test262.snap b/tasks/coverage/minifier_test262.snap index 6f4e52f07c7490..bb888dfae90306 100644 --- a/tasks/coverage/minifier_test262.snap +++ b/tasks/coverage/minifier_test262.snap @@ -2,4 +2,22 @@ commit: a1587416 minifier_test262 Summary: AST Parsed : 46406/46406 (100.00%) -Positive Passed: 46406/46406 (100.00%) +Positive Passed: 46388/46406 (99.96%) +Expect to Parse: "language/expressions/logical-and/S11.11.1_A3_T4.js" +Expect to Parse: "language/expressions/logical-not/S9.2_A1_T2.js" +Expect to Parse: "language/statements/block/S12.1_A5.js" +Expect to Parse: "language/statements/if/S12.5_A1.1_T1.js" +Expect to Parse: "language/statements/if/S12.5_A1.1_T2.js" +Expect to Parse: "language/statements/try/S12.14_A10_T5.js" +Expect to Parse: "language/statements/try/S12.14_A8.js" +Expect to Parse: "language/statements/try/S12.14_A9_T5.js" +Expect to Parse: "language/statements/with/S12.10_A3.10_T3.js" +Expect to Parse: "language/statements/with/S12.10_A3.1_T3.js" +Expect to Parse: "language/statements/with/S12.10_A3.2_T3.js" +Expect to Parse: "language/statements/with/S12.10_A3.2_T5.js" +Expect to Parse: "language/statements/with/S12.10_A3.3_T3.js" +Expect to Parse: "language/statements/with/S12.10_A3.4_T3.js" +Expect to Parse: "language/statements/with/S12.10_A3.5_T3.js" +Expect to Parse: "language/statements/with/S12.10_A3.6_T3.js" +Expect to Parse: "staging/explicit-resource-management/disposable-stack-adopt-and-defer.js" +Expect to Parse: "staging/explicit-resource-management/exception-handling.js" diff --git a/tasks/coverage/src/minifier.rs b/tasks/coverage/src/minifier.rs index b343cf91cf082e..81ac268322e744 100644 --- a/tasks/coverage/src/minifier.rs +++ b/tasks/coverage/src/minifier.rs @@ -1,8 +1,8 @@ use std::path::{Path, PathBuf}; use oxc_allocator::Allocator; -use oxc_codegen::WhitespaceRemover; -use oxc_minifier::{CompressOptions, Minifier, MinifierOptions}; +use oxc_codegen::CodeGenerator; +use oxc_minifier::{CompressOptions, Compressor}; use oxc_parser::Parser; use oxc_span::SourceType; @@ -82,12 +82,8 @@ impl Case for MinifierBabelCase { } // Test minification by minifying twice because it is a idempotent fn get_result(source_text: &str, source_type: SourceType) -> TestResult { - let options = MinifierOptions { - compress: CompressOptions { evaluate: false, ..CompressOptions::default() }, - ..MinifierOptions::default() - }; - let source_text1 = minify(source_text, source_type, options); - let source_text2 = minify(&source_text1, source_type, options); + let source_text1 = minify(source_text, source_type); + let source_text2 = minify(&source_text1, source_type); if source_text1 == source_text2 { TestResult::Passed } else { @@ -95,10 +91,10 @@ fn get_result(source_text: &str, source_type: SourceType) -> TestResult { } } -fn minify(source_text: &str, source_type: SourceType, options: MinifierOptions) -> String { +fn minify(source_text: &str, source_type: SourceType) -> String { let allocator = Allocator::default(); let ret = Parser::new(&allocator, source_text, source_type).parse(); let program = allocator.alloc(ret.program); - Minifier::new(options).build(&allocator, program); - WhitespaceRemover::new().build(program).source_text + Compressor::new(&allocator, CompressOptions::all_true()).build(program); + CodeGenerator::new().build(program).source_text }