From f7edee5eb67e48be30e4f30fcb3594cfd5c03a8f Mon Sep 17 00:00:00 2001 From: Boshen Date: Sun, 23 Jun 2024 01:52:52 +0800 Subject: [PATCH] feat(ast)!: add `directives` field to `TSModuleBlock` closes #3564 --- crates/oxc_ast/src/ast/ts.rs | 1 + crates/oxc_ast/src/ast_builder.rs | 3 +- crates/oxc_codegen/src/gen.rs | 19 +-- .../src/declaration.rs | 2 +- crates/oxc_parser/src/js/statement.rs | 64 ++++------ crates/oxc_parser/src/lib.rs | 2 +- crates/oxc_parser/src/ts/statement.rs | 20 +--- .../src/typescript/namespace.rs | 50 ++++---- crates/oxc_traverse/src/ancestor.rs | 113 +++++++++++------- crates/oxc_traverse/src/walk.rs | 11 +- 10 files changed, 141 insertions(+), 144 deletions(-) diff --git a/crates/oxc_ast/src/ast/ts.rs b/crates/oxc_ast/src/ast/ts.rs index 89ac627629bec3..ca826a9ffaad70 100644 --- a/crates/oxc_ast/src/ast/ts.rs +++ b/crates/oxc_ast/src/ast/ts.rs @@ -1004,6 +1004,7 @@ pub enum TSModuleDeclarationBody<'a> { pub struct TSModuleBlock<'a> { #[cfg_attr(feature = "serialize", serde(flatten))] pub span: Span, + pub directives: Vec<'a, Directive<'a>>, pub body: Vec<'a, Statement<'a>>, } diff --git a/crates/oxc_ast/src/ast_builder.rs b/crates/oxc_ast/src/ast_builder.rs index b61359b5ce059d..8702ab698e6c43 100644 --- a/crates/oxc_ast/src/ast_builder.rs +++ b/crates/oxc_ast/src/ast_builder.rs @@ -1761,9 +1761,10 @@ impl<'a> AstBuilder<'a> { pub fn ts_module_block( self, span: Span, + directives: Vec<'a, Directive<'a>>, body: Vec<'a, Statement<'a>>, ) -> Box<'a, TSModuleBlock<'a>> { - self.alloc(TSModuleBlock { span, body }) + self.alloc(TSModuleBlock { span, directives, body }) } #[inline] diff --git a/crates/oxc_codegen/src/gen.rs b/crates/oxc_codegen/src/gen.rs index 142dfd3c5ddb6e..ebc4cbf04ae359 100644 --- a/crates/oxc_codegen/src/gen.rs +++ b/crates/oxc_codegen/src/gen.rs @@ -51,19 +51,10 @@ impl<'a, const MINIFY: bool> Gen for Program<'a> { if let Some(hashbang) = &self.hashbang { hashbang.gen(p, ctx); } - print_directives_and_statements(p, &self.directives, &self.body, ctx); + p.print_directives_and_statements(Some(&self.directives), &self.body, ctx); } } -fn print_directives_and_statements( - p: &mut Codegen<{ MINIFY }>, - directives: &[Directive], - statements: &[Statement<'_>], - ctx: Context, -) { - p.print_directives_and_statements(Some(directives), statements, ctx); -} - impl<'a, const MINIFY: bool> Gen for Hashbang<'a> { fn gen(&self, p: &mut Codegen<{ MINIFY }>, _ctx: Context) { p.print_str(b"#!"); @@ -3300,11 +3291,9 @@ impl<'a, const MINIFY: bool> Gen for TSModuleDeclarationName<'a> { impl<'a, const MINIFY: bool> Gen for TSModuleBlock<'a> { fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) { - p.print_curly_braces(self.span, self.body.is_empty(), |p| { - for item in &self.body { - p.print_semicolon_if_needed(); - item.gen(p, ctx); - } + let is_empty = self.directives.is_empty() && self.body.is_empty(); + p.print_curly_braces(self.span, is_empty, |p| { + p.print_directives_and_statements(Some(&self.directives), &self.body, ctx); }); } } diff --git a/crates/oxc_isolated_declarations/src/declaration.rs b/crates/oxc_isolated_declarations/src/declaration.rs index 49b3e67620b31f..2dfb2f54967bcc 100644 --- a/crates/oxc_isolated_declarations/src/declaration.rs +++ b/crates/oxc_isolated_declarations/src/declaration.rs @@ -138,7 +138,7 @@ impl<'a> IsolatedDeclarations<'a> { self.scope.enter_scope(ScopeFlags::TsModuleBlock); let stmts = self.transform_statements_on_demand(&block.body); self.scope.leave_scope(); - self.ast.ts_module_block(SPAN, stmts) + self.ast.ts_module_block(SPAN, self.ast.new_vec(), stmts) } pub fn transform_ts_module_declaration( diff --git a/crates/oxc_parser/src/js/statement.rs b/crates/oxc_parser/src/js/statement.rs index 89653ae7a6dca7..a592e062f8e1af 100644 --- a/crates/oxc_parser/src/js/statement.rs +++ b/crates/oxc_parser/src/js/statement.rs @@ -37,52 +37,30 @@ impl<'a> ParserImpl<'a> { let mut expecting_directives = true; while !self.at(Kind::Eof) { - match self.cur_kind() { - Kind::RCurly if !is_top_level => break, - Kind::Import if !matches!(self.peek_kind(), Kind::Dot | Kind::LParen) => { - let stmt = self.parse_import_declaration()?; - statements.push(stmt); - expecting_directives = false; - } - Kind::Export => { - let stmt = self.parse_export_declaration()?; - statements.push(stmt); - expecting_directives = false; - } - Kind::At => { - self.eat_decorators()?; - expecting_directives = false; - continue; - } - _ => { - let stmt = self.parse_statement_list_item(StatementContext::StatementList)?; - - // Section 11.2.1 Directive Prologue - // The only way to get a correct directive is to parse the statement first and check if it is a string literal. - // All other method are flawed, see test cases in [babel](https://github.com/babel/babel/blob/main/packages/babel-parser/test/fixtures/core/categorized/not-directive/input.js) - if expecting_directives { - if let Statement::ExpressionStatement(expr) = &stmt { - if let Expression::StringLiteral(string) = &expr.expression { - // span start will mismatch if they are parenthesized when `preserve_parens = false` - if expr.span.start == string.span.start { - let src = &self.source_text[string.span.start as usize + 1 - ..string.span.end as usize - 1]; - let directive = self.ast.directive( - expr.span, - (*string).clone(), - Atom::from(src), - ); - directives.push(directive); - continue; - } - } + if !is_top_level && self.at(Kind::RCurly) { + break; + } + let stmt = self.parse_statement_list_item(StatementContext::StatementList)?; + // Section 11.2.1 Directive Prologue + // The only way to get a correct directive is to parse the statement first and check if it is a string literal. + // All other method are flawed, see test cases in [babel](https://github.com/babel/babel/blob/main/packages/babel-parser/test/fixtures/core/categorized/not-directive/input.js) + if expecting_directives { + if let Statement::ExpressionStatement(expr) = &stmt { + if let Expression::StringLiteral(string) = &expr.expression { + // span start will mismatch if they are parenthesized when `preserve_parens = false` + if expr.span.start == string.span.start { + let src = &self.source_text + [string.span.start as usize + 1..string.span.end as usize - 1]; + let directive = + self.ast.directive(expr.span, (*string).clone(), Atom::from(src)); + directives.push(directive); + continue; } - expecting_directives = false; } - - statements.push(stmt); } - }; + expecting_directives = false; + } + statements.push(stmt); } Ok((directives, statements)) diff --git a/crates/oxc_parser/src/lib.rs b/crates/oxc_parser/src/lib.rs index cbe6cc7329cda3..e432adbff1f84c 100644 --- a/crates/oxc_parser/src/lib.rs +++ b/crates/oxc_parser/src/lib.rs @@ -492,7 +492,7 @@ mod test { let sources = [ ("import x from 'foo'; 'use strict';", 2), ("export {x} from 'foo'; 'use strict';", 2), - ("@decorator 'use strict';", 1), + (";'use strict';", 2), ]; for (source, body_length) in sources { let ret = Parser::new(&allocator, source, source_type).parse(); diff --git a/crates/oxc_parser/src/ts/statement.rs b/crates/oxc_parser/src/ts/statement.rs index 910393658e6c22..263de11cf74e0e 100644 --- a/crates/oxc_parser/src/ts/statement.rs +++ b/crates/oxc_parser/src/ts/statement.rs @@ -11,7 +11,7 @@ use crate::{ js::{FunctionKind, VariableDeclarationContext, VariableDeclarationParent}, lexer::Kind, list::{NormalList, SeparatedList}, - ParserImpl, StatementContext, + ParserImpl, }; impl<'a> ParserImpl<'a> { @@ -217,21 +217,11 @@ impl<'a> ParserImpl<'a> { fn parse_ts_module_block(&mut self) -> Result>> { let span = self.start_span(); - - let mut statements = self.ast.new_vec(); - self.expect(Kind::LCurly)?; - - while !self.eat(Kind::RCurly) && !self.at(Kind::Eof) { - let stmt = self.parse_ts_module_item()?; - statements.push(stmt); - } - - Ok(self.ast.ts_module_block(self.end_span(span), statements)) - } - - fn parse_ts_module_item(&mut self) -> Result> { - self.parse_statement_list_item(StatementContext::StatementList) + let (directives, statements) = + self.parse_directives_and_statements(/* is_top_level */ false)?; + self.expect(Kind::RCurly)?; + Ok(self.ast.ts_module_block(self.end_span(span), directives, statements)) } pub(crate) fn parse_ts_namespace_or_module_declaration_body( diff --git a/crates/oxc_transformer/src/typescript/namespace.rs b/crates/oxc_transformer/src/typescript/namespace.rs index b2fcfe5195b304..7e81b4efdb314d 100644 --- a/crates/oxc_transformer/src/typescript/namespace.rs +++ b/crates/oxc_transformer/src/typescript/namespace.rs @@ -141,8 +141,15 @@ impl<'a> TypeScript<'a> { let symbol_id = ctx.generate_uid(&real_name, scope_id, SymbolFlags::FunctionScopedVariable); let name = self.ctx.ast.new_atom(ctx.symbols().get_name(symbol_id)); - let namespace_top_level = match body { - TSModuleDeclarationBody::TSModuleBlock(block) => block.unbox().body, + let directives; + let namespace_top_level; + + match body { + TSModuleDeclarationBody::TSModuleBlock(block) => { + let block = block.unbox(); + directives = block.directives; + namespace_top_level = block.body; + } // We handle `namespace X.Y {}` as if it was // namespace X { // export namespace Y {} @@ -152,9 +159,10 @@ impl<'a> TypeScript<'a> { let export_named_decl = self.ctx.ast.plain_export_named_declaration_declaration(SPAN, declaration); let stmt = Statement::ExportNamedDeclaration(export_named_decl); - self.ctx.ast.new_vec_single(stmt) + directives = self.ctx.ast.new_vec(); + namespace_top_level = self.ctx.ast.new_vec_single(stmt); } - }; + } let mut new_stmts = self.ctx.ast.new_vec(); @@ -256,7 +264,15 @@ impl<'a> TypeScript<'a> { return None; } - Some(self.transform_namespace(name, real_name, new_stmts, parent_export, scope_id, ctx)) + Some(self.transform_namespace( + name, + real_name, + new_stmts, + directives, + parent_export, + scope_id, + ctx, + )) } // `namespace Foo { }` -> `let Foo; (function (_Foo) { })(Foo || (Foo = {}));` @@ -280,35 +296,17 @@ impl<'a> TypeScript<'a> { // `namespace Foo { }` -> `let Foo; (function (_Foo) { })(Foo || (Foo = {}));` // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - #[allow(clippy::needless_pass_by_value)] + #[allow(clippy::needless_pass_by_value, clippy::too_many_arguments)] fn transform_namespace( &self, arg_name: Atom<'a>, real_name: Atom<'a>, - mut stmts: Vec<'a, Statement<'a>>, + stmts: Vec<'a, Statement<'a>>, + directives: Vec<'a, Directive<'a>>, parent_export: Option>, scope_id: ScopeId, ctx: &mut TraverseCtx, ) -> Statement<'a> { - let mut directives = self.ctx.ast.new_vec(); - - // Check if the namespace has a `use strict` directive - if stmts.first().is_some_and(|stmt| { - matches!(stmt, Statement::ExpressionStatement(es) if - matches!(&es.expression, Expression::StringLiteral(literal) if - literal.value == "use strict") - ) - }) { - stmts.remove(0); - let directive = self.ctx.ast.new_atom("use strict"); - let directive = Directive { - span: SPAN, - expression: StringLiteral::new(SPAN, directive.clone()), - directive, - }; - directives.push(directive); - } - // `(function (_N) { var x; })(N || (N = {}))`; // ^^^^^^^^^^^^^^^^^^^^^^^^^^ let callee = { diff --git a/crates/oxc_traverse/src/ancestor.rs b/crates/oxc_traverse/src/ancestor.rs index 6c8656d9be28b1..55b7dfa1984ca3 100644 --- a/crates/oxc_traverse/src/ancestor.rs +++ b/crates/oxc_traverse/src/ancestor.rs @@ -281,46 +281,47 @@ pub(crate) enum AncestorType { TSTypePredicateTypeAnnotation = 249, TSModuleDeclarationId = 250, TSModuleDeclarationBody = 251, - TSModuleBlockBody = 252, - TSTypeLiteralMembers = 253, - TSInferTypeTypeParameter = 254, - TSTypeQueryExprName = 255, - TSTypeQueryTypeParameters = 256, - TSImportTypeArgument = 257, - TSImportTypeQualifier = 258, - TSImportTypeAttributes = 259, - TSImportTypeTypeParameters = 260, - TSImportAttributesElements = 261, - TSImportAttributeName = 262, - TSImportAttributeValue = 263, - TSFunctionTypeThisParam = 264, - TSFunctionTypeParams = 265, - TSFunctionTypeReturnType = 266, - TSFunctionTypeTypeParameters = 267, - TSConstructorTypeParams = 268, - TSConstructorTypeReturnType = 269, - TSConstructorTypeTypeParameters = 270, - TSMappedTypeTypeParameter = 271, - TSMappedTypeNameType = 272, - TSMappedTypeTypeAnnotation = 273, - TSTemplateLiteralTypeQuasis = 274, - TSTemplateLiteralTypeTypes = 275, - TSAsExpressionExpression = 276, - TSAsExpressionTypeAnnotation = 277, - TSSatisfiesExpressionExpression = 278, - TSSatisfiesExpressionTypeAnnotation = 279, - TSTypeAssertionExpression = 280, - TSTypeAssertionTypeAnnotation = 281, - TSImportEqualsDeclarationId = 282, - TSImportEqualsDeclarationModuleReference = 283, - TSExternalModuleReferenceExpression = 284, - TSNonNullExpressionExpression = 285, - DecoratorExpression = 286, - TSExportAssignmentExpression = 287, - TSNamespaceExportDeclarationId = 288, - TSInstantiationExpressionExpression = 289, - TSInstantiationExpressionTypeParameters = 290, - JSDocNullableTypeTypeAnnotation = 291, + TSModuleBlockDirectives = 252, + TSModuleBlockBody = 253, + TSTypeLiteralMembers = 254, + TSInferTypeTypeParameter = 255, + TSTypeQueryExprName = 256, + TSTypeQueryTypeParameters = 257, + TSImportTypeArgument = 258, + TSImportTypeQualifier = 259, + TSImportTypeAttributes = 260, + TSImportTypeTypeParameters = 261, + TSImportAttributesElements = 262, + TSImportAttributeName = 263, + TSImportAttributeValue = 264, + TSFunctionTypeThisParam = 265, + TSFunctionTypeParams = 266, + TSFunctionTypeReturnType = 267, + TSFunctionTypeTypeParameters = 268, + TSConstructorTypeParams = 269, + TSConstructorTypeReturnType = 270, + TSConstructorTypeTypeParameters = 271, + TSMappedTypeTypeParameter = 272, + TSMappedTypeNameType = 273, + TSMappedTypeTypeAnnotation = 274, + TSTemplateLiteralTypeQuasis = 275, + TSTemplateLiteralTypeTypes = 276, + TSAsExpressionExpression = 277, + TSAsExpressionTypeAnnotation = 278, + TSSatisfiesExpressionExpression = 279, + TSSatisfiesExpressionTypeAnnotation = 280, + TSTypeAssertionExpression = 281, + TSTypeAssertionTypeAnnotation = 282, + TSImportEqualsDeclarationId = 283, + TSImportEqualsDeclarationModuleReference = 284, + TSExternalModuleReferenceExpression = 285, + TSNonNullExpressionExpression = 286, + DecoratorExpression = 287, + TSExportAssignmentExpression = 288, + TSNamespaceExportDeclarationId = 289, + TSInstantiationExpressionExpression = 290, + TSInstantiationExpressionTypeParameters = 291, + JSDocNullableTypeTypeAnnotation = 292, } /// Ancestor type used in AST traversal. @@ -781,6 +782,8 @@ pub enum Ancestor<'a> { AncestorType::TSModuleDeclarationId as u16, TSModuleDeclarationBody(TSModuleDeclarationWithoutBody<'a>) = AncestorType::TSModuleDeclarationBody as u16, + TSModuleBlockDirectives(TSModuleBlockWithoutDirectives<'a>) = + AncestorType::TSModuleBlockDirectives as u16, TSModuleBlockBody(TSModuleBlockWithoutBody<'a>) = AncestorType::TSModuleBlockBody as u16, TSTypeLiteralMembers(TSTypeLiteralWithoutMembers<'a>) = AncestorType::TSTypeLiteralMembers as u16, @@ -1681,7 +1684,7 @@ impl<'a> Ancestor<'a> { #[inline] pub fn is_ts_module_block(&self) -> bool { - matches!(self, Self::TSModuleBlockBody(_)) + matches!(self, Self::TSModuleBlockDirectives(_) | Self::TSModuleBlockBody(_)) } #[inline] @@ -10752,8 +10755,28 @@ impl<'a> TSModuleDeclarationWithoutBody<'a> { } pub(crate) const OFFSET_TS_MODULE_BLOCK_SPAN: usize = offset_of!(TSModuleBlock, span); +pub(crate) const OFFSET_TS_MODULE_BLOCK_DIRECTIVES: usize = offset_of!(TSModuleBlock, directives); pub(crate) const OFFSET_TS_MODULE_BLOCK_BODY: usize = offset_of!(TSModuleBlock, body); +#[repr(transparent)] +#[derive(Debug)] +pub struct TSModuleBlockWithoutDirectives<'a>(pub(crate) *const TSModuleBlock<'a>); + +impl<'a> TSModuleBlockWithoutDirectives<'a> { + #[inline] + pub fn span(&self) -> &Span { + unsafe { &*((self.0 as *const u8).add(OFFSET_TS_MODULE_BLOCK_SPAN) as *const Span) } + } + + #[inline] + pub fn body(&self) -> &Vec<'a, Statement<'a>> { + unsafe { + &*((self.0 as *const u8).add(OFFSET_TS_MODULE_BLOCK_BODY) + as *const Vec<'a, Statement<'a>>) + } + } +} + #[repr(transparent)] #[derive(Debug)] pub struct TSModuleBlockWithoutBody<'a>(pub(crate) *const TSModuleBlock<'a>); @@ -10763,6 +10786,14 @@ impl<'a> TSModuleBlockWithoutBody<'a> { pub fn span(&self) -> &Span { unsafe { &*((self.0 as *const u8).add(OFFSET_TS_MODULE_BLOCK_SPAN) as *const Span) } } + + #[inline] + pub fn directives(&self) -> &Vec<'a, Directive<'a>> { + unsafe { + &*((self.0 as *const u8).add(OFFSET_TS_MODULE_BLOCK_DIRECTIVES) + as *const Vec<'a, Directive<'a>>) + } + } } pub(crate) const OFFSET_TS_TYPE_LITERAL_SPAN: usize = offset_of!(TSTypeLiteral, span); diff --git a/crates/oxc_traverse/src/walk.rs b/crates/oxc_traverse/src/walk.rs index 6bb98779be1646..c53a9771c16735 100644 --- a/crates/oxc_traverse/src/walk.rs +++ b/crates/oxc_traverse/src/walk.rs @@ -4903,7 +4903,16 @@ pub(crate) unsafe fn walk_ts_module_block<'a, Tr: Traverse<'a>>( ctx: &mut TraverseCtx<'a>, ) { traverser.enter_ts_module_block(&mut *node, ctx); - ctx.push_stack(Ancestor::TSModuleBlockBody(ancestor::TSModuleBlockWithoutBody(node))); + ctx.push_stack(Ancestor::TSModuleBlockDirectives(ancestor::TSModuleBlockWithoutDirectives( + node, + ))); + for item in (*((node as *mut u8).add(ancestor::OFFSET_TS_MODULE_BLOCK_DIRECTIVES) + as *mut Vec)) + .iter_mut() + { + walk_directive(traverser, item as *mut _, ctx); + } + ctx.retag_stack(AncestorType::TSModuleBlockBody); walk_statements( traverser, (node as *mut u8).add(ancestor::OFFSET_TS_MODULE_BLOCK_BODY) as *mut Vec,