diff --git a/.github/generated/ast_changes_watch_list.yml b/.github/generated/ast_changes_watch_list.yml index b671c40bc73f7..6f7e577b0b832 100644 --- a/.github/generated/ast_changes_watch_list.yml +++ b/.github/generated/ast_changes_watch_list.yml @@ -20,7 +20,12 @@ src: - 'crates/oxc_ast/src/generated/derive_get_span_mut.rs' - 'crates/oxc_ast/src/generated/derive_take_in.rs' - 'crates/oxc_ast/src/generated/get_id.rs' - - 'crates/oxc_ast/src/serialize.rs' + - 'crates/oxc_ast/src/serialize/basic.rs' + - 'crates/oxc_ast/src/serialize/js.rs' + - 'crates/oxc_ast/src/serialize/jsx.rs' + - 'crates/oxc_ast/src/serialize/literal.rs' + - 'crates/oxc_ast/src/serialize/mod.rs' + - 'crates/oxc_ast/src/serialize/ts.rs' - 'crates/oxc_ast_macros/src/generated/mod.rs' - 'crates/oxc_ast_macros/src/lib.rs' - 'crates/oxc_ast_visit/src/generated/utf8_to_utf16_converter.rs' diff --git a/crates/oxc_ast/src/generated/derive_estree.rs b/crates/oxc_ast/src/generated/derive_estree.rs index 51720b7d69da9..67cb1c4dc4bdc 100644 --- a/crates/oxc_ast/src/generated/derive_estree.rs +++ b/crates/oxc_ast/src/generated/derive_estree.rs @@ -76,9 +76,9 @@ impl ESTree for IdentifierName<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("name", &JsonSafeString(self.name.as_str())); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -90,9 +90,9 @@ impl ESTree for IdentifierReference<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("name", &JsonSafeString(self.name.as_str())); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -104,9 +104,9 @@ impl ESTree for BindingIdentifier<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("name", &JsonSafeString(self.name.as_str())); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -118,9 +118,9 @@ impl ESTree for LabelIdentifier<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("name", &JsonSafeString(self.name.as_str())); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -200,7 +200,7 @@ impl ESTree for ArrayExpressionElement<'_> { impl ESTree for Elision { fn serialize(&self, serializer: S) { - crate::serialize::Null(self).serialize(serializer) + crate::serialize::basic::Null(self).serialize(serializer) } } @@ -236,7 +236,7 @@ impl ESTree for ObjectProperty<'_> { state.serialize_field("key", &self.key); state.serialize_field("value", &self.value); state.serialize_field("kind", &self.kind); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); state.end(); } } @@ -330,7 +330,7 @@ impl ESTree for TaggedTemplateExpression<'_> { impl ESTree for TemplateElement<'_> { fn serialize(&self, serializer: S) { - crate::serialize::TemplateElementConverter(self).serialize(serializer) + crate::serialize::literal::TemplateElementConverter(self).serialize(serializer) } } @@ -361,7 +361,7 @@ impl ESTree for ComputedMemberExpression<'_> { state.serialize_field("end", &self.span.end); state.serialize_field("object", &self.object); state.serialize_field("property", &self.expression); - state.serialize_field("computed", &crate::serialize::True(self)); + state.serialize_field("computed", &crate::serialize::basic::True(self)); state.serialize_field("optional", &self.optional); state.end(); } @@ -375,7 +375,7 @@ impl ESTree for StaticMemberExpression<'_> { state.serialize_field("end", &self.span.end); state.serialize_field("object", &self.object); state.serialize_field("property", &self.property); - state.serialize_field("computed", &crate::serialize::False(self)); + state.serialize_field("computed", &crate::serialize::basic::False(self)); state.serialize_field("optional", &self.optional); state.end(); } @@ -389,7 +389,7 @@ impl ESTree for PrivateFieldExpression<'_> { state.serialize_field("end", &self.span.end); state.serialize_field("object", &self.object); state.serialize_field("property", &self.field); - state.serialize_field("computed", &crate::serialize::False(self)); + state.serialize_field("computed", &crate::serialize::basic::False(self)); state.serialize_field("optional", &self.optional); state.end(); } @@ -516,7 +516,7 @@ impl ESTree for UnaryExpression<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("operator", &self.operator); - state.serialize_field("prefix", &crate::serialize::True(self)); + state.serialize_field("prefix", &crate::serialize::basic::True(self)); state.serialize_field("argument", &self.argument); state.end(); } @@ -542,7 +542,7 @@ impl ESTree for PrivateInExpression<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("left", &self.left); - state.serialize_field("operator", &crate::serialize::In(self)); + state.serialize_field("operator", &crate::serialize::basic::In(self)); state.serialize_field("right", &self.right); state.end(); } @@ -635,9 +635,9 @@ impl ESTree for ArrayAssignmentTarget<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("elements", &Concat2(&self.elements, &self.rest)); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -649,9 +649,9 @@ impl ESTree for ObjectAssignmentTarget<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("properties", &Concat2(&self.properties, &self.rest)); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -663,10 +663,10 @@ impl ESTree for AssignmentTargetRest<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("argument", &self.target); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); - state.serialize_ts_field("value", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); + state.serialize_ts_field("value", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -697,9 +697,9 @@ impl ESTree for AssignmentTargetWithDefault<'_> { state.serialize_field("end", &self.span.end); state.serialize_field("left", &self.binding); state.serialize_field("right", &self.init); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -719,16 +719,16 @@ impl ESTree for AssignmentTargetPropertyIdentifier<'_> { state.serialize_field("type", &JsonSafeString("Property")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("method", &crate::serialize::False(self)); - state.serialize_field("shorthand", &crate::serialize::True(self)); - state.serialize_field("computed", &crate::serialize::False(self)); + state.serialize_field("method", &crate::serialize::basic::False(self)); + state.serialize_field("shorthand", &crate::serialize::basic::True(self)); + state.serialize_field("computed", &crate::serialize::basic::False(self)); state.serialize_field("key", &self.binding); state.serialize_field( "value", - &crate::serialize::AssignmentTargetPropertyIdentifierValue(self), + &crate::serialize::js::AssignmentTargetPropertyIdentifierValue(self), ); - state.serialize_field("kind", &crate::serialize::Init(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); + state.serialize_field("kind", &crate::serialize::basic::Init(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); state.end(); } } @@ -739,13 +739,13 @@ impl ESTree for AssignmentTargetPropertyProperty<'_> { state.serialize_field("type", &JsonSafeString("Property")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("method", &crate::serialize::False(self)); - state.serialize_field("shorthand", &crate::serialize::False(self)); + state.serialize_field("method", &crate::serialize::basic::False(self)); + state.serialize_field("shorthand", &crate::serialize::basic::False(self)); state.serialize_field("computed", &self.computed); state.serialize_field("key", &self.name); state.serialize_field("value", &self.binding); - state.serialize_field("kind", &crate::serialize::Init(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); + state.serialize_field("kind", &crate::serialize::basic::Init(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); state.end(); } } @@ -959,8 +959,10 @@ impl ESTree for ExpressionStatement<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("expression", &self.expression); - state - .serialize_ts_field("directive", &crate::serialize::ExpressionStatementDirective(self)); + state.serialize_ts_field( + "directive", + &crate::serialize::ts::ExpressionStatementDirective(self), + ); state.end(); } } @@ -1278,9 +1280,9 @@ impl ESTree for AssignmentPattern<'_> { state.serialize_field("end", &self.span.end); state.serialize_field("left", &self.left); state.serialize_field("right", &self.right); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -1292,9 +1294,9 @@ impl ESTree for ObjectPattern<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("properties", &Concat2(&self.properties, &self.rest)); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -1305,13 +1307,13 @@ impl ESTree for BindingProperty<'_> { state.serialize_field("type", &JsonSafeString("Property")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("method", &crate::serialize::False(self)); + state.serialize_field("method", &crate::serialize::basic::False(self)); state.serialize_field("shorthand", &self.shorthand); state.serialize_field("computed", &self.computed); state.serialize_field("key", &self.key); state.serialize_field("value", &self.value); - state.serialize_field("kind", &crate::serialize::Init(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); + state.serialize_field("kind", &crate::serialize::basic::Init(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); state.end(); } } @@ -1323,9 +1325,9 @@ impl ESTree for ArrayPattern<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("elements", &Concat2(&self.elements, &self.rest)); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -1337,10 +1339,10 @@ impl ESTree for BindingRestElement<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("argument", &self.argument); - state.serialize_ts_field("decorators", &crate::serialize::TsEmptyArray(self)); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("typeAnnotation", &crate::serialize::TsNull(self)); - state.serialize_ts_field("value", &crate::serialize::TsNull(self)); + state.serialize_ts_field("decorators", &crate::serialize::basic::TsEmptyArray(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("typeAnnotation", &crate::serialize::basic::TsNull(self)); + state.serialize_ts_field("value", &crate::serialize::basic::TsNull(self)); state.end(); } } @@ -1352,10 +1354,10 @@ impl ESTree for Function<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("id", &self.id); - state.serialize_field("expression", &crate::serialize::False(self)); + state.serialize_field("expression", &crate::serialize::basic::False(self)); state.serialize_field("generator", &self.generator); state.serialize_field("async", &self.r#async); - state.serialize_field("params", &crate::serialize::FunctionParams(self)); + state.serialize_field("params", &crate::serialize::js::FunctionParams(self)); state.serialize_field("body", &self.body); state.serialize_ts_field("declare", &self.declare); state.serialize_ts_field("typeParameters", &self.type_parameters); @@ -1381,13 +1383,13 @@ impl ESTree for FunctionType { impl ESTree for FormalParameters<'_> { fn serialize(&self, serializer: S) { - crate::serialize::FormalParametersConverter(self).serialize(serializer) + crate::serialize::js::FormalParametersConverter(self).serialize(serializer) } } impl ESTree for FormalParameter<'_> { fn serialize(&self, serializer: S) { - crate::serialize::FormalParameterConverter(self).serialize(serializer) + crate::serialize::js::FormalParameterConverter(self).serialize(serializer) } } @@ -1423,12 +1425,12 @@ impl ESTree for ArrowFunctionExpression<'_> { state.serialize_field("type", &JsonSafeString("ArrowFunctionExpression")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("id", &crate::serialize::Null(self)); + state.serialize_field("id", &crate::serialize::basic::Null(self)); state.serialize_field("expression", &self.expression); - state.serialize_field("generator", &crate::serialize::False(self)); + state.serialize_field("generator", &crate::serialize::basic::False(self)); state.serialize_field("async", &self.r#async); state.serialize_field("params", &self.params); - state.serialize_field("body", &crate::serialize::ArrowFunctionExpressionBody(self)); + state.serialize_field("body", &crate::serialize::js::ArrowFunctionExpressionBody(self)); state.serialize_ts_field("typeParameters", &self.type_parameters); state.serialize_ts_field("returnType", &self.return_type); state.end(); @@ -1506,7 +1508,7 @@ impl ESTree for MethodDefinition<'_> { state.serialize_field("end", &self.span.end); state.serialize_field("static", &self.r#static); state.serialize_field("computed", &self.computed); - state.serialize_field("key", &crate::serialize::MethodDefinitionKey(self)); + state.serialize_field("key", &crate::serialize::js::MethodDefinitionKey(self)); state.serialize_field("kind", &self.kind); state.serialize_field("value", &self.value); state.serialize_ts_field("decorators", &self.decorators); @@ -1632,10 +1634,10 @@ impl ESTree for AccessorProperty<'_> { state.serialize_ts_field("decorators", &self.decorators); state.serialize_ts_field("definite", &self.definite); state.serialize_ts_field("accessibility", &self.accessibility); - state.serialize_ts_field("optional", &crate::serialize::TsFalse(self)); + state.serialize_ts_field("optional", &crate::serialize::basic::TsFalse(self)); state.serialize_ts_field("override", &self.r#override); - state.serialize_ts_field("readonly", &crate::serialize::TsFalse(self)); - state.serialize_ts_field("declare", &crate::serialize::TsFalse(self)); + state.serialize_ts_field("readonly", &crate::serialize::basic::TsFalse(self)); + state.serialize_ts_field("declare", &crate::serialize::basic::TsFalse(self)); state.end(); } } @@ -1658,9 +1660,15 @@ impl ESTree for ImportDeclaration<'_> { state.serialize_field("type", &JsonSafeString("ImportDeclaration")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("specifiers", &crate::serialize::ImportDeclarationSpecifiers(self)); + state.serialize_field( + "specifiers", + &crate::serialize::js::ImportDeclarationSpecifiers(self), + ); state.serialize_field("source", &self.source); - state.serialize_field("attributes", &crate::serialize::ImportDeclarationWithClause(self)); + state.serialize_field( + "attributes", + &crate::serialize::js::ImportDeclarationWithClause(self), + ); state.serialize_ts_field("importKind", &self.import_kind); state.end(); } @@ -1764,7 +1772,7 @@ impl ESTree for ExportNamedDeclaration<'_> { state.serialize_field("source", &self.source); state.serialize_field( "attributes", - &crate::serialize::ExportNamedDeclarationWithClause(self), + &crate::serialize::js::ExportNamedDeclarationWithClause(self), ); state.serialize_ts_field("exportKind", &self.export_kind); state.end(); @@ -1778,7 +1786,7 @@ impl ESTree for ExportDefaultDeclaration<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("declaration", &self.declaration); - state.serialize_ts_field("exportKind", &crate::serialize::TsValue(self)); + state.serialize_ts_field("exportKind", &crate::serialize::basic::TsValue(self)); state.end(); } } @@ -1791,8 +1799,10 @@ impl ESTree for ExportAllDeclaration<'_> { state.serialize_field("end", &self.span.end); state.serialize_field("exported", &self.exported); state.serialize_field("source", &self.source); - state - .serialize_field("attributes", &crate::serialize::ExportAllDeclarationWithClause(self)); + state.serialize_field( + "attributes", + &crate::serialize::js::ExportAllDeclarationWithClause(self), + ); state.serialize_ts_field("exportKind", &self.export_kind); state.end(); } @@ -1893,7 +1903,7 @@ impl ESTree for BooleanLiteral { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("value", &self.value); - state.serialize_field("raw", &crate::serialize::BooleanLiteralRaw(self)); + state.serialize_field("raw", &crate::serialize::literal::BooleanLiteralRaw(self)); state.end(); } } @@ -1904,8 +1914,8 @@ impl ESTree for NullLiteral { state.serialize_field("type", &JsonSafeString("Literal")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("value", &crate::serialize::Null(self)); - state.serialize_field("raw", &crate::serialize::NullLiteralRaw(self)); + state.serialize_field("value", &crate::serialize::basic::Null(self)); + state.serialize_field("raw", &crate::serialize::literal::NullLiteralRaw(self)); state.end(); } } @@ -1928,7 +1938,7 @@ impl ESTree for StringLiteral<'_> { state.serialize_field("type", &JsonSafeString("Literal")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("value", &crate::serialize::StringLiteralValue(self)); + state.serialize_field("value", &crate::serialize::literal::StringLiteralValue(self)); state.serialize_field("raw", &self.raw); state.end(); } @@ -1940,9 +1950,9 @@ impl ESTree for BigIntLiteral<'_> { state.serialize_field("type", &JsonSafeString("Literal")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("value", &crate::serialize::BigIntLiteralValue(self)); + state.serialize_field("value", &crate::serialize::literal::BigIntLiteralValue(self)); state.serialize_field("raw", &self.raw); - state.serialize_field("bigint", &crate::serialize::BigIntLiteralBigint(self)); + state.serialize_field("bigint", &crate::serialize::literal::BigIntLiteralBigint(self)); state.end(); } } @@ -1953,7 +1963,7 @@ impl ESTree for RegExpLiteral<'_> { state.serialize_field("type", &JsonSafeString("Literal")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("value", &crate::serialize::RegExpLiteralValue(self)); + state.serialize_field("value", &crate::serialize::literal::RegExpLiteralValue(self)); state.serialize_field("raw", &self.raw); state.serialize_field("regex", &self.regex); state.end(); @@ -1979,7 +1989,7 @@ impl ESTree for RegExpPattern<'_> { impl ESTree for RegExpFlags { fn serialize(&self, serializer: S) { - crate::serialize::RegExpFlagsConverter(self).serialize(serializer) + crate::serialize::literal::RegExpFlagsConverter(self).serialize(serializer) } } @@ -1989,7 +1999,7 @@ impl ESTree for JSXElement<'_> { state.serialize_field("type", &JsonSafeString("JSXElement")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("openingElement", &crate::serialize::JSXElementOpening(self)); + state.serialize_field("openingElement", &crate::serialize::jsx::JSXElementOpening(self)); state.serialize_field("closingElement", &self.closing_element); state.serialize_field("children", &self.children); state.end(); @@ -2004,7 +2014,10 @@ impl ESTree for JSXOpeningElement<'_> { state.serialize_field("end", &self.span.end); state.serialize_field("attributes", &self.attributes); state.serialize_field("name", &self.name); - state.serialize_field("selfClosing", &crate::serialize::JSXOpeningElementSelfClosing(self)); + state.serialize_field( + "selfClosing", + &crate::serialize::jsx::JSXOpeningElementSelfClosing(self), + ); state.serialize_ts_field("typeArguments", &self.type_arguments); state.end(); } @@ -2036,7 +2049,7 @@ impl ESTree for JSXFragment<'_> { impl ESTree for JSXOpeningFragment { fn serialize(&self, serializer: S) { - crate::serialize::JSXOpeningFragmentConverter(self).serialize(serializer) + crate::serialize::jsx::JSXOpeningFragmentConverter(self).serialize(serializer) } } @@ -2055,12 +2068,12 @@ impl ESTree for JSXElementName<'_> { match self { Self::Identifier(it) => it.serialize(serializer), Self::IdentifierReference(it) => { - crate::serialize::JSXElementIdentifierReference(it).serialize(serializer) + crate::serialize::jsx::JSXElementIdentifierReference(it).serialize(serializer) } Self::NamespacedName(it) => it.serialize(serializer), Self::MemberExpression(it) => it.serialize(serializer), Self::ThisExpression(it) => { - crate::serialize::JSXElementThisExpression(it).serialize(serializer) + crate::serialize::jsx::JSXElementThisExpression(it).serialize(serializer) } } } @@ -2094,11 +2107,11 @@ impl ESTree for JSXMemberExpressionObject<'_> { fn serialize(&self, serializer: S) { match self { Self::IdentifierReference(it) => { - crate::serialize::JSXElementIdentifierReference(it).serialize(serializer) + crate::serialize::jsx::JSXElementIdentifierReference(it).serialize(serializer) } Self::MemberExpression(it) => it.serialize(serializer), Self::ThisExpression(it) => { - crate::serialize::JSXElementThisExpression(it).serialize(serializer) + crate::serialize::jsx::JSXElementThisExpression(it).serialize(serializer) } } } @@ -2280,9 +2293,9 @@ impl ESTree for TSThisParameter<'_> { state.serialize_field("type", &JsonSafeString("Identifier")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("name", &crate::serialize::This(self)); - state.serialize_field("decorators", &crate::serialize::EmptyArray(self)); - state.serialize_field("optional", &crate::serialize::False(self)); + state.serialize_field("name", &crate::serialize::basic::This(self)); + state.serialize_field("decorators", &crate::serialize::basic::EmptyArray(self)); + state.serialize_field("optional", &crate::serialize::basic::False(self)); state.serialize_field("typeAnnotation", &self.type_annotation); state.end(); } @@ -2320,7 +2333,7 @@ impl ESTree for TSEnumMember<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("id", &self.id); - state.serialize_field("computed", &crate::serialize::TSEnumMemberComputed(self)); + state.serialize_field("computed", &crate::serialize::ts::TSEnumMemberComputed(self)); state.serialize_field("initializer", &self.initializer); state.end(); } @@ -2756,7 +2769,7 @@ impl ESTree for TSTypeName<'_> { fn serialize(&self, serializer: S) { match self { Self::IdentifierReference(it) => { - crate::serialize::TSTypeNameIdentifierReference(it).serialize(serializer) + crate::serialize::ts::TSTypeNameIdentifierReference(it).serialize(serializer) } Self::QualifiedName(it) => it.serialize(serializer), } @@ -2843,7 +2856,10 @@ impl ESTree for TSClassImplements<'_> { state.serialize_field("type", &JsonSafeString("TSClassImplements")); state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); - state.serialize_field("expression", &crate::serialize::TSClassImplementsExpression(self)); + state.serialize_field( + "expression", + &crate::serialize::ts::TSClassImplementsExpression(self), + ); state.serialize_field("typeArguments", &self.type_arguments); state.end(); } @@ -2886,8 +2902,8 @@ impl ESTree for TSPropertySignature<'_> { state.serialize_field("readonly", &self.readonly); state.serialize_field("key", &self.key); state.serialize_field("typeAnnotation", &self.type_annotation); - state.serialize_field("accessibility", &crate::serialize::Null(self)); - state.serialize_field("static", &crate::serialize::False(self)); + state.serialize_field("accessibility", &crate::serialize::basic::Null(self)); + state.serialize_field("static", &crate::serialize::basic::False(self)); state.end(); } } @@ -2914,7 +2930,7 @@ impl ESTree for TSIndexSignature<'_> { state.serialize_field("typeAnnotation", &self.type_annotation); state.serialize_field("readonly", &self.readonly); state.serialize_field("static", &self.r#static); - state.serialize_field("accessibility", &crate::serialize::Null(self)); + state.serialize_field("accessibility", &crate::serialize::basic::Null(self)); state.end(); } } @@ -2926,7 +2942,10 @@ impl ESTree for TSCallSignatureDeclaration<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("typeParameters", &self.type_parameters); - state.serialize_field("params", &crate::serialize::TSCallSignatureDeclarationParams(self)); + state.serialize_field( + "params", + &crate::serialize::ts::TSCallSignatureDeclarationParams(self), + ); state.serialize_field("returnType", &self.return_type); state.end(); } @@ -2953,11 +2972,11 @@ impl ESTree for TSMethodSignature<'_> { state.serialize_field("optional", &self.optional); state.serialize_field("kind", &self.kind); state.serialize_field("typeParameters", &self.type_parameters); - state.serialize_field("params", &crate::serialize::TSMethodSignatureParams(self)); + state.serialize_field("params", &crate::serialize::ts::TSMethodSignatureParams(self)); state.serialize_field("returnType", &self.return_type); - state.serialize_field("accessibility", &crate::serialize::Null(self)); - state.serialize_field("readonly", &crate::serialize::False(self)); - state.serialize_field("static", &crate::serialize::False(self)); + state.serialize_field("accessibility", &crate::serialize::basic::Null(self)); + state.serialize_field("readonly", &crate::serialize::basic::False(self)); + state.serialize_field("static", &crate::serialize::basic::False(self)); state.end(); } } @@ -2982,8 +3001,8 @@ impl ESTree for TSIndexSignatureName<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("name", &JsonSafeString(self.name.as_str())); - state.serialize_field("decorators", &crate::serialize::EmptyArray(self)); - state.serialize_field("optional", &crate::serialize::False(self)); + state.serialize_field("decorators", &crate::serialize::basic::EmptyArray(self)); + state.serialize_field("optional", &crate::serialize::basic::False(self)); state.serialize_field("typeAnnotation", &self.type_annotation); state.end(); } @@ -3025,7 +3044,7 @@ impl ESTree for TSTypePredicateName<'_> { impl ESTree for TSModuleDeclaration<'_> { fn serialize(&self, serializer: S) { - crate::serialize::TSModuleDeclarationConverter(self).serialize(serializer) + crate::serialize::ts::TSModuleDeclarationConverter(self).serialize(serializer) } } @@ -3107,7 +3126,7 @@ impl ESTree for TSTypeQueryExprName<'_> { match self { Self::TSImportType(it) => it.serialize(serializer), Self::IdentifierReference(it) => { - crate::serialize::TSTypeNameIdentifierReference(it).serialize(serializer) + crate::serialize::ts::TSTypeNameIdentifierReference(it).serialize(serializer) } Self::QualifiedName(it) => it.serialize(serializer), } @@ -3135,7 +3154,7 @@ impl ESTree for TSFunctionType<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("typeParameters", &self.type_parameters); - state.serialize_field("params", &crate::serialize::TSFunctionTypeParams(self)); + state.serialize_field("params", &crate::serialize::ts::TSFunctionTypeParams(self)); state.serialize_field("returnType", &self.return_type); state.end(); } @@ -3163,10 +3182,10 @@ impl ESTree for TSMappedType<'_> { state.serialize_field("end", &self.span.end); state.serialize_field("nameType", &self.name_type); state.serialize_field("typeAnnotation", &self.type_annotation); - state.serialize_field("optional", &crate::serialize::TSMappedTypeOptional(self)); + state.serialize_field("optional", &crate::serialize::ts::TSMappedTypeOptional(self)); state.serialize_field("readonly", &self.readonly); - state.serialize_field("key", &crate::serialize::TSMappedTypeKey(self)); - state.serialize_field("constraint", &crate::serialize::TSMappedTypeConstraint(self)); + state.serialize_field("key", &crate::serialize::ts::TSMappedTypeKey(self)); + state.serialize_field("constraint", &crate::serialize::ts::TSMappedTypeConstraint(self)); state.end(); } } @@ -3174,7 +3193,7 @@ impl ESTree for TSMappedType<'_> { impl ESTree for TSMappedTypeModifierOperator { fn serialize(&self, serializer: S) { match self { - Self::True => crate::serialize::True(()).serialize(serializer), + Self::True => crate::serialize::basic::True(()).serialize(serializer), Self::Plus => JsonSafeString("+").serialize(serializer), Self::Minus => JsonSafeString("-").serialize(serializer), } @@ -3247,7 +3266,7 @@ impl ESTree for TSModuleReference<'_> { match self { Self::ExternalModuleReference(it) => it.serialize(serializer), Self::IdentifierReference(it) => { - crate::serialize::TSTypeNameIdentifierReference(it).serialize(serializer) + crate::serialize::ts::TSTypeNameIdentifierReference(it).serialize(serializer) } Self::QualifiedName(it) => it.serialize(serializer), } diff --git a/crates/oxc_ast/src/serialize.rs b/crates/oxc_ast/src/serialize.rs deleted file mode 100644 index 858ad8e3adb6c..0000000000000 --- a/crates/oxc_ast/src/serialize.rs +++ /dev/null @@ -1,1531 +0,0 @@ -use std::cmp; - -use cow_utils::CowUtils; - -use crate::ast::*; -use oxc_ast_macros::ast_meta; -use oxc_estree::{ - CompactFixesJSSerializer, CompactFixesTSSerializer, CompactJSSerializer, CompactTSSerializer, - Concat2, ConcatElement, ESTree, FlatStructSerializer, JsonSafeString, LoneSurrogatesString, - PrettyFixesJSSerializer, PrettyFixesTSSerializer, PrettyJSSerializer, PrettyTSSerializer, - SequenceSerializer, Serializer, StructSerializer, -}; -use oxc_span::GetSpan; - -/// Main serialization methods for `Program`. -/// -/// Note: 8 separate methods for the different serialization options, rather than 1 method -/// with behavior controlled by flags -/// (e.g. `fn to_estree_json(&self, with_ts: bool, pretty: bool, fixes: bool)`) -/// to avoid bloating binary size. -/// -/// Most consumers (and Oxc crates) will use only 1 of these methods, so we don't want to needlessly -/// compile all 8 serializers when only 1 is used. -/// -/// Initial capacity for serializer's buffer is an estimate based on our benchmark fixtures -/// of ratio of source text size to JSON size. -/// -/// | File | Compact TS | Compact JS | Pretty TS | Pretty JS | -/// |----------------------------|------------|------------|-----------|-----------| -/// | antd.js | 10 | 9 | 76 | 72 | -/// | cal.com.tsx | 10 | 9 | 40 | 37 | -/// | checker.ts | 7 | 6 | 27 | 24 | -/// | pdf.mjs | 13 | 12 | 71 | 67 | -/// | RadixUIAdoptionSection.jsx | 10 | 9 | 45 | 44 | -/// |----------------------------|------------|------------|-----------|-----------| -/// | Maximum | 13 | 12 | 76 | 72 | -/// -/// It's better to over-estimate than under-estimate, as having to grow the buffer is expensive, -/// so have gone on the generous side. -const JSON_CAPACITY_RATIO_COMPACT: usize = 16; -const JSON_CAPACITY_RATIO_PRETTY: usize = 80; - -impl Program<'_> { - /// Serialize AST to ESTree JSON, including TypeScript fields. - pub fn to_estree_ts_json(&self) -> String { - let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT; - let mut serializer = CompactTSSerializer::with_capacity(capacity); - self.serialize(&mut serializer); - serializer.into_string() - } - - /// Serialize AST to ESTree JSON, without TypeScript fields. - pub fn to_estree_js_json(&self) -> String { - let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT; - let mut serializer = CompactJSSerializer::with_capacity(capacity); - self.serialize(&mut serializer); - serializer.into_string() - } - - /// Serialize AST to pretty-printed ESTree JSON, including TypeScript fields. - pub fn to_pretty_estree_ts_json(&self) -> String { - let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY; - let mut serializer = PrettyTSSerializer::with_capacity(capacity); - self.serialize(&mut serializer); - serializer.into_string() - } - - /// Serialize AST to pretty-printed ESTree JSON, without TypeScript fields. - pub fn to_pretty_estree_js_json(&self) -> String { - let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY; - let mut serializer = PrettyJSSerializer::with_capacity(capacity); - self.serialize(&mut serializer); - serializer.into_string() - } - - /// Serialize AST to ESTree JSON, including TypeScript fields, with list of fixes. - pub fn to_estree_ts_json_with_fixes(&self) -> String { - let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT; - let serializer = CompactFixesTSSerializer::with_capacity(capacity); - serializer.serialize_with_fixes(self) - } - - /// Serialize AST to ESTree JSON, without TypeScript fields, with list of fixes. - pub fn to_estree_js_json_with_fixes(&self) -> String { - let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT; - let serializer = CompactFixesJSSerializer::with_capacity(capacity); - serializer.serialize_with_fixes(self) - } - - /// Serialize AST to pretty-printed ESTree JSON, including TypeScript fields, with list of fixes. - pub fn to_pretty_estree_ts_json_with_fixes(&self) -> String { - let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY; - let serializer = PrettyFixesTSSerializer::with_capacity(capacity); - serializer.serialize_with_fixes(self) - } - - /// Serialize AST to pretty-printed ESTree JSON, without TypeScript fields, with list of fixes. - pub fn to_pretty_estree_js_json_with_fixes(&self) -> String { - let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY; - let serializer = PrettyFixesJSSerializer::with_capacity(capacity); - serializer.serialize_with_fixes(self) - } -} - -// -------------------- -// Program -// -------------------- - -/// Serializer for `Program`. -/// -/// In TS AST, set start span to start of first directive or statement. -/// This is required because unlike Acorn, TS-ESLint excludes whitespace and comments -/// from the `Program` start span. -/// See for more info. -/// -/// Special case where first statement is an `ExportNamedDeclaration` or `ExportDefaultDeclaration` -/// exporting a class with decorators, where one of the decorators is before `export`. -/// In these cases, the span of the statement starts after the span of the decorators. -/// e.g. `@dec export class C {}` - `ExportNamedDeclaration` span start is 5, `Decorator` span start is 0. -/// `Program` span start is 0 (not 5). -#[ast_meta] -#[estree(raw_deser = " - const body = DESER[Vec](POS_OFFSET.directives); - body.push(...DESER[Vec](POS_OFFSET.body)); - - /* IF_JS */ - const start = DESER[u32](POS_OFFSET.span.start); - /* END_IF_JS */ - - const end = DESER[u32](POS_OFFSET.span.end); - - /* IF_TS */ - let start; - if (body.length > 0) { - const first = body[0]; - start = first.start; - if (first.type === 'ExportNamedDeclaration' || first.type === 'ExportDefaultDeclaration') { - const {declaration} = first; - if ( - declaration !== null && declaration.type === 'ClassDeclaration' - && declaration.decorators.length > 0 - ) { - const decoratorStart = declaration.decorators[0].start; - if (decoratorStart < start) start = decoratorStart; - } - } - } else { - start = end; - } - /* END_IF_TS */ - - const program = { - type: 'Program', - start, - end, - body, - sourceType: DESER[ModuleKind](POS_OFFSET.source_type.module_kind), - hashbang: DESER[Option](POS_OFFSET.hashbang), - }; - program -")] -pub struct ProgramConverter<'a, 'b>(pub &'b Program<'a>); - -impl ESTree for ProgramConverter<'_, '_> { - fn serialize(&self, serializer: S) { - let program = self.0; - let span_start = - if S::INCLUDE_TS_FIELDS { get_ts_start_span(program) } else { program.span.start }; - - let mut state = serializer.serialize_struct(); - state.serialize_field("type", &JsonSafeString("Program")); - state.serialize_field("start", &span_start); - state.serialize_field("end", &program.span.end); - state.serialize_field("body", &Concat2(&program.directives, &program.body)); - state.serialize_field("sourceType", &program.source_type.module_kind()); - state.serialize_field("hashbang", &program.hashbang); - state.end(); - } -} - -fn get_ts_start_span(program: &Program<'_>) -> u32 { - if let Some(first_directive) = program.directives.first() { - return first_directive.span.start; - } - - let Some(first_stmt) = program.body.first() else { - // Program contains no statements or directives. Span start = span end. - return program.span.end; - }; - - match first_stmt { - Statement::ExportNamedDeclaration(decl) => { - let start = decl.span.start; - if let Some(Declaration::ClassDeclaration(class)) = &decl.declaration { - if let Some(decorator) = class.decorators.first() { - return cmp::min(start, decorator.span.start); - } - } - start - } - Statement::ExportDefaultDeclaration(decl) => { - let start = decl.span.start; - if let ExportDefaultDeclarationKind::ClassDeclaration(class) = &decl.declaration { - if let Some(decorator) = class.decorators.first() { - return cmp::min(start, decorator.span.start); - } - } - start - } - _ => first_stmt.span().start, - } -} - -// -------------------- -// Basic types -// -------------------- - -/// Serialized as `null`. -#[ast_meta] -#[estree(ts_type = "null", raw_deser = "null")] -pub struct Null(pub T); - -impl ESTree for Null { - fn serialize(&self, serializer: S) { - ().serialize(serializer); - } -} - -#[ast_meta] -#[estree(ts_type = "null", raw_deser = "null")] -#[ts] -pub struct TsNull(pub T); - -impl ESTree for TsNull { - fn serialize(&self, serializer: S) { - Null(()).serialize(serializer); - } -} - -/// Serialized as `true`. -#[ast_meta] -#[estree(ts_type = "true", raw_deser = "true")] -pub struct True(pub T); - -impl ESTree for True { - fn serialize(&self, serializer: S) { - true.serialize(serializer); - } -} - -/// Serialized as `false`. -#[ast_meta] -#[estree(ts_type = "false", raw_deser = "false")] -pub struct False(pub T); - -impl ESTree for False { - fn serialize(&self, serializer: S) { - false.serialize(serializer); - } -} - -#[ast_meta] -#[estree(ts_type = "false", raw_deser = "false")] -#[ts] -pub struct TsFalse(pub T); - -impl ESTree for TsFalse { - fn serialize(&self, serializer: S) { - false.serialize(serializer); - } -} - -/// Serialized as `"value"`. -#[ast_meta] -#[estree(ts_type = "'value'", raw_deser = "'value'")] -#[ts] -pub struct TsValue(pub T); - -impl ESTree for TsValue { - fn serialize(&self, serializer: S) { - JsonSafeString("value").serialize(serializer); - } -} - -/// Serialized as `"in"`. -#[ast_meta] -#[estree(ts_type = "'in'", raw_deser = "'in'")] -pub struct In(pub T); - -impl ESTree for In { - fn serialize(&self, serializer: S) { - JsonSafeString("in").serialize(serializer); - } -} - -/// Serialized as `"init"`. -#[ast_meta] -#[estree(ts_type = "'init'", raw_deser = "'init'")] -pub struct Init(pub T); - -impl ESTree for Init { - fn serialize(&self, serializer: S) { - JsonSafeString("init").serialize(serializer); - } -} - -/// Serialized as `"this"`. -#[ast_meta] -#[estree(ts_type = "'this'", raw_deser = "'this'")] -pub struct This(pub T); - -impl ESTree for This { - fn serialize(&self, serializer: S) { - JsonSafeString("this").serialize(serializer); - } -} - -/// Serialized as `[]`. -#[ast_meta] -#[estree(ts_type = "[]", raw_deser = "[]")] -pub struct EmptyArray(pub T); - -impl ESTree for EmptyArray { - fn serialize(&self, serializer: S) { - [(); 0].serialize(serializer); - } -} - -#[ast_meta] -#[estree(ts_type = "[]", raw_deser = "[]")] -#[ts] -pub struct TsEmptyArray(pub T); - -impl ESTree for TsEmptyArray { - fn serialize(&self, serializer: S) { - EmptyArray(()).serialize(serializer); - } -} - -// -------------------- -// Literals -// -------------------- - -/// Serializer for `raw` field of `BooleanLiteral`. -#[ast_meta] -#[estree( - ts_type = "string | null", - raw_deser = "(THIS.start === 0 && THIS.end === 0) ? null : THIS.value + ''" -)] -pub struct BooleanLiteralRaw<'b>(pub &'b BooleanLiteral); - -impl ESTree for BooleanLiteralRaw<'_> { - fn serialize(&self, serializer: S) { - #[expect(clippy::collection_is_never_read)] // Clippy is wrong! - let raw = if self.0.span.is_unspanned() { - None - } else if self.0.value { - Some(JsonSafeString("true")) - } else { - Some(JsonSafeString("false")) - }; - raw.serialize(serializer); - } -} - -/// Serializer for `raw` field of `NullLiteral`. -#[ast_meta] -#[estree( - ts_type = "'null' | null", - raw_deser = "(THIS.start === 0 && THIS.end === 0) ? null : 'null'" -)] -pub struct NullLiteralRaw<'b>(pub &'b NullLiteral); - -impl ESTree for NullLiteralRaw<'_> { - fn serialize(&self, serializer: S) { - #[expect(clippy::collection_is_never_read)] // Clippy is wrong! - let raw = if self.0.span.is_unspanned() { None } else { Some(JsonSafeString("null")) }; - raw.serialize(serializer); - } -} - -/// Serializer for `value` field of `StringLiteral`. -/// -/// Handle when `lone_surrogates` flag is set, indicating the string contains lone surrogates. -#[ast_meta] -#[estree( - ts_type = "string", - raw_deser = r#" - let value = DESER[Atom](POS_OFFSET.value); - if (DESER[bool](POS_OFFSET.lone_surrogates)) { - value = value.replace(/\uFFFD(.{4})/g, (_, hex) => String.fromCodePoint(parseInt(hex, 16))); - } - value - "# -)] -pub struct StringLiteralValue<'a, 'b>(pub &'b StringLiteral<'a>); - -impl ESTree for StringLiteralValue<'_, '_> { - fn serialize(&self, serializer: S) { - let lit = self.0; - #[expect(clippy::if_not_else)] - if !lit.lone_surrogates { - lit.value.serialize(serializer); - } else { - // String contains lone surrogates - self.serialize_lone_surrogates(serializer); - } - } -} - -impl StringLiteralValue<'_, '_> { - #[cold] - #[inline(never)] - fn serialize_lone_surrogates(&self, serializer: S) { - LoneSurrogatesString(self.0.value.as_str()).serialize(serializer); - } -} - -/// Serializer for `bigint` field of `BigIntLiteral`. -#[ast_meta] -#[estree(ts_type = "string", raw_deser = "THIS.raw.slice(0, -1).replace(/_/g, '')")] -pub struct BigIntLiteralBigint<'a, 'b>(pub &'b BigIntLiteral<'a>); - -impl ESTree for BigIntLiteralBigint<'_, '_> { - fn serialize(&self, serializer: S) { - let bigint = self.0.raw[..self.0.raw.len() - 1].cow_replace('_', ""); - JsonSafeString(bigint.as_ref()).serialize(serializer); - } -} - -/// Serializer for `value` field of `BigIntLiteral`. -/// -/// Serialized as `null` in JSON, but updated on JS side to contain a `BigInt`. -#[ast_meta] -#[estree(ts_type = "bigint", raw_deser = "BigInt(THIS.bigint)")] -pub struct BigIntLiteralValue<'a, 'b>(#[expect(dead_code)] pub &'b BigIntLiteral<'a>); - -impl ESTree for BigIntLiteralValue<'_, '_> { - fn serialize(&self, mut serializer: S) { - // Record that this node needs fixing on JS side - serializer.record_fix_path(); - Null(()).serialize(serializer); - } -} - -/// Serializer for `value` field of `RegExpLiteral`. -/// -/// Serialized as `null` in JSON, but updated on JS side to contain a `RegExp` if the regexp is valid. -#[ast_meta] -#[estree( - ts_type = "RegExp | null", - raw_deser = " - let value = null; - try { - value = new RegExp(THIS.regex.pattern, THIS.regex.flags); - } catch (e) {} - value - " -)] -pub struct RegExpLiteralValue<'a, 'b>(#[expect(dead_code)] pub &'b RegExpLiteral<'a>); - -impl ESTree for RegExpLiteralValue<'_, '_> { - fn serialize(&self, mut serializer: S) { - // Record that this node needs fixing on JS side - serializer.record_fix_path(); - Null(()).serialize(serializer); - } -} - -#[ast_meta] -#[estree( - ts_type = "string", - raw_deser = " - const flagBits = DESER[u8](POS); - let flags = ''; - // Alphabetical order - if (flagBits & 64) flags += 'd'; - if (flagBits & 1) flags += 'g'; - if (flagBits & 2) flags += 'i'; - if (flagBits & 4) flags += 'm'; - if (flagBits & 8) flags += 's'; - if (flagBits & 16) flags += 'u'; - if (flagBits & 128) flags += 'v'; - if (flagBits & 32) flags += 'y'; - flags - " -)] -pub struct RegExpFlagsConverter<'b>(pub &'b RegExpFlags); - -impl ESTree for RegExpFlagsConverter<'_> { - fn serialize(&self, serializer: S) { - JsonSafeString(self.0.to_inline_string().as_str()).serialize(serializer); - } -} - -/// Converter for `TemplateElement`. -/// -/// Decode `cooked` if it contains lone surrogates. -/// -/// Also adjust span in TS AST. -/// TS-ESLint produces a different span from Acorn: -/// ```js -/// const template = `abc${x}def${x}ghi`; -/// // Acorn: ^^^ ^^^ ^^^ -/// // TS-ESLint: ^^^^^^ ^^^^^^ ^^^^^ -/// ``` -// TODO: Raise an issue on TS-ESLint and see if they'll change span to match Acorn. -#[ast_meta] -#[estree(raw_deser = r#" - const tail = DESER[bool](POS_OFFSET.tail), - start = DESER[u32](POS_OFFSET.span.start) /* IF_TS */ - 1 /* END_IF_TS */, - end = DESER[u32](POS_OFFSET.span.end) /* IF_TS */ + 2 - tail /* END_IF_TS */, - value = DESER[TemplateElementValue](POS_OFFSET.value); - if (value.cooked !== null && DESER[bool](POS_OFFSET.lone_surrogates)) { - value.cooked = value.cooked - .replace(/\uFFFD(.{4})/g, (_, hex) => String.fromCodePoint(parseInt(hex, 16))); - } - { type: 'TemplateElement', start, end, value, tail } -"#)] -pub struct TemplateElementConverter<'a, 'b>(pub &'b TemplateElement<'a>); - -impl ESTree for TemplateElementConverter<'_, '_> { - fn serialize(&self, serializer: S) { - let element = self.0; - let mut state = serializer.serialize_struct(); - state.serialize_field("type", &JsonSafeString("TemplateElement")); - - let mut span = element.span; - if S::INCLUDE_TS_FIELDS { - span.start -= 1; - span.end += if element.tail { 1 } else { 2 }; - } - state.serialize_field("start", &span.start); - state.serialize_field("end", &span.end); - - state.serialize_field("value", &TemplateElementValue(element)); - state.serialize_field("tail", &element.tail); - state.end(); - } -} - -/// Serializer for `value` field of `TemplateElement`. -/// -/// Handle when `lone_surrogates` flag is set, indicating the cooked string contains lone surrogates. -/// -/// Implementation for `raw_deser` is included in `TemplateElementConverter` above. -#[ast_meta] -#[estree( - ts_type = "TemplateElementValue", - raw_deser = "(() => { throw new Error('Should not appear in deserializer code'); })()" -)] -pub struct TemplateElementValue<'a, 'b>(pub &'b TemplateElement<'a>); - -impl ESTree for TemplateElementValue<'_, '_> { - fn serialize(&self, serializer: S) { - let element = self.0; - #[expect(clippy::if_not_else)] - if !element.lone_surrogates { - element.value.serialize(serializer); - } else { - // String contains lone surrogates - self.serialize_lone_surrogates(serializer); - } - } -} - -impl TemplateElementValue<'_, '_> { - #[cold] - #[inline(never)] - fn serialize_lone_surrogates(&self, serializer: S) { - let value = &self.0.value; - - let mut state = serializer.serialize_struct(); - state.serialize_field("raw", &value.raw); - - let cooked = value.cooked.as_ref().map(|cooked| LoneSurrogatesString(cooked.as_str())); - state.serialize_field("cooked", &cooked); - - state.end(); - } -} - -// -------------------- -// Various -// -------------------- - -/// Serialize `FormalParameters`, to be estree compatible, with `items` and `rest` fields combined -/// and `argument` field flattened. -#[ast_meta] -#[estree( - ts_type = "ParamPattern[]", - raw_deser = " - const params = DESER[Vec](POS_OFFSET.items); - if (uint32[(POS_OFFSET.rest) >> 2] !== 0 && uint32[(POS_OFFSET.rest + 4) >> 2] !== 0) { - pos = uint32[(POS_OFFSET.rest) >> 2]; - params.push({ - type: 'RestElement', - start: DESER[u32]( POS_OFFSET.span.start ), - end: DESER[u32]( POS_OFFSET.span.end ), - argument: DESER[BindingPatternKind]( POS_OFFSET.argument.kind ), - /* IF_TS */ - decorators: [], - optional: DESER[bool]( POS_OFFSET.argument.optional ), - typeAnnotation: DESER[Option>]( - POS_OFFSET.argument.type_annotation - ), - value: null, - /* END_IF_TS */ - }); - } - params - " -)] -pub struct FormalParametersConverter<'a, 'b>(pub &'b FormalParameters<'a>); - -impl ESTree for FormalParametersConverter<'_, '_> { - fn serialize(&self, serializer: S) { - let mut seq = serializer.serialize_sequence(); - self.0.push_to_sequence(&mut seq); - seq.end(); - } -} - -impl ConcatElement for FormalParameters<'_> { - fn push_to_sequence(&self, seq: &mut S) { - self.items.push_to_sequence(seq); - if let Some(rest) = &self.rest { - seq.serialize_element(&FormalParametersRest(rest)); - } - } -} - -struct FormalParametersRest<'a, 'b>(&'b BindingRestElement<'a>); - -impl ESTree for FormalParametersRest<'_, '_> { - fn serialize(&self, serializer: S) { - let rest = self.0; - let mut state = serializer.serialize_struct(); - state.serialize_field("type", &JsonSafeString("RestElement")); - state.serialize_field("start", &rest.span.start); - state.serialize_field("end", &rest.span.end); - state.serialize_field("argument", &rest.argument.kind); - state.serialize_ts_field("decorators", &EmptyArray(())); - state.serialize_ts_field("optional", &rest.argument.optional); - state.serialize_ts_field("typeAnnotation", &rest.argument.type_annotation); - state.serialize_ts_field("value", &Null(())); - state.end(); - } -} - -/// Converter for `FormalParameter`. -/// -/// In TS-ESTree AST, if `accessibility` is `Some`, or `readonly` or `override` is `true`, -/// is serialized as `TSParameterProperty` instead, which has a different object shape. -#[ast_meta] -#[estree( - ts_type = "FormalParameter | TSParameterProperty", - raw_deser = " - /* IF_JS */ - DESER[BindingPatternKind](POS_OFFSET.pattern.kind) - /* END_IF_JS */ - - /* IF_TS */ - const accessibility = DESER[Option](POS_OFFSET.accessibility), - readonly = DESER[bool](POS_OFFSET.readonly), - override = DESER[bool](POS_OFFSET.override); - let param; - if (accessibility === null && !readonly && !override) { - param = { - ...DESER[BindingPatternKind](POS_OFFSET.pattern.kind), - decorators: DESER[Vec](POS_OFFSET.decorators), - optional: DESER[bool](POS_OFFSET.pattern.optional), - typeAnnotation: DESER[Option>](POS_OFFSET.pattern.type_annotation), - }; - } else { - param = { - type: 'TSParameterProperty', - start: DESER[u32](POS_OFFSET.span.start), - end: DESER[u32](POS_OFFSET.span.end), - accessibility, - decorators: DESER[Vec](POS_OFFSET.decorators), - override, - parameter: DESER[BindingPattern](POS_OFFSET.pattern), - readonly, - static: false, - }; - } - param - /* END_IF_TS */ - " -)] -pub struct FormalParameterConverter<'a, 'b>(pub &'b FormalParameter<'a>); - -impl ESTree for FormalParameterConverter<'_, '_> { - fn serialize(&self, serializer: S) { - let param = self.0; - - if S::INCLUDE_TS_FIELDS { - if param.has_modifier() { - let mut state = serializer.serialize_struct(); - state.serialize_field("type", &JsonSafeString("TSParameterProperty")); - state.serialize_field("start", ¶m.span.start); - state.serialize_field("end", ¶m.span.end); - state.serialize_field("accessibility", ¶m.accessibility); - state.serialize_field("decorators", ¶m.decorators); - state.serialize_field("override", ¶m.r#override); - state.serialize_field("parameter", ¶m.pattern); - state.serialize_field("readonly", ¶m.readonly); - state.serialize_field("static", &False(())); - state.end(); - } else { - let mut state = serializer.serialize_struct(); - param.pattern.kind.serialize(FlatStructSerializer(&mut state)); - state.serialize_field("decorators", ¶m.decorators); - state.serialize_field("optional", ¶m.pattern.optional); - state.serialize_field("typeAnnotation", ¶m.pattern.type_annotation); - state.end(); - } - } else { - param.pattern.kind.serialize(serializer); - } - } -} - -/// Serializer for `params` field of `Function`. -/// -/// In TS-ESTree, this adds `this_param` to start of the `params` array. -#[ast_meta] -#[estree( - ts_type = "ParamPattern[]", - raw_deser = " - const params = DESER[Box](POS_OFFSET.params); - /* IF_TS */ - const thisParam = DESER[Option>](POS_OFFSET.this_param); - if (thisParam !== null) params.unshift(thisParam); - /* END_IF_TS */ - params - " -)] -pub struct FunctionParams<'a, 'b>(pub &'b Function<'a>); - -impl ESTree for FunctionParams<'_, '_> { - fn serialize(&self, serializer: S) { - let func = self.0; - if S::INCLUDE_TS_FIELDS { - Concat2(&func.this_param, func.params.as_ref()).serialize(serializer); - } else { - func.params.serialize(serializer); - } - } -} - -/// Serializer for `key` field of `MethodDefinition`. -/// -/// In TS-ESTree `"constructor"` in `class C { "constructor"() {} }` -/// is represented as an `Identifier`. -/// In Acorn and Espree, it's a `Literal`. -/// -#[ast_meta] -#[estree( - ts_type = "PropertyKey", - raw_deser = " - /* IF_JS */ - DESER[PropertyKey](POS_OFFSET.key) - /* END_IF_JS */ - - /* IF_TS */ - let key = DESER[PropertyKey](POS_OFFSET.key); - if (THIS.kind === 'constructor') { - key = { - type: 'Identifier', - start: key.start, - end: key.end, - name: 'constructor', - decorators: [], - optional: false, - typeAnnotation: null, - }; - } - key - /* END_IF_TS */ - " -)] -pub struct MethodDefinitionKey<'a, 'b>(pub &'b MethodDefinition<'a>); - -impl ESTree for MethodDefinitionKey<'_, '_> { - fn serialize(&self, serializer: S) { - let method = self.0; - if S::INCLUDE_TS_FIELDS && method.kind == MethodDefinitionKind::Constructor { - // `key` can only be either an identifier `constructor`, or string `"constructor"` - let span = method.key.span(); - IdentifierName { span, name: Atom::from("constructor") }.serialize(serializer); - } else { - method.key.serialize(serializer); - } - } -} - -/// Serializer for `specifiers` field of `ImportDeclaration`. -/// -/// Serialize `specifiers` as an empty array if it's `None`. -#[ast_meta] -#[estree( - ts_type = "Array", - raw_deser = " - let specifiers = DESER[Option>](POS_OFFSET.specifiers); - if (specifiers === null) specifiers = []; - specifiers - " -)] -pub struct ImportDeclarationSpecifiers<'a, 'b>(pub &'b ImportDeclaration<'a>); - -impl ESTree for ImportDeclarationSpecifiers<'_, '_> { - fn serialize(&self, serializer: S) { - if let Some(specifiers) = &self.0.specifiers { - specifiers.serialize(serializer); - } else { - EmptyArray(()).serialize(serializer); - } - } -} - -/// Serializer for `ArrowFunctionExpression`'s `body` field. -/// -/// Serializes as either an expression (if `expression` property is set), -/// or a `BlockStatement` (if it's not). -#[ast_meta] -#[estree( - ts_type = "FunctionBody | Expression", - raw_deser = " - let body = DESER[Box](POS_OFFSET.body); - THIS.expression ? body.body[0].expression : body - " -)] -pub struct ArrowFunctionExpressionBody<'a>(pub &'a ArrowFunctionExpression<'a>); - -impl ESTree for ArrowFunctionExpressionBody<'_> { - fn serialize(&self, serializer: S) { - if let Some(expression) = self.0.get_expression() { - expression.serialize(serializer); - } else { - self.0.body.serialize(serializer); - } - } -} - -/// Serializer for `AssignmentTargetPropertyIdentifier`'s `init` field -/// (which is renamed to `value` in ESTree AST). -#[ast_meta] -#[estree( - ts_type = "IdentifierReference | AssignmentTargetWithDefault", - raw_deser = " - const init = DESER[Option](POS_OFFSET.init), - keyCopy = {...THIS.key}, - value = init === null - ? keyCopy - : { - type: 'AssignmentPattern', - start: THIS.start, - end: THIS.end, - left: keyCopy, - right: init, - /* IF_TS */ - decorators: [], - optional: false, - typeAnnotation: null, - /* END_IF_TS */ - }; - value - " -)] -pub struct AssignmentTargetPropertyIdentifierValue<'a>( - pub &'a AssignmentTargetPropertyIdentifier<'a>, -); - -impl ESTree for AssignmentTargetPropertyIdentifierValue<'_> { - fn serialize(&self, serializer: S) { - if let Some(init) = &self.0.init { - let mut state = serializer.serialize_struct(); - state.serialize_field("type", &JsonSafeString("AssignmentPattern")); - state.serialize_field("start", &self.0.span.start); - state.serialize_field("end", &self.0.span.end); - state.serialize_field("left", &self.0.binding); - state.serialize_field("right", init); - state.serialize_ts_field("decorators", &EmptyArray(())); - state.serialize_ts_field("optional", &False(())); - state.serialize_ts_field("typeAnnotation", &Null(())); - state.end(); - } else { - self.0.binding.serialize(serializer); - } - } -} - -// Serializers for `with_clause` field of `ImportDeclaration`, `ExportNamedDeclaration`, -// and `ExportAllDeclaration` (which are renamed to `attributes` in ESTree AST). -// -// Serialize only the `with_entries` field of `WithClause`, and serialize `None` as empty array (`[]`). -// -// https://github.com/estree/estree/blob/master/es2025.md#importdeclaration -// https://github.com/estree/estree/blob/master/es2025.md#exportnameddeclaration - -#[ast_meta] -#[estree( - ts_type = "Array", - raw_deser = " - const withClause = DESER[Option>](POS_OFFSET.with_clause); - withClause === null ? [] : withClause.withEntries - " -)] -pub struct ImportDeclarationWithClause<'a, 'b>(pub &'b ImportDeclaration<'a>); - -impl ESTree for ImportDeclarationWithClause<'_, '_> { - fn serialize(&self, serializer: S) { - if let Some(with_clause) = &self.0.with_clause { - with_clause.with_entries.serialize(serializer); - } else { - EmptyArray(()).serialize(serializer); - } - } -} - -#[ast_meta] -#[estree( - ts_type = "Array", - raw_deser = " - const withClause = DESER[Option>](POS_OFFSET.with_clause); - withClause === null ? [] : withClause.withEntries - " -)] -pub struct ExportNamedDeclarationWithClause<'a, 'b>(pub &'b ExportNamedDeclaration<'a>); - -impl ESTree for ExportNamedDeclarationWithClause<'_, '_> { - fn serialize(&self, serializer: S) { - if let Some(with_clause) = &self.0.with_clause { - with_clause.with_entries.serialize(serializer); - } else { - EmptyArray(()).serialize(serializer); - } - } -} - -#[ast_meta] -#[estree( - ts_type = "Array", - raw_deser = " - const withClause = DESER[Option>](POS_OFFSET.with_clause); - withClause === null ? [] : withClause.withEntries - " -)] -pub struct ExportAllDeclarationWithClause<'a, 'b>(pub &'b ExportAllDeclaration<'a>); - -impl ESTree for ExportAllDeclarationWithClause<'_, '_> { - fn serialize(&self, serializer: S) { - if let Some(with_clause) = &self.0.with_clause { - with_clause.with_entries.serialize(serializer); - } else { - EmptyArray(()).serialize(serializer); - } - } -} - -// -------------------- -// JSX -// -------------------- - -/// Serializer for `opening_element` field of `JSXElement`. -/// -/// `selfClosing` field of `JSXOpeningElement` depends on whether `JSXElement` has a `closing_element`. -#[ast_meta] -#[estree( - ts_type = "JSXOpeningElement", - raw_deser = " - const openingElement = DESER[Box](POS_OFFSET.opening_element); - if (THIS.closingElement === null) openingElement.selfClosing = true; - openingElement - " -)] -pub struct JSXElementOpening<'a, 'b>(pub &'b JSXElement<'a>); - -impl ESTree for JSXElementOpening<'_, '_> { - fn serialize(&self, serializer: S) { - let element = self.0; - let opening_element = element.opening_element.as_ref(); - - let mut state = serializer.serialize_struct(); - state.serialize_field("type", &JsonSafeString("JSXOpeningElement")); - state.serialize_field("start", &opening_element.span.start); - state.serialize_field("end", &opening_element.span.end); - state.serialize_field("attributes", &opening_element.attributes); - state.serialize_field("name", &opening_element.name); - state.serialize_field("selfClosing", &element.closing_element.is_none()); - state.serialize_ts_field("typeArguments", &opening_element.type_arguments); - state.end(); - } -} - -/// Converter for `selfClosing` field of `JSXOpeningElement`. -/// -/// This converter is not used for serialization - `JSXElementOpening` above handles serialization. -/// This type is only required to add `selfClosing: boolean` to TS type def, -/// and provide default value of `false` for raw transfer deserializer. -#[ast_meta] -#[estree(ts_type = "boolean", raw_deser = "false")] -pub struct JSXOpeningElementSelfClosing<'a, 'b>(#[expect(dead_code)] pub &'b JSXOpeningElement<'a>); - -impl ESTree for JSXOpeningElementSelfClosing<'_, '_> { - fn serialize(&self, _serializer: S) { - unreachable!() - } -} - -/// Serializer for `IdentifierReference` variant of `JSXElementName` and `JSXMemberExpressionObject`. -/// -/// Convert to `JSXIdentifier`. -#[ast_meta] -#[estree( - ts_type = "JSXIdentifier", - raw_deser = " - const ident = DESER[Box](POS); - {type: 'JSXIdentifier', start: ident.start, end: ident.end, name: ident.name} - " -)] -pub struct JSXElementIdentifierReference<'a, 'b>(pub &'b IdentifierReference<'a>); - -impl ESTree for JSXElementIdentifierReference<'_, '_> { - fn serialize(&self, serializer: S) { - JSXIdentifier { span: self.0.span, name: self.0.name }.serialize(serializer); - } -} - -/// Serializer for `ThisExpression` variant of `JSXElementName` and `JSXMemberExpressionObject`. -/// -/// Convert to `JSXIdentifier`. -#[ast_meta] -#[estree( - ts_type = "JSXIdentifier", - raw_deser = " - const thisExpr = DESER[Box](POS); - {type: 'JSXIdentifier', start: thisExpr.start, end: thisExpr.end, name: 'this'} - " -)] -pub struct JSXElementThisExpression<'b>(pub &'b ThisExpression); - -impl ESTree for JSXElementThisExpression<'_> { - fn serialize(&self, serializer: S) { - JSXIdentifier { span: self.0.span, name: Atom::from("this") }.serialize(serializer); - } -} - -/// Converter for `JSXOpeningFragment`. -/// -/// Add `attributes` and `selfClosing` fields in JS AST, but not in TS AST. -/// Acorn-JSX has these fields, but TS-ESLint parser does not. -/// -/// The extra fields are added to the type as `TsEmptyArray` and `TsFalse`, -/// which are incorrect, as these fields appear only in the *JS* AST, not the TS one. -/// But that results in the fields being optional in TS type definition. -// -// TODO: Find a better way to do this. -#[ast_meta] -#[estree(raw_deser = " - const node = { - type: 'JSXOpeningFragment', - start: DESER[u32](POS_OFFSET.span.start), - end: DESER[u32](POS_OFFSET.span.end), - /* IF_JS */ - attributes: [], - selfClosing: false, - /* END_IF_JS */ - }; - node -")] -pub struct JSXOpeningFragmentConverter<'b>(pub &'b JSXOpeningFragment); - -impl ESTree for JSXOpeningFragmentConverter<'_> { - fn serialize(&self, serializer: S) { - let mut state = serializer.serialize_struct(); - state.serialize_field("type", &JsonSafeString("JSXOpeningFragment")); - state.serialize_field("start", &self.0.span.start); - state.serialize_field("end", &self.0.span.end); - if !S::INCLUDE_TS_FIELDS { - state.serialize_field("attributes", &EmptyArray(())); - state.serialize_field("selfClosing", &False(())); - } - state.end(); - } -} - -// -------------------- -// TS -// -------------------- - -/// Serializer for `computed` field of `TSEnumMember`. -/// -/// `true` if `id` field is one of the computed variants of `TSEnumMemberName`. -// -// TODO: Not ideal to have to include the enum discriminant's value here explicitly. -// Need a "macro" e.g. `ENUM_MATCHES(id, ComputedString | ComputedTemplateString)`. -#[ast_meta] -#[estree(ts_type = "boolean", raw_deser = "DESER[u8](POS_OFFSET.id) > 1")] -pub struct TSEnumMemberComputed<'a, 'b>(pub &'b TSEnumMember<'a>); - -impl ESTree for TSEnumMemberComputed<'_, '_> { - fn serialize(&self, serializer: S) { - matches!( - self.0.id, - TSEnumMemberName::ComputedString(_) | TSEnumMemberName::ComputedTemplateString(_) - ) - .serialize(serializer); - } -} - -/// Serializer for `directive` field of `ExpressionStatement`. -/// This field is always `null`, and only appears in the TS AST, not JS ESTree. -#[ast_meta] -#[estree(ts_type = "string | null", raw_deser = "null")] -#[ts] -pub struct ExpressionStatementDirective<'a, 'b>( - #[expect(dead_code)] pub &'b ExpressionStatement<'a>, -); - -impl ESTree for ExpressionStatementDirective<'_, '_> { - fn serialize(&self, serializer: S) { - Null(()).serialize(serializer); - } -} - -/// Converter for `TSModuleDeclaration`. -/// -/// Our AST represents `module X.Y.Z {}` as 3 x nested `TSModuleDeclaration`s. -/// TS-ESTree represents it as a single `TSModuleDeclaration`, -/// with a nested tree of `TSQualifiedName`s as `id`. -#[ast_meta] -#[estree(raw_deser = " - const kind = DESER[TSModuleDeclarationKind](POS_OFFSET.kind), - global = kind === 'global', - start = DESER[u32](POS_OFFSET.span.start), - end = DESER[u32](POS_OFFSET.span.end), - declare = DESER[bool](POS_OFFSET.declare); - let id = DESER[TSModuleDeclarationName](POS_OFFSET.id), - body = DESER[Option](POS_OFFSET.body); - - // Flatten `body`, and nest `id` - if (body !== null && body.type === 'TSModuleDeclaration') { - let innerId = body.id; - if (innerId.type === 'Identifier') { - id = { - type: 'TSQualifiedName', - start: id.start, - end: innerId.end, - left: id, - right: innerId, - }; - } else { - // Replace `left` of innermost `TSQualifiedName` with a nested `TSQualifiedName` with `id` of - // this module on left, and previous `left` of innermost `TSQualifiedName` on right - while (true) { - innerId.start = id.start; - if (innerId.left.type === 'Identifier') break; - innerId = innerId.left; - } - innerId.left = { - type: 'TSQualifiedName', - start: id.start, - end: innerId.left.end, - left: id, - right: innerId.left, - }; - id = body.id; - } - body = Object.hasOwn(body, 'body') ? body.body : null; - } - - // Skip `body` field if `null` - const node = body === null - ? { type: 'TSModuleDeclaration', start, end, id, kind, declare, global } - : { type: 'TSModuleDeclaration', start, end, id, body, kind, declare, global }; - node -")] -pub struct TSModuleDeclarationConverter<'a, 'b>(pub &'b TSModuleDeclaration<'a>); - -impl ESTree for TSModuleDeclarationConverter<'_, '_> { - fn serialize(&self, serializer: S) { - let module = self.0; - - let mut state = serializer.serialize_struct(); - state.serialize_field("type", &JsonSafeString("TSModuleDeclaration")); - state.serialize_field("start", &module.span.start); - state.serialize_field("end", &module.span.end); - - match &module.body { - Some(TSModuleDeclarationBody::TSModuleDeclaration(inner_module)) => { - // Nested modules e.g. `module X.Y.Z {}`. - // Collect all IDs in a `Vec`, in order they appear (i.e. [`X`, `Y`, `Z`]). - // Also get the inner `TSModuleBlock`. - let mut parts = Vec::with_capacity(4); - - let TSModuleDeclarationName::Identifier(id) = &module.id else { unreachable!() }; - parts.push(id); - - let mut body = None; - let mut inner_module = inner_module.as_ref(); - loop { - let TSModuleDeclarationName::Identifier(id) = &inner_module.id else { - unreachable!() - }; - parts.push(id); - - match &inner_module.body { - Some(TSModuleDeclarationBody::TSModuleDeclaration(inner_inner_module)) => { - inner_module = inner_inner_module.as_ref(); - } - Some(TSModuleDeclarationBody::TSModuleBlock(block)) => { - body = Some(block.as_ref()); - break; - } - None => break, - } - } - - // Serialize `parts` as a nested tree of `TSQualifiedName`s - state.serialize_field("id", &TSModuleDeclarationIdParts(&parts)); - - // Skip `body` field if it's `None` - if let Some(body) = body { - state.serialize_field("body", body); - } - } - Some(TSModuleDeclarationBody::TSModuleBlock(block)) => { - // No nested modules. - // Serialize as usual, with `id` being either a `BindingIdentifier` or `StringLiteral`. - state.serialize_field("id", &module.id); - state.serialize_field("body", block); - } - None => { - // No body. Skip `body` field. - state.serialize_field("id", &module.id); - } - } - - state.serialize_field("kind", &module.kind); - state.serialize_field("declare", &module.declare); - state.serialize_field("global", &crate::serialize::TSModuleDeclarationGlobal(module)); - state.end(); - } -} - -struct TSModuleDeclarationIdParts<'a, 'b>(&'b [&'b BindingIdentifier<'a>]); - -impl ESTree for TSModuleDeclarationIdParts<'_, '_> { - fn serialize(&self, serializer: S) { - let parts = self.0; - assert!(!parts.is_empty()); - - let span_start = parts[0].span.start; - let (&last, rest) = parts.split_last().unwrap(); - - let mut state = serializer.serialize_struct(); - state.serialize_field("type", &JsonSafeString("TSQualifiedName")); - state.serialize_field("start", &span_start); - state.serialize_field("end", &last.span.end); - - if rest.len() == 1 { - // Only one part remaining (e.g. `X`). Serialize as `Identifier`. - state.serialize_field("left", &rest[0]); - } else { - // Multiple parts remaining (e.g. `X.Y`). Recurse to serialize as `TSQualifiedName`. - state.serialize_field("left", &TSModuleDeclarationIdParts(rest)); - } - - state.serialize_field("right", last); - state.end(); - } -} - -/// Serializer for `global` field of `TSModuleDeclaration`. -#[ast_meta] -#[estree(ts_type = "boolean", raw_deser = "THIS.kind === 'global'")] -pub struct TSModuleDeclarationGlobal<'a, 'b>(pub &'b TSModuleDeclaration<'a>); - -impl ESTree for TSModuleDeclarationGlobal<'_, '_> { - fn serialize(&self, serializer: S) { - self.0.kind.is_global().serialize(serializer); - } -} - -/// Serializer for `optional` field of `TSMappedType`. -/// -/// `None` is serialized as `false`. -#[ast_meta] -#[estree( - ts_type = "TSMappedTypeModifierOperator | false", - raw_deser = " - let optional = DESER[Option](POS_OFFSET.optional) || false; - if (optional === null) optional = false; - optional - " -)] -pub struct TSMappedTypeOptional<'a, 'b>(pub &'b TSMappedType<'a>); - -impl ESTree for TSMappedTypeOptional<'_, '_> { - fn serialize(&self, serializer: S) { - if let Some(optional) = self.0.optional { - optional.serialize(serializer); - } else { - False(()).serialize(serializer); - } - } -} - -/// Serializer for `key` and `constraint` field of `TSMappedType`. -#[ast_meta] -#[estree( - ts_type = "TSTypeParameter['name']", - raw_deser = " - const typeParameter = DESER[Box](POS_OFFSET.type_parameter); - typeParameter.name - " -)] -pub struct TSMappedTypeKey<'a, 'b>(pub &'b TSMappedType<'a>); - -impl ESTree for TSMappedTypeKey<'_, '_> { - fn serialize(&self, serializer: S) { - self.0.type_parameter.name.serialize(serializer); - } -} - -// NOTE: Variable `typeParameter` in `raw_deser` is shared between `key` and `constraint` serializers. -// They will be concatenated in the generated code. -#[ast_meta] -#[estree(ts_type = "TSTypeParameter['constraint']", raw_deser = "typeParameter.constraint")] -pub struct TSMappedTypeConstraint<'a, 'b>(pub &'b TSMappedType<'a>); - -impl ESTree for TSMappedTypeConstraint<'_, '_> { - fn serialize(&self, serializer: S) { - self.0.type_parameter.constraint.serialize(serializer); - } -} - -/// Serializer for `IdentifierReference` variant of `TSTypeName`. -/// -/// Where is an identifier called `this`, TS-ESTree presents it as a `ThisExpression`. -#[ast_meta] -#[estree( - ts_type = "IdentifierReference | ThisExpression", - raw_deser = " - let id = DESER[Box](POS); - if (id.name === 'this') id = { type: 'ThisExpression', start: id.start, end: id.end }; - id - " -)] -pub struct TSTypeNameIdentifierReference<'a, 'b>(pub &'b IdentifierReference<'a>); - -impl ESTree for TSTypeNameIdentifierReference<'_, '_> { - fn serialize(&self, serializer: S) { - let ident = self.0; - if ident.name == "this" { - ThisExpression { span: ident.span }.serialize(serializer); - } else { - ident.serialize(serializer); - } - } -} - -/// Serializer for `expression` field of `TSClassImplements`. -/// -/// Our AST represents `X.Y` in `class C implements X.Y {}` as a `TSQualifiedName`. -/// TS-ESTree represents `X.Y` as a `MemberExpression`. -/// -/// Where there are more parts e.g. `class C implements X.Y.Z {}`, the `TSQualifiedName`s (Oxc) -/// or `MemberExpression`s (TS-ESTree) are nested. -#[ast_meta] -#[estree( - ts_type = "IdentifierReference | ThisExpression | MemberExpression", - raw_deser = " - let expression = DESER[TSTypeName](POS_OFFSET.expression); - if (expression.type === 'TSQualifiedName') { - let parent = expression = { - type: 'MemberExpression', - start: expression.start, - end: expression.end, - object: expression.left, - property: expression.right, - computed: false, - optional: false, - }; - - while (parent.object.type === 'TSQualifiedName') { - const object = parent.object; - parent = parent.object = { - type: 'MemberExpression', - start: object.start, - end: object.end, - object: object.left, - property: object.right, - computed: false, - optional: false, - }; - } - } - expression - " -)] -pub struct TSClassImplementsExpression<'a, 'b>(pub &'b TSClassImplements<'a>); - -impl ESTree for TSClassImplementsExpression<'_, '_> { - #[inline] // Because it just delegates - fn serialize(&self, serializer: S) { - TSTypeNameAsMemberExpression(&self.0.expression).serialize(serializer); - } -} - -struct TSTypeNameAsMemberExpression<'a, 'b>(&'b TSTypeName<'a>); - -impl ESTree for TSTypeNameAsMemberExpression<'_, '_> { - fn serialize(&self, serializer: S) { - match self.0 { - TSTypeName::IdentifierReference(ident) => { - TSTypeNameIdentifierReference(ident).serialize(serializer); - } - TSTypeName::QualifiedName(name) => { - // Convert to `TSQualifiedName` to `MemberExpression`. - // Recursively convert `left` to `MemberExpression` too if it's a `TSQualifiedName`. - let mut state = serializer.serialize_struct(); - state.serialize_field("type", &JsonSafeString("MemberExpression")); - state.serialize_field("start", &name.span.start); - state.serialize_field("end", &name.span.end); - state.serialize_field("object", &TSTypeNameAsMemberExpression(&name.left)); - state.serialize_field("property", &name.right); - state.serialize_field("computed", &false); - state.serialize_field("optional", &false); - state.end(); - } - } - } -} - -/// Serializer for `params` field of `TSCallSignatureDeclaration`. -/// -/// These add `this_param` to start of the `params` array. -#[ast_meta] -#[estree( - ts_type = "ParamPattern[]", - raw_deser = " - const params = DESER[Box](POS_OFFSET.params); - const thisParam = DESER[Option>](POS_OFFSET.this_param); - if (thisParam !== null) params.unshift(thisParam); - params - " -)] -pub struct TSCallSignatureDeclarationParams<'a, 'b>(pub &'b TSCallSignatureDeclaration<'a>); - -impl ESTree for TSCallSignatureDeclarationParams<'_, '_> { - fn serialize(&self, serializer: S) { - let decl = self.0; - Concat2(&decl.this_param, decl.params.as_ref()).serialize(serializer); - } -} - -/// Serializer for `params` field of `TSMethodSignature`. -/// -/// These add `this_param` to start of the `params` array. -#[ast_meta] -#[estree( - ts_type = "ParamPattern[]", - raw_deser = " - const params = DESER[Box](POS_OFFSET.params); - const thisParam = DESER[Option>](POS_OFFSET.this_param); - if (thisParam !== null) params.unshift(thisParam); - params - " -)] -pub struct TSMethodSignatureParams<'a, 'b>(pub &'b TSMethodSignature<'a>); - -impl ESTree for TSMethodSignatureParams<'_, '_> { - fn serialize(&self, serializer: S) { - let sig = self.0; - Concat2(&sig.this_param, sig.params.as_ref()).serialize(serializer); - } -} - -/// Serializer for `params` field of `TSFunctionType`. -/// -/// These add `this_param` to start of the `params` array. -#[ast_meta] -#[estree( - ts_type = "ParamPattern[]", - raw_deser = " - const params = DESER[Box](POS_OFFSET.params); - const thisParam = DESER[Option>](POS_OFFSET.this_param); - if (thisParam !== null) params.unshift(thisParam); - params - " -)] -pub struct TSFunctionTypeParams<'a, 'b>(pub &'b TSFunctionType<'a>); - -impl ESTree for TSFunctionTypeParams<'_, '_> { - fn serialize(&self, serializer: S) { - let fn_type = self.0; - Concat2(&fn_type.this_param, fn_type.params.as_ref()).serialize(serializer); - } -} - -// -------------------- -// Comments -// -------------------- - -/// Serialize `value` field of `Comment`. -/// -/// This serializer does not work for JSON serializer, because there's no access to source text -/// in `fn serialize`. But in any case, comments often contain characters which need escaping in JSON, -/// which is slow, so it's probably faster to transfer comments as NAPI types (which we do). -/// -/// This meta type is only present for raw transfer, which can transfer faster. -#[ast_meta] -#[estree( - ts_type = "string", - raw_deser = " - const endCut = THIS.type === 'Line' ? 0 : 2; - SOURCE_TEXT.slice(THIS.start + 2, THIS.end - endCut) - " -)] -pub struct CommentValue<'b>(#[expect(dead_code)] pub &'b Comment); - -impl ESTree for CommentValue<'_> { - #[expect(clippy::unimplemented)] - fn serialize(&self, _serializer: S) { - unimplemented!(); - } -} diff --git a/crates/oxc_ast/src/serialize/basic.rs b/crates/oxc_ast/src/serialize/basic.rs new file mode 100644 index 0000000000000..a4d71c3fb2490 --- /dev/null +++ b/crates/oxc_ast/src/serialize/basic.rs @@ -0,0 +1,127 @@ +use oxc_ast_macros::ast_meta; +use oxc_estree::{ESTree, JsonSafeString, Serializer}; + +/// Serialized as `null`. +#[ast_meta] +#[estree(ts_type = "null", raw_deser = "null")] +pub struct Null(pub T); + +impl ESTree for Null { + fn serialize(&self, serializer: S) { + ().serialize(serializer); + } +} + +/// Serialized as `null`. Field only present in TS-ESTree AST. +#[ast_meta] +#[estree(ts_type = "null", raw_deser = "null")] +#[ts] +pub struct TsNull(pub T); + +impl ESTree for TsNull { + fn serialize(&self, serializer: S) { + Null(()).serialize(serializer); + } +} + +/// Serialized as `true`. +#[ast_meta] +#[estree(ts_type = "true", raw_deser = "true")] +pub struct True(pub T); + +impl ESTree for True { + fn serialize(&self, serializer: S) { + true.serialize(serializer); + } +} + +/// Serialized as `false`. +#[ast_meta] +#[estree(ts_type = "false", raw_deser = "false")] +pub struct False(pub T); + +impl ESTree for False { + fn serialize(&self, serializer: S) { + false.serialize(serializer); + } +} + +/// Serialized as `false`. Field only present in TS-ESTree AST. +#[ast_meta] +#[estree(ts_type = "false", raw_deser = "false")] +#[ts] +pub struct TsFalse(pub T); + +impl ESTree for TsFalse { + fn serialize(&self, serializer: S) { + false.serialize(serializer); + } +} + +/// Serialized as `"value"`. +#[ast_meta] +#[estree(ts_type = "'value'", raw_deser = "'value'")] +#[ts] +pub struct TsValue(pub T); + +impl ESTree for TsValue { + fn serialize(&self, serializer: S) { + JsonSafeString("value").serialize(serializer); + } +} + +/// Serialized as `"in"`. +#[ast_meta] +#[estree(ts_type = "'in'", raw_deser = "'in'")] +pub struct In(pub T); + +impl ESTree for In { + fn serialize(&self, serializer: S) { + JsonSafeString("in").serialize(serializer); + } +} + +/// Serialized as `"init"`. +#[ast_meta] +#[estree(ts_type = "'init'", raw_deser = "'init'")] +pub struct Init(pub T); + +impl ESTree for Init { + fn serialize(&self, serializer: S) { + JsonSafeString("init").serialize(serializer); + } +} + +/// Serialized as `"this"`. +#[ast_meta] +#[estree(ts_type = "'this'", raw_deser = "'this'")] +pub struct This(pub T); + +impl ESTree for This { + fn serialize(&self, serializer: S) { + JsonSafeString("this").serialize(serializer); + } +} + +/// Serialized as `[]`. +#[ast_meta] +#[estree(ts_type = "[]", raw_deser = "[]")] +pub struct EmptyArray(pub T); + +impl ESTree for EmptyArray { + fn serialize(&self, serializer: S) { + [(); 0].serialize(serializer); + } +} + +/// Serialized as `[]`. Field only present in TS-ESTree AST. +#[ast_meta] +#[estree(ts_type = "[]", raw_deser = "[]")] +#[ts] +pub struct TsEmptyArray(pub T); + +impl ESTree for TsEmptyArray { + fn serialize(&self, serializer: S) { + EmptyArray(()).serialize(serializer); + } +} diff --git a/crates/oxc_ast/src/serialize/js.rs b/crates/oxc_ast/src/serialize/js.rs new file mode 100644 index 0000000000000..9f4308f094771 --- /dev/null +++ b/crates/oxc_ast/src/serialize/js.rs @@ -0,0 +1,399 @@ +use oxc_ast_macros::ast_meta; +use oxc_estree::{ + Concat2, ConcatElement, ESTree, FlatStructSerializer, JsonSafeString, SequenceSerializer, + Serializer, StructSerializer, +}; +use oxc_span::GetSpan; + +use crate::ast::*; + +use super::{EmptyArray, Null}; + +// -------------------- +// Function params +// -------------------- + +/// Converter for `FormalParameters`. +/// +/// Combine `items` and `rest` fields. Convert `rest` field. +#[ast_meta] +#[estree( + ts_type = "ParamPattern[]", + raw_deser = " + const params = DESER[Vec](POS_OFFSET.items); + if (uint32[(POS_OFFSET.rest) >> 2] !== 0 && uint32[(POS_OFFSET.rest + 4) >> 2] !== 0) { + pos = uint32[(POS_OFFSET.rest) >> 2]; + params.push({ + type: 'RestElement', + start: DESER[u32]( POS_OFFSET.span.start ), + end: DESER[u32]( POS_OFFSET.span.end ), + argument: DESER[BindingPatternKind]( POS_OFFSET.argument.kind ), + /* IF_TS */ + decorators: [], + optional: DESER[bool]( POS_OFFSET.argument.optional ), + typeAnnotation: DESER[Option>]( + POS_OFFSET.argument.type_annotation + ), + value: null, + /* END_IF_TS */ + }); + } + params + " +)] +pub struct FormalParametersConverter<'a, 'b>(pub &'b FormalParameters<'a>); + +impl ESTree for FormalParametersConverter<'_, '_> { + fn serialize(&self, serializer: S) { + let mut seq = serializer.serialize_sequence(); + self.0.push_to_sequence(&mut seq); + seq.end(); + } +} + +impl ConcatElement for FormalParameters<'_> { + fn push_to_sequence(&self, seq: &mut S) { + self.items.push_to_sequence(seq); + if let Some(rest) = &self.rest { + seq.serialize_element(&FormalParametersRest(rest)); + } + } +} + +struct FormalParametersRest<'a, 'b>(&'b BindingRestElement<'a>); + +impl ESTree for FormalParametersRest<'_, '_> { + fn serialize(&self, serializer: S) { + let rest = self.0; + let mut state = serializer.serialize_struct(); + state.serialize_field("type", &JsonSafeString("RestElement")); + state.serialize_field("start", &rest.span.start); + state.serialize_field("end", &rest.span.end); + state.serialize_field("argument", &rest.argument.kind); + state.serialize_ts_field("decorators", &EmptyArray(())); + state.serialize_ts_field("optional", &rest.argument.optional); + state.serialize_ts_field("typeAnnotation", &rest.argument.type_annotation); + state.serialize_ts_field("value", &Null(())); + state.end(); + } +} + +/// Converter for `FormalParameter`. +/// +/// In TS-ESTree AST, if `accessibility` is `Some`, or `readonly` or `override` is `true`, +/// is serialized as `TSParameterProperty` instead, which has a different object shape. +#[ast_meta] +#[estree( + ts_type = "FormalParameter | TSParameterProperty", + raw_deser = " + /* IF_JS */ + DESER[BindingPatternKind](POS_OFFSET.pattern.kind) + /* END_IF_JS */ + + /* IF_TS */ + const accessibility = DESER[Option](POS_OFFSET.accessibility), + readonly = DESER[bool](POS_OFFSET.readonly), + override = DESER[bool](POS_OFFSET.override); + let param; + if (accessibility === null && !readonly && !override) { + param = { + ...DESER[BindingPatternKind](POS_OFFSET.pattern.kind), + decorators: DESER[Vec](POS_OFFSET.decorators), + optional: DESER[bool](POS_OFFSET.pattern.optional), + typeAnnotation: DESER[Option>](POS_OFFSET.pattern.type_annotation), + }; + } else { + param = { + type: 'TSParameterProperty', + start: DESER[u32](POS_OFFSET.span.start), + end: DESER[u32](POS_OFFSET.span.end), + accessibility, + decorators: DESER[Vec](POS_OFFSET.decorators), + override, + parameter: DESER[BindingPattern](POS_OFFSET.pattern), + readonly, + static: false, + }; + } + param + /* END_IF_TS */ + " +)] +pub struct FormalParameterConverter<'a, 'b>(pub &'b FormalParameter<'a>); + +impl ESTree for FormalParameterConverter<'_, '_> { + fn serialize(&self, serializer: S) { + let param = self.0; + + if S::INCLUDE_TS_FIELDS { + if param.has_modifier() { + let mut state = serializer.serialize_struct(); + state.serialize_field("type", &JsonSafeString("TSParameterProperty")); + state.serialize_field("start", ¶m.span.start); + state.serialize_field("end", ¶m.span.end); + state.serialize_field("accessibility", ¶m.accessibility); + state.serialize_field("decorators", ¶m.decorators); + state.serialize_field("override", ¶m.r#override); + state.serialize_field("parameter", ¶m.pattern); + state.serialize_field("readonly", ¶m.readonly); + state.serialize_field("static", &false); + state.end(); + } else { + let mut state = serializer.serialize_struct(); + param.pattern.kind.serialize(FlatStructSerializer(&mut state)); + state.serialize_field("decorators", ¶m.decorators); + state.serialize_field("optional", ¶m.pattern.optional); + state.serialize_field("typeAnnotation", ¶m.pattern.type_annotation); + state.end(); + } + } else { + param.pattern.kind.serialize(serializer); + } + } +} + +/// Serializer for `params` field of `Function`. +/// +/// In TS-ESTree, this adds `this_param` to start of the `params` array. +#[ast_meta] +#[estree( + ts_type = "ParamPattern[]", + raw_deser = " + const params = DESER[Box](POS_OFFSET.params); + /* IF_TS */ + const thisParam = DESER[Option>](POS_OFFSET.this_param); + if (thisParam !== null) params.unshift(thisParam); + /* END_IF_TS */ + params + " +)] +pub struct FunctionParams<'a, 'b>(pub &'b Function<'a>); + +impl ESTree for FunctionParams<'_, '_> { + fn serialize(&self, serializer: S) { + let func = self.0; + if S::INCLUDE_TS_FIELDS { + Concat2(&func.this_param, func.params.as_ref()).serialize(serializer); + } else { + func.params.serialize(serializer); + } + } +} + +// -------------------- +// Import / export +// -------------------- + +/// Serializer for `specifiers` field of `ImportDeclaration`. +/// +/// Serialize `specifiers` as an empty array if it's `None`. +#[ast_meta] +#[estree( + ts_type = "Array", + raw_deser = " + let specifiers = DESER[Option>](POS_OFFSET.specifiers); + if (specifiers === null) specifiers = []; + specifiers + " +)] +pub struct ImportDeclarationSpecifiers<'a, 'b>(pub &'b ImportDeclaration<'a>); + +impl ESTree for ImportDeclarationSpecifiers<'_, '_> { + fn serialize(&self, serializer: S) { + if let Some(specifiers) = &self.0.specifiers { + specifiers.serialize(serializer); + } else { + EmptyArray(()).serialize(serializer); + } + } +} + +// Serializers for `with_clause` field of `ImportDeclaration`, `ExportNamedDeclaration`, +// and `ExportAllDeclaration` (which are renamed to `attributes` in ESTree AST). +// +// Serialize only the `with_entries` field of `WithClause`, and serialize `None` as empty array (`[]`). +// +// https://github.com/estree/estree/blob/master/es2025.md#importdeclaration +// https://github.com/estree/estree/blob/master/es2025.md#exportnameddeclaration + +#[ast_meta] +#[estree( + ts_type = "Array", + raw_deser = " + const withClause = DESER[Option>](POS_OFFSET.with_clause); + withClause === null ? [] : withClause.withEntries + " +)] +pub struct ImportDeclarationWithClause<'a, 'b>(pub &'b ImportDeclaration<'a>); + +impl ESTree for ImportDeclarationWithClause<'_, '_> { + fn serialize(&self, serializer: S) { + if let Some(with_clause) = &self.0.with_clause { + with_clause.with_entries.serialize(serializer); + } else { + EmptyArray(()).serialize(serializer); + } + } +} + +#[ast_meta] +#[estree( + ts_type = "Array", + raw_deser = " + const withClause = DESER[Option>](POS_OFFSET.with_clause); + withClause === null ? [] : withClause.withEntries + " +)] +pub struct ExportNamedDeclarationWithClause<'a, 'b>(pub &'b ExportNamedDeclaration<'a>); + +impl ESTree for ExportNamedDeclarationWithClause<'_, '_> { + fn serialize(&self, serializer: S) { + if let Some(with_clause) = &self.0.with_clause { + with_clause.with_entries.serialize(serializer); + } else { + EmptyArray(()).serialize(serializer); + } + } +} + +#[ast_meta] +#[estree( + ts_type = "Array", + raw_deser = " + const withClause = DESER[Option>](POS_OFFSET.with_clause); + withClause === null ? [] : withClause.withEntries + " +)] +pub struct ExportAllDeclarationWithClause<'a, 'b>(pub &'b ExportAllDeclaration<'a>); + +impl ESTree for ExportAllDeclarationWithClause<'_, '_> { + fn serialize(&self, serializer: S) { + if let Some(with_clause) = &self.0.with_clause { + with_clause.with_entries.serialize(serializer); + } else { + EmptyArray(()).serialize(serializer); + } + } +} + +// -------------------- +// Misc +// -------------------- + +/// Serializer for `key` field of `MethodDefinition`. +/// +/// In TS-ESTree `"constructor"` in `class C { "constructor"() {} }` +/// is represented as an `Identifier`. +/// In Acorn and Espree, it's a `Literal`. +/// +#[ast_meta] +#[estree( + ts_type = "PropertyKey", + raw_deser = " + /* IF_JS */ + DESER[PropertyKey](POS_OFFSET.key) + /* END_IF_JS */ + + /* IF_TS */ + let key = DESER[PropertyKey](POS_OFFSET.key); + if (THIS.kind === 'constructor') { + key = { + type: 'Identifier', + start: key.start, + end: key.end, + name: 'constructor', + decorators: [], + optional: false, + typeAnnotation: null, + }; + } + key + /* END_IF_TS */ + " +)] +pub struct MethodDefinitionKey<'a, 'b>(pub &'b MethodDefinition<'a>); + +impl ESTree for MethodDefinitionKey<'_, '_> { + fn serialize(&self, serializer: S) { + let method = self.0; + if S::INCLUDE_TS_FIELDS && method.kind == MethodDefinitionKind::Constructor { + // `key` can only be either an identifier `constructor`, or string `"constructor"` + let span = method.key.span(); + IdentifierName { span, name: Atom::from("constructor") }.serialize(serializer); + } else { + method.key.serialize(serializer); + } + } +} + +/// Serializer for `ArrowFunctionExpression`'s `body` field. +/// +/// Serialize as either an expression (if `expression` property is set), +/// or a `BlockStatement` (if it's not). +#[ast_meta] +#[estree( + ts_type = "FunctionBody | Expression", + raw_deser = " + let body = DESER[Box](POS_OFFSET.body); + THIS.expression ? body.body[0].expression : body + " +)] +pub struct ArrowFunctionExpressionBody<'a>(pub &'a ArrowFunctionExpression<'a>); + +impl ESTree for ArrowFunctionExpressionBody<'_> { + fn serialize(&self, serializer: S) { + if let Some(expression) = self.0.get_expression() { + expression.serialize(serializer); + } else { + self.0.body.serialize(serializer); + } + } +} + +/// Serializer for `AssignmentTargetPropertyIdentifier`'s `init` field +/// (which is renamed to `value` in ESTree AST). +#[ast_meta] +#[estree( + ts_type = "IdentifierReference | AssignmentTargetWithDefault", + raw_deser = " + const init = DESER[Option](POS_OFFSET.init), + keyCopy = {...THIS.key}, + value = init === null + ? keyCopy + : { + type: 'AssignmentPattern', + start: THIS.start, + end: THIS.end, + left: keyCopy, + right: init, + /* IF_TS */ + decorators: [], + optional: false, + typeAnnotation: null, + /* END_IF_TS */ + }; + value + " +)] +pub struct AssignmentTargetPropertyIdentifierValue<'a>( + pub &'a AssignmentTargetPropertyIdentifier<'a>, +); + +impl ESTree for AssignmentTargetPropertyIdentifierValue<'_> { + fn serialize(&self, serializer: S) { + if let Some(init) = &self.0.init { + let mut state = serializer.serialize_struct(); + state.serialize_field("type", &JsonSafeString("AssignmentPattern")); + state.serialize_field("start", &self.0.span.start); + state.serialize_field("end", &self.0.span.end); + state.serialize_field("left", &self.0.binding); + state.serialize_field("right", init); + state.serialize_ts_field("decorators", &EmptyArray(())); + state.serialize_ts_field("optional", &false); + state.serialize_ts_field("typeAnnotation", &Null(())); + state.end(); + } else { + self.0.binding.serialize(serializer); + } + } +} diff --git a/crates/oxc_ast/src/serialize/jsx.rs b/crates/oxc_ast/src/serialize/jsx.rs new file mode 100644 index 0000000000000..8d590729704f4 --- /dev/null +++ b/crates/oxc_ast/src/serialize/jsx.rs @@ -0,0 +1,129 @@ +use oxc_ast_macros::ast_meta; +use oxc_estree::{ESTree, JsonSafeString, Serializer, StructSerializer}; + +use crate::ast::*; + +use super::EmptyArray; + +/// Serializer for `opening_element` field of `JSXElement`. +/// +/// `selfClosing` field of `JSXOpeningElement` depends on whether `JSXElement` has a `closing_element`. +#[ast_meta] +#[estree( + ts_type = "JSXOpeningElement", + raw_deser = " + const openingElement = DESER[Box](POS_OFFSET.opening_element); + if (THIS.closingElement === null) openingElement.selfClosing = true; + openingElement + " +)] +pub struct JSXElementOpening<'a, 'b>(pub &'b JSXElement<'a>); + +impl ESTree for JSXElementOpening<'_, '_> { + fn serialize(&self, serializer: S) { + let element = self.0; + let opening_element = element.opening_element.as_ref(); + + let mut state = serializer.serialize_struct(); + state.serialize_field("type", &JsonSafeString("JSXOpeningElement")); + state.serialize_field("start", &opening_element.span.start); + state.serialize_field("end", &opening_element.span.end); + state.serialize_field("attributes", &opening_element.attributes); + state.serialize_field("name", &opening_element.name); + state.serialize_field("selfClosing", &element.closing_element.is_none()); + state.serialize_ts_field("typeArguments", &opening_element.type_arguments); + state.end(); + } +} + +/// Converter for `selfClosing` field of `JSXOpeningElement`. +/// +/// This converter is not used for serialization - `JSXElementOpening` above handles serialization. +/// This type is only required to add `selfClosing: boolean` to TS type def, +/// and provide default value of `false` for raw transfer deserializer. +#[ast_meta] +#[estree(ts_type = "boolean", raw_deser = "false")] +pub struct JSXOpeningElementSelfClosing<'a, 'b>(#[expect(dead_code)] pub &'b JSXOpeningElement<'a>); + +impl ESTree for JSXOpeningElementSelfClosing<'_, '_> { + fn serialize(&self, _serializer: S) { + unreachable!() + } +} + +/// Serializer for `IdentifierReference` variant of `JSXElementName` and `JSXMemberExpressionObject`. +/// +/// Convert to `JSXIdentifier`. +#[ast_meta] +#[estree( + ts_type = "JSXIdentifier", + raw_deser = " + const ident = DESER[Box](POS); + {type: 'JSXIdentifier', start: ident.start, end: ident.end, name: ident.name} + " +)] +pub struct JSXElementIdentifierReference<'a, 'b>(pub &'b IdentifierReference<'a>); + +impl ESTree for JSXElementIdentifierReference<'_, '_> { + fn serialize(&self, serializer: S) { + JSXIdentifier { span: self.0.span, name: self.0.name }.serialize(serializer); + } +} + +/// Serializer for `ThisExpression` variant of `JSXElementName` and `JSXMemberExpressionObject`. +/// +/// Convert to `JSXIdentifier`. +#[ast_meta] +#[estree( + ts_type = "JSXIdentifier", + raw_deser = " + const thisExpr = DESER[Box](POS); + {type: 'JSXIdentifier', start: thisExpr.start, end: thisExpr.end, name: 'this'} + " +)] +pub struct JSXElementThisExpression<'b>(pub &'b ThisExpression); + +impl ESTree for JSXElementThisExpression<'_> { + fn serialize(&self, serializer: S) { + JSXIdentifier { span: self.0.span, name: Atom::from("this") }.serialize(serializer); + } +} + +/// Converter for `JSXOpeningFragment`. +/// +/// Add `attributes` and `selfClosing` fields in JS AST, but not in TS AST. +/// Acorn-JSX has these fields, but TS-ESLint parser does not. +/// +/// The extra fields are added to the type as `TsEmptyArray` and `TsFalse`, +/// which are incorrect, as these fields appear only in the *JS* AST, not the TS one. +/// But that results in the fields being optional in TS type definition. +// +// TODO: Find a better way to do this. +#[ast_meta] +#[estree(raw_deser = " + const node = { + type: 'JSXOpeningFragment', + start: DESER[u32](POS_OFFSET.span.start), + end: DESER[u32](POS_OFFSET.span.end), + /* IF_JS */ + attributes: [], + selfClosing: false, + /* END_IF_JS */ + }; + node +")] +pub struct JSXOpeningFragmentConverter<'b>(pub &'b JSXOpeningFragment); + +impl ESTree for JSXOpeningFragmentConverter<'_> { + fn serialize(&self, serializer: S) { + let mut state = serializer.serialize_struct(); + state.serialize_field("type", &JsonSafeString("JSXOpeningFragment")); + state.serialize_field("start", &self.0.span.start); + state.serialize_field("end", &self.0.span.end); + if !S::INCLUDE_TS_FIELDS { + state.serialize_field("attributes", &EmptyArray(())); + state.serialize_field("selfClosing", &false); + } + state.end(); + } +} diff --git a/crates/oxc_ast/src/serialize/literal.rs b/crates/oxc_ast/src/serialize/literal.rs new file mode 100644 index 0000000000000..de31e4a16d262 --- /dev/null +++ b/crates/oxc_ast/src/serialize/literal.rs @@ -0,0 +1,257 @@ +use cow_utils::CowUtils; + +use oxc_ast_macros::ast_meta; +use oxc_estree::{ESTree, JsonSafeString, LoneSurrogatesString, Serializer, StructSerializer}; + +use crate::ast::*; + +use super::Null; + +/// Serializer for `raw` field of `BooleanLiteral`. +/// +/// If `BooleanLiteral` has a span, set `raw` field to `"true"` or `"false"`. +/// If no span, `null`. +#[ast_meta] +#[estree( + ts_type = "string | null", + raw_deser = "(THIS.start === 0 && THIS.end === 0) ? null : THIS.value + ''" +)] +pub struct BooleanLiteralRaw<'b>(pub &'b BooleanLiteral); + +impl ESTree for BooleanLiteralRaw<'_> { + fn serialize(&self, serializer: S) { + #[expect(clippy::collection_is_never_read)] // Clippy is wrong! + let raw = if self.0.span.is_unspanned() { + None + } else if self.0.value { + Some(JsonSafeString("true")) + } else { + Some(JsonSafeString("false")) + }; + raw.serialize(serializer); + } +} + +/// Serializer for `raw` field of `NullLiteral`. +/// +/// If `NullLiteral` has a span, set `raw` field to `"null"`. If no span, `null`. +#[ast_meta] +#[estree( + ts_type = "'null' | null", + raw_deser = "(THIS.start === 0 && THIS.end === 0) ? null : 'null'" +)] +pub struct NullLiteralRaw<'b>(pub &'b NullLiteral); + +impl ESTree for NullLiteralRaw<'_> { + fn serialize(&self, serializer: S) { + #[expect(clippy::collection_is_never_read)] // Clippy is wrong! + let raw = if self.0.span.is_unspanned() { None } else { Some(JsonSafeString("null")) }; + raw.serialize(serializer); + } +} + +/// Serializer for `value` field of `StringLiteral`. +/// +/// Handle when `lone_surrogates` flag is set, indicating the string contains lone surrogates. +#[ast_meta] +#[estree( + ts_type = "string", + raw_deser = r#" + let value = DESER[Atom](POS_OFFSET.value); + if (DESER[bool](POS_OFFSET.lone_surrogates)) { + value = value.replace(/\uFFFD(.{4})/g, (_, hex) => String.fromCodePoint(parseInt(hex, 16))); + } + value + "# +)] +pub struct StringLiteralValue<'a, 'b>(pub &'b StringLiteral<'a>); + +impl ESTree for StringLiteralValue<'_, '_> { + fn serialize(&self, serializer: S) { + let lit = self.0; + #[expect(clippy::if_not_else)] + if !lit.lone_surrogates { + lit.value.serialize(serializer); + } else { + // String contains lone surrogates. Very uncommon, so cold path. + self.serialize_lone_surrogates(serializer); + } + } +} + +impl StringLiteralValue<'_, '_> { + #[cold] + #[inline(never)] + fn serialize_lone_surrogates(&self, serializer: S) { + LoneSurrogatesString(self.0.value.as_str()).serialize(serializer); + } +} + +/// Serializer for `bigint` field of `BigIntLiteral`. +/// +/// Serialize as `raw` value, with `n` postfix trimmed off, and underscores removed. +#[ast_meta] +#[estree(ts_type = "string", raw_deser = "THIS.raw.slice(0, -1).replace(/_/g, '')")] +pub struct BigIntLiteralBigint<'a, 'b>(pub &'b BigIntLiteral<'a>); + +impl ESTree for BigIntLiteralBigint<'_, '_> { + fn serialize(&self, serializer: S) { + let bigint = self.0.raw[..self.0.raw.len() - 1].cow_replace('_', ""); + JsonSafeString(bigint.as_ref()).serialize(serializer); + } +} + +/// Serializer for `value` field of `BigIntLiteral`. +/// +/// Serialized as `null` in JSON, but updated on JS side to contain a `BigInt`. +#[ast_meta] +#[estree(ts_type = "bigint", raw_deser = "BigInt(THIS.bigint)")] +pub struct BigIntLiteralValue<'a, 'b>(#[expect(dead_code)] pub &'b BigIntLiteral<'a>); + +impl ESTree for BigIntLiteralValue<'_, '_> { + fn serialize(&self, mut serializer: S) { + // Record that this node needs fixing on JS side + serializer.record_fix_path(); + Null(()).serialize(serializer); + } +} + +/// Serializer for `value` field of `RegExpLiteral`. +/// +/// Serialized as `null` in JSON, but updated on JS side to contain a `RegExp`, if the regexp is valid. +#[ast_meta] +#[estree( + ts_type = "RegExp | null", + raw_deser = " + let value = null; + try { + value = new RegExp(THIS.regex.pattern, THIS.regex.flags); + } catch (e) {} + value + " +)] +pub struct RegExpLiteralValue<'a, 'b>(#[expect(dead_code)] pub &'b RegExpLiteral<'a>); + +impl ESTree for RegExpLiteralValue<'_, '_> { + fn serialize(&self, mut serializer: S) { + // Record that this node needs fixing on JS side + serializer.record_fix_path(); + Null(()).serialize(serializer); + } +} + +/// Converter for `RegExpFlags`. +/// +/// Serialize as a string, with flags in alphabetical order. +#[ast_meta] +#[estree( + ts_type = "string", + raw_deser = " + const flagBits = DESER[u8](POS); + let flags = ''; + // Alphabetical order + if (flagBits & 64) flags += 'd'; + if (flagBits & 1) flags += 'g'; + if (flagBits & 2) flags += 'i'; + if (flagBits & 4) flags += 'm'; + if (flagBits & 8) flags += 's'; + if (flagBits & 16) flags += 'u'; + if (flagBits & 128) flags += 'v'; + if (flagBits & 32) flags += 'y'; + flags + " +)] +pub struct RegExpFlagsConverter<'b>(pub &'b RegExpFlags); + +impl ESTree for RegExpFlagsConverter<'_> { + fn serialize(&self, serializer: S) { + JsonSafeString(self.0.to_inline_string().as_str()).serialize(serializer); + } +} + +/// Converter for `TemplateElement`. +/// +/// Decode `cooked` if it contains lone surrogates. +/// +/// Also adjust span in TS AST. +/// TS-ESLint produces a different span from Acorn: +/// ```js +/// const template = `abc${x}def${x}ghi`; +/// // Acorn: ^^^ ^^^ ^^^ +/// // TS-ESLint: ^^^^^^ ^^^^^^ ^^^^^ +/// ``` +// TODO: Raise an issue on TS-ESLint and see if they'll change span to match Acorn. +#[ast_meta] +#[estree(raw_deser = r#" + const tail = DESER[bool](POS_OFFSET.tail), + start = DESER[u32](POS_OFFSET.span.start) /* IF_TS */ - 1 /* END_IF_TS */, + end = DESER[u32](POS_OFFSET.span.end) /* IF_TS */ + 2 - tail /* END_IF_TS */, + value = DESER[TemplateElementValue](POS_OFFSET.value); + if (value.cooked !== null && DESER[bool](POS_OFFSET.lone_surrogates)) { + value.cooked = value.cooked + .replace(/\uFFFD(.{4})/g, (_, hex) => String.fromCodePoint(parseInt(hex, 16))); + } + { type: 'TemplateElement', start, end, value, tail } +"#)] +pub struct TemplateElementConverter<'a, 'b>(pub &'b TemplateElement<'a>); + +impl ESTree for TemplateElementConverter<'_, '_> { + fn serialize(&self, serializer: S) { + let element = self.0; + let mut state = serializer.serialize_struct(); + state.serialize_field("type", &JsonSafeString("TemplateElement")); + + let mut span = element.span; + if S::INCLUDE_TS_FIELDS { + span.start -= 1; + span.end += if element.tail { 1 } else { 2 }; + } + state.serialize_field("start", &span.start); + state.serialize_field("end", &span.end); + + state.serialize_field("value", &TemplateElementValue(element)); + state.serialize_field("tail", &element.tail); + state.end(); + } +} + +/// Serializer for `value` field of `TemplateElement`. +/// +/// Handle when `lone_surrogates` flag is set, indicating the cooked string contains lone surrogates. +/// +/// Implementation for `raw_deser` is included in `TemplateElementConverter` above. +#[ast_meta] +#[estree( + ts_type = "TemplateElementValue", + raw_deser = "(() => { throw new Error('Should not appear in deserializer code'); })()" +)] +pub struct TemplateElementValue<'a, 'b>(pub &'b TemplateElement<'a>); + +impl ESTree for TemplateElementValue<'_, '_> { + fn serialize(&self, serializer: S) { + let element = self.0; + #[expect(clippy::if_not_else)] + if !element.lone_surrogates { + element.value.serialize(serializer); + } else { + // String contains lone surrogates. Very uncommon, so cold path. + self.serialize_lone_surrogates(serializer); + } + } +} + +impl TemplateElementValue<'_, '_> { + #[cold] + #[inline(never)] + fn serialize_lone_surrogates(&self, serializer: S) { + let value = &self.0.value; + + let mut state = serializer.serialize_struct(); + state.serialize_field("raw", &value.raw); + + let cooked = value.cooked.as_ref().map(|cooked| LoneSurrogatesString(cooked.as_str())); + state.serialize_field("cooked", &cooked); + + state.end(); + } +} diff --git a/crates/oxc_ast/src/serialize/mod.rs b/crates/oxc_ast/src/serialize/mod.rs new file mode 100644 index 0000000000000..3c7b03d541120 --- /dev/null +++ b/crates/oxc_ast/src/serialize/mod.rs @@ -0,0 +1,237 @@ +use std::cmp; + +use oxc_ast_macros::ast_meta; +use oxc_estree::{ + CompactFixesJSSerializer, CompactFixesTSSerializer, CompactJSSerializer, CompactTSSerializer, + Concat2, ESTree, JsonSafeString, PrettyFixesJSSerializer, PrettyFixesTSSerializer, + PrettyJSSerializer, PrettyTSSerializer, Serializer, StructSerializer, +}; +use oxc_span::GetSpan; + +use crate::ast::*; + +pub mod basic; +pub mod js; +pub mod jsx; +pub mod literal; +pub mod ts; +use basic::{EmptyArray, Null}; + +/// Main serialization methods for `Program`. +/// +/// Note: 8 separate methods for the different serialization options, rather than 1 method +/// with behavior controlled by flags +/// (e.g. `fn to_estree_json(&self, with_ts: bool, pretty: bool, fixes: bool)`) +/// to avoid bloating binary size. +/// +/// Most consumers (and Oxc crates) will use only 1 of these methods, so we don't want to needlessly +/// compile all 8 serializers when only 1 is used. +/// +/// Initial capacity for serializer's buffer is an estimate based on our benchmark fixtures +/// of ratio of source text size to JSON size. +/// +/// | File | Compact TS | Compact JS | Pretty TS | Pretty JS | +/// |----------------------------|------------|------------|-----------|-----------| +/// | antd.js | 10 | 9 | 76 | 72 | +/// | cal.com.tsx | 10 | 9 | 40 | 37 | +/// | checker.ts | 7 | 6 | 27 | 24 | +/// | pdf.mjs | 13 | 12 | 71 | 67 | +/// | RadixUIAdoptionSection.jsx | 10 | 9 | 45 | 44 | +/// |----------------------------|------------|------------|-----------|-----------| +/// | Maximum | 13 | 12 | 76 | 72 | +/// +/// It's better to over-estimate than under-estimate, as having to grow the buffer is expensive, +/// so have gone on the generous side. +const JSON_CAPACITY_RATIO_COMPACT: usize = 16; +const JSON_CAPACITY_RATIO_PRETTY: usize = 80; + +impl Program<'_> { + /// Serialize AST to ESTree JSON, including TypeScript fields. + pub fn to_estree_ts_json(&self) -> String { + let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT; + let mut serializer = CompactTSSerializer::with_capacity(capacity); + self.serialize(&mut serializer); + serializer.into_string() + } + + /// Serialize AST to ESTree JSON, without TypeScript fields. + pub fn to_estree_js_json(&self) -> String { + let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT; + let mut serializer = CompactJSSerializer::with_capacity(capacity); + self.serialize(&mut serializer); + serializer.into_string() + } + + /// Serialize AST to pretty-printed ESTree JSON, including TypeScript fields. + pub fn to_pretty_estree_ts_json(&self) -> String { + let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY; + let mut serializer = PrettyTSSerializer::with_capacity(capacity); + self.serialize(&mut serializer); + serializer.into_string() + } + + /// Serialize AST to pretty-printed ESTree JSON, without TypeScript fields. + pub fn to_pretty_estree_js_json(&self) -> String { + let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY; + let mut serializer = PrettyJSSerializer::with_capacity(capacity); + self.serialize(&mut serializer); + serializer.into_string() + } + + /// Serialize AST to ESTree JSON, including TypeScript fields, with list of fixes. + pub fn to_estree_ts_json_with_fixes(&self) -> String { + let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT; + let serializer = CompactFixesTSSerializer::with_capacity(capacity); + serializer.serialize_with_fixes(self) + } + + /// Serialize AST to ESTree JSON, without TypeScript fields, with list of fixes. + pub fn to_estree_js_json_with_fixes(&self) -> String { + let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT; + let serializer = CompactFixesJSSerializer::with_capacity(capacity); + serializer.serialize_with_fixes(self) + } + + /// Serialize AST to pretty-printed ESTree JSON, including TypeScript fields, with list of fixes. + pub fn to_pretty_estree_ts_json_with_fixes(&self) -> String { + let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY; + let serializer = PrettyFixesTSSerializer::with_capacity(capacity); + serializer.serialize_with_fixes(self) + } + + /// Serialize AST to pretty-printed ESTree JSON, without TypeScript fields, with list of fixes. + pub fn to_pretty_estree_js_json_with_fixes(&self) -> String { + let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY; + let serializer = PrettyFixesJSSerializer::with_capacity(capacity); + serializer.serialize_with_fixes(self) + } +} + +/// Serializer for `Program`. +/// +/// In TS AST, set start span to start of first directive or statement. +/// This is required because unlike Acorn, TS-ESLint excludes whitespace and comments +/// from the `Program` start span. +/// See for more info. +/// +/// Special case where first statement is an `ExportNamedDeclaration` or `ExportDefaultDeclaration` +/// exporting a class with decorators, where one of the decorators is before `export`. +/// In these cases, the span of the statement starts after the span of the decorators. +/// e.g. `@dec export class C {}` - `ExportNamedDeclaration` span start is 5, `Decorator` span start is 0. +/// `Program` span start is 0 (not 5). +#[ast_meta] +#[estree(raw_deser = " + const body = DESER[Vec](POS_OFFSET.directives); + body.push(...DESER[Vec](POS_OFFSET.body)); + + /* IF_JS */ + const start = DESER[u32](POS_OFFSET.span.start); + /* END_IF_JS */ + + const end = DESER[u32](POS_OFFSET.span.end); + + /* IF_TS */ + let start; + if (body.length > 0) { + const first = body[0]; + start = first.start; + if (first.type === 'ExportNamedDeclaration' || first.type === 'ExportDefaultDeclaration') { + const {declaration} = first; + if ( + declaration !== null && declaration.type === 'ClassDeclaration' + && declaration.decorators.length > 0 + ) { + const decoratorStart = declaration.decorators[0].start; + if (decoratorStart < start) start = decoratorStart; + } + } + } else { + start = end; + } + /* END_IF_TS */ + + const program = { + type: 'Program', + start, + end, + body, + sourceType: DESER[ModuleKind](POS_OFFSET.source_type.module_kind), + hashbang: DESER[Option](POS_OFFSET.hashbang), + }; + program +")] +pub struct ProgramConverter<'a, 'b>(pub &'b Program<'a>); + +impl ESTree for ProgramConverter<'_, '_> { + fn serialize(&self, serializer: S) { + let program = self.0; + let span_start = + if S::INCLUDE_TS_FIELDS { get_ts_start_span(program) } else { program.span.start }; + + let mut state = serializer.serialize_struct(); + state.serialize_field("type", &JsonSafeString("Program")); + state.serialize_field("start", &span_start); + state.serialize_field("end", &program.span.end); + state.serialize_field("body", &Concat2(&program.directives, &program.body)); + state.serialize_field("sourceType", &program.source_type.module_kind()); + state.serialize_field("hashbang", &program.hashbang); + state.end(); + } +} + +fn get_ts_start_span(program: &Program<'_>) -> u32 { + if let Some(first_directive) = program.directives.first() { + return first_directive.span.start; + } + + let Some(first_stmt) = program.body.first() else { + // Program contains no statements or directives. Span start = span end. + return program.span.end; + }; + + match first_stmt { + Statement::ExportNamedDeclaration(decl) => { + let start = decl.span.start; + if let Some(Declaration::ClassDeclaration(class)) = &decl.declaration { + if let Some(decorator) = class.decorators.first() { + return cmp::min(start, decorator.span.start); + } + } + start + } + Statement::ExportDefaultDeclaration(decl) => { + let start = decl.span.start; + if let ExportDefaultDeclarationKind::ClassDeclaration(class) = &decl.declaration { + if let Some(decorator) = class.decorators.first() { + return cmp::min(start, decorator.span.start); + } + } + start + } + _ => first_stmt.span().start, + } +} + +/// Serialize `value` field of `Comment`. +/// +/// This serializer does not work for JSON serializer, because there's no access to source text +/// in `fn serialize`. But in any case, comments often contain characters which need escaping in JSON, +/// which is slow, so it's probably faster to transfer comments as NAPI types (which we do). +/// +/// This meta type is only present for raw transfer, which can transfer faster. +#[ast_meta] +#[estree( + ts_type = "string", + raw_deser = " + const endCut = THIS.type === 'Line' ? 0 : 2; + SOURCE_TEXT.slice(THIS.start + 2, THIS.end - endCut) + " +)] +pub struct CommentValue<'b>(#[expect(dead_code)] pub &'b Comment); + +impl ESTree for CommentValue<'_> { + #[expect(clippy::unimplemented)] + fn serialize(&self, _serializer: S) { + unimplemented!(); + } +} diff --git a/crates/oxc_ast/src/serialize/ts.rs b/crates/oxc_ast/src/serialize/ts.rs new file mode 100644 index 0000000000000..d66cc9e8aeab5 --- /dev/null +++ b/crates/oxc_ast/src/serialize/ts.rs @@ -0,0 +1,422 @@ +use oxc_ast_macros::ast_meta; +use oxc_estree::{Concat2, ESTree, JsonSafeString, Serializer, StructSerializer}; + +use crate::ast::*; + +use super::Null; + +/// Serializer for `computed` field of `TSEnumMember`. +/// +/// `true` if `id` field is one of the computed variants of `TSEnumMemberName`. +// +// TODO: Not ideal to have to include the enum discriminant's value here explicitly. +// Need a "macro" e.g. `ENUM_MATCHES(id, ComputedString | ComputedTemplateString)`. +#[ast_meta] +#[estree(ts_type = "boolean", raw_deser = "DESER[u8](POS_OFFSET.id) > 1")] +pub struct TSEnumMemberComputed<'a, 'b>(pub &'b TSEnumMember<'a>); + +impl ESTree for TSEnumMemberComputed<'_, '_> { + fn serialize(&self, serializer: S) { + matches!( + self.0.id, + TSEnumMemberName::ComputedString(_) | TSEnumMemberName::ComputedTemplateString(_) + ) + .serialize(serializer); + } +} + +/// Serializer for `directive` field of `ExpressionStatement`. +/// +/// This field is always `null`, and only appears in the TS AST, not JS ESTree. +#[ast_meta] +#[estree(ts_type = "string | null", raw_deser = "null")] +#[ts] +pub struct ExpressionStatementDirective<'a, 'b>( + #[expect(dead_code)] pub &'b ExpressionStatement<'a>, +); + +impl ESTree for ExpressionStatementDirective<'_, '_> { + fn serialize(&self, serializer: S) { + Null(()).serialize(serializer); + } +} + +/// Converter for `TSModuleDeclaration`. +/// +/// Our AST represents `module X.Y.Z {}` as 3 x nested `TSModuleDeclaration`s. +/// TS-ESTree represents it as a single `TSModuleDeclaration`, +/// with a nested tree of `TSQualifiedName`s as `id`. +#[ast_meta] +#[estree(raw_deser = " + const kind = DESER[TSModuleDeclarationKind](POS_OFFSET.kind), + global = kind === 'global', + start = DESER[u32](POS_OFFSET.span.start), + end = DESER[u32](POS_OFFSET.span.end), + declare = DESER[bool](POS_OFFSET.declare); + let id = DESER[TSModuleDeclarationName](POS_OFFSET.id), + body = DESER[Option](POS_OFFSET.body); + + // Flatten `body`, and nest `id` + if (body !== null && body.type === 'TSModuleDeclaration') { + let innerId = body.id; + if (innerId.type === 'Identifier') { + id = { + type: 'TSQualifiedName', + start: id.start, + end: innerId.end, + left: id, + right: innerId, + }; + } else { + // Replace `left` of innermost `TSQualifiedName` with a nested `TSQualifiedName` with `id` of + // this module on left, and previous `left` of innermost `TSQualifiedName` on right + while (true) { + innerId.start = id.start; + if (innerId.left.type === 'Identifier') break; + innerId = innerId.left; + } + innerId.left = { + type: 'TSQualifiedName', + start: id.start, + end: innerId.left.end, + left: id, + right: innerId.left, + }; + id = body.id; + } + body = Object.hasOwn(body, 'body') ? body.body : null; + } + + // Skip `body` field if `null` + const node = body === null + ? { type: 'TSModuleDeclaration', start, end, id, kind, declare, global } + : { type: 'TSModuleDeclaration', start, end, id, body, kind, declare, global }; + node +")] +pub struct TSModuleDeclarationConverter<'a, 'b>(pub &'b TSModuleDeclaration<'a>); + +impl ESTree for TSModuleDeclarationConverter<'_, '_> { + fn serialize(&self, serializer: S) { + let module = self.0; + + let mut state = serializer.serialize_struct(); + state.serialize_field("type", &JsonSafeString("TSModuleDeclaration")); + state.serialize_field("start", &module.span.start); + state.serialize_field("end", &module.span.end); + + match &module.body { + Some(TSModuleDeclarationBody::TSModuleDeclaration(inner_module)) => { + // Nested modules e.g. `module X.Y.Z {}`. + // Collect all IDs in a `Vec`, in order they appear (i.e. [`X`, `Y`, `Z`]). + // Also get the inner `TSModuleBlock`. + let mut parts = Vec::with_capacity(4); + + let TSModuleDeclarationName::Identifier(id) = &module.id else { unreachable!() }; + parts.push(id); + + let mut body = None; + let mut inner_module = inner_module.as_ref(); + loop { + let TSModuleDeclarationName::Identifier(id) = &inner_module.id else { + unreachable!() + }; + parts.push(id); + + match &inner_module.body { + Some(TSModuleDeclarationBody::TSModuleDeclaration(inner_inner_module)) => { + inner_module = inner_inner_module.as_ref(); + } + Some(TSModuleDeclarationBody::TSModuleBlock(block)) => { + body = Some(block.as_ref()); + break; + } + None => break, + } + } + + // Serialize `parts` as a nested tree of `TSQualifiedName`s + state.serialize_field("id", &TSModuleDeclarationIdParts(&parts)); + + // Skip `body` field if it's `None` + if let Some(body) = body { + state.serialize_field("body", body); + } + } + Some(TSModuleDeclarationBody::TSModuleBlock(block)) => { + // No nested modules. + // Serialize as usual, with `id` being either a `BindingIdentifier` or `StringLiteral`. + state.serialize_field("id", &module.id); + state.serialize_field("body", block); + } + None => { + // No body. Skip `body` field. + state.serialize_field("id", &module.id); + } + } + + state.serialize_field("kind", &module.kind); + state.serialize_field("declare", &module.declare); + state.serialize_field("global", &TSModuleDeclarationGlobal(module)); + state.end(); + } +} + +struct TSModuleDeclarationIdParts<'a, 'b>(&'b [&'b BindingIdentifier<'a>]); + +impl ESTree for TSModuleDeclarationIdParts<'_, '_> { + fn serialize(&self, serializer: S) { + let parts = self.0; + assert!(!parts.is_empty()); + + let span_start = parts[0].span.start; + let (&last, rest) = parts.split_last().unwrap(); + + let mut state = serializer.serialize_struct(); + state.serialize_field("type", &JsonSafeString("TSQualifiedName")); + state.serialize_field("start", &span_start); + state.serialize_field("end", &last.span.end); + + if rest.len() == 1 { + // Only one part remaining (e.g. `X`). Serialize as `Identifier`. + state.serialize_field("left", &rest[0]); + } else { + // Multiple parts remaining (e.g. `X.Y`). Recurse to serialize as `TSQualifiedName`. + state.serialize_field("left", &TSModuleDeclarationIdParts(rest)); + } + + state.serialize_field("right", last); + state.end(); + } +} + +/// Serializer for `global` field of `TSModuleDeclaration`. +/// +/// `true` if `kind` is `TSModuleDeclarationKind::Global`. +#[ast_meta] +#[estree(ts_type = "boolean", raw_deser = "THIS.kind === 'global'")] +pub struct TSModuleDeclarationGlobal<'a, 'b>(pub &'b TSModuleDeclaration<'a>); + +impl ESTree for TSModuleDeclarationGlobal<'_, '_> { + fn serialize(&self, serializer: S) { + self.0.kind.is_global().serialize(serializer); + } +} + +/// Serializer for `optional` field of `TSMappedType`. +/// +/// `None` is serialized as `false`. +#[ast_meta] +#[estree( + ts_type = "TSMappedTypeModifierOperator | false", + raw_deser = " + let optional = DESER[Option](POS_OFFSET.optional) || false; + if (optional === null) optional = false; + optional + " +)] +pub struct TSMappedTypeOptional<'a, 'b>(pub &'b TSMappedType<'a>); + +impl ESTree for TSMappedTypeOptional<'_, '_> { + fn serialize(&self, serializer: S) { + if let Some(optional) = self.0.optional { + optional.serialize(serializer); + } else { + false.serialize(serializer); + } + } +} + +/// Serializer for `key` field of `TSMappedType`. +#[ast_meta] +#[estree( + ts_type = "TSTypeParameter['name']", + raw_deser = " + const typeParameter = DESER[Box](POS_OFFSET.type_parameter); + typeParameter.name + " +)] +pub struct TSMappedTypeKey<'a, 'b>(pub &'b TSMappedType<'a>); + +impl ESTree for TSMappedTypeKey<'_, '_> { + fn serialize(&self, serializer: S) { + self.0.type_parameter.name.serialize(serializer); + } +} + +/// Serializer for `constraint` field of `TSMappedType`. +/// +/// NOTE: Variable `typeParameter` in `raw_deser` is shared between `key` and `constraint` serializers. +/// They will be concatenated in the generated code. +#[ast_meta] +#[estree(ts_type = "TSTypeParameter['constraint']", raw_deser = "typeParameter.constraint")] +pub struct TSMappedTypeConstraint<'a, 'b>(pub &'b TSMappedType<'a>); + +impl ESTree for TSMappedTypeConstraint<'_, '_> { + fn serialize(&self, serializer: S) { + self.0.type_parameter.constraint.serialize(serializer); + } +} + +/// Serializer for `IdentifierReference` variant of `TSTypeName`. +/// +/// Where is an identifier called `this`, TS-ESTree presents it as a `ThisExpression`. +#[ast_meta] +#[estree( + ts_type = "IdentifierReference | ThisExpression", + raw_deser = " + let id = DESER[Box](POS); + if (id.name === 'this') id = { type: 'ThisExpression', start: id.start, end: id.end }; + id + " +)] +pub struct TSTypeNameIdentifierReference<'a, 'b>(pub &'b IdentifierReference<'a>); + +impl ESTree for TSTypeNameIdentifierReference<'_, '_> { + fn serialize(&self, serializer: S) { + let ident = self.0; + if ident.name == "this" { + ThisExpression { span: ident.span }.serialize(serializer); + } else { + ident.serialize(serializer); + } + } +} + +/// Serializer for `expression` field of `TSClassImplements`. +/// +/// Our AST represents `X.Y` in `class C implements X.Y {}` as a `TSQualifiedName`. +/// TS-ESTree represents `X.Y` as a `MemberExpression`. +/// +/// Where there are more parts e.g. `class C implements X.Y.Z {}`, the `TSQualifiedName`s (Oxc) +/// or `MemberExpression`s (TS-ESTree) are nested. +#[ast_meta] +#[estree( + ts_type = "IdentifierReference | ThisExpression | MemberExpression", + raw_deser = " + let expression = DESER[TSTypeName](POS_OFFSET.expression); + if (expression.type === 'TSQualifiedName') { + let parent = expression = { + type: 'MemberExpression', + start: expression.start, + end: expression.end, + object: expression.left, + property: expression.right, + computed: false, + optional: false, + }; + + while (parent.object.type === 'TSQualifiedName') { + const object = parent.object; + parent = parent.object = { + type: 'MemberExpression', + start: object.start, + end: object.end, + object: object.left, + property: object.right, + computed: false, + optional: false, + }; + } + } + expression + " +)] +pub struct TSClassImplementsExpression<'a, 'b>(pub &'b TSClassImplements<'a>); + +impl ESTree for TSClassImplementsExpression<'_, '_> { + #[inline] // Because it just delegates + fn serialize(&self, serializer: S) { + TSTypeNameAsMemberExpression(&self.0.expression).serialize(serializer); + } +} + +struct TSTypeNameAsMemberExpression<'a, 'b>(&'b TSTypeName<'a>); + +impl ESTree for TSTypeNameAsMemberExpression<'_, '_> { + fn serialize(&self, serializer: S) { + match self.0 { + TSTypeName::IdentifierReference(ident) => { + TSTypeNameIdentifierReference(ident).serialize(serializer); + } + TSTypeName::QualifiedName(name) => { + // Convert to `TSQualifiedName` to `MemberExpression`. + // Recursively convert `left` to `MemberExpression` too if it's a `TSQualifiedName`. + let mut state = serializer.serialize_struct(); + state.serialize_field("type", &JsonSafeString("MemberExpression")); + state.serialize_field("start", &name.span.start); + state.serialize_field("end", &name.span.end); + state.serialize_field("object", &TSTypeNameAsMemberExpression(&name.left)); + state.serialize_field("property", &name.right); + state.serialize_field("computed", &false); + state.serialize_field("optional", &false); + state.end(); + } + } + } +} + +/// Serializer for `params` field of `TSCallSignatureDeclaration`. +/// +/// Add `this_param` to start of the `params` array. +#[ast_meta] +#[estree( + ts_type = "ParamPattern[]", + raw_deser = " + const params = DESER[Box](POS_OFFSET.params); + const thisParam = DESER[Option>](POS_OFFSET.this_param); + if (thisParam !== null) params.unshift(thisParam); + params + " +)] +pub struct TSCallSignatureDeclarationParams<'a, 'b>(pub &'b TSCallSignatureDeclaration<'a>); + +impl ESTree for TSCallSignatureDeclarationParams<'_, '_> { + fn serialize(&self, serializer: S) { + let decl = self.0; + Concat2(&decl.this_param, decl.params.as_ref()).serialize(serializer); + } +} + +/// Serializer for `params` field of `TSMethodSignature`. +/// +/// Add `this_param` to start of the `params` array. +#[ast_meta] +#[estree( + ts_type = "ParamPattern[]", + raw_deser = " + const params = DESER[Box](POS_OFFSET.params); + const thisParam = DESER[Option>](POS_OFFSET.this_param); + if (thisParam !== null) params.unshift(thisParam); + params + " +)] +pub struct TSMethodSignatureParams<'a, 'b>(pub &'b TSMethodSignature<'a>); + +impl ESTree for TSMethodSignatureParams<'_, '_> { + fn serialize(&self, serializer: S) { + let sig = self.0; + Concat2(&sig.this_param, sig.params.as_ref()).serialize(serializer); + } +} + +/// Serializer for `params` field of `TSFunctionType`. +/// +/// Add `this_param` to start of the `params` array. +#[ast_meta] +#[estree( + ts_type = "ParamPattern[]", + raw_deser = " + const params = DESER[Box](POS_OFFSET.params); + const thisParam = DESER[Option>](POS_OFFSET.this_param); + if (thisParam !== null) params.unshift(thisParam); + params + " +)] +pub struct TSFunctionTypeParams<'a, 'b>(pub &'b TSFunctionType<'a>); + +impl ESTree for TSFunctionTypeParams<'_, '_> { + fn serialize(&self, serializer: S) { + let fn_type = self.0; + Concat2(&fn_type.this_param, fn_type.params.as_ref()).serialize(serializer); + } +} diff --git a/tasks/ast_tools/src/main.rs b/tasks/ast_tools/src/main.rs index 9ef0d53650bf6..b7049bdb42143 100644 --- a/tasks/ast_tools/src/main.rs +++ b/tasks/ast_tools/src/main.rs @@ -212,7 +212,12 @@ static SOURCE_PATHS: &[&str] = &[ "crates/oxc_ast/src/ast/jsx.rs", "crates/oxc_ast/src/ast/ts.rs", "crates/oxc_ast/src/ast/comment.rs", - "crates/oxc_ast/src/serialize.rs", + "crates/oxc_ast/src/serialize/mod.rs", + "crates/oxc_ast/src/serialize/basic.rs", + "crates/oxc_ast/src/serialize/literal.rs", + "crates/oxc_ast/src/serialize/js.rs", + "crates/oxc_ast/src/serialize/jsx.rs", + "crates/oxc_ast/src/serialize/ts.rs", "crates/oxc_syntax/src/lib.rs", "crates/oxc_syntax/src/module_record.rs", "crates/oxc_syntax/src/number.rs",