diff --git a/crates/oxc_ecmascript/src/constant_evaluation/mod.rs b/crates/oxc_ecmascript/src/constant_evaluation/mod.rs index a368262908466..ab47066c7de25 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_ecmascript/src/to_int_32.rs b/crates/oxc_ecmascript/src/to_int_32.rs index d0a69a48cf16d..468a8c5688c4c 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_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index f290a4f51cf8d..3880724ab826a 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_lone_blocks; pub mod no_loss_of_precision; pub mod no_magic_numbers; @@ -535,6 +536,7 @@ oxc_macros::declare_all_lint_rules! { eslint::max_classes_per_file, eslint::max_lines, eslint::max_params, + eslint::no_labels, eslint::no_lone_blocks, eslint::no_restricted_imports, eslint::no_object_constructor, 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 0000000000000..4fb28a7cc410b --- /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/rules/typescript/consistent_type_imports.rs b/crates/oxc_linter/src/rules/typescript/consistent_type_imports.rs index 607643b279a45..0c29635f85a5b 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_linter/src/snapshots/eslint_no_labels.snap b/crates/oxc_linter/src/snapshots/eslint_no_labels.snap new file mode 100644 index 0000000000000..b51a31830ffa7 --- /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; } } } + · ─ + ╰──── diff --git a/crates/oxc_minifier/src/ast_passes/mod.rs b/crates/oxc_minifier/src/ast_passes/mod.rs index d92458fd6724d..459265a14fe03 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_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs index dbff2b5eb207b..d0e55587a4aa3 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,11 +1504,50 @@ 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 - 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"); 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 692b2b06517ba..83ddc1af628a6 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/crates/oxc_semantic/src/builder.rs b/crates/oxc_semantic/src/builder.rs index 9503630f0f2ff..7a98ecb01f443 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 0000000000000..d45c6096c3b79 --- /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 0000000000000..8a0d8a108d6da --- /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 {} diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index df2d1af01df2b..8bb240402d3bf 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