diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index 54cbf7e3bf6bb..3abe163c2a6c1 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -141,12 +141,12 @@ impl<'a> Traverse<'a> for Transformer<'a> { } fn enter_expression(&mut self, expr: &mut Expression<'a>, _ctx: &TraverseCtx<'a>) { - self.x0_typescript.transform_expression(expr); self.x1_react.transform_expression(expr); self.x3_es2015.transform_expression(expr); } fn exit_expression(&mut self, expr: &mut Expression<'a>, _ctx: &TraverseCtx<'a>) { + self.x0_typescript.transform_expression_on_exit(expr); self.x3_es2015.transform_expression_on_exit(expr); } @@ -245,4 +245,8 @@ impl<'a> Traverse<'a> for Transformer<'a> { ) { self.x0_typescript.transform_module_declaration(decl); } + + fn enter_ts_type_assertion(&mut self, node: &mut TSTypeAssertion<'a>, _ctx: &TraverseCtx<'a>) { + self.x0_typescript.transform_ts_type_assertion(node); + } } diff --git a/crates/oxc_transformer/src/typescript/annotations.rs b/crates/oxc_transformer/src/typescript/annotations.rs index c82db5b210bea..6e802aa883666 100644 --- a/crates/oxc_transformer/src/typescript/annotations.rs +++ b/crates/oxc_transformer/src/typescript/annotations.rs @@ -11,7 +11,10 @@ use oxc_span::{Atom, SPAN}; use oxc_syntax::operator::AssignmentOperator; use rustc_hash::FxHashSet; -use super::collector::TypeScriptReferenceCollector; +use super::{ + collector::TypeScriptReferenceCollector, + diagnostics::{type_assertion_reserved, type_parameters_reserved}, +}; pub struct TypeScriptAnnotations<'a> { #[allow(dead_code)] @@ -197,6 +200,20 @@ impl<'a> TypeScriptAnnotations<'a> { } pub fn transform_arrow_expression(&mut self, expr: &mut ArrowFunctionExpression<'a>) { + if self.is_mts_or_ctx_file() { + // () => {} is invalid, but () => {} is valid + if let Some(type_parameters) = &expr.type_parameters { + if type_parameters.params.len() == 1 + && !type_parameters + .span + .source_text(self.ctx.source_text) + .trim_end() + .ends_with(',') + { + self.ctx.error(type_parameters_reserved(expr.span)); + } + } + } expr.type_parameters = None; expr.return_type = None; } @@ -240,7 +257,7 @@ impl<'a> TypeScriptAnnotations<'a> { }); } - pub fn transform_expression(&mut self, expr: &mut Expression<'a>) { + pub fn transform_expression_on_exit(&mut self, expr: &mut Expression<'a>) { *expr = self.ctx.ast.copy(expr.get_inner_expression()); } @@ -396,4 +413,15 @@ impl<'a> TypeScriptAnnotations<'a> { pub fn transform_jsx_fragment(&mut self, _elem: &mut JSXFragment<'a>) { self.has_jsx_fragment = true; } + + pub fn transform_ts_type_assertion(&mut self, node: &mut TSTypeAssertion<'a>) { + if self.is_mts_or_ctx_file() { + self.ctx.error(type_assertion_reserved(node.span)); + } + } + + pub fn is_mts_or_ctx_file(&self) -> bool { + let extension = self.ctx.source_path.extension(); + extension.is_some_and(|ext| matches!(ext.to_str(), Some("mts" | "cts"))) + } } diff --git a/crates/oxc_transformer/src/typescript/diagnostics.rs b/crates/oxc_transformer/src/typescript/diagnostics.rs index 381bf515f9536..a09d7ceef5a08 100644 --- a/crates/oxc_transformer/src/typescript/diagnostics.rs +++ b/crates/oxc_transformer/src/typescript/diagnostics.rs @@ -15,3 +15,11 @@ pub fn ambient_module_nested(span0: Span) -> OxcDiagnostic { OxcDiagnostic::warning("Ambient modules cannot be nested in other modules or namespaces.") .with_label(span0) } + +pub fn type_assertion_reserved(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warning("This syntax is reserved in files with the .mts or .cts extension. Use an `as` expression instead.").with_label(span) +} + +pub fn type_parameters_reserved(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warning("This syntax is reserved in files with the .mts or .cts extension. Add a trailing comma, as in `() => ...`.").with_label(span) +} diff --git a/crates/oxc_transformer/src/typescript/mod.rs b/crates/oxc_transformer/src/typescript/mod.rs index 776f35ac51904..b587e88eb871c 100644 --- a/crates/oxc_transformer/src/typescript/mod.rs +++ b/crates/oxc_transformer/src/typescript/mod.rs @@ -100,8 +100,8 @@ impl<'a> TypeScript<'a> { self.reference_collector.visit_transform_export_named_declaration(decl); } - pub fn transform_expression(&mut self, expr: &mut Expression<'a>) { - self.annotations.transform_expression(expr); + pub fn transform_expression_on_exit(&mut self, expr: &mut Expression<'a>) { + self.annotations.transform_expression_on_exit(expr); } pub fn transform_formal_parameter(&mut self, param: &mut FormalParameter<'a>) { @@ -211,4 +211,8 @@ impl<'a> TypeScript<'a> { pub fn transform_jsx_fragment(&mut self, elem: &mut JSXFragment<'a>) { self.annotations.transform_jsx_fragment(elem); } + + pub fn transform_ts_type_assertion(&mut self, node: &mut TSTypeAssertion<'a>) { + self.annotations.transform_ts_type_assertion(node); + } } diff --git a/tasks/transform_conformance/babel.snap.md b/tasks/transform_conformance/babel.snap.md index fead6a775b13c..8ded029893b64 100644 --- a/tasks/transform_conformance/babel.snap.md +++ b/tasks/transform_conformance/babel.snap.md @@ -1,6 +1,6 @@ commit: 4bd1b2c2 -Passed: 309/362 +Passed: 313/362 # All Passed: * babel-preset-react @@ -16,12 +16,8 @@ Passed: 309/362 * spec/newableArrowFunction-default/input.js * spec/newableArrowFunction-vs-spec-false/input.js -# babel-preset-typescript (7/16) +# babel-preset-typescript (11/16) * node-extensions/import-in-cts/input.cts -* node-extensions/type-assertion-in-cts/input.cts -* node-extensions/type-assertion-in-mts/input.mts -* node-extensions/type-param-arrow-in-cts/input.mts -* node-extensions/type-param-arrow-in-mts/input.mts * node-extensions/with-in-mts/input.mts * opts/allowDeclareFields/input.ts * opts/optimizeConstEnums/input.ts diff --git a/tasks/transform_conformance/src/test_case.rs b/tasks/transform_conformance/src/test_case.rs index fd12136e8d4ff..d39821aef98c4 100644 --- a/tasks/transform_conformance/src/test_case.rs +++ b/tasks/transform_conformance/src/test_case.rs @@ -467,6 +467,8 @@ fn get_babel_error(error: &str) -> String { "Duplicate __self prop found." => "Duplicate __self prop found. You are most likely using the deprecated transform-react-jsx-self Babel plugin. Both __source and __self are automatically set when using the automatic runtime. Please remove transform-react-jsx-source and transform-react-jsx-self from your Babel config.", "Duplicate __source prop found." => "Duplicate __source prop found. You are most likely using the deprecated transform-react-jsx-source Babel plugin. Both __source and __self are automatically set when using the automatic runtime. Please remove transform-react-jsx-source and transform-react-jsx-self from your Babel config.", "Expected `>` but found `/`" => "Unexpected token, expected \",\"", + "This syntax is reserved in files with the .mts or .cts extension. Use an `as` expression instead." => "This syntax is reserved in files with the .mts or .cts extension. Use an `as` expression instead. (1:0)", + "This syntax is reserved in files with the .mts or .cts extension. Add a trailing comma, as in `() => ...`." => "This syntax is reserved in files with the .mts or .cts extension. Add a trailing comma, as in `() => ...`. (1:0)", _ => error }.to_string() }