|
| 1 | +//! Emitting decorator metadata |
| 2 | +//! |
| 3 | +//! This plugin is used to emit decorator metadata for legacy decorators by |
| 4 | +//! the `__metadata` helper. |
| 5 | +//! |
| 6 | +//! ## Example |
| 7 | +//! |
| 8 | +//! Input: |
| 9 | +//! ```ts |
| 10 | +//! class Demo { |
| 11 | +//! @LogMethod |
| 12 | +//! public foo(bar: number) {} |
| 13 | +//! |
| 14 | +//! @Prop |
| 15 | +//! prop: string = "hello"; |
| 16 | +//! } |
| 17 | +//! ``` |
| 18 | +//! |
| 19 | +//! Output: |
| 20 | +//! ```js |
| 21 | +//! class Demo { |
| 22 | +//! foo(bar) {} |
| 23 | +//! prop = "hello"; |
| 24 | +//! } |
| 25 | +//! babelHelpers.decorate([ |
| 26 | +//! LogMethod, |
| 27 | +//! babelHelpers.decorateParam(0, babelHelpers.decorateMetadata("design:type", Function)), |
| 28 | +//! babelHelpers.decorateParam(0, babelHelpers.decorateMetadata("design:paramtypes", [Number])), |
| 29 | +//! babelHelpers.decorateParam(0, babelHelpers.decorateMetadata("design:returntype", void 0)) |
| 30 | +//! ], Demo.prototype, "foo", null); |
| 31 | +//! babelHelpers.decorate([Prop, babelHelpers.decorateMetadata("design:type", String)], Demo.prototype, "prop", void 0); |
| 32 | +//! ``` |
| 33 | +//! |
| 34 | +//! ## Implementation |
| 35 | +//! |
| 36 | +//! This is a port of the oxc implementation based on |
| 37 | +//! <https://github.com/microsoft/TypeScript/blob/d85767abfd83880cea17cea70f9913e9c4496dcc/src/compiler/transformers/ts.ts#L1119-L1136> |
| 38 | +//! |
| 39 | +//! ## Limitations |
| 40 | +//! |
| 41 | +//! ### Compared to TypeScript |
| 42 | +//! |
| 43 | +//! We lack the type inference ability that TypeScript has, so we cannot |
| 44 | +//! determine the exact type of type references. For example: |
| 45 | +//! |
| 46 | +//! Input: |
| 47 | +//! ```ts |
| 48 | +//! type Foo = string; |
| 49 | +//! class Cls { |
| 50 | +//! @dec |
| 51 | +//! p: Foo = "" |
| 52 | +//! } |
| 53 | +//! ``` |
| 54 | +//! |
| 55 | +//! TypeScript Output: |
| 56 | +//! ```js |
| 57 | +//! class Cls { |
| 58 | +//! constructor() { |
| 59 | +//! this.p = ""; |
| 60 | +//! } |
| 61 | +//! } |
| 62 | +//! __decorate([ |
| 63 | +//! dec, |
| 64 | +//! __metadata("design:type", String) // Infers that Foo is String |
| 65 | +//! ], Cls.prototype, "p", void 0); |
| 66 | +//! ``` |
| 67 | +//! |
| 68 | +//! Our Output: |
| 69 | +//! ```js |
| 70 | +//! var _ref; |
| 71 | +//! class Cls { |
| 72 | +//! p = ""; |
| 73 | +//! } |
| 74 | +//! babelHelpers.decorate([ |
| 75 | +//! dec, |
| 76 | +//! babelHelpers.decorateMetadata("design:type", typeof (_ref = typeof Foo === "undefined" && Foo) === "function" ? _ref : Object) |
| 77 | +//! ], |
| 78 | +//! Cls.prototype, "p", void 0); |
| 79 | +//! ``` |
| 80 | +//! |
| 81 | +//! ### Compared to SWC |
| 82 | +//! |
| 83 | +//! SWC also has the above limitation. SWC provides additional support for |
| 84 | +//! inferring enum members, which we currently do not have. The limitation may |
| 85 | +//! not be a problem as SWC has been adopted in [NestJS](https://docs.nestjs.com/recipes/swc#jest--swc). |
| 86 | +//! |
| 87 | +//! ## Porting Status |
| 88 | +//! |
| 89 | +//! **TODO**: This is a stub implementation. The full porting requires: |
| 90 | +//! |
| 91 | +//! 1. Type serialization logic for all TypeScript types |
| 92 | +//! 2. Enum type inference and tracking |
| 93 | +//! 3. Metadata generation for: |
| 94 | +//! - `design:type` - Type of the member |
| 95 | +//! - `design:paramtypes` - Types of parameters |
| 96 | +//! - `design:returntype` - Return type (for methods) |
| 97 | +//! 4. Integration with helper loader for `_metadata` helper |
| 98 | +//! 5. Proper handling of type annotations and their serialization |
| 99 | +//! |
| 100 | +//! The original oxc implementation (~800 lines) includes: |
| 101 | +//! - Type node serialization (void, function, array, boolean, string, number, |
| 102 | +//! bigint, symbol, etc.) |
| 103 | +//! - Type reference resolution with fallback for unavailable types |
| 104 | +//! - Union/intersection type handling |
| 105 | +//! - Enum type inference (string, number, mixed) |
| 106 | +//! - Entity name serialization with runtime checks |
| 107 | +//! - Metadata stacks for methods and constructors |
| 108 | +//! |
| 109 | +//! ## References |
| 110 | +//! * TypeScript's [emitDecoratorMetadata](https://www.typescriptlang.org/tsconfig#emitDecoratorMetadata) |
| 111 | +
|
| 112 | +use swc_ecma_ast::*; |
| 113 | +use swc_ecma_visit::{VisitMut, VisitMutWith}; |
| 114 | + |
| 115 | +/// Type of an enum inferred from its members |
| 116 | +#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 117 | +enum EnumType { |
| 118 | + /// All members are string literals or template literals with string-only |
| 119 | + /// expressions |
| 120 | + String, |
| 121 | + /// All members are numeric, bigint, unary numeric, or auto-incremented |
| 122 | + Number, |
| 123 | + /// Mixed types or computed values |
| 124 | + Object, |
| 125 | +} |
| 126 | + |
| 127 | +/// Metadata for decorated methods |
| 128 | +#[allow(dead_code)] |
| 129 | +pub(super) struct MethodMetadata { |
| 130 | + /// The `design:type` metadata expression |
| 131 | + pub r#type: Box<Expr>, |
| 132 | + /// The `design:paramtypes` metadata expression |
| 133 | + pub param_types: Box<Expr>, |
| 134 | + /// The `design:returntype` metadata expression (optional, omitted for |
| 135 | + /// getters/setters) |
| 136 | + pub return_type: Option<Box<Expr>>, |
| 137 | +} |
| 138 | + |
| 139 | +/// Legacy decorator metadata transformer |
| 140 | +/// |
| 141 | +/// Emits decorator metadata for TypeScript decorators |
| 142 | +pub struct LegacyDecoratorMetadata { |
| 143 | + // TODO: Add fields for tracking metadata state |
| 144 | + // - method_metadata_stack: Stack for method metadata |
| 145 | + // - constructor_metadata_stack: Stack for constructor metadata |
| 146 | + // - enum_types: Map of enum symbol IDs to their inferred types |
| 147 | +} |
| 148 | + |
| 149 | +impl LegacyDecoratorMetadata { |
| 150 | + /// Create a new metadata transformer |
| 151 | + pub fn new() -> Self { |
| 152 | + Self { |
| 153 | + // TODO: Initialize metadata tracking structures |
| 154 | + } |
| 155 | + } |
| 156 | + |
| 157 | + /// Infer the type of an enum based on its members |
| 158 | + #[allow(dead_code)] |
| 159 | + fn infer_enum_type(_members: &[TsEnumMember]) -> EnumType { |
| 160 | + // TODO: Implement enum type inference |
| 161 | + // Analyze members to determine if enum is String, Number, or Object type |
| 162 | + EnumType::Object |
| 163 | + } |
| 164 | + |
| 165 | + /// Serialize a TypeScript type node for use with decorator metadata |
| 166 | + /// |
| 167 | + /// Types are serialized as follows: |
| 168 | + /// - Void types -> "undefined" (e.g. "void 0") |
| 169 | + /// - Function and Constructor types -> global "Function" |
| 170 | + /// - Array and Tuple types -> global "Array" |
| 171 | + /// - Boolean types and type predicates -> global "Boolean" |
| 172 | + /// - String literal types and strings -> global "String" |
| 173 | + /// - Enum and number types -> global "Number" |
| 174 | + /// - Symbol types -> global "Symbol" |
| 175 | + /// - Type references to classes -> constructor for the class |
| 176 | + /// - Everything else -> global "Object" |
| 177 | + #[allow(dead_code)] |
| 178 | + fn serialize_type_node(&mut self, _node: &TsType) -> Box<Expr> { |
| 179 | + // TODO: Implement type serialization logic |
| 180 | + // This is the core of metadata emission |
| 181 | + Box::new(Expr::Ident(Ident::new_no_ctxt( |
| 182 | + "Object".into(), |
| 183 | + Default::default(), |
| 184 | + ))) |
| 185 | + } |
| 186 | + |
| 187 | + /// Create a metadata decorator call expression |
| 188 | + #[allow(dead_code)] |
| 189 | + fn create_metadata(&self, _key: &str, _value: Box<Expr>) -> Decorator { |
| 190 | + // TODO: Generate `_metadata(key, value)` helper call |
| 191 | + Decorator { |
| 192 | + span: Default::default(), |
| 193 | + expr: Box::new(Expr::Ident(Ident::new_no_ctxt( |
| 194 | + "TODO".into(), |
| 195 | + Default::default(), |
| 196 | + ))), |
| 197 | + } |
| 198 | + } |
| 199 | +} |
| 200 | + |
| 201 | +impl VisitMut for LegacyDecoratorMetadata { |
| 202 | + fn visit_mut_module(&mut self, n: &mut Module) { |
| 203 | + // TODO: Finalize metadata emission |
| 204 | + n.visit_mut_children_with(self); |
| 205 | + } |
| 206 | + |
| 207 | + fn visit_mut_script(&mut self, n: &mut Script) { |
| 208 | + // TODO: Finalize metadata emission |
| 209 | + n.visit_mut_children_with(self); |
| 210 | + } |
| 211 | + |
| 212 | + fn visit_mut_ts_enum_decl(&mut self, n: &mut TsEnumDecl) { |
| 213 | + // TODO: Collect enum type information for metadata generation |
| 214 | + n.visit_mut_children_with(self); |
| 215 | + } |
| 216 | + |
| 217 | + fn visit_mut_class(&mut self, n: &mut Class) { |
| 218 | + // TODO: Handle constructor metadata |
| 219 | + n.visit_mut_children_with(self); |
| 220 | + } |
| 221 | + |
| 222 | + fn visit_mut_class_method(&mut self, n: &mut ClassMethod) { |
| 223 | + // TODO: Generate method metadata (design:type, design:paramtypes, |
| 224 | + // design:returntype) |
| 225 | + n.visit_mut_children_with(self); |
| 226 | + } |
| 227 | + |
| 228 | + fn visit_mut_class_prop(&mut self, n: &mut ClassProp) { |
| 229 | + // TODO: Generate property metadata (design:type) |
| 230 | + if !n.decorators.is_empty() { |
| 231 | + // Should add design:type metadata decorator |
| 232 | + } |
| 233 | + n.visit_mut_children_with(self); |
| 234 | + } |
| 235 | + |
| 236 | + fn visit_mut_private_prop(&mut self, n: &mut PrivateProp) { |
| 237 | + // TODO: Generate private property metadata |
| 238 | + n.visit_mut_children_with(self); |
| 239 | + } |
| 240 | +} |
0 commit comments