diff --git a/crates/oxc_ast/src/ast/ts.rs b/crates/oxc_ast/src/ast/ts.rs index c352b862ff4aa..fd18de01b6338 100644 --- a/crates/oxc_ast/src/ast/ts.rs +++ b/crates/oxc_ast/src/ast/ts.rs @@ -1340,6 +1340,7 @@ pub enum TSTypeQueryExprName<'a> { pub struct TSImportType<'a> { pub span: Span, pub argument: TSType<'a>, + #[estree(via = TSImportTypeOptions)] pub options: Option>>, pub qualifier: Option>, pub type_arguments: Option>>, diff --git a/crates/oxc_ast/src/generated/derive_estree.rs b/crates/oxc_ast/src/generated/derive_estree.rs index bc58049be2d37..eaedb391098dd 100644 --- a/crates/oxc_ast/src/generated/derive_estree.rs +++ b/crates/oxc_ast/src/generated/derive_estree.rs @@ -3135,7 +3135,7 @@ impl ESTree for TSImportType<'_> { state.serialize_field("start", &self.span.start); state.serialize_field("end", &self.span.end); state.serialize_field("argument", &self.argument); - state.serialize_field("options", &self.options); + state.serialize_field("options", &crate::serialize::TSImportTypeOptions(self)); state.serialize_field("qualifier", &self.qualifier); state.serialize_field("typeArguments", &self.type_arguments); state.end(); diff --git a/crates/oxc_ast/src/serialize.rs b/crates/oxc_ast/src/serialize.rs index 17c97c7e71ec2..b67c84c98a022 100644 --- a/crates/oxc_ast/src/serialize.rs +++ b/crates/oxc_ast/src/serialize.rs @@ -1415,6 +1415,83 @@ impl ESTree for TSTypeNameAsMemberExpression<'_, '_> { } } +/// Serializer for `options` field of `TSImportType`. +/// +/// TS-ESLint parser replaces a property of `options` called `assert` with one called `with`. +/// +#[ast_meta] +#[estree( + ts_type = "ObjectExpression | null", + raw_deser = " + let options = DESER[Option>](POS_OFFSET.options); + if (options !== null && options.properties.length === 1) { + const prop = options.properties[0]; + if ( + !prop.method && !prop.shorthand && !prop.computed + && prop.key.type === 'Identifier' && prop.key.name === 'assert' + ) { + prop.key.name = 'with'; + } + } + options + " +)] +pub struct TSImportTypeOptions<'a, 'b>(pub &'b TSImportType<'a>); + +impl ESTree for TSImportTypeOptions<'_, '_> { + fn serialize(&self, serializer: S) { + let Some(options) = &self.0.options else { + Null(()).serialize(serializer); + return; + }; + + if options.properties.len() == 1 { + if let ObjectPropertyKind::ObjectProperty(prop) = &options.properties[0] { + if let PropertyKey::StaticIdentifier(ident) = &prop.key { + if !prop.method && !prop.shorthand && !prop.computed && ident.name == "assert" { + let assert_prop = TSImportTypeAssertProperty { prop, key_span: ident.span }; + + let mut state = serializer.serialize_struct(); + state.serialize_field("type", &JsonSafeString("ObjectExpression")); + state.serialize_field("start", &options.span.start); + state.serialize_field("end", &options.span.end); + state.serialize_field("properties", &[assert_prop]); + state.end(); + return; + } + } + } + } + + options.serialize(serializer); + } +} + +struct TSImportTypeAssertProperty<'a, 'b> { + prop: &'b ObjectProperty<'a>, + key_span: Span, +} + +impl ESTree for TSImportTypeAssertProperty<'_, '_> { + fn serialize(&self, serializer: S) { + let mut state = serializer.serialize_struct(); + state.serialize_field("type", &JsonSafeString("Property")); + state.serialize_field("start", &self.prop.span.start); + state.serialize_field("end", &self.prop.span.end); + state.serialize_field("method", &false); + state.serialize_field("shorthand", &false); + state.serialize_field("computed", &false); + state.serialize_field( + "key", + &IdentifierName { span: self.key_span, name: Atom::from("with") }, + ); + state.serialize_field("value", &self.prop.value); + state.serialize_field("kind", "init"); + state.serialize_field("optional", &false); + state.end(); + } +} + /// Serializer for `params` field of `TSCallSignatureDeclaration`. /// /// These add `this_param` to start of the `params` array. diff --git a/napi/parser/deserialize-js.js b/napi/parser/deserialize-js.js index e281ab2c7172f..00b55d93652e0 100644 --- a/napi/parser/deserialize-js.js +++ b/napi/parser/deserialize-js.js @@ -1853,12 +1853,22 @@ function deserializeTSTypeQuery(pos) { } function deserializeTSImportType(pos) { + let options = deserializeOptionBoxObjectExpression(pos + 24); + if (options !== null && options.properties.length === 1) { + const prop = options.properties[0]; + if ( + !prop.method && !prop.shorthand && !prop.computed && + prop.key.type === 'Identifier' && prop.key.name === 'assert' + ) { + prop.key.name = 'with'; + } + } return { type: 'TSImportType', start: deserializeU32(pos), end: deserializeU32(pos + 4), argument: deserializeTSType(pos + 8), - options: deserializeOptionBoxObjectExpression(pos + 24), + options, qualifier: deserializeOptionTSTypeName(pos + 32), typeArguments: deserializeOptionBoxTSTypeParameterInstantiation(pos + 48), }; diff --git a/napi/parser/deserialize-ts.js b/napi/parser/deserialize-ts.js index 4fcb262486767..b77e41e42519b 100644 --- a/napi/parser/deserialize-ts.js +++ b/napi/parser/deserialize-ts.js @@ -2005,12 +2005,22 @@ function deserializeTSTypeQuery(pos) { } function deserializeTSImportType(pos) { + let options = deserializeOptionBoxObjectExpression(pos + 24); + if (options !== null && options.properties.length === 1) { + const prop = options.properties[0]; + if ( + !prop.method && !prop.shorthand && !prop.computed && + prop.key.type === 'Identifier' && prop.key.name === 'assert' + ) { + prop.key.name = 'with'; + } + } return { type: 'TSImportType', start: deserializeU32(pos), end: deserializeU32(pos + 4), argument: deserializeTSType(pos + 8), - options: deserializeOptionBoxObjectExpression(pos + 24), + options, qualifier: deserializeOptionTSTypeName(pos + 32), typeArguments: deserializeOptionBoxTSTypeParameterInstantiation(pos + 48), }; diff --git a/tasks/coverage/snapshots/estree_typescript.snap b/tasks/coverage/snapshots/estree_typescript.snap index 181e46be2c12f..bc51d413d9d86 100644 --- a/tasks/coverage/snapshots/estree_typescript.snap +++ b/tasks/coverage/snapshots/estree_typescript.snap @@ -2,7 +2,7 @@ commit: 15392346 estree_typescript Summary: AST Parsed : 6480/6481 (99.98%) -Positive Passed: 6470/6481 (99.83%) +Positive Passed: 6472/6481 (99.86%) Mismatch: tasks/coverage/typescript/tests/cases/compiler/controlFlowInstanceofWithSymbolHasInstance.ts Mismatch: tasks/coverage/typescript/tests/cases/compiler/narrowingUnionToUnion.ts @@ -20,9 +20,5 @@ Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/tsxReactEmitEnti Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/tsxReactEmitNesting.tsx -Mismatch: tasks/coverage/typescript/tests/cases/conformance/moduleResolution/resolutionModeImportType1.ts - -Mismatch: tasks/coverage/typescript/tests/cases/conformance/node/nodeModulesImportTypeModeDeclarationEmit1.ts - Mismatch: tasks/coverage/typescript/tests/cases/conformance/types/stringLiteral/stringLiteralTypesAsTags02.ts