From a40a217de49c79cfac9a06950475e789f6f151c6 Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Sun, 4 Aug 2024 01:41:31 +0000 Subject: [PATCH] fix(parser): parse `assert` keyword in `TSImportAttributes` (#4610) closes #4601 --- crates/oxc_ast/src/ast/ts.rs | 3 +- .../oxc_ast/src/generated/assert_layouts.rs | 18 ++-- crates/oxc_ast/src/generated/ast_builder.rs | 8 +- crates/oxc_ast/src/generated/visit.rs | 1 + crates/oxc_ast/src/generated/visit_mut.rs | 1 + crates/oxc_ast_macros/Cargo.toml | 4 +- crates/oxc_codegen/src/gen.rs | 14 ++- crates/oxc_parser/src/ts/types.rs | 11 +- crates/oxc_traverse/src/ancestor.rs | 101 ++++++++++++------ crates/oxc_traverse/src/walk.rs | 11 +- tasks/coverage/parser_misc.snap | 5 +- tasks/coverage/parser_typescript.snap | 24 +---- 12 files changed, 122 insertions(+), 79 deletions(-) diff --git a/crates/oxc_ast/src/ast/ts.rs b/crates/oxc_ast/src/ast/ts.rs index b8c206947b3a1..8dc99f92656e6 100644 --- a/crates/oxc_ast/src/ast/ts.rs +++ b/crates/oxc_ast/src/ast/ts.rs @@ -1062,6 +1062,7 @@ pub struct TSImportType<'a> { pub struct TSImportAttributes<'a> { #[serde(flatten)] pub span: Span, + pub attributes_keyword: IdentifierName<'a>, // `with` or `assert` pub elements: Vec<'a, TSImportAttribute<'a>>, } @@ -1251,7 +1252,7 @@ pub struct TSNonNullExpression<'a> { /// @LogParam x: number // parameter decorator /// ) { /// // ... -/// } +/// } /// } /// ``` /// diff --git a/crates/oxc_ast/src/generated/assert_layouts.rs b/crates/oxc_ast/src/generated/assert_layouts.rs index b2f9d5bca7896..662c0add7c116 100644 --- a/crates/oxc_ast/src/generated/assert_layouts.rs +++ b/crates/oxc_ast/src/generated/assert_layouts.rs @@ -930,18 +930,19 @@ const _: () = { assert!(offset_of!(TSTypeQuery, type_parameters) == 24usize); assert!(size_of::() == 16usize); assert!(align_of::() == 8usize); - assert!(size_of::() == 96usize); + assert!(size_of::() == 120usize); assert!(align_of::() == 8usize); assert!(offset_of!(TSImportType, span) == 0usize); assert!(offset_of!(TSImportType, is_type_of) == 8usize); assert!(offset_of!(TSImportType, parameter) == 16usize); assert!(offset_of!(TSImportType, qualifier) == 32usize); assert!(offset_of!(TSImportType, attributes) == 48usize); - assert!(offset_of!(TSImportType, type_parameters) == 88usize); - assert!(size_of::() == 40usize); + assert!(offset_of!(TSImportType, type_parameters) == 112usize); + assert!(size_of::() == 64usize); assert!(align_of::() == 8usize); assert!(offset_of!(TSImportAttributes, span) == 0usize); - assert!(offset_of!(TSImportAttributes, elements) == 8usize); + assert!(offset_of!(TSImportAttributes, attributes_keyword) == 8usize); + assert!(offset_of!(TSImportAttributes, elements) == 32usize); assert!(size_of::() == 56usize); assert!(align_of::() == 8usize); assert!(offset_of!(TSImportAttribute, span) == 0usize); @@ -2050,18 +2051,19 @@ const _: () = { assert!(offset_of!(TSTypeQuery, type_parameters) == 16usize); assert!(size_of::() == 8usize); assert!(align_of::() == 4usize); - assert!(size_of::() == 56usize); + assert!(size_of::() == 72usize); assert!(align_of::() == 4usize); assert!(offset_of!(TSImportType, span) == 0usize); assert!(offset_of!(TSImportType, is_type_of) == 8usize); assert!(offset_of!(TSImportType, parameter) == 12usize); assert!(offset_of!(TSImportType, qualifier) == 20usize); assert!(offset_of!(TSImportType, attributes) == 28usize); - assert!(offset_of!(TSImportType, type_parameters) == 52usize); - assert!(size_of::() == 24usize); + assert!(offset_of!(TSImportType, type_parameters) == 68usize); + assert!(size_of::() == 40usize); assert!(align_of::() == 4usize); assert!(offset_of!(TSImportAttributes, span) == 0usize); - assert!(offset_of!(TSImportAttributes, elements) == 8usize); + assert!(offset_of!(TSImportAttributes, attributes_keyword) == 8usize); + assert!(offset_of!(TSImportAttributes, elements) == 24usize); assert!(size_of::() == 36usize); assert!(align_of::() == 4usize); assert!(offset_of!(TSImportAttribute, span) == 0usize); diff --git a/crates/oxc_ast/src/generated/ast_builder.rs b/crates/oxc_ast/src/generated/ast_builder.rs index a13e5d1d17dc3..c3de0e7024699 100644 --- a/crates/oxc_ast/src/generated/ast_builder.rs +++ b/crates/oxc_ast/src/generated/ast_builder.rs @@ -11832,14 +11832,16 @@ impl<'a> AstBuilder<'a> { /// /// ## Parameters /// - span: The [`Span`] covering this node + /// - attributes_keyword /// - elements #[inline] pub fn ts_import_attributes( self, span: Span, + attributes_keyword: IdentifierName<'a>, elements: Vec<'a, TSImportAttribute<'a>>, ) -> TSImportAttributes<'a> { - TSImportAttributes { span, elements } + TSImportAttributes { span, attributes_keyword, elements } } /// Builds a [`TSImportAttributes`] and stores it in the memory arena. @@ -11848,14 +11850,16 @@ impl<'a> AstBuilder<'a> { /// /// ## Parameters /// - span: The [`Span`] covering this node + /// - attributes_keyword /// - elements #[inline] pub fn alloc_ts_import_attributes( self, span: Span, + attributes_keyword: IdentifierName<'a>, elements: Vec<'a, TSImportAttribute<'a>>, ) -> Box<'a, TSImportAttributes<'a>> { - Box::new_in(self.ts_import_attributes(span, elements), self.allocator) + Box::new_in(self.ts_import_attributes(span, attributes_keyword, elements), self.allocator) } /// Builds a [`TSImportAttribute`] diff --git a/crates/oxc_ast/src/generated/visit.rs b/crates/oxc_ast/src/generated/visit.rs index 31124972cab09..784c8e343c23e 100644 --- a/crates/oxc_ast/src/generated/visit.rs +++ b/crates/oxc_ast/src/generated/visit.rs @@ -2157,6 +2157,7 @@ pub mod walk { it: &TSImportAttributes<'a>, ) { // NOTE: AstKind doesn't exists! + visitor.visit_identifier_name(&it.attributes_keyword); visitor.visit_ts_import_attribute_list(&it.elements); } diff --git a/crates/oxc_ast/src/generated/visit_mut.rs b/crates/oxc_ast/src/generated/visit_mut.rs index 872835fc7faca..fb7a3ab8d33a8 100644 --- a/crates/oxc_ast/src/generated/visit_mut.rs +++ b/crates/oxc_ast/src/generated/visit_mut.rs @@ -2230,6 +2230,7 @@ pub mod walk_mut { it: &mut TSImportAttributes<'a>, ) { // NOTE: AstType doesn't exists! + visitor.visit_identifier_name(&mut it.attributes_keyword); visitor.visit_ts_import_attribute_list(&mut it.elements); } diff --git a/crates/oxc_ast_macros/Cargo.toml b/crates/oxc_ast_macros/Cargo.toml index 5286f5812c24a..597d7c71f8c50 100644 --- a/crates/oxc_ast_macros/Cargo.toml +++ b/crates/oxc_ast_macros/Cargo.toml @@ -21,6 +21,6 @@ proc-macro = true doctest = false [dependencies] -quote = { workspace = true } -syn = { workspace = true, features = ["full"] } +quote = { workspace = true } +syn = { workspace = true, features = ["full"] } proc-macro2 = { workspace = true } diff --git a/crates/oxc_codegen/src/gen.rs b/crates/oxc_codegen/src/gen.rs index 4edf53841139a..50f096c6e2cda 100644 --- a/crates/oxc_codegen/src/gen.rs +++ b/crates/oxc_codegen/src/gen.rs @@ -3286,10 +3286,18 @@ impl<'a, const MINIFY: bool> Gen for TSImportType<'a> { impl<'a, const MINIFY: bool> Gen for TSImportAttributes<'a> { fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) { - // { with: { ... } } - p.print_str("{ with: { "); + p.print_char(b'{'); + p.print_soft_space(); + self.attributes_keyword.gen(p, ctx); + p.print_str(":"); + p.print_soft_space(); + p.print_char(b'{'); + p.print_soft_space(); p.print_list(&self.elements, ctx); - p.print_str(" }}"); + p.print_soft_space(); + p.print_char(b'}'); + p.print_soft_space(); + p.print_char(b'}'); } } diff --git a/crates/oxc_parser/src/ts/types.rs b/crates/oxc_parser/src/ts/types.rs index 5e70e26a37e85..b40a4eba8fa62 100644 --- a/crates/oxc_parser/src/ts/types.rs +++ b/crates/oxc_parser/src/ts/types.rs @@ -1001,9 +1001,14 @@ impl<'a> ParserImpl<'a> { fn parse_ts_import_attributes(&mut self) -> Result> { let span = self.start_span(); - // { with: self.expect(Kind::LCurly)?; - self.expect(Kind::With)?; + let attributes_keyword = match self.cur_kind() { + Kind::Assert if !self.cur_token().is_on_new_line => self.parse_identifier_name()?, + Kind::With => self.parse_identifier_name()?, + _ => { + return Err(self.unexpected()); + } + }; self.expect(Kind::Colon)?; self.expect(Kind::LCurly)?; let elements = self.parse_delimited_list( @@ -1014,7 +1019,7 @@ impl<'a> ParserImpl<'a> { )?; self.expect(Kind::RCurly)?; self.expect(Kind::RCurly)?; - Ok(self.ast.ts_import_attributes(span, elements)) + Ok(self.ast.ts_import_attributes(self.end_span(span), attributes_keyword, elements)) } fn parse_ts_import_attribute(&mut self) -> Result> { diff --git a/crates/oxc_traverse/src/ancestor.rs b/crates/oxc_traverse/src/ancestor.rs index 12796bfbc428d..15b0b140981df 100644 --- a/crates/oxc_traverse/src/ancestor.rs +++ b/crates/oxc_traverse/src/ancestor.rs @@ -292,38 +292,39 @@ pub(crate) enum AncestorType { TSImportTypeQualifier = 260, TSImportTypeAttributes = 261, TSImportTypeTypeParameters = 262, - TSImportAttributesElements = 263, - TSImportAttributeName = 264, - TSImportAttributeValue = 265, - TSFunctionTypeThisParam = 266, - TSFunctionTypeParams = 267, - TSFunctionTypeReturnType = 268, - TSFunctionTypeTypeParameters = 269, - TSConstructorTypeParams = 270, - TSConstructorTypeReturnType = 271, - TSConstructorTypeTypeParameters = 272, - TSMappedTypeTypeParameter = 273, - TSMappedTypeNameType = 274, - TSMappedTypeTypeAnnotation = 275, - TSTemplateLiteralTypeQuasis = 276, - TSTemplateLiteralTypeTypes = 277, - TSAsExpressionExpression = 278, - TSAsExpressionTypeAnnotation = 279, - TSSatisfiesExpressionExpression = 280, - TSSatisfiesExpressionTypeAnnotation = 281, - TSTypeAssertionExpression = 282, - TSTypeAssertionTypeAnnotation = 283, - TSImportEqualsDeclarationId = 284, - TSImportEqualsDeclarationModuleReference = 285, - TSExternalModuleReferenceExpression = 286, - TSNonNullExpressionExpression = 287, - DecoratorExpression = 288, - TSExportAssignmentExpression = 289, - TSNamespaceExportDeclarationId = 290, - TSInstantiationExpressionExpression = 291, - TSInstantiationExpressionTypeParameters = 292, - JSDocNullableTypeTypeAnnotation = 293, - JSDocNonNullableTypeTypeAnnotation = 294, + TSImportAttributesAttributesKeyword = 263, + TSImportAttributesElements = 264, + TSImportAttributeName = 265, + TSImportAttributeValue = 266, + TSFunctionTypeThisParam = 267, + TSFunctionTypeParams = 268, + TSFunctionTypeReturnType = 269, + TSFunctionTypeTypeParameters = 270, + TSConstructorTypeParams = 271, + TSConstructorTypeReturnType = 272, + TSConstructorTypeTypeParameters = 273, + TSMappedTypeTypeParameter = 274, + TSMappedTypeNameType = 275, + TSMappedTypeTypeAnnotation = 276, + TSTemplateLiteralTypeQuasis = 277, + TSTemplateLiteralTypeTypes = 278, + TSAsExpressionExpression = 279, + TSAsExpressionTypeAnnotation = 280, + TSSatisfiesExpressionExpression = 281, + TSSatisfiesExpressionTypeAnnotation = 282, + TSTypeAssertionExpression = 283, + TSTypeAssertionTypeAnnotation = 284, + TSImportEqualsDeclarationId = 285, + TSImportEqualsDeclarationModuleReference = 286, + TSExternalModuleReferenceExpression = 287, + TSNonNullExpressionExpression = 288, + DecoratorExpression = 289, + TSExportAssignmentExpression = 290, + TSNamespaceExportDeclarationId = 291, + TSInstantiationExpressionExpression = 292, + TSInstantiationExpressionTypeParameters = 293, + JSDocNullableTypeTypeAnnotation = 294, + JSDocNonNullableTypeTypeAnnotation = 295, } /// Ancestor type used in AST traversal. @@ -804,6 +805,8 @@ pub enum Ancestor<'a> { AncestorType::TSImportTypeAttributes as u16, TSImportTypeTypeParameters(TSImportTypeWithoutTypeParameters<'a>) = AncestorType::TSImportTypeTypeParameters as u16, + TSImportAttributesAttributesKeyword(TSImportAttributesWithoutAttributesKeyword<'a>) = + AncestorType::TSImportAttributesAttributesKeyword as u16, TSImportAttributesElements(TSImportAttributesWithoutElements<'a>) = AncestorType::TSImportAttributesElements as u16, TSImportAttributeName(TSImportAttributeWithoutName<'a>) = @@ -1726,7 +1729,10 @@ impl<'a> Ancestor<'a> { #[inline] pub fn is_ts_import_attributes(&self) -> bool { - matches!(self, Self::TSImportAttributesElements(_)) + matches!( + self, + Self::TSImportAttributesAttributesKeyword(_) | Self::TSImportAttributesElements(_) + ) } #[inline] @@ -11231,9 +11237,30 @@ impl<'a> TSImportTypeWithoutTypeParameters<'a> { } pub(crate) const OFFSET_TS_IMPORT_ATTRIBUTES_SPAN: usize = offset_of!(TSImportAttributes, span); +pub(crate) const OFFSET_TS_IMPORT_ATTRIBUTES_ATTRIBUTES_KEYWORD: usize = + offset_of!(TSImportAttributes, attributes_keyword); pub(crate) const OFFSET_TS_IMPORT_ATTRIBUTES_ELEMENTS: usize = offset_of!(TSImportAttributes, elements); +#[repr(transparent)] +#[derive(Debug)] +pub struct TSImportAttributesWithoutAttributesKeyword<'a>(pub(crate) *const TSImportAttributes<'a>); + +impl<'a> TSImportAttributesWithoutAttributesKeyword<'a> { + #[inline] + pub fn span(&self) -> &Span { + unsafe { &*((self.0 as *const u8).add(OFFSET_TS_IMPORT_ATTRIBUTES_SPAN) as *const Span) } + } + + #[inline] + pub fn elements(&self) -> &Vec<'a, TSImportAttribute<'a>> { + unsafe { + &*((self.0 as *const u8).add(OFFSET_TS_IMPORT_ATTRIBUTES_ELEMENTS) + as *const Vec<'a, TSImportAttribute<'a>>) + } + } +} + #[repr(transparent)] #[derive(Debug)] pub struct TSImportAttributesWithoutElements<'a>(pub(crate) *const TSImportAttributes<'a>); @@ -11243,6 +11270,14 @@ impl<'a> TSImportAttributesWithoutElements<'a> { pub fn span(&self) -> &Span { unsafe { &*((self.0 as *const u8).add(OFFSET_TS_IMPORT_ATTRIBUTES_SPAN) as *const Span) } } + + #[inline] + pub fn attributes_keyword(&self) -> &IdentifierName<'a> { + unsafe { + &*((self.0 as *const u8).add(OFFSET_TS_IMPORT_ATTRIBUTES_ATTRIBUTES_KEYWORD) + as *const IdentifierName<'a>) + } + } } pub(crate) const OFFSET_TS_IMPORT_ATTRIBUTE_SPAN: usize = offset_of!(TSImportAttribute, span); diff --git a/crates/oxc_traverse/src/walk.rs b/crates/oxc_traverse/src/walk.rs index 01ed03023929c..2d9ce8ef6df50 100644 --- a/crates/oxc_traverse/src/walk.rs +++ b/crates/oxc_traverse/src/walk.rs @@ -5125,9 +5125,16 @@ pub(crate) unsafe fn walk_ts_import_attributes<'a, Tr: Traverse<'a>>( ctx: &mut TraverseCtx<'a>, ) { traverser.enter_ts_import_attributes(&mut *node, ctx); - ctx.push_stack(Ancestor::TSImportAttributesElements( - ancestor::TSImportAttributesWithoutElements(node), + ctx.push_stack(Ancestor::TSImportAttributesAttributesKeyword( + ancestor::TSImportAttributesWithoutAttributesKeyword(node), )); + walk_identifier_name( + traverser, + (node as *mut u8).add(ancestor::OFFSET_TS_IMPORT_ATTRIBUTES_ATTRIBUTES_KEYWORD) + as *mut IdentifierName, + ctx, + ); + ctx.retag_stack(AncestorType::TSImportAttributesElements); for item in (*((node as *mut u8).add(ancestor::OFFSET_TS_IMPORT_ATTRIBUTES_ELEMENTS) as *mut Vec)) .iter_mut() diff --git a/tasks/coverage/parser_misc.snap b/tasks/coverage/parser_misc.snap index 6c3c9391c1955..58c32577ead24 100644 --- a/tasks/coverage/parser_misc.snap +++ b/tasks/coverage/parser_misc.snap @@ -90,12 +90,11 @@ Negative Passed: 14/14 (100.00%) · ───────── ╰──── - × Expected `with` but found `string` + × Unexpected token ╭─[fail/oxc-2394.ts:20:22] 19 │ export type LocalInterface = 20 │ & import("pkg", {"resolution-mode": "require"}).RequireInterface - · ────────┬──────── - · ╰── `with` expected + · ───────────────── 21 │ & import("pkg", {"resolution-mode": "import"}).ImportInterface; ╰──── diff --git a/tasks/coverage/parser_typescript.snap b/tasks/coverage/parser_typescript.snap index d9d36eb527fc2..e8a77e2f80d8a 100644 --- a/tasks/coverage/parser_typescript.snap +++ b/tasks/coverage/parser_typescript.snap @@ -1,8 +1,8 @@ commit: d8086f14 parser_typescript Summary: -AST Parsed : 6444/6456 (99.81%) -Positive Passed: 6421/6456 (99.46%) +AST Parsed : 6446/6456 (99.85%) +Positive Passed: 6423/6456 (99.49%) Negative Passed: 1167/5653 (20.64%) Expect Syntax Error: "compiler/ClassDeclaration10.ts" Expect Syntax Error: "compiler/ClassDeclaration11.ts" @@ -4817,16 +4817,6 @@ Expect to Parse: "conformance/jsdoc/overloadTag3.ts" · ────── 5 │ /** ╰──── -Expect to Parse: "conformance/moduleResolution/resolutionModeImportType1.ts" - - × Expected `with` but found `assert` - ╭─[conformance/moduleResolution/resolutionModeImportType1.ts:2:38] - 1 │ type Default = typeof import("foo").x; - 2 │ type Import = typeof import("foo", { assert: { "resolution-mode": "import" } }).x; - · ───┬── - · ╰── `with` expected - 3 │ type Require = typeof import("foo", { assert: { "resolution-mode": "require" } }).x; - ╰──── Expect to Parse: "conformance/moduleResolution/untypedModuleImport.ts" × Expected a semicolon or an implicit semicolon after a statement, but found none @@ -4845,16 +4835,6 @@ Expect to Parse: "conformance/moduleResolution/untypedModuleImport_vsAmbient.ts" · ▲ ╰──── help: Try insert a semicolon here -Expect to Parse: "conformance/node/nodeModulesImportTypeModeDeclarationEmit1.ts" - - × Expected `with` but found `assert` - ╭─[conformance/node/nodeModulesImportTypeModeDeclarationEmit1.ts:2:23] - 1 │ export type LocalInterface = - 2 │ & import("pkg", { assert: {"resolution-mode": "require"} }).RequireInterface - · ───┬── - · ╰── `with` expected - 3 │ & import("pkg", { assert: {"resolution-mode": "import"} }).ImportInterface; - ╰──── Expect to Parse: "conformance/salsa/annotatedThisPropertyInitializerDoesntNarrow.ts" × Cannot use export statement outside a module