Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 102 additions & 2 deletions crates/oxc_transformer/src/decorator/legacy/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,10 @@ use std::collections::VecDeque;
use oxc_allocator::{Box as ArenaBox, TakeIn};
use oxc_ast::ast::*;
use oxc_data_structures::stack::NonEmptyStack;
use oxc_semantic::ReferenceFlags;
use oxc_semantic::{Reference, ReferenceFlags, SymbolId};
use oxc_span::{ContentEq, SPAN};
use oxc_traverse::{MaybeBoundIdentifier, Traverse};
use rustc_hash::FxHashMap;

use crate::{
Helper,
Expand All @@ -103,6 +104,17 @@ use crate::{
utils::ast_builder::create_property_access,
};

/// Type of an enum inferred from its members
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum EnumType {
/// All members are string literals or template literals with string-only expressions
String,
/// All members are numeric, bigint, unary numeric, or auto-incremented
Number,
/// Mixed types or computed values
Object,
}

pub enum MethodMetadata<'a> {
Constructor(Expression<'a>),
Normal([Expression<'a>; 3]),
Expand All @@ -111,15 +123,32 @@ pub enum MethodMetadata<'a> {
pub struct LegacyDecoratorMetadata<'a, 'ctx> {
ctx: &'ctx TransformCtx<'a>,
metadata_stack: NonEmptyStack<VecDeque<MethodMetadata<'a>>>,
enum_types: FxHashMap<SymbolId, EnumType>,
}

impl<'a, 'ctx> LegacyDecoratorMetadata<'a, 'ctx> {
pub fn new(ctx: &'ctx TransformCtx<'a>) -> Self {
LegacyDecoratorMetadata { ctx, metadata_stack: NonEmptyStack::new(VecDeque::new()) }
LegacyDecoratorMetadata {
ctx,
metadata_stack: NonEmptyStack::new(VecDeque::new()),
enum_types: FxHashMap::default(),
}
}
}

impl<'a> Traverse<'a, TransformState<'a>> for LegacyDecoratorMetadata<'a, '_> {
// `#[inline]` because this is a hot path and most `Statement`s are not `TSEnumDeclaration`s.
// We want to avoid overhead of a function call for the common case.
#[inline]
fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
// Collect enum types here instead of in `enter_ts_enum_declaration` because the TypeScript
// plugin transforms enum declarations in `enter_statement`, and we need to collect the
// enum type before it gets transformed.
if let Statement::TSEnumDeclaration(decl) = stmt {
self.collect_enum_type(decl, ctx);
}
}

fn enter_class(&mut self, class: &mut Class<'a>, ctx: &mut TraverseCtx<'a>) {
if class.is_expression() || class.declare {
return;
Expand Down Expand Up @@ -201,6 +230,63 @@ impl<'a> Traverse<'a, TransformState<'a>> for LegacyDecoratorMetadata<'a, '_> {
}

impl<'a> LegacyDecoratorMetadata<'a, '_> {
/// Collects enum type information for decorator metadata generation.
fn collect_enum_type(&mut self, decl: &TSEnumDeclaration<'a>, ctx: &TraverseCtx<'a>) {
let symbol_id = decl.id.symbol_id();

// Optimization:
// If the enum doesn't have any type references, that implies that no decorators
// refer to this enum, so there is no need to infer its type.
let has_type_reference =
ctx.scoping().get_resolved_references(symbol_id).any(Reference::is_type);
if has_type_reference {
let enum_type = Self::infer_enum_type(&decl.body.members);
self.enum_types.insert(symbol_id, enum_type);
}
}

/// Check if an expression is a numeric expression (including unary expressions)
fn is_numeric_expression(expr: &Expression<'a>) -> bool {
expr.is_number_literal()
|| matches!(
expr, Expression::UnaryExpression(unary) if
matches!(
// These operators still produce numeric results.
unary.operator, UnaryOperator::UnaryNegation | UnaryOperator::UnaryPlus | UnaryOperator::BitwiseNot
) && unary.argument.is_number_literal()
)
}

/// Infer the type of an enum based on its members
fn infer_enum_type(members: &[TSEnumMember<'a>]) -> EnumType {
let mut enum_type = EnumType::Object;

for member in members {
if let Some(init) = &member.initializer {
match init {
Expression::StringLiteral(_) | Expression::TemplateLiteral(_)
if enum_type != EnumType::Number =>
{
enum_type = EnumType::String;
}
expr if Self::is_numeric_expression(expr) && enum_type != EnumType::String => {
enum_type = EnumType::Number;
}
// For other expressions, we can't determine the type statically
_ => return EnumType::Object,
}
} else {
// No initializer means numeric (auto-incrementing from previous member)
if enum_type == EnumType::String {
return EnumType::Object;
}
enum_type = EnumType::Number;
}
}

enum_type
}

pub fn pop_method_metadata(&mut self) -> Option<MethodMetadata<'a>> {
self.metadata_stack.last_mut().pop_front()
}
Expand Down Expand Up @@ -350,6 +436,20 @@ impl<'a> LegacyDecoratorMetadata<'a, '_> {
name: &TSTypeName<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
// Check if this is an enum type reference - if so, return the primitive type directly
if let TSTypeName::IdentifierReference(ident) = name {
let symbol_id = ctx.scoping().get_reference(ident.reference_id()).symbol_id();
if let Some(symbol_id) = symbol_id {
if let Some(enum_type) = self.enum_types.get(&symbol_id) {
return match enum_type {
EnumType::String => Self::global_string(ctx),
EnumType::Number => Self::global_number(ctx),
EnumType::Object => Self::global_object(ctx),
};
}
}
}

let Some(serialized_type) = self.serialize_entity_name_as_expression_fallback(name, ctx)
else {
// Reach here means the referent is a type symbol, so use `Object` as fallback.
Expand Down
6 changes: 6 additions & 0 deletions crates/oxc_transformer/src/decorator/legacy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ impl<'a, 'ctx> LegacyDecorator<'a, 'ctx> {
}

impl<'a> Traverse<'a, TransformState<'a>> for LegacyDecorator<'a, '_> {
fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
if self.emit_decorator_metadata {
self.metadata.enter_statement(stmt, ctx);
}
}

#[inline]
fn enter_class(&mut self, class: &mut Class<'a>, ctx: &mut TraverseCtx<'a>) {
if self.emit_decorator_metadata {
Expand Down
6 changes: 6 additions & 0 deletions crates/oxc_transformer/src/decorator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ impl<'a, 'ctx> Decorator<'a, 'ctx> {
}

impl<'a> Traverse<'a, TransformState<'a>> for Decorator<'a, '_> {
fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.legacy {
self.legacy_decorator.enter_statement(stmt, ctx);
}
}

fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.legacy {
self.legacy_decorator.exit_statement(stmt, ctx);
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_transformer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,7 @@ impl<'a> Traverse<'a, TransformState<'a>> for TransformerImpl<'a, '_> {
}

fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
self.decorator.enter_statement(stmt, ctx);
if let Some(typescript) = self.x0_typescript.as_mut() {
typescript.enter_statement(stmt, ctx);
}
Expand Down
16 changes: 8 additions & 8 deletions tasks/coverage/snapshots/semantic_typescript.snap
Original file line number Diff line number Diff line change
Expand Up @@ -20221,22 +20221,22 @@ after transform: ScopeId(4): ScopeFlags(0x0)
rebuilt : ScopeId(5): ScopeFlags(Function)
Symbol reference IDs mismatch for "A":
after transform: SymbolId(3): [ReferenceId(2)]
rebuilt : SymbolId(8): []
rebuilt : SymbolId(7): []
Symbol flags mismatch for "E":
after transform: SymbolId(5): SymbolFlags(RegularEnum)
rebuilt : SymbolId(10): SymbolFlags(FunctionScopedVariable)
rebuilt : SymbolId(9): SymbolFlags(FunctionScopedVariable)
Symbol reference IDs mismatch for "E":
after transform: SymbolId(5): [ReferenceId(6), ReferenceId(8), ReferenceId(9), ReferenceId(11), ReferenceId(13), ReferenceId(42), ReferenceId(47), ReferenceId(48), ReferenceId(55), ReferenceId(56)]
rebuilt : SymbolId(10): [ReferenceId(30), ReferenceId(39), ReferenceId(40), ReferenceId(53), ReferenceId(54)]
after transform: SymbolId(5): [ReferenceId(6), ReferenceId(8), ReferenceId(9), ReferenceId(11), ReferenceId(13), ReferenceId(42), ReferenceId(47), ReferenceId(48)]
rebuilt : SymbolId(9): [ReferenceId(30), ReferenceId(39), ReferenceId(40)]
Unresolved references mismatch:
after transform: ["Boolean", "Object", "String", "require"]
rebuilt : ["Boolean", "Object", "require"]
after transform: ["Boolean", "Number", "Object", "String", "require"]
rebuilt : ["Boolean", "Number", "Object", "require"]
Unresolved reference IDs mismatch for "Boolean":
after transform: [ReferenceId(20), ReferenceId(21), ReferenceId(24)]
rebuilt : [ReferenceId(14)]
Unresolved reference IDs mismatch for "Object":
after transform: [ReferenceId(0), ReferenceId(18), ReferenceId(25), ReferenceId(51), ReferenceId(53), ReferenceId(59), ReferenceId(61)]
rebuilt : [ReferenceId(9), ReferenceId(19), ReferenceId(42), ReferenceId(47), ReferenceId(56), ReferenceId(61)]
after transform: [ReferenceId(0), ReferenceId(18), ReferenceId(25), ReferenceId(51), ReferenceId(53), ReferenceId(57)]
rebuilt : [ReferenceId(9), ReferenceId(19), ReferenceId(42), ReferenceId(47), ReferenceId(57)]

semantic Error: tasks/coverage/typescript/tests/cases/compiler/metadataOfUnionWithNull.ts
Symbol reference IDs mismatch for "A":
Expand Down
102 changes: 100 additions & 2 deletions tasks/transform_conformance/snapshots/oxc.snap.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
commit: 41d96516

Passed: 183/304
Passed: 183/305

# All Passed:
* babel-plugin-transform-class-static-block
Expand Down Expand Up @@ -539,7 +539,7 @@ x Output mismatch
x Output mismatch


# legacy-decorators (6/80)
# legacy-decorators (6/81)
* oxc/metadata/abstract-class/input.ts
Symbol reference IDs mismatch for "Dependency":
after transform: SymbolId(1): [ReferenceId(1), ReferenceId(2), ReferenceId(3)]
Expand All @@ -562,6 +562,104 @@ Symbol span mismatch for "Example":
after transform: SymbolId(4): Span { start: 0, end: 0 }
rebuilt : SymbolId(3): Span { start: 87, end: 94 }

* oxc/metadata/enum-types/input.ts
Bindings mismatch:
after transform: ScopeId(1): ["StringEnum", "bar", "foo"]
rebuilt : ScopeId(1): ["StringEnum"]
Scope flags mismatch:
after transform: ScopeId(1): ScopeFlags(0x0)
rebuilt : ScopeId(1): ScopeFlags(Function)
Bindings mismatch:
after transform: ScopeId(2): ["TemplateStringEnum", "mixed", "template"]
rebuilt : ScopeId(2): ["TemplateStringEnum"]
Scope flags mismatch:
after transform: ScopeId(2): ScopeFlags(0x0)
rebuilt : ScopeId(2): ScopeFlags(Function)
Bindings mismatch:
after transform: ScopeId(3): ["NumberEnum", "a", "b"]
rebuilt : ScopeId(3): ["NumberEnum"]
Scope flags mismatch:
after transform: ScopeId(3): ScopeFlags(0x0)
rebuilt : ScopeId(3): ScopeFlags(Function)
Bindings mismatch:
after transform: ScopeId(4): ["BigIntEnum", "big", "bigger"]
rebuilt : ScopeId(4): ["BigIntEnum"]
Scope flags mismatch:
after transform: ScopeId(4): ScopeFlags(0x0)
rebuilt : ScopeId(4): ScopeFlags(Function)
Bindings mismatch:
after transform: ScopeId(5): ["UnaryEnum", "bitwise", "negative", "positive"]
rebuilt : ScopeId(5): ["UnaryEnum"]
Scope flags mismatch:
after transform: ScopeId(5): ScopeFlags(0x0)
rebuilt : ScopeId(5): ScopeFlags(Function)
Bindings mismatch:
after transform: ScopeId(6): ["AutoIncrementEnum", "first", "second", "third"]
rebuilt : ScopeId(6): ["AutoIncrementEnum"]
Scope flags mismatch:
after transform: ScopeId(6): ScopeFlags(0x0)
rebuilt : ScopeId(6): ScopeFlags(Function)
Bindings mismatch:
after transform: ScopeId(7): ["MixedEnum", "num", "str"]
rebuilt : ScopeId(7): ["MixedEnum"]
Scope flags mismatch:
after transform: ScopeId(7): ScopeFlags(0x0)
rebuilt : ScopeId(7): ScopeFlags(Function)
Bindings mismatch:
after transform: ScopeId(8): ["ComputedEnum", "computed", "expression"]
rebuilt : ScopeId(8): ["ComputedEnum"]
Scope flags mismatch:
after transform: ScopeId(8): ScopeFlags(0x0)
rebuilt : ScopeId(8): ScopeFlags(Function)
Symbol flags mismatch for "StringEnum":
after transform: SymbolId(0): SymbolFlags(RegularEnum)
rebuilt : SymbolId(0): SymbolFlags(FunctionScopedVariable)
Symbol reference IDs mismatch for "StringEnum":
after transform: SymbolId(0): [ReferenceId(2), ReferenceId(18), ReferenceId(24)]
rebuilt : SymbolId(0): [ReferenceId(3)]
Symbol flags mismatch for "TemplateStringEnum":
after transform: SymbolId(3): SymbolFlags(RegularEnum)
rebuilt : SymbolId(2): SymbolFlags(FunctionScopedVariable)
Symbol reference IDs mismatch for "TemplateStringEnum":
after transform: SymbolId(3): [ReferenceId(4), ReferenceId(28)]
rebuilt : SymbolId(2): [ReferenceId(7)]
Symbol flags mismatch for "NumberEnum":
after transform: SymbolId(6): SymbolFlags(RegularEnum)
rebuilt : SymbolId(4): SymbolFlags(FunctionScopedVariable)
Symbol reference IDs mismatch for "NumberEnum":
after transform: SymbolId(6): [ReferenceId(6), ReferenceId(19), ReferenceId(20), ReferenceId(34)]
rebuilt : SymbolId(4): [ReferenceId(13), ReferenceId(48)]
Symbol flags mismatch for "BigIntEnum":
after transform: SymbolId(9): SymbolFlags(RegularEnum)
rebuilt : SymbolId(6): SymbolFlags(FunctionScopedVariable)
Symbol reference IDs mismatch for "BigIntEnum":
after transform: SymbolId(9): [ReferenceId(8), ReferenceId(40)]
rebuilt : SymbolId(6): [ReferenceId(19)]
Symbol flags mismatch for "UnaryEnum":
after transform: SymbolId(12): SymbolFlags(RegularEnum)
rebuilt : SymbolId(8): SymbolFlags(FunctionScopedVariable)
Symbol reference IDs mismatch for "UnaryEnum":
after transform: SymbolId(12): [ReferenceId(10), ReferenceId(48)]
rebuilt : SymbolId(8): [ReferenceId(27)]
Symbol flags mismatch for "AutoIncrementEnum":
after transform: SymbolId(16): SymbolFlags(RegularEnum)
rebuilt : SymbolId(10): SymbolFlags(FunctionScopedVariable)
Symbol reference IDs mismatch for "AutoIncrementEnum":
after transform: SymbolId(16): [ReferenceId(12), ReferenceId(56)]
rebuilt : SymbolId(10): [ReferenceId(35)]
Symbol flags mismatch for "MixedEnum":
after transform: SymbolId(20): SymbolFlags(RegularEnum)
rebuilt : SymbolId(12): SymbolFlags(FunctionScopedVariable)
Symbol reference IDs mismatch for "MixedEnum":
after transform: SymbolId(20): [ReferenceId(14), ReferenceId(61)]
rebuilt : SymbolId(12): [ReferenceId(40)]
Symbol flags mismatch for "ComputedEnum":
after transform: SymbolId(23): SymbolFlags(RegularEnum)
rebuilt : SymbolId(14): SymbolFlags(FunctionScopedVariable)
Symbol reference IDs mismatch for "ComputedEnum":
after transform: SymbolId(23): [ReferenceId(16), ReferenceId(67)]
rebuilt : SymbolId(14): [ReferenceId(47)]

* oxc/metadata/imports/input.ts
Bindings mismatch:
after transform: ScopeId(0): ["Bar", "Cls", "Foo", "Zoo", "_ref", "dec"]
Expand Down
Loading
Loading