From 363a1aee20ddb70be87341428796caf4d9eeb385 Mon Sep 17 00:00:00 2001 From: PerishCode Date: Thu, 25 Jun 2026 15:43:50 +0800 Subject: [PATCH] typescript: accept Deno wrapper syntax --- .../src/parser/statements.rs | 3 ++ .../src/parser/types.rs | 12 +++++- .../flavor-plugin-typescript/tests/parser.rs | 38 +++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/crates/flavor-plugin-typescript/src/parser/statements.rs b/crates/flavor-plugin-typescript/src/parser/statements.rs index 23f4c5c..1fcbac5 100644 --- a/crates/flavor-plugin-typescript/src/parser/statements.rs +++ b/crates/flavor-plugin-typescript/src/parser/statements.rs @@ -36,6 +36,9 @@ impl<'a> Parser<'a> { pub(super) fn parse_for_statement(&mut self) { self.builder.start_node(kind::FOR_STATEMENT); self.bump(); + if self.at(kind::KEYWORD_AWAIT) { + self.bump(); + } self.parenthesized_condition("expected '(' to start for header"); self.statement_or_block(); self.builder.finish_node(); diff --git a/crates/flavor-plugin-typescript/src/parser/types.rs b/crates/flavor-plugin-typescript/src/parser/types.rs index 511a93e..d1289c2 100644 --- a/crates/flavor-plugin-typescript/src/parser/types.rs +++ b/crates/flavor-plugin-typescript/src/parser/types.rs @@ -22,7 +22,17 @@ impl<'a> Parser<'a> { let mut segment_stops = stops.to_vec(); segment_stops.push(kind::PIPE); segment_stops.push(kind::AMPERSAND); - self.parse_type_segment_until(&segment_stops); + if self.at(kind::OPEN_BRACE) && stops.contains(&kind::OPEN_BRACE) { + if self.is_mapped_type_start() { + self.parse_mapped_type(); + } else { + self.parse_object_type(); + } + } else if self.at_any(stops) { + break; + } else { + self.parse_type_segment_until(&segment_stops); + } if self.at(kind::PIPE) || self.at(kind::AMPERSAND) { self.bump(); self.ensure_progress(start, "type"); diff --git a/crates/flavor-plugin-typescript/tests/parser.rs b/crates/flavor-plugin-typescript/tests/parser.rs index 3d8b5df..e59f30e 100644 --- a/crates/flavor-plugin-typescript/tests/parser.rs +++ b/crates/flavor-plugin-typescript/tests/parser.rs @@ -140,6 +140,32 @@ mod declarations { assert!(output.diagnostics.is_empty()); } + #[test] + fn function_return_object_type() { + let output = ts_output( + "function splitPath(path: string): { dir: string; name: string } { return { dir: '', name: path }; }", + ); + + assert!(has_node(&output, kind::FUNCTION_DECLARATION)); + assert!(has_node(&output, kind::RETURN_TYPE)); + assert!(has_node(&output, kind::OBJECT_TYPE)); + assert!(has_node(&output, kind::BLOCK)); + assert!(output.diagnostics.is_empty(), "{:?}", output.diagnostics); + } + + #[test] + fn object_intersection_return() { + let output = ts_output( + "type Options = { raw: boolean }; function parseArgs(args: string[]): Options & { help: boolean } { return { raw: false, help: false }; }", + ); + + assert!(has_node(&output, kind::FUNCTION_DECLARATION)); + assert!(has_node(&output, kind::RETURN_TYPE)); + assert!(has_node(&output, kind::INTERSECTION_TYPE)); + assert!(has_node(&output, kind::OBJECT_TYPE)); + assert!(output.diagnostics.is_empty(), "{:?}", output.diagnostics); + } + #[test] fn decorated_class() { let output = ts_output("@sealed() class Example extends Base { value!: string; }"); @@ -268,6 +294,18 @@ mod control_flow { assert!(has_node(&output, kind::CONTINUE_STATEMENT)); assert!(output.diagnostics.is_empty()); } + + #[test] + fn async_iterator_statement() { + let output = ts_output( + "async function list(path: string): Promise { for await (const entry of Deno.readDir(path)) { console.log(entry.name); } }", + ); + + assert!(has_node(&output, kind::FUNCTION_DECLARATION)); + assert!(has_node(&output, kind::FOR_STATEMENT)); + assert!(has_token(&output, kind::KEYWORD_AWAIT)); + assert!(output.diagnostics.is_empty(), "{:?}", output.diagnostics); + } } mod expressions {