From 7110c7b94c744ac9558e6f91a4fa40d76edae1fa Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Wed, 25 Dec 2024 09:19:55 +0000 Subject: [PATCH 001/162] refactor(codegen): add `print_quoted_utf16` and `print_unquoted_utf16` methods (#8107) --- crates/oxc_codegen/src/gen.rs | 125 +-------------------------------- crates/oxc_codegen/src/lib.rs | 126 ++++++++++++++++++++++++++++++---- 2 files changed, 115 insertions(+), 136 deletions(-) diff --git a/crates/oxc_codegen/src/gen.rs b/crates/oxc_codegen/src/gen.rs index 549624b3f1d98e..937524494dd872 100644 --- a/crates/oxc_codegen/src/gen.rs +++ b/crates/oxc_codegen/src/gen.rs @@ -4,7 +4,6 @@ use cow_utils::CowUtils; use oxc_ast::ast::*; use oxc_span::GetSpan; use oxc_syntax::{ - identifier::{LS, PS}, operator::UnaryOperator, precedence::{GetPrecedence, Precedence}, }; @@ -1334,133 +1333,11 @@ impl Gen for RegExpLiteral<'_> { } } -fn print_unquoted_str(s: &str, quote: u8, p: &mut Codegen) { - let mut chars = s.chars().peekable(); - - while let Some(c) = chars.next() { - match c { - '\x00' => { - if chars.peek().is_some_and(|&next| next.is_ascii_digit()) { - p.print_str("\\x00"); - } else { - p.print_str("\\0"); - } - } - '\x07' => { - p.print_str("\\x07"); - } - // \b - '\u{8}' => { - p.print_str("\\b"); - } - // \v - '\u{b}' => { - p.print_str("\\v"); - } - // \f - '\u{c}' => { - p.print_str("\\f"); - } - '\n' => { - p.print_str("\\n"); - } - '\r' => { - p.print_str("\\r"); - } - '\x1B' => { - p.print_str("\\x1B"); - } - '\\' => { - p.print_str("\\\\"); - } - '\'' => { - if quote == b'\'' { - p.print_ascii_byte(b'\\'); - } - p.print_ascii_byte(b'\''); - } - '\"' => { - if quote == b'"' { - p.print_ascii_byte(b'\\'); - } - p.print_ascii_byte(b'"'); - } - '`' => { - if quote == b'`' { - p.print_ascii_byte(b'\\'); - } - p.print_ascii_byte(b'`'); - } - '$' => { - if chars.peek() == Some(&'{') { - p.print_ascii_byte(b'\\'); - } - p.print_ascii_byte(b'$'); - } - // Allow `U+2028` and `U+2029` in string literals - // - // - LS => p.print_str("\\u2028"), - PS => p.print_str("\\u2029"), - '\u{a0}' => { - p.print_str("\\xA0"); - } - _ => { - p.print_str(c.encode_utf8([0; 4].as_mut())); - } - } - } -} - impl Gen for StringLiteral<'_> { fn gen(&self, p: &mut Codegen, _ctx: Context) { p.add_source_mapping(self.span); let s = self.value.as_str(); - - let quote = if p.options.minify { - let mut single_cost: u32 = 0; - let mut double_cost: u32 = 0; - let mut backtick_cost: u32 = 0; - let mut bytes = s.as_bytes().iter().peekable(); - while let Some(b) = bytes.next() { - match b { - b'\n' if p.options.minify => { - backtick_cost = backtick_cost.saturating_sub(1); - } - b'\'' => { - single_cost += 1; - } - b'"' => { - double_cost += 1; - } - b'`' => { - backtick_cost += 1; - } - b'$' => { - if bytes.peek() == Some(&&b'{') { - backtick_cost += 1; - } - } - _ => {} - } - } - let mut quote = b'"'; - if double_cost > single_cost { - quote = b'\''; - if single_cost > backtick_cost { - quote = b'`'; - } - } else if double_cost > backtick_cost { - quote = b'`'; - } - quote - } else { - p.quote - }; - - p.print_ascii_byte(quote); - print_unquoted_str(s, quote, p); - p.print_ascii_byte(quote); + p.print_quoted_utf16(s); } } diff --git a/crates/oxc_codegen/src/lib.rs b/crates/oxc_codegen/src/lib.rs index c26c14f61b84ee..daf8d22ca0129b 100644 --- a/crates/oxc_codegen/src/lib.rs +++ b/crates/oxc_codegen/src/lib.rs @@ -21,7 +21,7 @@ use oxc_ast::ast::{ use oxc_mangler::Mangler; use oxc_span::{GetSpan, Span, SPAN}; use oxc_syntax::{ - identifier::{is_identifier_part, is_identifier_part_ascii}, + identifier::{is_identifier_part, is_identifier_part_ascii, LS, PS}, operator::{BinaryOperator, UnaryOperator, UpdateOperator}, precedence::Precedence, }; @@ -348,6 +348,17 @@ impl<'a> Codegen<'a> { } } + #[inline] + fn wrap(&mut self, wrap: bool, mut f: F) { + if wrap { + self.print_ascii_byte(b'('); + } + f(self); + if wrap { + self.print_ascii_byte(b')'); + } + } + #[inline] fn print_indent(&mut self) { if self.options.minify { @@ -576,6 +587,108 @@ impl<'a> Codegen<'a> { } } + fn print_quoted_utf16(&mut self, s: &str) { + let quote = if self.options.minify { + let mut single_cost: u32 = 0; + let mut double_cost: u32 = 0; + let mut backtick_cost: u32 = 0; + let mut bytes = s.as_bytes().iter().peekable(); + while let Some(b) = bytes.next() { + match b { + b'\n' if self.options.minify => { + backtick_cost = backtick_cost.saturating_sub(1); + } + b'\'' => { + single_cost += 1; + } + b'"' => { + double_cost += 1; + } + b'`' => { + backtick_cost += 1; + } + b'$' => { + if bytes.peek() == Some(&&b'{') { + backtick_cost += 1; + } + } + _ => {} + } + } + let mut quote = b'"'; + if double_cost > single_cost { + quote = b'\''; + if single_cost > backtick_cost { + quote = b'`'; + } + } else if double_cost > backtick_cost { + quote = b'`'; + } + quote + } else { + self.quote + }; + + self.print_ascii_byte(quote); + self.print_unquoted_utf16(s, quote); + self.print_ascii_byte(quote); + } + + fn print_unquoted_utf16(&mut self, s: &str, quote: u8) { + let mut chars = s.chars().peekable(); + + while let Some(c) = chars.next() { + match c { + '\x00' => { + if chars.peek().is_some_and(|&next| next.is_ascii_digit()) { + self.print_str("\\x00"); + } else { + self.print_str("\\0"); + } + } + '\x07' => self.print_str("\\x07"), + '\u{8}' => self.print_str("\\b"), // \b + '\u{b}' => self.print_str("\\v"), // \v + '\u{c}' => self.print_str("\\f"), // \f + '\n' => self.print_str("\\n"), + '\r' => self.print_str("\\r"), + '\x1B' => self.print_str("\\x1B"), + '\\' => self.print_str("\\\\"), + // Allow `U+2028` and `U+2029` in string literals + // + // + LS => self.print_str("\\u2028"), + PS => self.print_str("\\u2029"), + '\u{a0}' => self.print_str("\\xA0"), + '\'' => { + if quote == b'\'' { + self.print_ascii_byte(b'\\'); + } + self.print_ascii_byte(b'\''); + } + '\"' => { + if quote == b'"' { + self.print_ascii_byte(b'\\'); + } + self.print_ascii_byte(b'"'); + } + '`' => { + if quote == b'`' { + self.print_ascii_byte(b'\\'); + } + self.print_ascii_byte(b'`'); + } + '$' => { + if chars.peek() == Some(&'{') { + self.print_ascii_byte(b'\\'); + } + self.print_ascii_byte(b'$'); + } + _ => self.print_str(c.encode_utf8([0; 4].as_mut())), + } + } + } + // `get_minified_number` from terser // https://github.com/terser/terser/blob/c5315c3fd6321d6b2e076af35a70ef532f498505/lib/output.js#L2418 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss, clippy::cast_possible_wrap)] @@ -630,17 +743,6 @@ impl<'a> Codegen<'a> { candidates.into_iter().min_by_key(String::len).unwrap() } - #[inline] - fn wrap(&mut self, wrap: bool, mut f: F) { - if wrap { - self.print_ascii_byte(b'('); - } - f(self); - if wrap { - self.print_ascii_byte(b')'); - } - } - fn add_source_mapping(&mut self, span: Span) { if span == SPAN { return; From bdc241d41d8b1b954cf23bb8d80b8492f7132a9a Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Wed, 25 Dec 2024 11:22:54 +0000 Subject: [PATCH 002/162] fix(codegen): disallow template literals in object property key (#8108) --- crates/oxc_codegen/src/gen.rs | 5 ++++- crates/oxc_codegen/src/lib.rs | 22 +++++++------------- crates/oxc_codegen/tests/integration/unit.rs | 2 ++ 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/crates/oxc_codegen/src/gen.rs b/crates/oxc_codegen/src/gen.rs index 937524494dd872..f5cee102b14d31 100644 --- a/crates/oxc_codegen/src/gen.rs +++ b/crates/oxc_codegen/src/gen.rs @@ -1337,7 +1337,7 @@ impl Gen for StringLiteral<'_> { fn gen(&self, p: &mut Codegen, _ctx: Context) { p.add_source_mapping(self.span); let s = self.value.as_str(); - p.print_quoted_utf16(s); + p.print_quoted_utf16(s, /* allow_backtick */ true); } } @@ -1647,6 +1647,9 @@ impl Gen for PropertyKey<'_> { match self { Self::StaticIdentifier(ident) => ident.print(p, ctx), Self::PrivateIdentifier(ident) => ident.print(p, ctx), + Self::StringLiteral(s) => { + p.print_quoted_utf16(s.value.as_str(), /* allow_backtick */ false); + } match_expression!(Self) => { self.to_expression().print_expr(p, Precedence::Comma, Context::empty()); } diff --git a/crates/oxc_codegen/src/lib.rs b/crates/oxc_codegen/src/lib.rs index daf8d22ca0129b..5fb02ce4306b68 100644 --- a/crates/oxc_codegen/src/lib.rs +++ b/crates/oxc_codegen/src/lib.rs @@ -587,7 +587,7 @@ impl<'a> Codegen<'a> { } } - fn print_quoted_utf16(&mut self, s: &str) { + fn print_quoted_utf16(&mut self, s: &str, allow_backtick: bool) { let quote = if self.options.minify { let mut single_cost: u32 = 0; let mut double_cost: u32 = 0; @@ -595,18 +595,10 @@ impl<'a> Codegen<'a> { let mut bytes = s.as_bytes().iter().peekable(); while let Some(b) = bytes.next() { match b { - b'\n' if self.options.minify => { - backtick_cost = backtick_cost.saturating_sub(1); - } - b'\'' => { - single_cost += 1; - } - b'"' => { - double_cost += 1; - } - b'`' => { - backtick_cost += 1; - } + b'\n' if self.options.minify => backtick_cost = backtick_cost.saturating_sub(1), + b'\'' => single_cost += 1, + b'"' => double_cost += 1, + b'`' => backtick_cost += 1, b'$' => { if bytes.peek() == Some(&&b'{') { backtick_cost += 1; @@ -618,10 +610,10 @@ impl<'a> Codegen<'a> { let mut quote = b'"'; if double_cost > single_cost { quote = b'\''; - if single_cost > backtick_cost { + if single_cost > backtick_cost && allow_backtick { quote = b'`'; } - } else if double_cost > backtick_cost { + } else if double_cost > backtick_cost && allow_backtick { quote = b'`'; } quote diff --git a/crates/oxc_codegen/tests/integration/unit.rs b/crates/oxc_codegen/tests/integration/unit.rs index 1a8525f828e947..36d66059d2d89e 100644 --- a/crates/oxc_codegen/tests/integration/unit.rs +++ b/crates/oxc_codegen/tests/integration/unit.rs @@ -43,6 +43,8 @@ fn expr() { r#";'eval("\'\\vstr\\ving\\v\'") === "\\vstr\\ving\\v"'"#, r#";`eval("'\\vstr\\ving\\v'") === "\\vstr\\ving\\v"`;"#, ); + + test_minify_same(r#"({"http://a\r\" \n<'b:b@c\r\nd/e?f":{}});"#); } #[test] From de82492e97b62f34b6473de3a452762a20b40172 Mon Sep 17 00:00:00 2001 From: camc314 <18101008+camc314@users.noreply.github.com> Date: Wed, 25 Dec 2024 12:24:32 +0000 Subject: [PATCH 003/162] fix(parser): report syntax errors for missing constructor implementations (#8081) --- crates/oxc_semantic/src/builder.rs | 8 ++ crates/oxc_semantic/src/checker/typescript.rs | 20 +++++ .../coverage/snapshots/parser_typescript.snap | 74 ++++++++++++++++--- 3 files changed, 93 insertions(+), 9 deletions(-) diff --git a/crates/oxc_semantic/src/builder.rs b/crates/oxc_semantic/src/builder.rs index b1959b9f839f9f..9503630f0f2ff4 100644 --- a/crates/oxc_semantic/src/builder.rs +++ b/crates/oxc_semantic/src/builder.rs @@ -306,6 +306,14 @@ impl<'a> SemanticBuilder<'a> { self.errors.borrow_mut().push(error); } + pub(crate) fn in_declare_scope(&self) -> bool { + self.source_type.is_typescript_definition() + || self + .scope + .ancestors(self.current_scope_id) + .any(|scope_id| self.scope.get_flags(scope_id).is_ts_module_block()) + } + fn create_ast_node(&mut self, kind: AstKind<'a>) { let mut flags = self.current_node_flags; if self.build_jsdoc && self.jsdoc.retrieve_attached_jsdoc(&kind) { diff --git a/crates/oxc_semantic/src/checker/typescript.rs b/crates/oxc_semantic/src/checker/typescript.rs index ea4f2ed4502da0..cf79788ce3ea83 100644 --- a/crates/oxc_semantic/src/checker/typescript.rs +++ b/crates/oxc_semantic/src/checker/typescript.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; +use itertools::Itertools; use rustc_hash::FxHashMap; use oxc_ast::{ast::*, AstKind}; @@ -330,6 +331,10 @@ fn abstract_elem_in_concrete_class(is_property: bool, span: Span) -> OxcDiagnost .with_label(span) } +fn constructor_implementation_missing(span: Span) -> OxcDiagnostic { + OxcDiagnostic::error("Constructor implementation is missing.").with_label(span) +} + pub fn check_class<'a>(class: &Class<'a>, ctx: &SemanticBuilder<'a>) { if !class.r#abstract { for elem in &class.body.body { @@ -339,6 +344,21 @@ pub fn check_class<'a>(class: &Class<'a>, ctx: &SemanticBuilder<'a>) { } } } + + if !class.r#declare && !ctx.in_declare_scope() { + for (a, b) in class.body.body.iter().map(Some).chain(vec![None]).tuple_windows() { + if let Some(ClassElement::MethodDefinition(a)) = a { + if a.kind.is_constructor() + && a.value.r#type == FunctionType::TSEmptyBodyFunctionExpression + && b.map_or(true, |b| { + b.method_definition_kind().map_or(true, |kind| !kind.is_constructor()) + }) + { + ctx.error(constructor_implementation_missing(a.key.span())); + } + } + } + } } fn abstract_element_cannot_have_initializer( diff --git a/tasks/coverage/snapshots/parser_typescript.snap b/tasks/coverage/snapshots/parser_typescript.snap index c0fde9d6f4c20c..c57359438d4372 100644 --- a/tasks/coverage/snapshots/parser_typescript.snap +++ b/tasks/coverage/snapshots/parser_typescript.snap @@ -3,17 +3,13 @@ commit: d85767ab parser_typescript Summary: AST Parsed : 6494/6503 (99.86%) Positive Passed: 6483/6503 (99.69%) -Negative Passed: 1241/5747 (21.59%) -Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ClassDeclaration10.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ClassDeclaration11.ts +Negative Passed: 1249/5747 (21.73%) Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ClassDeclaration13.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ClassDeclaration14.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ClassDeclaration15.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ClassDeclaration21.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ClassDeclaration22.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ClassDeclaration24.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ClassDeclaration25.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ClassDeclaration8.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ClassDeclaration9.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ExportAssignment7.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ExportAssignment8.ts @@ -3723,11 +3719,8 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ec Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClass1.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClass2.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration1.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration10.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration11.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration12.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration13.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration14.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration15.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration18.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration2.ts @@ -3740,7 +3733,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ec Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration5.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration6.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration7.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration8.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration9.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ComputedPropertyNames/parserES5ComputedPropertyName1.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ComputedPropertyNames/parserES5ComputedPropertyName10.ts @@ -4826,6 +4818,30 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private · ──────────────── ╰──── + × Constructor implementation is missing. + ╭─[typescript/tests/cases/compiler/ClassDeclaration10.ts:2:4] + 1 │ class C { + 2 │ constructor(); + · ─────────── + 3 │ foo(); + ╰──── + + × Constructor implementation is missing. + ╭─[typescript/tests/cases/compiler/ClassDeclaration11.ts:2:4] + 1 │ class C { + 2 │ constructor(); + · ─────────── + 3 │ foo() { } + ╰──── + + × Constructor implementation is missing. + ╭─[typescript/tests/cases/compiler/ClassDeclaration14.ts:3:4] + 2 │ foo(); + 3 │ constructor(); + · ─────────── + 4 │ } + ╰──── + × TS(1248): A class member cannot have the 'const' keyword. ╭─[typescript/tests/cases/compiler/ClassDeclaration26.ts:2:18] 1 │ class C { @@ -4844,6 +4860,14 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private ╰──── help: Try insert a semicolon here + × Constructor implementation is missing. + ╭─[typescript/tests/cases/compiler/ClassDeclaration8.ts:2:3] + 1 │ class C { + 2 │ constructor(); + · ─────────── + 3 │ } + ╰──── + × TS(1248): A class member cannot have the 'const' keyword. ╭─[typescript/tests/cases/compiler/ClassDeclarationWithInvalidConstOnPropertyDeclaration.ts:2:16] 1 │ class AtomicNumbers { @@ -20249,6 +20273,38 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private · ╰── `,` expected ╰──── + × Constructor implementation is missing. + ╭─[typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration10.ts:2:4] + 1 │ class C { + 2 │ constructor(); + · ─────────── + 3 │ foo(); + ╰──── + + × Constructor implementation is missing. + ╭─[typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration11.ts:2:4] + 1 │ class C { + 2 │ constructor(); + · ─────────── + 3 │ foo() { } + ╰──── + + × Constructor implementation is missing. + ╭─[typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration14.ts:3:4] + 2 │ foo(); + 3 │ constructor(); + · ─────────── + 4 │ } + ╰──── + + × Constructor implementation is missing. + ╭─[typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration8.ts:2:3] + 1 │ class C { + 2 │ constructor(); + · ─────────── + 3 │ } + ╰──── + × TS(1164): Computed property names are not allowed in enums. ╭─[typescript/tests/cases/conformance/parser/ecmascript5/ComputedPropertyNames/parserES5ComputedPropertyName6.ts:2:4] 1 │ enum E { From 708e9cfac01ca2c1af0e7332ba0a7f377409e10e Mon Sep 17 00:00:00 2001 From: camc314 <18101008+camc314@users.noreply.github.com> Date: Wed, 25 Dec 2024 12:24:33 +0000 Subject: [PATCH 004/162] fix(semantic): report errors for missing class method impls (#8082) --- crates/oxc_semantic/src/checker/typescript.rs | 26 +- tasks/coverage/snapshots/parser_babel.snap | 104 ++++- .../coverage/snapshots/parser_typescript.snap | 440 ++++++++++++++++-- tasks/coverage/snapshots/semantic_babel.snap | 29 +- .../snapshots/semantic_typescript.snap | 32 +- 5 files changed, 550 insertions(+), 81 deletions(-) diff --git a/crates/oxc_semantic/src/checker/typescript.rs b/crates/oxc_semantic/src/checker/typescript.rs index cf79788ce3ea83..4a35aae3ee2bd6 100644 --- a/crates/oxc_semantic/src/checker/typescript.rs +++ b/crates/oxc_semantic/src/checker/typescript.rs @@ -335,6 +335,13 @@ fn constructor_implementation_missing(span: Span) -> OxcDiagnostic { OxcDiagnostic::error("Constructor implementation is missing.").with_label(span) } +fn function_implementation_missing(span: Span) -> OxcDiagnostic { + OxcDiagnostic::error( + "Function implementation is missing or not immediately following the declaration.", + ) + .with_label(span) +} + pub fn check_class<'a>(class: &Class<'a>, ctx: &SemanticBuilder<'a>) { if !class.r#abstract { for elem in &class.body.body { @@ -348,13 +355,24 @@ pub fn check_class<'a>(class: &Class<'a>, ctx: &SemanticBuilder<'a>) { if !class.r#declare && !ctx.in_declare_scope() { for (a, b) in class.body.body.iter().map(Some).chain(vec![None]).tuple_windows() { if let Some(ClassElement::MethodDefinition(a)) = a { - if a.kind.is_constructor() + if !a.r#type.is_abstract() + && !a.optional && a.value.r#type == FunctionType::TSEmptyBodyFunctionExpression - && b.map_or(true, |b| { - b.method_definition_kind().map_or(true, |kind| !kind.is_constructor()) + && b.map_or(true, |b| match b { + ClassElement::StaticBlock(_) + | ClassElement::PropertyDefinition(_) + | ClassElement::AccessorProperty(_) + | ClassElement::TSIndexSignature(_) => true, + ClassElement::MethodDefinition(b) => { + b.key.static_name() != a.key.static_name() + } }) { - ctx.error(constructor_implementation_missing(a.key.span())); + if a.kind.is_constructor() { + ctx.error(constructor_implementation_missing(a.key.span())); + } else { + ctx.error(function_implementation_missing(a.key.span())); + }; } } } diff --git a/tasks/coverage/snapshots/parser_babel.snap b/tasks/coverage/snapshots/parser_babel.snap index 433ecf0c73e2e7..abfe3e9ffc12d6 100644 --- a/tasks/coverage/snapshots/parser_babel.snap +++ b/tasks/coverage/snapshots/parser_babel.snap @@ -2,7 +2,7 @@ commit: 54a8389f parser_babel Summary: AST Parsed : 2205/2218 (99.41%) -Positive Passed: 2190/2218 (98.74%) +Positive Passed: 2184/2218 (98.47%) Negative Passed: 1522/1634 (93.15%) Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/annex-b/enabled/3.1-sloppy-labeled-functions-if-body/input.js Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/core/categorized/invalid-fn-decl-labeled-inside-if/input.js @@ -304,6 +304,91 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc · ╰── It can not be redeclared here 5 │ f(); ╰──── +Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/class/members-with-modifier-names/input.ts + + × Function implementation is missing or not immediately following the declaration. + ╭─[babel/packages/babel-parser/test/fixtures/typescript/class/members-with-modifier-names/input.ts:2:5] + 1 │ class C { + 2 │ public(): void; + · ────── + 3 │ public static(): void; + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[babel/packages/babel-parser/test/fixtures/typescript/class/members-with-modifier-names/input.ts:3:12] + 2 │ public(): void; + 3 │ public static(): void; + · ────── + 4 │ readonly = 0; + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[babel/packages/babel-parser/test/fixtures/typescript/class/members-with-modifier-names/input.ts:5:5] + 4 │ readonly = 0; + 5 │ async(): void; + · ───── + 6 │ abstract!:void; + ╰──── +Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/class/members-with-modifier-names-babel-7/input.ts + + × Function implementation is missing or not immediately following the declaration. + ╭─[babel/packages/babel-parser/test/fixtures/typescript/class/members-with-modifier-names-babel-7/input.ts:2:5] + 1 │ class C { + 2 │ public(): void; + · ────── + 3 │ public static(): void; + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[babel/packages/babel-parser/test/fixtures/typescript/class/members-with-modifier-names-babel-7/input.ts:3:12] + 2 │ public(): void; + 3 │ public static(): void; + · ────── + 4 │ readonly = 0; + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[babel/packages/babel-parser/test/fixtures/typescript/class/members-with-modifier-names-babel-7/input.ts:5:5] + 4 │ readonly = 0; + 5 │ async(): void; + · ───── + 6 │ abstract!:void; + ╰──── +Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/class/members-with-reserved-names/input.ts + + × Function implementation is missing or not immediately following the declaration. + ╭─[babel/packages/babel-parser/test/fixtures/typescript/class/members-with-reserved-names/input.ts:2:12] + 1 │ class C { + 2 │ public delete(): void; + · ────── + 3 │ } + ╰──── +Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/class/method-no-body/input.ts + + × Function implementation is missing or not immediately following the declaration. + ╭─[babel/packages/babel-parser/test/fixtures/typescript/class/method-no-body/input.ts:3:5] + 2 │ f(); + 3 │ f(): void; + · ─ + 4 │ } + ╰──── +Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/class/method-with-newline-without-body/input.ts + + × Function implementation is missing or not immediately following the declaration. + ╭─[babel/packages/babel-parser/test/fixtures/typescript/class/method-with-newline-without-body/input.ts:3:5] + 2 │ { + 3 │ m() + · ─ + 4 │ n() + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[babel/packages/babel-parser/test/fixtures/typescript/class/method-with-newline-without-body/input.ts:4:5] + 3 │ m() + 4 │ n() + · ─ + 5 │ } + ╰──── Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/class/modifiers-override/input.ts × Identifier `show` has already been declared @@ -399,6 +484,15 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc · ╰── It can not be redeclared here 8 │ } ╰──── +Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/class/static/input.ts + + × Function implementation is missing or not immediately following the declaration. + ╭─[babel/packages/babel-parser/test/fixtures/typescript/class/static/input.ts:5:20] + 4 │ protected static f(); + 5 │ private static f(); + · ─ + 6 │ } + ╰──── Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/declare/eval/input.ts × Cannot assign to 'eval' in strict mode @@ -11600,6 +11694,14 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc × Getters and setters must have an implementation. ╭─[babel/packages/babel-parser/test/fixtures/typescript/class/declare-accessor/input.ts:3:15] 2 │ declare get foo() + 3 │ declare set foo(v) + · ─── + 4 │ } + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[babel/packages/babel-parser/test/fixtures/typescript/class/declare-accessor/input.ts:3:15] + 2 │ declare get foo() 3 │ declare set foo(v) · ─── 4 │ } diff --git a/tasks/coverage/snapshots/parser_typescript.snap b/tasks/coverage/snapshots/parser_typescript.snap index c57359438d4372..b4a45f93a6b50b 100644 --- a/tasks/coverage/snapshots/parser_typescript.snap +++ b/tasks/coverage/snapshots/parser_typescript.snap @@ -2,15 +2,9 @@ commit: d85767ab parser_typescript Summary: AST Parsed : 6494/6503 (99.86%) -Positive Passed: 6483/6503 (99.69%) -Negative Passed: 1249/5747 (21.73%) -Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ClassDeclaration13.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ClassDeclaration15.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ClassDeclaration21.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ClassDeclaration22.ts +Positive Passed: 6481/6503 (99.66%) +Negative Passed: 1275/5747 (22.19%) Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ClassDeclaration24.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ClassDeclaration25.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ClassDeclaration9.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ExportAssignment7.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ExportAssignment8.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/FunctionDeclaration3.ts @@ -335,8 +329,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/classStaticP Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/classTypeParametersInStatics.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/classUsedBeforeInitializedVariables.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/classWithMultipleBaseClasses.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/classWithOverloadImplementationOfWrongName.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/classWithOverloadImplementationOfWrongName2.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/cloduleSplitAcrossFiles.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/cloduleStaticMembers.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/cloduleTest2.ts @@ -487,7 +479,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/crashInYield Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/crashInsourcePropertyIsRelatableToTargetProperty.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/crashIntypeCheckInvocationExpression.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/crashIntypeCheckObjectCreationExpression.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/crashOnMethodSignatures.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/crashRegressionTest.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/dataViewConstructor.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/declFileEmitDeclarationOnlyError1.ts @@ -903,7 +894,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/functionOver Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/functionOverloads40.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/functionOverloads41.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/functionOverloads5.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/functionOverloadsOutOfOrder.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/functionParameterArityMismatch.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/functionSignatureAssignmentCompat1.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/functionToFunctionWithPropError.ts @@ -1086,7 +1076,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/incompatible Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/incompatibleExports2.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/incompatibleGenericTypes.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/incompatibleTypes.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/incorrectClassOverloadChain.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/incorrectNumberOfTypeArgumentsDuringErrorReporting.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/incorrectRecursiveMappedTypeConstraint.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/incrementOnTypeParameter.ts @@ -1107,7 +1096,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/indexedAcces Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/indexedAccessRelation.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/indexedAccessWithFreshObjectLiteral.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/indexedAccessWithVariableElement.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/indexer2A.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/indexerConstraints.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/indirectDiscriminantAndExcessProperty.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/indirectSelfReference.ts @@ -1390,7 +1378,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/mismatchedEx Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/mismatchedGenericArguments1.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/missingCommaInTemplateStringsArray.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/missingDomElements.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/missingFunctionImplementation.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/missingFunctionImplementation2.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/missingMemberErrorHasShortPath.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/missingPropertiesOfClassExpression.ts @@ -1656,7 +1643,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/overloadingO Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/overloadresolutionWithConstraintCheckingDeferred.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/overloadsAndTypeArgumentArityErrors.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/overloadsInDifferentContainersDisagreeOnAmbient.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/overloadsWithComputedNames.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/overloadsWithProvisionalErrors.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/overridingPrivateStaticMembers.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/overshifts.ts @@ -1800,7 +1786,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/recursiveRes Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/recursiveTupleTypeInference.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/recursiveTypeComparison2.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/recursiveTypeParameterConstraintReferenceLacksTypeArgs.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/recursiveTypeRelations.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/recursivelyExpandingUnionNoStackoverflow.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/redefineArray.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/reexportMissingDefault.ts @@ -2352,7 +2337,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/classes/c Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractInheritance2.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractInstantiations1.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractMixedWithModifiers.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractOverloads.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractOverrideWithAbstract.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractProperties.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractSingleLineDecl.ts @@ -2519,7 +2503,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/classes/p Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingConstructorParameters.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/classes/propertyMemberDeclarations/memberFunctionDeclarations/derivedTypeAccessesHiddenBaseCallViaSuperPropertyAccess.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/classes/propertyMemberDeclarations/memberFunctionDeclarations/instanceMemberAssignsToClassPrototype.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/classes/propertyMemberDeclarations/memberFunctionDeclarations/memberFunctionOverloadMixingStaticAndInstance.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/classes/propertyMemberDeclarations/memberFunctionDeclarations/memberFunctionsWithPrivateOverloads.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/classes/propertyMemberDeclarations/memberFunctionDeclarations/memberFunctionsWithPublicPrivateOverloads.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/classes/propertyMemberDeclarations/memberFunctionDeclarations/staticMemberAssignsToConstructorFunctionMembers.ts @@ -2658,7 +2641,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/es6/Symbo Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/es6/Symbols/symbolProperty36.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/es6/Symbols/symbolProperty39.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/es6/Symbols/symbolProperty42.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/es6/Symbols/symbolProperty43.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/es6/Symbols/symbolProperty44.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/es6/Symbols/symbolProperty46.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/es6/Symbols/symbolProperty47.ts @@ -3720,23 +3702,16 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ec Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClass2.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration1.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration12.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration13.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration15.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration18.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration2.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration21.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration22.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration24.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration25.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration3.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration4.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration5.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration6.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration7.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration9.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ComputedPropertyNames/parserES5ComputedPropertyName1.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ComputedPropertyNames/parserES5ComputedPropertyName10.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ComputedPropertyNames/parserES5ComputedPropertyName11.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ComputedPropertyNames/parserES5ComputedPropertyName2.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ComputedPropertyNames/parserES5ComputedPropertyName3.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/ComputedPropertyNames/parserES5ComputedPropertyName4.ts @@ -3939,7 +3914,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ec Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/parserUsingConstructorAsIdentifier.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript5/parservoidInQualifiedName2.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript6/ComputedPropertyNames/parserComputedPropertyName10.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript6/ComputedPropertyNames/parserComputedPropertyName11.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript6/ComputedPropertyNames/parserComputedPropertyName12.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript6/ComputedPropertyNames/parserComputedPropertyName13.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/parser/ecmascript6/ComputedPropertyNames/parserComputedPropertyName14.ts @@ -4791,6 +4765,24 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/moduleResolut · ▲ ╰──── help: Try insert a semicolon here +Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/nonjsExtensions/declarationFileForHtmlFileWithinDeclarationFile.ts + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/conformance/nonjsExtensions/declarationFileForHtmlFileWithinDeclarationFile.ts:11:5] + 10 │ export class HTML5Element extends HTMLElement { + 11 │ connectedCallback(): void; + · ───────────────── + 12 │ } + ╰──── +Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/nonjsExtensions/declarationFileForHtmlImport.ts + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/conformance/nonjsExtensions/declarationFileForHtmlImport.ts:11:5] + 10 │ export class HTML5Element extends HTMLElement { + 11 │ connectedCallback(): void; + · ───────────────── + 12 │ } + ╰──── Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/plainJSRedeclare3.ts × Identifier `orbitol` has already been declared @@ -4826,6 +4818,14 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private 3 │ foo(); ╰──── + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/ClassDeclaration10.ts:3:4] + 2 │ constructor(); + 3 │ foo(); + · ─── + 4 │ } + ╰──── + × Constructor implementation is missing. ╭─[typescript/tests/cases/compiler/ClassDeclaration11.ts:2:4] 1 │ class C { @@ -4834,6 +4834,22 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private 3 │ foo() { } ╰──── + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/ClassDeclaration13.ts:2:4] + 1 │ class C { + 2 │ foo(); + · ─── + 3 │ bar() { } + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/ClassDeclaration14.ts:2:4] + 1 │ class C { + 2 │ foo(); + · ─── + 3 │ constructor(); + ╰──── + × Constructor implementation is missing. ╭─[typescript/tests/cases/compiler/ClassDeclaration14.ts:3:4] 2 │ foo(); @@ -4842,6 +4858,46 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private 4 │ } ╰──── + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/ClassDeclaration15.ts:2:4] + 1 │ class C { + 2 │ foo(); + · ─── + 3 │ constructor() { } + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/ClassDeclaration21.ts:2:5] + 1 │ class C { + 2 │ 0(); + · ─ + 3 │ 1() { } + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/ClassDeclaration22.ts:2:5] + 1 │ class C { + 2 │ "foo"(); + · ───── + 3 │ "bar"() { } + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/ClassDeclaration25.ts:6:5] + 5 │ class List implements IList { + 6 │ data(): U; + · ──── + 7 │ next(): string; + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/ClassDeclaration25.ts:7:5] + 6 │ data(): U; + 7 │ next(): string; + · ──── + 8 │ } + ╰──── + × TS(1248): A class member cannot have the 'const' keyword. ╭─[typescript/tests/cases/compiler/ClassDeclaration26.ts:2:18] 1 │ class C { @@ -4868,6 +4924,14 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private 3 │ } ╰──── + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/ClassDeclaration9.ts:2:4] + 1 │ class C { + 2 │ foo(); + · ─── + 3 │ } + ╰──── + × TS(1248): A class member cannot have the 'const' keyword. ╭─[typescript/tests/cases/compiler/ClassDeclarationWithInvalidConstOnPropertyDeclaration.ts:2:16] 1 │ class AtomicNumbers { @@ -4984,6 +5048,14 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private 16 │ get concreteWithNoBody(): string; ╰──── + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/abstractPropertyNegative.ts:16:9] + 15 │ abstract notAllowed: string; + 16 │ get concreteWithNoBody(): string; + · ────────────────── + 17 │ } + ╰──── + × Unexpected token ╭─[typescript/tests/cases/compiler/accessorBodyInTypeContext.ts:2:15] 1 │ type A = { @@ -6009,6 +6081,30 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private 2 │ } ╰──── + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/classWithOverloadImplementationOfWrongName.ts:3:5] + 2 │ foo(): string; + 3 │ foo(x): number; + · ─── + 4 │ bar(x): any { } + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/classWithOverloadImplementationOfWrongName2.ts:2:5] + 1 │ class C { + 2 │ foo(): string; + · ─── + 3 │ bar(x): any { } + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/classWithOverloadImplementationOfWrongName2.ts:4:5] + 3 │ bar(x): any { } + 4 │ foo(x): number; + · ─── + 5 │ } + ╰──── + × Cannot assign to 'arguments' in strict mode ╭─[typescript/tests/cases/compiler/collisionArgumentsClassConstructor.ts:3:31] 2 │ class c1 { @@ -6995,6 +7091,14 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private 293 │ class interface { } ╰──── + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/crashOnMethodSignatures.ts:2:5] + 1 │ class A { + 2 │ a(completed: () => any): void; + · ─ + 3 │ } + ╰──── + × Unexpected token ╭─[typescript/tests/cases/compiler/createArray.ts:1:19] 1 │ var na=new number[]; @@ -8168,6 +8272,22 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private 3 │ function f() { ╰──── + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/functionOverloadsOutOfOrder.ts:6:13] + 5 │ } + 6 │ private foo(s: string): string; + · ─── + 7 │ } + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/functionOverloadsOutOfOrder.ts:14:13] + 13 │ private foo(s: string): string; + 14 │ private foo(n: number): string; + · ─── + 15 │ } + ╰──── + × Expected `,` but found `var` ╭─[typescript/tests/cases/compiler/functionTypesLackingReturnTypes.ts:6:1] 5 │ // Error (no '=>') @@ -8312,6 +8432,14 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private 2 │ var x = tt; ╰──── + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/incorrectClassOverloadChain.ts:3:5] + 2 │ foo(): string; + 3 │ foo(x): number; + · ─── + 4 │ x = 1; + ╰──── + × Unexpected token ╭─[typescript/tests/cases/compiler/indexSignatureMustHaveTypeAnnotation.ts:4:16] 3 │ [x]: string; @@ -8385,6 +8513,14 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private · ─ ╰──── + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/indexer2A.ts:4:5] + 3 │ // Decided to enforce a semicolon after declarations + 4 │ hasOwnProperty(objectId: number): boolean + · ────────────── + 5 │ [objectId: number]: IHeapObjectProperty[] + ╰──── + × Unexpected token ╭─[typescript/tests/cases/compiler/indexerAsOptional.ts:3:10] 2 │ //Index signatures can't be optional @@ -9385,6 +9521,70 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private 3 │ while( (a2 > 0) && a1 ╰──── + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/missingFunctionImplementation.ts:2:3] + 1 │ export class C1 { + 2 │ m(): void; + · ─ + 3 │ } + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/missingFunctionImplementation.ts:7:3] + 6 │ export class C2 { + 7 │ m(): void; + · ─ + 8 │ } + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/missingFunctionImplementation.ts:15:3] + 14 │ m(a, b); + 15 │ m(a); + · ─ + 16 │ } + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/missingFunctionImplementation.ts:21:10] + 20 │ class C4 { + 21 │ static m(a): void; + · ─ + 22 │ } + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/missingFunctionImplementation.ts:27:10] + 26 │ static m(a): void; + 27 │ static m(): void; + · ─ + 28 │ } + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/missingFunctionImplementation.ts:32:10] + 31 │ class C6 { + 32 │ static m(): void; + · ─ + 33 │ } + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/missingFunctionImplementation.ts:40:10] + 39 │ static m(a): void; + 40 │ static m(): void; + · ─ + 41 │ } + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/missingFunctionImplementation.ts:48:10] + 47 │ static m(a): void; + 48 │ static m(a, b): void; + · ─ + 49 │ } + ╰──── + × The only valid meta property for new is new.target ╭─[typescript/tests/cases/compiler/misspelledNewMetaProperty.ts:1:16] 1 │ function foo(){new.targ} @@ -10238,6 +10438,38 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private 2 │ static test() ╰──── + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/overloadsWithComputedNames.ts:3:6] + 2 │ class Person { + 3 │ ["B"](a: number): string; + · ─── + 4 │ ["A"](a: string|number): number | string { + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/overloadsWithComputedNames.ts:14:6] + 13 │ class C { + 14 │ ["foo"](): void + · ───── + 15 │ ["bar"](): void; + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/overloadsWithComputedNames.ts:15:6] + 14 │ ["foo"](): void + 15 │ ["bar"](): void; + · ───── + 16 │ ["foo"]() { + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/overloadsWithComputedNames.ts:52:6] + 51 │ class C3 { + 52 │ [1](): void; // should error + · ─ + 53 │ [2](): void; + ╰──── + × Identifier `fnOverload` has already been declared ╭─[typescript/tests/cases/compiler/overloadsWithinClasses.ts:3:12] 2 │ @@ -10828,6 +11060,14 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private 6 │ ╰──── + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/compiler/recursiveTypeRelations.ts:8:5] + 7 │ class Query> { + 8 │ multiply>(x: B): Query; + · ──────── + 9 │ } + ╰──── + × Identifier `e` has already been declared ╭─[typescript/tests/cases/compiler/redeclareParameterInCatchBlock.ts:3:9] 2 │ @@ -13835,6 +14075,14 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private 9 │ } ╰──── + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractInstantiations2.ts:46:5] + 45 │ abstract nom(): boolean; + 46 │ nom(x : number): boolean; // error -- use of modifier abstract must match on all overloads. + · ─── + 47 │ } + ╰──── + × TS(1244): Abstract methods can only appear within an abstract class. ╭─[typescript/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractInstantiations2.ts:50:14] 49 │ class H { // error -- not declared abstract @@ -13923,6 +14171,14 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private 3 │ } ╰──── + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractOverloads.ts:15:5] + 14 │ + 15 │ qux(); + · ─── + 16 │ } + ╰──── + × TS(1244): Abstract methods can only appear within an abstract class. ╭─[typescript/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractUsingAbstractMethods2.ts:2:14] 1 │ class A { @@ -15283,6 +15539,38 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private 12 │ }; ╰──── + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/conformance/classes/propertyMemberDeclarations/memberFunctionDeclarations/memberFunctionOverloadMixingStaticAndInstance.ts:3:12] + 2 │ foo(); + 3 │ static foo(); // error + · ─── + 4 │ } + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/conformance/classes/propertyMemberDeclarations/memberFunctionDeclarations/memberFunctionOverloadMixingStaticAndInstance.ts:8:5] + 7 │ static foo(); + 8 │ foo(); // error + · ─── + 9 │ } + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/conformance/classes/propertyMemberDeclarations/memberFunctionDeclarations/memberFunctionOverloadMixingStaticAndInstance.ts:13:12] + 12 │ foo(x: T); + 13 │ static foo(x: number); // error + · ─── + 14 │ } + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/conformance/classes/propertyMemberDeclarations/memberFunctionDeclarations/memberFunctionOverloadMixingStaticAndInstance.ts:18:5] + 17 │ static foo(x: number); + 18 │ foo(x: T); // error + · ─── + 19 │ } + ╰──── + × Identifier `x` has already been declared ╭─[typescript/tests/cases/conformance/classes/propertyMemberDeclarations/propertyAndAccessorWithSameName.ts:2:5] 1 │ class C { @@ -15601,6 +15889,14 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private 9 │ const z = tag`\u{hello} \xtraordinary wonderful \uworld` // should work with Tagged NoSubstitutionTemplate ╰──── + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/conformance/es6/Symbols/symbolProperty43.ts:3:6] + 2 │ [Symbol.iterator](x: string): string; + 3 │ [Symbol.iterator](x: number): number; + · ─────────────── + 4 │ } + ╰──── + × Line terminator not permitted before arrow ╭─[typescript/tests/cases/conformance/es6/arrowFunction/disallowLineTerminatorBeforeArrow.ts:2:5] 1 │ var f1 = () @@ -20281,6 +20577,14 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private 3 │ foo(); ╰──── + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration10.ts:3:4] + 2 │ constructor(); + 3 │ foo(); + · ─── + 4 │ } + ╰──── + × Constructor implementation is missing. ╭─[typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration11.ts:2:4] 1 │ class C { @@ -20289,6 +20593,22 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private 3 │ foo() { } ╰──── + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration13.ts:2:4] + 1 │ class C { + 2 │ foo(); + · ─── + 3 │ bar() { } + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration14.ts:2:4] + 1 │ class C { + 2 │ foo(); + · ─── + 3 │ constructor(); + ╰──── + × Constructor implementation is missing. ╭─[typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration14.ts:3:4] 2 │ foo(); @@ -20297,6 +20617,46 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private 4 │ } ╰──── + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration15.ts:2:4] + 1 │ class C { + 2 │ foo(); + · ─── + 3 │ constructor() { } + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration21.ts:2:5] + 1 │ class C { + 2 │ 0(); + · ─ + 3 │ 1() { } + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration22.ts:2:5] + 1 │ class C { + 2 │ "foo"(); + · ───── + 3 │ "bar"() { } + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration25.ts:6:5] + 5 │ class List implements IList { + 6 │ data(): U; + · ──── + 7 │ next(): string; + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration25.ts:7:5] + 6 │ data(): U; + 7 │ next(): string; + · ──── + 8 │ } + ╰──── + × Constructor implementation is missing. ╭─[typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration8.ts:2:3] 1 │ class C { @@ -20305,6 +20665,22 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private 3 │ } ╰──── + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/conformance/parser/ecmascript5/ClassDeclarations/parserClassDeclaration9.ts:2:4] + 1 │ class C { + 2 │ foo(); + · ─── + 3 │ } + ╰──── + + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/conformance/parser/ecmascript5/ComputedPropertyNames/parserES5ComputedPropertyName11.ts:2:5] + 1 │ class C { + 2 │ [e](); + · ─ + 3 │ } + ╰──── + × TS(1164): Computed property names are not allowed in enums. ╭─[typescript/tests/cases/conformance/parser/ecmascript5/ComputedPropertyNames/parserES5ComputedPropertyName6.ts:2:4] 1 │ enum E { @@ -22482,6 +22858,14 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private · ─ ╰──── + × Function implementation is missing or not immediately following the declaration. + ╭─[typescript/tests/cases/conformance/parser/ecmascript6/ComputedPropertyNames/parserComputedPropertyName11.ts:2:5] + 1 │ class C { + 2 │ [e](); + · ─ + 3 │ } + ╰──── + × TS(1164): Computed property names are not allowed in enums. ╭─[typescript/tests/cases/conformance/parser/ecmascript6/ComputedPropertyNames/parserComputedPropertyName16.ts:2:4] 1 │ enum E { diff --git a/tasks/coverage/snapshots/semantic_babel.snap b/tasks/coverage/snapshots/semantic_babel.snap index b92ade877ba707..87e25ca4b1421b 100644 --- a/tasks/coverage/snapshots/semantic_babel.snap +++ b/tasks/coverage/snapshots/semantic_babel.snap @@ -354,19 +354,17 @@ after transform: ["T", "X"] rebuilt : [] tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/class/members-with-modifier-names/input.ts -semantic error: Scope children mismatch: -after transform: ScopeId(1): [ScopeId(2), ScopeId(3), ScopeId(4)] -rebuilt : ScopeId(1): [] +semantic error: Function implementation is missing or not immediately following the declaration. +Function implementation is missing or not immediately following the declaration. +Function implementation is missing or not immediately following the declaration. tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/class/members-with-modifier-names-babel-7/input.ts -semantic error: Scope children mismatch: -after transform: ScopeId(1): [ScopeId(2), ScopeId(3), ScopeId(4)] -rebuilt : ScopeId(1): [] +semantic error: Function implementation is missing or not immediately following the declaration. +Function implementation is missing or not immediately following the declaration. +Function implementation is missing or not immediately following the declaration. tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/class/members-with-reserved-names/input.ts -semantic error: Scope children mismatch: -after transform: ScopeId(1): [ScopeId(2)] -rebuilt : ScopeId(1): [] +semantic error: Function implementation is missing or not immediately following the declaration. tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/class/method-computed/input.ts semantic error: Scope children mismatch: @@ -377,14 +375,11 @@ after transform: ["Symbol"] rebuilt : [] tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/class/method-no-body/input.ts -semantic error: Scope children mismatch: -after transform: ScopeId(1): [ScopeId(2), ScopeId(3)] -rebuilt : ScopeId(1): [] +semantic error: Function implementation is missing or not immediately following the declaration. tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/class/method-with-newline-without-body/input.ts -semantic error: Scope children mismatch: -after transform: ScopeId(1): [ScopeId(2), ScopeId(3)] -rebuilt : ScopeId(1): [] +semantic error: Function implementation is missing or not immediately following the declaration. +Function implementation is missing or not immediately following the declaration. tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/class/modifiers-accessors/input.ts semantic error: Scope children mismatch: @@ -416,9 +411,7 @@ Identifier `x` has already been declared Identifier `x` has already been declared tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/class/static/input.ts -semantic error: Scope children mismatch: -after transform: ScopeId(1): [ScopeId(2), ScopeId(3), ScopeId(4), ScopeId(5)] -rebuilt : ScopeId(1): [] +semantic error: Function implementation is missing or not immediately following the declaration. tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/const/initializer-ambient-context/input.ts semantic error: Bindings mismatch: diff --git a/tasks/coverage/snapshots/semantic_typescript.snap b/tasks/coverage/snapshots/semantic_typescript.snap index 2847908bf50aca..d87e4bac544377 100644 --- a/tasks/coverage/snapshots/semantic_typescript.snap +++ b/tasks/coverage/snapshots/semantic_typescript.snap @@ -46350,38 +46350,10 @@ after transform: ["const"] rebuilt : [] tasks/coverage/typescript/tests/cases/conformance/nonjsExtensions/declarationFileForHtmlFileWithinDeclarationFile.ts -semantic error: Bindings mismatch: -after transform: ScopeId(0): ["HTML5Element", "blogPost", "doc"] -rebuilt : ScopeId(0): ["HTML5Element", "blogPost"] -Scope children mismatch: -after transform: ScopeId(1): [ScopeId(2)] -rebuilt : ScopeId(1): [] -Reference symbol mismatch for "doc": -after transform: SymbolId(0) "doc" -rebuilt : -Reference flags mismatch for "doc": -after transform: ReferenceId(1): ReferenceFlags(Read) -rebuilt : ReferenceId(0): ReferenceFlags(Read | Type) -Unresolved references mismatch: -after transform: ["Document", "Element", "HTMLElement"] -rebuilt : ["HTMLElement", "doc"] +semantic error: Function implementation is missing or not immediately following the declaration. tasks/coverage/typescript/tests/cases/conformance/nonjsExtensions/declarationFileForHtmlImport.ts -semantic error: Bindings mismatch: -after transform: ScopeId(0): ["HTML5Element", "blogPost", "doc"] -rebuilt : ScopeId(0): ["HTML5Element", "blogPost"] -Scope children mismatch: -after transform: ScopeId(1): [ScopeId(2)] -rebuilt : ScopeId(1): [] -Reference symbol mismatch for "doc": -after transform: SymbolId(0) "doc" -rebuilt : -Reference flags mismatch for "doc": -after transform: ReferenceId(1): ReferenceFlags(Read) -rebuilt : ReferenceId(0): ReferenceFlags(Read | Type) -Unresolved references mismatch: -after transform: ["Document", "Element", "HTMLElement"] -rebuilt : ["HTMLElement", "doc"] +semantic error: Function implementation is missing or not immediately following the declaration. tasks/coverage/typescript/tests/cases/conformance/nonjsExtensions/declarationFileForJsonImport.ts semantic error: Bindings mismatch: From 11c4bd868788de60a31c4c01397a92a96e873c11 Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Wed, 25 Dec 2024 12:24:34 +0000 Subject: [PATCH 005/162] feat(span): implement source type `{file basename}.d.{extension}.ts` (#8109) --- crates/oxc_span/src/source_type/mod.rs | 54 +++++++++++-------- .../coverage/snapshots/parser_typescript.snap | 20 +------ .../snapshots/semantic_typescript.snap | 48 +++++------------ 3 files changed, 46 insertions(+), 76 deletions(-) diff --git a/crates/oxc_span/src/source_type/mod.rs b/crates/oxc_span/src/source_type/mod.rs index f58acb3a80b924..ee22fd1fa72a70 100644 --- a/crates/oxc_span/src/source_type/mod.rs +++ b/crates/oxc_span/src/source_type/mod.rs @@ -468,25 +468,35 @@ impl SourceType { ) })?; - let (language, module_kind) = match extension { - "js" | "mjs" | "jsx" => (Language::JavaScript, ModuleKind::Module), - "cjs" => (Language::JavaScript, ModuleKind::Script), - "ts" if file_name.ends_with(".d.ts") => { - (Language::TypeScriptDefinition, ModuleKind::Module) - } - "mts" if file_name.ends_with(".d.mts") => { - (Language::TypeScriptDefinition, ModuleKind::Module) + let module_kind = match extension { + "js" | "tsx" | "ts" | "jsx" | "mts" | "mjs" => ModuleKind::Module, + "cjs" | "cts" => ModuleKind::Script, + _ => unreachable!(), + }; + + let language = match extension { + // https://www.typescriptlang.org/tsconfig/#allowArbitraryExtensions + // `{file basename}.d.{extension}.ts` + // https://github.com/microsoft/TypeScript/issues/50133 + "ts" => { + if file_name[..file_name.len() - 3].split('.').rev().take(2).any(|c| c == "d") { + Language::TypeScriptDefinition + } else { + Language::TypeScript + } } - "cts" if file_name.ends_with(".d.cts") => { - (Language::TypeScriptDefinition, ModuleKind::Script) + "js" | "cjs" | "mjs" | "jsx" => Language::JavaScript, + "tsx" => Language::TypeScript, + #[allow(clippy::case_sensitive_file_extension_comparisons)] + "mts" | "cts" => { + if file_name[..file_name.len() - 4].ends_with(".d") { + Language::TypeScriptDefinition + } else { + Language::TypeScript + } } - "ts" | "mts" | "tsx" => (Language::TypeScript, ModuleKind::Module), - "cts" => (Language::TypeScript, ModuleKind::Script), _ => { - #[cfg(debug_assertions)] unreachable!(); - #[cfg(not(debug_assertions))] - return Err(UnknownExtension(format!("Unknown extension: {extension}").into())); } }; @@ -547,14 +557,12 @@ mod tests { #[test] #[allow(clippy::similar_names)] fn test_d_ts_from_path() { - let dts = SourceType::from_path("foo.d.ts") - .expect("foo.d.ts should be a valid TypeScript definition file path."); - let dmts = SourceType::from_path("foo.d.mts") - .expect("foo.d.mts should be a valid TypeScript definition file path."); - let dcts = SourceType::from_path("foo.d.cts") - .expect("foo.d.cts should be a valid TypeScript definition file path."); - - for ty in &[dts, dmts, dcts] { + let dts = SourceType::from_path("foo.d.ts").unwrap(); + let dmts = SourceType::from_path("foo.d.mts").unwrap(); + let dcts = SourceType::from_path("foo.d.cts").unwrap(); + let arbitrary = SourceType::from_path("foo.d.ext.ts").unwrap(); + + for ty in &[dts, dmts, dcts, arbitrary] { assert!(ty.is_typescript()); assert!(ty.is_typescript_definition()); assert!(!ty.is_javascript()); diff --git a/tasks/coverage/snapshots/parser_typescript.snap b/tasks/coverage/snapshots/parser_typescript.snap index b4a45f93a6b50b..9cbe6b1320bce0 100644 --- a/tasks/coverage/snapshots/parser_typescript.snap +++ b/tasks/coverage/snapshots/parser_typescript.snap @@ -2,7 +2,7 @@ commit: d85767ab parser_typescript Summary: AST Parsed : 6494/6503 (99.86%) -Positive Passed: 6481/6503 (99.66%) +Positive Passed: 6483/6503 (99.69%) Negative Passed: 1275/5747 (22.19%) Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ClassDeclaration24.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ExportAssignment7.ts @@ -4765,24 +4765,6 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/moduleResolut · ▲ ╰──── help: Try insert a semicolon here -Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/nonjsExtensions/declarationFileForHtmlFileWithinDeclarationFile.ts - - × Function implementation is missing or not immediately following the declaration. - ╭─[typescript/tests/cases/conformance/nonjsExtensions/declarationFileForHtmlFileWithinDeclarationFile.ts:11:5] - 10 │ export class HTML5Element extends HTMLElement { - 11 │ connectedCallback(): void; - · ───────────────── - 12 │ } - ╰──── -Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/nonjsExtensions/declarationFileForHtmlImport.ts - - × Function implementation is missing or not immediately following the declaration. - ╭─[typescript/tests/cases/conformance/nonjsExtensions/declarationFileForHtmlImport.ts:11:5] - 10 │ export class HTML5Element extends HTMLElement { - 11 │ connectedCallback(): void; - · ───────────────── - 12 │ } - ╰──── Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/plainJSRedeclare3.ts × Identifier `orbitol` has already been declared diff --git a/tasks/coverage/snapshots/semantic_typescript.snap b/tasks/coverage/snapshots/semantic_typescript.snap index d87e4bac544377..10f3a3f22ab560 100644 --- a/tasks/coverage/snapshots/semantic_typescript.snap +++ b/tasks/coverage/snapshots/semantic_typescript.snap @@ -2,7 +2,7 @@ commit: d85767ab semantic_typescript Summary: AST Parsed : 6503/6503 (100.00%) -Positive Passed: 2848/6503 (43.80%) +Positive Passed: 2852/6503 (43.86%) tasks/coverage/typescript/tests/cases/compiler/2dArrays.ts semantic error: Symbol reference IDs mismatch for "Cell": after transform: SymbolId(0): [ReferenceId(1)] @@ -8118,11 +8118,6 @@ semantic error: Scope children mismatch: after transform: ScopeId(0): [ScopeId(1), ScopeId(2)] rebuilt : ScopeId(0): [ScopeId(1)] -tasks/coverage/typescript/tests/cases/compiler/declarationEmitTransitiveImportOfHtmlDeclarationItem.ts -semantic error: Scope children mismatch: -after transform: ScopeId(0): [ScopeId(1)] -rebuilt : ScopeId(0): [] - tasks/coverage/typescript/tests/cases/compiler/declarationEmitTripleSlashReferenceAmbientModule.ts semantic error: Unresolved references mismatch: after transform: ["Url"] @@ -20383,15 +20378,12 @@ rebuilt : ScopeId(0): [ScopeId(1), ScopeId(2)] tasks/coverage/typescript/tests/cases/compiler/moduleAugmentationsImports1.ts semantic error: Bindings mismatch: -after transform: ScopeId(0): ["./a", "A", "B", "Cls"] -rebuilt : ScopeId(0): ["A"] -Scope children mismatch: -after transform: ScopeId(0): [ScopeId(1), ScopeId(2), ScopeId(3), ScopeId(6)] -rebuilt : ScopeId(0): [ScopeId(1), ScopeId(2)] +after transform: ScopeId(0): ["A", "a", "b", "c"] +rebuilt : ScopeId(0): ["a", "b", "c"] tasks/coverage/typescript/tests/cases/compiler/moduleAugmentationsImports2.ts semantic error: Bindings mismatch: -after transform: ScopeId(0): ["./a", "A", "B"] +after transform: ScopeId(0): ["./a", "A", "Cls"] rebuilt : ScopeId(0): ["A"] Scope children mismatch: after transform: ScopeId(0): [ScopeId(1), ScopeId(2)] @@ -46349,30 +46341,18 @@ semantic error: Unresolved references mismatch: after transform: ["const"] rebuilt : [] -tasks/coverage/typescript/tests/cases/conformance/nonjsExtensions/declarationFileForHtmlFileWithinDeclarationFile.ts -semantic error: Function implementation is missing or not immediately following the declaration. - -tasks/coverage/typescript/tests/cases/conformance/nonjsExtensions/declarationFileForHtmlImport.ts -semantic error: Function implementation is missing or not immediately following the declaration. - -tasks/coverage/typescript/tests/cases/conformance/nonjsExtensions/declarationFileForJsonImport.ts -semantic error: Bindings mismatch: -after transform: ScopeId(0): ["val"] -rebuilt : ScopeId(0): [] -Reference symbol mismatch for "val": -after transform: SymbolId(0) "val" -rebuilt : -Reference flags mismatch for "val": -after transform: ReferenceId(0): ReferenceFlags(Read) -rebuilt : ReferenceId(0): ReferenceFlags(Read | Type) +tasks/coverage/typescript/tests/cases/conformance/nonjsExtensions/declarationFilesForNodeNativeModules.ts +semantic error: Missing SymbolId: "mod" +Missing ReferenceId: "require" +Binding symbols mismatch: +after transform: ScopeId(0): [SymbolId(0)] +rebuilt : ScopeId(0): [SymbolId(0)] +Reference symbol mismatch for "mod": +after transform: SymbolId(0) "mod" +rebuilt : SymbolId(0) "mod" Unresolved references mismatch: after transform: [] -rebuilt : ["val"] - -tasks/coverage/typescript/tests/cases/conformance/nonjsExtensions/declarationFilesForNodeNativeModules.ts -semantic error: Scope children mismatch: -after transform: ScopeId(0): [ScopeId(1)] -rebuilt : ScopeId(0): [] +rebuilt : ["require"] tasks/coverage/typescript/tests/cases/conformance/override/override10.ts semantic error: Scope children mismatch: From 5a1311e76ca0a464cd69ec4fe87ced388870f08d Mon Sep 17 00:00:00 2001 From: oxc-bot Date: Wed, 25 Dec 2024 21:03:09 +0800 Subject: [PATCH 006/162] release(crates): v0.44.0 (#8110) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [0.44.0] - 2024-12-25 - ad2a620 ast: [**BREAKING**] Add missing `AssignmentTargetProperty::computed` (#8097) (Boshen) ### Features - c2daa20 ast: Add `Expression::into_inner_expression` (#8048) (overlookmotel) - 618b6aa codege: Minify whitespace in object getter / setter (#8080) (Boshen) - 4727667 codegen: Minify arrow expr `(x) => y` -> `x => y` (#8078) (Boshen) - 0562830 codegen: Minify string with backtick when needed (#8095) (Boshen) - 6237c05 codegen: Minify more whitespace (#8089) (Boshen) - 6355b7c codegen: Minify `export { 's' as 's' }` -> `export { 's' }` (#8093) (Boshen) - fccfda9 codegen: Minify `class{static[computed]}` (#8088) (Boshen) - f873139 codegen: Minify `for (_ of [])` -> `for(_ of[])` (#8086) (Boshen) - 8b8cbcd codegen: Minify `case "foo"` -> `case"foo"` (#8085) (Boshen) - 414c118 codegen: Minify `yield "s"` -> `yield"s"` (#8084) (Boshen) - f8f067b codegen: Minify class method `async*fn(){}` (#8083) (Boshen) - 1d5ae81 codegen: Minify `const [foo] = bar` -> `const[foo]=bar` (#8079) (Boshen) - e3f78fb codegen: `new Foo()` -> `new Foo` when minify (#8077) (Boshen) - d84d60a codegen: Minify numbers with large exponents (#8074) (Boshen) - 373279b codegen: Balance string quotes when minify whitespace (#8072) (Boshen) - 5397fe9 minifier: Constant fold `undefined?.bar` -> `undefined` (#8075) (Boshen) - 1932f1e minifier: Fold `foo === undefined || foo === null` (#8063) (翠 / green) - 11c4bd8 span: Implement source type `{file basename}.d.{extension}.ts` (#8109) (Boshen) - be4feb4 syntax: Add `SymbolId::new` method (#8041) (overlookmotel) - e632a7b transformer: Remove typescript symbols after transform (#8069) (Boshen) ### Bug Fixes - bdc241d codegen: Disallow template literals in object property key (#8108) (Boshen) - 728ed20 codegen: Print `yield * ident` correctly (Boshen) - b605baa minifier: Constant fold strings with tab char (#8096) (Boshen) - de82492 parser: Report syntax errors for missing constructor implementations (#8081) (camc314) - 55d6eb9 parser: Disallow type parameters on class constructors (#8071) (injuly) - be2c60d parser: Parse `import source from from 'mod'` (#8056) (Boshen) - 708e9cf semantic: Report errors for missing class method impls (#8082) (camc314) - 3057686 transformer/class-properties: Unwrap parenthesised expressions (#8049) (overlookmotel) - e67cd05 transformer/class-properties: Correctly resolve private fields pointing to private accessors (#8047) (overlookmotel) - 6b08c6e transformer/class-properties: Correctly resolve private fields pointing to private methods (#8042) (overlookmotel) - 274f117 transformer/nullish-coalescing: Use correct scope id for binding (#8053) (camc314) ### Performance - 78d2e83 sourcemap: Improve perf of `search_original_line_and_column` (#7926) (Cameron) ### Refactor - 7110c7b codegen: Add `print_quoted_utf16` and `print_unquoted_utf16` methods (#8107) (Boshen) - 8b54d89 minifier: Remove parens must happen on enter (#8060) (Boshen) - 7cb84f3 minifier: Only minify on ast node exit (#8059) (Boshen) - 77d845a minifier: Fuse DCE AST passes (#8058) (Boshen) - 6123f5e minifier: Fold statements on exit (#8057) (Boshen) - cbd5169 transformer/class-properties: Do not recreate private field if not transforming it (#8044) (overlookmotel) - 98e8a72 transformer/class-properties: Do not take mut ref when immut ref will do (#8040) (overlookmotel) Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com> --- Cargo.lock | 44 ++++++++++----------- Cargo.toml | 44 ++++++++++----------- crates/oxc/Cargo.toml | 2 +- crates/oxc_allocator/Cargo.toml | 2 +- crates/oxc_ast/CHANGELOG.md | 11 ++++++ crates/oxc_ast/Cargo.toml | 2 +- crates/oxc_ast_macros/Cargo.toml | 2 +- crates/oxc_cfg/Cargo.toml | 2 +- crates/oxc_codegen/CHANGELOG.md | 34 ++++++++++++++++ crates/oxc_codegen/Cargo.toml | 2 +- crates/oxc_data_structures/Cargo.toml | 2 +- crates/oxc_diagnostics/Cargo.toml | 2 +- crates/oxc_ecmascript/CHANGELOG.md | 6 +++ crates/oxc_ecmascript/Cargo.toml | 2 +- crates/oxc_estree/Cargo.toml | 2 +- crates/oxc_isolated_declarations/Cargo.toml | 2 +- crates/oxc_mangler/Cargo.toml | 2 +- crates/oxc_minifier/CHANGELOG.md | 18 +++++++++ crates/oxc_minifier/Cargo.toml | 2 +- crates/oxc_napi/Cargo.toml | 2 +- crates/oxc_parser/CHANGELOG.md | 9 +++++ crates/oxc_parser/Cargo.toml | 2 +- crates/oxc_regular_expression/Cargo.toml | 2 +- crates/oxc_semantic/CHANGELOG.md | 11 ++++++ crates/oxc_semantic/Cargo.toml | 2 +- crates/oxc_span/CHANGELOG.md | 6 +++ crates/oxc_span/Cargo.toml | 2 +- crates/oxc_syntax/CHANGELOG.md | 6 +++ crates/oxc_syntax/Cargo.toml | 2 +- crates/oxc_transformer/CHANGELOG.md | 18 +++++++++ crates/oxc_transformer/Cargo.toml | 2 +- crates/oxc_traverse/CHANGELOG.md | 11 ++++++ crates/oxc_traverse/Cargo.toml | 2 +- napi/transform/Cargo.toml | 2 +- npm/oxc-parser/package.json | 2 +- npm/oxc-transform/package.json | 2 +- npm/oxc-types/CHANGELOG.md | 7 ++++ npm/oxc-types/package.json | 2 +- wasm/parser/package.json | 2 +- 39 files changed, 207 insertions(+), 70 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d1d2be451c7269..886f7c39b7980f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1384,7 +1384,7 @@ checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56" [[package]] name = "oxc" -version = "0.43.0" +version = "0.44.0" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1446,7 +1446,7 @@ dependencies = [ [[package]] name = "oxc_allocator" -version = "0.43.0" +version = "0.44.0" dependencies = [ "allocator-api2", "bumpalo", @@ -1457,7 +1457,7 @@ dependencies = [ [[package]] name = "oxc_ast" -version = "0.43.0" +version = "0.44.0" dependencies = [ "bitflags 2.6.0", "cow-utils", @@ -1475,7 +1475,7 @@ dependencies = [ [[package]] name = "oxc_ast_macros" -version = "0.43.0" +version = "0.44.0" dependencies = [ "proc-macro2", "quote", @@ -1523,7 +1523,7 @@ dependencies = [ [[package]] name = "oxc_cfg" -version = "0.43.0" +version = "0.44.0" dependencies = [ "bitflags 2.6.0", "itertools", @@ -1536,7 +1536,7 @@ dependencies = [ [[package]] name = "oxc_codegen" -version = "0.43.0" +version = "0.44.0" dependencies = [ "assert-unchecked", "base64", @@ -1597,7 +1597,7 @@ dependencies = [ [[package]] name = "oxc_data_structures" -version = "0.43.0" +version = "0.44.0" dependencies = [ "assert-unchecked", "ropey", @@ -1605,7 +1605,7 @@ dependencies = [ [[package]] name = "oxc_diagnostics" -version = "0.43.0" +version = "0.44.0" dependencies = [ "oxc-miette", "rustc-hash", @@ -1613,7 +1613,7 @@ dependencies = [ [[package]] name = "oxc_ecmascript" -version = "0.43.0" +version = "0.44.0" dependencies = [ "num-bigint", "num-traits", @@ -1624,7 +1624,7 @@ dependencies = [ [[package]] name = "oxc_estree" -version = "0.43.0" +version = "0.44.0" dependencies = [ "serde", ] @@ -1641,7 +1641,7 @@ dependencies = [ [[package]] name = "oxc_isolated_declarations" -version = "0.43.0" +version = "0.44.0" dependencies = [ "bitflags 2.6.0", "insta", @@ -1741,7 +1741,7 @@ dependencies = [ [[package]] name = "oxc_mangler" -version = "0.43.0" +version = "0.44.0" dependencies = [ "itertools", "oxc_ast", @@ -1752,7 +1752,7 @@ dependencies = [ [[package]] name = "oxc_minifier" -version = "0.43.0" +version = "0.44.0" dependencies = [ "cow-utils", "insta", @@ -1800,7 +1800,7 @@ dependencies = [ [[package]] name = "oxc_napi" -version = "0.43.0" +version = "0.44.0" dependencies = [ "napi", "napi-derive", @@ -1809,7 +1809,7 @@ dependencies = [ [[package]] name = "oxc_parser" -version = "0.43.0" +version = "0.44.0" dependencies = [ "assert-unchecked", "bitflags 2.6.0", @@ -1890,7 +1890,7 @@ dependencies = [ [[package]] name = "oxc_regular_expression" -version = "0.43.0" +version = "0.44.0" dependencies = [ "oxc_allocator", "oxc_ast_macros", @@ -1924,7 +1924,7 @@ dependencies = [ [[package]] name = "oxc_semantic" -version = "0.43.0" +version = "0.44.0" dependencies = [ "assert-unchecked", "hashbrown 0.15.2", @@ -1965,7 +1965,7 @@ dependencies = [ [[package]] name = "oxc_span" -version = "0.43.0" +version = "0.44.0" dependencies = [ "compact_str", "oxc-miette", @@ -1978,7 +1978,7 @@ dependencies = [ [[package]] name = "oxc_syntax" -version = "0.43.0" +version = "0.44.0" dependencies = [ "assert-unchecked", "bitflags 2.6.0", @@ -2036,7 +2036,7 @@ dependencies = [ [[package]] name = "oxc_transform_napi" -version = "0.43.0" +version = "0.44.0" dependencies = [ "napi", "napi-build", @@ -2049,7 +2049,7 @@ dependencies = [ [[package]] name = "oxc_transformer" -version = "0.43.0" +version = "0.44.0" dependencies = [ "base64", "compact_str", @@ -2082,7 +2082,7 @@ dependencies = [ [[package]] name = "oxc_traverse" -version = "0.43.0" +version = "0.44.0" dependencies = [ "compact_str", "itoa", diff --git a/Cargo.toml b/Cargo.toml index 8c41dff0112930..1bd59663b3e210 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,28 +78,28 @@ doc_lazy_continuation = "allow" # FIXME [workspace.dependencies] # publish = true -oxc = { version = "0.43.0", path = "crates/oxc" } -oxc_allocator = { version = "0.43.0", path = "crates/oxc_allocator" } -oxc_ast = { version = "0.43.0", path = "crates/oxc_ast" } -oxc_ast_macros = { version = "0.43.0", path = "crates/oxc_ast_macros" } -oxc_cfg = { version = "0.43.0", path = "crates/oxc_cfg" } -oxc_codegen = { version = "0.43.0", path = "crates/oxc_codegen" } -oxc_data_structures = { version = "0.43.0", path = "crates/oxc_data_structures" } -oxc_diagnostics = { version = "0.43.0", path = "crates/oxc_diagnostics" } -oxc_ecmascript = { version = "0.43.0", path = "crates/oxc_ecmascript" } -oxc_estree = { version = "0.43.0", path = "crates/oxc_estree" } -oxc_isolated_declarations = { version = "0.43.0", path = "crates/oxc_isolated_declarations" } -oxc_mangler = { version = "0.43.0", path = "crates/oxc_mangler" } -oxc_minifier = { version = "0.43.0", path = "crates/oxc_minifier" } -oxc_napi = { version = "0.43.0", path = "crates/oxc_napi" } -oxc_parser = { version = "0.43.0", path = "crates/oxc_parser" } -oxc_regular_expression = { version = "0.43.0", path = "crates/oxc_regular_expression" } -oxc_semantic = { version = "0.43.0", path = "crates/oxc_semantic" } -oxc_span = { version = "0.43.0", path = "crates/oxc_span" } -oxc_syntax = { version = "0.43.0", path = "crates/oxc_syntax" } -oxc_transform_napi = { version = "0.43.0", path = "napi/transform" } -oxc_transformer = { version = "0.43.0", path = "crates/oxc_transformer" } -oxc_traverse = { version = "0.43.0", path = "crates/oxc_traverse" } +oxc = { version = "0.44.0", path = "crates/oxc" } +oxc_allocator = { version = "0.44.0", path = "crates/oxc_allocator" } +oxc_ast = { version = "0.44.0", path = "crates/oxc_ast" } +oxc_ast_macros = { version = "0.44.0", path = "crates/oxc_ast_macros" } +oxc_cfg = { version = "0.44.0", path = "crates/oxc_cfg" } +oxc_codegen = { version = "0.44.0", path = "crates/oxc_codegen" } +oxc_data_structures = { version = "0.44.0", path = "crates/oxc_data_structures" } +oxc_diagnostics = { version = "0.44.0", path = "crates/oxc_diagnostics" } +oxc_ecmascript = { version = "0.44.0", path = "crates/oxc_ecmascript" } +oxc_estree = { version = "0.44.0", path = "crates/oxc_estree" } +oxc_isolated_declarations = { version = "0.44.0", path = "crates/oxc_isolated_declarations" } +oxc_mangler = { version = "0.44.0", path = "crates/oxc_mangler" } +oxc_minifier = { version = "0.44.0", path = "crates/oxc_minifier" } +oxc_napi = { version = "0.44.0", path = "crates/oxc_napi" } +oxc_parser = { version = "0.44.0", path = "crates/oxc_parser" } +oxc_regular_expression = { version = "0.44.0", path = "crates/oxc_regular_expression" } +oxc_semantic = { version = "0.44.0", path = "crates/oxc_semantic" } +oxc_span = { version = "0.44.0", path = "crates/oxc_span" } +oxc_syntax = { version = "0.44.0", path = "crates/oxc_syntax" } +oxc_transform_napi = { version = "0.44.0", path = "napi/transform" } +oxc_transformer = { version = "0.44.0", path = "crates/oxc_transformer" } +oxc_traverse = { version = "0.44.0", path = "crates/oxc_traverse" } # publish = false oxc_linter = { path = "crates/oxc_linter" } diff --git a/crates/oxc/Cargo.toml b/crates/oxc/Cargo.toml index 8b72f7782a1206..e7a1af65ca7112 100644 --- a/crates/oxc/Cargo.toml +++ b/crates/oxc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_allocator/Cargo.toml b/crates/oxc_allocator/Cargo.toml index 7949cdcd32a3e2..21596de120bd22 100644 --- a/crates/oxc_allocator/Cargo.toml +++ b/crates/oxc_allocator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_allocator" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_ast/CHANGELOG.md b/crates/oxc_ast/CHANGELOG.md index 3a82de3741aa81..e194520cc56c39 100644 --- a/crates/oxc_ast/CHANGELOG.md +++ b/crates/oxc_ast/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.44.0] - 2024-12-25 + +- ad2a620 ast: [**BREAKING**] Add missing `AssignmentTargetProperty::computed` (#8097) (Boshen) + +### Features + +- c2daa20 ast: Add `Expression::into_inner_expression` (#8048) (overlookmotel) + +### Bug Fixes + + ## [0.43.0] - 2024-12-21 ### Features diff --git a/crates/oxc_ast/Cargo.toml b/crates/oxc_ast/Cargo.toml index bef862fbe6fd73..d4a7e8524c19e7 100644 --- a/crates/oxc_ast/Cargo.toml +++ b/crates/oxc_ast/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_ast" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_ast_macros/Cargo.toml b/crates/oxc_ast_macros/Cargo.toml index ec2058aa62b357..f9990fde6f5d57 100644 --- a/crates/oxc_ast_macros/Cargo.toml +++ b/crates/oxc_ast_macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_ast_macros" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_cfg/Cargo.toml b/crates/oxc_cfg/Cargo.toml index 2f98dc83cfb528..a9d8bfd45af193 100644 --- a/crates/oxc_cfg/Cargo.toml +++ b/crates/oxc_cfg/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_cfg" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_codegen/CHANGELOG.md b/crates/oxc_codegen/CHANGELOG.md index 09385c7ead2098..beb04a02899741 100644 --- a/crates/oxc_codegen/CHANGELOG.md +++ b/crates/oxc_codegen/CHANGELOG.md @@ -4,6 +4,40 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.44.0] - 2024-12-25 + +- ad2a620 ast: [**BREAKING**] Add missing `AssignmentTargetProperty::computed` (#8097) (Boshen) + +### Features + +- 618b6aa codege: Minify whitespace in object getter / setter (#8080) (Boshen) +- 4727667 codegen: Minify arrow expr `(x) => y` -> `x => y` (#8078) (Boshen) +- 0562830 codegen: Minify string with backtick when needed (#8095) (Boshen) +- 6237c05 codegen: Minify more whitespace (#8089) (Boshen) +- 6355b7c codegen: Minify `export { 's' as 's' }` -> `export { 's' }` (#8093) (Boshen) +- fccfda9 codegen: Minify `class{static[computed]}` (#8088) (Boshen) +- f873139 codegen: Minify `for (_ of [])` -> `for(_ of[])` (#8086) (Boshen) +- 8b8cbcd codegen: Minify `case "foo"` -> `case"foo"` (#8085) (Boshen) +- 414c118 codegen: Minify `yield "s"` -> `yield"s"` (#8084) (Boshen) +- f8f067b codegen: Minify class method `async*fn(){}` (#8083) (Boshen) +- 1d5ae81 codegen: Minify `const [foo] = bar` -> `const[foo]=bar` (#8079) (Boshen) +- e3f78fb codegen: `new Foo()` -> `new Foo` when minify (#8077) (Boshen) +- d84d60a codegen: Minify numbers with large exponents (#8074) (Boshen) +- 373279b codegen: Balance string quotes when minify whitespace (#8072) (Boshen) + +### Bug Fixes + +- bdc241d codegen: Disallow template literals in object property key (#8108) (Boshen) +- 728ed20 codegen: Print `yield * ident` correctly (Boshen) + +### Performance + +- 78d2e83 sourcemap: Improve perf of `search_original_line_and_column` (#7926) (Cameron) + +### Refactor + +- 7110c7b codegen: Add `print_quoted_utf16` and `print_unquoted_utf16` methods (#8107) (Boshen) + ## [0.43.0] - 2024-12-21 ### Performance diff --git a/crates/oxc_codegen/Cargo.toml b/crates/oxc_codegen/Cargo.toml index 8f589f63126cfe..734bd3c470cc82 100644 --- a/crates/oxc_codegen/Cargo.toml +++ b/crates/oxc_codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_codegen" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_data_structures/Cargo.toml b/crates/oxc_data_structures/Cargo.toml index 68cc03592a3f36..c6b852140b18f0 100644 --- a/crates/oxc_data_structures/Cargo.toml +++ b/crates/oxc_data_structures/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_data_structures" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_diagnostics/Cargo.toml b/crates/oxc_diagnostics/Cargo.toml index 066668e3fa2afa..c03673ba615478 100644 --- a/crates/oxc_diagnostics/Cargo.toml +++ b/crates/oxc_diagnostics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_diagnostics" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_ecmascript/CHANGELOG.md b/crates/oxc_ecmascript/CHANGELOG.md index 92d6f3c50003b6..f0b101c074a1e5 100644 --- a/crates/oxc_ecmascript/CHANGELOG.md +++ b/crates/oxc_ecmascript/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.44.0] - 2024-12-25 + +### Features + +- 5397fe9 minifier: Constant fold `undefined?.bar` -> `undefined` (#8075) (Boshen) + ## [0.42.0] - 2024-12-18 ### Features diff --git a/crates/oxc_ecmascript/Cargo.toml b/crates/oxc_ecmascript/Cargo.toml index 9ad61b109d81ca..b943cb987b3921 100644 --- a/crates/oxc_ecmascript/Cargo.toml +++ b/crates/oxc_ecmascript/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_ecmascript" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_estree/Cargo.toml b/crates/oxc_estree/Cargo.toml index b374b90e9cefa8..cd081a349ebe02 100644 --- a/crates/oxc_estree/Cargo.toml +++ b/crates/oxc_estree/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_estree" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_isolated_declarations/Cargo.toml b/crates/oxc_isolated_declarations/Cargo.toml index fdaa95e0e87963..0bac7463a76996 100644 --- a/crates/oxc_isolated_declarations/Cargo.toml +++ b/crates/oxc_isolated_declarations/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_isolated_declarations" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_mangler/Cargo.toml b/crates/oxc_mangler/Cargo.toml index 6119fd9aa6bcd3..af04d0d49fd81c 100644 --- a/crates/oxc_mangler/Cargo.toml +++ b/crates/oxc_mangler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_mangler" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_minifier/CHANGELOG.md b/crates/oxc_minifier/CHANGELOG.md index 1c62e5f2b1b3d4..afbf0ffbdde961 100644 --- a/crates/oxc_minifier/CHANGELOG.md +++ b/crates/oxc_minifier/CHANGELOG.md @@ -4,6 +4,24 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.44.0] - 2024-12-25 + +### Features + +- 5397fe9 minifier: Constant fold `undefined?.bar` -> `undefined` (#8075) (Boshen) +- 1932f1e minifier: Fold `foo === undefined || foo === null` (#8063) (翠 / green) + +### Bug Fixes + +- b605baa minifier: Constant fold strings with tab char (#8096) (Boshen) + +### Refactor + +- 8b54d89 minifier: Remove parens must happen on enter (#8060) (Boshen) +- 7cb84f3 minifier: Only minify on ast node exit (#8059) (Boshen) +- 77d845a minifier: Fuse DCE AST passes (#8058) (Boshen) +- 6123f5e minifier: Fold statements on exit (#8057) (Boshen) + ## [0.42.0] - 2024-12-18 ### Features diff --git a/crates/oxc_minifier/Cargo.toml b/crates/oxc_minifier/Cargo.toml index f5a3ef76a2c1a4..07d6f5fa2a0c3a 100644 --- a/crates/oxc_minifier/Cargo.toml +++ b/crates/oxc_minifier/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_minifier" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_napi/Cargo.toml b/crates/oxc_napi/Cargo.toml index 4cff316a4c8f20..11e4a892b1f7c7 100644 --- a/crates/oxc_napi/Cargo.toml +++ b/crates/oxc_napi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_napi" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_parser/CHANGELOG.md b/crates/oxc_parser/CHANGELOG.md index e8fd0016088d43..68005f30abde75 100644 --- a/crates/oxc_parser/CHANGELOG.md +++ b/crates/oxc_parser/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.44.0] - 2024-12-25 + +- ad2a620 ast: [**BREAKING**] Add missing `AssignmentTargetProperty::computed` (#8097) (Boshen) + +### Bug Fixes + +- 55d6eb9 parser: Disallow type parameters on class constructors (#8071) (injuly) +- be2c60d parser: Parse `import source from from 'mod'` (#8056) (Boshen) + ## [0.42.0] - 2024-12-18 ### Features diff --git a/crates/oxc_parser/Cargo.toml b/crates/oxc_parser/Cargo.toml index 01c66f867f4365..186c8c9687057d 100644 --- a/crates/oxc_parser/Cargo.toml +++ b/crates/oxc_parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_parser" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_regular_expression/Cargo.toml b/crates/oxc_regular_expression/Cargo.toml index 4842f7566696f6..2923805449c29c 100644 --- a/crates/oxc_regular_expression/Cargo.toml +++ b/crates/oxc_regular_expression/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_regular_expression" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_semantic/CHANGELOG.md b/crates/oxc_semantic/CHANGELOG.md index d11a6c9918c353..1c5e31e92bd92e 100644 --- a/crates/oxc_semantic/CHANGELOG.md +++ b/crates/oxc_semantic/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.44.0] - 2024-12-25 + +### Features + +- e632a7b transformer: Remove typescript symbols after transform (#8069) (Boshen) + +### Bug Fixes + +- de82492 parser: Report syntax errors for missing constructor implementations (#8081) (camc314) +- 708e9cf semantic: Report errors for missing class method impls (#8082) (camc314) + ## [0.43.0] - 2024-12-21 - ed75e42 semantic: [**BREAKING**] Make SymbolTable fields `pub(crate)` instead of `pub` (#7999) (Boshen) diff --git a/crates/oxc_semantic/Cargo.toml b/crates/oxc_semantic/Cargo.toml index 4d77bab61bb95f..2c5039f4603b0e 100644 --- a/crates/oxc_semantic/Cargo.toml +++ b/crates/oxc_semantic/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_semantic" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_span/CHANGELOG.md b/crates/oxc_span/CHANGELOG.md index ed85108217b475..6b43a2795a5208 100644 --- a/crates/oxc_span/CHANGELOG.md +++ b/crates/oxc_span/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.44.0] - 2024-12-25 + +### Features + +- 11c4bd8 span: Implement source type `{file basename}.d.{extension}.ts` (#8109) (Boshen) + ## [0.42.0] - 2024-12-18 ### Features diff --git a/crates/oxc_span/Cargo.toml b/crates/oxc_span/Cargo.toml index cd5b6670aadcef..7eb7166789706f 100644 --- a/crates/oxc_span/Cargo.toml +++ b/crates/oxc_span/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_span" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_syntax/CHANGELOG.md b/crates/oxc_syntax/CHANGELOG.md index 1dbd6924035e8a..d1f2e7a96d08d4 100644 --- a/crates/oxc_syntax/CHANGELOG.md +++ b/crates/oxc_syntax/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.44.0] - 2024-12-25 + +### Features + +- be4feb4 syntax: Add `SymbolId::new` method (#8041) (overlookmotel) + ## [0.43.0] - 2024-12-21 ### Refactor diff --git a/crates/oxc_syntax/Cargo.toml b/crates/oxc_syntax/Cargo.toml index 58cf0e511e6fdd..f0fc82c6eba1d8 100644 --- a/crates/oxc_syntax/Cargo.toml +++ b/crates/oxc_syntax/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_syntax" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_transformer/CHANGELOG.md b/crates/oxc_transformer/CHANGELOG.md index dcbc406cba4b1c..eccc09d431081d 100644 --- a/crates/oxc_transformer/CHANGELOG.md +++ b/crates/oxc_transformer/CHANGELOG.md @@ -4,6 +4,24 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.44.0] - 2024-12-25 + +### Features + +- e632a7b transformer: Remove typescript symbols after transform (#8069) (Boshen) + +### Bug Fixes + +- 3057686 transformer/class-properties: Unwrap parenthesised expressions (#8049) (overlookmotel) +- e67cd05 transformer/class-properties: Correctly resolve private fields pointing to private accessors (#8047) (overlookmotel) +- 6b08c6e transformer/class-properties: Correctly resolve private fields pointing to private methods (#8042) (overlookmotel) +- 274f117 transformer/nullish-coalescing: Use correct scope id for binding (#8053) (camc314) + +### Refactor + +- cbd5169 transformer/class-properties: Do not recreate private field if not transforming it (#8044) (overlookmotel) +- 98e8a72 transformer/class-properties: Do not take mut ref when immut ref will do (#8040) (overlookmotel) + ## [0.43.0] - 2024-12-21 - de4c772 traverse: [**BREAKING**] Rename `Ancestor::is_via_*` methods to `is_parent_of_*` (#8031) (overlookmotel) diff --git a/crates/oxc_transformer/Cargo.toml b/crates/oxc_transformer/Cargo.toml index cd71689681bd52..93107bb4a531d7 100644 --- a/crates/oxc_transformer/Cargo.toml +++ b/crates/oxc_transformer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_transformer" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_traverse/CHANGELOG.md b/crates/oxc_traverse/CHANGELOG.md index 8a6c9af3598ae4..5169c8cfb502ef 100644 --- a/crates/oxc_traverse/CHANGELOG.md +++ b/crates/oxc_traverse/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.44.0] - 2024-12-25 + +- ad2a620 ast: [**BREAKING**] Add missing `AssignmentTargetProperty::computed` (#8097) (Boshen) + +### Features + +- e632a7b transformer: Remove typescript symbols after transform (#8069) (Boshen) + +### Bug Fixes + + ## [0.43.0] - 2024-12-21 - de4c772 traverse: [**BREAKING**] Rename `Ancestor::is_via_*` methods to `is_parent_of_*` (#8031) (overlookmotel) diff --git a/crates/oxc_traverse/Cargo.toml b/crates/oxc_traverse/Cargo.toml index 4b7e0dcadea87e..7b9d752a837eab 100644 --- a/crates/oxc_traverse/Cargo.toml +++ b/crates/oxc_traverse/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_traverse" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/napi/transform/Cargo.toml b/napi/transform/Cargo.toml index af3e3995d8c0a4..a3f1275770d4f8 100644 --- a/napi/transform/Cargo.toml +++ b/napi/transform/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_transform_napi" -version = "0.43.0" +version = "0.44.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/npm/oxc-parser/package.json b/npm/oxc-parser/package.json index febdc43390ce0d..bdbd64e11151df 100644 --- a/npm/oxc-parser/package.json +++ b/npm/oxc-parser/package.json @@ -1,6 +1,6 @@ { "name": "oxc-parser", - "version": "0.43.0", + "version": "0.44.0", "description": "Oxc Parser Node API", "keywords": [ "Parser" diff --git a/npm/oxc-transform/package.json b/npm/oxc-transform/package.json index 2e30f577fc978c..ddefdac67e79aa 100644 --- a/npm/oxc-transform/package.json +++ b/npm/oxc-transform/package.json @@ -1,6 +1,6 @@ { "name": "oxc-transform", - "version": "0.43.0", + "version": "0.44.0", "description": "Oxc transform Node API", "keywords": [ "transform" diff --git a/npm/oxc-types/CHANGELOG.md b/npm/oxc-types/CHANGELOG.md index 3c0fd025b03ada..fda0c301cb4177 100644 --- a/npm/oxc-types/CHANGELOG.md +++ b/npm/oxc-types/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project does not adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) until v1.0.0. +## [0.44.0] - 2024-12-25 + +- ad2a620 ast: [**BREAKING**] Add missing `AssignmentTargetProperty::computed` (#8097) (Boshen) + +### Bug Fixes + + ## [0.40.0] - 2024-12-10 - 72eab6c parser: [**BREAKING**] Stage 3 `import source` and `import defer` (#7706) (Boshen) diff --git a/npm/oxc-types/package.json b/npm/oxc-types/package.json index 9905ae4ab936b9..35709820b3b7ee 100644 --- a/npm/oxc-types/package.json +++ b/npm/oxc-types/package.json @@ -1,6 +1,6 @@ { "name": "@oxc-project/types", - "version": "0.43.0", + "version": "0.44.0", "description": "Types for Oxc AST nodes", "keywords": [ "AST", diff --git a/wasm/parser/package.json b/wasm/parser/package.json index 6e04776bd7dbf5..715a30a7942ac1 100644 --- a/wasm/parser/package.json +++ b/wasm/parser/package.json @@ -1,6 +1,6 @@ { "name": "@oxc-parser/wasm", - "version": "0.43.0", + "version": "0.44.0", "description": "Wasm target for the oxc parser.", "keywords": [ "JavaScript", From e594c3988d3c5456711a1f36f318e91189587f87 Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Wed, 25 Dec 2024 13:54:51 +0000 Subject: [PATCH 007/162] refactor(minifier): clean up `peephole_substitute_alternate_syntax.rs` (#8111) --- Cargo.lock | 2 + .../peephole_substitute_alternate_syntax.rs | 273 ++++++++---------- tasks/minsize/Cargo.toml | 2 + tasks/minsize/minsize.snap | 2 +- tasks/minsize/src/lib.rs | 9 + 5 files changed, 142 insertions(+), 146 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 886f7c39b7980f..cab6130bbc748a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1793,8 +1793,10 @@ dependencies = [ "oxc_codegen", "oxc_minifier", "oxc_parser", + "oxc_semantic", "oxc_span", "oxc_tasks_common", + "oxc_transformer", "rustc-hash", ] diff --git a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs index 4887eafcaed91a..75f2166eb2106c 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs @@ -79,76 +79,33 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax { fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { let ctx = Ctx(ctx); + + // Change syntax match expr { - Expression::AssignmentExpression(assignment_expr) => { - if let Some(new_expr) = - Self::try_compress_assignment_expression(assignment_expr, ctx) - { - *expr = new_expr; - self.changed = true; - } - } - Expression::LogicalExpression(logical_expr) => { - if let Some(new_expr) = Self::try_compress_is_null_or_undefined(logical_expr, ctx) { - *expr = new_expr; - self.changed = true; - } + Expression::ArrowFunctionExpression(arrow_expr) => { + self.try_compress_arrow_expression(arrow_expr, ctx); } + Expression::ChainExpression(e) => self.try_compress_chain_call_expression(e, ctx), _ => {} } - self.try_compress_boolean(expr, ctx); - self.try_compress_undefined(expr, ctx); - match expr { - Expression::NewExpression(new_expr) => { - if let Some(new_expr) = Self::try_fold_new_expression(new_expr, ctx) { - *expr = new_expr; - self.changed = true; - } - } - Expression::CallExpression(call_expr) => { - if let Some(new_expr) = - Self::try_fold_literal_constructor_call_expression(call_expr, ctx) - .or_else(|| Self::try_fold_simple_function_call(call_expr, ctx)) - { - *expr = new_expr; - self.changed = true; - } - } - Expression::ChainExpression(chain_expr) => { - if let ChainElement::CallExpression(call_expr) = &mut chain_expr.expression { - self.try_fold_chain_call_expression(call_expr, ctx); - } - } - Expression::TemplateLiteral(_) => { - if let Some(val) = expr.to_js_string() { - *expr = ctx.ast.expression_string_literal(expr.span(), val, None); - self.changed = true; - } - } - // `() => { return foo })` -> `() => foo` - Expression::ArrowFunctionExpression(arrow_expr) => { - if !arrow_expr.expression - && arrow_expr.body.directives.is_empty() - && arrow_expr.body.statements.len() == 1 - { - if let Some(body) = arrow_expr.body.statements.first_mut() { - if let Statement::ReturnStatement(ret_stmt) = body { - let return_stmt_arg = - ret_stmt.argument.as_mut().map(|arg| ctx.ast.move_expression(arg)); - - if let Some(return_stmt_arg) = return_stmt_arg { - *body = ctx.ast.statement_expression(SPAN, return_stmt_arg); - arrow_expr.expression = true; - self.changed = true; - } - } - } - } - } - Expression::BinaryExpression(expr) => { - self.compress_typeof_undefined(expr, ctx); + + // Fold + if let Some(folded_expr) = match expr { + Expression::Identifier(ident) => self.try_compress_undefined(ident, ctx), + Expression::BooleanLiteral(_) => self.try_compress_boolean(expr, ctx), + Expression::AssignmentExpression(e) => Self::try_compress_assignment_expression(e, ctx), + Expression::LogicalExpression(e) => Self::try_compress_is_null_or_undefined(e, ctx), + Expression::NewExpression(e) => Self::try_fold_new_expression(e, ctx), + Expression::CallExpression(e) => { + Self::try_fold_literal_constructor_call_expression(e, ctx) + .or_else(|| Self::try_fold_simple_function_call(e, ctx)) } - _ => {} + Expression::TemplateLiteral(t) => Self::try_fold_template_literal(t, ctx), + Expression::BinaryExpression(e) => Self::try_compress_typeof_undefined(e, ctx), + _ => None, + } { + *expr = folded_expr; + self.changed = true; } } } @@ -161,14 +118,18 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { /* Utilities */ /// Transforms `undefined` => `void 0` - fn try_compress_undefined(&mut self, expr: &mut Expression<'a>, ctx: Ctx<'a, 'b>) { + fn try_compress_undefined( + &self, + ident: &IdentifierReference<'a>, + ctx: Ctx<'a, 'b>, + ) -> Option> { if self.in_fixed_loop { - return; + return None; } - if ctx.is_expression_undefined(expr) { - *expr = ctx.ast.void_0(expr.span()); - self.changed = true; + if !ctx.is_identifier_undefined(ident) { + return None; } + Some(ctx.ast.void_0(ident.span)) } /// Test `Object.defineProperty(exports, ...)` @@ -207,48 +168,80 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { /// Transforms boolean expression `true` => `!0` `false` => `!1`. /// Do not compress `true` in `Object.defineProperty(exports, 'Foo', {enumerable: true, ...})`. - fn try_compress_boolean(&mut self, expr: &mut Expression<'a>, ctx: Ctx<'a, 'b>) { + fn try_compress_boolean( + &self, + expr: &mut Expression<'a>, + ctx: Ctx<'a, 'b>, + ) -> Option> { if self.in_fixed_loop { - return; + return None; } - let Expression::BooleanLiteral(lit) = expr else { return }; - if !self.in_define_export { - let parent = ctx.ancestry.parent(); - let no_unary = { - if let Ancestor::BinaryExpressionRight(u) = parent { - !matches!( - u.operator(), - BinaryOperator::Addition // Other effect, like string concatenation. + let Expression::BooleanLiteral(lit) = expr else { return None }; + if self.in_define_export { + return None; + } + let parent = ctx.ancestry.parent(); + let no_unary = { + if let Ancestor::BinaryExpressionRight(u) = parent { + !matches!( + u.operator(), + BinaryOperator::Addition // Other effect, like string concatenation. | BinaryOperator::Instanceof // Relational operator. | BinaryOperator::In | BinaryOperator::StrictEquality // It checks type, so we should not fold. | BinaryOperator::StrictInequality - ) - } else { - false - } - }; - // XOR: We should use `!neg` when it is not in binary expression. - let num = ctx.ast.expression_numeric_literal( - SPAN, - if lit.value ^ no_unary { 0.0 } else { 1.0 }, - None, - NumberBase::Decimal, - ); - *expr = if no_unary { - num + ) } else { - ctx.ast.expression_unary(SPAN, UnaryOperator::LogicalNot, num) - }; - self.changed = true; + false + } + }; + // XOR: We should use `!neg` when it is not in binary expression. + let num = ctx.ast.expression_numeric_literal( + SPAN, + if lit.value ^ no_unary { 0.0 } else { 1.0 }, + None, + NumberBase::Decimal, + ); + Some(if no_unary { + num + } else { + ctx.ast.expression_unary(SPAN, UnaryOperator::LogicalNot, num) + }) + } + + /// `() => { return foo })` -> `() => foo` + fn try_compress_arrow_expression( + &mut self, + arrow_expr: &mut ArrowFunctionExpression<'a>, + ctx: Ctx<'a, 'b>, + ) { + if !arrow_expr.expression + && arrow_expr.body.directives.is_empty() + && arrow_expr.body.statements.len() == 1 + { + if let Some(body) = arrow_expr.body.statements.first_mut() { + if let Statement::ReturnStatement(ret_stmt) = body { + let return_stmt_arg = + ret_stmt.argument.as_mut().map(|arg| ctx.ast.move_expression(arg)); + + if let Some(return_stmt_arg) = return_stmt_arg { + *body = ctx.ast.statement_expression(SPAN, return_stmt_arg); + arrow_expr.expression = true; + self.changed = true; + } + } + } } } /// Compress `typeof foo == "undefined"` into `typeof foo > "u"` /// Enabled by `compress.typeofs` - fn compress_typeof_undefined(&mut self, expr: &mut BinaryExpression<'a>, ctx: Ctx<'a, 'b>) { + fn try_compress_typeof_undefined( + expr: &mut BinaryExpression<'a>, + ctx: Ctx<'a, 'b>, + ) -> Option> { if !matches!(expr.operator, BinaryOperator::Equality | BinaryOperator::StrictEquality) { - return; + return None; } let pair = Self::commutative_pair( (&expr.left, &expr.right), @@ -264,16 +257,11 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { None }, ); - let Some((_void_exp, id_ref)) = pair else { - return; - }; + let (_void_exp, id_ref) = pair?; let argument = Expression::Identifier(ctx.alloc(id_ref)); let left = ctx.ast.expression_unary(SPAN, UnaryOperator::Typeof, argument); let right = ctx.ast.expression_string_literal(SPAN, "u", None); - let binary_expr = - ctx.ast.binary_expression(expr.span, left, BinaryOperator::GreaterThan, right); - *expr = binary_expr; - self.changed = true; + Some(ctx.ast.expression_binary(expr.span, left, BinaryOperator::GreaterThan, right)) } /// Compress `foo === null || foo === undefined` into `foo == null`. @@ -290,16 +278,11 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { ctx: Ctx<'a, 'b>, ) -> Option> { let op = expr.operator; - if !matches!(op, LogicalOperator::Or | LogicalOperator::And) { - return None; - } - #[allow(clippy::match_wildcard_for_single_variants)] let target_ops = match op { LogicalOperator::Or => (BinaryOperator::StrictEquality, BinaryOperator::Equality), LogicalOperator::And => (BinaryOperator::StrictInequality, BinaryOperator::Inequality), - _ => unreachable!(), + LogicalOperator::Coalesce => return None, }; - if let Some(new_expr) = Self::try_compress_is_null_or_undefined_for_left_and_right( &expr.left, &expr.right, @@ -309,14 +292,12 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { ) { return Some(new_expr); } - let Expression::LogicalExpression(left) = &mut expr.left else { return None; }; if left.operator != op { return None; } - Self::try_compress_is_null_or_undefined_for_left_and_right( &left.right, &expr.right, @@ -445,36 +426,32 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { ctx: Ctx<'a, 'b>, ) -> Option> { let target = expr.left.as_simple_assignment_target_mut()?; - if matches!(expr.operator, AssignmentOperator::Subtraction) { - match &expr.right { - Expression::NumericLiteral(num) if num.value.to_int_32() == 1 => { + if !matches!(expr.operator, AssignmentOperator::Subtraction) { + return None; + } + match &expr.right { + Expression::NumericLiteral(num) if num.value.to_int_32() == 1 => { + // The `_` will not be placed to the target code. + let target = std::mem::replace( + target, + ctx.ast.simple_assignment_target_identifier_reference(SPAN, "_"), + ); + Some(ctx.ast.expression_update(SPAN, UpdateOperator::Decrement, true, target)) + } + Expression::UnaryExpression(un) + if matches!(un.operator, UnaryOperator::UnaryNegation) => + { + let Expression::NumericLiteral(num) = &un.argument else { return None }; + (num.value.to_int_32() == 1).then(|| { // The `_` will not be placed to the target code. let target = std::mem::replace( target, ctx.ast.simple_assignment_target_identifier_reference(SPAN, "_"), ); - Some(ctx.ast.expression_update(SPAN, UpdateOperator::Decrement, true, target)) - } - Expression::UnaryExpression(un) - if matches!(un.operator, UnaryOperator::UnaryNegation) => - { - if let Expression::NumericLiteral(num) = &un.argument { - (num.value.to_int_32() == 1).then(|| { - // The `_` will not be placed to the target code. - let target = std::mem::replace( - target, - ctx.ast.simple_assignment_target_identifier_reference(SPAN, "_"), - ); - ctx.ast.expression_update(SPAN, UpdateOperator::Increment, true, target) - }) - } else { - None - } - } - _ => None, + ctx.ast.expression_update(SPAN, UpdateOperator::Increment, true, target) + }) } - } else { - None + _ => None, } } @@ -645,19 +622,25 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { } } - fn try_fold_chain_call_expression( + fn try_compress_chain_call_expression( &mut self, - call_expr: &mut CallExpression<'a>, + chain_expr: &mut ChainExpression<'a>, ctx: Ctx<'a, 'b>, ) { - // `window.Object?.()` -> `Object?.()` - if call_expr.arguments.is_empty() && Self::is_window_object(&call_expr.callee) { - call_expr.callee = - ctx.ast.expression_identifier_reference(call_expr.callee.span(), "Object"); - self.changed = true; + if let ChainElement::CallExpression(call_expr) = &mut chain_expr.expression { + // `window.Object?.()` -> `Object?.()` + if call_expr.arguments.is_empty() && Self::is_window_object(&call_expr.callee) { + call_expr.callee = + ctx.ast.expression_identifier_reference(call_expr.callee.span(), "Object"); + self.changed = true; + } } } + fn try_fold_template_literal(t: &TemplateLiteral, ctx: Ctx<'a, 'b>) -> Option> { + t.to_js_string().map(|val| ctx.ast.expression_string_literal(t.span(), val, None)) + } + /// returns an `Array()` constructor call with zero, one, or more arguments, copying from the input fn array_constructor_call( arguments: Vec<'a, Argument<'a>>, diff --git a/tasks/minsize/Cargo.toml b/tasks/minsize/Cargo.toml index bfc2bd01ed9c01..c842002da53fea 100644 --- a/tasks/minsize/Cargo.toml +++ b/tasks/minsize/Cargo.toml @@ -22,7 +22,9 @@ oxc_allocator = { workspace = true } oxc_codegen = { workspace = true } oxc_minifier = { workspace = true } oxc_parser = { workspace = true } +oxc_semantic = { workspace = true } oxc_span = { workspace = true } +oxc_transformer = { workspace = true } flate2 = { workspace = true } oxc_tasks_common = { workspace = true } diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 76c69877c44b5e..bc9fede0fae175 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -1,7 +1,7 @@ | Oxc | ESBuild | Oxc | ESBuild | Original | minified | minified | gzip | gzip | Fixture ------------------------------------------------------------------------------------- -72.14 kB | 24.04 kB | 23.70 kB | 8.61 kB | 8.54 kB | react.development.js +72.14 kB | 24.00 kB | 23.70 kB | 8.58 kB | 8.54 kB | react.development.js 173.90 kB | 61.55 kB | 59.82 kB | 19.54 kB | 19.33 kB | moment.js diff --git a/tasks/minsize/src/lib.rs b/tasks/minsize/src/lib.rs index f6f7d8eed3def0..e3655b6977010a 100644 --- a/tasks/minsize/src/lib.rs +++ b/tasks/minsize/src/lib.rs @@ -11,8 +11,10 @@ use oxc_allocator::Allocator; use oxc_codegen::{CodeGenerator, CodegenOptions}; use oxc_minifier::{CompressOptions, MangleOptions, Minifier, MinifierOptions}; use oxc_parser::Parser; +use oxc_semantic::SemanticBuilder; use oxc_span::SourceType; use oxc_tasks_common::{project_root, TestFile, TestFiles}; +use oxc_transformer::{ReplaceGlobalDefines, ReplaceGlobalDefinesConfig}; use rustc_hash::FxHashMap; // #[test] @@ -137,6 +139,13 @@ fn minify(source_text: &str, source_type: SourceType, options: MinifierOptions) let allocator = Allocator::default(); let ret = Parser::new(&allocator, source_text, source_type).parse(); let mut program = ret.program; + let (symbols, scopes) = + SemanticBuilder::new().build(&program).semantic.into_symbol_table_and_scope_tree(); + let _ = ReplaceGlobalDefines::new( + &allocator, + ReplaceGlobalDefinesConfig::new(&[("process.env.NODE_ENV", "'development'")]).unwrap(), + ) + .build(symbols, scopes, &mut program); let ret = Minifier::new(options).build(&allocator, &mut program); CodeGenerator::new() .with_options(CodegenOptions { minify: true, ..CodegenOptions::default() }) From 2331ea85d919e33ee028cd132a41e961d389fd65 Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Wed, 25 Dec 2024 14:52:58 +0000 Subject: [PATCH 008/162] feat(minifier): `typeof foo === 'number'` => `typeof foo == 'number'` (#8112) --- .../peephole_substitute_alternate_syntax.rs | 37 +++++++++++++++++++ crates/oxc_syntax/src/operator.rs | 5 +++ tasks/minsize/minsize.snap | 22 +++++------ 3 files changed, 53 insertions(+), 11 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs index 75f2166eb2106c..692b2b06517ba5 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs @@ -86,6 +86,7 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax { self.try_compress_arrow_expression(arrow_expr, ctx); } Expression::ChainExpression(e) => self.try_compress_chain_call_expression(e, ctx), + Expression::BinaryExpression(e) => self.try_compress_type_of_equal_string(e, ctx), _ => {} } @@ -622,6 +623,30 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { } } + /// `typeof foo === 'number'` -> `typeof foo == 'number'` + fn try_compress_type_of_equal_string( + &mut self, + e: &mut BinaryExpression<'a>, + _ctx: Ctx<'a, 'b>, + ) { + let op = match e.operator { + BinaryOperator::StrictEquality => BinaryOperator::Equality, + BinaryOperator::StrictInequality => BinaryOperator::Inequality, + _ => return, + }; + if Self::commutative_pair( + (&e.left, &e.right), + |a| a.is_string_literal().then_some(()), + |b| matches!(b, Expression::UnaryExpression(e) if e.operator.is_typeof()).then_some(()), + ) + .is_none() + { + return; + } + e.operator = op; + self.changed = true; + } + fn try_compress_chain_call_expression( &mut self, chain_expr: &mut ChainExpression<'a>, @@ -1100,4 +1125,16 @@ mod test { test("foo !== 1 || foo !== void 0 && foo !== null", "foo !== 1 || foo != null"); test_same("foo !== void 0 && bar !== null"); } + + #[test] + fn test_try_compress_type_of_equal_string() { + test("typeof foo === 'number'", "typeof foo == 'number'"); + test("'number' === typeof foo", "'number' == typeof foo"); + test("typeof foo === `number`", "typeof foo == 'number'"); + test("`number` === typeof foo", "'number' == typeof foo"); + test("typeof foo !== 'number'", "typeof foo != 'number'"); + test("'number' !== typeof foo", "'number' != typeof foo"); + test("typeof foo !== `number`", "typeof foo != 'number'"); + test("`number` !== typeof foo", "'number' != typeof foo"); + } } diff --git a/crates/oxc_syntax/src/operator.rs b/crates/oxc_syntax/src/operator.rs index 6c1c40cb64337e..76ac35d9e8bfc5 100644 --- a/crates/oxc_syntax/src/operator.rs +++ b/crates/oxc_syntax/src/operator.rs @@ -488,6 +488,11 @@ impl UnaryOperator { self == Self::BitwiseNot } + /// Returns `true` if this is the [`void`](UnaryOperator::Typeof) operator. + pub fn is_typeof(self) -> bool { + self == Self::Typeof + } + /// Returns `true` if this is the [`void`](UnaryOperator::Void) operator. pub fn is_void(self) -> bool { self == Self::Void diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index bc9fede0fae175..517880f8272fbf 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -1,27 +1,27 @@ | Oxc | ESBuild | Oxc | ESBuild | Original | minified | minified | gzip | gzip | Fixture ------------------------------------------------------------------------------------- -72.14 kB | 24.00 kB | 23.70 kB | 8.58 kB | 8.54 kB | react.development.js +72.14 kB | 23.96 kB | 23.70 kB | 8.58 kB | 8.54 kB | react.development.js -173.90 kB | 61.55 kB | 59.82 kB | 19.54 kB | 19.33 kB | moment.js +173.90 kB | 61.52 kB | 59.82 kB | 19.54 kB | 19.33 kB | moment.js -287.63 kB | 92.59 kB | 90.07 kB | 32.28 kB | 31.95 kB | jquery.js +287.63 kB | 92.51 kB | 90.07 kB | 32.29 kB | 31.95 kB | jquery.js -342.15 kB | 121.55 kB | 118.14 kB | 44.64 kB | 44.37 kB | vue.js +342.15 kB | 121.48 kB | 118.14 kB | 44.65 kB | 44.37 kB | vue.js 544.10 kB | 73.32 kB | 72.48 kB | 26.13 kB | 26.20 kB | lodash.js -555.77 kB | 276.01 kB | 270.13 kB | 91.13 kB | 90.80 kB | d3.js +555.77 kB | 275.83 kB | 270.13 kB | 91.13 kB | 90.80 kB | d3.js -1.01 MB | 466.62 kB | 458.89 kB | 126.69 kB | 126.71 kB | bundle.min.js +1.01 MB | 466.57 kB | 458.89 kB | 126.69 kB | 126.71 kB | bundle.min.js -1.25 MB | 661.47 kB | 646.76 kB | 163.94 kB | 163.73 kB | three.js +1.25 MB | 661.44 kB | 646.76 kB | 163.95 kB | 163.73 kB | three.js -2.14 MB | 740.43 kB | 724.14 kB | 181.34 kB | 181.07 kB | victory.js +2.14 MB | 740.15 kB | 724.14 kB | 181.34 kB | 181.07 kB | victory.js -3.20 MB | 1.02 MB | 1.01 MB | 332.00 kB | 331.56 kB | echarts.js +3.20 MB | 1.02 MB | 1.01 MB | 332.02 kB | 331.56 kB | echarts.js -6.69 MB | 2.39 MB | 2.31 MB | 495.62 kB | 488.28 kB | antd.js +6.69 MB | 2.39 MB | 2.31 MB | 495.64 kB | 488.28 kB | antd.js -10.95 MB | 3.54 MB | 3.49 MB | 909.73 kB | 915.50 kB | typescript.js +10.95 MB | 3.54 MB | 3.49 MB | 909.75 kB | 915.50 kB | typescript.js From c22062bab3cb5573deff8ab7494a4ec6a0d0edbe Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Wed, 25 Dec 2024 15:47:42 +0000 Subject: [PATCH 009/162] refactor(minifier): cleanup peephole_minimize_conditions (#8114) --- crates/oxc_minifier/src/ast_passes/mod.rs | 10 +++-- .../peephole_minimize_conditions.rs | 40 +++++++------------ 2 files changed, 21 insertions(+), 29 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/mod.rs b/crates/oxc_minifier/src/ast_passes/mod.rs index e1995fbb65f150..77cc7b0bc2d5fa 100644 --- a/crates/oxc_minifier/src/ast_passes/mod.rs +++ b/crates/oxc_minifier/src/ast_passes/mod.rs @@ -74,12 +74,13 @@ pub struct LatePeepholeOptimizations { impl LatePeepholeOptimizations { pub fn new() -> Self { + let in_fixed_loop = true; Self { x0_statement_fusion: StatementFusion::new(), x1_peephole_remove_dead_code: PeepholeRemoveDeadCode::new(), - x2_peephole_minimize_conditions: PeepholeMinimizeConditions::new(), + x2_peephole_minimize_conditions: PeepholeMinimizeConditions::new(in_fixed_loop), x3_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax::new( - /* in_fixed_loop */ true, + in_fixed_loop, ), x4_peephole_replace_known_methods: PeepholeReplaceKnownMethods::new(), x5_peephole_fold_constants: PeepholeFoldConstants::new(), @@ -195,10 +196,11 @@ pub struct PeepholeOptimizations { impl PeepholeOptimizations { pub fn new() -> Self { + let in_fixed_loop = false; Self { - x2_peephole_minimize_conditions: PeepholeMinimizeConditions::new(), + x2_peephole_minimize_conditions: PeepholeMinimizeConditions::new(in_fixed_loop), x3_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax::new( - /* in_fixed_loop */ false, + in_fixed_loop, ), x4_peephole_replace_known_methods: PeepholeReplaceKnownMethods::new(), x5_peephole_remove_dead_code: PeepholeRemoveDeadCode::new(), diff --git a/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs b/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs index 2175c1b71c9a7a..5a2c09b8296771 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs @@ -11,6 +11,10 @@ use crate::CompressorPass; /// /// pub struct PeepholeMinimizeConditions { + /// Do not compress syntaxes that are hard to analyze inside the fixed loop. + #[allow(unused)] + in_fixed_loop: bool, + pub(crate) changed: bool, } @@ -24,7 +28,7 @@ impl<'a> CompressorPass<'a> for PeepholeMinimizeConditions { impl<'a> Traverse<'a> for PeepholeMinimizeConditions { fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { if let Some(folded_expr) = match expr { - Expression::UnaryExpression(e) if e.operator.is_not() => Self::try_minimize_not(e, ctx), + Expression::UnaryExpression(e) => Self::try_minimize_not(e, ctx), _ => None, } { *expr = folded_expr; @@ -34,8 +38,8 @@ impl<'a> Traverse<'a> for PeepholeMinimizeConditions { } impl<'a> PeepholeMinimizeConditions { - pub fn new() -> Self { - Self { changed: false } + pub fn new(in_fixed_loop: bool) -> Self { + Self { in_fixed_loop, changed: false } } /// Try to minimize NOT nodes such as `!(x==y)`. @@ -43,14 +47,14 @@ impl<'a> PeepholeMinimizeConditions { expr: &mut UnaryExpression<'a>, ctx: &mut TraverseCtx<'a>, ) -> Option> { - debug_assert!(expr.operator.is_not()); - if let Expression::BinaryExpression(binary_expr) = &mut expr.argument { - if let Some(new_op) = binary_expr.operator.equality_inverse_operator() { - binary_expr.operator = new_op; - return Some(ctx.ast.move_expression(&mut expr.argument)); - } + // TODO: tryMinimizeCondition(node.getFirstChild()); + if !expr.operator.is_not() { + return None; } - None + let Expression::BinaryExpression(binary_expr) = &mut expr.argument else { return None }; + let new_op = binary_expr.operator.equality_inverse_operator()?; + binary_expr.operator = new_op; + Some(ctx.ast.move_expression(&mut expr.argument)) } } @@ -63,7 +67,7 @@ mod test { fn test(source_text: &str, positive: &str) { let allocator = Allocator::default(); - let mut pass = super::PeepholeMinimizeConditions::new(); + let mut pass = super::PeepholeMinimizeConditions::new(true); tester::test(&allocator, source_text, positive, &mut pass); } @@ -951,7 +955,6 @@ mod test { } #[test] - #[ignore] fn test_remove_else_cause3() { test_same("function f() { a:{if (x) break a; else f() } }"); test_same("function f() { if (x) { a:{ break a } } else f() }"); @@ -959,7 +962,6 @@ mod test { } #[test] - #[ignore] fn test_remove_else_cause4() { test_same("function f() { if (x) { if (y) { return 1; } } else f() }"); } @@ -1000,7 +1002,6 @@ mod test { } #[test] - #[ignore] fn test_coercion_substitution_disabled() { // enableTypeCheck(); test_same("var x = {}; if (x != null) throw 'a';"); @@ -1011,14 +1012,12 @@ mod test { } #[test] - #[ignore] fn test_coercion_substitution_boolean_result0() { // enableTypeCheck(); test_same("var x = {}; var y = x != null;"); } #[test] - #[ignore] fn test_coercion_substitution_boolean_result1() { // enableTypeCheck(); test_same("var x = {}; var y = x == null;"); @@ -1060,7 +1059,6 @@ mod test { } #[test] - #[ignore] fn test_coercion_substitution_expression() { // enableTypeCheck(); test_same("var x = {}; x != null && alert('b');"); @@ -1068,7 +1066,6 @@ mod test { } #[test] - #[ignore] fn test_coercion_substitution_hook() { // enableTypeCheck(); test_same(concat!("var x = {};", "var y = x != null ? 1 : 2;")); @@ -1087,7 +1084,6 @@ mod test { } #[test] - #[ignore] fn test_coercion_substitution_while() { // enableTypeCheck(); test_same("var x = {}; while (x != null) throw 'a';"); @@ -1095,7 +1091,6 @@ mod test { } #[test] - #[ignore] fn test_coercion_substitution_unknown_type() { // enableTypeCheck(); test_same("var x = /** @type {?} */ ({});\nif (x != null) throw 'a';\n"); @@ -1103,7 +1098,6 @@ mod test { } #[test] - #[ignore] fn test_coercion_substitution_all_type() { // enableTypeCheck(); test_same("var x = /** @type {*} */ ({});\nif (x != null) throw 'a';\n"); @@ -1111,7 +1105,6 @@ mod test { } #[test] - #[ignore] fn test_coercion_substitution_primitives_vs_null() { // enableTypeCheck(); test_same("var x = 0;\nif (x != null) throw 'a';\n"); @@ -1120,7 +1113,6 @@ mod test { } #[test] - #[ignore] fn test_coercion_substitution_non_number_vs_zero() { // enableTypeCheck(); test_same("var x = {};\nif (x != 0) throw 'a';\n"); @@ -1129,14 +1121,12 @@ mod test { } #[test] - #[ignore] fn test_coercion_substitution_boxed_number_vs_zero() { // enableTypeCheck(); test_same("var x = new Number(0);\nif (x != 0) throw 'a';\n"); } #[test] - #[ignore] fn test_coercion_substitution_boxed_primitives() { // enableTypeCheck(); test_same("var x = new Number(); if (x != null) throw 'a';"); From fef0b25fd36f134aba3d82857519eac12047182a Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Thu, 26 Dec 2024 05:22:02 +0000 Subject: [PATCH 010/162] feat(minifier): collapse `var` into for loop initializer (#8119) `var a = 0; for(;a<0;a++) {}` => `for(var a = 0;a<0;a++) {}` --- crates/oxc_ast/src/ast_impl/js.rs | 5 + .../collapse_variable_declarations.rs | 472 +++++++++++++----- tasks/minsize/minsize.snap | 20 +- 3 files changed, 368 insertions(+), 129 deletions(-) diff --git a/crates/oxc_ast/src/ast_impl/js.rs b/crates/oxc_ast/src/ast_impl/js.rs index e4b6e85877d9e9..37df05cc79dfea 100644 --- a/crates/oxc_ast/src/ast_impl/js.rs +++ b/crates/oxc_ast/src/ast_impl/js.rs @@ -886,6 +886,11 @@ impl fmt::Display for VariableDeclarationKind { } impl ForStatementInit<'_> { + /// Is `var` declaration + pub fn is_var_declaration(&self) -> bool { + matches!(self, Self::VariableDeclaration(decl) if decl.kind.is_var()) + } + /// LexicalDeclaration[In, Yield, Await] : /// LetOrConst BindingList[?In, ?Yield, ?Await] ; pub fn is_lexical_declaration(&self) -> bool { diff --git a/crates/oxc_minifier/src/ast_passes/collapse_variable_declarations.rs b/crates/oxc_minifier/src/ast_passes/collapse_variable_declarations.rs index da3431eb95ab65..65ccc811f346bb 100644 --- a/crates/oxc_minifier/src/ast_passes/collapse_variable_declarations.rs +++ b/crates/oxc_minifier/src/ast_passes/collapse_variable_declarations.rs @@ -6,8 +6,13 @@ use crate::CompressorPass; /// Collapse variable declarations. /// +/// Join Vars: /// `var a; var b = 1; var c = 2` => `var a, b = 1; c = 2` /// +/// +/// Collapse into for statements: +/// `var a = 0; for(;a<0;a++) {}` => `for(var a = 0;a<0;a++) {}` +/// pub struct CollapseVariableDeclarations { pub(crate) changed: bool, } @@ -22,9 +27,11 @@ impl<'a> CompressorPass<'a> for CollapseVariableDeclarations { impl<'a> Traverse<'a> for CollapseVariableDeclarations { fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) { self.join_vars(stmts, ctx); + self.maybe_collapse_into_for_statements(stmts, ctx); } } +// Join Vars impl<'a> CollapseVariableDeclarations { pub fn new() -> Self { Self { changed: false } @@ -104,6 +111,127 @@ impl<'a> CollapseVariableDeclarations { } } +// Collapse into for statements +impl<'a> CollapseVariableDeclarations { + fn maybe_collapse_into_for_statements( + &mut self, + stmts: &mut Vec<'a, Statement<'a>>, + ctx: &mut TraverseCtx<'a>, + ) { + if stmts.len() <= 1 { + return; + } + + for i in 0..stmts.len() - 1 { + match &stmts[i + 1] { + Statement::ForStatement(for_stmt) => match &stmts[i] { + Statement::ExpressionStatement(_) if for_stmt.init.is_none() => { + self.collapse_expr_into_for(i, stmts, ctx); + } + Statement::VariableDeclaration(decl) + if decl.kind.is_var() + && (for_stmt.init.is_none() + || for_stmt + .init + .as_ref() + .is_some_and(ForStatementInit::is_var_declaration)) => + { + self.collapse_var_into_for(i, stmts, ctx); + } + _ => {} + }, + Statement::ForInStatement(_) | Statement::ForOfStatement(_) => { + self.collapse_var_into_for_in_or_for_of(i, stmts, ctx); + } + _ => {} + } + } + + if self.changed { + stmts.retain(|stmt| !matches!(stmt, Statement::EmptyStatement(_))); + } + } + + fn collapse_expr_into_for( + &mut self, + i: usize, + stmts: &mut Vec<'a, Statement<'a>>, + ctx: &mut TraverseCtx<'a>, + ) { + if let Statement::ExpressionStatement(expr_stmt) = ctx.ast.move_statement(&mut stmts[i]) { + if let Statement::ForStatement(for_stmt) = &mut stmts[i + 1] { + for_stmt.init = Some(ForStatementInit::from(expr_stmt.unbox().expression)); + self.changed = true; + }; + } + } + + fn collapse_var_into_for( + &mut self, + i: usize, + stmts: &mut Vec<'a, Statement<'a>>, + ctx: &mut TraverseCtx<'a>, + ) { + if let Statement::VariableDeclaration(var) = ctx.ast.move_statement(&mut stmts[i]) { + if let Statement::ForStatement(for_stmt) = &mut stmts[i + 1] { + match for_stmt.init.as_mut() { + Some(ForStatementInit::VariableDeclaration(for_var)) => { + for_var.declarations.splice(0..0, var.unbox().declarations); + self.changed = true; + } + None => { + for_stmt.init = Some(ForStatementInit::VariableDeclaration(var)); + self.changed = true; + } + _ => { + unreachable!() + } + } + }; + } + } + + fn collapse_var_into_for_in_or_for_of( + &mut self, + i: usize, + stmts: &mut Vec<'a, Statement<'a>>, + ctx: &mut TraverseCtx<'a>, + ) { + if let Statement::VariableDeclaration(decl) = &stmts[i] { + if decl.kind.is_var() + && decl.declarations.len() == 1 + && decl.declarations[0].init.is_none() + { + if let BindingPatternKind::BindingIdentifier(binding) = + &decl.declarations[0].id.kind + { + if let ForStatementLeft::AssignmentTargetIdentifier(target) = + match &stmts[i + 1] { + Statement::ForInStatement(stmt) => &stmt.left, + Statement::ForOfStatement(stmt) => &stmt.left, + _ => unreachable!(), + } + { + if binding.name == target.name { + let var_stmt = ctx.ast.move_statement(&mut stmts[i]); + let Statement::VariableDeclaration(var) = var_stmt else { + unreachable!() + }; + let left = match &mut stmts[i + 1] { + Statement::ForInStatement(stmt) => &mut stmt.left, + Statement::ForOfStatement(stmt) => &mut stmt.left, + _ => unreachable!(), + }; + *left = ForStatementLeft::VariableDeclaration(var); + self.changed = true; + } + } + } + } + } + } +} + /// #[cfg(test)] mod test { @@ -121,151 +249,257 @@ mod test { test(source_text, source_text); } - #[test] - fn cjs() { - // Do not join `require` calls for cjs-module-lexer. - test_same( - " - Object.defineProperty(exports, '__esModule', { value: true }); - var compilerDom = require('@vue/compiler-dom'); - var runtimeDom = require('@vue/runtime-dom'); - var shared = require('@vue/shared'); - ", - ); - } + mod join_vars { + use super::{test, test_same}; + + #[test] + fn cjs() { + // Do not join `require` calls for cjs-module-lexer. + test_same( + " Object.defineProperty(exports, '__esModule', { value: true }); + var compilerDom = require('@vue/compiler-dom'); + var runtimeDom = require('@vue/runtime-dom'); + var shared = require('@vue/shared'); + ", + ); + } - #[test] - fn test_collapsing() { - // Basic collapsing - test("var a;var b;", "var a,b;"); + #[test] + fn test_collapsing() { + // Basic collapsing + test("var a;var b;", "var a,b;"); - // With initial values - test("var a = 1;var b = 1;", "var a=1,b=1;"); + // With initial values + test("var a = 1;var b = 1;", "var a=1,b=1;"); - // Already collapsed - test_same("var a, b;"); + // Already collapsed + test_same("var a, b;"); - // Already collapsed with values - test_same("var a = 1, b = 1;"); + // Already collapsed with values + test_same("var a = 1, b = 1;"); - // Some already collapsed - test("var a;var b, c;var d;", "var a,b,c,d;"); + // Some already collapsed + test("var a;var b, c;var d;", "var a,b,c,d;"); - // Some already collapsed with values - test("var a = 1;var b = 2, c = 3;var d = 4;", "var a=1,b=2,c=3,d=4;"); + // Some already collapsed with values + test("var a = 1;var b = 2, c = 3;var d = 4;", "var a=1,b=2,c=3,d=4;"); - test( - "var x = 2; foo(x); x = 3; x = 1; var y = 2; var z = 4; x = 5", - "var x = 2; foo(x); x = 3; x = 1; var y = 2, z = 4; x = 5", - ); - } + test( + "var x = 2; foo(x); x = 3; x = 1; var y = 2; var z = 4; x = 5", + "var x = 2; foo(x); x = 3; x = 1; var y = 2, z = 4; x = 5", + ); + } - #[test] - fn test_issue820() { - // Don't redeclare function parameters, this is incompatible with - // strict mode. - test_same("function f(a){ var b=1; a=2; var c; }"); - } + #[test] + fn test_issue820() { + // Don't redeclare function parameters, this is incompatible with + // strict mode. + test_same("function f(a){ var b=1; a=2; var c; }"); + } - #[test] - fn test_if_else_var_declarations() { - test_same("if (x) var a = 1; else var b = 2;"); - } + #[test] + fn test_if_else_var_declarations() { + test_same("if (x) var a = 1; else var b = 2;"); + } - #[test] - fn test_aggressive_redeclaration_in_for() { - test_same("for(var x = 1; x = 2; x = 3) {x = 4}"); - test_same("for(var x = 1; y = 2; z = 3) {var a = 4}"); - test_same("var x; for(x = 1; x = 2; z = 3) {x = 4}"); - } + #[test] + fn test_aggressive_redeclaration_in_for() { + test_same("for(var x = 1; x = 2; x = 3) {x = 4}"); + test_same("for(var x = 1; y = 2; z = 3) {var a = 4}"); + test_same("var x; for(x = 1; x = 2; z = 3) {x = 4}"); + } - #[test] - fn test_issue397() { - test_same("var x; x = 5; var z = 7;"); - test("var x; var y = 3; x = 5;", "var x, y = 3; x = 5;"); - test("var a = 1; var x; var y = 3; x = 5;", "var a = 1, x, y = 3; x = 5;"); - test("var x; var y = 3; x = 5; var z = 7;", "var x, y = 3; x = 5; var z = 7;"); - } + #[test] + fn test_issue397() { + test_same("var x; x = 5; var z = 7;"); + test("var x; var y = 3; x = 5;", "var x, y = 3; x = 5;"); + test("var a = 1; var x; var y = 3; x = 5;", "var a = 1, x, y = 3; x = 5;"); + test("var x; var y = 3; x = 5; var z = 7;", "var x, y = 3; x = 5; var z = 7;"); + } - #[test] - fn test_arguments_assignment() { - test_same("function f() {arguments = 1;}"); - } + #[test] + fn test_arguments_assignment() { + test_same("function f() {arguments = 1;}"); + } - // ES6 Tests - #[test] - fn test_collapsing_let_const() { - // Basic collapsing - test("let a;let b;", "let a,b;"); + // ES6 Tests + #[test] + fn test_collapsing_let_const() { + // Basic collapsing + test("let a;let b;", "let a,b;"); - // With initial values - test("const a = 1;const b = 1;", "const a=1,b=1;"); + // With initial values + test("const a = 1;const b = 1;", "const a=1,b=1;"); - // Already collapsed - test_same("let a, b;"); + // Already collapsed + test_same("let a, b;"); - // Already collapsed with values - test_same("let a = 1, b = 1;"); + // Already collapsed with values + test_same("let a = 1, b = 1;"); - // Some already collapsed - test("let a;let b, c;let d;", "let a,b,c,d;"); + // Some already collapsed + test("let a;let b, c;let d;", "let a,b,c,d;"); - // Some already collapsed with values - test("let a = 1;let b = 2, c = 3;let d = 4;", "let a=1,b=2,c=3,d=4;"); + // Some already collapsed with values + test("let a = 1;let b = 2, c = 3;let d = 4;", "let a=1,b=2,c=3,d=4;"); - // Different variable types - test_same("let a = 1; const b = 2;"); - } + // Different variable types + test_same("let a = 1; const b = 2;"); + } - #[test] - fn test_if_else_var_declarations_let() { - test_same("if (x) { let a = 1; } else { let b = 2; }"); - } + #[test] + fn test_if_else_var_declarations_let() { + test_same("if (x) { let a = 1; } else { let b = 2; }"); + } - #[test] - fn test_aggressive_redeclaration_of_let_in_for() { - test_same("for(let x = 1; x = 2; x = 3) {x = 4}"); - test_same("for(let x = 1; y = 2; z = 3) {let a = 4}"); - test_same("let x; for(x = 1; x = 2; z = 3) {x = 4}"); - } + #[test] + fn test_aggressive_redeclaration_of_let_in_for() { + test_same("for(let x = 1; x = 2; x = 3) {x = 4}"); + test_same("for(let x = 1; y = 2; z = 3) {let a = 4}"); + test_same("let x; for(x = 1; x = 2; z = 3) {x = 4}"); + } - #[test] - fn test_redeclaration_let_in_function() { - test( - "function f() { let x = 1; let y = 2; let z = 3; x + y + z; }", - "function f() { let x = 1, y = 2, z = 3; x + y + z; } ", - ); - - // recognize local scope version of x - test( - "var x = 1; function f() { let x = 1; let y = 2; x + y; }", - "var x = 1; function f() { let x = 1, y = 2; x + y } ", - ); - - // do not redeclare function parameters - // incompatible with strict mode - test_same("function f(x) { let y = 3; x = 4; x + y; }"); - } + #[test] + fn test_redeclaration_let_in_function() { + test( + "function f() { let x = 1; let y = 2; let z = 3; x + y + z; }", + "function f() { let x = 1, y = 2, z = 3; x + y + z; } ", + ); + + // recognize local scope version of x + test( + "var x = 1; function f() { let x = 1; let y = 2; x + y; }", + "var x = 1; function f() { let x = 1, y = 2; x + y } ", + ); + + // do not redeclare function parameters + // incompatible with strict mode + test_same("function f(x) { let y = 3; x = 4; x + y; }"); + } - #[test] - fn test_arrow_function() { - test("() => {let x = 1; let y = 2; x + y; }", "() => {let x = 1, y = 2; x + y; }"); + #[test] + fn test_arrow_function() { + test("() => {let x = 1; let y = 2; x + y; }", "() => {let x = 1, y = 2; x + y; }"); - // do not redeclare function parameters - // incompatible with strict mode - test_same("(x) => {x = 4; let y = 2; x + y; }"); - } + // do not redeclare function parameters + // incompatible with strict mode + test_same("(x) => {x = 4; let y = 2; x + y; }"); + } - #[test] - fn test_uncollapsable_declarations() { - test_same("let x = 1; var y = 2; const z = 3"); - test_same("let x = 1; var y = 2; let z = 3;"); + #[test] + fn test_uncollapsable_declarations() { + test_same("let x = 1; var y = 2; const z = 3"); + test_same("let x = 1; var y = 2; let z = 3;"); + } + + #[test] + fn test_mixed_declaration_types() { + // lets, vars, const declarations consecutive + test("let x = 1; let z = 3; var y = 2;", "let x = 1, z = 3; var y = 2;"); + test( + "let x = 1; let y = 2; var z = 3; var a = 4;", + "let x = 1, y = 2; var z = 3, a = 4", + ); + } } - #[test] - fn test_mixed_declaration_types() { - // lets, vars, const declarations consecutive - test("let x = 1; let z = 3; var y = 2;", "let x = 1, z = 3; var y = 2;"); - test("let x = 1; let y = 2; var z = 3; var a = 4;", "let x = 1, y = 2; var z = 3, a = 4"); + /// + #[cfg(test)] + mod collapse_for { + use super::{test, test_same}; + + #[test] + fn test_for() { + // Verify assignments are moved into the FOR init node. + test("a = 0; for(; a < 2 ; a++) foo()", "for(a = 0; a < 2 ; a++) foo();"); + // Verify vars are are moved into the FOR init node. + test("var a = 0; for(; c < b ; c++) foo()", "for(var a = 0; c < b ; c++) foo()"); + test( + "var a = 0; var b = 0; for(; c < b ; c++) foo()", + "for(var a = 0, b = 0; c < b ; c++) foo()", + ); + + // We don't handle labels yet. + test_same("var a = 0; a:for(; c < b ; c++) foo()"); + test_same("var a = 0; a:b:for(; c < b ; c++) foo()"); + + // Do not inline let or const + test_same("let a = 0; for(; c < b ; c++) foo()"); + test_same("const a = 0; for(; c < b ; c++) foo()"); + + // Verify FOR inside IFs. + test( + "if(x){var a = 0; for(; c < b; c++) foo()}", + "if(x){for(var a = 0; c < b; c++) foo()}", + ); + + // Any other expression. + test("init(); for(; a < 2 ; a++) foo()", "for(init(); a < 2 ; a++) foo();"); + + // Other statements are left as is. + test( + "function f(){ var a; for(; a < 2 ; a++) foo() }", + "function f(){ for(var a; a < 2 ; a++) foo() }", + ); + test_same("function f(){ return; for(; a < 2 ; a++) foo() }"); + + // TODO + // Verify destructuring assignments are moved. + // test( + // "[a, b] = [1, 2]; for (; a < 2; a = b++) foo();", + // "for ([a, b] = [1, 2]; a < 2; a = b++) foo();", + // ); + + // test( + // "var [a, b] = [1, 2]; for (; a < 2; a = b++) foo();", + // "var a; var b; for ([a, b] = [1, 2]; a < 2; a = b++) foo();", + // ); + } + + #[test] + fn test_for_in() { + test("var a; for(a in b) foo()", "for (var a in b) foo()"); + test_same("a = 0; for(a in b) foo()"); + test_same("var a = 0; for(a in b) foo()"); + + // We don't handle labels yet. + test_same("var a; a:for(a in b) foo()"); + test_same("var a; a:b:for(a in b) foo()"); + + // Verify FOR inside IFs. + test("if(x){var a; for(a in b) foo()}", "if(x){for(var a in b) foo()}"); + + // Any other expression. + test_same("init(); for(a in b) foo()"); + + // Other statements are left as is. + test_same("function f(){ return; for(a in b) foo() }"); + + // We don't handle destructuring patterns yet. + test("var a; var b; for ([a, b] in c) foo();", "var a, b; for ([a, b] in c) foo();"); + } + + #[test] + fn test_for_of() { + test("var a; for (a of b) foo()", "for (var a of b) foo()"); + test_same("a = 0; for (a of b) foo()"); + test_same("var a = 0; for (a of b) foo()"); + + // We don't handle labels yet. + test_same("var a; a: for (a of b) foo()"); + test_same("var a; a: b: for (a of b) foo()"); + + // Verify FOR inside IFs. + test("if (x) { var a; for (a of b) foo() }", "if (x) { for (var a of b) foo() }"); + + // Any other expression. + test_same("init(); for (a of b) foo()"); + + // Other statements are left as is. + test_same("function f() { return; for (a of b) foo() }"); + + // We don't handle destructuring patterns yet. + test("var a; var b; for ([a, b] of c) foo();", "var a, b; for ([a, b] of c) foo();"); + } } } diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 517880f8272fbf..93646406847edf 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -1,27 +1,27 @@ | Oxc | ESBuild | Oxc | ESBuild | Original | minified | minified | gzip | gzip | Fixture ------------------------------------------------------------------------------------- -72.14 kB | 23.96 kB | 23.70 kB | 8.58 kB | 8.54 kB | react.development.js +72.14 kB | 23.94 kB | 23.70 kB | 8.59 kB | 8.54 kB | react.development.js 173.90 kB | 61.52 kB | 59.82 kB | 19.54 kB | 19.33 kB | moment.js -287.63 kB | 92.51 kB | 90.07 kB | 32.29 kB | 31.95 kB | jquery.js +287.63 kB | 92.47 kB | 90.07 kB | 32.30 kB | 31.95 kB | jquery.js -342.15 kB | 121.48 kB | 118.14 kB | 44.65 kB | 44.37 kB | vue.js +342.15 kB | 121.36 kB | 118.14 kB | 44.67 kB | 44.37 kB | vue.js 544.10 kB | 73.32 kB | 72.48 kB | 26.13 kB | 26.20 kB | lodash.js -555.77 kB | 275.83 kB | 270.13 kB | 91.13 kB | 90.80 kB | d3.js +555.77 kB | 275.77 kB | 270.13 kB | 91.12 kB | 90.80 kB | d3.js -1.01 MB | 466.57 kB | 458.89 kB | 126.69 kB | 126.71 kB | bundle.min.js +1.01 MB | 466.38 kB | 458.89 kB | 126.72 kB | 126.71 kB | bundle.min.js -1.25 MB | 661.44 kB | 646.76 kB | 163.95 kB | 163.73 kB | three.js +1.25 MB | 660.41 kB | 646.76 kB | 163.99 kB | 163.73 kB | three.js -2.14 MB | 740.15 kB | 724.14 kB | 181.34 kB | 181.07 kB | victory.js +2.14 MB | 740.08 kB | 724.14 kB | 181.34 kB | 181.07 kB | victory.js -3.20 MB | 1.02 MB | 1.01 MB | 332.02 kB | 331.56 kB | echarts.js +3.20 MB | 1.02 MB | 1.01 MB | 332.26 kB | 331.56 kB | echarts.js -6.69 MB | 2.39 MB | 2.31 MB | 495.64 kB | 488.28 kB | antd.js +6.69 MB | 2.39 MB | 2.31 MB | 495.65 kB | 488.28 kB | antd.js -10.95 MB | 3.54 MB | 3.49 MB | 909.75 kB | 915.50 kB | typescript.js +10.95 MB | 3.54 MB | 3.49 MB | 909.94 kB | 915.50 kB | typescript.js From 72d996709e066e12af8420d310cb1cd8f0c49d91 Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Thu, 26 Dec 2024 07:02:45 +0000 Subject: [PATCH 011/162] feat(minifier): add `Normalize` ast pass (#8120) --- crates/oxc_minifier/src/ast_passes/mod.rs | 2 + .../oxc_minifier/src/ast_passes/normalize.rs | 67 +++++++++++++++++++ crates/oxc_minifier/src/compressor.rs | 5 +- tasks/minsize/minsize.snap | 20 +++--- 4 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 crates/oxc_minifier/src/ast_passes/normalize.rs diff --git a/crates/oxc_minifier/src/ast_passes/mod.rs b/crates/oxc_minifier/src/ast_passes/mod.rs index 77cc7b0bc2d5fa..a1cf75018f63c9 100644 --- a/crates/oxc_minifier/src/ast_passes/mod.rs +++ b/crates/oxc_minifier/src/ast_passes/mod.rs @@ -4,6 +4,7 @@ use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, Travers mod collapse_variable_declarations; mod exploit_assigns; +mod normalize; mod peephole_fold_constants; mod peephole_minimize_conditions; mod peephole_remove_dead_code; @@ -14,6 +15,7 @@ mod statement_fusion; pub use collapse_variable_declarations::CollapseVariableDeclarations; pub use exploit_assigns::ExploitAssigns; +pub use normalize::Normalize; pub use peephole_fold_constants::PeepholeFoldConstants; pub use peephole_minimize_conditions::PeepholeMinimizeConditions; pub use peephole_remove_dead_code::PeepholeRemoveDeadCode; diff --git a/crates/oxc_minifier/src/ast_passes/normalize.rs b/crates/oxc_minifier/src/ast_passes/normalize.rs new file mode 100644 index 00000000000000..cb252707420534 --- /dev/null +++ b/crates/oxc_minifier/src/ast_passes/normalize.rs @@ -0,0 +1,67 @@ +use oxc_ast::ast::*; +use oxc_syntax::scope::ScopeFlags; +use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx}; + +use crate::CompressorPass; + +/// Normalize AST +/// +/// Make subsequent AST passes easier to analyze: +/// +/// * convert whiles to fors +/// +/// +pub struct Normalize; + +impl<'a> CompressorPass<'a> for Normalize { + fn build(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>) { + traverse_mut_with_ctx(self, program, ctx); + } +} + +impl<'a> Traverse<'a> for Normalize { + fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { + if matches!(stmt, Statement::WhileStatement(_)) { + Self::convert_while_to_for(stmt, ctx); + } + } +} + +impl<'a> Normalize { + pub fn new() -> Self { + Self + } + + fn convert_while_to_for(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { + let Statement::WhileStatement(while_stmt) = ctx.ast.move_statement(stmt) else { return }; + let while_stmt = while_stmt.unbox(); + let for_stmt = ctx.ast.alloc_for_statement_with_scope_id( + while_stmt.span, + None, + Some(while_stmt.test), + None, + while_stmt.body, + ctx.create_child_scope_of_current(ScopeFlags::empty()), + ); + *stmt = Statement::ForStatement(for_stmt); + } +} + +#[cfg(test)] +mod test { + use oxc_allocator::Allocator; + + use crate::tester; + + fn test(source_text: &str, expected: &str) { + let allocator = Allocator::default(); + let mut pass = super::Normalize::new(); + tester::test(&allocator, source_text, expected, &mut pass); + } + + #[test] + fn test_while() { + // Verify while loops are converted to FOR loops. + test("while(c < b) foo()", "for(; c < b;) foo()"); + } +} diff --git a/crates/oxc_minifier/src/compressor.rs b/crates/oxc_minifier/src/compressor.rs index d0dd762b61b7fb..e284ee5a66368a 100644 --- a/crates/oxc_minifier/src/compressor.rs +++ b/crates/oxc_minifier/src/compressor.rs @@ -5,8 +5,8 @@ use oxc_traverse::ReusableTraverseCtx; use crate::{ ast_passes::{ - CollapsePass, DeadCodeElimination, LatePeepholeOptimizations, PeepholeOptimizations, - RemoveSyntax, + CollapsePass, DeadCodeElimination, LatePeepholeOptimizations, Normalize, + PeepholeOptimizations, RemoveSyntax, }, CompressOptions, CompressorPass, }; @@ -35,6 +35,7 @@ impl<'a> Compressor<'a> { ) { let mut ctx = ReusableTraverseCtx::new(scopes, symbols, self.allocator); RemoveSyntax::new(self.options).build(program, &mut ctx); + Normalize::new().build(program, &mut ctx); PeepholeOptimizations::new().build(program, &mut ctx); CollapsePass::new().build(program, &mut ctx); LatePeepholeOptimizations::new().run_in_loop(program, &mut ctx); diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 93646406847edf..c7682131d35eb1 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -5,23 +5,23 @@ Original | minified | minified | gzip | gzip | Fixture 173.90 kB | 61.52 kB | 59.82 kB | 19.54 kB | 19.33 kB | moment.js -287.63 kB | 92.47 kB | 90.07 kB | 32.30 kB | 31.95 kB | jquery.js +287.63 kB | 92.42 kB | 90.07 kB | 32.32 kB | 31.95 kB | jquery.js -342.15 kB | 121.36 kB | 118.14 kB | 44.67 kB | 44.37 kB | vue.js +342.15 kB | 121.31 kB | 118.14 kB | 44.69 kB | 44.37 kB | vue.js -544.10 kB | 73.32 kB | 72.48 kB | 26.13 kB | 26.20 kB | lodash.js +544.10 kB | 73.22 kB | 72.48 kB | 26.22 kB | 26.20 kB | lodash.js -555.77 kB | 275.77 kB | 270.13 kB | 91.12 kB | 90.80 kB | d3.js +555.77 kB | 275.67 kB | 270.13 kB | 91.19 kB | 90.80 kB | d3.js -1.01 MB | 466.38 kB | 458.89 kB | 126.72 kB | 126.71 kB | bundle.min.js +1.01 MB | 466.33 kB | 458.89 kB | 126.76 kB | 126.71 kB | bundle.min.js -1.25 MB | 660.41 kB | 646.76 kB | 163.99 kB | 163.73 kB | three.js +1.25 MB | 660.39 kB | 646.76 kB | 164.00 kB | 163.73 kB | three.js -2.14 MB | 740.08 kB | 724.14 kB | 181.34 kB | 181.07 kB | victory.js +2.14 MB | 739.97 kB | 724.14 kB | 181.42 kB | 181.07 kB | victory.js -3.20 MB | 1.02 MB | 1.01 MB | 332.26 kB | 331.56 kB | echarts.js +3.20 MB | 1.02 MB | 1.01 MB | 332.30 kB | 331.56 kB | echarts.js -6.69 MB | 2.39 MB | 2.31 MB | 495.65 kB | 488.28 kB | antd.js +6.69 MB | 2.39 MB | 2.31 MB | 495.67 kB | 488.28 kB | antd.js -10.95 MB | 3.54 MB | 3.49 MB | 909.94 kB | 915.50 kB | typescript.js +10.95 MB | 3.54 MB | 3.49 MB | 910.07 kB | 915.50 kB | typescript.js From f8200a8882affb4d7599291bc5bb4fbc425159e2 Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Thu, 26 Dec 2024 09:01:38 +0000 Subject: [PATCH 012/162] feat(minifier): minimize `if(foo) bar` -> `foo && bar` (#8121) --- crates/oxc_minifier/src/ast_passes/mod.rs | 77 ++++++++------- .../peephole_minimize_conditions.rs | 96 ++++++++++++++----- tasks/minsize/minsize.snap | 24 ++--- 3 files changed, 123 insertions(+), 74 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/mod.rs b/crates/oxc_minifier/src/ast_passes/mod.rs index a1cf75018f63c9..b6ffc5bb52dc13 100644 --- a/crates/oxc_minifier/src/ast_passes/mod.rs +++ b/crates/oxc_minifier/src/ast_passes/mod.rs @@ -66,12 +66,13 @@ impl<'a> Traverse<'a> for CollapsePass { // See `latePeepholeOptimizations` pub struct LatePeepholeOptimizations { x0_statement_fusion: StatementFusion, - x1_peephole_remove_dead_code: PeepholeRemoveDeadCode, + x1_collapse_variable_declarations: CollapseVariableDeclarations, + x2_peephole_remove_dead_code: PeepholeRemoveDeadCode, // TODO: MinimizeExitPoints - x2_peephole_minimize_conditions: PeepholeMinimizeConditions, - x3_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax, - x4_peephole_replace_known_methods: PeepholeReplaceKnownMethods, - x5_peephole_fold_constants: PeepholeFoldConstants, + x3_peephole_minimize_conditions: PeepholeMinimizeConditions, + x4_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax, + x5_peephole_replace_known_methods: PeepholeReplaceKnownMethods, + x6_peephole_fold_constants: PeepholeFoldConstants, } impl LatePeepholeOptimizations { @@ -79,32 +80,35 @@ impl LatePeepholeOptimizations { let in_fixed_loop = true; Self { x0_statement_fusion: StatementFusion::new(), - x1_peephole_remove_dead_code: PeepholeRemoveDeadCode::new(), - x2_peephole_minimize_conditions: PeepholeMinimizeConditions::new(in_fixed_loop), - x3_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax::new( + x1_collapse_variable_declarations: CollapseVariableDeclarations::new(), + x2_peephole_remove_dead_code: PeepholeRemoveDeadCode::new(), + x3_peephole_minimize_conditions: PeepholeMinimizeConditions::new(in_fixed_loop), + x4_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax::new( in_fixed_loop, ), - x4_peephole_replace_known_methods: PeepholeReplaceKnownMethods::new(), - x5_peephole_fold_constants: PeepholeFoldConstants::new(), + x5_peephole_replace_known_methods: PeepholeReplaceKnownMethods::new(), + x6_peephole_fold_constants: PeepholeFoldConstants::new(), } } fn reset_changed(&mut self) { self.x0_statement_fusion.changed = false; - self.x1_peephole_remove_dead_code.changed = false; - self.x2_peephole_minimize_conditions.changed = false; - self.x3_peephole_substitute_alternate_syntax.changed = false; - self.x4_peephole_replace_known_methods.changed = false; - self.x5_peephole_fold_constants.changed = false; + self.x1_collapse_variable_declarations.changed = false; + self.x2_peephole_remove_dead_code.changed = false; + self.x3_peephole_minimize_conditions.changed = false; + self.x4_peephole_substitute_alternate_syntax.changed = false; + self.x5_peephole_replace_known_methods.changed = false; + self.x6_peephole_fold_constants.changed = false; } fn changed(&self) -> bool { self.x0_statement_fusion.changed - || self.x1_peephole_remove_dead_code.changed - || self.x2_peephole_minimize_conditions.changed - || self.x3_peephole_substitute_alternate_syntax.changed - || self.x4_peephole_replace_known_methods.changed - || self.x5_peephole_fold_constants.changed + || self.x1_collapse_variable_declarations.changed + || self.x2_peephole_remove_dead_code.changed + || self.x3_peephole_minimize_conditions.changed + || self.x4_peephole_substitute_alternate_syntax.changed + || self.x5_peephole_replace_known_methods.changed + || self.x6_peephole_fold_constants.changed } pub fn run_in_loop<'a>( @@ -135,14 +139,9 @@ impl<'a> CompressorPass<'a> for LatePeepholeOptimizations { } impl<'a> Traverse<'a> for LatePeepholeOptimizations { - fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { - self.x1_peephole_remove_dead_code.exit_statement(stmt, ctx); - self.x2_peephole_minimize_conditions.exit_statement(stmt, ctx); - } - fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { self.x0_statement_fusion.exit_program(program, ctx); - self.x1_peephole_remove_dead_code.exit_program(program, ctx); + self.x2_peephole_remove_dead_code.exit_program(program, ctx); } fn exit_function_body(&mut self, body: &mut FunctionBody<'a>, ctx: &mut TraverseCtx<'a>) { @@ -150,7 +149,13 @@ impl<'a> Traverse<'a> for LatePeepholeOptimizations { } fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) { - self.x1_peephole_remove_dead_code.exit_statements(stmts, ctx); + self.x1_collapse_variable_declarations.exit_statements(stmts, ctx); + self.x2_peephole_remove_dead_code.exit_statements(stmts, ctx); + } + + fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { + self.x2_peephole_remove_dead_code.exit_statement(stmt, ctx); + self.x3_peephole_minimize_conditions.exit_statement(stmt, ctx); } fn exit_block_statement(&mut self, block: &mut BlockStatement<'a>, ctx: &mut TraverseCtx<'a>) { @@ -158,7 +163,7 @@ impl<'a> Traverse<'a> for LatePeepholeOptimizations { } fn exit_return_statement(&mut self, stmt: &mut ReturnStatement<'a>, ctx: &mut TraverseCtx<'a>) { - self.x3_peephole_substitute_alternate_syntax.exit_return_statement(stmt, ctx); + self.x4_peephole_substitute_alternate_syntax.exit_return_statement(stmt, ctx); } fn exit_variable_declaration( @@ -166,23 +171,23 @@ impl<'a> Traverse<'a> for LatePeepholeOptimizations { decl: &mut VariableDeclaration<'a>, ctx: &mut TraverseCtx<'a>, ) { - self.x3_peephole_substitute_alternate_syntax.exit_variable_declaration(decl, ctx); + self.x4_peephole_substitute_alternate_syntax.exit_variable_declaration(decl, ctx); } fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { - self.x1_peephole_remove_dead_code.exit_expression(expr, ctx); - self.x2_peephole_minimize_conditions.exit_expression(expr, ctx); - self.x3_peephole_substitute_alternate_syntax.exit_expression(expr, ctx); - self.x4_peephole_replace_known_methods.exit_expression(expr, ctx); - self.x5_peephole_fold_constants.exit_expression(expr, ctx); + self.x2_peephole_remove_dead_code.exit_expression(expr, ctx); + self.x3_peephole_minimize_conditions.exit_expression(expr, ctx); + self.x4_peephole_substitute_alternate_syntax.exit_expression(expr, ctx); + self.x5_peephole_replace_known_methods.exit_expression(expr, ctx); + self.x6_peephole_fold_constants.exit_expression(expr, ctx); } fn enter_call_expression(&mut self, expr: &mut CallExpression<'a>, ctx: &mut TraverseCtx<'a>) { - self.x3_peephole_substitute_alternate_syntax.enter_call_expression(expr, ctx); + self.x4_peephole_substitute_alternate_syntax.enter_call_expression(expr, ctx); } fn exit_call_expression(&mut self, expr: &mut CallExpression<'a>, ctx: &mut TraverseCtx<'a>) { - self.x3_peephole_substitute_alternate_syntax.exit_call_expression(expr, ctx); + self.x4_peephole_substitute_alternate_syntax.exit_call_expression(expr, ctx); } } diff --git a/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs b/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs index 5a2c09b8296771..43f090e59be866 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs @@ -26,6 +26,17 @@ impl<'a> CompressorPass<'a> for PeepholeMinimizeConditions { } impl<'a> Traverse<'a> for PeepholeMinimizeConditions { + fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { + if let Some(folded_stmt) = match stmt { + // If the condition is a literal, we'll let other optimizations try to remove useless code. + Statement::IfStatement(s) if !s.test.is_literal() => Self::try_minimize_if(stmt, ctx), + _ => None, + } { + *stmt = folded_stmt; + self.changed = true; + }; + } + fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { if let Some(folded_expr) = match expr { Expression::UnaryExpression(e) => Self::try_minimize_not(e, ctx), @@ -56,6 +67,40 @@ impl<'a> PeepholeMinimizeConditions { binary_expr.operator = new_op; Some(ctx.ast.move_expression(&mut expr.argument)) } + + fn try_minimize_if( + stmt: &mut Statement<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Option> { + let Statement::IfStatement(if_stmt) = stmt else { unreachable!() }; + if if_stmt.alternate.is_none() { + // `if(x)foo();` -> `x&&foo();` + if let Some(right) = Self::is_foldable_express_block(&mut if_stmt.consequent, ctx) { + let left = ctx.ast.move_expression(&mut if_stmt.test); + let logical_expr = + ctx.ast.expression_logical(if_stmt.span, left, LogicalOperator::And, right); + return Some(ctx.ast.statement_expression(if_stmt.span, logical_expr)); + } + } + None + } + + fn is_foldable_express_block( + stmt: &mut Statement<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Option> { + match stmt { + Statement::BlockStatement(block_stmt) if block_stmt.body.len() == 1 => { + if let Statement::ExpressionStatement(s) = &mut block_stmt.body[0] { + Some(ctx.ast.move_expression(&mut s.expression)) + } else { + None + } + } + Statement::ExpressionStatement(s) => Some(ctx.ast.move_expression(&mut s.expression)), + _ => None, + } + } } /// @@ -85,7 +130,6 @@ mod test { /** Check that removing blocks with 1 child works */ #[test] - #[ignore] fn test_fold_one_child_blocks() { // late = false; fold("function f(){if(x)a();x=3}", "function f(){x&&a();x=3}"); @@ -94,13 +138,13 @@ mod test { fold("function f(){if(x){a()}x=3}", "function f(){x&&a();x=3}"); fold("function f(){if(x){a?.()}x=3}", "function f(){x&&a?.();x=3}"); - fold("function f(){if(x){return 3}}", "function f(){if(x)return 3}"); + // fold("function f(){if(x){return 3}}", "function f(){if(x)return 3}"); fold("function f(){if(x){a()}}", "function f(){x&&a()}"); - fold("function f(){if(x){throw 1}}", "function f(){if(x)throw 1;}"); + // fold("function f(){if(x){throw 1}}", "function f(){if(x)throw 1;}"); // Try it out with functions fold("function f(){if(x){foo()}}", "function f(){x&&foo()}"); - fold("function f(){if(x){foo()}else{bar()}}", "function f(){x?foo():bar()}"); + // fold("function f(){if(x){foo()}else{bar()}}", "function f(){x?foo():bar()}"); // Try it out with properties and methods fold("function f(){if(x){a.b=1}}", "function f(){x&&(a.b=1)}"); @@ -122,35 +166,35 @@ mod test { // fold("if(x){do{foo()}while(y)}else bar()", "if(x){do foo();while(y)}else bar()"); // Play with nested IFs - fold("function f(){if(x){if(y)foo()}}", "function f(){x && (y && foo())}"); - fold("function f(){if(x){if(y)foo();else bar()}}", "function f(){x&&(y?foo():bar())}"); - fold("function f(){if(x){if(y)foo()}else bar()}", "function f(){x?y&&foo():bar()}"); - fold( - "function f(){if(x){if(y)foo();else bar()}else{baz()}}", - "function f(){x?y?foo():bar():baz()}", - ); + // fold("function f(){if(x){if(y)foo()}}", "function f(){x && (y && foo())}"); + // fold("function f(){if(x){if(y)foo();else bar()}}", "function f(){x&&(y?foo():bar())}"); + // fold("function f(){if(x){if(y)foo()}else bar()}", "function f(){x?y&&foo():bar()}"); + // fold( + // "function f(){if(x){if(y)foo();else bar()}else{baz()}}", + // "function f(){x?y?foo():bar():baz()}", + // ); // fold("if(e1){while(e2){if(e3){foo()}}}else{bar()}", "if(e1)while(e2)e3&&foo();else bar()"); // fold("if(e1){with(e2){if(e3){foo()}}}else{bar()}", "if(e1)with(e2)e3&&foo();else bar()"); - fold("if(a||b){if(c||d){var x;}}", "if(a||b)if(c||d)var x"); - fold("if(x){ if(y){var x;}else{var z;} }", "if(x)if(y)var x;else var z"); + // fold("if(a||b){if(c||d){var x;}}", "if(a||b)if(c||d)var x"); + // fold("if(x){ if(y){var x;}else{var z;} }", "if(x)if(y)var x;else var z"); // NOTE - technically we can remove the blocks since both the parent // and child have elses. But we don't since it causes ambiguities in // some cases where not all descendent ifs having elses - fold( - "if(x){ if(y){var x;}else{var z;} }else{var w}", - "if(x)if(y)var x;else var z;else var w", - ); - fold("if (x) {var x;}else { if (y) { var y;} }", "if(x)var x;else if(y)var y"); + // fold( + // "if(x){ if(y){var x;}else{var z;} }else{var w}", + // "if(x)if(y)var x;else var z;else var w", + // ); + // fold("if (x) {var x;}else { if (y) { var y;} }", "if(x)var x;else if(y)var y"); // Here's some of the ambiguous cases - fold( - "if(a){if(b){f1();f2();}else if(c){f3();}}else {if(d){f4();}}", - "if(a)if(b){f1();f2()}else c&&f3();else d&&f4()", - ); + // fold( + // "if(a){if(b){f1();f2();}else if(c){f3();}}else {if(d){f4();}}", + // "if(a)if(b){f1();f2()}else c&&f3();else d&&f4()", + // ); fold_same("function f(){foo()}"); fold_same("switch(x){case y: foo()}"); @@ -160,10 +204,10 @@ mod test { // Lexical declaration cannot appear in a single-statement context. fold_same("if (foo) { const bar = 1 } else { const baz = 1 }"); fold_same("if (foo) { let bar = 1 } else { let baz = 1 }"); - fold( - "if (foo) { var bar = 1 } else { var baz = 1 }", - "if (foo) var bar = 1; else var baz = 1;", - ); + // fold( + // "if (foo) { var bar = 1 } else { var baz = 1 }", + // "if (foo) var bar = 1; else var baz = 1;", + // ); } /** Try to minimize returns */ diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index c7682131d35eb1..a311960b71cef2 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -1,27 +1,27 @@ | Oxc | ESBuild | Oxc | ESBuild | Original | minified | minified | gzip | gzip | Fixture ------------------------------------------------------------------------------------- -72.14 kB | 23.94 kB | 23.70 kB | 8.59 kB | 8.54 kB | react.development.js +72.14 kB | 23.89 kB | 23.70 kB | 8.64 kB | 8.54 kB | react.development.js -173.90 kB | 61.52 kB | 59.82 kB | 19.54 kB | 19.33 kB | moment.js +173.90 kB | 61.42 kB | 59.82 kB | 19.60 kB | 19.33 kB | moment.js -287.63 kB | 92.42 kB | 90.07 kB | 32.32 kB | 31.95 kB | jquery.js +287.63 kB | 92.09 kB | 90.07 kB | 32.46 kB | 31.95 kB | jquery.js -342.15 kB | 121.31 kB | 118.14 kB | 44.69 kB | 44.37 kB | vue.js +342.15 kB | 120.77 kB | 118.14 kB | 44.86 kB | 44.37 kB | vue.js -544.10 kB | 73.22 kB | 72.48 kB | 26.22 kB | 26.20 kB | lodash.js +544.10 kB | 73.17 kB | 72.48 kB | 26.28 kB | 26.20 kB | lodash.js -555.77 kB | 275.67 kB | 270.13 kB | 91.19 kB | 90.80 kB | d3.js +555.77 kB | 275.52 kB | 270.13 kB | 91.48 kB | 90.80 kB | d3.js -1.01 MB | 466.33 kB | 458.89 kB | 126.76 kB | 126.71 kB | bundle.min.js +1.01 MB | 465.56 kB | 458.89 kB | 127.14 kB | 126.71 kB | bundle.min.js -1.25 MB | 660.39 kB | 646.76 kB | 164.00 kB | 163.73 kB | three.js +1.25 MB | 659.76 kB | 646.76 kB | 164.45 kB | 163.73 kB | three.js -2.14 MB | 739.97 kB | 724.14 kB | 181.42 kB | 181.07 kB | victory.js +2.14 MB | 739.69 kB | 724.14 kB | 181.77 kB | 181.07 kB | victory.js -3.20 MB | 1.02 MB | 1.01 MB | 332.30 kB | 331.56 kB | echarts.js +3.20 MB | 1.02 MB | 1.01 MB | 333.18 kB | 331.56 kB | echarts.js -6.69 MB | 2.39 MB | 2.31 MB | 495.67 kB | 488.28 kB | antd.js +6.69 MB | 2.39 MB | 2.31 MB | 496.55 kB | 488.28 kB | antd.js -10.95 MB | 3.54 MB | 3.49 MB | 910.07 kB | 915.50 kB | typescript.js +10.95 MB | 3.54 MB | 3.49 MB | 912.55 kB | 915.50 kB | typescript.js From f0b1ee5a06ad778b3ab1c095241eb002a8b9528f Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Thu, 26 Dec 2024 09:42:34 +0000 Subject: [PATCH 013/162] feat(minifier): minimize `if(!x) foo()` -> `x || foo()` (#8122) --- .../peephole_minimize_conditions.rs | 29 ++++++++++++++----- tasks/minsize/minsize.snap | 22 +++++++------- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs b/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs index 43f090e59be866..62eec660ef192f 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs @@ -74,12 +74,26 @@ impl<'a> PeepholeMinimizeConditions { ) -> Option> { let Statement::IfStatement(if_stmt) = stmt else { unreachable!() }; if if_stmt.alternate.is_none() { - // `if(x)foo();` -> `x&&foo();` if let Some(right) = Self::is_foldable_express_block(&mut if_stmt.consequent, ctx) { - let left = ctx.ast.move_expression(&mut if_stmt.test); - let logical_expr = - ctx.ast.expression_logical(if_stmt.span, left, LogicalOperator::And, right); - return Some(ctx.ast.statement_expression(if_stmt.span, logical_expr)); + let test = ctx.ast.move_expression(&mut if_stmt.test); + // `if(!x) foo()` -> `x || foo()` + if let Expression::UnaryExpression(unary_expr) = test { + if unary_expr.operator.is_not() { + let left = unary_expr.unbox().argument; + let logical_expr = ctx.ast.expression_logical( + if_stmt.span, + left, + LogicalOperator::Or, + right, + ); + return Some(ctx.ast.statement_expression(if_stmt.span, logical_expr)); + } + } else { + // `if(x) foo()` -> `x && foo()` + let logical_expr = + ctx.ast.expression_logical(if_stmt.span, test, LogicalOperator::And, right); + return Some(ctx.ast.statement_expression(if_stmt.span, logical_expr)); + } } } None @@ -356,12 +370,11 @@ mod test { } #[test] - #[ignore] fn test_not_cond() { fold("function f(){if(!x)foo()}", "function f(){x||foo()}"); fold("function f(){if(!x)b=1}", "function f(){x||(b=1)}"); - fold("if(!x)z=1;else if(y)z=2", "x ? y&&(z=2) : z=1;"); - fold("if(x)y&&(z=2);else z=1;", "x ? y&&(z=2) : z=1"); + // fold("if(!x)z=1;else if(y)z=2", "x ? y&&(z=2) : z=1;"); + // fold("if(x)y&&(z=2);else z=1;", "x ? y&&(z=2) : z=1"); fold("function f(){if(!(x=1))a.b=1}", "function f(){(x=1)||(a.b=1)}"); } diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index a311960b71cef2..b9f1059afe8d7e 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -3,25 +3,25 @@ Original | minified | minified | gzip | gzip | Fixture ------------------------------------------------------------------------------------- 72.14 kB | 23.89 kB | 23.70 kB | 8.64 kB | 8.54 kB | react.development.js -173.90 kB | 61.42 kB | 59.82 kB | 19.60 kB | 19.33 kB | moment.js +173.90 kB | 61.39 kB | 59.82 kB | 19.60 kB | 19.33 kB | moment.js -287.63 kB | 92.09 kB | 90.07 kB | 32.46 kB | 31.95 kB | jquery.js +287.63 kB | 92.05 kB | 90.07 kB | 32.44 kB | 31.95 kB | jquery.js -342.15 kB | 120.77 kB | 118.14 kB | 44.86 kB | 44.37 kB | vue.js +342.15 kB | 120.72 kB | 118.14 kB | 44.86 kB | 44.37 kB | vue.js -544.10 kB | 73.17 kB | 72.48 kB | 26.28 kB | 26.20 kB | lodash.js +544.10 kB | 73.15 kB | 72.48 kB | 26.28 kB | 26.20 kB | lodash.js -555.77 kB | 275.52 kB | 270.13 kB | 91.48 kB | 90.80 kB | d3.js +555.77 kB | 275.48 kB | 270.13 kB | 91.48 kB | 90.80 kB | d3.js -1.01 MB | 465.56 kB | 458.89 kB | 127.14 kB | 126.71 kB | bundle.min.js +1.01 MB | 465.45 kB | 458.89 kB | 127.12 kB | 126.71 kB | bundle.min.js -1.25 MB | 659.76 kB | 646.76 kB | 164.45 kB | 163.73 kB | three.js +1.25 MB | 659.74 kB | 646.76 kB | 164.45 kB | 163.73 kB | three.js -2.14 MB | 739.69 kB | 724.14 kB | 181.77 kB | 181.07 kB | victory.js +2.14 MB | 739.59 kB | 724.14 kB | 181.75 kB | 181.07 kB | victory.js -3.20 MB | 1.02 MB | 1.01 MB | 333.18 kB | 331.56 kB | echarts.js +3.20 MB | 1.02 MB | 1.01 MB | 332.98 kB | 331.56 kB | echarts.js -6.69 MB | 2.39 MB | 2.31 MB | 496.55 kB | 488.28 kB | antd.js +6.69 MB | 2.39 MB | 2.31 MB | 496.49 kB | 488.28 kB | antd.js -10.95 MB | 3.54 MB | 3.49 MB | 912.55 kB | 915.50 kB | typescript.js +10.95 MB | 3.54 MB | 3.49 MB | 912.49 kB | 915.50 kB | typescript.js From 777e13c534af4fd5a64e8bc04a70a2c0f6c87ba4 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Thu, 26 Dec 2024 21:21:01 +0900 Subject: [PATCH 014/162] chore(linter): use `assert_eq!` instead of `assert!` (#8124) This ensures that both the expected and actual values are printed in the log when the test fails. --- crates/oxc_linter/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/oxc_linter/src/lib.rs b/crates/oxc_linter/src/lib.rs index ffcf7a3bf3ecac..ec5a500ee6ffcf 100644 --- a/crates/oxc_linter/src/lib.rs +++ b/crates/oxc_linter/src/lib.rs @@ -57,7 +57,7 @@ fn size_asserts() { // `RuleEnum` runs in a really tight loop, make sure it is small for CPU cache. // A reduction from 168 bytes to 16 results 15% performance improvement. // See codspeed in https://github.com/oxc-project/oxc/pull/1783 - assert!(std::mem::size_of::() == 16); + assert_eq!(size_of::(), 16); } #[derive(Debug)] From 093ddd6f4d56aa167f8315ef4427fc5f86e88558 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Thu, 26 Dec 2024 22:52:55 +0900 Subject: [PATCH 015/162] chore(ast): use `assert_eq!` instead of `assert!` (`crates/oxc_ast/src/lib.rs`) (#8125) related to https://github.com/oxc-project/oxc/pull/8124 --- crates/oxc_ast/src/lib.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/oxc_ast/src/lib.rs b/crates/oxc_ast/src/lib.rs index 0010d1bb14c52f..4da8cae35cf9a6 100644 --- a/crates/oxc_ast/src/lib.rs +++ b/crates/oxc_ast/src/lib.rs @@ -119,18 +119,18 @@ fn size_asserts() { use crate::ast; - assert!(size_of::() == 16); - assert!(size_of::() == 16); - assert!(size_of::() == 16); - assert!(size_of::() == 16); - assert!(size_of::() == 16); - assert!(size_of::() == 16); - assert!(size_of::() == 16); - assert!(size_of::() == 16); - assert!(size_of::() == 16); - assert!(size_of::() == 16); - assert!(size_of::() == 16); - assert!(size_of::() == 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); } #[test] From e676fdfd832aefef479785f9ddb417f65e3a7af5 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Thu, 26 Dec 2024 22:54:05 +0900 Subject: [PATCH 016/162] chore(tasks): use `assert_eq!` instead of `assert!` (`tasks/minsize/src/lib.rs`) (#8129) related to https://github.com/oxc-project/oxc/pull/8124 --- tasks/minsize/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/minsize/src/lib.rs b/tasks/minsize/src/lib.rs index e3655b6977010a..3c149505e568d8 100644 --- a/tasks/minsize/src/lib.rs +++ b/tasks/minsize/src/lib.rs @@ -131,7 +131,7 @@ fn minify_twice(file: &TestFile) -> String { }; let source_text1 = minify(&file.source_text, source_type, options); let source_text2 = minify(&source_text1, source_type, options); - assert!(source_text1 == source_text2, "Minification failed for {}", &file.file_name); + assert_eq!(source_text1, source_text2, "Minification failed for {}", &file.file_name); source_text2 } From eb0f57f40f2463a9969e8fddd89b61698410f3d6 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Thu, 26 Dec 2024 22:54:21 +0900 Subject: [PATCH 017/162] chore(semantic): use `assert_eq!` instead of `assert!` (`crates/oxc_semantic/src/lib.rs`) (#8127) related to https://github.com/oxc-project/oxc/pull/8124 --- crates/oxc_semantic/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/oxc_semantic/src/lib.rs b/crates/oxc_semantic/src/lib.rs index 1680db8df736c7..8dabf4e3204118 100644 --- a/crates/oxc_semantic/src/lib.rs +++ b/crates/oxc_semantic/src/lib.rs @@ -312,7 +312,7 @@ mod tests { let allocator = Allocator::default(); let source_type: SourceType = SourceType::default().with_typescript(true); let semantic = get_semantic(&allocator, source, source_type); - assert!(semantic.symbols().references.len() == 1); + assert_eq!(semantic.symbols().references.len(), 1); } #[test] From 04c7d3828742f03d2bdfe43b620bfe10f3f1e38e Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Thu, 26 Dec 2024 23:14:33 +0900 Subject: [PATCH 018/162] chore(semantic): use `assert_eq!` instead of `assert!` (`crates/oxc_semantic/src/scope.rs`) (#8128) related to https://github.com/oxc-project/oxc/pull/8124 --- crates/oxc_semantic/src/scope.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/oxc_semantic/src/scope.rs b/crates/oxc_semantic/src/scope.rs index 522351e65fe63b..3edc5ff6b405fe 100644 --- a/crates/oxc_semantic/src/scope.rs +++ b/crates/oxc_semantic/src/scope.rs @@ -176,7 +176,7 @@ impl ScopeTree { self.cell.with_dependent_mut(|_allocator, inner| { let reference_ids = inner.root_unresolved_references.get_mut(name).unwrap(); if reference_ids.len() == 1 { - assert!(reference_ids[0] == reference_id); + assert_eq!(reference_ids[0], reference_id); inner.root_unresolved_references.remove(name); } else { let index = reference_ids.iter().position(|&id| id == reference_id).unwrap(); From f615bfa773b9087fa3070dc18c8c2e4f4287f06a Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Thu, 26 Dec 2024 15:36:25 +0000 Subject: [PATCH 019/162] feat(minifier): minimize `if (x) return; return 1` -> `return x ? void 0 : 1` (#8130) --- crates/oxc_minifier/src/ast_passes/mod.rs | 12 +- .../peephole_minimize_conditions.rs | 124 ++++++++++++++++-- tasks/minsize/minsize.snap | 24 ++-- 3 files changed, 135 insertions(+), 25 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/mod.rs b/crates/oxc_minifier/src/ast_passes/mod.rs index b6ffc5bb52dc13..d92458fd6724d3 100644 --- a/crates/oxc_minifier/src/ast_passes/mod.rs +++ b/crates/oxc_minifier/src/ast_passes/mod.rs @@ -151,6 +151,7 @@ impl<'a> Traverse<'a> for LatePeepholeOptimizations { fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) { self.x1_collapse_variable_declarations.exit_statements(stmts, ctx); self.x2_peephole_remove_dead_code.exit_statements(stmts, ctx); + self.x3_peephole_minimize_conditions.exit_statements(stmts, ctx); } fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { @@ -223,19 +224,20 @@ impl<'a> CompressorPass<'a> for PeepholeOptimizations { } impl<'a> Traverse<'a> for PeepholeOptimizations { - fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { - self.x2_peephole_minimize_conditions.exit_statement(stmt, ctx); - self.x5_peephole_remove_dead_code.exit_statement(stmt, ctx); - } - fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { self.x5_peephole_remove_dead_code.exit_program(program, ctx); } fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) { + self.x2_peephole_minimize_conditions.exit_statements(stmts, ctx); self.x5_peephole_remove_dead_code.exit_statements(stmts, ctx); } + fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { + self.x2_peephole_minimize_conditions.exit_statement(stmt, ctx); + self.x5_peephole_remove_dead_code.exit_statement(stmt, ctx); + } + fn exit_return_statement(&mut self, stmt: &mut ReturnStatement<'a>, ctx: &mut TraverseCtx<'a>) { self.x3_peephole_substitute_alternate_syntax.exit_return_statement(stmt, ctx); } diff --git a/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs b/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs index 62eec660ef192f..b31990be346a12 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs @@ -1,3 +1,4 @@ +use oxc_allocator::Vec; use oxc_ast::ast::*; use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx}; @@ -26,6 +27,21 @@ impl<'a> CompressorPass<'a> for PeepholeMinimizeConditions { } impl<'a> Traverse<'a> for PeepholeMinimizeConditions { + fn exit_statements( + &mut self, + stmts: &mut oxc_allocator::Vec<'a, Statement<'a>>, + ctx: &mut TraverseCtx<'a>, + ) { + self.try_replace_if(stmts, ctx); + while self.changed { + self.changed = false; + self.try_replace_if(stmts, ctx); + if stmts.iter().any(|stmt| matches!(stmt, Statement::EmptyStatement(_))) { + stmts.retain(|stmt| !matches!(stmt, Statement::EmptyStatement(_))); + } + } + } + fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { if let Some(folded_stmt) = match stmt { // If the condition is a literal, we'll let other optimizations try to remove useless code. @@ -115,6 +131,103 @@ impl<'a> PeepholeMinimizeConditions { _ => None, } } + + fn try_replace_if(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) { + for i in 0..stmts.len() { + let Statement::IfStatement(if_stmt) = &stmts[i] else { + continue; + }; + let then_branch = &if_stmt.consequent; + let else_branch = &if_stmt.alternate; + let next_node = stmts.get(i + 1); + + if next_node.is_some_and(|s| matches!(s, Statement::IfStatement(_))) + && else_branch.is_none() + && Self::is_return_block(then_branch) + { + /* TODO */ + } else if next_node.is_some_and(Self::is_return_expression) + && else_branch.is_none() + && Self::is_return_block(then_branch) + { + // `if (x) return; return 1` -> `return x ? void 0 : 1` + let Statement::IfStatement(if_stmt) = ctx.ast.move_statement(&mut stmts[i]) else { + unreachable!() + }; + let mut if_stmt = if_stmt.unbox(); + let consequent = Self::get_block_return_expression(&mut if_stmt.consequent, ctx); + let alternate = Self::take_return_argument(&mut stmts[i + 1], ctx); + let argument = ctx.ast.expression_conditional( + if_stmt.span, + if_stmt.test, + consequent, + alternate, + ); + stmts[i] = ctx.ast.statement_return(if_stmt.span, Some(argument)); + self.changed = true; + break; + } else if else_branch.is_some() && Self::statement_must_exit_parent(then_branch) { + let Statement::IfStatement(if_stmt) = &mut stmts[i] else { + unreachable!(); + }; + let else_branch = if_stmt.alternate.take().unwrap(); + stmts.insert(i + 1, else_branch); + self.changed = true; + } + } + } + + fn is_return_block(stmt: &Statement<'a>) -> bool { + match stmt { + Statement::BlockStatement(block_stmt) if block_stmt.body.len() == 1 => { + matches!(block_stmt.body[0], Statement::ReturnStatement(_)) + } + Statement::ReturnStatement(_) => true, + _ => false, + } + } + + fn is_return_expression(stmt: &Statement<'a>) -> bool { + matches!(stmt, Statement::ReturnStatement(return_stmt) if return_stmt.argument.is_some()) + } + + fn statement_must_exit_parent(stmt: &Statement<'a>) -> bool { + match stmt { + Statement::ThrowStatement(_) | Statement::ReturnStatement(_) => true, + Statement::BlockStatement(block_stmt) => { + block_stmt.body.last().is_some_and(Self::statement_must_exit_parent) + } + _ => false, + } + } + + fn get_block_return_expression( + stmt: &mut Statement<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Expression<'a> { + match stmt { + Statement::BlockStatement(block_stmt) if block_stmt.body.len() == 1 => { + if let Statement::ReturnStatement(_) = &mut block_stmt.body[0] { + Self::take_return_argument(stmt, ctx) + } else { + unreachable!() + } + } + Statement::ReturnStatement(_) => Self::take_return_argument(stmt, ctx), + _ => unreachable!(), + } + } + + fn take_return_argument(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) -> Expression<'a> { + let Statement::ReturnStatement(return_stmt) = ctx.ast.move_statement(stmt) else { + unreachable!() + }; + let return_stmt = return_stmt.unbox(); + match return_stmt.argument { + Some(e) => e, + None => ctx.ast.void_0(return_stmt.span), + } + } } /// @@ -226,7 +339,6 @@ mod test { /** Try to minimize returns */ #[test] - #[ignore] fn test_fold_returns() { fold("function f(){if(x)return 1;else return 2}", "function f(){return x?1:2}"); fold("function f(){if(x)return 1;return 2}", "function f(){return x?1:2}"); @@ -238,10 +350,10 @@ mod test { "function f(){return x?(y+=1):(y+=2)}", ); - fold("function f(){if(x)return;else return 2-x}", "function f(){if(x);else return 2-x}"); + fold("function f(){if(x)return;else return 2-x}", "function f(){return x?void 0:2-x}"); fold("function f(){if(x)return;return 2-x}", "function f(){return x?void 0:2-x}"); - fold("function f(){if(x)return x;else return}", "function f(){if(x)return x;{}}"); - fold("function f(){if(x)return x;return}", "function f(){if(x)return x}"); + fold("function f(){if(x)return x;else return}", "function f(){if(x)return x;return;}"); + fold("function f(){if(x)return x;return}", "function f(){if(x)return x;return}"); fold_same("function f(){for(var x in y) { return x.y; } return k}"); } @@ -347,7 +459,6 @@ mod test { } #[test] - #[ignore] fn test_fold_returns_integration2() { // late = true; // disableNormalize(); @@ -359,7 +470,6 @@ mod test { } #[test] - #[ignore] fn test_dont_remove_duplicate_statements_without_normalization() { // In the following test case, we can't remove the duplicate "alert(x);" lines since each "x" // refers to a different variable. @@ -516,13 +626,11 @@ mod test { } #[test] - #[ignore] fn test_preserve_if() { fold_same("if(!a&&!b)for(;f(););"); } #[test] - #[ignore] fn test_no_swap_with_dangling_else() { fold_same("if(!x) {for(;;)foo(); for(;;)bar()} else if(y) for(;;) f()"); fold_same("if(!a&&!b) {for(;;)foo(); for(;;)bar()} else if(y) for(;;) f()"); diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index b9f1059afe8d7e..7c52fdae174862 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -1,27 +1,27 @@ | Oxc | ESBuild | Oxc | ESBuild | Original | minified | minified | gzip | gzip | Fixture ------------------------------------------------------------------------------------- -72.14 kB | 23.89 kB | 23.70 kB | 8.64 kB | 8.54 kB | react.development.js +72.14 kB | 23.85 kB | 23.70 kB | 8.64 kB | 8.54 kB | react.development.js -173.90 kB | 61.39 kB | 59.82 kB | 19.60 kB | 19.33 kB | moment.js +173.90 kB | 60.60 kB | 59.82 kB | 19.54 kB | 19.33 kB | moment.js -287.63 kB | 92.05 kB | 90.07 kB | 32.44 kB | 31.95 kB | jquery.js +287.63 kB | 91.40 kB | 90.07 kB | 32.36 kB | 31.95 kB | jquery.js -342.15 kB | 120.72 kB | 118.14 kB | 44.86 kB | 44.37 kB | vue.js +342.15 kB | 120.07 kB | 118.14 kB | 44.79 kB | 44.37 kB | vue.js -544.10 kB | 73.15 kB | 72.48 kB | 26.28 kB | 26.20 kB | lodash.js +544.10 kB | 72.91 kB | 72.48 kB | 26.27 kB | 26.20 kB | lodash.js -555.77 kB | 275.48 kB | 270.13 kB | 91.48 kB | 90.80 kB | d3.js +555.77 kB | 275.31 kB | 270.13 kB | 91.45 kB | 90.80 kB | d3.js -1.01 MB | 465.45 kB | 458.89 kB | 127.12 kB | 126.71 kB | bundle.min.js +1.01 MB | 462.98 kB | 458.89 kB | 127.06 kB | 126.71 kB | bundle.min.js -1.25 MB | 659.74 kB | 646.76 kB | 164.45 kB | 163.73 kB | three.js +1.25 MB | 658.98 kB | 646.76 kB | 164.40 kB | 163.73 kB | three.js -2.14 MB | 739.59 kB | 724.14 kB | 181.75 kB | 181.07 kB | victory.js +2.14 MB | 736.98 kB | 724.14 kB | 181.33 kB | 181.07 kB | victory.js -3.20 MB | 1.02 MB | 1.01 MB | 332.98 kB | 331.56 kB | echarts.js +3.20 MB | 1.02 MB | 1.01 MB | 332.77 kB | 331.56 kB | echarts.js -6.69 MB | 2.39 MB | 2.31 MB | 496.49 kB | 488.28 kB | antd.js +6.69 MB | 2.38 MB | 2.31 MB | 495.91 kB | 488.28 kB | antd.js -10.95 MB | 3.54 MB | 3.49 MB | 912.49 kB | 915.50 kB | typescript.js +10.95 MB | 3.52 MB | 3.49 MB | 911.59 kB | 915.50 kB | typescript.js From 6b51e6dff1a505987a088d188ac9ed20482d88af Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Thu, 26 Dec 2024 16:21:34 +0000 Subject: [PATCH 020/162] feat(minifier): minimize `if(foo) bar else baz` -> `foo ? bar : baz` (#8133) --- .../peephole_minimize_conditions.rs | 94 ++++++++++++------- tasks/minsize/minsize.snap | 24 ++--- 2 files changed, 74 insertions(+), 44 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs b/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs index b31990be346a12..a4d504c418b7b2 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs @@ -89,47 +89,53 @@ impl<'a> PeepholeMinimizeConditions { ctx: &mut TraverseCtx<'a>, ) -> Option> { let Statement::IfStatement(if_stmt) = stmt else { unreachable!() }; - if if_stmt.alternate.is_none() { - if let Some(right) = Self::is_foldable_express_block(&mut if_stmt.consequent, ctx) { - let test = ctx.ast.move_expression(&mut if_stmt.test); - // `if(!x) foo()` -> `x || foo()` - if let Expression::UnaryExpression(unary_expr) = test { - if unary_expr.operator.is_not() { - let left = unary_expr.unbox().argument; + match &if_stmt.alternate { + None => { + if Self::is_foldable_express_block(&if_stmt.consequent) { + let right = Self::get_block_expression(&mut if_stmt.consequent, ctx); + let test = ctx.ast.move_expression(&mut if_stmt.test); + // `if(!x) foo()` -> `x || foo()` + if let Expression::UnaryExpression(unary_expr) = test { + if unary_expr.operator.is_not() { + let left = unary_expr.unbox().argument; + let logical_expr = ctx.ast.expression_logical( + if_stmt.span, + left, + LogicalOperator::Or, + right, + ); + return Some(ctx.ast.statement_expression(if_stmt.span, logical_expr)); + } + } else { + // `if(x) foo()` -> `x && foo()` let logical_expr = ctx.ast.expression_logical( if_stmt.span, - left, - LogicalOperator::Or, + test, + LogicalOperator::And, right, ); return Some(ctx.ast.statement_expression(if_stmt.span, logical_expr)); } - } else { - // `if(x) foo()` -> `x && foo()` - let logical_expr = - ctx.ast.expression_logical(if_stmt.span, test, LogicalOperator::And, right); - return Some(ctx.ast.statement_expression(if_stmt.span, logical_expr)); } } - } - None - } - - fn is_foldable_express_block( - stmt: &mut Statement<'a>, - ctx: &mut TraverseCtx<'a>, - ) -> Option> { - match stmt { - Statement::BlockStatement(block_stmt) if block_stmt.body.len() == 1 => { - if let Statement::ExpressionStatement(s) = &mut block_stmt.body[0] { - Some(ctx.ast.move_expression(&mut s.expression)) - } else { - None + Some(else_branch) => { + let then_branch_is_expression_block = + Self::is_foldable_express_block(&if_stmt.consequent); + let else_branch_is_expression_block = Self::is_foldable_express_block(else_branch); + // `if(foo) bar else baz` -> `foo ? bar : baz` + if then_branch_is_expression_block && else_branch_is_expression_block { + let test = ctx.ast.move_expression(&mut if_stmt.test); + let consequent = Self::get_block_expression(&mut if_stmt.consequent, ctx); + let else_branch = if_stmt.alternate.as_mut().unwrap(); + let alternate = Self::get_block_expression(else_branch, ctx); + let expr = + ctx.ast.expression_conditional(if_stmt.span, test, consequent, alternate); + return Some(ctx.ast.statement_expression(if_stmt.span, expr)); } } - Statement::ExpressionStatement(s) => Some(ctx.ast.move_expression(&mut s.expression)), - _ => None, } + + None } fn try_replace_if(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) { @@ -177,6 +183,30 @@ impl<'a> PeepholeMinimizeConditions { } } + fn is_foldable_express_block(stmt: &Statement<'a>) -> bool { + match stmt { + Statement::BlockStatement(block_stmt) if block_stmt.body.len() == 1 => { + matches!(&block_stmt.body[0], Statement::ExpressionStatement(_)) + } + Statement::ExpressionStatement(_) => true, + _ => false, + } + } + + fn get_block_expression(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) -> Expression<'a> { + match stmt { + Statement::BlockStatement(block_stmt) if block_stmt.body.len() == 1 => { + if let Statement::ExpressionStatement(s) = &mut block_stmt.body[0] { + ctx.ast.move_expression(&mut s.expression) + } else { + unreachable!() + } + } + Statement::ExpressionStatement(s) => ctx.ast.move_expression(&mut s.expression), + _ => unreachable!(), + } + } + fn is_return_block(stmt: &Statement<'a>) -> bool { match stmt { Statement::BlockStatement(block_stmt) if block_stmt.body.len() == 1 => { @@ -271,7 +301,7 @@ mod test { // Try it out with functions fold("function f(){if(x){foo()}}", "function f(){x&&foo()}"); - // fold("function f(){if(x){foo()}else{bar()}}", "function f(){x?foo():bar()}"); + fold("function f(){if(x){foo()}else{bar()}}", "function f(){x?foo():bar()}"); // Try it out with properties and methods fold("function f(){if(x){a.b=1}}", "function f(){x&&(a.b=1)}"); @@ -288,7 +318,7 @@ mod test { fold_same("function f(){switch(x){case 1:break}}"); // Do while loops stay in a block if that's where they started - // fold_same("function f(){if(e1){do foo();while(e2)}else foo2()}"); + fold_same("function f(){if(e1){do foo();while(e2)}else foo2()}"); // Test an obscure case with do and while // fold("if(x){do{foo()}while(y)}else bar()", "if(x){do foo();while(y)}else bar()"); diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 7c52fdae174862..b944c7e70f5c05 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -1,27 +1,27 @@ | Oxc | ESBuild | Oxc | ESBuild | Original | minified | minified | gzip | gzip | Fixture ------------------------------------------------------------------------------------- -72.14 kB | 23.85 kB | 23.70 kB | 8.64 kB | 8.54 kB | react.development.js +72.14 kB | 23.77 kB | 23.70 kB | 8.62 kB | 8.54 kB | react.development.js -173.90 kB | 60.60 kB | 59.82 kB | 19.54 kB | 19.33 kB | moment.js +173.90 kB | 60.22 kB | 59.82 kB | 19.49 kB | 19.33 kB | moment.js -287.63 kB | 91.40 kB | 90.07 kB | 32.36 kB | 31.95 kB | jquery.js +287.63 kB | 90.77 kB | 90.07 kB | 32.23 kB | 31.95 kB | jquery.js -342.15 kB | 120.07 kB | 118.14 kB | 44.79 kB | 44.37 kB | vue.js +342.15 kB | 119.00 kB | 118.14 kB | 44.65 kB | 44.37 kB | vue.js -544.10 kB | 72.91 kB | 72.48 kB | 26.27 kB | 26.20 kB | lodash.js +544.10 kB | 72.54 kB | 72.48 kB | 26.23 kB | 26.20 kB | lodash.js -555.77 kB | 275.31 kB | 270.13 kB | 91.45 kB | 90.80 kB | d3.js +555.77 kB | 274.27 kB | 270.13 kB | 91.27 kB | 90.80 kB | d3.js -1.01 MB | 462.98 kB | 458.89 kB | 127.06 kB | 126.71 kB | bundle.min.js +1.01 MB | 461.18 kB | 458.89 kB | 126.93 kB | 126.71 kB | bundle.min.js -1.25 MB | 658.98 kB | 646.76 kB | 164.40 kB | 163.73 kB | three.js +1.25 MB | 657.26 kB | 646.76 kB | 164.24 kB | 163.73 kB | three.js -2.14 MB | 736.98 kB | 724.14 kB | 181.33 kB | 181.07 kB | victory.js +2.14 MB | 735.73 kB | 724.14 kB | 181.11 kB | 181.07 kB | victory.js -3.20 MB | 1.02 MB | 1.01 MB | 332.77 kB | 331.56 kB | echarts.js +3.20 MB | 1.01 MB | 1.01 MB | 332.37 kB | 331.56 kB | echarts.js -6.69 MB | 2.38 MB | 2.31 MB | 495.91 kB | 488.28 kB | antd.js +6.69 MB | 2.38 MB | 2.31 MB | 495.35 kB | 488.28 kB | antd.js -10.95 MB | 3.52 MB | 3.49 MB | 911.59 kB | 915.50 kB | typescript.js +10.95 MB | 3.52 MB | 3.49 MB | 911.00 kB | 915.50 kB | typescript.js From ad61e7018657f8802a0957068d2d4d3936cd2d58 Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Fri, 27 Dec 2024 00:39:52 +0000 Subject: [PATCH 021/162] fix(codegen): print if else without block with proper indentation (#8135) --- crates/oxc_codegen/examples/codegen.rs | 9 +++++++-- crates/oxc_codegen/src/gen.rs | 7 ++++++- crates/oxc_codegen/tests/integration/unit.rs | 12 ++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/crates/oxc_codegen/examples/codegen.rs b/crates/oxc_codegen/examples/codegen.rs index 5755d891ba311d..e941dfcbe34bd9 100644 --- a/crates/oxc_codegen/examples/codegen.rs +++ b/crates/oxc_codegen/examples/codegen.rs @@ -3,7 +3,7 @@ use std::path::Path; use oxc_allocator::Allocator; use oxc_codegen::{CodeGenerator, CodegenOptions}; -use oxc_parser::{Parser, ParserReturn}; +use oxc_parser::{ParseOptions, Parser, ParserReturn}; use oxc_span::SourceType; use pico_args::Arguments; @@ -52,7 +52,12 @@ fn parse<'a>( source_text: &'a str, source_type: SourceType, ) -> Option> { - let ret = Parser::new(allocator, source_text, source_type).parse(); + let ret = Parser::new(allocator, source_text, source_type) + .with_options(ParseOptions { + allow_return_outside_function: true, + ..ParseOptions::default() + }) + .parse(); if !ret.errors.is_empty() { for error in ret.errors { println!("{:?}", error.with_source_code(source_text.to_string())); diff --git a/crates/oxc_codegen/src/gen.rs b/crates/oxc_codegen/src/gen.rs index f5cee102b14d31..5a9d864f549f22 100644 --- a/crates/oxc_codegen/src/gen.rs +++ b/crates/oxc_codegen/src/gen.rs @@ -301,7 +301,12 @@ fn print_if(if_stmt: &IfStatement<'_>, p: &mut Codegen, ctx: Context) { p.print_soft_newline(); } } - stmt => p.print_body(stmt, false, ctx), + stmt => { + p.print_body(stmt, false, ctx); + if if_stmt.alternate.is_some() { + p.print_indent(); + } + } } if let Some(alternate) = if_stmt.alternate.as_ref() { p.print_semicolon_if_needed(); diff --git a/crates/oxc_codegen/tests/integration/unit.rs b/crates/oxc_codegen/tests/integration/unit.rs index 36d66059d2d89e..a05ef1f408bf49 100644 --- a/crates/oxc_codegen/tests/integration/unit.rs +++ b/crates/oxc_codegen/tests/integration/unit.rs @@ -92,6 +92,18 @@ fn for_stmt() { ); } +#[test] +fn if_stmt() { + test( + "function f() { if (foo) return foo; else if (bar) return foo; }", + "function f() {\n\tif (foo) return foo;\n\telse if (bar) return foo;\n}\n", + ); + test_minify( + "function f() { if (foo) return foo; else if (bar) return foo; }", + "function f(){if(foo)return foo;else if(bar)return foo}", + ); +} + #[test] fn shorthand() { test("let _ = { x }", "let _ = { x };\n"); From 213364a400ec987f2d75cfc82dc014ed9c76274b Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Fri, 27 Dec 2024 02:44:47 +0000 Subject: [PATCH 022/162] feat(minifier): minimize `if (x) if (y) z` -> `if (x && y) z` (#8136) --- crates/oxc_ast/src/ast_impl/js.rs | 16 ++++ .../peephole_minimize_conditions.rs | 87 +++++++++---------- tasks/minsize/minsize.snap | 22 ++--- 3 files changed, 68 insertions(+), 57 deletions(-) diff --git a/crates/oxc_ast/src/ast_impl/js.rs b/crates/oxc_ast/src/ast_impl/js.rs index 37df05cc79dfea..db76870660d4a6 100644 --- a/crates/oxc_ast/src/ast_impl/js.rs +++ b/crates/oxc_ast/src/ast_impl/js.rs @@ -767,6 +767,22 @@ impl Statement<'_> { | Statement::WhileStatement(_) ) } + + /// Returns the single statement from block statement, or self + pub fn get_one_child(&self) -> Option<&Self> { + if let Statement::BlockStatement(block_stmt) = self { + return (block_stmt.body.len() == 1).then(|| &block_stmt.body[0]); + } + Some(self) + } + + /// Returns the single statement from block statement, or self + pub fn get_one_child_mut(&mut self) -> Option<&mut Self> { + if let Statement::BlockStatement(block_stmt) = self { + return (block_stmt.body.len() == 1).then_some(&mut block_stmt.body[0]); + } + Some(self) + } } impl<'a> FromIn<'a, Expression<'a>> for Statement<'a> { diff --git a/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs b/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs index a4d504c418b7b2..95568cd304af2c 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs @@ -1,5 +1,6 @@ use oxc_allocator::Vec; use oxc_ast::ast::*; +use oxc_span::GetSpan; use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx}; use crate::CompressorPass; @@ -89,7 +90,9 @@ impl<'a> PeepholeMinimizeConditions { ctx: &mut TraverseCtx<'a>, ) -> Option> { let Statement::IfStatement(if_stmt) = stmt else { unreachable!() }; - match &if_stmt.alternate { + let then_branch = &if_stmt.consequent; + let else_branch = &if_stmt.alternate; + match else_branch { None => { if Self::is_foldable_express_block(&if_stmt.consequent) { let right = Self::get_block_expression(&mut if_stmt.consequent, ctx); @@ -116,11 +119,31 @@ impl<'a> PeepholeMinimizeConditions { ); return Some(ctx.ast.statement_expression(if_stmt.span, logical_expr)); } + } else { + // `if (x) if (y) z` -> `if (x && y) z` + if let Some(Statement::IfStatement(then_if_stmt)) = then_branch.get_one_child() + { + if then_if_stmt.alternate.is_none() { + let and_left = ctx.ast.move_expression(&mut if_stmt.test); + let Statement::IfStatement(mut then_if_stmt) = + ctx.ast.move_statement(&mut if_stmt.consequent) + else { + unreachable!() + }; + let and_right = ctx.ast.move_expression(&mut then_if_stmt.test); + then_if_stmt.test = ctx.ast.expression_logical( + and_left.span(), + and_left, + LogicalOperator::And, + and_right, + ); + return Some(Statement::IfStatement(then_if_stmt)); + } + } } } Some(else_branch) => { - let then_branch_is_expression_block = - Self::is_foldable_express_block(&if_stmt.consequent); + let then_branch_is_expression_block = Self::is_foldable_express_block(then_branch); let else_branch_is_expression_block = Self::is_foldable_express_block(else_branch); // `if(foo) bar else baz` -> `foo ? bar : baz` if then_branch_is_expression_block && else_branch_is_expression_block { @@ -184,37 +207,18 @@ impl<'a> PeepholeMinimizeConditions { } fn is_foldable_express_block(stmt: &Statement<'a>) -> bool { - match stmt { - Statement::BlockStatement(block_stmt) if block_stmt.body.len() == 1 => { - matches!(&block_stmt.body[0], Statement::ExpressionStatement(_)) - } - Statement::ExpressionStatement(_) => true, - _ => false, - } + matches!(stmt.get_one_child(), Some(Statement::ExpressionStatement(_))) } fn get_block_expression(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) -> Expression<'a> { - match stmt { - Statement::BlockStatement(block_stmt) if block_stmt.body.len() == 1 => { - if let Statement::ExpressionStatement(s) = &mut block_stmt.body[0] { - ctx.ast.move_expression(&mut s.expression) - } else { - unreachable!() - } - } - Statement::ExpressionStatement(s) => ctx.ast.move_expression(&mut s.expression), - _ => unreachable!(), - } + let Some(Statement::ExpressionStatement(s)) = stmt.get_one_child_mut() else { + unreachable!() + }; + ctx.ast.move_expression(&mut s.expression) } fn is_return_block(stmt: &Statement<'a>) -> bool { - match stmt { - Statement::BlockStatement(block_stmt) if block_stmt.body.len() == 1 => { - matches!(block_stmt.body[0], Statement::ReturnStatement(_)) - } - Statement::ReturnStatement(_) => true, - _ => false, - } + matches!(stmt.get_one_child(), Some(Statement::ReturnStatement(_))) } fn is_return_expression(stmt: &Statement<'a>) -> bool { @@ -235,17 +239,8 @@ impl<'a> PeepholeMinimizeConditions { stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>, ) -> Expression<'a> { - match stmt { - Statement::BlockStatement(block_stmt) if block_stmt.body.len() == 1 => { - if let Statement::ReturnStatement(_) = &mut block_stmt.body[0] { - Self::take_return_argument(stmt, ctx) - } else { - unreachable!() - } - } - Statement::ReturnStatement(_) => Self::take_return_argument(stmt, ctx), - _ => unreachable!(), - } + let Some(stmt) = stmt.get_one_child_mut() else { unreachable!() }; + Self::take_return_argument(stmt, ctx) } fn take_return_argument(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) -> Expression<'a> { @@ -323,13 +318,13 @@ mod test { // fold("if(x){do{foo()}while(y)}else bar()", "if(x){do foo();while(y)}else bar()"); // Play with nested IFs - // fold("function f(){if(x){if(y)foo()}}", "function f(){x && (y && foo())}"); - // fold("function f(){if(x){if(y)foo();else bar()}}", "function f(){x&&(y?foo():bar())}"); - // fold("function f(){if(x){if(y)foo()}else bar()}", "function f(){x?y&&foo():bar()}"); - // fold( - // "function f(){if(x){if(y)foo();else bar()}else{baz()}}", - // "function f(){x?y?foo():bar():baz()}", - // ); + fold("function f(){if(x){if(y)foo()}}", "function f(){x && (y && foo())}"); + fold("function f(){if(x){if(y)foo();else bar()}}", "function f(){x&&(y?foo():bar())}"); + fold("function f(){if(x){if(y)foo()}else bar()}", "function f(){x?y&&foo():bar()}"); + fold( + "function f(){if(x){if(y)foo();else bar()}else{baz()}}", + "function f(){x?y?foo():bar():baz()}", + ); // fold("if(e1){while(e2){if(e3){foo()}}}else{bar()}", "if(e1)while(e2)e3&&foo();else bar()"); diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index b944c7e70f5c05..1df8712007678d 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -1,27 +1,27 @@ | Oxc | ESBuild | Oxc | ESBuild | Original | minified | minified | gzip | gzip | Fixture ------------------------------------------------------------------------------------- -72.14 kB | 23.77 kB | 23.70 kB | 8.62 kB | 8.54 kB | react.development.js +72.14 kB | 23.74 kB | 23.70 kB | 8.62 kB | 8.54 kB | react.development.js 173.90 kB | 60.22 kB | 59.82 kB | 19.49 kB | 19.33 kB | moment.js -287.63 kB | 90.77 kB | 90.07 kB | 32.23 kB | 31.95 kB | jquery.js +287.63 kB | 90.76 kB | 90.07 kB | 32.22 kB | 31.95 kB | jquery.js -342.15 kB | 119.00 kB | 118.14 kB | 44.65 kB | 44.37 kB | vue.js +342.15 kB | 119.00 kB | 118.14 kB | 44.66 kB | 44.37 kB | vue.js -544.10 kB | 72.54 kB | 72.48 kB | 26.23 kB | 26.20 kB | lodash.js +544.10 kB | 72.53 kB | 72.48 kB | 26.23 kB | 26.20 kB | lodash.js -555.77 kB | 274.27 kB | 270.13 kB | 91.27 kB | 90.80 kB | d3.js +555.77 kB | 274.26 kB | 270.13 kB | 91.26 kB | 90.80 kB | d3.js -1.01 MB | 461.18 kB | 458.89 kB | 126.93 kB | 126.71 kB | bundle.min.js +1.01 MB | 461.13 kB | 458.89 kB | 126.91 kB | 126.71 kB | bundle.min.js -1.25 MB | 657.26 kB | 646.76 kB | 164.24 kB | 163.73 kB | three.js +1.25 MB | 657.23 kB | 646.76 kB | 164.23 kB | 163.73 kB | three.js -2.14 MB | 735.73 kB | 724.14 kB | 181.11 kB | 181.07 kB | victory.js +2.14 MB | 735.70 kB | 724.14 kB | 181.11 kB | 181.07 kB | victory.js -3.20 MB | 1.01 MB | 1.01 MB | 332.37 kB | 331.56 kB | echarts.js +3.20 MB | 1.01 MB | 1.01 MB | 332.35 kB | 331.56 kB | echarts.js -6.69 MB | 2.38 MB | 2.31 MB | 495.35 kB | 488.28 kB | antd.js +6.69 MB | 2.38 MB | 2.31 MB | 495.34 kB | 488.28 kB | antd.js -10.95 MB | 3.52 MB | 3.49 MB | 911.00 kB | 915.50 kB | typescript.js +10.95 MB | 3.51 MB | 3.49 MB | 910.94 kB | 915.50 kB | typescript.js From 2b2a37347c07209fed0313b3dd89efb427f88ad4 Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Fri, 27 Dec 2024 05:46:51 +0000 Subject: [PATCH 023/162] feat(minifier): minimize `a + 'b' + 'c'` -> `a + 'bc'` (#8137) --- .../src/ast_passes/peephole_fold_constants.rs | 68 ++++++++++++++++++- tasks/minsize/minsize.snap | 10 +-- 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs index 82f7e50cc766ec..dbff2b5eb207bb 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs @@ -241,7 +241,6 @@ impl<'a, 'b> PeepholeFoldConstants { BinaryOperator::ShiftLeft | BinaryOperator::ShiftRight | BinaryOperator::ShiftRightZeroFill - | BinaryOperator::Addition | BinaryOperator::Subtraction | BinaryOperator::Division | BinaryOperator::Remainder @@ -249,6 +248,7 @@ impl<'a, 'b> PeepholeFoldConstants { | BinaryOperator::Exponential => { ctx.eval_binary_expression(e).map(|v| ctx.value_to_expr(e.span, v)) } + BinaryOperator::Addition => Self::try_fold_add(e, ctx), BinaryOperator::BitwiseAnd | BinaryOperator::BitwiseOR | BinaryOperator::BitwiseXOR => { if let Some(v) = ctx.eval_binary_expression(e) { return Some(ctx.value_to_expr(e.span, v)); @@ -260,6 +260,27 @@ impl<'a, 'b> PeepholeFoldConstants { } } + // Simplified version of `tryFoldAdd` from closure compiler. + fn try_fold_add(e: &mut BinaryExpression<'a>, ctx: Ctx<'a, 'b>) -> Option> { + if let Some(v) = ctx.eval_binary_expression(e) { + return Some(ctx.value_to_expr(e.span, v)); + } + debug_assert_eq!(e.operator, BinaryOperator::Addition); + // a + 'b' + 'c' -> a + 'bc' + if let Expression::BinaryExpression(left_binary_expr) = &mut e.left { + if let Expression::StringLiteral(left_str) = &left_binary_expr.right { + if let Expression::StringLiteral(right_str) = &e.right { + let span = Span::new(left_str.span.start, right_str.span.end); + let value = left_str.value.to_string() + right_str.value.as_str(); + let right = ctx.ast.expression_string_literal(span, value, None); + let left = ctx.ast.move_expression(&mut left_binary_expr.left); + return Some(ctx.ast.expression_binary(e.span, left, e.operator, right)); + } + } + } + None + } + fn try_fold_left_child_op( e: &mut BinaryExpression<'a>, ctx: Ctx<'a, '_>, @@ -1318,7 +1339,7 @@ mod test { } #[test] - fn test_fold_bit_shift() { + fn test_fold_bit_shifts() { test("x = 1 << 0", "x=1"); test("x = -1 << 0", "x=-1"); test("x = 1 << 1", "x=2"); @@ -1366,6 +1387,49 @@ mod test { test("8589934593 >>> 0", "1"); } + #[test] + fn test_string_add() { + test("x = 'a' + 'bc'", "x = 'abc'"); + test("x = 'a' + 5", "x = 'a5'"); + test("x = 5 + 'a'", "x = '5a'"); + // test("x = 'a' + 5n", "x = 'a5n'"); + // test("x = 5n + 'a'", "x = '5na'"); + test("x = 'a' + ''", "x = 'a'"); + test("x = 'a' + foo()", "x = 'a'+foo()"); + test("x = foo() + 'a' + 'b'", "x = foo()+'ab'"); + test("x = (foo() + 'a') + 'b'", "x = foo()+'ab'"); // believe it! + test("x = foo() + 'a' + 'b' + 'cd' + bar()", "x = foo()+'abcd'+bar()"); + test("x = foo() + 2 + 'b'", "x = foo()+2+\"b\""); // don't fold! + + // test("x = foo() + 'a' + 2", "x = foo()+\"a2\""); + test("x = '' + null", "x = 'null'"); + test("x = true + '' + false", "x = 'truefalse'"); + // test("x = '' + []", "x = ''"); + // test("x = foo() + 'a' + 1 + 1", "x = foo() + 'a11'"); + test("x = 1 + 1 + 'a'", "x = '2a'"); + test("x = 1 + 1 + 'a'", "x = '2a'"); + test("x = 'a' + (1 + 1)", "x = 'a2'"); + // test("x = '_' + p1 + '_' + ('' + p2)", "x = '_' + p1 + '_' + p2"); + // test("x = 'a' + ('_' + 1 + 1)", "x = 'a_11'"); + // test("x = 'a' + ('_' + 1) + 1", "x = 'a_11'"); + // test("x = 1 + (p1 + '_') + ('' + p2)", "x = 1 + (p1 + '_') + p2"); + // test("x = 1 + p1 + '_' + ('' + p2)", "x = 1 + p1 + '_' + p2"); + // test("x = 1 + 'a' + p1", "x = '1a' + p1"); + // test("x = (p1 + (p2 + 'a')) + 'b'", "x = (p1 + (p2 + 'ab'))"); + // test("'a' + ('b' + p1) + 1", "'ab' + p1 + 1"); + // test("x = 'a' + ('b' + p1 + 'c')", "x = 'ab' + (p1 + 'c')"); + test_same("x = 'a' + (4 + p1 + 'a')"); + test_same("x = p1 / 3 + 4"); + test_same("foo() + 3 + 'a' + foo()"); + test_same("x = 'a' + ('b' + p1 + p2)"); + test_same("x = 1 + ('a' + p1)"); + test_same("x = p1 + '' + p2"); + test_same("x = 'a' + (1 + p1)"); + test_same("x = (p2 + 'a') + (1 + p1)"); + test_same("x = (p2 + 'a') + (1 + p1 + p2)"); + test_same("x = (p2 + 'a') + (1 + (p1 + p2))"); + } + #[test] fn test_fold_arithmetic() { test("x = 10 + 20", "x = 30"); diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 1df8712007678d..df2d1af01df2b5 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -1,13 +1,13 @@ | Oxc | ESBuild | Oxc | ESBuild | Original | minified | minified | gzip | gzip | Fixture ------------------------------------------------------------------------------------- -72.14 kB | 23.74 kB | 23.70 kB | 8.62 kB | 8.54 kB | react.development.js +72.14 kB | 23.74 kB | 23.70 kB | 8.61 kB | 8.54 kB | react.development.js 173.90 kB | 60.22 kB | 59.82 kB | 19.49 kB | 19.33 kB | moment.js -287.63 kB | 90.76 kB | 90.07 kB | 32.22 kB | 31.95 kB | jquery.js +287.63 kB | 90.74 kB | 90.07 kB | 32.21 kB | 31.95 kB | jquery.js -342.15 kB | 119.00 kB | 118.14 kB | 44.66 kB | 44.37 kB | vue.js +342.15 kB | 118.77 kB | 118.14 kB | 44.54 kB | 44.37 kB | vue.js 544.10 kB | 72.53 kB | 72.48 kB | 26.23 kB | 26.20 kB | lodash.js @@ -17,11 +17,11 @@ Original | minified | minified | gzip | gzip | Fixture 1.25 MB | 657.23 kB | 646.76 kB | 164.23 kB | 163.73 kB | three.js -2.14 MB | 735.70 kB | 724.14 kB | 181.11 kB | 181.07 kB | victory.js +2.14 MB | 735.67 kB | 724.14 kB | 181.09 kB | 181.07 kB | victory.js 3.20 MB | 1.01 MB | 1.01 MB | 332.35 kB | 331.56 kB | echarts.js -6.69 MB | 2.38 MB | 2.31 MB | 495.34 kB | 488.28 kB | antd.js +6.69 MB | 2.38 MB | 2.31 MB | 495.33 kB | 488.28 kB | antd.js 10.95 MB | 3.51 MB | 3.49 MB | 910.94 kB | 915.50 kB | typescript.js From 75264ed7d67900965c5ce8db0d461fdacbbad8fe Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Fri, 27 Dec 2024 06:46:21 +0000 Subject: [PATCH 024/162] refactor(minifier): clean up `try_optimize_block` (#8139) --- .../ast_passes/peephole_remove_dead_code.rs | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs b/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs index b80a526a509dea..5590a62b53417c 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs @@ -25,9 +25,9 @@ impl<'a> CompressorPass<'a> for PeepholeRemoveDeadCode { impl<'a> Traverse<'a> for PeepholeRemoveDeadCode { fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { - self.compress_block(stmt, Ctx(ctx)); let ctx = Ctx(ctx); if let Some(new_stmt) = match stmt { + Statement::BlockStatement(block_stmt) => Self::try_optimize_block(block_stmt, ctx), Statement::IfStatement(if_stmt) => self.try_fold_if(if_stmt, ctx), Statement::ForStatement(for_stmt) => self.try_fold_for(for_stmt, ctx), Statement::ExpressionStatement(expr_stmt) => { @@ -126,28 +126,27 @@ impl<'a, 'b> PeepholeRemoveDeadCode { /// Remove block from single line blocks /// `{ block } -> block` - fn compress_block(&mut self, stmt: &mut Statement<'a>, ctx: Ctx<'a, 'b>) { - 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, ctx); - self.changed = true; - return; - } - if block.body.len() == 0 - && (ctx.parent().is_while_statement() - || ctx.parent().is_for_statement() - || ctx.parent().is_for_in_statement() - || ctx.parent().is_for_of_statement() - || ctx.parent().is_block_statement() - || ctx.parent().is_program()) - { - // Remove the block if it is empty and the parent is a block statement. - *stmt = ctx.ast.statement_empty(SPAN); - } + fn try_optimize_block( + stmt: &mut BlockStatement<'a>, + ctx: Ctx<'a, 'b>, + ) -> Option> { + // 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 stmt.body.len() == 1 && !stmt.body[0].is_declaration() { + return Some(stmt.body.remove(0)); + } + if stmt.body.len() == 0 + && (ctx.parent().is_while_statement() + || ctx.parent().is_for_statement() + || ctx.parent().is_for_in_statement() + || ctx.parent().is_for_of_statement() + || ctx.parent().is_block_statement() + || ctx.parent().is_program()) + { + // Remove the block if it is empty and the parent is a block statement. + return Some(ctx.ast.statement_empty(stmt.span)); } + None } fn try_fold_if( From 6615e1ea931d5339956c58da8999f41554388648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BF=A0=20/=20green?= Date: Fri, 27 Dec 2024 22:45:31 +0900 Subject: [PATCH 025/162] feat(minifier): constant fold `instanceof` (#8142) --- .../src/constant_evaluation/mod.rs | 21 ++++++++++ .../src/ast_passes/peephole_fold_constants.rs | 42 ++++++++++++++++++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/crates/oxc_ecmascript/src/constant_evaluation/mod.rs b/crates/oxc_ecmascript/src/constant_evaluation/mod.rs index a3682629084663..ab47066c7de256 100644 --- a/crates/oxc_ecmascript/src/constant_evaluation/mod.rs +++ b/crates/oxc_ecmascript/src/constant_evaluation/mod.rs @@ -347,6 +347,27 @@ pub trait ConstantEvaluation<'a> { } None } + BinaryOperator::Instanceof => { + if left.may_have_side_effects() { + return None; + } + + let left_ty = ValueType::from(left); + if left_ty == ValueType::Undetermined { + return None; + } + if left_ty == ValueType::Object { + if let Some(right_ident) = right.get_identifier_reference() { + if right_ident.name == "Object" && self.is_global_reference(right_ident) { + return Some(ConstantValue::Boolean(true)); + } + } + None + } else { + // Non-object types are never instances. + Some(ConstantValue::Boolean(false)) + } + } _ => None, } } diff --git a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs index dbff2b5eb207bb..3e915b39e0802a 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs @@ -245,7 +245,8 @@ impl<'a, 'b> PeepholeFoldConstants { | BinaryOperator::Division | BinaryOperator::Remainder | BinaryOperator::Multiplication - | BinaryOperator::Exponential => { + | BinaryOperator::Exponential + | BinaryOperator::Instanceof => { ctx.eval_binary_expression(e).map(|v| ctx.value_to_expr(e.span, v)) } BinaryOperator::Addition => Self::try_fold_add(e, ctx), @@ -1503,6 +1504,45 @@ mod test { test("(+x & 1) & 2", "+x & 0"); } + #[test] + fn test_fold_instance_of() { + // Non object types are never instances of anything. + test("64 instanceof Object", "false"); + test("64 instanceof Number", "false"); + test("'' instanceof Object", "false"); + test("'' instanceof String", "false"); + test("true instanceof Object", "false"); + test("true instanceof Boolean", "false"); + test("!0 instanceof Object", "false"); + test("!0 instanceof Boolean", "false"); + test("false instanceof Object", "false"); + test("null instanceof Object", "false"); + test("undefined instanceof Object", "false"); + test("NaN instanceof Object", "false"); + test("Infinity instanceof Object", "false"); + + // Array and object literals are known to be objects. + test("[] instanceof Object", "true"); + test("({}) instanceof Object", "true"); + + // These cases is foldable, but no handled currently. + test_same("new Foo() instanceof Object"); + // These would require type information to fold. + test_same("[] instanceof Foo"); + test_same("({}) instanceof Foo"); + + test("(function() {}) instanceof Object", "true"); + + // An unknown value should never be folded. + test_same("x instanceof Foo"); + } + + #[test] + fn test_fold_instance_of_additional() { + test("(typeof {}) instanceof Object", "false"); + test("(+{}) instanceof Number", "false"); + } + #[test] fn test_fold_left_child_op() { test_same("x & infinity & 2"); // FIXME: want x & 0 From 74572de625a3e08fa0d56d5d744c5d26d0aba98b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BF=A0=20/=20green?= Date: Fri, 27 Dec 2024 22:48:25 +0900 Subject: [PATCH 026/162] fix(ecmascript): incorrect `to_int_32` value for Infinity (#8144) https://tc39.es/ecma262/#sec-toint32 Infinity should return `0` when passed to `ToInt32` abstract operation. --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- crates/oxc_ecmascript/src/to_int_32.rs | 5 +++++ .../oxc_minifier/src/ast_passes/peephole_fold_constants.rs | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/oxc_ecmascript/src/to_int_32.rs b/crates/oxc_ecmascript/src/to_int_32.rs index d0a69a48cf16dc..468a8c5688c4c9 100644 --- a/crates/oxc_ecmascript/src/to_int_32.rs +++ b/crates/oxc_ecmascript/src/to_int_32.rs @@ -56,6 +56,11 @@ impl ToInt32 for f64 { let number = *self; + // NOTE: this also matches with negative zero + if !number.is_finite() || number == 0.0 { + return 0; + } + if number.is_finite() && number <= f64::from(i32::MAX) && number >= f64::from(i32::MIN) { let i = number as i32; if f64::from(i) == number { diff --git a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs index 3e915b39e0802a..d0e55587a4aa34 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs @@ -1545,9 +1545,9 @@ mod test { #[test] fn test_fold_left_child_op() { - test_same("x & infinity & 2"); // FIXME: want x & 0 - test_same("x - infinity - 2"); // FIXME: want "x-infinity" - test_same("x - 1 + infinity"); + test("x & Infinity & 2", "x & 0"); + test_same("x - Infinity - 2"); // FIXME: want "x-Infinity" + test_same("x - 1 + Infinity"); test_same("x - 2 + 1"); test_same("x - 2 + 3"); test_same("1 + x - 2 + 1"); From 79af10070deff5ef8a08420e904f653ba1102645 Mon Sep 17 00:00:00 2001 From: camc314 <18101008+camc314@users.noreply.github.com> Date: Fri, 27 Dec 2024 14:20:17 +0000 Subject: [PATCH 027/162] fix(semantic): reference flags not correctly resolved when after an export stmt (#8134) fixes https://github.com/oxc-project/oxc/issues/7879#issuecomment-2562574889 TLDR is curently here: https://github.com/oxc-project/oxc/blob/cdd121bfa4a66d5b2cf72a444f35e82daf81d11e/crates/oxc_semantic/src/builder.rs#L2130-L2135 `self.current_reference_flags.is_empty()` is not empty, which causes the current ref flags to be used (this is incorrect, we should be using a fresh version of reference flags). Buy setting ref flags to `None` on exit export node, this issue is avoided `export` **BEFORE** the reference ( incorrect reference flags) https://playground.oxc.rs/#eNpVjjuOwzAMRK8isElj7A/YxtttkVOkkR3aECCJBskkdgzdPZISG0ilGc3DcFbooQUXJmI1q/m3bJIZmII5fHx2lg9/p4hzTXWZsCL3N+RekB3qvRUxRyKDs2I8S61cEzRA0K7Al1geWaLaGVrlCzbgXdRNS08T7mYJHfnNKdsoA3GAdrBeMDUwWRbk3Jh1adn0jtYPUMsj5hOA8vP1/QuZ6OmMI5Yx2QQX3eCebLBx9K8FlYvK5I+ebiW9InckOX4uSOkBCrdvkw== `export` **AFTER** the reference ( correct reference flags) https://playground.oxc.rs/#eNpVjj2uwjAQhK9ibZMmen/Sa0JHwSlonLCJLNm70a6BhMh3xzEEQeUZz6fZWaCDBlwYWaJZzN6KSaYXDqb6+m6tVLsjHQmnknfeqpoDs8EpIp208Et6Q+I8Yum5ffTcqh3UwNAsIGdaH50p2gmaKGeswTuKm9aOR3yZObTsNxfFkvYsAZreesVUw2hFUXJj1mvLpl9o+YBoZcB8AlD/fn7/IRMdn3DAdUw2wZHr3YMNlgb/XFA4isL+4Pm6pheUljXHjwUp3QEtmnBd this PR fixes this issue by resetting the reference flags after exising an export stmt --- .../typescript/consistent_type_imports.rs | 8 ++++ crates/oxc_semantic/src/builder.rs | 28 ++++++------ .../tests/fixtures/oxc/ts/issue-7879.snap | 43 +++++++++++++++++++ .../tests/fixtures/oxc/ts/issue-7879.ts | 4 ++ 4 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 crates/oxc_semantic/tests/fixtures/oxc/ts/issue-7879.snap create mode 100644 crates/oxc_semantic/tests/fixtures/oxc/ts/issue-7879.ts diff --git a/crates/oxc_linter/src/rules/typescript/consistent_type_imports.rs b/crates/oxc_linter/src/rules/typescript/consistent_type_imports.rs index 607643b279a458..0c29635f85a5b3 100644 --- a/crates/oxc_linter/src/rules/typescript/consistent_type_imports.rs +++ b/crates/oxc_linter/src/rules/typescript/consistent_type_imports.rs @@ -1517,6 +1517,14 @@ fn test() { // ", // None, // ), + ( + "import { Bar } from './bar'; +export type { Baz } from './baz'; + +export class Foo extends Bar {} +", + None, + ), ]; let fail = vec![ diff --git a/crates/oxc_semantic/src/builder.rs b/crates/oxc_semantic/src/builder.rs index 9503630f0f2ff4..7a98ecb01f443e 100644 --- a/crates/oxc_semantic/src/builder.rs +++ b/crates/oxc_semantic/src/builder.rs @@ -1861,25 +1861,27 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { self.visit_declaration(declaration); } - for specifier in &it.specifiers { - // `export type { a }` or `export { type a }` -> `a` is a type reference - if it.export_kind.is_type() || specifier.export_kind.is_type() { - self.current_reference_flags = ReferenceFlags::Type; - } else { - // If the export specifier is not a explicit type export, we consider it as a potential - // type and value reference. If it references to a value in the end, we would delete the - // `ReferenceFlags::Type` flag in `fn resolve_references_for_current_scope`. - self.current_reference_flags = ReferenceFlags::Read | ReferenceFlags::Type; - } - self.visit_export_specifier(specifier); - } - if let Some(source) = &it.source { self.visit_string_literal(source); + self.visit_export_specifiers(&it.specifiers); + } else { + for specifier in &it.specifiers { + // `export type { a }` or `export { type a }` -> `a` is a type reference + if it.export_kind.is_type() || specifier.export_kind.is_type() { + self.current_reference_flags = ReferenceFlags::Type; + } else { + // If the export specifier is not a explicit type export, we consider it as a potential + // type and value reference. If it references to a value in the end, we would delete the + // `ReferenceFlags::Type` flag in `fn resolve_references_for_current_scope`. + self.current_reference_flags = ReferenceFlags::Read | ReferenceFlags::Type; + } + self.visit_export_specifier(specifier); + } } if let Some(with_clause) = &it.with_clause { self.visit_with_clause(with_clause); } + self.leave_node(kind); } diff --git a/crates/oxc_semantic/tests/fixtures/oxc/ts/issue-7879.snap b/crates/oxc_semantic/tests/fixtures/oxc/ts/issue-7879.snap new file mode 100644 index 00000000000000..d45c6096c3b79e --- /dev/null +++ b/crates/oxc_semantic/tests/fixtures/oxc/ts/issue-7879.snap @@ -0,0 +1,43 @@ +--- +source: crates/oxc_semantic/tests/main.rs +input_file: crates/oxc_semantic/tests/fixtures/oxc/ts/issue-7879.ts +--- +[ + { + "children": [ + { + "children": [], + "flags": "ScopeFlags(StrictMode)", + "id": 1, + "node": "Class(Foo)", + "symbols": [] + } + ], + "flags": "ScopeFlags(StrictMode | Top)", + "id": 0, + "node": "Program", + "symbols": [ + { + "flags": "SymbolFlags(Import)", + "id": 0, + "name": "Bar", + "node": "ImportSpecifier(Bar)", + "references": [ + { + "flags": "ReferenceFlags(Read)", + "id": 0, + "name": "Bar", + "node_id": 17 + } + ] + }, + { + "flags": "SymbolFlags(Class)", + "id": 1, + "name": "Foo", + "node": "Class(Foo)", + "references": [] + } + ] + } +] diff --git a/crates/oxc_semantic/tests/fixtures/oxc/ts/issue-7879.ts b/crates/oxc_semantic/tests/fixtures/oxc/ts/issue-7879.ts new file mode 100644 index 00000000000000..8a0d8a108d6dae --- /dev/null +++ b/crates/oxc_semantic/tests/fixtures/oxc/ts/issue-7879.ts @@ -0,0 +1,4 @@ +import { Bar } from "./bar"; +export type { Baz } from "./baz"; + +export class Foo extends Bar {} From 8fb71f518fbe4c1936f098725f0cb685dbb3dd0e Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Fri, 27 Dec 2024 15:09:41 +0000 Subject: [PATCH 028/162] feat(minifier): minify string `PropertyKey` (#8147) --- crates/oxc_minifier/src/ast_passes/mod.rs | 4 ++ .../peephole_substitute_alternate_syntax.rs | 43 +++++++++++++++++++ tasks/minsize/minsize.snap | 18 ++++---- 3 files changed, 56 insertions(+), 9 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/mod.rs b/crates/oxc_minifier/src/ast_passes/mod.rs index d92458fd6724d3..459265a14fe03a 100644 --- a/crates/oxc_minifier/src/ast_passes/mod.rs +++ b/crates/oxc_minifier/src/ast_passes/mod.rs @@ -190,6 +190,10 @@ impl<'a> Traverse<'a> for LatePeepholeOptimizations { fn exit_call_expression(&mut self, expr: &mut CallExpression<'a>, ctx: &mut TraverseCtx<'a>) { self.x4_peephole_substitute_alternate_syntax.exit_call_expression(expr, ctx); } + + fn exit_property_key(&mut self, key: &mut PropertyKey<'a>, ctx: &mut TraverseCtx<'a>) { + self.x4_peephole_substitute_alternate_syntax.exit_property_key(key, ctx); + } } // See `createPeepholeOptimizationsPass` diff --git a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs index 692b2b06517ba5..83ddc1af628a69 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs @@ -77,6 +77,10 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax { self.in_define_export = false; } + fn exit_property_key(&mut self, key: &mut PropertyKey<'a>, ctx: &mut TraverseCtx<'a>) { + self.try_compress_property_key(key, ctx); + } + fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { let ctx = Ctx(ctx); @@ -687,6 +691,39 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { fn empty_array_literal(ctx: Ctx<'a, 'b>) -> Expression<'a> { Self::array_literal(ctx.ast.vec(), ctx) } + + // https://github.com/swc-project/swc/blob/4e2dae558f60a9f5c6d2eac860743e6c0b2ec562/crates/swc_ecma_minifier/src/compress/pure/properties.rs + #[allow(clippy::cast_lossless)] + fn try_compress_property_key(&mut self, key: &mut PropertyKey<'a>, ctx: &mut TraverseCtx<'a>) { + use oxc_syntax::identifier::is_identifier_name; + let PropertyKey::StringLiteral(s) = key else { return }; + if match ctx.parent() { + Ancestor::ObjectPropertyKey(key) => *key.computed(), + Ancestor::BindingPropertyKey(key) => *key.computed(), + Ancestor::MethodDefinitionKey(key) => *key.computed(), + Ancestor::PropertyDefinitionKey(key) => *key.computed(), + Ancestor::AccessorPropertyKey(key) => *key.computed(), + _ => true, + } { + return; + } + if is_identifier_name(&s.value) { + self.changed = true; + *key = PropertyKey::StaticIdentifier( + ctx.ast.alloc_identifier_name(s.span, s.value.clone()), + ); + } else if (!s.value.starts_with('0') && !s.value.starts_with('+')) || s.value.len() <= 1 { + if let Ok(value) = s.value.parse::() { + self.changed = true; + *key = PropertyKey::NumericLiteral(ctx.ast.alloc_numeric_literal( + s.span, + value as f64, + None, + NumberBase::Decimal, + )); + } + } + } } /// Port from @@ -1137,4 +1174,10 @@ mod test { test("typeof foo !== `number`", "typeof foo != 'number'"); test("`number` !== typeof foo", "'number' != typeof foo"); } + + #[test] + fn test_object_key() { + test("({ '0': _, 'a': _ })", "({ 0: _, a: _ })"); + test_same("({ '1.1': _, '😊': _, 'a.a': _ })"); + } } diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index df2d1af01df2b5..8bb240402d3bf9 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -5,23 +5,23 @@ Original | minified | minified | gzip | gzip | Fixture 173.90 kB | 60.22 kB | 59.82 kB | 19.49 kB | 19.33 kB | moment.js -287.63 kB | 90.74 kB | 90.07 kB | 32.21 kB | 31.95 kB | jquery.js +287.63 kB | 90.61 kB | 90.07 kB | 32.19 kB | 31.95 kB | jquery.js -342.15 kB | 118.77 kB | 118.14 kB | 44.54 kB | 44.37 kB | vue.js +342.15 kB | 118.76 kB | 118.14 kB | 44.54 kB | 44.37 kB | vue.js -544.10 kB | 72.53 kB | 72.48 kB | 26.23 kB | 26.20 kB | lodash.js +544.10 kB | 72.05 kB | 72.48 kB | 26.19 kB | 26.20 kB | lodash.js -555.77 kB | 274.26 kB | 270.13 kB | 91.26 kB | 90.80 kB | d3.js +555.77 kB | 274.04 kB | 270.13 kB | 91.20 kB | 90.80 kB | d3.js 1.01 MB | 461.13 kB | 458.89 kB | 126.91 kB | 126.71 kB | bundle.min.js -1.25 MB | 657.23 kB | 646.76 kB | 164.23 kB | 163.73 kB | three.js +1.25 MB | 656.86 kB | 646.76 kB | 164.20 kB | 163.73 kB | three.js -2.14 MB | 735.67 kB | 724.14 kB | 181.09 kB | 181.07 kB | victory.js +2.14 MB | 735.43 kB | 724.14 kB | 181.01 kB | 181.07 kB | victory.js -3.20 MB | 1.01 MB | 1.01 MB | 332.35 kB | 331.56 kB | echarts.js +3.20 MB | 1.01 MB | 1.01 MB | 332.34 kB | 331.56 kB | echarts.js -6.69 MB | 2.38 MB | 2.31 MB | 495.33 kB | 488.28 kB | antd.js +6.69 MB | 2.36 MB | 2.31 MB | 495.04 kB | 488.28 kB | antd.js -10.95 MB | 3.51 MB | 3.49 MB | 910.94 kB | 915.50 kB | typescript.js +10.95 MB | 3.51 MB | 3.49 MB | 910.95 kB | 915.50 kB | typescript.js From 1c5db7298609314c9cfc67e4c386be714399c840 Mon Sep 17 00:00:00 2001 From: Anson Heung <57580593+AnsonH@users.noreply.github.com> Date: Fri, 27 Dec 2024 23:30:14 +0800 Subject: [PATCH 029/162] feat(linter): implement eslint/no-labels (#8131) Implements [eslint/no-labels](https://eslint.org/docs/latest/rules/no-labels) rule. Part of #479 --- crates/oxc_linter/src/rules.rs | 2 + .../oxc_linter/src/rules/eslint/no_labels.rs | 272 ++++++++++++++++++ .../src/snapshots/eslint_no_labels.snap | 237 +++++++++++++++ 3 files changed, 511 insertions(+) create mode 100644 crates/oxc_linter/src/rules/eslint/no_labels.rs create mode 100644 crates/oxc_linter/src/snapshots/eslint_no_labels.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 4245090ed683e2..0cfa9eb7a90dad 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -89,6 +89,7 @@ mod eslint { pub mod no_irregular_whitespace; pub mod no_iterator; pub mod no_label_var; + pub mod no_labels; pub mod no_loss_of_precision; pub mod no_magic_numbers; pub mod no_multi_str; @@ -534,6 +535,7 @@ oxc_macros::declare_all_lint_rules! { eslint::max_classes_per_file, eslint::max_lines, eslint::max_params, + eslint::no_labels, eslint::no_restricted_imports, eslint::no_object_constructor, eslint::no_duplicate_imports, diff --git a/crates/oxc_linter/src/rules/eslint/no_labels.rs b/crates/oxc_linter/src/rules/eslint/no_labels.rs new file mode 100644 index 00000000000000..4fb28a7cc410bc --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/no_labels.rs @@ -0,0 +1,272 @@ +use oxc_ast::{ + ast::{LabelIdentifier, Statement}, + AstKind, +}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_semantic::NodeId; +use oxc_span::Span; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +fn no_labels_diagnostic(message: &'static str, label_span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn(message).with_label(label_span) +} + +#[derive(Debug, Default, Clone)] +pub struct NoLabels { + allow_loop: bool, + allow_switch: bool, +} + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallow labeled statements. + /// + /// ### Why is this bad? + /// + /// Labeled statements in JavaScript are used in conjunction with `break` and `continue` to control flow around multiple loops. For example: + /// ```js + /// outer: + /// while (true) { + /// while (true) { + /// break outer; + /// } + /// } + /// ``` + /// The `break outer` statement ensures that this code will not result in an infinite loop because control is returned to the next statement after the `outer` label was applied. If this statement was changed to be just `break`, control would flow back to the outer `while` statement and an infinite loop would result. + /// While convenient in some cases, labels tend to be used only rarely and are frowned upon by some as a remedial form of flow control that is more error prone and harder to understand. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```js + /// label: + /// while(true) { + /// // ... + /// } + /// + /// label: + /// while(true) { + /// break label; + /// } + /// + /// label: + /// while(true) { + /// continue label; + /// } + /// + /// label: + /// switch (a) { + /// case 0: + /// break label; + /// } + /// + /// label: + /// { + /// break label; + /// } + /// + /// label: + /// if (a) { + /// break label; + /// } + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```js + /// var f = { + /// label: "foo" + /// }; + /// + /// while (true) { + /// break; + /// } + /// + /// while (true) { + /// continue; + /// } + /// ``` + /// + /// ### Options + /// + /// The options allow labels with loop or switch statements: + /// * `"allowLoop"` (`boolean`, default is `false`) - If this option was set `true`, this rule ignores labels which are sticking to loop statements. + /// * `"allowSwitch"` (`boolean`, default is `false`) - If this option was set `true`, this rule ignores labels which are sticking to switch statements. + /// + /// Actually labeled statements in JavaScript can be used with other than loop and switch statements. + /// However, this way is ultra rare, not well-known, so this would be confusing developers. + /// + /// #### allowLoop + /// + /// Examples of **correct** code for the `{ "allowLoop": true }` option: + /// ```js + /// label: + /// while (true) { + /// break label; + /// } + /// ``` + /// + /// #### allowSwitch + /// + /// Examples of **correct** code for the `{ "allowSwitch": true }` option: + /// ```js + /// label: + /// switch (a) { + /// case 0: + /// break label; + /// } + /// ``` + NoLabels, + style, +); + +impl Rule for NoLabels { + fn from_configuration(value: serde_json::Value) -> Self { + let allow_loop = value + .get(0) + .and_then(|config| config.get("allowLoop")) + .and_then(serde_json::Value::as_bool) + .unwrap_or(false); + + let allow_switch = value + .get(0) + .and_then(|config| config.get("allowSwitch")) + .and_then(serde_json::Value::as_bool) + .unwrap_or(false); + + Self { allow_loop, allow_switch } + } + + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + if let AstKind::LabeledStatement(labeled_stmt) = node.kind() { + if !self.is_allowed(&labeled_stmt.body) { + let label_span = labeled_stmt.label.span; + ctx.diagnostic(no_labels_diagnostic( + "Labeled statement is not allowed", + label_span, + )); + } + } + + if let AstKind::BreakStatement(break_stmt) = node.kind() { + let Some(label) = &break_stmt.label else { return }; + + if !self.is_allowed_in_break_or_continue(label, node.id(), ctx) { + ctx.diagnostic(no_labels_diagnostic( + "Label in break statement is not allowed", + label.span, + )); + } + } + + if let AstKind::ContinueStatement(cont_stmt) = node.kind() { + let Some(label) = &cont_stmt.label else { return }; + + if !self.is_allowed_in_break_or_continue(label, node.id(), ctx) { + ctx.diagnostic(no_labels_diagnostic( + "Label in continue statement is not allowed", + label.span, + )); + } + } + } +} + +impl NoLabels { + fn is_allowed(&self, stmt: &Statement) -> bool { + match stmt { + stmt if stmt.is_iteration_statement() => self.allow_loop, + Statement::SwitchStatement(_) => self.allow_switch, + _ => false, + } + } + + /// Whether the `label` in break/continue statement is allowed. + fn is_allowed_in_break_or_continue<'a>( + &self, + label: &LabelIdentifier<'a>, + stmt_node_id: NodeId, + ctx: &LintContext<'a>, + ) -> bool { + let nodes = ctx.nodes(); + for ancestor_kind in nodes.ancestor_kinds(stmt_node_id) { + if let AstKind::LabeledStatement(labeled_stmt) = ancestor_kind { + if label.name == labeled_stmt.label.name { + return self.is_allowed(&labeled_stmt.body); + } + } + } + false + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ("var f = { label: foo ()}", None), + ("while (true) {}", None), + ("while (true) { break; }", None), + ("while (true) { continue; }", None), + ("A: while (a) { break A; }", Some(serde_json::json!([{ "allowLoop": true }]))), + ( + "A: do { if (b) { break A; } } while (a);", + Some(serde_json::json!([{ "allowLoop": true }])), + ), + ( + "A: for (var a in obj) { for (;;) { switch (a) { case 0: continue A; } } }", + Some(serde_json::json!([{ "allowLoop": true }])), + ), + ("A: switch (a) { case 0: break A; }", Some(serde_json::json!([{ "allowSwitch": true }]))), + ]; + + let fail = vec![ + ("label: while(true) {}", None), + ("label: while (true) { break label; }", None), + ("label: while (true) { continue label; }", None), + ("A: var foo = 0;", None), + ("A: break A;", None), + ("A: { if (foo()) { break A; } bar(); };", None), + ("A: if (a) { if (foo()) { break A; } bar(); };", None), + ("A: switch (a) { case 0: break A; default: break; };", None), + ("A: switch (a) { case 0: B: { break A; } default: break; };", None), + ("A: var foo = 0;", Some(serde_json::json!([{ "allowLoop": true }]))), + ("A: break A;", Some(serde_json::json!([{ "allowLoop": true }]))), + ( + "A: { if (foo()) { break A; } bar(); };", + Some(serde_json::json!([{ "allowLoop": true }])), + ), + ( + "A: if (a) { if (foo()) { break A; } bar(); };", + Some(serde_json::json!([{ "allowLoop": true }])), + ), + ( + "A: switch (a) { case 0: break A; default: break; };", + Some(serde_json::json!([{ "allowLoop": true }])), + ), + ("A: var foo = 0;", Some(serde_json::json!([{ "allowSwitch": true }]))), + ("A: break A;", Some(serde_json::json!([{ "allowSwitch": true }]))), + ( + "A: { if (foo()) { break A; } bar(); };", + Some(serde_json::json!([{ "allowSwitch": true }])), + ), + ( + "A: if (a) { if (foo()) { break A; } bar(); };", + Some(serde_json::json!([{ "allowSwitch": true }])), + ), + ("A: while (a) { break A; }", Some(serde_json::json!([{ "allowSwitch": true }]))), + ( + "A: do { if (b) { break A; } } while (a);", + Some(serde_json::json!([{ "allowSwitch": true }])), + ), + ( + "A: for (var a in obj) { for (;;) { switch (a) { case 0: break A; } } }", + Some(serde_json::json!([{ "allowSwitch": true }])), + ), + ]; + + Tester::new(NoLabels::NAME, NoLabels::CATEGORY, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/eslint_no_labels.snap b/crates/oxc_linter/src/snapshots/eslint_no_labels.snap new file mode 100644 index 00000000000000..b51a31830ffa71 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/eslint_no_labels.snap @@ -0,0 +1,237 @@ +--- +source: crates/oxc_linter/src/tester.rs +snapshot_kind: text +--- + ⚠ eslint(no-labels): Labeled statement is not allowed + ╭─[no_labels.tsx:1:1] + 1 │ label: while(true) {} + · ───── + ╰──── + + ⚠ eslint(no-labels): Labeled statement is not allowed + ╭─[no_labels.tsx:1:1] + 1 │ label: while (true) { break label; } + · ───── + ╰──── + + ⚠ eslint(no-labels): Label in break statement is not allowed + ╭─[no_labels.tsx:1:29] + 1 │ label: while (true) { break label; } + · ───── + ╰──── + + ⚠ eslint(no-labels): Labeled statement is not allowed + ╭─[no_labels.tsx:1:1] + 1 │ label: while (true) { continue label; } + · ───── + ╰──── + + ⚠ eslint(no-labels): Label in continue statement is not allowed + ╭─[no_labels.tsx:1:32] + 1 │ label: while (true) { continue label; } + · ───── + ╰──── + + ⚠ eslint(no-labels): Labeled statement is not allowed + ╭─[no_labels.tsx:1:1] + 1 │ A: var foo = 0; + · ─ + ╰──── + + ⚠ eslint(no-labels): Labeled statement is not allowed + ╭─[no_labels.tsx:1:1] + 1 │ A: break A; + · ─ + ╰──── + + ⚠ eslint(no-labels): Label in break statement is not allowed + ╭─[no_labels.tsx:1:10] + 1 │ A: break A; + · ─ + ╰──── + + ⚠ eslint(no-labels): Labeled statement is not allowed + ╭─[no_labels.tsx:1:1] + 1 │ A: { if (foo()) { break A; } bar(); }; + · ─ + ╰──── + + ⚠ eslint(no-labels): Label in break statement is not allowed + ╭─[no_labels.tsx:1:25] + 1 │ A: { if (foo()) { break A; } bar(); }; + · ─ + ╰──── + + ⚠ eslint(no-labels): Labeled statement is not allowed + ╭─[no_labels.tsx:1:1] + 1 │ A: if (a) { if (foo()) { break A; } bar(); }; + · ─ + ╰──── + + ⚠ eslint(no-labels): Label in break statement is not allowed + ╭─[no_labels.tsx:1:32] + 1 │ A: if (a) { if (foo()) { break A; } bar(); }; + · ─ + ╰──── + + ⚠ eslint(no-labels): Labeled statement is not allowed + ╭─[no_labels.tsx:1:1] + 1 │ A: switch (a) { case 0: break A; default: break; }; + · ─ + ╰──── + + ⚠ eslint(no-labels): Label in break statement is not allowed + ╭─[no_labels.tsx:1:31] + 1 │ A: switch (a) { case 0: break A; default: break; }; + · ─ + ╰──── + + ⚠ eslint(no-labels): Labeled statement is not allowed + ╭─[no_labels.tsx:1:1] + 1 │ A: switch (a) { case 0: B: { break A; } default: break; }; + · ─ + ╰──── + + ⚠ eslint(no-labels): Labeled statement is not allowed + ╭─[no_labels.tsx:1:25] + 1 │ A: switch (a) { case 0: B: { break A; } default: break; }; + · ─ + ╰──── + + ⚠ eslint(no-labels): Label in break statement is not allowed + ╭─[no_labels.tsx:1:36] + 1 │ A: switch (a) { case 0: B: { break A; } default: break; }; + · ─ + ╰──── + + ⚠ eslint(no-labels): Labeled statement is not allowed + ╭─[no_labels.tsx:1:1] + 1 │ A: var foo = 0; + · ─ + ╰──── + + ⚠ eslint(no-labels): Labeled statement is not allowed + ╭─[no_labels.tsx:1:1] + 1 │ A: break A; + · ─ + ╰──── + + ⚠ eslint(no-labels): Label in break statement is not allowed + ╭─[no_labels.tsx:1:10] + 1 │ A: break A; + · ─ + ╰──── + + ⚠ eslint(no-labels): Labeled statement is not allowed + ╭─[no_labels.tsx:1:1] + 1 │ A: { if (foo()) { break A; } bar(); }; + · ─ + ╰──── + + ⚠ eslint(no-labels): Label in break statement is not allowed + ╭─[no_labels.tsx:1:25] + 1 │ A: { if (foo()) { break A; } bar(); }; + · ─ + ╰──── + + ⚠ eslint(no-labels): Labeled statement is not allowed + ╭─[no_labels.tsx:1:1] + 1 │ A: if (a) { if (foo()) { break A; } bar(); }; + · ─ + ╰──── + + ⚠ eslint(no-labels): Label in break statement is not allowed + ╭─[no_labels.tsx:1:32] + 1 │ A: if (a) { if (foo()) { break A; } bar(); }; + · ─ + ╰──── + + ⚠ eslint(no-labels): Labeled statement is not allowed + ╭─[no_labels.tsx:1:1] + 1 │ A: switch (a) { case 0: break A; default: break; }; + · ─ + ╰──── + + ⚠ eslint(no-labels): Label in break statement is not allowed + ╭─[no_labels.tsx:1:31] + 1 │ A: switch (a) { case 0: break A; default: break; }; + · ─ + ╰──── + + ⚠ eslint(no-labels): Labeled statement is not allowed + ╭─[no_labels.tsx:1:1] + 1 │ A: var foo = 0; + · ─ + ╰──── + + ⚠ eslint(no-labels): Labeled statement is not allowed + ╭─[no_labels.tsx:1:1] + 1 │ A: break A; + · ─ + ╰──── + + ⚠ eslint(no-labels): Label in break statement is not allowed + ╭─[no_labels.tsx:1:10] + 1 │ A: break A; + · ─ + ╰──── + + ⚠ eslint(no-labels): Labeled statement is not allowed + ╭─[no_labels.tsx:1:1] + 1 │ A: { if (foo()) { break A; } bar(); }; + · ─ + ╰──── + + ⚠ eslint(no-labels): Label in break statement is not allowed + ╭─[no_labels.tsx:1:25] + 1 │ A: { if (foo()) { break A; } bar(); }; + · ─ + ╰──── + + ⚠ eslint(no-labels): Labeled statement is not allowed + ╭─[no_labels.tsx:1:1] + 1 │ A: if (a) { if (foo()) { break A; } bar(); }; + · ─ + ╰──── + + ⚠ eslint(no-labels): Label in break statement is not allowed + ╭─[no_labels.tsx:1:32] + 1 │ A: if (a) { if (foo()) { break A; } bar(); }; + · ─ + ╰──── + + ⚠ eslint(no-labels): Labeled statement is not allowed + ╭─[no_labels.tsx:1:1] + 1 │ A: while (a) { break A; } + · ─ + ╰──── + + ⚠ eslint(no-labels): Label in break statement is not allowed + ╭─[no_labels.tsx:1:22] + 1 │ A: while (a) { break A; } + · ─ + ╰──── + + ⚠ eslint(no-labels): Labeled statement is not allowed + ╭─[no_labels.tsx:1:1] + 1 │ A: do { if (b) { break A; } } while (a); + · ─ + ╰──── + + ⚠ eslint(no-labels): Label in break statement is not allowed + ╭─[no_labels.tsx:1:24] + 1 │ A: do { if (b) { break A; } } while (a); + · ─ + ╰──── + + ⚠ eslint(no-labels): Labeled statement is not allowed + ╭─[no_labels.tsx:1:1] + 1 │ A: for (var a in obj) { for (;;) { switch (a) { case 0: break A; } } } + · ─ + ╰──── + + ⚠ eslint(no-labels): Label in break statement is not allowed + ╭─[no_labels.tsx:1:63] + 1 │ A: for (var a in obj) { for (;;) { switch (a) { case 0: break A; } } } + · ─ + ╰──── From 5234d964fb651083fdb421a9402158c2dc5307bc Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sat, 28 Dec 2024 08:44:44 +0900 Subject: [PATCH 030/162] feat(linter): implement `eslint/no-nested-ternary` (#8150) implement: https://eslint.org/docs/latest/rules/no-nested-ternary --------- Co-authored-by: Cameron Clark --- crates/oxc_linter/src/rules.rs | 2 + .../src/rules/eslint/no_nested_ternary.rs | 100 ++++++++++++++++++ .../snapshots/eslint_no_nested_ternary.snap | 63 +++++++++++ 3 files changed, 165 insertions(+) create mode 100644 crates/oxc_linter/src/rules/eslint/no_nested_ternary.rs create mode 100644 crates/oxc_linter/src/snapshots/eslint_no_nested_ternary.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 0cfa9eb7a90dad..053fec48b501e3 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -93,6 +93,7 @@ mod eslint { pub mod no_loss_of_precision; pub mod no_magic_numbers; pub mod no_multi_str; + pub mod no_nested_ternary; pub mod no_new; pub mod no_new_func; pub mod no_new_native_nonconstructor; @@ -535,6 +536,7 @@ oxc_macros::declare_all_lint_rules! { eslint::max_classes_per_file, eslint::max_lines, eslint::max_params, + eslint::no_nested_ternary, eslint::no_labels, eslint::no_restricted_imports, eslint::no_object_constructor, diff --git a/crates/oxc_linter/src/rules/eslint/no_nested_ternary.rs b/crates/oxc_linter/src/rules/eslint/no_nested_ternary.rs new file mode 100644 index 00000000000000..2508793abe4cdd --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/no_nested_ternary.rs @@ -0,0 +1,100 @@ +use oxc_ast::ast::Expression; +use oxc_ast::AstKind; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +fn no_nested_ternary_diagnostic(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Do not nest ternary expressions.").with_label(span) +} + +#[derive(Debug, Default, Clone)] +pub struct NoNestedTernary; + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallows nested ternary expressions to improve code readability and maintainability. + /// + /// ### Why is this bad? + /// + /// Nested ternary expressions make code harder to read and understand. They can lead to complex, difficult-to-debug logic. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```js + /// const result = condition1 ? (condition2 ? "a" : "b") : "c"; + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```js + /// let result; + /// if (condition1) { + /// result = condition2 ? "a" : "b"; + /// } else { + /// result = "c"; + /// } + /// ``` + NoNestedTernary, + style, +); + +impl Rule for NoNestedTernary { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + if let AstKind::ConditionalExpression(node) = node.kind() { + if matches!( + node.consequent.get_inner_expression(), + Expression::ConditionalExpression(_) + ) || matches!( + node.alternate.get_inner_expression(), + Expression::ConditionalExpression(_) + ) { + ctx.diagnostic(no_nested_ternary_diagnostic(node.span)); + } + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + "foo ? doBar() : doBaz();", + "var foo = bar === baz ? qux : quxx;", + "var result = foo && bar ? baz : qux || quux;", + "var result = foo ? bar : baz === qux;", + "foo ? doSomething(a, b) : doSomethingElse(c, d);", + // Parenthesized Expressions + "var result = (foo ? bar : baz) || qux;", + "var result = (foo ? bar : baz) && qux;", + "var result = foo === bar ? (baz || qux) : quux;", + "var result = (foo ? bar : baz) ? qux : quux;", + // TypeScript + "var result = foo! ? bar : baz;", + "var result = foo ? bar! : baz;", + "var result = (foo as boolean) ? bar : baz;", + "var result = foo ? (bar as string) : baz;", + ]; + + let fail = vec![ + "foo ? bar : baz === qux ? quxx : foobar;", + "foo ? baz === qux ? quxx : foobar : bar;", + // Parenthesized Expressions + "var result = foo ? (bar ? baz : qux) : quux;", + "var result = foo ? (bar === baz ? qux : quux) : foobar;", + "doSomething(foo ? bar : baz ? qux : quux);", + // Comment + "var result = foo /* comment */ ? bar : baz ? qux : quux;", + // TypeScript + "var result = foo! ? bar : baz! ? qux : quux;", + "var result = foo ? bar! : (baz! ? qux : quux);", + "var result = (foo as boolean) ? bar : (baz as string) ? qux : quux;", + "var result = foo ? (bar as string) : (baz as number ? qux : quux);", + ]; + + Tester::new(NoNestedTernary::NAME, NoNestedTernary::CATEGORY, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/eslint_no_nested_ternary.snap b/crates/oxc_linter/src/snapshots/eslint_no_nested_ternary.snap new file mode 100644 index 00000000000000..9bdafa63bd9d2b --- /dev/null +++ b/crates/oxc_linter/src/snapshots/eslint_no_nested_ternary.snap @@ -0,0 +1,63 @@ +--- +source: crates/oxc_linter/src/tester.rs +snapshot_kind: text +--- + ⚠ eslint(no-nested-ternary): Do not nest ternary expressions. + ╭─[no_nested_ternary.tsx:1:1] + 1 │ foo ? bar : baz === qux ? quxx : foobar; + · ─────────────────────────────────────── + ╰──── + + ⚠ eslint(no-nested-ternary): Do not nest ternary expressions. + ╭─[no_nested_ternary.tsx:1:1] + 1 │ foo ? baz === qux ? quxx : foobar : bar; + · ─────────────────────────────────────── + ╰──── + + ⚠ eslint(no-nested-ternary): Do not nest ternary expressions. + ╭─[no_nested_ternary.tsx:1:14] + 1 │ var result = foo ? (bar ? baz : qux) : quux; + · ────────────────────────────── + ╰──── + + ⚠ eslint(no-nested-ternary): Do not nest ternary expressions. + ╭─[no_nested_ternary.tsx:1:14] + 1 │ var result = foo ? (bar === baz ? qux : quux) : foobar; + · ───────────────────────────────────────── + ╰──── + + ⚠ eslint(no-nested-ternary): Do not nest ternary expressions. + ╭─[no_nested_ternary.tsx:1:13] + 1 │ doSomething(foo ? bar : baz ? qux : quux); + · ──────────────────────────── + ╰──── + + ⚠ eslint(no-nested-ternary): Do not nest ternary expressions. + ╭─[no_nested_ternary.tsx:1:14] + 1 │ var result = foo /* comment */ ? bar : baz ? qux : quux; + · ────────────────────────────────────────── + ╰──── + + ⚠ eslint(no-nested-ternary): Do not nest ternary expressions. + ╭─[no_nested_ternary.tsx:1:14] + 1 │ var result = foo! ? bar : baz! ? qux : quux; + · ────────────────────────────── + ╰──── + + ⚠ eslint(no-nested-ternary): Do not nest ternary expressions. + ╭─[no_nested_ternary.tsx:1:14] + 1 │ var result = foo ? bar! : (baz! ? qux : quux); + · ──────────────────────────────── + ╰──── + + ⚠ eslint(no-nested-ternary): Do not nest ternary expressions. + ╭─[no_nested_ternary.tsx:1:14] + 1 │ var result = (foo as boolean) ? bar : (baz as string) ? qux : quux; + · ───────────────────────────────────────────────────── + ╰──── + + ⚠ eslint(no-nested-ternary): Do not nest ternary expressions. + ╭─[no_nested_ternary.tsx:1:14] + 1 │ var result = foo ? (bar as string) : (baz as number ? qux : quux); + · ──────────────────────────────────────────────────── + ╰──── From 37c9959611c542d1ad3493c3d858682ce8acd6bc Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Sat, 28 Dec 2024 06:05:04 +0000 Subject: [PATCH 031/162] feat(minifier): normalize `Infinity` into `f64::Infinity` (#8148) --- .../oxc_minifier/src/ast_passes/normalize.rs | 23 ++++++++++++++++++- .../peephole_minimize_conditions.rs | 7 ++++-- .../ast_passes/peephole_remove_dead_code.rs | 2 +- .../src/ast_passes/statement_fusion.rs | 10 ++++---- crates/oxc_minifier/src/node_util/mod.rs | 7 ++++++ crates/oxc_minifier/src/tester.rs | 3 ++- tasks/minsize/minsize.snap | 12 +++++----- 7 files changed, 48 insertions(+), 16 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/normalize.rs b/crates/oxc_minifier/src/ast_passes/normalize.rs index cb252707420534..964e2c548d63ec 100644 --- a/crates/oxc_minifier/src/ast_passes/normalize.rs +++ b/crates/oxc_minifier/src/ast_passes/normalize.rs @@ -2,7 +2,7 @@ use oxc_ast::ast::*; use oxc_syntax::scope::ScopeFlags; use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx}; -use crate::CompressorPass; +use crate::{node_util::Ctx, CompressorPass}; /// Normalize AST /// @@ -25,6 +25,12 @@ impl<'a> Traverse<'a> for Normalize { Self::convert_while_to_for(stmt, ctx); } } + + fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + if let Expression::Identifier(_) = expr { + Self::convert_infinity_into_number(expr, ctx); + } + } } impl<'a> Normalize { @@ -45,6 +51,21 @@ impl<'a> Normalize { ); *stmt = Statement::ForStatement(for_stmt); } + + fn convert_infinity_into_number(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + let ctx = Ctx(ctx); + match expr { + Expression::Identifier(ident) if ctx.is_identifier_infinity(ident) => { + *expr = ctx.ast.expression_numeric_literal( + ident.span, + f64::INFINITY, + None, + NumberBase::Decimal, + ); + } + _ => {} + } + } } #[cfg(test)] diff --git a/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs b/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs index 95568cd304af2c..f3b4e8593bd3ab 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs @@ -1276,8 +1276,11 @@ mod test { #[test] fn test_coercion_substitution_while() { // enableTypeCheck(); - test_same("var x = {}; while (x != null) throw 'a';"); - test_same("var x = 1; while (x != 0) throw 'a';"); + test( + "var x = {}; while (x != null) throw 'a';", + "var x = {}; for (;x != null;) throw 'a';", + ); + test("var x = 1; while (x != 0) throw 'a';", "var x = 1; for (;x != 0;) throw 'a';"); } #[test] diff --git a/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs b/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs index 5590a62b53417c..18d23ed4b9353d 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs @@ -456,7 +456,7 @@ mod test { // Cases to test for empty block. // fold("while(x()){x}", "while(x());"); - fold("while(x()){x()}", "while(x())x()"); + fold("while(x()){x()}", "for(;x();)x()"); // fold("for(x=0;x<100;x++){x}", "for(x=0;x<100;x++);"); // fold("for(x in y){x}", "for(x in y);"); // fold("for (x of y) {x}", "for(x of y);"); diff --git a/crates/oxc_minifier/src/ast_passes/statement_fusion.rs b/crates/oxc_minifier/src/ast_passes/statement_fusion.rs index 017af1aa98481d..4d563d94789dc8 100644 --- a/crates/oxc_minifier/src/ast_passes/statement_fusion.rs +++ b/crates/oxc_minifier/src/ast_passes/statement_fusion.rs @@ -285,10 +285,10 @@ mod test { #[test] fn fuse_into_label() { - // fuse("a;b;c;label:for(x in y){}", "label:for(x in a,b,c,y){}"); - // fuse("a;b;c;label:for(;g;){}", "label:for(a,b,c;g;){}"); - // fuse("a;b;c;l1:l2:l3:for(;g;){}", "l1:l2:l3:for(a,b,c;g;){}"); - fuse_same("a;b;c;label:while(true){}"); + fuse("a;b;c;label:for(x in y){}", "label:for(x in a,b,c,y){}"); + fuse("a;b;c;label:for(;g;){}", "label:for(a,b,c;g;){}"); + fuse("a;b;c;l1:l2:l3:for(;g;){}", "l1:l2:l3:for(a,b,c;g;){}"); + fuse("a;b;c;label:while(true){}", "label:for(a,b,c;true;){}"); } #[test] @@ -304,7 +304,7 @@ mod test { #[test] fn no_fuse_into_while() { - fuse_same("a;b;c;while(x){}"); + fuse("a;b;c;while(x){}", "for(a,b,c;x;){}"); } #[test] diff --git a/crates/oxc_minifier/src/node_util/mod.rs b/crates/oxc_minifier/src/node_util/mod.rs index a3c62f6d084808..cf662b884bc52d 100644 --- a/crates/oxc_minifier/src/node_util/mod.rs +++ b/crates/oxc_minifier/src/node_util/mod.rs @@ -64,4 +64,11 @@ impl<'a> Ctx<'a, '_> { } false } + + pub fn is_identifier_infinity(self, ident: &IdentifierReference) -> bool { + if ident.name == "Infinity" && ident.is_global_reference(self.symbols()) { + return true; + } + false + } } diff --git a/crates/oxc_minifier/src/tester.rs b/crates/oxc_minifier/src/tester.rs index 0ae6dcaa875d02..25e438531028a0 100644 --- a/crates/oxc_minifier/src/tester.rs +++ b/crates/oxc_minifier/src/tester.rs @@ -6,7 +6,7 @@ use oxc_span::SourceType; use oxc_traverse::ReusableTraverseCtx; use crate::{ - ast_passes::{CompressorPass, RemoveSyntax}, + ast_passes::{CompressorPass, Normalize, RemoveSyntax}, CompressOptions, }; @@ -45,6 +45,7 @@ fn run<'a, P: CompressorPass<'a>>( SemanticBuilder::new().build(&program).semantic.into_symbol_table_and_scope_tree(); let mut ctx = ReusableTraverseCtx::new(scopes, symbols, allocator); RemoveSyntax::new(CompressOptions::all_false()).build(&mut program, &mut ctx); + Normalize::new().build(&mut program, &mut ctx); pass.build(&mut program, &mut ctx); } diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 8bb240402d3bf9..0d863febd2b803 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -9,19 +9,19 @@ Original | minified | minified | gzip | gzip | Fixture 342.15 kB | 118.76 kB | 118.14 kB | 44.54 kB | 44.37 kB | vue.js -544.10 kB | 72.05 kB | 72.48 kB | 26.19 kB | 26.20 kB | lodash.js +544.10 kB | 72.04 kB | 72.48 kB | 26.18 kB | 26.20 kB | lodash.js -555.77 kB | 274.04 kB | 270.13 kB | 91.20 kB | 90.80 kB | d3.js +555.77 kB | 273.90 kB | 270.13 kB | 91.19 kB | 90.80 kB | d3.js 1.01 MB | 461.13 kB | 458.89 kB | 126.91 kB | 126.71 kB | bundle.min.js -1.25 MB | 656.86 kB | 646.76 kB | 164.20 kB | 163.73 kB | three.js +1.25 MB | 656.81 kB | 646.76 kB | 164.16 kB | 163.73 kB | three.js -2.14 MB | 735.43 kB | 724.14 kB | 181.01 kB | 181.07 kB | victory.js +2.14 MB | 735.33 kB | 724.14 kB | 180.99 kB | 181.07 kB | victory.js -3.20 MB | 1.01 MB | 1.01 MB | 332.34 kB | 331.56 kB | echarts.js +3.20 MB | 1.01 MB | 1.01 MB | 332.27 kB | 331.56 kB | echarts.js 6.69 MB | 2.36 MB | 2.31 MB | 495.04 kB | 488.28 kB | antd.js -10.95 MB | 3.51 MB | 3.49 MB | 910.95 kB | 915.50 kB | typescript.js +10.95 MB | 3.51 MB | 3.49 MB | 910.93 kB | 915.50 kB | typescript.js From 1b9a5bae2e744c40973f274dd086a1eb91acd12d Mon Sep 17 00:00:00 2001 From: camc314 <18101008+camc314@users.noreply.github.com> Date: Sat, 28 Dec 2024 13:49:10 +0000 Subject: [PATCH 032/162] fix(linter): false positiver in private member expr in oxc/const-comparison (#8164) fixes https://github.com/oxc-project/oxc/issues/8163 --- .../oxc_linter/src/rules/oxc/const_comparisons.rs | 4 ++++ .../src/snapshots/oxc_const_comparisons.snap | 15 ++++++++++++++- crates/oxc_linter/src/utils/unicorn.rs | 11 ++++++++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/crates/oxc_linter/src/rules/oxc/const_comparisons.rs b/crates/oxc_linter/src/rules/oxc/const_comparisons.rs index bc2fa4c94b643b..bc998236ed52aa 100644 --- a/crates/oxc_linter/src/rules/oxc/const_comparisons.rs +++ b/crates/oxc_linter/src/rules/oxc/const_comparisons.rs @@ -415,9 +415,11 @@ fn test() { "status_code > 500 && foo() && bar || status_code < 400;", // oxc specific "a < b", + "a.b.c < b.b.c", "a <= b", "a > b", "a >= b", + "class Foo { #a; #b; constructor() { this.#a = 1; }; test() { return this.#a > this.#b } }", ]; let fail = vec![ @@ -500,10 +502,12 @@ fn test() { "a <= a", "a > a", "a >= a", + "a.b.c >= a.b.c", "a == b && a == b", "a == b || a == b", "!foo && !foo", "!foo || !foo", + "class Foo { #a; #b; constructor() { this.#a = 1; }; test() { return this.#a > this.#a } }", ]; Tester::new(ConstComparisons::NAME, ConstComparisons::CATEGORY, pass, fail).test_and_snapshot(); diff --git a/crates/oxc_linter/src/snapshots/oxc_const_comparisons.snap b/crates/oxc_linter/src/snapshots/oxc_const_comparisons.snap index f6cc782939f090..baf7b23541f8bb 100644 --- a/crates/oxc_linter/src/snapshots/oxc_const_comparisons.snap +++ b/crates/oxc_linter/src/snapshots/oxc_const_comparisons.snap @@ -1,6 +1,5 @@ --- source: crates/oxc_linter/src/tester.rs -snapshot_kind: text --- ⚠ oxc(const-comparisons): Unexpected constant comparison ╭─[const_comparisons.tsx:1:1] @@ -264,6 +263,13 @@ snapshot_kind: text ╰──── help: Because `a` will always be equal to itself + ⚠ oxc(const-comparisons): This comparison will always evaluate to true + ╭─[const_comparisons.tsx:1:1] + 1 │ a.b.c >= a.b.c + · ────────────── + ╰──── + help: Because `a.b.c` will always be equal to itself + ⚠ oxc(const-comparisons): Both sides of the logical operator are the same ╭─[const_comparisons.tsx:1:1] 1 │ a == b && a == b @@ -299,3 +305,10 @@ snapshot_kind: text · ╰── If this expression evaluates to true ╰──── help: This logical expression will always evaluate to the same value as the expression itself. + + ⚠ oxc(const-comparisons): This comparison will always evaluate to false + ╭─[const_comparisons.tsx:1:69] + 1 │ class Foo { #a; #b; constructor() { this.#a = 1; }; test() { return this.#a > this.#a } } + · ───────────────── + ╰──── + help: Because `this.#a` will never be greater than itself diff --git a/crates/oxc_linter/src/utils/unicorn.rs b/crates/oxc_linter/src/utils/unicorn.rs index 7edbcb543f8b29..a486ae38a55dda 100644 --- a/crates/oxc_linter/src/utils/unicorn.rs +++ b/crates/oxc_linter/src/utils/unicorn.rs @@ -274,7 +274,16 @@ pub fn is_same_member_expression( (Some(_), None) | (None, Some(_)) => { return false; } - _ => {} + (None, None) => { + if let ( + MemberExpression::PrivateFieldExpression(left), + MemberExpression::PrivateFieldExpression(right), + ) = (left, right) + { + return left.field.name == right.field.name + && is_same_expression(&left.object, &right.object, ctx); + } + } } if let ( From 1171e00a3c2d6a1374472f43cef33614bcdf7673 Mon Sep 17 00:00:00 2001 From: "Alexander S." Date: Sat, 28 Dec 2024 17:34:58 +0100 Subject: [PATCH 033/162] fix(linter): disable `react/rules-of-hooks` for vue and svelte files (#8165) because of https://github.com/oxc-project/oxc/issues/8003 I used the FrameworkFlags in hope they are only active in `.vue` and `.svelte` files. --- crates/oxc_linter/src/rules/react/rules_of_hooks.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/oxc_linter/src/rules/react/rules_of_hooks.rs b/crates/oxc_linter/src/rules/react/rules_of_hooks.rs index e6eb833f6882a1..ac670d111b1f33 100644 --- a/crates/oxc_linter/src/rules/react/rules_of_hooks.rs +++ b/crates/oxc_linter/src/rules/react/rules_of_hooks.rs @@ -16,7 +16,7 @@ use crate::{ context::LintContext, rule::Rule, utils::{is_react_component_or_hook_name, is_react_function_call, is_react_hook}, - AstNode, + AstNode, FrameworkFlags, }; mod diagnostics { @@ -99,7 +99,7 @@ pub struct RulesOfHooks; declare_oxc_lint!( /// ### What it does /// - /// This enforcecs the Rules of Hooks + /// This enforces the Rules of Hooks /// /// /// @@ -108,6 +108,12 @@ declare_oxc_lint!( ); impl Rule for RulesOfHooks { + fn should_run(&self, ctx: &crate::rules::ContextHost) -> bool { + // disable this rule in vue/nuxt and svelte(kit) files + // top level useFunction are very common + !ctx.frameworks().contains(FrameworkFlags::SvelteKit | FrameworkFlags::Nuxt) + } + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { let AstKind::CallExpression(call) = node.kind() else { return }; From 65796c47a11d70cc31fcafc9e3fae8d886a4531f Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sun, 29 Dec 2024 01:42:01 +0900 Subject: [PATCH 034/162] feat(linter): implement `eslint/prefer-rest-params` (#8155) implement: https://eslint.org/docs/latest/rules/prefer-rest-params --- crates/oxc_linter/src/rules.rs | 2 + .../src/rules/eslint/prefer_rest_params.rs | 126 ++++++++++++++++++ .../snapshots/eslint_prefer_rest_params.snap | 27 ++++ 3 files changed, 155 insertions(+) create mode 100644 crates/oxc_linter/src/rules/eslint/prefer_rest_params.rs create mode 100644 crates/oxc_linter/src/snapshots/eslint_prefer_rest_params.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 053fec48b501e3..a9b681dae87966 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -141,6 +141,7 @@ mod eslint { pub mod prefer_exponentiation_operator; pub mod prefer_numeric_literals; pub mod prefer_object_has_own; + pub mod prefer_rest_params; pub mod prefer_spread; pub mod radix; pub mod require_await; @@ -630,6 +631,7 @@ oxc_macros::declare_all_lint_rules! { eslint::no_var, eslint::no_void, eslint::no_with, + eslint::prefer_rest_params, eslint::prefer_exponentiation_operator, eslint::prefer_numeric_literals, eslint::prefer_object_has_own, diff --git a/crates/oxc_linter/src/rules/eslint/prefer_rest_params.rs b/crates/oxc_linter/src/rules/eslint/prefer_rest_params.rs new file mode 100644 index 00000000000000..f7807fc8fba51f --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/prefer_rest_params.rs @@ -0,0 +1,126 @@ +use crate::{context::LintContext, rule::Rule, AstNode}; +use oxc_ast::AstKind; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::{GetSpan, Span}; + +fn prefer_rest_params_diagnostic(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Use the rest parameters instead of 'arguments'.").with_label(span) +} + +#[derive(Debug, Default, Clone)] +pub struct PreferRestParams; + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallows the use of the `arguments` object and instead enforces the use of rest parameters. + /// + /// ### Why is this bad? + /// + /// The `arguments` object does not have methods from `Array.prototype`, making it inconvenient for array-like operations. + /// Using rest parameters provides a more intuitive and efficient way to handle variadic arguments. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```javascript + /// function foo() { + /// console.log(arguments); + /// } + /// + /// function foo(action) { + /// var args = Array.prototype.slice.call(arguments, 1); + /// action.apply(null, args); + /// } + /// + /// function foo(action) { + /// var args = [].slice.call(arguments, 1); + /// action.apply(null, args); + /// } + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```javascript + /// function foo(...args) { + /// console.log(args); + /// } + /// + /// function foo(action, ...args) { + /// action.apply(null, args); // Or use `action(...args)` (related to `prefer-spread` rule). + /// } + /// + /// // Note: Implicit `arguments` can be shadowed. + /// function foo(arguments) { + /// console.log(arguments); // This refers to the first argument. + /// } + /// function foo() { + /// var arguments = 0; + /// console.log(arguments); // This is a local variable. + /// } + /// ``` + PreferRestParams, + style, +); + +impl Rule for PreferRestParams { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + if let AstKind::IdentifierReference(identifier) = node.kind() { + if identifier.name != "arguments" + || !is_inside_of_function(node, ctx) + || is_not_normal_member_access(node, ctx) + { + return; + } + let binding = ctx.scopes().find_binding(node.scope_id(), "arguments"); + if binding.is_none() { + ctx.diagnostic(prefer_rest_params_diagnostic(node.span())); + } + } + } +} + +fn is_inside_of_function(node: &AstNode, ctx: &LintContext) -> bool { + let mut current = node; + while let Some(parent) = ctx.nodes().parent_node(current.id()) { + if matches!(parent.kind(), AstKind::Function(_)) { + return true; + } + current = parent; + } + false +} + +fn is_not_normal_member_access(identifier: &AstNode, ctx: &LintContext) -> bool { + let parent = ctx.nodes().parent_node(identifier.id()); + if let Some(parent) = parent { + if let AstKind::MemberExpression(member) = parent.kind() { + return member.object().span() == identifier.span() && !member.is_computed(); + } + } + false +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + "arguments;", + "function foo(arguments) { arguments; }", + "function foo() { var arguments; arguments; }", + "var foo = () => arguments;", + "function foo(...args) { args; }", + "function foo() { arguments.length; }", + "function foo() { arguments.callee; }", + ]; + + let fail = vec![ + "function foo() { arguments; }", + "function foo() { arguments[0]; }", + "function foo() { arguments[1]; }", + "function foo() { arguments[Symbol.iterator]; }", + ]; + + Tester::new(PreferRestParams::NAME, PreferRestParams::CATEGORY, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/eslint_prefer_rest_params.snap b/crates/oxc_linter/src/snapshots/eslint_prefer_rest_params.snap new file mode 100644 index 00000000000000..d5b62a95ff17df --- /dev/null +++ b/crates/oxc_linter/src/snapshots/eslint_prefer_rest_params.snap @@ -0,0 +1,27 @@ +--- +source: crates/oxc_linter/src/tester.rs +snapshot_kind: text +--- + ⚠ eslint(prefer-rest-params): Use the rest parameters instead of 'arguments'. + ╭─[prefer_rest_params.tsx:1:18] + 1 │ function foo() { arguments; } + · ───────── + ╰──── + + ⚠ eslint(prefer-rest-params): Use the rest parameters instead of 'arguments'. + ╭─[prefer_rest_params.tsx:1:18] + 1 │ function foo() { arguments[0]; } + · ───────── + ╰──── + + ⚠ eslint(prefer-rest-params): Use the rest parameters instead of 'arguments'. + ╭─[prefer_rest_params.tsx:1:18] + 1 │ function foo() { arguments[1]; } + · ───────── + ╰──── + + ⚠ eslint(prefer-rest-params): Use the rest parameters instead of 'arguments'. + ╭─[prefer_rest_params.tsx:1:18] + 1 │ function foo() { arguments[Symbol.iterator]; } + · ───────── + ╰──── From f3a36e1dbfd6e5a5cb4ef60de991ecec83ed5e50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BF=A0=20/=20green?= Date: Sun, 29 Dec 2024 01:50:56 +0900 Subject: [PATCH 035/162] feat(minifier): fold `typeof foo != "undefined"` into `typeof foo < "u"` (#8159) --- .../peephole_substitute_alternate_syntax.rs | 32 ++++++++++++++++--- tasks/minsize/minsize.snap | 20 ++++++------ 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs index 83ddc1af628a69..f28c082a823b11 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs @@ -240,14 +240,24 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { } /// Compress `typeof foo == "undefined"` into `typeof foo > "u"` + /// + /// - `typeof foo == "undefined"` -> `typeof foo > "u"` + /// - `typeof foo != "undefined"` -> `typeof foo < "u"` + /// /// Enabled by `compress.typeofs` fn try_compress_typeof_undefined( expr: &mut BinaryExpression<'a>, ctx: Ctx<'a, 'b>, ) -> Option> { - if !matches!(expr.operator, BinaryOperator::Equality | BinaryOperator::StrictEquality) { - return None; - } + let new_op = match expr.operator { + BinaryOperator::Equality | BinaryOperator::StrictEquality => { + BinaryOperator::GreaterThan + } + BinaryOperator::Inequality | BinaryOperator::StrictInequality => { + BinaryOperator::LessThan + } + _ => return None, + }; let pair = Self::commutative_pair( (&expr.left, &expr.right), |a| a.is_specific_string_literal("undefined").then_some(()), @@ -266,7 +276,7 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { let argument = Expression::Identifier(ctx.alloc(id_ref)); let left = ctx.ast.expression_unary(SPAN, UnaryOperator::Typeof, argument); let right = ctx.ast.expression_string_literal(SPAN, "u", None); - Some(ctx.ast.expression_binary(expr.span, left, BinaryOperator::GreaterThan, right)) + Some(ctx.ast.expression_binary(expr.span, left, new_op, right)) } /// Compress `foo === null || foo === undefined` into `foo == null`. @@ -1143,6 +1153,20 @@ mod test { test_same("const foo = () => { foo; return 'baz' }"); } + /// Port from + #[test] + fn test_fold_is_typeof_equals_undefined() { + test("typeof x !== 'undefined'", "typeof x < 'u'"); + test("typeof x != 'undefined'", "typeof x < 'u'"); + test("'undefined' !== typeof x", "typeof x < 'u'"); + test("'undefined' != typeof x", "typeof x < 'u'"); + + test("typeof x === 'undefined'", "typeof x > 'u'"); + test("typeof x == 'undefined'", "typeof x > 'u'"); + test("'undefined' === typeof x", "typeof x > 'u'"); + test("'undefined' == typeof x", "typeof x > 'u'"); + } + #[test] fn test_fold_is_null_or_undefined() { test("foo === null || foo === undefined", "foo == null"); diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 0d863febd2b803..8b34fd75ae1fb6 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -3,25 +3,25 @@ Original | minified | minified | gzip | gzip | Fixture ------------------------------------------------------------------------------------- 72.14 kB | 23.74 kB | 23.70 kB | 8.61 kB | 8.54 kB | react.development.js -173.90 kB | 60.22 kB | 59.82 kB | 19.49 kB | 19.33 kB | moment.js +173.90 kB | 60.16 kB | 59.82 kB | 19.49 kB | 19.33 kB | moment.js -287.63 kB | 90.61 kB | 90.07 kB | 32.19 kB | 31.95 kB | jquery.js +287.63 kB | 90.59 kB | 90.07 kB | 32.19 kB | 31.95 kB | jquery.js -342.15 kB | 118.76 kB | 118.14 kB | 44.54 kB | 44.37 kB | vue.js +342.15 kB | 118.61 kB | 118.14 kB | 44.54 kB | 44.37 kB | vue.js 544.10 kB | 72.04 kB | 72.48 kB | 26.18 kB | 26.20 kB | lodash.js -555.77 kB | 273.90 kB | 270.13 kB | 91.19 kB | 90.80 kB | d3.js +555.77 kB | 273.89 kB | 270.13 kB | 91.18 kB | 90.80 kB | d3.js -1.01 MB | 461.13 kB | 458.89 kB | 126.91 kB | 126.71 kB | bundle.min.js +1.01 MB | 461.09 kB | 458.89 kB | 126.91 kB | 126.71 kB | bundle.min.js -1.25 MB | 656.81 kB | 646.76 kB | 164.16 kB | 163.73 kB | three.js +1.25 MB | 656.66 kB | 646.76 kB | 164.14 kB | 163.73 kB | three.js -2.14 MB | 735.33 kB | 724.14 kB | 180.99 kB | 181.07 kB | victory.js +2.14 MB | 735.28 kB | 724.14 kB | 180.98 kB | 181.07 kB | victory.js -3.20 MB | 1.01 MB | 1.01 MB | 332.27 kB | 331.56 kB | echarts.js +3.20 MB | 1.01 MB | 1.01 MB | 332.23 kB | 331.56 kB | echarts.js -6.69 MB | 2.36 MB | 2.31 MB | 495.04 kB | 488.28 kB | antd.js +6.69 MB | 2.36 MB | 2.31 MB | 494.93 kB | 488.28 kB | antd.js -10.95 MB | 3.51 MB | 3.49 MB | 910.93 kB | 915.50 kB | typescript.js +10.95 MB | 3.51 MB | 3.49 MB | 910.92 kB | 915.50 kB | typescript.js From 9d62284202a3a4d9493e88834ea51bb4067c1ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BF=A0=20/=20green?= Date: Sun, 29 Dec 2024 01:55:45 +0900 Subject: [PATCH 036/162] chore(tasks): print diff for minify idempotency assertion (#8161) Made the assertion error output more easier to understand. Example output: ![image](https://github.com/user-attachments/assets/445910fb-b389-4884-b4c5-70a095bc523f) --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- Cargo.lock | 19 ++++++++++++++++++- Cargo.toml | 1 + tasks/minsize/Cargo.toml | 2 ++ tasks/minsize/src/lib.rs | 23 ++++++++++++++++++++++- 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cab6130bbc748a..9461a3e989753b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -174,6 +174,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" dependencies = [ "memchr", + "regex-automata", "serde", ] @@ -1082,7 +1083,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -1787,6 +1788,7 @@ dependencies = [ name = "oxc_minsize" version = "0.0.0" dependencies = [ + "cow-utils", "flate2", "humansize", "oxc_allocator", @@ -1798,6 +1800,7 @@ dependencies = [ "oxc_tasks_common", "oxc_transformer", "rustc-hash", + "similar-asserts", ] [[package]] @@ -2749,6 +2752,20 @@ name = "similar" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" +dependencies = [ + "bstr", + "unicode-segmentation", +] + +[[package]] +name = "similar-asserts" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe85670573cd6f0fa97940f26e7e6601213c3b0555246c24234131f88c5709e" +dependencies = [ + "console", + "similar", +] [[package]] name = "siphasher" diff --git a/Cargo.toml b/Cargo.toml index 1bd59663b3e210..1eff0ff6634e57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -189,6 +189,7 @@ serde-wasm-bindgen = "0.6.5" sha1 = "0.10.6" simdutf8 = { version = "0.1.5", features = ["aarch64_neon"] } similar = "2.6.0" +similar-asserts = "1.6.0" string_wizard = "0.0.25" tempfile = "3.14.0" tokio = "1.42.0" diff --git a/tasks/minsize/Cargo.toml b/tasks/minsize/Cargo.toml index c842002da53fea..5a39fe439d4035 100644 --- a/tasks/minsize/Cargo.toml +++ b/tasks/minsize/Cargo.toml @@ -29,5 +29,7 @@ oxc_transformer = { workspace = true } flate2 = { workspace = true } oxc_tasks_common = { workspace = true } +cow-utils = { workspace = true } humansize = { workspace = true } rustc-hash = { workspace = true } +similar-asserts = { workspace = true } diff --git a/tasks/minsize/src/lib.rs b/tasks/minsize/src/lib.rs index 3c149505e568d8..91eda08f1e8df0 100644 --- a/tasks/minsize/src/lib.rs +++ b/tasks/minsize/src/lib.rs @@ -16,6 +16,7 @@ use oxc_span::SourceType; use oxc_tasks_common::{project_root, TestFile, TestFiles}; use oxc_transformer::{ReplaceGlobalDefines, ReplaceGlobalDefinesConfig}; use rustc_hash::FxHashMap; +use similar_asserts::assert_eq; // #[test] // #[cfg(any(coverage, coverage_nightly))] @@ -23,6 +24,21 @@ use rustc_hash::FxHashMap; // run().unwrap(); // } +macro_rules! assert_eq_minified_code { + ($left:expr, $right:expr, $($arg:tt)*) => { + if $left != $right { + let normalized_left = $crate::normalize_minified_code($left); + let normalized_right = $crate::normalize_minified_code($right); + assert_eq!(normalized_left, normalized_right, $($arg)*); + } + }; +} + +fn normalize_minified_code(code: &str) -> String { + use cow_utils::CowUtils; + code.cow_replace(";", ";\n").cow_replace(",", ",\n").into_owned() +} + /// # Panics /// # Errors pub fn run() -> Result<(), io::Error> { @@ -131,7 +147,12 @@ fn minify_twice(file: &TestFile) -> String { }; let source_text1 = minify(&file.source_text, source_type, options); let source_text2 = minify(&source_text1, source_type, options); - assert_eq!(source_text1, source_text2, "Minification failed for {}", &file.file_name); + assert_eq_minified_code!( + &source_text1, + &source_text2, + "Minification failed for {}", + &file.file_name + ); source_text2 } From d8d2ec625729582b58633e0e17e39059a248dd81 Mon Sep 17 00:00:00 2001 From: "Alexander S." Date: Sat, 28 Dec 2024 18:03:11 +0100 Subject: [PATCH 037/162] perf(linter): run rules which require typescript syntax only when source type is actually typescript (#8166) --- .../src/rules/typescript/consistent_generic_constructors.rs | 4 ++++ .../oxc_linter/src/rules/typescript/no_empty_object_type.rs | 4 ++++ crates/oxc_linter/src/rules/typescript/no_misused_new.rs | 4 ++++ .../src/rules/typescript/no_unsafe_function_type.rs | 4 ++++ .../src/rules/typescript/no_wrapper_object_types.rs | 4 ++++ 5 files changed, 20 insertions(+) diff --git a/crates/oxc_linter/src/rules/typescript/consistent_generic_constructors.rs b/crates/oxc_linter/src/rules/typescript/consistent_generic_constructors.rs index 8ee66e9916c97a..bcb86b4f21156f 100644 --- a/crates/oxc_linter/src/rules/typescript/consistent_generic_constructors.rs +++ b/crates/oxc_linter/src/rules/typescript/consistent_generic_constructors.rs @@ -118,6 +118,10 @@ impl Rule for ConsistentGenericConstructors { .unwrap_or_default(), })) } + + fn should_run(&self, ctx: &crate::rules::ContextHost) -> bool { + ctx.source_type().is_typescript() + } } impl ConsistentGenericConstructors { diff --git a/crates/oxc_linter/src/rules/typescript/no_empty_object_type.rs b/crates/oxc_linter/src/rules/typescript/no_empty_object_type.rs index 12c1bc5f91fe6f..8e479d1d8e83f4 100644 --- a/crates/oxc_linter/src/rules/typescript/no_empty_object_type.rs +++ b/crates/oxc_linter/src/rules/typescript/no_empty_object_type.rs @@ -140,6 +140,10 @@ impl Rule for NoEmptyObjectType { _ => {} } } + + fn should_run(&self, ctx: &crate::rules::ContextHost) -> bool { + ctx.source_type().is_typescript() + } } fn check_interface_declaration( diff --git a/crates/oxc_linter/src/rules/typescript/no_misused_new.rs b/crates/oxc_linter/src/rules/typescript/no_misused_new.rs index acc5d646d29929..64e5c77c35b98d 100644 --- a/crates/oxc_linter/src/rules/typescript/no_misused_new.rs +++ b/crates/oxc_linter/src/rules/typescript/no_misused_new.rs @@ -113,6 +113,10 @@ impl Rule for NoMisusedNew { _ => {} } } + + fn should_run(&self, ctx: &crate::rules::ContextHost) -> bool { + ctx.source_type().is_typescript() + } } #[test] diff --git a/crates/oxc_linter/src/rules/typescript/no_unsafe_function_type.rs b/crates/oxc_linter/src/rules/typescript/no_unsafe_function_type.rs index 23ff43c4704f05..dd7fe7af08a3b1 100644 --- a/crates/oxc_linter/src/rules/typescript/no_unsafe_function_type.rs +++ b/crates/oxc_linter/src/rules/typescript/no_unsafe_function_type.rs @@ -76,6 +76,10 @@ impl Rule for NoUnsafeFunctionType { _ => {} } } + + fn should_run(&self, ctx: &crate::rules::ContextHost) -> bool { + ctx.source_type().is_typescript() + } } fn handle_function_type<'a>(identifier: &'a IdentifierReference<'a>, ctx: &LintContext<'a>) { diff --git a/crates/oxc_linter/src/rules/typescript/no_wrapper_object_types.rs b/crates/oxc_linter/src/rules/typescript/no_wrapper_object_types.rs index f8adc33f1f4b74..d320944f24c177 100644 --- a/crates/oxc_linter/src/rules/typescript/no_wrapper_object_types.rs +++ b/crates/oxc_linter/src/rules/typescript/no_wrapper_object_types.rs @@ -99,6 +99,10 @@ impl Rule for NoWrapperObjectTypes { } } } + + fn should_run(&self, ctx: &crate::rules::ContextHost) -> bool { + ctx.source_type().is_typescript() + } } #[test] From faf746473351eaaba330605efdb8c40975137763 Mon Sep 17 00:00:00 2001 From: "Alexander S." Date: Sat, 28 Dec 2024 19:52:10 +0100 Subject: [PATCH 038/162] fix(linter): disable rule `react/rules-of-hook` by file extension (#8168) --- crates/oxc_linter/src/rules/react/rules_of_hooks.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/oxc_linter/src/rules/react/rules_of_hooks.rs b/crates/oxc_linter/src/rules/react/rules_of_hooks.rs index ac670d111b1f33..15422b18f97271 100644 --- a/crates/oxc_linter/src/rules/react/rules_of_hooks.rs +++ b/crates/oxc_linter/src/rules/react/rules_of_hooks.rs @@ -16,7 +16,7 @@ use crate::{ context::LintContext, rule::Rule, utils::{is_react_component_or_hook_name, is_react_function_call, is_react_hook}, - AstNode, FrameworkFlags, + AstNode, }; mod diagnostics { @@ -110,8 +110,9 @@ declare_oxc_lint!( impl Rule for RulesOfHooks { fn should_run(&self, ctx: &crate::rules::ContextHost) -> bool { // disable this rule in vue/nuxt and svelte(kit) files - // top level useFunction are very common - !ctx.frameworks().contains(FrameworkFlags::SvelteKit | FrameworkFlags::Nuxt) + // react hook can be build in only `.ts` files, + // but `useX` functions are popular and can be false positive in other frameworks + !ctx.file_path().extension().is_some_and(|ext| ext == "vue" || ext == "svelte") } fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { From 3c5718d68b06ec4fb29cd8c012c807e2365f15c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BF=A0=20/=20green?= Date: Sun, 29 Dec 2024 12:49:57 +0900 Subject: [PATCH 039/162] feat(minifier): fold `typeof foo == undefined` into `foo == undefined` when possible (#8160) --- .../peephole_substitute_alternate_syntax.rs | 52 ++++++++++++++++--- tasks/minsize/minsize.snap | 8 +-- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs index f28c082a823b11..06e98791e46a08 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs @@ -239,8 +239,10 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { } } - /// Compress `typeof foo == "undefined"` into `typeof foo > "u"` + /// Compress `typeof foo == "undefined"` /// + /// - `typeof foo == "undefined"` (if foo is resolved) -> `foo === undefined` + /// - `typeof foo != "undefined"` (if foo is resolved) -> `foo !== undefined` /// - `typeof foo == "undefined"` -> `typeof foo > "u"` /// - `typeof foo != "undefined"` -> `typeof foo < "u"` /// @@ -249,12 +251,12 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { expr: &mut BinaryExpression<'a>, ctx: Ctx<'a, 'b>, ) -> Option> { - let new_op = match expr.operator { + let (new_eq_op, new_comp_op) = match expr.operator { BinaryOperator::Equality | BinaryOperator::StrictEquality => { - BinaryOperator::GreaterThan + (BinaryOperator::StrictEquality, BinaryOperator::GreaterThan) } BinaryOperator::Inequality | BinaryOperator::StrictInequality => { - BinaryOperator::LessThan + (BinaryOperator::StrictInequality, BinaryOperator::LessThan) } _ => return None, }; @@ -273,10 +275,17 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { }, ); let (_void_exp, id_ref) = pair?; - let argument = Expression::Identifier(ctx.alloc(id_ref)); - let left = ctx.ast.expression_unary(SPAN, UnaryOperator::Typeof, argument); - let right = ctx.ast.expression_string_literal(SPAN, "u", None); - Some(ctx.ast.expression_binary(expr.span, left, new_op, right)) + let is_resolved = ctx.scopes().find_binding(ctx.current_scope_id(), &id_ref.name).is_some(); + if is_resolved { + let left = Expression::Identifier(ctx.alloc(id_ref)); + let right = ctx.ast.void_0(SPAN); + Some(ctx.ast.expression_binary(expr.span, left, new_eq_op, right)) + } else { + let argument = Expression::Identifier(ctx.alloc(id_ref)); + let left = ctx.ast.expression_unary(SPAN, UnaryOperator::Typeof, argument); + let right = ctx.ast.expression_string_literal(SPAN, "u", None); + Some(ctx.ast.expression_binary(expr.span, left, new_comp_op, right)) + } } /// Compress `foo === null || foo === undefined` into `foo == null`. @@ -1153,6 +1162,33 @@ mod test { test_same("const foo = () => { foo; return 'baz' }"); } + #[test] + fn test_fold_is_typeof_equals_undefined_resolved() { + test("var x; typeof x !== 'undefined'", "var x; x !== void 0"); + test("var x; typeof x != 'undefined'", "var x; x !== void 0"); + test("var x; 'undefined' !== typeof x", "var x; x !== void 0"); + test("var x; 'undefined' != typeof x", "var x; x !== void 0"); + + test("var x; typeof x === 'undefined'", "var x; x === void 0"); + test("var x; typeof x == 'undefined'", "var x; x === void 0"); + test("var x; 'undefined' === typeof x", "var x; x === void 0"); + test("var x; 'undefined' == typeof x", "var x; x === void 0"); + + test( + "var x; function foo() { typeof x !== 'undefined' }", + "var x; function foo() { x !== void 0 }", + ); + test( + "typeof x !== 'undefined'; function foo() { var x }", + "typeof x < 'u'; function foo() { var x }", + ); + test("typeof x !== 'undefined'; { var x }", "x !== void 0; { var x }"); + test("typeof x !== 'undefined'; { let x }", "typeof x < 'u'; { let x }"); + test("typeof x !== 'undefined'; var x", "x !== void 0; var x"); + // input and output both errors with same TDZ error + test("typeof x !== 'undefined'; let x", "x !== void 0; let x"); + } + /// Port from #[test] fn test_fold_is_typeof_equals_undefined() { diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 8b34fd75ae1fb6..f453e189930348 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -5,7 +5,7 @@ Original | minified | minified | gzip | gzip | Fixture 173.90 kB | 60.16 kB | 59.82 kB | 19.49 kB | 19.33 kB | moment.js -287.63 kB | 90.59 kB | 90.07 kB | 32.19 kB | 31.95 kB | jquery.js +287.63 kB | 90.59 kB | 90.07 kB | 32.18 kB | 31.95 kB | jquery.js 342.15 kB | 118.61 kB | 118.14 kB | 44.54 kB | 44.37 kB | vue.js @@ -17,11 +17,11 @@ Original | minified | minified | gzip | gzip | Fixture 1.25 MB | 656.66 kB | 646.76 kB | 164.14 kB | 163.73 kB | three.js -2.14 MB | 735.28 kB | 724.14 kB | 180.98 kB | 181.07 kB | victory.js +2.14 MB | 735.26 kB | 724.14 kB | 180.97 kB | 181.07 kB | victory.js 3.20 MB | 1.01 MB | 1.01 MB | 332.23 kB | 331.56 kB | echarts.js -6.69 MB | 2.36 MB | 2.31 MB | 494.93 kB | 488.28 kB | antd.js +6.69 MB | 2.36 MB | 2.31 MB | 494.89 kB | 488.28 kB | antd.js -10.95 MB | 3.51 MB | 3.49 MB | 910.92 kB | 915.50 kB | typescript.js +10.95 MB | 3.51 MB | 3.49 MB | 910.90 kB | 915.50 kB | typescript.js From 29dc0dc070472dd5d15343f18a9cd0358861c2ec Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Sun, 29 Dec 2024 04:09:42 +0000 Subject: [PATCH 040/162] feat(minifier): change `foo['bar']` -> foo.bar (#8169) --- .../peephole_substitute_alternate_syntax.rs | 42 ++++++++++++++++--- tasks/minsize/minsize.snap | 22 +++++----- tasks/minsize/src/lib.rs | 40 ++++++++---------- 3 files changed, 65 insertions(+), 39 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs index 06e98791e46a08..39c0ccc3765ee2 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs @@ -4,6 +4,7 @@ use oxc_ecmascript::{ToInt32, ToJsString}; use oxc_semantic::IsGlobalReference; use oxc_span::{GetSpan, SPAN}; use oxc_syntax::{ + identifier::is_identifier_name, number::NumberBase, operator::{BinaryOperator, UnaryOperator}, }; @@ -86,9 +87,7 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax { // Change syntax match expr { - Expression::ArrowFunctionExpression(arrow_expr) => { - self.try_compress_arrow_expression(arrow_expr, ctx); - } + Expression::ArrowFunctionExpression(e) => self.try_compress_arrow_expression(e, ctx), Expression::ChainExpression(e) => self.try_compress_chain_call_expression(e, ctx), Expression::BinaryExpression(e) => self.try_compress_type_of_equal_string(e, ctx), _ => {} @@ -101,12 +100,15 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax { Expression::AssignmentExpression(e) => Self::try_compress_assignment_expression(e, ctx), Expression::LogicalExpression(e) => Self::try_compress_is_null_or_undefined(e, ctx), Expression::NewExpression(e) => Self::try_fold_new_expression(e, ctx), + Expression::TemplateLiteral(t) => Self::try_fold_template_literal(t, ctx), + Expression::BinaryExpression(e) => Self::try_compress_typeof_undefined(e, ctx), + Expression::ComputedMemberExpression(e) => { + self.try_compress_computed_member_expression(e, ctx) + } Expression::CallExpression(e) => { Self::try_fold_literal_constructor_call_expression(e, ctx) .or_else(|| Self::try_fold_simple_function_call(e, ctx)) } - Expression::TemplateLiteral(t) => Self::try_fold_template_literal(t, ctx), - Expression::BinaryExpression(e) => Self::try_compress_typeof_undefined(e, ctx), _ => None, } { *expr = folded_expr; @@ -714,7 +716,9 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { // https://github.com/swc-project/swc/blob/4e2dae558f60a9f5c6d2eac860743e6c0b2ec562/crates/swc_ecma_minifier/src/compress/pure/properties.rs #[allow(clippy::cast_lossless)] fn try_compress_property_key(&mut self, key: &mut PropertyKey<'a>, ctx: &mut TraverseCtx<'a>) { - use oxc_syntax::identifier::is_identifier_name; + if self.in_fixed_loop { + return; + } let PropertyKey::StringLiteral(s) = key else { return }; if match ctx.parent() { Ancestor::ObjectPropertyKey(key) => *key.computed(), @@ -743,6 +747,26 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { } } } + + /// `foo['bar']` -> `foo.bar` + fn try_compress_computed_member_expression( + &self, + e: &mut ComputedMemberExpression<'a>, + ctx: Ctx<'a, 'b>, + ) -> Option> { + if self.in_fixed_loop { + return None; + } + let Expression::StringLiteral(s) = &e.expression else { return None }; + if !is_identifier_name(&s.value) { + return None; + } + let property = ctx.ast.identifier_name(s.span, s.value.clone()); + let object = ctx.ast.move_expression(&mut e.object); + Some(Expression::StaticMemberExpression( + ctx.ast.alloc_static_member_expression(e.span, object, property, false), + )) + } } /// Port from @@ -1240,4 +1264,10 @@ mod test { test("({ '0': _, 'a': _ })", "({ 0: _, a: _ })"); test_same("({ '1.1': _, '😊': _, 'a.a': _ })"); } + + #[test] + fn test_computed_to_member_expression() { + test("x['true']", "x.true"); + test_same("x['😊']"); + } } diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index f453e189930348..4a350420714ebf 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -3,25 +3,25 @@ Original | minified | minified | gzip | gzip | Fixture ------------------------------------------------------------------------------------- 72.14 kB | 23.74 kB | 23.70 kB | 8.61 kB | 8.54 kB | react.development.js -173.90 kB | 60.16 kB | 59.82 kB | 19.49 kB | 19.33 kB | moment.js +173.90 kB | 60.16 kB | 59.82 kB | 19.48 kB | 19.33 kB | moment.js -287.63 kB | 90.59 kB | 90.07 kB | 32.18 kB | 31.95 kB | jquery.js +287.63 kB | 90.70 kB | 90.07 kB | 32.21 kB | 31.95 kB | jquery.js -342.15 kB | 118.61 kB | 118.14 kB | 44.54 kB | 44.37 kB | vue.js +342.15 kB | 118.59 kB | 118.14 kB | 44.53 kB | 44.37 kB | vue.js -544.10 kB | 72.04 kB | 72.48 kB | 26.18 kB | 26.20 kB | lodash.js +544.10 kB | 72.49 kB | 72.48 kB | 26.22 kB | 26.20 kB | lodash.js -555.77 kB | 273.89 kB | 270.13 kB | 91.18 kB | 90.80 kB | d3.js +555.77 kB | 274.10 kB | 270.13 kB | 91.25 kB | 90.80 kB | d3.js -1.01 MB | 461.09 kB | 458.89 kB | 126.91 kB | 126.71 kB | bundle.min.js +1.01 MB | 461.06 kB | 458.89 kB | 126.91 kB | 126.71 kB | bundle.min.js -1.25 MB | 656.66 kB | 646.76 kB | 164.14 kB | 163.73 kB | three.js +1.25 MB | 656.99 kB | 646.76 kB | 164.18 kB | 163.73 kB | three.js -2.14 MB | 735.26 kB | 724.14 kB | 180.97 kB | 181.07 kB | victory.js +2.14 MB | 728.22 kB | 724.14 kB | 180.43 kB | 181.07 kB | victory.js -3.20 MB | 1.01 MB | 1.01 MB | 332.23 kB | 331.56 kB | echarts.js +3.20 MB | 1.01 MB | 1.01 MB | 332.24 kB | 331.56 kB | echarts.js -6.69 MB | 2.36 MB | 2.31 MB | 494.89 kB | 488.28 kB | antd.js +6.69 MB | 2.34 MB | 2.31 MB | 493.51 kB | 488.28 kB | antd.js -10.95 MB | 3.51 MB | 3.49 MB | 910.90 kB | 915.50 kB | typescript.js +10.95 MB | 3.51 MB | 3.49 MB | 910.88 kB | 915.50 kB | typescript.js diff --git a/tasks/minsize/src/lib.rs b/tasks/minsize/src/lib.rs index 91eda08f1e8df0..638a48287c8da9 100644 --- a/tasks/minsize/src/lib.rs +++ b/tasks/minsize/src/lib.rs @@ -5,6 +5,7 @@ use std::{ path::Path, }; +use cow_utils::CowUtils; use flate2::{write::GzEncoder, Compression}; use humansize::{format_size, DECIMAL}; use oxc_allocator::Allocator; @@ -16,7 +17,6 @@ use oxc_span::SourceType; use oxc_tasks_common::{project_root, TestFile, TestFiles}; use oxc_transformer::{ReplaceGlobalDefines, ReplaceGlobalDefinesConfig}; use rustc_hash::FxHashMap; -use similar_asserts::assert_eq; // #[test] // #[cfg(any(coverage, coverage_nightly))] @@ -24,21 +24,6 @@ use similar_asserts::assert_eq; // run().unwrap(); // } -macro_rules! assert_eq_minified_code { - ($left:expr, $right:expr, $($arg:tt)*) => { - if $left != $right { - let normalized_left = $crate::normalize_minified_code($left); - let normalized_right = $crate::normalize_minified_code($right); - assert_eq!(normalized_left, normalized_right, $($arg)*); - } - }; -} - -fn normalize_minified_code(code: &str) -> String { - use cow_utils::CowUtils; - code.cow_replace(";", ";\n").cow_replace(",", ",\n").into_owned() -} - /// # Panics /// # Errors pub fn run() -> Result<(), io::Error> { @@ -147,12 +132,7 @@ fn minify_twice(file: &TestFile) -> String { }; let source_text1 = minify(&file.source_text, source_type, options); let source_text2 = minify(&source_text1, source_type, options); - assert_eq_minified_code!( - &source_text1, - &source_text2, - "Minification failed for {}", - &file.file_name - ); + assert_eq_minified_code(&source_text1, &source_text2, &file.file_name); source_text2 } @@ -181,3 +161,19 @@ fn gzip_size(s: &str) -> usize { let s = e.finish().unwrap(); s.len() } + +fn assert_eq_minified_code(s1: &str, s2: &str, filename: &str) { + if s1 != s2 { + let normalized_left = normalize_minified_code(s1); + let normalized_right = normalize_minified_code(s2); + similar_asserts::assert_eq!( + normalized_left, + normalized_right, + "Minification failed for {filename}" + ); + } +} + +fn normalize_minified_code(code: &str) -> String { + code.cow_replace(";", ";\n").cow_replace(",", ",\n").into_owned() +} From fc43ec530e7e1c7b7c38c12b323ca092b180e44a Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Sun, 29 Dec 2024 07:14:13 +0000 Subject: [PATCH 041/162] feat(minifier): fold `string.length` / `array.length` (#8172) --- .../src/constant_evaluation/mod.rs | 28 ++++++++++++- .../src/ast_passes/peephole_fold_constants.rs | 39 ++++++++++++++++++- tasks/minsize/minsize.snap | 4 +- 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/crates/oxc_ecmascript/src/constant_evaluation/mod.rs b/crates/oxc_ecmascript/src/constant_evaluation/mod.rs index ab47066c7de256..79722b5aedbf97 100644 --- a/crates/oxc_ecmascript/src/constant_evaluation/mod.rs +++ b/crates/oxc_ecmascript/src/constant_evaluation/mod.rs @@ -1,7 +1,7 @@ use std::{borrow::Cow, cmp::Ordering}; use num_bigint::BigInt; -use num_traits::Zero; +use num_traits::{ToPrimitive, Zero}; use oxc_ast::ast::*; @@ -193,6 +193,7 @@ pub trait ConstantEvaluation<'a> { Expression::StringLiteral(lit) => { Some(ConstantValue::String(Cow::Borrowed(lit.value.as_str()))) } + Expression::StaticMemberExpression(e) => self.eval_static_member_expression(e), _ => None, } } @@ -458,6 +459,31 @@ pub trait ConstantEvaluation<'a> { } } + fn eval_static_member_expression( + &self, + expr: &StaticMemberExpression<'a>, + ) -> Option> { + match expr.property.name.as_str() { + "length" => { + if let Some(ConstantValue::String(s)) = self.eval_expression(&expr.object) { + // TODO(perf): no need to actually convert, only need the length + Some(ConstantValue::Number(s.encode_utf16().count().to_f64().unwrap())) + } else { + if expr.object.may_have_side_effects() { + return None; + } + + if let Expression::ArrayExpression(arr) = &expr.object { + Some(ConstantValue::Number(arr.elements.len().to_f64().unwrap())) + } else { + None + } + } + } + _ => None, + } + } + /// fn is_less_than( &self, diff --git a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs index d0e55587a4aa34..60d5eadd18b17e 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs @@ -51,7 +51,9 @@ impl<'a> Traverse<'a> for PeepholeFoldConstants { _ => ctx.eval_unary_expression(e).map(|v| ctx.value_to_expr(e.span, v)), } } - // TODO: return tryFoldGetProp(subtree); + Expression::StaticMemberExpression(e) => { + Self::try_fold_static_member_expression(e, ctx) + } Expression::LogicalExpression(e) => Self::try_fold_logical_expression(e, ctx), Expression::ChainExpression(e) => Self::try_fold_optional_chain(e, ctx), // TODO: tryFoldGetElem @@ -97,6 +99,16 @@ impl<'a, 'b> PeepholeFoldConstants { None } + fn try_fold_static_member_expression( + static_member_expr: &mut StaticMemberExpression<'a>, + ctx: Ctx<'a, 'b>, + ) -> Option> { + // TODO: tryFoldObjectPropAccess(n, left, name) + + ctx.eval_static_member_expression(static_member_expr) + .map(|value| ctx.value_to_expr(static_member_expr.span, value)) + } + fn try_fold_logical_expression( logical_expr: &mut LogicalExpression<'a>, ctx: Ctx<'a, 'b>, @@ -1504,6 +1516,31 @@ mod test { test("(+x & 1) & 2", "+x & 0"); } + #[test] + fn test_fold_array_length() { + // Can fold + test("x = [].length", "x = 0"); + test("x = [1,2,3].length", "x = 3"); + // test("x = [a,b].length", "x = 2"); + + // Not handled yet + test("x = [,,1].length", "x = 3"); + + // Cannot fold + test("x = [foo(), 0].length", "x = [foo(),0].length"); + test_same("x = y.length"); + } + + #[test] + fn test_fold_string_length() { + // Can fold basic strings. + test("x = ''.length", "x = 0"); + test("x = '123'.length", "x = 3"); + + // Test Unicode escapes are accounted for. + test("x = '123\\u01dc'.length", "x = 4"); + } + #[test] fn test_fold_instance_of() { // Non object types are never instances of anything. diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 4a350420714ebf..aa5be2bf411f7b 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -13,7 +13,7 @@ Original | minified | minified | gzip | gzip | Fixture 555.77 kB | 274.10 kB | 270.13 kB | 91.25 kB | 90.80 kB | d3.js -1.01 MB | 461.06 kB | 458.89 kB | 126.91 kB | 126.71 kB | bundle.min.js +1.01 MB | 461.05 kB | 458.89 kB | 126.89 kB | 126.71 kB | bundle.min.js 1.25 MB | 656.99 kB | 646.76 kB | 164.18 kB | 163.73 kB | three.js @@ -23,5 +23,5 @@ Original | minified | minified | gzip | gzip | Fixture 6.69 MB | 2.34 MB | 2.31 MB | 493.51 kB | 488.28 kB | antd.js -10.95 MB | 3.51 MB | 3.49 MB | 910.88 kB | 915.50 kB | typescript.js +10.95 MB | 3.51 MB | 3.49 MB | 910.84 kB | 915.50 kB | typescript.js From 1fa53413e032a1077e0cc59f276d20b163acef39 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Sun, 29 Dec 2024 07:19:13 +0000 Subject: [PATCH 042/162] test(minifier): port tests from ConvertToDottedPropertiesTest (#8175) --- .../peephole_substitute_alternate_syntax.rs | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) diff --git a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs index 39c0ccc3765ee2..2693cb60876fa6 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs @@ -1270,4 +1270,211 @@ mod test { test("x['true']", "x.true"); test_same("x['😊']"); } + + // ---------- + + /// Port from + #[test] + fn test_convert_to_dotted_properties_convert() { + test("a['p']", "a.p"); + test("a['_p_']", "a._p_"); + test("a['_']", "a._"); + test("a['$']", "a.$"); + test("a.b.c['p']", "a.b.c.p"); + test("a.b['c'].p", "a.b.c.p"); + test("a['p']();", "a.p();"); + test("a()['p']", "a().p"); + // ASCII in Unicode is always safe. + test("a['\\u0041A']", "a.AA"); + // This is safe for ES5+. (keywords cannot be used for ES3) + test("a['default']", "a.default"); + // This is safe for ES2015+. (\u1d17 was introduced in Unicode 3.1, ES2015+ uses Unicode 5.1+) + test("a['\\u1d17A']", "a.\u{1d17}A"); + // Latin capital N with tilde - this is safe for ES3+. + test("a['\\u00d1StuffAfter']", "a.\u{00d1}StuffAfter"); + } + + #[test] + fn test_convert_to_dotted_properties_do_not_convert() { + test_same("a[0]"); + test_same("a['']"); + test_same("a[' ']"); + test_same("a[',']"); + test_same("a[';']"); + test_same("a[':']"); + test_same("a['.']"); + test_same("a['0']"); + test_same("a['p ']"); + test_same("a['p' + '']"); + test_same("a[p]"); + test_same("a[P]"); + test_same("a[$]"); + test_same("a[p()]"); + // Ignorable control characters are ok in Java identifiers, but not in JS. + test_same("a['A\\u0004']"); + } + + #[test] + fn test_convert_to_dotted_properties_already_dotted() { + test_same("a.b"); + test_same("var a = {b: 0};"); + } + + #[test] + fn test_convert_to_dotted_properties_quoted_props() { + test_same("({'':0})"); + test_same("({'1.0':0})"); + test("({'\\u1d17A':0})", "({ \u{1d17}A: 0 })"); + test_same("({'a\\u0004b':0})"); + } + + #[test] + fn test5746867() { + test_same("var a = { '$\\\\' : 5 };"); + test_same("var a = { 'x\\\\u0041$\\\\' : 5 };"); + } + + #[test] + #[ignore] + fn test_convert_to_dotted_properties_optional_chaining() { + test("data?.['name']", "data?.name"); + test("data?.['name']?.['first']", "data?.name?.first"); + test("data['name']?.['first']", "data.name?.first"); + test_same("a?.[0]"); + test_same("a?.['']"); + test_same("a?.[' ']"); + test_same("a?.[',']"); + test_same("a?.[';']"); + test_same("a?.[':']"); + test_same("a?.['.']"); + test_same("a?.['0']"); + test_same("a?.['p ']"); + test_same("a?.['p' + '']"); + test_same("a?.[p]"); + test_same("a?.[P]"); + test_same("a?.[$]"); + test_same("a?.[p()]"); + // This is safe for ES5+. (keywords cannot be used for ES3) + test("a?.['default']", "a?.default"); + } + + #[test] + #[ignore] + fn test_convert_to_dotted_properties_computed_property_or_field() { + test("const test1 = {['prop1']:87};", "const test1 = {prop1:87};"); + test( + "const test1 = {['prop1']:87,['prop2']:bg,['prop3']:'hfd'};", + "const test1 = {prop1:87,prop2:bg,prop3:'hfd'};", + ); + test( + "o = {['x']: async function(x) { return await x + 1; }};", + "o = {x:async function (x) { return await x + 1; }};", + ); + test("o = {['x']: function*(x) {}};", "o = {x: function*(x) {}};"); + test( + "o = {['x']: async function*(x) { return await x + 1; }};", + "o = {x:async function*(x) { return await x + 1; }};", + ); + test("class C {'x' = 0; ['y'] = 1;}", "class C { x= 0;y= 1;}"); + test("class C {'m'() {} }", "class C {m() {}}"); + + test("const o = {'b'() {}, ['c']() {}};", "const o = {b: function() {}, c:function(){}};"); + test("o = {['x']: () => this};", "o = {x: () => this};"); + + test("const o = {get ['d']() {}};", "const o = {get d() {}};"); + test("const o = { set ['e'](x) {}};", "const o = { set e(x) {}};"); + test( + "class C {'m'() {} ['n']() {} 'x' = 0; ['y'] = 1;}", + "class C {m() {} n() {} x= 0;y= 1;}", + ); + test( + "const o = { get ['d']() {}, set ['e'](x) {}};", + "const o = {get d() {}, set e(x){}};", + ); + test( + "const o = {['a']: 1,'b'() {}, ['c']() {}, get ['d']() {}, set ['e'](x) {}};", + "const o = {a: 1,b: function() {}, c: function() {}, get d() {}, set e(x) {}};", + ); + + // test static keyword + test( + r" + class C { + 'm'(){} + ['n'](){} + static 'x' = 0; + static ['y'] = 1;} + ", + r" + class C { + m(){} + n(){} + static x = 0; + static y= 1;} + ", + ); + test( + r" + window['MyClass'] = class { + static ['Register'](){} + }; + ", + r" + window.MyClass = class { + static Register(){} + }; + ", + ); + test( + r" + class C { + 'method'(){} + async ['method1'](){} + *['method2'](){} + static ['smethod'](){} + static async ['smethod1'](){} + static *['smethod2'](){}} + ", + r" + class C { + method(){} + async method1(){} + *method2(){} + static smethod(){} + static async smethod1(){} + static *smethod2(){}} + ", + ); + + test_same("const o = {[fn()]: 0}"); + test_same("const test1 = {[0]:87};"); + test_same("const test1 = {['default']:87};"); + test_same("class C { ['constructor']() {} }"); + test_same("class C { ['constructor'] = 0 }"); + } + + #[test] + #[ignore] + fn test_convert_to_dotted_properties_computed_property_with_default_value() { + test("const {['o']: o = 0} = {};", "const {o:o = 0} = {};"); + } + + #[test] + #[ignore] + fn test_convert_to_dotted_properties_continue_optional_chaining() { + test("const opt1 = window?.a?.['b'];", "const opt1 = window?.a?.b;"); + + test("const opt2 = window?.a['b'];", "const opt2 = window?.a.b;"); + test( + r" + const chain = + window['a'].x.y.b.x.y['c'].x.y?.d.x.y['e'].x.y + ['f-f'].x.y?.['g-g'].x.y?.['h'].x.y['i'].x.y; + ", + r" + const chain = window.a.x.y.b.x.y.c.x.y?.d.x.y.e.x.y + ['f-f'].x.y?.['g-g'].x.y?.h.x.y.i.x.y; + ", + ); + } } From 75d5f17dbd892e28dd7458c8e10b51ef047ba612 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Sun, 29 Dec 2024 07:49:42 +0000 Subject: [PATCH 043/162] fix(minifier): minify string `PropertyKey` (#8177) #8169 added `if self.in_fixed_loop` in `try_compress_property_key`. Because `exit_property_key` was not called for `PeepholeOptimizations`, `try_compress_property_key` was never called. refs #8147 --- crates/oxc_minifier/src/ast_passes/mod.rs | 4 ++++ tasks/minsize/minsize.snap | 16 ++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/mod.rs b/crates/oxc_minifier/src/ast_passes/mod.rs index 459265a14fe03a..447f56f5365b20 100644 --- a/crates/oxc_minifier/src/ast_passes/mod.rs +++ b/crates/oxc_minifier/src/ast_passes/mod.rs @@ -269,6 +269,10 @@ impl<'a> Traverse<'a> for PeepholeOptimizations { fn exit_call_expression(&mut self, expr: &mut CallExpression<'a>, ctx: &mut TraverseCtx<'a>) { self.x3_peephole_substitute_alternate_syntax.exit_call_expression(expr, ctx); } + + fn exit_property_key(&mut self, key: &mut PropertyKey<'a>, ctx: &mut TraverseCtx<'a>) { + self.x3_peephole_substitute_alternate_syntax.exit_property_key(key, ctx); + } } pub struct DeadCodeElimination { diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index aa5be2bf411f7b..05892fab0ea2ff 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -5,23 +5,23 @@ Original | minified | minified | gzip | gzip | Fixture 173.90 kB | 60.16 kB | 59.82 kB | 19.48 kB | 19.33 kB | moment.js -287.63 kB | 90.70 kB | 90.07 kB | 32.21 kB | 31.95 kB | jquery.js +287.63 kB | 90.57 kB | 90.07 kB | 32.18 kB | 31.95 kB | jquery.js 342.15 kB | 118.59 kB | 118.14 kB | 44.53 kB | 44.37 kB | vue.js -544.10 kB | 72.49 kB | 72.48 kB | 26.22 kB | 26.20 kB | lodash.js +544.10 kB | 72.02 kB | 72.48 kB | 26.18 kB | 26.20 kB | lodash.js -555.77 kB | 274.10 kB | 270.13 kB | 91.25 kB | 90.80 kB | d3.js +555.77 kB | 273.89 kB | 270.13 kB | 91.18 kB | 90.80 kB | d3.js -1.01 MB | 461.05 kB | 458.89 kB | 126.89 kB | 126.71 kB | bundle.min.js +1.01 MB | 461.04 kB | 458.89 kB | 126.89 kB | 126.71 kB | bundle.min.js -1.25 MB | 656.99 kB | 646.76 kB | 164.18 kB | 163.73 kB | three.js +1.25 MB | 656.62 kB | 646.76 kB | 164.13 kB | 163.73 kB | three.js -2.14 MB | 728.22 kB | 724.14 kB | 180.43 kB | 181.07 kB | victory.js +2.14 MB | 727.99 kB | 724.14 kB | 180.35 kB | 181.07 kB | victory.js 3.20 MB | 1.01 MB | 1.01 MB | 332.24 kB | 331.56 kB | echarts.js -6.69 MB | 2.34 MB | 2.31 MB | 493.51 kB | 488.28 kB | antd.js +6.69 MB | 2.32 MB | 2.31 MB | 493.24 kB | 488.28 kB | antd.js -10.95 MB | 3.51 MB | 3.49 MB | 910.84 kB | 915.50 kB | typescript.js +10.95 MB | 3.51 MB | 3.49 MB | 910.83 kB | 915.50 kB | typescript.js From ad146bbb90d114d334e2846d4ebb3eb31d38b09d Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Sun, 29 Dec 2024 12:20:47 +0000 Subject: [PATCH 044/162] feat(codegen): print real newline when `\n` is inside template literals (#8178) --- crates/oxc_codegen/src/lib.rs | 14 ++++++++---- crates/oxc_codegen/tests/integration/unit.rs | 14 ++++++++---- tasks/minsize/minsize.snap | 24 ++++++++++---------- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/crates/oxc_codegen/src/lib.rs b/crates/oxc_codegen/src/lib.rs index 5fb02ce4306b68..9c15e0f9fea665 100644 --- a/crates/oxc_codegen/src/lib.rs +++ b/crates/oxc_codegen/src/lib.rs @@ -589,9 +589,9 @@ impl<'a> Codegen<'a> { fn print_quoted_utf16(&mut self, s: &str, allow_backtick: bool) { let quote = if self.options.minify { - let mut single_cost: u32 = 0; - let mut double_cost: u32 = 0; - let mut backtick_cost: u32 = 0; + let mut single_cost: i32 = 0; + let mut double_cost: i32 = 0; + let mut backtick_cost: i32 = 0; let mut bytes = s.as_bytes().iter().peekable(); while let Some(b) = bytes.next() { match b { @@ -642,7 +642,13 @@ impl<'a> Codegen<'a> { '\u{8}' => self.print_str("\\b"), // \b '\u{b}' => self.print_str("\\v"), // \v '\u{c}' => self.print_str("\\f"), // \f - '\n' => self.print_str("\\n"), + '\n' => { + if quote == b'`' { + self.print_ascii_byte(b'\n'); + } else { + self.print_str("\\n"); + } + } '\r' => self.print_str("\\r"), '\x1B' => self.print_str("\\x1B"), '\\' => self.print_str("\\\\"), diff --git a/crates/oxc_codegen/tests/integration/unit.rs b/crates/oxc_codegen/tests/integration/unit.rs index a05ef1f408bf49..007e41c80da27c 100644 --- a/crates/oxc_codegen/tests/integration/unit.rs +++ b/crates/oxc_codegen/tests/integration/unit.rs @@ -39,11 +39,6 @@ fn expr() { test("delete 2e308", "delete (0, Infinity);\n"); test_minify("delete 2e308", "delete(1/0);"); - test_minify( - r#";'eval("\'\\vstr\\ving\\v\'") === "\\vstr\\ving\\v"'"#, - r#";`eval("'\\vstr\\ving\\v'") === "\\vstr\\ving\\v"`;"#, - ); - test_minify_same(r#"({"http://a\r\" \n<'b:b@c\r\nd/e?f":{}});"#); } @@ -438,3 +433,12 @@ fn getter_setter() { test_minify("({ get [foo]() {} })", "({get[foo](){}});"); test_minify("({ set [foo]() {} })", "({set[foo](){}});"); } + +#[test] +fn string() { + test_minify( + r#";'eval("\'\\vstr\\ving\\v\'") === "\\vstr\\ving\\v"'"#, + r#";`eval("'\\vstr\\ving\\v'") === "\\vstr\\ving\\v"`;"#, + ); + test_minify(r#"foo("\n")"#, "foo(`\n`);"); +} diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 05892fab0ea2ff..fcc5fd5eb42f56 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -1,27 +1,27 @@ | Oxc | ESBuild | Oxc | ESBuild | Original | minified | minified | gzip | gzip | Fixture ------------------------------------------------------------------------------------- -72.14 kB | 23.74 kB | 23.70 kB | 8.61 kB | 8.54 kB | react.development.js +72.14 kB | 23.72 kB | 23.70 kB | 8.62 kB | 8.54 kB | react.development.js -173.90 kB | 60.16 kB | 59.82 kB | 19.48 kB | 19.33 kB | moment.js +173.90 kB | 60.15 kB | 59.82 kB | 19.50 kB | 19.33 kB | moment.js -287.63 kB | 90.57 kB | 90.07 kB | 32.18 kB | 31.95 kB | jquery.js +287.63 kB | 90.57 kB | 90.07 kB | 32.19 kB | 31.95 kB | jquery.js -342.15 kB | 118.59 kB | 118.14 kB | 44.53 kB | 44.37 kB | vue.js +342.15 kB | 118.54 kB | 118.14 kB | 44.56 kB | 44.37 kB | vue.js -544.10 kB | 72.02 kB | 72.48 kB | 26.18 kB | 26.20 kB | lodash.js +544.10 kB | 72.00 kB | 72.48 kB | 26.20 kB | 26.20 kB | lodash.js -555.77 kB | 273.89 kB | 270.13 kB | 91.18 kB | 90.80 kB | d3.js +555.77 kB | 273.88 kB | 270.13 kB | 91.19 kB | 90.80 kB | d3.js -1.01 MB | 461.04 kB | 458.89 kB | 126.89 kB | 126.71 kB | bundle.min.js +1.01 MB | 461.00 kB | 458.89 kB | 126.92 kB | 126.71 kB | bundle.min.js -1.25 MB | 656.62 kB | 646.76 kB | 164.13 kB | 163.73 kB | three.js +1.25 MB | 653.54 kB | 646.76 kB | 164.05 kB | 163.73 kB | three.js -2.14 MB | 727.99 kB | 724.14 kB | 180.35 kB | 181.07 kB | victory.js +2.14 MB | 727.91 kB | 724.14 kB | 180.39 kB | 181.07 kB | victory.js -3.20 MB | 1.01 MB | 1.01 MB | 332.24 kB | 331.56 kB | echarts.js +3.20 MB | 1.01 MB | 1.01 MB | 332.27 kB | 331.56 kB | echarts.js -6.69 MB | 2.32 MB | 2.31 MB | 493.24 kB | 488.28 kB | antd.js +6.69 MB | 2.32 MB | 2.31 MB | 493.25 kB | 488.28 kB | antd.js -10.95 MB | 3.51 MB | 3.49 MB | 910.83 kB | 915.50 kB | typescript.js +10.95 MB | 3.51 MB | 3.49 MB | 910.90 kB | 915.50 kB | typescript.js From 41ddf60ab02c31ce90d27bedf9cf9e448ba71280 Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Sun, 29 Dec 2024 12:27:32 +0000 Subject: [PATCH 045/162] feat(minfier): add `CompressOptions::target` (#8179) --- Cargo.lock | 1 + crates/oxc_minifier/src/options.rs | 13 +++- crates/oxc_syntax/Cargo.toml | 1 + crates/oxc_syntax/src/es_target.rs | 68 ++++++++++++++++++ crates/oxc_syntax/src/lib.rs | 1 + crates/oxc_transformer/src/options/env.rs | 1 + .../oxc_transformer/src/options/es_target.rs | 69 ++----------------- crates/oxc_transformer/src/options/mod.rs | 1 + crates/oxc_wasm/src/lib.rs | 1 + 9 files changed, 90 insertions(+), 66 deletions(-) create mode 100644 crates/oxc_syntax/src/es_target.rs diff --git a/Cargo.lock b/Cargo.lock index 9461a3e989753b..77b21a8d4c01d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1987,6 +1987,7 @@ version = "0.44.0" dependencies = [ "assert-unchecked", "bitflags 2.6.0", + "cow-utils", "nonmax", "oxc_allocator", "oxc_ast_macros", diff --git a/crates/oxc_minifier/src/options.rs b/crates/oxc_minifier/src/options.rs index 0cb3aa16e37246..0b5d45aa89f6ef 100644 --- a/crates/oxc_minifier/src/options.rs +++ b/crates/oxc_minifier/src/options.rs @@ -1,5 +1,14 @@ +use oxc_syntax::es_target::ESTarget; + #[derive(Debug, Clone, Copy)] pub struct CompressOptions { + /// Enable features that are targeted above. + /// + /// e.g. + /// + /// * catch optional binding when >= es2019 + pub target: ESTarget, + /// Remove `debugger;` statements. /// /// Default `true` @@ -20,10 +29,10 @@ impl Default for CompressOptions { impl CompressOptions { pub fn all_true() -> Self { - Self { drop_debugger: true, drop_console: true } + Self { target: ESTarget::ESNext, drop_debugger: true, drop_console: true } } pub fn all_false() -> Self { - Self { drop_debugger: false, drop_console: false } + Self { target: ESTarget::ESNext, drop_debugger: false, drop_console: false } } } diff --git a/crates/oxc_syntax/Cargo.toml b/crates/oxc_syntax/Cargo.toml index f0fc82c6eba1d8..fbd8355e0096f9 100644 --- a/crates/oxc_syntax/Cargo.toml +++ b/crates/oxc_syntax/Cargo.toml @@ -28,6 +28,7 @@ oxc_span = { workspace = true } assert-unchecked = { workspace = true } bitflags = { workspace = true } +cow-utils = { workspace = true } nonmax = { workspace = true } phf = { workspace = true, features = ["macros"] } rustc-hash = { workspace = true } diff --git a/crates/oxc_syntax/src/es_target.rs b/crates/oxc_syntax/src/es_target.rs new file mode 100644 index 00000000000000..4e6f86d5e1fa69 --- /dev/null +++ b/crates/oxc_syntax/src/es_target.rs @@ -0,0 +1,68 @@ +//! ECMAScript Target +use std::{fmt, str::FromStr}; + +use cow_utils::CowUtils; + +/// ECMAScript Target +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] +#[allow(missing_docs)] +pub enum ESTarget { + ES5, + ES2015, + ES2016, + ES2017, + ES2018, + ES2019, + ES2020, + ES2021, + ES2022, + ES2023, + ES2024, + ES2025, + #[default] + ESNext, +} + +impl FromStr for ESTarget { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.cow_to_lowercase().as_ref() { + "es5" => Ok(Self::ES5), + "es6" | "es2015" => Ok(Self::ES2015), + "es2016" => Ok(Self::ES2016), + "es2017" => Ok(Self::ES2017), + "es2018" => Ok(Self::ES2018), + "es2019" => Ok(Self::ES2019), + "es2020" => Ok(Self::ES2020), + "es2021" => Ok(Self::ES2021), + "es2022" => Ok(Self::ES2022), + "es2023" => Ok(Self::ES2023), + "es2024" => Ok(Self::ES2024), + "es2025" => Ok(Self::ES2025), + "esnext" => Ok(Self::ESNext), + _ => Err(format!("Invalid target \"{s}\".")), + } + } +} + +impl fmt::Display for ESTarget { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = match self { + Self::ES5 => "es5", + Self::ES2015 => "es2015", + Self::ES2016 => "es2016", + Self::ES2017 => "es2017", + Self::ES2018 => "es2018", + Self::ES2019 => "es2019", + Self::ES2020 => "es2020", + Self::ES2021 => "es2021", + Self::ES2022 => "es2022", + Self::ES2023 => "es2023", + Self::ES2024 => "es2024", + Self::ES2025 => "es2025", + Self::ESNext => "esnext", + }; + write!(f, "{s}",) + } +} diff --git a/crates/oxc_syntax/src/lib.rs b/crates/oxc_syntax/src/lib.rs index 94b728ff74dd82..4e564d2d7c2fb5 100644 --- a/crates/oxc_syntax/src/lib.rs +++ b/crates/oxc_syntax/src/lib.rs @@ -1,6 +1,7 @@ //! Common code for JavaScript Syntax #![warn(missing_docs)] pub mod class; +pub mod es_target; pub mod identifier; pub mod keyword; pub mod module_record; diff --git a/crates/oxc_transformer/src/options/env.rs b/crates/oxc_transformer/src/options/env.rs index eee0aa45861156..893668269764f3 100644 --- a/crates/oxc_transformer/src/options/env.rs +++ b/crates/oxc_transformer/src/options/env.rs @@ -119,6 +119,7 @@ impl EnvOptions { /// /// * When the query failed to parse. pub fn from_target_list>(list: &[S]) -> Result { + use crate::options::es_target::ESVersion; let mut es_target = None; let mut engine_targets = EngineTargets::default(); diff --git a/crates/oxc_transformer/src/options/es_target.rs b/crates/oxc_transformer/src/options/es_target.rs index e067ddf7f84783..2ac1982d529732 100644 --- a/crates/oxc_transformer/src/options/es_target.rs +++ b/crates/oxc_transformer/src/options/es_target.rs @@ -1,72 +1,13 @@ -use std::{fmt, str::FromStr}; - use browserslist::Version; -use cow_utils::CowUtils; - -#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] -pub enum ESTarget { - ES5, - ES2015, - ES2016, - ES2017, - ES2018, - ES2019, - ES2020, - ES2021, - ES2022, - ES2023, - ES2024, - ES2025, - #[default] - ESNext, -} - -impl FromStr for ESTarget { - type Err = String; - fn from_str(s: &str) -> Result { - match s.cow_to_lowercase().as_ref() { - "es5" => Ok(Self::ES5), - "es6" | "es2015" => Ok(Self::ES2015), - "es2016" => Ok(Self::ES2016), - "es2017" => Ok(Self::ES2017), - "es2018" => Ok(Self::ES2018), - "es2019" => Ok(Self::ES2019), - "es2020" => Ok(Self::ES2020), - "es2021" => Ok(Self::ES2021), - "es2022" => Ok(Self::ES2022), - "es2023" => Ok(Self::ES2023), - "es2024" => Ok(Self::ES2024), - "es2025" => Ok(Self::ES2025), - "esnext" => Ok(Self::ESNext), - _ => Err(format!("Invalid target \"{s}\".")), - } - } -} +pub use oxc_syntax::es_target::ESTarget; -impl fmt::Display for ESTarget { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = match self { - Self::ES5 => "es5", - Self::ES2015 => "es2015", - Self::ES2016 => "es2016", - Self::ES2017 => "es2017", - Self::ES2018 => "es2018", - Self::ES2019 => "es2019", - Self::ES2020 => "es2020", - Self::ES2021 => "es2021", - Self::ES2022 => "es2022", - Self::ES2023 => "es2023", - Self::ES2024 => "es2024", - Self::ES2025 => "es2025", - Self::ESNext => "esnext", - }; - write!(f, "{s}",) - } +pub trait ESVersion { + fn version(&self) -> Version; } -impl ESTarget { - pub fn version(&self) -> Version { +impl ESVersion for ESTarget { + fn version(&self) -> Version { match self { Self::ES5 => Version(5, 0, 0), Self::ES2015 => Version(2015, 0, 0), diff --git a/crates/oxc_transformer/src/options/mod.rs b/crates/oxc_transformer/src/options/mod.rs index 82a40e53e91534..647125b057db6e 100644 --- a/crates/oxc_transformer/src/options/mod.rs +++ b/crates/oxc_transformer/src/options/mod.rs @@ -121,6 +121,7 @@ impl TransformOptions { impl From for TransformOptions { fn from(target: ESTarget) -> Self { + use crate::options::es_target::ESVersion; let mut engine_targets = EngineTargets::default(); engine_targets.insert(Engine::Es, target.version()); let mut env = EnvOptions::from(engine_targets); diff --git a/crates/oxc_wasm/src/lib.rs b/crates/oxc_wasm/src/lib.rs index a43d0a7495c32c..23c77bba88ebc1 100644 --- a/crates/oxc_wasm/src/lib.rs +++ b/crates/oxc_wasm/src/lib.rs @@ -275,6 +275,7 @@ impl Oxc { CompressOptions { drop_console: compress_options.drop_console, drop_debugger: compress_options.drop_debugger, + ..CompressOptions::all_false() } } else { CompressOptions::all_false() From 8149e34bf0dae739686281be1ce6f8ed68470338 Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Sun, 29 Dec 2024 12:27:33 +0000 Subject: [PATCH 046/162] feat(minifier): optional catch binding when es target >= es2019 (#8180) --- crates/oxc_minifier/src/ast_passes/mod.rs | 16 +++++- .../peephole_substitute_alternate_syntax.rs | 49 ++++++++++++++++--- crates/oxc_minifier/src/compressor.rs | 6 +-- tasks/minsize/minsize.snap | 20 ++++---- 4 files changed, 70 insertions(+), 21 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/mod.rs b/crates/oxc_minifier/src/ast_passes/mod.rs index 447f56f5365b20..3c781f838d43d4 100644 --- a/crates/oxc_minifier/src/ast_passes/mod.rs +++ b/crates/oxc_minifier/src/ast_passes/mod.rs @@ -24,6 +24,8 @@ pub use peephole_substitute_alternate_syntax::PeepholeSubstituteAlternateSyntax; pub use remove_syntax::RemoveSyntax; pub use statement_fusion::StatementFusion; +use crate::CompressOptions; + pub trait CompressorPass<'a>: Traverse<'a> { fn build(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>); } @@ -76,7 +78,7 @@ pub struct LatePeepholeOptimizations { } impl LatePeepholeOptimizations { - pub fn new() -> Self { + pub fn new(options: CompressOptions) -> Self { let in_fixed_loop = true; Self { x0_statement_fusion: StatementFusion::new(), @@ -84,6 +86,7 @@ impl LatePeepholeOptimizations { x2_peephole_remove_dead_code: PeepholeRemoveDeadCode::new(), x3_peephole_minimize_conditions: PeepholeMinimizeConditions::new(in_fixed_loop), x4_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax::new( + options, in_fixed_loop, ), x5_peephole_replace_known_methods: PeepholeReplaceKnownMethods::new(), @@ -194,6 +197,10 @@ impl<'a> Traverse<'a> for LatePeepholeOptimizations { fn exit_property_key(&mut self, key: &mut PropertyKey<'a>, ctx: &mut TraverseCtx<'a>) { self.x4_peephole_substitute_alternate_syntax.exit_property_key(key, ctx); } + + fn exit_catch_clause(&mut self, catch: &mut CatchClause<'a>, ctx: &mut TraverseCtx<'a>) { + self.x4_peephole_substitute_alternate_syntax.exit_catch_clause(catch, ctx); + } } // See `createPeepholeOptimizationsPass` @@ -207,11 +214,12 @@ pub struct PeepholeOptimizations { } impl PeepholeOptimizations { - pub fn new() -> Self { + pub fn new(options: CompressOptions) -> Self { let in_fixed_loop = false; Self { x2_peephole_minimize_conditions: PeepholeMinimizeConditions::new(in_fixed_loop), x3_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax::new( + options, in_fixed_loop, ), x4_peephole_replace_known_methods: PeepholeReplaceKnownMethods::new(), @@ -273,6 +281,10 @@ impl<'a> Traverse<'a> for PeepholeOptimizations { fn exit_property_key(&mut self, key: &mut PropertyKey<'a>, ctx: &mut TraverseCtx<'a>) { self.x3_peephole_substitute_alternate_syntax.exit_property_key(key, ctx); } + + fn exit_catch_clause(&mut self, catch: &mut CatchClause<'a>, ctx: &mut TraverseCtx<'a>) { + self.x3_peephole_substitute_alternate_syntax.exit_catch_clause(catch, ctx); + } } pub struct DeadCodeElimination { diff --git a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs index 2693cb60876fa6..9fc2796662fc14 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs @@ -4,19 +4,21 @@ use oxc_ecmascript::{ToInt32, ToJsString}; use oxc_semantic::IsGlobalReference; use oxc_span::{GetSpan, SPAN}; use oxc_syntax::{ + es_target::ESTarget, identifier::is_identifier_name, number::NumberBase, operator::{BinaryOperator, UnaryOperator}, }; use oxc_traverse::{traverse_mut_with_ctx, Ancestor, ReusableTraverseCtx, Traverse, TraverseCtx}; -use crate::{node_util::Ctx, CompressorPass}; +use crate::{node_util::Ctx, CompressOptions, CompressorPass}; /// A peephole optimization that minimizes code by simplifying conditional /// expressions, replacing IFs with HOOKs, replacing object constructors /// with literals, and simplifying returns. /// pub struct PeepholeSubstituteAlternateSyntax { + options: CompressOptions, /// Do not compress syntaxes that are hard to analyze inside the fixed loop. /// e.g. Do not compress `undefined -> void 0`, `true` -> `!0`. /// Opposite of `late` in Closure Compiler. @@ -45,6 +47,10 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax { self.compress_return_statement(stmt); } + fn exit_catch_clause(&mut self, catch: &mut CatchClause<'a>, _ctx: &mut TraverseCtx<'a>) { + self.compress_catch_clause(catch); + } + fn exit_variable_declaration( &mut self, decl: &mut VariableDeclaration<'a>, @@ -118,8 +124,8 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax { } impl<'a, 'b> PeepholeSubstituteAlternateSyntax { - pub fn new(in_fixed_loop: bool) -> Self { - Self { in_fixed_loop, in_define_export: false, changed: false } + pub fn new(options: CompressOptions, in_fixed_loop: bool) -> Self { + Self { options, in_fixed_loop, in_define_export: false, changed: false } } /* Utilities */ @@ -767,6 +773,20 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { ctx.ast.alloc_static_member_expression(e.span, object, property, false), )) } + + fn compress_catch_clause(&mut self, catch: &mut CatchClause<'a>) { + if catch.body.body.is_empty() + && !self.in_fixed_loop + && self.options.target >= ESTarget::ES2019 + { + if let Some(param) = &catch.param { + if param.pattern.kind.is_binding_identifier() { + catch.param = None; + self.changed = true; + } + }; + } + } } /// Port from @@ -774,11 +794,12 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { mod test { use oxc_allocator::Allocator; - use crate::tester; + use crate::{tester, CompressOptions}; fn test(source_text: &str, expected: &str) { let allocator = Allocator::default(); - let mut pass = super::PeepholeSubstituteAlternateSyntax::new(false); + let options = CompressOptions::default(); + let mut pass = super::PeepholeSubstituteAlternateSyntax::new(options, false); tester::test(&allocator, source_text, expected, &mut pass); } @@ -802,7 +823,7 @@ mod test { test("var x = undefined", "var x"); test_same("var undefined = 1;function f() {var undefined=2;var x;}"); test("function f(undefined) {}", "function f(undefined){}"); - test("try {} catch(undefined) {}", "try{}catch(undefined){}"); + test("try {} catch(undefined) {foo}", "try{}catch(undefined){foo}"); test("for (undefined in {}) {}", "for(undefined in {}){}"); test("undefined++;", "undefined++"); test("undefined += undefined;", "undefined+=void 0"); @@ -1271,6 +1292,22 @@ mod test { test_same("x['😊']"); } + #[test] + fn optional_catch_binding() { + test("try {} catch(e) {}", "try {} catch {}"); + test_same("try {} catch([e]) {}"); + test_same("try {} catch({e}) {}"); + + let allocator = Allocator::default(); + let options = CompressOptions { + target: oxc_syntax::es_target::ESTarget::ES2018, + ..CompressOptions::default() + }; + let mut pass = super::PeepholeSubstituteAlternateSyntax::new(options, false); + let code = "try {} catch(e) {}"; + tester::test(&allocator, code, code, &mut pass); + } + // ---------- /// Port from diff --git a/crates/oxc_minifier/src/compressor.rs b/crates/oxc_minifier/src/compressor.rs index e284ee5a66368a..83c7213589eecf 100644 --- a/crates/oxc_minifier/src/compressor.rs +++ b/crates/oxc_minifier/src/compressor.rs @@ -36,10 +36,10 @@ impl<'a> Compressor<'a> { let mut ctx = ReusableTraverseCtx::new(scopes, symbols, self.allocator); RemoveSyntax::new(self.options).build(program, &mut ctx); Normalize::new().build(program, &mut ctx); - PeepholeOptimizations::new().build(program, &mut ctx); + PeepholeOptimizations::new(self.options).build(program, &mut ctx); CollapsePass::new().build(program, &mut ctx); - LatePeepholeOptimizations::new().run_in_loop(program, &mut ctx); - PeepholeOptimizations::new().build(program, &mut ctx); + LatePeepholeOptimizations::new(self.options).run_in_loop(program, &mut ctx); + PeepholeOptimizations::new(self.options).build(program, &mut ctx); } pub fn dead_code_elimination(self, program: &mut Program<'a>) { diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index fcc5fd5eb42f56..93fdf6ac1f50f9 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -1,27 +1,27 @@ | Oxc | ESBuild | Oxc | ESBuild | Original | minified | minified | gzip | gzip | Fixture ------------------------------------------------------------------------------------- -72.14 kB | 23.72 kB | 23.70 kB | 8.62 kB | 8.54 kB | react.development.js +72.14 kB | 23.71 kB | 23.70 kB | 8.62 kB | 8.54 kB | react.development.js 173.90 kB | 60.15 kB | 59.82 kB | 19.50 kB | 19.33 kB | moment.js -287.63 kB | 90.57 kB | 90.07 kB | 32.19 kB | 31.95 kB | jquery.js +287.63 kB | 90.56 kB | 90.07 kB | 32.18 kB | 31.95 kB | jquery.js -342.15 kB | 118.54 kB | 118.14 kB | 44.56 kB | 44.37 kB | vue.js +342.15 kB | 118.53 kB | 118.14 kB | 44.56 kB | 44.37 kB | vue.js -544.10 kB | 72.00 kB | 72.48 kB | 26.20 kB | 26.20 kB | lodash.js +544.10 kB | 71.98 kB | 72.48 kB | 26.19 kB | 26.20 kB | lodash.js 555.77 kB | 273.88 kB | 270.13 kB | 91.19 kB | 90.80 kB | d3.js -1.01 MB | 461.00 kB | 458.89 kB | 126.92 kB | 126.71 kB | bundle.min.js +1.01 MB | 460.99 kB | 458.89 kB | 126.92 kB | 126.71 kB | bundle.min.js -1.25 MB | 653.54 kB | 646.76 kB | 164.05 kB | 163.73 kB | three.js +1.25 MB | 653.54 kB | 646.76 kB | 164.04 kB | 163.73 kB | three.js -2.14 MB | 727.91 kB | 724.14 kB | 180.39 kB | 181.07 kB | victory.js +2.14 MB | 727.90 kB | 724.14 kB | 180.39 kB | 181.07 kB | victory.js -3.20 MB | 1.01 MB | 1.01 MB | 332.27 kB | 331.56 kB | echarts.js +3.20 MB | 1.01 MB | 1.01 MB | 332.29 kB | 331.56 kB | echarts.js -6.69 MB | 2.32 MB | 2.31 MB | 493.25 kB | 488.28 kB | antd.js +6.69 MB | 2.32 MB | 2.31 MB | 493.24 kB | 488.28 kB | antd.js -10.95 MB | 3.51 MB | 3.49 MB | 910.90 kB | 915.50 kB | typescript.js +10.95 MB | 3.51 MB | 3.49 MB | 910.88 kB | 915.50 kB | typescript.js From bf9cafea90bb7da8a98c70f34c8729c50e14fc88 Mon Sep 17 00:00:00 2001 From: Boshen Date: Sun, 29 Dec 2024 20:49:39 +0800 Subject: [PATCH 047/162] refactor(minifier): clean up `peephole_substitute_alternate_syntax` a little bit --- .../peephole_substitute_alternate_syntax.rs | 348 +++++++++--------- 1 file changed, 167 insertions(+), 181 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs index 9fc2796662fc14..f1d2a26c2d5f53 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs @@ -128,23 +128,6 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { Self { options, in_fixed_loop, in_define_export: false, changed: false } } - /* Utilities */ - - /// Transforms `undefined` => `void 0` - fn try_compress_undefined( - &self, - ident: &IdentifierReference<'a>, - ctx: Ctx<'a, 'b>, - ) -> Option> { - if self.in_fixed_loop { - return None; - } - if !ctx.is_identifier_undefined(ident) { - return None; - } - Some(ctx.ast.void_0(ident.span)) - } - /// Test `Object.defineProperty(exports, ...)` fn is_object_define_property_exports(call_expr: &CallExpression<'a>) -> bool { let Some(Argument::Identifier(ident)) = call_expr.arguments.first() else { return false }; @@ -164,20 +147,20 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { false } - /* Statements */ - - // /// 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 = ctx.ast.expression_this(SPAN); - // let test = std::mem::replace(&mut while_stmt.test, dummy_test); - // let body = ctx.ast.move_statement(&mut while_stmt.body); - // *stmt = ctx.ast.statement_for(SPAN, None, Some(test), None, body); - // } - // } - - /* Expressions */ + /// Transforms `undefined` => `void 0` + fn try_compress_undefined( + &self, + ident: &IdentifierReference<'a>, + ctx: Ctx<'a, 'b>, + ) -> Option> { + if self.in_fixed_loop { + return None; + } + if !ctx.is_identifier_undefined(ident) { + return None; + } + Some(ctx.ast.void_0(ident.span)) + } /// Transforms boolean expression `true` => `!0` `false` => `!1`. /// Do not compress `true` in `Object.defineProperty(exports, 'Foo', {enumerable: true, ...})`. @@ -398,11 +381,9 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { if left_id_ref.name != right_id_ref.name { return None; } - let left_id_expr = ctx.ast.expression_identifier_reference(left_id_expr_span, left_id_ref.name); let null_expr = ctx.ast.expression_null_literal(null_expr_span); - Some(ctx.ast.expression_binary(span, left_id_expr, replace_op, null_expr)) } @@ -1308,162 +1289,166 @@ mod test { tester::test(&allocator, code, code, &mut pass); } - // ---------- - /// Port from - #[test] - fn test_convert_to_dotted_properties_convert() { - test("a['p']", "a.p"); - test("a['_p_']", "a._p_"); - test("a['_']", "a._"); - test("a['$']", "a.$"); - test("a.b.c['p']", "a.b.c.p"); - test("a.b['c'].p", "a.b.c.p"); - test("a['p']();", "a.p();"); - test("a()['p']", "a().p"); - // ASCII in Unicode is always safe. - test("a['\\u0041A']", "a.AA"); - // This is safe for ES5+. (keywords cannot be used for ES3) - test("a['default']", "a.default"); - // This is safe for ES2015+. (\u1d17 was introduced in Unicode 3.1, ES2015+ uses Unicode 5.1+) - test("a['\\u1d17A']", "a.\u{1d17}A"); - // Latin capital N with tilde - this is safe for ES3+. - test("a['\\u00d1StuffAfter']", "a.\u{00d1}StuffAfter"); - } + mod convert_to_dotted_properties { + use super::{test, test_same}; + + #[test] + fn test_convert_to_dotted_properties_convert() { + test("a['p']", "a.p"); + test("a['_p_']", "a._p_"); + test("a['_']", "a._"); + test("a['$']", "a.$"); + test("a.b.c['p']", "a.b.c.p"); + test("a.b['c'].p", "a.b.c.p"); + test("a['p']();", "a.p();"); + test("a()['p']", "a().p"); + // ASCII in Unicode is always safe. + test("a['\\u0041A']", "a.AA"); + // This is safe for ES5+. (keywords cannot be used for ES3) + test("a['default']", "a.default"); + // This is safe for ES2015+. (\u1d17 was introduced in Unicode 3.1, ES2015+ uses Unicode 5.1+) + test("a['\\u1d17A']", "a.\u{1d17}A"); + // Latin capital N with tilde - this is safe for ES3+. + test("a['\\u00d1StuffAfter']", "a.\u{00d1}StuffAfter"); + } - #[test] - fn test_convert_to_dotted_properties_do_not_convert() { - test_same("a[0]"); - test_same("a['']"); - test_same("a[' ']"); - test_same("a[',']"); - test_same("a[';']"); - test_same("a[':']"); - test_same("a['.']"); - test_same("a['0']"); - test_same("a['p ']"); - test_same("a['p' + '']"); - test_same("a[p]"); - test_same("a[P]"); - test_same("a[$]"); - test_same("a[p()]"); - // Ignorable control characters are ok in Java identifiers, but not in JS. - test_same("a['A\\u0004']"); - } + #[test] + fn test_convert_to_dotted_properties_do_not_convert() { + test_same("a[0]"); + test_same("a['']"); + test_same("a[' ']"); + test_same("a[',']"); + test_same("a[';']"); + test_same("a[':']"); + test_same("a['.']"); + test_same("a['0']"); + test_same("a['p ']"); + test_same("a['p' + '']"); + test_same("a[p]"); + test_same("a[P]"); + test_same("a[$]"); + test_same("a[p()]"); + // Ignorable control characters are ok in Java identifiers, but not in JS. + test_same("a['A\\u0004']"); + } - #[test] - fn test_convert_to_dotted_properties_already_dotted() { - test_same("a.b"); - test_same("var a = {b: 0};"); - } + #[test] + fn test_convert_to_dotted_properties_already_dotted() { + test_same("a.b"); + test_same("var a = {b: 0};"); + } - #[test] - fn test_convert_to_dotted_properties_quoted_props() { - test_same("({'':0})"); - test_same("({'1.0':0})"); - test("({'\\u1d17A':0})", "({ \u{1d17}A: 0 })"); - test_same("({'a\\u0004b':0})"); - } + #[test] + fn test_convert_to_dotted_properties_quoted_props() { + test_same("({'':0})"); + test_same("({'1.0':0})"); + test("({'\\u1d17A':0})", "({ \u{1d17}A: 0 })"); + test_same("({'a\\u0004b':0})"); + } - #[test] - fn test5746867() { - test_same("var a = { '$\\\\' : 5 };"); - test_same("var a = { 'x\\\\u0041$\\\\' : 5 };"); - } + #[test] + fn test5746867() { + test_same("var a = { '$\\\\' : 5 };"); + test_same("var a = { 'x\\\\u0041$\\\\' : 5 };"); + } - #[test] - #[ignore] - fn test_convert_to_dotted_properties_optional_chaining() { - test("data?.['name']", "data?.name"); - test("data?.['name']?.['first']", "data?.name?.first"); - test("data['name']?.['first']", "data.name?.first"); - test_same("a?.[0]"); - test_same("a?.['']"); - test_same("a?.[' ']"); - test_same("a?.[',']"); - test_same("a?.[';']"); - test_same("a?.[':']"); - test_same("a?.['.']"); - test_same("a?.['0']"); - test_same("a?.['p ']"); - test_same("a?.['p' + '']"); - test_same("a?.[p]"); - test_same("a?.[P]"); - test_same("a?.[$]"); - test_same("a?.[p()]"); - // This is safe for ES5+. (keywords cannot be used for ES3) - test("a?.['default']", "a?.default"); - } + #[test] + #[ignore] + fn test_convert_to_dotted_properties_optional_chaining() { + test("data?.['name']", "data?.name"); + test("data?.['name']?.['first']", "data?.name?.first"); + test("data['name']?.['first']", "data.name?.first"); + test_same("a?.[0]"); + test_same("a?.['']"); + test_same("a?.[' ']"); + test_same("a?.[',']"); + test_same("a?.[';']"); + test_same("a?.[':']"); + test_same("a?.['.']"); + test_same("a?.['0']"); + test_same("a?.['p ']"); + test_same("a?.['p' + '']"); + test_same("a?.[p]"); + test_same("a?.[P]"); + test_same("a?.[$]"); + test_same("a?.[p()]"); + // This is safe for ES5+. (keywords cannot be used for ES3) + test("a?.['default']", "a?.default"); + } - #[test] - #[ignore] - fn test_convert_to_dotted_properties_computed_property_or_field() { - test("const test1 = {['prop1']:87};", "const test1 = {prop1:87};"); - test( - "const test1 = {['prop1']:87,['prop2']:bg,['prop3']:'hfd'};", - "const test1 = {prop1:87,prop2:bg,prop3:'hfd'};", - ); - test( - "o = {['x']: async function(x) { return await x + 1; }};", - "o = {x:async function (x) { return await x + 1; }};", - ); - test("o = {['x']: function*(x) {}};", "o = {x: function*(x) {}};"); - test( - "o = {['x']: async function*(x) { return await x + 1; }};", - "o = {x:async function*(x) { return await x + 1; }};", - ); - test("class C {'x' = 0; ['y'] = 1;}", "class C { x= 0;y= 1;}"); - test("class C {'m'() {} }", "class C {m() {}}"); + #[test] + #[ignore] + fn test_convert_to_dotted_properties_computed_property_or_field() { + test("const test1 = {['prop1']:87};", "const test1 = {prop1:87};"); + test( + "const test1 = {['prop1']:87,['prop2']:bg,['prop3']:'hfd'};", + "const test1 = {prop1:87,prop2:bg,prop3:'hfd'};", + ); + test( + "o = {['x']: async function(x) { return await x + 1; }};", + "o = {x:async function (x) { return await x + 1; }};", + ); + test("o = {['x']: function*(x) {}};", "o = {x: function*(x) {}};"); + test( + "o = {['x']: async function*(x) { return await x + 1; }};", + "o = {x:async function*(x) { return await x + 1; }};", + ); + test("class C {'x' = 0; ['y'] = 1;}", "class C { x= 0;y= 1;}"); + test("class C {'m'() {} }", "class C {m() {}}"); - test("const o = {'b'() {}, ['c']() {}};", "const o = {b: function() {}, c:function(){}};"); - test("o = {['x']: () => this};", "o = {x: () => this};"); + test( + "const o = {'b'() {}, ['c']() {}};", + "const o = {b: function() {}, c:function(){}};", + ); + test("o = {['x']: () => this};", "o = {x: () => this};"); - test("const o = {get ['d']() {}};", "const o = {get d() {}};"); - test("const o = { set ['e'](x) {}};", "const o = { set e(x) {}};"); - test( - "class C {'m'() {} ['n']() {} 'x' = 0; ['y'] = 1;}", - "class C {m() {} n() {} x= 0;y= 1;}", - ); - test( - "const o = { get ['d']() {}, set ['e'](x) {}};", - "const o = {get d() {}, set e(x){}};", - ); - test( - "const o = {['a']: 1,'b'() {}, ['c']() {}, get ['d']() {}, set ['e'](x) {}};", - "const o = {a: 1,b: function() {}, c: function() {}, get d() {}, set e(x) {}};", - ); + test("const o = {get ['d']() {}};", "const o = {get d() {}};"); + test("const o = { set ['e'](x) {}};", "const o = { set e(x) {}};"); + test( + "class C {'m'() {} ['n']() {} 'x' = 0; ['y'] = 1;}", + "class C {m() {} n() {} x= 0;y= 1;}", + ); + test( + "const o = { get ['d']() {}, set ['e'](x) {}};", + "const o = {get d() {}, set e(x){}};", + ); + test( + "const o = {['a']: 1,'b'() {}, ['c']() {}, get ['d']() {}, set ['e'](x) {}};", + "const o = {a: 1,b: function() {}, c: function() {}, get d() {}, set e(x) {}};", + ); - // test static keyword - test( - r" + // test static keyword + test( + r" class C { 'm'(){} ['n'](){} static 'x' = 0; static ['y'] = 1;} ", - r" + r" class C { m(){} n(){} static x = 0; static y= 1;} ", - ); - test( - r" + ); + test( + r" window['MyClass'] = class { static ['Register'](){} }; ", - r" + r" window.MyClass = class { static Register(){} }; ", - ); - test( - r" + ); + test( + r" class C { 'method'(){} async ['method1'](){} @@ -1472,7 +1457,7 @@ mod test { static async ['smethod1'](){} static *['smethod2'](){}} ", - r" + r" class C { method(){} async method1(){} @@ -1481,37 +1466,38 @@ mod test { static async smethod1(){} static *smethod2(){}} ", - ); + ); - test_same("const o = {[fn()]: 0}"); - test_same("const test1 = {[0]:87};"); - test_same("const test1 = {['default']:87};"); - test_same("class C { ['constructor']() {} }"); - test_same("class C { ['constructor'] = 0 }"); - } + test_same("const o = {[fn()]: 0}"); + test_same("const test1 = {[0]:87};"); + test_same("const test1 = {['default']:87};"); + test_same("class C { ['constructor']() {} }"); + test_same("class C { ['constructor'] = 0 }"); + } - #[test] - #[ignore] - fn test_convert_to_dotted_properties_computed_property_with_default_value() { - test("const {['o']: o = 0} = {};", "const {o:o = 0} = {};"); - } + #[test] + #[ignore] + fn test_convert_to_dotted_properties_computed_property_with_default_value() { + test("const {['o']: o = 0} = {};", "const {o:o = 0} = {};"); + } - #[test] - #[ignore] - fn test_convert_to_dotted_properties_continue_optional_chaining() { - test("const opt1 = window?.a?.['b'];", "const opt1 = window?.a?.b;"); + #[test] + #[ignore] + fn test_convert_to_dotted_properties_continue_optional_chaining() { + test("const opt1 = window?.a?.['b'];", "const opt1 = window?.a?.b;"); - test("const opt2 = window?.a['b'];", "const opt2 = window?.a.b;"); - test( - r" + test("const opt2 = window?.a['b'];", "const opt2 = window?.a.b;"); + test( + r" const chain = window['a'].x.y.b.x.y['c'].x.y?.d.x.y['e'].x.y ['f-f'].x.y?.['g-g'].x.y?.['h'].x.y['i'].x.y; ", - r" + r" const chain = window.a.x.y.b.x.y.c.x.y?.d.x.y.e.x.y ['f-f'].x.y?.['g-g'].x.y?.h.x.y.i.x.y; ", - ); + ); + } } } From cfb51f255114b4de6d15be7eecbd106f3539871b Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Sun, 29 Dec 2024 14:57:20 +0000 Subject: [PATCH 048/162] refactor(minifier): fuse ast passes (#8184) --- crates/oxc_minifier/src/ast_passes/mod.rs | 140 ++-------------------- crates/oxc_minifier/src/compressor.rs | 11 +- tasks/minsize/minsize.snap | 2 +- 3 files changed, 16 insertions(+), 137 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/mod.rs b/crates/oxc_minifier/src/ast_passes/mod.rs index 3c781f838d43d4..e95def30417d2e 100644 --- a/crates/oxc_minifier/src/ast_passes/mod.rs +++ b/crates/oxc_minifier/src/ast_passes/mod.rs @@ -3,7 +3,7 @@ use oxc_ast::ast::*; use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx}; mod collapse_variable_declarations; -mod exploit_assigns; +// mod exploit_assigns; mod normalize; mod peephole_fold_constants; mod peephole_minimize_conditions; @@ -14,7 +14,7 @@ mod remove_syntax; mod statement_fusion; pub use collapse_variable_declarations::CollapseVariableDeclarations; -pub use exploit_assigns::ExploitAssigns; +// pub use exploit_assigns::ExploitAssigns; pub use normalize::Normalize; pub use peephole_fold_constants::PeepholeFoldConstants; pub use peephole_minimize_conditions::PeepholeMinimizeConditions; @@ -30,56 +30,20 @@ pub trait CompressorPass<'a>: Traverse<'a> { fn build(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>); } -// See `peepholeOptimizationsOnce` - -// For pass: -// ``` -// if (options.collapseVariableDeclarations) { -// passes.maybeAdd(exploitAssign); -// passes.maybeAdd(collapseVariableDeclarations); -// } -// ``` -pub struct CollapsePass { - _x0_exploit_assigns: ExploitAssigns, - x1_collapse_variable_declarations: CollapseVariableDeclarations, -} - -impl CollapsePass { - pub fn new() -> Self { - Self { - _x0_exploit_assigns: ExploitAssigns::new(), - x1_collapse_variable_declarations: CollapseVariableDeclarations::new(), - } - } -} - -impl<'a> CompressorPass<'a> for CollapsePass { - fn build(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>) { - traverse_mut_with_ctx(self, program, ctx); - } -} - -impl<'a> Traverse<'a> for CollapsePass { - fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) { - self.x1_collapse_variable_declarations.exit_statements(stmts, ctx); - } -} - // See `latePeepholeOptimizations` -pub struct LatePeepholeOptimizations { +pub struct PeepholeOptimizations { x0_statement_fusion: StatementFusion, x1_collapse_variable_declarations: CollapseVariableDeclarations, x2_peephole_remove_dead_code: PeepholeRemoveDeadCode, - // TODO: MinimizeExitPoints x3_peephole_minimize_conditions: PeepholeMinimizeConditions, x4_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax, x5_peephole_replace_known_methods: PeepholeReplaceKnownMethods, x6_peephole_fold_constants: PeepholeFoldConstants, + x7_collapse_variable_declarations: CollapseVariableDeclarations, } -impl LatePeepholeOptimizations { - pub fn new(options: CompressOptions) -> Self { - let in_fixed_loop = true; +impl PeepholeOptimizations { + pub fn new(in_fixed_loop: bool, options: CompressOptions) -> Self { Self { x0_statement_fusion: StatementFusion::new(), x1_collapse_variable_declarations: CollapseVariableDeclarations::new(), @@ -91,6 +55,7 @@ impl LatePeepholeOptimizations { ), x5_peephole_replace_known_methods: PeepholeReplaceKnownMethods::new(), x6_peephole_fold_constants: PeepholeFoldConstants::new(), + x7_collapse_variable_declarations: CollapseVariableDeclarations::new(), } } @@ -102,6 +67,7 @@ impl LatePeepholeOptimizations { self.x4_peephole_substitute_alternate_syntax.changed = false; self.x5_peephole_replace_known_methods.changed = false; self.x6_peephole_fold_constants.changed = false; + self.x7_collapse_variable_declarations.changed = false; } fn changed(&self) -> bool { @@ -112,6 +78,7 @@ impl LatePeepholeOptimizations { || self.x4_peephole_substitute_alternate_syntax.changed || self.x5_peephole_replace_known_methods.changed || self.x6_peephole_fold_constants.changed + || self.x7_collapse_variable_declarations.changed } pub fn run_in_loop<'a>( @@ -135,13 +102,13 @@ impl LatePeepholeOptimizations { } } -impl<'a> CompressorPass<'a> for LatePeepholeOptimizations { +impl<'a> CompressorPass<'a> for PeepholeOptimizations { fn build(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>) { traverse_mut_with_ctx(self, program, ctx); } } -impl<'a> Traverse<'a> for LatePeepholeOptimizations { +impl<'a> Traverse<'a> for PeepholeOptimizations { fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { self.x0_statement_fusion.exit_program(program, ctx); self.x2_peephole_remove_dead_code.exit_program(program, ctx); @@ -155,6 +122,7 @@ impl<'a> Traverse<'a> for LatePeepholeOptimizations { self.x1_collapse_variable_declarations.exit_statements(stmts, ctx); self.x2_peephole_remove_dead_code.exit_statements(stmts, ctx); self.x3_peephole_minimize_conditions.exit_statements(stmts, ctx); + self.x7_collapse_variable_declarations.exit_statements(stmts, ctx); } fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { @@ -203,90 +171,6 @@ impl<'a> Traverse<'a> for LatePeepholeOptimizations { } } -// See `createPeepholeOptimizationsPass` -pub struct PeepholeOptimizations { - // TODO: MinimizeExitPoints - x2_peephole_minimize_conditions: PeepholeMinimizeConditions, - x3_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax, - x4_peephole_replace_known_methods: PeepholeReplaceKnownMethods, - x5_peephole_remove_dead_code: PeepholeRemoveDeadCode, - x6_peephole_fold_constants: PeepholeFoldConstants, -} - -impl PeepholeOptimizations { - pub fn new(options: CompressOptions) -> Self { - let in_fixed_loop = false; - Self { - x2_peephole_minimize_conditions: PeepholeMinimizeConditions::new(in_fixed_loop), - x3_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax::new( - options, - in_fixed_loop, - ), - x4_peephole_replace_known_methods: PeepholeReplaceKnownMethods::new(), - x5_peephole_remove_dead_code: PeepholeRemoveDeadCode::new(), - x6_peephole_fold_constants: PeepholeFoldConstants::new(), - } - } -} - -impl<'a> CompressorPass<'a> for PeepholeOptimizations { - fn build(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>) { - traverse_mut_with_ctx(self, program, ctx); - } -} - -impl<'a> Traverse<'a> for PeepholeOptimizations { - fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { - self.x5_peephole_remove_dead_code.exit_program(program, ctx); - } - - fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) { - self.x2_peephole_minimize_conditions.exit_statements(stmts, ctx); - self.x5_peephole_remove_dead_code.exit_statements(stmts, ctx); - } - - fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { - self.x2_peephole_minimize_conditions.exit_statement(stmt, ctx); - self.x5_peephole_remove_dead_code.exit_statement(stmt, ctx); - } - - fn exit_return_statement(&mut self, stmt: &mut ReturnStatement<'a>, ctx: &mut TraverseCtx<'a>) { - self.x3_peephole_substitute_alternate_syntax.exit_return_statement(stmt, ctx); - } - - fn exit_variable_declaration( - &mut self, - decl: &mut VariableDeclaration<'a>, - ctx: &mut TraverseCtx<'a>, - ) { - self.x3_peephole_substitute_alternate_syntax.exit_variable_declaration(decl, ctx); - } - - fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { - self.x2_peephole_minimize_conditions.exit_expression(expr, ctx); - self.x3_peephole_substitute_alternate_syntax.exit_expression(expr, ctx); - self.x4_peephole_replace_known_methods.exit_expression(expr, ctx); - self.x5_peephole_remove_dead_code.exit_expression(expr, ctx); - self.x6_peephole_fold_constants.exit_expression(expr, ctx); - } - - fn enter_call_expression(&mut self, expr: &mut CallExpression<'a>, ctx: &mut TraverseCtx<'a>) { - self.x3_peephole_substitute_alternate_syntax.enter_call_expression(expr, ctx); - } - - fn exit_call_expression(&mut self, expr: &mut CallExpression<'a>, ctx: &mut TraverseCtx<'a>) { - self.x3_peephole_substitute_alternate_syntax.exit_call_expression(expr, ctx); - } - - fn exit_property_key(&mut self, key: &mut PropertyKey<'a>, ctx: &mut TraverseCtx<'a>) { - self.x3_peephole_substitute_alternate_syntax.exit_property_key(key, ctx); - } - - fn exit_catch_clause(&mut self, catch: &mut CatchClause<'a>, ctx: &mut TraverseCtx<'a>) { - self.x3_peephole_substitute_alternate_syntax.exit_catch_clause(catch, ctx); - } -} - pub struct DeadCodeElimination { x1_peephole_fold_constants: PeepholeFoldConstants, x2_peephole_remove_dead_code: PeepholeRemoveDeadCode, diff --git a/crates/oxc_minifier/src/compressor.rs b/crates/oxc_minifier/src/compressor.rs index 83c7213589eecf..3a0b8188852c36 100644 --- a/crates/oxc_minifier/src/compressor.rs +++ b/crates/oxc_minifier/src/compressor.rs @@ -4,10 +4,7 @@ use oxc_semantic::{ScopeTree, SemanticBuilder, SymbolTable}; use oxc_traverse::ReusableTraverseCtx; use crate::{ - ast_passes::{ - CollapsePass, DeadCodeElimination, LatePeepholeOptimizations, Normalize, - PeepholeOptimizations, RemoveSyntax, - }, + ast_passes::{DeadCodeElimination, Normalize, PeepholeOptimizations, RemoveSyntax}, CompressOptions, CompressorPass, }; @@ -36,10 +33,8 @@ impl<'a> Compressor<'a> { let mut ctx = ReusableTraverseCtx::new(scopes, symbols, self.allocator); RemoveSyntax::new(self.options).build(program, &mut ctx); Normalize::new().build(program, &mut ctx); - PeepholeOptimizations::new(self.options).build(program, &mut ctx); - CollapsePass::new().build(program, &mut ctx); - LatePeepholeOptimizations::new(self.options).run_in_loop(program, &mut ctx); - PeepholeOptimizations::new(self.options).build(program, &mut ctx); + PeepholeOptimizations::new(true, self.options).run_in_loop(program, &mut ctx); + PeepholeOptimizations::new(false, self.options).build(program, &mut ctx); } pub fn dead_code_elimination(self, program: &mut Program<'a>) { diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 93fdf6ac1f50f9..65e520ff85b201 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -11,7 +11,7 @@ Original | minified | minified | gzip | gzip | Fixture 544.10 kB | 71.98 kB | 72.48 kB | 26.19 kB | 26.20 kB | lodash.js -555.77 kB | 273.88 kB | 270.13 kB | 91.19 kB | 90.80 kB | d3.js +555.77 kB | 273.85 kB | 270.13 kB | 91.17 kB | 90.80 kB | d3.js 1.01 MB | 460.99 kB | 458.89 kB | 126.92 kB | 126.71 kB | bundle.min.js From f3050d4f310c748f1ffc7a4c5122c68a702dad8d Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Mon, 30 Dec 2024 02:10:33 +0900 Subject: [PATCH 049/162] fix(linter): exclude svelte files from `no_unused_vars` rule (#8170) --- apps/oxlint/src/lint.rs | 2 +- crates/oxc_linter/src/rules/eslint/no_unused_vars/mod.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/oxlint/src/lint.rs b/apps/oxlint/src/lint.rs index ffe842a8613742..0e1b55444292eb 100644 --- a/apps/oxlint/src/lint.rs +++ b/apps/oxlint/src/lint.rs @@ -628,7 +628,7 @@ mod test { let args = &["fixtures/svelte/debugger.svelte"]; let result = test(args); assert_eq!(result.number_of_files, 1); - assert_eq!(result.number_of_warnings, 2); + assert_eq!(result.number_of_warnings, 1); assert_eq!(result.number_of_errors, 0); } diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/mod.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/mod.rs index 6ae3bb022f9321..9a5f605ca6ec7b 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unused_vars/mod.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/mod.rs @@ -214,12 +214,12 @@ impl Rule for NoUnusedVars { } fn should_run(&self, ctx: &ContextHost) -> bool { - // ignore .d.ts and vue files. + // ignore .d.ts and vue/svelte files. // 1. declarations have side effects (they get merged together) - // 2. vue scripts declare variables that get used in the template, which + // 2. vue/svelte scripts declare variables that get used in the template, which // we can't detect !ctx.source_type().is_typescript_definition() - && !ctx.file_path().extension().is_some_and(|ext| ext == "vue") + && !ctx.file_path().extension().is_some_and(|ext| ext == "vue" || ext == "svelte") } } From a2adc4e832d6f5ca0dccff5899e71eb0e3353361 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Mon, 30 Dec 2024 02:12:51 +0900 Subject: [PATCH 050/162] chore(linter): move `no-negated-condition` to eslint (#8149) Moves `unicorn/no-negated-condition` to `eslint/no-negated-condition` Maps `unicorn/no-negated-condition`to `eslint/no-negated-condition` docs: https://eslint.org/docs/latest/rules/no-negated-condition --- crates/oxc_linter/src/config/rules.rs | 4 + crates/oxc_linter/src/rules.rs | 4 +- .../no_negated_condition.rs | 21 +++++ ....snap => eslint_no_negated_condition.snap} | 78 ++++++++++++++----- 4 files changed, 87 insertions(+), 20 deletions(-) rename crates/oxc_linter/src/rules/{unicorn => eslint}/no_negated_condition.rs (85%) rename crates/oxc_linter/src/snapshots/{unicorn_no_negated_condition.snap => eslint_no_negated_condition.snap} (57%) diff --git a/crates/oxc_linter/src/config/rules.rs b/crates/oxc_linter/src/config/rules.rs index e661f876f9fe92..251026b5f088c2 100644 --- a/crates/oxc_linter/src/config/rules.rs +++ b/crates/oxc_linter/src/config/rules.rs @@ -160,6 +160,10 @@ fn transform_rule_and_plugin_name<'a>( return (rule_name, "eslint"); } + if plugin_name == "unicorn" && rule_name == "no-negated-condition" { + return ("no-negated-condition", "eslint"); + } + (rule_name, plugin_name) } diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index a9b681dae87966..9a9df738ed5a6d 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -93,6 +93,7 @@ mod eslint { pub mod no_loss_of_precision; pub mod no_magic_numbers; pub mod no_multi_str; + pub mod no_negated_condition; pub mod no_nested_ternary; pub mod no_new; pub mod no_new_func; @@ -321,7 +322,6 @@ mod unicorn { pub mod no_length_as_slice_end; pub mod no_lonely_if; pub mod no_magic_array_flat_depth; - pub mod no_negated_condition; pub mod no_negation_in_equality_check; pub mod no_nested_ternary; pub mod no_new_array; @@ -588,6 +588,7 @@ oxc_macros::declare_all_lint_rules! { eslint::no_label_var, eslint::no_loss_of_precision, eslint::no_magic_numbers, + eslint::no_negated_condition, eslint::no_multi_str, eslint::no_new_func, eslint::no_new_native_nonconstructor, @@ -920,7 +921,6 @@ oxc_macros::declare_all_lint_rules! { unicorn::no_length_as_slice_end, unicorn::no_lonely_if, unicorn::no_magic_array_flat_depth, - unicorn::no_negated_condition, unicorn::no_negation_in_equality_check, unicorn::no_nested_ternary, unicorn::no_new_array, diff --git a/crates/oxc_linter/src/rules/unicorn/no_negated_condition.rs b/crates/oxc_linter/src/rules/eslint/no_negated_condition.rs similarity index 85% rename from crates/oxc_linter/src/rules/unicorn/no_negated_condition.rs rename to crates/oxc_linter/src/rules/eslint/no_negated_condition.rs index 2786b86798af2b..60ffa1770a1693 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_negated_condition.rs +++ b/crates/oxc_linter/src/rules/eslint/no_negated_condition.rs @@ -106,6 +106,20 @@ fn test() { use crate::tester::Tester; let pass = vec![ + "if (a) {}", + "if (a) {} else {}", + "if (!a) {}", + "if (!a) {} else if (b) {}", + "if (!a) {} else if (b) {} else {}", + "if (a == b) {}", + "if (a == b) {} else {}", + "if (a != b) {}", + "if (a != b) {} else if (b) {}", + "if (a != b) {} else if (b) {} else {}", + "if (a !== b) {}", + "if (a === b) {} else {}", + "a ? b : c", + // Test cases from eslint-plugin-unicorn r"if (a) {}", r"if (a) {} else {}", r"if (!a) {}", @@ -122,6 +136,13 @@ fn test() { ]; let fail = vec![ + "if (!a) {;} else {;}", + "if (a != b) {;} else {;}", + "if (a !== b) {;} else {;}", + "!a ? b : c", + "a != b ? c : d", + "a !== b ? c : d", + // Test cases from eslint-plugin-unicorn r"if (!a) {;} else {;}", r"if (a != b) {;} else {;}", r"if (a !== b) {;} else {;}", diff --git a/crates/oxc_linter/src/snapshots/unicorn_no_negated_condition.snap b/crates/oxc_linter/src/snapshots/eslint_no_negated_condition.snap similarity index 57% rename from crates/oxc_linter/src/snapshots/unicorn_no_negated_condition.snap rename to crates/oxc_linter/src/snapshots/eslint_no_negated_condition.snap index 97873e2abaf6cf..88967b97f4f880 100644 --- a/crates/oxc_linter/src/snapshots/unicorn_no_negated_condition.snap +++ b/crates/oxc_linter/src/snapshots/eslint_no_negated_condition.snap @@ -2,126 +2,168 @@ source: crates/oxc_linter/src/tester.rs snapshot_kind: text --- - ⚠ eslint-plugin-unicorn(no-negated-condition): Unexpected negated condition. + ⚠ eslint(no-negated-condition): Unexpected negated condition. ╭─[no_negated_condition.tsx:1:5] 1 │ if (!a) {;} else {;} · ── ╰──── help: Remove the negation operator and switch the consequent and alternate branches. - ⚠ eslint-plugin-unicorn(no-negated-condition): Unexpected negated condition. + ⚠ eslint(no-negated-condition): Unexpected negated condition. ╭─[no_negated_condition.tsx:1:5] 1 │ if (a != b) {;} else {;} · ────── ╰──── help: Remove the negation operator and switch the consequent and alternate branches. - ⚠ eslint-plugin-unicorn(no-negated-condition): Unexpected negated condition. + ⚠ eslint(no-negated-condition): Unexpected negated condition. ╭─[no_negated_condition.tsx:1:5] 1 │ if (a !== b) {;} else {;} · ─────── ╰──── help: Remove the negation operator and switch the consequent and alternate branches. - ⚠ eslint-plugin-unicorn(no-negated-condition): Unexpected negated condition. + ⚠ eslint(no-negated-condition): Unexpected negated condition. ╭─[no_negated_condition.tsx:1:1] 1 │ !a ? b : c · ── ╰──── help: Remove the negation operator and switch the consequent and alternate branches. - ⚠ eslint-plugin-unicorn(no-negated-condition): Unexpected negated condition. + ⚠ eslint(no-negated-condition): Unexpected negated condition. ╭─[no_negated_condition.tsx:1:1] 1 │ a != b ? c : d · ────── ╰──── help: Remove the negation operator and switch the consequent and alternate branches. - ⚠ eslint-plugin-unicorn(no-negated-condition): Unexpected negated condition. + ⚠ eslint(no-negated-condition): Unexpected negated condition. ╭─[no_negated_condition.tsx:1:1] 1 │ a !== b ? c : d · ─────── ╰──── help: Remove the negation operator and switch the consequent and alternate branches. - ⚠ eslint-plugin-unicorn(no-negated-condition): Unexpected negated condition. + ⚠ eslint(no-negated-condition): Unexpected negated condition. + ╭─[no_negated_condition.tsx:1:5] + 1 │ if (!a) {;} else {;} + · ── + ╰──── + help: Remove the negation operator and switch the consequent and alternate branches. + + ⚠ eslint(no-negated-condition): Unexpected negated condition. + ╭─[no_negated_condition.tsx:1:5] + 1 │ if (a != b) {;} else {;} + · ────── + ╰──── + help: Remove the negation operator and switch the consequent and alternate branches. + + ⚠ eslint(no-negated-condition): Unexpected negated condition. + ╭─[no_negated_condition.tsx:1:5] + 1 │ if (a !== b) {;} else {;} + · ─────── + ╰──── + help: Remove the negation operator and switch the consequent and alternate branches. + + ⚠ eslint(no-negated-condition): Unexpected negated condition. + ╭─[no_negated_condition.tsx:1:1] + 1 │ !a ? b : c + · ── + ╰──── + help: Remove the negation operator and switch the consequent and alternate branches. + + ⚠ eslint(no-negated-condition): Unexpected negated condition. + ╭─[no_negated_condition.tsx:1:1] + 1 │ a != b ? c : d + · ────── + ╰──── + help: Remove the negation operator and switch the consequent and alternate branches. + + ⚠ eslint(no-negated-condition): Unexpected negated condition. + ╭─[no_negated_condition.tsx:1:1] + 1 │ a !== b ? c : d + · ─────── + ╰──── + help: Remove the negation operator and switch the consequent and alternate branches. + + ⚠ eslint(no-negated-condition): Unexpected negated condition. ╭─[no_negated_condition.tsx:1:4] 1 │ (( !a )) ? b : c · ── ╰──── help: Remove the negation operator and switch the consequent and alternate branches. - ⚠ eslint-plugin-unicorn(no-negated-condition): Unexpected negated condition. + ⚠ eslint(no-negated-condition): Unexpected negated condition. ╭─[no_negated_condition.tsx:1:1] 1 │ !(( a )) ? b : c · ──────── ╰──── help: Remove the negation operator and switch the consequent and alternate branches. - ⚠ eslint-plugin-unicorn(no-negated-condition): Unexpected negated condition. + ⚠ eslint(no-negated-condition): Unexpected negated condition. ╭─[no_negated_condition.tsx:1:4] 1 │ if(!(( a ))) b(); else c(); · ──────── ╰──── help: Remove the negation operator and switch the consequent and alternate branches. - ⚠ eslint-plugin-unicorn(no-negated-condition): Unexpected negated condition. + ⚠ eslint(no-negated-condition): Unexpected negated condition. ╭─[no_negated_condition.tsx:1:7] 1 │ if((( !a ))) b(); else c(); · ── ╰──── help: Remove the negation operator and switch the consequent and alternate branches. - ⚠ eslint-plugin-unicorn(no-negated-condition): Unexpected negated condition. + ⚠ eslint(no-negated-condition): Unexpected negated condition. ╭─[no_negated_condition.tsx:1:21] 1 │ function a() {return!a ? b : c} · ── ╰──── help: Remove the negation operator and switch the consequent and alternate branches. - ⚠ eslint-plugin-unicorn(no-negated-condition): Unexpected negated condition. + ⚠ eslint(no-negated-condition): Unexpected negated condition. ╭─[no_negated_condition.tsx:1:21] 1 │ function a() {return!(( a )) ? b : c} · ──────── ╰──── help: Remove the negation operator and switch the consequent and alternate branches. - ⚠ eslint-plugin-unicorn(no-negated-condition): Unexpected negated condition. + ⚠ eslint(no-negated-condition): Unexpected negated condition. ╭─[no_negated_condition.tsx:1:1] 1 │ !a ? b : c ? d : e · ── ╰──── help: Remove the negation operator and switch the consequent and alternate branches. - ⚠ eslint-plugin-unicorn(no-negated-condition): Unexpected negated condition. + ⚠ eslint(no-negated-condition): Unexpected negated condition. ╭─[no_negated_condition.tsx:1:1] 1 │ !a ? b : (( c ? d : e )) · ── ╰──── help: Remove the negation operator and switch the consequent and alternate branches. - ⚠ eslint-plugin-unicorn(no-negated-condition): Unexpected negated condition. + ⚠ eslint(no-negated-condition): Unexpected negated condition. ╭─[no_negated_condition.tsx:1:4] 1 │ if(!a) b(); else c() · ── ╰──── help: Remove the negation operator and switch the consequent and alternate branches. - ⚠ eslint-plugin-unicorn(no-negated-condition): Unexpected negated condition. + ⚠ eslint(no-negated-condition): Unexpected negated condition. ╭─[no_negated_condition.tsx:1:4] 1 │ if(!a) {b()} else {c()} · ── ╰──── help: Remove the negation operator and switch the consequent and alternate branches. - ⚠ eslint-plugin-unicorn(no-negated-condition): Unexpected negated condition. + ⚠ eslint(no-negated-condition): Unexpected negated condition. ╭─[no_negated_condition.tsx:1:4] 1 │ if(!!a) b(); else c(); · ─── ╰──── help: Remove the negation operator and switch the consequent and alternate branches. - ⚠ eslint-plugin-unicorn(no-negated-condition): Unexpected negated condition. + ⚠ eslint(no-negated-condition): Unexpected negated condition. ╭─[no_negated_condition.tsx:1:2] 1 │ (!!a) ? b() : c(); · ─── From afc21a6071087422b64cef465a3d60e123cf2ef2 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Mon, 30 Dec 2024 02:13:45 +0900 Subject: [PATCH 051/162] feat(linter): implement `eslint/vars-on-top` (#8157) implement: https://eslint.org/docs/latest/rules/vars-on-top --- crates/oxc_linter/src/rules.rs | 2 + .../src/rules/eslint/vars_on_top.rs | 544 ++++++++++++++++++ .../src/snapshots/eslint_vars_on_top.snap | 232 ++++++++ 3 files changed, 778 insertions(+) create mode 100644 crates/oxc_linter/src/rules/eslint/vars_on_top.rs create mode 100644 crates/oxc_linter/src/snapshots/eslint_vars_on_top.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 9a9df738ed5a6d..2724a3eb142ea7 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -154,6 +154,7 @@ mod eslint { pub mod unicode_bom; pub mod use_isnan; pub mod valid_typeof; + pub mod vars_on_top; pub mod yoda; } @@ -647,6 +648,7 @@ oxc_macros::declare_all_lint_rules! { eslint::unicode_bom, eslint::use_isnan, eslint::valid_typeof, + eslint::vars_on_top, eslint::yoda, import::default, import::export, diff --git a/crates/oxc_linter/src/rules/eslint/vars_on_top.rs b/crates/oxc_linter/src/rules/eslint/vars_on_top.rs new file mode 100644 index 00000000000000..b2d4226262f69a --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/vars_on_top.rs @@ -0,0 +1,544 @@ +use oxc_ast::ast::{Declaration, Expression, Program, Statement, VariableDeclarationKind}; +use oxc_ast::AstKind; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::{GetSpan, Span}; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +fn vars_on_top_diagnostic(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("All 'var' declarations must be at the top of the function scope.") + .with_help("Consider moving this to the top of the functions scope or using let or const to declare this variable.") + .with_label(span) +} + +#[derive(Debug, Default, Clone)] +pub struct VarsOnTop; + +declare_oxc_lint!( + /// ### What it does + /// + /// Enforces that all `var` declarations are placed at the top of their containing scope. + /// + /// ### Why is this bad? + /// + /// In JavaScript, `var` declarations are hoisted to the top of their containing scope. Placing `var` declarations at the top explicitly improves code readability and maintainability by making the scope of variables clear. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```js + /// function doSomething() { + /// if (true) { + /// var first = true; + /// } + /// var second; + /// } + /// + /// function doSomethingElse() { + /// for (var i = 0; i < 10; i++) {} + /// } + /// + /// f(); + /// var a; + /// + /// class C { + /// static { + /// if (something) { + /// var a = true; + /// } + /// } + /// static { + /// f(); + /// var a; + /// } + /// } + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```js + /// function doSomething() { + /// var first; + /// var second; + /// if (true) { + /// first = true; + /// } + /// } + /// + /// function doSomethingElse() { + /// var i; + /// for (i = 0; i < 10; i++) {} + /// } + /// + /// var a; + /// f(); + /// + /// class C { + /// static { + /// var a; + /// if (something) { + /// a = true; + /// } + /// } + /// static { + /// var a; + /// f(); + /// } + /// } + /// ``` + VarsOnTop, + style, +); + +impl Rule for VarsOnTop { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::VariableDeclaration(declaration) = node.kind() else { + return; + }; + if declaration.kind != VariableDeclarationKind::Var { + return; + } + let Some(parent) = ctx.nodes().parent_node(node.id()) else { + return; + }; + + match parent.kind() { + AstKind::ExportNamedDeclaration(_) => { + if let Some(grand_parent) = ctx.nodes().parent_node(parent.id()) { + if let AstKind::Program(grand_parent) = grand_parent.kind() { + global_var_check(parent, grand_parent, ctx); + } + } + } + AstKind::Program(parent) => { + global_var_check(node, parent, ctx); + } + _ => block_scope_var_check(node, ctx), + } + } +} + +fn looks_like_directive(node: &Statement) -> bool { + matches!( + node, + Statement::ExpressionStatement(expr_stmt) if matches!( + &expr_stmt.expression, + Expression::StringLiteral(_) + ) + ) +} + +fn looks_like_import(node: &Statement) -> bool { + matches!(node, Statement::ImportDeclaration(_)) +} + +fn is_variable_declaration(node: &Statement) -> bool { + if matches!(node, Statement::VariableDeclaration(_)) { + return true; + } + + if let Statement::ExportNamedDeclaration(export) = node { + return matches!(export.declaration, Some(Declaration::VariableDeclaration(_))); + } + + false +} + +fn is_var_on_top(node: &AstNode, statements: &[Statement], ctx: &LintContext) -> bool { + let mut i = 0; + let len = statements.len(); + let parent = ctx.nodes().parent_node(node.id()); + + if let Some(parent) = parent { + if !matches!(parent.kind(), AstKind::StaticBlock(_)) { + while i < len { + if !looks_like_directive(&statements[i]) && !looks_like_import(&statements[i]) { + break; + } + i += 1; + } + } + } + + let node_span = node.span(); + while i < len { + if !is_variable_declaration(&statements[i]) { + return false; + } + let stmt_span = statements[i].span(); + + if stmt_span == node_span { + return true; + } + i += 1; + } + + false +} + +fn global_var_check(node: &AstNode, parent: &Program, ctx: &LintContext) { + if !is_var_on_top(node, &parent.body, ctx) { + ctx.diagnostic(vars_on_top_diagnostic(node.span())); + } +} + +fn block_scope_var_check(node: &AstNode, ctx: &LintContext) { + if let Some(parent) = ctx.nodes().parent_node(node.id()) { + match parent.kind() { + AstKind::BlockStatement(block) => { + if check_var_on_top_in_function_scope(node, &block.body, parent, ctx) { + return; + } + } + AstKind::FunctionBody(block) => { + if check_var_on_top_in_function_scope(node, &block.statements, parent, ctx) { + return; + } + } + AstKind::StaticBlock(block) => { + if is_var_on_top(node, &block.body, ctx) { + return; + } + } + _ => {} + } + } + ctx.diagnostic(vars_on_top_diagnostic(node.span())); +} + +fn check_var_on_top_in_function_scope( + node: &AstNode, + statements: &[Statement], + parent: &AstNode, + ctx: &LintContext, +) -> bool { + if let Some(grandparent) = ctx.nodes().parent_node(parent.id()) { + if matches!( + grandparent.kind(), + AstKind::Function(_) | AstKind::FunctionBody(_) | AstKind::ArrowFunctionExpression(_) + ) && is_var_on_top(node, statements, ctx) + { + return true; + } + } + + false +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + "var first = 0; + function foo() { + first = 2; + } + ", +"function foo() { + } + ", +"function foo() { + var first; + if (true) { + first = true; + } else { + first = 1; + } + } + ", +"function foo() { + var first; + var second = 1; + var third; + var fourth = 1, fifth, sixth = third; + var seventh; + if (true) { + third = true; + } + first = second; + } + ", +"function foo() { + var i; + for (i = 0; i < 10; i++) { + alert(i); + } + } + ", +"function foo() { + var outer; + function inner() { + var inner = 1; + var outer = inner; + } + outer = 1; + } + ", +"function foo() { + var first; + //Hello + var second = 1; + first = second; + } + ", +"function foo() { + var first; + /* + Hello Clarice + */ + var second = 1; + first = second; + } + ", +"function foo() { + var first; + var second = 1; + function bar(){ + var first; + first = 5; + } + first = second; + } + ", +"function foo() { + var first; + var second = 1; + function bar(){ + var third; + third = 5; + } + first = second; + } + ", +"function foo() { + var first; + var bar = function(){ + var third; + third = 5; + } + first = 5; + } + ", +"function foo() { + var first; + first.onclick(function(){ + var third; + third = 5; + }); + first = 5; + } + ", +"function foo() { + var i = 0; + for (let j = 0; j < 10; j++) { + alert(j); + } + i = i + 1; + }", // { "ecmaVersion": 6 }, +"'use strict'; var x; f();", +"'use strict'; 'directive'; var x; var y; f();", +"function f() { 'use strict'; var x; f(); }", +"function f() { 'use strict'; 'directive'; var x; var y; f(); }", +"import React from 'react'; var y; function f() { 'use strict'; var x; var y; f(); }", // { "ecmaVersion": 6, "sourceType": "module" }, +"'use strict'; import React from 'react'; var y; function f() { 'use strict'; var x; var y; f(); }", // { "ecmaVersion": 6, "sourceType": "module" }, +"import React from 'react'; 'use strict'; var y; function f() { 'use strict'; var x; var y; f(); }", // { "ecmaVersion": 6, "sourceType": "module" }, +"import * as foo from 'mod.js'; 'use strict'; var y; function f() { 'use strict'; var x; var y; f(); }", // { "ecmaVersion": 6, "sourceType": "module" }, +"import { square, diag } from 'lib'; 'use strict'; var y; function f() { 'use strict'; var x; var y; f(); }", // { "ecmaVersion": 6, "sourceType": "module" }, +"import { default as foo } from 'lib'; 'use strict'; var y; function f() { 'use strict'; var x; var y; f(); }", // { "ecmaVersion": 6, "sourceType": "module" }, +"import 'src/mylib'; 'use strict'; var y; function f() { 'use strict'; var x; var y; f(); }", // { "ecmaVersion": 6, "sourceType": "module" }, +"import theDefault, { named1, named2 } from 'src/mylib'; 'use strict'; var y; function f() { 'use strict'; var x; var y; f(); }", // { "ecmaVersion": 6, "sourceType": "module" }, +"export var x; + var y; + var z;", // { "ecmaVersion": 6, "sourceType": "module" }, +"var x; + export var y; + var z;", // { "ecmaVersion": 6, "sourceType": "module" }, +"var x; + var y; + export var z;", // { "ecmaVersion": 6, "sourceType": "module" }, +"class C { + static { + var x; + } + }", // { "ecmaVersion": 2022 }, +"class C { + static { + var x; + foo(); + } + }", // { "ecmaVersion": 2022 }, +"class C { + static { + var x; + var y; + } + }", // { "ecmaVersion": 2022 }, +"class C { + static { + var x; + var y; + foo(); + } + }", // { "ecmaVersion": 2022 }, +"class C { + static { + let x; + var y; + } + }", // { "ecmaVersion": 2022 }, +"class C { + static { + foo(); + let x; + } + }", // { "ecmaVersion": 2022 } + ]; + + let fail = vec![ + "var first = 0; + function foo() { + first = 2; + second = 2; + } + var second = 0;", + "function foo() { + var first; + first = 1; + first = 2; + first = 3; + first = 4; + var second = 1; + second = 2; + first = second; + }", + "function foo() { + var first; + if (true) { + var second = true; + } + first = second; + }", + "function foo() { + for (var i = 0; i < 10; i++) { + alert(i); + } + }", + "function foo() { + var first = 10; + var i; + for (i = 0; i < first; i ++) { + var second = i; + } + }", + "function foo() { + var first = 10; + var i; + switch (first) { + case 10: + var hello = 1; + break; + } + }", + "function foo() { + var first = 10; + var i; + try { + var hello = 1; + } catch (e) { + alert('error'); + } + }", + "function foo() { + var first = 10; + var i; + try { + asdf; + } catch (e) { + var hello = 1; + } + }", + "function foo() { + var first = 10; + while (first) { + var hello = 1; + } + }", + "function foo() { + var first = 10; + do { + var hello = 1; + } while (first == 10); + }", + "function foo() { + var first = [1,2,3]; + for (var item in first) { + item++; + } + }", + "function foo() { + var first = [1,2,3]; + var item; + for (item in first) { + var hello = item; + } + }", + "var foo = () => { + var first = [1,2,3]; + var item; + for (item in first) { + var hello = item; + } + }", // { "ecmaVersion": 6 }, + "'use strict'; 0; var x; f();", + "'use strict'; var x; 'directive'; var y; f();", + "function f() { 'use strict'; 0; var x; f(); }", + "function f() { 'use strict'; var x; 'directive'; var y; f(); }", + "export function f() {} + var x;", // { "ecmaVersion": 6, "sourceType": "module" }, + "var x; + export function f() {} + var y;", // { "ecmaVersion": 6, "sourceType": "module" }, + "import {foo} from 'foo'; + export {foo}; + var test = 1;", // { "ecmaVersion": 6, "sourceType": "module" }, + "export {foo} from 'foo'; + var test = 1;", // { "ecmaVersion": 6, "sourceType": "module" }, + "export * from 'foo'; + var test = 1;", // { "ecmaVersion": 6, "sourceType": "module" }, + "class C { + static { + foo(); + var x; + } + }", // { "ecmaVersion": 2022 }, + "class C { + static { + 'use strict'; + var x; + } + }", // { "ecmaVersion": 2022 }, + "class C { + static { + var x; + foo(); + var y; + } + }", // { "ecmaVersion": 2022 }, + "class C { + static { + if (foo) { + var x; + } + } + }", // { "ecmaVersion": 2022 }, + "class C { + static { + if (foo) + var x; + } + }", // { "ecmaVersion": 2022 } + ]; + + Tester::new(VarsOnTop::NAME, VarsOnTop::CATEGORY, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/eslint_vars_on_top.snap b/crates/oxc_linter/src/snapshots/eslint_vars_on_top.snap new file mode 100644 index 00000000000000..c78948a63eaefc --- /dev/null +++ b/crates/oxc_linter/src/snapshots/eslint_vars_on_top.snap @@ -0,0 +1,232 @@ +--- +source: crates/oxc_linter/src/tester.rs +snapshot_kind: text +--- + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:6:4] + 5 │ } + 6 │ var second = 0; + · ─────────────── + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:7:7] + 6 │ first = 4; + 7 │ var second = 1; + · ─────────────── + 8 │ second = 2; + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:4:11] + 3 │ if (true) { + 4 │ var second = true; + · ────────────────── + 5 │ } + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:2:12] + 1 │ function foo() { + 2 │ for (var i = 0; i < 10; i++) { + · ───────── + 3 │ alert(i); + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:5:11] + 4 │ for (i = 0; i < first; i ++) { + 5 │ var second = i; + · ─────────────── + 6 │ } + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:6:15] + 5 │ case 10: + 6 │ var hello = 1; + · ────────────── + 7 │ break; + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:5:11] + 4 │ try { + 5 │ var hello = 1; + · ────────────── + 6 │ } catch (e) { + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:7:11] + 6 │ } catch (e) { + 7 │ var hello = 1; + · ────────────── + 8 │ } + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:4:11] + 3 │ while (first) { + 4 │ var hello = 1; + · ────────────── + 5 │ } + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:4:11] + 3 │ do { + 4 │ var hello = 1; + · ────────────── + 5 │ } while (first == 10); + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:3:12] + 2 │ var first = [1,2,3]; + 3 │ for (var item in first) { + · ──────── + 4 │ item++; + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:5:11] + 4 │ for (item in first) { + 5 │ var hello = item; + · ───────────────── + 6 │ } + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:5:11] + 4 │ for (item in first) { + 5 │ var hello = item; + · ───────────────── + 6 │ } + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:1:18] + 1 │ 'use strict'; 0; var x; f(); + · ────── + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:1:35] + 1 │ 'use strict'; var x; 'directive'; var y; f(); + · ────── + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:1:33] + 1 │ function f() { 'use strict'; 0; var x; f(); } + · ────── + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:1:51] + 1 │ function f() { 'use strict'; var x; 'directive'; var y; f(); } + · ────── + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:2:4] + 1 │ export function f() {} + 2 │ var x; + · ────── + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:3:4] + 2 │ export function f() {} + 3 │ var y; + · ────── + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:3:4] + 2 │ export {foo}; + 3 │ var test = 1; + · ───────────── + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:2:4] + 1 │ export {foo} from 'foo'; + 2 │ var test = 1; + · ───────────── + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:2:4] + 1 │ export * from 'foo'; + 2 │ var test = 1; + · ───────────── + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:4:12] + 3 │ foo(); + 4 │ var x; + · ────── + 5 │ } + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:4:12] + 3 │ 'use strict'; + 4 │ var x; + · ────── + 5 │ } + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:5:12] + 4 │ foo(); + 5 │ var y; + · ────── + 6 │ } + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:4:16] + 3 │ if (foo) { + 4 │ var x; + · ────── + 5 │ } + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:4:16] + 3 │ if (foo) + 4 │ var x; + · ────── + 5 │ } + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable. From 384858b9edc1bf6837dfac7edc124212950d75ce Mon Sep 17 00:00:00 2001 From: Tyler Earls Date: Sun, 29 Dec 2024 11:14:37 -0600 Subject: [PATCH 052/162] feat(linter): implement `jsx-a11y/no-noninteractive-tabindex` (#8167) This PR implements the [jsx-a11y/no-noninteractive-tabindex](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/91e39b45ade789c86ae14df869a86b0ea468ed95/docs/rules/no-noninteractive-tabindex.md) rule. I set the default values as the ones that are in the recommended config. I used this [html spec section](https://html.spec.whatwg.org/multipage/dom.html#interactive-content) to source the interactive elements. The interactive roles came from [this article in the MDN docs](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles#2._widget_roles). --- crates/oxc_linter/src/rules.rs | 2 + .../jsx_a11y/no_noninteractive_tabindex.rs | 217 ++++++++++++++++++ .../jsx_a11y_no_noninteractive_tabindex.snap | 31 +++ 3 files changed, 250 insertions(+) create mode 100644 crates/oxc_linter/src/rules/jsx_a11y/no_noninteractive_tabindex.rs create mode 100644 crates/oxc_linter/src/snapshots/jsx_a11y_no_noninteractive_tabindex.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 2724a3eb142ea7..a11826c8441f2e 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -413,6 +413,7 @@ mod jsx_a11y { pub mod no_aria_hidden_on_focusable; pub mod no_autofocus; pub mod no_distracting_elements; + pub mod no_noninteractive_tabindex; pub mod no_redundant_roles; pub mod prefer_tag_over_role; pub mod role_has_required_aria_props; @@ -752,6 +753,7 @@ oxc_macros::declare_all_lint_rules! { jsx_a11y::lang, jsx_a11y::media_has_caption, jsx_a11y::mouse_events_have_key_events, + jsx_a11y::no_noninteractive_tabindex, jsx_a11y::no_access_key, jsx_a11y::no_aria_hidden_on_focusable, jsx_a11y::no_autofocus, diff --git a/crates/oxc_linter/src/rules/jsx_a11y/no_noninteractive_tabindex.rs b/crates/oxc_linter/src/rules/jsx_a11y/no_noninteractive_tabindex.rs new file mode 100644 index 00000000000000..2251324f9ee4df --- /dev/null +++ b/crates/oxc_linter/src/rules/jsx_a11y/no_noninteractive_tabindex.rs @@ -0,0 +1,217 @@ +use oxc_ast::{ + ast::{JSXAttributeItem, JSXAttributeValue}, + AstKind, +}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::{CompactStr, Span}; +use phf::phf_set; + +use crate::{ + context::LintContext, + rule::Rule, + utils::{get_element_type, has_jsx_prop_ignore_case}, + AstNode, +}; + +fn no_noninteractive_tabindex_diagnostic(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("tabIndex should only be declared on interactive elements") + .with_help("tabIndex attribute should be removed") + .with_label(span) +} + +#[derive(Debug, Clone)] +pub struct NoNoninteractiveTabindex(Box); + +#[derive(Debug, Clone)] +struct NoNoninteractiveTabindexConfig { + tags: Vec, + roles: Vec, + allow_expression_values: bool, +} + +impl Default for NoNoninteractiveTabindex { + fn default() -> Self { + Self(Box::new(NoNoninteractiveTabindexConfig { + roles: vec![CompactStr::new("tabpanel")], + allow_expression_values: true, + tags: vec![], + })) + } +} + +declare_oxc_lint!( + /// ### What it does + /// This rule checks that non-interactive elements don't have a tabIndex which would make them interactive via keyboard navigation. + /// + /// ### Why is this bad? + /// + /// Tab key navigation should be limited to elements on the page that can be interacted with. + /// Thus it is not necessary to add a tabindex to items in an unordered list, for example, + /// to make them navigable through assistive technology. + /// + /// These applications already afford page traversal mechanisms based on the HTML of the page. + /// Generally, we should try to reduce the size of the page's tab ring rather than increasing it. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```jsx + ///
+ ///
+ ///
+ ///
+ /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```jsx + ///
+ /// + ///