feat(ast)!: add directives field to TSModuleBlock (#3830)

closes #3564
This commit is contained in:
Boshen 2024-06-22 18:14:08 +00:00
parent 445603444f
commit cfcef241db
10 changed files with 141 additions and 144 deletions

View file

@ -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>>,
}

View file

@ -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]

View file

@ -51,19 +51,10 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> 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<const MINIFY: bool>(
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<MINIFY> 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<MINIFY> for TSModuleDeclarationName<'a> {
impl<'a, const MINIFY: bool> Gen<MINIFY> 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);
});
}
}

View file

@ -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(

View file

@ -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))

View file

@ -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();

View file

@ -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<Box<'a, TSModuleBlock<'a>>> {
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<Statement<'a>> {
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(

View file

@ -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<Expression<'a>>,
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 = {

View file

@ -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);

View file

@ -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<Directive>))
.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<Statement>,