Skip to content

Commit f7edee5

Browse files
committed
feat(ast)!: add directives field to TSModuleBlock
closes #3564
1 parent 4456034 commit f7edee5

File tree

10 files changed

+141
-144
lines changed

10 files changed

+141
-144
lines changed

crates/oxc_ast/src/ast/ts.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,7 @@ pub enum TSModuleDeclarationBody<'a> {
10041004
pub struct TSModuleBlock<'a> {
10051005
#[cfg_attr(feature = "serialize", serde(flatten))]
10061006
pub span: Span,
1007+
pub directives: Vec<'a, Directive<'a>>,
10071008
pub body: Vec<'a, Statement<'a>>,
10081009
}
10091010

crates/oxc_ast/src/ast_builder.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1761,9 +1761,10 @@ impl<'a> AstBuilder<'a> {
17611761
pub fn ts_module_block(
17621762
self,
17631763
span: Span,
1764+
directives: Vec<'a, Directive<'a>>,
17641765
body: Vec<'a, Statement<'a>>,
17651766
) -> Box<'a, TSModuleBlock<'a>> {
1766-
self.alloc(TSModuleBlock { span, body })
1767+
self.alloc(TSModuleBlock { span, directives, body })
17671768
}
17681769

17691770
#[inline]

crates/oxc_codegen/src/gen.rs

+4-15
Original file line numberDiff line numberDiff line change
@@ -51,19 +51,10 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for Program<'a> {
5151
if let Some(hashbang) = &self.hashbang {
5252
hashbang.gen(p, ctx);
5353
}
54-
print_directives_and_statements(p, &self.directives, &self.body, ctx);
54+
p.print_directives_and_statements(Some(&self.directives), &self.body, ctx);
5555
}
5656
}
5757

