diff --git a/crates/oxc_parser/src/diagnostics.rs b/crates/oxc_parser/src/diagnostics.rs index 5abf5e2577717..c4bf822e2126e 100644 --- a/crates/oxc_parser/src/diagnostics.rs +++ b/crates/oxc_parser/src/diagnostics.rs @@ -1284,3 +1284,23 @@ pub fn ts_import_type_options_invalid_key(span: Span) -> OxcDiagnostic { pub fn ts_import_type_options_no_spread(span: Span) -> OxcDiagnostic { OxcDiagnostic::error("Spread elements are not allowed in import type options.").with_label(span) } + +// This syntax is reserved in files with the .mts or .cts extension. Use an `as` expression instead. ts(7059) +#[cold] +pub fn jsx_type_assertion_in_mts_cts(span: Span) -> OxcDiagnostic { + ts_error( + "7059", + "This syntax is reserved in files with the .mts or .cts extension. Use an `as` expression instead.", + ) + .with_label(span) +} + +// This syntax is reserved in files with the .mts or .cts extension. Add a trailing comma or explicit constraint. ts(7060) +#[cold] +pub fn jsx_type_parameter_in_mts_cts(span: Span) -> OxcDiagnostic { + ts_error( + "7060", + "This syntax is reserved in files with the .mts or .cts extension. Add a trailing comma or explicit constraint.", + ) + .with_label(span) +} diff --git a/crates/oxc_parser/src/js/arrow.rs b/crates/oxc_parser/src/js/arrow.rs index 788371f60cd72..0ef09bf6993fa 100644 --- a/crates/oxc_parser/src/js/arrow.rs +++ b/crates/oxc_parser/src/js/arrow.rs @@ -1,6 +1,6 @@ use oxc_allocator::Box; use oxc_ast::{NONE, ast::*}; -use oxc_span::GetSpan; +use oxc_span::{FileExtension, GetSpan}; use oxc_syntax::precedence::Precedence; use super::{FunctionKind, Tristate}; @@ -252,7 +252,17 @@ impl<'a> ParserImpl<'a> { let has_await = self.ctx.has_await(); self.ctx = self.ctx.union_await_if(r#async); - let type_parameters = self.parse_ts_type_parameters(); + let (type_parameters, has_trailing_comma) = + self.parse_ts_type_parameters_with_trailing_comma(); + + if let Some(type_params) = &type_parameters + && matches!(self.source_type.extension(), Some(FileExtension::Mts | FileExtension::Cts)) + && type_params.params.len() == 1 + && type_params.params[0].constraint.is_none() + && !has_trailing_comma + { + self.error(diagnostics::jsx_type_parameter_in_mts_cts(type_params.params[0].name.span)); + } let (this_param, params) = self.parse_formal_parameters( FunctionKind::Expression, diff --git a/crates/oxc_parser/src/ts/statement.rs b/crates/oxc_parser/src/ts/statement.rs index da7f0d4a18110..b9c9e549d981f 100644 --- a/crates/oxc_parser/src/ts/statement.rs +++ b/crates/oxc_parser/src/ts/statement.rs @@ -1,6 +1,6 @@ use oxc_allocator::{Box, Vec}; use oxc_ast::ast::*; -use oxc_span::GetSpan; +use oxc_span::{FileExtension, GetSpan}; use crate::{ Context, ParserImpl, diagnostics, @@ -552,7 +552,13 @@ impl<'a> ParserImpl<'a> { self.expect(Kind::RAngle); let lhs_span = self.start_span(); let expression = self.parse_simple_unary_expression(lhs_span); - self.ast.expression_ts_type_assertion(self.end_span(span), type_annotation, expression) + let span = self.end_span(span); + + if matches!(self.source_type.extension(), Some(FileExtension::Mts | FileExtension::Cts)) { + self.error(diagnostics::jsx_type_assertion_in_mts_cts(span)); + } + + self.ast.expression_ts_type_assertion(span, type_annotation, expression) } pub(crate) fn parse_ts_import_equals_declaration( diff --git a/crates/oxc_parser/src/ts/types.rs b/crates/oxc_parser/src/ts/types.rs index e9a5652a4c28d..d7ee5e40148b5 100644 --- a/crates/oxc_parser/src/ts/types.rs +++ b/crates/oxc_parser/src/ts/types.rs @@ -151,16 +151,30 @@ impl<'a> ParserImpl<'a> { pub(crate) fn parse_ts_type_parameters( &mut self, ) -> Option>> { + self.parse_ts_type_parameters_impl().0 + } + + /// Parse TypeScript type parameters and return whether there was a trailing comma. + /// Used for arrow functions to check for TS7060 (JSX-like type parameters in .mts/.cts). + pub(crate) fn parse_ts_type_parameters_with_trailing_comma( + &mut self, + ) -> (Option>>, bool) { + self.parse_ts_type_parameters_impl() + } + + fn parse_ts_type_parameters_impl( + &mut self, + ) -> (Option>>, bool) { if !self.is_ts { - return None; + return (None, false); } if !self.at(Kind::LAngle) { - return None; + return (None, false); } let span = self.start_span(); let opening_span = self.cur_token().span(); self.expect(Kind::LAngle); - let (params, _) = self.parse_delimited_list( + let (params, trailing_comma) = self.parse_delimited_list( Kind::RAngle, Kind::Comma, opening_span, @@ -171,7 +185,7 @@ impl<'a> ParserImpl<'a> { if params.is_empty() { self.error(diagnostics::ts_empty_type_parameter_list(span)); } - Some(self.ast.alloc_ts_type_parameter_declaration(span, params)) + (Some(self.ast.alloc_ts_type_parameter_declaration(span, params)), trailing_comma.is_some()) } pub(crate) fn parse_ts_implements_clause(&mut self) -> Vec<'a, TSClassImplements<'a>> { diff --git a/tasks/coverage/snapshots/parser_typescript.snap b/tasks/coverage/snapshots/parser_typescript.snap index b2fab81612855..2e27d96c9d270 100644 --- a/tasks/coverage/snapshots/parser_typescript.snap +++ b/tasks/coverage/snapshots/parser_typescript.snap @@ -3,7 +3,7 @@ commit: 95e3aaa9 parser_typescript Summary: AST Parsed : 9842/9843 (99.99%) Positive Passed: 9838/9843 (99.95%) -Negative Passed: 1514/2557 (59.21%) +Negative Passed: 1515/2557 (59.25%) Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/FunctionDeclaration3.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/FunctionDeclaration4.ts @@ -1690,8 +1690,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/node/node Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/node/nodeModulesExportsBlocksTypesVersions.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/node/nodeModulesForbidenSyntax.ts - Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/node/nodeModulesGeneratedNameCollisions.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/node/nodeModulesImportAssertions.ts @@ -21115,6 +21113,30 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/statements/Va ╰──── help: TypeScript transforms 'import ... =' to 'const ... =' + × TS(7060): This syntax is reserved in files with the .mts or .cts extension. Add a trailing comma or explicit constraint. + ╭─[typescript/tests/cases/conformance/node/nodeModulesForbidenSyntax.ts:2:12] + 1 │ // cjs format file + 2 │ const x = () => (void 0); + · ─ + 3 │ export {x}; + ╰──── + + × TS(7059): This syntax is reserved in files with the .mts or .cts extension. Use an `as` expression instead. + ╭─[typescript/tests/cases/conformance/node/nodeModulesForbidenSyntax.ts:2:23] + 1 │ // cjs format file + 2 │ const x = () => (void 0); + · ───────────── + 3 │ export {x}; + ╰──── + + × TS(7059): This syntax is reserved in files with the .mts or .cts extension. Use an `as` expression instead. + ╭─[typescript/tests/cases/conformance/node/nodeModulesForbidenSyntax.ts:2:20] + 1 │ // cjs format file + 2 │ const x = () => (void 0); + · ──────────────── + 3 │ export {x}; + ╰──── + × Expected 'with' in import type options ╭─[typescript/tests/cases/conformance/node/nodeModulesImportAttributesTypeModeDeclarationEmitErrors.ts:3:22] 2 │ export type LocalInterface = diff --git a/tasks/transform_conformance/snapshots/babel.snap.md b/tasks/transform_conformance/snapshots/babel.snap.md index d5f6aadaaab94..ba3a7990c1da6 100644 --- a/tasks/transform_conformance/snapshots/babel.snap.md +++ b/tasks/transform_conformance/snapshots/babel.snap.md @@ -1,6 +1,6 @@ commit: 92c052dc -Passed: 699/1164 +Passed: 695/1160 # All Passed: * babel-plugin-transform-logical-assignment-operators @@ -1311,7 +1311,7 @@ x Output mismatch x Output mismatch -# babel-preset-typescript (11/16) +# babel-preset-typescript (7/12) * node-extensions/import-in-cts/input.cts x Output mismatch