diff --git a/crates/oxc_ast/src/ast/ts.rs b/crates/oxc_ast/src/ast/ts.rs index ee2310dd42d04..6697dca4c0340 100644 --- a/crates/oxc_ast/src/ast/ts.rs +++ b/crates/oxc_ast/src/ast/ts.rs @@ -403,6 +403,7 @@ pub struct TSIntersectionType<'a> { #[ast(visit)] #[derive(Debug)] #[generate_derive(CloneIn, Dummy, TakeIn, GetSpan, GetSpanMut, ContentEq, ESTree)] +#[estree(via = TSParenthesizedTypeConverter)] pub struct TSParenthesizedType<'a> { pub span: Span, pub type_annotation: TSType<'a>, diff --git a/crates/oxc_ast/src/generated/derive_estree.rs b/crates/oxc_ast/src/generated/derive_estree.rs index fda85cf22a378..707520425fcc8 100644 --- a/crates/oxc_ast/src/generated/derive_estree.rs +++ b/crates/oxc_ast/src/generated/derive_estree.rs @@ -2346,11 +2346,7 @@ impl ESTree for TSIntersectionType<'_> { impl ESTree for TSParenthesizedType<'_> { fn serialize(&self, serializer: S) { - let mut state = serializer.serialize_struct(); - state.serialize_field("type", &JsonSafeString("TSParenthesizedType")); - state.serialize_field("typeAnnotation", &self.type_annotation); - state.serialize_span(self.span); - state.end(); + crate::serialize::ts::TSParenthesizedTypeConverter(self).serialize(serializer) } } diff --git a/crates/oxc_ast/src/serialize/ts.rs b/crates/oxc_ast/src/serialize/ts.rs index 512b038d24f6a..f63ba30c051d9 100644 --- a/crates/oxc_ast/src/serialize/ts.rs +++ b/crates/oxc_ast/src/serialize/ts.rs @@ -401,3 +401,38 @@ impl ESTree for TSFunctionTypeParams<'_, '_> { Concat2(&fn_type.this_param, fn_type.params.as_ref()).serialize(serializer); } } + +/// Converter for [`TSParenthesizedType`]. +/// +/// In raw transfer, do not produce a `TSParenthesizedType` node in AST if `preserveParens` is false. +/// +/// Not useful in `oxc-parser`, as can use parser option `preserve_parens`. +/// Required for `oxlint` plugins where we run parser with `preserve_parens` set to `true`, +/// to preserve them on Rust side, but need to remove them on JS side. +/// +/// ESTree implementation is unchanged from the auto-generated version. +#[ast_meta] +#[estree(raw_deser = " + let node = DESER[TSType](POS_OFFSET.type_annotation); + if (preserveParens) { + node = { + type: 'TSParenthesizedType', + typeAnnotation: node, + start: DESER[u32]( POS_OFFSET.span.start ), + end: DESER[u32]( POS_OFFSET.span.end ), + }; + } + node +")] +pub struct TSParenthesizedTypeConverter<'a, 'b>(pub &'b TSParenthesizedType<'a>); + +impl ESTree for TSParenthesizedTypeConverter<'_, '_> { + fn serialize(&self, serializer: S) { + let paren_type = self.0; + let mut state = serializer.serialize_struct(); + state.serialize_field("type", &JsonSafeString("TSParenthesizedType")); + state.serialize_field("typeAnnotation", &paren_type.type_annotation); + state.serialize_span(paren_type.span); + state.end(); + } +} diff --git a/napi/parser/generated/deserialize/js.mjs b/napi/parser/generated/deserialize/js.mjs index fa6d959562c46..0fa5ce6b65a38 100644 --- a/napi/parser/generated/deserialize/js.mjs +++ b/napi/parser/generated/deserialize/js.mjs @@ -1371,12 +1371,16 @@ function deserializeTSIntersectionType(pos) { } function deserializeTSParenthesizedType(pos) { - return { - type: 'TSParenthesizedType', - typeAnnotation: deserializeTSType(pos + 8), - start: deserializeU32(pos), - end: deserializeU32(pos + 4), - }; + let node = deserializeTSType(pos + 8); + if (preserveParens) { + node = { + type: 'TSParenthesizedType', + typeAnnotation: node, + start: deserializeU32(pos), + end: deserializeU32(pos + 4), + }; + } + return node; } function deserializeTSTypeOperator(pos) { diff --git a/napi/parser/generated/deserialize/ts.mjs b/napi/parser/generated/deserialize/ts.mjs index 9bf639dd3d0e4..754c06aff11d5 100644 --- a/napi/parser/generated/deserialize/ts.mjs +++ b/napi/parser/generated/deserialize/ts.mjs @@ -1502,12 +1502,16 @@ function deserializeTSIntersectionType(pos) { } function deserializeTSParenthesizedType(pos) { - return { - type: 'TSParenthesizedType', - typeAnnotation: deserializeTSType(pos + 8), - start: deserializeU32(pos), - end: deserializeU32(pos + 4), - }; + let node = deserializeTSType(pos + 8); + if (preserveParens) { + node = { + type: 'TSParenthesizedType', + typeAnnotation: node, + start: deserializeU32(pos), + end: deserializeU32(pos + 4), + }; + } + return node; } function deserializeTSTypeOperator(pos) { diff --git a/napi/parser/test/parse-raw.test.ts b/napi/parser/test/parse-raw.test.ts index 35a95bcd8fd37..13885258805eb 100644 --- a/napi/parser/test/parse-raw.test.ts +++ b/napi/parser/test/parse-raw.test.ts @@ -277,12 +277,13 @@ describe.concurrent('`preserveParens` option', () => { }); it.concurrent('TS', async () => { - const code = 'let x = (1 + 2);'; + const code = 'let x = (1 + 2); type T = (string);'; // @ts-ignore let ret = parseSync('test.ts', code, { experimentalRawTransfer: true, preserveParens: false }); expect(ret.errors.length).toBe(0); expect(ret.program.body[0].declarations[0].init.type).toBe('BinaryExpression'); + expect(ret.program.body[1].typeAnnotation.type).toBe('TSStringKeyword'); }); }); @@ -297,12 +298,13 @@ describe.concurrent('`preserveParens` option', () => { }); it.concurrent('TS', async () => { - const code = 'let x = (1 + 2);'; + const code = 'let x = (1 + 2); type T = (string);'; // @ts-ignore let ret = parseSync('test.ts', code, { experimentalRawTransfer: true, preserveParens: true }); expect(ret.errors.length).toBe(0); expect(ret.program.body[0].declarations[0].init.type).toBe('ParenthesizedExpression'); + expect(ret.program.body[1].typeAnnotation.type).toBe('TSParenthesizedType'); }); }); });