Skip to content

Commit

Permalink
feat(ast)!: add directives field to TSModuleBlock
Browse files Browse the repository at this point in the history
closes #3564
  • Loading branch information
Boshen committed Jun 22, 2024
1 parent 4456034 commit f7edee5
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 144 deletions.
1 change: 1 addition & 0 deletions crates/oxc_ast/src/ast/ts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>>,
}

Expand Down
3 changes: 2 additions & 1 deletion crates/oxc_ast/src/ast_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
19 changes: 4 additions & 15 deletions crates/oxc_codegen/src/gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"#!");
Expand Down Expand Up @@ -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);
});
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_isolated_declarations/src/declaration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
64 changes: 21 additions & 43 deletions crates/oxc_parser/src/js/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
20 changes: 5 additions & 15 deletions crates/oxc_parser/src/ts/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
js::{FunctionKind, VariableDeclarationContext, VariableDeclarationParent},
lexer::Kind,
list::{NormalList, SeparatedList},
ParserImpl, StatementContext,
ParserImpl,
};

impl<'a> ParserImpl<'a> {
Expand Down Expand Up @@ -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(
Expand Down
50 changes: 24 additions & 26 deletions crates/oxc_transformer/src/typescript/namespace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand All @@ -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();

Expand Down Expand Up @@ -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 = {}));`
Expand All @@ -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 = {
Expand Down
Loading

0 comments on commit f7edee5

Please sign in to comment.