58-
fn print_directives_and_statements<const MINIFY: bool>(
59-
p: &mut Codegen<{ MINIFY }>,
60-
directives: &[Directive],
61-
statements: &[Statement<'_>],
62-
ctx: Context,
63-
) {
64-
p.print_directives_and_statements(Some(directives), statements, ctx);
65-
}
66-
6758
impl<'a, const MINIFY: bool> Gen<MINIFY> for Hashbang<'a> {
6859
fn gen(&self, p: &mut Codegen<{ MINIFY }>, _ctx: Context) {
6960
p.print_str(b"#!");
@@ -3300,11 +3291,9 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for TSModuleDeclarationName<'a> {
33003291

33013292
impl<'a, const MINIFY: bool> Gen<MINIFY> for TSModuleBlock<'a> {
33023293
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
3303-
p.print_curly_braces(self.span, self.body.is_empty(), |p| {
3304-
for item in &self.body {
3305-
p.print_semicolon_if_needed();
3306-
item.gen(p, ctx);
3307-
}
3294+
let is_empty = self.directives.is_empty() && self.body.is_empty();
3295+
p.print_curly_braces(self.span, is_empty, |p| {
3296+
p.print_directives_and_statements(Some(&self.directives), &self.body, ctx);
33083297
});
33093298
}
33103299
}

crates/oxc_isolated_declarations/src/declaration.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ impl<'a> IsolatedDeclarations<'a> {
138138
self.scope.enter_scope(ScopeFlags::TsModuleBlock);
139139
let stmts = self.transform_statements_on_demand(&block.body);
140140
self.scope.leave_scope();
141-
self.ast.ts_module_block(SPAN, stmts)
141+
self.ast.ts_module_block(SPAN, self.ast.new_vec(), stmts)
142142
}
143143

144144
pub fn transform_ts_module_declaration(

crates/oxc_parser/src/js/statement.rs

+21-43
Original file line numberDiff line numberDiff line change
@@ -37,52 +37,30 @@ impl<'a> ParserImpl<'a> {
3737

3838
let mut expecting_directives = true;
3939
while !self.at(Kind::Eof) {
40-
match self.cur_kind() {
41-
Kind::RCurly if !is_top_level => break,
42-
Kind::Import if !matches!(self.peek_kind(), Kind::Dot | Kind::LParen) => {
43-
let stmt = self.parse_import_declaration()?;
44-
statements.push(stmt);
45-
expecting_directives = false;
46-
}
47-
Kind::Export => {
48-
let stmt = self.parse_export_declaration()?;
49-
statements.push(stmt);
50-
expecting_directives = false;
51-
}
52-
Kind::At => {
53-
self.eat_decorators()?;
54-
expecting_directives = false;
55-
continue;
56-
}
57-
_ => {
58-
let stmt = self.parse_statement_list_item(StatementContext::StatementList)?;
59-
60-
// Section 11.2.1 Directive Prologue
61-
// The only way to get a correct directive is to parse the statement first and check if it is a string literal.
62-
// 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)
63-
if expecting_directives {
64-
if let Statement::ExpressionStatement(expr) = &stmt {
65-
if let Expression::StringLiteral(string) = &expr.expression {
66-
// span start will mismatch if they are parenthesized when `preserve_parens = false`
67-
if expr.span.start == string.span.start {
68-
let src = &self.source_text[string.span.start as usize + 1
69-
..string.span.end as usize - 1];
70-
let directive = self.ast.directive(
71-
expr.span,
72-
(*string).clone(),
73-
Atom::from(src),
74-
);
75-
directives.push(directive);
76-
continue;
77-
}
78-
}
40+
if !is_top_level && self.at(Kind::RCurly) {
41+
break;
42+
}
43+
let stmt = self.parse_statement_list_item(StatementContext::StatementList)?;
44+
// Section 11.2.1 Directive Prologue
45+
// The only way to get a correct directive is to parse the statement first and check if it is a string literal.
46+
// 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)
47+
if expecting_directives {
48+
if let Statement::ExpressionStatement(expr) = &stmt {
49+
if let Expression::StringLiteral(string) = &expr.expression {
50+
// span start will mismatch if they are parenthesized when `preserve_parens = false`
51+
if expr.span.start == string.span.start {
52+
let src = &self.source_text
53+
[string.span.start as usize + 1..string.span.end as usize - 1];
54+
let directive =
55+
self.ast.directive(expr.span, (*string).clone(), Atom::from(src));
56+
directives.push(directive);
57+
continue;
7958
}
80-
expecting_directives = false;
8159
}
82-
83-
statements.push(stmt);
8460
}
85-
};
61+
expecting_directives = false;
62+
}
63+
statements.push(stmt);
8664
}
8765

8866
Ok((directives, statements))

crates/oxc_parser/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ mod test {
492492
let sources = [
493493
("import x from 'foo'; 'use strict';", 2),
494494
("export {x} from 'foo'; 'use strict';", 2),
495-
("@decorator 'use strict';", 1),
495+
(";'use strict';", 2),
496496
];
497497
for (source, body_length) in sources {
498498
let ret = Parser::new(&allocator, source, source_type).parse();

crates/oxc_parser/src/ts/statement.rs

+5-15
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::{
1111
js::{FunctionKind, VariableDeclarationContext, VariableDeclarationParent},
1212
lexer::Kind,
1313
list::{NormalList, SeparatedList},
14-
ParserImpl, StatementContext,
14+
ParserImpl,
1515
};
1616

1717
impl<'a> ParserImpl<'a> {
@@ -217,21 +217,11 @@ impl<'a> ParserImpl<'a> {
217217

218218
fn parse_ts_module_block(&mut self) -> Result<Box<'a, TSModuleBlock<'a>>> {
219219
let span = self.start_span();
220-
221-
let mut statements = self.ast.new_vec();
222-
223220
self.expect(Kind::LCurly)?;
224-
225-
while !self.eat(Kind::RCurly) && !self.at(Kind::Eof) {
226-
let stmt = self.parse_ts_module_item()?;
227-
statements.push(stmt);
228-
}
229-
230-
Ok(self.ast.ts_module_block(self.end_span(span), statements))
231-
}
232-
233-
fn parse_ts_module_item(&mut self) -> Result<Statement<'a>> {
234-
self.parse_statement_list_item(StatementContext::StatementList)
221+
let (directives, statements) =
222+
self.parse_directives_and_statements(/* is_top_level */ false)?;
223+
self.expect(Kind::RCurly)?;
224+
Ok(self.ast.ts_module_block(self.end_span(span), directives, statements))
235225
}
236226

237227
pub(crate) fn parse_ts_namespace_or_module_declaration_body(

crates/oxc_transformer/src/typescript/namespace.rs

+24-26
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,15 @@ impl<'a> TypeScript<'a> {
141141
let symbol_id = ctx.generate_uid(&real_name, scope_id, SymbolFlags::FunctionScopedVariable);
142142
let name = self.ctx.ast.new_atom(ctx.symbols().get_name(symbol_id));
143143

144-
let namespace_top_level = match body {
145-
TSModuleDeclarationBody::TSModuleBlock(block) => block.unbox().body,
144+
let directives;
145+
let namespace_top_level;
146+
147+
match body {
148+
TSModuleDeclarationBody::TSModuleBlock(block) => {
149+
let block = block.unbox();
150+
directives = block.directives;
151+
namespace_top_level = block.body;
152+
}
146153
// We handle `namespace X.Y {}` as if it was
147154
// namespace X {
148155
// export namespace Y {}
@@ -152,9 +159,10 @@ impl<'a> TypeScript<'a> {
152159
let export_named_decl =
153160
self.ctx.ast.plain_export_named_declaration_declaration(SPAN, declaration);
154161
let stmt = Statement::ExportNamedDeclaration(export_named_decl);
155-
self.ctx.ast.new_vec_single(stmt)
162+
directives = self.ctx.ast.new_vec();
163+
namespace_top_level = self.ctx.ast.new_vec_single(stmt);
156164
}
157-
};
165+
}
158166

159167
let mut new_stmts = self.ctx.ast.new_vec();
160168

@@ -256,7 +264,15 @@ impl<'a> TypeScript<'a> {
256264
return None;
257265
}
258266

259-
Some(self.transform_namespace(name, real_name, new_stmts, parent_export, scope_id, ctx))
267+
Some(self.transform_namespace(
268+
name,
269+
real_name,
270+
new_stmts,
271+
directives,
272+
parent_export,
273+
scope_id,
274+
ctx,
275+
))
260276
}
261277

262278
// `namespace Foo { }` -> `let Foo; (function (_Foo) { })(Foo || (Foo = {}));`
@@ -280,35 +296,17 @@ impl<'a> TypeScript<'a> {
280296

281297
// `namespace Foo { }` -> `let Foo; (function (_Foo) { })(Foo || (Foo = {}));`
282298
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
283-
#[allow(clippy::needless_pass_by_value)]
299+
#[allow(clippy::needless_pass_by_value, clippy::too_many_arguments)]
284300
fn transform_namespace(
285301
&self,
286302
arg_name: Atom<'a>,
287303
real_name: Atom<'a>,
288-
mut stmts: Vec<'a, Statement<'a>>,
304+
stmts: Vec<'a, Statement<'a>>,
305+
directives: Vec<'a, Directive<'a>>,
289306
parent_export: Option<Expression<'a>>,
290307
scope_id: ScopeId,
291308
ctx: &mut TraverseCtx,
292309
) -> Statement<'a> {
293-
let mut directives = self.ctx.ast.new_vec();
294-
295-
// Check if the namespace has a `use strict` directive
296-
if stmts.first().is_some_and(|stmt| {
297-
matches!(stmt, Statement::ExpressionStatement(es) if
298-
matches!(&es.expression, Expression::StringLiteral(literal) if
299-
literal.value == "use strict")
300-
)
301-
}) {
302-
stmts.remove(0);
303-
let directive = self.ctx.ast.new_atom("use strict");
304-
let directive = Directive {
305-
span: SPAN,
306-
expression: StringLiteral::new(SPAN, directive.clone()),
307-
directive,
308-
};
309-
directives.push(directive);
310-
}
311-
312310
// `(function (_N) { var x; })(N || (N = {}))`;
313311
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
314312
let callee = {

0 commit comments

Comments
 (0)